技术成长日记-Vim-3.Vim中重要的概念

3.1 配置文件.vimrc_vimrc

.vimrc文件是个人定制Vim的脚本文件,该文件中的每一行都会被当作Ex命令在Vim进行初始化的时候执行。一般在类Unix系统中把该文件存放在$HOME/.vimrc或$HOME/.vim/vimrc,windows下存放在$HOME/_vimrc, $HOME/vimfiles/vimrc 或 $VIM/_vimrc中,MacOS系统放在$HOME/.vimrc, $HOME/vimfiles/vimrc 或 $VIM/.vimrc (或 _vimrc)。

.vimrc文件并不是Vim安装的,如果要定制自己的Vim,首先要在上述的目录创建该文件。创建后在该文件中添加自己的设置,在buffer中显示行号,可以添加如下一行:

set number

一般每行一条命令,如果想在单行添加多条命令需要用’|’字符进行分隔。

3.2 $HOME/.vim目录及其子目录

除了可以在vimrc文件中定制Vim,用户也可以根据自己的需要安装第三方插件。比如通过第三方的mru.vim插件实现最近打开列表功能,这个插件要放在目录$HOME/.vim/plugin下,每次运行Vim时输入:MRU命令可以查看最近打开文件列表。再比如当进行文件类型检测的时候,要设置缩进格式、每行字符数、是否需要空格替换制表符、是否需要自动缩进等,可以自己实现一个特定文件类型的定制插件,放在$HOME/.vim/ftplugin目录下,这样Vim就可以随时根据用户需要对文件格式进行设置了。另外在Vim运行的生命周期中,可能打开了c文件,同时还打开了pyhon文件,这里一个文件类型是c,另一个是py,它们需要不同的格式设置,自己可以分别完成一个c.vim和一个py.vim插件放在$HOME/.vim/ftplugin目录下,用户不再需要关心格式相关的问题,你只要打开对应类型的文件,Vim都能按照你的意愿自动帮你完成设置。$HOME/.vim目录是用于存放第三方插件的根目录,可以在该目录下创建一些规定的子目录用于特定目的,下面对这些常用的子目录进行简单说明。

1. plugin和doc

用于存放第三方插件和它们的帮助文档,可以在网上搜索到很多第三方插件,它们很多是压缩包,解压后包含plugin和doc两个目录,需要把plugin目录下以.vim为后缀的Vim脚本放入$HOME/.vim/plugin目录,doc目录下的txt放在$HOME/.vim/doc目录下。Vim再次初始化时会默认加载$HOME/.vim/plugin目录下的所有插件。如果不想退出Vim,可以执行”:source $HOME/.vim/plugin/xxx.vim”命令加载新的插件。

2. ftplugin

该目录用于放置文件类型相关的插件。在设置”filetype plugin on”的情况下,Vim会在打开某种类型的文件时对文件类型进行检测,然后加载该目录下对应的插件,执行属于自己的设置。在本节开始已经提到,可以定制任何你需要的参数。这里需要注意的是Vim在加载该目录下的插件时对文件名称有要求,比如要定制自己的c语言文件的格式,需要命名为c.vim或c_xxx.vim(xxx代表任意可以作为文件名的字符串),任何其他名称不会被Vim识别。

3. ftdetect

如果你不想用Vim默认的文件类型检测功能或者Vim不包含你的文件类型的相关设置,那么Vim允许用户实现自己需要的文件类型检测插件,可以把它们放在$HOME/.vim/ftdetect目录下。例如实现对mine类型的检查,写一条自动命令(3.15节自动命令)进行动态文件类型检测:

au BufRead,BufNewFile *.mine set filetype=mine

然后保存该文件

:w $HOME/.vim/ftdetect/mine.vim

这样Vim就可以识别mine类型的文件了。

4. autoload

随着对Vim的深度定制,Vim初始化时所加载的插件越来越多,其中可能包含一些大型插件,导致Vim初始化非常耗时。autoload的目的就是尽量延迟插件的加载。

引用autoload目录下的插件时,需要提供文件路径、文件名和函数名,并用’#’连接它们。例如,autoload目录下有一个插件名为bar.vim,bar.vim中定义了函数test(),要通过如下方式调用test函数:

:call bar#test()

如果,bar.vim插件存放在autoload子目录foo下,也就是autoload/foo/bar.vim,为了调用test()函数,需要如下使用如下格式:

:call foo#bar#test()

引用autoload目录下插件中的全局变量使用同样的方式。

5. after

该目录主要用于存放只对Vim内置插件功能进行小小的增强或改动的插件。比如说在你想要设置c语言文件的textwidth宽度为80,新建一个c.vim脚本,输入”setlocal textwidth=80”,然后放在$HOME/.vim/after/ftplugin目录中。Vim再次启动时会根据文件类型加载该目录中对应的插件,类似于$HOME/.vim/ftplugin目录。

3.3 .swp文件

以.swp为后缀的文件用于存储对文件的修改,它的命名方式是在当前打开文件的名字前加一个’.’(成为隐藏文件),然后加一个后缀“.swp”。例如,Vim正在打开一个名为aaa.c的文件,那么对应的swp文件名为.aaa.c.swp,当关闭aaa.c文件后,swp文件会立即被删除。需要注意的是,在vim非正常退出后,swp文件可能仍然存在,再次打开swp文件对应的文件时vim会询问用户需要的操作,如图3.1:

 5个选项分别为只读打开、编辑、恢复、退出、终止等,用户根据需要做出选择。如果你认为不需要恢复则可以直接删掉这个swp文件,再打开文件。

3.4 viminfo

1. viminfo文件是Vim用来记录退出时的状态,当再次运行vim时可以快速恢复这些状态,该文件会存储以下信息:

2.  viminfo可以存储的信息

Vim提供’viminfo’选项用来定制在viminfo文件中要存储哪些信息以及存储数量。表3.1中列出了该选项中常用标志:


‘viminfo’在8.0.1453版本的默认值是“‘100,<50,s10,h”,根据上面的表格可以知道”’100”表示最多纪录100个文件的浏览标记,”<50”表示每个寄存器最多保存50行数据,”s10”表示最多保存每个寄存器中10kb的数据,”h”表示取消高亮搜索结果。

3. 如何在下次运行Vim时打开最后一次浏览的文件并定位到插入符最后的位置呢?

在viminfo纪录的文件的数据中,书签’0-‘9纪录了插入符所停留过的位置,其中’0保存当前的位置,’1保存上一个’0值,’2保存上一个’1值,以此类推。所以’0保存了退出Vim时的文件名和文件内位置,运行下面命令可以直接打开上次退出时的文件及对应的位置。

vim -c "normal '0"

3.5 状态栏

状态栏在Vim底部命令行区域之上,它可以显示和当前打开文件有关的信息,通过’statusline’选项可以定制用户需要显示的内容:

更多选项请查看:h ‘statusline’选项的帮助。

‘laststatus'选项用于控制状态栏的显示行为:

在选择每个选项时,要在前面加上’%’,请看下面的例子:

:set statusline=[%f]%r%w%m%=%l/%L,%c\ %p%%

:set laststatus=2

每个选项的含义请参考上表3.2。

为statusline定制颜色:

例子,(a)在状态栏中显示文件相对路径,颜色显示为白底绿色;(b)用highlight预定义的颜色组合LineNr修饰行号:

hi User1 ctermbg=White ctermfg=Green

set statusline+=%1*%f%*

set statusline+=%#LineNr#

set statusline+=%l

3.6 缓冲区(buffer),窗口(window)和标签页(tab)

1. buffer

buffer是Vim和实际文件交互的通道,它存在于内存中。Vim打开文件时在内存中建立对应的buffer,在Vim进行文件编辑时实际是对buffer的编辑,只有执行写入命令的时候才对文件进行实质性的操作。

只运行Vim时,Vim会创建一个空的buffer,它不和任何文件关联。

buffer列表的查看命令是”:ls”,”:buffers”或”:files”,可以用”:{buffer number}buffer”的命令进行遍历,也可以通过命令“:bnext/:bprevious”等命令进行遍历。更多帮助信息请查看帮助文档:h windows.txt第十一节Using hidden buffers。

2. window

上面的buffer创建好了需要通过Window来显示,window提供buffer的可见视图,一个buffer可以对应一个或多个window。

可以用“:split”或“:vsplit”命令分别创建一个水平分割或垂直分割的窗口。或者在Vim初始化时传入”-o”或”-O”参数后跟多个文件,Vim为每个文件创建一个window。例如,打开三个文件每个文件对应一个window,窗口之间水平分割:

vim -o file1 file2 file3

更多详细信息请查看:h windows.txt。

3. tab

tab是窗口的集合,所以它可以包含一个或多个窗口。创建tab的命令是”:tabnew”。也可以在Vim初始化时传入-p参数后跟多个文件,此时每个文件对应一个tab。例如打开三个文件,每个文件对应一个tab:

vim -p file1 file2 file3

可以用”:tabnext/:tabprevious”命令对tabs进行遍历。更多详细信息请查看:h tabpage.txt。

3.7 寄存器

Vim的寄存器用来保存编辑内容或者其他一些Vim状态的一组容器或者变量,它们大致分为两类,一类是自动更新的,例如执行”d” “c” “x” “s” “y”等操作符时,无名寄存器””自动保存删除的内容,”.寄存器自动保存添加的内容;另一类需要手动保存内容,例如”a-“z寄存器。下面是来自Vim帮助文档change.txt中对所有寄存器种类的描述:

1. 无名寄存器””

Vim用删除命令d,c,x,s和复制命令y来填充这个寄存器,

2. 10个数字寄存器”0到”9

数字寄存器保存yank和delete命令操作的内容。”0寄存器保存最后一次执行yank的相关的内容,只有这一个数字寄存器受yank命令影响,其他的都是delete命令使用。”1寄存器保存最后一次删除的内容, “2到“9寄存器会顺移“1中的内容,也就是如果多次执行删除命令,“1中的内容移动到”2,“2中的内容移动到”3,以此类推,“9中的内容最终会被丢弃。

3. 低级的删除寄存器”-

当只删除某一行的部分内容时,被删除内容保存在这个寄存器中。它与”0寄存器不是冲突的。

4. 26个命名寄存器”a到”z或者”A到”Z

这组寄存器供用户使用,选择”a-“z保存目的文本,”A-“Z用于追加内容到”a-z”寄存器的末尾。例如,复制某一行内容到”a寄存器中:

“ayy

通过:reg “a命令查看当前”a的内容,然后追加另外一行到”a寄存器中:

“Ayy

再通过:reg “a命令查看当前”a的内容,这时”a内容是被追加过的。粘贴”a中的内容时用寄存器后跟p命令:

“ap

5. 3个只读寄存器

“.寄存器保存最后插入的内容。

“%保存当前窗口所显示文件的路径。

“:保存最后一条被执行的命令。

6. 交换buffer寄存器”#

保存当前窗口上一次打开文件的路径。

7. 表达式寄存器”=

执行一个表达式,而不是保存文本。

8. 选择寄存器”*,”+

“*和”+用于和系统剪切板进行交互,复制内容时可以使用下面命令:

“+y或”*y

9. 黑洞寄存器”_

丢弃内容的时候使用,类似于Linux中的/dev/null文件。如果在执行命令时不想影响其他寄存器,可以显示指定该寄存器。例如删除一行:

”_dd

注意是个这个寄存器删除的内容无法找回。

10. 搜索样式寄存器”/

保存用/或?命令上一次搜索的内容。

3.8 "."命令

“.”命令用于重复最后一次改变的内容,可以通过数量控制单次执行该命令的次数,命令原型为”{count}.”。下面是向下逐行删除的例子:

dd

j 

.

3.9 宏

其实在Vim并没用宏这个概念,在帮助文档中用的标题是”Complex repeats”,即复杂的重复,但是本质上和宏没有区别,所有我们在这用“宏”这个名词代替。

宏可以记录一组命令,用来重复复杂的操作。宏可以记录插入符的移动和文本编辑等操作。

在录制宏的过程中,可以把记录的操作步骤存入{0-9a-zA-Z}(大写字母的寄存器用来添加内容)寄存器中,具体操作步骤如下:

  1. 在正常模式下,先按q键后跟寄存器名。比如要录制一个名为a的宏,就要按下qa,这时在Vim的命令行出现”recording @a”字样。

  2. 开始输入命令进行操作。

  3. 完成操作后,按q键结束宏的录制。

  4. 按@a键执行上面录制的宏,可以加入次数控制,例如执行3次宏a,则按3@a。

  5. @@命令执行上次执行的宏,也可以可以加入次数。

  6. :reg registers查看宏的命令序列。

3.10 操作符和动作命令

操作符是为了修改文本而存在的一种命令,它们大致有下面几种模式:

1. 通常的操作符有:

a. 删除操作符d(delete)

该操作符是删除所选的内容,被删除的内容保存在无名寄存器“”(:h “”)和数字寄存器”1中,可以通过paste命令粘贴该寄存器中的内容。

b. 复制操作符y(yank)

类似于windows中的复制操作,同样所复制的内容保存在无名寄存器“”和数字寄存器“0中。

c. 粘贴操作符p(paste)

粘贴无名寄存器(“”)中的内容。小写p粘贴到当前行的下面位置,大写P粘贴到当前行的上面位置。

d. 操作符c(change)

删除选择内容的同时进入插入模式。例如删除文本并进入插入模式的几种操作cw(W)或c$:

cw或cW删除一个单词并进入插入模式,其中w遇到非字母字符会停止,W遇到空白字符才停止。

c$删除插入符所在位置到行尾的字符并进入插入模式。

e. 操作符g

g操作符没有固定种类的操作,常用的操作有如下几种:

需要注意的是,这里所说的g操作符都是关于内容编辑的命令。

f. 操作符z

和g操作符一样,也是一个难以归类的操作符。它包含多种操作,例如重画窗口、文本折叠、拼写检查相关的操作等。例子,对一个c函数创建一个折叠区域,然后执行折叠命令,再打开该折叠,最后删除该折叠区域:

g. 其他操作符

2. 动作命令(motion)

动作命令用于辅助操作符执行操作,Vim定义了几种移动插入符的方式,完成移动后产生不同的文本范围(插入符移动前到移动后的位置)提供给操作符。Vim的动作命令大致有下面几种:

3.11 文本对象及动作命令

文本对象是Vim操作文本的实体,Vim提供多种这样的实体概念,这些实体包括一个单词、一个句子、一个段落等,或者是小括号()、中括号[]、大括号{}、单引号’’、双引号””、尖括号<>、反单引号``等闭合字符中的内容,甚至还包括HEML和XML中的标签。每个这种文本对象对应一个单字符命令,例如,代表单词的字符为w。

该功能需要Vim的+textobjects属性的支持。

文本对象的动作命令设计到两个前缀字符—i和a。其中i的inner的缩写,表示内部的选择;a是all的缩写,表示选择文本对象的所有,包括对文本对象边界字符的选择。这两个字符可以分别和文本对象所对应的字符进行组合成为动作{motion},再配合前面讲的操作符,组成一个操作命令。

文本对象的选择命令一般用在两种模式下,一种是在正常模式下执行{count}operator{count}{motion}(3.10节 操作符)的操作;另一种是可视模式下选择文本对象,然后对选中的内容进行操作,v{count}{motion}operator。下面在可视模式下的例子:

当插入符在一个单词中的任意一个字符时,按下viw,则这个单词被选中,用户可以执行d或者y操作符等。如果按下vaw,则单词及其后面的空格都被选中。对于有闭合字符的字符串,以小括号为例,vi(选中小括号中的内容,va(小括号也被选中。表3.4 列出了文本对象动作的绝大部分实例:

下表是操作符d加文本对象动作的操作命令:

3.12 substitute命令

Vim中的替换命令是非常常用和重要的一个功能,下面是它的格式:

:[range]s[ubstitute]/{pattern}/{string}/[flags] [count]

其中[range]定义替换范围,s是substitute命令的简写,{pattern}是匹配目标字符串的样式(一个正则表达式实例)用来匹配将被替换的内容,{string}是所替换的字符串,[flags]是替换的控制标志,[count]是替换目标字符串的次数,以行为单位。这里需要注意替换范围的控制:

3.13 批量操作

有时候需要在多个文件中执行同样的命令,比如需要在多个打开文件中设置某些选项,但不需要对所有的打开文件都进行设置。再比如在多个文件中对某个字符串进行替换。批量操作可以选择多个要进行操作的文件,然后统一执行某个命令。常用的批量操作命令有:bufdo/:windo/:tabdo/:argdo等,这几个命令都可以对多个buffer(或文件)同时操作,唯一的差别就是选择多个文件的方式的不同,下面是分别对这4个命令详细的介绍:

1. bufdo

在当前Vim会话中的每个buffer中执行同样的命令,可以通过:ls/:buffers/:files命令查看当前Vim会话有哪些buffer(有很多第三方插件也可以做的,并且操作buffer更简单,例如bufexplorer,3.18.2.c节有介绍),命令的格式为:

:[range]bufdo[!] {cmd}

用:ls命令列出buffer列表,可以查看每个buffer的编号,所以可以用这些buffer编号指定[range]参数,当然这个参数可以省略,代表对所有buffer生效。[!]声明在未保存修改的情况下继续执行bufdo命令。{cmd}定义要执行的命令,但不能是删除或添加buffer的命令。可以使用’|’字符顺序执行更多的命令。一般要加上’!’,或者用’|’符号连接:update命令,否则因为修改内容未被保存导致:bufdo命令被中断,后面的buffer不能被操作。

2. windo

windo对当前tab中所有的窗口进行批量操作,格式是:

:[range]windo {cmd}

[range]表示窗口范围,可以省略,{cmd}是要执行的命令,但不能是打开或关闭窗口或者给窗口重新排序。可以使用’|’字符顺序执行更多的命令。

3. tabdo

在每个tab中执行相同的命令,格式为:

:[range]tabd[o] {cmd}

[range]指定tabdo的操作范围。tabdo中的o可以省略。{cmd}是要执行命令,但不能是打开或关闭tab或者给tab重新排序。使用:tabs命令查看当前tab列表。可以使用’|’字符顺序执行更多的命令。需要注意的是在每个tab中只对当前的窗口生效。

4. argdo

argdo用来自由选择一些文件进行批量操作。在进行任何操作之前要通过:argadd或:args *.x命令添加文件路径到argment列表中。默认情况下argment列表是Vim初始化时所打开的文件。argdo的格式与bufdo类似,如下:

:[range]argdo[!] {cmd}

[range]指定操作范围,也可以省略。[!]声明在未保存修改的情况下不中断argdo命令。{cmd}代表要执行的命令。使用:args命令查看当前的argment列表。可以使用’|’字符顺序执行更多的命令。这里{cmd}不能是改变argment列表的命令。

5. 例子

使用bufdo命令在所有的buffer中把cat替换成dog:

:bufdo %s/cat/dog/g | update

:bufdo! %s/cat/dog/g

:wall

其中%s/cat/dog/g是替换命令,’%’声明在整个文件中进行替换,’|’声明subtitute命令执行完成后还有执行其他命令,这里是:update。这里:update命令是必须的,否则bufdo在第一个buffer中执行完成后,可能由于未保存修改会导致执行失败,其他的buffer得不到执行的机会。

第二种方法是先用bufdo命令进行修改但不保存,修改完成后用:wall命令对所有buffer执行保存操作。

3.14 自动命令(autocmd)

自动命令是在Vim会话中发生某个事件的时候自动执行某些命令序列或函数,就像c程序中的callback。例如读一个文件时可以捕获一个BufRead事件,把它定义到autocmd中可以执行自己的命令或函数。

自动命令看起来功能非常强大,但是它可能导致无法预料的副作用,所以使用时要小心谨慎。官方在帮助文档中给出了一些建议,具体请查询:h autocmd第一节Introduction。

自动命令的格如下:

:au[tocmd] [group] {event} {pat} [nested] {cmd}

其中

定义自动命令的一般步骤:

1. 指定一个组名

:augroup mine

2. 删除组中所有的自动命令

:autocmd!

3. 定义新的自动命令

au BufNewFile,BufRead *.html set tabstop=4 

4. 完成该组自动命令的定义

:autogroup END

例子,定义一个自动命令,在Vim退出时保存所有未保存的修改,但是略过只读文件,下面是实现该功能的代码清单:

function! SaveFile()

    if filewritable(bufname("%")) && getbufvar(“%","&modified") “如果文件是可写的并且’modified’选项被设置过,则执行write命令

        write

    endif

endfunction


function! SaveAllFiles()

    brewind!

    let s:count = 0

    while bufnr("%") <= bufnr(“$") “只对buffer number范围内buffer执行SaveFile()函数

        call SaveFile()

        bnext “跳转到下一个buffer

        if s:count > bufnr("$")

            break

        endif

        let s:count += 1

    endwhile

endfunction

augroup savemyfiles “定义自动命令组

    au! “清除该组中以前的autocmds

    au QuitPre *.c,*.h,*.txt,*.sh,*.py,*.vim call SaveAllFiles() “定义哪些类型的文件需要退出时自动保存

augroup END “声明该自动命令组的结束

在QuitPre事件到来时,调用SaveAllFiles函数。这里SaveAllFiles函数实现了遍历当前Vim会话中所有的buffer,为每个buffer调用SaveFile函数,如果buffer被修改过并且所对应的文件是可写的,那么就对这样的buffer执行:write命令。

查看当前自动命令的一些方法:

Vim还支持手动执行autocmd,使用:do[autocmd]命令,命令格式如下:

:do[autocmd] [<nomodeline>] [group] {event} [fname]

有时候用户想要禁用某个或某些事件的自动命令,通过设置’eventignore’选项禁用autocmd所对应的某些事件,该选项是一个列表,列表中每一项对应一个事件的名称,但是自动命令中的其他事件还是有效的。如果临时禁用所有的自动命令(类似于onshot的操作),使用:noautocmd命令,它执行时设置’eventignore’选项为all,禁止所有的事件。

3.15 筛选命令

在命令行模式,’!’前缀告知Vim执行外部命令,外部命令完成的操作是读入在Vim中选中的内容通过标准输入发送到外部程序,外部程序运行的结果通过标准输出返回到Vim,然后替换之前选择的内容。如果不选中任何内容,可以通过:read命令读入。

例子,通过筛选命令添加当前目录的所有文件名:

或者用:read命令读取目标目录列表:

:read !ls .

3.16 了解vim内置选项

Vim内置选项是Vim功能控制的开关,通过他们实现对Vim基本功能的定制,例如打开文件时要显示文件的行号,需要用到’number’选项。在8.0版本中支持391个选项,一般通过set命令进行设置。

1. 操作方法

a. 显示所有与默认值不同的选项

:set

b. 显示所有选项,但不包括和终端相关的选项

:set all

c. 设置和取消选项

设置选项:set {option}

重置选项:set no{option}

d. 反转选项

:set {option}!

或者:set inv{option}

e. 查看选项当前值

:set {option}?

f. 恢复选项默认值

:set {option}&

g. 添加选项中的子选项

:set {option}+=x

h. 删除选项中的子选项

:set {option}-=x

i. 选项只在当前buffer中生效

:setlocal {option}

j. 同时设置多个选项

:set ai nosi sw=3 ts=3

2. 重要的内置选项

a. ‘compatible’

设置compatible声明Vim对Vi兼容,使Vim的行为更像Vi。设置’nocompatible’时,可以使用Vim很多改进的和Vim独有的功能,Vim在发现vimrc或gvimrc文件时设置该值。Vi和Vim的不同可以通过:help vi-differences查看。

b. ‘runtimepath’

Vim初始化时扫描’runtimepath’所给出的路径,并加载这些路径中的Vim插件。在大多数系统中,Vim一般会扫描下列几个路径:

上列的环境变量可以在Vim命令行通过:echo命令查看,例如查看$VIM的值执行:echo $VIM命令。

c. ‘hidden’

该选项用来控制vim中buffer被换出时的行为,buffer的状态分为3种active,hidden,inactive,

当不设置’hidden’选项时,不被显示的buffers将被卸载,它们的状态为inactive;当设置’hidden’时,不被显示的buffers仍然存在于内存中,这时候即使buffer中的修改未被保存,用户仍然可以浏览其他的buffer。

一种特殊的情况是,用户并未设置’hidden’同时也没有设置’autowrite’选项,但是用户在浏览buffer时使用了’!’符号,之前的buffer被强制换出了,那么它的状态会被Vim置为hidden。

例如,在修改一个buffer后未保存,紧接着执行:bnext!命令,buffer自动进入hidden状态。

d. ’modeline’

vim允许在文件的最后部分设置选项,可以实现只针对某个文件相关选项的个性化定制。在打开这样的文件时,vim会读入这些设置,使Vim合适的显示和编辑该文件。例如可以在vim的脚本文件的最后一行添加modeline设置:

“ vim:tw=80:ts=4:et:ft=vim:

vim声明本行是modeline行,需要注意的是vim三个字符前面必须有一个空白字符。tw是’textwidth’选项的缩写,代表每行文本的宽度。ts是’tabstop’选项的缩写,表示一个制表符的占位宽度。et是’expandtab’选项的缩写,表示输入制表符时,用tabstop个空格代替。ft是’filetype’选项的缩写,这里设置文件类型为vim。在vimrc中设置’modeline’选项使能modeline,’modelines’选项设置了文件中有效的modeline行数。

另外,每种文件类型的modeline要用相应的注释方法进行注释,否则影响代码的编译或运行。

详情请参考:help modeline。

e. ‘encoding’

‘encoding’选项用于Vim内部处理文本时所用的编码格式,例如buffer、寄存器、表达式中的字符串、viminfo文件等。

f. ‘fileencoding’ & ‘fileencodings’

‘fileencoding’用于实际文件读写时使用的编码格式,在Vim写入文件时,如果为空则使用’encoding’的值。

在读入文件时,’fileencoding’会被设置为’fileencodings’其中一项,’fileencodings’选项表示了一个编码列表,在打开文件时提供适当的值。如果’fileencodings’列表中没有合适的选项,’fileencoding’会被设置为空,这种情况下使用’encoding’的值。另外,在’fileencodings’为空时,不会修改’fileencoding’的值。

g. ‘coptions’

coptions的值由一系列字符标志组成,特定字符代表Vim是否兼容Vi中某个功能,设置某标志时则是对Vi某个功能的兼容,反之则不兼容。当设置’compitable’选项时coptions的默认值包含所有的标志,当重置’compitable’选项时,默认值为”aABceFs”.

h. ‘number’

设置该选项时,Vim在窗口左侧显示buffer行号。

i. ‘backspace’

通过该选项控制<BS>、<Del>、CTRL-W和CTRL-U等按键在插入模式下的行为。它包含3个参数

j. ‘history’

用于设置命令行和搜索命令’/’的历史记录数,默认是50,最大可以设置到10000。

k. ‘fileformat’

设置文件的编码格式,详情请参考4.5.6节 文本编码格式及转换。

3.17 快捷键映射

按键映射在Vim中是非常常用的功能,用户可以定义自己的快捷键,并且可以定义只在某种模式下生效。映射快捷键的格式如下:

{cmd} {attr} {lhs} {rhs}

{cmd}表示使用哪个map命令,可以是map,nmap,vmap,imap,cmap等。{attr}表示一些特殊的的参数,对map命令的行为加以限制,这些参数包括:"<buffer>", "<nowait>", "<silent>", "<special>", "<script>", “<expr>”,“<unique>”,它们必须紧跟map命令之后,用空格隔开。可以添加一个或多个{attr},顺序没有限制,用逗号作为分隔符。


{lhs}表示一系列按键,它用于触发map映射,执行{rhs}中的命令。

{rhs}表示一系列命令,在用户按下{lhs}定义的按键后,执行这些命令。

1. 不同模式中的按键映射

a. map/nmap/imap/vmap/cmap

map命令可以实现在三种模式下的下的映射,分别是正常模式、可视模式、操作待决模式;nmap表示映射只在正常模式下有效;imap表示映射只在插入模式下生效;vmap表示只在可视模式下生效;cmap表示只在命令行模式下生效。

例如,在正常模式下执行一个xyz命令,可以这样实现:

:nmap xyz :echo “Hello World”<CR>

nmap对应前面的{cmd}部分;xyz对应{lhs}部分;:echo “hello world”<CR>对应{rhs}部分,其中<CR>表示回车确认echo命令的执行。

b. noremap/nnoremap/vnoremap/inoremap/cnoremp

noremap与map不同的是它禁止对{rhs}部分进行映射扩展,禁止进行递归映射。那么什么是递归映射?它跟Vim允许多重映射有关系,我们仍然用上面hello world的例子,定义以“xyz”分别为{lhs}和{rhs}的两个映射:

:nmap xyz :echo “hello world”<CR>

:nmap abc xyz

vim会在执行abc这个映射的时候判断xyz是否是另一个映射的{lhs},如果是那么Vim会递归展开xyz的映射,也就是:echo命令,所以如果执行abc的时候,实际会执行到:echo命令。那么这时候问题来了,你映射的{lhs}可能只是另外一个映射{rhs}的一部分,Vim递归展开时会导致最终执行的命令非你所愿,下面是另外一个例子:

:nmap <F2> diw

:nmap di dd

根据上面的两个映射,在按下<F2>键后Vim执行diw,这时di展开为dd,最终执行ddw的操作,dd是删除当前行的命令,但是我们映射<F2>的本意是删除一个单词。为了解决这个问题使用nnoremap命令对<F2>进行映射:

:nnoremap <F2> diw

nnoremap表示不再对{rhs}部分执行展开操作。

c. unmap/nunmap/iunmap/vunmap/cunmap

unmap用于解除map命令的映射关系,格式为:

{cmd} {lhs}

这些命令和上面的命令是对应的,不同的前缀代表不同的模式。仍然以上面的:nmap为例,来解除对:echo命令的映射:

nunmap xyz

如果一个映射使用了:map命令在多个模式上映射{lhs},但是用:nummap解除{lhs},则其他模式的映射仍然保留。注意{lhs}后边不能有空白字符,因为它们都会被记入{lhs},这样导致unmap不生效。unmap命令同时会影响noremap的命令实现的映射。

d. mapclear/nmapclear/imapclear/vmapclear/cmapclear

用于解除对应模式中的所有按键映射。前缀n/i/v/c相同于与第a节中的命令,不同模式下的映射对应不同的前缀,mapclear命令可以解除所有的映射。

2. 使用mapleader

mapleader是表示一个字符串,它也是Vim的一个内置全局变量,用于在用户不知道用哪些按键序列进行映射的时候,可以在一组已存在按键前加一个mapleader,尤其是在想用尽量少的按键来映射一个新的快捷键序列时非常有用,可以很容易的避免与已经存在的映射有冲突。例如,用两个字符映射上面的:echo命令:

nnoremap <Leader>e :echo “hello world!”<CR>

默认情况下mapleader是不被设置的,那么<Leader>会用斜杠”\”字符,所以上面的映射等同于:

nnoremap \e :echo “hello world!”<CR>

在正常模式下依次按下\e键进行触发。mapleader可以通过let命令修改,例如改成逗号”,”

:let mapleader = “,”

这样在使用<Leader>进行映射的时候,所有的按键序列都以“,”开头了,但是不会对之前已经定义的<Leader>映射产生影响。

3.18 Vim插件

1. 内置插件

Vim有很多内置插件,在vim中执行:script命令可以列出Vim当前所加载的插件。可以发现大部分插件来自/usr/share/vim/vim80/目录,这就是Vim内部插件安装的根目录。在我的/usr/share/vim/vim80/目录下执行“find . “*.vim” | wc -l”命令,有1346个之多。在3.2节,我们已经介绍了$HOME/.vim目录下有几个固定名称的文件夹,Vim内置插件的目录结构是类似的,这里只介绍这些目录下一些最常用的插件。

a. ftplugin.vim

该插件通过自动命令截获FileType事件,然后获取触发自动命令时文件的后缀,根据这个后缀名扫描’runtimepath’选项中所设置的路径,把当前文件类型的相关插件加载到vim的运行环境中。

它首先构建了一个自动命令:

au FileType * call s:LoadFTPlugin()

在’filetype’选项被设置时,触发FileType事件,这种情况下调用s:LoadFTPlugin函数。在该函数中通过<amatch>获取了自动命令所匹配的文件后缀。然后执行在for语句中有最关键的一句代码:

exe 'runtime! ftplugin/' . name . '.vim ftplugin/' . name . '_*.vim ftplugin/' . name . '/*.vim'

它通过一个runtime命令加载相关文件类型的插件。以c语言源码文件为例,runtime的命令则为“runtime! ftplugin/c.vim ftplugin/c_*.vim ftplugin/c/*.vim”,所以当检测到c文件类型时,自动命令完成对c文件类型相关插件的加载。

Vim会在’runtimepath’(3.16.2.b节)选项中所列出的路径依次搜索,在我当前Vim版本中’runtimepath’选项的值为“~/.vim,/var/lib/vim/addons,/usr/share/vim/vimfiles,/usr/share/vim/vim80,/usr/share/vim/vimfiles/after,/var/lib/vim/addons/after,~/.vim/after”,它包含Vim的安装路径,也包含用户的~/.vim和~/.vim/after等目录。所以如果你在自己的家目录下创建了上面所描述的目录及插件,Vim在运行时通过ftplugin.vim插件加载了它们。

b. filetype.vim

该插件主要功能是在打开文件的时候根据文件后缀设置’filetype’选项。仍然以c代码文件为例,可以在filetype.vim中看到如下自动命令:

au BufNewFile,BufRead *.c call dist#ft#FTlpc()

打开c文件时,触发BufNewFile或BufRead事件,调用dist目录下ft.vim中的dist#ft#FTlpc()函数,该函数会设置文件类型为c。’filetype’选项被设置时触发FileType事件,在上面所提到的ftplugin.vim插件中已经实现了FileType事件的自动命令,触发该事件时加载所有c文件相关的文件类型插件。

c. netrw.vim

netrw插件主要用于在本地或网络上浏览文件夹。netrw.vim存放在/usr/share/vim/vim80/autoload/目录中,所以该插件在必要时才被加载(3.2.4节有对autoload文件夹的介绍)。

通常用Vim打开一个文件夹时,syntax/netrw.vim插件会设置浏览文件夹时的语法高亮。

另外一个插件plugin/netrwPlugin.vim,它对netrw.vim所实现的功能进行封装,通过一些自动命令实现user->vim->netrw.vim三者之间的交互。

d. ftplugin/qf.vim和syntax/qf.vim

qf.vim是和quickfix窗口相关的辅助插件,它的实现非常简单,就是设置quickfix窗口的’statusline’。syntax/qf.vim用来设置quickfix窗口的语法高亮。

e. syntax/syntax.vim和syntax/synload.vim

在执行:syntax on或:syntax enable命令时,Vim加载syntax.vim。syntax.vim定义了设置’syntax’选项的自动命令,其中的自动命令监测FileType事件,同时加载synload.vim插件,synload.vim定义了Syntax事件的自动命令。当设置’filetype’选项时,syntax.vim中的自动命令被触发,它设置’syntax’选项为相应的文件类型,这又触发了Syntax事件,在synload.vim中Syntax事件的自动命令会加载与语法高亮有关的插件。

/usr/share/vim/vim80/syntax目录中包含了大多数文件类型的语法高亮插件。

f. syntax/nosyntax.vim

移除所有的为语法高亮设置的自动命令,同时清除所有buffer的语法高亮设置。运行:syntax off命令时加载该插件。

g. colors/目录

colors目录包含Vim自带的不同颜色组合的插件,其中default.vim是默认的颜色组合插件。:colorscheme {name}命令可以切换不同的颜色组合。

2. 常用的第三方插件

a. taglist.vim

用于显示函数、类、结构体、宏、变量、枚举类型等的定义,当在TagList窗口对某个定义选择时可以自动跳到相应定义的位置,所以它是用来浏览代码的利器。

b. mru.vim

用于快速查找最近打开过的文件,它会自动保存所打开过的文件路径到家目录下的.vim_mru_file文件中,在需要的时候执行:MRU命令打开MRU窗口,选择需要的文件。

c. bufexplorer.vim

用于浏览当前Vim中的buffer列表,可以在各buffer间快速切换。

d. NERD_tree.vim

用于显示目录树。

e. FuzzyFinder

它提供快速查找buffer/file/command/bookmark/tag等内容的方法。使用该插件之前需要安装l9库,它的下载地址是https://github.com/vim-scripts/L9.git。

f. winmanager

提供窗口管理功能,可以和bufexplorer.vim、taglist.vim等插件配合使用,你可以在Vim中同时显示多个不同插件的窗口,使Vim看起来更像一个IDE。winmanager还有浏览目录的功能,这和NERD_tree.vim插件以及内置插件netrw.vim有一些功能重叠,但不完全一样。

使用第三方插件时需要注意的是,有些插件需要创建自己的显示窗口,如果用:edit命令打开其他的文件,在执行命令之前需要手动切换回真正在显示文件的那个窗口,否则你打开的文件会显示在插件的窗口中,这会弄乱显示区。

3.19 vim内置函数

vim的内置函数可以使用户更灵活的控制Vim,通过Vim脚本语言的配合内置函数定制自己理想的功能。当然可以在Vim的命令行模式进行函数调用,但是通常情况下这样使用效率并不高。由于我们主要在本文中只对Vim的基本功能进行介绍,所以不对Vim的内置函数做过多介绍,这里只是供有兴趣的同学了解一下。执行:help functions命令查看Vim所有的内置函数。

在Vim的命令行执行call命令进行函数调用,例如获取当前文件的绝对路径,可以输入下面的命令:

:call expand(“%”)

这里“%”表示当前的文件路径。

3.20 标记(mark)

它相当于书签功能,用起来非常简单。标记分为两种,当前文件内标记,多文件标记。当前文件的标记使用{a-z}26个小写字母命名,大写字母{A-Z}和10个数字{0-9}用于在不同的文件中定义标签,这是Vim内定的,不能自己定义标签名称。当需要定义标记时只要在正常模式下按m,紧接着按下上面提到的标签名称完成定义(除数字标记)。当需要跳转到相应标记时,在正常模式下按下单引号’或反引号`加标记名称即可,比如要在当前行定义标记a:

ma

代码浏览到其他行时需要到标记a处:

‘a 或 `a

列出所有的定义的标记:

:marks

删除标记a:

:delm[arks] a

删除当前文件中的所有标记。不包括{A-Z0-9}:

:delm[arks]!

多文件标记{A-Z0-9}可以永久保存在viminfo中。

特殊标记{0-9},它们不能直接被设置。’0用于存储在Vim写viminfo文件(Vim退出或执行:wviminfo命令)时的插入符位置,旧的’0值会保存在’1中,旧’1的值保存在’2中,以此类推。




本文为我原创

本文禁止转载或摘编

-- --
  • 投诉或建议
评论