-
Notifications
You must be signed in to change notification settings - Fork 0
Syntax
The snippet syntax used by this package has its roots at least as far as 2005 TextMate. The core of this TextMate snippet syntax has been adapted by at least Atom, Sublime Text, and VS Code, with only minor variations between editors.
The following is a description of the syntax of snippets as provided by this package. The syntax is (mostly) a superset of the original snippets
package, adding in some features that have been available in other those editors for a long time. In future it will also offer a choice of parsing modes, in particular a 'strict' mode, to help future-proof your snippets against new syntax additions. The one guarantee offered by this package is that all syntax additions will try to involve $
in some way, so by escaping any $
symbols that are not already part of the syntax you minimise the chances of snippets breaking.
Most of a snippet body is plain text, until a $
is reached. $
is the base of every 'special' part of a snippet. The only other exception to everything as plain text is that \
will escape a following $
or \
(but will be inserted literally otherwise). So foo\$bar\\baz\hmm
will be expanded as foo$bar\baz\hmm
.
Tab stops are anything of the form $n
or ${n}
, where n
is an positive integer number. E.g., $1
, ${1000}
. They represent a place to put the cursor. Tab stops are grouped together by number; a snippet will cycle through the groups in ascending order (group numbers do not need to be consecutive), before finishing on the special $0
group.
Tab stops of the form ${n|foo,bar,baz|}
are treated as choices, where the comma separated list of plain text items are offered in the autocomplete popup. Choices belong to the tab stop group; stops with different sets of choices in the same group will offer the union of the choices. Choices do not restrict what you can type, they only offer the ability to autocomplete to them. While the syntax is parsed, the popup itself is not supported at this point in time.
Currently $
may not be escaped inside of choices, but this may be changed to allow for consistency (so you can escape $
anywhere), and it would allow for special syntax such as variables to be put into choices.
Tab stops also have the extended form ${n:placeholder}
, where n
is as before and placeholder
is arbitrary snippet contents (can contain plain text, tab stops, and the other features listed below). When made active, the cursor will select the placeholder contents. Typing would then delete the placeholder, or you can use the arrow keys to remove the selection or move to the next tab stop group. If you delete text containing tab stops inside of a placeholder, the expected behaviour is not yet well defined. Currently the stops are effectively 'pushed' to the edges of the deleted range, but it is also reasonable to delete them completely. If this behaviour is changed, a setting will be provided to configure it.
Tab stops alternatively can have the form ${n/find/replace/flags}
, where n
is as before, find
is a JS regular expression, replace
is a special snippet-like context, and flags
are the flags applied to the find
regex. They are applied upon leaving the active tab stop group.
The replace
section may contain a mix of plain text and formats. Formats, like tab stops, are started by $
. There are several kinds of format:
-
$n
,${n}
: Like tab stops, but then
is a backreference to the capture group of thefind
regex. Using this format will insert the capture group contents directly, or the empty string if it did not match. Note that$0
can be used for the entire matched text. -
${n:+if}
: The:+
form will check the contents of then
th capture group, and, if it succeeded, insertif
as though it was there directly.if
is equivalent to areplace
section, able to contain plain text and formats itself. Further regular expression transformations are not possible though; the formats in theif
section still refer to the capture groups of the tab stopfind
regex. -
${n:-else}
: Same as${n:+if}
, except the contents is used if the capture group failed to match. Note that the empty string is considered a valid match; considering(abc)|(.*)
, if the input is an empty string then the first capture group fails to match, but the second succeeds because it accepts the empty string. If the capture group succeeded, nothing is inserted. -
${n:?if:else}
: A combination of the if and else formats. -
${n:else}
An alternative form for${n:-else}
. Prefer to use the:-
version, as otherwise you risk accidentally turning it into an if format if the first character happens to be+
. -
${n:/name}
: A named transformation.name
is a word made of ASCII letters, digits, and underscores.name
may not start with a number. Inserts the capture group after being processed by thename
transformer. See this page for a list of builtin names and their effects.
There is an additional kind of format referred to as escaped modifiers. They are state based, and act directly on the replace
'output stream', so they are applied after named transformations and the effects still apply even if they were raised inside of a format. There are only 5 of these, with the following effects
-
\l
: Lowercase the next character -
\L
: Lowercase all the following characters -
\u
: Uppercase the next character -
\U
: Uppercase all the following characters -
\E
: Cancel any current escaped modifier effects
This group is like a tab stop in that it the cursor will go to it, it can have a placeholder, and you can have multiple of them. However once this group is reached, the snippet expansion mode is ended and you will no longer be able to cycle between tab stops. This also means that transformations are pointless, as the snippet mode will not be active to evaluate them. If your snippet does not have any $0
stops then one is implicitly inserted at the end of the snippet. If you do add some, then no implicit stop is inserted.
Variables are defined similarly to tab stops, except instead of a number they use a name. The name has the same restrictions as in named transforms: only ASCII letters and numbers are allowed, as well as underscores, and a name may not start with a number. Simple variables have the form $name
or ${name}
.
All variables in the snippet are evaluated at the time of expansion. Variables are either evaluated to a string which is inserted directly, or are not resolved and converted into tab stops. See this page for a list of builtin variables and their meanings.
Like tab stops, variables of the form ${name:placeholder}
can have arbitrary placeholder
contents. Unlike tab stops, the placeholder is only used if the variable fails to be resolved. The placeholder is used as an alternative to converting an unresolved variable into a tab stop. Otherwise, the variables position will be turned into a tab stop with the variable name as its placeholder. This tab stop will be reached after the proper tab stops, but before the $0
stop. Unresolved variables are treated as independent, even if they share a name; variables with the same name will receive different tab stops.
Variables have the same transformation support as tab stops, except all transformations are resolved upon snippet expansion (like the variable itself is). The syntax is ${name/find/replace/flags}
.
Inside of special syntax blocks like tab stops or formats, the general rule is that only the minimal number of characters necessary can be escaped. For example, you can escape }
inside of a tab stop placeholder, as otherwise it would preemptively end the placeholder. You can also escape :
in the if half of an if-else format. However, you cannot escape +
inside of the ${n:else}
format. The solution here if you want a leading +
is to use the ${n:-else}
form.