Vimscript 外部命令

2018-02-24 16:03 更新

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

讓我們在插件中添加一些跟Potion編譯器交互的命令,來淺嘗在Vim里面調(diào)用外部命令的方法。

編譯

首先我們將加入一個命令來編譯和執(zhí)行當前Potion文件。 有很多方法可以實現(xiàn)這一點,不過我們暫且用外部命令簡單地實現(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>

第一部分以全局變量的形式儲存著用來執(zhí)行Potion代碼的命令,如果還沒有設置過的話。 我們之前見過類似的檢查了。

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

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

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

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

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

Bang!

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

:!ls

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

當這樣執(zhí)行時,Vim不會傳遞任何輸入給外部命令。為了驗證,執(zhí)行:

:!cat

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

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

:silent !echo Hello, world.

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

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

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

讓我們回到PotionCompileAndRun()上來:

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

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

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

!potion factorial.pn

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

顯示字節(jié)碼

Potion編譯器有一個顯示由它生成的字節(jié)碼的選項。如果你正試圖在非常低級的層次下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
...

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

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

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

這里沒有什么特別的 -- 只是一個簡單的映射。現(xiàn)在先描劃出函數(shù)的大概框架:

function! PotionShowBytecode()
    " Get the bytecode.

    " Open a new split and set it up.

    " Insert the bytecode.

endfunction

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

system()

有許多不同的方法可以實現(xiàn)這一點,所以我選擇相對便捷的一個。

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

:echom system("ls")

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

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

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

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

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

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會在屏幕的底部顯示字節(jié)碼。一旦看到它成功執(zhí)行了,你可以移除echom。

在分割上打草稿

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

為此我們將創(chuàng)建一個"草稿"分割:一個分割,它包括一個永不保存并每次執(zhí)行映射都會被覆蓋的緩沖區(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

新增的命令應該很好理解。

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

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

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

最后還剩下把我們保存在bytecode變量的字節(jié)碼轉(zhuǎ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()接受兩個參數(shù):一個將被附加內(nèi)容的行號和一個將按行附加的字符串列表。 舉個例子,嘗試執(zhí)行下面命令:

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

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

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

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

練習

閱讀:help bufname。

閱讀:help buftype

閱讀:help append()。

閱讀:help split()

閱讀:help :!。

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

閱讀:help system()

閱讀:help design-not。

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

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

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

加分題

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

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

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

額外的加分題

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

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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號