什么是 clice?
clice 是一个全新的 C++ 语言服务器,旨在解决现有 C++ 语言服务器的不足。它为你的编辑器提供代码导航和智能建议。
为什么需要新的语言服务器?
那么第一个问题是:为什么要开发新的语言服务器?有必要重复造轮子吗?
这个问题值得认真回答。在开发这个项目之前,我写过许多大大小小的项目。但其中绝大多数都是玩具项目,只是为了验证某个想法或用于个人学习,并没有解决任何实际问题。clice 并非如此——它确实是为了解决现有问题(具体问题将在后文讨论),而不是为了重写而重写。
今年年初,我想参与 LLVM 项目的开发。我想从自己比较熟悉的领域入手——C++,具体来说就是 clang。但没有具体需求,我总不能只是盯着源码看。这种情况下,通常会从一些适合新手的 issue 入手,逐步熟悉项目。但我觉得这很无聊——我想从一开始就做点大事,比如实现某项新的 C++ 标准特性。然而,我发现自己几乎没有参与这类工作的机会,因为新特性几乎总是由少数几位 clang 核心开发者实现。好吧,既然这里没有机会,那就去别处看看。我的注意力自然转向了 clangd,因为我主要使用 VS Code 开发,而 VS Code 上最好的 C++ 语言服务器就是 clangd。
当时我对 clangd 一无所知,只是发现它对关键字的高亮渲染似乎不正确。于是,我一边阅读 clangd 的源码,一边浏览 clangd 的大量 issue,看看有没有自己能解决的问题。翻阅了几百个 issue 后,我发现这里的问题还真不少。当时,我对一个有关模板内代码补全的 issue 特别感兴趣。为什么会对它感兴趣?熟悉我的读者可能知道,我是个颇有经验的元编程玩家,此前也写过许多相关文章。自然,我不仅好奇模板元编程本身如何运作,也好奇 clang 作为编译器是如何实现相关特性的。这个 issue 对我来说是个很好的切入点。花了几周时间探索原型实现后,我初步解决了那个 issue,但随后我发现根本没有人来审查相关代码!
一番调查之后,我发现 clangd 目前的情况相当糟糕。让我们梳理一下时间线:clangd 最初只是 LLVM 内部一个简单的小项目,功能和易用性都不突出。正如 MaskRay 在这篇关于 ccls 的 博客 中提到的,当时的 clangd 只能处理单个编译单元,无法处理跨编译单元的请求。这篇博客发布于 2017 年,这也是 MaskRay 选择编写 ccls 的原因之一。ccls 同样是一个 C/C++ 语言服务器,当时比 clangd 更强。然而,后来 Google 开始派人改进 clangd,以满足其内部大型代码库的需求。与此同时,LSP 标准的内容不断扩充,clangd 也在持续跟进新内容,但 ccls 的作者似乎逐渐忙于其他事情,没有太多时间维护 ccls。所以最终,clangd 整体上超越了 ccls。转折点大约出现在 2023 年,当时 clangd 似乎已经达到了足以满足 Google 内部使用需求的状态,原先负责 clangd 的员工也被调去做其他事情了。目前,clangd 的 issue 主要由 HighCommander4 一个人处理,他纯粹出于热爱参与其中,并未受雇于任何人。由于没有受雇专门维护 clangd,他只能在有限的空闲时间处理 issue,而且只能回答问题和进行极为有限的代码审查。正如他在这条 评论 中提到的:
另一部分原因是缺乏资源去实践我们已有的想法,例如上面提到的通过更激进的 Preamble 缓存,增加磁盘使用以分担更多负担。我是一名业余贡献者,能投入 clangd 的时间非常有限,主要用于回答问题、进行一些代码审查,以及偶尔做些小修复或改进;我没有足够的精力推动这类与性能相关的实验。
既然如此,像 clangd 的 C++20 模块初步支持 这样的大型 PR 被拖延了将近一年,也就不足为奇了。意识到这一现状后,我萌生了自己编写一个语言服务器的想法。我估算了一下项目规模:不计测试代码,大约需要两万行就能完成,这是一个人花一段时间可以完成的工作量,而且也有 ccls 和 rust-analyzer 这样的先例。另外,clangd 的代码已经有些陈旧了——尽管注释很多,相关逻辑仍然非常复杂,大规模修改所需的时间可能并不比重写更短。
于是我开始动手。我对 clangd 的数百个 issue 进行了分类,想看看其中是否有些问题因 clangd 最初的架构设计错误而难以解决,最终被搁置。如果有,能否在重新设计时将它们纳入考虑并加以解决?我发现确实有一些!于是,在接下来的时间里,我花了大约两个月学习和研究 clang 中的相关机制,探索相关问题的解决方案和原型实现。在确认这些问题基本都能解决后,我正式开始开发 clice。
