vterm -> eatした理由
vtermでC-hとかは動いたけどC-kがうまく動かなかった。
色々ガチャガチャやってたけどだるくなったときにeatというものを知ったので試してみたところ、
ターミナルとして要求していたことをほぼ達成できたため、移行することにした。
EmacsのターミナルエミュレーターをvtermからEatに移行しました | Ki_chi@Blog
今回のEat移行の設定はこの記事を参考にした。本当に感謝。
また、これまで利用していたvtermについても感謝を伝えたい。恐らく要求されることはできたとは思うが、私の熱量が足りないためにそれを実現できなかったと見ている。
発端: vtermのC-kが動かない
vtermで以下のような設定を書いていた。
(defun my/vterm-send-C-k ()
"Send C-k to vterm terminal."
(interactive)
(let ((inhibit-read-only t))
(vterm-send-key "k" nil nil t)))
(use-package vterm
:bind (:map vterm-mode-map
("C-h" . vterm--self-insert)
("C-k" . my/vterm-send-C-k))
:config
(setq vterm-term-environment-variable "xterm-256color"))
C-hは狙い通り動くのにC-kだけBuffer is read-onlyエラーで弾かれる。
最初は「vterm-keymap-exceptionsのデフォルトにC-kが含まれているせいだろう」と当たりをつけたが、実際にvtermのソースを確認したところこれは誤りだった。デフォルトの除外リストは以下で、C-kは含まれていない。
'("C-c" "C-x" "C-u" "C-g" "C-h" "C-l" "M-x" "M-o" "C-y" "M-y")
さらにvterm-send-key自体の実装を見ると、関数内部でinhibit-read-onlyを自前でletしている。つまりmy/vterm-send-C-kが本当に呼ばれているなら、read-onlyエラーはそもそも起きようがない。
ということは、C-kは自分が定義した関数にディスパッチされておらず、別の何か(kill-lineのような通常のEmacs編集コマンドなど)が割り込んでいることになる。C-h k C-kで実際の解決結果を確認すれば犯人は分かるはずだが、ここまで調べた時点で「そもそもvtermをやめてEatに乗り換える」という選択肢が視野に入ってきたので、根本原因の特定は一旦保留にした。
なぜEatなのか
そもそもvtermのキーマップあれこれで詰まってしまったのがすべてではあるが、後付の理由もあって、
Eatは実装がすべてEmacs Lispで完結している点は素晴らしいと思う。コンパイル済みモジュールに依存しないので初期でcmakeを要求されたりしない。所詮cmakeかつlibvtermのため、ほとんどの環境で動作するとは思うが、個人的にはElispオンリーというのは少し惹かれた。
逆に怖いところ
GitHub - akermu/emacs-libvterm: Emacs libvterm integration · GitHub
akib/emacs-eat: Emulate A Terminal, in a region, in a buffer and in Eshell - Codeberg.org
vtermは先週もコミットが入っている一方、eatは去年当たりから止まっている。
いちおうIssueを見てみると
#237 - Is this project abandoned? - akib/emacs-eat - Codeberg.org
作者は生存が確認できており、どうにも忙しいみたいだ。
人に寄ってはこれらの情報もeatを利用する際には小さくない要素となるだろう。
インストールで詰まった話
use-packageで以下のように書いて導入を試みた。
(use-package eat
:ensure t
:bind ("C-c v" . eat-project-other-window)
:config
(setq vterm-term-environment-variable "xterm-256color"))
初回セットアップとして案内されているM-x eat-compile-terminfoを実行したところ、次のエラーで止まった。
Eat not installed properly: Terminfo source file not found
Eatのソースを確認すると、eat-compile-terminfoは「実際にロードされたeat.elが置かれているディレクトリ」からeat.tiというファイルを探しにいく実装になっている。つまりこのエラーは、パッケージとして.elファイル以外(.tiやterminfo関連ディレクトリ)が正しく取得されていないことを意味する。
自分のdotfilesリポジトリを確認したところ、manager.elでstraight-use-package-by-default tを設定していた。つまりuse-package :ensure tは実質straight.el経由のインストールになっていて、straight.elのデフォルトレシピでは*.tiのような非標準拡張子のファイルまでは拾いきれていなかった、というのが原因だった。
対処は、eatのレシピだけ明示的に:files指定で上書きすることだった。
(use-package eat
:straight (eat :type git :host codeberg :repo "akib/emacs-eat"
:files ("*.el" ("term" "term/*.el") "*.texi" "*.ti"
("terminfo/e" "terminfo/e/*")
("terminfo/65" "terminfo/65/*")
("integration" "integration/*")
(:exclude ".dir-locals.el" "*-tests.el")))
:bind ("C-c v" . eat-project-other-window))
古いビルドがstraight配下に残っていたので、一度straight/build/eatとstraight/repos/eatを消してから再インストールし、無事eat-compile-terminfoが通った。
移行後: C-kは直ったがC-hが効かない
Eatに切り替えた結果、C-lはターミナル側のクリア処理としてそのまま動作し、懸案だったC-kも特に設定を足さずに動くようになった。vterm側の根本原因は結局特定しないままだったが、実用上は解決した形になる。
一方でC-hが反応しない。これはEatの仕様で、デフォルトの入力モードである"semi-char"モードには、Emacs側に処理を譲るキーの除外リストがあり、C-hはそこに含まれている。vtermのvterm-keymap-exceptionsと発想は同じだが、Eat側の変数名はeat-semi-char-non-bound-keysで、扱い方にクセがある。
その場しのぎならC-q(次の1キーをそのままターミナルに送る)を使えばいい。
C-q C-h
恒久的に直すなら、除外リストからキーを取り除いた上で、明示的にキーマップの再構築とEatの再読み込みを行う必要がある。単にsetqしただけでは反映されない。
(use-package eat
:straight (eat :type git :host codeberg :repo "akib/emacs-eat"
:files ("*.el" ("term" "term/*.el") "*.texi" "*.ti"
("terminfo/e" "terminfo/e/*")
("terminfo/65" "terminfo/65/*")
("integration" "integration/*")
(:exclude ".dir-locals.el" "*-tests.el")))
:bind ("C-c v" . eat-project-other-window)
:config
(setq eat-semi-char-non-bound-keys
(delete [?\C-h] eat-semi-char-non-bound-keys))
(eat-update-semi-char-mode-map)
(eat-reload))
"C-h"のような文字列ではなく[?\C-h]というベクタ表記で指定する必要がある点も、vtermのvterm-keymap-exceptionsとは違うところで少しハマりポイントだった。
ターミナルバッファの文字列をコピーしたいとき
Eatに乗り換えてから地味に困ったのが、ターミナル内に表示されている文字列のコピー方法だった。semi-charモードのままだとキー入力がそのままシェルに送られてしまうので、普通にリージョン選択してM-w、というわけにはいかない。
手順としては以下になる。
C-c C-eで"emacs"モードに切り替える。入力送信が止まり、read-onlyな普通のバッファとして扱えるようになる- 通常のEmacsコマンドでリージョンを選択し、
M-w(kill-ring-save)でkill-ringにコピー - 用が済んだら
C-c C-jで"semi-char"モードに戻す
コピーしたテキストはC-y(eat-yank)でEatバッファにも、他の通常のバッファにも貼り付けられる。
一点注意点として、“emacs"モード中もC-c C-kだけは特別扱いでプロセスをkillする動作になっている。vtermのcopy-modeとは違い緊急停止用のキーが生きたままなので、選択中に誤って押さないようにしたい。
まとめ
- vtermの
C-k問題は、vterm-keymap-exceptionsのデフォルトにC-kは含まれておらず、vterm-send-key自体もread-onlyエラーを起こさない実装だったため、根本原因は結局特定できなかった - Eatへの移行はCモジュールのビルド依存を切り離せる点が動機になった
straight-use-package-by-default tな環境でuse-package :ensure tしただけだと、Eatのterminfoソース(*.ti)がデフォルトレシピの:files指定から漏れてビルドに失敗する。明示的なレシピ上書きが必要- Eat側にもvtermの
vterm-keymap-exceptionsに相当するeat-semi-char-non-bound-keysがあるが、setqだけでは反映されず、eat-update-semi-char-mode-mapとeat-reloadの呼び出しがセットで必要 - ターミナル内の文字列をコピーするには"emacs"モードに切り替えて普通のEmacsコマンドで選択・コピーする
- vtermは開発が継続している一方、eatは開発がほぼ止まっている。作者の生存とIssue追跡は確認できているが、活発なメンテナンスを期待できる状態ではない