@@ -1,9 +1,7 @@
11use std::borrow::Cow;
22
33use anyhow::{anyhow, Result};
4−use helix_core::SmallVec;
5−
6−use crate::{util::lsp_pos_to_pos, OffsetEncoding};
4+use helix_core::{SmallVec, smallvec};
75
86#[derive(Debug, PartialEq, Eq)]
97pub enum CaseChange {
@@ -34,7 +32,7 @@ pub enum SnippetElement<'a> {
3432 },
3533 Placeholder {
3634 tabstop: usize,
37− value: Box<SnippetElement<'a>>,
35+ value: Vec<SnippetElement<'a>>,
3836 },
3937 Choice {
4038 tabstop: usize,
@@ -57,141 +55,108 @@ pub fn parse(s: &str) -> Result<Snippet<'_>> {
5755 parser::parse(s).map_err(|rest| anyhow!("Failed to parse snippet. Remaining input: {}", rest))
5856}
5957
60−pub fn into_transaction<'a>(
61− snippet: Snippet<'a>,
62− doc: &helix_core::Rope,
63− selection: &helix_core::Selection,
64− edit: &lsp_types::TextEdit,
65− line_ending: &str,
66− offset_encoding: OffsetEncoding,
58+fn render_elements(
59+ snippet_elements: &[SnippetElement<'_>],
60+ insert: &mut String,
61+ offset: &mut usize,
62+ tabstops: &mut Vec<(usize, (usize, usize))>,
63+ newline_with_offset: &String,
6764 include_placeholer: bool,
68−) -> helix_core::Transaction {
69− use helix_core::{smallvec, Range, Transaction};
65+) {
7066 use SnippetElement::*;
7167
72− let text = doc.slice(..);
73− let primary_cursor = selection.primary().cursor(text);
74−
75− let start_offset = match lsp_pos_to_pos(doc, edit.range.start, offset_encoding) {
76− Some(start) => start as i128 - primary_cursor as i128,
77− None => return Transaction::new(doc),
78− };
79− let end_offset = match lsp_pos_to_pos(doc, edit.range.end, offset_encoding) {
80− Some(end) => end as i128 - primary_cursor as i128,
81− None => return Transaction::new(doc),
82− };
83−
84− let newline_with_offset = format!(
85− "{line_ending}{blank:width$}",
86− width = edit.range.start.character as usize,
87− blank = ""
88− );
89−
90− let mut offset = 0;
91− let mut insert = String::new();
92− let mut tabstops: Vec<(usize, usize, usize)> = Vec::new();
93−
94− for element in snippet.elements {
68+ for element in snippet_elements {
9569 match element {
96− Text(text) => {
70+ &Text(text) => {
9771 // small optimization to avoid calling replace when it's unnecessary
9872 let text = if text.contains('\n') {
99− Cow::Owned(text.replace('\n', &newline_with_offset))
73+ Cow::Owned(text.replace('\n', newline_with_offset))
10074 } else {
10175 Cow::Borrowed(text)
10276 };
103− offset += text.chars().count();
77+ *offset += text.chars().count();
10478 insert.push_str(&text);
10579 }
106− Variable {
107− name: _name,
108− regex: None,
80+ &Variable {
81+ name: _,
82+ regex: _,
10983 r#default,
11084 } => {
11185 // TODO: variables. For now, fall back to the default, which defaults to "".
11286 let text = r#default.unwrap_or_default();
113− offset += text.chars().count();
87+ *offset += text.chars().count();
11488 insert.push_str(text);
11589 }
116− Tabstop { tabstop } => {
117− tabstops.push((tabstop, offset, offset));
90+ &Tabstop { tabstop } => {
91+ tabstops.push((tabstop, (*offset, *offset)));
11892 }
119− Placeholder { tabstop, value } => match value.as_ref() {
120− // https://doc.rust-lang.org/beta/unstable-book/language-features/box-patterns.html
121− // would make this a bit nicer
122− Text(text) => {
123− if include_placeholer {
124− let len_chars = text.chars().count();
125− tabstops.push((tabstop, offset, offset + len_chars + 1));
126− offset += len_chars;
127− insert.push_str(text);
128− } else {
129− tabstops.push((tabstop, offset, offset));
130− }
131− }
132− other => {
133− log::error!(
134− "Discarding snippet: generating a transaction for placeholder contents {:?} is unimplemented.",
135− other
93+ Placeholder {
94+ tabstop,
95+ value: inner_snippet_elements,
96+ } => {
97+ let start_offset = *offset;
98+ if include_placeholer {
99+ render_elements(
100+ inner_snippet_elements,
101+ insert,
102+ offset,
103+ tabstops,
104+ newline_with_offset,
105+ include_placeholer,
136106 );
137− return Transaction::new(doc);
138107 }
139− },
140− other => {
141− log::error!(
142− "Discarding snippet: generating a transaction for {:?} is unimplemented.",
143− other
144− );
145− return Transaction::new(doc);
108+ tabstops.push((*tabstop, (start_offset, *offset)));
109+ }
110+ &Choice {
111+ tabstop,
112+ choices: _,
113+ } => {
114+ // TODO: choices
115+ tabstops.push((tabstop, (*offset, *offset)));
146116 }
147117 }
148118 }
119+}
149120
150− let transaction = Transaction::change_by_selection(doc, selection, |range| {
151− let cursor = range.cursor(text);
152− (
153− (cursor as i128 + start_offset) as usize,
154− (cursor as i128 + end_offset) as usize,
155− Some(insert.clone().into()),
156− )
157− });
121+#[allow(clippy::type_complexity)] // only used one time
122+pub fn render(
123+ snippet: &Snippet<'_>,
124+ newline_with_offset: String,
125+ include_placeholer: bool,
126+) -> (String, Vec<SmallVec<[(usize, usize); 1]>>) {
127+ let mut insert = String::new();
128+ let mut tabstops = Vec::new();
129+ let mut offset = 0;
130+
131+ render_elements(
132+ &snippet.elements,
133+ &mut insert,
134+ &mut offset,
135+ &mut tabstops,
136+ &newline_with_offset,
137+ include_placeholer,
138+ );
158139
159140 // sort in ascending order (except for 0, which should always be the last one (per lsp doc))
160− tabstops.sort_unstable_by_key(|(n, _o1, _o2)| if *n == 0 { usize::MAX } else { *n });
141+ tabstops.sort_unstable_by_key(|(n, _)| if *n == 0 { usize::MAX } else { *n });
161142
162143 // merge tabstops with the same index (we take advantage of the fact that we just sorted them
163144 // above to simply look backwards)
164145 let mut ntabstops = Vec::<SmallVec<[(usize, usize); 1]>>::new();
165146 {
166147 let mut prev = None;
167− for (tabstop, o1, o2) in tabstops {
148+ for (tabstop, r) in tabstops {
168149 if prev == Some(tabstop) {
169150 let len_1 = ntabstops.len() - 1;
170− ntabstops[len_1].push((o1, o2));
151+ ntabstops[len_1].push(r);
171152 } else {
172153 prev = Some(tabstop);
173− ntabstops.push(smallvec![(o1, o2)]);
154+ ntabstops.push(smallvec![r]);
174155 }
175156 }
176157 }
177158
178− if let Some(first) = ntabstops.first() {
179− let cursor_offset = insert.chars().count() as i128 - (end_offset - start_offset);
180− let mut extra_offset = start_offset;
181− transaction.with_selection(selection.clone().transform_iter(|range| {
182− let cursor = range.cursor(text);
183− let iter = first.iter().map(move |first| {
184− Range::new(
185− (cursor as i128 + first.0 as i128 + extra_offset) as usize,
186− (cursor as i128 + first.1 as i128 + extra_offset) as usize,
187− )
188− });
189− extra_offset += cursor_offset;
190− iter
191− }))
192− } else {
193− transaction
194− }
159+ (insert, ntabstops)
195160}
196161
197162mod parser {
@@ -343,14 +308,15 @@ mod parser {
343308 fn placeholder<'a>() -> impl Parser<'a, Output = SnippetElement<'a>> {
344309 // TODO: why doesn't parse_as work?
345310 // let value = reparse_as(take_until(|c| c == '}'), anything());
311+ // TODO: fix this to parse nested placeholders (take until terminates too early)
346312 let value = filter_map(take_until(|c| c == '}'), |s| {
347− anything().parse(s).map(|parse_result| parse_result.1).ok()
313+ snippet().parse(s).map(|parse_result| parse_result.1).ok()
348314 });
349315
350316 map(seq!("${", digit(), ":", value, "}"), |seq| {
351317 SnippetElement::Placeholder {
352318 tabstop: seq.1,
353− value: Box::new(seq.3),
319+ value: seq.3.elements,
354320 }
355321 })
356322 }
@@ -430,7 +396,7 @@ mod parser {
430396 Text("match("),
431397 Placeholder {
432398 tabstop: 1,
433− value: Box::new(Text("Arg1")),
399+ value: vec!(Text("Arg1")),
434400 },
435401 Text(")")
436402 ]
@@ -447,12 +413,12 @@ mod parser {
447413 Text("local "),
448414 Placeholder {
449415 tabstop: 1,
450− value: Box::new(Text("var")),
416+ value: vec!(Text("var")),
451417 },
452418 Text(" = "),
453419 Placeholder {
454420 tabstop: 1,
455− value: Box::new(Text("value")),
421+ value: vec!(Text("value")),
456422 },
457423 ]
458424 }),
@@ -460,6 +426,19 @@ mod parser {
460426 )
461427 }
462428
429+ #[test]
430+ fn parse_tabstop_nested_in_placeholder() {
431+ assert_eq!(
432+ Ok(Snippet {
433+ elements: vec![Placeholder {
434+ tabstop: 1,
435+ value: vec!(Text("var, "), Tabstop { tabstop: 2 },),
436+ },]
437+ }),
438+ parse("${1:var, $2}")
439+ )
440+ }
441+
463442 #[test]
464443 fn parse_all() {
465444 assert_eq!(