Browse Source

sidebar: add customizable mailbox tree

Concerns #72
memfd
Manos Pitsidianakis 1 year ago
parent
commit
fbf2b7dc7b
Signed by untrusted user: epilys GPG Key ID: 73627C2F690DF710
  1. 81
      meli.conf.5
  2. 81
      src/components/mail/listing.rs
  3. 24
      src/conf/accounts.rs
  4. 57
      src/conf/listing.rs
  5. 16
      src/conf/overrides.rs

81
meli.conf.5

@ -772,7 +772,88 @@ Example:
.Bd -literal
filter = "not flags:seen" # show only unseen messages
.Ed
.It Ic index_style Ar String
Sets the way mailboxes are displayed.
.It Ic sidebar_mailbox_tree_has_sibling Ar String
.Pq Em optional
Sets the string to print in the mailbox tree for a level where its root has a sibling.
See example below for a clear explanation and examples.
.It Ic sidebar_mailbox_tree_no_sibling Ar String
.Pq Em optional
Sets the string to print in the mailbox tree for a level where its root has no sibling.
.It Ic sidebar_mailbox_tree_has_sibling_leaf Ar String
.Pq Em optional
Sets the string to print in the mailbox tree for a leaf level where its root has a sibling.
.It Ic sidebar_mailbox_tree_no_sibling_leaf Ar String
.Pq Em optional
Sets the string to print in the mailbox tree for a leaf level where its root has no sibling.
.El
.Ss Examples of sidebar mailbox tree customization
The default values
.Bd
has_sibling = " "
no_sibling = " ";
has_sibling_leaf = " "
no_sibling_leaf = " "
.Ed
render a mailbox tree like the following:
.Bd -literal
0 Inbox 3
1 Archive
2 Drafts
3 Lists
4 example-list-a
5 example-list-b
6 Sent
7 Spam
8 Trash
.Ed
Other possible trees:
.Bd -literal
has_sibling = " ┃"
no_sibling = " "
has_sibling_leaf = " ┣━"
no_sibling_leaf = " ┗━"
.Ed
.Bd -literal
0 Inbox 3
1 ┣━Archive
2 ┣━Drafts
3 ┣━Lists
4 ┃ ┣━example-list-a
5 ┃ ┗━example-list-b
6 ┣━Sent
7 ┣━Spam
8 ┗━Trash
.Ed
A completely ASCII one:
.Bd -literal
has_sibling = " |"
no_sibling = " "
has_sibling_leaf = " |\\_"
no_sibling_leaf = " \\_"
.Ed
.Bd -literal
0 Inbox 3
1 |\\_Archive
2 |\\_Drafts
3 |\\_Lists
4 | |\\_example-list-a
5 | \\_example-list-b
6 |\\_Sent
7 |\\_Spam
8 \\_Trash
.Ed
.Sh TAGS
.Bl -tag -width 36n
.It Ic colours Ar hash table String[Color]

81
src/components/mail/listing.rs

@ -136,7 +136,7 @@ struct AccountMenuEntry {
name: String,
hash: AccountHash,
index: usize,
entries: SmallVec<[(usize, MailboxHash); 16]>,
entries: SmallVec<[(usize, u32, bool, MailboxHash); 16]>,
}
pub trait MailListingTrait: ListingTrait {
@ -587,7 +587,7 @@ impl Component for Listing {
.ref_mailbox
.is_subscribed()
})
.map(|f| (f.depth, f.hash))
.map(|f| (f.depth, f.indentation, f.has_sibling, f.hash))
.collect::<_>();
self.set_dirty(true);
}
@ -607,7 +607,7 @@ impl Component for Listing {
.ref_mailbox
.is_subscribed()
})
.map(|f| (f.depth, f.hash))
.map(|f| (f.depth, f.indentation, f.has_sibling, f.hash))
.collect::<_>();
if self.cursor_pos.0 == account_index {
self.cursor_pos.1 = std::cmp::min(
@ -616,7 +616,7 @@ impl Component for Listing {
);
self.component.set_coordinates((
self.accounts[self.cursor_pos.0].hash,
self.accounts[self.cursor_pos.0].entries[self.cursor_pos.1].1,
self.accounts[self.cursor_pos.0].entries[self.cursor_pos.1].3,
));
self.component.refresh_mailbox(context, true);
}
@ -772,7 +772,7 @@ impl Component for Listing {
return true;
}
Action::ViewMailbox(idx) => {
if let Some((_, mailbox_hash)) =
if let Some((_, _, _, mailbox_hash)) =
self.accounts[self.cursor_pos.0].entries.get(*idx)
{
let account_hash = self.accounts[self.cursor_pos.0].hash;
@ -1195,7 +1195,7 @@ impl Component for Listing {
}
fn get_status(&self, context: &Context) -> String {
let mailbox_hash = if let Some((_, mailbox_hash)) = self.accounts[self.cursor_pos.0]
let mailbox_hash = if let Some((_, _, _, mailbox_hash)) = self.accounts[self.cursor_pos.0]
.entries
.get(self.cursor_pos.1)
{
@ -1233,11 +1233,11 @@ impl Listing {
.iter()
.enumerate()
.map(|(i, (h, a))| {
let entries: SmallVec<[(usize, MailboxHash); 16]> = a
let entries: SmallVec<[(usize, u32, bool, MailboxHash); 16]> = a
.list_mailboxes()
.into_iter()
.filter(|mailbox_node| a[&mailbox_node.hash].ref_mailbox.is_subscribed())
.map(|f| (f.depth, f.hash))
.map(|f| (f.depth, f.indentation, f.has_sibling, f.hash))
.collect::<_>();
AccountMenuEntry {
@ -1321,18 +1321,20 @@ impl Listing {
&& self.cursor_pos.0 == a.index)
|| (self.focus == ListingFocus::Menu && self.menu_cursor_pos.0 == a.index);
let mut lines: Vec<(usize, usize, MailboxHash, Option<usize>)> = Vec::new();
let mut lines: Vec<(usize, usize, u32, bool, MailboxHash, Option<usize>)> = Vec::new();
for (i, &(depth, mailbox_hash)) in a.entries.iter().enumerate() {
for (i, &(depth, indentation, has_sibling, mailbox_hash)) in a.entries.iter().enumerate() {
if mailboxes[&mailbox_hash].is_subscribed() {
match context.accounts[a.index][&mailbox_hash].status {
crate::conf::accounts::MailboxStatus::Failed(_) => {
lines.push((depth, i, mailbox_hash, None));
lines.push((depth, i, indentation, has_sibling, mailbox_hash, None));
}
_ => {
lines.push((
depth,
i,
indentation,
has_sibling,
mailbox_hash,
mailboxes[&mailbox_hash].count().ok().map(|(v, _)| v),
));
@ -1372,6 +1374,7 @@ impl Listing {
let lines_len = lines.len();
let mut idx = 0;
let mut branches = String::with_capacity(16);
for y in get_y(upper_left) + 1..get_y(bottom_right) {
if idx == lines_len {
@ -1411,7 +1414,7 @@ impl Listing {
)
};
let (depth, inc, mailbox_idx, count) = lines[idx];
let (depth, inc, indentation, has_sibling, mailbox_idx, count) = lines[idx];
/* Calculate how many columns the mailbox index tags should occupy with right alignment,
* eg.
* 1
@ -1429,6 +1432,33 @@ impl Listing {
}
ctr
};
let has_sibling_str: &str =
account_settings!(context[a.hash].listing.sidebar_mailbox_tree_has_sibling)
.as_ref()
.map(|s| s.as_str())
.unwrap_or(" ");
let no_sibling_str: &str =
account_settings!(context[a.hash].listing.sidebar_mailbox_tree_no_sibling)
.as_ref()
.map(|s| s.as_str())
.unwrap_or(" ");
let has_sibling_leaf_str: &str = account_settings!(
context[a.hash]
.listing
.sidebar_mailbox_tree_has_sibling_leaf
)
.as_ref()
.map(|s| s.as_str())
.unwrap_or(" ");
let no_sibling_leaf_str: &str =
account_settings!(context[a.hash].listing.sidebar_mailbox_tree_no_sibling_leaf)
.as_ref()
.map(|s| s.as_str())
.unwrap_or(" ");
let (x, _) = write_string_to_grid(
&format!("{:>width$}", inc, width = total_mailbox_no_digits),
grid,
@ -1438,8 +1468,29 @@ impl Listing {
(set_y(upper_left, y), bottom_right),
None,
);
{
branches.clear();
branches.push_str(no_sibling_str);
let mut o = 1;
let leading_zeros = indentation.leading_zeros();
for _ in 0..(30_u32.saturating_sub(leading_zeros)) {
if indentation & o > 0 {
branches.push_str(has_sibling_str);
} else {
branches.push_str(no_sibling_str);
}
o <<= 1;
}
if depth > 0 {
if has_sibling {
branches.push_str(has_sibling_leaf_str);
} else {
branches.push_str(no_sibling_leaf_str);
}
}
}
let (x, _) = write_string_to_grid(
&" ".repeat(depth + 1),
&branches,
grid,
att.fg,
att.bg,
@ -1511,10 +1562,10 @@ impl Listing {
.ref_mailbox
.is_subscribed()
})
.map(|f| (f.depth, f.hash))
.map(|f| (f.depth, f.indentation, f.has_sibling, f.hash))
.collect::<_>();
/* Account might have no mailboxes yet if it's offline */
if let Some((_, mailbox_hash)) = self.accounts[self.cursor_pos.0]
if let Some((_, _, _, mailbox_hash)) = self.accounts[self.cursor_pos.0]
.entries
.get(self.cursor_pos.1)
{

24
src/conf/accounts.rs

@ -390,6 +390,8 @@ impl Drop for Account {
pub struct MailboxNode {
pub hash: MailboxHash,
pub depth: usize,
pub indentation: u32,
pub has_sibling: bool,
pub children: Vec<MailboxNode>,
}
@ -2088,6 +2090,8 @@ fn build_mailboxes_order(
hash: h,
children: Vec::new(),
depth,
indentation: 0,
has_sibling: false,
};
for &c in mailbox_entries[&h].ref_mailbox.children() {
if mailbox_entries.contains_key(&c) {
@ -2151,4 +2155,24 @@ fn build_mailboxes_order(
stack.extend(next.children.iter().rev().map(Some));
}
}
drop(stack);
for node in tree.iter_mut() {
fn rec(node: &mut MailboxNode, depth: usize, mut indentation: u32, has_sibling: bool) {
node.indentation = indentation;
node.has_sibling = has_sibling;
let mut iter = (0..node.children.len()).peekable();
if has_sibling {
indentation <<= 1;
indentation |= 1;
} else {
indentation <<= 1;
}
while let Some(i) = iter.next() {
let c = &mut node.children[i];
rec(c, depth + 1, indentation, iter.peek() != None);
}
};
rec(node, 0, 1, false);
}
}

57
src/conf/listing.rs

@ -24,6 +24,31 @@ use melib::search::Query;
use melib::{MeliError, Result};
/// Settings for mail listings
///
///
/// Tree decoration examples:
///
///```no_run
///const HAS_SIBLING: &'static str = " ┃";
///const NO_SIBLING: &'static str = " ";
///const HAS_SIBLING_LEAF: &'static str = " ┣━";
///const NO_SIBLING_LEAF: &'static str = " ┗━";
///
///const HAS_SIBLING: &'static str = " |";
///const NO_SIBLING: &'static str = " ";
///const HAS_SIBLING_LEAF: &'static str = " |\\_";
///const NO_SIBLING_LEAF: &'static str = " \\_";
///
///const HAS_SIBLING: &'static str = " ";
///const NO_SIBLING: &'static str = " ";
///const HAS_SIBLING_LEAF: &'static str = " ";
///const NO_SIBLING_LEAF: &'static str = " ";
///
///const HAS_SIBLING: &'static str = " │";
///const NO_SIBLING: &'static str = " ";
///const HAS_SIBLING_LEAF: &'static str = " ├─";
///const NO_SIBLING_LEAF: &'static str = " ╰─";
///```
#[derive(Debug, Deserialize, Clone, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ListingSettings {
@ -49,6 +74,22 @@ pub struct ListingSettings {
#[serde(default, alias = "index-style")]
pub index_style: IndexStyle,
///Default: " "
#[serde(default = "none")]
pub sidebar_mailbox_tree_has_sibling: Option<String>,
///Default: " "
#[serde(default)]
pub sidebar_mailbox_tree_no_sibling: Option<String>,
///Default: " "
#[serde(default)]
pub sidebar_mailbox_tree_has_sibling_leaf: Option<String>,
///Default: " "
#[serde(default)]
pub sidebar_mailbox_tree_no_sibling_leaf: Option<String>,
}
impl Default for ListingSettings {
@ -59,6 +100,10 @@ impl Default for ListingSettings {
recent_dates: true,
filter: None,
index_style: IndexStyle::default(),
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,
}
}
}
@ -74,6 +119,18 @@ impl DotAddressable for ListingSettings {
"recent_dates" => self.recent_dates.lookup(field, tail),
"filter" => self.filter.lookup(field, tail),
"index_style" => self.index_style.lookup(field, tail),
"sidebar_mailbox_tree_has_sibling" => {
self.sidebar_mailbox_tree_has_sibling.lookup(field, tail)
}
"sidebar_mailbox_tree_no_sibling" => {
self.sidebar_mailbox_tree_no_sibling.lookup(field, tail)
}
"sidebar_mailbox_tree_has_sibling_leaf" => self
.sidebar_mailbox_tree_has_sibling_leaf
.lookup(field, tail),
"sidebar_mailbox_tree_no_sibling_leaf" => self
.sidebar_mailbox_tree_no_sibling_leaf
.lookup(field, tail),
other => Err(MeliError::new(format!(
"{} has no field named {}",
parent_field, other

16
src/conf/overrides.rs

@ -119,6 +119,18 @@ pub struct ListingSettingsOverride {
#[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>>,
}
impl Default for ListingSettingsOverride {
fn default() -> Self {
@ -128,6 +140,10 @@ impl Default for ListingSettingsOverride {
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,
}
}
}

Loading…
Cancel
Save