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