概述

Vim从8.0版本开始支持异步IO,通过job来开始执行另一个进程,通过channel来进行进程通信。

只要Vim的版本高于8.0,并且在编译时有+channel+job的feature,就可以通过使用Vim的异步支持来让插件或脚本拥有更好的体验,比如异步编译使Vim在编译时不在阻塞、异步语法高亮来时Vim获得更快的速度等。

使用

has('channel')
has('job')

来判断Vim是否有相关支持。

我有一个在Vim中翻译单词的插件,但在网络不好的情况下翻译会阻塞正常的浏览,本文是我希望插件可以进行异步翻译而进行的学习笔记,只简单记录了job的工作方式,更多详细内容请在Vim中使用:help channel查看。

job的工作方式

job可以理解为Linux中的进程,通过:

job_start(command, {options})

可以在这个一个进程(job)中执行command,job_start返回job对象,使用

let channel = job_getchannel(job)

可以获得channel用于进程(job)之间通信,但我的目的只是异步执行翻译的job,用不到在job之间通信,所以不再讨论。

command是要执行的外部命令,如果要获取命令的执行结果或状态,可以使用options设置回调函数来完成。

处理任务输出

捕获每次输出

如果command产生输出,可以使用out_cb定义回调函数处理输出:

func! Handler(channel, msg)
    " deal with msg
endfunc

let job = job_start(command, {'out_cb': 'Handler'})

cb表示callback

或者可以使用ch_read(job)或者cd_readraw(job)读取job产生的输出。

捕获结束输出

如果不想获取处理任务的中间输出,可以使用close_cb定义回调函数获取结束job的输出:

func! CloseHandler(channel)
  while ch_status(a:channel, {'part': 'out'}) == 'buffered'
    echomsg ch_read(a:channel)
  endwhile
endfunc

let job = job_start(command, {'close_cb': 'CloseHandler'})
处理任务的错误输出

out_cb指定的回调函数不会接收标准错误输出,可以使用err_cb指定的回调函处理错误输出:

let job = job_start(command, {'out_cb': 'Handler',
        \			          'err_cb': 'ErrHandler'})

callback指定的回调函数既可以接收错误输出,也可以接收普通输出。

let job = job_start(command, {"callback": "MyHandler"})

控制job

得到job状态:

job_status(job)

停止job:

job_stop(job)

示例

如果我们有一个耗时3s的外部程序要执行,程序执行之后输出日期,如果使用过去的方法,将其映射到<F3>键(这里使用sleep 3s模拟该耗时程序):

nnoremap <F3> :!/bin/bash -c 'sleep 3s; date'<CR>

点击<F3>后Vim将有3s处于阻塞状态,无法进行任何操作。

接下来使用异步来处理这个程序:

" 回调函数
func! Handler(channel, msg)
    echo a:msg
endfunc

" 执行job
func! GetDate()
    call job_start(['/bin/bash', '-c', 'sleep 3s; date'], {'callback': 'Handler'})
endfunc

nnoremap <F3> :call GetDate()<cr>

此时再点击<F3>,Vim将不会阻塞,可以继续进行操作,并在3s后输出日期。