Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-term/src/ui/completion.rs')
| -rw-r--r-- | helix-term/src/ui/completion.rs | 216 |
1 files changed, 109 insertions, 107 deletions
diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index cb0af6fc..c50832af 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -16,7 +16,11 @@ use tui::{buffer::Buffer as Surface, text::Span}; use std::{borrow::Cow, sync::Arc}; -use helix_core::{self as core, chars, Change, Transaction}; +use helix_core::{ + self as core, chars, + snippets::{ActiveSnippet, RenderedSnippet, Snippet}, + Change, Transaction, +}; use helix_view::{graphics::Rect, Document, Editor}; use crate::ui::{menu, Markdown, Menu, Popup, PromptEvent}; @@ -133,101 +137,6 @@ impl Completion { // Then create the menu let menu = Menu::new(items, (), move |editor: &mut Editor, item, event| { - fn lsp_item_to_transaction( - doc: &Document, - view_id: ViewId, - item: &lsp::CompletionItem, - offset_encoding: OffsetEncoding, - trigger_offset: usize, - include_placeholder: bool, - replace_mode: bool, - ) -> Transaction { - use helix_lsp::snippet; - let selection = doc.selection(view_id); - let text = doc.text().slice(..); - let primary_cursor = selection.primary().cursor(text); - - let (edit_offset, new_text) = if let Some(edit) = &item.text_edit { - let edit = match edit { - lsp::CompletionTextEdit::Edit(edit) => edit.clone(), - lsp::CompletionTextEdit::InsertAndReplace(item) => { - let range = if replace_mode { - item.replace - } else { - item.insert - }; - lsp::TextEdit::new(range, item.new_text.clone()) - } - }; - - let Some(range) = - util::lsp_range_to_range(doc.text(), edit.range, offset_encoding) - else { - return Transaction::new(doc.text()); - }; - - let start_offset = range.anchor as i128 - primary_cursor as i128; - let end_offset = range.head as i128 - primary_cursor as i128; - - (Some((start_offset, end_offset)), edit.new_text) - } else { - let new_text = item - .insert_text - .clone() - .unwrap_or_else(|| item.label.clone()); - // check that we are still at the correct savepoint - // we can still generate a transaction regardless but if the - // document changed (and not just the selection) then we will - // likely delete the wrong text (same if we applied an edit sent by the LS) - debug_assert!(primary_cursor == trigger_offset); - (None, new_text) - }; - - if matches!(item.kind, Some(lsp::CompletionItemKind::SNIPPET)) - || matches!( - item.insert_text_format, - Some(lsp::InsertTextFormat::SNIPPET) - ) - { - match snippet::parse(&new_text) { - Ok(snippet) => util::generate_transaction_from_snippet( - doc.text(), - selection, - edit_offset, - replace_mode, - snippet, - doc.line_ending.as_str(), - include_placeholder, - doc.tab_width(), - doc.indent_width(), - ), - Err(err) => { - log::error!( - "Failed to parse snippet: {:?}, remaining output: {}", - &new_text, - err - ); - Transaction::new(doc.text()) - } - } - } else { - util::generate_transaction_from_completion_edit( - doc.text(), - selection, - edit_offset, - replace_mode, - new_text, - ) - } - } - - fn completion_changes(transaction: &Transaction, trigger_offset: usize) -> Vec<Change> { - transaction - .changes_iter() - .filter(|(start, end, _)| (*start..=*end).contains(&trigger_offset)) - .collect() - } - let (view, doc) = current!(editor); macro_rules! language_server { @@ -272,18 +181,17 @@ impl Completion { let item = item.unwrap(); match item { - CompletionItem::Lsp(item) => doc.apply_temporary( - &lsp_item_to_transaction( + CompletionItem::Lsp(item) => { + let (transaction, _) = lsp_item_to_transaction( doc, view.id, &item.item, language_server!(item).offset_encoding(), trigger_offset, - true, replace_mode, - ), - view.id, - ), + ); + doc.apply_temporary(&transaction, view.id) + } CompletionItem::Other(core::CompletionItem { transaction, .. }) => { doc.apply_temporary(transaction, view.id) } @@ -303,7 +211,7 @@ impl Completion { doc.append_changes_to_history(view); // item always present here - let (transaction, additional_edits) = match item.unwrap().clone() { + let (transaction, additional_edits, snippet) = match item.unwrap().clone() { CompletionItem::Lsp(mut item) => { let language_server = language_server!(item); @@ -318,29 +226,40 @@ impl Completion { }; let encoding = language_server.offset_encoding(); - let transaction = lsp_item_to_transaction( + let (transaction, snippet) = lsp_item_to_transaction( doc, view.id, &item.item, encoding, trigger_offset, - false, replace_mode, ); let add_edits = item.item.additional_text_edits; - (transaction, add_edits.map(|edits| (edits, encoding))) + ( + transaction, + add_edits.map(|edits| (edits, encoding)), + snippet, + ) } CompletionItem::Other(core::CompletionItem { transaction, .. }) => { - (transaction, None) + (transaction, None, None) } }; doc.apply(&transaction, view.id); + let placeholder = snippet.is_some(); + if let Some(snippet) = snippet { + doc.active_snippet = match doc.active_snippet.take() { + Some(active) => active.insert_subsnippet(snippet), + None => ActiveSnippet::new(snippet), + }; + } editor.last_completion = Some(CompleteAction::Applied { trigger_offset, changes: completion_changes(&transaction, trigger_offset), + placeholder, }); // TODO: add additional _edits to completion_changes? @@ -581,3 +500,86 @@ impl Component for Completion { markdown_doc.render(doc_area, surface, cx); } } +fn lsp_item_to_transaction( + doc: &Document, + view_id: ViewId, + item: &lsp::CompletionItem, + offset_encoding: OffsetEncoding, + trigger_offset: usize, + replace_mode: bool, +) -> (Transaction, Option<RenderedSnippet>) { + let selection = doc.selection(view_id); + let text = doc.text().slice(..); + let primary_cursor = selection.primary().cursor(text); + + let (edit_offset, new_text) = if let Some(edit) = &item.text_edit { + let edit = match edit { + lsp::CompletionTextEdit::Edit(edit) => edit.clone(), + lsp::CompletionTextEdit::InsertAndReplace(item) => { + let range = if replace_mode { + item.replace + } else { + item.insert + }; + lsp::TextEdit::new(range, item.new_text.clone()) + } + }; + + let Some(range) = util::lsp_range_to_range(doc.text(), edit.range, offset_encoding) else { + return (Transaction::new(doc.text()), None); + }; + + let start_offset = range.anchor as i128 - primary_cursor as i128; + let end_offset = range.head as i128 - primary_cursor as i128; + + (Some((start_offset, end_offset)), edit.new_text) + } else { + let new_text = item + .insert_text + .clone() + .unwrap_or_else(|| item.label.clone()); + // check that we are still at the correct savepoint + // we can still generate a transaction regardless but if the + // document changed (and not just the selection) then we will + // likely delete the wrong text (same if we applied an edit sent by the LS) + debug_assert!(primary_cursor == trigger_offset); + (None, new_text) + }; + + if matches!(item.kind, Some(lsp::CompletionItemKind::SNIPPET)) + || matches!( + item.insert_text_format, + Some(lsp::InsertTextFormat::SNIPPET) + ) + { + let Ok(snippet) = Snippet::parse(&new_text) else { + log::error!("Failed to parse snippet: {new_text:?}",); + return (Transaction::new(doc.text()), None); + }; + let (transaction, snippet) = util::generate_transaction_from_snippet( + doc.text(), + selection, + edit_offset, + replace_mode, + snippet, + &mut doc.snippet_ctx(), + ); + (transaction, Some(snippet)) + } else { + let transaction = util::generate_transaction_from_completion_edit( + doc.text(), + selection, + edit_offset, + replace_mode, + new_text, + ); + (transaction, None) + } +} + +fn completion_changes(transaction: &Transaction, trigger_offset: usize) -> Vec<Change> { + transaction + .changes_iter() + .filter(|(start, end, _)| (*start..=*end).contains(&trigger_offset)) + .collect() +} |