Skip to content

Commit 707906a

Browse files
committed
Autosave all when the terminal loses focus
1 parent 83f177d commit 707906a

File tree

5 files changed

+38
-36
lines changed

5 files changed

+38
-36
lines changed

book/src/configuration.md

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ You may also specify a file to use for configuration with the `-c` or
4444
| `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"]` |
4545
| `auto-completion` | Enable automatic pop up of auto-completion. | `true` |
4646
| `auto-format` | Enable automatic formatting on save. | `true` |
47+
| `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` |
4748
| `idle-timeout` | Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant. | `400` |
4849
| `completion-trigger-len` | The min-length of word under cursor to trigger autocompletion | `2` |
4950
| `auto-info` | Whether to display infoboxes | `true` |

helix-term/src/application.rs

+6-3
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ use anyhow::{Context, Error};
3030

3131
use crossterm::{
3232
event::{
33-
DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, EnableMouseCapture,
34-
Event as CrosstermEvent,
33+
DisableBracketedPaste, DisableFocusChange, DisableMouseCapture, EnableBracketedPaste,
34+
EnableFocusChange, EnableMouseCapture, Event as CrosstermEvent,
3535
},
3636
execute, terminal,
3737
tty::IsTty,
@@ -797,6 +797,7 @@ impl Application {
797797
let mut stdout = stdout();
798798
execute!(stdout, terminal::EnterAlternateScreen, EnableBracketedPaste)?;
799799
execute!(stdout, terminal::Clear(terminal::ClearType::All))?;
800+
execute!(stdout, EnableFocusChange)?;
800801
if self.config.load().editor.mouse {
801802
execute!(stdout, EnableMouseCapture)?;
802803
}
@@ -810,6 +811,7 @@ impl Application {
810811
// Ignore errors on disabling, this might trigger on windows if we call
811812
// disable without calling enable previously
812813
let _ = execute!(stdout, DisableMouseCapture);
814+
execute!(stdout, DisableFocusChange)?;
813815
execute!(stdout, terminal::LeaveAlternateScreen)?;
814816
terminal::disable_raw_mode()?;
815817
Ok(())
@@ -831,7 +833,8 @@ impl Application {
831833
let _ = execute!(
832834
std::io::stdout(),
833835
terminal::LeaveAlternateScreen,
834-
DisableBracketedPaste
836+
DisableBracketedPaste,
837+
DisableFocusChange,
835838
);
836839
let _ = terminal::disable_raw_mode();
837840
hook(info);

helix-term/src/commands/typed.rs

+19-32
Original file line numberDiff line numberDiff line change
@@ -535,28 +535,24 @@ pub(super) fn buffers_remaining_impl(editor: &mut Editor) -> anyhow::Result<()>
535535
Ok(())
536536
}
537537

538-
fn write_all_impl(
538+
pub fn write_all_impl(
539539
cx: &mut compositor::Context,
540-
_args: &[Cow<str>],
541-
event: PromptEvent,
542-
quit: bool,
543540
force: bool,
541+
write_scratch: bool,
544542
) -> anyhow::Result<()> {
545-
if event != PromptEvent::Validate {
546-
return Ok(());
547-
}
548-
549543
let mut errors = String::new();
550544
let auto_format = cx.editor.config().auto_format;
551545
let jobs = &mut cx.jobs;
552546
// save all documents
553547
for doc in &mut cx.editor.documents.values_mut() {
554-
if doc.path().is_none() {
555-
errors.push_str("cannot write a buffer without a filename\n");
548+
if !doc.is_modified() {
556549
continue;
557550
}
558551

559-
if !doc.is_modified() {
552+
if doc.path().is_none() {
553+
if write_scratch {
554+
errors.push_str("cannot write a buffer without a filename\n");
555+
}
560556
continue;
561557
}
562558

@@ -579,55 +575,46 @@ fn write_all_impl(
579575
jobs.add(Job::new(future).wait_before_exiting());
580576
}
581577

582-
if quit {
583-
if !force {
584-
buffers_remaining_impl(cx.editor)?;
585-
}
586-
587-
// close all views
588-
let views: Vec<_> = cx.editor.tree.views().map(|(view, _)| view.id).collect();
589-
for view_id in views {
590-
cx.editor.close(view_id);
591-
}
578+
match errors.len() {
579+
0 => Ok(()),
580+
_ => bail!(errors),
592581
}
593-
594-
bail!(errors)
595582
}
596583

597584
fn write_all(
598585
cx: &mut compositor::Context,
599-
args: &[Cow<str>],
586+
_args: &[Cow<str>],
600587
event: PromptEvent,
601588
) -> anyhow::Result<()> {
602589
if event != PromptEvent::Validate {
603590
return Ok(());
604591
}
605592

606-
write_all_impl(cx, args, event, false, false)
593+
write_all_impl(cx, false, true)
607594
}
608595

609596
fn write_all_quit(
610597
cx: &mut compositor::Context,
611-
args: &[Cow<str>],
598+
_args: &[Cow<str>],
612599
event: PromptEvent,
613600
) -> anyhow::Result<()> {
614601
if event != PromptEvent::Validate {
615602
return Ok(());
616603
}
617-
618-
write_all_impl(cx, args, event, true, false)
604+
write_all_impl(cx, false, true)?;
605+
quit_all_impl(cx.editor, false)
619606
}
620607

621608
fn force_write_all_quit(
622609
cx: &mut compositor::Context,
623-
args: &[Cow<str>],
610+
_args: &[Cow<str>],
624611
event: PromptEvent,
625612
) -> anyhow::Result<()> {
626613
if event != PromptEvent::Validate {
627614
return Ok(());
628615
}
629-
630-
write_all_impl(cx, args, event, true, true)
616+
let _ = write_all_impl(cx, true, true);
617+
quit_all_impl(cx.editor, true)
631618
}
632619

633620
fn quit_all_impl(editor: &mut Editor, force: bool) -> anyhow::Result<()> {
@@ -2008,7 +1995,7 @@ pub static TYPABLE_COMMAND_MAP: Lazy<HashMap<&'static str, &'static TypableComma
20081995
.collect()
20091996
});
20101997

2011-
pub fn command_mode(cx: &mut Context) {
1998+
pub(super) fn command_mode(cx: &mut Context) {
20121999
let mut prompt = Prompt::new(
20132000
":".into(),
20142001
Some(':'),

helix-term/src/ui/editor.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -1277,7 +1277,15 @@ impl Component for EditorView {
12771277
}
12781278

12791279
Event::Mouse(event) => self.handle_mouse_event(event, &mut cx),
1280-
Event::FocusGained | Event::FocusLost => EventResult::Ignored(None),
1280+
Event::FocusGained => EventResult::Ignored(None),
1281+
Event::FocusLost => {
1282+
if context.editor.config().auto_save {
1283+
if let Err(e) = commands::typed::write_all_impl(context, false, false) {
1284+
context.editor.set_error(format!("{}", e));
1285+
}
1286+
}
1287+
EventResult::Consumed(None)
1288+
}
12811289
}
12821290
}
12831291

helix-view/src/editor.rs

+3
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ pub struct Config {
136136
pub auto_completion: bool,
137137
/// Automatic formatting on save. Defaults to true.
138138
pub auto_format: bool,
139+
/// Automatic save on focus lost. Defaults to true.
140+
pub auto_save: bool,
139141
/// Time in milliseconds since last keypress before idle timers trigger.
140142
/// Used for autocompletion, set to 0 for instant. Defaults to 400ms.
141143
#[serde(
@@ -542,6 +544,7 @@ impl Default for Config {
542544
auto_pairs: AutoPairConfig::default(),
543545
auto_completion: true,
544546
auto_format: true,
547+
auto_save: false,
545548
idle_timeout: Duration::from_millis(400),
546549
completion_trigger_len: 2,
547550
auto_info: true,

0 commit comments

Comments
 (0)