【ITニュース解説】Custom Parser Highlighting Not Working in nvim-treesitter? The Cause and a Solution Using the `after/` Directory
2025年09月20日に「Dev.to」が公開したITニュース「Custom Parser Highlighting Not Working in nvim-treesitter? The Cause and a Solution Using the `after/` Directory」について初心者にもわかりやすく解説しています。
ITニュース概要
Neovimのnvim-treesitterでカスタムパーサーのハイライトが適用されない場合、nvim-treesitterがカスタムリポジトリの色付けルールを読み込まないのが原因だ。解決策は、ハイライト定義を`after/queries/{ファイルタイプ}/`に配置し、カスタムパーサーリポジトリをプラグインとして読み込むこと。
ITニュース解説
Neovimは、多くのプログラマーに利用されている高機能なテキストエディターだ。特に、最近のNeovimでは「Tree-sitter」という技術が標準で組み込まれており、これがコードの構文解析とハイライト(色付け)の精度を大きく向上させている。Tree-sitterは、コードを単なる文字列としてではなく、その構造を理解し、意味のあるまとまり(ノード)として解析する。これにより、関数名や変数名、キーワードなどが正確に色分けされ、コードが非常に読みやすくなる。システムエンジニアにとって、この正確な構文ハイライトは、日々の開発作業においてコードの理解度を高め、エラーを発見しやすくする重要な機能だ。
しかし、Tree-sitterを使っていると、標準的なプログラミング言語(例えばC++やPythonなど)は問題なくハイライトされる一方で、特定のフレームワークやカスタム構文に対応する際、思わぬ問題に直面することがある。今回解説する記事は、まさにそうしたケース、具体的には「自作したTree-sitterパーサー(構文解析器)をNeovimで使おうとしたのに、なぜか構文ハイライトが適用されない」という問題に焦点を当て、その原因と具体的な解決策を詳しく説明している。これは、カスタムな開発環境を構築しようとするシステムエンジニアの卵たちにとって、非常に役立つ知識となるだろう。
記事の筆者は、人気ゲーム開発エンジンのUnreal Engine(UE)向けにNeovimでの開発をより快適にするため、UE特有のC++マクロ(例えば、クラス定義に使うUCLASSやプロパティ定義に使うUPROPERTYなど)をTree-sitterが認識できるように、独自のパーサー「tree-sitter-unreal-cpp」を作成した。このカスタムパーサーを作る過程とテストは順調に進み、Neovimに組み込んだ後も、nvim-treesitterというNeovimのプラグイン設定で、標準のC++パーサーを自作パーサーに置き換える設定を行った。具体的には、nvim-treesitterの設定ファイル内で、C++パーサーのインストール情報に自作パーサーのリポジトリURLを指定したのだ。
設定後、Neovimの:コマンドで:TSPlaygroundToggleというTree-sitterのデバッグ機能を試してみると、自作パーサーが正しく機能していることが確認できた。例えば、UCLASSマクロがuclass_macroという独自のノードとして認識され、コードの構造が正しく解析されていることが視覚的に表示されたのだ。これはパーサー自体は正常に動作していることを意味する。しかし、エディター上ではこれらのカスタムマクロに一切色がついておらず、期待したハイライトが適用されないという問題が発生した。さらに、:TSCaptureUnderCursorという現在カーソルがある箇所のハイライトグループを調べるコマンドを使っても、何も検出されなかった。これは、パーサーはコード構造を理解しているものの、その構造に対してどのような色を付けるべきかという情報がNeovimに伝わっていない状態を示していた。なぜパーサーは動いているのに、ハイライトだけが機能しないのか、これが記事の核心となる問題だった。
この奇妙な現象の背後には、nvim-treesitterというプラグインのクエリファイルの読み込みに関する特定の仕様があった。nvim-treesitterは、パーサー本体のインストールについては非常に柔軟で、設定で指定されたGitHubリポジトリのURLからソースコードをダウンロードし、そこからパーサーをビルドして利用してくれる。筆者もこの機能を使って、自作のUnreal C++パーサーをNeovimに組み込むことができたのだ。
しかし、構文ハイライトの定義などを行う「クエリファイル」(通常は.scmという拡張子を持つ)については、nvim-treesitterは異なる挙動をする。たとえカスタムパーサーのリポジトリ内にqueries/{filetype}/highlights.scmといったパスでクエリファイルが配置されていても、nvim-treesitterはそれらを自動的に読み込もうとしないのだ。その代わりに、nvim-treesitter自身が内部に持っているqueries/{filetype}/ディレクトリ内のクエリファイルを常に優先して利用する。これは、nvim-treesitterが提供する標準のハイライト定義を常に基盤として使うことを意図した設計だと言える。
つまり、筆者が作成したtree-sitter-unreal-cppリポジトリの中に含まれていた、Unreal Engine特有のマクロに対するハイライト定義が書かれたクエリファイルは、nvim-treesitterによって完全に無視されていたのだ。パーサーがコードを正しく解析し、uclass_macroのようなカスタムノードを認識しても、そのノードに「@attribute」といったハイライトの種類を割り当てるための情報がnvim-treesitterに伝わっていなかったため、最終的にエディター上での色付けが行われなかったのである。これが、パーサーは機能するのにハイライトだけが適用されないという問題の根本原因だった。この仕様は、初めてカスタムパーサーを扱う開発者には直感的に分かりにくい点で、多くの人が同じように戸惑う可能性があるだろう。
この問題を解決するための鍵は、Neovimが提供する「after/ディレクトリ」という特別な仕組みを理解し、活用することだ。Neovimには、設定ファイルを読み込む際の決まった順序があり、通常の設定ファイル群が読み込まれた「後」に、after/ディレクトリ内に配置されたファイルが追加で読み込まれるという特性がある。この特性を利用することで、既存の設定を上書きするのではなく、既存の設定を「拡張」したり「追加」したりすることが可能になる。
今回のケースで言えば、nvim-treesitterが標準のC++クエリファイルを読み込んだ後、さらにafter/ディレクトリ経由で、自作パーサーに関する追加のハイライト定義を読み込ませることで、問題を解決できる。これにより、標準のC++構文のハイライトはそのまま維持しつつ、Unreal Engine特有のカスタムマクロにも適切なハイライトを適用できるようになるのだ。この方法は、既存のシステムと協調しながら、必要な部分だけカスタマイズを加えるという、システム開発でよく使われる設計思想にも通じる。
具体的な解決策は、二つのステップから成る。一つ目は、カスタムパーサーのリポジトリ内に、after/queries/{filetype}/highlights.scmという特定のパス構造でクエリファイルを作成すること。具体的には、リポジトリのルートディレクトリ直下にafterという名前のディレクトリを作成し、その中にさらにqueries/cpp/highlights.scmというパスでファイルを作成する。このhighlights.scmファイルには、nvim-treesitterが読み込む既存のC++ハイライトクエリを「拡張」する形で、自作パーサーで認識される新しい構文要素(この場合はUnreal Engineのマクロ)に対するハイライト定義のみを記述する。ファイルの先頭に;; extendsというコメントを追記すると、このファイルが既存のクエリを拡張する意図で作成されたものであることを明示できる。例えば、記事の例では、uclass_macroというパーサーで認識されるノードに対して、"UCLASS" @attributeという記述を加えることで、UCLASSというキーワードを「属性」としてハイライトするよう定義している。このように、既存のC++の構文解析やハイライト定義には一切手を加えず、純粋に新しい構文要素に対するハイライト情報だけを追加していくのだ。
二つ目のステップは、そのカスタムパーサーのリポジトリ自体をNeovimのプラグイン管理システム(記事ではlazy.nvimを使用)に「プラグインとして」登録することだ。これは単にパーサーのソースコードを読み込む以上の意味を持つ。Neovimのプラグインとして登録されると、そのプラグインのリポジトリ内に存在するafter/ディレクトリが、Neovimの「ランタイムパス」に自動的に追加される。ランタイムパスとは、Neovimが設定ファイルやスクリプト、プラグインなどを探しに行くためのディレクトリのリストのことだ。このリストにafter/ディレクトリが追加されることで、Neovimは、nvim-treesitterが標準のC++クエリファイルを読み込んだ後、さらにカスタムパーサーリポジトリ内のafter/queries/cpp/highlights.scmファイルを発見し、その中に書かれているハイライト定義も追加で読み込んでくれるようになるのだ。記事では、lazy.nvimの設定ファイルに、自作パーサーのリポジトリをnvim-treesitterのプラグイン設定とは別に、独立したプラグインとして記述している。以前行ったnvim-treesitterの設定で、標準C++パーサーを自作パーサーに置き換えるためのinstall_info設定は依然として必要であり、これはパーサー本体をNeovimに認識させる役割を果たす。そして、今回追加したプラグインとしてのロードが、カスタムハイライトクエリを読み込ませる役割を果たす。この二段階のアプローチにより、最終的にすべてのC++コードが期待通りに色分けされて表示されるようになる。
今回の問題解決の肝は、nvim-treesitterがパーサー本体とクエリファイルを異なる方法で扱うという特性を理解することにあった。この問題に対する効果的な解決策は、Neovimのafter/ディレクトリという仕組みと、プラグインのロード方法を組み合わせることで実現された。この方法を用いることで、既存のC++ハイライト定義を壊すことなく、Unreal EngineのUCLASSやUPROPERTYといったカスタムマクロにも適切に色がつき、期待通りの構文ハイライトが実現される。システムエンジニアを目指す上で、このような既存のシステムと自作の拡張機能を連携させる技術は非常に重要だ。特に、オープンソースのツールやフレームワークを自分のニーズに合わせてカスタマイズする際には、今回のような「システムの内部動作を理解し、その仕様に合わせた解決策を見つける」という思考プロセスが大いに役立つだろう。この知識は、Neovimに限らず、様々な開発環境やツールのカスタマイズに応用できる基本的な考え方となる。