引き続きビューとバッファ、およびコントローラのそれぞれのやりとりについて考える。ちなみにここで言うコントローラとは教科書通りに、ユーザーからの入力を適切にバッファに送信する担当のことだ。ただし今回 wasavi に対して施そうとしている修正の場合バッファはエクステンションのバックエンド側に位置することになるので、それに合わせてコントローラも分割し、コントローラ(フロントエンド)およびコントローラ(バックエンド)を分けて考える必要がある。
まずキー入力からそれが再表示されるまでのサイクルを考えると、
- キー入力が行われ、コントローラ(フロントエンド)がそれを受ける
- キー情報と、ビューの情報をバックエンドへ送信する
- コントローラ(バックエンド)ではキー入力からエディタのどの機能をディスパッチするか判断し、バッファの機能を適宜呼び出す
- 再表示用の情報を生成し、フロントエンドへ送信する
- ビューがそれを受け取り、再表示する
- 再表示が完了したことをイベントとしてバックエンドへ送信する
ということになると思う。それぞれ掘り下げてみたい。
キー入力
キー入力はもちろん keypress や keydown イベントで得たものなのだがここでは qeema によって統合する。すなわち qeema はフロントエンド側に置く必要がある。一方でキー入力が実際の vi コマンドにディスパッチされるまでにはもう一つマップマネージャも経由する必要があるが、こちらはバックエンド側に置く(じゃないとマップ情報をその都度フロントエンドに転送しないといけない)。
ビューの情報
必要ならスクロールさせるために、バッファが参照する最低限のビュー側の情報を通知する必要がある。これは文字通り最低限でなければいけなくて、なぜならキータイプごとにバックエンドに通知されるものだからだ。
- 見えている領域の先頭行のインデックス
- 見えている領域の行数
- 見えている領域に対して最大何行表示できるか、の行数
- 見えている領域のうち、隠れていない最初の行のオフセット
- 見えている領域のうち、隠れていない最期の行のオフセット
程度のものでいい。
vi エディタとしての機能
この部分に関しては既存のコードの通り。ただし、新しい再表示のメカニズムに合わせた情報を追加で生成する必要がある箇所はある。
再表示用情報の生成
これがこの修正のミソになる。そしてこの情報も、ビュー側へ送るサイズは最低限のものにする必要がある。ところで再表示用情報と言っているのは当然エディタスクリーンに対してのものなのだけど、それは狭義のものであって、広義で言うとステータスラインの更新とか、コンソールの開閉とか、カーソルの表示非表示とか、そういうものも存在する。従って、広義の再表示情報群の構造は
[
{
type: 'repaint-editor',
...
},
{
type: 'show-cursor',
...
}
]
と言った、再表示命令の配列みたいな形になり、これを受け取ったビューが interpret する流れになる。
再表示用情報の構造
エディタの各機能を実行する前のカーソル位置と、後のカーソル位置とを比較して再表示すべき行を取り出す。
ちなみにそれ以外の副次的な情報も必要で:
{
type: 'repaint-editor',
length: 1000, // バッファの全行数
oldRowIndex: 0, // 直前のカーソル行位置
oldColIndex: 0, // 直前のカーソル桁位置
rowIndex: 1, // 現在のカーソル行位置
colIndex: 0, // 現在のカーソル桁位置
rowDelta: 1, // カーソル周辺から何行増減したか、の行数
updateLInes: {
'100': 'line #100',
'101': 'line #101'
},
updateKeys: [
100, 101
]
}
まあ大体こんな感じになる。いずれにしても、去年考えた通り、再表示にあたって最低限必要な行だけを算出する。もしスクロールが発生して 1 行だけ再表示する必要があれば updateLines は当然ただ 1 行分、そして再表示処理もただ 1 行分の DOM 生成だけ行い、すでに表示済みの行のうち再利用できるものは全て再利用する。また、カーソル移動だけの場合は当然再表示される行はない。
再表示完了イベント
ビュー側で再表示が完了したら、その旨をバックエンドに送信する。これは必要があってそうする。
再表示用情報の生成時にスクロールが発生することが判明したとして、ビューが上へ移動した場合はカーソルはビューの上端、下へ移動した場合はビューの下端に位置するようになる。しかし、vi のスクロールコマンドはそうならないのである。
- [cci]
[/cci] 1 画面分ビューが下へ移動する。カーソルはビューの上端 - [cci]
[/cci] 1 画面分ビューが上へ移動する。カーソルはビューの上端 - [cci]
[/cci] 半画面分ビューが下へ移動する。カーソルは画面上の位置を保持する - [cci]
[/cci] 半画面分ビューが上へ移動する。カーソルは画面上の位置を保持する
しかし、これらの動作は今回導入するバッファでは実装することができない。例えばビューが下に移動しつつ、カーソルはビューの上端に持ってきたいとしても、ビューの上端に位置する行のインデックスをバッファは知らない。
どうするかというと、キー入力から再表示までのサイクルを複数回実行する必要がある。一旦スクロールだけ行い、再表示完了イベントをバッファへ送信する。その時、あらためて最新のビュー情報も送りつける。2 回目のサイクルでカーソルをビュー上端へ置く。2 回目の再表示でカーソルを表示する…という感じ。
そのために、コントローラ(バックエンド)では送りつけられたキー入力 + ビュー情報のオブジェクトをキューとして保持する必要がある(マップの展開により仮想的に複数のキーストロークに展開される可能性を考えるとどのみち必要なのだが)。また、再表示リストの最後にはカーソル表示コマンドを置くわけなのだが、それはキューをすべて処理した場合に限らないと表示がちらついてしまうのでその辺の細かい場合分けもしないといけない。
完全再表示
上記までの再表示の仕組みは、変更前と後の差分だけを取り扱う手法と言えるのだが、vi コマンドの中には差分ではなく完全再表示したほうが良いものもある。[cci]
* * *
とまあ大体こんな感じだろうか。