Run clippy and rustfmt

embed
Manos Pitsidianakis 2018-08-07 15:01:15 +03:00
parent 43ad31d2ab
commit c30f77a312
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
29 changed files with 800 additions and 440 deletions

View File

@ -1,3 +1,24 @@
/*
* meli - async module
*
* Copyright 2017 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/>.
*/
/*! /*!
* Primitive Async/Wait implementation. * Primitive Async/Wait implementation.
* *
@ -69,7 +90,7 @@ impl<T> Async<T> {
pub fn extract(self) -> T { pub fn extract(self) -> T {
self.value.unwrap() self.value.unwrap()
} }
/// Polls worker thread and returns result. /// Polls worker thread and returns result.
pub fn poll(&mut self) -> Result<AsyncStatus, ()> { pub fn poll(&mut self) -> Result<AsyncStatus, ()> {
if self.value.is_some() { if self.value.is_some() {
return Ok(AsyncStatus::Finished); return Ok(AsyncStatus::Finished);
@ -99,4 +120,3 @@ impl<T> Async<T> {
return Ok(AsyncStatus::Finished); return Ok(AsyncStatus::Finished);
} }
} }

View File

@ -27,9 +27,9 @@ pub mod pager;
use pager::PagerSettings; use pager::PagerSettings;
use std::collections::hash_map::DefaultHasher; use std::collections::hash_map::DefaultHasher;
use std::hash::Hasher;
use std::collections::HashMap; use std::collections::HashMap;
use std::fs; use std::fs;
use std::hash::Hasher;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]

View File

@ -1,3 +1,26 @@
/*
* meli - pager conf module
*
* Copyright 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/>.
*/
// TODO: Move this to `ui` crate.
fn false_val() -> bool { fn false_val() -> bool {
true true
} }

View File

@ -18,10 +18,10 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
pub mod async;
pub mod conf; pub mod conf;
pub mod error; pub mod error;
pub mod mailbox; pub mod mailbox;
pub mod async;
#[macro_use] #[macro_use]
extern crate serde_derive; extern crate serde_derive;

View File

@ -23,10 +23,10 @@
* Account management from user configuration. * Account management from user configuration.
*/ */
use async::*;
use conf::{AccountSettings, Folder}; use conf::{AccountSettings, Folder};
use mailbox::backends::{Backends, RefreshEventConsumer}; use mailbox::backends::{Backends, RefreshEventConsumer};
use mailbox::*; use mailbox::*;
use async::*;
use std::ops::{Index, IndexMut}; use std::ops::{Index, IndexMut};
use std::result; use std::result;
@ -89,73 +89,73 @@ impl Account {
&mut self.workers &mut self.workers
} }
fn load_mailbox(&mut self, index: usize, envelopes: Result<Vec<Envelope>>) -> () { fn load_mailbox(&mut self, index: usize, envelopes: Result<Vec<Envelope>>) -> () {
let folder = &self.settings.folders[index]; let folder = &self.settings.folders[index];
if self.sent_folder.is_some() { if self.sent_folder.is_some() {
let id = self.sent_folder.unwrap(); let id = self.sent_folder.unwrap();
if id == index { if id == index {
self.folders[index] =
Some(Mailbox::new(folder, &None, envelopes));
} else {
let (sent, cur) = {
let ptr = self.folders.as_mut_ptr();
unsafe {
use std::slice::from_raw_parts_mut;
(
from_raw_parts_mut(ptr.offset(id as isize), id + 1),
from_raw_parts_mut(ptr.offset(index as isize), index + 1),
)
}
};
let sent_path = &self.settings.folders[id];
if sent[0].is_none() {
sent[0] = Some(Mailbox::new(sent_path, &None, envelopes.clone()));
}
cur[0] = Some(Mailbox::new(folder, &sent[0], envelopes));
}
} else {
self.folders[index] = Some(Mailbox::new(folder, &None, envelopes)); self.folders[index] = Some(Mailbox::new(folder, &None, envelopes));
}; } else {
let (sent, cur) = {
let ptr = self.folders.as_mut_ptr();
unsafe {
use std::slice::from_raw_parts_mut;
(
from_raw_parts_mut(ptr.offset(id as isize), id + 1),
from_raw_parts_mut(ptr.offset(index as isize), index + 1),
)
}
};
let sent_path = &self.settings.folders[id];
if sent[0].is_none() {
sent[0] = Some(Mailbox::new(sent_path, &None, envelopes.clone()));
}
cur[0] = Some(Mailbox::new(folder, &sent[0], envelopes));
}
} else {
self.folders[index] = Some(Mailbox::new(folder, &None, envelopes));
};
} }
pub fn status(&mut self, index: usize) -> result::Result<(), usize> { pub fn status(&mut self, index: usize) -> result::Result<(), usize> {
match self.workers[index].as_mut() { match self.workers[index].as_mut() {
None => { return Ok(()); }, None => {
Some(ref mut w) => { return Ok(());
match w.poll() { }
Ok(AsyncStatus::NoUpdate) => { Some(ref mut w) => match w.poll() {
return Err(0); Ok(AsyncStatus::NoUpdate) => {
}, return Err(0);
Ok(AsyncStatus::Finished) => { }
}, Ok(AsyncStatus::Finished) => {}
Ok(AsyncStatus::ProgressReport(n)) => { Ok(AsyncStatus::ProgressReport(n)) => {
return Err(n); return Err(n);
}, }
a => { a => {
eprintln!("{:?}", a); eprintln!("{:?}", a);
return Err(0); return Err(0);
}
} }
}, },
}; };
let m = self.workers[index].take().unwrap().extract(); let m = self.workers[index].take().unwrap().extract();
self.load_mailbox(index, m); self.load_mailbox(index, m);
self.workers[index] = None; self.workers[index] = None;
Ok(()) Ok(())
} }
} }
impl Index<usize> for Account { impl Index<usize> for Account {
type Output = Result<Mailbox>; type Output = Result<Mailbox>;
fn index(&self, index: usize) -> &Result<Mailbox> { fn index(&self, index: usize) -> &Result<Mailbox> {
&self.folders[index].as_ref().expect("BUG: Requested mailbox that is not yet available.") &self.folders[index]
.as_ref()
.expect("BUG: Requested mailbox that is not yet available.")
} }
} }
/// Will panic if mailbox hasn't loaded, ask `status()` first. /// Will panic if mailbox hasn't loaded, ask `status()` first.
impl IndexMut<usize> for Account { impl IndexMut<usize> for Account {
fn index_mut(&mut self, index: usize) -> &mut Result<Mailbox> { fn index_mut(&mut self, index: usize) -> &mut Result<Mailbox> {
self.folders[index].as_mut().expect("BUG: Requested mailbox that is not yet available.") self.folders[index]
.as_mut()
.expect("BUG: Requested mailbox that is not yet available.")
} }
} }

View File

@ -19,11 +19,11 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use async::*;
use conf::Folder; use conf::Folder;
use error::Result; use error::Result;
use async::*;
use mailbox::backends::{MailBackend, RefreshEventConsumer}; use mailbox::backends::{MailBackend, RefreshEventConsumer};
use mailbox::email::{Envelope, }; use mailbox::email::Envelope;
/// `BackendOp` implementor for Imap /// `BackendOp` implementor for Imap
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
@ -35,7 +35,6 @@ impl ImapOp {
} }
} }
/* /*
impl BackendOp for ImapOp { impl BackendOp for ImapOp {

View File

@ -40,10 +40,10 @@ use std::sync::mpsc::channel;
use std::thread; use std::thread;
extern crate crossbeam; extern crate crossbeam;
use memmap::{Mmap, Protection}; use memmap::{Mmap, Protection};
use std::path::PathBuf;
use std::collections::hash_map::DefaultHasher; use std::collections::hash_map::DefaultHasher;
use std::hash::Hasher;
use std::fs; use std::fs;
use std::hash::Hasher;
use std::path::PathBuf;
/// `BackendOp` implementor for Maildir /// `BackendOp` implementor for Maildir
#[derive(Debug, Default)] #[derive(Debug, Default)]
@ -115,7 +115,10 @@ impl BackendOp for MaildirOp {
flag flag
} }
fn set_flag(&mut self, envelope: &mut Envelope, f: &Flag) -> Result<()> { fn set_flag(&mut self, envelope: &mut Envelope, f: &Flag) -> Result<()> {
let idx: usize = self.path.rfind(":2,").ok_or(MeliError::new(format!("Invalid email filename: {:?}", self)))? + 3; let idx: usize = self.path.rfind(":2,").ok_or(MeliError::new(format!(
"Invalid email filename: {:?}",
self
)))? + 3;
let mut new_name: String = self.path[..idx].to_string(); let mut new_name: String = self.path[..idx].to_string();
let mut flags = self.fetch_flags(); let mut flags = self.fetch_flags();
flags.toggle(*f); flags.toggle(*f);
@ -139,13 +142,9 @@ impl BackendOp for MaildirOp {
} }
fs::rename(&self.path, &new_name)?; fs::rename(&self.path, &new_name)?;
envelope.set_operation_token( envelope.set_operation_token(Box::new(BackendOpGenerator::new(Box::new(move || {
Box::new( Box::new(MaildirOp::new(new_name.clone()))
BackendOpGenerator::new( }))));
Box::new( move || Box::new(MaildirOp::new(new_name.clone())))
)
)
);
Ok(()) Ok(())
} }
} }
@ -189,10 +188,8 @@ impl MailBackend for MaildirType {
let mut hasher = DefaultHasher::new(); let mut hasher = DefaultHasher::new();
hasher.write(path.as_bytes()); hasher.write(path.as_bytes());
sender.send(RefreshEvent { sender.send(RefreshEvent {
folder: format!( folder: format!("{}", path),
"{}", path hash: hasher.finish(),
),
hash: hasher.finish(),
}); });
} }
_ => {} _ => {}
@ -201,7 +198,7 @@ impl MailBackend for MaildirType {
} }
} }
}) })
.unwrap(); .unwrap();
} }
} }
@ -219,9 +216,9 @@ impl MaildirType {
p.push(d); p.push(d);
if !p.is_dir() { if !p.is_dir() {
return Err(MeliError::new(format!( return Err(MeliError::new(format!(
"{} is not a valid maildir folder", "{} is not a valid maildir folder",
path path
))); )));
} }
p.pop(); p.pop();
} }
@ -236,7 +233,7 @@ impl MaildirType {
thread::Builder::new() thread::Builder::new()
.name(format!("parsing {:?}", folder)) .name(format!("parsing {:?}", folder))
.spawn(move || { .spawn(move || {
MaildirType::is_valid(&folder)?; MaildirType::is_valid(&folder)?;
let path = folder.path(); let path = folder.path();
let mut path = PathBuf::from(path); let mut path = PathBuf::from(path);
@ -264,21 +261,24 @@ impl MaildirType {
let mut tx = tx.clone(); let mut tx = tx.clone();
let s = scope.spawn(move || { let s = scope.spawn(move || {
let len = chunk.len(); let len = chunk.len();
let size = if len <= 100 { 100 } else { (len / 100) * 100}; let size = if len <= 100 { 100 } else { (len / 100) * 100 };
let mut local_r: Vec<Envelope> = Vec::with_capacity(chunk.len()); let mut local_r: Vec<
Envelope,
> = Vec::with_capacity(chunk.len());
for c in chunk.chunks(size) { for c in chunk.chunks(size) {
let len = c.len(); let len = c.len();
for e in c { for e in c {
let e_copy = e.to_string(); let e_copy = e.to_string();
if let Some(mut e) = if let Some(mut e) = Envelope::from_token(Box::new(
Envelope::from_token(Box::new(BackendOpGenerator::new(Box::new( BackendOpGenerator::new(Box::new(move || {
move || Box::new(MaildirOp::new(e_copy.clone())), Box::new(MaildirOp::new(e_copy.clone()))
)))) { })),
if e.populate_headers().is_err() { )) {
continue; if e.populate_headers().is_err() {
} continue;
local_r.push(e);
} }
local_r.push(e);
}
} }
tx.send(AsyncStatus::ProgressReport(len)); tx.send(AsyncStatus::ProgressReport(len));
} }
@ -294,8 +294,9 @@ impl MaildirType {
} }
tx.send(AsyncStatus::Finished); tx.send(AsyncStatus::Finished);
Ok(r) Ok(r)
}).unwrap() })
}; .unwrap()
};
w.build(handle) w.build(handle)
} }
} }

View File

@ -19,11 +19,15 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
/*!
* https://wiki2.dovecot.org/MailboxFormat/mbox
*/
use async::*;
use conf::Folder; use conf::Folder;
use error::Result; use error::Result;
use async::*;
use mailbox::backends::{MailBackend, RefreshEventConsumer}; use mailbox::backends::{MailBackend, RefreshEventConsumer};
use mailbox::email::{Envelope, }; use mailbox::email::Envelope;
/// `BackendOp` implementor for Mbox /// `BackendOp` implementor for Mbox
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]

View File

@ -22,9 +22,9 @@ pub mod imap;
pub mod maildir; pub mod maildir;
pub mod mbox; pub mod mbox;
use async::*;
use conf::Folder; use conf::Folder;
use error::Result; use error::Result;
use async::*;
use mailbox::backends::imap::ImapType; use mailbox::backends::imap::ImapType;
use mailbox::backends::maildir::MaildirType; use mailbox::backends::maildir::MaildirType;
use mailbox::backends::mbox::MboxType; use mailbox::backends::mbox::MboxType;

View File

@ -47,7 +47,9 @@ impl Display for MultipartType {
MultipartType::Mixed => write!(f, "multipart/mixed"), MultipartType::Mixed => write!(f, "multipart/mixed"),
MultipartType::Alternative => write!(f, "multipart/alternative"), MultipartType::Alternative => write!(f, "multipart/alternative"),
MultipartType::Digest => write!(f, "multipart/digest"), MultipartType::Digest => write!(f, "multipart/digest"),
MultipartType::Unsupported { tag: ref t } => write!(f, "multipart/{}", String::from_utf8_lossy(t)), MultipartType::Unsupported { tag: ref t } => {
write!(f, "multipart/{}", String::from_utf8_lossy(t))
}
} }
} }
} }
@ -136,7 +138,7 @@ impl AttachmentBuilder {
let mut boundary = None; let mut boundary = None;
for (n, v) in params { for (n, v) in params {
if n.eq_ignore_ascii_case(b"boundary") { if n.eq_ignore_ascii_case(b"boundary") {
let mut vec: Vec<u8> = Vec::with_capacity(v.len()+4); let mut vec: Vec<u8> = Vec::with_capacity(v.len() + 4);
vec.extend_from_slice(b"--"); vec.extend_from_slice(b"--");
vec.extend(v); vec.extend(v);
vec.extend_from_slice(b"--"); vec.extend_from_slice(b"--");
@ -148,9 +150,7 @@ impl AttachmentBuilder {
self.content_type.0 = ContentType::Multipart { self.content_type.0 = ContentType::Multipart {
boundary: boundary.unwrap(), boundary: boundary.unwrap(),
}; };
self.content_type.1 = ContentSubType::Other { self.content_type.1 = ContentSubType::Other { tag: cst.into() };
tag: cst.into(),
};
} else if ct.eq_ignore_ascii_case(b"text") { } else if ct.eq_ignore_ascii_case(b"text") {
self.content_type.0 = ContentType::Text; self.content_type.0 = ContentType::Text;
if !cst.eq_ignore_ascii_case(b"plain") { if !cst.eq_ignore_ascii_case(b"plain") {
@ -203,25 +203,23 @@ impl AttachmentBuilder {
.as_bytes(), .as_bytes(),
) { ) {
Ok(ref s) => { Ok(ref s) => {
let s:Vec<u8> = s.clone(); let s: Vec<u8> = s.clone();
{ {
let slice = &s[..]; let slice = &s[..];
if slice.find(b"\r\n").is_some() { if slice.find(b"\r\n").is_some() {
s.replace(b"\r\n", b"\n"); s.replace(b"\r\n", b"\n");
}
} }
} s
s
} }
_ => self.raw.clone() _ => self.raw.clone(),
}, },
ContentTransferEncoding::QuotedPrintable => parser::quoted_printable_text(&self.raw) ContentTransferEncoding::QuotedPrintable => parser::quoted_printable_text(&self.raw)
.to_full_result() .to_full_result()
.unwrap(), .unwrap(),
ContentTransferEncoding::_7Bit ContentTransferEncoding::_7Bit
| ContentTransferEncoding::_8Bit | ContentTransferEncoding::_8Bit
| ContentTransferEncoding::Other { .. } => { | ContentTransferEncoding::Other { .. } => self.raw.clone(),
self.raw.clone()
}
} }
} }
pub fn build(self) -> Attachment { pub fn build(self) -> Attachment {
@ -235,7 +233,7 @@ impl AttachmentBuilder {
b"mixed" => MultipartType::Mixed, b"mixed" => MultipartType::Mixed,
b"alternative" => MultipartType::Alternative, b"alternative" => MultipartType::Alternative,
b"digest" => MultipartType::Digest, b"digest" => MultipartType::Digest,
_ => MultipartType::Unsupported { tag:tag.clone() }, _ => MultipartType::Unsupported { tag: tag.clone() },
}, },
_ => panic!(), _ => panic!(),
}; };

View File

@ -25,19 +25,19 @@
pub mod attachments; pub mod attachments;
pub mod parser; pub mod parser;
use parser::BytesExt;
pub use self::attachments::*; pub use self::attachments::*;
use error::{MeliError, Result}; use error::{MeliError, Result};
use mailbox::backends::BackendOpGenerator; use mailbox::backends::BackendOpGenerator;
use parser::BytesExt;
use std::borrow::Cow;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::collections::hash_map::DefaultHasher;
use std::fmt; use std::fmt;
use std::hash::Hasher;
use std::option::Option; use std::option::Option;
use std::string::String; use std::string::String;
use std::sync::Arc; use std::sync::Arc;
use std::borrow::Cow;
use std::collections::hash_map::DefaultHasher;
use std::hash::Hasher;
use chrono; use chrono;
use chrono::TimeZone; use chrono::TimeZone;
@ -257,7 +257,7 @@ impl Envelope {
Some(e) Some(e)
} }
pub fn set_operation_token(&mut self, operation_token: Box<BackendOpGenerator>) { pub fn set_operation_token(&mut self, operation_token: Box<BackendOpGenerator>) {
self.operation_token= Arc::new(operation_token); self.operation_token = Arc::new(operation_token);
} }
pub fn populate_headers(&mut self) -> Result<()> { pub fn populate_headers(&mut self) -> Result<()> {
@ -373,7 +373,10 @@ impl Envelope {
} }
pub fn bytes(&self) -> Vec<u8> { pub fn bytes(&self) -> Vec<u8> {
let mut operation = self.operation_token.generate(); let mut operation = self.operation_token.generate();
operation.as_bytes().map(|v| v.into()).unwrap_or_else(|_| Vec::new()) operation
.as_bytes()
.map(|v| v.into())
.unwrap_or_else(|_| Vec::new())
} }
pub fn body(&self) -> Attachment { pub fn body(&self) -> Attachment {
let mut operation = self.operation_token.generate(); let mut operation = self.operation_token.generate();
@ -385,7 +388,7 @@ impl Envelope {
eprintln!("error in parsing mail\n{}", operation.description()); eprintln!("error in parsing mail\n{}", operation.description());
let error_msg = b"Mail cannot be shown because of errors."; let error_msg = b"Mail cannot be shown because of errors.";
let mut builder = AttachmentBuilder::new(error_msg); let mut builder = AttachmentBuilder::new(error_msg);
return builder.build() return builder.build();
} }
}; };
let mut builder = AttachmentBuilder::new(body); let mut builder = AttachmentBuilder::new(body);
@ -513,7 +516,8 @@ impl Envelope {
} }
pub fn references(&self) -> Vec<&MessageID> { pub fn references(&self) -> Vec<&MessageID> {
match self.references { match self.references {
Some(ref s) => s.refs Some(ref s) => s
.refs
.iter() .iter()
.fold(Vec::with_capacity(s.refs.len()), |mut acc, x| { .fold(Vec::with_capacity(s.refs.len()), |mut acc, x| {
acc.push(x); acc.push(x);

View File

@ -57,7 +57,8 @@ impl BytesExt for [u8] {
} }
// https://stackoverflow.com/a/35907071 // https://stackoverflow.com/a/35907071
fn find(&self, needle: &[u8]) -> Option<usize> { fn find(&self, needle: &[u8]) -> Option<usize> {
self.windows(needle.len()).position(|window| window == needle) self.windows(needle.len())
.position(|window| window == needle)
} }
fn replace(&self, from: &[u8], to: &[u8]) -> Vec<u8> { fn replace(&self, from: &[u8], to: &[u8]) -> Vec<u8> {
let mut ret = self.to_vec(); let mut ret = self.to_vec();
@ -68,7 +69,6 @@ impl BytesExt for [u8] {
} }
} }
fn quoted_printable_byte(input: &[u8]) -> IResult<&[u8], u8> { fn quoted_printable_byte(input: &[u8]) -> IResult<&[u8], u8> {
if input.len() < 3 { if input.len() < 3 {
IResult::Incomplete(Needed::Size(1)) IResult::Incomplete(Needed::Size(1))
@ -107,9 +107,9 @@ fn header_value(input: &[u8]) -> IResult<&[u8], &[u8]> {
let input_len = input.len(); let input_len = input.len();
for (i, x) in input.iter().enumerate() { for (i, x) in input.iter().enumerate() {
if *x == b'\n' { if *x == b'\n' {
if (i + 1) < input_len && input[i + 1] != b' ' && input[i + 1] != b'\t' { if ((i + 1) < input_len && input[i + 1] != b' ' && input[i + 1] != b'\t')
return IResult::Done(&input[(i + 1)..], &input[0..i]); || i + 1 == input_len
} else if i + 1 == input_len { {
return IResult::Done(&input[(i + 1)..], &input[0..i]); return IResult::Done(&input[(i + 1)..], &input[0..i]);
} }
} }
@ -281,7 +281,8 @@ named!(
acc += x.len(); acc += x.len();
acc acc
}); });
let bytes = list.iter() let bytes = list
.iter()
.fold(Vec::with_capacity(list_len), |mut acc, x| { .fold(Vec::with_capacity(list_len), |mut acc, x| {
acc.append(&mut x.clone()); acc.append(&mut x.clone());
acc acc
@ -636,8 +637,9 @@ pub fn date(input: &[u8]) -> Option<chrono::DateTime<chrono::FixedOffset>> {
let parsed_result = phrase(&eat_comments(input)) let parsed_result = phrase(&eat_comments(input))
.to_full_result() .to_full_result()
.unwrap() .unwrap()
.replace(b"-",b"+"); .replace(b"-", b"+");
chrono::DateTime::parse_from_rfc2822(String::from_utf8_lossy(parsed_result.trim()).as_ref()).ok() chrono::DateTime::parse_from_rfc2822(String::from_utf8_lossy(parsed_result.trim()).as_ref())
.ok()
} }
#[test] #[test]
@ -712,10 +714,11 @@ fn test_attachments() {
named!( named!(
content_type_parameter<(&[u8], &[u8])>, content_type_parameter<(&[u8], &[u8])>,
do_parse!( do_parse!(
tag!(";") >> tag!(";") >> name: terminated!(ws!(take_until!("=")), tag!("="))
name: terminated!(ws!(take_until!("=")) , tag!("=")) >> >> value:
value: ws!(alt_complete!( delimited!(tag!("\""), take_until!("\""), tag!("\"")) | is_not!(";"))) >> ws!(alt_complete!(
({ (name, value) }) delimited!(tag!("\""), take_until!("\""), tag!("\"")) | is_not!(";")
)) >> ({ (name, value) })
) )
); );

View File

@ -385,7 +385,8 @@ pub fn build_threads(
if indentation > 0 && thread.has_message() { if indentation > 0 && thread.has_message() {
let subject = collection[thread.message().unwrap()].subject(); let subject = collection[thread.message().unwrap()].subject();
if subject == root_subject if subject == root_subject
|| subject.starts_with("Re: ") && subject.as_ref().ends_with(root_subject.as_ref()) || subject.starts_with("Re: ")
&& subject.as_ref().ends_with(root_subject.as_ref())
{ {
threads[i].set_show_subject(false); threads[i].set_show_subject(false);
} }

View File

@ -63,7 +63,7 @@ fn make_input_thread(
UIMode::Fork, UIMode::Fork,
))); )));
}, },
rx, &rx,
) )
}) })
.unwrap() .unwrap()
@ -134,11 +134,11 @@ fn main() {
receiver.recv() -> r => { receiver.recv() -> r => {
match r.unwrap() { match r.unwrap() {
ThreadEvent::Input(Key::Ctrl('z')) => { ThreadEvent::Input(Key::Ctrl('z')) => {
state.to_main_screen(); state.switch_to_main_screen();
//_thread_handler.join().expect("Couldn't join on the associated thread"); //_thread_handler.join().expect("Couldn't join on the associated thread");
let self_pid = nix::unistd::Pid::this(); let self_pid = nix::unistd::Pid::this();
nix::sys::signal::kill(self_pid, nix::sys::signal::Signal::SIGSTOP).unwrap(); nix::sys::signal::kill(self_pid, nix::sys::signal::Signal::SIGSTOP).unwrap();
state.to_alternate_screen(); state.switch_to_alternate_screen();
_thread_handler = make_input_thread(sender.clone(), rx.clone()); _thread_handler = make_input_thread(sender.clone(), rx.clone());
// BUG: thread sends input event after one received key // BUG: thread sends input event after one received key
state.update_size(); state.update_size();

View File

@ -1,8 +1,28 @@
/*
* 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 super::*;
const MAX_COLS: usize = 500; const MAX_COLS: usize = 500;
/// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the `Envelope` content in a /// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the `Envelope` content in a
/// `MailView`. /// `MailView`.
pub struct MailListing { pub struct MailListing {
@ -21,6 +41,12 @@ pub struct MailListing {
view: Option<MailView>, view: Option<MailView>,
} }
impl Default for MailListing {
fn default() -> Self {
Self::new()
}
}
impl MailListing { impl MailListing {
/// Helper function to format entry strings for MailListing */ /// Helper function to format entry strings for MailListing */
/* TODO: Make this configurable */ /* TODO: Make this configurable */
@ -67,11 +93,9 @@ impl MailListing {
// Get mailbox as a reference. // Get mailbox as a reference.
// //
loop { loop {
match context.accounts[self.cursor_pos.0].status(self.cursor_pos.1) { // TODO: Show progress visually
Ok(()) => { break; }, if let Ok(()) = context.accounts[self.cursor_pos.0].status(self.cursor_pos.1) {
Err(_) => { break;
// TODO: Show progress visually
}
} }
} }
let mailbox = &mut context.accounts[self.cursor_pos.0][self.cursor_pos.1] let mailbox = &mut context.accounts[self.cursor_pos.0][self.cursor_pos.1]
@ -92,7 +116,7 @@ impl MailListing {
Color::Default, Color::Default,
((0, 0), (MAX_COLS - 1, 0)), ((0, 0), (MAX_COLS - 1, 0)),
true, true,
); );
self.content = content; self.content = content;
return; return;
} }
@ -101,31 +125,29 @@ impl MailListing {
if threaded { if threaded {
let mut indentations: Vec<bool> = Vec::with_capacity(6); let mut indentations: Vec<bool> = Vec::with_capacity(6);
let mut thread_idx = 0; // needed for alternate thread colors let mut thread_idx = 0; // needed for alternate thread colors
/* Draw threaded view. */ /* Draw threaded view. */
let mut local_collection: Vec<usize> = mailbox.threaded_collection.clone(); let mut local_collection: Vec<usize> = mailbox.threaded_collection.clone();
let mut threads: Vec<&Container> = mailbox.threads.iter().map(|v| v).collect(); let threads: &Vec<Container> = &mailbox.threads;
local_collection.sort_by(|a, b| { local_collection.sort_by(|a, b| match self.sort {
match self.sort { (SortField::Date, SortOrder::Desc) => {
(SortField::Date, SortOrder::Desc) => { mailbox.thread(*b).date().cmp(&mailbox.thread(*a).date())
mailbox.thread(*b).date().cmp(&mailbox.thread(*a).date()) }
}, (SortField::Date, SortOrder::Asc) => {
(SortField::Date, SortOrder::Asc) => { mailbox.thread(*a).date().cmp(&mailbox.thread(*b).date())
mailbox.thread(*a).date().cmp(&mailbox.thread(*b).date()) }
}, (SortField::Subject, SortOrder::Desc) => {
(SortField::Subject, SortOrder::Desc) => { let a = mailbox.thread(*a);
let a = mailbox.thread(*a); let b = mailbox.thread(*b);
let b = mailbox.thread(*b); let ma = &mailbox.collection[*a.message().as_ref().unwrap()];
let ma = &mailbox.collection[*a.message().as_ref().unwrap()]; let mb = &mailbox.collection[*b.message().as_ref().unwrap()];
let mb = &mailbox.collection[*b.message().as_ref().unwrap()]; ma.subject().cmp(&mb.subject())
ma.subject().cmp(&mb.subject()) }
}, (SortField::Subject, SortOrder::Asc) => {
(SortField::Subject, SortOrder::Asc) => { let a = mailbox.thread(*a);
let a = mailbox.thread(*a); let b = mailbox.thread(*b);
let b = mailbox.thread(*b); let ma = &mailbox.collection[*a.message().as_ref().unwrap()];
let ma = &mailbox.collection[*a.message().as_ref().unwrap()]; let mb = &mailbox.collection[*b.message().as_ref().unwrap()];
let mb = &mailbox.collection[*b.message().as_ref().unwrap()]; mb.subject().cmp(&ma.subject())
mb.subject().cmp(&ma.subject())
},
} }
}); });
let mut iter = local_collection.iter().enumerate().peekable(); let mut iter = local_collection.iter().enumerate().peekable();
@ -137,14 +159,14 @@ impl MailListing {
.count(); .count();
/* This is just a desugared for loop so that we can use .peek() */ /* This is just a desugared for loop so that we can use .peek() */
while let Some((idx, i)) = iter.next() { while let Some((idx, i)) = iter.next() {
let container = threads[*i]; let container = &threads[*i];
let indentation = container.indentation(); let indentation = container.indentation();
if indentation == 0 { if indentation == 0 {
thread_idx += 1; thread_idx += 1;
} }
assert!(container.has_message() == true); assert!(container.has_message());
match iter.peek() { match iter.peek() {
Some(&(_, x)) if threads[*x].indentation() == indentation => { Some(&(_, x)) if threads[*x].indentation() == indentation => {
indentations.pop(); indentations.pop();
@ -180,13 +202,13 @@ impl MailListing {
container, container,
&indentations, &indentations,
len, len,
), ),
&mut content, &mut content,
fg_color, fg_color,
bg_color, bg_color,
((0, idx), (MAX_COLS - 1, idx)), ((0, idx), (MAX_COLS - 1, idx)),
false, false,
); );
for x in x..MAX_COLS { for x in x..MAX_COLS {
content[(x, idx)].set_ch(' '); content[(x, idx)].set_ch(' ');
content[(x, idx)].set_bg(bg_color); content[(x, idx)].set_bg(bg_color);
@ -236,7 +258,7 @@ impl MailListing {
bg_color, bg_color,
((0, y), (MAX_COLS - 1, y)), ((0, y), (MAX_COLS - 1, y)),
false, false,
); );
for x in x..MAX_COLS { for x in x..MAX_COLS {
content[(x, y)].set_ch(' '); content[(x, y)].set_ch(' ');
@ -269,15 +291,19 @@ impl MailListing {
} else { } else {
Color::Default Color::Default
}; };
let bg_color = let bg_color = if !envelope.is_seen() {
if !envelope.is_seen() { Color::Byte(251)
Color::Byte(251) } else if idx % 2 == 0 {
} else if idx % 2 == 0 { Color::Byte(236)
Color::Byte(236) } else {
} else { Color::Default
Color::Default };
}; change_colors(
change_colors(&mut self.content, ((0, idx), (MAX_COLS-1, idx)), fg_color, bg_color); &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) { fn highlight_line(&self, grid: &mut CellBuffer, area: Area, idx: usize, context: &Context) {
@ -301,14 +327,12 @@ impl MailListing {
}; };
let bg_color = if self.cursor_pos.2 == idx { let bg_color = if self.cursor_pos.2 == idx {
Color::Byte(246) Color::Byte(246)
} else if !envelope.is_seen() {
Color::Byte(251)
} else if idx % 2 == 0 {
Color::Byte(236)
} else { } else {
if !envelope.is_seen() { Color::Default
Color::Byte(251)
} else if idx % 2 == 0 {
Color::Byte(236)
} else {
Color::Default
}
}; };
change_colors(grid, area, fg_color, bg_color); change_colors(grid, area, fg_color, bg_color);
} }
@ -337,7 +361,7 @@ impl MailListing {
if self.cursor_pos.2 != self.new_cursor_pos.2 && prev_page_no == page_no { if self.cursor_pos.2 != self.new_cursor_pos.2 && prev_page_no == page_no {
let old_cursor_pos = self.cursor_pos; let old_cursor_pos = self.cursor_pos;
self.cursor_pos = self.new_cursor_pos; self.cursor_pos = self.new_cursor_pos;
for idx in [old_cursor_pos.2, self.new_cursor_pos.2].iter() { for idx in &[old_cursor_pos.2, self.new_cursor_pos.2] {
if *idx >= self.length { if *idx >= self.length {
continue; //bounds check continue; //bounds check
} }
@ -370,8 +394,6 @@ impl MailListing {
context, context,
); );
context.dirty_areas.push_back(area); context.dirty_areas.push_back(area);
} }
fn make_thread_entry( fn make_thread_entry(
@ -379,7 +401,7 @@ impl MailListing {
idx: usize, idx: usize,
indent: usize, indent: usize,
container: &Container, container: &Container,
indentations: &Vec<bool>, indentations: &[bool],
idx_width: usize, idx_width: usize,
) -> String { ) -> String {
let has_sibling = container.has_sibling(); let has_sibling = container.has_sibling();
@ -508,10 +530,9 @@ impl Component for MailListing {
} }
{ {
/* TODO: Move the box drawing business in separate functions */ /* TODO: Move the box drawing business in separate functions */
if get_x(upper_left) > 0 { if get_x(upper_left) > 0 && grid[(get_x(upper_left) - 1, mid)].ch() == VERT_BOUNDARY
if grid[(get_x(upper_left) - 1, mid)].ch() == VERT_BOUNDARY { {
grid[(get_x(upper_left) - 1, mid)].set_ch(LIGHT_VERTICAL_AND_RIGHT); grid[(get_x(upper_left) - 1, mid)].set_ch(LIGHT_VERTICAL_AND_RIGHT);
}
} }
for i in get_x(upper_left)..=get_x(bottom_right) { for i in get_x(upper_left)..=get_x(bottom_right) {
@ -524,17 +545,18 @@ impl Component for MailListing {
// TODO: Make headers view configurable // TODO: Make headers view configurable
if !self.dirty { if !self.dirty {
self.view if let Some(v) = self.view.as_mut() {
.as_mut() v.draw(grid, (set_y(upper_left, mid + 1), bottom_right), context);
.map(|v| v.draw(grid, (set_y(upper_left, mid + 1), bottom_right), context)); }
return; return;
} }
self.view = Some(MailView::new(self.cursor_pos, None, None)); self.view = Some(MailView::new(self.cursor_pos, None, None));
self.view self.view.as_mut().unwrap().draw(
.as_mut() grid,
.map(|v| v.draw(grid, (set_y(upper_left, mid + 1), bottom_right), context)); (set_y(upper_left, mid + 1), bottom_right),
context,
);
self.dirty = false; self.dirty = false;
} }
} }
fn process_event(&mut self, event: &UIEvent, context: &mut Context) { fn process_event(&mut self, event: &UIEvent, context: &mut Context) {
@ -551,11 +573,11 @@ impl Component for MailListing {
self.dirty = true; self.dirty = true;
} }
} }
UIEventType::Input(Key::Char('\n')) if self.unfocused == false => { UIEventType::Input(Key::Char('\n')) if !self.unfocused => {
self.unfocused = true; self.unfocused = true;
self.dirty = true; self.dirty = true;
} }
UIEventType::Input(Key::Char('m')) if self.unfocused == false => { UIEventType::Input(Key::Char('m')) if !self.unfocused => {
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
/* Kill input thread so that spawned command can be sole receiver of stdin */ /* Kill input thread so that spawned command can be sole receiver of stdin */
{ {
@ -602,7 +624,7 @@ impl Component for MailListing {
}); });
return; return;
} }
UIEventType::Input(Key::Char('i')) if self.unfocused == true => { UIEventType::Input(Key::Char('i')) if self.unfocused => {
self.unfocused = false; self.unfocused = false;
self.dirty = true; self.dirty = true;
self.view = None; self.view = None;
@ -683,20 +705,20 @@ impl Component for MailListing {
self.refresh_mailbox(context); self.refresh_mailbox(context);
self.dirty = true; self.dirty = true;
return; return;
}, }
Action::ViewMailbox(idx) => { Action::ViewMailbox(idx) => {
self.new_cursor_pos.1 = *idx; self.new_cursor_pos.1 = *idx;
self.dirty = true; self.dirty = true;
self.refresh_mailbox(context); self.refresh_mailbox(context);
return; return;
}, }
Action::Sort(field, order) => { Action::Sort(field, order) => {
self.sort = (field.clone(), order.clone()); self.sort = (field.clone(), order.clone());
self.dirty = true; self.dirty = true;
self.refresh_mailbox(context); self.refresh_mailbox(context);
return; return;
}, }
_ => {}, _ => {}
}, },
_ => {} _ => {}
} }

View File

@ -1,3 +1,24 @@
/*
* 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/>.
*/
/*! Entities that handle Mail specific functions. /*! Entities that handle Mail specific functions.
*/ */
use super::*; use super::*;
@ -23,7 +44,7 @@ pub struct AccountMenu {
} }
impl AccountMenu { impl AccountMenu {
pub fn new(accounts: &Vec<Account>) -> Self { pub fn new(accounts: &[Account]) -> Self {
let accounts = accounts let accounts = accounts
.iter() .iter()
.enumerate() .enumerate()
@ -40,12 +61,18 @@ impl AccountMenu {
}) })
.collect(); .collect();
AccountMenu { AccountMenu {
accounts: accounts, accounts,
dirty: true, dirty: true,
cursor: None, cursor: None,
} }
} }
fn print_account(&self, grid: &mut CellBuffer, area: Area, a: &AccountMenuEntry, context: &mut Context) -> usize { fn print_account(
&self,
grid: &mut CellBuffer,
area: Area,
a: &AccountMenuEntry,
context: &mut Context,
) -> usize {
if !is_valid_area!(area) { if !is_valid_area!(area) {
eprintln!("BUG: invalid area in print_account"); eprintln!("BUG: invalid area in print_account");
} }
@ -70,7 +97,7 @@ impl AccountMenu {
let mut inc = 0; let mut inc = 0;
let mut depth = String::from(""); let mut depth = String::from("");
let mut s = String::from(format!("{}\n", a.name)); let mut s = format!("{}\n", a.name);
fn pop(depth: &mut String) { fn pop(depth: &mut String) {
depth.pop(); depth.pop();
depth.pop(); depth.pop();
@ -82,24 +109,33 @@ impl AccountMenu {
fn print( fn print(
root: usize, root: usize,
parents: &Vec<Option<usize>>, parents: &[Option<usize>],
depth: &mut String, depth: &mut String,
entries: &Vec<(usize, Folder)>, entries: &[(usize, Folder)],
s: &mut String, s: &mut String,
inc: &mut usize, inc: &mut usize,
index: usize, //account index index: usize, //account index
context: &mut Context, context: &mut Context,
) -> () { ) -> () {
let len = s.len(); let len = s.len();
match context.accounts[index].status(root) { match context.accounts[index].status(root) {
Ok(()) => {}, Ok(()) => {}
Err(_) => { Err(_) => {
return; return;
// TODO: Show progress visually // TODO: Show progress visually
} }
} }
let count = context.accounts[index][root].as_ref().unwrap().collection.iter().filter(|e| !e.is_seen()).count(); let count = context.accounts[index][root]
s.insert_str(len, &format!("{} {} {}\n ", *inc, &entries[root].1.name(), count)); .as_ref()
.unwrap()
.collection
.iter()
.filter(|e| !e.is_seen())
.count();
s.insert_str(
len,
&format!("{} {} {}\n ", *inc, &entries[root].1.name(), count),
);
*inc += 1; *inc += 1;
let children_no = entries[root].1.children().len(); let children_no = entries[root].1.children().len();
for (idx, child) in entries[root].1.children().iter().enumerate() { for (idx, child) in entries[root].1.children().iter().enumerate() {
@ -111,7 +147,9 @@ impl AccountMenu {
} }
} }
for r in roots { for r in roots {
print(r, &parents, &mut depth, &a.entries, &mut s, &mut inc, a.index, context); print(
r, &parents, &mut depth, &a.entries, &mut s, &mut inc, a.index, context,
);
} }
let lines: Vec<&str> = s.lines().collect(); let lines: Vec<&str> = s.lines().collect();
@ -125,9 +163,9 @@ impl AccountMenu {
break; break;
} }
let s = if idx == lines_len - 2 { let s = if idx == lines_len - 2 {
format!("{}", lines[idx].replace("", "")) lines[idx].replace("", "")
} else { } else {
format!("{}", lines[idx]) lines[idx].to_string()
}; };
let (color_fg, color_bg) = if highlight { let (color_fg, color_bg) = if highlight {
if idx > 1 && self.cursor.unwrap().1 == idx - 2 { if idx > 1 && self.cursor.unwrap().1 == idx - 2 {

View File

@ -1,3 +1,24 @@
/*
* 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 super::*;
use linkify::{Link, LinkFinder}; use linkify::{Link, LinkFinder};
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
@ -40,9 +61,9 @@ impl MailView {
subview: Option<Box<MailView>>, subview: Option<Box<MailView>>,
) -> Self { ) -> Self {
MailView { MailView {
coordinates: coordinates, coordinates,
pager: pager, pager,
subview: subview, subview,
dirty: true, dirty: true,
mode: ViewMode::Normal, mode: ViewMode::Normal,
@ -73,9 +94,7 @@ impl Component for MailView {
if self.mode == ViewMode::Raw { if self.mode == ViewMode::Raw {
clear_area(grid, area); clear_area(grid, area);
context context.dirty_areas.push_back(area);
.dirty_areas
.push_back(area);
(envelope_idx, get_y(upper_left) - 1) (envelope_idx, get_y(upper_left) - 1)
} else { } else {
let (x, y) = write_string_to_grid( let (x, y) = write_string_to_grid(
@ -85,7 +104,7 @@ impl Component for MailView {
Color::Default, Color::Default,
area, area,
true, true,
); );
for x in x..=get_x(bottom_right) { for x in x..=get_x(bottom_right) {
grid[(x, y)].set_ch(' '); grid[(x, y)].set_ch(' ');
grid[(x, y)].set_bg(Color::Default); grid[(x, y)].set_bg(Color::Default);
@ -98,7 +117,7 @@ impl Component for MailView {
Color::Default, Color::Default,
(set_y(upper_left, y + 1), bottom_right), (set_y(upper_left, y + 1), bottom_right),
true, true,
); );
for x in x..=get_x(bottom_right) { for x in x..=get_x(bottom_right) {
grid[(x, y)].set_ch(' '); grid[(x, y)].set_ch(' ');
grid[(x, y)].set_bg(Color::Default); grid[(x, y)].set_bg(Color::Default);
@ -111,7 +130,7 @@ impl Component for MailView {
Color::Default, Color::Default,
(set_y(upper_left, y + 1), bottom_right), (set_y(upper_left, y + 1), bottom_right),
true, true,
); );
for x in x..=get_x(bottom_right) { for x in x..=get_x(bottom_right) {
grid[(x, y)].set_ch(' '); grid[(x, y)].set_ch(' ');
grid[(x, y)].set_bg(Color::Default); grid[(x, y)].set_bg(Color::Default);
@ -124,7 +143,7 @@ impl Component for MailView {
Color::Default, Color::Default,
(set_y(upper_left, y + 1), bottom_right), (set_y(upper_left, y + 1), bottom_right),
true, true,
); );
for x in x..=get_x(bottom_right) { for x in x..=get_x(bottom_right) {
grid[(x, y)].set_ch(' '); grid[(x, y)].set_ch(' ');
grid[(x, y)].set_bg(Color::Default); grid[(x, y)].set_bg(Color::Default);
@ -137,7 +156,7 @@ impl Component for MailView {
Color::Default, Color::Default,
(set_y(upper_left, y + 1), bottom_right), (set_y(upper_left, y + 1), bottom_right),
true, true,
); );
for x in x..=get_x(bottom_right) { for x in x..=get_x(bottom_right) {
grid[(x, y)].set_ch(' '); grid[(x, y)].set_ch(' ');
grid[(x, y)].set_bg(Color::Default); grid[(x, y)].set_bg(Color::Default);
@ -174,9 +193,7 @@ impl Component for MailView {
} }
t t
} }
ViewMode::Raw => { ViewMode::Raw => String::from_utf8_lossy(&envelope.bytes()).into_owned(),
String::from_utf8_lossy(&envelope.bytes()).into_owned()
},
ViewMode::Url => { ViewMode::Url => {
let mut t = envelope.body().text().to_string(); let mut t = envelope.body().text().to_string();
for (lidx, l) in finder.links(&envelope.body().text()).enumerate() { for (lidx, l) in finder.links(&envelope.body().text()).enumerate() {
@ -195,28 +212,25 @@ impl Component for MailView {
} }
ViewMode::Attachment(aidx) => { ViewMode::Attachment(aidx) => {
let attachments = envelope.body().attachments(); let attachments = envelope.body().attachments();
let mut ret = format!("Viewing attachment. Press `r` to return \n"); let mut ret = "Viewing attachment. Press `r` to return \n".to_string();
ret.push_str(&attachments[aidx].text()); ret.push_str(&attachments[aidx].text());
ret ret
} }
}; };
let mut buf = CellBuffer::from(&text); let mut buf = CellBuffer::from(&text);
match self.mode { if self.mode == ViewMode::Url {
ViewMode::Url => { // URL indexes must be colored (ugh..)
// URL indexes must be colored (ugh..) let lines: Vec<&str> = text.split('\n').collect();
let lines: Vec<&str> = text.split('\n').collect(); let mut shift = 0;
let mut shift = 0; for r in &lines {
for r in lines.iter() { for l in finder.links(&r) {
for l in finder.links(&r) { buf[(l.start() + shift - 1, 0)].set_fg(Color::Byte(226));
buf[(l.start() + shift - 1, 0)].set_fg(Color::Byte(226)); buf[(l.start() + shift - 2, 0)].set_fg(Color::Byte(226));
buf[(l.start() + shift - 2, 0)].set_fg(Color::Byte(226)); buf[(l.start() + shift - 3, 0)].set_fg(Color::Byte(226));
buf[(l.start() + shift - 3, 0)].set_fg(Color::Byte(226));
}
// Each Cell represents one char so next line will be:
shift += r.chars().count() + 1;
} }
// Each Cell represents one char so next line will be:
shift += r.chars().count() + 1;
} }
_ => {}
} }
buf buf
}; };
@ -225,12 +239,13 @@ impl Component for MailView {
} else { } else {
self.pager.as_mut().map(|p| p.cursor_pos()) self.pager.as_mut().map(|p| p.cursor_pos())
}; };
self.pager = Some(Pager::from_buf(buf, cursor_pos)); self.pager = Some(Pager::from_buf(&buf, cursor_pos));
self.dirty = false; self.dirty = false;
} }
self.pager
.as_mut() if let Some(p) = self.pager.as_mut() {
.map(|p| p.draw(grid, (set_y(upper_left, y + 1), bottom_right), context)); p.draw(grid, (set_y(upper_left, y + 1), bottom_right), context);
}
} }
fn process_event(&mut self, event: &UIEvent, context: &mut Context) { fn process_event(&mut self, event: &UIEvent, context: &mut Context) {
@ -241,7 +256,9 @@ impl Component for MailView {
UIEventType::Input(Key::Char(c)) if c >= '0' && c <= '9' => { UIEventType::Input(Key::Char(c)) if c >= '0' && c <= '9' => {
self.cmd_buf.push(c); self.cmd_buf.push(c);
} }
UIEventType::Input(Key::Char('r')) if self.mode == ViewMode::Normal || self.mode == ViewMode::Raw => { UIEventType::Input(Key::Char('r'))
if self.mode == ViewMode::Normal || self.mode == ViewMode::Raw =>
{
self.mode = if self.mode == ViewMode::Raw { self.mode = if self.mode == ViewMode::Raw {
ViewMode::Normal ViewMode::Normal
} else { } else {
@ -254,7 +271,7 @@ impl Component for MailView {
self.dirty = true; self.dirty = true;
} }
UIEventType::Input(Key::Char('a')) UIEventType::Input(Key::Char('a'))
if self.cmd_buf.len() > 0 && self.mode == ViewMode::Normal => if !self.cmd_buf.is_empty() && self.mode == ViewMode::Normal =>
{ {
let lidx = self.cmd_buf.parse::<usize>().unwrap(); let lidx = self.cmd_buf.parse::<usize>().unwrap();
self.cmd_buf.clear(); self.cmd_buf.clear();
@ -282,9 +299,9 @@ impl Component for MailView {
ContentType::Multipart { .. } => { ContentType::Multipart { .. } => {
context.replies.push_back(UIEvent { context.replies.push_back(UIEvent {
id: 0, id: 0,
event_type: UIEventType::StatusNotification(format!( event_type: UIEventType::StatusNotification(
"Multipart attachments are not supported yet." "Multipart attachments are not supported yet.".to_string(),
)), ),
}); });
return; return;
} }
@ -298,7 +315,9 @@ impl Component for MailView {
.stdin(Stdio::piped()) .stdin(Stdio::piped())
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.spawn() .spawn()
.expect(&format!("Failed to start {}", binary.display())); .unwrap_or_else(|_| {
panic!("Failed to start {}", binary.display())
});
} else { } else {
context.replies.push_back(UIEvent { context.replies.push_back(UIEvent {
id: 0, id: 0,
@ -324,7 +343,7 @@ impl Component for MailView {
}; };
} }
UIEventType::Input(Key::Char('g')) UIEventType::Input(Key::Char('g'))
if self.cmd_buf.len() > 0 && self.mode == ViewMode::Url => if !self.cmd_buf.is_empty() && self.mode == ViewMode::Url =>
{ {
let lidx = self.cmd_buf.parse::<usize>().unwrap(); let lidx = self.cmd_buf.parse::<usize>().unwrap();
self.cmd_buf.clear(); self.cmd_buf.clear();
@ -378,10 +397,8 @@ impl Component for MailView {
} }
if let Some(ref mut sub) = self.subview { if let Some(ref mut sub) = self.subview {
sub.process_event(event, context); sub.process_event(event, context);
} else { } else if let Some(ref mut p) = self.pager {
if let Some(ref mut p) = self.pager { p.process_event(event, context);
p.process_event(event, context);
}
} }
} }
fn is_dirty(&self) -> bool { fn is_dirty(&self) -> bool {

View File

@ -1,5 +1,5 @@
/* /*
* meli - ui module. * meli - ui crate.
* *
* Copyright 2017-2018 Manos Pitsidianakis * Copyright 2017-2018 Manos Pitsidianakis
* *
@ -35,7 +35,6 @@ pub mod notifications;
pub mod utilities; pub mod utilities;
pub use self::utilities::*; pub use self::utilities::*;
use super::{Key, UIEvent, UIEventType}; use super::{Key, UIEvent, UIEventType};
/// The upper and lower boundary char. /// The upper and lower boundary char.
const HORZ_BOUNDARY: char = '─'; const HORZ_BOUNDARY: char = '─';
@ -140,11 +139,8 @@ pub fn copy_area(grid_dest: &mut CellBuffer, grid_src: &CellBuffer, dest: Area,
let mut src_y = get_y(upper_left!(src)); let mut src_y = get_y(upper_left!(src));
let (cols, rows) = grid_src.size(); let (cols, rows) = grid_src.size();
if src_x >= cols || src_y >= rows { if src_x >= cols || src_y >= rows {
eprintln!( eprintln!("DEBUG: src area outside of grid_src in copy_area",);
"DEBUG: src area outside of grid_src in copy_area",
);
return; return;
} }
for y in get_y(upper_left!(dest))..=get_y(bottom_right!(dest)) { for y in get_y(upper_left!(dest))..=get_y(bottom_right!(dest)) {

View File

@ -1,3 +1,24 @@
/*
* 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/>.
*/
/*! /*!
Notification handling components. Notification handling components.
*/ */
@ -11,16 +32,13 @@ pub struct XDGNotifications {}
impl Component for XDGNotifications { impl Component for XDGNotifications {
fn draw(&mut self, _grid: &mut CellBuffer, _area: Area, _context: &mut Context) {} fn draw(&mut self, _grid: &mut CellBuffer, _area: Area, _context: &mut Context) {}
fn process_event(&mut self, event: &UIEvent, _context: &mut Context) { fn process_event(&mut self, event: &UIEvent, _context: &mut Context) {
match event.event_type { if let UIEventType::Notification(ref t) = event.event_type {
UIEventType::Notification(ref t) => { notify_Notification::new()
notify_Notification::new() .summary("Refresh Event")
.summary("Refresh Event") .body(t)
.body(t) .icon("dialog-information")
.icon("dialog-information") .show()
.show() .unwrap();
.unwrap();
}
_ => {}
} }
} }
} }

View File

@ -1,3 +1,24 @@
/*
* 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/>.
*/
/*! Various useful components that can be used in a generic fashion. /*! Various useful components that can be used in a generic fashion.
*/ */
use super::*; use super::*;
@ -13,10 +34,10 @@ pub struct HSplit {
impl HSplit { impl HSplit {
pub fn new(top: Entity, bottom: Entity, ratio: usize, show_divider: bool) -> Self { pub fn new(top: Entity, bottom: Entity, ratio: usize, show_divider: bool) -> Self {
HSplit { HSplit {
top: top, top,
bottom: bottom, bottom,
show_divider: show_divider, show_divider,
ratio: ratio, ratio,
} }
} }
} }
@ -37,7 +58,7 @@ impl Component for HSplit {
grid[(i, mid)].set_ch('─'); grid[(i, mid)].set_ch('─');
} }
} }
let _ = self.top.component.draw( self.top.component.draw(
grid, grid,
( (
upper_left, upper_left,
@ -45,7 +66,7 @@ impl Component for HSplit {
), ),
context, context,
); );
let _ = self.bottom.component.draw( self.bottom.component.draw(
grid, grid,
((get_x(upper_left), get_y(upper_left) + mid), bottom_right), ((get_x(upper_left), get_y(upper_left) + mid), bottom_right),
context, context,
@ -72,10 +93,10 @@ pub struct VSplit {
impl VSplit { impl VSplit {
pub fn new(left: Entity, right: Entity, ratio: usize, show_divider: bool) -> Self { pub fn new(left: Entity, right: Entity, ratio: usize, show_divider: bool) -> Self {
VSplit { VSplit {
left: left, left,
right: right, right,
show_divider: show_divider, show_divider,
ratio: ratio, ratio,
} }
} }
} }
@ -92,14 +113,12 @@ impl Component for VSplit {
let mid = get_x(bottom_right) - right_entity_width; let mid = get_x(bottom_right) - right_entity_width;
if get_y(upper_left) > 1 { if get_y(upper_left) > 1 {
let c = grid.get(mid, get_y(upper_left) - 1) let c = grid
.get(mid, get_y(upper_left) - 1)
.map(|a| a.ch()) .map(|a| a.ch())
.unwrap_or_else(|| ' '); .unwrap_or_else(|| ' ');
match c { if let HORZ_BOUNDARY = 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);
}
_ => {}
} }
} }
@ -110,7 +129,8 @@ impl Component for VSplit {
grid[(mid, i)].set_bg(Color::Default); grid[(mid, i)].set_bg(Color::Default);
} }
if get_y(bottom_right) > 1 { if get_y(bottom_right) > 1 {
let c = grid.get(mid, get_y(bottom_right) - 1) let c = grid
.get(mid, get_y(bottom_right) - 1)
.map(|a| a.ch()) .map(|a| a.ch())
.unwrap_or_else(|| ' '); .unwrap_or_else(|| ' ');
match c { match c {
@ -121,14 +141,12 @@ impl Component for VSplit {
} }
} }
} }
let _ = self.left
self.left .component
.component .draw(grid, (upper_left, (mid - 1, get_y(bottom_right))), context);
.draw(grid, (upper_left, (mid - 1, get_y(bottom_right))), context); self.right
let _ = .component
self.right .draw(grid, ((mid + 1, get_y(upper_left)), bottom_right), context);
.component
.draw(grid, ((mid + 1, get_y(upper_left)), bottom_right), context);
} }
fn process_event(&mut self, event: &UIEvent, context: &mut Context) { fn process_event(&mut self, event: &UIEvent, context: &mut Context) {
self.left.rcv_event(event, context); self.left.rcv_event(event, context);
@ -199,35 +217,33 @@ impl Pager {
Pager::print_string(&mut content, s); Pager::print_string(&mut content, s);
Pager { Pager {
cursor_pos: cursor_pos.unwrap_or(0), cursor_pos: cursor_pos.unwrap_or(0),
height: height, height,
width: width, width,
dirty: true, dirty: true,
content: content, content,
} }
} }
pub fn from_buf(buf: CellBuffer, cursor_pos: Option<usize>) -> Self { pub fn from_buf(buf: &CellBuffer, cursor_pos: Option<usize>) -> Self {
let lines: Vec<&[Cell]> = buf.split(|cell| cell.ch() == '\n').collect(); let lines: Vec<&[Cell]> = buf.split(|cell| cell.ch() == '\n').collect();
let height = lines.len(); let height = lines.len();
let width = lines.iter().map(|l| l.len()).max().unwrap_or(0) + 1; let width = lines.iter().map(|l| l.len()).max().unwrap_or(0) + 1;
let mut content = CellBuffer::new(width, height, Cell::with_char(' ')); let mut content = CellBuffer::new(width, height, Cell::with_char(' '));
{ {
let mut x; let mut x;
let mut y = 0;
let c_slice: &mut [Cell] = &mut content; let c_slice: &mut [Cell] = &mut content;
for l in lines { for (y, l) in lines.iter().enumerate() {
let y_r = y * width; let y_r = y * width;
x = l.len() + y_r; x = l.len() + y_r;
c_slice[y_r..x].copy_from_slice(l); c_slice[y_r..x].copy_from_slice(l);
c_slice[x].set_ch('\n'); c_slice[x].set_ch('\n');
y += 1;
} }
} }
Pager { Pager {
cursor_pos: cursor_pos.unwrap_or(0), cursor_pos: cursor_pos.unwrap_or(0),
height: height, height,
width: width, width,
dirty: true, dirty: true,
content: content, content,
} }
} }
pub fn print_string(content: &mut CellBuffer, s: &str) { pub fn print_string(content: &mut CellBuffer, s: &str) {
@ -320,7 +336,7 @@ pub struct StatusBar {
impl StatusBar { impl StatusBar {
pub fn new(container: Entity) -> Self { pub fn new(container: Entity) -> Self {
StatusBar { StatusBar {
container: container, container,
status: String::with_capacity(256), status: String::with_capacity(256),
notifications: VecDeque::new(), notifications: VecDeque::new(),
ex_buffer: String::with_capacity(256), ex_buffer: String::with_capacity(256),
@ -376,7 +392,7 @@ impl Component for StatusBar {
} }
let height = self.height; let height = self.height;
let _ = self.container.component.draw( self.container.component.draw(
grid, grid,
( (
upper_left, upper_left,
@ -414,14 +430,12 @@ impl Component for StatusBar {
match &event.event_type { match &event.event_type {
UIEventType::RefreshMailbox((ref idx_a, ref idx_f)) => { UIEventType::RefreshMailbox((ref idx_a, ref idx_f)) => {
match context.accounts[*idx_a].status(*idx_f) { match context.accounts[*idx_a].status(*idx_f) {
Ok(()) => {}, Ok(()) => {}
Err(_) => { Err(_) => {
return; return;
} }
} }
let m = &context.accounts[*idx_a][*idx_f] let m = &context.accounts[*idx_a][*idx_f].as_ref().unwrap();
.as_ref()
.unwrap();
self.status = format!( self.status = format!(
"{} | Mailbox: {}, Messages: {}, New: {}", "{} | Mailbox: {}, Messages: {}, New: {}",
self.mode, self.mode,
@ -435,7 +449,7 @@ impl Component for StatusBar {
let offset = self.status.find('|').unwrap_or_else(|| self.status.len()); let offset = self.status.find('|').unwrap_or_else(|| self.status.len());
self.status.replace_range(..offset, &format!("{} ", m)); self.status.replace_range(..offset, &format!("{} ", m));
self.dirty = true; self.dirty = true;
self.mode = m.clone(); self.mode = *m;
match m { match m {
UIMode::Normal => { UIMode::Normal => {
self.height = 1; self.height = 1;
@ -498,7 +512,7 @@ impl Progress {
pub fn new(s: String, total_work: usize) -> Self { pub fn new(s: String, total_work: usize) -> Self {
Progress { Progress {
description: s, description: s,
total_work: total_work, total_work,
finished: 0, finished: 0,
} }
} }

View File

@ -1,3 +1,24 @@
/*
* 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/>.
*/
/*! /*!
* User actions that need to be handled by the UI * User actions that need to be handled by the UI
*/ */
@ -21,7 +42,6 @@ pub enum SortField {
Date, Date,
} }
impl FromStr for SortField { impl FromStr for SortField {
type Err = (); type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
@ -30,7 +50,7 @@ impl FromStr for SortField {
"subject" | "s" | "sub" | "sbj" | "subj" => { "subject" | "s" | "sub" | "sbj" | "subj" => {
eprintln!("parsed: subject"); eprintln!("parsed: subject");
} }
"date" | "d" => { "date" | "d" => {
eprintln!("parsed date"); eprintln!("parsed date");
} }
_ => { _ => {
@ -39,8 +59,8 @@ impl FromStr for SortField {
} }
match s.trim() { match s.trim() {
"subject" | "s" | "sub" | "sbj" | "subj" => Ok(SortField::Subject), "subject" | "s" | "sub" | "sbj" | "subj" => Ok(SortField::Subject),
"date" | "d" => Ok(SortField::Date), "date" | "d" => Ok(SortField::Date),
_ => Err(()) _ => Err(()),
} }
} }
} }
@ -52,7 +72,7 @@ impl FromStr for SortOrder {
match s.trim() { match s.trim() {
"asc" => Ok(SortOrder::Asc), "asc" => Ok(SortOrder::Asc),
"desc" => Ok(SortOrder::Desc), "desc" => Ok(SortOrder::Desc),
_ => Err(()) _ => Err(()),
} }
} }
} }

View File

@ -1,3 +1,24 @@
/*
* 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/>.
*/
/*! A parser module for user commands passed through the Ex mode. /*! A parser module for user commands passed through the Ex mode.
*/ */
use nom::{digit, not_line_ending}; use nom::{digit, not_line_ending};
@ -5,7 +26,6 @@ use std;
pub mod actions; pub mod actions;
pub use actions::*; pub use actions::*;
named!( named!(
usize_c<usize>, usize_c<usize>,
map_res!( map_res!(
@ -14,46 +34,50 @@ named!(
) )
); );
named!(sortfield<SortField>, named!(
map_res!( sortfield<SortField>,
map_res!(take_until_s!(" "), std::str::from_utf8), map_res!(
std::str::FromStr::from_str)); map_res!(take_until_s!(" "), std::str::from_utf8),
std::str::FromStr::from_str
)
);
named!(
sortorder<SortOrder>,
map_res!(
map_res!(call!(not_line_ending), std::str::from_utf8),
std::str::FromStr::from_str
)
);
named!(sortorder<SortOrder>, named!(
map_res!( goto<Action>,
map_res!(call!(not_line_ending), std::str::from_utf8), preceded!(tag!("b "), map!(call!(usize_c), Action::ViewMailbox))
std::str::FromStr::from_str)); );
named!(
subsort<Action>,
do_parse!(tag!("subsort ") >> p: pair!(sortfield, sortorder) >> (Action::SubSort(p.0, p.1)))
);
named!(
sort<Action>,
do_parse!(
tag!("sort ")
>> p: separated_pair!(sortfield, tag!(" "), sortorder)
>> (Action::Sort(p.0, p.1))
)
);
named!(
named!(goto<Action>, threaded<Action>,
preceded!(tag!("b "), map!(ws!(tag!("threaded")), |_| {
map!(call!(usize_c), |v| Action::ViewMailbox(v)) Action::MailListing(MailListingAction::ToggleThreaded)
)); })
);
named!(subsort<Action>, do_parse!( named!(
tag!("subsort ") >> toggle<Action>,
p: pair!(sortfield,sortorder) >> preceded!(tag!("toggle "), alt_complete!(threaded))
( );
Action::SubSort(p.0, p.1)
)
));
named!(sort<Action>, do_parse!(
tag!("sort ") >>
p: separated_pair!(sortfield,tag!(" "), sortorder) >>
(
Action::Sort(p.0, p.1)
)
));
named!(threaded<Action>,
map!(ws!(tag!("threaded")), |_| Action::MailListing(MailListingAction::ToggleThreaded)));
named!(toggle<Action>,
preceded!(tag!("toggle "),
alt_complete!( threaded )));
named!(pub parse_command<Action>, named!(pub parse_command<Action>,
alt_complete!( goto | toggle | sort | subsort) alt_complete!( goto | toggle | sort | subsort)

View File

@ -1,7 +1,7 @@
/* /*
* meli - ui module. * meli - ui crate.
* *
* Copyright 2017 Manos Pitsidianakis * Copyright 2017-2018 Manos Pitsidianakis
* *
* This file is part of meli. * This file is part of meli.
* *

View File

@ -1,3 +1,24 @@
/*
* 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/>.
*/
/*! The application's state. /*! The application's state.
The UI crate has an Entity-Component-System design. The System part, is also the application's state, so they're both merged in the `State` struct. The UI crate has an Entity-Component-System design. The System part, is also the application's state, so they're both merged in the `State` struct.
@ -10,13 +31,12 @@
use super::*; use super::*;
use chan::Sender; use chan::Sender;
use fnv::FnvHashMap; use fnv::FnvHashMap;
use termion::raw::IntoRawMode;
use termion::screen::AlternateScreen;
use termion::{clear, cursor, style};
use std::io::Write; use std::io::Write;
use std::thread; use std::thread;
use std::time; use std::time;
use termion::raw::IntoRawMode;
use termion::screen::AlternateScreen;
use termion::{clear, cursor, style};
/// A context container for loaded settings, accounts, UI changes, etc. /// A context container for loaded settings, accounts, UI changes, etc.
pub struct Context { pub struct Context {
@ -116,39 +136,41 @@ impl State<std::io::Stdout> {
sender.send(ThreadEvent::UIEvent(UIEventType::StartupCheck)); sender.send(ThreadEvent::UIEvent(UIEventType::StartupCheck));
thread::sleep(dur); thread::sleep(dur);
} }
}).unwrap() })
.unwrap()
}; };
let mut s = State { let mut s = State {
cols: cols, cols,
rows: rows, rows,
grid: CellBuffer::new(cols, rows, Cell::with_char(' ')), grid: CellBuffer::new(cols, rows, Cell::with_char(' ')),
stdout: Some(stdout), stdout: Some(stdout),
child: None, child: None,
mode: UIMode::Normal, mode: UIMode::Normal,
sender: sender, sender,
entities: Vec::with_capacity(1), entities: Vec::with_capacity(1),
context: Context { context: Context {
accounts: accounts, accounts,
_backends: backends, _backends: backends,
settings: settings.clone(), settings: settings.clone(),
runtime_settings: settings, runtime_settings: settings,
dirty_areas: VecDeque::with_capacity(5), dirty_areas: VecDeque::with_capacity(5),
replies: VecDeque::with_capacity(5), replies: VecDeque::with_capacity(5),
input_thread: input_thread, input_thread,
}, },
startup_thread: Some(startup_tx), startup_thread: Some(startup_tx),
threads: FnvHashMap::with_capacity_and_hasher(1, Default::default()), threads: FnvHashMap::with_capacity_and_hasher(1, Default::default()),
}; };
s.threads.insert(startup_thread.thread().id(), startup_thread); s.threads
.insert(startup_thread.thread().id(), startup_thread);
write!( write!(
s.stdout(), s.stdout(),
"{}{}{}", "{}{}{}",
cursor::Hide, cursor::Hide,
clear::All, clear::All,
cursor::Goto(1, 1) cursor::Goto(1, 1)
).unwrap(); ).unwrap();
s.flush(); s.flush();
for account in &mut s.context.accounts { for account in &mut s.context.accounts {
let sender = s.sender.clone(); let sender = s.sender.clone();
@ -174,14 +196,14 @@ impl State<std::io::Stdout> {
return; return;
} }
{ {
let tx = self.startup_thread.take().unwrap(); let tx = self.startup_thread.take().unwrap();
tx.send(true); tx.send(true);
} }
} }
/// Switch back to the terminal's main screen (The command line the user sees before opening /// Switch back to the terminal's main screen (The command line the user sees before opening
/// the application) /// the application)
pub fn to_main_screen(&mut self) { pub fn switch_to_main_screen(&mut self) {
write!( write!(
self.stdout(), self.stdout(),
"{}{}", "{}{}",
@ -192,7 +214,7 @@ impl State<std::io::Stdout> {
self.stdout = None; self.stdout = None;
self.context.input_thread.send(false); self.context.input_thread.send(false);
} }
pub fn to_alternate_screen(&mut self) { pub fn switch_to_alternate_screen(&mut self) {
let s = std::io::stdout(); let s = std::io::stdout();
s.lock(); s.lock();
self.stdout = Some(AlternateScreen::from(s.into_raw_mode().unwrap())); self.stdout = Some(AlternateScreen::from(s.into_raw_mode().unwrap()));
@ -312,13 +334,16 @@ impl<W: Write> State<W> {
self.entities.push(entity); self.entities.push(entity);
} }
/// Convert user commands to actions/method calls. /// Convert user commands to actions/method calls.
fn parse_command(&mut self, cmd: String) { fn parse_command(&mut self, cmd: &str) {
eprintln!("cmd is {}", cmd); eprintln!("cmd is {}", cmd);
let result = parse_command(&cmd.as_bytes()).to_full_result(); let result = parse_command(&cmd.as_bytes()).to_full_result();
eprintln!("rseult is {:?}", result); eprintln!("rseult is {:?}", result);
if let Ok(v) = result { if let Ok(v) = result {
self.rcv_event(UIEvent { id: 0, event_type: UIEventType::Action(v) }); self.rcv_event(UIEvent {
id: 0,
event_type: UIEventType::Action(v),
});
} }
} }
@ -327,7 +352,7 @@ impl<W: Write> State<W> {
match event.event_type { match event.event_type {
// Command type is handled only by State. // Command type is handled only by State.
UIEventType::Command(cmd) => { UIEventType::Command(cmd) => {
self.parse_command(cmd); self.parse_command(&cmd);
return; return;
} }
UIEventType::Fork(child) => { UIEventType::Fork(child) => {
@ -351,7 +376,7 @@ impl<W: Write> State<W> {
let mut f = file.file(); let mut f = file.file();
f.read_to_end(&mut buf).unwrap(); f.read_to_end(&mut buf).unwrap();
in_pipe.write(&buf).unwrap(); in_pipe.write_all(&buf).unwrap();
std::fs::remove_file(file.path()).unwrap(); std::fs::remove_file(file.path()).unwrap();
} }
output.wait_with_output().expect("Failed to read stdout"); output.wait_with_output().expect("Failed to read stdout");
@ -378,7 +403,7 @@ impl<W: Write> State<W> {
} }
if !self.context.replies.is_empty() { if !self.context.replies.is_empty() {
let replies: Vec<UIEvent>= self.context.replies.drain(0..).collect(); let replies: Vec<UIEvent> = self.context.replies.drain(0..).collect();
// Pass replies to self and call count on the map iterator to force evaluation // Pass replies to self and call count on the map iterator to force evaluation
replies.into_iter().map(|r| self.rcv_event(r)).count(); replies.into_iter().map(|r| self.rcv_event(r)).count();
} }
@ -402,33 +427,32 @@ impl<W: Write> State<W> {
} }
pub fn try_wait_on_child(&mut self) -> Option<bool> { pub fn try_wait_on_child(&mut self) -> Option<bool> {
if { if match self.child {
match self.child { Some(ForkType::NewDraft(_, ref mut c)) => {
Some(ForkType::NewDraft(_, ref mut c)) => { let mut w = c.try_wait();
let mut w = c.try_wait(); match w {
match w { Ok(Some(_)) => true,
Ok(Some(_)) => true, Ok(None) => false,
Ok(None) => false, Err(_) => {
Err(_) => { return None;
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;
}
}
}
_ => {
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;
}
}
}
_ => {
return None;
}
}
{
if let Some(ForkType::NewDraft(f, _)) = std::mem::replace(&mut self.child, None) { if let Some(ForkType::NewDraft(f, _)) = std::mem::replace(&mut self.child, None) {
self.rcv_event(UIEvent { self.rcv_event(UIEvent {
id: 0, id: 0,
@ -440,7 +464,7 @@ impl<W: Write> State<W> {
Some(false) Some(false)
} }
fn flush(&mut self) { fn flush(&mut self) {
self.stdout.as_mut().map(|s| 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>> { fn stdout(&mut self) -> &mut termion::screen::AlternateScreen<termion::raw::RawTerminal<W>> {
self.stdout.as_mut().unwrap() self.stdout.as_mut().unwrap()

View File

@ -1,3 +1,24 @@
/*
* 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/>.
*/
/*! /*!
Define a (x, y) point in the terminal display as a holder of a character, foreground/background Define a (x, y) point in the terminal display as a holder of a character, foreground/background
colors and attributes. colors and attributes.
@ -88,8 +109,8 @@ impl CellBuffer {
/// `cell` as a blank. /// `cell` as a blank.
pub fn new(cols: usize, rows: usize, cell: Cell) -> CellBuffer { pub fn new(cols: usize, rows: usize, cell: Cell) -> CellBuffer {
CellBuffer { CellBuffer {
cols: cols, cols,
rows: rows, rows,
buf: vec![cell; cols * rows], buf: vec![cell; cols * rows],
} }
} }
@ -130,13 +151,13 @@ impl CellAccessor for CellBuffer {
impl Deref for CellBuffer { impl Deref for CellBuffer {
type Target = [Cell]; type Target = [Cell];
fn deref<'a>(&'a self) -> &'a [Cell] { fn deref(&self) -> &[Cell] {
&self.buf &self.buf
} }
} }
impl DerefMut for CellBuffer { impl DerefMut for CellBuffer {
fn deref_mut<'a>(&'a mut self) -> &'a mut [Cell] { fn deref_mut(&mut self) -> &mut [Cell] {
&mut self.buf &mut self.buf
} }
} }
@ -144,14 +165,14 @@ impl DerefMut for CellBuffer {
impl Index<Pos> for CellBuffer { impl Index<Pos> for CellBuffer {
type Output = Cell; type Output = Cell;
fn index<'a>(&'a self, index: Pos) -> &'a Cell { fn index(&self, index: Pos) -> &Cell {
let (x, y) = index; let (x, y) = index;
self.get(x, y).expect("index out of bounds") self.get(x, y).expect("index out of bounds")
} }
} }
impl IndexMut<Pos> for CellBuffer { impl IndexMut<Pos> for CellBuffer {
fn index_mut<'a>(&'a mut self, index: Pos) -> &'a mut Cell { fn index_mut(&mut self, index: Pos) -> &mut Cell {
let (x, y) = index; let (x, y) = index;
self.get_mut(x, y).expect("index out of bounds") self.get_mut(x, y).expect("index out of bounds")
} }
@ -178,7 +199,7 @@ impl<'a> From<&'a String> for CellBuffer {
impl fmt::Display for CellBuffer { impl fmt::Display for CellBuffer {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
'_y: for y in 0..self.rows { '_y: for y in 0..self.rows {
'_x: for x in 0..self.cols { for x in 0..self.cols {
let c: &char = &self[(x, y)].ch(); let c: &char = &self[(x, y)].ch();
write!(f, "{}", *c).unwrap(); write!(f, "{}", *c).unwrap();
if *c == '\n' { if *c == '\n' {
@ -217,12 +238,7 @@ impl Cell {
/// assert_eq!(cell.attrs(), Attr::Default); /// assert_eq!(cell.attrs(), Attr::Default);
/// ``` /// ```
pub fn new(ch: char, fg: Color, bg: Color, attrs: Attr) -> Cell { pub fn new(ch: char, fg: Color, bg: Color, attrs: Attr) -> Cell {
Cell { Cell { ch, fg, bg, attrs }
ch: ch,
fg: fg,
bg: bg,
attrs: attrs,
}
} }
/// Creates a new `Cell` with the given `char` and default style. /// Creates a new `Cell` with the given `char` and default style.
@ -426,8 +442,8 @@ pub enum Color {
impl Color { impl Color {
/// Returns the `u8` representation of the `Color`. /// Returns the `u8` representation of the `Color`.
pub fn as_byte(&self) -> u8 { pub fn as_byte(self) -> u8 {
match *self { match self {
Color::Black => 0x00, Color::Black => 0x00,
Color::Red => 0x01, Color::Red => 0x01,
Color::Green => 0x02, Color::Green => 0x02,
@ -441,8 +457,8 @@ impl Color {
} }
} }
pub fn as_termion(&self) -> AnsiValue { pub fn as_termion(self) -> AnsiValue {
match *self { match self {
b @ Color::Black b @ Color::Black
| b @ Color::Red | b @ Color::Red
| b @ Color::Green | b @ Color::Green

View File

@ -1,3 +1,24 @@
/*
* 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 std; use std;
use std::io::Write; use std::io::Write;
use std::path::PathBuf; use std::path::PathBuf;
@ -42,7 +63,7 @@ pub fn create_temp_file(bytes: &[u8], filename: Option<&PathBuf>) -> File {
let mut f = std::fs::File::create(path).unwrap(); let mut f = std::fs::File::create(path).unwrap();
f.write(bytes).unwrap(); f.write_all(bytes).unwrap();
f.flush().unwrap(); f.flush().unwrap();
File { path: path.clone() } File { path: path.clone() }
} }

View File

@ -1,8 +1,28 @@
use termion::event::Key as TermionKey; /*
use termion::input::TermRead; * 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 chan; use chan;
use std::io; use std::io;
use termion::event::Key as TermionKey;
use termion::input::TermRead;
#[derive(Debug)] #[derive(Debug)]
pub enum Key { pub enum Key {
@ -83,7 +103,7 @@ pub fn get_events(
stdin: io::Stdin, stdin: io::Stdin,
mut closure: impl FnMut(Key), mut closure: impl FnMut(Key),
mut exit: impl FnMut(), mut exit: impl FnMut(),
rx: chan::Receiver<bool>, rx: &chan::Receiver<bool>,
) -> () { ) -> () {
for c in stdin.keys() { for c in stdin.keys() {
chan_select! { chan_select! {

View File

@ -1,3 +1,24 @@
/*
* 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/>.
*/
#[macro_use] #[macro_use]
mod cells; mod cells;
#[macro_use] #[macro_use]
@ -15,8 +36,8 @@ use super::execute::Action;
use melib::RefreshEvent; use melib::RefreshEvent;
use std; use std;
use std::thread;
use std::fmt; use std::fmt;
use std::thread;
/// `ThreadEvent` encapsulates all of the possible values we need to transfer between our threads /// `ThreadEvent` encapsulates all of the possible values we need to transfer between our threads
/// to the main process. /// to the main process.

View File

@ -1,3 +1,24 @@
/*
* 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/>.
*/
/*! /*!
Simple type definitions and macro helper for a (x,y) position on the terminal and the areas they define. Simple type definitions and macro helper for a (x,y) position on the terminal and the areas they define.
@ -25,30 +46,65 @@ pub fn set_y(p: Pos, new_y: usize) -> Pos {
} }
/// An `Area` consists of two points: the upper left and bottom right corners. /// An `Area` consists of two points: the upper left and bottom right corners.
///
/// Example:
/// ```
/// use ui::position;
///
/// let new_area = ((0, 0), (1, 1));
/// ```
pub type Area = (Pos, Pos); pub type Area = (Pos, Pos);
/// Get the upper left Position of an area
///
/// Example:
/// ```
/// use ui::position;
///
/// let new_area = ((0, 0), (1, 1));
/// assert_eq!(upper_left!(new_area), (0, 0));
/// ```
#[macro_export] #[macro_export]
macro_rules! upper_left { macro_rules! upper_left {
($a:expr) => { ($a:expr) => {
$a.0 $a.0
}; };
} }
/// Get the bottom right Position of an area
///
/// Example:
/// ```
/// use ui::position;
///
/// let new_area = ((0, 0), (1, 1));
/// assert_eq!(bottom_right!(new_area), (1, 1));
/// ```
#[macro_export] #[macro_export]
macro_rules! bottom_right { macro_rules! bottom_right {
($a:expr) => { ($a:expr) => {
$a.1 $a.1
}; };
} }
/// Check if area is valid.
///
/// Example:
/// ```
/// use ui::position;
///
/// let valid_area = ((0, 0), (1, 1));
/// assert!(is_valid_area!(valid_area));
///
/// let invalid_area = ((2, 2), (1, 1));
/// assert!(!is_valid_area!(invalid_area));
///
#[macro_export] #[macro_export]
macro_rules! is_valid_area { macro_rules! is_valid_area {
($a:expr) => {{ ($a:expr) => {{
let upper_left = upper_left!($a); let upper_left = upper_left!($a);
let bottom_right = bottom_right!($a); let bottom_right = bottom_right!($a);
if get_y(upper_left) > get_y(bottom_right) || get_x(upper_left) > get_x(bottom_right) { !(get_y(upper_left) > get_y(bottom_right) || get_x(upper_left) > get_x(bottom_right))
false
} else {
true
}
}}; }};
} }