From a17f0b4fd44ca9d9a9e81c735ccd96493d8ed0b9 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Sun, 7 Jun 2020 14:25:33 +0300 Subject: [PATCH] listing: rework MailListingTrait split redraw_list() to redraw_threads_list() and redraw_envelope_list() --- src/components/mail/listing.rs | 13 +- src/components/mail/listing/compact.rs | 666 +++++++++++-------- src/components/mail/listing/conversations.rs | 462 ++++++------- src/components/mail/listing/offline.rs | 12 + src/components/mail/listing/plain.rs | 101 +-- src/components/mail/listing/thread.rs | 25 +- src/types.rs | 2 +- 7 files changed, 723 insertions(+), 558 deletions(-) diff --git a/src/components/mail/listing.rs b/src/components/mail/listing.rs index 86eabfc72..4a3e19fad 100644 --- a/src/components/mail/listing.rs +++ b/src/components/mail/listing.rs @@ -228,8 +228,17 @@ pub trait MailListingTrait: ListingTrait { fn row_updates(&mut self) -> &mut SmallVec<[ThreadHash; 8]>; fn get_focused_items(&self, _context: &Context) -> SmallVec<[ThreadHash; 8]>; - fn redraw_list(&mut self, _context: &Context, _items: Box>) { - unimplemented!() + fn redraw_threads_list( + &mut self, + context: &Context, + items: Box>, + ); + + fn redraw_envelope_list( + &mut self, + _context: &Context, + _items: Box>, + ) { } /// Use `force` when there have been changes in the mailbox or account lists in `context` diff --git a/src/components/mail/listing/compact.rs b/src/components/mail/listing/compact.rs index e7f204396..f684d204b 100644 --- a/src/components/mail/listing/compact.rs +++ b/src/components/mail/listing/compact.rs @@ -58,6 +58,8 @@ pub struct CompactListing { order: HashMap, /// Cache current view. data_columns: DataColumns, + rows_drawn: SegmentTree, + rows: Vec<((usize, (ThreadHash, EnvelopeHash)), EntryStrings)>, filter_term: String, filtered_selection: Vec, @@ -172,7 +174,7 @@ impl MailListingTrait for CompactListing { &context.accounts[self.cursor_pos.0].collection.envelopes, ); - self.redraw_list( + self.redraw_threads_list( context, Box::new(roots.into_iter()) as Box>, ); @@ -185,6 +187,167 @@ impl MailListingTrait for CompactListing { self.view = ThreadView::new(self.new_cursor_pos, thread, None, context); } } + + fn redraw_threads_list( + &mut self, + context: &Context, + items: Box>, + ) { + let account = &context.accounts[self.cursor_pos.0]; + + let threads = &account.collection.threads[&self.cursor_pos.1]; + 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 mut row_widths: ( + SmallVec<[u8; 1024]>, + SmallVec<[u8; 1024]>, + SmallVec<[u8; 1024]>, + SmallVec<[u8; 1024]>, + SmallVec<[u8; 1024]>, + ) = ( + SmallVec::new(), + SmallVec::new(), + SmallVec::new(), + SmallVec::new(), + SmallVec::new(), + ); + + for thread in items { + let thread_node = &threads.thread_nodes()[&threads.thread_ref(thread).root()]; + let root_env_hash = thread_node.message().unwrap_or_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() + }); + if !context.accounts[self.cursor_pos.0].contains_key(root_env_hash) { + debug!("key = {}", root_env_hash); + debug!( + "name = {} {}", + account[&self.cursor_pos.1].name(), + context.accounts[self.cursor_pos.0].name() + ); + debug!("{:#?}", context.accounts); + + panic!(); + } + let root_envelope: EnvelopeRef = context.accounts[self.cursor_pos.0] + .collection + .get_env(root_env_hash); + use melib::search::QueryTrait; + if let Some(filter_query) = mailbox_settings!( + context[self.cursor_pos.0][&self.cursor_pos.1] + .listing + .filter + ) + .as_ref() + { + if !root_envelope.is_match(filter_query) { + continue; + } + } + + let entry_strings = self.make_entry_string(&root_envelope, context, threads, thread); + row_widths.1.push( + entry_strings + .date + .grapheme_width() + .try_into() + .unwrap_or(255), + ); /* date */ + row_widths.2.push( + entry_strings + .from + .grapheme_width() + .try_into() + .unwrap_or(255), + ); /* from */ + row_widths.3.push( + entry_strings + .flag + .grapheme_width() + .try_into() + .unwrap_or(255), + ); /* flags */ + row_widths.4.push( + (entry_strings.subject.grapheme_width() + 1 + entry_strings.tags.grapheme_width()) + .try_into() + .unwrap_or(255), + ); + 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() + 1 + entry_strings.tags.grapheme_width(), + ); /* subject */ + rows.push(((self.length, (thread, root_env_hash)), entry_strings)); + self.all_threads.insert(thread); + + self.order.insert(thread, self.length); + self.selection.insert(thread, false); + self.length += 1; + } + + min_width.0 = self.length.saturating_sub(1).to_string().len(); + + let default_cell = { + let mut ret = Cell::with_char(' '); + ret.set_fg(self.color_cache.theme_default.fg) + .set_bg(self.color_cache.theme_default.bg) + .set_attrs(self.color_cache.theme_default.attrs); + ret + }; + /* index column */ + self.data_columns.columns[0] = + CellBuffer::new_with_context(min_width.0, rows.len(), default_cell, context); + + /* date column */ + self.data_columns.columns[1] = + CellBuffer::new_with_context(min_width.1, rows.len(), default_cell, context); + /* from column */ + self.data_columns.columns[2] = + CellBuffer::new_with_context(min_width.2, rows.len(), default_cell, context); + self.data_columns.segment_tree[2] = row_widths.2.into(); + /* flags column */ + self.data_columns.columns[3] = + CellBuffer::new_with_context(min_width.3, rows.len(), default_cell, context); + /* subject column */ + self.data_columns.columns[4] = + CellBuffer::new_with_context(min_width.4, rows.len(), default_cell, context); + self.data_columns.segment_tree[4] = row_widths.4.into(); + + self.rows = rows; + self.rows_drawn = SegmentTree::from( + std::iter::repeat(1) + .take(self.rows.len()) + .collect::>(), + ); + debug_assert!(self.rows_drawn.array.len() == self.rows.len()); + self.draw_rows( + context, + 0, + std::cmp::min(80, self.rows.len().saturating_sub(1)), + ); + if self.length == 0 && self.filter_term.is_empty() { + let message = format!("{} is empty", account[&self.cursor_pos.1].name()); + self.data_columns.columns[0] = + CellBuffer::new_with_context(message.len(), self.length + 1, default_cell, context); + write_string_to_grid( + &message, + &mut self.data_columns.columns[0], + self.color_cache.theme_default.fg, + self.color_cache.theme_default.bg, + self.color_cache.theme_default.attrs, + ((0, 0), (MAX_COLS - 1, 0)), + None, + ); + } + } } impl ListingTrait for CompactListing { @@ -366,6 +529,11 @@ impl ListingTrait for CompactListing { let page_no = (self.new_cursor_pos.2).wrapping_div(rows); let top_idx = page_no * rows; + self.draw_rows( + context, + top_idx, + cmp::min(self.length.saturating_sub(1), top_idx + rows - 1), + ); /* If cursor position has changed, remove the highlight from the previous position and * apply it in the new one. */ @@ -601,7 +769,7 @@ impl ListingTrait for CompactListing { self.data_columns.columns[0] = CellBuffer::new_with_context(0, 0, default_cell, context); } - self.redraw_list( + self.redraw_threads_list( context, Box::new(self.filtered_selection.clone().into_iter()) as Box>, @@ -669,6 +837,8 @@ impl CompactListing { selection: HashMap::default(), row_updates: SmallVec::new(), data_columns: DataColumns::default(), + rows_drawn: SegmentTree::default(), + rows: vec![], dirty: true, force_draw: true, unfocused: false, @@ -744,291 +914,6 @@ impl CompactListing { } } - fn redraw_list(&mut self, context: &Context, items: Box>) { - let account = &context.accounts[self.cursor_pos.0]; - - let threads = &account.collection.threads[&self.cursor_pos.1]; - 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 mut row_widths: ( - SmallVec<[u8; 1024]>, - SmallVec<[u8; 1024]>, - SmallVec<[u8; 1024]>, - SmallVec<[u8; 1024]>, - SmallVec<[u8; 1024]>, - ) = ( - SmallVec::new(), - SmallVec::new(), - SmallVec::new(), - SmallVec::new(), - SmallVec::new(), - ); - - for thread in items { - let thread_node = &threads.thread_nodes()[&threads.thread_ref(thread).root()]; - let root_env_hash = thread_node.message().unwrap_or_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() - }); - if !context.accounts[self.cursor_pos.0].contains_key(root_env_hash) { - debug!("key = {}", root_env_hash); - debug!( - "name = {} {}", - account[&self.cursor_pos.1].name(), - context.accounts[self.cursor_pos.0].name() - ); - debug!("{:#?}", context.accounts); - - panic!(); - } - let root_envelope: EnvelopeRef = context.accounts[self.cursor_pos.0] - .collection - .get_env(root_env_hash); - use melib::search::QueryTrait; - if let Some(filter_query) = mailbox_settings!( - context[self.cursor_pos.0][&self.cursor_pos.1] - .listing - .filter - ) - .as_ref() - { - if !root_envelope.is_match(filter_query) { - continue; - } - } - - let entry_strings = self.make_entry_string(&root_envelope, context, threads, thread); - row_widths.1.push( - entry_strings - .date - .grapheme_width() - .try_into() - .unwrap_or(255), - ); /* date */ - row_widths.2.push( - entry_strings - .from - .grapheme_width() - .try_into() - .unwrap_or(255), - ); /* from */ - row_widths.3.push( - entry_strings - .flag - .grapheme_width() - .try_into() - .unwrap_or(255), - ); /* flags */ - row_widths.4.push( - (entry_strings.subject.grapheme_width() + 1 + entry_strings.tags.grapheme_width()) - .try_into() - .unwrap_or(255), - ); - 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() + 1 + entry_strings.tags.grapheme_width(), - ); /* subject */ - rows.push(((self.length, (thread, root_env_hash)), entry_strings)); - self.all_threads.insert(thread); - - self.order.insert(thread, self.length); - self.selection.insert(thread, false); - self.length += 1; - } - - min_width.0 = self.length.saturating_sub(1).to_string().len(); - - let default_cell = { - let mut ret = Cell::with_char(' '); - ret.set_fg(self.color_cache.theme_default.fg) - .set_bg(self.color_cache.theme_default.bg) - .set_attrs(self.color_cache.theme_default.attrs); - ret - }; - /* index column */ - self.data_columns.columns[0] = - CellBuffer::new_with_context(min_width.0, rows.len(), default_cell, context); - - /* date column */ - self.data_columns.columns[1] = - CellBuffer::new_with_context(min_width.1, rows.len(), default_cell, context); - /* from column */ - self.data_columns.columns[2] = - CellBuffer::new_with_context(min_width.2, rows.len(), default_cell, context); - self.data_columns.segment_tree[2] = row_widths.2.into(); - /* flags column */ - self.data_columns.columns[3] = - CellBuffer::new_with_context(min_width.3, rows.len(), default_cell, context); - /* subject column */ - self.data_columns.columns[4] = - CellBuffer::new_with_context(min_width.4, rows.len(), default_cell, context); - self.data_columns.segment_tree[4] = row_widths.4.into(); - - for ((idx, (thread, root_env_hash)), strings) in rows { - if !context.accounts[self.cursor_pos.0].contains_key(root_env_hash) { - //debug!("key = {}", root_env_hash); - //debug!( - // "name = {} {}", - // account[&self.cursor_pos.1].name(), - // context.accounts[self.cursor_pos.0].name() - //); - //debug!("{:#?}", context.accounts); - - panic!(); - } - let thread = threads.thread_ref(thread); - let row_attr = if thread.unseen() > 0 { - if idx % 2 == 0 { - self.color_cache.even_unseen - } else { - self.color_cache.odd_unseen - } - } else if idx % 2 == 0 { - self.color_cache.even - } else { - self.color_cache.odd - }; - let (x, _) = write_string_to_grid( - &idx.to_string(), - &mut self.data_columns.columns[0], - row_attr.fg, - row_attr.bg, - row_attr.attrs, - ((0, idx), (min_width.0, idx)), - None, - ); - for x in x..min_width.0 { - self.data_columns.columns[0][(x, idx)] - .set_bg(row_attr.bg) - .set_attrs(row_attr.attrs); - } - let (x, _) = write_string_to_grid( - &strings.date, - &mut self.data_columns.columns[1], - row_attr.fg, - row_attr.bg, - row_attr.attrs, - ((0, idx), (min_width.1, idx)), - None, - ); - for x in x..min_width.1 { - self.data_columns.columns[1][(x, idx)] - .set_bg(row_attr.bg) - .set_attrs(row_attr.attrs); - } - let (x, _) = write_string_to_grid( - &strings.from, - &mut self.data_columns.columns[2], - row_attr.fg, - row_attr.bg, - row_attr.attrs, - ((0, idx), (min_width.2, idx)), - None, - ); - for x in x..min_width.2 { - self.data_columns.columns[2][(x, idx)] - .set_bg(row_attr.bg) - .set_attrs(row_attr.attrs); - } - let (x, _) = write_string_to_grid( - &strings.flag, - &mut self.data_columns.columns[3], - row_attr.fg, - row_attr.bg, - row_attr.attrs, - ((0, idx), (min_width.3, idx)), - None, - ); - for x in x..min_width.3 { - self.data_columns.columns[3][(x, idx)] - .set_bg(row_attr.bg) - .set_attrs(row_attr.attrs); - } - let (x, _) = write_string_to_grid( - &strings.subject, - &mut self.data_columns.columns[4], - row_attr.fg, - row_attr.bg, - row_attr.attrs, - ((0, idx), (min_width.4, idx)), - None, - ); - let x = { - let mut x = x + 1; - for (t, &color) in strings.tags.split_whitespace().zip(strings.tags.1.iter()) { - let color = color.unwrap_or(self.color_cache.tag_default.bg); - let (_x, _) = write_string_to_grid( - t, - &mut self.data_columns.columns[4], - self.color_cache.tag_default.fg, - color, - self.color_cache.tag_default.attrs, - ((x + 1, idx), (min_width.4, idx)), - None, - ); - self.data_columns.columns[4][(x, idx)].set_bg(color); - if _x < min_width.4 { - self.data_columns.columns[4][(_x, idx)].set_bg(color); - self.data_columns.columns[4][(_x, idx)].set_keep_bg(true); - } - for x in (x + 1).._x { - self.data_columns.columns[4][(x, idx)].set_keep_fg(true); - self.data_columns.columns[4][(x, idx)].set_keep_bg(true); - } - self.data_columns.columns[4][(x, idx)].set_keep_bg(true); - x = _x + 1; - } - x - }; - for x in x..min_width.4 { - self.data_columns.columns[4][(x, idx)] - .set_ch(' ') - .set_bg(row_attr.bg) - .set_attrs(row_attr.attrs); - } - match (thread.snoozed(), thread.has_attachments()) { - (true, true) => { - self.data_columns.columns[3][(0, idx)] - .set_fg(self.color_cache.attachment_flag.fg); - self.data_columns.columns[3][(2, idx)] - .set_fg(self.color_cache.thread_snooze_flag.fg); - } - (true, false) => { - self.data_columns.columns[3][(0, idx)] - .set_fg(self.color_cache.thread_snooze_flag.fg); - } - (false, true) => { - self.data_columns.columns[3][(0, idx)] - .set_fg(self.color_cache.attachment_flag.fg); - } - (false, false) => {} - } - } - if self.length == 0 && self.filter_term.is_empty() { - let message = format!("{} is empty", account[&self.cursor_pos.1].name()); - self.data_columns.columns[0] = - CellBuffer::new_with_context(message.len(), self.length + 1, default_cell, context); - write_string_to_grid( - &message, - &mut self.data_columns.columns[0], - self.color_cache.theme_default.fg, - self.color_cache.theme_default.bg, - self.color_cache.theme_default.attrs, - ((0, 0), (MAX_COLS - 1, 0)), - None, - ); - } - } - fn get_thread_under_cursor(&self, cursor: usize) -> ThreadHash { if self.filter_term.is_empty() { *self @@ -1186,6 +1071,213 @@ impl CompactListing { } } } + + fn draw_rows(&mut self, context: &Context, start: usize, end: usize) { + if self.length == 0 { + return; + } + debug_assert!(end >= start); + if self.rows_drawn.get_max(start, end) == 0 { + //debug!("not drawing {}-{}", start, end); + return; + } + //debug!("drawing {}-{}", start, end); + for i in start..=end { + self.rows_drawn.update(i, 0); + } + let min_width = ( + self.data_columns.columns[0].size().0, + self.data_columns.columns[1].size().0, + self.data_columns.columns[2].size().0, + self.data_columns.columns[3].size().0, + self.data_columns.columns[4].size().0, + ); + let account = &context.accounts[self.cursor_pos.0]; + + let threads = &account.collection.threads[&self.cursor_pos.1]; + + for ((idx, (thread, root_env_hash)), strings) in + self.rows.iter().skip(start).take(end - start + 1) + { + let idx = *idx; + if !context.accounts[self.cursor_pos.0].contains_key(*root_env_hash) { + //debug!("key = {}", root_env_hash); + //debug!( + // "name = {} {}", + // account[&self.cursor_pos.1].name(), + // context.accounts[self.cursor_pos.0].name() + //); + //debug!("{:#?}", context.accounts); + + panic!(); + } + let thread = threads.thread_ref(*thread); + let row_attr = if thread.unseen() > 0 { + if idx % 2 == 0 { + self.color_cache.even_unseen + } else { + self.color_cache.odd_unseen + } + } else if idx % 2 == 0 { + self.color_cache.even + } else { + self.color_cache.odd + }; + let (x, _) = write_string_to_grid( + &idx.to_string(), + &mut self.data_columns.columns[0], + row_attr.fg, + row_attr.bg, + row_attr.attrs, + ((0, idx), (min_width.0, idx)), + None, + ); + for x in x..min_width.0 { + self.data_columns.columns[0][(x, idx)] + .set_bg(row_attr.bg) + .set_attrs(row_attr.attrs); + } + let (x, _) = write_string_to_grid( + &strings.date, + &mut self.data_columns.columns[1], + row_attr.fg, + row_attr.bg, + row_attr.attrs, + ((0, idx), (min_width.1, idx)), + None, + ); + for x in x..min_width.1 { + self.data_columns.columns[1][(x, idx)] + .set_bg(row_attr.bg) + .set_attrs(row_attr.attrs); + } + let (x, _) = write_string_to_grid( + &strings.from, + &mut self.data_columns.columns[2], + row_attr.fg, + row_attr.bg, + row_attr.attrs, + ((0, idx), (min_width.2, idx)), + None, + ); + #[cfg(feature = "regexp")] + { + for text_formatter in + debug!(crate::conf::text_format_regexps(context, "listing.from")) + { + let t = self.data_columns.columns[2].insert_tag(text_formatter.tag); + for _match in text_formatter.regexp.0.find_iter(strings.from.as_bytes()) { + if let Ok(_match) = _match { + self.data_columns.columns[2].set_tag( + t, + (_match.start(), idx), + (_match.end(), idx), + ); + } + } + } + } + for x in x..min_width.2 { + self.data_columns.columns[2][(x, idx)] + .set_bg(row_attr.bg) + .set_attrs(row_attr.attrs); + } + let (x, _) = write_string_to_grid( + &strings.flag, + &mut self.data_columns.columns[3], + row_attr.fg, + row_attr.bg, + row_attr.attrs, + ((0, idx), (min_width.3, idx)), + None, + ); + for x in x..min_width.3 { + self.data_columns.columns[3][(x, idx)] + .set_bg(row_attr.bg) + .set_attrs(row_attr.attrs); + } + let (x, _) = write_string_to_grid( + &strings.subject, + &mut self.data_columns.columns[4], + row_attr.fg, + row_attr.bg, + row_attr.attrs, + ((0, idx), (min_width.4, idx)), + None, + ); + #[cfg(feature = "regexp")] + { + for text_formatter in + debug!(crate::conf::text_format_regexps(context, "listing.subject")) + { + let t = self.data_columns.columns[4].insert_tag(text_formatter.tag); + for _match in text_formatter + .regexp + .0 + .find_iter(strings.subject.as_bytes()) + { + if let Ok(_match) = _match { + self.data_columns.columns[4].set_tag( + t, + (_match.start(), idx), + (_match.end(), idx), + ); + } + } + } + } + let x = { + let mut x = x + 1; + for (t, &color) in strings.tags.split_whitespace().zip(strings.tags.1.iter()) { + let color = color.unwrap_or(self.color_cache.tag_default.bg); + let (_x, _) = write_string_to_grid( + t, + &mut self.data_columns.columns[4], + self.color_cache.tag_default.fg, + color, + self.color_cache.tag_default.attrs, + ((x + 1, idx), (min_width.4, idx)), + None, + ); + self.data_columns.columns[4][(x, idx)].set_bg(color); + if _x < min_width.4 { + self.data_columns.columns[4][(_x, idx)].set_bg(color); + self.data_columns.columns[4][(_x, idx)].set_keep_bg(true); + } + for x in (x + 1).._x { + self.data_columns.columns[4][(x, idx)].set_keep_fg(true); + self.data_columns.columns[4][(x, idx)].set_keep_bg(true); + } + self.data_columns.columns[4][(x, idx)].set_keep_bg(true); + x = _x + 1; + } + x + }; + for x in x..min_width.4 { + self.data_columns.columns[4][(x, idx)] + .set_ch(' ') + .set_bg(row_attr.bg) + .set_attrs(row_attr.attrs); + } + match (thread.snoozed(), thread.has_attachments()) { + (true, true) => { + self.data_columns.columns[3][(0, idx)] + .set_fg(self.color_cache.attachment_flag.fg); + self.data_columns.columns[3][(2, idx)] + .set_fg(self.color_cache.thread_snooze_flag.fg); + } + (true, false) => { + self.data_columns.columns[3][(0, idx)] + .set_fg(self.color_cache.thread_snooze_flag.fg); + } + (false, true) => { + self.data_columns.columns[3][(0, idx)] + .set_fg(self.color_cache.attachment_flag.fg); + } + (false, false) => {} + } + } + } } impl Component for CompactListing { diff --git a/src/components/mail/listing/conversations.rs b/src/components/mail/listing/conversations.rs index f2b054797..b8efe0a09 100644 --- a/src/components/mail/listing/conversations.rs +++ b/src/components/mail/listing/conversations.rs @@ -155,7 +155,7 @@ impl MailListingTrait for ConversationsListing { &context.accounts[self.cursor_pos.0].collection.envelopes, ); - self.redraw_list( + self.redraw_threads_list( context, Box::new(roots.into_iter()) as Box>, ); @@ -169,6 +169,242 @@ impl MailListingTrait for ConversationsListing { self.view = ThreadView::new(self.new_cursor_pos, thread_group, None, context); } } + + fn redraw_threads_list( + &mut self, + context: &Context, + items: Box>, + ) { + let account = &context.accounts[self.cursor_pos.0]; + + let threads = &account.collection.threads[&self.cursor_pos.1]; + self.order.clear(); + self.selection.clear(); + self.length = 0; + let mut rows = Vec::with_capacity(1024); + let mut max_entry_columns = 0; + + let mut from_address_list = Vec::new(); + let mut from_address_set: std::collections::HashSet> = + std::collections::HashSet::new(); + 'items_for_loop: for thread in items { + let thread_node = &threads.thread_nodes()[&threads.thread_ref(thread).root()]; + let root_env_hash = if let Some(h) = thread_node.message().or_else(|| { + if thread_node.children().is_empty() { + return None; + } + let mut iter_ptr = thread_node.children()[0]; + while threads.thread_nodes()[&iter_ptr].message().is_none() { + if threads.thread_nodes()[&iter_ptr].children().is_empty() { + return None; + } + iter_ptr = threads.thread_nodes()[&iter_ptr].children()[0]; + } + threads.thread_nodes()[&iter_ptr].message() + }) { + h + } else { + continue 'items_for_loop; + }; + if !context.accounts[self.cursor_pos.0].contains_key(root_env_hash) { + debug!("key = {}", root_env_hash); + debug!( + "name = {} {}", + account[&self.cursor_pos.1].name(), + context.accounts[self.cursor_pos.0].name() + ); + debug!("{:#?}", context.accounts); + + panic!(); + } + from_address_list.clear(); + from_address_set.clear(); + for (_, h) in threads.thread_group_iter(thread) { + let env_hash = threads.thread_nodes()[&h].message().unwrap(); + + let envelope: &EnvelopeRef = &context.accounts[self.cursor_pos.0] + .collection + .get_env(env_hash); + for addr in envelope.from().iter() { + if from_address_set.contains(addr.raw()) { + continue; + } + from_address_set.insert(addr.raw().to_vec()); + from_address_list.push(addr.clone()); + } + } + let root_envelope: &EnvelopeRef = &context.accounts[self.cursor_pos.0] + .collection + .get_env(root_env_hash); + use melib::search::QueryTrait; + if let Some(filter_query) = mailbox_settings!( + context[self.cursor_pos.0][&self.cursor_pos.1] + .listing + .filter + ) + .as_ref() + { + if !root_envelope.is_match(filter_query) { + continue; + } + } + + let strings = + self.make_entry_string(root_envelope, context, &from_address_list, threads, thread); + max_entry_columns = std::cmp::max( + max_entry_columns, + strings.flag.len() + + 3 + + strings.subject.grapheme_width() + + 1 + + strings.tags.grapheme_width(), + ); + max_entry_columns = std::cmp::max( + max_entry_columns, + strings.date.len() + 1 + strings.from.grapheme_width(), + ); + rows.push(((self.length, (thread, root_env_hash)), strings)); + self.all_threads.insert(thread); + + self.order.insert(thread, self.length); + self.selection.insert(thread, false); + self.length += 1; + } + + let width = max_entry_columns; + self.content = + CellBuffer::new_with_context(width, 4 * rows.len(), Cell::with_char(' '), context); + + let padding_fg = self.color_cache.padding.fg; + + for ((idx, (thread, root_env_hash)), strings) in rows { + if !context.accounts[self.cursor_pos.0].contains_key(root_env_hash) { + panic!(); + } + let thread = threads.thread_ref(thread); + let fg_color = if thread.unseen() > 0 { + self.color_cache.unseen.fg + } else { + self.color_cache.theme_default.fg + }; + let bg_color = if thread.unseen() > 0 { + self.color_cache.unseen.bg + } else { + self.color_cache.theme_default.bg + }; + /* draw flags */ + let (x, _) = write_string_to_grid( + &strings.flag, + &mut self.content, + fg_color, + bg_color, + Attr::DEFAULT, + ((0, 3 * idx), (width - 1, 3 * idx)), + None, + ); + for x in x..(x + 3) { + self.content[(x, 3 * idx)].set_bg(bg_color); + } + /* draw subject */ + let (mut x, _) = write_string_to_grid( + &strings.subject, + &mut self.content, + fg_color, + bg_color, + Attr::BOLD, + ((x, 3 * idx), (width - 1, 3 * idx)), + None, + ); + for (t, &color) in strings.tags.split_whitespace().zip(strings.tags.1.iter()) { + let color = color.unwrap_or(self.color_cache.tag_default.bg); + let (_x, _) = write_string_to_grid( + t, + &mut self.content, + self.color_cache.tag_default.fg, + color, + self.color_cache.tag_default.attrs, + ((x + 1, 3 * idx), (width - 1, 3 * idx)), + None, + ); + self.content[(x, 3 * idx)].set_bg(color); + if _x < width { + self.content[(_x, 3 * idx)].set_bg(color).set_keep_bg(true); + } + for x in (x + 1).._x { + self.content[(x, 3 * idx)] + .set_keep_fg(true) + .set_keep_bg(true); + } + self.content[(x, 3 * idx)].set_keep_bg(true); + x = _x + 1; + } + for x in x..width { + self.content[(x, 3 * idx)] + .set_ch(' ') + .set_fg(fg_color) + .set_bg(bg_color); + } + /* Next line, draw date */ + let (x, _) = write_string_to_grid( + &strings.date, + &mut self.content, + fg_color, + bg_color, + Attr::DEFAULT, + ((0, 3 * idx + 1), (width - 1, 3 * idx + 1)), + None, + ); + for x in x..(x + 4) { + self.content[(x, 3 * idx + 1)] + .set_ch('▁') + .set_fg(fg_color) + .set_bg(bg_color); + } + /* draw from */ + let (x, _) = write_string_to_grid( + &strings.from, + &mut self.content, + fg_color, + bg_color, + Attr::DEFAULT, + ((x + 4, 3 * idx + 1), (width - 1, 3 * idx + 1)), + None, + ); + + for x in x..width { + self.content[(x, 3 * idx + 1)] + .set_ch('▁') + .set_fg(fg_color) + .set_bg(bg_color); + } + for x in 0..width { + self.content[(x, 3 * idx + 2)] + .set_ch('▓') + .set_fg(padding_fg) + .set_bg(bg_color); + } + } + if self.length == 0 && self.filter_term.is_empty() { + let default_cell = { + let mut ret = Cell::with_char(' '); + ret.set_fg(self.color_cache.theme_default.fg) + .set_bg(self.color_cache.theme_default.bg) + .set_attrs(self.color_cache.theme_default.attrs); + ret + }; + let message = format!("{} is empty", account[&self.cursor_pos.1].name()); + self.content = CellBuffer::new_with_context(message.len(), 1, default_cell, context); + write_string_to_grid( + &message, + &mut self.content, + self.color_cache.theme_default.fg, + self.color_cache.theme_default.bg, + self.color_cache.theme_default.attrs, + ((0, 0), (message.len() - 1, 0)), + None, + ); + } + } } impl ListingTrait for ConversationsListing { @@ -508,7 +744,7 @@ impl ListingTrait for ConversationsListing { }; self.content = CellBuffer::new_with_context(0, 0, default_cell, context); } - self.redraw_list( + self.redraw_threads_list( context, Box::new(self.filtered_selection.clone().into_iter()) as Box>, @@ -652,228 +888,6 @@ impl ConversationsListing { } } - fn redraw_list(&mut self, context: &Context, items: Box>) { - let account = &context.accounts[self.cursor_pos.0]; - - let threads = &account.collection.threads[&self.cursor_pos.1]; - self.order.clear(); - self.selection.clear(); - self.length = 0; - let mut rows = Vec::with_capacity(1024); - let mut max_entry_columns = 0; - - let mut from_address_list = Vec::new(); - let mut from_address_set: std::collections::HashSet> = - std::collections::HashSet::new(); - for thread in items { - let thread_node = &threads.thread_nodes()[&threads.thread_ref(thread).root()]; - let root_env_hash = thread_node.message().unwrap_or_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() - }); - if !context.accounts[self.cursor_pos.0].contains_key(root_env_hash) { - debug!("key = {}", root_env_hash); - debug!( - "name = {} {}", - account[&self.cursor_pos.1].name(), - context.accounts[self.cursor_pos.0].name() - ); - debug!("{:#?}", context.accounts); - - panic!(); - } - from_address_list.clear(); - from_address_set.clear(); - for (_, h) in threads.thread_group_iter(thread) { - let env_hash = threads.thread_nodes()[&h].message().unwrap(); - - let envelope: &EnvelopeRef = &context.accounts[self.cursor_pos.0] - .collection - .get_env(env_hash); - for addr in envelope.from().iter() { - if from_address_set.contains(addr.raw()) { - continue; - } - from_address_set.insert(addr.raw().to_vec()); - from_address_list.push(addr.clone()); - } - } - let root_envelope: &EnvelopeRef = &context.accounts[self.cursor_pos.0] - .collection - .get_env(root_env_hash); - use melib::search::QueryTrait; - if let Some(filter_query) = mailbox_settings!( - context[self.cursor_pos.0][&self.cursor_pos.1] - .listing - .filter - ) - .as_ref() - { - if !root_envelope.is_match(filter_query) { - continue; - } - } - - let strings = - self.make_entry_string(root_envelope, context, &from_address_list, threads, thread); - max_entry_columns = std::cmp::max( - max_entry_columns, - strings.flag.len() - + 3 - + strings.subject.grapheme_width() - + 1 - + strings.tags.grapheme_width(), - ); - max_entry_columns = std::cmp::max( - max_entry_columns, - strings.date.len() + 1 + strings.from.grapheme_width(), - ); - rows.push(((self.length, (thread, root_env_hash)), strings)); - self.all_threads.insert(thread); - - self.order.insert(thread, self.length); - self.selection.insert(thread, false); - self.length += 1; - } - - let width = max_entry_columns; - self.content = - CellBuffer::new_with_context(width, 4 * rows.len(), Cell::with_char(' '), context); - - let padding_fg = self.color_cache.padding.fg; - - for ((idx, (thread, root_env_hash)), strings) in rows { - if !context.accounts[self.cursor_pos.0].contains_key(root_env_hash) { - panic!(); - } - let thread = threads.thread_ref(thread); - let fg_color = if thread.unseen() > 0 { - self.color_cache.unseen.fg - } else { - self.color_cache.theme_default.fg - }; - let bg_color = if thread.unseen() > 0 { - self.color_cache.unseen.bg - } else { - self.color_cache.theme_default.bg - }; - /* draw flags */ - let (x, _) = write_string_to_grid( - &strings.flag, - &mut self.content, - fg_color, - bg_color, - Attr::DEFAULT, - ((0, 3 * idx), (width - 1, 3 * idx)), - None, - ); - for x in x..(x + 3) { - self.content[(x, 3 * idx)].set_bg(bg_color); - } - /* draw subject */ - let (mut x, _) = write_string_to_grid( - &strings.subject, - &mut self.content, - fg_color, - bg_color, - Attr::BOLD, - ((x, 3 * idx), (width - 1, 3 * idx)), - None, - ); - for (t, &color) in strings.tags.split_whitespace().zip(strings.tags.1.iter()) { - let color = color.unwrap_or(self.color_cache.tag_default.bg); - let (_x, _) = write_string_to_grid( - t, - &mut self.content, - self.color_cache.tag_default.fg, - color, - self.color_cache.tag_default.attrs, - ((x + 1, 3 * idx), (width - 1, 3 * idx)), - None, - ); - self.content[(x, 3 * idx)].set_bg(color); - if _x < width { - self.content[(_x, 3 * idx)].set_bg(color).set_keep_bg(true); - } - for x in (x + 1).._x { - self.content[(x, 3 * idx)] - .set_keep_fg(true) - .set_keep_bg(true); - } - self.content[(x, 3 * idx)].set_keep_bg(true); - x = _x + 1; - } - for x in x..width { - self.content[(x, 3 * idx)] - .set_ch(' ') - .set_fg(fg_color) - .set_bg(bg_color); - } - /* Next line, draw date */ - let (x, _) = write_string_to_grid( - &strings.date, - &mut self.content, - fg_color, - bg_color, - Attr::DEFAULT, - ((0, 3 * idx + 1), (width - 1, 3 * idx + 1)), - None, - ); - for x in x..(x + 4) { - self.content[(x, 3 * idx + 1)] - .set_ch('▁') - .set_fg(fg_color) - .set_bg(bg_color); - } - /* draw from */ - let (x, _) = write_string_to_grid( - &strings.from, - &mut self.content, - fg_color, - bg_color, - Attr::DEFAULT, - ((x + 4, 3 * idx + 1), (width - 1, 3 * idx + 1)), - None, - ); - - for x in x..width { - self.content[(x, 3 * idx + 1)] - .set_ch('▁') - .set_fg(fg_color) - .set_bg(bg_color); - } - for x in 0..width { - self.content[(x, 3 * idx + 2)] - .set_ch('▓') - .set_fg(padding_fg) - .set_bg(bg_color); - } - } - if self.length == 0 && self.filter_term.is_empty() { - let default_cell = { - let mut ret = Cell::with_char(' '); - ret.set_fg(self.color_cache.theme_default.fg) - .set_bg(self.color_cache.theme_default.bg) - .set_attrs(self.color_cache.theme_default.attrs); - ret - }; - let message = format!("{} is empty", account[&self.cursor_pos.1].name()); - self.content = CellBuffer::new_with_context(message.len(), 1, default_cell, context); - write_string_to_grid( - &message, - &mut self.content, - self.color_cache.theme_default.fg, - self.color_cache.theme_default.bg, - self.color_cache.theme_default.attrs, - ((0, 0), (message.len() - 1, 0)), - None, - ); - } - } - pub(super) fn format_date(context: &Context, epoch: UnixTimestamp) -> String { let d = std::time::UNIX_EPOCH + std::time::Duration::from_secs(epoch); let now: std::time::Duration = std::time::SystemTime::now() diff --git a/src/components/mail/listing/offline.rs b/src/components/mail/listing/offline.rs index a520f492d..d898a67e6 100644 --- a/src/components/mail/listing/offline.rs +++ b/src/components/mail/listing/offline.rs @@ -40,6 +40,18 @@ impl MailListingTrait for OfflineListing { } fn refresh_mailbox(&mut self, _context: &mut Context, _force: bool) {} + fn redraw_threads_list( + &mut self, + _context: &Context, + _items: Box>, + ) { + } + fn redraw_envelope_list( + &mut self, + _context: &Context, + _items: Box>, + ) { + } } impl ListingTrait for OfflineListing { diff --git a/src/components/mail/listing/plain.rs b/src/components/mail/listing/plain.rs index 01b94251b..9519334a2 100644 --- a/src/components/mail/listing/plain.rs +++ b/src/components/mail/listing/plain.rs @@ -176,8 +176,37 @@ impl MailListingTrait for PlainListing { .iter() .map(|h| (*h, env_lck[h].thread())) .collect(); + let sort = self.sort; + self.local_collection.sort_by(|a, b| match sort { + (SortField::Date, SortOrder::Desc) => { + let ma = &env_lck[a]; + let mb = &env_lck[b]; + mb.date().cmp(&ma.date()) + } + (SortField::Date, SortOrder::Asc) => { + let ma = &env_lck[a]; + let mb = &env_lck[b]; + ma.date().cmp(&mb.date()) + } + (SortField::Subject, SortOrder::Desc) => { + let ma = &env_lck[a]; + let mb = &env_lck[b]; + ma.subject().cmp(&mb.subject()) + } + (SortField::Subject, SortOrder::Asc) => { + let ma = &env_lck[a]; + let mb = &env_lck[b]; + mb.subject().cmp(&ma.subject()) + } + }); + for &env_hash in &self.local_collection { + self.all_envelopes.insert(env_hash); + } + let items = Box::new(self.local_collection.clone().into_iter()) + as Box>; + + self.redraw_list(context, items); drop(env_lck); - self.redraw_list(context); if self.length > 0 { let env_hash = self.get_env_under_cursor(self.cursor_pos.2, context); @@ -189,6 +218,31 @@ impl MailListingTrait for PlainListing { } } } + + fn redraw_threads_list( + &mut self, + context: &Context, + items: Box>, + ) { + let account = &context.accounts[self.cursor_pos.0]; + let threads = &account.collection.threads[&self.cursor_pos.1]; + let roots = items + .filter_map(|r| threads.groups[&r].root().map(|r| r.root)) + .collect::<_>(); + let thread_nodes: &HashMap = &threads.thread_nodes(); + let env_hash_iter = Box::new( + threads + .threads_group_iter(roots) + .filter_map(|(_, thread_node_hash, _)| { + let thread_node = &thread_nodes[&thread_node_hash]; + + thread_node.message() + }) + .collect::>() + .into_iter(), + ) as Box>; + self.redraw_list(context, env_hash_iter); + } } impl ListingTrait for PlainListing { @@ -571,7 +625,11 @@ impl ListingTrait for PlainListing { self.data_columns.columns[0] = CellBuffer::new_with_context(0, 0, default_cell, context); } - self.redraw_list(context); + self.redraw_list( + context, + Box::new(self.filtered_selection.clone().into_iter()) + as Box>, + ); } Err(e) => { self.cursor_pos.2 = 0; @@ -690,7 +748,7 @@ impl PlainListing { } } - fn redraw_list(&mut self, context: &Context) { + fn redraw_list(&mut self, context: &Context, iter: Box>) { let account = &context.accounts[self.cursor_pos.0]; let mailbox = &account[&self.cursor_pos.1]; @@ -700,40 +758,6 @@ impl PlainListing { let mut rows = Vec::with_capacity(1024); let mut min_width = (0, 0, 0, 0, 0); - let envelopes = account.collection.envelopes.read().unwrap(); - let sort = self.sort; - self.local_collection.sort_by(|a, b| match sort { - (SortField::Date, SortOrder::Desc) => { - let ma = &envelopes[a]; - let mb = &envelopes[b]; - mb.date().cmp(&ma.date()) - } - (SortField::Date, SortOrder::Asc) => { - let ma = &envelopes[a]; - let mb = &envelopes[b]; - ma.date().cmp(&mb.date()) - } - (SortField::Subject, SortOrder::Desc) => { - let ma = &envelopes[a]; - let mb = &envelopes[b]; - ma.subject().cmp(&mb.subject()) - } - (SortField::Subject, SortOrder::Asc) => { - let ma = &envelopes[a]; - let mb = &envelopes[b]; - mb.subject().cmp(&ma.subject()) - } - }); - - 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 i in iter { if !context.accounts[self.cursor_pos.0].contains_key(i) { debug!("key = {}", i); @@ -769,9 +793,6 @@ impl PlainListing { entry_strings.subject.grapheme_width() + 1 + entry_strings.tags.grapheme_width(), ); /* tags + subject */ rows.push(entry_strings); - if refresh_mailbox { - self.all_envelopes.insert(i); - } self.order.insert(i, self.length); self.selection.insert(i, false); diff --git a/src/components/mail/listing/thread.rs b/src/components/mail/listing/thread.rs index 55f529224..243ac7473 100644 --- a/src/components/mail/listing/thread.rs +++ b/src/components/mail/listing/thread.rs @@ -115,6 +115,25 @@ impl MailListingTrait for ThreadListing { return; } } + let threads = &context.accounts[self.cursor_pos.0].collection.threads[&self.cursor_pos.1]; + let mut roots = threads.roots(); + threads.group_inner_sort_by( + &mut roots, + self.sort, + &context.accounts[self.cursor_pos.0].collection.envelopes, + ); + + self.redraw_threads_list( + context, + Box::new(roots.into_iter()) as Box>, + ); + } + + fn redraw_threads_list( + &mut self, + context: &Context, + items: Box>, + ) { let account = &context.accounts[self.cursor_pos.0]; let threads = &account.collection.threads[&self.cursor_pos.1]; self.length = 0; @@ -146,10 +165,8 @@ impl MailListingTrait for ThreadListing { let mut indentations: Vec = Vec::with_capacity(6); let mut thread_idx = 0; // needed for alternate thread colors /* Draw threaded view. */ - let mut roots = threads.roots(); - threads.group_inner_sort_by(&mut roots, self.sort, &account.collection.envelopes); - let roots = roots - .into_iter() + + let roots = items .filter_map(|r| threads.groups[&r].root().map(|r| r.root)) .collect::<_>(); let mut iter = threads.threads_group_iter(roots).peekable(); diff --git a/src/types.rs b/src/types.rs index 2a893ab39..271704376 100644 --- a/src/types.rs +++ b/src/types.rs @@ -177,7 +177,7 @@ pub mod segment_tree { #[derive(Default, Debug, Clone)] pub struct SegmentTree { - array: SmallVec<[u8; 1024]>, + pub array: SmallVec<[u8; 1024]>, tree: SmallVec<[u8; 1024]>, }