09-缓冲区与布局 - Some-soda

09-缓冲区与布局

第 9 章 缓冲区与布局

无论你使用哪种编程语言,都不可避免地会同时处理多个文件。以及在同一文件内的多个区域工作。

像所有编码编辑器(除了记事本)一样,Neovim 有一套强大的处理多个文件的系统。LazyVim 配置了一个强大的缓冲区、文件和窗口管理系统,起初可能感觉熟悉,但实际上比你的普通编辑器强大得多。

9.1. 一些术语

似乎每个窗口管理系统都用相同的词来指代不同的事物。如果你阅读例如 tmux、emacs、kitty、vim 和 i3 的文档,你会得到像“window”、“pane”、“tab”和“layout”这些词的多种定义。

我将坚持使用 Vim 对这些词的定义,这样你可以在本书和大多数 Vim 及 Vim 插件帮助文件、教程和文档之间切换而不会混淆。不幸的是,这可能意味着你在与其他任何软件交互时会感到困惑!

这个列表大致从最不具体到最具体排列,但请理解,这些元素中大多数之间的关系是图状而非树状;它不是一个严格的层级结构。

  • 服务器 (Server)

    Neovim 可以以服务器模式运行,并且可以有多个客户端连接。这意味着你可以有多个视图指向同一个 Neovim 实例,而这些视图可以来自不同的终端或 GUI 软件、Web 浏览器甚至是 VS Code 扩展。你可能不需要考虑 Neovim 服务器,我在本书中也不会再提及它。但如果你想做一些有趣的事情,例如连接到一个现有的 Neovim 实例来打开一个提交信息,而不是在一个新窗口中打开 Neovim,你现在知道这是可能的。

  • 客户端 (Client)

    你实际运行的 Neovim 应用程序。通常连接到它自己的独立服务器,但可以配置为连接到现有的或远程的服务器。当你输入 nvim 时,一个终端客户端会启动,但其他客户端包括像 Neovide 或 VimR 这样的 GUI。

  • 标签页 (Tab)

    一个客户端可以有多个标签页。每个标签页是一个或多或少独立于其他标签页的全屏布局。你可以在每个标签页上拥有不同的可见缓冲区和不同的窗口分割配置。任何时候只有一个标签页是可见的。这与 VS Code 和许多其他环境中每个分割都有自己的一组标签页的范式大相径庭。

    译者注: Vim 的 Tab Page 更像是工作区或布局的集合,而不是像浏览器或 VS Code 那样用于单个文件的标签。

  • 窗口 (Window)

    也称为“窗格”(pane) 或“分割”(split),窗口是屏幕上专门用于查看缓冲区的一个区域。每个标签页上有一个或多个窗口。每个窗口通常是完全可见的;窗口之间没有重叠。例外是浮动窗口,例如当你打开选择器或 Lazy Extras 时弹出的那些。如果缓冲区的内容不适合窗口大小,可以滚动窗口。

  • 缓冲区 (Buffer)

    这是 Vim 对当前已打开并可供查看/编辑的文件的称呼。一个缓冲区可以显示在多个窗口中,这意味着你可以有两个并排的视图指向同一个文件,但处于不同的滚动位置,或者你可以在多个标签页中查看同一个缓冲区。如果一个缓冲区在两个地方都可见,它们将拥有完全相同的内容(除了滚动位置)。对于每个文件,永远只有一个底层的缓冲区被打开,无论该缓冲区的视图在不同的窗口或标签页中可见多少次。

    译者注: Buffer 是内存中文件的副本。Window 是 Buffer 的显示视口。Tab 是 Window 的布局集合。

  • 折叠 (Fold)

    在缓冲区的任何一个视图内,可以将该文件的一个部分(例如一个函数、类或缩进级别)“折叠”成单行,有效地隐藏其内容。这允许你同时查看同一文件的两个不相连的部分,同时保持这两个部分之间不相关的信息被隐藏。

  • 文件 (File)

    存在于磁盘上的文件。每个缓冲区最多链接到一个文件,尽管也可能存在没有文件的缓冲区(有时称为“草稿”缓冲区,这个词借鉴自 Emacs 的说法)。如果缓冲区尚未保存,其内容可能与磁盘上文件的内容不同。

到目前为止,在本书中,你所有的交互都发生在一个标签页的单个窗口中,可能打开了多个缓冲区。现在,事情将变得有趣得多。

9.2. 缓冲区 (Buffers)

如果你用过选择器、explorer 或 mini.files 打开过多个文件,你很可能会认为缓冲区就是一个标签页。在这个视图中,我打开了三个缓冲区,其中只有一个当前可见:

buffer line dark

图 33. 包含三个缓冲区的 Bufferline

是的,我知道它们看起来像任何其他软件中的标签页,但这是因为 LazyVim 配置了 bufferline 让它看起来像标签页。当 bufferline 可见时,你可能实际上不需要经常使用(真正的)Vim 标签页,但在 Vim 中,标签页是一个完全不同的概念。

译者注: Bufferline 是一个 UI 插件,用于在顶部(或底部)显示已打开的缓冲区列表,模仿了其他编辑器的标签页功能,方便在缓冲区之间切换。但这不是 Vim 内建的 Tab Page 功能。

无论你打开了多少个窗口,只有一个 bufferline。在下面的截图中,我打开了三个缓冲区,其中两个在并排的独立窗口中可见。但编辑器顶部仍然只有一个 bufferline。

buffer line split dark

图 34. 只有一个 Bufferline

这意味着缓冲区是一个“全局”概念。整个 Neovim 客户端有一个缓冲区集合,你可以从任何窗口(或标签页)访问这些缓冲区中的任何一个。

你当然可以用鼠标单击 bufferline 来选择不同的缓冲区。但是当 LazyVim 中有如此多用键盘访问缓冲区的方法时,你为什么要那样做呢?

9.2.1. 在打开的缓冲区之间导航

切换缓冲区的绝对最简单方法是使用 HL(即 Shift-hShift-l)键。到这时,你可能已经非常熟悉 h 表示左,l 表示右用于光标移动。如果你同时按下 shift 键,你将把当前活动窗口中可见的缓冲区切换为 bufferline 中当前缓冲区左侧或右侧的那个。

译者注: H/L 在 bufferline 插件中用于在可见的缓冲区标签之间左右移动。

或者,你可以使用映射到相同功能的 [b]b 命令。

译者注: [b/]b (previous/next buffer) 是更通用的缓冲区切换方式,不依赖 bufferline 插件。

令人烦恼的是,你会发现这些快捷键不接受计数。所以默认情况下,你不能使用 2L 向右跳转两个标签。这让我很沮丧,因为我知道底层的 :bnext:bprev 命令确实接受计数。

事实证明,LazyVim 将这些映射到底层插件 bufferline.nvim 提供的 BufferLineCycleNext 命令,而据我所知,该插件不支持计数。

经过调查,我了解到 BufferLineCycle* 命令的存在是因为该插件可以为缓冲区列表配置某种排序机制。但 LazyVim 没有配置使用该机制。所以我们可以改用老式的命令。为此,在你的 plugins 配置文件夹中创建一个名为(例如)extend-bufferline.lua 的新文件:

列表 23. 简化缓冲区导航快捷键

-- lua/plugins/extend-bufferline.lua
-- 注意:只有在你启用了 bufferline.nvim 插件时才需要这个
return {
  "akinsho/bufferline.nvim",
  keys = {
    -- 使用 vim.cmd 执行 :bnext/:bprev 并传递计数
    {
      "L",
      function() vim.cmd("bnext " .. vim.v.count1) end,
      desc = "下一个缓冲区",
    },
    {
      "H",
      function() vim.cmd("bprev " .. vim.v.count1) end,
      desc = "上一个缓冲区",
    },
    -- 也覆盖 [b 和 ]b,使其行为一致并支持计数
    {
      "]b",
      function() vim.cmd("bnext " .. vim.v.count1) end,
      desc = "下一个缓冲区",
    },
    {
      "[b",
      function() vim.cmd("bprev " .. vim.v.count1) end,
      desc = "上一个缓冲区",
    },
  },
}

如果你像我一样禁用了 bufferline,你就不需要上面的配置。在默认的 LazyVim 快捷键中,]b[b 已经映射到了 bnextbprev

vim.v.count1 变量在快捷键被带计数调用时设置,因此可以在回调内部访问它,并使用字符串连接(.. 操作符)将其传递给 Vim 命令。重启 Neovim,你就可以做类似 3L 的操作来向右跳转 bufferline 上的三个缓冲区了。

另一个你在缓冲区之间跳转时会想用的快捷键是 <Space><Backtick> (空格 + 反引号 `)。这个键简单地在当前文件和最近在当前窗口中打开的文件之间跳转。在 Vim 的说法中,这被称为“交替文件”(alternate file)。

译者注: Vim 中 <C-^> (Ctrl + 6) 或 :b#:e# 通常用于切换到交替文件。<Space> 是 LazyVim 的自定义映射。

如果你打开了大量的缓冲区,bufferline 可能会变得非常拥挤。到某个时候,它会在缓冲区栏的左侧和/或右侧显示两个箭头,告诉你存在“隐藏”的缓冲区。当你导航缓冲区时,它总是确保活动缓冲区是可见的。这是一个非常满的 bufferline,左边隐藏了四个缓冲区,右边隐藏了两个:

buffer line full dark

图 35. 完整的 Bufferline

如果你打开了这么多缓冲区,你可能会发现使用选择器来搜索打开的缓冲区更容易。弹出当前打开缓冲区的可过滤、可滚动列表的快捷键是 <Space><comma> (空格 + 逗号)。它的内容与 bufferline 完全相同,但交互效果不同。

如果你正在处理的文件太多以至于用 <Space><Space> 搜索它们很困难的大型项目,这会很有用。将你实际需要访问的相对较少数量的文件作为活动缓冲区打开,这样它们就很容易在 <Space><comma> 缓冲区列表中过滤。

就我个人而言,我完全禁用了 Bufferline 插件,而 <Space><comma> 是我管理缓冲区的主要界面。

9.2.2. 关闭缓冲区

你经常会想要关闭当前缓冲区,而不关闭它当前所在的分割窗口。 bunun için anahtar bağlama <Space>bd dir, burada <Space>b diğer tamponla ilgili işlevlerin yararlı bir menüsünü açar ve d “sil” anlamına gelir. Bunu yaptığınızda aslında temel dosyayı silmiyorsunuz; sadece tamponu Vim’in belleğinden siliyorsunuz: yani kapatıyorsunuz.

译者注: <Space>bd (buffer delete)。

以下是你可以用来关闭缓冲区的其他几个命令:

快捷键 描述 助记符
<Space>bD 关闭缓冲区及其所在的窗口分割。 Delete 但“更大”
<Space>bl 关闭标签行(Bufferline)中左侧所有缓冲区。
<Space>br 关闭标签行(Bufferline)中右侧所有缓冲区。
<Space>bo 关闭除活动缓冲区外的所有缓冲区。 only” this buffer (仅此)
<Space>bP 删除所有未固定的缓冲区。 “P” 与 “p” 相反

最后一个需要一些说明。你可以使用 <Space>bp (buffer pin) 在任何活动缓冲区上切换“固定”状态。你会看到缓冲区名称左侧出现一个图钉图标。这个图钉的唯一目的是,如果你想使用 <Space>bP 关闭所有“不太重要”(未固定)的文件时,让它保持打开状态。我个人不使用缓冲区固定,所以对我来说,<Space>bP 是“关闭所有缓冲区”的快捷方式;在我完成一项任务并准备开始另一项时很有用。

我发现某些“关闭缓冲区”的操作太常见了,不值得用三个键,所以我会在我的 keymaps.lua 中添加以下三个快捷键:

列表 24. 关闭缓冲区键映射

-- lua/config/keymaps.lua
-- 注意:Snacks.bufdelete* 可能已过时,建议使用 :bd, :bufdo bd 等命令
-- 使用 :bd 关闭当前缓冲区
vim.keymap.set("n", "<leader><delete>", "<cmd>bdelete<cr>", { desc = "关闭缓冲区" })
-- 使用 :bufdo bdelete | b# 删除其他缓冲区 (保留当前)
vim.keymap.set("n", "<leader>bo", "<cmd>%bd|e#<cr>", { desc = "关闭其他缓冲区" })
-- 使用 :%bd 关闭所有缓冲区 (警告:这会关闭所有,包括未保存的)
vim.keymap.set("n", "<leader><CR>", "<cmd>%bdelete!<cr>", { desc = "关闭所有缓冲区" })

译者注: 更新了代码示例,使用更健壮的 Vim 内建命令 :bdelete (:bd) 及其变体来关闭缓冲区,而不是依赖可能变化的 Snacks 插件 API。

  • <leader><delete>: 关闭当前缓冲区。
  • <leader>bo: 关闭除了当前缓冲区之外的所有其他缓冲区。%bd 尝试删除所有缓冲区,|e# 重新打开上一个缓冲区(即当前缓冲区)。
  • <leader><CR>: 强制关闭所有缓冲区 (!)。

前两个行为类似 <Space>bd<Space>bo,分别关闭当前或所有其他缓冲区。第三个使用我们将在第 12 章讨论搜索时介绍的 ex 命令和范围语法关闭所有缓冲区。

你也可以直接从缓冲区选择器界面(来自 <Space>,)使用 Control-x 来关闭光标所在的缓冲区。

9.2.3. 草稿缓冲区 (Scratch Buffers)

草稿缓冲区是记录项目笔记的便捷区域。历史上,我总是把笔记放在一个单独的新窗口中,如果意识到需要让它持续超过一个编辑会话,就用某个随意的名字保存它。除了……有时我会忘记保存它。

草稿缓冲区与当前工作目录绑定。要打开一个,只需使用 <Space>. (空格 + 句点)。它会弹出一个浮动窗口,像这样:

empty scratch dark

图 36. 草稿缓冲区

在里面输入任何你想输入的内容,按 <Escape> 切换到普通模式,然后按 q 关闭缓冲区。如果你愿意,可以关闭并重新打开 Neovim;草稿缓冲区不在乎。你的笔记将被保存,直到你再次需要它们。然后只需再次按 <Space>.,只要你的 cwd 与你上次打开草稿缓冲区时相同,它们就在那里。

译者注: 草稿缓冲区通常由插件(如 scratch.nvimsnacks.nvim 的一部分)提供,用于临时记录,内容通常保存在 Neovim 的特定数据目录中,与项目目录关联。

偶尔,你可能需要打开一个在不同 cwd 或 git 分支上保存的草稿缓冲区。你可以使用 <Space>S 快捷键查看草稿缓冲区的选择器列表。

现在你对缓冲区了如指掌了,让我们来讨论窗口。

9.3. 窗口 (Windows)

在大多数现代环境中,“窗口”指的是操作系统级别的窗口,例如你运行 Neovim 的终端。由于 Vi 早于此类环境,他们能够使用窗口这个词来指代现在在其他环境中更常被称为“窗格”(panes) 或“分割”(splits) 的东西。

窗口管理命令收集在 <Space>w 子模式菜单中:

window menu dark

图 37. 窗口菜单

我们将在以下部分介绍其中的许多内容。

这个菜单也可以用 Control-w 访问。历史上,这是 Vim 和 Neovim 中默认启用的快捷键,尽管 LazyVim 为其添加了一些额外的快捷键。然而,<Space>w 输入起来更容易一些。

9.3.1. 创建窗口分割

LazyVim 中的窗口可以随时动态创建。要将当前窗口“垂直”分割成两半,左边一个窗口,右边一个新窗口,请使用 <Space>wv 键映射 (window vertical)。

当你创建一个分割时,新窗口将包含你已经在查看的缓冲区的另一个视图,并排显示。但是一旦分割打开,你可以使用任何缓冲区管理命令或通过使用我们之前讨论过的任何打开文件的工具来打开一个新文件,从而切换该窗口中的缓冲区。

要创建两个窗口之间的水平分割(一个在另一个上方),请使用 <Space>ws (window split)。不幸的是,这个的助记符只是“分割”。他们不能重用 <Space>wh,因为那已经被用于切换窗口了。

LazyVim 也允许你用 <Space>| 创建垂直分割,其中 | 是你 Shift-反斜杠 时的竖线,以及用 <Space><Minus> (空格 + 减号) 创建水平分割。我觉得 <Space>ws<Space>wv 输入起来更容易。

9.3.2. 打开文件时创建分割

你已经知道可以通过将光标移动到 Neo-Tree 中的文件并按 <Enter> 来在当前窗口中打开文件。你也可以在 Neo-Tree 中使用 s 键将其在垂直分割中打开(令人恼火地与在普通缓冲区中创建水平分割的 <Space>ws 键不对称)。Neo-Tree 中的大写形式 S 用于创建水平分割。

如果你使用选择器打开文件,你将使用另一套快捷键!要在垂直分割中打开文件,请使用 Control-v 快捷键。要在水平分割中打开它,请使用 Control-s

译者注: 在文件选择器(如 Telescope)中:Ctrl-v = vertical split, Ctrl-s = horizontal split, Ctrl-t = new tab。

最后,如果你使用 mini.files,你可以合理地使用与普通窗口中相同的快捷键(<Space>wv<Space>ws)在分割中打开文件。

9.3.3. 在窗口之间导航

你可以通过按住 control 键并配合任何 hjkl 主行“箭头键”方向来在窗口分割之间移动光标。如果你想跳过一个窗口到达下一个窗口,它们也可以前缀数字计数。

译者注: Ctrl-w h, Ctrl-w j, Ctrl-w k, Ctrl-w l 是 Vim 内建的在窗口间移动的标准方式。

或者,你可以使用 <Space>w 配合相同的键。所以 <Space>wh 将移动到当前窗口左侧的窗口。

智能分割 (Smart Splits)

我建议使用 mrjones2014/smart-splits.nvim 插件,它可以配置为使用相同的快捷键在 Vim 窗口和 Kitty、Wezterm 或 Tmux 窗格之间导航。考虑这个截图:

vim kitty splits dark

图 38. Kitty 和 Vim 分割

我打开了三个 Kitty 终端窗格。左边一个运行着 Neovim,里面有两个窗口,一个在另一个上方。右边被分割成两个普通的终端窗格。默认情况下,如果我想在三个 Kitty 窗格之间导航,我必须使用一套快捷键,如果我想在两个 Neovim 窗口之间导航,我必须使用另一套快捷键。有了 smart-splits.nvim 插件,我可以用相同的快捷键在所有窗口之间导航,无论我的光标在哪里。

设置 smart splits 的终端集成超出了本书的范围(GitHub 仓库中 README 上的文档应该足够了),但在 Neovim 中配置 smart-splits 插件,请在 plugins 目录中创建一个名为例如 smart-splits.lua 的文件:

列表 25. Smart-splits 配置

-- lua/plugins/smart-splits.lua
return {
  "mrjones2014/smart-splits.nvim",
  -- 如果使用 Kitty,可能需要构建步骤
  -- build = "./kitty/install-kittens.bash",
  -- 如果使用 WezTerm 或 Tmux,通常不需要 build
  lazy = false, -- 建议尽早加载以便快速响应
  keys = {
    -- Alt + hjkl 移动光标到相邻的 Vim 窗口或终端窗格
    {
      "<A-h>",
      function() require("smart-splits").move_cursor_left() end,
      desc = "移动到左侧窗口",
    },
    {
      "<A-l>",
      function() require("smart-splits").move_cursor_right() end,
      desc = "移动到右侧窗口",
    },
    {
      "<A-j>",
      function() require("smart-splits").move_cursor_down() end,
      desc = "移动到下方窗口",
    },
    {
      "<A-k>",
      function() require("smart-splits").move_cursor_up() end,
      desc = "移动到上方窗口",
    },
    -- 可以添加 Alt + Shift + hjkl 来调整窗口大小
    -- { "<A-S-h>", function() require("smart-splits").resize_left() end, desc = "向左调整大小" },
    -- ... 其他调整大小的映射
  },
}

译者注: <A-h> 表示 Alt+h。你需要确保你的终端配置能正确传递 Alt 组合键。

如果你使用 WezTerm 或 Tmux,你不需要 build = 行,但对于所有这三种环境,你还需要像插件 README 中描述的那样,向你的 Kitty、WezTerm 或 Tmux 配置中添加一些配置。

9.3.4. 关闭窗口分割

你可以随时使用以下三个快捷键之一关闭窗口:

  • <Space>wq:关闭窗口,如果是唯一打开的窗口,则退出 (quit) Neovim。
  • <Space>wcclose 关闭窗口,除非它是唯一打开的窗口,这种情况下它会显示错误并拒绝关闭。
  • <Space>wddelete 删除窗口。它实际上执行的操作与 <Space>wc 完全相同,但它有助于肌肉记忆,因为它与“删除”打开的缓冲区的 <Space>bd 对称。

在所有三种情况下,缓冲区在 bufferline 中保持打开状态。只有窗口分割被关闭。

如果你想关闭除活动窗口外的所有分割,请使用 <Space>wo,代表“only this window”(仅此窗口)或“close other”(关闭其他)(哪个更容易记住就用哪个)。

9.3.5. 调整窗口大小

以我非传统的观点来看,调整 Vim 分割大小最简单的方法是使用……鼠标。垂直分割之间有一个竖条,你可以点击并拖动。鼠标光标不会改变来给你任何可以点击和拖动的反馈,但它确实有效。

对于水平分割(当两个窗口一个在另一个上方时),没有明显的条可以点击。但你实际上可以拖动“上方”窗口的状态栏来向上或向下移动它。

如果你坚持使用键盘,快捷键在 <Space>w 菜单中:<Space>w+<Space>w- 分别增加或减少水平分割中活动窗口的高度,<Space>w><Space>w< 分别增加或减少垂直分割的宽度。它们一次只移动一行或一列,所以你几乎肯定想要给这些命令加上大于 10 的计数前缀,或者使用接下来讨论的“Hydra”模式。

译者注: Vim 内建调整大小的命令是 Ctrl-w +/-/>/<<Space>w 前缀是 LazyVim 添加的。

要将所有内容更改为“默认”大小,请使用 <Space>w=,这将使所有窗口“等高和等宽”。

9.3.6. Hydra 模式

有时,你会想连续运行几个窗口命令,例如在调整窗口大小或创建包含多个分割的布局时。在这些情况下,每次命令之间都输入 <Space>w 会相当繁琐,所以 which-key 插件为我们提供了 Hydra 模式。

译者注: Hydra 模式(名字来源于希腊神话中的九头蛇)允许你“锁定”一个快捷键菜单(如 <Space>w 菜单),这样你就可以连续按下菜单中的后续键来执行多个相关命令,而无需每次都重新输入前缀。

要进入 Hydra 窗口模式,请按 <Space>w<Space>。这所做的只是“钉住”窗口菜单使其保持打开状态,这样你就可以从中发出多个按键而不会自动离开菜单。例如,<Space>w<Space>vvvs 将创建四个分割(三个垂直和一个水平)。

要离开 Hydra 模式,只需按 <Escape> 并返回到你的正常编辑状态。

9.3.7. 缩放 (Zoom) 和禅定 (Zen) 模式

要进入或退出禅定模式,请使用 <Space>uz 快捷键,它映射到 snacks.nvim 中 UI 菜单下的一个动作。禅定模式会在你现有文件的前面打开一个居中的新窗口,并在后台将它们调暗,像这样:

zen mode dark

图 39. 禅定模式

除了光标附近区域外,语法高亮被禁用。表面上看,这有助于集中注意力。我通常没有太多注意力集中的问题,所以不怎么用它,但有注意力缺陷障碍的人可能会发现它比默认布局干扰更少。

缩放模式类似,它也临时改变窗口布局。它只是用一个包含当前文件的最大化窗口替换所有现有窗口。你可以用 <Space>uZ(这次 Z 是大写的)访问它。

译者注: <Space>uz (zen) 和 <Space>uZ (Zoom) 用于临时改变视图以减少干扰或最大化当前窗口。

如果你需要临时专注于或展开一个缓冲区,这可能很方便。我总是通过在新标签页中打开同一个缓冲区来获得这种行为,所以接下来让我们讨论标签页。

9.4. 标签页 (Tabs)

Vim 中的标签页相当不寻常。其他一些范式可能会将它们描述为“布局”(Layouts)。所有标签页都连接到当前打开的相同缓冲区列表,这与大多数标签页模型不同,但每个标签页可以被分割成不同的窗口布局。所以你可能有一个包含三个垂直分割的标签页,和另一个包含四个窗口呈网格状打开的标签页。在这七个分割中的每一个,你可以打开任何你喜欢的缓冲区,可能在多个位置打开。

LazyVim 有一个专门的标签页菜单,通过按 <Space><Tab> 访问:

tab menu dark

图 40. Space Tab 菜单

要创建一个新标签页,只需按 <Space><Tab><Tab>。如果你想将当前打开的窗口分割“移动”到一个新标签页,请使用 <Space>wT(这是一个大写的 T)。这将有效地关闭当前窗口分割,并在该标签页中创建一个包含相同缓冲区的新标签页。

在你创建标签页后,你可能会发现很难再找到它!标签页被分组在 bufferline 的右端:

tabs in buffer line dark

图 41. Bufferline 中的标签页

这个截图中有两个标签页,在右侧编号为 1 和 2,旁边有一个 X。左侧的两个缓冲区不是标签页。我强调这个次数是不是太多了?

不幸的是,除了数字,标签页没有任何东西让它们看起来独特;无法预览每个标签页中活动的缓冲区或它们的布局。

要在标签页之间导航,你可以点击数字,或者你可以使用默认的 vim 快捷键 gtgT 转到下一个或上一个标签页。或者,LazyVim 提供的 <Space><Tab>[<Space><Tab>] 快捷键也可以切换标签页。要按编号转到特定标签页,请在调用 gt 时使用该编号作为计数。例如 3gt 将显示标签页 3,而不是向右跳转三个标签页。

译者注: Vim 内建 Tab Page 命令:

  • :tabnew: 打开新标签页。
  • gt: 转到下一个标签页。
  • gT: 转到上一个标签页。
  • <N>gt: 转到第 N 个标签页。
  • :tabclose: 关闭当前标签页。

有几种方法可以关闭标签页:

  • 只需关闭标签页中的最后一个窗口(即用 <Space>wq),标签页就会消失。
  • <Space><tab>d 快捷键将关闭标签页中的所有窗口以及标签页本身。缓冲区将保持打开状态。
  • 点击标签栏中标签页右侧的 X 图标。

9.5. 代码折叠 (Code Folding)

Vim 的代码折叠系统几乎过于强大,可能是因为它多年来经历了多次“最佳实践”的迭代。LazyVim 配置了当前的最佳实践,所以你通常只需要完整折叠命令列表中的一小部分。

如果你不熟悉这个概念,代码折叠允许你通过将整段代码折叠成单行来隐藏它们。视觉上,这与水平分割窗口然后在分割上方和下方阅读同一文件的两个部分效果类似,但是当你使用折叠时,只有一个缓冲区视图是可见的,并且它作为一个整体滚动。

考虑这段代码:

no folds dark

图 42. 没有折叠的代码

在编辑时,假设我对截图顶部的 clearExistingTimeout 函数和底部的 addTodo 函数感兴趣,但目前对两个 save 回调的内容不感兴趣。我可以折叠那些部分,我的屏幕看起来像这样:

folds dark

图 43. 折叠后的代码

大多数折叠操作都可以从普通模式下输入 z 访问的 z 模式菜单中获得(我们在第 3 章处理滚动时讨论了部分 z 模式操作)。要将一段代码折叠起来,使用任何你喜欢的导航操作到达该部分,然后输入 zc 代表“collapse fold”(折叠)。

要再次打开它,使用 zo 代表“open fold”(打开)。

或者,如果你只想记住一个快捷键,za 将切换折叠状态,如果你不在折叠行上则折叠,如果在则展开。

如果你折叠了一些代码并想快速回到没有任何代码被折叠的状态,使用 zR 打开所有折叠。我不知道 R 应该对应什么助记符,但一位早期的读者很有帮助地指出 zr 是“减少折叠”(reduce folding),所以 zR 是“减少折叠但范围更大”。

译者注:

  • zc: 关闭光标下的折叠。
  • zC: 关闭光标下的所有嵌套折叠。
  • zo: 打开光标下的折叠。
  • zO: 打开光标下的所有嵌套折叠。
  • za: 切换光标下折叠的打开/关闭状态。
  • zA: 递归切换光标下折叠的打开/关闭状态。
  • zr: 减少所有折叠的层级(打开最外层)。
  • zR: 打开所有折叠。
  • zm: 增加所有折叠的层级(关闭更多层)。
  • zM: 关闭所有折叠。

你甚至可以通过折叠已经折叠的代码来嵌套折叠。如果你想递归地打开折叠,请使用 zO,它将打开一个折叠以及该折叠下嵌套的任何折叠。

LazyVim 的配置方式让你对折叠的内容没有太多控制权,但它通常会根据你的光标在文档中的位置做出接近你期望的事情。“你期望的”取决于 LSP 和你正在使用的语言的 TreeSitter 语法,但我发现最好让它自己运行,不要与它争论。

如果你发现你想要对代码折叠有更多的控制权,我建议完整阅读 :help folding,然后你会发现你根本不想要对折叠有更多控制权,只想让 LazyVim 为你处理它!

9.6. 会话 (Sessions)

经过一天辛苦的编码,你可能打开了几个缓冲区,并且你的分割和标签页配置了所有文件都在恰当的位置。如果能在晚上收起代码,然后回来时所有那些缓冲区、标签页和分割都还像原来一样,那岂不是很美妙?

LazyVim 默认启用了内置的会话管理。只需用 <Space>qq 关闭 LazyVim 就行了。明天早上,用你的终端 cd 到你工作的文件夹。用 nvim 命令打开会话到启动面板,然后按 s 即可继续工作。

译者注: 会话管理由 persistence.nvim 或类似插件提供,它可以保存和恢复 Neovim 的状态,包括打开的缓冲区、窗口布局、当前目录、历史记录等。

如果你忘记了立即打开它,并且启动面板早已消失,你可以使用 <Space>qs 将 Neovim 恢复到你上次关闭它时的状态(不过你在此期间修改并保存的任何文件仍将是它们的新内容)。

会话是按文件夹划分范围的。所以每次你更改工作目录时,都会存储一个不同的会话,并保持更新直到你退出。要打开一个会话,要么在打开 Neovim 之前在终端中 cd 到一个目录,要么在打开它之后使用 :cd 命令。

或者,在你打开 Neovim 后使用 <space>qS 命令。这将打开一个包含你最近工作过的所有文件夹的选择器。选择其中一个文件夹,LazyVim 将自动 :cd 到该文件夹并打开会话。

如果你使用 Neovim 的图形前端,如 Neovide 或 VimR,<Space>qS 命令对于选择最近的项目可能非常有用。事实上,它可能非常有用,以至于你想要像第 5 章讨论的那样,为它在你的启动面板上添加一个命令。

关于会话的最后一点说明:如果你临时打开了 Neovim,并希望在关闭它时不清除上次关闭时保存的会话,请在你打开 Neovim 之后、关闭它之前的任何时候按 <Space>qd。在某些情况下(特别是 git 提交信息),这会自动发生,所以你不必担心在关闭编辑器后进行提交然后丢失会话。

9.7. 总结

在本章中,我们学习了 Vim 的缓冲区、窗口和标签页,以及它们不仅彼此不同,而且与许多其他窗口管理范式也不同的地方。Vim 拥有与其他编辑器相同的概念,但它们有时以不同的方式混合或命名。

我们还介绍了代码折叠,以便更容易地处理大文件,以及会话管理,以便返回到你的窗口配置并在以后回来。当与 LazyVim 闪电般的启动时间相结合时,这尤其有用。当你没有在编辑代码时,你的代码编辑器就不会一直开着消耗内存。

在下一章中,我们将深入了解 LazyVim 提供的一些极好的编程语言支持。这可以说是使 VS Code 变得惊人的那件事,但 Vim 社区已经从其竞争对手那里学习并最终超越了它们。