未来のターミナル
(jyn.dev)- 既存のターミナル構造の複雑さと限界を指摘し、入力・出力・プロセス制御を新たに統合した次世代ターミナルの概念を提示
- Jupyter Notebookをモデルとして、画像レンダリング・コマンド再実行・編集可能な出力・内蔵エディタなど、対話型インターフェースの実現可能性を探る
- WarpとiTerm2の事例を通じて、シェルとターミナル間の深い統合(shell integration)、長時間実行プロセスの管理、セッションの分離・復旧機能を具体的に説明
- データフロー追跡(dataflow tracking)と永続性(persistence)を基盤として、コマンドのundo/redo、自動再実行、協調型ターミナルなどの拡張機能を構想
- 段階的なアプローチにより、トランザクション型CLI → 永続セッション → 構造化RPC → Jupyter型フロントエンドへ発展させる漸進的な構築戦略を提示
ターミナルの基本構造
- ターミナルは4つの構成要素から成る: ターミナルエミュレータ、仮想端末(PTY)、シェル、プロセスグループ
- ターミナルエミュレータは、画面上にグリッド構造をレンダリングするプログラム
- PTYはカーネル内部の状態で、入力をプロセスグループに渡し、信号(signal)の変換を担う
- シェルは入力を読み取り、解析し、プロセスを生成するイベントループの役割を果たす
- プロセス群は入力・出力を通じて上記の構成要素と相互作用する
- 入力は単なるテキストではなく信号(signal)を含み、出力はANSIエスケープシーケンスで構成され、書式を表現する
より良いターミナルの構想
- 既存のターミナルには機能的な制約が多く、拡張性と対話性が不足している
- Jupyter Notebookは、従来のVT100エミュレータでは不可能な機能を提供する
- 高解像度画像のレンダリング
- 過去の出力を追加せず置き換える「最初から再実行」ボタン
- ソースコードと出力をその場で書き換えられる「ビュー」(例: Markdownをソースまたはレンダリング済みHTMLとして表示)
- 構文ハイライト、タブ、パネル、マウスサポートなどを備えた内蔵エディタ
- しかし、シェルをカーネルとして使うJupyterノートブックの概念はいくつもの問題に直面する
- シェルはコマンドを一度に受け取るため、タブ補完、構文ハイライト、自動提案が機能しない
- 長時間実行プロセスの扱いに問題がある: Jupyterは基本的にセルが完了するまで実行し、キャンセルはできるが、一時停止、再開、対話、実行中プロセスの表示は不可能
- 「セル再実行」ボタンがコンピュータの状態に問題を引き起こす(特に
rm -rfのようなコマンドを含む場合) - undo/redoが機能しない
これはどう動くのか?
-
シェル統合(Shell Integration)
- Warpターミナルは、ターミナルとシェルの間にネイティブ統合を構築している
- 各コマンドの開始と終了、出力、ユーザー入力をターミナルが理解する
- 標準機能(ユーザー定義DCS)を使って実装されている
- iTerm2も同様の方法でOSC 133エスケープコードを利用できる
- 単一のショートカットキーでコマンド間を移動
- コマンド完了時の通知
- 出力が画面外に流れたとき、現在のコマンドを「オーバーレイ」として表示
- Warpターミナルは、ターミナルとシェルの間にネイティブ統合を構築している
-
長時間実行プロセスの管理
- 対話(interacting):
- 長時間実行プロセスとやり取りするには双方向通信が必要
- TUIの例:
top,gdb,vim - Jupyterは、変更・更新可能なインタラクティブ出力の設計に強みがある
- TUIの例:
- 想定されるターミナル機能: 常に「自由入力セル」を提供
- 対話的なプロセスがウィンドウ上部で実行され、下部に入力セルを提供
- 長時間実行プロセスとやり取りするには双方向通信が必要
- 一時停止(suspending):
- プロセスの「一時停止」は「ジョブ制御(job control)」と呼ばれる
- 現代的なターミナルでは、一時停止中およびバックグラウンドプロセスを継続的な視覚表示で示すことが期待される
- IntelliJが下部のタスクバーに「インデックス作成中...」を表示するのと似ている
- 切断(disconnecting): セッションの分離・復旧には3つのアプローチがある
- Tmux / Zellij / Screen: ターミナルエミュレータとプログラムの間に追加のターミナルエミュレータを挿入する。サーバーがPTYを所有して出力をレンダリングし、クライアントが実際のターミナルエミュレータに出力を表示する。クライアントの分離、再接続、複数クライアントの同時接続が可能。iTermはtmuxクライアントを迂回し、サーバーと直接通信する独自クライアントとして動作する
- Mosh: SSHの代替。ネットワーク中断後にターミナルセッションへ再接続できる。サーバー側で状態マシンを実行し、ビューポートの増分差分をクライアントへ再生する。マルチプレクシングやスクロールバックはターミナルエミュレータが担う想定。クライアントがネットワーク側で実際に実行されるため、ローカルな行編集が即時になる
- alden/shpool/dtach/abduco/diss: クライアント/サーバーとしてセッションの分離/再開のみを扱い、ネットワークやスクロールバックは含まず、独自のターミナルエミュレータも持たない。tmuxやmoshと比べてより高い分離レベルを提供する
- 対話(interacting):
-
再実行と巻き戻し
- 解決策はデータフロー追跡
- pluto.jlがJuliaコンパイラに接続して、今日すでにこれを実現している
- 以前のセルに依存するセルをリアルタイムで更新
- 依存関係が変わっていない場合、セルを更新しない
- スプレッドシートに近いJupyterとして、必要なときだけコードを再実行する
- **直交永続性(orthogonal persistence)**による一般化
- プロセスをサンドボックス化し、すべてのI/Oを追跡し、サンドボックス内の他プロセスと通信しない限り「あまりに奇妙な」ことを防ぐ
- プロセスを入力の純粋関数として扱えるようにし、その入力とは「ファイルシステム全体、すべての環境変数、すべてのプロセス属性」である
派生機能
- Jupyterフロントエンドが必要:
- Runbooks(実際にはJupyterとPTYプリミティブだけで構築可能)
- 奇妙なカスタム言語やANSIカラーコードなしで、通常のCSSを使うターミナルカスタマイズ
- 出力/タイムスタンプによるコマンド検索: 現在のセッションの全出力を検索したり、すべてのコマンド入力履歴を検索したりできるが、スマートフィルタがなく、出力はセッションをまたいで永続化されない
- シェル統合が必要:
- 各コマンドのタイムスタンプと実行時間
- ネットワーク境界を越えてもローカルな行編集
- タブを押さなくてもシェルコマンドに対するIntelliSenseを提供し、ターミナルに統合されたレンダリング
- サンドボックス追跡が必要:
- サンドボックス追跡のすべての機能: 協調型ターミナル、コマンドで変更されたファイルのクエリ、実行時に編集可能なasciinema、ビルドシステム追跡
- コマンド実行時点のディスク状態まで検索対象にするスマート検索への拡張
- undo/redoをgitに似た分岐モデルへ拡張(emacs undo-treeはすでに対応)、プロセスツリーの複数の「ビュー」を提供
- undo-treeモデルとサンドボックス化により、LLMにプロジェクトへのアクセス権を与え、複数を同時並列に実行できるようにし、互いの状態を上書きせず、作業内容を確認・編集し、後で使うrunbookとして保存できる
- 本番環境でマシン状態に影響を与えず、既存状態だけを検査するターミナル
段階的な構築戦略
-
第1段階: トランザクション意味論(transactional semantics)
- ターミナル再設計でターミナルエミュレータから始めるのは誤ったアプローチ
- ユーザーはエミュレータに愛着を持ち、設定、外観、キーバインドを作り込んでいる
- エミュレータの乗り換えコストは高い
- CLIレイヤーから始めるのが正しい方法
- CLIプログラムはインストールと実行が容易で、乗り換えコストが非常に低い
- ワークフロー全体を変えずに単発で利用できる
- ターミナル向けのトランザクション意味論を実装するCLIを書く
transaction [start|rollback|commit]のようなインターフェースstart以降に実行されたすべてを取り消せる- これだけでも事業全体を構築するのに十分である
- ターミナル再設計でターミナルエミュレータから始めるのは誤ったアプローチ
-
第2段階: 永続セッション(Persistent Sessions)
- トランザクション意味論を確立した後、tmuxとmoshから永続性を分離する
- PTYの永続性を得るにはクライアント/サーバーモデルの導入が必要
- カーネルはPTYの両端が常に接続されていることを前提としている
- aldenのようなコマンドや類似ライブラリを使えば、ターミナルエミュレータやPTYセッション内で実行中のプログラムに影響を与えず、単純に実装できる
- スクロールバックを確保するため、サーバーが入出力を無期限に保存し、クライアント再接続時に再生する
- ターミナルエミュレータが他の出力と同様に扱えるネイティブスクロールバックを提供
- 任意の開始点から再生・再開できる
- ANSIエスケープコードの解析が必要だが、十分な作業量で実現可能
- moshのようなネットワーク再開のためにEternal TCPを使う(QUIC上に構築して効率化することも可能)
- PTYの永続性とネットワーク接続の永続性を分離
- Eternal TCPは純粋な最適化であり、
ssh host eternal-pty attachをループ実行するbashスクリプトの上にも構築できる
- この時点でtmuxのように単一のターミナルセッションに複数クライアントを接続可能で、ウィンドウ管理は依然としてターミナルエミュレータが担う
- 統合ウィンドウ管理が必要なら、ターミナルエミュレータはiTermのようにtmux -CCプロトコルを使える
- この段階のすべての要素はトランザクション意味論とは独立して並行実行できるが、事業構築にはそれだけでは十分でない
-
第3段階: 構造化RPC
- クライアント/サーバーモデルに依存する
- ターミナルエミュレータとクライアントの間にサーバーが介在すると、メタデータによるI/Oタグ付けのような機能を実装できる
- すべてのデータにタイムスタンプを追加できる
- 入力と出力を区別できる
- xterm.jsはこれに近い形で動作する
- シェル統合と組み合わせることで、データレイヤーでシェルプロンプトとプログラム出力を区別できる
- ターミナルセッションの構造化ログを得られる
- asciinemaのようにログを録画として再生
- すべてのコマンドを再実行せずにシェルプロンプトを変換
- Jupyter NotebookやAtuin Desktopへインポート
- コマンドを保存し、後でスクリプトとして再実行
- ターミナルがデータになる
-
第4段階: Jupyter風フロントエンド
- ターミナルエミュレータに初めて手を入れる段階であり、意図的に最後の段階とされている
- 乗り換えコストが最も高いため
- 構築したすべての機能を活かして優れたUIを提供する
transactionCLIは、ネストしたトランザクションが不要なら、もはや必要ない- ターミナルセッション全体がデフォルトでトランザクションとして開始される
- すべてのピースを統合したことで、前述したすべての派生機能を提供できる
- ターミナルエミュレータに初めて手を入れる段階であり、意図的に最後の段階とされている
結論
- この構想は大胆で野心的であり、全体の構築には約10年かかる可能性があると見込まれている
- 忍耐強く、段階的に進めるべきである
- この文章が誰かに刺激を与え、実際に構築を始めるきっかけになることを願っている
まだコメントはありません。