最近いろいろな意味でヤル気が無くなっていたけれど、久しぶりに更新。一年以上何もか書かなかったか。まあそれもまたよし。今日はテキストエディタの「選択範囲」という概念について、2点書いておこう。さすがにTwitterにはつぶやける長さでないし、Facebookなんてもっての他。自前ブログもたまには良いね 🙂
拙作テキストエディタエンジンAzukiの初期設計における大きなミスの一つに、Documentに選択情報を持たせたことがある。Azukiは、Windows標準提供のRichEditをテキストエディタとして使っていく不満に対して「じゃあ作ってしまえ」と思い立ったことが開発のきっかけだった。したがってRichEditの影響をやはり少なからず受けるわけで、RichEditの「選択範囲という概念で無選択状態のキャレット位置まで表現可能」という仕様も踏襲してしまった(正直に言うと今でもこの考え方は結構好き。好きかどうかはさておき引きずってしまった点はScintillaも同じではないかと勝手に推測)。しかしながら、Documentが選択範囲を管理する構造は、機能追加するに従って微妙な雰囲気を呈してくる。
(1)たとえば矩形選択においては、選択範囲は本質的に「左上座標と右下座標」といった画像的な矩形で定義することになるため(プロポーショナルフォントも考える)各文字のX座標はViewが計算することになり、矩形選択中にDocumentへ選択に関する情報を問い合わせてもトンチンカンな答えしか出せなくなる(現状は、問い合わせるメソッドにViewを渡すことで解決)。
(2)また画面分割機能のように複数UIで同一Documentを開くケースを想定すると極端で、選択範囲を全UIが共有してしまうため使い物にならない(普通に実装すると編集直後はScrollToCaretするため全UIが同じ場所を表示することになるし、窓を行ったり来たりしながら「あっち直してこっち直して…」といった作業もできない)。
(3)さらに、Document内のインデックスで選択範囲を記録する方法は行末以降にカーソルを置く「フリーカーソルモード」との相性がきわめて悪い。インデックスは当然個々の文字の位置を指すものであるから、文字が存在していない「行末以降」なる概念は表せない。もちろん、「行末以降」という概念はGUIで描画して初めて現れるのでView側の概念だと思う。なおこれはDocumentが管理すること自体の問題というよりはインデックスで表現することの問題ではある。
上記のように様々な問題等を考えると、やはりDocumentは編集しているデータだけを管理するべきであり、いわば「どうやって編集しようか」というユーザの意思を表現した選択範囲はViewに近いモジュールで管理すべきという話になると思う。Azukiの場合、モジュール分けが結構崩壊しているけれど、ユーザインタラクションを担当する UserInterface モジュールがあるためそちらの管轄として移管するのがあるべき姿だろうと思う。ということで、選択周りの移管を試しに行う実験を正月休みにやっている。想像以上に大変で互換性も壊れることになるが、フリーカーソルが実現できれば正式採用も考えたい。行末以降に矩形選択範囲を伸ばせないのは本当に不便とみな思っている…はずだと思っている。
その実験中に一点、気付いたことがあるので備忘録。これまでAzukiは内部的に矩形選択時だけ選択範囲を特別に扱ってきたが、実は通常の選択と併合できると思われる。具体的には、選択範囲を「一つ以上の、開始インデックス(anchor)と終了インデックス(caret)のペアのリスト」で表現すれば良い。そして通常の選択時の動作を各選択範囲について実行するだけでかなり綺麗に処理できる。たとえば矩形選択範囲をカットする場合は選択範囲Nのテキストをクリップボードにコピーして、選択範囲Nのテキストを削除する。また一部のエディタがサポートしている「幅0の矩形選択を作って文字入力すると各行に同じ文字を一括して挿入・上書きできる」という機能(Scintilla 2.0以降、Visual Studio 2010以降)についても、単純に選択範囲リストにループ処理するだけで良いことになる。当然細かい部分では手を入れる必要があろうが、概念的にはかなりクリアに実装できる。こちらは「選択範囲をインデックスで持たないようにする」という先に記した話と相反するように聞こえるかもしれないが、こちらの肝は「複数選択を前提にすべての処理を考える」という点であるため、おそらく両方の話は共存できると想定している。
…長いなぁ。まあ、それもまたよし。