现在我们已经有了最终方案的基本雏形了,现在是时候来让它变功能更加强大了。
记住我们最开始的目标是创建一个grep操作符。为了达到这个目标,我们还有很多需要去做的,但是这里我们还是要和上一章节一样,先从一些简单的地方开始,然后把它变成我们所要的结果。
在开始之前,我们先要把之前在vimrc文件里添加的映射给删除掉,因为接下来我们还是要用相同的按键来做映射。
创建一个操作符需要很多的命令,如果手动去输入的话,就比较麻烦了。你也可以把这些命令添加到你的vimrc文件里,但是这里我们用一个单独的文件来存放这些命令。用单独的文件的话就更便于维护了。
首先,找到你的Vim plugin目录。在Linux或者OS X系统下,这个目录在~/.vim/plugin。如果是windows系统,它会再你的home目录下的vimfiles里(如果你不清楚的话,可以用在vim里用:echo $HOME来查看)。如果这个目录不存在,你可以直接创建一个。
在plugin目录里创建一个grep-operator.vim的文件。在这个文件里会放关于这个新操作符的命令。当你编辑完这个文件后,可以用:source %来重新加载它,使得新编辑的代码生效。这个文件也会在你每次打开vim的时候自动加载,就像~/.vimrc文件一样。
记住每次加载之前要先写入文件,才能看得到效果!
要写一个新的vim操作符,你需要从两个组件开始:一个函数和一个映射。首先把下面的代码加入到grep-operator.vim里:
nnoremap g :set operatorfunc=GrepOperatorg@
function! GrepOperator(type)
echom "Test"
endfunction
写入该文件,并且用:source %来加载它。通过按键
这个函数很简便,并且没有什么是我们以前没有接触到的,但是这个映射是有一点点复杂的。首先我们operatorfunc选项设置成我们的函数,然后然后g@,这样会把这个函数当作操作符来调用。这个看起来有点晕,但是实际上就是这么运行的。
现在可以把这个映射当作一个很盒子来看待。后面你可以仔细研究一下相关的文档。
我们把操作符加入到normal模式里了,但是也想在visual模式下去使用它。这之前的映射下面再添加一个映射:
vnoremap g :call GrepOperator(visualmode())
写入并且加载这个文件,现在在visual模式下选中一些文本,并且按
我们之前已经看到过很多次
Vim这样做是为了方便你,插入这些文本的作用是为了让你将要运行的命令作用在选择的文本上。但是在这里,这个功能不起任何作用。我们用
这里的call GrepOperator()和之前我们所见到的函数调用一样,不过这里我们用了一个visualmode()函数来作为参数,这个之前是没有见过的。这是一个vim的内置函数,它会返回一个单字符的字符串,来代表visual模式的上一次输入:‘v’代表characterwise,“V”代表linewise,ctrl-v代表blockwise。
我们之前定义的函数会接收一个type参数。我们知道当在visual模式下的时候,这个参数是visualmode()函数的返回值,那么在normal模式下作为一个operator运行时的参数又是什么呢?
编辑函数,使得和下面的内容相同:
nnoremap g :set operatorfunc=GrepOperatorg@
vnoremap g :set operatorfunc=GrepOperator
function! GrepOperator(type)
echom a:type
endfunction
加载这个文件,通过不同的方式来试试这个函数。一些示例的输出如下面所示:
现在我们知道怎么来分辨不同的动作类型了,这个在我们选择搜索的文本的时候是很重要的。
我们的函数需要能够操作用户想要去搜索的文本,做简单的方式就是去复制它。编辑函数,使得它的内容如下:
nnoremap g :set operatorfunc=GrepOperatorg@
vnoremap g :call GrepOperator
function! GrepOperator(type)
if a:type ==# 'v'
execute "normal! `v`y"
elseif a:type ==# 'char'
execute "normal! `[v`]y"
else
return
endif
echom @@
endfunction
现在这个函数里多了很多新的内容。试试类似于
我们来分解一下上面的新代码。首先我们有一个if语句,它会根据a:type的不同值执行不同的命令。如果当前的type是v,也就是说这个函数是从characterwise的visual模式下调用的,所以我们就通过命令来复制visual模式下选中的文本。
注意我们用的是大小写敏感的比较符号==#。如果我们只是用==来进行比较,并且用户设置了ignorecase选项的话,就会导致'V'也被匹配上,这不是我们想要的结果。防御式编程!
第二个if语句会在操作符在visual下用一个characterwise动作调用的时候执行。
最后一个分支直接返回了。我们显示地忽略了linewise/blockwise visual模式和linewise/blockwise动作。Grep默认不支持跨行的搜索,所以在搜索的文本里带入一个换行符是没有意义的。
上面的两个if语句都执行了一个normal!命令,它们做了两件事:
暂时不用考虑上面用到的特殊标志。在完成了本章的练习后,你会明白为什么要用不同的命令来做同样的操作。
函数的最后一行输出变量@@。记住,以@开始的变量都是寄存器。@@是无名寄存器,它就是在没有指定寄存器进行复制和删除操作时,vim存放文本的寄存器。
简而言之:我们选中了要搜索的文本,复制它,并且粘贴它。
现在我们已经有了我们需要的文本,接下来我们需要像在前面一章一样,对它进行转义。把echom命令修改成下面所示:
nnoremap g :set operatorfunc=GrepOperatorg@
nnoremap g :call GrepOperator
function! GrepOperator(type)
if a:type ==# 'v'
execute "normal! `v`y"
elseif a:type ==# 'char'
execute "normal! `[v`]y"
else
return
endif
echom shellescape(@@)
endfunction
写入文件,并加载它。试着在visual模式下选择一段带有特殊字符的文笔,然后输入
现在我们就只差添加grep!命令了,加上它就可以进行搜索的动作了。替换echom命令:
nnoremap g :set operatorfunc=GrepOperatorg@
vnoremap g :call GrepOperator(visualmode())
function! GrepOperator(type)
if a:type ==# 'v'
normal! `y
elseif a:type ==# 'char'
normal! `[v`]y
else
return
endif
silent execute "grep! -R " . shellescape(@@) . " ."
copenendfunction
现在看起来就有点熟悉了。我们只是在后面执行了上一章的“silenct execute "grep! ...”命令。现在这个命令的可读性更高了,因为我们不再是把所有的内容都堆在一行里了。
写入文件,并加载。试试这个操作符,并且享受一下你的劳动成果。
因为我们已经定义了一个新的操作符,所以我们可以用不同的方式来使用它,例如:
viwg: 在visual模式下选择一个单词,并且搜索它
g4w: 搜索接下来的四个单词
gt;: 搜索直到分号
gi[: 搜索方括号里的内容
这个强调了vim里最有用的东西:它的编辑命令就像一门语言。当你添加一个新的动词,它就能自动地和大部分的已存的名词和形容词结合起来。
练习
- Read :help visualmode().
- Read :help c_ctrl-u.
- Read :help operatorfunc.
- Read :help map-operator.