Skip to content

Commit

Permalink
Refactored the Completion Menu
Browse files Browse the repository at this point in the history
- Added a CompletionItem for the Completion menu to (potentially) support more than just LSP CompletionItems
- Added a method 'add_completion_items' to asynchronously add more options if the completion menu is already open
  • Loading branch information
Philipp-M committed May 24, 2022
1 parent c8ebd4c commit a705b70
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 87 deletions.
32 changes: 16 additions & 16 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ use movement::Movement;
use crate::{
args,
compositor::{self, Component, Compositor},
ui::{self, overlay::overlayed, FilePicker, Picker, Popup, Prompt, PromptEvent},
ui::{
self, menu::Item, overlay::overlayed, CompletionItem, FilePicker, Picker, Popup, Prompt,
PromptEvent,
},
};

use crate::job::{self, Job, Jobs};
Expand Down Expand Up @@ -3615,6 +3618,8 @@ pub fn completion(cx: &mut Context) {
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);
Expand Down Expand Up @@ -3652,17 +3657,19 @@ pub fn completion(cx: &mut Context) {
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
.as_ref()
.unwrap_or(&item.label)
.starts_with(&prefix)
})
.filter(|item| item.filter_text().starts_with(&prefix))
.collect();
}

Expand All @@ -3672,14 +3679,7 @@ pub fn completion(cx: &mut Context) {
}
let size = compositor.size();
let ui = compositor.find::<ui::EditorView>().unwrap();
ui.set_completion(
editor,
items,
offset_encoding,
start_offset,
trigger_offset,
size,
);
ui.set_completion(editor, items, start_offset, trigger_offset, size);
},
);
}
Expand Down
158 changes: 94 additions & 64 deletions helix-term/src/ui/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,59 +11,81 @@ use helix_view::{graphics::Rect, Document, Editor};
use crate::commands;
use crate::ui::{menu, Markdown, Menu, Popup, PromptEvent};

use helix_lsp::{lsp, util};
use lsp::CompletionItem;
use helix_lsp::{lsp, util, OffsetEncoding};

#[derive(Clone)]
pub enum CompletionItem {
LSP {
language_server_id: usize,
item: lsp::CompletionItem,
offset_encoding: OffsetEncoding,
},
}

impl menu::Item for CompletionItem {
fn sort_text(&self) -> &str {
self.filter_text.as_ref().unwrap_or(&self.label).as_str()
match self {
CompletionItem::LSP { item, .. } => {
item.filter_text.as_ref().unwrap_or(&item.label).as_str()
}
}
}

fn filter_text(&self) -> &str {
self.filter_text.as_ref().unwrap_or(&self.label).as_str()
match self {
CompletionItem::LSP { item, .. } => {
item.filter_text.as_ref().unwrap_or(&item.label).as_str()
}
}
}

fn label(&self) -> &str {
self.label.as_str()
match self {
CompletionItem::LSP { item, .. } => item.label.as_str(),
}
}

fn row(&self) -> menu::Row {
menu::Row::new(vec![
menu::Cell::from(self.label.as_str()),
menu::Cell::from(match self.kind {
Some(lsp::CompletionItemKind::TEXT) => "text",
Some(lsp::CompletionItemKind::METHOD) => "method",
Some(lsp::CompletionItemKind::FUNCTION) => "function",
Some(lsp::CompletionItemKind::CONSTRUCTOR) => "constructor",
Some(lsp::CompletionItemKind::FIELD) => "field",
Some(lsp::CompletionItemKind::VARIABLE) => "variable",
Some(lsp::CompletionItemKind::CLASS) => "class",
Some(lsp::CompletionItemKind::INTERFACE) => "interface",
Some(lsp::CompletionItemKind::MODULE) => "module",
Some(lsp::CompletionItemKind::PROPERTY) => "property",
Some(lsp::CompletionItemKind::UNIT) => "unit",
Some(lsp::CompletionItemKind::VALUE) => "value",
Some(lsp::CompletionItemKind::ENUM) => "enum",
Some(lsp::CompletionItemKind::KEYWORD) => "keyword",
Some(lsp::CompletionItemKind::SNIPPET) => "snippet",
Some(lsp::CompletionItemKind::COLOR) => "color",
Some(lsp::CompletionItemKind::FILE) => "file",
Some(lsp::CompletionItemKind::REFERENCE) => "reference",
Some(lsp::CompletionItemKind::FOLDER) => "folder",
Some(lsp::CompletionItemKind::ENUM_MEMBER) => "enum_member",
Some(lsp::CompletionItemKind::CONSTANT) => "constant",
Some(lsp::CompletionItemKind::STRUCT) => "struct",
Some(lsp::CompletionItemKind::EVENT) => "event",
Some(lsp::CompletionItemKind::OPERATOR) => "operator",
Some(lsp::CompletionItemKind::TYPE_PARAMETER) => "type_param",
Some(kind) => unimplemented!("{:?}", kind),
None => "",
}),
// self.detail.as_deref().unwrap_or("")
// self.label_details
// .as_ref()
// .or(self.detail())
// .as_str(),
menu::Cell::from(self.label()),
match self {
CompletionItem::LSP { item, .. } => {
menu::Cell::from(match item.kind {
Some(lsp::CompletionItemKind::TEXT) => "text",
Some(lsp::CompletionItemKind::METHOD) => "method",
Some(lsp::CompletionItemKind::FUNCTION) => "function",
Some(lsp::CompletionItemKind::CONSTRUCTOR) => "constructor",
Some(lsp::CompletionItemKind::FIELD) => "field",
Some(lsp::CompletionItemKind::VARIABLE) => "variable",
Some(lsp::CompletionItemKind::CLASS) => "class",
Some(lsp::CompletionItemKind::INTERFACE) => "interface",
Some(lsp::CompletionItemKind::MODULE) => "module",
Some(lsp::CompletionItemKind::PROPERTY) => "property",
Some(lsp::CompletionItemKind::UNIT) => "unit",
Some(lsp::CompletionItemKind::VALUE) => "value",
Some(lsp::CompletionItemKind::ENUM) => "enum",
Some(lsp::CompletionItemKind::KEYWORD) => "keyword",
Some(lsp::CompletionItemKind::SNIPPET) => "snippet",
Some(lsp::CompletionItemKind::COLOR) => "color",
Some(lsp::CompletionItemKind::FILE) => "file",
Some(lsp::CompletionItemKind::REFERENCE) => "reference",
Some(lsp::CompletionItemKind::FOLDER) => "folder",
Some(lsp::CompletionItemKind::ENUM_MEMBER) => "enum_member",
Some(lsp::CompletionItemKind::CONSTANT) => "constant",
Some(lsp::CompletionItemKind::STRUCT) => "struct",
Some(lsp::CompletionItemKind::EVENT) => "event",
Some(lsp::CompletionItemKind::OPERATOR) => "operator",
Some(lsp::CompletionItemKind::TYPE_PARAMETER) => "type_param",
Some(kind) => unimplemented!("{:?}", kind),
None => "",
})
// self.detail.as_deref().unwrap_or("")
// self.label_details
// .as_ref()
// .or(self.detail())
// .as_str(),
}
},
])
}
}
Expand All @@ -81,18 +103,24 @@ impl Completion {
pub fn new(
editor: &Editor,
items: Vec<CompletionItem>,
offset_encoding: helix_lsp::OffsetEncoding,
start_offset: usize,
trigger_offset: usize,
) -> Self {
let menu = Menu::new(items, move |editor: &mut Editor, item, event| {
fn item_to_transaction(
doc: &Document,
item: &CompletionItem,
offset_encoding: helix_lsp::OffsetEncoding,
start_offset: usize,
trigger_offset: usize,
) -> Transaction {
// for now only LSP support
let (item, offset_encoding) = match item {
CompletionItem::LSP {
item,
offset_encoding,
..
} => (item, *offset_encoding),
};
let transaction = if let Some(edit) = &item.text_edit {
let edit = match edit {
lsp::CompletionTextEdit::Edit(edit) => edit.clone(),
Expand Down Expand Up @@ -142,13 +170,7 @@ impl Completion {
// always present here
let item = item.unwrap();

let transaction = item_to_transaction(
doc,
item,
offset_encoding,
start_offset,
trigger_offset,
);
let transaction = item_to_transaction(doc, item, start_offset, trigger_offset);

// initialize a savepoint
doc.savepoint();
Expand All @@ -163,13 +185,7 @@ impl Completion {
// always present here
let item = item.unwrap();

let transaction = item_to_transaction(
doc,
item,
offset_encoding,
start_offset,
trigger_offset,
);
let transaction = item_to_transaction(doc, item, start_offset, trigger_offset);

doc.apply(&transaction, view.id);

Expand All @@ -178,22 +194,34 @@ impl Completion {
changes: completion_changes(&transaction, trigger_offset),
});

let (lsp_item, offset_encoding, language_server_id) = match item {
CompletionItem::LSP {
item,
offset_encoding,
language_server_id,
} => (item, *offset_encoding, *language_server_id),
};

// apply additional edits, mostly used to auto import unqualified types
let resolved_item = if item
let resolved_item = if lsp_item
.additional_text_edits
.as_ref()
.map(|edits| !edits.is_empty())
.unwrap_or(false)
{
None
} else {
Self::resolve_completion_item(doc, item.clone())
let language_server = editor
.language_servers
.get_by_id(language_server_id)
.unwrap();
Self::resolve_completion_item(language_server, lsp_item.clone())
};

if let Some(additional_edits) = resolved_item
.as_ref()
.and_then(|item| item.additional_text_edits.as_ref())
.or(item.additional_text_edits.as_ref())
.or(lsp_item.additional_text_edits.as_ref())
{
if !additional_edits.is_empty() {
let transaction = util::generate_transaction_from_edits(
Expand Down Expand Up @@ -221,11 +249,9 @@ impl Completion {
}

fn resolve_completion_item(
doc: &Document,
language_server: &helix_lsp::Client,
completion_item: lsp::CompletionItem,
) -> Option<CompletionItem> {
// TODO support multiple language servers instead of taking the first language server
let language_server = doc.language_servers().first().map(|l| *l)?;
) -> Option<lsp::CompletionItem> {
let completion_resolve_provider = language_server
.capabilities()
.completion_provider
Expand All @@ -246,6 +272,10 @@ impl Completion {
}
}

pub fn add_completion_items(&mut self, items: Vec<CompletionItem>) {
self.popup.contents_mut().add_options(items);
}

pub fn recompute_filter(&mut self, editor: &Editor) {
// recompute menu based on matches
let menu = self.popup.contents_mut();
Expand Down Expand Up @@ -306,7 +336,7 @@ impl Component for Completion {
self.popup.render(area, surface, cx);

// if we have a selection, render a markdown popup on top/below with info
if let Some(option) = self.popup.contents().selection() {
if let Some(CompletionItem::LSP { item: option, .. }) = self.popup.contents().selection() {
// need to render:
// option.detail
// ---
Expand Down
8 changes: 3 additions & 5 deletions helix-term/src/ui/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::{
compositor::{Component, Context, EventResult},
key,
keymap::{KeymapResult, Keymaps},
ui::{Completion, ProgressSpinners},
ui::{Completion, CompletionItem, ProgressSpinners},
};

use helix_core::{
Expand Down Expand Up @@ -914,14 +914,12 @@ impl EditorView {
pub fn set_completion(
&mut self,
editor: &mut Editor,
items: Vec<helix_lsp::lsp::CompletionItem>,
offset_encoding: helix_lsp::OffsetEncoding,
items: Vec<CompletionItem>,
start_offset: usize,
trigger_offset: usize,
size: Rect,
) {
let mut completion =
Completion::new(editor, items, offset_encoding, start_offset, trigger_offset);
let mut completion = Completion::new(editor, items, start_offset, trigger_offset);

if completion.is_empty() {
// skip if we got no completion results
Expand Down
4 changes: 2 additions & 2 deletions helix-term/src/ui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ mod prompt;
mod spinner;
mod text;

pub use completion::Completion;
pub use completion::{Completion, CompletionItem};
pub use editor::EditorView;
pub use markdown::Markdown;
pub use menu::Menu;
pub use picker::{FileLocation, FilePicker, Picker};
pub use overlay::Overlay;
pub use picker::{FileLocation, FilePicker, Picker};
pub use popup::Popup;
pub use prompt::{Prompt, PromptEvent};
pub use spinner::{ProgressSpinners, Spinner};
Expand Down

0 comments on commit a705b70

Please sign in to comment.