自作エディターを作る! はじめに
「使ってるエディタはなんですか?」
「自作です」
って言えたらかっこいいなってことで、自作エディターを作り始めました。
背景
さっそく自己紹介記事に書いてないことをやり始めてますが、いいんです。趣味だから。やりたいことを楽しみましょう。ちなみに趣味だから途中で尻すぼみになりそう…でも趣味だからいいんです。
方針ですが、まずはGUIを考えなくていいコンソール上でできるものを作って行くつもりです。コンソールで動くエディタには、1キロライン以下で書かれたkiloなんかもありますし、頑張れば作れそう〜という発想です。まあ、単純にGUIエディタを作るフレームワークをどうしようかな?に悩んだとこがあります。
使用言語はやはりなれているのとコンソールを操作しやすそうで、STLとか使ってらくできそうなC++にします。Qiitaの記事にコンソール上で動くエディタを作るアドベントカレンダーがあったのでそれを参考にしていきます。
開発とか設計情報とか
リポジトリを作る
つくりました。
またちなみにある程度実装もしています。
WSLのgccでビルドして、WSL上で動かせます。
第1引数で受け取ったテキストエディタをコンソール上に表示して、10行固定で表示します。PageDownとPageUpに対応していて表示ページを切り替えることができます。
まだコンソール画面の大きさを見れていないので、1ページあたりの行数は10行固定になっています。
簡単な設計もどきみたいなもの
目指せオブジェクト指向!ではないですが、機能単位にまとめて作っていけたらなと…。
- main.cpp - main関数がいる。後述するCEditEngineをrunする人。MFCでいうCXXXDlgを作る人みたいな。
- class CEditEngine - エディタのエンジン。今の所、画面の表示とコマンドの処理がメイン。キー入力もこいつが見ている。
- class CLineMgr - エディタのバッファ管理クラス。ファイルをバッファに読み込んだり、CEditEngineからの依頼に答えて、特定行の文字列を返してあげたりする。
- console.cpp - コンソールの操作コマンド集。クラスとはちょっと違うし、関数群にするかなあ。こいつでコンソールのrawモードとcookedモードを切り替えている。
とりあえず現状動いているけど、直近の課題としてCEditEngineからキー入力の受け取りを除外したい。キー入力管理クラスを作って、このクラスの中でCEditEngineに対する操作を考えて、メッセージを投げるみたいなやり方にしたい。その方が、Undo/Redoを実装するときにきっとやりやすいはず。(Undo/Redo管理クラスを作って、CEditEngineへのメッセージの逆操作をstackに積んでおけば、上から取り出してメッセージ発行するだけでOK)。
なので改良後の様子としては、以下のクラスが追加になる感じ。
- class CEditControl - これからはこいつがキー入力を受け取る。キー入力を受け取って、CEditEngineに操作メッセージを投げる。編集結果のコンソールへの描画も行う。(描画は他のクラスに投げちゃってもいいかも)
- class CUndoRedoMgr - この人がUndoとRedoを管理する? 予定。CEditEngineにきたメッセージのうち、Undo/Redo対象のものについてはバッファに積んでおく。Undo/Redoの実行メッセージが来たら積んでおいたバッファから適宜処理。
- class CEditEngine(変更) - キー入力の処理とか画面描画とかはやめて、エディタ的なバッファへの操作、表示ページ管理とかをやる。
改良後のクラス図はこんな感じかな?を書きました。あまりクラス図を新しく起こすことはないので勉強し直しました。mainとかconsoleのクラス図への組み込み方が自信ないな。
今後の方針
ひとまずは、CEditEngineからキー解釈を分離しましょうかね。あとに回して、ガチガチに絡み合ってからだと大変ですし。その場合、エディタの状態まで管理するのは誰にしようかが悩みどころですが…。
ただUndo/Redoとか、キーバインドの導入とかを考えると、CEditEngineはキー解釈に係る部分の状態管理はできない気がしますね。
メモ書き - Undo/Redoについて
Undo/Redoの設計アイデアについて、忘れないようにメモ書きしておきます。
Undo/Redoは操作によるCEditEngine へのコマンドについて、逆コマンドをstackに積んで実現しようと考えていますが、このstackのデータ構造について、複数のコマンドをつなげたコマンドチェーンにしようと考えています。
というのも、例えばある範囲を選択した状態で、ペースト操作をするとその範囲の文字列がペーストした文字で置き換わりますが、置き換えというコマンドを作りたくないのです。
これは削除→挿入で代用できます。
Undo/Redoの操作単位にコマンドチェーンを採用すれば、stackに積むコマンドは挿入と削除の組み合わせでよく、挿入<->削除の1対1関係になるので実装も簡単になるはず。