Vim遵循UNIX哲學"做一件事,做好它"。 與其試圖集成你可能想要的功能到編輯器自身,更好的辦法是在適當時使用Vim來調用外部命令。
讓我們在插件中添加一些跟Potion編譯器交互的命令,來淺嘗在Vim里面調用外部命令的方法。
## 編譯
首先我們將加入一個命令來編譯和執行當前Potion文件。 有很多方法可以實現這一點,不過我們暫且用外部命令簡單地實現。
在你的插件的repo中創建`potion/ftplugin/potion/running.vim`文件。 這將是我們創建編譯和執行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>
~~~
第一部分以全局變量的形式儲存著用來執行Potion代碼的命令,如果還沒有設置過的話。 我們之前見過類似的檢查了。
如果`potion`不在用戶的`$PATH`內,這將允許用戶覆蓋掉它, 比如在`~/.vimrc`添加類似`let g:potion_command = "/Users/sjl/src/potion/potion"`的一行。
最后一行添加了一個buffer-local的映射來調用我們前面定義的函數。 不要忘了,由于這個文件位于`ftdetect/potion`文件夾,每次一個文件的`filetype`設置成`potion`,它都會被執行。
真正實現了功能的地方在`PotionCompileAndRunFile()`。 保存文件,打開`factorial.pn`并按下`<localleader>r`來執行這個映射,看看發生了什么。
如果`potion`位于你的`$PATH`下,代碼會被執行,你應該在終端看到輸出(或者在窗口底部,如果你用的是GUI vim)。 如果你看到了沒有找到`potion`命令的錯誤,你需要像上面提到那樣在`~/.vimrc`內設置`g:potion_command`。
讓我們了解一下`PotionCompileAndRunFile()`的工作原理。
## Bang!
`:!`命令(念作"bang")會執行外部命令并在屏幕上顯示它們的輸出。嘗試執行下面的命令:
~~~
:!ls
~~~
Vim將輸出`ls`命令的結果,同時還有"請按 ENTER 或其它命令繼續"的提示。
當這樣執行時,Vim不會傳遞任何輸入給外部命令。為了驗證,執行:
~~~
:!cat
~~~
打入一些行,然后你將看到`cat`命令把它們都吐回來了,就像你是在Vim之外執行`cat`。按下Ctrl-D來結束。
想要執行一個外部命令并避免`請按 ENTER 或其它命令繼續`的提示,使用`:silent !`。執行下面的命令:
~~~
:silent !echo Hello, world.
~~~
如果在GUI Vim比如MacVim或gVim下執行,你將不會看到`Hello,world.`的輸出。
如果你在終端下執行,你看到的結果取決于你的配置。 一旦執行了一個`:silent !`,你可能需要執行`:redraw!`來重新刷新屏幕。
注意這個命令是`:silent !`而不是`:silent!`(看到空格了嗎?)! 這是兩個不一樣的命令,我們想要的是前者!Vimscript奇妙吧?
讓我們回到`PotionCompileAndRun()`上來:
~~~
function! PotionCompileAndRunFile()
silent !clear
execute "!" . g:potion_command . " " . bufname("%")
endfunction
~~~
首先我們執行一個`silent !clear`命令,來清空屏幕輸出并避免產生提示。 這將確保我們僅僅看到本次命令的輸出,如果一再執行同樣的命令,你會覺得有用的。
在下一行我們使用老朋友`execute`來動態創建一個命令。建立的命令看上去類似于:
~~~
!potion factorial.pn
~~~
注意這里沒有`silent`,所以用戶將看到命令輸出,并不得不按下enter來返回Vim。 這就是我們想要的,所以就這樣設置好了。
## 顯示字節碼
Potion編譯器有一個顯示由它生成的字節碼的選項。如果你正試圖在非常低級的層次下debug,這將幫上忙。 在shell里執行下面的命令:
~~~
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代碼生成的字節碼的映射, 這樣他們能更方便地瀏覽并測試輸出。
首先,在`ftplugin/potion/running.vim`底部添加下面一行:
~~~
nnoremap <buffer> <localleader>b :call PotionShowBytecode()<cr>
~~~
這里沒有什么特別的 -- 只是一個簡單的映射。現在先描劃出函數的大概框架:
~~~
function! PotionShowBytecode()
" Get the bytecode.
" Open a new split and set it up.
" Insert the bytecode.
endfunction
~~~
既然已經建立起一個框架,讓我們把它變成現實吧。
## system()
有許多不同的方法可以實現這一點,所以我選擇相對便捷的一個。
執行下面的命令:
~~~
:echom system("ls")
~~~
你應該在屏幕的底部看到`ls`命令的輸出。如果執行`:message`,你也能看到它們。 Vim函數`system()`接受一個字符串命令作為參數并以字符串形式返回那個命令的輸出。
你可以把另一個字符串作為參數傳遞給`system()`。執行下面命令:
~~~
:echom system("wc -c", "abcdefg")
~~~
Vim將顯示`7`(以及一些別的)。 如果你像這樣傳遞第二個參數,Vim將寫入它到臨時文件中并通過管道作為標準輸入輸入到命令里。 目前我們不需要這個特性,不過它值得了解。
回到我們的函數。編輯`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`處執行`:set ft=potion`重新加載,并使用`<lovalleader>b`嘗試一下。 Vim會在屏幕的底部顯示字節碼。一旦看到它成功執行了,你可以移除`echom`。
## 在分割上打草稿
接下來我們將打開一個新的分割把結果展示給用戶。 這將讓用戶能夠借助Vim的全部功能來瀏覽字節碼,而不是僅僅只在屏幕上曇花一現。
為此我們將創建一個"草稿"分割:一個分割,它包括一個永不保存并每次執行映射都會被覆蓋的緩沖區。 把`PotionShowBytecode()`函數改成這樣:
~~~
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`創建了名為`__Potion_Bytecode__`的新豎直分割。 我們用下劃線包起名字,使得用戶注意到這不是普通的文件(它只是顯示輸出的緩沖區)。 下劃線不是什么特殊用法,只是約定俗成罷了。
接著我們用`normal! ggdG`刪除緩沖區中的所有東西。 第一次執行這個映射時,并不需要這樣做,但之后我們將重用`__Potion_Bytecode__`緩沖區,所以需要清空它。
接下來我們為這個緩沖區設置兩個本地設置。首先我們設置它的文件類型為`potionbytecode`,只是為了指明它的用途。 我們也改變`buftype`為`nofile`,告訴Vim這個緩沖區與磁盤上的文件不相關,這樣它就不會把緩沖區寫入。
最后還剩下把我們保存在`bytecode`變量的字節碼轉儲進緩沖區。完成函數,讓它看上去像這樣:
~~~
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函數`append()`接受兩個參數:一個將被附加內容的行號和一個將按行附加的字符串列表。 舉個例子,嘗試執行下面命令:
~~~
:call append(3, ["foo", "bar"])
~~~
這將附加兩行,`foo`和`bar`,在你當前緩沖區的第三行之后。 這次我們將在表示文件開頭的第0行之后添加。
我們需要一個字符串列表來附加,但我們只有來自`system()`調用的單個包括換行符的字符串。 我們使用Vim的`split()`函數來分割這一大坨文本成一個字符串列表。?`split()`接受一個待分割的字符串和一個查找分割點的正則表達式。這真的很簡單。
現在函數已經完成了,試一下對應的映射。 當你在`factorial.pn`中執行`<localleader>b`,Vim將打開新的包括Potion字節碼的緩沖區。 修改Potion源代碼,保存文件,執行映射來看看會有什么不同的結果。
## 練習
閱讀`:help bufname`。
閱讀`:help buftype`。
閱讀`:help append()`。
閱讀`:help split()`。
閱讀`:help :!`。
閱讀`:help :read`和`:help :read!`(我們沒有講到這些命令,不過它們非常好用)。
閱讀`:help system()`。
閱讀`:help design-not`。
目前,我們的插件要求用戶在執行映射之前手動保存文件來使得他們的改變起效。 當今撤銷已經變得非常輕易,所以修改寫過的函數來自動替他們保存。
如果你在一個帶語法錯誤的Potion文件上執行這個字節碼映射,會發生什么?為什么?
修改`PotionShowBytecode()`函數來探測Potion編譯器是否返回一個錯誤,并向用戶輸出錯誤信息。
## 加分題
每次你執行字節碼映射時,一個新的豎直分割都會被創建,即使用戶沒有關閉上一個。 如果用戶沒有一再關閉這些窗口,他們最終將被大量額外的窗口困住。
修改`PotionShowBytecode()`來探測`__Potion_Bytecode__`緩沖區的窗口是否已經打開了, 如果是,切換到它上去而不是創建新的分割。
你大概想要閱讀`:help bufwinnr()`來獲取幫助。
## 額外的加分題
還記得我們設置臨時緩沖區的`filetype`為`potionbytecode`? 創建`syntax/potionbytecode.vim`文件并為Potion字節碼定義語法高亮,使得它們更易讀。
- 前言
- 鳴謝
- 預備知識
- 打印信息
- 設置選項
- 基本映射
- 模式映射
- 精確映射
- Leaders
- 編輯你的Vimrc文件
- Abbreviations
- 更多的Mappings
- 鍛煉你的手指
- 本地緩沖區的選項設置和映射
- 自動命令
- 本地緩沖區縮寫
- 自動命令組
- Operator-Pending映射
- 更多Operator-Pending映射
- 狀態條
- 負責任的編碼
- 變量
- 變量作用域
- 條件語句
- 比較
- 函數
- 函數參數
- 數字
- 字符串
- 字符串函數
- Execute命令
- Normal命令
- 執行normal!
- 基本的正則表達式
- 實例研究:Grep 運算符(Operator),第一部分
- 實例研究:Grep運算符(Operator),第二部分
- 實例研究:Grep運算符(Operator),第三部分
- 列表
- 循環
- 字典
- 切換
- 函數式編程
- 路徑
- 創建一個完整的插件
- 舊社會下的插件配置方式
- 新希望:用Pathogen配置插件
- 檢測文件類型
- 基本語法高亮
- 高級語法高亮
- 更高級的語法高亮
- 基本折疊
- 高級折疊
- 段移動原理
- Potion段移動
- 外部命令
- 自動加載
- 文檔
- 發布
- 還剩下什么?