on master help sign in
various fixes in write-all path
57de4e6Skyler Hawthorne
helix-term/src/commands/typed.rs+19−6
@@ -573,13 +573,20 @@ fn write_all_impl(
573 return Ok(());
573 return Ok(());
574 }
574 }
575
575
576− let mut errors = String::new();
576+ let mut errors: Option<String> = None;
577 let auto_format = cx.editor.config().auto_format;
577 let auto_format = cx.editor.config().auto_format;
578 let jobs = &mut cx.jobs;
578 let jobs = &mut cx.jobs;
579+
579 // save all documents
580 // save all documents
580 for doc in &mut cx.editor.documents.values_mut() {
581 for doc in &mut cx.editor.documents.values_mut() {
581 if doc.path().is_none() {
582 if doc.path().is_none() {
583+ errors = errors
584+ .or_else(|| Some(String::new()))
585+ .map(|mut errs: String| {
582− errors.push_str("cannot write a buffer without a filename\n");
586+ errs.push_str("cannot write a buffer without a filename\n");
587+ errs
588+ });
589+
583 continue;
590 continue;
584 }
591 }
585
592
@@ -591,7 +598,7 @@ fn write_all_impl(
591 doc.auto_format().map(|fmt| {
598 doc.auto_format().map(|fmt| {
592 let callback =
599 let callback =
593 make_format_callback(doc.id(), doc.version(), fmt, Some((None, force)));
600 make_format_callback(doc.id(), doc.version(), fmt, Some((None, force)));
594− jobs.callback(callback);
601+ jobs.add(Job::with_callback(callback).wait_before_exiting());
595 })
602 })
596 } else {
603 } else {
597 None
604 None
@@ -603,12 +610,12 @@ fn write_all_impl(
603 }
610 }
604
611
605 if quit {
612 if quit {
613+ cx.block_try_flush_writes()?;
614+
606 if !force {
615 if !force {
607 buffers_remaining_impl(cx.editor)?;
616 buffers_remaining_impl(cx.editor)?;
608 }
617 }
609
618
610− cx.block_try_flush_writes()?;
611−
612 // close all views
619 // close all views
613 let views: Vec<_> = cx.editor.tree.views().map(|(view, _)| view.id).collect();
620 let views: Vec<_> = cx.editor.tree.views().map(|(view, _)| view.id).collect();
614 for view_id in views {
621 for view_id in views {
@@ -616,7 +623,13 @@ fn write_all_impl(
616 }
623 }
617 }
624 }
618
625
619− bail!(errors)
626+ if let Some(errs) = errors {
627+ if !force {
628+ bail!(errs);
629+ }
630+ }
631+
632+ Ok(())
620}
633}
621
634
622fn write_all(
635fn write_all(
helix-term/src/compositor.rs+6−6
@@ -29,17 +29,17 @@ pub struct Context<'a> {
29
29
30impl<'a> Context<'a> {
30impl<'a> Context<'a> {
31 /// Waits on all pending jobs, and then tries to flush all pending write
31 /// Waits on all pending jobs, and then tries to flush all pending write
32− /// operations for the current document.
32+ /// operations for all documents.
33 pub fn block_try_flush_writes(&mut self) -> anyhow::Result<()> {
33 pub fn block_try_flush_writes(&mut self) -> anyhow::Result<()> {
34 tokio::task::block_in_place(|| {
34 tokio::task::block_in_place(|| {
35 helix_lsp::block_on(self.jobs.finish(Some(self.editor), None))
35 helix_lsp::block_on(self.jobs.finish(Some(self.editor), None))
36 })?;
36 })?;
37
37
38− let doc = doc_mut!(self.editor);
38+ for doc in &mut self.editor.documents.values_mut() {
39−
40− tokio::task::block_in_place(|| helix_lsp::block_on(doc.try_flush_saves()))
39+ tokio::task::block_in_place(|| helix_lsp::block_on(doc.try_flush_saves()))
41− .map(|result| result.map(|_| ()))
40+ .map(|result| result.map(|_| ()))
42− .unwrap_or(Ok(()))?;
41+ .unwrap_or(Ok(()))?;
42+ }
43
43
44 Ok(())
44 Ok(())
45 }
45 }
helix-term/tests/integration.rs+1
@@ -22,5 +22,6 @@ mod test {
22 mod commands;
22 mod commands;
23 mod movement;
23 mod movement;
24 mod prompt;
24 mod prompt;
25+ mod splits;
25 mod write;
26 mod write;
26}
27}
helix-term/tests/test/commands.rs+2−10
@@ -1,7 +1,4 @@
1−use std::{
2− io::{Read, Write},
3− ops::RangeInclusive,
1+use std::ops::RangeInclusive;
4−};
5
2
6use helix_core::diagnostic::Severity;
3use helix_core::diagnostic::Severity;
7use helix_term::application::Application;
4use helix_term::application::Application;
@@ -86,12 +83,7 @@ async fn test_buffer_close_concurrent() -> anyhow::Result<()> {
86 )
83 )
87 .await?;
84 .await?;
88
85
89− file.as_file_mut().flush()?;
86+ helpers::assert_file_has_content(file.as_file_mut(), &RANGE.end().to_string())?;
90− file.as_file_mut().sync_all()?;
91−
92− let mut file_content = String::new();
93− file.as_file_mut().read_to_string(&mut file_content)?;
94− assert_eq!(RANGE.end().to_string(), file_content);
95
87
96 Ok(())
88 Ok(())
97}
89}
helix-term/tests/test/helpers.rs+25−3
@@ -1,10 +1,15 @@
1+use std::{
2+ fs::File,
3+ io::{Read, Write},
4+ path::PathBuf,
1−use std::{io::Write, path::PathBuf, time::Duration};
5+ time::Duration,
6+};
2
7
3use anyhow::bail;
8use anyhow::bail;
4use crossterm::event::{Event, KeyEvent};
9use crossterm::event::{Event, KeyEvent};
5−use helix_core::{test, Selection, Transaction};
10+use helix_core::{diagnostic::Severity, test, Selection, Transaction};
6use helix_term::{application::Application, args::Args, config::Config};
11use helix_term::{application::Application, args::Args, config::Config};
7−use helix_view::{doc, input::parse_macro};
12+use helix_view::{doc, input::parse_macro, Editor};
8use tempfile::NamedTempFile;
13use tempfile::NamedTempFile;
9use tokio_stream::wrappers::UnboundedReceiverStream;
14use tokio_stream::wrappers::UnboundedReceiverStream;
10
15
@@ -213,3 +218,20 @@ pub fn app_with_file<P: Into<PathBuf>>(path: P) -> anyhow::Result<Application> {
213 Config::default(),
218 Config::default(),
214 )
219 )
215}
220}
221+
222+pub fn assert_file_has_content(file: &mut File, content: &str) -> anyhow::Result<()> {
223+ file.flush()?;
224+ file.sync_all()?;
225+
226+ let mut file_content = String::new();
227+ file.read_to_string(&mut file_content)?;
228+ assert_eq!(content, file_content);
229+
230+ Ok(())
231+}
232+
233+pub fn assert_status_not_error(editor: &Editor) {
234+ if let Some((_, sev)) = editor.get_status() {
235+ assert_ne!(&Severity::Error, sev);
236+ }
237+}
helix-term/tests/test/splits.rsnew file+122
@@ -0,0 +1,122 @@
1+use super::*;
2+
3+#[tokio::test(flavor = "multi_thread")]
4+async fn test_split_write_quit_all() -> anyhow::Result<()> {
5+ let mut file1 = tempfile::NamedTempFile::new()?;
6+ let mut file2 = tempfile::NamedTempFile::new()?;
7+ let mut file3 = tempfile::NamedTempFile::new()?;
8+
9+ test_key_sequences(
10+ &mut helpers::app_with_file(file1.path())?,
11+ vec![
12+ (
13+ Some(&format!(
14+ "ihello1<esc>:sp<ret>:o {}<ret>ihello2<esc>:sp<ret>:o {}<ret>ihello3<esc>",
15+ file2.path().to_string_lossy(),
16+ file3.path().to_string_lossy()
17+ )),
18+ Some(&|app| {
19+ let docs: Vec<_> = app.editor.documents().collect();
20+ assert_eq!(3, docs.len());
21+
22+ let doc1 = docs
23+ .iter()
24+ .find(|doc| doc.path().unwrap() == file1.path())
25+ .unwrap();
26+
27+ assert_eq!("hello1", doc1.text().to_string());
28+
29+ let doc2 = docs
30+ .iter()
31+ .find(|doc| doc.path().unwrap() == file2.path())
32+ .unwrap();
33+
34+ assert_eq!("hello2", doc2.text().to_string());
35+
36+ let doc3 = docs
37+ .iter()
38+ .find(|doc| doc.path().unwrap() == file3.path())
39+ .unwrap();
40+
41+ assert_eq!("hello3", doc3.text().to_string());
42+
43+ helpers::assert_status_not_error(&app.editor);
44+ assert_eq!(3, app.editor.tree.views().count());
45+ }),
46+ ),
47+ (
48+ Some(":wqa<ret>"),
49+ Some(&|app| {
50+ helpers::assert_status_not_error(&app.editor);
51+ assert_eq!(0, app.editor.tree.views().count());
52+ }),
53+ ),
54+ ],
55+ true,
56+ )
57+ .await?;
58+
59+ helpers::assert_file_has_content(file1.as_file_mut(), "hello1")?;
60+ helpers::assert_file_has_content(file2.as_file_mut(), "hello2")?;
61+ helpers::assert_file_has_content(file3.as_file_mut(), "hello3")?;
62+
63+ Ok(())
64+}
65+
66+#[tokio::test(flavor = "multi_thread")]
67+async fn test_split_write_quit_same_file() -> anyhow::Result<()> {
68+ let mut file = tempfile::NamedTempFile::new()?;
69+
70+ test_key_sequences(
71+ &mut helpers::app_with_file(file.path())?,
72+ vec![
73+ (
74+ Some("O<esc>ihello<esc>:sp<ret>ogoodbye<esc>"),
75+ Some(&|app| {
76+ assert_eq!(2, app.editor.tree.views().count());
77+ helpers::assert_status_not_error(&app.editor);
78+
79+ let mut docs: Vec<_> = app.editor.documents().collect();
80+ assert_eq!(1, docs.len());
81+
82+ let doc = docs.pop().unwrap();
83+
84+ assert_eq!(
85+ helpers::platform_line("hello\ngoodbye"),
86+ doc.text().to_string()
87+ );
88+
89+ assert!(doc.is_modified());
90+ }),
91+ ),
92+ (
93+ Some(":wq<ret>"),
94+ Some(&|app| {
95+ helpers::assert_status_not_error(&app.editor);
96+ assert_eq!(1, app.editor.tree.views().count());
97+
98+ let mut docs: Vec<_> = app.editor.documents().collect();
99+ assert_eq!(1, docs.len());
100+
101+ let doc = docs.pop().unwrap();
102+
103+ assert_eq!(
104+ helpers::platform_line("hello\ngoodbye"),
105+ doc.text().to_string()
106+ );
107+
108+ assert!(!doc.is_modified());
109+ }),
110+ ),
111+ ],
112+ false,
113+ )
114+ .await?;
115+
116+ helpers::assert_file_has_content(
117+ file.as_file_mut(),
118+ &helpers::platform_line("hello\ngoodbye"),
119+ )?;
120+
121+ Ok(())
122+}