好长时间没有上来更新了, 今天趁老板不再上来休闲一下. 本章要说的是和vim的tags相关的内容. 之所以在跳转之后就说明tags是因为这个功能相当的重要和实用. 好的东西自然是需要提前分享的.
首先, 要说的是关于vim使用ctags, cscope的相关教程, 网络上已经有相当详尽的文章可以搜索到. 这里不会在重复大多数网络上可以搜索到的入门教程了. 并且在此提醒阅读本篇博客的读者, 如果你现在对ctags和cscope等并不了解, 那么请先在网络里google所有和他们相关的教程, 花上大约至少一天上的时间认真研究他们的基本用法. 等到对他们有了基本的了解之后再回来看这里的说明你将会有更大的收获.
好了, 废话我就不再多说, 下面进入真题, 首先要说的是ctags是一个linux上很普遍的源码分析工具, 可以将代码中的函数变量等定义的位置记录在一个名称为tags的文件. 类似于数据库记录功能. tags文件的产出最简单的方法是在需要生成tags的工程项目的根目录下执行ctags -R命令, 这会调用tags递归的扫描当前目录以及所有子目录中可以被tags识别的文件所以文件数据信息都会汇集到tags文件中.这里总结一下几个本人暂时知道的几个需要注意的地方:
->ubuntu在默认系统环境中已经集成了ctags功能, 但这个ctags并不是完整的版本, 为了保证vim可正常的使用tags建议安装完整的ctags支持, ubuntu完整的ctags被重名为exuberant-ctags但依然可以通过ctags索引到, 安装命令如下:
sudo apt-get install ctags
->如果你的vim有使用echofunc插件来显示函数的参数定义, 那么在使用ctags生产索引文件时需要使用如下附加参数:
ctags -R --fields=+lS
->ctags默认生成的索引文件只包含了对C语言的语法分析, 如果你需要ctags支持对C++语法分析. 需要使用下面的命令:
ctags -R --c++-kinds=+p --fields=+iaS --extra=+q
->如果你在C语言编写的代码中使用上面提到的C++命令生成tags, 那么你将惊讶的发现, 当你希望通过ctags跳转到光标下函数定义的地方的时候, vim总是跳转到这个函数定义的地方, 原因是ctags的C++命令增加了额外的语法分析以便支持C++更加复杂的语法结构, 这种额外的语法分析用在C语言中的时候就会出现跳转默认定位到函数声明的地方.
->ctags默认是支持C和C++的由于本人暂时没有做过其他语言的开发工作, 所以不清楚ctags是否支持其他编程语言.
->请不要每次生成tags的时候都使用上面的命令, 包括以后其他的很多功能的实现都是如此, 命令是实现一个功能的原型, 如果仅仅使用命令在类unix(windows下面也是如此)的操作系统上实现常用的操作和功能, 那么你的操作将会变得很低效. 个人认为vim的使用也有这个规律, 最普遍的操作最好不要使用长长的命令来实现. 取而代之的方法是设置快捷键和命令的简写映射.
vim中的tags使用
vim配置tags使用的过程是, 先在终端中生成一个项目的tags文件, 再在vimrc中告诉vim哪里去找这个tags. 假如我们有一个项目在/home/boddy/hello/下面, 并且这个目录下已经生成tags文件.那么只要在vimrc文件中添加如下语句就可以让vim在每次启动的时候自动找到这tags了:
set tags+=/home/boddy/holle/tags
如果你有多个tags需要使用,可以重复上面的语句, 也可以在同一个语句中加入多个路径,每个路径用","隔开.
这种方法是我最初使用tags的的方式, 缺点显而易见, 每当我们开始新的项目的时候,不得不重新修改set tags后面的路径, 虽然这样的修改并不麻烦, 也不需要经常操作. 但不免还是让人觉得太死板不智能.
为了让vim对tags的支持更加自动话, 首先想到的方法是在vimrc中添加 settags+=./tags语句, 这样当前目录下的tags就不需要我们手动添加了.但我们不可能永远在项目的根目录操作. 如果在项目的子目录里操作, 这个方法将会失效. 事实上, 任何一个项目文件夹都有一个显著的特点: 任何子目录递归向上都可以到达项目的根目录. 这个特点将会在下面的实践中大显身手, 让我们从各种和项目整体相关的操作中解脱出来. 这包括ctags cscope lookupfile等相关索引文件的自动添加和随时更新. 在项目任意子目录下的任意时刻对项目的自动make和make clean等. 这些最频繁操作要么是自动完成的要么是通过vimrc的map功能映射到不同的快捷键上, 这会让我们的vim具有所以IDE所具有的便捷性的同时保持了vim在编码方面无比的高校性和在功能定制方便的无比的可定制性. 我想这就是我为什么宁愿选择折腾vim而没有使用成熟的IDE的原因.
上面提到工程项目的目录递归特性, 实际上, 我们需要使用一个tags最典型的需求是希望在一个项目中快速的找到函数,变量,宏等定义的地方. 这些查找大多是在项目文件夹内完成. 因此, 只要我们能上vim实现对当前所在目录的递归查找功能, 这个需求就会满足. 只要项目的根目录下存在tags文件, 任何时候任在任何一个项目的子目录下使用vim都可以正确的找到这个tags, 同时如果我们的电脑中存在多个项目, 当我们切换到另一个项目的子目录的时候, vim递归查找到永远是当前项目根目录下面的tags(前提是tags存在于项目根目录), 无意间就实现tags的自动切换功能.
实现vim对tags的自动递归查找其实很简单, 因为vim已经实现了这个功能, 只是默认没有开启. 在vimrc添加下面两行配置, 就会是见证奇迹的时刻:
set autochdir
set tags=tags;
set autochdir表示自动切换目录的意思, set tags=tags;表示自动查找, 这两句同时设置vim即可实现递归的tags查找, 注意: set tags=tags;这一句的最后有一个分号, 这个分号是不能省略的. vim的配置文件使用的是vim自己的脚步语言. 这里是少数几个在行尾需要使用分号的地方之一.
最后需要说明的一点: ctags在默认的命令下生成的tags中使用的是相对路径的存放所有查找结果, 这在多数情况下是一个优点, 因为相对路径不依赖于项目的根目录所在位置. 这样在整个项目转移到别的位置的时候, 相对路径的tags依然可以正常的实现跳转. 不过相对路径的tags并不是没有缺点, 如果你的vim中使用了FuzzyFinder来作为查找项目文件的工具, 你将惊讶的发现如果你在项目根目录的子目录下执行项目文件查找,在找到了想要的文件并最后回车跳转的时候如果tags使用的是相对路径, 这一步将会失败, 因为FuzzyFinder无法正确的通过当前的目录(不是项目根目录)加上tags中的相对路径计算出正确的文件位置. 解决的办法是在ctags生产tags文件的时候使用绝对路径, 使用方法是在ctags -R 的命令后面添加项目根目录的绝对路径, 如: ctags -R /home/boddy/hello/ , 使用绝对路径可以保证tags在任何调用他的工具中正确的找到文件位置. 唯一的缺点是如果项目移动了, tags必需重新生成, 鉴于我们的项目在自己的电脑中移动的需求很小的同时重新生成一tags的时间也就是几秒钟的事情(只要不是超级大的项目,tagseh生成还是很快的), 我个人选择了在任何时候使用ctags都使用绝对路径, 当然这个功能是在vim中通过参数自动实现的, 本文的最后将会提到.
好了, vim对tags的自动查找功能实现了, 只是一个开始哦. 下面的介绍将会进一步增强vim在代码跳转和搜索上的能力.
ctags的优点是使用简单, 生成的tags文件比较小, 使用时对tags的检索也相对比较快, 对c语言的函数和宏定义跳转相当准确高效. 他的缺点功能相对单一, 没办法实现对一个关键字出现位置的统计, 一个函数被调用的位置统计, 局部变量的定义跳转往往没有效果等. 我们在编码的时候除了随时查看函数定义的需求以外, 另一个比较常用的需求是对一个函数在项目中出现位置查找. 这个功能ctags是没有的, 为了让vim实现这个功能, 我们需要借助另一tags索引工具:cscope
网络上对cscope的用法说明也很多, cscope的使用要不ctags复杂的不少,因为一样的, 请先自行google cscope相关的教程提前了解.
cscope的用法和上面的ctags的用法很相似, 不同的地方有:
->生成索引文件的命令不同: cscope -Rbkq
-> 索引文件的名字不同: 一共有三个, 主要的文件是cscope.out, 另外两个cscope.in.out cscope.po.out 是在生成命令中使用q参数才会有的文件, 这两个文件可以加速cscope的查找速度, 注意 : windows下面也可以使用tags和ecscope的,但cscope的-q参数并不支持, 因为windows下面将不会有cscope.in.out 和 cscope.po.out文件
->vim中用法不同: ctags 默认在vim中典型的用法是ctrl+] 跳转到光标下的关键字的定义处, ctrl+t跳转回来. cscope在vim下面并没有映射快捷键. 因为cscope的查找模式有近八种之多, 因此和vim配合使用的时候,默认是通过一组:cs find 开头的命令来实现的. cscope的官方网站上有一份在vim中使用的建议快捷键映射配置. 个人觉得cscope虽然查找方式众多, 但真正经常用到的也就三到四个. 可以试着将其分配到容易操作的键位上来. 官方的那个映射还是稍微有点难按了点.
->ubuntu默认系统下完全不支持cscope, 需要通过下面的命令安装cscope:
sudo apt-get install cscope
cscope是一个ctags的增强版本, 可以提供上面提到的各种查找功能. 其实我们完全可以不用ctags而只用cscope的, 因为cscope可以做到所有ctags的功能. 但出于ctags在c语言函数等定义上跳转的高效和准确性. 我个人是两个同时使用的, 定义的跳转通过ctrl+]和ctrl+t实现, 只要在需要其他查找的时候才会动用:cs find 命令.
好了, 既然实现了ctags的自动查找, cscope自然也要实现, 不能输入人家ctags啊. 不过在vim中实现cscope的递归查找就比较麻烦. 首先要注意的一点是:简单的在vimrc添加cscope路径有两种写法:
linux的终端版本的vim7.3中需要使用如下的写法vim才能正确的识别cscope的tags文件
cs add /home/boddy/hello/cscope.out /home/boddy/hello/
第二个参数是告诉vim cscope的索引文件中记录的数据的相对路径的起始位置在那里, 大多情况下的这个位置是我们项目的根目录.
windows下面的gvim7.3需要使用如下的写法gvim才能正确识别cscope
cs a E:/project/hello/cscope.out
这里不需要添加相对路径的起始位置, 具体原因不清楚, 不过就是这个问题导致我在windows下面使用vim在很长一段时间中没能使用cscope.
上面说的注意事项是我在vim学习之初遇到的问题, 下面实现的递归索引功能将不会关注这个问题, 但依然需要主要这个注意事项.
vim中实现cscope的递归查找有两种方式, 要么自己在vimrc中写一个简单函数让vim启动的时候去递归查找并添加cscope.out, 要么使用插件. 通过简单函数的实现在网络上可以搜索到, 同时我在最初也是用这种方法实现的, 不过在后来的某一天偶然让我发现了vim有一个实现递归查找cscope的插件后就删除了这个在vimrc中的函数,并使用插件来实现. 这样可是简化vimrc的长度的同时别人写的插件肯定比简单的函数要完善.
到下面的网站中搜索autoload_cscope.vim即可下载到这个插件.
http://vim-scripts.org/vim/scripts.html
针对这个插件有如下的说明:
这个插件只有一个脚步文件, 放到你的~/.vim/plugin目下即可使用, 该插件默认情况下是在我们打开.h/.c/.cpp文件的时候才会自动递归查找并添加cscope.out文件的,个人觉得既然我们在已经在项目子目录下了, 大多时候都是希望cscope可用的, 即便我们是在编辑一个.txt文件我们可能希望手动搜索一个当前项目的关键字等. 另外即便是cscope.out文件我们用不到,但将其添加到当前编辑文件中来在性能上几乎是没有影响的. 因此, 我人为的修改了这个插件,以便让它实现在打开任何文件的时候(包括新建空文件)都递归查找cscope.out
修改方法如下:
打开autoload_cscope.vim定位到文件的最后几行,你将看到:
au BufEnter *.[chly] call
au BufEnter *.cc call
au BufUnload *.[chly] call
au BufUnload *.cc call
将他们修改为下面内容即可
au BufEnter * call
au BufUnload * call
另外可以在vimrc中选择性的添加如下内容
set nocst "在cscope数据库添加成功的时候不在命令栏现实提示信息.
set cspc=6 "cscope的查找结果在格式上最多显示6层目录.
let g:autocscope_menus=0 "关闭autocscope插件的快捷健映射.防止和我们定义的快捷键冲突.
"个人cscope的快捷键映射
"cscope相关的快捷键映射
nmap ff
"s:查找即查找C语言符号出现的地方
nmap fs :cs find s
"g:查找函数、宏、枚举等定义的位置
nmap fg :cs find g
"c:查找光标下的函数被调用的地方
nmap fc :cs find c
"t: 查找指定的字符串出现的地方
nmap ft :cs find t
"e:egrep模式查找,相当于egrep功能
nmap fe :cs find e
"f: 查找文件名,相当于lookupfile
nmap fn :cs find f
"i: 查找当前文件名出现过的地方
nmap fi :cs find i
"d: 查找本当前函数调用的函数
nmap fd :cs find d
"cscope相关的快捷键映射
nmap ff
nmap ss
"s:查找即查找C语言符号出现的地方
nmap fs :cs find s
"g:查找函数、宏、枚举等定义的位置
nmap fg :cs find g
"c:查找光标下的函数被调用的地方
nmap fc :cs find c
"t: 查找指定的字符串出现的地方
nmap ft :cs find t
"e:egrep模式查找,相当于egrep功能
nmap fe :cs find e
"f: 查找文件名,相当于lookupfile
nmap fn :cs find f
"i: 查找当前文件名出现过的地方
nmap fi :cs find i
"d: 查找本当前函数调用的函数
nmap fd :cs find d
好了, cscope的自动化使用到此也大功告成,如果你想在vim中查看auto_cscope是否成功为你自动加载了cscope.out,可以在vim中执行 ":cs s" (这s是show的缩写) 来查看与当前vim窗口关联的所有cscope文件的信息.
下面是今天的最后一个自动索引功能的实现. 我废话真的太多了, 写博客都写累了....休息中....
最后一个查找跳转类型的数据库文件是lookupfile这个插件. 实际上这个插件在很大程度上是多余的, 首先是cscope提供了文件查找功能. 其次是如果使用FuzzyFinder插件可以实现借助tags文件来查找项目文件. 不过鉴于lookupfile这个插件遇见的较早, 功能简单, 查找优美, 设置好了之后使用也很方便又不和任何其他插件的冲突, 我还是永久的留下了这个插件. 下面是这个插件的相关设置过程:
到http://vim-scripts.org/vim/scripts.html 搜索lookupfile下载并安装该插件.
编写生成lookupfile索引文件的脚本, 这个我也是从网络上抄来的, 内容可以如下:
#!/bin/sh
# generate tag file for lookupfile plugin
echo -e "!_TAG_FILE_SORTED/t2/t/2=foldcase/" > name.tags
find . -not -regex '.*/./(png/|gif/)' -type f -printf "%f/t%p/t1/n" | sort -f >> name.tags
其中的name.tags是生成的的索引文件的名字, 你可以修改成任何期它的名字, 不过需要注意一定要使用一个后缀名, 不可起如nametags这样的名字, 这在后面的会说明原因.
将上面的内容保存为.sh后缀的文件(如:nametags.sh),将这个shell文件移动的到一个目录下并将该目录添加到环境变量中去. 个人建议是在~/.vim目录下建立一个bin文件夹, 将nametags.sh移动到这里, 以后只要是和编程有关的shell文件或自建的批处理等文件都可以放到这里来, 这样在日常备份的时候只要将~/.vim 文件夹和~/.vimrc文件打包就可以备份很多开发环境了, 本这这个原则, 我个人开发相关的一些其他的内容(如vim的补全列表, 标准项目模板, 个人使用的函数库等)都会放在.vim文件夹中.bin文件夹下面存在放的都是需要执行的文件, 因此在第一次使用之前,重装系统或移动到一台上使用的时候需要做如下两个操作:
->将bin目录下所有的文件赋予执行权限: sudo chmod 775 ~/.vim/bin/*
->将~/.vim/bin目录添加到环境变量中去. 这个操作有很多中方式, 每个linux版本也不是太一样, 可以自行百度解决.
nametags.sh在赋予可执行权限和添加环境变量操作之后就可以在任何目录执行了, 执行的结果是nametags.sh 索引当前目录的所有的子目录, 将.c .h等源文件排序后添加到当前目录的name.tags文件来.这name.tags相当与tags文件, 只是他的功能只能用来查找项目文件而已.
在项目根目录执行nametags.sh之后我们需要向ctags和cscope一样通过vimrc来告诉vim的lookupfile插件那里去找这个项目文件的索引,当然, 我们也需要实现vim自动对name.tags文件的自动递归查找. 这个功能在网络上可就没有现成的方法了, 因为很少有人会想到这个需求, 同时实现起来也很困难, 因为没有多少人对vim了解到会编写脚步的地步, 我至今还不太会编写比较复杂的vim脚本文件. 好在大多数复杂的功能都已经被大神和前辈们实现了, 而我们需要做的就是让这些功能更加的便捷并定制成符合自己需求的样子就可以了.
在看过了lookupfile的帮助文档和查阅了一些vim脚步资料之后, 在我不断的尝试性的编写下竟然让我实现了lookupfile的递归查找name.tags的功能. 这个算的上是我完全独立实现的功能啦. 有纪念意义哦....
在vimrc中添加如下两条语句,将实现lookupfile的自动递归查找name.tags文件:
let g:name_file=findfile("name.tags", ".;")
let g:LookupFile_TagExpr='g:name_file'
第一句的findfile函数实现了递归查找,注意:findfile函数似乎有一个神奇的特点, 如果你查找的是诸如name.tags这种带有后缀名的文件, 那么它会返回一个绝对路径的文件名字符串,可是如果你查找的是诸如nametags这样没有后缀名的文件, findfile依然可以正确的找到这个文件, 但返回的东西就很坑爹了, 直接是nametags, 路径全部被吞掉了!!!
第二句是将第一句查找的结果连同路径一起赋值给LookupFile_TagExpr变量, 这里似乎必须在name_file前面添加一个g:或s:才能正确的被vim识别.
为更家便捷的使用lookupfile查询文件,我在vimrc设置了如下的对lookupfile的设置和快捷键的映射并从网络找到了一个可以实现lookupfile查找时忽略大小写的功能函数:
let g:LookupFile_MinPatLength=2
let g:LookupFile_PreserveLastPattern=0
let g:LookupFile_PreservePatternHistory=1
let g:LookupFile_AlwaysAcceptFirst=1
let g:LookupFile_AllowNewFiles=0
let g:indentLine_color_gui='#4e5656'
nmap ,l :LUTags
nmap ,kk :LUBufs
nmap ,kl :LUWalk
"LookupFile搜索时不区分大小写
func! LookupFile_IgnoreCaseFunc(pattern)
let _tags = &tags
try
let &tags = eval(g:LookupFile_TagExpr)
let newpattern = '/c' . a:pattern
let tags = taglist(newpattern)
catch
echohl ErrorMsg | echo "Exception: " . v:exception | echohl NONE
return ""
finally
let &tags = _tags
endtry
let files = map(tags, 'v:val["filename"]')
return files
endfunc
let g:LookupFile_LookupFunc = 'LookupFile_IgnoreCaseFunc'
由于这三个索引功能的文件都不会在项目文件发生修改的时候自动更新, 因此需要我们手动的在需要更新的时候一个一个的更新他们,事实上这样的更新在我们查看别人的项目和linux内核文件的时候并不是太过频繁,有时甚至很少. 例如在查看linux内核源文件的时候我几乎只需要建立一次索引就可以了, 重点这是一个很幸运的事情, 因为linux的内核文件是相当的庞大的, 对它建立tags和cscope.out以及name.tags文件加一起更新一次就需要近十来分钟. 这三个数据库文件都不支持增量更新. 即便我们只改动了整个项目的很小的一部分内核, 重新构建tags的时候依然需要对整个项目进去完全的检索. 从这一点来看ctags和cscope并不适合超大型项目的频繁构建. 之所以说这是一个幸运是因为我们很少会参与诸如linux内核这样的项目构建. 即便公司真的有一个超大的项目, 分配到我们身上的大多是很小的一部分或一个模块功能. ctags和cscope建立的速度还相当快了, linux内核产生的ctags可以达到100多兆, cscope.out更是达到300-400兆. 这样的数据在几分钟内完成,可想而知, 不是超级巨型的项目, tags建立基本可以在十妙内完成.
现在还剩下最后一个问题, 虽然既然我们很幸运的可以正常使用他们, 但是在开发一个常规项目的时候频繁的重新构建tags,cscope.out和name.tags是正常的操作之一, 这些索引数据都是通过命令建立的.每个命令又不是那么简短. 编码时,如果希望刷新tags的时候就跑到项目根目录挨个执行一通他们的命令肯定是一件没几个人能接受的事情. 好在这些tags的构建名利几乎都是固定的. vim提供了直接执行shell命令的能力. 再加上快捷键映射功能和项目的递归特性. 我们便有办法将tags的更新简化到一键操作, 一键操作是一个很了不起的能力, 因为除了全自动的操作, 应该没有比一键操作还要简洁高效的了.
下面是实现ctags, cscope,和lookupfile的一键刷新功能的设置:
->在项目的根目录新建一个空的文件名为TOP的文件. 这个文件用来标识项目根目录所在位置.(你可以将这个空文件做为你的项目标准模板的一部分)
->在vimrc中添加如下的函数和设置:
"nmap
"/ --extra=+q $PWD
"imap
"/ --extra=+q $PWD
"nmap
"/:!cscope -Rbkq
"imap
"/:!cscope -Rbkq
nmap
/:!cscope -Rbkq
imap
/:!cscope -Rbkq
func! Go_top()
wall
let g:Curr_dir=getcwd()
let i = 1
while i < 10
if filereadable("TOP")
return
else
cd ..
let i += 1
endif
endwhile
exec 'cd'.g:Curr_dir
endfunc
func! Go_curr()
exec 'cd'.g:Curr_dir
endfunc
func! Do_CsTag()
silent! exec "!find . -name '*.h' -o -name '*.c' -o -name '*.cpp'
/ -o -name 'Makefile' -o -name 'makefile' -o -name 'make*'
/ -o -name '*.cc' -o -name '*.C'-o -name '*.s'-o -name '*.S'>cscope.files"
silent! exec"!cscope -Rbkq -i cscope.files"
silent! exec"!cscope -Rbkq"
endfunc
说明:
->前面几行用 " 注视掉的内容是对C++的支持, 在使用C++的时候可以解除他们的注释并将下面C语言使用的映射添加注释.
->整个对
->映射中在执行ctags -R的时候额外的传递了一个$PWD变量, 这个变量在vim表示的是vim的当前工作目录, 由于在执行ctags前已经调用Go_top()将当前目录更改为项目的根目录, 所以这里传递的实际上就项目的跟目录, 这个变量的作用在之前说过, 它可是让ctags生成的文件使用绝对路径, 这会保证几乎所有调用的tags的插件都可以正常的工作.
->Go_top()函数首先记录下当前目录位置然后递归的查找当前目录所以父目录下是否存在TOP文件, 如果存在就定位到那个目录并返回, 如果不存在通过之前记录的目录返回到之前的目录, 这样做的好处是的即便没有找到TOP标识,vim的当前目标也不至于跑到系统的根目录上去.
->Go_curr()函数实现vim在成功找到TOP标识并在TOP所在目录执行完所有命令之后通过g:Curr_dir记录的全局变量来让vim的当前目录中回到执行命令前的状态.
->Do_Cstags()函数是一个可选函数, 他的功能是可以在生成cscope的时候过滤掉一些不需要建立索引的文件. 事实上, 这个功能并是经常用到, 因为项目文件夹中的的文件大多和项目相关. 不相关的一些说明文件cscope也可能对其建立索引.为了让所有可能建立索引的特殊文件(如:makefile)也可以实现跳转.我自己并没有过滤任何文件.
当上面所有的设置都正确工作了之后, 恭喜, vim和tags相关的配置就算告一段落了. 以后你要做的就是在项目根目录建立TOP文件.在任何项目目录中打开vim按下f12键, vim将会自动为你建立所需的所有tags文件. 之后只要你在任意项目目录中打开vim属于这个项目的所有tags文件都会被vim自动加载并正确工作. 在你编辑代码的任何时刻, 你都可以通过一个f12键迅速的更新整个项目的所有tags索引. 你可以通过ctrl+]和ctrl+t来调用ctags, 在普通模式下通过"f"打头快捷键调用cscope查询,通过 ",l" 来调用lookupfile查询项目文件. 这篇文章中提到到所有功能最终被浓缩到一个f12键上. 这种一劳永逸并可以大幅提高效率的感觉会让你觉得付出的一切的是那么的值得. 这也是为什么我愿意花掉整个下午的时间来写这篇博客的原因. 希望这几千的说明可以被更多愿意使用vim的人看到. 也希望有其他的vim爱好者能够和我更多的交流学习....