input モードのショートカット ^T、^D をなんとかしよう。
これらの機能、実はあまり利用したことがない。とりあえず ^D の posix の仕様は以下の通り:
^D
^D は、行指向コマンド(/, ?, :, !)によって開始されたテキスト入力モードでは特別な意味を持たない。またこのコマンドは、ブロックモードの端末ではサポートされてなくてもよい。
カーソルが字下げ文字に続いていない、または字下げ文字、’0′ または ‘^’ に続いていない場合:
- カーソルが 1 桁目にある場合、^D は無視され何も起こらない
- そうではない場合、^D は何の意味も持たない<(^D そのものが挿入される)
最後に入力した文字が ‘0’ であった場合、カーソルは 1 桁目に移動しなければならない。
そうではなく最後に入力した文字が ‘^’ であった場合、カーソルは 1 桁目に移動し、加えて、次の入力行の自動字下げレベルは現在行の(もともとの)字下げ量をもたらした行から同様にもたらされなければならない。
そうではない場合、カーソルは shiftwidth 単位で前に戻される。
現在行: 変更されない。
現在桁: ^D の前に ‘^’ または ‘0’ があった場合は 1。そうでない場合は (column -1) -((column -2) % shiftwidth)。
という感じ。結局のところはインデントを制御するショートカットということだ。なお vi では ^D はカーソル行が /^\s+[^0]?/ な状態のときのみ効力を持つのだが、vim では拡張されいつでも実行可能になっている。
この中で、異質なのはもちろん ‘0’ や ‘^’ を前置した上で ^D すると特別扱いされるという点だ。これらの文字を打った直後に ^D を押すと、カレント行のインデントが削除される。0 と ^ の違いは、インデントの削除が一時的かどうかだ。[cci]:set ai[/cci] な状態で、前者はインデントを削除した行で enter を押した場合、次の行もインデントなしになる。つまり、カレント行が次の行のインデント量を算出する基準になる。一方後者の場合、カレント行は次の行のインデント量を算出する基準から外される。これは例えば、C ソースでラベルを書く場合とかに有用:
foo();
loop:
bar();
なんてコードだと、1 行目で改行した時点で自動的に 1 レベルのインデントが挿入されるが、そこで ^^D し、1 桁目から loop: と打つ。で、さらに改行すると、新しい行のインデントは 2 行目ではなく 1 行目から派生し、1 レベルインデントが生成される。ちなみにインデント量が補正された場合、’0′ や ‘^’ は自動的に削除される。
というわけで、そうなるように実装した。
さて、vi の input モードで定義されている特別な機能はこれで一通り実装したのだけど、実はもうひとつだけ残っている。input モードに入った直後に NUL、つまりコードポイント 0 の文字を入力すると、最後に input モードに入力した文字が自動的に再度入力され、かつ input モードも自動的に抜ける。
この機能はどうなんだ。まずキーボードから NUL って入力できていいのか? 一般的にコントロールコードは Ctrl + A とかで打つ。そうすると、A のコードポイントを 0x1f で論理積を取った値をコードポイントとした文字が打たれたことになる。そう考えるとコードポイント 0 は Ctrl + @ になる。しかし US 配列のキーボードの場合 @ は Shift + 2 で打つので、実際には Shift + Ctrl + 2 ということになるが、これだと制御キーのためのショートカットだとキーイベントハンドラで判別できない。javascript ではキーボードレイアウトを意識するのは非常に難しいのだ。
とりあえず、ctrl+space を特別扱いし、それが押されたら wasavi の中では NUL が押されたことにする。
というわけで、その機能も作った。