diff options
author | Pascal Kuthe | 2023-11-30 23:03:27 +0000 |
---|---|---|
committer | Blaž Hrastnik | 2024-01-23 02:20:19 +0000 |
commit | 8e592a151fe7adfbf3fb35ae134b7f2a70700f09 (patch) | |
tree | 603a94042068620e52f50cb26cf881d5461d1c8d /helix-term/src/ui/completion.rs | |
parent | 13ed4f6c4748019787d24c2b686d417b71604242 (diff) |
refactor completion and signature help using hooks
Diffstat (limited to 'helix-term/src/ui/completion.rs')
-rw-r--r-- | helix-term/src/ui/completion.rs | 87 |
1 files changed, 49 insertions, 38 deletions
diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index 7c6a0055..48d97fbd 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -1,8 +1,12 @@ -use crate::compositor::{Component, Context, Event, EventResult}; +use crate::{ + compositor::{Component, Context, Event, EventResult}, + handlers::trigger_auto_completion, +}; use helix_view::{ document::SavePoint, editor::CompleteAction, graphics::Margin, + handlers::lsp::SignatureHelpInvoked, theme::{Modifier, Style}, ViewId, }; @@ -10,7 +14,7 @@ use tui::{buffer::Buffer as Surface, text::Span}; use std::{borrow::Cow, sync::Arc}; -use helix_core::{Change, Transaction}; +use helix_core::{chars, Change, Transaction}; use helix_view::{graphics::Rect, Document, Editor}; use crate::commands; @@ -95,10 +99,9 @@ pub struct 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 + filter: String, } impl Completion { @@ -108,7 +111,6 @@ impl Completion { editor: &Editor, savepoint: Arc<SavePoint>, mut items: Vec<CompletionItem>, - start_offset: usize, trigger_offset: usize, ) -> Self { let preview_completion_insert = editor.config().preview_completion_insert; @@ -246,7 +248,7 @@ impl Completion { // (also without sending the transaction to the LS) *before any further transaction is applied*. // Otherwise incremental sync breaks (since the state of the LS doesn't match the state the transaction // is applied to). - if editor.last_completion.is_none() { + if matches!(editor.last_completion, Some(CompleteAction::Triggered)) { editor.last_completion = Some(CompleteAction::Selected { savepoint: doc.savepoint(view), }) @@ -324,8 +326,18 @@ impl Completion { doc.apply(&transaction, view.id); } } + // we could have just inserted a trigger char (like a `crate::` completion for rust + // so we want to retrigger immediately when accepting a completion. + trigger_auto_completion(&editor.handlers.completions, editor, true); } }; + + // In case the popup was deleted because of an intersection w/ the auto-complete menu. + if event != PromptEvent::Update { + editor + .handlers + .trigger_signature_help(SignatureHelpInvoked::Automatic, editor); + } }); let margin = if editor.menu_border() { @@ -339,14 +351,30 @@ impl Completion { .ignore_escape_key(true) .margin(margin); + let (view, doc) = current_ref!(editor); + let text = doc.text().slice(..); + let cursor = doc.selection(view.id).primary().cursor(text); + let offset = text + .chars_at(cursor) + .reversed() + .take_while(|ch| chars::char_is_word(*ch)) + .count(); + let start_offset = cursor.saturating_sub(offset); + + let fragment = doc.text().slice(start_offset..cursor); let mut completion = Self { popup, - start_offset, trigger_offset, + // TODO: expand nucleo api to allow moving straight to a Utf32String here + // and avoid allocation during matching + filter: String::from(fragment), }; // need to recompute immediately in case start_offset != trigger_offset - completion.recompute_filter(editor); + completion + .popup + .contents_mut() + .score(&completion.filter, false); completion } @@ -366,39 +394,22 @@ impl Completion { } } - pub fn recompute_filter(&mut self, editor: &Editor) { + /// Appends (`c: Some(c)`) or removes (`c: None`) a character to/from the filter + /// this should be called whenever the user types or deletes a character in insert mode. + pub fn update_filter(&mut self, c: Option<char>) { // recompute menu based on matches let menu = self.popup.contents_mut(); - let (view, doc) = current_ref!(editor); - - // cx.hooks() - // cx.add_hook(enum type, ||) - // cx.trigger_hook(enum type, &str, ...) <-- there has to be enough to identify doc/view - // callback with editor & compositor - // - // trigger_hook sends event into channel, that's consumed in the global loop and - // triggers all registered callbacks - // TODO: hooks should get processed immediately so maybe do it after select!(), before - // looping? - - let cursor = doc - .selection(view.id) - .primary() - .cursor(doc.text().slice(..)); - if self.trigger_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); - } else { - // we backspaced before the start offset, clear the menu - // this will cause the editor to remove the completion popup - menu.clear(); + match c { + Some(c) => self.filter.push(c), + None => { + self.filter.pop(); + if self.filter.is_empty() { + menu.clear(); + return; + } + } } - } - - pub fn update(&mut self, cx: &mut commands::Context) { - self.recompute_filter(cx.editor) + menu.score(&self.filter, c.is_some()); } pub fn is_empty(&self) -> bool { |