Skip to content

Commit cdd9499

Browse files
author
Rose Hogenson
committed
Handle prompt completions with AsyncHook.
This change allows prompt completions to be calculated in the background, to avoid blocking the UI on slow file IO such as over a networked FS.
1 parent 30aa375 commit cdd9499

File tree

3 files changed

+178
-86
lines changed

3 files changed

+178
-86
lines changed

helix-term/src/commands/typed.rs

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use helix_core::{line_ending, shellwords::Shellwords};
1212
use helix_view::document::{read_to_string, DEFAULT_LANGUAGE_NAME};
1313
use helix_view::editor::{CloseError, ConfigEvent};
1414
use serde_json::Value;
15-
use ui::completers::{self, Completer};
15+
use ui::completers::{self, Completer, CompletionResult};
1616

1717
#[derive(Clone)]
1818
pub struct TypableCommand {
@@ -3187,19 +3187,22 @@ pub(super) fn command_mode(cx: &mut Context) {
31873187
.get(&words[0] as &str)
31883188
.map(|tc| tc.completer_for_argument_number(argument_number))
31893189
{
3190-
completer(editor, word)
3191-
.into_iter()
3192-
.map(|(range, file)| {
3193-
let file = shellwords::escape(file);
3194-
3195-
// offset ranges to input
3196-
let offset = input.len() - word_len;
3197-
let range = (range.start + offset)..;
3198-
(range, file)
3199-
})
3200-
.collect()
3190+
let input = String::from(input);
3191+
completer(editor, word).map(move |completion| {
3192+
completion
3193+
.into_iter()
3194+
.map(|(range, file)| {
3195+
let file = shellwords::escape(file);
3196+
3197+
// offset ranges to input
3198+
let offset = input.len() - word_len;
3199+
let range = (range.start + offset)..;
3200+
(range, file)
3201+
})
3202+
.collect()
3203+
})
32013204
} else {
3202-
Vec::new()
3205+
CompletionResult::Immediate(Vec::new())
32033206
}
32043207
}
32053208
}, // completion

helix-term/src/ui/mod.rs

Lines changed: 84 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ mod text_decorations;
1717
use crate::compositor::Compositor;
1818
use crate::filter_picker_entry;
1919
use crate::job::{self, Callback};
20+
use crate::ui::completers::CompletionResult;
2021
pub use completion::{Completion, CompletionItem};
2122
pub use editor::EditorView;
2223
use helix_stdx::rope;
@@ -36,7 +37,7 @@ pub fn prompt(
3637
cx: &mut crate::commands::Context,
3738
prompt: std::borrow::Cow<'static, str>,
3839
history_register: Option<char>,
39-
completion_fn: impl FnMut(&Editor, &str) -> Vec<prompt::Completion> + 'static,
40+
completion_fn: impl FnMut(&Editor, &str) -> CompletionResult + 'static,
4041
callback_fn: impl FnMut(&mut crate::compositor::Context, &str, PromptEvent) + 'static,
4142
) {
4243
let mut prompt = Prompt::new(prompt, history_register, completion_fn, callback_fn);
@@ -45,24 +46,11 @@ pub fn prompt(
4546
cx.push_layer(Box::new(prompt));
4647
}
4748

48-
pub fn prompt_with_input(
49-
cx: &mut crate::commands::Context,
50-
prompt: std::borrow::Cow<'static, str>,
51-
input: String,
52-
history_register: Option<char>,
53-
completion_fn: impl FnMut(&Editor, &str) -> Vec<prompt::Completion> + 'static,
54-
callback_fn: impl FnMut(&mut crate::compositor::Context, &str, PromptEvent) + 'static,
55-
) {
56-
let prompt = Prompt::new(prompt, history_register, completion_fn, callback_fn)
57-
.with_line(input, cx.editor);
58-
cx.push_layer(Box::new(prompt));
59-
}
60-
6149
pub fn regex_prompt(
6250
cx: &mut crate::commands::Context,
6351
prompt: std::borrow::Cow<'static, str>,
6452
history_register: Option<char>,
65-
completion_fn: impl FnMut(&Editor, &str) -> Vec<prompt::Completion> + 'static,
53+
completion_fn: impl FnMut(&Editor, &str) -> CompletionResult + 'static,
6654
fun: impl Fn(&mut crate::compositor::Context, rope::Regex, PromptEvent) + 'static,
6755
) {
6856
raw_regex_prompt(
@@ -77,7 +65,7 @@ pub fn raw_regex_prompt(
7765
cx: &mut crate::commands::Context,
7866
prompt: std::borrow::Cow<'static, str>,
7967
history_register: Option<char>,
80-
completion_fn: impl FnMut(&Editor, &str) -> Vec<prompt::Completion> + 'static,
68+
completion_fn: impl FnMut(&Editor, &str) -> CompletionResult + 'static,
8169
fun: impl Fn(&mut crate::compositor::Context, rope::Regex, &str, PromptEvent) + 'static,
8270
) {
8371
let (view, doc) = current!(cx.editor);
@@ -275,13 +263,40 @@ pub mod completers {
275263
use once_cell::sync::Lazy;
276264
use std::borrow::Cow;
277265

278-
pub type Completer = fn(&Editor, &str) -> Vec<Completion>;
266+
pub enum CompletionResult {
267+
Immediate(Vec<Completion>),
268+
Callback(Box<dyn FnOnce() -> Vec<Completion> + Send + Sync>),
269+
}
270+
271+
fn callback(f: impl FnOnce() -> Vec<Completion> + Send + Sync + 'static) -> CompletionResult {
272+
CompletionResult::Callback(Box::new(f))
273+
}
274+
275+
impl CompletionResult {
276+
pub fn map(
277+
self,
278+
f: impl FnOnce(Vec<Completion>) -> Vec<Completion> + Send + Sync + 'static,
279+
) -> CompletionResult {
280+
match self {
281+
CompletionResult::Immediate(v) => CompletionResult::Immediate(f(v)),
282+
CompletionResult::Callback(v) => callback(move || f(v())),
283+
}
284+
}
285+
}
279286

280-
pub fn none(_editor: &Editor, _input: &str) -> Vec<Completion> {
281-
Vec::new()
287+
impl FromIterator<Completion> for CompletionResult {
288+
fn from_iter<T: IntoIterator<Item = Completion>>(items: T) -> Self {
289+
Self::Immediate(items.into_iter().collect())
290+
}
282291
}
283292

284-
pub fn buffer(editor: &Editor, input: &str) -> Vec<Completion> {
293+
pub type Completer = fn(&Editor, &str) -> CompletionResult;
294+
295+
pub fn none(_editor: &Editor, _input: &str) -> CompletionResult {
296+
CompletionResult::Immediate(Vec::new())
297+
}
298+
299+
pub fn buffer(editor: &Editor, input: &str) -> CompletionResult {
285300
let names = editor.documents.values().map(|doc| {
286301
doc.relative_path()
287302
.map(|p| p.display().to_string().into())
@@ -294,20 +309,23 @@ pub mod completers {
294309
.collect()
295310
}
296311

297-
pub fn theme(_editor: &Editor, input: &str) -> Vec<Completion> {
298-
let mut names = theme::Loader::read_names(&helix_loader::config_dir().join("themes"));
299-
for rt_dir in helix_loader::runtime_dirs() {
300-
names.extend(theme::Loader::read_names(&rt_dir.join("themes")));
301-
}
302-
names.push("default".into());
303-
names.push("base16_default".into());
304-
names.sort();
305-
names.dedup();
312+
pub fn theme(_editor: &Editor, input: &str) -> CompletionResult {
313+
let input = String::from(input);
314+
callback(move || {
315+
let mut names = theme::Loader::read_names(&helix_loader::config_dir().join("themes"));
316+
for rt_dir in helix_loader::runtime_dirs() {
317+
names.extend(theme::Loader::read_names(&rt_dir.join("themes")));
318+
}
319+
names.push("default".into());
320+
names.push("base16_default".into());
321+
names.sort();
322+
names.dedup();
306323

307-
fuzzy_match(input, names, false)
308-
.into_iter()
309-
.map(|(name, _)| ((0..), name.into()))
310-
.collect()
324+
fuzzy_match(&input, names, false)
325+
.into_iter()
326+
.map(|(name, _)| ((0..), name.into()))
327+
.collect()
328+
})
311329
}
312330

313331
/// Recursive function to get all keys from this value and add them to vec
@@ -326,7 +344,7 @@ pub mod completers {
326344
}
327345
}
328346

329-
pub fn setting(_editor: &Editor, input: &str) -> Vec<Completion> {
347+
pub fn setting(_editor: &Editor, input: &str) -> CompletionResult {
330348
static KEYS: Lazy<Vec<String>> = Lazy::new(|| {
331349
let mut keys = Vec::new();
332350
let json = serde_json::json!(Config::default());
@@ -340,27 +358,30 @@ pub mod completers {
340358
.collect()
341359
}
342360

343-
pub fn filename(editor: &Editor, input: &str) -> Vec<Completion> {
361+
pub fn filename(editor: &Editor, input: &str) -> CompletionResult {
344362
filename_with_git_ignore(editor, input, true)
345363
}
346364

347365
pub fn filename_with_git_ignore(
348-
editor: &Editor,
366+
_editor: &Editor,
349367
input: &str,
350368
git_ignore: bool,
351-
) -> Vec<Completion> {
352-
filename_impl(editor, input, git_ignore, |entry| {
353-
let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir());
354-
355-
if is_dir {
356-
FileMatch::AcceptIncomplete
357-
} else {
358-
FileMatch::Accept
359-
}
369+
) -> CompletionResult {
370+
let input = String::from(input);
371+
callback(move || {
372+
filename_impl(&input, git_ignore, |entry| {
373+
let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir());
374+
375+
if is_dir {
376+
FileMatch::AcceptIncomplete
377+
} else {
378+
FileMatch::Accept
379+
}
380+
})
360381
})
361382
}
362383

363-
pub fn language(editor: &Editor, input: &str) -> Vec<Completion> {
384+
pub fn language(editor: &Editor, input: &str) -> CompletionResult {
364385
let text: String = "text".into();
365386

366387
let loader = editor.syn_loader.load();
@@ -375,7 +396,7 @@ pub mod completers {
375396
.collect()
376397
}
377398

378-
pub fn lsp_workspace_command(editor: &Editor, input: &str) -> Vec<Completion> {
399+
pub fn lsp_workspace_command(editor: &Editor, input: &str) -> CompletionResult {
379400
let commands = doc!(editor)
380401
.language_servers_with_feature(LanguageServerFeature::WorkspaceCommand)
381402
.flat_map(|ls| {
@@ -391,23 +412,26 @@ pub mod completers {
391412
.collect()
392413
}
393414

394-
pub fn directory(editor: &Editor, input: &str) -> Vec<Completion> {
415+
pub fn directory(editor: &Editor, input: &str) -> CompletionResult {
395416
directory_with_git_ignore(editor, input, true)
396417
}
397418

398419
pub fn directory_with_git_ignore(
399-
editor: &Editor,
420+
_editor: &Editor,
400421
input: &str,
401422
git_ignore: bool,
402-
) -> Vec<Completion> {
403-
filename_impl(editor, input, git_ignore, |entry| {
404-
let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir());
405-
406-
if is_dir {
407-
FileMatch::Accept
408-
} else {
409-
FileMatch::Reject
410-
}
423+
) -> CompletionResult {
424+
let input = String::from(input);
425+
callback(move || {
426+
filename_impl(&input, git_ignore, |entry| {
427+
let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir());
428+
429+
if is_dir {
430+
FileMatch::Accept
431+
} else {
432+
FileMatch::Reject
433+
}
434+
})
411435
})
412436
}
413437

@@ -423,12 +447,7 @@ pub mod completers {
423447
}
424448

425449
// TODO: we could return an iter/lazy thing so it can fetch as many as it needs.
426-
fn filename_impl<F>(
427-
_editor: &Editor,
428-
input: &str,
429-
git_ignore: bool,
430-
filter_fn: F,
431-
) -> Vec<Completion>
450+
fn filename_impl<F>(input: &str, git_ignore: bool, filter_fn: F) -> Vec<Completion>
432451
where
433452
F: Fn(&ignore::DirEntry) -> FileMatch,
434453
{
@@ -522,7 +541,7 @@ pub mod completers {
522541
}
523542
}
524543

525-
pub fn register(editor: &Editor, input: &str) -> Vec<Completion> {
544+
pub fn register(editor: &Editor, input: &str) -> CompletionResult {
526545
let iter = editor
527546
.registers
528547
.iter_preview()

0 commit comments

Comments
 (0)