解析 neovim 的 remote plugin 機制

解析 neovim 的 remote plugin 機制

來自專欄 Vim19 人贊了文章

neovim 添加的主要功能例如: 非同步任務,自帶 terminal 都在 vim 中實現了,儘管實現有所差別,但是使用上並沒有太大問題。本文所要介紹的是 neovim 的獨有功能: remote plugin。

主要內容:

  • 什麼是 remote plugin
  • remote plugin 主要優勢
  • remote plugin 存在的問題
  • 解決 remote plugin 的問題
  • 讓 vim 支持 remote plugin

什麼是 remote plugin

在 neovim 中使用 :h remote-plugin 可以查看官方文檔,簡單的說就是 neovim 在啟動時開啟了一個基於 named pipes 的服務(使用 :echo $NVIM_LISTEN_ADDRESS 查看),開發者可以用其它編程語言開發的插件連接這個服務,進而與 neovim 交互。

為了幫助用戶避免處理底層的一些問題,neovim 對一些常見語言提供了 host 支持,目前默認支持 python,nodejs 和 ruby。

Host 可以幫助開發者降低開發的難度,例如可以這樣在 python 文件聲明一個 vim 的函數:

@neovim.function(Func) def function_handler(self, args): self._increment_calls() self.vim.current.line = ( Function: Called %d times, args: %s % (self.calls, args))

執行::UpdateRemotePlugins 後再重啟 neovim,你就可以使用 python 裡面定義的 vim 函數了。

remote plugin 主要優勢

  • 不需要交叉編譯,因為是基於 client server 的方式,所以不用像 vim 一樣需要編譯時配置。
  • 容易編寫和維護。

vimscript 做為一個古老的腳本語言,很容易不小心就掉坑裡,儘管寫起來比較方便,但是一旦插件變得複雜,其編寫和維護的成本都是比較高的。個人比較推薦基於 typescript 的 node-client,藉助 tsserver 的智能支持,可以降低很多開發和重構的成本。

  • 按需載入,啟動更快。

remote plugin 的 host 只有在第一次調用相關方法時才會啟動,所以理論上不會增加 vim 的啟動時間,同時也沒有按需載入的必要。

  • 更好的 io 效率,藉助底層的 libuv,neovim 的 io 效率要比 vim 的 job 好不少。

remote plugin 存在的問題

  • 大幅增加 vim 啟動時間。

如果你的 vim 插件在啟動時調用了 python 就很可能碰到啟動時間大幅增加的問題,主要的原因是 neovim 完成 host 的初始化相當耗時,這個過程主要包含以下幾步:

    • 從環境變數或者命令調用等方式找出 host 啟動文件
    • 啟動 host 服務
    • 客戶端調用 nvim_get_api_info 方法獲取 channelId 以及 api 的 meta
    • nvim 調用一個 `rpcrequrest("poll")` 方法確保 host 服務正常

第一個和最後一步通常都會非常耗時。

為了避免第一步的查找,可以在 init.vim 中將 host 的位置寫死,例如:

let g:python_host_prog = /usr/local/bin/pythonlet g:python3_host_prog = /usr/local/bin/python3let g:ruby_host_prog = exepath(neovim-ruby-host)let g:node_host_prog = /usr/local/lib/node_modules/neovim/bin/cli.js

為了解決 rpcrequest 調用慢的問題,可以把它去掉,參考 neovim/issues/5728#issuecomment-406475616,但是會有錯誤無法及時發現的副作用。

理想的情況是不要在 vim 啟動時初始化 remote plugin 的 host。

某些插件例如 fcitx.vim 會在 plugin 的腳本裡面直接調用 pyfile命令, 所以以上辦法無法完全解決載入慢的問題,這種插件可以考慮將函數定義在 autoload 文件裡面,然後在 autoload 文件里執行 pyfile, 這樣就避免了啟動時載入 python host。

  • API 更新後用戶需要執行 :UpdateRemotePlugins來更新插件的介面,因為某些插件管理插件並不支持更新後自動執行,所以可能導致 bug
  • 用戶需要額外安裝 remote host 模塊,例如為了使用 node 插件,用戶需要使用

npm i -g neovim

來安裝 host。如果開發者想使用新的功能,還需要讓用戶更新這個全局的 remote host 模塊。

  • 不同插件存在互相影響,儘管 node-client 使用了 sandbox 方式隔離不同 node 插件的運行環境,它們還是共用了 process 模塊,所以開發者也不能在插件中捕獲 uncaughtException 以及 unhandledRejection ,因為這可能會捕獲到其它插件的錯誤。

解決 remote plugin 的問題

現在的問題都是 host 造成的,所以通過避免使用 host 就可以解決以上問題,其實現也不是非常麻煩。

以 node 為例, 實現監聽 neovim 的 rpc 服務 server.js

const attach = require(neovim).attachconst nvim = attach({ socket: process.env.NVIM_LISTEN_ADDRESS})nvim.on(notification, (method, args) => { switch (method) { case VimEnter: // TODO dosomething onEnter return }})nvim.on(request, (method, args, resp) => { switch (method) { case Action: // TODO dosomthing on Action return }})nvim.channelId.then(channelId => { // assign channel id to vim global variable nvim.setVar(my_channel_id, channelId)})

啟動 rpc 服務:

let s:job_opts = {}let s:std_err = []call jobstart([node, /path/to/server.js], s:job_opts)function! s:job_opts.on_stderr(chan_id, data, event) dict call extend(s:std_err, a:data)endfunctionfunction! s:job_opts.on_exit(chan_id, code, event) dict let g:my_channel_id = 0 if a:code != 0 for msg in s:std_err echoerr msg endfor endifendfunction

調用 rpc 服務:

autocmd VimEnter * call s:notify(VimEnter, [])call s:request(Action, [])function s:notify(method, args) if !get(g:, my_channel_id, 0) | return | endif call call(rpcnotify, [g:my_channel_id, a:method] + a:args)endfunctionfunction s:request(method, args) if !get(g:, my_channel_id, 0) | return | endif call call(rpcrequest, [g:my_channel_id, a:method] + a:args)endfunction

讓 vim 支持 remote plugin

可以使用 vim-hug-neovim-rpc 或者 vim-node-rpc 。前者只能支持 python 插件而且需要 vim 有 python 支持,後者性能更好,而且可以支持 node, 但是目前沒有 host 相關功能。

對比數據:

1000 次從 vim 同步請求 client 數據對比

1000 次從 client 同步調用 vim 函數獲取數據對比


如果你想體驗一個實用的 node 插件,可以了解一下

neoclide/coc.nvim?

github.com圖標
推薦閱讀:

理解使用 vim 中的正則表達式
Vim操作記錄
11 個超棒的 Vi 技巧和竅門
VIM學習筆記 縮進 (Indent)
Linux基礎--vim以及bash

TAG:計算機科學 | Vim |