如何使用Python编写vim插件

前言

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还不能正常工作。
此时需要检查:

  1. 系统上是否装了Python?
  2. Python是32位还是64位跟vim是否匹配?
  3. Python的版本跟编译时的版本是否一致(编译时的版本可以使用:version查看)
  4. 通过pythondllpythonthreedll来分别指定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表达式的值的类型是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对象对应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-localbuffer-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的进程(https://github.com/vim/vim/issues/906),计算密集型任务想利用多核来提高性能已不可能。

奇技淫巧

  • 把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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容