From 1a4221fd867c3a5e8245a9f584e75482ea2db19b Mon Sep 17 00:00:00 2001 From: Michael Lyons Date: Mon, 15 Jun 2026 11:12:19 -0400 Subject: [PATCH 01/17] Nest Syntax section --- docs/.vitepress/config.ts | 9 +++++++-- docs/reference/{ => syntax}/syntaxdefs_legacy.md | 0 2 files changed, 7 insertions(+), 2 deletions(-) rename docs/reference/{ => syntax}/syntaxdefs_legacy.md (100%) diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index c288cb87..360f1a37 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -187,8 +187,13 @@ export default defineConfig({ { text: 'Python API', link: '/reference/python_api.md' }, { text: 'Settings', link: '/reference/settings.md' }, { text: 'Symbols', link: '/reference/symbols.md' }, - { text: 'Syntax', link: 'https://www.sublimetext.com/docs/syntax.html' }, - { text: 'Syntax Definitions Legacy', link: '/reference/syntaxdefs_legacy.md' }, + { + text: 'Syntax', + items: [ + { text: 'Syntax Reference', link: 'https://www.sublimetext.com/docs/syntax.html' }, + { text: 'Legacy Syntax Formats', link: '/reference/syntax/syntaxdefs_legacy.md' }, + ], + }, { text: 'Keyboard Shortcuts', items: [ diff --git a/docs/reference/syntaxdefs_legacy.md b/docs/reference/syntax/syntaxdefs_legacy.md similarity index 100% rename from docs/reference/syntaxdefs_legacy.md rename to docs/reference/syntax/syntaxdefs_legacy.md From 6a6049aa8375745b3d3e611924193eac4c434cb1 Mon Sep 17 00:00:00 2001 From: Michael Lyons Date: Mon, 15 Jun 2026 13:26:55 -0400 Subject: [PATCH 02/17] Add a page about the syntax engine Sources: - Michael Lyons' description of tmLanguage https://stackoverflow.com/a/70007359/241211 - Nelo Mitranim's comment block in Go sublimehq/Packages#1662 --- docs/.vitepress/config.ts | 1 + docs/reference/syntax/engine.md | 149 ++++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 docs/reference/syntax/engine.md diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 360f1a37..1d971142 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -190,6 +190,7 @@ export default defineConfig({ { text: 'Syntax', items: [ + { text: 'Engine Overview', link: '/reference/syntax/engine.md' }, { text: 'Syntax Reference', link: 'https://www.sublimetext.com/docs/syntax.html' }, { text: 'Legacy Syntax Formats', link: '/reference/syntax/syntaxdefs_legacy.md' }, ], diff --git a/docs/reference/syntax/engine.md b/docs/reference/syntax/engine.md new file mode 100644 index 00000000..52a418d6 --- /dev/null +++ b/docs/reference/syntax/engine.md @@ -0,0 +1,149 @@ +--- +title: Syntax Engine Overview +--- + +# Syntax Engine Overview + +## TextMate + +Sublime Text originally used the TextMate format +for syntax definitions. +Their `tmLanguage` describes a stack-based system of regular expressions +to assign [scopes](#scopes) to specific sets of characters. +While Sublime Text now includes features beyond `tmLanguage`, +it is useful to understand its concepts first. +Many syntax definitions only use TextMate features, +and several other editors use TextMate-derived engines. + +This page uses the Sublime Syntax's names for keywords, +but the originals are available +on [the legacy page](syntaxdefs_legacy.html). + + +## Walkthrough + +1. The engine pushes the first stack frame, `main`. + It also set the current character + to the first character + of the unprocessed editor text. + +2. For the current character, + evaluate the regular expressions + in the current stack frame + from first to last + until one matches. + +3. Consume characters + in the matching regexp + and assign scopes to them. + +4. Optionally push or pop the stack frame. + The Sublime Text engine has a significant change + from TextMate, + wherein multiple stack frames can be pushed or popped at a time. + Sublime Text also supports replacing the current frame + with a different one. + +5. If none of the regular expressions + in the current frame match + at the current position in the file, + the engine will advance + to the next character of the file + and restart the list of regexps + in the current frame. + +6. Go to step 2 at the new character position. + +### Caveats + +1. Regular expressions that consume no characters + should change the stack. + If they do not, + the current character is advanced + as in step 5 + to avoid an infinite loop. + +2. Regular expressions do not match across line breaks. + + +## Reusing Matches + +### `contexts` + +It would be a mess to repeat relevant regular expressions +in each stack frame that needed them. +This is the purpose of the `contexts` section. +Contexts are sets of matches and other instructions +that a stack frame can `include` in its matches. +They are processed in the same order +as if they were `match` instructions +at the location of the `include` instruction. + +Contexts can also be pushed onto the stack +as a new frame. +This is why stack frames are often +colloquially referred to as "contexts," +including in Sublime Text's scope debugger. + +### `variables` + +Portions of regular expressions can also be saved +in the `variables` section and reused in multiple expressions. +Variables also make some ugly regexps readable. + +### Match reuse best practice + +Well-designed syntax definitions will define utility contexts +that `include` equivalent things together for re-usability: + +- A normal programming language will have things like + - A **statements** group of all things that can be directly executed. + This then may or may not (language-dependent) include… + - An **expressions** group of things + that you can put on the right-hand-side of an assignment, + which will definitely include… + - An **atoms** group of strings, numbers, chars, etc. + that might also be valid statements, + but that also depends on your language. + - **function-definitions** probably won't be in **expressions** + (unless they are lambdas) + but probably _would_ be in **statements**. + Function definitions might push into a context + that lets you `return` and so on. + +- A markup language might have + - An **inlines** group to keep track of all the markup + one can have within a block. + - A **blocks** group to hold lists, quotes, paragraphs, headers. + - … + + + + + + + + + +## Scopes + +Scopes are the names or tags for tokens +matched by a syntax definition. + +After scopes have been assigned by a syntax definition, +the user's [Color Scheme][] maps them +to colors and styles to apply to the text. +Using [established conventions for scopes][scope-names] +helps preserve consistency +for colors and styles +across multiple languages that a user may have installed. + +Scopes are also used for + +- indexing definitions, references, and headings +- context for which completions are available +- context for keybindings +- comment style + +[color scheme]: https://www.sublimetext.com/docs/color_schemes.html +[scope-names]: https://www.sublimetext.com/docs/scope_naming.html From 0abe5fea3b94cb546e01e7536ce3d5c2679244ee Mon Sep 17 00:00:00 2001 From: Michael Lyons Date: Mon, 15 Jun 2026 16:51:40 -0400 Subject: [PATCH 03/17] WIP Shuffle syntax guides --- docs/.vitepress/config.ts | 9 +- docs/guide/extensibility/syntax/index.md | 128 ++++++++++++++++++ .../tutorial_legacy.md} | 63 +-------- docs/reference/syntax/engine.md | 2 +- 4 files changed, 139 insertions(+), 63 deletions(-) create mode 100644 docs/guide/extensibility/syntax/index.md rename docs/guide/extensibility/{syntaxdefs.md => syntax/tutorial_legacy.md} (86%) diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 1d971142..866ffd80 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -132,7 +132,14 @@ export default defineConfig({ ], }, { text: 'Snippets', link: '/guide/extensibility/snippets.md' }, - { text: 'Syntax Definitions', link: '/guide/extensibility/syntaxdefs.md' }, + { + text: 'Syntax Definitions', + link: '/guide/extensibility/syntax/', + items: [ + // { text: 'Tutorial', link: '/guide/extensibility/syntax/tutorial.md' }, + { text: 'Tutorial (Legacy)', link: '/guide/extensibility/syntax/tutorial_legacy.md' }, + ], + }, { text: 'Troubleshooting', link: '/guide/extensibility/troubleshooting.md' }, ], }, diff --git a/docs/guide/extensibility/syntax/index.md b/docs/guide/extensibility/syntax/index.md new file mode 100644 index 00000000..3cf86c69 --- /dev/null +++ b/docs/guide/extensibility/syntax/index.md @@ -0,0 +1,128 @@ +--- +title: Syntax Definitions +--- + + +# Syntax Definitions + +Syntax definitions make Sublime Text aware +of programming and markup languages. +Most noticeably, they work together with color schemes +to provide syntax highlighting. +Syntax definitions define *[scopes](#scopes)* +that divide the text in a buffer into named regions. +Several editing features in Sublime Text make extensive use +of this fine-grained contextual information. + +Essentially, syntax definitions consist +of regular expressions used to find text, +as well as more-or-less arbitrary, dot-separated strings +called *scopes* or *scope names*. +For every occurrence of a given regular expression, +Sublime Text gives the matching text its corresponding *scope name*. + + +## Syntax Definition Formats + +### `sublime-syntax` + +For Sublime Text 3 (Build 3084), +a new syntax definition format has been added +with the `.sublime-syntax` extension. +This is based on YAML +and uses slightly different keywords. + +It is highly encouraged to be used +in favor of the legacy TextMate format below, +unless compatibility with older versions +or other editors is desired. + +Reference documentation is available +on the [official website][sublime-syntax]. + +[sublime-syntax]: https://www.sublimetext.com/docs/syntax.html + + +### TextMate `tmLanguage` and derivatives + +Sublime Text originally used TextMate language files +(with the `.tmLanguage` extension) +in [property list][plist] (PList) format +for syntax definitions. +Because the XML of PList was cumbersome, +many developers used a YAML or JSON representation +and compiled it to PList afterward. + +Reference documentation is available +at [TextMate Syntax Definitions](/reference/syntax/syntaxdefs_legacy.md) + +[plist]: https://en.wikipedia.org/wiki/Property_list + + +## Scopes + +::: info See Also +[Scope Naming][] +: Official documentation on assigning scopes to code, + including a section on Color Schemes + +[Selectors][] +: Official documentation on scope selectors +::: + +Scopes are a key concept in Sublime Text, +inherited from the macOS editor TextMate. +Essentially, scopes are named text regions in a buffer. +They don't do anything by themselves, +but Sublime Text peeks at them when it needs contextual information. + +For instance, when you trigger a snippet, Sublime Text checks the scope +bound to the snippet and looks at the caret's position in the file. If +the caret's current position matches the snippet's scope selector, +Sublime Text fires it off. Otherwise, nothing happens. + +Furthermore, [Color Schemes][] make extensive use of scopes +to style every aspect of a language in the desired color. + +::: tip Info +There's a slight difference between *scopes* and *[scope selectors][]*: Scopes +are the names defined in a syntax definition, while scope selectors are used +in items like snippets and key bindings to target scopes. When creating a +new syntax definition, you care about scopes; when you want to constrain a +snippet to a certain scope, you use a scope selector. +::: + +Scopes can be nested to allow for a high degree of granularity. You can drill +down the hierarchy very much like with CSS selectors. For instance, thanks to +scope selectors, you could have a key binding activated only within single +quoted strings in Python source code, but not inside single quoted strings in +any other language. + +[scope naming]: https://www.sublimetext.com/docs/scope_naming.html +[scope selectors]:https://www.sublimetext.com/docs/selectors.html +[color schemes]: /guide/customization/color_schemes.md + + +## How Syntax Definitions Work + +At their core, syntax definitions are arrays of regular expressions +paired with scope names. Sublime Text will try to match these patterns +against a buffer's text and attach the corresponding scope name to all +occurrences. These pairs of regular expressions and scope names are +known as *rules*. + +Rules are applied in order, one line at a time. Rules are applied in the +following order: + +1. The rule that matches at the first position in a line +2. The rule that comes first in the array + +Each rule consumes the matched text region, which therefore will be +excluded from the next rule's matching attempt (save for a few +exceptions). In practical terms, this means that you should take care to +go from more specific rules to more general ones when you create a new +syntax definition. Otherwise, a greedy regular expression might swallow +parts you'd like to have styled differently. + +Syntax definitions from separate files can be combined, and they can be +recursively applied too. diff --git a/docs/guide/extensibility/syntaxdefs.md b/docs/guide/extensibility/syntax/tutorial_legacy.md similarity index 86% rename from docs/guide/extensibility/syntaxdefs.md rename to docs/guide/extensibility/syntax/tutorial_legacy.md index 8b2321fe..68b0cd82 100644 --- a/docs/guide/extensibility/syntaxdefs.md +++ b/docs/guide/extensibility/syntax/tutorial_legacy.md @@ -1,8 +1,8 @@ --- -title: Syntax Definitions +title: Syntax Definition Tutorial (Legacy) --- -# Syntax Definitions +# Syntax Definition Tutorial (Legacy) Syntax definitions make Sublime Text aware of programming and markup languages. Most noticeably, they work together with colors to provide syntax highlighting. @@ -65,65 +65,6 @@ XML, but always keep in mind their differing needs in regards to escape sequences, many XML tags etc. -## Scopes - -Scopes are a key concept in Sublime Text. Essentially, they are named -text regions in a buffer. They don't do anything by themselves, but -Sublime Text peeks at them when it needs contextual information. - -For instance, when you trigger a snippet, Sublime Text checks the scope -bound to the snippet and looks at the caret's position in the file. If -the caret's current position matches the snippet's scope selector, -Sublime Text fires it off. Otherwise, nothing happens. - -::: tip Info -There's a slight difference between *scopes* and *scope selectors*: Scopes -are the names defined in a syntax definition, while scope selectors are used -in items like snippets and key bindings to target scopes. When creating a -new syntax definition, you care about scopes; when you want to constrain a -snippet to a certain scope, you use a scope selector. -::: - -Scopes can be nested to allow for a high degree of granularity. You can drill -down the hierarchy very much like with CSS selectors. For instance, thanks to -scope selectors, you could have a key binding activated only within single -quoted strings in Python source code, but not inside single quoted strings in -any other language. - -Sublime Text inherits the idea of scopes from Textmate, a text editor for Mac. -[Textmate's online manual][] contains further information about scope selectors -that's useful for Sublime Text users too. In particular, Color Schemes make -extensive use of scopes to style every aspect of a language in the desired -color. - -[Textmate's online manual]: https://manual.macromates.com/en/scope_selectors - - -## How Syntax Definitions Work - -At their core, syntax definitions are arrays of regular expressions -paired with scope names. Sublime Text will try to match these patterns -against a buffer's text and attach the corresponding scope name to all -occurrences. These pairs of regular expressions and scope names are -known as *rules*. - -Rules are applied in order, one line at a time. Rules are applied in the -following order: - -1. The rule that matches at the first position in a line -2. The rule that comes first in the array - -Each rule consumes the matched text region, which therefore will be -excluded from the next rule's matching attempt (save for a few -exceptions). In practical terms, this means that you should take care to -go from more specific rules to more general ones when you create a new -syntax definition. Otherwise, a greedy regular expression might swallow -parts you'd like to have styled differently. - -Syntax definitions from separate files can be combined, and they can be -recursively applied too. - - ## Your First Syntax Definition By way of example, let's create a syntax definition for Sublime Text diff --git a/docs/reference/syntax/engine.md b/docs/reference/syntax/engine.md index 52a418d6..2d7f1d4c 100644 --- a/docs/reference/syntax/engine.md +++ b/docs/reference/syntax/engine.md @@ -17,7 +17,7 @@ and several other editors use TextMate-derived engines. This page uses the Sublime Syntax's names for keywords, but the originals are available -on [the legacy page](syntaxdefs_legacy.html). +on [the legacy page](syntaxdefs_legacy.md). ## Walkthrough From 89711d21e0ed1d89320374a3a71d28c81c33344c Mon Sep 17 00:00:00 2001 From: Michael Lyons Date: Mon, 15 Jun 2026 16:55:26 -0400 Subject: [PATCH 04/17] WIP Shuffle syntax guides --- docs/.vitepress/config.ts | 4 +- .../extensibility}/syntax/engine.md | 0 docs/guide/extensibility/syntax/tutorial.md | 522 ++++++++++++++++++ .../{syntax => }/syntaxdefs_legacy.md | 0 4 files changed, 524 insertions(+), 2 deletions(-) rename docs/{reference => guide/extensibility}/syntax/engine.md (100%) create mode 100644 docs/guide/extensibility/syntax/tutorial.md rename docs/reference/{syntax => }/syntaxdefs_legacy.md (100%) diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 866ffd80..0f25dd32 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -136,6 +136,7 @@ export default defineConfig({ text: 'Syntax Definitions', link: '/guide/extensibility/syntax/', items: [ + // { text: 'Engine Overview', link: '/guide/extensibility/syntax/engine.md' }, // { text: 'Tutorial', link: '/guide/extensibility/syntax/tutorial.md' }, { text: 'Tutorial (Legacy)', link: '/guide/extensibility/syntax/tutorial_legacy.md' }, ], @@ -197,9 +198,8 @@ export default defineConfig({ { text: 'Syntax', items: [ - { text: 'Engine Overview', link: '/reference/syntax/engine.md' }, { text: 'Syntax Reference', link: 'https://www.sublimetext.com/docs/syntax.html' }, - { text: 'Legacy Syntax Formats', link: '/reference/syntax/syntaxdefs_legacy.md' }, + { text: 'Legacy Syntax Formats', link: '/reference/syntaxdefs_legacy.md' }, ], }, { diff --git a/docs/reference/syntax/engine.md b/docs/guide/extensibility/syntax/engine.md similarity index 100% rename from docs/reference/syntax/engine.md rename to docs/guide/extensibility/syntax/engine.md diff --git a/docs/guide/extensibility/syntax/tutorial.md b/docs/guide/extensibility/syntax/tutorial.md new file mode 100644 index 00000000..8bd9f829 --- /dev/null +++ b/docs/guide/extensibility/syntax/tutorial.md @@ -0,0 +1,522 @@ +--- +title: Syntax Definition Tutorial +--- + +# Syntax Definitions + +## Prerequisites + +In order to follow this tutorial, you will need to install +[PackageDev](https://github.com/SublimeText/PackageDev), a package +intended to ease the creation of new syntax definitions for Sublime +Text. Follow the installation notes in the "Getting Started" section of +the readme. + + +## File format + +Sublime Text uses [property list](https://en.wikipedia.org/wiki/Property_list) +(Plist) files to store syntax definitions. However, because editing XML files is +a cumbersome task, we'll use [YAML](https://en.wikipedia.org/wiki/YAML) instead +and convert it to Plist format afterwards. This is where the package +(mentioned above) comes in. + +::: tip Note +If you experience unexpected errors during this tutorial, chances are + or YAML is to blame. Don't immediately think your problem is +due to a bug in Sublime Text. +::: + +By all means, do edit the Plist files by hand if you prefer to work in +XML, but always keep in mind their differing needs in regards to escape +sequences, many XML tags etc. + + +## Your First Syntax Definition + +By way of example, let's create a syntax definition for Sublime Text +snippets. We'll be styling the actual snippet content, not the whole +`.sublime-snippet` file. + +::: tip Note +Since syntax definitions are primarily used to enable syntax highlighting, +we'll use the phrase *to style* to mean *to break down a source code file +into scopes*. Keep in mind, however, that colors are a different thing from +syntax definitions and that scopes have many more uses besides syntax +highlighting. +::: + +Here are the elements we want to style in a snippet: + +- Variables (`$PARAM1`, `$USER_NAME`\ ...) +- Simple fields (`$0`, `$1`\ ...) +- Complex fields with placeholders (`${1:Hello}`) +- Nested fields (`${1:Hello ${2:World}!}`) +- Escape sequences (`\$`, `\<`, …) +- Illegal sequences (`$`, `<`, `\`, …) + +Here are the elements we don't want to style because they are too complex for +this example: + +- Variable Substitution (`${1/Hello/Hi/g}`) + +::: tip Note +Before continuing, make sure you've installed the package as +explained above. +::: + + +## Creating A New Syntax Definition + +To create a new syntax definition, follow these steps: + +1. Go to **Tools | Packages | Package Development | New Syntax + Definition** +1. Save the new file in your `Packages/User` folder as a `.YAML-tmLanguage` file. + +You now should see a file like this: + +```yaml +# [PackageDev] target_format: plist, ext: tmLanguage +--- +name: Syntax Name +scopeName: source.syntax_name +fileTypes: [] +uuid: 0da65be4-5aac-4b6f-8071-1aadb970b8d9 + +patterns: +- +... +``` + +Let's examine the key elements. + + - `name`
+ The name that Sublime Text will display in the syntax definition + drop-down list. Use a short, descriptive name. Typically, you will + use the name of the programming language you are creating the syntax + definition for. + + - `scopeName`
+ The topmost scope for this syntax definition. It takes the form + `source.` or `text.`. For programming + languages, use `source`. For markup and everything else, use `text`. + + - `fileTypes`
+ This is a list of file extensions (without the leading dot). When + opening files of these types, Sublime Text will automatically + activate this syntax definition for them. + + - `uuid`
+ This is a unique identifier for this syntax definition. Each new + syntax definition gets its own uuid. Even though Sublime Text itself + ignores it, don't modify this. + + - `patterns`
+ A container for your patterns. + +For our example, fill the template with the following information: + +```yaml +# [PackageDev] target_format: plist, ext: tmLanguage +--- +name: Sublime Snippet (Raw) +scopeName: source.ssraw +fileTypes: [ssraw] +uuid: 0da65be4-5aac-4b6f-8071-1aadb970b8d9 + +patterns: +- +... +``` + +::: tip Note +YAML is not a very strict format, but can cause headaches when you don't +know its conventions. It supports single and double quotes, but you may also +omit them as long as the content does not create another YAML literal. If +the conversion to Plist fails, take a look at the output panel for more +information on the error. We'll explain later how to convert a syntax +definition in YAML to Plist. This will also cover the first commented line +in the template. + +The `---` and `...` are optional. +::: + + +## Analyzing Patterns + +The `patterns` array can contain several types of element. We'll look at +some of them in the following sections. If you want to learn more about +patterns, refer to Textmate's online manual. + + +### Matches + +Matches take this form: + +``` yaml +match: (?i:m)y \s+[Rr]egex +name: string.format +comment: This comment is optional. +``` + +Sublime Text uses [Oniguruma][]'s syntax for regular expressions in +syntax definitions. Several existing syntax definitions make use of +features supported by this regular expression engine that aren't part of +perl-style regular expressions, hence the requirement for Oniguruma. + +[Oniguruma]: https://github.com/kkos/oniguruma/blob/master/doc/RE + +`match` +: A regular expression Sublime Text will use to find matches. + +`name` +: The name of the scope that should be applied to any occurrences of `match`. + +`comment` +: An optional comment about this pattern. + +Let's go back to our example. It looks like this: + +``` yaml +# [PackageDev] target_format: plist, ext: tmLanguage +--- +name: Sublime Snippet (Raw) +scopeName: source.ssraw +fileTypes: [ssraw] +uuid: 0da65be4-5aac-4b6f-8071-1aadb970b8d9 + +patterns: +- +... +``` + +That is, make sure the `patterns` array is empty. + +Now we can begin to add our rules for Sublime snippets. Let's start with +simple fields. These could be matched with a regex like so: + +``` perl +\$[0-9]+ +# or... +\$\d+ +``` + +We can then build our pattern like this: +``` yaml +name: keyword.other.ssraw +match: \$\d+ +comment: Tab stops like $1, $2... +``` + +::: tip Choosing the Right Scope Name +Naming scopes isn't obvious sometimes. Check the [Textmate naming +conventions][] for guidance on scope names. automatically +provides completions for scope names according to these conventions. It +is important to re-use the basic categories outlined there if you want +to achieve the highest compatibility with existing colors. + +[Textmate naming conventions]: https://manual.macromates.com/en/language_grammars#naming_conventions + +Color schemes have hardcoded scope names in them. They could not +possibly include every scope name you can think of, so they target the +standard ones plus some rarer ones on occasion (like for CSS or +Markdown). This means that two color schemes using the same syntax +definition may render the text differently! + +Bear in mind too that you should use the scope name that best suits your +needs or preferences. It'd be perfectly fine to assign a scope like +`constant.numeric` to anything other than a number if you have a good +reason to do so. +::: + +And we can add it to our syntax definition too: + +``` yaml +# [PackageDev] target_format: plist, ext: tmLanguage +--- +name: Sublime Snippet (Raw) +scopeName: source.ssraw +fileTypes: [ssraw] +uuid: 0da65be4-5aac-4b6f-8071-1aadb970b8d9 + +patterns: +- comment: Tab stops like $1, $2... + name: keyword.other.ssraw + match: \$\d+ +... +``` + +::: tip Note +You should use two spaces for indent. This is the recommended indent for +YAML and lines up with lists like shown above. +::: + +We're now ready to convert our file to `.tmLanguage`. Syntax definitions use +Textmate's `.tmLanguage` extension for compatibility reasons. As explained +above, they are simply Plist XML files. + +Follow these steps to perform the conversion: + +- Make sure that `Automatic` is selected in **Tools | Build System**, or + select `Convert to ...`. +- Press . + A `.tmLanguage` file will be generated for you in the same folder as + your `.YAML-tmLanguage` file. +- Sublime Text will reload the changes to the syntax definition. + +In case you are wondering why knows what you want to convert your +file to: It's specified in the first comment line. + +You have now created your first syntax definition. Next, open a new file and +save it with the extension `.ssraw`. The buffer's syntax name should switch to +"Sublime Snippet (Raw)" automatically, and you should get syntax highlighting if +you type `$1` or any other simple snippet field. + +Let's proceed to creating another rule for environment variables. + +``` yaml +comment: Variables like $PARAM1, $TM_SELECTION... +name: keyword.other.ssraw +match: \$[A-Za-z][A-Za-z0-9_]+ +``` + +Repeat the above steps to update the `.tmLanguage` file. + + +### Fine Tuning Matches + +You might have noticed, for instance, that the entire text in `$PARAM1` is +styled the same way. Depending on your needs or your personal preferences, you +may want the `$` to stand out. That's where `captures` come in. Using +captures, you can break a pattern down into components to target them +individually. + +Let's rewrite one of our previous patterns to use `captures`: + +```yaml +comment: Variables like $PARAM1, $TM_SELECTION... +name: keyword.other.ssraw +match: \$([A-Za-z][A-Za-z0-9_]+) +captures: + '1': {name: constant.numeric.ssraw} +``` + +Captures introduce complexity to your rule, but they are pretty straightforward. +Notice how numbers refer to parenthesized groups left to right. Of course, you +can have as many capture groups as you want. + +::: tip Note +Writing `1` on a new line and pressing tab will autocomplete to `'1': +{name: }` thanks to . +::: + +Arguably, you'd want the other scope to be visually consistent with this one. +Go ahead and change it too. + +::: tip Note +As with ususal regular expressions and substitutions, the capture group +`'0'` applies to the whole match. +::: + + +### Begin-End Rules + +Up to now we've been using a simple rule. Although we've seen how to +dissect patterns into smaller components, sometimes you'll want to +target a larger portion of your source code that is clearly delimited by +start and end marks. + +Literal strings enclosed by quotation marks or other delimiting +constructs are better dealt with by begin-end rules. This is a skeleton +for one of these rules: + +```yaml +name: +begin: +end: +``` + +Well, at least in their simplest version. Let's take a look at one that +includes all available options: + +``` yaml +name: +contentName: +begin: +beginCaptures: + '0': {name: } + # ... +end: +endCaptures: + '0': {name: } + # ... +patterns: +- name: + match: +# ... +``` + +Some elements may look familiar, but their combination might be +daunting. Let's inspect them individually. + +`name` +: Just like with simple captures this sets the following scope name to + the whole match, including `begin` and `end` marks. Effectively, + this will create nested scopes for `beginCaptures`, `endCaptures` + and `patterns` defined within this rule. Optional. + +`contentName` +: Unlike the `name` this only applies a scope name to the enclosed + text. Optional. + +`begin` +: Regex for the opening mark for this scope. + +`end` +: Regex for the end mark for this scope. + +`beginCaptures` +: Captures for the `begin` marker. They work like captures for simple + matches. Optional. + +`endCaptures` +: Same as `beginCaptures` but for the `end` marker. Optional. + +`patterns` +: An array of patterns to match **only** against the begin-end's + content; they aren't matched against the text consumed by `begin` or + `end` themselves. Optional. + +We'll use this rule to style nested complex fields in snippets: + +``` yaml +name: variable.complex.ssraw +contentName: string.other.ssraw +begin: '(\$)(\{)([0-9]+):' +beginCaptures: + '1': {name: keyword.other.ssraw} + '3': {name: constant.numeric.ssraw} +end: \} +patterns: +- include: $self +- name: support.other.ssraw + match: . +``` + +This is the most complex pattern we'll see in this tutorial. The `begin` and +`end` keys are self-explanatory: they define a region enclosed between +`${:` and `}`. We need to wrap the begin pattern into quotes because +otherwise the trailing `:` would tell the parser to expect another +dictionary key. `beginCaptures` further divides the begin mark into smaller +scopes. + +The most interesting part, however, is `patterns`. Recursion, and the +importance of ordering, have finally made their appearance here. + +We've seen above that fields can be nested. In order to account for this, we +need to style nested fields recursively. That's what the `include` rule does +when we furnish it the `$self` value: it recursively applies our **entire +syntax definition** to the text captured by our begin-end rule. This portion +excludes the text individually consumed by the regexes for `begin` and +`end`. + +Remember, matched text is consumed; thus, it is excluded from the next match +attempt and can't be matched again. + +To finish off complex fields, we'll style placeholders as strings. Since we've +already matched all possible tokens inside a complex field, we can safely tell +Sublime Text to give any remaining text (`.`) a literal string scope. Note +that this doesn't work if we made the pattern greedy (`.+`) because this +includes possible nested references. + +::: tip Note +We could've used `contentName: string.other.ssraw` instead of the last +pattern but this way we introduce the importance of ordering and how matches +are consumed. +::: + + +### Final Touches + +Lastly, let's style escape sequences and illegal sequences, and then we +can wrap up. + +``` yaml +- comment: Sequences like \$, \> and \< + name: constant.character.escape.ssraw + match: \\[$<>] + +- comment: Unescaped and unmatched magic characters + name: invalid.illegal.ssraw + match: '[$<>]' +``` + +The only hard thing here is not forgetting that `[]` enclose arrays in +YAML and thus must be wrapped in quotes. Other than that, the rules are +pretty straightforward if you're familiar with regular expressions. + +However, you must take care to place the second rule after any others +matching the `$` character, since otherwise it will be consumed and +result in every following expression not matching. + +Also, even after adding these two additional rules, note that our +recursive begin-end rule from above continues to work as expected. + +At long last, here's the final syntax definition: + +``` yaml +# [PackageDev] target_format: plist, ext: tmLanguage +--- +name: Sublime Snippet (Raw) +scopeName: source.ssraw +fileTypes: [ssraw] +uuid: 0da65be4-5aac-4b6f-8071-1aadb970b8d9 + +patterns: +- comment: Tab stops like $1, $2... + name: keyword.other.ssraw + match: \$(\d+) + captures: + '1': {name: constant.numeric.ssraw} + +- comment: Variables like $PARAM1, $TM_SELECTION... + name: keyword.other.ssraw + match: \$([A-Za-z][A-Za-z0-9_]+) + captures: + '1': {name: constant.numeric.ssraw} + +- name: variable.complex.ssraw + begin: '(\$)(\{)([0-9]+):' + beginCaptures: + '1': {name: keyword.other.ssraw} + '3': {name: constant.numeric.ssraw} + end: \} + patterns: + - include: $self + - name: support.other.ssraw + match: . + +- comment: Sequences like \$, \> and \< + name: constant.character.escape.ssraw + match: \\[$<>] + +- comment: Unescaped and unmatched magic characters + name: invalid.illegal.ssraw + match: '[$<>]' +... +``` + +There are more available constructs and code reuse techniques using a +"repository", but the above explanations should get you started with the +creation of syntax definitions. + +::: tip Note +If you previously used JSON for syntax definitions you are still able to do +this because is backwards compatible. + +If you want to consider switching to YAML (either from JSON or directly from +Plist), it provides a command named `PackageDev: Convert to YAML and +Rearrange Syntax Definition` which will automatically format the resulting +YAML in a pleasurable way. +::: diff --git a/docs/reference/syntax/syntaxdefs_legacy.md b/docs/reference/syntaxdefs_legacy.md similarity index 100% rename from docs/reference/syntax/syntaxdefs_legacy.md rename to docs/reference/syntaxdefs_legacy.md From 5e7736dcde4aab844349d81c301a663c0ac3296e Mon Sep 17 00:00:00 2001 From: Michael Lyons Date: Tue, 16 Jun 2026 10:29:55 -0400 Subject: [PATCH 05/17] fixup broken links --- docs/guide/extensibility/snippets.md | 2 +- docs/guide/extensibility/syntax/engine.md | 2 +- docs/guide/extensibility/syntax/index.md | 2 +- docs/reference/completions.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/guide/extensibility/snippets.md b/docs/guide/extensibility/snippets.md index 9241db0b..55d71c0a 100644 --- a/docs/guide/extensibility/snippets.md +++ b/docs/guide/extensibility/snippets.md @@ -82,7 +82,7 @@ look at each of these parts in turn. : Used when showing the snippet in the Snippets menu. If not present, Sublime Text defaults to the file name of the snippet. -[Scopes]: /guide/extensibility/syntaxdefs.md#scopes +[Scopes]: /guide/extensibility/syntax/index.md#scopes With this information, you can start writing your own snippets as described in the next sections. diff --git a/docs/guide/extensibility/syntax/engine.md b/docs/guide/extensibility/syntax/engine.md index 2d7f1d4c..373113bd 100644 --- a/docs/guide/extensibility/syntax/engine.md +++ b/docs/guide/extensibility/syntax/engine.md @@ -17,7 +17,7 @@ and several other editors use TextMate-derived engines. This page uses the Sublime Syntax's names for keywords, but the originals are available -on [the legacy page](syntaxdefs_legacy.md). +on [the legacy page](/reference/syntaxdefs_legacy.md). ## Walkthrough diff --git a/docs/guide/extensibility/syntax/index.md b/docs/guide/extensibility/syntax/index.md index 3cf86c69..e305e665 100644 --- a/docs/guide/extensibility/syntax/index.md +++ b/docs/guide/extensibility/syntax/index.md @@ -54,7 +54,7 @@ many developers used a YAML or JSON representation and compiled it to PList afterward. Reference documentation is available -at [TextMate Syntax Definitions](/reference/syntax/syntaxdefs_legacy.md) +at [TextMate Syntax Definitions](/reference/syntaxdefs_legacy.md) [plist]: https://en.wikipedia.org/wiki/Property_list diff --git a/docs/reference/completions.md b/docs/reference/completions.md index 9e3ac752..293ac2d6 100644 --- a/docs/reference/completions.md +++ b/docs/reference/completions.md @@ -52,7 +52,7 @@ Here's an example (with HTML completions): See [Scopes][] for more information. -[Scopes]: /guide/extensibility/syntaxdefs.md#scopes +[Scopes]: /guide/extensibility/syntax/index.md#scopes **completions** : Array of *completions*. From cf3a5ad674ed027bbc8c3bdfd2e6cc321dc0e708 Mon Sep 17 00:00:00 2001 From: Michael Lyons Date: Tue, 16 Jun 2026 11:46:40 -0400 Subject: [PATCH 06/17] Consolidate syntax engine descriptions --- docs/.vitepress/config.ts | 1 - docs/guide/extensibility/syntax/engine.md | 149 --------------- docs/guide/extensibility/syntax/index.md | 218 +++++++++++++++++++--- 3 files changed, 197 insertions(+), 171 deletions(-) delete mode 100644 docs/guide/extensibility/syntax/engine.md diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 0f25dd32..f55ae051 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -136,7 +136,6 @@ export default defineConfig({ text: 'Syntax Definitions', link: '/guide/extensibility/syntax/', items: [ - // { text: 'Engine Overview', link: '/guide/extensibility/syntax/engine.md' }, // { text: 'Tutorial', link: '/guide/extensibility/syntax/tutorial.md' }, { text: 'Tutorial (Legacy)', link: '/guide/extensibility/syntax/tutorial_legacy.md' }, ], diff --git a/docs/guide/extensibility/syntax/engine.md b/docs/guide/extensibility/syntax/engine.md deleted file mode 100644 index 373113bd..00000000 --- a/docs/guide/extensibility/syntax/engine.md +++ /dev/null @@ -1,149 +0,0 @@ ---- -title: Syntax Engine Overview ---- - -# Syntax Engine Overview - -## TextMate - -Sublime Text originally used the TextMate format -for syntax definitions. -Their `tmLanguage` describes a stack-based system of regular expressions -to assign [scopes](#scopes) to specific sets of characters. -While Sublime Text now includes features beyond `tmLanguage`, -it is useful to understand its concepts first. -Many syntax definitions only use TextMate features, -and several other editors use TextMate-derived engines. - -This page uses the Sublime Syntax's names for keywords, -but the originals are available -on [the legacy page](/reference/syntaxdefs_legacy.md). - - -## Walkthrough - -1. The engine pushes the first stack frame, `main`. - It also set the current character - to the first character - of the unprocessed editor text. - -2. For the current character, - evaluate the regular expressions - in the current stack frame - from first to last - until one matches. - -3. Consume characters - in the matching regexp - and assign scopes to them. - -4. Optionally push or pop the stack frame. - The Sublime Text engine has a significant change - from TextMate, - wherein multiple stack frames can be pushed or popped at a time. - Sublime Text also supports replacing the current frame - with a different one. - -5. If none of the regular expressions - in the current frame match - at the current position in the file, - the engine will advance - to the next character of the file - and restart the list of regexps - in the current frame. - -6. Go to step 2 at the new character position. - -### Caveats - -1. Regular expressions that consume no characters - should change the stack. - If they do not, - the current character is advanced - as in step 5 - to avoid an infinite loop. - -2. Regular expressions do not match across line breaks. - - -## Reusing Matches - -### `contexts` - -It would be a mess to repeat relevant regular expressions -in each stack frame that needed them. -This is the purpose of the `contexts` section. -Contexts are sets of matches and other instructions -that a stack frame can `include` in its matches. -They are processed in the same order -as if they were `match` instructions -at the location of the `include` instruction. - -Contexts can also be pushed onto the stack -as a new frame. -This is why stack frames are often -colloquially referred to as "contexts," -including in Sublime Text's scope debugger. - -### `variables` - -Portions of regular expressions can also be saved -in the `variables` section and reused in multiple expressions. -Variables also make some ugly regexps readable. - -### Match reuse best practice - -Well-designed syntax definitions will define utility contexts -that `include` equivalent things together for re-usability: - -- A normal programming language will have things like - - A **statements** group of all things that can be directly executed. - This then may or may not (language-dependent) include… - - An **expressions** group of things - that you can put on the right-hand-side of an assignment, - which will definitely include… - - An **atoms** group of strings, numbers, chars, etc. - that might also be valid statements, - but that also depends on your language. - - **function-definitions** probably won't be in **expressions** - (unless they are lambdas) - but probably _would_ be in **statements**. - Function definitions might push into a context - that lets you `return` and so on. - -- A markup language might have - - An **inlines** group to keep track of all the markup - one can have within a block. - - A **blocks** group to hold lists, quotes, paragraphs, headers. - - … - - - - - - - - - -## Scopes - -Scopes are the names or tags for tokens -matched by a syntax definition. - -After scopes have been assigned by a syntax definition, -the user's [Color Scheme][] maps them -to colors and styles to apply to the text. -Using [established conventions for scopes][scope-names] -helps preserve consistency -for colors and styles -across multiple languages that a user may have installed. - -Scopes are also used for - -- indexing definitions, references, and headings -- context for which completions are available -- context for keybindings -- comment style - -[color scheme]: https://www.sublimetext.com/docs/color_schemes.html -[scope-names]: https://www.sublimetext.com/docs/scope_naming.html diff --git a/docs/guide/extensibility/syntax/index.md b/docs/guide/extensibility/syntax/index.md index e305e665..c9c43ce6 100644 --- a/docs/guide/extensibility/syntax/index.md +++ b/docs/guide/extensibility/syntax/index.md @@ -105,24 +105,200 @@ any other language. ## How Syntax Definitions Work -At their core, syntax definitions are arrays of regular expressions -paired with scope names. Sublime Text will try to match these patterns -against a buffer's text and attach the corresponding scope name to all -occurrences. These pairs of regular expressions and scope names are -known as *rules*. - -Rules are applied in order, one line at a time. Rules are applied in the -following order: - -1. The rule that matches at the first position in a line -2. The rule that comes first in the array - -Each rule consumes the matched text region, which therefore will be -excluded from the next rule's matching attempt (save for a few -exceptions). In practical terms, this means that you should take care to -go from more specific rules to more general ones when you create a new -syntax definition. Otherwise, a greedy regular expression might swallow -parts you'd like to have styled differently. - -Syntax definitions from separate files can be combined, and they can be -recursively applied too. +At their core, +syntax definitions are arrays of regular expressions +paired with scope names. +Sublime Text will try to match these patterns +against a buffer's text +and attach the corresponding scope name +to each occurrence. +These pairs of regular expressions and scope names +are known as *rules*. +Sets of rules called *contexts* +are pushed to and popped from a stack. + +### Basic Processing + +The basic loop for the syntax engine looks like this: + +1. The engine pushes the first context, `main`. + It also set the current character + to the first character + of the unprocessed editor text. + +2. For the current character, + evaluate the regular expressions + in the current stack frame + from first to last + until one matches. + +3. Consume characters + in the matching regexp + and assign scopes to them. + +4. Optionally push or pop a context on the stack. + The `sublime-syntax` format has a significant change + from TextMate, + wherein multiple stack frames can be pushed or popped at a time. + It also supports replacing the current context + with a different one. + +5. If none of the regular expressions + in the current context match + at the current position in the file, + the engine will advance + to the next character of the file + and restart the list of regexps + in the current context. + +6. Go to step 2 at the new character position. + +::: warning Caveats +- Regular expressions that consume no characters + should change the stack. + If they do not, + the current character is advanced + as in step 5 + to avoid an infinite loop. + +- Regular expressions *do not match* across line breaks. + +- Since rules are matched in order, + make sure that more specific rules + come sooner in each context. + Otherwise, a greedy regular expression might swallow parts + you'd like to have styled differently. +::: + + +## Reusing Matches + +### `contexts` + +It would be a mess to repeat relevant regular expressions +in each stack frame that needed them. +This is the purpose of the `contexts` section. +Contexts are sets of matches and other instructions +that a stack frame can `include` in its matches. +They are processed in the same order +as if they were `match` instructions +at the location of the `include` instruction. + +Contexts can also be pushed onto the stack +as a new frame. +This is why stack frames are often +colloquially referred to as "contexts," +including in Sublime Text's scope debugger. + +::: tip +- Contexts can be included from separate files. + +- Rules can recurse through pushed contexts. + (`main` -> `brace-blocks` -> `main`) +::: + +### `variables` + +Portions of regular expressions can also be saved +in the `variables` section and reused in multiple expressions. +Variables also make some ugly regexps readable. + + +### Match reuse best practice + +Well-designed syntax definitions will define utility contexts +that `include` equivalent things together for re-usability: + +- A normal programming language will have things like + - A **statements** group of all things that can be directly executed. + This then may or may not (language-dependent) include… + - An **expressions** group of things + that you can put on the right-hand-side of an assignment, + which will definitely include… + - An **atoms** group of strings, numbers, chars, etc. + that might also be valid statements, + but that also depends on your language. + - **function-definitions** probably won't be in **expressions** + (unless they are lambdas) + but probably _would_ be in **statements**. + Function definitions might push into a context + that lets you `return` and so on. + +- A markup language might have + - An **inlines** group to keep track of all the markup + one can have within a block. + - A **blocks** group to hold lists, quotes, paragraphs, headers. + - … + + + + + +## Other Instruction Keywords + +Consult [the official documentation][sublime-syntax] +for more detail on the terms below. + + +### Meta scopes + +`meta_scope` +: Apply a scope to a whole context, + including the matches that push and pop it. + +`meta_content_scope` +: Apply a scope to a whole context, + except for the matches that push and pop it. + + +### Prototyping + +The `prototype` context +: A special context included + at the beginning of every context + except contexts included by `prototype` itself. + For example: comments + +`meta_include_prototype` +: Keyword to disable including `prototype`. + For example: inside strings + +`with_prototype` +: When pushing a context, + also include these rules + at the beginning of *every* nested context. + + +### Embedding + +`embed` +: Like a pushed context, + popping rules are different + as described in `escape`. + +`escape` +: Aggressively return directly to the embedding context, + popping any number of contexts upon match. + +`escape_captures` +: Allow assigning scopes to the regexp in `escape`. + + +### Branching + +Sometimes the appropriate scope is not decidable +without context beyond a line break. +For these cases, the `branch` keyword +describes an array of speculative contexts +to try until a `fail` match rewinds +back to the `branch_point`. + + +### Inheritance + +Syntaxes can extend from other syntaxes. +Each variable can be overridden at will. +Each context can be prepended to, appended to, or replaced outright. + + + From 21e713dc057ea34629685fe3a92e1b263193d25c Mon Sep 17 00:00:00 2001 From: Michael Lyons Date: Tue, 16 Jun 2026 12:12:10 -0400 Subject: [PATCH 07/17] Update syntax tutorial --- docs/.vitepress/config.ts | 2 +- docs/guide/extensibility/syntax/tutorial.md | 540 ++++++++---------- .../extensibility/syntax/tutorial_legacy.md | 2 +- 3 files changed, 254 insertions(+), 290 deletions(-) diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index f55ae051..ff1bc5c9 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -136,7 +136,7 @@ export default defineConfig({ text: 'Syntax Definitions', link: '/guide/extensibility/syntax/', items: [ - // { text: 'Tutorial', link: '/guide/extensibility/syntax/tutorial.md' }, + { text: 'Tutorial', link: '/guide/extensibility/syntax/tutorial.md' }, { text: 'Tutorial (Legacy)', link: '/guide/extensibility/syntax/tutorial_legacy.md' }, ], }, diff --git a/docs/guide/extensibility/syntax/tutorial.md b/docs/guide/extensibility/syntax/tutorial.md index 8bd9f829..af9b4fb4 100644 --- a/docs/guide/extensibility/syntax/tutorial.md +++ b/docs/guide/extensibility/syntax/tutorial.md @@ -13,25 +13,6 @@ Text. Follow the installation notes in the "Getting Started" section of the readme. -## File format - -Sublime Text uses [property list](https://en.wikipedia.org/wiki/Property_list) -(Plist) files to store syntax definitions. However, because editing XML files is -a cumbersome task, we'll use [YAML](https://en.wikipedia.org/wiki/YAML) instead -and convert it to Plist format afterwards. This is where the package -(mentioned above) comes in. - -::: tip Note -If you experience unexpected errors during this tutorial, chances are - or YAML is to blame. Don't immediately think your problem is -due to a bug in Sublime Text. -::: - -By all means, do edit the Plist files by hand if you prefer to work in -XML, but always keep in mind their differing needs in regards to escape -sequences, many XML tags etc. - - ## Your First Syntax Definition By way of example, let's create a syntax definition for Sublime Text @@ -70,64 +51,51 @@ explained above. To create a new syntax definition, follow these steps: -1. Go to **Tools | Packages | Package Development | New Syntax - Definition** -1. Save the new file in your `Packages/User` folder as a `.YAML-tmLanguage` file. +1. Open the Command Palette and choose **New Syntax…** +1. Save the new file in your `Packages/User` folder + as a `.sublime-syntax` file. You now should see a file like this: ```yaml -# [PackageDev] target_format: plist, ext: tmLanguage +%YAML 1.2 --- -name: Syntax Name -scopeName: source.syntax_name -fileTypes: [] -uuid: 0da65be4-5aac-4b6f-8071-1aadb970b8d9 - -patterns: -- -... +# See http://www.sublimetext.com/docs/syntax.html +file_extensions: + - ec +scope: source.example-c +contexts: + main: +# ... ``` Let's examine the key elements. - - `name`
- The name that Sublime Text will display in the syntax definition - drop-down list. Use a short, descriptive name. Typically, you will - use the name of the programming language you are creating the syntax - definition for. - - - `scopeName`
- The topmost scope for this syntax definition. It takes the form - `source.` or `text.`. For programming - languages, use `source`. For markup and everything else, use `text`. +`scope` +: The topmost scope for this syntax definition. + It takes the form `source.` or `text.`. + For programming languages, use `source`. + For markup and everything else, use `text`. - - `fileTypes`
- This is a list of file extensions (without the leading dot). When - opening files of these types, Sublime Text will automatically - activate this syntax definition for them. +`file_extensions` +: This is a list of file extensions (without the leading dot). + When opening files of these types, + Sublime Text will automatically activate this syntax definition for them. - - `uuid`
- This is a unique identifier for this syntax definition. Each new - syntax definition gets its own uuid. Even though Sublime Text itself - ignores it, don't modify this. +`contexts` +: A container for your patterns. - - `patterns`
- A container for your patterns. - -For our example, fill the template with the following information: +For our example, edit the template with the following information: ```yaml -# [PackageDev] target_format: plist, ext: tmLanguage ---- name: Sublime Snippet (Raw) -scopeName: source.ssraw -fileTypes: [ssraw] -uuid: 0da65be4-5aac-4b6f-8071-1aadb970b8d9 +scope: source.ssraw +file_extensions: + - ssraw -patterns: -- -... +contexts: + main: +# ... ``` ::: tip Note @@ -145,56 +113,62 @@ The `---` and `...` are optional. ## Analyzing Patterns -The `patterns` array can contain several types of element. We'll look at -some of them in the following sections. If you want to learn more about -patterns, refer to Textmate's online manual. +Values in the `contexts` dictionary can contain several types of element. +We'll look at some of them in the following sections. +If you want to learn more, +refer to [the official documentation][sublime-syntax]. ### Matches Matches take this form: -``` yaml -match: (?i:m)y \s+[Rr]egex -name: string.format -comment: This comment is optional. +```yaml +- match: (?i:m)y \s+[Rr]egex + scope: string.format + comment: This comment is optional. ``` -Sublime Text uses [Oniguruma][]'s syntax for regular expressions in -syntax definitions. Several existing syntax definitions make use of -features supported by this regular expression engine that aren't part of -perl-style regular expressions, hence the requirement for Oniguruma. +Sublime Text uses a custom engine for regular expressions +in syntax definitions, +with a fallback to [Oniguruma][]'s engine. +Several existing syntax definitions make use of +features supported by Oniguruma that aren't part of +perl-style regular expressions, hence the requirement for the fallback. [Oniguruma]: https://github.com/kkos/oniguruma/blob/master/doc/RE `match` : A regular expression Sublime Text will use to find matches. -`name` +`scope` : The name of the scope that should be applied to any occurrences of `match`. `comment` : An optional comment about this pattern. -Let's go back to our example. It looks like this: +Let's go back to our example. It looks like this, ``` yaml -# [PackageDev] target_format: plist, ext: tmLanguage ---- name: Sublime Snippet (Raw) -scopeName: source.ssraw -fileTypes: [ssraw] -uuid: 0da65be4-5aac-4b6f-8071-1aadb970b8d9 +scope: source.ssraw +file_extensions: + - ssraw -patterns: -- -... +contexts: + main: +# ... + double_quoted_string: +# ... + line_comment: +# ... ``` -That is, make sure the `patterns` array is empty. - -Now we can begin to add our rules for Sublime snippets. Let's start with -simple fields. These could be matched with a regex like so: +but we can wipe everything after `main:` +to prepare for our own rules. +Then we can begin to add our rules for Sublime snippets. +Let's start with simple fields. +These could be matched with a regex like so: ``` perl \$[0-9]+ @@ -203,20 +177,22 @@ simple fields. These could be matched with a regex like so: ``` We can then build our pattern like this: -``` yaml -name: keyword.other.ssraw -match: \$\d+ -comment: Tab stops like $1, $2... +```yaml +# Tab stops like $1, $2... +- match: \$\d+ + scope: keyword.other.ssraw ``` ::: tip Choosing the Right Scope Name -Naming scopes isn't obvious sometimes. Check the [Textmate naming -conventions][] for guidance on scope names. automatically -provides completions for scope names according to these conventions. It -is important to re-use the basic categories outlined there if you want -to achieve the highest compatibility with existing colors. +Naming scopes isn't obvious sometimes. +Check the [naming conventions][] for guidance on scope names. + automatically provides completions +for scope names according to these conventions. +It is important to re-use the basic categories outlined there +if you want to achieve the highest compatibility +with existing color schemes. -[Textmate naming conventions]: https://manual.macromates.com/en/language_grammars#naming_conventions +[naming conventions]: https://www.sublimetext.com/docs/scope_naming.html Color schemes have hardcoded scope names in them. They could not possibly include every scope name you can think of, so they target the @@ -233,18 +209,16 @@ reason to do so. And we can add it to our syntax definition too: ``` yaml -# [PackageDev] target_format: plist, ext: tmLanguage ---- name: Sublime Snippet (Raw) -scopeName: source.ssraw -fileTypes: [ssraw] -uuid: 0da65be4-5aac-4b6f-8071-1aadb970b8d9 - -patterns: -- comment: Tab stops like $1, $2... - name: keyword.other.ssraw - match: \$\d+ -... +scope: source.ssraw +file_extensions: + - ssraw + +contexts: + main: + # Tab stops like $1, $2... + - match: \$\d+ + scope: keyword.other.ssraw ``` ::: tip Note @@ -252,187 +226,185 @@ You should use two spaces for indent. This is the recommended indent for YAML and lines up with lists like shown above. ::: -We're now ready to convert our file to `.tmLanguage`. Syntax definitions use -Textmate's `.tmLanguage` extension for compatibility reasons. As explained -above, they are simply Plist XML files. - -Follow these steps to perform the conversion: - -- Make sure that `Automatic` is selected in **Tools | Build System**, or - select `Convert to ...`. -- Press . - A `.tmLanguage` file will be generated for you in the same folder as - your `.YAML-tmLanguage` file. -- Sublime Text will reload the changes to the syntax definition. - -In case you are wondering why knows what you want to convert your -file to: It's specified in the first comment line. - -You have now created your first syntax definition. Next, open a new file and -save it with the extension `.ssraw`. The buffer's syntax name should switch to -"Sublime Snippet (Raw)" automatically, and you should get syntax highlighting if -you type `$1` or any other simple snippet field. +You have now created your first syntax definition. +Next, open a new file and +save it with the extension `.ssraw`. +The buffer's syntax name should switch +to "Sublime Snippet (Raw)" automatically, +and you should get syntax highlighting +if you type `$1` or any other simple snippet field. Let's proceed to creating another rule for environment variables. ``` yaml -comment: Variables like $PARAM1, $TM_SELECTION... -name: keyword.other.ssraw -match: \$[A-Za-z][A-Za-z0-9_]+ +# Variables like $PARAM1, $TM_SELECTION... +- match: \$[A-Za-z][A-Za-z0-9_]+ + scope: keyword.other.ssraw ``` -Repeat the above steps to update the `.tmLanguage` file. - ### Fine Tuning Matches -You might have noticed, for instance, that the entire text in `$PARAM1` is -styled the same way. Depending on your needs or your personal preferences, you -may want the `$` to stand out. That's where `captures` come in. Using -captures, you can break a pattern down into components to target them -individually. +You might have noticed, for instance, +that the entire text in `$PARAM1` is styled the same way. +Depending on your needs or your personal preferences, +you may want the `$` to stand out. +That's where `captures` come in. +Using captures, you can break a pattern down +into components to target them individually. Let's rewrite one of our previous patterns to use `captures`: ```yaml -comment: Variables like $PARAM1, $TM_SELECTION... -name: keyword.other.ssraw -match: \$([A-Za-z][A-Za-z0-9_]+) -captures: - '1': {name: constant.numeric.ssraw} +# Variables like $PARAM1, $TM_SELECTION... +- match: \$([A-Za-z][A-Za-z0-9_]+) + scope: keyword.other.ssraw + captures: + 1: constant.numeric.ssraw ``` -Captures introduce complexity to your rule, but they are pretty straightforward. -Notice how numbers refer to parenthesized groups left to right. Of course, you -can have as many capture groups as you want. - -::: tip Note -Writing `1` on a new line and pressing tab will autocomplete to `'1': -{name: }` thanks to . -::: +Captures introduce complexity to your rule, +but they are pretty straightforward. +Notice how numbers refer to parenthesized groups left to right. +Of course, you can have as many capture groups as you want. Arguably, you'd want the other scope to be visually consistent with this one. Go ahead and change it too. ::: tip Note -As with ususal regular expressions and substitutions, the capture group -`'0'` applies to the whole match. +As with usual regular expressions and substitutions, the capture group +`0` applies to the whole match. ::: -### Begin-End Rules +### Push and Pop Rules -Up to now we've been using a simple rule. Although we've seen how to -dissect patterns into smaller components, sometimes you'll want to -target a larger portion of your source code that is clearly delimited by -start and end marks. +Up to now we've been using a simple rule. +Although we've seen how to dissect patterns into smaller components, +sometimes you'll want to target a larger portion of your source code +that is clearly delimited by start and end marks. -Literal strings enclosed by quotation marks or other delimiting -constructs are better dealt with by begin-end rules. This is a skeleton -for one of these rules: +Literal strings enclosed by quotation marks or other delimiting constructs +are better dealt with by push and pop rules. This is a skeleton +for one of these rules that pushes an anonymous context: ```yaml -name: -begin: -end: +- match: + scope: + push: + - match: + scope: + pop: 1 ``` -Well, at least in their simplest version. Let's take a look at one that -includes all available options: +Well, at least in their simplest version. +Let's take a look at one that includes all available options: ``` yaml -name: -contentName: -begin: -beginCaptures: - '0': {name: } - # ... -end: -endCaptures: - '0': {name: } - # ... -patterns: -- name: - match: +- match: + scope: + push: + - meta_scope: + - meta_content_scope: + - match: + scope: + pop: 1 # ... ``` Some elements may look familiar, but their combination might be daunting. Let's inspect them individually. -`name` -: Just like with simple captures this sets the following scope name to - the whole match, including `begin` and `end` marks. Effectively, - this will create nested scopes for `beginCaptures`, `endCaptures` - and `patterns` defined within this rule. Optional. +`meta_scope` +: Just like with simple captures, + this sets the following scope name to + the whole match. + Optional. -`contentName` -: Unlike the `name` this only applies a scope name to the enclosed - text. Optional. +`meta_content_scope` +: Unlike the `meta_scope`, + this only applies a scope name to the enclosed text. + Optional. -`begin` +outer `match` : Regex for the opening mark for this scope. -`end` +inner `match` : Regex for the end mark for this scope. -`beginCaptures` -: Captures for the `begin` marker. They work like captures for simple - matches. Optional. - -`endCaptures` -: Same as `beginCaptures` but for the `end` marker. Optional. - -`patterns` -: An array of patterns to match **only** against the begin-end's - content; they aren't matched against the text consumed by `begin` or - `end` themselves. Optional. +`pop` +: Notes a number of contexts to pop off of the stack. + Multiple matches can have `pop` instructions. + Optional. We'll use this rule to style nested complex fields in snippets: ``` yaml -name: variable.complex.ssraw -contentName: string.other.ssraw -begin: '(\$)(\{)([0-9]+):' -beginCaptures: - '1': {name: keyword.other.ssraw} - '3': {name: constant.numeric.ssraw} -end: \} -patterns: -- include: $self -- name: support.other.ssraw - match: . +- match: '(\$)(\{)([0-9]+):' + captures: + 1: keyword.other.ssraw + 3: constant.numeric.ssraw + push: + - meta_scope: variable.complex.ssraw + - meta_content_scope: string.other.ssraw + - match: \} + pop: 1 + - include: main + - match: . + scope: support.other.ssraw ``` -This is the most complex pattern we'll see in this tutorial. The `begin` and -`end` keys are self-explanatory: they define a region enclosed between -`${:` and `}`. We need to wrap the begin pattern into quotes because -otherwise the trailing `:` would tell the parser to expect another -dictionary key. `beginCaptures` further divides the begin mark into smaller -scopes. +Although it is possible to push anonymous contexts, +best practice is to name them for ease of debugging. -The most interesting part, however, is `patterns`. Recursion, and the -importance of ordering, have finally made their appearance here. +Let's give this context a name: -We've seen above that fields can be nested. In order to account for this, we -need to style nested fields recursively. That's what the `include` rule does -when we furnish it the `$self` value: it recursively applies our **entire -syntax definition** to the text captured by our begin-end rule. This portion -excludes the text individually consumed by the regexes for `begin` and -`end`. +``` yaml +contexts: + main: + # ... (other rules) ... + + # Complex variables ${: ... } + - match: '(\$)(\{)([0-9]+):' + captures: + 1: keyword.other.ssraw + 3: constant.numeric.ssraw + push: complex_variable_body + + complex_variable_body: + - meta_scope: variable.complex.ssraw + - meta_content_scope: string.other.ssraw + - match: \} + pop: 1 + - include: main + - match: . + scope: support.other.ssraw +``` -Remember, matched text is consumed; thus, it is excluded from the next match -attempt and can't be matched again. +This is the most complex pattern we'll see in this tutorial. -To finish off complex fields, we'll style placeholders as strings. Since we've -already matched all possible tokens inside a complex field, we can safely tell -Sublime Text to give any remaining text (`.`) a literal string scope. Note -that this doesn't work if we made the pattern greedy (`.+`) because this -includes possible nested references. +Notice that there are other matches and includes +that do not pop the context stack. +These will be matched until a `}` is encountered. +It even includes **the `main` context,** +which happily recurses if another `${\d` match is discovered! + +Remember, matched text is consumed; +thus, it is excluded from the next match attempt +and can't be matched again. +Make sure your additional matches do not +accidentally eat the popping match. + +To finish off complex fields, we'll style placeholders as strings. +Since we've already matched all possible tokens inside a complex field, +we can safely tell +Sublime Text to give any remaining text (`.`) a literal string scope. +Note that this doesn't work if we made the pattern greedy (`.+`) +because this includes possible nested references. ::: tip Note We could've used `contentName: string.other.ssraw` instead of the last -pattern but this way we introduce the importance of ordering and how matches +pattern, but this way we introduce the importance of ordering and how matches are consumed. ::: @@ -443,13 +415,13 @@ Lastly, let's style escape sequences and illegal sequences, and then we can wrap up. ``` yaml -- comment: Sequences like \$, \> and \< - name: constant.character.escape.ssraw - match: \\[$<>] +# Sequences like \$, \> and \< +- match: \\[$<>] + scope: constant.character.escape.ssraw -- comment: Unescaped and unmatched magic characters - name: invalid.illegal.ssraw - match: '[$<>]' +# Unescaped and unmatched magic characters +- match: '[$<>]' + scope: invalid.illegal.ssraw ``` The only hard thing here is not forgetting that `[]` enclose arrays in @@ -465,58 +437,50 @@ recursive begin-end rule from above continues to work as expected. At long last, here's the final syntax definition: -``` yaml -# [PackageDev] target_format: plist, ext: tmLanguage +```yaml +%YAML 1.2 --- +# See http://www.sublimetext.com/docs/syntax.html name: Sublime Snippet (Raw) -scopeName: source.ssraw -fileTypes: [ssraw] -uuid: 0da65be4-5aac-4b6f-8071-1aadb970b8d9 - -patterns: -- comment: Tab stops like $1, $2... - name: keyword.other.ssraw - match: \$(\d+) - captures: - '1': {name: constant.numeric.ssraw} - -- comment: Variables like $PARAM1, $TM_SELECTION... - name: keyword.other.ssraw - match: \$([A-Za-z][A-Za-z0-9_]+) - captures: - '1': {name: constant.numeric.ssraw} - -- name: variable.complex.ssraw - begin: '(\$)(\{)([0-9]+):' - beginCaptures: - '1': {name: keyword.other.ssraw} - '3': {name: constant.numeric.ssraw} - end: \} - patterns: - - include: $self - - name: support.other.ssraw - match: . - -- comment: Sequences like \$, \> and \< - name: constant.character.escape.ssraw - match: \\[$<>] - -- comment: Unescaped and unmatched magic characters - name: invalid.illegal.ssraw - match: '[$<>]' -... +scope: source.ssraw +file_extensions: + - ssraw + +contexts: + main: + # Tab stops like $1, $2... + - match: \$\d+ + scope: keyword.other.ssraw + + # Variables like $PARAM1, $TM_SELECTION... + - match: \$[A-Za-z][A-Za-z0-9_]+ + scope: keyword.other.ssraw + + # Complex variables ${: ... } + - match: '(\$)(\{)([0-9]+):' + captures: + 1: keyword.other.ssraw + 3: constant.numeric.ssraw + push: complex_variable_body + + # Sequences like \$, \> and \< + - match: \\[$<>] + scope: constant.character.escape.ssraw + + # Unescaped and unmatched magic characters + - match: '[$<>]' + scope: invalid.illegal.ssraw + + complex_variable_body: + - meta_scope: variable.complex.ssraw + - meta_content_scope: string.other.ssraw + - match: \} + pop: 1 + - include: main + - match: . + scope: support.other.ssraw ``` -There are more available constructs and code reuse techniques using a -"repository", but the above explanations should get you started with the +There are more available constructs and code reuse techniques, +but the above explanations should get you started with the creation of syntax definitions. - -::: tip Note -If you previously used JSON for syntax definitions you are still able to do -this because is backwards compatible. - -If you want to consider switching to YAML (either from JSON or directly from -Plist), it provides a command named `PackageDev: Convert to YAML and -Rearrange Syntax Definition` which will automatically format the resulting -YAML in a pleasurable way. -::: diff --git a/docs/guide/extensibility/syntax/tutorial_legacy.md b/docs/guide/extensibility/syntax/tutorial_legacy.md index 68b0cd82..baec0571 100644 --- a/docs/guide/extensibility/syntax/tutorial_legacy.md +++ b/docs/guide/extensibility/syntax/tutorial_legacy.md @@ -348,7 +348,7 @@ Arguably, you'd want the other scope to be visually consistent with this one. Go ahead and change it too. ::: tip Note -As with ususal regular expressions and substitutions, the capture group +As with usual regular expressions and substitutions, the capture group `'0'` applies to the whole match. ::: From 86991c74aa8272d9bac05c0edd646a1b3f4874bb Mon Sep 17 00:00:00 2001 From: Michael Lyons Date: Tue, 16 Jun 2026 16:38:47 -0400 Subject: [PATCH 08/17] Update syntax descriptions based on feedback. --- docs/guide/extensibility/syntax/index.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/docs/guide/extensibility/syntax/index.md b/docs/guide/extensibility/syntax/index.md index c9c43ce6..e60a20ec 100644 --- a/docs/guide/extensibility/syntax/index.md +++ b/docs/guide/extensibility/syntax/index.md @@ -66,12 +66,12 @@ at [TextMate Syntax Definitions](/reference/syntaxdefs_legacy.md) : Official documentation on assigning scopes to code, including a section on Color Schemes -[Selectors][] +[Selectors][scope selectors] : Official documentation on scope selectors ::: Scopes are a key concept in Sublime Text, -inherited from the macOS editor TextMate. +which it inherits from the macOS editor TextMate. Essentially, scopes are named text regions in a buffer. They don't do anything by themselves, but Sublime Text peeks at them when it needs contextual information. @@ -99,7 +99,7 @@ quoted strings in Python source code, but not inside single quoted strings in any other language. [scope naming]: https://www.sublimetext.com/docs/scope_naming.html -[scope selectors]:https://www.sublimetext.com/docs/selectors.html +[scope selectors]: https://www.sublimetext.com/docs/selectors.html [color schemes]: /guide/customization/color_schemes.md @@ -122,11 +122,12 @@ are pushed to and popped from a stack. The basic loop for the syntax engine looks like this: 1. The engine pushes the first context, `main`. - It also set the current character + It also sets the current character to the first character of the unprocessed editor text. -2. For the current character, +2. From the current character + to the end of its line, evaluate the regular expressions in the current stack frame from first to last @@ -160,6 +161,10 @@ The basic loop for the syntax engine looks like this: the current character is advanced as in step 5 to avoid an infinite loop. + Common examples are + lookaheads like `(?=\S)`, + BOL or EOL anchors like `^` and `$`, + and the null regexp `''` that always matches. - Regular expressions *do not match* across line breaks. From 451ad48e60573eb6b3b3f44b3483f6af7cd0b119 Mon Sep 17 00:00:00 2001 From: Michael Lyons Date: Tue, 16 Jun 2026 16:45:47 -0400 Subject: [PATCH 09/17] Drop defunct syntax tutorial tip --- docs/guide/extensibility/syntax/tutorial.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docs/guide/extensibility/syntax/tutorial.md b/docs/guide/extensibility/syntax/tutorial.md index af9b4fb4..3453abbb 100644 --- a/docs/guide/extensibility/syntax/tutorial.md +++ b/docs/guide/extensibility/syntax/tutorial.md @@ -402,12 +402,6 @@ Sublime Text to give any remaining text (`.`) a literal string scope. Note that this doesn't work if we made the pattern greedy (`.+`) because this includes possible nested references. -::: tip Note -We could've used `contentName: string.other.ssraw` instead of the last -pattern, but this way we introduce the importance of ordering and how matches -are consumed. -::: - ### Final Touches From 8eefacb6c1c6c43e8f16928c3169e5e1ab630c42 Mon Sep 17 00:00:00 2001 From: Michael Lyons Date: Sat, 20 Jun 2026 11:54:13 -0400 Subject: [PATCH 10/17] Add a `sregex` section to Syntax --- docs/guide/extensibility/syntax/index.md | 34 +++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/docs/guide/extensibility/syntax/index.md b/docs/guide/extensibility/syntax/index.md index e60a20ec..c589774e 100644 --- a/docs/guide/extensibility/syntax/index.md +++ b/docs/guide/extensibility/syntax/index.md @@ -306,4 +306,36 @@ Each variable can be overridden at will. Each context can be prepended to, appended to, or replaced outright. - +## Regular Expression Performance + +Sublime built a custom regexp engine to process rules, +commonly called `sregex`. +It explicitly excludes support for certain constructs +that are slow or explode backtracking. + +Oniguruma is still available and used where necessary, +but the best practice for development is to eliminate incompatible patterns. + +In practice, this means to avoid + +- Anything non-regular in the formal sense + (backreferences, recursive matches, etc.). + + Except when capture groups are used in a `push`. + Those are available in a `pop` as backrefs. + +- Lookbehinds + + Except in `escape` patterns. + +- Atomic groups and possessive quantifiers + +- Some Unicode character properties + +- Named captures + + +### Testing `sregex` Compatibility + +Syntax definitions have a build variant +to test pattern compatibility with the `sregex` engine. +Use and choose the +**Syntax Tests - Regex Compatibility** option. From 873ef9fb32606be22ca1845a6e83405c22c89ef8 Mon Sep 17 00:00:00 2001 From: Michael Lyons Date: Sat, 20 Jun 2026 12:48:23 -0400 Subject: [PATCH 11/17] Incorporate some tips into syntax guide --- docs/guide/extensibility/syntax/index.md | 222 ++++++++++++++++++++++- 1 file changed, 220 insertions(+), 2 deletions(-) diff --git a/docs/guide/extensibility/syntax/index.md b/docs/guide/extensibility/syntax/index.md index c589774e..552baaf4 100644 --- a/docs/guide/extensibility/syntax/index.md +++ b/docs/guide/extensibility/syntax/index.md @@ -235,8 +235,226 @@ that `include` equivalent things together for re-usability: - A **blocks** group to hold lists, quotes, paragraphs, headers. - … - - + +## Manipulating the Stack + +Sublime's syntax definitions allow better stack control +than TextMate languages, +allowing some common patterns. +These are adapted from [the Tips issue][tips] +on the repository for ST's own syntaxes. + +[tips]: https://github.com/sublimehq/Packages/issues/757 + + +### Pushing multiple contexts + +When you have a construction +where you expect a list of elements in sequence, +put them all onto the stack at once. +The stack will unwind as the elements are recognized. + +```yaml +contexts: + + else-pop: + - match: (?=\S) + pop: 1 + + functions: + - match: function(?=\s) + scope: keyword.declaration.function + push: + - function-body + - function-params + - function-name + + function-name: + - match: (?:{{identifier_function}})?(?=[({]) + scope: entity.name.function + pop: 1 + - include: storage-modifiers # global, private, etc. + + function-params: + - match: \( + scope: punctuation.section.parameters.begin + push: function-param-body + - include: else-pop + + function-param-body: + - meta_scope: meta.function.parameters + - match: \) + scope: punctuation.section.parameters.end + pop: 2 + - ... + + function-body: + - meta_scope: meta.function + - match: \{ + scope: punctuation.section.block.begin + push: function-body-content + - include: else-pop + + function-body-content: + - match: \} + scope: punctuation.section.block.end + pop: 2 + - include: statements +``` + +As an added benefit, most of these scopes can be reused: + +```yaml + immediately-pop: + - match: '' + pop: 1 + + statements: + ... + - match: \{ + scope: punctuation.section.braces.begin + push: + - meta-block + - expect-closing-brace + - statements + ... + + meta-block: + - meta_scope: meta.block + - include: immediately-pop +``` + +As a bonus, states stacked this way are implicitly optional. +If one is omitted, +the highlighter will move on to the next without interruption. +For instance, in the first example, +the construction will be parsed correctly +whether or not the author supplies a function name. + +::: tip Tip +Use plural context names to indicate non-popping contexts. +In other words, plural contexts can match multiple times. + +Use singular context names where the contents can only match once. +::: + + +### Context chaining + +You can also manipulate the stack with sequences of `set`s. +Before making an elaborate state machine, +ask yourself if you *really* need to. + + +#### Push your first state + +While it is absolutely possible to have a match in `main` +which `set`s into a chain of stateful contexts +and subsequently sets back into `main` at the end, +it is not recommended. +`main` should be a stateless "baseline" context +that is always the last element on the stack. + +Instead, have your match in `main` use push +to get into your first state, +then `pop` out of the last state. +For example, imagine we wanted +to match the sequence `abc` with each character scoped differently +and only when they follow each other. +For illustration purposes, we will also match numerics in `main`: + +```yaml +contexts: + main: + - match: a + scope: first + push: expect-b + - match: \d+ + scope: constant.numeric + + expect-b: + - match: b + scope: second + set: expect-c + + expect-c: + - match: c + scope: third + pop: 1 +``` + +Notice how `a` pushes `expect-b`. +We don't set the first context, only the second one. +Once we find the terminator, we pop out. + + +#### Lookahead push for meta scoping + +Sometimes you need to apply a meta scope +to an entire stateful chunk. +When this is the case, +you almost certainly want your push rule +to be a non-consuming lookahead +rather than a consuming scoped match. +We can modify the above: + +```yaml +contexts: + main: + - match: (?=a) + push: expect-a + - match: \d+ + scope: constant.numeric + + expect-a: + - meta_scope: meta.abc + - match: a + scope: first + set: expect-b + + expect-b: + - meta_scope: meta.abc + - match: b + scope: second + set: expect-c + + expect-c: + - meta_scope: meta.abc + - match: c + scope: third + pop: 1 +``` + + +#### Bail outs + +Always remember that you're writing a parser +for a set of partially valid syntax fragments. +The normal mode of operation is that someone is actively typing new text. +For this reason, +make sure that any and all stateful contexts you use +have aggressive "bail-outs" for when something goes wrong. +As a rule of thumb, if there's a case where a compiler's parser would have produced an error, +your syntax mode should handle that case by `pop`ing back to `main`. + +Consider the example from above. Imagine the user is typing typing into the following buffer: + +``` +42 +ab +12 +``` + +Even if the user is actively typing `c` following `b`, +it would be a terrible experience for the scoping on `12` +to shift back and forth as they type in the middle. +For this reason, you should always end your mid-state scopes +with a lookahead match like `else-pop` from the multi-push section above +that pops out of the state chain. + +Getting this wrong is one of the easiest ways +to create a terrible experience for users of your mode +without even realizing it yourself. ## Other Instruction Keywords From 648c7e78c750b1652b91a47d34237a6bd2eabab6 Mon Sep 17 00:00:00 2001 From: Michael Lyons Date: Fri, 10 Jul 2026 14:57:24 -0400 Subject: [PATCH 12/17] Format syntax tutorial --- docs/guide/extensibility/syntax/tutorial.md | 39 +++++++++++---------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/docs/guide/extensibility/syntax/tutorial.md b/docs/guide/extensibility/syntax/tutorial.md index 3453abbb..e396456b 100644 --- a/docs/guide/extensibility/syntax/tutorial.md +++ b/docs/guide/extensibility/syntax/tutorial.md @@ -6,11 +6,14 @@ title: Syntax Definition Tutorial ## Prerequisites -In order to follow this tutorial, you will need to install -[PackageDev](https://github.com/SublimeText/PackageDev), a package -intended to ease the creation of new syntax definitions for Sublime -Text. Follow the installation notes in the "Getting Started" section of -the readme. +In order to follow this tutorial, +you will need to install [PackageDev][], +a package intended to ease the creation +of new syntax definitions for Sublime Text. +Follow the installation notes +in the "Getting Started" section of its ReadMe. + +[packagedev]: https://github.com/SublimeText/PackageDev ## Your First Syntax Definition @@ -42,12 +45,12 @@ this example: - Variable Substitution (`${1/Hello/Hi/g}`) ::: tip Note -Before continuing, make sure you've installed the package as -explained above. +Before continuing, make sure you've installed the + package as explained above. ::: -## Creating A New Syntax Definition +## Creating a New Syntax Definition To create a new syntax definition, follow these steps: @@ -60,7 +63,7 @@ You now should see a file like this: ```yaml %YAML 1.2 --- -# See http://www.sublimetext.com/docs/syntax.html +# See https://www.sublimetext.com/docs/syntax.html file_extensions: - ec scope: source.example-c @@ -134,9 +137,9 @@ in syntax definitions, with a fallback to [Oniguruma][]'s engine. Several existing syntax definitions make use of features supported by Oniguruma that aren't part of -perl-style regular expressions, hence the requirement for the fallback. +PERL-style regular expressions, hence the requirement for the fallback. -[Oniguruma]: https://github.com/kkos/oniguruma/blob/master/doc/RE +[oniguruma]: https://github.com/kkos/oniguruma/blob/master/doc/RE `match` : A regular expression Sublime Text will use to find matches. @@ -170,7 +173,7 @@ Then we can begin to add our rules for Sublime snippets. Let's start with simple fields. These could be matched with a regex like so: -``` perl +```perl \$[0-9]+ # or... \$\d+ @@ -208,7 +211,7 @@ reason to do so. And we can add it to our syntax definition too: -``` yaml +```yaml name: Sublime Snippet (Raw) scope: source.ssraw file_extensions: @@ -236,7 +239,7 @@ if you type `$1` or any other simple snippet field. Let's proceed to creating another rule for environment variables. -``` yaml +```yaml # Variables like $PARAM1, $TM_SELECTION... - match: \$[A-Za-z][A-Za-z0-9_]+ scope: keyword.other.ssraw @@ -339,7 +342,7 @@ inner `match` We'll use this rule to style nested complex fields in snippets: -``` yaml +```yaml - match: '(\$)(\{)([0-9]+):' captures: 1: keyword.other.ssraw @@ -359,7 +362,7 @@ best practice is to name them for ease of debugging. Let's give this context a name: -``` yaml +```yaml contexts: main: # ... (other rules) ... @@ -408,7 +411,7 @@ because this includes possible nested references. Lastly, let's style escape sequences and illegal sequences, and then we can wrap up. -``` yaml +```yaml # Sequences like \$, \> and \< - match: \\[$<>] scope: constant.character.escape.ssraw @@ -434,7 +437,7 @@ At long last, here's the final syntax definition: ```yaml %YAML 1.2 --- -# See http://www.sublimetext.com/docs/syntax.html +# See https://www.sublimetext.com/docs/syntax.html name: Sublime Snippet (Raw) scope: source.ssraw file_extensions: From ae9899ccf8d7eafcf38327f1ddf06a766a1b234a Mon Sep 17 00:00:00 2001 From: Michael Lyons Date: Fri, 10 Jul 2026 15:00:51 -0400 Subject: [PATCH 13/17] Address some syntax tutorial feedback --- docs/guide/extensibility/syntax/tutorial.md | 47 +++++++-------------- 1 file changed, 15 insertions(+), 32 deletions(-) diff --git a/docs/guide/extensibility/syntax/tutorial.md b/docs/guide/extensibility/syntax/tutorial.md index e396456b..c355a26e 100644 --- a/docs/guide/extensibility/syntax/tutorial.md +++ b/docs/guide/extensibility/syntax/tutorial.md @@ -88,7 +88,8 @@ Let's examine the key elements. `contexts` : A container for your patterns. -For our example, edit the template with the following information: +For our example, edit the template with the following information, +and throw away everything after `main:`. ```yaml name: Sublime Snippet (Raw) @@ -98,17 +99,16 @@ file_extensions: contexts: main: -# ... ``` ::: tip Note YAML is not a very strict format, but can cause headaches when you don't know its conventions. It supports single and double quotes, but you may also -omit them as long as the content does not create another YAML literal. If -the conversion to Plist fails, take a look at the output panel for more -information on the error. We'll explain later how to convert a syntax -definition in YAML to Plist. This will also cover the first commented line -in the template. +omit them as long as the content does not create another YAML literal. + + syntax highlighting is very good +at demonstrating where strings will be correctly or incorrectly parsed. +When quotes are necessary, convention is to use single quotes. The `---` and `...` are optional. ::: @@ -129,12 +129,13 @@ Matches take this form: ```yaml - match: (?i:m)y \s+[Rr]egex scope: string.format - comment: This comment is optional. ``` -Sublime Text uses a custom engine for regular expressions -in syntax definitions, -with a fallback to [Oniguruma][]'s engine. +Sublime Text uses a custom engine called `sregex` +for regular expressions in syntax definitions +that uses [Oniguruma][]'s format. +There is a fallback to the Oniguruma engine +for features `sregex` doesn't support. Several existing syntax definitions make use of features supported by Oniguruma that aren't part of PERL-style regular expressions, hence the requirement for the fallback. @@ -150,27 +151,9 @@ PERL-style regular expressions, hence the requirement for the fallback. `comment` : An optional comment about this pattern. -Let's go back to our example. It looks like this, - -``` yaml -name: Sublime Snippet (Raw) -scope: source.ssraw -file_extensions: - - ssraw - -contexts: - main: -# ... - double_quoted_string: -# ... - line_comment: -# ... -``` - -but we can wipe everything after `main:` -to prepare for our own rules. -Then we can begin to add our rules for Sublime snippets. -Let's start with simple fields. +Let's go back to our example +and begin to add our rules for Sublime snippets. +We'll start with simple fields. These could be matched with a regex like so: ```perl From aae63b86d8e68cf01f96dce5d699374e502241e3 Mon Sep 17 00:00:00 2001 From: Michael Lyons Date: Fri, 10 Jul 2026 15:16:39 -0400 Subject: [PATCH 14/17] fixup feedback --- docs/guide/extensibility/syntax/tutorial.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/guide/extensibility/syntax/tutorial.md b/docs/guide/extensibility/syntax/tutorial.md index c355a26e..c61865ac 100644 --- a/docs/guide/extensibility/syntax/tutorial.md +++ b/docs/guide/extensibility/syntax/tutorial.md @@ -148,9 +148,6 @@ PERL-style regular expressions, hence the requirement for the fallback. `scope` : The name of the scope that should be applied to any occurrences of `match`. -`comment` -: An optional comment about this pattern. - Let's go back to our example and begin to add our rules for Sublime snippets. We'll start with simple fields. From be377c1b572fada3d36732205105b4f870223d3d Mon Sep 17 00:00:00 2001 From: Michael Lyons Date: Fri, 10 Jul 2026 16:00:46 -0400 Subject: [PATCH 15/17] Address more syntax tutorial feedback --- docs/guide/extensibility/syntax/tutorial.md | 184 ++++++++++---------- 1 file changed, 96 insertions(+), 88 deletions(-) diff --git a/docs/guide/extensibility/syntax/tutorial.md b/docs/guide/extensibility/syntax/tutorial.md index c61865ac..b77f780d 100644 --- a/docs/guide/extensibility/syntax/tutorial.md +++ b/docs/guide/extensibility/syntax/tutorial.md @@ -75,7 +75,7 @@ contexts: Let's examine the key elements. `scope` -: The topmost scope for this syntax definition. +: The topmost [scope][] for this syntax definition. It takes the form `source.` or `text.`. For programming languages, use `source`. For markup and everything else, use `text`. @@ -86,7 +86,13 @@ Let's examine the key elements. Sublime Text will automatically activate this syntax definition for them. `contexts` -: A container for your patterns. +: A container for contexts that contain your match patterns. + You'll notice the `main` context + from [How Syntax Definitions Work][syndef-work] + and some other sample contexts we won't need. + +[scope]: index.md#scopes +[syndef-work]: index.md#how-syntax-definitions-work For our example, edit the template with the following information, and throw away everything after `main:`. @@ -114,7 +120,11 @@ The `---` and `...` are optional. ::: -## Analyzing Patterns +## Creating some rules + +If you don't remember [how syntax definition processing works][syndef-work] +at a general level, +you may want to read [a synopsis][syndef-work]. Values in the `contexts` dictionary can contain several types of element. We'll look at some of them in the following sections. @@ -146,7 +156,8 @@ PERL-style regular expressions, hence the requirement for the fallback. : A regular expression Sublime Text will use to find matches. `scope` -: The name of the scope that should be applied to any occurrences of `match`. +: The name of the [scope][] that should be applied + to any occurrences of `match`. Let's go back to our example and begin to add our rules for Sublime snippets. @@ -163,9 +174,35 @@ We can then build our pattern like this: ```yaml # Tab stops like $1, $2... - match: \$\d+ - scope: keyword.other.ssraw + scope: variable.language.ssraw +``` + +or even add a capture group to further scope the punctuation: +```yaml +# Tab stops like $1, $2... +- match: (\$)\d+ + scope: variable.language.ssraw + captures: + 1: punctuation.definition.variable.begin.ssraw ``` +Captures introduce complexity to your rule, +but they are pretty straightforward. +Notice how numbers refer to parenthesized groups left to right. +Of course, you can have as many capture groups as you want. + +::: tip Note +As with usual regular expressions and substitutions, the capture group +`0` applies to the whole match. +::: + +We choose `variable.language` because the `$1` is a variable, +but its value is set by the language itself, +not by an assignment or declaration. +The `punctuation...` scope lets color schemes carefully target +the punctuation if their designers want them to. + + ::: tip Choosing the Right Scope Name Naming scopes isn't obvious sometimes. Check the [naming conventions][] for guidance on scope names. @@ -173,22 +210,21 @@ Check the [naming conventions][] for guidance on scope names. for scope names according to these conventions. It is important to re-use the basic categories outlined there if you want to achieve the highest compatibility -with existing color schemes. - -[naming conventions]: https://www.sublimetext.com/docs/scope_naming.html +with existing [color schemes][]. Color schemes have hardcoded scope names in them. They could not possibly include every scope name you can think of, so they target the standard ones plus some rarer ones on occasion (like for CSS or Markdown). This means that two color schemes using the same syntax definition may render the text differently! - -Bear in mind too that you should use the scope name that best suits your -needs or preferences. It'd be perfectly fine to assign a scope like -`constant.numeric` to anything other than a number if you have a good -reason to do so. +That's left to the color scheme, though. +Resist any temptation to use novel scopes +to select a specific color in your color scheme. ::: +[color schemes]: /guide/customization/color_schemes.md +[naming conventions]: https://www.sublimetext.com/docs/scope_naming.html + And we can add it to our syntax definition too: ```yaml @@ -200,8 +236,10 @@ file_extensions: contexts: main: # Tab stops like $1, $2... - - match: \$\d+ - scope: keyword.other.ssraw + - match: (\$)\d+ + scope: variable.language.ssraw + captures: + 1: punctuation.definition.variable.begin.ssraw ``` ::: tip Note @@ -221,44 +259,12 @@ Let's proceed to creating another rule for environment variables. ```yaml # Variables like $PARAM1, $TM_SELECTION... -- match: \$[A-Za-z][A-Za-z0-9_]+ - scope: keyword.other.ssraw -``` - - -### Fine Tuning Matches - -You might have noticed, for instance, -that the entire text in `$PARAM1` is styled the same way. -Depending on your needs or your personal preferences, -you may want the `$` to stand out. -That's where `captures` come in. -Using captures, you can break a pattern down -into components to target them individually. - -Let's rewrite one of our previous patterns to use `captures`: - -```yaml -# Variables like $PARAM1, $TM_SELECTION... -- match: \$([A-Za-z][A-Za-z0-9_]+) - scope: keyword.other.ssraw +- match: (\$)[A-Za-z][A-Za-z0-9_]* + scope: variable.language.ssraw captures: - 1: constant.numeric.ssraw + 1: punctuation.definition.variable.begin.ssraw ``` -Captures introduce complexity to your rule, -but they are pretty straightforward. -Notice how numbers refer to parenthesized groups left to right. -Of course, you can have as many capture groups as you want. - -Arguably, you'd want the other scope to be visually consistent with this one. -Go ahead and change it too. - -::: tip Note -As with usual regular expressions and substitutions, the capture group -`0` applies to the whole match. -::: - ### Push and Pop Rules @@ -299,14 +305,15 @@ Some elements may look familiar, but their combination might be daunting. Let's inspect them individually. `meta_scope` -: Just like with simple captures, - this sets the following scope name to - the whole match. +: This sets the following scope name to + *the whole context* and the match that pushed it, + above any of that match's `scope` or `captures`. Optional. `meta_content_scope` : Unlike the `meta_scope`, - this only applies a scope name to the enclosed text. + this only applies a scope name to the portion of the context + that does not include the pushing or popping matches. Optional. outer `match` @@ -323,18 +330,18 @@ inner `match` We'll use this rule to style nested complex fields in snippets: ```yaml -- match: '(\$)(\{)([0-9]+):' +- match: (\$)(\{)[0-9]+(:) captures: - 1: keyword.other.ssraw - 3: constant.numeric.ssraw + 1: punctuation.definition.variable.begin.ssraw + 2: punctuation.section.interpolation.begin.ssraw + 3: punctuation.separator.ssraw push: - - meta_scope: variable.complex.ssraw - - meta_content_scope: string.other.ssraw + - meta_scope: variable.language.complex.ssraw + - meta_content_scope: string.unquoted.ssraw - match: \} + scope: punctuation.section.interpolation.end.ssraw pop: 1 - include: main - - match: . - scope: support.other.ssraw ``` Although it is possible to push anonymous contexts, @@ -348,20 +355,20 @@ contexts: # ... (other rules) ... # Complex variables ${: ... } - - match: '(\$)(\{)([0-9]+):' + - match: (\$)(\{)[0-9]+(:) captures: - 1: keyword.other.ssraw - 3: constant.numeric.ssraw + 1: punctuation.definition.variable.begin.ssraw + 2: punctuation.section.interpolation.begin.ssraw + 3: punctuation.separator.ssraw push: complex_variable_body complex_variable_body: - - meta_scope: variable.complex.ssraw - - meta_content_scope: string.other.ssraw + - meta_scope: variable.language.complex.ssraw + - meta_content_scope: string.unquoted.ssraw - match: \} + scope: punctuation.section.interpolation.end.ssraw pop: 1 - include: main - - match: . - scope: support.other.ssraw ``` This is the most complex pattern we'll see in this tutorial. @@ -372,18 +379,15 @@ These will be matched until a `}` is encountered. It even includes **the `main` context,** which happily recurses if another `${\d` match is discovered! -Remember, matched text is consumed; -thus, it is excluded from the next match attempt +Remember, [matched text is consumed][syndef-work]. +It is consequently excluded from the next match attempt and can't be matched again. -Make sure your additional matches do not +Make sure your additional matches **do not** accidentally eat the popping match. -To finish off complex fields, we'll style placeholders as strings. -Since we've already matched all possible tokens inside a complex field, -we can safely tell -Sublime Text to give any remaining text (`.`) a literal string scope. -Note that this doesn't work if we made the pattern greedy (`.+`) -because this includes possible nested references. +To finish off complex fields, +we've styled the placeholders as strings +with the `meta_content_scope` field. ### Final Touches @@ -426,18 +430,23 @@ file_extensions: contexts: main: # Tab stops like $1, $2... - - match: \$\d+ - scope: keyword.other.ssraw + - match: (\$)\d+ + scope: variable.language.ssraw + captures: + 1: punctuation.definition.variable.begin.ssraw # Variables like $PARAM1, $TM_SELECTION... - - match: \$[A-Za-z][A-Za-z0-9_]+ - scope: keyword.other.ssraw + - match: (\$)[A-Za-z][A-Za-z0-9_]* + scope: variable.language.ssraw + captures: + 1: punctuation.definition.variable.begin.ssraw # Complex variables ${: ... } - - match: '(\$)(\{)([0-9]+):' + - match: (\$)(\{)[0-9]+(:) captures: - 1: keyword.other.ssraw - 3: constant.numeric.ssraw + 1: punctuation.definition.variable.begin.ssraw + 2: punctuation.section.interpolation.begin.ssraw + 3: punctuation.separator.ssraw push: complex_variable_body # Sequences like \$, \> and \< @@ -449,13 +458,12 @@ contexts: scope: invalid.illegal.ssraw complex_variable_body: - - meta_scope: variable.complex.ssraw - - meta_content_scope: string.other.ssraw + - meta_scope: variable.language.complex.ssraw + - meta_content_scope: string.unquoted.ssraw - match: \} + scope: punctuation.section.interpolation.end.ssraw pop: 1 - include: main - - match: . - scope: support.other.ssraw ``` There are more available constructs and code reuse techniques, From 70221675bf44399003490fa88980fe32f72aefa3 Mon Sep 17 00:00:00 2001 From: Michael Lyons Date: Sat, 11 Jul 2026 14:19:36 -0400 Subject: [PATCH 16/17] Link syntax overview more times from tutorial --- docs/guide/extensibility/syntax/tutorial.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/docs/guide/extensibility/syntax/tutorial.md b/docs/guide/extensibility/syntax/tutorial.md index b77f780d..63cef984 100644 --- a/docs/guide/extensibility/syntax/tutorial.md +++ b/docs/guide/extensibility/syntax/tutorial.md @@ -273,9 +273,14 @@ Although we've seen how to dissect patterns into smaller components, sometimes you'll want to target a larger portion of your source code that is clearly delimited by start and end marks. +From [the synopsis of how syntax engines work][syndef-work], +you'll remember that the array of rules to match on +can be changed by manipulating the stack of [contexts](index.md#contexts). Literal strings enclosed by quotation marks or other delimiting constructs -are better dealt with by push and pop rules. This is a skeleton -for one of these rules that pushes an anonymous context: +are better dealt with by push and pop rules. +This is a skeleton for one of these rules +that pushes an anonymous context, +namely one that doesn't have an identifier in `contexts`. ```yaml - match: @@ -286,7 +291,7 @@ for one of these rules that pushes an anonymous context: pop: 1 ``` -Well, at least in their simplest version. +That is the simplest version. Let's take a look at one that includes all available options: ``` yaml @@ -308,13 +313,13 @@ daunting. Let's inspect them individually. : This sets the following scope name to *the whole context* and the match that pushed it, above any of that match's `scope` or `captures`. - Optional. + **Optional.** `meta_content_scope` : Unlike the `meta_scope`, this only applies a scope name to the portion of the context that does not include the pushing or popping matches. - Optional. + **Optional.** outer `match` : Regex for the opening mark for this scope. @@ -325,7 +330,7 @@ inner `match` `pop` : Notes a number of contexts to pop off of the stack. Multiple matches can have `pop` instructions. - Optional. + **Optional.** We'll use this rule to style nested complex fields in snippets: From 078c27a15f5dd82136f6d54d07615e5ee4e8267f Mon Sep 17 00:00:00 2001 From: Michael Lyons Date: Sun, 12 Jul 2026 21:56:01 -0400 Subject: [PATCH 17/17] Add a variables section to syntax tutorial --- docs/guide/extensibility/syntax/tutorial.md | 116 +++++++++++++++++++- 1 file changed, 113 insertions(+), 3 deletions(-) diff --git a/docs/guide/extensibility/syntax/tutorial.md b/docs/guide/extensibility/syntax/tutorial.md index 63cef984..d3761dd9 100644 --- a/docs/guide/extensibility/syntax/tutorial.md +++ b/docs/guide/extensibility/syntax/tutorial.md @@ -255,16 +255,103 @@ to "Sublime Snippet (Raw)" automatically, and you should get syntax highlighting if you type `$1` or any other simple snippet field. + +### Defining Variables for Regexes + Let's proceed to creating another rule for environment variables. ```yaml -# Variables like $PARAM1, $TM_SELECTION... +# Variables like $SELECTION, $TM_FILEPATH... - match: (\$)[A-Za-z][A-Za-z0-9_]* scope: variable.language.ssraw captures: 1: punctuation.definition.variable.begin.ssraw ``` +That doesn't quite match what users' experience will be, though: +not every word is a language-supported placeholder. +Let's demote the generic case to a generic variable scope +and add an allowlist of the specific words that snippets recognize. +Be sure to add the special rule before the catch-all. + +```yaml +# Known variables like $SELECTION, $TM_FILEPATH... +- match: (\$)(?:SELECTION|TM_SELECTED_TEXT|TM_LINE_INDEX|this gets very long) + scope: variable.language.ssraw + captures: + 1: punctuation.definition.variable.begin.ssraw + +# Unknown variables or incompletely-typed ones +- match: (\$)[A-Za-z][A-Za-z0-9_]* + scope: variable.other.ssraw + captures: + 1: punctuation.definition.variable.begin.ssraw +``` + +That list of variables is actually rather long, +and we might even want to use it elsewhere in our file. +Let's pull it out into `variables` and refer to it by name: + +```yaml +contexts: + main: + # ... + + # Known variables like $SELECTION, $TM_FILEPATH... + - match: (\$){{variable_name}} + scope: variable.language.ssraw + captures: + 1: punctuation.definition.variable.begin.ssraw + + # Unknown variables or incompletely-typed ones + - match: (\$)[A-Za-z][A-Za-z0-9_]* + scope: variable.other.ssraw + captures: + 1: punctuation.definition.variable.begin.ssraw + + # ... + +variables: + variable_name: |- + (?x:\b(?: + SELECTION + | TM_SELECTED_TEXT + | TM_LINE_INDEX + | TM_LINE_NUMBER + | TM_DIRECTORY + | TM_FILEPATH + | TM_FILENAME + | TM_CURRENT_WORD + | TM_CURRENT_LINE + | TM_TAB_SIZE + | TM_SOFT_TABS + | TM_SCOPE + )\b) +``` + +Now `variable_name` holds a regex snippet that can be re-used +by `match` patterns with `{{variable_name}}`. + + +::: tip Best Practice +Remember that variables are stamped directly +into the regex string as parsed by YAML. + +Avoid capturing groups if possible. +They will offset the `captures` in a match. + +But wrap your variables in a non-capturing group! +This lets the match patterns apply quantifiers. +If you assign variable **boolean** to `true|false`, +then `{{boolean}}?` will only make the `e` in "false" optional. +You want `(?:true|false)` instead. + +Long variables (or `match` regexes) can use multi-line mode +for ease of comprehension, +usually with a YAML block string. +You can even leave line comments with `#`. +::: + ### Push and Pop Rules @@ -440,12 +527,18 @@ contexts: captures: 1: punctuation.definition.variable.begin.ssraw - # Variables like $PARAM1, $TM_SELECTION... - - match: (\$)[A-Za-z][A-Za-z0-9_]* + # Known variables like $SELECTION, $TM_FILEPATH... + - match: (\$){{variable_name}} scope: variable.language.ssraw captures: 1: punctuation.definition.variable.begin.ssraw + # Unknown variables or incompletely-typed ones + - match: (\$)[A-Za-z][A-Za-z0-9_]* + scope: variable.other.ssraw + captures: + 1: punctuation.definition.variable.begin.ssraw + # Complex variables ${: ... } - match: (\$)(\{)[0-9]+(:) captures: @@ -469,6 +562,23 @@ contexts: scope: punctuation.section.interpolation.end.ssraw pop: 1 - include: main + +variables: + variable_name: |- + (?x:\b(?: + SELECTION + | TM_SELECTED_TEXT + | TM_LINE_INDEX + | TM_LINE_NUMBER + | TM_DIRECTORY + | TM_FILEPATH + | TM_FILENAME + | TM_CURRENT_WORD + | TM_CURRENT_LINE + | TM_TAB_SIZE + | TM_SOFT_TABS + | TM_SCOPE + )\b) ``` There are more available constructs and code reuse techniques,