本稿は自作エディタをつくる Advent Calendar 2016の5日目です、レポジトリはこちら
このアドベントカレンダーは、コンソール上で動くエディタを25日間で1から自作することを目標としています。
前回までで、自分の中で全体像が見えてきたので、今日は設計を考えます
コンソールの幅・高さを取得する
できなかったら詰むな・・・、と思って調べたらここにそのものズバリのことが書いてありました。
できそうでよかった。
設計を考える
コンソールに何を出力したかを覚えて置いて差分だけ更新していく方針も考えたのですが、
いまどきprintfするコストは十分小さいだろうということで、何か変更があれば全部出力しなおす方針にします。
iOS/AndroidアプリやWebアプリと違って、view自体からのフィードバックは来ないので、ある意味単純なモデルにできそうです。
なんとなくFluxっぽい設計にしたいですが、Pub-Subでイベントを処理したり、Web-APIを考慮したり、といった部分を省くと
- ファイル読み込み、を内部モデルに変換する
- キーボードから入力を受け取る
- 入力キーによって「カーソルを動かす」「文字Aをカーソル位置に追加する」などの内部DSLのようなものに変換する
- 内部DSLによって内部モデルを更新する
- 内部モデルからコンソールに出力する文字列を生成する
- 文字列を丸ごと更新(必要があれば差分更新)する
- キーボード入力待ちに戻る
こんな感じの流れになるかなぁ、と思います。
用語集を作る
まず、上の設計からコード上の変数名を決めていきます
- ファイル読み込み、を内部モデルに変換する -> モデル名をContextとします
- キーボードから入力を受け取る -> 関数名をkey_scan、戻り値をinput_keyとします
- 入力キーによって「カーソルを動かす」「文字Aをカーソル位置に追加する」などの内部DSLのようなものに変換する -> 内部DSLをcommandとします
- 内部DSLによって内部モデルを更新する -> update_contextとします
- 内部モデルからコンソールに出力する文字列を生成する -> renderとします
- 文字列を丸ごと更新(必要があれば差分更新)する -> render内にあるprinterとします
- キーボード入力待ちに戻る
次に、マルチバイト文字、行の扱い等の用語を決めていきます。
マルチバイト文字の扱いで辛いところは、以下の例です。
- 「あ」は3バイトだが、カーソル移動で見ると1文字であり、表示上は(日本人がよく使う等幅フォントにおいて)半角2文字分である
それぞれの概念に、用語を決めていきます
- 文字のバイトに着目した処理は、bytesと呼ぶ
- カーソル移動用の文字数に着目した処理は、position_xとよぶ
- 表示上の幅に着目した処理は、view_widthとよぶ
次に、行の扱いについて考えます。
emacsでは行について、論理行と物理行と呼ばれる概念があるようです。
- 長い行について、見た目には2行に折り返しているが、実際は1行である。
ということを表しているようですが、どちらが論理行でどちらが物理行かわかりにくい気がします。
今回は、見た目の行が2行であることをview_height=2、実際には1行であることをposition_y=1と呼ぶことにします。
ざっくりmainループを書く
void render(context context){
str = rendering_text(context);
printer(str);
}
int main(int argc, char *argv[]) {
char* filename = get_args(argc, argv);
context context = file_read(filename);
while(1)
{
input_key key = key_scan();
command = key_parse(key);
context = update_context(context, command);
render(context);
}
return EXIT_SUCCESS;
}
こんな感じの関数にちまちま実装を当てはめていけば完成しそうな気がします。
今日のまとめ
- ざっくりとした設計をした
- 用語を整理した
後はもくもくと実装して行けばよさそうですね。
最近寒いですが、今日はたまたま暖かかった気がします。
(過去分の読みづらかったところを少し直しました。内容はそのままです)