参加你附近的 ,了解 VS Code 中的 AI 辅助开发。

Markdown 语言服务器介绍

2022 年 8 月 16 日,由 Matt Bierner (@MattBierner)发布

Markdown 支持是我在 2016 年加入 Visual Studio Code 团队时接手的第一个功能。哇,真的已经过去六年了吗?不过这真是个完美的匹配。我使用 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 认知的演变过程:从最初把它看作是加了几个星号、方括号和井号来点缀的纯文本,到后来把它理解为一种标记语言,并且认为它可以从我们为 TypeScript 或 Python 等编程语言提供的许多相同工具中受益。

深入了解 Markdown 工具

在我发现 VS Code 之前,我主要用一个简单的文本编辑器来编码。这意味着我必须记住符号名称,并且每次想用它们时都要手动输入。如果我想重命名一个变量,我会进行文本查找/替换,并希望我的单元测试能捕捉到那些不可避免的输错或搞乱名称的情况。这是一种缓慢且不可靠的工作方式,但我当时很满足,因为我不知道事情可以变得更好。直到我最终接触到更智能的工具后,我才真正意识到我以前的工作流程是多么原始。

最近,我对 Markdown 也有了同样的感悟。多年来,我一直满足于使用 VS Code 相对简单的 Markdown 编辑器。我对语法高亮和内置的 Markdown 预览感到满意。文档大纲和可点击的编辑器链接只是额外的福利。我已经习惯了手动写出链接。我已经接受了如果我更改了一个标题名称,就需要进行文本搜索来更新所有指向该标题的链接。而且因为我把 Markdown 看作是比花哨的纯文本多不了多少的东西,我甚至无法想象还有更好的方式存在。

但有一天,在我感觉像是第一百次输错图片路径后,我终于恍然大悟:这毫无乐趣!我为什么要把生命浪费在手动输入和验证这些链接上?这应该是工具该干的活!我知道我想要的不是随便一个工具,我想要的是一个能帮助我像阅读和编写文本一样处理 Markdown 的工具,而不是把 Markdown 源码隐藏在某种所见即所得(WYSIWYG)式的 UI 魔法背后。这与 VS Code 的精神以及我们对编程语言支持的思考方式非常一致。为什么我们为传统编程语言提供的许多智能功能不能同样应用于 Markdown 呢?第二天,我就开始着手开发链接补全功能。

链接补全是一种建议功能,可以帮助你编写指向当前文件内标题或工作区中其他文件的链接。我甚至添加了对补全指向其他 Markdown 文件内标题链接的支持。太棒了!这只是一个小小的补充,但却极大地提高了我的工作效率。很快,我就无法想象没有它我该怎么活。

Markdown 补全功能的成功让我欣喜若狂,我陶醉地想象着接下来还能为 Markdown 带来哪些其他的语言智能功能。我想象着自己自信地在标题上按 F2 键来安全地重命名它们。我幻想着红色的波浪线从模糊的文本海洋中射出,帮助识别无效链接。这一切看起来都那么理所当然!为什么我几年前没有想到呢?我开始将 Markdown 理解为结构化文本,而不仅仅是纯文本,更好的 Markdown 工具的可能性似乎无穷无尽。

Markdown 语言功能

我不会用每个新功能背后的故事来烦你,也不会深入探讨它们是如何实现的全部细节。总而言之,我采取了一种增量式的方法,这使得在我投入到 VS Code Markdown 支持的有限时间内,所有这些努力都成为可能。例如,我没有直接开始构建重命名支持,而是先让一个可靠版本的“查找所有引用”功能运行起来(因为如果你想重命名一个符号,你首先需要知道所有引用它的地方)。以增量方式工作,并在已有功能的基础上构建新功能,也帮助我在实现新功能时测试旧功能。例如,实现链接重命名帮助我捕捉了大量链接检测的 bug。(这种方法的唯一缺点是,你会意识到你那“如此优雅”的高塔是建立在几个非常杂乱的正则表达式之上的)。

当报告无效文件/图片链接的实验性支持在春末推出时,我退后一步审视我的工作。现在的 Markdown 语言功能集包括:

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

我知道这些新工具将使处理 Markdown 的工作更快、更安全。但当我回顾这个常见于编程语言的功能列表时,一个想法一直在困扰着我。几个月前我还认为这很荒谬,但现在,当我再次思考时,我意识到也许是时候开发一个 Markdown 语言服务器了。

你被“服务”了吗? (Are you being servered?)

到 2022 年春末,所有 VS Code 的 Markdown 工具仍然运行在常规的扩展 API 上。虽然我想探索将所有这些工具迁移到一个正式的语言服务器上,但进行这种改变会有实际的工程成本。我需要确保这是值得的。

我为此反复纠结了一个多月。尽管现有代码状况良好,但仍有许多未知数。如果我进行到一半才发现行不通怎么办?我以前甚至从未真正开发过一个语言服务器。

在 debating 这一切的同时,我让自己忙于重构 Markdown 扩展的源代码,就好像它将要被迁移到语言服务器一样。我试图隔离对 VS Code 扩展 API 的依赖,将更多逻辑切换为使用服务注入,并确保测试不依赖于文件系统。这样,即使我最终没有迈出语言服务器这一步,至少我也在清理代码库。

最终,有几个考虑说服了我,一个 Markdown 语言服务器是正确的下一步。首先是一个相当平凡的原因:我发现为 Markdown 文件高效地实现链接诊断非常具有挑战性。在一个大型的 Markdown 工作区,比如 vscode-docs,我总是会不小心阻塞扩展主进程几百毫秒。这很不好。而语言服务器则作为其自己的进程运行。不仅如此,语言服务器现在还有一种新的拉取模型 (pull model) 用于诊断,我非常想尝试一下。

然后还有一些更高尚的理由。例如,一个 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 工具未来的邀请函。如果你有兴趣做出贡献,请查看这些新项目,看看你能用它们创造出什么。你可以提交 bug 报告和功能请求,甚至可以提交一个 PR!还有许多我甚至没有梦想过的智能 Markdown 语言功能。让我们一起构建它们吧!

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

编码愉快!

Matt Bierner, @MattBierner