Emacs + Claude Code の環境について
Claude CodeなどのAI Codingツールは基本的にTUIアプリケーションのため、Emacsとの相性は悪いです。 今回はそんなEmacsでClaude Codeをどうやって上手く使っているかの話をしたいと思います。
なぜEmacsとClaude Codeの相性が悪いのか。そんなことはこの記事を見ているEmacs使いは知っていると思います。 知らないのに見ている奇特な方に簡単に説明すると、TUIの描画が重い上にEmacsのフリーズに巻き込まれてClaude Codeが一緒に死ぬからです。
ではどうやって解決するかです。
まずEmacs上でTUIを起動するとすごい重くなる話からです。
近年のEmacsはGUIアプリとして作られています。
そのため、GUIアプリとして起動すると、描画も全てEmacsのプロセスで行います。
しかし、Emacsは古のシングルスレッドアプリです。
そこにTUIのような大量の描画を行うものを使うと、CPUが独占されて簡単にハングという訳です。
この解決策の一つがTUI版Emacsを使うというものです。EmacsはGUIアプリとしての使用が主流のため、TUIのサポートは貧弱なイメージが強い方も多いと思います。
しかし、近年はサポートも充実してきています。そのため、今はむしろTUIの方が描画を丸投げできる分、メリットがでかいです。
例えば、以下のようにマウスの操作を可能にすることもできます。
(unless (display-graphic-p)
(xterm-mouse-mode 1))
近年流行りのcorfuとかは、TUIサポートをしていないため、これで足踏みする人もいるかもしれませんが、emacs-corfu-terminalなどのTUIサポートを可能にするパッケージもあるので、GUI版と使用感変わらず使えます。
しかし、これだけでもまだ、Claude Codeの描画は重いです。EmacsとClaude CodeのTerminalを分けろと思うかもしれませんが、Emacs使いは諦めません。そこで、TUIを使うための適切なTerminal Emulatorの話になります。
近年のおすすめはghostelです。
これは、Ghosttyの内部ライブラリを流用することで、描画を最適化したEmacs専用のTerminal Emulatorです。
少し前に流行したeatの影響を受けており、使用感はeatに近いです。
このパッケージを使うと描画がかなり最適化される上に、通知機能まで追加されるのでかなりおすすめです。
ただ、そのままだとちょっとかゆい所があるので、筆者は以下のような設定を入れています。参考までに以下に貼ります。
(use-package ghostel :straight t
:init
(defun my/ghostel-send-string (string)
(interactive "sInput: ")
(ghostel-send-string string))
(defun my/ghostel-send-escape()
(interactive)
(ghostel--on-user-input)
(ghostel--send-encoded "escape" ""))
(defun my/ghostel-notify-focus (status buffer)
"my/ghostel-notify-focus notifies status to ghostel when buffer is ghostel mode."
(when (and buffer (buffer-live-p buffer))
(with-current-buffer buffer
(when (derived-mode-p 'ghostel-mode)
(ghostel-send-string status)))))
(defun my/ghostel-os-focus-change-handler ()
(let ((current-buf (car (buffer-list))))
(if (frame-focus-state)
(my/ghostel-notify-focus "\e[I" current-buf)
(my/ghostel-notify-focus "\e[O" current-buf))))
(defun my/ghostel-focus-change ()
(let* ((search-string "ghostel*")
(case-fold-search nil)
(ghostel-buffer-list (seq-filter (lambda (buf)
(string-search search-string (buffer-name buf)))
(buffer-list))))
(dolist (buf ghostel-buffer-list)
(if (get-buffer-window buf 'visible)
;; focus in
(my/ghostel-notify-focus "\e[I" buf)
;; focus out
(my/ghostel-notify-focus "\e[O" buf)))))
(defvar my/window-update-ghostel-timer nil
"timer to change ghostel status")
(defun my/ghostel-focus-change-hook (&rest _)
(when my/window-update-ghostel-timer
(cancel-timer my/window-update-ghostel-timer))
(setq my/window-update-ghostel-timer
(run-with-idle-timer 0.5 nil #'my/ghostel-focus-change)))
;; 画面やバッファから離れたときに、ghostelに「離れた」というシグナルをターミナルに送るためのフック
(add-hook 'window-selection-change-functions #'my/ghostel-focus-change-hook)
(add-function :after after-focus-change-function #'my/ghostel-os-focus-change-handler)
:hook
(buffer-list-update . my/ghostel-focus-change-hook)
:bind
("<f12>" . ghostel-project)
(:map ghostel-mode-map
;; ターミナルに日本語を送信するためのショートカット
("C-c s" . my/ghostel-send-string))
(:map ghostel-semi-char-mode-map
;; C-q でescape をターミナルに送信する
("C-q" . my/ghostel-send-escape)))
ここまでしたら大分軽いと思いますが、まだ重くなることがあります。それは行数が増えていったときです。
Claude CodeなどのTUIアプリは画面のサイズが変わると再描画が走ります。これは過去ログも含めて画面に表示されている全てのテキストが描画対象となります。
普通のTUIの場合、これは頻繁に走りませんが、Emacsはバッファのリサイズが頻発するため、割とクリティカルな問題です。
これの解決方法がFullscreen modeです。
これを設定すると見えている部分しか描画されなくなります。
Antigravity CLI(旧 Gemini CLI)でも同様の設定があり/configからRendering Modeで選択できます。
この設定をすると画面サイズの変更があろうと、見えている部分の再描画で終わるため、どれだけ会話が長くなろうと一定の処理量となります。EmacsでClaude Codeを触る上では必須の設定だと思います。
最後にEmacsが落ちるとClaude Codeが一緒に死ぬ問題です。これは、Emacsというプロセスで動いている以上、しょうがない思いがしますが、解決策があります。
それがtmuxです。tmuxは画面分割やセッション管理ができることで有名ですが、今回着目するのは、tmuxのプロセスが起動元のプロセスと切り離される点です。
つまり、Emacsに立つプロセスはtmuxのプロセスを「見る」プロセスのみが立ち、tmuxのプロセスはOS管理の別プロセスとなります(例えるとテレビの番組と視聴者の関係です)。
これにより、Emacsが落ちてもClaude Codeのプロセスが残るため、安全にEmacsで重い処理を実行できます。
大体このぐらいな気がします。これらの設定をしたら快適にVibe Codingできるようになりました。
こんなに頑張らないといけないのか…、と思った方もいるかもしれませんが、Emacsから離れないで完結するというメリットはでかいので、筆者は満足しています。