Browse Source

Run rustfmt

tags/pre-alpha-0.0
Manos Pitsidianakis 7 months ago
parent
commit
bf038428c2
No known key found for this signature in database

+ 7
- 7
melib/src/addressbook.rs View File

@@ -19,8 +19,8 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use chrono::{DateTime, Local};
use uuid::Uuid;
use fnv::FnvHashMap;
use uuid::Uuid;

use std::ops::Deref;

@@ -31,7 +31,7 @@ pub struct AddressBook {
display_name: String,
created: DateTime<Local>,
last_edited: DateTime<Local>,
cards: FnvHashMap<CardId, Card>
cards: FnvHashMap<CardId, Card>,
}

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
@@ -44,7 +44,6 @@ pub struct Card {
name_prefix: String,
name_suffix: String,
//address

birthday: Option<DateTime<Local>>,
email: String,
url: String,
@@ -74,7 +73,11 @@ impl AddressBook {
self.cards.contains_key(&card_id)
}
pub fn search(&self, term: &str) -> Vec<String> {
self.cards.values().filter(|c| c.email.contains(term)).map(|c| c.email.clone()).collect()
self.cards
.values()
.filter(|c| c.email.contains(term))
.map(|c| c.email.clone())
.collect()
}
}

@@ -86,7 +89,6 @@ impl Deref for AddressBook {
}
}


impl Card {
pub fn new() -> Card {
Card {
@@ -98,7 +100,6 @@ impl Card {
name_prefix: String::new(),
name_suffix: String::new(),
//address

birthday: None,
email: String::new(),
url: String::new(),
@@ -182,7 +183,6 @@ impl Card {
pub fn extra_property(&self, key: &str) -> Option<&str> {
self.extra_properties.get(key).map(|v| v.as_str())
}

}

impl From<FnvHashMap<String, String>> for Card {

+ 2
- 2
melib/src/lib.rs View File

@@ -18,11 +18,11 @@
* 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 addressbook;
pub mod async;
pub mod conf;
pub mod error;
pub mod mailbox;
pub mod addressbook;

#[macro_use]
extern crate serde_derive;
@@ -38,8 +38,8 @@ extern crate chan;

#[macro_use]
extern crate bitflags;
extern crate uuid;
extern crate fnv;
extern crate uuid;

pub use conf::*;
pub use mailbox::*;

+ 20
- 17
melib/src/mailbox/backends/maildir/backend.rs View File

@@ -27,7 +27,7 @@ extern crate xdg;
use super::{MaildirFolder, MaildirOp};
use async::*;
use conf::AccountSettings;
use error::{Result, MeliError};
use error::{MeliError, Result};
use mailbox::backends::{
BackendFolder, BackendOp, Folder, FolderHash, MailBackend, RefreshEvent, RefreshEventConsumer,
RefreshEventKind::*,
@@ -132,16 +132,16 @@ fn get_file_hash(file: &Path) -> EnvelopeHash {
}

fn move_to_cur(p: PathBuf) -> PathBuf {
let mut new = p.clone();
{
let file_name = p.file_name().unwrap();
new.pop();
new.pop();
let mut new = p.clone();
{
let file_name = p.file_name().unwrap();
new.pop();
new.pop();

new.push("cur");
new.push(file_name);
new.set_extension(":2,");
}
new.push("cur");
new.push(file_name);
new.set_extension(":2,");
}
eprintln!("moved to cur: {}", new.display());
fs::rename(p, &new).unwrap();
new
@@ -347,7 +347,10 @@ impl MailBackend for MaildirType {
}
}

Err(MeliError::new(format!("'{}' is not a valid folder.", folder)))
Err(MeliError::new(format!(
"'{}' is not a valid folder.",
folder
)))
}
}

@@ -447,13 +450,13 @@ impl MaildirType {
let thunk = move || {
let mut path = path.clone();
let cache_dir = cache_dir.clone();
path.push("new");
for d in path.read_dir()? {
if let Ok(p) = d {
move_to_cur(p.path());
}
path.push("new");
for d in path.read_dir()? {
if let Ok(p) = d {
move_to_cur(p.path());
}
path.pop();
}
path.pop();

path.push("cur");
let iter = path.read_dir()?;

+ 18
- 18
melib/src/mailbox/collection.rs View File

@@ -56,24 +56,24 @@ impl Collection {
let threads = Threads::new(&mut envelopes);

/*let cache_dir =
xdg::BaseDirectories::with_profile("meli", format!("{}_Thread", folder.hash()))
.unwrap();
if let Some(cached) = cache_dir.find_cache_file("threads") {
let reader = io::BufReader::new(fs::File::open(cached).unwrap());
let result: result::Result<Threads, _> = bincode::deserialize_from(reader);
let ret = if let Ok(mut cached_t) = result {
use std::iter::FromIterator;
eprintln!("loaded cache, our hash set is {:?}\n and the cached one is {:?}", FnvHashSet::from_iter(envelopes.keys().cloned()), cached_t.hash_set);
cached_t.amend(&mut envelopes);
cached_t
} else {
Threads::new(&mut envelopes)
};
ret
} else {
Threads::new(&mut envelopes)
};
*/
xdg::BaseDirectories::with_profile("meli", format!("{}_Thread", folder.hash()))
.unwrap();
if let Some(cached) = cache_dir.find_cache_file("threads") {
let reader = io::BufReader::new(fs::File::open(cached).unwrap());
let result: result::Result<Threads, _> = bincode::deserialize_from(reader);
let ret = if let Ok(mut cached_t) = result {
use std::iter::FromIterator;
eprintln!("loaded cache, our hash set is {:?}\n and the cached one is {:?}", FnvHashSet::from_iter(envelopes.keys().cloned()), cached_t.hash_set);
cached_t.amend(&mut envelopes);
cached_t
} else {
Threads::new(&mut envelopes)
};
ret
} else {
Threads::new(&mut envelopes)
};
*/

Collection {
folder: folder.clone(),

+ 18
- 18
melib/src/mailbox/email.rs View File

@@ -74,7 +74,7 @@ impl Address {
Address::Group(g) => g.display_name.display(&g.raw),
}
}
pub fn get_email(&self) -> String {
match self {
Address::Mailbox(m) => m.address_spec.display(&m.raw),
@@ -94,11 +94,11 @@ impl PartialEq for Address {
s.address_spec.display(&s.raw) == o.address_spec.display(&o.raw)
}
(Address::Group(s), Address::Group(o)) => {
s.display_name.display(&s.raw) == o.display_name.display(&o.raw) && s
.mailbox_list
.iter()
.zip(o.mailbox_list.iter())
.fold(true, |b, (s, o)| b && (s == o))
s.display_name.display(&s.raw) == o.display_name.display(&o.raw)
&& s.mailbox_list
.iter()
.zip(o.mailbox_list.iter())
.fold(true, |b, (s, o)| b && (s == o))
}
}
}
@@ -616,22 +616,22 @@ impl Envelope {
pub fn message_id_raw(&self) -> Cow<str> {
String::from_utf8_lossy(self.message_id.raw())
}
fn set_date(&mut self, new_val: &[u8]) {
fn set_date(&mut self, new_val: &[u8]) {
self.date = String::from_utf8_lossy(new_val).into_owned();
}
fn set_bcc(&mut self, new_val: Vec<Address>) {
fn set_bcc(&mut self, new_val: Vec<Address>) {
self.bcc = new_val;
}
fn set_cc(&mut self, new_val: Vec<Address>) {
fn set_cc(&mut self, new_val: Vec<Address>) {
self.cc = new_val;
}
fn set_from(&mut self, new_val: Vec<Address>) {
fn set_from(&mut self, new_val: Vec<Address>) {
self.from = new_val;
}
fn set_to(&mut self, new_val: Vec<Address>) {
fn set_to(&mut self, new_val: Vec<Address>) {
self.to = new_val;
}
fn set_in_reply_to(&mut self, new_val: &[u8]) {
fn set_in_reply_to(&mut self, new_val: &[u8]) {
let slice = match parser::message_id(new_val).to_full_result() {
Ok(v) => v,
Err(_) => {
@@ -641,10 +641,10 @@ impl Envelope {
};
self.in_reply_to = Some(MessageID::new(new_val, slice));
}
fn set_subject(&mut self, new_val: Vec<u8>) {
fn set_subject(&mut self, new_val: Vec<u8>) {
self.subject = Some(new_val);
}
fn set_message_id(&mut self, new_val: &[u8]) {
fn set_message_id(&mut self, new_val: &[u8]) {
let slice = match parser::message_id(new_val).to_full_result() {
Ok(v) => v,
Err(_) => {
@@ -653,7 +653,7 @@ impl Envelope {
};
self.message_id = MessageID::new(new_val, slice);
}
fn push_references(&mut self, new_val: &[u8]) {
fn push_references(&mut self, new_val: &[u8]) {
let slice = match parser::message_id(new_val).to_full_result() {
Ok(v) => v,
Err(_) => {
@@ -685,7 +685,7 @@ impl Envelope {
}
}
}
fn set_references(&mut self, new_val: &[u8]) {
fn set_references(&mut self, new_val: &[u8]) {
match self.references {
Some(ref mut s) => {
s.raw = new_val.into();
@@ -713,10 +713,10 @@ impl Envelope {
pub fn thread(&self) -> usize {
self.thread
}
pub fn set_thread(&mut self, new_val: usize) {
pub fn set_thread(&mut self, new_val: usize) {
self.thread = new_val;
}
pub fn set_datetime(&mut self, new_val: chrono::DateTime<chrono::FixedOffset>) {
pub fn set_datetime(&mut self, new_val: chrono::DateTime<chrono::FixedOffset>) {
self.timestamp = new_val.timestamp() as UnixTimestamp;
}
pub fn set_flag(&mut self, f: Flag, mut operation: Box<BackendOp>) -> Result<()> {

+ 81
- 75
melib/src/mailbox/email/attachments.rs View File

@@ -71,73 +71,77 @@ impl AttachmentBuilder {
}
pub fn content_type(&mut self, value: &[u8]) -> &Self {
match parser::content_type(value).to_full_result() {
Ok((ct, cst, params)) => if ct.eq_ignore_ascii_case(b"multipart") {
let mut boundary = None;
for (n, v) in params {
if n.eq_ignore_ascii_case(b"boundary") {
boundary = Some(v);
break;
Ok((ct, cst, params)) => {
if ct.eq_ignore_ascii_case(b"multipart") {
let mut boundary = None;
for (n, v) in params {
if n.eq_ignore_ascii_case(b"boundary") {
boundary = Some(v);
break;
}
}
}
assert!(boundary.is_some());
let _boundary = boundary.unwrap();
let offset = (_boundary.as_ptr() as usize).wrapping_sub(value.as_ptr() as usize);
let boundary = SliceBuild::new(offset, _boundary.len());
let subattachments = Self::subattachments(&self.raw, boundary.get(&value));
assert!(!subattachments.is_empty());
self.content_type = ContentType::Multipart {
boundary,
kind: if cst.eq_ignore_ascii_case(b"mixed") {
MultipartType::Mixed
} else if cst.eq_ignore_ascii_case(b"alternative") {
MultipartType::Alternative
} else if cst.eq_ignore_ascii_case(b"digest") {
MultipartType::Digest
} else {
Default::default()
},
subattachments,
};
} else if ct.eq_ignore_ascii_case(b"text") {
self.content_type = ContentType::Text {
kind: Text::Plain,
charset: Charset::UTF8,
};
for (n, v) in params {
if n.eq_ignore_ascii_case(b"charset") {
assert!(boundary.is_some());
let _boundary = boundary.unwrap();
let offset =
(_boundary.as_ptr() as usize).wrapping_sub(value.as_ptr() as usize);
let boundary = SliceBuild::new(offset, _boundary.len());
let subattachments = Self::subattachments(&self.raw, boundary.get(&value));
assert!(!subattachments.is_empty());
self.content_type = ContentType::Multipart {
boundary,
kind: if cst.eq_ignore_ascii_case(b"mixed") {
MultipartType::Mixed
} else if cst.eq_ignore_ascii_case(b"alternative") {
MultipartType::Alternative
} else if cst.eq_ignore_ascii_case(b"digest") {
MultipartType::Digest
} else {
Default::default()
},
subattachments,
};
} else if ct.eq_ignore_ascii_case(b"text") {
self.content_type = ContentType::Text {
kind: Text::Plain,
charset: Charset::UTF8,
};
for (n, v) in params {
if n.eq_ignore_ascii_case(b"charset") {
if let ContentType::Text {
charset: ref mut c, ..
} = self.content_type
{
*c = Charset::from(v);
}
break;
}
}
if cst.eq_ignore_ascii_case(b"html") {
if let ContentType::Text {
charset: ref mut c, ..
kind: ref mut k, ..
} = self.content_type
{
*c = Charset::from(v);
*k = Text::Html;
}
} else if !cst.eq_ignore_ascii_case(b"plain") {
if let ContentType::Text {
kind: ref mut k, ..
} = self.content_type
{
*k = Text::Other { tag: cst.into() };
}
break;
}
}
if cst.eq_ignore_ascii_case(b"html") {
if let ContentType::Text {
kind: ref mut k, ..
} = self.content_type
{
*k = Text::Html;
}
} else if !cst.eq_ignore_ascii_case(b"plain") {
if let ContentType::Text {
kind: ref mut k, ..
} = self.content_type
{
*k = Text::Other { tag: cst.into() };
}
} else if ct.eq_ignore_ascii_case(b"message") && cst.eq_ignore_ascii_case(b"rfc822")
{
self.content_type = ContentType::MessageRfc822;
} else {
let mut tag: Vec<u8> = Vec::with_capacity(ct.len() + cst.len() + 1);
tag.extend(ct);
tag.push(b'/');
tag.extend(cst);
self.content_type = ContentType::Unsupported { tag };
}
} else if ct.eq_ignore_ascii_case(b"message") && cst.eq_ignore_ascii_case(b"rfc822") {
self.content_type = ContentType::MessageRfc822;
} else {
let mut tag: Vec<u8> = Vec::with_capacity(ct.len() + cst.len() + 1);
tag.extend(ct);
tag.push(b'/');
tag.extend(cst);
self.content_type = ContentType::Unsupported { tag };
},
}
Err(v) => {
eprintln!("parsing error in content_type: {:?} {:?}", value, v);
}
@@ -405,23 +409,25 @@ fn decode_rec_helper(a: &Attachment, filter: &Option<Filter>) -> Vec<u8> {
kind: ref multipart_type,
subattachments: ref sub_att_vec,
..
} => if *multipart_type == MultipartType::Alternative {
for a in sub_att_vec {
if let ContentType::Text {
kind: Text::Plain, ..
} = a.content_type
{
return decode_helper(a, filter);
} => {
if *multipart_type == MultipartType::Alternative {
for a in sub_att_vec {
if let ContentType::Text {
kind: Text::Plain, ..
} = a.content_type
{
return decode_helper(a, filter);
}
}
decode_helper(a, filter)
} else {
let mut vec = Vec::new();
for a in sub_att_vec {
vec.extend(decode_rec_helper(a, filter));
}
vec
}
decode_helper(a, filter)
} else {
let mut vec = Vec::new();
for a in sub_att_vec {
vec.extend(decode_rec_helper(a, filter));
}
vec
},
}
};
if let Some(filter) = filter {
filter(a, &mut ret);

+ 9
- 7
melib/src/mailbox/email/compose.rs View File

@@ -3,8 +3,8 @@ use chrono::{DateTime, Local};
use data_encoding::BASE64_MIME;
use std::str;

mod random;
mod mime;
mod random;

//use self::mime::*;

@@ -70,13 +70,16 @@ impl str::FromStr for Draft {
if ignore_header(k) {
continue;
}
if ret.headers.insert(
String::from_utf8(k.to_vec())?,
String::from_utf8(v.to_vec())?,
).is_none() {
if ret
.headers
.insert(
String::from_utf8(k.to_vec())?,
String::from_utf8(v.to_vec())?,
)
.is_none()
{
ret.header_order.push(String::from_utf8(k.to_vec())?);
}

}

let body = Envelope::new(0).body_bytes(s.as_bytes());
@@ -191,7 +194,6 @@ impl Draft {
}

Ok(ret)

}
}


+ 10
- 7
melib/src/mailbox/email/parser.rs View File

@@ -135,16 +135,19 @@ named!(

named!(
header_no_val<(&[u8], &[u8])>,
do_parse!(
name: complete!(name) >>
tag!(b":") >>
opt!(is_a!(" \t")) >>
tag!(b"\n") >>
( { (name, b"") } )));
do_parse!(
name: complete!(name)
>> tag!(b":")
>> opt!(is_a!(" \t"))
>> tag!(b"\n")
>> ({ (name, b"") })
)
);

named!(
header<(&[u8], &[u8])>,
alt_complete!(header_no_val | header_has_val));
alt_complete!(header_no_val | header_has_val)
);
/* Parse all headers -> Vec<(&str, Vec<&str>)> */
named!(pub headers<std::vec::Vec<(&[u8], &[u8])>>,
many1!(complete!(header)));

+ 0
- 0
rustfmt.toml View File


+ 11
- 5
src/bin.rs View File

@@ -48,9 +48,9 @@ fn main() {
//let _stdout = stdout();
//let mut _stdout = _stdout.lock();
/*
let _stderr = stderr();
let mut _stderr = _stderr.lock();
*/
let _stderr = stderr();
let mut _stderr = _stderr.lock();
*/

/* Catch SIGWINCH to handle terminal resizing */
let signal = chan_signal::notify(&[Signal::WINCH]);
@@ -66,7 +66,11 @@ fn main() {
let menu = Entity::from(Box::new(AccountMenu::new(&state.context.accounts)));
let listing = listing::Listing::from(IndexStyle::Compact);
let b = Entity::from(Box::new(listing));
let tabs = Box::new(Tabbed::new(vec![Box::new(VSplit::new(menu, b, 90, true)), Box::new(AccountsPanel::new(&state.context)), Box::new(ContactList::default())]));
let tabs = Box::new(Tabbed::new(vec![
Box::new(VSplit::new(menu, b, 90, true)),
Box::new(AccountsPanel::new(&state.context)),
Box::new(ContactList::default()),
]));
let window = Entity::from(tabs);

let status_bar = Entity::from(Box::new(StatusBar::new(window)));
@@ -75,7 +79,9 @@ fn main() {
let xdg_notifications =
Entity::from(Box::new(ui::components::notifications::XDGNotifications {}));
state.register_entity(xdg_notifications);
state.register_entity(Entity::from(Box::new(ui::components::notifications::NotificationFilter {})));
state.register_entity(Entity::from(Box::new(
ui::components::notifications::NotificationFilter {},
)));

/* Keep track of the input mode. See ui::UIMode for details */
'main: loop {

+ 23
- 20
ui/src/components.rs View File

@@ -20,10 +20,10 @@
*/

/*!
Components are ways to handle application data. They can draw on the terminal and receive events, but also do other stuff as well. (For example, see the `notifications` module.)
Components are ways to handle application data. They can draw on the terminal and receive events, but also do other stuff as well. (For example, see the `notifications` module.)

See the `Component` Trait for more details.
*/
See the `Component` Trait for more details.
*/

use super::*;

@@ -76,8 +76,6 @@ const _DOUBLE_DOWN_AND_LEFT: char = 'โ•—';
const _DOUBLE_UP_AND_LEFT: char = 'โ•';
const _DOUBLE_UP_AND_RIGHT: char = 'โ•š';



type EntityId = Uuid;

/// `Entity` is a container for Components.
@@ -157,7 +155,9 @@ pub trait Component: Display + Debug + Send {
fn kill(&mut self, _id: EntityId) {}
fn set_id(&mut self, _id: EntityId) {}

fn get_shortcuts(&self, context: &Context) -> ShortcutMap { Default::default() }
fn get_shortcuts(&self, context: &Context) -> ShortcutMap {
Default::default()
}
}

/*
@@ -429,20 +429,23 @@ pub(crate) fn set_and_join_box(grid: &mut CellBuffer, idx: Pos, ch: char) {
}

pub fn create_box(grid: &mut CellBuffer, area: Area) {
let upper_left = upper_left!(area);
let bottom_right = bottom_right!(area);
if !is_valid_area!(area) {
return;
}
let upper_left = upper_left!(area);
let bottom_right = bottom_right!(area);

for x in get_x(upper_left)..get_x(bottom_right) {
grid[(x, get_y(upper_left))].set_ch(HORZ_BOUNDARY);
grid[(x, get_y(bottom_right))].set_ch(HORZ_BOUNDARY);
}
for x in get_x(upper_left)..get_x(bottom_right) {
grid[(x, get_y(upper_left))].set_ch(HORZ_BOUNDARY);
grid[(x, get_y(bottom_right))].set_ch(HORZ_BOUNDARY);
}

for y in get_y(upper_left)..get_y(bottom_right) {
grid[(get_x(upper_left), y)].set_ch(VERT_BOUNDARY);
grid[(get_x(bottom_right), y)].set_ch(VERT_BOUNDARY);
}
set_and_join_box(grid, upper_left, HORZ_BOUNDARY);
set_and_join_box(grid, set_x(upper_left, get_x(bottom_right)), HORZ_BOUNDARY);
set_and_join_box(grid, set_y(upper_left, get_y(bottom_right)), VERT_BOUNDARY);
set_and_join_box(grid, bottom_right, VERT_BOUNDARY);
for y in get_y(upper_left)..get_y(bottom_right) {
grid[(get_x(upper_left), y)].set_ch(VERT_BOUNDARY);
grid[(get_x(bottom_right), y)].set_ch(VERT_BOUNDARY);
}
set_and_join_box(grid, upper_left, HORZ_BOUNDARY);
set_and_join_box(grid, set_x(upper_left, get_x(bottom_right)), HORZ_BOUNDARY);
set_and_join_box(grid, set_y(upper_left, get_y(bottom_right)), VERT_BOUNDARY);
set_and_join_box(grid, bottom_right, VERT_BOUNDARY);
}

+ 57
- 33
ui/src/components/contacts.rs View File

@@ -78,7 +78,7 @@ impl ContactManager {
Color::Default,
((0, 0), (width, 0)),
false,
);
);
let (x, _) = write_string_to_grid(
"Last edited: ",
&mut self.content,
@@ -86,7 +86,7 @@ impl ContactManager {
Color::Default,
((x, 0), (width, 0)),
false,
);
);
write_string_to_grid(
&self.card.last_edited(),
&mut self.content,
@@ -94,15 +94,23 @@ impl ContactManager {
Color::Default,
((x, 0), (width, 0)),
false,
);
);
self.form = FormWidget::new("Save".into());
self.form.add_button(("Cancel".into(), false));
self.form.push(("First Name".into(), self.card.firstname().to_string()));
self.form.push(("Last Name".into(), self.card.lastname().to_string()));
self.form.push(("Additional Name".into(), self.card.additionalname().to_string()));
self.form.push(("Name Prefix".into(), self.card.name_prefix().to_string()));
self.form.push(("Name Suffix".into(), self.card.name_suffix().to_string()));
self.form.push(("E-mail".into(), self.card.email().to_string()));
self.form
.push(("First Name".into(), self.card.firstname().to_string()));
self.form
.push(("Last Name".into(), self.card.lastname().to_string()));
self.form.push((
"Additional Name".into(),
self.card.additionalname().to_string(),
));
self.form
.push(("Name Prefix".into(), self.card.name_prefix().to_string()));
self.form
.push(("Name Suffix".into(), self.card.name_suffix().to_string()));
self.form
.push(("E-mail".into(), self.card.email().to_string()));
self.form.push(("url".into(), self.card.url().to_string()));
self.form.push(("key".into(), self.card.key().to_string()));
}
@@ -120,55 +128,71 @@ impl Component for ContactManager {

let upper_left = upper_left!(area);
let bottom_right = bottom_right!(area);
self.form.draw(grid, (set_y(upper_left, get_y(upper_left) + 1), bottom_right), context);
self.form.draw(
grid,
(set_y(upper_left, get_y(upper_left) + 1), bottom_right),
context,
);
context.dirty_areas.push_back(area);
}

fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
if self.form.process_event(event, context) {
match self.form.buttons_result() {
None => {},
None => {}
Some(true) => {
let mut fields = std::mem::replace(&mut self.form, FormWidget::default()).collect().unwrap();
let fields: FnvHashMap<String, String> = fields.into_iter().map(|(s, v)| {
(s, match v {
Field::Text(v, _, _) | Field::TextArea(v, _) => v,
Field::Choice(mut v, c) => v.remove(c),
})}).collect();
let mut fields = std::mem::replace(&mut self.form, FormWidget::default())
.collect()
.unwrap();
let fields: FnvHashMap<String, String> = fields
.into_iter()
.map(|(s, v)| {
(
s,
match v {
Field::Text(v, _, _) | Field::TextArea(v, _) => v,
Field::Choice(mut v, c) => v.remove(c),
},
)
})
.collect();
let mut new_card = Card::from(fields);
new_card.set_id(*self.card.id());
context.accounts[self.account_pos].address_book.add_card(new_card);
context.accounts[self.account_pos]
.address_book
.add_card(new_card);
context.replies.push_back(UIEvent {
id: 0,
event_type: UIEventType::StatusEvent(StatusEvent::DisplayMessage("Saved.".into())),
event_type: UIEventType::StatusEvent(StatusEvent::DisplayMessage(
"Saved.".into(),
)),
});
context.replies.push_back(UIEvent {
id: 0,
event_type: UIEventType::EntityKill(self.id),
});
},
}
Some(false) => {
context.replies.push_back(UIEvent {
id: 0,
event_type: UIEventType::EntityKill(self.id),
});

},
}
}
return true;
}
/*
match event.event_type {
UIEventType::Input(Key::Char('\n')) => {
context.replies.push_back(UIEvent {
id: 0,
event_type: UIEventType::EntityKill(self.id),
});
return true;
},
_ => {},
}
*/
match event.event_type {
UIEventType::Input(Key::Char('\n')) => {
context.replies.push_back(UIEvent {
id: 0,
event_type: UIEventType::EntityKill(self.id),
});
return true;
},
_ => {},
}
*/
false
}


+ 51
- 23
ui/src/components/contacts/contact_list.rs View File

@@ -20,7 +20,7 @@ pub struct ContactList {
content: CellBuffer,

id_positions: Vec<CardId>,
mode: ViewMode,
dirty: bool,
view: Option<Entity>,
@@ -60,29 +60,30 @@ impl ContactList {
..Self::new()
}
}
fn initialize(&mut self, context: &mut Context) {
let account = &mut context.accounts[self.account_pos];
let book = &mut account.address_book;
self.length = book.len();
self.content.resize(MAX_COLS, book.len(), Cell::with_char(' '));
self.content
.resize(MAX_COLS, book.len(), Cell::with_char(' '));

self.id_positions.clear();
if self.id_positions.capacity() < book.len() {
self.id_positions.reserve(book.len());
self.id_positions.reserve(book.len());
}

for (i, c) in book.values().enumerate() {
self.id_positions.push(*c.id());
write_string_to_grid(
c.email(),
&mut self.content,
Color::Default,
Color::Default,
((0, i), (MAX_COLS - 1, book.len() - 1)),
false
);
false,
);
}
}
}
@@ -105,7 +106,15 @@ impl Component for ContactList {
if self.dirty {
self.initialize(context);
clear_area(grid, area);
copy_area(grid, &self.content, area, ((0, 0), (MAX_COLS - 1, self.content.size().1.saturating_sub(1))));
copy_area(
grid,
&self.content,
area,
(
(0, 0),
(MAX_COLS - 1, self.content.size().1.saturating_sub(1)),
),
);
context.dirty_areas.push_back(area);
self.dirty = false;
}
@@ -116,11 +125,27 @@ impl Component for ContactList {
/* Reset previously highlighted line */
let fg_color = Color::Default;
let bg_color = Color::Default;
change_colors(grid, (pos_inc(upper_left, (0, self.cursor_pos)), set_y(bottom_right, get_y(upper_left) + self.cursor_pos)), fg_color, bg_color);
change_colors(
grid,
(
pos_inc(upper_left, (0, self.cursor_pos)),
set_y(bottom_right, get_y(upper_left) + self.cursor_pos),
),
fg_color,
bg_color,
);

/* Highlight current line */
let bg_color = Color::Byte(246);
change_colors(grid, (pos_inc(upper_left, (0, self.new_cursor_pos)), set_y(bottom_right, get_y(upper_left) + self.new_cursor_pos)), fg_color, bg_color);
change_colors(
grid,
(
pos_inc(upper_left, (0, self.new_cursor_pos)),
set_y(bottom_right, get_y(upper_left) + self.new_cursor_pos),
),
fg_color,
bg_color,
);
self.cursor_pos = self.new_cursor_pos;
}

@@ -139,10 +164,10 @@ impl Component for ContactList {

self.mode = ViewMode::View(*entity.id());
self.view = Some(entity);
return true;
},
}
UIEventType::Input(ref key) if *key == shortcuts["edit_contact"] && self.length > 0 => {
let account = &mut context.accounts[self.account_pos];
let book = &mut account.address_book;
@@ -154,9 +179,9 @@ impl Component for ContactList {

self.mode = ViewMode::View(*entity.id());
self.view = Some(entity);
return true;
},
}
UIEventType::Input(Key::Char('n')) => {
let card = Card::new();
let mut manager = ContactManager::default();
@@ -165,27 +190,26 @@ impl Component for ContactList {
let entity = Entity::from(Box::new(manager));
self.mode = ViewMode::View(*entity.id());
self.view = Some(entity);
return true;
},
}
UIEventType::Input(Key::Up) => {
self.set_dirty();
self.new_cursor_pos = self.cursor_pos.saturating_sub(1);
return true;
},
}
UIEventType::Input(Key::Down) if self.cursor_pos < self.length.saturating_sub(1) => {
self.set_dirty();
self.new_cursor_pos += 1;
return true;
},
}
UIEventType::EntityKill(ref kill_id) if self.mode == ViewMode::View(*kill_id) => {
self.mode = ViewMode::List;
self.view.take();
self.set_dirty();
return true;

},
_ => {},
}
_ => {}
}
false
}
@@ -205,7 +229,11 @@ impl Component for ContactList {
self.mode = ViewMode::Close(uuid);
}
fn get_shortcuts(&self, context: &Context) -> ShortcutMap {
let mut map = self.view.as_ref().map(|p| p.get_shortcuts(context)).unwrap_or_default();
let mut map = self
.view
.as_ref()
.map(|p| p.get_shortcuts(context))
.unwrap_or_default();

let config_map = context.settings.shortcuts.contact_list.key_values();
map.insert("create_contact", (*config_map["create_contact"]).clone());

+ 1
- 2
ui/src/components/indexer.rs View File

@@ -58,8 +58,7 @@ impl Default for Indexer {
}

impl Indexer {
fn draw_menu(&mut self, _grid: &mut CellBuffer, _area: Area, _context: &mut Context) {
}
fn draw_menu(&mut self, _grid: &mut CellBuffer, _area: Area, _context: &mut Context) {}
}

impl Component for Indexer {

+ 1
- 1
ui/src/components/indexer/index.rs View File

@@ -42,7 +42,7 @@ impl Index {
let bg_color = if self.cursor_pos == idx {
Color::Byte(246)
/* } else if idx % 2 == 0 {
Color::Byte(236)*/
Color::Byte(236)*/
} else {
Color::Default
};

+ 2
- 1
ui/src/components/mail.rs View File

@@ -73,7 +73,8 @@ impl AccountMenu {
}
entries
},
}).collect();
})
.collect();
AccountMenu {
accounts,
dirty: true,

+ 19
- 21
ui/src/components/mail/accounts.rs View File

@@ -35,7 +35,6 @@ impl fmt::Display for AccountsPanel {
}
}


impl Component for AccountsPanel {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
if self.dirty {
@@ -46,26 +45,26 @@ impl Component for AccountsPanel {
Color::Default,
((2, 3), (120 - 1, 3)),
true,
);
);

for (i, a) in context.accounts.iter().enumerate() {
create_box(&mut self.content, ((2,5+i*10 ), (120-1, 15+i*10)));
create_box(&mut self.content, ((2, 5 + i * 10), (120 - 1, 15 + i * 10)));
let (x, y) = write_string_to_grid(
a.name(),
&mut self.content,
Color::Default,
Color::Default,
((3, 5 + i*10), (120 - 2, 5 + i*10)),
((3, 5 + i * 10), (120 - 2, 5 + i * 10)),
true,
);
);
write_string_to_grid(
" โ–’โ–ˆโ–ˆโ–’ ",
&mut self.content,
Color::Byte(32),
Color::Default,
((x, y), (120 - 2, 5 + i*10)),
((x, y), (120 - 2, 5 + i * 10)),
true,
);
);
write_string_to_grid(
&a.runtime_settings.account().identity,
&mut self.content,
@@ -73,10 +72,10 @@ impl Component for AccountsPanel {
Color::Default,
((4, y + 2), (120 - 2, y + 2)),
true,
);
);
if i == self.cursor {
for h in 1..8 {
self.content[(2, h+y+1)].set_ch('*');
self.content[(2, h + y + 1)].set_ch('*');
}
}
write_string_to_grid(
@@ -86,7 +85,7 @@ impl Component for AccountsPanel {
Color::Default,
((5, y + 3), (120 - 2, y + 3)),
true,
);
);
write_string_to_grid(
"- Contacts",
&mut self.content,
@@ -94,7 +93,7 @@ impl Component for AccountsPanel {
Color::Default,
((5, y + 4), (120 - 2, y + 4)),
true,
);
);
write_string_to_grid(
"- Mailing Lists",
&mut self.content,
@@ -102,10 +101,7 @@ impl Component for AccountsPanel {
Color::Default,
((5, y + 5), (120 - 2, y + 5)),
true,
);


);
}
self.dirty = false;
}
@@ -121,22 +117,24 @@ impl Component for AccountsPanel {
self.cursor = self.cursor.saturating_sub(1);
self.dirty = true;
return true;
},
}
UIEventType::Input(Key::Down) => {
if self.cursor + 1 < context.accounts.len() {
self.cursor += 1;
self.dirty = true;
}
return true;
},
}
UIEventType::Input(Key::Char('\n')) => {
context.replies.push_back(UIEvent {
id: 0,
event_type: UIEventType::Action(Tab(TabOpen(Some(Box::new(ContactList::for_account(self.cursor)))
)))});
event_type: UIEventType::Action(Tab(TabOpen(Some(Box::new(
ContactList::for_account(self.cursor),
))))),
});
return true;
},
_ => {},
}
_ => {}
}

false

+ 139
- 121
ui/src/components/mail/compose.rs View File

@@ -24,7 +24,6 @@ use super::*;
use melib::Draft;
use std::str::FromStr;


#[derive(Debug, PartialEq)]
enum Cursor {
Headers,
@@ -173,10 +172,15 @@ impl Composer {
let account_cursor = self.account_cursor;
for &k in &["Date", "From", "To", "Cc", "Bcc", "Subject"] {
if k == "To" {
self.form.push_cl((k.into(), headers[k].to_string(), Box::new(move |c, term| {
let book: &AddressBook = &c.accounts[account_cursor].address_book;
let results: Vec<String> = book.search(term);
results})));
self.form.push_cl((
k.into(),
headers[k].to_string(),
Box::new(move |c, term| {
let book: &AddressBook = &c.accounts[account_cursor].address_book;
let results: Vec<String> = book.search(term);
results
}),
));
} else {
self.form.push((k.into(), headers[k].to_string()));
}
@@ -184,36 +188,36 @@ impl Composer {
}

/*
let (x, y) = if k == "From" {
write_string_to_grid(
"โ—€ ",
grid,
Color::Byte(251),
Color::Default,
((x, y), set_y(bottom_right, y)),
true,
)
} else {
(x, y)
};
let (x, y) = write_string_to_grid(
&headers[k],
grid,
Color::Default,
bg_color,
((x, y), set_y(bottom_right, y)),
true,
);
if k == "From" {
write_string_to_grid(
" โ–ถ",
grid,
Color::Byte(251),
Color::Default,
((x, y), set_y(bottom_right, y)),
true,
)
*/
let (x, y) = if k == "From" {
write_string_to_grid(
"โ—€ ",
grid,
Color::Byte(251),
Color::Default,
((x, y), set_y(bottom_right, y)),
true,
)
} else {
(x, y)
};
let (x, y) = write_string_to_grid(
&headers[k],
grid,
Color::Default,
bg_color,
((x, y), set_y(bottom_right, y)),
true,
);
if k == "From" {
write_string_to_grid(
" โ–ถ",
grid,
Color::Byte(251),
Color::Default,
((x, y), set_y(bottom_right, y)),
true,
)
*/
}

impl Component for Composer {
@@ -283,14 +287,21 @@ impl Component for Composer {
}

if self.dirty {
for i in get_x(upper_left) + mid + 1..=get_x(upper_left) + mid + width.saturating_sub(0) {
for i in get_x(upper_left) + mid + 1..=get_x(upper_left) + mid + width.saturating_sub(0)
{
//set_and_join_box(grid, (i, header_height), HORZ_BOUNDARY);
//grid[(i, header_height)].set_fg(Color::Default);
//grid[(i, header_height)].set_bg(Color::Default);
}
}

let header_area = (pos_inc(upper_left, (mid + 1, 0)), (get_x(bottom_right).saturating_sub(mid), get_y(upper_left) + header_height + 1));
let header_area = (
pos_inc(upper_left, (mid + 1, 0)),
(
get_x(bottom_right).saturating_sub(mid),
get_y(upper_left) + header_height + 1,
),
);
let body_area = (
pos_inc(upper_left, (mid + 1, header_height + 2)),
pos_dec(bottom_right, ((mid, 0))),
@@ -303,21 +314,26 @@ impl Component for Composer {
ViewMode::Overview | ViewMode::Pager => {
self.pager.set_dirty();
self.pager.draw(grid, body_area, context);
},
}
ViewMode::Discard(_) => {
/* Let user choose whether to quit with/without saving or cancel */
let mid_x = {
std::cmp::max(width!(area) / 2, width / 2) - width / 2
};
let mid_y = {
std::cmp::max(height!(area) / 2, 11) - 11
};
let mid_x = { std::cmp::max(width!(area) / 2, width / 2) - width / 2 };
let mid_y = { std::cmp::max(height!(area) / 2, 11) - 11 };

let upper_left = upper_left!(body_area);
let bottom_right = bottom_right!(body_area);
let area = (pos_inc(upper_left, (mid_x, mid_y)), pos_dec(bottom_right, (mid_x, mid_y)));
let area = (
pos_inc(upper_left, (mid_x, mid_y)),
pos_dec(bottom_right, (mid_x, mid_y)),
);
create_box(grid, area);
let area = (pos_inc(upper_left, (mid_x + 2, mid_y + 2)), pos_dec(bottom_right, (mid_x.saturating_sub(2), mid_y.saturating_sub(2))));
let area = (
pos_inc(upper_left, (mid_x + 2, mid_y + 2)),
pos_dec(
bottom_right,
(mid_x.saturating_sub(2), mid_y.saturating_sub(2)),
),
);

let (_, y) = write_string_to_grid(
&format!("Draft \"{:10}\"", self.draft.headers()["Subject"]),
@@ -326,7 +342,7 @@ impl Component for Composer {
Color::Default,
area,
true,
);
);
let (_, y) = write_string_to_grid(
"[x] quit without saving",
grid,
@@ -334,7 +350,7 @@ impl Component for Composer {
Color::Default,
(set_y(upper_left!(area), y + 2), bottom_right!(area)),
true,
);
);
let (_, y) = write_string_to_grid(
"[y] save draft and quit",
grid,
@@ -342,7 +358,7 @@ impl Component for Composer {
Color::Default,
(set_y(upper_left!(area), y + 1), bottom_right!(area)),
true,
);
);
write_string_to_grid(
"[n] cancel",
grid,
@@ -350,9 +366,8 @@ impl Component for Composer {
Color::Default,
(set_y(upper_left!(area), y + 1), bottom_right!(area)),
true,
);

},
);
}
}

context.dirty_areas.push_back(area);
@@ -371,7 +386,7 @@ impl Component for Composer {
self.dirty = true;
return true;
}
},
}
_ => {}
}
if self.form.process_event(event, context) {
@@ -381,36 +396,36 @@ impl Component for Composer {
match event.event_type {
UIEventType::Resize => {
self.set_dirty();
},
}
/*
/* Switch e-mail From: field to the `left` configured account. */
UIEventType::Input(Key::Left) if self.cursor == Cursor::From => {
self.account_cursor = self.account_cursor.saturating_sub(1);
self.draft.headers_mut().insert(
"From".into(),
get_display_name(context, self.account_cursor),
);
self.dirty = true;
return true;
self.account_cursor = self.account_cursor.saturating_sub(1);
self.draft.headers_mut().insert(
"From".into(),
get_display_name(context, self.account_cursor),
);
self.dirty = true;
return true;
}
/* Switch e-mail From: field to the `right` configured account. */
UIEventType::Input(Key::Right) if self.cursor == Cursor::From => {
if self.account_cursor + 1 < context.accounts.len() {
self.account_cursor += 1;
self.draft.headers_mut().insert(
"From".into(),
get_display_name(context, self.account_cursor),
);
self.dirty = true;
}
return true;
if self.account_cursor + 1 < context.accounts.len() {
self.account_cursor += 1;
self.draft.headers_mut().insert(
"From".into(),
get_display_name(context, self.account_cursor),
);
self.dirty = true;
}
return true;
}*/
UIEventType::Input(Key::Up) => {
self.cursor = Cursor::Headers;
},
}
UIEventType::Input(Key::Down) => {
self.cursor = Cursor::Body;
},
}
UIEventType::Input(Key::Char(key)) if self.mode.is_discard() => {
match (key, &self.mode) {
('x', ViewMode::Discard(u)) => {
@@ -420,7 +435,7 @@ impl Component for Composer {
});
return true;
}
('n', _) => {},
('n', _) => {}
('y', ViewMode::Discard(u)) => {
let account = &context.accounts[self.account_cursor];
let draft = std::mem::replace(&mut self.draft, Draft::default());
@@ -432,7 +447,7 @@ impl Component for Composer {
event_type: UIEventType::Action(Tab(Kill(*u))),
});
return true;
},
}
_ => {
return false;
}
@@ -454,48 +469,48 @@ impl Component for Composer {
return true;
}
UIEventType::Input(Key::Char('e')) if self.cursor == Cursor::Body => {
/* Edit draft in $EDITOR */
use std::process::{Command, Stdio};
/* Kill input thread so that spawned command can be sole receiver of stdin */
{
context.input_kill();
}
/* update Draft's headers based on form values */
self.update_draft();
let mut f =
create_temp_file(self.draft.to_string().unwrap().as_str().as_bytes(), None);
//let mut f = Box::new(std::fs::File::create(&dir).unwrap());
// TODO: check exit status
Command::new("vim")
.arg("+/^$")
.arg(&f.path())
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.output()
.expect("failed to execute process");
let result = f.read_to_string();
self.draft = Draft::from_str(result.as_str()).unwrap();
self.initialized = false;
context.replies.push_back(UIEvent {
id: 0,
event_type: UIEventType::Fork(ForkType::Finished),
});
context.restore_input();
/*
Cursor::To | Cursor::Cc | Cursor::Bcc => {
let account = &context.accounts[self.account_cursor];
let mut entries = account.address_book.values().map(|v| (v.id().as_bytes().to_vec(), v.email().to_string())).collect();
self.mode = ViewMode::Selector(Selector::new(entries, true));
},
Cursor::Attachments => {
unimplemented!()
},
Cursor::From => {
return true;
}
*/
/* Edit draft in $EDITOR */
use std::process::{Command, Stdio};
/* Kill input thread so that spawned command can be sole receiver of stdin */
{
context.input_kill();
}
/* update Draft's headers based on form values */
self.update_draft();
let mut f =
create_temp_file(self.draft.to_string().unwrap().as_str().as_bytes(), None);
//let mut f = Box::new(std::fs::File::create(&dir).unwrap());
// TODO: check exit status
Command::new("vim")
.arg("+/^$")
.arg(&f.path())
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.output()
.expect("failed to execute process");
let result = f.read_to_string();
self.draft = Draft::from_str(result.as_str()).unwrap();
self.initialized = false;
context.replies.push_back(UIEvent {
id: 0,
event_type: UIEventType::Fork(ForkType::Finished),
});
context.restore_input();
/*
Cursor::To | Cursor::Cc | Cursor::Bcc => {
let account = &context.accounts[self.account_cursor];
let mut entries = account.address_book.values().map(|v| (v.id().as_bytes().to_vec(), v.email().to_string())).collect();
self.mode = ViewMode::Selector(Selector::new(entries, true));
},
Cursor::Attachments => {
unimplemented!()
},
Cursor::From => {
return true;
}
*/
self.dirty = true;
return true;
}
@@ -505,11 +520,14 @@ impl Component for Composer {
}

fn is_dirty(&self) -> bool {
self.dirty || self.pager.is_dirty() || self
.reply_context
.as_ref()
.map(|(_, p)| p.is_dirty())
.unwrap_or(false) || self.form.is_dirty()
self.dirty
|| self.pager.is_dirty()
|| self
.reply_context
.as_ref()
.map(|(_, p)| p.is_dirty())
.unwrap_or(false)
|| self.form.is_dirty()
}

fn set_dirty(&mut self) {

+ 0
- 1
ui/src/components/mail/listing.rs View File

@@ -131,7 +131,6 @@ impl From<IndexStyle> for Listing {
IndexStyle::Plain => Listing::Plain(Default::default()),
IndexStyle::Threaded => Listing::Threaded(Default::default()),
IndexStyle::Compact => Listing::Compact(Default::default()),

}
}
}

+ 80
- 12
ui/src/components/mail/listing/compact.rs View File

@@ -342,7 +342,9 @@ impl CompactListing {

fn format_date(envelope: &Envelope) -> String {
let d = std::time::UNIX_EPOCH + std::time::Duration::from_secs(envelope.date());
let now: std::time::Duration = std::time::SystemTime::now().duration_since(d).unwrap_or_else(|_| std::time::Duration::new(std::u64::MAX, 0));
let now: std::time::Duration = std::time::SystemTime::now()
.duration_since(d)
.unwrap_or_else(|_| std::time::Duration::new(std::u64::MAX, 0));
match now.as_secs() {
n if n < 10 * 60 * 60 => format!("{} hours ago{}", n / (60 * 60), " ".repeat(8)),
n if n < 24 * 60 * 60 => format!("{} hours ago{}", n / (60 * 60), " ".repeat(7)),
@@ -527,20 +529,86 @@ impl Component for CompactListing {
}

fn get_shortcuts(&self, context: &Context) -> ShortcutMap {
let mut map = self.view.as_ref().map(|p| p.get_shortcuts(context)).unwrap_or_default();
let mut map = self
.view
.as_ref()
.map(|p| p.get_shortcuts(context))
.unwrap_or_default();

let config_map = context.settings.shortcuts.compact_listing.key_values();
map.insert("open_thread", if let Some(key) = config_map.get("open_thread") { (*key).clone() } else { Key::Char('\n') });
map.insert("prev_page", if let Some(key) = config_map.get("prev_page") { (*key).clone() } else { Key::PageUp });
map.insert("next_page", if let Some(key) = config_map.get("next_page") { (*key).clone() } else { Key::PageDown });
map.insert("exit_thread", if let Some(key) = config_map.get("exit_thread") { (*key).clone() } else { Key::Char('i') });
map.insert("prev_folder", if let Some(key) = config_map.get("prev_folder") { (*key).clone() } else { Key::Char('J') });
map.insert("next_folder", if let Some(key) = config_map.get("next_folder") { (*key).clone() } else { Key::Char('K') });
map.insert("prev_account", if let Some(key) = config_map.get("prev_account") { (*key).clone() } else { Key::Char('h') });
map.insert("next_account", if let Some(key) = config_map.get("next_account") { (*key).clone() } else { Key::Char('l') });
map.insert("new_mail", if let Some(key) = config_map.get("new_mail") { (*key).clone() } else { Key::Char('m') });
map.insert(
"open_thread",
if let Some(key) = config_map.get("open_thread") {
(*key).clone()
} else {
Key::Char('\n')
},
);
map.insert(
"prev_page",
if let Some(key) = config_map.get("prev_page") {
(*key).clone()
} else {
Key::PageUp
},
);
map.insert(
"next_page",
if let Some(key) = config_map.get("next_page") {
(*key).clone()
} else {
Key::PageDown
},
);
map.insert(
"exit_thread",
if let Some(key) = config_map.get("exit_thread") {
(*key).clone()
} else {
Key::Char('i')
},
);
map.insert(
"prev_folder",
if let Some(key) = config_map.get("prev_folder") {
(*key).clone()
} else {
Key::Char('J')
},
);
map.insert(
"next_folder",
if let Some(key) = config_map.get("next_folder") {
(*key).clone()
} else {
Key::Char('K')
},
);
map.insert(
"prev_account",
if let Some(key) = config_map.get("prev_account") {
(*key).clone()
} else {
Key::Char('h')
},
);
map.insert(
"next_account",
if let Some(key) = config_map.get("next_account") {
(*key).clone()
} else {
Key::Char('l')
},
);
map.insert(
"new_mail",
if let Some(key) = config_map.get("new_mail") {
(*key).clone()
} else {
Key::Char('m')
},
);

map
}
}

+ 8
- 4
ui/src/components/mail/listing/thread.rs View File

@@ -413,7 +413,9 @@ impl ThreadListing {
}
fn format_date(envelope: &Envelope) -> String {
let d = std::time::UNIX_EPOCH + std::time::Duration::from_secs(envelope.date());
let now: std::time::Duration = std::time::SystemTime::now().duration_since(d).unwrap_or_else(|_| std::time::Duration::new(std::u64::MAX, 0));
let now: std::time::Duration = std::time::SystemTime::now()
.duration_since(d)
.unwrap_or_else(|_| std::time::Duration::new(std::u64::MAX, 0));
match now.as_secs() {
n if n < 10 * 60 * 60 => format!("{} hours ago{}", n / (60 * 60), " ".repeat(8)),
n if n < 24 * 60 * 60 => format!("{} hours ago{}", n / (60 * 60), " ".repeat(7)),
@@ -486,8 +488,10 @@ impl Component for ThreadListing {
backend.operation(hash, folder_hash)
};
let mailbox = &mut account[self.cursor_pos.1].as_mut().unwrap();
let envelope: &mut Envelope =
mailbox.collection.get_mut(&self.locations[self.cursor_pos.2]).unwrap();
let envelope: &mut Envelope = mailbox
.collection
.get_mut(&self.locations[self.cursor_pos.2])
.unwrap();
envelope.set_seen(op).unwrap();
true
} else {
@@ -538,7 +542,7 @@ impl Component for ThreadListing {
self.cursor_pos.0,
self.cursor_pos.1,
self.locations[self.cursor_pos.2],
);
);

self.view = Some(MailView::new(coordinates, None, None));
self.view.as_mut().unwrap().draw(

+ 29
- 18
ui/src/components/mail/view.rs View File

@@ -123,7 +123,8 @@ impl MailView {
v.extend(html_filter.wait_with_output().unwrap().stdout);
}
})),
)).into_owned();
))
.into_owned();
match self.mode {
ViewMode::Normal | ViewMode::Subview => {
let mut t = body_text.to_string();
@@ -171,8 +172,8 @@ impl MailView {
let mut ret = "Viewing attachment. Press `r` to return \n".to_string();
ret.push_str(&attachments[aidx].text());
ret
},
ViewMode::ContactSelector(_) => { unimplemented!()},
}
ViewMode::ContactSelector(_) => unimplemented!(),
}
}
pub fn plain_text_to_buf(s: &str, highlight_urls: bool) -> CellBuffer {
@@ -341,13 +342,13 @@ impl Component for MailView {
};
self.dirty = false;
}
match self.mode {
ViewMode::Subview => {
if let Some(s) = self.subview.as_mut() {
s.draw(grid, (set_y(upper_left, y + 1), bottom_right), context);
}
},
}
ViewMode::ContactSelector(ref mut s) => {
clear_area(grid, (set_y(upper_left, y + 1), bottom_right));
s.draw(grid, (set_y(upper_left, y + 1), bottom_right), context);
@@ -368,12 +369,12 @@ impl Component for MailView {
return true;
}
}
},
}
ViewMode::ContactSelector(ref mut s) => {
if s.process_event(event, context) {
return true;
}
},
}
_ => {
if let Some(p) = self.pager.as_mut() {
if p.process_event(event, context) {
@@ -386,21 +387,25 @@ impl Component for MailView {
match event.event_type {
UIEventType::Input(Key::Char('c')) => {
/*
let mut new_card: Card = Card::new();
new_card.set_email(&envelope.from()[0].get_email());
new_card.set_firstname(&envelope.from()[0].get_display_name());
let mut new_card: Card = Card::new();
new_card.set_email(&envelope.from()[0].get_email());
new_card.set_firstname(&envelope.from()[0].get_display_name());

eprintln!("{:?}", new_card);
eprintln!("{:?}", new_card);

*/
*/
if let ViewMode::ContactSelector(_) = self.mode {
if let ViewMode::ContactSelector(s) = std::mem::replace(&mut self.mode, ViewMode::Normal) {
if let ViewMode::ContactSelector(s) =
std::mem::replace(&mut self.mode, ViewMode::Normal)
{
for c in s.collect() {
let mut new_card: Card = Card::new();
let email = String::from_utf8(c).unwrap();
new_card.set_email(&email);
new_card.set_firstname("");
context.accounts[self.coordinates.0].address_book.add_card(new_card);
context.accounts[self.coordinates.0]
.address_book
.add_card(new_card);
}
//eprintln!("{:?}", s.collect());
}
@@ -413,12 +418,18 @@ impl Component for MailView {
.unwrap();
let envelope: &Envelope = &mailbox.collection[&self.coordinates.2];
let mut entries = Vec::new();
entries.push((envelope.from()[0].get_email().into_bytes(), format!("{}", envelope.from()[0])));
entries.push((envelope.to()[0].get_email().into_bytes(), format!("{}", envelope.to()[0])));
entries.push((
envelope.from()[0].get_email().into_bytes(),
format!("{}", envelope.from()[0]),
));
entries.push((
envelope.to()[0].get_email().into_bytes(),
format!("{}", envelope.to()[0]),
));
self.mode = ViewMode::ContactSelector(Selector::new(entries, true));
self.dirty = true;
//context.accounts.context(self.coordinates.0).address_book.add_card(new_card);
},
}
UIEventType::Input(Key::Esc) | UIEventType::Input(Key::Alt('')) => {
self.cmd_buf.clear();
context.replies.push_back(UIEvent {
@@ -603,7 +614,7 @@ impl Component for MailView {
true
}
fn is_dirty(&self) -> bool {
self.dirty
self.dirty
|| self.pager.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
|| self.subview.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
|| if let ViewMode::ContactSelector(ref s) = self.mode {

+ 6
- 3
ui/src/components/mail/view/envelope.rs View File

@@ -108,7 +108,8 @@ impl EnvelopeView {
v.extend(html_filter.wait_with_output().unwrap().stdout);
}
})),
)).into_owned();
))
.into_owned();
match self.mode {
ViewMode::Normal | ViewMode::Subview => {
let mut t = body_text.to_string();
@@ -378,8 +379,10 @@ impl Component for EnvelopeView {
ContentType::MessageRfc822 => {
self.mode = ViewMode::Subview;
self.subview = Some(Box::new(Pager::from_string(
String::from_utf8_lossy(&decode_rec(u, None)).to_string(), context,
None, None
String::from_utf8_lossy(&decode_rec(u, None)).to_string(),
context,
None,
None,
)));
}


+ 19
- 25
ui/src/components/notifications.rs View File

@@ -20,8 +20,8 @@
*/

/*!
Notification handling components.
*/
Notification handling components.
*/
use notify_rust::Notification as notify_Notification;
use std::process::{Command, Stdio};

@@ -45,12 +45,8 @@ impl Component for XDGNotifications {
notify_Notification::new()
.appname("meli")
.icon("mail-message-new")
.summary(
title
.as_ref()
.map(|v| v.as_str())
.unwrap_or("Event"),
).body(&escape_str(body))
.summary(title.as_ref().map(|v| v.as_str()).unwrap_or("Event"))
.body(&escape_str(body))
.icon("dialog-information")
.show()
.unwrap();
@@ -60,7 +56,6 @@ impl Component for XDGNotifications {
fn set_dirty(&mut self) {}
}


fn escape_str(s: &str) -> String {
let mut ret: String = String::with_capacity(s.len());
for c in s.chars() {
@@ -72,16 +67,17 @@ fn escape_str(s: &str) -> String {
'"' => ret.push_str("&quot;"),
_ => {
let i = c as u32;
if (0x1 <= i && i <= 0x8) ||
(0xb <= i && i <= 0xc) ||
(0xe <= i && i <= 0x1f) ||
(0x7f <= i && i <= 0x84) ||
(0x86 <= i && i <= 0x9f) {
if (0x1 <= i && i <= 0x8)
|| (0xb <= i && i <= 0xc)
|| (0xe <= i && i <= 0x1f)
|| (0x7f <= i && i <= 0x84)
|| (0x86 <= i && i <= 0x9f)
{
ret.push_str(&format!("&#{:x}%{:x};", i, i));
} else {
ret.push(c);
}
},
}
}
}
ret
@@ -123,16 +119,14 @@ impl Component for NotificationFilter {
if let UIEventType::Notification(ref title, ref body) = event.event_type {
if let Some(ref bin) = context.runtime_settings.notifications.script {
if let Err(v) = Command::new(bin)
.arg(title
.as_ref()
.map(|v| v.as_str())
.unwrap_or("Event"))
.arg(body)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn() {
eprintln!("{:?}",v);
}
.arg(title.as_ref().map(|v| v.as_str()).unwrap_or("Event"))
.arg(body)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
{
eprintln!("{:?}", v);
}
}
}
false

+ 85
- 44
ui/src/components/utilities.rs View File

@@ -252,7 +252,12 @@ impl Pager {
self.cursor_pos = 0;
self.max_cursor_pos = None;
}
pub fn from_string(mut text: String, context: &mut Context, cursor_pos: Option<usize>, width: Option<usize>) -> Self {
pub fn from_string(
mut text: String,
context: &mut Context,
cursor_pos: Option<usize>,
width: Option<usize>,
) -> Self {
let pager_filter: Option<&String> = context.settings.pager.filter.as_ref();
//let format_flowed: bool = context.settings.pager.format_flowed;
if let Some(bin) = pager_filter {
@@ -275,10 +280,10 @@ impl Pager {
.wait_with_output()
.expect("Failed to wait on filter")
.stdout,
).to_string();
)
.to_string();
}


let content = {
let lines: Vec<&str> = if let Some(width) = width {
word_break_string(text.as_str(), width)
@@ -313,7 +318,7 @@ impl Pager {
let height = lines.len() + 1;
let width = width.unwrap_or_else(|| lines.iter().map(|l| l.len()).max().unwrap_or(0));
let mut content = CellBuffer::new(width, height, Cell::with_char(' '));
Pager::print_string(&mut content, lines);
Pager {
text: text.to_string(),
@@ -347,7 +352,7 @@ impl Pager {
Color::Default,
((0, i), (width - 1, i)),
true,
);
);
}
}
pub fn cursor_pos(&self) -> usize {
@@ -824,7 +829,10 @@ impl Tabbed {
let cslice: &mut [Cell] = grid;
//TODO: bounds check
let cslice_len = cslice.len();
for c in cslice[(y * cols) + x.saturating_sub(1)..std::cmp::min((y * cols) + x.saturating_sub(1), cslice_len)].iter_mut() {
for c in cslice[(y * cols) + x.saturating_sub(1)
..std::cmp::min((y * cols) + x.saturating_sub(1), cslice_len)]
.iter_mut()
{
c.set_bg(Color::Byte(7));
c.set_ch(' ');
}
@@ -846,7 +854,13 @@ impl fmt::Display for Tabbed {
impl Component for Tabbed {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
if self.dirty {
clear_area(grid, (upper_left!(area), set_x(upper_left!(area), get_x(bottom_right!(area)))));
clear_area(
grid,
(
upper_left!(area),
set_x(upper_left!(area), get_x(bottom_right!(area))),
),
);
self.dirty = false;
}

@@ -871,29 +885,50 @@ impl Component for Tabbed {

if self.show_shortcuts {
let area = (
pos_inc(upper_left!(area), (2, 1)), set_x(bottom_right!(area), get_x(bottom_right!(area)).saturating_sub(2)));
pos_inc(upper_left!(area), (2, 1)),
set_x(
bottom_right!(area),
get_x(bottom_right!(area)).saturating_sub(2),
),
);
clear_area(grid, area);
create_box(grid, area);

// TODO: print into a pager
for (idx, (k, v)) in self.children[self.cursor_pos].get_shortcuts(context).into_iter().enumerate() {
let (x, y) = write_string_to_grid(
&k,
grid,
Color::Byte(29),
Color::Default,
(pos_inc(upper_left!(area), (2, 1 + idx)), set_x(bottom_right!(area), get_x(bottom_right!(area)).saturating_sub(2))),
false,
);
write_string_to_grid(
&format!("{:?}", v),
grid,
Color::Default,
Color::Default,
((x + 2, y), set_x(bottom_right!(area), get_x(bottom_right!(area)).saturating_sub(2))),
false,
);
};
for (idx, (k, v)) in self.children[self.cursor_pos]
.get_shortcuts(context)
.into_iter()
.enumerate()
{
let (x, y) = write_string_to_grid(
&k,
grid,
Color::Byte(29),
Color::Default,
(
pos_inc(upper_left!(area), (2, 1 + idx)),
set_x(
bottom_right!(area),
get_x(bottom_right!(area)).saturating_sub(2),
),
),
false,
);
write_string_to_grid(
&format!("{:?}", v),
grid,
Color::Default,
Color::Default,
(
(x + 2, y),
set_x(
bottom_right!(area),
get_x(bottom_right!(area)).saturating_sub(2),
),
),
false,
);
}
context.dirty_areas.push_back(area);
}
}
@@ -959,12 +994,12 @@ impl Component for Tabbed {
}
}


type EntryIdentifier = Vec<u8>;
/// Shows selection to user
#[derive(Debug, PartialEq)]
pub struct Selector {