Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add native job support for neovim. #234

Merged
merged 5 commits into from
Aug 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ services:
- xvfb
script:
- '[ $CI_TARGET = neovim ] && VROOM_ARGS="--neovim" || VROOM_ARGS=""'
- vroom $VROOM_ARGS --crawl --skip=vroom/system-vimjob.vroom
- vroom $VROOM_ARGS --crawl --skip=vroom/system-job.vroom
matrix:
fast_finish: true
allow_failures:
Expand Down
20 changes: 12 additions & 8 deletions autoload/maktaba/syscall.vim
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ endfunction
" Returns whether the current vim session supports asynchronous calls.
function! maktaba#syscall#IsAsyncAvailable() abort
return !s:async_disabled && (
\ (!s:vimjob_disabled && has('job')) ||
\ (!s:vimjob_disabled && (has('job') || has('nvim'))) ||
\ (has('clientserver') && !empty(v:servername)))
endfunction

Expand Down Expand Up @@ -312,12 +312,12 @@ endfunction
" env_dict contains tab, buffer, path, column and line info. This fallback will
" be deprecated and stop working in future versions of maktaba.
"
" Asynchronous calls are executed via vim's |job| support. If the vim instance
" is missing job support, this will try to fall back to legacy |clientserver|
" invocation, which has a few preconditions of its own (see below). If neither
" option is available, asynchronous calls are not possible, and the call will
" either throw |ERROR(MissingFeature)| or fall back to synchronous calls,
" depending on the {allow_sync_fallback} parameter.
" Asynchronous calls are executed via vim's |job| support, or neovim's
" |job-control|. If the vim instance is missing job support, this will try to
" fall back to legacy |clientserver| invocation, which has a few preconditions
" of its own (see below). If neither option is available, asynchronous calls are
" not possible, and the call will either throw |ERROR(MissingFeature)| or fall
" back to synchronous calls, depending on the {allow_sync_fallback} parameter.
"
" The legacy fallback executes calls via |--remote-expr| using vim's
" |clientserver| capabilities, so the preconditions for it are vim being
Expand Down Expand Up @@ -345,7 +345,7 @@ function! maktaba#syscall#CallAsync(Callback, allow_sync_fallback) abort dict
if s:async_disabled
let l:reason = 'disabled by maktaba#syscall#SetAsyncDisabled'
else
let l:reason = 'no +job support and no fallback available'
let l:reason = 'no +job support, not nvim, and no fallback available'
endif
throw maktaba#error#MissingFeature(
\ 'Cannot run async commands (%s). See :help Syscall.CallAsync',
Expand All @@ -357,6 +357,10 @@ function! maktaba#syscall#CallAsync(Callback, allow_sync_fallback) abort dict
let l:vimjob_invocation =
\ maktaba#syscall#async#CreateInvocation(self, l:invocation)
call l:vimjob_invocation.Start()
elseif !s:vimjob_disabled && has('nvim')
let l:vimjob_invocation =
\ maktaba#syscall#neovim#CreateInvocation(self, l:invocation)
call l:vimjob_invocation.Start()
else
" TODO(#190): Remove clientserver implementation.
let l:clientserver_invocation =
Expand Down
47 changes: 47 additions & 0 deletions autoload/maktaba/syscall/neovim.vim
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
" CallAsync implementation using neovim's job support.

""
" @private
function! maktaba#syscall#neovim#CreateInvocation(syscall, invocation) abort
" We'll pass this entire invocation to jobstart, which will pass it to the
" on_exit callback.
return {
\ '_syscall': a:syscall,
\ '_invocation': a:invocation,
\ 'Start': function('maktaba#syscall#neovim#Start'),
\ 'stdout_buffered': 1,
\ 'stderr_buffered': 1,
\ 'on_exit': function('maktaba#syscall#neovim#HandleJobExit')}
endfunction


""
" @private
" @dict SyscallNeovimInvocation
" Dispatches syscall through |jobstart()|, and invokes the invocation's
" callback once the command completes, passing in stdout, stderr and exit code
" to it.
" The neovim |job_control| implementation for @function(#CallAsync).
function! maktaba#syscall#neovim#Start() abort dict
let self._job = jobstart(self._syscall.GetCommand(), self)
" Send stdin immediately and close. Streaming input to stdin not supported.
if has_key(self._syscall, 'stdin')
call chansend(self._job, self._syscall.stdin)
endif
call chanclose(self._job, 'stdin')
endfunction


""
" @private
" @dict SyscallNeovimInvocation
function! maktaba#syscall#neovim#HandleJobExit(
\ unused_job,
\ status,
\ unused_event) abort dict
" jobcontrol sets stdout and stderr when no callbacks are given.
call self._invocation.Finish({
\ 'status': a:status,
\ 'stdout': join(self.stdout, "\n"),
\ 'stderr': join(self.stderr, "\n")})
endfunction
13 changes: 7 additions & 6 deletions doc/maktaba.txt
Original file line number Diff line number Diff line change
Expand Up @@ -551,12 +551,13 @@ Syscall.CallAsync({callback}, {allow_sync_fallback}) *Syscall.CallAsync()*
where env_dict contains tab, buffer, path, column and line info. This
fallback will be deprecated and stop working in future versions of maktaba.

Asynchronous calls are executed via vim's |job| support. If the vim instance
is missing job support, this will try to fall back to legacy |clientserver|
invocation, which has a few preconditions of its own (see below). If neither
option is available, asynchronous calls are not possible, and the call will
either throw |ERROR(MissingFeature)| or fall back to synchronous calls,
depending on the {allow_sync_fallback} parameter.
Asynchronous calls are executed via vim's |job| support. For nvim, they are
executed using |job_control|. If the vim instance is missing job support,
this will try to fall back to legacy |clientserver| invocation, which has a
few preconditions of its own (see below). If neither option is available,
asynchronous calls are not possible, and the call will either throw
|ERROR(MissingFeature)| or fall back to synchronous calls, depending on the
{allow_sync_fallback} parameter.

The legacy fallback executes calls via |--remote-expr| using vim's
|clientserver| capabilities, so the preconditions for it are vim being
Expand Down
26 changes: 19 additions & 7 deletions vroom/system-vimjob.vroom → vroom/system-job.vroom
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
In addition to the functionality featured in system.vroom, the maktaba#syscall#
helpers feature asynchronous execution powered by vim's native jobs feature.

Note that we use delays of 0.1s on the asynchronous calls to ensure nvim has
enough time to flush the contents of stdout to vroom. This shouldn't be
necessary for non-test usage.
(See https://github.com/google/vim-maktaba/pull/234 for more details.)

First, we need to work around for nvim prompting users to press enter.
(See https://github.com/google/vim-codefmt/pull/131)

:if has('nvim')<CR>
| set cmdheight=30<CR>
|endif<CR>

Before we dive in, let's get maktaba installed and set up the shell override:

@system (STRICT)
Expand All @@ -14,9 +26,9 @@ Before we dive in, let's get maktaba installed and set up the shell override:
The examples featured in this file only work with vim instances that support
vim jobs.

:if !has('job')<CR>
:if !has('job') && !has('nvim')<CR>
| echomsg maktaba#error#MissingFeature(
| 'Must have +job support to run system-vimjob.vroom examples')<CR>
| 'Must have +job support or nvim to run system-job.vroom examples')<CR>
|endif


Expand All @@ -35,7 +47,7 @@ To execute system calls asynchronously, use CallAsync.
:function Callback(result) abort<CR>
| let g:callback_stdout = a:result.stdout<CR>
|endfunction
:let g:invocation = AsyncWait(g:syscall.CallAsync('Callback', 0), 2)
:let g:invocation = AsyncWait(g:syscall.CallAsync('Callback', 0), 2) (0.2s)
! echo hi
:echomsg g:callback_stdout
~ hi
Expand All @@ -46,7 +58,7 @@ It is also possible to force synchronous execution.

:call maktaba#syscall#SetAsyncDisabledForTesting(1)
:call maktaba#syscall#ForceSyncFallbackAllowedForTesting(1)
:let g:invocation = AsyncWait(g:syscall.CallAsync('Callback', 0), 2)
:let g:invocation = AsyncWait(g:syscall.CallAsync('Callback', 0), 2) (0.2s)
! echo hi.*

:call maktaba#syscall#SetAsyncDisabledForTesting(0)
Expand All @@ -55,21 +67,21 @@ It is also possible to force synchronous execution.
Asynchronous calls can also take a stdin parameter.

:let g:syscall = maktaba#syscall#Create(['cat']).WithStdin("Hello")
:let g:invocation = AsyncWait(g:syscall.CallAsync('Callback', 0), 2)
:let g:invocation = AsyncWait(g:syscall.CallAsync('Callback', 0), 2) (0.2s)
! cat
:echomsg g:invocation.stdout
~ Hello

And() and Or() can also be used to chain Asynchronous commands.

:let g:syscall = maktaba#syscall#Create('true').And('echo SUCCESS')
:let g:invocation = AsyncWait(g:syscall.CallAsync('Callback', 0), 2)
:let g:invocation = AsyncWait(g:syscall.CallAsync('Callback', 0), 2) (0.2s)
! true && echo SUCCESS
:echomsg g:invocation.stdout
~ SUCCESS

:let g:syscall = maktaba#syscall#Create('false').And('echo FAILURE')
:let g:invocation = AsyncWait(g:syscall.CallAsync('Callback', 0), 2)
:let g:invocation = AsyncWait(g:syscall.CallAsync('Callback', 0), 2) (0.2s)
! false && echo FAILURE
:echomsg g:invocation.stdout
~ FAILURE