@@ -633,31 +633,378 @@ mod parser {
633 parse("macro_rules! $1 {\n ($2) => {\n $0\n };\n}")
633 parse("macro_rules! $1 {\n ($2) => {\n $0\n };\n}")
637+ fn assert_text(snippet: &str, parsed_text: &str) {
638+ let res = parse(snippet).unwrap();
639+ let text = crate::snippet::render(&res, "\n", true).0;
640+ assert_eq!(text, parsed_text)
637 fn robust_parsing() {
644 fn robust_parsing() {
638− assert_eq!(
645+ assert_text("$", "$");
639− Ok(Snippet {
646+ assert_text("\\\\$", "\\$");
640− elements: vec![
647+ assert_text("{", "{");
641− Text("$".into()),
648+ assert_text("\\}", "}");
642− Text("{}".into()),
649+ assert_text("\\abc", "\\abc");
643− Text("$".into()),
650+ assert_text("foo${f:\\}}bar", "foo}bar");
644− Text("\\a$}\\".into()),
651+ assert_text("\\{", "\\{");
645− ]
652+ assert_text("I need \\\\\\$", "I need \\$");
646− }),
653+ assert_text("\\", "\\");
647− parse("${}$\\a\\$\\}\\\\")
654+ assert_text("\\{{", "\\{{");
655+ assert_text("{{", "{{");
656+ assert_text("{{dd", "{{dd");
657+ assert_text("}}", "}}");
658+ assert_text("ff}}", "ff}}");
659+ assert_text("farboo", "farboo");
660+ assert_text("far{{}}boo", "far{{}}boo");
661+ assert_text("far{{123}}boo", "far{{123}}boo");
662+ assert_text("far\\{{123}}boo", "far\\{{123}}boo");
663+ assert_text("far{{id:bern}}boo", "far{{id:bern}}boo");
664+ assert_text("far{{id:bern {{basel}}}}boo", "far{{id:bern {{basel}}}}boo");
666+ "far{{id:bern {{id:basel}}}}boo",
667+ "far{{id:bern {{id:basel}}}}boo",
649− assert_eq!(
669+ assert_text(
650− Ok(Snippet {
670+ "far{{id:bern {{id2:basel}}}}boo",
651− elements: vec![
671+ "far{{id:bern {{id2:basel}}}}boo",
652− Placeholder {
672+ );
653− tabstop: 1,
673+ assert_text("${}$\\a\\$\\}\\\\", "${}$\\a$}\\");
654− value: vec![Text("$".into()), Text("{".into())]
674+ assert_text("farboo", "farboo");
675+ assert_text("far{{}}boo", "far{{}}boo");
676+ assert_text("far{{123}}boo", "far{{123}}boo");
677+ assert_text("far\\{{123}}boo", "far\\{{123}}boo");
678+ assert_text("far`123`boo", "far`123`boo");
679+ assert_text("far\\`123\\`boo", "far\\`123\\`boo");
680+ assert_text("\\$far-boo", "$far-boo");
683+ fn assert_snippet(snippet: &str, expect: &[SnippetElement]) {
684+ let parsed_snippet = parse(snippet).unwrap();
685+ assert_eq!(parsed_snippet.elements, expect.to_owned())
689+ fn parse_variable() {
690+ use SnippetElement::*;
699+ Text("-boo".into()),
724+ assert_snippet("$123", &[Tabstop { tabstop: 123 }]);
744+ Text("000_".into()),
750+ Text("_000".into()),
756+ fn parse_variable_transform() {
762+ value: Tendril::new(),
763+ replacement: Vec::new(),
764+ options: Tendril::new(),
770+ "${foo/regex/format/gmi}",
774+ value: "regex".into(),
775+ replacement: vec![FormatItem::Text("format".into())],
776+ options: "gmi".into(),
782+ "${foo/([A-Z][a-z])/format/}",
786+ value: "([A-Z][a-z])".into(),
787+ replacement: vec![FormatItem::Text("format".into())],
788+ options: Tendril::new(),
794+ // invalid regex TODO: reneable tests once we actually parse this regex flavour
796+ // "${foo/([A-Z][a-z])/format/GMI}",
797+ // "${foo/([A-Z][a-z])/format/GMI}",
800+ // "${foo/([A-Z][a-z])/format/funky}",
801+ // "${foo/([A-Z][a-z])/format/funky}",
803+ // assert_text("${foo/([A-Z][a-z]/format/}", "${foo/([A-Z][a-z]/format/}");
805+ "${foo/regex\\/format/options}",
806+ "${foo/regex\\/format/options}",
811+ "${foo/m\\/atch/$1/i}",
815+ value: "m/atch".into(),
816+ replacement: vec![FormatItem::Capture(1)],
817+ options: "i".into(),
824+ assert_text("${foo///", "${foo///");
825+ assert_text("${foo/regex/format/options", "${foo/regex/format/options");
829+ "${foo/.*/${0:fooo}/i}",
834+ replacement: vec![FormatItem::Conditional(0, None, Some("fooo".into()))],
835+ options: "i".into(),
846+ replacement: vec![FormatItem::Capture(1)],
847+ options: "i".into(),
858+ replacement: vec![FormatItem::Capture(1)],
859+ options: "i".into(),
865+ "${foo/.*/This-$1-encloses/i}",
871+ FormatItem::Text("This-".into()),
872+ FormatItem::Capture(1),
873+ FormatItem::Text("-encloses".into()),
875+ options: "i".into(),
881+ "${foo/.*/complex${1:else}/i}",
887+ FormatItem::Text("complex".into()),
888+ FormatItem::Conditional(1, None, Some("else".into())),
890+ options: "i".into(),
896+ "${foo/.*/complex${1:-else}/i}",
902+ FormatItem::Text("complex".into()),
903+ FormatItem::Conditional(1, None, Some("else".into())),
905+ options: "i".into(),
911+ "${foo/.*/complex${1:+if}/i}",
917+ FormatItem::Text("complex".into()),
918+ FormatItem::Conditional(1, Some("if".into()), None),
920+ options: "i".into(),
926+ "${foo/.*/complex${1:?if:else}/i}",
932+ FormatItem::Text("complex".into()),
933+ FormatItem::Conditional(1, Some("if".into()), Some("else".into())),
935+ options: "i".into(),
941+ "${foo/.*/complex${1:/upcase}/i}",
947+ FormatItem::Text("complex".into()),
948+ FormatItem::CaseChange(1, CaseChange::Upcase),
950+ options: "i".into(),
956+ "${TM_DIRECTORY/src\\//$1/}",
958+ name: "TM_DIRECTORY",
960+ value: "src/".into(),
961+ replacement: vec![FormatItem::Capture(1)],
962+ options: Tendril::new(),
968+ "${TM_SELECTED_TEXT/a/\\/$1/g}",
970+ name: "TM_SELECTED_TEXT",
973+ replacement: vec![FormatItem::Text("/".into()), FormatItem::Capture(1)],
974+ options: "g".into(),
980+ "${TM_SELECTED_TEXT/a/in\\/$1ner/g}",
982+ name: "TM_SELECTED_TEXT",
986+ FormatItem::Text("in/".into()),
987+ FormatItem::Capture(1),
988+ FormatItem::Text("ner".into()),
656− Text("}".into())
990+ options: "g".into(),
659− parse("${1:${}}")
995+ assert_snippet(
996+ "${TM_SELECTED_TEXT/a/end\\//g}",
998+ name: "TM_SELECTED_TEXT",
1001+ replacement: vec![FormatItem::Text("end/".into())],
1002+ options: "g".into(),
1008+ // TODO port more tests from https://github.com/microsoft/vscode/blob/dce493cb6e36346ef2714e82c42ce14fc461b15c/src/vs/editor/contrib/snippet/test/browser/snippetParser.test.ts