Vimのマッピングを拡張するサブモードに開始処理・終了処理を加える

by
カテゴリ:
タグ:

Vimはノーマルモードやインサートモードなどモードに応じてキーの機能が変わるエディタです。実はマッピングを工夫することで同一モード内で一時的に機能を変更するサブモード(通称)も実現できます。

たとえば、折り返しを考慮してカーソルを上下に移動するgjgkの繰り返しを楽にするサブモードは以下のように簡単に定義できます。通常なら下下上下下と移動するのにgjgjgkgjgjと入力すべきところ、gjjkjjといった具合にgの入力を省略可能にするサブモードですね。

" 通常のgj, gkのあとにplugマッピングでサブモードに入る
" 実際にはplugマッピングをprefixにした入力待ち状態にすぎない
nnoremap gj gj<plug>(submode-gjk)
nnoremap gk gk<plug>(submode-gjk)

" サブモード内でのj,kの挙動を定義
nnoremap <plug>(submode-gjk)j gj<plug>(submode-gjk)
nnoremap <plug>(submode-gjk)k gk<plug>(submode-gjk)

" サブモード終了時はなにもしない
nnoremap <plug>(submode-gjk) <nop>

このサブモードでjk以外を入力すると親モードの挙動に従うので、gjjlとすれば、2行下の1文字右に移動できます。

同様にgtttTtTのようにしてタブページを移動しても便利ですし、他にも様々な応用があります。

ところでサブモードの終了処理の実装方法についてvim-jp slackで質問がありました。

そこで、本記事ではサブモードの開始処理・終了処理をマッピングで実現する方法を紹介します。

サブモード開始処理

開始処理は素朴にはgjgkのマッピング内で実現できます。

nnoremap gj gj<cmd>echo 'Enter submode'<cr><plug>(submode-gjk-enter)
nnoremap gk gk<cmd>echo 'Enter submode'<cr><plug>(submode-gjk-enter)

コード重複を避けるには、サブモード開始を別のplugマッピングに分けるとよいでしょう。サブモードで大量のマッピングを定義する場合、開始処理を一括で変更できるので便利です。

nnoremap gj gj<plug>(submode-gjk-enter)
nnoremap gk gk<plug>(submode-gjk-enter)
nnoremap <plug>(submode-gjk-enter) <cmd>echo "Enter submode"<cr><plug>(submode-gjk)

サブモード終了処理

終了処理を素朴に実装するなら<plug>(submode-gjk)<nop>以外を割り当てます。これでサブモード待機時間の終了やサブモード対象外キーの入力による終了処理を定義できます。

nnoremap <plug>(submode-gjk) <cmd>echo "Leave submode"<cr>

終了処理は別のplugマッピングに分けて定義してもよいでしょう。 gjgkくらいなら関係ないかもしれませんが、特定条件で即座にサブモードを終了したい場合に便利です。たとえば、gj<esc>したらサブモードを即座に終了することもできます。マッピングなしにgj<esc>してもサブモードは終了しますが、<esc>はサブモードを終了してから親モードで実行されるため、意図しない挙動になる可能性があります。

nnoremap <plug>(submode-gjk) <plug>(submode-gjk-leave)
nnoremap <plug>(submode-gjk-leave) <cmd>echo "Leave submode"<cr>

" サブモード中に`x`を押したら何もせずに終了
nnoremap <plug>(submode-gjk)<esc> <plug>(submode-gjk-leave)

まとめ

以上をまとめると以下のようになります。みなさんもオレオレサブモードを作ってみてください!

" サブモードを開始するマッピングの定義
nnoremap gj gj<plug>(submode-gjk-enter)
nnoremap gk gk<plug>(submode-gjk-enter)

" サブモード開始処理
nnoremap <plug>(submode-gjk-enter) <cmd>echo "Enter submode"<cr><plug>(submode-gjk)

" サブモード終了処理
nnoremap <plug>(submode-gjk) <plug>(submode-gjk-leave)
nnoremap <plug>(submode-gjk-leave) <cmd>echo "Leave submode"<cr>

" サブモード内でのj,kの挙動を定義
nnoremap <plug>(submode-gjk)j gj<plug>(submode-gjk)
nnoremap <plug>(submode-gjk)k gk<plug>(submode-gjk)

ENJOY!

Vim駅伝2025-10-10の記事でした。

前回2025-10-17の記事はkawarimidollさんによる「Neovimのファイルタイプ検出は自分で微調整できるよ」でした。

vim.filetype.add()による微調整って便利ですよね。記事内でも言及頂いてますが、私も多用しています。

次回2025-10-22の記事はkamechaさんによる「縦のカーソル移動を怠惰にやろう」だそうです。これまた先日もvim-jp slackで話題になったところ……!

お楽しみに!