vim 是个伟大的编辑器,不仅在于她特立独行的编辑方式,还在于她强大的扩展能力。然而,vim 自身用于写插件的语言 vimL 功能有很大的局限性,实现功能复杂的插件往往力不从心,而且运行效率也不高。幸好,vim 早就想到了这一点,她提供了很多外部语言接口,比如 Python,ruby,lua,Perl 等,可以很方便的编写 vim 插件。本文主要介绍如何使用 Python 编写 vim 插件。
在编译之前,configure
的时候加上--enable-pythoninterp
和--enable-python3interp
选项,使之分别支持 Python2 和 Python3
编译好之后,可以通过vim --version | grep +python
来查看是否已经支持 Python,结果中应该包含+python
和 +python3
,当然也可以编译成只支持 Python2 或 Python3。
现在好多平台都有直接编译好的版本,已经包含 Python 支持,直接下载就可以了:
brew install vim
来安装。虽然 vim 已经支持 Python,但是可能:echo has("python")
或:echo has("python3")
的结果仍是0
,说明 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
。
在命令行窗口执行:pyx print("hello world!")
,输出“hello world!”,说明 Python 工作正常,此时我们已经可以使用 Python 来作为 vim 的EX
命令了。
怎么用 Python 来访问 vim 的信息以及操作 vim 呢?很简单,vim 的 Python 接口提供了一个叫 vim 的模块(module)。vim 模块是 Python 和 vim 沟通的桥梁,通过它,Python 可以访问 vim 的一切信息以及操作 vim,就像使用 vimL 一样。所以写脚本,首先要import 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 |
访问 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"
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
内嵌式
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
。