🤪

neovimターミナルモードエスケープと即座に確定するマッピング

2023/03/24に公開

TERMINALモード

vim, neovimにはウィンドウでターミナルを開く機能が存在します。

:term

しかし、初めてvim, neovimのTERMINALモードを利用した時に<ESC>でNORMALモードに戻ることができず驚いた方もいるのではないかと思います。(自分がそうというやつ)

エスケープのデフォルトマッピングがめんどい

neovimのTERMINALモード(TERMINALウインドウでのINSERTモード)からNORMALモードに戻るためのキーバインドは、デフォルトでは次のようになっています。(vimはこれ以外にもあって使い勝手がいいのですが、neovimにはこれしかない)

<c-\><c-n>

確かにこんなマッピングなら、ターミナル内のアプリケーションと衝突することもそうそうないでしょう。しかし、2ストローク必要で、キー自体そこそこ押しにくいので、めんどいです。できれば普通のバッファと同じく<ESC>でNORMALに戻りたいものです。

脳死で<ESC>でNORMALに抜けるようにするとターミナル内のアプリケーションに<ESC>が渡せない

tnoremap <ESC> <c-\><c-n>

を設定すると、<ESC>を打ってNORMALに戻れるようになります。

しかし、<ESC>を打ってもvim側がNORMALに戻るマッピングとして処理してしまうので、アプリケーション側に<ESC>を渡せなくなります。

TERMINALからNORMALに抜けるマッピングには、押しやすさだけではなく、競合しにくさも求められるので、少し工夫が必要になります。

NORMALに戻るのを<ESC>2回押しにしてみる

tnoremap <ESC><ESC> <c-\><c-n>

<ESC>2回押しでNORMALに抜ける設定にすれば、1回押しをターミナル内のアプリケーション用に残すことができます。
つまり、

  • <ESC> 1回押し → TERMINAL内のアプリケーションに <ESC> を渡す
  • <ESC> 2回押し → NORMALに戻る

と打ち分ける方式です。

これでOKに見えますが、残念ながら1回押しの時点では入力が確定しないという問題があります。

<ESC>を1回押しただけの段階では、その入力が<ESC><ESC>の一部なのか、<ESC>1回で終わるつもりなのかvimからすると判別できません。そのため、<ESC>1回押しの操作はデフォルトで1秒待つか別のキー入力があるまで確定されません。

ちなみにこの「別のキー入力」は、入力後に<ESC>とまとめて2ストローク送られるので、なんでもいいという訳ではなく、<ESC>の後に入力する予定のキーを打つ必要があります。

ただし、当然ながら入力の確定待ちの内は画面が動かないわけなので、自分がサクサク操作していた所に異質なつっかかりが発生することになり、わかっていてもなんだか難しいです。

あと1秒の待ち時間もクロノスタシスっぽい感じでちょっと長く感じます。

<Plug>で待たないマッピング

しばらく前、vim-jp Slackでこんな技が紹介されていました。

nnoremap j j<Plug>(j)
nnoremap <Plug>(j)k <cmd>echo 'hi'<cr>

これは、jは普通に(即座に)jとして機能するが、jkと入力すると何か(例ではecho 'hi')を実行する、という"待たないマッピング"です。

<Plug>(name)は物理的なキー入力とはマッチしないマップを作成するもの、言わば仮想キーを定義するものです。

nnoremap jk xxxとする代わりに<Plug>(j)を間に置いて分割することでjは即座に実行され、待つのは<Plug>(j)の役目になります。jを押してから1秒以内にkを追加入力することで、<Plug>(j)kに設定した操作が行われます。

使用感としては仮想キーを置いているというより1秒間有効なフラグを立てているような感じでした(個人の感想です)。

こないだ思いついたTERMINALエスケープ

ようやく本題、私が先日思い付いたのがこのようなマッピングです。

tnoremap <ESC> <c-\><c-n><Plug>(esc)
nnoremap <Plug>(esc)<ESC> i<ESC>

このマッピングでは、ESCを押すと即座にNORMALに抜け、続けてESCを入れるとTERMINALに入り直してアプリケーション側にESCが渡るようになっています。

  • <ESC> 1回押し → 押してすぐ、待たずにNORMALに行く
  • <ESC> 2回押し → TERMINALに戻ってアプリケーションの方に <ESC> を渡す

先程と順序が逆なのは誤爆の可能性があるからです。(vimでNORMALに戻ろうとした途中でターミナル内アプリケーションに<ESC>を送ってしまう。)

いろいろなマッピング

このマッピングをvim-jp Slackで紹介したところ、人それぞれのマッピングが返ってきて面白かったです。

<Esc>1回で抜ける方式

tnoremap <Esc> <C-\><C-N>

記事の途中で登場したものです。ターミナル内への<ESC>を潰してしまいますが、わかりやすいです。

<ESC>2回で抜ける方式

tnoremap <ESC><ESC> <c-\><c-n>

これも記事の途中で登場したものです。よくあるらしいです。

<C-\>

tnoremap <C-\> <C-\><C-N>

シンプルに短縮されたものです。

jj

tnoremap jj <c-\><c-n>

安定のjj

fj

tnoremap fj <C-\><C-N>

こんなパターン入れないやろ、とのこと。jjの系譜っぽさがあります。

<C-]>

tnoremap <C-]> <C-\><C-n>

<C-[>からの類推だそうです。

まとめのようなもの

neovimのTERMINALモードは少々使いづらく、カスタム前提みたいなところがあると思っています。

色々記事を読んだり、vim-jp Slackに参加したりして"キミだけの最強vimrc"を作り出しましょう。組合せはたぶん無限だと思います。

追記

結局、現在は前前項の取り上げた<C-]>をエスケープとターミナルのfloatwinのトグルに使用しています。てへ

GitHubで編集を提案

Discussion