diff --git a/melib/src/structs.rs b/melib/src/structs.rs index a6073c4d..f4d1b427 100644 --- a/melib/src/structs.rs +++ b/melib/src/structs.rs @@ -21,6 +21,7 @@ use std::iter::{Extend, FromIterator}; use std::ops::Index; +use std::ops::IndexMut; const STACK_VEC_CAPACITY: usize = 32; #[derive(Debug, Clone, Default, Serialize, Deserialize)] @@ -154,6 +155,16 @@ impl Index for StackVec { } } +impl IndexMut for StackVec { + fn index_mut(&mut self, idx: usize) -> &mut T { + if self.len > self.array.len() { + &mut self.heap_vec[idx] + } else { + &mut self.array[idx] + } + } +} + impl Extend for StackVec { fn extend(&mut self, iter: I) where diff --git a/ui/src/components/mail/listing.rs b/ui/src/components/mail/listing.rs index 176fe41b..72634796 100644 --- a/ui/src/components/mail/listing.rs +++ b/ui/src/components/mail/listing.rs @@ -20,6 +20,7 @@ */ use super::*; +use crate::types::segment_tree::SegmentTree; mod conversations; pub use self::conversations::*; @@ -37,6 +38,7 @@ pub use self::plain::*; pub struct DataColumns { pub columns: [CellBuffer; 12], pub widths: [usize; 12], // widths of columns calculated in first draw and after size changes + pub segment_tree: [SegmentTree; 12], } #[derive(Debug)] diff --git a/ui/src/components/mail/listing/compact.rs b/ui/src/components/mail/listing/compact.rs index 2aa2f678..89015609 100644 --- a/ui/src/components/mail/listing/compact.rs +++ b/ui/src/components/mail/listing/compact.rs @@ -23,6 +23,7 @@ use super::EntryStrings; use super::*; use crate::components::utilities::PageMovement; use std::cmp; +use std::convert::TryInto; use std::iter::FromIterator; const MAX_COLS: usize = 500; @@ -294,6 +295,29 @@ impl ListingTrait for CompactListing { self.data_columns.widths[2] = min_col_width; } } + for &i in &[2, 4] { + /* Set From and Subject column widths to their maximum value width in the range + * [top_idx, top_idx + rows]. By using a segment tree the query is O(logn), which is + * great! + */ + self.data_columns.widths[i] = + self.data_columns.segment_tree[i].get_max(top_idx, top_idx + rows) as usize; + } + if self.data_columns.widths.iter().fold(0, |acc, &w| acc + w) > width { + let diff = self.data_columns.widths.iter().fold(0, |acc, &w| acc + w) - width; + if self.data_columns.widths[2] > 2 * diff { + self.data_columns.widths[2] -= diff; + } else { + self.data_columns.widths[2] = std::cmp::max( + 15, + self.data_columns.widths[2].saturating_sub((2 * diff) / 3), + ); + self.data_columns.widths[4] = std::cmp::max( + 15, + self.data_columns.widths[4].saturating_sub(diff / 3 + diff % 3), + ); + } + } clear_area(grid, area); /* Page_no has changed, so draw new page */ let mut x = get_x(upper_left); @@ -656,6 +680,19 @@ impl CompactListing { self.length = 0; let mut rows = Vec::with_capacity(1024); let mut min_width = (0, 0, 0, 0, 0); + let mut row_widths: ( + StackVec, + StackVec, + StackVec, + StackVec, + StackVec, + ) = ( + StackVec::new(), + StackVec::new(), + StackVec::new(), + StackVec::new(), + StackVec::new(), + ); threads.sort_by(self.sort, self.subsort, &account.collection.envelopes); @@ -700,6 +737,32 @@ impl CompactListing { thread_node, threads.is_snoozed(root_idx), ); + row_widths.1.push( + entry_strings + .date + .grapheme_width() + .try_into() + .unwrap_or(255), + ); /* date */ + row_widths.2.push( + entry_strings + .from + .grapheme_width() + .try_into() + .unwrap_or(255), + ); /* from */ + row_widths.3.push( + entry_strings + .flag + .grapheme_width() + .try_into() + .unwrap_or(255), + ); /* flags */ + row_widths.4.push( + (entry_strings.subject.grapheme_width() + 1 + entry_strings.tags.grapheme_width()) + .try_into() + .unwrap_or(255), + ); min_width.1 = cmp::max(min_width.1, entry_strings.date.grapheme_width()); /* date */ min_width.2 = cmp::max(min_width.2, entry_strings.from.grapheme_width()); /* from */ min_width.3 = cmp::max(min_width.3, entry_strings.flag.grapheme_width()); /* flags */ @@ -721,18 +784,21 @@ impl CompactListing { /* index column */ self.data_columns.columns[0] = CellBuffer::new_with_context(min_width.0, rows.len(), Cell::with_char(' '), context); + /* date column */ self.data_columns.columns[1] = CellBuffer::new_with_context(min_width.1, rows.len(), Cell::with_char(' '), context); /* from column */ self.data_columns.columns[2] = CellBuffer::new_with_context(min_width.2, rows.len(), Cell::with_char(' '), 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(), Cell::with_char(' '), context); /* subject column */ self.data_columns.columns[4] = CellBuffer::new_with_context(min_width.4, rows.len(), Cell::with_char(' '), context); + self.data_columns.segment_tree[4] = row_widths.4.into(); let threads_iter = if self.filter_term.is_empty() { Box::new(threads.root_iter()) as Box> } else { diff --git a/ui/src/types.rs b/ui/src/types.rs index b3ed0d0b..f5d9344d 100644 --- a/ui/src/types.rs +++ b/ui/src/types.rs @@ -147,3 +147,97 @@ pub struct Notification { _timestamp: std::time::Instant, } + +pub mod segment_tree { + /*! Simple segment tree implementation for maximum in range queries. This is useful if given an + * array of numbers you want to get the maximum value inside an interval quickly. + */ + use melib::StackVec; + use std::convert::TryFrom; + use std::iter::FromIterator; + + #[derive(Default, Debug, Clone)] + pub struct SegmentTree { + array: StackVec, + tree: StackVec, + } + + impl From> for SegmentTree { + fn from(val: StackVec) -> SegmentTree { + SegmentTree::new(val) + } + } + + impl SegmentTree { + pub fn new(val: StackVec) -> SegmentTree { + if val.is_empty() { + return SegmentTree { + array: val.clone(), + tree: val, + }; + } + + let height = (f64::from(u32::try_from(val.len()).unwrap_or(0))) + .log2() + .ceil() as u32; + let max_size = 2 * (2_usize.pow(height)) - 1; + + let mut segment_tree: StackVec = + StackVec::from_iter(core::iter::repeat(0).take(max_size)); + for i in 0..val.len() { + segment_tree[val.len() + i] = val[i]; + } + + for i in (1..val.len()).rev() { + segment_tree[i] = std::cmp::max(segment_tree[2 * i], segment_tree[2 * i + 1]); + } + + SegmentTree { + array: val, + tree: segment_tree, + } + } + + /// (left, right) is inclusive + pub fn get_max(&self, mut left: usize, mut right: usize) -> u8 { + let len = self.array.len(); + debug_assert!(left <= right); + if right >= len { + right = len.saturating_sub(1); + } + + left += len; + right += len + 1; + + let mut max = 0; + + while left < right { + if (left & 1) > 0 { + max = std::cmp::max(max, self.tree[left]); + left += 1; + } + + if (right & 1) > 0 { + right -= 1; + max = std::cmp::max(max, self.tree[right]); + } + + left /= 2; + right /= 2; + } + max + } + } + + #[test] + fn test_segment_tree() { + let array: StackVec = [9, 1, 17, 2, 3, 23, 4, 5, 6, 37] + .into_iter() + .cloned() + .collect::>(); + let segment_tree = SegmentTree::from(array.clone()); + + assert_eq!(segment_tree.get_max(0, 5), 23); + assert_eq!(segment_tree.get_max(6, 9), 37); + } +}