All patches and comments are welcome. Please squash your changes to logical
commits before using git-format-patch and git-send-email to
patches@git.madduck.net.
If you'd read over the Git project's submission guidelines and adhered to them,
I'd be especially grateful.
1 " https://github.com/prabirshrestha/async.vim#2082d13bb195f3203d41a308b89417426a7deca1 (dirty)
2 " :AsyncEmbed path=./autoload/lsp/utils/job.vim namespace=lsp#utils#job
4 " Author: Prabir Shrestha <mail at prabir dot me>
5 " Website: https://github.com/prabirshrestha/async.vim
6 " License: The MIT License {{{
7 " The MIT License (MIT)
9 " Copyright (c) 2016 Prabir Shrestha
11 " Permission is hereby granted, free of charge, to any person obtaining a copy
12 " of this software and associated documentation files (the "Software"), to deal
13 " in the Software without restriction, including without limitation the rights
14 " to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 " copies of the Software, and to permit persons to whom the Software is
16 " furnished to do so, subject to the following conditions:
18 " The above copyright notice and this permission notice shall be included in all
19 " copies or substantial portions of the Software.
21 " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 " IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 " FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 " AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 " LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 " OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
34 let s:jobs = {} " { job, opts, type: 'vimjob|nvimjob'}
35 let s:job_type_nvimjob = 'nvimjob'
36 let s:job_type_vimjob = 'vimjob'
37 let s:job_error_unsupported_job_type = -2 " unsupported job type
39 function! s:noop(...) abort
42 function! s:job_supported_types() abort
43 let l:supported_types = []
45 let l:supported_types += [s:job_type_nvimjob]
47 if !has('nvim') && has('job') && has('channel') && has('lambda')
48 let l:supported_types += [s:job_type_vimjob]
50 return l:supported_types
53 function! s:job_supports_type(type) abort
54 return index(s:job_supported_types(), a:type) >= 0
57 function! s:out_cb(jobid, opts, job, data) abort
58 call a:opts.on_stdout(a:jobid, a:data, 'stdout')
61 function! s:out_cb_array(jobid, opts, job, data) abort
62 call a:opts.on_stdout(a:jobid, split(a:data, "\n", 1), 'stdout')
65 function! s:err_cb(jobid, opts, job, data) abort
66 call a:opts.on_stderr(a:jobid, a:data, 'stderr')
69 function! s:err_cb_array(jobid, opts, job, data) abort
70 call a:opts.on_stderr(a:jobid, split(a:data, "\n", 1), 'stderr')
73 function! s:exit_cb(jobid, opts, job, status) abort
74 if has_key(a:opts, 'on_exit')
75 call a:opts.on_exit(a:jobid, a:status, 'exit')
77 if has_key(s:jobs, a:jobid)
78 call remove(s:jobs, a:jobid)
82 function! s:on_stdout(jobid, data, event) abort
83 let l:jobinfo = s:jobs[a:jobid]
84 call l:jobinfo.opts.on_stdout(a:jobid, a:data, a:event)
87 function! s:on_stdout_string(jobid, data, event) abort
88 let l:jobinfo = s:jobs[a:jobid]
89 call l:jobinfo.opts.on_stdout(a:jobid, join(a:data, "\n"), a:event)
92 function! s:on_stderr(jobid, data, event) abort
93 let l:jobinfo = s:jobs[a:jobid]
94 call l:jobinfo.opts.on_stderr(a:jobid, a:data, a:event)
97 function! s:on_stderr_string(jobid, data, event) abort
98 let l:jobinfo = s:jobs[a:jobid]
99 call l:jobinfo.opts.on_stderr(a:jobid, join(a:data, "\n"), a:event)
102 function! s:on_exit(jobid, status, event) abort
103 if has_key(s:jobs, a:jobid)
104 let l:jobinfo = s:jobs[a:jobid]
105 if has_key(l:jobinfo.opts, 'on_exit')
106 call l:jobinfo.opts.on_exit(a:jobid, a:status, a:event)
108 if has_key(s:jobs, a:jobid)
109 call remove(s:jobs, a:jobid)
114 function! s:job_start(cmd, opts) abort
115 let l:jobtypes = s:job_supported_types()
118 if has_key(a:opts, 'type')
119 if type(a:opts.type) == type('')
120 if !s:job_supports_type(a:opts.type)
121 return s:job_error_unsupported_job_type
123 let l:jobtype = a:opts.type
125 let l:jobtypes = a:opts.type
130 " find the best jobtype
131 for l:jobtype2 in l:jobtypes
132 if s:job_supports_type(l:jobtype2)
133 let l:jobtype = l:jobtype2
139 return s:job_error_unsupported_job_type
142 " options shared by both vim and neovim
144 if has_key(a:opts, 'cwd')
145 let l:jobopt.cwd = a:opts.cwd
147 if has_key(a:opts, 'env')
148 let l:jobopt.env = a:opts.env
151 let l:normalize = get(a:opts, 'normalize', 'array') " array/string/raw
153 if l:jobtype == s:job_type_nvimjob
154 if l:normalize ==# 'string'
155 let l:jobopt['on_stdout'] = has_key(a:opts, 'on_stdout') ? function('s:on_stdout_string') : function('s:noop')
156 let l:jobopt['on_stderr'] = has_key(a:opts, 'on_stderr') ? function('s:on_stderr_string') : function('s:noop')
158 let l:jobopt['on_stdout'] = has_key(a:opts, 'on_stdout') ? function('s:on_stdout') : function('s:noop')
159 let l:jobopt['on_stderr'] = has_key(a:opts, 'on_stderr') ? function('s:on_stderr') : function('s:noop')
161 call extend(l:jobopt, { 'on_exit': function('s:on_exit') })
162 let l:job = jobstart(a:cmd, l:jobopt)
166 let l:jobid = l:job " nvimjobid and internal jobid is same
167 let s:jobs[l:jobid] = {
168 \ 'type': s:job_type_nvimjob,
171 let s:jobs[l:jobid].job = l:job
172 elseif l:jobtype == s:job_type_vimjob
173 let s:jobidseq = s:jobidseq + 1
174 let l:jobid = s:jobidseq
175 if l:normalize ==# 'array'
176 let l:jobopt['out_cb'] = has_key(a:opts, 'on_stdout') ? function('s:out_cb_array', [l:jobid, a:opts]) : function('s:noop')
177 let l:jobopt['err_cb'] = has_key(a:opts, 'on_stderr') ? function('s:err_cb_array', [l:jobid, a:opts]) : function('s:noop')
179 let l:jobopt['out_cb'] = has_key(a:opts, 'on_stdout') ? function('s:out_cb', [l:jobid, a:opts]) : function('s:noop')
180 let l:jobopt['err_cb'] = has_key(a:opts, 'on_stderr') ? function('s:err_cb', [l:jobid, a:opts]) : function('s:noop')
182 call extend(l:jobopt, {
183 \ 'exit_cb': function('s:exit_cb', [l:jobid, a:opts]),
186 if has('patch-8.1.889')
187 let l:jobopt['noblock'] = 1
189 let l:job = job_start(a:cmd, l:jobopt)
190 if job_status(l:job) !=? 'run'
193 let s:jobs[l:jobid] = {
194 \ 'type': s:job_type_vimjob,
197 \ 'channel': job_getchannel(l:job),
201 return s:job_error_unsupported_job_type
207 function! s:job_stop(jobid) abort
208 if has_key(s:jobs, a:jobid)
209 let l:jobinfo = s:jobs[a:jobid]
210 if l:jobinfo.type == s:job_type_nvimjob
211 " See: vital-Whisky/System.Job
213 call jobstop(a:jobid)
214 catch /^Vim\%((\a\+)\)\=:E900/
216 " Vim does not raise exception even the job has already closed so fail
217 " silently for 'E900: Invalid job id' exception
219 elseif l:jobinfo.type == s:job_type_vimjob
220 if type(s:jobs[a:jobid].job) == v:t_job
221 call job_stop(s:jobs[a:jobid].job)
222 elseif type(s:jobs[a:jobid].job) == v:t_channel
223 call ch_close(s:jobs[a:jobid].job)
229 function! s:job_send(jobid, data, opts) abort
230 let l:jobinfo = s:jobs[a:jobid]
231 let l:close_stdin = get(a:opts, 'close_stdin', 0)
232 if l:jobinfo.type == s:job_type_nvimjob
233 call jobsend(a:jobid, a:data)
235 call chanclose(a:jobid, 'stdin')
237 elseif l:jobinfo.type == s:job_type_vimjob
238 " There is no easy way to know when ch_sendraw() finishes writing data
239 " on a non-blocking channels -- has('patch-8.1.889') -- and because of
240 " this, we cannot safely call ch_close_in(). So when we find ourselves
241 " in this situation (i.e. noblock=1 and close stdin after send) we fall
242 " back to using s:flush_vim_sendraw() and wait for transmit buffer to be
245 " Ref: https://groups.google.com/d/topic/vim_dev/UNNulkqb60k/discussion
246 if has('patch-8.1.818') && (!has('patch-8.1.889') || !l:close_stdin)
247 call ch_sendraw(l:jobinfo.channel, a:data)
249 let l:jobinfo.buffer .= a:data
250 call s:flush_vim_sendraw(a:jobid, v:null)
253 while len(l:jobinfo.buffer) != 0
256 call ch_close_in(l:jobinfo.channel)
261 function! s:flush_vim_sendraw(jobid, timer) abort
262 " https://github.com/vim/vim/issues/2548
263 " https://github.com/natebosch/vim-lsc/issues/67#issuecomment-357469091
264 let l:jobinfo = s:jobs[a:jobid]
266 if len(l:jobinfo.buffer) <= 4096
267 call ch_sendraw(l:jobinfo.channel, l:jobinfo.buffer)
268 let l:jobinfo.buffer = ''
270 let l:to_send = l:jobinfo.buffer[:4095]
271 let l:jobinfo.buffer = l:jobinfo.buffer[4096:]
272 call ch_sendraw(l:jobinfo.channel, l:to_send)
273 call timer_start(1, function('s:flush_vim_sendraw', [a:jobid]))
277 function! s:job_wait_single(jobid, timeout, start) abort
278 if !has_key(s:jobs, a:jobid)
282 let l:jobinfo = s:jobs[a:jobid]
283 if l:jobinfo.type == s:job_type_nvimjob
284 let l:timeout = a:timeout - reltimefloat(reltime(a:start)) * 1000
285 return jobwait([a:jobid], float2nr(l:timeout))[0]
286 elseif l:jobinfo.type == s:job_type_vimjob
287 let l:timeout = a:timeout / 1000.0
289 while l:timeout < 0 || reltimefloat(reltime(a:start)) < l:timeout
290 let l:info = job_info(l:jobinfo.job)
291 if l:info.status ==# 'dead'
292 return l:info.exitval
293 elseif l:info.status ==# 'fail'
298 catch /^Vim:Interrupt$/
305 function! s:job_wait(jobids, timeout) abort
306 let l:start = reltime()
309 for l:jobid in a:jobids
310 if l:exitcode != -2 " Not interrupted.
311 let l:exitcode = s:job_wait_single(l:jobid, a:timeout, l:start)
313 let l:ret += [l:exitcode]
318 function! s:job_pid(jobid) abort
319 if !has_key(s:jobs, a:jobid)
323 let l:jobinfo = s:jobs[a:jobid]
324 if l:jobinfo.type == s:job_type_nvimjob
325 return jobpid(a:jobid)
326 elseif l:jobinfo.type == s:job_type_vimjob
327 let l:vimjobinfo = job_info(a:jobid)
328 if type(l:vimjobinfo) == type({}) && has_key(l:vimjobinfo, 'process')
329 return l:vimjobinfo['process']
335 function! s:callback_cb(jobid, opts, ch, data) abort
336 if has_key(a:opts, 'on_stdout')
337 call a:opts.on_stdout(a:jobid, a:data, 'stdout')
341 function! s:callback_cb_array(jobid, opts, ch, data) abort
342 if has_key(a:opts, 'on_stdout')
343 call a:opts.on_stdout(a:jobid, split(a:data, "\n", 1), 'stdout')
347 function! s:close_cb(jobid, opts, ch) abort
348 if has_key(a:opts, 'on_exit')
349 call a:opts.on_exit(a:jobid, 'closed', 'exit')
351 if has_key(s:jobs, a:jobid)
352 call remove(s:jobs, a:jobid)
357 function! lsp#utils#job#start(cmd, opts) abort
358 return s:job_start(a:cmd, a:opts)
361 function! lsp#utils#job#stop(jobid) abort
362 call s:job_stop(a:jobid)
365 function! lsp#utils#job#send(jobid, data, ...) abort
366 let l:opts = get(a:000, 0, {})
367 call s:job_send(a:jobid, a:data, l:opts)
370 function! lsp#utils#job#wait(jobids, ...) abort
371 let l:timeout = get(a:000, 0, -1)
372 return s:job_wait(a:jobids, l:timeout)
375 function! lsp#utils#job#pid(jobid) abort
376 return s:job_pid(a:jobid)
379 function! lsp#utils#job#connect(addr, opts) abort
380 let s:jobidseq = s:jobidseq + 1
381 let l:jobid = s:jobidseq
383 let l:normalize = get(a:opts, 'normalize', 'array') " array/string/raw
385 let l:ch = ch_open(a:addr, {'waittime': 1000})
386 call ch_setoptions(l:ch, {
387 \ 'callback': function(l:normalize ==# 'array' ? 's:callback_cb_array' : 's:callback_cb', [l:jobid, a:opts]),
388 \ 'close_cb': function('s:close_cb', [l:jobid, a:opts]),
391 if ch_status(l:ch) ==# 'open'
397 let s:jobs[l:jobid] = {
398 \ 'type': s:job_type_vimjob,
408 let &cpo = s:save_cpo