From 7e29ee6daeb86fbd20a8b5eaae30b2e0f868147d Mon Sep 17 00:00:00 2001 From: Charlie Groves Date: Thu, 20 Oct 2022 21:35:02 -0400 Subject: [PATCH] Autosave all when the terminal loses focus (#3178) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Autosave all when the terminal loses focus * Correct comment on focus config Co-authored-by: Blaž Hrastnik * Need a block_try_flush_writes in all quit_all paths Co-authored-by: Blaž Hrastnik --- book/src/configuration.md | 1 + helix-term/src/application.rs | 12 ++++-- helix-term/src/commands/typed.rs | 70 ++++++++++++-------------------- helix-term/src/ui/editor.rs | 10 ++++- helix-view/src/editor.rs | 3 ++ 5 files changed, 47 insertions(+), 49 deletions(-) diff --git a/book/src/configuration.md b/book/src/configuration.md index 996c5fb6ada3..9f3fe2fa95bd 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -49,6 +49,7 @@ on unix operating systems. | `gutters` | Gutters to display: Available are `diagnostics` 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", "line-numbers"]` | | `auto-completion` | Enable automatic pop up of auto-completion. | `true` | | `auto-format` | Enable automatic formatting on save. | `true` | +| `auto-save` | Enable automatic saving on 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. Used for autocompletion, set to 0 for instant. | `400` | | `completion-trigger-len` | The min-length of word under cursor to trigger autocompletion | `2` | | `auto-info` | Whether to display infoboxes | `true` | diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index b4b4a6751bd0..8ee0802f2018 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -37,8 +37,8 @@ use anyhow::{Context, Error}; use crossterm::{ event::{ - DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, EnableMouseCapture, - Event as CrosstermEvent, + DisableBracketedPaste, DisableFocusChange, DisableMouseCapture, EnableBracketedPaste, + EnableFocusChange, EnableMouseCapture, Event as CrosstermEvent, }, execute, terminal, tty::IsTty, @@ -102,6 +102,7 @@ fn restore_term() -> Result<(), Error> { execute!( stdout, DisableBracketedPaste, + DisableFocusChange, terminal::LeaveAlternateScreen )?; terminal::disable_raw_mode()?; @@ -925,7 +926,12 @@ impl Application { async fn claim_term(&mut self) -> Result<(), Error> { terminal::enable_raw_mode()?; let mut stdout = stdout(); - execute!(stdout, terminal::EnterAlternateScreen, EnableBracketedPaste)?; + execute!( + stdout, + terminal::EnterAlternateScreen, + EnableBracketedPaste, + EnableFocusChange + )?; execute!(stdout, terminal::Clear(terminal::ClearType::All))?; if self.config.load().editor.mouse { execute!(stdout, EnableMouseCapture)?; diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index f20e71c25f23..f154fd3897f6 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -559,17 +559,11 @@ pub(super) fn buffers_remaining_impl(editor: &mut Editor) -> anyhow::Result<()> Ok(()) } -fn write_all_impl( +pub fn write_all_impl( cx: &mut compositor::Context, - _args: &[Cow], - event: PromptEvent, - quit: bool, force: bool, + write_scratch: bool, ) -> anyhow::Result<()> { - if event != PromptEvent::Validate { - return Ok(()); - } - let mut errors: Vec<&'static str> = Vec::new(); let auto_format = cx.editor.config().auto_format; let jobs = &mut cx.jobs; @@ -580,12 +574,13 @@ fn write_all_impl( .documents .values() .filter_map(|doc| { - if doc.path().is_none() { - errors.push("cannot write a buffer without a filename\n"); + if !doc.is_modified() { return None; } - - if !doc.is_modified() { + if doc.path().is_none() { + if write_scratch { + errors.push("cannot write a buffer without a filename\n"); + } return None; } @@ -611,20 +606,6 @@ fn write_all_impl( cx.editor.save::(id, None, force)?; } - if quit { - cx.block_try_flush_writes()?; - - if !force { - buffers_remaining_impl(cx.editor)?; - } - - // close all views - let views: Vec<_> = cx.editor.tree.views().map(|(view, _)| view.id).collect(); - for view_id in views { - cx.editor.close(view_id); - } - } - if !errors.is_empty() && !force { bail!("{:?}", errors); } @@ -634,49 +615,50 @@ fn write_all_impl( fn write_all( cx: &mut compositor::Context, - args: &[Cow], + _args: &[Cow], event: PromptEvent, ) -> anyhow::Result<()> { if event != PromptEvent::Validate { return Ok(()); } - write_all_impl(cx, args, event, false, false) + write_all_impl(cx, false, true) } fn write_all_quit( cx: &mut compositor::Context, - args: &[Cow], + _args: &[Cow], event: PromptEvent, ) -> anyhow::Result<()> { if event != PromptEvent::Validate { return Ok(()); } - - write_all_impl(cx, args, event, true, false) + write_all_impl(cx, false, true)?; + quit_all_impl(cx, false) } fn force_write_all_quit( cx: &mut compositor::Context, - args: &[Cow], + _args: &[Cow], event: PromptEvent, ) -> anyhow::Result<()> { if event != PromptEvent::Validate { return Ok(()); } - - write_all_impl(cx, args, event, true, true) + let _ = write_all_impl(cx, true, true); + quit_all_impl(cx, true) } -fn quit_all_impl(editor: &mut Editor, force: bool) -> anyhow::Result<()> { +fn quit_all_impl(cx: &mut compositor::Context, force: bool) -> anyhow::Result<()> { + cx.block_try_flush_writes()?; if !force { - buffers_remaining_impl(editor)?; + buffers_remaining_impl(cx.editor)?; } // close all views - let views: Vec<_> = editor.tree.views().map(|(view, _)| view.id).collect(); + let views: Vec<_> = cx.editor.tree.views().map(|(view, _)| view.id).collect(); for view_id in views { - editor.close(view_id); + cx.editor.close(view_id); } Ok(()) @@ -691,8 +673,7 @@ fn quit_all( return Ok(()); } - cx.block_try_flush_writes()?; - quit_all_impl(cx.editor, false) + quit_all_impl(cx, false) } fn force_quit_all( @@ -704,7 +685,7 @@ fn force_quit_all( return Ok(()); } - quit_all_impl(cx.editor, true) + quit_all_impl(cx, true) } fn cquit( @@ -722,8 +703,7 @@ fn cquit( .unwrap_or(1); cx.editor.exit_code = exit_code; - cx.block_try_flush_writes()?; - quit_all_impl(cx.editor, false) + quit_all_impl(cx, false) } fn force_cquit( @@ -741,7 +721,7 @@ fn force_cquit( .unwrap_or(1); cx.editor.exit_code = exit_code; - quit_all_impl(cx.editor, true) + quit_all_impl(cx, true) } fn theme( @@ -2141,7 +2121,7 @@ pub static TYPABLE_COMMAND_MAP: Lazy self.handle_mouse_event(event, &mut cx), Event::IdleTimeout => self.handle_idle_timeout(&mut cx), - Event::FocusGained | Event::FocusLost => EventResult::Ignored(None), + Event::FocusGained => EventResult::Ignored(None), + Event::FocusLost => { + if context.editor.config().auto_save { + if let Err(e) = commands::typed::write_all_impl(context, false, false) { + context.editor.set_error(format!("{}", e)); + } + } + EventResult::Consumed(None) + } } } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index fe243b92d181..af69ceeae19a 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -140,6 +140,8 @@ 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, /// Time in milliseconds since last keypress before idle timers trigger. /// Used for autocompletion, set to 0 for instant. Defaults to 400ms. #[serde( @@ -592,6 +594,7 @@ impl Default for Config { auto_pairs: AutoPairConfig::default(), auto_completion: true, auto_format: true, + auto_save: false, idle_timeout: Duration::from_millis(400), completion_trigger_len: 2, auto_info: true,