使用 Ruby 拓展 Vim
作为一款历史悠久的编辑器,Vim 不仅支持用别具一格的 Vimscript 编写插件,还提供了 Python、Ruby、Lua 和 Perl 等语言对应的接口,甚至包括了对 Tcl 的支持,注意我说的是名为 Tcl 的编程语言,不是某家电品牌。通过这些语言,开发者可以摆脱 Vimscript 的限制,挑选自己最擅长的工具来拓展自己的编辑器。几年前,我曾经心血来潮,学了一段时间 Vimscript,帮忙翻译了《笨方法学Vimscript》一书。然而学到的知识如沙滩上的城堡,早已被时光之潮拍得支离破碎。所以现在要想写点小效果,都会用 Vimscript 搭个脚手架,用其他语言实现具体的逻辑。考虑到 NeoVim 当前并不支持 Lua 和 Perl 等小众语言,出于适用的目标,通常只会选择用 Python 或者 Ruby 来实现。鉴于如何用 Ruby 拓展 Vim 的资料相对缺乏,我决定写下本文,以供后来者参考。
前提
你所用的 Vim 可能不支持 Ruby 拓展。通过键入 :echo has('ruby')
,你可以了解 Ruby 拓展功能是否已启用。幸运的是,从 Linux 包管理器上安装的 Vim 默认是支持 Ruby 的。如果不支持,就只能自己重新编译一份了。值得注意的是,NeoVim 还需要运行 gem install neovim
来下载对应的 Ruby Client。
Hello World
一切先从 Hello World 开始::ruby p 'hello world'
。你会看到一条 “hello world” 打印在编辑器下方。通常的做法是用 Vimscript 写一个函数,在这个函数里面调用 ruby
命令去执行 ruby 代码。像这样:
function! Test() ruby <<EOS p "hello world" EOS endfunction command! Test :call Test()
这里用到了 Vimscript 的 heredoc 语法,让 ruby
命令执行一个多行的 Ruby 代码字符串。最后一行把这个函数映射到 Test
命令上,这样就能通过 :Test
的方式调用它。
如果要写的 Ruby 代码比较多,推荐放到一个独立的文件里面,然后再从 ruby
命令里面 require
进来。记得处理下 ruby 文件加载的路径。
Vim 跟 Ruby 相关的 API 文档可以通过 vert help ruby
看到,整篇说明也不过一两百行。功能是少了点,不过日常写点小玩意,代替成段 Vimscript 还是能做到的。
IO
要想写出超越 Hello World 的代码,不能不了解 Vim 提供的输入输出 API。
Vim 暴露在 Ruby 代码里的 API,都在 Vim
这个模块下面。
有两种方式可以获取当前 Vim 状态(输入 API):
- 通过
Vim::evaluate(expr)
的方式执行任意 Vimscript 表达式并获得其结果。这种方法用于获取 Vim 变量,比如:ruby p Vim::evaluate(g:maplocalleader)
。 - 通过
Vim::Buffer
和Vim::Window
两个子模块,获取 Buffer 或 Window 的各种状态。比如:ruby p Vim::Buffer.current[1]
会返回第一行的内容。可惜的是,没有 Tab 模块。
对应有两种方法可以修改当前 Vim 状态(输出 API):
- 通过
Vim::command(cmd)
的方式执行任意命令。其效果等同于:cmd
。比如:ruby Vim::command('set paste')
其实就是:set paste
的意思。 - 通过
Vim::Buffer
和Vim::Window
去设置 Buffer 或 Window 的状态。比如:ruby Vim::Buffer.current[1] = 'ruby evaluation'
会把第一行变成ruby evaluation
。
More
如果你对用 Ruby 拓展 Vim 感兴趣,而又恰好使用 NeoVim,可以看下这个项目:https://github.com/alexgenco/...
这个项目提供了我们前面安装的 neovim gem。除了 Vim
模块,这个 gem 还在 NeoVim
模块下面放了更多的 API。如果你在写的 Ruby 拓展需要更多的 API,可以考虑给这个 gem 贡献代码。当然,额外引入的新功能就不会兼容原生 Vim 了。