介绍 Markdown 语言服务器 (Language Server)

2022 年 8 月 16 日,作者:Matt Bierner,@MattBierner

2016 年我加入 Visual Studio Code 团队时,Markdown 支持是我负责的第一个功能。天哪,竟然已经过去六年了吗?这是一个绝佳的契合点。我使用 Markdown 的时间足够长,以至于我经常发现自己习惯性地在 Twitter、Outlook 以及光标所在的几乎每一个文本框中输入反引号和星号。这些年来,不断壮大 VS Code 内置的 Markdown 支持,并观察我们的 Markdown 扩展如何直接或间接地塑造了 Webview 和 Notebook 等核心功能,是一件非常有成就感的事情。

正因如此,我很高兴能分享一个我过去半年里一直在默默推进的项目,我认为它代表了 VS Code Markdown 工具的下一步发展:Markdown 语言服务器。有了这个语言服务器,我们将 VS Code 大部分内置的 Markdown 语言工具——从文档大纲、智能折叠到路径补全——开放给其他编辑器和工具使用。我们的目标是为 Markdown 工具注入通常与编程语言相关联的“智能”特性,从而推动其向前发展。

Markdown 语言服务器的工作被拆分为两个新的(且名字相似的!)开源库:

虽然这些库仍处于早期阶段,但它们已在 VS Code 1.70+ 版本中使用(希望你甚至从未察觉到 :-))。这种转变已经带来了一些好处,例如将 Markdown 工具移至独立进程,从而不会阻塞其他扩展。

但在我扯远之前,你可能想问:为什么要开发 Markdown 语言服务器?说实话,我花了六年时间才想通这一点。这也记录了我从“认为 Markdown 只是添加了几个星号、括号和井号的纯文本”到“意识到 Markdown 是一种标记语言,并且可以从我们为 TypeScript 或 Python 等编程语言提供的工具中获益”的演变过程。

深入了解 Markdown 工具

在发现 VS Code 之前,我主要使用简单的文本编辑器进行编码。这意味着我必须记住符号名称,并且每次使用时都得手动输入。如果我想重命名一个变量,我会进行文本查找/替换,并祈祷单元测试能捕捉到因拼写错误或格式混乱而产生的必然问题。这是一种缓慢且不可靠的工作方式,但我当时对此感到满足,因为我不知道还有更好的方案。直到我真正用上了更智能的工具,我才明白我的工作流是多么原始。

最近我对 Markdown 也有了同样的认识。多年来,我一直满足于使用 VS Code 相对简单的 Markdown 编辑器。我对语法高亮和内置的 Markdown 预览感到满意。文档大纲和可点击的编辑器链接只是锦上添花。我习惯了手动编写链接。我接受了这样一个事实:如果我更改了标题名称,就需要进行文本搜索来更新所有指向该标题的链接。由于我只把 Markdown 看作花哨的纯文本,我甚至无法想象还有更好的方式。

但在某次因为打错图片路径而感到(这大概是第一百次了)沮丧时,我终于意识到:这太糟糕了!为什么我要浪费生命去手动输入和验证这些链接?这本该是工具的工作!我知道我想要的不仅仅是一个工具,我想要的是一个能帮助我像编辑文本一样阅读和编写 Markdown,而不是用某种 WYSIWYG(所见即所得)风格的 UI 魔术将 Markdown 源码隐藏起来的工具。这非常符合 VS Code 的精神,以及我们对编程语言支持的思考方式。为什么我们为传统编程语言提供的许多“智能”特性不能应用于 Markdown 呢?第二天我就开始了链接补全功能的开发。

链接补全是一种建议功能,可以帮助你编写指向当前文件内标题或其他工作区文件的链接。我甚至增加了对指向其他 Markdown 文件内标题的链接补全支持。很酷吧!这只是一个小改进,却让我的生产力有了巨大的提升。很快,我就无法想象没有它的生活了。

沉浸在 Markdown 补全成功的喜悦中,我开始憧憬能为 Markdown 带来什么其他的语言智能。我幻想着自信地按 F2 键安全地重命名标题。我梦想着在模糊的文本海洋中闪烁出红色的波浪线,以帮助识别无效链接。这一切似乎都显而易见!为什么我几年前没有想到呢?我开始将 Markdown 理解为结构化文本,而不仅仅是纯文本,更好的 Markdown 工具的潜力似乎无穷无尽。

Markdown 语言特性

我不会用每个新功能背后的故事来让你感到无聊,也不会深入探讨它们是如何实现的细节。简而言之,我采取了增量式的方法,这使得我在投入到 VS Code Markdown 支持的有限时间内完成了所有工作。例如,我没有直接跳到构建重命名支持,而是先完成了一个稳健的查找所有引用 (Find All References) 功能(因为如果你想重命名一个符号,你首先需要知道它在何处被引用)。增量式开发并在此基础上构建每个功能,也帮助我在实现新功能时测试了旧功能。例如,实现链接重命名帮助我修复了大量链接检测中的漏洞。(这种方法的唯一缺点是,你会意识到你引以为傲的“优雅”塔楼其实是建立在几个非常复杂的正则表达式之上的)。

当无效文件/图片链接报告的实验性支持在春末推出时,我回顾了一下自己的工作。现在的 Markdown 语言特性集包括:

  • 文档大纲
  • 工作区符号
  • 文档链接
  • 智能折叠
  • 智能选择
  • 补全
  • 重命名
  • 查找所有引用
  • 转到定义
  • 断链诊断
  • 文件移动/重命名时的链接自动更新

我知道这些新工具会让 Markdown 的使用更快捷、更安全。但当我回顾这个编程语言通用的功能列表时,一个想法不断浮现在脑海。几个月前我还认为这很荒谬,但现在仔细思考,我意识到也许是时候引入 Markdown 语言服务器了。

需要“服务器”吗?

到 2022 年春末,所有 VS Code 的 Markdown 工具仍然运行在普通的扩展 API 上。虽然我想探索将所有这些工具转移到一个真正的语言服务器上,但这将带来真正的工程成本。我需要确保这是值得的。

我纠结了一个多月。即使现有的代码状态尚可,依然存在许多未知。如果中途发现行不通怎么办?我此前从未认真参与过语言服务器的开发。

在争论这些问题的同时,我开始重构 Markdown 扩展的源码,就好像它真的要移到语言服务器上一样。我尝试隔离对 VS Code 扩展 API 的依赖,将更多逻辑切换为使用服务注入,并确保测试不依赖于文件系统。这样即使我最终没有踏出这一步,至少也清理了代码库。

一些考量最终让我确信 Markdown 语言服务器是正确的下一步。首先是一个比较现实的问题:我发现高效实现 Markdown 文件的链接诊断非常困难。在像 vscode-docs 这样的大型 Markdown 工作区上,我经常不小心阻塞扩展宿主几百毫秒。这并不好。另一方面,语言服务器作为独立进程运行。不仅如此,语言服务器现在还有一种新的“拉取”模型用于诊断,我非常渴望尝试。

还有更崇高的原因。例如,Markdown 语言服务器对其他编辑器和工具也很有用。这包括 VS Code 团队发布的另一个编辑器:Monaco!更不用说像 Markdown CLI 工具这样的可能性。如果我没时间自己构建这样的工具,也许其他人可以使用语言服务器作为起点来完成。我在 VS Code 的 Markdown 工具上投入了大量精力,如果这些成果也能造福他人,那将再好不过。

通过提供一个新的语言服务器,我或许能够开启一项围绕改进 Markdown 工具的共同事业。VS Code 既是开源软件的杰出生产者,也是使用者,我深知此类项目带来的明显益处。开源的 Markdown 语言服务器将帮助其他编辑器,反过来也会吸引贡献,最终帮助到 VS Code!与其让每个编辑器/工具都重复造轮子去实现自己的 Markdown 支持,不如让语言服务器将开发者聚集在一起,共同完成一个造福所有人的大项目。

所有这些宏大的思考如果没有构建语言服务器的计划,都是空谈。即使在我完成了所有重构之后,将代码转移到语言服务器依然是一项巨大的工作!这看起来让人不知所措,直到我意识到我不需要一次性完成。我可以增量地构建服务器,将功能一个接一个地从 VS Code Markdown 扩展迁移到新的 Markdown 语言服务器。如果我做得对,我可以提交每一个小的增量迁移,这样用户在功能构建过程中就能体验到新的语言服务器。理想情况下,用户甚至不会注意到功能何时从扩展转移到了语言服务器。

也许这很显而易见,但我已经成为了这种大型代码变更增量方法的忠实拥护者。不需要十万行的 PR,也不需要持续数月(甚至数年!)的大型特性分支。相反,只需对 main 分支进行大量小而安全的修改。如果一切按计划进行,完成所有工作的最终提交应该是平淡无奇的。这就是我们逐步在 整个 VS Code 代码库中应用严格空值检查 所采用的方法,也是我觉得可以快速且尽可能平滑地将所有 VS Code Markdown 工具迁移到新语言服务器的方法。

剧透预警:成功了!我一个接一个地迁移了语言特性。我在过程中学习,并在明显需要时进行重构。诊断功能是最后一个迁移的功能,因为我不仅将其移到了语言服务器,还重写了它,使其使用语言服务器新的拉取诊断模型。整个工作最后的提交几乎删除了 Markdown 扩展中不再使用的代码。所以今天,如果你使用的是 VS Code 1.70+,几乎所有的 Markdown 语言特性都在使用新的语言服务器。

共同构建更好的 Markdown 工具

在许多方面,过去六个月里 VS Code Markdown 工具的进步超过了我过去六年在该领域所做的努力。今天,我们发布了许多新工具,其中一些是 Markdown 以前从未有过的。这些功能中的许多既造福于最普通的 Markdown 阅读者和编写者,也受到高级用户的赞赏。然而,尽管取得了这些进展,我知道我们才刚刚开始探索 Markdown 工具的可能性。

真正让我对 Markdown 语言服务器感到兴奋的是,这个项目现在不仅仅属于 VS Code 了。通过让我们的 Markdown 工具易于使用,我希望能够帮助所有人推动 Markdown 工具的发展。这些开源项目是邀请大家共同构建 Markdown 工具未来的通行证。如果你有兴趣做出贡献,请查看这些新项目,看看你能用它们创造出什么。你可以提交错误报告和功能请求,甚至提交 PR!还有许多我甚至还没想到的智能 Markdown 语言特性。让我们一起去实现它们吧!

如果你有兴趣查看源代码或做出贡献,可以在 GitHub 和 npm 上找到 Markdown 语言服务和服务器。

编码愉快!

Matt Bierner, @MattBierner

© . This site is unofficial and not affiliated with Microsoft.