【図解Vim】mapとnoremap

2013-10-24

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

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

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

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

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

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

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

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

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

11 → 10

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

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

:map j k

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

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

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

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

10 → 9

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

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

:map t <C-a>

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

10 → 9

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

よく似たコマンド「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の違い

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

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

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

mapとnoremapの使い分け

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

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

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

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

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

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

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

例えば、あなたが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) に対応するものを探してしまう。 が、そんなものは定義されているはずもなく、何も実行されない。

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

参考

最後に

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

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