目前為止,我們已經完成了一個原型,是時候擴充它,讓它更加強大。
記住:我們初始目標是創建"grep運算符"。我們還需要做一大堆新的東西來達成目標, 但要像前一章的過程一樣:從簡單的東西開始,并逐步改進直到它滿足我們的需求。
在開始之前,注釋掉`~/.vimrc`中在前一章創建的映射。我們還要用同樣的快捷鍵來映射新的運算符。
## 新建一個文件
創建一個新的運算符需要許多命令,把它們手工打出來將很快變成一種折磨。 你可以把它附加到`~/.vimrc`,但讓我們為這個運算符創建一個獨立的文件。我們有足夠的必要這么做。
首先,找到你的Vim`plugin`文件夾。在Linux或OS X,這將會是`~/.vim/plugin`。 如果你是Windows用戶,它將位于你的主目錄下的`vimfiles`文件夾。(如果你找不到,在Vim里使用`:echo $HOME命令) 如果這個文件夾不存在,創建一個。
在`plugin/`下新建文件`grep-operator.vim`。這就是你放置新運算符的代碼的地方。 一旦文件被修改,你可以執行`:source %`來重新加載代碼。 每次你打開Vim,這個文件也會被重新加載,就像`~/.vimrc`。
不要忘了,在你source之前,你_必須_先保存文件,這樣才能看到變化!
## 骨架(Skeleton)
要創建一個新的Vim運算符,你需要從兩個組件開始:一個函數還有一個映射。 先添加下面的代碼到`grep-operator.vim`:
~~~
nnoremap <leader>g :set operatorfunc=GrepOperator<cr>g@
function! GrepOperator(type)
echom "Test"
endfunction
~~~
保存文件并用`:source %`source它。嘗試通過按下`<leader>giw`來執行"grep整個詞"。 Vim將在接受`iw`動作(motion)后,輸出`Test`,意味著我們已經搭起了骨架。
函數部分是簡單的,沒有什么是我們沒講過的。不過映射部分比較復雜。 我們首先對函數設置了`operatorfunc`選項,然后執行`g@`來以運算符的方式調用這個函數。 看起來這有點繞,不過這就是Vim工作的原理。
暫時把這個映射看作黑魔法吧。稍后你可以到文檔里一探究竟。
## 可視模式
我們已經在normal模式下加入了這個運算符,但還想要在visual模式下用到它。 在之前的映射下面添加多一個:
~~~
vnoremap <leader>g :<c-u>call GrepOperator(visualmode())<cr>
~~~
保存并source文件。現在在visual模式下選擇一些東西并按下`<leader>g`。 什么也沒發生,但Vim確實輸出了`Test`,所以我們的函數已經運行了。
之前我們就見過`<c-u>`,但是還沒有解釋它是做什么的。試一下在可視模式下選中一些文本并按下`:`。 Vim將打開一個命令行就像平時按下了`:`一樣,但是命令行的開頭自動添加了`'<,'>`!
Vim為了提高效率,插入了這些文本來讓你的命令在被選擇的范圍內執行。 但是這次,我們不需要它添倒忙。我們用`<c-u>`來執行"從光標所在處刪除到行首的內容",移除多余文本。 最后剩下一個孤零零的`:`,為調用`call`命令作準備。
我們傳遞過去的`visualMode()`參數還沒有講過呢。 這個函數是Vim的內置函數,它返回一個單字符的字符串來表示visual模式的類型:?`"v"`代表字符寬度(characterwise),`"V"`代表行寬度(linewise),`Ctrl-v`代表塊寬度(blockwise)。
## 動作類型
我們定義的函數接受一個`type`參數。我們知道在visual模式下它將會是`visualmode()`的返回值, 但是在normal模式下呢?
編輯函數體部分,讓代碼像這樣:
~~~
nnoremap <leader>g :set operatorfunc=GrepOperator<cr>g@
vnoremap <leader>g :<c-u>call GrepOperator(visualmode())<cr>
function! GrepOperator(type)
echom a:type
endfunction
~~~
Source文件,然后繼續并用多種的方式測試它。你可能會得到類似下面的結果:
* 按下`viw<leader>g`顯示`v`,因為我們處于字符寬度的visual模式。
* 按下`Vjj<leader>g`顯示`V`,因為我們處于行寬度的visual模式。
* 按下`<leader>giw`顯示`char`,因為我們在字符寬度的動作(characterwise motion)中使用該運算符。
* 按下`<leader>gG`顯示`line`,因為我們在行寬度的動作(linewise motion)中使用該運算符。
現在我們已經知道怎么區分不同種類的動作,這對于我們選擇需要搜索的詞是很重要的。
## 復制文本
我們的函數將需要獲取用戶想要搜索的文本,而這樣做最簡單的方法就是復制它。 把函數修改成這樣:
~~~
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>g2e`和`vi(<leader>g`看看。 每次Vim都會輸出動作所包括的文本,顯然我們已經走上正道了!
讓我們把這段代碼一步步分開來看。首先我們用`if`語句檢查`a:type`參數。如果是`'v'`, 它就是使用在字符寬度的visual模式下,所以我們復制了可視模式下的選中文本。
注意我們使用大小寫敏感比較`==#`。如果我們只用了`==`而用戶設置`ignorecase`,?`"V"`也會是匹配的,結果_不會_如我們所愿。重視防御性編程!
`if`語句的第二個分支則會攔住normal模式下使用字符寬度的動作。
剩下的情況只是默默地退出。我們直接忽略行寬度/塊寬度的visual模式和對應的動作類型。 Grep默認情況下不會搜索多行文本,所以在搜索內容中夾雜著換行符是毫無意義的。
我們每一個`if`分支都會執行`normal!`命令來做兩件事:
* 在可視狀態下選中我們想要的文本范圍:
* 先移動到范圍開頭,并標記
* 進入字符寬度的visual模式
* 移動到范圍結尾的標記
* 復制可視狀態下選中的文本。
先不要糾結于特殊標記方式。你將會在完成本章結尾的練習時學到為什么它們會不一樣。
函數的最后一行輸出變量`@@`。不要忘了以`@`開頭的變量是寄存器。`@@`是"未命名"(unnamed)寄存器: 如果你在刪除或復制文本時沒有指定一個寄存器,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
~~~
保存并source文件,然后在可視模式下選中帶特殊字符的文本,按下`<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`命令里,現在代碼甚至更加清晰易懂了!
保存并source文件,然后嘗試一下,享受自己辛勤勞動的成果吧!
因為定義了一個全新的Vim運算符,現在我們可以在許多場景下使用它了,比如:
* `viw<leader>g`: 可視模式下選中一個詞,然后grep它。
* `<leader>g4w`: Grep接下來的四個詞。
* `<leader>gt;`: Grep到分號為止的文本。
* `<leader>gi[`: Grep方括號里的文本.
這里彰顯了Vim的優越性:它的編輯命令就像一門語言。當你加入新的動詞,它會自動地跟(大多數)現存的名詞和形容詞搭配起來。
## 練習
閱讀`:help visualmode()`。
閱讀`:help c_ctrl-u`。
閱讀`:help operatorfunc`。
閱讀`:help map-operator`。
- 前言
- 鳴謝
- 預備知識
- 打印信息
- 設置選項
- 基本映射
- 模式映射
- 精確映射
- Leaders
- 編輯你的Vimrc文件
- Abbreviations
- 更多的Mappings
- 鍛煉你的手指
- 本地緩沖區的選項設置和映射
- 自動命令
- 本地緩沖區縮寫
- 自動命令組
- Operator-Pending映射
- 更多Operator-Pending映射
- 狀態條
- 負責任的編碼
- 變量
- 變量作用域
- 條件語句
- 比較
- 函數
- 函數參數
- 數字
- 字符串
- 字符串函數
- Execute命令
- Normal命令
- 執行normal!
- 基本的正則表達式
- 實例研究:Grep 運算符(Operator),第一部分
- 實例研究:Grep運算符(Operator),第二部分
- 實例研究:Grep運算符(Operator),第三部分
- 列表
- 循環
- 字典
- 切換
- 函數式編程
- 路徑
- 創建一個完整的插件
- 舊社會下的插件配置方式
- 新希望:用Pathogen配置插件
- 檢測文件類型
- 基本語法高亮
- 高級語法高亮
- 更高級的語法高亮
- 基本折疊
- 高級折疊
- 段移動原理
- Potion段移動
- 外部命令
- 自動加載
- 文檔
- 發布
- 還剩下什么?