4

给 Vim 插件添加上异步调用的功能

 2 years ago
source link: https://hiberabyss.github.io/2018/05/03/add-async-support-for-vim-plugin/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

一些 Vim 插件可能因为计算密集或者是访问网络资源导致命令调用特别慢, 进而导致 Vim 的操作界面被 hang 住, 时不时地 hang 一下还是很影响流畅度和编码心情的 ):

还好在 neovim/vim8 中添加了对异步调用的支持, 我们可以利用 job 机制让耗时的操作在后台执行, 然后通过 callback 机制把处理结束后的结果输出. 加上异步操作后, 各种操作都如丝般润滑, 再也不用担心卡顿了 (:

下面我就以插件 vim-youdao-translater 为例来介绍如何给插件添加上异步调用的功能.

vim-youdao-translater 介绍

这个插件是可以在 Vim 中调用有道词典在线版来翻译英文单词, 官方 repo 库 中有详细的使用介绍.

但这个插件最开始不是异步调用方式, 因为需要访问有道的网络 api , 经常会在翻译的时候会出现卡顿的情况. 我便提交了 Pull Request , 在 neovim/vim8 里把翻译过程变成了异步调用. 可以通过 vim-plug 的方式安装异步调用的版本:

Plug 'https://github.com/ianva/vim-youdao-translater.git', {'branch': 'dev-async'}

安装完成后我们便可以通过 :Ydc 翻译光标所在位置的单词了.

如何添加异步调用

我添加的这个异步调用是通过 neovim/vim8 的 job 机制来实现的, 它的功能和 system 函数类似, 用来执行外部程序, 但不同于 system 调用, 使用 job 执行程序的时候不会等待程序结束时再返回, 而是直接返回, 同时可以在启动 job 的时候增加回调函数来处理外部程序执行的结果.

启动 job 的函数为 jobstart (neovim) 或 job_start (vim8), 可以通过 :help jobstart(:help job_start( 来查看它们的详细使用文档.

具体到 youdao-translater 这个插件, 我们需要通过 jobstart 等来执行一个 python 脚本以执行翻译的操作. 先上代码吧:

let s:translator_file = expand('<sfile>:p:h') . "/../youdao.py"
let s:translator = {'stdout_buffered': v:true, 'stderr_buffered': v:true}

function! s:translator.on_stdout(jobid, data, event)
if !empty(a:data) | echo join(a:data) | endif
endfunction
let s:translator.on_stderr = function(s:translator.on_stdout)

function! s:translator.start(lines)
let python_cmd = ydt#GetAvailablePythonCmd()
if empty(python_cmd)
echoerr "[YouDaoTranslator] [Error]: Python package neeeds to be installed!"
return -1
endif

let cmd = printf("%s %s %s", python_cmd, s:translator_file, a:lines)
if exists('*jobstart')
return jobstart(cmd, self)
elseif exists('*job_start')
return job_start(cmd, {'out_cb': "ydt#VimOutCallback"})
else
echo system(cmd)
endif
endfunction

这里调用 jobstart 的时候是用面向对象的方式来写的, s:translator.on_stdout 是对 stdout 内容的回调函数, 可以看出我们只是简单地调用 echo 把 python 脚本输出的翻译结果打印了出来.

调用 job_start 时的工作机制类似, 只不过是使用了不同的回调函数, 因为 neovim he vim8 的回调函数的参数是不一样的. 函数 ydt#VimOutCallback 的实现如下所示:

function! ydt#VimOutCallback(chan, msg)
echo a:msg
endfunction

这里函数之所以起了这样一个名字, 是因为用到了 vim 的 autoload 机制, 上面的函数是保存在文件 autoload/ydt.vim 中的, 所以函数名需要以 ydt# 开头.

Vim 的 autoload 机制可以提高 Vim 的启动速度, 因为 autoload 目录下的脚本不会在启动时被加载, 它们只有在有关函数被用到时才会找到对应的文件, 然后把整个文件都加载进来.

当实现了上面的异步调用接口之后, 我们便可以实现对应的函数调用和命令了:

function! s:YoudaoCursorTranslate()
call s:translator.start(expand("<cword>"))
endfunction

command! Ydc call <SID>YoudaoCursorTranslate()

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK