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::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() {

View File

@ -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("-", "+");

View File

@ -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)
}
}