Editor component まとめ

テキストエディタ実装したい病」が再発したので、いろいろ調べてみたまとめ。

SWT StyledText

SWT StyledText は Canvas を継承して実装されている。継承してしまえば描画はやりたい放題で、文字以外の描画も自由自在。ただし、残念なことに特定のフォントで特定の文字列を描画する場合の文字幅を取得することができず、代替手段として FontMetrics#getAverageCharWidth() を使用して、大体の幅を取得するしかない。これは致命的。

行の折り返しこそ存在するものの、単語単位での折り返ししか行わないため、画面端で強制的に折り返すなどができない。

なお StyledText を使用している Eclipse は、そもそも行の折り返しをサポートしない。フォーマッターは一行が任意のカラム超過で折り返しではなく改行を入れる。

ソースコードなら適当な場所で改行を入れるので折り返しがなくても何とかなるが、自然言語の文章は僕の場合段落単位で改行を入れるため、折り返しのないテキストエディタではろくな文章が書けない。

StyledText をベースにしてテキストエディタはありえないという結論。

Swing JTextPane

JTextPane は超強力なエディタコンポーネントだ。ただし、IME との親和性が一昔前と比べれば改善はしているものの、まだまだ。

これは JTextPane 上で Google IME で入力した状態。予測変換のウィンドウがあらぬところに表示されてしまう。

昔は確定前の文字列すらあらぬところに表示されていたので、かなりまともになったとは思うが。

Swing ベースのテキストエディタの代表作として jEdit があるが、やはり同じ状況だ。Java が今後バージョンアップしていくに従い、改善されるだろうか・・・。誰にも分からない。

なお、上記スクリーンショットは JTextPane 以外を SWT で作ってみたプロトタイプだ。「SWT ベースでエディタコンポーネントだけ JTextPane を使えば最強じゃね?」と思ってやってみたんだけど、IME との親和性がやっぱり気になるのでここまで。

Scintilla

前から気になっていたオープンソースC++ 製エディタコンポーネントを少し試してみた。

オープンソースなだけあって、好きなように変更できるのが楽しい。オリジナルは改行表示がきつすぎる、全角スペースが表示されない、EOF が表示されない点を:

このように変更してみた。文字の幅や文字列の幅が正確に取れるのはもちろん、描画 API も備えていて、フォントグリフ以外も描画可能な自由度の高さがある。

Scintillaクロスプラットフォームのエディタコンポーネントなので、これをベースにテキストエディタを作れば、簡単にクロスプラットフォームテキストエディタの完成じゃん、と思っていたのだが、実はそんなに簡単な問題ではなかったということがわかった。

Scintilla は見たところ Windows 向けに Win32 API 実装が、Mac 向けに Cocoa 実装が、Unix 向けに GTK+ と Qt 実装がある。Scintilla のコアは共通のコードだが、各プラットフォームでのコンポーネントの使い方は異なり、つまり Win32 API の場合はウィンドウに対してメッセージ送信を行って Scintilla を操作するのに対し、GTK+ の場合はウィジェットとの通信によって Scintilla を操作する。Cocoa に至っては Objective-C で実装しなければならない。

どういうことか。

つまり、クロスプラットフォームではあるものの、ソースコードは全く共通化できないということを意味する。実際、Scintilla の作者による SciTE というテキストエディタは、Win32 と GTK+ の全く異なる実装が存在している*1

なるほど、そういうことか。

ネイティブのコンポーネントを直接利用するメリットとして、例えば Mac OSX なら OS 標準のスペルチェック機能が利用できる。そうしたネイティブの恩恵を受ける代わりに、ネイティブ固有のコードを書かなければならず、Scintilla はその固有コードが GUI Toolkit のコードだということになるわけだ。例えば Qt を利用した Scintilla を Mac OSX 上で動かした場合、スペルチェック機能は失われてしまうだろう。

最後に、Scintilla の重大な制約というか仕様を。

Scintilla はエディタコンポーネントとしては超優秀なんだけど、サポートする文字コードが今ひとつであるように思う。どうやら、初期のバージョンで Windows 向けに開発されていた際、Windows のコードページをベースに文字コードのサポートが実装されたようだ。日本語のコードページは CP932 で、いわゆる Shift_JIS がサポートされているが、EUC-JP や JIS などは Scintilla ではサポートされていない。

Scintilla にテキストを渡す前に、ICU などで独自に文字コードを解析し、UTF-8 などに変換して渡す、というような処理が必要になってくる*2。出力はその逆。変換で失われてしまう情報があるのが痛いところだ。

NCurses

NCurses はエディタコンポーネントなのか?と言うのは置いておいて。

一日中ずっとターミナルの中にいる時期があり、vim 以外の快適なエディタが欲しいなぁ、と常々思っていて調べてみた。

NCurses は簡単な CUI だったら本当に簡単に実装できてしまうが、NCurses はサロゲートペアを正しく扱えないという弱点が存在する。

NCurses では、マルチバイト文字は wchar_t を使用して出力する。waddch() などがそうだ。ただし、サロゲートペアは、一文字が wchar_t ふたつで構成されているため、waddch() を二度呼び出す必要があるが、これを一文字とは認識してくれないどころか、正しくも表示されない。waddchstr() で wchar_t* で渡したとしても同様。

サロゲートペアをターミナルに正しく表示させるには addch() で UTF-8 として渡せば良かったりする*3。ただし、この場合は画面上に表示されている文字数と NCurses 内部で管理しているバッファに差異が生じ、具体的にはキャレットの位置がおかしくなる。

NCurses でテキストエディタを実装する場合、サロゲートペアを発見したら〓などに変換して表示するなどの割り切りが必要になる。

NCurses を使わずに実装するなら、termcap/terminfo を直接読み、エスケープシーケンスを解析しなければならない。これがまた鬼門である。その理由は、termcap/terminfo から実行時に得られる情報が不足しているからに他ならず、総合的に使えるようにするには xterm や VT100 などの固有のターミナルエミュレータに対応していく必要がある。

vimemacsサロゲートペアを適切に扱えるため、彼らはそうした茨の道を歩んだのだと思う。

ちょっと気になったのは、Curses は vi の IO を抽出して実装されたはずなのに、vim はそれを使用していないということ、Curses は termcap/terminfo の機能を使用して実装されたのだと思うんだけど、今となっては terminfo は NCurses の一部になってしまっていることなどだ。

*1:実装は全く異なるが、メンテナンス性のために似せてはあるようだ

*2:ScintillaUTF-16 もサポートしているが、内部では UTF-8 で管理されているの

*3:これは正しい方法ではない