Vimscript 實例研究:Grep運算符(Operator),第二部分

2018-02-24 16:02 更新

目前為止,我們已經(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之前,你_必須_先保存文件,這樣才能看到變化!

骨架(Skeleton)

要創(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>g2evi(<leader>g看看。 每次Vim都會輸出動作所包括的文本,顯然我們已經(jīng)走上正道了!

讓我們把這段代碼一步步分開來看。首先我們用if語句檢查a:type參數(shù)。如果是'v', 它就是使用在字符寬度的visual模式下,所以我們復制了可視模式下的選中文本。

注意我們使用大小寫敏感比較==#。如果我們只用了==而用戶設(shè)置ignorecase,?"V"也會是匹配的,結(jié)果_不會_如我們所愿。重視防御性編程!

if語句的第二個分支則會攔住normal模式下使用字符寬度的動作。

剩下的情況只是默默地退出。我們直接忽略行寬度/塊寬度的visual模式和對應的動作類型。 Grep默認情況下不會搜索多行文本,所以在搜索內(nèi)容中夾雜著換行符是毫無意義的。

我們每一個if分支都會執(zhí)行normal!命令來做兩件事:

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

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

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

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

轉(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顯示一個被轉(zhuǎn)義了的能安全地傳遞給shell命令的文本。

執(zhí)行Grep

我們終于可以加上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

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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號