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 <miguelvojito@gmail.com> Co-authored-by: Miguel Perez <perezoji@cs.fsu.edu>
This commit is contained in:
parent
a1cda3c19e
commit
265608a3d8
6 changed files with 143 additions and 5 deletions
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
61
helix-term/src/handlers/auto_save.rs
Normal file
61
helix-term/src/handlers/auto_save.rs
Normal file
|
@ -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(())
|
||||
});
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Reference in a new issue