纪念Bram Moolenaar
上一期的漏掉的点以及错误
错误
上一次说的d2w和2dw都是删除两个单词,区别在于d2w是告诉d要删除两个单词,而2dw是执行两次dw,总体结果一样但是有细微区别。
% 能操作用于跳转到匹配的括号处,可以匹配(),{},[]和xml标签,没有<>。
漏掉的重要点
正常模式
.可以重复执行上一次操作,或者重复编写你上一次插入模式下干了什么。
这个非常有用,比如
你本来想删除两行,结果你执行了dd只删除了一行,你就可以按.再删除一行。
或者你想写6个test,你还可以这样写,下面是从正常模式下开始键入字符的,其中<ESC>代表esc键
itest <ESC>Atest <ESC>....
这样你就写了5个test
dd可以删除当前行
yy可以复制当前行
={motion} 可以自动匹配缩进,不知道motion有什么作用的可以看看上期。(此操作可以用在可视模式下,下面有说)
== 自动匹配当前行缩进。
可视模式
除了i(,a(,i[,a[,i{,a{的操作,还有i<,a<,i",a",i',a'和i`,a`,这些东西的用法如出一辙,可以看上一期(注意这是可视模式下的操作,不要搞错了)。
选择一段内容后可以按=自动匹配一块代码的缩进
正文
第四部分命令行模式
命令行模式可以在正常模式或者可视模式下键入:(冒号)进入。
如果说移动光标是vim的入门,那么我认为操作命令行模式将是你进阶vim的必经之路!
正常模式下的命令行模式
使用:进入命令行模式可以调用vim内置命令或函数,当然你也可以自己写命令和函数,因为vim有自己的脚本语言叫vimscript!
常用的命令有:
此图摘自《vim实用技巧》
老样子先说说新的参数有什么作用,[range]用于指定一个范围。比如:1,3指定1到3行的数据。当然还有一些特别的变量,比如
%用于指定整个文件的范围
. 用于代表当前行数
$ 用于代表文件最后一行
更多详情请执行:h range
参数[x]是寄存器,由于还没有详细讲寄存器所以知道一下就行,如果没有指定寄存器参数,默认会存进vim特殊的寄存器中,可以在正常模式下用p直接粘贴出来(可以看看上一期)。
参数{file}指定一个文件
参数{address}指定一个地址,在这里是行地址。
参数{pattern}用于正则表达式的匹配
[flags]是一些参数用于改变某个命令的行为
{string}就是简简单单的字符串辣。
此外这里所有的命令,除了copy和normal,copy可以简写为co,normal简写为norm,剩下的都可以用它们的首字母来代替整个命令,比如:delete,你可以直接写成:d,不必写出全名,极大的方便了操作!
比如你可以试着执行以下命令,看看都有什么效果!其中<CR>代表回车
:%d<CR>
:.,$y<CR>
:.m.-2<CR>
:1,5j<CR>
:%normal I//<CR>
:%normal A;<CR>
接下来解释这几行命令
:%d<CR> %指定整个文件d代表删除,所以这个命令清空了这个文件的缓冲区。
:.,$y<CR> .代表当前行,$代表缓冲区最后一行,y是复制,所以这条命令复制了当前行到最后一行的所有内容
:.m.-2<CR> .是当前行,m用于移动,所以这条命令是把当前行移动到.-2行的下面,也就是把这一行挪到上一行。举着个例子的主要意图就是说一下这些特殊的变量可以进行运算。
:1,5j<CR> 是将1到5行内容和并成一行用空格隔开。
:%norm I//<CR> 很好的指令,我们来解读一下,%表示整个缓冲区,norm用于将范围中的每一行都执行一个正常模式的命令,I(大写i)//这个东西不是一个指令,正常模式中I是跳到本行行首并进入插入模式,然后插入//,所以这个指令的最终效果是注释整个文件
:%norm A; 把每一行的末尾都加上一个;
这个命令试着自己分析一下!
现在有一个需求试着自己写一下,有这样一个代码
enum Test{
TEST_1 = 0,
TEST_2 = 1,
TEST_3 = 2,
TEST_4 = 3,
TEST_5 = 4,
TEST_6 = 5
};
typedef enum Test Test;
Test t;
.....; //t经过一系列操作
switch (t){
}
你需要将这些枚举成员全都在switch里面列出来,尝试思考一下怎样才能最快的完成这个任务!其实这个任务使用substitute比较简单,但是你可以试试在使用其他的指令(比如normal)完成。
关于substitute和global指令,因为需要学习完正则表达式才能发挥最大威力,所以这里只是让大家知道一下。
可视模式下的命令行模式
其实和正常模式下的命令都是一样的,区别在于从可视模式下进入命令行模式(键入:)会自动指定range。比如你现在有一些这样的文本。
I am vimmer.
I am currently studying Vim.
Input : used to enter Ex commands.
在正常模式下
现在你的光标在第一行(不在第一行键入gg跳到第一行),键入V(大写v),再键入:进入命令行模式,你会发现此时的命令行模式多了一些内容,没错就是:'<,'>,
其中'<用于指定可视块的开头行(对于linewise-visual),
或者指定可视块的开始字符(对于characterwise-visual),
或者指定可视块的第一行的最后一个字符(对于blockwise-visual),
这里全都用官方的名字,对于上一期我自创的名字深感抱歉。
由于刚才是使用V(大写v)进入的linewise-visual所以'<表示块开头行,'>表示块末尾行,此时你可以进行一些操作,比如d删除,y复制,或者m移动,等等…。从可视模式进入命令行模式会自动指定范围,这种操作可以说是非常的方便!
现在你用可视模式指定了第一行的文本,接下来可以用m命令进行移动,输入:m'<-2<CR>。可以让内容向上移动一行,输入:m'>+1<CR>可以让内容向下移动一行。
看起来不错,但是太费劲了!移动一些代码还要写这么多东西。不过你可以讲以下两行内容放到~/.vimrc的最末尾
vnoremap J :m'>+1<CR>gv=gv
vnoremap K :m'<-2<CR>gv=gv
这样你就把可视模式下的J和K映射成了移动代码的操作,你可能有疑问gv=gv是什么意思?回顾上一期,gv是重新选中上一次的可视块=用来自动匹配缩进,最后再gv选中上一次的可视块。这样整个操作就形成了闭环,不仅可以连续的上下移动,还可以根据代码自动调整缩进!
第五部分搜索字符串和替换字符串
在众多内容中搜索特定的字符串,岂不美哉?
搜索命令是在正常模式下按/或者?区别如下
/{pattern} 正向搜索
?{pattern} 反向搜索
比如/test,在当前缓冲区中正向搜索test字符串,如果有多个匹配可以使用n跳到下一个,N跳到上一个匹配的位置。
替换字符串使用substitue指令,缩写为s。
比如
:%s/int/long long int/g 这个指令会把缓冲区所有的int全部换成long long int,后面这个g代表全局,不指定g默认只会替换每一行的第一个。
最简单的搜索肯定是没有意思的,这里所有的{pattern}参数都可以传入一个正则表达式,这样搜索和替换命令可以达到非常大的威力!
第六部分vim正则表达式
vim正则表达式语法风格更接近POSIX版本
1.基本正则表达式字符
- . :匹配任意单个字符。
- * :匹配前一个字符0次或多次。
- ^ :匹配行的开始。
- $ :匹配行的结束。
- [abc] :匹配方括号内的任意一个字符(在这个例子中是a、b或c)。
- [^abc] :匹配不在方括号内的任意一个字符。
- [a-z0-9]: 匹配a到z所有的小写字母和所有数字
- \ :转义字符,用于匹配特殊字符或字面意义上的正则表达式符号
2. 特殊字符类
- \d :匹配任意数字,等同于 [0-9] 。
- \D :匹配任意非数字。
- \w :匹配单词字符(字母、数字和下划线),等同于 [A-Za-z0-9_] 。
- \W :匹配非单词字符。
- \s :匹配空白字符(空格、制表符等)。
- \S :匹配非空白字符。
3. 量词
- + :匹配前一个字符1次或多次。
- ? :匹配前一个字符0次或1次。
- {n} :匹配前一个字符恰好n次。
- {n,} :匹配前一个字符至少n次。
- {n,m} :匹配前一个字符至少n次,但不超过m次。
4. 分组和引用
- (abc) :将abc看作一个整体进行匹配。
- \1 :引用第一个匹配的分组。
-
\2 :引用第二个匹配的分组,以此类推。
学过正则表达式的肯定会觉得非常的轻松。
现在有这么一个搜索任务,
body { color: #3c3c3c; }
a { color: #0000EE; }
strong { color: #000; }
内容摘自《vim实用技巧》。
这是一个css片段代码,你的任务是搜索到所有的16进制颜色代码,聪明的人肯定会说,那我就/color 搜索color我就能找到16进制颜色代码了。确实,但是这样不是我们想要的,我们要用正则表达式直接匹配16进制颜色代码。
众所周知css颜色代码有6位,3位和8位16进制代码,所以我们可以写出如下表达式
/#\([0-9a-fA-F]\{8}\|[0-9a-fA-F]\{6}\|[0-9a-fA-F]\{3}\)
好长啊!我们来解读一下,#在这里只是匹配普通的#字符,后面使用\(转义,意思是这个括号不是正常的括号,而是要进行分组了,[0-9a-fA-F]这个不难看出这是用来匹配一位16进制数。\{8}这也是一个转义,转义成一个量词匹配这个16进制数恰好出现三次,\|转义为或。整体意思就是说,匹配8位或者6位或者3位十六进制数。
这下理解了,但是这也太长了,特别是\用了7次!,那么如何缩短呢?正好vim提供了一种very magic搜索模式,进入这种搜索模式可以简化上面的表达式
/\v#([0-9a-fA-F]{8}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})
看起来舒服一些了,\v就是用于进入very magic模式,这样可能又有人问了,进入这种模式我该怎么匹配正常的(字符呢?很简单用\(转义,实际上有点反过来的意味,但是并不都是。具体了解请:h /\v。
那么这个表达式还能再精简,因为vim提供了一个特殊字符类\x用于匹配16进制数,也就是说\x就是[0-9a-fA-F]。那么最后的表达式会变成这样
/\v#(\x{8}|\x{6}|\x{3})
太棒了!非常简约的表达式,一看就知道什么意思。
说完正则表达式搜索,可以想想替换了,还记得在第4部分留的练习题吗,我们就拿它来下手!
enum Test{
TEST_1 = 0,
TEST_2 = 1,
TEST_3 = 2,
TEST_4 = 3,
TEST_5 = 4,
TEST_6 = 5
};
typedef enum Test Test;
Test t;
.....; //t经过一系列操作
switch (t){
}
假设switch在第50行,enum在第1行,我们可以这样做
正常模式下
:2,7co50<CR>
:51,56s/\v(\w+)\s\=\s\d+,?/case \1: break;/<CR>
简单快捷,首先:2,7co50<CR>就把2到7行的内容复制到了50行下面,也就是switch下面,然后替换51行到56行的字符串,搜索规则是
\v(\w+)\s\=\s\d+,? 首先\v代表进入very magic模式,(\w+) \w是[0-9a-zA-Z_],+代表1个以上,所以整体就是匹配一个标识符,外面的()代表加入到分组,现在是加入到分组1,\s\=\s,\s是匹配空格或者制表符,注意这里的\=匹配的是普通的=,在very magic中=是有特殊意义的。然后\d+,\d是[0-9]的意思,这就是在匹配一个数字,这里的+用的很巧妙,因为数字可能不止一位。最后的,?意思是: 逗号出现了0次或者1次,因为在enum中最后一个元素可能没有逗号所以这么写。
再看替换规则
case \1: break;
这里面\1是引用刚才的分组,也就是把每行的标识符给拿了过来,其他的都是普通的字符串。最后完成了这个枚举成员全部写到switch里面的操作。非常快速,说vim是神之编辑器可以说是一点也不夸张的。
那么这次的技巧就写到这里了,正则表达式可以千变万化,试试针对你的需求自己也写一个练习一下吧!