如何使用 Python 编写 vim 插件 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Yggdroot
V2EX    Vim

如何使用 Python 编写 vim 插件

  •  
  •   Yggdroot 2017-11-28 08:06:36 +08:00 8772 次点击
    这是一个创建于 2880 天前的主题,其中的信息可能已经有所发展或是发生改变。

    前言

    vim 是个伟大的编辑器,不仅在于她特立独行的编辑方式,还在于她强大的扩展能力。然而,vim 自身用于写插件的语言 vimL 功能有很大的局限性,实现功能复杂的插件往往力不从心,而且运行效率也不高。幸好,vim 早就想到了这一点,她提供了很多外部语言接口,比如 Python,ruby,lua,Perl 等,可以很方便的编写 vim 插件。本文主要介绍如何使用 Python 编写 vim 插件。

    准备工作

    1. 编译 vim,使 vim 支持 Pyhon

    在编译之前,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-currentvim.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

    9 条回复    2017-12-02 18:17:05 +08:00
    bramblex
        1
    bramblex  
       2017-11-28 08:33:20 +08:00
    虽然我没用过 python 写 vim 插件

    不过就从这里看来, 跟直接用 VimL 写没什么太大差别啊
    BBCCBB
        2
    BBCCBB  
       2017-11-28 08:33:42 +08:00
    厉害了
    Keyes
        3
    Keyes  
       2017-11-28 08:57:47 +08:00 via Android
    py 运行效率也就那么回事儿吧……
    4BVL25L90W260T9U
        4
    4BVL25L90W260T9U  
       2017-11-28 09:14:59 +08:00 via iPad
    @bramblex 不需要学习 vimL 了呀
    ahonn
        5
    ahonn  
       2017-11-28 10:55:22 +08:00
    @ospider 就算你用 Python 写插件,不懂 VimL 也写不出什么花来。而实际上能用 VimL 完成的简单功能根本不需要用 Python。
    ahonn
        6
    ahonn  
       2017-11-28 10:57:23 +08:00
    简单功能。用 VimL
    =>
    VimL 写起来比较麻烦,但用 Python 比较简便。用 Python

    不管怎么样,想写 Vim 插件还是得先学习 VimL ...
    Yggdroot
        7
    Yggdroot  
    OP
       2017-11-28 12:42:28 +08:00
    @Keyes 至少比 vimL 要好
    dychenyi
        8
    dychenyi  
       2017-11-28 14:36:04 +08:00
    如果把 vim 比喻成拖拉机的话,他的优点是适合改装,上山,下水,甚至上天都可以。 如果真要把他当做优雅的小汽车来日常使用的话,我觉得除非是没办法, 不然真不如直接用相关的 IDE。

    一直 linux 用 vim 写 c++的飘过,需要登录服务器,所以像 qtcreator 这样的 IDE 比较卡顿。 要我选,肯定还是用 qtcreator、eclipse,vs 之类的。不管怎么样,喜欢就好,比如拿 ubuntu 作为日常用机的一样。。
    tracyone
        9
    tracyone  
       2017-12-02 18:17:05 +08:00
    牛逼
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     6382 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 02:30 PVG 10:30 LAX 19:30 JFK 22:30
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86