ここぽんのーと

コードとデザインの境界に生きるエンジニアの、雑多な記録帳。

【図解Vim】mapとnoremap

2013/10/24, cocopon

Vimの設定を少しずつ.vimrcに加えはじめた当時は、mapとnoremapの違いがわからなかった。
情報はWeb上にたくさんあったが、当時の自分にとってはどれも説明が難しくて、しばらく曖昧なまま放置してしまった記憶がある。
そんな昔の自分に向けて、この記事を書いてみる。

この記事は、 Vim Advent Calendar 2012328日目の記事です。
昨日の記事は、 @raa0121 さんの「Jenkinsとvimenvで最新版のVimを自動で手に入れよう」。

mapの話をする前に: 便利なCTRL-A, CTRL-X

mapの話をする前に、ひとつだけ。
CTRL-ACTRL-Xを使ったことがあるだろうか。
もし初耳であれば、便利なのでこの機会に覚えてしまおう。

適当な数字を入力し、ノーマルモードに戻る。

2013-10-21 22.18.03

入力した数字にカーソルを合わせて、CTRL-Aを押してみよう。

2013-10-21 22.18.09

数字が1増えた。何が起こったのだろうか?
:help CTRL-Aで、その機能を見てみよう。

カーソルの下または後の数またはアルファベットに [count]を加える。 {Vi にはない}

ふむ、CTRL-Aは、カーソルの置かれた数字を1増やす機能を持っているんだ。
次に、CTRL-Xを押してみる。

11 → 10

CTRL-Xはその逆の機能を持っていて、カーソルのある数字を1減らす。

「map」コマンドは、キー入力を別のキーに割り当てる

話を元に戻そう。
mapコマンドを使うと、あるキー入力を別のキーに割り当てることができる。
例えば、以下のコマンドを実行してみる。

:map j k

すると、「j」キーを押したときには、「k」キーを押したときの機能を実行するようになる。つまり、「j」キーでカーソルを上に動かせるようになったのだ。(こんな割り当ては誰も使わないけど)

map_jk

では、先のCTRL-ACTRL-Xを使って実験してみよう。
mapコマンドでは、CTRL-Aを「<C-a>」という書式で書くことになっているので、以下のように書いてみる。

:map <C-a> <C-x>

実行したら、数字にカーソルを合わせてCTRL-Aを押してみよう。

10 → 9

数字が減った。
CTRL-Aを押すと、先ほど設定したマップによりCTRL-Xの機能が呼び出されて、数字が減るという仕組みだ。

map_ax

もうひとつマップを追加してみよう。

:map t <C-a>

数字にカーソルを合わせて、「t」キーを押すと…。

10 → 9

これも数字が減った。
図にすると下のような感じで、マップをたどってCTRL-Xの機能が呼び出されている。

map_tax

よく似たコマンド「noremap」

mapとよく似たコマンドに、noremapというものがある。
:help noremapで、ヘルプを見てみよう。

:no[remap] {lhs} {rhs}
キー入力 {lhs} を {rhs} に割り当てます。作成したマップ、はマップコマンドに対応したモードで使用できます。
{rhs} は再マップされないので、マップが入れ子になったり再帰的になることはありません。
コマンドを再定義するときによく使われます。

「{rhs}(= 右辺値)は再マップされないので、マップが入れ子になったり再帰的になることはありません。」
この文の意味が、はじめはわからなかった。

実際に動かして確かめてみよう。
先の「t」キーのmapを、noremapに変えてみる。

:map <C-a> <C-x>
:noremap t <C-a>

「t」キーを押すと…。

10 → 11

こんどは数字が増えた。
1行目でCTRL-ACTRL-Xに割り当てているのに、CTRL-Xの減らす機能ではなく、CTRL-Aの増やす機能が呼ばれているようだ。なぜだろう?

mapとnoremapの違い

先の状態を図にすると、このようになる。

noremap_ta

「再マップされない」とは、ユーザが定義した他のマップの影響を受けずに、何も設定されていない「素のVim」が持っている機能に割り当てる、ということだ。

「テメーがCTRL-Aに何を割り当てたかは知らねーが、素のVimではCTRL-Aは『1増やす』だから、オレは1増やす!」といった具合である。

mapとnoremapの使い分け

キーの割り当て方法が2種類あることはわかった。
では、どう使い分ければよいのだろう?

特別な理由がなければnoremapを使おう

まず、特別な理由がなければ、noremapを利用したほうがよい。
mapの動作は他のマッピングに影響されるので、意図しない結果を引き起こすことがあるかもしれない。

とはいえ、どうしても必要になってくるケースもある。

mapが必要なケース: プラグインが提供する機能を割り当てるとき

Vimでは、プラグインが持つ機能を割り当てるために、<Plug>という特別な仕組みを用意している。

特別な文字列 "<Plug>" を使ってスクリプトの内部作業用のマップを定義できます。これはどのキー入力にもマッチしません。プラグインを作成するときに便利です

例えば、あなたがHelloWorldというプラグインをつくるとしよう。
このプラグインは最高にクールなhello worldを出力する機能を持っていて、これをユーザに使ってもらいたい。
こんなとき、プラグイン作者は以下のように機能を提供できる。

function! SayHello()
  echo 'hello, world'
endfunction

noremap <Plug>(say_hello) :<C-u>call SayHello()<CR>

上記のコードでは、<Plug>(say_hello)に、メッセージ出力用の関数SayHelloの実行を割り当てている。
こうしておけば、ユーザが<Plug>(say_hello)にキーを割り当てて、その機能を呼び出すことができるようになるからだ。
「これはユーザ側で自由に呼び出してよい機能なんだ」ということが明確になるので、プラグインのインタフェースが見やすくなる利点がある。

ユーザは、例えば「e」キーを押したときにhello worldを出力したければ、こんな風に書けばよい。

:map e <Plug>(say_hello)

それで、このときの割り当てをnoremapにしてしまうと問題が発生する。
もし、noremapを使って

:noremap e <Plug>(say_hello)

のように書いてしまうと、noremapは「再マップしない」ので、「素のVim」のキーマッピングで<Plug>(say_hello)に対応するものを探してしまう。
が、そんなものは定義されているはずもなく、何も実行されない。

plugin_noremap

この場合は、プラグインによって割り当てられた関数を呼ぶ必要があるので、noremapではなくmapを利用するのが正解である。

plugin_map

参考

Vimテクニックバイブル ~作業効率をカイゼンする150の技
Vimサポーターズ
技術評論社
売り上げランキング: 25,042

最後に

内容の正確さには気をつけていますが、もし誤りがありましたらTwitterやはてブで指摘いただけると幸いです。

明日の担当は、manga_osyoさんです。