【vterm】
Emacs内でCodexを動かした

  • EmacsでCodex CLIを使うため、標準のshellやeshellでは対話的なTUIアプリが正しく表示されないという問題がありました。
  • そこでlibvtermベースのvtermを導入することで、ほぼ完全なターミナル互換性を確保しました。
  • インストールにはCMakeやHomebrew版libtoolなどの依存関係が必要で、コンパイルを伴います。

vtermを使うと、Emacs内でコード編集とターミナル操作を同一ウィンドウで完結できます。

関連記事

1. Emacsの標準ターミナルでは不十分

OpenAIのCodex CLIを使うようになって、ターミナルを利用することが増えました。
そこで、Emacsで編集作業中にアプリケーションを切り替える手間が気になり始めました。

Emacs内でvtermを使ってClaude Codeを動かす > エディタとターミナルを統合 アプリ切り替え不要

Emacsでは、バッファを分割し、複数のファイルを同時に表示できます。
それなら、ターミナルもEmacs内で動かせば、すべてが一つのウィンドウで完結するのではないか、と考えました。

Emacsには標準でshelltermansi-termといったターミナル機能があります。
しかし、これらには制限がありました。

2. vtermという選択肢 vterm 本格的なターミナルエミュレータ C言語 libvtermベース 完全 ターミナル互換性 高速 ネイティブコード

shellは単純なコマンド実行には便利ですが、対話的なアプリケーションには対応していません。
termansi-termはターミナルエミュレータとして動作しますが、エスケープシーケンスの実装が不完全です。
複雑なTUI(Text User Interface)を持つアプリケーションが正しく表示されないことがあります。

Codex CLIは、対話的なTUIツールで、より完全なターミナルエミュレータが必要でした。

1.1. vtermという選択肢

vterm」は、Emacs用の本格的なターミナルエミュレータです1

C言語で書かれたlibvtermをベースにしているため、ほぼ完全なターミナル互換性を持ちます。

vtermを使えば、Emacs内でCodexCLIのような高度なTUIアプリケーションを動かせます。
ただし、vtermのインストールにはコンパイルが必要です。
Pure Emacs Lispではなく、ネイティブモジュールを使用しているためです。

2. インストールの準備

vtermをインストールする前に、いくつかの依存関係を満たす必要がありました。

3. インストールの準備 1 CMakeとビルドツール brew install cmake libtool libvterm ⚠ macOS標準libtoolは不可 → Homebrew版が必要 2 Emacsモジュールサポート確認 M-: module-file-suffix RET → “.so” / “.dylib” が返ればOK

vtermはコンパイルが必要なため、CMakeとビルドツールをインストールします。
macOSの場合、Homebrewを使うと簡単です。

brew install cmake libtool libvterm

cmakeはビルドシステム、libtoolはライブラリ作成ツール、libvtermは本体のライブラリです。
特に注意が必要なのはlibtoolです。
macOSには標準でlibtoolがインストールされていますが、これはApple版で互換性がありません。
Homebrew版を使う必要があります。

パスの優先順位を確認します。

which libtool

/opt/homebrew/bin/libtool または
/usr/local/bin/libtoolと表示されれば正しいです。
/usr/bin/libtoolと表示された場合は、パスの設定が必要です。

export PATH="/opt/homebrew/bin:$PATH"
Code language: JavaScript (javascript)

この設定は、~/.zshrcに追加しておくと永続化されます。

2.1. 【補足】Lubuntuの場合(apt, bash)

sudo apt install cmake libvterm-dev libtool-bin

2.2. Emacsのモジュールサポート確認

vtermを動かすには、Emacsがネイティブモジュールに対応している必要があります2
確認方法は簡単です。

Emacsを起動し、以下を実行します。

M-: module-file-suffix RETCode language: JavaScript (javascript)

".so"".dylib"といった文字列が返ってくれば、モジュールサポートが有効です。
nilが返ってきた場合、Emacsをモジュールサポート付きで再ビルドする必要があります。

3. vtermのインストール

依存関係が揃ったら、vtermをインストールします。
Emacsのパッケージマネージャを使います。

4. vtermのインストール 1 MELPA設定 (add-to-list ‘package-archives ‘(“melpa” . “https://melpa.org/packages/”)) 2 インストール実行 M-x package-refresh-contents M-x package-install RET vterm ※ 初回は自動コンパイル(数十秒)

vtermはMELPAというパッケージリポジトリから提供されています。
まず、MELPAを設定に追加します。

~/.emacs.d/init.elに以下を追記します。

(require 'package)
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
(package-initialize)
Code language: PHP (php)

この設定により、Emacsは公式リポジトリに加えて、MELPAからもパッケージを取得できるようになります。

3.1. vtermのインストール実行

設定を保存してEmacsを再起動します。
その後、パッケージをインストールします。

M-x package-refresh-contents RET
M-x package-install RET vterm RET

インストール中、自動的にコンパイルが始まります。
初回は数十秒かかります。
エラーが出なければ成功です。

4. vtermの起動

インストールが完了したら、vtermを起動してみます。

M-x vterm RET

新しいバッファが開き、シェルプロンプトが表示されます。
見た目は通常のターミナルと変わりません。

ここで、Codexを実行してみます。

codex

Codexが起動し、対話的なUIが正しく表示されました。
色付き、カーソル位置、入力ボックス、すべてが期待通りに動作します。
標準のtermでは崩れていたレイアウトが、vtermでは完璧に再現されています。

4.1. コピーモード(vterm-copy-mode)

Emacsのvterm(emacs-libvterm)で出力をスクロール・コピーするには、コピーモード(vterm-copy-mode, C-c C-t)を使用します。
C-c C-tでコピーモードに入ると、通常のEmacsバッファと同様にM-v/C-vでのスクロール、カーソル移動、検索(C-s/C-r)、テキスト選択・コピーが可能になります。

vtermは通常のターミナルモードでは出力時に自動的に最下部へスクロールしますが、コピーモードではこの挙動が停止し、履歴を自由に確認できます。
テキストを選択してEnterを押すとコピーされ、自動的に通常モードに戻ります。
またはqでコピーモードを抜け、通常の入力モードに戻ります。

5. vtermでの注意点

vtermをしばらく使ってみて、いくつかの特徴に気づきました。

5.1. vterm内でemacsを誤起動しないために

Emacsのvtermを日常的に使っていると、つい癖でemacsと打ってしまうことがあります。
するとEmacsの中でEmacsが起動し、画面が増えたり挙動が怪しくなったりします。

INSIDE_EMACSという環境変数を使うと、Emacsの中で起動したシェルかどうかを判定できます。

if [ -n "$INSIDE_EMACS" ]; then
  emacs() {
    echo "vterm内ではemacsを起動しないでください"
    return 1
  }
fiCode language: PHP (php)

vtermのときだけemacsを無効化するようにしました。

5.2. スクロールバックの制限(vterm-max-scrollback

vtermにはスクロールバックの制限があります3
デフォルトでは約13万文字分の履歴が保持されます。
長時間のセッションでは、古いログが消えることがあります。

必要に応じて制限を増やせます。

(setq vterm-max-scrollback 100000)

ただし、値を大きくしすぎると、パフォーマンスが低下します。
適度な値を設定することが重要です。

5.3. カーソルが表示されないときは

vtermのカーソルは、バッファ内で管理されているようです。

(setq-local cursor-type 'box)

init.elに追加しておくと、自動で設定できます。

;; vtermのcursorが表示されなかったので、設定し直した
(with-eval-after-load 'vterm
  (defun my-vterm-cursor-settings ()
    (setq-local cursor-type 'box)) ; または 'hollow, 'bar
  (add-hook 'vterm-mode-hook #'my-vterm-cursor-settings))Code language: PHP (php)

6. Emacsでのウィンドウ管理の利点

vtermを使い始めて、Emacs内でウィンドウ管理をする利点を実感しました。

まず、アプリケーション切り替えが不要になりました。
コードを編集しながら、同じウィンドウ内でターミナルを確認できます。
C-x oでウィンドウ間を移動するだけです。

次に、レイアウトの柔軟性です。
Emacsのウィンドウ分割は自由度が高く、縦分割、横分割、さらに細かい分割も簡単です。
エディタ、ターミナル、ドキュメント、すべてを一つの画面に配置できます。

そして、一貫した操作感です。
すべてのウィンドウがEmacsのキーバインドで操作できます。

  1. vtermの公式リポジトリによれば、vtermはlibvtermをベースにしたフル機能のターミナルエミュレータで、コンパイルされたコードを使用することでEmacsの他のターミナルエミュレータと比較して優れたパフォーマンスと互換性を実現しています – GitHub – akermu/emacs-libvterm
  2. Emacsのモジュールサポートは、Emacs 25.1から導入された機能で、--with-modulesオプションを指定してコンパイルすることで有効になります。多くのLinuxディストリビューションのパッケージマネージャから入手できるEmacsには、デフォルトでこのオプションが有効になっています – Dynamic Modules (GNU Emacs Lisp Reference Manual)
  3. vtermのスクロールバックサイズはvterm-max-scrollback変数で制御されており、デフォルト値を変更することで履歴の保持量を調整できます。ただし、値を大きくしすぎるとメモリ使用量が増加し、パフォーマンスに影響を与える可能性があります – GitHub – akermu/emacs-libvterm