使用 Ruby 拓展 Vim

shutFuckingup 2019-06-26

作为一款历史悠久的编辑器,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):

  1. 通过 Vim::evaluate(expr) 的方式执行任意 Vimscript 表达式并获得其结果。这种方法用于获取 Vim 变量,比如 :ruby p Vim::evaluate(g:maplocalleader)
  2. 通过 Vim::BufferVim::Window 两个子模块,获取 Buffer 或 Window 的各种状态。比如 :ruby p Vim::Buffer.current[1] 会返回第一行的内容。可惜的是,没有 Tab 模块。

对应有两种方法可以修改当前 Vim 状态(输出 API):

  1. 通过 Vim::command(cmd) 的方式执行任意命令。其效果等同于 :cmd。比如 :ruby Vim::command('set paste') 其实就是 :set paste 的意思。
  2. 通过 Vim::BufferVim::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 了。

相关推荐