The same construct as above can be duplicated and just slightly altered in order to do something like this.
First, if we focus in on this part of the syntax example I provided above:
main:
- match: '<'
scope: punctuation.definition.note.begin
push: note
note:
- meta_scope: note.text
- match: '>'
scope: punctuation.definition.note.end
pop: true
Syntax definitions contain more or more context
sets that each specify a set of rules that are applied in order to match text and determine the structure of the file. There can only ever be one active context
at a time, and only rules in that active context
are used; all other rules in other contexts are ignored.
In this example, there are two context
s; main
and note
. There’s always a context
named main
, which specifies the rules that are in effect initially. You could think of this as the place where the “program” that is the syntax matching starts.
In addition to this, context
s are managed on a stack
. If you’re unfamiliar with the concept, think of a stack of plates at the end of a buffet; There are a lot of them there, but you can only take the plate off the top of the pile. If you put a plate on top, the next person that comes along has to take that plate before someone can get to the plate underneath, but you know specifically what plate will come next.
The operations you can do in a syntax definition regarding a context
is push
, pop
and set
(set
is not visualized here).
-
push
means “put a plate on top of the pile”
-
pop
means “take a plate off the top of the pile”
-
set
means “take a plate off the top of the pile, then put a new plate on top” (that is, it is the same as doing a pop
followed by a push
).
When the syntax definition starts parsing your file, it starts with the match
rules defined in the main
context. There’s only one thing that it recognizes, and that’s a <
character. Anything that’s not that character doesn’t match a rule and gets no special treatment.
However, when it sees a <
character the rule hits. The scope
applies data to the matched text to tell color schemes how to color it, and then there’s a push
.
The push
changes the active context from main
to note
; now the rules in main
are ignored and only the rules in note
are active. Here the match
rule is very similar to the other one, only when it sees the special character >
the action it takes is to pop
instead of push
. That tells the syntax engine to go back to the set of rules that used to be active before this one (or if you will, it’s “removing a plate” from the stack, revealing the main
plate).
So, replicating this is more or less a matter of adding an extra rule in main
that matches a different character (say #
) and a new context that has a rule that recognizes the ending character (say #
) and does a pop
. There’s one extra gotcha here, but lets skip that for a second.
If you’re going to have multiples of these, we could rewrite the original example to look like this instead:
main:
- match: '<'
scope: punctuation.definition.note.begin
push:
- meta_scope: note.text
- match: '>'
scope: punctuation.definition.note.end
pop: true
Here there is only one context
, main
. Everything works as it does in the original, except that the push
is pushing an “anonymous” context. We can see here that the indentation changed a bit, but the lines under the push
are doing exactly what they did in the previous example.
This sort of thing isn’t necessary, but it can make reading the resulting syntax a bit easier. Here we were creating a whole context
just to have a single rule, so it’s a bit clearer to just see exactly what’s going to happen all in one place.
Based on this, you could just duplicate this rule and change the match
rules as appropriate; for example:
- match: '#'
scope: punctuation.definition.note.begin
push:
- meta_scope: note.text
- match: '#'
scope: punctuation.definition.note.end
pop: true
This match
rule does the same as the above, but uses different characters. It’s also self contained.
Adding this will sort of do what you want, except for that “Gotcha” I mentioned above. This rule applies the same note.text
scope to the text as the first rule does, which means that as far as the color scheme is concerned the two kinds of notes are identical, so they’ll appear in the same color:
In order to apply different colors, each item needs to have a distinct scope applied to it, so you would need to adjust the syntax definition to take that into account.
With the changes explained here, the original example would become something similar to this:
%YAML 1.2
---
# See http://www.sublimetext.com/docs/3/syntax.html
file_extensions:
- prose
scope: text.prose
name: My Prose File Type
contexts:
main:
- match: '<'
scope: punctuation.definition.note.one.begin
push:
- meta_scope: note.text.one
- match: '>'
scope: punctuation.definition.note.one.end
pop: true
- match: '#'
scope: punctuation.definition.note.two.begin
push:
- meta_scope: note.text.two
- match: '#'
scope: punctuation.definition.note.two.end
pop: true
Here we’ve added one
and two
to the scopes in each item to create a note.text.one
and a note.text.two
. If you have specific notions about what each style of note is meant to represent, you could replace one
and two
with different terms to make everything clearer (or maybe it’s just about the colors ).
With the new syntax rules in place, you can add in extra rules in your color scheme to be able to match them.
For example:
{
"scope": "text.prose note",
"foreground": "red",
"background": "darkblue",
},
{
"scope": "text.prose note.text.two",
"foreground": "white",
"background": "darkgreen",
},
This is just the content of the rules
part of the color_scheme
example I gave originally, and the first rule is still the same. There’s now also a second rule that specifically matches note.text.two
, so it will catch the notes of that type. The other rule is less specific, so it applies to note.text.one
and colors things as before: