From 561ba9c87b57e1012ad89bde08506a2beacb7fff Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Sun, 16 Jul 2023 14:13:55 +0300 Subject: [PATCH] listing: add relative_list_indices setting for thread listing --- CHANGELOG.md | 3 + meli/docs/meli.conf.5 | 6 ++ meli/src/components/mail/listing/thread.rs | 98 +++++++++++++++++++--- meli/src/conf/listing.rs | 7 ++ meli/src/conf/overrides.rs | 2 +- 5 files changed, 102 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5441f99d..ccd1d9f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Warn if draft has no subject and no body. They can be disabled with `[composing.disabled_compose_hooks]` setting. +- `e9cd800f` Added support for storing flags locally for NNTP accounts. +- Added options to show relative row numbers in menus and listings to make jumping easier. + `[listing.relative_menu_indices]` and `[listing.relative_list_indices]`. ### Changed diff --git a/meli/docs/meli.conf.5 b/meli/docs/meli.conf.5 index 24f9c070..60f3079b 100644 --- a/meli/docs/meli.conf.5 +++ b/meli/docs/meli.conf.5 @@ -1145,6 +1145,12 @@ Should threads with differentiating Subjects show a list of those subjects on th In threaded listing style, repeat identical From column values within a thread. Not repeating adds empty space in the From column which might result in less visual clutter. .Pq Em "false" \" default value +.It Ic relative_menu_indices Ar bool +Show relative indices in menu mailboxes to quickly help with jumping to them. +.Pq Em true \" default value +.It Ic relative_list_indices Ar bool +Show relative indices in listings to quickly help with jumping to them. +.Pq Em true \" default value .El .Ss Examples of sidebar mailbox tree customization The default values diff --git a/meli/src/components/mail/listing/thread.rs b/meli/src/components/mail/listing/thread.rs index cd3a4438..e02dc6a4 100644 --- a/meli/src/components/mail/listing/thread.rs +++ b/meli/src/components/mail/listing/thread.rs @@ -529,6 +529,9 @@ impl ListingTrait for ThreadListing { if self.cursor_pos.2 != self.new_cursor_pos.2 && prev_page_no == page_no { let old_cursor_pos = self.cursor_pos; self.cursor_pos = self.new_cursor_pos; + if *account_settings!(context[self.cursor_pos.0].listing.relative_list_indices) { + self.draw_relative_numbers(grid, area, top_idx); + } for &(idx, highlight) in &[(old_cursor_pos.2, false), (self.new_cursor_pos.2, true)] { if idx >= self.length { continue; //bounds check @@ -544,6 +547,9 @@ impl ListingTrait for ThreadListing { } context.dirty_areas.push_back(new_area); } + if *account_settings!(context[self.cursor_pos.0].listing.relative_list_indices) { + context.dirty_areas.push_back(area); + } if !self.force_draw { return; } @@ -569,6 +575,9 @@ impl ListingTrait for ThreadListing { /* copy table columns */ self.data_columns .draw(grid, top_idx, self.cursor_pos.2, grid.bounds_iter(area)); + if *account_settings!(context[self.cursor_pos.0].listing.relative_list_indices) { + self.draw_relative_numbers(grid, area, top_idx); + } /* apply each row colors separately */ for i in top_idx..(top_idx + height!(area)) { if let Some(row_attr) = self.rows.row_attr_cache.get(&i) { @@ -907,19 +916,21 @@ impl ThreadListing { panic!(); } let row_attr = self.rows.row_attr_cache[&idx]; - let (x, _) = write_string_to_grid( - &idx.to_string(), - &mut self.data_columns.columns[0], - row_attr.fg, - row_attr.bg, - row_attr.attrs, - ((0, idx), (min_width.0, idx)), - None, - ); - for x in x..min_width.0 { - self.data_columns.columns[0][(x, idx)] - .set_bg(row_attr.bg) - .set_attrs(row_attr.attrs); + if !*account_settings!(context[self.cursor_pos.0].listing.relative_list_indices) { + let (x, _) = write_string_to_grid( + &idx.to_string(), + &mut self.data_columns.columns[0], + row_attr.fg, + row_attr.bg, + row_attr.attrs, + ((0, idx), (min_width.0, idx)), + None, + ); + for x in x..min_width.0 { + self.data_columns.columns[0][(x, idx)] + .set_bg(row_attr.bg) + .set_attrs(row_attr.attrs); + } } let (x, _) = write_string_to_grid( &strings.date, @@ -1072,6 +1083,67 @@ impl ThreadListing { *self.rows.entries.get_mut(idx).unwrap() = ((thread_hash, env_hash), strings); } + + fn draw_relative_numbers(&mut self, grid: &mut CellBuffer, area: Area, top_idx: usize) { + let width = self.data_columns.columns[0].size().0; + let upper_left = upper_left!(area); + for i in 0..height!(area) { + let row_attr = row_attr!(self.color_cache, (top_idx + i) % 2 == 0, false, true, false); + + clear_area( + &mut self.data_columns.columns[0], + ((0, i), (width - 1, i + 1)), + row_attr, + ); + clear_area( + grid, + ( + pos_inc(upper_left, (0, i)), + pos_inc(upper_left, (width - 1, i + 1)), + ), + row_attr, + ); + let (x, _) = write_string_to_grid( + &if self.new_cursor_pos.2.saturating_sub(top_idx) == i { + self.new_cursor_pos.2.to_string() + } else { + (i as isize - (self.new_cursor_pos.2 - top_idx) as isize) + .abs() + .to_string() + }, + &mut self.data_columns.columns[0], + row_attr.fg, + row_attr.bg, + row_attr.attrs, + ((0, i), (width, i + 1)), + None, + ); + for x in x..width { + self.data_columns.columns[0][(x, i)] + .set_ch(' ') + .set_bg(row_attr.bg) + .set_attrs(row_attr.attrs); + } + _ = write_string_to_grid( + &if self.new_cursor_pos.2.saturating_sub(top_idx) == i { + self.new_cursor_pos.2.to_string() + } else { + (i as isize - (self.new_cursor_pos.2 - top_idx) as isize) + .abs() + .to_string() + }, + grid, + row_attr.fg, + row_attr.bg, + row_attr.attrs, + ( + pos_inc(upper_left, (0, i)), + pos_inc(upper_left, (width, i + 1)), + ), + None, + ); + } + } } impl Component for ThreadListing { diff --git a/meli/src/conf/listing.rs b/meli/src/conf/listing.rs index f56bb226..2ebf5936 100644 --- a/meli/src/conf/listing.rs +++ b/meli/src/conf/listing.rs @@ -147,6 +147,11 @@ pub struct ListingSettings { /// them. Default: "true" #[serde(default = "true_val", alias = "relative-menu-indices")] pub relative_menu_indices: bool, + + /// Show relative indices in listings to quickly help with jumping to + /// them. Default: "true" + #[serde(default = "true_val", alias = "relative-list-indices")] + pub relative_list_indices: bool, } const fn default_divider() -> char { @@ -179,6 +184,7 @@ impl Default for ListingSettings { thread_subject_pack: true, threaded_repeat_identical_from_values: false, relative_menu_indices: true, + relative_list_indices: true, } } } @@ -218,6 +224,7 @@ impl DotAddressable for ListingSettings { .threaded_repeat_identical_from_values .lookup(field, tail), "relative_menu_indices" => self.relative_menu_indices.lookup(field, tail), + "relative_list_indices" => self.relative_list_indices.lookup(field, tail), other => Err(Error::new(format!( "{} has no field named {}", parent_field, other diff --git a/meli/src/conf/overrides.rs b/meli/src/conf/overrides.rs index 3faeb48a..b16e7dc2 100644 --- a/meli/src/conf/overrides.rs +++ b/meli/src/conf/overrides.rs @@ -29,7 +29,7 @@ use melib::HeaderName; # [derive (Debug , Serialize , Deserialize , Clone)] # [serde (deny_unknown_fields)] pub struct PagerSettingsOverride { # [doc = " Number of context lines when going to next page."] # [doc = " Default: 0"] # [serde (alias = "pager-context")] # [serde (default)] pub pager_context : Option < usize > , # [doc = " Stop at the end instead of displaying next mail."] # [doc = " Default: false"] # [serde (alias = "pager-stop")] # [serde (default)] pub pager_stop : Option < bool > , # [doc = " Always show headers when scrolling."] # [doc = " Default: true"] # [serde (alias = "sticky-headers" , alias = "headers-sticky" , alias = "headers_sticky")] # [serde (default)] pub sticky_headers : Option < bool > , # [doc = " The height of the pager in mail view, in percent."] # [doc = " Default: 80"] # [serde (alias = "pager-ratio")] # [serde (default)] pub pager_ratio : Option < usize > , # [doc = " A command to pipe mail output through for viewing in pager."] # [doc = " Default: None"] # [serde (deserialize_with = "non_empty_opt_string")] # [serde (default)] pub filter : Option < Option < String > > , # [doc = " A command to pipe html output before displaying it in a pager"] # [doc = " Default: None"] # [serde (deserialize_with = "non_empty_opt_string" , alias = "html-filter")] # [serde (default)] pub html_filter : Option < Option < String > > , # [doc = " Respect \"format=flowed\""] # [doc = " Default: true"] # [serde (alias = "format-flowed")] # [serde (default)] pub format_flowed : Option < bool > , # [doc = " Split long lines that would overflow on the x axis."] # [doc = " Default: true"] # [serde (alias = "split-long-lines")] # [serde (default)] pub split_long_lines : Option < bool > , # [doc = " Minimum text width in columns."] # [doc = " Default: 80"] # [serde (alias = "minimum-width")] # [serde (default)] pub minimum_width : Option < usize > , # [doc = " Choose `text/html` alternative if `text/plain` is empty in"] # [doc = " `multipart/alternative` attachments."] # [doc = " Default: true"] # [serde (alias = "auto-choose-multipart-alternative")] # [serde (default)] pub auto_choose_multipart_alternative : Option < ToggleFlag > , # [doc = " Show Date: in my timezone"] # [doc = " Default: true"] # [serde (alias = "show-date-in-my-timezone")] # [serde (default)] pub show_date_in_my_timezone : Option < ToggleFlag > , # [doc = " A command to launch URLs with. The URL will be given as the first"] # [doc = " argument of the command. Default: None"] # [serde (deserialize_with = "non_empty_opt_string")] # [serde (default)] pub url_launcher : Option < Option < String > > , # [doc = " A command to open html files."] # [doc = " Default: None"] # [serde (deserialize_with = "non_empty_opt_string" , alias = "html-open")] # [serde (default)] pub html_open : Option < Option < String > > , # [doc = " Extra headers to display, if present, in the default header preamble."] # [doc = " Default: []"] # [serde (alias = "show-extra-headers")] # [serde (default)] pub show_extra_headers : Option < Vec < String > > } impl Default for PagerSettingsOverride { fn default () -> Self { PagerSettingsOverride { pager_context : None , pager_stop : None , sticky_headers : None , pager_ratio : None , filter : None , html_filter : None , format_flowed : None , split_long_lines : None , minimum_width : None , auto_choose_multipart_alternative : None , show_date_in_my_timezone : None , url_launcher : None , html_open : None , show_extra_headers : None } } } -# [derive (Debug , Serialize , Deserialize , Clone)] # [serde (deny_unknown_fields)] pub struct ListingSettingsOverride { # [doc = " Number of context lines when going to next page."] # [doc = " Default: 0"] # [serde (alias = "context-lines")] # [serde (default)] pub context_lines : Option < usize > , # [doc = " Show auto-hiding scrollbar in accounts sidebar menu."] # [doc = " Default: True"] # [serde (default)] pub show_menu_scrollbar : Option < bool > , # [doc = " Datetime formatting passed verbatim to strftime(3)."] # [doc = " Default: %Y-%m-%d %T"] # [serde (alias = "datetime-fmt")] # [serde (default)] pub datetime_fmt : Option < Option < String > > , # [doc = " Show recent dates as `X {minutes,hours,days} ago`, up to 7 days."] # [doc = " Default: true"] # [serde (alias = "recent-dates")] # [serde (default)] pub recent_dates : Option < bool > , # [doc = " Show only envelopes that match this query"] # [doc = " Default: None"] # [serde (default)] pub filter : Option < Option < Query > > , # [serde (alias = "index-style")] # [serde (default)] pub index_style : Option < IndexStyle > , # [doc = " Default: \" \""] # [serde (default)] pub sidebar_mailbox_tree_has_sibling : Option < Option < String > > , # [doc = " Default: \" \""] # [serde (default)] pub sidebar_mailbox_tree_no_sibling : Option < Option < String > > , # [doc = " Default: \" \""] # [serde (default)] pub sidebar_mailbox_tree_has_sibling_leaf : Option < Option < String > > , # [doc = " Default: \" \""] # [serde (default)] pub sidebar_mailbox_tree_no_sibling_leaf : Option < Option < String > > , # [doc = " Default: ' '"] # [serde (default)] pub sidebar_divider : Option < char > , # [doc = " Default: 90"] # [serde (default)] pub sidebar_ratio : Option < usize > , # [doc = " Flag to show if thread entry contains unseen mail."] # [doc = " Default: \"●\""] # [serde (default)] pub unseen_flag : Option < Option < String > > , # [doc = " Flag to show if thread has been snoozed."] # [doc = " Default: \"💤\""] # [serde (default)] pub thread_snoozed_flag : Option < Option < String > > , # [doc = " Flag to show if thread entry has been selected."] # [doc = " Default: \"☑\u{fe0f}\""] # [serde (default)] pub selected_flag : Option < Option < String > > , # [doc = " Flag to show if thread entry contains attachments."] # [doc = " Default: \"📎\""] # [serde (default)] pub attachment_flag : Option < Option < String > > , # [doc = " Should threads with different Subjects show a list of those"] # [doc = " subjects on the entry title?"] # [doc = " Default: \"true\""] # [serde (default)] pub thread_subject_pack : Option < bool > , # [doc = " In threaded listing style, repeat identical From column values within a"] # [doc = " thread. Not repeating adds empty space in the From column which"] # [doc = " might result in less visual clutter."] # [doc = " Default: \"false\""] # [serde (default)] pub threaded_repeat_identical_from_values : Option < bool > , # [doc = " Show relative indices in menu mailboxes to quickly help with jumping to"] # [doc = " them. Default: \"true\""] # [serde (alias = "relative-menu-indices")] # [serde (default)] pub relative_menu_indices : Option < bool > } impl Default for ListingSettingsOverride { fn default () -> Self { ListingSettingsOverride { context_lines : None , show_menu_scrollbar : None , datetime_fmt : None , recent_dates : None , filter : None , index_style : None , sidebar_mailbox_tree_has_sibling : None , sidebar_mailbox_tree_no_sibling : None , sidebar_mailbox_tree_has_sibling_leaf : None , sidebar_mailbox_tree_no_sibling_leaf : None , sidebar_divider : None , sidebar_ratio : None , unseen_flag : None , thread_snoozed_flag : None , selected_flag : None , attachment_flag : None , thread_subject_pack : None , threaded_repeat_identical_from_values : None , relative_menu_indices : None } } } +# [derive (Debug , Serialize , Deserialize , Clone)] # [serde (deny_unknown_fields)] pub struct ListingSettingsOverride { # [doc = " Number of context lines when going to next page."] # [doc = " Default: 0"] # [serde (alias = "context-lines")] # [serde (default)] pub context_lines : Option < usize > , # [doc = " Show auto-hiding scrollbar in accounts sidebar menu."] # [doc = " Default: True"] # [serde (default)] pub show_menu_scrollbar : Option < bool > , # [doc = " Datetime formatting passed verbatim to strftime(3)."] # [doc = " Default: %Y-%m-%d %T"] # [serde (alias = "datetime-fmt")] # [serde (default)] pub datetime_fmt : Option < Option < String > > , # [doc = " Show recent dates as `X {minutes,hours,days} ago`, up to 7 days."] # [doc = " Default: true"] # [serde (alias = "recent-dates")] # [serde (default)] pub recent_dates : Option < bool > , # [doc = " Show only envelopes that match this query"] # [doc = " Default: None"] # [serde (default)] pub filter : Option < Option < Query > > , # [serde (alias = "index-style")] # [serde (default)] pub index_style : Option < IndexStyle > , # [doc = " Default: \" \""] # [serde (default)] pub sidebar_mailbox_tree_has_sibling : Option < Option < String > > , # [doc = " Default: \" \""] # [serde (default)] pub sidebar_mailbox_tree_no_sibling : Option < Option < String > > , # [doc = " Default: \" \""] # [serde (default)] pub sidebar_mailbox_tree_has_sibling_leaf : Option < Option < String > > , # [doc = " Default: \" \""] # [serde (default)] pub sidebar_mailbox_tree_no_sibling_leaf : Option < Option < String > > , # [doc = " Default: ' '"] # [serde (default)] pub sidebar_divider : Option < char > , # [doc = " Default: 90"] # [serde (default)] pub sidebar_ratio : Option < usize > , # [doc = " Flag to show if thread entry contains unseen mail."] # [doc = " Default: \"●\""] # [serde (default)] pub unseen_flag : Option < Option < String > > , # [doc = " Flag to show if thread has been snoozed."] # [doc = " Default: \"💤\""] # [serde (default)] pub thread_snoozed_flag : Option < Option < String > > , # [doc = " Flag to show if thread entry has been selected."] # [doc = " Default: \"☑\u{fe0f}\""] # [serde (default)] pub selected_flag : Option < Option < String > > , # [doc = " Flag to show if thread entry contains attachments."] # [doc = " Default: \"📎\""] # [serde (default)] pub attachment_flag : Option < Option < String > > , # [doc = " Should threads with different Subjects show a list of those"] # [doc = " subjects on the entry title?"] # [doc = " Default: \"true\""] # [serde (default)] pub thread_subject_pack : Option < bool > , # [doc = " In threaded listing style, repeat identical From column values within a"] # [doc = " thread. Not repeating adds empty space in the From column which"] # [doc = " might result in less visual clutter."] # [doc = " Default: \"false\""] # [serde (default)] pub threaded_repeat_identical_from_values : Option < bool > , # [doc = " Show relative indices in menu mailboxes to quickly help with jumping to"] # [doc = " them. Default: \"true\""] # [serde (alias = "relative-menu-indices")] # [serde (default)] pub relative_menu_indices : Option < bool > , # [doc = " Show relative indices in listings to quickly help with jumping to"] # [doc = " them. Default: \"true\""] # [serde (alias = "relative-list-indices")] # [serde (default)] pub relative_list_indices : Option < bool > } impl Default for ListingSettingsOverride { fn default () -> Self { ListingSettingsOverride { context_lines : None , show_menu_scrollbar : None , datetime_fmt : None , recent_dates : None , filter : None , index_style : None , sidebar_mailbox_tree_has_sibling : None , sidebar_mailbox_tree_no_sibling : None , sidebar_mailbox_tree_has_sibling_leaf : None , sidebar_mailbox_tree_no_sibling_leaf : None , sidebar_divider : None , sidebar_ratio : None , unseen_flag : None , thread_snoozed_flag : None , selected_flag : None , attachment_flag : None , thread_subject_pack : None , threaded_repeat_identical_from_values : None , relative_menu_indices : None , relative_list_indices : None } } } # [derive (Debug , Serialize , Deserialize , Clone)] # [serde (deny_unknown_fields)] pub struct NotificationsSettingsOverride { # [doc = " Enable notifications."] # [doc = " Default: True"] # [serde (default)] pub enable : Option < bool > , # [doc = " A command to pipe notifications through."] # [doc = " Default: None"] # [serde (default)] pub script : Option < Option < String > > , # [doc = " A command to pipe new mail notifications through (preferred over"] # [doc = " `script`). Default: None"] # [serde (default)] pub new_mail_script : Option < Option < String > > , # [doc = " A file location which has its size changed when new mail arrives (max"] # [doc = " 128 bytes). Can be used to trigger new mail notifications eg with"] # [doc = " `xbiff(1)`. Default: None"] # [serde (alias = "xbiff-file-path")] # [serde (default)] pub xbiff_file_path : Option < Option < String > > , # [serde (alias = "play-sound")] # [serde (default)] pub play_sound : Option < ToggleFlag > , # [serde (alias = "sound-file")] # [serde (default)] pub sound_file : Option < Option < String > > } impl Default for NotificationsSettingsOverride { fn default () -> Self { NotificationsSettingsOverride { enable : None , script : None , new_mail_script : None , xbiff_file_path : None , play_sound : None , sound_file : None } } }