diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 0e81fe0077321..2a36d76f5c397 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -3611,23 +3611,8 @@ pub fn completion(cx: &mut Context) { let (view, doc) = current!(cx.editor); - // TODO merge completion items of multiple language servers, - // instead of taking the first language server for completion - let language_server = match doc.language_servers().first() { - Some(language_server) => *language_server, - None => return, - }; - - let language_server_id = language_server.id(); - - let offset_encoding = language_server.offset_encoding(); let text = doc.text().slice(..); let cursor = doc.selection(view.id).primary().cursor(text); - - let pos = pos_to_lsp_pos(doc.text(), cursor, offset_encoding); - - let future = language_server.completion(doc.identifier(), pos, None); - let trigger_offset = cursor; // TODO: trigger_offset should be the cursor offset but we also need a starting offset from where we want to apply @@ -3640,48 +3625,64 @@ pub fn completion(cx: &mut Context) { let start_offset = cursor.saturating_sub(offset); let prefix = text.slice(start_offset..cursor).to_string(); - cx.callback( - future, - move |editor, compositor, response: Option| { - let doc = doc!(editor); - if doc.mode() != Mode::Insert { - // we're not in insert mode anymore - return; - } + let mut requests = Vec::new(); - let mut items = match response { - Some(lsp::CompletionResponse::Array(items)) => items, - // TODO: do something with is_incomplete - Some(lsp::CompletionResponse::List(lsp::CompletionList { - is_incomplete: _is_incomplete, - items, - })) => items, - None => Vec::new(), - } - .into_iter() - .map(|item| CompletionItem::LSP { - language_server_id, - item, - offset_encoding, - }) - .collect::>(); + for language_server in doc.language_servers() { + let language_server_id = language_server.id(); - if !prefix.is_empty() { - items = items - .into_iter() - .filter(|item| item.filter_text().starts_with(&prefix)) - .collect(); - } + let offset_encoding = language_server.offset_encoding(); - if items.is_empty() { - // editor.set_error("No completion available"); - return; - } - let size = compositor.size(); - let ui = compositor.find::().unwrap(); - ui.set_completion(editor, items, start_offset, trigger_offset, size); - }, - ); + let pos = pos_to_lsp_pos(doc.text(), cursor, offset_encoding); + + let future = language_server.completion(doc.identifier(), pos, None); + requests.push((future, language_server_id, offset_encoding)); + } + + for (future, language_server_id, offset_encoding) in requests { + let prefix = prefix.clone(); + cx.callback( + future, + move |editor, compositor, response: Option| { + let doc = doc!(editor); + if doc.mode() != Mode::Insert { + // we're not in insert mode anymore + return; + } + + let mut items = match response { + Some(lsp::CompletionResponse::Array(items)) => items, + // TODO: do something with is_incomplete + Some(lsp::CompletionResponse::List(lsp::CompletionList { + is_incomplete: _is_incomplete, + items, + })) => items, + None => Vec::new(), + } + .into_iter() + .map(|item| CompletionItem::LSP { + language_server_id, + item, + offset_encoding, + }) + .collect::>(); + + if !prefix.is_empty() { + items = items + .into_iter() + .filter(|item| item.filter_text().starts_with(&prefix)) + .collect(); + } + + if items.is_empty() { + // editor.set_error("No completion available"); + return; + } + let size = compositor.size(); + let ui = compositor.find::().unwrap(); + ui.set_or_extend_completion(editor, items, start_offset, trigger_offset, size); + }, + ); + } } // comments diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index f2388940cf019..0ddf697265bbb 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -271,6 +271,14 @@ impl Completion { } } } + + pub fn trigger_offset(&self) -> usize { + self.trigger_offset + } + + pub fn start_offset(&self) -> usize { + self.start_offset + } pub fn add_completion_items(&mut self, items: Vec) { self.popup.contents_mut().add_options(items); diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 218d47167f608..01c6c28f86c81 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -937,6 +937,30 @@ impl EditorView { self.completion = Some(completion); } + pub fn set_or_extend_completion( + &mut self, + editor: &mut Editor, + items: Vec, + start_offset: usize, + trigger_offset: usize, + size: Rect, + ) { + match &mut self.completion { + Some(completion) => { + // cheap check, if the completion menu resulted of the same 'completion' trigger (e.g. by commands::completion) + // TODO test/check if this is enough/safe... + if start_offset == completion.start_offset() + && completion.trigger_offset() == trigger_offset + { + completion.add_completion_items(items) + } else { + self.set_completion(editor, items, start_offset, trigger_offset, size) + } + } + None => self.set_completion(editor, items, start_offset, trigger_offset, size), + } + } + pub fn clear_completion(&mut self, editor: &mut Editor) { self.completion = None;