14.2K Views
April 26, 24
スライド概要
2024/05/25 のゴリラ.vim にて、Lua プラグインのライブコーディング前に事前知識として発表した資料です。
- Tree-sitter とは
- Neovim は Tree-sitter をどう使っているか
- Tree-sitter を活用した Neovim プラグイン
Neovim + Tree‑sitter で Lua プラグインを作ってみよう monaqa 2024 年 4 月 25 日
自己紹介 名前 monaqa GitHub @monaqa Bluesky monaqa.bsky.social テキストエディタ Neovim(5 年半) よく書く言語 Python, Rust, Typst, Lua, TypeScript 最近の Vim 活 Vim 駅伝で記事を書くなど 1/18
Tree‑sitter Tree‑sitter
Tree‑sitter とは テキストエディタでの利用に適した構文解析ライブラリ Tree‑sitter|Introduction https://tree-sitter.github.io/tree-sitter/ tree‑sitter / tree‑sitter An incremental parsing system for programming tools https://github.com/tree-sitter/tree-sitter Tree‑sitter 2/18
Tree‑sitter の活用事例 ◆ ◆ ◆ ◆ ◆ シンタックスハイライト — バッファの色付け ▶ 正規表現ベースのハイライトよりも賢く色付けできることが多い 折り畳み — テキストバッファの一部分を折りたたむ機能 テキストオブジェクト — テキストの一部分を選択する機能 ▶ Neovim では nvim‑treesitter‑textobjects が対応 見出し・アウトラインの作成 ▶ Neovim では aerial.nvim などが対応 賢いスペルチェック ▶ スペルチェックを行う範囲を指定できる(コメント内のみチェックする等) etc… Tree‑sitter 3/18
Tree‑sitter の利点 ◆ ◆ ◆ ◆ 様々な言語に対応 ▶ Tree‑sitter 自体はパーサジェネレータであり、自分でパーサが作れる ▶ 主要なプログラミング言語なら既にパーサ実装がある 高速 ▶ incremental parsing: 一部をパースし直すだけで構文木を更新できる ▶ 文字入力などの頻繁なソースコードの変更にも容易に追従できる 頑健 ▶ 多少構文エラーがあっても、可能な限りいい感じの構文木を返そうとする 依存が少ない ▶ C 言語で書かれており、様々なアプリケーションに組み込みやすい Neovim だけでなく、Helix, Zed など新しめのエディタにも採用されている Tree‑sitter 4/18
Tree‑sitter を用いたハイライトの仕組み Tree‑sitter 5/18
クエリ — 構文木の検索機能 ◆ 構文木のうち、特定のパターンに合致するノードを探す機能 ◆ クエリファイル: 構文木のどんなノードに注目すべきか記したファイル python/highlights.scm (identifier) @variable ▶ identifier という名前のノードを @variable という名前でキャプチャしてね ▶ identifier は @variable というハイライトグループで色付けしてね Tree‑sitter 6/18
クエリファイルの種類 ◆ Neovim では以下のようなクエリファイルが使われている ファイル名 用途 highlights.scm シンタックスハイライト ▶ injection.scm インジェクション indents.scm インデント folds.scm 折りたたみ インジェクション: 構文木の途中に、別の言語の構文木を生やすこと 例: Rust の doc comment を Markdown でハイライトする ● ◆ プラグインが自分専用のクエリファイルを作ることもできる Tree‑sitter 7/18
複雑なクエリ markdown/injections.scm (fenced_code_block (info_string (language) @_lang) (code_fence_content) @injection.content (#set-lang-from-info-string! @_lang)) ◆ 探すのは、以下のような木構造になっている箇所 - fenced_code_block - info_string - language - code_fence_content 8/18
複雑なクエリ markdown/injections.scm (fenced_code_block (info_string (language) @_lang) (code_fence_content) @injection.content (#set-lang-from-info-string! @_lang)) ◆ code_fence_content を @injection.content でキャプチャ ▶ @injection.content は「別の言語の構文木を埋め込んでね」という意味 ◆ language を @_lang でキャプチャ ◆ (#set-lang-from-info-string! @_lang) は ディレクティブ キャプチャ時にメタ情報を処理側に渡せる仕組み ▶ 今回は「言語判定には @_lang でキャプチャされた文字列を使ってね」の意味 ▶ 8/18
Neovim + Tree‑sitter Neovim + Tree‑sitter
Neovim は Tree‑sitter をどう使っているか 1. 本体の API ◆ vim.treesitter: tree‑sitter ライブラリのバインディング 2. nvim‑treesitter プラグイン ◆ Neovim で tree‑sitter の諸機能を便利に使うためのプラグイン ◆ パーサのインストールやクエリファイルの管理などを行う 3. remote modules ◆ Tree‑sitter の機能を用いるサードパーティのプラグイン ◆ nvim‑treesitter にも入っていない機能を使いたいときに適宜いれる ◆ 例: nvim‑treesitter / nvim‑treesitter‑textobjects Syntax aware text‑objects, select, move, swap, and peek support. https://github.com/nvim-treesitter/nvim-treesitter-textobjects Neovim + Tree‑sitter 9/18
各コンポーネントの相関図 Neovim + Tree‑sitter 10/18
nvim-treesitter の役割 1. パーサのインストール・管理 ◆ :TSInstall python で Python の tree‑sitter パーサをインストールしたり ◆ :TSUpdate で一斉に更新したり 2. クエリファイルの管理 ◆ キャプチャ名にはエディタ固有の事情が入るため、Neovim 側で管理が必要 3. 基本的な機能の提供 ◆ fold や indent などの機能を提供 ◆ 一部は vim.treesitter がその役割を担っている (highlighting) 4. 便利なヘルパー関数の提供 ◆ 最近は vim.treesitter にその役割を譲りつつある? Neovim + Tree‑sitter 11/18
カスタマイズに役立つ機能 :InspectTree — 現在開いているバッファの構文木を表示 Neovim + Tree‑sitter 12/18
カスタマイズに役立つ機能 :Inspect — カーソル位置にどんなハイライトが付いているか表示するコマンド Neovim + Tree‑sitter 12/18
カスタマイズに役立つ機能 :TSEditQuery {query-group} — クエリファイルを開くコマンド Neovim + Tree‑sitter 12/18
よくある誤解 Tree‑sitter ってシンタックスハイライトのための仕組みでしょ? ▶ たしかに賢いシンタックスハイライトは tree‑sitter の目玉機能の 1 つ ▶ だが tree‑sitter の本質は「いつでも手軽に構文木が使える仕組み」 ▶ アイデア次第で様々な用途に活かせる 折りたたみ アウトラインの作成 etc. ● ● Tree‑sitter で色を付けるとカラフルになりすぎるから苦手… ▶ たしかにデフォルトだと Vim のハイライトより色が多くなりがち ▶ 一方、色付けルールはユーザ側で自由にカスタマイズ可能 ▶ カラフルな色付けが苦手な人も、tree‑sitter を避ける必要はない Tree‑sitter は設定の自由度が高く、応用先も広い 13/18
よくある誤解 Tree‑sitter での色付けは、 Vim の syntax 機能での色付けより高速? ▶ 巨大なファイルの読み込み時は tree‑sitter でも遅い ▶ Vim の syntax 機能のほうが有利なことも 未使用変数を灰色にする、みたいなことも tree‑sitter でできる? ▶ Tree‑sitter が行えるのはあくまで構文解析のレイヤーまで ▶ 意味解析が必要なことは基本できない ▶(そういうのは language server protocol の仕事) Tree‑sitter は万能ではなく、Vim の syntax 機能の上位互換でもない 13/18
Tree‑sitter を活用した Neovim プラグイン Tree‑sitter を活用した Neovim プラグイン
Remote Plugin ◆ Tree‑sitter を活用して何かを実現するプラグイン ▶ vim.treesitter を使うのが基本 ▶ nvim-treesitter に依存しているものもある Neovim で tree‑sitter 機能を使う人の殆どは nvim‑treesitter を入れている ● Tree‑sitter を活用した Neovim プラグイン 14/18
プラグインの例 nvim‑treesitter / nvim‑treesitter‑textobjects Syntax aware text‑objects, select, move, swap, and peek support. https://github.com/nvim-treesitter/nvim-treesitter-textobjects 「関数」「クラス」といった単位でテキストを選択できる機能を提供 ◆「関数」を表す構文は当然言語により異なるが、 クエリファイルを整備することで それらの差異を吸収している ◆ Tree‑sitter を活用した Neovim プラグイン 15/18
プラグインの例 stevearc / aerial.nvim Neovim plugin for a code outline window https://github.com/stevearc/aerial.nvim バッファのアウトライン(見出し一覧)を表示するプラグイン ◆ tree‑sitter 連携機能はオプショナル ▶ language server protocol の機能を用いて見出しを表示することもできる ◆ tree‑sitter と連携する場合、クエリファイルを用いてカスタマイズ可能 ▶「何を見出しとして表示するか」をユーザで自由にいじれる ◆ Tree‑sitter を活用した Neovim プラグイン 15/18
プラグインを作ってみよう ◆ vim.treesitter API を使えば構文木にアクセスできる -- パーサを取得して、バッファを実際にパース local buf = vim.fn.bufnr("%") local parser = vim.treesitter.get_parser(buf) local tree = parser:parse()[1] local root_node = tree:root() -- markdown/highlights.scm に対応するクエリを取得 local query = vim.treesitter.query.get("typst", "highlights") -- root_node 配下でクエリにマッチする場所を探す local matches = vim.iter(query:iter_matches(root_node, buf)):totable() Tree‑sitter を活用した Neovim プラグイン 16/18
まとめ Tree‑sitter は万能ではありません。 17/18
まとめ Tree‑sitter は万能ではありません。 しかし、様々な用途に応用できる可能性を秘めています。 17/18
まとめ Tree‑sitter は万能ではありません。 しかし、様々な用途に応用できる可能性を秘めています。 Neovim と tree‑sitter との関係を把握して、 tree‑sitter を使いこなしてみませんか? 17/18
もっと深く知りたい方は ◆ Tree‑sitter そのものを深く知りたい人: 公式ドキュメントが最もおすすめ Tree‑sitter|Introduction https://tree-sitter.github.io/tree-sitter/ ◆ クエリファイルの弄り方については私の過去記事なども読んでみてください 日常に彩りを加える nvim‑treesitter の設定術 https://zenn.dev/monaqa/articles/2021-12-22-vim-nvim-treesitter-highlight ▶ ただしちょっと内容が古め 18/18