EmacsでTree-sitterを利用してシンタックスハイライトする

Emacs 29以降の設定方法

Emacs 29以降の設定方法は こちら の記事で説明しています!

Tree-sitterとは

Tree-sitter自体はパーサ生成ツールと、かつそれを利用して作成された様々な言語のパーサのセットのようです。
Tree-sitterのパーサを利用することで高速かつ正確なシンタックスハイライトを適用することができます。
AtomではTree-sitterをベースにしたシンタックスハイライトシステムを使用しているようです。
emacsではELisp Tree-sitterというEmacs Lispバインディングパッケージを通じてシンタックスハイライトを適用できます。

Tree-sitterそのものについての解説は下記の記事がわかりやすかったです。
https://www.soum.co.jp/misc/vim-advanced/6/

インストール

インストール手順に則ってElisp Tree-sitterパッケージを追加します。
併せて、tree-sitter-langs パッケージも追加します。
tree-sitter-langsには、言語用の文法バイナリとハイライトのパターンの定義を行っているSchemeのファイルなどが含まれており、これを導入することで様々な言語でハイライトできるようになる。といった感じです。
tree-sitter-langsでは現状下記の言語に対応しているようです。
https://github.com/emacs-tree-sitter/tree-sitter-langs/tree/master/repos

  • agda
  • bash
  • c
  • c-sharp
  • cpp
  • css
  • elm
  • fluent
  • go
  • hcl
  • html
  • janet-simple
  • java
  • javascript
  • jsdoc
  • json
  • julia
  • ocaml
  • pgn
  • php
  • python
  • ruby
  • rust
  • scala
  • swift
  • typescript

設定

設定を以下に記載します。
自分はleaf.elを利用しているので、下記のように設定し、
melpa経由でtree-sitterとtree-sitter-langsを追加します。

  (leaf tree-sitter
    :ensure (t tree-sitter-langs)
    :require tree-sitter-langs
    :config
    (global-tree-sitter-mode)
    (add-hook 'tree-sitter-after-on-hook #'tree-sitter-hl-mode)
    ;; TSXの対応
    (tree-sitter-require 'tsx)
    (add-to-list 'tree-sitter-major-mode-language-alist '(typescript-tsx-mode . tsx))
    ;; ハイライトの追加
    (tree-sitter-hl-add-patterns 'tsx
      [
       ;; styled.div``
       (call_expression
        function: (member_expression
                   object: (identifier) @function.call
                   (.eq? @function.call "styled"))
        arguments: ((template_string) @property.definition
                    (.offset! @property.definition 0 1 0 -1)))
       ;; styled(Component)``
       (call_expression
        function: (call_expression
                   function: (identifier) @function.call
                   (.eq? @function.call "styled"))
        arguments: ((template_string) @property.definition
                    (.offset! @property.definition 0 1 0 -1)))
       ])
    )

上記の設定を入れることで下図のようにハイライトされるようになりました。

  • Before(typescript-modeのみ)
    Before

  • After(typescript-mode + tree-sitter-mode)

TSXの対応

TSXを書くことが多いので、.tsxファイルでハイライトされるようになっていて欲しいです。
tree-sitter-langsではTypeScript自体には対応しているのですが、
major-modeに応じてどのハイライトを適用するか決定しているようで、その中にTSXハイライトへの対応はありません。
そこで、下記のワークアラウンドでTSXファイルがハイライトできるようにしてます。
https://github.com/emacs-tree-sitter/tree-sitter-langs/issues/23#issuecomment-778692779

以下がtypescript-modeの設定です。

(leaf typescript-mode
  :ensure t
  :init
  (define-derived-mode typescript-tsx-mode typescript-mode "TSX")
  (add-to-list 'auto-mode-alist '("\\.tsx\\'" . typescript-tsx-mode)))

define-derived-modeマクロを利用してtypescript-tsx-modeを作成し、.tsxファイルの場合はtypescript-tsx-modeを利用するようにします。
あとは設定で記載した通りtree-sitterの設定の中で

(tree-sitter-require 'tsx)
(add-to-list 'tree-sitter-major-mode-language-alist '(typescript-tsx-mode . tsx))

することでTSXファイルのハイライトに対応できるようになります。

ハイライトパターンの追加

Customizationを参考にハイライトパターンの追加をしています。
自分はstyled-componentsを利用するので、簡単なそれ用の設定を入れています。
勉強不足で自信はないのですが、下記のIssueに対応していただけたらmmm-modeを利用する感じでcssのハイライトに適用できるようになるのかなと期待しています。
https://github.com/emacs-tree-sitter/elisp-tree-sitter/issues/33

クエリはTree-sitterのplaygroundで構文木とクエリの確認ができるのでこれを参照しつつ書きました。

ハイライトするためのワードなどは
elisp-tree-sitterのtree-sitter-hl.elや、
tree-sitter-langのhighlights.scmが参考になりました。

終わりに

Tree-sitterの紹介とEmacsへの導入について説明しました。

Tree-sitter自体わかってないところも多いですが、
major-modeによらずいい感じでハイライトできるようになったのと、コーディング時にハイライトの処理の遅延を感じることもないのでよかったです。

個人的にこれで一番恩恵を受けたのは、TSXを描く際に元々利用していたweb-modeを利用しなくてもいい感じでハイライトできるようになったため、それだけ早くなったと感じます。。(typescript-modeだとHTML部のインデントは正しく行なってくれませんが…)

original: zenn