Lem
GitHubDiscord Matrix モードの切替 ダーク/ライト/自動モードの切替 ダーク/ライト/自動モードの切替 ダーク/ライト/自動トップページへ戻る

Tree-sitter 統合

Lemはtree-sitterを統合し、高度な構文ハイライトとインデントを提供しています。Tree-sitterはインクリメンタルパースを提供し、編集に応じて構文木を効率的に更新できるため、パターンマッチングだけでなくコード構造を理解した機能が可能になります。

概要

LemのTree-sitter統合は以下を提供します:

  • 構文ハイライト: ASTノードに基づいた正確で言語対応のハイライト
  • インデント: Tree-sitterクエリを使用した構造ベースのインデント
  • インクリメンタルパース: ファイル全体を再パースせずに効率的な更新
  • グレースフルフォールバック: Tree-sitterが利用できない場合は正規表現ベースのハイライトにフォールバック

アーキテクチャ

                              ┌─────────────────┐
                              │  tree-sitter-cl │
                              │  (FFIバインディング) │
                              └────────┬────────┘
                                       │
┌─────────────────┐           ┌────────▼────────┐
│   言語モード    │──────────▶│ lem-tree-sitter │
│  (json, nix...) │           │   (拡張機能)    │
└─────────────────┘           └────────┬────────┘
                                       │
                              ┌────────▼────────┐
                              │   Lem バッファ   │
                              │  (syntax-parser) │
                              └─────────────────┘

統合は以下のコンポーネントで構成されます:

コンポーネント場所目的
tree-sitter-cl外部ネイティブtree-sitterへのFFIバインディング
lem-tree-sitterextensions/tree-sitter/コア統合モジュール
クエリファイルextensions/<mode>/tree-sitter/言語ごとのハイライト/インデントルール

対応言語

以下のモードがTree-sitterをサポートしています:

言語構文ハイライトインデント
JSON-
YAML-
Nix
Markdown-
WAT (WebAssembly Text)-

モードにTree-sitterサポートを追加する

ステップ1: クエリファイルの作成

モードの拡張フォルダ内にtree-sitter/ディレクトリを作成します:

extensions/your-mode/
├── your-mode.lisp
├── your-mode.asd
└── tree-sitter/
    ├── highlights.scm    # 構文ハイライトルール
    └── indents.scm       # インデントルール(オプション)

ステップ2: ハイライトクエリの作成

ハイライトクエリはTree-sitterのS式クエリ構文を使用します。各クエリはASTノードをキャプチャし、ハイライトグループに割り当てます。

簡単な言語のhighlights.scmの例:

;; コメント
(comment) @comment

;; キーワード
[
  "if"
  "else"
  "let"
  "in"
  "function"
] @keyword

;; リテラル
(string) @string
(number) @number
(boolean) @constant.builtin

;; 関数
(function_definition
  name: (identifier) @function)

(function_call
  function: (identifier) @function.call)

;; 変数
(variable_declaration
  name: (identifier) @variable)

;; 演算子
["+" "-" "*" "/" "==" "!="] @operator

;; 括弧
["(" ")" "{" "}" "[" "]"] @punctuation.bracket
["," ";" ":"] @punctuation.delimiter

ステップ3: モード定義でTree-sitterを有効化

モードのLispファイル内:

(defpackage :lem-your-mode
  (:use :cl :lem :lem/language-mode))
(in-package :lem-your-mode)

(defvar *your-syntax-table*
  (let ((table (make-syntax-table ...)))
    ;; ベースの構文テーブルを設定
    table))

(defun tree-sitter-query-path ()
  "Tree-sitterハイライトクエリのパスを返す。"
  (asdf:system-relative-pathname :lem-your-mode "tree-sitter/highlights.scm"))

(defun tree-sitter-indent-query-path ()
  "Tree-sitterインデントクエリのパスを返す。"
  (asdf:system-relative-pathname :lem-your-mode "tree-sitter/indents.scm"))

(define-major-mode your-mode language-mode
    (:name "Your Mode"
     :syntax-table *your-syntax-table*
     :mode-hook *your-mode-hook*)
  ;; Tree-sitterを有効化(オプションのインデントクエリ付き)
  (lem-tree-sitter:enable-tree-sitter-for-mode
   *your-syntax-table*
   "your-language"  ; tree-sitter言語名
   (tree-sitter-query-path)
   :indent-query-path (tree-sitter-indent-query-path))
  (setf (variable-value 'enable-syntax-highlight) t))

ステップ4: フォールバック処理(オプション)

Tree-sitterが利用できない場合の堅牢なエラーハンドリング:

(defun try-enable-tree-sitter ()
  "Tree-sitterの有効化を試み、成功時にT、失敗時にNILを返す。"
  (ignore-errors
    (when (and (find-package :lem-tree-sitter)
               (funcall (find-symbol "TREE-SITTER-AVAILABLE-P" :lem-tree-sitter)))
      (funcall (find-symbol "ENABLE-TREE-SITTER-FOR-MODE" :lem-tree-sitter)
               *your-syntax-table* "your-language" (tree-sitter-query-path))
      t)))

(define-major-mode your-mode language-mode
    (:syntax-table *your-syntax-table*)
  (unless (try-enable-tree-sitter)
    ;; tmlanguageベースのハイライトにフォールバック
    (set-syntax-parser *your-syntax-table* (make-tmlanguage-your-lang)))
  (setf (variable-value 'enable-syntax-highlight) t))

キャプチャ名リファレンス

LemはTree-sitterのキャプチャ名を構文属性にマッピングします。以下のキャプチャがサポートされています:

コード要素

キャプチャLem属性用途
@keywordsyntax-keyword-attribute言語キーワード
@keyword.controlsyntax-keyword-attribute制御フローキーワード
@keyword.functionsyntax-keyword-attribute関数関連キーワード
@stringsyntax-string-attribute文字列リテラル
@string.escapesyntax-constant-attributeエスケープシーケンス
@numbersyntax-constant-attribute数値リテラル
@commentsyntax-comment-attributeコメント
@functionsyntax-function-name-attribute関数名
@function.callsyntax-function-name-attribute関数呼び出し
@function.builtinsyntax-builtin-attributeビルトイン関数
@typesyntax-type-attribute型名
@variablesyntax-variable-attribute変数名
@variable.builtinsyntax-builtin-attributeビルトイン変数
@constantsyntax-constant-attribute定数
@constant.builtinsyntax-constant-attributeビルトイン定数
@operatorsyntax-builtin-attribute演算子
@propertysyntax-variable-attributeオブジェクトプロパティ

Markdown/ドキュメント要素

キャプチャLem属性用途
@markup.heading.1 - @markup.heading.6document-header1-attribute - document-header6-attribute見出し
@markup.bolddocument-bold-attribute太字
@markup.italicdocument-italic-attributeイタリック
@markup.rawdocument-code-block-attributeコードブロック
@markup.linkdocument-link-attributeリンク
@markup.quotedocument-blockquote-attribute引用ブロック

階層的フォールバック

キャプチャ名は階層的フォールバックをサポートしています。例えば、@keyword.controlは特定のマッピングが存在しない場合、@keywordにフォールバックします。

インデントクエリの作成

インデントクエリはHelixエディタの形式に従い、@indent@outdentキャプチャを使用します。

基本構文

;; インデントを増やすノード
[
  (block)
  (object)
  (array)
] @indent

;; インデントを減らすトークン
[
  "}"
  "]"
  ")"
] @outdent

スコープルール

インデントシステムはスコープルールを使用します:

  • @indent(スコープ: tail): ノードが前の行で始まる場合のみ適用
  • @outdent(スコープ: all): トークンが行の先頭に現れる場合に適用

例: Nixのインデント

;; インデントに寄与するノード
[
  (attrset_expression)
  (rec_attrset_expression)
  (let_expression)
  (list_expression)
  (function_expression)
  (if_expression)
] @indent

;; 閉じトークン
[
  "}"
  "]"
  ")"
] @outdent

;; 特別: "in"は"let"と同じレベル
(let_expression "in" @outdent)

;; 特別: "then"と"else"は"if"と同じレベル
(if_expression "then" @outdent)
(if_expression "else" @outdent)

APIリファレンス

主要関数

enable-tree-sitter-for-mode

(enable-tree-sitter-for-mode syntax-table language query-path &key indent-query-path)

モードの構文テーブルに対してTree-sitterを有効化します。

パラメータ説明
syntax-tableモードの構文テーブル
languageTree-sitter言語名(例: "json"
query-pathhighlights.scmへのパス
indent-query-pathindents.scmへのオプションパス

成功時にT、失敗時にNILを返します。

tree-sitter-available-p

(tree-sitter-available-p) => boolean

システムでTree-sitterが利用可能かどうかをチェックします。

get-buffer-treesitter-parser

(get-buffer-treesitter-parser buffer) => treesitter-parser or NIL

バッファのTree-sitterパーサーを取得します(存在する場合)。

treesitter-parserクラス

Tree-sitter統合のコアクラス:

スロットアクセサ説明
language-nametreesitter-parser-language-name言語名(例: “json”)
treetreesitter-parser-tree現在の構文木
highlight-querytreesitter-parser-highlight-queryコンパイル済みハイライトクエリ
indent-querytreesitter-parser-indent-queryコンパイル済みインデントクエリ

インクリメンタルパース

Lemは自動的にインクリメンタルパースを処理します。バッファを編集すると:

  1. 編集イベントがafter-change-functionsフックで記録される
  2. 保留中の編集が既存の木に適用される
  3. Tree-sitterが変更されていないASTノードを再利用
  4. 変更された部分のみが再パースされる

これにより、大きなファイルでも効率的なパフォーマンスが得られます。

Tree-sitter文法のインストール

Tree-sitter文法はシステムにインストールする必要があります。一般的な方法:

tree-sitter CLIを使用

# tree-sitter CLIをインストール
npm install -g tree-sitter-cli

# 文法をクローンしてビルド
git clone https://github.com/tree-sitter/tree-sitter-json
cd tree-sitter-json
tree-sitter generate

システムパッケージマネージャを使用

多くのディストリビューションがビルド済み文法を提供しています:

# Arch Linux
pacman -S tree-sitter-grammars

# macOS (Homebrew)
brew install tree-sitter

# Nix
nix-env -iA nixpkgs.tree-sitter-grammars.tree-sitter-json

トラブルシューティング

Tree-sitterが動作しない

  1. Tree-sitterが利用可能かチェック:

    (lem-tree-sitter:tree-sitter-available-p)
    
  2. 言語文法がインストールされているかチェック:

    (tree-sitter:get-language "json")
    
  3. クエリファイルの存在を確認:

    (probe-file (tree-sitter-query-path))
    

不正確なハイライト

  1. tree-sitter CLIでクエリをテスト:

    tree-sitter query highlights.scm example.json
    
  2. クエリファイルの構文エラーをチェック

  3. キャプチャ名がサポートされている名前と一致しているか確認(キャプチャ名リファレンスを参照)

パフォーマンスの問題

  • Tree-sitterはインクリメンタルパースを使用しますが、非常に大きなファイルでは遅くなる可能性があります
  • 極めて大きなファイルではハイライト範囲の制限を検討してください
  • 文法に既知のパフォーマンス問題がないか確認してください

リソース