Emacs tree-sitter 初体验
tags: programming
文章目录
Tree-sitter 简介
Emacs 29 的一大亮点就是原生支持了 tree-sitter(后文简称 ts),它的官网是这么定义的:
Tree-sitter is a parser generator tool and an incremental parsing library.
现在较为流行的编辑器,如 Neovim 都已支持 ts 作为解析库。
在 Emacs 中,之前都是采用正则的方式去解析语言的语法结构,这种方式虽然大多数情况是没有问题的,但主要有以下几个问题:
- 正则比较消耗 CPU,导致对大文件进行高亮时容易导致 Emacs 卡主,参见:Regexp Problems (GNU Emacs Lisp Reference Manual)
- 对于有些语法结构,正则并不能很好的工作,参见:The true power of regular expressions
因此,有了 tree-sitter 这种 parser generator 后,就可以比较方便地写出不同语言的解析器,而且能够保证高效。
安装步骤
由于 tree-sitter 核心部分使用 C 编写,因此 Emacs 默认并不链接它,需要我们先手动安装,不同平台的包管理器一般都有预编译好 ts,可以直接安装,例如:
1# macOS
2brew install tree-sitter
3
4# Ubuntu
5sudo apt install libtree-sitter-dev
6
7# Arch Linux
8sudo pacman -S tree-sitter之后就可以编译 Emacs 了下面是笔者编译 Emacs 时用的命令,供读者参考:
1# 国外可以用 https://git.savannah.gnu.org/git/emacs.git
2git clone https://gitee.com/mirrors/emacs.git
3cd emacs
4git checkout emacs-29
5git clean -xfd
6./autogen.sh
7./configure --with-tree-sitter
8
9export CPATH=`xcrun --show-sdk-path`/usr/include:`xcrun --show-sdk-path`/usr/include/libxml2
10
11make -j 4 && make install编译完 Emacs 后,可以使用如下命令来测试:
1(treesit-available-p)返回 t 则说明配置成功。但到这里安装并没有完成,这只是完成了核心部分,不同语言的解析器需要单独安装,不过幸好社区内已经有人整理好了,
Emacs 默认从 treesit-extra-load-path 指定的路径去加载动态链接库,如果这个值是 nil ,它默认会从 user-emacs-directory 下的 tree-sitter 内加载。
所以在下载好对应平台的压缩包后,可以解压到上面指定的目录内。一般来说,语言和动态链接库名字都是一一对应的,比如 c 对应 libtree-sitter-c.dylib (Linux 下后缀为 so),所以需要对解压后的内容进行重命名,下面的命令供参考(依赖 fd):
1fd -t f dylib --exclude 'libtree*' --exec mv {} libtree-sitter-{/} \;如果语言和动态链接库名字不对应,可以通过 treesit-load-name-override-list 来配置,比如:
1(setq treesit-load-name-override-list
2 '((c++ "libtree-sitter-cpp" "tree_sitter_cpp")
3 (js "libtree-sitter-javascript" "tree_sitter_javascript"))
4 )在安装完不同语言的动态链接库后,可以使用 (treesit-language-available-p 'c) 这个方式来测试。
在确认指定语言的库安装好后,接下来需要做的是开启 Emacs 对应的 mode,比如 c-mode 对应的 c-ts-mode ,Emacs 为不同的语言重新实现了一个以 ts-mode 结尾的新 mode,在 29 版本中,主要有如下几个:
12 candidates:
lisp/progmodes/c-ts-mode.el
lisp/progmodes/cmake-ts-mode.el
lisp/progmodes/dockerfile-ts-mode.el
lisp/progmodes/erts-mode.el
lisp/progmodes/go-ts-mode.el
lisp/progmodes/java-ts-mode.el
lisp/progmodes/json-ts-mode.el
lisp/progmodes/ruby-ts-mode.el
lisp/progmodes/rust-ts-mode.el
lisp/progmodes/typescript-ts-mode.el
lisp/textmodes/toml-ts-mode.el
lisp/textmodes/yaml-ts-mode.el
为了能够自动打开对应的 ts-mode ,可以通过配置 major-mode-remap-alist 来实现:
1(setq major-mode-remap-alist
2 '((yaml-mode . yaml-ts-mode)
3 (sh-mode . bash-ts-mode)
4 (js-mode . js-ts-mode)
5 (css-mode . css-ts-mode)
6 (c-mode . c-ts-mode)
7 (c++-mode . c++-ts-mode)
8 (c-or-c++-mode . c-or-c++-ts-mode)
9 (python-mode . python-ts-mode)))这样在进入 c-mode 时,会自动替换成 c-ts-mode ,到这里 tree-sitter 就算安装成功了。
社区插件
虽然 tree-sitter 还相对较新,但是社区内已经有些基于它实现的包了,比如:
其他的包一般都有 issue 跟踪,比如:
对于 expand-region 的用户,可以通过下面的配置来使用 ts:
1(when (treesit-available-p)
2 (defun my/treesit-mark-bigger-node ()
3 "https://emacs-china.org/t/treesit-expand-region-el/23406"
4 (let* ((root (treesit-buffer-root-node))
5 (node (treesit-node-descendant-for-range root (region-beginning) (region-end)))
6 (node-start (treesit-node-start node))
7 (node-end (treesit-node-end node)))
8 ;; Node fits the region exactly. Try its parent node instead.
9 (when (and (= (region-beginning) node-start) (= (region-end) node-end))
10 (when-let ((node (treesit-node-parent node)))
11 (setq node-start (treesit-node-start node)
12 node-end (treesit-node-end node))))
13 (set-mark node-end)
14 (goto-char node-start)))
15
16 (add-to-list 'er/try-expand-list 'my/treesit-mark-bigger-node))现有问题
由于 ts-mode 的支持刚开始,原有 major-mode 的功能并不是都支持了,在笔者的测试中, rust-ts-mode, json-ts-mode 开启后,高亮会有丢失。
收听方式

反馈
- 对节目有想法或发现内容错误?欢迎来信交流️