Vim の +job を使ってみる
前回からの続きです。
前回書き忘れましたが今回使用する Vim は Vim 7.4-2028
になります。
+channel
+timer
+job
が追加された流れについて前回を参照してください。
ちなみにわたしも全部を把握しているわけではないのでところどころ曖昧です。
[+job
とは]
+job
とは外部コマンドを非同期で実行するための機能です。
今までは sysmte()
で外部コマンドを実行していましたが、job_start()
を利用する事が出来ます。
これにより vimproc.vim 等に依存することなく Vim の機能だけで非同期実行する事が出来ます。
[+job
を使用する上での注意点]
前回も書きましたが、まだバグが残っている可能性もあるので、なるべく最新版の Vim を使用してください。
また、+job
の :help
も日本語版ではなくて本体に付属してる英語版を使用してください(:help job@en
[外部コマンドを非同期で実行する]
では、早速使ってみましょう。
と、言っても +job
を使うのはそこまで難しくなく、コールバック関数を指定すれば簡単に非同期で外部コマンドを実行する事が出来ます。
let s:count = 0 " job で実行された外部コマンドの結果を読みこむときに呼ばれる function! Disp(ch, msg) let s:count += 1 echom s:count a:msg endfunction " 第一引数に実行するコマンド(とオプション)を渡す " 第二引数にオプションを辞書で渡す(今回はコールバック関数を指定) " 戻り値に Job object が返ってくる let s:job = job_start("vim --version", { "callback" : "Disp"}) " 外部コマンドの実行をブロックせずにすぐに呼ばれる echom "start"
[出力結果]
start 1 VIM - Vi IMproved 7.4 (2013 Aug 10, compiled Jul 12 2016 13:33:51) 2 適用済パッチ: 1-2028 3 Compiled by worker@worker-System-Product-Name 4 Huge 版 with GTK2 GUI. 機能の一覧 有効(+)/無効(-) 5 +acl +file_in_path -mouse_sysmouse -tag_any_white 6 +arabic +find_in_path +mouse_urxvt -tcl 7 +autocmd +float +mouse_xterm +termguicolors 8 +balloon_eval +folding +multi_byte +terminfo 9 +browse -footer +multi_lang +termresponse 10 ++builtin_terms +fork() -mzscheme +textobjects 11 +byte_offset +gettext +netbeans_intg +timers 12 +channel -hangul_input +num64 +title 13 +cindent +iconv +packages +toolbar 14 +clientserver +insert_expand +path_extra +user_commands 15 +clipboard +job +perl +vertsplit 16 +cmdline_compl +jumplist +persistent_undo +virtualedit 17 +cmdline_hist +keymap +postscript +visual 18 +cmdline_info +langmap +printer +visualextra 19 +comments +libcall +profile +viminfo 20 +conceal +linebreak +python/dyn +vreplace 21 +cryptv +lispindent +python3/dyn +wildignore 22 +cscope +listcmds +quickfix +wildmenu 23 +cursorbind +localmap +reltime +windows 24 +cursorshape +lua +rightleft +writebackup 25 +dialog_con_gui +menu +ruby +X11 26 +diff +mksession +scrollbind -xfontset 27 +digraphs +modify_fname +signs +xim 28 +dnd +mouse +smartindent +xsmp_interact 29 -ebcdic +mouseshape +startuptime +xterm_clipboard 30 +emacs_tags +mouse_dec +statusline -xterm_save 31 +eval +mouse_gpm -sun_workshop +xpm 32 +ex_extra -mouse_jsbterm +syntax 33 +extra_search +mouse_netterm +tag_binary 34 +farsi +mouse_sgr +tag_old_static 35 システム vimrc: "$VIM/vimrc" 36 ユーザー vimrc: "$HOME/.vimrc" 37 第2ユーザー vimrc: "~/.vim/vimrc" 38 ユーザー exrc: "$HOME/.exrc" 39 システム gvimrc: "$VIM/gvimrc" 40 ユーザー gvimrc: "$HOME/.gvimrc" 41 第2ユーザー gvimrc: "~/.vim/gvimrc" 42 システムメニュー: "$VIMRUNTIME/menu.vim" 43 省略時の $VIM: "/usr/local/share/vim" 44 コンパイル: gcc -c -I. -Iproto -DHAVE_CONFIG_H -DFEAT_GUI_GTK -pthread -I/usr/include/gtk-2.0 -I/usr/lib/x86_64-linux-gnu/gtk-2.0/include -I/usr/include/atk-1.0 -I/usr/include/cairo -I/usr/include/gdk-pixbuf-2.0 -I/usr/include/pango-1.0 -I/usr/include/gio-unix-2.0/ -I/usr/include/freetype2 -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -I/usr/include/pixman-1 -I/usr/include/libpng12 -I/usr/include/harfbuzz -g -O2 -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=1 45 リンク: gcc -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -L/build/ruby2.2-l9vij4/ruby2.2-2.2.3/debian/lib -fstack-protector -rdynamic -Wl,-export-dynamic -Wl,-E -L/usr/local/lib -Wl,--as-needed -o vim -lgtk-x11-2.0 -lgdk-x11-2.0 -latk-1.0 -lgio-2.0 -lpangoft2-1.0 -lpangocairo-1.0 -lgdk_pixbuf-2.0 -lcairo -lpango-1.0 -lfontconfig -lgobject-2.0 -lglib-2.0 -lfreetype -lSM -lICE -lXpm -lXt -lX11 -lXdmcp -lSM -lICE -lm -ltinfo -lnsl -lselinux -lacl -lattr -lgpm -ldl -L/usr/lib/x86_64-linux-gnu -lluajit-5.1 -Wl,-E -fstack-protector -L/usr/local/lib -L/usr/lib/perl/5.18/CORE -lperl -ldl -lm -lpthread -lcrypt -lruby-2.2 -lpthread -lgmp -ldl -lcrypt -lm
job_start()
を使用して外部コマンドを実行します。
出力結果を見てわかるとおり外部コマンドの実行時にメインプロセスをブロックせずに即座に :echom "start"
が呼ばれているのがわかると思います。
また job_start()
の戻り値の Job object を利用して外部コマンドの実行後にプロセスの制御を行うことも出来ます。
[コマンドを中断する]
job_stop()
を使用して実行したコマンドのプロセスを中断する事が出来ます。
let s:job = job_start("vim --version") " 現在の Job のステータスを表示 echom job_status(s:job) " => run " Job を強制終了させる call job_stop(s:job, "kill") echom job_status(s:job) " => dead
[標準出力、標準エラーを分ける]
"callback"
の変わりにそれぞれ "out_cb"
と "err_cb"
に対してコールバック関数を指定することで、標準出力と標準エラーを分けることが出来ます(正確にいえば、"out_cb"
等が設定されていない場合に "callback"
が呼ばれます。
function! Stdout(ch, msg) echom "Stdout" a:msg endfunction function! Stderr(ch, msg) echom "Stderr" a:msg endfunction let s:opt = { "out_cb" : "Stdout", "err_cb" : "Stderr" } " OK " call job_start("vim --version", s:opt) " Error call job_start("vimaaaaa --version", s:opt) " output: " Stderr executing job failed: そのようなファイルやディレクトリはありません
[コマンドに "
含まれている時に上手く動作しない場合の対処方法]
これはわたしもよく原因を把握してるわけではないんですが、例えば次のようにコマンドに "
に含まれている場合に上手く動作しない事があります。
function! Disp(ch, msg) echom a:msg endfunction " 意図しては Ruby で "puts 42" というコードを実行させたいが上手く動作しない… call job_start('ruby -e "puts 42"', { "callback" : "Disp"})
こういう場合、コマンドを分割してリストで渡すと上手く動作します。
function! Disp(ch, msg) echom a:msg endfunction " コマンドをオプション単位で分割する call job_start(['ruby', '-e', 'puts 42'], { "callback" : "Disp"})
これで意図した動作します。
[バッファに出力する]
+job
は実行結果を任意のバッファに出力することも出来ます。
call job_start("vim --version", { "out_io" : "buffer", "out_name" : "job_test" }) " バッファを開く場合 " split job_test
"out_io"
に "buffer"
を指定し、"out_name"
に出力されるバッファ名を指定します。
ちなみに "out_name"
の変わりに "out_buf"
を使用すればバッファ番号を指定することも出来ます。
これにより標準出力が指定したバッファ名の末尾に追加されます。
また、"out_io"
と "out_name"
では『標準出力』のみ出力されるので『標準エラー』を出力したい場合は "err_io"
と "err_name"
を別途指定する必要があります。
call job_start("vim --version", { \ "out_io" : "buffer", \ "out_name" : "job_test", \ "err_io" : "buffer", \ "err_name" : "job_test" \})
また、バッファ以外にもファイルなんかにも出力する事が出来ます。
[まとめ]
と、いう感じで簡単に使い方を紹介してみました。
正直、使う分には難しくないんですが『最低限の機能しか用意されていないので凝ったことをする場合は工夫が必要』というような感じですかね。
しかしながら今までこういう非同期処理を行う場合は vimproc.vim に依存する必要があったのでやはり Vim 本体に非同期機能が実装されたのはとても大きいことだと思います。
今はまだ安定してなかったり実装されていなかったばかりでそんなにガッツリと触ってる人は少ないと思いますが、これから Vim 本体がメジャーリリース等されればどんどん活用されていくような気がします。
今後、非同期処理を使用してどんなプラグインが出てくるのかが楽しみです。