前言
vim是個偉大的編輯器,不僅在於她特立獨行的編輯方式,還在於她強大的擴充套件能力。然而,vim自身用於寫外掛的語言vimL功能有很大的侷限性,實現功能複雜的外掛往往力不從心,而且執行效率也不高。幸好,vim早就想到了這一點,她提供了很多外部語言介面,比如Python,ruby,lua,Perl等,可以很方便的編寫vim外掛。本文主要介紹如何使用Python編寫vim外掛。
準備工作
1. 編譯vim,使vim支援Python
在編譯之前,configure
的時候加上--enable-pythoninterp
和--enable-python3interp
選項,使之分別支援Python2和Python3
編譯好之後,可以通過vim --version | grep +python
來檢視是否已經支援Python,結果中應該包含+python
和 +python3
,當然也可以編譯成只支援Python2或Python3。
現在好多平臺都有直接編譯好的版本,已經包含Python支援,直接下載就可以了:
- Windows:可以在這裡下載。
- Mac OS:可以直接
brew install vim
來安裝。 - Linux:也有快捷的安裝方式,就不贅言了。
2. 如何讓Python能正常工作
雖然vim已經支援Python,但是可能:echo has("python")
或:echo has("python3")
的結果仍是0
,說明Python還不能正常工作。
此時需要檢查:
- 系統上是否裝了Python?
- Python是32位還是64位跟vim是否匹配?
- Python的版本跟編譯時的版本是否一致(編譯時的版本可以使用
:version
檢視) - 通過
pythondll
和pythonthreedll
來分別指定Python2和Python3所使用的動態庫。
例如,可以在vimrc裡新增set pythondll=/Users/yggdroot/.python2.7.6/lib/libpython2.7.so
經此4步,99%能讓Python工作起來,剩下的1%就看人品了。
補充一點:
對於neovim,執行
pip2 install --user --upgrade neovim
pip3 install --user --upgrade neovim複製程式碼
就可以新增Python2和Python3的支援,具體參見:h provider-python
。
從hello world開始
在命令列視窗執行:pyx print("hello world!")
,輸出“hello world!”,說明Python工作正常,此時我們已經可以使用Python來作為vim的EX
命令了。
操作vim像vimL一樣容易
怎麼用Python來訪問vim的資訊以及操作vim呢?很簡單,vim的Python介面提供了一個叫vim的模組(module)。vim模組是Python和vim溝通的橋樑,通過它,Python可以訪問vim的一切資訊以及操作vim,就像使用vimL一樣。所以寫指令碼,首先要import vim
。
vim模組
vim模組提供了兩個非常有用的函式介面:
vim.command(str)
執行vim中的命令str
(ex-mode),返回值為None,例如::py vim.command("%s/\s\+$//g") :py vim.command("set shiftwidth=4") :py vim.command("normal! dd")複製程式碼
vim.eval(str)
求vim表示式str
的值,(什麼是vim表示式,參見:h expr
),返回結果型別為:string
: 如果vim表示式的值的型別是string
或number
list
:如果vim表示式的值的型別是一個vim list(:h list
)dictionary
:如果vim表示式的值的型別是一個vim dictionary(:h dict
)
例如:
:py sw = vim.eval("&shiftwidth") :py print vim.eval("expand('%:p')") :py print vim.eval("@a")複製程式碼
vim模組還提供了一些有用的物件:
Tabpage
物件(:h python-tabpage
)
一個Tabpage
物件對應vim的一個Tabpage。Window
物件(:h python-window
)
一個Window
物件對應vim的一個Window。Buffer
物件(:h python-buffer
)
一個Buffer
物件對應vim的一個buffer,Buffer物件提供了一些屬性和方法,可以很方便操作buffer。
例如 (假定b
是當前的buffer) ::py print b.name # write the buffer file name :py b[0] = "hello!!!" # replace the top line :py b[:] = None # delete the whole buffer :py del b[:] # delete the whole buffer :py b[0:0] = [ "a line" ] # add a line at the top :py del b[2] # delete a line (the third) :py b.append("bottom") # add a line at the bottom :py n = len(b) # number of lines :py (row,col) = b.mark('a') # named mark :py r = b.range(1,5) # a sub-range of the buffer :py b.vars["foo"] = "bar" # assign b:foo variable :py b.options["ff"] = "dos" # set fileformat :py del b.options["ar"] # same as :set autoread<複製程式碼
vim.current
物件(:h python-current
)vim.current
物件提供了一些屬性,可以方便的訪問“當前”的vim物件
屬性 | 含義 | 型別 |
---|---|---|
vim.current.line | The current line (RW) | String |
vim.current.buffer | The current buffer (RW) | Buffer |
vim.current.window | The current window (RW) | Window |
vim.current.tabpage | The current tab page (RW) | TabPage |
vim.current.range | The current line range (RO) | Range |
python訪問vim中的變數
訪問vim中的變數,可以通過前面介紹的vim.eval(str)
來訪問,例如:
:py print vim.eval("v:version")複製程式碼
但是, 還有更pythonic的方法:
預定義vim變數(
v:var
)
可以通過vim.vvars
來訪問預定義vim變數,vim.vvars
是個類似Dictionary
的物件。例如,訪問v:version
::py print vim.vvars["version"]複製程式碼
全域性變數(
g:var
)
可以通過vim.vars
來訪問全域性變數,vim.vars
也是個類似Dictionary
的物件。例如,改變全域性變數g:global_var
的值::py vim.vars["global_var"] = 123複製程式碼
tabpage變數(
t:var
)
例如::py vim.current.tabpage.vars["var"] = "Tabpage"複製程式碼
window變數(
w:var
)
例如::py vim.current.window.vars["var"] = "Window"複製程式碼
buffer變數(
b:var
)
例如::py vim.current.buffer.vars["var"] = "Buffer"複製程式碼
python訪問vim中的選項(options
)
訪問vim中的選項,可以通過前面介紹的vim.command(str)
和vim.eval(str)
來訪問,例如:
:py vim.command("set shiftwidth=4")
:py print vim.eval("&shiftwidth")複製程式碼
當然, 還有更pythonic的方法:
全域性選項設定(
:h python-options
)
例如::py vim.options["autochdir"] = True複製程式碼
注意:如果是
window-local
或者buffer-local
選項,此種方法會報KeyError
異常。對於window-local
和buffer-local
選項,請往下看。window-local
選項設定
例如::py vim.current.window.options["number"] = True複製程式碼
buffer-local
選項設定
例如::py vim.current.buffer.options["shiftwidth"] = 4複製程式碼
兩種方式寫vim外掛
內嵌式
py[thon] << {endmarker} {script} {endmarker}複製程式碼
{script}
中的內容為Python程式碼,{endmarker}
是一個標記符號,可以是任何字串,不過{endmarker}
前面不能有任何的空白字元,也就是要頂格寫。
例如,寫一個函式,列印出當前buffer所有的行(Demo.vim
):function! Demo() py << EOF import vim for line in vim.current.buffer: print line EOF endfunction call Demo()複製程式碼
執行
:source %
檢視結果。獨立式
把Python程式碼寫到*.py
中,vimL只用來定義全域性變數、map、command等,LeaderF就是採用這種方式。個人更喜歡這種方式,可以把全部精力集中在寫Python程式碼上。
非同步
多執行緒
可以通過Python的threading
模組來實現多執行緒。但是,執行緒裡面只能實現與vim無關的邏輯,任何試圖線上程裡面操作vim的行為都可能(也許用“肯定會”更合適)導致vim崩潰,甚至包括只讀一個vim選項。雖然如此,也比vimL好多了,畢竟聊勝於無。subprocess
可以通過Python的subprocess
模組來呼叫外部命令。
例如::py import subprocess :py print subprocess.Popen("ls -l", shell=True, stdout=subprocess.PIPE).stdout.read()複製程式碼
也就是說,從支援Python起,vim就已經支援非同步了(雖然直到vim7.4才基本沒有bug),Neovim所增加的非同步功能,對用Python寫外掛的小夥伴來說,沒有任何吸引力。好多Neovim粉竟以引入非同步(job)而引以為傲,它什麼時候能引入真正的多執行緒支援我才會服它。
案例
著名的補全外掛YCM和模糊查詢神器LeaderF都是使用Python編寫的。
缺陷
由於GIL的原因,Python執行緒無法並行處理;而vim又不支援Python的程式(github.com/vim/vim/iss… ),計算密集型任務想利用多核來提高效能已不可能。
奇技淫巧
把buffer中所有單詞首字母變為大寫字母
:%pydo return line.title()複製程式碼
把buffer中所有的行映象顯示
例如,把
vim is very useful 123 456 789 abc def ghi who am I複製程式碼
變為
lufesu yrev si miv 987 654 321 ihg fed cba I ma ohw複製程式碼
可以執行此命令:
:%pydo return line[::-1]
總結
以上只是簡單的介紹,更詳細的資料可以參考:h python
。