emacs29以降のコード支援機能

Emacs29で tree-sittereglotが標準機能としてバンドルされました。
ただし、emacsは各自の環境をコードでセットアップする暗黙の仮定があり、elispで適切に設定できた場合に使えるという状況です。

Tree-sitter

Tree-sitterは、主にsyntax highlightingを高速に処理できるパーサを提供しています。
emacsの場合、tree-sitterと統合した新しいメジャーモードを起動すると動作します。

emacs29.1同梱のパッケージはtreesit機能を提供しており、元のtree-sitterパッケージの全機能が含まれているわけではないようです。
また、各言語に対応するgrammerは別途追加する必要があり、.emacs.d/tree-sitter/を参照します。

次のようなelispをコマンドラインからemacs --batch -Q -l build-treesit.elのようにバッチ実行するとgrammerをインストールできます。

;;; build-treesit.el --- Batch build of tree-sitter grammars

(require 'treesit)

;; -------- 言語パーサ一覧をここで指定する --------
(setq treesit-language-source-alist
      '((bash        "https://github.com/tree-sitter/tree-sitter-bash")
        (c           "https://github.com/tree-sitter/tree-sitter-c")
        (cpp         "https://github.com/tree-sitter/tree-sitter-cpp")
        (css         "https://github.com/tree-sitter/tree-sitter-css")
        (html        "https://github.com/tree-sitter/tree-sitter-html")
        (javascript  "https://github.com/tree-sitter/tree-sitter-javascript" "src")
        (json        "https://github.com/tree-sitter/tree-sitter-json")
        (python      "https://github.com/tree-sitter/tree-sitter-python")
        (rust        "https://github.com/tree-sitter/tree-sitter-rust")
        (toml        "https://github.com/tree-sitter/tree-sitter-toml")
        (typescript  "https://github.com/tree-sitter/tree-sitter-typescript" "typescript/src")
        (tsx         "https://github.com/tree-sitter/tree-sitter-typescript" "tsx/src")))

;; -------- ビルド出力ディレクトリ --------
(setq treesit-extra-load-path
      (list (expand-file-name "~/.emacs.d/tree-sitter/")))

(make-directory (car treesit-extra-load-path) t)

;; -------- インストール処理 --------
(dolist (lang (mapcar #'car treesit-language-source-alist))
  (condition-case err
      (progn
        (message "=== Building tree-sitter grammar for %s ===" lang)
        (treesit-install-language-grammar lang)
        (message "✓ Finished: %s" lang))
    (error
     (message "✗ Error building %s: %s" lang err))))

(message "All builds finished.")

または、必要に応じて自動でgrammerを追加させる方法として treesit-autoを追加する方法があります。 サポートしている言語は treesit-auto.eltreesit-auto-recipe-listにリストしています。

パッケージをインストールしたうえで、emacs29.1から同梱された use-packageを利用して設定できます。
treesit-auto-installを変更して自動実行する挙動を指定しておきます。

(use-package treesit-auto
  :config
  (setq treesit-auto-install t)
  (global-treesit-auto-mode))

より詳細な挙動は、 How to Get Started with Tree-Sitterの解説が参考になります。

Spacemacsの場合

パッケージじたいは、dotspacemacs-additional-packagesに追加することでインストールできます。

   dotspacemacs-additional-packages
   '(
     treesit-auto
     )

パッケージ設定は先ほどのuse-package式をuser-configに追加すると動作します。

treesit-autoは、たとえばrust-modeが呼ばれた際にrust-ts-modeを起動する挙動を提供するのですが、Spacemacsはまた別のメジャーモードを起動することが多いため、手動で変更する必要があります。

(defun dotspacemacs/user-config ()
  (add-to-list 'major-mode-remap-alist '(rustic-mode . rust-mode))

当然ながらこの例ではrustic-modeが提供していた機能は使えなくなります。
現在有効なモード構成は、M-x describe-modeで確認できます。

また、利用できるgrammerをあらかじめ一括で追加したい場合には、scratchバッファで以下のコードを手動実行すると入ります。

(require 'treesit-auto)
(treesit-auto-install-all)

eglot

eglotは、LSPクライアントです。
lsp-modeは自前でメッセージ類を表示する部分が多いのですが、eglotはFlymakeやEldocに出力を渡す挙動が主になっています。

M-x eglotで手動起動できるほか、対応するモードの起動時にeglot-ensureを呼ぶことで自動起動できます。

(add-hook 'rust-ts-mode-hook 'eglot-ensure)

Spacemacsの場合

Spacemacsでも、user-configでhookを定義しておくと、eglotを起動できます。

(defun dotspacemacs/user-config ()
  (add-hook 'rust-ts-mode-hook 'eglot-ensure)

Spacemacsの各言語レイヤーはLSP設定を提供しているのですが、その多くがlsp-modeとlsp-uiを対象としており、eglotをサポートしていません。
lsp-modeについては、 Spacemacs導入で解説しています。

lsp-modeを抑止する統一的な設定もないのですが、Rust Layerの例ではrust-backendに無効な値をセットすることで停止できています。

dot-spacemacs-configuration-layers
'(
  (rust :variables rust-backend 'eglot)

deno lsp

Denoの公式ドキュメントにeglot向けの設定があります。
ただし、emacs起動時にはeglotをロードしていないのでuse-packageでラップした方が良いことと、tree-sitterのメジャーモードを追加すべき点を考慮すると、次のような設定になります。

(use-package eglot
  :config
  (add-to-list 'eglot-server-programs '((js-mode js-ts-mode typescript-mode typescript-ts-mode) . (eglot-deno "deno" "lsp")))
  (defclass eglot-deno (eglot-lsp-server) ()
    :documentation "A custom class for deno lsp.")
  (cl-defmethod eglot-initialization-options ((server eglot-deno))
    "Passes through required deno initialization options"
    (list :enable t
    :lint t)))

さらにプロジェクト別により細かく設定したいケースについては、 Deno + Tree Sitter + Emacsが参考になります。

emacsのIDE機能拡充がSpacemacsと干渉しつつある

emacsのリリース履歴を見ると、27.1でJSONのネイティブパース、28.1でLispパッケージのネイティブコンパイルを経て、29.1でシンタックス表示のネイティブパースと標準LSPクライアントのバンドルを進めてきています。
バニラ状態のemacsがIDE機能をアグレッシブに統合し、処理スピードも向上しています。

当初からemacs -nwはターミナルで動作するため、とくにCUIのIDEとしては驚異的な進化を継続している例といえます。

これまでもelispによる拡張パッケージで同様の環境は作れていたため、Spacemacsのようなディストリビューションが成立しているのですが、近年のemacs本体の選択とずれが増えてきています。
Spacemacsについては、各レイヤーがeglotなどemacs標準の構成をサポートした方が良い状況ですが、そのように進まない可能性もあります。

レイヤーが提供していた機能がコアに統合されていった場合、Spacemacsの最後の特徴はviキーバインドを提供するevil-modeに絞られるでしょう。
長い目で見ると、evil-modeのキーバインド集に絞ったパッケージとバニラのemacsの構成に回帰した方が良いかもしれません。

⁋ 2023/10/15↻ 2023/10/16
中馬崇尋
Chuma Takahiro