comintはプロンプトと入出力を管理するが端末をエミュレートしない

Emacsのcomintは、非同期プロセスとの対話を通常のバッファ操作へ組み込むための基盤である。派生モードはプロンプトの正規表現、入力履歴、入力の送信方法、プロセスから届いた出力の処理方法を定められる。shell-mode、各種REPL、agent-shellが利用するshell-makerは、この行指向の対話モデルを土台にしている。

comintのバッファにプロセス出力が表示されることは、「端末がエミュレートされている」ことを意味しない。PTYを割り当てるだけでも足りない。全画面TUIを正しく表示するには、カーソル移動、行の消去、代替画面、色、文字属性、端末サイズ、キーボードとマウスの符号化といったVTの状態遷移を解釈し、画面モデルを保持する必要がある。comintはこの画面モデルを持たず、プロンプトを区切りとして入力と出力が積み重なるバッファを管理する。

このため、改行単位のREPLや、JSON-RPCのような別のプロトコルをEmacs Lispが解釈して表示するアプリにはcomintが合う。プロセスの出力を検索、編集、再送しやすく、Emacsのテキスト処理をそのまま利用できるからである。一方、既存のCLIがVT制御シーケンスを使って画面の同じ位置を何度も更新する場合は、Ghostel、vterm、eatのような端末エミュレーターが必要になる。

端末エミュレーターは、子プロセスのバイト列を端末状態へ変換し、その時点の画面をEmacsバッファへ反映する。この方式は全画面アプリとの互換性を得る代わりに、出力を単純な追記テキストとして扱いにくくする。コピー用またはEmacs操作用のモードが別に必要になるのは、端末へキーを渡す操作とEmacsでバッファを操作する入力が競合するためである。

したがって、Emacsのプロセス対話機能を選ぶときは、シェルらしい見た目ではなく、相手が要求するプロトコルを見る。行や構造化メッセージを解釈できるならcomint系、VT端末として振る舞う必要があるなら端末エミュレーターを選ぶ。この区別を当てはめると、agent-shellとGhostelの違いも、表示の好みではなく統合するプロトコル層の違いとして理解できる。

https://github.com/emacs-mirror/emacs/blob/master/lisp/comint.el

https://github.com/xenodium/shell-maker

https://github.com/dakra/ghostel

#emacs #comint #terminal #process