-
-
Save airblade/4585556f713f0f9cc537d14054056a3b to your computer and use it in GitHub Desktop.
| let s:async_sync_id = 0 | |
| let s:async_sync_outputs = {} | |
| function! s:next_async_sync_id() | |
| let async_sync_id = s:async_sync_id | |
| let s:async_sync_id += 1 | |
| return async_sync_id | |
| endfunction | |
| function! s:async_sync_output(async_sync_id, output) | |
| if type(a:output) == v:t_list | |
| " Ensure empty list becomes an empty string. | |
| let output = join(a:output, "\n") | |
| else | |
| let output = a:output | |
| endif | |
| let s:async_sync_outputs[a:async_sync_id] = output " job can now be garbage collected | |
| endfunction | |
| " Executes `cmd` asynchronously but looks synchronous to the caller. | |
| function! Async_sync(cmd) | |
| let async_sync_id = s:next_async_sync_id() | |
| let s:async_sync_outputs[async_sync_id] = Async_execute(a:cmd, function('s:async_sync_output', [async_sync_id])) | |
| while type(s:async_sync_outputs[async_sync_id]) == v:t_job | |
| call job_status(s:async_sync_outputs[async_sync_id]) | |
| sleep 10m | |
| endwhile | |
| let output = s:async_sync_outputs[async_sync_id] | |
| unlet s:async_sync_outputs[async_sync_id] | |
| return output | |
| endfunction | |
| " Optional argument is data (JSON) to pass to cmd's stdin. | |
| function! Async_execute(cmd, handler, ...) | |
| let options = { | |
| \ 'stdoutbuffer': [], | |
| \ 'handler': a:handler, | |
| \ } | |
| let command = s:build_command(a:cmd) | |
| if has('nvim') | |
| let jobid = jobstart(command, extend(options, { | |
| \ 'on_stdout': function('s:on_stdout_nvim'), | |
| \ 'on_exit': function('s:on_exit_nvim') | |
| \ })) | |
| if a:0 | |
| call chansend(jobid, a:1) | |
| call chanclose(jobid, 'stdin') | |
| endif | |
| else | |
| let job = job_start(command, { | |
| \ 'out_cb': function('s:on_stdout_vim', options), | |
| \ 'close_cb': function('s:on_close_vim', options) | |
| \ }) | |
| if a:0 | |
| let channel = job_getchannel(job) | |
| call ch_sendraw(channel, a:1) | |
| call ch_close_in(channel) | |
| endif | |
| return job | |
| endif | |
| endfunction | |
| function! s:build_command(cmd) | |
| if has('nvim') | |
| if has('unix') | |
| return ['sh', '-c', a:cmd] | |
| elseif has('win64') || has('win32') | |
| return ['cmd.exe', '/c', a:cmd] | |
| else | |
| throw 'unknown os' | |
| endif | |
| else | |
| if has('unix') | |
| return ['sh', '-c', a:cmd] | |
| elseif has('win64') || has('win32') | |
| return 'cmd.exe /c '.a:cmd | |
| else | |
| throw 'unknown os' | |
| endif | |
| endif | |
| endfunction | |
| function! s:on_stdout_vim(_channel, data) dict | |
| " a:data - an output line | |
| call add(self.stdoutbuffer, a:data) | |
| endfunction | |
| function! s:on_close_vim(channel) dict | |
| call self.handler(self.stdoutbuffer) | |
| endfunction | |
| function! s:on_stdout_nvim(_job_id, data, event) dict | |
| if empty(self.stdoutbuffer) | |
| let self.stdoutbuffer = a:data | |
| else | |
| let self.stdoutbuffer = self.stdoutbuffer[:-2] + | |
| \ [self.stdoutbuffer[-1] . a:data[0]] + | |
| \ a:data[1:] | |
| endif | |
| endfunction | |
| function! s:on_exit_nvim(_job_id, _data, _event) dict | |
| call map(self.stdoutbuffer, 'substitute(v:val, "\r$", "", "")') | |
| call self.handler(self.stdoutbuffer) | |
| endfunction |
The problem is that, under certain circumstances, a job's close_cb isn't called until I press ctrl-c. (This callback is the one that gathers the data received so that it can be returned to the original caller.) When I use the channel log, I see
channel_select_check(): Read EOF from ch_part[2], closing
looking for messages on channels
Invoking channel callback 61_on_stdout_vim [many times]
Job ended
looking for messages on channels
ui_delay(10)
channel_select_check(): Read EOF from ch_part[1], closing
looking for messages on channels
ui_delay(10)
looking for messages on channels
ui_delay(10)
looking for messages on channels
ui_delay(10)
...
Then I press ctrl-c:
Closing channel because all readable fds are closed
Closing channel
Invoking callbacks and flushing buffers before clsoing
Invoking callback 61_on_close_vim
At which point things go back to normal. (Although I never see messages like "Job ended", "Freeing job", "Closing channel", "Clearing channel", "Freeing channel" which I do normally.)
Maybe there's a race condition which I can't see?
Usage:
I know I'm not returning anything from
Async_execute()on nvim; I'm just trying to get it working on vim.