on master help sign in
Add some async job infrastructure.
d64d75eJoe Neeman
helix-term/src/application.rs+10−24
@@ -6,6 +6,7 @@ use crate::{
6 args::Args,
6 args::Args,
7 compositor::Compositor,
7 compositor::Compositor,
8 config::Config,
8 config::Config,
9+ job::Jobs,
9 keymap::Keymaps,
10 keymap::Keymaps,
10 ui::{self, Spinner},
11 ui::{self, Spinner},
11};
12};
@@ -31,13 +32,6 @@ use crossterm::{
31
32
32use futures_util::{future, stream::FuturesUnordered};
33use futures_util::{future, stream::FuturesUnordered};
33
34
34−type BoxFuture<T> = Pin<Box<dyn Future<Output = T> + Send>>;
35−pub type LspCallback =
36− BoxFuture<Result<Box<dyn FnOnce(&mut Editor, &mut Compositor) + Send>, anyhow::Error>>;
37−
38−pub type LspCallbacks = FuturesUnordered<LspCallback>;
39−pub type LspCallbackWrapper = Box<dyn FnOnce(&mut Editor, &mut Compositor) + Send>;
40−
41pub struct Application {
35pub struct Application {
42 compositor: Compositor,
36 compositor: Compositor,
43 editor: Editor,
37 editor: Editor,
@@ -48,7 +42,7 @@ pub struct Application {
48 theme_loader: Arc<theme::Loader>,
42 theme_loader: Arc<theme::Loader>,
49 syn_loader: Arc<syntax::Loader>,
43 syn_loader: Arc<syntax::Loader>,
50
44
51− callbacks: LspCallbacks,
45+ jobs: Jobs,
52 lsp_progress: LspProgressMap,
46 lsp_progress: LspProgressMap,
53}
47}
54
48
@@ -120,7 +114,7 @@ impl Application {
120 theme_loader,
114 theme_loader,
121 syn_loader,
115 syn_loader,
122
116
123− callbacks: FuturesUnordered::new(),
117+ jobs: Jobs::new(),
124 lsp_progress: LspProgressMap::new(),
118 lsp_progress: LspProgressMap::new(),
125 };
119 };
126
120
@@ -130,11 +124,11 @@ impl Application {
130 fn render(&mut self) {
124 fn render(&mut self) {
131 let editor = &mut self.editor;
125 let editor = &mut self.editor;
132 let compositor = &mut self.compositor;
126 let compositor = &mut self.compositor;
133− let callbacks = &mut self.callbacks;
127+ let jobs = &mut self.jobs;
134
128
135 let mut cx = crate::compositor::Context {
129 let mut cx = crate::compositor::Context {
136 editor,
130 editor,
137− callbacks,
131+ jobs,
138 scroll: None,
132 scroll: None,
139 };
133 };
140
134
@@ -148,6 +142,7 @@ impl Application {
148
142
149 loop {
143 loop {
150 if self.editor.should_close() {
144 if self.editor.should_close() {
145+ self.jobs.finish();
151 break;
146 break;
152 }
147 }
153
148
@@ -172,27 +167,18 @@ impl Application {
172 }
167 }
173 self.render();
168 self.render();
174 }
169 }
175− Some(callback) = &mut self.callbacks.next() => {
170+ Some(callback) = self.jobs.next() => {
176− self.handle_language_server_callback(callback)
171+ self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback);
172+ self.render();
177 }
173 }
178 }
174 }
179 }
175 }
180 }
176 }
181− pub fn handle_language_server_callback(
182− &mut self,
183− callback: Result<LspCallbackWrapper, anyhow::Error>,
184− ) {
185− if let Ok(callback) = callback {
186− // TODO: handle Err()
187− callback(&mut self.editor, &mut self.compositor);
188− self.render();
189− }
190− }
191
177
192 pub fn handle_terminal_events(&mut self, event: Option<Result<Event, crossterm::ErrorKind>>) {
178 pub fn handle_terminal_events(&mut self, event: Option<Result<Event, crossterm::ErrorKind>>) {
193 let mut cx = crate::compositor::Context {
179 let mut cx = crate::compositor::Context {
194 editor: &mut self.editor,
180 editor: &mut self.editor,
195− callbacks: &mut self.callbacks,
181+ jobs: &mut self.jobs,
196 scroll: None,
182 scroll: None,
197 };
183 };
198 // Handle key events
184 // Handle key events
helix-term/src/commands.rs+18−14
@@ -35,8 +35,8 @@ use crate::{
35 ui::{self, Completion, Picker, Popup, Prompt, PromptEvent},
35 ui::{self, Completion, Picker, Popup, Prompt, PromptEvent},
36};
36};
37
37
38−use crate::application::{LspCallback, LspCallbackWrapper, LspCallbacks};
38+use crate::job::{self, Job, JobFuture, Jobs};
39−use futures_util::FutureExt;
39+use futures_util::{FutureExt, TryFutureExt};
40use std::{fmt, future::Future, path::Display, str::FromStr};
40use std::{fmt, future::Future, path::Display, str::FromStr};
41
41
42use std::{
42use std::{
@@ -54,7 +54,7 @@ pub struct Context<'a> {
54
54
55 pub callback: Option<crate::compositor::Callback>,
55 pub callback: Option<crate::compositor::Callback>,
56 pub on_next_key_callback: Option<Box<dyn FnOnce(&mut Context, KeyEvent)>>,
56 pub on_next_key_callback: Option<Box<dyn FnOnce(&mut Context, KeyEvent)>>,
57− pub callbacks: &'a mut LspCallbacks,
57+ pub jobs: &'a mut Jobs,
58}
58}
59
59
60impl<'a> Context<'a> {
60impl<'a> Context<'a> {
@@ -85,13 +85,13 @@ impl<'a> Context<'a> {
85 let callback = Box::pin(async move {
85 let callback = Box::pin(async move {
86 let json = call.await?;
86 let json = call.await?;
87 let response = serde_json::from_value(json)?;
87 let response = serde_json::from_value(json)?;
88− let call: LspCallbackWrapper =
88+ let call: job::Callback =
89 Box::new(move |editor: &mut Editor, compositor: &mut Compositor| {
89 Box::new(move |editor: &mut Editor, compositor: &mut Compositor| {
90 callback(editor, compositor, response)
90 callback(editor, compositor, response)
91 });
91 });
92 Ok(call)
92 Ok(call)
93 });
93 });
94− self.callbacks.push(callback);
94+ self.jobs.callback(callback);
95 }
95 }
96
96
97 /// Returns 1 if no explicit count was provided
97 /// Returns 1 if no explicit count was provided
@@ -1149,7 +1149,7 @@ mod cmd {
1149 path: Option<P>,
1149 path: Option<P>,
1150 ) -> Result<tokio::task::JoinHandle<Result<(), anyhow::Error>>, anyhow::Error> {
1150 ) -> Result<tokio::task::JoinHandle<Result<(), anyhow::Error>>, anyhow::Error> {
1151 use anyhow::anyhow;
1151 use anyhow::anyhow;
1152− let callbacks = &mut cx.callbacks;
1152+ let jobs = &mut cx.jobs;
1153 let (view, doc) = current!(cx.editor);
1153 let (view, doc) = current!(cx.editor);
1154
1154
1155 if let Some(path) = path {
1155 if let Some(path) = path {
@@ -1168,7 +1168,7 @@ mod cmd {
1168 doc.format().map(|fmt| {
1168 doc.format().map(|fmt| {
1169 let shared = fmt.shared();
1169 let shared = fmt.shared();
1170 let callback = make_format_callback(doc.id(), doc.version(), true, shared.clone());
1170 let callback = make_format_callback(doc.id(), doc.version(), true, shared.clone());
1171− callbacks.push(callback);
1171+ jobs.callback(callback);
1172 shared
1172 shared
1173 })
1173 })
1174 } else {
1174 } else {
@@ -1178,8 +1178,12 @@ mod cmd {
1178 }
1178 }
1179
1179
1180 fn write(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
1180 fn write(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
1181− if let Err(e) = write_impl(cx, args.first()) {
1181+ match write_impl(cx, args.first()) {
1182− cx.editor.set_error(e.to_string());
1182+ Err(e) => cx.editor.set_error(e.to_string()),
1183+ Ok(handle) => {
1184+ cx.jobs
1185+ .add(Job::new(handle.unwrap_or_else(|e| Err(e.into()))).wait_before_exiting());
1186+ }
1183 };
1187 };
1184 }
1188 }
1185
1189
@@ -1192,7 +1196,7 @@ mod cmd {
1192
1196
1193 if let Some(format) = doc.format() {
1197 if let Some(format) = doc.format() {
1194 let callback = make_format_callback(doc.id(), doc.version(), false, format);
1198 let callback = make_format_callback(doc.id(), doc.version(), false, format);
1195− cx.callbacks.push(callback);
1199+ cx.jobs.callback(callback);
1196 }
1200 }
1197 }
1201 }
1198
1202
@@ -1918,10 +1922,10 @@ fn make_format_callback(
1918 doc_version: i32,
1922 doc_version: i32,
1919 set_unmodified: bool,
1923 set_unmodified: bool,
1920 format: impl Future<Output = helix_lsp::util::LspFormatting> + Send + 'static,
1924 format: impl Future<Output = helix_lsp::util::LspFormatting> + Send + 'static,
1921−) -> LspCallback {
1925+) -> impl Future<Output = anyhow::Result<job::Callback>> {
1922− Box::pin(async move {
1926+ async move {
1923 let format = format.await;
1927 let format = format.await;
1924− let call: LspCallbackWrapper =
1928+ let call: job::Callback =
1925 Box::new(move |editor: &mut Editor, compositor: &mut Compositor| {
1929 Box::new(move |editor: &mut Editor, compositor: &mut Compositor| {
1926 let view_id = view!(editor).id;
1930 let view_id = view!(editor).id;
1927 if let Some(doc) = editor.document_mut(doc_id) {
1931 if let Some(doc) = editor.document_mut(doc_id) {
@@ -1937,7 +1941,7 @@ fn make_format_callback(
1937 }
1941 }
1938 });
1942 });
1939 Ok(call)
1943 Ok(call)
1940− })
1944+ }
1941}
1945}
1942
1946
1943enum Open {
1947enum Open {
helix-term/src/compositor.rs+2−2
@@ -26,12 +26,12 @@ pub enum EventResult {
26
26
27use helix_view::Editor;
27use helix_view::Editor;
28
28
29−use crate::application::LspCallbacks;
29+use crate::job::Jobs;
30
30
31pub struct Context<'a> {
31pub struct Context<'a> {
32 pub editor: &'a mut Editor,
32 pub editor: &'a mut Editor,
33 pub scroll: Option<usize>,
33 pub scroll: Option<usize>,
34− pub callbacks: &'a mut LspCallbacks,
34+ pub jobs: &'a mut Jobs,
35}
35}
36
36
37pub trait Component: Any + AnyComponent {
37pub trait Component: Any + AnyComponent {
helix-term/src/job.rsnew file+100
@@ -0,0 +1,100 @@
1+use helix_view::Editor;
2+
3+use crate::compositor::Compositor;
4+
5+use futures_util::future::{self, BoxFuture, Future, FutureExt};
6+use futures_util::stream::{self, FuturesUnordered, Select, StreamExt};
7+
8+pub type Callback = Box<dyn FnOnce(&mut Editor, &mut Compositor) + Send>;
9+pub type JobFuture = BoxFuture<'static, anyhow::Result<Option<Callback>>>;
10+
11+pub struct Job {
12+ pub future: BoxFuture<'static, anyhow::Result<Option<Callback>>>,
13+ /// Do we need to wait for this job to finish before exiting?
14+ pub wait: bool,
15+}
16+
17+#[derive(Default)]
18+pub struct Jobs {
19+ futures: FuturesUnordered<JobFuture>,
20+ /// These are the ones that need to complete before we exit.
21+ wait_futures: FuturesUnordered<JobFuture>,
22+}
23+
24+impl Job {
25+ pub fn new<F: Future<Output = anyhow::Result<()>> + Send + 'static>(f: F) -> Job {
26+ Job {
27+ future: f.map(|r| r.map(|()| None)).boxed(),
28+ wait: false,
29+ }
30+ }
31+
32+ pub fn with_callback<F: Future<Output = anyhow::Result<Callback>> + Send + 'static>(
33+ f: F,
34+ ) -> Job {
35+ Job {
36+ future: f.map(|r| r.map(|x| Some(x))).boxed(),
37+ wait: false,
38+ }
39+ }
40+
41+ pub fn wait_before_exiting(mut self) -> Job {
42+ self.wait = true;
43+ self
44+ }
45+}
46+
47+impl Jobs {
48+ pub fn new() -> Jobs {
49+ Jobs::default()
50+ }
51+
52+ pub fn spawn<F: Future<Output = anyhow::Result<()>> + Send + 'static>(&mut self, f: F) {
53+ self.add(Job::new(f));
54+ }
55+
56+ pub fn callback<F: Future<Output = anyhow::Result<Callback>> + Send + 'static>(
57+ &mut self,
58+ f: F,
59+ ) {
60+ self.add(Job::with_callback(f));
61+ }
62+
63+ pub fn handle_callback(
64+ &mut self,
65+ editor: &mut Editor,
66+ compositor: &mut Compositor,
67+ call: anyhow::Result<Option<Callback>>,
68+ ) {
69+ match call {
70+ Ok(None) => {}
71+ Ok(Some(call)) => {
72+ call(editor, compositor);
73+ }
74+ Err(e) => {
75+ editor.set_error(format!("Async job failed: {}", e));
76+ }
77+ }
78+ }
79+
80+ pub fn next<'a>(
81+ &'a mut self,
82+ ) -> impl Future<Output = Option<anyhow::Result<Option<Callback>>>> + 'a {
83+ future::select(self.futures.next(), self.wait_futures.next())
84+ .map(|either| either.factor_first().0)
85+ }
86+
87+ pub fn add(&mut self, j: Job) {
88+ if j.wait {
89+ self.wait_futures.push(j.future);
90+ } else {
91+ self.futures.push(j.future);
92+ }
93+ }
94+
95+ /// Blocks until all the jobs that need to be waited on are done.
96+ pub fn finish(&mut self) {
97+ let wait_futures = std::mem::take(&mut self.wait_futures);
98+ helix_lsp::block_on(wait_futures.for_each(|_| future::ready(())));
99+ }
100+}
helix-term/src/lib.rs+1
@@ -8,5 +8,6 @@ pub mod args;
8pub mod commands;
8pub mod commands;
9pub mod compositor;
9pub mod compositor;
10pub mod config;
10pub mod config;
11+pub mod job;
11pub mod keymap;
12pub mod keymap;
12pub mod ui;
13pub mod ui;
helix-term/src/ui/editor.rs+2−2
@@ -621,7 +621,7 @@ impl Component for EditorView {
621 count: None,
621 count: None,
622 callback: None,
622 callback: None,
623 on_next_key_callback: None,
623 on_next_key_callback: None,
624− callbacks: cx.callbacks,
624+ jobs: cx.jobs,
625 };
625 };
626
626
627 if let Some(on_next_key) = self.on_next_key.take() {
627 if let Some(on_next_key) = self.on_next_key.take() {
@@ -639,7 +639,7 @@ impl Component for EditorView {
639 // use a fake context here
639 // use a fake context here
640 let mut cx = Context {
640 let mut cx = Context {
641 editor: cxt.editor,
641 editor: cxt.editor,
642− callbacks: cxt.callbacks,
642+ jobs: cxt.jobs,
643 scroll: None,
643 scroll: None,
644 };
644 };
645 let res = completion.handle_event(event, &mut cx);
645 let res = completion.handle_event(event, &mut cx);