Today I learned how to create a VS Code extension to do custom syntax highlighting.
At my day job, developing Textual, I have been tasked with writing a VS Code extension that does proper syntax colouring of Textual CSS.
That's because Textual CSS looks a lot like regular CSS but the syntax highlighting of regular CSS doesn't look great. For example, most editors will highlight the code below in an uneven way:
ModalScreen, #input, .some-class {
border: heavy white;
tint: $primary 30%;
}
(Try copying & pasting into your code editor of choice.)
For example, the selectors #input
and .some-class
are highlighted but the selector ModalScreen
isn't.
Furthermore, Textual CSS has rules that regular CSS doesn't, and that is why border
is a different colour than tint
.
After getting started with VS Code's guide on writing your first extension, I found a guide specific to creating extensions that do custom syntax highlighting.
A few rabbit holes later and I had an extension that does syntax highlighting of the words "textual" and "Textual" in .tcss
files.
"All" I needed to do was create a new extension template with yo code
(as the first guide teaches).
Then, I went to the extension manifest file package.json
and modified the "contributes"
key:
{
"contributes": {
"languages": [
{
"id": "Textual CSS",
"extensions": [".tcss"]
}
],
"grammars": [
{
"language": "Textual CSS",
"scopeName": "source.tcss",
"path": "./syntaxes/tcss.tmGrammar.json"
}
]
}
}
First, we need to "contribute" a language, which is Textual CSS, and that corresponds to the language that we want to target with our extension.
Then, we contribute a grammar, which is the TextMate grammar file that specifies how tokens get parsed.
Token names and scopes are still a bit confusing to me, but after reading an article that is linked to by the VS Code guide, I seem to understand that we prefix the scope name with source
so that we create a nested scope inside source
.
In other words, the scope source
is already a scope that VS Code knows about and we are saying that our Textual CSS files not only contain source code (scoped as source
), but that they contain a more specific type of source code.
This nesting is achieved with the .tcss
.
Finally, we need to create the actual grammar file.
The file tcss.tmGrammar.json
currently looks like this:
{
"scopeName": "source.tcss",
"patterns": [{"include": "#textual"}],
"repository": {
"textual": {
"match": "(T|t)extual",
"name": "keyword"
}
}
}
The value that you attribute to "name"
inside "textual"
inside "repository"
is what classifies the words "textual" and "Textual" as a meaningful token.
In this example, I randomly chose keyword
.
Then, it is up to the theme that you use to specify what is the colour that keyword
tokens use.
In the screenshot below, you can see a couple of lines of text with some random highlighting:
That's it for now! Stay tuned and I'll see you around!
The next cohort of the Intermediate Python Course starts soon.
Grab your spot now and learn the Python skills you've been missing!