From 32f40a98e32a92bdea405c52bc077cd2e551749e Mon Sep 17 00:00:00 2001 From: wp Date: Thu, 3 Oct 2024 17:11:30 +0200 Subject: [PATCH 1/5] Implementation for issue #4087 --- helix-term/src/commands/typed.rs | 34 ++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 68ba9bab556e..0d169663ef4a 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -66,6 +66,25 @@ impl CommandSignature { } } +fn xit( + cx: &mut compositor::Context, + args: &[Cow], + event: PromptEvent, +)-> anyhow::Result<()> { + + if event != PromptEvent::Validate { + return Ok(()); + } + + let (_view, doc) = current!(cx.editor); + + if doc.is_modified() { + write_impl(cx, args.first(), false)?; + } + cx.block_try_flush_writes()?; + quit(cx, &[], event) +} + fn quit(cx: &mut compositor::Context, args: &[Cow], event: PromptEvent) -> anyhow::Result<()> { log::debug!("quitting..."); @@ -2520,7 +2539,18 @@ fn read(cx: &mut compositor::Context, args: &[Cow], event: PromptEvent) -> Ok(()) } +fn exit() { + +} + pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ + TypableCommand { + name: "xit", + aliases: &["x"], + doc: "Write changes to disk if any are made. Otherwise just close. Doesn't require a path if buffer is not modified.", + fun: xit, + signature: CommandSignature::positional(&[completers::filename]), + }, TypableCommand { name: "quit", aliases: &["q"], @@ -2673,14 +2703,14 @@ 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, signature: CommandSignature::positional(&[completers::filename]), }, 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, signature: CommandSignature::positional(&[completers::filename]), From 69f3b0eb3ee9204ebddf140dc74a23b8f2d76b4c Mon Sep 17 00:00:00 2001 From: williamporomaa Date: Fri, 4 Oct 2024 15:42:13 +0200 Subject: [PATCH 2/5] Removed unused empty function Removed unused function that accidentally got pushed up with previous commit --- helix-term/src/commands/typed.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 0d169663ef4a..ac9212461c3b 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -2539,10 +2539,6 @@ fn read(cx: &mut compositor::Context, args: &[Cow], event: PromptEvent) -> Ok(()) } -fn exit() { - -} - pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ TypableCommand { name: "xit", From 47afdbf84c8c88646a56cc490dc784cee4f9b8f1 Mon Sep 17 00:00:00 2001 From: Phyfl <100218248+Phyfl@users.noreply.github.com> Date: Mon, 7 Oct 2024 13:50:34 +0200 Subject: [PATCH 3/5] Added force version of command xit, called force_xit. --- helix-term/src/commands/typed.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index ac9212461c3b..fec44022304c 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -85,6 +85,25 @@ fn xit( quit(cx, &[], event) } +fn force_xit( + cx: &mut compositor::Context, + args: &[Cow], + event: PromptEvent, +)-> anyhow::Result<()> { + + if event != PromptEvent::Validate { + return Ok(()); + } + + let (_view, doc) = current!(cx.editor); + + if doc.is_modified() { + write_impl(cx, args.first(), true)?; + } + cx.block_try_flush_writes()?; + quit(cx, &[], event) +} + fn quit(cx: &mut compositor::Context, args: &[Cow], event: PromptEvent) -> anyhow::Result<()> { log::debug!("quitting..."); @@ -2547,6 +2566,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ fun: xit, signature: CommandSignature::positional(&[completers::filename]), }, + TypableCommand { + name: "xit!", + aliases: &["x!"], + doc: "Write changes to disk if any are made. Otherwise just close. Doesn't require a path if buffer is not modified. Force write.", + fun: force_xit, + signature: CommandSignature::positional(&[completers::filename]), + }, TypableCommand { name: "quit", aliases: &["q"], From 205b5167b6a4d0c45526e6ca3460a23df3f1ce71 Mon Sep 17 00:00:00 2001 From: Phyfl <100218248+Phyfl@users.noreply.github.com> Date: Mon, 7 Oct 2024 19:21:24 +0200 Subject: [PATCH 4/5] Updated write.rs integrationtest to comply with new definition of :x --- helix-term/tests/test/commands/write.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helix-term/tests/test/commands/write.rs b/helix-term/tests/test/commands/write.rs index aba101e9fd6c..bb3992d49174 100644 --- a/helix-term/tests/test/commands/write.rs +++ b/helix-term/tests/test/commands/write.rs @@ -144,7 +144,7 @@ async fn test_overwrite_protection() -> anyhow::Result<()> { file.as_file_mut().flush()?; file.as_file_mut().sync_all()?; - test_key_sequence(&mut app, Some(":x"), None, false).await?; + test_key_sequence(&mut app, Some("iOverwriteData:x"), None, false).await?; reload_file(&mut file).unwrap(); let mut file_content = String::new(); From 64a5ab632e0a30f2c95cbb6959b829962ce1c44a Mon Sep 17 00:00:00 2001 From: Phyfl <100218248+Phyfl@users.noreply.github.com> Date: Mon, 7 Oct 2024 22:30:29 +0200 Subject: [PATCH 5/5] Created tests for 4 cases of the xit operation --- helix-term/tests/test/commands/write.rs | 97 +++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/helix-term/tests/test/commands/write.rs b/helix-term/tests/test/commands/write.rs index bb3992d49174..51ddd8e01a25 100644 --- a/helix-term/tests/test/commands/write.rs +++ b/helix-term/tests/test/commands/write.rs @@ -9,6 +9,103 @@ use helix_view::doc; use super::*; + +#[tokio::test(flavor = "multi_thread")] +async fn test_xit_w_buffer_w_path() -> anyhow::Result<()> { + let mut file = tempfile::NamedTempFile::new()?; + let mut app = helpers::AppBuilder::new() + .with_file(file.path(), None) + .build()?; + //Check for write operation on given path and edited buffer + test_key_sequence( + &mut app, + Some("iBecause of the obvious threat to untold numbers of citizens due to the crisis that is even now developing, this radio station will remain on the air day and night.:x"), + None, + true, + ) + .await?; + + + reload_file(&mut file).unwrap(); + let mut file_content = String::new(); + file.as_file_mut().read_to_string(&mut file_content)?; + + assert_eq!( + LineFeedHandling::Native.apply("Because of the obvious threat to untold numbers of citizens due to the crisis that is even now developing, this radio station will remain on the air day and night.\n"), + file_content + ); + + Ok(()) +} + + +#[tokio::test(flavor = "multi_thread")] +async fn test_xit_wo_buffer_w_path() -> anyhow::Result<()> { + let mut file = tempfile::NamedTempFile::new()?; + let mut app = helpers::AppBuilder::new() + .with_file(file.path(), None) + .build()?; + + helpers::run_event_loop_until_idle(&mut app).await; + + file.as_file_mut() + .write_all("extremely important content".as_bytes())?; + file.as_file_mut().flush()?; + file.as_file_mut().sync_all()?; + + test_key_sequence(&mut app, Some(":x"), None, true).await?; + + reload_file(&mut file).unwrap(); + let mut file_content = String::new(); + file.read_to_string(&mut file_content)?; + //check that nothing is written to file + assert_eq!("extremely important content", file_content); + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_xit_wo_buffer_wo_path() -> anyhow::Result<()> { + + test_key_sequence( + &mut AppBuilder::new().build()?, + Some(format!(":x").as_ref()), + None, + true, + ) + .await?; + + //helpers::assert_file_has_content(&mut file, &LineFeedHandling::Native.apply("hello"))?; + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_xit_w_buffer_wo_file() -> anyhow::Result<()> { + let mut file = tempfile::NamedTempFile::new()?; + test_key_sequence(//try to write without destination + &mut AppBuilder::new().build()?, + Some(format!("itest:x").as_ref()), + None, + false + ) + .await?; + test_key_sequence(//try to write with path succeeds + &mut AppBuilder::new().build()?, + Some(format!("iMicCheck:x {}", file.path().to_string_lossy()).as_ref()), + Some(&|app| { + assert!(!app.editor.is_err()); + }), + true, + ) + .await?; + + helpers::assert_file_has_content(&mut file, &LineFeedHandling::Native.apply("MicCheck"))?; + + Ok(()) +} + + #[tokio::test(flavor = "multi_thread")] async fn test_write_quit_fail() -> anyhow::Result<()> { let file = helpers::new_readonly_tempfile()?;