on master help sign in
fix write-quit with auto format
aaa1450Skyler Hawthorne
helix-term/src/application.rs+5−1
@@ -971,7 +971,11 @@ impl Application {
971 // want to try to run as much cleanup as we can, regardless of
971 // want to try to run as much cleanup as we can, regardless of
972 // errors along the way
972 // errors along the way
973
973
974− let mut result = match self.jobs.finish().await {
974+ let mut result = match self
975+ .jobs
976+ .finish(Some(&mut self.editor), Some(&mut self.compositor))
977+ .await
978+ {
975 Ok(_) => Ok(()),
979 Ok(_) => Ok(()),
976 Err(err) => {
980 Err(err) => {
977 log::error!("Error executing job: {}", err);
981 log::error!("Error executing job: {}", err);
helix-term/src/commands.rs+11−8
@@ -47,6 +47,7 @@ use movement::Movement;
47use crate::{
47use crate::{
48 args,
48 args,
49 compositor::{self, Component, Compositor},
49 compositor::{self, Component, Compositor},
50+ job::Callback,
50 keymap::ReverseKeymap,
51 keymap::ReverseKeymap,
51 ui::{self, overlay::overlayed, FilePicker, Picker, Popup, Prompt, PromptEvent},
52 ui::{self, overlay::overlayed, FilePicker, Picker, Popup, Prompt, PromptEvent},
52};
53};
@@ -107,10 +108,11 @@ impl<'a> Context<'a> {
107 let callback = Box::pin(async move {
108 let callback = Box::pin(async move {
108 let json = call.await?;
109 let json = call.await?;
109 let response = serde_json::from_value(json)?;
110 let response = serde_json::from_value(json)?;
110− let call: job::Callback =
111+ let call: job::Callback = Callback::EditorCompositor(Box::new(
111− Box::new(move |editor: &mut Editor, compositor: &mut Compositor| {
112+ move |editor: &mut Editor, compositor: &mut Compositor| {
112 callback(editor, compositor, response)
113 callback(editor, compositor, response)
114+ },
113− });
115+ ));
114 Ok(call)
116 Ok(call)
115 });
117 });
116 self.jobs.callback(callback);
118 self.jobs.callback(callback);
@@ -1925,8 +1927,8 @@ fn global_search(cx: &mut Context) {
1925 let show_picker = async move {
1927 let show_picker = async move {
1926 let all_matches: Vec<FileResult> =
1928 let all_matches: Vec<FileResult> =
1927 UnboundedReceiverStream::new(all_matches_rx).collect().await;
1929 UnboundedReceiverStream::new(all_matches_rx).collect().await;
1928− let call: job::Callback =
1930+ let call: job::Callback = Callback::EditorCompositor(Box::new(
1929− Box::new(move |editor: &mut Editor, compositor: &mut Compositor| {
1931+ move |editor: &mut Editor, compositor: &mut Compositor| {
1930 if all_matches.is_empty() {
1932 if all_matches.is_empty() {
1931 editor.set_status("No matches found");
1933 editor.set_status("No matches found");
1932 return;
1934 return;
@@ -1962,7 +1964,8 @@ fn global_search(cx: &mut Context) {
1962 },
1964 },
1963 );
1965 );
1964 compositor.push(Box::new(overlayed(picker)));
1966 compositor.push(Box::new(overlayed(picker)));
1967+ },
1965− });
1968+ ));
1966 Ok(call)
1969 Ok(call)
1967 };
1970 };
1968 cx.jobs.callback(show_picker);
1971 cx.jobs.callback(show_picker);
@@ -2516,7 +2519,7 @@ async fn make_format_callback(
2516 write: Option<(Option<PathBuf>, bool)>,
2519 write: Option<(Option<PathBuf>, bool)>,
2517) -> anyhow::Result<job::Callback> {
2520) -> anyhow::Result<job::Callback> {
2518 let format = format.await?;
2521 let format = format.await?;
2519− let call: job::Callback = Box::new(move |editor, _compositor| {
2522+ let call: job::Callback = Callback::EditorCompositor(Box::new(move |editor, _compositor| {
2520 if !editor.documents.contains_key(&doc_id) {
2523 if !editor.documents.contains_key(&doc_id) {
2521 return;
2524 return;
2522 }
2525 }
@@ -2546,7 +2549,7 @@ async fn make_format_callback(
2546 } else {
2549 } else {
2547 log::info!("discarded formatting changes because the document changed");
2550 log::info!("discarded formatting changes because the document changed");
2548 }
2551 }
2549− });
2552+ }));
2550
2553
2551 Ok(call)
2554 Ok(call)
2552}
2555}
helix-term/src/commands/dap.rs+14−11
@@ -118,11 +118,14 @@ fn dap_callback<T, F>(
118 let callback = Box::pin(async move {
118 let callback = Box::pin(async move {
119 let json = call.await?;
119 let json = call.await?;
120 let response = serde_json::from_value(json)?;
120 let response = serde_json::from_value(json)?;
121+ let call: Callback = Callback::EditorCompositor(Box::new(
121− let call: Callback = Box::new(move |editor: &mut Editor, compositor: &mut Compositor| {
122+ move |editor: &mut Editor, compositor: &mut Compositor| {
122− callback(editor, compositor, response)
123+ callback(editor, compositor, response)
124+ },
123− });
125+ ));
124 Ok(call)
126 Ok(call)
125 });
127 });
128+
126 jobs.callback(callback);
129 jobs.callback(callback);
127}
130}
128
131
@@ -274,10 +277,10 @@ pub fn dap_launch(cx: &mut Context) {
274 let completions = template.completion.clone();
277 let completions = template.completion.clone();
275 let name = template.name.clone();
278 let name = template.name.clone();
276 let callback = Box::pin(async move {
279 let callback = Box::pin(async move {
277− let call: Callback = Box::new(move |_editor, compositor| {
280+ let call: Callback = Callback::Compositor(Box::new(move |compositor| {
278 let prompt = debug_parameter_prompt(completions, name, Vec::new());
281 let prompt = debug_parameter_prompt(completions, name, Vec::new());
279 compositor.push(Box::new(prompt));
282 compositor.push(Box::new(prompt));
280− });
283+ }));
281 Ok(call)
284 Ok(call)
282 });
285 });
283 cx.jobs.callback(callback);
286 cx.jobs.callback(callback);
@@ -332,10 +335,10 @@ fn debug_parameter_prompt(
332 let config_name = config_name.clone();
335 let config_name = config_name.clone();
333 let params = params.clone();
336 let params = params.clone();
334 let callback = Box::pin(async move {
337 let callback = Box::pin(async move {
335− let call: Callback = Box::new(move |_editor, compositor| {
338+ let call: Callback = Callback::Compositor(Box::new(move |compositor| {
336 let prompt = debug_parameter_prompt(completions, config_name, params);
339 let prompt = debug_parameter_prompt(completions, config_name, params);
337 compositor.push(Box::new(prompt));
340 compositor.push(Box::new(prompt));
338− });
341+ }));
339 Ok(call)
342 Ok(call)
340 });
343 });
341 cx.jobs.callback(callback);
344 cx.jobs.callback(callback);
@@ -582,7 +585,7 @@ pub fn dap_edit_condition(cx: &mut Context) {
582 None => return,
585 None => return,
583 };
586 };
584 let callback = Box::pin(async move {
587 let callback = Box::pin(async move {
585− let call: Callback = Box::new(move |editor, compositor| {
588+ let call: Callback = Callback::EditorCompositor(Box::new(move |editor, compositor| {
586 let mut prompt = Prompt::new(
589 let mut prompt = Prompt::new(
587 "condition:".into(),
590 "condition:".into(),
588 None,
591 None,
@@ -610,7 +613,7 @@ pub fn dap_edit_condition(cx: &mut Context) {
610 prompt.insert_str(&condition, editor)
613 prompt.insert_str(&condition, editor)
611 }
614 }
612 compositor.push(Box::new(prompt));
615 compositor.push(Box::new(prompt));
613− });
616+ }));
614 Ok(call)
617 Ok(call)
615 });
618 });
616 cx.jobs.callback(callback);
619 cx.jobs.callback(callback);
@@ -624,7 +627,7 @@ pub fn dap_edit_log(cx: &mut Context) {
624 None => return,
627 None => return,
625 };
628 };
626 let callback = Box::pin(async move {
629 let callback = Box::pin(async move {
627− let call: Callback = Box::new(move |editor, compositor| {
630+ let call: Callback = Callback::EditorCompositor(Box::new(move |editor, compositor| {
628 let mut prompt = Prompt::new(
631 let mut prompt = Prompt::new(
629 "log-message:".into(),
632 "log-message:".into(),
630 None,
633 None,
@@ -651,7 +654,7 @@ pub fn dap_edit_log(cx: &mut Context) {
651 prompt.insert_str(&log_message, editor);
654 prompt.insert_str(&log_message, editor);
652 }
655 }
653 compositor.push(Box::new(prompt));
656 compositor.push(Box::new(prompt));
654− });
657+ }));
655 Ok(call)
658 Ok(call)
656 });
659 });
657 cx.jobs.callback(callback);
660 cx.jobs.callback(callback);
helix-term/src/commands/typed.rs+10−7
@@ -519,9 +519,10 @@ fn write_quit(
519
519
520 write_impl(cx, args.first(), false)?;
520 write_impl(cx, args.first(), false)?;
521
521
522+ tokio::task::block_in_place(|| helix_lsp::block_on(cx.jobs.finish(Some(cx.editor), None)))?;
523+
522 let doc = doc_mut!(cx.editor);
524 let doc = doc_mut!(cx.editor);
523
525
524− tokio::task::block_in_place(|| helix_lsp::block_on(cx.jobs.finish()))?;
525 tokio::task::block_in_place(|| helix_lsp::block_on(doc.try_flush_saves()))
526 tokio::task::block_in_place(|| helix_lsp::block_on(doc.try_flush_saves()))
526 .map(|result| result.map(|_| ()))
527 .map(|result| result.map(|_| ()))
527 .unwrap_or(Ok(()))?;
528 .unwrap_or(Ok(()))?;
@@ -1491,12 +1492,13 @@ fn tree_sitter_subtree(
1491 let contents = format!("```tsq\n{}\n```", selected_node.to_sexp());
1492 let contents = format!("```tsq\n{}\n```", selected_node.to_sexp());
1492
1493
1493 let callback = async move {
1494 let callback = async move {
1494− let call: job::Callback =
1495+ let call: job::Callback = Callback::EditorCompositor(Box::new(
1495− Box::new(move |editor: &mut Editor, compositor: &mut Compositor| {
1496+ move |editor: &mut Editor, compositor: &mut Compositor| {
1496 let contents = ui::Markdown::new(contents, editor.syn_loader.clone());
1497 let contents = ui::Markdown::new(contents, editor.syn_loader.clone());
1497 let popup = Popup::new("hover", contents).auto_close(true);
1498 let popup = Popup::new("hover", contents).auto_close(true);
1498 compositor.replace_or_push("hover", popup);
1499 compositor.replace_or_push("hover", popup);
1500+ },
1499− });
1501+ ));
1500 Ok(call)
1502 Ok(call)
1501 };
1503 };
1502
1504
@@ -1604,8 +1606,8 @@ fn run_shell_command(
1604
1606
1605 if !output.is_empty() {
1607 if !output.is_empty() {
1606 let callback = async move {
1608 let callback = async move {
1607− let call: job::Callback =
1609+ let call: job::Callback = Callback::EditorCompositor(Box::new(
1608− Box::new(move |editor: &mut Editor, compositor: &mut Compositor| {
1610+ move |editor: &mut Editor, compositor: &mut Compositor| {
1609 let contents = ui::Markdown::new(
1611 let contents = ui::Markdown::new(
1610 format!("```sh\n{}\n```", output),
1612 format!("```sh\n{}\n```", output),
1611 editor.syn_loader.clone(),
1613 editor.syn_loader.clone(),
@@ -1614,7 +1616,8 @@ fn run_shell_command(
1614 helix_core::Position::new(editor.cursor().0.unwrap_or_default().row, 2),
1616 helix_core::Position::new(editor.cursor().0.unwrap_or_default().row, 2),
1615 ));
1617 ));
1616 compositor.replace_or_push("shell", popup);
1618 compositor.replace_or_push("shell", popup);
1619+ },
1617− });
1620+ ));
1618 Ok(call)
1621 Ok(call)
1619 };
1622 };
1620
1623
helix-term/src/job.rs+41−6
@@ -5,7 +5,12 @@ use crate::compositor::Compositor;
5use futures_util::future::{BoxFuture, Future, FutureExt};
5use futures_util::future::{BoxFuture, Future, FutureExt};
6use futures_util::stream::{FuturesUnordered, StreamExt};
6use futures_util::stream::{FuturesUnordered, StreamExt};
7
7
8+pub enum Callback {
9+ EditorCompositor(Box<dyn FnOnce(&mut Editor, &mut Compositor) + Send>),
8−pub type Callback = Box<dyn FnOnce(&mut Editor, &mut Compositor) + Send>;
10+ Editor(Box<dyn FnOnce(&mut Editor) + Send>),
11+ Compositor(Box<dyn FnOnce(&mut Compositor) + Send>),
12+}
13+
9pub type JobFuture = BoxFuture<'static, anyhow::Result<Option<Callback>>>;
14pub type JobFuture = BoxFuture<'static, anyhow::Result<Option<Callback>>>;
10
15
11pub struct Job {
16pub struct Job {
@@ -68,9 +73,11 @@ impl Jobs {
68 ) {
73 ) {
69 match call {
74 match call {
70 Ok(None) => {}
75 Ok(None) => {}
71− Ok(Some(call)) => {
76+ Ok(Some(call)) => match call {
72− call(editor, compositor);
77+ Callback::EditorCompositor(call) => call(editor, compositor),
78+ Callback::Editor(call) => call(editor),
79+ Callback::Compositor(call) => call(compositor),
73− }
80+ },
74 Err(e) => {
81 Err(e) => {
75 editor.set_error(format!("Async job failed: {}", e));
82 editor.set_error(format!("Async job failed: {}", e));
76 }
83 }
@@ -93,13 +100,41 @@ impl Jobs {
93 }
100 }
94
101
95 /// Blocks until all the jobs that need to be waited on are done.
102 /// Blocks until all the jobs that need to be waited on are done.
96− pub async fn finish(&mut self) -> anyhow::Result<()> {
103+ pub async fn finish(
104+ &mut self,
105+ mut editor: Option<&mut Editor>,
106+ mut compositor: Option<&mut Compositor>,
107+ ) -> anyhow::Result<()> {
97 log::debug!("waiting on jobs...");
108 log::debug!("waiting on jobs...");
98 let mut wait_futures = std::mem::take(&mut self.wait_futures);
109 let mut wait_futures = std::mem::take(&mut self.wait_futures);
99 while let (Some(job), tail) = wait_futures.into_future().await {
110 while let (Some(job), tail) = wait_futures.into_future().await {
100 match job {
111 match job {
101− Ok(_) => {
112+ Ok(callback) => {
102 wait_futures = tail;
113 wait_futures = tail;
114+
115+ if let Some(callback) = callback {
116+ // clippy doesn't realize this is an error without the derefs
117+ #[allow(clippy::needless_option_as_deref)]
118+ match callback {
119+ Callback::EditorCompositor(call)
120+ if editor.is_some() && compositor.is_some() =>
121+ {
122+ call(
123+ editor.as_deref_mut().unwrap(),
124+ compositor.as_deref_mut().unwrap(),
125+ )
126+ }
127+ Callback::Editor(call) if editor.is_some() => {
128+ call(editor.as_deref_mut().unwrap())
129+ }
130+ Callback::Compositor(call) if compositor.is_some() => {
131+ call(compositor.as_deref_mut().unwrap())
132+ }
133+
134+ // skip callbacks for which we don't have the necessary references
135+ _ => (),
136+ }
137+ }
103 }
138 }
104 Err(e) => {
139 Err(e) => {
105 self.wait_futures = tail;
140 self.wait_futures = tail;
helix-term/src/ui/editor.rs+4−3
@@ -1,7 +1,8 @@
1use crate::{
1use crate::{
2 commands,
2 commands,
3 compositor::{Component, Context, Event, EventResult},
3 compositor::{Component, Context, Event, EventResult},
4+ job::{self, Callback},
4− job, key,
5+ key,
5 keymap::{KeymapResult, Keymaps},
6 keymap::{KeymapResult, Keymaps},
6 ui::{Completion, ProgressSpinners},
7 ui::{Completion, ProgressSpinners},
7};
8};
@@ -944,9 +945,9 @@ impl EditorView {
944
945
945 // TODO: Use an on_mode_change hook to remove signature help
946 // TODO: Use an on_mode_change hook to remove signature help
946 cxt.jobs.callback(async {
947 cxt.jobs.callback(async {
947− let call: job::Callback = Box::new(|_editor, compositor| {
948+ let call: job::Callback = Callback::Compositor(Box::new(|compositor| {
948 compositor.remove(SignatureHelp::ID);
949 compositor.remove(SignatureHelp::ID);
949− });
950+ }));
950 Ok(call)
951 Ok(call)
951 });
952 });
952 }
953 }