@@ -11,17 +11,17 @@ pub enum CaseChange {
1111}
1212
1313#[derive(Debug, PartialEq, Eq)]
14−pub enum FormatItem<'a> {
14+pub enum FormatItem {
1515 Text(Tendril),
1616 Capture(usize),
1717 CaseChange(usize, CaseChange),
18− Conditional(usize, Option<&'a str>, Option<&'a str>),
18+ Conditional(usize, Option<Tendril>, Option<Tendril>),
1919}
2020
2121#[derive(Debug, PartialEq, Eq)]
22−pub struct Regex<'a> {
22+pub struct Regex {
2323 value: Tendril,
24− replacement: Vec<FormatItem<'a>>,
24+ replacement: Vec<FormatItem>,
2525 options: Tendril,
2626}
2727
@@ -40,8 +40,8 @@ pub enum SnippetElement<'a> {
4040 },
4141 Variable {
4242 name: &'a str,
43− default: Option<&'a str>,
44− regex: Option<Regex<'a>>,
43+ default: Option<Vec<SnippetElement<'a>>>,
44+ regex: Option<Regex>,
4545 },
4646 Text(Tendril),
4747}
@@ -77,15 +77,20 @@ fn render_elements(
7777 *offset += text.chars().count();
7878 insert.push_str(&text);
7979 }
80− &Variable {
80+ Variable {
8181 name: _,
8282 regex: _,
8383 r#default,
8484 } => {
8585 // TODO: variables. For now, fall back to the default, which defaults to "".
86− let text = r#default.unwrap_or_default();
87− *offset += text.chars().count();
88− insert.push_str(text);
86+ render_elements(
87+ r#default.as_deref().unwrap_or_default(),
88+ insert,
89+ offset,
90+ tabstops,
91+ newline_with_offset,
92+ include_placeholer,
93+ );
8994 }
9095 &Tabstop { tabstop } => {
9196 tabstops.push((tabstop, (*offset, *offset)));
@@ -212,25 +217,28 @@ mod parser {
212217 }
213218
214219 const TEXT_ESCAPE_CHARS: &[char] = &['\\', '}', '$'];
215− const REPLACE_ESCAPE_CHARS: &[char] = &['\\', '}', '$', '/'];
216− const CHOICE_TEXT_ESCAPE_CHARS: &[char] = &['\\', '}', '$', '|', ','];
220+ const CHOICE_TEXT_ESCAPE_CHARS: &[char] = &['\\', '|', ','];
217221
218− fn text<'a>(escape_chars: &'static [char]) -> impl Parser<'a, Output = Tendril> {
222+ fn text<'a>(
223+ escape_chars: &'static [char],
224+ term_chars: &'static [char],
225+ ) -> impl Parser<'a, Output = Tendril> {
219226 move |input: &'a str| {
220− let mut chars = input.char_indices();
227+ let mut chars = input.char_indices().peekable();
221228 let mut res = Tendril::new();
222229 while let Some((i, c)) = chars.next() {
223230 match c {
224231 '\\' => {
225− if let Some((_, c)) = chars.next() {
232+ if let Some(&(_, c)) = chars.peek() {
226233 if escape_chars.contains(&c) {
234+ chars.next();
227235 res.push(c);
228236 continue;
229237 }
230238 }
231− return Ok((&input[i..], res));
239+ res.push('\\');
232240 }
233− c if escape_chars.contains(&c) => return Ok((&input[i..], res)),
241+ c if term_chars.contains(&c) => return Ok((&input[i..], res)),
234242 c => res.push(c),
235243 }
236244 }
@@ -253,7 +261,7 @@ mod parser {
253261 )
254262 }
255263
256− fn format<'a>() -> impl Parser<'a, Output = FormatItem<'a>> {
264+ fn format<'a>() -> impl Parser<'a, Output = FormatItem> {
257265 use FormatItem::*;
258266
259267 choice!(
@@ -267,7 +275,7 @@ mod parser {
267275 }),
268276 // '${' int ':+' if '}'
269277 map(
270− seq!("${", digit(), ":+", take_until(|c| c == '}'), "}"),
278+ seq!("${", digit(), ":+", text(TEXT_ESCAPE_CHARS, &['}']), "}"),
271279 |seq| { Conditional(seq.1, Some(seq.3), None) }
272280 ),
273281 // '${' int ':?' if ':' else '}'
@@ -276,9 +284,9 @@ mod parser {
276284 "${",
277285 digit(),
278286 ":?",
279− take_until(|c| c == ':'),
287+ text(TEXT_ESCAPE_CHARS, &[':']),
280288 ":",
281− take_until(|c| c == '}'),
289+ text(TEXT_ESCAPE_CHARS, &['}']),
282290 "}"
283291 ),
284292 |seq| { Conditional(seq.1, Some(seq.3), Some(seq.5)) }
@@ -290,7 +298,7 @@ mod parser {
290298 digit(),
291299 ":",
292300 optional("-"),
293− take_until(|c| c == '}'),
301+ text(TEXT_ESCAPE_CHARS, &['}']),
294302 "}"
295303 ),
296304 |seq| { Conditional(seq.1, None, Some(seq.4)) }
@@ -298,19 +306,24 @@ mod parser {
298306 )
299307 }
300308
301− fn regex<'a>() -> impl Parser<'a, Output = Regex<'a>> {
309+ fn regex<'a>() -> impl Parser<'a, Output = Regex> {
302310 map(
303311 seq!(
304312 "/",
305313 // TODO parse as ECMAScript and convert to rust regex
306− non_empty(text(&['/', '\\'])),
314+ non_empty(text(&['/'], &['/'])),
307315 "/",
308316 one_or_more(choice!(
309317 format(),
310− map(text(REPLACE_ESCAPE_CHARS), FormatItem::Text)
318+ // text doesn't parse $, if format fails we just accept the $ as text
319+ map("$", |_| FormatItem::Text("$".into())),
320+ map(text(&['\\', '/'], &['/', '$']), FormatItem::Text),
311321 )),
312322 "/",
313− text(&['}', '\\',]),
323+ // vscode really doesn't allow escaping } here
324+ // so it's impossible to write a regex escape containing a }
325+ // we can consider deviating here and allowing the escape
326+ text(&[], &['}']),
314327 ),
315328 |(_, value, _, replacement, _, options)| Regex {
316329 value,
@@ -341,7 +354,7 @@ mod parser {
341354 // The example there contains both a placeholder text and a nested placeholder
342355 // which indicates a list. Looking at the VSCode sourcecode, the placeholder
343356 // is indeed parsed as zero_or_more so the grammar is simply incorrect here
344− zero_or_more(anything(TEXT_ESCAPE_CHARS)),
357+ zero_or_more(anything(TEXT_ESCAPE_CHARS, true)),
345358 "}"
346359 ),
347360 |seq| SnippetElement::Placeholder {
@@ -357,7 +370,7 @@ mod parser {
357370 "${",
358371 digit(),
359372 "|",
360− sep(text(CHOICE_TEXT_ESCAPE_CHARS), ","),
373+ sep(text(CHOICE_TEXT_ESCAPE_CHARS, &['|', ',']), ","),
361374 "|}",
362375 ),
363376 |seq| SnippetElement::Choice {
@@ -377,7 +390,13 @@ mod parser {
377390 }),
378391 // ${var:default}
379392 map(
380− seq!("${", var(), ":", take_until(|c| c == '}'), "}",),
393+ seq!(
394+ "${",
395+ var(),
396+ ":",
397+ zero_or_more(anything(TEXT_ESCAPE_CHARS, true)),
398+ "}",
399+ ),
381400 |values| SnippetElement::Variable {
382401 name: values.1,
383402 default: Some(values.3),
@@ -395,22 +414,27 @@ mod parser {
395414 )
396415 }
397416
398− fn anything<'a>(escape_chars: &'static [char]) -> impl Parser<'a, Output = SnippetElement<'a>> {
417+ fn anything<'a>(
418+ escape_chars: &'static [char],
419+ end_at_brace: bool,
420+ ) -> impl Parser<'a, Output = SnippetElement<'a>> {
421+ let term_chars: &[_] = if end_at_brace { &['$', '}'] } else { &['$'] };
399422 move |input: &'a str| {
400423 let parser = choice!(
401424 tabstop(),
402425 placeholder(),
403426 choice(),
404427 variable(),
405− map(text(escape_chars), SnippetElement::Text)
428+ map("$", |_| SnippetElement::Text("$".into())),
429+ map(text(escape_chars, term_chars), SnippetElement::Text),
406430 );
407431 parser.parse(input)
408432 }
409433 }
410434
411435 fn snippet<'a>() -> impl Parser<'a, Output = Snippet<'a>> {
412− map(one_or_more(anything(TEXT_ESCAPE_CHARS)), |parts| Snippet {
413− elements: parts,
436+ map(one_or_more(anything(TEXT_ESCAPE_CHARS, false)), |parts| {
437+ Snippet { elements: parts }
414438 })
415439 }
416440
@@ -452,8 +476,13 @@ mod parser {
452476 }
453477
454478 #[test]
455− fn parse_unterminated_placeholder_error() {
456− assert_eq!(Err("${1:)"), parse("match(${1:)"))
479+ fn unterminated_placeholder() {
480+ assert_eq!(
481+ Ok(Snippet {
482+ elements: vec![Text("match(".into()), Text("$".into()), Text("{1:)".into())]
483+ }),
484+ parse("match(${1:)")
485+ )
457486 }
458487
459488 #[test]
@@ -542,7 +571,7 @@ mod parser {
542571 Text(" ".into()),
543572 Variable {
544573 name: "name",
545− default: Some("foo"),
574+ default: Some(vec![Text("foo".into())]),
546575 regex: None
547576 },
548577 Text(" ".into()),
@@ -572,12 +601,56 @@ mod parser {
572601 default: None,
573602 regex: Some(Regex {
574603 value: "(.*).+$".into(),
575− replacement: vec![FormatItem::Capture(1)],
604+ replacement: vec![FormatItem::Capture(1), FormatItem::Text("$".into())],
576605 options: Tendril::new(),
577606 }),
578607 }]
579608 }),
580− parse("${TM_FILENAME/(.*).+$/$1/}")
609+ parse("${TM_FILENAME/(.*).+$/$1$/}")
610+ );
611+ }
612+
613+ #[test]
614+ fn rust_macro() {
615+ assert_eq!(
616+ Ok(Snippet {
617+ elements: vec![
618+ Text("macro_rules! ".into()),
619+ Tabstop { tabstop: 1 },
620+ Text(" {\n (".into()),
621+ Tabstop { tabstop: 2 },
622+ Text(") => {\n ".into()),
623+ Tabstop { tabstop: 0 },
624+ Text("\n };\n}".into())
625+ ]
626+ }),
627+ parse("macro_rules! $1 {\n ($2) => {\n $0\n };\n}")
628+ );
629+ }
630+ #[test]
631+ fn robust_parsing() {
632+ assert_eq!(
633+ Ok(Snippet {
634+ elements: vec![
635+ Text("$".into()),
636+ Text("{}".into()),
637+ Text("$".into()),
638+ Text("\\a$}\\".into()),
639+ ]
640+ }),
641+ parse("${}$\\a\\$\\}\\\\")
642+ );
643+ assert_eq!(
644+ Ok(Snippet {
645+ elements: vec![
646+ Placeholder {
647+ tabstop: 1,
648+ value: vec![Text("$".into()), Text("{".into())]
649+ },
650+ Text("}".into())
651+ ]
652+ }),
653+ parse("${1:${}}")
581654 );
582655 }
583656 }