企画編 試作編 設計編 実装編

U's Text Editor

まずは「メモ帳」相当の機能から

基本機能

最初の取り組みとして「メモ帳」相当の機能を持つコードを作り、今後の開発の基礎にします。ファイルを開いて内容を読み出し、編集し、書き戻すだけのごく単純なSDI形式のアプリケーションです。同時に作業できるファイルは1つだけです。キー操作については、EDITを基本として、一般的な作法に従います。クリップボードには対応しますが、検索や置換などは不要です。このイテレーションでは基本機能を満たすことに集中します。

バイリンガルUIと極東言語テキストの編集

実行ファイルに日本語版と英語版のリソースを持たせ、日本語版以外のWindowsで実行した場合は英語で対応します。また、Shift-JISだけでなく中文(GBK/Big5)やハングル(Wansung)など、他の極東言語のテキストも編集できるようにDBCSの処理を汎化しておきます。

document-view アーキテクチャ

document-view アーキテクチャは、MVC(Model-View-Controller)パターンを簡略化したものとして知られています。Controllerに相当する機能の位置付けが定まらない点を問題視する向きもありますが、データモデルとそのプレゼンテーションを分離する考え方はソフトウェアデザインにおける定石とも言えるので、その原則に従ってオブジェクトを切り分けます。

view-editor アプローチ

document-view アーキテクチャにおけるviewの部分は、さらにviewとeditorに分割します。これはEclipseから拝借したアイデアで(フレームワークが異なるのでまったく同じとは言えませんが...)、viewが純粋に表示に関する処理のみを司るのに対し、editorはキー入力やマウス入力を検出してviewやdocumentに指示を与えます。言わばMVCパターンのControllerに相当する役割を担います。

オンメモリ型エディタ

テキストの編集作業はすべてメモリ上で行います。一時ファイルなどは持たせません。オンメモリで作業するため極端に大きなテキストの編集には向きませんが、ログファイルなど機械的に作成されたテキストを相手にするつもりはありませんので、そのような特殊な用途は他のエディタに任せます。とは言え、最近のPCはメモリを潤沢に積んでいるので、そんなに気にするほどのこともないでしょう。

テキストバッファ

テキストバッファの形式は、アプリケーションのアーキテクチャに大きく影響します。オンメモリで作業する場合はとくにそれが顕著になります。U's Text Editorの心臓部とも言える部分なので、それなりに慎重な設計が必要になります。いくつかの形式が知られていますが、STLを使って表すと次の2つのタイプに大別できます。

vector<string>テキストを単純な2次元配列で保持する形式です。ランダムアクセスが可能で管理情報などのオーバーヘッドが比較的小さいのが特徴ですが、一度に大きなメモリブロックを確保しなければならない点が問題となります。テキストが小さい内はよいのですが、行数が増えるほどヒープのフラグメンテーションが心配になってきます。せっかくメモリがあまっていても、それらが分散してしまうと結果的にメモリ不足を起こしてしまいます。

list<string>テキストの各行を双方向リンクでつないで保持する形式です。シーケンシャルアクセスが主体で、ランダムアクセスの効率が著しく低下してしまうこと、管理情報などのオーバーヘッドが多少増えてしまうことが欠点です。しかし、巨大なメモリブロックを必要としない点はメリットです。ランダムアクセスについては、キャッシュを設けることで、ある程度効率の低下を抑えることが出来ます。

どちらの形式にも一長一短があり選択に悩むところですが、U's Text Editorでは後者のlist<string>を採用しています。なかなか管理の難しい形式ではありますが、メモリ効率を優先しました。ちなみに、各行の保持には「ギャップバッファ」を用いる方法もあります。挿入ポイントに合わせてバッファのギャップをダイナミックに移動させる手法で、編集時の入力効率の向上が期待できます。しかし、余計な小細工をするよりも単純に処理した方がかえって効率的と考え、採用はしませんでした。その代わり、テキストを物理行単位に分割し、上限付きの文字配列として管理しています。

論理行と物理行

理想を言えば物理行はviewの管轄なのですが、そう単純にはいかないのが現実です。スクロール範囲の計算や行の折り返し処理など、結局はview自身もある種のデータモデルを管理しなければならなくなり、documentとの同期化が難しくなってきます。そこで、素直に、documentで物理行を管理するようにしました。物理行と論理行でリンクを二重化することも考えましたが、果たしてそれが効果的な解なのかどうか結論は出ていません。今後のイテレーションでもう一度検討してみようと思います。

コードページ

テキスト中のDBCSコードは、Windowsのユーザーロケールに設定されたコードページに従って判別します。ただし、実行中にコードページを切り替えることは出来ないので、起動時に取得した情報に基づいてコードページを決定します。そのため、最初に決定されたロケールに合致する入力ロケールでなければ入力した文字が化ける場合があります。これらの制約はDBCSの特性によるものなので、あえて回避するようなことはしません。運用上の問題であると考えます。

なお、処理を単純化するため、2バイトコードのグリフ(全角文字)は1バイトコードのグリフ(半角文字)のちょうど2倍の幅を持つことを前提にしています。ハングル系フォントの一部には無効な2バイトコードが半角の?になってしまう現象があるようですが(Win2K英語版で確認)、無理に対応はしません。

オブジェクトの粒度

オブジェクト指向の難しさは、オブジェクトの見分け方にあります。オブジェクトを細かく切り分けすぎるとすぐにクラス数が「爆発」してしまい、管理が難しくなるだけでなく、スパゲッティコードと紙一重になってしまいます。かと言ってあまり粒度を大きくしてもオブジェクト指向のメリットを享受できないので、そのさじ加減が決め手になります。U's Text Editorにおけるオブジェクトの粒度は、大きすぎず小さすぎず、といったところです。新しく作るクラスは一人の頭で十分に把握できる程度に絞り、できるだけ標準ライブラリやSTLを活用しています。

ソースコード

U's Text Editor Ver. 0.11 2003/02/05 ZIP 46.6KB
  • min/maxマクロが使われていたところを_MIN/_MAXマクロに変更しました。
  • 行が挿入/削除された際のCTextView::OnUpdate()におけるスクロール量の計算に誤りがありました。先頭行の一部がスクロールアウトしている状態でインデント/アウトデントを行うと、表示が乱れる場合があります。
U's Text Editor Ver. 0.10 2003/02/01 ZIP 46.5KB

現段階ではまだリファクタリングの余地があるでしょうし、実験的なコードも含まれています。また、将来を見据えて多少冗長的な造りにもなっています。XP(eXtreme Programming)推進派からは非難を浴びそうですが、何事も度が過ぎると逆効果にもなりかねないので、臨機応変、良かれと思うならすぐに実施してしまうのが私の主義です。

命名規則

識別子の命名規則にはハンガリアン記法を採用しています。ただし、一部には標準C/C++ライブラリの命名規則も含まれています。これらは主に標準ライブラリやSTLを多用するコードに見られます。命名規則を一貫させるのが原則ではありますが、ソースファイルごとにどちらの規則を優先させるか判断しています。たとえば、標準ライブラリを拡張するのが目的であれば、標準ライブラリの命名規則を優先させますし、場合によっては混在することもあります。

ファイル構成

ReadMe.txt
著作権とライセンス条項を記したファイルです(英文)。
USEdit.dsp
Visual C++のプロジェクトファイルです。
USEdit.ico
USEdit.rc
Toolbar.bmp
Resource.h
リソーススクリプトやイメージファイルなど、リソース関連のファイルです。
StdAfx.h
StdAfx.cpp
標準的なヘッダファイルのプリコンパイルに使われるファイルです。
WinMain.cpp
WinMain関数とスタートアップルーチンの実装です。
AboutBox.h
AboutBox.cpp
About-boxの実装です。アプリケーション名や著作権など、表示する内容はバージョン情報リソースから取得します。
AlertBox.h
AlertBox.cpp
警告用メッセージボックスの実装です。指定されたWin32エラーコードに対応するメッセージをシステムライブラリから取得して表示します。
FileBuff.h
ファイルI/Oのためのストリームバッファクラスです。標準C++ライブラリのbasic_filebufをモデルにしていますが、APIを直接呼び出す点が異なります。
ClipBuff.h
クリップボードとデータを交換するためのストリームバッファクラスです。ファイルI/Oと同様、標準C++ライブラリのbasic_filebufをモデルにしています。
DocView.h
document-view アーキテクチャの基本サービスの実装です。
MainFrm.h
MainFrm.cpp
フレームウィンドウの実装です。フレームウィンドウは、メニュー、ツールバー、ステータスバーを管理し、アイドル時にこれらUIアイテムの状態を更新します。
TextBase.h
テキストバッファの実装です。U's Text Editorの心臓部です。
TextDoc.h
TextDoc.cpp
document部を担うクラスの実装です。テキストバッファを継承し、document-view アーキテクチャに適合させます。
TextView.h
TextView.cpp
view部を担うクラスの実装です。WTLのCScrollWindowImplクラスを継承してdocumentが保持するテキストを視覚化します。
TextEdit.h
TextEdit.cpp
editor部を担うクラスの実装です。viewを継承し、キー入力やマウス入力を捉えてviewやdocumentに指示を与えます。

注意一部にIDEのClassViewペインに表示されないクラスがあります。私の場合、リソースの管理やプロジェクトのビルド、デバッグ以外にIDEを利用することがないので気にはしていませんが、普段これらの機能を活用している方々には不便を強いることになるかもしれません。あらかじめご了承ください。

今後の課題

マルチドキュメントAlt+1〜9によるテキストの切り替えは、私が普段よく使っているEDITの機能のひとつです。複数のテキストを同時に編集できるのはもちろんですが、テキストの切り替えに余計な手間がかからない点が重要です。いくらブラインドタッチができない私でも、タイピング中にわざわざマウスを操作したいとは思いません。できれば簡単なキーボード操作だけで済ませたいところです。

カスタマイズ現在はフォントのタイプフェイス名や大きさ、各要素の配色がハードコーディングされており、プロパティシートのような設定画面は設けていません。また、キーバインディングも自由に変更できるようにしたいところです。ユーザビリティの改善は、今後の大きな課題です。

検索/置換実際の作業では、やはり、検索/置換は是非とも欲しい機能の一つです。正規表現が使えれば言うことはありません。これらの機能は、エディタとして基本機能がしっかりしていれば後からいくらでも追加できるものなので、今後のイテレーションで充実させていきたいと思います。

テキストブラウザ本来の目的であるFD+EDITの置き換えのためには、これまでFDで行ってきたテキストファイルのブラウジングを実現しなければなりません。単純なテキストビュアーであればテキストエディタのコードの大部分が流用できるので、あとはUIの作り込み次第です。

UnicodeまだまだDBCSが現役とは言え、Unicodeへの対応もいずれは考えなくてはなりません。もちろん、現在のアーキテクチャを根本的に改める必要があるので、まったく別のツールとして作り直すことになるかもしれません。