Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-term/src/ui/completion.rs')
| -rw-r--r-- | helix-term/src/ui/completion.rs | 115 |
1 files changed, 18 insertions, 97 deletions
diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index 2b3437e1..7a08c90c 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -1,9 +1,7 @@ use crate::{ compositor::{Component, Context, Event, EventResult}, - handlers::trigger_auto_completion, - job, + handlers::{completion::ResolveHandler, trigger_auto_completion}, }; -use helix_event::AsyncHook; use helix_view::{ document::SavePoint, editor::CompleteAction, @@ -12,17 +10,16 @@ use helix_view::{ theme::{Modifier, Style}, ViewId, }; -use tokio::time::Instant; use tui::{buffer::Buffer as Surface, text::Span}; -use std::{borrow::Cow, sync::Arc, time::Duration}; +use std::{borrow::Cow, sync::Arc}; use helix_core::{chars, Change, Transaction}; use helix_view::{graphics::Rect, Document, Editor}; use crate::ui::{menu, Markdown, Menu, Popup, PromptEvent}; -use helix_lsp::{lsp, util, OffsetEncoding}; +use helix_lsp::{lsp, util, LanguageServerId, OffsetEncoding}; impl menu::Item for CompletionItem { type Data = (); @@ -104,7 +101,7 @@ pub struct Completion { #[allow(dead_code)] trigger_offset: usize, filter: String, - resolve_handler: tokio::sync::mpsc::Sender<CompletionItem>, + resolve_handler: ResolveHandler, } impl Completion { @@ -365,7 +362,7 @@ impl Completion { // TODO: expand nucleo api to allow moving straight to a Utf32String here // and avoid allocation during matching filter: String::from(fragment), - resolve_handler: ResolveHandler::default().spawn(), + resolve_handler: ResolveHandler::new(), }; // need to recompute immediately in case start_offset != trigger_offset @@ -383,7 +380,16 @@ impl Completion { language_server: &helix_lsp::Client, completion_item: lsp::CompletionItem, ) -> Option<lsp::CompletionItem> { - let future = language_server.resolve_completion_item(completion_item)?; + if !matches!( + language_server.capabilities().completion_provider, + Some(lsp::CompletionOptions { + resolve_provider: Some(true), + .. + }) + ) { + return None; + } + let future = language_server.resolve_completion_item(&completion_item); let response = helix_lsp::block_on(future); match response { Ok(item) => Some(item), @@ -416,7 +422,7 @@ impl Completion { self.popup.contents().is_empty() } - fn replace_item(&mut self, old_item: CompletionItem, new_item: CompletionItem) { + pub fn replace_item(&mut self, old_item: &CompletionItem, new_item: CompletionItem) { self.popup.contents_mut().replace_option(old_item, new_item); } @@ -438,12 +444,12 @@ impl Component for Completion { self.popup.render(area, surface, cx); // if we have a selection, render a markdown popup on top/below with info - let option = match self.popup.contents().selection() { + let option = match self.popup.contents_mut().selection_mut() { Some(option) => option, None => return, }; if !option.resolved { - helix_event::send_blocking(&self.resolve_handler, option.clone()); + self.resolve_handler.ensure_item_resolved(cx.editor, option); } // need to render: // option.detail @@ -541,88 +547,3 @@ impl Component for Completion { markdown_doc.render(doc_area, surface, cx); } } - -/// A hook for resolving incomplete completion items. -/// -/// From the [LSP spec](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_completion): -/// -/// > If computing full completion items is expensive, servers can additionally provide a -/// > handler for the completion item resolve request. ... -/// > A typical use case is for example: the `textDocument/completion` request doesn't fill -/// > in the `documentation` property for returned completion items since it is expensive -/// > to compute. When the item is selected in the user interface then a -/// > 'completionItem/resolve' request is sent with the selected completion item as a parameter. -/// > The returned completion item should have the documentation property filled in. -#[derive(Debug, Default)] -struct ResolveHandler { - trigger: Option<CompletionItem>, - request: Option<helix_event::CancelTx>, -} - -impl AsyncHook for ResolveHandler { - type Event = CompletionItem; - - fn handle_event( - &mut self, - item: Self::Event, - timeout: Option<tokio::time::Instant>, - ) -> Option<tokio::time::Instant> { - if self - .trigger - .as_ref() - .is_some_and(|trigger| trigger == &item) - { - timeout - } else { - self.trigger = Some(item); - self.request = None; - Some(Instant::now() + Duration::from_millis(150)) - } - } - - fn finish_debounce(&mut self) { - let Some(item) = self.trigger.take() else { return }; - let (tx, rx) = helix_event::cancelation(); - self.request = Some(tx); - job::dispatch_blocking(move |editor, _| resolve_completion_item(editor, item, rx)) - } -} - -fn resolve_completion_item( - editor: &mut Editor, - item: CompletionItem, - cancel: helix_event::CancelRx, -) { - let Some(language_server) = editor.language_server_by_id(item.language_server_id) else { - return; - }; - - let Some(future) = language_server.resolve_completion_item(item.item.clone()) else { - return; - }; - - tokio::spawn(async move { - match helix_event::cancelable_future(future, cancel).await { - Some(Ok(resolved_item)) => { - job::dispatch(move |_, compositor| { - if let Some(completion) = &mut compositor - .find::<crate::ui::EditorView>() - .unwrap() - .completion - { - let resolved_item = CompletionItem { - item: resolved_item, - language_server_id: item.language_server_id, - resolved: true, - }; - - completion.replace_item(item, resolved_item); - }; - }) - .await - } - Some(Err(err)) => log::error!("completion resolve request failed: {err}"), - None => (), - } - }); -} |