Skip to content

Instantly share code, notes, and snippets.

@airblade
Last active November 30, 2020 11:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save airblade/4585556f713f0f9cc537d14054056a3b to your computer and use it in GitHub Desktop.
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
@airblade
Copy link
Author

Usage:

:echo Async_sync('ls')

I know I'm not returning anything from Async_execute() on nvim; I'm just trying to get it working on vim.

@airblade
Copy link
Author

airblade commented Nov 16, 2020

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment