feat(commands): make :x behave more like in vim

Closes #4087
pull/13898/head
Rene Leonhardt 2025-07-06 18:08:49 +02:00
parent 479c3b5584
commit 2fe15d8618
No known key found for this signature in database
GPG Key ID: 8C95C84F75AB1E8E
2 changed files with 42 additions and 2 deletions

View File

@ -663,6 +663,28 @@ fn force_write_quit(
force_quit(cx, Args::default(), event) 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 /// Results in an error if there are modified buffers remaining and sets editor
/// error, otherwise returns `Ok(())`. If the current document is unmodified, /// error, otherwise returns `Ok(())`. If the current document is unmodified,
/// and there are modified documents, switches focus to one of them. /// and there are modified documents, switches focus to one of them.
@ -2816,7 +2838,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
}, },
TypableCommand { TypableCommand {
name: "write-quit", 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)", doc: "Write changes to disk and close the current view. Accepts an optional path (:wq some/path.txt)",
fun: write_quit, fun: write_quit,
completer: CommandCompleter::positional(&[completers::filename]), completer: CommandCompleter::positional(&[completers::filename]),
@ -2827,7 +2849,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
}, },
TypableCommand { TypableCommand {
name: "write-quit!", 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)", doc: "Write changes to disk and close the current view forcefully. Accepts an optional path (:wq! some/path.txt)",
fun: force_write_quit, fun: force_write_quit,
completer: CommandCompleter::positional(&[completers::filename]), completer: CommandCompleter::positional(&[completers::filename]),
@ -2836,6 +2858,17 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
..Signature::DEFAULT ..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 { TypableCommand {
name: "write-all", name: "write-all",
aliases: &["wa"], aliases: &["wa"],

View File

@ -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) // Detect if the file is readonly and change the readonly field if necessary (unix only)
pub fn detect_readonly(&mut self) { pub fn detect_readonly(&mut self) {
// Allows setting the flag for files the user cannot modify, like root files // Allows setting the flag for files the user cannot modify, like root files