在本章和下一章中,我們將使用Vimscript來實現一個相當復雜的程序。我們將探討一些聞所未聞的東西, 也將在實戰中把之前學過的東西聯系起來。
在本實例研究中,遇到不熟悉的內容,你得用`:help`弄懂它。如果你只是走馬觀花,就將所獲無多。
## Grep
如果你未曾用過`:grep`,現在你應該花費一分鐘讀讀`:help :grep`和`:help :make`。 如果之前沒用過quickfix window,閱讀`:help quickfix-window`。
簡明扼要地說:`:grep ...`將用你給的參數來運行一個外部的grep程序,解析結果,填充quickfix列表, 這樣你就能在Vim里面跳轉到對應結果。
我們將會添加一個"grep運算符"到任意Vim的內置(或自定義!)的動作中,來選擇想要搜索的文本, 讓`:grep`更容易使用。
## 用法
在寫下每一個有意義的Vimscript程序的第一步,你需要思索一個問題:“它會被用戶怎么使用呢?”。 嘗試構思出一種優雅,簡易,符合直覺的調用方法。
這次我會替你把這活干了:
* 我們將創造一個"grep運算符"并綁定到`<leader>g`。
* 它將表現得同其他任意Vim運算符一樣,還可以加入到組合鍵(比如`w`和`i{`)中。
* 它將立刻開始搜索并打開quickfix窗口展示結果。
* 它將_不會_跳到第一個結果,因為當第一個結果不是你想要的時候,這樣做會困擾你。
一些你將怎么使用它的用例:
* `<leader>giw`: Grep光標下的詞(word)。
* `<leader>giW`: Grep光標下的詞的大寫形式(WORD)。
* `<leader>gi'`: Grep當前所在的單引號括住的詞。
* `viwe<leader>g`: 可視狀態下選中一個詞并拓展選擇范圍到下一詞,然后Grep。
有很多,_很多_其他的方法可以用它。看上去它好像需要寫很多,很多代碼, 但事實上我們只需要實現"運算符"功能然后Vim就會完成剩下的工作。
## 一個原型
在埋頭寫下巨量(trickey bits)的Vimscript之前,有一個也許會幫上忙的方法是簡化你的目標并實現_它_, 來推測你最終解決方案可能的"外形"。
讓我們簡化我們的目標為"創造一個映射來搜索光標下的詞"。這有用而且應該更簡單,所以我們能更快得到可運行的成果。 目前我們將映射它到`<leader>g`。
我們從一個映射骨架開始并逐漸填補它。執行這個命令:
~~~
:nnoremap <leader>g :grep -R something .<cr>
~~~
如果你閱讀過`:help grep`,你就能輕易理解這個命令。我們之前也看過許多映射,這里沒有什么是新的。
顯然我們還沒做什么,所以讓我們一步步打磨這個映射直到它符合我們的要求。
## 搜索部分
首先我們需要搜索光標下的詞,而不是`something`。執行下面的命令:
~~~
:nnoremap <leader>g :grep -R <cword> .<cr>
~~~
現在試一下。`<cword>`是一個Vim的command-line模式的特殊變量, Vim會在執行命令之前把它替換為"光標下面的那個詞"。
你可以使用`<cWORD>`來得到大寫形式(WORD)。執行這個命令:
~~~
:nnoremap <leader>g :grep -R <cWORD> .<cr>
~~~
現在試試把光標放在諸如`foo-bar`的詞上面。Vim將grep`foo-bar`而不是其中的一部分。
我們的搜索部分還有一個問題:如果這里面有什么特殊的shell字符,Vim會毫不猶豫地傳遞給外部的grep命令。 這樣會導致程序崩潰(或更糟:鑄成某些大錯)。
讓我們看看如何使它掛掉。輸入`foo;ls`并把光標放上去執行映射。grep命令失敗了, 而Vim將執行`ls`命令!這肯定糟透了,如果詞里包括比`ls`更危險的命令呢?
為了解決這個問題,我們將調用參數用引號括起來。執行這個命令:
~~~
:nnoremap <leader>g :grep -R '<cWORD>' .<cr>
~~~
大多數shell把單引號括起來的內容當作(大體上)字面量,所以我們的映射現在更加健壯了。
## 轉義Shell命令參數
搜索部分還有一個問題。在`that's`上嘗試這個映射。它不會工作,因為詞里的單引號與grep命令的單引號發生了沖突!
為了解決問題,我們可以使用Vim的`shellescape`函數。 閱讀`:help escape()`和`:help shellescape()`來看它是怎樣工作的(真的很簡單)。
因為`shellescape()`要求Vim字符串,我們需要用`execute`動態創建命令。 首先執行下面命令來轉換`:grep`映射到`:execute "..."`形式:
~~~
:nnoremap <leader>g :execute "grep -R '<cWORD>' ."<cr>
~~~
試一下并確信它可以工作。如果不行,找出拼寫錯誤并改正。 然后執行下面的使用了`shellescape`的命令。
~~~
:nnoremap <leader>g :execute "grep -R " . shellescape("<cWORD>") . " ."<cr>
~~~
在一般的詞比如`foo`上執行這個命令試試。它可以工作。再到一個帶單引號的詞,比如`that's`,上試試看。 它還是不行!為什么會這樣?
問題在于Vim在拓展命令行中的特殊變量,比如`<cWORD>`,的_之前_,就已經執行了`shellescape()`。 所以Vim shell-escaped了字面量字符串`"<cWORD>"`(什么都不做,除了給它添上一對單引號)并連接到我們的`grep`命令上。
通過執行下面的命令,你可以親眼目睹這一切。
~~~
:echom shellescape("<cWORD>")
~~~
Vim將輸出`'<cWORD>'`。注意引號也是輸出字符串的一部分。Vim把它作為shell命令參數保護了起來。
為解決這個問題,我們將使用`expand()`函數來強制拓展`<cWORD>`為對應字符串, 搶在它被傳遞給`shellescape`_之前_。
讓我們單獨看看這一部分是怎么工作的。把你的光標移到帶單引號的詞(比如`that's`)上去, 并執行下面命令:
~~~
:echom expand("<cWORD>")
~~~
Vim輸出`that's`,因為`expand("<cWORD>")`以Vim字符串的形式返回當前光標下的詞。 是時候加入`shellescape`的部分了:
~~~
:echom shellescape(expand("<cWORD>"))
~~~
這次Vim輸出`'that'\''s'`。 如果覺得這看上去真可笑,你大概沒有感受過看透了各種shell轉義的瘋狂形式后的淡定吧。 目前,不用為此而糾結。就相信Vim接受了`expand`的輸出并正確地轉義了它。
目前我們已經得到了光標下的詞的徹底轉義版本。是時候連接它到我們的映射了! 執行下面的命令:
~~~
:nnoremap <leader>g :exe "grep -R " . shellescape(expand("<cWORD>")) . " ."<cr>
~~~
試一下。這個映射不再有問題,即使我們用它搜索帶古怪符號的詞。
"從簡單的Vimscript開始并一點點轉變它直到達成你的目標"這樣的工作方式將會被你一再取用。
## 整理整理
在完成映射之前,還要處理一些小問題。首先,我們說過我們不想自動跳到第一個結果, 所以要用`grep!`替換掉`grep`。執行下面的命令:
~~~
:nnoremap <leader>g :execute "grep! -R " . shellescape(expand("<cWORD>")) . " ."<cr>
~~~
再一次試試,發現什么都沒發生。Vim用結果填充了quickfix窗口,我們卻無法打開。 執行下面的命令:
~~~
:nnoremap <leader>g :execute "grep! -R " . shellescape(expand("<cWORD>")) . " ."<cr>:copen<cr>
~~~
現在試試這個映射,你將看到Vim自動打開了包含搜索結果的quickfix窗口。 我們所做的僅僅是在映射的結尾續上`:copen<cr>`。
最后一點,在搜索的時候,我們要移除Vim所有的grep輸出。執行下面的命令:
~~~
:nnoremap <leader>g :silent execute "grep! -R " . shellescape(expand("<cWORD>")) . " ."<cr>:copen<cr>
~~~
我們完成了,試一試并犒勞一下自己吧!`silent`命令僅僅是在運行一個命令的同時隱藏它的正常輸出。
## 練習
把我們剛剛做出來的映射加入到你的`~/.vimrc`文件。
如果你未曾讀過`:help :grep`,去讀它。
閱讀`:help cword`。
閱讀`:help cnext`和`help cprevious`。修改你的grep映射,試一下它們。
設置`:cnext`和`:cprevious`的映射,讓在匹配內容間的移動更加方便。
閱讀`:help expand`。
閱讀`:help copen`。
在我們創建的映射中加入height參數到`:copen`命令中,看看quickfix窗口能不能以指定的高度打開。
閱讀`:help silent`。
- 前言
- 鳴謝
- 預備知識
- 打印信息
- 設置選項
- 基本映射
- 模式映射
- 精確映射
- Leaders
- 編輯你的Vimrc文件
- Abbreviations
- 更多的Mappings
- 鍛煉你的手指
- 本地緩沖區的選項設置和映射
- 自動命令
- 本地緩沖區縮寫
- 自動命令組
- Operator-Pending映射
- 更多Operator-Pending映射
- 狀態條
- 負責任的編碼
- 變量
- 變量作用域
- 條件語句
- 比較
- 函數
- 函數參數
- 數字
- 字符串
- 字符串函數
- Execute命令
- Normal命令
- 執行normal!
- 基本的正則表達式
- 實例研究:Grep 運算符(Operator),第一部分
- 實例研究:Grep運算符(Operator),第二部分
- 實例研究:Grep運算符(Operator),第三部分
- 列表
- 循環
- 字典
- 切換
- 函數式編程
- 路徑
- 創建一個完整的插件
- 舊社會下的插件配置方式
- 新希望:用Pathogen配置插件
- 檢測文件類型
- 基本語法高亮
- 高級語法高亮
- 更高級的語法高亮
- 基本折疊
- 高級折疊
- 段移動原理
- Potion段移動
- 外部命令
- 自動加載
- 文檔
- 發布
- 還剩下什么?