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

现在我们已经得到了一个解决方案的初步框架,是时候给它填充更多能量了。

记住:我们最初的目标是创建一个“grep操作符”。为了实现这个,还有一大堆的新东西要讲。 但是像上一章节里一样:我们先从一些简单的内容开始,然后不停的改造,直到成为你想要的。

在我们开始之前,先把 ~/.vimrc 文件中上一章节刚创建的映射注释掉 —— 我们会为新建的操作符绑定同样的按键。

创建一个文件

创建一个操作符需要若干命令,徒手键入这些很快就会令人乏味。 你可以添加到 ~/.vimrc 文件中,但我们还是为这个操作符单独新建一个文件。 它已经足够丰富来独自支撑一个文件了。

首先,找到你 Vim 的 plugin 目录。Linux 或者 OS X 系统里,是在 ~/.vim/plugin。 如果是 Windows 系统,会在用户目录下的 vimrfiles 目录里。 (如果你不确认用户目录在哪,在 Vim 中使用 :echo $HOME 命令查看)。 如果这个目录不存在,那创建一个。

plugin/ 中创建一个名为 grep-operator.vim 的文件。你将在这里编写新的操作符代码。 在编辑文件的同时,你可以随时运行 :source % 来重载代码。 这个文件也会和 ~/.vimrc 一样,在每次打开 Vim 时载入。

记住,你必须先保存文件,再载入执行,这样才能看到更改!

框架

要创建一个新的 Vim 操作符,你需要从两个组件开始:函数和映射。 首先在 grep-operator.vim 文件中添加以下代码:

nnoremap <leader>g :set operatorfunc=GrepOperator<cr>g@

function! GrepOperator(type)
    echom "Test"
endfunction

保存文件,然后用 :source % 载入执行。试着按下 <leader>giw 来表示 “替换里面的单词”。 Vim 会在接收了 iw 移动命令之后回显 Test,这意味着我们已经布置好了框架。

这个函数很简单,也没有什么之前没见过的东西,但是映射就有点复杂了。 首先我们把 operatorfunc 选项设置为这个函数,然后运行 [email protected],会把它作为一个操作符。 这可能感觉有点绕,但 Vim 就是这么运行的。

暂时可以先认为这个映射是种黑魔法。你可以以后再专研详细文档。

可视模式

我们已经把操作符加入到了普通模式,但也想在可视模式下使用。 在之前的映射下面添加另外一个映射:

vnoremap <leader>g :<c-u>call GrepOperator(visualmode())<cr>

保存并载入执行。现在可视化选择一些内容,然后按下 <leader>g。 什么都没有发生,但是 Vim 回显了 Test,那么我们的函数就是被调用了。

我们之前见过这个命令中出现的 <c-u>,但是没有解释过做什么用的。 试试可视化选择一些文本,然后按下 :。 Vim 会像通常一样,打开一个命令行,但这次会在行首的位置自动填入 '<,'>

Vim 会帮忙插入这些文本,保证你的命令会在可视化选择的内容范围中运行。 然而,在这个例子中,我们不需要任何帮助。 我们使用 <c-u> 来表示删除“从光标位置到行首”的所有内容。 这会仅保留一个 :,准备来调用 call 命令。

就像我们之前看到过的一样,call GrepOperator() 只是一个简单的函数调用,但是传入的 visualmode() 是一个新参数。 这是一个内置的 Vim 函数,它会返回一个单字字符串表示最后使用的可视模式的类型: "v" 表示字符模式,"V" 表示行模式,而Ctrl-v` 表示块模式。

移动类型

我们定义的函数接收一个 type 参数。我们知道在可视模式下使用这个操作符时,它会是 visualmode() 的返回结果, 但是如果我们在普通模式下运行,结果会怎样呢?

编辑函数主体后,文件如下:

nnoremap <leader>g :set operatorfunc=GrepOperator<cr>g@
vnoremap <leader>g :<c-u>call GrepOperator(visualmode())<cr>

function! GrepOperator(type)
    echom a:type
endfunction

载入执行文件,然后用不同的方法试试。你可以看看这些例子以及输出:

现在,我们知道了移动类型的不同,这在选择需要搜索的文本时是非常重要的。

复制文本

我们的函数现在需要以某种方式来获取用户想搜索的文本,而最简单的方式就是进行复制。 把函数编辑成如下:

nnoremap <leader>g :set operatorfunc=GrepOperator<cr>g@
vnoremap <leader>g :<c-u>call GrepOperator(visualmode())<cr>

function! GrepOperator(type)
    if a:type ==# 'v'
        execute "normal! `<v`>y"
    elseif a:type ==# 'char'
        execute "normal! `[v`]y"
    else
        return
    endif

    echom @@
endfunction

哇。有好多新东西。试试按下 <leader>giw<leader>g2evi(<leader>g。 每次 Vim 都会显示移动命令所覆盖的文本,那么毫无疑问我们已经取得进展了!

让我们把这些新代码一步一步的分解来看。首先,是一个 if 语句,用来检查 a:type 参数。 如果类型是 'v',意味着它就是在字符可视模式中被调用的,然后我们需要做些工作来复制可视化选择的文本。

注意,我们使用了大小写敏感的比较符 ==#。 如果是使用了普通的 ==,并且用户也设置了 ignorecase,它也会匹配 "V",而这不是我们期望的。 这就是防御性编程!

if 的第二种情况会在操作符从普通模式下调用并进行字符移动时触发。

最后一种情况只有返回。我们故意忽略了行/块可视模式和行/块移动。 grep 默认不会跨行搜索,所以在搜索模式中有换行符没有任何意义!

两种 if 情况都运行了一个 normal! 命令,它能完成如下两件事:

目前不用担心出现的特殊标签。你会在完成本章节后面的练习之后明白它们为什么不同。

函数的最后一行回显变量 @@。记住,以 @ 开始的变量是寄存器。 @@ 是“无名”寄存器:如果没有指定具体寄存器,Vim 会把复制或删除的文本寄存在这里。

简而言之:我们选择搜索一段文本,复制它,然后回显复制的文本。

转义搜索项

由于我们已经从 Vim 字符串中获取了需要的文本,这时就可以将它进行转义,就像以前章节中做过的那样。 修改 echom 命令,像下面这样:

nnoremap <leader>g :set operatorfunc=GrepOperator<cr>g@
vnoremap <leader>g :<c-u>call GrepOperator(visualmode())<cr>

function! GrepOperator(type)
    if a:type ==# 'v'
        normal! `<v`>y
    elseif a:type ==# 'char'
        normal! `[v`]y
    else
        return
    endif

    echom shellescape(@@)
endfunction

保存并载入执行,试试可视化选择一些带有特殊字符的文本,然后按下 <leader>g。 Vim 会显示已选择文本的一个适合用于 shell 命令的版本。

运行 grep

最后,我们已经准备好添加 grep! 命令,它会执行正真的搜索。 把 echom 那一行替换掉,然后代码如下:

nnoremap <leader>g :set operatorfunc=GrepOperator<cr>g@
vnoremap <leader>g :<c-u>call GrepOperator(visualmode())<cr>

function! GrepOperator(type)
    if a:type ==# 'v'
        normal! `<v`>y
    elseif a:type ==# 'char'
        normal! `[v`]y
    else
        return
    endif

    silent execute "grep! -R " . shellescape(@@) . " ."
    copen
endfunction

这应该看起来很眼熟。上一章节中,我们单独执行过 silent execute "grep! ..." 命令。 这里甚至更易读,因为我们没有把全部的东西都加入到 nnoremap 命令中!

保存并载入执行文件,然后试试看,尽情享受吧!

既然已经定义了一个全新的 Vim 操作符,那么我们就可以通过各种不同的方式来使用它,例如:

这突显了 Vim 最棒的一个特点:它的编辑命令就像一门语言。 当你添加了一个新的动词,它能自动和(大多数)的名词和形容词结合起来。

练习

阅读 :help visualmode()

阅读 :help c_ctrl-u

阅读 :help operatorfunc

阅读 :help map-operator

原文地址:http://learnvimscriptthehardway.stevelosh.com/chapters/33.html