rustfmt everything

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

View File

@ -1,9 +1,9 @@
#![feature(test)]
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;

View File

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

View File

@ -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")]

View File

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

View File

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

View File

@ -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]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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!(

View File

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

View File

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

View File

@ -27,8 +27,8 @@ The mail handling stuff is done in the `melib` crate which includes all backend
extern crate melib;
extern crate 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;
}
}
}
}
}

View File

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

View File

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

View File

@ -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 {

View File

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

View File

@ -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();
}
_ => {}
}
}
}

View File

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

View File

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

View File

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

View File

@ -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 => {},

View File

@ -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.