diff options
Diffstat (limited to 'helix-term/src/ui/completion.rs')
-rw-r--r-- | helix-term/src/ui/completion.rs | 134 |
1 files changed, 61 insertions, 73 deletions
diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index c75b24f1..dd782d29 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -5,7 +5,7 @@ use tui::buffer::Buffer as Surface; use std::borrow::Cow; use helix_core::Transaction; -use helix_view::{graphics::Rect, Document, Editor, View}; +use helix_view::{graphics::Rect, Document, Editor}; use crate::commands; use crate::ui::{menu, Markdown, Menu, Popup, PromptEvent}; @@ -30,31 +30,32 @@ impl menu::Item for CompletionItem { 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::EnumMember) => "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::TypeParameter) => "type_param", + 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("") @@ -83,13 +84,13 @@ impl Completion { start_offset: usize, trigger_offset: usize, ) -> Self { - // let items: Vec<CompletionItem> = Vec::new(); let menu = Menu::new(items, move |editor: &mut Editor, item, event| { fn item_to_transaction( doc: &Document, - view: &View, item: &CompletionItem, offset_encoding: helix_lsp::OffsetEncoding, + start_offset: usize, + trigger_offset: usize, ) -> Transaction { if let Some(edit) = &item.text_edit { let edit = match edit { @@ -105,63 +106,52 @@ impl Completion { ) } else { let text = item.insert_text.as_ref().unwrap_or(&item.label); - let cursor = doc - .selection(view.id) - .primary() - .cursor(doc.text().slice(..)); + // Some LSPs just give you an insertText with no offset ¯\_(ツ)_/¯ + // in these cases we need to check for a common prefix and remove it + let prefix = Cow::from(doc.text().slice(start_offset..trigger_offset)); + let text = text.trim_start_matches::<&str>(&prefix); Transaction::change( doc.text(), - vec![(cursor, cursor, Some(text.as_str().into()))].into_iter(), + vec![(trigger_offset, trigger_offset, Some(text.into()))].into_iter(), ) } } + let (view, doc) = current!(editor); + + // if more text was entered, remove it + doc.restore(view.id); + match event { PromptEvent::Abort => {} PromptEvent::Update => { - let (view, doc) = current!(editor); - // always present here let item = item.unwrap(); - // if more text was entered, remove it - // TODO: ideally to undo we should keep the last completion tx revert, and map it over new changes - let cursor = doc - .selection(view.id) - .primary() - .cursor(doc.text().slice(..)); - if trigger_offset < cursor { - let remove = Transaction::change( - doc.text(), - vec![(trigger_offset, cursor, None)].into_iter(), - ); - doc.apply(&remove, view.id); - } + let transaction = item_to_transaction( + doc, + item, + offset_encoding, + start_offset, + trigger_offset, + ); + + // initialize a savepoint + doc.savepoint(); - let transaction = item_to_transaction(doc, view, item, offset_encoding); doc.apply(&transaction, view.id); } PromptEvent::Validate => { - let (view, doc) = current!(editor); - // always present here let item = item.unwrap(); - // if more text was entered, remove it - // TODO: ideally to undo we should keep the last completion tx revert, and map it over new changes - let cursor = doc - .selection(view.id) - .primary() - .cursor(doc.text().slice(..)); - if trigger_offset < cursor { - let remove = Transaction::change( - doc.text(), - vec![(trigger_offset, cursor, None)].into_iter(), - ); - doc.apply(&remove, view.id); - } - - let transaction = item_to_transaction(doc, view, item, offset_encoding); + let transaction = item_to_transaction( + doc, + item, + offset_encoding, + start_offset, + trigger_offset, + ); doc.apply(&transaction, view.id); if let Some(additional_edits) = &item.additional_text_edits { @@ -210,7 +200,7 @@ impl Completion { .selection(view.id) .primary() .cursor(doc.text().slice(..)); - if self.start_offset <= cursor { + if self.trigger_offset <= cursor { let fragment = doc.text().slice(self.start_offset..cursor); let text = Cow::from(fragment); // TODO: logic is same as ui/picker @@ -274,12 +264,10 @@ impl Component for Completion { .language() .and_then(|scope| scope.strip_prefix("source.")) .unwrap_or(""); - let cursor_pos = doc - .selection(view.id) - .primary() - .cursor(doc.text().slice(..)); - let cursor_pos = (helix_core::coords_at_pos(doc.text().slice(..), cursor_pos).row - - view.offset.row) as u16; + let text = doc.text().slice(..); + let cursor_pos = doc.selection(view.id).primary().cursor(text); + let coords = helix_core::visual_coords_at_pos(text, cursor_pos, doc.tab_width()); + let cursor_pos = (coords.row - view.offset.row) as u16; let mut markdown_doc = match &option.documentation { Some(lsp::Documentation::String(contents)) | Some(lsp::Documentation::MarkupContent(lsp::MarkupContent { |