dabbrev-expandが手元のarchの環境で期待通りに動かない理由を調べた

(これはとても個人的なメモというか備忘録です)

dabbrev-expandが手元のarchの環境で期待通りに動かない理由を調べた(付け加えておくと、手元のarchの環境が悪いのであってarchが悪いわけではない)。

はじめに

最近、自分の使っているemacsdabbrev-expandの挙動が、手元のmacとarchで異なっていてなんでだろう?とずっと不思議に思っていた。具体的に言うと。以下の様な状況で。

fooBar
fooBoo
foo| ; |がカーソル

dabbrev-expandを実行(M-/)していたときに上手く期待通りにならなかった。 期待する挙動とは foo -> fooBoo -> fooBar と変わるというもの。一方で手元のarchの場合はdabbrevが一度しか効かない状態になっていた。

環境 挙動
mac foo -> fooBoo -> fooBar
arch foo -> fooBoo

CapsLock

いろいろ調べてみたところ、原因はCapsLockの設定だった。現在のemacsの設定ではCapsLockのキー自体はCtrlにしているのだけれど。そのキーは英数のトグルのキーでもあるらしく。そちらはめんどくさかったので無効にしていなかった。

そして考えてみれば、dabbrev-expandもC-[ / 経由で M-/ を実行していて、かつ失敗するのはC-[のCtrlがCapsLockのときだった。

dabbrev-expandが次の候補を探す条件

それではどうしてdabbrev-expandが失敗していたかと言うと。dabbrev-expandのコード自体に以下のような条件式が存在している。

(or (eq last-command this-command)
    (and (window-minibuffer-p)
         (= dabbrev--last-abbrev-location
         (point))))

dabbrev-expandはこの条件が真の場合に、次の補完候補を探すというような仕様になっている。

eisu-toggle

また、これは完全に現在使っている環境の問題なのだけれど。キーボード上の英数のトグルの方の機能は潰していなかったので。emacs上でCapsLockキーを入力した時に以下の様なメッセージが出ていた。

<eisu-toggle> is undefined

つまるところ、英数のトグルのキーとして認識されていて、何にもbindされていないというエラーメッセージ。これが邪魔だったので 'ignoreをbindさせていた。この関数はsubr.elで定義されている本当に何もしない関数。

(defun ignore (&rest _ignore)
  "Do nothing and return nil.
This function accepts any number of arguments, but ignores them."
  (interactive)
  nil)

ここでもう一度dabbrev-expandの条件の (eq last-command this-command) を見て欲しいのだけれど。CapsLockをCtrlキーとして使って C-[ / と入力した場合には、このignoreという関数が呼ばれてしまっていた。結果として (eq 'ignore 'dabbrev-expand) となってしまい。補完は完了したという扱いになってしまっていた。

最悪なwork-around

ここでテキトウにemacsのドキュメントを覗いてみると以下の様なことが書かれている。

Normally, whenever a function is executed, Emacs sets the value of this-command to the function being executed (which in this case would be copy-region-as-kill). At the same time, Emacs sets the value of last-command to the previous value of this-command.

emacsは最後に実行されたコマンドを覚えておくらしい。まぁそんなわけですごくお行儀の悪い以下の様なコマンドを作成しこれをbindしたらとりあえずは動くようになった。

(defun my:ignore (&rest args)
  (interactive)
  (setq this-command last-command)
  nil)

(global-set-key (kbd "<eisu-toggle>") 'my:ignore)