Skip to content
This repository was archived by the owner on Dec 15, 2022. It is now read-only.

Commit e247bbd

Browse files
committed
start on new syntax
1 parent 36054ba commit e247bbd

File tree

3 files changed

+198
-82
lines changed

3 files changed

+198
-82
lines changed

lib/snippet-body-old.pegjs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
{
2+
// Joins all consecutive strings in a collection without clobbering any
3+
// non-string members.
4+
function coalesce (parts) {
5+
const result = [];
6+
for (let i = 0; i < parts.length; i++) {
7+
const part = parts[i];
8+
const ri = result.length - 1;
9+
if (typeof part === 'string' && typeof result[ri] === 'string') {
10+
result[ri] = result[ri] + part;
11+
} else {
12+
result.push(part);
13+
}
14+
}
15+
return result;
16+
}
17+
18+
function flatten (parts) {
19+
return parts.reduce(function (flat, rest) {
20+
return flat.concat(Array.isArray(rest) ? flatten(rest) : rest);
21+
}, []);
22+
}
23+
}
24+
bodyContent = content:(tabStop / bodyContentText)* { return content; }
25+
bodyContentText = text:bodyContentChar+ { return text.join(''); }
26+
bodyContentChar = escaped / !tabStop char:. { return char; }
27+
28+
escaped = '\\' char:. { return char; }
29+
tabStop = tabStopWithTransformation / tabStopWithPlaceholder / tabStopWithoutPlaceholder / simpleTabStop
30+
31+
simpleTabStop = '$' index:[0-9]+ {
32+
return { index: parseInt(index.join("")), content: [] };
33+
}
34+
tabStopWithoutPlaceholder = '${' index:[0-9]+ '}' {
35+
return { index: parseInt(index.join("")), content: [] };
36+
}
37+
tabStopWithPlaceholder = '${' index:[0-9]+ ':' content:placeholderContent '}' {
38+
return { index: parseInt(index.join("")), content: content };
39+
}
40+
tabStopWithTransformation = '${' index:[0-9]+ substitution:transformationSubstitution '}' {
41+
return {
42+
index: parseInt(index.join(""), 10),
43+
content: [],
44+
substitution: substitution
45+
};
46+
}
47+
48+
placeholderContent = content:(tabStop / placeholderContentText / variable )* { return flatten(content); }
49+
placeholderContentText = text:placeholderContentChar+ { return coalesce(text); }
50+
placeholderContentChar = escaped / placeholderVariableReference / !tabStop !variable char:[^}] { return char; }
51+
52+
placeholderVariableReference = '$' digit:[0-9]+ {
53+
return { index: parseInt(digit.join(""), 10), content: [] };
54+
}
55+
56+
variable = '${' variableContent '}' {
57+
return ''; // we eat variables and do nothing with them for now
58+
}
59+
variableContent = content:(variable / variableContentText)* { return content; }
60+
variableContentText = text:variableContentChar+ { return text.join(''); }
61+
variableContentChar = !variable char:('\\}' / [^}]) { return char; }
62+
63+
escapedForwardSlash = pair:'\\/' { return pair; }
64+
65+
// A pattern and replacement for a transformed tab stop.
66+
transformationSubstitution = '/' find:(escapedForwardSlash / [^/])* '/' replace:formatString* '/' flags:[imy]* {
67+
let reFind = new RegExp(find.join(''), flags.join('') + 'g');
68+
return { find: reFind, replace: replace[0] };
69+
}
70+
71+
formatString = content:(formatStringEscape / formatStringReference / escapedForwardSlash / [^/])+ {
72+
return content;
73+
}
74+
// Backreferencing a substitution. Different from a tab stop.
75+
formatStringReference = '$' digits:[0-9]+ {
76+
return { backreference: parseInt(digits.join(''), 10) };
77+
};
78+
// One of the special control flags in a format string for case folding and
79+
// other tasks.
80+
formatStringEscape = '\\' flag:[ULulErn$] {
81+
return { escape: flag };
82+
}

lib/snippet-body.pegjs

Lines changed: 105 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,105 @@
1-
{
2-
// Joins all consecutive strings in a collection without clobbering any
3-
// non-string members.
4-
function coalesce (parts) {
5-
const result = [];
6-
for (let i = 0; i < parts.length; i++) {
7-
const part = parts[i];
8-
const ri = result.length - 1;
9-
if (typeof part === 'string' && typeof result[ri] === 'string') {
10-
result[ri] = result[ri] + part;
11-
} else {
12-
result.push(part);
13-
}
14-
}
15-
return result;
16-
}
17-
18-
function flatten (parts) {
19-
return parts.reduce(function (flat, rest) {
20-
return flat.concat(Array.isArray(rest) ? flatten(rest) : rest);
21-
}, []);
22-
}
23-
}
24-
bodyContent = content:(tabStop / bodyContentText)* { return content; }
25-
bodyContentText = text:bodyContentChar+ { return text.join(''); }
26-
bodyContentChar = escaped / !tabStop char:. { return char; }
27-
28-
escaped = '\\' char:. { return char; }
29-
tabStop = tabStopWithTransformation / tabStopWithPlaceholder / tabStopWithoutPlaceholder / simpleTabStop
30-
31-
simpleTabStop = '$' index:[0-9]+ {
32-
return { index: parseInt(index.join("")), content: [] };
33-
}
34-
tabStopWithoutPlaceholder = '${' index:[0-9]+ '}' {
35-
return { index: parseInt(index.join("")), content: [] };
36-
}
37-
tabStopWithPlaceholder = '${' index:[0-9]+ ':' content:placeholderContent '}' {
38-
return { index: parseInt(index.join("")), content: content };
39-
}
40-
tabStopWithTransformation = '${' index:[0-9]+ substitution:transformationSubstitution '}' {
41-
return {
42-
index: parseInt(index.join(""), 10),
43-
content: [],
44-
substitution: substitution
45-
};
46-
}
47-
48-
placeholderContent = content:(tabStop / placeholderContentText / variable )* { return flatten(content); }
49-
placeholderContentText = text:placeholderContentChar+ { return coalesce(text); }
50-
placeholderContentChar = escaped / placeholderVariableReference / !tabStop !variable char:[^}] { return char; }
51-
52-
placeholderVariableReference = '$' digit:[0-9]+ {
53-
return { index: parseInt(digit.join(""), 10), content: [] };
54-
}
55-
56-
variable = '${' variableContent '}' {
57-
return ''; // we eat variables and do nothing with them for now
58-
}
59-
variableContent = content:(variable / variableContentText)* { return content; }
60-
variableContentText = text:variableContentChar+ { return text.join(''); }
61-
variableContentChar = !variable char:('\\}' / [^}]) { return char; }
62-
63-
escapedForwardSlash = pair:'\\/' { return pair; }
64-
65-
// A pattern and replacement for a transformed tab stop.
66-
transformationSubstitution = '/' find:(escapedForwardSlash / [^/])* '/' replace:formatString* '/' flags:[imy]* {
67-
let reFind = new RegExp(find.join(''), flags.join('') + 'g');
68-
return { find: reFind, replace: replace[0] };
69-
}
70-
71-
formatString = content:(formatStringEscape / formatStringReference / escapedForwardSlash / [^/])+ {
72-
return content;
73-
}
74-
// Backreferencing a substitution. Different from a tab stop.
75-
formatStringReference = '$' digits:[0-9]+ {
76-
return { backreference: parseInt(digits.join(''), 10) };
77-
};
78-
// One of the special control flags in a format string for case folding and
79-
// other tasks.
80-
formatStringEscape = '\\' flag:[ULulErn$] {
81-
return { escape: flag };
82-
}
1+
/*
2+
3+
Target grammar:
4+
5+
(Based on VS Code and TextMate, with particular emphasis on supporting LSP snippets)
6+
See https://microsoft.github.io/language-server-protocol/specification#snippet_syntax
7+
8+
any ::= (text | tabstop | choice | variable)*
9+
10+
text ::= anything that's not something else
11+
12+
tabstop ::= '$' int | '${' int '}' | '${' int transform '}' | '${' int ':' any '}'
13+
14+
choice ::= '${' int '|' text (',' text)* '|}'
15+
16+
variable ::= '$' var | '${' var '}' | '${' var ':' any '}' | '${' var transform '}'
17+
18+
transform ::= '/' regex '/' replace '/' options
19+
20+
replace ::= (format | text)*
21+
22+
format ::= '$' int | '${' int '}' | '${' int ':' modifier '}' | '${' int ':+' if:replace '}' | '${' int ':?' if:replace ':' else:replace '}' | '${' int ':-' else:replace '}' | '${' int ':' else:replace '}'
23+
24+
regex ::= JS regex value
25+
26+
options ::= JS regex options // NOTE: Unrecognised options should be ignored for the best fault tolerance (can log a warning though)
27+
28+
var ::= [a-zA-Z_][a-zA-Z_0-9]*
29+
30+
int ::= [0-9]+
31+
32+
*/
33+
34+
// Grab anything that isn't \ or $, then try to build a special node out of it, and (at the top level) if that fails then just accept it as text
35+
topLevelContent = content:(text / escapedTopLevel / tabStop / choice / variable / any)* { return content; }
36+
37+
tabStopContent = content:(text / escapedTabStop / tabStop / choice / variable)* { return content; }
38+
39+
tabStop = tabStopSimple / tabStopWithoutPlaceholder / tabStopWithPlaceholder / tabStopWithTransform
40+
41+
tabStopSimple = '$' n:integer { return { index: n }; }
42+
43+
tabStopWithoutPlaceholder = '${' n:integer '}' { return { index: n }; }
44+
45+
tabStopWithPlaceholder = '${' n:integer ':' content:tabStopContent '}' { return { index: n, content }; }
46+
47+
tabStopWithTransform = '${' n:integer t:transformation '}' { return { index: n, transformation: t }; }
48+
49+
transformation = '/' capture:regexString '/' replace:replace '/' flags:flags { return { capture, flags, replace }; }
50+
51+
regexString = r:[^/]* { return r.join(""); }
52+
53+
replace = (format / replaceText)*
54+
55+
format = formatSimple / formatPlain / formatWithModifier / formatWithIf / formatWithIfElse / formatWithElse
56+
57+
formatSimple = '$' n:integer { return { backreference: n }; }
58+
59+
formatPlain = '${' n:integer '}' { return { backreference: n }; }
60+
61+
formatWithModifier = '${' n:integer ':' modifier:modifier '}' { return { backreference: n, modifier }; }
62+
63+
formatWithIf = '${' n:integer ':+' ifContent:replace '}' { return { backreference: n, ifContent }; }
64+
65+
formatWithIfElse = '${' n:integer ':?' ifContent:replace ':' elseContent:replace '}' { return { backreference: n, ifContent, elseContent }; }
66+
67+
formatWithElse = '${' n:integer ':' '-'? elseContent:replace { return { backreference: n, elseContent }; }
68+
69+
modifier = '/' modifier:var { return modifier; }
70+
71+
flags = f:[a-z]* { return f; }
72+
73+
choice = '${' n:integer '|' choiceText (',' choiceText)* '|}'
74+
75+
variable = variableSimple / variablePlain / variableWithPlaceholder / variableWithTransform
76+
77+
variableSimple = '$' v:var { return { variable: v }; }
78+
79+
variablePlain = '${' v:var '}' { return { variable: v }; }
80+
81+
variableWithPlaceholder = '${' v:var ':' content:tabStopContent '}' { return { variable: v, content }; }
82+
83+
variableWithTransform = '${' v:var t:transformation '}' { return { variable: v, transformation: t }; }
84+
85+
text = t:[^$\\}]+ { return t.join("") }
86+
87+
choiceText = t:[^,|]+ { return t.join(""); }
88+
89+
replaceText = t:[^$\\}/]+ { return t.join(""); }
90+
91+
// Match an escaped character. The set of characters that can be escaped is based on context, generally restricted to the minimum set that enables expressing any text content
92+
escapedTopLevel = '\\' c:[$\\}] { return c; }
93+
94+
escapedTabStop = escapedTopLevel
95+
96+
escapedChoice = '\\' c:[$\\,|] { return c; }
97+
98+
// Match nonnegative integers like those used for tab stop ordering
99+
integer = digits:[0-9]+ { return parseInt(digits.join(""), 10); }
100+
101+
// Match variable names like TM_SELECTED_TEXT
102+
var = a:[a-zA-Z_] b:[a-zA-Z_0-9]* { return a + b.join(""); }
103+
104+
// Match any single character. Useful to resolve any parse errors where something that looked like it would be special had malformed syntax.
105+
any = a:. { return a; }

package.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,16 @@
2929
},
3030
"devDependencies": {
3131
"coffeelint": "^1.9.7"
32+
},
33+
"configSchema": {
34+
"snippetSyntax": {
35+
"type": "enum",
36+
"description": "Configures the syntax used for snippets. LSP is mostly a superset of original, with support for more features.",
37+
"default": "LSP",
38+
"enum": [
39+
"LSP",
40+
"original"
41+
]
42+
}
3243
}
3344
}

0 commit comments

Comments
 (0)