每一个程序员都有一款心心念叨的代码编辑器。而在众多妖颜魅惑的编辑器偶像团体前,vim 就像个不加粉饰的农村姑娘,咋一看是那样朴实无华,难有倾心。但只要走近一点,来个亲密接触,又会被她的似水柔情所俘获。
这是青笔亲身经历。公司从腾讯空降技术总监,时不时会分享一些他的开发经验。其中说到编辑器,他的脸上总会泛起淡淡桃花,“我就喜欢 vim!” 。尽管我们曾一致向他引荐当下红极一时集美貌与才华于一身的 vscode 。他却依然心有所属,不为所动。
多少次,我百思不得其解,这个“相貌平平”的 vim 到底有何魔力,能牢牢扣住总监的心弦。在我真正走进她的内心世界,多次昼夜相伴之后。我最终也被她的清新脱俗所拜倒。
接下来就让我们一起来了解这名神秘的“女子”。
以下是网上找到一张图。如对 vim 发展历史感兴趣可以参考维基百科的词条 Vim (text editor)
。
https://en.wikipedia.org/wiki/Vim_(text_editor)
早期的 UNIX 操作系统上标准的编辑器是 ed ,这是一个面向行的编辑器,只能显示当前编辑的行。1976年,Bill Joy 在 ed 上做了扩展,使之支持了全屏显示和编辑,命名为 ex 。ex 是 ed 的超集,通过在 ex 输入命令 vi 来启动扩展功能。后来这个命令由于经常使用而被独立出来,也就是现在 Linux (包括 macOS)系统预装的命令行编辑器 vi 。直到 1991 年,Bram Moolenaar 才开发了 vi 的改进版(improvement),命名为 VIM (寓意:Vi IMprovement),同年 11 月 4 日发布了 vim 的第一个版本。下图来自 vim.org 网站首页的一条新闻,也就是去年的11月4日迎来了 vim 的27岁生日。
vim 自带了一个面向初学者开发的教程。可通过独立的子程序 vimtutor
启动教程。
如果系统已经安装了 vim (macOS 预装了 vim,部分 linux 发行版可能需要手动安装),可以在直接在终端输入 vimtutor
命令打开教程。
vimtutor
教程实际也是使用 vim 打开系统上的一个文本文件。文件通常在 /usr/share/vim/vim80/tutor/
目录下。当然,这个目录下有很多个文件,分别是不同语言版本的教程。使用 vimtutor
的不同之处是能自动打开用户所在地的语言版本。
上面看到的简体中文版实际就是文件 /usr/share/vim/vim80/tutor/tutor.zh_cn.utf-8
。
因此,你可以将其复制一份放到当前目录下,再使用 vim 打开,用做练习,随意编辑文件内容。
cp /usr/share/vim/vim80/tutor/tutor.zh_cn.utf-8 ./
vim tutor.zh_cn.utf-8
但如果按照教程来练习,个人用起来感觉最舒服的方式是:同时打开两个终端,一个使用 vimtutor 打开本地教程用于阅览,另一个将教程的英文版复制到当前目录,然后使用 vim 在复制的英文版上进行编辑练习(因为我们实际应用场景大多是英文环境,包括编辑代码和配置文件)。
复制英文版教程并进行编辑练习:
cp /usr/share/vim/vim80/tutor/tutor ./
vim tutor
对于习惯了 GUI 编辑器的开发者,刚开始接触 vim ,很容易被 vim 的工作模式搞得一头雾水,而且很可能就此放弃使用 vim 。但如果熟悉了,就觉得一切也理所当然。按照维基百科的介绍,vim 一共有 12 种不同模式,但只有 6 种基本模式,剩下 6 种都是基本模式的变种。但实际我们最常接触的只有两类模式:命令模式和编辑模式。其中命令模式又细分为,Normal(默认)模式和 Cmdline (底部命令行) 模式。而 Normal 模式是启动 vim 进入的默认模式,并且在任何模式下,按 ESC
(键盘左上角)键都将回到这个模式,作用类似于手机的 Home 键。因此,当你开始迷糊自己当前处在什么模式下,最好的办法就是按 ESC
回到 Normal 模式。我们使用 vim 的绝大多数命令都在 Normal 模式运行,而 Cmdline(命令行)模式通常只在退出和保存时使用,因此,如不特别说明,当我们在说命令模式时,就是指代 Normal 模式。此外,本文将 Cmdline 统称为命令行模式。
下图是三种模式的切换方式:
如果要简单概括这三种模式的使用场景,那就是:在命令模式中执行光标导航,复制粘贴删除,撤销重做,以及查找替换等操作;在编辑模式下,编写代码或进行创作;在命令行模式模式下,输入 q
退出,输入w
保存,输入wq
保存并退出。当然这里还不是全部的操作,但是熟悉这些操作,基本就能将 vim 应用得得心应手了。
要使用 vim 熟练地进行代码和文本的编辑,首先需要能够精确快速的将光标定位到要编辑的地方。最基本的就是上下左右移动光标了。这些操作当然可以使用键盘的四个方向键来完成。但是更常用的是使用字母输入区域连续的四个按键 h, j, k, l 。事实上,当你按照标准的手势接触键盘,你右手的食指触碰的就是 j 键(按键有个凸起)。因此,在你保持正常输入的手势下,顺其自然地敲动食指,就能将光标往下移动一行,而紧挨着的中指接触的是 k 键,进行的是和 j 相反的操作,即将光标向上移动一行,你会发现这样用起来相当顺手,这就理解为什么要使用字母按键来进行光标移动操作了。当然这是在命令模式下,按下字母键不会作为输入而改变编辑内容。
h
: 向左移动一个字符l
: 向右移动一个字符j
: 向下移动一行k
: 向上移动一行虽然可以使用 j, k 左右两边的 h 和 l 来实现向左和向右移动光标。这在一次只移动一个字符时,也还是很方便。但是如果向左或向右移动一个字(word,英文中空格和标点符号隔开的单词,标点符号也算一个字)时,还使用 h 和 l 就显得有些笨拙了。向左(向后)和向右(向前)移动一个字的方法如下:
w
: 向右或向前移动一个字,光标定位在字的首字符b
: 向左或向后移动一个字,光标定位在字的首字符命令模式按下字母w
将光标向右移动一个字:
命令模式按下字母b
将光标向左移动一个字:
如果想近一步扩大光标单次移动的范围,就要用到按句子和段落来前后移动来。两对圆括弧(
和)
分别将光标向后和向前移动一个句子,对应两个花括弧{
和}
分别将光标向后和向前移动一个段落。
(
: 向后移动一个句子,光标定位在句子开始)
: 向前移动一个句子,光标定位在句子开始{
: 向后移动一个段落,光标定位在段落开始}
: 向前移动一个段落,光标定位在段落开始命令模式按下)
和(
向前和向后移动一个句子:
命令模式按下}
和{
向前和向后移动一个段落:
此外你还可以在屏幕所见范围内进行快速移动光标。分别使用大写的H
,M
,L
。但是这三种定位并不是很精确,通常用做快速大范围级定位,然后再使用前面的命令进行更加精确的定位。读者可以自行尝试。
H
: 将光标定位到屏幕顶部一行的最左端M
: 将光标定位到屏幕中间一行L
: 将光标定位到屏幕的底部一行还有一种我们非常熟悉的应用场景,就是在调试程序抛出异常时,通常会显示出异常产生的行号,这时就需要根据行号快速将光标定位到指定的位置。这个命令相比前面的命令要复杂一点,它需要先输入代表行号的一个或多个数字,然后按下大写字母G
来完成。例如将光标定位到文件的第 80 行。需要先在命令模式输入80,然后快速按下大写字母G
。
将删除和撤销两个操作放在一起讲,其中一个原因是考虑到可能因为还不知道如何撤销删除操作,而害怕尝试删除操作的心理(尽管我们已经事先做了备份,但这应该是一种普通的心理和人性害怕失去是同样的道理)。我们知道可以撤销,因此在删除这件事上就有了“安全感”。也许你不是这么认为,但是这样还是能帮助我们更好的练习和记忆(我们可以循环往复地练习删除撤销)。
和移动光标一样,删除也可以按不同粒度进行。如删除单个字符,字,行,句子,段落以及屏幕首尾。删除操作由用字母d
加上表示删除范围的标识符构成。同时删除具体范围还受光标当前所在的具体位置决定。例如删除字使用字母组合dw
,如果光标出现在单词hello
的第二个字符e
上,此时在命令模式连续按下d
和w
,将删除从e
开始之后的整个字,但是会e
前面的h
不会删除,删除后的结果就是还剩一个字母h
。其他粒度的删除,也遵循相似的规律。
删除单个字符
删除单个字符有两者方法。两者等效,都是删除光标所在的字符,但是使用x
更简单一些,因为只需要输入一个字母。
x
dl
删除字
dw
: 从光标所在位置开始,删除到字的末尾(包含光标所在位置的字符)db
: 与dw
相反方向删除,即删除光标所在位置前面的字符(不包含光标所在位置的字符)删除行
dd
: 删除光标所在的行3dd
: 删除从光标开始的3行,当然这是一个例子,可以更改前面的数字删除任意数量的行一次删除3行:
删除到行尾
如果你不想删除整行。而是从光标开始到行尾的字符,可以直接使用一个大写D
来实现。
使用大写字母D
删除从光标位置开始到行尾的字符:
从行首删除
与删除到行尾对应的是使用d0
从行首删除:
d^与d$
如果对正则表达式熟悉,应该很容易猜到两者的含义。也是删除行首和行尾,但是与前面的 d0
与 D
所不同的是不删除行首和行尾的空格。
d^
: 删除光标前面知道行首的字符,不包含行首的空格d$
: 删除从光标开始知道行尾的字符,不包含行尾的空格删除句子和段落
熟悉了前面的删除,以下删除句子和段落也可以以此类推。唯一区别仅仅在于删除粒度不同。
d(
: 从句首删除d)
: 删除到句尾d{
: 从段首删除d}
: 删除到段尾替换:删除并输入
说到删除不得不再提到替换。因为两者就像兄弟一般,实际替换像删除的哥哥,只比弟弟删除多做了一步。就是在删除后紧接着进入编辑模式,在已删除的地方进行编辑。这两个组合动作不就是替换嘛。需要注意的还有,替换会改变当前工作模式,也就是进入下文会讲到的编辑模式,因此在完成输入后,需要按ESC
重新回到命令模式。与删除d
对应的替换命令是c
。因此将删除里讲到的所有操作中的d
换成c
就是对应的替换操作,当然执行后的细节会有些差异。但是这种差异在你亲自试过后就很容易理解,因此不作过多论述。读者可以自行尝试。
撤销操作
撤销操作很简单,命令模式下,按下小写字母u
(undo)即可。可以连续按多次,以快速撤销多个历史操作。
复制 y
复制使用字母y
。它的用法和删除操作d
很像,不同的是y
是复制而不是删除。
下面以复制行为例。其他粒度的复制类比删除操作d
即可。
与dd
删除当前行对应的复制当前行操作是yy
,当然也可以复制多行,和删除多行格式相似。在yy
前加上要复制的行数。例如3yy
复制从光标所在行开始的三行。
yy
: 复制光标所在的行3yy
: 复制从光标开始的3行,这里只是例子,实际可以是任意行数粘贴 p
粘贴命令就简单多了。它没有没有那么多的粒度区分,而只需要将已经复制的内容插入到当前光标之后或者之前的位置。分别使用小写的p
和大写的P
。
p
: 将复制的内容插入到光标之后P
: 将复制的内容插入到光标之前在命令模式输入斜杠/
(向下查找)或问号?
(向上查找),光标定位到终端底部一行,输入要查找的字符串,再按回车(RETURN),首先定位到一个找到的字符串,接着按小写的n
查找下一个,按大写的N
查找前一个。
上面介绍的所有命令都是在命令模式下进行的。如果要开始编写代码或其他文字编辑工作,就需要从命令行模式进入编辑模式,否则输入的字母(除能进入编辑模式的字母外)都将视作命令,而不会编辑到到文件中。
进入编辑模式有以下6种方式,区别仅在于进入编辑模式后,光标所在的位置的不同。
i
: 在当前光标前插入I
: 在行首插入a
: 在当前光标后追加A
: 在行尾追加o
: 在当前光标所在行之后添加新行O
: 在当前光标所在行之前添加新行在命令模式下,按下冒号:
,在终端底部出现输入行,表示当前是命令行模式。在命令行模式可以使用 vim 自身支持的很多命令。如前文已经谈到的最常用用到的就是退出和保存。
:q
+ RETURN: 退出编辑:w
+ RETURN: 保存不退出:wq
+ RETURN: 保存并退出常用的还有对 vim 进行设置。以下命令都需要在输入完后按下回车键 RETURN 才能执行(和在终端执行命令方式差不多)。
:syntax on
: 开启语法高亮:set number
: 显示行号:set tabstop=4
: 设置 Tab 键宽度:set expandtab
: 使用空格替代 Tab:set softtabstop=4
: 设置软件 Tab (自动Tab)宽度为 4:set shiftwidth=4
: 设置自动缩进宽度为 4:set autoindent
: 开启自动缩进,通常用于编写程序当然以上对 vim 的设置仅仅影响当前打开的 vim 。要想每次打开都使用同样的设置,需要将设置命令统一保存到 vim 的配置文件 ~/.vimrc
中,方法下一节将讲到。
此外,可以使用help
来获取帮助。例如输入:help user-manual
+ RETURN 查看用户手册。
如果说以上都是 vim 内部执行命令,那么在命令行模式下,实际也可以调用外部 shell 的命令。方法是以感叹号!
开头,标识感叹号!
之后的命令是一条外部 shell 的命令。如查看当前目录下的文件,:!ls -l
。第一次按下 RETURN 会隐藏当前编辑区域,显示终端界面,第二次按下 RETURN 再次回到 vim 编辑界面。
前文已经讲到如果想要每次打开 vim 都使用同样和界面和设置,需要将设置命令统一保存在配置文件中。在 linux 系统(含macOS)推荐保存的路径是 ~/.vimrc
。如果从来没有设置过,这个文件可能还不存在。这就需要我们先创建一个。为了不覆盖已有的,我们可以使用如下命令:
test -f ~/.vimrc || vim ~/.vimrc
这条 shell 命令会先判断 ~/.vimrc
是否存在,不存在就新建一个空白的配置文件,并使用 vim 打开这个新建的配置文件。
可选的方式也可以复制一份系统的上的配置文件,然后在此基础上添加和覆盖默认配置。
test -f ~/.vimrc || cp /usr/share/vim/vimrc ~/.vimrc
以下是青笔自己的配置文件参考:
colorscheme default " 设置颜色主题
syntax on " 语法高亮
filetype on " 检测文件的类型
set number " 显示行号
set ruler " 在编辑过程中,在右下角显示光标位置的状态行
set laststatus=2 " 显示状态栏 (默认值为 1, 无法显示状态栏)
set statusline=\ %<%F[%1*%M%*%n%R%H]%=\ %y\ %0(%{&fileformat}\ %{&encoding}\ %c:%l/%L%) " 设置在状态行显示的信息
set tabstop=4 " Tab键的宽度
set expandtab " 使用空格替换Tab
set softtabstop=4
set shiftwidth=4 " 统一缩进为4
set autoindent " vim使用自动对齐,也就是把当前行的对齐格式应用到下一行(自动缩进)
set cindent " (cindent是特别针对 C语言语法自动缩进)
set smartindent " 依据上面的对齐格式,智能的选择对齐方式,对于类似C语言编写上有用
set scrolloff=3 " 光标移动到buffer的顶部和底部时保持3行距离
set incsearch " 输入搜索内容时就显示搜索结果
set hlsearch " 搜索时高亮显示被找到的文本
set foldmethod=indent " 设置缩进折叠
set foldlevel=99 " 设置折叠层数
nnoremap <space> @=((foldclosed(line('.')) < 0) ? 'zc' : 'zo')<CR>
" 用空格键来开关折叠
" 自动跳转到上次退出的位置
if has("autocmd")
au BufReadPost * if line("'\"") > 1 && line("'\"") <= line("$") | exe "normal! g'\"" | endif
endif
关于配置文件的更多信息,也可通过 vim 内置的帮助手册来查看。与配置相关的手册为 usr_05
。在命令行模式输入:help usr_05
(冒号用于进入命令行模式,输入后回车)即可打开该手册。
本文从 vim 的发展历史开始,以具备能够熟练驾驭 vim 编辑器来满足日常代码编写的基本操作为主线,讲解了在 vim 中进行模式切换,光标导航,删除,撤销,替换,复制,粘贴,插入文本,以及配置编辑器外观设置等必要技能。当然,vim 支持的操作和命令还有很多。本文只介绍到基础部分。但是却是我们进行代码编辑最常用到的功能。熟练掌握后,可以进一步查看手册学习更多的技巧。查看手册方式上文已经给出。即在命令行模式输入::help user-manual
再回车。
如果本文能让你重新认识了 vim 这位眉目清新又纯洁善良的“姑娘”,并且从此对她有了一份独有的爱意,算是本文创作的最大收获所在^^。