-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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 #9732
Changes from all commits
9d7350d
be25c9c
cd0588a
acbf4f8
636b2cf
7ae5152
0714a44
376e9c2
06dc754
95a0d8b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
use std::time::Duration; | ||
|
||
use anyhow::Ok; | ||
use arc_swap::access::Access; | ||
|
||
use helix_event::{register_hook, send_blocking}; | ||
use helix_view::{ | ||
editor::SaveStyle, | ||
events::DocumentDidChange, | ||
handlers::{ | ||
lsp::{AutoSaveEvent, AutoSaveInvoked}, | ||
Handlers, | ||
}, | ||
Editor, | ||
}; | ||
use tokio::time::Instant; | ||
|
||
use crate::{ | ||
commands, compositor, | ||
job::{self, Jobs}, | ||
}; | ||
|
||
#[derive(Debug)] | ||
enum State { | ||
Closed, | ||
} | ||
|
||
#[derive(Debug)] | ||
pub(super) struct AutoSaveHandler { | ||
trigger: Option<AutoSaveInvoked>, | ||
state: State, | ||
} | ||
|
||
impl AutoSaveHandler { | ||
pub fn new() -> AutoSaveHandler { | ||
AutoSaveHandler { | ||
trigger: None, | ||
state: State::Closed, | ||
} | ||
} | ||
} | ||
|
||
impl helix_event::AsyncHook for AutoSaveHandler { | ||
type Event = AutoSaveEvent; | ||
|
||
fn handle_event( | ||
&mut self, | ||
event: Self::Event, | ||
_: Option<tokio::time::Instant>, | ||
) -> Option<Instant> { | ||
match event { | ||
AutoSaveEvent::Trigger => { | ||
if matches!(self.state, State::Closed) { | ||
self.trigger = Some(AutoSaveInvoked::Automatic); | ||
return Some(Instant::now() + Duration::from_millis(1000)); | ||
} | ||
} | ||
AutoSaveEvent::Cancel => { | ||
self.state = State::Closed; | ||
return None; | ||
} | ||
} | ||
|
||
if self.trigger.is_none() { | ||
self.trigger = Some(AutoSaveInvoked::Automatic) | ||
} | ||
|
||
Some(Instant::now() + Duration::from_millis(1000)) | ||
} | ||
|
||
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)); | ||
} | ||
|
||
if let Err(e) = context.block_try_flush_writes() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think thsi should be here, this is only intended for shutdown. We don't want to freeze the editor while writing to disk |
||
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 && config.save_style == SaveStyle::AfterDelay { | ||
send_blocking(&tx, AutoSaveEvent::Trigger); | ||
} | ||
Ok(()) | ||
}); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -243,6 +243,8 @@ pub struct Config { | |
pub auto_format: bool, | ||
/// Automatic save on focus lost. Defaults to false. | ||
pub auto_save: bool, | ||
/// When saves are performed. Defaults to on focus lost. | ||
pub save_style: SaveStyle, | ||
/// Set a global text_width | ||
pub text_width: usize, | ||
/// Time in milliseconds since last keypress before idle timers trigger. | ||
|
@@ -252,6 +254,13 @@ pub struct Config { | |
deserialize_with = "deserialize_duration_millis" | ||
)] | ||
pub idle_timeout: Duration, | ||
/// Time in milliseconds since last keypress before auto save timers trigger. | ||
/// Used for various UI timeouts. Defaults to 1000ms. | ||
#[serde( | ||
serialize_with = "serialize_duration_millis", | ||
deserialize_with = "deserialize_duration_millis" | ||
)] | ||
pub save_delay_timeout: Duration, | ||
/// Time in milliseconds after typing a word character before auto completions | ||
/// are shown, set to 5 for instant. Defaults to 250ms. | ||
#[serde( | ||
|
@@ -731,6 +740,13 @@ impl WhitespaceRender { | |
} | ||
} | ||
|
||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] | ||
#[serde(rename_all = "kebab-case")] | ||
pub enum SaveStyle { | ||
FocusLost, | ||
AfterDelay, | ||
} | ||
|
||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] | ||
#[serde(default)] | ||
pub struct WhitespaceCharacters { | ||
|
@@ -840,7 +856,9 @@ impl Default for Config { | |
auto_completion: true, | ||
auto_format: true, | ||
auto_save: false, | ||
save_style: SaveStyle::FocusLost, | ||
idle_timeout: Duration::from_millis(250), | ||
save_delay_timeout: Duration::from_millis(1000), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe the default should be longer like 5s? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For me the primary benefit of auto saving after a delay is updating lints from LSPs such as rust-analyzer. IMO one second is the best default for this use case. This also seems to be main use case mentioned on #3451. 1s is also the default in vscode and it works well there. |
||
completion_timeout: Duration::from_millis(250), | ||
preview_completion_insert: true, | ||
completion_trigger_len: 2, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no need to keep the trigger around , you don't actually use this