Vim 系列教程目录:
- Vim 1 基本使用
- Vim 2 高级用法
- Vim 3 vimrc
- Vim 4 常用插件
- Vim 5 其他编辑器的 Vim 插件
本节会讲一些 Vim 中的高级概念和进阶用法, 了解了这些之后, 可以解开很多疑惑, 使用起来也会更得心应手.
CWD
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
查看指定寄存器中的内容, 例如: 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 编号切换到指定 Buffer, 简写:b
-
:buffer
通过文件名切换到指定 Buffer, 简写:b
-
:bnext
,:bprevious
切换到下一个/上一个 Buffer, 简写:bn
,:bp
-
:bfirst
,:blast
切换到第一个/最后一个 Buffer, 简写:bf
,:bl
-
:bdelete
删除指定缓冲区, 简写:bd
关于 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:
A 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 中.
可以再看看这些文章:
- Using Vim's tabs like buffers
- 如何理解并正确使用 Vim 中的 Buffer、Window 和 Tab
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!
或者:r !
即可, 注意
指的是你要输入的系统命令 - 将 Vim 命令的执行结果输出到文件, 需要如下三步:
- 执行命令
:redir > a.txt
, 此命令意为将命令输出到 a.txt 中, 默认会在 pwd 目录下新建文件, 你也可以指定文件路径,
如果文件已存在, 可使用redir!
进行覆盖, 或使用redir >> a.txt
进行追加. - 执行 Vim 命令
- 再次执行命令:
redir END
, 此命令意为重置命令输出位置.
- 执行命令
小结
本节讲了一些 Vim 中的进阶用法, 可以让你处理文本时更加方面快捷. 另外, 对于 Vim 中的寄存器, Buffer, Window, Tab等概念也有详细的介绍, 了解这些会让你理解 Vim 的一些行为. 还是那句话, 多练习, 在用的过程中学习, 感悟.