rustfmt everything
parent
ffbd70e40b
commit
2f91d29326
|
@ -1,9 +1,9 @@
|
|||
#![feature(test)]
|
||||
extern crate melib;
|
||||
|
||||
use melib::mailbox::email::Envelope;
|
||||
use melib::mailbox::backends::BackendOpGenerator;
|
||||
use melib::mailbox::backends::maildir::MaildirOp;
|
||||
use melib::mailbox::backends::BackendOpGenerator;
|
||||
use melib::mailbox::email::Envelope;
|
||||
|
||||
extern crate test;
|
||||
use self::test::Bencher;
|
||||
|
|
|
@ -20,18 +20,16 @@
|
|||
*/
|
||||
|
||||
extern crate config;
|
||||
extern crate xdg;
|
||||
extern crate serde;
|
||||
extern crate xdg;
|
||||
pub mod pager;
|
||||
|
||||
|
||||
use pager::PagerSettings;
|
||||
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::Hasher;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::hash::Hasher;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
|
@ -64,7 +62,6 @@ impl Folder {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct FileAccount {
|
||||
folders: String,
|
||||
|
@ -73,7 +70,6 @@ struct FileAccount {
|
|||
threaded: bool,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct FileSettings {
|
||||
accounts: HashMap<String, FileAccount>,
|
||||
|
@ -104,7 +100,6 @@ pub struct Settings {
|
|||
pub pager: PagerSettings,
|
||||
}
|
||||
|
||||
|
||||
use self::config::{Config, File, FileFormat};
|
||||
impl FileSettings {
|
||||
pub fn new() -> FileSettings {
|
||||
|
@ -117,7 +112,7 @@ impl FileSettings {
|
|||
let s = s.merge(File::new(config_path.to_str().unwrap(), FileFormat::Toml));
|
||||
|
||||
// TODO: Return result
|
||||
s.unwrap().deserialize().unwrap()
|
||||
s.unwrap().deserialize().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -134,16 +129,20 @@ impl Settings {
|
|||
for f in f.iter_mut() {
|
||||
{
|
||||
let path = f.path();
|
||||
if path.ends_with("cur") || path.ends_with("new") ||
|
||||
path.ends_with("tmp")
|
||||
if path.ends_with("cur")
|
||||
|| path.ends_with("new")
|
||||
|| path.ends_with("tmp")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if path.is_dir() {
|
||||
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));
|
||||
children.push(folders.len()-1);
|
||||
|
||||
folders.push(Folder::new(
|
||||
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_children = recurse_folders(&mut folders, &path);
|
||||
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));
|
||||
s.insert(
|
||||
|
@ -168,6 +171,9 @@ impl Settings {
|
|||
);
|
||||
}
|
||||
|
||||
Settings { accounts: s, pager: fs.pager }
|
||||
Settings {
|
||||
accounts: s,
|
||||
pager: fs.pager,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
fn false_val () -> bool {
|
||||
fn false_val() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn true_val () -> bool {
|
||||
fn true_val() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn zero_val () -> usize {
|
||||
fn zero_val() -> usize {
|
||||
0
|
||||
}
|
||||
fn eighty_percent () -> usize {
|
||||
fn eighty_percent() -> usize {
|
||||
80
|
||||
}
|
||||
|
||||
|
@ -39,12 +39,12 @@ pub struct PagerSettings {
|
|||
/// Default: 80
|
||||
#[serde(default = "eighty_percent")]
|
||||
pub pager_ratio: usize,
|
||||
|
||||
|
||||
/// A command to pipe mail output through for viewing in pager.
|
||||
/// Default: None
|
||||
#[serde(default = "none")]
|
||||
pub filter: Option<String>,
|
||||
|
||||
|
||||
/// Respect "format=flowed"
|
||||
/// Default: true
|
||||
#[serde(default = "true_val")]
|
||||
|
|
|
@ -20,8 +20,8 @@
|
|||
*/
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::result;
|
||||
use std::io;
|
||||
use std::result;
|
||||
|
||||
use nom;
|
||||
|
||||
|
|
|
@ -18,10 +18,9 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
pub mod mailbox;
|
||||
pub mod conf;
|
||||
pub mod error;
|
||||
|
||||
pub mod mailbox;
|
||||
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
@ -29,16 +28,16 @@ extern crate serde_derive;
|
|||
#[macro_use]
|
||||
extern crate nom;
|
||||
extern crate chrono;
|
||||
extern crate memmap;
|
||||
extern crate encoding;
|
||||
extern crate data_encoding;
|
||||
extern crate encoding;
|
||||
extern crate memmap;
|
||||
|
||||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
|
||||
pub use mailbox::*;
|
||||
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 error::{Result, MeliError};
|
||||
|
|
|
@ -19,9 +19,9 @@
|
|||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use mailbox::*;
|
||||
use mailbox::backends::{RefreshEventConsumer, Backends};
|
||||
use conf::{AccountSettings, Folder};
|
||||
use mailbox::backends::{Backends, RefreshEventConsumer};
|
||||
use mailbox::*;
|
||||
use std::ops::{Index, IndexMut};
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -36,7 +36,6 @@ pub struct Account {
|
|||
pub backend: Box<MailBackend>,
|
||||
}
|
||||
|
||||
|
||||
impl Account {
|
||||
pub fn new(name: String, settings: AccountSettings, backends: &Backends) -> Self {
|
||||
let sent_folder = settings
|
||||
|
@ -88,7 +87,8 @@ impl IndexMut<usize> for Account {
|
|||
if self.sent_folder.is_some() {
|
||||
let id = self.sent_folder.unwrap();
|
||||
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 {
|
||||
let (sent, cur) = {
|
||||
let ptr = self.folders.as_mut_ptr();
|
||||
|
@ -104,10 +104,10 @@ impl IndexMut<usize> for Account {
|
|||
if sent[0].is_none() {
|
||||
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 {
|
||||
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]
|
||||
|
|
|
@ -19,21 +19,18 @@
|
|||
* 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 error::Result;
|
||||
use mailbox::backends::{BackendOp, MailBackend, RefreshEventConsumer};
|
||||
use mailbox::email::{Envelope, Flag};
|
||||
|
||||
/// `BackendOp` implementor for Imap
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct ImapOp {
|
||||
}
|
||||
pub struct ImapOp {}
|
||||
|
||||
impl ImapOp {
|
||||
pub fn new(_path: String) -> Self {
|
||||
ImapOp {
|
||||
}
|
||||
ImapOp {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,12 +52,9 @@ impl BackendOp for ImapOp {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/// Imap backend
|
||||
/// Imap backend
|
||||
#[derive(Debug)]
|
||||
pub struct ImapType {
|
||||
}
|
||||
|
||||
pub struct ImapType {}
|
||||
|
||||
impl MailBackend for ImapType {
|
||||
fn get(&self, _folder: &Folder) -> Result<Vec<Envelope>> {
|
||||
|
@ -73,7 +67,6 @@ impl MailBackend for ImapType {
|
|||
|
||||
impl ImapType {
|
||||
pub fn new(_path: &str) -> Self {
|
||||
ImapType {
|
||||
}
|
||||
ImapType {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,15 +19,17 @@
|
|||
* 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 error::{MeliError, Result};
|
||||
use mailbox::backends::{
|
||||
BackendOp, BackendOpGenerator, MailBackend, RefreshEvent, RefreshEventConsumer,
|
||||
};
|
||||
use mailbox::email::parser;
|
||||
use mailbox::email::{Envelope, Flag};
|
||||
|
||||
extern crate notify;
|
||||
|
||||
use self::notify::{Watcher, RecursiveMode, watcher, DebouncedEvent};
|
||||
use self::notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
|
||||
use std::time::Duration;
|
||||
|
||||
use std::sync::mpsc::channel;
|
||||
|
@ -36,8 +38,8 @@ use std::sync::mpsc::channel;
|
|||
//use std::time::Duration;
|
||||
use std::thread;
|
||||
extern crate crossbeam;
|
||||
use std::path::PathBuf;
|
||||
use memmap::{Mmap, Protection};
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// `BackendOp` implementor for Maildir
|
||||
#[derive(Debug, Default)]
|
||||
|
@ -70,9 +72,7 @@ impl BackendOp for MaildirOp {
|
|||
}
|
||||
fn as_bytes(&mut self) -> Result<&[u8]> {
|
||||
if self.slice.is_none() {
|
||||
self.slice = Some(
|
||||
Mmap::open_path(self.path.to_string(), Protection::Read)?,
|
||||
);
|
||||
self.slice = Some(Mmap::open_path(self.path.to_string(), Protection::Read)?);
|
||||
}
|
||||
/* Unwrap is safe since we use ? above. */
|
||||
Ok(unsafe { self.slice.as_ref().unwrap().as_slice() })
|
||||
|
@ -104,24 +104,20 @@ impl BackendOp for MaildirOp {
|
|||
'T' => flag |= Flag::TRASHED,
|
||||
'D' => flag |= Flag::DRAFT,
|
||||
'F' => flag |= Flag::FLAGGED,
|
||||
_ => panic!(),
|
||||
_ => panic!(),
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
flag
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Maildir backend https://cr.yp.to/proto/maildir.html
|
||||
#[derive(Debug)]
|
||||
pub struct MaildirType {
|
||||
path: String,
|
||||
}
|
||||
|
||||
|
||||
impl MailBackend for MaildirType {
|
||||
fn get(&self, folder: &Folder) -> Result<Vec<Envelope>> {
|
||||
self.multicore(4, folder)
|
||||
|
@ -129,34 +125,40 @@ impl MailBackend for MaildirType {
|
|||
fn watch(&self, sender: RefreshEventConsumer, folders: &[Folder]) -> () {
|
||||
let folders = folders.to_vec();
|
||||
|
||||
thread::Builder::new().name("folder watch".to_string()).spawn(move || {
|
||||
let (tx, rx) = channel();
|
||||
let mut watcher = watcher(tx, Duration::from_secs(1)).unwrap();
|
||||
for f in folders {
|
||||
if MaildirType::is_valid(&f).is_err() {
|
||||
continue;
|
||||
}
|
||||
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 {
|
||||
match rx.recv() {
|
||||
Ok(event) => {
|
||||
match event {
|
||||
DebouncedEvent::Create(pathbuf) => {
|
||||
sender.send(RefreshEvent { folder: format!("{}", pathbuf.parent().unwrap().to_str().unwrap()) });
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
thread::Builder::new()
|
||||
.name("folder watch".to_string())
|
||||
.spawn(move || {
|
||||
let (tx, rx) = channel();
|
||||
let mut watcher = watcher(tx, Duration::from_secs(1)).unwrap();
|
||||
for f in folders {
|
||||
if MaildirType::is_valid(&f).is_err() {
|
||||
continue;
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}).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),
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -172,9 +174,10 @@ impl MaildirType {
|
|||
for d in &["cur", "new", "tmp"] {
|
||||
p.push(d);
|
||||
if !p.is_dir() {
|
||||
return Err(MeliError::new(
|
||||
format!("{} is not a valid maildir folder", path),
|
||||
));
|
||||
return Err(MeliError::new(format!(
|
||||
"{} is not a valid maildir folder",
|
||||
path
|
||||
)));
|
||||
}
|
||||
p.pop();
|
||||
}
|
||||
|
@ -209,9 +212,10 @@ impl MaildirType {
|
|||
let mut local_r: Vec<Envelope> = Vec::with_capacity(chunk.len());
|
||||
for e in chunk {
|
||||
let e_copy = e.to_string();
|
||||
if let Some(mut e) = Envelope::from_token(Box::new(BackendOpGenerator::new(
|
||||
Box::new(move || Box::new(MaildirOp::new(e_copy.clone()))),
|
||||
))) {
|
||||
if let Some(mut e) =
|
||||
Envelope::from_token(Box::new(BackendOpGenerator::new(Box::new(
|
||||
move || Box::new(MaildirOp::new(e_copy.clone())),
|
||||
)))) {
|
||||
e.populate_headers();
|
||||
local_r.push(e);
|
||||
}
|
||||
|
|
|
@ -19,23 +19,18 @@
|
|||
* 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 error::Result;
|
||||
use mailbox::backends::{BackendOp, MailBackend, RefreshEventConsumer};
|
||||
use mailbox::email::{Envelope, Flag};
|
||||
|
||||
/// `BackendOp` implementor for Mbox
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct MboxOp {
|
||||
}
|
||||
pub struct MboxOp {}
|
||||
|
||||
impl MboxOp {
|
||||
pub fn new(_path: String) -> Self {
|
||||
MboxOp {
|
||||
}
|
||||
MboxOp {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -57,12 +52,9 @@ impl BackendOp for MboxOp {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/// Mbox backend
|
||||
/// Mbox backend
|
||||
#[derive(Debug)]
|
||||
pub struct MboxType {
|
||||
}
|
||||
|
||||
pub struct MboxType {}
|
||||
|
||||
impl MailBackend for MboxType {
|
||||
fn get(&self, _folder: &Folder) -> Result<Vec<Envelope>> {
|
||||
|
@ -75,7 +67,6 @@ impl MailBackend for MboxType {
|
|||
|
||||
impl MboxType {
|
||||
pub fn new(_path: &str) -> Self {
|
||||
MboxType {
|
||||
}
|
||||
MboxType {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,19 +18,18 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
pub mod imap;
|
||||
pub mod maildir;
|
||||
pub mod mbox;
|
||||
pub mod imap;
|
||||
|
||||
use conf::Folder;
|
||||
use mailbox::email::{Envelope, Flag};
|
||||
use error::Result;
|
||||
use mailbox::backends::imap::ImapType;
|
||||
use mailbox::backends::maildir::MaildirType;
|
||||
use mailbox::backends::mbox::MboxType;
|
||||
use mailbox::backends::imap::ImapType;
|
||||
use error::Result;
|
||||
use mailbox::email::{Envelope, Flag};
|
||||
use std::fmt;
|
||||
|
||||
|
||||
extern crate fnv;
|
||||
use self::fnv::FnvHashMap;
|
||||
use std;
|
||||
|
@ -44,9 +43,12 @@ pub struct Backends {
|
|||
impl Backends {
|
||||
pub fn new() -> Self {
|
||||
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("imap".to_string(), Box::new(|| Box::new(ImapType::new(""))));
|
||||
b
|
||||
|
@ -66,7 +68,6 @@ impl Backends {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
pub struct RefreshEvent {
|
||||
pub folder: String,
|
||||
}
|
||||
|
@ -87,7 +88,7 @@ impl RefreshEventConsumer {
|
|||
}
|
||||
pub trait MailBackend: ::std::fmt::Debug {
|
||||
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>;
|
||||
//login function
|
||||
}
|
||||
|
|
|
@ -40,60 +40,39 @@ pub enum MultipartType {
|
|||
Unsupported { tag: String },
|
||||
}
|
||||
|
||||
|
||||
impl Display for MultipartType {
|
||||
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
||||
match self {
|
||||
MultipartType::Mixed => {
|
||||
write!(f, "multipart/mixed")
|
||||
},
|
||||
MultipartType::Alternative => {
|
||||
write!(f, "multipart/alternative")
|
||||
},
|
||||
MultipartType::Digest => {
|
||||
write!(f, "multipart/digest")
|
||||
},
|
||||
MultipartType::Unsupported { tag: ref t } => {
|
||||
write!(f, "multipart/{}", t)
|
||||
},
|
||||
MultipartType::Mixed => write!(f, "multipart/mixed"),
|
||||
MultipartType::Alternative => write!(f, "multipart/alternative"),
|
||||
MultipartType::Digest => write!(f, "multipart/digest"),
|
||||
MultipartType::Unsupported { tag: ref t } => write!(f, "multipart/{}", t),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum AttachmentType {
|
||||
Data { tag: String },
|
||||
Text { content: String },
|
||||
Data {
|
||||
tag: String,
|
||||
},
|
||||
Text {
|
||||
content: String,
|
||||
},
|
||||
Multipart {
|
||||
of_type: MultipartType,
|
||||
subattachments: Vec<Attachment>,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
impl Display for AttachmentType {
|
||||
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
||||
match self {
|
||||
AttachmentType::Data { tag: ref t } => {
|
||||
write!(f, "{}", t)
|
||||
},
|
||||
AttachmentType::Text { content: ref c } => {
|
||||
write!(f, "{}", c)
|
||||
},
|
||||
AttachmentType::Multipart { of_type: ref t, .. } => {
|
||||
write!(f, "{}", t)
|
||||
|
||||
},
|
||||
|
||||
|
||||
AttachmentType::Data { tag: 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)]
|
||||
pub enum ContentType {
|
||||
|
@ -207,32 +186,33 @@ impl AttachmentBuilder {
|
|||
fn decode(&self) -> String {
|
||||
// TODO: Use charset for decoding
|
||||
match self.content_transfer_encoding {
|
||||
ContentTransferEncoding::Base64 => {
|
||||
match BASE64_MIME.decode(str::from_utf8(&self.raw)
|
||||
ContentTransferEncoding::Base64 => match BASE64_MIME.decode(
|
||||
str::from_utf8(&self.raw)
|
||||
.unwrap()
|
||||
.trim()
|
||||
.lines()
|
||||
.fold(String::with_capacity(self.raw.len()), |mut acc, x| {
|
||||
acc.push_str(x);
|
||||
acc
|
||||
}).as_bytes()) {
|
||||
Ok(ref v) => {
|
||||
let s = String::from_utf8_lossy(v);
|
||||
if s.find("\r\n").is_some() {
|
||||
s.replace("\r\n", "\n")
|
||||
} else {
|
||||
s.into_owned()
|
||||
}
|
||||
})
|
||||
.as_bytes(),
|
||||
) {
|
||||
Ok(ref v) => {
|
||||
let s = String::from_utf8_lossy(v);
|
||||
if s.find("\r\n").is_some() {
|
||||
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)
|
||||
.to_full_result()
|
||||
.unwrap(),
|
||||
ContentTransferEncoding::_7Bit |
|
||||
ContentTransferEncoding::_8Bit |
|
||||
ContentTransferEncoding::Other { .. } => {
|
||||
.to_full_result()
|
||||
.unwrap(),
|
||||
ContentTransferEncoding::_7Bit
|
||||
| ContentTransferEncoding::_8Bit
|
||||
| ContentTransferEncoding::Other { .. } => {
|
||||
String::from_utf8_lossy(&self.raw).into_owned()
|
||||
}
|
||||
}
|
||||
|
@ -310,7 +290,6 @@ impl AttachmentBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Attachment {
|
||||
content_type: (ContentType, ContentSubType),
|
||||
|
@ -327,19 +306,20 @@ impl Display for Attachment {
|
|||
AttachmentType::Data { .. } => {
|
||||
write!(f, "Data attachment of type {}", self.mime_type())
|
||||
}
|
||||
AttachmentType::Text { .. } => {
|
||||
write!(f, "Text attachment")
|
||||
}
|
||||
AttachmentType::Text { .. } => write!(f, "Text attachment"),
|
||||
AttachmentType::Multipart {
|
||||
of_type: ref multipart_type,
|
||||
subattachments: ref sub_att_vec,
|
||||
} => 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 {
|
||||
write!(f, "Multipart attachment with {} subs", sub_att_vec.len())
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -388,9 +368,7 @@ impl Attachment {
|
|||
let mut ret = Vec::new();
|
||||
fn count_recursive(att: &Attachment, ret: &mut Vec<Attachment>) {
|
||||
match att.attachment_type {
|
||||
AttachmentType::Data { .. } | AttachmentType::Text { .. } => {
|
||||
ret.push(att.clone())
|
||||
}
|
||||
AttachmentType::Data { .. } | AttachmentType::Text { .. } => ret.push(att.clone()),
|
||||
AttachmentType::Multipart {
|
||||
of_type: ref multipart_type,
|
||||
subattachments: ref sub_att_vec,
|
||||
|
@ -405,8 +383,6 @@ impl Attachment {
|
|||
|
||||
count_recursive(&self, &mut ret);
|
||||
ret
|
||||
|
||||
|
||||
}
|
||||
pub fn count_attachments(&self) -> usize {
|
||||
self.attachments().len()
|
||||
|
@ -422,7 +398,6 @@ impl Attachment {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn interpret_format_flowed(_t: &str) -> String {
|
||||
//let mut n = String::with_capacity(t.len());
|
||||
unimplemented!()
|
||||
|
@ -431,19 +406,15 @@ pub fn interpret_format_flowed(_t: &str) -> String {
|
|||
pub fn decode(a: &Attachment) -> Vec<u8> {
|
||||
// TODO: Use charset for decoding
|
||||
match a.content_transfer_encoding {
|
||||
ContentTransferEncoding::Base64 => {
|
||||
match BASE64_MIME.decode(a.bytes()) {
|
||||
Ok(v) => {
|
||||
v
|
||||
}
|
||||
_ => a.bytes().to_vec(),
|
||||
}
|
||||
}
|
||||
ContentTransferEncoding::QuotedPrintable => parser::quoted_printed_bytes(&a.bytes()).to_full_result() .unwrap(),
|
||||
ContentTransferEncoding::_7Bit |
|
||||
ContentTransferEncoding::_8Bit |
|
||||
ContentTransferEncoding::Other { .. } => {
|
||||
a.bytes().to_vec()
|
||||
}
|
||||
ContentTransferEncoding::Base64 => match BASE64_MIME.decode(a.bytes()) {
|
||||
Ok(v) => v,
|
||||
_ => a.bytes().to_vec(),
|
||||
},
|
||||
ContentTransferEncoding::QuotedPrintable => parser::quoted_printed_bytes(&a.bytes())
|
||||
.to_full_result()
|
||||
.unwrap(),
|
||||
ContentTransferEncoding::_7Bit
|
||||
| ContentTransferEncoding::_8Bit
|
||||
| ContentTransferEncoding::Other { .. } => a.bytes().to_vec(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,38 +19,36 @@
|
|||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
pub mod parser;
|
||||
pub mod attachments;
|
||||
pub mod parser;
|
||||
|
||||
use mailbox::backends::BackendOpGenerator;
|
||||
pub use self::attachments::*;
|
||||
use mailbox::backends::BackendOpGenerator;
|
||||
|
||||
use std::string::String;
|
||||
use std::sync::Arc;
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt;
|
||||
use std::option::Option;
|
||||
use std::string::String;
|
||||
use std::sync::Arc;
|
||||
|
||||
use chrono;
|
||||
use chrono::TimeZone;
|
||||
|
||||
|
||||
|
||||
#[derive(Clone, Debug, )]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GroupAddress {
|
||||
raw: String,
|
||||
display_name: StrBuilder,
|
||||
mailbox_list: Vec<Address>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, )]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MailboxAddress {
|
||||
raw: String,
|
||||
display_name: StrBuilder,
|
||||
address_spec: StrBuilder,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, )]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Address {
|
||||
Mailbox(MailboxAddress),
|
||||
Group(GroupAddress),
|
||||
|
@ -60,17 +58,19 @@ impl Eq for Address {}
|
|||
impl PartialEq for Address {
|
||||
fn eq(&self, other: &Address) -> bool {
|
||||
match (self, other) {
|
||||
(Address::Mailbox(_), Address::Group(_)) |
|
||||
(Address::Group(_), Address::Mailbox(_)) => {
|
||||
(Address::Mailbox(_), Address::Group(_)) | (Address::Group(_), Address::Mailbox(_)) => {
|
||||
false
|
||||
},
|
||||
}
|
||||
(Address::Mailbox(s), Address::Mailbox(o)) => {
|
||||
s.address_spec.display(&s.raw) == o.address_spec.display(&o.raw)
|
||||
},
|
||||
}
|
||||
(Address::Group(s), Address::Group(o)) => {
|
||||
s.display_name.display(&s.raw) == o.display_name.display(&o.raw) &&
|
||||
s.mailbox_list.iter().zip(o.mailbox_list.iter()).fold(true, |b, (s, o)| b && (s == o))
|
||||
},
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -78,25 +78,27 @@ impl PartialEq for Address {
|
|||
impl fmt::Display for Address {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Address::Mailbox(m) if m.display_name.length > 0 => {
|
||||
write!(f, "{} <{}>", m.display_name.display(&m.raw), m.address_spec.display(&m.raw))
|
||||
},
|
||||
Address::Group(g) => {
|
||||
let attachment_strings: Vec<String> = g.mailbox_list.iter().map(|a| format!("{}", a)).collect();
|
||||
write!(f, "{}: {}", g.display_name.display(&g.raw), attachment_strings.join(", "))
|
||||
},
|
||||
Address::Mailbox(m) => {
|
||||
write!(f, "{}", m.address_spec.display(&m.raw))
|
||||
},
|
||||
Address::Mailbox(m) if m.display_name.length > 0 => write!(
|
||||
f,
|
||||
"{} <{}>",
|
||||
m.display_name.display(&m.raw),
|
||||
m.address_spec.display(&m.raw)
|
||||
),
|
||||
Address::Group(g) => {
|
||||
let attachment_strings: Vec<String> =
|
||||
g.mailbox_list.iter().map(|a| format!("{}", a)).collect();
|
||||
write!(
|
||||
f,
|
||||
"{}: {}",
|
||||
g.display_name.display(&g.raw),
|
||||
attachment_strings.join(", ")
|
||||
)
|
||||
}
|
||||
Address::Mailbox(m) => write!(f, "{}", m.address_spec.display(&m.raw)),
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Helper struct to return slices from a struct field on demand.
|
||||
#[derive(Clone, Debug)]
|
||||
struct StrBuilder {
|
||||
|
@ -118,7 +120,7 @@ impl StrBuilder {
|
|||
fn display<'a>(&self, s: &'a str) -> &'a str {
|
||||
let offset = self.offset;
|
||||
let length = self.length;
|
||||
&s[offset..offset+length]
|
||||
&s[offset..offset + length]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,14 +135,14 @@ impl StrBuild for MessageID {
|
|||
string.to_string(),
|
||||
StrBuilder {
|
||||
offset: offset,
|
||||
length: slice.len()+ 1,
|
||||
length: slice.len() + 1,
|
||||
},
|
||||
)
|
||||
)
|
||||
}
|
||||
fn raw(&self) -> &str {
|
||||
let offset = self.1.offset;
|
||||
let length = self.1.length;
|
||||
&self.0[offset..offset+length-1]
|
||||
&self.0[offset..offset + length - 1]
|
||||
}
|
||||
fn val(&self) -> &str {
|
||||
&self.0
|
||||
|
@ -159,8 +161,8 @@ fn test_strbuilder() {
|
|||
offset: 1,
|
||||
length: 43,
|
||||
}
|
||||
)
|
||||
);
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
impl PartialEq for MessageID {
|
||||
|
@ -180,7 +182,6 @@ struct References {
|
|||
refs: Vec<MessageID>,
|
||||
}
|
||||
|
||||
|
||||
bitflags! {
|
||||
#[derive(Default)]
|
||||
pub struct Flag: u8 {
|
||||
|
@ -220,7 +221,6 @@ pub struct Envelope {
|
|||
flags: Flag,
|
||||
}
|
||||
|
||||
|
||||
impl Envelope {
|
||||
pub fn new(token: Box<BackendOpGenerator>) -> Self {
|
||||
Envelope {
|
||||
|
@ -349,7 +349,7 @@ impl Envelope {
|
|||
}
|
||||
pub fn to(&self) -> &Vec<Address> {
|
||||
&self.to
|
||||
}
|
||||
}
|
||||
pub fn to_to_string(&self) -> String {
|
||||
let _strings: Vec<String> = self.to.iter().map(|a| format!("{}", a)).collect();
|
||||
_strings.join(", ")
|
||||
|
@ -511,6 +511,10 @@ impl Envelope {
|
|||
self.datetime = Some(new_val);
|
||||
self.timestamp = new_val.timestamp() as u64;
|
||||
}
|
||||
pub fn set_flag(&mut self, f: Flag) -> () {
|
||||
self.flags |= f;
|
||||
|
||||
}
|
||||
pub fn flags(&self) -> Flag {
|
||||
self.flags
|
||||
}
|
||||
|
|
|
@ -18,23 +18,23 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* 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::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 {
|
||||
($var:ident) => (
|
||||
($var:ident) => {
|
||||
$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'
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
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)
|
||||
*/
|
||||
|
||||
|
||||
fn header_value(input: &[u8]) -> IResult<&[u8], &str> {
|
||||
if input.is_empty() || input[0] == b'\n' {
|
||||
IResult::Incomplete(Needed::Unknown)
|
||||
|
@ -79,8 +78,7 @@ fn header_value(input: &[u8]) -> IResult<&[u8], &str> {
|
|||
let input_len = input.len();
|
||||
for (i, x) in input.iter().enumerate() {
|
||||
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]) {
|
||||
Ok(v) => IResult::Done(&input[(i + 1)..], v),
|
||||
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 */
|
||||
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)));
|
||||
|
||||
//named!(pub headers_raw<&[u8]>,
|
||||
//take_until1!("\n\n"));
|
||||
//take_until1!("\n\n"));
|
||||
|
||||
pub fn headers_raw(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
if input.is_empty() {
|
||||
return IResult::Incomplete(Needed::Unknown)
|
||||
return IResult::Incomplete(Needed::Unknown);
|
||||
}
|
||||
for (i, x) in input.iter().enumerate() {
|
||||
if *x == b'\n' && i + 1 < input.len() && input[i+1] == b'\n' {
|
||||
return IResult::Done(&input[(i + 1)..], &input[0..i+1]);
|
||||
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::Error(error_code!(ErrorKind::Custom(43)));
|
||||
|
@ -131,7 +128,6 @@ named!(pub body_raw<&[u8]>,
|
|||
body: take_while!(call!(|_| true)) >>
|
||||
( { body } )));
|
||||
|
||||
|
||||
named!(pub mail<(std::vec::Vec<(&str, &str)>, &[u8])>,
|
||||
separated_pair!(headers, tag!("\n"), take_while!(call!(|_| true))));
|
||||
named!(pub attachment<(std::vec::Vec<(&str, &str)>, &[u8])>,
|
||||
|
@ -255,19 +251,32 @@ named!(
|
|||
))
|
||||
);
|
||||
|
||||
named!(encoded_word_list<String>, ws!(do_parse!(
|
||||
list: separated_nonempty_list!(complete!(is_a!(" \n\r\t")), encoded_word) >>
|
||||
( {
|
||||
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});
|
||||
named!(
|
||||
encoded_word_list<String>,
|
||||
ws!(do_parse!(
|
||||
list: separated_nonempty_list!(complete!(is_a!(" \n\r\t")), encoded_word) >> ({
|
||||
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()
|
||||
} )
|
||||
)));
|
||||
named!(ascii_token<String>, do_parse!(
|
||||
word: alt!(terminated!(take_until1!("=?"), peek!(tag_no_case!("=?UTF-8?"))) | take_while!(call!(|_| { true }))) >>
|
||||
( {
|
||||
String::from_utf8_lossy(word).into_owned()
|
||||
} )));
|
||||
})
|
||||
))
|
||||
);
|
||||
named!(
|
||||
ascii_token<String>,
|
||||
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> {
|
||||
if input.is_empty() || input.len() < 3 {
|
||||
|
@ -280,8 +289,7 @@ fn display_addr(input: &[u8]) -> IResult<&[u8], Address> {
|
|||
let mut flag = false;
|
||||
for (i, b) in input[0..].iter().enumerate() {
|
||||
if *b == b'<' {
|
||||
|
||||
display_name.length = if i != 0 { i-1 } else { 0 };
|
||||
display_name.length = if i != 0 { i - 1 } else { 0 };
|
||||
flag = true;
|
||||
break;
|
||||
}
|
||||
|
@ -291,7 +299,7 @@ fn display_addr(input: &[u8]) -> IResult<&[u8], Address> {
|
|||
}
|
||||
let mut end = input.len();
|
||||
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'@' {
|
||||
flag = true;
|
||||
}
|
||||
|
@ -305,19 +313,22 @@ fn display_addr(input: &[u8]) -> IResult<&[u8], Address> {
|
|||
offset: display_name.length + 2,
|
||||
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::Incomplete(i) => IResult::Incomplete(i),
|
||||
IResult::Done(rest, raw) => {
|
||||
display_name.length = raw.find('<').unwrap();
|
||||
address_spec.offset = display_name.length + 1;
|
||||
address_spec.length = raw.len() - display_name.length - 2;
|
||||
IResult::Done(rest, Address::Mailbox(MailboxAddress {
|
||||
raw: raw,
|
||||
display_name: display_name,
|
||||
address_spec: address_spec,
|
||||
}))
|
||||
},
|
||||
IResult::Done(
|
||||
rest,
|
||||
Address::Mailbox(MailboxAddress {
|
||||
raw: raw,
|
||||
display_name: display_name,
|
||||
address_spec: address_spec,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
||||
|
@ -325,11 +336,8 @@ fn display_addr(input: &[u8]) -> IResult<&[u8], Address> {
|
|||
} else {
|
||||
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn addr_spec(input: &[u8]) -> IResult<&[u8], Address> {
|
||||
if input.is_empty() || input.len() < 3 {
|
||||
IResult::Incomplete(Needed::Size(1))
|
||||
|
@ -346,17 +354,20 @@ fn addr_spec(input: &[u8]) -> IResult<&[u8], Address> {
|
|||
}
|
||||
}
|
||||
if flag {
|
||||
IResult::Done(&input[end..], Address::Mailbox(MailboxAddress {
|
||||
raw: String::from_utf8_lossy(&input[0..end+1]).to_string(),
|
||||
display_name: StrBuilder {
|
||||
offset: 0,
|
||||
length: 0,
|
||||
},
|
||||
address_spec: StrBuilder {
|
||||
offset: 0,
|
||||
length: input[0..end+1].len(),
|
||||
},
|
||||
}))
|
||||
IResult::Done(
|
||||
&input[end..],
|
||||
Address::Mailbox(MailboxAddress {
|
||||
raw: String::from_utf8_lossy(&input[0..end + 1]).to_string(),
|
||||
display_name: StrBuilder {
|
||||
offset: 0,
|
||||
length: 0,
|
||||
},
|
||||
address_spec: StrBuilder {
|
||||
offset: 0,
|
||||
length: input[0..end + 1].len(),
|
||||
},
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
||||
}
|
||||
|
@ -365,39 +376,43 @@ fn addr_spec(input: &[u8]) -> IResult<&[u8], Address> {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
named!(mailbox<Address>, ws!(alt_complete!(
|
||||
display_addr |
|
||||
addr_spec
|
||||
)));
|
||||
named!(
|
||||
mailbox<Address>,
|
||||
ws!(alt_complete!(display_addr | addr_spec))
|
||||
);
|
||||
named!(mailbox_list<Vec<Address>>, many0!(mailbox));
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_mailbox() {
|
||||
{
|
||||
let s = b"epilys@postretch";
|
||||
let r = mailbox(s).unwrap().1;
|
||||
match r {
|
||||
Address::Mailbox(ref m) => {
|
||||
println!("----\n`{}`, `{}`\n----", m.display_name.display(&m.raw), m.address_spec.display(&m.raw));
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
let s = b"epilys@postretch";
|
||||
let r = mailbox(s).unwrap().1;
|
||||
match r {
|
||||
Address::Mailbox(ref m) => {
|
||||
println!(
|
||||
"----\n`{}`, `{}`\n----",
|
||||
m.display_name.display(&m.raw),
|
||||
m.address_spec.display(&m.raw)
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let s = b"Manos <epilys@postretch>";
|
||||
eprintln!("{:?}", display_addr(s).unwrap());
|
||||
let r = display_addr(s).unwrap().1;
|
||||
match r {
|
||||
Address::Mailbox(ref m) => {
|
||||
println!("----\n`{}`, `{}`\n----", m.display_name.display(&m.raw), m.address_spec.display(&m.raw));
|
||||
},
|
||||
_ => {},
|
||||
println!(
|
||||
"----\n`{}`, `{}`\n----",
|
||||
m.display_name.display(&m.raw),
|
||||
m.address_spec.display(&m.raw)
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
//named!(group_t<GroupAddress>, ws!( do_parse!(
|
||||
// display_name: take_until1!(":") >>
|
||||
// mailbox_list: many0!(mailbox) >>
|
||||
|
@ -427,28 +442,26 @@ fn group(input: &[u8]) -> IResult<&[u8], Address> {
|
|||
return IResult::Error(e);
|
||||
}
|
||||
IResult::Done(rest, vec) => {
|
||||
let size: usize = (rest.as_ptr() as usize) - ((&input[0..] as &[u8]).as_ptr() as usize);
|
||||
return IResult::Done(rest, Address::Group(GroupAddress {
|
||||
raw: String::from_utf8(input[0..size].to_vec()).unwrap(),
|
||||
display_name: StrBuilder {
|
||||
offset: 0,
|
||||
length: dlength,
|
||||
},
|
||||
mailbox_list: vec,
|
||||
}));
|
||||
},
|
||||
let size: usize = (rest.as_ptr() as usize) - ((&input[0..] as &[u8]).as_ptr() as usize);
|
||||
return IResult::Done(
|
||||
rest,
|
||||
Address::Group(GroupAddress {
|
||||
raw: String::from_utf8(input[0..size].to_vec()).unwrap(),
|
||||
display_name: StrBuilder {
|
||||
offset: 0,
|
||||
length: dlength,
|
||||
},
|
||||
mailbox_list: vec,
|
||||
}),
|
||||
);
|
||||
}
|
||||
IResult::Incomplete(i) => {
|
||||
return IResult::Incomplete(i);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
named!(address<Address>, ws!(
|
||||
alt_complete!(mailbox | group)));
|
||||
named!(address<Address>, ws!(alt_complete!(mailbox | group)));
|
||||
|
||||
#[test]
|
||||
fn test_address() {
|
||||
|
@ -456,18 +469,14 @@ fn test_address() {
|
|||
qemu-devel <qemu-devel@nongnu.org>, qemu-block <qemu-block@nongnu.org>,
|
||||
Alberto Garcia <berto@igalia.com>, Stefan Hajnoczi <stefanha@redhat.com>";
|
||||
println!("{:?}", rfc2822address_list(s).unwrap());
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
named!(pub rfc2822address_list<Vec<Address>>, ws!(
|
||||
separated_list!(is_a!(","), address)
|
||||
|
||||
|
||||
));
|
||||
|
||||
|
||||
named!(pub address_list<String>, ws!(do_parse!(
|
||||
list: alt_complete!( encoded_word_list | ascii_token) >>
|
||||
( {
|
||||
|
@ -677,18 +686,17 @@ fn test_attachments() {
|
|||
named!(
|
||||
content_type_parameter<(&str, &str)>,
|
||||
do_parse!(
|
||||
tag!(";") >>
|
||||
name: terminated!(map_res!(ws!(take_until!("=")), from_utf8), tag!("=")) >>
|
||||
value: map_res!(ws!(
|
||||
alt_complete!(delimited!(tag!("\""), take_until!("\""), tag!("\"")) | is_not!(";"))),
|
||||
from_utf8) >>
|
||||
( {
|
||||
(name, value)
|
||||
} )
|
||||
)
|
||||
tag!(";") >> name: terminated!(map_res!(ws!(take_until!("=")), from_utf8), tag!("="))
|
||||
>> value:
|
||||
map_res!(
|
||||
ws!(alt_complete!(
|
||||
delimited!(tag!("\""), take_until!("\""), tag!("\"")) | is_not!(";")
|
||||
)),
|
||||
from_utf8
|
||||
) >> ({ (name, value) })
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
named!(pub content_type< (&str, &str, Vec<(&str, &str)>) >,
|
||||
do_parse!(
|
||||
_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>,
|
||||
do_parse!(
|
||||
bytes: many0!(alt_complete!(
|
||||
|
|
|
@ -23,8 +23,8 @@ pub mod email;
|
|||
pub use self::email::*;
|
||||
/* Mail backends. Currently only maildir is supported */
|
||||
pub mod backends;
|
||||
use mailbox::backends::MailBackend;
|
||||
use error::Result;
|
||||
use mailbox::backends::MailBackend;
|
||||
pub mod accounts;
|
||||
pub use mailbox::accounts::Account;
|
||||
pub mod thread;
|
||||
|
@ -34,7 +34,6 @@ use conf::Folder;
|
|||
|
||||
use std::option::Option;
|
||||
|
||||
|
||||
/// `Mailbox` represents a folder of mail.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Mailbox {
|
||||
|
@ -44,7 +43,6 @@ pub struct Mailbox {
|
|||
threads: Vec<Container>,
|
||||
}
|
||||
|
||||
|
||||
impl Mailbox {
|
||||
pub fn new_dummy() -> Self {
|
||||
Mailbox {
|
||||
|
@ -54,7 +52,11 @@ impl Mailbox {
|
|||
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?;
|
||||
collection.sort_by(|a, b| a.date().cmp(&b.date()));
|
||||
let (threads, threaded_collection) = build_threads(&mut collection, sent_folder);
|
||||
|
|
|
@ -19,9 +19,9 @@
|
|||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use error::Result;
|
||||
use mailbox::email::*;
|
||||
use mailbox::Mailbox;
|
||||
use error::Result;
|
||||
|
||||
extern crate fnv;
|
||||
use self::fnv::FnvHashMap;
|
||||
|
@ -172,8 +172,8 @@ fn build_collection(
|
|||
iasf += 1;
|
||||
let parent_id = if id_table.contains_key(r.raw()) {
|
||||
let p = id_table[r.raw()];
|
||||
if !(threads[p].is_descendant(threads, &threads[curr_ref]) ||
|
||||
threads[curr_ref].is_descendant(threads, &threads[p]))
|
||||
if !(threads[p].is_descendant(threads, &threads[curr_ref])
|
||||
|| threads[curr_ref].is_descendant(threads, &threads[p]))
|
||||
{
|
||||
threads[curr_ref].parent = Some(p);
|
||||
if threads[p].first_child.is_none() {
|
||||
|
@ -228,7 +228,6 @@ fn build_collection(
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/// Builds threads from a collection.
|
||||
pub fn build_threads(
|
||||
collection: &mut Vec<Envelope>,
|
||||
|
@ -262,9 +261,9 @@ pub fn build_threads(
|
|||
let sent_mailbox = sent_mailbox.unwrap();
|
||||
|
||||
for x in &sent_mailbox.collection {
|
||||
if id_table.contains_key(x.message_id_raw()) ||
|
||||
(!x.in_reply_to_raw().is_empty() &&
|
||||
id_table.contains_key(x.in_reply_to_raw()))
|
||||
if id_table.contains_key(x.message_id_raw())
|
||||
|| (!x.in_reply_to_raw().is_empty()
|
||||
&& id_table.contains_key(x.in_reply_to_raw()))
|
||||
{
|
||||
let mut x: Envelope = (*x).clone();
|
||||
if id_table.contains_key(x.message_id_raw()) {
|
||||
|
@ -277,8 +276,8 @@ pub fn build_threads(
|
|||
assert!(threads[c].has_children());
|
||||
threads[c].date = x.date();
|
||||
x.set_thread(c);
|
||||
} else if !x.in_reply_to_raw().is_empty() &&
|
||||
id_table.contains_key(x.in_reply_to_raw())
|
||||
} else if !x.in_reply_to_raw().is_empty()
|
||||
&& id_table.contains_key(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()) {
|
||||
|
@ -300,8 +299,8 @@ pub fn build_threads(
|
|||
tidx - 1
|
||||
};
|
||||
threads[c].parent = Some(p);
|
||||
if threads[p].is_descendant(&threads, &threads[c]) ||
|
||||
threads[c].is_descendant(&threads, &threads[p])
|
||||
if threads[p].is_descendant(&threads, &threads[c])
|
||||
|| threads[c].is_descendant(&threads, &threads[p])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
@ -344,8 +343,9 @@ pub fn build_threads(
|
|||
let mut root_set = Vec::with_capacity(collection.len());
|
||||
'root_set: for v in id_table.values() {
|
||||
if threads[*v].parent.is_none() {
|
||||
if !threads[*v].has_message() && threads[*v].has_children() &&
|
||||
!threads[threads[*v].first_child.unwrap()].has_sibling()
|
||||
if !threads[*v].has_message()
|
||||
&& 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
|
||||
* -- unless there is only one child, in which case, do. */
|
||||
|
@ -369,15 +369,14 @@ pub fn build_threads(
|
|||
) {
|
||||
let thread = threads[i];
|
||||
if threads[root_subject_idx].has_message() {
|
||||
let root_subject =
|
||||
collection[threads[root_subject_idx].message().unwrap()].subject();
|
||||
let root_subject = collection[threads[root_subject_idx].message().unwrap()].subject();
|
||||
/* 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
|
||||
* list.) */
|
||||
if indentation > 0 && thread.has_message() {
|
||||
let subject = collection[thread.message().unwrap()].subject();
|
||||
if subject == root_subject ||
|
||||
subject.starts_with("Re: ") && subject.ends_with(root_subject)
|
||||
if subject == root_subject
|
||||
|| subject.starts_with("Re: ") && subject.ends_with(root_subject)
|
||||
{
|
||||
threads[i].set_show_subject(false);
|
||||
}
|
||||
|
@ -420,6 +419,5 @@ pub fn build_threads(
|
|||
);
|
||||
}
|
||||
|
||||
|
||||
(threads, threaded_collection)
|
||||
}
|
||||
|
|
69
src/bin.rs
69
src/bin.rs
|
@ -27,8 +27,8 @@ The mail handling stuff is done in the `melib` crate which includes all backend
|
|||
extern crate melib;
|
||||
extern crate ui;
|
||||
|
||||
use ui::*;
|
||||
pub use melib::*;
|
||||
use ui::*;
|
||||
|
||||
use std::thread;
|
||||
|
||||
|
@ -40,17 +40,28 @@ use chan_signal::Signal;
|
|||
|
||||
extern crate nix;
|
||||
|
||||
fn make_input_thread(sx: chan::Sender<ThreadEvent>, rx: chan::Receiver<bool>) -> thread::JoinHandle<()> {
|
||||
let stdin = std::io::stdin();
|
||||
thread::Builder::new().name("input-thread".to_string()).spawn(move || {
|
||||
|
||||
get_events(stdin,
|
||||
|k| {
|
||||
sx.send(ThreadEvent::Input(k));
|
||||
},
|
||||
|| {
|
||||
sx.send(ThreadEvent::UIEventType(UIEventType::ChangeMode(UIMode::Fork)));
|
||||
}, rx)}).unwrap()
|
||||
fn make_input_thread(
|
||||
sx: chan::Sender<ThreadEvent>,
|
||||
rx: chan::Receiver<bool>,
|
||||
) -> thread::JoinHandle<()> {
|
||||
let stdin = std::io::stdin();
|
||||
thread::Builder::new()
|
||||
.name("input-thread".to_string())
|
||||
.spawn(move || {
|
||||
get_events(
|
||||
stdin,
|
||||
|k| {
|
||||
sx.send(ThreadEvent::Input(k));
|
||||
},
|
||||
|| {
|
||||
sx.send(ThreadEvent::UIEventType(UIEventType::ChangeMode(
|
||||
UIMode::Fork,
|
||||
)));
|
||||
},
|
||||
rx,
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
fn main() {
|
||||
/* Lock all stdio outs */
|
||||
|
@ -68,7 +79,6 @@ fn main() {
|
|||
* */
|
||||
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
|
||||
* stdin, see get_events() for details
|
||||
|
@ -78,18 +88,27 @@ fn main() {
|
|||
let mut _thread_handler = make_input_thread(sender.clone(), rx.clone());
|
||||
|
||||
/* 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 */
|
||||
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 b = Entity { component: Box::new(listing) };
|
||||
let window = Entity { component: Box::new(VSplit::new(menu, b, 90, true)) };
|
||||
let status_bar = Entity { component: Box::new(StatusBar::new(window)) };
|
||||
let b = Entity {
|
||||
component: Box::new(listing),
|
||||
};
|
||||
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);
|
||||
|
||||
|
||||
let xdg_notifications = Entity { component: Box::new(ui::components::notifications::XDGNotifications {}) };
|
||||
let xdg_notifications = Entity {
|
||||
component: Box::new(ui::components::notifications::XDGNotifications {}),
|
||||
};
|
||||
state.register_entity(xdg_notifications);
|
||||
|
||||
/* Keep track of the input mode. See ui::UIMode for details */
|
||||
|
@ -191,18 +210,18 @@ fn main() {
|
|||
make_input_thread(sender.clone(), rx.clone());
|
||||
state.mode = UIMode::Normal;
|
||||
state.render();
|
||||
},
|
||||
}
|
||||
Some(false) => {
|
||||
use std::{thread, time};
|
||||
let ten_millis = time::Duration::from_millis(1500);
|
||||
thread::sleep(ten_millis);
|
||||
|
||||
continue 'reap;
|
||||
},
|
||||
None => {break 'reap;},
|
||||
}
|
||||
None => {
|
||||
break 'reap;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,13 +2,12 @@
|
|||
Define a (x, y) point in the terminal display as a holder of a character, foreground/background
|
||||
colors and attributes.
|
||||
*/
|
||||
use std::ops::{Index, IndexMut, Deref, DerefMut};
|
||||
use super::position::*;
|
||||
use std::convert::From;
|
||||
use std::fmt;
|
||||
use super::position::*;
|
||||
use std::ops::{Deref, DerefMut, Index, IndexMut};
|
||||
use termion::color::AnsiValue;
|
||||
|
||||
|
||||
/// Types and implementations taken from rustty for convenience.
|
||||
|
||||
pub trait CellAccessor: HasSize {
|
||||
|
@ -180,7 +179,7 @@ impl fmt::Display for CellBuffer {
|
|||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
'_y: for y in 0..self.rows {
|
||||
'_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();
|
||||
if *c == '\n' {
|
||||
continue '_y;
|
||||
|
@ -444,15 +443,17 @@ impl Color {
|
|||
|
||||
pub fn as_termion(&self) -> AnsiValue {
|
||||
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 =>
|
||||
{
|
||||
AnsiValue(b.as_byte())
|
||||
},
|
||||
Color::Byte(b) => {
|
||||
AnsiValue(b as u8)
|
||||
},
|
||||
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 => AnsiValue(b.as_byte()),
|
||||
Color::Byte(b) => AnsiValue(b as u8),
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,12 @@ impl MailListing {
|
|||
/// Helper function to format entry strings for MailListing */
|
||||
/* TODO: Make this configurable */
|
||||
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 {
|
||||
|
@ -51,42 +56,52 @@ impl MailListing {
|
|||
self.cursor_pos.1 = self.new_cursor_pos.1;
|
||||
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.
|
||||
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.
|
||||
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 {
|
||||
mailbox.threaded_collection.len()
|
||||
} else {
|
||||
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 {
|
||||
write_string_to_grid(&format!("Folder `{}` is empty.",
|
||||
mailbox.folder.name()),
|
||||
&mut content,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
((0, 0), (MAX_COLS-1, 0)),
|
||||
true);
|
||||
write_string_to_grid(
|
||||
&format!("Folder `{}` is empty.", mailbox.folder.name()),
|
||||
&mut content,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
((0, 0), (MAX_COLS - 1, 0)),
|
||||
true,
|
||||
);
|
||||
self.content = content;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// TODO: Fix the threaded hell and refactor stuff into seperate functions and/or modules.
|
||||
if threaded {
|
||||
let mut indentations: Vec<bool> = Vec::with_capacity(6);
|
||||
let mut thread_idx = 0; // needed for alternate thread colors
|
||||
/* Draw threaded view. */
|
||||
let mut iter = mailbox
|
||||
/* Draw threaded view. */
|
||||
let mut iter = mailbox.threaded_collection.iter().enumerate().peekable();
|
||||
let len = mailbox
|
||||
.threaded_collection
|
||||
.iter()
|
||||
.enumerate()
|
||||
.peekable();
|
||||
let len = mailbox.threaded_collection.len().to_string().chars().count();
|
||||
.len()
|
||||
.to_string()
|
||||
.chars()
|
||||
.count();
|
||||
/* This is just a desugared for loop so that we can use .peek() */
|
||||
while let Some((idx, i)) = iter.next() {
|
||||
let container = mailbox.thread(*i);
|
||||
|
@ -98,12 +113,10 @@ impl MailListing {
|
|||
|
||||
assert_eq!(container.has_message(), true);
|
||||
match iter.peek() {
|
||||
Some(&(_, x))
|
||||
if mailbox.thread(*x).indentation() == indentation =>
|
||||
{
|
||||
indentations.pop();
|
||||
indentations.push(true);
|
||||
}
|
||||
Some(&(_, x)) if mailbox.thread(*x).indentation() == indentation => {
|
||||
indentations.pop();
|
||||
indentations.push(true);
|
||||
}
|
||||
_ => {
|
||||
indentations.pop();
|
||||
indentations.push(false);
|
||||
|
@ -113,7 +126,7 @@ impl MailListing {
|
|||
indentations.pop();
|
||||
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() {
|
||||
Color::Byte(0)
|
||||
} else {
|
||||
|
@ -126,43 +139,46 @@ impl MailListing {
|
|||
} else {
|
||||
Color::Default
|
||||
};
|
||||
let (x, _) = write_string_to_grid(&MailListing::make_thread_entry(envelope, idx, indentation, container, &indentations, len),
|
||||
&mut content,
|
||||
fg_color,
|
||||
bg_color,
|
||||
((0, idx) , (MAX_COLS-1, idx)),
|
||||
false);
|
||||
let (x, _) = write_string_to_grid(
|
||||
&MailListing::make_thread_entry(
|
||||
envelope,
|
||||
idx,
|
||||
indentation,
|
||||
container,
|
||||
&indentations,
|
||||
len,
|
||||
),
|
||||
&mut content,
|
||||
fg_color,
|
||||
bg_color,
|
||||
((0, idx), (MAX_COLS - 1, idx)),
|
||||
false,
|
||||
);
|
||||
for x in x..MAX_COLS {
|
||||
content[(x,idx)].set_ch(' ');
|
||||
content[(x,idx)].set_bg(bg_color);
|
||||
content[(x, idx)].set_ch(' ');
|
||||
content[(x, idx)].set_bg(bg_color);
|
||||
}
|
||||
|
||||
match iter.peek() {
|
||||
Some(&(_, x))
|
||||
if mailbox.thread(*x).indentation() > indentation =>
|
||||
{
|
||||
indentations.push(false);
|
||||
}
|
||||
Some(&(_, x))
|
||||
if mailbox.thread(*x).indentation() < indentation =>
|
||||
{
|
||||
for _ in 0..(indentation - mailbox.thread(*x).indentation()) {
|
||||
indentations.pop();
|
||||
}
|
||||
Some(&(_, x)) if mailbox.thread(*x).indentation() > indentation => {
|
||||
indentations.push(false);
|
||||
}
|
||||
Some(&(_, x)) if mailbox.thread(*x).indentation() < indentation => {
|
||||
for _ in 0..(indentation - mailbox.thread(*x).indentation()) {
|
||||
indentations.pop();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
// Populate `CellBuffer` with every entry.
|
||||
// TODO: Lazy load?
|
||||
let mut idx = 0;
|
||||
for y in 0..=self.length {
|
||||
if idx >= self.length {
|
||||
/* No more entries left, so fill the rest of the area with empty space */
|
||||
clear_area(&mut content,
|
||||
((0, y), (MAX_COLS-1, self.length)));
|
||||
clear_area(&mut content, ((0, y), (MAX_COLS - 1, self.length)));
|
||||
break;
|
||||
}
|
||||
/* Write an entire line for each envelope entry. */
|
||||
|
@ -180,28 +196,35 @@ impl MailListing {
|
|||
} else {
|
||||
Color::Default
|
||||
};
|
||||
let (x, y)= write_string_to_grid(&MailListing::make_entry_string(envelope, idx),
|
||||
&mut content,
|
||||
fg_color,
|
||||
bg_color,
|
||||
((0, y) , (MAX_COLS-1, y)),
|
||||
false);
|
||||
let (x, y) = write_string_to_grid(
|
||||
&MailListing::make_entry_string(envelope, idx),
|
||||
&mut content,
|
||||
fg_color,
|
||||
bg_color,
|
||||
((0, y), (MAX_COLS - 1, y)),
|
||||
false,
|
||||
);
|
||||
|
||||
for x in x..MAX_COLS {
|
||||
content[(x,y)].set_ch(' ');
|
||||
content[(x,y)].set_bg(bg_color);
|
||||
content[(x, y)].set_ch(' ');
|
||||
content[(x, y)].set_bg(bg_color);
|
||||
}
|
||||
|
||||
idx+=1;
|
||||
idx += 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
self.content = content;
|
||||
}
|
||||
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 mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1].as_ref().unwrap().as_ref().unwrap();
|
||||
let threaded = context.accounts[self.cursor_pos.0]
|
||||
.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 i = mailbox.threaded_mail(idx);
|
||||
&mailbox.collection[i]
|
||||
|
@ -237,7 +260,7 @@ impl MailListing {
|
|||
let bottom_right = bottom_right!(area);
|
||||
if self.length == 0 {
|
||||
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);
|
||||
return;
|
||||
}
|
||||
|
@ -245,8 +268,7 @@ impl MailListing {
|
|||
let prev_page_no = (self.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
|
||||
* apply it in the new one. */
|
||||
|
@ -257,31 +279,57 @@ impl MailListing {
|
|||
if *idx >= self.length {
|
||||
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);
|
||||
context.dirty_areas.push_back(new_area);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/* Page_no has changed, so draw new page */
|
||||
copy_area(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);
|
||||
copy_area(
|
||||
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);
|
||||
}
|
||||
|
||||
fn make_thread_entry(envelope: &Envelope, idx: usize, indent: usize,
|
||||
container: &Container, indentations: &Vec<bool>, idx_width: usize) -> String {
|
||||
fn make_thread_entry(
|
||||
envelope: &Envelope,
|
||||
idx: usize,
|
||||
indent: usize,
|
||||
container: &Container,
|
||||
indentations: &Vec<bool>,
|
||||
idx_width: usize,
|
||||
) -> String {
|
||||
let has_sibling = container.has_sibling();
|
||||
let has_parent = container.has_parent();
|
||||
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 {
|
||||
if indentations.len() > i && indentations[i]
|
||||
{
|
||||
if indentations.len() > i && indentations[i] {
|
||||
s.push('│');
|
||||
} else {
|
||||
s.push(' ');
|
||||
|
@ -298,7 +346,8 @@ impl MailListing {
|
|||
} else {
|
||||
s.push('└');
|
||||
}
|
||||
s.push('─'); s.push('>');
|
||||
s.push('─');
|
||||
s.push('>');
|
||||
}
|
||||
|
||||
if show_subject {
|
||||
|
@ -306,7 +355,7 @@ impl MailListing {
|
|||
}
|
||||
let attach_count = envelope.body().count_attachments();
|
||||
if attach_count > 1 {
|
||||
s.push_str(&format!(" {}∞ ", attach_count-1));
|
||||
s.push_str(&format!(" {}∞ ", attach_count - 1));
|
||||
}
|
||||
s
|
||||
}
|
||||
|
@ -314,20 +363,12 @@ impl MailListing {
|
|||
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();
|
||||
match now.as_secs() {
|
||||
n if n < 10*60*60 => {
|
||||
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 => {
|
||||
format!("{} days ago{}", n / (24*60*60), " ".repeat(9))
|
||||
},
|
||||
_ => {
|
||||
envelope.datetime().format("%Y-%m-%d %H:%M:%S").to_string()
|
||||
},
|
||||
|
||||
|
||||
n if n < 10 * 60 * 60 => 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 => {
|
||||
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 */
|
||||
let total_rows = get_y(bottom_right) - get_y(upper_left);
|
||||
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 {
|
||||
clear_area(grid, area);
|
||||
|
@ -360,9 +401,14 @@ impl Component for MailListing {
|
|||
return;
|
||||
}
|
||||
let mid = get_y(upper_left) + total_rows - bottom_entity_rows;
|
||||
self.draw_list(grid,
|
||||
(upper_left, (get_x(bottom_right), get_y(upper_left)+ mid-1)),
|
||||
context);
|
||||
self.draw_list(
|
||||
grid,
|
||||
(
|
||||
upper_left,
|
||||
(get_x(bottom_right), get_y(upper_left) + mid - 1),
|
||||
),
|
||||
context,
|
||||
);
|
||||
if self.length == 0 {
|
||||
self.dirty = false;
|
||||
return;
|
||||
|
@ -378,16 +424,22 @@ impl Component for MailListing {
|
|||
for i in get_x(upper_left)..=get_x(bottom_right) {
|
||||
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
|
||||
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -398,17 +450,17 @@ impl Component for MailListing {
|
|||
self.new_cursor_pos.2 -= 1;
|
||||
self.dirty = true;
|
||||
}
|
||||
},
|
||||
}
|
||||
UIEventType::Input(Key::Down) => {
|
||||
if self.length > 0 && self.new_cursor_pos.2 < self.length - 1 {
|
||||
self.new_cursor_pos.2 += 1;
|
||||
self.dirty = true;
|
||||
}
|
||||
},
|
||||
}
|
||||
UIEventType::Input(Key::Char('\n')) if self.unfocused == false => {
|
||||
self.unfocused = true;
|
||||
self.dirty = true;
|
||||
},
|
||||
}
|
||||
UIEventType::Input(Key::Char('m')) if self.unfocused == false => {
|
||||
use std::process::{Command, Stdio};
|
||||
/* 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
|
||||
* input-thread
|
||||
*/
|
||||
context.replies.push_back(UIEvent { id: 0, event_type: UIEventType::Fork(ForkType::NewDraft(f, output)) });
|
||||
context.replies.push_back(UIEvent { id: 0, event_type: UIEventType::ChangeMode(UIMode::Fork) });
|
||||
context.replies.push_back(UIEvent {
|
||||
id: 0,
|
||||
event_type: UIEventType::Fork(ForkType::NewDraft(f, output)),
|
||||
});
|
||||
context.replies.push_back(UIEvent {
|
||||
id: 0,
|
||||
event_type: UIEventType::ChangeMode(UIMode::Fork),
|
||||
});
|
||||
return;
|
||||
},
|
||||
}
|
||||
UIEventType::Input(Key::Char('i')) if self.unfocused == true => {
|
||||
self.unfocused = false;
|
||||
self.dirty = true;
|
||||
self.view = None;
|
||||
|
||||
},
|
||||
}
|
||||
UIEventType::Input(Key::Char(k @ 'J')) | UIEventType::Input(Key::Char(k @ 'K')) => {
|
||||
let folder_length = context.accounts[self.cursor_pos.0].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.dirty = true;
|
||||
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.1 = 0;
|
||||
self.dirty = true;
|
||||
self.refresh_mailbox(context);
|
||||
}
|
||||
},
|
||||
}
|
||||
'K' => {
|
||||
if self.cursor_pos.1 > 0 {
|
||||
self.new_cursor_pos.1 = self.cursor_pos.1 - 1;
|
||||
self.dirty = true;
|
||||
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.1 = 0;
|
||||
self.dirty = true;
|
||||
self.refresh_mailbox(context);
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
},
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
},
|
||||
}
|
||||
UIEventType::Input(Key::Char(k @ 'h')) | UIEventType::Input(Key::Char(k @ 'l')) => {
|
||||
let accounts_length = context.accounts.len();
|
||||
match k {
|
||||
|
@ -496,40 +553,38 @@ impl Component for MailListing {
|
|||
self.new_cursor_pos.1 = 0;
|
||||
self.dirty = true;
|
||||
self.refresh_mailbox(context);
|
||||
},
|
||||
}
|
||||
'l' if self.cursor_pos.0 > 0 => {
|
||||
self.new_cursor_pos.0 = self.cursor_pos.0 - 1;
|
||||
self.new_cursor_pos.1 = 0;
|
||||
self.dirty = true;
|
||||
self.refresh_mailbox(context);
|
||||
},
|
||||
_ => {
|
||||
},
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
},
|
||||
}
|
||||
UIEventType::RefreshMailbox(_) => {
|
||||
self.dirty = true;
|
||||
self.view = None;
|
||||
},
|
||||
}
|
||||
UIEventType::ChangeMode(UIMode::Normal) => {
|
||||
self.dirty = true;
|
||||
},
|
||||
}
|
||||
UIEventType::Resize => {
|
||||
self.dirty = true;
|
||||
},
|
||||
UIEventType::Action(ref action) => {
|
||||
match action {
|
||||
Action::MailListing(MailListingAction::ToggleThreaded) => {
|
||||
context.accounts[self.cursor_pos.0].runtime_settings.threaded = !context.accounts[self.cursor_pos.0].runtime_settings.threaded;
|
||||
self.refresh_mailbox(context);
|
||||
self.dirty = true;
|
||||
|
||||
|
||||
},
|
||||
}
|
||||
UIEventType::Action(ref action) => match action {
|
||||
Action::MailListing(MailListingAction::ToggleThreaded) => {
|
||||
context.accounts[self.cursor_pos.0]
|
||||
.runtime_settings
|
||||
.threaded = !context.accounts[self.cursor_pos.0]
|
||||
.runtime_settings
|
||||
.threaded;
|
||||
self.refresh_mailbox(context);
|
||||
self.dirty = true;
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
if let Some(ref mut v) = self.view {
|
||||
v.process_event(event, context);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*! Entities that handle Mail specific functions.
|
||||
*/
|
||||
*/
|
||||
use super::*;
|
||||
|
||||
pub mod listing;
|
||||
|
@ -7,7 +7,6 @@ pub mod view;
|
|||
pub use listing::*;
|
||||
pub use view::*;
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
struct AccountMenuEntry {
|
||||
name: String,
|
||||
|
@ -15,7 +14,6 @@ struct AccountMenuEntry {
|
|||
entries: Vec<(usize, Folder)>,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AccountMenu {
|
||||
accounts: Vec<AccountMenuEntry>,
|
||||
|
@ -25,8 +23,10 @@ pub struct AccountMenu {
|
|||
|
||||
impl AccountMenu {
|
||||
pub fn new(accounts: &Vec<Account>) -> Self {
|
||||
let accounts = accounts.iter().enumerate().map(|(i, a)| {
|
||||
AccountMenuEntry {
|
||||
let accounts = accounts
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, a)| AccountMenuEntry {
|
||||
name: a.name().to_string(),
|
||||
index: i,
|
||||
entries: {
|
||||
|
@ -34,9 +34,10 @@ impl AccountMenu {
|
|||
for (idx, acc) in a.list_folders().iter().enumerate() {
|
||||
entries.push((idx, acc.clone()));
|
||||
}
|
||||
entries}
|
||||
}
|
||||
}).collect();
|
||||
entries
|
||||
},
|
||||
})
|
||||
.collect();
|
||||
AccountMenu {
|
||||
accounts: accounts,
|
||||
dirty: true,
|
||||
|
@ -50,10 +51,9 @@ impl AccountMenu {
|
|||
let upper_left = upper_left!(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 c in e.1.children() {
|
||||
|
@ -79,15 +79,22 @@ impl AccountMenu {
|
|||
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();
|
||||
s.insert_str(len, &format!("{} {}\n ", *inc, &entries[root].1.name()));
|
||||
s.insert_str(len, &format!("{} {}\n ", *inc, &entries[root].1.name()));
|
||||
*inc += 1;
|
||||
let children_no = entries[root].1.children().len();
|
||||
for (idx, child) in entries[root].1.children().iter().enumerate() {
|
||||
let len = s.len();
|
||||
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);
|
||||
pop(depth);
|
||||
}
|
||||
|
@ -128,17 +135,24 @@ impl AccountMenu {
|
|||
Color::Default
|
||||
};
|
||||
|
||||
let (x, _) = write_string_to_grid(&s,
|
||||
grid,
|
||||
color_fg,
|
||||
color_bg,
|
||||
(set_y(upper_left, y), bottom_right),
|
||||
false);
|
||||
let (x, _) = write_string_to_grid(
|
||||
&s,
|
||||
grid,
|
||||
color_fg,
|
||||
color_bg,
|
||||
(set_y(upper_left, y), bottom_right),
|
||||
false,
|
||||
);
|
||||
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
|
@ -147,7 +161,6 @@ impl AccountMenu {
|
|||
} else {
|
||||
idx - 1
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -162,9 +175,7 @@ impl Component for AccountMenu {
|
|||
self.dirty = false;
|
||||
let mut y = get_y(upper_left);
|
||||
for a in &self.accounts {
|
||||
y += self.print_account(grid,
|
||||
(set_y(upper_left, y), bottom_right),
|
||||
&a);
|
||||
y += self.print_account(grid, (set_y(upper_left, y), bottom_right), &a);
|
||||
}
|
||||
|
||||
context.dirty_areas.push_back(area);
|
||||
|
@ -174,15 +185,14 @@ impl Component for AccountMenu {
|
|||
UIEventType::RefreshMailbox(c) => {
|
||||
self.cursor = Some(c);
|
||||
self.dirty = true;
|
||||
},
|
||||
}
|
||||
UIEventType::ChangeMode(UIMode::Normal) => {
|
||||
self.dirty = true;
|
||||
},
|
||||
}
|
||||
UIEventType::Resize => {
|
||||
self.dirty = true;
|
||||
},
|
||||
_ => {
|
||||
},
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
fn is_dirty(&self) -> bool {
|
||||
|
|
|
@ -26,16 +26,16 @@
|
|||
*/
|
||||
|
||||
use super::*;
|
||||
pub mod utilities;
|
||||
pub mod mail;
|
||||
pub mod notifications;
|
||||
pub mod utilities;
|
||||
|
||||
pub use utilities::*;
|
||||
pub use mail::*;
|
||||
pub use utilities::*;
|
||||
|
||||
use super::cells::{Color, CellBuffer};
|
||||
use super::position::{Area, };
|
||||
use super::{UIEvent, UIEventType, Key};
|
||||
use super::cells::{CellBuffer, Color};
|
||||
use super::position::Area;
|
||||
use super::{Key, UIEvent, UIEventType};
|
||||
|
||||
/// The upper and lower boundary char.
|
||||
const HORZ_BOUNDARY: char = '─';
|
||||
|
@ -84,11 +84,18 @@ pub trait Component {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// 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) {
|
||||
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;
|
||||
}
|
||||
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_;
|
||||
}
|
||||
|
||||
grid_dest[(x,y)] = grid_src[(src_x, src_y)];
|
||||
grid_dest[(x, y)] = grid_src[(src_x, src_y)];
|
||||
src_x += 1;
|
||||
if src_x >= get_x(bottom_right!(src)) {
|
||||
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.
|
||||
pub fn copy_area(grid_dest: &mut CellBuffer, grid_src: &CellBuffer, dest: Area, src: Area) {
|
||||
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;
|
||||
}
|
||||
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_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)) {
|
||||
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));
|
||||
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;
|
||||
}
|
||||
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 x in get_x(upper_left!(area))..=get_x(bottom_right!(area)) {
|
||||
grid[(x,y)].set_fg(fg_color);
|
||||
grid[(x,y)].set_bg(bg_color);
|
||||
grid[(x, y)].set_fg(fg_color);
|
||||
grid[(x, y)].set_bg(bg_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// 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 upper_left = upper_left!(area);
|
||||
let bottom_right = bottom_right!(area);
|
||||
let (mut x, mut y) = upper_left;
|
||||
if y > (get_y(bottom_right)) || x > get_x(bottom_right) ||
|
||||
y > get_y(bounds) || x > get_x(bounds) {
|
||||
eprintln!(" Invalid area with string {} and area {:?}", s, area);
|
||||
if y > (get_y(bottom_right))
|
||||
|| x > get_x(bottom_right)
|
||||
|| y > get_y(bounds)
|
||||
|| x > get_x(bounds)
|
||||
{
|
||||
eprintln!(" Invalid area with string {} and area {:?}", s, area);
|
||||
return (x, y);
|
||||
}
|
||||
for l in s.lines() {
|
||||
'char: for c in l.chars() {
|
||||
grid[(x,y)].set_ch(c);
|
||||
grid[(x,y)].set_fg(fg_color);
|
||||
grid[(x,y)].set_bg(bg_color);
|
||||
grid[(x, y)].set_ch(c);
|
||||
grid[(x, y)].set_fg(fg_color);
|
||||
grid[(x, y)].set_bg(bg_color);
|
||||
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);
|
||||
y += 1;
|
||||
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);
|
||||
for y in get_y(upper_left)..=get_y(bottom_right) {
|
||||
for x in get_x(upper_left)..=get_x(bottom_right) {
|
||||
grid[(x,y)].set_ch(' ');
|
||||
grid[(x,y)].set_bg(Color::Default);
|
||||
grid[(x,y)].set_fg(Color::Default);
|
||||
grid[(x, y)].set_ch(' ');
|
||||
grid[(x, y)].set_bg(Color::Default);
|
||||
grid[(x, y)].set_fg(Color::Default);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,20 +9,18 @@ use super::*;
|
|||
pub struct 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) {
|
||||
match event.event_type {
|
||||
UIEventType::Notification(ref t) => {
|
||||
notify_Notification::new()
|
||||
.summary("Refresh Event")
|
||||
.body(t)
|
||||
.icon("dialog-information")
|
||||
.show().unwrap();
|
||||
},
|
||||
notify_Notification::new()
|
||||
.summary("Refresh Event")
|
||||
.body(t)
|
||||
.icon("dialog-information")
|
||||
.show()
|
||||
.unwrap();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*! Various useful components that can be used in a generic fashion.
|
||||
*/
|
||||
*/
|
||||
use super::*;
|
||||
|
||||
/// A horizontally split in half container.
|
||||
|
@ -21,7 +21,6 @@ impl HSplit {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
impl Component for HSplit {
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
if !is_valid_area!(area) {
|
||||
|
@ -30,7 +29,7 @@ impl Component for HSplit {
|
|||
let upper_left = upper_left!(area);
|
||||
let bottom_right = bottom_right!(area);
|
||||
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;
|
||||
|
||||
if self.show_divider {
|
||||
|
@ -38,19 +37,26 @@ impl Component for HSplit {
|
|||
grid[(i, mid)].set_ch('─');
|
||||
}
|
||||
}
|
||||
let _ = self.top.component.draw(grid,
|
||||
(upper_left, (get_x(bottom_right), get_y(upper_left) + mid-1)),
|
||||
context);
|
||||
let _ = self.bottom.component.draw(grid,
|
||||
((get_x(upper_left), get_y(upper_left) + mid), bottom_right),
|
||||
context);
|
||||
let _ = self.top.component.draw(
|
||||
grid,
|
||||
(
|
||||
upper_left,
|
||||
(get_x(bottom_right), get_y(upper_left) + mid - 1),
|
||||
),
|
||||
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) {
|
||||
self.top.rcv_event(event, context);
|
||||
self.bottom.rcv_event(event, context);
|
||||
}
|
||||
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 {
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
if !is_valid_area!(area) {
|
||||
|
@ -83,16 +88,18 @@ impl Component for VSplit {
|
|||
let upper_left = upper_left!(area);
|
||||
let bottom_right = bottom_right!(area);
|
||||
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;
|
||||
|
||||
if get_y(upper_left)> 1 {
|
||||
let c = grid.get(mid, get_y(upper_left)-1).map(|a| a.ch()).unwrap_or_else(|| ' ');
|
||||
if get_y(upper_left) > 1 {
|
||||
let c = grid.get(mid, get_y(upper_left) - 1)
|
||||
.map(|a| a.ch())
|
||||
.unwrap_or_else(|| ' ');
|
||||
match c {
|
||||
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_bg(Color::Default);
|
||||
}
|
||||
if get_y(bottom_right)> 1 {
|
||||
let c = grid.get(mid, get_y(bottom_right)-1).map(|a| a.ch()).unwrap_or_else(|| ' ');
|
||||
if get_y(bottom_right) > 1 {
|
||||
let c = grid.get(mid, get_y(bottom_right) - 1)
|
||||
.map(|a| a.ch())
|
||||
.unwrap_or_else(|| ' ');
|
||||
match c {
|
||||
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,
|
||||
(upper_left, (mid-1, get_y(bottom_right))),
|
||||
context);
|
||||
let _ = self.right.component.draw(grid,
|
||||
((mid+1, get_y(upper_left)), bottom_right),
|
||||
context);
|
||||
let _ =
|
||||
self.left
|
||||
.component
|
||||
.draw(grid, (upper_left, (mid - 1, get_y(bottom_right))), context);
|
||||
let _ =
|
||||
self.right
|
||||
.component
|
||||
.draw(grid, ((mid + 1, get_y(upper_left)), bottom_right), context);
|
||||
}
|
||||
fn process_event(&mut self, event: &UIEvent, context: &mut Context) {
|
||||
self.left.rcv_event(event, context);
|
||||
self.right.rcv_event(event, context);
|
||||
}
|
||||
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()
|
||||
.expect("Failed to start pager filter process");
|
||||
{
|
||||
let mut stdin =
|
||||
filter_child.stdin.as_mut().expect("failed to open stdin");
|
||||
stdin.write_all(text.as_bytes()).expect("Failed to write to stdin");
|
||||
let mut stdin = filter_child.stdin.as_mut().expect("failed to open stdin");
|
||||
stdin
|
||||
.write_all(text.as_bytes())
|
||||
.expect("Failed to write to stdin");
|
||||
}
|
||||
|
||||
|
||||
text = String::from_utf8_lossy(&filter_child.wait_with_output().expect("Failed to wait on filter").stdout).to_string();
|
||||
text = String::from_utf8_lossy(
|
||||
&filter_child
|
||||
.wait_with_output()
|
||||
.expect("Failed to wait on filter")
|
||||
.stdout,
|
||||
).to_string();
|
||||
}
|
||||
|
||||
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);
|
||||
if width > 0 {
|
||||
for (i, l) in lines.iter().enumerate() {
|
||||
write_string_to_grid(l,
|
||||
content,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
((0, i), (width -1, i)),
|
||||
true);
|
||||
write_string_to_grid(
|
||||
l,
|
||||
content,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
((0, i), (width - 1, i)),
|
||||
true,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -249,13 +267,17 @@ impl Component for Pager {
|
|||
return;
|
||||
}
|
||||
|
||||
clear_area(grid,
|
||||
(upper_left, bottom_right));
|
||||
clear_area(grid, (upper_left, bottom_right));
|
||||
//let pager_context: usize = context.settings.pager.pager_context;
|
||||
//let pager_stop: bool = context.settings.pager.pager_stop;
|
||||
//let rows = y(bottom_right) - y(upper_left);
|
||||
//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);
|
||||
}
|
||||
fn process_event(&mut self, event: &UIEvent, _context: &mut Context) {
|
||||
|
@ -265,21 +287,20 @@ impl Component for Pager {
|
|||
self.cursor_pos -= 1;
|
||||
self.dirty = true;
|
||||
}
|
||||
},
|
||||
}
|
||||
UIEventType::Input(Key::Char('j')) => {
|
||||
if self.height > 0 && self.cursor_pos < self.height - 1 {
|
||||
self.cursor_pos += 1;
|
||||
self.dirty = true;
|
||||
}
|
||||
},
|
||||
}
|
||||
UIEventType::ChangeMode(UIMode::Normal) => {
|
||||
self.dirty = true;
|
||||
},
|
||||
}
|
||||
UIEventType::Resize => {
|
||||
self.dirty = true;
|
||||
},
|
||||
_ => {
|
||||
},
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
fn is_dirty(&self) -> bool {
|
||||
|
@ -314,34 +335,35 @@ impl StatusBar {
|
|||
clear_area(grid, area);
|
||||
if let Some(n) = self.notifications.pop_front() {
|
||||
self.dirty = true;
|
||||
write_string_to_grid(&n,
|
||||
grid,
|
||||
Color::Byte(219),
|
||||
Color::Byte(88),
|
||||
area, false);
|
||||
write_string_to_grid(&n, grid, Color::Byte(219), Color::Byte(88), area, false);
|
||||
} else {
|
||||
write_string_to_grid(&self.status,
|
||||
grid,
|
||||
Color::Byte(123),
|
||||
Color::Byte(26),
|
||||
area, false);
|
||||
write_string_to_grid(
|
||||
&self.status,
|
||||
grid,
|
||||
Color::Byte(123),
|
||||
Color::Byte(26),
|
||||
area,
|
||||
false,
|
||||
);
|
||||
}
|
||||
change_colors(grid, area, Color::Byte(123), Color::Byte(26));
|
||||
context.dirty_areas.push_back(area);
|
||||
}
|
||||
fn draw_execute_bar(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
clear_area(grid, area);
|
||||
write_string_to_grid(&self.ex_buffer,
|
||||
grid,
|
||||
Color::Byte(219),
|
||||
Color::Byte(88),
|
||||
area, false);
|
||||
write_string_to_grid(
|
||||
&self.ex_buffer,
|
||||
grid,
|
||||
Color::Byte(219),
|
||||
Color::Byte(88),
|
||||
area,
|
||||
false,
|
||||
);
|
||||
change_colors(grid, area, Color::Byte(219), Color::Byte(88));
|
||||
context.dirty_areas.push_back(area);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Component for StatusBar {
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
if !is_valid_area!(area) {
|
||||
|
@ -356,36 +378,57 @@ impl Component for StatusBar {
|
|||
}
|
||||
let height = self.height;
|
||||
|
||||
let _ = self.container.component.draw(grid,
|
||||
(upper_left, (get_x(bottom_right), get_y(bottom_right) - height)),
|
||||
context);
|
||||
let _ = self.container.component.draw(
|
||||
grid,
|
||||
(
|
||||
upper_left,
|
||||
(get_x(bottom_right), get_y(bottom_right) - height),
|
||||
),
|
||||
context,
|
||||
);
|
||||
|
||||
if !self.is_dirty() {
|
||||
return;
|
||||
}
|
||||
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 {
|
||||
UIMode::Normal => {
|
||||
},
|
||||
UIMode::Normal => {}
|
||||
UIMode::Execute => {
|
||||
self.draw_execute_bar(grid,
|
||||
(set_y(upper_left, get_y(bottom_right) - height + 1), set_y(bottom_right, get_y(bottom_right) - height+1)),
|
||||
context);
|
||||
},
|
||||
_ => {},
|
||||
|
||||
self.draw_execute_bar(
|
||||
grid,
|
||||
(
|
||||
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) {
|
||||
self.container.rcv_event(event, context);
|
||||
match &event.event_type {
|
||||
UIEventType::RefreshMailbox((ref idx_a, ref idx_f)) => {
|
||||
let m = &context.accounts[*idx_a][*idx_f].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());
|
||||
let m = &context.accounts[*idx_a][*idx_f]
|
||||
.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;
|
||||
|
||||
},
|
||||
}
|
||||
UIEventType::ChangeMode(m) => {
|
||||
let offset = self.status.find('|').unwrap_or(self.status.len());
|
||||
self.status.replace_range(..offset, &format!("{} ", m));
|
||||
|
@ -394,27 +437,30 @@ impl Component for StatusBar {
|
|||
match m {
|
||||
UIMode::Normal => {
|
||||
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()
|
||||
},
|
||||
}
|
||||
UIMode::Execute => {
|
||||
self.height = 2;
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
},
|
||||
}
|
||||
UIEventType::ExInput(Key::Char(c)) => {
|
||||
self.dirty = true;
|
||||
self.ex_buffer.push(*c);
|
||||
},
|
||||
}
|
||||
UIEventType::Resize => {
|
||||
self.dirty = true;
|
||||
},
|
||||
}
|
||||
UIEventType::StatusNotification(s) => {
|
||||
self.notifications.push_back(s.clone());
|
||||
self.dirty = true;
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
fn is_dirty(&self) -> bool {
|
||||
|
@ -422,7 +468,6 @@ impl Component for StatusBar {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// A box with a text content.
|
||||
pub struct TextBox {
|
||||
_content: String,
|
||||
|
@ -430,16 +475,12 @@ pub struct TextBox {
|
|||
|
||||
impl TextBox {
|
||||
pub fn new(s: String) -> Self {
|
||||
TextBox {
|
||||
_content: s,
|
||||
}
|
||||
TextBox { _content: s }
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
/*! A parser module for user commands passed through the Ex mode.
|
||||
*/
|
||||
use nom::digit;
|
||||
use std;
|
||||
use nom::{digit, };
|
||||
|
||||
|
||||
named!(usize_c<usize>,
|
||||
map_res!(map_res!(ws!(digit), std::str::from_utf8), std::str::FromStr::from_str));
|
||||
named!(
|
||||
usize_c<usize>,
|
||||
map_res!(
|
||||
map_res!(ws!(digit), std::str::from_utf8),
|
||||
std::str::FromStr::from_str
|
||||
)
|
||||
);
|
||||
|
||||
named!(pub goto<usize>,
|
||||
preceded!(tag!("b "),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std;
|
||||
use std::path::PathBuf;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
|
@ -9,7 +9,6 @@ pub struct File {
|
|||
path: PathBuf,
|
||||
}
|
||||
|
||||
|
||||
impl File {
|
||||
pub fn file(&mut self) -> std::fs::File {
|
||||
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
|
||||
pub fn create_temp_file(bytes: &[u8], filename: Option<&PathBuf>) -> File {
|
||||
let mut dir = std::env::temp_dir();
|
||||
|
@ -28,7 +26,10 @@ pub fn create_temp_file(bytes: &[u8], filename: Option<&PathBuf>) -> File {
|
|||
p
|
||||
} else {
|
||||
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();
|
||||
dir.push(u.hyphenated().to_string());
|
||||
&dir
|
||||
|
@ -38,8 +39,5 @@ pub fn create_temp_file(bytes: &[u8], filename: Option<&PathBuf>) -> File {
|
|||
|
||||
f.write(bytes).unwrap();
|
||||
f.flush().unwrap();
|
||||
File {
|
||||
path: path.clone(),
|
||||
}
|
||||
File { path: path.clone() }
|
||||
}
|
||||
|
||||
|
|
228
ui/src/lib.rs
228
ui/src/lib.rs
|
@ -19,7 +19,6 @@
|
|||
* 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.
|
||||
|
||||
|
@ -38,10 +37,10 @@ pub use helpers::*;
|
|||
|
||||
#[macro_use]
|
||||
mod execute;
|
||||
use execute::goto;
|
||||
pub use self::position::*;
|
||||
use self::cells::*;
|
||||
pub use self::components::*;
|
||||
pub use self::position::*;
|
||||
use execute::goto;
|
||||
|
||||
extern crate melib;
|
||||
extern crate mime_apps;
|
||||
|
@ -53,15 +52,15 @@ extern crate linkify;
|
|||
extern crate uuid;
|
||||
use melib::*;
|
||||
|
||||
use std::io::{Write, };
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt;
|
||||
use std::io::Write;
|
||||
extern crate termion;
|
||||
use termion::{clear, style, cursor};
|
||||
use termion::raw::IntoRawMode;
|
||||
use termion::event::{Key as TermionKey, };
|
||||
use termion::event::Key as TermionKey;
|
||||
use termion::input::TermRead;
|
||||
use termion::raw::IntoRawMode;
|
||||
use termion::screen::AlternateScreen;
|
||||
use termion::{clear, cursor, style};
|
||||
|
||||
#[macro_use]
|
||||
extern crate nom;
|
||||
|
@ -79,7 +78,9 @@ pub enum ThreadEvent {
|
|||
/// User input.
|
||||
Input(Key),
|
||||
/// A watched folder has been refreshed.
|
||||
RefreshMailbox{ name: String },
|
||||
RefreshMailbox {
|
||||
name: String,
|
||||
},
|
||||
UIEventType(UIEventType),
|
||||
//Decode { _ }, // For gpg2 signature check
|
||||
}
|
||||
|
@ -96,12 +97,11 @@ pub enum ForkType {
|
|||
NewDraft(File, std::process::Child),
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum UIEventType {
|
||||
Input(Key),
|
||||
ExInput(Key),
|
||||
RefreshMailbox((usize,usize)),
|
||||
RefreshMailbox((usize, usize)),
|
||||
//Quit?
|
||||
Resize,
|
||||
/// Force redraw.
|
||||
|
@ -115,7 +115,6 @@ pub enum UIEventType {
|
|||
StatusNotification(String),
|
||||
}
|
||||
|
||||
|
||||
/// An event passed from `State` to its Entities.
|
||||
#[derive(Debug)]
|
||||
pub struct UIEvent {
|
||||
|
@ -132,11 +131,15 @@ pub enum UIMode {
|
|||
|
||||
impl fmt::Display for UIMode {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", match *self {
|
||||
UIMode::Normal => { "NORMAL" },
|
||||
UIMode::Execute => { "EX" },
|
||||
UIMode::Fork => { "FORK" },
|
||||
})
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
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
|
||||
/// managing the terminal and interfacing with `melib`
|
||||
pub struct State<W: Write> {
|
||||
|
@ -187,13 +189,19 @@ pub struct State<W: Write> {
|
|||
sender: Sender<ThreadEvent>,
|
||||
entities: Vec<Entity>,
|
||||
pub context: Context,
|
||||
|
||||
}
|
||||
|
||||
impl<W: Write> Drop for State<W> {
|
||||
fn drop(&mut self) {
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
@ -207,12 +215,16 @@ impl State<std::io::Stdout> {
|
|||
let stdout = AlternateScreen::from(_stdout.into_raw_mode().unwrap());
|
||||
|
||||
let termsize = termion::terminal_size().ok();
|
||||
let termcols = termsize.map(|(w,_)| w);
|
||||
let termrows = termsize.map(|(_,h)| h);
|
||||
let termcols = termsize.map(|(w, _)| w);
|
||||
let termrows = termsize.map(|(_, h)| h);
|
||||
let cols = termcols.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();
|
||||
accounts.sort_by(|a,b| a.name().cmp(&b.name()) );
|
||||
let mut accounts: Vec<Account> = settings
|
||||
.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 {
|
||||
cols: cols,
|
||||
rows: rows,
|
||||
|
@ -223,7 +235,6 @@ impl State<std::io::Stdout> {
|
|||
sender: sender,
|
||||
entities: Vec::with_capacity(1),
|
||||
|
||||
|
||||
context: Context {
|
||||
accounts: accounts,
|
||||
_backends: backends,
|
||||
|
@ -235,19 +246,29 @@ impl State<std::io::Stdout> {
|
|||
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();
|
||||
for account in &mut s.context.accounts {
|
||||
let sender = s.sender.clone();
|
||||
account.watch(RefreshEventConsumer::new(Box::new(move |r| {
|
||||
sender.send(ThreadEvent::from(r));
|
||||
})));
|
||||
|
||||
}
|
||||
s
|
||||
}
|
||||
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.stdout = None;
|
||||
self.context.input_thread.send(false);
|
||||
|
@ -257,24 +278,36 @@ impl State<std::io::Stdout> {
|
|||
s.lock();
|
||||
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();
|
||||
}
|
||||
}
|
||||
impl<W: Write> State<W> {
|
||||
pub fn update_size(&mut self) {
|
||||
let termsize = termion::terminal_size().ok();
|
||||
let termcols = termsize.map(|(w,_)| w);
|
||||
let termrows = termsize.map(|(_,h)| h);
|
||||
if termcols.unwrap_or(72) as usize != self.cols || termrows.unwrap_or(120) as usize != self.rows {
|
||||
eprintln!("Size updated, from ({}, {}) -> ({:?}, {:?})", self.cols, self.rows, termcols, termrows);
|
||||
|
||||
let termcols = termsize.map(|(w, _)| w);
|
||||
let termrows = termsize.map(|(_, h)| h);
|
||||
if termcols.unwrap_or(72) as usize != self.cols
|
||||
|| 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.rows = termrows.unwrap_or(120) as usize;
|
||||
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) {
|
||||
|
@ -292,9 +325,13 @@ impl<W: Write> State<W> {
|
|||
let bottom_right = bottom_right!(area);
|
||||
|
||||
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) {
|
||||
let c = self.grid[(x,y)];
|
||||
let c = self.grid[(x, y)];
|
||||
|
||||
if c.bg() != cells::Color::Default {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
write!(self.stdout(), "{}", termion::color::Fg(termion::color::Reset)).unwrap();
|
||||
write!(
|
||||
self.stdout(),
|
||||
"{}",
|
||||
termion::color::Fg(termion::color::Reset)
|
||||
).unwrap();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
self.flush();
|
||||
|
@ -324,17 +368,19 @@ impl<W: Write> State<W> {
|
|||
let cols = self.cols;
|
||||
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) {
|
||||
let entity = &mut self.entities[idx];
|
||||
let upper_left = (0,0);
|
||||
let bottom_right = (self.cols-1, self.rows-1);
|
||||
let upper_left = (0, 0);
|
||||
let bottom_right = (self.cols - 1, self.rows - 1);
|
||||
|
||||
if entity.component.is_dirty() {
|
||||
entity.component.draw(&mut self.grid,
|
||||
(upper_left, bottom_right),
|
||||
&mut self.context);
|
||||
entity.component.draw(
|
||||
&mut self.grid,
|
||||
(upper_left, bottom_right),
|
||||
&mut self.context,
|
||||
);
|
||||
}
|
||||
}
|
||||
pub fn register_entity(&mut self, entity: Entity) {
|
||||
|
@ -357,22 +403,22 @@ impl<W: Write> State<W> {
|
|||
UIEventType::Command(cmd) => {
|
||||
self.parse_command(cmd);
|
||||
return;
|
||||
},
|
||||
}
|
||||
UIEventType::Fork(child) => {
|
||||
self.mode = UIMode::Fork;
|
||||
self.child = Some(child);
|
||||
self.flush();
|
||||
return;
|
||||
},
|
||||
}
|
||||
UIEventType::EditDraft(mut file) => {
|
||||
use std::process::{Command, Stdio};
|
||||
use std::io::Read;
|
||||
use std::process::{Command, Stdio};
|
||||
let mut output = Command::new("msmtp")
|
||||
.arg("-t")
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("failed to execute process") ;
|
||||
.expect("failed to execute process");
|
||||
{
|
||||
let mut in_pipe = output.stdin.as_mut().unwrap();
|
||||
let mut buf = Vec::new();
|
||||
|
@ -385,13 +431,20 @@ impl<W: Write> State<W> {
|
|||
output.wait_with_output().expect("Failed to read stdout");
|
||||
|
||||
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 */
|
||||
for i in 0..self.entities.len() {
|
||||
|
@ -402,45 +455,56 @@ impl<W: Write> State<W> {
|
|||
/// Tries to load a mailbox's content
|
||||
pub fn refresh_mailbox(&mut self, account_idx: usize, folder_idx: usize) {
|
||||
let flag = match &mut self.context.accounts[account_idx][folder_idx] {
|
||||
Some(Ok(_)) => {
|
||||
true
|
||||
},
|
||||
Some(Err(e)) => { eprintln!("error {:?}", e); false },
|
||||
None => { eprintln!("None"); false },
|
||||
Some(Ok(_)) => true,
|
||||
Some(Err(e)) => {
|
||||
eprintln!("error {:?}", e);
|
||||
false
|
||||
}
|
||||
None => {
|
||||
eprintln!("None");
|
||||
false
|
||||
}
|
||||
};
|
||||
if flag {
|
||||
|
||||
self.rcv_event(UIEvent { id: 0, event_type: UIEventType::RefreshMailbox((account_idx, folder_idx)) });
|
||||
self.rcv_event(UIEvent {
|
||||
id: 0,
|
||||
event_type: UIEventType::RefreshMailbox((account_idx, folder_idx)),
|
||||
});
|
||||
}
|
||||
}
|
||||
pub fn try_wait_on_child(&mut self) -> Option<bool> {
|
||||
if {
|
||||
match self.child {
|
||||
Some(ForkType::NewDraft(_,ref mut c)) => {
|
||||
Some(ForkType::NewDraft(_, ref mut c)) => {
|
||||
let mut w = c.try_wait();
|
||||
match w {
|
||||
Ok(Some(_)) => { true },
|
||||
Ok(None) => { false },
|
||||
Err(_) => { return None; },
|
||||
Ok(Some(_)) => true,
|
||||
Ok(None) => false,
|
||||
Err(_) => {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
Some(ForkType::Generic(ref mut c)) => {
|
||||
let mut w = c.try_wait();
|
||||
match w {
|
||||
Ok(Some(_)) => { true },
|
||||
Ok(None) => { false },
|
||||
Err(_) => { return None; },
|
||||
Ok(Some(_)) => true,
|
||||
Ok(None) => false,
|
||||
Err(_) => {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
_ => {
|
||||
return 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);
|
||||
}
|
||||
|
@ -448,15 +512,12 @@ impl<W: Write> State<W> {
|
|||
}
|
||||
fn flush(&mut self) {
|
||||
self.stdout.as_mut().map(|s| s.flush().unwrap());
|
||||
|
||||
}
|
||||
fn stdout(&mut self) -> &mut termion::screen::AlternateScreen<termion::raw::RawTerminal<W>> {
|
||||
self.stdout.as_mut().unwrap()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Key {
|
||||
/// Backspace.
|
||||
|
@ -499,9 +560,8 @@ pub enum Key {
|
|||
Esc,
|
||||
}
|
||||
|
||||
|
||||
impl From<TermionKey> for Key {
|
||||
fn from(k: TermionKey ) -> Self {
|
||||
fn from(k: TermionKey) -> Self {
|
||||
match k {
|
||||
TermionKey::Backspace => Key::Backspace,
|
||||
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
|
||||
* 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.
|
||||
*/
|
||||
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() {
|
||||
chan_select! {
|
||||
default => {},
|
||||
|
|
|
@ -28,22 +28,28 @@ pub fn set_y(p: Pos, new_y: usize) -> Pos {
|
|||
pub type Area = (Pos, Pos);
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! upper_left { ($a:expr) => ( $a.0 ) }
|
||||
macro_rules! upper_left {
|
||||
($a:expr) => {
|
||||
$a.0
|
||||
};
|
||||
}
|
||||
#[macro_export]
|
||||
macro_rules! bottom_right { ($a:expr) => ( $a.1 ) }
|
||||
macro_rules! bottom_right {
|
||||
($a:expr) => {
|
||||
$a.1
|
||||
};
|
||||
}
|
||||
#[macro_export]
|
||||
macro_rules! is_valid_area { ($a:expr) =>
|
||||
{
|
||||
{
|
||||
let upper_left = upper_left!($a);
|
||||
let bottom_right = bottom_right!($a);
|
||||
if get_y(upper_left) > get_y(bottom_right) || get_x(upper_left) > get_x(bottom_right) {
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
macro_rules! is_valid_area {
|
||||
($a:expr) => {{
|
||||
let upper_left = upper_left!($a);
|
||||
let bottom_right = bottom_right!($a);
|
||||
if get_y(upper_left) > get_y(bottom_right) || get_x(upper_left) > get_x(bottom_right) {
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
/// A `(cols, rows)` size.
|
||||
|
|
Loading…
Reference in New Issue