Vimscript 實(shí)例研究:Grep運(yùn)算符(Operator),第二部分

2018-02-24 16:02 更新

目前為止,我們已經(jīng)完成了一個(gè)原型,是時(shí)候擴(kuò)充它,讓它更加強(qiáng)大。

記?。何覀兂跏寄繕?biāo)是創(chuàng)建"grep運(yùn)算符"。我們還需要做一大堆新的東西來達(dá)成目標(biāo), 但要像前一章的過程一樣:從簡單的東西開始,并逐步改進(jìn)直到它滿足我們的需求。

在開始之前,注釋掉~/.vimrc中在前一章創(chuàng)建的映射。我們還要用同樣的快捷鍵來映射新的運(yùn)算符。

新建一個(gè)文件

創(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之前,你_必須_先保存文件,這樣才能看到變化!

骨架(Skeleton)

要?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)。

動(dòng)作類型

我們定義的函數(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ì)于我們選擇需要搜索的詞是很重要的。

復(fù)制文本

我們的函數(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>g2evi(<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!命令來做兩件事:

  • 在可視狀態(tài)下選中我們想要的文本范圍:
    • 先移動(dòng)到范圍開頭,并標(biāo)記
    • 進(jìn)入字符寬度的visual模式
    • 移動(dòng)到范圍結(jié)尾的標(biāo)記
  • 復(fù)制可視狀態(tài)下選中的文本。

先不要糾結(jié)于特殊標(biāo)記方式。你將會(huì)在完成本章結(jié)尾的練習(xí)時(shí)學(xué)到為什么它們會(huì)不一樣。

函數(shù)的最后一行輸出變量@@。不要忘了以@開頭的變量是寄存器。@@是"未命名"(unnamed)寄存器: 如果你在刪除或復(fù)制文本時(shí)沒有指定一個(gè)寄存器,Vim就會(huì)把文本放在這里。

簡明扼要地說:我們選中要搜索的文本,復(fù)制它,然后輸出被復(fù)制的文本。

轉(zhuǎn)義搜索文本

既然得到了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命令的文本。

執(zhí)行Grep

我們終于可以加上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)存的名詞和形容詞搭配起來。

練習(xí)

閱讀:help visualmode()。

閱讀:help c_ctrl-u。

閱讀:help operatorfunc。

閱讀:help map-operator。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)