Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-lsp/src/lib.rs')
-rw-r--r--helix-lsp/src/lib.rs132
1 files changed, 15 insertions, 117 deletions
diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs
index 47f38bcf..134cb74f 100644
--- a/helix-lsp/src/lib.rs
+++ b/helix-lsp/src/lib.rs
@@ -2,7 +2,6 @@ mod client;
pub mod file_event;
mod file_operations;
pub mod jsonrpc;
-pub mod snippet;
mod transport;
use arc_swap::ArcSwap;
@@ -67,7 +66,8 @@ pub enum OffsetEncoding {
pub mod util {
use super::*;
use helix_core::line_ending::{line_end_byte_index, line_end_char_index};
- use helix_core::{chars, RopeSlice, SmallVec};
+ use helix_core::snippets::{RenderedSnippet, Snippet, SnippetRenderCtx};
+ use helix_core::{chars, RopeSlice};
use helix_core::{diagnostic::NumberOrString, Range, Rope, Selection, Tendril, Transaction};
/// Converts a diagnostic in the document to [`lsp::Diagnostic`].
@@ -355,25 +355,17 @@ pub mod util {
transaction.with_selection(selection)
}
- /// Creates a [Transaction] from the [snippet::Snippet] in a completion response.
+ /// Creates a [Transaction] from the [Snippet] in a completion response.
/// The transaction applies the edit to all cursors.
- #[allow(clippy::too_many_arguments)]
pub fn generate_transaction_from_snippet(
doc: &Rope,
selection: &Selection,
edit_offset: Option<(i128, i128)>,
replace_mode: bool,
- snippet: snippet::Snippet,
- line_ending: &str,
- include_placeholder: bool,
- tab_width: usize,
- indent_width: usize,
- ) -> Transaction {
+ snippet: Snippet,
+ cx: &mut SnippetRenderCtx,
+ ) -> (Transaction, RenderedSnippet) {
let text = doc.slice(..);
-
- let mut off = 0i128;
- let mut mapped_doc = doc.clone();
- let mut selection_tabstops: SmallVec<[_; 1]> = SmallVec::new();
let (removed_start, removed_end) = completion_range(
text,
edit_offset,
@@ -382,8 +374,7 @@ pub mod util {
)
.expect("transaction must be valid for primary selection");
let removed_text = text.slice(removed_start..removed_end);
-
- let (transaction, mut selection) = Transaction::change_by_selection_ignore_overlapping(
+ let (transaction, mapped_selection, snippet) = snippet.render(
doc,
selection,
|range| {
@@ -392,108 +383,15 @@ pub mod util {
.filter(|(start, end)| text.slice(start..end) == removed_text)
.unwrap_or_else(|| find_completion_range(text, replace_mode, cursor))
},
- |replacement_start, replacement_end| {
- let mapped_replacement_start = (replacement_start as i128 + off) as usize;
- let mapped_replacement_end = (replacement_end as i128 + off) as usize;
-
- let line_idx = mapped_doc.char_to_line(mapped_replacement_start);
- let indent_level = helix_core::indent::indent_level_for_line(
- mapped_doc.line(line_idx),
- tab_width,
- indent_width,
- ) * indent_width;
-
- let newline_with_offset = format!(
- "{line_ending}{blank:indent_level$}",
- line_ending = line_ending,
- blank = ""
- );
-
- let (replacement, tabstops) =
- snippet::render(&snippet, &newline_with_offset, include_placeholder);
- selection_tabstops.push((mapped_replacement_start, tabstops));
- mapped_doc.remove(mapped_replacement_start..mapped_replacement_end);
- mapped_doc.insert(mapped_replacement_start, &replacement);
- off +=
- replacement_start as i128 - replacement_end as i128 + replacement.len() as i128;
-
- Some(replacement)
- },
+ cx,
);
-
- let changes = transaction.changes();
- if changes.is_empty() {
- return transaction;
- }
-
- // Don't normalize to avoid merging/reording selections which would
- // break the association between tabstops and selections. Most ranges
- // will be replaced by tabstops anyways and the final selection will be
- // normalized anyways
- selection = selection.map_no_normalize(changes);
- let mut mapped_selection = SmallVec::with_capacity(selection.len());
- let mut mapped_primary_idx = 0;
- let primary_range = selection.primary();
- for (range, (tabstop_anchor, tabstops)) in selection.into_iter().zip(selection_tabstops) {
- if range == primary_range {
- mapped_primary_idx = mapped_selection.len()
- }
-
- let tabstops = tabstops.first().filter(|tabstops| !tabstops.is_empty());
- let Some(tabstops) = tabstops else {
- // no tabstop normal mapping
- mapped_selection.push(range);
- continue;
- };
-
- // expand the selection to cover the tabstop to retain the helix selection semantic
- // the tabstop closest to the range simply replaces `head` while anchor remains in place
- // the remaining tabstops receive their own single-width cursor
- if range.head < range.anchor {
- let last_idx = tabstops.len() - 1;
- let last_tabstop = tabstop_anchor + tabstops[last_idx].0;
-
- // if selection is forward but was moved to the right it is
- // contained entirely in the replacement text, just do a point
- // selection (fallback below)
- if range.anchor > last_tabstop {
- let range = Range::new(range.anchor, last_tabstop);
- mapped_selection.push(range);
- let rem_tabstops = tabstops[..last_idx]
- .iter()
- .map(|tabstop| Range::point(tabstop_anchor + tabstop.0));
- mapped_selection.extend(rem_tabstops);
- continue;
- }
- } else {
- let first_tabstop = tabstop_anchor + tabstops[0].0;
-
- // if selection is forward but was moved to the right it is
- // contained entirely in the replacement text, just do a point
- // selection (fallback below)
- if range.anchor < first_tabstop {
- // we can't properly compute the the next grapheme
- // here because the transaction hasn't been applied yet
- // that is not a problem because the range gets grapheme aligned anyway
- // tough so just adding one will always cause head to be grapheme
- // aligned correctly when applied to the document
- let range = Range::new(range.anchor, first_tabstop + 1);
- mapped_selection.push(range);
- let rem_tabstops = tabstops[1..]
- .iter()
- .map(|tabstop| Range::point(tabstop_anchor + tabstop.0));
- mapped_selection.extend(rem_tabstops);
- continue;
- }
- };
-
- let tabstops = tabstops
- .iter()
- .map(|tabstop| Range::point(tabstop_anchor + tabstop.0));
- mapped_selection.extend(tabstops);
- }
-
- transaction.with_selection(Selection::new(mapped_selection, mapped_primary_idx))
+ let transaction = transaction.with_selection(snippet.first_selection(
+ // we keep the direction of the old primary selection in case it changed during mapping
+ // but use the primary idx from the mapped selection in case ranges had to be merged
+ selection.primary().direction(),
+ mapped_selection.primary_index(),
+ ));
+ (transaction, snippet)
}
pub fn generate_transaction_from_edits(