Browse Source

ui: update rows on TagAdd/TagRemove

Except for threadlisting
tags/alpha-0.4.1
Manos Pitsidianakis 2 years ago
parent
commit
f632bc4c08
Signed by: epilys GPG Key ID: 73627C2F690DF710
  1. 15
      ui/src/components/mail/listing.rs
  2. 289
      ui/src/components/mail/listing/compact.rs
  3. 257
      ui/src/components/mail/listing/conversations.rs
  4. 43
      ui/src/components/mail/listing/plain.rs
  5. 4
      ui/src/components/mail/listing/thread.rs
  6. 22
      ui/src/conf/tags.rs
  7. 609
      ui/src/terminal/cells.rs

15
ui/src/components/mail/listing.rs

@ -92,7 +92,6 @@ pub trait MailListingTrait: ListingTrait {
StatusEvent::DisplayMessage(e.to_string()),
));
}
self.row_updates().push(thread_hash);
}
ListingAction::SetUnseen => {
if let Err(e) = envelope.set_unseen(op) {
@ -153,8 +152,7 @@ pub trait MailListingTrait: ListingTrait {
}
fn row_updates(&mut self) -> &mut StackVec<ThreadHash>;
fn update_line(&mut self, context: &Context, thread_hash: ThreadHash);
fn get_focused_items(&self, _context: &Context) -> StackVec<ThreadHash>;
}
pub trait ListingTrait: Component {
@ -508,6 +506,17 @@ impl Component for Listing {
self.component.set_style(IndexStyle::Conversations);
return true;
}
Action::Listing(a @ ListingAction::SetSeen)
| Action::Listing(a @ ListingAction::SetUnseen)
| Action::Listing(a @ ListingAction::Delete)
| Action::Listing(a @ ListingAction::Tag(_)) => {
let focused = self.component.get_focused_items(context);
for i in focused {
self.component.perform_action(context, i, a);
}
self.component.set_dirty();
return true;
}
_ => {}
},
UIEvent::RefreshMailbox((idxa, folder_hash)) => {

289
ui/src/components/mail/listing/compact.rs

@ -79,68 +79,23 @@ impl MailListingTrait for CompactListing {
&mut self.row_updates
}
fn update_line(&mut self, context: &Context, thread_hash: ThreadHash) {
let account = &context.accounts[self.cursor_pos.0];
let folder_hash = account[self.cursor_pos.1].unwrap().folder.hash();
let threads = &account.collection.threads[&folder_hash];
if let Some(env_hash) = threads[&thread_hash].message() {
if !account.contains_key(env_hash) {
/* The envelope has been renamed or removed, so wait for the appropriate event to
* arrive */
return;
}
let envelope: EnvelopeRef = account.collection.get_env(env_hash);
let has_attachments = envelope.has_attachments();
drop(envelope);
let fg_color = if threads[&thread_hash].has_unseen() {
Color::Byte(0)
} else {
Color::Default
};
let idx = self.order[&thread_hash];
let bg_color = if context.settings.terminal.theme == "light" {
if threads[&thread_hash].has_unseen() {
Color::Byte(251)
} else if idx % 2 == 0 {
Color::Byte(252)
} else {
Color::Default
}
} else {
if threads[&thread_hash].has_unseen() {
Color::Byte(253)
} else if idx % 2 == 0 {
Color::Byte(236)
} else {
Color::Default
}
};
for i in 0..self.data_columns.columns.len() {
let column_width = self.data_columns.columns[i].size().0;
if column_width == 0 {
continue;
}
change_colors(
&mut self.data_columns.columns[i],
((0, idx), (column_width - 1, idx)),
fg_color,
bg_color,
);
}
match (threads.is_snoozed(thread_hash), has_attachments) {
(true, true) => {
self.data_columns.columns[3][(0, idx)].set_fg(Color::Byte(103));
self.data_columns.columns[3][(2, idx)].set_fg(Color::Red);
}
(true, false) => {
self.data_columns.columns[3][(0, idx)].set_fg(Color::Red);
}
(false, true) => {
self.data_columns.columns[3][(0, idx)].set_fg(Color::Byte(103));
}
(false, false) => {}
}
}
fn get_focused_items(&self, context: &Context) -> StackVec<ThreadHash> {
let is_selection_empty = self.selection.values().cloned().any(std::convert::identity);
let i = [self.get_thread_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();
StackVec::from_iter(iter.into_iter())
}
}
@ -564,7 +519,7 @@ impl CompactListing {
}
fn make_entry_string(
&self,
e: EnvelopeRef,
e: &Envelope,
context: &Context,
thread_node: &ThreadNode,
is_snoozed: bool,
@ -592,10 +547,16 @@ impl CompactListing {
tags.push(' ');
tags.push_str(tags_lck.get(t).as_ref().unwrap());
tags.push(' ');
if let Some(&c) = context.settings.tags.colors.get(t) {
if let Some(&c) = folder
.conf_override
.tags
.as_ref()
.map(|s| s.colors.get(t))
.unwrap_or(None)
{
colors.push(c);
} else {
colors.push(8);
colors.push(Color::Byte(8));
}
}
if !tags.is_empty() {
@ -732,7 +693,7 @@ impl CompactListing {
context.accounts[self.cursor_pos.0].collection.get_env(i);
let entry_strings = self.make_entry_string(
root_envelope,
&root_envelope,
context,
thread_node,
threads.is_snoozed(root_idx),
@ -885,14 +846,14 @@ impl CompactListing {
t,
&mut self.data_columns.columns[4],
Color::White,
Color::Byte(color),
color,
Attr::Bold,
((x + 1, idx), (min_width.4, idx)),
None,
);
self.data_columns.columns[4][(x, idx)].set_bg(Color::Byte(color));
self.data_columns.columns[4][(x, idx)].set_bg(color);
if _x < min_width.4 {
self.data_columns.columns[4][(_x, idx)].set_bg(Color::Byte(color));
self.data_columns.columns[4][(_x, idx)].set_bg(color);
self.data_columns.columns[4][(_x, idx)].set_keep_bg(true);
}
for x in (x + 1).._x {
@ -905,6 +866,7 @@ impl CompactListing {
x
};
for x in x..min_width.4 {
self.data_columns.columns[4][(x, idx)].set_ch(' ');
self.data_columns.columns[4][(x, idx)].set_bg(bg_color);
}
match (
@ -958,6 +920,163 @@ impl CompactListing {
self.filtered_selection[cursor]
}
}
fn update_line(&mut self, context: &Context, thread_hash: ThreadHash) {
let account = &context.accounts[self.cursor_pos.0];
let folder_hash = account[self.cursor_pos.1].unwrap().folder.hash();
let threads = &account.collection.threads[&folder_hash];
if let Some(env_hash) = threads[&thread_hash].message() {
if !account.contains_key(env_hash) {
/* The envelope has been renamed or removed, so wait for the appropriate event to
* arrive */
return;
}
let envelope: EnvelopeRef = account.collection.get_env(env_hash);
let has_attachments = envelope.has_attachments();
let fg_color = if threads[&thread_hash].has_unseen() {
Color::Byte(0)
} else {
Color::Default
};
let idx = self.order[&thread_hash];
let bg_color = if context.settings.terminal.theme == "light" {
if threads[&thread_hash].has_unseen() {
Color::Byte(251)
} else if idx % 2 == 0 {
Color::Byte(252)
} else {
Color::Default
}
} else {
if threads[&thread_hash].has_unseen() {
Color::Byte(253)
} else if idx % 2 == 0 {
Color::Byte(236)
} else {
Color::Default
}
};
let strings = self.make_entry_string(
&envelope,
context,
&threads[&thread_hash],
threads.is_snoozed(thread_hash),
);
drop(envelope);
let columns = &mut self.data_columns.columns;
let min_width = (
columns[0].size().0,
columns[1].size().0,
columns[2].size().0,
columns[3].size().0,
columns[4].size().0,
);
let (x, _) = write_string_to_grid(
&idx.to_string(),
&mut columns[0],
fg_color,
bg_color,
Attr::Default,
((0, idx), (min_width.0, idx)),
None,
);
for c in columns[0].row_iter((x, min_width.0.saturating_sub(1)), idx) {
columns[0][c].set_bg(bg_color);
}
let (x, _) = write_string_to_grid(
&strings.date,
&mut columns[1],
fg_color,
bg_color,
Attr::Default,
((0, idx), (min_width.1.saturating_sub(1), idx)),
None,
);
for c in columns[1].row_iter((x, min_width.1.saturating_sub(1)), idx) {
columns[1][c].set_bg(bg_color);
}
let (x, _) = write_string_to_grid(
&strings.from,
&mut columns[2],
fg_color,
bg_color,
Attr::Default,
((0, idx), (min_width.2, idx)),
None,
);
for c in columns[2].row_iter((x, min_width.2.saturating_sub(1)), idx) {
columns[2][c].set_bg(bg_color);
}
let (x, _) = write_string_to_grid(
&strings.flag,
&mut columns[3],
fg_color,
bg_color,
Attr::Default,
((0, idx), (min_width.3, idx)),
None,
);
for c in columns[3].row_iter((x, min_width.3.saturating_sub(1)), idx) {
columns[3][c].set_bg(bg_color);
}
let (x, _) = write_string_to_grid(
&strings.subject,
&mut columns[4],
fg_color,
bg_color,
Attr::Default,
((0, idx), (min_width.4, idx)),
None,
);
let x = {
let mut x = x + 1;
for (t, &color) in strings.tags.split_whitespace().zip(strings.tags.1.iter()) {
let (_x, _) = write_string_to_grid(
t,
&mut columns[4],
Color::White,
color,
Attr::Bold,
((x + 1, idx), (min_width.4, idx)),
None,
);
for c in columns[4].row_iter((x, x), idx) {
columns[4][c].set_bg(color);
}
for c in columns[4].row_iter((_x, _x), idx) {
columns[4][c].set_bg(color);
columns[4][c].set_keep_bg(true);
}
for c in columns[4].row_iter((x + 1, _x), idx) {
columns[4][c].set_keep_fg(true);
columns[4][c].set_keep_bg(true);
}
for c in columns[4].row_iter((x, x), idx) {
columns[4][c].set_keep_bg(true);
}
x = _x + 1;
}
x
};
for c in columns[4].row_iter((x, min_width.4.saturating_sub(1)), idx) {
columns[4][c].set_ch(' ');
columns[4][c].set_bg(bg_color);
}
match (threads.is_snoozed(thread_hash), has_attachments) {
(true, true) => {
columns[3][(0, idx)].set_fg(Color::Byte(103));
columns[3][(2, idx)].set_fg(Color::Red);
}
(true, false) => {
columns[3][(0, idx)].set_fg(Color::Red);
}
(false, true) => {
columns[3][(0, idx)].set_fg(Color::Byte(103));
}
(false, false) => {}
}
}
}
}
impl Component for CompactListing {
@ -1132,38 +1251,6 @@ impl Component for CompactListing {
self.refresh_mailbox(context);
return true;
}
Action::Listing(a @ ListingAction::SetSeen)
| Action::Listing(a @ ListingAction::SetUnseen)
| Action::Listing(a @ ListingAction::Delete)
| Action::Listing(a @ ListingAction::Tag(_))
if !self.unfocused =>
{
let is_selection_empty =
self.selection.values().cloned().any(std::convert::identity);
let i = [self.get_thread_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 = StackVec::from_iter(iter.into_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;
}
_ => {}
},

257
ui/src/components/mail/listing/conversations.rs

@ -73,7 +73,7 @@ column_str!(struct DateString(String));
column_str!(struct FromString(String));
column_str!(struct SubjectString(String));
column_str!(struct FlagString(String));
column_str!(struct TagString(String, StackVec<u8>));
column_str!(struct TagString(String, StackVec<Color>));
/// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the `Envelope` content in a
/// `ThreadView`.
@ -111,43 +111,26 @@ impl MailListingTrait for ConversationsListing {
&mut self.row_updates
}
fn update_line(&mut self, context: &Context, thread_hash: ThreadHash) {
let account = &context.accounts[self.cursor_pos.0];
let folder_hash = account[self.cursor_pos.1].unwrap().folder.hash();
let threads = &account.collection.threads[&folder_hash];
let thread_node = &threads.thread_nodes[&thread_hash];
let row: usize = self.order[&thread_hash];
let width = self.content.size().0;
let fg_color = if thread_node.has_unseen() {
Color::Byte(0)
fn get_focused_items(&self, context: &Context) -> StackVec<ThreadHash> {
let is_selection_empty = self.selection.values().cloned().any(std::convert::identity);
let i = [self.get_thread_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 {
Color::Default
};
let bg_color = if thread_node.has_unseen() {
Color::Byte(251)
} else {
Color::Default
cursor_iter = Some(i.iter());
None
};
change_colors(
&mut self.content,
((0, 3 * row), (width - 1, 3 * row + 1)),
fg_color,
bg_color,
);
let padding_fg = if context.settings.terminal.theme == "light" {
Color::Byte(254)
} else {
Color::Byte(235)
};
change_colors(
&mut self.content,
((0, 3 * row + 2), (width - 1, 3 * row + 2)),
padding_fg,
bg_color,
);
let iter = sel_iter
.into_iter()
.flatten()
.chain(cursor_iter.into_iter().flatten())
.cloned();
StackVec::from_iter(iter.into_iter())
}
}
impl ListingTrait for ConversationsListing {
fn coordinates(&self) -> (usize, usize) {
(self.new_cursor_pos.0, self.new_cursor_pos.1)
@ -559,10 +542,16 @@ impl ConversationsListing {
tags.push(' ');
tags.push_str(tags_lck.get(t).as_ref().unwrap());
tags.push(' ');
if let Some(&c) = context.settings.tags.colors.get(t) {
if let Some(&c) = folder
.conf_override
.tags
.as_ref()
.map(|s| s.colors.get(t))
.unwrap_or(None)
{
colors.push(c);
} else {
colors.push(8);
colors.push(Color::Byte(8));
}
}
if !tags.is_empty() {
@ -830,14 +819,14 @@ impl ConversationsListing {
t,
&mut self.content,
Color::White,
Color::Byte(color),
color,
Attr::Bold,
((x + 1, 3 * idx), (width - 1, 3 * idx)),
None,
);
self.content[(x, 3 * idx)].set_bg(Color::Byte(color));
self.content[(x, 3 * idx)].set_bg(color);
if _x < width {
self.content[(_x, 3 * idx)].set_bg(Color::Byte(color));
self.content[(_x, 3 * idx)].set_bg(color);
self.content[(_x, 3 * idx)].set_keep_bg(true);
}
for x in (x + 1).._x {
@ -848,6 +837,7 @@ impl ConversationsListing {
x = _x + 1;
}
for x in x..width {
self.content[(x, 3 * idx)].set_ch(' ');
self.content[(x, 3 * idx)].set_bg(bg_color);
}
/* Next line, draw date */
@ -940,6 +930,164 @@ impl ConversationsListing {
self.filtered_selection[cursor]
}
}
fn update_line(&mut self, context: &Context, thread_hash: ThreadHash) {
let account = &context.accounts[self.cursor_pos.0];
let folder_hash = account[self.cursor_pos.1].unwrap().folder.hash();
let threads = &account.collection.threads[&folder_hash];
let thread_node = &threads.thread_nodes[&thread_hash];
let idx: usize = self.order[&thread_hash];
let width = self.content.size().0;
let fg_color = if thread_node.has_unseen() {
Color::Byte(0)
} else {
Color::Default
};
let bg_color = if thread_node.has_unseen() {
Color::Byte(251)
} else {
Color::Default
};
let padding_fg = if context.settings.terminal.theme == "light" {
Color::Byte(254)
} else {
Color::Byte(235)
};
let mut from_address_list = Vec::new();
let mut from_address_set: std::collections::HashSet<Vec<u8>> =
std::collections::HashSet::new();
let mut stack = StackVec::new();
stack.push(thread_hash);
while let Some(h) = stack.pop() {
let env_hash = if let Some(h) = threads.thread_nodes()[&h].message() {
h
} else {
break;
};
let envelope: &EnvelopeRef = &context.accounts[self.cursor_pos.0]
.collection
.get_env(env_hash);
for addr in envelope.from().iter() {
if from_address_set.contains(addr.raw()) {
continue;
}
from_address_set.insert(addr.raw().to_vec());
from_address_list.push(addr.clone());
}
for c in threads.thread_nodes()[&h].children() {
stack.push(*c);
}
}
let env_hash = threads[&thread_hash].message().unwrap();
let envelope: EnvelopeRef = account.collection.get_env(env_hash);
let strings = self.make_entry_string(
&envelope,
context,
&from_address_list,
&threads[&thread_hash],
threads.is_snoozed(thread_hash),
);
drop(envelope);
/* draw flags */
let (x, _) = write_string_to_grid(
&strings.flag,
&mut self.content,
fg_color,
bg_color,
Attr::Default,
((0, 3 * idx), (width - 1, 3 * idx)),
None,
);
for c in self.content.row_iter((x, x + 3), 3 * idx) {
self.content[c].set_bg(bg_color);
}
/* draw subject */
let (x, _) = write_string_to_grid(
&strings.subject,
&mut self.content,
fg_color,
bg_color,
Attr::Bold,
((x, 3 * idx), (width - 1, 3 * idx)),
None,
);
let x = {
let mut x = x + 1;
for (t, &color) in strings.tags.split_whitespace().zip(strings.tags.1.iter()) {
let (_x, _) = write_string_to_grid(
t,
&mut self.content,
Color::White,
color,
Attr::Bold,
((x + 1, 3 * idx), (width - 1, 3 * idx)),
None,
);
for c in self.content.row_iter((x, x), 3 * idx) {
self.content[c].set_bg(color);
}
for c in self.content.row_iter((_x, _x), 3 * idx) {
self.content[c].set_bg(color);
self.content[c].set_keep_bg(true);
}
for c in self.content.row_iter((x + 1, _x), 3 * idx) {
self.content[c].set_keep_fg(true);
self.content[c].set_keep_bg(true);
}
for c in self.content.row_iter((x, x), 3 * idx) {
self.content[c].set_keep_bg(true);
}
x = _x + 1;
}
x
};
for c in self.content.row_iter((x, width.saturating_sub(1)), 3 * idx) {
self.content[c].set_ch(' ');
self.content[c].set_bg(bg_color);
}
/* Next line, draw date */
let (x, _) = write_string_to_grid(
&strings.date,
&mut self.content,
fg_color,
bg_color,
Attr::Default,
((0, 3 * idx + 1), (width - 1, 3 * idx + 1)),
None,
);
for c in self.content.row_iter((x, x + 4), 3 * idx + 1) {
self.content[c].set_ch('โ–');
self.content[c].set_bg(bg_color);
}
/* draw from */
let (x, _) = write_string_to_grid(
&strings.from,
&mut self.content,
fg_color,
bg_color,
Attr::Default,
((x + 4, 3 * idx + 1), (width - 1, 3 * idx + 1)),
None,
);
for c in self
.content
.row_iter((x, width.saturating_sub(1)), 3 * idx + 1)
{
self.content[c].set_ch('โ–');
self.content[c].set_bg(bg_color);
}
for c in self
.content
.row_iter((0, width.saturating_sub(1)), 3 * idx + 2)
{
self.content[c].set_ch('โ–“');
self.content[c].set_fg(padding_fg);
self.content[c].set_bg(bg_color);
}
}
}
impl Component for ConversationsListing {
@ -1191,37 +1339,6 @@ impl Component for ConversationsListing {
self.refresh_mailbox(context);
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_thread_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 = StackVec::from_iter(iter.into_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;
}
_ => {}
},
_ => {}

43
ui/src/components/mail/listing/plain.rs

@ -62,6 +62,7 @@ pub struct PlainListing {
filtered_selection: Vec<EnvelopeHash>,
filtered_order: FnvHashMap<EnvelopeHash, usize>,
selection: FnvHashMap<EnvelopeHash, bool>,
thread_hashes: FnvHashMap<EnvelopeHash, ThreadHash>,
local_collection: Vec<EnvelopeHash>,
/// If we must redraw on next redraw event
dirty: bool,
@ -81,7 +82,20 @@ impl MailListingTrait for PlainListing {
&mut self._row_updates
}
fn update_line(&mut self, _context: &Context, _thread_hash: ThreadHash) {}
fn get_focused_items(&self, context: &Context) -> StackVec<ThreadHash> {
let is_selection_empty = self.selection.values().cloned().any(std::convert::identity);
if is_selection_empty {
self.selection
.iter()
.filter(|(_, v)| **v)
.map(|(k, _)| self.thread_hashes[k])
.collect()
} else {
let mut ret = StackVec::new();
ret.push(self.get_thread_under_cursor(self.cursor_pos.2, context));
ret
}
}
}
impl ListingTrait for PlainListing {
@ -470,6 +484,7 @@ impl PlainListing {
subsort: (SortField::Date, SortOrder::Desc),
all_envelopes: fnv::FnvHashSet::default(),
local_collection: Vec::new(),
thread_hashes: FnvHashMap::default(),
order: FnvHashMap::default(),
filter_term: String::new(),
filtered_selection: Vec::new(),
@ -520,7 +535,7 @@ impl PlainListing {
{
colors.push(c);
} else {
colors.push(8);
colors.push(Color::Byte(8));
}
}
if !tags.is_empty() {
@ -587,6 +602,18 @@ impl PlainListing {
.iter()
.cloned()
.collect();
let env_lck = context.accounts[self.cursor_pos.0]
.collection
.envelopes
.read()
.unwrap();
self.thread_hashes = context.accounts[self.cursor_pos.0][folder_hash]
.unwrap()
.envelopes
.iter()
.map(|h| (*h, env_lck[h].thread()))
.collect();
drop(env_lck);
self.redraw_list(context);
if self.length > 0 {
@ -803,16 +830,16 @@ impl PlainListing {
t,
&mut columns[4],
Color::White,
Color::Byte(color),
color,
Attr::Bold,
((x + 1, idx), (min_width.4, idx)),
None,
);
for c in columns[4].row_iter((x, x), idx) {
columns[4][c].set_bg(Color::Byte(color));
columns[4][c].set_bg(color);
}
for c in columns[4].row_iter((_x, _x), idx) {
columns[4][c].set_bg(Color::Byte(color));
columns[4][c].set_bg(color);
columns[4][c].set_keep_bg(true);
}
for c in columns[4].row_iter((x + 1, _x), idx) {
@ -866,6 +893,12 @@ impl PlainListing {
}
}
fn get_thread_under_cursor(&self, cursor: usize, context: &Context) -> ThreadHash {
let account = &context.accounts[self.cursor_pos.0];
let env_hash = self.get_env_under_cursor(cursor, context);
account.collection.get_env(env_hash).thread()
}
fn format_date(envelope: &Envelope) -> String {
let d = std::time::UNIX_EPOCH + std::time::Duration::from_secs(envelope.date());
let now: std::time::Duration = std::time::SystemTime::now()

4
ui/src/components/mail/listing/thread.rs

@ -54,7 +54,9 @@ impl MailListingTrait for ThreadListing {
&mut self.row_updates
}
fn update_line(&mut self, _context: &Context, _thread_hash: ThreadHash) {}
fn get_focused_items(&self, _context: &Context) -> StackVec<ThreadHash> {
StackVec::new()
}
}
impl ListingTrait for ThreadListing {

22
ui/src/conf/tags.rs

@ -19,6 +19,7 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use crate::terminal::Color;
use serde::{Deserialize, Deserializer};
use std::collections::{hash_map::DefaultHasher, HashMap, HashSet};
use std::hash::Hasher;
@ -26,7 +27,7 @@ use std::hash::Hasher;
#[derive(Debug, Deserialize, Clone, Serialize)]
pub struct TagsSettings {
#[serde(default, deserialize_with = "tag_color_de")]
pub colors: HashMap<u64, u8>,
pub colors: HashMap<u64, Color>,
#[serde(default, deserialize_with = "tag_set_de")]
pub ignore_tags: HashSet<u64>,
}
@ -54,16 +55,29 @@ where
.collect())
}
pub fn tag_color_de<'de, D>(deserializer: D) -> std::result::Result<HashMap<u64, u8>, D::Error>
pub fn tag_color_de<'de, D>(deserializer: D) -> std::result::Result<HashMap<u64, Color>, D::Error>
where
D: Deserializer<'de>,
{
Ok(<HashMap<String, u8>>::deserialize(deserializer)?
#[derive(Deserialize)]
#[serde(untagged)]
enum _Color {
B(u8),
C(Color),
}
Ok(<HashMap<String, _Color>>::deserialize(deserializer)?
.into_iter()
.map(|(tag, color)| {
let mut hasher = DefaultHasher::new();
hasher.write(tag.as_bytes());
(hasher.finish(), color)
(
hasher.finish(),
match color {
_Color::B(b) => Color::Byte(b),
_Color::C(c) => c,
},
)
})
.collect())
}

609
ui/src/terminal/cells.rs

@ -28,6 +28,7 @@ use super::position::*;
use crate::state::Context;
use text_processing::wcwidth;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use std::convert::From;
use std::fmt;
use std::ops::{Deref, DerefMut, Index, IndexMut};
@ -740,6 +741,614 @@ impl Color {
}
}
impl Default for Color {
fn default() -> Self {
Color::Default
}
}
impl<'de> Deserialize<'de> for Color {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
if let Ok(s) = <String>::deserialize(deserializer) {
let byte = match s.as_str() {
"Aqua" => 14,
"Aquamarine1" => 122,
"Aquamarine2" => 86,
"Aquamarine3" => 79,
"Black" => 0,
"Blue" => 12,
"Blue1" => 21,
"Blue2" => 19,
"Blue3" => 20,
"BlueViolet" => 57,
"CadetBlue" => 72,
"CadetBlue1" => 73,
"Chartreuse1" => 118,
"Chartreuse2" => 112,
"Chartreuse3" => 82,
"Chartreuse4" => 70,
"Chartreuse5" => 76,
"Chartreuse6" => 64,
"CornflowerBlue" => 69,
"Cornsilk1" => 230,
"Cyan1" => 51,
"Cyan2" => 50,
"Cyan3" => 43,
"DarkBlue" => 18,
"DarkCyan" => 36,
"DarkGoldenrod" => 136,
"DarkGreen" => 22,
"DarkKhaki" => 143,
"DarkMagenta" => 90,
"DarkMagenta1" => 91,
"DarkOliveGreen1" => 192,
"DarkOliveGreen2" => 155,
"DarkOliveGreen3" => 191,
"DarkOliveGreen4" => 107,
"DarkOliveGreen5" => 113,
"DarkOliveGreen6" => 149,
"DarkOrange" => 208,
"DarkOrange2" => 130,
"DarkOrange3" => 166,
"DarkRed" => 52,
"DarkRed2" => 88,
"DarkSeaGreen" => 108,
"DarkSeaGreen1" => 158,
"DarkSeaGreen2" => 193,
"DarkSeaGreen3" => 151,
"DarkSeaGreen4" => 157,
"DarkSeaGreen5" => 115,
"DarkSeaGreen6" => 150,
"DarkSeaGreen7" => 65,
"DarkSeaGreen8" => 71,
"DarkSlateGray1" => 123,
"DarkSlateGray2" => 87,
"DarkSlateGray3" => 116,
"DarkTurquoise" => 44,
"DarkViolet" => 128,
"DarkViolet1" => 92,
"DeepPink1" => 199,
"DeepPink2" => 197,
"DeepPink3" => 198,
"DeepPink4" => 125,
"DeepPink6" => 162,
"DeepPink7" => 89,
"DeepPink8" => 53,
"DeepPink9" => 161,
"DeepSkyBlue1" => 39,
"DeepSkyBlue2" => 38,
"DeepSkyBlue3" => 31,
"DeepSkyBlue4" => 32,
"DeepSkyBlue5" => 23,
"DeepSkyBlue6" => 24,
"DeepSkyBlue7" => 25,
"DodgerBlue1" => 33,
"DodgerBlue2" => 27,
"DodgerBlue3" => 26,
"Fuchsia" => 13,
"Gold1" => 220,
"Gold2" => 142,
"Gold3" => 178,
"Green" => 2,
"Green1" => 46,
"Green2" => 34,
"Green3" => 40,
"Green4" => 28,
"GreenYellow" => 154,
"Grey" => 8,
"Grey0" => 16,
"Grey100" => 231,
"Grey11" => 234,
"Grey15" => 235,
"Grey19" => 236,
"Grey23" => 237,
"Grey27" => 238,
"Grey3" => 232,
"Grey30" => 239,
"Grey35" => 240,
"Grey37" => 59,
"Grey39" => 241,
"Grey42" => 242,
"Grey46" => 243,
"Grey50" => 244,
"Grey53" => 102,
"Grey54" => 245,
"Grey58" => 246,
"Grey62" => 247,
"Grey63" => 139,
"Grey66" => 248,
"Grey69" => 145,
"Grey7" => 233,
"Grey70" => 249,
"Grey74" => 250,
"Grey78" => 251,
"Grey82" => 252,
"Grey84" => 188,
"Grey85" => 253,
"Grey89" => 254,
"Grey93" => 255,
"Honeydew2" => 194,
"HotPink" => 205,
"HotPink1" => 206,
"HotPink2" => 169,
"HotPink3" => 132,
"HotPink4" => 168,
"IndianRed" => 131,
"IndianRed1" => 167,
"IndianRed2" => 204,
"IndianRed3" => 203,
"Khaki1" => 228,
"Khaki3" => 185,
"LightCoral" => 210,
"LightCyan2" => 195,
"LightCyan3" => 152,
"LightGoldenrod1" => 227,
"LightGoldenrod2" => 222,
"LightGoldenrod3" => 179,
"LightGoldenrod4" => 221,
"LightGoldenrod5" => 186,
"LightGreen" => 119,
"LightGreen1" => 120,
"LightPink1" => 217,
"LightPink2" => 174,
"LightPink3" => 95,
"LightSalmon1" => 216,
"LightSalmon2" => 137,
"LightSalmon3" => 173,
"LightSeaGreen" => 37,
"LightSkyBlue1" => 153,
"LightSkyBlue2" => 109,
"LightSkyBlue3" => 110,
"LightSlateBlue" => 105,
"LightSlateGrey" => 103,
"LightSteelBlue" => 147,
"LightSteelBlue1" => 189,
"LightSteelBlue3" => 146,
"LightYellow3" => 187,
"Lime" => 10,
"Magenta1" => 201,
"Magenta2" => 165,
"Magenta3" => 200,
"Magenta4" => 127,
"Magenta5" => 163,
"Magenta6" => 164,
"Maroon" => 1,
"MediumOrchid" => 134,
"MediumOrchid1" => 171,
"MediumOrchid2" => 207,
"MediumOrchid3" => 133,
"MediumPurple" => 104,
"MediumPurple1" => 141,
"MediumPurple2" => 135,
"MediumPurple3" => 140,
"MediumPurple4" => 97,
"MediumPurple5" => 98,
"MediumPurple6" => 60,
"MediumSpringGreen" => 49,
"MediumTurquoise" => 80,
"MediumVioletRed" => 126,
"MistyRose1" => 224,
"MistyRose3" => 181,
"NavajoWhite1" => 223,
"NavajoWhite3" => 144,
"Navy" => 4,
"NavyBlue" => 17,
"Olive" => 3,
"Orange1" => 214,
"Orange2" => 172,
"Orange3" => 58,
"Orange4" => 94,
"OrangeRed1" => 202,
"Orchid" => 170,
"Orchid1" => 213,
"Orchid2" => 212,
"PaleGreen1" => 121,
"PaleGreen2" => 156,
"PaleGreen3" => 114,
"PaleGreen4" => 77,
"PaleTurquoise1" => 159,
"PaleTurquoise4" => 66,
"PaleVioletRed1" => 211,
"Pink1" => 218,
"Pink3" => 175,
"Plum1" => 219,
"Plum2" => 183,
"Plum3" => 176,
"Plum4" => 96,
"Purple" => 129,
"Purple1" => 5,
"Purple2" => 93,
"Purple3" => 56,
"Purple4" => 54,
"Purple5" => 55,
"Red" => 9,
"Red1" => 196,
"Red2" => 124,
"Red3" => 160,
"RosyBrown" => 138,
"RoyalBlue1" => 63,
"Salmon1" => 209,
"SandyBrown" => 215,
"SeaGreen1" => 84,
"SeaGreen2" => 85,
"SeaGreen3" => 83,
"SeaGreen4" => 78,
"Silver" => 7,
"SkyBlue1" => 117,
"SkyBlue2" => 111,
"SkyBlue3" => 74,
"SlateBlue1" => 99,
"SlateBlue2" => 61,
"SlateBlue3" => 62,
"SpringGreen1" => 48,
"SpringGreen2" => 42,
"SpringGreen3" => 47,
"SpringGreen4" => 35,
"SpringGreen5" => 41,
"SpringGreen6" => 29,
"SteelBlue" => 67,
"SteelBlue1" => 75,
"SteelBlue2" => 81,
"SteelBlue3" => 68,
"Tan" => 180,
"Teal" => 6,
"Thistle1" => 225,
"Thistle3" => 182,
"Turquoise2" => 45,
"Turquoise4" => 30,
"Violet" => 177,
"Wheat1" => 229,
"Wheat4" => 101,
"White" => 15,
"Yellow" => 11,
"Yellow1" => 226,
"Yellow2" => 190,
"Yellow3" => 184,
"Yellow4" => 100,
"Yellow5" => 106,
"Yellow6" => 148,
s if s.starts_with("#")
&& s.len() == 7
&& s[1..].as_bytes().iter().all(|&b| {
(b >= b'0' && b <= b'9')
|| (b >= b'a' && b <= b'f')
|| (b >= b'A' && b <= b'F')
}) =>
{
return Ok(Color::Rgb(
u8::from_str_radix(&s[1..3], 16)
.map_err(|_| de::Error::custom("invalid `color` value"))?,
u8::from_str_radix(&s[3..5], 16)
.map_err(|_| de::Error::custom("invalid `color` value"))?,
u8::from_str_radix(&s[5..7], 16)
.map_err(|_| de::Error::custom("invalid `color` value"))?,
))
}
s if s.starts_with("#")
&& s.len() == 4
&& s[1..].as_bytes().iter().all(|&b| {
(b >= b'0' && b <= b'9')
|| (b >= b'a' && b <= b'f')
|| (b >= b'A' && b <= b'F')
}) =>
{
return Ok(Color::Rgb(
17 * u8::from_str_radix(&s[1..2], 16)
.map_err(|_| de::Error::custom("invalid `color` value"))?,
17 * u8::from_str_radix(&s[2..3], 16)
.map_err(|_| de::Error::custom("invalid `color` value"))?,
17 * u8::from_str_radix(&s[3..4], 16)
.map_err(|_| de::Error::custom("invalid `color` value"))?,
))
}
_ => return Err(de::Error::custom("invalid `color` value")),
};
return Ok(Color::Byte(byte));
}
Err(de::Error::custom("invalid `color` value"))
}
}
#[test]
fn test_color_de() {
#[derive(Debug, Deserialize, PartialEq)]
struct V {
k: Color,
}
macro_rules! test_color {
($s:literal, ok $v:expr) => {
assert_eq!(
toml::from_str::<V>(std::concat!("k = \"", $s, "\"")),
Ok(V { k: $v })
);
};
($s:literal, err $v:literal) => {
assert_eq!(
toml::from_str::<V>(std::concat!("k = \"", $s, "\""))
.unwrap_err()
.to_string(),
$v.to_string()
);
};
}
test_color!("#Ff6600", ok Color::Rgb(255, 102, 0));
test_color!("#f60", ok Color::Rgb(255, 102, 0));
test_color!("#gb0", err "invalid `color` value for key `k` at line 1 column 1");
test_color!("Olive", ok Color::Byte(3));
test_color!("Oafahifdave", err "invalid `color` value for key `k` at line 1 column 1");
}
impl Serialize for Color {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Color::Black | Color::Byte(0) => serializer.serialize_str("Black"),
Color::Byte(1) => serializer.serialize_str("Maroon"),
Color::Green | Color::Byte(2) => serializer.serialize_str("Green"),
Color::Byte(3) => serializer.serialize_str("Olive"),
Color::Byte(4) => serializer.serialize_str("Navy"),
Color::Byte(5) => serializer.serialize_str("Purple"),
Color::Byte(6) => serializer.serialize_str("Teal"),
Color::Byte(7) => serializer.serialize_str("Silver"),
Color::Byte(8) => serializer.serialize_str("Grey"),
Color::Red | Color::Byte(9) => serializer.serialize_str("Red"),
Color::Byte(10) => serializer.serialize_str("Lime"),
Color::Yellow | Color::Byte(11) => serializer.serialize_str("Yellow"),
Color::Blue | Color::Byte(12) => serializer.serialize_str("Blue"),
Color::Byte(13) => serializer.serialize_str("Fuchsia"),
Color::Byte(14) => serializer.serialize_str("Aqua"),
Color::White | Color::Byte(15) => serializer.serialize_str("White"),
Color::Byte(16) => serializer.serialize_str("Grey0"),
Color::Byte(17) => serializer.serialize_str("NavyBlue"),
Color::Byte(18) => serializer.serialize_str("DarkBlue"),
Color::Byte(19) => serializer.serialize_str("Blue3"),
Color::Byte(20) => serializer.serialize_str("Blue3"),
Color::Byte(21) => serializer.serialize_str("Blue1"),
Color::Byte(22) => serializer.serialize_str("DarkGreen"),
Color::Byte(23) => serializer.serialize_str("DeepSkyBlue4"),
Color::Byte(24) => serializer.serialize_str("DeepSkyBlue4"),
Color::Byte(25) => serializer.serialize_str("DeepSkyBlue4"),
Color::Byte(26) => serializer.serialize_str("DodgerBlue3"),