Sublime Text 4, LSP, VS Code and clangd
Sublime Text 4
前几天 Sublime Text 4 Stable 发布了。在此之前我是 Sublime Text 3 的付费用户。大约 2014 - 2015 年,我开始转用 LaTeX 写当时所学课程大大小小的课程作业,顺便发现了 Sublime Text 和 LaTeXTools 插件。彼时没有怎么见过世面的我,因为这种丝滑的智能提示和自动补全功能深受感动,觉得这么让人精神愉悦的软件不充点钱简直对不起作者。后来一激动一咬牙,就为信仰充值了:
后来 Visual Studio Code 问世,并且在之后的几年里发展十分迅速,如今应该已经是最(?)受欢迎的 Code Editor 了。VS Code 的更新维护十分活跃,插件的质量也很高。
很长一段时间里我用 Text Editor 最重要的用途是写 LaTeX。后来 VS Code + LaTeX Workshop 的组合已经十分令我满意了,我也不再去折腾 Sublime Text 及其 LaTeXTools 插件。对我而言,Sublime Text 相比 VS Code 还剩下的为数不多的优势就是启动快。另外,Sublime Text 的 Key binding 也是先入为主,我至今依然习惯在 VS Code 和 JetBrains 的 IDE 里面使用 Sublime Text 快捷键键位。
这次 Sublime Text 4 的 License Upgrade (折扣)价格是 70USD,与 6 年前我原价购买 ST3 时价格一致。现如今我倒暂时没有什么续费欲望了。不过 Sublime Text 是一个良心「唠叨软件(Nagware)」,也就是说如果不掏钱,它只会不时地弹出一个窗口劝你掏钱,除此之外没有功能或时间限制。
于是这次 ST4 我也及时升级了。升级之后趁着新鲜,去搜索围观别人有什么推荐的 Sublime Text 插件。这次首先留意到的是 LSP。
Language Server Protocol
Language Server Protocol (LSP) 是 Microsoft 发布的一个协议,用于 Language Server 和 Development Tools(比如 Text Editor 或 IDE)之间的跨进程通信。
Language Server 是一种将「语言特性」和「编辑工具」解耦的尝试。这种想法现在看来非常自然:
我们使用编辑器浏览、编辑代码,常常希望 Editor 能够有智能提示、自动补全、语法检查、引用检查、跳转等功能。而且我们希望这些功能足够智能,以至于 Editor 需要理解具体编程语言(如 C++,Python)的语义,乃至了解整个项目中多个源文件的关系。
以前,如果要求每一个编辑器或自动补全插件的作者自己去实现这些功能,那将是非常浩大的工程:编程界的语种多如牛毛,且其中不乏复杂庞大的语言。由于 Editor 插件的开发者精力有限,必然导致这样的智能补全/提示工具质量参差不齐。
那么,何不让专业的工具做专业的事呢?假设有一个与编程语言相关的程序在后台运行,这个程序负责理解特定语言(如 C++)的代码;而 Editor 负责与用户交互:检测用户的行为,比如打开文件、输入字符、鼠标悬停在文件的某个位置、执行了「Go to definition」命令等等;然后 Editor 将这些信息传递给后台那个负责理解语言的后台程序,该程序根据语言的语义给出响应,比如对于鼠标悬停,就给出悬停位置那个变量或函数的文档;对于「Go to definition」,就返回 Definition 所在的文件及行号。
这个在后台运行的、理解语言特性的程序,就是 Language Server。而 Language 与 Editor 之前互传信息所使用的通信协议,即 LSP。
有了 LSP 这个设计,一个 Language Server 就可以支持多种 Editor 的智能编辑;而一个 Editor 如果要想增强对一个语言的支持,它无需花费太大成本自行开发那个语言的解析功能,而只要能够兼容 LSP,能够连接对应语言的 Language Server 就可以了。
clangd
clangd 是 C++ 的 Language Server 之一(其他的一些 Language Servers 见此页面)。clangd 的 Slogan 很有意思:
teach your editor C++
macOS 安装 clangd
由于 clangd 是 LLVM Project 的一部分,实际上 Apple 的工具链已经包含了 clangd,但却可能没有被添加到默认 PATH
变量中。
如果安装过 Xcode Command Line Tools 或者 Xcode.app 二者之一(如果没有,可以用 xcode-select --install
安装,或直接去 Mac App Store 下载 Xcode),clangd
大概就在下面的 2 个位置之一:
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clangd --version
# Apple clangd version 12.0.5 (clang-1205.0.22.9)
/Library/Developer/CommandLineTools/usr/bin/clangd --version
# Apple clangd version 12.0.5 (clang-1205.0.22.9)
或者也可以使用 Homebrew 安装(注意这依然不在默认 PATH
里面):
brew install llvm
/usr/local/opt/llvm/bin/clangd --version
# clangd version 12.0.0
注: Apple Toolchains 里面的 clangd 似乎不能正确读取下面将要讨论的 clangd 配置文件 (config.yaml
) 里面的键。所以我暂且用 Homebrew 安装的 clangd.
Linux 安装 clangd
通常直接使用包管理器安装即可,如 apt install clangd
.
Sublime Text 安装 LSP 并使用 clangd
Sublime Text 安装插件管理器 PackageControl 并用 PackageControl 安装 LSP。对于一个项目,在使用 Sublime Text 打开后,应该用 Cmd/Ctrl + Shift + P
然后 Enable Language Server in Project/Globally
。
如果你已经自行把 clangd
所在目录添加到了 PATH
环境变量中,那么这套 Sublime Text + LSP + clangd 的组合应该是 works out of the box。如果没有,也可以在 LSP 插件的配置文件(Sublime 菜单 - Preferences - Package Settings - LSP - Settings)中指定 clangd
的路径:
"clients": {
"clangd": {
"command": [
"/usr/local/opt/llvm/bin/clangd"
]
}
}
如果你的项目非常简单,比如只有一个 hello.cpp
源文件,clangd 默认你的项目只要简单地
clang hello.cpp
就能编译成功,那 clangd 同样也能很好地完成智能语法检查、提示。但如果你的项目较大,使用某种构建系统,涉及到多文件管理,依赖也比较复杂:例如,某个源码引用了一个 Header #include "a.h"
,并且在编译时要用 -I/some/special/path/to/a
指定搜索路径才能找到该 Header;但默认情况下, clangd 并不知道要去搜索额外的路径,它只会在默认搜索路径下寻找,找不到 a.h
然后给出(不该给的)报错。
CMake 项目输出 compile_commands.json
解决上述问题的方法自然是告诉 clangd 你将要用的所有编译命令。如果项目使用 CMake 构建系统,则可以在 CMakeLists.txt
里加入
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
或者在执行 CMake Configure 的时候直接带命令行参数
cmake -H. -Bbuild -DCMAKE_EXPORT_COMPILE_COMMANDS=1
这样 CMake 便会在构建目录下生成一个 compile_commands.json
文件(即 Compilation Database),把它放在项目源码根目录下即可。
如果使用 CMake 之外的其他构建系统,这里有一些生成 Compilation Database 的说明:JSON Compilation Database Format Specification;Compilation database - JetBrains Clion Help Center.
效果
一切就绪之后,打开一个 C++ 源文件,鼠标悬停于某些变量或函数之上,应该可以看到弹出的提示;也可以点击 Definition、Declaration 等链接进行跳转。打开 LSP Log,还可以看到 Editor 与 clangd 之间的通信过程:
clangd 配置
clangd 有一些特性并未默认开启,比如 Clang-Tidy.
可以创建一个 clangd 配置文件开启这些特性:
# macOS: ~/Library/Preferences/clangd/config.yaml
# Linux: $XDG_CONFIG_HOME/clangd/config.yaml, ~/.config/clangd/config.yaml
Diagnostics:
ClangTidy:
Add: [performance-*, bugprone-*, portability-*, modernize*]
Remove: modernize-use-trailing-return-type
重启一下 Language Server, 便可获得 Clang-Tidy 提示:
Visual Studio Code
借 Sublime Text 4 发布的机缘,我了解了 LSP 的机制。不过最后我还是没有用 ST4 写 C++ 代码,而是又转回了 VS Code。VS Code 官方(即 Microsoft)也有一个 C++ 的 Language Server,即 C/C++ 插件,智能辅助的功能也叫 IntelliSense。不过经过我肤浅的体验,我觉得 IntelliSense 没有 clangd 用起来舒服,所以将 VS Code 的 C++ Language Server 也设置成了 clangd。
这个过程当然很简单,只有 2 点:安装并启用 clangd,并关闭 IntelliSense:
// settings.json
"C_Cpp.intelliSenseEngine": "Disabled",
"C_Cpp.autocomplete": "Disabled",
"C_Cpp.errorSquiggles": "Disabled",
// If clangd is not in PATH:
"clangd.path": "/path/to/clangd"
换到 VS Code 的原因是 VS Code 的 Remote 插件实在是太易用了(个人感觉远好于我常用的 CLion),我可以轻松地连接到我在实验室的台式机,直接打开台式机里面的项目源码进行编辑。而由于 clangd 这类 Language Server 的存在,VS Code 对代码的「理解」能力大大增强,与 IDE 之间的鸿沟也显著缩小。另外,那台台式机的 i7 - 9700K CPU 无论是 indexing 还是编译代码,都比我的笔记本爽快不少。