diff --git a/docs/meli.1 b/docs/meli.1 index 6aaf45947..ccccd12d2 100644 --- a/docs/meli.1 +++ b/docs/meli.1 @@ -488,6 +488,15 @@ to .It Cm printenv Ar KEY print environment variable .Ar KEY +.It Cm quit +Quits +.Nm Ns +\&. +.It Cm reload-config +Reloads configuration but only if account configuration is unchanged. +Useful if you want to reload some settings without restarting +.Nm Ns +\&. .El .Sh SHORTCUTS See diff --git a/src/command.rs b/src/command.rs index ac08a898f..2ad88fd35 100644 --- a/src/command.rs +++ b/src/command.rs @@ -790,6 +790,17 @@ Alternatives(&[to_stream!(One(Literal("add-attachment")), One(Filepath)), to_str Ok((input, Quit)) } ) + }, + { tags: ["reload-config"], + desc: "reload configuration file", + tokens: &[One(Literal("reload-config"))], + parser:( + fn reload_config(input: &[u8]) -> IResult<&[u8], Action> { + let (input, _) = tag("reload-config")(input.trim())?; + let (input, _) = eof(input.trim())?; + Ok((input, ReloadConfiguration)) + } + ) } ]); @@ -883,6 +894,7 @@ pub fn parse_command(input: &[u8]) -> Result { account_action, print_setting, toggle_mouse, + reload_config, quit, ))(input) .map(|(_, v)| v) diff --git a/src/command/actions.rs b/src/command/actions.rs index 110a637c3..85895644d 100644 --- a/src/command/actions.rs +++ b/src/command/actions.rs @@ -121,6 +121,7 @@ pub enum Action { Mailbox(AccountName, MailboxOperation), AccountAction(AccountName, AccountAction), PrintSetting(String), + ReloadConfiguration, ToggleMouse, Quit, } @@ -143,6 +144,7 @@ impl Action { Action::PrintSetting(_) => false, Action::ToggleMouse => false, Action::Quit => true, + Action::ReloadConfiguration => false, } } } diff --git a/src/components/contacts.rs b/src/components/contacts.rs index 4bb1e852b..9feb6e42a 100644 --- a/src/components/contacts.rs +++ b/src/components/contacts.rs @@ -59,13 +59,6 @@ impl fmt::Display for ContactManager { impl ContactManager { fn new(context: &Context) -> Self { let theme_default: ThemeAttribute = crate::conf::value(context, "theme_default"); - let default_cell = { - let mut ret = Cell::with_char(' '); - ret.set_fg(theme_default.fg) - .set_bg(theme_default.bg) - .set_attrs(theme_default.attrs); - ret - }; ContactManager { id: Uuid::nil(), parent_id: Uuid::nil(), @@ -73,7 +66,7 @@ impl ContactManager { mode: ViewMode::Edit, form: FormWidget::default(), account_pos: 0, - content: CellBuffer::new(100, 1, default_cell), + content: CellBuffer::new_with_context(100, 1, None, context), theme_default, dirty: true, has_changes: false, @@ -189,6 +182,15 @@ impl Component for ContactManager { } fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool { + match event { + UIEvent::ConfigReload { old_settings: _ } => { + self.theme_default = crate::conf::value(context, "theme_default"); + self.content = CellBuffer::new_with_context(100, 1, None, context); + self.initialized = false; + self.set_dirty(true); + } + _ => {} + } match self.mode { ViewMode::Discard(ref mut selector) => { if selector.process_event(event, context) { diff --git a/src/components/contacts/contact_list.rs b/src/components/contacts/contact_list.rs index d56cee967..e0b345e31 100644 --- a/src/components/contacts/contact_list.rs +++ b/src/components/contacts/contact_list.rs @@ -596,6 +596,14 @@ impl Component for ContactList { } fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool { + if let UIEvent::ConfigReload { old_settings: _ } = event { + self.theme_default = crate::conf::value(context, "theme_default"); + self.initialized = false; + self.sidebar_divider = context.settings.listing.sidebar_divider; + self.sidebar_divider_theme = conf::value(context, "mail.sidebar_divider"); + self.set_dirty(true); + } + if let Some(ref mut v) = self.view { if v.process_event(event, context) { return true; diff --git a/src/components/mail/compose.rs b/src/components/mail/compose.rs index 97f279455..097ce4384 100644 --- a/src/components/mail/compose.rs +++ b/src/components/mail/compose.rs @@ -1177,6 +1177,9 @@ impl Component for Composer { } match *event { + UIEvent::ConfigReload { old_settings: _ } => { + self.set_dirty(true); + } UIEvent::Resize => { self.set_dirty(true); } diff --git a/src/components/mail/listing.rs b/src/components/mail/listing.rs index f8b0f13b8..2e74dbf86 100644 --- a/src/components/mail/listing.rs +++ b/src/components/mail/listing.rs @@ -613,6 +613,15 @@ impl Component for Listing { fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool { match event { + UIEvent::ConfigReload { old_settings: _ } => { + self.theme_default = crate::conf::value(context, "theme_default"); + let account_hash = context.accounts[self.cursor_pos.0].hash(); + self.sidebar_divider = + *account_settings!(context[account_hash].listing.sidebar_divider); + self.sidebar_divider_theme = conf::value(context, "mail.sidebar_divider"); + self.menu_content = CellBuffer::new_with_context(0, 0, None, context); + self.set_dirty(true); + } UIEvent::Timer(n) if *n == self.menu_scrollbar_show_timer.id() => { if self.show_menu_scrollbar == ShowMenuScrollbar::True { self.show_menu_scrollbar = ShowMenuScrollbar::False; diff --git a/src/components/mail/listing/compact.rs b/src/components/mail/listing/compact.rs index 882a77435..dbc4b940f 100644 --- a/src/components/mail/listing/compact.rs +++ b/src/components/mail/listing/compact.rs @@ -1691,6 +1691,43 @@ impl Component for CompactListing { } } match *event { + UIEvent::ConfigReload { old_settings: _ } => { + self.color_cache = ColorCache { + even_unseen: crate::conf::value(context, "mail.listing.compact.even_unseen"), + even_selected: crate::conf::value( + context, + "mail.listing.compact.even_selected", + ), + even_highlighted: crate::conf::value( + context, + "mail.listing.compact.even_highlighted", + ), + odd_unseen: crate::conf::value(context, "mail.listing.compact.odd_unseen"), + odd_selected: crate::conf::value(context, "mail.listing.compact.odd_selected"), + odd_highlighted: crate::conf::value( + context, + "mail.listing.compact.odd_highlighted", + ), + even: crate::conf::value(context, "mail.listing.compact.even"), + odd: crate::conf::value(context, "mail.listing.compact.odd"), + attachment_flag: crate::conf::value(context, "mail.listing.attachment_flag"), + thread_snooze_flag: crate::conf::value( + context, + "mail.listing.thread_snooze_flag", + ), + tag_default: crate::conf::value(context, "mail.listing.tag_default"), + theme_default: crate::conf::value(context, "theme_default"), + ..self.color_cache + }; + if !context.settings.terminal.use_color() { + self.color_cache.highlighted.attrs |= Attr::REVERSE; + self.color_cache.tag_default.attrs |= Attr::REVERSE; + self.color_cache.even_highlighted.attrs |= Attr::REVERSE; + self.color_cache.odd_highlighted.attrs |= Attr::REVERSE; + } + self.refresh_mailbox(context, true); + self.set_dirty(true); + } UIEvent::MailboxUpdate((ref idxa, ref idxf)) if (*idxa, *idxf) == (self.new_cursor_pos.0, self.cursor_pos.1) => { diff --git a/src/components/mail/listing/conversations.rs b/src/components/mail/listing/conversations.rs index 47e2570d8..aeff946a3 100644 --- a/src/components/mail/listing/conversations.rs +++ b/src/components/mail/listing/conversations.rs @@ -1616,6 +1616,34 @@ impl Component for ConversationsListing { } } match *event { + UIEvent::ConfigReload { old_settings: _ } => { + self.color_cache = ColorCache { + theme_default: crate::conf::value(context, "mail.listing.conversations"), + subject: crate::conf::value(context, "mail.listing.conversations.subject"), + from: crate::conf::value(context, "mail.listing.conversations.from"), + date: crate::conf::value(context, "mail.listing.conversations.date"), + selected: crate::conf::value(context, "mail.listing.conversations.selected"), + unseen: crate::conf::value(context, "mail.listing.conversations.unseen"), + highlighted: crate::conf::value( + context, + "mail.listing.conversations.highlighted", + ), + attachment_flag: crate::conf::value(context, "mail.listing.attachment_flag"), + thread_snooze_flag: crate::conf::value( + context, + "mail.listing.thread_snooze_flag", + ), + tag_default: crate::conf::value(context, "mail.listing.tag_default"), + ..self.color_cache + }; + + if !context.settings.terminal.use_color() { + self.color_cache.highlighted.attrs |= Attr::REVERSE; + self.color_cache.tag_default.attrs |= Attr::REVERSE; + } + self.refresh_mailbox(context, true); + self.set_dirty(true); + } UIEvent::MailboxUpdate((ref idxa, ref idxf)) if (*idxa, *idxf) == (self.new_cursor_pos.0, self.cursor_pos.1) => { diff --git a/src/components/mail/listing/plain.rs b/src/components/mail/listing/plain.rs index e0a38ea87..93b9dec6b 100644 --- a/src/components/mail/listing/plain.rs +++ b/src/components/mail/listing/plain.rs @@ -1215,6 +1215,41 @@ impl Component for PlainListing { } } match *event { + UIEvent::ConfigReload { old_settings: _ } => { + self.color_cache = ColorCache { + even: crate::conf::value(context, "mail.listing.plain.even"), + odd: crate::conf::value(context, "mail.listing.plain.odd"), + even_unseen: crate::conf::value(context, "mail.listing.plain.even_unseen"), + odd_unseen: crate::conf::value(context, "mail.listing.plain.odd_unseen"), + even_highlighted: crate::conf::value( + context, + "mail.listing.plain.even_highlighted", + ), + odd_highlighted: crate::conf::value( + context, + "mail.listing.plain.odd_highlighted", + ), + even_selected: crate::conf::value(context, "mail.listing.plain.even_selected"), + odd_selected: crate::conf::value(context, "mail.listing.plain.odd_selected"), + attachment_flag: crate::conf::value(context, "mail.listing.attachment_flag"), + thread_snooze_flag: crate::conf::value( + context, + "mail.listing.thread_snooze_flag", + ), + tag_default: crate::conf::value(context, "mail.listing.tag_default"), + theme_default: crate::conf::value(context, "theme_default"), + ..self.color_cache + }; + if !context.settings.terminal.use_color() { + self.color_cache.highlighted.attrs |= Attr::REVERSE; + self.color_cache.tag_default.attrs |= Attr::REVERSE; + self.color_cache.even_highlighted.attrs |= Attr::REVERSE; + self.color_cache.odd_highlighted.attrs |= Attr::REVERSE; + } + + self.refresh_mailbox(context, true); + self.set_dirty(true); + } UIEvent::MailboxUpdate((ref idxa, ref idxf)) if (*idxa, *idxf) == (self.new_cursor_pos.0, self.cursor_pos.1) => { diff --git a/src/components/mail/listing/thread.rs b/src/components/mail/listing/thread.rs index 705fada0a..e21bfb6ae 100644 --- a/src/components/mail/listing/thread.rs +++ b/src/components/mail/listing/thread.rs @@ -1184,6 +1184,39 @@ impl Component for ThreadListing { } } match *event { + UIEvent::ConfigReload { old_settings: _ } => { + self.color_cache = ColorCache { + even_unseen: crate::conf::value(context, "mail.listing.plain.even_unseen"), + even_selected: crate::conf::value(context, "mail.listing.plain.even_selected"), + even_highlighted: crate::conf::value( + context, + "mail.listing.plain.even_highlighted", + ), + odd_unseen: crate::conf::value(context, "mail.listing.plain.odd_unseen"), + odd_selected: crate::conf::value(context, "mail.listing.plain.odd_selected"), + odd_highlighted: crate::conf::value( + context, + "mail.listing.plain.odd_highlighted", + ), + even: crate::conf::value(context, "mail.listing.plain.even"), + odd: crate::conf::value(context, "mail.listing.plain.odd"), + attachment_flag: crate::conf::value(context, "mail.listing.attachment_flag"), + thread_snooze_flag: crate::conf::value( + context, + "mail.listing.thread_snooze_flag", + ), + tag_default: crate::conf::value(context, "mail.listing.tag_default"), + theme_default: crate::conf::value(context, "theme_default"), + ..self.color_cache + }; + if !context.settings.terminal.use_color() { + self.color_cache.highlighted.attrs |= Attr::REVERSE; + self.color_cache.tag_default.attrs |= Attr::REVERSE; + self.color_cache.even_highlighted.attrs |= Attr::REVERSE; + self.color_cache.odd_highlighted.attrs |= Attr::REVERSE; + } + self.set_dirty(true); + } UIEvent::Input(Key::Char('\n')) if !self.unfocused => { self.unfocused = true; self.dirty = true; diff --git a/src/components/mail/status.rs b/src/components/mail/status.rs index ca5748b5b..d63e5384a 100644 --- a/src/components/mail/status.rs +++ b/src/components/mail/status.rs @@ -403,6 +403,10 @@ impl Component for AccountStatus { fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool { let shortcuts = self.get_shortcuts(context); match *event { + UIEvent::ConfigReload { old_settings: _ } => { + self.theme_default = crate::conf::value(context, "theme_default"); + self.set_dirty(true); + } UIEvent::Resize => { self.dirty = true; } diff --git a/src/components/mail/view.rs b/src/components/mail/view.rs index bc329354b..3ad73f342 100644 --- a/src/components/mail/view.rs +++ b/src/components/mail/view.rs @@ -1763,6 +1763,10 @@ impl Component for MailView { let shortcuts = &self.get_shortcuts(context); match *event { + UIEvent::ConfigReload { old_settings: _ } => { + self.theme_default = crate::conf::value(context, "theme_default"); + self.set_dirty(true); + } UIEvent::Input(ref key) if shortcut!(key == shortcuts[MailView::DESCRIPTION]["reply"]) => { diff --git a/src/components/utilities.rs b/src/components/utilities.rs index 7d8697845..f1a233ed6 100644 --- a/src/components/utilities.rs +++ b/src/components/utilities.rs @@ -442,6 +442,25 @@ impl Component for StatusBar { } match event { + UIEvent::ConfigReload { old_settings: _ } => { + let mut progress_spinner = ProgressSpinner::new(19, context); + match context.settings.terminal.progress_spinner_sequence.as_ref() { + Some(conf::terminal::ProgressSpinnerSequence::Integer(k)) => { + progress_spinner.set_kind(*k); + } + Some(conf::terminal::ProgressSpinnerSequence::Custom(ref s)) => { + progress_spinner.set_custom_kind(s.clone()); + } + None => {} + } + if self.progress_spinner.is_active() { + progress_spinner.start(); + } + self.progress_spinner = progress_spinner; + self.mouse = context.settings.terminal.use_mouse.is_true(); + self.set_dirty(true); + self.container.set_dirty(true); + } UIEvent::ChangeMode(m) => { let offset = self.status.find('|').unwrap_or_else(|| self.status.len()); self.status.replace_range( @@ -1194,6 +1213,10 @@ impl Component for Tabbed { fn process_event(&mut self, mut event: &mut UIEvent, context: &mut Context) -> bool { let shortcuts = &self.help_curr_views; match &mut event { + UIEvent::ConfigReload { old_settings: _ } => { + self.theme_default = crate::conf::value(context, "theme_default"); + self.set_dirty(true); + } UIEvent::Input(Key::Alt(no)) if *no >= '1' && *no <= '9' => { let no = *no as usize - '1' as usize; if no < self.children.len() { diff --git a/src/components/utilities/dialogs.rs b/src/components/utilities/dialogs.rs index 4e5d788ff..66d95edaa 100644 --- a/src/components/utilities/dialogs.rs +++ b/src/components/utilities/dialogs.rs @@ -49,6 +49,7 @@ pub struct Selector, + entry_titles: Vec, pub content: CellBuffer, theme_default: ThemeAttribute, @@ -104,6 +105,12 @@ impl Component for UIDialo } fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool { + if let UIEvent::ConfigReload { old_settings: _ } = event { + self.initialise(context); + self.set_dirty(true); + return false; + } + let (width, height) = self.content.size(); let shortcuts = self.get_shortcuts(context); let mut highlighted_attrs = crate::conf::value(context, "widgets.options.highlighted"); @@ -419,6 +426,12 @@ impl Component for UIConfirmationDialog { } fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool { + if let UIEvent::ConfigReload { old_settings: _ } = event { + self.initialise(context); + self.set_dirty(true); + return false; + } + let (width, height) = self.content.size(); let shortcuts = self.get_shortcuts(context); let mut highlighted_attrs = crate::conf::value(context, "widgets.options.highlighted"); @@ -734,68 +747,15 @@ impl Component for UIConfirmationDialog { impl Selector { pub fn new( title: &str, - entries: Vec<(T, String)>, + mut entries: Vec<(T, String)>, single_only: bool, done_fn: F, context: &Context, ) -> Selector { - let theme_default = crate::conf::value(context, "theme_default"); - let width = std::cmp::max( - OK_CANCEL.len(), - std::cmp::max( - entries - .iter() - .max_by_key(|e| e.1.len()) - .map(|v| v.1.len()) - .unwrap_or(0), - title.len(), - ), - ) + 5; - let height = entries.len() - + if single_only { - 0 - } else { - /* Extra room for buttons Okay/Cancel */ - 2 - }; - let mut content = CellBuffer::new_with_context(width, height, None, context); - if single_only { - for (i, e) in entries.iter().enumerate() { - write_string_to_grid( - &e.1, - &mut content, - theme_default.fg, - theme_default.bg, - theme_default.attrs, - ((0, i), (width - 1, i)), - None, - ); - } - } else { - for (i, e) in entries.iter().enumerate() { - write_string_to_grid( - &format!("[ ] {}", e.1), - &mut content, - theme_default.fg, - theme_default.bg, - theme_default.attrs, - ((0, i), (width - 1, i)), - None, - ); - } - write_string_to_grid( - OK_CANCEL, - &mut content, - theme_default.fg, - theme_default.bg, - theme_default.attrs | Attr::BOLD, - ( - ((width - OK_CANCEL.len()) / 2, height - 1), - (width - 1, height - 1), - ), - None, - ); - } + let entry_titles = entries + .iter_mut() + .map(|(_id, ref mut title)| std::mem::replace(title, String::new())) + .collect::>(); let mut identifiers: Vec<(T, bool)> = entries.into_iter().map(|(id, _)| (id, false)).collect(); if single_only { @@ -803,10 +763,11 @@ impl Selec identifiers[0].1 = true; } - Selector { + let mut ret = Selector { single_only, entries: identifiers, - content, + entry_titles, + content: Default::default(), cursor: SelectorCursor::Unfocused, vertical_alignment: Alignment::Center, horizontal_alignment: Alignment::Center, @@ -814,9 +775,72 @@ impl Selec done: false, done_fn, dirty: true, - theme_default, + theme_default: Default::default(), id: ComponentId::new_v4(), + }; + ret.initialise(context); + ret + } + + fn initialise(&mut self, context: &Context) { + self.theme_default = crate::conf::value(context, "theme_default"); + let width = std::cmp::max( + OK_CANCEL.len(), + std::cmp::max( + self.entry_titles + .iter() + .max_by_key(|e| e.len()) + .map(|v| v.len()) + .unwrap_or(0), + self.title.len(), + ), + ) + 5; + let height = self.entries.len() + + if self.single_only { + 0 + } else { + /* Extra room for buttons Okay/Cancel */ + 2 + }; + let mut content = CellBuffer::new_with_context(width, height, None, context); + if self.single_only { + for (i, e) in self.entry_titles.iter().enumerate() { + write_string_to_grid( + &e, + &mut content, + self.theme_default.fg, + self.theme_default.bg, + self.theme_default.attrs, + ((0, i), (width - 1, i)), + None, + ); + } + } else { + for (i, e) in self.entry_titles.iter().enumerate() { + write_string_to_grid( + &format!("[ ] {}", &e), + &mut content, + self.theme_default.fg, + self.theme_default.bg, + self.theme_default.attrs, + ((0, i), (width - 1, i)), + None, + ); + } + write_string_to_grid( + OK_CANCEL, + &mut content, + self.theme_default.fg, + self.theme_default.bg, + self.theme_default.attrs | Attr::BOLD, + ( + ((width - OK_CANCEL.len()) / 2, height - 1), + (width - 1, height - 1), + ), + None, + ); } + self.content = content; } pub fn is_done(&self) -> bool { diff --git a/src/components/utilities/pager.rs b/src/components/utilities/pager.rs index 8e1e7c0d6..da46653ec 100644 --- a/src/components/utilities/pager.rs +++ b/src/components/utilities/pager.rs @@ -609,6 +609,10 @@ impl Component for Pager { fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool { let shortcuts = self.get_shortcuts(context); match event { + UIEvent::ConfigReload { old_settings: _ } => { + self.set_colors(crate::conf::value(context, "theme_default")); + self.set_dirty(true); + } UIEvent::Input(ref key) if shortcut!(key == shortcuts[Self::DESCRIPTION]["scroll_up"]) => { diff --git a/src/state.rs b/src/state.rs index e5a40b150..1553c5238 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1075,6 +1075,39 @@ impl State { })), &mut self.context, ))); + } else if let Action::ReloadConfiguration = action { + match Settings::new().and_then(|new_settings| { + let old_accounts = self.context.settings.accounts.keys().collect::>(); + let new_accounts = new_settings.accounts.keys().collect::>(); + if old_accounts != new_accounts { + return Err("cannot reload account configuration changes; restart meli instead.".into()); + } + for (key, acc) in new_settings.accounts.iter() { + if toml::Value::try_from(&acc) != toml::Value::try_from(&self.context.settings.accounts[key]) { + return Err("cannot reload account configuration changes; restart meli instead.".into()); + } + } + if toml::Value::try_from(&new_settings) == toml::Value::try_from(&self.context.settings) { + return Err("No changes detected.".into()); + } + Ok(new_settings) + }) { + Ok(new_settings) => { + let old_settings = std::mem::replace(&mut self.context.settings, new_settings); + self.context.replies.push_back(UIEvent::ConfigReload { + old_settings + }); + self.context.replies.push_back(UIEvent::Resize); + } + Err(err) => { + self.context.replies.push_back(UIEvent::StatusEvent( + StatusEvent::DisplayMessage(format!( + "Could not load configuration: {}", + err + )), + )); + } + } } else { self.exec_command(action); } diff --git a/src/types.rs b/src/types.rs index 0fa2eed00..47dc6a021 100644 --- a/src/types.rs +++ b/src/types.rs @@ -146,6 +146,9 @@ pub enum UIEvent { Callback(CallbackFn), GlobalUIDialog(Box), Timer(Uuid), + ConfigReload { + old_settings: crate::conf::Settings, + }, } pub struct CallbackFn(pub Box () + Send + 'static>);