rustfmt everything

embed
Manos Pitsidianakis 2018-07-27 21:37:56 +03:00
parent ffbd70e40b
commit 2f91d29326
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
26 changed files with 1002 additions and 809 deletions

View File

@ -1,9 +1,9 @@
#![feature(test)] #![feature(test)]
extern crate melib; extern crate melib;
use melib::mailbox::email::Envelope;
use melib::mailbox::backends::BackendOpGenerator;
use melib::mailbox::backends::maildir::MaildirOp; use melib::mailbox::backends::maildir::MaildirOp;
use melib::mailbox::backends::BackendOpGenerator;
use melib::mailbox::email::Envelope;
extern crate test; extern crate test;
use self::test::Bencher; use self::test::Bencher;

View File

@ -20,18 +20,16 @@
*/ */
extern crate config; extern crate config;
extern crate xdg;
extern crate serde; extern crate serde;
extern crate xdg;
pub mod pager; pub mod pager;
use pager::PagerSettings; use pager::PagerSettings;
use std::collections::HashMap;
use std::collections::hash_map::DefaultHasher; use std::collections::hash_map::DefaultHasher;
use std::hash::Hasher; use std::collections::HashMap;
use std::fs; use std::fs;
use std::hash::Hasher;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
@ -64,7 +62,6 @@ impl Folder {
} }
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
struct FileAccount { struct FileAccount {
folders: String, folders: String,
@ -73,7 +70,6 @@ struct FileAccount {
threaded: bool, threaded: bool,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
struct FileSettings { struct FileSettings {
accounts: HashMap<String, FileAccount>, accounts: HashMap<String, FileAccount>,
@ -104,7 +100,6 @@ pub struct Settings {
pub pager: PagerSettings, pub pager: PagerSettings,
} }
use self::config::{Config, File, FileFormat}; use self::config::{Config, File, FileFormat};
impl FileSettings { impl FileSettings {
pub fn new() -> FileSettings { pub fn new() -> FileSettings {
@ -117,7 +112,7 @@ impl FileSettings {
let s = s.merge(File::new(config_path.to_str().unwrap(), FileFormat::Toml)); let s = s.merge(File::new(config_path.to_str().unwrap(), FileFormat::Toml));
// TODO: Return result // TODO: Return result
s.unwrap().deserialize().unwrap() s.unwrap().deserialize().unwrap()
} }
} }
@ -134,16 +129,20 @@ impl Settings {
for f in f.iter_mut() { for f in f.iter_mut() {
{ {
let path = f.path(); let path = f.path();
if path.ends_with("cur") || path.ends_with("new") || if path.ends_with("cur")
path.ends_with("tmp") || path.ends_with("new")
|| path.ends_with("tmp")
{ {
continue; continue;
} }
if path.is_dir() { if path.is_dir() {
let path_children = recurse_folders(folders, &path); let path_children = recurse_folders(folders, &path);
folders.push(Folder::new(path.to_str().unwrap().to_string(), path.file_name().unwrap().to_str().unwrap().to_string(), path_children)); folders.push(Folder::new(
children.push(folders.len()-1); path.to_str().unwrap().to_string(),
path.file_name().unwrap().to_str().unwrap().to_string(),
path_children,
));
children.push(folders.len() - 1);
} }
} }
} }
@ -153,7 +152,11 @@ impl Settings {
let path = PathBuf::from(&x.folders); let path = PathBuf::from(&x.folders);
let path_children = recurse_folders(&mut folders, &path); let path_children = recurse_folders(&mut folders, &path);
if path.is_dir() { if path.is_dir() {
folders.push(Folder::new(path.to_str().unwrap().to_string(), path.file_name().unwrap().to_str().unwrap().to_string(), path_children)); folders.push(Folder::new(
path.to_str().unwrap().to_string(),
path.file_name().unwrap().to_str().unwrap().to_string(),
path_children,
));
} }
//folders.sort_by(|a, b| b.name.cmp(&a.name)); //folders.sort_by(|a, b| b.name.cmp(&a.name));
s.insert( s.insert(
@ -168,6 +171,9 @@ impl Settings {
); );
} }
Settings { accounts: s, pager: fs.pager } Settings {
accounts: s,
pager: fs.pager,
}
} }
} }

View File

@ -1,15 +1,15 @@
fn false_val () -> bool { fn false_val() -> bool {
true true
} }
fn true_val () -> bool { fn true_val() -> bool {
true true
} }
fn zero_val () -> usize { fn zero_val() -> usize {
0 0
} }
fn eighty_percent () -> usize { fn eighty_percent() -> usize {
80 80
} }
@ -39,12 +39,12 @@ pub struct PagerSettings {
/// Default: 80 /// Default: 80
#[serde(default = "eighty_percent")] #[serde(default = "eighty_percent")]
pub pager_ratio: usize, pub pager_ratio: usize,
/// A command to pipe mail output through for viewing in pager. /// A command to pipe mail output through for viewing in pager.
/// Default: None /// Default: None
#[serde(default = "none")] #[serde(default = "none")]
pub filter: Option<String>, pub filter: Option<String>,
/// Respect "format=flowed" /// Respect "format=flowed"
/// Default: true /// Default: true
#[serde(default = "true_val")] #[serde(default = "true_val")]

View File

@ -20,8 +20,8 @@
*/ */
use std::error::Error; use std::error::Error;
use std::fmt; use std::fmt;
use std::result;
use std::io; use std::io;
use std::result;
use nom; use nom;

View File

@ -18,10 +18,9 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
pub mod mailbox;
pub mod conf; pub mod conf;
pub mod error; pub mod error;
pub mod mailbox;
#[macro_use] #[macro_use]
extern crate serde_derive; extern crate serde_derive;
@ -29,16 +28,16 @@ extern crate serde_derive;
#[macro_use] #[macro_use]
extern crate nom; extern crate nom;
extern crate chrono; extern crate chrono;
extern crate memmap;
extern crate encoding;
extern crate data_encoding; extern crate data_encoding;
extern crate encoding;
extern crate memmap;
#[macro_use] #[macro_use]
extern crate bitflags; extern crate bitflags;
pub use mailbox::*;
pub use conf::*; pub use conf::*;
pub use mailbox::*;
pub use mailbox::backends::{RefreshEventConsumer, RefreshEvent, Backends}; pub use error::{MeliError, Result};
pub use mailbox::backends::{Backends, RefreshEvent, RefreshEventConsumer};
pub use mailbox::email::{Envelope, Flag}; pub use mailbox::email::{Envelope, Flag};
pub use error::{Result, MeliError};

View File

@ -19,9 +19,9 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use mailbox::*;
use mailbox::backends::{RefreshEventConsumer, Backends};
use conf::{AccountSettings, Folder}; use conf::{AccountSettings, Folder};
use mailbox::backends::{Backends, RefreshEventConsumer};
use mailbox::*;
use std::ops::{Index, IndexMut}; use std::ops::{Index, IndexMut};
#[derive(Debug)] #[derive(Debug)]
@ -36,7 +36,6 @@ pub struct Account {
pub backend: Box<MailBackend>, pub backend: Box<MailBackend>,
} }
impl Account { impl Account {
pub fn new(name: String, settings: AccountSettings, backends: &Backends) -> Self { pub fn new(name: String, settings: AccountSettings, backends: &Backends) -> Self {
let sent_folder = settings let sent_folder = settings
@ -88,7 +87,8 @@ impl IndexMut<usize> for Account {
if self.sent_folder.is_some() { if self.sent_folder.is_some() {
let id = self.sent_folder.unwrap(); let id = self.sent_folder.unwrap();
if id == index { if id == index {
self.folders[index] = Some(Mailbox::new(folder, &None, self.backend.get(&folder))); self.folders[index] =
Some(Mailbox::new(folder, &None, self.backend.get(&folder)));
} else { } else {
let (sent, cur) = { let (sent, cur) = {
let ptr = self.folders.as_mut_ptr(); let ptr = self.folders.as_mut_ptr();
@ -104,10 +104,10 @@ impl IndexMut<usize> for Account {
if sent[0].is_none() { if sent[0].is_none() {
sent[0] = Some(Mailbox::new(sent_path, &None, self.backend.get(&folder))); sent[0] = Some(Mailbox::new(sent_path, &None, self.backend.get(&folder)));
} }
cur[0] = Some(Mailbox::new(folder, &sent[0],self.backend.get(folder))); cur[0] = Some(Mailbox::new(folder, &sent[0], self.backend.get(folder)));
} }
} else { } else {
self.folders[index] = Some(Mailbox::new(folder, &None,self.backend.get(&folder))); self.folders[index] = Some(Mailbox::new(folder, &None, self.backend.get(&folder)));
}; };
} }
&mut self.folders[index] &mut self.folders[index]

View File

@ -19,21 +19,18 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use mailbox::email::{Envelope, Flag};
use error::{Result};
use mailbox::backends::{BackendOp, MailBackend, RefreshEventConsumer};
use conf::Folder; use conf::Folder;
use error::Result;
use mailbox::backends::{BackendOp, MailBackend, RefreshEventConsumer};
use mailbox::email::{Envelope, Flag};
/// `BackendOp` implementor for Imap /// `BackendOp` implementor for Imap
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct ImapOp { pub struct ImapOp {}
}
impl ImapOp { impl ImapOp {
pub fn new(_path: String) -> Self { pub fn new(_path: String) -> Self {
ImapOp { ImapOp {}
}
} }
} }
@ -55,12 +52,9 @@ impl BackendOp for ImapOp {
} }
} }
/// Imap backend
/// Imap backend
#[derive(Debug)] #[derive(Debug)]
pub struct ImapType { pub struct ImapType {}
}
impl MailBackend for ImapType { impl MailBackend for ImapType {
fn get(&self, _folder: &Folder) -> Result<Vec<Envelope>> { fn get(&self, _folder: &Folder) -> Result<Vec<Envelope>> {
@ -73,7 +67,6 @@ impl MailBackend for ImapType {
impl ImapType { impl ImapType {
pub fn new(_path: &str) -> Self { pub fn new(_path: &str) -> Self {
ImapType { ImapType {}
}
} }
} }

View File

@ -19,15 +19,17 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use mailbox::email::{Envelope, Flag};
use error::{MeliError, Result};
use mailbox::backends::{BackendOp, BackendOpGenerator, MailBackend, RefreshEvent, RefreshEventConsumer};
use mailbox::email::parser;
use conf::Folder; use conf::Folder;
use error::{MeliError, Result};
use mailbox::backends::{
BackendOp, BackendOpGenerator, MailBackend, RefreshEvent, RefreshEventConsumer,
};
use mailbox::email::parser;
use mailbox::email::{Envelope, Flag};
extern crate notify; extern crate notify;
use self::notify::{Watcher, RecursiveMode, watcher, DebouncedEvent}; use self::notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
use std::time::Duration; use std::time::Duration;
use std::sync::mpsc::channel; use std::sync::mpsc::channel;
@ -36,8 +38,8 @@ use std::sync::mpsc::channel;
//use std::time::Duration; //use std::time::Duration;
use std::thread; use std::thread;
extern crate crossbeam; extern crate crossbeam;
use std::path::PathBuf;
use memmap::{Mmap, Protection}; use memmap::{Mmap, Protection};
use std::path::PathBuf;
/// `BackendOp` implementor for Maildir /// `BackendOp` implementor for Maildir
#[derive(Debug, Default)] #[derive(Debug, Default)]
@ -70,9 +72,7 @@ impl BackendOp for MaildirOp {
} }
fn as_bytes(&mut self) -> Result<&[u8]> { fn as_bytes(&mut self) -> Result<&[u8]> {
if self.slice.is_none() { if self.slice.is_none() {
self.slice = Some( self.slice = Some(Mmap::open_path(self.path.to_string(), Protection::Read)?);
Mmap::open_path(self.path.to_string(), Protection::Read)?,
);
} }
/* Unwrap is safe since we use ? above. */ /* Unwrap is safe since we use ? above. */
Ok(unsafe { self.slice.as_ref().unwrap().as_slice() }) Ok(unsafe { self.slice.as_ref().unwrap().as_slice() })
@ -104,24 +104,20 @@ impl BackendOp for MaildirOp {
'T' => flag |= Flag::TRASHED, 'T' => flag |= Flag::TRASHED,
'D' => flag |= Flag::DRAFT, 'D' => flag |= Flag::DRAFT,
'F' => flag |= Flag::FLAGGED, 'F' => flag |= Flag::FLAGGED,
_ => panic!(), _ => panic!(),
} }
} }
flag flag
} }
} }
/// Maildir backend https://cr.yp.to/proto/maildir.html /// Maildir backend https://cr.yp.to/proto/maildir.html
#[derive(Debug)] #[derive(Debug)]
pub struct MaildirType { pub struct MaildirType {
path: String, path: String,
} }
impl MailBackend for MaildirType { impl MailBackend for MaildirType {
fn get(&self, folder: &Folder) -> Result<Vec<Envelope>> { fn get(&self, folder: &Folder) -> Result<Vec<Envelope>> {
self.multicore(4, folder) self.multicore(4, folder)
@ -129,34 +125,40 @@ impl MailBackend for MaildirType {
fn watch(&self, sender: RefreshEventConsumer, folders: &[Folder]) -> () { fn watch(&self, sender: RefreshEventConsumer, folders: &[Folder]) -> () {
let folders = folders.to_vec(); let folders = folders.to_vec();
thread::Builder::new().name("folder watch".to_string()).spawn(move || { thread::Builder::new()
let (tx, rx) = channel(); .name("folder watch".to_string())
let mut watcher = watcher(tx, Duration::from_secs(1)).unwrap(); .spawn(move || {
for f in folders { let (tx, rx) = channel();
if MaildirType::is_valid(&f).is_err() { let mut watcher = watcher(tx, Duration::from_secs(1)).unwrap();
continue; for f in folders {
} if MaildirType::is_valid(&f).is_err() {
let mut p = PathBuf::from(&f.path()); continue;
p.push("cur");
watcher.watch(&p, RecursiveMode::NonRecursive).unwrap();
p.pop();
p.push("new");
watcher.watch(&p, RecursiveMode::NonRecursive).unwrap();
}
loop {
match rx.recv() {
Ok(event) => {
match event {
DebouncedEvent::Create(pathbuf) => {
sender.send(RefreshEvent { folder: format!("{}", pathbuf.parent().unwrap().to_str().unwrap()) });
},
_ => {},
}
} }
Err(e) => eprintln!("watch error: {:?}", e), let mut p = PathBuf::from(&f.path());
p.push("cur");
watcher.watch(&p, RecursiveMode::NonRecursive).unwrap();
p.pop();
p.push("new");
watcher.watch(&p, RecursiveMode::NonRecursive).unwrap();
} }
} loop {
}).unwrap(); match rx.recv() {
Ok(event) => match event {
DebouncedEvent::Create(pathbuf) => {
sender.send(RefreshEvent {
folder: format!(
"{}",
pathbuf.parent().unwrap().to_str().unwrap()
),
});
}
_ => {}
},
Err(e) => eprintln!("watch error: {:?}", e),
}
}
})
.unwrap();
} }
} }
@ -172,9 +174,10 @@ impl MaildirType {
for d in &["cur", "new", "tmp"] { for d in &["cur", "new", "tmp"] {
p.push(d); p.push(d);
if !p.is_dir() { if !p.is_dir() {
return Err(MeliError::new( return Err(MeliError::new(format!(
format!("{} is not a valid maildir folder", path), "{} is not a valid maildir folder",
)); path
)));
} }
p.pop(); p.pop();
} }
@ -209,9 +212,10 @@ impl MaildirType {
let mut local_r: Vec<Envelope> = Vec::with_capacity(chunk.len()); let mut local_r: Vec<Envelope> = Vec::with_capacity(chunk.len());
for e in chunk { for e in chunk {
let e_copy = e.to_string(); let e_copy = e.to_string();
if let Some(mut e) = Envelope::from_token(Box::new(BackendOpGenerator::new( if let Some(mut e) =
Box::new(move || Box::new(MaildirOp::new(e_copy.clone()))), Envelope::from_token(Box::new(BackendOpGenerator::new(Box::new(
))) { move || Box::new(MaildirOp::new(e_copy.clone())),
)))) {
e.populate_headers(); e.populate_headers();
local_r.push(e); local_r.push(e);
} }

View File

@ -19,23 +19,18 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use mailbox::email::{Envelope, Flag};
use error::{Result};
use mailbox::backends::{BackendOp, MailBackend, RefreshEventConsumer};
use conf::Folder; use conf::Folder;
use error::Result;
use mailbox::backends::{BackendOp, MailBackend, RefreshEventConsumer};
use mailbox::email::{Envelope, Flag};
/// `BackendOp` implementor for Mbox /// `BackendOp` implementor for Mbox
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct MboxOp { pub struct MboxOp {}
}
impl MboxOp { impl MboxOp {
pub fn new(_path: String) -> Self { pub fn new(_path: String) -> Self {
MboxOp { MboxOp {}
}
} }
} }
@ -57,12 +52,9 @@ impl BackendOp for MboxOp {
} }
} }
/// Mbox backend
/// Mbox backend
#[derive(Debug)] #[derive(Debug)]
pub struct MboxType { pub struct MboxType {}
}
impl MailBackend for MboxType { impl MailBackend for MboxType {
fn get(&self, _folder: &Folder) -> Result<Vec<Envelope>> { fn get(&self, _folder: &Folder) -> Result<Vec<Envelope>> {
@ -75,7 +67,6 @@ impl MailBackend for MboxType {
impl MboxType { impl MboxType {
pub fn new(_path: &str) -> Self { pub fn new(_path: &str) -> Self {
MboxType { MboxType {}
}
} }
} }

View File

@ -18,19 +18,18 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
pub mod imap;
pub mod maildir; pub mod maildir;
pub mod mbox; pub mod mbox;
pub mod imap;
use conf::Folder; use conf::Folder;
use mailbox::email::{Envelope, Flag}; use error::Result;
use mailbox::backends::imap::ImapType;
use mailbox::backends::maildir::MaildirType; use mailbox::backends::maildir::MaildirType;
use mailbox::backends::mbox::MboxType; use mailbox::backends::mbox::MboxType;
use mailbox::backends::imap::ImapType; use mailbox::email::{Envelope, Flag};
use error::Result;
use std::fmt; use std::fmt;
extern crate fnv; extern crate fnv;
use self::fnv::FnvHashMap; use self::fnv::FnvHashMap;
use std; use std;
@ -44,9 +43,12 @@ pub struct Backends {
impl Backends { impl Backends {
pub fn new() -> Self { pub fn new() -> Self {
let mut b = Backends { let mut b = Backends {
map: FnvHashMap::with_capacity_and_hasher(1, Default::default()) map: FnvHashMap::with_capacity_and_hasher(1, Default::default()),
}; };
b.register("maildir".to_string(), Box::new(|| Box::new(MaildirType::new("")))); b.register(
"maildir".to_string(),
Box::new(|| Box::new(MaildirType::new(""))),
);
b.register("mbox".to_string(), Box::new(|| Box::new(MboxType::new("")))); b.register("mbox".to_string(), Box::new(|| Box::new(MboxType::new(""))));
b.register("imap".to_string(), Box::new(|| Box::new(ImapType::new("")))); b.register("imap".to_string(), Box::new(|| Box::new(ImapType::new(""))));
b b
@ -66,7 +68,6 @@ impl Backends {
} }
} }
pub struct RefreshEvent { pub struct RefreshEvent {
pub folder: String, pub folder: String,
} }
@ -87,7 +88,7 @@ impl RefreshEventConsumer {
} }
pub trait MailBackend: ::std::fmt::Debug { pub trait MailBackend: ::std::fmt::Debug {
fn get(&self, folder: &Folder) -> Result<Vec<Envelope>>; fn get(&self, folder: &Folder) -> Result<Vec<Envelope>>;
fn watch(&self, sender:RefreshEventConsumer, folders: &[Folder]) -> (); fn watch(&self, sender: RefreshEventConsumer, folders: &[Folder]) -> ();
//fn new(folders: &Vec<String>) -> Box<Self>; //fn new(folders: &Vec<String>) -> Box<Self>;
//login function //login function
} }

View File

@ -40,60 +40,39 @@ pub enum MultipartType {
Unsupported { tag: String }, Unsupported { tag: String },
} }
impl Display for MultipartType { impl Display for MultipartType {
fn fmt(&self, f: &mut Formatter) -> FmtResult { fn fmt(&self, f: &mut Formatter) -> FmtResult {
match self { match self {
MultipartType::Mixed => { MultipartType::Mixed => write!(f, "multipart/mixed"),
write!(f, "multipart/mixed") MultipartType::Alternative => write!(f, "multipart/alternative"),
}, MultipartType::Digest => write!(f, "multipart/digest"),
MultipartType::Alternative => { MultipartType::Unsupported { tag: ref t } => write!(f, "multipart/{}", t),
write!(f, "multipart/alternative")
},
MultipartType::Digest => {
write!(f, "multipart/digest")
},
MultipartType::Unsupported { tag: ref t } => {
write!(f, "multipart/{}", t)
},
} }
} }
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum AttachmentType { pub enum AttachmentType {
Data { tag: String }, Data {
Text { content: String }, tag: String,
},
Text {
content: String,
},
Multipart { Multipart {
of_type: MultipartType, of_type: MultipartType,
subattachments: Vec<Attachment>, subattachments: Vec<Attachment>,
}, },
} }
impl Display for AttachmentType { impl Display for AttachmentType {
fn fmt(&self, f: &mut Formatter) -> FmtResult { fn fmt(&self, f: &mut Formatter) -> FmtResult {
match self { match self {
AttachmentType::Data { tag: ref t } => { AttachmentType::Data { tag: ref t } => write!(f, "{}", t),
write!(f, "{}", t) AttachmentType::Text { content: ref c } => write!(f, "{}", c),
}, AttachmentType::Multipart { of_type: ref t, .. } => write!(f, "{}", t),
AttachmentType::Text { content: ref c } => {
write!(f, "{}", c)
},
AttachmentType::Multipart { of_type: ref t, .. } => {
write!(f, "{}", t)
},
} }
} }
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum ContentType { pub enum ContentType {
@ -207,32 +186,33 @@ impl AttachmentBuilder {
fn decode(&self) -> String { fn decode(&self) -> String {
// TODO: Use charset for decoding // TODO: Use charset for decoding
match self.content_transfer_encoding { match self.content_transfer_encoding {
ContentTransferEncoding::Base64 => { ContentTransferEncoding::Base64 => match BASE64_MIME.decode(
match BASE64_MIME.decode(str::from_utf8(&self.raw) str::from_utf8(&self.raw)
.unwrap() .unwrap()
.trim() .trim()
.lines() .lines()
.fold(String::with_capacity(self.raw.len()), |mut acc, x| { .fold(String::with_capacity(self.raw.len()), |mut acc, x| {
acc.push_str(x); acc.push_str(x);
acc acc
}).as_bytes()) { })
Ok(ref v) => { .as_bytes(),
let s = String::from_utf8_lossy(v); ) {
if s.find("\r\n").is_some() { Ok(ref v) => {
s.replace("\r\n", "\n") let s = String::from_utf8_lossy(v);
} else { if s.find("\r\n").is_some() {
s.into_owned() s.replace("\r\n", "\n")
} } else {
s.into_owned()
} }
_ => String::from_utf8_lossy(&self.raw).into_owned(),
} }
} _ => String::from_utf8_lossy(&self.raw).into_owned(),
},
ContentTransferEncoding::QuotedPrintable => parser::quoted_printable_text(&self.raw) ContentTransferEncoding::QuotedPrintable => parser::quoted_printable_text(&self.raw)
.to_full_result() .to_full_result()
.unwrap(), .unwrap(),
ContentTransferEncoding::_7Bit | ContentTransferEncoding::_7Bit
ContentTransferEncoding::_8Bit | | ContentTransferEncoding::_8Bit
ContentTransferEncoding::Other { .. } => { | ContentTransferEncoding::Other { .. } => {
String::from_utf8_lossy(&self.raw).into_owned() String::from_utf8_lossy(&self.raw).into_owned()
} }
} }
@ -310,7 +290,6 @@ impl AttachmentBuilder {
} }
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Attachment { pub struct Attachment {
content_type: (ContentType, ContentSubType), content_type: (ContentType, ContentSubType),
@ -327,19 +306,20 @@ impl Display for Attachment {
AttachmentType::Data { .. } => { AttachmentType::Data { .. } => {
write!(f, "Data attachment of type {}", self.mime_type()) write!(f, "Data attachment of type {}", self.mime_type())
} }
AttachmentType::Text { .. } => { AttachmentType::Text { .. } => write!(f, "Text attachment"),
write!(f, "Text attachment")
}
AttachmentType::Multipart { AttachmentType::Multipart {
of_type: ref multipart_type, of_type: ref multipart_type,
subattachments: ref sub_att_vec, subattachments: ref sub_att_vec,
} => if *multipart_type == MultipartType::Alternative { } => if *multipart_type == MultipartType::Alternative {
write!(f, "Multipart/alternative attachment with {} subs", sub_att_vec.len()) write!(
f,
"Multipart/alternative attachment with {} subs",
sub_att_vec.len()
)
} else { } else {
write!(f, "Multipart attachment with {} subs", sub_att_vec.len()) write!(f, "Multipart attachment with {} subs", sub_att_vec.len())
}, },
} }
} }
} }
@ -388,9 +368,7 @@ impl Attachment {
let mut ret = Vec::new(); let mut ret = Vec::new();
fn count_recursive(att: &Attachment, ret: &mut Vec<Attachment>) { fn count_recursive(att: &Attachment, ret: &mut Vec<Attachment>) {
match att.attachment_type { match att.attachment_type {
AttachmentType::Data { .. } | AttachmentType::Text { .. } => { AttachmentType::Data { .. } | AttachmentType::Text { .. } => ret.push(att.clone()),
ret.push(att.clone())
}
AttachmentType::Multipart { AttachmentType::Multipart {
of_type: ref multipart_type, of_type: ref multipart_type,
subattachments: ref sub_att_vec, subattachments: ref sub_att_vec,
@ -405,8 +383,6 @@ impl Attachment {
count_recursive(&self, &mut ret); count_recursive(&self, &mut ret);
ret ret
} }
pub fn count_attachments(&self) -> usize { pub fn count_attachments(&self) -> usize {
self.attachments().len() self.attachments().len()
@ -422,7 +398,6 @@ impl Attachment {
} }
} }
pub fn interpret_format_flowed(_t: &str) -> String { pub fn interpret_format_flowed(_t: &str) -> String {
//let mut n = String::with_capacity(t.len()); //let mut n = String::with_capacity(t.len());
unimplemented!() unimplemented!()
@ -431,19 +406,15 @@ pub fn interpret_format_flowed(_t: &str) -> String {
pub fn decode(a: &Attachment) -> Vec<u8> { pub fn decode(a: &Attachment) -> Vec<u8> {
// TODO: Use charset for decoding // TODO: Use charset for decoding
match a.content_transfer_encoding { match a.content_transfer_encoding {
ContentTransferEncoding::Base64 => { ContentTransferEncoding::Base64 => match BASE64_MIME.decode(a.bytes()) {
match BASE64_MIME.decode(a.bytes()) { Ok(v) => v,
Ok(v) => { _ => a.bytes().to_vec(),
v },
} ContentTransferEncoding::QuotedPrintable => parser::quoted_printed_bytes(&a.bytes())
_ => a.bytes().to_vec(), .to_full_result()
} .unwrap(),
} ContentTransferEncoding::_7Bit
ContentTransferEncoding::QuotedPrintable => parser::quoted_printed_bytes(&a.bytes()).to_full_result() .unwrap(), | ContentTransferEncoding::_8Bit
ContentTransferEncoding::_7Bit | | ContentTransferEncoding::Other { .. } => a.bytes().to_vec(),
ContentTransferEncoding::_8Bit |
ContentTransferEncoding::Other { .. } => {
a.bytes().to_vec()
}
} }
} }

View File

@ -19,38 +19,36 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
pub mod parser;
pub mod attachments; pub mod attachments;
pub mod parser;
use mailbox::backends::BackendOpGenerator;
pub use self::attachments::*; pub use self::attachments::*;
use mailbox::backends::BackendOpGenerator;
use std::string::String;
use std::sync::Arc;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::fmt; use std::fmt;
use std::option::Option; use std::option::Option;
use std::string::String;
use std::sync::Arc;
use chrono; use chrono;
use chrono::TimeZone; use chrono::TimeZone;
#[derive(Clone, Debug)]
#[derive(Clone, Debug, )]
pub struct GroupAddress { pub struct GroupAddress {
raw: String, raw: String,
display_name: StrBuilder, display_name: StrBuilder,
mailbox_list: Vec<Address>, mailbox_list: Vec<Address>,
} }
#[derive(Clone, Debug, )] #[derive(Clone, Debug)]
pub struct MailboxAddress { pub struct MailboxAddress {
raw: String, raw: String,
display_name: StrBuilder, display_name: StrBuilder,
address_spec: StrBuilder, address_spec: StrBuilder,
} }
#[derive(Clone, Debug, )] #[derive(Clone, Debug)]
pub enum Address { pub enum Address {
Mailbox(MailboxAddress), Mailbox(MailboxAddress),
Group(GroupAddress), Group(GroupAddress),
@ -60,17 +58,19 @@ impl Eq for Address {}
impl PartialEq for Address { impl PartialEq for Address {
fn eq(&self, other: &Address) -> bool { fn eq(&self, other: &Address) -> bool {
match (self, other) { match (self, other) {
(Address::Mailbox(_), Address::Group(_)) | (Address::Mailbox(_), Address::Group(_)) | (Address::Group(_), Address::Mailbox(_)) => {
(Address::Group(_), Address::Mailbox(_)) => {
false false
}, }
(Address::Mailbox(s), Address::Mailbox(o)) => { (Address::Mailbox(s), Address::Mailbox(o)) => {
s.address_spec.display(&s.raw) == o.address_spec.display(&o.raw) s.address_spec.display(&s.raw) == o.address_spec.display(&o.raw)
}, }
(Address::Group(s), Address::Group(o)) => { (Address::Group(s), Address::Group(o)) => {
s.display_name.display(&s.raw) == o.display_name.display(&o.raw) && 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)) && s.mailbox_list
}, .iter()
.zip(o.mailbox_list.iter())
.fold(true, |b, (s, o)| b && (s == o))
}
} }
} }
} }
@ -78,25 +78,27 @@ impl PartialEq for Address {
impl fmt::Display for Address { impl fmt::Display for Address {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
Address::Mailbox(m) if m.display_name.length > 0 => { Address::Mailbox(m) if m.display_name.length > 0 => write!(
write!(f, "{} <{}>", m.display_name.display(&m.raw), m.address_spec.display(&m.raw)) f,
}, "{} <{}>",
Address::Group(g) => { m.display_name.display(&m.raw),
let attachment_strings: Vec<String> = g.mailbox_list.iter().map(|a| format!("{}", a)).collect(); m.address_spec.display(&m.raw)
write!(f, "{}: {}", g.display_name.display(&g.raw), attachment_strings.join(", ")) ),
}, Address::Group(g) => {
Address::Mailbox(m) => { let attachment_strings: Vec<String> =
write!(f, "{}", m.address_spec.display(&m.raw)) 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 {
@ -118,7 +120,7 @@ impl StrBuilder {
fn display<'a>(&self, s: &'a str) -> &'a str { fn display<'a>(&self, s: &'a str) -> &'a str {
let offset = self.offset; let offset = self.offset;
let length = self.length; let length = self.length;
&s[offset..offset+length] &s[offset..offset + length]
} }
} }
@ -133,14 +135,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..offset+length-1] &self.0[offset..offset + length - 1]
} }
fn val(&self) -> &str { fn val(&self) -> &str {
&self.0 &self.0
@ -159,8 +161,8 @@ fn test_strbuilder() {
offset: 1, offset: 1,
length: 43, length: 43,
} }
) )
); );
} }
impl PartialEq for MessageID { impl PartialEq for MessageID {
@ -180,7 +182,6 @@ struct References {
refs: Vec<MessageID>, refs: Vec<MessageID>,
} }
bitflags! { bitflags! {
#[derive(Default)] #[derive(Default)]
pub struct Flag: u8 { pub struct Flag: u8 {
@ -220,7 +221,6 @@ pub struct Envelope {
flags: Flag, flags: Flag,
} }
impl Envelope { impl Envelope {
pub fn new(token: Box<BackendOpGenerator>) -> Self { pub fn new(token: Box<BackendOpGenerator>) -> Self {
Envelope { Envelope {
@ -349,7 +349,7 @@ impl Envelope {
} }
pub fn to(&self) -> &Vec<Address> { pub fn to(&self) -> &Vec<Address> {
&self.to &self.to
} }
pub fn to_to_string(&self) -> String { pub fn to_to_string(&self) -> String {
let _strings: Vec<String> = self.to.iter().map(|a| format!("{}", a)).collect(); let _strings: Vec<String> = self.to.iter().map(|a| format!("{}", a)).collect();
_strings.join(", ") _strings.join(", ")
@ -511,6 +511,10 @@ impl Envelope {
self.datetime = Some(new_val); self.datetime = Some(new_val);
self.timestamp = new_val.timestamp() as u64; self.timestamp = new_val.timestamp() as u64;
} }
pub fn set_flag(&mut self, f: Flag) -> () {
self.flags |= f;
}
pub fn flags(&self) -> Flag { pub fn flags(&self) -> Flag {
self.flags self.flags
} }

View File

@ -18,23 +18,23 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use super::*;
use chrono;
use data_encoding::BASE64_MIME;
use encoding::{DecoderTrap, Encoding};
use nom::{is_hex_digit, le_u8};
use nom::{Compare, CompareResult};
use nom::{ErrorKind, IResult, Needed};
use std; use std;
use std::str::from_utf8; use std::str::from_utf8;
use data_encoding::BASE64_MIME;
use chrono;
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 { macro_rules! is_whitespace {
($var:ident) => ( ($var:ident) => {
$var == b' ' && $var == b'\t' && $var == b'\n' && $var == b'\r' $var == b' ' && $var == b'\t' && $var == b'\n' && $var == b'\r'
); };
($var:expr) => ( ($var:expr) => {
$var == b' ' && $var == b'\t' && $var == b'\n' && $var == b'\r' $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> {
@ -71,7 +71,6 @@ fn quoted_printable_byte(input: &[u8]) -> IResult<&[u8], u8> {
* Tue, 5 Jan 2016 21:30:44 +0100 (CET) * Tue, 5 Jan 2016 21:30:44 +0100 (CET)
*/ */
fn header_value(input: &[u8]) -> IResult<&[u8], &str> { fn header_value(input: &[u8]) -> IResult<&[u8], &str> {
if input.is_empty() || input[0] == b'\n' { if input.is_empty() || input[0] == b'\n' {
IResult::Incomplete(Needed::Unknown) IResult::Incomplete(Needed::Unknown)
@ -79,8 +78,7 @@ fn header_value(input: &[u8]) -> IResult<&[u8], &str> {
let input_len = input.len(); let input_len = input.len();
for (i, x) in input.iter().enumerate() { for (i, x) in input.iter().enumerate() {
if *x == b'\n' { if *x == b'\n' {
if (i + 1) < input_len && input[i + 1] != b' ' && input[i + 1] != b'\t' if (i + 1) < input_len && input[i + 1] != b' ' && input[i + 1] != b'\t' {
{
return match from_utf8(&input[0..i]) { return match from_utf8(&input[0..i]) {
Ok(v) => IResult::Done(&input[(i + 1)..], v), Ok(v) => IResult::Done(&input[(i + 1)..], v),
Err(_) => IResult::Error(error_code!(ErrorKind::Custom(43))), Err(_) => IResult::Error(error_code!(ErrorKind::Custom(43))),
@ -97,7 +95,6 @@ fn header_value(input: &[u8]) -> IResult<&[u8], &str> {
} }
} }
/* Parse the name part of the header -> &str */ /* Parse the name part of the header -> &str */
named!(name<&str>, map_res!(is_not!(":\n"), from_utf8)); named!(name<&str>, map_res!(is_not!(":\n"), from_utf8));
@ -111,15 +108,15 @@ named!(pub headers<std::vec::Vec<(&str, &str)>>,
many1!(complete!(header))); many1!(complete!(header)));
//named!(pub headers_raw<&[u8]>, //named!(pub headers_raw<&[u8]>,
//take_until1!("\n\n")); //take_until1!("\n\n"));
pub fn headers_raw(input: &[u8]) -> IResult<&[u8], &[u8]> { pub fn headers_raw(input: &[u8]) -> IResult<&[u8], &[u8]> {
if input.is_empty() { if input.is_empty() {
return IResult::Incomplete(Needed::Unknown) return IResult::Incomplete(Needed::Unknown);
} }
for (i, x) in input.iter().enumerate() { for (i, x) in input.iter().enumerate() {
if *x == b'\n' && i + 1 < input.len() && input[i+1] == b'\n' { if *x == b'\n' && i + 1 < input.len() && input[i + 1] == b'\n' {
return IResult::Done(&input[(i + 1)..], &input[0..i+1]); return IResult::Done(&input[(i + 1)..], &input[0..i + 1]);
} }
} }
return IResult::Error(error_code!(ErrorKind::Custom(43))); return IResult::Error(error_code!(ErrorKind::Custom(43)));
@ -131,7 +128,6 @@ named!(pub body_raw<&[u8]>,
body: take_while!(call!(|_| true)) >> body: take_while!(call!(|_| true)) >>
( { body } ))); ( { body } )));
named!(pub mail<(std::vec::Vec<(&str, &str)>, &[u8])>, named!(pub mail<(std::vec::Vec<(&str, &str)>, &[u8])>,
separated_pair!(headers, tag!("\n"), take_while!(call!(|_| true)))); separated_pair!(headers, tag!("\n"), take_while!(call!(|_| true))));
named!(pub attachment<(std::vec::Vec<(&str, &str)>, &[u8])>, named!(pub attachment<(std::vec::Vec<(&str, &str)>, &[u8])>,
@ -255,19 +251,32 @@ named!(
)) ))
); );
named!(encoded_word_list<String>, ws!(do_parse!( named!(
list: separated_nonempty_list!(complete!(is_a!(" \n\r\t")), encoded_word) >> encoded_word_list<String>,
( { ws!(do_parse!(
let list_len = list.iter().fold(0, |mut acc, x| { acc+=x.len(); acc }); list: separated_nonempty_list!(complete!(is_a!(" \n\r\t")), encoded_word) >> ({
let bytes = list.iter().fold(Vec::with_capacity(list_len), |mut acc, x| { acc.append(&mut x.clone()); acc}); let list_len = list.iter().fold(0, |mut acc, x| {
acc += x.len();
acc
});
let bytes = list.iter()
.fold(Vec::with_capacity(list_len), |mut acc, x| {
acc.append(&mut x.clone());
acc
});
String::from_utf8_lossy(&bytes).into_owned() String::from_utf8_lossy(&bytes).into_owned()
} ) })
))); ))
named!(ascii_token<String>, do_parse!( );
word: alt!(terminated!(take_until1!("=?"), peek!(tag_no_case!("=?UTF-8?"))) | take_while!(call!(|_| { true }))) >> named!(
( { ascii_token<String>,
String::from_utf8_lossy(word).into_owned() do_parse!(
} ))); word: alt!(
terminated!(take_until1!("=?"), peek!(tag_no_case!("=?UTF-8?")))
| take_while!(call!(|_| true))
) >> ({ String::from_utf8_lossy(word).into_owned() })
)
);
fn display_addr(input: &[u8]) -> IResult<&[u8], Address> { fn display_addr(input: &[u8]) -> IResult<&[u8], Address> {
if input.is_empty() || input.len() < 3 { if input.is_empty() || input.len() < 3 {
@ -280,8 +289,7 @@ fn display_addr(input: &[u8]) -> IResult<&[u8], Address> {
let mut flag = false; let mut flag = false;
for (i, b) in input[0..].iter().enumerate() { for (i, b) in input[0..].iter().enumerate() {
if *b == b'<' { if *b == b'<' {
display_name.length = if i != 0 { i - 1 } else { 0 };
display_name.length = if i != 0 { i-1 } else { 0 };
flag = true; flag = true;
break; break;
} }
@ -291,7 +299,7 @@ fn display_addr(input: &[u8]) -> IResult<&[u8], Address> {
} }
let mut end = input.len(); let mut end = input.len();
let mut flag = false; let mut flag = false;
for (i, b) in input[display_name.length+2..].iter().enumerate() { for (i, b) in input[display_name.length + 2..].iter().enumerate() {
if *b == b'@' { if *b == b'@' {
flag = true; flag = true;
} }
@ -305,19 +313,22 @@ fn display_addr(input: &[u8]) -> IResult<&[u8], Address> {
offset: display_name.length + 2, offset: display_name.length + 2,
length: end, length: end,
}; };
match phrase(&input[0..end+display_name.length+3]) { match phrase(&input[0..end + display_name.length + 3]) {
IResult::Error(e) => IResult::Error(e), IResult::Error(e) => IResult::Error(e),
IResult::Incomplete(i) => IResult::Incomplete(i), IResult::Incomplete(i) => IResult::Incomplete(i),
IResult::Done(rest, raw) => { IResult::Done(rest, raw) => {
display_name.length = raw.find('<').unwrap(); display_name.length = raw.find('<').unwrap();
address_spec.offset = display_name.length + 1; address_spec.offset = display_name.length + 1;
address_spec.length = raw.len() - display_name.length - 2; address_spec.length = raw.len() - display_name.length - 2;
IResult::Done(rest, Address::Mailbox(MailboxAddress { IResult::Done(
raw: raw, rest,
display_name: display_name, Address::Mailbox(MailboxAddress {
address_spec: address_spec, raw: raw,
})) display_name: display_name,
}, address_spec: address_spec,
}),
)
}
} }
} else { } else {
IResult::Error(error_code!(ErrorKind::Custom(43))) IResult::Error(error_code!(ErrorKind::Custom(43)))
@ -325,11 +336,8 @@ fn display_addr(input: &[u8]) -> IResult<&[u8], Address> {
} else { } else {
IResult::Error(error_code!(ErrorKind::Custom(43))) IResult::Error(error_code!(ErrorKind::Custom(43)))
} }
} }
fn addr_spec(input: &[u8]) -> IResult<&[u8], Address> { fn addr_spec(input: &[u8]) -> IResult<&[u8], Address> {
if input.is_empty() || input.len() < 3 { if input.is_empty() || input.len() < 3 {
IResult::Incomplete(Needed::Size(1)) IResult::Incomplete(Needed::Size(1))
@ -346,17 +354,20 @@ fn addr_spec(input: &[u8]) -> IResult<&[u8], Address> {
} }
} }
if flag { if flag {
IResult::Done(&input[end..], Address::Mailbox(MailboxAddress { IResult::Done(
raw: String::from_utf8_lossy(&input[0..end+1]).to_string(), &input[end..],
display_name: StrBuilder { Address::Mailbox(MailboxAddress {
offset: 0, raw: String::from_utf8_lossy(&input[0..end + 1]).to_string(),
length: 0, display_name: StrBuilder {
}, offset: 0,
address_spec: StrBuilder { length: 0,
offset: 0, },
length: input[0..end+1].len(), address_spec: StrBuilder {
}, offset: 0,
})) length: input[0..end + 1].len(),
},
}),
)
} else { } else {
IResult::Error(error_code!(ErrorKind::Custom(43))) IResult::Error(error_code!(ErrorKind::Custom(43)))
} }
@ -365,39 +376,43 @@ fn addr_spec(input: &[u8]) -> IResult<&[u8], Address> {
} }
} }
named!(
named!(mailbox<Address>, ws!(alt_complete!( mailbox<Address>,
display_addr | ws!(alt_complete!(display_addr | addr_spec))
addr_spec );
)));
named!(mailbox_list<Vec<Address>>, many0!(mailbox)); named!(mailbox_list<Vec<Address>>, many0!(mailbox));
#[test] #[test]
fn test_mailbox() { fn test_mailbox() {
{ {
let s = b"epilys@postretch"; let s = b"epilys@postretch";
let r = mailbox(s).unwrap().1; let r = mailbox(s).unwrap().1;
match r { match r {
Address::Mailbox(ref m) => { Address::Mailbox(ref m) => {
println!("----\n`{}`, `{}`\n----", m.display_name.display(&m.raw), m.address_spec.display(&m.raw)); println!(
}, "----\n`{}`, `{}`\n----",
_ => {}, m.display_name.display(&m.raw),
} m.address_spec.display(&m.raw)
);
}
_ => {}
}
} }
let s = b"Manos <epilys@postretch>"; let s = b"Manos <epilys@postretch>";
eprintln!("{:?}", display_addr(s).unwrap()); eprintln!("{:?}", display_addr(s).unwrap());
let r = display_addr(s).unwrap().1; let r = display_addr(s).unwrap().1;
match r { match r {
Address::Mailbox(ref m) => { Address::Mailbox(ref m) => {
println!("----\n`{}`, `{}`\n----", m.display_name.display(&m.raw), m.address_spec.display(&m.raw)); println!(
}, "----\n`{}`, `{}`\n----",
_ => {}, m.display_name.display(&m.raw),
m.address_spec.display(&m.raw)
);
}
_ => {}
} }
} }
//named!(group_t<GroupAddress>, ws!( do_parse!( //named!(group_t<GroupAddress>, ws!( do_parse!(
// display_name: take_until1!(":") >> // display_name: take_until1!(":") >>
// mailbox_list: many0!(mailbox) >> // mailbox_list: many0!(mailbox) >>
@ -427,28 +442,26 @@ fn group(input: &[u8]) -> IResult<&[u8], Address> {
return IResult::Error(e); return IResult::Error(e);
} }
IResult::Done(rest, vec) => { IResult::Done(rest, vec) => {
let size: usize = (rest.as_ptr() as usize) - ((&input[0..] as &[u8]).as_ptr() as usize); let size: usize = (rest.as_ptr() as usize) - ((&input[0..] as &[u8]).as_ptr() as usize);
return IResult::Done(rest, Address::Group(GroupAddress { return IResult::Done(
raw: String::from_utf8(input[0..size].to_vec()).unwrap(), rest,
display_name: StrBuilder { Address::Group(GroupAddress {
offset: 0, raw: String::from_utf8(input[0..size].to_vec()).unwrap(),
length: dlength, display_name: StrBuilder {
}, offset: 0,
mailbox_list: vec, length: dlength,
})); },
}, mailbox_list: vec,
}),
);
}
IResult::Incomplete(i) => { IResult::Incomplete(i) => {
return IResult::Incomplete(i); return IResult::Incomplete(i);
}, }
} }
} }
named!(address<Address>, ws!(alt_complete!(mailbox | group)));
named!(address<Address>, ws!(
alt_complete!(mailbox | group)));
#[test] #[test]
fn test_address() { fn test_address() {
@ -456,18 +469,14 @@ fn test_address() {
qemu-devel <qemu-devel@nongnu.org>, qemu-block <qemu-block@nongnu.org>, qemu-devel <qemu-devel@nongnu.org>, qemu-block <qemu-block@nongnu.org>,
Alberto Garcia <berto@igalia.com>, Stefan Hajnoczi <stefanha@redhat.com>"; Alberto Garcia <berto@igalia.com>, Stefan Hajnoczi <stefanha@redhat.com>";
println!("{:?}", rfc2822address_list(s).unwrap()); println!("{:?}", rfc2822address_list(s).unwrap());
} }
named!(pub rfc2822address_list<Vec<Address>>, ws!( named!(pub rfc2822address_list<Vec<Address>>, ws!(
separated_list!(is_a!(","), address) 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) >>
( { ( {
@ -677,18 +686,17 @@ fn test_attachments() {
named!( named!(
content_type_parameter<(&str, &str)>, content_type_parameter<(&str, &str)>,
do_parse!( do_parse!(
tag!(";") >> tag!(";") >> name: terminated!(map_res!(ws!(take_until!("=")), from_utf8), tag!("="))
name: terminated!(map_res!(ws!(take_until!("=")), from_utf8), tag!("=")) >> >> value:
value: map_res!(ws!( map_res!(
alt_complete!(delimited!(tag!("\""), take_until!("\""), tag!("\"")) | is_not!(";"))), ws!(alt_complete!(
from_utf8) >> delimited!(tag!("\""), take_until!("\""), tag!("\"")) | is_not!(";")
( { )),
(name, value) from_utf8
} ) ) >> ({ (name, value) })
) )
); );
named!(pub content_type< (&str, &str, Vec<(&str, &str)>) >, named!(pub content_type< (&str, &str, Vec<(&str, &str)>) >,
do_parse!( do_parse!(
_type: map_res!(take_until!("/"), from_utf8) >> _type: map_res!(take_until!("/"), from_utf8) >>
@ -700,7 +708,6 @@ named!(pub content_type< (&str, &str, Vec<(&str, &str)>) >,
} ) } )
)); ));
named!(pub quoted_printable_text<String>, named!(pub quoted_printable_text<String>,
do_parse!( do_parse!(
bytes: many0!(alt_complete!( bytes: many0!(alt_complete!(

View File

@ -23,8 +23,8 @@ pub mod email;
pub use self::email::*; pub use self::email::*;
/* Mail backends. Currently only maildir is supported */ /* Mail backends. Currently only maildir is supported */
pub mod backends; pub mod backends;
use mailbox::backends::MailBackend;
use error::Result; use error::Result;
use mailbox::backends::MailBackend;
pub mod accounts; pub mod accounts;
pub use mailbox::accounts::Account; pub use mailbox::accounts::Account;
pub mod thread; pub mod thread;
@ -34,7 +34,6 @@ use conf::Folder;
use std::option::Option; use std::option::Option;
/// `Mailbox` represents a folder of mail. /// `Mailbox` represents a folder of mail.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Mailbox { pub struct Mailbox {
@ -44,7 +43,6 @@ pub struct Mailbox {
threads: Vec<Container>, threads: Vec<Container>,
} }
impl Mailbox { impl Mailbox {
pub fn new_dummy() -> Self { pub fn new_dummy() -> Self {
Mailbox { Mailbox {
@ -54,7 +52,11 @@ impl Mailbox {
threads: Vec::with_capacity(0), threads: Vec::with_capacity(0),
} }
} }
pub fn new(folder: &Folder, sent_folder: &Option<Result<Mailbox>>, collection: Result<Vec<Envelope>>) -> Result<Mailbox> { pub fn new(
folder: &Folder,
sent_folder: &Option<Result<Mailbox>>,
collection: Result<Vec<Envelope>>,
) -> Result<Mailbox> {
let mut collection: Vec<Envelope> = collection?; let mut collection: Vec<Envelope> = collection?;
collection.sort_by(|a, b| a.date().cmp(&b.date())); collection.sort_by(|a, b| a.date().cmp(&b.date()));
let (threads, threaded_collection) = build_threads(&mut collection, sent_folder); let (threads, threaded_collection) = build_threads(&mut collection, sent_folder);

View File

@ -19,9 +19,9 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use error::Result;
use mailbox::email::*; use mailbox::email::*;
use mailbox::Mailbox; use mailbox::Mailbox;
use error::Result;
extern crate fnv; extern crate fnv;
use self::fnv::FnvHashMap; use self::fnv::FnvHashMap;
@ -172,8 +172,8 @@ fn build_collection(
iasf += 1; iasf += 1;
let parent_id = if id_table.contains_key(r.raw()) { let parent_id = if id_table.contains_key(r.raw()) {
let p = id_table[r.raw()]; let p = id_table[r.raw()];
if !(threads[p].is_descendant(threads, &threads[curr_ref]) || if !(threads[p].is_descendant(threads, &threads[curr_ref])
threads[curr_ref].is_descendant(threads, &threads[p])) || threads[curr_ref].is_descendant(threads, &threads[p]))
{ {
threads[curr_ref].parent = Some(p); threads[curr_ref].parent = Some(p);
if threads[p].first_child.is_none() { if threads[p].first_child.is_none() {
@ -228,7 +228,6 @@ fn build_collection(
} }
} }
/// Builds threads from a collection. /// Builds threads from a collection.
pub fn build_threads( pub fn build_threads(
collection: &mut Vec<Envelope>, collection: &mut Vec<Envelope>,
@ -262,9 +261,9 @@ pub fn build_threads(
let sent_mailbox = sent_mailbox.unwrap(); let sent_mailbox = sent_mailbox.unwrap();
for x in &sent_mailbox.collection { for x in &sent_mailbox.collection {
if id_table.contains_key(x.message_id_raw()) || if id_table.contains_key(x.message_id_raw())
(!x.in_reply_to_raw().is_empty() && || (!x.in_reply_to_raw().is_empty()
id_table.contains_key(x.in_reply_to_raw())) && id_table.contains_key(x.in_reply_to_raw()))
{ {
let mut x: Envelope = (*x).clone(); let mut x: Envelope = (*x).clone();
if id_table.contains_key(x.message_id_raw()) { if id_table.contains_key(x.message_id_raw()) {
@ -277,8 +276,8 @@ pub fn build_threads(
assert!(threads[c].has_children()); assert!(threads[c].has_children());
threads[c].date = x.date(); threads[c].date = x.date();
x.set_thread(c); x.set_thread(c);
} else if !x.in_reply_to_raw().is_empty() && } else if !x.in_reply_to_raw().is_empty()
id_table.contains_key(x.in_reply_to_raw()) && id_table.contains_key(x.in_reply_to_raw())
{ {
let p = id_table[x.in_reply_to_raw()]; let p = id_table[x.in_reply_to_raw()];
let c = if id_table.contains_key(x.message_id_raw()) { let c = if id_table.contains_key(x.message_id_raw()) {
@ -300,8 +299,8 @@ pub fn build_threads(
tidx - 1 tidx - 1
}; };
threads[c].parent = Some(p); threads[c].parent = Some(p);
if threads[p].is_descendant(&threads, &threads[c]) || if threads[p].is_descendant(&threads, &threads[c])
threads[c].is_descendant(&threads, &threads[p]) || threads[c].is_descendant(&threads, &threads[p])
{ {
continue; continue;
} }
@ -344,8 +343,9 @@ pub fn build_threads(
let mut root_set = Vec::with_capacity(collection.len()); let mut root_set = Vec::with_capacity(collection.len());
'root_set: for v in id_table.values() { 'root_set: for v in id_table.values() {
if threads[*v].parent.is_none() { if threads[*v].parent.is_none() {
if !threads[*v].has_message() && threads[*v].has_children() && if !threads[*v].has_message()
!threads[threads[*v].first_child.unwrap()].has_sibling() && threads[*v].has_children()
&& !threads[threads[*v].first_child.unwrap()].has_sibling()
{ {
/* Do not promote the children if doing so would promote them to the root set /* Do not promote the children if doing so would promote them to the root set
* -- unless there is only one child, in which case, do. */ * -- unless there is only one child, in which case, do. */
@ -369,15 +369,14 @@ pub fn build_threads(
) { ) {
let thread = threads[i]; let thread = threads[i];
if threads[root_subject_idx].has_message() { if threads[root_subject_idx].has_message() {
let root_subject = let root_subject = collection[threads[root_subject_idx].message().unwrap()].subject();
collection[threads[root_subject_idx].message().unwrap()].subject();
/* If the Container has no Message, but does have children, remove this container but /* If the Container has no Message, but does have children, remove this container but
* promote its children to this level (that is, splice them in to the current child * promote its children to this level (that is, splice them in to the current child
* list.) */ * list.) */
if indentation > 0 && thread.has_message() { if indentation > 0 && thread.has_message() {
let subject = collection[thread.message().unwrap()].subject(); let subject = collection[thread.message().unwrap()].subject();
if subject == root_subject || if subject == root_subject
subject.starts_with("Re: ") && subject.ends_with(root_subject) || subject.starts_with("Re: ") && subject.ends_with(root_subject)
{ {
threads[i].set_show_subject(false); threads[i].set_show_subject(false);
} }
@ -420,6 +419,5 @@ pub fn build_threads(
); );
} }
(threads, threaded_collection) (threads, threaded_collection)
} }

View File

@ -27,8 +27,8 @@ The mail handling stuff is done in the `melib` crate which includes all backend
extern crate melib; extern crate melib;
extern crate ui; extern crate ui;
use ui::*;
pub use melib::*; pub use melib::*;
use ui::*;
use std::thread; use std::thread;
@ -40,17 +40,28 @@ use chan_signal::Signal;
extern crate nix; extern crate nix;
fn make_input_thread(sx: chan::Sender<ThreadEvent>, rx: chan::Receiver<bool>) -> thread::JoinHandle<()> { fn make_input_thread(
let stdin = std::io::stdin(); sx: chan::Sender<ThreadEvent>,
thread::Builder::new().name("input-thread".to_string()).spawn(move || { rx: chan::Receiver<bool>,
) -> thread::JoinHandle<()> {
get_events(stdin, let stdin = std::io::stdin();
|k| { thread::Builder::new()
sx.send(ThreadEvent::Input(k)); .name("input-thread".to_string())
}, .spawn(move || {
|| { get_events(
sx.send(ThreadEvent::UIEventType(UIEventType::ChangeMode(UIMode::Fork))); stdin,
}, rx)}).unwrap() |k| {
sx.send(ThreadEvent::Input(k));
},
|| {
sx.send(ThreadEvent::UIEventType(UIEventType::ChangeMode(
UIMode::Fork,
)));
},
rx,
)
})
.unwrap()
} }
fn main() { fn main() {
/* Lock all stdio outs */ /* Lock all stdio outs */
@ -68,7 +79,6 @@ fn main() {
* */ * */
let (sender, receiver) = chan::sync(::std::mem::size_of::<ThreadEvent>()); let (sender, receiver) = chan::sync(::std::mem::size_of::<ThreadEvent>());
/* /*
* Create async channel to block the input-thread if we need to fork and stop it from reading * Create async channel to block the input-thread if we need to fork and stop it from reading
* stdin, see get_events() for details * stdin, see get_events() for details
@ -78,18 +88,27 @@ fn main() {
let mut _thread_handler = make_input_thread(sender.clone(), rx.clone()); let mut _thread_handler = make_input_thread(sender.clone(), rx.clone());
/* Create the application State. This is the 'System' part of an ECS architecture */ /* Create the application State. This is the 'System' part of an ECS architecture */
let mut state = State::new(sender.clone(), tx ); let mut state = State::new(sender.clone(), tx);
/* Register some reasonably useful interfaces */ /* Register some reasonably useful interfaces */
let menu = Entity {component: Box::new(AccountMenu::new(&state.context.accounts)) }; let menu = Entity {
component: Box::new(AccountMenu::new(&state.context.accounts)),
};
let listing = MailListing::new(); let listing = MailListing::new();
let b = Entity { component: Box::new(listing) }; let b = Entity {
let window = Entity { component: Box::new(VSplit::new(menu, b, 90, true)) }; component: Box::new(listing),
let status_bar = Entity { component: Box::new(StatusBar::new(window)) }; };
let window = Entity {
component: Box::new(VSplit::new(menu, b, 90, true)),
};
let status_bar = Entity {
component: Box::new(StatusBar::new(window)),
};
state.register_entity(status_bar); state.register_entity(status_bar);
let xdg_notifications = Entity {
let xdg_notifications = Entity { component: Box::new(ui::components::notifications::XDGNotifications {}) }; component: Box::new(ui::components::notifications::XDGNotifications {}),
};
state.register_entity(xdg_notifications); state.register_entity(xdg_notifications);
/* Keep track of the input mode. See ui::UIMode for details */ /* Keep track of the input mode. See ui::UIMode for details */
@ -191,18 +210,18 @@ fn main() {
make_input_thread(sender.clone(), rx.clone()); make_input_thread(sender.clone(), rx.clone());
state.mode = UIMode::Normal; state.mode = UIMode::Normal;
state.render(); state.render();
}, }
Some(false) => { Some(false) => {
use std::{thread, time}; use std::{thread, time};
let ten_millis = time::Duration::from_millis(1500); let ten_millis = time::Duration::from_millis(1500);
thread::sleep(ten_millis); thread::sleep(ten_millis);
continue 'reap; continue 'reap;
}, }
None => {break 'reap;}, None => {
break 'reap;
}
} }
} }
} }
} }

View File

@ -2,13 +2,12 @@
Define a (x, y) point in the terminal display as a holder of a character, foreground/background Define a (x, y) point in the terminal display as a holder of a character, foreground/background
colors and attributes. colors and attributes.
*/ */
use std::ops::{Index, IndexMut, Deref, DerefMut}; use super::position::*;
use std::convert::From; use std::convert::From;
use std::fmt; use std::fmt;
use super::position::*; use std::ops::{Deref, DerefMut, Index, IndexMut};
use termion::color::AnsiValue; use termion::color::AnsiValue;
/// Types and implementations taken from rustty for convenience. /// Types and implementations taken from rustty for convenience.
pub trait CellAccessor: HasSize { pub trait CellAccessor: HasSize {
@ -180,7 +179,7 @@ impl fmt::Display for CellBuffer {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
'_y: for y in 0..self.rows { '_y: for y in 0..self.rows {
'_x: for x in 0..self.cols { '_x: for x in 0..self.cols {
let c: &char = &self[(x,y)].ch(); let c: &char = &self[(x, y)].ch();
write!(f, "{}", *c).unwrap(); write!(f, "{}", *c).unwrap();
if *c == '\n' { if *c == '\n' {
continue '_y; continue '_y;
@ -444,15 +443,17 @@ impl Color {
pub fn as_termion(&self) -> AnsiValue { pub fn as_termion(&self) -> AnsiValue {
match *self { match *self {
b @ Color::Black | b @ Color::Red | b @ Color::Green | b @ Color::Yellow | b @ Color::Blue | b @ Color::Magenta | b @ Color::Cyan | b @ Color::White | b @ Color::Default => b @ Color::Black
{ | b @ Color::Red
AnsiValue(b.as_byte()) | b @ Color::Green
}, | b @ Color::Yellow
Color::Byte(b) => { | b @ Color::Blue
AnsiValue(b as u8) | b @ Color::Magenta
}, | b @ Color::Cyan
| b @ Color::White
| b @ Color::Default => AnsiValue(b.as_byte()),
Color::Byte(b) => AnsiValue(b as u8),
} }
} }
} }

View File

@ -27,7 +27,12 @@ impl MailListing {
/// Helper function to format entry strings for MailListing */ /// Helper function to format entry strings for MailListing */
/* TODO: Make this configurable */ /* TODO: Make this configurable */
fn make_entry_string(e: &Envelope, idx: usize) -> String { fn make_entry_string(e: &Envelope, idx: usize) -> String {
format!("{} {} {:.85}",idx,&e.datetime().format("%Y-%m-%d %H:%M:%S").to_string(),e.subject()) format!(
"{} {} {:.85}",
idx,
&e.datetime().format("%Y-%m-%d %H:%M:%S").to_string(),
e.subject()
)
} }
pub fn new() -> Self { pub fn new() -> Self {
@ -51,42 +56,52 @@ impl MailListing {
self.cursor_pos.1 = self.new_cursor_pos.1; self.cursor_pos.1 = self.new_cursor_pos.1;
self.cursor_pos.0 = self.new_cursor_pos.0; self.cursor_pos.0 = self.new_cursor_pos.0;
let threaded = context.accounts[self.cursor_pos.0].runtime_settings.threaded; let threaded = context.accounts[self.cursor_pos.0]
.runtime_settings
.threaded;
// Get mailbox as a reference. // Get mailbox as a reference.
let mailbox = &mut context.accounts[self.cursor_pos.0][self.cursor_pos.1].as_ref().unwrap().as_ref().unwrap(); let mailbox = &mut context.accounts[self.cursor_pos.0][self.cursor_pos.1]
.as_ref()
.unwrap()
.as_ref()
.unwrap();
// Inform State that we changed the current folder view. // Inform State that we changed the current folder view.
context.replies.push_back(UIEvent { id: 0, event_type: UIEventType::RefreshMailbox((self.cursor_pos.0, self.cursor_pos.1)) }); context.replies.push_back(UIEvent {
id: 0,
event_type: UIEventType::RefreshMailbox((self.cursor_pos.0, self.cursor_pos.1)),
});
self.length = if threaded { self.length = if threaded {
mailbox.threaded_collection.len() mailbox.threaded_collection.len()
} else { } else {
mailbox.len() mailbox.len()
}; };
let mut content = CellBuffer::new(MAX_COLS, self.length+1, Cell::with_char(' ')); let mut content = CellBuffer::new(MAX_COLS, self.length + 1, Cell::with_char(' '));
if self.length == 0 { if self.length == 0 {
write_string_to_grid(&format!("Folder `{}` is empty.", write_string_to_grid(
mailbox.folder.name()), &format!("Folder `{}` is empty.", mailbox.folder.name()),
&mut content, &mut content,
Color::Default, Color::Default,
Color::Default, Color::Default,
((0, 0), (MAX_COLS-1, 0)), ((0, 0), (MAX_COLS - 1, 0)),
true); true,
);
self.content = content; self.content = content;
return; return;
} }
// TODO: Fix the threaded hell and refactor stuff into seperate functions and/or modules. // TODO: Fix the threaded hell and refactor stuff into seperate functions and/or modules.
if threaded { if threaded {
let mut indentations: Vec<bool> = Vec::with_capacity(6); let mut indentations: Vec<bool> = Vec::with_capacity(6);
let mut thread_idx = 0; // needed for alternate thread colors let mut thread_idx = 0; // needed for alternate thread colors
/* Draw threaded view. */ /* Draw threaded view. */
let mut iter = mailbox let mut iter = mailbox.threaded_collection.iter().enumerate().peekable();
let len = mailbox
.threaded_collection .threaded_collection
.iter() .len()
.enumerate() .to_string()
.peekable(); .chars()
let len = mailbox.threaded_collection.len().to_string().chars().count(); .count();
/* This is just a desugared for loop so that we can use .peek() */ /* This is just a desugared for loop so that we can use .peek() */
while let Some((idx, i)) = iter.next() { while let Some((idx, i)) = iter.next() {
let container = mailbox.thread(*i); let container = mailbox.thread(*i);
@ -98,12 +113,10 @@ impl MailListing {
assert_eq!(container.has_message(), true); assert_eq!(container.has_message(), true);
match iter.peek() { match iter.peek() {
Some(&(_, x)) Some(&(_, x)) if mailbox.thread(*x).indentation() == indentation => {
if mailbox.thread(*x).indentation() == indentation => indentations.pop();
{ indentations.push(true);
indentations.pop(); }
indentations.push(true);
}
_ => { _ => {
indentations.pop(); indentations.pop();
indentations.push(false); indentations.push(false);
@ -113,7 +126,7 @@ impl MailListing {
indentations.pop(); indentations.pop();
indentations.push(true); indentations.push(true);
} }
let envelope : &Envelope = &mailbox.collection[container.message().unwrap()]; let envelope: &Envelope = &mailbox.collection[container.message().unwrap()];
let fg_color = if !envelope.is_seen() { let fg_color = if !envelope.is_seen() {
Color::Byte(0) Color::Byte(0)
} else { } else {
@ -126,43 +139,46 @@ impl MailListing {
} else { } else {
Color::Default Color::Default
}; };
let (x, _) = write_string_to_grid(&MailListing::make_thread_entry(envelope, idx, indentation, container, &indentations, len), let (x, _) = write_string_to_grid(
&mut content, &MailListing::make_thread_entry(
fg_color, envelope,
bg_color, idx,
((0, idx) , (MAX_COLS-1, idx)), indentation,
false); container,
&indentations,
len,
),
&mut content,
fg_color,
bg_color,
((0, idx), (MAX_COLS - 1, idx)),
false,
);
for x in x..MAX_COLS { for x in x..MAX_COLS {
content[(x,idx)].set_ch(' '); content[(x, idx)].set_ch(' ');
content[(x,idx)].set_bg(bg_color); content[(x, idx)].set_bg(bg_color);
} }
match iter.peek() { match iter.peek() {
Some(&(_, x)) Some(&(_, x)) if mailbox.thread(*x).indentation() > indentation => {
if mailbox.thread(*x).indentation() > indentation => indentations.push(false);
{ }
indentations.push(false); Some(&(_, x)) if mailbox.thread(*x).indentation() < indentation => {
} for _ in 0..(indentation - mailbox.thread(*x).indentation()) {
Some(&(_, x)) indentations.pop();
if mailbox.thread(*x).indentation() < indentation =>
{
for _ in 0..(indentation - mailbox.thread(*x).indentation()) {
indentations.pop();
}
} }
}
_ => {} _ => {}
} }
} }
} else { } else {
// Populate `CellBuffer` with every entry. // Populate `CellBuffer` with every entry.
// TODO: Lazy load? // TODO: Lazy load?
let mut idx = 0; let mut idx = 0;
for y in 0..=self.length { for y in 0..=self.length {
if idx >= self.length { if idx >= self.length {
/* No more entries left, so fill the rest of the area with empty space */ /* No more entries left, so fill the rest of the area with empty space */
clear_area(&mut content, clear_area(&mut content, ((0, y), (MAX_COLS - 1, self.length)));
((0, y), (MAX_COLS-1, self.length)));
break; break;
} }
/* Write an entire line for each envelope entry. */ /* Write an entire line for each envelope entry. */
@ -180,28 +196,35 @@ impl MailListing {
} else { } else {
Color::Default Color::Default
}; };
let (x, y)= write_string_to_grid(&MailListing::make_entry_string(envelope, idx), let (x, y) = write_string_to_grid(
&mut content, &MailListing::make_entry_string(envelope, idx),
fg_color, &mut content,
bg_color, fg_color,
((0, y) , (MAX_COLS-1, y)), bg_color,
false); ((0, y), (MAX_COLS - 1, y)),
false,
);
for x in x..MAX_COLS { for x in x..MAX_COLS {
content[(x,y)].set_ch(' '); content[(x, y)].set_ch(' ');
content[(x,y)].set_bg(bg_color); content[(x, y)].set_bg(bg_color);
} }
idx+=1; idx += 1;
} }
} }
self.content = content; self.content = content;
} }
fn highlight_line(&self, grid: &mut CellBuffer, area: Area, idx: usize, context: &Context) { fn highlight_line(&self, grid: &mut CellBuffer, area: Area, idx: usize, context: &Context) {
let threaded = context.accounts[self.cursor_pos.0].runtime_settings.threaded; let threaded = context.accounts[self.cursor_pos.0]
let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1].as_ref().unwrap().as_ref().unwrap(); .runtime_settings
.threaded;
let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1]
.as_ref()
.unwrap()
.as_ref()
.unwrap();
let envelope: &Envelope = if threaded { let envelope: &Envelope = if threaded {
let i = mailbox.threaded_mail(idx); let i = mailbox.threaded_mail(idx);
&mailbox.collection[i] &mailbox.collection[i]
@ -237,7 +260,7 @@ impl MailListing {
let bottom_right = bottom_right!(area); let bottom_right = bottom_right!(area);
if self.length == 0 { if self.length == 0 {
clear_area(grid, area); clear_area(grid, area);
copy_area(grid, &self.content, area, ((0, 0), (MAX_COLS-1, 0))); copy_area(grid, &self.content, area, ((0, 0), (MAX_COLS - 1, 0)));
context.dirty_areas.push_back(area); context.dirty_areas.push_back(area);
return; return;
} }
@ -245,8 +268,7 @@ impl MailListing {
let prev_page_no = (self.cursor_pos.2).wrapping_div(rows); let prev_page_no = (self.cursor_pos.2).wrapping_div(rows);
let page_no = (self.new_cursor_pos.2).wrapping_div(rows); let page_no = (self.new_cursor_pos.2).wrapping_div(rows);
let top_idx = page_no*rows; let top_idx = page_no * rows;
/* If cursor position has changed, remove the highlight from the previous position and /* If cursor position has changed, remove the highlight from the previous position and
* apply it in the new one. */ * apply it in the new one. */
@ -257,31 +279,57 @@ impl MailListing {
if *idx >= self.length { if *idx >= self.length {
continue; //bounds check continue; //bounds check
} }
let new_area = (set_y(upper_left, get_y(upper_left)+(*idx % rows)), set_y(bottom_right, get_y(upper_left) + (*idx % rows))); let new_area = (
set_y(upper_left, get_y(upper_left) + (*idx % rows)),
set_y(bottom_right, get_y(upper_left) + (*idx % rows)),
);
self.highlight_line(grid, new_area, *idx, context); self.highlight_line(grid, new_area, *idx, context);
context.dirty_areas.push_back(new_area); context.dirty_areas.push_back(new_area);
} }
return; return;
} else if self.cursor_pos != self.new_cursor_pos { } else if self.cursor_pos != self.new_cursor_pos {
self.cursor_pos = self.new_cursor_pos; self.cursor_pos = self.new_cursor_pos;
} }
/* Page_no has changed, so draw new page */ /* Page_no has changed, so draw new page */
copy_area(grid, &self.content, area, ((0, top_idx), (MAX_COLS - 1, self.length))); copy_area(
self.highlight_line(grid, (set_y(upper_left, get_y(upper_left)+(self.cursor_pos.2 % rows)), set_y(bottom_right, get_y(upper_left) + (self.cursor_pos.2 % rows))), self.cursor_pos.2, context); grid,
&self.content,
area,
((0, top_idx), (MAX_COLS - 1, self.length)),
);
self.highlight_line(
grid,
(
set_y(upper_left, get_y(upper_left) + (self.cursor_pos.2 % rows)),
set_y(bottom_right, get_y(upper_left) + (self.cursor_pos.2 % rows)),
),
self.cursor_pos.2,
context,
);
context.dirty_areas.push_back(area); context.dirty_areas.push_back(area);
} }
fn make_thread_entry(envelope: &Envelope, idx: usize, indent: usize, fn make_thread_entry(
container: &Container, indentations: &Vec<bool>, idx_width: usize) -> String { envelope: &Envelope,
idx: usize,
indent: usize,
container: &Container,
indentations: &Vec<bool>,
idx_width: usize,
) -> String {
let has_sibling = container.has_sibling(); let has_sibling = container.has_sibling();
let has_parent = container.has_parent(); let has_parent = container.has_parent();
let show_subject = container.show_subject(); let show_subject = container.show_subject();
let mut s = format!("{}{}{} ", idx, " ".repeat(idx_width + 2 - (idx.to_string().chars().count())), MailListing::format_date(&envelope)); let mut s = format!(
"{}{}{} ",
idx,
" ".repeat(idx_width + 2 - (idx.to_string().chars().count())),
MailListing::format_date(&envelope)
);
for i in 0..indent { for i in 0..indent {
if indentations.len() > i && indentations[i] if indentations.len() > i && indentations[i] {
{
s.push('│'); s.push('│');
} else { } else {
s.push(' '); s.push(' ');
@ -298,7 +346,8 @@ impl MailListing {
} else { } else {
s.push('└'); s.push('└');
} }
s.push('─'); s.push('>'); s.push('─');
s.push('>');
} }
if show_subject { if show_subject {
@ -306,7 +355,7 @@ impl MailListing {
} }
let attach_count = envelope.body().count_attachments(); let attach_count = envelope.body().count_attachments();
if attach_count > 1 { if attach_count > 1 {
s.push_str(&format!(" {}", attach_count-1)); s.push_str(&format!(" {}", attach_count - 1));
} }
s s
} }
@ -314,20 +363,12 @@ impl MailListing {
let d = std::time::UNIX_EPOCH + std::time::Duration::from_secs(envelope.date()); let d = std::time::UNIX_EPOCH + std::time::Duration::from_secs(envelope.date());
let now: std::time::Duration = std::time::SystemTime::now().duration_since(d).unwrap(); let now: std::time::Duration = std::time::SystemTime::now().duration_since(d).unwrap();
match now.as_secs() { match now.as_secs() {
n if n < 10*60*60 => { n if n < 10 * 60 * 60 => format!("{} hours ago{}", n / (60 * 60), " ".repeat(8)),
format!("{} hours ago{}", n / (60*60), " ".repeat(8)) n if n < 24 * 60 * 60 => format!("{} hours ago{}", n / (60 * 60), " ".repeat(7)),
}, n if n < 4 * 24 * 60 * 60 => {
n if n < 24*60*60 => { format!("{} days ago{}", n / (24 * 60 * 60), " ".repeat(9))
format!("{} hours ago{}", n / (60*60), " ".repeat(7)) }
}, _ => envelope.datetime().format("%Y-%m-%d %H:%M:%S").to_string(),
n if n < 4*24*60*60 => {
format!("{} days ago{}", n / (24*60*60), " ".repeat(9))
},
_ => {
envelope.datetime().format("%Y-%m-%d %H:%M:%S").to_string()
},
} }
} }
} }
@ -352,7 +393,7 @@ impl Component for MailListing {
/* Render the mail body in a pager, basically copy what HSplit does */ /* Render the mail body in a pager, basically copy what HSplit does */
let total_rows = get_y(bottom_right) - get_y(upper_left); let total_rows = get_y(bottom_right) - get_y(upper_left);
let pager_ratio = context.runtime_settings.pager.pager_ratio; let pager_ratio = context.runtime_settings.pager.pager_ratio;
let bottom_entity_rows = (pager_ratio*total_rows )/100; let bottom_entity_rows = (pager_ratio * total_rows) / 100;
if bottom_entity_rows > total_rows { if bottom_entity_rows > total_rows {
clear_area(grid, area); clear_area(grid, area);
@ -360,9 +401,14 @@ impl Component for MailListing {
return; return;
} }
let mid = get_y(upper_left) + total_rows - bottom_entity_rows; let mid = get_y(upper_left) + total_rows - bottom_entity_rows;
self.draw_list(grid, self.draw_list(
(upper_left, (get_x(bottom_right), get_y(upper_left)+ mid-1)), grid,
context); (
upper_left,
(get_x(bottom_right), get_y(upper_left) + mid - 1),
),
context,
);
if self.length == 0 { if self.length == 0 {
self.dirty = false; self.dirty = false;
return; return;
@ -378,16 +424,22 @@ impl Component for MailListing {
for i in get_x(upper_left)..=get_x(bottom_right) { for i in get_x(upper_left)..=get_x(bottom_right) {
grid[(i, mid)].set_ch('─'); grid[(i, mid)].set_ch('─');
} }
context.dirty_areas.push_back((set_y(upper_left, mid), set_y(bottom_right, mid))); context
.dirty_areas
.push_back((set_y(upper_left, mid), set_y(bottom_right, mid)));
} }
// TODO: Make headers view configurable // TODO: Make headers view configurable
if !self.dirty { if !self.dirty {
self.view.as_mut().map(|v| v.draw(grid, (set_y(upper_left, mid + 1), bottom_right), context)); self.view
.as_mut()
.map(|v| v.draw(grid, (set_y(upper_left, mid + 1), bottom_right), context));
return; return;
} }
self.view = Some(MailView::new(self.cursor_pos, None, None)); self.view = Some(MailView::new(self.cursor_pos, None, None));
self.view.as_mut().map(|v| v.draw(grid, (set_y(upper_left, mid + 1), bottom_right), context)); self.view
.as_mut()
.map(|v| v.draw(grid, (set_y(upper_left, mid + 1), bottom_right), context));
self.dirty = false; self.dirty = false;
} }
} }
@ -398,17 +450,17 @@ impl Component for MailListing {
self.new_cursor_pos.2 -= 1; self.new_cursor_pos.2 -= 1;
self.dirty = true; self.dirty = true;
} }
}, }
UIEventType::Input(Key::Down) => { UIEventType::Input(Key::Down) => {
if self.length > 0 && self.new_cursor_pos.2 < self.length - 1 { if self.length > 0 && self.new_cursor_pos.2 < self.length - 1 {
self.new_cursor_pos.2 += 1; self.new_cursor_pos.2 += 1;
self.dirty = true; self.dirty = true;
} }
}, }
UIEventType::Input(Key::Char('\n')) if self.unfocused == false => { UIEventType::Input(Key::Char('\n')) if self.unfocused == false => {
self.unfocused = true; self.unfocused = true;
self.dirty = true; self.dirty = true;
}, }
UIEventType::Input(Key::Char('m')) if self.unfocused == false => { UIEventType::Input(Key::Char('m')) if self.unfocused == false => {
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
/* Kill input thread so that spawned command can be sole receiver of stdin */ /* Kill input thread so that spawned command can be sole receiver of stdin */
@ -446,16 +498,21 @@ impl Component for MailListing {
* Main loop will wait on children and when they reap them the loop spawns a new * Main loop will wait on children and when they reap them the loop spawns a new
* input-thread * input-thread
*/ */
context.replies.push_back(UIEvent { id: 0, event_type: UIEventType::Fork(ForkType::NewDraft(f, output)) }); context.replies.push_back(UIEvent {
context.replies.push_back(UIEvent { id: 0, event_type: UIEventType::ChangeMode(UIMode::Fork) }); id: 0,
event_type: UIEventType::Fork(ForkType::NewDraft(f, output)),
});
context.replies.push_back(UIEvent {
id: 0,
event_type: UIEventType::ChangeMode(UIMode::Fork),
});
return; return;
}, }
UIEventType::Input(Key::Char('i')) if self.unfocused == true => { UIEventType::Input(Key::Char('i')) if self.unfocused == true => {
self.unfocused = false; self.unfocused = false;
self.dirty = true; self.dirty = true;
self.view = None; self.view = None;
}
},
UIEventType::Input(Key::Char(k @ 'J')) | UIEventType::Input(Key::Char(k @ 'K')) => { UIEventType::Input(Key::Char(k @ 'J')) | UIEventType::Input(Key::Char(k @ 'K')) => {
let folder_length = context.accounts[self.cursor_pos.0].len(); let folder_length = context.accounts[self.cursor_pos.0].len();
let accounts_length = context.accounts.len(); let accounts_length = context.accounts.len();
@ -465,29 +522,29 @@ impl Component for MailListing {
self.new_cursor_pos.1 = self.cursor_pos.1 + 1; self.new_cursor_pos.1 = self.cursor_pos.1 + 1;
self.dirty = true; self.dirty = true;
self.refresh_mailbox(context); self.refresh_mailbox(context);
} else if accounts_length > 0 && self.new_cursor_pos.0 < accounts_length - 1 { } else if accounts_length > 0 && self.new_cursor_pos.0 < accounts_length - 1
{
self.new_cursor_pos.0 = self.cursor_pos.0 + 1; self.new_cursor_pos.0 = self.cursor_pos.0 + 1;
self.new_cursor_pos.1 = 0; self.new_cursor_pos.1 = 0;
self.dirty = true; self.dirty = true;
self.refresh_mailbox(context); self.refresh_mailbox(context);
} }
}, }
'K' => { 'K' => {
if self.cursor_pos.1 > 0 { if self.cursor_pos.1 > 0 {
self.new_cursor_pos.1 = self.cursor_pos.1 - 1; self.new_cursor_pos.1 = self.cursor_pos.1 - 1;
self.dirty = true; self.dirty = true;
self.refresh_mailbox(context); self.refresh_mailbox(context);
} else if self.cursor_pos.0 > 0 { } else if self.cursor_pos.0 > 0 {
self.new_cursor_pos.0 = self.cursor_pos.0 - 1; self.new_cursor_pos.0 = self.cursor_pos.0 - 1;
self.new_cursor_pos.1 = 0; self.new_cursor_pos.1 = 0;
self.dirty = true; self.dirty = true;
self.refresh_mailbox(context); self.refresh_mailbox(context);
} }
}, }
_ => { _ => {}
},
} }
}, }
UIEventType::Input(Key::Char(k @ 'h')) | UIEventType::Input(Key::Char(k @ 'l')) => { UIEventType::Input(Key::Char(k @ 'h')) | UIEventType::Input(Key::Char(k @ 'l')) => {
let accounts_length = context.accounts.len(); let accounts_length = context.accounts.len();
match k { match k {
@ -496,40 +553,38 @@ impl Component for MailListing {
self.new_cursor_pos.1 = 0; self.new_cursor_pos.1 = 0;
self.dirty = true; self.dirty = true;
self.refresh_mailbox(context); self.refresh_mailbox(context);
}, }
'l' if self.cursor_pos.0 > 0 => { 'l' if self.cursor_pos.0 > 0 => {
self.new_cursor_pos.0 = self.cursor_pos.0 - 1; self.new_cursor_pos.0 = self.cursor_pos.0 - 1;
self.new_cursor_pos.1 = 0; self.new_cursor_pos.1 = 0;
self.dirty = true; self.dirty = true;
self.refresh_mailbox(context); self.refresh_mailbox(context);
}, }
_ => { _ => {}
},
} }
}, }
UIEventType::RefreshMailbox(_) => { UIEventType::RefreshMailbox(_) => {
self.dirty = true; self.dirty = true;
self.view = None; self.view = None;
}, }
UIEventType::ChangeMode(UIMode::Normal) => { UIEventType::ChangeMode(UIMode::Normal) => {
self.dirty = true; self.dirty = true;
}, }
UIEventType::Resize => { UIEventType::Resize => {
self.dirty = true; self.dirty = true;
}, }
UIEventType::Action(ref action) => { UIEventType::Action(ref action) => match action {
match action { Action::MailListing(MailListingAction::ToggleThreaded) => {
Action::MailListing(MailListingAction::ToggleThreaded) => { context.accounts[self.cursor_pos.0]
context.accounts[self.cursor_pos.0].runtime_settings.threaded = !context.accounts[self.cursor_pos.0].runtime_settings.threaded; .runtime_settings
self.refresh_mailbox(context); .threaded = !context.accounts[self.cursor_pos.0]
self.dirty = true; .runtime_settings
.threaded;
self.refresh_mailbox(context);
}, self.dirty = true;
} }
}, },
_ => { _ => {}
},
} }
if let Some(ref mut v) = self.view { if let Some(ref mut v) = self.view {
v.process_event(event, context); v.process_event(event, context);

View File

@ -1,5 +1,5 @@
/*! Entities that handle Mail specific functions. /*! Entities that handle Mail specific functions.
*/ */
use super::*; use super::*;
pub mod listing; pub mod listing;
@ -7,7 +7,6 @@ pub mod view;
pub use listing::*; pub use listing::*;
pub use view::*; pub use view::*;
#[derive(Debug)] #[derive(Debug)]
struct AccountMenuEntry { struct AccountMenuEntry {
name: String, name: String,
@ -15,7 +14,6 @@ struct AccountMenuEntry {
entries: Vec<(usize, Folder)>, entries: Vec<(usize, Folder)>,
} }
#[derive(Debug)] #[derive(Debug)]
pub struct AccountMenu { pub struct AccountMenu {
accounts: Vec<AccountMenuEntry>, accounts: Vec<AccountMenuEntry>,
@ -25,8 +23,10 @@ pub struct AccountMenu {
impl AccountMenu { impl AccountMenu {
pub fn new(accounts: &Vec<Account>) -> Self { pub fn new(accounts: &Vec<Account>) -> Self {
let accounts = accounts.iter().enumerate().map(|(i, a)| { let accounts = accounts
AccountMenuEntry { .iter()
.enumerate()
.map(|(i, a)| AccountMenuEntry {
name: a.name().to_string(), name: a.name().to_string(),
index: i, index: i,
entries: { entries: {
@ -34,9 +34,10 @@ impl AccountMenu {
for (idx, acc) in a.list_folders().iter().enumerate() { for (idx, acc) in a.list_folders().iter().enumerate() {
entries.push((idx, acc.clone())); entries.push((idx, acc.clone()));
} }
entries} entries
} },
}).collect(); })
.collect();
AccountMenu { AccountMenu {
accounts: accounts, accounts: accounts,
dirty: true, dirty: true,
@ -50,10 +51,9 @@ impl AccountMenu {
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 highlight = self.cursor.map(|(x, _)| x == a.index).unwrap_or(false);
let highlight = self.cursor.map(|(x,_)| x == a.index).unwrap_or(false); let mut parents: Vec<Option<usize>> = vec![None; a.entries.len()];
let mut parents: Vec<Option<usize>> = vec!(None; a.entries.len());
for (idx, e) in a.entries.iter().enumerate() { for (idx, e) in a.entries.iter().enumerate() {
for c in e.1.children() { for c in e.1.children() {
@ -79,15 +79,22 @@ impl AccountMenu {
depth.push(c); depth.push(c);
} }
fn print(root: usize, parents: &Vec<Option<usize>>, depth: &mut String, entries: &Vec<(usize, Folder)>, s: &mut String, inc: &mut usize) -> () { fn print(
root: usize,
parents: &Vec<Option<usize>>,
depth: &mut String,
entries: &Vec<(usize, Folder)>,
s: &mut String,
inc: &mut usize,
) -> () {
let len = s.len(); let len = s.len();
s.insert_str(len, &format!("{} {}\n ", *inc, &entries[root].1.name())); s.insert_str(len, &format!("{} {}\n ", *inc, &entries[root].1.name()));
*inc += 1; *inc += 1;
let children_no = entries[root].1.children().len(); let children_no = entries[root].1.children().len();
for (idx, child) in entries[root].1.children().iter().enumerate() { for (idx, child) in entries[root].1.children().iter().enumerate() {
let len = s.len(); let len = s.len();
s.insert_str(len, &format!("{}├─", depth)); s.insert_str(len, &format!("{}├─", depth));
push(depth, if idx == children_no - 1 {'│'} else { ' ' }); push(depth, if idx == children_no - 1 { '│' } else { ' ' });
print(*child, parents, depth, entries, s, inc); print(*child, parents, depth, entries, s, inc);
pop(depth); pop(depth);
} }
@ -128,17 +135,24 @@ impl AccountMenu {
Color::Default Color::Default
}; };
let (x, _) = write_string_to_grid(&s, let (x, _) = write_string_to_grid(
grid, &s,
color_fg, grid,
color_bg, color_fg,
(set_y(upper_left, y), bottom_right), color_bg,
false); (set_y(upper_left, y), bottom_right),
false,
);
if highlight && idx > 1 && self.cursor.unwrap().1 == idx - 2 { if highlight && idx > 1 && self.cursor.unwrap().1 == idx - 2 {
change_colors(grid, ((x, y),(get_x(bottom_right)+1, y)), color_fg , color_bg); change_colors(
grid,
((x, y), (get_x(bottom_right) + 1, y)),
color_fg,
color_bg,
);
} else { } else {
change_colors(grid, ((x, y),set_y(bottom_right, y)), color_fg , color_bg); change_colors(grid, ((x, y), set_y(bottom_right, y)), color_fg, color_bg);
} }
idx += 1; idx += 1;
} }
@ -147,7 +161,6 @@ impl AccountMenu {
} else { } else {
idx - 1 idx - 1
} }
} }
} }
@ -162,9 +175,7 @@ impl Component for AccountMenu {
self.dirty = false; self.dirty = false;
let mut y = get_y(upper_left); let mut y = get_y(upper_left);
for a in &self.accounts { for a in &self.accounts {
y += self.print_account(grid, y += self.print_account(grid, (set_y(upper_left, y), bottom_right), &a);
(set_y(upper_left, y), bottom_right),
&a);
} }
context.dirty_areas.push_back(area); context.dirty_areas.push_back(area);
@ -174,15 +185,14 @@ impl Component for AccountMenu {
UIEventType::RefreshMailbox(c) => { UIEventType::RefreshMailbox(c) => {
self.cursor = Some(c); self.cursor = Some(c);
self.dirty = true; self.dirty = true;
}, }
UIEventType::ChangeMode(UIMode::Normal) => { UIEventType::ChangeMode(UIMode::Normal) => {
self.dirty = true; self.dirty = true;
}, }
UIEventType::Resize => { UIEventType::Resize => {
self.dirty = true; self.dirty = true;
}, }
_ => { _ => {}
},
} }
} }
fn is_dirty(&self) -> bool { fn is_dirty(&self) -> bool {

View File

@ -26,16 +26,16 @@
*/ */
use super::*; use super::*;
pub mod utilities;
pub mod mail; pub mod mail;
pub mod notifications; pub mod notifications;
pub mod utilities;
pub use utilities::*;
pub use mail::*; pub use mail::*;
pub use utilities::*;
use super::cells::{Color, CellBuffer}; use super::cells::{CellBuffer, Color};
use super::position::{Area, }; use super::position::Area;
use super::{UIEvent, UIEventType, Key}; use super::{Key, UIEvent, UIEventType};
/// The upper and lower boundary char. /// The upper and lower boundary char.
const HORZ_BOUNDARY: char = '─'; const HORZ_BOUNDARY: char = '─';
@ -84,11 +84,18 @@ pub trait Component {
} }
} }
// TODO: word break. // TODO: word break.
pub fn copy_area_with_break(grid_dest: &mut CellBuffer, grid_src: &CellBuffer, dest: Area, src: Area) { pub fn copy_area_with_break(
grid_dest: &mut CellBuffer,
grid_src: &CellBuffer,
dest: Area,
src: Area,
) {
if !is_valid_area!(dest) || !is_valid_area!(src) { if !is_valid_area!(dest) || !is_valid_area!(src) {
eprintln!("BUG: Invalid areas in copy_area:\n src: {:?}\n dest: {:?}", src, dest); eprintln!(
"BUG: Invalid areas in copy_area:\n src: {:?}\n dest: {:?}",
src, dest
);
return; return;
} }
let mut src_x = get_x(upper_left!(src)); let mut src_x = get_x(upper_left!(src));
@ -105,7 +112,7 @@ pub fn copy_area_with_break(grid_dest: &mut CellBuffer, grid_src: &CellBuffer, d
continue 'y_; continue 'y_;
} }
grid_dest[(x,y)] = grid_src[(src_x, src_y)]; grid_dest[(x, y)] = grid_src[(src_x, src_y)];
src_x += 1; src_x += 1;
if src_x >= get_x(bottom_right!(src)) { if src_x >= get_x(bottom_right!(src)) {
src_y += 1; src_y += 1;
@ -120,11 +127,13 @@ pub fn copy_area_with_break(grid_dest: &mut CellBuffer, grid_src: &CellBuffer, d
} }
} }
/// Copy a source `Area` to a destination. /// Copy a source `Area` to a destination.
pub fn copy_area(grid_dest: &mut CellBuffer, grid_src: &CellBuffer, dest: Area, src: Area) { pub fn copy_area(grid_dest: &mut CellBuffer, grid_src: &CellBuffer, dest: Area, src: Area) {
if !is_valid_area!(dest) || !is_valid_area!(src) { if !is_valid_area!(dest) || !is_valid_area!(src) {
eprintln!("BUG: Invalid areas in copy_area:\n src: {:?}\n dest: {:?}", src, dest); eprintln!(
"BUG: Invalid areas in copy_area:\n src: {:?}\n dest: {:?}",
src, dest
);
return; return;
} }
let mut src_x = get_x(upper_left!(src)); let mut src_x = get_x(upper_left!(src));
@ -132,7 +141,7 @@ pub fn copy_area(grid_dest: &mut CellBuffer, grid_src: &CellBuffer, dest: Area,
for y in get_y(upper_left!(dest))..=get_y(bottom_right!(dest)) { for y in get_y(upper_left!(dest))..=get_y(bottom_right!(dest)) {
'for_x: for x in get_x(upper_left!(dest))..=get_x(bottom_right!(dest)) { 'for_x: for x in get_x(upper_left!(dest))..=get_x(bottom_right!(dest)) {
grid_dest[(x,y)] = grid_src[(src_x, src_y)]; grid_dest[(x, y)] = grid_src[(src_x, src_y)];
if src_x >= get_x(bottom_right!(src)) { if src_x >= get_x(bottom_right!(src)) {
break 'for_x; break 'for_x;
} }
@ -140,7 +149,10 @@ pub fn copy_area(grid_dest: &mut CellBuffer, grid_src: &CellBuffer, dest: Area,
} }
src_x = get_x(upper_left!(src)); src_x = get_x(upper_left!(src));
if src_y >= get_y(bottom_right!(src)) { if src_y >= get_y(bottom_right!(src)) {
clear_area(grid_dest, ((get_x(upper_left!(dest)), y), bottom_right!(dest))); clear_area(
grid_dest,
((get_x(upper_left!(dest)), y), bottom_right!(dest)),
);
break; break;
} }
src_y += 1; src_y += 1;
@ -155,32 +167,41 @@ pub fn change_colors(grid: &mut CellBuffer, area: Area, fg_color: Color, bg_colo
} }
for y in get_y(upper_left!(area))..=get_y(bottom_right!(area)) { for y in get_y(upper_left!(area))..=get_y(bottom_right!(area)) {
for x in get_x(upper_left!(area))..=get_x(bottom_right!(area)) { for x in get_x(upper_left!(area))..=get_x(bottom_right!(area)) {
grid[(x,y)].set_fg(fg_color); grid[(x, y)].set_fg(fg_color);
grid[(x,y)].set_bg(bg_color); grid[(x, y)].set_bg(bg_color);
} }
} }
} }
/// Write an `&str` to a `CellBuffer` in a specified `Area` with the passed colors. /// Write an `&str` to a `CellBuffer` in a specified `Area` with the passed colors.
fn write_string_to_grid(s: &str, grid: &mut CellBuffer, fg_color: Color, bg_color: Color, area: Area, line_break: bool) -> Pos { fn write_string_to_grid(
s: &str,
grid: &mut CellBuffer,
fg_color: Color,
bg_color: Color,
area: Area,
line_break: bool,
) -> Pos {
let bounds = grid.size(); let bounds = grid.size();
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 (mut x, mut y) = upper_left; let (mut x, mut y) = upper_left;
if y > (get_y(bottom_right)) || x > get_x(bottom_right) || if y > (get_y(bottom_right))
y > get_y(bounds) || x > get_x(bounds) { || x > get_x(bottom_right)
eprintln!(" Invalid area with string {} and area {:?}", s, area); || y > get_y(bounds)
|| x > get_x(bounds)
{
eprintln!(" Invalid area with string {} and area {:?}", s, area);
return (x, y); return (x, y);
} }
for l in s.lines() { for l in s.lines() {
'char: for c in l.chars() { 'char: for c in l.chars() {
grid[(x,y)].set_ch(c); grid[(x, y)].set_ch(c);
grid[(x,y)].set_fg(fg_color); grid[(x, y)].set_fg(fg_color);
grid[(x,y)].set_bg(bg_color); grid[(x, y)].set_bg(bg_color);
x += 1; x += 1;
if x == (get_x(bottom_right))+1 || x > get_x(bounds) { if x == (get_x(bottom_right)) + 1 || x > get_x(bounds) {
x = get_x(upper_left); x = get_x(upper_left);
y += 1; y += 1;
if y >= (get_y(bottom_right)) || y > get_y(bounds) { if y >= (get_y(bottom_right)) || y > get_y(bounds) {
@ -204,9 +225,9 @@ fn clear_area(grid: &mut CellBuffer, area: Area) {
let bottom_right = bottom_right!(area); let bottom_right = bottom_right!(area);
for y in get_y(upper_left)..=get_y(bottom_right) { for y in get_y(upper_left)..=get_y(bottom_right) {
for x in get_x(upper_left)..=get_x(bottom_right) { for x in get_x(upper_left)..=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);
} }
} }
} }

View File

@ -9,20 +9,18 @@ use super::*;
pub struct XDGNotifications {} pub struct XDGNotifications {}
impl Component for XDGNotifications { impl Component for XDGNotifications {
fn draw(&mut self, _grid: &mut CellBuffer, _area: Area, _context: &mut Context) { fn draw(&mut self, _grid: &mut CellBuffer, _area: Area, _context: &mut 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::Notification(ref t) => { UIEventType::Notification(ref t) => {
notify_Notification::new() notify_Notification::new()
.summary("Refresh Event") .summary("Refresh Event")
.body(t) .body(t)
.icon("dialog-information") .icon("dialog-information")
.show().unwrap(); .show()
}, .unwrap();
}
_ => {} _ => {}
} }
} }
} }

View File

@ -1,5 +1,5 @@
/*! Various useful components that can be used in a generic fashion. /*! Various useful components that can be used in a generic fashion.
*/ */
use super::*; use super::*;
/// A horizontally split in half container. /// A horizontally split in half container.
@ -21,7 +21,6 @@ impl HSplit {
} }
} }
impl Component for HSplit { impl Component for HSplit {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
if !is_valid_area!(area) { if !is_valid_area!(area) {
@ -30,7 +29,7 @@ impl Component for HSplit {
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 total_rows = get_y(bottom_right) - get_y(upper_left); let total_rows = get_y(bottom_right) - get_y(upper_left);
let bottom_entity_height = (self.ratio*total_rows )/100; let bottom_entity_height = (self.ratio * total_rows) / 100;
let mid = get_y(upper_left) + total_rows - bottom_entity_height; let mid = get_y(upper_left) + total_rows - bottom_entity_height;
if self.show_divider { if self.show_divider {
@ -38,19 +37,26 @@ impl Component for HSplit {
grid[(i, mid)].set_ch('─'); grid[(i, mid)].set_ch('─');
} }
} }
let _ = self.top.component.draw(grid, let _ = self.top.component.draw(
(upper_left, (get_x(bottom_right), get_y(upper_left) + mid-1)), grid,
context); (
let _ = self.bottom.component.draw(grid, upper_left,
((get_x(upper_left), get_y(upper_left) + mid), bottom_right), (get_x(bottom_right), get_y(upper_left) + mid - 1),
context); ),
context,
);
let _ = self.bottom.component.draw(
grid,
((get_x(upper_left), get_y(upper_left) + mid), bottom_right),
context,
);
} }
fn process_event(&mut self, event: &UIEvent, context: &mut Context) { fn process_event(&mut self, event: &UIEvent, context: &mut Context) {
self.top.rcv_event(event, context); self.top.rcv_event(event, context);
self.bottom.rcv_event(event, context); self.bottom.rcv_event(event, context);
} }
fn is_dirty(&self) -> bool { fn is_dirty(&self) -> bool {
self.top.component.is_dirty() || self.bottom.component.is_dirty() self.top.component.is_dirty() || self.bottom.component.is_dirty()
} }
} }
@ -74,7 +80,6 @@ impl VSplit {
} }
} }
impl Component for VSplit { impl Component for VSplit {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
if !is_valid_area!(area) { if !is_valid_area!(area) {
@ -83,16 +88,18 @@ impl Component for VSplit {
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 total_cols = get_x(bottom_right) - get_x(upper_left); let total_cols = get_x(bottom_right) - get_x(upper_left);
let right_entity_width = (self.ratio*total_cols )/100; let right_entity_width = (self.ratio * total_cols) / 100;
let mid = get_x(bottom_right) - right_entity_width; let mid = get_x(bottom_right) - right_entity_width;
if get_y(upper_left)> 1 { if get_y(upper_left) > 1 {
let c = grid.get(mid, get_y(upper_left)-1).map(|a| a.ch()).unwrap_or_else(|| ' '); let c = grid.get(mid, get_y(upper_left) - 1)
.map(|a| a.ch())
.unwrap_or_else(|| ' ');
match c { match c {
HORZ_BOUNDARY => { HORZ_BOUNDARY => {
grid[(mid, get_y(upper_left)-1)].set_ch(LIGHT_DOWN_AND_HORIZONTAL); grid[(mid, get_y(upper_left) - 1)].set_ch(LIGHT_DOWN_AND_HORIZONTAL);
}, }
_ => {}, _ => {}
} }
} }
@ -102,29 +109,33 @@ impl Component for VSplit {
grid[(mid, i)].set_fg(Color::Default); grid[(mid, i)].set_fg(Color::Default);
grid[(mid, i)].set_bg(Color::Default); grid[(mid, i)].set_bg(Color::Default);
} }
if get_y(bottom_right)> 1 { if get_y(bottom_right) > 1 {
let c = grid.get(mid, get_y(bottom_right)-1).map(|a| a.ch()).unwrap_or_else(|| ' '); let c = grid.get(mid, get_y(bottom_right) - 1)
.map(|a| a.ch())
.unwrap_or_else(|| ' ');
match c { match c {
HORZ_BOUNDARY => { HORZ_BOUNDARY => {
grid[(mid, get_y(bottom_right)+1)].set_ch(LIGHT_UP_AND_HORIZONTAL); grid[(mid, get_y(bottom_right) + 1)].set_ch(LIGHT_UP_AND_HORIZONTAL);
}, }
_ => {}, _ => {}
} }
} }
} }
let _ = self.left.component.draw(grid, let _ =
(upper_left, (mid-1, get_y(bottom_right))), self.left
context); .component
let _ = self.right.component.draw(grid, .draw(grid, (upper_left, (mid - 1, get_y(bottom_right))), context);
((mid+1, get_y(upper_left)), bottom_right), let _ =
context); self.right
.component
.draw(grid, ((mid + 1, get_y(upper_left)), bottom_right), context);
} }
fn process_event(&mut self, event: &UIEvent, context: &mut Context) { fn process_event(&mut self, event: &UIEvent, context: &mut Context) {
self.left.rcv_event(event, context); self.left.rcv_event(event, context);
self.right.rcv_event(event, context); self.right.rcv_event(event, context);
} }
fn is_dirty(&self) -> bool { fn is_dirty(&self) -> bool {
self.left.component.is_dirty() || self.right.component.is_dirty() self.left.component.is_dirty() || self.right.component.is_dirty()
} }
} }
@ -152,13 +163,18 @@ impl Pager {
.spawn() .spawn()
.expect("Failed to start pager filter process"); .expect("Failed to start pager filter process");
{ {
let mut stdin = let mut stdin = filter_child.stdin.as_mut().expect("failed to open stdin");
filter_child.stdin.as_mut().expect("failed to open stdin"); stdin
stdin.write_all(text.as_bytes()).expect("Failed to write to stdin"); .write_all(text.as_bytes())
.expect("Failed to write to stdin");
} }
text = String::from_utf8_lossy(
text = String::from_utf8_lossy(&filter_child.wait_with_output().expect("Failed to wait on filter").stdout).to_string(); &filter_child
.wait_with_output()
.expect("Failed to wait on filter")
.stdout,
).to_string();
} }
let lines: Vec<&str> = text.trim().split('\n').collect(); let lines: Vec<&str> = text.trim().split('\n').collect();
@ -219,12 +235,14 @@ impl Pager {
let width = lines.iter().map(|l| l.len()).max().unwrap_or(0); let width = lines.iter().map(|l| l.len()).max().unwrap_or(0);
if width > 0 { if width > 0 {
for (i, l) in lines.iter().enumerate() { for (i, l) in lines.iter().enumerate() {
write_string_to_grid(l, write_string_to_grid(
content, l,
Color::Default, content,
Color::Default, Color::Default,
((0, i), (width -1, i)), Color::Default,
true); ((0, i), (width - 1, i)),
true,
);
} }
} }
} }
@ -249,13 +267,17 @@ impl Component for Pager {
return; return;
} }
clear_area(grid, clear_area(grid, (upper_left, bottom_right));
(upper_left, bottom_right));
//let pager_context: usize = context.settings.pager.pager_context; //let pager_context: usize = context.settings.pager.pager_context;
//let pager_stop: bool = context.settings.pager.pager_stop; //let pager_stop: bool = context.settings.pager.pager_stop;
//let rows = y(bottom_right) - y(upper_left); //let rows = y(bottom_right) - y(upper_left);
//let page_length = rows / self.height; //let page_length = rows / self.height;
copy_area_with_break(grid, &self.content, area, ((0, self.cursor_pos), (self.width - 1, self.height - 1))); copy_area_with_break(
grid,
&self.content,
area,
((0, self.cursor_pos), (self.width - 1, self.height - 1)),
);
context.dirty_areas.push_back(area); context.dirty_areas.push_back(area);
} }
fn process_event(&mut self, event: &UIEvent, _context: &mut Context) { fn process_event(&mut self, event: &UIEvent, _context: &mut Context) {
@ -265,21 +287,20 @@ impl Component for Pager {
self.cursor_pos -= 1; self.cursor_pos -= 1;
self.dirty = true; self.dirty = true;
} }
}, }
UIEventType::Input(Key::Char('j')) => { UIEventType::Input(Key::Char('j')) => {
if self.height > 0 && self.cursor_pos < self.height - 1 { if self.height > 0 && self.cursor_pos < self.height - 1 {
self.cursor_pos += 1; self.cursor_pos += 1;
self.dirty = true; self.dirty = true;
} }
}, }
UIEventType::ChangeMode(UIMode::Normal) => { UIEventType::ChangeMode(UIMode::Normal) => {
self.dirty = true; self.dirty = true;
}, }
UIEventType::Resize => { UIEventType::Resize => {
self.dirty = true; self.dirty = true;
}, }
_ => { _ => {}
},
} }
} }
fn is_dirty(&self) -> bool { fn is_dirty(&self) -> bool {
@ -314,34 +335,35 @@ impl StatusBar {
clear_area(grid, area); clear_area(grid, area);
if let Some(n) = self.notifications.pop_front() { if let Some(n) = self.notifications.pop_front() {
self.dirty = true; self.dirty = true;
write_string_to_grid(&n, write_string_to_grid(&n, grid, Color::Byte(219), Color::Byte(88), area, false);
grid,
Color::Byte(219),
Color::Byte(88),
area, false);
} else { } else {
write_string_to_grid(&self.status, write_string_to_grid(
grid, &self.status,
Color::Byte(123), grid,
Color::Byte(26), Color::Byte(123),
area, false); Color::Byte(26),
area,
false,
);
} }
change_colors(grid, area, Color::Byte(123), Color::Byte(26)); change_colors(grid, area, Color::Byte(123), Color::Byte(26));
context.dirty_areas.push_back(area); context.dirty_areas.push_back(area);
} }
fn draw_execute_bar(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { fn draw_execute_bar(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
clear_area(grid, area); clear_area(grid, area);
write_string_to_grid(&self.ex_buffer, write_string_to_grid(
grid, &self.ex_buffer,
Color::Byte(219), grid,
Color::Byte(88), Color::Byte(219),
area, false); Color::Byte(88),
area,
false,
);
change_colors(grid, area, Color::Byte(219), Color::Byte(88)); change_colors(grid, area, Color::Byte(219), Color::Byte(88));
context.dirty_areas.push_back(area); context.dirty_areas.push_back(area);
} }
} }
impl Component for StatusBar { impl Component for StatusBar {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
if !is_valid_area!(area) { if !is_valid_area!(area) {
@ -356,36 +378,57 @@ impl Component for StatusBar {
} }
let height = self.height; let height = self.height;
let _ = self.container.component.draw(grid, let _ = self.container.component.draw(
(upper_left, (get_x(bottom_right), get_y(bottom_right) - height)), grid,
context); (
upper_left,
(get_x(bottom_right), get_y(bottom_right) - height),
),
context,
);
if !self.is_dirty() { if !self.is_dirty() {
return; return;
} }
self.dirty = false; self.dirty = false;
self.draw_status_bar(grid, (set_y(upper_left, get_y(bottom_right)), bottom_right), context); self.draw_status_bar(
grid,
(set_y(upper_left, get_y(bottom_right)), bottom_right),
context,
);
match self.mode { match self.mode {
UIMode::Normal => { UIMode::Normal => {}
},
UIMode::Execute => { UIMode::Execute => {
self.draw_execute_bar(grid, self.draw_execute_bar(
(set_y(upper_left, get_y(bottom_right) - height + 1), set_y(bottom_right, get_y(bottom_right) - height+1)), grid,
context); (
}, set_y(upper_left, get_y(bottom_right) - height + 1),
_ => {}, set_y(bottom_right, get_y(bottom_right) - height + 1),
),
context,
);
}
_ => {}
} }
} }
fn process_event(&mut self, event: &UIEvent, context: &mut Context) { fn process_event(&mut self, event: &UIEvent, context: &mut Context) {
self.container.rcv_event(event, context); self.container.rcv_event(event, context);
match &event.event_type { match &event.event_type {
UIEventType::RefreshMailbox((ref idx_a, ref idx_f)) => { UIEventType::RefreshMailbox((ref idx_a, ref idx_f)) => {
let m = &context.accounts[*idx_a][*idx_f].as_ref().unwrap().as_ref().unwrap(); let m = &context.accounts[*idx_a][*idx_f]
self.status = format!("{} |Mailbox: {}, Messages: {}, New: {}", self.mode, m.folder.name(), m.collection.len(), m.collection.iter().filter(|e| !e.is_seen()).count()); .as_ref()
.unwrap()
.as_ref()
.unwrap();
self.status = format!(
"{} |Mailbox: {}, Messages: {}, New: {}",
self.mode,
m.folder.name(),
m.collection.len(),
m.collection.iter().filter(|e| !e.is_seen()).count()
);
self.dirty = true; self.dirty = true;
}
},
UIEventType::ChangeMode(m) => { UIEventType::ChangeMode(m) => {
let offset = self.status.find('|').unwrap_or(self.status.len()); let offset = self.status.find('|').unwrap_or(self.status.len());
self.status.replace_range(..offset, &format!("{} ", m)); self.status.replace_range(..offset, &format!("{} ", m));
@ -394,27 +437,30 @@ impl Component for StatusBar {
match m { match m {
UIMode::Normal => { UIMode::Normal => {
self.height = 1; self.height = 1;
context.replies.push_back(UIEvent { id: 0, event_type: UIEventType::Command(self.ex_buffer.clone())}); context.replies.push_back(UIEvent {
id: 0,
event_type: UIEventType::Command(self.ex_buffer.clone()),
});
self.ex_buffer.clear() self.ex_buffer.clear()
}, }
UIMode::Execute => { UIMode::Execute => {
self.height = 2; self.height = 2;
}, }
_ => {}, _ => {}
}; };
}, }
UIEventType::ExInput(Key::Char(c)) => { UIEventType::ExInput(Key::Char(c)) => {
self.dirty = true; self.dirty = true;
self.ex_buffer.push(*c); self.ex_buffer.push(*c);
}, }
UIEventType::Resize => { UIEventType::Resize => {
self.dirty = true; self.dirty = true;
}, }
UIEventType::StatusNotification(s) => { UIEventType::StatusNotification(s) => {
self.notifications.push_back(s.clone()); self.notifications.push_back(s.clone());
self.dirty = true; self.dirty = true;
}, }
_ => {}, _ => {}
} }
} }
fn is_dirty(&self) -> bool { fn is_dirty(&self) -> bool {
@ -422,7 +468,6 @@ impl Component for StatusBar {
} }
} }
// A box with a text content. // A box with a text content.
pub struct TextBox { pub struct TextBox {
_content: String, _content: String,
@ -430,16 +475,12 @@ pub struct TextBox {
impl TextBox { impl TextBox {
pub fn new(s: String) -> Self { pub fn new(s: String) -> Self {
TextBox { TextBox { _content: s }
_content: s,
}
} }
} }
impl Component for TextBox { impl Component for TextBox {
fn draw(&mut self, _grid: &mut CellBuffer, _area: Area, _context: &mut Context) { fn draw(&mut self, _grid: &mut CellBuffer, _area: Area, _context: &mut Context) {}
}
fn process_event(&mut self, _event: &UIEvent, _context: &mut Context) { fn process_event(&mut self, _event: &UIEvent, _context: &mut Context) {
return; return;
} }

View File

@ -1,11 +1,15 @@
/*! A parser module for user commands passed through the Ex mode. /*! A parser module for user commands passed through the Ex mode.
*/ */
use nom::digit;
use std; use std;
use nom::{digit, };
named!(
named!(usize_c<usize>, usize_c<usize>,
map_res!(map_res!(ws!(digit), std::str::from_utf8), std::str::FromStr::from_str)); map_res!(
map_res!(ws!(digit), std::str::from_utf8),
std::str::FromStr::from_str
)
);
named!(pub goto<usize>, named!(pub goto<usize>,
preceded!(tag!("b "), preceded!(tag!("b "),

View File

@ -1,6 +1,6 @@
use std; use std;
use std::path::PathBuf;
use std::io::Write; use std::io::Write;
use std::path::PathBuf;
use uuid::Uuid; use uuid::Uuid;
@ -9,7 +9,6 @@ pub struct File {
path: PathBuf, path: PathBuf,
} }
impl File { impl File {
pub fn file(&mut self) -> std::fs::File { pub fn file(&mut self) -> std::fs::File {
std::fs::File::create(&self.path).unwrap() std::fs::File::create(&self.path).unwrap()
@ -19,7 +18,6 @@ impl File {
} }
} }
//TODO: add temp files to a list to reap them when dropped //TODO: add temp files to a list to reap them when dropped
pub fn create_temp_file(bytes: &[u8], filename: Option<&PathBuf>) -> File { pub fn create_temp_file(bytes: &[u8], filename: Option<&PathBuf>) -> File {
let mut dir = std::env::temp_dir(); let mut dir = std::env::temp_dir();
@ -28,7 +26,10 @@ pub fn create_temp_file(bytes: &[u8], filename: Option<&PathBuf>) -> File {
p p
} else { } else {
dir.push("meli"); dir.push("meli");
std::fs::DirBuilder::new().recursive(true).create(&dir).unwrap(); std::fs::DirBuilder::new()
.recursive(true)
.create(&dir)
.unwrap();
let u = Uuid::new_v4(); let u = Uuid::new_v4();
dir.push(u.hyphenated().to_string()); dir.push(u.hyphenated().to_string());
&dir &dir
@ -38,8 +39,5 @@ pub fn create_temp_file(bytes: &[u8], filename: Option<&PathBuf>) -> File {
f.write(bytes).unwrap(); f.write(bytes).unwrap();
f.flush().unwrap(); f.flush().unwrap();
File { File { path: path.clone() }
path: path.clone(),
}
} }

View File

@ -19,7 +19,6 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
/*! /*!
The UI crate has an Entity-Component-System design. The System part, is also the application's state, so they're both merged in the `State` struct. The UI crate has an Entity-Component-System design. The System part, is also the application's state, so they're both merged in the `State` struct.
@ -38,10 +37,10 @@ pub use helpers::*;
#[macro_use] #[macro_use]
mod execute; mod execute;
use execute::goto;
pub use self::position::*;
use self::cells::*; use self::cells::*;
pub use self::components::*; pub use self::components::*;
pub use self::position::*;
use execute::goto;
extern crate melib; extern crate melib;
extern crate mime_apps; extern crate mime_apps;
@ -53,15 +52,15 @@ extern crate linkify;
extern crate uuid; extern crate uuid;
use melib::*; use melib::*;
use std::io::{Write, };
use std::collections::VecDeque; use std::collections::VecDeque;
use std::fmt; use std::fmt;
use std::io::Write;
extern crate termion; extern crate termion;
use termion::{clear, style, cursor}; use termion::event::Key as TermionKey;
use termion::raw::IntoRawMode;
use termion::event::{Key as TermionKey, };
use termion::input::TermRead; use termion::input::TermRead;
use termion::raw::IntoRawMode;
use termion::screen::AlternateScreen; use termion::screen::AlternateScreen;
use termion::{clear, cursor, style};
#[macro_use] #[macro_use]
extern crate nom; extern crate nom;
@ -79,7 +78,9 @@ pub enum ThreadEvent {
/// User input. /// User input.
Input(Key), Input(Key),
/// A watched folder has been refreshed. /// A watched folder has been refreshed.
RefreshMailbox{ name: String }, RefreshMailbox {
name: String,
},
UIEventType(UIEventType), UIEventType(UIEventType),
//Decode { _ }, // For gpg2 signature check //Decode { _ }, // For gpg2 signature check
} }
@ -96,12 +97,11 @@ pub enum ForkType {
NewDraft(File, std::process::Child), NewDraft(File, std::process::Child),
} }
#[derive(Debug)] #[derive(Debug)]
pub enum UIEventType { pub enum UIEventType {
Input(Key), Input(Key),
ExInput(Key), ExInput(Key),
RefreshMailbox((usize,usize)), RefreshMailbox((usize, usize)),
//Quit? //Quit?
Resize, Resize,
/// Force redraw. /// Force redraw.
@ -115,7 +115,6 @@ pub enum UIEventType {
StatusNotification(String), StatusNotification(String),
} }
/// An event passed from `State` to its Entities. /// An event passed from `State` to its Entities.
#[derive(Debug)] #[derive(Debug)]
pub struct UIEvent { pub struct UIEvent {
@ -132,11 +131,15 @@ pub enum UIMode {
impl fmt::Display for UIMode { impl fmt::Display for UIMode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", match *self { write!(
UIMode::Normal => { "NORMAL" }, f,
UIMode::Execute => { "EX" }, "{}",
UIMode::Fork => { "FORK" }, match *self {
}) UIMode::Normal => "NORMAL",
UIMode::Execute => "EX",
UIMode::Fork => "FORK",
}
)
} }
} }
@ -173,7 +176,6 @@ impl Context {
} }
} }
/// A State object to manage and own components and entities of the UI. `State` is responsible for /// A State object to manage and own components and entities of the UI. `State` is responsible for
/// managing the terminal and interfacing with `melib` /// managing the terminal and interfacing with `melib`
pub struct State<W: Write> { pub struct State<W: Write> {
@ -187,13 +189,19 @@ pub struct State<W: Write> {
sender: Sender<ThreadEvent>, sender: Sender<ThreadEvent>,
entities: Vec<Entity>, entities: Vec<Entity>,
pub context: Context, pub context: Context,
} }
impl<W: Write> Drop for State<W> { impl<W: Write> Drop for State<W> {
fn drop(&mut self) { fn drop(&mut self) {
// When done, restore the defaults to avoid messing with the terminal. // When done, restore the defaults to avoid messing with the terminal.
write!(self.stdout(), "{}{}{}{}", clear::All, style::Reset, cursor::Goto(1, 1), cursor::Show).unwrap(); write!(
self.stdout(),
"{}{}{}{}",
clear::All,
style::Reset,
cursor::Goto(1, 1),
cursor::Show
).unwrap();
self.flush(); self.flush();
} }
} }
@ -207,12 +215,16 @@ impl State<std::io::Stdout> {
let stdout = AlternateScreen::from(_stdout.into_raw_mode().unwrap()); let stdout = AlternateScreen::from(_stdout.into_raw_mode().unwrap());
let termsize = termion::terminal_size().ok(); let termsize = termion::terminal_size().ok();
let termcols = termsize.map(|(w,_)| w); let termcols = termsize.map(|(w, _)| w);
let termrows = termsize.map(|(_,h)| h); let termrows = termsize.map(|(_, h)| h);
let cols = termcols.unwrap_or(0) as usize; let cols = termcols.unwrap_or(0) as usize;
let rows = termrows.unwrap_or(0) as usize; let rows = termrows.unwrap_or(0) as usize;
let mut accounts: Vec<Account> = settings.accounts.iter().map(|(n, a_s)| { Account::new(n.to_string(), a_s.clone(), &backends) }).collect(); let mut accounts: Vec<Account> = settings
accounts.sort_by(|a,b| a.name().cmp(&b.name()) ); .accounts
.iter()
.map(|(n, a_s)| Account::new(n.to_string(), a_s.clone(), &backends))
.collect();
accounts.sort_by(|a, b| a.name().cmp(&b.name()));
let mut s = State { let mut s = State {
cols: cols, cols: cols,
rows: rows, rows: rows,
@ -223,7 +235,6 @@ impl State<std::io::Stdout> {
sender: sender, sender: sender,
entities: Vec::with_capacity(1), entities: Vec::with_capacity(1),
context: Context { context: Context {
accounts: accounts, accounts: accounts,
_backends: backends, _backends: backends,
@ -235,19 +246,29 @@ impl State<std::io::Stdout> {
input_thread: input_thread, input_thread: input_thread,
}, },
}; };
write!(s.stdout(), "{}{}{}", cursor::Hide, clear::All, cursor::Goto(1,1)).unwrap(); write!(
s.stdout(),
"{}{}{}",
cursor::Hide,
clear::All,
cursor::Goto(1, 1)
).unwrap();
s.flush(); s.flush();
for account in &mut s.context.accounts { for account in &mut s.context.accounts {
let sender = s.sender.clone(); let sender = s.sender.clone();
account.watch(RefreshEventConsumer::new(Box::new(move |r| { account.watch(RefreshEventConsumer::new(Box::new(move |r| {
sender.send(ThreadEvent::from(r)); sender.send(ThreadEvent::from(r));
}))); })));
} }
s s
} }
pub fn to_main_screen(&mut self) { pub fn to_main_screen(&mut self) {
write!(self.stdout(), "{}{}", termion::screen::ToMainScreen, cursor::Show).unwrap(); write!(
self.stdout(),
"{}{}",
termion::screen::ToMainScreen,
cursor::Show
).unwrap();
self.flush(); self.flush();
self.stdout = None; self.stdout = None;
self.context.input_thread.send(false); self.context.input_thread.send(false);
@ -257,24 +278,36 @@ impl State<std::io::Stdout> {
s.lock(); s.lock();
self.stdout = Some(AlternateScreen::from(s.into_raw_mode().unwrap())); self.stdout = Some(AlternateScreen::from(s.into_raw_mode().unwrap()));
write!(self.stdout(), "{}{}", termion::screen::ToAlternateScreen, cursor::Hide).unwrap(); write!(
self.stdout(),
"{}{}",
termion::screen::ToAlternateScreen,
cursor::Hide
).unwrap();
self.flush(); self.flush();
} }
} }
impl<W: Write> State<W> { impl<W: Write> State<W> {
pub fn update_size(&mut self) { pub fn update_size(&mut self) {
let termsize = termion::terminal_size().ok(); let termsize = termion::terminal_size().ok();
let termcols = termsize.map(|(w,_)| w); let termcols = termsize.map(|(w, _)| w);
let termrows = termsize.map(|(_,h)| h); let termrows = termsize.map(|(_, h)| h);
if termcols.unwrap_or(72) as usize != self.cols || termrows.unwrap_or(120) as usize != self.rows { if termcols.unwrap_or(72) as usize != self.cols
eprintln!("Size updated, from ({}, {}) -> ({:?}, {:?})", self.cols, self.rows, termcols, termrows); || termrows.unwrap_or(120) as usize != self.rows
{
eprintln!(
"Size updated, from ({}, {}) -> ({:?}, {:?})",
self.cols, self.rows, termcols, termrows
);
} }
self.cols = termcols.unwrap_or(72) as usize; self.cols = termcols.unwrap_or(72) as usize;
self.rows = termrows.unwrap_or(120) as usize; self.rows = termrows.unwrap_or(120) as usize;
self.grid.resize(self.cols, self.rows, Cell::with_char(' ')); self.grid.resize(self.cols, self.rows, Cell::with_char(' '));
self.rcv_event(UIEvent { id: 0, event_type: UIEventType::Resize }); self.rcv_event(UIEvent {
id: 0,
event_type: UIEventType::Resize,
});
} }
pub fn redraw(&mut self) { pub fn redraw(&mut self) {
@ -292,9 +325,13 @@ impl<W: Write> State<W> {
let bottom_right = bottom_right!(area); let bottom_right = bottom_right!(area);
for y in get_y(upper_left)..=get_y(bottom_right) { for y in get_y(upper_left)..=get_y(bottom_right) {
write!(self.stdout(), "{}", cursor::Goto(get_x(upper_left) as u16 + 1,(y+1) as u16)).unwrap(); write!(
self.stdout(),
"{}",
cursor::Goto(get_x(upper_left) as u16 + 1, (y + 1) as u16)
).unwrap();
for x in get_x(upper_left)..=get_x(bottom_right) { for x in get_x(upper_left)..=get_x(bottom_right) {
let c = self.grid[(x,y)]; let c = self.grid[(x, y)];
if c.bg() != cells::Color::Default { if c.bg() != cells::Color::Default {
write!(self.stdout(), "{}", termion::color::Bg(c.bg().as_termion())).unwrap(); write!(self.stdout(), "{}", termion::color::Bg(c.bg().as_termion())).unwrap();
@ -302,14 +339,21 @@ impl<W: Write> State<W> {
if c.fg() != cells::Color::Default { if c.fg() != cells::Color::Default {
write!(self.stdout(), "{}", termion::color::Fg(c.fg().as_termion())).unwrap(); write!(self.stdout(), "{}", termion::color::Fg(c.fg().as_termion())).unwrap();
} }
write!(self.stdout(), "{}",c.ch()).unwrap(); write!(self.stdout(), "{}", c.ch()).unwrap();
if c.bg() != cells::Color::Default { if c.bg() != cells::Color::Default {
write!(self.stdout(), "{}", termion::color::Bg(termion::color::Reset)).unwrap(); write!(
self.stdout(),
"{}",
termion::color::Bg(termion::color::Reset)
).unwrap();
} }
if c.fg() != cells::Color::Default { if c.fg() != cells::Color::Default {
write!(self.stdout(), "{}", termion::color::Fg(termion::color::Reset)).unwrap(); write!(
self.stdout(),
"{}",
termion::color::Fg(termion::color::Reset)
).unwrap();
} }
} }
} }
self.flush(); self.flush();
@ -324,17 +368,19 @@ impl<W: Write> State<W> {
let cols = self.cols; let cols = self.cols;
let rows = self.rows; let rows = self.rows;
self.draw_area(((0, 0), (cols-1, rows-1))); self.draw_area(((0, 0), (cols - 1, rows - 1)));
} }
pub fn draw_entity(&mut self, idx: usize) { pub fn draw_entity(&mut self, idx: usize) {
let entity = &mut self.entities[idx]; let entity = &mut self.entities[idx];
let upper_left = (0,0); let upper_left = (0, 0);
let bottom_right = (self.cols-1, self.rows-1); let bottom_right = (self.cols - 1, self.rows - 1);
if entity.component.is_dirty() { if entity.component.is_dirty() {
entity.component.draw(&mut self.grid, entity.component.draw(
(upper_left, bottom_right), &mut self.grid,
&mut self.context); (upper_left, bottom_right),
&mut self.context,
);
} }
} }
pub fn register_entity(&mut self, entity: Entity) { pub fn register_entity(&mut self, entity: Entity) {
@ -357,22 +403,22 @@ impl<W: Write> State<W> {
UIEventType::Command(cmd) => { UIEventType::Command(cmd) => {
self.parse_command(cmd); self.parse_command(cmd);
return; return;
}, }
UIEventType::Fork(child) => { UIEventType::Fork(child) => {
self.mode = UIMode::Fork; self.mode = UIMode::Fork;
self.child = Some(child); self.child = Some(child);
self.flush(); self.flush();
return; return;
}, }
UIEventType::EditDraft(mut file) => { UIEventType::EditDraft(mut file) => {
use std::process::{Command, Stdio};
use std::io::Read; use std::io::Read;
use std::process::{Command, Stdio};
let mut output = Command::new("msmtp") let mut output = Command::new("msmtp")
.arg("-t") .arg("-t")
.stdin(Stdio::piped()) .stdin(Stdio::piped())
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.spawn() .spawn()
.expect("failed to execute process") ; .expect("failed to execute process");
{ {
let mut in_pipe = output.stdin.as_mut().unwrap(); let mut in_pipe = output.stdin.as_mut().unwrap();
let mut buf = Vec::new(); let mut buf = Vec::new();
@ -385,13 +431,20 @@ impl<W: Write> State<W> {
output.wait_with_output().expect("Failed to read stdout"); output.wait_with_output().expect("Failed to read stdout");
return; return;
}
UIEventType::Input(Key::Char('t')) => for i in 0..self.entities.len() {
self.entities[i].rcv_event(
&UIEvent {
id: 0,
event_type: UIEventType::Action(Action::MailListing(
MailListingAction::ToggleThreaded,
)),
},
&mut self.context,
);
}, },
UIEventType::Input(Key::Char('t')) =>
for i in 0..self.entities.len() {
self.entities[i].rcv_event(&UIEvent{ id: 0, event_type: UIEventType::Action(Action::MailListing(MailListingAction::ToggleThreaded)) }, &mut self.context);
}
_ => {}, _ => {}
} }
/* inform each entity */ /* inform each entity */
for i in 0..self.entities.len() { for i in 0..self.entities.len() {
@ -402,45 +455,56 @@ impl<W: Write> State<W> {
/// Tries to load a mailbox's content /// Tries to load a mailbox's content
pub fn refresh_mailbox(&mut self, account_idx: usize, folder_idx: usize) { pub fn refresh_mailbox(&mut self, account_idx: usize, folder_idx: usize) {
let flag = match &mut self.context.accounts[account_idx][folder_idx] { let flag = match &mut self.context.accounts[account_idx][folder_idx] {
Some(Ok(_)) => { Some(Ok(_)) => true,
true Some(Err(e)) => {
}, eprintln!("error {:?}", e);
Some(Err(e)) => { eprintln!("error {:?}", e); false }, false
None => { eprintln!("None"); false }, }
None => {
eprintln!("None");
false
}
}; };
if flag { if flag {
self.rcv_event(UIEvent {
self.rcv_event(UIEvent { id: 0, event_type: UIEventType::RefreshMailbox((account_idx, folder_idx)) }); id: 0,
event_type: UIEventType::RefreshMailbox((account_idx, folder_idx)),
});
} }
} }
pub fn try_wait_on_child(&mut self) -> Option<bool> { pub fn try_wait_on_child(&mut self) -> Option<bool> {
if { if {
match self.child { match self.child {
Some(ForkType::NewDraft(_,ref mut c)) => { Some(ForkType::NewDraft(_, ref mut c)) => {
let mut w = c.try_wait(); let mut w = c.try_wait();
match w { match w {
Ok(Some(_)) => { true }, Ok(Some(_)) => true,
Ok(None) => { false }, Ok(None) => false,
Err(_) => { return None; }, Err(_) => {
return None;
}
} }
}, }
Some(ForkType::Generic(ref mut c)) => { Some(ForkType::Generic(ref mut c)) => {
let mut w = c.try_wait(); let mut w = c.try_wait();
match w { match w {
Ok(Some(_)) => { true }, Ok(Some(_)) => true,
Ok(None) => { false }, Ok(None) => false,
Err(_) => { return None; }, Err(_) => {
return None;
}
} }
}, }
_ => { _ => {
return None; return None;
} }
} }
} } {
{
if let Some(ForkType::NewDraft(f, _)) = std::mem::replace(&mut self.child, None) { if let Some(ForkType::NewDraft(f, _)) = std::mem::replace(&mut self.child, None) {
self.rcv_event(UIEvent { id: 0, event_type: UIEventType::EditDraft(f) }); self.rcv_event(UIEvent {
id: 0,
event_type: UIEventType::EditDraft(f),
});
} }
return Some(true); return Some(true);
} }
@ -448,15 +512,12 @@ impl<W: Write> State<W> {
} }
fn flush(&mut self) { fn flush(&mut self) {
self.stdout.as_mut().map(|s| s.flush().unwrap()); self.stdout.as_mut().map(|s| s.flush().unwrap());
} }
fn stdout(&mut self) -> &mut termion::screen::AlternateScreen<termion::raw::RawTerminal<W>> { fn stdout(&mut self) -> &mut termion::screen::AlternateScreen<termion::raw::RawTerminal<W>> {
self.stdout.as_mut().unwrap() self.stdout.as_mut().unwrap()
} }
} }
#[derive(Debug)] #[derive(Debug)]
pub enum Key { pub enum Key {
/// Backspace. /// Backspace.
@ -499,9 +560,8 @@ pub enum Key {
Esc, Esc,
} }
impl From<TermionKey> for Key { impl From<TermionKey> for Key {
fn from(k: TermionKey ) -> Self { fn from(k: TermionKey) -> Self {
match k { match k {
TermionKey::Backspace => Key::Backspace, TermionKey::Backspace => Key::Backspace,
TermionKey::Left => Key::Left, TermionKey::Left => Key::Left,
@ -525,7 +585,6 @@ impl From<TermionKey> for Key {
} }
} }
/* /*
* If we fork (for example start $EDITOR) we want the input-thread to stop reading from stdin. The * If we fork (for example start $EDITOR) we want the input-thread to stop reading from stdin. The
* best way I came up with right now is to send a signal to the thread that is read in the first * best way I came up with right now is to send a signal to the thread that is read in the first
@ -534,7 +593,12 @@ impl From<TermionKey> for Key {
* *
* The main loop uses try_wait_on_child() to check if child has exited. * The main loop uses try_wait_on_child() to check if child has exited.
*/ */
pub fn get_events(stdin: std::io::Stdin, mut closure: impl FnMut(Key), mut exit: impl FnMut(), rx: chan::Receiver<bool>) -> (){ pub fn get_events(
stdin: std::io::Stdin,
mut closure: impl FnMut(Key),
mut exit: impl FnMut(),
rx: chan::Receiver<bool>,
) -> () {
for c in stdin.keys() { for c in stdin.keys() {
chan_select! { chan_select! {
default => {}, default => {},

View File

@ -28,22 +28,28 @@ pub fn set_y(p: Pos, new_y: usize) -> Pos {
pub type Area = (Pos, Pos); pub type Area = (Pos, Pos);
#[macro_export] #[macro_export]
macro_rules! upper_left { ($a:expr) => ( $a.0 ) } macro_rules! upper_left {
($a:expr) => {
$a.0
};
}
#[macro_export] #[macro_export]
macro_rules! bottom_right { ($a:expr) => ( $a.1 ) } macro_rules! bottom_right {
($a:expr) => {
$a.1
};
}
#[macro_export] #[macro_export]
macro_rules! is_valid_area { ($a:expr) => macro_rules! is_valid_area {
{ ($a:expr) => {{
{ let upper_left = upper_left!($a);
let upper_left = upper_left!($a); let bottom_right = bottom_right!($a);
let bottom_right = bottom_right!($a); if get_y(upper_left) > get_y(bottom_right) || get_x(upper_left) > get_x(bottom_right) {
if get_y(upper_left) > get_y(bottom_right) || get_x(upper_left) > get_x(bottom_right) { false
false } else {
} else { true
true
}
} }
} }};
} }
/// A `(cols, rows)` size. /// A `(cols, rows)` size.