From fa1a25419bc821ceb075ab5ab95f5be6fc80325f Mon Sep 17 00:00:00 2001 From: TobTobXX Date: Mon, 3 Apr 2023 01:25:22 +0200 Subject: [PATCH 1/6] Add entering normal mode as autosave trigger --- helix-term/src/ui/editor.rs | 4 ++-- helix-view/src/editor.rs | 31 +++++++++++++++++++++++++++++-- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index fd8e8fb21b47..7fe35eb3a4c7 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -23,7 +23,7 @@ use helix_core::{ }; use helix_view::{ document::{Mode, SavePoint, SCRATCH_BUFFER_NAME}, - editor::{CompleteAction, CursorShapeConfig}, + editor::{CompleteAction, CursorShapeConfig, AutoSaveTrigger}, graphics::{Color, CursorKind, Modifier, Rect, Style}, input::{KeyEvent, MouseButton, MouseEvent, MouseEventKind}, keyboard::{KeyCode, KeyModifiers}, @@ -1346,7 +1346,7 @@ impl Component for EditorView { Event::IdleTimeout => self.handle_idle_timeout(&mut cx), Event::FocusGained => EventResult::Ignored(None), Event::FocusLost => { - if context.editor.config().auto_save { + if context.editor.config().auto_save.contains(&AutoSaveTrigger::Unfocused) { if let Err(e) = commands::typed::write_all_impl(context, false, false) { context.editor.set_error(format!("{}", e)); } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 34c59b9b4b75..82035b3d896b 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -211,6 +211,15 @@ impl Default for FilePickerConfig { } } +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum AutoSaveTrigger { + /// Auto save when the terminal window looses focus + Unfocused, + /// Auto save when the editor (re-)enters normal mode. + NormalMode, +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case", default, deny_unknown_fields)] pub struct Config { @@ -241,7 +250,7 @@ pub struct Config { /// Automatic formatting on save. Defaults to true. pub auto_format: bool, /// Automatic save on focus lost. Defaults to false. - pub auto_save: bool, + pub auto_save: Vec, /// Set a global text_width pub text_width: usize, /// Time in milliseconds since last keypress before idle timers trigger. @@ -732,7 +741,7 @@ impl Default for Config { auto_pairs: AutoPairConfig::default(), auto_completion: true, auto_format: true, - auto_save: false, + auto_save: Vec::new(), idle_timeout: Duration::from_millis(400), completion_trigger_len: 2, auto_info: true, @@ -1658,6 +1667,24 @@ impl Editor { doc.set_selection(view.id, selection); doc.restore_cursor = false; } + + // Trigger autosave if configured + if self.config().auto_save.contains(&AutoSaveTrigger::NormalMode) { + match self.save::(doc!(self).id, None, false) { + Err(_) => { + // Silently ignore errors. + // Common sources of errors include: + // - No file path set on focused document + // - Partent directory doesn't exist. + // + // In either case, autosave freaking out is probably not + // desired. + } + Ok(_) => { + // TODO: make 'modified' icon in statusline disappear. + }, + } + } } pub fn current_stack_frame(&self) -> Option<&StackFrame> { From 950cd8a993681f64da169f048429d53908b5f298 Mon Sep 17 00:00:00 2001 From: TobTobXX Date: Mon, 3 Apr 2023 01:38:57 +0200 Subject: [PATCH 2/6] cargo fmt --- helix-term/src/ui/editor.rs | 10 ++++++++-- helix-view/src/editor.rs | 8 ++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 7fe35eb3a4c7..2eb7eac10103 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -23,7 +23,7 @@ use helix_core::{ }; use helix_view::{ document::{Mode, SavePoint, SCRATCH_BUFFER_NAME}, - editor::{CompleteAction, CursorShapeConfig, AutoSaveTrigger}, + editor::{AutoSaveTrigger, CompleteAction, CursorShapeConfig}, graphics::{Color, CursorKind, Modifier, Rect, Style}, input::{KeyEvent, MouseButton, MouseEvent, MouseEventKind}, keyboard::{KeyCode, KeyModifiers}, @@ -1346,7 +1346,13 @@ impl Component for EditorView { Event::IdleTimeout => self.handle_idle_timeout(&mut cx), Event::FocusGained => EventResult::Ignored(None), Event::FocusLost => { - if context.editor.config().auto_save.contains(&AutoSaveTrigger::Unfocused) { + // Trigger autosave if configured + if context + .editor + .config() + .auto_save + .contains(&AutoSaveTrigger::Unfocused) + { if let Err(e) = commands::typed::write_all_impl(context, false, false) { context.editor.set_error(format!("{}", e)); } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 82035b3d896b..356674dbc13b 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -1669,7 +1669,11 @@ impl Editor { } // Trigger autosave if configured - if self.config().auto_save.contains(&AutoSaveTrigger::NormalMode) { + if self + .config() + .auto_save + .contains(&AutoSaveTrigger::NormalMode) + { match self.save::(doc!(self).id, None, false) { Err(_) => { // Silently ignore errors. @@ -1682,7 +1686,7 @@ impl Editor { } Ok(_) => { // TODO: make 'modified' icon in statusline disappear. - }, + } } } } From fa1a45b04245ca64361dc736603ec613dc4090ad Mon Sep 17 00:00:00 2001 From: TobTobXX Date: Mon, 3 Apr 2023 01:48:29 +0200 Subject: [PATCH 3/6] Documentation for auto-save --- book/src/configuration.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/book/src/configuration.md b/book/src/configuration.md index 4c8ff0647cbf..7d952a8b1c69 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -50,7 +50,7 @@ Its settings will be merged with the configuration directory `config.toml` and t | `gutters` | Gutters to display: Available are `diagnostics` and `diff` and `line-numbers` and `spacer`, note that `diagnostics` also includes other features like breakpoints, 1-width padding will be inserted if gutters is non-empty | `["diagnostics", "spacer", "line-numbers", "spacer", "diff"]` | | `auto-completion` | Enable automatic pop up of auto-completion | `true` | | `auto-format` | Enable automatic formatting on save | `true` | -| `auto-save` | Enable automatic saving on the focus moving away from Helix. Requires [focus event support](https://github.com/helix-editor/helix/wiki/Terminal-Support) from your terminal | `false` | +| `auto-save` | Enable automatic saving on certain triggers, such as `"unfocused"` (when the terminal looses focus)[^1] or `"normal-mode"` (when entering normal mode) | `[]` | | `idle-timeout` | Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant | `400` | | `completion-trigger-len` | The min-length of word under cursor to trigger autocompletion | `2` | | `completion-replace` | Set to `true` to make completions always replace the entire word and not just the part before the cursor | `false` | @@ -63,6 +63,8 @@ Its settings will be merged with the configuration directory `config.toml` and t | `text-width` | Maximum line length. Used for the `:reflow` command and soft-wrapping if `soft-wrap.wrap_at_text_width` is set | `80` | | `workspace-lsp-roots` | Directories relative to the workspace root that are treated as LSP roots. Should only be set in `.helix/config.toml` | `[]` | +[^1]: Requires [focus event support](https://github.com/helix-editor/helix/wiki/Terminal-Support) from your terminal. + ### `[editor.statusline]` Section Allows configuring the statusline at the bottom of the editor. From ffc71fd95e49b227256bfe86e3cf507986375d4f Mon Sep 17 00:00:00 2001 From: TobTobXX Date: Mon, 3 Apr 2023 01:51:47 +0200 Subject: [PATCH 4/6] clippy --- helix-view/src/editor.rs | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 356674dbc13b..0bb999377cc5 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -1674,20 +1674,17 @@ impl Editor { .auto_save .contains(&AutoSaveTrigger::NormalMode) { - match self.save::(doc!(self).id, None, false) { - Err(_) => { - // Silently ignore errors. - // Common sources of errors include: - // - No file path set on focused document - // - Partent directory doesn't exist. - // - // In either case, autosave freaking out is probably not - // desired. - } - Ok(_) => { - // TODO: make 'modified' icon in statusline disappear. - } + if self.save::(doc!(self).id, None, false).is_ok() { + // TODO: make 'modified' icon in statusline disappear. } + + // Silently ignore errors. + // Common sources of errors include: + // - No file path set on focused document + // - Partent directory doesn't exist. + // + // In either case, autosave freaking out is probably not + // desired. } } From 0ebb73303c77713d993db1897c05b487d3115dea Mon Sep 17 00:00:00 2001 From: TobTobXX Date: Mon, 3 Apr 2023 02:17:21 +0200 Subject: [PATCH 5/6] Compatibility with old auto-save setting --- helix-term/src/ui/editor.rs | 2 +- helix-view/src/editor.rs | 33 ++++++++++++++++++++++++++++++--- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 2eb7eac10103..1feb0b12eb41 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -1351,7 +1351,7 @@ impl Component for EditorView { .editor .config() .auto_save - .contains(&AutoSaveTrigger::Unfocused) + .should_trigger_on(AutoSaveTrigger::Unfocused) { if let Err(e) = commands::typed::write_all_impl(context, false, false) { context.editor.set_error(format!("{}", e)); diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 0bb999377cc5..5b537619307e 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -211,6 +211,15 @@ impl Default for FilePickerConfig { } } +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum AutoSaveConfig { + /// Compatibility with the old `auto-save = true` setting. + Unfocused(bool), + /// The new way: a list of autosave triggers. + TriggerList(Vec), +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum AutoSaveTrigger { @@ -220,6 +229,24 @@ pub enum AutoSaveTrigger { NormalMode, } +impl Default for AutoSaveConfig { + fn default() -> Self { + Self::TriggerList(Vec::new()) + } +} + +impl AutoSaveConfig { + pub fn should_trigger_on(&self, trigger: AutoSaveTrigger) -> bool { + match self { + // Compatibility with old setting: + // false -> false + // true -> Unfocused trigger is active + AutoSaveConfig::Unfocused(s) => *s && trigger == AutoSaveTrigger::Unfocused, + AutoSaveConfig::TriggerList(triggers) => triggers.contains(&trigger), + } + } +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case", default, deny_unknown_fields)] pub struct Config { @@ -250,7 +277,7 @@ pub struct Config { /// Automatic formatting on save. Defaults to true. pub auto_format: bool, /// Automatic save on focus lost. Defaults to false. - pub auto_save: Vec, + pub auto_save: AutoSaveConfig, /// Set a global text_width pub text_width: usize, /// Time in milliseconds since last keypress before idle timers trigger. @@ -741,7 +768,7 @@ impl Default for Config { auto_pairs: AutoPairConfig::default(), auto_completion: true, auto_format: true, - auto_save: Vec::new(), + auto_save: AutoSaveConfig::default(), idle_timeout: Duration::from_millis(400), completion_trigger_len: 2, auto_info: true, @@ -1672,7 +1699,7 @@ impl Editor { if self .config() .auto_save - .contains(&AutoSaveTrigger::NormalMode) + .should_trigger_on(AutoSaveTrigger::NormalMode) { if self.save::(doc!(self).id, None, false).is_ok() { // TODO: make 'modified' icon in statusline disappear. From b37155c08b46de4b2983520e66aaf0ff56303965 Mon Sep 17 00:00:00 2001 From: TobTobXX Date: Mon, 3 Apr 2023 03:09:06 +0200 Subject: [PATCH 6/6] Custom deserializer for compatibility --- helix-term/src/ui/editor.rs | 2 +- helix-view/src/editor.rs | 64 ++++++++++++++++++++++--------------- 2 files changed, 40 insertions(+), 26 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 1feb0b12eb41..2eb7eac10103 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -1351,7 +1351,7 @@ impl Component for EditorView { .editor .config() .auto_save - .should_trigger_on(AutoSaveTrigger::Unfocused) + .contains(&AutoSaveTrigger::Unfocused) { if let Err(e) = commands::typed::write_all_impl(context, false, false) { context.editor.set_error(format!("{}", e)); diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 5b537619307e..b616ccd70852 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -211,15 +211,6 @@ impl Default for FilePickerConfig { } } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(untagged)] -pub enum AutoSaveConfig { - /// Compatibility with the old `auto-save = true` setting. - Unfocused(bool), - /// The new way: a list of autosave triggers. - TriggerList(Vec), -} - #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum AutoSaveTrigger { @@ -229,22 +220,44 @@ pub enum AutoSaveTrigger { NormalMode, } -impl Default for AutoSaveConfig { - fn default() -> Self { - Self::TriggerList(Vec::new()) - } -} +/// Parse the value of the `auto-save` key. +/// Can be either a boolean or a list of [AutoSaveTrigger]s. +fn deserialize_autosave_compat<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + struct AutoSaveVisitor; + + impl<'de> serde::de::Visitor<'de> for AutoSaveVisitor { + type Value = Vec; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a list of autosave triggers or a boolean") + } -impl AutoSaveConfig { - pub fn should_trigger_on(&self, trigger: AutoSaveTrigger) -> bool { - match self { - // Compatibility with old setting: - // false -> false - // true -> Unfocused trigger is active - AutoSaveConfig::Unfocused(s) => *s && trigger == AutoSaveTrigger::Unfocused, - AutoSaveConfig::TriggerList(triggers) => triggers.contains(&trigger), + /// New configuration: a list of AutoSaveTriggers. + fn visit_seq(self, seq: S) -> Result + where + S: serde::de::SeqAccess<'de>, + { + Deserialize::deserialize(serde::de::value::SeqAccessDeserializer::new(seq)) + } + + /// Old configuration: + /// if false => no trigger + /// if true => Only the Unfocused trigger. + fn visit_bool(self, value: bool) -> Result + where + E: serde::de::Error, + { + match value { + false => Ok(Vec::new()), + true => Ok(vec![AutoSaveTrigger::Unfocused]), + } } } + + deserializer.deserialize_any(AutoSaveVisitor) } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -277,7 +290,8 @@ pub struct Config { /// Automatic formatting on save. Defaults to true. pub auto_format: bool, /// Automatic save on focus lost. Defaults to false. - pub auto_save: AutoSaveConfig, + #[serde(deserialize_with = "deserialize_autosave_compat")] + pub auto_save: Vec, /// Set a global text_width pub text_width: usize, /// Time in milliseconds since last keypress before idle timers trigger. @@ -768,7 +782,7 @@ impl Default for Config { auto_pairs: AutoPairConfig::default(), auto_completion: true, auto_format: true, - auto_save: AutoSaveConfig::default(), + auto_save: Vec::new(), idle_timeout: Duration::from_millis(400), completion_trigger_len: 2, auto_info: true, @@ -1699,7 +1713,7 @@ impl Editor { if self .config() .auto_save - .should_trigger_on(AutoSaveTrigger::NormalMode) + .contains(&AutoSaveTrigger::NormalMode) { if self.save::(doc!(self).id, None, false).is_ok() { // TODO: make 'modified' icon in statusline disappear.