如何使用Python編寫vim插件

前言

vim是個偉大的編輯器,不僅在于她特立獨行的編輯方式,還在于她強大的擴展能力。然而,vim自身用于寫插件的語言vimL功能有很大的局限性,實現(xiàn)功能復(fù)雜的插件往往力不從心,而且運行效率也不高。幸好,vim早就想到了這一點,她提供了很多外部語言接口,比如Python,ruby,lua,Perl等,可以很方便的編寫vim插件。本文主要介紹如何使用Python編寫vim插件。

準備工作

1. 編譯vim,使vim支持Python

在編譯之前,configure的時候加上--enable-pythoninterp--enable-python3interp選項,使之分別支持Python2和Python3
編譯好之后,可以通過vim --version | grep +python來查看是否已經(jīng)支持Python,結(jié)果中應(yīng)該包含+python+python3,當(dāng)然也可以編譯成只支持Python2或Python3。

現(xiàn)在好多平臺都有直接編譯好的版本,已經(jīng)包含Python支持,直接下載就可以了:

  • Windows:可以在這里下載。
  • Mac OS:可以直接brew install vim來安裝。
  • Linux:也有快捷的安裝方式,就不贅言了。

2. 如何讓Python能正常工作

雖然vim已經(jīng)支持Python,但是可能:echo has("python"):echo has("python3")的結(jié)果仍是0,說明Python還不能正常工作。
此時需要檢查:

  1. 系統(tǒng)上是否裝了Python?
  2. Python是32位還是64位跟vim是否匹配?
  3. Python的版本跟編譯時的版本是否一致(編譯時的版本可以使用:version查看)
  4. 通過pythondllpythonthreedll來分別指定Python2和Python3所使用的動態(tài)庫。
    例如,可以在vimrc里添加
    set pythondll=/Users/yggdroot/.python2.7.6/lib/libpython2.7.so

經(jīng)此4步,99%能讓Python工作起來,剩下的1%就看人品了。

補充一點
對于neovim,執(zhí)行

pip2 install --user --upgrade neovim
pip3 install --user --upgrade neovim

就可以添加Python2和Python3的支持,具體參見:h provider-python

從hello world開始

在命令行窗口執(zhí)行:pyx print("hello world!"),輸出“hello world!”,說明Python工作正常,此時我們已經(jīng)可以使用Python來作為vim的EX命令了。

操作vim像vimL一樣容易

怎么用Python來訪問vim的信息以及操作vim呢?很簡單,vim的Python接口提供了一個叫vim的模塊(module)。vim模塊是Python和vim溝通的橋梁,通過它,Python可以訪問vim的一切信息以及操作vim,就像使用vimL一樣。所以寫腳本,首先要import vim

vim模塊

vim模塊提供了兩個非常有用的函數(shù)接口:

  • vim.command(str)
    執(zhí)行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),返回結(jié)果類型為:

    • string: 如果vim表達式的值的類型是stringnumber
    • 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對象對應(yīng)vim的一個Tabpage。

  • Window對象(:h python-window
    一個Window對象對應(yīng)vim的一個Window。

  • Buffer對象(:h python-buffer
    一個Buffer對象對應(yīng)vim的一個buffer,Buffer對象提供了一些屬性和方法,可以很方便操作buffer。
    例如 (假定b是當(dāng)前的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對象提供了一些屬性,可以方便的訪問“當(dāng)前”的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的方法:

  • 預(yù)定義vim變量(v:var
    可以通過vim.vvars來訪問預(yù)定義vim變量,vim.vvars是個類似Dictionary的對象。例如,訪問v:version

    :py print vim.vvars["version"]
    
  • 全局變量(g:var
    可以通過vim.vars來訪問全局變量,vim.vars也是個類似Dictionary的對象。例如,改變?nèi)肿兞?code>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")

當(dāng)然, 還有更pythonic的方法:

  • 全局選項設(shè)置(:h python-options
    例如:

    :py vim.options["autochdir"] = True
    

    注意:如果是window-local或者buffer-local選項,此種方法會報KeyError異常。對于window-localbuffer-local選項,請往下看。

  • window-local選項設(shè)置
    例如:

    :py vim.current.window.options["number"] = True
    
  • buffer-local選項設(shè)置
    例如:

    :py vim.current.buffer.options["shiftwidth"] = 4
    

兩種方式寫vim插件

  • 內(nèi)嵌式
py[thon] << {endmarker}
{script}
{endmarker}

{script}中的內(nèi)容為Python代碼,{endmarker}是一個標記符號,可以是任何字符串,不過{endmarker}前面不能有任何的空白字符,也就是要頂格寫。
例如,寫一個函數(shù),打印出當(dāng)前buffer所有的行(Demo.vim):

function! Demo()
py << EOF
import vim
for line in vim.current.buffer:
    print line
EOF
endfunction
call Demo()

運行:source %查看結(jié)果。

  • 獨立式
    把Python代碼寫到*.py中,vimL只用來定義全局變量、map、command等,LeaderF就是采用這種方式。個人更喜歡這種方式,可以把全部精力集中在寫Python代碼上。

異步

  • 多線程
    可以通過Python的threading模塊來實現(xiàn)多線程。但是,線程里面只能實現(xiàn)與vim無關(guān)的邏輯,任何試圖在線程里面操作vim的行為都可能(也許用“肯定會”更合適)導(dǎo)致vim崩潰,甚至包括只一個vim選項。雖然如此,也比vimL好多了,畢竟聊勝于無。

  • subprocess
    可以通過Python的subprocess模塊來調(diào)用外部命令。
    例如:

    :py import subprocess
    :py print subprocess.Popen("ls -l", shell=True, stdout=subprocess.PIPE).stdout.read()
    

也就是說,從支持Python起,vim就已經(jīng)支持異步了(雖然直到vim7.4才基本沒有bug),Neovim所增加的異步功能,對用Python寫插件的小伙伴來說,沒有任何吸引力。好多Neovim粉竟以引入異步(job)而引以為傲,它什么時候能引入真正的多線程支持我才會服它。

案例

著名的補全插件YCM和模糊查找神器LeaderF都是使用Python編寫的。

缺陷

由于GIL的原因,Python線程無法并行處理;而vim又不支持Python的進程(https://github.com/vim/vim/issues/906),計算密集型任務(wù)想利用多核來提高性能已不可能。

奇技淫巧

  • 把buffer中所有單詞首字母變?yōu)榇髮懽帜?/p>

    :%pydo return line.title()
    
  • 把buffer中所有的行鏡像顯示

    例如,把

    vim is very useful
    123 456 789
    abc def ghi
    who am I
    

    變?yōu)?/p>

    lufesu yrev si miv
    987 654 321
    ihg fed cba
    I ma ohw
    

    可以執(zhí)行此命令::%pydo return line[::-1]

總結(jié)

以上只是簡單的介紹,更詳細的資料可以參考:h python

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,460評論 6 538
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,067評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,467評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,468評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,184評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,582評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,616評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,794評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,343評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,096評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,291評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,863評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,513評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,941評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,190評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,026評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,253評論 2 375

推薦閱讀更多精彩內(nèi)容