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