Vimの設定を少しずつ.vimrcに加えはじめた当時は、mapとnoremapの違いがわからなかった。 情報はWeb上にたくさんあったが、当時の自分にとってはどれも説明が難しくて、しばらく曖昧なまま放置してしまった記憶がある。 そんな昔の自分に向けて、この記事を書いてみる。
—
この記事は、 Vim Advent Calendar 2012 の328日目の記事です。 昨日の記事は、 @raa0121 さんの「Jenkinsとvimenvで最新版のVimを自動で手に入れよう」。
mapの話をする前に、ひとつだけ。
CTRL-A
と CTRL-X
を使ったことがあるだろうか。
もし初耳であれば、便利なのでこの機会に覚えてしまおう。
適当な数字を入力し、ノーマルモードに戻る。
入力した数字にカーソルを合わせて、 CTRL-A
を押してみよう。
数字が1増えた。何が起こったのだろうか?
:help CTRL-A
で、その機能を見てみよう。
カーソルの下または後の数またはアルファベットに [count]を加える。 {Vi にはない}
ふむ、 CTRL-A
は、カーソルの置かれた数字を1増やす機能を持っているんだ。
次に、 CTRL-X
を押してみる。
11 → 10
CTRL-X
はその逆の機能を持っていて、カーソルのある数字を1減らす。
話を元に戻そう。 mapコマンドを使うと、あるキー入力を別のキーに割り当てることができる。 例えば、以下のコマンドを実行してみる。
:map j k
すると、「j」キーを押したときには、「k」キーを押したときの機能を実行するようになる。つまり、「j」キーでカーソルを上に動かせるようになったのだ。(こんな割り当ては誰も使わないけど)
では、先の CTRL-A
と CTRL-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
の機能が呼び出されている。
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-A
を CTRL-X
に割り当てているのに、 CTRL-X
の減らす機能ではなく、 CTRL-A
の増やす機能が呼ばれているようだ。なぜだろう?
先の状態を図にすると、このようになる。
「再マップされない」とは、ユーザが定義した他のマップの影響を受けずに、何も設定されていない「素のVim」が持っている機能に割り当てる、ということだ。
「テメーが CTRL-A
に何を割り当てたかは知らねーが、素のVimでは CTRL-A
は『1増やす』だから、オレは1増やす!」といった具合である。
キーの割り当て方法が2種類あることはわかった。 では、どう使い分ければよいのだろう?
まず、特別な理由がなければ、noremapを利用したほうがよい。 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)
に対応するものを探してしまう。
が、そんなものは定義されているはずもなく、何も実行されない。
この場合は、プラグインによって割り当てられた関数を呼ぶ必要があるので、noremapではなくmapを利用するのが正解である。
:help noremap
:help <Plug>
:help using-<Plug>
内容の正確さには気をつけていますが、もし誤りがありましたらTwitterやはてブで指摘いただけると幸いです。
明日の担当は、manga_osyoさんです。