Skip to content

Commit 3a7ac80

Browse files
the-mikedavisShekhinah Memmel
authored andcommitted
lsp: Resolve completion items missing documentation on idle (helix-editor#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.
1 parent b1b672b commit 3a7ac80

File tree

3 files changed

+38
-4
lines changed

3 files changed

+38
-4
lines changed

helix-term/src/ui/completion.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

300321
impl Component for Completion {

helix-term/src/ui/editor.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1099,10 +1099,15 @@ impl EditorView {
10991099
}
11001100

11011101
pub fn handle_idle_timeout(&mut self, cx: &mut commands::Context) -> EventResult {
1102-
if self.completion.is_some()
1103-
|| cx.editor.mode != Mode::Insert
1104-
|| !cx.editor.config().auto_completion
1105-
{
1102+
if let Some(completion) = &mut self.completion {
1103+
return if completion.ensure_item_resolved(cx) {
1104+
EventResult::Consumed(None)
1105+
} else {
1106+
EventResult::Ignored(None)
1107+
};
1108+
}
1109+
1110+
if cx.editor.mode != Mode::Insert || !cx.editor.config().auto_completion {
11061111
return EventResult::Ignored(None);
11071112
}
11081113

helix-term/src/ui/menu.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}

0 commit comments

Comments
 (0)