@@ -1,3 +1,5 @@
1+use std::borrow::Cow;
2+
13use anyhow::{anyhow, Result};
24
35use crate::{util::lsp_pos_to_pos, OffsetEncoding};
@@ -54,6 +56,112 @@ pub fn parse(s: &str) -> Result<Snippet<'_>> {
5456 parser::parse(s).map_err(|rest| anyhow!("Failed to parse snippet. Remaining input: {}", rest))
5557}
5658
59+pub fn into_transaction<'a>(
60+ snippet: Snippet<'a>,
61+ doc: &helix_core::Rope,
62+ selection: &helix_core::Selection,
63+ edit: &lsp_types::TextEdit,
64+ line_ending: &str,
65+ offset_encoding: OffsetEncoding,
66+) -> helix_core::Transaction {
67+ use helix_core::{smallvec, Range, Selection, Transaction};
68+ use SnippetElement::*;
69+
70+ let text = doc.slice(..);
71+ let primary_cursor = selection.primary().cursor(text);
72+
73+ let start_offset = match lsp_pos_to_pos(doc, edit.range.start, offset_encoding) {
74+ Some(start) => start as i128 - primary_cursor as i128,
75+ None => return Transaction::new(doc),
76+ };
77+ let end_offset = match lsp_pos_to_pos(doc, edit.range.end, offset_encoding) {
78+ Some(end) => end as i128 - primary_cursor as i128,
79+ None => return Transaction::new(doc),
80+ };
81+
82+ let newline_with_offset = format!(
83+ "{line_ending}{blank:width$}",
84+ width = edit.range.start.character as usize,
85+ blank = ""
86+ );
87+
88+ let mut insert = String::new();
89+ let mut offset = (primary_cursor as i128 + start_offset) as usize;
90+ let mut tabstops: Vec<Range> = Vec::new();
91+
92+ for element in snippet.elements {
93+ match element {
94+ Text(text) => {
95+ // small optimization to avoid calling replace when it's unnecessary
96+ let text = if text.contains('\n') {
97+ Cow::Owned(text.replace('\n', &newline_with_offset))
98+ } else {
99+ Cow::Borrowed(text)
100+ };
101+ offset += text.chars().count();
102+ insert.push_str(&text);
103+ }
104+ Variable {
105+ name: _name,
106+ regex: None,
107+ r#default,
108+ } => {
109+ // TODO: variables. For now, fall back to the default, which defaults to "".
110+ let text = r#default.unwrap_or_default();
111+ offset += text.chars().count();
112+ insert.push_str(text);
113+ }
114+ Tabstop { .. } => {
115+ // TODO: tabstop indexing: 0 is final cursor position. 1,2,.. are positions.
116+ // TODO: merge tabstops with the same index
117+ tabstops.push(Range::point(offset));
118+ }
119+ Placeholder {
120+ tabstop: _tabstop,
121+ value,
122+ } => match value.as_ref() {
123+ // https://doc.rust-lang.org/beta/unstable-book/language-features/box-patterns.html
124+ // would make this a bit nicer
125+ Text(text) => {
126+ let len_chars = text.chars().count();
127+ tabstops.push(Range::new(offset, offset + len_chars + 1));
128+ offset += len_chars;
129+ insert.push_str(text);
130+ }
131+ other => {
132+ log::error!(
133+ "Discarding snippet: generating a transaction for placeholder contents {:?} is unimplemented.",
134+ other
135+ );
136+ return Transaction::new(doc);
137+ }
138+ },
139+ other => {
140+ log::error!(
141+ "Discarding snippet: generating a transaction for {:?} is unimplemented.",
142+ other
143+ );
144+ return Transaction::new(doc);
145+ }
146+ }
147+ }
148+
149+ let transaction = Transaction::change_by_selection(doc, selection, |range| {
150+ let cursor = range.cursor(text);
151+ (
152+ (cursor as i128 + start_offset) as usize,
153+ (cursor as i128 + end_offset) as usize,
154+ Some(insert.clone().into()),
155+ )
156+ });
157+
158+ if let Some(first) = tabstops.first() {
159+ transaction.with_selection(Selection::new(smallvec![*first], 0))
160+ } else {
161+ transaction
162+ }
163+}
164+
57165mod parser {
58166 use helix_parsec::*;
59167