Add address_list parser
parent
ea9bdd2074
commit
ffbd70e40b
|
@ -27,7 +27,6 @@ pub use self::attachments::*;
|
|||
|
||||
use std::string::String;
|
||||
use std::sync::Arc;
|
||||
//use std;
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt;
|
||||
use std::option::Option;
|
||||
|
@ -35,6 +34,69 @@ use std::option::Option;
|
|||
use chrono;
|
||||
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.
|
||||
#[derive(Clone, Debug)]
|
||||
struct StrBuilder {
|
||||
|
@ -52,6 +114,14 @@ pub trait StrBuild {
|
|||
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.
|
||||
#[derive(Clone)]
|
||||
pub struct MessageID(String, StrBuilder);
|
||||
|
@ -63,14 +133,14 @@ impl StrBuild for MessageID {
|
|||
string.to_string(),
|
||||
StrBuilder {
|
||||
offset: offset,
|
||||
length: slice.len() + 1,
|
||||
length: slice.len()+ 1,
|
||||
},
|
||||
)
|
||||
}
|
||||
fn raw(&self) -> &str {
|
||||
let offset = self.1.offset;
|
||||
let length = self.1.length;
|
||||
&self.0[offset..length]
|
||||
&self.0[offset..offset+length-1]
|
||||
}
|
||||
fn val(&self) -> &str {
|
||||
&self.0
|
||||
|
@ -133,8 +203,8 @@ bitflags! {
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct Envelope {
|
||||
date: String,
|
||||
from: Option<String>,
|
||||
to: Option<String>,
|
||||
from: Vec<Address>,
|
||||
to: Vec<Address>,
|
||||
body: Option<Attachment>,
|
||||
subject: Option<String>,
|
||||
message_id: Option<MessageID>,
|
||||
|
@ -155,8 +225,8 @@ impl Envelope {
|
|||
pub fn new(token: Box<BackendOpGenerator>) -> Self {
|
||||
Envelope {
|
||||
date: "".to_string(),
|
||||
from: None,
|
||||
to: None,
|
||||
from: Vec::new(),
|
||||
to: Vec::new(),
|
||||
body: None,
|
||||
subject: None,
|
||||
message_id: None,
|
||||
|
@ -198,23 +268,23 @@ impl Envelope {
|
|||
continue;
|
||||
}
|
||||
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() {
|
||||
parse_result.to_full_result().unwrap()
|
||||
} else {
|
||||
"".to_string()
|
||||
Vec::new()
|
||||
};
|
||||
self.set_to(value);
|
||||
} 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() {
|
||||
parse_result.to_full_result().unwrap()
|
||||
} else {
|
||||
"".to_string()
|
||||
Vec::new()
|
||||
};
|
||||
self.set_from(value);
|
||||
} 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() {
|
||||
parse_result.to_full_result().unwrap()
|
||||
} else {
|
||||
|
@ -269,17 +339,20 @@ impl Envelope {
|
|||
pub fn date_as_str(&self) -> &str {
|
||||
&self.date
|
||||
}
|
||||
pub fn from(&self) -> &str {
|
||||
match self.from {
|
||||
Some(ref s) => s,
|
||||
None => "",
|
||||
}
|
||||
pub fn from(&self) -> &Vec<Address> {
|
||||
&self.from
|
||||
}
|
||||
pub fn to(&self) -> &str {
|
||||
match self.to {
|
||||
Some(ref s) => s,
|
||||
None => "",
|
||||
}
|
||||
|
||||
pub fn from_to_string(&self) -> String {
|
||||
let _strings: Vec<String> = self.from.iter().map(|a| format!("{}", a)).collect();
|
||||
_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 {
|
||||
let mut operation = self.operation_token.generate();
|
||||
|
@ -338,11 +411,11 @@ impl Envelope {
|
|||
fn set_date(&mut self, new_val: String) -> () {
|
||||
self.date = new_val;
|
||||
}
|
||||
fn set_from(&mut self, new_val: String) -> () {
|
||||
self.from = Some(new_val);
|
||||
fn set_from(&mut self, new_val: Vec<Address>) -> () {
|
||||
self.from = new_val;
|
||||
}
|
||||
fn set_to(&mut self, new_val: String) -> () {
|
||||
self.to = Some(new_val);
|
||||
fn set_to(&mut self, new_val: Vec<Address>) -> () {
|
||||
self.to = new_val;
|
||||
}
|
||||
fn set_in_reply_to(&mut self, new_val: &str) -> () {
|
||||
let slice = match parser::message_id(new_val.as_bytes()).to_full_result() {
|
||||
|
|
|
@ -26,6 +26,16 @@ use nom::{is_hex_digit, le_u8};
|
|||
use nom::{ErrorKind, IResult, Needed};
|
||||
use nom::{Compare, CompareResult};
|
||||
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> {
|
||||
if input.is_empty() || input.len() < 3 {
|
||||
|
@ -259,6 +269,205 @@ named!(ascii_token<String>, do_parse!(
|
|||
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!(
|
||||
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)) >>
|
||||
( {
|
||||
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]
|
||||
fn test_subject() {
|
||||
let subject_s = "list.free.de mailing list memberships reminder".as_bytes();
|
||||
fn test_phrase() {
|
||||
let phrase_s = "list.free.de mailing list memberships reminder".as_bytes();
|
||||
assert_eq!(
|
||||
(
|
||||
&b""[..],
|
||||
"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!(
|
||||
(
|
||||
&b""[..],
|
||||
"Νέο προσωπικό μήνυμα αφίχθη".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!(
|
||||
(
|
||||
&b""[..],
|
||||
"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();
|
||||
assert_eq!(
|
||||
"Νέο προσωπικό μήνυμα αφί".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!(
|
||||
"Πρόσθε".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!(
|
||||
"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?=CE=B1=CF=83=CF=84=CE=B9=CE=BA=CE=AE?="
|
||||
.as_bytes();
|
||||
|
@ -352,7 +562,7 @@ fn test_subject() {
|
|||
&b""[..],
|
||||
"Πρόσθετη εξεταστική".to_string()
|
||||
),
|
||||
subject(subject_s).unwrap()
|
||||
phrase(phrase_s).unwrap()
|
||||
);
|
||||
}
|
||||
fn eat_comments(input: &str) -> String {
|
||||
|
@ -388,7 +598,7 @@ fn test_eat_comments() {
|
|||
*
|
||||
* We should use a custom parser here*/
|
||||
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()
|
||||
.unwrap()
|
||||
.replace("-", "+");
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
use super::*;
|
||||
use linkify::{LinkFinder, Link};
|
||||
use linkify::{Link, LinkFinder};
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
use mime_apps::query_default_app;
|
||||
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
enum ViewMode {
|
||||
Normal,
|
||||
|
@ -35,8 +34,11 @@ pub struct 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 {
|
||||
coordinates: coordinates,
|
||||
pager: pager,
|
||||
|
@ -49,15 +51,20 @@ impl MailView {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
impl Component for MailView {
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
let upper_left = upper_left!(area);
|
||||
let bottom_right = bottom_right!(area);
|
||||
|
||||
let (envelope_idx, y): (usize, usize) = {
|
||||
let threaded = context.accounts[self.coordinates.0].runtime_settings.threaded;
|
||||
let mailbox = &mut context.accounts[self.coordinates.0][self.coordinates.1].as_ref().unwrap().as_ref().unwrap();
|
||||
let threaded = context.accounts[self.coordinates.0]
|
||||
.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 {
|
||||
mailbox.threaded_mail(self.coordinates.2)
|
||||
} else {
|
||||
|
@ -66,98 +73,125 @@ impl Component for MailView {
|
|||
|
||||
let envelope: &Envelope = &mailbox.collection[envelope_idx];
|
||||
|
||||
let (x,y) = write_string_to_grid(&format!("Date: {}", envelope.date_as_str()),
|
||||
grid,
|
||||
Color::Byte(33),
|
||||
Color::Default,
|
||||
area,
|
||||
true);
|
||||
let (x, y) = write_string_to_grid(
|
||||
&format!("Date: {}", envelope.date_as_str()),
|
||||
grid,
|
||||
Color::Byte(33),
|
||||
Color::Default,
|
||||
area,
|
||||
true,
|
||||
);
|
||||
for x in x..=get_x(bottom_right) {
|
||||
grid[(x, y)].set_ch(' ');
|
||||
grid[(x, y)].set_bg(Color::Default);
|
||||
grid[(x, y)].set_fg(Color::Default);
|
||||
}
|
||||
let (x,y) = write_string_to_grid(&format!("From: {}", envelope.from()),
|
||||
grid,
|
||||
Color::Byte(33),
|
||||
Color::Default,
|
||||
(set_y(upper_left, y+1), bottom_right),
|
||||
true);
|
||||
let (x, y) = write_string_to_grid(
|
||||
&format!("From: {}", envelope.from_to_string()),
|
||||
grid,
|
||||
Color::Byte(33),
|
||||
Color::Default,
|
||||
(set_y(upper_left, y + 1), bottom_right),
|
||||
true,
|
||||
);
|
||||
for x in x..=get_x(bottom_right) {
|
||||
grid[(x, y)].set_ch(' ');
|
||||
grid[(x, y)].set_bg(Color::Default);
|
||||
grid[(x, y)].set_fg(Color::Default);
|
||||
}
|
||||
let (x,y) = write_string_to_grid(&format!("To: {}", envelope.to()),
|
||||
grid,
|
||||
Color::Byte(33),
|
||||
Color::Default,
|
||||
(set_y(upper_left, y+1), bottom_right),
|
||||
true);
|
||||
let (x, y) = write_string_to_grid(
|
||||
&format!("To: {}", envelope.to_to_string()),
|
||||
grid,
|
||||
Color::Byte(33),
|
||||
Color::Default,
|
||||
(set_y(upper_left, y + 1), bottom_right),
|
||||
true,
|
||||
);
|
||||
for x in x..=get_x(bottom_right) {
|
||||
grid[(x, y)].set_ch(' ');
|
||||
grid[(x, y)].set_bg(Color::Default);
|
||||
grid[(x, y)].set_fg(Color::Default);
|
||||
}
|
||||
let (x,y) = write_string_to_grid(&format!("Subject: {}", envelope.subject()),
|
||||
grid,
|
||||
Color::Byte(33),
|
||||
Color::Default,
|
||||
(set_y(upper_left, y+1), bottom_right),
|
||||
true);
|
||||
let (x, y) = write_string_to_grid(
|
||||
&format!("Subject: {}", envelope.subject()),
|
||||
grid,
|
||||
Color::Byte(33),
|
||||
Color::Default,
|
||||
(set_y(upper_left, y + 1), bottom_right),
|
||||
true,
|
||||
);
|
||||
for x in x..=get_x(bottom_right) {
|
||||
grid[(x, y)].set_ch(' ');
|
||||
grid[(x, y)].set_bg(Color::Default);
|
||||
grid[(x, y)].set_fg(Color::Default);
|
||||
}
|
||||
let (x, y) = write_string_to_grid(&format!("Message-ID: {}", envelope.message_id_raw()),
|
||||
grid,
|
||||
Color::Byte(33),
|
||||
Color::Default,
|
||||
(set_y(upper_left, y+1), bottom_right),
|
||||
true);
|
||||
let (x, y) = write_string_to_grid(
|
||||
&format!("Message-ID: {}", envelope.message_id_raw()),
|
||||
grid,
|
||||
Color::Byte(33),
|
||||
Color::Default,
|
||||
(set_y(upper_left, y + 1), bottom_right),
|
||||
true,
|
||||
);
|
||||
for x in x..=get_x(bottom_right) {
|
||||
grid[(x, y)].set_ch(' ');
|
||||
grid[(x, y)].set_bg(Color::Default);
|
||||
grid[(x, y)].set_fg(Color::Default);
|
||||
}
|
||||
clear_area(grid,
|
||||
(set_y(upper_left, y+1), set_y(bottom_right, y+2)));
|
||||
context.dirty_areas.push_back((upper_left, set_y(bottom_right, y+1)));
|
||||
clear_area(grid, (set_y(upper_left, y + 1), set_y(bottom_right, y + 2)));
|
||||
context
|
||||
.dirty_areas
|
||||
.push_back((upper_left, set_y(bottom_right, y + 1)));
|
||||
(envelope_idx, y + 1)
|
||||
};
|
||||
|
||||
if self.dirty {
|
||||
let buf = {
|
||||
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 envelope : &Envelope = &mailbox.collection[envelope_idx];
|
||||
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 envelope: &Envelope = &mailbox.collection[envelope_idx];
|
||||
|
||||
let finder = LinkFinder::new();
|
||||
let mut text = match self.mode {
|
||||
ViewMode::Url => {
|
||||
let mut t = envelope.body().text().to_string();
|
||||
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 {
|
||||
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
|
||||
},
|
||||
}
|
||||
ViewMode::Attachment(aidx) => {
|
||||
let attachments = envelope.body().attachments();
|
||||
let mut ret = format!("Viewing attachment. Press `r` to return \n");
|
||||
ret.push_str(&attachments[aidx].text());
|
||||
ret
|
||||
},
|
||||
}
|
||||
_ => {
|
||||
let mut t = envelope.body().text().to_string();
|
||||
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
|
||||
},
|
||||
}
|
||||
};
|
||||
let mut buf = CellBuffer::from(&text);
|
||||
match self.mode {
|
||||
|
@ -172,10 +206,10 @@ impl Component for MailView {
|
|||
buf[(l.start() + shift - 3, 0)].set_fg(Color::Byte(226));
|
||||
}
|
||||
// Each Cell represents one char so next line will be:
|
||||
shift += r.chars().count()+1;
|
||||
shift += r.chars().count() + 1;
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
buf
|
||||
};
|
||||
|
@ -188,29 +222,41 @@ impl Component for MailView {
|
|||
self.pager = Some(Pager::from_buf(buf, cursor_pos));
|
||||
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) {
|
||||
match event.event_type {
|
||||
UIEventType::Input(Key::Esc) => {
|
||||
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);
|
||||
},
|
||||
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.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();
|
||||
self.cmd_buf.clear();
|
||||
|
||||
{
|
||||
let threaded = context.accounts[self.coordinates.0].runtime_settings.threaded;
|
||||
let mailbox = &mut context.accounts[self.coordinates.0][self.coordinates.1].as_ref().unwrap().as_ref().unwrap();
|
||||
let threaded = context.accounts[self.coordinates.0]
|
||||
.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 {
|
||||
mailbox.threaded_mail(self.coordinates.2)
|
||||
} else {
|
||||
|
@ -223,11 +269,16 @@ impl Component for MailView {
|
|||
ContentType::Text => {
|
||||
self.mode = ViewMode::Attachment(lidx);
|
||||
self.dirty = true;
|
||||
},
|
||||
}
|
||||
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;
|
||||
},
|
||||
}
|
||||
ContentType::Unsupported { .. } => {
|
||||
let attachment_type = u.mime_type();
|
||||
let binary = query_default_app(&attachment_type);
|
||||
|
@ -240,28 +291,45 @@ impl Component for MailView {
|
|||
.spawn()
|
||||
.expect(&format!("Failed to start {}", binary.display()));
|
||||
} 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;
|
||||
}
|
||||
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
} 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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
},
|
||||
UIEventType::Input(Key::Char('g')) if self.cmd_buf.len() > 0 && self.mode == ViewMode::Url => { //TODO:this should be an Action
|
||||
}
|
||||
UIEventType::Input(Key::Char('g'))
|
||||
if self.cmd_buf.len() > 0 && self.mode == ViewMode::Url =>
|
||||
{
|
||||
//TODO:this should be an Action
|
||||
|
||||
let lidx = self.cmd_buf.parse::<usize>().unwrap();
|
||||
self.cmd_buf.clear();
|
||||
let url = {
|
||||
let threaded = context.accounts[self.coordinates.0].runtime_settings.threaded;
|
||||
let mailbox = &mut context.accounts[self.coordinates.0][self.coordinates.1].as_ref().unwrap().as_ref().unwrap();
|
||||
let threaded = context.accounts[self.coordinates.0]
|
||||
.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 {
|
||||
mailbox.threaded_mail(self.coordinates.2)
|
||||
} else {
|
||||
|
@ -275,33 +343,37 @@ impl Component for MailView {
|
|||
if let Some(u) = links.get(lidx) {
|
||||
u.as_str().to_string()
|
||||
} 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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Command::new("xdg-open")
|
||||
.arg(url)
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("Failed to start xdg_open");
|
||||
|
||||
},
|
||||
UIEventType::Input(Key::Char('u')) => { //TODO:this should be an Action
|
||||
}
|
||||
UIEventType::Input(Key::Char('u')) => {
|
||||
//TODO:this should be an Action
|
||||
match self.mode {
|
||||
ViewMode::Normal => { self.mode = ViewMode::Url },
|
||||
ViewMode::Url => { self.mode = ViewMode::Normal },
|
||||
_ => {},
|
||||
ViewMode::Normal => self.mode = ViewMode::Url,
|
||||
ViewMode::Url => self.mode = ViewMode::Normal,
|
||||
_ => {}
|
||||
}
|
||||
self.dirty = true;
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
if let Some(ref mut sub) = self.subview {
|
||||
sub.process_event(event, context);
|
||||
|
||||
} else {
|
||||
if let Some(ref mut p) = self.pager {
|
||||
p.process_event(event, context);
|
||||
|
@ -309,7 +381,8 @@ impl Component for MailView {
|
|||
}
|
||||
}
|
||||
fn is_dirty(&self) -> bool {
|
||||
self.dirty || self.pager.as_ref().map(|p| p.is_dirty()).unwrap_or(false) ||
|
||||
self.subview.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
|
||||
self.dirty
|
||||
|| self.pager.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
|
||||
|| self.subview.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue