Skip to content

Commit 5ff7725

Browse files
committed
jobs: allow callbacks to return followup jobs
This introduces `Callback::Followup` that can optionally return another job to be queued after the callback finishes. This allows creation of async operation chains where the editor state needs to be queried in between async operations, without needing blocking operations. Needed for features like running multiple LSP code actions in order, where each action must operate on the latest document state after the previous action completes.
1 parent 583dba4 commit 5ff7725

File tree

2 files changed

+30
-9
lines changed

2 files changed

+30
-9
lines changed

helix-term/src/application.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,9 @@ impl Application {
339339
self.handle_terminal_events(event).await;
340340
}
341341
Some(callback) = self.jobs.callbacks.recv() => {
342-
self.jobs.handle_callback(&mut self.editor, &mut self.compositor, Ok(Some(callback)));
342+
if let Some(job) = self.jobs.handle_callback(&mut self.editor, &mut self.compositor, Ok(Some(callback))) {
343+
self.jobs.add(job);
344+
}
343345
self.render().await;
344346
}
345347
Some(msg) = self.jobs.status_messages.recv() => {

helix-term/src/job.rs

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use tokio::sync::mpsc::{channel, Receiver, Sender};
1111

1212
pub type EditorCompositorCallback = Box<dyn FnOnce(&mut Editor, &mut Compositor) + Send>;
1313
pub type EditorCallback = Box<dyn FnOnce(&mut Editor) + Send>;
14+
pub type EditorCallbackFollowup = Box<dyn FnOnce(&mut Editor) -> Option<Job> + Send>;
1415

1516
runtime_local! {
1617
static JOB_QUEUE: OnceCell<Sender<Callback>> = OnceCell::new();
@@ -35,6 +36,7 @@ pub fn dispatch_blocking(job: impl FnOnce(&mut Editor, &mut Compositor) + Send +
3536
pub enum Callback {
3637
EditorCompositor(EditorCompositorCallback),
3738
Editor(EditorCallback),
39+
Followup(EditorCallbackFollowup),
3840
}
3941

4042
pub type JobFuture = BoxFuture<'static, anyhow::Result<Option<Callback>>>;
@@ -104,15 +106,23 @@ impl Jobs {
104106
editor: &mut Editor,
105107
compositor: &mut Compositor,
106108
call: anyhow::Result<Option<Callback>>,
107-
) {
109+
) -> Option<Job> {
108110
match call {
109-
Ok(None) => {}
111+
Ok(None) => None,
110112
Ok(Some(call)) => match call {
111-
Callback::EditorCompositor(call) => call(editor, compositor),
112-
Callback::Editor(call) => call(editor),
113+
Callback::EditorCompositor(call) => {
114+
call(editor, compositor);
115+
None
116+
}
117+
Callback::Editor(call) => {
118+
call(editor);
119+
None
120+
}
121+
Callback::Followup(call) => call(editor),
113122
},
114123
Err(e) => {
115124
editor.set_error(format!("Async job failed: {}", e));
125+
None
116126
}
117127
}
118128
}
@@ -148,14 +158,23 @@ impl Jobs {
148158
if let Some(callback) = callback {
149159
// clippy doesn't realize this is an error without the derefs
150160
#[allow(clippy::needless_option_as_deref)]
151-
match callback {
161+
if let Some(job) = match callback {
152162
Callback::EditorCompositor(call) if compositor.is_some() => {
153-
call(editor, compositor.as_deref_mut().unwrap())
163+
call(editor, compositor.as_deref_mut().unwrap());
164+
None
154165
}
155-
Callback::Editor(call) => call(editor),
166+
Callback::Editor(call) => {
167+
call(editor);
168+
None
169+
}
170+
Callback::Followup(call) => call(editor),
156171

157172
// skip callbacks for which we don't have the necessary references
158-
_ => (),
173+
_ => None,
174+
} {
175+
if job.wait {
176+
self.wait_futures.push(job.future);
177+
}
159178
}
160179
}
161180
}

0 commit comments

Comments
 (0)