From d7d0d5ffb79b6f2e09c6ab8af6e112c41e6f73e8 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Fri, 21 Oct 2022 20:04:50 -0500 Subject: [PATCH] lsp: Resolve completion items missing documentation on idle (#4406) Some language servers may not send the `documentation` field if it is expensive to compute. Clients can request the missing field with a completionItem/resolve request. In this change we use the idle-timeout event to ensure that the current completion item is resolved. --- helix-term/src/ui/completion.rs | 21 +++++++++++++++++++++ helix-term/src/ui/editor.rs | 13 +++++++++---- helix-term/src/ui/menu.rs | 8 ++++++++ 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index 7348dcf44..a21767f94 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -295,6 +295,27 @@ impl Completion { pub fn is_empty(&self) -> bool { self.popup.contents().is_empty() } + + 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. ... + // > 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. + // 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; + } + true + } + _ => false, + } + } } impl Component for Completion { diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 43b1e7a07..b1bb02c75 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -1097,10 +1097,15 @@ impl EditorView { } pub fn handle_idle_timeout(&mut self, cx: &mut commands::Context) -> EventResult { - if self.completion.is_some() - || cx.editor.mode != Mode::Insert - || !cx.editor.config().auto_completion - { + if let Some(completion) = &mut self.completion { + return if completion.ensure_item_resolved(cx) { + EventResult::Consumed(None) + } else { + EventResult::Ignored(None) + }; + } + + if cx.editor.mode != Mode::Insert || !cx.editor.config().auto_completion { return EventResult::Ignored(None); } diff --git a/helix-term/src/ui/menu.rs b/helix-term/src/ui/menu.rs index 4b1155e3e..75769b905 100644 --- a/helix-term/src/ui/menu.rs +++ b/helix-term/src/ui/menu.rs @@ -206,6 +206,14 @@ impl Menu { }) } + pub fn selection_mut(&mut self) -> Option<&mut T> { + self.cursor.and_then(|cursor| { + self.matches + .get(cursor) + .map(|(index, _score)| &mut self.options[*index]) + }) + } + pub fn is_empty(&self) -> bool { self.matches.is_empty() }