Skip to content

Commit

Permalink
Added support for merging completion results of multiple language ser…
Browse files Browse the repository at this point in the history
…vers
  • Loading branch information
Philipp-M committed May 24, 2022
1 parent a705b70 commit d8bbfe7
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 54 deletions.
109 changes: 55 additions & 54 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<lsp::CompletionResponse>| {
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::<Vec<_>>();
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::<ui::EditorView>().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<lsp::CompletionResponse>| {
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::<Vec<_>>();

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::<ui::EditorView>().unwrap();
ui.set_or_extend_completion(editor, items, start_offset, trigger_offset, size);
},
);
}
}

// comments
Expand Down
8 changes: 8 additions & 0 deletions helix-term/src/ui/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<CompletionItem>) {
self.popup.contents_mut().add_options(items);
Expand Down
24 changes: 24 additions & 0 deletions helix-term/src/ui/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -937,6 +937,30 @@ impl EditorView {
self.completion = Some(completion);
}

pub fn set_or_extend_completion(
&mut self,
editor: &mut Editor,
items: Vec<CompletionItem>,
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;

Expand Down

0 comments on commit d8bbfe7

Please sign in to comment.