Skip to content

Commit 8994714

Browse files
grovesarchseer
authored andcommitted
Autosave all when the terminal loses focus (helix-editor#3178)
* Autosave all when the terminal loses focus * Correct comment on focus config Co-authored-by: Blaž Hrastnik <blaz@mxxn.io> * Need a block_try_flush_writes in all quit_all paths Co-authored-by: Blaž Hrastnik <blaz@mxxn.io>
1 parent d0e663b commit 8994714

File tree

5 files changed

+47
-49
lines changed

5 files changed

+47
-49
lines changed

book/src/configuration.md

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ on unix operating systems.
4949
| `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"]` |
5050
| `auto-completion` | Enable automatic pop up of auto-completion. | `true` |
5151
| `auto-format` | Enable automatic formatting on save. | `true` |
52+
| `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` |
5253
| `idle-timeout` | Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant. | `400` |
5354
| `completion-trigger-len` | The min-length of word under cursor to trigger autocompletion | `2` |
5455
| `auto-info` | Whether to display infoboxes | `true` |

helix-term/src/application.rs

+9-3
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ use anyhow::{Context, Error};
3737

3838
use crossterm::{
3939
event::{
40-
DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, EnableMouseCapture,
41-
Event as CrosstermEvent,
40+
DisableBracketedPaste, DisableFocusChange, DisableMouseCapture, EnableBracketedPaste,
41+
EnableFocusChange, EnableMouseCapture, Event as CrosstermEvent,
4242
},
4343
execute, terminal,
4444
tty::IsTty,
@@ -102,6 +102,7 @@ fn restore_term() -> Result<(), Error> {
102102
execute!(
103103
stdout,
104104
DisableBracketedPaste,
105+
DisableFocusChange,
105106
terminal::LeaveAlternateScreen
106107
)?;
107108
terminal::disable_raw_mode()?;
@@ -925,7 +926,12 @@ impl Application {
925926
async fn claim_term(&mut self) -> Result<(), Error> {
926927
terminal::enable_raw_mode()?;
927928
let mut stdout = stdout();
928-
execute!(stdout, terminal::EnterAlternateScreen, EnableBracketedPaste)?;
929+
execute!(
930+
stdout,
931+
terminal::EnterAlternateScreen,
932+
EnableBracketedPaste,
933+
EnableFocusChange
934+
)?;
929935
execute!(stdout, terminal::Clear(terminal::ClearType::All))?;
930936
if self.config.load().editor.mouse {
931937
execute!(stdout, EnableMouseCapture)?;

helix-term/src/commands/typed.rs

+25-45
Original file line numberDiff line numberDiff line change
@@ -559,17 +559,11 @@ pub(super) fn buffers_remaining_impl(editor: &mut Editor) -> anyhow::Result<()>
559559
Ok(())
560560
}
561561

562-
fn write_all_impl(
562+
pub fn write_all_impl(
563563
cx: &mut compositor::Context,
564-
_args: &[Cow<str>],
565-
event: PromptEvent,
566-
quit: bool,
567564
force: bool,
565+
write_scratch: bool,
568566
) -> anyhow::Result<()> {
569-
if event != PromptEvent::Validate {
570-
return Ok(());
571-
}
572-
573567
let mut errors: Vec<&'static str> = Vec::new();
574568
let auto_format = cx.editor.config().auto_format;
575569
let jobs = &mut cx.jobs;
@@ -580,12 +574,13 @@ fn write_all_impl(
580574
.documents
581575
.values()
582576
.filter_map(|doc| {
583-
if doc.path().is_none() {
584-
errors.push("cannot write a buffer without a filename\n");
577+
if !doc.is_modified() {
585578
return None;
586579
}
587-
588-
if !doc.is_modified() {
580+
if doc.path().is_none() {
581+
if write_scratch {
582+
errors.push("cannot write a buffer without a filename\n");
583+
}
589584
return None;
590585
}
591586

@@ -611,20 +606,6 @@ fn write_all_impl(
611606
cx.editor.save::<PathBuf>(id, None, force)?;
612607
}
613608

614-
if quit {
615-
cx.block_try_flush_writes()?;
616-
617-
if !force {
618-
buffers_remaining_impl(cx.editor)?;
619-
}
620-
621-
// close all views
622-
let views: Vec<_> = cx.editor.tree.views().map(|(view, _)| view.id).collect();
623-
for view_id in views {
624-
cx.editor.close(view_id);
625-
}
626-
}
627-
628609
if !errors.is_empty() && !force {
629610
bail!("{:?}", errors);
630611
}
@@ -634,49 +615,50 @@ fn write_all_impl(
634615

635616
fn write_all(
636617
cx: &mut compositor::Context,
637-
args: &[Cow<str>],
618+
_args: &[Cow<str>],
638619
event: PromptEvent,
639620
) -> anyhow::Result<()> {
640621
if event != PromptEvent::Validate {
641622
return Ok(());
642623
}
643624

644-
write_all_impl(cx, args, event, false, false)
625+
write_all_impl(cx, false, true)
645626
}
646627

647628
fn write_all_quit(
648629
cx: &mut compositor::Context,
649-
args: &[Cow<str>],
630+
_args: &[Cow<str>],
650631
event: PromptEvent,
651632
) -> anyhow::Result<()> {
652633
if event != PromptEvent::Validate {
653634
return Ok(());
654635
}
655-
656-
write_all_impl(cx, args, event, true, false)
636+
write_all_impl(cx, false, true)?;
637+
quit_all_impl(cx, false)
657638
}
658639

659640
fn force_write_all_quit(
660641
cx: &mut compositor::Context,
661-
args: &[Cow<str>],
642+
_args: &[Cow<str>],
662643
event: PromptEvent,
663644
) -> anyhow::Result<()> {
664645
if event != PromptEvent::Validate {
665646
return Ok(());
666647
}
667-
668-
write_all_impl(cx, args, event, true, true)
648+
let _ = write_all_impl(cx, true, true);
649+
quit_all_impl(cx, true)
669650
}
670651

671-
fn quit_all_impl(editor: &mut Editor, force: bool) -> anyhow::Result<()> {
652+
fn quit_all_impl(cx: &mut compositor::Context, force: bool) -> anyhow::Result<()> {
653+
cx.block_try_flush_writes()?;
672654
if !force {
673-
buffers_remaining_impl(editor)?;
655+
buffers_remaining_impl(cx.editor)?;
674656
}
675657

676658
// close all views
677-
let views: Vec<_> = editor.tree.views().map(|(view, _)| view.id).collect();
659+
let views: Vec<_> = cx.editor.tree.views().map(|(view, _)| view.id).collect();
678660
for view_id in views {
679-
editor.close(view_id);
661+
cx.editor.close(view_id);
680662
}
681663

682664
Ok(())
@@ -691,8 +673,7 @@ fn quit_all(
691673
return Ok(());
692674
}
693675

694-
cx.block_try_flush_writes()?;
695-
quit_all_impl(cx.editor, false)
676+
quit_all_impl(cx, false)
696677
}
697678

698679
fn force_quit_all(
@@ -704,7 +685,7 @@ fn force_quit_all(
704685
return Ok(());
705686
}
706687

707-
quit_all_impl(cx.editor, true)
688+
quit_all_impl(cx, true)
708689
}
709690

710691
fn cquit(
@@ -722,8 +703,7 @@ fn cquit(
722703
.unwrap_or(1);
723704

724705
cx.editor.exit_code = exit_code;
725-
cx.block_try_flush_writes()?;
726-
quit_all_impl(cx.editor, false)
706+
quit_all_impl(cx, false)
727707
}
728708

729709
fn force_cquit(
@@ -741,7 +721,7 @@ fn force_cquit(
741721
.unwrap_or(1);
742722
cx.editor.exit_code = exit_code;
743723

744-
quit_all_impl(cx.editor, true)
724+
quit_all_impl(cx, true)
745725
}
746726

747727
fn theme(
@@ -2141,7 +2121,7 @@ pub static TYPABLE_COMMAND_MAP: Lazy<HashMap<&'static str, &'static TypableComma
21412121
.collect()
21422122
});
21432123

2144-
pub fn command_mode(cx: &mut Context) {
2124+
pub(super) fn command_mode(cx: &mut Context) {
21452125
let mut prompt = Prompt::new(
21462126
":".into(),
21472127
Some(':'),

helix-term/src/ui/editor.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -1473,7 +1473,15 @@ impl Component for EditorView {
14731473

14741474
Event::Mouse(event) => self.handle_mouse_event(event, &mut cx),
14751475
Event::IdleTimeout => self.handle_idle_timeout(&mut cx),
1476-
Event::FocusGained | Event::FocusLost => EventResult::Ignored(None),
1476+
Event::FocusGained => EventResult::Ignored(None),
1477+
Event::FocusLost => {
1478+
if context.editor.config().auto_save {
1479+
if let Err(e) = commands::typed::write_all_impl(context, false, false) {
1480+
context.editor.set_error(format!("{}", e));
1481+
}
1482+
}
1483+
EventResult::Consumed(None)
1484+
}
14771485
}
14781486
}
14791487

helix-view/src/editor.rs

+3
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ pub struct Config {
140140
pub auto_completion: bool,
141141
/// Automatic formatting on save. Defaults to true.
142142
pub auto_format: bool,
143+
/// Automatic save on focus lost. Defaults to false.
144+
pub auto_save: bool,
143145
/// Time in milliseconds since last keypress before idle timers trigger.
144146
/// Used for autocompletion, set to 0 for instant. Defaults to 400ms.
145147
#[serde(
@@ -592,6 +594,7 @@ impl Default for Config {
592594
auto_pairs: AutoPairConfig::default(),
593595
auto_completion: true,
594596
auto_format: true,
597+
auto_save: false,
595598
idle_timeout: Duration::from_millis(400),
596599
completion_trigger_len: 2,
597600
auto_info: true,

0 commit comments

Comments
 (0)