parent
7a6fc1ce94
commit
b98a04f35b
|
@ -9,35 +9,35 @@ use self::test::Bencher;
|
|||
#[bench]
|
||||
fn bench_threads_1(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
let folder = Folder::new(String::from(""), vec![]);
|
||||
let folder = Folder::new(String::from(""), String::from(""), vec![]);
|
||||
MaildirType::new("").multicore(1, &folder)
|
||||
});
|
||||
}
|
||||
#[bench]
|
||||
fn bench_threads_2(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
let folder = Folder::new(String::from(""), vec![]);
|
||||
let folder = Folder::new(String::from(""), String::from(""), vec![]);
|
||||
MaildirType::new("").multicore(2, &folder)
|
||||
});
|
||||
}
|
||||
#[bench]
|
||||
fn bench_threads_3(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
let folder = Folder::new(String::from(""), vec![]);
|
||||
let folder = Folder::new(String::from(""), String::from(""), vec![]);
|
||||
MaildirType::new("").multicore(3, &folder)
|
||||
});
|
||||
}
|
||||
#[bench]
|
||||
fn bench_threads_4(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
let folder = Folder::new(String::from(""), vec![]);
|
||||
let folder = Folder::new(String::from(""), String::from(""), vec![]);
|
||||
MaildirType::new("").multicore(4, &folder)
|
||||
});
|
||||
}
|
||||
#[bench]
|
||||
fn bench_threads_6(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
let folder = Folder::new(String::from(""), vec![]);
|
||||
let folder = Folder::new(String::from(""), String::from(""), vec![]);
|
||||
MaildirType::new("").multicore(6, &folder)
|
||||
});
|
||||
}
|
||||
|
|
|
@ -26,53 +26,22 @@ pub mod pager;
|
|||
|
||||
use pager::PagerSettings;
|
||||
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::hash::Hasher;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Folder {
|
||||
hash: u64,
|
||||
name: String,
|
||||
path: String,
|
||||
children: Vec<usize>,
|
||||
}
|
||||
|
||||
impl Folder {
|
||||
pub fn new(path: String, file_name: String, children: Vec<usize>) -> Self {
|
||||
let mut h = DefaultHasher::new();
|
||||
h.write(&path.as_bytes());
|
||||
Folder {
|
||||
hash: h.finish(),
|
||||
name: file_name,
|
||||
path: path,
|
||||
children: children,
|
||||
}
|
||||
}
|
||||
pub fn hash(&self) -> u64 {
|
||||
self.hash
|
||||
}
|
||||
pub fn path(&self) -> &str {
|
||||
&self.path
|
||||
}
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
pub fn children(&self) -> &Vec<usize> {
|
||||
&self.children
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct FileAccount {
|
||||
folders: String,
|
||||
pub struct FileAccount {
|
||||
root_folder: String,
|
||||
format: String,
|
||||
sent_folder: String,
|
||||
threaded: bool,
|
||||
}
|
||||
|
||||
impl FileAccount {
|
||||
pub fn folder(&self) -> &str {
|
||||
&self.root_folder
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct FileSettings {
|
||||
accounts: HashMap<String, FileAccount>,
|
||||
|
@ -82,7 +51,7 @@ struct FileSettings {
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct AccountSettings {
|
||||
name: String,
|
||||
pub folders: Vec<Folder>,
|
||||
root_folder: String,
|
||||
format: String,
|
||||
pub sent_folder: String,
|
||||
pub threaded: bool,
|
||||
|
@ -95,6 +64,9 @@ impl AccountSettings {
|
|||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
pub fn root_folder(&self) -> &str {
|
||||
&self.root_folder
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
|
@ -126,53 +98,20 @@ impl Settings {
|
|||
let mut s: HashMap<String, AccountSettings> = HashMap::new();
|
||||
|
||||
for (id, x) in fs.accounts {
|
||||
let mut folders = Vec::new();
|
||||
fn recurse_folders<P: AsRef<Path>>(folders: &mut Vec<Folder>, p: P) -> Vec<usize> {
|
||||
let mut children = Vec::new();
|
||||
for mut f in fs::read_dir(p).unwrap() {
|
||||
for f in f.iter_mut() {
|
||||
{
|
||||
let path = f.path();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
children
|
||||
};
|
||||
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.sort_by(|a, b| b.name.cmp(&a.name));
|
||||
s.insert(
|
||||
id.clone(),
|
||||
AccountSettings {
|
||||
let format = x.format.to_lowercase();
|
||||
let sent_folder = x.sent_folder;
|
||||
let threaded = x.threaded;
|
||||
let root_folder = x.root_folder;
|
||||
|
||||
let acc = AccountSettings {
|
||||
name: id.clone(),
|
||||
folders: folders,
|
||||
format: x.format.to_lowercase(),
|
||||
sent_folder: x.sent_folder.clone(),
|
||||
threaded: x.threaded,
|
||||
},
|
||||
);
|
||||
root_folder,
|
||||
format,
|
||||
sent_folder,
|
||||
threaded,
|
||||
};
|
||||
|
||||
s.insert(id, acc);
|
||||
}
|
||||
|
||||
Settings {
|
||||
|
|
|
@ -23,11 +23,11 @@
|
|||
* An error object for `melib`
|
||||
*/
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::result;
|
||||
use std::borrow::Cow;
|
||||
|
||||
use nom;
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
*/
|
||||
|
||||
use async::*;
|
||||
use conf::{AccountSettings, Folder};
|
||||
use conf::AccountSettings;
|
||||
use mailbox::backends::{Backends, RefreshEventConsumer};
|
||||
use mailbox::*;
|
||||
use std::ops::{Index, IndexMut};
|
||||
|
@ -47,15 +47,15 @@ pub struct Account {
|
|||
}
|
||||
|
||||
impl Account {
|
||||
pub fn new(name: String, settings: AccountSettings, backends: &Backends) -> Self {
|
||||
let sent_folder = settings
|
||||
.folders
|
||||
pub fn new(name: String, settings: AccountSettings, map: &Backends) -> Self {
|
||||
let backend = map.get(settings.format())(&settings);
|
||||
let ref_folders: Vec<Folder> = backend.folders();
|
||||
let mut folders: Vec<Option<Result<Mailbox>>> = Vec::with_capacity(ref_folders.len());
|
||||
let mut workers: Vec<Worker> = Vec::new();
|
||||
let sent_folder = ref_folders
|
||||
.iter()
|
||||
.position(|x| *x.path() == settings.sent_folder);
|
||||
let mut folders = Vec::with_capacity(settings.folders.len());
|
||||
let mut workers = Vec::new();
|
||||
let backend = backends.get(settings.format());
|
||||
for f in &settings.folders {
|
||||
.position(|x: &Folder| x.name() == settings.sent_folder);
|
||||
for f in ref_folders {
|
||||
folders.push(None);
|
||||
let mut handle = backend.get(&f);
|
||||
workers.push(Some(handle));
|
||||
|
@ -73,14 +73,14 @@ impl Account {
|
|||
}
|
||||
}
|
||||
pub fn watch(&self, r: RefreshEventConsumer) -> () {
|
||||
self.backend.watch(r, &self.settings.folders[..]);
|
||||
self.backend.watch(r).unwrap();
|
||||
}
|
||||
/* This doesn't represent the number of correctly parsed mailboxes though */
|
||||
pub fn len(&self) -> usize {
|
||||
self.folders.len()
|
||||
}
|
||||
pub fn list_folders(&self) -> Vec<Folder> {
|
||||
self.settings.folders.clone()
|
||||
self.backend.folders()
|
||||
}
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
|
@ -89,7 +89,8 @@ impl Account {
|
|||
&mut self.workers
|
||||
}
|
||||
fn load_mailbox(&mut self, index: usize, envelopes: Result<Vec<Envelope>>) -> () {
|
||||
let folder = &self.settings.folders[index];
|
||||
let folders = self.backend.folders();
|
||||
let folder = &folders[index];
|
||||
if self.sent_folder.is_some() {
|
||||
let id = self.sent_folder.unwrap();
|
||||
if id == index {
|
||||
|
@ -105,7 +106,7 @@ impl Account {
|
|||
)
|
||||
}
|
||||
};
|
||||
let sent_path = &self.settings.folders[id];
|
||||
let sent_path = &folders[id];
|
||||
if sent[0].is_none() {
|
||||
sent[0] = Some(Mailbox::new(sent_path, &None, envelopes.clone()));
|
||||
}
|
||||
|
|
|
@ -19,10 +19,10 @@
|
|||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
use async::*;
|
||||
use conf::Folder;
|
||||
use error::Result;
|
||||
use mailbox::backends::{MailBackend, RefreshEventConsumer};
|
||||
use mailbox::backends::{MailBackend, RefreshEventConsumer, Folder};
|
||||
use mailbox::email::Envelope;
|
||||
|
||||
/// `BackendOp` implementor for Imap
|
||||
|
@ -35,7 +35,6 @@ impl ImapOp {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
impl BackendOp for ImapOp {
|
||||
fn description(&self) -> String {
|
||||
|
@ -53,7 +52,7 @@ impl BackendOp for ImapOp {
|
|||
fn fetch_flags(&self) -> Flag {
|
||||
unimplemented!();
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
/// Imap backend
|
||||
#[derive(Debug)]
|
||||
|
@ -72,4 +71,4 @@ impl ImapType {
|
|||
pub fn new(_path: &str) -> Self {
|
||||
ImapType {}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
|
|
@ -20,10 +20,11 @@
|
|||
*/
|
||||
|
||||
use async::*;
|
||||
use conf::Folder;
|
||||
use conf::AccountSettings;
|
||||
use error::{MeliError, Result};
|
||||
use mailbox::backends::{
|
||||
BackendOp, BackendOpGenerator, MailBackend, RefreshEvent, RefreshEventConsumer,
|
||||
BackendFolder, BackendOp, BackendOpGenerator, Folder, MailBackend, RefreshEvent,
|
||||
RefreshEventConsumer,
|
||||
};
|
||||
use mailbox::email::parser;
|
||||
use mailbox::email::{Envelope, Flag};
|
||||
|
@ -43,7 +44,7 @@ use memmap::{Mmap, Protection};
|
|||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::fs;
|
||||
use std::hash::Hasher;
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// `BackendOp` implementor for Maildir
|
||||
#[derive(Debug, Default)]
|
||||
|
@ -152,38 +153,42 @@ impl BackendOp for MaildirOp {
|
|||
/// Maildir backend https://cr.yp.to/proto/maildir.html
|
||||
#[derive(Debug)]
|
||||
pub struct MaildirType {
|
||||
folders: Vec<MaildirFolder>,
|
||||
path: String,
|
||||
idx: (usize, usize),
|
||||
}
|
||||
|
||||
impl MailBackend for MaildirType {
|
||||
fn folders(&self) -> Vec<Folder> {
|
||||
self.folders.iter().map(|f| f.clone()).collect()
|
||||
}
|
||||
fn get(&self, folder: &Folder) -> Async<Result<Vec<Envelope>>> {
|
||||
self.multicore(4, folder)
|
||||
}
|
||||
fn watch(&self, sender: RefreshEventConsumer, folders: &[Folder]) -> () {
|
||||
let folders = folders.to_vec();
|
||||
|
||||
thread::Builder::new()
|
||||
.name("folder watch".to_string())
|
||||
.spawn(move || {
|
||||
fn watch(&self, sender: RefreshEventConsumer) -> Result<()> {
|
||||
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() {
|
||||
for f in &self.folders {
|
||||
if f.is_valid().is_err() {
|
||||
continue;
|
||||
}
|
||||
eprintln!("watching {}", f.path());
|
||||
let mut p = PathBuf::from(&f.path());
|
||||
eprintln!("watching {:?}", f);
|
||||
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();
|
||||
}
|
||||
thread::Builder::new()
|
||||
.name("folder watch".to_string())
|
||||
.spawn(move || {
|
||||
// Move `watcher` in the closure's scope so that it doesn't get dropped.
|
||||
let _watcher = watcher;
|
||||
loop {
|
||||
match rx.recv() {
|
||||
Ok(event) => match event {
|
||||
DebouncedEvent::Create(mut pathbuf) | DebouncedEvent::Remove(mut pathbuf) => {
|
||||
DebouncedEvent::Create(mut pathbuf)
|
||||
| DebouncedEvent::Remove(mut pathbuf) => {
|
||||
let path = if pathbuf.is_dir() {
|
||||
if pathbuf.ends_with("cur") | pathbuf.ends_with("new") {
|
||||
pathbuf.pop();
|
||||
|
@ -207,45 +212,76 @@ impl MailBackend for MaildirType {
|
|||
Err(e) => eprintln!("watch error: {:?}", e),
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl MaildirType {
|
||||
pub fn new(path: &str, idx: (usize, usize)) -> Self {
|
||||
MaildirType {
|
||||
path: path.to_string(),
|
||||
idx: idx,
|
||||
}
|
||||
}
|
||||
fn is_valid(f: &Folder) -> Result<()> {
|
||||
pub fn new(f: &AccountSettings) -> Self {
|
||||
let mut folders: Vec<MaildirFolder> = Vec::new();
|
||||
fn recurse_folders<P: AsRef<Path>>(folders: &mut Vec<MaildirFolder>, p: P) -> Vec<usize> {
|
||||
let mut children = Vec::new();
|
||||
for mut f in fs::read_dir(p).unwrap() {
|
||||
for f in f.iter_mut() {
|
||||
{
|
||||
let path = f.path();
|
||||
let mut p = PathBuf::from(path);
|
||||
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
|
||||
)));
|
||||
if path.ends_with("cur") || path.ends_with("new") || path.ends_with("tmp") {
|
||||
continue;
|
||||
}
|
||||
p.pop();
|
||||
if path.is_dir() {
|
||||
let path_children = recurse_folders(folders, &path);
|
||||
if let Ok(f) = MaildirFolder::new(
|
||||
path.to_str().unwrap().to_string(),
|
||||
path.file_name().unwrap().to_str().unwrap().to_string(),
|
||||
path_children,
|
||||
) {
|
||||
folders.push(f);
|
||||
children.push(folders.len() - 1);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
children
|
||||
};
|
||||
let path = PathBuf::from(f.root_folder());
|
||||
let path_children = recurse_folders(&mut folders, &path);
|
||||
if path.is_dir() {
|
||||
if let Ok(f) = MaildirFolder::new(
|
||||
path.to_str().unwrap().to_string(),
|
||||
path.file_name().unwrap().to_str().unwrap().to_string(),
|
||||
path_children,
|
||||
) {
|
||||
folders.push(f);
|
||||
}
|
||||
}
|
||||
MaildirType {
|
||||
folders,
|
||||
path: f.root_folder().to_string(),
|
||||
}
|
||||
}
|
||||
fn owned_folder_idx(&self, folder: &Folder) -> usize {
|
||||
for (idx, f) in self.folders.iter().enumerate() {
|
||||
if f.hash() == folder.hash() {
|
||||
return idx;
|
||||
}
|
||||
}
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
pub fn multicore(&self, cores: usize, folder: &Folder) -> Async<Result<Vec<Envelope>>> {
|
||||
let mut w = AsyncBuilder::new();
|
||||
let handle = {
|
||||
let tx = w.tx();
|
||||
// TODO: Avoid clone
|
||||
let folder = folder.clone();
|
||||
let folder: &MaildirFolder = &self.folders[self.owned_folder_idx(folder)];
|
||||
let path = folder.path().to_string();
|
||||
let name = format!("parsing {:?}", folder.name());
|
||||
|
||||
thread::Builder::new()
|
||||
.name(format!("parsing {:?}", folder))
|
||||
.name(name)
|
||||
.spawn(move || {
|
||||
MaildirType::is_valid(&folder)?;
|
||||
let path = folder.path();
|
||||
let mut path = PathBuf::from(path);
|
||||
path.push("cur");
|
||||
let iter = path.read_dir()?;
|
||||
|
@ -310,3 +346,64 @@ impl MaildirType {
|
|||
w.build(handle)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MaildirFolder {
|
||||
hash: u64,
|
||||
name: String,
|
||||
path: String,
|
||||
children: Vec<usize>,
|
||||
}
|
||||
|
||||
impl MaildirFolder {
|
||||
pub fn new(path: String, file_name: String, children: Vec<usize>) -> Result<Self> {
|
||||
let mut h = DefaultHasher::new();
|
||||
h.write(&path.as_bytes());
|
||||
|
||||
let ret = MaildirFolder {
|
||||
hash: h.finish(),
|
||||
name: file_name,
|
||||
path: path,
|
||||
children: children,
|
||||
};
|
||||
ret.is_valid()?;
|
||||
Ok(ret)
|
||||
}
|
||||
pub fn path(&self) -> &str {
|
||||
&self.path
|
||||
}
|
||||
fn is_valid(&self) -> Result<()> {
|
||||
let path = self.path();
|
||||
let mut p = PathBuf::from(path);
|
||||
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
|
||||
)));
|
||||
}
|
||||
p.pop();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl BackendFolder for MaildirFolder {
|
||||
fn hash(&self) -> u64 {
|
||||
self.hash
|
||||
}
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
fn children(&self) -> &Vec<usize> {
|
||||
&self.children
|
||||
}
|
||||
fn clone(&self) -> Folder {
|
||||
Box::new(MaildirFolder {
|
||||
hash: self.hash,
|
||||
name: self.name.clone(),
|
||||
path: self.path.clone(),
|
||||
children: self.children.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,10 +23,10 @@
|
|||
* https://wiki2.dovecot.org/MailboxFormat/mbox
|
||||
*/
|
||||
|
||||
/*
|
||||
use async::*;
|
||||
use conf::Folder;
|
||||
use error::Result;
|
||||
use mailbox::backends::{MailBackend, RefreshEventConsumer};
|
||||
use mailbox::backends::{MailBackend, RefreshEventConsumer, Folder};
|
||||
use mailbox::email::Envelope;
|
||||
|
||||
/// `BackendOp` implementor for Mbox
|
||||
|
@ -39,7 +39,6 @@ impl MboxOp {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
impl BackendOp for MboxOp {
|
||||
fn description(&self) -> String {
|
||||
unimplemented!();
|
||||
|
@ -60,7 +59,6 @@ impl BackendOp for MboxOp {
|
|||
unimplemented!()
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/// Mbox backend
|
||||
#[derive(Debug)]
|
||||
|
@ -80,3 +78,4 @@ impl MboxType {
|
|||
MboxType {}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
|
@ -23,22 +23,25 @@ pub mod maildir;
|
|||
pub mod mbox;
|
||||
|
||||
use async::*;
|
||||
use conf::Folder;
|
||||
use conf::AccountSettings;
|
||||
use error::Result;
|
||||
use mailbox::backends::imap::ImapType;
|
||||
//use mailbox::backends::imap::ImapType;
|
||||
//use mailbox::backends::mbox::MboxType;
|
||||
use mailbox::backends::maildir::MaildirType;
|
||||
use mailbox::backends::mbox::MboxType;
|
||||
use mailbox::email::{Envelope, Flag};
|
||||
use std::fmt;
|
||||
use std::fmt::Debug;
|
||||
|
||||
extern crate fnv;
|
||||
use self::fnv::FnvHashMap;
|
||||
use std;
|
||||
|
||||
pub type BackendCreator = Box<Fn(&AccountSettings) -> Box<MailBackend>>;
|
||||
|
||||
/// A hashmap containing all available mail backends.
|
||||
/// An abstraction over any available backends.
|
||||
pub struct Backends {
|
||||
map: FnvHashMap<std::string::String, Box<Fn() -> Box<MailBackend>>>,
|
||||
map: FnvHashMap<std::string::String, Box<Fn() -> BackendCreator>>,
|
||||
}
|
||||
|
||||
impl Backends {
|
||||
|
@ -48,20 +51,20 @@ impl Backends {
|
|||
};
|
||||
b.register(
|
||||
"maildir".to_string(),
|
||||
Box::new(|| Box::new(MaildirType::new("", (0, 0)))),
|
||||
Box::new(|| Box::new(|f| Box::new(MaildirType::new(f)))),
|
||||
);
|
||||
b.register("mbox".to_string(), Box::new(|| Box::new(MboxType::new(""))));
|
||||
b.register("imap".to_string(), Box::new(|| Box::new(ImapType::new(""))));
|
||||
//b.register("mbox".to_string(), Box::new(|| Box::new(MboxType::new(""))));
|
||||
//b.register("imap".to_string(), Box::new(|| Box::new(ImapType::new(""))));
|
||||
b
|
||||
}
|
||||
|
||||
pub fn get(&self, key: &str) -> Box<MailBackend> {
|
||||
pub fn get(&self, key: &str) -> BackendCreator {
|
||||
if !self.map.contains_key(key) {
|
||||
panic!("{} is not a valid mail backend", key);
|
||||
}
|
||||
self.map[key]()
|
||||
}
|
||||
pub fn register(&mut self, key: String, backend: Box<Fn() -> Box<MailBackend>>) -> () {
|
||||
pub fn register(&mut self, key: String, backend: Box<Fn() -> BackendCreator>) -> () {
|
||||
if self.map.contains_key(&key) {
|
||||
panic!("{} is an already registered backend", key);
|
||||
}
|
||||
|
@ -90,8 +93,8 @@ impl RefreshEventConsumer {
|
|||
}
|
||||
pub trait MailBackend: ::std::fmt::Debug {
|
||||
fn get(&self, folder: &Folder) -> Async<Result<Vec<Envelope>>>;
|
||||
fn watch(&self, sender: RefreshEventConsumer, folders: &[Folder]) -> ();
|
||||
//fn new(folders: &Vec<String>) -> Box<Self>;
|
||||
fn watch(&self, sender: RefreshEventConsumer) -> Result<()>;
|
||||
fn folders(&self) -> Vec<Folder>;
|
||||
//login function
|
||||
}
|
||||
|
||||
|
@ -168,3 +171,37 @@ impl fmt::Debug for BackendOpGenerator {
|
|||
write!(f, "BackendOpGenerator: {}", op.description())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait BackendFolder: Debug {
|
||||
fn hash(&self) -> u64;
|
||||
fn name(&self) -> &str;
|
||||
fn clone(&self) -> Folder;
|
||||
fn children(&self) -> &Vec<usize>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DummyFolder {
|
||||
v: Vec<usize>,
|
||||
}
|
||||
|
||||
impl BackendFolder for DummyFolder {
|
||||
fn hash(&self) -> u64 {
|
||||
0
|
||||
}
|
||||
fn name(&self) -> &str {
|
||||
""
|
||||
}
|
||||
fn clone(&self) -> Folder {
|
||||
folder_default()
|
||||
}
|
||||
fn children(&self) -> &Vec<usize> {
|
||||
&self.v
|
||||
}
|
||||
}
|
||||
pub fn folder_default() -> Folder {
|
||||
Box::new(DummyFolder {
|
||||
v: Vec::with_capacity(0),
|
||||
})
|
||||
}
|
||||
|
||||
pub type Folder = Box<BackendFolder>;
|
||||
|
|
|
@ -69,9 +69,10 @@ pub enum ContentType {
|
|||
|
||||
impl Default for ContentType {
|
||||
fn default() -> Self {
|
||||
ContentType::Text{ charset: Charset::UTF8 }
|
||||
ContentType::Text {
|
||||
charset: Charset::UTF8,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Display for ContentType {
|
||||
|
@ -128,4 +129,3 @@ pub enum ContentTransferEncoding {
|
|||
QuotedPrintable,
|
||||
Other { tag: Vec<u8> },
|
||||
}
|
||||
|
||||
|
|
|
@ -18,10 +18,10 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
use std::fmt;
|
||||
use std::str;
|
||||
use data_encoding::BASE64_MIME;
|
||||
use mailbox::email::parser;
|
||||
use std::fmt;
|
||||
use std::str;
|
||||
|
||||
pub use mailbox::email::attachment_types::*;
|
||||
|
||||
|
@ -44,7 +44,14 @@ impl fmt::Debug for AttachmentType {
|
|||
match self {
|
||||
AttachmentType::Data { .. } => write!(f, "AttachmentType::Data {{ .. }}"),
|
||||
AttachmentType::Text { .. } => write!(f, "AttachmentType::Text {{ .. }}"),
|
||||
AttachmentType::Multipart { of_type, subattachments } => write!(f, "AttachmentType::Multipart {{ of_type: {:?},\nsubattachments: {:?} }}", of_type, subattachments),
|
||||
AttachmentType::Multipart {
|
||||
of_type,
|
||||
subattachments,
|
||||
} => write!(
|
||||
f,
|
||||
"AttachmentType::Multipart {{ of_type: {:?},\nsubattachments: {:?} }}",
|
||||
of_type, subattachments
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -124,7 +131,9 @@ impl AttachmentBuilder {
|
|||
self.content_type.0 = Default::default();
|
||||
for (n, v) in params {
|
||||
if n.eq_ignore_ascii_case(b"charset") {
|
||||
self.content_type.0 = ContentType::Text { charset: Charset::from(v) };
|
||||
self.content_type.0 = ContentType::Text {
|
||||
charset: Charset::from(v),
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -186,7 +195,11 @@ impl AttachmentBuilder {
|
|||
};
|
||||
|
||||
let decoded_result = parser::decode_charset(&bytes, charset);
|
||||
decoded_result.as_ref().map(|v| v.as_bytes()).unwrap_or_else(|_| &self.raw).to_vec()
|
||||
decoded_result
|
||||
.as_ref()
|
||||
.map(|v| v.as_bytes())
|
||||
.unwrap_or_else(|_| &self.raw)
|
||||
.to_vec()
|
||||
}
|
||||
pub fn build(self) -> Attachment {
|
||||
let attachment_type = match self.content_type.0 {
|
||||
|
@ -197,7 +210,9 @@ impl AttachmentBuilder {
|
|||
let multipart_type = match self.content_type.1 {
|
||||
ContentSubType::Other { ref tag } => match &tag[..] {
|
||||
b"mixed" | b"Mixed" | b"MIXED" => MultipartType::Mixed,
|
||||
b"alternative" | b"Alternative" | b"ALTERNATIVE" => MultipartType::Alternative,
|
||||
b"alternative" | b"Alternative" | b"ALTERNATIVE" => {
|
||||
MultipartType::Alternative
|
||||
}
|
||||
b"digest" | b"Digest" | b"DIGEST" => MultipartType::Digest,
|
||||
_ => MultipartType::Unsupported { tag: tag.clone() },
|
||||
},
|
||||
|
@ -261,14 +276,15 @@ impl AttachmentBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
impl fmt::Display for Attachment {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self.attachment_type {
|
||||
AttachmentType::Data { .. } => {
|
||||
write!(f, "Data attachment of type {}", self.mime_type())
|
||||
}
|
||||
AttachmentType::Text { .. } => write!(f, "Text attachment of type {}", self.mime_type()),
|
||||
AttachmentType::Text { .. } => {
|
||||
write!(f, "Text attachment of type {}", self.mime_type())
|
||||
}
|
||||
AttachmentType::Multipart {
|
||||
of_type: ref multipart_type,
|
||||
subattachments: ref sub_att_vec,
|
||||
|
@ -276,10 +292,16 @@ impl fmt::Display for Attachment {
|
|||
write!(
|
||||
f,
|
||||
"{} attachment with {} subs",
|
||||
self.mime_type(), sub_att_vec.len()
|
||||
self.mime_type(),
|
||||
sub_att_vec.len()
|
||||
)
|
||||
} else {
|
||||
write!(f, "{} attachment with {} subs", self.mime_type(), sub_att_vec.len())
|
||||
write!(
|
||||
f,
|
||||
"{} attachment with {} subs",
|
||||
self.mime_type(),
|
||||
sub_att_vec.len()
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -340,7 +362,7 @@ impl Attachment {
|
|||
for a in sub_att_vec {
|
||||
count_recursive(a, ret);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -374,7 +396,7 @@ fn decode_rec_helper(a: &Attachment, filter: &Option<Box<Fn(&Attachment) -> Vec<
|
|||
return filter(a);
|
||||
}
|
||||
match a.attachment_type {
|
||||
AttachmentType::Data { .. } => { Vec::new()},
|
||||
AttachmentType::Data { .. } => Vec::new(),
|
||||
AttachmentType::Text { .. } => decode_helper(a, filter),
|
||||
AttachmentType::Multipart {
|
||||
of_type: ref multipart_type,
|
||||
|
@ -424,7 +446,11 @@ fn decode_helper(a: &Attachment, filter: &Option<Box<Fn(&Attachment) -> Vec<u8>>
|
|||
|
||||
if a.content_type().0.is_text() {
|
||||
let decoded_result = parser::decode_charset(&bytes, charset);
|
||||
decoded_result.as_ref().map(|v| v.as_bytes()).unwrap_or_else(|_| a.bytes()).to_vec()
|
||||
decoded_result
|
||||
.as_ref()
|
||||
.map(|v| v.as_bytes())
|
||||
.unwrap_or_else(|_| a.bytes())
|
||||
.to_vec()
|
||||
} else {
|
||||
bytes.to_vec()
|
||||
}
|
||||
|
|
|
@ -203,6 +203,33 @@ bitflags! {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct EnvelopeBuilder {
|
||||
from: Option<Vec<Address>>,
|
||||
to: Vec<Address>,
|
||||
body: Option<Attachment>,
|
||||
in_reply_to: Option<MessageID>,
|
||||
flags: Flag,
|
||||
}
|
||||
|
||||
impl EnvelopeBuilder {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn build(self) -> Envelope {
|
||||
unimplemented!();
|
||||
|
||||
/*
|
||||
* 1. Check for date. Default is now
|
||||
* 2.
|
||||
Envelope {
|
||||
|
||||
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
/// `Envelope` represents all the data of an email we need to know.
|
||||
///
|
||||
/// Attachments (the email's body) is parsed on demand with `body`.
|
||||
|
|
|
@ -166,7 +166,7 @@ named!(pub attachment<(std::vec::Vec<(&[u8], &[u8])>, &[u8])>,
|
|||
|
||||
/* TODO: make a map of encodings and decoding functions so that they can be reused and easily
|
||||
* extended */
|
||||
use encoding::all::{ISO_8859_1, ISO_8859_2, ISO_8859_7, WINDOWS_1253, WINDOWS_1252, GBK};
|
||||
use encoding::all::{ISO_8859_1, ISO_8859_2, ISO_8859_7, WINDOWS_1252, WINDOWS_1253, GBK};
|
||||
|
||||
fn encoded_word(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
|
||||
if input.len() < 5 {
|
||||
|
@ -266,33 +266,15 @@ fn encoded_word(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
|
|||
|
||||
pub fn decode_charset(s: &[u8], charset: Charset) -> Result<String> {
|
||||
match charset {
|
||||
Charset::UTF8 | Charset::Ascii => {
|
||||
Ok(String::from_utf8(s.to_vec()).unwrap())
|
||||
}
|
||||
Charset::ISO8859_7 => {
|
||||
Ok(ISO_8859_7.decode(s, DecoderTrap::Strict)?)
|
||||
}
|
||||
Charset::ISO8859_1 => {
|
||||
Ok(ISO_8859_1.decode(s, DecoderTrap::Strict)?)
|
||||
}
|
||||
Charset::ISO8859_2 => {
|
||||
Ok(ISO_8859_2.decode(s, DecoderTrap::Strict)?)
|
||||
}
|
||||
Charset::GBK => {
|
||||
Ok(GBK.decode(s, DecoderTrap::Strict)?)
|
||||
}
|
||||
Charset::Windows1252 => {
|
||||
Ok(WINDOWS_1252.decode(s, DecoderTrap::Strict)?)
|
||||
},
|
||||
Charset::Windows1253 => {
|
||||
Ok(WINDOWS_1253.decode(s, DecoderTrap::Strict)?)
|
||||
},
|
||||
Charset::GB2312 => {
|
||||
unimplemented!()
|
||||
},
|
||||
Charset::UTF16 => {
|
||||
unimplemented!()
|
||||
},
|
||||
Charset::UTF8 | Charset::Ascii => Ok(String::from_utf8(s.to_vec()).unwrap()),
|
||||
Charset::ISO8859_7 => Ok(ISO_8859_7.decode(s, DecoderTrap::Strict)?),
|
||||
Charset::ISO8859_1 => Ok(ISO_8859_1.decode(s, DecoderTrap::Strict)?),
|
||||
Charset::ISO8859_2 => Ok(ISO_8859_2.decode(s, DecoderTrap::Strict)?),
|
||||
Charset::GBK => Ok(GBK.decode(s, DecoderTrap::Strict)?),
|
||||
Charset::Windows1252 => Ok(WINDOWS_1252.decode(s, DecoderTrap::Strict)?),
|
||||
Charset::Windows1253 => Ok(WINDOWS_1253.decode(s, DecoderTrap::Strict)?),
|
||||
Charset::GB2312 => unimplemented!(),
|
||||
Charset::UTF16 => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -322,12 +304,10 @@ named!(
|
|||
pub quoted_printable_bytes<Vec<u8>>,
|
||||
many0!(alt_complete!(
|
||||
preceded!(quoted_printable_soft_break, quoted_printable_byte) |
|
||||
preceded!(quoted_printable_soft_break, le_u8)
|
||||
| quoted_printable_byte | le_u8
|
||||
preceded!(quoted_printable_soft_break, le_u8) | quoted_printable_byte | le_u8
|
||||
))
|
||||
);
|
||||
|
||||
|
||||
named!(
|
||||
encoded_word_list<Vec<u8>>,
|
||||
ws!(do_parse!(
|
||||
|
@ -397,7 +377,10 @@ fn display_addr(input: &[u8]) -> IResult<&[u8], Address> {
|
|||
IResult::Done(rest, raw) => {
|
||||
display_name.length = raw.find(b"<").unwrap().saturating_sub(1);
|
||||
address_spec.offset = display_name.length + 2;
|
||||
address_spec.length = raw.len().saturating_sub(display_name.length).saturating_sub(3);
|
||||
address_spec.length = raw
|
||||
.len()
|
||||
.saturating_sub(display_name.length)
|
||||
.saturating_sub(3);
|
||||
IResult::Done(
|
||||
rest,
|
||||
Address::Mailbox(MailboxAddress {
|
||||
|
|
|
@ -30,18 +30,16 @@ pub use self::email::*;
|
|||
/* Mail backends. Currently only maildir is supported */
|
||||
pub mod backends;
|
||||
use error::Result;
|
||||
use mailbox::backends::MailBackend;
|
||||
use mailbox::backends::{folder_default, Folder, MailBackend};
|
||||
pub mod accounts;
|
||||
pub use mailbox::accounts::Account;
|
||||
pub mod thread;
|
||||
pub use mailbox::thread::{build_threads, Container};
|
||||
|
||||
use conf::Folder;
|
||||
|
||||
use std::option::Option;
|
||||
|
||||
/// `Mailbox` represents a folder of mail.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug)]
|
||||
pub struct Mailbox {
|
||||
pub folder: Folder,
|
||||
pub collection: Vec<Envelope>,
|
||||
|
@ -49,10 +47,21 @@ pub struct Mailbox {
|
|||
pub threads: Vec<Container>,
|
||||
}
|
||||
|
||||
impl Clone for Mailbox {
|
||||
fn clone(&self) -> Self {
|
||||
Mailbox {
|
||||
folder: self.folder.clone(),
|
||||
collection: self.collection.clone(),
|
||||
threaded_collection: self.threaded_collection.clone(),
|
||||
threads: self.threads.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mailbox {
|
||||
pub fn new_dummy() -> Self {
|
||||
Mailbox {
|
||||
folder: Folder::default(),
|
||||
folder: folder_default(),
|
||||
collection: Vec::with_capacity(0),
|
||||
threaded_collection: Vec::with_capacity(0),
|
||||
threads: Vec::with_capacity(0),
|
||||
|
@ -67,7 +76,7 @@ impl Mailbox {
|
|||
collection.sort_by(|a, b| a.date().cmp(&b.date()));
|
||||
let (threads, threaded_collection) = build_threads(&mut collection, sent_folder);
|
||||
Ok(Mailbox {
|
||||
folder: folder.clone(),
|
||||
folder: (*folder).clone(),
|
||||
collection: collection,
|
||||
threads: threads,
|
||||
threaded_collection: threaded_collection,
|
||||
|
|
11
src/bin.rs
11
src/bin.rs
|
@ -59,9 +59,7 @@ fn make_input_thread(
|
|||
sx.send(ThreadEvent::Input(k));
|
||||
},
|
||||
|| {
|
||||
sx.send(ThreadEvent::UIEvent(UIEventType::ChangeMode(
|
||||
UIMode::Fork,
|
||||
)));
|
||||
sx.send(ThreadEvent::UIEvent(UIEventType::ChangeMode(UIMode::Fork)));
|
||||
},
|
||||
&rx,
|
||||
)
|
||||
|
@ -104,9 +102,10 @@ fn main() {
|
|||
let b = Entity {
|
||||
component: Box::new(listing),
|
||||
};
|
||||
let window = Entity {
|
||||
component: Box::new(VSplit::new(menu, b, 90, true)),
|
||||
};
|
||||
let mut tabs = Box::new(Tabbed::new(vec![Box::new(VSplit::new(menu, b, 90, true))]));
|
||||
tabs.add_component(Box::new(Composer {}));
|
||||
let window = Entity { component: tabs };
|
||||
|
||||
let status_bar = Entity {
|
||||
component: Box::new(StatusBar::new(window)),
|
||||
};
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#![cfg(feature = "python")]
|
||||
use pyo3::prelude::*;
|
||||
|
||||
|
||||
#[pymodinit(pythonmeli)]
|
||||
fn pythonmeli(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
// pyo3 aware function. All of our python interface could be declared in a separate module.
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* meli - ui crate
|
||||
*
|
||||
* Copyright 2017-2018 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* 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::*;
|
||||
|
||||
pub struct Composer {}
|
||||
|
||||
impl fmt::Display for Composer {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
// TODO display subject/info
|
||||
write!(f, "compose")
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Composer {
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
clear_area(grid, area);
|
||||
context.dirty_areas.push_back(area);
|
||||
}
|
||||
|
||||
fn process_event(&mut self, event: &UIEvent, context: &mut Context) {}
|
||||
|
||||
fn is_dirty(&self) -> bool {
|
||||
true
|
||||
}
|
||||
fn set_dirty(&mut self) {}
|
||||
}
|
|
@ -0,0 +1,679 @@
|
|||
/*
|
||||
* meli - ui crate.
|
||||
*
|
||||
* Copyright 2017-2018 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* 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::*;
|
||||
const MAX_COLS: usize = 500;
|
||||
|
||||
/// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the thread's content in a
|
||||
/// `ThreadView`.
|
||||
pub struct CompactMailListing {
|
||||
/// (x, y, z): x is accounts, y is folders, z is index inside a folder.
|
||||
cursor_pos: (usize, usize, usize),
|
||||
new_cursor_pos: (usize, usize, usize),
|
||||
length: usize,
|
||||
sort: (SortField, SortOrder),
|
||||
//subsort: (SortField, SortOrder),
|
||||
/// Cache current view.
|
||||
content: CellBuffer,
|
||||
/// If we must redraw on next redraw event
|
||||
dirty: bool,
|
||||
/// If `self.view` exists or not.
|
||||
unfocused: bool,
|
||||
view: Option<ThreadView>,
|
||||
}
|
||||
|
||||
impl Default for CompactMailListing {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CompactMailListing {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "mail")
|
||||
}
|
||||
}
|
||||
|
||||
impl CompactMailListing {
|
||||
pub fn new() -> Self {
|
||||
let content = CellBuffer::new(0, 0, Cell::with_char(' '));
|
||||
CompactMailListing {
|
||||
cursor_pos: (0, 1, 0),
|
||||
new_cursor_pos: (0, 0, 0),
|
||||
length: 0,
|
||||
sort: (SortField::Date, SortOrder::Desc),
|
||||
//subsort: (SortField::Date, SortOrder::Asc),
|
||||
content: content,
|
||||
dirty: true,
|
||||
unfocused: false,
|
||||
view: None,
|
||||
}
|
||||
}
|
||||
/// Fill the `self.content` `CellBuffer` with the contents of the account folder the user has
|
||||
/// chosen.
|
||||
fn refresh_mailbox(&mut self, context: &mut Context) {
|
||||
self.dirty = true;
|
||||
self.cursor_pos.2 = 0;
|
||||
self.new_cursor_pos.2 = 0;
|
||||
self.cursor_pos.1 = self.new_cursor_pos.1;
|
||||
self.cursor_pos.0 = self.new_cursor_pos.0;
|
||||
|
||||
// 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)),
|
||||
});
|
||||
// Get mailbox as a reference.
|
||||
//
|
||||
loop {
|
||||
// TODO: Show progress visually
|
||||
if let Ok(()) = context.accounts[self.cursor_pos.0].status(self.cursor_pos.1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let mailbox = &mut context.accounts[self.cursor_pos.0][self.cursor_pos.1]
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
|
||||
self.length = mailbox.threads.len();
|
||||
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,
|
||||
);
|
||||
self.content = content;
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Fix the threaded hell and refactor stuff into seperate functions and/or modules.
|
||||
let mut indentations: Vec<bool> = Vec::with_capacity(6);
|
||||
let mut thread_idx = 0; // needed for alternate thread colors
|
||||
/* Draw threaded view. */
|
||||
let mut local_collection: Vec<usize> = mailbox.threaded_collection.clone();
|
||||
let threads: &Vec<Container> = &mailbox.threads;
|
||||
local_collection.sort_by(|a, b| match self.sort {
|
||||
(SortField::Date, SortOrder::Desc) => {
|
||||
mailbox.thread(*b).date().cmp(&mailbox.thread(*a).date())
|
||||
}
|
||||
(SortField::Date, SortOrder::Asc) => {
|
||||
mailbox.thread(*a).date().cmp(&mailbox.thread(*b).date())
|
||||
}
|
||||
(SortField::Subject, SortOrder::Desc) => {
|
||||
let a = mailbox.thread(*a);
|
||||
let b = mailbox.thread(*b);
|
||||
let ma = &mailbox.collection[*a.message().as_ref().unwrap()];
|
||||
let mb = &mailbox.collection[*b.message().as_ref().unwrap()];
|
||||
ma.subject().cmp(&mb.subject())
|
||||
}
|
||||
(SortField::Subject, SortOrder::Asc) => {
|
||||
let a = mailbox.thread(*a);
|
||||
let b = mailbox.thread(*b);
|
||||
let ma = &mailbox.collection[*a.message().as_ref().unwrap()];
|
||||
let mb = &mailbox.collection[*b.message().as_ref().unwrap()];
|
||||
mb.subject().cmp(&ma.subject())
|
||||
}
|
||||
});
|
||||
let mut iter = local_collection.iter().enumerate().peekable();
|
||||
let len = mailbox
|
||||
.threaded_collection
|
||||
.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 = &threads[*i];
|
||||
let indentation = container.indentation();
|
||||
|
||||
if indentation == 0 {
|
||||
thread_idx += 1;
|
||||
}
|
||||
|
||||
assert!(container.has_message());
|
||||
match iter.peek() {
|
||||
Some(&(_, x)) if threads[*x].indentation() == indentation => {
|
||||
indentations.pop();
|
||||
indentations.push(true);
|
||||
}
|
||||
_ => {
|
||||
indentations.pop();
|
||||
indentations.push(false);
|
||||
}
|
||||
}
|
||||
if container.has_sibling() {
|
||||
indentations.pop();
|
||||
indentations.push(true);
|
||||
}
|
||||
let envelope: &Envelope = &mailbox.collection[container.message().unwrap()];
|
||||
let fg_color = if !envelope.is_seen() {
|
||||
Color::Byte(0)
|
||||
} else {
|
||||
Color::Default
|
||||
};
|
||||
let bg_color = if !envelope.is_seen() {
|
||||
Color::Byte(251)
|
||||
} else if thread_idx % 2 == 0 {
|
||||
Color::Byte(236)
|
||||
} else {
|
||||
Color::Default
|
||||
};
|
||||
let (x, _) = write_string_to_grid(
|
||||
&CompactMailListing::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);
|
||||
}
|
||||
|
||||
match iter.peek() {
|
||||
Some(&(_, x)) if threads[*x].indentation() > indentation => {
|
||||
indentations.push(false);
|
||||
}
|
||||
Some(&(_, x)) if threads[*x].indentation() < indentation => {
|
||||
for _ in 0..(indentation - threads[*x].indentation()) {
|
||||
indentations.pop();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
self.content = content;
|
||||
}
|
||||
|
||||
fn highlight_line_self(&mut self, 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();
|
||||
let envelope: &Envelope = if threaded {
|
||||
let i = mailbox.threaded_mail(idx);
|
||||
&mailbox.collection[i]
|
||||
} else {
|
||||
&mailbox.collection[idx]
|
||||
};
|
||||
|
||||
let fg_color = if !envelope.is_seen() {
|
||||
Color::Byte(0)
|
||||
} else {
|
||||
Color::Default
|
||||
};
|
||||
let bg_color = if !envelope.is_seen() {
|
||||
Color::Byte(251)
|
||||
} else if idx % 2 == 0 {
|
||||
Color::Byte(236)
|
||||
} else {
|
||||
Color::Default
|
||||
};
|
||||
change_colors(
|
||||
&mut self.content,
|
||||
((0, idx), (MAX_COLS - 1, idx)),
|
||||
fg_color,
|
||||
bg_color,
|
||||
);
|
||||
}
|
||||
|
||||
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();
|
||||
let envelope: &Envelope = if threaded {
|
||||
let i = mailbox.threaded_mail(idx);
|
||||
&mailbox.collection[i]
|
||||
} else {
|
||||
&mailbox.collection[idx]
|
||||
};
|
||||
|
||||
let fg_color = if !envelope.is_seen() {
|
||||
Color::Byte(0)
|
||||
} else {
|
||||
Color::Default
|
||||
};
|
||||
let bg_color = if self.cursor_pos.2 == idx {
|
||||
Color::Byte(246)
|
||||
} else if !envelope.is_seen() {
|
||||
Color::Byte(251)
|
||||
} else if idx % 2 == 0 {
|
||||
Color::Byte(236)
|
||||
} else {
|
||||
Color::Default
|
||||
};
|
||||
change_colors(grid, area, fg_color, bg_color);
|
||||
}
|
||||
|
||||
/// Draw the list of `Envelope`s.
|
||||
fn draw_list(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
if self.cursor_pos.1 != self.new_cursor_pos.1 {
|
||||
self.refresh_mailbox(context);
|
||||
}
|
||||
let upper_left = upper_left!(area);
|
||||
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)));
|
||||
context.dirty_areas.push_back(area);
|
||||
return;
|
||||
}
|
||||
let rows = get_y(bottom_right) - get_y(upper_left) + 1;
|
||||
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;
|
||||
|
||||
/* If cursor position has changed, remove the highlight from the previous position and
|
||||
* apply it in the new one. */
|
||||
if self.cursor_pos.2 != self.new_cursor_pos.2 && prev_page_no == page_no {
|
||||
let old_cursor_pos = self.cursor_pos;
|
||||
self.cursor_pos = self.new_cursor_pos;
|
||||
for idx in &[old_cursor_pos.2, self.new_cursor_pos.2] {
|
||||
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)),
|
||||
);
|
||||
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 {
|
||||
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,
|
||||
);
|
||||
context.dirty_areas.push_back(area);
|
||||
}
|
||||
|
||||
fn make_thread_entry(
|
||||
envelope: &Envelope,
|
||||
idx: usize,
|
||||
indent: usize,
|
||||
container: &Container,
|
||||
indentations: &[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())),
|
||||
CompactMailListing::format_date(&envelope)
|
||||
);
|
||||
for i in 0..indent {
|
||||
if indentations.len() > i && indentations[i] {
|
||||
s.push('│');
|
||||
} else {
|
||||
s.push(' ');
|
||||
}
|
||||
if i > 0 {
|
||||
s.push(' ');
|
||||
}
|
||||
}
|
||||
if indent > 0 {
|
||||
if has_sibling && has_parent {
|
||||
s.push('├');
|
||||
} else if has_sibling {
|
||||
s.push('┬');
|
||||
} else {
|
||||
s.push('└');
|
||||
}
|
||||
s.push('─');
|
||||
s.push('>');
|
||||
}
|
||||
|
||||
if show_subject {
|
||||
s.push_str(&format!("{:.85}", envelope.subject()));
|
||||
}
|
||||
let attach_count = envelope.body().count_attachments();
|
||||
if attach_count > 1 {
|
||||
s.push_str(&format!(" {}∞ ", attach_count - 1));
|
||||
}
|
||||
s
|
||||
}
|
||||
fn format_date(envelope: &Envelope) -> String {
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for CompactMailListing {
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
if !self.unfocused {
|
||||
if !self.is_dirty() {
|
||||
return;
|
||||
}
|
||||
self.dirty = false;
|
||||
/* Draw the entire list */
|
||||
self.draw_list(grid, area, context);
|
||||
} else {
|
||||
let upper_left = upper_left!(area);
|
||||
let bottom_right = bottom_right!(area);
|
||||
if self.length == 0 && self.dirty {
|
||||
clear_area(grid, area);
|
||||
context.dirty_areas.push_back(area);
|
||||
}
|
||||
|
||||
/* 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;
|
||||
|
||||
if bottom_entity_rows > total_rows {
|
||||
clear_area(grid, area);
|
||||
context.dirty_areas.push_back(area);
|
||||
return;
|
||||
}
|
||||
/* Mark message as read */
|
||||
let idx = self.cursor_pos.2;
|
||||
let must_highlight = {
|
||||
if self.length == 0 {
|
||||
false
|
||||
} else {
|
||||
let threaded = context.accounts[self.cursor_pos.0]
|
||||
.runtime_settings
|
||||
.threaded;
|
||||
let mailbox = &mut context.accounts[self.cursor_pos.0][self.cursor_pos.1]
|
||||
.as_mut()
|
||||
.unwrap();
|
||||
let envelope: &mut Envelope = if threaded {
|
||||
let i = mailbox.threaded_mail(idx);
|
||||
&mut mailbox.collection[i]
|
||||
} else {
|
||||
&mut mailbox.collection[idx]
|
||||
};
|
||||
if !envelope.is_seen() {
|
||||
envelope.set_seen().unwrap();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
};
|
||||
if must_highlight {
|
||||
self.highlight_line_self(idx, context);
|
||||
}
|
||||
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,
|
||||
);
|
||||
if self.length == 0 {
|
||||
self.dirty = false;
|
||||
return;
|
||||
}
|
||||
{
|
||||
/* TODO: Move the box drawing business in separate functions */
|
||||
if get_x(upper_left) > 0 && grid[(get_x(upper_left) - 1, mid)].ch() == VERT_BOUNDARY
|
||||
{
|
||||
grid[(get_x(upper_left) - 1, mid)].set_ch(LIGHT_VERTICAL_AND_RIGHT);
|
||||
}
|
||||
|
||||
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)));
|
||||
}
|
||||
// TODO: Make headers view configurable
|
||||
|
||||
if !self.dirty {
|
||||
if let Some(v) = self.view.as_mut() {
|
||||
v.draw(grid, (set_y(upper_left, mid + 1), bottom_right), context);
|
||||
}
|
||||
return;
|
||||
}
|
||||
self.view = Some(ThreadView::new(Vec::new()));
|
||||
self.view.as_mut().unwrap().draw(
|
||||
grid,
|
||||
(set_y(upper_left, mid + 1), bottom_right),
|
||||
context,
|
||||
);
|
||||
self.dirty = false;
|
||||
}
|
||||
}
|
||||
fn process_event(&mut self, event: &UIEvent, context: &mut Context) {
|
||||
match event.event_type {
|
||||
UIEventType::Input(Key::Up) => {
|
||||
if self.cursor_pos.2 > 0 {
|
||||
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 => {
|
||||
self.unfocused = true;
|
||||
self.dirty = true;
|
||||
}
|
||||
UIEventType::Input(Key::Char('m')) if !self.unfocused => {
|
||||
use std::process::{Command, Stdio};
|
||||
/* Kill input thread so that spawned command can be sole receiver of stdin */
|
||||
{
|
||||
/* I tried thread::park() here but for some reason it never blocked and always
|
||||
* returned. Spinlocks are also useless because you have to keep the mutex
|
||||
* guard alive til the child process exits, which requires some effort.
|
||||
*
|
||||
* The only problem with this approach is tht the user has to send some input
|
||||
* in order for the input-thread to wake up and realise it should kill itself.
|
||||
*
|
||||
* I tried writing to stdin/tty manually but for some reason rustty didn't
|
||||
* acknowledge it.
|
||||
*/
|
||||
|
||||
/*
|
||||
* tx sends to input-thread and it kills itself.
|
||||
*/
|
||||
let tx = context.input_thread();
|
||||
tx.send(true);
|
||||
}
|
||||
let mut f = create_temp_file(&new_draft(context), None);
|
||||
//let mut f = Box::new(std::fs::File::create(&dir).unwrap());
|
||||
|
||||
// TODO: check exit status
|
||||
let mut output = Command::new("vim")
|
||||
.arg("+/^$")
|
||||
.arg(&f.path())
|
||||
.stdin(Stdio::inherit())
|
||||
.stdout(Stdio::inherit())
|
||||
.spawn()
|
||||
.expect("failed to execute process");
|
||||
|
||||
/*
|
||||
* 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),
|
||||
});
|
||||
return;
|
||||
}
|
||||
UIEventType::Input(Key::Char('i')) if self.unfocused => {
|
||||
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();
|
||||
match k {
|
||||
'J' if folder_length > 0 => {
|
||||
if self.new_cursor_pos.1 < folder_length - 1 {
|
||||
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
|
||||
{
|
||||
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 {
|
||||
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 {
|
||||
'h' 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);
|
||||
}
|
||||
'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::MailboxUpdate((ref idxa, ref idxf)) => {
|
||||
if *idxa == self.new_cursor_pos.1 && *idxf == self.new_cursor_pos.0 {
|
||||
self.refresh_mailbox(context);
|
||||
self.dirty = true;
|
||||
}
|
||||
}
|
||||
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;
|
||||
return;
|
||||
}
|
||||
Action::ViewMailbox(idx) => {
|
||||
self.new_cursor_pos.1 = *idx;
|
||||
self.dirty = true;
|
||||
self.refresh_mailbox(context);
|
||||
return;
|
||||
}
|
||||
Action::Sort(field, order) => {
|
||||
self.sort = (field.clone(), order.clone());
|
||||
self.dirty = true;
|
||||
self.refresh_mailbox(context);
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
if let Some(ref mut v) = self.view {
|
||||
v.process_event(event, context);
|
||||
}
|
||||
}
|
||||
fn is_dirty(&self) -> bool {
|
||||
self.dirty || self.view.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
|
||||
}
|
||||
fn set_dirty(&mut self) {
|
||||
self.dirty = true;
|
||||
}
|
||||
}
|
|
@ -21,6 +21,9 @@
|
|||
|
||||
use super::*;
|
||||
|
||||
mod compact;
|
||||
pub use self::compact::*;
|
||||
|
||||
const MAX_COLS: usize = 500;
|
||||
|
||||
/// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the `Envelope` content in a
|
||||
|
@ -31,7 +34,7 @@ pub struct MailListing {
|
|||
new_cursor_pos: (usize, usize, usize),
|
||||
length: usize,
|
||||
sort: (SortField, SortOrder),
|
||||
subsort: (SortField, SortOrder),
|
||||
//subsort: (SortField, SortOrder),
|
||||
/// Cache current view.
|
||||
content: CellBuffer,
|
||||
/// If we must redraw on next redraw event
|
||||
|
@ -47,6 +50,12 @@ impl Default for MailListing {
|
|||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for MailListing {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "mail")
|
||||
}
|
||||
}
|
||||
|
||||
impl MailListing {
|
||||
/// Helper function to format entry strings for MailListing */
|
||||
/* TODO: Make this configurable */
|
||||
|
@ -66,7 +75,7 @@ impl MailListing {
|
|||
new_cursor_pos: (0, 0, 0),
|
||||
length: 0,
|
||||
sort: (SortField::Date, SortOrder::Desc),
|
||||
subsort: (SortField::Date, SortOrder::Asc),
|
||||
//subsort: (SortField::Date, SortOrder::Asc),
|
||||
content: content,
|
||||
dirty: true,
|
||||
unfocused: false,
|
||||
|
@ -728,4 +737,7 @@ impl Component for MailListing {
|
|||
fn is_dirty(&self) -> bool {
|
||||
self.dirty || self.view.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
|
||||
}
|
||||
fn set_dirty(&mut self) {
|
||||
self.dirty = true;
|
||||
}
|
||||
}
|
|
@ -22,11 +22,14 @@
|
|||
/*! Entities that handle Mail specific functions.
|
||||
*/
|
||||
use super::*;
|
||||
use melib::backends::Folder;
|
||||
|
||||
pub mod listing;
|
||||
pub mod view;
|
||||
pub use listing::*;
|
||||
pub mod view;
|
||||
pub use view::*;
|
||||
mod compose;
|
||||
pub use self::compose::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct AccountMenuEntry {
|
||||
|
@ -43,6 +46,13 @@ pub struct AccountMenu {
|
|||
cursor: Option<(usize, usize)>,
|
||||
}
|
||||
|
||||
impl fmt::Display for AccountMenu {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
// TODO display subject/info
|
||||
write!(f, "menu")
|
||||
}
|
||||
}
|
||||
|
||||
impl AccountMenu {
|
||||
pub fn new(accounts: &[Account]) -> Self {
|
||||
let accounts = accounts
|
||||
|
@ -53,8 +63,10 @@ impl AccountMenu {
|
|||
index: i,
|
||||
entries: {
|
||||
let mut entries = Vec::with_capacity(a.len());
|
||||
for (idx, acc) in a.list_folders().iter().enumerate() {
|
||||
entries.push((idx, acc.clone()));
|
||||
let mut idx = 0;
|
||||
for acc in a.list_folders() {
|
||||
entries.push((idx, acc));
|
||||
idx += 1;
|
||||
}
|
||||
entries
|
||||
},
|
||||
|
@ -187,12 +199,7 @@ impl AccountMenu {
|
|||
);
|
||||
|
||||
if highlight && idx > 1 && self.cursor.unwrap().1 == idx - 2 {
|
||||
change_colors(
|
||||
grid,
|
||||
((x, y), (get_x(bottom_right), y)),
|
||||
color_fg,
|
||||
color_bg,
|
||||
);
|
||||
change_colors(grid, ((x, y), (get_x(bottom_right), y)), color_fg, color_bg);
|
||||
} else {
|
||||
change_colors(grid, ((x, y), set_y(bottom_right, y)), color_fg, color_bg);
|
||||
}
|
||||
|
@ -240,4 +247,7 @@ impl Component for AccountMenu {
|
|||
fn is_dirty(&self) -> bool {
|
||||
self.dirty
|
||||
}
|
||||
fn set_dirty(&mut self) {
|
||||
self.dirty = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,6 +54,13 @@ impl HtmlView {
|
|||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for HtmlView {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
// TODO display subject/info
|
||||
write!(f, "view")
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for HtmlView {
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
self.pager.draw(grid, area, context);
|
||||
|
@ -90,4 +97,5 @@ impl Component for HtmlView {
|
|||
fn is_dirty(&self) -> bool {
|
||||
self.pager.is_dirty()
|
||||
}
|
||||
fn set_dirty(&mut self) {}
|
||||
}
|
||||
|
|
|
@ -24,8 +24,9 @@ use linkify::{Link, LinkFinder};
|
|||
use std::process::{Command, Stdio};
|
||||
|
||||
mod html;
|
||||
|
||||
pub use self::html::*;
|
||||
mod thread;
|
||||
pub use self::thread::*;
|
||||
|
||||
use mime_apps::query_default_app;
|
||||
|
||||
|
@ -59,6 +60,13 @@ pub struct MailView {
|
|||
cmd_buf: String,
|
||||
}
|
||||
|
||||
impl fmt::Display for MailView {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
// TODO display subject/info
|
||||
write!(f, "view mail")
|
||||
}
|
||||
}
|
||||
|
||||
impl MailView {
|
||||
pub fn new(
|
||||
coordinates: (usize, usize, usize),
|
||||
|
@ -80,9 +88,12 @@ impl MailView {
|
|||
fn attachment_to_text(&self, body: Attachment) -> String {
|
||||
let finder = LinkFinder::new();
|
||||
let body_text = if body.content_type().0.is_text() && body.content_type().1.is_html() {
|
||||
let mut s = String::from("Text piped through `w3m`. Press `v` to open in web browser. \n\n");
|
||||
let mut s =
|
||||
String::from("Text piped through `w3m`. Press `v` to open in web browser. \n\n");
|
||||
s.extend(
|
||||
String::from_utf8_lossy(&decode(&body, Some(Box::new(|a: &Attachment| {
|
||||
String::from_utf8_lossy(&decode(
|
||||
&body,
|
||||
Some(Box::new(|a: &Attachment| {
|
||||
use std::io::Write;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
|
@ -94,9 +105,17 @@ impl MailView {
|
|||
.spawn()
|
||||
.expect("Failed to start html filter process");
|
||||
|
||||
html_filter.stdin.as_mut().unwrap().write_all(&raw).expect("Failed to write to w3m stdin");
|
||||
html_filter
|
||||
.stdin
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.write_all(&raw)
|
||||
.expect("Failed to write to w3m stdin");
|
||||
html_filter.wait_with_output().unwrap().stdout
|
||||
})))).into_owned().chars());
|
||||
})),
|
||||
)).into_owned()
|
||||
.chars(),
|
||||
);
|
||||
s
|
||||
} else {
|
||||
String::from_utf8_lossy(&decode_rec(&body, None)).into()
|
||||
|
@ -105,13 +124,14 @@ impl MailView {
|
|||
ViewMode::Normal | ViewMode::Subview => {
|
||||
let mut t = body_text.to_string();
|
||||
if body.count_attachments() > 1 {
|
||||
t = body.attachments().iter().enumerate().fold(
|
||||
t,
|
||||
|mut s, (idx, a)| {
|
||||
t = body
|
||||
.attachments()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.fold(t, |mut s, (idx, a)| {
|
||||
s.push_str(&format!("[{}] {}\n\n", idx, a));
|
||||
s
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
t
|
||||
}
|
||||
|
@ -131,13 +151,14 @@ impl MailView {
|
|||
t.insert_str(l.start() + offset, &format!("[{}]", lidx));
|
||||
}
|
||||
if body.count_attachments() > 1 {
|
||||
t = body.attachments().iter().enumerate().fold(
|
||||
t,
|
||||
|mut s, (idx, a)| {
|
||||
t = body
|
||||
.attachments()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.fold(t, |mut s, (idx, a)| {
|
||||
s.push_str(&format!("[{}] {}\n\n", idx, a));
|
||||
s
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
t
|
||||
}
|
||||
|
@ -190,9 +211,7 @@ impl Component for MailView {
|
|||
|
||||
let (envelope_idx, y): (usize, usize) = {
|
||||
let accounts = &mut context.accounts;
|
||||
let threaded = accounts[self.coordinates.0]
|
||||
.runtime_settings
|
||||
.threaded;
|
||||
let threaded = accounts[self.coordinates.0].runtime_settings.threaded;
|
||||
let mailbox = &mut accounts[self.coordinates.0][self.coordinates.1]
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
|
@ -291,12 +310,15 @@ impl Component for MailView {
|
|||
let body = envelope.body();
|
||||
match self.mode {
|
||||
ViewMode::Attachment(aidx) if body.attachments()[aidx].is_html() => {
|
||||
self.subview = Some(Box::new(HtmlView::new(decode(&body.attachments()[aidx], None))));
|
||||
},
|
||||
self.subview = Some(Box::new(HtmlView::new(decode(
|
||||
&body.attachments()[aidx],
|
||||
None,
|
||||
))));
|
||||
}
|
||||
ViewMode::Normal if body.is_html() => {
|
||||
self.subview = Some(Box::new(HtmlView::new(decode(&body, None))));
|
||||
self.mode = ViewMode::Subview;
|
||||
},
|
||||
}
|
||||
_ => {
|
||||
let buf = {
|
||||
let text = self.attachment_to_text(body);
|
||||
|
@ -309,7 +331,7 @@ impl Component for MailView {
|
|||
self.pager.as_mut().map(|p| p.cursor_pos())
|
||||
};
|
||||
self.pager = Some(Pager::from_buf(&buf, cursor_pos));
|
||||
},
|
||||
}
|
||||
};
|
||||
self.dirty = false;
|
||||
}
|
||||
|
@ -351,9 +373,7 @@ impl Component for MailView {
|
|||
|
||||
{
|
||||
let accounts = &mut context.accounts;
|
||||
let threaded = accounts[self.coordinates.0]
|
||||
.runtime_settings
|
||||
.threaded;
|
||||
let threaded = accounts[self.coordinates.0].runtime_settings.threaded;
|
||||
let mailbox = &mut accounts[self.coordinates.0][self.coordinates.1]
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
|
@ -424,9 +444,7 @@ impl Component for MailView {
|
|||
self.cmd_buf.clear();
|
||||
let url = {
|
||||
let accounts = &mut context.accounts;
|
||||
let threaded = accounts[self.coordinates.0]
|
||||
.runtime_settings
|
||||
.threaded;
|
||||
let threaded = accounts[self.coordinates.0].runtime_settings.threaded;
|
||||
let mailbox = &mut accounts[self.coordinates.0][self.coordinates.1]
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
|
@ -482,4 +500,7 @@ impl Component for MailView {
|
|||
|| self.pager.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
|
||||
|| self.subview.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
|
||||
}
|
||||
fn set_dirty(&mut self) {
|
||||
self.dirty = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* meli - ui crate.
|
||||
*
|
||||
* Copyright 2017-2018 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* 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 std::io::Write;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
pub struct ThreadView {
|
||||
pager: Pager,
|
||||
bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
impl ThreadView {
|
||||
pub fn new(bytes: Vec<u8>) -> Self {
|
||||
let mut html_filter = Command::new("w3m")
|
||||
.args(&["-I", "utf-8", "-T", "text/html"])
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("Failed to start html filter process");
|
||||
html_filter
|
||||
.stdin
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.write_all(&bytes)
|
||||
.expect("Failed to write to w3m stdin");
|
||||
let mut display_text =
|
||||
String::from("Text piped through `w3m`. Press `v` to open in web browser. \n\n");
|
||||
display_text.push_str(&String::from_utf8_lossy(
|
||||
&html_filter.wait_with_output().unwrap().stdout,
|
||||
));
|
||||
|
||||
let buf = MailView::plain_text_to_buf(&display_text, true);
|
||||
let pager = Pager::from_buf(&buf, None);
|
||||
ThreadView { pager, bytes }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ThreadView {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
// TODO display subject/info
|
||||
write!(f, "view thread")
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for ThreadView {
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
self.pager.draw(grid, area, context);
|
||||
}
|
||||
fn process_event(&mut self, event: &UIEvent, context: &mut Context) {
|
||||
match event.event_type {
|
||||
UIEventType::Input(Key::Char('v')) => {
|
||||
// TODO: Optional filter that removes outgoing resource requests (images and
|
||||
// scripts)
|
||||
let binary = query_default_app("text/html");
|
||||
if let Ok(binary) = binary {
|
||||
let mut p = create_temp_file(&self.bytes, None);
|
||||
Command::new(&binary)
|
||||
.arg(p.path())
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.unwrap_or_else(|_| panic!("Failed to start {}", binary.display()));
|
||||
context.temp_files.push(p);
|
||||
} else {
|
||||
context.replies.push_back(UIEvent {
|
||||
id: 0,
|
||||
event_type: UIEventType::StatusNotification(format!(
|
||||
"Couldn't find a default application for html files."
|
||||
)),
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
self.pager.process_event(event, context);
|
||||
}
|
||||
fn is_dirty(&self) -> bool {
|
||||
self.pager.is_dirty()
|
||||
}
|
||||
fn set_dirty(&mut self) {}
|
||||
}
|
|
@ -35,6 +35,10 @@ pub mod notifications;
|
|||
pub mod utilities;
|
||||
pub use self::utilities::*;
|
||||
|
||||
use std::fmt;
|
||||
use std::fmt::Display;
|
||||
use std::ops::Deref;
|
||||
|
||||
use super::{Key, UIEvent, UIEventType};
|
||||
/// The upper and lower boundary char.
|
||||
const HORZ_BOUNDARY: char = '─';
|
||||
|
@ -65,6 +69,14 @@ pub struct Entity {
|
|||
pub component: Box<Component>, // more than one?
|
||||
}
|
||||
|
||||
impl Deref for Entity {
|
||||
type Target = Box<Component>;
|
||||
|
||||
fn deref(&self) -> &Box<Component> {
|
||||
&self.component
|
||||
}
|
||||
}
|
||||
|
||||
impl Entity {
|
||||
/// Pass events to child component.
|
||||
pub fn rcv_event(&mut self, event: &UIEvent, context: &mut Context) {
|
||||
|
@ -75,12 +87,13 @@ impl Entity {
|
|||
/// Types implementing this Trait can draw on the terminal and receive events.
|
||||
/// If a type wants to skip drawing if it has not changed anything, it can hold some flag in its
|
||||
/// fields (eg self.dirty = false) and act upon that in their `draw` implementation.
|
||||
pub trait Component {
|
||||
pub trait Component: Display {
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context);
|
||||
fn process_event(&mut self, event: &UIEvent, context: &mut Context);
|
||||
fn is_dirty(&self) -> bool {
|
||||
true
|
||||
}
|
||||
fn set_dirty(&mut self);
|
||||
}
|
||||
|
||||
// TODO: word break.
|
||||
|
|
|
@ -29,6 +29,13 @@ use super::*;
|
|||
/// Passes notifications to the OS using the XDG specifications.
|
||||
pub struct XDGNotifications {}
|
||||
|
||||
impl fmt::Display for XDGNotifications {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
// TODO display subject/info
|
||||
write!(f, "")
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for XDGNotifications {
|
||||
fn draw(&mut self, _grid: &mut CellBuffer, _area: Area, _context: &mut Context) {}
|
||||
fn process_event(&mut self, event: &UIEvent, _context: &mut Context) {
|
||||
|
@ -41,4 +48,5 @@ impl Component for XDGNotifications {
|
|||
.unwrap();
|
||||
}
|
||||
}
|
||||
fn set_dirty(&mut self) {}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,13 @@ pub struct HSplit {
|
|||
ratio: usize, // bottom/whole height * 100
|
||||
}
|
||||
|
||||
impl fmt::Display for HSplit {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
// TODO display subject/info
|
||||
self.top.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl HSplit {
|
||||
pub fn new(top: Entity, bottom: Entity, ratio: usize, show_divider: bool) -> Self {
|
||||
HSplit {
|
||||
|
@ -79,6 +86,10 @@ impl Component for HSplit {
|
|||
fn is_dirty(&self) -> bool {
|
||||
self.top.component.is_dirty() || self.bottom.component.is_dirty()
|
||||
}
|
||||
fn set_dirty(&mut self) {
|
||||
self.top.component.set_dirty();
|
||||
self.bottom.component.set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
/// A vertically split in half container.
|
||||
|
@ -90,6 +101,13 @@ pub struct VSplit {
|
|||
ratio: usize, // right/(container width) * 100
|
||||
}
|
||||
|
||||
impl fmt::Display for VSplit {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
// TODO display focused entity
|
||||
self.right.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl VSplit {
|
||||
pub fn new(left: Entity, right: Entity, ratio: usize, show_divider: bool) -> Self {
|
||||
VSplit {
|
||||
|
@ -155,6 +173,10 @@ impl Component for VSplit {
|
|||
fn is_dirty(&self) -> bool {
|
||||
self.left.component.is_dirty() || self.right.component.is_dirty()
|
||||
}
|
||||
fn set_dirty(&mut self) {
|
||||
self.left.component.set_dirty();
|
||||
self.right.component.set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
/// A pager for text.
|
||||
|
@ -168,6 +190,13 @@ pub struct Pager {
|
|||
content: CellBuffer,
|
||||
}
|
||||
|
||||
impl fmt::Display for Pager {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
// TODO display info
|
||||
write!(f, "pager")
|
||||
}
|
||||
}
|
||||
|
||||
impl Pager {
|
||||
pub fn from_string(mut text: String, context: &mut Context, cursor_pos: Option<usize>) -> Self {
|
||||
let pager_filter: Option<&String> = context.settings.pager.filter.as_ref();
|
||||
|
@ -325,6 +354,9 @@ impl Component for Pager {
|
|||
fn is_dirty(&self) -> bool {
|
||||
self.dirty
|
||||
}
|
||||
fn set_dirty(&mut self) {
|
||||
self.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Status bar.
|
||||
|
@ -338,6 +370,13 @@ pub struct StatusBar {
|
|||
dirty: bool,
|
||||
}
|
||||
|
||||
impl fmt::Display for StatusBar {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
// TODO display info
|
||||
write!(f, "status bar")
|
||||
}
|
||||
}
|
||||
|
||||
impl StatusBar {
|
||||
pub fn new(container: Entity) -> Self {
|
||||
StatusBar {
|
||||
|
@ -497,6 +536,9 @@ impl Component for StatusBar {
|
|||
fn is_dirty(&self) -> bool {
|
||||
self.dirty || self.container.component.is_dirty()
|
||||
}
|
||||
fn set_dirty(&mut self) {
|
||||
self.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
// A box with a text content.
|
||||
|
@ -510,11 +552,17 @@ impl TextBox {
|
|||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for TextBox {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
// TODO display info
|
||||
write!(f, "text box")
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for TextBox {
|
||||
fn draw(&mut self, _grid: &mut CellBuffer, _area: Area, _context: &mut Context) {}
|
||||
fn process_event(&mut self, _event: &UIEvent, _context: &mut Context) {
|
||||
return;
|
||||
}
|
||||
fn process_event(&mut self, _event: &UIEvent, _context: &mut Context) {}
|
||||
fn set_dirty(&mut self) {}
|
||||
}
|
||||
|
||||
pub struct Progress {
|
||||
|
@ -552,6 +600,13 @@ impl Progress {
|
|||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Progress {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
// TODO display info
|
||||
write!(f, "progress bar")
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Progress {
|
||||
fn draw(&mut self, _grid: &mut CellBuffer, _area: Area, _context: &mut Context) {
|
||||
unimplemented!()
|
||||
|
@ -559,4 +614,97 @@ impl Component for Progress {
|
|||
fn process_event(&mut self, _event: &UIEvent, _context: &mut Context) {
|
||||
return;
|
||||
}
|
||||
fn set_dirty(&mut self) {}
|
||||
}
|
||||
|
||||
pub struct Tabbed {
|
||||
children: Vec<Box<Component>>,
|
||||
cursor_pos: usize,
|
||||
}
|
||||
|
||||
impl Tabbed {
|
||||
pub fn new(children: Vec<Box<Component>>) -> Self {
|
||||
Tabbed {
|
||||
children,
|
||||
cursor_pos: 0,
|
||||
}
|
||||
}
|
||||
fn draw_tabs(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
let mut x = get_x(upper_left!(area));
|
||||
let mut y: usize = get_y(upper_left!(area));
|
||||
for (idx, c) in self.children.iter().enumerate() {
|
||||
let (fg, bg) = if idx == self.cursor_pos {
|
||||
(Color::Default, Color::Default)
|
||||
} else {
|
||||
(Color::Byte(15), Color::Byte(8))
|
||||
};
|
||||
let (x_, _y_) = write_string_to_grid(
|
||||
&format!(" {} ", c),
|
||||
grid,
|
||||
fg,
|
||||
bg,
|
||||
(set_x(upper_left!(area), x), bottom_right!(area)),
|
||||
false,
|
||||
);
|
||||
x = x_ + 1;
|
||||
if y != _y_ {
|
||||
break;
|
||||
}
|
||||
y = _y_;
|
||||
}
|
||||
let (cols, _) = grid.size();
|
||||
let cslice: &mut [Cell] = grid;
|
||||
for c in cslice[(y * cols) + x..(y * cols) + cols].iter_mut() {
|
||||
c.set_bg(Color::Byte(7));
|
||||
}
|
||||
context.dirty_areas.push_back(area);
|
||||
}
|
||||
pub fn add_component(&mut self, new: Box<Component>) {
|
||||
self.children.push(new);
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Tabbed {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
// TODO display info
|
||||
write!(f, "tabs")
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Tabbed {
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
if self.children.len() > 1 {
|
||||
self.draw_tabs(
|
||||
grid,
|
||||
(
|
||||
upper_left!(area),
|
||||
set_x(upper_left!(area), get_x(bottom_right!(area))),
|
||||
),
|
||||
context,
|
||||
);
|
||||
let y = get_y(upper_left!(area));
|
||||
self.children[self.cursor_pos].draw(
|
||||
grid,
|
||||
(set_y(upper_left!(area), y + 1), bottom_right!(area)),
|
||||
context,
|
||||
);
|
||||
} else {
|
||||
self.children[self.cursor_pos].draw(grid, area, context);
|
||||
}
|
||||
}
|
||||
fn process_event(&mut self, event: &UIEvent, context: &mut Context) {
|
||||
match &event.event_type {
|
||||
UIEventType::Input(Key::Char('T')) => {
|
||||
self.cursor_pos = (self.cursor_pos + 1) % self.children.len();
|
||||
self.children[self.cursor_pos].set_dirty();
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
self.children[self.cursor_pos].process_event(event, context);
|
||||
}
|
||||
fn is_dirty(&self) -> bool {
|
||||
self.children[self.cursor_pos].is_dirty()
|
||||
}
|
||||
fn set_dirty(&mut self) {}
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -50,7 +50,7 @@ pub struct Context {
|
|||
|
||||
/// Events queue that components send back to the state
|
||||
pub replies: VecDeque<UIEvent>,
|
||||
_backends: Backends,
|
||||
backends: Backends,
|
||||
|
||||
input_thread: chan::Sender<bool>,
|
||||
pub temp_files: Vec<File>,
|
||||
|
@ -103,8 +103,8 @@ impl State<std::io::Stdout> {
|
|||
pub fn new(sender: Sender<ThreadEvent>, input_thread: chan::Sender<bool>) -> Self {
|
||||
let _stdout = std::io::stdout();
|
||||
_stdout.lock();
|
||||
let settings = Settings::new();
|
||||
let backends = Backends::new();
|
||||
let settings = Settings::new();
|
||||
let stdout = AlternateScreen::from(_stdout.into_raw_mode().unwrap());
|
||||
|
||||
let termsize = termion::terminal_size().ok();
|
||||
|
@ -155,7 +155,7 @@ impl State<std::io::Stdout> {
|
|||
accounts,
|
||||
mailbox_hashes: FnvHashMap::with_capacity_and_hasher(1, Default::default()),
|
||||
|
||||
_backends: backends,
|
||||
backends,
|
||||
settings: settings.clone(),
|
||||
runtime_settings: settings,
|
||||
dirty_areas: VecDeque::with_capacity(5),
|
||||
|
@ -178,7 +178,7 @@ impl State<std::io::Stdout> {
|
|||
).unwrap();
|
||||
s.flush();
|
||||
for (x, account) in s.context.accounts.iter_mut().enumerate() {
|
||||
for (y, folder) in account.settings.folders.iter().enumerate() {
|
||||
for (y, folder) in account.backend.folders().iter().enumerate() {
|
||||
s.context.mailbox_hashes.insert(folder.hash(), (x, y));
|
||||
}
|
||||
let sender = s.sender.clone();
|
||||
|
@ -463,8 +463,7 @@ impl<W: Write> State<W> {
|
|||
_ => {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
{
|
||||
} {
|
||||
if let Some(ForkType::NewDraft(f, _)) = std::mem::replace(&mut self.child, None) {
|
||||
self.rcv_event(UIEvent {
|
||||
id: 0,
|
||||
|
@ -476,7 +475,9 @@ impl<W: Write> State<W> {
|
|||
Some(false)
|
||||
}
|
||||
fn flush(&mut self) {
|
||||
if let Some(s) = self.stdout.as_mut() { s.flush().unwrap(); }
|
||||
if let Some(s) = self.stdout.as_mut() {
|
||||
s.flush().unwrap();
|
||||
}
|
||||
}
|
||||
fn stdout(&mut self) -> &mut termion::screen::AlternateScreen<termion::raw::RawTerminal<W>> {
|
||||
self.stdout.as_mut().unwrap()
|
||||
|
|
|
@ -20,9 +20,9 @@
|
|||
*/
|
||||
|
||||
use std;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::fs::OpenOptions;
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
|
@ -39,7 +39,12 @@ impl Drop for File {
|
|||
|
||||
impl File {
|
||||
pub fn file(&mut self) -> std::fs::File {
|
||||
OpenOptions::new().read(true).write(true).create(true).open(&self.path).unwrap()
|
||||
OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(&self.path)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn path(&self) -> &PathBuf {
|
||||
|
|
Loading…
Reference in New Issue