Run clippy and rustfmt

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

View File

@ -27,9 +27,9 @@ pub mod pager;
use pager::PagerSettings;
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)]

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

View File

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

View File

@ -23,10 +23,10 @@
* Account management from user configuration.
*/
use async::*;
use conf::{AccountSettings, Folder};
use mailbox::backends::{Backends, RefreshEventConsumer};
use mailbox::*;
use async::*;
use std::ops::{Index, IndexMut};
use std::result;
@ -89,73 +89,73 @@ impl Account {
&mut self.workers
}
fn load_mailbox(&mut self, index: usize, envelopes: Result<Vec<Envelope>>) -> () {
let folder = &self.settings.folders[index];
if self.sent_folder.is_some() {
let id = self.sent_folder.unwrap();
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 {
let folder = &self.settings.folders[index];
if self.sent_folder.is_some() {
let id = self.sent_folder.unwrap();
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));
};
}
pub fn status(&mut self, index: usize) -> result::Result<(), usize> {
match self.workers[index].as_mut() {
None => { return Ok(()); },
Some(ref mut w) => {
match w.poll() {
Ok(AsyncStatus::NoUpdate) => {
return Err(0);
},
Ok(AsyncStatus::Finished) => {
},
Ok(AsyncStatus::ProgressReport(n)) => {
return Err(n);
},
a => {
eprintln!("{:?}", a);
return Err(0);
}
None => {
return Ok(());
}
Some(ref mut w) => match w.poll() {
Ok(AsyncStatus::NoUpdate) => {
return Err(0);
}
Ok(AsyncStatus::Finished) => {}
Ok(AsyncStatus::ProgressReport(n)) => {
return Err(n);
}
a => {
eprintln!("{:?}", a);
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.workers[index] = None;
Ok(())
}
}
impl Index<usize> for Account {
type Output = 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.
impl IndexMut<usize> for Account {
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/>.
*/
use async::*;
use conf::Folder;
use error::Result;
use async::*;
use mailbox::backends::{MailBackend, RefreshEventConsumer};
use mailbox::email::{Envelope, };
use mailbox::email::Envelope;
/// `BackendOp` implementor for Imap
#[derive(Debug, Default, Clone)]
@ -35,7 +35,6 @@ impl ImapOp {
}
}
/*
impl BackendOp for ImapOp {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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.
*/
use super::*;
@ -23,7 +44,7 @@ pub struct AccountMenu {
}
impl AccountMenu {
pub fn new(accounts: &Vec<Account>) -> Self {
pub fn new(accounts: &[Account]) -> Self {
let accounts = accounts
.iter()
.enumerate()
@ -40,12 +61,18 @@ impl AccountMenu {
})
.collect();
AccountMenu {
accounts: accounts,
accounts,
dirty: true,
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) {
eprintln!("BUG: invalid area in print_account");
}
@ -70,7 +97,7 @@ impl AccountMenu {
let mut inc = 0;
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) {
depth.pop();
depth.pop();
@ -82,24 +109,33 @@ impl AccountMenu {
fn print(
root: usize,
parents: &Vec<Option<usize>>,
parents: &[Option<usize>],
depth: &mut String,
entries: &Vec<(usize, Folder)>,
entries: &[(usize, Folder)],
s: &mut String,
inc: &mut usize,
index: usize, //account index
context: &mut Context,
) -> () {
) -> () {
let len = s.len();
match context.accounts[index].status(root) {
Ok(()) => {},
Ok(()) => {}
Err(_) => {
return;
// TODO: Show progress visually
}
}
let count = context.accounts[index][root].as_ref().unwrap().collection.iter().filter(|e| !e.is_seen()).count();
s.insert_str(len, &format!("{} {} {}\n ", *inc, &entries[root].1.name(), count));
let count = context.accounts[index][root]
.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;
let children_no = entries[root].1.children().len();
for (idx, child) in entries[root].1.children().iter().enumerate() {
@ -111,7 +147,9 @@ impl AccountMenu {
}
}
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();
@ -125,9 +163,9 @@ impl AccountMenu {
break;
}
let s = if idx == lines_len - 2 {
format!("{}", lines[idx].replace("", ""))
lines[idx].replace("", "")
} else {
format!("{}", lines[idx])
lines[idx].to_string()
};
let (color_fg, color_bg) = if highlight {
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 linkify::{Link, LinkFinder};
use std::process::{Command, Stdio};
@ -40,9 +61,9 @@ impl MailView {
subview: Option<Box<MailView>>,
) -> Self {
MailView {
coordinates: coordinates,
pager: pager,
subview: subview,
coordinates,
pager,
subview,
dirty: true,
mode: ViewMode::Normal,
@ -73,9 +94,7 @@ impl Component for MailView {
if self.mode == ViewMode::Raw {
clear_area(grid, area);
context
.dirty_areas
.push_back(area);
context.dirty_areas.push_back(area);
(envelope_idx, get_y(upper_left) - 1)
} else {
let (x, y) = write_string_to_grid(
@ -85,7 +104,7 @@ impl Component for MailView {
Color::Default,
area,
true,
);
);
for x in x..=get_x(bottom_right) {
grid[(x, y)].set_ch(' ');
grid[(x, y)].set_bg(Color::Default);
@ -98,7 +117,7 @@ impl Component for MailView {
Color::Default,
(set_y(upper_left, y + 1), bottom_right),
true,
);
);
for x in x..=get_x(bottom_right) {
grid[(x, y)].set_ch(' ');
grid[(x, y)].set_bg(Color::Default);
@ -111,7 +130,7 @@ impl Component for MailView {
Color::Default,
(set_y(upper_left, y + 1), bottom_right),
true,
);
);
for x in x..=get_x(bottom_right) {
grid[(x, y)].set_ch(' ');
grid[(x, y)].set_bg(Color::Default);
@ -124,7 +143,7 @@ impl Component for MailView {
Color::Default,
(set_y(upper_left, y + 1), bottom_right),
true,
);
);
for x in x..=get_x(bottom_right) {
grid[(x, y)].set_ch(' ');
grid[(x, y)].set_bg(Color::Default);
@ -137,7 +156,7 @@ impl Component for MailView {
Color::Default,
(set_y(upper_left, y + 1), bottom_right),
true,
);
);
for x in x..=get_x(bottom_right) {
grid[(x, y)].set_ch(' ');
grid[(x, y)].set_bg(Color::Default);
@ -174,9 +193,7 @@ impl Component for MailView {
}
t
}
ViewMode::Raw => {
String::from_utf8_lossy(&envelope.bytes()).into_owned()
},
ViewMode::Raw => String::from_utf8_lossy(&envelope.bytes()).into_owned(),
ViewMode::Url => {
let mut t = envelope.body().text().to_string();
for (lidx, l) in finder.links(&envelope.body().text()).enumerate() {
@ -195,28 +212,25 @@ impl Component for MailView {
}
ViewMode::Attachment(aidx) => {
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
}
};
let mut buf = CellBuffer::from(&text);
match self.mode {
ViewMode::Url => {
// URL indexes must be colored (ugh..)
let lines: Vec<&str> = text.split('\n').collect();
let mut shift = 0;
for r in lines.iter() {
for l in finder.links(&r) {
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 - 3, 0)].set_fg(Color::Byte(226));
}
// Each Cell represents one char so next line will be:
shift += r.chars().count() + 1;
if self.mode == ViewMode::Url {
// URL indexes must be colored (ugh..)
let lines: Vec<&str> = text.split('\n').collect();
let mut shift = 0;
for r in &lines {
for l in finder.links(&r) {
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 - 3, 0)].set_fg(Color::Byte(226));
}
// Each Cell represents one char so next line will be:
shift += r.chars().count() + 1;
}
_ => {}
}
buf
};
@ -225,12 +239,13 @@ impl Component for MailView {
} else {
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.pager
.as_mut()
.map(|p| p.draw(grid, (set_y(upper_left, y + 1), bottom_right), context));
if let Some(p) = self.pager.as_mut() {
p.draw(grid, (set_y(upper_left, y + 1), bottom_right), 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' => {
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 {
ViewMode::Normal
} else {
@ -254,7 +271,7 @@ impl Component for MailView {
self.dirty = true;
}
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();
self.cmd_buf.clear();
@ -282,9 +299,9 @@ impl Component for MailView {
ContentType::Multipart { .. } => {
context.replies.push_back(UIEvent {
id: 0,
event_type: UIEventType::StatusNotification(format!(
"Multipart attachments are not supported yet."
)),
event_type: UIEventType::StatusNotification(
"Multipart attachments are not supported yet.".to_string(),
),
});
return;
}
@ -298,7 +315,9 @@ impl Component for MailView {
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect(&format!("Failed to start {}", binary.display()));
.unwrap_or_else(|_| {
panic!("Failed to start {}", binary.display())
});
} else {
context.replies.push_back(UIEvent {
id: 0,
@ -324,7 +343,7 @@ impl Component for MailView {
};
}
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();
self.cmd_buf.clear();
@ -378,10 +397,8 @@ impl Component for MailView {
}
if let Some(ref mut sub) = self.subview {
sub.process_event(event, context);
} else {
if let Some(ref mut p) = self.pager {
p.process_event(event, context);
}
} else if let Some(ref mut p) = self.pager {
p.process_event(event, context);
}
}
fn is_dirty(&self) -> bool {

View File

@ -1,5 +1,5 @@
/*
* meli - ui module.
* meli - ui crate.
*
* Copyright 2017-2018 Manos Pitsidianakis
*
@ -35,7 +35,6 @@ pub mod notifications;
pub mod utilities;
pub use self::utilities::*;
use super::{Key, UIEvent, UIEventType};
/// The upper and lower 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 (cols, rows) = grid_src.size();
if src_x >= cols || src_y >= rows {
eprintln!(
"DEBUG: src area outside of grid_src in copy_area",
);
eprintln!("DEBUG: src area outside of grid_src in copy_area",);
return;
}
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.
*/
@ -11,16 +32,13 @@ pub struct XDGNotifications {}
impl Component for XDGNotifications {
fn draw(&mut self, _grid: &mut CellBuffer, _area: Area, _context: &mut Context) {}
fn process_event(&mut self, event: &UIEvent, _context: &mut Context) {
match event.event_type {
UIEventType::Notification(ref t) => {
notify_Notification::new()
.summary("Refresh Event")
.body(t)
.icon("dialog-information")
.show()
.unwrap();
}
_ => {}
if let UIEventType::Notification(ref t) = event.event_type {
notify_Notification::new()
.summary("Refresh Event")
.body(t)
.icon("dialog-information")
.show()
.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.
*/
use super::*;
@ -13,10 +34,10 @@ pub struct HSplit {
impl HSplit {
pub fn new(top: Entity, bottom: Entity, ratio: usize, show_divider: bool) -> Self {
HSplit {
top: top,
bottom: bottom,
show_divider: show_divider,
ratio: ratio,
top,
bottom,
show_divider,
ratio,
}
}
}
@ -37,7 +58,7 @@ impl Component for HSplit {
grid[(i, mid)].set_ch('─');
}
}
let _ = self.top.component.draw(
self.top.component.draw(
grid,
(
upper_left,
@ -45,7 +66,7 @@ impl Component for HSplit {
),
context,
);
let _ = self.bottom.component.draw(
self.bottom.component.draw(
grid,
((get_x(upper_left), get_y(upper_left) + mid), bottom_right),
context,
@ -72,10 +93,10 @@ pub struct VSplit {
impl VSplit {
pub fn new(left: Entity, right: Entity, ratio: usize, show_divider: bool) -> Self {
VSplit {
left: left,
right: right,
show_divider: show_divider,
ratio: ratio,
left,
right,
show_divider,
ratio,
}
}
}
@ -92,14 +113,12 @@ impl Component for VSplit {
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)
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);
}
_ => {}
if let HORZ_BOUNDARY = c {
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);
}
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())
.unwrap_or_else(|| ' ');
match c {
@ -121,14 +141,12 @@ impl Component for VSplit {
}
}
}
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);
self.left
.component
.draw(grid, (upper_left, (mid - 1, get_y(bottom_right))), context);
self.right
.component
.draw(grid, ((mid + 1, get_y(upper_left)), bottom_right), context);
}
fn process_event(&mut self, event: &UIEvent, context: &mut Context) {
self.left.rcv_event(event, context);
@ -199,35 +217,33 @@ impl Pager {
Pager::print_string(&mut content, s);
Pager {
cursor_pos: cursor_pos.unwrap_or(0),
height: height,
width: width,
height,
width,
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 height = lines.len();
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 x;
let mut y = 0;
let c_slice: &mut [Cell] = &mut content;
for l in lines {
for (y, l) in lines.iter().enumerate() {
let y_r = y * width;
x = l.len() + y_r;
c_slice[y_r..x].copy_from_slice(l);
c_slice[x].set_ch('\n');
y += 1;
}
}
Pager {
cursor_pos: cursor_pos.unwrap_or(0),
height: height,
width: width,
height,
width,
dirty: true,
content: content,
content,
}
}
pub fn print_string(content: &mut CellBuffer, s: &str) {
@ -320,7 +336,7 @@ pub struct StatusBar {
impl StatusBar {
pub fn new(container: Entity) -> Self {
StatusBar {
container: container,
container,
status: String::with_capacity(256),
notifications: VecDeque::new(),
ex_buffer: String::with_capacity(256),
@ -376,7 +392,7 @@ impl Component for StatusBar {
}
let height = self.height;
let _ = self.container.component.draw(
self.container.component.draw(
grid,
(
upper_left,
@ -414,14 +430,12 @@ impl Component for StatusBar {
match &event.event_type {
UIEventType::RefreshMailbox((ref idx_a, ref idx_f)) => {
match context.accounts[*idx_a].status(*idx_f) {
Ok(()) => {},
Ok(()) => {}
Err(_) => {
return;
}
}
let m = &context.accounts[*idx_a][*idx_f]
.as_ref()
.unwrap();
let m = &context.accounts[*idx_a][*idx_f].as_ref().unwrap();
self.status = format!(
"{} | Mailbox: {}, Messages: {}, New: {}",
self.mode,
@ -435,7 +449,7 @@ impl Component for StatusBar {
let offset = self.status.find('|').unwrap_or_else(|| self.status.len());
self.status.replace_range(..offset, &format!("{} ", m));
self.dirty = true;
self.mode = m.clone();
self.mode = *m;
match m {
UIMode::Normal => {
self.height = 1;
@ -498,7 +512,7 @@ impl Progress {
pub fn new(s: String, total_work: usize) -> Self {
Progress {
description: s,
total_work: total_work,
total_work,
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
*/
@ -21,7 +42,6 @@ pub enum SortField {
Date,
}
impl FromStr for SortField {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
@ -30,7 +50,7 @@ impl FromStr for SortField {
"subject" | "s" | "sub" | "sbj" | "subj" => {
eprintln!("parsed: subject");
}
"date" | "d" => {
"date" | "d" => {
eprintln!("parsed date");
}
_ => {
@ -39,8 +59,8 @@ impl FromStr for SortField {
}
match s.trim() {
"subject" | "s" | "sub" | "sbj" | "subj" => Ok(SortField::Subject),
"date" | "d" => Ok(SortField::Date),
_ => Err(())
"date" | "d" => Ok(SortField::Date),
_ => Err(()),
}
}
}
@ -52,7 +72,7 @@ impl FromStr for SortOrder {
match s.trim() {
"asc" => Ok(SortOrder::Asc),
"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.
*/
use nom::{digit, not_line_ending};
@ -5,7 +26,6 @@ use std;
pub mod actions;
pub use actions::*;
named!(
usize_c<usize>,
map_res!(
@ -14,46 +34,50 @@ named!(
)
);
named!(sortfield<SortField>,
map_res!(
map_res!(take_until_s!(" "), std::str::from_utf8),
std::str::FromStr::from_str));
named!(
sortfield<SortField>,
map_res!(
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>,
map_res!(
map_res!(call!(not_line_ending), std::str::from_utf8),
std::str::FromStr::from_str));
named!(
goto<Action>,
preceded!(tag!("b "), map!(call!(usize_c), Action::ViewMailbox))
);
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!(goto<Action>,
preceded!(tag!("b "),
map!(call!(usize_c), |v| Action::ViewMailbox(v))
));
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!(threaded<Action>,
map!(ws!(tag!("threaded")), |_| Action::MailListing(MailListingAction::ToggleThreaded)));
named!(toggle<Action>,
preceded!(tag!("toggle "),
alt_complete!( threaded )));
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>,
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.
*

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 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 chan::Sender;
use fnv::FnvHashMap;
use termion::raw::IntoRawMode;
use termion::screen::AlternateScreen;
use termion::{clear, cursor, style};
use std::io::Write;
use std::thread;
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.
pub struct Context {
@ -116,39 +136,41 @@ impl State<std::io::Stdout> {
sender.send(ThreadEvent::UIEvent(UIEventType::StartupCheck));
thread::sleep(dur);
}
}).unwrap()
})
.unwrap()
};
let mut s = State {
cols: cols,
rows: rows,
cols,
rows,
grid: CellBuffer::new(cols, rows, Cell::with_char(' ')),
stdout: Some(stdout),
child: None,
mode: UIMode::Normal,
sender: sender,
sender,
entities: Vec::with_capacity(1),
context: Context {
accounts: accounts,
accounts,
_backends: backends,
settings: settings.clone(),
runtime_settings: settings,
dirty_areas: VecDeque::with_capacity(5),
replies: VecDeque::with_capacity(5),
input_thread: input_thread,
input_thread,
},
startup_thread: Some(startup_tx),
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!(
s.stdout(),
"{}{}{}",
cursor::Hide,
clear::All,
cursor::Goto(1, 1)
).unwrap();
).unwrap();
s.flush();
for account in &mut s.context.accounts {
let sender = s.sender.clone();
@ -174,14 +196,14 @@ impl State<std::io::Stdout> {
return;
}
{
let tx = self.startup_thread.take().unwrap();
let tx = self.startup_thread.take().unwrap();
tx.send(true);
}
}
/// Switch back to the terminal's main screen (The command line the user sees before opening
/// the application)
pub fn to_main_screen(&mut self) {
pub fn switch_to_main_screen(&mut self) {
write!(
self.stdout(),
"{}{}",
@ -192,7 +214,7 @@ impl State<std::io::Stdout> {
self.stdout = None;
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();
s.lock();
self.stdout = Some(AlternateScreen::from(s.into_raw_mode().unwrap()));
@ -312,13 +334,16 @@ impl<W: Write> State<W> {
self.entities.push(entity);
}
/// 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);
let result = parse_command(&cmd.as_bytes()).to_full_result();
eprintln!("rseult is {:?}", 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 {
// Command type is handled only by State.
UIEventType::Command(cmd) => {
self.parse_command(cmd);
self.parse_command(&cmd);
return;
}
UIEventType::Fork(child) => {
@ -351,7 +376,7 @@ impl<W: Write> State<W> {
let mut f = file.file();
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();
}
output.wait_with_output().expect("Failed to read stdout");
@ -378,7 +403,7 @@ impl<W: Write> State<W> {
}
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
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> {
if {
match self.child {
Some(ForkType::NewDraft(_, ref mut c)) => {
let mut w = c.try_wait();
match w {
Ok(Some(_)) => true,
Ok(None) => false,
Err(_) => {
return None;
}
if match self.child {
Some(ForkType::NewDraft(_, ref mut c)) => {
let mut w = c.try_wait();
match w {
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;
}
}
}
_ => {
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) {
self.rcv_event(UIEvent {
id: 0,
@ -440,7 +464,7 @@ impl<W: Write> State<W> {
Some(false)
}
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>> {
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
colors and attributes.
@ -88,8 +109,8 @@ impl CellBuffer {
/// `cell` as a blank.
pub fn new(cols: usize, rows: usize, cell: Cell) -> CellBuffer {
CellBuffer {
cols: cols,
rows: rows,
cols,
rows,
buf: vec![cell; cols * rows],
}
}
@ -130,13 +151,13 @@ impl CellAccessor for CellBuffer {
impl Deref for CellBuffer {
type Target = [Cell];
fn deref<'a>(&'a self) -> &'a [Cell] {
fn deref(&self) -> &[Cell] {
&self.buf
}
}
impl DerefMut for CellBuffer {
fn deref_mut<'a>(&'a mut self) -> &'a mut [Cell] {
fn deref_mut(&mut self) -> &mut [Cell] {
&mut self.buf
}
}
@ -144,14 +165,14 @@ impl DerefMut for CellBuffer {
impl Index<Pos> for CellBuffer {
type Output = Cell;
fn index<'a>(&'a self, index: Pos) -> &'a Cell {
fn index(&self, index: Pos) -> &Cell {
let (x, y) = index;
self.get(x, y).expect("index out of bounds")
}
}
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;
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 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
'_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();
write!(f, "{}", *c).unwrap();
if *c == '\n' {
@ -217,12 +238,7 @@ impl Cell {
/// assert_eq!(cell.attrs(), Attr::Default);
/// ```
pub fn new(ch: char, fg: Color, bg: Color, attrs: Attr) -> Cell {
Cell {
ch: ch,
fg: fg,
bg: bg,
attrs: attrs,
}
Cell { ch, fg, bg, attrs }
}
/// Creates a new `Cell` with the given `char` and default style.
@ -426,8 +442,8 @@ pub enum Color {
impl Color {
/// Returns the `u8` representation of the `Color`.
pub fn as_byte(&self) -> u8 {
match *self {
pub fn as_byte(self) -> u8 {
match self {
Color::Black => 0x00,
Color::Red => 0x01,
Color::Green => 0x02,
@ -441,8 +457,8 @@ impl Color {
}
}
pub fn as_termion(&self) -> AnsiValue {
match *self {
pub fn as_termion(self) -> AnsiValue {
match self {
b @ Color::Black
| b @ Color::Red
| 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::io::Write;
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();
f.write(bytes).unwrap();
f.write_all(bytes).unwrap();
f.flush().unwrap();
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 std::io;
use termion::event::Key as TermionKey;
use termion::input::TermRead;
#[derive(Debug)]
pub enum Key {
@ -83,7 +103,7 @@ pub fn get_events(
stdin: io::Stdin,
mut closure: impl FnMut(Key),
mut exit: impl FnMut(),
rx: chan::Receiver<bool>,
rx: &chan::Receiver<bool>,
) -> () {
for c in stdin.keys() {
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]
mod cells;
#[macro_use]
@ -15,8 +36,8 @@ use super::execute::Action;
use melib::RefreshEvent;
use std;
use std::thread;
use std::fmt;
use std::thread;
/// `ThreadEvent` encapsulates all of the possible values we need to transfer between our threads
/// 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.
@ -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.
///
/// Example:
/// ```
/// use ui::position;
///
/// let new_area = ((0, 0), (1, 1));
/// ```
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_rules! upper_left {
($a:expr) => {
$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_rules! bottom_right {
($a:expr) => {
$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_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
}
!(get_y(upper_left) > get_y(bottom_right) || get_x(upper_left) > get_x(bottom_right))
}};
}