mail/listing.rs: add RowsState struct
Keep state of rows in lists in this struct to reduce code duplication in list implementationspull/168/head
parent
b776409d6c
commit
cc439b239a
|
@ -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());
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue