VS Code の Elixir Syntax Highlighting を少しカスタマイズ
記事に関連する範囲で自己紹介すると、
- 最近 Elixir の勉強をしている
- ふだん VS Code を使う
- ノートは Markdown で書くことが多い
- Croma という Elixir ライブラリを使う機会がある
という感じです。
この記事の大まかな内容は以下の 2 つです:
- Markdown において Elixir の fenced code block に syntax highlighting したい
- Croma の
defun
,defunp
,defunpt
も syntax highlighting したい
VS Code Extension もつくりました。
Elixir の fenced code block に syntax highlighting
実はこの機能は vscode-elixir にすでに PR (#124) されていますが、まだ merge されていません。待てないので、以下 2 通りの方法を考えてみます。
- vscode-elixir を fork し、#124 の変更部分を merge し、自分で packaging する。
- vscode-elixir とは別に extension をつくる。
前者のほうが簡単ですが、後述する Croma に関する部分も考慮して extension をつくってしまうことにしました。
Extension をつくる
公式の Getting started にあるように Yeoman と VS Code Extension Generator を使います。ただし、Getting started では type of extension として New Extension (TypeScript)
を選択していますが、ここでは New Language Support
を選択します。質問に答えていくと extension のソースを含む directory が生成されます。
Extension の中身については、上で述べた PR #124 や vscode-fenced-code-block-grammar-injection-example がとても参考になります(参考というか、そのまま使えます)。
Croma の syntax highlighting
ここでは Croma.Defun
に注目します。このモジュールには型情報を伴う関数を簡潔に定義するためのマクロとして defun
, defunp
, defunpt
があります。やっていることは関数定義なので、def
や defp
と同じようにハイライトされて欲しい、というのが私の希望です。
vscode-elixir では、ハイライトのルールは syntaxes/elixir.json に書かれています。関数に関する部分を抜き出すと下記のようになります。
{
"comment": "Function with parameters",
"begin": "\\b(defp?|defmacrop?)\\b\\s*([_$a-z][$\\w]*[!?]?)\\s*\\(",
"beginCaptures": {
"1": {
"name": "keyword.control.elixir"
},
"2": {
"name": "entity.name.function.elixir"
}
},
"end": "\\)",
"patterns": [
{
"include": "$self"
},
{
"include": "#function_parameter"
}
]
},
{
"comment": "Function without parameters",
"match": "\\b(defp?|defmacrop?)\\b\\s*([_$a-z][$\\w]*[!?]?)",
"captures": {
"1": {
"name": "keyword.control.elixir"
},
"2": {
"name": "entity.name.function.elixir"
}
}
},
(defp?|defmacrop?)
のところを (defun(p|pt)?)
とすればマッチしそうです。
Extension に機能を追加する
先に作成した extension に Elixir-Croma
という言語名で syntax 情報を記述します。元々の Elixir
との差分は defun
, defunp
, defunpt
だけなので、パターンの大部分は include して使えます。ルールを記述した elixir-croma.json
は下記のようになります。
{
"comment": "Syntax Parser for Elixir using Croma.",
"fileTypes": [
"ex",
"exs"
],
"firstLineMatch": "^#!/.*\\belixir",
"foldingStartMarker": "(after|else|catch|rescue|\\-\\>|\\{|\\[|do)\\s*$",
"foldingStopMarker": "^\\s*((\\}|\\]|after|else|catch|rescue)\\s*$|end\\b)",
"name": "Elixir-Croma",
"patterns": [
{
"include": "source.elixir"
},
{
"comment": "Function (Croma) with parameters",
"begin": "\\b(defun(p|pt)?)\\b\\s*([_$a-z][$\\w]*[!?]?)\\s*\\(",
"beginCaptures": {
"1": {
"name": "keyword.control.elixir"
},
"2": {
"name": "keyword.control.elixir"
},
"3": {
"name": "entity.name.function.elixir"
}
},
"end": "\\)",
"patterns": [
{
"include": "$self"
},
{
"include": "#function_parameter"
}
]
},
{
"comment": "Function (Croma) without parameters",
"match": "\\b(defun(p|pt)?)\\b\\s*([_$a-z][$\\w]*[!?]?)",
"captures": {
"1": {
"name": "keyword.control.elixir"
},
"2": {
"name": "keyword.control.elixir"
},
"3": {
"name": "entity.name.function.elixir"
}
}
}
],
"scopeName": "source.elixir-croma"
}
個人的にポイントだと思っているのは以下の 2 点です:
-
"include": "source.elixir"
で vscode-elixir のパターンを流用している。 -
(defp?|defmacrop?)
を(defun(p|pt)?)
に変更したため()
の数が 1 つ増えた。その結果、正規表現における 3 番目のグループが関数名になるため、"3": {"name": "entity.name.function.elixir"}
とする。
このあたりの詳細は公式のここに書いてあります。
見た目はこんな感じです
おわりに
Language support の VS Code extension は JSON をいじるだけでつくれるのでお手軽です。
追記
vscode-icons を使っている場合、ファイルの言語を Elixir-Croma
に設定したときに Elixir のファイルアイコンが表示されなくなってしまいます。settings.json
に下記を追加しておく必要があります。このへんの設定を extension settings として簡単に管理できるようにしたいです。。。
"vsicons.associations.files": [
{
"icon": "elixir",
"languages": [
{
"ids": "elixir-croma",
"defaultExtension": "ex"
},
],
"format": "svg"
}
]
追々記
この記事では Elixir-Croma
という新しい言語設定を作成しましたが、言語が変わることによって使えなくなる機能も出てくることが分かりました。結局、既存の Elixir syntax にパターンを追加するほうが良いという結論に至りました。このあたりの詳細は公式のここに書いてあります。
自作の VS Code extension もアップデートしてあります。