From 2fe15d8618ad2ec3c39f22f69780968ef5e43ed9 Mon Sep 17 00:00:00 2001 From: Rene Leonhardt <65483435+reneleonhardt@users.noreply.github.com> Date: Sun, 6 Jul 2025 18:08:49 +0200 Subject: [PATCH] feat(commands): make :x behave more like in vim Closes #4087 --- helix-term/src/commands/typed.rs | 37 ++++++++++++++++++++++++++++++-- helix-view/src/document.rs | 7 ++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index e1bb8ee32..d6ba238e0 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -663,6 +663,28 @@ fn force_write_quit( force_quit(cx, Args::default(), event) } +/// exit command: Write only if named and modified (but not externally), then quit. +fn write_exit(cx: &mut compositor::Context, _args: Args, event: PromptEvent) -> anyhow::Result<()> { + if event != PromptEvent::Validate { + return Ok(()); + } + + let (_, doc) = current!(cx.editor); + + // Current document named and modified? + // Write changes if not externally modified already. + if doc.is_modified() { + if doc.path().is_some() && !doc.externally_overwritten() { + write_impl(cx, None, false)?; + cx.block_try_flush_writes()?; + } else { + doc.reset_modified(); + } + } + + quit(cx, Args::default(), event) +} + /// Results in an error if there are modified buffers remaining and sets editor /// error, otherwise returns `Ok(())`. If the current document is unmodified, /// and there are modified documents, switches focus to one of them. @@ -2816,7 +2838,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ }, TypableCommand { name: "write-quit", - aliases: &["wq", "x"], + aliases: &["wq"], doc: "Write changes to disk and close the current view. Accepts an optional path (:wq some/path.txt)", fun: write_quit, completer: CommandCompleter::positional(&[completers::filename]), @@ -2827,7 +2849,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ }, TypableCommand { name: "write-quit!", - aliases: &["wq!", "x!"], + aliases: &["wq!"], doc: "Write changes to disk and close the current view forcefully. Accepts an optional path (:wq! some/path.txt)", fun: force_write_quit, completer: CommandCompleter::positional(&[completers::filename]), @@ -2836,6 +2858,17 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ ..Signature::DEFAULT }, }, + TypableCommand { + name: "exit", + aliases: &["xit", "x", "x!"], + doc: "Save the current view if named and modified (but not externally), then quit.", + fun: write_exit, + completer: CommandCompleter::none(), + signature: Signature { + positionals: (0, Some(0)), + ..Signature::DEFAULT + }, + }, TypableCommand { name: "write-all", aliases: &["wa"], diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index a0a56113c..874f84d6e 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -1199,6 +1199,13 @@ impl Document { }; } + /// Path given and externally modified since `last_saved_time`? + pub fn externally_overwritten(&self) -> bool { + self.path() + .and_then(|p| p.metadata().ok().and_then(|m| m.modified().ok())) + .is_some_and(|mtime| mtime > self.last_saved_time) + } + // Detect if the file is readonly and change the readonly field if necessary (unix only) pub fn detect_readonly(&mut self) { // Allows setting the flag for files the user cannot modify, like root files