Separation #3

テキストバッファの実装が DOM から文字列の単純な配列になると、基本的には操作も単純になるのだが、そうでない場合もある。DOM の場合、ある 1 行の構造が例えば

some text


などという形になっている。ここで span 要素は何かというと、マークだ。wasavi のマークは vi や vim のそれに比べてほんの少しだけ賢くて、つまり行内の文字の挿入や削除に応じて自動的に位置を調整する。

内部的には、マークを span 要素で表現し、かつテキストを操作する際は div の innerText で一気に書き換えるのではなく NodeIterator でテキストノードを走査し、必要な部分だけを書き換えるという処理を行う。その上で操作後に span 要素の新しいオフセット(つまり span より若い位置にあるテキストノードの文字数)を算出しなおし、それをマークの新しい桁位置にする…というやりかたになっている。

テキストの保持の仕方が単純な文字列になると、このやり方でマーク位置を追跡することはできなくなる。現状では Buffer クラスはマークのことは知らない。ただ DOM レベルでの要素の並びの通りに内容を編集しているだけだ。これを、Buffer がマークのことをよく知っているように変更する必要があるかもしれない。それと、バッファの編集操作の中で shift() だけはマークの仕様を知っている必要があったので、このメソッドは大掛かりに変更する必要がある。

Chromium versus Firefox on xubuntu

かつて、Presto Opera からの移行先をいろいろ検討した結果、Firefox を選択したわけなのだが、残念ながら、Firefox がベストなブラウザというわけでは全然ない。

xubuntu のノート上で Firefox を起動し、ノートをサスペンドし、リジュームする。そうすると困ったことにやたらノートが発熱する。不思議なことに load average は特段変化はないのだけれど、しかしノートの底面は信じられないほど熱くなる。Chromium ではそういうことはない。

そのほかにも、Linux 版の Firefox はなかなか不安定だ。入れているのが Developer Edition というのを差し引いたとしても週に数度は落ちるし、Flash のコンテンツは「運が良ければ見られる」レベルだし、そもそも Linux 版の Flash をどうするのかステートメントがいまいちはっきりしないし、製品はもとより拡張の行く末を始めとして Mozilla の動き自体が迷走に迷走を重ねていて、Mozilla というブランドの信用性はもう本当に、急降下している。

そんなわけで Chromium もちょっとずつ使うようにしているのだけど、困ったことにこちらにもいくつか問題があって、オムニボックス上の文字入力がやたら重いのである。キーを 1 つ打って、そのレスポンスが帰ってくるまでに数秒かかる。これはもしかしたら、いわゆる「おま環」なのかもしれない。ググってみるとプロファイルを一新すると治る的な記事もある。しかしこの現象、うちの環境では Chromium だけではなく Opera Developer でも同じなのだ。それなりに普遍的な不具合ではないのだろうか。

Separation #2

ぼちぼち書いている。テキストを HTML ドキュメントではなく、単純に String[] に保持させる。面倒なのは、従来の Buffer クラスはモデルとビューを兼ねていたので Buffer クラスが getClosestOffsetToPixels() 等々ビジュアルな要素に係る機能を持ってたりするのだが、それを分離する必要があったりする。

それから、今までテストは Selenium を用いた機能テストが主で、実はユニットテストはあんまりしていなかったのだが(UAX #14 に関してだけ qunit でテストできるようにしていた)、nodeunit で大掛かりにテストするようにしてみた。

Separation

そろそろ、モデルとビューの分離について考えたい。

モデルはここではデータ構造、つまりテキストを保持するバッファで、ビューはそれを表示する仕組みだ。現状は、それらは wasavi の iframe が保持する html ドキュメントそのものになっているのだが、DOM ベースのドキュメントをテキストエディタのバッファ代わりにした時のメモリ効率はたぶんあんまりあるいは全然良くない(ブラウザの実装によるかもしれない)。

それから、ビュー側でそのレンダリング処理をブラウザ任せにしている。これはこれで楽といえば楽なのだが、無駄だ。ブラウザのレンダリングって要するにページ全体のビットマップイメージを保持してるわけで、1万行とかあるテキストの編集でそれを行うのはとっても無駄だ。見えてる部分だけレンダリングすればよいのだ。

そういうわけでモデルとビューを分離した上で、それぞれを効率のよい実装のものに置き換える必要がある。

weblioPane fixed #2

以前 weblioPane の変更版を作ったのだが、それ以来いくつかのリクエストがあって:

  • pdf 上で動作しない(オリジナルはできた)
  • mht 上で動作しない(オリジナルはできた)
  • EPubReader 上で動作しない(オリジナルは不明)

なぜオリジナルの weblioPane で出来ていたことが、変更版ではできなくなっているかというと、これは選択範囲が生成されたことを検知する仕組みを変えたからだ。

オリジナル版では SDK が提供する selection モジュールを使用しているのだが、しかしこのモジュール、e10s を有効にすると動かなくなる。この不具合は Mozilla 内でも認識済みのようなのだが、どういうわけか一向に治る気配がない。

そんなわけで、変更版では selection モジュールではなく PageMod モジュールでコード片を各ページに差し込み、選択範囲が生成されたイベントを拡張側に通知するようにしてある。従って、変更版で動かなくなったページがあるとしたらこの変更点が最も怪しいということになる。

Handling Unicode #11

様々な箇所で必要なら Unistring を使用するようにする修正が完了しつつある。実際には Buffer クラスの他、様々な箇所で Position クラスの col プロパティを直接インクリメント・デクリメントしており、それを修正していくことになる。

このアプローチはつまり、基本的には文字列が UTF-16 のシーケンスであることを意識した上で、各箇所で論理的な文字単位と UTF-16 のインデックスとを相互変換するということで、割と煩雑だ。

一方、異なるアプローチも考えられる。文字のインデックスは常に論理的な文字単位をベースにし、Buffer クラス内でレンダリングする際に Unistring を使う。おそらくは、こちらが正しい。ただ現在はレンダリングはブラウザ任せなので、やりたいけどそうはいかない。これは将来の課題だ。

ところで javascript で構築した vi という点でいろいろな人が作った諸々を見てみると、おそらくサロゲートペアと結合文字列を意識した動作をするものはない。たとえば CodeMirror の vim バインディングはなかなか良く出来ているが、上記のトピックを正しく処理しない。その点で wasavi のアドバンテージが 1 つ増えたわけで、これは誇っていいと思う。

Handling Unicode #10

他のモーションも修正する。

  • [cci]|[/cci] このコマンドは、与えられたカウントを桁数とみなしてカーソルをそこへ移動させる。従来はカウンタを単純に UTF-16 シーケンスのインデックスとして使用していたが、charWidth 変数(文字の平均幅をピクセル単位で表す)×カウンタの位置に最も近い書記素クラスタのインデックスを算出するようにした。
  • [cci]ga[/cci] このコマンドは、カーソル位置の文字のコードポイントを表示する。書記素クラスタ全体を対象とするように修正。
  • [cci]x/X[/cci] このコマンドは、カーソル位置の前方、あるいは後方に向かってカウント分文字を削除する。これを書記素クラスタ単位で削除するように修正。ただ、後方削除が実際にどういった削除を行うかは方法が 2 通り考えられる:
    • 一度に書記素クラスタ全体を削除する – vim など
    • 基底文字、結合文字単位で削除する – Chrome や Firefox の textarea など

    とりあえず vim に合わせてみる。

その他、モーション以外に編集コマンド、bound モード、line_input モードが残っている。なかなか先は長い。

Handling Unicode #9

[cci]motionUpDown[/cci]。

テキストエディタはカーソルの行位置・桁位置を管理保持する。このとき桁位置に関して、メモ帳のようなシンプルなエディタ以外はたいてい、カーソルの本来の桁位置とは別に架空の桁位置を持っている。この架空の桁位置は、カーソルが水平移動した時にカーソルの本来の桁位置に同期する。カーソルが垂直移動した時は変更されず、かつカーソルの桁位置は架空の桁位置に最も近い位置に算出される。

これで何が良くなるのかというと:

aaaaaaaaaaa

bbbbbbbbbbb

なんてテキストの先頭行の最終桁にカーソルがあった時、最終行の最終桁近辺を編集したくなったとする。そこで、下矢印キーを 2 回押すわけだが。架空の桁位置がないと中間の行でカーソル位置が行頭に固定されてしまい、最終行に達した時にはわざわざ最終桁へ移動させる手間が増えてしまう。架空の桁位置の仕組みがあると、最終行にカーソルが移動した時その桁位置は架空の桁位置に最も近い位置に再生されて、無駄なカーソル移動をしなくて済むというわけだ。このとき、絶対に等幅のフォントでしか描画しない!というわけでなければ、架空の桁位置はピクセル単位で保持することになる。そういうわけで、「あるピクセル位置に最も近い、文字列上の桁位置」というのを算出する処理が必要になる。

処理の内容は、素朴に考えれば、文字列の先頭から1文字ずつ切り出すループを設け、その部分文字列の offsetWidth を出し、それが基準ピクセル位置を超えていたならば、超える直前の offsetWidth と比較して距離が近い方を採用し、それに対応するループカウンタが結果の桁位置になる……という感じになる。

しかしこのまま組むと結構遅い。offsetWidth というのはそんなに軽くないメソッドなので、100 文字あったら 100 回 offsetWidth を呼ぶというのは避けなければならない。

で、実は [cci]motionUpDown()[/cci] はすでにそうなっていて(offsetWidth の呼び出しが算出される桁位置の log2 〜 2log2 で収まるようになっている)、それを Unistring を使うように修正する必要があり、そうした。

この修正は、折り返し行単位でのカーソルの上下移動とも関わるのでこれで終わりではない。

* * *

そういうわけで、その辺の諸々を更新。