Mercurial > forks > helix
changeset 5797:6151661ff5aa
Auto Save All Buffers After A Delay (#10899)
* auto save after delay
* configable
* clearer names
* init
* working with some odd behaviour
* working with greater consistency
* Apply reviewer suggestions
- Remove unneccessary field
- Remove blocking save
* Improve auto-save configuration
Auto save can be configured to trigger on focus loss:
```toml
auto-save.focus-lost = true|false
```
and after a time delay (in milli seconds) since last keypress:
```toml
auto-save.after-delay.enable = true|false
auto-save.after-delay.timeout = [0, u64::MAX] # default: 3000
```
* Remove boilerplate and unnecessary types
* Remove more useless types
* Update docs for auto-save.after-delay
* Fix wording of (doc) comments relating to auto-save
* book: Move auto-save descriptions to separate section
---------
Co-authored-by: Miguel Perez <[email protected]>
Co-authored-by: Miguel Perez <[email protected]>
author | Hendrik Wolff <hendrik.wolff@agdsn.me> |
---|---|
date | Tue, 11 Jun 2024 00:39:06 +0200 |
parents | 71b7c392ceee |
children | a366329fb92e |
files | book/src/editor.md helix-term/src/handlers.rs helix-term/src/handlers/auto_save.rs helix-term/src/ui/editor.rs helix-view/src/editor.rs helix-view/src/handlers.rs |
diffstat | 6 files changed, 143 insertions(+), 5 deletions(-) [+] |
line wrap: on
line diff
--- a/book/src/editor.md Mon Jun 10 17:08:39 2024 +0200 +++ b/book/src/editor.md Tue Jun 11 00:39:06 2024 +0200 @@ -32,7 +32,6 @@ | `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` | | `idle-timeout` | Time in milliseconds since last keypress before idle timers trigger. | `250` | | `completion-timeout` | Time in milliseconds after typing a word character before completions are shown, set to 5 for instant. | `250` | | `preview-completion-insert` | Whether to apply completion item instantly when selected | `true` | @@ -222,6 +221,16 @@ '<' = '>' ``` +### `[editor.auto-save]` Section + +Control auto save behavior. + +| Key | Description | Default | +|--|--|---------| +| `focus-lost` | 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` | +| `after-delay.enable` | Enable automatic saving after `auto-save.after-delay.timeout` milliseconds have passed since last edit. | `false` | +| `after-delay.timeout` | Time in milliseconds since last edit before auto save timer triggers. | `3000` | + ### `[editor.search]` Section Search specific options.
--- a/helix-term/src/handlers.rs Mon Jun 10 17:08:39 2024 +0200 +++ b/helix-term/src/handlers.rs Tue Jun 11 00:39:06 2024 +0200 @@ -5,12 +5,14 @@ use crate::config::Config; use crate::events; +use crate::handlers::auto_save::AutoSaveHandler; use crate::handlers::completion::CompletionHandler; use crate::handlers::signature_help::SignatureHelpHandler; pub use completion::trigger_auto_completion; pub use helix_view::handlers::Handlers; +mod auto_save; pub mod completion; mod signature_help; @@ -19,11 +21,16 @@ let completions = CompletionHandler::new(config).spawn(); let signature_hints = SignatureHelpHandler::new().spawn(); + let auto_save = AutoSaveHandler::new().spawn(); + let handlers = Handlers { completions, signature_hints, + auto_save, }; + completion::register_hooks(&handlers); signature_help::register_hooks(&handlers); + auto_save::register_hooks(&handlers); handlers }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/helix-term/src/handlers/auto_save.rs Tue Jun 11 00:39:06 2024 +0200 @@ -0,0 +1,61 @@ +use std::time::Duration; + +use anyhow::Ok; +use arc_swap::access::Access; + +use helix_event::{register_hook, send_blocking}; +use helix_view::{events::DocumentDidChange, handlers::Handlers, Editor}; +use tokio::time::Instant; + +use crate::{ + commands, compositor, + job::{self, Jobs}, +}; + +#[derive(Debug)] +pub(super) struct AutoSaveHandler; + +impl AutoSaveHandler { + pub fn new() -> AutoSaveHandler { + AutoSaveHandler + } +} + +impl helix_event::AsyncHook for AutoSaveHandler { + type Event = u64; + + fn handle_event( + &mut self, + timeout: Self::Event, + _: Option<tokio::time::Instant>, + ) -> Option<Instant> { + Some(Instant::now() + Duration::from_millis(timeout)) + } + + fn finish_debounce(&mut self) { + job::dispatch_blocking(move |editor, _| request_auto_save(editor)) + } +} + +fn request_auto_save(editor: &mut Editor) { + let context = &mut compositor::Context { + editor, + scroll: Some(0), + jobs: &mut Jobs::new(), + }; + + if let Err(e) = commands::typed::write_all_impl(context, false, false) { + context.editor.set_error(format!("{}", e)); + } +} + +pub(super) fn register_hooks(handlers: &Handlers) { + let tx = handlers.auto_save.clone(); + register_hook!(move |event: &mut DocumentDidChange<'_>| { + let config = event.doc.config.load(); + if config.auto_save.after_delay.enable { + send_blocking(&tx, config.auto_save.after_delay.timeout); + } + Ok(()) + }); +}
--- a/helix-term/src/ui/editor.rs Mon Jun 10 17:08:39 2024 +0200 +++ b/helix-term/src/ui/editor.rs Tue Jun 11 00:39:06 2024 +0200 @@ -1451,7 +1451,7 @@ EventResult::Consumed(None) } Event::FocusLost => { - if context.editor.config().auto_save { + if context.editor.config().auto_save.focus_lost { if let Err(e) = commands::typed::write_all_impl(context, false, false) { context.editor.set_error(format!("{}", e)); }
--- a/helix-view/src/editor.rs Mon Jun 10 17:08:39 2024 +0200 +++ b/helix-view/src/editor.rs Tue Jun 11 00:39:06 2024 +0200 @@ -55,6 +55,8 @@ ArcSwap, }; +pub const DEFAULT_AUTO_SAVE_DELAY: u64 = 3000; + fn deserialize_duration_millis<'de, D>(deserializer: D) -> Result<Duration, D::Error> where D: serde::Deserializer<'de>, @@ -266,8 +268,11 @@ pub auto_completion: bool, /// Automatic formatting on save. Defaults to true. pub auto_format: bool, - /// Automatic save on focus lost. Defaults to false. - pub auto_save: bool, + /// Automatic save on focus lost and/or after delay. + /// Time delay in milliseconds since last edit after which auto save timer triggers. + /// Time delay defaults to false with 3000ms delay. Focus lost defaults to false. + #[serde(deserialize_with = "deserialize_auto_save")] + pub auto_save: AutoSave, /// Set a global text_width pub text_width: usize, /// Time in milliseconds since last keypress before idle timers trigger. @@ -771,6 +776,61 @@ } } +#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +pub struct AutoSave { + /// Auto save after a delay in milliseconds. Defaults to disabled. + #[serde(default)] + pub after_delay: AutoSaveAfterDelay, + /// Auto save on focus lost. Defaults to false. + #[serde(default)] + pub focus_lost: bool, +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +pub struct AutoSaveAfterDelay { + #[serde(default)] + /// Enable auto save after delay. Defaults to false. + pub enable: bool, + #[serde(default = "default_auto_save_delay")] + /// Time delay in milliseconds. Defaults to [DEFAULT_AUTO_SAVE_DELAY]. + pub timeout: u64, +} + +impl Default for AutoSaveAfterDelay { + fn default() -> Self { + Self { + enable: false, + timeout: DEFAULT_AUTO_SAVE_DELAY, + } + } +} + +fn default_auto_save_delay() -> u64 { + DEFAULT_AUTO_SAVE_DELAY +} + +fn deserialize_auto_save<'de, D>(deserializer: D) -> Result<AutoSave, D::Error> +where + D: serde::Deserializer<'de>, +{ + #[derive(Deserialize, Serialize)] + #[serde(untagged, deny_unknown_fields, rename_all = "kebab-case")] + enum AutoSaveToml { + EnableFocusLost(bool), + AutoSave(AutoSave), + } + + match AutoSaveToml::deserialize(deserializer)? { + AutoSaveToml::EnableFocusLost(focus_lost) => Ok(AutoSave { + focus_lost, + ..Default::default() + }), + AutoSaveToml::AutoSave(auto_save) => Ok(auto_save), + } +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(default)] pub struct WhitespaceCharacters { @@ -881,7 +941,7 @@ auto_pairs: AutoPairConfig::default(), auto_completion: true, auto_format: true, - auto_save: false, + auto_save: AutoSave::default(), idle_timeout: Duration::from_millis(250), completion_timeout: Duration::from_millis(250), preview_completion_insert: true,
--- a/helix-view/src/handlers.rs Mon Jun 10 17:08:39 2024 +0200 +++ b/helix-view/src/handlers.rs Tue Jun 11 00:39:06 2024 +0200 @@ -11,6 +11,7 @@ // only public because most of the actual implementation is in helix-term right now :/ pub completions: Sender<lsp::CompletionEvent>, pub signature_hints: Sender<lsp::SignatureHelpEvent>, + pub auto_save: Sender<u64>, } impl Handlers {