Vim の :nmap と :nnoremap の違い
どちらもノーマルモードのマッピングを設定するコマンドで, 一見どちらを使っても問題ないように思えますが, :nnoremap の方を使われることを強くお勧めします.
:nmap
の危険性
:nmap
は再帰的にコマンドを実行し, :nnoremap
は再帰的にコマンドを実行しません.
再帰的というのはどういう意味かと言いますと, 次のようなコマンドをみていただければと思います:
:nmap j gj
:nmap gj j
j
はカーソルを一行下に下げるコマンドですが, 一行目で gj
と設定することにより, ソフトラップされた仮想的な行も一行ずつカーソルを下げられるようにしました.
そして 2 行目で gj
を j
とマップして, j
本来の挙動を設定しました.
つまりは j
と gj
を入れ替えたというつもりです.
でも実際それらの 2 行を設定して j
や gj
をノーマルモードで入力すると次のようなエラーが表示されます:
E223: recursive mapping
recursive mapping
とあるように, 再帰的なマッピングのため操作が中断されてしまいました.
どうしてこのようなことになってしまったのかというのは, :namp
の設定するコマンドと同名のマッピングが存在している場合, そのマッピングを実行してしまうためです.
namp
の再帰的というのは, そのような意味での再帰的ということになります.
つまりは 1 行目の j
にマップされている gj
というコマンドは, 2 行目の gj
にマップされている j
が実際のところ呼び出されていたということになります.
そして j
の gj
が呼び出され, gj
の j
が呼び出されてと, このように無限ループに陥ってしまいます.
:nnoremap
を使おう
なので, j
と gj
を入れ替えるようにマッピングさせたい場合, 次のように :nnoremap
を代わりに使う必要があります:
:nnoremap j gj
:nnoremap gj j
このようにすることによって, 意図した通りの挙動をしてくれるようになります.
:nmap
というコマンド, 一見こちらの方が一般的な印象を受け, :nnoremap
よりも使いたくなってしまいますが, 実際のところはそのような再帰的にコマンドを実行するという危険性をはらんでいるため, むしろ :nnoremap
の方が普段のマッピングに適しています.
なので僕はノーマルモードのマッピングを設定する時は, もっぱら :nnoremap
を使っています.
あとちなみになんですが, Vim ってコマンドの省力形が色々あるので, :nmap
の場合, :nm[ap]
となり, 最小文字数として :nm
とすることもできます.
:nnoremap
も :nn[oremap]
となるので, 最小文字数として :nn
とすることもできます.
なので他の人の .vimrc
(Vim の設定ファイル) を見たときに :nm
や :nn
などのコマンドを見たときは, それぞれ :nmap
と :nnoremap
の省略形を使っているのねと解釈する事ができます.
モード毎のマップコマンド
あと :nmap
や :nnoremap
はノーマルモードでのマッピングを設定するためのコマンドですが, Vim はご存知の通りモードという概念がありますので, モード毎に次のようなマッピングを設定するためのコマンドがあります:
モード | 再帰的 | 非再帰的 |
---|---|---|
ノーマル, ビジュアル, セレクト, 操作待機中 | :map | :no[remap] |
ノーマル | :nm[ap] | :nn[oremap] |
ビジュアル, セレクト | :vm[ap] | :vn[oremap] |
ビジュアル | :xm[ap] | :xn[oremap] |
セレクト | :smap | :snor[emap] |
操作待機中 | :om[ap] | :ono[remap] |
インサート, コマンドライン | :map! | :no[remap]! |
インサート | :im[ap] | :ino[remap] |
インサート, コマンドライン, Lang-Arg | :lm[ap] | :ln[oremap] |
コマンドライン | :cm[ap] | :cno[remap] |
ターミナル | :tma[p] | :tno[remap] |
見ていただくと分かるように, ノーマル, ビジュアル, インサートといった基本的なモードの他にもこれだけ色々と細かくあります.
なので状況に応じて, 設定していきたいですね.
まとめ
:nmap
と :nnoremap
その両者は, ノーマルモードのマッピングを設定するということは同じですが, 再帰的にコマンドを実行するかしないかという決定的な違いがあります.
:nmap
でマッピングを設定してしまうと, 自分が設定した他のマッピングや他の人のプラグインによって設定されたマッピングによって, 再帰的にコマンドが実行され意図しない操作が起きないかということを考えなくてはいけなくなります.
設定されているマッピングが自分によるものだけならまだなんとかいけそうですが, 10 や 20 ものプラグインをインストールしていて, 色々なマッピングが設定されているとなると :nmap
でマッピングを設定するというのは, ほぼ無理ゲーです.
仮にインストールしているプラグインが設定しているマッピングを全て覚えて, そのマッピングと被らないように :nmap
で設定するにしても, 労力に対する効果がほとんど得られません.
というか被らないように設定するとなると, 他のマッピングやプラグインを追加していくに従って, 使えるコマンドも限られていきます. (むしろマゾゲー)
なので自分のマッピングを設定するときは, ノーマルモードだったら :nmap
ではなく :nnoremap
, ビジュアルモードだったら :vmap
ではなく :vnoremap
, インサートモードだったら :imap
ではなく :inoremap
というように, 非再帰的なコマンドの方を使われることを強くお勧めします.
:nmap
と :nnoremap
の違い, ひいては再帰的マップコマンドと非再帰的マップコマンドの違いをそれなりに分かりやすくお伝えする事ができましたら幸いです.
マップコマンドに関する情報は :h mapping
で見れます.
どのマップコマンドはどのモードと関係しているのかという情報は :h map-modes
で見れます.
再帰的なマッピングに関する情報は :h recursive_mapping
で見れます.
関連記事
Homebrew で macOS に Neovim をインストールして, 使えるように設定する方法2018.08.03
Ruby のクラスやメソッドのドキュメントを見れる ri コマンドが便利な件2018.09.08
Vim で現在の日時を挿入する方法2018.08.02
Homebrew で macOS に GNU コマンドをインストールする2018.07.25
Git のコミット履歴を大胆に書き換えるなら git rebase -i がオススメ2018.08.23