"============================================================================= " job.vim --- job api " Copyright (c) 2016-2017 Wang Shidong & Contributors " Author: Wang Shidong < wsdjeg at 163.com > " URL: https://spacevim.org " License: GPLv3 "============================================================================= "" " @section job, api-job " @parentsection api " provides some functions to manager job " " start({cmd}[, {opt}]) " " spawns {cmd} as a job. {opts} is a dictionary with these keys: " " on_stdout: stdout event handler (function name or Funcref) " " on_stderr: stderr event handler (function name or Funcref) " " on_exit: exit event handler (function name or Funcref) " " cwd: working directory of the job; defaults to current directory function! SpaceVim#api#job#get() abort return deepcopy(s:self) endfunction " make vim and neovim use same job func. let s:self = {} let s:self.jobs = {} let s:self.nvim_job = has('nvim') let s:self.vim_job = !has('nvim') && has('job') && has('patch-8.0.0027') let s:self.vim_co = SpaceVim#api#import('vim#compatible') let s:self._message = [] if !s:self.nvim_job && !s:self.vim_job augroup SpaceVim_job au! au! User SpaceVim_job_stdout nested call call(s:self.opts.on_stdout, s:self.job_argv) au! User SpaceVim_job_stderr nested call call(s:self.opts.on_stderr, s:self.job_argv) au! User SpaceVim_job_exit nested call call(s:self.opts.on_exit, s:self.job_argv) augroup ENd endif function! s:self.warn(...) abort if len(a:000) == 0 echohl WarningMsg | echom 'Current version do not support job feature, fallback to sync system()' | echohl None elseif len(a:000) == 1 && type(a:1) == type('') echohl WarningMsg | echom a:1| echohl None else endif endfunction function! s:self.warp(argv, opts) abort let obj = {} let obj._argv = a:argv let obj._opts = a:opts let obj.in_io = get(a:opts, 'in_io', 'pipe') " @vimlint(EVL103, 1, a:job_id) function! obj._out_cb(job_id, data) abort if has_key(self._opts, 'on_stdout') call self._opts.on_stdout(self._opts.jobpid, [a:data], 'stdout') endif endfunction function! obj._err_cb(job_id, data) abort if has_key(self._opts, 'on_stderr') call self._opts.on_stderr(self._opts.jobpid, [a:data], 'stderr') endif endfunction function! obj._exit_cb(job_id, data) abort if has_key(self._opts, 'on_exit') call self._opts.on_exit(self._opts.jobpid, a:data, 'exit') endif endfunction " @vimlint(EVL103, 0, a:job_id) let obj = { \ 'argv': a:argv, \ 'opts': { \ 'mode': 'nl', \ 'in_io' : obj.in_io, \ 'out_cb': obj._out_cb, \ 'err_cb': obj._err_cb, \ 'exit_cb': obj._exit_cb, \ } \ } if has_key(a:opts, 'cwd') call extend(obj.opts, {'cwd' : a:opts.cwd}) endif return obj endfunction " start a job, and return the job_id. function! s:self.start(argv, ...) abort if self.nvim_job try if len(a:000) > 0 let job = jobstart(a:argv, a:1) else let job = jobstart(a:argv) endi catch /^Vim\%((\a\+)\)\=:E903/ return -1 endtry if job > 0 let msg = ['process '. jobpid(job), ' run'] call extend(self.jobs, {job : msg}) else if job == -1 call add(self._message, 'Failed to start job:' . (type(a:argv) == 3 ? a:argv[0] : a:argv) . ' is not executeable') elseif job == 0 call add(self._message, 'Failed to start job: invalid arguments') endif endif return job elseif self.vim_job if len(a:000) > 0 let opts = a:1 else let opts = {} endif let id = len(self.jobs) + 1 let opts.jobpid = id let wrapped = self.warp(a:argv, opts) if has_key(wrapped.opts, 'cwd') && !has('patch-8.0.0902') let old_wd = getcwd() let cwd = expand(wrapped.opts.cwd, 1) " Avoid error E475: Invalid argument: cwd call remove(wrapped.opts, 'cwd') exe 'cd' fnameescape(cwd) endif let job = job_start(wrapped.argv, wrapped.opts) if exists('old_wd') exe 'cd' fnameescape(old_wd) endif call extend(self.jobs, {id : job}) return id else if len(a:000) > 0 let opts = a:1 else let opts = {} endif if has_key(opts, 'cwd') let old_wd = getcwd() let cwd = expand(opts.cwd, 1) exe 'cd' fnameescape(cwd) endif let output = self.vim_co.systemlist(a:argv) if exists('old_wd') exe 'cd' fnameescape(old_wd) endif let id = -1 let s:self.opts = opts if v:shell_error if has_key(opts,'on_stderr') let s:self.job_argv = [id, output, 'stderr'] try doautocmd User SpaceVim_job_stderr catch doautocmd User SpaceVim_job_stderr endtry endif else if has_key(opts,'on_stdout') let s:self.job_argv = [id, output, 'stdout'] try doautocmd User SpaceVim_job_stdout catch doautocmd User SpaceVim_job_stdout endtry endif endif if has_key(opts,'on_exit') let s:self.job_argv = [id, v:shell_error, 'exit'] try doautocmd User SpaceVim_job_exit catch doautocmd User SpaceVim_job_exit endtry endif return id endif endfunction function! s:self.stop(id) abort if self.nvim_job if has_key(self.jobs, a:id) call jobstop(a:id) call remove(self.jobs, a:id) else call self.warn('[job API] Failed to stop job :' . a:id) endif elseif self.vim_job if has_key(self.jobs, a:id) call job_stop(get(self.jobs, a:id)) call remove(self.jobs, a:id) endif else call self.warn() endif endfunction function! s:self.send(id, data) abort if self.nvim_job if has_key(self.jobs, a:id) if type(a:data) == type('') call jobsend(a:id, [a:data, '']) else call jobsend(a:id, a:data) endif else call self.warn('[job API] Failed to send data to job: ' . a:id) endif elseif self.vim_job if has_key(self.jobs, a:id) let job = get(self.jobs, a:id) let chanel = job_getchannel(job) if type(a:data) == type('') call ch_sendraw(chanel, a:data . "\n") else call ch_sendraw(chanel, join(a:data, "\n")) endif else call self.warn('[job API] Failed to send data to job: ' . a:id) endif else call self.warn() endif endfunction function! s:self.status(id) abort if self.nvim_job if has_key(self.jobs, a:id) return get(self.jobs, a:id)[1] endif elseif self.vim_job if has_key(self.jobs, a:id) return job_status(get(self.jobs, a:id)) endif else call self.warn('[job API] Failed to get job status: ' . a:id) endif endfunction function! s:self.list() abort return copy(self.jobs) endfunction function! s:self.info(id) abort let info = {} if self.nvim_job let info.status = self.status(a:id) let info.job_id = a:id return info elseif self.vim_job if has_key(self.jobs, a:id) return job_info(get(self.jobs, a:id)) else call self.warn('[job API] Failed to get job info: ' . a:id) endif else call self.warn() endif endfunction function! s:self.debug() abort echo join(self._message, "\n") endfunction