Vimscript 外部命令

2018-02-24 16:03 更新

Vim遵循UNIX哲學(xué)"做一件事,做好它"。 與其試圖集成你可能想要的功能到編輯器自身,更好的辦法是在適當(dāng)時(shí)使用Vim來(lái)調(diào)用外部命令。

讓我們?cè)诓寮刑砑右恍└鶳otion編譯器交互的命令,來(lái)淺嘗在Vim里面調(diào)用外部命令的方法。

編譯

首先我們將加入一個(gè)命令來(lái)編譯和執(zhí)行當(dāng)前Potion文件。 有很多方法可以實(shí)現(xiàn)這一點(diǎn),不過(guò)我們暫且用外部命令簡(jiǎn)單地實(shí)現(xiàn)。

在你的插件的repo中創(chuàng)建potion/ftplugin/potion/running.vim文件。 這將是我們創(chuàng)建編譯和執(zhí)行Potion文件的映射的地方。

if !exists("g:potion_command")
    let g:potion_command = "potion"
endif

function! PotionCompileAndRunFile()
    silent !clear
    execute "!" . g:potion_command . " " . bufname("%")
endfunction

nnoremap <buffer> <localleader>r :call PotionCompileAndRunFile()<cr>

第一部分以全局變量的形式儲(chǔ)存著用來(lái)執(zhí)行Potion代碼的命令,如果還沒(méi)有設(shè)置過(guò)的話。 我們之前見(jiàn)過(guò)類似的檢查了。

如果potion不在用戶的$PATH內(nèi),這將允許用戶覆蓋掉它, 比如在~/.vimrc添加類似let g:potion_command = "/Users/sjl/src/potion/potion"的一行。

最后一行添加了一個(gè)buffer-local的映射來(lái)調(diào)用我們前面定義的函數(shù)。 不要忘了,由于這個(gè)文件位于ftdetect/potion文件夾,每次一個(gè)文件的filetype設(shè)置成potion,它都會(huì)被執(zhí)行。

真正實(shí)現(xiàn)了功能的地方在PotionCompileAndRunFile()。 保存文件,打開(kāi)factorial.pn并按下<localleader>r來(lái)執(zhí)行這個(gè)映射,看看發(fā)生了什么。

如果potion位于你的$PATH下,代碼會(huì)被執(zhí)行,你應(yīng)該在終端看到輸出(或者在窗口底部,如果你用的是GUI vim)。 如果你看到了沒(méi)有找到potion命令的錯(cuò)誤,你需要像上面提到那樣在~/.vimrc內(nèi)設(shè)置g:potion_command。

讓我們了解一下PotionCompileAndRunFile()的工作原理。

Bang!

:!命令(念作"bang")會(huì)執(zhí)行外部命令并在屏幕上顯示它們的輸出。嘗試執(zhí)行下面的命令:

:!ls

Vim將輸出ls命令的結(jié)果,同時(shí)還有"請(qǐng)按 ENTER 或其它命令繼續(xù)"的提示。

當(dāng)這樣執(zhí)行時(shí),Vim不會(huì)傳遞任何輸入給外部命令。為了驗(yàn)證,執(zhí)行:

:!cat

打入一些行,然后你將看到cat命令把它們都吐回來(lái)了,就像你是在Vim之外執(zhí)行cat。按下Ctrl-D來(lái)結(jié)束。

想要執(zhí)行一個(gè)外部命令并避免請(qǐng)按 ENTER 或其它命令繼續(xù)的提示,使用:silent !。執(zhí)行下面的命令:

:silent !echo Hello, world.

如果在GUI Vim比如MacVim或gVim下執(zhí)行,你將不會(huì)看到Hello,world.的輸出。

如果你在終端下執(zhí)行,你看到的結(jié)果取決于你的配置。 一旦執(zhí)行了一個(gè):silent !,你可能需要執(zhí)行:redraw!來(lái)重新刷新屏幕。

注意這個(gè)命令是:silent !而不是:silent!(看到空格了嗎?)! 這是兩個(gè)不一樣的命令,我們想要的是前者!Vimscript奇妙吧?

讓我們回到PotionCompileAndRun()上來(lái):

function! PotionCompileAndRunFile()
    silent !clear
    execute "!" . g:potion_command . " " . bufname("%")
endfunction

首先我們執(zhí)行一個(gè)silent !clear命令,來(lái)清空屏幕輸出并避免產(chǎn)生提示。 這將確保我們僅僅看到本次命令的輸出,如果一再執(zhí)行同樣的命令,你會(huì)覺(jué)得有用的。

在下一行我們使用老朋友execute來(lái)動(dòng)態(tài)創(chuàng)建一個(gè)命令。建立的命令看上去類似于:

!potion factorial.pn

注意這里沒(méi)有silent,所以用戶將看到命令輸出,并不得不按下enter來(lái)返回Vim。 這就是我們想要的,所以就這樣設(shè)置好了。

顯示字節(jié)碼

Potion編譯器有一個(gè)顯示由它生成的字節(jié)碼的選項(xiàng)。如果你正試圖在非常低級(jí)的層次下debug,這將幫上忙。 在shell里執(zhí)行下面的命令:

potion -c -V factorial.pn

你將看到一大堆像這樣的輸出:

-- parsed --
code ...
-- compiled --
; function definition: 0x109d6e9c8 ; 108 bytes
; () 3 registers
.local factorial ; 0
.local print_line ; 1
.local print_factorial ; 2
...
[ 2] move     1 0
[ 3] loadk    0 0   ; string
[ 4] bind     0 1
[ 5] loadpn   2 0   ; nil
[ 6] call     0 2
...

讓我們添加一個(gè)使用戶可以在新的Vim分割下,看到當(dāng)前Potion代碼生成的字節(jié)碼的映射, 這樣他們能更方便地瀏覽并測(cè)試輸出。

首先,在ftplugin/potion/running.vim底部添加下面一行:

nnoremap <buffer> <localleader>b :call PotionShowBytecode()<cr>

這里沒(méi)有什么特別的 -- 只是一個(gè)簡(jiǎn)單的映射?,F(xiàn)在先描劃出函數(shù)的大概框架:

function! PotionShowBytecode()
    " Get the bytecode.

    " Open a new split and set it up.

    " Insert the bytecode.

endfunction

既然已經(jīng)建立起一個(gè)框架,讓我們把它變成現(xiàn)實(shí)吧。

system()

有許多不同的方法可以實(shí)現(xiàn)這一點(diǎn),所以我選擇相對(duì)便捷的一個(gè)。

執(zhí)行下面的命令:

:echom system("ls")

你應(yīng)該在屏幕的底部看到ls命令的輸出。如果執(zhí)行:message,你也能看到它們。 Vim函數(shù)system()接受一個(gè)字符串命令作為參數(shù)并以字符串形式返回那個(gè)命令的輸出。

你可以把另一個(gè)字符串作為參數(shù)傳遞給system()。執(zhí)行下面命令:

:echom system("wc -c", "abcdefg")

Vim將顯示7(以及一些別的)。 如果你像這樣傳遞第二個(gè)參數(shù),Vim將寫入它到臨時(shí)文件中并通過(guò)管道作為標(biāo)準(zhǔn)輸入輸入到命令里。 目前我們不需要這個(gè)特性,不過(guò)它值得了解。

回到我們的函數(shù)。編輯PotionShowBytecode()來(lái)填充框架的第一部分:

function! PotionShowBytecode()
    " Get the bytecode.
    let bytecode = system(g:potion_command . " -c -V " . bufname("%"))
    echom bytecode

    " Open a new split and set it up.

    " Insert the bytecode.

endfunction

保存文件,在factorial.pn處執(zhí)行:set ft=potion重新加載,并使用<lovalleader>b嘗試一下。 Vim會(huì)在屏幕的底部顯示字節(jié)碼。一旦看到它成功執(zhí)行了,你可以移除echom。

在分割上打草稿

接下來(lái)我們將打開(kāi)一個(gè)新的分割把結(jié)果展示給用戶。 這將讓用戶能夠借助Vim的全部功能來(lái)瀏覽字節(jié)碼,而不是僅僅只在屏幕上曇花一現(xiàn)。

為此我們將創(chuàng)建一個(gè)"草稿"分割:一個(gè)分割,它包括一個(gè)永不保存并每次執(zhí)行映射都會(huì)被覆蓋的緩沖區(qū)。 把PotionShowBytecode()函數(shù)改成這樣:

function! PotionShowBytecode()
    " Get the bytecode.
    let bytecode = system(g:potion_command . " -c -V " . bufname("%"))

    " Open a new split and set it up.
    vsplit __Potion_Bytecode__
    normal! ggdG
    setlocal filetype=potionbytecode
    setlocal buftype=nofile

    " Insert the bytecode.

endfunction

新增的命令應(yīng)該很好理解。

vsplit創(chuàng)建了名為__Potion_Bytecode__的新豎直分割。 我們用下劃線包起名字,使得用戶注意到這不是普通的文件(它只是顯示輸出的緩沖區(qū))。 下劃線不是什么特殊用法,只是約定俗成罷了。

接著我們用normal! ggdG刪除緩沖區(qū)中的所有東西。 第一次執(zhí)行這個(gè)映射時(shí),并不需要這樣做,但之后我們將重用__Potion_Bytecode__緩沖區(qū),所以需要清空它。

接下來(lái)我們?yōu)檫@個(gè)緩沖區(qū)設(shè)置兩個(gè)本地設(shè)置。首先我們?cè)O(shè)置它的文件類型為potionbytecode,只是為了指明它的用途。 我們也改變buftypenofile,告訴Vim這個(gè)緩沖區(qū)與磁盤上的文件不相關(guān),這樣它就不會(huì)把緩沖區(qū)寫入。

最后還剩下把我們保存在bytecode變量的字節(jié)碼轉(zhuǎn)儲(chǔ)進(jìn)緩沖區(qū)。完成函數(shù),讓它看上去像這樣:

function! PotionShowBytecode()
    " Get the bytecode.
    let bytecode = system(g:potion_command . " -c -V " . bufname("%") . " 2>&1")

    " Open a new split and set it up.
    vsplit __Potion_Bytecode__
    normal! ggdG
    setlocal filetype=potionbytecode
    setlocal buftype=nofile

    " Insert the bytecode.
    call append(0, split(bytecode, '\v\n'))
endfunction

Vim函數(shù)append()接受兩個(gè)參數(shù):一個(gè)將被附加內(nèi)容的行號(hào)和一個(gè)將按行附加的字符串列表。 舉個(gè)例子,嘗試執(zhí)行下面命令:

:call append(3, ["foo", "bar"])

這將附加兩行,foobar,在你當(dāng)前緩沖區(qū)的第三行之后。 這次我們將在表示文件開(kāi)頭的第0行之后添加。

我們需要一個(gè)字符串列表來(lái)附加,但我們只有來(lái)自system()調(diào)用的單個(gè)包括換行符的字符串。 我們使用Vim的split()函數(shù)來(lái)分割這一大坨文本成一個(gè)字符串列表。?split()接受一個(gè)待分割的字符串和一個(gè)查找分割點(diǎn)的正則表達(dá)式。這真的很簡(jiǎn)單。

現(xiàn)在函數(shù)已經(jīng)完成了,試一下對(duì)應(yīng)的映射。 當(dāng)你在factorial.pn中執(zhí)行<localleader>b,Vim將打開(kāi)新的包括Potion字節(jié)碼的緩沖區(qū)。 修改Potion源代碼,保存文件,執(zhí)行映射來(lái)看看會(huì)有什么不同的結(jié)果。

練習(xí)

閱讀:help bufname。

閱讀:help buftype。

閱讀:help append()。

閱讀:help split()。

閱讀:help :!

閱讀:help :read:help :read!(我們沒(méi)有講到這些命令,不過(guò)它們非常好用)。

閱讀:help system()。

閱讀:help design-not。

目前,我們的插件要求用戶在執(zhí)行映射之前手動(dòng)保存文件來(lái)使得他們的改變起效。 當(dāng)今撤銷已經(jīng)變得非常輕易,所以修改寫過(guò)的函數(shù)來(lái)自動(dòng)替他們保存。

如果你在一個(gè)帶語(yǔ)法錯(cuò)誤的Potion文件上執(zhí)行這個(gè)字節(jié)碼映射,會(huì)發(fā)生什么?為什么?

修改PotionShowBytecode()函數(shù)來(lái)探測(cè)Potion編譯器是否返回一個(gè)錯(cuò)誤,并向用戶輸出錯(cuò)誤信息。

加分題

每次你執(zhí)行字節(jié)碼映射時(shí),一個(gè)新的豎直分割都會(huì)被創(chuàng)建,即使用戶沒(méi)有關(guān)閉上一個(gè)。 如果用戶沒(méi)有一再關(guān)閉這些窗口,他們最終將被大量額外的窗口困住。

修改PotionShowBytecode()來(lái)探測(cè)__Potion_Bytecode__緩沖區(qū)的窗口是否已經(jīng)打開(kāi)了, 如果是,切換到它上去而不是創(chuàng)建新的分割。

你大概想要閱讀:help bufwinnr()來(lái)獲取幫助。

額外的加分題

還記得我們?cè)O(shè)置臨時(shí)緩沖區(qū)的filetypepotionbytecode? 創(chuàng)建syntax/potionbytecode.vim文件并為Potion字節(jié)碼定義語(yǔ)法高亮,使得它們更易讀。

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

掃描二維碼

下載編程獅App

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

編程獅公眾號(hào)