diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index aea0d89f..c2ae4651 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -6,6 +6,7 @@ use crate::{ args::Args, compositor::Compositor, config::Config, + job::Jobs, keymap::Keymaps, ui::{self, Spinner}, }; @@ -31,13 +32,6 @@ use crossterm::{ use futures_util::{future, stream::FuturesUnordered}; -type BoxFuture = Pin + Send>>; -pub type LspCallback = - BoxFuture, anyhow::Error>>; - -pub type LspCallbacks = FuturesUnordered; -pub type LspCallbackWrapper = Box; - pub struct Application { compositor: Compositor, editor: Editor, @@ -48,7 +42,7 @@ pub struct Application { theme_loader: Arc, syn_loader: Arc, - callbacks: LspCallbacks, + jobs: Jobs, lsp_progress: LspProgressMap, } @@ -120,7 +114,7 @@ impl Application { theme_loader, syn_loader, - callbacks: FuturesUnordered::new(), + jobs: Jobs::new(), lsp_progress: LspProgressMap::new(), }; @@ -130,11 +124,11 @@ impl Application { fn render(&mut self) { let editor = &mut self.editor; let compositor = &mut self.compositor; - let callbacks = &mut self.callbacks; + let jobs = &mut self.jobs; let mut cx = crate::compositor::Context { editor, - callbacks, + jobs, scroll: None, }; @@ -148,6 +142,7 @@ impl Application { loop { if self.editor.should_close() { + self.jobs.finish(); break; } @@ -172,27 +167,18 @@ impl Application { } self.render(); } - Some(callback) = &mut self.callbacks.next() => { - self.handle_language_server_callback(callback) + Some(callback) = self.jobs.next() => { + self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback); + self.render(); } } } } - pub fn handle_language_server_callback( - &mut self, - callback: Result, - ) { - if let Ok(callback) = callback { - // TODO: handle Err() - callback(&mut self.editor, &mut self.compositor); - self.render(); - } - } pub fn handle_terminal_events(&mut self, event: Option>) { let mut cx = crate::compositor::Context { editor: &mut self.editor, - callbacks: &mut self.callbacks, + jobs: &mut self.jobs, scroll: None, }; // Handle key events diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 710fc2a6..5dd539cc 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -35,8 +35,8 @@ use crate::{ ui::{self, Completion, Picker, Popup, Prompt, PromptEvent}, }; -use crate::application::{LspCallback, LspCallbackWrapper, LspCallbacks}; -use futures_util::FutureExt; +use crate::job::{self, Job, JobFuture, Jobs}; +use futures_util::{FutureExt, TryFutureExt}; use std::{fmt, future::Future, path::Display, str::FromStr}; use std::{ @@ -54,7 +54,7 @@ pub struct Context<'a> { pub callback: Option, pub on_next_key_callback: Option>, - pub callbacks: &'a mut LspCallbacks, + pub jobs: &'a mut Jobs, } impl<'a> Context<'a> { @@ -85,13 +85,13 @@ impl<'a> Context<'a> { let callback = Box::pin(async move { let json = call.await?; let response = serde_json::from_value(json)?; - let call: LspCallbackWrapper = + let call: job::Callback = Box::new(move |editor: &mut Editor, compositor: &mut Compositor| { callback(editor, compositor, response) }); Ok(call) }); - self.callbacks.push(callback); + self.jobs.callback(callback); } /// Returns 1 if no explicit count was provided @@ -1149,7 +1149,7 @@ mod cmd { path: Option

, ) -> Result>, anyhow::Error> { use anyhow::anyhow; - let callbacks = &mut cx.callbacks; + let jobs = &mut cx.jobs; let (view, doc) = current!(cx.editor); if let Some(path) = path { @@ -1168,7 +1168,7 @@ mod cmd { doc.format().map(|fmt| { let shared = fmt.shared(); let callback = make_format_callback(doc.id(), doc.version(), true, shared.clone()); - callbacks.push(callback); + jobs.callback(callback); shared }) } else { @@ -1178,8 +1178,12 @@ mod cmd { } fn write(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) { - if let Err(e) = write_impl(cx, args.first()) { - cx.editor.set_error(e.to_string()); + match write_impl(cx, args.first()) { + Err(e) => cx.editor.set_error(e.to_string()), + Ok(handle) => { + cx.jobs + .add(Job::new(handle.unwrap_or_else(|e| Err(e.into()))).wait_before_exiting()); + } }; } @@ -1192,7 +1196,7 @@ mod cmd { if let Some(format) = doc.format() { let callback = make_format_callback(doc.id(), doc.version(), false, format); - cx.callbacks.push(callback); + cx.jobs.callback(callback); } } @@ -1918,10 +1922,10 @@ fn make_format_callback( doc_version: i32, set_unmodified: bool, format: impl Future + Send + 'static, -) -> LspCallback { - Box::pin(async move { +) -> impl Future> { + async move { let format = format.await; - let call: LspCallbackWrapper = + let call: job::Callback = Box::new(move |editor: &mut Editor, compositor: &mut Compositor| { let view_id = view!(editor).id; if let Some(doc) = editor.document_mut(doc_id) { @@ -1937,7 +1941,7 @@ fn make_format_callback( } }); Ok(call) - }) + } } enum Open { diff --git a/helix-term/src/compositor.rs b/helix-term/src/compositor.rs index c00b95e9..ba8c4bc7 100644 --- a/helix-term/src/compositor.rs +++ b/helix-term/src/compositor.rs @@ -26,12 +26,12 @@ pub enum EventResult { use helix_view::Editor; -use crate::application::LspCallbacks; +use crate::job::Jobs; pub struct Context<'a> { pub editor: &'a mut Editor, pub scroll: Option, - pub callbacks: &'a mut LspCallbacks, + pub jobs: &'a mut Jobs, } pub trait Component: Any + AnyComponent { diff --git a/helix-term/src/job.rs b/helix-term/src/job.rs new file mode 100644 index 00000000..fcecfbce --- /dev/null +++ b/helix-term/src/job.rs @@ -0,0 +1,100 @@ +use helix_view::Editor; + +use crate::compositor::Compositor; + +use futures_util::future::{self, BoxFuture, Future, FutureExt}; +use futures_util::stream::{self, FuturesUnordered, Select, StreamExt}; + +pub type Callback = Box; +pub type JobFuture = BoxFuture<'static, anyhow::Result>>; + +pub struct Job { + pub future: BoxFuture<'static, anyhow::Result>>, + /// Do we need to wait for this job to finish before exiting? + pub wait: bool, +} + +#[derive(Default)] +pub struct Jobs { + futures: FuturesUnordered, + /// These are the ones that need to complete before we exit. + wait_futures: FuturesUnordered, +} + +impl Job { + pub fn new> + Send + 'static>(f: F) -> Job { + Job { + future: f.map(|r| r.map(|()| None)).boxed(), + wait: false, + } + } + + pub fn with_callback> + Send + 'static>( + f: F, + ) -> Job { + Job { + future: f.map(|r| r.map(|x| Some(x))).boxed(), + wait: false, + } + } + + pub fn wait_before_exiting(mut self) -> Job { + self.wait = true; + self + } +} + +impl Jobs { + pub fn new() -> Jobs { + Jobs::default() + } + + pub fn spawn> + Send + 'static>(&mut self, f: F) { + self.add(Job::new(f)); + } + + pub fn callback> + Send + 'static>( + &mut self, + f: F, + ) { + self.add(Job::with_callback(f)); + } + + pub fn handle_callback( + &mut self, + editor: &mut Editor, + compositor: &mut Compositor, + call: anyhow::Result>, + ) { + match call { + Ok(None) => {} + Ok(Some(call)) => { + call(editor, compositor); + } + Err(e) => { + editor.set_error(format!("Async job failed: {}", e)); + } + } + } + + pub fn next<'a>( + &'a mut self, + ) -> impl Future>>> + 'a { + future::select(self.futures.next(), self.wait_futures.next()) + .map(|either| either.factor_first().0) + } + + pub fn add(&mut self, j: Job) { + if j.wait { + self.wait_futures.push(j.future); + } else { + self.futures.push(j.future); + } + } + + /// Blocks until all the jobs that need to be waited on are done. + pub fn finish(&mut self) { + let wait_futures = std::mem::take(&mut self.wait_futures); + helix_lsp::block_on(wait_futures.for_each(|_| future::ready(()))); + } +} diff --git a/helix-term/src/lib.rs b/helix-term/src/lib.rs index dc8ec38e..3f288188 100644 --- a/helix-term/src/lib.rs +++ b/helix-term/src/lib.rs @@ -8,5 +8,6 @@ pub mod args; pub mod commands; pub mod compositor; pub mod config; +pub mod job; pub mod keymap; pub mod ui; diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 7d97c2ba..b55a830e 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -621,7 +621,7 @@ impl Component for EditorView { count: None, callback: None, on_next_key_callback: None, - callbacks: cx.callbacks, + jobs: cx.jobs, }; if let Some(on_next_key) = self.on_next_key.take() { @@ -639,7 +639,7 @@ impl Component for EditorView { // use a fake context here let mut cx = Context { editor: cxt.editor, - callbacks: cxt.callbacks, + jobs: cxt.jobs, scroll: None, }; let res = completion.handle_event(event, &mut cx);