えーと。

OK, J・J*1, 遅いのはバッファのせいじゃなさそうだ。それは認めよう。未知のバッファ構造に触れて私が興奮していたのは事実だし、それで多少は意味不明な事を書いて恥をさらしたのも認めるのにやぶさかでは無いよ。
だがちょっと待ってくれ。じゃあ、何が遅いんだ?
色分けロジックが致命的に遅いのは承知している。だが、これは最初こそ遅いが一旦最後まで色分けが済んでしまうと、後は差分だけの処理だ。Windows.pasを開いて、最後までスクロールしてみたまえ。初回はイライラさせられるが、二回目以降はスムーズだ。感動的だね。途中を編集したって、色分けを再計算するのは前後の数行でしかない。
それで疑問となる。じゃあ、何が遅いんだ?

OutputString(PChar(IntToStr(GetTickCount)))というあまり精度のよくなさげというよりお粗末な式を埋めこんで測定しましたところ、一文字の挿入が0.020秒程度ならスムーズに処理され、0.030秒程度なら多少表示が引っかかるものの待たされはしないようです。恐らくこの0.010秒の差が、WM_PAINTのタイミングを奪ってるんでしょう。もちろんGetTickCountは0.001秒単位で取得はできますが精度はもっと悪いのは承知の上──。

で、現象としては、ファイルの末尾に近づくほど高速に編集できます。前の方ほど待たされます。4MBのファイルの先頭部分なんか、0.060秒もかかってます。

で、結果から書きますと…バッファとは別に、行の管理情報が双方向リンクリストになっているわけです。そこには、その行が先頭から何バイト目からはじまっているかが、32ビット整数として記録されています。一文字挿入したら、現在キャレットがある行以降の全てに対して、リンクリストを辿ってそのデータフィールドに+1して回らなければなりません。
4MBのファイルは行にして27万行近くあります。
以下の処理を27万回繰り返すのが、時間のほとんどを食っていた、というわけです。

add TLine[eax].Position, edx
mov eax, TLine[eax].Succ;
test eax, eax
jnz @loop

GreenPad…普通のエディタでも同じですが、一度引用した縁で今回も引用させてください…では、一文字挿入では以降の行を更新する必要はありません。というよりソース見て驚いたのですが、行が行番号等の全文書中の位置を示す情報を全く持っていないため、その手の必要は全く無いのです。
Thebeでは愚かにも、行は、開始バイト位置と、行番号というふたつの、文書中の位置を示す情報を格納しています。故に…遅くなるのです。

Q.E.D.

…って行番号は工夫すれば取り除けますけど、開始バイト位置は…行が実際のデータを持てるならまだしも、ひとつのバッファを複数のビューが、しかも異なるエンコーディングや表示形式で共有するがために、各「行」にバッファ内の位置を示す何らかのポインタが必要な訳で…

OK, J・J. そのバッファもまた単方向リンクリストだ。前の方に挿入/削除を行っても後ろの方に被害は無い。バイト位置だから毎回更新が必要になるのであって、各細切れ要素へのポインタ+その細切れ要素内での位置、という構造なら、無関係でいられる場所が増えるかも知れない。

…しかし…ビューは複数あるんですよ?矛盾無く整合性を保てるのか…は私の技量次第なんですが、今更そんな大規模な変更を!?

やだー

*1:エラリー・クイーンの国名シリーズに出てくる「聞き手」