Unnamed repository; edit this file 'description' to name the repository.
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]>
Hendrik Wolff 2024-06-11
parent a1cda3c · commit 265608a
-rw-r--r--book/src/editor.md11
-rw-r--r--helix-term/src/handlers.rs7
-rw-r--r--helix-term/src/handlers/auto_save.rs61
-rw-r--r--helix-term/src/ui/editor.rs2
-rw-r--r--helix-view/src/editor.rs66
-rw-r--r--helix-view/src/handlers.rs1
6 files changed, 143 insertions, 5 deletions
diff --git a/book/src/editor.md b/book/src/editor.md
index 88541a7b..ba03e90e 100644
--- a/book/src/editor.md
+++ b/book/src/editor.md
@@ -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 @@ name = "rust"
'<' = '>'
```
+### `[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.
diff --git a/helix-term/src/handlers.rs b/helix-term/src/handlers.rs
index d45809d3..fc927313 100644
--- a/helix-term/src/handlers.rs
+++ b/helix-term/src/handlers.rs
@@ -5,12 +5,14 @@ use helix_event::AsyncHook;
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 @@ pub fn setup(config: Arc<ArcSwap<Config>>) -> Handlers {
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
}
diff --git a/helix-term/src/handlers/auto_save.rs b/helix-term/src/handlers/auto_save.rs
new file mode 100644
index 00000000..d3f7f6fc
--- /dev/null
+++ b/helix-term/src/handlers/auto_save.rs
@@ -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(())
+ });
+}
diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs
index 97f90f62..d584afbb 100644
--- a/helix-term/src/ui/editor.rs
+++ b/helix-term/src/ui/editor.rs
@@ -1451,7 +1451,7 @@ impl Component for EditorView {
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));
}
diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs
index ef491853..635f7261 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -55,6 +55,8 @@ use arc_swap::{
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 struct Config {
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 @@ impl WhitespaceRender {
}
}
+#[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 @@ impl Default for Config {
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,
diff --git a/helix-view/src/handlers.rs b/helix-view/src/handlers.rs
index 724e7b19..352abb88 100644
--- a/helix-view/src/handlers.rs
+++ b/helix-view/src/handlers.rs
@@ -11,6 +11,7 @@ pub struct Handlers {
// 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 {