fix write-quit with auto format

write-quit will now save all files successfully even when there is auto
formatting
pull/2267/head
Skyler Hawthorne 2 years ago
parent c9418582d2
commit aaa1450678

@ -971,7 +971,11 @@ impl Application {
// want to try to run as much cleanup as we can, regardless of // want to try to run as much cleanup as we can, regardless of
// errors along the way // errors along the way
let mut result = match self.jobs.finish().await { let mut result = match self
.jobs
.finish(Some(&mut self.editor), Some(&mut self.compositor))
.await
{
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(err) => { Err(err) => {
log::error!("Error executing job: {}", err); log::error!("Error executing job: {}", err);

@ -47,6 +47,7 @@ use movement::Movement;
use crate::{ use crate::{
args, args,
compositor::{self, Component, Compositor}, compositor::{self, Component, Compositor},
job::Callback,
keymap::ReverseKeymap, keymap::ReverseKeymap,
ui::{self, overlay::overlayed, FilePicker, Picker, Popup, Prompt, PromptEvent}, ui::{self, overlay::overlayed, FilePicker, Picker, Popup, Prompt, PromptEvent},
}; };
@ -107,10 +108,11 @@ impl<'a> Context<'a> {
let callback = Box::pin(async move { let callback = Box::pin(async move {
let json = call.await?; let json = call.await?;
let response = serde_json::from_value(json)?; let response = serde_json::from_value(json)?;
let call: job::Callback = let call: job::Callback = Callback::EditorCompositor(Box::new(
Box::new(move |editor: &mut Editor, compositor: &mut Compositor| { move |editor: &mut Editor, compositor: &mut Compositor| {
callback(editor, compositor, response) callback(editor, compositor, response)
}); },
));
Ok(call) Ok(call)
}); });
self.jobs.callback(callback); self.jobs.callback(callback);
@ -1925,8 +1927,8 @@ fn global_search(cx: &mut Context) {
let show_picker = async move { let show_picker = async move {
let all_matches: Vec<FileResult> = let all_matches: Vec<FileResult> =
UnboundedReceiverStream::new(all_matches_rx).collect().await; UnboundedReceiverStream::new(all_matches_rx).collect().await;
let call: job::Callback = let call: job::Callback = Callback::EditorCompositor(Box::new(
Box::new(move |editor: &mut Editor, compositor: &mut Compositor| { move |editor: &mut Editor, compositor: &mut Compositor| {
if all_matches.is_empty() { if all_matches.is_empty() {
editor.set_status("No matches found"); editor.set_status("No matches found");
return; return;
@ -1962,7 +1964,8 @@ fn global_search(cx: &mut Context) {
}, },
); );
compositor.push(Box::new(overlayed(picker))); compositor.push(Box::new(overlayed(picker)));
}); },
));
Ok(call) Ok(call)
}; };
cx.jobs.callback(show_picker); cx.jobs.callback(show_picker);
@ -2516,7 +2519,7 @@ async fn make_format_callback(
write: Option<(Option<PathBuf>, bool)>, write: Option<(Option<PathBuf>, bool)>,
) -> anyhow::Result<job::Callback> { ) -> anyhow::Result<job::Callback> {
let format = format.await?; let format = format.await?;
let call: job::Callback = Box::new(move |editor, _compositor| { let call: job::Callback = Callback::EditorCompositor(Box::new(move |editor, _compositor| {
if !editor.documents.contains_key(&doc_id) { if !editor.documents.contains_key(&doc_id) {
return; return;
} }
@ -2546,7 +2549,7 @@ async fn make_format_callback(
} else { } else {
log::info!("discarded formatting changes because the document changed"); log::info!("discarded formatting changes because the document changed");
} }
}); }));
Ok(call) Ok(call)
} }

@ -118,11 +118,14 @@ fn dap_callback<T, F>(
let callback = Box::pin(async move { let callback = Box::pin(async move {
let json = call.await?; let json = call.await?;
let response = serde_json::from_value(json)?; let response = serde_json::from_value(json)?;
let call: Callback = Box::new(move |editor: &mut Editor, compositor: &mut Compositor| { let call: Callback = Callback::EditorCompositor(Box::new(
callback(editor, compositor, response) move |editor: &mut Editor, compositor: &mut Compositor| {
}); callback(editor, compositor, response)
},
));
Ok(call) Ok(call)
}); });
jobs.callback(callback); jobs.callback(callback);
} }
@ -274,10 +277,10 @@ pub fn dap_launch(cx: &mut Context) {
let completions = template.completion.clone(); let completions = template.completion.clone();
let name = template.name.clone(); let name = template.name.clone();
let callback = Box::pin(async move { let callback = Box::pin(async move {
let call: Callback = Box::new(move |_editor, compositor| { let call: Callback = Callback::Compositor(Box::new(move |compositor| {
let prompt = debug_parameter_prompt(completions, name, Vec::new()); let prompt = debug_parameter_prompt(completions, name, Vec::new());
compositor.push(Box::new(prompt)); compositor.push(Box::new(prompt));
}); }));
Ok(call) Ok(call)
}); });
cx.jobs.callback(callback); cx.jobs.callback(callback);
@ -332,10 +335,10 @@ fn debug_parameter_prompt(
let config_name = config_name.clone(); let config_name = config_name.clone();
let params = params.clone(); let params = params.clone();
let callback = Box::pin(async move { let callback = Box::pin(async move {
let call: Callback = Box::new(move |_editor, compositor| { let call: Callback = Callback::Compositor(Box::new(move |compositor| {
let prompt = debug_parameter_prompt(completions, config_name, params); let prompt = debug_parameter_prompt(completions, config_name, params);
compositor.push(Box::new(prompt)); compositor.push(Box::new(prompt));
}); }));
Ok(call) Ok(call)
}); });
cx.jobs.callback(callback); cx.jobs.callback(callback);
@ -582,7 +585,7 @@ pub fn dap_edit_condition(cx: &mut Context) {
None => return, None => return,
}; };
let callback = Box::pin(async move { let callback = Box::pin(async move {
let call: Callback = Box::new(move |editor, compositor| { let call: Callback = Callback::EditorCompositor(Box::new(move |editor, compositor| {
let mut prompt = Prompt::new( let mut prompt = Prompt::new(
"condition:".into(), "condition:".into(),
None, None,
@ -610,7 +613,7 @@ pub fn dap_edit_condition(cx: &mut Context) {
prompt.insert_str(&condition, editor) prompt.insert_str(&condition, editor)
} }
compositor.push(Box::new(prompt)); compositor.push(Box::new(prompt));
}); }));
Ok(call) Ok(call)
}); });
cx.jobs.callback(callback); cx.jobs.callback(callback);
@ -624,7 +627,7 @@ pub fn dap_edit_log(cx: &mut Context) {
None => return, None => return,
}; };
let callback = Box::pin(async move { let callback = Box::pin(async move {
let call: Callback = Box::new(move |editor, compositor| { let call: Callback = Callback::EditorCompositor(Box::new(move |editor, compositor| {
let mut prompt = Prompt::new( let mut prompt = Prompt::new(
"log-message:".into(), "log-message:".into(),
None, None,
@ -651,7 +654,7 @@ pub fn dap_edit_log(cx: &mut Context) {
prompt.insert_str(&log_message, editor); prompt.insert_str(&log_message, editor);
} }
compositor.push(Box::new(prompt)); compositor.push(Box::new(prompt));
}); }));
Ok(call) Ok(call)
}); });
cx.jobs.callback(callback); cx.jobs.callback(callback);

@ -519,9 +519,10 @@ fn write_quit(
write_impl(cx, args.first(), false)?; write_impl(cx, args.first(), false)?;
tokio::task::block_in_place(|| helix_lsp::block_on(cx.jobs.finish(Some(cx.editor), None)))?;
let doc = doc_mut!(cx.editor); let doc = doc_mut!(cx.editor);
tokio::task::block_in_place(|| helix_lsp::block_on(cx.jobs.finish()))?;
tokio::task::block_in_place(|| helix_lsp::block_on(doc.try_flush_saves())) tokio::task::block_in_place(|| helix_lsp::block_on(doc.try_flush_saves()))
.map(|result| result.map(|_| ())) .map(|result| result.map(|_| ()))
.unwrap_or(Ok(()))?; .unwrap_or(Ok(()))?;
@ -1491,12 +1492,13 @@ fn tree_sitter_subtree(
let contents = format!("```tsq\n{}\n```", selected_node.to_sexp()); let contents = format!("```tsq\n{}\n```", selected_node.to_sexp());
let callback = async move { let callback = async move {
let call: job::Callback = let call: job::Callback = Callback::EditorCompositor(Box::new(
Box::new(move |editor: &mut Editor, compositor: &mut Compositor| { move |editor: &mut Editor, compositor: &mut Compositor| {
let contents = ui::Markdown::new(contents, editor.syn_loader.clone()); let contents = ui::Markdown::new(contents, editor.syn_loader.clone());
let popup = Popup::new("hover", contents).auto_close(true); let popup = Popup::new("hover", contents).auto_close(true);
compositor.replace_or_push("hover", popup); compositor.replace_or_push("hover", popup);
}); },
));
Ok(call) Ok(call)
}; };
@ -1604,8 +1606,8 @@ fn run_shell_command(
if !output.is_empty() { if !output.is_empty() {
let callback = async move { let callback = async move {
let call: job::Callback = let call: job::Callback = Callback::EditorCompositor(Box::new(
Box::new(move |editor: &mut Editor, compositor: &mut Compositor| { move |editor: &mut Editor, compositor: &mut Compositor| {
let contents = ui::Markdown::new( let contents = ui::Markdown::new(
format!("```sh\n{}\n```", output), format!("```sh\n{}\n```", output),
editor.syn_loader.clone(), editor.syn_loader.clone(),
@ -1614,7 +1616,8 @@ fn run_shell_command(
helix_core::Position::new(editor.cursor().0.unwrap_or_default().row, 2), helix_core::Position::new(editor.cursor().0.unwrap_or_default().row, 2),
)); ));
compositor.replace_or_push("shell", popup); compositor.replace_or_push("shell", popup);
}); },
));
Ok(call) Ok(call)
}; };

@ -5,7 +5,12 @@ use crate::compositor::Compositor;
use futures_util::future::{BoxFuture, Future, FutureExt}; use futures_util::future::{BoxFuture, Future, FutureExt};
use futures_util::stream::{FuturesUnordered, StreamExt}; use futures_util::stream::{FuturesUnordered, StreamExt};
pub type Callback = Box<dyn FnOnce(&mut Editor, &mut Compositor) + Send>; pub enum Callback {
EditorCompositor(Box<dyn FnOnce(&mut Editor, &mut Compositor) + Send>),
Editor(Box<dyn FnOnce(&mut Editor) + Send>),
Compositor(Box<dyn FnOnce(&mut Compositor) + Send>),
}
pub type JobFuture = BoxFuture<'static, anyhow::Result<Option<Callback>>>; pub type JobFuture = BoxFuture<'static, anyhow::Result<Option<Callback>>>;
pub struct Job { pub struct Job {
@ -68,9 +73,11 @@ impl Jobs {
) { ) {
match call { match call {
Ok(None) => {} Ok(None) => {}
Ok(Some(call)) => { Ok(Some(call)) => match call {
call(editor, compositor); Callback::EditorCompositor(call) => call(editor, compositor),
} Callback::Editor(call) => call(editor),
Callback::Compositor(call) => call(compositor),
},
Err(e) => { Err(e) => {
editor.set_error(format!("Async job failed: {}", e)); editor.set_error(format!("Async job failed: {}", e));
} }
@ -93,13 +100,41 @@ impl Jobs {
} }
/// Blocks until all the jobs that need to be waited on are done. /// Blocks until all the jobs that need to be waited on are done.
pub async fn finish(&mut self) -> anyhow::Result<()> { pub async fn finish(
&mut self,
mut editor: Option<&mut Editor>,
mut compositor: Option<&mut Compositor>,
) -> anyhow::Result<()> {
log::debug!("waiting on jobs..."); log::debug!("waiting on jobs...");
let mut wait_futures = std::mem::take(&mut self.wait_futures); let mut wait_futures = std::mem::take(&mut self.wait_futures);
while let (Some(job), tail) = wait_futures.into_future().await { while let (Some(job), tail) = wait_futures.into_future().await {
match job { match job {
Ok(_) => { Ok(callback) => {
wait_futures = tail; wait_futures = tail;
if let Some(callback) = callback {
// clippy doesn't realize this is an error without the derefs
#[allow(clippy::needless_option_as_deref)]
match callback {
Callback::EditorCompositor(call)
if editor.is_some() && compositor.is_some() =>
{
call(
editor.as_deref_mut().unwrap(),
compositor.as_deref_mut().unwrap(),
)
}
Callback::Editor(call) if editor.is_some() => {
call(editor.as_deref_mut().unwrap())
}
Callback::Compositor(call) if compositor.is_some() => {
call(compositor.as_deref_mut().unwrap())
}
// skip callbacks for which we don't have the necessary references
_ => (),
}
}
} }
Err(e) => { Err(e) => {
self.wait_futures = tail; self.wait_futures = tail;

@ -1,7 +1,8 @@
use crate::{ use crate::{
commands, commands,
compositor::{Component, Context, Event, EventResult}, compositor::{Component, Context, Event, EventResult},
job, key, job::{self, Callback},
key,
keymap::{KeymapResult, Keymaps}, keymap::{KeymapResult, Keymaps},
ui::{Completion, ProgressSpinners}, ui::{Completion, ProgressSpinners},
}; };
@ -944,9 +945,9 @@ impl EditorView {
// TODO: Use an on_mode_change hook to remove signature help // TODO: Use an on_mode_change hook to remove signature help
cxt.jobs.callback(async { cxt.jobs.callback(async {
let call: job::Callback = Box::new(|_editor, compositor| { let call: job::Callback = Callback::Compositor(Box::new(|compositor| {
compositor.remove(SignatureHelp::ID); compositor.remove(SignatureHelp::ID);
}); }));
Ok(call) Ok(call)
}); });
} }

Loading…
Cancel
Save