作为编辑器之神,vim一直是我编辑文本的不二选择,哪怕其坎坷的学习曲线让人头疼不已.末学总结一下经验教训,以作备忘.

入门

个人习惯编译选项

./configure --with-features=huge --enable-cscope --enable-fontset --enable-multibyte --enable-perlinterp --enable-rubyinterp --enable-pythoninterp

工作目录

:pw[d] 显示当前工作目录
:cd[!] {path} 工作目录切换到path

移动: h j k l 0 ^ $ e E b B w W f F t T ; , % gg G [ ]
编辑: d y p r c o

在vim中还存在如x,s等编辑命令,这些命令只是 编辑命令 + 移动命令 的简答组合,如x X D C s S Y

简单总结大小写操作命令的区别:

作用于行首: I
作用于行尾: D,C,A,R
作用于整行: Y,S,V
作用于逆方向: X,F,P,O,N
搜索时大小写相关: \C
改变大小写: gU

关于编辑命令,需要说明一点: x,d,c这些命令会把脏数据置入vim的粘贴板(所谓脏数据,也就是被命令删除或改变的那部分数据).

利用vim的这个特性,我们可以轻易的实现文本剪切,如交换两个字符,交换两行文本,甚至是交换两段文本:

16进制编辑,码农必备,lol

:%!xxd 16进制编辑
:%!xxd -r 文本编辑

保存会话

Session可以忠实地记录vim当前的视图,如windows和tabs,甚至是高亮.真是谁用谁知道.

:mks[!] [file] 把当前视图保存到file,如file未指定,则缺省为Session.vim
vim -S Session.vim 打开Session.vim,也就是Reload view,重新打开视图.

可视化编辑

v: charwise
V: linewise

Ctrl-V: blockwise,后文介绍.

gv 重选上次选中的区域,谁用谁知道

区域

% 作用于当前打开的整个文件
'<,'> 作用于当前选中的区域,其中,'<表示选中区域第一行,'>表示选中区域的最后一行.
{num1},{num2} 作用于从第num1行到第num2行

内置命令

. 当前行,比如说,想要作用于从当前行开始的总共8行文本,则可以8:,
$ 最后一行,

:[range] substitute/from/to/[flags] 替换文本

:[range] copy {num1} {num2} 复制文本

:[range] Tabluarize /{char1} 对齐文本

甚至可以是:
:[range] TOhtml 文本转为html

查找: / ? * #

:help pattern

/ 向前查找keyword
? 向后查找keyword

n 重做最后一次/或?
N 反方向重做最后一次/或?

\c 查找时忽略大小写
\C 查找时大小写相关

\< 表示一个词的开始
\> 表示一个词的结束

tabs

:help tabpage

:tabnew 新建tab
:tabclose 关闭当前tab
:tabedit {file} 新建tab,并在新创建的tab打开file (等价于: tabnew后:edit {file})
:tabmove {idx} 把当前tab移动到第idx tab之后

gt 切换到下一tab
gT 切换到上一tab
{idx}gt 切换到第idx tab

为了方便,我的vimrc配置如下:

536 map <A-1> 1gt
537 map <A-2> 2gt
538 map <A-3> 3gt
539 map <A-4> 4gt
540 map <A-5> 5gt
541 map <A-6> 6gt
542 map <A-7> 7gt
543 map <A-8> 8gt
544 map <A-9> 9gt

尽管很dirty,好歹能工作.

windows

:sp {file} 横向(horizontally)切割窗口,并在新窗口打开file
:vsp {file} 竖向(vertically)切割窗口,并在新窗口打开file

h 切换到左侧窗口
j 切换到下方窗口
k 切换到上方窗口
l 切换到右侧窗口

如果对所有vimer的配置做统计,下面这段配置绝对是出现频率最高的:

577 map <C-h> <C-w>h
578 map <C-j> <C-w>j
579 map <C-k> <C-w>k
580 map <C-l> <C-w>l

省下的绝对不仅是一次w按键,vimer你懂的.

关于窗口的扩大缩小, :help window-resize

marks

:help marks

我用得最多的marks操作是:

'[ 跳到上一次被改变(changed)或者复制的文本段的第一个字符
'' 跳回上一次跳转的地方
'^ 跳到插入模式最后一次结束的地方
'. 跳到上一次文本被修改的地方

缩进与对齐

:help indent.txt

> 向右缩进shiftwidth个字符大小
< 同上,但是向左缩进
= 对齐文本

>,<.=这三个命令的作用域既可以是选中的一段文本,也可以是一个文本对象(后文进阶部门会解释).

进阶

[N]<command> 执行<command>N次

NOTE: [N]<command><range>和<command>[N]<range>是不一样的,如:d3w和3dw两个操作虽然看似一样,但实际上它们在vim内部的行为是有本质区别的:
d3w表示一次删除3个w,而3dw表示一次删除一个w,重复3次.

[start pos]<command>[end pos] 从start pos开始执行<command>到end pos,[]表示其内部的命令不是必须的,也就是说,start pos和end pos都不是必须的.

gg=G和^y$是两个极好的例子:

gg=G 对齐整个文件(gg跳到第一行,=对齐,G最后一行)
^y$ 从当前行行首复制至行尾(^行首,y复制,$行尾),你能看出^y$和Y的区别吗?

NOTE: start pos和end pos仅仅表示一个位置(黑话叫锚点),至于如何从光标移动到start pos或者end pos,vim并没有做出要求.于是,我们可以轻松地敲出如下命令,大大提高文本编辑的效率:

df=, yf=, cf=, vf= 从当前字符开始删除(复制,改变,选中),直到遇到=之后
dt", yt", ct", vt" 从当前字符开始删除(复制,改变,选中),直到遇到"之前

text object

:help text-objects

其中,action可以是v,d,c,y,甚至可以是>,<等
而object可以是w,W,s,p,b,B,以及各种成对符号,如',",<,{,(,[等

有了文本对象,写起代码来更是得心应手,如:

向右缩进一段代码: >i{
删除(复制,改变,选中)光标所在单词: diw, yiw, ciw, viw
删除(复制,改变,选中)""内所有文本: di", yi", ci", vi"
删除(复制,改变,选中)""号内所有文本,包括引号本身: da", ya", ca", va"

visual block

${select region}<commands>

块操作可以一次编辑多行文本,对有规律的编辑需要实在是一大利器.如:

所谓宏,就是一段录制好的操作.

q${register}<commands>q 录制commands到寄存器register
[N]@register 重放寄存器register中的宏N次

看起来,宏和块操作的区别非常明显: 宏"可以认为"是linewise,而块操作是blockwise,也就是说,宏对应的是几行文本,块操作对应的则是选中的block.

从这明显的区别中我们可以推出一个重要的结论: 宏中的锚点有相对的概念,而块操作是绝对的.比如说,行尾就是一个最简单的相对概念,每一行的行尾所在的锚点可能都不一样,但这丝毫不影响宏正确的在所有行行尾插入一段文本.而在块操作中,命令A(在行尾插入)对应的语义却变成了block的尾部,显然,"block的尾部"这一概念对block中的所有行都是相同的,也就是所谓绝对的位置.

折腾

配色

:help syntax

vim自带了许多配色方案(在这里有各种预览),可以用colorscheme命令选择,如: colorscheme desertEx

哪怕再性感的配色,看久了也会生烦,所幸vim自带了synIDattr函数,在vimrc中加入如下脚本:

215 nmap <C-S-P> :call <SID>SynStack()<CR>
216 function! <SID>SynStack()
217    if !exists("*synstack")
218       return
219    endif
220    echo map(synstack(line('.'), col('.')), 'synIDattr(v:val, "name")')
221 endfunc

:so %安装后就可以通过Ctrl-Shift-P组合键方便地查看某段文本的ID了.

得到文本ID之后,需要通过指定颜色来实现自定义.vim支持rgb配色(#445599之流),可是,身为毫无艺术感的二逼后台开发,我自然更偏爱skyblue这类见其名即可知其意的配色方案了:).为了让生活容易些,可以:runtime syntax/colortest.vim直接预览,谁用谁知道.

vim的强大总是让人爱不释手,我们甚至可以自定义ID,比如说,我的代码中TODO横行,为了更直观的显示TODO项,于是便有了这段配置:

60 highlight RipGroup ctermbg=yellow cterm=none ctermfg=black
61 match RipGroup /TODO/

btw,在终端下切记要打开256色: set t_Co=256

再次btw,简单回答一个可以很好的区分vim新手和老鸟的问题,对Alt键的map为何在终端模式下如此虐心? 因为终端他妈的自动在Alt前面加了Esc前缀,这该让人多胸闷啊.

代码折叠

:help fold.txt

vim支持多种折叠方法(fold methods),如indent,expr,marker,syntax等.我偏向于按syntax折叠,配置如下:

716 set foldenable           " enable folden
717 set foldmethod=syntax    " manual : Folds are created manually.
718                          " indent : Lines with equal indent form a fold.
719                          " expr   : 'foldexpr' gives the fold level of a line.
720                          " marker : Markers are used to specify folds.
721                          " syntax : Syntax highlighting items specify folds.
722                          " diff   : Fold text that is not changed.
728
729 "set foldclose=all
730 " use space to folden
731 nnoremap <space> @=((foldclosed(line('.')) < 0) ? 'zc' : 'zo')<CR>

其中,.表示当前行,zo表示展开,zc表示折叠,整行配置的意思就是通过空格键折叠代码,效果如下:

vim默认在搜索和undo时会展开你辛辛苦苦设置好的折叠,这是让人非常难受的,所以我会追加这么一段配置:

726 set foldopen-=search     " dont open folds when I search into thm
727 set foldopen-=undo       " dont open folds when I undo stuff

编码

249 set encoding=utf-8
250 set fileencodings=ucs-bom,utf-8,cp936,gb18030,big5,gbk,euc-jp,euc-kr,latin1
251 if has("win32")
252     set fileencoding=chinese
253     " fix menu gibberish
254     source $VIMRUNTIME/delmenu.vim
255     source $VIMRUNTIME/menu.vim
256     " fix console gibberish
257     language messages zh_CN.utf-8
258 else
259     set termencoding=utf-8
260     set fileencoding=utf-8
261 endif

gui设置

简单介绍一下gvim的设置,首先是字体,我的配置如下:

229 set guifont=Courier_New:h9:cANSI
231 set guifontwide=幼圆:h10:cGB2312

guifont对应的应为字体,guifontwide对应所谓的宽字节字体,中文就是宽字节.

我个人倾向于隐藏gvim菜单栏,工具栏,滚动条等,以最大化代码可视面积:

237 if has("gui_running")
238     " set guioptions-=m  " remove menu bar
239     set guioptions-=T  " remove toolbar
240     set guioptions-=r  " remove right-hand scroll bar
241     set guioptions-=l  " remove left-hand scroll bar
242     set guioptions-=L  " remove left-hand scroll bar even if there is a vertical split
243     set guioptions-=b  " remove bottom scroll bar
244 endif

我要吐槽的是,即便设置了set guioptions-=l,当切割了横向窗口时,左侧的滚动条还是会如幽灵般出现.各种不解后查了手册才明白,原来还要set guioptions-=L,但是,右侧滚动条却没有这个坑,简直坑爹.

tags

:help tags

tags是什么,程序员都懂.通过ctags程序可以很方便的为C++/C项目生成tags:

ctags -R --c++-kinds=+p --fields=+iaS --extra=+q .

--c++-kinds=+p 生成函数原型,该选项默认关闭.同样默认关闭的选项还有l(局部变量)和x(外部变量).
--fileds=+iaS 分别对应类的继承inheritabce,类成员访问权限(access)和routine签名(Signature, 如原型或参数列表等).
--extra=+q 为类成员生成的tag加上其所属的类信息.

这行命令敲起来太累了,不如按一下F5来的痛快:

492 map <F5> :!ctags -R --c++-kinds=+p --fields=+iaS --extra=+q .<CR>

该命令生成的文件为当前目录下的tags.

作为后台开发程序员,查阅系统源码是常有的事,不妨为/usr/include目录生成tags,然后配置vimrc,以便每次启动vim时自动加载(即便有再多的autoload,即便vim启动速度再慢,也足以秒杀emacs了...这算是降维攻击不?):

274 if has("win32")
275     set tags+=E:\workspace\linux\tags  " tags for /usr/include/
276 else
277     set tags+=~/.vim/tags/include/tags " tags for /usr/include/
278 endif
279 set tags+=tags                         " tags for current project

需要说明的是,我偶尔需要在windows上写代码,所以我把linux下的/usr/include目录拷贝到了windows上,然后用ctags windows版生成了tags,于是在windows上写代码也舒适了许多.

插件

vim什么都硬,只是有一点比较短: 扩展性,vim在这一点被咖啡机emacs拉下远远不止一条街,但是对于普通青年我,却也算是差强人意了."Do one thing and do it well",这恐怕是不少vim拥趸自我解脱的说辞.

下面是个人最喜欢的一些插件,排名不分先后:

Quickfix.vim

:help quickfix

Quickfix是vim的标准插件,它是一个典型的plumber: 只要输入符合error format(efm),则vim可以正确理解和识别"错误列表",并跳转到对应行.

我们通过gcc和grep的输出来更好地理解error format:

Error: Unclassifiable statement at hello-world.f90:9.4 gcc编译器的错误提示
./sys/net/bpf.c:137: bpf_wakeup __P((struct bpf_d *)); grep的输出(加上-n)

可以看到,它们的格式是非常类似的.

虽然Quickfix插件原意是为了更方便地调试代码,可是借用error format实现文本的匹配也是非常拉轰的:

505 " search word under cursor like source insight
506 " <cword> is replaced with the word under the cursor (like |star|) (:help cmdline or :help cword)
507 map <C-F> :execute "let g:word=expand(\"<cword>\")"<Bar>execute "vimgrep /\\<" . g:word ."\\>/g **/*.[ch] **/*.cpp"<Bar>execute "cc 1"<Bar>execute "cw"<CR>
508 " next matched line
509 map <silent> <F10> :cnext<CR>
510 " previous matched line
511 map <silent> <F11> :cprevious<CR>
512 " open QuickFix
513 " :copen
514 " close QuickFix
515 " :cclose

简单说明一下这段配置,表示当前光标下的单词(:help cmdline or :help cword),也就是|,表示串联命令.cc 1表示跳到"错误列表"第一条,cw表示打开quickfix窗口,如果存在可识别的错误列表.cnext和cprevious表示在错误列表中切换.

btw,vim在匹配时默认使用自带的vimgrep插件,如果觉得不方便,可以显示指定使用grep: set grepprg=grep

A.vim

A.vim插件可以方便地切换源文件和头文件,还是那句话,谁用谁知道啊.

:A 在同一tab切换源文件/头文件
:AV 竖向切割窗口,打开对应的源文件/头文件.
:AS 横向切割窗口,打开对应的源文件/头文件.

NERDTree.vim

NERDTree插件可以清楚地展示目录树,而且支持许多快捷键.个人最喜欢的快捷键是t: 在新建的tab打开光标所对应的文件.可惜的是,NERDTree原版插件对所新建的tab的命名看起来没什么具体的含义,于是我用上了二手版,配置如下:

378 map <F6> <plug>NERDTreeTabsToggle<CR>
379
380 let g:nerdtree_tabs_open_on_gui_startup=1     " Open NERDTree on gvim/macvim startup
381 let g:nerdtree_tabs_open_on_console_startup=1 " Open NERDTree on console vim startup
382 let g:nerdtree_tabs_open_on_new_tab=1         " Open NERDTree on new tab creation
383 let g:nerdtree_tabs_meaningful_tab_names=1    " Unfocus NERDTree when leaving a tab for descriptive tab names
384 let g:nerdtree_tabs_autoclose=1               " Close current tab if there is only one window in it and it's NERDTree
385 let g:nerdtree_tabs_synchronize_view=1        " Synchronize view of all NERDTree windows (scroll and cursor position)
386
387 " When switching into a tab, make sure that focus is on the file window, not in the NERDTree window.
388 let g:nerdtree_tabs_focus_on_files=1

OmniCppCompelete.vim

OmniCppComplete借助于tags实现智能补全,Ctrl-X Ctrl-O弹出待选择tags菜单,Ctrl-N切换至下一选项,Ctrl-P切换至上一选项.

个人配置如下:

318 " :help omnicppcomplete
319 set completeopt=longest,menu      " I really HATE the preview window!!!
320 let OmniCpp_NameSpaceSearch=1     " 0: namespaces disabled
321                                   " 1: search namespaces in the current buffer [default]
322                                   " 2: search namespaces in the current buffer and in included files
323 let OmniCpp_GlobalScopeSearch=1   " 0: disabled 1:enabled
324 let OmniCpp_ShowAccess=1          " 1: show access
325 let OmniCpp_ShowPrototypeInAbbr=1 " 1: display prototype in abbreviation
326 let OmniCpp_MayCompleteArrow=1    " autocomplete after ->
327 let OmniCpp_MayCompleteDot=1      " autocomplete after .
328 let OmniCpp_MayCompleteScope=1    " autocomplete after ::

在智能补全时,OmniCppComplete会在当前工作窗口上方横向切割出一个preview窗口,preview窗口包含当前待选项的各种说明.杯具的是,当通过Ctrl-N或者Ctrl-P切换待选tags时,该preview窗口将会随着待选说明的变化而增大缩小,如果切换速度较快,则preview窗口看起来就像抖动一般.这也让人很难受,我们可以如下配置,关闭preview特性.

319 set completeopt=longest,menu      " I really HATE the preview window!!!

另外,不要被插件名字欺骗了,OmniCppCompelete同样支持其他语言,如python,xml等.配置如下:

331 autocmd FileType python set omnifunc=pythoncomplete#Complete
332 autocmd FileType javascript set omnifunc=javascriptcomplete#CompleteJS
333 autocmd FileType html set omnifunc=htmlcomplete#CompleteTags
334 autocmd FileType css set omnifunc=csscomplete#CompleteCSS
335 autocmd FileType xml set omnifunc=xmlcomplete#CompleteTags
336 autocmd FileType php set omnifunc=phpcomplete#CompletePHP
337 autocmd FileType c set omnifunc=ccomplete#Complete

关于autocmd,找manual: help autocmd

Supertab.vim

vim在插入模式下支持13种补全方式(:help ins-completion),普通青年记不住,于是有了Supertab插件,配置如下:

312 let g:SuperTabRetainCOmpletionType=2    " 2: remember last autocomplete type, unless I use ESC to exit insert mode
313 let g:SuperTabDefaultCompletionType="<C-X><C-O>"

Taglist.vim

该插件展示当前文件对应的tags列表.配置如下:

299 if has("win32")
300     let Tlist_Ctags_Cmd='ctags'             " set ctags path
301 else
302     let Tlist_Ctags_Cmd='~/ctags-5.8/ctags' " set ctags path
303 endif
304 let Tlist_Show_One_File=1               " only show current file's taglist
305 let Tlist_Exit_OnlyWindow=1             " if taglist is of the last windows, exit vim
306 let Tlist_Use_Right_Window=1            " show taglist at right
307 let Tlist_File_Fold_Auto_Close=1        " hide taglist if it's not for current file

mark.vim

这是一款高亮插件.<leader>m高亮当前光标所对应的单词,再次<leader>m清除高亮.<leader>n清除所有高亮.

mark.vim默认只有下面这6种高亮颜色.如果觉得太少,可以自由地在mark.vim中添加.

 68 " default colors/groups
 69 " you may define your own colors in you vimrc file, in the form as below:
 70 hi MarkWord1  ctermbg=Cyan     ctermfg=Black  guibg=#8CCBEA    guifg=Black
 71 hi MarkWord2  ctermbg=Green    ctermfg=Black  guibg=#A4E57E    guifg=Black
 72 hi MarkWord3  ctermbg=Yellow   ctermfg=Black  guibg=#FFDB72    guifg=Black
 73 hi MarkWord4  ctermbg=Red      ctermfg=Black  guibg=#FF7272    guifg=Black
 74 hi MarkWord5  ctermbg=Magenta  ctermfg=Black  guibg=#FFB3FF    guifg=Black
 75 hi MarkWord6  ctermbg=Blue     ctermfg=Black  guibg=#9999FF    guifg=Black

最后

推荐两大暗爽已久的神器,一是vimperator,在firefox上高度仿真了vim.一是hhkb,其惊艳的键位布局彻底释放了我被压抑多时的小拇指(码农你懂的),而奢侈的电容键盘更让人概叹"RealForce的素质,那仅仅是HHKB的起点而已".当然,如果拿hhkb来码中文,那就是另一回事了...