阅读 190

Vim 2 高级用法

Vim 系列教程目录:

本节会讲一些 Vim 中的高级概念和进阶用法, 了解了这些之后, 可以解开很多疑惑, 使用起来也会更得心应手.

CWD/PWD

CWD(Current Working Directory), 当前工作目录, 这是 Vim 中一个挺重要, 但是却又经常被忽略的概念. 简单来说, CWD 是 Vim 和操作系统的文件系统进行交互时的上下文环境.

要查看 Vim 的 CWD, 你可以使用 :pwd 命令来查看. 正常来说, 当你点击桌面上的 gVim 图标打开 Vim 时, Vim 的 CWD 是用户目录, 当你在文件上右键->用 Vim 编辑打开 Vim 时, Vim 的 CWD 是文件所在路径.

CWD 有什么用呢? 现在请你打开 Vim, 然后在命令模式下执行 :e test_vim_cwd.txt, 你会看到打开了一个名为 test_vim_cwd.txt 的新文件, 你随便添加一些文本, 然后保存, 你会发现这次 Vim 不再提示你没有文件名, 你可以直接保存了. 那么问题来了, 这个文件被存到哪里了呢?

我想你应该已经猜到了, 没错, 在 Vim 里新建的文件, 如果不指定路径, 会被保存到 CWD 里. 所以说 CWD 是 Vim 和文件系统交互时的上下文环境.

除此之外, Vim 的很多插件工作时也依赖 CWD, 比如 NERDTree, CtrlP 等等.关于插件我们以后再讲, 现在你先记住, CWD 是会影响一些插件的表现的.

在现阶段, 你不需要关心 CWD, 因为我们现在还是单文件操作, CWD 是哪都无所谓. 但如果你确实想改一下 CWD, 可使用 :cd 命令修改 CWD:

cd d:/xxx/yyy
复制代码

这和命令行里切换目录的方式是一样.

OK, 关于 CWD, 先说到这, 后面还会再说. 另外, 现在你又学会一个新命令: :e, 这个命令可以新建一个新文件. 其实, :e 这个命令后面可以路径/文件名, 如果给定的路径文件存在, 则是打开, 如果不存在, 则是新建.

文本对象

好, 我们再回到 Vim 的操作中, 请打开一个英文文本文件或输入一些英文, 以便接下来的学习.

之前的复制和删除都是以字符, 行为单位, 而 w, e, b的作用是从光标处到下个单词开头或本单词结尾, 所以要想删除整个单词, 你得这么做: bdw, 这表示先将光标移动到单词开头, 然后 dw. 这很麻烦, 有时候不小心看错了, 光标就移动错了. 要解决这个问题, 可以使用 文本对象.

现在请移动到一个单词的任意一个字母上, 然后执行指令 daw, 你会发现整个单词被删除了, 神奇吧. 这个指令中的 aw 在 Vim 中代表一个文本对象: a word, 即一个单词, 执行 daw 就表示删掉一个单词, 而且, 无论你在这个单词的哪个字母上, 都可以执行此命令删掉整个单词.

除了 aw, Vim 还支持下列文本对象:

  • aw: a word, 表示一个单词, 及其后面的空白, daw 表示删除光标所在单词及空白
  • iw: inner word, 也是表示一个单词, 但是不包括单词后面的空白
  • as: a sentence, 表示一个句子, 及其后面的空白, das表示删除光标所在句子及空白
  • is: inner sentence, 也是表示一个句子, 但是不包括句子后面的空白
  • ap/ip: a paragraph, 一个段落, 细节同上
  • a[, a] / i[, i]: 一个 [] 块, a[, a] 包括两边的 "[]", i[, i] 不包括两边的 []
  • a(, a) / i(, i) / ab ib: 一个小 block, 细节同上
  • a{, a} / i{, i} / aB iB: 一个大 block, 细节同上
  • a<, a> / i<, i>: 一个尖括号块, 细节同上
  • a", a', a` / i", i', i`: 一对引号, 细节同上
  • at/it: 一个 tag, 匹配 HTML 或者 XML 中的 tag 及其内容, 会忽略单标签

上述文本对象大都可以使用计数器, 例如:

  • d3aw: 表示删除3个单词, as, ap 同理
  • y2ab / y2a(: 表示向外复制两层小括号的内容, 其他类似括号的同理
  • ", ', `, 这几个不可使用计数器

这样一来, 一次性可操作的文本就大大增加了, 而且不用关心光标的位置, 非常便捷.

寄存器

我们之前说过, 剪切(删除)的文本会进入到 Vim 中的寄存器里, 那什么是寄存器呢?

所谓寄存器, 就是存放文本和指令/命令的地方, 例如使用 y, d, c 等命令复制或剪切的文本都会被自动存放在 Vim 的寄存器中, 用户可以将文本和指令放在寄存器中, 也可以从寄存器中读出来.

Vim 中共有9类寄存器, 具体如下:

类型 标识 读写者 是否只读 包含的字符来源
Unnamed " Vim 最近一次的复制或删除操作 (d, c, s, x, y)
Numbered 0 到 9 Vim 寄存器 0: 最近一次复制. 寄存器 1: 最近一次删除. 寄存器 2: 倒数第二次删除, 以此类推. 每来一次新的删除和修改, Vim 就把前一次的寄存器 1 的内容复制到寄存器 2, 2 到 3, 依此类推. 而寄存器 9 的内容就丢失了
Small delete 中横线 - Vim 最近一次行内删除
Named a 到 z 或 A 到 Z 用户 由用户指定时使用, 用户可将文本存储到这些寄存器中. 如果存储至寄存器 a, 那么 a 中的文本就会被覆盖. 如果你存储至 A, 那么会将文本添加给寄存器 a,不会覆盖之前已有的文本
Read-only : 与 . 与 % 和 # Vim : 为最近一次使用的命令, . 为最近一次添加的文本, % 为当前的文件名, # 为轮换文件名
Expression = 用户 Vim 存储表达式的地方, 用户只可读
Selection/Drop + 与 * 和 ~ Vim +*为 GUI 选择寄存器, 你可以理解为它们就是系统的剪切板, -为鼠标拖放的寄存器
Black hole 下划线 _ Vim 一般称为黑洞寄存器, 当把文本写到这个寄存器中时, 什么都不会发生, 且不可读, 这个寄存器可用来删除文本而不影响其他寄存器
Last search pattern / Vim 最近一次通过 /?:global 等命令调用的匹配条件

要查看这些寄存器中到底有什么, 可以使用如下命令:

  • :reg 查看所有寄存器中的内容
  • :reg <register name> 查看指定寄存器中的内容, 例如: reg 0

需要注意的是, 有些寄存器名字为特殊字符, 需要使用 \ 转义.

现在你可以先复制一段文本, 然后执行命令 :reg ", 查看一下 Unnamed 寄存器中的值.

寄存器的读写

要访问寄存器, 需要使用 " 作为前缀, 例如: "0, "a.

接下来我们做一个测试, 例如将 'hello' 这个单词存储到寄存器 a, 先将光标移动到 'hello' 这个单词上, 然后执行:

"ayiw
复制代码

上述指令表示: 使用寄存器 a, 然后复制一个单词. 此时使用命令 :reg a 查看寄存器 a 中的内容, 就能看到 'hello' 了. 需要注意的是, 当使用复制剪切等命令向指定寄存器中写入内容时, 内容同时也会被写入到 Unnamed 寄存器.

每次向寄存器中写入内容, 会将寄存器中已有的内容覆盖, 如果想要往寄存器中追加内容, 则需使用大写字母防卫寄存器, 如: "Ayiw, 这表示将当前单词追加到 寄存器 a 中.

要读取寄存器 a 中的内容, 则可使用如下命令:

"ap
复制代码

这表示先启用 寄存器 a, 然后进行粘贴操作, 你就可以看到, 寄存器 a 中的内容被粘贴出来了.

另外, 在插入模式下也可以访问寄存器, 在 插入模式中按 Ctrl-r, 然后再输入寄存器标识(不用输入", 直接输入标识), 就可以将对应寄存器中的内容输出.

访问系统剪切板

上面说了, y, d, c, x, s 等命令都是将内容存进了 Unnamed 寄存器中("), Unnamed 寄存器是 Vim 自己的寄存器, 操作系统是访问不到的, 所以你到别的软件了使用 Ctrl-v 是是无法粘贴出来的.

但是 Vim 中有另外两个寄存器: +*, 这两个寄存器可以理解为操作系统的剪切板, 所以我们可以通过这两个寄存器对系统的剪切板进行读写, 方式如下:

  • 通过 "+y / "+d / "+c 将内容复制/剪切到系统剪切板
  • 通过 "+p 将系统剪切板内容粘贴出来

PS. 使用 "+ 和 "* 效果一样

上节我们讲过 . 这个指令, 可以重复上次的操作, 我们也说了, . 本质上是一个"宏", 那么什么"宏"呢?

所谓"宏", 其实就是可以反复播放的一系列操作的集合. 前面说了, 寄存器不只是可以存储数据, 还可以存储指令/命令, Vim 中的宏就是将指令存储到寄存器中, 然后再读取出来. 宏的使用步骤如下:

  • 在普通模式下, 按 q 键开始录制宏, 后面一般跟上 Named 寄存器的名字, 如 qm, 表示将宏录制到 m 寄存器中.
  • 进行一系列操作, 都会被记录下来, 各种模式中的操作都会被记录到宏中
  • 回到命令模式, 再次按q, 退出宏录制
  • @m 播放m寄存器中的宏, 前面可以加数字表示播放次数
  • @@ 表示播放之前播放过那个个宏

录制好宏之后, 可以通过 reg 查看宏中的内容. 另外, 同样可以通过使用大写字母访问寄存器, 追加宏命令.

缓冲区/窗口/标签页

缓冲区(Buffer)

在 Vim 中打开的文件都会被存放在 Vim 的缓冲区中. 缓冲区在内存里, 当修改了文件还未保存时, 改动就在缓冲区中, 当保存时, Vim 会将缓冲区中的内容写到文件. 要查看缓冲区中有哪些文件, 可使用 :buffers 命令, :ls, :files 命令可以起到同样的效果.

运行这个命令后, 你会看到类似的输出:

  1      "Android\AFeed\02_项目基本配置.md" 第 39 行
  3  a   "[未命名]"                     第 0 行
  5      "Develop\Editor\Vim_1_基本使用.md" 第 177 行
  6 %a   "Develop\Editor\Vim_2_高级用法.md" 第 203 行
  7  a   "\Program Files (x86)\Vim\_vimrc" 第 0 行
  8      "Develop\Editor\Vim_3_vimrc.md" 第 86 行
  9      "Develop\Editor\Vim_5_常用插件(通用).md" 第 117 行
复制代码

缓冲区列表第一列是其序号, 第二列是标记, 标记的含义如下:

  • a 当前 active 并且 visible 的 Buffer
  • % 表示在当前窗口显示的 Buffer
  • = 只读 Buffer
  • h 隐藏的 Buffer

如何操作这些 Buffer 呢? 可使用如下命令:

  • :buffer <buffer no> 通过 Buffer 编号切换到指定 Buffer, 简写 :b <buffer no>
  • :buffer <file name> 通过文件名切换到指定 Buffer, 简写 :b <file name>
  • :bnext, :bprevious 切换到下一个/上一个 Buffer, 简写 :bn, :bp
  • :bfirst, :blast 切换到第一个/最后一个 Buffer, 简写 :bf, :bl
  • :bdelete <buffer no> 删除指定缓冲区, 简写 :bd <buffer no>

关于 Buffer 要注意:

  • Buffer 一旦创建, 默认就一直存在, 除非你手动删掉. 这个特性会导致一个怪现象: 你在文件系统里把一个文件删除了, 但是 Buffer 没删除, 当你又读取并修改保存了这个 Buffer 后, 你会发现文件系统里的那个文件又回来了... 这是因为, Buffer 是文件在内存中的缓存, 你把文件从硬盘上删了, 但是内存中的缓存还在, 当你保存时, 又把文件写回去了. 所以删除文件时, 最好也把 Buffer 删了.
  • 同样的, 删除 Buffer 不会影响文件系统中的文件, 删除 Buffer 的操作只影响内存中的 Buffer.

窗口(Window)

Vim 中一个编辑区域中可以有多个窗口. 所谓窗口, 其实就是把编辑区分割成不同区域, 每个区域被称为一个窗口. 窗口是用来显示 Buffer 的, 当你打开 Vim 时, 其实同时也打开了一个窗口, 只是这个窗口占满了整个编辑区域.

使用如下命令, 可以把当前分割窗口:

  • :split: 在当前窗口上边打开新窗口, 新窗口中依然是当前文件, 简写为 :sp
  • :split filenpath: 在当前文件上边打开新窗口, 新窗口中为指定文件, 简写为 :sp filepath
  • :vsplit: 在当前文件左边打开新窗口, 新窗口中依然是当前文件, 简写为 :vsp, 同样可指定文件名
  • :new: 在当前文件上边打开新的空白窗口, 不可指定文件名
  • :vnew: 在当前文件左边打开新的空白窗口, 不可指定文件名

注意:上述命令都可以在前面加一个数字, 表示新窗口的大小(行数), 如 :3sp a.txt, 则打开 a.txt 的窗口只有三行. 另外, 可以反复使用上述命令, 把窗口分割成更小的窗口.

关于窗口的几个快捷键:

  • Ctrl-w w: 先按 Ctrl-w, 再按一次 w, 在多个窗口间切换
  • Ctrl-w h/j/k/l: 切换到指定方向的窗口
  • Ctrl-w t/b: 切换到最上面/最下面的窗口
  • Ctrl-w H/J/K/L: 将窗口移动到指定的方向
  • Ctrl-w +/-: 更改窗口大小, 当然, 使用鼠标拖拽也可以

关于窗口的几个命令:

  • :close 关闭当前窗口, 其实 q 命令和 ZZ 也是可以关闭当前窗口的, 只不过 :close 命令可以保证不会关闭最后一个窗口
  • :only 只保留当前窗口, 关闭其他窗口, 如果其他窗口中的文件没保存, 会有警告
  • :wall 保存所有窗口
  • :qall 退出所有窗口
  • :wqall 保存并退出所有窗口

标签页(Tab)

除了窗口功能, Vim 还支持多标签页功能. 标签页是窗口的合集, 即一个标签页中有多个窗口, 而:wall, :qall 等窗口功能其实只对当前标签页中的窗口有效. 使用下列命令, 可以开启新的标签页:

  • :tabedit: 打开新的空白标签页, 简写: tabe
  • :tabedit filepath: 在新的标签页中打开指定文件, 简写: tabe filepath
  • :tab split: 打开新的标签页, 其内容是当前标签页
  • :tab help: 在新的标签页中打开帮助文档

关于标签页的几个指令/命令:

  • gt / :tabn 切换到下一个标签页
  • gT / :tabp 切换到上一个标签页
  • 数字gt / :tabn 数字: 切换到指定位置的标签页, 从1开始
  • :tabc 关闭当前标签页( tabclose 的简写)
  • :tabo 只保留当前标签页, 关闭其他标签页(tabonly 的简写)

正确理解三者的关系

先引用文档中的原话(help window):

Summary: SummaryA buffer is the in-memory text of a file. A window is a viewport on a buffer. A tab page is a collection of windows.

我的理解:

  • Buffer 是文件在内存中的缓冲区, 一个 Buffer 对应一个文件, 无论是新打开且未编辑的空文件, 还是 vimrc 或是其他文件, 在内存中都有对应的缓冲区.
  • Window 是用来显示 Buffer 的, 一个 Window 对应一个 Buffer, 但是一个 Buffer 可以同时被多个 Window 显示. Buffer 只有显示在 Window 中的时候才是可见的, 不可见的 Buffer 可以使用 Buffer 相关命令查看.
  • Tab 是 Window 的集合, 一个 Tab 可以分割成多个 Window, 多个 Tab 间的 Window 和一个 Tab 中的 Window 没区别. 不同的 Tab 本质上操作的是同一组 Buffer, 都是通过 Window 来展示.

一些奇怪现象的解释:

  • 打开一个文件的时候, 自动分割出一个 Window, 这是因为之前 Window 中的 Buffer 还未保存, Vim 默认不会隐藏未保存的 Buffer, 会保持其在 Window 中的可见性, 所以分割新 Window 来显示新 Buffer.
  • 在一个 Tab 中打开一个 Buffer 时, 突然跳到了另一个 Tab, 这是因为不同的 Tab 其实操作的是同一组 Buffer, 如果一个 Buffer 已经在 Tab1 中打开了, 你在 Tab2 中再次打开这个 Buffer, 会跳到 Tab1 中.

可以再看看这些文章:

Session

很多软件都具有这样一种功能: 在你下一次启动该软件时,它会自动为你恢复到你上次退出的环境, 恢复窗口布局, 所打开的文件等等. Vim 也有类似的功能, 只是用起来比较麻烦...

当你要退出 Vim 时, 可以保存一个 Vim Session(会话), Vim 会话存放着所 有跟你的编辑相关的信息. 这包括诸如文件列表, 窗口布局, 全局变量, 选 项, 以及其它信息.

要保存会话信息, 可使用命令 :mksession sessionname, 简写为 :mks sessionname, sessionname 由你指定, 会话信息会保存到名为 sessionname 的文件中, 默认情况下, Session 文件是存在用户目录下的, 当然你也可以指定 Session 文件的路径, 例如: :mksession d:/vim/session1. 如果 Session 文件已存在, 你需要使用 :mksession! d:/vim/session1 来保存 Session.

下次你打开 Vim, 可以载入这个 Session 文件, 就能恢复之前的会话信息了, 要载入会话, 可使用命令 :source sessionname.

比起其他编辑器, Vim 的 Session 稍显难用一些.

折叠

折叠... 怎么解释呢, 就是把一段文本显示为一行, 就像一张纸, 要把它缩短些, 就折叠起来. 被折叠的文本其实还在, 只是显示方式变了. 折叠的好处 是, 通过把多行的文本折叠成带有折叠提示的一行, 会使你更好地了解对文 本的宏观结构.

要创建一个折叠, 可使用 zf 指令, 你可以把光标移动到某一个段落内, 然后使用指令 zfap, 你会发现, 这段文字被折叠了. 这个指令的含义是: zf 是折叠指令, ap 是文本对象, 表示一段, zfap 就表示折叠一段文字.

要展开折叠的文本, 可将光标移动到折叠行上, 使用 zo 指令, 折叠的文字就会展开. 要想再次折叠, 使用 zc 指令, 这次为啥不用 zf 了? 因为 zf 是创建折叠的.

常用折叠指令:

  • za 打开/关闭折叠, 相当于 zo/zc 交替使用
  • zM 折叠所有
  • zR 打开所有折叠
  • zD 删除折叠

总的来说, 折叠用的不多, 这里就不多介绍了.

其他技巧

  • 将命令行(系统命令)返回的结果输出在 Vim 中 在命令模式下输入:r! <cmd> 或者 :r !<cmd> 即可, 注意 <cmd> 指的是你要输入的系统命令
  • 将 Vim 命令的执行结果输出到文件, 需要如下三步:
    • 执行命令 :redir > a.txt, 此命令意为将命令输出到 a.txt 中, 默认会在 pwd 目录下新建文件, 你也可以指定文件路径, 如果文件已存在, 可使用 redir! 进行覆盖, 或使用 redir >> a.txt 进行追加.
    • 执行 Vim 命令
    • 再次执行命令: redir END, 此命令意为重置命令输出位置.

小结

本节讲了一些 Vim 中的进阶用法, 可以让你处理文本时更加方面快捷. 另外, 对于 Vim 中的寄存器, Buffer, Window, Tab等概念也有详细的介绍, 了解这些会让你理解 Vim 的一些行为. 还是那句话, 多练习, 在用的过程中学习, 感悟.

关注下面的标签,发现更多相似文章
评论