aboutsummaryrefslogtreecommitdiff
path: root/runtime/doc/job_control.txt
blob: edbc1bca8105fb18174babeacdb5562e0df87e3c (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
*job_control.txt*    Nvim


		 NVIM REFERENCE MANUAL    by Thiago de Arruda


Nvim's facilities for job control				  *job-control*

				      Type <M-]> to see the table of contents.

==============================================================================
1. Introduction						    *job-control-intro*

Job control is a simple way to perform multitasking in vimscript. Wikipedia
contains a more generic/detailed description:

"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
facilities for calling with external commands, job control does not depend on
available shells, instead relying on OS functionality for process management.

Internally, Nvim job control is powered by libuv, which has a nice
cross-platform API for managing processes. See https://github.com/libuv/libuv
for details.

==============================================================================
2. Usage						*job-control-usage*

Job control is achieved by calling a combination of the |jobstart()|,
|jobsend()| and |jobstop()| functions. Here's an 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, copy it to the file ~/jobcontrol.vim and start with a clean
nvim instance:
>
    nvim -u NONE -S ~/jobcontrol.vim
<
Here's what is happening:

- 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.
- The `JobHandler()` function is a callback passed to |jobstart()| to handle
  various job events. It takes care of displaying stdout/stderr received from
  the shells.
						*on_stdout* *on_stderr* *on_exit*
- The arguments passed to `JobHandler()` are:

  0: The job id
  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".

  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

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 = {}

    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, one can use the |jobsend()| function, like
this:
>
    :call jobsend(job1, "ls\n")
    :call jobsend(job1, "invalid-command\n")
    :call jobsend(job1, "exit\n")
<
A job may be killed at any time with the |jobstop()| function:
>
    :call jobstop(job1)
<
When |jobstop()| is called, `SIGTERM` will be sent to the job. If a job does
not exit after 2 seconds, `SIGKILL` will be sent.

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