I’ve started using Microsoft’s new Visual Studio Code text editor at work and it’s pretty neat. I like it more than the Brackets editor that I was using before. It’s similar, but more polished and has some excellent features like an integrated terminal and a debugger. As you might expect there is a comprehensive extension repository but once again there was no syntax highlighter for Asterisk dialplan code so I took it upon myself to fill the void and wrote one. This time around there was no existing language grammar for me to use, so it was a slightly more formidable undertaking.

If you’d like to make your own extension I’d recommend following the extensive documentation to get started, it’s all explained very well and I won’t repeat it here. You can npm install Yeoman and the VS Code Extension Generator to create the boiler plate including a howto reference which is nice to have on hand.

The extension framework is fairly simple, define the extension properties in package.json and create a syntaxes/asterisk.tmLanguage file for the TextMate grammar. A few extra features are defined in the language-configuration.json file, such as the comment character, character pairs that will be auto-closed and characters that can be used to surround a selection, like quote marks or braces.

Syntax highlighting extensions use the TextMate framework for language grammars, similar to Atom and Sublime and .. well, TextMate. A grammar is defined by defining a regex that will match a syntax element and then setting a scope. The scope might be comment.quoted or meta.function and depending on your current colour theme, each scope will take on a certain colour and style in your editor.

Fully fledged language extensions (i.e. intellisense, auto-correct) are much more complex, I didn’t need such features for this extension.

The Grammar

Creating the regexes for the Asterisk dialplan grammar was equal parts easy and hard. I don’t know of an official definition for the grammar so I just worked with the syntax that I know, and the little documentation there is. This is further compounded by the fact that I work with an older version of Asterisk (1.6), so there is some newer syntax I’m not familiar with. In any case, I did what I could, and I will improve the extension as I can.

Matching keywords, or a variable definition, or a function call was pretty easy. It was harder to match a variable inside a quoted string inside a function call, but it was all possible, and after some trial and error I have it working quite nicely.

Here’s an example of a simple match for a file import declaration like #include extensions.conf.

<dict>
    <key>match</key>
    <string>^#include</string>
    <key>name</key>
    <string>keyword.control.import</string>
</dict>

Here’s an example of a much more complicated match on a variable that can contain a nested variable ${CHANNEL_${MAX_CHANNELS}}, or a nested function ${CDR(accountcode)}. They key to effectively writing this match was capturing the open and closing parts, then capturing the nested function and including a match on $self for nested variables before finally matching on the rest of the inner text with a match on anything that isn’t the closing part.

<key>VariableNested</key>
<dict>
    <key>begin</key>
    <string>(\$\{)</string>
    <key>beginCaptures</key>
    <dict>
        <key>1</key>
        <dict>
            <key>name</key>
            <string>variable</string>
        </dict>
    </dict>
    <key>end</key>
    <string>(\})</string>
    <key>endCaptures</key>
    <dict>
        <key>1</key>
        <dict>
            <key>name</key>
            <string>variable</string>
        </dict>
    </dict>
    <key>patterns</key>
    <array>
        <dict>
            <key>include</key>
            <string>#FunctionNested</string>
        </dict>
        <dict>
            <key>include</key>
            <string>$self</string>
        </dict>
        <dict>
            <key>match</key>
            <string>[^}]</string>
            <key>name</key>
            <string>variable</string>
        </dict>
    </array>
</dict>

A cool feature of the grammar file is the repository. You can create a repository of named matches using the <repository> tag. Once defined, you can include a named match in the main part of the file as required. This could be a top level match, or a sub level match. In my grammar, I define a variable, and then I include it as a stand alone variable, as a nested variable inside a quoted string, as a nested variable inside a function and also as a nested variable inside an expression. Don’t repeat yourself! :)

I found the TextMate manual and the Sublime scope naming page very helpful. I also dug out the colour theme definitions inside the VS Code folder to see which scopes the included themes target for highlighting. I ended up choosing some scopes that were semantically incorrect, but resulted in a better highlight across multiple themes. I don’t feel particularly good about that, but at the end of the day, better highlighting means faster Asterisk dialplan development, so I compromised on correctness.

Publishing the extension was simple, but you do need to create a Visual Studio Team System (VSTS) account in order to generate a Personal Access Token. The documentation walks you through the process. Once you have your token, publish the extension by running vsce publish -p <token> and you’re done!

You can see the full extension code on Github.