diff --git a/meli/src/mail/listing.rs b/meli/src/mail/listing.rs index 1867d662..e043c29e 100644 --- a/meli/src/mail/listing.rs +++ b/meli/src/mail/listing.rs @@ -233,8 +233,8 @@ impl RowsState { //mod conversations; //pub use self::conversations::*; -//mod compact; -//pub use self::compact::*; +mod compact; +pub use self::compact::*; //mod thread; //pub use self::thread::*; @@ -922,7 +922,7 @@ pub trait ListingTrait: Component { #[derive(Debug)] pub enum ListingComponent { - //Compact(Box), + Compact(Box), //Conversations(Box), Offline(Box), Plain(Box), @@ -935,7 +935,7 @@ impl std::ops::Deref for ListingComponent { fn deref(&self) -> &Self::Target { match &self { - //Compact(ref l) => l.as_ref(), + Compact(ref l) => l.as_ref(), //Conversations(ref l) => l.as_ref(), Offline(ref l) => l.as_ref(), Plain(ref l) => l.as_ref(), @@ -947,7 +947,7 @@ impl std::ops::Deref for ListingComponent { impl std::ops::DerefMut for ListingComponent { fn deref_mut(&mut self) -> &mut (dyn MailListingTrait + 'static) { match self { - //Compact(l) => l.as_mut(), + Compact(l) => l.as_mut(), //Conversations(l) => l.as_mut(), Offline(l) => l.as_mut(), Plain(l) => l.as_mut(), @@ -959,7 +959,7 @@ impl std::ops::DerefMut for ListingComponent { impl ListingComponent { fn id(&self) -> ComponentId { match self { - //Compact(l) => l.as_component().id(), + Compact(l) => l.as_component().id(), //Conversations(l) => l.as_component().id(), Offline(l) => l.as_component().id(), Plain(l) => l.as_component().id(), @@ -1036,7 +1036,7 @@ pub struct Listing { impl std::fmt::Display for Listing { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self.component { - //Compact(ref l) => write!(f, "{}", l), + Compact(ref l) => write!(f, "{}", l), //Conversations(ref l) => write!(f, "{}", l), Offline(ref l) => write!(f, "{}", l), Plain(ref l) => write!(f, "{}", l), @@ -2439,7 +2439,7 @@ impl Component for Listing { ret.insert( self.component.id(), match &self.component { - //Compact(l) => l.as_component(), + Compact(l) => l.as_component(), //Conversations(l) => l.as_component(), Offline(l) => l.as_component(), Plain(l) => l.as_component(), @@ -2455,7 +2455,7 @@ impl Component for Listing { ret.insert( self.component.id(), match &mut self.component { - //Compact(l) => l.as_component_mut(), + Compact(l) => l.as_component_mut(), //Conversations(l) => l.as_component_mut(), Offline(l) => l.as_component_mut(), Plain(l) => l.as_component_mut(), @@ -3091,61 +3091,54 @@ impl Listing { !matches!(self.component.focus(), Focus::EntryFullscreen) && self.menu_visibility } - fn set_style(&mut self, _new_style: IndexStyle, context: &mut Context) { - if matches!(self.component, Plain(_)) { - return; - } - let coordinates = self.component.coordinates(); - self.component = Plain(PlainListing::new(self.id, coordinates)); + fn set_style(&mut self, new_style: IndexStyle, context: &mut Context) { + let old = match new_style { + IndexStyle::Conversations | IndexStyle::Threaded | IndexStyle::Plain => { + if matches!(self.component, Plain(_)) { + return; + } + let coordinates = self.component.coordinates(); + std::mem::replace( + &mut self.component, + Plain(PlainListing::new(self.id, coordinates)), + ) + } + //IndexStyle::Threaded => { + // return; + // //if matches!(self.component, Threaded(_)) { + // // return; + // //} + // //let coordinates = self.component.coordinates(); + // //std::mem::replace( + // // &mut self.component, + // // Threaded(ThreadListing::new(self.id, coordinates, + // // context)), //) + //} + IndexStyle::Compact => { + if matches!(self.component, Compact(_)) { + return; + } + let coordinates = self.component.coordinates(); + std::mem::replace( + &mut self.component, + Compact(CompactListing::new(self.id, coordinates)), + ) + } //IndexStyle::Conversations => { + // return; + // //if matches!(self.component, Conversations(_)) { + // // return; + // //} + // //let coordinates = self.component.coordinates(); + // //std::mem::replace( + // // &mut self.component, + // // Conversations(ConversationsListing::new(self.id, + // // coordinates)), + //} + }; self.component .process_event(&mut UIEvent::VisibilityChange(true), context); - //let old = match new_style { - // IndexStyle::Plain => { - // if matches!(self.component, Plain(_)) { - // return; - // } - // let coordinates = self.component.coordinates(); - // std::mem::replace( - // &mut self.component, - // Plain(PlainListing::new(self.id, coordinates)), - // ) - // } - // IndexStyle::Threaded => { - // return; - // //if matches!(self.component, Threaded(_)) { - // // return; - // //} - // //let coordinates = self.component.coordinates(); - // //std::mem::replace( - // // &mut self.component, - // // Threaded(ThreadListing::new(self.id, coordinates, - // context)), //) - // } - // IndexStyle::Compact => { - // return; - // //if matches!(self.component, Compact(_)) { - // // return; - // //} - // //let coordinates = self.component.coordinates(); - // //std::mem::replace( - // // &mut self.component, - // // Compact(CompactListing::new(self.id, coordinates)), - // //) - // } - // IndexStyle::Conversations => { - // return; - // //if matches!(self.component, Conversations(_)) { - // // return; - // //} - // //let coordinates = self.component.coordinates(); - // //std::mem::replace( - // // &mut self.component, - // // Conversations(ConversationsListing::new(self.id, - // coordinates)), //) - // } - //}; - //old.unrealize(context); - //self.component.realize(self.id.into(), context); + old.unrealize(context); + self.component.realize(self.id.into(), context); } } diff --git a/meli/src/mail/listing/compact.rs b/meli/src/mail/listing/compact.rs index bd607c1f..de52565f 100644 --- a/meli/src/mail/listing/compact.rs +++ b/meli/src/mail/listing/compact.rs @@ -19,13 +19,13 @@ * along with meli. If not, see . */ -use std::{cmp, collections::BTreeMap, convert::TryInto, iter::FromIterator}; +use std::{collections::BTreeMap, convert::TryInto, iter::FromIterator}; use indexmap::IndexSet; -use melib::{SortField, SortOrder, TagHash, Threads}; +use melib::{Address, SortField, SortOrder, TagHash, Threads}; use super::*; -use crate::{components::PageMovement, jobs::JoinHandle}; +use crate::{components::PageMovement, jobs::JoinHandle, segment_tree::SegmentTree}; macro_rules! row_attr { ($color_cache:expr, $even: expr, $unseen:expr, $highlighted:expr, $selected:expr $(,)*) => {{ @@ -230,11 +230,10 @@ impl MailListingTrait for CompactListing { match context.accounts[&self.cursor_pos.0].load(self.cursor_pos.1) { Ok(()) => {} Err(_) => { + self.length = 0; let message: String = context.accounts[&self.cursor_pos.0][&self.cursor_pos.1].status(); - self.data_columns.columns[0].resize_with_context(message.len(), 1, context); - self.length = 0; - { + if self.data_columns.columns[0].resize_with_context(message.len(), 1, context) { let area = self.data_columns.columns[0].area(); self.data_columns.columns[0].grid_mut().write_string( message.as_str(), @@ -243,8 +242,8 @@ impl MailListingTrait for CompactListing { self.color_cache.theme_default.attrs, area, None, - ) - }; + ); + } return; } } @@ -265,13 +264,15 @@ impl MailListingTrait for CompactListing { Box::new(roots.into_iter()) as Box>, ); - if !force && old_cursor_pos == self.new_cursor_pos { - self.kick_parent(self.parent, ListingMessage::UpdateView, context); - } else if self.unfocused() { - if let Some((thread_hash, env_hash)) = self - .get_thread_under_cursor(self.cursor_pos.2) - .and_then(|thread| self.rows.thread_to_env.get(&thread).map(|e| (thread, e[0]))) - { + if let Some((thread_hash, env_hash)) = self + .get_thread_under_cursor(self.cursor_pos.2) + .and_then(|thread| self.rows.thread_to_env.get(&thread).map(|e| (thread, e[0]))) + { + if !force && old_cursor_pos == self.new_cursor_pos { + self.kick_parent(self.parent, ListingMessage::UpdateView, context); + } else if self.unfocused() { + self.force_draw = true; + self.dirty = true; self.kick_parent( self.parent, ListingMessage::OpenEntryUnderCursor { @@ -293,9 +294,10 @@ impl MailListingTrait for CompactListing { items: Box>, ) { let account = &context.accounts[&self.cursor_pos.0]; - let threads = account.collection.get_threads(self.cursor_pos.1); + self.rows.clear(); + self.length = 0; // Use account settings only if no sortcmd has been used if !self.sortcmd { self.sort = context.accounts[&self.cursor_pos.0].settings.account.order @@ -350,7 +352,7 @@ impl MailListingTrait for CompactListing { ); log::debug!("{:#?}", context.accounts); - panic!(); + continue; } let root_envelope: EnvelopeRef = context.accounts[&self.cursor_pos.0] .collection @@ -447,24 +449,19 @@ impl MailListingTrait for CompactListing { /* subject */ row_widths.3.push( (entry_strings.flag.grapheme_width() - + 1 + entry_strings.subject.grapheme_width() + 1 - + entry_strings.tags.grapheme_width() - + 16) - .try_into() - .unwrap_or(255), + + entry_strings.tags.grapheme_width()) + .try_into() + .unwrap_or(255), ); - min_width.1 = cmp::max(min_width.1, entry_strings.date.grapheme_width()); /* date */ - min_width.2 = cmp::max(min_width.2, entry_strings.from.grapheme_width()); /* from */ - min_width.3 = cmp::max( - min_width.3, + min_width.1 = min_width.1.max(entry_strings.date.grapheme_width()); /* date */ + min_width.2 = min_width.2.max(entry_strings.from.grapheme_width()); /* from */ + min_width.3 = min_width.3.max( entry_strings.flag.grapheme_width() - + 1 + entry_strings.subject.grapheme_width() + 1 - + entry_strings.tags.grapheme_width() - + 16, + + entry_strings.tags.grapheme_width(), ); /* subject */ self.rows.insert_thread( thread, @@ -484,7 +481,7 @@ impl MailListingTrait for CompactListing { self.data_columns.elasticities[0].set_rigid(); self.data_columns.elasticities[1].set_rigid(); - self.data_columns.elasticities[2].set_grow(5, Some(35)); + self.data_columns.elasticities[2].set_grow(15, Some(35)); self.data_columns.elasticities[3].set_rigid(); self.data_columns .cursor_config @@ -498,17 +495,16 @@ impl MailListingTrait for CompactListing { .set_even_odd_theme(self.color_cache.even, self.color_cache.odd); /* index column */ - self.data_columns.columns[0].resize_with_context(min_width.0, self.rows.len(), context); - self.data_columns.segment_tree[0] = row_widths.0.into(); - + _ = self.data_columns.columns[0].resize_with_context(min_width.0, self.rows.len(), context); /* date column */ - self.data_columns.columns[1].resize_with_context(min_width.1, self.rows.len(), context); - self.data_columns.segment_tree[1] = row_widths.1.into(); + _ = self.data_columns.columns[1].resize_with_context(min_width.1, self.rows.len(), context); /* from column */ - self.data_columns.columns[2].resize_with_context(min_width.2, self.rows.len(), context); - self.data_columns.segment_tree[2] = row_widths.2.into(); + _ = self.data_columns.columns[2].resize_with_context(min_width.2, self.rows.len(), context); /* subject column */ - self.data_columns.columns[3].resize_with_context(min_width.3, self.rows.len(), context); + _ = self.data_columns.columns[3].resize_with_context(min_width.3, self.rows.len(), context); + self.data_columns.segment_tree[0] = row_widths.0.into(); + self.data_columns.segment_tree[1] = row_widths.1.into(); + self.data_columns.segment_tree[2] = row_widths.2.into(); self.data_columns.segment_tree[3] = row_widths.3.into(); self.rows_drawn = SegmentTree::from( @@ -516,16 +512,11 @@ impl MailListingTrait for CompactListing { .take(self.rows.len()) .collect::>(), ); - debug_assert!(self.rows_drawn.array.len() == self.rows.len()); - self.draw_rows( - context, - 0, - std::cmp::min(80, self.rows.len().saturating_sub(1)), - ); + debug_assert_eq!(self.rows_drawn.array.len(), self.rows.len()); + self.draw_rows(context, 0, 80.min(self.rows.len().saturating_sub(1))); if self.length == 0 && self.filter_term.is_empty() { let message: String = account[&self.cursor_pos.1].status(); - self.data_columns.columns[0].resize_with_context(message.len(), 1, context); - { + if self.data_columns.columns[0].resize_with_context(message.len(), 1, context) { let area = self.data_columns.columns[0].area(); self.data_columns.columns[0].grid_mut().write_string( &message, @@ -534,8 +525,8 @@ impl MailListingTrait for CompactListing { self.color_cache.theme_default.attrs, area, None, - ) - }; + ); + } } } } @@ -637,8 +628,6 @@ impl ListingTrait for CompactListing { { self.refresh_mailbox(context, false); } - let upper_left = area.upper_left(); - let bottom_right = area.bottom_right(); if self.length == 0 { grid.clear_area(area, self.color_cache.theme_default); @@ -679,7 +668,20 @@ impl ListingTrait for CompactListing { self.new_cursor_pos.2 = (self.length.saturating_sub(1) / rows) * rows; } } - PageMovement::Right(_) | PageMovement::Left(_) => {} + PageMovement::Right(amount) => { + self.data_columns.x_offset += amount; + self.data_columns.x_offset = self.data_columns.x_offset.min( + self.data_columns + .widths + .iter() + .map(|w| w + 2) + .sum::() + .saturating_sub(2), + ); + } + PageMovement::Left(amount) => { + self.data_columns.x_offset = self.data_columns.x_offset.saturating_sub(amount); + } PageMovement::Home => { self.new_cursor_pos.2 = 0; } @@ -689,11 +691,15 @@ impl ListingTrait for CompactListing { } } + if self.force_draw { + grid.clear_area(area, self.color_cache.theme_default); + } + let prev_page_no = (self.cursor_pos.2).wrapping_div(rows); let page_no = (self.new_cursor_pos.2).wrapping_div(rows); let top_idx = page_no * rows; - let end_idx = cmp::min(self.length.saturating_sub(1), top_idx + rows - 1); + let end_idx = self.length.saturating_sub(1).min(top_idx + rows - 1); self.draw_rows(context, top_idx, end_idx); /* If cursor position has changed, remove the highlight from the previous @@ -727,11 +733,11 @@ impl ListingTrait for CompactListing { self.cursor_pos.2 = self.new_cursor_pos.2; } + if !self.force_draw { + grid.clear_area(area, self.color_cache.theme_default); + } /* Page_no has changed, so draw new page */ - _ = self - .data_columns - .recalc_widths((area.width(), area.height()), top_idx); - grid.clear_area(area, self.color_cache.theme_default); + _ = self.data_columns.recalc_widths(area.size(), top_idx); /* copy table columns */ self.data_columns .draw(grid, top_idx, self.cursor_pos.2, grid.bounds_iter(area)); @@ -754,11 +760,13 @@ impl ListingTrait for CompactListing { /* clear gap if available height is more than count of entries */ if top_idx + rows > self.length { - grid.clear_area( - area.skip_rows(top_idx + rows - self.length), + grid.change_theme( + area.skip_rows(self.length - top_idx), self.color_cache.theme_default, ); } + + self.force_draw = false; context.dirty_areas.push_back(area); } @@ -799,10 +807,9 @@ impl ListingTrait for CompactListing { self.sort, &context.accounts[&self.cursor_pos.0].collection.envelopes, ); - self.new_cursor_pos.2 = - std::cmp::min(self.filtered_selection.len() - 1, self.cursor_pos.2); + self.new_cursor_pos.2 = self.cursor_pos.2.min(self.filtered_selection.len() - 1); } else { - self.data_columns.columns[0].resize_with_context(0, 0, context); + _ = self.data_columns.columns[0].resize_with_context(0, 0, context); } self.redraw_threads_list( context, @@ -891,10 +898,9 @@ impl std::fmt::Display for CompactListing { } impl CompactListing { - pub const DESCRIPTION: &'static str = "compact listing"; pub fn new(parent: ComponentId, coordinates: (AccountHash, MailboxHash)) -> Box { Box::new(CompactListing { - cursor_pos: (coordinates.0, MailboxHash::default(), 0), + cursor_pos: (AccountHash::default(), MailboxHash::default(), 0), new_cursor_pos: (coordinates.0, coordinates.1, 0), length: 0, sort: (Default::default(), Default::default()), @@ -1332,6 +1338,7 @@ impl CompactListing { self.data_columns.columns[3].area().width(), ); + let columns = &mut self.data_columns.columns; for (idx, ((_thread_hash, root_env_hash), strings)) in self .rows .entries @@ -1349,12 +1356,12 @@ impl CompactListing { //); //debug!("{:#?}", context.accounts); - panic!(); + continue; } let row_attr = self.rows.row_attr_cache[&idx]; let (x, _) = { - let area = self.data_columns.columns[0].area().nth_row(idx); - self.data_columns.columns[0].grid_mut().write_string( + let area = columns[0].area().nth_row(idx); + columns[0].grid_mut().write_string( &idx.to_string(), row_attr.fg, row_attr.bg, @@ -1363,14 +1370,17 @@ impl CompactListing { None, ) }; - for x in x..min_width.0 { - self.data_columns.columns[0].grid_mut()[(x, idx)] + for c in { + let area = columns[0].area(); + columns[0].grid_mut().row_iter(area, x..min_width.0, idx) + } { + columns[0].grid_mut()[c] .set_bg(row_attr.bg) .set_attrs(row_attr.attrs); } let (x, _) = { - let area = self.data_columns.columns[1].area().nth_row(idx); - self.data_columns.columns[1].grid_mut().write_string( + let area = columns[1].area().nth_row(idx); + columns[1].grid_mut().write_string( &strings.date, row_attr.fg, row_attr.bg, @@ -1379,14 +1389,17 @@ impl CompactListing { None, ) }; - for x in x..min_width.1 { - self.data_columns.columns[1].grid_mut()[(x, idx)] + for c in { + let area = columns[1].area(); + columns[1].grid_mut().row_iter(area, x..min_width.1, idx) + } { + columns[1].grid_mut()[c] .set_bg(row_attr.bg) .set_attrs(row_attr.attrs); } let (x, _) = { - let area = self.data_columns.columns[2].area().nth_row(idx); - self.data_columns.columns[2].grid_mut().write_string( + let area = columns[2].area().nth_row(idx); + columns[2].grid_mut().write_string( &strings.from, row_attr.fg, row_attr.bg, @@ -1395,29 +1408,27 @@ impl CompactListing { None, ) }; + for c in { + let area = columns[2].area(); + columns[2].grid_mut().row_iter(area, x..min_width.2, idx) + } { + columns[2].grid_mut()[c] + .set_bg(row_attr.bg) + .set_attrs(row_attr.attrs) + .set_ch(' '); + } #[cfg(feature = "regexp")] { for text_formatter in crate::conf::text_format_regexps(context, "listing.from") { - let t = self.data_columns.columns[2] - .grid_mut() - .insert_tag(text_formatter.tag); + let t = columns[2].grid_mut().insert_tag(text_formatter.tag); for (start, end) in text_formatter.regexp.find_iter(strings.from.as_str()) { - self.data_columns.columns[2].grid_mut().set_tag( - t, - (start, idx), - (end, idx), - ); + columns[2].grid_mut().set_tag(t, (start, idx), (end, idx)); } } } - for x in x..min_width.2 { - self.data_columns.columns[2].grid_mut()[(x, idx)] - .set_bg(row_attr.bg) - .set_attrs(row_attr.attrs); - } let (x, _) = { - let area = self.data_columns.columns[3].area().nth_row(idx); - self.data_columns.columns[3].grid_mut().write_string( + let area = columns[3].area().nth_row(idx); + columns[3].grid_mut().write_string( &strings.flag, row_attr.fg, row_attr.bg, @@ -1426,69 +1437,92 @@ impl CompactListing { None, ) }; - let (x, _) = self.data_columns.columns[3].grid_mut().write_string( - &strings.subject, - row_attr.fg, - row_attr.bg, - row_attr.attrs, - self.data_columns.columns[3] - .area() - .nth_row(idx) - .skip_cols(x), - None, - ); + let x = { + let area = columns[3].area().nth_row(idx).skip_cols(x); + columns[3] + .grid_mut() + .write_string( + &strings.subject, + row_attr.fg, + row_attr.bg, + row_attr.attrs, + area, + None, + ) + .0 + + x + }; #[cfg(feature = "regexp")] { for text_formatter in crate::conf::text_format_regexps(context, "listing.subject") { - let t = self.data_columns.columns[3] - .grid_mut() - .insert_tag(text_formatter.tag); + let t = columns[3].grid_mut().insert_tag(text_formatter.tag); for (start, end) in text_formatter.regexp.find_iter(strings.subject.as_str()) { - self.data_columns.columns[3].grid_mut().set_tag( - t, - (start, idx), - (end, idx), - ); + columns[3].grid_mut().set_tag(t, (start, idx), (end, idx)); } } } - let x = { - let mut x = x + 1; - for (t, &color) in strings.tags.split_whitespace().zip(strings.tags.1.iter()) { - let color = color.unwrap_or(self.color_cache.tag_default.bg); - let (_x, _) = self.data_columns.columns[3].grid_mut().write_string( - t, - self.color_cache.tag_default.fg, - color, - self.color_cache.tag_default.attrs, - self.data_columns.columns[3] - .area() - .nth_row(idx) - .skip_cols(x + 1), - None, - ); - self.data_columns.columns[3].grid_mut()[(x, idx)].set_bg(color); - if _x < min_width.3 { - self.data_columns.columns[3].grid_mut()[(_x, idx)] - .set_bg(color) - .set_keep_bg(true); - } - for x in (x + 1).._x { - self.data_columns.columns[3].grid_mut()[(x, idx)] - .set_keep_fg(true) - .set_keep_bg(true) - .set_keep_attrs(true); - } - self.data_columns.columns[3].grid_mut()[(x, idx)].set_keep_bg(true); - x = _x + 1; + 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 = { + let area = columns[3].area().nth_row(idx).skip_cols(x + 1); + columns[3] + .grid_mut() + .write_string( + t, + self.color_cache.tag_default.fg, + color, + self.color_cache.tag_default.attrs, + area, + None, + ) + .0 + + x + + 1 + }; + for c in { + let area = columns[3].area(); + columns[3].grid_mut().row_iter(area, x..(x + 1), idx) + } { + columns[3].grid_mut()[c].set_bg(color); } - x - }; - for x in x..min_width.3 { - self.data_columns.columns[3].grid_mut()[(x, idx)] - .set_ch(' ') - .set_bg(row_attr.bg) - .set_attrs(row_attr.attrs); + for c in { + let area = columns[3].area(); + columns[3].grid_mut().row_iter(area, _x..(_x + 1), idx) + } { + columns[3].grid_mut()[c].set_bg(color).set_keep_bg(true); + } + for c in { + let area = columns[3].area(); + columns[3].grid_mut().row_iter(area, (x + 1)..(_x + 1), idx) + } { + columns[3].grid_mut()[c] + .set_keep_fg(true) + .set_keep_bg(true) + .set_keep_attrs(true); + } + for c in { + let area = columns[3].area(); + columns[3].grid_mut().row_iter(area, x..(x + 1), idx) + } { + columns[3].grid_mut()[c].set_keep_bg(true); + } + x = _x + 2; + } + } + if self.length == 0 && self.filter_term.is_empty() { + let account = &context.accounts[&self.cursor_pos.0]; + let message: String = account[&self.cursor_pos.1].status(); + if self.data_columns.columns[0].resize_with_context(message.len(), 1, context) { + let area = self.data_columns.columns[0].area(); + self.data_columns.columns[0].grid_mut().write_string( + message.as_str(), + self.color_cache.theme_default.fg, + self.color_cache.theme_default.bg, + self.color_cache.theme_default.attrs, + area, + None, + ); } } } @@ -1550,7 +1584,7 @@ impl Component for CompactListing { return; } - if !self.unfocused() { + if matches!(self.focus, Focus::None) { let mut area = area; if !self.filter_term.is_empty() { let (x, y) = grid.write_string( @@ -1563,24 +1597,15 @@ impl Component for CompactListing { self.color_cache.theme_default.bg, self.color_cache.theme_default.attrs, area, - None, + Some(0), ); - let default_cell = { - let mut ret = Cell::with_char(' '); - ret.set_fg(self.color_cache.theme_default.fg) - .set_bg(self.color_cache.theme_default.bg) - .set_attrs(self.color_cache.theme_default.attrs); - ret - }; - for row in grid.bounds_iter(area.nth_row(y).skip_cols(x)) { - for c in row { - grid[c] = default_cell; - } - } + + grid.clear_area(area.skip(x, y).nth_row(y), self.color_cache.theme_default); context.dirty_areas.push_back(area); area = area.skip_rows(y + 1); } + let rows = area.height(); if let Some(modifier) = self.modifier_command.take() { @@ -1633,8 +1658,8 @@ impl Component for CompactListing { } } PageMovement::Down(amount) => { - for c in self.cursor_pos.2 - ..std::cmp::min(self.length, self.cursor_pos.2 + amount + 1) + for c in + self.cursor_pos.2..self.length.min(self.cursor_pos.2 + amount + 1) { if let Some(thread) = self.get_thread_under_cursor(c) { self.rows.update_selection_with_thread( @@ -1652,8 +1677,7 @@ impl Component for CompactListing { } if modifier == Modifier::Intersection { for c in (0..self.cursor_pos.2).chain( - (std::cmp::min(self.length, self.cursor_pos.2 + amount) + 1) - ..self.length, + self.length.min(self.cursor_pos.2 + amount) + 1..self.length, ) { if let Some(thread) = self.get_thread_under_cursor(c) { self.rows @@ -1664,10 +1688,7 @@ impl Component for CompactListing { } PageMovement::PageDown(multiplier) => { for c in self.cursor_pos.2 - ..std::cmp::min( - self.cursor_pos.2 + rows * multiplier + 1, - self.length, - ) + ..self.length.min(self.cursor_pos.2 + rows * multiplier + 1) { if let Some(thread) = self.get_thread_under_cursor(c) { self.rows.update_selection_with_thread( @@ -1685,10 +1706,8 @@ impl Component for CompactListing { } if modifier == Modifier::Intersection { for c in (0..self.cursor_pos.2).chain( - (std::cmp::min( - self.cursor_pos.2 + rows * multiplier, - self.length, - ) + 1)..self.length, + self.length.min(self.cursor_pos.2 + rows * multiplier) + 1 + ..self.length, ) { if let Some(thread) = self.get_thread_under_cursor(c) { self.rows @@ -1772,13 +1791,11 @@ impl Component for CompactListing { self.draw_list(grid, area, context); } } else { + self.view_area = area.into(); if self.length == 0 && self.dirty { grid.clear_area(area, self.color_cache.theme_default); context.dirty_areas.push_back(area); - return; } - - self.view_area = area.into(); } self.dirty = false; } @@ -1787,6 +1804,11 @@ impl Component for CompactListing { let shortcuts = self.shortcuts(context); match (&event, self.focus) { + (UIEvent::VisibilityChange(true), _) => { + self.force_draw = true; + self.set_dirty(true); + return true; + } (UIEvent::Input(ref k), Focus::Entry) if shortcut!(k == shortcuts[Shortcuts::LISTING]["focus_right"]) => { @@ -2019,6 +2041,7 @@ impl Component for CompactListing { } }; self.set_dirty(true); + return true; } UIEvent::Action(Action::Listing(Select(ref search_term))) if !self.unfocused() => { match context.accounts[&self.cursor_pos.0].search( @@ -2046,6 +2069,7 @@ impl Component for CompactListing { } }; self.set_dirty(true); + return true; } UIEvent::StatusEvent(StatusEvent::JobFinished(ref job_id)) if self @@ -2090,11 +2114,11 @@ impl Component for CompactListing { } fn is_dirty(&self) -> bool { - match self.focus { - Focus::None => self.dirty, - Focus::Entry => self.dirty, - Focus::EntryFullscreen => false, - } + self.force_draw + || match self.focus { + Focus::None | Focus::Entry => self.dirty, + Focus::EntryFullscreen => false, + } } fn set_dirty(&mut self, value: bool) { diff --git a/meli/src/mail/listing/plain.rs b/meli/src/mail/listing/plain.rs index f6460297..f2633ca0 100644 --- a/meli/src/mail/listing/plain.rs +++ b/meli/src/mail/listing/plain.rs @@ -19,7 +19,7 @@ * along with meli. If not, see . */ -use std::{cmp, iter::FromIterator}; +use std::iter::FromIterator; use melib::{Address, SortField, SortOrder, ThreadNode}; @@ -141,6 +141,8 @@ pub struct PlainListing { #[allow(clippy::type_complexity)] search_job: Option<(String, JoinHandle>>)>, + #[allow(clippy::type_complexity)] + select_job: Option<(String, JoinHandle>>)>, filter_term: String, filtered_selection: Vec, filtered_order: HashMap, @@ -286,6 +288,7 @@ impl MailListingTrait for PlainListing { }, context, ); + self.set_focus(Focus::Entry, context); } } } @@ -359,9 +362,7 @@ impl ListingTrait for PlainListing { } fn highlight_line(&mut self, grid: &mut CellBuffer, area: Area, idx: usize, context: &Context) { - let i = if let Some(i) = self.get_env_under_cursor(idx) { - i - } else { + let Some(i) = self.get_env_under_cursor(idx) else { // self.length == 0 return; }; @@ -579,8 +580,7 @@ impl ListingTrait for PlainListing { } } if !self.filtered_selection.is_empty() { - self.new_cursor_pos.2 = - std::cmp::min(self.filtered_selection.len() - 1, self.cursor_pos.2); + self.new_cursor_pos.2 = self.cursor_pos.2.min(self.filtered_selection.len() - 1); } else { _ = self.data_columns.columns[0].resize_with_context(0, 0, context); } @@ -683,6 +683,7 @@ impl PlainListing { local_collection: Vec::new(), filter_term: String::new(), search_job: None, + select_job: None, filtered_selection: Vec::new(), filtered_order: HashMap::default(), data_columns: DataColumns::default(), @@ -824,7 +825,7 @@ impl PlainListing { ); log::debug!("{:#?}", context.accounts); - panic!(); + continue; } let envelope: EnvelopeRef = context.accounts[&self.cursor_pos.0].collection.get_env(i); use melib::search::QueryTrait; @@ -849,6 +850,9 @@ impl PlainListing { self.rows.row_attr_cache.insert(self.length, row_attr); let entry_strings = self.make_entry_string(&envelope, context); + row_widths + .0 + .push(digits_of_num!(self.length).try_into().unwrap_or(255)); row_widths.1.push( entry_strings .date @@ -871,10 +875,9 @@ impl PlainListing { .try_into() .unwrap_or(255), ); - min_width.1 = cmp::max(min_width.1, entry_strings.date.grapheme_width()); /* date */ - min_width.2 = cmp::max(min_width.2, entry_strings.from.grapheme_width()); /* from */ - min_width.3 = cmp::max( - min_width.3, + min_width.1 = min_width.1.max(entry_strings.date.grapheme_width()); /* date */ + min_width.2 = min_width.2.max(entry_strings.from.grapheme_width()); /* from */ + min_width.3 = min_width.3.max( entry_strings.flag.grapheme_width() + entry_strings.subject.grapheme_width() + 1 @@ -889,9 +892,6 @@ impl PlainListing { self.length += 1; } - row_widths - .0 - .push(digits_of_num!(self.length).try_into().unwrap_or(255)); min_width.0 = self.length.saturating_sub(1).to_string().len(); @@ -1276,6 +1276,51 @@ impl PlainListing { } *self.rows.entries.get_mut(idx).unwrap() = ((thread_hash, env_hash), strings); } + + fn select( + &mut self, + search_term: &str, + results: Result>, + context: &mut Context, + ) { + let account = &context.accounts[&self.cursor_pos.0]; + match results { + Ok(results) => { + let threads = account.collection.get_threads(self.cursor_pos.1); + for env_hash in results { + if !account.collection.contains_key(&env_hash) { + continue; + } + let env_thread_node_hash = account.collection.get_env(env_hash).thread(); + if !threads.thread_nodes.contains_key(&env_thread_node_hash) { + continue; + } + let thread = + threads.find_group(threads.thread_nodes[&env_thread_node_hash].group); + if self.rows.all_threads.contains(&thread) { + self.rows + .selection + .entry(env_hash) + .and_modify(|entry| *entry = true); + } + } + } + Err(err) => { + self.cursor_pos.2 = 0; + self.new_cursor_pos.2 = 0; + let message = format!( + "Encountered an error while searching for `{}`: {}.", + search_term, &err + ); + log::error!("{}", message); + context.replies.push_back(UIEvent::Notification( + Some("Could not perform search".to_string()), + message, + Some(crate::types::NotificationType::Error(err.kind)), + )); + } + } + } } impl Component for PlainListing { @@ -1363,8 +1408,8 @@ impl Component for PlainListing { } } PageMovement::Down(amount) => { - for c in self.cursor_pos.2 - ..std::cmp::min(self.length, self.cursor_pos.2 + amount + 1) + for c in + self.cursor_pos.2..self.length.min(self.cursor_pos.2 + amount + 1) { if let Some(env_hash) = self.get_env_under_cursor(c) { self.rows.update_selection_with_env( @@ -1382,8 +1427,7 @@ impl Component for PlainListing { } if modifier == Modifier::Intersection { for c in (0..self.cursor_pos.2).chain( - (std::cmp::min(self.length, self.cursor_pos.2 + amount) + 1) - ..self.length, + self.length.min(self.cursor_pos.2 + amount) + 1..self.length, ) { if let Some(env_hash) = self.get_env_under_cursor(c) { self.rows @@ -1394,7 +1438,7 @@ impl Component for PlainListing { } PageMovement::PageDown(multiplier) => { for c in self.cursor_pos.2 - ..std::cmp::min(self.cursor_pos.2 + rows * multiplier, self.length) + ..self.length.min(self.cursor_pos.2 + rows * multiplier) { if let Some(env_hash) = self.get_env_under_cursor(c) { self.rows.update_selection_with_env( @@ -1412,10 +1456,8 @@ impl Component for PlainListing { } if modifier == Modifier::Intersection { for c in (0..self.cursor_pos.2).chain( - (std::cmp::min( - self.cursor_pos.2 + rows * multiplier, - self.length, - ) + 1)..self.length, + self.length.min(self.cursor_pos.2 + rows * multiplier) + 1 + ..self.length, ) { if let Some(env_hash) = self.get_env_under_cursor(c) { self.rows @@ -1514,8 +1556,6 @@ impl Component for PlainListing { if self.length == 0 && self.dirty { grid.clear_area(area, self.color_cache.theme_default); context.dirty_areas.push_back(area); - self.dirty = false; - return; } } self.dirty = false; @@ -1724,6 +1764,35 @@ impl Component for PlainListing { } }; self.set_dirty(true); + return true; + } + UIEvent::Action(Action::Listing(Select(ref search_term))) if !self.unfocused() => { + match context.accounts[&self.cursor_pos.0].search( + search_term, + self.sort, + self.cursor_pos.1, + ) { + Ok(job) => { + let mut handle = context.accounts[&self.cursor_pos.0] + .main_loop_handler + .job_executor + .spawn_specialized("select_by_search".into(), job); + if let Ok(Some(search_result)) = try_recv_timeout!(&mut handle.chan) { + self.select(search_term, search_result, context); + } else { + self.select_job = Some((search_term.to_string(), handle)); + } + } + Err(err) => { + context.replies.push_back(UIEvent::Notification( + Some("Could not perform search".to_string()), + err.to_string(), + Some(crate::types::NotificationType::Error(err.kind)), + )); + } + }; + self.set_dirty(true); + return true; } UIEvent::StatusEvent(StatusEvent::JobFinished(ref job_id)) if self @@ -1747,6 +1816,21 @@ impl Component for PlainListing { } self.set_dirty(true); } + UIEvent::StatusEvent(StatusEvent::JobFinished(ref job_id)) + if self + .select_job + .as_ref() + .map(|(_, j)| j == job_id) + .unwrap_or(false) => + { + let (search_term, mut handle) = self.select_job.take().unwrap(); + match handle.chan.try_recv() { + Err(_) => { /* search was canceled */ } + Ok(None) => { /* something happened, perhaps a worker thread panicked */ } + Ok(Some(results)) => self.select(&search_term, results, context), + } + self.set_dirty(true); + } _ => {} } false @@ -1755,8 +1839,8 @@ impl Component for PlainListing { fn is_dirty(&self) -> bool { self.force_draw || match self.focus { - Focus::None => self.dirty, - Focus::Entry | Focus::EntryFullscreen => false, + Focus::None | Focus::Entry => self.dirty, + Focus::EntryFullscreen => false, } } diff --git a/meli/src/terminal/cells.rs b/meli/src/terminal/cells.rs index 2a9a0882..1d537dc5 100644 --- a/meli/src/terminal/cells.rs +++ b/meli/src/terminal/cells.rs @@ -95,7 +95,7 @@ impl std::fmt::Debug for CellBuffer { } impl CellBuffer { - pub const MAX_SIZE: usize = 1_000_000; + pub const MAX_SIZE: usize = 100_000_000; pub fn nil(area: Area) -> Self { Self {