あれこれ調べたり聞いたりしたまとめ。
あらまし
- Markdownを書くことが多いので、そのシンタックスハイライトにはちょっとだけこだわりがあった
- 具体的には、
# タイトル
と書くとき、#
とタイトル
を別の色にしたい - なので、それができるカラースキームを探して使ってた
というのが、だいたい去年の秋くらいまでの話。
ところが、ある日を境にそれができなくなった。
なんで?とは思いつつ、その原因を探るヒマがないまま月日は流れ・・・、今回その理由をやっと解明できた!という話。
TL;DR
Markdown missing header mark highlights · Issue #6260 · nvim-treesitter/nvim-treesitter https://github.com/nvim-treesitter/nvim-treesitter/issues/6260
このIssueがずばりそれ。
@markup.heading.1.marker.markdown
というハイライトグループが- ある日のコミットからなくなってしまった
#
部とタイトル部がまとめて@markup.heading.1.markdown
になってしまった
というのが原因で、その理由はメンテナンス性のためだそうな・・・。
詳しい事情は知らんけど、まあなんかいろいろあるんでしょう。
それぞれの関係
さて本題。
このあたりの仕組みを理解するには、登場人物とその役割を把握する必要がある。
tree-sitter
(ライブラリ)tree-sitter-markdown
(ライブラリ)- Nvim(のTreesitterインテグレーション)
nvim-treesitter
(プラグイン)
まず、tree-sitter
それ自体は、Cで書かれたソースコードをインクリメンタルに解析するためのライブラリ。
tree-sitter/tree-sitter: An incremental parsing system for programming tools https://github.com/tree-sitter/tree-sitter
で、その構文解析機を拡張する言語ごとのパーサーがいろいろあり、tree-sitter-markdown
はそのMarkdown向けのもの。
tree-sitter-grammars/tree-sitter-markdown: Markdown grammar for tree-sitter https://github.com/tree-sitter-grammars/tree-sitter-markdown/
ここまでの話には、まだNvimは出てこない。
Treesitter自体は汎用的なもので、シンタックスハイライトにも使えるけど、用途はそれだけに限らない。
たとえばast-grep
なんかはTreesitterを使ってASTを生成してるし、Nvimでもインデント検知や折りたたみなどなどの用途もある。
で、Nvimには(Experimentalではあるが)Treesitterインテグレーションが入ってる。
Treesitter - Neovim docs https://neovim.io/doc/user/treesitter.html
ただデフォルトでは、いくつかの言語向けのパーサーしか同梱されていないらしい。
そこで、nvim-treesitter
プラグインなり手動でやるなり、足りないものは自分でパーサーを追加してねという建付けとのこと。
パーサーとクエリスキーマとハイライトグループ
tree-sitter
とtree-sitter-markdown
の世界で得られるのは、あくまでTreesitterでソースコードをパースして得られた構文木だけ。
(document ; [0, 0] - [72, 0]
(section ; [6, 0] - [20, 0]
(atx_heading ; [6, 0] - [7, 0]
(atx_h2_marker) ; [6, 0] - [6, 2]
heading_content: (inline)) ; [6, 3] - [6, 15]
(list ; [8, 0] - [12, 0]
(list_item ; [8, 0] - [9, 0]
(list_marker_minus) ; [8, 0] - [8, 2]
(paragraph ; [8, 2] - [9, 0]
(inline))) ; [8, 2] - [8, 130]
(list_item ; [9, 0] - [10, 0]
(list_marker_minus) ; [9, 0] - [9, 2]
(paragraph ; [9, 2] - [10, 0]
(inline))) ; [9, 2] - [9, 98]
(list_item ; [10, 0] - [12, 0]
(list_marker_minus) ; [10, 0] - [10, 2]
(paragraph ; [10, 2] - [11, 0]
(inline) ; [10, 2] - [10, 77]
(block_continuation)))) ; [11, 0] - [11, 0]
(paragraph ; [12, 0] - [13, 0]
(inline)) ; [12, 0] - [12, 66]
(paragraph ; [14, 0] - [15, 0]
(inline)) ; [14, 0] - [14, 66]
こういうやつ。:InspectTree
ってやると見えるやつ。
で、これをシンタックスハイライトに使うためには、この木のノードの名称、つまりはキャプチャ名を、Nvimのハイライトグループにマッピングする必要がある。
それをやってるのは、highlights.scm
という名前のクエリファイルの呼ばれるもの。
[
(atx_h1_marker)
(atx_h2_marker)
(atx_h3_marker)
(atx_h4_marker)
(atx_h5_marker)
(atx_h6_marker)
(setext_h1_underline)
(setext_h2_underline)
] @punctuation.special
たとえばこれは、atx_h1_marker
などのキャプチャされたノードを、@punctuation.special
というハイライトグループに指定するという定義。
:Insepct
すると、カーソルのある場所の指定がわかる。
ちなみに以前はTS
プレフィックスのついたハイライトグループが使われてたけど、最近は@
キャプチャ名をそのまま使えるように変更されたらしい。
ともあれ、これでvim.api.nvim_set_hl(0, "@punctuation.special", { link = "Delimiter" })
のように書けばハイライトが適応できるし、各カラースキームが頑張って定義してくれてる。
実際にインストールされるファイルたち
クエリファイル自体は、nvim-treesitter
に同梱されてる。
リポジトリでいうと、/queries/{lang}/highlights.scm
にあるのがそれ。
なので、なんらかのプラグインマネージャーを使ってても、nvim-treesitter
のファイルが配置されるところを見れば、そのクエリファイルを見つけることができる。
人によって違うかもしらんけど、手元のlazy.nvim
環境の場合は、~/.local/share/nvim/lazy/nvim-treesitter
にあった。
紛らわしいのは、
tree-sitter-markdown
のような拡張パーサー側にも、クエリファイルが存在する場合がある- その場合でも、クエリファイルは
nvim-treesitter
側にあるものが使われる
というところ。
そしてこれらは同期されてるわけではない。ざっくり調べた限り、各拡張側のクエリファイルはしばらくアップデートされてない感じが多かった。
個人的にもうひとつ紛らわしく感じたのは、nvim-treesitter
にリポジトリに、lockfile.json
という各拡張ごとにコミットハッシュが列挙されたファイルがあること。
https://github.com/nvim-treesitter/nvim-treesitter/blob/master/lockfile.json
これは、パーサーのバージョンを追跡するためだけに使われるもので、:TSUpdate
した時に、このバージョンの変更に従ってパーサーをリビルドするためのもの。
そのコミットハッシュにおける、各拡張側のクエリファイルが使われるわけではない。
というわけで
それぞれの関係性がわかったところで、冒頭の#
だけ色を変えたいケースにどう対応すればいいかというと、現状では自分でクエリファイルを用意するしかなさそう。
2022年の nvim-treesitter の変更・新機能を振り返る https://zenn.dev/vim_jp/articles/2022-12-25-vim-nvim-treesitter-2022-changes#%E4%BB%8A%E5%BE%8C%E3%81%A9%E3%81%86%E3%81%99%E3%82%8C%E3%81%B0%E3%82%88%E3%81%84%E3%81%8B-1
そもそもなんでクエリの定義内容を変えたんよ?って思うけど、クエリの定義を他のエディタと共有できるようにしたいなど、水面下の動きもあるらしい。
Roadmap: Nvim-treesitter 1.0 · Issue #4767 · nvim-treesitter/nvim-treesitter https://github.com/nvim-treesitter/nvim-treesitter/issues/4767
奥が深い。