summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorManos Pitsidianakis <el13635@mail.ntua.gr>2022-11-07 16:35:21 +0200
committerManos Pitsidianakis <el13635@mail.ntua.gr>2022-11-07 20:36:59 +0200
commitcc439b239ae27ae84fbcf50fbd82ec591c147c94 (patch)
treed358b08fc2db96c595a7331b491e2c2799563b69
parentb776409d6c9caec3732bada9e25637c2676af3b8 (diff)
downloadmeli-cc439b239ae27ae84fbcf50fbd82ec591c147c94.zip
mail/listing.rs: add RowsState struct
Keep state of rows in lists in this struct to reduce code duplication in list implementations
-rw-r--r--src/components/mail/listing.rs194
-rw-r--r--src/components/mail/listing/compact.rs690
-rw-r--r--src/components/mail/listing/conversations.rs418
-rw-r--r--src/components/mail/listing/offline.rs18
-rw-r--r--src/components/mail/listing/plain.rs274
-rw-r--r--src/components/mail/listing/thread.rs429
6 files changed, 1176 insertions, 847 deletions
diff --git a/src/components/mail/listing.rs b/src/components/mail/listing.rs
index ae4f85f0..99fcc78e 100644
--- a/src/components/mail/listing.rs
+++ b/src/components/mail/listing.rs
@@ -46,6 +46,156 @@ pub const DEFAULT_SELECTED_FLAG: &str = "☑️";
pub const DEFAULT_UNSEEN_FLAG: &str = "●";
pub const DEFAULT_SNOOZED_FLAG: &str = "💤";
+#[derive(Debug, Default)]
+pub struct RowsState<T> {
+ pub selection: HashMap<EnvelopeHash, bool>,
+ pub row_updates: SmallVec<[EnvelopeHash; 8]>,
+ pub thread_to_env: HashMap<ThreadHash, SmallVec<[EnvelopeHash; 8]>>,
+ pub env_to_thread: HashMap<EnvelopeHash, ThreadHash>,
+ pub thread_order: HashMap<ThreadHash, usize>,
+ pub env_order: HashMap<EnvelopeHash, usize>,
+ #[allow(clippy::type_complexity)]
+ pub entries: Vec<(T, EntryStrings)>,
+ pub all_threads: HashSet<ThreadHash>,
+ pub all_envelopes: HashSet<EnvelopeHash>,
+}
+
+impl<T> RowsState<T> {
+ #[inline(always)]
+ pub fn clear(&mut self) {
+ self.selection.clear();
+ self.row_updates.clear();
+ self.thread_to_env.clear();
+ self.env_to_thread.clear();
+ self.thread_order.clear();
+ self.env_order.clear();
+ self.entries.clear();
+ self.all_threads.clear();
+ self.all_envelopes.clear();
+ }
+
+ #[inline(always)]
+ pub fn is_thread_selected(&self, thread: ThreadHash) -> bool {
+ debug_assert!(self.all_threads.contains(&thread));
+ debug_assert!(self.thread_order.contains_key(&thread));
+ debug_assert!(self.thread_to_env.contains_key(&thread));
+ self.thread_to_env
+ .get(&thread)
+ .iter()
+ .map(|v| v.iter())
+ .flatten()
+ .any(|env_hash| self.selection[env_hash])
+ }
+
+ #[inline(always)]
+ pub fn insert_thread(
+ &mut self,
+ thread: ThreadHash,
+ metadata: T,
+ env_hashes: SmallVec<[EnvelopeHash; 8]>,
+ entry_strings: EntryStrings,
+ ) {
+ let index = self.entries.len();
+ self.thread_order.insert(thread, index);
+ self.all_threads.insert(thread);
+ for &env_hash in &env_hashes {
+ self.selection.insert(env_hash, false);
+ self.env_to_thread.insert(env_hash, thread);
+ self.env_order.insert(env_hash, index);
+ self.all_envelopes.insert(env_hash);
+ }
+ self.thread_to_env.insert(thread, env_hashes);
+ self.entries.push((metadata, entry_strings));
+ }
+
+ #[inline(always)]
+ pub fn row_update_add_thread(&mut self, thread: ThreadHash) {
+ let env_hashes = self.thread_to_env.entry(thread).or_default().clone();
+ for env_hash in env_hashes {
+ self.row_updates.push(env_hash);
+ }
+ }
+
+ #[inline(always)]
+ pub fn row_update_add_envelope(&mut self, env_hash: EnvelopeHash) {
+ self.row_updates.push(env_hash);
+ }
+
+ #[inline(always)]
+ pub fn contains_thread(&self, thread: ThreadHash) -> bool {
+ debug_assert_eq!(
+ self.all_threads.contains(&thread),
+ self.thread_order.contains_key(&thread)
+ );
+ debug_assert_eq!(
+ self.thread_order.contains_key(&thread),
+ self.thread_to_env.contains_key(&thread)
+ );
+ self.thread_order.contains_key(&thread)
+ }
+
+ #[inline(always)]
+ pub fn contains_env(&self, env_hash: EnvelopeHash) -> bool {
+ self.all_envelopes.contains(&env_hash)
+ }
+
+ #[inline(always)]
+ pub fn update_selection_with_thread(
+ &mut self,
+ thread: ThreadHash,
+ mut cl: impl FnMut(&mut bool),
+ ) {
+ let env_hashes = self.thread_to_env.entry(thread).or_default().clone();
+ for env_hash in env_hashes {
+ self.selection.entry(env_hash).and_modify(&mut cl);
+ self.row_updates.push(env_hash);
+ }
+ }
+
+ #[inline(always)]
+ pub fn update_selection_with_env(
+ &mut self,
+ env_hash: EnvelopeHash,
+ mut cl: impl FnMut(&mut bool),
+ ) {
+ self.selection.entry(env_hash).and_modify(&mut cl);
+ self.row_updates.push(env_hash);
+ }
+
+ #[inline(always)]
+ pub fn len(&self) -> usize {
+ self.entries.len()
+ }
+
+ #[inline(always)]
+ pub fn clear_selection(&mut self) {
+ for (k, v) in self.selection.iter_mut() {
+ if *v {
+ *v = false;
+ self.row_updates.push(*k);
+ }
+ }
+ }
+
+ pub fn rename_env(&mut self, old_hash: EnvelopeHash, new_hash: EnvelopeHash) {
+ self.row_updates.push(new_hash);
+ if let Some(row) = self.env_order.remove(&old_hash) {
+ self.env_order.insert(new_hash, row);
+ }
+ if let Some(thread) = self.env_to_thread.remove(&old_hash) {
+ self.thread_to_env
+ .entry(thread)
+ .or_default()
+ .retain(|h| *h != old_hash);
+ self.thread_to_env.entry(thread).or_default().push(new_hash);
+ }
+ let selection_status = self.selection.remove(&old_hash).unwrap_or(false);
+ self.selection.insert(new_hash, selection_status);
+ self.all_envelopes.remove(&old_hash);
+ self.all_envelopes.insert(old_hash);
+ }
+}
+
mod conversations;
pub use self::conversations::*;
@@ -116,12 +266,12 @@ struct ColorCache {
}
#[derive(Debug)]
-pub(super) struct EntryStrings {
- pub(super) date: DateString,
- pub(super) subject: SubjectString,
- pub(super) flag: FlagString,
- pub(super) from: FromString,
- pub(super) tags: TagString,
+pub struct EntryStrings {
+ pub date: DateString,
+ pub subject: SubjectString,
+ pub flag: FlagString,
+ pub from: FromString,
+ pub tags: TagString,
}
#[macro_export]
@@ -146,7 +296,7 @@ macro_rules! column_str {
(
struct $name:ident($($t:ty),+)) => {
#[derive(Debug)]
- pub(super) struct $name($(pub $t),+);
+ pub struct $name($(pub $t),+);
impl Deref for $name {
type Target = String;
@@ -190,14 +340,13 @@ pub trait MailListingTrait: ListingTrait {
fn perform_action(
&mut self,
context: &mut Context,
- thread_hashes: SmallVec<[ThreadHash; 8]>,
+ envs_to_set: SmallVec<[EnvelopeHash; 8]>,
a: &ListingAction,
) {
let account_hash = self.coordinates().0;
let account = &mut context.accounts[&account_hash];
- let mut envs_to_set: SmallVec<[EnvelopeHash; 8]> = SmallVec::new();
let mailbox_hash = self.coordinates().1;
- {
+ /*{
let threads_lck = account.collection.get_threads(mailbox_hash);
for thread_hash in thread_hashes {
for (_, h) in threads_lck.thread_group_iter(thread_hash) {
@@ -206,10 +355,12 @@ pub trait MailListingTrait: ListingTrait {
self.row_updates().push(thread_hash);
}
}
- if envs_to_set.is_empty() {
+ */
+ let env_hashes = if let Ok(batch) = EnvelopeHashBatch::try_from(envs_to_set.as_slice()) {
+ batch
+ } else {
return;
- }
- let env_hashes = EnvelopeHashBatch::try_from(envs_to_set.as_slice()).unwrap();
+ };
match a {
ListingAction::SetSeen => {
let job = account.backend.write().unwrap().set_flags(
@@ -484,9 +635,9 @@ pub trait MailListingTrait: ListingTrait {
self.set_dirty(true);
}
- fn row_updates(&mut self) -> &mut SmallVec<[ThreadHash; 8]>;
- fn selection(&mut self) -> &mut HashMap<ThreadHash, bool>;
- fn get_focused_items(&self, _context: &Context) -> SmallVec<[ThreadHash; 8]>;
+ fn row_updates(&mut self) -> &mut SmallVec<[EnvelopeHash; 8]>;
+ fn selection(&mut self) -> &mut HashMap<EnvelopeHash, bool>;
+ fn get_focused_items(&self, _context: &Context) -> SmallVec<[EnvelopeHash; 8]>;
fn redraw_threads_list(
&mut self,
context: &Context,
@@ -517,11 +668,9 @@ pub trait ListingTrait: Component {
) {
}
fn unfocused(&self) -> bool;
- fn set_modifier_active(&mut self, _new_val: bool) {}
- fn set_modifier_command(&mut self, _new_val: Option<Modifier>) {}
- fn modifier_command(&self) -> Option<Modifier> {
- None
- }
+ fn set_modifier_active(&mut self, _new_val: bool);
+ fn set_modifier_command(&mut self, _new_val: Option<Modifier>);
+ fn modifier_command(&self) -> Option<Modifier>;
fn set_movement(&mut self, mvm: PageMovement);
fn focus(&self) -> Focus;
fn set_focus(&mut self, new_value: Focus, context: &mut Context);
@@ -1161,13 +1310,14 @@ impl Component for Listing {
| Action::Listing(a @ ListingAction::Tag(_)) => {
let focused = self.component.get_focused_items(context);
self.component.perform_action(context, focused, a);
- let mut row_updates: SmallVec<[ThreadHash; 8]> = SmallVec::new();
+ let mut row_updates: SmallVec<[EnvelopeHash; 8]> = SmallVec::new();
for (k, v) in self.component.selection().iter_mut() {
if *v {
*v = false;
row_updates.push(*k);
}
}
+ self.component.row_updates().extend(row_updates.into_iter());
}
_ => {}
},
diff --git a/src/components/mail/listing/compact.rs b/src/components/mail/listing/compact.rs
index 2ae5ce0d..131ec097 100644
--- a/src/components/mail/listing/compact.rs
+++ b/src/components/mail/listing/compact.rs
@@ -167,13 +167,10 @@ pub struct CompactListing {
sort: (SortField, SortOrder),
sortcmd: bool,
subsort: (SortField, SortOrder),
- all_threads: HashSet<ThreadHash>,
- order: HashMap<ThreadHash, usize>,
/// Cache current view.
data_columns: DataColumns,
rows_drawn: SegmentTree,
- #[allow(clippy::type_complexity)]
- rows: Vec<((usize, (ThreadHash, EnvelopeHash)), EntryStrings)>,
+ rows: RowsState<(ThreadHash, EnvelopeHash)>,
#[allow(clippy::type_complexity)]
search_job: Option<(String, JoinHandle<Result<SmallVec<[EnvelopeHash; 512]>>>)>,
@@ -182,14 +179,12 @@ pub struct CompactListing {
filter_term: String,
filtered_selection: Vec<ThreadHash>,
filtered_order: HashMap<ThreadHash, usize>,
- selection: HashMap<ThreadHash, bool>,
/// If we must redraw on next redraw event
dirty: bool,
force_draw: bool,
/// If `self.view` exists or not.
focus: Focus,
view: Box<ThreadView>,
- row_updates: SmallVec<[ThreadHash; 8]>,
color_cache: ColorCache,
movement: Option<PageMovement>,
@@ -199,30 +194,46 @@ pub struct CompactListing {
}
impl MailListingTrait for CompactListing {
- fn row_updates(&mut self) -> &mut SmallVec<[ThreadHash; 8]> {
- &mut self.row_updates
+ fn row_updates(&mut self) -> &mut SmallVec<[EnvelopeHash; 8]> {
+ &mut self.rows.row_updates
}
- fn selection(&mut self) -> &mut HashMap<ThreadHash, bool> {
- &mut self.selection
+ fn selection(&mut self) -> &mut HashMap<EnvelopeHash, bool> {
+ &mut self.rows.selection
}
- fn get_focused_items(&self, _context: &Context) -> SmallVec<[ThreadHash; 8]> {
- let is_selection_empty = self.selection.values().cloned().any(std::convert::identity);
- let i = [self.get_thread_under_cursor(self.cursor_pos.2)];
+ fn get_focused_items(&self, _context: &Context) -> SmallVec<[EnvelopeHash; 8]> {
+ let is_selection_empty = !self
+ .rows
+ .selection
+ .values()
+ .cloned()
+ .any(std::convert::identity);
let cursor_iter;
- let sel_iter = if is_selection_empty {
+ let sel_iter = if !is_selection_empty {
cursor_iter = None;
- Some(self.selection.iter().filter(|(_, v)| **v).map(|(k, _)| k))
+ Some(
+ self.rows
+ .selection
+ .iter()
+ .filter(|(_, v)| **v)
+ .map(|(k, _)| *k),
+ )
} else {
- cursor_iter = Some(i.iter());
+ if let Some(env_hashes) = self
+ .get_thread_under_cursor(self.cursor_pos.2)
+ .and_then(|thread| self.rows.thread_to_env.get(&thread).map(|v| v.clone()))
+ {
+ cursor_iter = Some(env_hashes.into_iter());
+ } else {
+ cursor_iter = None;
+ }
None
};
let iter = sel_iter
.into_iter()
.flatten()
- .chain(cursor_iter.into_iter().flatten())
- .cloned();
+ .chain(cursor_iter.into_iter().flatten());
SmallVec::from_iter(iter)
}
@@ -230,8 +241,7 @@ impl MailListingTrait for CompactListing {
/// chosen.
fn refresh_mailbox(&mut self, context: &mut Context, force: bool) {
self.dirty = true;
- self.all_threads.clear();
- self.selection.clear();
+ self.rows.clear();
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)
@@ -305,9 +315,9 @@ impl MailListingTrait for CompactListing {
if !force && old_cursor_pos == self.new_cursor_pos {
self.view.update(context);
} else if self.unfocused() {
- let thread = self.get_thread_under_cursor(self.cursor_pos.2);
-
- self.view = Box::new(ThreadView::new(self.new_cursor_pos, thread, None, context));
+ if let Some(thread) = self.get_thread_under_cursor(self.cursor_pos.2) {
+ self.view = Box::new(ThreadView::new(self.new_cursor_pos, thread, None, context));
+ }
}
}
@@ -319,13 +329,12 @@ impl MailListingTrait for CompactListing {
let account = &context.accounts[&self.cursor_pos.0];
let threads = account.collection.get_threads(self.cursor_pos.1);
- self.order.clear();
+ self.rows.clear();
// Use account settings only if no sortcmd has been used
if !self.sortcmd {
self.sort = context.accounts[&self.cursor_pos.0].settings.account.order
}
self.length = 0;
- let mut rows = Vec::with_capacity(1024);
let mut min_width = (0, 0, 0, 0);
#[allow(clippy::type_complexity)]
let mut row_widths: (
@@ -426,11 +435,17 @@ impl MailListingTrait for CompactListing {
+ 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.rows.insert_thread(
+ thread,
+ (thread, root_env_hash),
+ threads
+ .thread_to_envelope
+ .get(&thread)
+ .cloned()
+ .unwrap_or_default()
+ .into(),
+ entry_strings,
+ );
self.length += 1;
}
@@ -438,23 +453,22 @@ impl MailListingTrait for CompactListing {
/* index column */
self.data_columns.columns[0] =
- CellBuffer::new_with_context(min_width.0, rows.len(), None, context);
+ CellBuffer::new_with_context(min_width.0, self.rows.len(), None, context);
self.data_columns.segment_tree[0] = row_widths.0.into();
/* date column */
self.data_columns.columns[1] =
- CellBuffer::new_with_context(min_width.1, rows.len(), None, context);
+ CellBuffer::new_with_context(min_width.1, self.rows.len(), None, context);
self.data_columns.segment_tree[1] = row_widths.1.into();
/* from column */
self.data_columns.columns[2] =
- CellBuffer::new_with_context(min_width.2, rows.len(), None, context);
+ CellBuffer::new_with_context(min_width.2, self.rows.len(), None, context);
self.data_columns.segment_tree[2] = row_widths.2.into();
/* subject column */
self.data_columns.columns[3] =
- CellBuffer::new_with_context(min_width.3, rows.len(), None, context);
+ CellBuffer::new_with_context(min_width.3, self.rows.len(), None, context);
self.data_columns.segment_tree[3] = row_widths.3.into();
- self.rows = rows;
self.rows_drawn = SegmentTree::from(
std::iter::repeat(1)
.take(self.rows.len())
@@ -495,14 +509,15 @@ impl ListingTrait for CompactListing {
self.filtered_selection.clear();
self.filtered_order.clear();
self.filter_term.clear();
- self.row_updates.clear();
+ self.rows.row_updates.clear();
}
fn highlight_line(&mut self, grid: &mut CellBuffer, area: Area, idx: usize, context: &Context) {
- if self.length == 0 {
+ let thread_hash = if let Some(h) = self.get_thread_under_cursor(idx) {
+ h
+ } else {
return;
- }
- let thread_hash = self.get_thread_under_cursor(idx);
+ };
let account = &context.accounts[&self.cursor_pos.0];
let threads = account.collection.get_threads(self.cursor_pos.1);
@@ -513,7 +528,7 @@ impl ListingTrait for CompactListing {
idx % 2 == 0,
thread.unseen() > 0,
self.cursor_pos.2 == idx,
- self.selection[&thread_hash]
+ self.rows.is_thread_selected(thread_hash)
);
let (upper_left, bottom_right) = area;
let x = get_x(upper_left)
@@ -723,25 +738,26 @@ impl ListingTrait for CompactListing {
let account = &context.accounts[&self.cursor_pos.0];
let threads = account.collection.get_threads(self.cursor_pos.1);
for r in 0..cmp::min(self.length - top_idx, rows) {
- let thread_hash = self.get_thread_under_cursor(r + top_idx);
- let row_attr = row_attr!(
- self.color_cache,
- (r + top_idx) % 2 == 0,
- threads.thread_ref(thread_hash).unseen() > 0,
- self.cursor_pos.2 == (r + top_idx),
- self.selection[&thread_hash]
- );
- change_colors(
- grid,
- (
- pos_inc(upper_left, (0, r)),
- (flag_x.saturating_sub(1), get_y(upper_left) + r),
- ),
- row_attr.fg,
- row_attr.bg,
- );
- for x in flag_x..get_x(bottom_right) {
- grid[(x, get_y(upper_left) + r)].set_bg(row_attr.bg);
+ if let Some(thread_hash) = self.get_thread_under_cursor(r + top_idx) {
+ let row_attr = row_attr!(
+ self.color_cache,
+ (r + top_idx) % 2 == 0,
+ threads.thread_ref(thread_hash).unseen() > 0,
+ self.cursor_pos.2 == (r + top_idx),
+ self.rows.is_thread_selected(thread_hash)
+ );
+ change_colors(
+ grid,
+ (
+ pos_inc(upper_left, (0, r)),
+ (flag_x.saturating_sub(1), get_y(upper_left) + r),
+ ),
+ row_attr.fg,
+ row_attr.bg,
+ );
+ for x in flag_x..get_x(bottom_right) {
+ grid[(x, get_y(upper_left) + r)].set_bg(row_attr.bg);
+ }
}
}
@@ -771,13 +787,10 @@ impl ListingTrait for CompactListing {
results: SmallVec<[EnvelopeHash; 512]>,
context: &Context,
) {
- self.order.clear();
- self.selection.clear();
self.length = 0;
self.filtered_selection.clear();
self.filtered_order.clear();
self.filter_term = filter_term;
- self.row_updates.clear();
let account = &context.accounts[&self.cursor_pos.0];
let threads = account.collection.get_threads(self.cursor_pos.1);
@@ -793,7 +806,7 @@ impl ListingTrait for CompactListing {
if self.filtered_order.contains_key(&thread) {
continue;
}
- if self.all_threads.contains(&thread) {
+ if self.rows.all_threads.contains(&thread) {
self.filtered_selection.push(thread);
self.filtered_order
.insert(thread, self.filtered_selection.len() - 1);
@@ -844,7 +857,7 @@ impl ListingTrait for CompactListing {
self.view
.process_event(&mut UIEvent::VisibilityChange(false), context);
self.dirty = true;
- /* If self.row_updates is not empty and we exit a thread, the row_update events
+ /* If self.rows.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;
@@ -882,19 +895,15 @@ impl CompactListing {
sort: (Default::default(), Default::default()),
sortcmd: false,
subsort: (SortField::Date, SortOrder::Desc),
- all_threads: HashSet::default(),
- order: HashMap::default(),
search_job: None,
select_job: None,
filter_term: String::new(),
filtered_selection: Vec::new(),
filtered_order: HashMap::default(),
- selection: HashMap::default(),
focus: Focus::None,
- row_updates: SmallVec::new(),
data_columns: DataColumns::default(),
rows_drawn: SegmentTree::default(),
- rows: vec![],
+ rows: RowsState::default(),
dirty: true,
force_draw: true,
view: Box::new(ThreadView::default()),
@@ -962,7 +971,7 @@ impl CompactListing {
},
flag: FlagString(format!(
"{selected}{snoozed}{unseen}{attachments}{whitespace}",
- selected = if self.selection.get(&hash).cloned().unwrap_or(false) {
+ selected = if self.rows.selection.get(&e.hash()).cloned().unwrap_or(false) {
mailbox_settings!(
context[self.cursor_pos.0][&self.cursor_pos.1]
.listing
@@ -1010,7 +1019,7 @@ impl CompactListing {
} else {
""
},
- whitespace = if self.selection.get(&hash).cloned().unwrap_or(false)
+ whitespace = if self.rows.selection.get(&e.hash()).cloned().unwrap_or(false)
|| thread.unseen() > 0
|| thread.snoozed()
|| thread.has_attachments()
@@ -1025,27 +1034,21 @@ impl CompactListing {
}
}
- fn get_thread_under_cursor(&self, cursor: usize) -> ThreadHash {
+ fn get_thread_under_cursor(&self, cursor: usize) -> Option<ThreadHash> {
if self.filter_term.is_empty() {
- *self
- .order
+ self.rows
+ .thread_order
.iter()
.find(|(_, &r)| r == cursor)
- .unwrap_or_else(|| {
- debug!("self.order empty ? cursor={} {:#?}", cursor, &self.order);
- panic!();
- })
- .0
+ .map(|(h, _)| h)
+ .cloned()
} else {
- self.filtered_selection[cursor]
+ self.filtered_selection.get(cursor).cloned()
}
}
- fn update_line(&mut self, context: &Context, thread_hash: ThreadHash) {
+ fn update_line(&mut self, context: &Context, env_hash: EnvelopeHash) {
let account = &context.accounts[&self.cursor_pos.0];
- let threads = account.collection.get_threads(self.cursor_pos.1);
- let thread = threads.thread_ref(thread_hash);
- let thread_node_hash = threads.thread_group_iter(thread_hash).next().unwrap().1;
let selected_flag_len = mailbox_settings!(
context[self.cursor_pos.0][&self.cursor_pos.1]
@@ -1084,143 +1087,144 @@ impl CompactListing {
.unwrap_or(super::DEFAULT_ATTACHMENT_FLAG)
.grapheme_width();
- if let Some(env_hash) = threads.thread_nodes()[&thread_node_hash].message() {
- if !account.contains_key(env_hash) {
- /* The envelope has been renamed or removed, so wait for the appropriate event to
- * arrive */
- return;
- }
- let idx = self.order[&thread_hash];
- let row_attr = row_attr!(
- self.color_cache,
- idx % 2 == 0,
- thread.unseen() > 0,
- false,
- false,
- );
- let envelope: EnvelopeRef = account.collection.get_env(env_hash);
- let strings = self.make_entry_string(&envelope, context, &threads, thread_hash);
- drop(envelope);
- let columns = &mut self.data_columns.columns;
- let min_width = (
- columns[0].size().0,
- columns[1].size().0,
- columns[2].size().0,
- columns[3].size().0,
- );
- let (x, _) = write_string_to_grid(
- &idx.to_string(),
- &mut columns[0],
- row_attr.fg,
- row_attr.bg,
- row_attr.attrs,
- ((0, idx), (min_width.0, idx)),
- None,
- );
- for c in columns[0].row_iter(x..min_width.0, idx) {
- columns[0][c].set_bg(row_attr.bg).set_ch(' ');
- }
- let (x, _) = write_string_to_grid(
- &strings.date,
- &mut columns[1],
- row_attr.fg,
- row_attr.bg,
- row_attr.attrs,
- ((0, idx), (min_width.1.saturating_sub(1), idx)),
- None,
- );
- for c in columns[1].row_iter(x..min_width.1, idx) {
- columns[1][c].set_bg(row_attr.bg).set_ch(' ');
- }
- let (x, _) = write_string_to_grid(
- &strings.from,
- &mut columns[2],
- row_attr.fg,
- row_attr.bg,
- row_attr.attrs,
- ((0, idx), (min_width.2, idx)),
- None,
- );
- for c in columns[2].row_iter(x..min_width.2, idx) {
- columns[2][c].set_bg(row_attr.bg).set_ch(' ');
- }
- let (x, _) = write_string_to_grid(
- &strings.flag,
- &mut columns[3],
- row_attr.fg,
- row_attr.bg,
- row_attr.attrs,
- ((0, idx), (min_width.3, idx)),
- None,
- );
- let (x, _) = write_string_to_grid(
- &strings.subject,
- &mut columns[3],
- row_attr.fg,
- row_attr.bg,
- row_attr.attrs,
- ((x, idx), (min_width.3, idx)),
- None,
- );
- columns[3][(x, idx)].set_bg(row_attr.bg).set_ch(' ');
- 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 columns[3],
- self.color_cache.tag_default.fg,
- color,
- self.color_cache.tag_default.attrs,
- ((x + 1, idx), (min_width.3, idx)),
- None,
- );
- for c in columns[3].row_iter(x..(x + 1), idx) {
- columns[3][c].set_bg(color);
- }
- for c in columns[3].row_iter(_x..(_x + 1), idx) {
- columns[3][c].set_bg(color).set_keep_bg(true);
- }
- for c in columns[3].row_iter((x + 1)..(_x + 1), idx) {
- columns[3][c]
- .set_keep_fg(true)
- .set_keep_bg(true)
- .set_keep_attrs(true);
- }
- for c in columns[3].row_iter(x..(x + 1), idx) {
- columns[3][c].set_keep_bg(true);
- }
- x = _x + 1;
- columns[3][(x, idx)].set_bg(row_attr.bg).set_ch(' ');
+ if !account.contains_key(env_hash) {
+ /* The envelope has been renamed or removed, so wait for the appropriate event to
+ * arrive */
+ return;
+ }
+ let envelope: EnvelopeRef = account.collection.get_env(env_hash);
+ let thread_hash = self.rows.env_to_thread[&env_hash];
+ let threads = account.collection.get_threads(self.cursor_pos.1);
+ let thread = threads.thread_ref(thread_hash);
+ let idx = self.rows.thread_order[&thread_hash];
+ let row_attr = row_attr!(
+ self.color_cache,
+ idx % 2 == 0,
+ thread.unseen() > 0,
+ false,
+ false,
+ );
+ let strings = self.make_entry_string(&envelope, context, &threads, thread_hash);
+ drop(envelope);
+ let columns = &mut self.data_columns.columns;
+ let min_width = (
+ columns[0].size().0,
+ columns[1].size().0,
+ columns[2].size().0,
+ columns[3].size().0,
+ );
+ let (x, _) = write_string_to_grid(
+ &idx.to_string(),
+ &mut columns[0],
+ row_attr.fg,
+ row_attr.bg,
+ row_attr.attrs,
+ ((0, idx), (min_width.0, idx)),
+ None,
+ );
+ for c in columns[0].row_iter(x..min_width.0, idx) {
+ columns[0][c].set_bg(row_attr.bg).set_ch(' ');
+ }
+ let (x, _) = write_string_to_grid(
+ &strings.date,
+ &mut columns[1],
+ row_attr.fg,
+ row_attr.bg,
+ row_attr.attrs,
+ ((0, idx), (min_width.1.saturating_sub(1), idx)),
+ None,
+ );
+ for c in columns[1].row_iter(x..min_width.1, idx) {
+ columns[1][c].set_bg(row_attr.bg).set_ch(' ');
+ }
+ let (x, _) = write_string_to_grid(
+ &strings.from,
+ &mut columns[2],
+ row_attr.fg,
+ row_attr.bg,
+ row_attr.attrs,
+ ((0, idx), (min_width.2, idx)),
+ None,
+ );
+ for c in columns[2].row_iter(x..min_width.2, idx) {
+ columns[2][c].set_bg(row_attr.bg).set_ch(' ');
+ }
+ let (x, _) = write_string_to_grid(
+ &strings.flag,
+ &mut columns[3],
+ row_attr.fg,
+ row_attr.bg,
+ row_attr.attrs,
+ ((0, idx), (min_width.3, idx)),
+ None,
+ );
+ let (x, _) = write_string_to_grid(
+ &strings.subject,
+ &mut columns[3],
+ row_attr.fg,
+ row_attr.bg,
+ row_attr.attrs,
+ ((x, idx), (min_width.3, idx)),
+ None,
+ );
+ columns[3][(x, idx)].set_bg(row_attr.bg).set_ch(' ');
+ 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 columns[3],
+ self.color_cache.tag_default.fg,
+ color,
+ self.color_cache.tag_default.attrs,
+ ((x + 1, idx), (min_width.3, idx)),
+ None,
+ );
+ for c in columns[3].row_iter(x..(x + 1), idx) {
+ columns[3][c].set_bg(color);
}
- x
- };
- for c in columns[3].row_iter(x..min_width.3, idx) {
- columns[3][c].set_ch(' ').set_bg(row_attr.bg);
- }
- /* Set fg color for flags */
- let mut x = 0;
- if self.selection.get(&thread_hash).cloned().unwrap_or(false) {
- x += selected_flag_len;
- }
- if thread.snoozed() {
- for x in x..(x + thread_snoozed_flag_len) {
- columns[3][(x, idx)].set_fg(self.color_cache.thread_snooze_flag.fg);
+ for c in columns[3].row_iter(_x..(_x + 1), idx) {
+ columns[3][c].set_bg(color).set_keep_bg(true);
}
- x += thread_snoozed_flag_len;
+ for c in columns[3].row_iter((x + 1)..(_x + 1), idx) {
+ columns[3][c]
+ .set_keep_fg(true)
+ .set_keep_bg(true)
+ .set_keep_attrs(true);
+ }
+ for c in columns[3].row_iter(x..(x + 1), idx) {
+ columns[3][c].set_keep_bg(true);
+ }
+ x = _x + 1;
+ columns[3][(x, idx)].set_bg(row_attr.bg).set_ch(' ');
}
- if thread.unseen() > 0 {
- x += unseen_flag_len;
+ x
+ };
+ for c in columns[3].row_iter(x..min_width.3, idx) {
+ columns[3][c].set_ch(' ').set_bg(row_attr.bg);
+ }
+ /* Set fg color for flags */
+ let mut x = 0;
+ if self.rows.selection.get(&env_hash).cloned().unwrap_or(false) {
+ x += selected_flag_len;
+ }
+ if thread.snoozed() {
+ for x in x..(x + thread_snoozed_flag_len) {
+ columns[3][(x, idx)].set_fg(self.color_cache.thread_snooze_flag.fg);
}
- if thread.has_attachments() {
- for x in x..(x + attachment_flag_len) {
- columns[3][(x, idx)].set_fg(self.color_cache.attachment_flag.fg);
- }
+ x += thread_snoozed_flag_len;
+ }
+ if thread.unseen() > 0 {
+ x += unseen_flag_len;
+ }
+ if thread.has_attachments() {
+ for x in x..(x + attachment_flag_len) {
+ columns[3][(x, idx)].set_fg(self.color_cache.attachment_flag.fg);
}
- *self.rows.get_mut(idx).unwrap() = ((idx, (thread_hash, env_hash)), strings);
- self.rows_drawn.update(idx, 1);
}
+ *self.rows.entries.get_mut(idx).unwrap() = ((thread_hash, env_hash), strings);
+ self.rows_drawn.update(idx, 1);
}
fn draw_rows(&mut self, context: &Context, start: usize, end: usize) {
@@ -1283,10 +1287,14 @@ impl CompactListing {
.unwrap_or(super::DEFAULT_ATTACHMENT_FLAG)
.grapheme_width();
- for ((idx, (thread_hash, root_env_hash)), strings) in
- self.rows.iter().skip(start).take(end - start + 1)
+ for (idx, ((thread_hash, root_env_hash), strings)) in self
+ .rows
+ .entries
+ .iter()
+ .enumerate()
+ .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!(
@@ -1304,7 +1312,7 @@ impl CompactListing {
idx % 2 == 0,
thread.unseen() > 0,
self.cursor_pos.2 == idx,
- self.selection[thread_hash]
+ self.rows.selection[root_env_hash]
);
let (x, _) = write_string_to_grid(
&idx.to_string(),
@@ -1422,7 +1430,13 @@ impl CompactListing {
}
/* Set fg color for flags */
let mut x = 0;
- if self.selection.get(thread_hash).cloned().unwrap_or(false) {
+ if self
+ .rows
+ .selection
+ .get(root_env_hash)
+ .cloned()
+ .unwrap_or(false)
+ {
x += selected_flag_len;
}
if thread.snoozed() {
@@ -1465,9 +1479,10 @@ impl CompactListing {
}
let thread =
threads.find_group(threads.thread_nodes[&env_thread_node_hash].group);
- if self.all_threads.contains(&thread) {
- self.selection
- .entry(thread)
+ if self.rows.all_threads.contains(&thread) {
+ self.rows
+ .selection
+ .entry(env_hash)
.and_modify(|entry| *entry = true);
}
}
@@ -1545,28 +1560,28 @@ impl Component for CompactListing {
for c in self.new_cursor_pos.2.saturating_sub(*amount)
..=self.new_cursor_pos.2
{
- let thread = self.get_thread_under_cursor(c);
- match modifier {
- Modifier::SymmetricDifference => {
- self.selection.entry(thread).and_modify(|e| *e = !*e);
- }
- Modifier::Union => {
- self.selection.entry(thread).and_modify(|e| *e = true);
- }
- Modifier::Difference => {
- self.selection.entry(thread).and_modify(|e| *e = false);
- }
- Modifier::Intersection => {}
+ if let Some(thread) = self.get_thread_under_cursor(c) {
+ self.rows.update_selection_with_thread(
+ thread,
+ match modifier {
+ Modifier::SymmetricDifference => {
+ |e: &mut bool| *e = !*e
+ }
+ Modifier::Union => |e: &mut bool| *e = true,
+ Modifier::Difference => |e: &mut bool| *e = false,
+ Modifier::Intersection => |_: &mut bool| {},
+ },
+ );
}
- self.row_updates.push(thread);
}
if modifier == Modifier::Intersection {
for c in (0..self.new_cursor_pos.2.saturating_sub(*amount))
.chain((self.new_cursor_pos.2 + 2)..self.length)
{
- let thread = self.get_thread_under_cursor(c);
- self.selection.entry(thread).and_modify(|e| *e = false);
- self.row_updates.push(thread);
+ if let Some(thread) = self.get_thread_under_cursor(c) {
+ self.rows
+ .update_selection_with_thread(thread, |e| *e = false);
+ }
}
}
}
@@ -1574,49 +1589,48 @@ impl Component for CompactListing {
for c in self.new_cursor_pos.2.saturating_sub(rows * multiplier)
..=self.new_cursor_pos.2
{
- let thread = self.get_thread_under_cursor(c);
- match modifier {
- Modifier::SymmetricDifference => {
- self.selection.entry(thread).and_modify(|e| *e = !*e);
- }
- Modifier::Union => {
- self.selection.entry(thread).and_modify(|e| *e = true);
- }
- Modifier::Difference => {
- self.selection.entry(thread).and_modify(|e| *e = false);
- }
- Modifier::Intersection => {}
+ if let Some(thread) = self.get_thread_under_cursor(c) {
+ self.rows.update_selection_with_thread(
+ thread,
+ match modifier {
+ Modifier::SymmetricDifference => {
+ |e: &mut bool| *e = !*e
+ }
+ Modifier::Union => |e: &mut bool| *e = true,
+ Modifier::Difference => |e: &mut bool| *e = false,
+ Modifier::Intersection => |_: &mut bool| {},
+ },
+ );
}
- self.row_updates.push(thread);
}
}
PageMovement::Down(amount) => {
for c in self.new_cursor_pos.2
..std::cmp::min(self.length, self.new_cursor_pos.2 + amount + 1)
{
- let thread = self.get_thread_under_cursor(c);
- match modifier {
- Modifier::SymmetricDifference => {
- self.selection.entry(thread).and_modify(|e| *e = !*e);
- }
- Modifier::Union => {
- self.selection.entry(thread).and_modify(|e| *e = true);
- }
- Modifier::Difference => {
- self.selection.entry(thread).and_modify(|e| *e = false);
- }
- Modifier::Intersection => {}
+ if let Some(thread) = self.get_thread_under_cursor(c) {
+ self.rows.update_selection_with_thread(
+ thread,
+ match modifier {
+ Modifier::SymmetricDifference => {
+ |e: &mut bool| *e = !*e
+ }
+ Modifier::Union => |e: &mut bool| *e = true,
+ Modifier::Difference => |e: &mut bool| *e = false,
+ Modifier::Intersection => |_: &mut bool| {},
+ },
+ );
}
- self.row_updates.push(thread);
}
if modifier == Modifier::Intersection {
for c in (0..self.new_cursor_pos.2).chain(
(std::cmp::min(self.length, self.new_cursor_pos.2 + amount + 1)
+ 1)..self.length,
) {
- let thread = self.get_thread_under_cursor(c);
- self.selection.entry(thread).and_modify(|e| *e = false);
- self.row_updates.push(thread);
+ if let Some(thread) = self.get_thread_under_cursor(c) {
+ self.rows
+ .update_selection_with_thread(thread, |e| *e = false);
+ }
}
}
}
@@ -1627,20 +1641,19 @@ impl Component for CompactListing {
self.length,
)
{
- let thread = self.get_thread_under_cursor(c);
- match modifier {
- Modifier::SymmetricDifference => {
- self.selection.entry(thread).and_modify(|e| *e = !*e);
- }
- Modifier::Union => {
- self.selection.entry(thread).and_modify(|e| *e = true);
- }
- Modifier::Difference => {
- self.selection.entry(thread).and_modify(|e| *e = false);
- }
- Modifier::Intersection => {}
+ if let Some(thread) = self.get_thread_under_cursor(c) {
+ self.rows.update_selection_with_thread(
+ thread,
+ match modifier {
+ Modifier::SymmetricDifference => {
+ |e: &mut bool| *e = !*e
+ }
+ Modifier::Union => |e: &mut bool| *e = true,
+ Modifier::Difference => |e: &mut bool| *e = false,
+ Modifier::Intersection => |_: &mut bool| {},
+ },
+ );
}
- self.row_updates.push(thread);
}
if modifier == Modifier::Intersection {
for c in (0..self.new_cursor_pos.2).chain(
@@ -1649,60 +1662,61 @@ impl Component for CompactListing {
self.length,
) + 1)..self.length,
) {
- let thread = self.get_thread_under_cursor(c);
- self.selection.entry(thread).and_modify(|e| *e = false);
- self.row_updates.push(thread);
+ if let Some(thread) = self.get_thread_under_cursor(c) {
+ self.rows
+ .update_selection_with_thread(thread, |e| *e = false);
+ }
}
}
}
PageMovement::Right(_) | PageMovement::Left(_) => {}
PageMovement::Home => {
for c in 0..=self.new_cursor_pos.2 {
- let thread = self.get_thread_under_cursor(c);
- match modifier {
- Modifier::SymmetricDifference => {
- self.selection.entry(thread).and_modify(|e| *e = !*e);
- }
- Modifier::Union => {
- self.selection.entry(thread).and_modify(|e| *e = true);
- }
- Modifier::Difference => {
- self.selection.entry(thread).and_modify(|e| *e = false);
- }
- Modifier::Intersection => {}
+ if let Some(thread) = self.get_thread_under_cursor(c) {
+ self.rows.update_selection_with_thread(
+ thread,
+ match modifier {
+ Modifier::SymmetricDifference => {
+ |e: &mut bool| *e = !*e
+ }
+ Modifier::Union => |e: &mut bool| *e = true,
+ Modifier::Difference => |e: &mut bool| *e = false,
+ Modifier::Intersection => |_: &mut bool| {},
+ },
+ );
}
- self.row_updates.push(thread);
}
if modifier == Modifier::Intersection {
for c in (self.new_cursor_pos.2 + 1)..self.length {
- let thread = self.get_thread_under_cursor(c);
- self.selection.entry(thread).and_modify(|e| *e = false);
- self.row_updates.push(thread);
+ if let Some(thread) = self.get_thread_under_cursor(c) {
+ self.rows
+ .update_selection_with_thread(thread, |e| *e = false);
+ }
}
}
}
PageMovement::End => {
for c in self.new_cursor_pos.2..self.length {
- let thread = self.get_thread_under_cursor(c);
- match modifier {
- Modifier::SymmetricDifference => {
- self.selection.entry(thread).and_modify(|e| *e = !*e);
- }
- Modifier::Union => {
- self.selection.entry(thread).and_modify(|e| *e = true);
- }
- Modifier::Difference => {
- self.selection.entry(thread).and_modify(|e| *e = false);
- }
- Modifier::Intersection => {}
+ if let Some(thread) = self.get_thread_under_cursor(c) {
+ self.rows.update_selection_with_thread(
+ thread,
+ match modifier {
+ Modifier::SymmetricDifference => {
+ |e: &mut bool| *e = !*e
+ }
+ Modifier::Union => |e: &mut bool| *e = true,
+ Modifier::Difference => |e: &mut bool| *e = false,
+ Modifier::Intersection => |_: &mut bool| {},
+ },
+ );
}
- self.row_updates.push(thread);
}
if modifier == Modifier::Intersection {
for c in 0..self.new_cursor_pos.2 {
- let thread = self.get_thread_under_cursor(c);
- self.selection.entry(thread).and_modify(|e| *e = false);
- self.row_updates.push(thread);
+ if let Some(thread) = self.get_thread_under_cursor(c) {
+ self.rows
+ .update_selection_with_thread(thread, |e| *e = false);
+ }
}
}
}
@@ -1711,10 +1725,10 @@ impl Component for CompactListing {
self.force_draw = true;
}
- if !self.row_updates.is_empty() {
- while let Some(row) = self.row_updates.pop() {
+ if !self.rows.row_updates.is_empty() {
+ while let Some(row) = self.rows.row_updates.pop() {
self.update_line(context, row);
- let row: usize = self.order[&row];
+ let row: usize = self.rows.env_order[&row];
let page_no = (self.new_cursor_pos.2).wrapping_div(rows);
let top_idx = page_no * rows;
@@ -1784,9 +1798,11 @@ impl Component for CompactListing {
&& (shortcut!(k == shortcuts[Listing::DESCRIPTION]["open_entry"])
|| shortcut!(k == shortcuts[Listing::DESCRIPTION]["focus_right"])) =>
{
- let thread = self.get_thread_under_cursor(self.cursor_pos.2);
- self.view = Box::new(ThreadView::new(self.cursor_pos, thread, None, context));
- self.set_focus(Focus::Entry, context);
+ if let Some(thread) = self.get_thread_under_cursor(self.cursor_pos.2) {
+ self.view =
+ Box::new(ThreadView::new(self.cursor_pos, thread, None, context));
+ self.set_focus(Focus::Entry, context);
+ }
return true;
}
UIEvent::Input(ref k)
@@ -1827,9 +1843,10 @@ impl Component for CompactListing {
if self.modifier_active && self.modifier_command.is_none() {
self.modifier_command = Some(Modifier::default());
} else {
- let thread_hash = self.get_thread_under_cursor(self.cursor_pos.2);
- self.selection.entry(thread_hash).and_modify(|e| *e = !*e);
- self.row_updates.push(thread_hash);
+ if let Some(thread_hash) = self.get_thread_under_cursor(self.cursor_pos.2) {
+ self.rows
+ .update_selection_with_thread(thread_hash, |e| *e = !*e);
+ }
}
return true;
}
@@ -1854,6 +1871,7 @@ impl Component for CompactListing {
return true;
}
Action::Listing(ToggleThreadSnooze) if !self.unfocused() => {
+ /*
let thread = self.get_thread_under_cursor(self.cursor_pos.2);
let account = &mut context.accounts[&self.cursor_pos.0];
account
@@ -1866,8 +1884,9 @@ impl Component for CompactListing {
let is_snoozed = threads.thread_ref(thread).snoozed();
threads.thread_ref_mut(thread).set_snoozed(!is_snoozed);
});
- self.row_updates.push(thread);
+ self.rows.row_updates.push(thread);
self.refresh_mailbox(context, false);
+ */
return true;
}
@@ -1938,8 +1957,8 @@ impl Component for CompactListing {
let thread: ThreadHash =
threads.find_group(threads.thread_nodes()[&new_env_thread_node_hash].group);
drop(threads);
- if self.order.contains_key(&thread) {
- self.row_updates.push(thread);
+ if self.rows.contains_thread(thread) {
+ self.rows.row_update_add_thread(thread);
}
self.dirty = true;
@@ -1950,7 +1969,7 @@ impl Component for CompactListing {
}
}
UIEvent::EnvelopeRemove(ref _env_hash, ref thread_hash) => {
- if self.order.contains_key(thread_hash) {
+ if self.rows.thread_order.contains_key(thread_hash) {
self.refresh_mailbox(context, false);
self.set_dirty(true);
}
@@ -1968,8 +1987,8 @@ impl Component for CompactListing {
let thread: ThreadHash =
threads.find_group(threads.thread_nodes()[&new_env_thread_node_hash].group);
drop(threads);
- if self.order.contains_key(&thread) {
- self.row_updates.push(thread);
+ if self.rows.contains_thread(thread) {
+ self.rows.row_update_add_thread(thread);
}
self.dirty = true;
@@ -1987,9 +2006,14 @@ impl Component for CompactListing {
}
UIEvent::Input(Key::Esc)
if !self.unfocused()
- && self.selection.values().cloned().any(std::convert::identity) =>
+ && self
+ .rows
+ .selection
+ .values()
+ .cloned()
+ .any(std::convert::identity) =>
{
- for v in self.selection.values_mut() {
+ for v in self.rows.selection.values_mut() {
*v = false;
}
self.dirty = true;
diff --git a/src/components/mail/listing/conversations.rs b/src/components/mail/listing/conversations.rs
index 3e3df96e..2b05a7e1 100644
--- a/src/components/mail/listing/conversations.rs
+++ b/src/components/mail/listing/conversations.rs
@@ -100,24 +100,20 @@ pub struct ConversationsListing {
length: usize,
sort: (SortField, SortOrder),
subsort: (SortField, SortOrder),
- all_threads: HashSet<ThreadHash>,
- order: HashMap<ThreadHash, usize>,
- #[allow(clippy::type_complexity)]
- rows: std::result::Result<Vec<((usize, (ThreadHash, EnvelopeHash)), EntryStrings)>, String>,
+ rows: RowsState<(ThreadHash, EnvelopeHash)>,
+ error: std::result::Result<(), String>,
#[allow(clippy::type_complexity)]
search_job: Option<(String, JoinHandle<Result<SmallVec<[EnvelopeHash; 512]>>>)>,
filter_term: String,
filtered_selection: Vec<ThreadHash>,
filtered_order: HashMap<ThreadHash, usize>,
- selection: HashMap<ThreadHash, bool>,
/// If we must redraw on next redraw event
dirty: bool,
force_draw: bool,
/// If `self.view` exists or not.
focus: Focus,
view: ThreadView,
- row_updates: SmallVec<[ThreadHash; 8]>,
color_cache: ColorCache,
movement: Option<PageMovement>,
@@ -127,30 +123,46 @@ pub struct ConversationsListing {
}
impl MailListingTrait for ConversationsListing {
- fn row_updates(&mut self) -> &mut SmallVec<[ThreadHash; 8]> {
- &mut self.row_updates
+ fn row_updates(&mut self) -> &mut SmallVec<[EnvelopeHash; 8]> {
+ &mut self.rows.row_updates
}
- fn selection(&mut self) -> &mut HashMap<ThreadHash, bool> {
- &mut self.selection
+ fn selection(&mut self) -> &mut HashMap<EnvelopeHash, bool> {
+ &mut self.rows.selection
}
- fn get_focused_items(&self, _context: &Context) -> SmallVec<[ThreadHash; 8]> {
- let is_selection_empty = self.selection.values().cloned().any(std::convert::identity);
- let i = [self.get_thread_under_cursor(self.cursor_pos.2)];
+ fn get_focused_items(&self, _context: &Context) -> SmallVec<[EnvelopeHash; 8]> {
+ let is_selection_empty = !self
+ .rows
+ .selection
+ .values()
+ .cloned()
+ .any(std::convert::identity);
let cursor_iter;
- let sel_iter = if is_selection_empty {
+ let sel_iter = if !is_selection_empty {
cursor_iter = None;
- Some(self.selection.iter().filter(|(_, v)| **v).map(|(k, _)| k))
+ Some(
+ self.rows
+ .selection
+ .iter()
+ .filter(|(_, v)| **v)
+ .map(|(k, _)| *k),
+ )
} else {
- cursor_iter = Some(i.iter());
+ if let Some(env_hashes) = self
+ .get_thread_under_cursor(self.cursor_pos.2)
+ .and_then(|thread| self.rows.thread_to_env.get(&thread).map(|v| v.clone()))
+ {
+ cursor_iter = Some(env_hashes.into_iter());
+ } else {
+ cursor_iter = None;
+ }
None
};
let iter = sel_iter
.into_iter()
.flatten()
- .chain(cursor_iter.into_iter().flatten())
- .cloned();
+ .chain(cursor_iter.into_iter().flatten());
SmallVec::from_iter(iter)
}
@@ -192,7 +204,7 @@ impl MailListingTrait for ConversationsListing {
Err(_) => {
let message: String =
context.accounts[&self.cursor_pos.0][&self.cursor_pos.1].status();
- self.rows = Err(message);
+ self.error = Err(message);
return;
}
}
@@ -200,7 +212,6 @@ impl MailListingTrait for ConversationsListing {
let threads = context.accounts[&self.cursor_pos.0]
.collection
.get_threads(self.cursor_pos.1);
- self.all_threads.clear();
let mut roots = threads.roots();
threads.group_inner_sort_by(
&mut roots,
@@ -217,9 +228,9 @@ impl MailListingTrait for ConversationsListing {
{
self.view.update(context);
} else if self.unfocused() {
- let thread_group = self.get_thread_under_cursor(self.cursor_pos.2);
-
- self.view = ThreadView::new(self.new_cursor_pos, thread_group, None, context);
+ if let Some(thread_group) = self.get_thread_under_cursor(self.cursor_pos.2) {
+ self.view = ThreadView::new(self.new_cursor_pos, thread_group, None, context);
+ }
}
}
@@ -231,13 +242,10 @@ impl MailListingTrait for ConversationsListing {
let account = &context.accounts[&self.cursor_pos.0];
let threads = account.collection.get_threads(self.cursor_pos.1);
- self.order.clear();
- self.selection.clear();
+ self.rows.clear();
self.length = 0;
- if self.rows.is_err() {
- self.rows = Ok(vec![]);
- } else {
- self.rows.as_mut().unwrap().clear();
+ if self.error.is_err() {
+ self.error = Ok(());
}
let mut max_entry_columns = 0;
@@ -343,20 +351,23 @@ impl MailListingTrait for ConversationsListing {
max_entry_columns,
strings.date.len() + 1 + strings.from.grapheme_width(),
);
- self.rows
- .as_mut()
- .unwrap()
- .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.rows.insert_thread(
+ thread,
+ (thread, root_env_hash),
+ threads
+ .thread_to_envelope
+ .get(&thread)
+ .cloned()
+ .unwrap_or_default()
+ .into(),
+ strings,
+ );
self.length += 1;
}
if self.length == 0 && self.filter_term.is_empty() {
let message: String = account[&self.cursor_pos.1].status();
- self.rows = Err(message);
+ self.error = Err(message);
}
}
}
@@ -373,7 +384,7 @@ impl ListingTrait for ConversationsListing {
self.filtered_selection.clear();
self.filtered_order.clear();
self.filter_term.clear();
- self.row_updates.clear();
+ self.rows.clear();
}
fn highlight_line(&mut self, grid: &mut CellBuffer, area: Area, idx: usize, context: &Context) {
@@ -391,7 +402,7 @@ impl ListingTrait for ConversationsListing {
}
let upper_left = upper_left!(area);
let bottom_right = bottom_right!(area);
- if let Err(message) = self.rows.as_ref() {
+ if let Err(message) = self.error.as_ref() {
clear_area(grid, area, self.color_cache.theme_default);
write_string_to_grid(
message,
@@ -503,16 +514,10 @@ impl ListingTrait for ConversationsListing {
return;
}
- self.order.clear();
- self.selection.clear();
self.length = 0;
self.filtered_selection.clear();
self.filtered_order.clear();
self.filter_term = filter_term;
- self.row_updates.clear();
- for v in self.selection.values_mut() {
- *v = false;
- }
let account = &context.accounts[&self.cursor_pos.0];
let threads = account.collection.get_threads(self.cursor_pos.1);
@@ -528,7 +533,7 @@ impl ListingTrait for ConversationsListing {
if self.filtered_order.contains_key(&thread) {
continue;
}
- if self.all_threads.contains(&thread) {
+ if self.rows.all_threads.contains(&thread) {
self.filtered_selection.push(thread);
self.filtered_order
.insert(thread, self.filtered_selection.len().saturating_sub(1));
@@ -579,7 +584,7 @@ impl ListingTrait for ConversationsListing {
self.view
.process_event(&mut UIEvent::VisibilityChange(false), context);
self.dirty = true;
- /* If self.row_updates is not empty and we exit a thread, the row_update events
+ /* If self.rows.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;
@@ -618,15 +623,12 @@ impl ConversationsListing {
length: 0,
sort: (Default::default(), Default::default()),
subsort: (SortField::Date, SortOrder::Desc),
- order: HashMap::default(),
- all_threads: HashSet::default(),
+ rows: RowsState::default(),
+ error: Ok(()),
search_job: None,
filter_term: String::new(),
filtered_selection: Vec::new(),
filtered_order: HashMap::default(),
- selection: HashMap::default(),
- row_updates: SmallVec::new(),
- rows: Ok(Vec::with_capacity(1024)),
dirty: true,
force_draw: true,
focus: Focus::None,
@@ -765,28 +767,23 @@ impl ConversationsListing {
}
}
- fn get_thread_under_cursor(&self, cursor: usize) -> ThreadHash {
+ fn get_thread_under_cursor(&self, cursor: usize) -> Option<ThreadHash> {
if self.filter_term.is_empty() {
- *self
- .order
+ self.rows
+ .thread_order
.iter()
.find(|(_, &r)| r == cursor)
- .unwrap_or_else(|| {
- debug!("self.order empty ? cursor={} {:#?}", cursor, &self.order);
- panic!();
- })
- .0
+ .map(|(k, _)| *k)
} else {
- self.filtered_selection[cursor]
+ self.filtered_selection.get(cursor).cloned()
}
}
- fn update_line(&mut self, context: &Context, thread_hash: ThreadHash) {
+ fn update_line(&mut self, context: &Context, env_hash: EnvelopeHash) {
let account = &context.accounts[&self.cursor_pos.0];
+ let thread_hash = self.rows.env_to_thread[&env_hash];
let threads = account.collection.get_threads(self.cursor_pos.1);
- let thread_node_hash = threads.thread_group_iter(thread_hash).next().unwrap().1;
- let idx: usize = self.order[&thread_hash];
- let env_hash = threads.thread_nodes()[&thread_node_hash].message().unwrap();
+ let idx: usize = self.rows.thread_order[&thread_hash];
let mut other_subjects = IndexSet::new();
let mut from_address_list = Vec::new();
@@ -829,22 +826,18 @@ impl ConversationsListing {
thread_hash,
);
drop(envelope);
- if let Ok(rows) = self.rows.as_mut() {
- if let Some(row) = rows.get_mut(idx) {
- row.1 = strings;
- }
+ if let Some(row) = self.rows.entries.get_mut(idx) {
+ row.1 = strings;
}
}
fn draw_rows(&self, grid: &mut CellBuffer, area: Area, context: &Context, top_idx: usize) {
- let rows_ref = match self.rows.as_ref() {
- Ok(rows) => rows,
- Err(_) => return,
- };
let account = &context.accounts[&self.cursor_pos.0];
let threads = account.collection.get_threads(self.cursor_pos.1);
let (mut upper_left, bottom_right) = area;
- for ((idx, (thread_hash, root_env_hash)), strings) in rows_ref.iter().skip(top_idx) {
+ for (idx, ((thread_hash, root_env_hash), strings)) in
+ self.rows.entries.iter().enumerate().skip(top_idx)
+ {
if !context.accounts[&self.cursor_pos.0].contains_key(*root_env_hash) {
panic!();
}
@@ -853,8 +846,8 @@ impl ConversationsListing {
let row_attr = row_attr!(
self.color_cache,
thread.unseen() > 0,
- self.cursor_pos.2 == *idx,
- self.selection[thread_hash]
+ self.cursor_pos.2 == idx,
+ self.rows.is_thread_selected(*thread_hash)
);
/* draw flags */
let (x, _) = write_string_to_grid(
@@ -873,8 +866,8 @@ impl ConversationsListing {
subject,
self.color_cache,
thread.unseen() > 0,
- self.cursor_pos.2 == *idx,
- self.selection[thread_hash]
+ self.cursor_pos.2 == idx,
+ self.rows.is_thread_selected(*thread_hash)
);
/* draw subject */
let (mut x, _) = write_string_to_grid(
@@ -919,8 +912,8 @@ impl ConversationsListing {
date,
self.color_cache,
thread.unseen() > 0,
- self.cursor_pos.2 == *idx,
- self.selection[thread_hash]
+ self.cursor_pos.2 == idx,
+ self.rows.is_thread_selected(*thread_hash)
);
upper_left.1 += 1;
if upper_left.1 >= bottom_right.1 {
@@ -946,8 +939,8 @@ impl ConversationsListing {
from,
self.color_cache,
thread.unseen() > 0,
- self.cursor_pos.2 == *idx,
- self.selection[thread_hash]
+ self.cursor_pos.2 == idx,
+ self.rows.is_thread_selected(*thread_hash)
);
/* draw from */
let (x, _) = write_string_to_grid(
@@ -1025,28 +1018,28 @@ impl Component for ConversationsListing {
for c in self.new_cursor_pos.2.saturating_sub(*amount)
..=self.new_cursor_pos.2
{
- let thread = self.get_thread_under_cursor(c);
- match modifier {
- Modifier::SymmetricDifference => {
- self.selection.entry(thread).and_modify(|e| *e = !*e);
- }
- Modifier::Union => {
- self.selection.entry(thread).and_modify(|e| *e = true);
- }
- Modifier::Difference => {
- self.selection.entry(thread).and_modify(|e| *e = false);
- }
- Modifier::Intersection => {}
+ if let Some(thread) = self.get_thread_under_cursor(c) {
+ self.rows.update_selection_with_thread(
+ thread,
+ match modifier {
+ Modifier::SymmetricDifference => {
+ |e: &mut bool| *e = !*e
+ }
+ Modifier::Union => |e: &mut bool| *e = true,
+ Modifier::Difference => |e: &mut bool| *e = false,
+ Modifier::Intersection => |_: &mut bool| {},
+ },
+ );
}
- self.row_updates.push(thread);
}
if modifier == Modifier::Intersection {
for c in (0..self.new_cursor_pos.2.saturating_sub(*amount))
.chain((self.new_cursor_pos.2 + 2)..self.length)
{
- let thread = self.get_thread_under_cursor(c);
- self.selection.entry(thread).and_modify(|e| *e = false);
- self.row_updates.push(thread);
+ if let Some(thread) = self.get_thread_under_cursor(c) {
+ self.rows
+ .update_selection_with_thread(thread, |e| *e = false);
+ }
}
}
}
@@ -1054,49 +1047,48 @@ impl Component for ConversationsListing {
for c in self.new_cursor_pos.2.saturating_sub(rows * multiplier)
..=self.new_cursor_pos.2
{
- let thread = self.get_thread_under_cursor(c);
- match modifier {
- Modifier::SymmetricDifference => {
- self.selection.entry(thread).and_modify(|e| *e = !*e);
- }
- Modifier::Union => {
- self.selection.entry(thread).and_modify(|e| *e = true);
- }
- Modifier::Difference => {
- self.selection.entry(thread).and_modify(|e| *e = false);
- }
- Modifier::Intersection => {}
+ if let Some(thread) = self.get_thread_under_cursor(c) {
+ self.rows.update_selection_with_thread(
+ thread,
+ match modifier {
+ Modifier::SymmetricDifference => {
+ |e: &mut bool| *e = !*e
+ }
+ Modifier::Union => |e: &mut bool| *e = true,
+ Modifier::Difference => |e: &mut bool| *e = false,
+ Modifier::Intersection => |_: &mut bool| {},
+ },
+ );
}
- self.row_updates.push(thread);
}
}
PageMovement::Down(amount) => {
for c in self.new_cursor_pos.2
..std::cmp::min(self.length, self.new_cursor_pos.2 + amount + 1)
{
- let thread = self.get_thread_under_cursor(c);
- match modifier {
- Modifier::SymmetricDifference => {
- self.selection.entry(thread).and_modify(|e| *e = !*e);
- }
- Modifier::Union => {
- self.selection.entry(thread).and_modify(|e| *e = true);
- }
- Modifier::Difference => {
- self.selection.entry(thread).and_modify(|e| *e = false);
- }
- Modifier::Intersection => {}
+ if let Some(thread) = self.get_thread_under_cursor(c) {
+ self.rows.update_selection_with_thread(
+ thread,
+ match modifier {
+ Modifier::SymmetricDifference => {
+ |e: &mut bool| *e = !*e
+ }
+ Modifier::Union => |e: &mut bool| *e = true,
+ Modifier::Difference => |e: &mut bool| *e = false,
+ Modifier::Intersection => |_: &mut bool| {},
+ },
+ );
}
- self.row_updates.push(thread);
}
if modifier == Modifier::Intersection {
for c in (0..self.new_cursor_pos.2).chain(
(std::cmp::min(self.length, self.new_cursor_pos.2 + amount + 1)
+ 1)..self.length,
) {
- let thread = self.get_thread_under_cursor(c);
- self.selection.entry(thread).and_modify(|e| *e = false);
- self.row_updates.push(thread);
+ if let Some(thread) = self.get_thread_under_cursor(c) {
+ self.rows
+ .update_selection_with_thread(thread, |e| *e = false);
+ }
}
}
}
@@ -1107,20 +1099,19 @@ impl Component for ConversationsListing {
self.length,
)
{
- let thread = self.get_thread_under_cursor(c);
- match modifier {
- Modifier::SymmetricDifference => {
- self.selection.entry(thread).and_modify(|e| *e = !*e);
- }
- Modifier::Union => {
- self.selection.entry(thread).and_modify(|e| *e = true);
- }
- Modifier::Difference => {
- self.selection.entry(thread).and_modify(|e| *e = false);
- }
- Modifier::Intersection => {}
+ if let Some(thread) = self.get_thread_under_cursor(c) {
+ self.rows.update_selection_with_thread(
+ thread,
+ match modifier {
+ Modifier::SymmetricDifference => {
+ |e: &mut bool| *e = !*e
+ }
+ Modifier::Union => |e: &mut bool| *e = true,
+ Modifier::Difference => |e: &mut bool| *e = false,
+ Modifier::Intersection => |_: &mut bool| {},
+ },
+ );
}
- self.row_updates.push(thread);
}
if modifier == Modifier::Intersection {
for c in (0..self.new_cursor_pos.2).chain(
@@ -1129,72 +1120,73 @@ impl Component for ConversationsListing {
self.length,
) + 1)..self.length,
) {
- let thread = self.get_thread_under_cursor(c);
- self.selection.entry(thread).and_modify(|e| *e = false);
- self.row_updates.push(thread);
+ if let Some(thread) = self.get_thread_under_cursor(c) {
+ self.rows
+ .update_selection_with_thread(thread, |e| *e = false);
+ }
}
}
}
PageMovement::Right(_) | PageMovement::Left(_) => {}
PageMovement::Home => {
for c in 0..=self.new_cursor_pos.2 {
- let thread = self.get_thread_under_cursor(c);
- match modifier {
- Modifier::SymmetricDifference => {
- self.selection.entry(thread).and_modify(|e| *e = !*e);
- }
- Modifier::Union => {
- self.selection.entry(thread).and_modify(|e| *e = true);
- }
- Modifier::Difference => {
- self.selection.entry(thread).and_modify(|e| *e = false);
- }
- Modifier::Intersection => {}
+ if let Some(thread) = self.get_thread_under_cursor(c) {
+ self.rows.update_selection_with_thread(
+ thread,
+ match modifier {
+ Modifier::SymmetricDifference => {
+ |e: &mut bool| *e = !*e
+ }
+ Modifier::Union => |e: &mut bool| *e = true,
+ Modifier::Difference => |e: &mut bool| *e = false,
+ Modifier::Intersection => |_: &mut bool| {},
+ },
+ );
}
- self.row_updates.push(thread);
}
if modifier == Modifier::Intersection {
for c in (self.new_cursor_pos.2 + 1)..self.length {
- let thread = self.get_thread_under_cursor(c);
- self.selection.entry(thread).and_modify(|e| *e = false);
- self.row_updates.push(thread);
+ if let Some(thread) = self.get_thread_under_cursor(c) {
+ self.rows
+ .update_selection_with_thread(thread, |e| *e = false);
+ }
}
}
}
PageMovement::End => {
for c in self.new_cursor_pos.2..self.length {
- let thread = self.get_thread_under_cursor(c);
- match modifier {
- Modifier::SymmetricDifference => {
- self.selection.entry(thread).and_modify(|e| *e = !*e);
- }
- Modifier::Union => {
- self.selection.entry(thread).and_modify(|e| *e = true);
- }
- Modifier::Difference => {
- self.selection.entry(thread).and_modify(|e| *e = false);
- }
- Modifier::Intersection => {}
+ if let Some(thread) = self.get_thread_under_cursor(c) {
+ self.rows.update_selection_with_thread(
+ thread,
+ match modifier {
+ Modifier::SymmetricDifference => {
+ |e: &mut bool| *e = !*e
+ }
+ Modifier::Union => |e: &mut bool| *e = true,
+ Modifier::Difference => |e: &mut bool| *e = false,
+ Modifier::Intersection => |_: &mut bool| {},
+ },
+ );
}
- self.row_updates.push(thread);
}
if modifier == Modifier::Intersection {
for c in 0..self.new_cursor_pos.2 {
- let thread = self.get_thread_under_cursor(c);
- self.selection.entry(thread).and_modify(|e| *e = false);
- self.row_updates.push(thread);
+ if let Some(thread) = self.get_thread_under_cursor(c) {
+ self.rows
+ .update_selection_with_thread(thread, |e| *e = false);
+ }
}
}
}
}
}
}
- if !self.row_updates.is_empty() {
+ if !self.rows.row_updates.is_empty() {
/* certain rows need to be updated (eg an unseen message was just set seen)
* */
- while let Some(row) = self.row_updates.pop() {
+ while let Some(row) = self.rows.row_updates.pop() {
self.update_line(context, row);
- let row: usize = self.order[&row];
+ let row: usize = self.rows.env_order[&row];
let page_no = (self.cursor_pos.2).wrapping_div(rows);
@@ -1271,9 +1263,10 @@ impl Component for ConversationsListing {
&& (shortcut!(k == shortcuts[Listing::DESCRIPTION]["open_entry"])
|| shortcut!(k == shortcuts[Listing::DESCRIPTION]["focus_right"])) =>
{
- let thread = self.get_thread_under_cursor(self.cursor_pos.2);
- self.view = ThreadView::new(self.cursor_pos, thread, None, context);
- self.set_focus(Focus::Entry, context);
+ if let Some(thread) = self.get_thread_under_cursor(self.cursor_pos.2) {
+ self.view = ThreadView::new(self.cursor_pos, thread, None, context);
+ self.set_focus(Focus::Entry, context);
+ }
return true;
}
UIEvent::Input(ref k)
@@ -1314,9 +1307,9 @@ impl Component for ConversationsListing {
if self.modifier_active && self.modifier_command.is_none() {
self.modifier_command = Some(Modifier::default());
} else {
- let thread_hash = self.get_thread_under_cursor(self.cursor_pos.2);
- self.selection.entry(thread_hash).and_modify(|e| *e = !*e);
- self.row_updates.push(thread_hash);
+ if let Some(thread) = self.get_thread_under_cursor(self.cursor_pos.2) {
+ self.rows.update_selection_with_thread(thread, |e| *e = !*e);
+ }
}
return true;
}
@@ -1333,8 +1326,8 @@ impl Component for ConversationsListing {
let thread: ThreadHash =
threads.find_group(threads.thread_nodes()[&env_thread_node_hash].group);
drop(threads);
- if self.order.contains_key(&thread) {
- self.row_updates.push(thread);
+ if self.rows.thread_order.contains_key(&thread) {
+ self.rows.rename_env(*old_hash, *new_hash);
}
self.dirty = true;
@@ -1347,7 +1340,7 @@ impl Component for ConversationsListing {
}
}
UIEvent::EnvelopeRemove(ref _env_hash, ref thread_hash) => {
- if self.order.contains_key(thread_hash) {
+ if self.rows.thread_order.contains_key(thread_hash) {
self.refresh_mailbox(context, false);
self.set_dirty(true);
}
@@ -1365,8 +1358,8 @@ impl Component for ConversationsListing {
let thread: ThreadHash =
threads.find_group(threads.thread_nodes()[&env_thread_node_hash].group);
drop(threads);
- if self.order.contains_key(&thread) {
- self.row_updates.push(thread);
+ if self.rows.thread_order.contains_key(&thread) {
+ self.rows.row_updates.push(*env_hash);
}
self.dirty = true;
@@ -1410,20 +1403,23 @@ impl Component for ConversationsListing {
return true;
}
Action::Listing(ToggleThreadSnooze) if !self.unfocused() => {
- let thread = self.get_thread_under_cursor(self.cursor_pos.2);
- let account = &mut context.accounts[&self.cursor_pos.0];
- account
- .collection
- .threads
- .write()
- .unwrap()
- .entry(self.cursor_pos.1)
- .and_modify(|threads| {
- let is_snoozed = threads.thread_ref(thread).snoozed();
- threads.thread_ref_mut(thread).set_snoozed(!is_snoozed);
- });
- self.row_updates.push(thread);
- self.refresh_mailbox(context, false);
+ /*
+ if let Some(thread) = self.get_thread_under_cursor(self.cursor_pos.2) {
+ let account = &mut context.accounts[&self.cursor_pos.0];
+ account
+ .collection
+ .threads
+ .write()
+ .unwrap()
+ .entry(self.cursor_pos.1)
+ .and_modify(|threads| {
+ let is_snoozed = threads.thread_ref(thread).snoozed();
+ threads.thread_ref_mut(thread).set_snoozed(!is_snoozed);
+ });
+ self.rows.row_updates.push(thread);
+ self.refresh_mailbox(context, false);
+ }
+ */
return true;
}
_ => {}
@@ -1504,14 +1500,14 @@ impl Component for ConversationsListing {
},
UIEvent::Input(Key::Esc)
if !self.unfocused()
- && self.selection.values().cloned().any(std::convert::identity) =>
+ && self
+ .rows
+ .selection
+ .values()
+ .cloned()
+ .any(std::convert::identity) =>
{
- for (k, v) in self.selection.iter_mut() {
- if *v {
- *v = false;
- self.row_updates.push(*k);
- }
- }
+ self.rows.clear_selection();
self.dirty = true;
return true;
}
diff --git a/src/components/mail/listing/offline.rs b/src/components/mail/listing/offline.rs
index bd3402c7..3fdcebe7 100644
--- a/src/components/mail/listing/offline.rs
+++ b/src/components/mail/listing/offline.rs
@@ -26,23 +26,23 @@ use std::borrow::Cow;
#[derive(Debug)]
pub struct OfflineListing {
cursor_pos: (AccountHash, MailboxHash),
- _row_updates: SmallVec<[ThreadHash; 8]>,
- _selection: HashMap<ThreadHash, bool>,
+ _row_updates: SmallVec<[EnvelopeHash; 8]>,
+ _selection: HashMap<EnvelopeHash, bool>,
messages: Vec<Cow<'static, str>>,
dirty: bool,
id: ComponentId,
}
impl MailListingTrait for OfflineListing {
- fn row_updates(&mut self) -> &mut SmallVec<[ThreadHash; 8]> {
+ fn row_updates(&mut self) -> &mut SmallVec<[EnvelopeHash; 8]> {
&mut self._row_updates
}
- fn selection(&mut self) -> &mut HashMap<ThreadHash, bool> {
+ fn selection(&mut self) -> &mut HashMap<EnvelopeHash, bool> {
&mut self._selection
}
- fn get_focused_items(&self, _context: &Context) -> SmallVec<[ThreadHash; 8]> {
+ fn get_focused_items(&self, _context: &Context) -> SmallVec<[EnvelopeHash; 8]> {
SmallVec::new()
}
@@ -85,6 +85,14 @@ impl ListingTrait for OfflineListing {
false
}
+ fn set_modifier_active(&mut self, _: bool) {}
+
+ fn set_modifier_command(&mut self, _: Option<Modifier>) {}
+
+ fn modifier_command(&self) -> Option<Modifier> {
+ None
+ }
+
fn set_movement(&mut self, _: PageMovement) {}
fn focus(&self) -> Focus {
diff --git a/src/components/mail/listing/plain.rs b/src/components/mail/listing/plain.rs
index c4073f11..c4296a4c 100644
--- a/src/components/mail/listing/plain.rs
+++ b/src/components/mail/listing/plain.rs
@@ -22,7 +22,7 @@
use super::EntryStrings;
use super::*;
use crate::components::PageMovement;
-use crate::jobs::{JobId, JoinHandle};
+use crate::jobs::JoinHandle;
use std::cmp;
use std::iter::FromIterator;
@@ -128,8 +128,7 @@ pub struct PlainListing {
length: usize,
sort: (SortField, SortOrder),
subsort: (SortField, SortOrder),
- all_envelopes: HashSet<EnvelopeHash>,
- order: HashMap<EnvelopeHash, usize>,
+ rows: RowsState<(ThreadHash, EnvelopeHash)>,
/// Cache current view.
data_columns: DataColumns,
@@ -138,9 +137,6 @@ pub struct PlainListing {
filter_term: String,
filtered_selection: Vec<EnvelopeHash>,
filtered_order: HashMap<EnvelopeHash, usize>,
- selection: HashMap<EnvelopeHash, bool>,
- _selection: HashMap<ThreadHash, bool>,
- thread_node_hashes: HashMap<EnvelopeHash, ThreadNodeHash>,
local_collection: Vec<EnvelopeHash>,
/// If we must redraw on next redraw event
dirty: bool,
@@ -148,40 +144,42 @@ pub struct PlainListing {
/// If `self.view` exists or not.
focus: Focus,
view: MailView,
- row_updates: SmallVec<[EnvelopeHash; 8]>,
- _row_updates: SmallVec<[ThreadHash; 8]>,
color_cache: ColorCache,
-
- active_jobs: HashMap<JobId, JoinHandle<Result<()>>>,
movement: Option<PageMovement>,
+ modifier_active: bool,
+ modifier_command: Option<Modifier>,
id: ComponentId,
}
impl MailListingTrait for PlainListing {
- fn row_updates(&mut self) -> &mut SmallVec<[ThreadHash; 8]> {
- &mut self._row_updates
+ fn row_updates(&mut self) -> &mut SmallVec<[EnvelopeHash; 8]> {
+ &mut self.rows.row_updates
}
- fn selection(&mut self) -> &mut HashMap<ThreadHash, bool> {
- &mut self._selection
+ fn selection(&mut self) -> &mut HashMap<EnvelopeHash, bool> {
+ &mut self.rows.selection
}
- fn get_focused_items(&self, _context: &Context) -> SmallVec<[ThreadHash; 8]> {
- SmallVec::new()
- /*
- let is_selection_empty = self.selection.values().cloned().any(std::convert::identity);
+ fn get_focused_items(&self, _context: &Context) -> SmallVec<[EnvelopeHash; 8]> {
+ let is_selection_empty: bool = !self
+ .rows
+ .selection
+ .values()
+ .cloned()
+ .any(std::convert::identity);
+ dbg!(is_selection_empty);
if is_selection_empty {
- self.selection
- .iter()
- .filter(|(_, v)| **v)
- .map(|(k, _)| self.thread_node_hashes[k])
- .collect()
- } else {
- let mut ret = SmallVec::new();
- ret.push(self.get_thread_under_cursor(self.cursor_pos.2, context));
- ret
+ return dbg!(self.get_env_under_cursor(self.cursor_pos.2))
+ .into_iter()
+ .collect::<_>();
}
- */
+ SmallVec::from_iter(
+ self.rows
+ .selection
+ .iter()
+ .filter(|(_, &v)| v)
+ .map(|(k, _)| *k),
+ )
}
/// Fill the `self.data_columns` `CellBuffers` with the contents of the account mailbox the user has
@@ -253,12 +251,6 @@ impl MailListingTrait for PlainListing {
.envelopes
.read()
.unwrap();
- self.thread_node_hashes = context.accounts[&self.cursor_pos.0]
- .collection
- .get_mailbox(self.cursor_pos.1)
- .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) => {
@@ -282,17 +274,13 @@ impl MailListingTrait for PlainListing {
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<dyn Iterator<Item = EnvelopeHash>>;
self.redraw_list(context, items);
drop(env_lck);
- if self.length > 0 {
- let env_hash = self.get_env_under_cursor(self.cursor_pos.2, context);
+ if let Some(env_hash) = self.get_env_under_cursor(self.cursor_pos.2) {
let temp = (self.new_cursor_pos.0, self.new_cursor_pos.1, env_hash);
if !force && old_cursor_pos == self.new_cursor_pos {
self.view.update(temp, context);
@@ -340,14 +328,16 @@ impl ListingTrait for PlainListing {
self.filtered_selection.clear();
self.filtered_order.clear();
self.filter_term.clear();
- self.row_updates.clear();
+ self.rows.row_updates.clear();
}
fn highlight_line(&mut self, grid: &mut CellBuffer, area: Area, idx: usize, context: &Context) {
- if self.length == 0 {
+ let i = if let Some(i) = self.get_env_under_cursor(idx) {
+ i
+ } else {
+ // 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(i);
@@ -357,7 +347,7 @@ impl ListingTrait for PlainListing {
idx % 2 == 0,
!envelope.is_seen(),
self.cursor_pos.2 == idx,
- self.selection[&i]
+ self.rows.selection[&i]
);
let (upper_left, bottom_right) = area;
@@ -602,14 +592,12 @@ impl ListingTrait for PlainListing {
return;
}
- self.order.clear();
- self.selection.clear();
self.length = 0;
self.filtered_selection.clear();
self.filtered_order.clear();
self.filter_term = filter_term;
- self.row_updates.clear();
- for v in self.selection.values_mut() {
+ self.rows.row_updates.clear();
+ for v in self.rows.selection.values_mut() {
*v = false;
}
@@ -621,7 +609,7 @@ impl ListingTrait for PlainListing {
if self.filtered_order.contains_key(&env_hash) {
continue;
}
- if self.all_envelopes.contains(&env_hash) {
+ if self.rows.contains_env(env_hash) {
self.filtered_selection.push(env_hash);
self.filtered_order
.insert(env_hash, self.filtered_selection.len() - 1);
@@ -644,6 +632,18 @@ impl ListingTrait for PlainListing {
!matches!(self.focus, Focus::None)
}
+ fn set_modifier_active(&mut self, new_val: bool) {
+ self.modifier_active = new_val;
+ }
+
+ fn set_modifier_command(&mut self, new_val: Option<Modifier>) {
+ self.modifier_command = new_val;
+ }
+
+ fn modifier_command(&self) -> Option<Modifier> {
+ self.modifier_command
+ }
+
fn set_movement(&mut self, mvm: PageMovement) {
self.movement = Some(mvm);
self.set_dirty(true);
@@ -655,18 +655,19 @@ impl ListingTrait for PlainListing {
self.view
.process_event(&mut UIEvent::VisibilityChange(false), context);
self.dirty = true;
- /* If self.row_updates is not empty and we exit a thread, the row_update events
+ /* If self.rows.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;
}
Focus::Entry => {
- 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, context);
- self.force_draw = true;
- self.dirty = true;
- self.view.set_dirty(true);
+ if let Some(env_hash) = self.get_env_under_cursor(self.cursor_pos.2) {
+ let temp = (self.cursor_pos.0, self.cursor_pos.1, env_hash);
+ self.view = MailView::new(temp, None, None, context);
+ self.force_draw = true;
+ self.dirty = true;
+ self.view.set_dirty(true);
+ }
}
Focus::EntryFullscreen => {
self.dirty = true;
@@ -696,32 +697,26 @@ impl PlainListing {
length: 0,
sort: (Default::default(), Default::default()),
subsort: (SortField::Date, SortOrder::Desc),
- all_envelopes: HashSet::default(),
+ rows: RowsState::default(),
local_collection: Vec::new(),
- thread_node_hashes: HashMap::default(),
- order: HashMap::default(),
filter_term: String::new(),
search_job: None,
filtered_selection: Vec::new(),
filtered_order: HashMap::default(),
- selection: HashMap::default(),
- _selection: HashMap::default(),
- row_updates: SmallVec::new(),
- _row_updates: SmallVec::new(),
data_columns: DataColumns::default(),
dirty: true,
force_draw: true,
focus: Focus::None,
view: MailView::default(),
color_cache: ColorCache::default(),
- active_jobs: HashMap::default(),
-
movement: None,
+ modifier_active: false,
+ modifier_command: None,
id: ComponentId::new_v4(),
})
}
- fn make_entry_string(&self, e: EnvelopeRef, context: &Context) -> EntryStrings {
+ fn make_entry_string(&self, e: &Envelope, context: &Context) -> EntryStrings {
let mut tags = String::new();
let mut colors = SmallVec::new();
let account = &context.accounts[&self.cursor_pos.0];
@@ -766,7 +761,7 @@ impl PlainListing {
subject: SubjectString(subject),
flag: FlagString(format!(
"{selected}{unseen}{attachments}{whitespace}",
- selected = if self.selection.get(&e.hash()).cloned().unwrap_or(false) {
+ selected = if self.rows.selection.get(&e.hash()).cloned().unwrap_or(false) {
mailbox_settings!(
context[self.cursor_pos.0][&self.cursor_pos.1]
.listing
@@ -802,7 +797,7 @@ impl PlainListing {
} else {
""
},
- whitespace = if self.selection.get(&e.hash()).cloned().unwrap_or(false)
+ whitespace = if self.rows.selection.get(&e.hash()).cloned().unwrap_or(false)
|| !e.is_seen()
|| e.has_attachments()
{
@@ -819,11 +814,10 @@ impl PlainListing {
fn redraw_list(&mut self, context: &Context, iter: Box<dyn Iterator<Item = EnvelopeHash>>) {
let account = &context.accounts[&self.cursor_pos.0];
let mailbox = &account[&self.cursor_pos.1];
+ let threads = account.collection.get_threads(self.cursor_pos.1);
- self.order.clear();
- self.selection.clear();
+ self.rows.clear();
self.length = 0;
- let mut rows = Vec::with_capacity(1024);
let mut min_width = (0, 0, 0, 0, 0);
for i in iter {
@@ -852,7 +846,7 @@ impl PlainListing {
}
}
- let entry_strings = self.make_entry_string(envelope, context);
+ let entry_strings = self.make_entry_string(&envelope, context);
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(
@@ -862,10 +856,13 @@ impl PlainListing {
+ 1
+ entry_strings.tags.grapheme_width(),
); /* tags + subject */
- rows.push(entry_strings);
+ self.rows.insert_thread(
+ threads.envelope_to_thread[&i],
+ (threads.envelope_to_thread[&i], i),
+ smallvec::smallvec![i],
+ entry_strings,
+ );
- self.order.insert(i, self.length);
- self.selection.insert(i, false);
self.length += 1;
}
@@ -873,16 +870,16 @@ impl PlainListing {
/* index column */
self.data_columns.columns[0] =
- CellBuffer::new_with_context(min_width.0, rows.len(), None, context);
+ CellBuffer::new_with_context(min_width.0, self.rows.len(), None, context);
/* date column */
self.data_columns.columns[1] =
- CellBuffer::new_with_context(min_width.1, rows.len(), None, context);
+ CellBuffer::new_with_context(min_width.1, self.rows.len(), None, context);
/* from column */
self.data_columns.columns[2] =
- CellBuffer::new_with_context(min_width.2, rows.len(), None, context);
+ CellBuffer::new_with_context(min_width.2, self.rows.len(), None, context);
/* subject column */
self.data_columns.columns[3] =
- CellBuffer::new_with_context(min_width.3, rows.len(), None, context);
+ CellBuffer::new_with_context(min_width.3, self.rows.len(), None, context);
let iter = if self.filter_term.is_empty() {
Box::new(self.local_collection.iter().cloned())
@@ -893,7 +890,7 @@ impl PlainListing {
};
let columns = &mut self.data_columns.columns;
- for ((idx, i), strings) in iter.enumerate().zip(rows) {
+ for ((idx, i), (_, strings)) in iter.enumerate().zip(self.rows.entries.iter()) {
if !context.accounts[&self.cursor_pos.0].contains_key(i) {
//debug!("key = {}", i);
//debug!(
@@ -1003,7 +1000,7 @@ impl PlainListing {
}
/* Set fg color for flags */
let mut x = 0;
- if self.selection.get(&i).cloned().unwrap_or(false) {
+ if self.rows.selection.get(&i).cloned().unwrap_or(false) {
x += 1;
}
if !envelope.is_seen() {
@@ -1029,11 +1026,11 @@ impl PlainListing {
}
}
- fn get_env_under_cursor(&self, cursor: usize, _context: &Context) -> EnvelopeHash {
+ fn get_env_under_cursor(&self, cursor: usize) -> Option<EnvelopeHash> {
if self.filter_term.is_empty() {
- self.local_collection[cursor]
+ self.local_collection.get(cursor).cloned()
} else {
- self.filtered_selection[cursor]
+ self.filtered_selection.get(cursor).cloned()
}
}
@@ -1051,42 +1048,6 @@ impl PlainListing {
_ => melib::datetime::timestamp_to_string(envelope.datetime(), None, false),
}
}
-
- fn perform_action(&mut self, context: &mut Context, env_hash: EnvelopeHash, a: &ListingAction) {
- let account = &mut context.accounts[&self.cursor_pos.0];
- match {
- match a {
- ListingAction::SetSeen => account.backend.write().unwrap().set_flags(
- env_hash.into(),
- self.cursor_pos.1,
- smallvec::smallvec![(Ok(Flag::SEEN), true)],
- ),
- ListingAction::SetUnseen => account.backend.write().unwrap().set_flags(
- env_hash.into(),
- self.cursor_pos.1,
- smallvec::smallvec![(Ok(Flag::SEEN), false)],
- ),
- ListingAction::Delete => {
- /* do nothing */
- Err(MeliError::new("Delete is unimplemented"))
- }
- _ => unreachable!(),
- }
- } {
- Err(e) => {
- context
- .replies
- .push_back(UIEvent::StatusEvent(StatusEvent::DisplayMessage(
- e.to_string(),
- )));
- }
- Ok(fut) => {
- let handle = account.job_executor.spawn_specialized(fut);
- self.active_jobs.insert(handle.job_id, handle);
- }
- }
- self.row_updates.push(env_hash);
- }
}
impl Component for PlainListing {
@@ -1128,10 +1089,10 @@ impl Component for PlainListing {
area = (set_y(upper_left, y + 1), bottom_right);
}
- if !self.row_updates.is_empty() {
+ if !self.rows.row_updates.is_empty() {
let (upper_left, bottom_right) = area;
- while let Some(row) = self.row_updates.pop() {
- let row: usize = self.order[&row];
+ while let Some(row) = self.rows.row_updates.pop() {
+ let row: usize = self.rows.env_order[&row];
let rows = get_y(bottom_right) - get_y(upper_left) + 1;
let page_no = (self.new_cursor_pos.2).wrapping_div(rows);
@@ -1233,8 +1194,14 @@ impl Component for PlainListing {
if !self.unfocused()
&& shortcut!(key == shortcuts[Listing::DESCRIPTION]["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);
+ if self.modifier_active && self.modifier_command.is_none() {
+ self.modifier_command = Some(Modifier::default());
+ } else {
+ if let Some(env_hash) = self.get_env_under_cursor(self.cursor_pos.2) {
+ self.rows.update_selection_with_env(env_hash, |e| *e = !*e);
+ }
+ }
+ return true;
}
UIEvent::Action(ref action) => match action {
Action::SubSort(field, order) if !self.unfocused() => {
@@ -1253,37 +1220,6 @@ impl Component for PlainListing {
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: SmallVec<[_; 8]> = SmallVec::from_iter(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;
- }
_ => {}
},
@@ -1347,16 +1283,11 @@ impl Component for PlainListing {
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.rows.rename_env(*old_hash, *new_hash);
+ for h in self.filtered_selection.iter_mut() {
+ if *h == *old_hash {
+ *h = *new_hash;
+ break;
}
}
@@ -1378,7 +1309,7 @@ impl Component for PlainListing {
return false;
}
- self.row_updates.push(*env_hash);
+ self.rows.row_updates.push(*env_hash);
self.dirty = true;
if self.unfocused() {
@@ -1394,9 +1325,14 @@ impl Component for PlainListing {
}
UIEvent::Input(Key::Esc)
if !self.unfocused()
- && self.selection.values().cloned().any(std::convert::identity) =>
+ && self
+ .rows
+ .selection
+ .values()
+ .cloned()
+ .any(std::convert::identity) =>
{
- for v in self.selection.values_mut() {
+ for v in self.rows.selection.values_mut() {
*v = false;
}
self.dirty = true;
diff --git a/src/components/mail/listing/thread.rs b/src/components/mail/listing/thread.rs
index 464fc352..6129730d 100644
--- a/src/components/mail/listing/thread.rs
+++ b/src/components/mail/listing/thread.rs
@@ -24,6 +24,7 @@ use crate::components::PageMovement;
use std::cmp;
use std::convert::TryInto;
use std::fmt::Write;
+use std::iter::FromIterator;
macro_rules! row_attr {
($color_cache:expr, $even: expr, $unseen:expr, $highlighted:expr, $selected:expr $(,)*) => {{
@@ -122,31 +123,48 @@ pub struct ThreadListing {
data_columns: DataColumns,
rows_drawn: SegmentTree,
- rows: Vec<((usize, bool, bool, EnvelopeHash), EntryStrings)>,
- row_updates: SmallVec<[ThreadHash; 8]>,
- selection: HashMap<ThreadHash, bool>,
- order: HashMap<EnvelopeHash, usize>,
+ rows: RowsState<(bool, bool, ThreadHash, EnvelopeHash)>,
/// If we must redraw on next redraw event
dirty: bool,
/// If `self.view` is focused or not.
focus: Focus,
initialised: bool,
view: Option<Box<MailView>>,
+ modifier_active: bool,
+ modifier_command: Option<Modifier>,
movement: Option<PageMovement>,
id: ComponentId,
}
impl MailListingTrait for ThreadListing {
- fn row_updates(&mut self) -> &mut SmallVec<[ThreadHash; 8]> {
- &mut self.row_updates
+ fn row_updates(&mut self) -> &mut SmallVec<[EnvelopeHash; 8]> {
+ &mut self.rows.row_updates
}
- fn selection(&mut self) -> &mut HashMap<ThreadHash, bool> {
- &mut self.selection
+ fn selection(&mut self) -> &mut HashMap<EnvelopeHash, bool> {
+ &mut self.rows.selection
}
- fn get_focused_items(&self, _context: &Context) -> SmallVec<[ThreadHash; 8]> {
- SmallVec::new()
+ fn get_focused_items(&self, _context: &Context) -> SmallVec<[EnvelopeHash; 8]> {
+ let is_selection_empty: bool = !self
+ .rows
+ .selection
+ .values()
+ .cloned()
+ .any(std::convert::identity);
+ if is_selection_empty {
+ return self
+ .get_env_under_cursor(self.cursor_pos.2)
+ .into_iter()
+ .collect::<_>();
+ }
+ SmallVec::from_iter(
+ self.rows
+ .selection
+ .iter()
+ .filter(|(_, &v)| v)
+ .map(|(k, _)| *k),
+ )
}
/// Fill the `self.content` `CellBuffer` with the contents of the account mailbox the user has
@@ -230,7 +248,7 @@ impl MailListingTrait for ThreadListing {
let account = &context.accounts[&self.cursor_pos.0];
let threads = account.collection.get_threads(self.cursor_pos.1);
self.length = 0;
- self.order.clear();
+ self.rows.clear();
if threads.len() == 0 {
let message: String = account[&self.cursor_pos.1].status();
self.data_columns.columns[0] =
@@ -246,7 +264,6 @@ impl MailListingTrait for ThreadListing {
);
return;
}
- let mut rows = Vec::with_capacity(1024);
let mut min_width = (0, 0, 0, 0, 0);
#[allow(clippy::type_complexity)]
let mut row_widths: (
@@ -270,15 +287,13 @@ impl MailListingTrait for ThreadListing {
let mut iter = threads.threads_group_iter(roots).peekable();
let thread_nodes: &HashMap<ThreadNodeHash, ThreadNode> = threads.thread_nodes();
/* This is just a desugared for loop so that we can use .peek() */
- let mut idx = 0;
+ let mut idx: usize = 0;
let mut prev_group = ThreadHash::null();
while let Some((indentation, thread_node_hash, has_sibling)) = iter.next() {
let thread_node = &thread_nodes[&thread_node_hash];
- if thread_node.has_message() {
- let envelope: EnvelopeRef =
- account.collection.get_env(thread_node.message().unwrap());
- self.order.insert(envelope.hash(), idx);
+ if let Some(env_hash) = thread_node.message() {
+ let envelope: EnvelopeRef = account.collection.get_env(env_hash);
use melib::search::QueryTrait;
if let Some(filter_query) = mailbox_settings!(
context[self.cursor_pos.0][&self.cursor_pos.1]
@@ -341,15 +356,17 @@ impl MailListingTrait for ThreadListing {
+ 1
+ entry_strings.tags.grapheme_width(),
); /* tags + subject */
- rows.push((
+ self.rows.insert_thread(
+ threads.envelope_to_thread[&env_hash],
(
- idx,
envelope.is_seen(),
envelope.has_attachments(),
- envelope.hash(),
+ threads.envelope_to_thread[&env_hash],
+ env_hash,
),
+ smallvec::smallvec![env_hash],
entry_strings,
- ));
+ );
idx += 1;
} else {
continue;
@@ -374,24 +391,23 @@ impl MailListingTrait for ThreadListing {
min_width.0 = idx.saturating_sub(1).to_string().len();
/* index column */
self.data_columns.columns[0] =
- CellBuffer::new_with_context(min_width.0, rows.len(), None, context);
+ CellBuffer::new_with_context(min_width.0, self.rows.len(), None, context);
/* date column */
self.data_columns.columns[1] =
- CellBuffer::new_with_context(min_width.1, rows.len(), None, context);
+ CellBuffer::new_with_context(min_width.1, self.rows.len(), None, context);
/* from column */
self.data_columns.columns[2] =
- CellBuffer::new_with_context(min_width.2, rows.len(), None, context);
+ CellBuffer::new_with_context(min_width.2, self.rows.len(), None, 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(), None, context);
+ CellBuffer::new_with_context(min_width.3, self.rows.len(), None, context);
/* subject column */
self.data_columns.columns[4] =
- CellBuffer::new_with_context(min_width.4, rows.len(), None, context);
+ CellBuffer::new_with_context(min_width.4, self.rows.len(), None, 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())
@@ -403,7 +419,7 @@ impl MailListingTrait for ThreadListing {
0,
std::cmp::min(80, self.rows.len().saturating_sub(1)),
);
- self.length = self.order.len();
+ self.length = self.rows.len();
}
}
@@ -416,8 +432,7 @@ impl ListingTrait for ThreadListing {
self.new_cursor_pos = (coordinates.0, coordinates.1, 0);
self.focus = Focus::None;
self.view = None;
- self.order.clear();
- self.row_updates.clear();
+ self.rows.clear();
self.initialised = false;
}
@@ -620,15 +635,15 @@ impl ListingTrait for ThreadListing {
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)];
- /*
- let thread_hash = self.get_thread_under_cursor(r + top_idx);
-
- if self.selection[&thread_hash] {
- (c.fg(), self.color_cache.selected.bg)
+ if let Some(env_hash) = self.get_env_under_cursor(r + top_idx) {
+ if self.rows.selection[&env_hash] {
+ (c.fg(), self.color_cache.selected.bg)
+ } else {
+ (c.fg(), c.bg())
+ }
} else {
+ (c.fg(), c.bg())
}
- */
- (c.fg(), c.bg())
};
change_colors(
grid,
@@ -703,11 +718,13 @@ impl ListingTrait for ThreadListing {
}
fn highlight_line(&mut self, grid: &mut CellBuffer, area: Area, idx: usize, context: &Context) {
- if self.length == 0 {
+ let env_hash = if let Some(i) = self.get_env_under_cursor(idx) {
+ i
+ } else {
+ // self.length == 0
return;
- }
+ };
- let env_hash = self.get_env_under_cursor(idx, context);
let envelope: EnvelopeRef = context.accounts[&self.cursor_pos.0]
.collection
.get_env(env_hash);
@@ -717,7 +734,7 @@ impl ListingTrait for ThreadListing {
idx % 2 == 0,
!envelope.is_seen(),
self.cursor_pos.2 == idx,
- false,
+ self.rows.selection[&env_hash],
);
for row in grid.bounds_iter(area) {
for c in row {
@@ -746,6 +763,18 @@ impl ListingTrait for ThreadListing {
!matches!(self.focus, Focus::None)
}
+ fn set_modifier_active(&mut self, new_val: bool) {
+ self.modifier_active = new_val;
+ }
+
+ fn set_modifier_command(&mut self, new_val: Option<Modifier>) {
+ self.modifier_command = new_val;
+ }
+
+ fn modifier_command(&self) -> Option<Modifier> {
+ self.modifier_command
+ }
+
fn set_movement(&mut self, mvm: PageMovement) {
self.movement = Some(mvm);
self.set_dirty(true);
@@ -756,28 +785,26 @@ impl ListingTrait for ThreadListing {
Focus::None => {
self.view = None;
self.dirty = true;
- /* If self.row_updates is not empty and we exit a thread, the row_update events
+ /* If self.rows.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;
}
Focus::Entry => {
- // self.force_draw = true;
- self.dirty = true;
- let coordinates = (
- self.cursor_pos.0,
- self.cursor_pos.1,
- self.get_env_under_cursor(self.cursor_pos.2, context),
- );
+ if let Some(env_hash) = self.get_env_under_cursor(self.cursor_pos.2) {
+ // self.force_draw = true;
+ self.dirty = true;
+ let coordinates = (self.cursor_pos.0, self.cursor_pos.1, env_hash);
- if let Some(ref mut v) = self.view {
- v.update(coordinates, context);
- } else {
- self.view = Some(Box::new(MailView::new(coordinates, None, None, context)));
- }
+ if let Some(ref mut v) = self.view {
+ v.update(coordinates, context);
+ } else {
+ self.view = Some(Box::new(MailView::new(coordinates, None, None, context)));
+ }
- if let Some(ref mut s) = self.view {
- s.set_dirty(true);
+ if let Some(ref mut s) = self.view {
+ s.set_dirty(true);
+ }
}
}
Focus::EntryFullscreen => {
@@ -811,15 +838,14 @@ impl ThreadListing {
color_cache: ColorCache::default(),
data_columns: DataColumns::default(),
rows_drawn: SegmentTree::default(),
- rows: vec![],
- row_updates: SmallVec::new(),
- selection: HashMap::default(),
- order: HashMap::default(),
+ rows: RowsState::default(),
dirty: true,
focus: Focus::None,
view: None,
initialised: false,
movement: None,
+ modifier_active: false,
+ modifier_command: None,
id: ComponentId::new_v4(),
search_job: None,
})
@@ -889,16 +915,13 @@ impl ThreadListing {
s
}
- fn get_env_under_cursor(&self, cursor: usize, _context: &Context) -> EnvelopeHash {
- *self
- .order
+ fn get_env_under_cursor(&self, cursor: usize) -> Option<EnvelopeHash> {
+ self.rows
+ .env_order
.iter()
.find(|(_, &r)| r == cursor)
- .unwrap_or_else(|| {
- debug!("self.order empty ? cursor={} {:#?}", cursor, &self.order);
- panic!();
- })
- .0
+ .map(|v| v.0)
+ .cloned()
}
fn make_entry_string(&self, e: &Envelope, context: &Context) -> EntryStrings {
@@ -971,10 +994,14 @@ impl ThreadListing {
self.data_columns.columns[4].size().0,
);
- for ((idx, is_seen, has_attachments, env_hash), strings) in
- self.rows.iter().skip(start).take(end - start + 1)
+ for (idx, ((is_seen, has_attachments, _thread_hash, env_hash), strings)) in self
+ .rows
+ .entries
+ .iter()
+ .enumerate()
+ .skip(start)
+ .take(end - start + 1)
{
- let idx = *idx;
if !context.accounts[&self.cursor_pos.0].contains_key(*env_hash) {
//debug!("key = {}", root_env_hash);
//debug!(
@@ -986,7 +1013,13 @@ impl ThreadListing {
panic!();
}
- let row_attr = row_attr!(self.color_cache, idx % 2 == 0, !*is_seen, false, false);
+ let row_attr = row_attr!(
+ self.color_cache,
+ idx % 2 == 0,
+ !*is_seen,
+ self.cursor_pos.2 == idx,
+ self.rows.selection[&env_hash],
+ );
let (x, _) = write_string_to_grid(
&idx.to_string(),
&mut self.data_columns.columns[0],
@@ -1115,27 +1148,187 @@ impl ThreadListing {
impl Component for ThreadListing {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
- /*
- 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 (upper_left, bottom_right) = area;
+ let rows = get_y(bottom_right) - get_y(upper_left) + 1;
- let rows = get_y(bottom_right) - get_y(upper_left) + 1;
- let page_no = (self.new_cursor_pos.2).wrapping_div(rows);
+ if let Some(modifier) = self.modifier_command.take() {
+ if let Some(mvm) = self.movement.as_ref() {
+ match mvm {
+ PageMovement::Up(amount) => {
+ for c in
+ self.new_cursor_pos.2.saturating_sub(*amount)..=self.new_cursor_pos.2
+ {
+ if let Some(env_hash) = self.get_env_under_cursor(c) {
+ self.rows.update_selection_with_env(
+ env_hash,
+ match modifier {
+ Modifier::SymmetricDifference => |e: &mut bool| *e = !*e,
+ Modifier::Union => |e: &mut bool| *e = true,
+ Modifier::Difference => |e: &mut bool| *e = false,
+ Modifier::Intersection => |_: &mut bool| {},
+ },
+ );
+ }
+ }
+ if modifier == Modifier::Intersection {
+ for c in (0..self.new_cursor_pos.2.saturating_sub(*amount))
+ .chain((self.new_cursor_pos.2 + 2)..self.length)
+ {
+ if let Some(env_hash) = self.get_env_under_cursor(c) {
+ self.rows
+ .update_selection_with_env(env_hash, |e| *e = false);
+ }
+ }
+ }
+ }
+ PageMovement::PageUp(multiplier) => {
+ for c in self.new_cursor_pos.2.saturating_sub(rows * multiplier)
+ ..=self.new_cursor_pos.2
+ {
+ if let Some(env_hash) = self.get_env_under_cursor(c) {
+ self.rows.update_selection_with_env(
+ env_hash,
+ match modifier {
+ Modifier::SymmetricDifference => |e: &mut bool| *e = !*e,
+ Modifier::Union => |e: &mut bool| *e = true,
+ Modifier::Difference => |e: &mut bool| *e = false,
+ Modifier::Intersection => |_: &mut bool| {},
+ },
+ );
+ }
+ }
+ }
+ PageMovement::Down(amount) => {
+ for c in self.new_cursor_pos.2
+ ..std::cmp::min(self.length, self.new_cursor_pos.2 + amount + 1)
+ {
+ if let Some(env_hash) = self.get_env_under_cursor(c) {
+ self.rows.update_selection_with_env(
+ env_hash,
+ match modifier {
+ Modifier::SymmetricDifference => |e: &mut bool| *e = !*e,
+ Modifier::Union => |e: &mut bool| *e = true,
+ Modifier::Difference => |e: &mut bool| *e = false,
+ Modifier::Intersection => |_: &mut bool| {},
+ },
+ );
+ }
+ }
+ if modifier == Modifier::Intersection {
+ for c in (0..self.new_cursor_pos.2).chain(
+ (std::cmp::min(self.length, self.new_cursor_pos.2 + amount + 1) + 1)
+ ..self.length,
+ ) {
+ if let Some(env_hash) = self.get_env_under_cursor(c) {
+ self.rows
+ .update_selection_with_env(env_hash, |e| *e = false);
+ }
+ }
+ }
+ }
+ PageMovement::PageDown(multiplier) => {
+ for c in self.new_cursor_pos.2
+ ..std::cmp::min(
+ self.new_cursor_pos.2 + rows * multiplier + 1,
+ self.length,
+ )
+ {
+ if let Some(env_hash) = self.get_env_under_cursor(c) {
+ self.rows.update_selection_with_env(
+ env_hash,
+ match modifier {
+ Modifier::SymmetricDifference => |e: &mut bool| *e = !*e,
+ Modifier::Union => |e: &mut bool| *e = true,
+ Modifier::Difference => |e: &mut bool| *e = false,
+ Modifier::Intersection => |_: &mut bool| {},
+ },
+ );
+ }
+ }
+ if modifier == Modifier::Intersection {
+ for c in (0..self.new_cursor_pos.2).chain(
+ (std::cmp::min(
+ self.new_cursor_pos.2 + rows * multiplier + 1,
+ self.length,
+ ) + 1)..self.length,
+ ) {
+ if let Some(env_hash) = self.get_env_under_cursor(c) {
+ self.rows
+ .update_selection_with_env(env_hash, |e| *e = false);
+ }
+ }
+ }
+ }
+ PageMovement::Right(_) | PageMovement::Left(_) => {}
+ PageMovement::Home => {
+ for c in 0..=self.new_cursor_pos.2 {
+ if let Some(env_hash) = self.get_env_under_cursor(c) {
+ self.rows.update_selection_with_env(
+ env_hash,
+ match modifier {
+ Modifier::SymmetricDifference => |e: &mut bool| *e = !*e,
+ Modifier::Union => |e: &mut bool| *e = true,
+ Modifier::Difference => |e: &mut bool| *e = false,
+ Modifier::Intersection => |_: &mut bool| {},
+ },
+ );
+ }
+ }
+ if modifier == Modifier::Intersection {
+ for c in (self.new_cursor_pos.2 + 1)..self.length {
+ if let Some(env_hash) = self.get_env_under_cursor(c) {
+ self.rows
+ .update_selection_with_env(env_hash, |e| *e = false);
+ }
+ }
+ }
+ }
+ PageMovement::End => {
+ for c in self.new_cursor_pos.2..self.length {
+ if let Some(env_hash) = self.get_env_under_cursor(c) {
+ self.rows.update_selection_with_env(
+ env_hash,
+ match modifier {
+ Modifier::SymmetricDifference => |e: &mut bool| *e = !*e,
+ Modifier::Union => |e: &mut bool| *e = true,
+ Modifier::Difference => |e: &mut bool| *e = false,
+ Modifier::Intersection => |_: &mut bool| {},
+ },
+ );
+ }
+ }
+ if modifier == Modifier::Intersection {
+ for c in 0..self.new_cursor_pos.2 {
+ if let Some(env_hash) = self.get_env_under_cursor(c) {
+ self.rows
+ .update_selection_with_env(env_hash, |e| *e = false);
+ }
+ }
+ }
+ }
+ }
+ }
+ //self.force_draw = true;
+ }
+
+ if !self.rows.row_updates.is_empty() {
+ let page_no = (self.new_cursor_pos.2).wrapping_div(rows);
+ let top_idx = page_no * rows;
+
+ while let Some(row) = self.rows.row_updates.pop() {
+ let row: usize = self.rows.env_order[&row];
- let top_idx = page_no * rows;
if row >= top_idx && row <= top_idx + rows {
- let area = (
+ let new_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);
+ self.highlight_line(grid, new_area, row, context);
+ context.dirty_areas.push_back(new_area);
}
}
}
- */
+
if !self.is_dirty() {
return;
}
@@ -1180,14 +1373,12 @@ impl Component for ThreadListing {
/* Mark message as read */
let must_highlight = {
- if self.length == 0 {
- false
- } else {
+ if let Some(env_hash) = self.get_env_under_cursor(idx) {
let account = &context.accounts[&self.cursor_pos.0];
- let envelope: EnvelopeRef = account
- .collection
- .get_env(self.get_env_under_cursor(idx, context));
+ let envelope: EnvelopeRef = account.collection.get_env(env_hash);
envelope.is_seen()
+ } else {
+ false
}
};
@@ -1221,7 +1412,6 @@ impl Component for ThreadListing {
.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() {
@@ -1230,16 +1420,14 @@ impl Component for ThreadListing {
return;
}
- let coordinates = (
- self.cursor_pos.0,
- self.cursor_pos.1,
- self.get_env_under_cursor(self.cursor_pos.2, context),
- );
+ if let Some(env_hash) = self.get_env_under_cursor(self.cursor_pos.2) {
+ let coordinates = (self.cursor_pos.0, self.cursor_pos.1, env_hash);
- if let Some(ref mut v) = self.view {
- v.update(coordinates, context);
- } else {
- self.view = Some(Box::new(MailView::new(coordinates, None, None, context)));
+ if let Some(ref mut v) = self.view {
+ v.update(coordinates, context);
+ } else {
+ self.view = Some(Box::new(MailView::new(coordinates, None, None, context)));
+ }
}
if let Some(v) = self.view.as_mut() {
@@ -1369,10 +1557,9 @@ impl Component for ThreadListing {
if !account.collection.contains_key(new_hash) {
return false;
}
- if let Some(row) = self.order.remove(old_hash) {
- self.order.insert(*new_hash, row);
- (self.rows[row].0).3 = *new_hash;
- //self.row_updates.push(old_hash);
+ self.rows.rename_env(*old_hash, *new_hash);
+ if let Some(&row) = self.rows.env_order.get(new_hash) {
+ (self.rows.entries[row].0).3 = *new_hash;
}
self.dirty = true;
@@ -1387,7 +1574,7 @@ impl Component for ThreadListing {
}
}
UIEvent::EnvelopeRemove(ref env_hash, _) => {
- if self.order.contains_key(env_hash) {
+ if self.rows.contains_env(*env_hash) {
self.refresh_mailbox(context, false);
self.set_dirty(true);
}
@@ -1397,8 +1584,8 @@ impl Component for ThreadListing {
if !account.collection.contains_key(env_hash) {
return false;
}
- if self.order.contains_key(env_hash) {
- //self.row_updates.push(*env_hash);
+ if self.rows.contains_env(*env_hash) {
+ self.rows.row_updates.push(*env_hash);
}
self.dirty = true;
@@ -1415,6 +1602,34 @@ impl Component for ThreadListing {
UIEvent::Resize => {
self.dirty = true;
}
+ UIEvent::Input(Key::Esc)
+ if !self.unfocused()
+ && self
+ .rows
+ .selection
+ .values()
+ .cloned()
+ .any(std::convert::identity) =>
+ {
+ for v in self.rows.selection.values_mut() {
+ *v = false;
+ }
+ self.dirty = true;
+ return true;
+ }
+ UIEvent::Input(ref key)
+ if !self.unfocused()
+ && shortcut!(key == shortcuts[Listing::DESCRIPTION]["select_entry"]) =>
+ {
+ if self.modifier_active && self.modifier_command.is_none() {
+ self.modifier_command = Some(Modifier::default());
+ } else {
+ if let Some(env_hash) = self.get_env_under_cursor(self.cursor_pos.2) {
+ self.rows.update_selection_with_env(env_hash, |e| *e = !*e);
+ }
+ }
+ return true;
+ }
UIEvent::Action(ref action) => match action {
Action::SubSort(field, order) => {
debug!("SubSort {:?} , {:?}", field, order);