Vim documentation: job_control

main help file
*job_control.txt*    Nvim


		 NVIM REFERENCE MANUAL    by Thiago de Arruda



Nvim job control					*job-control*

Job control is a way to perform multitasking in Nvim, so scripts can spawn and
control multiple processes without blocking the current Nvim instance.

				      Type |gO| to see the table of contents.

==============================================================================
Concepts


Job Id							*job-id*

When a job starts it is assigned a number, unique for the life of the current
Nvim session. Functions like |jobstart()| return job ids. Functions like
|jobsend()|, |jobstop()|, |rpcnotify()|, and |rpcrequest()| take job ids.

==============================================================================

Usage							*job-control-usage*

To control jobs, use the "job…" family of functions: |jobstart()|,
|jobsend()|, |jobstop()|.

Example:

    function! s:JobHandler(job_id, data, event) dict
      if a:event == 'stdout'
        let str = self.shell.' stdout: '.join(a:data)
      elseif a:event == 'stderr'
        let str = self.shell.' stderr: '.join(a:data)
      else
        let str = self.shell.' exited'
      endif

      call append(line('$'), str)
    endfunction
    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))

To test the above script, copy it to a file ~/foo.vim and run it:
    nvim -u ~/foo.vim
 
Description of what happens:
  - Two bash shells are spawned by |jobstart()| with their stdin/stdout/stderr
    streams connected to nvim.
  - The first shell is idle, waiting to read commands from its stdin.
  - The second shell is started with -c which executes the command (a for-loop
    printing 0 through 9) and then exits.
  - `JobHandler()` callback is passed to |jobstart()| to handle various job
    events. It displays stdout/stderr data received from the shells.


							*on_stdout*
Arguments passed to on_stdout callback:
  0: |job-id|
  1: List of lines read from the stream. If the last item is not "" (empty
     string), then it is an incomplete line that might be continued at the
     next on_stdout invocation. See Note 2 below.
  2: Event type: "stdout"

							*on_stderr*
Arguments passed to on_stderr callback:
  0: |job-id|
  1: List of lines read from the stream. If the last item is not "" (empty
     string), then it is an incomplete line that might be continued at the
     next on_stderr invocation. See Note 2 below.
  2: Event type: "stderr"

							*on_exit*
Arguments passed to on_exit callback:
  0: |job-id|
  1: Exit-code of the process.
  2: Event type: "exit"

  Note: Buffered stdout/stderr data which has not been flushed by the sender
	will not trigger the on_stdout/on_stderr callback (but if the process
	ends, the on_exit callback will be invoked).
        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

  Note 2:
	Job event handlers may receive partial (incomplete) lines. For a given
	invocation of on_stdout/on_stderr, `a:data` is not guaranteed to end
	with a newline.
	  - `abcdefg` may arrive as `['abc']`, `['defg']`.
	  - `abc\nefg` may arrive as `['abc', '']`, `['efg']` or `['abc']`,
	    `['','efg']`, or even `['ab']`, `['c','efg']`.
	Easy way to deal with this: initialize a list as `['']`, then append
	to it as follows:
	  let s:chunks = ['']
	  func! s:on_stdout(job_id, data, event) dict
	    let s:chunks[-1] .= a:data[0]
	    call extend(s:chunks, a:data[1:])
	  endf
 

The |jobstart-options| dictionary is passed as |self| to the callback.
The above example could be written in this "object-oriented" style:

    let Shell = {}

    function Shell.on_stdout(_job_id, data, event)
      call append(line('$'),
            \ printf('[%s] %s: %s', a:event, self.name, join(a:data[:-2])))
    endfunction

    let Shell.on_stderr = function(Shell.on_stdout)

    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))
    endfunction

    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
    endfunction

    let instance = Shell.new('bomb',
          \ 'for i in $(seq 9 -1 1); do echo $i 1>&$((i % 2 + 1)); sleep 1; done')
 
To send data to the job's stdin, use YXXYjobsend()|:
    :call jobsend(job1, "ls\n")
    :call jobsend(job1, "invalid-command\n")
    :call jobsend(job1, "exit\n")
 
A job may be killed with YXXYjobstop()|:
    :call jobstop(job1)
 
==============================================================================
top - main help file