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")會執(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。 這就是我們想要的,所以就這樣設置好了。
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)實吧。
有許多不同的方法可以實現(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
,只是為了指明它的用途。 我們也改變buftype
為nofile
,告訴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"])
這將附加兩行,foo
和bar
,在你當前緩沖區(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ū)的filetype
為potionbytecode
? 創(chuàng)建syntax/potionbytecode.vim
文件并為Potion字節(jié)碼定義語法高亮,使得它們更易讀。
更多建議: