V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Yggdroot
V2EX  ›  Vim

如何使用 Python 编写 vim 插件

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

    前言

    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-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 运行效率也就那么回事儿吧……
    ospider
        4
    ospider  
       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   ·   实用小工具   ·   2760 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 13:56 · PVG 21:56 · LAX 05:56 · JFK 08:56
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.