リモートのEmacsでもC-upでbackward-paragraphしたい→必要なのはTerminal.appの設定だった

手元のCarbon Emacsと、MacのTerminal.app越しのリモートのEmacsとでキーバインド周りの挙動を揃えたいと思った。挙動が違うといちいち頭を切り替えるのに無駄な負担がかかるので。

手元のCarbon EmacsではC-up(Ctrl押しながら上矢印)が「前のパラグラフ」にバインドされている。describe-key(F1 k)すると runs the command backward-paragraphと表示される。これがなぜかターミナル越しでは動かないので「ああ、あっちのEmacsではC-upがbackward-paragraphにバインドされていないんだな」と思った。

そう思ってglobal-set-keyでバインドしてみた。

(global-set-key [C-up] 'backward-paragrah)

しかしこれでは動かない。あれれなんでだろう?と describe-key してみたら、C-upを押していてもについての解説が出ている。じゃあ問題はEmacsにあるのではなくターミナルにあるってことか。

こちらの記事が参考になった: Control and up/down keys in terminal for use by emacs - Unix and Linux

ざっくり訳す。ターミナルが知ってるのは文字だけであって、キーのことは知らない。だから文字に対応していないキーはエスケープシーケンスに変換して送らなければならない。他のエスケープシーケンスとぶつかったりしなければどんなものを選んでもいい。残念ながら標準化されていない。\e[1;5A とか \e[1;5B がポピュラーな選択肢だ。エスケープシーケンスをキーに戻すのには input-decode-map を使えば良い。

今まで意識してなかったけども、ターミナルの設定画面を見ると確かにF1は「\033OP」というようにキーからエスケープシーケンスへの対応が並んでいる。Emacsはこの\033OPを見て、F1キーが押されたと判断しているんだな。というわけでここにC-upとC-downを追加した。エスケープシーケンスは、入力欄に直接入力しようとすると、\を押しただけで\\が入ったりして困ったので、エディタに書いてからコピペした。他のコマンドに合わせてエスケープ文字は\033と表現しておいた。

emacs.elにはこんなかんじに書いた。こちらはエスケープ文字は\eになっている。

(define-key input-decode-map "\e[1;5A" [C-up])
(define-key input-decode-map "\e[1;5B" [C-down])

無事期待通り動くようになった。めでたしめでたし。えんいー。