はじめに
この記事を見たのがきっかけで、C言語1000行でテキストエディタを作るkiloというプロジェクトがあることを知りました。
C++の勉強がてらなにかプロダクトを作ってみたかったので、kiloをC++で書きかえたkilo++を作ってみました。
ただし、それだけだと「お勉強」で半ば終わってしまうので、このプログラムの作り方をステップバイステップで説明するチュートリアルを生成AIを使って作ってみました。
C++でテキストエディタを作る
基本的にはオリジナルのkiloの内容をオブジェクト指向プログラミングにしただけです。
kiloでは1ファイルに1000行全て納めたシンプルさをコンセプトにしていたのに対して、kilo++ではファイルを分割し、拡張性を意識した、より実践的な内容にしてみました。
kilo++/
├── include/
│ └── kilo++/
│ ├── Editor.hpp
│ └── EditorUtils.hpp
├── src/
| ├── CMakeLists.txt
| ├── Editor.cpp
| ├── EditorUtils.cpp
| └── kilo.cpp
└── CMakeLists.txt
目ぼしい変更点としては、
kiloでは入力されるテキストデータをポインタで管理していたのに対し、(C++なら当然のこととして)kilo++ではstringとvectorを使うようにしたこと。テキストファイルのサイズが増大するとパフォーマンスがどうなるのか心配ですが、これに伴って、できる限りコンテナ操作のための組み込み関数を活用するように変更しました。
struct EditorRow
{
EditorRow(int index) : idx(index), row({}), rendered({}), hl({})
{
hl_open_comment = false;
};
int idx;
std::string row;
std::string rendered;
std::vector<EditorHighlight> hl;
bool hl_open_comment;
};
// Editor class
class Editor
{
...
private:
/*** members ***/
std::vector<EditorRow> m_rows;
int m_cx = 0, m_cy = 0;
int m_rx = 0;
int m_rowoff = 0, m_coloff = 0;
int m_screenrows, m_screencols;
uint8_t m_dirty = 0;
std::string m_filename = "";
std::string m_statusmsg = "\0";
time_t m_statusmsg_time = 0;
std::shared_ptr<EditorSyntax> m_syntax = nullptr;
};
難点だったのが、termios構造体の扱い。
termiosはターミナルI/Oインターフェースを提供するUnix APIですが、staticな関数でないと扱うことができず、Editorクラスのメンバーとして管理することができませんでした。
結局、termiosを使う部分だけ別ファイルにして、namespaceを割り当てて管理するという方針に落ち着きました。
#include <termios.h>
#include <unistd.h>
namespace terminal_manager
{
termios orig_termios;
void disableRawMode()
{
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios) == -1)
die("tcsetattr");
}
void enableRawMode()
{
if (tcgetattr(STDIN_FILENO, &orig_termios) == -1)
die("tcgetattr");
atexit(disableRawMode);
生成AIでチュートリアルを作成する
これも元ネタはオリジナルのkiloが同様のチュートリアルを公開していたから。
この内容を参考にしつつ、ソースコードを入力としてチュートリアルのドキュメントを作れるか試してみました。
まずは無料枠でできるか試す
最初に試したのがClaude。
Artifactによる成果物の確認、編集がしやすいので真っ先に使ってみました。
しかし、問題だったのが、オリジナルのkiloのチュートリアルドキュメントの入力。
最近の生成AIはYoutubeの内容を要約できたりするので、URLで指定したWebページも読みこんでくれると期待しましたが、無理でした。
そこで、ソースコードはrepomixでテキストファイルに集約。
kiloのチュートリアルはPDFファイルにして、プロンプトに添付しました。
最初の方はこれで上手く結果を出力できていたのですが、やはり入力トークンが多すぎて、一時的にClaudeを利用できなくなってしまいました。ChatGPTでも同様です。
そこで試したのが、AWS Bedrock。
オンデマンドで利用できるはずなので、使った分のコストはかかるにしても処理しきれることを期待しましたが、これもダメでした。Playgroundで試したからかもしれません。
最後に試したのが、Googleが提供するNotebook LMとGemini。
特にNotebook LMはURLを指定するだけで、指定先のWebページの情報を読み取ってくれるので今回の用途にはうってつけに思えました。
しかし、どちらも英語で出力することを明記しても、なぜか日本語で出力する始末。プロンプトも英語で書けばよかったのかもしれません。
また、オリジナルのkiloとkilo++を混同しだして、両方のコードをミックスしたような内容を吐き出しはじめたので、ここでの利用は断念しました。
有料プランを試す
そういう訳で結局、ClaudeのProfessional Planを契約しました。
Gemini Advanced(初回1カ月無料!)にしてもよかったのですが、上記の問題もあってClaudeにしました。
Professional Planにしても顕著だったのが、ドキュメント中でコードの記載部分をまとめて書いてしまうこと。
kiloのチュートリアルはコードの説明部分はかなり詳細に書いていて、関数ごとに区切って実装とその説明が書かれています。
Claudeでも、他のLLMサービスでも、クラス単位で丸ごと書いてしまったり、関数も複数まとめて書いてしまったりという傾向が見て取れました。
そこは毎回、「ステップバイステップで実装できるように関数ごとに分割して説明して」と注文をつけて作り直させました。
最終的にできたものがこれです。Github Pagesで公開しています。
こんな感じ↓
おわりに
プログラミング教材と見たときに残念だったのが、非同期処理やマルチプロセスを実装する必要がなかったこと。テキストエディタってこんな感じで実装できるんだというのは分かりました。
今回の取り組みで、こういったプログラミング教材やチュートリアルを生成AIで簡単に作れることが分かったのは一つの発見だと思っています。チュートリアルドキュメントについては、RAGの機能を活用すればうまくできたのかもしれません。そこは今後、検討したいところです。