diff options
author | Manos Pitsidianakis <el13635@mail.ntua.gr> | 2018-08-11 18:00:21 +0300 |
---|---|---|
committer | Manos Pitsidianakis <el13635@mail.ntua.gr> | 2019-06-10 19:40:28 +0300 |
commit | b98a04f35bffa29392c66cc8094f0c1572ebdac4 (patch) | |
tree | bf3892f2d3d211cb807b27a95749174e6db55bc3 /ui | |
parent | 7a6fc1ce9454bb3483d70d72f4dc054b7d7c4d98 (diff) |
Make backend folders completely agnostic (remove maildir logic from
conf)
Diffstat (limited to 'ui')
-rw-r--r-- | ui/src/components/mail/compose.rs | 45 | ||||
-rw-r--r-- | ui/src/components/mail/listing/compact.rs | 679 | ||||
-rw-r--r-- | ui/src/components/mail/listing/mod.rs (renamed from ui/src/components/mail/listing.rs) | 16 | ||||
-rw-r--r-- | ui/src/components/mail/mod.rs | 28 | ||||
-rw-r--r-- | ui/src/components/mail/view/html.rs | 8 | ||||
-rw-r--r-- | ui/src/components/mail/view/mod.rs | 97 | ||||
-rw-r--r-- | ui/src/components/mail/view/thread.rs | 101 | ||||
-rw-r--r-- | ui/src/components/mod.rs | 15 | ||||
-rw-r--r-- | ui/src/components/notifications.rs | 8 | ||||
-rw-r--r-- | ui/src/components/utilities.rs | 154 | ||||
-rw-r--r-- | ui/src/compose/mod.rs | 1 | ||||
-rw-r--r-- | ui/src/state.rs | 15 | ||||
-rw-r--r-- | ui/src/types/helpers.rs | 9 |
13 files changed, 1114 insertions, 62 deletions
diff --git a/ui/src/components/mail/compose.rs b/ui/src/components/mail/compose.rs new file mode 100644 index 00000000..0a01eea8 --- /dev/null +++ b/ui/src/components/mail/compose.rs @@ -0,0 +1,45 @@ +/* + * meli - ui crate + * + * Copyright 2017-2018 Manos Pitsidianakis + * + * This file is part of meli. + * + * meli is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * meli is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with meli. If not, see <http://www.gnu.org/licenses/>. + */ + +use super::*; + +pub struct Composer {} + +impl fmt::Display for Composer { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // TODO display subject/info + write!(f, "compose") + } +} + +impl Component for Composer { + fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { + clear_area(grid, area); + context.dirty_areas.push_back(area); + } + + fn process_event(&mut self, event: &UIEvent, context: &mut Context) {} + + fn is_dirty(&self) -> bool { + true + } + fn set_dirty(&mut self) {} +} diff --git a/ui/src/components/mail/listing/compact.rs b/ui/src/components/mail/listing/compact.rs new file mode 100644 index 00000000..71159b3d --- /dev/null +++ b/ui/src/components/mail/listing/compact.rs @@ -0,0 +1,679 @@ +/* + * meli - ui crate. + * + * Copyright 2017-2018 Manos Pitsidianakis + * + * This file is part of meli. + * + * meli is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * meli is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with meli. If not, see <http://www.gnu.org/licenses/>. + */ + +use super::*; +const MAX_COLS: usize = 500; + +/// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the thread's content in a +/// `ThreadView`. +pub struct CompactMailListing { + /// (x, y, z): x is accounts, y is folders, z is index inside a folder. + cursor_pos: (usize, usize, usize), + new_cursor_pos: (usize, usize, usize), + length: usize, + sort: (SortField, SortOrder), + //subsort: (SortField, SortOrder), + /// Cache current view. + content: CellBuffer, + /// If we must redraw on next redraw event + dirty: bool, + /// If `self.view` exists or not. + unfocused: bool, + view: Option<ThreadView>, +} + +impl Default for CompactMailListing { + fn default() -> Self { + Self::new() + } +} + +impl fmt::Display for CompactMailListing { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "mail") + } +} + +impl CompactMailListing { + pub fn new() -> Self { + let content = CellBuffer::new(0, 0, Cell::with_char(' ')); + CompactMailListing { + cursor_pos: (0, 1, 0), + new_cursor_pos: (0, 0, 0), + length: 0, + sort: (SortField::Date, SortOrder::Desc), + //subsort: (SortField::Date, SortOrder::Asc), + content: content, + dirty: true, + unfocused: false, + view: None, + } + } + /// Fill the `self.content` `CellBuffer` with the contents of the account folder the user has + /// chosen. + fn refresh_mailbox(&mut self, context: &mut Context) { + self.dirty = true; + self.cursor_pos.2 = 0; + self.new_cursor_pos.2 = 0; + self.cursor_pos.1 = self.new_cursor_pos.1; + self.cursor_pos.0 = self.new_cursor_pos.0; + + // Inform State that we changed the current folder view. + context.replies.push_back(UIEvent { + id: 0, + event_type: UIEventType::RefreshMailbox((self.cursor_pos.0, self.cursor_pos.1)), + }); + // Get mailbox as a reference. + // + loop { + // TODO: Show progress visually + if let Ok(()) = context.accounts[self.cursor_pos.0].status(self.cursor_pos.1) { + break; + } + } + let mailbox = &mut context.accounts[self.cursor_pos.0][self.cursor_pos.1] + .as_ref() + .unwrap(); + + self.length = mailbox.threads.len(); + let mut content = CellBuffer::new(MAX_COLS, self.length + 1, Cell::with_char(' ')); + if self.length == 0 { + write_string_to_grid( + &format!("Folder `{}` is empty.", mailbox.folder.name()), + &mut content, + Color::Default, + Color::Default, + ((0, 0), (MAX_COLS - 1, 0)), + true, + ); + self.content = content; + return; + } + + // TODO: Fix the threaded hell and refactor stuff into seperate functions and/or modules. + let mut indentations: Vec<bool> = Vec::with_capacity(6); + let mut thread_idx = 0; // needed for alternate thread colors + /* Draw threaded view. */ + let mut local_collection: Vec<usize> = mailbox.threaded_collection.clone(); + let threads: &Vec<Container> = &mailbox.threads; + local_collection.sort_by(|a, b| match self.sort { + (SortField::Date, SortOrder::Desc) => { + mailbox.thread(*b).date().cmp(&mailbox.thread(*a).date()) + } + (SortField::Date, SortOrder::Asc) => { + mailbox.thread(*a).date().cmp(&mailbox.thread(*b).date()) + } + (SortField::Subject, SortOrder::Desc) => { + let a = mailbox.thread(*a); + let b = mailbox.thread(*b); + let ma = &mailbox.collection[*a.message().as_ref().unwrap()]; + let mb = &mailbox.collection[*b.message().as_ref().unwrap()]; + ma.subject().cmp(&mb.subject()) + } + (SortField::Subject, SortOrder::Asc) => { + let a = mailbox.thread(*a); + let b = mailbox.thread(*b); + let ma = &mailbox.collection[*a.message().as_ref().unwrap()]; + let mb = &mailbox.collection[*b.message().as_ref().unwrap()]; + mb.subject().cmp(&ma.subject()) + } + }); + let mut iter = local_collection.iter().enumerate().peekable(); + let len = mailbox + .threaded_collection + .len() + .to_string() + .chars() + .count(); + /* This is just a desugared for loop so that we can use .peek() */ + while let Some((idx, i)) = iter.next() { + let container = &threads[*i]; + let indentation = container.indentation(); + + if indentation == 0 { + thread_idx += 1; + } + + assert!(container.has_message()); + match iter.peek() { + Some(&(_, x)) if threads[*x].indentation() == indentation => { + indentations.pop(); + indentations.push(true); + } + _ => { + indentations.pop(); + indentations.push(false); + } + } + if container.has_sibling() { + indentations.pop(); + indentations.push(true); + } + let envelope: &Envelope = &mailbox.collection[container.message().unwrap()]; + let fg_color = if !envelope.is_seen() { + Color::Byte(0) + } else { + Color::Default + }; + let bg_color = if !envelope.is_seen() { + Color::Byte(251) + } else if thread_idx % 2 == 0 { + Color::Byte(236) + } else { + Color::Default + }; + let (x, _) = write_string_to_grid( + &CompactMailListing::make_thread_entry( + envelope, + idx, + indentation, + container, + &indentations, + len, + ), + &mut content, + fg_color, + bg_color, + ((0, idx), (MAX_COLS - 1, idx)), + false, + ); + for x in x..MAX_COLS { + content[(x, idx)].set_ch(' '); + content[(x, idx)].set_bg(bg_color); + } + + match iter.peek() { + Some(&(_, x)) if threads[*x].indentation() > indentation => { + indentations.push(false); + } + Some(&(_, x)) if threads[*x].indentation() < indentation => { + for _ in 0..(indentation - threads[*x].indentation()) { + indentations.pop(); + } + } + _ => {} + } + } + + self.content = content; + } + + fn highlight_line_self(&mut self, idx: usize, context: &Context) { + let threaded = context.accounts[self.cursor_pos.0] + .runtime_settings + .threaded; + let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1] + .as_ref() + .unwrap(); + let envelope: &Envelope = if threaded { + let i = mailbox.threaded_mail(idx); + &mailbox.collection[i] + } else { + &mailbox.collection[idx] + }; + + let fg_color = if !envelope.is_seen() { + Color::Byte(0) + } else { + Color::Default + }; + let bg_color = if !envelope.is_seen() { + Color::Byte(251) + } else if idx % 2 == 0 { + Color::Byte(236) + } else { + Color::Default + }; + change_colors( + &mut self.content, + ((0, idx), (MAX_COLS - 1, idx)), + fg_color, + bg_color, + ); + } + + fn highlight_line(&self, grid: &mut CellBuffer, area: Area, idx: usize, context: &Context) { + let threaded = context.accounts[self.cursor_pos.0] + .runtime_settings + .threaded; + let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1] + .as_ref() + .unwrap(); + let envelope: &Envelope = if threaded { + let i = mailbox.threaded_mail(idx); + &mailbox.collection[i] + } else { + &mailbox.collection[idx] + }; + + let fg_color = if !envelope.is_seen() { + Color::Byte(0) + } else { + Color::Default + }; + let bg_color = if self.cursor_pos.2 == idx { + Color::Byte(246) + } else if !envelope.is_seen() { + Color::Byte(251) + } else if idx % 2 == 0 { + Color::Byte(236) + } else { + Color::Default + }; + change_colors(grid, area, fg_color, bg_color); + } + + /// Draw the list of `Envelope`s. + fn draw_list(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { + if self.cursor_pos.1 != self.new_cursor_pos.1 { + self.refresh_mailbox(context); + } + let upper_left = upper_left!(area); + let bottom_right = bottom_right!(area); + if self.length == 0 { + clear_area(grid, area); + copy_area(grid, &self.content, area, ((0, 0), (MAX_COLS - 1, 0))); + context.dirty_areas.push_back(area); + return; + } + let rows = get_y(bottom_right) - get_y(upper_left) + 1; + let prev_page_no = (self.cursor_pos.2).wrapping_div(rows); + let page_no = (self.new_cursor_pos.2).wrapping_div(rows); + + let top_idx = page_no * rows; + + /* If cursor position has changed, remove the highlight from the previous position and + * apply it in the new one. */ + if self.cursor_pos.2 != self.new_cursor_pos.2 && prev_page_no == page_no { + let old_cursor_pos = self.cursor_pos; + self.cursor_pos = self.new_cursor_pos; + for idx in &[old_cursor_pos.2, self.new_cursor_pos.2] { + if *idx >= self.length { + continue; //bounds check + } + let new_area = ( + set_y(upper_left, get_y(upper_left) + (*idx % rows)), + set_y(bottom_right, get_y(upper_left) + (*idx % rows)), + ); + self.highlight_line(grid, new_area, *idx, context); + context.dirty_areas.push_back(new_area); + } + return; + } else if self.cursor_pos != self.new_cursor_pos { + self.cursor_pos = self.new_cursor_pos; + } + + /* Page_no has changed, so draw new page */ + copy_area( + grid, + &self.content, + area, + ((0, top_idx), (MAX_COLS - 1, self.length)), + ); + self.highlight_line( + grid, + ( + set_y(upper_left, get_y(upper_left) + (self.cursor_pos.2 % rows)), + set_y(bottom_right, get_y(upper_left) + (self.cursor_pos.2 % rows)), + ), + self.cursor_pos.2, + context, + ); + context.dirty_areas.push_back(area); + } + + fn make_thread_entry( + envelope: &Envelope, + idx: usize, + indent: usize, + container: &Container, + indentations: &[bool], + idx_width: usize, + ) -> String { + let has_sibling = container.has_sibling(); + let has_parent = container.has_parent(); + let show_subject = container.show_subject(); + + let mut s = format!( + "{}{}{} ", + idx, + " ".repeat(idx_width + 2 - (idx.to_string().chars().count())), + CompactMailListing::format_date(&envelope) + ); + for i in 0..indent { + if indentations.len() > i && indentations[i] { + s.push('│'); + } else { + s.push(' '); + } + if i > 0 { + s.push(' '); + } + } + if indent > 0 { + if has_sibling && has_parent { + s.push('├'); + } else if has_sibling { + s.push('┬'); + } else { + s.push('└'); + } + s.push('─'); + s.push('>'); + } + + if show_subject { + s.push_str(&format!("{:.85}", envelope.subject())); + } + let attach_count = envelope.body().count_attachments(); + if attach_count > 1 { + s.push_str(&format!(" {}∞ ", attach_count - 1)); + } + s + } + fn format_date(envelope: &Envelope) -> String { + let d = std::time::UNIX_EPOCH + std::time::Duration::from_secs(envelope.date()); + let now: std::time::Duration = std::time::SystemTime::now().duration_since(d).unwrap(); + match now.as_secs() { + n if n < 10 * 60 * 60 => format!("{} hours ago{}", n / (60 * 60), " ".repeat(8)), + n if n < 24 * 60 * 60 => format!("{} hours ago{}", n / (60 * 60), " ".repeat(7)), + n if n < 4 * 24 * 60 * 60 => { + format!("{} days ago{}", n / (24 * 60 * 60), " ".repeat(9)) + } + _ => envelope.datetime().format("%Y-%m-%d %H:%M:%S").to_string(), + } + } +} + +impl Component for CompactMailListing { + fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { + if !self.unfocused { + if !self.is_dirty() { + return; + } + self.dirty = false; + /* Draw the entire list */ + self.draw_list(grid, area, context); + } else { + let upper_left = upper_left!(area); + let bottom_right = bottom_right!(area); + if self.length == 0 && self.dirty { + clear_area(grid, area); + context.dirty_areas.push_back(area); + } + + /* Render the mail body in a pager, basically copy what HSplit does */ + let total_rows = get_y(bottom_right) - get_y(upper_left); + let pager_ratio = context.runtime_settings.pager.pager_ratio; + let bottom_entity_rows = (pager_ratio * total_rows) / 100; + + if bottom_entity_rows > total_rows { + clear_area(grid, area); + context.dirty_areas.push_back(area); + return; + } + /* Mark message as read */ + let idx = self.cursor_pos.2; + let must_highlight = { + if self.length == 0 { + false + } else { + let threaded = context.accounts[self.cursor_pos.0] + .runtime_settings + .threaded; + let mailbox = &mut context.accounts[self.cursor_pos.0][self.cursor_pos.1] + .as_mut() + .unwrap(); + let envelope: &mut Envelope = if threaded { + let i = mailbox.threaded_mail(idx); + &mut mailbox.collection[i] + } else { + &mut mailbox.collection[idx] + }; + if !envelope.is_seen() { + envelope.set_seen().unwrap(); + true + } else { + false + } + } + }; + if must_highlight { + self.highlight_line_self(idx, context); + } + let mid = get_y(upper_left) + total_rows - bottom_entity_rows; + self.draw_list( + grid, + ( + upper_left, + (get_x(bottom_right), get_y(upper_left) + mid - 1), + ), + context, + ); + if self.length == 0 { + self.dirty = false; + return; + } + { + /* TODO: Move the box drawing business in separate functions */ + if get_x(upper_left) > 0 && grid[(get_x(upper_left) - 1, mid)].ch() == VERT_BOUNDARY + { + grid[(get_x(upper_left) - 1, mid)].set_ch(LIGHT_VERTICAL_AND_RIGHT); + } + + for i in get_x(upper_left)..=get_x(bottom_right) { + grid[(i, mid)].set_ch('─'); + } + context + .dirty_areas + .push_back((set_y(upper_left, mid), set_y(bottom_right, mid))); + } + // TODO: Make headers view configurable + + if !self.dirty { + if let Some(v) = self.view.as_mut() { + v.draw(grid, (set_y(upper_left, mid + 1), bottom_right), context); + } + return; + } + self.view = Some(ThreadView::new(Vec::new())); + self.view.as_mut().unwrap().draw( + grid, + (set_y(upper_left, mid + 1), bottom_right), + context, + ); + self.dirty = false; + } + } + fn process_event(&mut self, event: &UIEvent, context: &mut Context) { + match event.event_type { + UIEventType::Input(Key::Up) => { + if self.cursor_pos.2 > 0 { + self.new_cursor_pos.2 -= 1; + self.dirty = true; + } + } + UIEventType::Input(Key::Down) => { + if self.length > 0 && self.new_cursor_pos.2 < self.length - 1 { + self.new_cursor_pos.2 += 1; + self.dirty = true; + } + } + UIEventType::Input(Key::Char('\n')) if !self.unfocused => { + self.unfocused = true; + self.dirty = true; + } + UIEventType::Input(Key::Char('m')) if !self.unfocused => { + use std::process::{Command, Stdio}; + /* Kill input thread so that spawned command can be sole receiver of stdin */ + { + /* I tried thread::park() here but for some reason it never blocked and always + * returned. Spinlocks are also useless because you have to keep the mutex + * guard alive til the child process exits, which requires some effort. + * + * The only problem with this approach is tht the user has to send some input + * in order for the input-thread to wake up and realise it should kill itself. + * + * I tried writing to stdin/tty manually but for some reason rustty didn't + * acknowledge it. + */ + + /* + * tx sends to input-thread and it kills itself. + */ + let tx = context.input_thread(); + tx.send(true); + } + let mut f = create_temp_file(&new_draft(context), None); + //let mut f = Box::new(std::fs::File::create(&dir).unwrap()); + + // TODO: check exit status + let mut output = Command::new("vim") + .arg("+/^$") + .arg(&f.path()) + .stdin(Stdio::inherit()) + .stdout(Stdio::inherit()) + .spawn() + .expect("failed to execute process"); + + /* + * Main loop will wait on children and when they reap them the loop spawns a new + * input-thread + */ + context.replies.push_back(UIEvent { + id: 0, + event_type: UIEventType::Fork(ForkType::NewDraft(f, output)), + }); + context.replies.push_back(UIEvent { + id: 0, + event_type: UIEventType::ChangeMode(UIMode::Fork), + }); + return; + } + UIEventType::Input(Key::Char('i')) if self.unfocused => { + self.unfocused = false; + self.dirty = true; + self.view = None; + } + UIEventType::Input(Key::Char(k @ 'J')) | UIEventType::Input(Key::Char(k @ 'K')) => { + let folder_length = context.accounts[self.cursor_pos.0].len(); + let accounts_length = context.accounts.len(); + match k { + 'J' if folder_length > 0 => { + if self.new_cursor_pos.1 < folder_length - 1 { + self.new_cursor_pos.1 = self.cursor_pos.1 + 1; + self.dirty = true; + self.refresh_mailbox(context); + } else if accounts_length > 0 && self.new_cursor_pos.0 < accounts_length - 1 + { + self.new_cursor_pos.0 = self.cursor_pos.0 + 1; + self.new_cursor_pos.1 = 0; + self.dirty = true; + self.refresh_mailbox(context); + } + } + 'K' => { + if self.cursor_pos.1 > 0 { + self.new_cursor_pos.1 = self.cursor_pos.1 - 1; + self.dirty = true; + self.refresh_mailbox(context); + } else if self.cursor_pos.0 > 0 { + self.new_cursor_pos.0 = self.cursor_pos.0 - 1; + self.new_cursor_pos.1 = 0; + self.dirty = true; + self.refresh_mailbox(context); + } + } + _ => {} + } + } + UIEventType::Input(Key::Char(k @ 'h')) | UIEventType::Input(Key::Char(k @ 'l')) => { + let accounts_length = context.accounts.len(); + match k { + 'h' if accounts_length > 0 && self.new_cursor_pos.0 < accounts_length - 1 => { + self.new_cursor_pos.0 = self.cursor_pos.0 + 1; + self.new_cursor_pos.1 = 0; + self.dirty = true; + self.refresh_mailbox(context); + } + 'l' if self.cursor_pos.0 > 0 => { + self.new_cursor_pos.0 = self.cursor_pos.0 - 1; + self.new_cursor_pos.1 = 0; + self.dirty = true; + self.refresh_mailbox(context); + } + _ => {} + } + } + UIEventType::RefreshMailbox(_) => { + self.dirty = true; + self.view = None; + } + UIEventType::MailboxUpdate((ref idxa, ref idxf)) => { + if *idxa == self.new_cursor_pos.1 && *idxf == self.new_cursor_pos.0 { + self.refresh_mailbox(context); + self.dirty = true; + } + } + UIEventType::ChangeMode(UIMode::Normal) => { + self.dirty = true; + } + UIEventType::Resize => { + self.dirty = true; + } + UIEventType::Action(ref action) => match action { + Action::MailListing(MailListingAction::ToggleThreaded) => { + context.accounts[self.cursor_pos.0] + .runtime_settings + .threaded = !context.accounts[self.cursor_pos.0] + .runtime_settings + .threaded; + self.refresh_mailbox(context); + self.dirty = true; + return; + } + Action::ViewMailbox(idx) => { + self.new_cursor_pos.1 = *idx; + self.dirty = true; + self.refresh_mailbox(context); + return; + } + Action::Sort(field, order) => { + self.sort = (field.clone(), order.clone()); + self.dirty = true; + self.refresh_mailbox(context); + return; + } + _ => {} + }, + _ => {} + } + if let Some(ref mut v) = self.view { + v.process_event(event, context); + } + } + fn is_dirty(&self) -> bool { + self.dirty || self.view.as_ref().map(|p| p.is_dirty()).unwrap_or(false) + } + fn set_dirty(&mut self) { + self.dirty = true; + } +} diff --git a/ui/src/components/mail/listing.rs b/ui/src/components/mail/listing/mod.rs index f6484b47..d16228af 100644 --- a/ui/src/components/mail/listing.rs +++ b/ui/src/components/mail/listing/mod.rs @@ -21,6 +21,9 @@ use super::*; +mod compact; +pub use self::compact::*; + const MAX_COLS: usize = 500; /// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the `Envelope` content in a @@ -31,7 +34,7 @@ pub struct MailListing { new_cursor_pos: (usize, usize, usize), length: usize, sort: (SortField, SortOrder), - subsort: (SortField, SortOrder), + //subsort: (SortField, SortOrder), /// Cache current view. content: CellBuffer, /// If we must redraw on next redraw event @@ -47,6 +50,12 @@ impl Default for MailListing { } } +impl fmt::Display for MailListing { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "mail") + } +} + impl MailListing { /// Helper function to format entry strings for MailListing */ /* TODO: Make this configurable */ @@ -66,7 +75,7 @@ impl MailListing { new_cursor_pos: (0, 0, 0), length: 0, sort: (SortField::Date, SortOrder::Desc), - subsort: (SortField::Date, SortOrder::Asc), + //subsort: (SortField::Date, SortOrder::Asc), content: content, dirty: true, unfocused: false, @@ -728,4 +737,7 @@ impl Component for MailListing { fn is_dirty(&self) -> bool { self.dirty || self.view.as_ref().map(|p| p.is_dirty()).unwrap_or(false) } + fn set_dirty(&mut self) { + self.dirty = true; + } } diff --git a/ui/src/components/mail/mod.rs b/ui/src/components/mail/mod.rs index e04e44c4..96cfc8b8 100644 --- a/ui/src/components/mail/mod.rs +++ b/ui/src/components/mail/mod.rs @@ -22,11 +22,14 @@ /*! Entities that handle Mail specific functions. */ use super::*; +use melib::backends::Folder; pub mod listing; -pub mod view; pub use listing::*; +pub mod view; pub use view::*; +mod compose; +pub use self::compose::*; #[derive(Debug)] struct AccountMenuEntry { @@ -43,6 +46,13 @@ pub struct AccountMenu { cursor: Option<(usize, usize)>, } +impl fmt::Display for AccountMenu { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // TODO display subject/info + write!(f, "menu") + } +} + impl AccountMenu { pub fn new(accounts: &[Account]) -> Self { let accounts = accounts @@ -53,8 +63,10 @@ impl AccountMenu { index: i, entries: { let mut entries = Vec::with_capacity(a.len()); - for (idx, acc) in a.list_folders().iter().enumerate() { - entries.push((idx, acc.clone())); + let mut idx = 0; + for acc in a.list_folders() { + entries.push((idx, acc)); + idx += 1; } entries }, @@ -187,12 +199,7 @@ impl AccountMenu { ); if highlight && idx > 1 && self.cursor.unwrap().1 == idx - 2 { - change_colors( - grid, - ((x, y), (get_x(bottom_right), y)), - color_fg, - color_bg, - ); + change_colors(grid, ((x, y), (get_x(bottom_right), y)), color_fg, color_bg); } else { change_colors(grid, ((x, y), set_y(bottom_right, y)), color_fg, color_bg); } @@ -240,4 +247,7 @@ impl Component for AccountMenu { fn is_dirty(&self) -> bool { self.dirty } + fn set_dirty(&mut self) { + self.dirty = true; + } } diff --git a/ui/src/components/mail/view/html.rs b/ui/src/components/mail/view/html.rs index d8812c4d..9ccbbf7a 100644 --- a/ui/src/components/mail/view/html.rs +++ b/ui/src/components/mail/view/html.rs @@ -54,6 +54,13 @@ impl HtmlView { } } +impl fmt::Display for HtmlView { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // TODO display subject/info + write!(f, "view") + } +} + impl Component for HtmlView { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { self.pager.draw(grid, area, context); @@ -90,4 +97,5 @@ impl Component for HtmlView { fn is_dirty(&self) -> bool { self.pager.is_dirty() } + fn set_dirty(&mut self) {} } diff --git a/ui/src/components/mail/view/mod.rs b/ui/src/components/mail/view/mod.rs index 17a290c5..25c7733e 100644 --- a/ui/src/components/mail/view/mod.rs +++ b/ui/src/components/mail/view/mod.rs @@ -24,8 +24,9 @@ use linkify::{Link, LinkFinder}; use std::process::{Command, Stdio}; mod html; - pub use self::html::*; +mod thread; +pub use self::thread::*; use mime_apps::query_default_app; @@ -59,6 +60,13 @@ pub struct MailView { cmd_buf: String, } +impl fmt::Display |