Syntax Highlighting for Embedded Languages inside VS Code
Wrote a cool DSL and want to get syntax highlighting within a host language? Want to add syntax highlighting support for an existing language? This guide will walk through how to modify a VS code extension to do so.
In this example, a DSL for programming board games is embedded into typescript’s tag functions. When we’re done, it will look something like this:
Note: This guide will assume you have written a regular syntax-highlighting extension. VS Code has a guide on how to do this.
Note: The > Developer: Inspect Editor Tokens and Scopes
command in VS Code is useful for debugging which scopes are assigned.
Creating the injection syntax
First, create a file like the following to find points within your host language’s source. In this example, begin
is a regex which will find the start of a string template literal utilizing a tag function called ludi
.
syntaxes/ts-injection.tmLanguage.json
{
"scopeName": "source.ts.embedded.ludi",
// source.ts is the scope for our host language
// `L` means to inject this grammar before `source.ts`
// `-string -comment` means to exclude string and comment scopes
"injectionSelector": "L:source.ts -string -comment",
"patterns": [
{
"begin": "(?:ludi)\\s*`", // Look for strings like ludi`
"end": "`", // Look for the ending `
"contentName": "meta.embedded.block.ludi.typescript source.ludi",
"patterns": [
{
// Process the ludi scope inside
"include": "source.ludi"
}
]
}
]
}
Adding the injection to the plugin
Next, the grammar is added to package.json
. In this example, support for entire ludi language already exists, so only the last section is added.
package.json
{
"name": "ludi",
...
"contributes": {
// Language configuration
"languages": [{
"id": "ludi",
"aliases": ["Ludi", "ludi"],
"extensions": [".ludi"],
"configuration": "./language-configuration.json"
}],
"grammars": [
// Grammar for Ludi
{
"language": "ludi",
"scopeName": "source.ludi",
"path": "./syntaxes/ludi.tmLanguage.json"
},
// Injecting into typescript's grammar. This part is new.
{
"path": "./syntaxes/ts-injection.tmLanguage.json",
"scopeName": "source.ts.embedded.ludi", // Scope matches `scopeName` in the grammar
"injectTo": ["source.ts"], // Host language's scope
"embeddedLanguages": {
"meta.embedded.inline.ludi.typescript": "ludi" // The language id defined above
}
}
]
}
}
That’s it, you’re done!