diff options
author | Blaž Hrastnik | 2021-10-10 13:11:01 +0000 |
---|---|---|
committer | GitHub | 2021-10-10 13:11:01 +0000 |
commit | f8f63c55081ee966c7cb2b84139c25c6301b5fff (patch) | |
tree | c4c74c5c013d8c26f6b9b124821fabd9200c3218 /helix-term/src | |
parent | a7f49fa56fecd7f44efca7e6074e5cd9e5d91c92 (diff) | |
parent | 76b1bbc23ad5fc47765472cd9e83727a43c97ff3 (diff) |
Merge pull request #821 from helix-editor/idle-timer
Idle timer / Autocompletion
Diffstat (limited to 'helix-term/src')
-rw-r--r-- | helix-term/src/application.rs | 37 | ||||
-rw-r--r-- | helix-term/src/commands.rs | 28 | ||||
-rw-r--r-- | helix-term/src/ui/completion.rs | 26 | ||||
-rw-r--r-- | helix-term/src/ui/editor.rs | 16 |
4 files changed, 92 insertions, 15 deletions
diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 6206e6f2..c39a9173 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -199,6 +199,11 @@ impl Application { self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback); self.render(); } + _ = &mut self.editor.idle_timer => { + // idle timeout + self.editor.clear_idle_timer(); + self.handle_idle_timeout(); + } } } } @@ -228,6 +233,38 @@ impl Application { } } + pub fn handle_idle_timeout(&mut self) { + use crate::commands::{completion, Context}; + use helix_view::document::Mode; + + if doc_mut!(self.editor).mode != Mode::Insert { + return; + } + let editor_view = self + .compositor + .find(std::any::type_name::<ui::EditorView>()) + .expect("expected at least one EditorView"); + let editor_view = editor_view + .as_any_mut() + .downcast_mut::<ui::EditorView>() + .unwrap(); + + if editor_view.completion.is_some() { + return; + } + + let mut cx = Context { + register: None, + editor: &mut self.editor, + jobs: &mut self.jobs, + count: None, + callback: None, + on_next_key_callback: None, + }; + completion(&mut cx); + self.render(); + } + pub fn handle_terminal_events(&mut self, event: Option<Result<Event, crossterm::ErrorKind>>) { let mut cx = crate::compositor::Context { editor: &mut self.editor, diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index c1fd7bfe..95c46a4e 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -4051,7 +4051,7 @@ fn remove_primary_selection(cx: &mut Context) { doc.set_selection(view.id, selection); } -fn completion(cx: &mut Context) { +pub fn completion(cx: &mut Context) { // trigger on trigger char, or if user calls it // (or on word char typing??) // after it's triggered, if response marked is_incomplete, update on every subsequent keypress @@ -4096,10 +4096,8 @@ fn completion(cx: &mut Context) { }; let offset_encoding = language_server.offset_encoding(); - let cursor = doc - .selection(view.id) - .primary() - .cursor(doc.text().slice(..)); + let text = doc.text().slice(..); + let cursor = doc.selection(view.id).primary().cursor(text); let pos = pos_to_lsp_pos(doc.text(), cursor, offset_encoding); @@ -4107,6 +4105,15 @@ fn completion(cx: &mut Context) { let trigger_offset = cursor; + // TODO: trigger_offset should be the cursor offset but we also need a starting offset from where we want to apply + // completion filtering. For example logger.te| should filter the initial suggestion list with "te". + + use helix_core::chars; + let mut iter = text.chars_at(cursor); + iter.reverse(); + let offset = iter.take_while(|ch| chars::char_is_word(*ch)).count(); + let start_offset = cursor.saturating_sub(offset); + cx.callback( future, move |editor: &mut Editor, @@ -4129,7 +4136,7 @@ fn completion(cx: &mut Context) { }; if items.is_empty() { - editor.set_error("No completion available".to_string()); + // editor.set_error("No completion available".to_string()); return; } let size = compositor.size(); @@ -4137,7 +4144,14 @@ fn completion(cx: &mut Context) { .find(std::any::type_name::<ui::EditorView>()) .unwrap(); if let Some(ui) = ui.as_any_mut().downcast_mut::<ui::EditorView>() { - ui.set_completion(items, offset_encoding, trigger_offset, size); + ui.set_completion( + editor, + items, + offset_encoding, + start_offset, + trigger_offset, + size, + ); }; }, ); diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index 6c9e3a80..ba009c50 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -69,14 +69,18 @@ impl menu::Item for CompletionItem { /// Wraps a Menu. pub struct Completion { popup: Popup<Menu<CompletionItem>>, + start_offset: usize, + #[allow(dead_code)] trigger_offset: usize, // TODO: maintain a completioncontext with trigger kind & trigger char } impl Completion { pub fn new( + editor: &Editor, items: Vec<CompletionItem>, offset_encoding: helix_lsp::OffsetEncoding, + start_offset: usize, trigger_offset: usize, ) -> Self { // let items: Vec<CompletionItem> = Vec::new(); @@ -175,16 +179,22 @@ impl Completion { }; }); let popup = Popup::new(menu); - Self { + let mut completion = Self { popup, + start_offset, trigger_offset, - } + }; + + // need to recompute immediately in case start_offset != trigger_offset + completion.recompute_filter(editor); + + completion } - pub fn update(&mut self, cx: &mut commands::Context) { + pub fn recompute_filter(&mut self, editor: &Editor) { // recompute menu based on matches let menu = self.popup.contents_mut(); - let (view, doc) = current!(cx.editor); + let (view, doc) = current_ref!(editor); // cx.hooks() // cx.add_hook(enum type, ||) @@ -200,14 +210,18 @@ impl Completion { .selection(view.id) .primary() .cursor(doc.text().slice(..)); - if self.trigger_offset <= cursor { - let fragment = doc.text().slice(self.trigger_offset..cursor); + if self.start_offset <= cursor { + let fragment = doc.text().slice(self.start_offset..cursor); let text = Cow::from(fragment); // TODO: logic is same as ui/picker menu.score(&text); } } + pub fn update(&mut self, cx: &mut commands::Context) { + self.recompute_filter(cx.editor) + } + pub fn is_empty(&self) -> bool { self.popup.contents().is_empty() } diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 0605e2c7..9234bb96 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -33,7 +33,7 @@ pub struct EditorView { keymaps: Keymaps, on_next_key: Option<Box<dyn FnOnce(&mut commands::Context, KeyEvent)>>, last_insert: (commands::Command, Vec<KeyEvent>), - completion: Option<Completion>, + pub(crate) completion: Option<Completion>, spinners: ProgressSpinners, autoinfo: Option<Info>, } @@ -721,12 +721,21 @@ impl EditorView { pub fn set_completion( &mut self, + editor: &Editor, items: Vec<helix_lsp::lsp::CompletionItem>, offset_encoding: helix_lsp::OffsetEncoding, + start_offset: usize, trigger_offset: usize, size: Rect, ) { - let mut completion = Completion::new(items, offset_encoding, trigger_offset); + let mut completion = + Completion::new(editor, items, offset_encoding, start_offset, trigger_offset); + + if completion.is_empty() { + // skip if we got no completion results + return; + } + // TODO : propagate required size on resize to completion too completion.required_size((size.width, size.height)); self.completion = Some(completion); @@ -901,6 +910,7 @@ impl Component for EditorView { EventResult::Consumed(None) } Event::Key(key) => { + cxt.editor.reset_idle_timer(); let mut key = KeyEvent::from(key); canonicalize_key(&mut key); // clear status @@ -935,6 +945,7 @@ impl Component for EditorView { if callback.is_some() { // assume close_fn self.completion = None; + cxt.editor.clear_idle_timer(); // don't retrigger } } } @@ -948,6 +959,7 @@ impl Component for EditorView { completion.update(&mut cxt); if completion.is_empty() { self.completion = None; + cxt.editor.clear_idle_timer(); // don't retrigger } } } |