summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorManos Pitsidianakis <el13635@mail.ntua.gr>2019-06-22 16:13:40 +0300
committerManos Pitsidianakis <el13635@mail.ntua.gr>2019-06-22 16:13:40 +0300
commite0e520b2c40b41f7adea475e4ffa28ca2dd7b48e (patch)
tree8a60fa6222a6184f55fea40b645a8f3310be00e5
parentbb292486f45d780ec13f4308f9d16c38225173d1 (diff)
ui: add filter method in ListingTrait
Implemented in CompactListing only for now. Filter results are stored in the filter* fields of the struct.
-rw-r--r--ui/src/components/mail/listing.rs7
-rw-r--r--ui/src/components/mail/listing/compact.rs294
-rw-r--r--ui/src/components/utilities.rs1
-rw-r--r--ui/src/execute.rs11
-rw-r--r--ui/src/execute/actions.rs1
5 files changed, 290 insertions, 24 deletions
diff --git a/ui/src/components/mail/listing.rs b/ui/src/components/mail/listing.rs
index d8e40c19..8d44d380 100644
--- a/ui/src/components/mail/listing.rs
+++ b/ui/src/components/mail/listing.rs
@@ -42,12 +42,19 @@ struct AccountMenuEntry {
// Index in the config account vector.
index: usize,
}
+#[derive(Debug, Default, Clone)]
+pub(in crate::listing) struct CachedSearchStrings {
+ subject: String,
+ from: String,
+ body: String,
+}
trait ListingTrait {
fn coordinates(&self) -> (usize, usize, Option<EnvelopeHash>);
fn set_coordinates(&mut self, _: (usize, usize, Option<EnvelopeHash>));
fn draw_list(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context);
fn highlight_line(&mut self, grid: &mut CellBuffer, area: Area, idx: usize, context: &Context);
+ fn filter(&mut self, filter_term: &str, context: &Context) {}
}
#[derive(Debug)]
diff --git a/ui/src/components/mail/listing/compact.rs b/ui/src/components/mail/listing/compact.rs
index eb3b79d6..36efe45e 100644
--- a/ui/src/components/mail/listing/compact.rs
+++ b/ui/src/components/mail/listing/compact.rs
@@ -82,6 +82,10 @@ pub struct CompactListing {
order: FnvHashMap<EnvelopeHash, usize>,
/// Cache current view.
data_columns: DataColumns,
+
+ filter_term: String,
+ filtered_selection: Vec<EnvelopeHash>,
+ filtered_order: FnvHashMap<EnvelopeHash, usize>,
/// If we must redraw on next redraw event
dirty: bool,
/// If `self.view` exists or not.
@@ -109,16 +113,20 @@ impl ListingTrait for CompactListing {
let account = &context.accounts[self.cursor_pos.0];
let mailbox = account[self.cursor_pos.1].as_ref().unwrap();
let threads = &account.collection.threads[&mailbox.folder.hash()];
- let thread_node = threads.root_set(idx);
- let thread_node = &threads.thread_nodes()[&thread_node];
- let i = if let Some(i) = thread_node.message() {
- i
- } else {
- let mut iter_ptr = thread_node.children()[0];
- while threads.thread_nodes()[&iter_ptr].message().is_none() {
- iter_ptr = threads.thread_nodes()[&iter_ptr].children()[0];
+ let i = if self.filtered_selection.is_empty() {
+ let thread_node = threads.root_set(idx);
+ let thread_node = &threads.thread_nodes()[&thread_node];
+ if let Some(i) = thread_node.message() {
+ i
+ } else {
+ let mut iter_ptr = thread_node.children()[0];
+ while threads.thread_nodes()[&iter_ptr].message().is_none() {
+ iter_ptr = threads.thread_nodes()[&iter_ptr].children()[0];
+ }
+ threads.thread_nodes()[&iter_ptr].message().unwrap()
}
- threads.thread_nodes()[&iter_ptr].message().unwrap()
+ } else {
+ self.filtered_selection[idx]
};
let root_envelope: &Envelope = &account.get_env(&i);
@@ -210,7 +218,7 @@ impl ListingTrait for CompactListing {
grid,
&self.data_columns.columns[0],
area,
- ((0, 0), (MAX_COLS - 1, self.length)),
+ ((0, 0), pos_dec(self.data_columns.columns[0].size(), (1, 1))),
);
context.dirty_areas.push_back(area);
return;
@@ -361,22 +369,17 @@ impl ListingTrait for CompactListing {
bg_color,
);
}
- let temp_copy_because_of_nll = self.cursor_pos.2; // FIXME
+
self.highlight_line(
grid,
(
- set_y(
- upper_left,
- get_y(upper_left) + (temp_copy_because_of_nll % rows),
- ),
- set_y(
- bottom_right,
- get_y(upper_left) + (temp_copy_because_of_nll % rows),
- ),
+ 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)),
),
- temp_copy_because_of_nll,
+ self.cursor_pos.2,
context,
);
+
if top_idx + rows > self.length {
clear_area(
grid,
@@ -388,6 +391,52 @@ impl ListingTrait for CompactListing {
}
context.dirty_areas.push_back(area);
}
+ fn filter(&mut self, filter_term: &str, context: &Context) {
+ self.filtered_order.clear();
+ self.filtered_selection.clear();
+ self.filter_term.clear();
+
+ for (i, h) in self.order.keys().enumerate() {
+ let account = &context.accounts[self.cursor_pos.0];
+ let envelope = &account.collection[h];
+ if envelope.subject().contains(&filter_term) {
+ self.filtered_selection.push(*h);
+ self.filtered_order.insert(*h, i);
+ continue;
+ }
+ if envelope.field_from_to_string().contains(&filter_term) {
+ self.filtered_selection.push(*h);
+ self.filtered_order.insert(*h, i);
+ continue;
+ }
+ let op = account.operation(*h);
+ let body = envelope.body(op);
+ let decoded = decode_rec(&body, None);
+ let body_text = String::from_utf8_lossy(&decoded);
+ if body_text.contains(&filter_term) {
+ self.filtered_selection.push(*h);
+ self.filtered_order.insert(*h, i);
+ }
+ }
+ if !self.filtered_selection.is_empty() {
+ self.filter_term = filter_term.to_string();
+ self.cursor_pos.2 = std::cmp::min(self.filtered_selection.len() - 1, self.cursor_pos.2);
+ self.length = self.filtered_selection.len();
+ } else {
+ self.length = 0;
+ let message = format!("No results for `{}`.", filter_term);
+ self.data_columns.columns[0] =
+ CellBuffer::new(message.len(), self.length + 1, Cell::with_char(' '));
+ write_string_to_grid(
+ &message,
+ &mut self.data_columns.columns[0],
+ Color::Default,
+ Color::Default,
+ ((0, 0), (MAX_COLS - 1, 0)),
+ false,
+ );
+ }
+ }
}
impl fmt::Display for CompactListing {
@@ -412,6 +461,9 @@ impl CompactListing {
sort: (Default::default(), Default::default()),
subsort: (SortField::Date, SortOrder::Desc),
order: FnvHashMap::default(),
+ filter_term: String::new(),
+ filtered_selection: Vec::new(),
+ filtered_order: FnvHashMap::default(),
row_updates: StackVec::new(),
data_columns: DataColumns::default(),
dirty: true,
@@ -689,8 +741,8 @@ impl CompactListing {
self.order.insert(i, idx);
}
- let message = format!("Folder `{}` is empty.", mailbox.folder.name());
if self.length == 0 {
+ let message = format!("Folder `{}` is empty.", mailbox.folder.name());
self.data_columns.columns[0] =
CellBuffer::new(message.len(), self.length + 1, Cell::with_char(' '));
write_string_to_grid(
@@ -719,6 +771,142 @@ impl CompactListing {
_ => envelope.datetime().format("%Y-%m-%d %H:%M:%S").to_string(),
}
}
+ fn draw_filtered_selection(&mut self, context: &mut Context) {
+ if self.filtered_selection.is_empty() {
+ return;
+ }
+ let account = &context.accounts[self.cursor_pos.0];
+ let mailbox = account[self.cursor_pos.1].as_ref().unwrap();
+
+ let threads = &account.collection.threads[&mailbox.folder.hash()];
+ self.length = 0;
+ let mut rows = Vec::with_capacity(1024);
+ let mut min_width = (0, 0, 0, 0, 0);
+
+ for (idx, envelope_hash) in self.filtered_selection.iter().enumerate() {
+ self.length += 1;
+ let envelope: &Envelope = &context.accounts[self.cursor_pos.0].get_env(&envelope_hash);
+ let t_idx = envelope.thread();
+ let strings = CompactListing::make_entry_string(
+ envelope,
+ threads[&envelope.thread()].len(),
+ idx,
+ threads.is_snoozed(t_idx),
+ );
+ min_width.0 = cmp::max(min_width.0, strings.0.grapheme_width()); /* index */
+ min_width.1 = cmp::max(min_width.1, strings.1.grapheme_width()); /* date */
+ min_width.2 = cmp::max(min_width.2, strings.2.grapheme_width()); /* from */
+ min_width.3 = cmp::max(min_width.3, strings.3.grapheme_width()); /* flags */
+ min_width.4 = cmp::max(min_width.4, strings.4.grapheme_width()); /* subject */
+ rows.push(strings);
+ }
+
+ /* index column */
+ self.data_columns.columns[0] =
+ CellBuffer::new(min_width.0, rows.len(), Cell::with_char(' '));
+ /* date column */
+ self.data_columns.columns[1] =
+ CellBuffer::new(min_width.1, rows.len(), Cell::with_char(' '));
+ /* from column */
+ self.data_columns.columns[2] =
+ CellBuffer::new(min_width.2, rows.len(), Cell::with_char(' '));
+ /* flags column */
+ self.data_columns.columns[3] =
+ CellBuffer::new(min_width.3, rows.len(), Cell::with_char(' '));
+ /* subject column */
+ self.data_columns.columns[4] =
+ CellBuffer::new(min_width.4, rows.len(), Cell::with_char(' '));
+
+ for ((idx, envelope_hash), strings) in self.filtered_selection.iter().enumerate().zip(rows)
+ {
+ let envelope: &Envelope = &context.accounts[self.cursor_pos.0].get_env(&envelope_hash);
+ 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
+ };
+ let (x, _) = write_string_to_grid(
+ &strings.0,
+ &mut self.data_columns.columns[0],
+ fg_color,
+ bg_color,
+ ((0, idx), (min_width.0, idx)),
+ false,
+ );
+ for x in x..min_width.0 {
+ self.data_columns.columns[0][(x, idx)].set_bg(bg_color);
+ }
+ let (x, _) = write_string_to_grid(
+ &strings.1,
+ &mut self.data_columns.columns[1],
+ fg_color,
+ bg_color,
+ ((0, idx), (min_width.1, idx)),
+ false,
+ );
+ for x in x..min_width.1 {
+ self.data_columns.columns[1][(x, idx)].set_bg(bg_color);
+ }
+ let (x, _) = write_string_to_grid(
+ &strings.2,
+ &mut self.data_columns.columns[2],
+ fg_color,
+ bg_color,
+ ((0, idx), (min_width.2, idx)),
+ false,
+ );
+ for x in x..min_width.2 {
+ self.data_columns.columns[2][(x, idx)].set_bg(bg_color);
+ }
+ let (x, _) = write_string_to_grid(
+ &strings.3,
+ &mut self.data_columns.columns[3],
+ fg_color,
+ bg_color,
+ ((0, idx), (min_width.3, idx)),
+ false,
+ );
+ 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.4,
+ &mut self.data_columns.columns[4],
+ fg_color,
+ bg_color,
+ ((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);
+ }
+ match (
+ threads.is_snoozed(envelope.thread()),
+ &context.accounts[self.cursor_pos.0]
+ .get_env(&envelope_hash)
+ .has_attachments(),
+ ) {
+ (true, true) => {
+ self.data_columns.columns[3][(0, idx)].set_fg(Color::Red);
+ self.data_columns.columns[3][(1, idx)].set_fg(Color::Byte(103));
+ }
+ (true, false) => {
+ self.data_columns.columns[3][(0, idx)].set_fg(Color::Red);
+ }
+ (false, true) => {
+ self.data_columns.columns[3][(0, idx)].set_fg(Color::Byte(103));
+ }
+ (false, false) => {}
+ }
+ }
+ }
}
impl Component for CompactListing {
@@ -727,6 +915,26 @@ impl Component for CompactListing {
if !self.is_dirty() {
return;
}
+ if !self.filtered_selection.is_empty() {
+ self.draw_filtered_selection(context);
+ let (upper_left, bottom_right) = area;
+ let (x, y) = write_string_to_grid(
+ &format!("Filter (Press ESC to exit): {}", self.filter_term),
+ grid,
+ Color::Default,
+ Color::Default,
+ area,
+ true,
+ );
+ clear_area(grid, ((x, y), set_y(bottom_right, y)));
+ context
+ .dirty_areas
+ .push_back((upper_left, set_y(bottom_right, y + 1)));
+
+ self.draw_list(grid, (set_y(upper_left, y + 1), bottom_right), context);
+ self.dirty = false;
+ return;
+ }
if !self.row_updates.is_empty() {
let (upper_left, bottom_right) = area;
while let Some(row) = self.row_updates.pop() {
@@ -784,7 +992,30 @@ impl Component for CompactListing {
return true;
}
UIEvent::Input(ref k) if !self.unfocused && *k == shortcuts["open_thread"] => {
- self.view = ThreadView::new(self.cursor_pos, None, context);
+ if self.filtered_selection.is_empty() {
+ self.view = ThreadView::new(self.cursor_pos, None, context);
+ } else {
+ let mut temp = self.cursor_pos;
+ let account = &mut context.accounts[self.cursor_pos.0];
+ let thread_hash = {
+ account
+ .get_env(&self.filtered_selection[self.cursor_pos.2])
+ .thread()
+ .clone()
+ };
+ let folder_hash = account[self.cursor_pos.1]
+ .as_ref()
+ .map(|m| m.folder.hash())
+ .unwrap();
+ let threads = &account.collection.threads[&folder_hash];
+ let root_thread_index = threads.root_iter().position(|t| t == thread_hash);
+ if let Some(pos) = root_thread_index {
+ temp.2 = pos;
+ self.view = ThreadView::new(temp, Some(thread_hash), context);
+ } else {
+ return true;
+ }
+ }
self.unfocused = true;
self.dirty = true;
return true;
@@ -837,6 +1068,13 @@ impl Component for CompactListing {
row,
context,
);
+ for h in self.filtered_selection.iter_mut() {
+ if *h == *old_hash {
+ *h = *new_hash;
+ break;
+ }
+ }
+
self.row_updates.push(*new_hash);
self.dirty = true;
} else {
@@ -860,6 +1098,7 @@ impl Component for CompactListing {
{
return true;
}
+ self.filtered_selection.clear();
self.new_cursor_pos.1 = *idx;
self.refresh_mailbox(context);
return true;
@@ -902,8 +1141,19 @@ impl Component for CompactListing {
self.refresh_mailbox(context);
return true;
}
+ Action::Listing(Filter(ref filter_term)) => {
+ self.filter(filter_term, context);
+ self.dirty = true;
+ }
_ => {}
},
+ UIEvent::Input(Key::Esc) => {
+ self.filter_term.clear();
+ self.filtered_selection.clear();
+ self.filtered_order.clear();
+ self.refresh_mailbox(context);
+ return true;
+ }
_ => {}
}
false
diff --git a/ui/src/components/utilities.rs b/ui/src/components/utilities.rs
index 05226520..59b4c797 100644
--- a/ui/src/components/utilities.rs
+++ b/ui/src/components/utilities.rs
@@ -714,7 +714,6 @@ impl Component for StatusBar {
.collect();
if suggestions.is_empty() && !self.auto_complete.suggestions().is_empty() {
self.auto_complete.set_suggestions(suggestions);
- self.auto_complete.set_cursor(0);
/* redraw self.container because we have got ridden of an autocomplete
* box, and it must be drawn over */
self.container.set_dirty();
diff --git a/ui/src/execute.rs b/ui/src/execute.rs
index 81822637..d74da648 100644
--- a/ui/src/execute.rs
+++ b/ui/src/execute.rs
@@ -97,6 +97,15 @@ named!(
);
named!(
+ filter<Action>,
+ do_parse!(
+ ws!(tag!("filter"))
+ >> string: map_res!(call!(not_line_ending), std::str::from_utf8)
+ >> (Listing(Filter(String::from(string))))
+ )
+);
+
+named!(
mailinglist<Action>,
alt_complete!(
map!(ws!(tag!("list-post")), |_| MailingListAction(ListPost))
@@ -110,5 +119,5 @@ named!(
);
named!(pub parse_command<Action>,
- alt_complete!( goto | toggle | sort | subsort | close | toggle_thread_snooze | mailinglist)
+ alt_complete!( goto | toggle | sort | subsort | close | toggle_thread_snooze | mailinglist |filter)
);
diff --git a/ui/src/execute/actions.rs b/ui/src/execute/actions.rs
index 2a89ef24..d7de81f9 100644
--- a/ui/src/execute/actions.rs
+++ b/ui/src/execute/actions.rs
@@ -36,6 +36,7 @@ pub enum ListingAction {
SetPlain,
SetThreaded,
SetCompact,
+ Filter(String),
}
#[derive(Debug)]