aboutsummaryrefslogtreecommitdiff
path: root/helix-lsp/src/lib.rs
diff options
context:
space:
mode:
authorAndrii Grynenko2023-02-17 15:51:00 +0000
committerBlaž Hrastnik2023-03-08 01:48:35 +0000
commit1866b43cd355ff6d41d579b4b710a0f602aa79d1 (patch)
tree8550d0bc46df11e0a28c301becf75642ac70317d /helix-lsp/src/lib.rs
parentec6e575a408372400b7789b90cdf6ac271f51182 (diff)
Render every LSP snippets for every cursor
This refactors the snippet logic to be largely unaware of the rest of the document. The completion application logic is moved into generate_transaction_from_snippet which is extended to support dynamically computing replacement text.
Diffstat (limited to 'helix-lsp/src/lib.rs')
-rw-r--r--helix-lsp/src/lib.rs79
1 files changed, 79 insertions, 0 deletions
diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs
index cce848ab..5b4f7ee4 100644
--- a/helix-lsp/src/lib.rs
+++ b/helix-lsp/src/lib.rs
@@ -60,6 +60,7 @@ pub mod util {
use super::*;
use helix_core::line_ending::{line_end_byte_index, line_end_char_index};
use helix_core::{diagnostic::NumberOrString, Range, Rope, Selection, Tendril, Transaction};
+ use helix_core::{smallvec, SmallVec};
/// Converts a diagnostic in the document to [`lsp::Diagnostic`].
///
@@ -282,6 +283,84 @@ pub mod util {
})
}
+ /// Creates a [Transaction] from the [snippet::Snippet] in a completion response.
+ /// The transaction applies the edit to all cursors.
+ pub fn generate_transaction_from_snippet(
+ doc: &Rope,
+ selection: &Selection,
+ edit_range: &lsp::Range,
+ snippet: snippet::Snippet,
+ line_ending: &str,
+ include_placeholder: bool,
+ offset_encoding: OffsetEncoding,
+ ) -> Transaction {
+ let text = doc.slice(..);
+ let primary_cursor = selection.primary().cursor(text);
+
+ let start_offset = match lsp_pos_to_pos(doc, edit_range.start, offset_encoding) {
+ Some(start) => start as i128 - primary_cursor as i128,
+ None => return Transaction::new(doc),
+ };
+ let end_offset = match lsp_pos_to_pos(doc, edit_range.end, offset_encoding) {
+ Some(end) => end as i128 - primary_cursor as i128,
+ None => return Transaction::new(doc),
+ };
+
+ // For each cursor store offsets for the first tabstop
+ let mut cursor_tabstop_offsets = Vec::<SmallVec<[(i128, i128); 1]>>::new();
+ let transaction = Transaction::change_by_selection(doc, selection, |range| {
+ let cursor = range.cursor(text);
+ let replacement_start = (cursor as i128 + start_offset) as usize;
+ let replacement_end = (cursor as i128 + end_offset) as usize;
+ let newline_with_offset = format!(
+ "{line_ending}{blank:width$}",
+ line_ending = line_ending,
+ width = replacement_start - doc.line_to_char(doc.char_to_line(replacement_start)),
+ blank = ""
+ );
+
+ let (replacement, tabstops) =
+ snippet::render(&snippet, newline_with_offset, include_placeholder);
+
+ let replacement_len = replacement.chars().count();
+ cursor_tabstop_offsets.push(
+ tabstops
+ .first()
+ .unwrap_or(&smallvec![(replacement_len, replacement_len)])
+ .iter()
+ .map(|(from, to)| -> (i128, i128) {
+ (
+ *from as i128 - replacement_len as i128,
+ *to as i128 - replacement_len as i128,
+ )
+ })
+ .collect(),
+ );
+
+ (replacement_start, replacement_end, Some(replacement.into()))
+ });
+
+ // Create new selection based on the cursor tabstop from above
+ let mut cursor_tabstop_offsets_iter = cursor_tabstop_offsets.iter();
+ let selection = selection
+ .clone()
+ .map(transaction.changes())
+ .transform_iter(|range| {
+ cursor_tabstop_offsets_iter
+ .next()
+ .unwrap()
+ .iter()
+ .map(move |(from, to)| {
+ Range::new(
+ (range.anchor as i128 + *from) as usize,
+ (range.anchor as i128 + *to) as usize,
+ )
+ })
+ });
+
+ transaction.with_selection(selection)
+ }
+
pub fn generate_transaction_from_edits(
doc: &Rope,
mut edits: Vec<lsp::TextEdit>,