Vim指令整理和思考

一  前言

        做毕业设计都一段时间了。由于开发使用的是Linux的运行环境,所以开发环境也换到了Linux下。但是总归有一个优秀的开发环境阿?所以花个一小时配置Vim其实是很有意义的。但是前人已经做过的事后人再做感觉就有点浪费时间,而网上有缺乏总结性的文章,因此在这里用一篇日志来归纳以下Vim及其插件的用法。

        这里的指令有些是vim内建的,有些是我自己加的,有些是按照最近的项目定制的,不是规范用法我会声明,如果没有说明,则应该是在所有Vim都有效。关于使用到的插件右边是下载链接: vim+vimrc  (为什么是百度云呢?因为我就没在debian下成功上传到csdn过) 。下载后在$HOME文件夹下解压即可:

$ cd
$ unzip vim.zip

这里的$是shell的用户标识符,键入以后部分。


另外,由于csdn蛋疼的字体,注意大写i I和小写L l的区别,你会想办法的。


二  下载Vim

         想要用Vim之前,必须得下载相应的软件,如果是Debian系(Debian, Ubuntu, etc),可以在终端输入:

$ sudo apt-get install vim ctags cscope
         注意在Debian下把当前用户加入sudoers list或者su后再运行。

        有图形界面要求的,可以再运行:

$ sudo apt-get install vim-gtk
安装GUI版本。


         如果你的电脑上用的是Emacs,那么请再多运行下面指令再继续往下看:

$ sudo apt-get autoremove emacs

三  生成标签

        Vim一项很强大的功能就是代码追踪定位,为此我们在上面安装的ctags和cscope就起到这个作用,但是在让这两个插件发挥作用之前,我们必须生成对应的标签,在项目根目录运行下面脚本即可(注意用csdn代码框上的复制按钮拷贝,恶心的csdn):


#!/bin/bash

ctags "--langmap=c++:.c.cpp.idl.inc.h.hpp.cxx..x.pde" -R *
find . -name *.h -o -name *.pde -o -name *.cpp -o -name *.c -o -name *.cxx | \
	sed -n -e 's/\(.*\)/\"\1\"/p' > cscope.files
cscope -bq -i cscope.files

        假如我的项目目录是/home/user/my_project,并且已经把上面脚本保存在这个目录下的taggen.sh中,那么运行下面指令应该就能完成任务:

$ cd /home/user/my_project
$ chmod +x taggen.sh
$ ./taggen.sh

        这里要注意的是,随着项目开发,新的符号被开发出来以后,要及时运行这个脚本来更新标签,否则结果就是没法用ctags和cscope指令追踪到相应的代码。因此,这里推荐在项目的makefile内加入下面规则,以增加开发效率:

# inside of makefile at project root
.PHONY: tags clean_tags
tags:
	@./taggen.sh
clean_tags:
	-@rm tags cscope.*

        当脚本运行完以后,我们发现项目目录下会生成几个文件,其中两个最重要的是tags和cscope.out,这两个文件生成后就可以通过我的配置文件来进行代码定位了。


        这一小节接下来的部分是对上面的脚本稍作说明,以让读者能够进行一定程度的定制,不感兴趣的可以直接跳过看下一节。

        首先看第一部分:

ctags "--langmap=c++:.c.cpp.idl.inc.h.hpp.cxx..x.pde" -R *
        这条指令生成tags文件,相关指令是:

  1. -R。这是递归搜索的意思,用来递归搜索项目目录下所有子目录的标签。最好打上,除非你的项目目录没有目录管理;
  2. --langmap=c++:.c.cpp.idl.inc.h.hpp.cxx..x.pde。这是在更改语言默认的后缀名。自己编写的项目或许不存在这个问题,但是有些开源项目喜欢自己发明一种文件后缀,里面放的都是C/C++或者其他语言的代码。这时候不要说有多蛋疼。langmap就是用来解决这个问题的,格式是:

--langmap=<language>:<suffix1><suffix2>...<suffix_n>,<language>:<suffix1><suffix2>...<suffix_n>...

各种<suffix>是后缀名的枚举。在我这里,就是把后缀是.c.cpp.idl.inc.h.hpp.cxx..x.pde的文件都认为是C++的源文件。

对于自己编写的源文件,可以直接把命令简化成:

ctags -R

        针对同样问题,我们也希望cscope去搜索非典型后缀名的源文件,但是cscope缺乏相应的指令,因此我们需要比较显式地告诉它我们需要扫描什么文件,因此就给出了这两条指令:

find . -name *.h -o -name *.pde -o -name *.cpp -o -name *.c -o -name *.cxx | \
	sed -n -e 's/\(.*\)/\"\1\"/p' > cscope.files
cscope -bq -i cscope.files
        find指令负责搜索出所有.h,.pde,.c,.cxx,.cpp文件,然后把输出结果送到sed,并让sed给每行输出加上“”,这是为了让cscope可以正常扫描名字带空格的文件,并打印到cscope.files中。接下来一条指令就很琐碎了,-i后指定输入文件。如果是自己编写的项目,可以把命令简化成:

cscope  -Rbq

四  Vim工作模式及简介

        接下来我们就会脱离shell,完全进入Vim的世界了。

        为了防止新手看不懂下面的内容,这里必须得对Vim作出一些简介。

        所谓Vim是Vi IMproved的简称,顾名思义是Vi的升级版,而Vi受到了初期编辑器Ed和ex的影响,借鉴了这两款编辑器的“行编辑”的风格。一般来讲,在Linux下安装了Vim以后可以直接在命令行输入

$ vi

启动Vim。


工作模式

        Vim有三种主要工作模式,分别是normal mode,command mode,insert mode。我把replace mode当成normal mode下的一个特例,还有一个不必要的visual mode,在本文不做介绍(人话:我压根就没用过)。

  1. normal mode:Vim的默认工作模式。基本上键盘上每一个按键都代表一条指令。具体含义再在下面介绍;
  2. command mode:在normal mode下输入冒号(:)进入,在屏幕最下方提供命令行界面,输入特定的指令。具体指令在下面介绍;
  3. insert mode:在normal mode下按aAiIoOs等其他按键进入,和一般WYSIWYG(所见即所得)的编辑器是一样效果,没什么好介绍。

        当读者做了一些奇怪的操作,使得自己不知道处于什么工作模式的时候,疯狂按esc是个很好的习惯。在Vim,无论处于什么模式,在可数次esc的按键后必然可以返回到normal mode,并允许使用者继续后续的编辑。


关于shift和ctrl

        我可以说Vim是个很感性的编辑器,用习惯了会发现很多地方都是直觉性的,用到我在GUI下搞开发都不习惯了。话又说回来,如果选择使用Vim,直觉是跟重要的,在真正介绍Vim指令之前,我觉得有必要介绍一些Vim按键的直觉(intuition)。

  • 关于shift。shift这个按键本身的含义就是换档,因此在Vim里也是这个意思,具体解释一下,就是“换个方式实现一样的功能”。先举个例子,在normal mode下按o是在当前行的“下”一行新建一行,而按O是在当前行的“上”一行新建一行。在这里新建的位置可以说是相反的,但是本质上都是新建一行,只不过实现的方式不一样了。再举一个例子,在normal mode下按zo是展开折叠,但是只展开一层,如果里面还有折叠块则不会被展开;如果按zO,怎所有折叠块都会被展开。显然,这里表现成程度不一样了,zO似乎比zo更加“重量级”,但是命令的本质都是展开折叠,只不过实现的方式不一样了。在Vim里常识shift过后的指令是什么行为是一件让人满心欢愉的事;
  • 关于ctrl。ctrl本身的含义就是控制,因此在Vim中和一些控制相关的指令关联。不过让人很不爽的地方是有些按键还是意外地好用,直接用ctrl往往并不是一个迅速的方法,我会选择通过map来完成对应的动作,这个方案后面再讨论。
  • 关于alt。我用这么久Vim好像还真没怎么用过alt???连小标题都把它忘了,那我只好算了不说它了。


关于normal mode下数字的作用

        给定一个变量v,5v是什么意思?学过数学的人都知道是5个v的意思。在Vim里也是这个intuition。指令前面跟上数字往往都是重复执行同一条指令的意思。比方说指令:

5dd
的意思就是删除当前行在内的5行。不难得出dd就是只删除当前行。在Vim里,很多数字配上指令都是异曲同工之效。


关于normal mode下部分指令的行为

        在该模式下,部分指令是按下就有效果的。但是部分指令需要再多按一个以上的按键才能发挥对应的作用。这个时候可以认为这些指令的第一次按键是一次请求,比如说我要删除,所以我按了d,“请求”了删除过程。然后再按下一个按键来“确认”,比如说我再继续按下d表示确认删掉所在行,或者w表示删掉当前位置到下个单词为止的字符。

        有EE背景的人可以把这个看作是一个状态切换的过程。


五  normal mode下的指令

          下面终于要开始介绍Vim的指令了!不过从实际角度出发,不实践就没法熟练,因此建议你拷贝下面文本到你的工作目录下的learn.c中并在其中对照表格练习:

#include <stdio.h>
#include <stdlib.h>

int main() {
	if (1){
		printf("hello world!\n");
	}
	return 0;
}

plain text:
no zuo no die
ha-ha gets high
i can't continue making_up
i am tired of type
        相信这个文本已经有足够的元素练习一定的指令了,下面都让我们摆指令吧!注意下面所有的指令都实在normal mode下输入的。

        另外约定一般指令大小写敏感,<C-x>是ctrl+x的意思,和<C-X>是一个意思,因为其实没有ctrl+shift的快捷键操作(这是大小写敏感的唯一一个例外)。


移动

字符间:

h, j, k, l左下上右(或者用相应的arrow按键)


单词间:

多个字符组合的块都是单词。

指令作用助记
w, W向后一个单词移动(看看什么才会被认为是单词的分节符?)word
b, B向前一个单词移动back
e, E移动到所在单词的结尾end

行内、行间:

一行是两个回车符之间的距离。

指令作用
0, $移动至当行的第一个,最后一个字符(推荐使用home和end)
^移动到当行第一个非空字符(空是所有显示为空白的字符)
+, -下行、上行第一个非空字符
<num>|移动到当行第<num>个字符(回忆起上面在normal mode下数字的作用)

句、段落:

一般来讲程序员可能只在意段落,这里省略句节间移动(反正很少用)

{, }前一段,下一段的开始

屏幕间:

<C-F>, <C-B>前一屏,后一屏(推荐用pageup, pagedown,更加直观 )

指定行号:

<num>G移动到第<num>行或者通过command mode下:<num>实现
G移动到末行

编辑

注意normal mode下的编辑指令有几个是对应的,存在一个停留在normal mode下的版本和进入insert mode下的版本,下面整理并且对比这些指令

留在normal进入insert作用助记motion?
 i,I(大写i)在光标前、当行前插入文本(注意到shift起的作用了?)insert 
 a,A在光标后、当行后插入文本append 
 o,O在当行下、上插入新行??? 
d, D 删除,删除到行尾deletey
 c,C修改,修改到行尾changey
r,R 覆盖当前字符为下一个输入字符,进入replace modereplace 
 s, S替换,替换当行substitude 
x, X 删除当前、前一个字符??? 
y 复制yanky
p,P 光标前,光标后粘贴paste 
. 重做编辑(注意必须是编辑,即必须对文本产生影响)??? 
u,U 撤销上次编辑,撤销当前行编辑undo 

        这里的一些指令在表格中的“motion?”列是置y的,这是什么意思呢?对应到“关于normal mode下部分指令的行为”一节,意思就是指令本身是只是请求,还需要确认。确认的方法为重复输入请求(这时默认对当前行有效,但是前面输入数字可以改变作用范围,对应到“关于normal mode下数字的作用”一节),或者移动指令,表示作用范围为当前光标位置至移动指令的结果位置。

        在这里可以对开发者给出的一点提示是:这些编辑指令虽说最好都要记得,但是对后面跟motion的命令,更常用的是对当前行的操作,什么单词定位之类的记得的时候用一下即可。

        另外,关于Vim缓冲区的概念。任何对Vim内文本的删除都会暂时存放在缓冲区内,而p指令是从缓冲区内读出数据。因此上述和删除相关的指令都会把文字冲进缓冲区(按照次数缓冲),可以供p指令读取(默认缓冲区只能记住10次内操作)。另外还可以指定命名缓冲区和缓冲区序号,但是在这里就不陈列太多了。记得住上面内容就不容易了。


其他

其他实用指令

zc, zC折叠、全部折叠当前块
zo, zO展开、全部展开当前块(右arrow可实现小展开)

六  command模式下的指令

在normal mode下键入冒号(:)进入该模式。由于这个模式行为太庞杂了,所以只挑出来几个常用的。注意,下面所有指令后加感叹号!表示强制。

指令作用助记
:w保存write
:q退出quit
:qa退出全部quit all
:wqa保存并退出全部write and quit all
:e打开文件(不跟文件名则新建匿名文件)???
:b<num>切换至buffer区的编号为<num>的文件(只在打开多个文件时有用)buffer
:bn,:bp切换至buffer区下一个、上一个文件buffer next, previous
:make进行当前目录下makefile规定的动作 
:!<command>在shell执行<command> 
command mode命令最多,我将在后面作跟进介绍。


七    基于正则表达式的文本定位

        这是一个比较高级的话题。正则让Vim的编辑力上了一个档次,也节省了Vim的使用者大量的时间。对正则没有基础的人或许一次不能了解所有内容,我在这里也点到即止,不展开所有话题。

        Vim在normal mode和command mode下都可以输入正则表达式,在normal mode只可以搜索,在command mode可以实现更强大的功能(从ed和ex下继承过来的)。


normal mode下搜索

形式如下:

/<pattern>, ?<pattern>向下,向上搜索pattern(这里shift也起了作用)
n, N重复上次搜索,反向重复上次搜索
特别地,当<pattern>为空时,起n的作用。


        比如在上面给出的文本中,我依稀记得好像看到了某位长者,但是我too young,竟然不记得长者出现什么地方了,这时候我可以直接在normal模式下输入:

/ha-ha
或者

?ha-ha
这取决于相对于长者的位置。同时我们通过按n或者N可以重复搜索,我们不难发现文本里只有一位长者,这是好的。


Vim的正则语法是传统的正则语法,包含少数的特殊字符,总结如下:

特殊字符意义
.通配符,匹配任意字符
*前一个字符(集)可以出现0到无穷次
\转义字符
/正则表达式的分隔符号(注意?不是)
[字符集开始括号
]字符集结束括号
^仅当出现在正则开始时特殊,位置标识符,表示从行初开始匹配
$仅当出现在正则最后一个字符时特殊,位置标识符,表示匹配至行末
当特殊字符需要表达字符本身含义的时候,需要用\进行转移。

除了上面字符,其他字符都表示字符本身含义。

当然,对特定普通字符进行转移,有可能会让其变得特殊,下面列出常用的几个:

\w匹配任意单词字符,在Vim中等效下列字符集[a-zA-Z0-9_]
\d匹配任意数字,等效于[0-9]
\n换行符
\s空白符。在ASCII中存在7个空白符,常见的是空格, \r, \n, \t这三种

注意到我在上面的说明也给出了一种字符集形式了。正如名字同义,这是一个集合,枚举在列的所有字符。字符集的规则比较简单,而且每次出现只匹配一个出现在字符集中的字符,下面对出现在字符集的特殊字符进行总结:

特殊字符意义
[, ]如上所述,表示开始结束
\转义
-到,如a-z表示a到z的所有字符
^出现在字符集的第一个字符时表示不匹配枚举字符
当然,如果需要表达字符本身含义的时候需要转义。


因此,如果我这样输入正则,

/[ha][ha]

那么我将有可能匹配hh, ha, ah, aa这四种可能。如果我对长者已经不敬到名字都记不清的程度了,那么我可以用这种方法来碰运气。

或者我可能只记得横杠了,那么我可以这样,

/..-..
也不失为一个方法。


另外值得补充的是,我忽略了正则的不少细节,因此上面所述的还不是正则的全部。正则匹配结合特殊字符理论上可以匹配任何一种模式的字符串,如果对正则感兴趣了,可以找任何一本关于正则匹配的书来进行学习(推荐看Perl相关的)。如果只是一些简单的需求,知道上面的也已经足够。


command mode下的正则

        这一节虽然也讲正则,但已经不讲正则的写法,而是讲正则在command mode中使用的各种方法。

        首先可以肯定的是,在command mode下的正则包含了normal mode下的所有功能:

:/<pattern>
:?<pattern>
可以实现完全一样的功能。但是我下面要说明的是,这里虽然现象是一样的,但是Vim看待这两个模式下的正则却是不同的。


      首先我先扯开一点,来讲command mode如何进行行定位,当然,这很简单,所以很无聊:

:<num>
通过这条指令到达第<num>行。不过我们换种写法:
:<num1>,<num2>
这条执行的结果是让光标定位到<num2>上了。那难道command mode下的逗号是忽略的意思吗??此言差矣,这个回答只看到了现象。尝试键入下面命令:

:1,15s/a/i
我们惊异地发现,长者竟然被消灭了!为了水表健康,我们赶紧按u来撤销操作。
        我刚刚做了啥?简单地说,我刚刚把1到15行的第一个a换成了i,因此第一个ha变成了hi。这个子语句
s/<pattern>/<string>/<flags>
的意思是把匹配<pattern>的字符串替换成<string>,s的含义可以理解成substitude。<flags>是一些修饰符,可以为空,为g时是对同一行匹配多次,其他修饰符不作深入讨论。特别地,当前面的行定位操作不存在,即只有简单地:

:s/<pattern>/<string>/<flags>
时,只对本行进行操作。
        因此我们再次回顾到

:<num1>,<num2>
就不难解释出它实际的行为:从<num1>行到<num2>行执行空操作,因而最后光标停留在了<num2>行。


        到这里,我相信都能理解为什么我扯开去了。在command mode键入单个正则实际上就是寻找第一个匹配到<pattern>的行(注意寻找方向),并执行空操作,结果就是寻找到第一个匹配到的行。因此这里并不是单纯的寻找活动。

        相信眼光敏锐的人已经发现,既然在command mode上的单个正则是一个寻找并操作的模式,那我岂不是可以做更好玩的事?如下:

:/<pattern>/,<num><ops>
这个想法显然是正确的,这也是以行为单位的文字编辑器的魅力之一。这里完成的工作是“从第一个匹配<pattern>的行到第<num>行进行<ops>操作”,这里的<ops>可以是很多command mode操作,更精典的是上面的s///操作。

        当然,我完全可以用两个正则来定位,这需要编辑者对文本相当熟悉:

:/<pattern1>/,/<pattern2>/<ops>
这个用法很高级,实际上太难用了,我平时也很少用到。

        更常用地,我们需要定行定点进行s///匹配,我们需要一些更方便的工具,如下面几个操作是比较常用的:

:%s/<pattern>/<string>/<flags>
:.,.+<offset>s/<pattern>/<string>/<flags>
其中%是全局的意思,匹配文本所有行。而.是当前行的意思,第二个语句是“对当前行,到其后<offset>行进行相应替换”。


Vim正则总结

        直接可以下的结论是:正则对编写正则正确率高的编辑者很有帮助,而要正则写得又快又好则需要练习,因此对于新手,如果不能快速掌握,这是太正常不过了。因此在实际使用中,更多使用的,可能是从normal mode下定位的功能。command mode下的一些复杂替换,对于sed脚本的编写者来讲或许也是需要一定时间的调试。

        另外再强调一次,上面对正则用法的解释是浅层的,有很多细节没有给出(包括分组等,本人另外一篇讲Perl的日志也有讲到正则),实际上,讲解正则规范和编写正则的方法可以写一本书(<Mastering Regular Expression>, O'Reilly Press,基于Perl讲解正则),因此,如果对正则表达式感兴趣,或者有这个需求,推荐从Perl入手学习正则表达式。


八  更快地编辑:分屏操作

        分屏操作是Vim很大的优势,相较于GUI界面的IDE,可以节省大量的寻找文件、在文件间切换的时间。再者,在阅读源代码的时候,有分屏功能可以保证阅读者的思路连贯性,可以说百利而无一害。下面是Vim内建的指令:

按键功能
:sp <filename>, :vsp <filename>水平、垂直分屏,如<filename>不为空则分屏打开此文件
:close关闭当前分屏
:res +/-<num>水平增加、减少当前分屏<num>行
:vertical res +/-<num>垂直增加、减少当前分屏<num>列
<C-w>w移动至下一个分屏
<C-w>+, <C-w>-水平增加、减少当前分屏<num>行
<C-w> >, <C-w> <垂直、减少当前分屏<num>列
<C-w>h, <C-w>j, <C-w>k, <C-w>l移动至左、下、上、右的分屏
<C-w>H, <C-w>J, <C-w>K, <C-w>L把当前分屏向左、下、上、右移动
显然,上面的命令还是稍显麻烦,我自己定义了一些快捷键,加速分屏操作:

快捷键等效按键
F3<C-w> <
F4<C-w> >
F5<C-w>+
F6<C-w>-
F9<C-w>w

九  插件

        终于到插件了!上面讲的所有东西其实都和插件没有关系,这一部分要说明插件怎么用,并且我自己在.vimrc下加入了哪些快捷键,使得和网上其他人说的并不一样。下面如果不特别说明,指令都在normal mode和command mode中,以冒号区分。首先要说明的是两个重量级的插件,开发时经常用到:


ctags

        如果在上面自己折腾过脚本的人估计对ctags已经有点认识了。现在要对ctags在Vim中的功能进行说明。这里指出的是,ctags的指令都是插件给出的,我并没有自己添加。

        首先,我们先离开我上面给出的示例文本,实际到项目目录(最好保证是以C/C++为主的)中去了。首先我们先运行一下上面的脚本,保证tags文件生成完毕。随便打开一个源文件,让光标停留在一个感兴趣的函数名字上(不管是声明、定义、还是调用),然后按一下<C-]>,这时我们发现Vim已经跳转到了函数的(其中一个)定义处,而按一下<C-t>返回原来位置。下面先列举这个模块的一个功能:

按键功能
<C-]>追踪至声明、定义
<C-t>或多次<C-o>返回上次阅读位置
:tags查看tag栈
:tag <tagname>查找对应<tagname>(支持tab自动补全)
:stag <tagname>水平分屏查找对应<tagname>(支持tab自动补全)
:ts展开tag列表(tag list)
:tn, :tp查找tag的下一个、上一个对应的位置
这个插件强大的地方在于辅助快速阅读代码的能力。和分屏结合在一起,可以保持思路连续的情况下迅速读通大量代码。但是对于一些项目,一个函数的声明可能伴随多个实现(比如说linux内核代码中的原子操作),这时候用ctags追踪可能达不到想达到的实现去。这时候注意到Vim左下角,如果有多个tag目标,可以通过上表最后一行(:tn, :tp)查找上下结果来找到对应的实现。如果发现左下角显示搜索到的tag太多,那么可以输入:ts来打开tag列表,从中根据文件名来判断应该追踪到哪个文件中的tag。注意到,这里虽然我用函数来举例,但是ctags生成的tag还包括宏,文件,全局变量,数据结构等,随时定位关键代码。

        不过,这里也彰显ctags的一个缺陷:只能追踪到定义。如果我们需要更强大的搜索功能,就要用下一个插件,cscope了。


cscope

        很多GUI的IDE不仅有查询函数定义,还能查找函数在哪里调用,然而,只用ctags不能实现这个功能。不过这么说并不代表Vim有这方面的缺陷,cscope就是用来填补这方面的问题的。

        cscope可以说是加强版的ctags,有更丰富的功能。首先,我们确保已经通过一开始的脚本生成了正确的cscope.out文件,然后我们可以在vim内输入

:cs add cscope.out
将cscope标签添加入Vim中。注意,如果使用我上面给出的链接中的.vimrc,则在vim启动的时候会自动添加,可以省略这一步。下面列举Vim中的实操指令,和我定义的快捷键:

指令快捷键功能
:cs find g <name><C-_>g查找<name>的定义
:cs find c <name><C-_>c查找<name>的调用
:cs find t <text><C-_>t全局查找纯文本<text>
:cs find d <name><C-_>d查找<name>调用的函数
:cs find s <name><C-_>s查找<name>的声明
:cs find e <rep><C-_>e正则查找
:cs find i <file><C-_>i查找包含了<file>的文件
:cs find f <file><C-_>f查找<file>文件
:cw 对于多个查找结果,给出结果列表
其中列在前面的五个比较常用。然而更多的功能的弊端就是更多的按键,对于简单的项目,ctags的结果可能更加理想。cscope可能更常用于一个函数声明多个实现的情况。


        这里值得注意的重要工具是“全局查找纯文本<text>”的功能,针对大项目,在必要的时候要提醒自己记得使用,尤其是只能通过打印信息来debug的时候(这种情况太常见了,一般见于两种情况,一个是OS内核开发,一个是嵌入式系统的调试,不过更蛋疼的时嵌入式OS内核的开发呃...)。对于独特的打印信息,一般这样操作:

  1. 锁定关键字,在command mode下输入
    :cs find t <keyword>
    这里要注意<keyword>必须是纯文本,且不要有任何标点符号和空格。我试过加上标点符号就变得什么都搜不到了。如果搜到大量位置在通过
    :cw
    打开结果列表
  2. 然后,注意到结果列表本质上就是Vim的一个分屏,我们可以在里面用/或者?来进行进一步的搜索。给定合适的正则表达式和关键字截取,可以很快定位到打印错误信息的函数。因而可以在1分钟之内不用在线调试手段定位到一个错误代码信息。


WinManager和Taglist

        这两个插件是我每次打开Vim第一个同时运行的插件。它的作用是在左侧(这个可以改成右侧)显示一个每个GUI IDE都有的项目目录和当个原文件的tag信息。其中,这个taglist是依赖ctags生成的tags文件的,所以你会想总是保证tags文件的有效性。

:WMTogglewm打开或者关闭WinManager和Taglist窗口
注意到可以通过分频移动指令来移动到WinManager,并通过回车来打开文件和文件夹。不过这个插件唯一的鸡肋就是不能新建文件和文件夹。因此我总是会出去shell干这两件事。另外,在这个分屏窗口下的F5会变成窗口刷新,垂直增加列的功能会无效,因而要用其它指令来实现(具体看“分屏”部分)。


A

        这个插件太简单了。它就是用来进行.h和.c/.cpp文件之间切换的。具体用法和快捷键如下:

:AF12在.h和.c/.cpp文件之间切换
这里要注意的一点是,这个插件的实现比较傻瓜,不要指望它会进行搜索,而实际上,它也是可以为你在同一个目录下的.h和.c/.cpp文件之间切换,十万八千里之外的真命恐怕不能靠它了。


自动补全

        这个实现GUI IDE中常见的自动补全的功能。

<C-x><C-o>打开标签的自动补全列表
这里会打开一个列表。通过上下和回车来选择。这里可以补全所有tag,包括宏、函数、结构体、联合体等。注意自动补全依赖于tags文件,你会想要随时更新tags文件的。


supertab

        这个插件不会添加任何的按键,但是当打开一个以上的文件是,会在屏幕上方显示出列表,其中文件名前的数字是该文件在buffer中的位置,可以在command mode下的

:b<num>
:bn
:bp
并根据supertag的数字打开对应的文件。

visualmark

        这个插件实现的是标签功能,但是其实Vim自身也有标签功能,不过这个插件增加了高亮功能

mm把当前行设置高亮
F2在mm标亮的行之间切换

但是Vim自身的标记功能更加强大

m<c>把当前光标位置记录到<c>buffer处
'<c>光标去到<c>buffer记录的位置处
这里的<c>是a到z任何一个字符。也就是说,Vim内部有26个位置buffer,但是其中<c>为m的buffer则被visualmark占用。而相较于Vim内建更强大的是,mm的功能是可以标记多行,尝试一下就知道了。


十  后话

        为什么要用Vim?似乎使用者需要花更多的时间来记忆和学习怎么使用这个编辑器,可以称得上是有点反人类。接下来几点就是我认为使用Vim的好处:

  1. 一字曰之:快!首先我们在GUI的IDE阅读代码或者进行开发的时候,除了编写代码,我们还把时间花在什么上面呢?我相信至少有下面几个大头:
    • 寻找文件和文件之间的切换;
    • 使用鼠标带来的focus lost;
    • 键盘和鼠标之间来回切换的时间。
    下面我详细解释一下为什么上面这几项花费时间和在Vim中的解决方案:
    • 比如在eclipse和VS中,虽然上面有一个已经打开的文件列表,但是随着打开的文件数量增加,我们已经没办法只通过简单的一次鼠标点击来完成文件之间的切换。在文件量达到一定程度以后,我们想要通过眼睛在文件列表中搜索出目标文件,这绝对是无法想象的噩梦。在Vim,supertab允许可视地在buffer中选择合适的文件,通过简单的几次键盘敲击可以完成文件之间的切换。而ctags和cscope带来的功能绝对是文件间自由切换的好帮手。通过这种方式开发、阅读代码无疑至少节省了这方面的时间开销。对于屏幕面积足够大的开发者,分屏永远是更加合适和快速的选择。
    • focus lost是GUI的大问题。这个问题是OS不知道你的操作对象所引起的。当我们鼠标点击了某个按键,我们必须在文本编辑框再点击一次才能再继续编辑,这里的时间花在了多次点击和鼠标移动上,而通过这个时间,我可以完成更多的编辑(利用正则的编辑是批量性的)。但是在Vim压根就没有这个问题:你在什么时候会想在Vim里用到鼠标?
    • 开发者其实可以注意到这个规律:代码写得越多,鼠标就动得越少。我们敲击键盘的时候必然是双手进行配合的。我很难想象怎样才能右手移动鼠标的同时左手在进行有意义的键盘敲击。因此当我们右手握住鼠标的时候,其实左手也暂停了工作。这个时候不仅仅是右手来回切换的问题,这里同时还蕴含着开发者的思路暂停和整体产量的下降。开发者要把握光阴进行开发,通过使用CLI下的编辑器来减少鼠标的使用绝对是一个很好的方案。
    当然,在GUI下开发或许不止在上面三点浪费了时间,从本质上来讲,GUI的IDE是用CLI的复杂的记忆和使用换来了简单但是慢速的开发。在我看来,GUI IDE可能还蕴含下面几点不够快的地方:
    • 和右键的配合。什么go to definition之类的Vim中是两个键的事。
    • 批量处理的困难。在GUI要把1到100行中的几个字从小写换成大写到底要做什么操作?我完全没有概念了。
    • 配置。可能刚接触Vim的人不知道如何配置它,那试问你要刚开始用Eclipse,你又花了多少时间在配置(大头是寻找配置选项在哪,最后还是估计要去百度找)和安装插件上呢?不见得真比Vim的少。更何况Vim可以把自己的配置文件拷贝无数份给别人用,有多方便,功德无量
    最后得声明的是,虽然我针对Eclipse说了蛮多,不过这里我还是得说,其实我还是很喜欢Eclipse的。
  2. 熟练在CLI下开发、管理的必要性。可能做应用的人会对此不屑:我都多少年没写过C了,管这些干啥?但不要忘了,所有的应用层开发都是有一大帮人在后面维护应用层接口的。很多时候,对于这些接口的调试手段之少,以至于GUI下的开发是很奢侈的事情。或许很难有人能想象当前屏幕在打印的东西其实是目标机器在2、3秒之前已经发出了,这在嵌入式开发中不要太正常。对于远程调试由同样的问题。因此我们需要有CLI下的编辑和调试手段。如果不熟悉CLI下至少一款编辑器,恐怕调试的过程是煎熬的。
  3. 还是快。但这里的快是针对运行环境而言的。虽然Vim在启动时要运行不少的脚本,但是对于VS之类启动前的读条,那真是小巫见大巫了。GUI本身是通过时间循环采集的,进程在后台依然是active,在OS调度的层面来讲,恐怕进程优先级也不如Vim的简单阻塞高,最后的结果是Vim的响应速度可能比GUI IDE更快。这可以在VM里得到完美验证。
  4. 估计写到这都没人看了,最后吐槽一下。尼玛工作怎么这么难找。难道我就差到打个电话约个面试的能力都没有吗?HR的职能应该是要成为伯乐,要会识人啊!我看那些公司,都他妈是找实习生来当HR,估计选人都是有模板的,通过checklist来选人又怎么能选得到好人呢??唉,找不到工作只能在项目中疯狂和疯狂写日志中打发寂寥了。要我掌权了,第一件有贡献的事就是撤销HR部门。

关于Vim的简单介绍就到此为止。假设你可以掌握上面给出的一些指令并且灵活应用,保守估计都能比在GUI下编程快个2倍以上,而且思路更加连贯,bug率更加少。磨刀不误砍材功,学好一门工具比啥都重要(不过要是哪家公司让我用emacs就请我我马上把这日志删了TAT)。


十一  细节话题

        下面都是Vim的细节,做更加详尽的介绍,给感兴趣的人看。

.vim和.vimrc

        在$HOME目录下,存在这两个隐藏文件,也是我上面给出的压缩包中所包含的。

.vim文件夹

        这个文件夹包含两个子文件夹,分别是doc和plugin。doc是存放documentation的,plugin是存放插件的。我上面举出的一些插件是存放在plugin文件夹下的。


.vimrc文件

        这个文件是当前用户在打开Vim的时候会自动运行的。里面的语句可以理解成每条语句都在command mode下运行一遍。

        比如说下面这一句:

syntax on
可以认为是打开Vim后手动键入
:syntax on
的结果。文件中其他行结果同理。由于我的vimrc中对大部分常用开关都做了设置我就不一一解释了。另外一提的是,英文的双引号"起到注释的作用,后面的内容不会被执行。

另外Vim还有一个全局有效的配置文件,但是我不建议修改它,因为会影响其他用户,尽管这台电脑只有一个使用者也是如此。


其他command mode指令

        下面给出一些不错的command mode指令,可以一定层度加快开发速度,基于完整性给出,一定程度上不建议经常用:

指令功能助记例子
:abbr <abbr> <string>在insert mode将<abbr>展开成<string>,注意<string>中不应该包含<abbr>abbreviation:abbr abc American-born Chinese
:unab <abbr>取消上条指令定义的缩略语unabbreviation 
:<position>d删除<position>标志的行。还记得<position>可以怎么定义的吗?在上面command mode正则一节有述delete:1,15d
:<position>m<dst>把<position>标志的行移动到<dst>行处move:.,$m1
:<position>co<dst>或 :<position>t<dst>把<position>标志的行复制到<dst>行处copy:.,.+3co0
:set <option>打开Vim的<option>项目 :set nu
:set no<option>关闭Vim的<option>项目 :set nonu
值得提醒的是,abbr指令是简单展开,如果<string>中包含<abbr>会被递归展开,后果一发不可收拾。


map指令

我上面一直提到我设定的快捷键,但是快捷键是怎么设置的呢?map就是设置的方法。设置快捷键的方法如下:

  1.  选择一组按键,使得当前Vim不会对其做任何动作(也就是按键动作为空,或者未定义);
  2. 想清楚我们想在什么模式下使用?n(normal mode), c(command mode), i(insert mode)还是全局?
  3. 设计我们的按键顺序,然后在vimrc或者command mode下输入下面指令

指令功能例子
:map <seq1> <seq2>在全局把<seq2>映射成<seq1>(按键组<seq1>是<seq2>的快捷键):map <F9> <C-w>w
:nmap, :cmap, :imap <seq1> <seq2>功能和上面的指令近似,但分别只在normal mode, command mode, insert mode下起作用:nmap <C-a> ggvG$
:unmap <seq1>取消<seq1>代表的快捷键映射 
:noremap <seq1> <seq2>在全局把<seq2>映射成<seq1>,但如果<seq2>包含<seq1>不会递归进行(map族定义的会) 
:nnoremap, inoremap, cnoremap关系与map族指令类似nnoremap <silent> <F11> :Grep<CR>
这里要注意的是,map族指令和abbr很像,也是简单展开,因此可能会引起递归展开的问题,但是和abbr不同的是,有时候我们就真的希望它递归展开以节省按键次数(但这个做法很高级而且冒险,没有保证当前文本必然会提供停止条件,因此不建议用)。而相对的,用noremap族可能会更加安全。

还有一点小细节就是,如果映射command mode下的指令,我们还需要模拟回车的键入,就和上面的例子一样,用<cr>表示。


另外提醒一下,任何在command mode下运行的指令希望在.vimrc中加载,不需要键入开始的冒号:



下面开始的就是Vim使用的高级话题了,或许心情好了再做更新吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值