使用python编写vim插件

Use a single editor well. The editor should be an extension of your hand; make sure your editor is configurable, extensible, and programmable. - Andrew Hunt

平常总是用那些大神们写的vim插件,今天闲来无事学写了一下vim插件,顺便写一个简单的教程。
先来看下我一个旺盛的需求:平常总会遇到分享本机或者服务器代码的情况,我经常使用paste.ubuntu.com网站粘贴代码(我记性不好总是忘记scp咋使的),然后把链接分享给别人。类似github的gist服务,不过比较简单,而且gist国内用户访问有时候会出现奇奇怪怪的问题,所以一般我用ubuntu的pastebin多一些。但是步骤麻烦点,我写这个插件就是要一个命令帮我完成在vim里直接贴代码到paste.ubuntu.com的功能。

步骤如下:
看了一个youtube视频讲怎么用python写vim插件的,笔者以前的经验只限于使用vim,并不会vim脚本。现学现卖,同想写插件的可以看看,即使不用深入理解,照葫芦画瓢也不难。简单的插件从学习到实现大概只会花你半天到一天左右的时间,以后你就可以定制你的vim编辑器了。
Writing Vim plugins with Python


让vim支持python函数

首先确认你的vim编译时支持python或者python3,用vim --version | grep +python查看输出,否则你可能要重新编译安装vim。 笔者之前没有写过vim脚本,由于对python比较熟悉,现在用python实现。这里举个简单的例子,vim example.vim

function! HelloVim()
    echo "Hello Vim"
endfunction

然后在vim里执行 :source %,之后在 :call HelloVim() 就能看到函数执行结果了。

怎么在vim脚本里使用python函数呢?这里有个简单的文档 Vim documentation: if_pyth
来用python实现一个类似的Hello函数

" vim sript function
function! HelloVim()
    echo "Hello Vim"
endfunction

" use python function, vim compiled with +python or +python3
function! HelloPython()
python3 << endOfPython

print('Hello Python')

endOfPython
endfunction

我的vim使用+python3编译的,你可能需要把上边的python3换成python。然后在vim里执行 :source %,之后在 :call HelloVim() 就能看到函数执行结果了。


vim插件的目录结构

我们直接偷懒使用项目生成框架starter kit(就和我直接偷懒用flask-cookitecutter一样),项目仓库在这里vim-plugin-starter-kit 如果你有兴趣也可以看看这个项目的wiki,有一些教程。 直接克隆项目,进入文件夹以后执行python create_plugin_scaffold.py然后填写一下项目名称就好了,直接帮助你生成一个项目模板。我的项目树如下:

vim-ubuntu-pastebin
├── README.md
├── doc
│   └── vim-ubuntu-pastebin.txt
└── plugin
    ├── __init__.py
    ├── test.txt
    ├── tests
    │   ├── __init__.py
    │   └── vim_ubuntu_pastebin_tests.py
    ├── vim_ubuntu_pastebin.py
    └── vim_ubuntu_pastebin.vim

编写简单的vim插件

我的需求有两个,一个是把当前的文本发送到 paste.ubuntu.com ,还有一个是把该功能集成到vim里。第一个功能我们使用python完成,功能也比较简单。我们去网站上随便粘贴一段文本用浏览器开发者工具观察发送的post请求就好了,之后用requests模拟提交(也可以用python内置的urllib,不过这里还要兼容python2和python3,所以直接用requests方便很多),编辑vim_ubuntu_pastebin_tests.py文件如下:

# -*- coding: utf-8 -*-

import os
import requests
import webbrowser

try:
    from urllib import urlencode    # py2
except ImportError:
    from urllib.parse import urlencode   # py3


PASTEBIN_SUPPORT_TYPE = frozenset([
    'text', 'Cucumber', 'abap', 'ada', 'ahk', 'antlr', 'antlr-as', 'antlr-cpp', 'antlr-csharp', 'antlr-java', 'antlr-objc', 'antlr-perl', 'antlr-python', 'antlr-ruby', 'apacheconf', 'applescript', 'as', 'as3', 'aspx-cs', 'aspx-vb', 'asy', 'basemake', 'bash', 'bat', 'bbcode', 'befunge', 'blitzmax', 'boo', 'c', 'c-objdump', 'cfm', 'cfs', 'cheetah', 'clojure', 'cmake', 'coffee-script', 'common-lisp', 'console', 'control', 'cpp', 'cpp-objdump', 'csharp', 'css', 'css+django', 'css+erb', 'css+genshitext', 'css+mako', 'css+myghty', 'css+php', 'css+smarty', 'cython', 'd', 'd-objdump', 'delphi', 'diff', 'django', 'dpatch', 'duel', 'dylan', 'erb', 'erl', 'erlang', 'evoque', 'factor', 'felix', 'fortran', 'gas', 'genshi', 'genshitext', 'glsl', 'gnuplot', 'go', 'gooddata-cl', 'groff', 'haml', 'haskell', 'html', 'html+cheetah', 'html+django', 'html+evoque', 'html+genshi', 'html+mako', 'html+myghty', 'html+php', 'html+smarty', 'html+velocity', 'hx', 'hybris', 'ini', 'io', 'ioke', 'irc', 'jade', 'java', 'js', 'js+cheetah', 'js+django', 'js+erb', 'js+genshitext', 'js+mako', 'js+myghty', 'js+php', 'js+smarty', 'jsp', 'lhs', 'lighty', 'llvm', 'logtalk', 'lua', 'make', 'mako', 'maql', 'mason', 'matlab', 'matlabsession', 'minid', 'modelica', 'modula2', 'moocode', 'mupad', 'mxml', 'myghty', 'mysql', 'nasm', 'newspeak', 'nginx', 'numpy', 'objdump', 'objective-c', 'objective-j', 'ocaml', 'ooc', 'perl', 'php', 'postscript', 'pot', 'pov', 'prolog', 'properties', 'protobuf', 'py3tb', 'pycon', 'pytb', 'python', 'python3', 'ragel', 'ragel-c', 'ragel-cpp', 'ragel-d', 'ragel-em', 'ragel-java', 'ragel-objc', 'ragel-ruby', 'raw', 'rb', 'rbcon', 'rconsole', 'rebol', 'redcode', 'rhtml', 'rst', 'sass', 'scala', 'scaml', 'scheme', 'scss', 'smalltalk', 'smarty', 'sourceslist', 'splus', 'sql', 'sqlite3', 'squidconf', 'ssp', 'tcl', 'tcsh', 'tex', 'text', 'trac-wiki', 'v', 'vala', 'vb.net', 'velocity', 'vim', 'xml', 'xml+cheetah', 'xml+django', 'xml+erb', 'xml+evoque', 'xml+mako', 'xml+myghty', 'xml+php', 'xml+smarty', 'xml+velocity', 'xquery', 'xslt', 'yaml', ])


def post_buffer_to_ubuntu_pastebin(buffer, filetype, open_in_borwser=True):
    """ post current file buffer content to http://paste.ubuntu.com/
    and return url.

    Args:
        buffer (vim.current.buffer)
        filetype (str)

    Returns:
        url (str)
    """
    lines = os.linesep.join(list(buffer))
    url = 'http://paste.ubuntu.com/'
    data = urlencode(
        {
            'poster': os.environ.get('USER', 'anonymous'),
            'syntax': filetype if filetype in PASTEBIN_SUPPORT_TYPE else 'text',
            'content': lines
        }
    ).encode('utf-8')
    r = requests.post(url, data=data, allow_redirects=True)
    if open_in_borwser:
        webbrowser.open_new_tab(r.url)
    return r.url

这样发送文本内容到 paste.ubuntu.com 的需求就完成了,还差一步就是怎么在vim里能够使用这段python代码。接下来编辑 vim_ubuntu_pastebin.vim文件,看着也挺简单:

" --------------------------------
" Add our plugin to the path
" --------------------------------

if !(has('python3') || has('python'))
    echo "Error: Required vim compiled with +python or +python3"
    finish
endif


if has('python3')
python3 import sys
python3 import vim
python3 sys.path.append(vim.eval('expand("<sfile>:h")'))

function! Pastebin()
python3 << endOfPython

from vim_ubuntu_pastebin import post_buffer_to_ubuntu_pastebin
filetype = vim.eval('&filetype')    " 获取文件类型
url = post_buffer_to_ubuntu_pastebin(vim.current.buffer, filetype)
print(url)

endOfPython
endfunction
endif


" 下边代码仅仅是'python3'替换成了'python'
if has('python')
python import sys
python import vim
python sys.path.append(vim.eval('expand("<sfile>:h")'))

function! Pastebin()
python << endOfPython

from vim_ubuntu_pastebin import post_buffer_to_ubuntu_pastebin
filetype = vim.eval('&filetype')
url = post_buffer_to_ubuntu_pastebin(vim.current.buffer, filetype)
print(url)

endOfPython
endfunction
endif

" 定义一个命令Pastebin就可以在终端
command! -nargs=0 Pastebin call Pastebin()

我们直接在这个vim脚本里调用上边的python函数就可以了,使用list(vim.current.buffer)就能获取当前buffer里每行的内容,用换行符拼起来,之后把内容发送出去就好了。这样就实现了一个简单的vim插件了,是不是挺容易的。把当前项目推到github上,别人就可以使用Bundle等工具使用你的插件了。亲测有效,以后贴代码方便多了,在你的vim里头执行命令 :Pastebin 当前文件就自动贴到 paste.ubuntu.com 上并且输出url了,如果是桌面版操作系统还会自动帮你打开页面。


Ref:

vim-ubuntu-pastebin - 上边小项目的代码
Vim documentation: if_pyth - 你需要看一下python操作vim buffer的接口