]> git.madduck.net Git - etc/vim.git/blob - .vim/bundle/vim-lsp/autoload/lsp/utils/job.vim

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

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.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

Do not set EDITOR/VISUAL for shell
[etc/vim.git] / .vim / bundle / vim-lsp / autoload / lsp / utils / job.vim
1 " https://github.com/prabirshrestha/async.vim#2082d13bb195f3203d41a308b89417426a7deca1 (dirty)
2 "    :AsyncEmbed path=./autoload/lsp/utils/job.vim namespace=lsp#utils#job
3
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)
8 "
9 "   Copyright (c) 2016 Prabir Shrestha
10 "
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:
17 "
18 "   The above copyright notice and this permission notice shall be included in all
19 "   copies or substantial portions of the Software.
20 "
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
27 "   SOFTWARE.
28 " }}}
29
30 let s:save_cpo = &cpo
31 set cpo&vim
32
33 let s:jobidseq = 0
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
38
39 function! s:noop(...) abort
40 endfunction
41
42 function! s:job_supported_types() abort
43     let l:supported_types = []
44     if has('nvim')
45         let l:supported_types += [s:job_type_nvimjob]
46     endif
47     if !has('nvim') && has('job') && has('channel') && has('lambda')
48         let l:supported_types += [s:job_type_vimjob]
49     endif
50     return l:supported_types
51 endfunction
52
53 function! s:job_supports_type(type) abort
54     return index(s:job_supported_types(), a:type) >= 0
55 endfunction
56
57 function! s:out_cb(jobid, opts, job, data) abort
58     call a:opts.on_stdout(a:jobid, a:data, 'stdout')
59 endfunction
60
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')
63 endfunction
64
65 function! s:err_cb(jobid, opts, job, data) abort
66     call a:opts.on_stderr(a:jobid, a:data, 'stderr')
67 endfunction
68
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')
71 endfunction
72
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')
76     endif
77     if has_key(s:jobs, a:jobid)
78         call remove(s:jobs, a:jobid)
79     endif
80 endfunction
81
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)
85 endfunction
86
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)
90 endfunction
91
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)
95 endfunction
96
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)
100 endfunction
101
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)
107         endif
108         if has_key(s:jobs, a:jobid)
109             call remove(s:jobs, a:jobid)
110         endif
111     endif
112 endfunction
113
114 function! s:job_start(cmd, opts) abort
115     let l:jobtypes = s:job_supported_types()
116     let l:jobtype = ''
117
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
122             endif
123             let l:jobtype = a:opts.type
124         else
125             let l:jobtypes = a:opts.type
126         endif
127     endif
128
129     if empty(l:jobtype)
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
134             endif
135         endfor
136     endif
137
138     if l:jobtype ==? ''
139         return s:job_error_unsupported_job_type
140     endif
141
142     " options shared by both vim and neovim
143     let l:jobopt = {}
144     if has_key(a:opts, 'cwd')
145       let l:jobopt.cwd = a:opts.cwd
146     endif
147     if has_key(a:opts, 'env')
148       let l:jobopt.env = a:opts.env
149     endif
150
151     let l:normalize = get(a:opts, 'normalize', 'array') " array/string/raw
152
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')
157         else " array or raw
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')
160         endif
161         call extend(l:jobopt, { 'on_exit': function('s:on_exit') })
162         let l:job = jobstart(a:cmd, l:jobopt)
163         if l:job <= 0
164             return l:job
165         endif
166         let l:jobid = l:job " nvimjobid and internal jobid is same
167         let s:jobs[l:jobid] = {
168             \ 'type': s:job_type_nvimjob,
169             \ 'opts': a:opts,
170         \ }
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')
178         else " raw or string
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')
181         endif
182         call extend(l:jobopt, {
183             \ 'exit_cb': function('s:exit_cb', [l:jobid, a:opts]),
184             \ 'mode': 'raw',
185         \ })
186         if has('patch-8.1.889')
187           let l:jobopt['noblock'] = 1
188         endif
189         let l:job  = job_start(a:cmd, l:jobopt)
190         if job_status(l:job) !=? 'run'
191             return -1
192         endif
193         let s:jobs[l:jobid] = {
194             \ 'type': s:job_type_vimjob,
195             \ 'opts': a:opts,
196             \ 'job': l:job,
197             \ 'channel': job_getchannel(l:job),
198             \ 'buffer': ''
199         \ }
200     else
201         return s:job_error_unsupported_job_type
202     endif
203
204     return l:jobid
205 endfunction
206
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
212             try
213               call jobstop(a:jobid)
214             catch /^Vim\%((\a\+)\)\=:E900/
215               " NOTE:
216               " Vim does not raise exception even the job has already closed so fail
217               " silently for 'E900: Invalid job id' exception
218             endtry
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)
224             endif
225         endif
226     endif
227 endfunction
228
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)
234         if l:close_stdin
235           call chanclose(a:jobid, 'stdin')
236         endif
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
243         " empty
244         "
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)
248         else
249             let l:jobinfo.buffer .= a:data
250             call s:flush_vim_sendraw(a:jobid, v:null)
251         endif
252         if l:close_stdin
253             while len(l:jobinfo.buffer) != 0
254                 sleep 1m
255             endwhile
256             call ch_close_in(l:jobinfo.channel)
257         endif
258     endif
259 endfunction
260
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]
265     sleep 1m
266     if len(l:jobinfo.buffer) <= 4096
267         call ch_sendraw(l:jobinfo.channel, l:jobinfo.buffer)
268         let l:jobinfo.buffer = ''
269     else
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]))
274     endif
275 endfunction
276
277 function! s:job_wait_single(jobid, timeout, start) abort
278     if !has_key(s:jobs, a:jobid)
279         return -3
280     endif
281
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
288         try
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'
294                     return -3
295                 endif
296                 sleep 1m
297             endwhile
298         catch /^Vim:Interrupt$/
299             return -2
300         endtry
301     endif
302     return -1
303 endfunction
304
305 function! s:job_wait(jobids, timeout) abort
306     let l:start = reltime()
307     let l:exitcode = 0
308     let l:ret = []
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)
312         endif
313         let l:ret += [l:exitcode]
314     endfor
315     return l:ret
316 endfunction
317
318 function! s:job_pid(jobid) abort
319     if !has_key(s:jobs, a:jobid)
320         return 0
321     endif
322
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']
330         endif
331     endif
332     return 0
333 endfunction
334
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')
338     endif
339 endfunction
340
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')
344     endif
345 endfunction
346
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')
350     endif
351     if has_key(s:jobs, a:jobid)
352         call remove(s:jobs, a:jobid)
353     endif
354 endfunction
355
356 " public apis {{{
357 function! lsp#utils#job#start(cmd, opts) abort
358     return s:job_start(a:cmd, a:opts)
359 endfunction
360
361 function! lsp#utils#job#stop(jobid) abort
362     call s:job_stop(a:jobid)
363 endfunction
364
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)
368 endfunction
369
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)
373 endfunction
374
375 function! lsp#utils#job#pid(jobid) abort
376     return s:job_pid(a:jobid)
377 endfunction
378
379 function! lsp#utils#job#connect(addr, opts) abort
380     let s:jobidseq = s:jobidseq + 1
381     let l:jobid = s:jobidseq
382     let l:retry = 0
383     let l:normalize = get(a:opts, 'normalize', 'array') " array/string/raw
384     while l:retry < 5
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]),
389             \ 'mode': 'raw',
390         \})
391         if ch_status(l:ch) ==# 'open'
392             break
393         endif
394         sleep 100m
395         let l:retry += 1
396     endwhile
397     let s:jobs[l:jobid] = {
398         \ 'type': s:job_type_vimjob,
399         \ 'opts': a:opts,
400         \ 'job': l:ch,
401         \ 'channel': l:ch,
402         \ 'buffer': ''
403     \}
404     return l:jobid
405 endfunction
406 " }}}
407
408 let &cpo = s:save_cpo
409 unlet s:save_cpo