EmacsがC-xC-cで終了しないようにする
C-x C-c
でEmacsが終了しないようにしました。
背景
少し前にキーボードを変えたのですが、誤操作でEmacsを終了させてしまうことがあり、とても困っていました。
Emacsのコマンドは C-x C-f
(find-file)、 C-x b
(switch-to-buffer) など C-x
(コントロールキーを押しながらXキーを押す) という操作を多用します。私はSキーとDキーの間の下あたりにXキーがある、ロースタガードなキーボードを長年使ってきた習慣が身体に染み付いており、最近使いはじめたカラムスタッガードなキーボード(Keyball44)においても C-x
の操作をするつもりでXキーとCキーの真ん中あたりを叩いてしまうことがよくあります。それが C-x C-c
と認識されることで、Emacsを終了させてしまうことがありました。
いきなりEmacsが終了してしまうとびっくりしてしまうので、どうにかして回避したいと思いました。
キー設定を解除する
一番簡単な手は C-x C-c
のコマンド割り当てを解除してしまうことです。
(global-unset-key (kbd "C-x C-c"))
こうした場合、C-x C-c
するとエコーエリアに
C-x C-c is undefined
と表示され、Emacsは終了しません。しかしこの場合には、本当にEmacsを終了させたいときにどうしたらいいかがわからないという別の困り事が現れます。
キー設定を上書きする
キー設定を解除するのではなく、
(global-set-key (kbd "C-x C-c")
(lambda ()
(interactive)
(error (substitute-command-keys
"use \\[save-buffers-kill-emacs]"))))
として、C-x C-c
されたら代替手段を提示するようキー設定を上書きしてみます。
こうすると、例えばmacOSであれば C-x C-c
とするとエコーエリアに
use s-q
と表示されEmacsは終了しません。そして提示された代替手段に沿って Cmd+q すればEmacsを終了させることもできます。[1]
マルチターミナルでのC-xC-c
ところでEmacsはマルチターミナルに対応しており、emacsclientコマンドを使うことで既存のEmacsプロセスのフレームを別のターミナル上で開いて操作できます(Invoking emacsclient, Multiple Terminals)。クライアントプロセスで C-x C-c
してもメインプロセスが終了するわけではないので、この動作は維持しつつ、Emacsが終了しないようにしたいです。
コマンドの上書き
C-x C-c
コマンドで実行される関数を上書きして、メインプロセスでのみ C-x C-c
でEmacsが終了しないようにしましょう。
C-h k C-x C-c
とすると C-x C-c
コマンドで呼び出される関数が save-buffers-kill-terminal
で、ソースコード files.el
で定義されているとわかります。ソースコードのファイル名のところにカーソルを移動してEnter (あるいはマウスクリック)すると、実装にジャンプすることができます。
実装を読むと、(frame-parameter nil 'client)
が偽の場合に save-buffers-kill-emacs
を呼ぶことでEmacsを終了していることがわかります。
;; オリジナルの関数定義
(defun save-buffers-kill-terminal (&optional arg)
"Offer to save each buffer, then kill the current connection.
If the current frame has no client, kill Emacs itself using
`save-buffers-kill-emacs'.
With prefix ARG, silently save all file-visiting buffers, then kill.
If emacsclient was started with a list of file names to edit, then
only these files will be asked to be saved."
(interactive "P")
(if (frame-parameter nil 'client)
(server-save-buffers-kill-terminal arg)
(save-buffers-kill-emacs arg))) ; ← Emacsを終了する
この関数定義を ~/.emacs.d/init.el
にコピーして、save-buffers-kill-emacs
を呼び出すのをやめて、エコーエリアに代替手段を提示するようにします。
;; 改変した関数定義
(defun save-buffers-kill-terminal (&optional arg)
"Offer to save each buffer, then kill the current connection.
If the current frame has no client, kill Emacs itself using
`save-buffers-kill-emacs'.
With prefix ARG, silently save all file-visiting buffers, then kill.
If emacsclient was started with a list of file names to edit, then
only these files will be asked to be saved."
(interactive "P")
(if (frame-parameter nil 'client)
(server-save-buffers-kill-terminal arg)
(error (substitute-command-keys
"use \\[save-buffers-kill-emacs]")))) ; ← エコーエリアに代替手段を提示
これで、要求は満たせました。
コマンドにフックする
Emacsに含まれる関数定義をコピーして編集し、関数定義を置き換えることで要求は満たせたのですが、そのやり方だと、Emacsのバージョンアップで関数 save-buffers-kill-terminal
の定義がもし更新されたとして、更新内容が反映されないという問題があります。
Emacs には任意の関数にフックする仕組み(Advising Emacs Lisp Functions)があるのでそれを使って処理を追加すれば、自身でメンテナンスが必要になるのは追加した部分だけになります。
今回の場合だと advice-add で :before を指定して save-buffers-kill-terminal
の手前に処理を差し込んでみましょう。
(defun prevent-kill-emacs-on-server (&optional arg)
(unless (frame-parameter nil 'client)
(error (substitute-command-keys
"use \\[save-buffers-kill-emacs]"))))
(advice-add 'save-buffers-kill-terminal :before 'prevent-kill-emacs-on-server)
これでメンテナンス性を保ちつつ、 C-x C-c
でEmacsが終了しないようにできました!
おしまい
この記事は C-x C-c
で終了しなくなったEmacsとKeyball44を使って書きました。
-
Windows環境だと
use M-x save-buffers-kill-emacs
と表示されました。ちょっと手間ですがその通りにすれば終了できます。 ↩︎
Discussion