on master help sign in
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.

commitd7d0d5ffb79b6f2e09c6ab8af6e112c41e6f73e8 authorMichael Davis <mcarsondavis@gmail.com> dateFri 21 Oct 2022, 20:04 (4 years ago) parent17daf6a
3 files changed+38−4
helix-term/src/ui/completion.rs+21 helix-term/src/ui/editor.rs+9−4 helix-term/src/ui/menu.rs+8
helix-term/src/ui/completion.rs+21
@@ -295,6 +295,27 @@ impl Completion {
295295 pub fn is_empty(&self) -> bool {
296296 self.popup.contents().is_empty()
297297 }
298+
299+ pub fn ensure_item_resolved(&mut self, cx: &mut commands::Context) -> bool {
300+ // > If computing full completion items is expensive, servers can additionally provide a
301+ // > handler for the completion item resolve request. ...
302+ // > A typical use case is for example: the `textDocument/completion` request doesn't fill
303+ // > in the `documentation` property for returned completion items since it is expensive
304+ // > to compute. When the item is selected in the user interface then a
305+ // > 'completionItem/resolve' request is sent with the selected completion item as a parameter.
306+ // > The returned completion item should have the documentation property filled in.
307+ // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_completion
308+ match self.popup.contents_mut().selection_mut() {
309+ Some(item) if item.documentation.is_none() => {
310+ let doc = doc!(cx.editor);
311+ if let Some(resolved_item) = Self::resolve_completion_item(doc, item.clone()) {
312+ *item = resolved_item;
313+ }
314+ true
315+ }
316+ _ => false,
317+ }
318+ }
298319}
299320
300321impl Component for Completion {
helix-term/src/ui/editor.rs+9−4
@@ -1097,10 +1097,15 @@ impl EditorView {
10971097 }
10981098
10991099 pub fn handle_idle_timeout(&mut self, cx: &mut commands::Context) -> EventResult {
1100− if self.completion.is_some()
1101− || cx.editor.mode != Mode::Insert
1102− || !cx.editor.config().auto_completion
1103− {
1100+ if let Some(completion) = &mut self.completion {
1101+ return if completion.ensure_item_resolved(cx) {
1102+ EventResult::Consumed(None)
1103+ } else {
1104+ EventResult::Ignored(None)
1105+ };
1106+ }
1107+
1108+ if cx.editor.mode != Mode::Insert || !cx.editor.config().auto_completion {
11041109 return EventResult::Ignored(None);
11051110 }
11061111
helix-term/src/ui/menu.rs+8
@@ -206,6 +206,14 @@ impl<T: Item> Menu<T> {
206206 })
207207 }
208208
209+ pub fn selection_mut(&mut self) -> Option<&mut T> {
210+ self.cursor.and_then(|cursor| {
211+ self.matches
212+ .get(cursor)
213+ .map(|(index, _score)| &mut self.options[*index])
214+ })
215+ }
216+
209217 pub fn is_empty(&self) -> bool {
210218 self.matches.is_empty()
211219 }