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
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
}
87 changes: 87 additions & 0 deletions helix-term/src/handlers/auto_save.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
use std::time::Duration;

use anyhow::Ok;
use arc_swap::access::Access;

use helix_event::{register_hook, send_blocking};
use helix_view::{
editor::DEFAULT_AUTO_SAVE_DELAY,
events::DocumentDidChange,
handlers::{lsp::AutoSaveEvent, Handlers},
Editor,
};
use tokio::time::Instant;

use crate::{
commands, compositor,
job::{self, Jobs},
};

#[derive(Debug)]
enum State {
the-mikedavis marked this conversation as resolved.
Show resolved Hide resolved
Closed,
}

#[derive(Debug)]
pub(super) struct AutoSaveHandler {
state: State,
}

impl AutoSaveHandler {
pub fn new() -> AutoSaveHandler {
AutoSaveHandler {
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(delay) => {
if matches!(self.state, State::Closed) {
return Some(Instant::now() + Duration::from_millis(delay));
}
}
AutoSaveEvent::Cancel => {
self.state = State::Closed;
return None;
}
}

Some(Instant::now() + Duration::from_millis(DEFAULT_AUTO_SAVE_DELAY))
}

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 let Some(delay) = config.auto_save.after_delay {
send_blocking(&tx, AutoSaveEvent::Trigger(delay));
}
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
116 changes: 113 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,10 @@ 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 keypress after which auto save timer triggers.
/// Time delay defaults to false with 3000ms delay. Focus lost defaults to false.
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 +775,112 @@ impl WhitespaceRender {
}
}

#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct AutoSave {
/// Auto save after `n` milli seconds. Defaults to None.
pub after_delay: Option<u64>,
the-mikedavis marked this conversation as resolved.
Show resolved Hide resolved
/// Auto save on focus lost. Defaults to false.
pub focus_lost: bool,
}

// Intermediate type to have a flattened config
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(untagged, deny_unknown_fields, rename_all = "kebab-case")]
enum AutoSaveToml {
EnableFocusLost(bool),
#[serde(rename_all = "kebab-case")]
AutoSave {
#[serde(default)]
after_delay: AutoSaveAfterDelay,
#[serde(default)]
focus_lost: bool,
},
}

// Intermediate type to have a flattened config
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
struct AutoSaveAfterDelay {
#[serde(default)]
pub enable: bool,
#[serde(default = "default_auto_save_delay")]
pub timeout: u64,
}

fn default_auto_save_delay() -> u64 {
DEFAULT_AUTO_SAVE_DELAY
}

impl From<AutoSave> for AutoSaveToml {
fn from(value: AutoSave) -> Self {
let after_delay = match value.after_delay {
Some(timeout) => AutoSaveAfterDelay {
enable: true,
timeout,
},
None => Default::default(),
};
Self::AutoSave {
after_delay,
focus_lost: value.focus_lost,
}
}
}

impl From<AutoSaveToml> for AutoSave {
fn from(value: AutoSaveToml) -> Self {
match value {
AutoSaveToml::EnableFocusLost(focus_lost) => Self {
focus_lost,
..Default::default()
},
AutoSaveToml::AutoSave {
after_delay,
focus_lost,
} => {
let after_delay: Option<u64> = after_delay.enable.then_some(after_delay.timeout);
Self {
after_delay,
focus_lost,
}
}
}
}
}

impl Default for AutoSaveToml {
fn default() -> Self {
Self::EnableFocusLost(false)
}
}

impl Default for AutoSaveAfterDelay {
fn default() -> Self {
Self {
enable: false,
timeout: DEFAULT_AUTO_SAVE_DELAY,
}
}
}

impl<'de> Deserialize<'de> for AutoSave {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Ok(AutoSaveToml::deserialize(deserializer)?.into())
}
}
impl Serialize for AutoSave {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let a: AutoSaveToml = self.clone().into();
a.serialize(serializer)
}
}
the-mikedavis marked this conversation as resolved.
Show resolved Hide resolved

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct WhitespaceCharacters {
Expand Down Expand Up @@ -881,7 +991,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<lsp::AutoSaveEvent>,
}

impl Handlers {
Expand Down
11 changes: 11 additions & 0 deletions helix-view/src/handlers/lsp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,17 @@ pub enum SignatureHelpEvent {
RequestComplete { open: bool },
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum AutoSaveInvoked {
Automatic,
}

pub enum AutoSaveEvent {
// contains delay in milli seconds
Trigger(u64),
Cancel,
}
the-mikedavis marked this conversation as resolved.
Show resolved Hide resolved

#[derive(Debug)]
pub struct ApplyEditError {
pub kind: ApplyEditErrorKind,
Expand Down
Loading