Vim from zero to hero - Vim 从入门到精通
Vim 是一个历史悠久的文本编辑器,可以追溯到 qed。Bram Moolenaar 于 1991 年发布初始版本。
该项目托管在 vim.org。
获取 Vim :用包管理器安装或者直接到 vim.org 下载。
讨论使用相关问题最好使用 vim_use 邮件列表或者使用 IRC(Freenode) 的 #vim
频道。
项目在 Github 上开发,项目讨论请订阅 vim_dev 邮件列表。
通过阅读 Why, oh WHY, do those #?@! nutheads use vi? 来对 Vim 进行大致的了解。
Vim 采用模式编辑的理念,即它提供了多种模式,按键在不同的模式下作用不同。你可以在 普通模式 下浏览文件,在 插入模式 下插入文本,在 可视模式 下选择行,在 命令模式 下执行命令等等。起初这听起来可能很复杂,但是这有一个很大的优点:不需要通过同时按住多个键来完成操作,大多数时候你只需要依次按下这些按键即可。越常用的操作,所需要的按键数量越少。
和模式编辑紧密相连的概念是“操作符”和“动作”。_操作符_开始一些行为,例如:修改,删除,或者选择文本。之后你要用一个_动作_来指定需要操作的文本区域。比如,要改变括号内的文本,需要执行 ci(
(读做 _change inner parentheses_);删除整个段落的内容,需要执行 dap
(读做:_delete
around paragraph_)。
如果你能看见 Vim 老司机操作,你会发现他们使用 Vim 脚本语言就如同钢琴师弹钢琴一样。复杂的操作只需要几个按键就能完成。他们甚至不用刻意去想,因为这已经成为肌肉记忆了。这减少认识负荷并帮助人们专注于实际任务。
Vim 自带一个交互式的教程,内含你需要了解的最基础的信息,你可以通过终端运行以下命令打开教程:
$ vimtutor
不要因为这个看上去很无聊而跳过,按照此教程多练习。你以前用的 IDE 或者其他编辑器很少是有“模式”概念的,因此一开始你会很难适应模式切换。但是你 Vim 使用的越多,肌肉记忆 将越容易形成。
Vim 基于一个 vi 克隆,叫做 Stevie,支持两种运行模式:"compatible" 和 "nocompatible"。在兼容模式下运行 Vim 意味着使用 vi 的默认设置,而不是 Vim 的默认设置。除非你新建一个用户的 vimrc
或者使用 vim -N
命令启动 Vim ,否则就是在兼容模式下运行 Vim !请大家不要在兼容模式下运行 Vim 。
下一步
最后一个建议:使用插件之前,请先掌握 Vim 的基本操作。很多插件都只是对 Vim 自带功能的封装。
用户的 vimrc 配置文件可以放在 ~/.vimrc
,或者为了更好的分离放在 ~/.vim/vimrc
,后者更便于通过版本控制软件备份和同步整个配置,比方说 Github 。
你可以在网上找到许多精简的 vimrc 配置文件,我的版本可能并不是最简单的版本,但是我的版本提供了一套我认为良好的,非常适合入门的设置。
最终你需要阅读完那些设置,然后自行决定需要使用哪些。:-)
精简的 vimrc 地址:minimal-vimrc
如果你有兴趣,这里是我(原作者)的 vimrc。
建议:大多数插件作者都维护不止一个插件并且将他们的 vimrc 放在 Github 上展示(通常放在叫做 "vim-config" 或者 "dotfiles" 的仓库中),所以当你发现你喜欢的插件时,去插件维护者的 Github 主页看看有没有这样的仓库。
使用 :version
命令将向你展示当前正在运行的 Vim 的所有相关信息,包括它是如何编译的。
第一行告诉你这个二进制文件的编译时间和版本号,比如: 7.4 。接下来的一行呈现 Included patches: 1-1051
,这是补丁版本包。因此你 Vim 确切的版本号是 7.4.1051 。
另一行显示着一些像 Tiny version without GUI
或者 Huge version with GUI
的信息。很显然这些信息告诉你当前的 Vim 是否支持 GUI ,例如:从终端中运行 gvim
或者从终端模拟器中的 Vim 内运行 :gui
命令。另一个重要的信息是 Tiny
和 Huge
。 Vim 的特性集区分被叫做 tiny
,small
,normal
,big
and huge
,所有的都实现不同的功能子集。
:version
主要的输出内容是特性列表。+clipboard
意味这剪贴板功能被编译支持了,-clipboard
意味着剪贴板特性没有被编译支持。
一些功能特性需要编译支持才能正常工作。例如:为了让 :prof
工作,你需要使用 huge
模式编译的 Vim ,因为那种模式启用了 +profile
特性。
如果你的输出情况并不是那样,并且你是从包管理器安装 Vim 的,确保你安装了 vim-x
,vim-x11
,vim-gtk
,vim-gnome
这些包或者相似的,因为这些包通常都是 huge
模式编译的。
你也可以运行下面这段代码来测试 Vim 版本以及功能支持:
" Do something if running at least Vim 7.4.42 with +profile enabled.
if (v:version > 704 || v:version == 704 && has('patch42')) && has('profile')
" do stuff
endif
相关帮助:
:h :version
:h feature-list
:h +feature-list
为了避免版权问题,我只贴出链接:
或者在 Vim 中快速打开备忘录:vim-cheat40。
Vim 是一个文本编辑器。每次文本都是作为缓冲区的一部分显示的。每一份文件都是在他们自己独有的缓冲区打开的,插件显示的内容也在它们自己的缓冲区中。
缓冲区有很多属性,比如这个缓冲区的内容是否可以修改,或者这个缓冲区是否和文件相关联,是否需要同步保存到磁盘上。
窗口 是缓冲区上一层的视窗。如果你想同时查看几个文件或者查看同一文件的不同位置,那样你会需要窗口。
请别把他们叫做_分屏_。你可以把一个窗口分割成两个,但是这并没有让这两个窗口完全_分离_。
窗口可以水平或者竖直分割并且现有窗口的高度和宽度都是可以被调节设置的,因此,如果你需要多种窗口布局,请考虑使用标签。
标签页 (标签)是窗口的集合。因此使用标签当你想使用多种窗口布局的时候。
简单的说,如果你启动 Vim 的时候没有附带任何参数,你会得到一个包含着一个呈现一个缓冲区的窗口的标签。
顺带提一下,缓冲区列表是全局可见的,你可以在任何标签中访问任何一个缓冲区。
用类似 vim file1
的命令启动 Vim 。这个文件的内容将会被加载到缓冲区中,你现在有一个已载入的缓冲区。如果你在 Vim 中保存这个文件,缓冲区内容将会被同步到磁盘上(写回文件中)。
由于这个缓冲区也在一个窗口上显示,所以他也是一个已激活的缓冲区。如果你现在通过 :e file2
命令加载另一个文件,file1
将会变成一个隐藏的缓冲区,并且 file2
变成已激活缓冲区。
使用 :ls
我们能够列出所有可以列出的缓冲区。插件缓冲区和帮助缓冲区通常被标记为不可以列出的缓冲区,因为那并不是你经常需要在编辑器中编辑的常规文件。通过 :ls!
命令可以显示被放入缓冲区列表的和未被放入列表的缓冲区。
未命名的缓冲区是一种没有关联特定文件的缓冲区,这种缓冲区经常被插件使用。比如 :enew
将会创建一个无名临时缓冲区。添加一些文本然后使用 :w /tmp/foo
将他写入到磁盘,这样这个缓冲区就会变成一个已命名的缓冲区。
全局缓冲区列表是 Vim 的特性。在这之前的 vi 中,仅仅只有参数列表,参数列表在 Vim 中依旧可以使用。
每一个通过 shell 命令传递给 Vim 的文件名都被记录在一个参数列表中。可以有多个参数列表:默认情况下所有参数都被放在全局参数列表下,但是你可以使用 :arglocal
命令去创建一个新的本地窗口的参数列表。
使用 :args
命令可以列出当前参数。使用 :next
,:previous
,:first
,:last
命令可以在切换在参数列表中的文件。通过使用 :argadd
,:argdelete
或者 :args
等命令加上一个文件列表可以改变参数列表。
偏爱缓冲区列表还是参数列表完全是个人选择,我的印象中大多数人都是使用缓冲区列表的。
然而参数列表在有些情况下被大量使用:批处理
使用 :argdo
! 一个简单的重构例子:
:args **/*.[ch]
:argdo %s/foo/bar/ge | update
这条命令将替换掉当前目录下以及当前目录的子目录中所有的 C 源文件和头文件中的“ foo ”,并用“ bar ”代替。
相关帮助::h argument-list
使用 :map
命令家族你可以定义属于你自己的快捷键。该家族的每一个命令都限定在特定的模式下。从技术上来说 Vim 自带高达 12 中模式,其中 6 种可以被映射。另外一些命令作用于多种模式:
| 递归 | 非递归 | 模式 |
|-----------|---------------|----------------------------------|
| :map
| :noremap
| normal, visual, operator-pending |
| :nmap
| :nnoremap
| normal |
| :xmap
| :xnoremap
| visual |
| :cmap
| :cnoremap
| command-line |
| :omap
| :onoremap
| operator-pending |
| :imap
| :inoremap
| insert |
例如:这个自定义的快捷键只在普通模式下工作。
:nmap <space> :echo "foo"<cr>
使用 :nunmap <space>
可以取消这个映射。
对于更少数,不常见的模式(或者他们的组合),查看 :h map-modes
。
到现在为止还好,对新手而言有一个问题会困扰他们::nmap
是递归执行的!结果是,右边执行可能的映射。
你自定义了一个简单的映射去输出“ Foo ”:
:nmap b :echo "Foo"<cr>
但是如果你想要映射 b
(回退一个单词)的默认功能到一个键上呢?
:nmap a b
如果你敲击<kbd>a</kbd>,我们期望着光标回退到上一个单词,但是实际情况是“ Foo ”被输出到命令行里!因为在右边,b
已经被映射到别的行为上了,换句话说就是 :echo "Foo"<cr>
。
解决此问题的正确方法是使用一种_非递归_的映射代替:
:nnoremap a b
经验法则:除非递归是必须的,否则总是使用非递归映射。
通过不给一个右值来检查你的映射。比如:nmap
显示所以普通模式下的映射,:nmap <leader>
显示所有以 <leader>
键开头的普通模式下的映射。
如果你想禁止用标准映射,把他们映射到特殊字符 <nop>
上,例如::noremap <left> <nop>
。
相关帮助:
:h key-notation
:h mapping
:h 05.3
映射占位符( Leader 键)本身就是一个按键映射,默认为 <kbd>\</kbd>。我们可以通过在 map
中调用 <leader>
来为把它添加到其他按键映射中。
nnoremap <leader>h :helpgrep<space>
这样,我们只需要先按 <kbd>\</kbd> 然后连续按 <kbd>\h</kbd> 就可以激活这个映射 :helpgrep<space>
。如果你想通过先按 <kbd>空格</kbd> 键来触发,只需要这样做:
let mapleader = ' '
nnoremap <leader>h :helpgrep<space>
另外,还有一个叫 <localleader>
的,可以把它理解为局部环境中的 <leader>
,默认值依然为 <kbd>\</kbd>。当我们需要只对某一个条件下(比如,特定文件类型的插件)的缓冲区设置特别的 <leader>
键,那么我们就可以通过修改当前环境下的 <localleader>
来实现。
注意:如果你打算设置 Leader 键,请确保在设置按键映射之前,先设置好 Leader 键。如果你先设置了含有 Leader 键的映射,然后又修改了 Leader 键,那么之前映射内的 Leader 键是不会因此而改变的。你可以通过执行 :nmap <leader>
来查看普通模式中已绑定给 Leader 键的所有映射。
请参阅 :h mapleader
与 :h maploacalleader
来获取更多帮助。
寄存器就是存储文本的地方。我们常用的「复制」操作就是把文本存储到寄存器,「 粘贴」 操作就是把文本从寄存器中读出来。顺便,在 Vim 中复制的快捷键是 <kbd>y</kbd>,粘贴的快捷键是 <kbd>p</kbd>。
Vim 为我们提供了如下的寄存器:
| 类型 | 标识 | 读写者 | 是否为只读 | 包含的字符来源 |
| ---- | ---- | ------ | ---------- | -------------- |
| Unnamed | "
| vim | 否 | 最近一次的复制或删除操作 (d
, c
, s
, x
, y
) |
| Numbered | 0
至9
| vim | 否 | 寄存器 0
: 最近一次复制。寄存器 1
: 最近一次删除。寄存器 2
: 倒数第二次删除,以此类推。对于寄存器 1
至 9
,他们其实是只读的最多包含 9 个元素的队列。这里的队列即为数据类型 queue |
| Small delete | -
| vim | 否 | 最近一次行内删除 |
| Named | a
至z
, A
至Z
| 用户 | 否 | 如果你通过复制操作存储文本至寄存器 a
,那么 a
中的文本就会被完全覆盖。如果你存储至 A
,那么会将文本添加给寄存器 a
,不会覆盖之前已有的文本 |
| Read-only | :
与.
和%
| vim | 是 | :
: 最近一次使用的命令,.
: 最近一次添加的文本,%
: 当前的文件名 |
| Alternate buffer | #
| vim | 否 | 大部分情况下,这个寄存器是当前窗口中,上一次访问的缓冲区。请参阅 :h alternate-file
来获取更多帮助 |
| Expression | =
| 用户 | 否 | 复制 VimL 代码时,这个寄存器用于存储代码片段的执行结果。比如,在插入模式下复制 <c-r>=5+5<cr>
,那么这个寄存器就会存入 10 |
| Selection | +
和*
| vim | 否 | *
和 +
是 剪贴板 寄存器 |
| Drop | ~
| vim | 是 | 最后一次拖拽添加至 Vim 的文本(需要 "+dnd" 支持,暂时只支持 GTK GUI 。请参阅 :help dnd
及 :help quote~
) |
| Black hole | _
| vim | 否 | 一般称为黑洞寄存器。对于当前操作,如果你不希望在其他寄存器中保留文本,那就在命令前加上 _
。比如,"_dd
命令不会将文本放到寄存器 "
、1
、+
或 *
中 |
| Last search pattern | /
| vim | 否 | 最近一次通过 /
、?
或 :global
等命令调用的匹配条件 |
只要不是只读的寄存器,用户都有权限修改它的内容,比如:
:let @/ = 'register'
这样,我们按 <kbd>n</kbd> 的时候就会跳转到单词"register" 出现的地方。
有些时候,你的操作可能已经修改了寄存器,而你没有察觉到。请参阅 :h registers
获取更多帮助。
上面提到过,复制的命令是 <kbd>y</kbd>,粘贴的命令是 <kbd>p</kbd> 或者 <kbd>P</kbd>。但请注意, Vim 会区分「字符选取」与「行选取」。请参阅 :h linewise
获取更多帮助。
行选取:
命令 yy
或 Y
都是复制当前行。这时移动光标至其他位置,按下 p
就可以在光标下方粘贴复制的行,按下 P
就可以在光标上方粘贴至复制的行。
字符选取:
命令 0yw
可以复制第一个单词。这时移动光标至其他位置,按下 p
就可以在当前行、光标后的位置粘贴单词,按下 P
就可以在当前行、光标前的位置粘贴单词。
将文本存到指定的寄存器中:
命令 "aY
可以将当前行复制,并存储到寄存器 a
中。这时移动光标至其他位置,通过命令 "AY
就可以把这一行的内容扩展到寄存器 a
中,而之前存储的内容也不会丢失。
为了便于理解和记忆,建议大家现在就试一试上面提到的这些操作。操作过程中,你可以随时通过 :reg
来查看寄存器的变化。
有趣的是:
在 Vim 中,y
是复制命令,源于单词 "yanking"。而在 Emacs 中,"yanking" 代表的是粘贴(或者说,重新插入刚才删掉的内容),而并不是复制。
范围 (Ranges) 其实很好理解,但很多 Vim 用户的理解不到位。
,
或 ;
分割的行号:write
和 :global
是默认作用于所有行的范围的使用是十分直观的。以下为一些例子(其中,:d
为 :delete
的缩写):
| 命令 | 操作的行 |
| ---- | -------- |
| :d
| 当前行 |
| :.d
| 当前行 |
| :1d
| 第一行 |
| :$d
| 最后一行 |
| :1,$d
| 所有行 |
| :%d
| 所有行(这是 1,$
的语法糖) |
| :.,5d
| 当前行至第 5 行 |
| :,5d
| 同样是当前行至第 5 行 |
| :,+3d
| 当前行及接下来的 3 行 |
| :1,+3d
| 第一行至当前行再加 3 行 |
| :,-3d
| 当前行及向上的 3 行( Vim 会弹出提示信息,因为这是一个保留的范围) |
| :3,'xdelete
| 第三行至标注 为 x 的那一行 |
| :/^foo/,$delete
| 当前行以下,以字符 "foo" 开头的那一行至结尾 |
| :/^foo/+1,$delete
| 当前行以下,以字符 "foo" 开头的那一行的下一行至结尾 |
需要注意的是,;
也可以用于表示范围。区别在于,a,b
的 b
是以当前行作为参考的。而 a;b
的 b
是以 a
行作为参考的。举个例子,现在你的光标在第 5 行。这时 :1,+1d
会删除第 1 行至第 6 行,而 :1;+1d
会删除第 1 行和第 2 行。
如果你想设置多个寻找条件,只需要在条件前加上 /
,比如:
:/foo//bar//quux/d
这就会删除当前行之后的某一行。定位方式是,先在当前行之后寻找第一个包含 "foo" 字符的那一行,然后在找到的这一行之后寻找第一个包含 "bar" 字符的那一行,然后再在找到的这一行之后寻找第一个包含 "quux" 的那一行。删除的就是最后找到的这一行。
有时, Vim 会在命令前自动添加范围。举个例子,如果你先通过 V
命令进入行选取模式,选中一些行后按下 :
进入命令模式,这时候你会发现 Vim 自动添加了 '<,'>
范围。这表示,接下来的命令会使用之前选取的行号作为范围。但如果后续命令不支持范围, Vim 就会报错。为了避免这样的情况发生,有些人会设置这样的按键映射::vnoremap foo :<c-u>command
,组合键 <kbd>Ctrl + u</kbd> 可以清除当前命令行中的内容。
另一个例子是在普通模式中按下 !!
,命令行中会出现 :.!
。如果这时你如果输入一个外部命令,那么当前行的内容就会被这个外部命令的输出替换。你也可以通过命令 :?^$?+1,/^$/-1!ls
把当前段落的内容替换成外部命令 ls
的输出,原理是向前和向后各搜索一个空白行,删除这两个空白行之间的内容,并将外部命令 ls
的输出放到这两个空白行之间。
请参阅以下两个命令来获取更多帮助:
:h cmdline-ranges
:h 10.3
可以协助我们核对翻译,或者从章节列表中认领章节进行翻译。