EmacsがC-gで終了しなくなったときの対処方法

Emacs使いならば、Emacsが無限ループに陥ったときに「やめれ!」と叫ばんばかりにC-gを押すのが癖になっているだろう。しかし、それでも止まらないときがある。その原因は「inhibit-quit」変数がtになっているからだ。

 -- Variable: quit-flag
     `inhibit-quit'が`nil'であれば, この変数が`nil'以外であるとEmacsは
     ただちに中断する.  `C-g'は, `inhibit-quit'に関わらず, 通常,
     `quit-flag'に`nil'以外を設定する.
 -- Variable: inhibit-quit
     この変数は, `quit-flag'が`nil'以外の値に設定されたときにEmacsが中
     断すべきかどうかを決定する.  `inhibit-quit'が`nil'以外であると,
     `quit-flag'に特別な意味はない.

だから、たとえばこんなコマンドを実行したら、CPU喰らいつくししかも止められない。死ねます。良い子は真似しないように。

(defun die ()
  (interactive)
  (let ((inhibit-quit t))
    (while t)))

inhibit-quitがどんなに危険な変数であるかはわかっただろう。

Emacs Lispのソースをgrepしてみると、けっこう使われているんだわ。font-lockにも使われているから、めちゃ長い行の色付けが死ぬほど遅くて「やめれ」と叫んでも止まらない。待つしかない。なんてこったい。

inhibit-quit はrun-with-idle-timer等の遅延実行に使われる関数にも使われている。たとえば、このstart-loopコマンドを実行すると1秒後に暴走する。

(defun do-loop ()
  (while t))
(defun start-loop ()
  (interactive)
  (run-with-idle-timer 1 nil 'do-loop))

この対処法はrun-with-idle-timerが実行する関数内で inhibit-quit を nil にしておくことだ。start-loop2は止まる。

(defun do-loop2 ()
  (let ((inhibit-quit nil))
    (while t)))
(defun start-loop2 ()
  (interactive)
  (run-with-idle-timer 1 nil 'do-loop2))

run-with-idle-timer 等の遅延実行関数を使うときは、 inhibit-quit を nil にしておこう!!

anything.elでは anything-input-idle-delay を設定していたり、 delayed ソースを実行するときにこの関数が使われている。anything-c-moccurを使っていて無限ループになってC-gが効かなくて泣く泣くEmacs再起動した人もいるだろう。原因は検索結果の出力(anything.el側)で run-with-idle-timer が使われていて、その内部で inhibit-quit が t になっていることだ。anything.elを修正したのでよろしく。