diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs index 50c4b87f2..424cbabfa 100644 --- a/helix-lsp/src/client.rs +++ b/helix-lsp/src/client.rs @@ -650,12 +650,11 @@ impl Client { self.call::(params) } - pub async fn resolve_completion_item( + pub fn resolve_completion_item( &self, completion_item: lsp::CompletionItem, - ) -> Result { - self.request::(completion_item) - .await + ) -> impl Future> { + self.call::(completion_item) } pub fn text_document_signature_help( diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index 229dcda18..4e6ee4246 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -257,7 +257,7 @@ impl Completion { let future = language_server.resolve_completion_item(completion_item); let response = helix_lsp::block_on(future); match response { - Ok(completion_item) => Some(completion_item), + Ok(value) => serde_json::from_value(value).ok(), Err(err) => { log::error!("execute LSP command: {}", err); None @@ -304,6 +304,12 @@ impl Completion { self.popup.contents().is_empty() } + fn replace_item(&mut self, old_item: lsp::CompletionItem, new_item: lsp::CompletionItem) { + self.popup.contents_mut().replace_option(old_item, new_item); + } + + /// Asynchronously requests that the currently selection completion item is + /// resolved through LSP `completionItem/resolve`. pub fn ensure_item_resolved(&mut self, cx: &mut commands::Context) -> bool { // > If computing full completion items is expensive, servers can additionally provide a // > handler for the completion item resolve request. ... @@ -313,16 +319,38 @@ impl Completion { // > 'completionItem/resolve' request is sent with the selected completion item as a parameter. // > The returned completion item should have the documentation property filled in. // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_completion - match self.popup.contents_mut().selection_mut() { - Some(item) if item.documentation.is_none() => { - let doc = doc!(cx.editor); - if let Some(resolved_item) = Self::resolve_completion_item(doc, item.clone()) { - *item = resolved_item; + let current_item = match self.popup.contents().selection() { + Some(item) if item.documentation.is_none() => item.clone(), + _ => return false, + }; + + let language_server = match doc!(cx.editor).language_server() { + Some(language_server) => language_server, + None => return false, + }; + + // This method should not block the compositor so we handle the response asynchronously. + let future = language_server.resolve_completion_item(current_item.clone()); + + cx.callback( + future, + move |_editor, compositor, response: Option| { + let resolved_item = match response { + Some(item) => item, + None => return, + }; + + if let Some(completion) = &mut compositor + .find::() + .unwrap() + .completion + { + completion.replace_item(current_item, resolved_item); } - true - } - _ => false, - } + }, + ); + + true } } diff --git a/helix-term/src/ui/menu.rs b/helix-term/src/ui/menu.rs index 961b7451a..b9c1f9ded 100644 --- a/helix-term/src/ui/menu.rs +++ b/helix-term/src/ui/menu.rs @@ -224,6 +224,17 @@ impl Menu { } } +impl Menu { + pub fn replace_option(&mut self, old_option: T, new_option: T) { + for option in &mut self.options { + if old_option == *option { + *option = new_option; + break; + } + } + } +} + use super::PromptEvent as MenuEvent; impl Component for Menu {