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.rs | 132 |
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( |