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.rs216
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()
+}