2022.09.24
你可以用其他任何语言为 Neovim 开发插件,我听到这个的时候觉得很不可思议,到底是怎么做到的,Neovim 的插件,不对,任何 IDE 的插件都应该有对应的开发语言吧,怎么做到用别的语言开发的?
抱着这个疑惑我开始调查了,发现原理其实很简单,插件只提供服务不就行了吗,就像前端和后端,插件这里充当后端的角色,当 Neovim 要比如格式化一个 buffer 的时候,你只需要将 buffer 的内容(比如)给到插件,插件调用自己写的某个函数将其格式化,然后将格式化的内容再发给 Neovim 不就可以了吗,Neovim 再调用自己的 API 来处理 UI 相关的操作。
当插件让 Neovim 新创建一个 buffer 呢?同样,Neovim 在启动的时候会创建一个默认的 RPC 地址用来监听,当插件通过 RPC 向 Neovim 发送消息后,Neovim 进行解析,然后创建一个 buffer 来进行 UI 相关的操作。(至于什么是 RPC 呢,我们下面会讲)
觉得很妙。
RPC 的全称就是 Remote procedure call,其实也很好理解,还是 Neovim 和它的插件的例子,我们要格式化的时候,我们要调用一个函数来进行格式化,这个函数可以是 Neovim 这个宿主自己定义的函数,也可以是我们为其开发的插件里面定义的函数,我们发个(call)请求到远程(Remote)的插件里,然后调用插件里面定义的函数(procedure),然后返回到 Neovim 进行 UI 的更新。
下面再来具体讲下如何用 nodejs 来进行一个 remote plugin 的插件开发,
在 Neovim 初始化的时候会默认创建一个 RPC Socket,你可以在使用 nvim 创建一个 Neovim 实例后,输入,:echo serverlist() 来查看 Neovim 监听的地址。
为了方便,我们直接在 ~/.config/nvim/rplugin 目录下创建一个 node 文件夹,然后在里面创建一个 index.js 文件,
function onBufWrite() {
console.log('Buffer written!');
}
module.exports = (plugin) => {
function setLine() {
const line = process.env.NVIM;
plugin.nvim.setLine(line);
}
plugin.registerCommand('SetMyLine', [plugin.nvim.buffer, setLine]);
plugin.registerAutocmd('BufWritePre', onBufWrite, { pattern: '*' });
};
这样一个简单的插件就开发完成了,然后你需要执行 :UpdateRemotePlugins 命令,来生成这些 remote plugin 的 manifest,你可以在 ~/.local/share/nvim/rplugin.vim 这里看到,
" node plugins
call remote#host#RegisterPlugin('node', '/Users/cary/workspace/github/dotfiles/.config/nvim/rplugin/node/index.js', [
\ {'sync': v:false, 'name': 'BufWritePre', 'type': 'autocmd', 'opts': {'pattern': '*'}},
\ {'sync': v:null, 'name': 'SetMyLine', 'type': 'command', 'opts': {}},
\ ])
诶,但是这里我们还没看到 RPC 通信的部分,我们只看到了 plugin.registerCommand('SetMyLine', [plugin.nvim.buffer, setLine]);,当我们执行 :SetMyLine 的时候,就会进行 RPC 通信了,当然偏底层的创建 Socket 等和 Neovim 进行通信的这些模版我们每次写一个插件都要重新写的话就太浪费时间了,所以 Neovim 官方将这些通信的部分做了封装,发了个叫 neovim 的 npm 包,也就是所谓的 host,我们开发的插件利用的是这个 host 提供的能力来快速开发一个 remote plugin。
我们使用 npm install -g neovim 安装了个 nodejs 的 host 用来进行 RPC 通信。
然后我们用 Neovim 创建一个 buffer 然后使用 : SetMyLine 命令就可以看到效果了,到此一个插件的开发就完成了。
我们再来做个测试,如果我们将这个 host 卸载掉会怎么样,然后执行 :UpdateRemotePlugins 命令,发现报了 Cannot find the "neovim" node package 错,难道 neovim 包是必须的吗?但是肯定也有其他的 nodejs 的 Neovim 的 RPC host 才对吧?
然后我看到了这个 Neovim 的 PR,然后我全局搜了下,发现 node-client 这个之前克隆下来的包里的 package.json 里面有,
"bin": {
"neovim-node-host": "./bin/cli.js"
},
然后再搜了下这个 PR 对应的文档发现,如果你要使用 nodejs 开发 Neovim 的插件的话,它会去 npm root -g 里找 neovim-node-host 二进制可执行文件,而这个文件是我们在 npm install -g neovim 的时候安装上去的,然后你在 npmjs 上找一下就会发现,node-client 发包用的包名就是 neovim,这样也就解释的通了,package.json 里面指定的 bin 字段,在你安装这个 npm 包的时候会自动被链接到全局的 bin 文件所在的目录了,这样 Neovim 就可以直接通过 neovim-node-host 跑这个命令来启动一个 RPC host 了。(npm install -g neovim 安装完后,你就可以在命令行里直接输入 neovim-node-host 了,你可以直接测试下)

那么不使用这个 host 也可以开发吗?理论上应该是可以的,但是具体怎么操作我还要研究下,就在这挖个坑吧 :3
想起,之前使用 Neovim 的时候使用的一个自动补全的一个插件 Conquer of Completion 也是用 nodejs 写的,然后去看了下去看了下它的 package.json 文件,结果发现它引用了 @chemzqm/neovim 这个包,而这个包是我们 npm install -g neovim 里的 neovim 这个包的 fork,但是它没有在全局装,所以后面还得再看看。
另外可以使用 echo $NVIM_LOG_FILE 命令查看 Neovim 的 log 日志地址。