From 96d6f6f5330fbc24466110554e9945f2672e2caa Mon Sep 17 00:00:00 2001 From: Charlie Groves Date: Sun, 24 Jul 2022 09:50:57 -0400 Subject: [PATCH] Autosave all when the terminal loses focus --- book/src/configuration.md | 1 + helix-term/src/application.rs | 12 ++++++-- helix-term/src/commands/typed.rs | 51 ++++++++++++-------------------- helix-term/src/ui/editor.rs | 10 ++++++- helix-view/src/editor.rs | 3 ++ 5 files changed, 41 insertions(+), 36 deletions(-) diff --git a/book/src/configuration.md b/book/src/configuration.md index fdabe7687dd38..a2ccb4a326a4d 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -44,6 +44,7 @@ You may also specify a file to use for configuration with the `-c` or | `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 9e79e7c960373..41d6dfd974c16 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -30,8 +30,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, @@ -95,6 +95,7 @@ fn restore_term() -> Result<(), Error> { execute!( stdout, DisableBracketedPaste, + DisableFocusChange, terminal::LeaveAlternateScreen )?; terminal::disable_raw_mode()?; @@ -811,7 +812,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 ad4e7f4ce9da6..02e2fc6f2c5c7 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -535,28 +535,24 @@ 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 = String::new(); let auto_format = cx.editor.config().auto_format; let jobs = &mut cx.jobs; // save all documents for doc in &mut cx.editor.documents.values_mut() { - if doc.path().is_none() { - errors.push_str("cannot write a buffer without a filename\n"); + if !doc.is_modified() { continue; } - if !doc.is_modified() { + if doc.path().is_none() { + if write_scratch { + errors.push_str("cannot write a buffer without a filename\n"); + } continue; } @@ -579,55 +575,46 @@ fn write_all_impl( jobs.add(Job::new(future).wait_before_exiting()); } - if quit { - 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); - } + match errors.len() { + 0 => Ok(()), + _ => bail!(errors), } - - bail!(errors) } 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.editor, 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.editor, true) } fn quit_all_impl(editor: &mut Editor, force: bool) -> anyhow::Result<()> { @@ -2008,7 +1995,7 @@ pub static TYPABLE_COMMAND_MAP: Lazy self.handle_mouse_event(event, &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 65e64b16c78ee..1bbf3871979ed 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -136,6 +136,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 true. + 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( @@ -562,6 +564,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,