2017-03-21 09:08:19 -07:00
|
|
|
*job_control.txt* Nvim
|
2014-09-13 12:21:15 -07:00
|
|
|
|
|
|
|
|
|
|
|
NVIM REFERENCE MANUAL by Thiago de Arruda
|
|
|
|
|
|
|
|
|
|
|
|
Nvim's facilities for job control *job-control*
|
|
|
|
|
2017-05-01 08:09:29 -07:00
|
|
|
Type <M-]> to see the table of contents.
|
2014-09-13 12:21:15 -07:00
|
|
|
|
|
|
|
==============================================================================
|
|
|
|
1. Introduction *job-control-intro*
|
|
|
|
|
|
|
|
Job control is a simple way to perform multitasking in vimscript. Wikipedia
|
2014-12-08 17:00:05 -07:00
|
|
|
contains a more generic/detailed description:
|
2014-09-13 12:21:15 -07:00
|
|
|
|
|
|
|
"Job control in computing refers to the control of multiple tasks or Jobs on a
|
|
|
|
computer system, ensuring that they each have access to adequate resources to
|
|
|
|
perform correctly, that competition for limited resources does not cause a
|
|
|
|
deadlock where two or more jobs are unable to complete, resolving such
|
|
|
|
situations where they do occur, and terminating jobs that, for any reason, are
|
|
|
|
not performing as expected."
|
|
|
|
|
|
|
|
In a few words: It allows a vimscript programmer to concurrently spawn and
|
|
|
|
control multiple processes without blocking the current Nvim instance.
|
|
|
|
|
|
|
|
Nvim's job control was designed to be simple and familiar to vimscript
|
|
|
|
programmers, instead of being very powerful but complex. Unlike Vim's
|
2014-12-08 17:00:05 -07:00
|
|
|
facilities for calling with external commands, job control does not depend on
|
|
|
|
available shells, instead relying on OS functionality for process management.
|
2014-09-13 12:21:15 -07:00
|
|
|
|
|
|
|
Internally, Nvim job control is powered by libuv, which has a nice
|
2014-11-27 08:05:14 -07:00
|
|
|
cross-platform API for managing processes. See https://github.com/libuv/libuv
|
2015-05-19 07:11:32 -07:00
|
|
|
for details.
|
2014-09-13 12:21:15 -07:00
|
|
|
|
|
|
|
==============================================================================
|
|
|
|
2. Usage *job-control-usage*
|
|
|
|
|
|
|
|
Job control is achieved by calling a combination of the |jobstart()|,
|
2015-03-25 19:11:05 -07:00
|
|
|
|jobsend()| and |jobstop()| functions. Here's an example:
|
2014-09-13 12:21:15 -07:00
|
|
|
>
|
2016-12-14 09:47:53 -07:00
|
|
|
function! s:JobHandler(job_id, data, event) dict
|
2015-03-25 19:11:05 -07:00
|
|
|
if a:event == 'stdout'
|
|
|
|
let str = self.shell.' stdout: '.join(a:data)
|
|
|
|
elseif a:event == 'stderr'
|
|
|
|
let str = self.shell.' stderr: '.join(a:data)
|
2014-09-13 12:21:15 -07:00
|
|
|
else
|
2015-03-25 19:11:05 -07:00
|
|
|
let str = self.shell.' exited'
|
2014-09-13 12:21:15 -07:00
|
|
|
endif
|
2014-12-08 17:00:05 -07:00
|
|
|
|
2014-09-13 12:21:15 -07:00
|
|
|
call append(line('$'), str)
|
|
|
|
endfunction
|
2015-03-25 19:11:05 -07:00
|
|
|
let s:callbacks = {
|
|
|
|
\ 'on_stdout': function('s:JobHandler'),
|
|
|
|
\ 'on_stderr': function('s:JobHandler'),
|
|
|
|
\ 'on_exit': function('s:JobHandler')
|
|
|
|
\ }
|
|
|
|
let job1 = jobstart(['bash'], extend({'shell': 'shell 1'}, s:callbacks))
|
|
|
|
let job2 = jobstart(['bash', '-c', 'for i in {1..10}; do echo hello $i!; sleep 1; done'], extend({'shell': 'shell 2'}, s:callbacks))
|
2014-12-08 17:00:05 -07:00
|
|
|
|
2014-09-13 12:21:15 -07:00
|
|
|
<
|
2014-12-08 17:00:05 -07:00
|
|
|
To test the above, copy it to the file ~/jobcontrol.vim and start with a clean
|
2014-09-13 12:21:15 -07:00
|
|
|
nvim instance:
|
2014-12-08 17:00:05 -07:00
|
|
|
>
|
|
|
|
nvim -u NONE -S ~/jobcontrol.vim
|
2014-09-13 12:21:15 -07:00
|
|
|
<
|
|
|
|
Here's what is happening:
|
|
|
|
|
2014-12-08 17:00:05 -07:00
|
|
|
- Two bash instances are spawned by |jobstart()| with their stdin/stdout/stderr
|
|
|
|
connected to nvim.
|
|
|
|
- The first shell is idle, waiting to read commands from its stdin.
|
|
|
|
- The second shell is started with the -c argument, causing it to execute a
|
|
|
|
command then exit. In this case, the command is a for loop that will print 0
|
|
|
|
through 9 then exit.
|
2015-03-25 19:11:05 -07:00
|
|
|
- The `JobHandler()` function is a callback passed to |jobstart()| to handle
|
|
|
|
various job events. It takes care of displaying stdout/stderr received from
|
2014-09-13 12:21:15 -07:00
|
|
|
the shells.
|
2017-05-22 07:57:16 -07:00
|
|
|
*on_stdout* *on_stderr* *on_exit*
|
2015-05-19 07:11:32 -07:00
|
|
|
- The arguments passed to `JobHandler()` are:
|
2015-03-25 19:11:05 -07:00
|
|
|
|
2014-09-13 12:21:15 -07:00
|
|
|
0: The job id
|
2015-03-25 19:11:05 -07:00
|
|
|
1: If the event is "stdout" or "stderr", a list with lines read from the
|
|
|
|
corresponding stream. For "exit", it is the status returned by the
|
|
|
|
program.
|
|
|
|
2: The event type, which is "stdout", "stderr" or "exit".
|
|
|
|
|
2016-06-26 15:26:10 -07:00
|
|
|
Note: Buffered stdout/stderr data which has not been flushed by the sender
|
|
|
|
will not trigger the "stdout" callback (but if the process ends, the
|
|
|
|
"exit" callback will be triggered).
|
|
|
|
For example, "ruby -e" buffers output, so small strings will be
|
|
|
|
buffered unless "auto-flushing" ($stdout.sync=true) is enabled. >
|
|
|
|
function! Receive(job_id, data, event)
|
|
|
|
echom printf('%s: %s',a:event,string(a:data))
|
|
|
|
endfunction
|
|
|
|
call jobstart(['ruby', '-e',
|
|
|
|
\ '$stdout.sync = true; 5.times do sleep 1 and puts "Hello Ruby!" end'],
|
|
|
|
\ {'on_stdout': 'Receive'})
|
|
|
|
< https://github.com/neovim/neovim/issues/1592
|
|
|
|
|
2015-03-25 19:11:05 -07:00
|
|
|
The options dictionary is passed as the "self" variable to the callback
|
|
|
|
function. Here's a more object-oriented version of the above:
|
|
|
|
>
|
|
|
|
let Shell = {}
|
2015-05-19 07:11:32 -07:00
|
|
|
|
2017-07-12 05:21:24 -07:00
|
|
|
function Shell.on_stdout(_job_id, data, event)
|
|
|
|
call append(line('$'),
|
|
|
|
\ printf('[%s] %s: %s', a:event, self.name, join(a:data[:-2])))
|
2015-03-25 19:11:05 -07:00
|
|
|
endfunction
|
2015-05-19 07:11:32 -07:00
|
|
|
|
2017-07-12 05:21:24 -07:00
|
|
|
let Shell.on_stderr = function(Shell.on_stdout)
|
2015-05-19 07:11:32 -07:00
|
|
|
|
2017-07-12 05:21:24 -07:00
|
|
|
function Shell.on_exit(job_id, _data, event)
|
|
|
|
let msg = printf('job %d ("%s") finished', a:job_id, self.name)
|
|
|
|
call append(line('$'), printf('[%s] BOOM!', a:event))
|
|
|
|
call append(line('$'), printf('[%s] %s!', a:event, msg))
|
2015-03-25 19:11:05 -07:00
|
|
|
endfunction
|
2015-05-19 07:11:32 -07:00
|
|
|
|
2017-07-12 05:21:24 -07:00
|
|
|
function Shell.new(name, cmd)
|
|
|
|
let object = extend(copy(g:Shell), {'name': a:name})
|
|
|
|
let object.cmd = ['sh', '-c', a:cmd]
|
|
|
|
let object.id = jobstart(object.cmd, object)
|
|
|
|
$
|
|
|
|
return object
|
2015-03-25 19:11:05 -07:00
|
|
|
endfunction
|
2015-05-19 07:11:32 -07:00
|
|
|
|
2017-07-12 05:21:24 -07:00
|
|
|
let instance = Shell.new('bomb',
|
|
|
|
\ 'for i in $(seq 9 -1 1); do echo $i 1>&$((i % 2 + 1)); sleep 1; done')
|
|
|
|
<
|
2014-09-13 12:21:15 -07:00
|
|
|
To send data to the job's stdin, one can use the |jobsend()| function, like
|
|
|
|
this:
|
|
|
|
>
|
2014-12-08 17:00:05 -07:00
|
|
|
:call jobsend(job1, "ls\n")
|
|
|
|
:call jobsend(job1, "invalid-command\n")
|
|
|
|
:call jobsend(job1, "exit\n")
|
2014-09-13 12:21:15 -07:00
|
|
|
<
|
|
|
|
A job may be killed at any time with the |jobstop()| function:
|
|
|
|
>
|
|
|
|
:call jobstop(job1)
|
|
|
|
<
|
2014-12-08 17:00:05 -07:00
|
|
|
When |jobstop()| is called, `SIGTERM` will be sent to the job. If a job does
|
|
|
|
not exit after 2 seconds, `SIGKILL` will be sent.
|
|
|
|
|
2014-09-13 12:21:15 -07:00
|
|
|
==============================================================================
|
|
|
|
vim:tw=78:ts=8:noet:ft=help:norl:
|