Add address_list parser

embed
Manos Pitsidianakis 2018-07-27 18:01:52 +03:00
parent ea9bdd2074
commit ffbd70e40b
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
3 changed files with 488 additions and 132 deletions

View File

@ -27,7 +27,6 @@ pub use self::attachments::*;
use std::string::String; use std::string::String;
use std::sync::Arc; use std::sync::Arc;
//use std;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::fmt; use std::fmt;
use std::option::Option; use std::option::Option;
@ -35,6 +34,69 @@ use std::option::Option;
use chrono; use chrono;
use chrono::TimeZone; use chrono::TimeZone;
#[derive(Clone, Debug, )]
pub struct GroupAddress {
raw: String,
display_name: StrBuilder,
mailbox_list: Vec<Address>,
}
#[derive(Clone, Debug, )]
pub struct MailboxAddress {
raw: String,
display_name: StrBuilder,
address_spec: StrBuilder,
}
#[derive(Clone, Debug, )]
pub enum Address {
Mailbox(MailboxAddress),
Group(GroupAddress),
}
impl Eq for Address {}
impl PartialEq for Address {
fn eq(&self, other: &Address) -> bool {
match (self, other) {
(Address::Mailbox(_), Address::Group(_)) |
(Address::Group(_), Address::Mailbox(_)) => {
false
},
(Address::Mailbox(s), Address::Mailbox(o)) => {
s.address_spec.display(&s.raw) == o.address_spec.display(&o.raw)
},
(Address::Group(s), Address::Group(o)) => {
s.display_name.display(&s.raw) == o.display_name.display(&o.raw) &&
s.mailbox_list.iter().zip(o.mailbox_list.iter()).fold(true, |b, (s, o)| b && (s == o))
},
}
}
}
impl fmt::Display for Address {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Address::Mailbox(m) if m.display_name.length > 0 => {
write!(f, "{} <{}>", m.display_name.display(&m.raw), m.address_spec.display(&m.raw))
},
Address::Group(g) => {
let attachment_strings: Vec<String> = g.mailbox_list.iter().map(|a| format!("{}", a)).collect();
write!(f, "{}: {}", g.display_name.display(&g.raw), attachment_strings.join(", "))
},
Address::Mailbox(m) => {
write!(f, "{}", m.address_spec.display(&m.raw))
},
}
}
}
/// Helper struct to return slices from a struct field on demand. /// Helper struct to return slices from a struct field on demand.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct StrBuilder { struct StrBuilder {
@ -52,6 +114,14 @@ pub trait StrBuild {
fn val(&self) -> &str; fn val(&self) -> &str;
} }
impl StrBuilder {
fn display<'a>(&self, s: &'a str) -> &'a str {
let offset = self.offset;
let length = self.length;
&s[offset..offset+length]
}
}
/// `MessageID` is accessed through the `StrBuild` trait. /// `MessageID` is accessed through the `StrBuild` trait.
#[derive(Clone)] #[derive(Clone)]
pub struct MessageID(String, StrBuilder); pub struct MessageID(String, StrBuilder);
@ -63,14 +133,14 @@ impl StrBuild for MessageID {
string.to_string(), string.to_string(),
StrBuilder { StrBuilder {
offset: offset, offset: offset,
length: slice.len() + 1, length: slice.len()+ 1,
}, },
) )
} }
fn raw(&self) -> &str { fn raw(&self) -> &str {
let offset = self.1.offset; let offset = self.1.offset;
let length = self.1.length; let length = self.1.length;
&self.0[offset..length] &self.0[offset..offset+length-1]
} }
fn val(&self) -> &str { fn val(&self) -> &str {
&self.0 &self.0
@ -133,8 +203,8 @@ bitflags! {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Envelope { pub struct Envelope {
date: String, date: String,
from: Option<String>, from: Vec<Address>,
to: Option<String>, to: Vec<Address>,
body: Option<Attachment>, body: Option<Attachment>,
subject: Option<String>, subject: Option<String>,
message_id: Option<MessageID>, message_id: Option<MessageID>,
@ -155,8 +225,8 @@ impl Envelope {
pub fn new(token: Box<BackendOpGenerator>) -> Self { pub fn new(token: Box<BackendOpGenerator>) -> Self {
Envelope { Envelope {
date: "".to_string(), date: "".to_string(),
from: None, from: Vec::new(),
to: None, to: Vec::new(),
body: None, body: None,
subject: None, subject: None,
message_id: None, message_id: None,
@ -198,23 +268,23 @@ impl Envelope {
continue; continue;
} }
if name.eq_ignore_ascii_case("to") { if name.eq_ignore_ascii_case("to") {
let parse_result = parser::address_list(value.as_bytes()); let parse_result = parser::rfc2822address_list(value.as_bytes());
let value = if parse_result.is_done() { let value = if parse_result.is_done() {
parse_result.to_full_result().unwrap() parse_result.to_full_result().unwrap()
} else { } else {
"".to_string() Vec::new()
}; };
self.set_to(value); self.set_to(value);
} else if name.eq_ignore_ascii_case("from") { } else if name.eq_ignore_ascii_case("from") {
let parse_result = parser::address_list(value.as_bytes()); let parse_result = parser::rfc2822address_list(value.as_bytes());
let value = if parse_result.is_done() { let value = if parse_result.is_done() {
parse_result.to_full_result().unwrap() parse_result.to_full_result().unwrap()
} else { } else {
"".to_string() Vec::new()
}; };
self.set_from(value); self.set_from(value);
} else if name.eq_ignore_ascii_case("subject") { } else if name.eq_ignore_ascii_case("subject") {
let parse_result = parser::subject(value.trim().as_bytes()); let parse_result = parser::phrase(value.trim().as_bytes());
let value = if parse_result.is_done() { let value = if parse_result.is_done() {
parse_result.to_full_result().unwrap() parse_result.to_full_result().unwrap()
} else { } else {
@ -269,17 +339,20 @@ impl Envelope {
pub fn date_as_str(&self) -> &str { pub fn date_as_str(&self) -> &str {
&self.date &self.date
} }
pub fn from(&self) -> &str { pub fn from(&self) -> &Vec<Address> {
match self.from { &self.from
Some(ref s) => s,
None => "",
}
} }
pub fn to(&self) -> &str {
match self.to { pub fn from_to_string(&self) -> String {
Some(ref s) => s, let _strings: Vec<String> = self.from.iter().map(|a| format!("{}", a)).collect();
None => "", _strings.join(", ")
} }
pub fn to(&self) -> &Vec<Address> {
&self.to
}
pub fn to_to_string(&self) -> String {
let _strings: Vec<String> = self.to.iter().map(|a| format!("{}", a)).collect();
_strings.join(", ")
} }
pub fn body(&self) -> Attachment { pub fn body(&self) -> Attachment {
let mut operation = self.operation_token.generate(); let mut operation = self.operation_token.generate();
@ -338,11 +411,11 @@ impl Envelope {
fn set_date(&mut self, new_val: String) -> () { fn set_date(&mut self, new_val: String) -> () {
self.date = new_val; self.date = new_val;
} }
fn set_from(&mut self, new_val: String) -> () { fn set_from(&mut self, new_val: Vec<Address>) -> () {
self.from = Some(new_val); self.from = new_val;
} }
fn set_to(&mut self, new_val: String) -> () { fn set_to(&mut self, new_val: Vec<Address>) -> () {
self.to = Some(new_val); self.to = new_val;
} }
fn set_in_reply_to(&mut self, new_val: &str) -> () { fn set_in_reply_to(&mut self, new_val: &str) -> () {
let slice = match parser::message_id(new_val.as_bytes()).to_full_result() { let slice = match parser::message_id(new_val.as_bytes()).to_full_result() {

View File

@ -26,6 +26,16 @@ use nom::{is_hex_digit, le_u8};
use nom::{ErrorKind, IResult, Needed}; use nom::{ErrorKind, IResult, Needed};
use nom::{Compare, CompareResult}; use nom::{Compare, CompareResult};
use encoding::{DecoderTrap, Encoding}; use encoding::{DecoderTrap, Encoding};
use super::*;
macro_rules! is_whitespace {
($var:ident) => (
$var == b' ' && $var == b'\t' && $var == b'\n' && $var == b'\r'
);
($var:expr) => (
$var == b' ' && $var == b'\t' && $var == b'\n' && $var == b'\r'
);
}
fn quoted_printable_byte(input: &[u8]) -> IResult<&[u8], u8> { fn quoted_printable_byte(input: &[u8]) -> IResult<&[u8], u8> {
if input.is_empty() || input.len() < 3 { if input.is_empty() || input.len() < 3 {
@ -259,6 +269,205 @@ named!(ascii_token<String>, do_parse!(
String::from_utf8_lossy(word).into_owned() String::from_utf8_lossy(word).into_owned()
} ))); } )));
fn display_addr(input: &[u8]) -> IResult<&[u8], Address> {
if input.is_empty() || input.len() < 3 {
IResult::Incomplete(Needed::Size(1))
} else if !is_whitespace!(input[0]) {
let mut display_name = StrBuilder {
offset: 0,
length: 0,
};
let mut flag = false;
for (i, b) in input[0..].iter().enumerate() {
if *b == b'<' {
display_name.length = if i != 0 { i-1 } else { 0 };
flag = true;
break;
}
}
if !flag {
return IResult::Error(error_code!(ErrorKind::Custom(43)));
}
let mut end = input.len();
let mut flag = false;
for (i, b) in input[display_name.length+2..].iter().enumerate() {
if *b == b'@' {
flag = true;
}
if *b == b'>' {
end = i;
break;
}
}
if flag {
let mut address_spec = StrBuilder {
offset: display_name.length + 2,
length: end,
};
match phrase(&input[0..end+display_name.length+3]) {
IResult::Error(e) => IResult::Error(e),
IResult::Incomplete(i) => IResult::Incomplete(i),
IResult::Done(rest, raw) => {
display_name.length = raw.find('<').unwrap();
address_spec.offset = display_name.length + 1;
address_spec.length = raw.len() - display_name.length - 2;
IResult::Done(rest, Address::Mailbox(MailboxAddress {
raw: raw,
display_name: display_name,
address_spec: address_spec,
}))
},
}
} else {
IResult::Error(error_code!(ErrorKind::Custom(43)))
}
} else {
IResult::Error(error_code!(ErrorKind::Custom(43)))
}
}
fn addr_spec(input: &[u8]) -> IResult<&[u8], Address> {
if input.is_empty() || input.len() < 3 {
IResult::Incomplete(Needed::Size(1))
} else if !is_whitespace!(input[0]) {
let mut end = input[1..].len();
let mut flag = false;
for (i, b) in input[1..].iter().enumerate() {
if *b == b'@' {
flag = true;
}
if is_whitespace!(*b) {
end = i;
break;
}
}
if flag {
IResult::Done(&input[end..], Address::Mailbox(MailboxAddress {
raw: String::from_utf8_lossy(&input[0..end+1]).to_string(),
display_name: StrBuilder {
offset: 0,
length: 0,
},
address_spec: StrBuilder {
offset: 0,
length: input[0..end+1].len(),
},
}))
} else {
IResult::Error(error_code!(ErrorKind::Custom(43)))
}
} else {
IResult::Error(error_code!(ErrorKind::Custom(42)))
}
}
named!(mailbox<Address>, ws!(alt_complete!(
display_addr |
addr_spec
)));
named!(mailbox_list<Vec<Address>>, many0!(mailbox));
#[test]
fn test_mailbox() {
{
let s = b"epilys@postretch";
let r = mailbox(s).unwrap().1;
match r {
Address::Mailbox(ref m) => {
println!("----\n`{}`, `{}`\n----", m.display_name.display(&m.raw), m.address_spec.display(&m.raw));
},
_ => {},
}
}
let s = b"Manos <epilys@postretch>";
eprintln!("{:?}", display_addr(s).unwrap());
let r = display_addr(s).unwrap().1;
match r {
Address::Mailbox(ref m) => {
println!("----\n`{}`, `{}`\n----", m.display_name.display(&m.raw), m.address_spec.display(&m.raw));
},
_ => {},
}
}
//named!(group_t<GroupAddress>, ws!( do_parse!(
// display_name: take_until1!(":") >>
// mailbox_list: many0!(mailbox) >>
// end: is_a!(";") >>
// ({
//
// })
// )));
//
fn group(input: &[u8]) -> IResult<&[u8], Address> {
let mut flag = false;
let mut dlength = 0;
for (i, b) in input.iter().enumerate() {
if *b == b':' {
flag = true;
dlength = i;
break;
}
}
if !flag {
return IResult::Error(error_code!(ErrorKind::Custom(43)));
}
match mailbox_list(&input[dlength..]) {
IResult::Error(e) => {
return IResult::Error(e);
}
IResult::Done(rest, vec) => {
let size: usize = (rest.as_ptr() as usize) - ((&input[0..] as &[u8]).as_ptr() as usize);
return IResult::Done(rest, Address::Group(GroupAddress {
raw: String::from_utf8(input[0..size].to_vec()).unwrap(),
display_name: StrBuilder {
offset: 0,
length: dlength,
},
mailbox_list: vec,
}));
},
IResult::Incomplete(i) => {
return IResult::Incomplete(i);
},
}
}
named!(address<Address>, ws!(
alt_complete!(mailbox | group)));
#[test]
fn test_address() {
let s = b"Manos Pitsidianakis <el13635@mail.ntua.gr>,
qemu-devel <qemu-devel@nongnu.org>, qemu-block <qemu-block@nongnu.org>,
Alberto Garcia <berto@igalia.com>, Stefan Hajnoczi <stefanha@redhat.com>";
println!("{:?}", rfc2822address_list(s).unwrap());
}
named!(pub rfc2822address_list<Vec<Address>>, ws!(
separated_list!(is_a!(","), address)
));
named!(pub address_list<String>, ws!(do_parse!( named!(pub address_list<String>, ws!(do_parse!(
list: alt_complete!( encoded_word_list | ascii_token) >> list: alt_complete!( encoded_word_list | ascii_token) >>
( { ( {
@ -278,7 +487,8 @@ named!(pub address_list<String>, ws!(do_parse!(
} ) } )
))); )));
named!(pub subject<String>, ws!(do_parse!(
named!(pub phrase<String>, ws!(do_parse!(
list: many0!(alt_complete!( encoded_word_list | ascii_token)) >> list: many0!(alt_complete!( encoded_word_list | ascii_token)) >>
( { ( {
let string_len = list.iter().fold(0, |mut acc, x| { acc+=x.len(); acc }) + list.len() - 1; let string_len = list.iter().fold(0, |mut acc, x| { acc+=x.len(); acc }) + list.len() - 1;
@ -298,52 +508,52 @@ named!(pub subject<String>, ws!(do_parse!(
))); )));
#[test] #[test]
fn test_subject() { fn test_phrase() {
let subject_s = "list.free.de mailing list memberships reminder".as_bytes(); let phrase_s = "list.free.de mailing list memberships reminder".as_bytes();
assert_eq!( assert_eq!(
( (
&b""[..], &b""[..],
"list.free.de mailing list memberships reminder".to_string() "list.free.de mailing list memberships reminder".to_string()
), ),
subject(subject_s).unwrap() phrase(phrase_s).unwrap()
); );
let subject_s = "=?UTF-8?B?zp3Orc6/IM+Az4HOv8+Dz4nPgM65zrrPjCDOvM6uzr3Phc68zrEgzrHPhs6v?= =?UTF-8?B?z4fOuM63?=".as_bytes(); let phrase_s = "=?UTF-8?B?zp3Orc6/IM+Az4HOv8+Dz4nPgM65zrrPjCDOvM6uzr3Phc68zrEgzrHPhs6v?= =?UTF-8?B?z4fOuM63?=".as_bytes();
assert_eq!( assert_eq!(
( (
&b""[..], &b""[..],
"Νέο προσωπικό μήνυμα αφίχθη".to_string() "Νέο προσωπικό μήνυμα αφίχθη".to_string()
), ),
subject(subject_s).unwrap() phrase(phrase_s).unwrap()
); );
let subject_s = "=?utf-8?B?bW9vZGxlOiDOsc69zrHPg866z4zPgM63z4POtyDOv868zqzOtM6xz4Igz4M=?= =?utf-8?B?z4XOts63z4TOrs+DzrXPic69?=".as_bytes(); let phrase_s = "=?utf-8?B?bW9vZGxlOiDOsc69zrHPg866z4zPgM63z4POtyDOv868zqzOtM6xz4Igz4M=?= =?utf-8?B?z4XOts63z4TOrs+DzrXPic69?=".as_bytes();
assert_eq!( assert_eq!(
( (
&b""[..], &b""[..],
"moodle: ανασκόπηση ομάδας συζητήσεων".to_string() "moodle: ανασκόπηση ομάδας συζητήσεων".to_string()
), ),
subject(subject_s).unwrap() phrase(phrase_s).unwrap()
); );
let subject_s = let phrase_s =
"=?UTF-8?B?zp3Orc6/IM+Az4HOv8+Dz4nPgM65zrrPjCDOvM6uzr3Phc68zrEgzrHPhs6v?=".as_bytes(); "=?UTF-8?B?zp3Orc6/IM+Az4HOv8+Dz4nPgM65zrrPjCDOvM6uzr3Phc68zrEgzrHPhs6v?=".as_bytes();
assert_eq!( assert_eq!(
"Νέο προσωπικό μήνυμα αφί".to_string(), "Νέο προσωπικό μήνυμα αφί".to_string(),
from_utf8(&encoded_word(subject_s).to_full_result().unwrap()).unwrap() from_utf8(&encoded_word(phrase_s).to_full_result().unwrap()).unwrap()
); );
let subject_s = "=?UTF-8?Q?=CE=A0=CF=81=CF=8C=CF=83=CE=B8=CE=B5?=".as_bytes(); let phrase_s = "=?UTF-8?Q?=CE=A0=CF=81=CF=8C=CF=83=CE=B8=CE=B5?=".as_bytes();
assert_eq!( assert_eq!(
"Πρόσθε".to_string(), "Πρόσθε".to_string(),
from_utf8(&encoded_word(subject_s).to_full_result().unwrap()).unwrap() from_utf8(&encoded_word(phrase_s).to_full_result().unwrap()).unwrap()
); );
let subject_s = "=?iso-8859-7?B?UmU6INDx/OLr5+zhIOzlIPTn7SDh9fHp4e3eIOLh8eTp4Q==?=".as_bytes(); let phrase_s = "=?iso-8859-7?B?UmU6INDx/OLr5+zhIOzlIPTn7SDh9fHp4e3eIOLh8eTp4Q==?=".as_bytes();
assert_eq!( assert_eq!(
"Re: Πρόβλημα με την αυριανή βαρδια".to_string(), "Re: Πρόβλημα με την αυριανή βαρδια".to_string(),
from_utf8(&encoded_word(subject_s).to_full_result().unwrap()).unwrap() from_utf8(&encoded_word(phrase_s).to_full_result().unwrap()).unwrap()
); );
let subject_s = "=?UTF-8?Q?=CE=A0=CF=81=CF=8C=CF=83=CE=B8=CE=B5?= let phrase_s = "=?UTF-8?Q?=CE=A0=CF=81=CF=8C=CF=83=CE=B8=CE=B5?=
=?UTF-8?Q?=CF=84=CE=B7_=CE=B5=CE=BE=CE=B5=CF=84?= =?UTF-8?Q?=CF=84=CE=B7_=CE=B5=CE=BE=CE=B5=CF=84?=
=?UTF-8?Q?=CE=B1=CF=83=CF=84=CE=B9=CE=BA=CE=AE?=" =?UTF-8?Q?=CE=B1=CF=83=CF=84=CE=B9=CE=BA=CE=AE?="
.as_bytes(); .as_bytes();
@ -352,7 +562,7 @@ fn test_subject() {
&b""[..], &b""[..],
"Πρόσθετη εξεταστική".to_string() "Πρόσθετη εξεταστική".to_string()
), ),
subject(subject_s).unwrap() phrase(phrase_s).unwrap()
); );
} }
fn eat_comments(input: &str) -> String { fn eat_comments(input: &str) -> String {
@ -388,7 +598,7 @@ fn test_eat_comments() {
* *
* We should use a custom parser here*/ * We should use a custom parser here*/
pub fn date(input: &str) -> Option<chrono::DateTime<chrono::FixedOffset>> { pub fn date(input: &str) -> Option<chrono::DateTime<chrono::FixedOffset>> {
let parsed_result = subject(eat_comments(input).as_bytes()) let parsed_result = phrase(eat_comments(input).as_bytes())
.to_full_result() .to_full_result()
.unwrap() .unwrap()
.replace("-", "+"); .replace("-", "+");

View File

@ -1,10 +1,9 @@
use super::*; use super::*;
use linkify::{LinkFinder, Link}; use linkify::{Link, LinkFinder};
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
use mime_apps::query_default_app; use mime_apps::query_default_app;
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
enum ViewMode { enum ViewMode {
Normal, Normal,
@ -35,8 +34,11 @@ pub struct MailView {
} }
impl MailView { impl MailView {
pub fn new(coordinates: (usize, usize, usize), pager: Option<Pager>, subview: Option<Box<MailView>>) -> Self { pub fn new(
coordinates: (usize, usize, usize),
pager: Option<Pager>,
subview: Option<Box<MailView>>,
) -> Self {
MailView { MailView {
coordinates: coordinates, coordinates: coordinates,
pager: pager, pager: pager,
@ -49,15 +51,20 @@ impl MailView {
} }
} }
impl Component for MailView { impl Component for MailView {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
let upper_left = upper_left!(area); let upper_left = upper_left!(area);
let bottom_right = bottom_right!(area); let bottom_right = bottom_right!(area);
let (envelope_idx, y): (usize, usize) = { let (envelope_idx, y): (usize, usize) = {
let threaded = context.accounts[self.coordinates.0].runtime_settings.threaded; let threaded = context.accounts[self.coordinates.0]
let mailbox = &mut context.accounts[self.coordinates.0][self.coordinates.1].as_ref().unwrap().as_ref().unwrap(); .runtime_settings
.threaded;
let mailbox = &mut context.accounts[self.coordinates.0][self.coordinates.1]
.as_ref()
.unwrap()
.as_ref()
.unwrap();
let envelope_idx: usize = if threaded { let envelope_idx: usize = if threaded {
mailbox.threaded_mail(self.coordinates.2) mailbox.threaded_mail(self.coordinates.2)
} else { } else {
@ -66,98 +73,125 @@ impl Component for MailView {
let envelope: &Envelope = &mailbox.collection[envelope_idx]; let envelope: &Envelope = &mailbox.collection[envelope_idx];
let (x,y) = write_string_to_grid(&format!("Date: {}", envelope.date_as_str()), let (x, y) = write_string_to_grid(
grid, &format!("Date: {}", envelope.date_as_str()),
Color::Byte(33), grid,
Color::Default, Color::Byte(33),
area, Color::Default,
true); area,
true,
);
for x in x..=get_x(bottom_right) { for x in x..=get_x(bottom_right) {
grid[(x, y)].set_ch(' '); grid[(x, y)].set_ch(' ');
grid[(x, y)].set_bg(Color::Default); grid[(x, y)].set_bg(Color::Default);
grid[(x, y)].set_fg(Color::Default); grid[(x, y)].set_fg(Color::Default);
} }
let (x,y) = write_string_to_grid(&format!("From: {}", envelope.from()), let (x, y) = write_string_to_grid(
grid, &format!("From: {}", envelope.from_to_string()),
Color::Byte(33), grid,
Color::Default, Color::Byte(33),
(set_y(upper_left, y+1), bottom_right), Color::Default,
true); (set_y(upper_left, y + 1), bottom_right),
true,
);
for x in x..=get_x(bottom_right) { for x in x..=get_x(bottom_right) {
grid[(x, y)].set_ch(' '); grid[(x, y)].set_ch(' ');
grid[(x, y)].set_bg(Color::Default); grid[(x, y)].set_bg(Color::Default);
grid[(x, y)].set_fg(Color::Default); grid[(x, y)].set_fg(Color::Default);
} }
let (x,y) = write_string_to_grid(&format!("To: {}", envelope.to()), let (x, y) = write_string_to_grid(
grid, &format!("To: {}", envelope.to_to_string()),
Color::Byte(33), grid,
Color::Default, Color::Byte(33),
(set_y(upper_left, y+1), bottom_right), Color::Default,
true); (set_y(upper_left, y + 1), bottom_right),
true,
);
for x in x..=get_x(bottom_right) { for x in x..=get_x(bottom_right) {
grid[(x, y)].set_ch(' '); grid[(x, y)].set_ch(' ');
grid[(x, y)].set_bg(Color::Default); grid[(x, y)].set_bg(Color::Default);
grid[(x, y)].set_fg(Color::Default); grid[(x, y)].set_fg(Color::Default);
} }
let (x,y) = write_string_to_grid(&format!("Subject: {}", envelope.subject()), let (x, y) = write_string_to_grid(
grid, &format!("Subject: {}", envelope.subject()),
Color::Byte(33), grid,
Color::Default, Color::Byte(33),
(set_y(upper_left, y+1), bottom_right), Color::Default,
true); (set_y(upper_left, y + 1), bottom_right),
true,
);
for x in x..=get_x(bottom_right) { for x in x..=get_x(bottom_right) {
grid[(x, y)].set_ch(' '); grid[(x, y)].set_ch(' ');
grid[(x, y)].set_bg(Color::Default); grid[(x, y)].set_bg(Color::Default);
grid[(x, y)].set_fg(Color::Default); grid[(x, y)].set_fg(Color::Default);
} }
let (x, y) = write_string_to_grid(&format!("Message-ID: {}", envelope.message_id_raw()), let (x, y) = write_string_to_grid(
grid, &format!("Message-ID: {}", envelope.message_id_raw()),
Color::Byte(33), grid,
Color::Default, Color::Byte(33),
(set_y(upper_left, y+1), bottom_right), Color::Default,
true); (set_y(upper_left, y + 1), bottom_right),
true,
);
for x in x..=get_x(bottom_right) { for x in x..=get_x(bottom_right) {
grid[(x, y)].set_ch(' '); grid[(x, y)].set_ch(' ');
grid[(x, y)].set_bg(Color::Default); grid[(x, y)].set_bg(Color::Default);
grid[(x, y)].set_fg(Color::Default); grid[(x, y)].set_fg(Color::Default);
} }
clear_area(grid, clear_area(grid, (set_y(upper_left, y + 1), set_y(bottom_right, y + 2)));
(set_y(upper_left, y+1), set_y(bottom_right, y+2))); context
context.dirty_areas.push_back((upper_left, set_y(bottom_right, y+1))); .dirty_areas
.push_back((upper_left, set_y(bottom_right, y + 1)));
(envelope_idx, y + 1) (envelope_idx, y + 1)
}; };
if self.dirty { if self.dirty {
let buf = { let buf = {
let mailbox_idx = self.coordinates; // coordinates are mailbox idxs let mailbox_idx = self.coordinates; // coordinates are mailbox idxs
let mailbox = &mut context.accounts[mailbox_idx.0][mailbox_idx.1].as_ref().unwrap().as_ref().unwrap(); let mailbox = &mut context.accounts[mailbox_idx.0][mailbox_idx.1]
let envelope : &Envelope = &mailbox.collection[envelope_idx]; .as_ref()
.unwrap()
.as_ref()
.unwrap();
let envelope: &Envelope = &mailbox.collection[envelope_idx];
let finder = LinkFinder::new(); let finder = LinkFinder::new();
let mut text = match self.mode { let mut text = match self.mode {
ViewMode::Url => { ViewMode::Url => {
let mut t = envelope.body().text().to_string(); let mut t = envelope.body().text().to_string();
for (lidx, l) in finder.links(&envelope.body().text()).enumerate() { for (lidx, l) in finder.links(&envelope.body().text()).enumerate() {
t.insert_str(l.start()+(lidx*3), &format!("[{}]", lidx)); t.insert_str(l.start() + (lidx * 3), &format!("[{}]", lidx));
} }
if envelope.body().count_attachments() > 1 { if envelope.body().count_attachments() > 1 {
t = envelope.body().attachments().iter().enumerate().fold(t, |mut s, (idx, a)| { s.push_str(&format!("[{}] {}\n\n", idx, a)); s }); t = envelope.body().attachments().iter().enumerate().fold(
t,
|mut s, (idx, a)| {
s.push_str(&format!("[{}] {}\n\n", idx, a));
s
},
);
} }
t t
}, }
ViewMode::Attachment(aidx) => { ViewMode::Attachment(aidx) => {
let attachments = envelope.body().attachments(); let attachments = envelope.body().attachments();
let mut ret = format!("Viewing attachment. Press `r` to return \n"); let mut ret = format!("Viewing attachment. Press `r` to return \n");
ret.push_str(&attachments[aidx].text()); ret.push_str(&attachments[aidx].text());
ret ret
}, }
_ => { _ => {
let mut t = envelope.body().text().to_string(); let mut t = envelope.body().text().to_string();
if envelope.body().count_attachments() > 1 { if envelope.body().count_attachments() > 1 {
t = envelope.body().attachments().iter().enumerate().fold(t, |mut s, (idx, a)| { s.push_str(&format!("[{}] {}\n\n", idx, a)); s }); t = envelope.body().attachments().iter().enumerate().fold(
t,
|mut s, (idx, a)| {
s.push_str(&format!("[{}] {}\n\n", idx, a));
s
},
);
} }
t t
}, }
}; };
let mut buf = CellBuffer::from(&text); let mut buf = CellBuffer::from(&text);
match self.mode { match self.mode {
@ -172,10 +206,10 @@ impl Component for MailView {
buf[(l.start() + shift - 3, 0)].set_fg(Color::Byte(226)); buf[(l.start() + shift - 3, 0)].set_fg(Color::Byte(226));
} }
// Each Cell represents one char so next line will be: // Each Cell represents one char so next line will be:
shift += r.chars().count()+1; shift += r.chars().count() + 1;
} }
}, }
_ => {}, _ => {}
} }
buf buf
}; };
@ -188,29 +222,41 @@ impl Component for MailView {
self.pager = Some(Pager::from_buf(buf, cursor_pos)); self.pager = Some(Pager::from_buf(buf, cursor_pos));
self.dirty = false; self.dirty = false;
} }
self.pager.as_mut().map(|p| p.draw(grid, (set_y(upper_left, y + 1),bottom_right), context)); self.pager
.as_mut()
.map(|p| p.draw(grid, (set_y(upper_left, y + 1), bottom_right), context));
} }
fn process_event(&mut self, event: &UIEvent, context: &mut Context) { fn process_event(&mut self, event: &UIEvent, context: &mut Context) {
match event.event_type { match event.event_type {
UIEventType::Input(Key::Esc) => { UIEventType::Input(Key::Esc) => {
self.cmd_buf.clear(); self.cmd_buf.clear();
}, }
UIEventType::Input(Key::Char(c)) if c >= '0' && c <= '9' => { //TODO:this should be an Action UIEventType::Input(Key::Char(c)) if c >= '0' && c <= '9' => {
//TODO:this should be an Action
self.cmd_buf.push(c); self.cmd_buf.push(c);
}, }
UIEventType::Input(Key::Char('r')) if self.mode.is_attachment() => { //TODO:one quit shortcut? UIEventType::Input(Key::Char('r')) if self.mode.is_attachment() => {
//TODO:one quit shortcut?
self.mode = ViewMode::Normal; self.mode = ViewMode::Normal;
self.dirty = true; self.dirty = true;
}, }
UIEventType::Input(Key::Char('a')) if self.cmd_buf.len() > 0 && self.mode == ViewMode::Normal => { //TODO:this should be an Action UIEventType::Input(Key::Char('a'))
if self.cmd_buf.len() > 0 && self.mode == ViewMode::Normal =>
{
//TODO:this should be an Action
let lidx = self.cmd_buf.parse::<usize>().unwrap(); let lidx = self.cmd_buf.parse::<usize>().unwrap();
self.cmd_buf.clear(); self.cmd_buf.clear();
{ {
let threaded = context.accounts[self.coordinates.0].runtime_settings.threaded; let threaded = context.accounts[self.coordinates.0]
let mailbox = &mut context.accounts[self.coordinates.0][self.coordinates.1].as_ref().unwrap().as_ref().unwrap(); .runtime_settings
.threaded;
let mailbox = &mut context.accounts[self.coordinates.0][self.coordinates.1]
.as_ref()
.unwrap()
.as_ref()
.unwrap();
let envelope_idx: usize = if threaded { let envelope_idx: usize = if threaded {
mailbox.threaded_mail(self.coordinates.2) mailbox.threaded_mail(self.coordinates.2)
} else { } else {
@ -223,11 +269,16 @@ impl Component for MailView {
ContentType::Text => { ContentType::Text => {
self.mode = ViewMode::Attachment(lidx); self.mode = ViewMode::Attachment(lidx);
self.dirty = true; self.dirty = true;
}, }
ContentType::Multipart { .. } => { ContentType::Multipart { .. } => {
context.replies.push_back(UIEvent { id: 0, event_type: UIEventType::StatusNotification(format!("Multipart attachments are not supported yet.")) }); context.replies.push_back(UIEvent {
id: 0,
event_type: UIEventType::StatusNotification(format!(
"Multipart attachments are not supported yet."
)),
});
return; return;
}, }
ContentType::Unsupported { .. } => { ContentType::Unsupported { .. } => {
let attachment_type = u.mime_type(); let attachment_type = u.mime_type();
let binary = query_default_app(&attachment_type); let binary = query_default_app(&attachment_type);
@ -240,28 +291,45 @@ impl Component for MailView {
.spawn() .spawn()
.expect(&format!("Failed to start {}", binary.display())); .expect(&format!("Failed to start {}", binary.display()));
} else { } else {
context.replies.push_back(UIEvent { id: 0, event_type: UIEventType::StatusNotification(format!("Couldn't find a default application for type {}", attachment_type)) }); context.replies.push_back(UIEvent {
id: 0,
event_type: UIEventType::StatusNotification(format!(
"Couldn't find a default application for type {}",
attachment_type
)),
});
return; return;
} }
}
},
} }
} else { } else {
context.replies.push_back(UIEvent { id: 0, event_type: UIEventType::StatusNotification(format!("Attachment `{}` not found.", lidx)) }); context.replies.push_back(UIEvent {
id: 0,
event_type: UIEventType::StatusNotification(format!(
"Attachment `{}` not found.",
lidx
)),
});
return; return;
} }
}; };
}
UIEventType::Input(Key::Char('g'))
}, if self.cmd_buf.len() > 0 && self.mode == ViewMode::Url =>
UIEventType::Input(Key::Char('g')) if self.cmd_buf.len() > 0 && self.mode == ViewMode::Url => { //TODO:this should be an Action {
//TODO:this should be an Action
let lidx = self.cmd_buf.parse::<usize>().unwrap(); let lidx = self.cmd_buf.parse::<usize>().unwrap();
self.cmd_buf.clear(); self.cmd_buf.clear();
let url = { let url = {
let threaded = context.accounts[self.coordinates.0].runtime_settings.threaded; let threaded = context.accounts[self.coordinates.0]
let mailbox = &mut context.accounts[self.coordinates.0][self.coordinates.1].as_ref().unwrap().as_ref().unwrap(); .runtime_settings
.threaded;
let mailbox = &mut context.accounts[self.coordinates.0][self.coordinates.1]
.as_ref()
.unwrap()
.as_ref()
.unwrap();
let envelope_idx: usize = if threaded { let envelope_idx: usize = if threaded {
mailbox.threaded_mail(self.coordinates.2) mailbox.threaded_mail(self.coordinates.2)
} else { } else {
@ -275,33 +343,37 @@ impl Component for MailView {
if let Some(u) = links.get(lidx) { if let Some(u) = links.get(lidx) {
u.as_str().to_string() u.as_str().to_string()
} else { } else {
context.replies.push_back(UIEvent { id: 0, event_type: UIEventType::StatusNotification(format!("Link `{}` not found.", lidx)) }); context.replies.push_back(UIEvent {
id: 0,
event_type: UIEventType::StatusNotification(format!(
"Link `{}` not found.",
lidx
)),
});
return; return;
} }
}; };
Command::new("xdg-open") Command::new("xdg-open")
.arg(url) .arg(url)
.stdin(Stdio::piped()) .stdin(Stdio::piped())
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.spawn() .spawn()
.expect("Failed to start xdg_open"); .expect("Failed to start xdg_open");
}
}, UIEventType::Input(Key::Char('u')) => {
UIEventType::Input(Key::Char('u')) => { //TODO:this should be an Action //TODO:this should be an Action
match self.mode { match self.mode {
ViewMode::Normal => { self.mode = ViewMode::Url }, ViewMode::Normal => self.mode = ViewMode::Url,
ViewMode::Url => { self.mode = ViewMode::Normal }, ViewMode::Url => self.mode = ViewMode::Normal,
_ => {}, _ => {}
} }
self.dirty = true; self.dirty = true;
}, }
_ => {}, _ => {}
} }
if let Some(ref mut sub) = self.subview { if let Some(ref mut sub) = self.subview {
sub.process_event(event, context); sub.process_event(event, context);
} else { } else {
if let Some(ref mut p) = self.pager { if let Some(ref mut p) = self.pager {
p.process_event(event, context); p.process_event(event, context);
@ -309,7 +381,8 @@ impl Component for MailView {
} }
} }
fn is_dirty(&self) -> bool { fn is_dirty(&self) -> bool {
self.dirty || self.pager.as_ref().map(|p| p.is_dirty()).unwrap_or(false) || self.dirty
self.subview.as_ref().map(|p| p.is_dirty()).unwrap_or(false) || self.pager.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
|| self.subview.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
} }
} }