🔤

Vim キーマップをカスタマイズするとき考えていること

2023/05/19に公開

はじめに

Vim のキーマップ設定の自由度の高さは圧巻です。たとえば Vim の設定で以下のように書けば、ノーマルモードでスペースキーを押したあとエンターキーを押すことで、ノーマルモードにいながらカーソルの直後に改行を挿入できるようになります。

.vimrc
" ユーザが <Space><CR> と押せば以下が順に実行される
" a    : カーソルの直後から挿入モードに入る
" <CR> : 改行を挿入
" <Esc>: 挿入モードを抜ける
nnoremap <Space><CR> a<CR><Esc>

他にもコマンドを実行したり、プラグインと組み合わせたりと、アイデア次第で様々な機能をキーマップとして登録できます。この自由度の高さこそ、私が Vim をこよなく愛する理由の一つです。
キーマップの割り当ては時として編集体験に大きな影響を与えます。 ほんの1行 .vimrc に追加するだけで大幅に編集しやすくなる、そんなことも珍しくありません。適切なキーマップに便利な機能を割り当てればテキストエディタの操作性が大きく向上するものの、闇雲に設定すると思わぬトラブルを招くこともあります。
そこで今日は、キーマップを設定するときに私が気をつけていることを紹介します。

望ましいキーマップの条件

望ましいキーマップとはなんでしょうか。私は、以下の3つの条件を満たすものだと考えています。

  • 打ちやすいこと
  • 覚えやすいこと
  • 副作用が少ないこと

打ちやすいこと

打ちやすいキーマップが望ましいのは当然ですが、「打ちやすい」キーマップとはなんでしょうか。これもいくつかの条件に分けて考えることができます。

  • 押下するキーの数が少ない
  • 連打しやすい
  • 物理的に押しやすい位置にある

押下するキーの数が少ない

あるキーマップを実際に打つとき、押下しなければならないキーの総数(ここでは 総打鍵数 と呼ぶことにします)が少なければ少ないほど素早くコマンドを打てます。総打鍵数の具体例は以下のとおりです。

キーマップ 総打鍵数 内訳
d 1 d
w 1 w
dd 2 d + d
dw 2 d + w
D 2 Shift キー + d
<Space>d 2 Space キー + d
<C-d> 2 Ctrl キー + d
diw 3 d + i + w
<C-w><C-d> 3 Ctrl キー + w + d
<C-w>D 4 Ctrl キー + w + Shift キー + d
gUiW 6 g + Shift キー + u + i + Shift キー + w

打鍵数が少ない上の方のキーマップほど打ちやすい傾向にあるはずです。

連打しやすい

Vim で実行する操作のうち、モーション [1] や undo などの機能はしばしば連続で実行したくなります。このような機能では、連打のしやすさが重要です。総打鍵数が多いキーマップは連打しづらいため、頻繁に連続実行するコマンドを割り当てる場合は総打鍵数を絞るのが良いでしょう。

では、同じ総打鍵数のキーマップなら連打しやすさも同じ、と言えるでしょうか。<Space>d<C-d> の例(ともに総打鍵数は2)を考えてみましょう。

  • <Space>d: 連打するときは以下のように押す必要がある。

             -----時間軸----→
    <Space>: * * * * * * ...
          d:  * * * * * *...
    

    このようにリズミカルに交互に打つ必要があり、少し連打しづらい。

  • <C-d>: CTRL キーを押し続けながら d を押す

          -----時間軸----→
    CTRL: ************...
       d:  * * * * * *...
    

    このように CTRL キーは押下し続けるだけでよく、実質 d を連打するだけで良いため、連打しやすい。

このように同じ総打鍵数を持つキーマップであっても修飾キーなどの要素によっては連打しやすさが変わりうるため、別途考慮する必要が出てきます。

物理的に押しやすい位置にある

総打鍵数という考え方では、f0 も等しく総打鍵数 1 です。しかし、一般的なキーボードではすべてのキーが同様に押しやすいわけではありません。どのキーが特に押しやすいかについてはキーボードの種類やキーボードの叩き方の癖にも大きく依存しますが、一般論としては

  • アルファベットは比較的打ちやすい
  • ホームポジションから遠いファンクションキーや記号などは比較的打ちづらい

といえます。このような物理的な押しやすさも考慮する必要があります。

覚えやすいこと

残念ながら、せっかく定義したキーマップの存在が忘れ去られてしまうことは多々あります。どんな便利なキーマップも実際に使われてこそですから、キーマップの覚えやすさは大切です。覚え方は人により違いますが、一般には以下のような性質を満たすキーマップが覚えやすそうです。

  • 他のキーマップ・コマンドから連想できる
  • 理屈付けがある
  • prefix で統一されている

他のキーマップ・コマンドから連想できる

すでに存在するキーマップの延長で考えると覚えやすくなります。たとえば Vim には言わずと知れた hjkl モーションがあり、h が左側、 j が下側、…とアルファベットが方向に対応しています。このことから連想すれば、<C-w>h が「左側のウィンドウに移動する」キーマップであることは覚えやすくなるでしょう。

理屈付けがある

キーマップに何かしらの由来があると覚えやすくなります。たとえば git commit の頭文字を取って gc をコミット用のキーマップにする、といった割り当て方は、覚えやすくする工夫の一つです。英単語の頭を取るのは定番ですが、自分がいざ使う際に連想さえできるなら日本語のローマ字読みでも語呂合わせでも何でも構いません。

prefix で統一されている

「ウィンドウ移動のキーマップは s から始める、language server 関連は , から始める、その他一般のキーマップは <Space> から始まる」というように、自分で定義するキーマップの頭のキー (prefix) を決めておくのは有効な方法です。異なる種類の操作を異なる prefix に割り当てることで、より覚えやすくなるからです。
一方、prefixに拘りすぎると総打鍵数が犠牲になることがあるため、prefixはなるべく短くしたいところです。たとえばprefixが2文字以上になると、必然的にそのprefixを使うキーマップの総打鍵数が最低でも3となり、若干ハードルが上がります。

副作用が少ないこと

自分でキーマップを定義するという営みは、良いことずくめではありません。ときには思いもよらぬ副作用を招いてしまうことがあります。

  • 既存のマッピングとの競合: すでにあるキーマップの機能を上書きしてしまい、元々の機能が使えなくなる。
  • キー待機時間の発生: キー内容を確定させるための「待ち」が発生し、特定のキーマップがスムーズに使えなくなる。
  • 暴発: タイプミスにより誤ったキーマップを発火させてしまい、何らかの不都合が生じる。

既存のマッピングとの競合

たとえば以下のようなマッピングを考えましょう。

nnoremap <C-v> "+p

<C-v> でクリップボードの内容をペーストできるようにした例ですが、元々 Vim の <C-v> には「矩形ビジュアルモードに入る」という、大変有用な機能が割り当てられています。このままでは、矩形ビジュアルモードに入るのが面倒になってしまいます [2]。こういった事態は可能な限り避けたいものです。

一番の理想は既存機能が存在しないキーマップへ割り当てることですが、Vim のコマンドは種類が大変多く、空いているキーはさほど多くありません。以下のような方法で妥協するのが現実的です。

  • 既存機能を別のキーに割り当てる。もしくは swap する。

    • 例: :; の機能を入れ替えている有名な例。

      nnoremap ; :
      nnoremap : ;
      

      : はコマンドラインモードに入るキーマップ、; は直前の f モーションを再度実行するキーマップです。ほとんどのユーザは : のほうをよく使うものの、US キーボードで押しやすいのは ; のため、US キーボードの利用者はこのキーマップを採用している場合があります。

  • 既存機能は別の方法で実行する。

    • 例: ga はカーソル下の文字の Unicode コードポイント等を表示するキーコマンドですが、:ascii コマンド(短縮形: :as)も同じ機能を持ちます。打鍵数は増えますが、コードポイント等の表示を頻繁に行わない人は :as で代替してもよいでしょう。
    • 例: <Space> は「1文字右へ移動する」モーションであり、これは l とほぼ同じです [3]。上書きしてもまず問題ないでしょう。
    • 例: s は「1文字削除して挿入モードに入る」コマンドですが、これは cl で代替できます。総打鍵数が1増えるというデメリットをどう捉えるかは人次第ですが、s に別の機能を割り当てる選択も合理的だと思います。
  • 諦めて既存機能を捨てる。

    • 捨てる前に、本当に捨てても問題ない機能なのかどうか Vim の公式ドキュメントを読んで確認することをお勧めします。もしかすると、自分の知らない便利機能が眠っているかもしれません。

キー待機時間の発生

たとえば、pp と打つことでクリップボードの内容を貼り付けられるようにするため、以下のような定義を入れたとします。

nnoremap pp "+p

しかしこのキーマップには1つ問題があります。デフォルトの p にはそれ単体で「無名レジスタの内容を貼り付ける」という機能を持っています。そのため、p と打った時点ではユーザの意図がp を実行したいのか pp を実行したいのか判別できず、キー押下から実行までの間に待ち時間が生じてしまいます。待ち時間が発生すると機能の発動に遅延が生じるため、本来待ちが生じない場面で新たに待ち時間が発生する事態は極力避けたほうが良いでしょう。

暴発

ここでの暴発は、タイプミスにより意図せぬキーマップを実行してしまうことを指します。キーボードを打つのが人間である限り暴発自体は防げません。問題は暴発した後、どの程度テキスト編集に影響があるかです。

極端な例として、以下のようなキーマップを考えます。

.vimrc
nnoremap q <Cmd>qa!<CR>

こうすると、q を押すだけで編集内容を破棄しつつ Vim を終了させることができます。これが危険であることは想像に難くないでしょう。たとえば挿入モードに入るため a を押そうとして間違えて上の q を押してしまったとき、その時点で保存していなかったあらゆる変更が無に帰してしまうのです。

一般にモーションのような操作は影響が小さく、外部コマンド実行などの操作は影響が大きいことが多いです。暴発の影響が大きいコマンドは、たとえ頻繁に使う場合でもあえて簡単に押せないキーマップを選ぶとよいでしょう。そもそもキーマップにする必要があるのか(Ex コマンドで十分ではないか)も含めて検討してみてもよいかもしれません。

キーマップの具体例

行末移動

行末移動はよく使うモーションの1つです。デフォルトでは $ に割り当てられているものの、押しにくいなと不満を感じているとしましょう。どこにキーを割り当てるのが良いでしょうか。

まずは「行末移動」という機能の性質を考えてみましょう。

  • 頻度: かなり頻繁に使う。ただし、連打することはない。
  • 右方向に動くモーション。よって、l に関連付けると覚えやすそう。
  • 暴発時の影響: バッファの中身を変えないため、暴発したところで影響は限定的。

よって、例えば以下のような候補が思い浮かびます。

  • $ のまま:
    • 総打鍵数2。
    • 物理的な距離が遠く、押しづらい。
  • <C-l>:
    • 総打鍵数2。
    • 連打しやすいが、行末移動モーションを割り当てる場合は利点にならない。
    • スクリーン再描画の機能とかぶる。
  • L:
    • 総打鍵数2。
    • 連打しやすいが、行末移動モーションを割り当てる場合は利点にならない。
    • ウィンドウ下部に移動するモーションとかぶる。
  • <Space>l:
    • 総打鍵数2。
    • <C-l> などと比べて連打しづらいが、行末移動モーションを割り当てる場合は欠点にならない。
    • 先述の通り、<Space> モーションは l と実質同じなので上書きしても問題ない。

ということで、私は <Space>l を選びました。もちろん、人によって正解は様々です。

最も大切なこと

ここまで長々と語ってきましたが、いちばん大事なのは「気にしすぎない」ことかもしれません。.vimrc は個人のものです。どんな個性的なマッピングをしたところで他人から怒られる筋合いはありません。その人の性格、普段書く言語などによって正解も変わってきます。その人にあったマッピングを見つけることが重要です。

また、先程「覚えやすいキーマップが良い」と書きましたが、そうはいっても大抵のキーマップは定着する前に忘れてしまいます。私も自分の設定を見返していて、「そういえばこれ定義したけどほとんど使ってないな」と気付くことは珍しくありません。しかし、私はそれでもよいと思います。10個キーマップを定義して1つ便利なキーマップが残れば儲けもの、ぐらいの心構えでよいのではないでしょうか。

大事なのは、「これってもしかしてキーマップにすれば便利なんじゃないか」というひらめきをすぐさま行動に移すこと。その繰り返しが「思考の速度で編集する」道につながるのだと思います。

おわりに

以上、自分が Vim のキーマップを定義するときに考えていることをまとめてみました。Vim のキーマップの割り当て方にお悩みの方にとって、少しでも参考になれば幸いです。

脚注
  1. 「行末に移動する」「次の段落の冒頭に移動する」など、カーソル移動操作のこと。 ↩︎

  2. このマッピング1つで矩形ビジュアルモードに入る手段が完全になくなるわけではありませんが、矩形ビジュアルモードの有用性を考えると、サクッと入れなくなることの影響は大きいと言えるでしょう。 ↩︎

  3. 'wrapscan' の設定値によって行末にカーソルがあるときの挙動が変わるものの、いずれにせよかなり限定的な違いです。 ↩︎

Discussion