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