Vimscript Potion段移動

2018-02-24 16:03 更新

既然知道了段移動的工作原理,讓我們重新映射這些命令來使得它們對于Potion文件起作用。

首先我們要決定Potion文件中"段"的意義。 有兩對段移動命令,所以我們可以總結(jié)出兩套組合,我們的用戶可以選擇自己喜歡的一個。

讓我們使用下面兩個組合來決定哪里是Potion中的段:

  1. 任何在空行之后的,第一個字符為非空字符的行,以及文件首行。
  2. 任何第一個字符為非空字符,包括一個等于號,并以冒號結(jié)尾的行。

稍微拓展我們的factorial.pn例子,這就是那些規(guī)則當作段頭的地方:

# factorial.pn                              1
# Print some factorials, just for fun.

factorial = (n):                            1 2
    total = 1

    n to 1 (i):
        total *= i.

    total.

print_line = ():                            1 2
    "-=-=-=-=-=-=-=-\n" print.

print_factorial = (i):                      1 2
    i string print
    '! is: ' print
    factorial (i) string print
    "\n" print.

"Here are some factorials:\n\n" print       1

print_line ()                               1
10 times (i):
    print_factorial (i).
print_line ()

我們的第一個定義更加自由。它定義一個段為一個"頂級的文本塊"。

第二個定義則嚴格一點。它定義一個段為一個函數(shù)定義。

自定義映射

在你的插件的repo中創(chuàng)建ftplugin/potion/sections.vim。 這將是我們放置段移動代碼的地方。記得一旦一個緩沖區(qū)的filetype設(shè)置為potion,這里的代碼就會執(zhí)行。

我們將重新映射全部四個段移動命令,所以繼續(xù)并創(chuàng)建一個骨架:

noremap <script> <buffer> <silent> [[ <nop>
noremap <script> <buffer> <silent> ]] <nop>

noremap <script> <buffer> <silent> [] <nop>
noremap <script> <buffer> <silent> ][ <nop>

Notice that we use?noremap?commands instead of?nnoremap, because we want these to work in operator-pending mode too. That way you'll be able to do things like?d]]?to "delete from here to the next section". 注意我們使用noremap而不是nnoremap,因為我們想要這些命令也能在operator-pending模式下工作。 這樣你就能使用d]]命令來刪除從這到下一段之間的內(nèi)容。

我們設(shè)置映射生效于buffer-local,所以它們只對Potion文件起作用,不會替換全局選項。

我們也設(shè)置了silent,因為用戶不應(yīng)關(guān)心我們實現(xiàn)段移動的細節(jié)。

使用一個函數(shù)

每個命令中實現(xiàn)段移動的代碼會是非常相似的,所以讓我們把它抽象出供映射調(diào)用的一個函數(shù)。

你將在那些創(chuàng)建了一些相似的映射的Vim插件中頻繁看到這種策略。 比起把所有的功能堆砌于各個映射中,這樣做不僅更易讀,而且更易維護。

sections.vim文件中加上下面內(nèi)容:

function! s:NextSection(type, backwards)
endfunction

noremap <script> <buffer> <silent> ]]
        \ :call <SID>NextSection(1, 0)<cr>

noremap <script> <buffer> <silent> [[
        \ :call <SID>NextSection(1, 1)<cr>

noremap <script> <buffer> <silent> ][
        \ :call <SID>NextSection(2, 0)<cr>

noremap <script> <buffer> <silent> []
        \ :call <SID>NextSection(2, 1)<cr>

這里我用到了Vimscript的斷行特性,因為我不想看到又長又臭的代碼。 注意反斜杠是放在第二行前面進行轉(zhuǎn)義的。閱讀:help line-continuation以了解更多。

注意我們使用<SID>和一個腳本本地命名空間內(nèi)定義的函數(shù)來避免污染全局空間。

每個映射簡單地以適當參數(shù)調(diào)用NextSection實現(xiàn)對應(yīng)的移動。 現(xiàn)在我們可以開始實現(xiàn)NextSection了。

基本移動

讓我們考慮下我們的函數(shù)需要做什么。 我們想要移動光標到下一段,而移動光標,有一個簡單的辦法就是利用/?命令。

編輯NextSection成這樣:

function! s:NextSection(type, backwards)
    if a:backwards
        let dir = '?'
    else
        let dir = '/'
    endif

    execute 'silent normal! ' . dir . 'foo' . "\r"
endfunction

現(xiàn)在這個函數(shù)使用我們之前見過的execute normal!來執(zhí)行/foo?foo,取決于backwards的值。 這將是個好的開始。

繼續(xù)前進,我們顯然需要搜索foo以外的東西,是什么則取決于用的是段頭的第一個還是第二個定義。

NextSection改成這樣:

function! s:NextSection(type, backwards)
    if a:type == 1
        let pattern = 'one'
    elseif a:type == 2
        let pattern = 'two'
    endif

    if a:backwards
        let dir = '?'
    else
        let dir = '/'
    endif

    execute 'silent normal! ' . dir . pattern . "\r"
endfunction

現(xiàn)在只需要補上匹配的模式了(pattern),讓我們繼續(xù)完成它吧。

頂級文本段

用下面一行替換掉第一個let pattern = '...'

let pattern = '\v(\n\n^\S|%^)'

如果不理解這個正則表達式是干什么的,請回憶我們正在實現(xiàn)的"段"的定義。

任何在空行之后的,第一個字符為非空字符的行,以及文件首行。

開頭的\v強制切換為"very magic"模式,一如之前的幾次。

剩下的正則表達式由兩個選項組成。第一個,\n\n^\S,搜索"兩個換行符,接著之后是一個非空字符"。 這正好是我們的定義中的第一種情況。

另一個是%^,在Vim中,這是一個代表文件開頭的特殊正則符號。

我們現(xiàn)在已經(jīng)到了嘗試前兩個映射的時機了。 保存ftplugin/potion/sections.vim并在你的Potion例子緩沖區(qū)中執(zhí)行:set filetype=potion。?[[]]命令應(yīng)該可以工作,但會顯得古怪。

搜索標記

你大概注意到了,在段之間移動時光標會位于真正想要移動到的地方上方的空行。 在繼續(xù)閱讀之前,先想想為什么會這樣。

問題在于我們使用/(或?)進行搜索,而在默認情況下Vim會把光標移動到匹配開始處。 舉個例子,當你執(zhí)行/foo光標會位于foo中的f。

為了讓Vim把光標移動到匹配結(jié)束處而不是開始處,我們可以使用搜索標記(search flag)。 試試在Potion文件中這么搜索:

/factorial/e

Vim將找到factorial并帶你到那。按下幾次n來在匹配處之間移動。?e標記將使得Vim把光標移動到到匹配結(jié)束處而不是開始處。在另一個方向也試試:

?factorial?e

讓我們來修改我們的函數(shù),用搜索標記來放置光標到匹配的段頭的另一端。

function! s:NextSection(type, backwards)
    if a:type == 1
        let pattern = '\v(\n\n^\S|%^)'
        let flags = 'e'
    elseif a:type == 2
        let pattern = 'two'
        let flags = ''
    endif

    if a:backwards
        let dir = '?'
    else
        let dir = '/'
    endif

    execute 'silent normal! ' . dir . pattern . dir . flags . "\r"
endfunction

我們這里改動了兩處。首先,我們依照段移動的類型設(shè)置flags變量的值。 現(xiàn)在我們僅需處理第一種情況,所以設(shè)置了標記e

其次,我們在搜索字符串中連接dirflags。這將依照我們搜索的方向加入?e/e。

保存文件,切換回Potion示例文件,并執(zhí)行:set ft=potion來讓改動生效。 現(xiàn)在嘗試[[]]來看看我們的成果吧!

函數(shù)定義

是時候處理我們對"段"的第二個定義了,幸運的是這個比起第一個簡單多了。 重新說一下我們需要實現(xiàn)的定義:

任何第一個字符為非空字符,包括一個等于號,并以冒號結(jié)尾的行。

我們可以使用一個簡單的正則表達式來查找這樣的行。 修改函數(shù)中第二個let pattern = '...'成這樣:

let pattern = '\v^\S.*\=.*:$'

這個正則表達式比上一個沒那么嚇人多了。我把指出它是怎么工作的任務(wù)作為你的練習 -- 它只是我們的定義的一個直白的翻譯。

保存文件,在factorial.pn處執(zhí)行:set filetype=potion,然后試試新的][[]映射。它們應(yīng)該能如期工作。

在這里我們不需要搜索標記,因為默認的移動到匹配處開頭正是我們想要的。

可視模式

我們的段移動命令在normal模式下一切正常,但要讓它們也能在visual模式下工作,我們還需要增加一些東西。 首先,把函數(shù)改成這樣:

function! s:NextSection(type, backwards, visual)
    if a:visual
        normal! gv
    endif

    if a:type == 1
        let pattern = '\v(\n\n^\S|%^)' 
        let flags = 'e'
    elseif a:type == 2
        let pattern = '\v^\S.*\=.*:$'
        let flags = ''
    endif

    if a:backwards
        let dir = '?'
    else
        let dir = '/'
    endif

    execute 'silent normal! ' . dir . pattern . dir . flags . "\r"
endfunction

Two things have changed. First, the function takes an extra argument so it knows whether it's being called from visual mode or not. Second, if it's called from visual mode we run?gv?to restore the visual selection. 兩個地方改變了。首先,函數(shù)接受的參數(shù)多了一個,這樣它能知道自己是否是在visual模式下調(diào)用的。 其次,如果它是在visual模式下調(diào)用的,我們執(zhí)行gv來恢復(fù)可視選擇區(qū)域。

為什么我們要這么做?來,讓我展示給你看。 在visual模式下隨意選擇一些文本并執(zhí)行下面命令:

:echom "hello"

Vim將顯示hello,但可視模式下選擇的范圍也隨之清空!

當用:執(zhí)行一個ex模式下的命令,可視選擇的范圍總會被清空。?gv命令重新選擇之前的可視選擇范圍,相當于撤銷了清空。 這是個有用的命令,你會在日常工作中因此受益的。

現(xiàn)在我們需要更新前面的映射,傳遞0給新的visual參數(shù):

noremap <script> <buffer> <silent> ]]
        \ :call <SID>NextSection(1, 0, 0)<cr>

noremap <script> <buffer> <silent> [[
        \ :call <SID>NextSection(1, 1, 0)<cr>

noremap <script> <buffer> <silent> ][
        \ :call <SID>NextSection(2, 0, 0)<cr>

noremap <script> <buffer> <silent> []
        \ :call <SID>NextSection(2, 1, 0)<cr>

這里沒什么是過于復(fù)雜的?,F(xiàn)在讓我們加上visual模式映射,作為最后一塊拼圖。

vnoremap <script> <buffer> <silent> ]]
        \ :<c-u>call <SID>NextSection(1, 0, 1)<cr>

vnoremap <script> <buffer> <silent> [[
        \ :<c-u>call <SID>NextSection(1, 1, 1)<cr>

vnoremap <script> <buffer> <silent> ][
        \ :<c-u>call <SID>NextSection(2, 0, 1)<cr>

vnoremap <script> <buffer> <silent> []
        \ :<c-u>call <SID>NextSection(2, 1, 1)<cr>

這些映射都設(shè)置visual參數(shù)的值為1,來告訴Vim在移動之前重新選擇上一次的選擇范圍。 這里也用到了我們在Grep Operator那幾章學到的<c-u>技巧。

保存文件,在Potion文件中set ft=potion,大功告成!嘗試一下你的新映射吧。 像v]]d[]這樣的命令現(xiàn)在應(yīng)該可以正常地工作了。

我們得到了什么?

這是冗長的一章,盡管我們只實現(xiàn)了一些看上去簡單的功能,但是你學到了(并充分地練習了)下列有用的知識:

  • 使用noremap而不是nnoremap來創(chuàng)建可以作為移動和動作使用的命令。
  • 在創(chuàng)建相關(guān)聯(lián)的映射時,使用一個單一的接受多個參數(shù)的函數(shù)來簡化你的工作。
  • 逐漸增強一個Vimscript函數(shù)的能力。
  • 動態(tài)地組建一個`execute 'normal! ...'字符串。
  • 結(jié)合正則表達式,使用簡單的搜索來實現(xiàn)移動。
  • 使用特殊的正則符號,比如%^(文件開頭) 。
  • 使用搜索標記來改變搜索的行為。
  • 實現(xiàn)不會改變可視選擇范圍的visual模式映射

堅持下并完成練習(不過是閱讀一些文檔),然后賞自己一些冰激凌。你值得擁有!

練習

閱讀:help search()。這是一個值得了解的函數(shù),不過你也可以使用跟/?列在一起的標記。

閱讀:help ordinary-atom來認識能在搜索模式(pattern)中用到的更多有趣的東西。

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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號