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. |
|
| `: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. |
|
| `: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. |
|
| `: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` | 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 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` | Write changes from all buffers to disk. |
|
||||||
| `:write-all!`, `:wa!` | Forcefully write changes from all buffers to disk creating necessary subdirectories. |
|
| `: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. |
|
| `: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)
|
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.
|
||||||
|
@ -2906,7 +2928,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]),
|
||||||
|
@ -2918,7 +2940,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]),
|
||||||
|
@ -2928,6 +2950,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"],
|
||||||
|
|
|
@ -143,8 +143,10 @@ async fn test_overwrite_protection() -> anyhow::Result<()> {
|
||||||
|
|
||||||
file.as_file_mut().flush()?;
|
file.as_file_mut().flush()?;
|
||||||
file.as_file_mut().sync_all()?;
|
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();
|
reload_file(&mut file).unwrap();
|
||||||
let mut file_content = String::new();
|
let mut file_content = String::new();
|
||||||
|
@ -152,6 +154,46 @@ async fn test_overwrite_protection() -> anyhow::Result<()> {
|
||||||
|
|
||||||
assert_eq!("extremely important content", file_content);
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1183,23 +1183,21 @@ impl Document {
|
||||||
|
|
||||||
pub fn pickup_last_saved_time(&mut self) {
|
pub fn pickup_last_saved_time(&mut self) {
|
||||||
self.last_saved_time = match self.path() {
|
self.last_saved_time = match self.path() {
|
||||||
Some(path) => match path.metadata() {
|
Some(path) => path.metadata().and_then(|m| m.modified()).unwrap_or_else(|err| {
|
||||||
Ok(metadata) => match metadata.modified() {
|
log::debug!("Could not fetch file system's mtime, falling back to current system time: {}", err);
|
||||||
Ok(mtime) => mtime,
|
SystemTime::now()
|
||||||
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()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => 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)
|
// 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
|
||||||
|
|
Loading…
Reference in New Issue