Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto Save All Buffers After A Delay #10899

Merged
merged 13 commits into from
Jun 10, 2024
4 changes: 3 additions & 1 deletion book/src/editor.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
| `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.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` |
| `auto-save.after-delay.enable` | Enable automatic saving after `auto-save.after-delay.timeout` milliseconds have passed since last edit. | `false` |
| `auto-save.after-delay.timeout` | Time in milliseconds since last edit before auto save timer triggers. | `3000` |
the-mikedavis marked this conversation as resolved.
Show resolved Hide resolved
| `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` |
Expand Down
7 changes: 7 additions & 0 deletions helix-term/src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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 changes: 61 additions & 0 deletions helix-term/src/handlers/auto_save.rs
Original file line number Diff line number Diff line change
@@ -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(())
});
}
2 changes: 1 addition & 1 deletion helix-term/src/ui/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down
66 changes: 63 additions & 3 deletions helix-view/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions helix-view/src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading