TL;DR
-
claude-code-ide.elを EXWM 上で使うと、Claude の Ink TUI 再描画中に Emacs が固まることがある。原因はeatバックエンドのターミナル処理が純 Elisp で main loop 上で走り、xelb の X イベント受信を飢餓状態にすること。 - これを回避するために、
ghostel(libghostty-vt のネイティブモジュール) をclaude-code-ideの第3のバックエンドとして使えるようにする小さなアドオンclaude-code-ide-ghostelを作った。 - ついでに、ddskk / mozc など IM を活かして複数行プロンプトを編集できる child frame UI
claude-code-ide-composeも同梱した。 - 両方まとめて
frenzieddoll/claude-code-ide-extrasで公開している。
動機
問題1: EXWM + eat バックエンドで Emacs が固まる
私は普段 EXWM をウィンドウマネージャとして使っている。この状態で claude-code-ide の eat バックエンド経由で Claude セッションを動かすと、Claude 側 (Ink TUI) の再描画が激しいタイミングで Emacs 全体が一時的に固まる現象に遭遇した。
メカニズムを追ってみると:
-
eatはターミナルエミュレーション一式を 純 Elisp で実装している。出力処理はすべて Emacs の main loop 上。 - EXWM は X11 プロトコルを xelb (これも純 Elisp) で喋っている。X イベントの受信処理も main loop。
- Claude CLI の Ink TUI は文字単位で頻繁に再描画してくる。
- すると main loop が
eatのパース処理で詰まり、xelb の X イベント受信が後回しになる。 - 結果として、ウィンドウ操作が秒単位で固まる。
問題2: ターミナルバッファ上で日本語 IM (ddskk 等) を活かして複数行プロンプトを書きたい
Claude CLI のプロンプトは複数行入れたい場面が多い (コード片を貼る、説明を構造化する、など)。しかし:
-
claude-code-ide-send-promptは ミニバッファ入力。RETで送信されてしまうので複数行プロンプトに不向き。 - かといってターミナルバッファ (
vterm/eat/ghostel) に直接打つと、ターミナルモードが多くのキーを横取りして ddskk などのキーバインドが壊れる。
どちらも嫌なので、独立した text-mode のバッファでプロンプトを編集して、まとめて送る UI が欲しかった。
解決1: claude-code-ide-ghostel — ghostel バックエンド
ghostel は libghostty (Ghostty ターミナルエミュレータの C 実装) の VT パーサ部分を Emacs のネイティブモジュールとして使うパッケージ。ターミナル状態管理が C 側に降りているので、Emacs の main loop は数マイクロ秒で xelb に戻れる。
claude-code-ide は内部で claude-code-ide-terminal-backend ('vterm / 'eat) で分岐しているので、そこに 'ghostel を足す形で :around advice を当てている。
インストール
;; leaf.el を使う場合
(leaf claude-code-ide
:when (executable-find "claude")
:vc (:url "https://github.com/manzaltu/claude-code-ide.el" :rev :newest)
:bind ("C-c C-'" . claude-code-ide-menu)
:config
(claude-code-ide-emacs-tools-setup)
(setq claude-code-ide-cli-extra-flags "--remote-control"))
(leaf claude-code-ide-extras
:vc (:url "https://github.com/frenzieddoll/claude-code-ide-extras.git" :rev :newest)
:require t
:bind ("C-c j" . claude-code-ide-compose)
:custom (claude-code-ide-terminal-backend . 'ghostel)
:config
(claude-code-ide-ghostel-mode 1))
ghostel 本体は現時点で MELPA に無いので、一度だけ手で入れる:
M-x package-vc-install RET https://github.com/dakra/ghostel RET
M-x ghostel-download-module RET
ghostel-download-module がプラットフォーム向けの prebuilt なネイティブモジュールを取ってきてくれる。zig などのツールチェインは要らない。
効果
私の EXWM 環境では、eat バックエンド時に出ていた「Claude が高速に出力している間 WM 操作が固まる」状態が再現しなくなった。
(注: EXWM 上で eat がフリーズする問題は私の実体験ベースの話で、claude-code-ide.el のリポジトリに公式なバグ報告として上がっているわけではない。ただメカニズム的には xelb と eat がどちらも main loop を奪い合う以上、似た構成なら再現してもおかしくないと思う。)
解決2: claude-code-ide-compose — 複数行プロンプト用 child frame
text-mode の child frame を Claude バッファの下端に重ねて、複数行プロンプトを編集・送信するための UI。
| キー | 動作 |
|---|---|
M-x claude-code-ide-compose |
compose overlay を開く |
C-c C-c |
内容を Claude に送信して閉じる |
C-c C-k |
破棄 (overlay を閉じる。下書きは残る) |
C-u M-x claude-code-ide-compose |
下書きを消して開き直す |
中身は text-mode バッファそのものなので:
- ddskk / mozc など IM がそのまま動く
-
RETで送信されない (普通の改行になる) - 編集中は Emacs の編集機能 (移動・kill ring・undo …) が全部使える
送信時には、バッファ内の改行を \\ + RET (Claude の in-prompt newline 規約) に変換して送るので、Claude 側では「複数回の送信」ではなく「1回の複数行プロンプト」として届く。これは upstream の claude-code-ide-insert-newline の慣習に合わせている。
おまけ: C-g を Emacs 側に届ける
Claude CLI は標準で C-g を自分で消費する。なので、ターミナルバッファ上で C-g を押しても Emacs の keyboard-quit が走らない。Emacs ユーザーは C-g を連打する習慣があるので地味に困る (evil-mode ユーザーだと、思わぬ state 遷移を起こすこともある)。
~/.claude/settings.json で Chat コンテキストの ctrl+g を無効化すると、C-g がそのまま端末に流れ、Emacs の keyboard-quit まで届く:
{
"binding": [
{
"context": "Chat",
"bindings": {
"ctrl+g": null
}
}
]
}
Claude 本体側の設定なので、バックエンド (ghostel / vterm / eat) は問わない。
まとめ
- EXWM で
claude-code-ide.elを使うならghostelバックエンドを試す価値がある - ターミナル上で日本語複数行プロンプトを書くなら child frame compose UI が便利
-
C-g問題は~/.claude/settings.jsonで解決
リポジトリは frenzieddoll/claude-code-ide-extras です。
動作確認環境
- Emacs: 30.2
- claude-code-ide.el: 09875f8
- ghostel: 20260522.1543(elpa)
- WM: EXWM
- OS: Arch Linux