diff --git a/ui/src/components/mail/listing/plain.rs b/ui/src/components/mail/listing/plain.rs
index d71ae19b..608406a4 100644
--- a/ui/src/components/mail/listing/plain.rs
+++ b/ui/src/components/mail/listing/plain.rs
@@ -19,11 +19,14 @@
* along with meli. If not, see .
*/
+use super::EntryStrings;
use super::*;
-
+use crate::components::utilities::PageMovement;
use std::cmp;
-use std::ops::{Deref, DerefMut};
+use std::iter::FromIterator;
+
const MAX_COLS: usize = 500;
+
macro_rules! address_list {
(($name:expr) as comma_sep_list) => {{
let mut ret: String =
@@ -40,30 +43,6 @@ macro_rules! address_list {
}};
}
-macro_rules! column_str {
- (
- struct $name:ident(String)) => {
- pub struct $name(String);
-
- impl Deref for $name {
- type Target = String;
- fn deref(&self) -> &String {
- &self.0
- }
- }
- impl DerefMut for $name {
- fn deref_mut(&mut self) -> &mut String {
- &mut self.0
- }
- }
- };
-}
-
-column_str!(struct IndexNoString(String));
-column_str!(struct DateString(String));
-column_str!(struct FromString(String));
-column_str!(struct SubjectString(String));
-
/// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the `Envelope` content in a
/// `MailView`.
#[derive(Debug)]
@@ -72,16 +51,26 @@ pub struct PlainListing {
cursor_pos: (usize, usize, usize),
new_cursor_pos: (usize, usize, usize),
length: usize,
- local_collection: Vec,
sort: (SortField, SortOrder),
subsort: (SortField, SortOrder),
+ all_envelopes: fnv::FnvHashSet,
+ order: FnvHashMap,
/// Cache current view.
- content: CellBuffer,
+ data_columns: DataColumns,
+
+ filter_term: String,
+ filtered_selection: Vec,
+ filtered_order: FnvHashMap,
+ selection: FnvHashMap,
+ local_collection: Vec,
/// If we must redraw on next redraw event
dirty: bool,
+ force_draw: bool,
/// If `self.view` exists or not.
unfocused: bool,
- view: Option,
+ view: MailView,
+ row_updates: StackVec,
+
movement: Option,
id: ComponentId,
}
@@ -90,44 +79,71 @@ impl ListingTrait for PlainListing {
fn coordinates(&self) -> (usize, usize, Option) {
(self.new_cursor_pos.0, self.new_cursor_pos.1, None)
}
+
fn set_coordinates(&mut self, coordinates: (usize, usize, Option)) {
self.new_cursor_pos = (coordinates.0, coordinates.1, 0);
self.unfocused = false;
- self.local_collection.clear();
+ self.filtered_selection.clear();
+ self.filtered_order.clear();
+ self.filter_term.clear();
+ self.row_updates.clear();
}
+
fn highlight_line(&mut self, grid: &mut CellBuffer, area: Area, idx: usize, context: &Context) {
+ if self.length == 0 {
+ return;
+ }
+ let i = self.get_env_under_cursor(idx, context);
+
let account = &context.accounts[self.cursor_pos.0];
- let envelope: EnvelopeRef = account.collection.get_env(self.local_collection[idx]);
+ let envelope: EnvelopeRef = account.collection.get_env(i);
- let fg_color = if !envelope.is_seen() {
- Color::Byte(0)
- } else {
- Color::Default
- };
+ let fg_color = self.data_columns.columns[0][(0, idx)].fg();
let bg_color = if context.settings.terminal.theme == "light" {
if self.cursor_pos.2 == idx {
- Color::Byte(246)
+ Color::Byte(244)
+ } else if self.selection[&i] {
+ Color::Byte(210)
} else if !envelope.is_seen() {
Color::Byte(251)
- } else if idx % 2 == 0 {
- Color::Byte(252)
} else {
- Color::Default
+ self.data_columns.columns[0][(0, idx)].bg()
}
} else {
if self.cursor_pos.2 == idx {
Color::Byte(246)
+ } else if self.selection[&i] {
+ Color::Byte(210)
} else if !envelope.is_seen() {
Color::Byte(251)
- } else if idx % 2 == 0 {
- Color::Byte(236)
} else {
- Color::Default
+ self.data_columns.columns[0][(0, idx)].bg()
}
};
+
+ let (upper_left, bottom_right) = area;
change_colors(grid, area, fg_color, bg_color);
- }
+ let mut x = get_x(upper_left)
+ + self.data_columns.widths[0]
+ + self.data_columns.widths[1]
+ + self.data_columns.widths[2]
+ + 3 * 2;
+ copy_area(
+ grid,
+ &self.data_columns.columns[3],
+ (set_x(upper_left, x), bottom_right),
+ (
+ (0, idx),
+ pos_dec(self.data_columns.columns[3].size(), (1, 1)),
+ ),
+ );
+ for _ in 0..self.data_columns.widths[3] {
+ grid[(x, get_y(upper_left))].set_bg(bg_color);
+ x += 1;
+ }
+ return;
+ }
/// 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.cursor_pos.0 != self.new_cursor_pos.0
@@ -138,11 +154,17 @@ impl ListingTrait for PlainListing {
let bottom_right = bottom_right!(area);
if self.length == 0 {
clear_area(grid, area);
- copy_area(grid, &self.content, area, ((0, 0), (MAX_COLS - 1, 0)));
+ copy_area(
+ grid,
+ &self.data_columns.columns[0],
+ area,
+ ((0, 0), pos_dec(self.data_columns.columns[0].size(), (1, 1))),
+ );
context.dirty_areas.push_back(area);
return;
}
let rows = get_y(bottom_right) - get_y(upper_left) + 1;
+
if let Some(mvm) = self.movement.take() {
match mvm {
PageMovement::PageUp => {
@@ -169,6 +191,7 @@ impl ListingTrait for PlainListing {
}
}
}
+
let prev_page_no = (self.cursor_pos.2).wrapping_div(rows);
let page_no = (self.new_cursor_pos.2).wrapping_div(rows);
@@ -194,14 +217,111 @@ impl ListingTrait for PlainListing {
} else if self.cursor_pos != self.new_cursor_pos {
self.cursor_pos = self.new_cursor_pos;
}
+ if self.new_cursor_pos.2 >= self.length {
+ self.new_cursor_pos.2 = self.length - 1;
+ self.cursor_pos.2 = self.new_cursor_pos.2;
+ }
- /* Page_no has changed, so draw new page */
- copy_area(
- grid,
- &self.content,
- area,
- ((0, top_idx), (MAX_COLS - 1, self.length)),
+ let width = width!(area);
+ self.data_columns.widths = Default::default();
+ self.data_columns.widths[0] = self.data_columns.columns[0].size().0;
+ self.data_columns.widths[1] = self.data_columns.columns[1].size().0; /* date*/
+ self.data_columns.widths[2] = self.data_columns.columns[2].size().0; /* from */
+ self.data_columns.widths[3] = self.data_columns.columns[3].size().0; /* flags */
+ self.data_columns.widths[4] = self.data_columns.columns[4].size().0; /* subject */
+
+ let min_col_width = std::cmp::min(
+ 15,
+ std::cmp::min(self.data_columns.widths[4], self.data_columns.widths[2]),
);
+ if self.data_columns.widths[0] + self.data_columns.widths[1] + 3 * min_col_width + 8 > width
+ {
+ let remainder = width
+ .saturating_sub(self.data_columns.widths[0])
+ .saturating_sub(self.data_columns.widths[1])
+ - 4;
+ self.data_columns.widths[2] = remainder / 6;
+ self.data_columns.widths[4] = (2 * remainder) / 3 - self.data_columns.widths[3];
+ } else {
+ let remainder = width
+ .saturating_sub(self.data_columns.widths[0])
+ .saturating_sub(self.data_columns.widths[1])
+ .saturating_sub(8);
+ if min_col_width + self.data_columns.widths[4] > remainder {
+ self.data_columns.widths[4] =
+ remainder - min_col_width - self.data_columns.widths[3];
+ self.data_columns.widths[2] = min_col_width;
+ }
+ }
+ clear_area(grid, area);
+ /* Page_no has changed, so draw new page */
+ let mut x = get_x(upper_left);
+ let mut flag_x = 0;
+ for i in 0..self.data_columns.columns.len() {
+ let column_width = self.data_columns.columns[i].size().0;
+ if i == 3 {
+ flag_x = x;
+ }
+ if self.data_columns.widths[i] == 0 {
+ continue;
+ }
+ copy_area(
+ grid,
+ &self.data_columns.columns[i],
+ (
+ set_x(upper_left, x),
+ set_x(
+ bottom_right,
+ std::cmp::min(get_x(bottom_right), x + (self.data_columns.widths[i])),
+ ),
+ ),
+ (
+ (0, top_idx),
+ (column_width.saturating_sub(1), self.length - 1),
+ ),
+ );
+ x += self.data_columns.widths[i] + 2; // + SEPARATOR
+ if x > get_x(bottom_right) {
+ break;
+ }
+ }
+ for r in 0..cmp::min(self.length - top_idx, rows) {
+ let (fg_color, bg_color) = {
+ let c = &self.data_columns.columns[0][(0, r + top_idx)];
+ (c.fg(), c.bg())
+ };
+ change_colors(
+ grid,
+ (
+ pos_inc(upper_left, (0, r)),
+ (flag_x.saturating_sub(1), get_y(upper_left) + r),
+ ),
+ fg_color,
+ bg_color,
+ );
+ for x in flag_x
+ ..std::cmp::min(
+ get_x(bottom_right),
+ flag_x + 2 + self.data_columns.widths[3],
+ )
+ {
+ grid[(x, get_y(upper_left) + r)].set_bg(bg_color);
+ }
+ change_colors(
+ grid,
+ (
+ (
+ flag_x + 2 + self.data_columns.widths[3],
+ get_y(upper_left) + r,
+ ),
+ (get_x(bottom_right), get_y(upper_left) + r),
+ ),
+ fg_color,
+ bg_color,
+ );
+ }
+
+ /* TODO: highlight selected entries */
self.highlight_line(
grid,
(
@@ -211,92 +331,177 @@ impl ListingTrait for PlainListing {
self.cursor_pos.2,
context,
);
+
+ if top_idx + rows > self.length {
+ clear_area(
+ grid,
+ (
+ pos_inc(upper_left, (0, self.length - top_idx)),
+ bottom_right,
+ ),
+ );
+ }
context.dirty_areas.push_back(area);
}
+ fn filter(&mut self, filter_term: &str, context: &Context) {
+ if filter_term.is_empty() {
+ return;
+ }
+
+ self.order.clear();
+ self.selection.clear();
+ self.length = 0;
+ self.filtered_selection.clear();
+ self.filtered_order.clear();
+ self.filter_term = filter_term.to_string();
+ self.row_updates.clear();
+ for v in self.selection.values_mut() {
+ *v = false;
+ }
+
+ let account = &context.accounts[self.cursor_pos.0];
+ let folder_hash = account[self.cursor_pos.1].unwrap().folder.hash();
+ match account.search(&self.filter_term, self.sort, folder_hash) {
+ Ok(results) => {
+ for env_hash in results {
+ if !account.collection.contains_key(&env_hash) {
+ continue;
+ }
+ if self.filtered_order.contains_key(&env_hash) {
+ continue;
+ }
+ if self.all_envelopes.contains(&env_hash) {
+ self.filtered_selection.push(env_hash);
+ self.filtered_order
+ .insert(env_hash, self.filtered_selection.len() - 1);
+ }
+ }
+ if !self.filtered_selection.is_empty() {
+ self.new_cursor_pos.2 =
+ std::cmp::min(self.filtered_selection.len() - 1, self.cursor_pos.2);
+ } else {
+ self.data_columns.columns[0] =
+ CellBuffer::new_with_context(0, 0, Cell::with_char(' '), context);
+ }
+ self.redraw_list(context);
+ }
+ Err(e) => {
+ self.cursor_pos.2 = 0;
+ self.new_cursor_pos.2 = 0;
+ let message = format!(
+ "Encountered an error while searching for `{}`: {}.",
+ &self.filter_term, e
+ );
+ log(
+ format!("Failed to search for term {}: {}", &self.filter_term, e),
+ ERROR,
+ );
+ self.data_columns.columns[0] =
+ CellBuffer::new_with_context(message.len(), 1, Cell::with_char(' '), context);
+ write_string_to_grid(
+ &message,
+ &mut self.data_columns.columns[0],
+ Color::Default,
+ Color::Default,
+ Attr::Default,
+ ((0, 0), (message.len() - 1, 0)),
+ false,
+ );
+ }
+ }
+ }
+
fn set_movement(&mut self, mvm: PageMovement) {
self.movement = Some(mvm);
self.set_dirty();
}
}
-impl Default for PlainListing {
- fn default() -> Self {
- Self::new()
- }
-}
-
impl fmt::Display for PlainListing {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "mail")
}
}
-impl PlainListing {
- /// Helper function to format entry strings for PlainListing */
- /* TODO: Make this configurable */
- fn make_entry_string(
- e: &Envelope,
- idx: usize,
- ) -> (IndexNoString, FromString, DateString, SubjectString) {
- (
- IndexNoString(idx.to_string()),
- FromString(address_list!((e.from()) as comma_sep_list)),
- DateString(PlainListing::format_date(e)),
- SubjectString(format!(
- "{}{}",
- e.subject(),
- if e.has_attachments() { " 📎" } else { "" },
- )),
- )
+impl Default for PlainListing {
+ fn default() -> Self {
+ PlainListing::new()
}
+}
- pub fn new() -> Self {
- let content = CellBuffer::new(0, 0, Cell::with_char(' '));
+impl PlainListing {
+ const DESCRIPTION: &'static str = "plain listing";
+ fn new() -> Self {
PlainListing {
cursor_pos: (0, 1, 0),
new_cursor_pos: (0, 0, 0),
length: 0,
- local_collection: Vec::new(),
sort: (Default::default(), Default::default()),
- subsort: (Default::default(), Default::default()),
- content,
+ subsort: (SortField::Date, SortOrder::Desc),
+ all_envelopes: fnv::FnvHashSet::default(),
+ local_collection: Vec::new(),
+ order: FnvHashMap::default(),
+ filter_term: String::new(),
+ filtered_selection: Vec::new(),
+ filtered_order: FnvHashMap::default(),
+ selection: FnvHashMap::default(),
+ row_updates: StackVec::new(),
+ data_columns: DataColumns::default(),
dirty: true,
+ force_draw: true,
unfocused: false,
- view: None,
+ view: MailView::default(),
+
movement: None,
id: ComponentId::new_v4(),
}
}
- /// Fill the `self.content` `CellBuffer` with the contents of the account folder the user has
+ fn make_entry_string(e: EnvelopeRef) -> EntryStrings {
+ EntryStrings {
+ date: DateString(PlainListing::format_date(&e)),
+ subject: SubjectString(format!("{}", e.subject())),
+ flag: FlagString(format!("{}", if e.has_attachments() { "📎" } else { "" },)),
+ from: FromString(address_list!((e.from()) as comma_sep_list)),
+ }
+ }
+
+ /// Fill the `self.data_columns` `CellBuffers` with the contents of the account folder the user has
/// chosen.
fn refresh_mailbox(&mut self, context: &mut Context) {
self.dirty = true;
+ let old_cursor_pos = self.cursor_pos;
if !(self.cursor_pos.0 == self.new_cursor_pos.0
&& self.cursor_pos.1 == self.new_cursor_pos.1)
{
self.cursor_pos.2 = 0;
self.new_cursor_pos.2 = 0;
}
- self.cursor_pos.0 = self.new_cursor_pos.0;
self.cursor_pos.1 = self.new_cursor_pos.1;
- let folder_hash = context.accounts[self.cursor_pos.0].folders_order[self.cursor_pos.1];
+ self.cursor_pos.0 = self.new_cursor_pos.0;
+ let folder_hash = if let Some(h) = context.accounts[self.cursor_pos.0]
+ .folders_order
+ .get(self.cursor_pos.1)
+ {
+ *h
+ } else {
+ self.cursor_pos.1 = old_cursor_pos.1;
+ self.dirty = false;
+ return;
+ };
- // Inform State that we changed the current folder view.
- context
- .replies
- .push_back(UIEvent::RefreshMailbox((self.cursor_pos.0, folder_hash)));
// Get mailbox as a reference.
//
match context.accounts[self.cursor_pos.0].status(folder_hash) {
- Ok(_) => {}
+ Ok(()) => {}
Err(_) => {
let message: String = context.accounts[self.cursor_pos.0][folder_hash].to_string();
- self.content = CellBuffer::new(message.len(), 1, Cell::with_char(' '));
+ self.data_columns.columns[0] =
+ CellBuffer::new_with_context(message.len(), 1, Cell::with_char(' '), context);
self.length = 0;
write_string_to_grid(
message.as_str(),
- &mut self.content,
+ &mut self.data_columns.columns[0],
Color::Default,
Color::Default,
Attr::Default,
@@ -306,25 +511,36 @@ impl PlainListing {
return;
}
}
+ let envelopes = context.accounts[self.cursor_pos.0]
+ .collection
+ .envelopes
+ .read()
+ .unwrap();
+ self.local_collection = envelopes.keys().cloned().collect();
+ if self.length > 0 {
+ let env_hash = self.get_env_under_cursor(self.cursor_pos.2, context);
+ let temp = (self.new_cursor_pos.0, self.new_cursor_pos.1, env_hash);
+ if old_cursor_pos == self.new_cursor_pos {
+ self.view.update(temp);
+ } else if self.unfocused {
+ self.view = MailView::new(temp, None, None);
+ }
+ }
+
+ self.redraw_list(context);
+ }
+
+ fn redraw_list(&mut self, context: &Context) {
let account = &context.accounts[self.cursor_pos.0];
let mailbox = &account[self.cursor_pos.1].unwrap();
- self.length = mailbox.len();
- self.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 self.content,
- Color::Default,
- Color::Default,
- Attr::Default,
- ((0, 0), (MAX_COLS - 1, 0)),
- true,
- );
- return;
- }
+ self.order.clear();
+ self.selection.clear();
+ self.length = 0;
+ let mut rows = Vec::with_capacity(1024);
+ let mut min_width = (0, 0, 0, 0, 0);
+
let envelopes = account.collection.envelopes.read().unwrap();
- self.local_collection = envelopes.keys().cloned().collect();
let sort = self.sort;
self.local_collection.sort_by(|a, b| match sort {
(SortField::Date, SortOrder::Desc) => {
@@ -349,41 +565,84 @@ impl PlainListing {
}
});
- let mut rows = Vec::with_capacity(1024);
- let mut min_width = (0, 0, 0);
- let widths: (usize, usize, usize);
+ let mut refresh_mailbox = false;
+ let iter = if self.filter_term.is_empty() {
+ refresh_mailbox = true;
+ Box::new(self.local_collection.iter().cloned())
+ as Box>
+ } else {
+ Box::new(self.filtered_selection.iter().map(|h| *h))
+ as Box>
+ };
+ for (idx, i) in iter.enumerate() {
+ self.length += 1;
+ if !context.accounts[self.cursor_pos.0].contains_key(i) {
+ debug!("key = {}", i);
+ debug!(
+ "name = {} {}",
+ mailbox.name(),
+ context.accounts[self.cursor_pos.0].name()
+ );
+ debug!("{:#?}", context.accounts);
+
+ panic!();
+ }
+ let envelope: EnvelopeRef = context.accounts[self.cursor_pos.0].collection.get_env(i);
- for idx in 0..self.local_collection.len() {
- let envelope: EnvelopeRef = account.collection.get_env(self.local_collection[idx]);
+ let entry_strings = PlainListing::make_entry_string(envelope);
+ min_width.1 = cmp::max(min_width.1, entry_strings.date.grapheme_width()); /* date */
+ min_width.2 = cmp::max(min_width.2, entry_strings.from.grapheme_width()); /* from */
+ min_width.3 = cmp::max(min_width.3, entry_strings.flag.grapheme_width()); /* flags */
+ min_width.4 = cmp::max(min_width.4, entry_strings.subject.grapheme_width()); /* subject */
+ rows.push(entry_strings);
+ if refresh_mailbox {
+ self.all_envelopes.insert(i);
+ }
- let strings = PlainListing::make_entry_string(&envelope, idx);
- min_width.0 = cmp::max(min_width.0, strings.0.len()); /* index */
- min_width.1 = cmp::max(min_width.1, strings.2.split_graphemes().len()); /* date */
- min_width.2 = cmp::max(min_width.2, strings.3.split_graphemes().len()); /* subject */
- rows.push(strings);
+ self.order.insert(i, idx);
+ self.selection.insert(i, false);
}
- let column_sep: usize = if MAX_COLS >= min_width.0 + min_width.1 + min_width.2 {
- widths = min_width;
- 2
+
+ min_width.0 = self.length.saturating_sub(1).to_string().len();
+
+ /* index column */
+ self.data_columns.columns[0] =
+ CellBuffer::new_with_context(min_width.0, rows.len(), Cell::with_char(' '), context);
+ /* date column */
+ self.data_columns.columns[1] =
+ CellBuffer::new_with_context(min_width.1, rows.len(), Cell::with_char(' '), context);
+ /* from column */
+ self.data_columns.columns[2] =
+ CellBuffer::new_with_context(min_width.2, rows.len(), Cell::with_char(' '), context);
+ /* flags column */
+ self.data_columns.columns[3] =
+ CellBuffer::new_with_context(min_width.3, rows.len(), Cell::with_char(' '), context);
+ /* subject column */
+ self.data_columns.columns[4] =
+ CellBuffer::new_with_context(min_width.4, rows.len(), Cell::with_char(' '), context);
+
+ let iter = if self.filter_term.is_empty() {
+ Box::new(self.local_collection.iter().cloned())
+ as Box>
} else {
- let width = MAX_COLS - 3 - min_width.0;
- widths = (
- min_width.0,
- cmp::min(min_width.1, width / 3),
- cmp::min(min_width.2, width / 3),
- );
- 1
+ Box::new(self.filtered_selection.iter().map(|h| *h))
+ as Box>
};
- // Populate `CellBuffer` with every entry.
- for (idx, y) in (0..=self.length).enumerate() {
- if idx >= self.length {
- /* No more entries left, so fill the rest of the area with empty space */
- clear_area(&mut self.content, ((0, y), (MAX_COLS - 1, self.length)));
- break;
+
+ for ((idx, i), strings) in iter.enumerate().zip(rows) {
+ if !context.accounts[self.cursor_pos.0].contains_key(i) {
+ //debug!("key = {}", i);
+ //debug!(
+ // "name = {} {}",
+ // mailbox.name(),
+ // context.accounts[self.cursor_pos.0].name()
+ //);
+ //debug!("{:#?}", context.accounts);
+
+ panic!();
}
- /* Write an entire line for each envelope entry. */
- let envelope: EnvelopeRef = account.collection.get_env(self.local_collection[idx]);
+ let envelope: EnvelopeRef = context.accounts[self.cursor_pos.0].collection.get_env(i);
let fg_color = if !envelope.is_seen() {
Color::Byte(0)
} else {
@@ -399,7 +658,7 @@ impl PlainListing {
}
} else {
if !envelope.is_seen() {
- Color::Byte(251)
+ Color::Byte(253)
} else if idx % 2 == 0 {
Color::Byte(236)
} else {
@@ -407,82 +666,102 @@ impl PlainListing {
}
};
let (x, _) = write_string_to_grid(
- &rows[idx].0,
- &mut self.content,
+ &idx.to_string(),
+ &mut self.data_columns.columns[0],
fg_color,
bg_color,
Attr::Default,
- ((0, idx), (widths.0, idx)),
+ ((0, idx), (min_width.0, idx)),
false,
);
- for x in x..=widths.0 + column_sep {
- self.content[(x, idx)].set_bg(bg_color);
+ for x in x..min_width.0 {
+ self.data_columns.columns[0][(x, idx)].set_bg(bg_color);
}
- let mut _x = widths.0 + column_sep;
let (x, _) = write_string_to_grid(
- &rows[idx].2,
- &mut self.content,
+ &strings.date,
+ &mut self.data_columns.columns[1],
fg_color,
bg_color,
Attr::Default,
- ((_x, idx), (widths.1 + _x, idx)),
+ ((0, idx), (min_width.1, idx)),
false,
);
- _x += widths.1 + column_sep + 1;
- for x in x.._x {
- self.content[(x, idx)].set_bg(bg_color);
+ for x in x..min_width.1 {
+ self.data_columns.columns[1][(x, idx)].set_bg(bg_color);
}
let (x, _) = write_string_to_grid(
- &rows[idx].1,
- &mut self.content,
+ &strings.from,
+ &mut self.data_columns.columns[2],
fg_color,
bg_color,
Attr::Default,
- ((_x, idx), (widths.1 + _x, idx)),
+ ((0, idx), (min_width.2, idx)),
false,
);
- _x += widths.1 + column_sep + 2;
- for x in x.._x {
- self.content[(x, idx)].set_bg(bg_color);
+ for x in x..min_width.2 {
+ self.data_columns.columns[2][(x, idx)].set_bg(bg_color);
}
let (x, _) = write_string_to_grid(
- &rows[idx].3,
- &mut self.content,
+ &strings.flag,
+ &mut self.data_columns.columns[3],
fg_color,
bg_color,
Attr::Default,
- ((_x, idx), (widths.2 + _x, idx)),
+ ((0, idx), (min_width.3, idx)),
false,
);
-
- for x in x..MAX_COLS {
- self.content[(x, y)].set_ch(' ');
- self.content[(x, y)].set_bg(bg_color);
+ for x in x..min_width.3 {
+ self.data_columns.columns[3][(x, idx)].set_bg(bg_color);
+ }
+ let (x, _) = write_string_to_grid(
+ &strings.subject,
+ &mut self.data_columns.columns[4],
+ fg_color,
+ bg_color,
+ Attr::Default,
+ ((0, idx), (min_width.4, idx)),
+ false,
+ );
+ for x in x..min_width.4 {
+ self.data_columns.columns[4][(x, idx)].set_bg(bg_color);
}
+ if context.accounts[self.cursor_pos.0]
+ .collection
+ .get_env(i)
+ .has_attachments()
+ {
+ self.data_columns.columns[3][(0, idx)].set_fg(Color::Byte(103));
+ }
+ }
+ if self.length == 0 && self.filter_term.is_empty() {
+ let mailbox = &account[self.cursor_pos.1];
+ let message = mailbox.to_string();
+ self.data_columns.columns[0] = CellBuffer::new_with_context(
+ message.len(),
+ self.length + 1,
+ Cell::with_char(' '),
+ context,
+ );
+ write_string_to_grid(
+ &message,
+ &mut self.data_columns.columns[0],
+ Color::Default,
+ Color::Default,
+ Attr::Default,
+ ((0, 0), (MAX_COLS - 1, 0)),
+ false,
+ );
}
}
- fn unhighlight_line(&mut self, idx: usize, context: &Context) {
- let fg_color = Color::Default;
- let bg_color = if context.settings.terminal.theme == "light" {
- if idx % 2 == 0 {
- Color::Byte(252)
- } else {
- Color::Default
- }
+ fn get_env_under_cursor(&self, cursor: usize, context: &Context) -> EnvelopeHash {
+ let account = &context.accounts[self.cursor_pos.0];
+ let folder_hash = account[self.cursor_pos.1].unwrap().folder.hash();
+ if self.filter_term.is_empty() {
+ self.local_collection[cursor]
} 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,
- );
+ self.filtered_selection[cursor]
+ }
}
fn format_date(envelope: &Envelope) -> String {
@@ -499,6 +778,36 @@ impl PlainListing {
_ => envelope.datetime().format("%Y-%m-%d %H:%M:%S").to_string(),
}
}
+
+ fn perform_action(&mut self, context: &mut Context, env_hash: EnvelopeHash, a: &ListingAction) {
+ let account = &mut context.accounts[self.cursor_pos.0];
+ let hash = account.collection.get_env(env_hash).hash();
+ let op = account.operation(hash);
+ let mut envelope: EnvelopeRefMut = account.collection.get_env_mut(env_hash);
+ match a {
+ ListingAction::SetSeen => {
+ if let Err(e) = envelope.set_seen(op) {
+ context
+ .replies
+ .push_back(UIEvent::StatusEvent(StatusEvent::DisplayMessage(
+ e.to_string(),
+ )));
+ }
+ }
+ ListingAction::SetUnseen => {
+ if let Err(e) = envelope.set_unseen(op) {
+ context
+ .replies
+ .push_back(UIEvent::StatusEvent(StatusEvent::DisplayMessage(
+ e.to_string(),
+ )));
+ }
+ }
+ ListingAction::Delete => { /* do nothing */ }
+ _ => unreachable!(),
+ }
+ self.row_updates.push(env_hash);
+ }
}
impl Component for PlainListing {
@@ -507,146 +816,165 @@ impl Component for PlainListing {
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);
+ let mut area = area;
+ if !self.filter_term.is_empty() {
+ let (x, y) = write_string_to_grid(
+ &format!(
+ "{} results for `{}` (Press ESC to exit)",
+ self.filtered_selection.len(),
+ self.filter_term
+ ),
+ grid,
+ Color::Default,
+ Color::Default,
+ Attr::Default,
+ area,
+ true,
+ );
+ let (upper_left, bottom_right) = area;
+ clear_area(grid, ((x, y), set_y(bottom_right, y)));
+ context
+ .dirty_areas
+ .push_back((upper_left, set_y(bottom_right, y + 1)));
+
+ area = (set_y(upper_left, y + 1), bottom_right);
}
- /* 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 !self.row_updates.is_empty() {
+ let (upper_left, bottom_right) = area;
+ while let Some(row) = self.row_updates.pop() {
+ let row: usize = self.order[&row];
+ let rows = get_y(bottom_right) - get_y(upper_left) + 1;
+ let page_no = (self.new_cursor_pos.2).wrapping_div(rows);
- 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_unhighlight = {
- if self.length == 0 {
- false
- } else {
- let account = &context.accounts[self.cursor_pos.0];
- let envelope: EnvelopeRef =
- account.collection.get_env(self.local_collection[idx]);
- !envelope.is_seen()
- }
- };
- if must_unhighlight {
- self.unhighlight_line(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);
+ let top_idx = page_no * rows;
+ if row >= top_idx && row <= top_idx + rows {
+ let area = (
+ set_y(upper_left, get_y(upper_left) + (row % rows)),
+ set_y(bottom_right, get_y(upper_left) + (row % rows)),
+ );
+ self.highlight_line(grid, area, row, context);
+ context.dirty_areas.push_back(area);
+ }
}
-
- for i in get_x(upper_left)..=get_x(bottom_right) {
- grid[(i, mid)].set_ch('─');
+ if self.force_draw {
+ /* Draw the entire list */
+ self.draw_list(grid, area, context);
+ self.force_draw = false;
}
- context
- .dirty_areas
- .push_back((set_y(upper_left, mid), set_y(bottom_right, mid)));
+ } else {
+ /* Draw the entire list */
+ self.draw_list(grid, area, context);
}
- // 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);
- }
+ } else {
+ if self.length == 0 && self.dirty {
+ clear_area(grid, area);
+ context.dirty_areas.push_back(area);
return;
}
- {
- let coordinates = self.cursor_pos;
- let coordinates = (
- coordinates.0,
- coordinates.1,
- self.local_collection[self.cursor_pos.2],
- );
- self.view = Some(MailView::new(coordinates, None, None));
- }
- self.view.as_mut().unwrap().draw(
- grid,
- (set_y(upper_left, mid + 1), bottom_right),
- context,
- );
- self.dirty = false;
+
+ self.view.draw(grid, area, context);
}
+ self.dirty = false;
}
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
- if let Some(ref mut v) = self.view {
- if v.process_event(event, context) {
- return true;
- }
+ if self.unfocused && self.view.process_event(event, context) {
+ return true;
}
- match *event {
- UIEvent::Input(Key::Up) => {
- if self.cursor_pos.2 > 0 {
- self.new_cursor_pos.2 -= 1;
+
+ let shortcuts = &self.get_shortcuts(context)[PlainListing::DESCRIPTION];
+ if self.length > 0 {
+ match *event {
+ UIEvent::Input(Key::Up) => {
+ if self.cursor_pos.2 > 0 {
+ self.new_cursor_pos.2 = self.new_cursor_pos.2.saturating_sub(1);
+ self.dirty = true;
+ }
+ return true;
+ }
+ UIEvent::Input(Key::Down) => {
+ if self.new_cursor_pos.2 < self.length - 1 {
+ self.new_cursor_pos.2 += 1;
+ self.dirty = true;
+ }
+ return true;
+ }
+ UIEvent::Input(ref k) if !self.unfocused && *k == shortcuts["open_thread"] => {
+ let env_hash = self.get_env_under_cursor(self.cursor_pos.2, context);
+ let temp = (self.cursor_pos.0, self.cursor_pos.1, env_hash);
+ self.view = MailView::new(temp, None, None);
+ self.unfocused = true;
self.dirty = true;
+ return true;
}
- return true;
- }
- UIEvent::Input(Key::Down) => {
- if self.length > 0 && self.new_cursor_pos.2 < self.length - 1 {
- self.new_cursor_pos.2 += 1;
+ UIEvent::Input(ref k) if self.unfocused && *k == shortcuts["exit_thread"] => {
+ self.unfocused = false;
self.dirty = true;
+ /* If self.row_updates is not empty and we exit a thread, the row_update events
+ * will be performed but the list will not be drawn. So force a draw in any case.
+ * */
+ self.force_draw = true;
+ return true;
}
- return true;
- }
- UIEvent::Input(ref key) if *key == Key::PageUp => {
- self.movement = Some(PageMovement::PageUp);
- self.set_dirty();
- }
- UIEvent::Input(ref key) if *key == Key::PageDown => {
- self.movement = Some(PageMovement::PageDown);
- self.set_dirty();
- }
- UIEvent::Input(ref key) if *key == Key::Home => {
- self.movement = Some(PageMovement::Home);
- self.set_dirty();
- }
- UIEvent::Input(ref key) if *key == Key::End => {
- self.movement = Some(PageMovement::End);
- self.set_dirty();
- }
- UIEvent::Input(Key::Char('\n')) if !self.unfocused => {
- self.unfocused = true;
- self.dirty = true;
- return true;
- }
- UIEvent::Input(Key::Char('i')) if self.unfocused => {
- self.unfocused = false;
- self.dirty = true;
- self.view = None;
- return true;
- }
- UIEvent::RefreshMailbox(_) => {
- self.dirty = true;
- self.view = None;
+ UIEvent::Input(ref key) if !self.unfocused && *key == shortcuts["select_entry"] => {
+ let env_hash = self.get_env_under_cursor(self.cursor_pos.2, context);
+ self.selection.entry(env_hash).and_modify(|e| *e = !*e);
+ }
+ UIEvent::Action(ref action) => match action {
+ Action::SubSort(field, order) if !self.unfocused => {
+ debug!("SubSort {:?} , {:?}", field, order);
+ self.subsort = (*field, *order);
+ //if !self.filtered_selection.is_empty() {
+ // let threads = &account.collection.threads[&folder_hash];
+ // threads.vec_inner_sort_by(&mut self.filtered_selection, self.sort, &account.collection);
+ //} else {
+ // self.refresh_mailbox(context);
+ //}
+ return true;
+ }
+ Action::Sort(field, order) if !self.unfocused => {
+ debug!("Sort {:?} , {:?}", field, order);
+ self.sort = (*field, *order);
+ return true;
+ }
+ Action::Listing(a @ ListingAction::SetSeen)
+ | Action::Listing(a @ ListingAction::SetUnseen)
+ | Action::Listing(a @ ListingAction::Delete)
+ if !self.unfocused =>
+ {
+ let is_selection_empty =
+ self.selection.values().cloned().any(std::convert::identity);
+ let i = [self.get_env_under_cursor(self.cursor_pos.2, context)];
+ let cursor_iter;
+ let sel_iter = if is_selection_empty {
+ cursor_iter = None;
+ Some(self.selection.iter().filter(|(_, v)| **v).map(|(k, _)| k))
+ } else {
+ cursor_iter = Some(i.iter());
+ None
+ };
+ let iter = sel_iter
+ .into_iter()
+ .flatten()
+ .chain(cursor_iter.into_iter().flatten())
+ .cloned();
+ let stack = StackVec::from_iter(iter.into_iter());
+ for i in stack {
+ self.perform_action(context, i, a);
+ }
+ self.dirty = true;
+ for v in self.selection.values_mut() {
+ *v = false;
+ }
+ return true;
+ }
+
+ _ => {}
+ },
+ _ => {}
}
+ }
+ match *event {
UIEvent::MailboxUpdate((ref idxa, ref idxf))
if (*idxa, *idxf)
== (
@@ -660,38 +988,64 @@ impl Component for PlainListing {
}
UIEvent::StartupCheck(ref f)
if *f
- == context.accounts[self.new_cursor_pos.0].folders_order
- [self.new_cursor_pos.1] =>
+ == context.accounts[self.cursor_pos.0].folders_order[self.new_cursor_pos.1] =>
{
self.refresh_mailbox(context);
self.set_dirty();
}
+ UIEvent::EnvelopeRename(ref old_hash, ref new_hash) => {
+ let account = &context.accounts[self.cursor_pos.0];
+ let folder_hash = account[self.cursor_pos.1].unwrap().folder.hash();
+ if !account.collection.contains_key(new_hash) {
+ return false;
+ }
+
+ self.row_updates.push(*new_hash);
+ if let Some(row) = self.order.remove(old_hash) {
+ self.order.insert(*new_hash, row);
+ let selection_status = self.selection.remove(old_hash).unwrap();
+ self.selection.insert(*new_hash, selection_status);
+ for h in self.filtered_selection.iter_mut() {
+ if *h == *old_hash {
+ *h = *new_hash;
+ break;
+ }
+ }
+ }
+
+ self.dirty = true;
+
+ self.view
+ .process_event(&mut UIEvent::EnvelopeRename(*old_hash, *new_hash), context);
+ }
UIEvent::ChangeMode(UIMode::Normal) => {
self.dirty = true;
}
UIEvent::Resize => {
self.dirty = true;
}
+ UIEvent::Input(Key::Esc) if !self.unfocused && !self.filter_term.is_empty() => {
+ self.set_coordinates((self.new_cursor_pos.0, self.new_cursor_pos.1, None));
+ self.refresh_mailbox(context);
+ return true;
+ }
UIEvent::Action(ref action) => match action {
Action::ViewMailbox(idx) => {
+ if context.accounts[self.cursor_pos.0]
+ .folders_order
+ .get(*idx)
+ .is_none()
+ {
+ return true;
+ }
+ self.filtered_selection.clear();
self.new_cursor_pos.1 = *idx;
- self.dirty = true;
- self.refresh_mailbox(context);
- return true;
- }
- Action::SubSort(field, order) => {
- debug!("SubSort {:?} , {:?}", field, order);
- self.subsort = (*field, *order);
- self.dirty = true;
self.refresh_mailbox(context);
return true;
}
- Action::Sort(field, order) => {
- debug!("Sort {:?} , {:?}", field, order);
- self.sort = (*field, *order);
+ Action::Listing(Filter(ref filter_term)) if !self.unfocused => {
+ self.filter(filter_term, context);
self.dirty = true;
- self.refresh_mailbox(context);
- return true;
}
_ => {}
},
@@ -700,15 +1054,39 @@ impl Component for PlainListing {
false
}
fn is_dirty(&self) -> bool {
- self.dirty || self.view.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
+ self.dirty
+ || if self.unfocused {
+ self.view.is_dirty()
+ } else {
+ false
+ }
}
fn set_dirty(&mut self) {
- if let Some(p) = self.view.as_mut() {
- p.set_dirty();
- };
+ if self.unfocused {
+ self.view.set_dirty();
+ }
self.dirty = true;
}
+ fn get_shortcuts(&self, context: &Context) -> ShortcutMaps {
+ let mut map = if self.unfocused {
+ self.view.get_shortcuts(context)
+ } else {
+ ShortcutMaps::default()
+ };
+
+ let config_map = context.settings.shortcuts.compact_listing.key_values();
+ map.insert(
+ PlainListing::DESCRIPTION.to_string(),
+ config_map
+ .into_iter()
+ .map(|(k, v)| (k, v.clone()))
+ .collect(),
+ );
+
+ map
+ }
+
fn id(&self) -> ComponentId {
self.id
}