10-编程语言支持 - Some-soda

10-编程语言支持

第 10 章 编程语言支持

Visual Studio Code 向世界带来了语言服务器的概念,所有其他文本编辑器都欣然接受了这个想法。Vim 中语言服务器协议(LSP)的早期实现令人沮丧而且笨拙,并且往往需要那些脆弱还复杂的插件。

然后 Neovim 决定将对语言服务器的支持内置到编辑器本身中。Neovim 的内置支持仍然令人沮丧且笨拙,但随着时间的推移,强大而简单的插件已经发展起来,使得语言服务器体验几乎是自动的。LazyVim 则代表了这次演变的顶峰。

此外,Neovim 还内置了对 TreeSitter 的支持,这是一个强大的库,用于在编辑源代码时解析和识别抽象语法树,并且 LazyVim 配置了使 TreeSitter Just Work™ (开箱即用) 所需的插件。

译者注:

  • TreeSitter: 提供更精确和快速的语法高亮、代码折叠、基于语法的导航和文本对象选择等功能。

语言服务器协议为我们提供了诸如代码导航、签名帮助、自动补全、某些高亮和格式化行为、诊断信息等支持。TreeSitter 为我们提供了更好的语法高亮、代码折叠以及基于语法的导航,例如你已经知道的 S 命令所提供的那种。

在 LazyVim 中使用语言服务器主要有两个工具:各种语言的 Lazy Extras,以及 Mason.nvim 插件。我们将了解这两者,然后学习如何更好地使用它们提供的一些工具。

10.1. lang.* Lazy Extras

我们已经使用过 LazyVim extras 进行插件配置,并且我告诉过你要为你经常使用的任何语言安装 extras。这些 extras 包括预配置的插件,为常见的编程语言提供一流的支持。大多数都带有预配置的语言服务器,许多还包括对那些语言有用的额外 Neovim 插件。

一旦你安装了这些 extras,通常是开箱即用的,你不需要为每种语言提供的命令学习任何新的快捷键。然而,阅读 extra 安装的插件的 Readme(可以通过在 LazyVim 网站上查找 Extra 的文档并点击标题来访问)以确保你没有错过该语言提供的任何命令,也不会有什么坏处。例如,Python extra 自带了 venv-selector.nvim 插件,它允许你自动或按需激活多种类型的 Python 虚拟环境。LazyVim 安装了一个快捷键,使用 <Space>cs 打开 virtualenv 选择器,其中 <Space>c 是“代码”(Code) 子模式。

10.2. Mason.nvim

Lazy Extras 可能不会安装你需要的所有东西。例如,相对于默认的 Typescript 格式化和 linting 工具,我更喜欢使用一个新兴的超快速工具叫做 Biome。

要安装像这样的东西,你可以使用 Mason.nvim 插件,它随 LazyVim 预装。要打开 Mason,请使用 <Space>cm 快捷键。弹出的窗口看起来类似于 Lazy.nvim 和 Lazy Extras 浮动窗口,尽管它自带了令人烦恼的不相关快捷键。

译者注: <Space>cm 是 “Code Mason” 的快捷键。

Mason 是一个非常大的编程语言支持工具数据库,包括语言服务器、格式化器和 linter,以及安装它们的说明。

Mason.nvim 假定你的系统上已经安装了某个基础配置;例如,如果你要安装基于 Rust 的东西,你最好有一个 cargo 二进制文件,如果你要安装需要 Python 支持的东西,Python 和 pip 需要可用。在大多数情况下,如果你正在用某种语言编码,你已经拥有 Mason 完成其工作所需的工具。Mason 主要负责确保工具以其他 Neovim 插件可以找到它们的方式安装。

使用 Mason 最困难的任务是知道你想安装什么工具。我在设置 LazyVim 时已经在使用 Biome 了,所以我知道我需要为它安装编辑器支持。那没问题;只需在 Mason 列表中找到 biome(像任何窗口一样,它是可滚动的、可搜索的和可 seek 的,并且 Mason 很贴心地将所有内容按字母顺序排列)。

但是当我开始写这本书时,我决定需要一个高级的 Markdown 格式化器,但我不知道该用哪一个。我可以在窗口中搜索 markdown,然后在任何匹配的行上按 Enter,这会给出描述和一些其他信息,但我必须用网络浏览器做一些研究(以及一些试错)才找到适合我的工具。

不幸的是,我无法帮助你弄清楚什么适合你,但一旦你在 Mason 中找到该工具,只需使用 i 来安装光标下的包。你在 Mason 中会经常使用的另一个命令是 Shift-U 来更新所有已安装的工具,你可以用 g? 查找其余的命令。

10.3. 验证安装是否干净

尽管 LazyExtras 和 Mason 在安装语言服务器、linting 和格式化工具方面都做得很好,但设置它们是最容易出错的地方之一,无论你使用哪个编辑器。所以现在是介绍几个命令来验证事情是否按预期工作的好时机。

首先,LazyVim 会在右上角弹出通知,就像你看到的插件更新一样。这些通知几秒钟后会消失。每隔一段时间,你需要能够回顾它们。

秘诀是使用快捷键 <Space>sn 打开“Noice”搜索菜单。Noice 是提供那些小弹出窗口的插件。大多数时候,你会想接着按 al 来查看所有最近的 Noice 消息,或者只看最后一条。

译者注: <Space>sn 是"Show Notifications" 的快捷键。Noice.nvim 是一个用于美化 Neovim 通知和消息的插件。

你也可以使用 <Space>snd 来关闭任何当前打开的通知,但老实说,等你完成那四个按键时,通知可能已经自己消失了!

你需要用于调试 LSP 的第二个命令是 <Space>cl,它运行命令 :LspInfo。它显示有关当前正在运行的任何语言服务器以及它们附加到哪些缓冲区的信息。例如,在编辑 Markdown 文档时,我的 LSPInfo 窗口看起来像这样:

lsp info dark

图 44. Lspinfo 窗口

在这种情况下,一切看起来都很好(尽管我很惊讶 tailwind 服务器与 Markdown 相关联),但如果你的 LSP 行为不正确,这个窗口可能会给你一些关于问题所在的提示。

如果你的 LSP 遇到临时问题——比如显示不正确的诊断信息或找不到你知道存在的文件——有时只需要用 :LspRestart,好好踢它一脚就行了。Svelte 语言服务器有一个坏习惯,就是不识别新文件,所以我最近经常使用这个命令,以至于为它添加了一个快捷键。

另外两个非常有用的命令是 :checkhealth:LazyHealth。两者都提供有关各种已安装插件健康状况的信息。前者是一个 Neovim 命令,插件可以向其注册以提供插件健康信息,而后者提供 LazyVim 特定的信息。输出内容有很多重叠,但我发现 :LazyHealth 的输出更容易阅读,而 :checkhealth 的输出更全面一些。所以我通常先使用 :LazyHealth,只有在 :LazyHealth 没有给出我需要的答案时才切换到 checkhealth

不要期望看到满屏的绿色对勾;你会把自己逼疯的。例如,我的 checkhealth 输出包含一堆来自 Mason 的警告:

lazy health dark

图 45. Lazy Health 警告

我最近使用过的工具(以及出于某种原因还有 Ruby的配置)都已安装,而对于我通常不需要编辑文件的语言,我收到了警告。所以如果你不用 Java 编码,就没有理由浪费精力去尝试消除 java 的警告。

10.4. 诊断信息 (Diagnostics)

语言服务器实现了几个有用的功能,包括识别代码问题、linting、格式化、上下文感知的代码导航和文档。我们将在本章和下一章讨论所有这些。

我们在第 7 章已经瞥见了诊断信息,当时我们讨论了使用 unimpaired 快捷键 [d[w[e]d]w]e 在错误消息之间跳转。诊断信息通常以特定文本段下的小波浪线形式出现,当你跳转到它们时,通常会得到一个小的覆盖窗口,告诉你该位置出了什么问题。例如,这个截图中我有一个简单的拼写错误导致了错误:

diagnostic dark

图 46. 诊断信息覆盖层

我拼错了“tracingMiddleware”,我在那一行得到了一个有用的错误消息(在虚拟文本中),并且当我用 ]d 导航到该错误时弹出了一个窗口。这个窗口有时比虚拟文本包含更多信息。此外,导入正确拼写变量的那一行显示了一个提示,告诉我它没有被使用。

译者注: 虚拟文本 (Virtual Text) 是 Neovim LSP 的一项功能,可以在代码行的末尾(或旁边)显示额外的信息,如类型提示、参数名称或诊断信息,而无需实际修改缓冲区内容。

诊断信息的颜色传达了严重性——无论是提示、警告还是错误——这样你就可以决定修复它是否有价值。我通常会尝试修复或忽略所有诊断信息,因为如果噪音太多,它们就会变得不那么有用。

如果在导航到诊断信息时窗口没有弹出,只要你的光标位于下划线文本内的某个位置,你就可以使用 <Space>cd 快捷键来调用它。你可以通过任何移动键移动光标来使窗口消失。

译者注: <Space>cd 是 “Code Diagnostics” 的快捷键,用于显示光标处诊断信息的详细信息。

10.4.1. Trouble 和 Quickfix

你也可以使用 Trouble 菜单导航诊断信息。Trouble 是一个 LazyVim 插件,提供“增强的 quickfix”体验。如果你是 Vim 新手并且不知道“quickfix”是什么意思,这对你来说毫无意义!

译者注: Quickfix 列表 是 Vim 内建的一个特殊窗口,用于显示来自编译错误、grep 搜索结果、LSP 诊断等的位置列表。你可以方便地在这些位置之间跳转。Trouble.nvim 插件提供了一个功能更丰富、界面更友好的替代方案来显示和导航这些列表。

Quickfix 窗口本质上是一个文件和行号的列表,这些文件和行号由于某种原因被标记为“有趣的”,而这个原因取决于上下文。它可以用来表示多文件搜索结果、诊断信息、编译器错误消息等,具体取决于你如何打开它。你可以轻松地在目标位置之间跳转,进行更改或修正,而不会丢失你最初搜索内容的上下文。

在其更简单的形式中,Trouble 是同样的东西,只是看起来更漂亮一些,带有颜色、图标,并且当修复位置在多个文件中时有很好的分组。

Quickfix 和 Trouble 窗口的内容完全取决于你如何打开它们。它们中的大多数都可以从 <space>x(我假设 x 代表“fiX”)菜单访问,看起来像这样:

diagnostics menu dark

图 47. 诊断菜单

让我们以待办事项 (to-dos) 为例,因为我在这本书里有很多。这么说很奇怪,因为等你看到的时候它们就已经消失了,但这张截图将永存:

todos dark

图 48. 待办事项列表

这个位置列表最酷的地方在于它们并不都在同一个文件中。没有 Trouble,我可以使用 [t]t 键在当前文件中的待办事项之间导航。然而,使用 Trouble,我可以通过将光标移动到 Trouble 窗口中的相应行并按 Enter 来在多个文件中的待办事项之间导航。它将打开文件并将光标直接移动到“有问题的”那一行。

或者你可以使用 [q]q 命令,它们将在 Quickfix Trouble 位置之间导航,无论它们在哪个文件中,甚至无需聚焦 Trouble 窗口。

译者注: [q/]q 通常用于在 Quickfix 列表中的上一个/下一个条目之间跳转。Trouble 插件通常会接管 Quickfix 列表,所以这些命令也适用于 Trouble 列表。

对于诊断信息,用 <Space>xx<Space>xX 打开 Trouble 菜单。小写版本显示当前文件中的诊断信息以供快速概览,而“但更大”的大写 X 显示当前工作空间中的所有诊断信息(尽管这有点取决于语言服务器;一些语言服务器只显示所有当前打开缓冲区的诊断信息,而不是整个项目)。

如果你想知道“位置列表”(Location List) 是什么,它是一个与当前窗口(不是缓冲区)关联的 Quickfix 窗口。我从不使用它;我的大脑一次只能处理一个问题,即使它分散在数百个文件中!

10.4.2. 从选择器发送到 Trouble 和 Quickfix

Quickfix 和 Trouble 窗口不仅仅用于引用。许多操作都可以将新的文件位置列表添加到这些窗口中。一个常见的用法是从选择器窗口激活它。任何选择器窗口都可以通过显示选择器并按 Control-q 转换为 Quickfix 窗口中的可跳转位置列表。所有文件都将成为 Quickfix 条目。你也可以在用 Control-q 将文件发送到 Quickfix 之前,用 Tab 选择文件的子集。

从选择器发送文件到 Trouble 同样简单。聚焦文件或选择多个文件,然后使用 Alt-t 将它们发送到 Trouble 窗口。

这个功能在各种选择器中都很有用,但我最常在搜索结果和“转到引用”功能中使用它,我们将在后续章节讨论。

10.5. 代码操作 (Code Actions)

VS Code 刚问世时让它看起来很神奇的事情之一是代码操作。并不是说它们存在,因为这个概念已经存在很长时间了,而是说它们能用。如今,我有点把它们视为理所当然了。

你可能习惯于通过将手移到鼠标并点击灯泡图标或右键单击诊断信息来访问代码操作。在 LazyVim 中,它(当然)是一个快捷键。使用任何适合你的快捷键(我靠 ]d 过活)导航到一个诊断信息,然后调用 <Space>ca 菜单,其中 ca 表示“code action”(代码操作)。一个选择器菜单会弹出,列出你可以执行的任何操作。你可以使用箭头键或 <Escape> 后跟 jk 在它们之间导航,或者你可以输入数字或行中的任何文本进行过滤。按 <Enter> 执行操作,或按 <Escape><Escape> 取消菜单(只需按一次 escape 就可以让你在搜索框中进入普通模式,这样你就可以使用你现在已经习惯的许多 LazyVim 导航按键了)。

10.6. 代码风格检查 (Linting)

Linting 主要是使用 nvim-lint 插件而不是 LSP 来处理的。在我使用 LazyVim 之前的日子里,这是一个主要的痛点,因为让 LSP 和 linter 协作通常需要一些认真的故障排除。然后再加上格式化,我就会浪费一两天时间。说实话,当我使用 VS Code 时也是如此。

使用 LazyVim,实际上你很可能不知道是谁在为你做 linting。老实说,我不知道。我的一些诊断信息来自 LSP,另一些来自 linter。我懒得去追问错误的来源;我只是修复它们。

Linting 的难点在于确保安装了合适的 linter(Mason 在这方面支持你),并正确配置。如果你很幸运,并且你喜欢使用的语言有 Lazy Extras,那么它可能已经正确配置了。否则,你可能需要做一些调整。不幸的是,所涉及的调整取决于语言,但你可能需要在你的 plugins 目录下的一个(例如)extend-nvim-lint.lua 文件中加入类似这样的内容:

列表 26. Nvim-lint 自定义

-- lua/plugins/extend-nvim-lint.lua
return {
  "mfussenegger/nvim-lint",
  opts = {
    -- 配置特定文件类型的 linter
    linters_by_ft = {
      typescript = { "eslint_d" }, -- 示例:为 typescript 文件指定使用 eslint_d
      python = { "flake8", "mypy" }, -- 示例:为 python 文件指定 flake8 和 mypy
      -- 根据需要为其他文件类型添加 linter
      -- lua = { "luacheck" },
      -- cpp = { "cpplint" },
    },
    -- 可以配置 linter 的参数等,参考 nvim-lint 文档
  },
}

阅读 :help nvim-lint 获取更多信息,如果需要进一步澄清,请参阅 LazyVim 关于此配置的文档。

好的一点是,一旦你配置好了 linting,错误将使用上面描述的相同诊断信息显示出来,你可以使用相同的快捷键、Trouble 窗口、代码操作等与它们互动。

10.7. 格式化 (Formatting)

与 linting 类似,代码格式化可以由某些 LSP 处理,但人们已经意识到使用语言服务器通常比直接调用格式化器更复杂。所以 LazyVim 自带了 conform.nvim 插件。

同样与 linting 类似,如果你幸运的话,在你安装了相应的 Lazy Extra 和/或 Mason 工具后,它会 Just Work™。然而,如果你不喜欢默认的格式化器(或者它不工作),你将不得不熟悉 LazyVim 和 conform.nvim 的文档来找出所需的确切配置方法。

我唯一需要手动配置的格式化器是使用 Prettier 来格式化 Markdown。它看起来与 nvim-lint 的配置惊人地相似:

列表 27. Conform 自定义

-- lua/plugins/extend-conform.lua
return {
  "stevearc/conform.nvim",
  opts = {
    -- 配置特定文件类型的格式化器
    formatters_by_ft = {
      markdown = { "prettier" }, -- 为 markdown 文件指定使用 prettier
      lua = { "stylua" },       -- 示例:为 lua 文件指定 stylua
      python = { "black", "isort" }, -- 示例:为 python 文件指定 black 和 isort
      -- javascript = { "prettier" },
      -- typescript = { "prettier" },
      -- ["*"] = { "trim_whitespace" } -- 可以为所有文件类型配置通用格式化器
    },
    -- 配置保存时自动格式化
    format_on_save = {
      timeout_ms = 500, -- 超时时间
      lsp_fallback = true, -- 如果 conform 失败,尝试使用 LSP 格式化
    },
  },
}

一旦设置好(我承认这可能不是一件容易的事),LazyVim 中的格式化通常是即发即弃的:保存你的文件,它就会格式化。如果你想在不保存的情况下手动调用它,请使用 <Space>cf 快捷键。我怎么强调你有多幸运都不为过;没有 LazyVim,无数个小时都浪费在试图让“保存时格式化”的自动命令工作上!

译者注: <Space>cf 是 “Code Format” 的快捷键。

10.8. 配置非标准 LSP

如果你安装了一个 LazyVim 不知道的 LSP,你可能需要调整 nvim-lspconfig 插件。你至少需要让它知道你的语言服务器可用,并且可能需要根据你的需求对其进行配置。例如,我最喜欢的编程语言之一是 Rescript,它没有庞大的生态系统,因此没有 LazyVim extra。我用 Mason 很容易就安装了语言服务器,但我还需要在我的 extend-lspconfig.lua 文件中添加以下内容,LazyVim 才能识别它:

列表 28. 第三方 LSP 服务器

-- lua/plugins/extend-lspconfig.lua
return {
  "neovim/nvim-lspconfig",
  opts = {
    -- 添加或修改 servers 表
    servers = {
      rescriptls = {}, -- 添加 rescriptls 并使用默认配置
      -- pyright = { settings = { ... } }, -- 可以覆盖现有 LSP 的设置
    },
    -- 可以添加 setup 函数来自定义特定 LSP 的安装后配置
    -- setup = {
    --   rescriptls = function(_, opts)
    --     -- 自定义 rescriptls 的设置
    --     return false -- 返回 false 表示使用自定义设置覆盖默认行为
    --   end,
    -- }
  },
}

第二个例子,css_variables 语言服务器,我将它与优秀的 open-props CSS 框架一起使用,它对 css 文件开箱即用,但我需要使用不同的配置来在 svelte 文件中激活它:

列表 29. Css Variable LSP 配置

-- lua/plugins/extend-lspconfig.lua
return {
  {
    "neovim/nvim-lspconfig",
    opts = {
      servers = {
        css_variables = { -- 配置 css_variables 服务器
          -- 指定它应该附加到的文件类型
          filetypes = { "css", "scss", "less", "svelte" },
        },
        -- 其他服务器配置...
      },
    },
  },
}

10.9. 总结

在本章中,我们学习了 LazyVim 如何集成 VS Code 带给世界的语言服务器协议。它通常是快速而无痛的,这比手动配置 LSP 要好得多。然而,可能还是会遇到一些麻烦,特别是在 linting 和格式化方面。这在任何编辑器中都是如此,有时它们会帮你,有时它们会碍事。如果你卡住了,请在 GitHub 上的 LazyVim discussions 群组中联系我们(但请先搜索;你可能不是第一个遇到麻烦的人)。

在下一章中,我们将学习更多关于使用 LSP、TreeSitter 和几个插件来导航代码的知识。