mirror of https://github.com/helix-editor/helix
Compare commits
6 Commits
88fef86f58
...
40a38073ef
Author | SHA1 | Date |
---|---|---|
|
40a38073ef | |
|
4281228da3 | |
|
4647374449 | |
|
45fb545ce9 | |
|
c4fb8bfafa | |
|
2fe15d8618 |
|
@ -21,8 +21,9 @@
|
|||
| `:line-ending` | Set the document's default line ending. Options: crlf, lf. |
|
||||
| `:earlier`, `:ear` | Jump back to an earlier point in edit history. Accepts a number of steps or a time span. |
|
||||
| `:later`, `:lat` | Jump to a later point in edit history. Accepts a number of steps or a time span. |
|
||||
| `:write-quit`, `:wq`, `:x` | Write changes to disk and close the current view. Accepts an optional path (:wq some/path.txt) |
|
||||
| `:write-quit!`, `:wq!`, `:x!` | Write changes to disk and close the current view forcefully. Accepts an optional path (:wq! some/path.txt) |
|
||||
| `:write-quit`, `:wq` | Write changes to disk and close the current view. Accepts an optional path (:wq some/path.txt) |
|
||||
| `:write-quit!`, `:wq!` | Write changes to disk and close the current view forcefully. Accepts an optional path (:wq! some/path.txt) |
|
||||
| `:exit`, `:xit`, `:x`, `:x!` | Save the current view if named and modified (but not externally), then quit. |
|
||||
| `:write-all`, `:wa` | Write changes from all buffers to disk. |
|
||||
| `:write-all!`, `:wa!` | Forcefully write changes from all buffers to disk creating necessary subdirectories. |
|
||||
| `:write-quit-all`, `:wqa`, `:xa` | Write changes from all buffers to disk and close all views. |
|
||||
|
|
|
@ -728,6 +728,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.
|
||||
|
@ -2906,7 +2928,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]),
|
||||
|
@ -2918,7 +2940,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]),
|
||||
|
@ -2928,6 +2950,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"],
|
||||
|
|
|
@ -143,8 +143,10 @@ async fn test_overwrite_protection() -> anyhow::Result<()> {
|
|||
|
||||
file.as_file_mut().flush()?;
|
||||
file.as_file_mut().sync_all()?;
|
||||
let last_saved_time = file.path().metadata()?.modified()?;
|
||||
|
||||
test_key_sequence(&mut app, Some(":x<ret>"), None, false).await?;
|
||||
// Exit empty unmodified view, externally changed file shouldn't be overwritten
|
||||
test_key_sequence(&mut app, Some(":x<ret>"), None, true).await?;
|
||||
|
||||
reload_file(&mut file).unwrap();
|
||||
let mut file_content = String::new();
|
||||
|
@ -152,6 +154,46 @@ async fn test_overwrite_protection() -> anyhow::Result<()> {
|
|||
|
||||
assert_eq!("extremely important content", file_content);
|
||||
|
||||
let saved_time = file.path().metadata()?.modified()?;
|
||||
assert_eq!(saved_time, last_saved_time);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_overwrite_if_not_modified_since() -> anyhow::Result<()> {
|
||||
let mut file = tempfile::NamedTempFile::new()?;
|
||||
file.as_file_mut().write_all("34".as_bytes())?;
|
||||
file.as_file_mut().flush()?;
|
||||
file.as_file_mut().sync_all()?;
|
||||
|
||||
let mut app = helpers::AppBuilder::new()
|
||||
.with_file(file.path(), None)
|
||||
.build()?;
|
||||
|
||||
helpers::run_event_loop_until_idle(&mut app).await;
|
||||
|
||||
// Modify and exit allowing saving because file hasn't been changed externally since opening
|
||||
test_key_sequence(&mut app, Some("i12<esc>:x<ret>"), None, true).await?;
|
||||
|
||||
reload_file(&mut file).unwrap();
|
||||
let mut file_content = String::new();
|
||||
file.read_to_string(&mut file_content)?;
|
||||
|
||||
assert_eq!("1234", file_content);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_exit_if_not_named() -> anyhow::Result<()> {
|
||||
let mut app = helpers::AppBuilder::new().build()?;
|
||||
|
||||
helpers::run_event_loop_until_idle(&mut app).await;
|
||||
|
||||
// Modify and exit without saving because file hasn't been named yet
|
||||
test_key_sequence(&mut app, Some("i12<esc>:x<ret>"), None, true).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -1183,23 +1183,21 @@ impl Document {
|
|||
|
||||
pub fn pickup_last_saved_time(&mut self) {
|
||||
self.last_saved_time = match self.path() {
|
||||
Some(path) => match path.metadata() {
|
||||
Ok(metadata) => match metadata.modified() {
|
||||
Ok(mtime) => mtime,
|
||||
Err(err) => {
|
||||
log::debug!("Could not fetch file system's mtime, falling back to current system time: {}", err);
|
||||
SystemTime::now()
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
log::debug!("Could not fetch file system's mtime, falling back to current system time: {}", err);
|
||||
SystemTime::now()
|
||||
}
|
||||
},
|
||||
Some(path) => path.metadata().and_then(|m| m.modified()).unwrap_or_else(|err| {
|
||||
log::debug!("Could not fetch file system's mtime, falling back to current system time: {}", err);
|
||||
SystemTime::now()
|
||||
}),
|
||||
None => SystemTime::now(),
|
||||
};
|
||||
}
|
||||
|
||||
/// 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
|
||||
|
|
Loading…
Reference in New Issue