JavaScriptでターミナルエディタを自作する:kilo.cポーティングの6年後レポート
JavaScriptでターミナルエディタを自作する:kilo.cポーティングの6年後レポート
「小さく作って深く理解する」エディタ実装記。
インストールの仕方
npm i -g kilo-editor
イントロダクション:なぜJavaScriptでエディタを作るのか?
「エディタの内部構造を理解したい」──これが動機でした。
kilo.cはC言語で書かれた極小エディタで、画面描画・入力処理・バッファ管理などが手作りで詰まっている最高の教材です。この“ミニマルで全部見える”感覚が、徐々に理解ができます。
では、なぜJavaScript?
- 普段使いの言語で理解が「道具」を理解したい
- Node.jsの標準API(stdin/stdout/TTY)でどこまでやれるか試してみたかった
6年前は「動いた!すごい!」で終わっていたのですが、あらためて眺めてみました。
技術的な工夫・ポーティングの肝
1. Cのポインタ/メモリ管理を、JSの抽象化で置き換える
kilo.cではバッファ操作や文字列処理にポインタ操作が登場します。JavaScriptでは直接メモリを触れないので、配列や文字列操作で代替します。
- 行バッファ:
erow(編集用の文字列配列) - 描画用バッファ:
render(タブ展開などの見た目用配列) - 画面への描画バッファ:
abuf(端末制御シーケンスをまとめて吐く)
2. Node.jsの標準機能で“素のターミナル”を操る
エディタの基本はRAWモードに入ってキー入力をダイレクトに扱うこと。Node.jsなら process.stdin.setRawMode(true) で実現できます。
-
readline.emitKeypressEvents()でキーイベント化 -
process.stdin.on("keypress", ...)で入力ハンドリング -
process.stdoutに ANSI エスケープシーケンスを吐き出して描画
「GUIを一切使わず、端末だけで操作する」感覚が気持ちよかったのを覚えています。
3. RAWモードと描画は“バッファリング”が命
C版と同じく、画面描画は逐次書き込みではなく、
abuf に集めてから一気に process.stdout.write で出力します。
この「バッファリング」戦略がないと、
画面がチカチカしたり描画が遅延したりします。
ソースコード解説(ハイライト)
※ 以下は“ここだけ読めば全体が見える”場所を抜粋して解説します。
1. メインループ:イベントで回るエディタ
kilo.jsではループを手で回しません。
keypress イベントが入力のたびに処理を呼ぶイベント駆動型ループになっています。
Kilo.enableRawMode();
if (typeof this.E.filename !== "undefined") {
this.editorOpen();
}
this.editorRefreshScreen();
process.stdin.on("keypress", this.editorReadKey.bind(this));
process.stdout.on("resize", this.editorResize.bind(this));
- RAWモードに入る
- ファイルを読み込み
- 初期描画
-
keypressで入力を処理 -
resizeで再描画
Cの無限ループと比べて、JSはイベント駆動のほうが自然です。
2. スクロールとレンダリング:rx/rowoff/coloffが肝
スクロールの基本は「カーソルが画面外に出たらオフセットを動かす」というもの。kilo.jsでは editorScroll() がそれを担っています。
-
rx(レンダリング後のX位置) -
rowoff(縦スクロール) -
coloff(横スクロール)
これらを毎回計算して論理カーソル位置と画面上の描画位置を同期します。
3. 画面描画:ANSIシーケンスの積み上げ
描画のコアは editorRefreshScreen()。
- カーソルを消す
- 画面原点に移動
- 行を描画
- ステータスバー/メッセージバー
- カーソル位置を復元して表示
この手順が、“端末UI”を成立させる最小セットです。
4. 検索機能:配列にヒット位置を積む
検索は「ヒットした位置を配列に積んで、左右キーで巡回する」方式。
-
sx/syにヒット位置を収集 -
siに現在のインデックス -
←/→で前後に移動
シンプルですが、エディタの“インクリメンタル検索”感が出てくるポイントです。
5. シンタックスハイライト:正規表現の力技
色付けは editorUpdateSyntax() で実装。
正規表現で特定パターンを色付けしています。
- 数字 / 文字列 / コメント / キーワード
- ANSIカラーコードで装飾
構文解析器なしでも、ある程度“それっぽく見える”のが面白いところ。
状態遷移図
今振り返って(6年後の視点)
当時の自分に教えたいこと
-
構造化された状態管理を導入しておけば良かった(今見ると
this.Eが巨大) - “描画”と“ロジック”を分けると将来が楽(UI差し替えが容易)
当時は「動いたら勝ち」だったけど、今は「読みやすさと保守性」も勝ち条件になっている気がします。
まとめ:車輪の再発明でしか得られないもの
エディタを作るのは、“車輪の再発明” です。
でもその過程で手に入るのは、
- 文字列の扱い方
- 端末の描画モデル
- 状態管理の難しさ
- “UIを手で描く”という体験
が手に入ります。。
もし「エディタの仕組みに興味がある」なら、
自作してみる は最高の学習になります。
Discussion