Vimの:terminalの使い方 2つのモードとジョブの扱い
Vimを終了せずにシェルを触りたいとき、:!で単発のコマンドを打つのとは別に、ウインドウの1つを端末そのものにしてしまう方法があります。それが:terminalです。ビルドを流しっぱなしにしたり、対話的なCLIを置いたりできる反面、開いた直後はjを押しても下に動かず戸惑います。モードが2つあるのが理由で、そこさえ分かれば普通のウインドウと同じ感覚で使えます。
目次(9項目)
| コマンド・キー | これだけ覚える |
|---|---|
:terminal | 端末を新しいウインドウで開く(:termと省略可) |
| Ctrl-w N | Vimの操作に切り替える(スクロール・ヤンク) |
| i | 端末への入力に戻る |
set termwinsize=10x0 | 端末ウインドウの高さを固定する |
:q! | ジョブが動いていても閉じる |
検証環境:macOS + Vim 9.1.1752 / Neovim 0.12.4。画面表示・エラーメッセージ・オプションの既定値はすべてこの環境の実物です。
:terminalはVim 8.1の機能
端末ウインドウはVim 8.1で追加された機能です。Vimのリリースノートには、この機能が想定している使い道が3つ挙げられています。
You can now open a window which functions as a terminal. You can use it for: Running a command, such as "make", while editing in other windows / Running a shell and execute several commands / Use the terminal debugger plugin
同じ箇所には「これらはVimをリモート(ssh)接続で使っていて、端末を簡単に増やせないときに特に便利です」とも書かれています。ローカルならターミナルのタブを増やせばいい話ですが、踏み台越しの1本のセッションしか無いときには代わりがありません。
オプション機能なので、自分のVimに入っているかは次で確認できます。
:echo has('terminal')
1が返れば使えます。0なら+terminal付きでビルドされたVimに入れ替えるしかありません。:!との使い分けは別記事で扱っているので、ここでは端末ウインドウそのものの挙動に絞ります。
開き方とウインドウのサイズ
:terminalを引数なしで実行するとシェルが起動します。コマンドを渡せばそれを実行する端末になります。
:terminal " シェルを開く
:term make " makeを実行する端末を開く
:vert term " 左右に分割して開く
:tab term " 新しいタブで開く
:term ++rows=10 make " 高さ10行で開く
:term ++curwin make " 分割せず今のウインドウで開く
:term ++close make " 終了したらウインドウも閉じる
既定では水平分割になり、端末が上、元のファイルが下に来ます。高さは画面のおよそ半分です。18行の画面でファイルを開いた状態から:termを実行すると、次のような配置になりました。
hello
1
2
3
!sh -c "echo hello; seq 1 5; sleep 60" [running] 0,0-1 All
def add(a, b):
return a + b
~
demo.py 1,1 All
毎回++rowsを打つのが面倒なら、'termwinsize'で既定のサイズを決められます。行数x列数の形式で、0は「指定しない」の意味です。set termwinsize=5x0にしてから:termを実行すると、実際に上5行だけが端末になり、残りがファイルに割り当てられました。通常のウインドウサイズ変更と同じく、開いたあとにCtrl-w +で広げることもできます。
set termwinsize=10x0 " 高さ10行、幅は成り行き
2つのモードを行き来する
ここが最初の関門です。端末ウインドウには状態が2つあり、キー入力の行き先が変わります。
| モード | キー入力の行き先 | できること |
|---|---|---|
| Terminal-Job | 端末の中のプログラム | 普通にシェルを操作する |
| Terminal-Normal | Vim | カーソル移動、検索、ヤンク |
開いた直後はTerminal-Jobモードです。この状態でjを押しても画面は動かず、シェルにjという文字が渡ります。Vimの操作に切り替えるにはCtrl-w Nを押します。すると端末の出力が一時停止し、通常のバッファと同じようにggや/、yyが使えるようになります。端末への入力に戻るのはiやaです。
今どちらにいるかはステータス行で分かります。実行中は[running]、Terminal-Normalモードに入ると[Terminal]に変わりました。
!sh -c "seq 1 3; sleep 60" [running] 0,0-1 All " Terminal-Job
!sh -c "seq 1 3; sleep 60" [Terminal] 3,1 All " Ctrl-w N のあと
カーソル位置の表示も0,0-1から3,1に変わっていて、Vimがバッファとして扱い始めたことが見て取れます。ちなみにCtrl-\ Ctrl-nでも同じ切り替えができます。こちらはNeovimでも通じるので、両方を行き来する人はこのキーで覚えるほうが潰しが効きます。
端末バッファもバッファとして扱える
端末はウインドウではなくバッファです。:lsで一覧を出すと、通常のファイルと並んで表示され、実行中を示すRフラグが付きます。
:ls
1 #a "[No Name]" line 1
2 %aR "!sh [running]" line 1
バッファなので、Terminal-Normalモードにさえ入ればVimの機能がそのまま効きます。長いビルドログを/で検索する、エラー行をyyでヤンクして別のファイルに貼る、といった操作にマウスは要りません。さかのぼれる行数は'termwinscroll'で決まり、手元の既定値は10000行でした。それを超えると古い行から捨てられます。
ジョブが動いているときの終了
端末ウインドウを閉じようとして:qを打つと、ジョブが動いている間は止められます。
E948: Job still running (add ! to end the job)
メッセージのとおり:q!にすればジョブごと終了します。実行中のプロセスを巻き添えにするので、ビルド中に気軽に叩くものではありません。逆にジョブが終わったあとの端末バッファは、確認なしで:qで閉じられることを確認しました。
終わったバッファは自動では消えず、[finished]と表示されたまま残ります。出力を読み返せるようにするための挙動で、読み終わったら手で閉じます。毎回閉じるのが面倒な使い捨てのコマンドには++closeを付けておくと、終了と同時にウインドウごと片付きます。
:term ++close make " 終わったら勝手に閉じる
:term ++kill=term make " Vim終了時にジョブへ送るシグナルを指定
Ctrl-wが端末の中のプログラムと衝突するとき
端末ウインドウではCtrl-wがVimのウインドウ操作キーとして横取りされます。ところがCtrl-wは多くのシェルで「直前の単語を削除する」キーでもあるので、端末の中で単語を消したいのにウインドウ操作になってしまう、という衝突が起きます。
これを解決するのが'termwinkey'です。端末ウインドウでの前置キーを別のキーに移せます。
set termwinkey=<C-x> " 端末内の前置キーを Ctrl-x にする
この設定で試したところ、Ctrl-x NでTerminal-Normalモードに入れました。Ctrl-wはそのままシェルに渡るようになるので、端末の中では本来の単語削除として使えます。既定値は空で、その場合はCtrl-wが使われます。
Neovimとの違い
Neovimにも:terminalはありますが、挙動がいくつか違います。同じ操作を両方で試して確かめました。
| 項目 | Vim | Neovim |
|---|---|---|
| ウインドウ | 水平分割して開く | 今のウインドウを置き換える |
| バッファ名 | !sh | term://<パス>//<pid>:<コマンド> |
| Vim操作へ戻る | Ctrl-w N/Ctrl-\ Ctrl-n | Ctrl-\ Ctrl-n |
| サイズ指定 | 'termwinsize'/++rows | 分割してから開く |
いちばん戸惑うのが1行目です。Neovimで:terminalを実行すると編集中のファイルが画面から消えたように見えます。消えたわけではなくバッファが切り替わっただけですが、分割したいなら:split | terminalのように自分で分けてから開く必要があります。Neovimとの差異を意識せずに設定ファイルを共有していると、ここで挙動が変わります。
最近いちばん使っているのが、AIのコーディングCLIを:term ++rows=15で開いて、下半分でファイルを見ながら指示を出す構成です。CLIが出したパスをCtrl-w Nしてからyyでヤンクし、そのまま:eに貼れるのが地味に効きます。一方で、端末の中でうっかりCtrl-wを押してウインドウを移動してしまう事故が続いたので、'termwinkey'をCtrl-xに移しました。
まとめ
:terminalはウインドウの1つを端末にする機能で、Vimを閉じずにビルドや対話的なCLIを走らせられます。つまずくのはたいてい2つのモードの区別で、Ctrl-w NでVimの操作に切り替え、iで端末に戻る、この往復さえ覚えれば普通のバッファと同じに扱えます。ジョブが動いている間は:qがE948で止まるので、終わらせてよいときだけ:q!を使ってください。Ctrl-wの衝突が気になるなら'termwinkey'を移すのが早く、Neovimと設定を共有しているなら分割の有無が違う点だけ頭に入れておくと混乱しません。