on master help sign in
only resolve completion items once
commit28b730381cc93d06e5a090b8734505edbebb1af4 authorPascal Kuthe <pascal.kuthe@semimod.de> dateWed 5 Apr 2023, 20:25 (4 years ago) parentbcb8c3d
1 file changed+55−42
helix-term/src/ui/completion.rs+55−42
helix-term/src/ui/completion.rs+55−42
@@ -16,7 +16,6 @@ use crate::commands;
1616use crate::ui::{menu, Markdown, Menu, Popup, PromptEvent};
1717
1818use helix_lsp::{lsp, util};
19−use lsp::CompletionItem;
2019
2120impl menu::Item for CompletionItem {
2221 type Data = ();
@@ -26,28 +25,29 @@ impl menu::Item for CompletionItem {
2625
2726 #[inline]
2827 fn filter_text(&self, _data: &Self::Data) -> Cow<str> {
29− self.filter_text
28+ self.item
29+ .filter_text
3030 .as_ref()
31− .unwrap_or(&self.label)
31+ .unwrap_or(&self.item.label)
3232 .as_str()
3333 .into()
3434 }
3535
3636 fn format(&self, _data: &Self::Data) -> menu::Row {
37− let deprecated = self.deprecated.unwrap_or_default()
38− || self.tags.as_ref().map_or(false, |tags| {
37+ let deprecated = self.item.deprecated.unwrap_or_default()
38+ || self.item.tags.as_ref().map_or(false, |tags| {
3939 tags.contains(&lsp::CompletionItemTag::DEPRECATED)
4040 });
4141 menu::Row::new(vec![
4242 menu::Cell::from(Span::styled(
43− self.label.as_str(),
43+ self.item.label.as_str(),
4444 if deprecated {
4545 Style::default().add_modifier(Modifier::CROSSED_OUT)
4646 } else {
4747 Style::default()
4848 },
4949 )),
50− menu::Cell::from(match self.kind {
50+ menu::Cell::from(match self.item.kind {
5151 Some(lsp::CompletionItemKind::TEXT) => "text",
5252 Some(lsp::CompletionItemKind::METHOD) => "method",
5353 Some(lsp::CompletionItemKind::FUNCTION) => "function",
@@ -88,6 +88,12 @@ impl menu::Item for CompletionItem {
8888 }
8989}
9090
91+#[derive(Debug, PartialEq, Default, Clone)]
92+struct CompletionItem {
93+ item: lsp::CompletionItem,
94+ resolved: bool,
95+}
96+
9197/// Wraps a Menu.
9298pub struct Completion {
9399 popup: Popup<Menu<CompletionItem>>,
@@ -103,7 +109,7 @@ impl Completion {
103109 pub fn new(
104110 editor: &Editor,
105111 savepoint: Arc<SavePoint>,
106− mut items: Vec<CompletionItem>,
112+ mut items: Vec<lsp::CompletionItem>,
107113 offset_encoding: helix_lsp::OffsetEncoding,
108114 start_offset: usize,
109115 trigger_offset: usize,
@@ -111,6 +117,13 @@ impl Completion {
111117 let replace_mode = editor.config().completion_replace;
112118 // Sort completion items according to their preselect status (given by the LSP server)
113119 items.sort_by_key(|item| !item.preselect.unwrap_or(false));
120+ let items = items
121+ .into_iter()
122+ .map(|item| CompletionItem {
123+ item,
124+ resolved: false,
125+ })
126+ .collect();
114127
115128 // Then create the menu
116129 let menu = Menu::new(items, (), move |editor: &mut Editor, item, event| {
@@ -128,7 +141,7 @@ impl Completion {
128141 let text = doc.text().slice(..);
129142 let primary_cursor = selection.primary().cursor(text);
130143
131− let (edit_offset, new_text) = if let Some(edit) = &item.text_edit {
144+ let (edit_offset, new_text) = if let Some(edit) = &item.item.text_edit {
132145 let edit = match edit {
133146 lsp::CompletionTextEdit::Edit(edit) => edit.clone(),
134147 lsp::CompletionTextEdit::InsertAndReplace(item) => {
@@ -151,9 +164,10 @@ impl Completion {
151164 (Some((start_offset, end_offset)), edit.new_text)
152165 } else {
153166 let new_text = item
167+ .item
154168 .insert_text
155169 .clone()
156− .unwrap_or_else(|| item.label.clone());
170+ .unwrap_or_else(|| item.item.label.clone());
157171 // check that we are still at the correct savepoint
158172 // we can still generate a transaction regardless but if the
159173 // document changed (and not just the selection) then we will
@@ -162,9 +176,9 @@ impl Completion {
162176 (None, new_text)
163177 };
164178
165− if matches!(item.kind, Some(lsp::CompletionItemKind::SNIPPET))
179+ if matches!(item.item.kind, Some(lsp::CompletionItemKind::SNIPPET))
166180 || matches!(
167− item.insert_text_format,
181+ item.item.insert_text_format,
168182 Some(lsp::InsertTextFormat::SNIPPET)
169183 )
170184 {
@@ -251,26 +265,22 @@ impl Completion {
251265 doc.restore(view, &savepoint, false);
252266 }
253267 // always present here
254− let item = item.unwrap();
255−
256− // apply additional edits, mostly used to auto import unqualified types
257− let resolved_item = if item
258− .additional_text_edits
259− .as_ref()
260− .map(|edits| !edits.is_empty())
261− .unwrap_or(false)
262− {
263− None
264− } else {
265− Self::resolve_completion_item(doc, item.clone())
268+ let mut item = item.unwrap().clone();
269+
270+ // resolve item if not yet resolved
271+ if !item.resolved {
272+ if let Some(resolved) =
273+ Self::resolve_completion_item(doc, item.item.clone())
274+ {
275+ item.item = resolved;
276+ }
266277 };
267−
268278 // if more text was entered, remove it
269279 doc.restore(view, &savepoint, true);
270280 let transaction = item_to_transaction(
271281 doc,
272282 view.id,
273− item,
283+ &item,
274284 offset_encoding,
275285 trigger_offset,
276286 false,
@@ -283,15 +293,12 @@ impl Completion {
283293 changes: completion_changes(&transaction, trigger_offset),
284294 });
285295
286− if let Some(additional_edits) = resolved_item
287− .as_ref()
288− .and_then(|item| item.additional_text_edits.as_ref())
289− .or(item.additional_text_edits.as_ref())
290− {
296+ // TOOD: add additional _edits to completion_changes?
297+ if let Some(additional_edits) = item.item.additional_text_edits {
291298 if !additional_edits.is_empty() {
292299 let transaction = util::generate_transaction_from_edits(
293300 doc.text(),
294− additional_edits.clone(),
301+ additional_edits,
295302 offset_encoding, // TODO: should probably transcode in Client
296303 );
297304 doc.apply(&transaction, view.id);
@@ -318,7 +325,7 @@ impl Completion {
318325 fn resolve_completion_item(
319326 doc: &Document,
320327 completion_item: lsp::CompletionItem,
321− ) -> Option<CompletionItem> {
328+ ) -> Option<lsp::CompletionItem> {
322329 let language_server = doc.language_server()?;
323330
324331 let future = language_server.resolve_completion_item(completion_item)?;
@@ -371,7 +378,7 @@ impl Completion {
371378 self.popup.contents().is_empty()
372379 }
373380
374− fn replace_item(&mut self, old_item: lsp::CompletionItem, new_item: lsp::CompletionItem) {
381+ fn replace_item(&mut self, old_item: CompletionItem, new_item: CompletionItem) {
375382 self.popup.contents_mut().replace_option(old_item, new_item);
376383 }
377384
@@ -387,7 +394,7 @@ impl Completion {
387394 // > The returned completion item should have the documentation property filled in.
388395 // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_completion
389396 let current_item = match self.popup.contents().selection() {
390− Some(item) if item.documentation.is_none() => item.clone(),
397+ Some(item) if !item.resolved => item.clone(),
391398 _ => return false,
392399 };
393400
@@ -397,7 +404,7 @@ impl Completion {
397404 };
398405
399406 // This method should not block the compositor so we handle the response asynchronously.
400− let future = match language_server.resolve_completion_item(current_item.clone()) {
407+ let future = match language_server.resolve_completion_item(current_item.item.clone()) {
401408 Some(future) => future,
402409 None => return false,
403410 };
@@ -415,7 +422,13 @@ impl Completion {
415422 .unwrap()
416423 .completion
417424 {
418− completion.replace_item(current_item, resolved_item);
425+ completion.replace_item(
426+ current_item,
427+ CompletionItem {
428+ item: resolved_item,
429+ resolved: true,
430+ },
431+ );
419432 }
420433 },
421434 );
@@ -469,25 +482,25 @@ impl Component for Completion {
469482 Markdown::new(md, cx.editor.syn_loader.clone())
470483 };
471484
472− let mut markdown_doc = match &option.documentation {
485+ let mut markdown_doc = match &option.item.documentation {
473486 Some(lsp::Documentation::String(contents))
474487 | Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
475488 kind: lsp::MarkupKind::PlainText,
476489 value: contents,
477490 })) => {
478491 // TODO: convert to wrapped text
479− markdowned(language, option.detail.as_deref(), Some(contents))
492+ markdowned(language, option.item.detail.as_deref(), Some(contents))
480493 }
481494 Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
482495 kind: lsp::MarkupKind::Markdown,
483496 value: contents,
484497 })) => {
485498 // TODO: set language based on doc scope
486− markdowned(language, option.detail.as_deref(), Some(contents))
499+ markdowned(language, option.item.detail.as_deref(), Some(contents))
487500 }
488− None if option.detail.is_some() => {
501+ None if option.item.detail.is_some() => {
489502 // TODO: set language based on doc scope
490− markdowned(language, option.detail.as_deref(), None)
503+ markdowned(language, option.item.detail.as_deref(), None)
491504 }
492505 None => return,
493506 };