Emacs集成开发环境和远程开发配置
Ping Zhou, 2022-08-14
一个经典问题,如何把 Emacs 配置出 IDE 的效果?
对于本地开发来说,可以用 projectile+lsp 配合相应语言的 language server(例如 C++的 clangd),基本上就能满足需要。
如果 clangd 不够智能,例如有些头文件或者符号找不到,可以用 compile_commands.json
文件来指导 clangd。 compile_commands.json
文件,你可以把它理解为编译时产生的 trace,记录了整个 project 的编译步骤,通过它 clangd 就能知道每个文件是如何编译的,包括头文件在哪里,链接库在哪里,乃至各个符号的定义等。有了这个,你的 Emacs 基本上就具备了集成开发环境的主要能力。
如何生成 compile_commands.json
文件呢?取决于项目的构建工具。如果是 cmake,ninja 等比较现代的工具,都内置了导出 compile_commands.json
文件的功能,比如 cmake 里只要加上这个环境变量就行了:
CMAKE_EXPORT_COMPILE_COMMANDS
如果工具不支持自动导出 compile_commands.json
,可以在编译时用 bear 来记录。bear 相当于一个命令的 wrapper,后面的参数就是你的编译命令。例如:
./configure bear make
bear
会把 make
过程中执行的步骤全都记录在 compile_commands.json
里。
但是还有个问题:如果项目代码在远程机器上呢?Emacs 能否有类似 VSCode 的远程开发能力?
答案是可以的,但是需要你自己配置。:-)
Emacs 内置有远程连接的客户端 tramp,支持 SSH 等多种连接方式。我们可以通过 tramp 来访问远程机器上的文件。例如我们在打开文件时输入这样的地址:
/ssh:user@192.168.0.100#1234:/home/user/test.c
这个地址告诉 tramp,我们用 ssh 方式打开主机 192.168.0.100 上的文件 /home/user/test.c
,登录用户名 user,连接端口是 1234 (如果 ssh 用的是默认配置,端口可以省略)。如果需要,tramp 会提示你输入密码登录。
这里有个坑,就是有时候 tramp 会挂在那里无法连接。原因是 tramp 会解析服务器那边的登录提示来判断如何登录,但是如果远程的服务器提示过于 fancy,tramp 解析不了,就会停在那里。解决方式如下:
给 tramp 设置一个专用的终端类型:
(setq tramp-terminal-type "tramp")
然后在远程主机那里,如果看到登录的终端类型是 tramp,就简化 shell 提示。例如在 .zshrc
或者 .zprofile
最后加上这个:
[[ $TERM == "tramp" ]] && unsetopt zle && PS1='$ '
这样 tramp 就能正确连接到远程主机了。
连接到远程主机只是第一步,如何配置类似本地的集成开发环境呢?
答案是我们可以给 lsp 注册一个远程的 language server:
(lsp-register-client (make-lsp-client :new-connection (lsp-tramp-connection "/usr/bin/clangd") :major-modes '(c-mode cc-mode) :remote? t :server-id 'clangd-remote)) (add-to-list 'lsp-enabled-clients 'clangd-remote)
这里注册了一个名字叫 clangd-remote 的 LSP 客户端,然后把它加到了已激活的客户端列表里。当我们打开远程主机上的 C/C++文件时,就会启动这个 LSP 客户端,而这个客户端会在远程执行配置里的命令行 /usr/bin/clangd
,这样我们就可以像在本地一样使用远程主机上的语言服务器了。
tramp 还有个方便的功能:如果当前文件是远程的,那么当我们打开终端时(例如 vterm
)会自动连接到远程主机,并且落到当前文件所在的目录里。
不过实话实说,基于 Emacs+tramp 的远程开发,和 VSCode 相比还是有一定差距的,还需要进一步 tune。