从本章开始,我们将会来构造一些很复杂的Vimscript。我们将会讨论一些我们之前没有看到过的东西,并且把它们和我们之前学习的东西结合起来使用。
在你学习这些例子的时候,如果碰到不熟悉的东西,记得用:help命令来查看一下。如果你没有完全理解所有的东西的话,你就不会学到很多东西的。
如果你从来没有用过:grep,你需要花几分钟时间来阅读一下:help :grep和:help :make。如果你没有用过快速补全窗口,那么也阅读一下:help quickfix-window。
简单的来说,:grep ...调用外部命令来处理你输入的参数,解析返回结果,并填充在快速补全窗口里,以方便在vim里使用。
在我们的例子里,为了方便调用,我们会添加一个“grep操作符”,使得你能够在任何vim的内置动作来选择你要搜索的文本。
当你在写一段不简单的Vimscript时,你首先要考虑的事情是:“这个功能会被怎么使用呢?”。试着去想出一个对你以及你的代码的使用者而言更加流畅,更加舒服,更加直观的方式。
在这个例子里,我会帮你做上门那一步:
一些你可能会使用这个功能的例子有:
viwe
当然,还有其他很多可以用到这个的地方。看起来实现这些功能需要写很多代码,其实我们只要实现这个操作符,其他的东西,vim会帮我们处理的。
当你需要写一些棘手的Vimscript时,一个比较好的方式就是,先简化你的目标,并且实现它来让你对你的最终目标有个基本的了解。
让我们把目标简化为“创建一个映射来搜索当前光标下的单词”。这个也很有用,并且也比较简单,这样我们就能够更快的运行我们的代码。我们会暂时把这个命令映射为
我们会先从这个脚本的基本模型开始,然后再慢慢完善它。运行下面的命令:
:nnoremap g :grep -R something .
如果你阅读过:help grep的话,那么这个命令就很容易去理解了。我们之前已经看过很多映射了,这个就没什么新的东西了。
很显然这个还没有完成我们的功能,所以我们需要继续完善它以至于能够达到我们简化的目标。
首先,我们要搜索的是当前光标下的单词,而非是someting。运行下面的命令:
:nnoremap g :grep -R .
现在试试它。
你可以用
:nnoremap g :grep -R .
现在把你的光标放在类似于“foo-bar”的文本上,再试试上面的命令。vim会搜索“foo-bar”,而不只是这个文本的一部分。
但是这里还有另外一个问题,如果选择的文本里有shell的关键词的话,vim也是会传过去的,但是这样会出问题的(比较坏的情况是做一些很可怕的事情)。
你可以去试试这样做,以加深印象。在一个文件里输入foo;ls,保持光标在这个文本串上,然后运行上面那个映射。这时,grep命令会运行失败,但是vim会同时运行一个ls命令。很显然,如果是其他比ls更加危险的命令的话,那就比较可怕了。
我们可以通过给grep的参数加上引号来解决这个问题。运行下面的命令:
:nnoremap g :grep -R '' .
大部分的shell也会把单引号里的字符串当作常量来处理,所以我们的映射就更加健壮了。
但是,对于搜索的内容,这里还存在一个问题。试着在“that's”文本上使用这个映射。它不起作用,因为搜索文本里的单引号对grep命令的单引号造成了影响。
为了解决这个问题,我们可以用Vim的shellescape函数。阅读:help escape()和:help shellescape()来看看它是如何工作的(这个非常简单)。
因为shellescape()函数只对vim字符串有效,所以我们需要动态拼接命令和execute。首先运行下面的命令来把:grep映射成:execute "..."的形式:
:nnoremap g :execute "grep -R '' ."
试试上面的命令,保证它还能够正常工作。如果不行的话,找到输错的地方并进行纠正。然后运行下面的命令,这个命令里用shellescape函数来修复搜索内容上的问题:
:nonoremap g :execute "grep -R " . shellescape("") . " ."
试着对一个正常的单词例如“foo”执行上面的命令,它很好地起了作用。再试试一个带引号的单词,例如“that's”。它又失效了,到底是怎么一回事呢?
这是因为Vim会在特殊字符串被替换之前就先进行了shellescape()函数。所以Vim直接对字符串“
你可以通过下面的命令来验证上面的说法:
:echom shellescape("")
Vim会输出'
可以用expand()函数来修复它,expand函数可以在字符串传给shellescape之前,强制把它转换成它实际上是表示的文本。
我们可以先看看expand函数的功能。把你的光标放在一个带引号的字符串串上,例如“that's”,然后运行下面的命令:
:echom expand("")
Vim会输出“that's”,因为expand("
:echom shellescape(expand(""))
这次Vim会输出'that'\''s'。这个看起来有点复杂,相信你不愿意去理睬shell的复杂语法。现在,不用担心这个,只要相信vim从expand里拿到字符串,并正确的进行了转义即可。
现在我们知道了怎么取得当前光标下的文本,并且进行转移了。现在只需要把它和我们的影射结合起来就可以了!运行下面的命令:
:nnoremap g :execute "grep -R " . shellescape(expand("")) . " ."
再试试看。这次我们的命令不会再因为我们搜索的文本里包含特殊字符而崩溃了。
这个通过一个小的Vimscript,来慢慢进行转换成接近你目标的版本的方法会对你很有用的。
这里还有一些小问题我们需要关注。首先,我们说过,我们不不需要自动跳到第一个匹配的文本上去,我们可以用grep!代替grep来实现它。运行下面的命令:
:nnoremap g :execute "grep! -R " . shellescape(expand("")) . " ."
试试这个命令,不过什么都不会出现。Vim已经在快速补全窗口里填充了结果,但是我们还没有打开它。运行下面的命令:
:nnoremap g :execute "grep -R " . shellescape(expand("")) . " .":copen
在试试上面的命令,你可以看到vim会打开快速补全窗口,并且里面有搜索的结果。我们所做的只是在映射的末尾加上:copen
作为最后一步的工作,我们需要清除在搜索时,vim打印出的所有grep命令的输出。运行下面的命令:
:nnoremap g :silent execute "grep! -R " . shellescape(expand("")) . " ."
现在,我们的工作都完成了,现在试一试。silient命令的功能是只运行它后面的命令,但是忽略它所有应该输出的信息。