Vimscript编程指南

实例分析:grep操作符,第二部分

现在我们已经有了最终方案的基本雏形了,现在是时候来让它变功能更加强大了。

记住我们最开始的目标是创建一个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 %来加载它。通过按键giw来触发“单词内搜索”的功能。Vim会在接受到iw动作的时候输出“Test”,这说明我们的基本框架已经搭建好了。

这个函数很简便,并且没有什么是我们以前没有接触到的,但是这个映射是有一点点复杂的。首先我们operatorfunc选项设置成我们的函数,然后然后g@,这样会把这个函数当作操作符来调用。这个看起来有点晕,但是实际上就是这么运行的。

现在可以把这个映射当作一个很盒子来看待。后面你可以仔细研究一下相关的文档。

Visual模式

我们把操作符加入到normal模式里了,但是也想在visual模式下去使用它。这之前的映射下面再添加一个映射:


     vnoremap g :call GrepOperator(visualmode())

写入并且加载这个文件,现在在visual模式下选中一些文本,并且按g。什么都没有发生,但是vim输出了“Test”,所以这说明我们的函数被调用了。

我们之前已经看到过很多次,但是从来没有解释它的作用。试着在visual模式下选中一些文本,然后输入:。vim这时会显示一个命令行,但是和一般情况不同的是它会在命令行的开始处填入'<,'>。

Vim这样做是为了方便你,插入这些文本的作用是为了让你将要运行的命令作用在选择的文本上。但是在这里,这个功能不起任何作用。我们用来告诉vim删除从光标到行首的文笔,来删除这些文本。这样就会只剩下一个:,接下来就可以用call调用函数了。

这里的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

加载这个文件,通过不同的方式来试试这个函数。一些示例的输出如下面所示:

  • 按viw 会输出v,因为我们在chacterwise模式
  • 按Vjj 会输出V,因为我们在linewise模式
  • giw输出char,因为我们用了一个characterwise动作。
  • gG 输出line,因为我们对这个操作符用了一个linewise动作。

现在我们知道怎么来分辨不同的动作类型了,这个在我们选择搜索的文本的时候是很重要的。

复制文本

我们的函数需要能够操作用户想要去搜索的文本,做简单的方式就是去复制它。编辑函数,使得它的内容如下:


     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     

现在这个函数里多了很多新的内容。试试类似于giw,g2e和vi()g等命令。每次vim会输出当前动作所包含的文本,很显然,我们的功能在慢慢完善。

我们来分解一下上面的新代码。首先我们有一个if语句,它会根据a:type的不同值执行不同的命令。如果当前的type是v,也就是说这个函数是从characterwise的visual模式下调用的,所以我们就通过命令来复制visual模式下选中的文本。

注意我们用的是大小写敏感的比较符号==#。如果我们只是用==来进行比较,并且用户设置了ignorecase选项的话,就会导致'V'也被匹配上,这不是我们想要的结果。防御式编程!

第二个if语句会在操作符在visual下用一个characterwise动作调用的时候执行。

最后一个分支直接返回了。我们显示地忽略了linewise/blockwise visual模式和linewise/blockwise动作。Grep默认不支持跨行的搜索,所以在搜索的文本里带入一个换行符是没有意义的。

上面的两个if语句都执行了一个normal!命令,它们做了两件事:

  • 在visual模式下选择我们要的文本范围:
    • 移动到文本范围开始的标志处
    • 进入characterwise visual模式
    • 移动到文本范围结尾的标志处
  • 复制选中的文本

暂时不用考虑上面用到的特殊标志。在完成了本章的练习后,你会明白为什么要用不同的命令来做同样的操作。

函数的最后一行输出变量@@。记住,以@开始的变量都是寄存器。@@是无名寄存器,它就是在没有指定寄存器进行复制和删除操作时,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模式下选择一段带有特殊字符的文笔,然后输入g>。Vim会输出选中文本转义后的内容,以便作为参数传递给shell命令。

运行Grep

现在我们就只差添加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.