19-LazyVim 配置全面指南 - Some-soda

19-LazyVim 配置全面指南

第 19 章 LazyVim 配置全面指南

我们在第 5 章介绍了基本的插件配置,并且在需要配置特定插件时,我已经详细说明了如何处理更复杂的情况,但这些内容分散在整本书中。

本章将首先回顾所有这些内容,然后尝试为你提供所需的工具,以便查找和配置其他 Neovim 插件,即使它们并未作为 Lazy Extras 提供。

译者注: 回顾一下,Lazy Extras 是 LazyVim 提供的一系列预配置好的插件和设置集合,用户可以通过 :LazyExtras 命令方便地启用或禁用。我们已经走了很远很远了吧。

19.1. 插件目录

正如我们在第 5 章所介绍的,插件由 Lazy.nvim 插件管理器管理。它被配置为自动加载你配置文件夹下 lua/plugins 目录中的任何 .lua 文件。一般是 ~/.config/nvim/lua/plugins,其中 ~ 代表你的主目录。但是,如果你使用了 NVIM_APPNAME 环境变量,那么路径将是 ~/.config/$NVIM_APPNAME/lua/plugins

译者注: ~ 指用户的主目录,例如 Linux/macOS 上的 /home/username/Users/usernameNVIM_APPNAME 环境变量允许用户为不同的 Neovim 配置指定不同的名称和存储位置,方便管理多个独立的 Neovim 环境。

此目录中的 Lua 文件应始终返回一个 Lua 表(table)。因此,它们的结构将如下所示:

清单 78. Empty Plugin

return {

}

这个 Lua 表可以包含一个插件规范(plugin specification),或者一个包含多个插件规范(每个都在自己的 Lua 表中)的表。例如,结构将是:

清单 79. Basic Single Plugin

return {
  "username/plugin", -- 插件来源,通常是 GitHub 的 "用户名/仓库名"
  opts = {...},      -- 传递给插件 setup 函数的选项
  keys = {...},      -- 为插件设置的快捷键绑定
  -- ... 其他配置项
}

对于单个插件规范,或者:

清单 80. Multiple Plugins in One File

return {
  {
    "username/plugin",
    opts = {...},
    keys = {...},
    -- ...
  },
  {
    "username2/plugin2",
    -- ...
  }
  -- ... 可以有更多插件
}

就我个人而言,我通常将每个插件放在它自己的文件中,这样当我使用 <Space>fc 快捷键(查找配置文件)时很容易搜索到它们。但是,当一个插件的存在需要我修改另一个插件的配置文件时,我确实会使用包含多个插件的文件格式。

译者注: <Space>fc 是 LazyVim 中查找配置文件的默认快捷键之一。将每个插件配置放在单独的文件中(例如 lua/plugins/telescope.lua),可以使得按文件名搜索更方便。

19.2. 插件规范会级联合并

同一个插件可以在你的配置中被指定多次,LazyVim 会将所有的配置合并在一起。在以下几种情况下这很有用:

  • 有些人喜欢将键绑定(keybindings)配置在一个与插件选项和配置分开的地方。
  • 有时你希望将主要的插件配置放在一个文件中,然后让第二个相关的插件为该插件配置一些覆盖(overrides)。
  • (最常见的情况)LazyVim 预先配置了许多插件,并提供了合理的默认设置,但你偶尔会想用你偏好的键绑定或选项来覆盖这些默认设置。

**译者注:**我一直把keybinding翻译成快捷键,这里就回归原本翻译了。

19.3. 插件规范

最简单的插件规范只是一个包含单个字符串的表,该字符串包含由 / 分隔的 GitHub 用户名和仓库名。有时,这已经是你所需要的全部了,特别是对于 VimScript 插件。如果所讨论的插件并非托管在 GitHub 上,你可以省略这第一个参数,并传递 dir=/path/to/a/folderurl=https://domain.com/path/to/plugin

译者注: dir 指向本地文件系统上的插件目录。url 指向非 GitHub 的 Git 仓库地址。

如果你需要将插件固定到特定的版本或 git 分支,你可以传递 branchtagcommitversion 参数。对于托管在 GitHub 上的插件,你可以在 GitHub 上找到这些版本说明符的值。你很可能只会偶尔使用这个功能,因为通常安装插件的 main(或 master)分支是正常的做法。但是,如果你知道插件最近的更改给你带来了问题,或者你想尝试一些尚未合并的尖端功能,你就需要设置其中一个参数。

译者注: version 通常用于指定语义化版本标签,例如 version = "v1.2.3"version = "^1.0" (表示兼容 1.x 的最新版)。

你可以传递给 Lazy.nvim 插件规范的选项有将近二十多种,所有这些都在 https://lazy.folke.io/spec 有文档记录。我们在第 5 章讨论了其中的一些,即 enabledoptskeys。现在我们将接触其他几个。

19.4. 插件生命周期方法

有几个选项会在插件生命周期的不同时间点被调用。你只需要很少指定这些,但它们对于控制代码何时执行非常有用,特别是如果你试图将“原始配置”(raw config)的安装说明移植到 Lazy.nvim 配置中。

19.4.1. 构建 (Build)

build 选项在插件安装或更新时调用一次,在 Neovim 的正常启动或执行期间不会被调用。我们在第 9 章的 smart-splits 配置中看到了它的一个例子。在那个例子中,我们传递了一个指向插件附带的 shell 脚本的字符串路径。每次插件安装或升级时,该构建命令都会运行,确保相关的 Kitty 脚本已安装。除了字符串路径名,build 也可以是:

  • 一个接受插件规范作为其唯一参数的 Lua 函数。
  • 一个指向任意 Lua 文件的路径。
  • 字符串 "rockspec",这将构建一个 luarocks 包。Luarocks 是 Lua 模块的包管理器和索引,分别类似于 Python、Javascript 和 Rust 生态系统中的 pypi、npm 或 crates。
  • : 开头的字符串,将执行任意 Vim 命令。
  • 包含上述一种或多种类型的列表。

译者注: smart-splits 是一个用于在 Neovim 和终端模拟器(如 Kitty、Wezterm)之间智能导航分割窗口的插件。Luarocks 是 Lua 语言的包管理器。

作为插件的使用者,你很可能只在插件文档指示你这样做时才会指定 build 函数。

19.4.2. 初始化 (Init)

init 选项在程序启动期间执行,所以为了保持启动时间短,最好非绝对必要时避免使用它。它接受一个 Lua 函数作为参数,该函数带有一个参数,持有该插件的任何规范。(具体来说,它是一个 LazyPlugin 的实例)。

译者注: init 函数在 Neovim 启动早期、但在插件管理器加载插件定义之后、插件实际加载(如果延迟加载)之前执行。它通常用于设置全局变量或执行一些非常早期的设置。

我实际上从未在我的任何插件配置中指定过 init。通常,如果我需要插件在启动时执行,我会使用 lazy=false,这样它就会在启动时配置整个插件。我可以看到 init 在存在合法的两步设置时可能很有用,但根据我的经验,所有这些情况都发生在 LazyVim 代我管理的插件中,所以我从未需要它。

19.4.3. 配置 (Config)

config 选项是你最有可能指定的选项,但尽量只在用尽其他选项后才考虑它。它在插件加载时被调用,这可能是在启动时,也可能是在首次使用时,具体取决于它的配置方式。

关于 config,你首先需要理解的是,如果你不指定它,它的默认行为是什么。

在基于 Lua 的插件中,有一个事实上的标准,即提供一个 setup 函数,该函数接受一个参数,这个参数是该插件的选项表。你遇到的大多数插件的 README 中都会有指示,让你编写类似这样的代码:

清单 81. Non-LazyVim Setup Call

require('pluginName').setup({key = value, key2 = value2...})

他们会告诉你把这个放在你的 init.lua 文件中。如果你不使用 LazyVim,这些是好的说明,但它不适合我们。

Lazy.nvim 提供的默认 config 函数会在你的规范包含任何 opts 时自动调用此代码。所以上面的代码应该总是被移植成类似这样:

清单 82. LazyVim Options

return {
  'username/plugin',
  opts = {key = value, key2 = value2}
}

在底层,如果你在插件规范中提供了 optsconfig 函数将为你调用 .setup 代码。

这是一件很棒的事情,因为如果你在不同的地方为同一个插件提供了多个规范,LazyVim 会自动合并 opts 表。如果你覆盖了 config,你就不能那么容易地利用这种合并行为。

译者注: opts 是 LazyVim 的一个便利特性。它假设插件遵循 require('plugin').setup(options_table) 的模式。LazyVim 会收集所有为该插件定义的 opts 表,将它们深度合并(后定义的会覆盖先定义的同名键),然后自动调用 setup 函数并将合并后的 opts 传入。

然而,如果你正在配置的插件不标准,或者需要在启动时运行额外的代码,你就需要自己指定 config

config 函数总是一个接受两个参数的 Lua 函数。第一个是与 init 接收的相同的 LazyPlugin 规范。第二个是 Lazy.nvim 为你创建的 opts 表,可能是通过合并多个定义中的 opts 而来的。

这主要在什么时候成为问题呢?当你想要自定义某个插件的默认 LazyVim 配置,而正好该默认配置覆盖了 config

optskeys 不同,只有一个 config 函数会被调用(最后加载的那一个)。所以如果你为一个插件指定了 config,LazyVim 的那个就不会执行。

译者注: 这意味着如果你定义了 config,你就完全接管了插件的配置逻辑,LazyVim 默认的 require('plugin').setup(opts) 行为就不会发生,除非你在你自己的 config 函数里显式调用它。optskeys 是可以合并的,但 config 是覆盖式的。

通常,这些被覆盖的配置可以通过明智地使用 optskeys 来修改,但如果你需要执行与 LazyVim 提供的不同的命令式任务(imperative tasks),很有可能你需要将该插件的整个配置从 https://lazyvim.org 网站复制到你的个人配置中。

译者注: “命令式任务” 指需要执行一系列具体步骤或调用多个函数来完成的配置,而不仅仅是传递一个选项表。

虽然这不是一个理想的结果,但它仍然比你完全不使用 LazyVim 要好,因为那样你就必须从头开始编写整个配置,而不是复制和修改一个可信赖的来源。

19.5. 原地修改选项 (Modifying Options In-place)

如果你提供一个表(table),LazyVim 对 opts 进行的“合并”可能并不总是能正确处理所有情况,特别是对于嵌套表,或者如果你想删除而不是添加一个键。有时,更好的方法是将 opts 指定为一个 Lua 函数,该函数接收现有的 opts 作为输入并原地修改它。一个很好的例子是向仪表盘(dashboard)菜单添加一个条目:

清单 83. Modifying Options With Function

return {
  -- 假设这里是 dashboard 插件的配置入口,例如 'goolord/alpha-nvim' 或 LazyVim 自己的
  -- 这里用 "alpha-nvim" 作为例子,实际应替换为 LazyVim 使用的 dashboard 插件配置
  "goolord/alpha-nvim", -- 示例,实际插件名可能不同
  opts = function(_, opts)
    -- 假设 opts.dashboard.section.header.val 是 dashboard 的按键列表
    -- 并且我们要插入到第7个位置(索引从1开始)
    -- 注意:这里的 'opts' 结构取决于具体 dashboard 插件的配置方式
    -- 以下代码仅为示例结构,需要根据实际情况调整
    local keys_table -- = opts.dashboard.section.buttons.val  -- 假设这是按键表
    -- 示例: 假设按键表在 opts.config.layout[1].val (alpha-nvim 的一种可能结构)
    if opts and opts.config and opts.config.layout and opts.config.layout[1] and type(opts.config.layout[1].val) == 'table' then
      keys_table = opts.config.layout[1].val
      table.insert(
        keys_table,
        7, -- 插入位置,Lua 表索引从 1 开始
        {
          key = "S",
          desc = " 选择会话 (Select Session)",
          -- icon = "󰒇 ", -- 可选图标
          -- cmd = "SessionSelect" -- 假设有此命令
          cmd = function() require("persistence").select() end -- 调用 Lua 函数
        }
      )
    else
      -- 如果结构不同,需要找到正确的表并插入
      print("警告:无法找到预期的 dashboard keys 表来插入新条目。请检查 opts 结构。")
      -- 可以用 print(vim.inspect(opts)) 来查看实际的 opts 结构
    end
    -- 函数不需要显式返回 opts,因为它是原地修改的
  end,
}

-- 如果是针对 LazyVim 内置的 dashboard (lazyvim.util.dashboard)
-- 可能需要修改不同的 opts 结构,例如:
-- return {
--   "LazyVim/LazyVim",
--   opts = function(_, opts)
--     -- 查找 opts.ui.dashboard.buttons
--     if opts and opts.ui and opts.ui.dashboard and opts.ui.dashboard.buttons then
--         table.insert(
--             opts.ui.dashboard.buttons,
--             7, -- 插入位置
--             { icon = "S ", key = "S", desc = " 选择会话", cmd = function() require("persistence").select() end }
--         )
--     else
--       print("警告:无法找到 LazyVim dashboard buttons 表。")
--     end
--   end
-- }

译者注: 上面的示例代码进行了修改和补充说明。原书中使用了 snacks.nvim 插件和 opts.dashboard.preset.keys 结构。实际应用中,你需要根据你正在使用的 dashboard 插件(如 alpha-nvim 或 LazyVim 内置的 dashboard 功能)的 opts 结构来找到正确的表并插入新条目。persistence.nvim 是一个用于保存和恢复会话的插件。table.insert 用于在 Lua 表的指定位置插入元素。当 opts 是一个函数时,它的第一个参数通常是插件对象本身(这里用 _ 忽略),第二个参数 opts 是 LazyVim 合并好的选项表。添加了如何查看 opts 结构的提示。

这里,opts 被指定为一个接受两个参数的函数,我们修改了第二个参数,也就是 LazyVim 正在构建以传递给 config 的 Lua 表。

19.6. 复杂插件示例:Telescope-live-grep-args

译者注: Telescope 是 Neovim 中一个非常流行的模糊查找插件。live_grep 是它的一个功能,用于在项目中实时搜索文本。ripgrep 是一个快速的文件内容搜索工具,通常被 Telescope 用作 live_grep 的后端。

LazyVim 的插件抽象通常非常优雅,但偶尔也会碍事。我想包含一个复杂的例子,希望能帮助你应对更棘手的情况。

注意:不幸的是,这个例子已经过时了,我还没来得及找另一个。它依赖于使用 Telescope 文件选择器而不是 Snacks.nvim(译者注:Snacks.nvim 是另一个 UI 组件库,LazyVim 后来切换了默认组件),这是我不推荐的。如果你想使用它或测试这个例子,你可以在 :LazyExtras 中启用 Telescope.nvim extra。

译者注: 即使例子过时,其展示的配置思路(如何处理依赖、如何避免覆盖 config、如何处理延迟加载的函数调用)仍然具有参考价值。

LazyVim 自带的 Telescope live_grep 集成默认不允许我们向 ripgrep 传递参数。然而,有一个 telescope-live-grep-args 扩展允许自定义 ripgrep 参数。

首先访问 telescope-live-grep-args.nvim 仓库。你会找到针对 Lazy.nvim(这是插件管理器,不是 LazyVim 发行版本身)的安装说明,在撰写本文时看起来像这样:

清单 84. Incorrect Configuration for Telescope Plugin

-- 这对于 LazyVim 发行版没有帮助
use {
  "nvim-telescope/telescope.nvim",
  dependencies = {
    {
        "nvim-telescope/telescope-live-grep-args.nvim" ,
    },
  },
  config = function()
    require("telescope").load_extension("live_grep_args")
  end
}

我之所以在那里添加“没有帮助”的注释,是因为那个 config 调用。LazyVim 已经用一个相当复杂的函数配置了 Telescope,你可以在 LazyVim 网站上插件菜单的 editor 部分下找到它(点击 Full spec 标签页)。

译者注: 这里的关键在于,LazyVim 发行版已经为 Telescope 提供了一个 config 函数。如果你在自己的配置中再次定义 config,就会覆盖掉 LazyVim 的配置,可能导致其他依赖 Telescope 的 LazyVim 功能失效。

在大多数情况下,LazyVim 在合并其自身默认设置与你对其设置的各种插件所做的任何自定义方面做得很好。例如,更改或删除键绑定,或覆盖传递给 setup 函数的选项都很容易。

但是覆盖 config 并不容易。

可以做类似这样的事情:

清单 85. Another Incorrect Configuration for Telescope Plugin

-- 这仍然可能覆盖 LazyVim 的复杂 config
config = function(_, opts)
  require("telescope").setup(opts) -- 尝试模拟默认行为
  require("telescope").load_extension("live_grep_args") -- 添加我们的扩展加载
end

这样做能行,是因为 Lazy.nvim 中 config 的默认行为是调用 require(<the_plugin>).setup(opts)。所以我们基本上是将该函数的内容复制到我们的自定义函数中。但是,如果 LazyVim 碰巧为 Telescope 提供了一个非常复杂的 config,你就必须把整个东西复制进来,而且它最终会与 LazyVim 未来所做的任何更改脱节。这对你来说维护起来会很痛苦。更重要的是,它很有可能会覆盖掉 LazyVim 通过其他插件或 extras 对 Telescope 所做的任何相关更改。

总的来说,在覆盖 LazyVim 的默认设置时,我尽量避免实现 config。如果我正在添加一个 LazyVim 不知道的新插件,那么拥有一个自定义的 config 实现是可以的,因为我本来就要负责维护它。但是当我在定制插件时,我尽量不覆盖 config

(在这种情况下)秘诀是使用 Lazy.nvim 的“依赖项”(dependencies)功能。LazyVim 网站上的“完整规范”(Full Spec)有一个如何设置 telescope-fzf-native.nvim 扩展的例子,看起来像这样:

清单 86. How LazyVim Configures Telescope-fzf-native

  dependencies = {
    {
      "nvim-telescope/telescope-fzf-native.nvim",
      -- snipped some build instructions
      config = function()
        -- 确保在 telescope.nvim 加载后才加载扩展
        LazyVim.on_load("telescope.nvim", function()
          -- snipped loading the extension
        end)
      end,
    },
  },

这向我们展示了如何为一个依赖插件设置一个迷你的 config,这正是我们想要的。此外,如果我们自定义我们的规范,dependencies 是 Lazy.nvim 将会与父规范(由 LazyVim 创建的那个)合并的表之一。所以我们不需要将上面的代码复制到我们的 Telescope 扩展文件中,以免它被覆盖。相反,我们只需要创建一个新的表条目。

这是配置(我把它放在 lua/plugins/extend-telescope.lua 文件里):

清单 87. Correct Configuuration for Telescope Plugin

-- lua/plugins/extend-telescope.lua
return {
  -- 1. 确保存储库本身被 Lazy.nvim 管理
  { "nvim-telescope/telescope-live-grep-args.nvim" },

  -- 2. 修改 telescope.nvim 的配置,添加依赖和加载逻辑
  {
    "nvim-telescope/telescope.nvim",
    dependencies = {
      -- 将 live-grep-args 添加为依赖,并为其定义一个 config
      {
        "nvim-telescope/telescope-live-grep-args.nvim",
        -- 这个 config 只针对 live-grep-args 依赖项
        config = function()
          -- 使用 LazyVim.on_load 确保在 telescope.nvim 加载后执行
          LazyVim.on_load("telescope.nvim", function()
            pcall(require("telescope").load_extension, "live_grep_args")
            print("Telescope live_grep_args extension loaded.") -- 添加打印语句用于调试确认
          end)
        end,
      },
      -- LazyVim 原有的其他依赖项(如 fzf-native)会被自动合并进来,无需我们复制
    },
  }
}

译者注: 这段配置的核心思想是利用 dependencies 的合并特性。我们只定义了 live-grep-args 作为依赖,并为其指定了一个 config 函数。LazyVim 会将这个依赖项合并到它自己为 telescope.nvim 定义的依赖列表中。LazyVim.on_load 确保了扩展加载的时机是正确的,即在 telescope.nvim 插件本身加载完成之后。使用了 pcall 来安全地加载扩展,即使加载失败也不会中断 Neovim 启动。添加了一个 print 语句可以帮助确认扩展是否按预期加载。

这有点冗长,但这应该是启用该插件所需的全部内容。

接下来,你需要设置一个键绑定来调用该插件。你可以选择覆盖现有的 <Space>/ 键绑定,或者如果你想分别拥有“默认 live_grep”和“live_grep_args”模式,也许可以使用 <Space>?

如果你遵循 live-grep-args README 的指示,你的第一次尝试可能看起来像这样:

清单 88. Incorrect configuration for Live Grep Args

-- 这不会工作
keys = {
  {
    "<leader>/",
    -- 在 keys 定义时尚未加载 live_grep_args 扩展
    require("telescope").extensions.live_grep_args.live_grep_args,
    desc = "带参数 Grep (Grep with Args)",
  },
},

不幸的是,这对 LazyVim 来说太简单(指太早执行了)。因为 live_grep_args 插件已经被设置为在 LazyVim.on_load 中运行,所以在创建这个 keys 数组时它还没有被定义。

解决方案是将调用包装在另一个函数中,这样导入只在你按下键绑定之后发生。这能行,因为到那时 onLoad 处理程序已经被调用了:

清单 89. Correct Configuration for Live Grep Args

-- 这应该放在 telescope.nvim 的配置块内
{
  "nvim-telescope/telescope.nvim",
  -- ... dependencies 配置如上 ...
  keys = {
    -- LazyVim 可能已经为 <leader>/ 定义了默认的 live_grep
    -- 如果想覆盖,可以像下面这样定义。如果想用新键位,例如 <leader>?,则修改键位即可。
    {
      "<leader>/",
      function()
        -- 将 require 调用放在函数内部,确保在运行时执行
        -- 使用 pcall 保证安全调用
        local ok, lga = pcall(require, "telescope.extensions.live_grep_args.live_grep_args")
        if ok then
          lga()
        else
          print("错误:无法加载 telescope live_grep_args: " .. tostring(lga))
        end
      end,
      desc = "带参数 Grep (根目录)",
    },
    -- 你可能想保留原有的 live_grep,例如绑定到其他键
    -- { "<leader>fg", function() require("telescope.builtin").live_grep() end, desc = "Live Grep" }
  },
  -- ... opts 配置可能在下面 ...
}

译者注: 在清单 89 中添加了 pcall 来进行安全的函数调用,防止因插件未加载或函数不存在而报错。

在我们继续实际使用 live-grep-args 插件之前,还有一个地方你需要应用这种奇怪的嵌套函数调用技巧。Telescope-live-grep-args 建议将 ctrl-k 连接到 quote_prompt() 操作,像这样:

清单 90. Incorrect Configuration for Live Grep Args Keymaps

-- 不要这样做
local lga_actions = require("telescope-live-grep-args.actions") -- 顶层 require 太早了
telescope.setup {
  extensions = {
    live_grep_args = {
      mappings = { -- 扩展映射
        i = { -- 插入模式下的映射
          ["<C-k>"] = lga_actions.quote_prompt(),
        },
      },
    }
  }
}

传递给 setup 的那个表就是来自我们的 opts 数组,但我们同样需要避免像那样在顶层导入 telescope-live-grep-args。相反,我们需要一个新的函数。但是有几个陷阱:

  • quote_prompt() 是一个返回另一个函数的函数。所以我们需要用一种看起来很奇怪的 ()() 语法来调用那个函数。
  • Telescope 映射接受一个整数参数(选择器 picker 的内部 id),所以我们需要将其转发给被调用的函数。

最终的 opts 数组看起来像这样:

清单 91. Correct Configuration For Live Grep Args Keymaps

-- 这应该放在 telescope.nvim 的配置块内的 opts 字段
opts = {
  extensions = {
    live_grep_args = {
      mappings = {
        i = { -- 插入模式
          ["<C-k>"] = function(picker) -- 包装在函数内
            -- 在运行时 require,并调用返回的函数,传递 picker
            -- 使用 pcall 保证安全
            local ok, actions = pcall(require, "telescope-live-grep-args.actions")
            if ok and actions and actions.quote_prompt then
              actions.quote_prompt()(picker)
            else
              print("错误: 无法加载 telescope-live-grep-args.actions 或 quote_prompt: " .. tostring(actions))
            end
          end,
        },
      },
    },
    -- 确保这里合并了 LazyVim 可能存在的其他 Telescope extensions opts
    -- 如果使用函数式 opts,则无需手动合并基础 extensions 表,但需确保不覆盖其他扩展的映射
    -- 示例:
    -- extensions = vim.tbl_deep_extend("force", opts.extensions or {}, {
    --   live_grep_args = { ... }
    -- }),
  },
  -- 确保这里合并了 LazyVim 可能存在的其他 Telescope opts
  -- 如果使用函数式 opts,通常直接修改传入的 opts 表即可
}

译者注: 在清单 91 中同样添加了 pcall 进行安全调用。并补充了关于合并 opts 的注释,强调了在使用表形式 opts 时需要注意合并,而函数形式 opts 则更方便。

为了完整性(也因为上面所有片段单独来看可能在缩进上没有意义),这里是我完整的 Telescope 配置(放在 lua/plugins/extend-telescope.lua):

清单 92. Complete Configuration for Live Grep Args

-- lua/plugins/extend-telescope.lua
return {
  -- 1. 依赖插件本身
  { "nvim-telescope/telescope-live-grep-args.nvim" },

  -- 2. 配置 Telescope 主插件
  {
    "nvim-telescope/telescope.nvim",
    cmd = "Telescope", -- 优化:仅在调用 Telescope 时加载
    -- 添加依赖项并配置其加载
    dependencies = {
      {
        "nvim-telescope/telescope-live-grep-args.nvim",
        -- 这个 config 只针对 live-grep-args 依赖项
        config = function()
          -- 确保在 telescope.nvim 加载后执行
          LazyVim.on_load("telescope.nvim", function()
             -- 安全加载扩展
            local ok, _ = pcall(require("telescope").load_extension, "live_grep_args")
            if ok then
              print("Telescope live_grep_args extension loaded via dependency config.")
            else
              print("警告: 加载 telescope live_grep_args 扩展失败。")
            end
          end)
        end,
      },
      -- 其他依赖(如 plenary, fzf-native)会被 LazyVim 自动合并
      {"nvim-lua/plenary.nvim"},
      -- 如果需要 fzf native,确保它也被包含
      -- { "nvim-telescope/telescope-fzf-native.nvim", build = "make" },
    },
    -- 配置键绑定
    keys = {
      {
        "<leader>/", -- 或者你选择的其他键位
        function()
          -- 安全调用 live_grep_args 函数
          local ok, lga = pcall(require, "telescope.extensions.live_grep_args.live_grep_args")
          if ok then
            lga()
          else
             print("错误: 无法调用 telescope live_grep_args: " .. tostring(lga))
          end
        end,
        desc = "带参数 Grep (根目录)",
      },
      -- 可以保留或添加其他 Telescope 键绑定
    },
    -- 配置选项 (使用函数方式避免覆盖问题)
    opts = function(_, opts)
      -- 确保 extensions 表存在
      opts.extensions = opts.extensions or {}
      -- 配置 live_grep_args 的映射
      opts.extensions.live_grep_args = vim.tbl_deep_extend("force", opts.extensions.live_grep_args or {}, {
        mappings = {
          i = { -- 插入模式
            ["<C-k>"] = function(picker)
              -- 安全调用 quote_prompt
              local ok, actions = pcall(require, "telescope-live-grep-args.actions")
              if ok and actions and actions.quote_prompt then
                actions.quote_prompt()(picker)
              else
                print("错误: 无法调用 quote_prompt: " .. tostring(actions))
              end
            end,
          },
        },
      })
      -- 如果需要合并 LazyVim 的其他默认 opts,可以在这里处理
      -- 但由于传入的 opts 已经是合并后的,通常直接修改即可
      -- 例如:opts.defaults.prompt_prefix = "> "
      return opts -- 函数式 opts 推荐返回修改后的表
    end,
  }
}

译者注: 这个完整示例采用了将 opts 定义为函数的方式,这是处理与 LazyVim 默认配置合并时更安全、更推荐的方法。添加了 cmd = "Telescope" 来延迟加载 Telescope 插件,直到首次使用。在 opts 函数中使用了 vim.tbl_deep_extend 来确保安全地合并 live_grep_args 的映射,以防 opts.extensions.live_grep_args 已经存在其他配置。并明确返回了修改后的 opts 表。

我知道,这有点乱。部分混乱是因为 Telescope 本身就相当通用,即使你自己管理配置,这种混乱也仍然会存在。但部分混乱是因为当我们加入自定义插件时,需要与 LazyVim 协作,配置必然比不这样做时更复杂。像往常一样,我对此可以接受,因为我很少需要这样做,而且我很感激不必大部分时间都自己管理配置。

19.7. 配置非插件选项

Vim 是一个高度可配置的编辑器,而 Neovim 则更是如此。在 :help option-list 输出中有超过三百个选项。对于其中大部分选项,默认的 Vim 配置是没问题的,尽管有少数几个由于历史原因有着愚蠢的默认值。Neovim 已经修复了其中的一些,而 LazyVim 设置了将近三分之一的选项,使得开箱即用的体验接近大多数现代开发者想要的。

然而,你可能仍然想改变一些选项以适应你的风格。这通常在 lua/config/options.lua 文件中完成。LazyVim 默认会加载这个文件。

译者注: 这是 LazyVim 的约定,将全局 Neovim 选项放在 lua/config/options.lua 中。

对于任何给定的选项,你很可能想要设置 vim.opt.<optionname> 字段。vim.opt 是一个特殊的 Lua 表,允许你像……嗯,一个特殊的 Lua 表那样与 Vim 设置交互。如果你在搜索 Vim 设置时看到有地方说你应该调用 :set option=value,如果你想临时设置,那是可以的。但是如果你想存储设置以便下次启动 Neovim 时使用,你需要将其转换为 vim.opt.option = value

译者注: vim.opt 是 Neovim 提供的 Lua API,用于访问和设置 Vim 选项。它比传统的 :set 命令更适合在 Lua 配置文件中使用。例如,:set number 对应 vim.opt.number = true:set shiftwidth=4 对应 vim.opt.shiftwidth = 4:set listchars=tab:>-,trail:~ 对应 vim.opt.listchars = 'tab:>-,trail:~'

有时,在使用 Vim 25 年后我仍然不确定具体是什么时候,你需要改为设置 vim.g.<optionname> = value。这里的 g 指的是全局(global)。如果你看到指令要求设置一个变量,如 let g:varname = value,你可能需要使用 vim.g.varname = value。有些选项本质上是全局的,而另一些只适用于当前缓冲区(buffer)除非你指定了 g。一些插件,特别是较旧的非 Lua 插件,是通过全局变量来配置的,这也是你会用到的语法。

译者注: vim.g 用于设置全局变量,通常用于配置插件(特别是老的 Vimscript 插件)或定义你自己的全局状态。vim.b 用于设置缓冲区局部变量,vim.w 用于设置窗口局部变量,vim.t 用于设置标签页局部变量。

总的来说,使用 vim.opt,除非你在文档或你复制的代码中看到 vim.gg:

19.8. 设置颜色方案 (主题)

Vim 有两个用于设置颜色方案的选项,它们可能会以意想不到的方式相互作用。

首先,你可以将窗口背景设置为 dark(深色)或 light(浅色)。你可以在运行时使用“Ui”菜单下的 <Space>ub 键绑定来切换此设置。通常当你切换背景时,颜色方案会改变前景颜色以适应所选的背景,但这并不总是可靠。有时它甚至会将颜色方案更改为更适合浅色背景的相关方案。例如,如果你启用了 catpuccin-mocha,并将背景更改为浅色,catpuccin-latte 将成为选定的颜色方案。

译者注: <Space>ub 是 LazyVim 切换背景亮暗(Toggle Background)的快捷键。Catppuccin 是一个流行的颜色主题,有多个变种(Latte, Frappé, Macchiato, Mocha),分别对应不同的亮暗程度。

如果你想永久更改背景,请在你的 options.lua 文件中设置 vim.opt.background = "light"vim.opt.background = "dark"

要完全切换到不同的颜色方案,请使用 <Space>uC(其中 C 是大写的)。这将弹出一个选择器,其中包含所有当前安装的颜色方案。Neovim 自带了几个默认颜色方案,LazyVim 添加了 CatpuccinTokyo Night(默认)的几个变种。这些颜色方案相对于 Neovim 提供的方案的优势在于,它们为 LazyVim 安装的各种插件提供了大量额外的语法高亮组(highlight groups)。

译者注: <Space>uC 是 LazyVim 切换颜色方案(Colorscheme)的快捷键。

如果你想永久更改颜色方案,它应该作为 LazyVim/LazyVim 插件的一个 opt 来设置。我更喜欢 catppuccin 颜色方案,所以我有一个 lua/plugins/theme.lua 文件(或 core.lua,文件名不重要),内容如下:

清单 93. Colour Scheme Configuration

-- lua/plugins/theme.lua
return {
  {
    "LazyVim/LazyVim",
    opts = {
      -- colorscheme = "tokyonight", -- 默认是 tokyonight
      colorscheme = "catppuccin", -- 设置为 catppuccin (需要安装 catppuccin 插件或使用 extra)
      -- 如果使用 catppuccin extra, extra 会自动处理插件安装和设置 colorscheme
      -- 如果手动安装,需要确保 catppuccin 插件已安装
    },
  },
}

译者注: 设置 colorscheme 的最推荐方式是通过启用 LazyVim 的主题 extra,例如 :LazyExtras 中启用 theme.catppuccin。Extra 会负责安装主题插件并将此 colorscheme 选项设置好。如果手动配置,如清单 93 所示,你需要确保对应的主题插件(例如 catppuccin/nvim)也被安装了。

但是,如果你想将颜色方案设置为 LazyVim 默认未附带的方案,你需要安装提供该颜色方案的插件。例如,流行的经典主题 gruvbox 可以这样安装:

清单 94. Third Party Colour Scheme

-- lua/plugins/theme-gruvbox.lua
return {
  -- 1. 安装 gruvbox 主题插件
  { "ellisonleao/gruvbox.nvim", lazy = false, priority = 1000 }, -- 建议尽早加载主题

  -- 2. 配置 LazyVim 使用该主题
  {
    "LazyVim/LazyVim",
    opts = {
      colorscheme = "gruvbox",
    },
  },
}

译者注: 对于第三方主题,建议设置 lazy = false 和较高的 priority (例如 1000),以确保主题在 Neovim 启动时尽早加载和应用,避免界面闪烁。

在寻找新的颜色方案时,尝试寻找维护良好、支持 “treesitter” 并为你经常使用的所有插件(包括 LazyVim 自带的插件)提供高亮组(highlight groups)的仓库。你可以在 Awesome Neovim list 上找到颜色方案。

译者注: “Treesitter support” 指颜色主题能利用 Treesitter 提供的更精细的语法信息来着色代码,通常效果更好。“Highlight groups” 是 Vim/Neovim 用来定义界面元素(如关键字、注释、背景、状态栏等)颜色的机制。好的主题会为常用插件(如 Telescope, NvimTree 等)定义专门的高亮组,使界面风格统一。

19.9. 惰性加载 (Lazy Loading)

LazyVim 会自动按需惰性加载(lazy-load)插件,而不是在程序启动期间加载。这可以为你的启动时间节省几毫秒(真是非常宝贵的资源呢)。

译者注: 惰性加载意味着插件的代码只在实际需要时才被加载到内存中执行,而不是 Neovim 一启动就全部加载。这对于加快启动速度非常重要。惰性加载常应用于图片加载、模块导入、数据库查询等场景,以节省内存和带宽。

LazyVim 知道在插件的代码被 require 时,或者在 keys 数组中指定的任何键绑定被按下时加载插件。这通常正是你希望它加载的时候。然而,如果你遇到插件工作不正常的情况,你可能需要调整惰性加载配置。

首先,尝试在插件规范中添加 lazy = false。这将确保插件在启动时加载。如果在添加这个之后插件工作正常了,你有两个选择:

  • 就保留 lazy = false,然后继续你的生活。
  • 进行微优化,尝试强制插件在正确的时间惰性加载。

在大多数情况下,我推荐第一种选择。微优化在为公开发布优化插件的背景下是有意义的。插件维护者可能会在他们的包中使用它,当然像 Folke(LazyVim 作者)本人这样的发行版维护者会花费大量时间在这上面。但对你来说,这可能不值得。

在启用和禁用惰性加载的情况下运行插件,并在仪表盘(dashboard)中检查启动时间。如果它产生了你想要解决的差异,请继续阅读。

如果一个插件只应针对某些文件类型(filetypes)指定,那么在规范中添加一个 ft 键。这个键接受一个字符串或代表文件类型的字符串列表。(要获取当前缓冲区的文件类型,输入 :set ft?:lua print(vim.bo.filetype)<Enter>)。

译者注: 例如 ft = "python"ft = { "javascript", "typescript" }。当打开对应类型的文件时,插件会被加载。修正了获取文件类型的命令。

如果插件只应在调用某些命令(commands)时加载,那么在 cmd 键下指定一个包含命令名称的字符串列表。

译者注: 例如 cmd = "Telescope"。当用户执行 :Telescope 命令时,插件会被加载。

你还可以指定一个应触发插件加载的 Neovim 事件(event)。这些事件太多了,无法在本书中列出,所以我将引导你参考 :help events。最常用于触发插件加载的事件是 BufEnter(进入缓冲区时)、BufRead(读取缓冲区后)和 BufWrite(写入缓冲区前/后)。

译者注: 例如 event = "BufReadPre"event = { "BufReadPost", "BufNewFile" }。常用的还有 VeryLazy 事件,它在 UI 加载完成后触发,适合加载不需要立即使用的插件。

19.10. 文件类型特定配置

如果你需要为特定文件类型配置某些操作,你需要 nvim_create_autocmd 函数。从技术上讲,你可以将这个调用放在任何地方,包括 init.lua 或特定插件的 configinit 中,但 LazyVim 的约定是把它们放在 lua/config/autocmds.lua 文件中。

译者注: Autocommand(自动命令)是 Vim/Neovim 的一个强大特性,允许你在特定事件发生时(如打开某种类型的文件、保存文件等)自动执行命令或函数。nvim_create_autocmd 是 Neovim 提供的用于创建自动命令的 Lua API。

我实际上只有一个自动命令,因为 LazyVim 在为我配置特定于文件类型的行为方面做得非常好(通常使用相应的 lang.* LazyVim Extra)。那个命令看起来像这样:

清单 95. Filetype-specific Autocommands

-- lua/config/autocmds.lua

-- 获取 Neovim API
local api = vim.api

-- 创建自动命令组 (可选但推荐,方便管理)
local my_filetype_group = api.nvim_create_augroup("MyFileTypeSettings", { clear = true })

-- 定义自动命令
api.nvim_create_autocmd({ "BufRead", "BufNewFile" }, {
  group = my_filetype_group, -- 关联到自动命令组
  pattern = "*.svx", -- 匹配 .svx 文件
  -- command = "setlocal filetype=markdown", -- 执行 Vim 命令设置文件类型为 markdown
  -- 或者使用 callback 执行 Lua 函数
  callback = function(args)
    -- args.buf 是触发事件的缓冲区编号
    vim.bo[args.buf].filetype = 'markdown' -- 设置缓冲区局部选项 filetype
    print("已为 .svx 文件设置 filetype 为 markdown (缓冲区: " .. args.buf .. ")") -- 添加打印用于确认
  end,
})

-- 你可以在这里添加更多的自动命令...

译者注: 对清单 95 进行了补充和改进。可能会导致预期之外的行为(*^_^*)

  1. 添加了 vim.api 的引用。
  2. 推荐使用 nvim_create_augroup 创建自动命令组,方便管理和清除 (clear = true 会在加载配置时清除同名组,避免重复定义)。
  3. 同时展示了使用 commandcallback 两种方式。对于 Lua 配置,callback 通常更灵活,并且可以接收包含事件信息的参数(如 args.buf)。
  4. 使用 vim.bo[args.buf].filetype 设置缓冲区局部选项,这是更精确的方式。
  5. 添加了包含缓冲区编号的 print 语句帮助调试。 *.svx 是一种结合了 Svelte 和 Markdown 的文件格式 (Svelte + MDX)。这个自动命令的作用是告诉 Neovim 将 .svx 文件识别为 Markdown 文件,以便应用 Markdown 的语法高亮和相关设置。

第一个参数是一个无键(keyless)的 Lua 表,包含字符串事件名称;在本例中,每当我创建或读取文件时,自动命令就会运行。你还可以使用其他事件,但大多数情况下,除非你正在编写自己的插件,否则你只需要这两个事件(完整的列表请参见 :help autocmd-events)。

第二个参数包含自动命令的选项。在本例中,我包含了一个 pattern,这是一个用于匹配我想要的文件类型的 Vim 正则表达式。我特意说了“Vim 正则表达式”来解释那个笨拙的 \*,正如第 12 章所讨论的。

译者注: Vim 的正则表达式语法与常见的 PCRE 有些许不同,例如 * 需要转义成 \* 来匹配字面星号,而未转义的 * 表示重复前一个原子零次或多次。. 匹配任意字符,所以 *.svx 匹配以 .svx 结尾的任何文件名。

在本例中,我使用 command 键在每次读取 *.svx 文件时执行一个命令。你也可以指定一个 callback 键,其值是一个 Lua 函数,该函数将在事件发生时被调用。

19.11. 项目特定配置

有时,你会希望为特定项目提供自定义的 LazyVim 配置。例如,我的大多数 Typescript 项目都是用 Svelte 编写的,这意味着我还不能(目前)为它们使用出色的 biome linter/formatter。这意味着我在这些仓库中使用 prettier 进行格式化。我的 lua/plugins/extend-conform.lua 插件规范看起来像这样:

译者注: conform.nvim 是一个用于管理和运行格式化工具的 Neovim 插件。biome (前身为 Rome) 和 prettier 都是流行的代码格式化工具。

清单 96. Prettier in Default Configuration

-- lua/plugins/formatting.lua (或任何你放 conform 配置的文件)
return {
  {
    "stevearc/conform.nvim",
    opts = {
      -- 配置按文件类型使用的格式化器
      formatters_by_ft = {
        ["typescript"] = { "prettier" },
        ["typescriptreact"] = { "prettier" }, -- 可能也需要为 .tsx 配置
        ["javascript"] = { "prettier" },
        ["javascriptreact"] = { "prettier" }, -- 可能也需要为 .jsx 配置
        ["markdown"] = { "prettier" },
        ["yaml"] = { "prettier" },
        ["svelte"] = { "prettier" }, -- Svelte 项目使用 prettier
        -- 其他文件类型...
        ["lua"] = { "stylua" },
        ["python"] = {"isort", "black"}, -- 示例:Python 使用多个格式化器
      },
      -- 可以配置 format_on_save 等其他选项
      -- format_on_save = {
      --   timeout_ms = 500,
      --   lsp_fallback = true,
      -- },
    },
  },
}

译者注: 添加了对 JavaScript/TypeScript React 文件类型的配置示例,补充了 Lua 文件类型的格式化器 stylua,并添加了 Python 使用多个格式化器的示例 (isortblack)。

然而,对于我的 Typescript API 服务器,我可以使用 Biome,并且我只想为那些项目覆盖我的配置。

LazyVim 让这变得极其简单:只需在你的项目根目录下创建一个 .lazy.lua 文件。它可以返回任何有效的插件规范,并且它将在加载任何其他插件之后被调用,覆盖它们。所以我的 Hono API 服务器有一个 .lazy.lua 文件,看起来像这样:

译者注: Hono 是一个用于 Web 框架,常用于构建 API。.lazy.lua 文件是 LazyVim 提供的项目级配置入口。

清单 97. Project-specific .lazy.lua

-- /path/to/my/hono-api-project/.lazy.lua
return {
  {
    "stevearc/conform.nvim",
    -- 注意:这里只定义需要覆盖的 opts 部分
    opts = {
      formatters_by_ft = {
        -- 仅覆盖 typescript 的格式化器为 biome
        ["typescript"] = { "biome" },
        ["typescriptreact"] = { "biome" }, -- 如果项目中有 tsx 文件
        -- 其他文件类型(如 markdown, yaml)将保持全局配置(即 prettier)
        -- 如果全局配置了 python, 这里的 python 设置会覆盖全局的
        -- ["python"] = {"ruff_format"} -- 示例:项目特定地使用 ruff
      },
    },
  },
  -- 你还可以在这里为这个项目添加或禁用其他插件
  -- 例如,禁用全局配置的一个插件:
  -- { "some/unused-plugin", enabled = false }
}

译者注: .lazy.lua 中的配置会与全局配置进行深度合并(deep merge)。对于 opts 表,.lazy.lua 中的设置会递归地覆盖全局 plugins/*.lua 文件中同名的键。这意味着在这个例子中,只有 TypeScript (和 TSX) 文件的格式化器被改成了 biome,而 Markdown, YAML 等其他文件类型仍然使用全局配置中指定的 prettier。还添加了项目特定覆盖其他文件类型(Python)和禁用插件的示例。

你可以选择将此文件提交到你的项目中,或者根据项目的标准将其添加到 .gitignore 文件中。

19.12. LazyVim 配方 (Recipes)

LazyVim 很有帮助地在其主页上收集了一些最常被要求的功能作为“配方”(recipes)。其中大部分可以直接复制粘贴到你的 lua/plugins 文件夹下的任何文件中。记得在它们前面加上 return,以便它们提供的任何表都能被导出。

大多数配方只是提供了一组建议的 opts 来触发所讨论的行为。这些 opts 总是特定于插件的,你需要访问插件的帮助文件或 README 来理解它们的作用。

19.13. 总结

本章全部是关于配置 LazyVim 的。其中很多内容可能是我在书中添加插件以支持特定功能时所用示例的回顾。但是,我想确保在一个地方涵盖所有内容,以便你想要查找时使用。