Vim 8.0 Advent Calendar 3 日目 ジョブ

この記事は [Vim 8.0 Advent Calendar](http://qiita.com/advent-calendar/2016/vim8) の 3 日目の記事です。

ジョブ機能を使うことで、外部プロセスを非同期で実行することができます。

## ジョブを使う

この例では、ジョブを使って外部コマンド `git grep -n word` を実行し、結果を 1 行ずつ非同期で処理し、quickfix に追加しています。

```vim
function! s:handler(ch, msg) abort
  caddexpr a:msg
  cwindow
endfunction

call setqflist([])
let s:job = job_start(
\   ['git', 'grep', '-n', 'word'],
\   {'out_cb': function('s:handler')})
```

このように、ジョブを使うことで外部プロセスをバックグラウンドで実行し、チャンネルとコールバック関数を使うことで結果を非同期に処理できます。処理に時間のかかるソースコードのチェック処理などを裏で実行し、結果が出たら表示するといったことが可能になります。

## ジョブのオプション

ジョブで指定できるオプションについて簡単に紹介します。ここで紹介しているものが全てではありません。詳細については help を参照してください。

```vim
let job_options = {}
```

### モード

ジョブと接続される、標準入力、標準出力、標準エラー出力の各チャンネルのモードを指定します。デフォルトは `nl` で、改行を 1 つのメッセージとします。

```vim
" 標準入力のモードです。
let job_options.in_mode = 'nl'
" 標準出力のモードです。
let job_options.out_mode = 'nl'
" 標準エラー出力のモードです。
let job_options.err_mode = 'nl'
```

### 標準入出力の接続先

標準入出力についての細かい指定が行えます。

```vim
" 標準入力を使いません。
let job_options.in_io = 'null'

" 標準入力をチャンネルに接続します。デフォルトです。
let job_options.in_io = 'pipe'

" 標準入力をファイルから読み込みます。
let job_options.in_io = 'file'
" 標準入力をファイルから読み込む際のファイルへのパスです。
let job_options.in_name = '/path/file'

" 標準入力をバッファから読み込みます。
let job_options.in_io = 'buffer'
" 標準入力をバッファから読み取る際の読み取るバッファのバッファ番号です。
let job_options.in_buf = 1
" 標準入力をバッファから読み取る際のバッファの読み取り範囲の先頭行です。デフォルトは 1 です。
let job_options.in_top = 1
" 標準入力をバッファから読み取る際のバッファの読み取り範囲の最終行です。デフォルトはバッファの最終行です。
let job_options.in_bot = 9999  " 注意: 最終行にしたい場合はこの値は指定してはいけません。


" 標準出力を使いません。
let job_options.out_io = 'null'

" 標準出力をチャンネルに接続します。デフォルトです。
let job_options.out_io = 'pipe'

" 標準出力をファイルに出力します。
let job_options.out_io = 'file'
" 標準出力をファイルに出力する際のファイルへのパスです。
let job_options.out_name = '/path/file'

" 標準出力をバッファに出力します。
let job_options.out_io = 'buffer'
" 標準出力をバッファに出力する際のバッファ番号です。
" 指定がない場合や、存在しないバッファだった場合は、新しくバッファが作成されます。
let job_options.out_buf = 1
" 出力先がバッファの場合に 0 を指定すると、出力先バッファの 'modifiable' オプションをオフにします。
" 出力は行われますが、ユーザーはバッファを変更できなくなります。
let job_options.out_modifiable = 0
" 出力先がバッファの場合に 1 を指定すると、新しく作られたバッファの 1 行目にメッセージを出力します。
" メッセージは "Reading from channel output..." のようなものです。
let job_options.out_msg = 1
```

標準エラー出力は、標準出力のオプションのキーの `out` を `err` に変えたものが同じように用意されています。

### コールバック

ジョブ側で何かが起きた際に呼び出される関数を指定します。指定しない場合は特に何も呼び出されません。

```vim
" 標準出力もしくは標準エラー出力から何か読み出せるようになった際に呼び出されます。
" 下記 2 つとは併用できません。
let job_options.callback = { ch, msg => [] }
" 標準出力から何か読み出せるようになった際に呼び出されます。
let job_options.out_cb = { ch, msg => [] }
" 標準エラー出力から何か読み出せるようになった際に呼び出されます。
let job_options.err_cb = { ch, msg => [] }
" チャンネルが閉じられた際に呼び出されます。
let job_options.close_cb = { ch => [] }
" ジョブ(外部プロセス)が終了した際に呼び出されます。
let job_options.exit_cb = { job, exit_status => [] }
```

### その他

```vim
" ch_evalexpr() などでデータを読み取る際のタイムアウト時間(ミリ秒)です。
let job_options.timeout = 2000
" 標準出力を読み取る際のタイムアウト時間(ミリ秒)です。"timeout" を上書きします。
let job_options.out_timeout = 2000
" 標準エラー出力を読み取る際のタイムアウト時間(ミリ秒)です。"timeout" を上書きします。
let job_options.err_timeout = 2000

" Vim 終了時に、ジョブに対して job_stop() を呼び出します。
" デフォルトは 'term' で、Vim 終了時にジョブを停止します。
" 空文字列を指定すると、何もしません。
let job_options.stoponexit = 'term'
```

## ジョブを制御する

### ジョブを停止する

`job_stop()` 関数でジョブを終了させることができます。実際にはシグナルを送信したりします。
実際に何が起きるかは OS 依存です。

```vim
" ジョブを停止します。
" 第2引数には他に 'hup' 'quit' 'int' 'kill' や、シグナルの数値などが指定できます。
" 省略時は 'term' になります。
call job_stop(job, 'term')
```

### ジョブの状態や情報を得る

`job_status()` 関数や `job_info()` 関数で、ジョブの状態や情報を取得できます。

```vim
echo job_status(job)
" => 'run' (ジョブが実行中の場合)
" => 'fail' (ジョブの開始に失敗した場合)
" => 'dead' (ジョブの実行が終了している場合)

" ジョブに紐付けられたチャンネルです。
let ch = job_getchannel(job)

" 様々な情報を辞書で取得します。
echo job_info(job)
" 'status'     job_status() の戻り値と同じです。
" 'channel'    job_getchannel() の戻り値と同じです。
" 'exitval'    終了コードです。'status' が 'dead' の場合のみ参照できます。
" 'exit_cb'    exit_cb オプションに指定された関数参照の値です。
" 'stoponexit' stoponexit オプションの値です。
```