はじめに
kotte は、520 行の Python で書かれた vi 風の日本語対応フルスクリーンエディタです。短いコードでも、それなりのエディタは作れる - それを形にしたものです。
Python で curses を使ってみたい方や、マルチバイト文字を扱うエディタ実装に興味がある方に向けて紹介します。
GitHub のリポジトリはこちら:
https://github.com/iigura/kotte
kotte が実装している機能
約 520 行という規模でも、次のような基本操作は一通り実装しています:
- vi/vim のようなコロンコマンド(:w [fileName] :wq :q :q! :e [fileName])
- d,z,Z による 2 ストロークコマンド(dd で行削除など)
- /,n,N による検索文字列の設定、後方検索、前方検索
- h,l,j,k とカーソルキーによる移動
- w と b によるワード単位の移動
- 0 と $ による行頭行末への移動
- g と G による先頭末尾への移動
- H と L によるスクリーントップとボトムへの移動
- i と a による挿入と追加
- x による文字の削除
- o と O による行の前後への追加
- r による文字置換
- J による行の連結
特徴
- 単一の Python ファイル(約 520 行)
- 日本語(マルチバイト)、全角半角混在表示対応
- curses を使ったフルスクリーンエディタ
- 文字色や背景色にも対応
ant.c との関係(君は IOCCC を知っているか?)
kotte の実装は IOCCC 1991 で Best Utility を受賞した ant.c からヒントを得ています。 https://github.com/ioccc-src/winner/blob/master/1991/ant/ant.c
ant.c は 28 行で書かれた vi っぽいフルスクリーンスクリーンエディタです。是非とも実際のコードをみて下さい。本当にこれでフルスクリーンエディタとして機能するのか?という疑問と、それが肯定的に(良い意味で)裏切られる体験ができると思います。
ちなみに IOCCC というのは、国際邪悪な C コードコンテストとでも訳されるのでしょうか、The International Obfuscated C Code Contest のことです。 https://www.ioccc.org
ant.c については、以前 Qiita に記事を書きましたので、そちらも見て頂ければ幸いです(Python 版は Zenn の方に投稿しました)。「120 行で vi っぽいエディタを作る」 https://qiita.com/iigura/items/678aca225956272bdc10
(ant.c == Anthon's Editor ということで ae と呼ばれていたように記憶しています。しかし、今調べてみたのですが、その痕跡を見つけることはできませんでした)
ant.c は超絶コンパクトなコードでフルスクリーンエディタを実装していますが、その源泉は現代的に言うならば doc-view 構造にあると感じています。kotte は、その思想を Python で再解釈し、日本語対応を加えています。
tiny code の価値
何を以って「小さな」コード、「小規模」であるのかは人それぞれかと思います。vi のようなフルスクリーンエディタが約 520 行で書けることを示したものです。これが tiny であるか否かはさておき、520 行という規模は容易に全体を見渡せるサイズです。読み進めるうちに「エディタはこう動いているのか」という全体像が自然に見えてくるかと思います。tiny code の魅力は、高密度なコードが動くだけではなく、「全部が見える」部分にある、とも思っています。
xtte への展開
小さなプログラムにまとめるという行為は、本質的な機能とは何ぞや、という問いに答えることだと思います。kotte は、extensible tiny text editor (xtte) というフルスクリーンテキストエディタモジュールへの展開を見据えています。そのために、余計なものを削ぎ落とし、必要最小限の構造を探るため、あえて約 520 行にまとめました。「削ること」が設計を見せるための道具になっています。
作った感想
当初はもっと小規模にまとめられると思っていました。たかをくくっていたといっても良いかもしれません。実際に作ってみると、テキストデータ中の n 文字目は、画面上の何桁目に表示されるのか?ということですら、自明には分からないことに気が付きました。
フルスクリーンエディタを製作してみると、「テキストと画面の位置の対応」という単純そうな問題が意外と奥深いことに気づかされました。短いコードで作ると、エディタの中身は驚くほど「人間臭い」ものに見えてきます。
2025 年の現時点では AI が主体的に関わるコーディングが話題になっています。kotte の開発でも利用してみましたが、上手くいきませんでした。これは、スクリーン上の位置、という概念の理解が難しいようで、英語対応のエディタを日本語対応させることはできませんでした(フルスクリーンエディタとしての挙動が変なままでした)。まだしばらくは人間の介入が必要なんだな、としみじみと感じました。
付録
ここでは kotte の実装や開発時に役立った小さな工夫をいくつか紹介します。実際にコードを触る方や、日本語対応のエディタを作ってみたい方に向けたメモです。
curses を使うプログラムのデバッグ方法
あと、curses を使っていると、pudb などのデバッガの活用が困難となります。remote_pdb が(一応)役に立ちました。ブレークしたい部分に、
from remote_pdb import RemotePdb
RemotePdb('127.0.0.1', 4444).set_trace()
と書き、プログラムが中断したら telnet 等で
telnet 127.0.0.1 4444
すれば pdb でのデバッグが可能となります。
ただ、「(一応)」と書いたのは、製作の前半は pdb が大活躍していましたが、開発中頃からはあまり pdb は使いませんでした。というのも、フルスクリーンテキストエディタはインタラクティブなプログラムですので、コマンドベースのデバッガよりも内部状態を観察することが重要になってきたからです。
そのため、途中からはステータスラインに必要な情報を表示させるようにし、それをデバッグに役立てる、ということを行っていました。例えばシステム側で把握しているカーソルの位置にある文字を表示させたりしていました。
設計:バッファと表示の扱い
kotte の設計では、全てのテキストデータを一つのリストにまとめる方針を取りました。これは ant.c の設計方針そのままです(厳密に言えば ant.c では穴開きバッファを用いており、配列を仮想化したうえで利用しています)。"abc" というテキストデータから成るテキストファイルがあった場合、kotte では ['a','b','c'] というリストでテキストデータを管理します。
このテキストデータはグローバル変数の Buf というリストに格納されています。この Buf のデータを適切に表示しているだけです。
Buf に全てのテキストデータが格納されているのですが、日本語表示については、少々面倒くさい処理が必要でした。1 文字 1 キャラクタ幅の ASCII 文字などでは発生しない現象なのですが、例えば「あと 1 文字余白がある」という場合において、次に表示するべき文字が日本語であった場合、その日本語の文字は次の行の先頭に表示されるべきです(だって、表示するだけの余白が無いのだから)。
kotte の開発においては、この行末の文字送りの処理が結構面倒でした。もっと良い方法があるのかもしれませんが、そのような方法を知っている人・見つけた人は是非ともコメント欄でコメントして頂ければ幸いです。
この問題の難しさは、バッファ Buf 内に格納されている任意の位置の文字が表示されるべき桁数(row,col における col の値、もしくは x,y における x の値)が単純には決定しない、というところにあります。
kotte では、行頭になる部分を探し出して ー つまり、直前の文字が改行文字 '\n' であるとか、バッファの先頭の文字であるとか、を利用してカラム 0 に表示される文字を特定し、それを基準に当該のインデックス値の文字がどこに表示されるのかを都度計算しています(ちなみにこの処理を担当しているのが getCol 関数です)。