GitHub ActionsのワークフローにはYAMLファイルを使いますが、Vimのシンタックスハイライトがうまく効かなくて困ることがよくありました。
Actionsでは複数行にわたる文字列に複雑なシェルスクリプトを書くことが多いのですが、
その中の一部がYAMLのフロースカラースタイルの文字列として認識されてしまい、ハイライトが壊れることがあるのです。
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: |
echo "Hello world!" | cut -f1 -d" "
echo "This line is broken!"
- env:
test: ${{ inputs.* | join(' ') }}
run: |
echo 'This line is broken!'
はてなブログも全く同じ問題があることから、中ではVimのシンタックスハイライトが使われているようです。
私の修正がブログのシステムに取り込まれると、このエントリーも更新できなくなりますが…
参考: YAMLの用語
参考: YAMLの用語
ブロックスカラースタイル: |
これはブロックスカラースタイルの
文字列です (改行を維持しない > もあるよ)
フロースカラースタイル:
"これはダブルクォートされた
フロースカラースタイルの
文字列です (シングルクォートもあるよ)"
プレインスタイル: これは、
クォートのないフロースカラースタイル、
つまりプレインスタイルの文字列です
ブロックスカラースタイルのコードの中のシェルスクリプトとしての文字列に色がつくのは便利な気もしますが、YAMLのフロースカラースタイルの文字列は改行も含めるなど自由度が高いので、ブロックスタイルの文字列の中でフロースタイルの解釈をするのはおかしいですね。
また、プレインスタイル文字列の中のシングルクォートの解釈もおかしいです (上のjoin
の例)。
GitHub Actionsをよく触るようになってからYAMLのシンタックスハイライトが壊れることが多くなったので、重い腰を上げてVimのYAMLのシンタックスハイライトを改善しました (色々とあって取り込まれてからブログを書くまで時間がかかってしまいました…)。
github.com
このパッチの中でも、特に重要なのは以下の部分です。
syn match yamlBlockScalarHeader '[|>]\%([1-9][+-]\|[+-]\?[1-9]\?\)\%(\s\+#.*\)\?$' contained
\ contains=yamlComment nextgroup=yamlBlockString skipnl
syn region yamlBlockString start=/^\z(\s\+\)/ skip=/^$/ end=/^\%(\z1\)\@!/ contained
|-
や>1-
といったブロックスカラースタイルのヘッダーを正しく認識し、その後に続くブロックスカラースタイルの文字列を認識するようになりました。
これによって、文字列の中に複雑なシェルスクリプトを書いてもYAMLとしてのハイライトが壊れにくくなりました。
Vimのシンタックス定義の詳しいドキュメントはこちらです。
ここでは軽くシンタックスを作る時の考え方を書いておこうと思います。
Vimのシンタックス定義の二つの大事な考え方は、nextgroup
による状態遷移と、contains
による構文アイテムの入れ子構造です。
シンタックスハイライトの構文アイテムには、トップレベルのアイテムとそうではないものがあります。
まずはトップレベルの構文アイテムでもって、そのパターンがあればファイルのどこでも認識したいものを定義し、そこからnextgroup
を使って次に続くアイテムを指定していくというのが基本的な考え方です。
例えば、YAMLだと行が /^\s*\zs-\ze\%(\s\|$\)/
にマッチした場合は基本的にファイルのどこでもリストのマーカーとして認識させたいので、トップレベルのアイテムとして定義します。
トップレベルのアイテムがあることで、ファイルの途中からでも構文を認識できるようになっています。
多くのプログラミング言語(PietやBefungeといった例外を除く)の文法は、木構造で表現できます。
つまり構文要素の隣接関係と包含関係によって表現できるということです。
これらに対応するのが、nextgroup
とcontains
です。
隣接関係とは、function
というキーワードの後には識別子が続き、さらに開き括弧、引数、閉じ括弧、ブロックの開始が続くといった感じです。
Vimのシンタックス定義では、nextgroup
というオプションで次に続く構文アイテムを指定することができます。
ここにはトップレベルではないアイテム、contained
というオプションを指定したアイテムを指定します。
実際に書いていくと同じアイテムたちを何度も指定したくなりますが、そのような場合はsyntax cluster
を使うと便利です。
トップレベルのアイテムとnextgroup
を使えばある程度の文法は表現できるのですが、これだけでは表現できない文法もあります。
例えば、"1 ${2 + 3} 4"
みたいな文字列補間は、隣接関係だけでは表現できません。
他にも、文字列の中の\uXXXX
のようなエスケープシーケンスとか、コメントの中のTODO
とか、そういうものに色をつけたいことがあります。
こういった包含関係を表現するために、contains
というオプションで構文アイテムの入れ子構造を表現します。
状態遷移と入れ子構造を組み合わせることで、Vimのシンタックス定義は複雑な構文に対応しているのです。
今回、YAMLのシンタックスハイライトを改善したことで、Vimのシンタックス定義の仕組みを改めて自分の中で整理できました。
Vimのシンタックスを改善したい人に、この記事が参考になれば幸いです。
個人的には、よく編集するGitHub Actionsのワークフローファイルでのハイライトを改善できましたし、積年のいくつかの問題 (#8234・#10730・#11517) を一気に解決できたので、とても満足しています。
それでは、また。