aboutsummaryrefslogtreecommitdiff
path: root/runtime/doc/job_control.txt
blob: 37a4e2ebb1e5a02bcb13ec921797bc9566b53955 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
*job_control.txt*    Nvim


		 NVIM REFERENCE MANUAL    by Thiago de Arruda


Nvim job control					*job* *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*

Each job is identified by an integer id, unique for the life of the current
Nvim session. Each job-id is a valid |channel-id|: they share the same "key
space". Functions like |jobstart()| return job ids; functions like
|jobstop()|, |chansend()|, |rpcnotify()|, and |rpcrequest()| take job ids.

Job stdio streams form a |channel| which can send and receive raw bytes or
|msgpack-rpc| messages.

==============================================================================
Usage							*job-control-usage*

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

Example: >vim

    function! s:OnEvent(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:OnEvent'),
    \ 'on_stderr': function('s:OnEvent'),
    \ 'on_exit': function('s:OnEvent')
    \ }
    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: >bash
    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.
  - `OnEvent()` callback is passed to |jobstart()| to handle various job
    events. It displays stdout/stderr data received from the shells.

For |on_stdout| and |on_stderr| see |channel-callback|.
							*on_exit*
Arguments passed to on_exit callback:
  0: |job-id|
  1: Exit-code of the process, or 128+SIGNUM if by signal (e.g. 143 on SIGTERM).
  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. >vim
          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: >vim
	  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: >vim

    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 |chansend()|: >vim
    :call chansend(job1, "ls\n")
    :call chansend(job1, "invalid-command\n")
    :call chansend(job1, "exit\n")
<
A job may be killed with |jobstop()|: >vim
    :call jobstop(job1)
<
A job may be killed at any time with the |jobstop()| function:
>vim
    :call jobstop(job1)
<
Individual streams can be closed without killing the job, see |chanclose()|.

==============================================================================
 vim:tw=78:ts=8:noet:ft=help:norl: