Emacs
emacs-lisp

[Emacs] key-chord.el を改良してキーバインドし放題になった話

デモ

screencast.gif

際限なくキーバインドができるので、「bootstrap.css を読み込む link タグを挿入する」のような超ニッチなコマンドにも気軽にショートカットを割り当てられます (ステノワードみたいな気分です)。

ダウンロード

ここでダウンロードできます☞ https://github.com/zk-phi/key-chord/

個人のタイピングスピードに合わせてチューニングが必要です。

(setq key-chord-two-keys-delay           0.15
      key-chord-safety-interval-backward 0.1
      key-chord-safety-interval-forward  0.25)

詳細は下を読んでください。

key-chord.el とは

key-chord.el は、複数のキーの同時押しにコマンドを割り当てることができる Emacs プラグインです。

(key-chord-define-global "hj" 'undo) ;; h と j 同時押しで undo
(key-chord-mode 1) ;; キーバインドを有効に

ある入力が同時押しかどうかは、2つのキー入力の間が 0.1 秒以内 (key-chord-two-keys-delay で変更可能) かどうかで判定されます。

key-chord.el を使うと、 C- とか M- だけじゃなく同時押しにも別のコマンドをバインドできるので、1ストロークで呼び出せるコマンドがかなり増えるように見えます(しかも装飾キー不要)。

key-chord.el の弱点

使い始めてみると気づくのですが、 key-chord.el には「よく出てくるキーの並びを登録できない」という弱点があります (ソースの Commentary にも書かれています)。

たとえば

(key-chord-define-global "fi" 'find-file) ; f, i 同時押しでファイルを開く

とキーバインドして

final

をすばやく入力すると、 fi 部分が同時押しと判定されて M-x find-file nal の挙動になり、びっくりすることがあります。とはいえ「単語の中に fi (or if) が含まれている場合はゆっくりめに打つ」というのも厳しいし、本末転倒な感じです。

一応 key-chord-two-keys-delay の閾値をシビアに調整すればマシにはなるのですが、今度は正確に同時押ししないと「微妙にずれていて普通に fi が入力されてしまった」みたいになってしまうので、それはそれで疲れます。

そんなわけで、 key-chord.el は一見すごい可能性を秘めているように見えて、実際に使ってみると使いやすい同時押しは案外限られていることに気づきます。

key-chord.el の誤発火 を減らす

key-chord.el の誤発火を減らすためにやったことです:

アイデア

しばらく key-chord.el を使っていて気づいたことは、「同時押しは想像よりは大変」ということです。

2つのキーを同時に押すために、その2つのキーの位置に2本の指を移動して、いっぺんに押すので、単語を入力するときのように流れで1つづつキーを押すのに比べるとけっこう大変です。少なくとも、同じスピードではそうそう押せません。

そこで自分が考えたのは、今まで key-chord.el が「2つのキー入力の間が十分短いこと」だけで同時押しを判定していたのを、「ほかのキー入力からワンテンポ遅れていること」も条件に加えることで誤入力を減らすことです。

従来の key-chord.el イメージ:

 \ カタカタ /      \ バン /     \ カタカタ /
h o g e h o g e      hj       h o g e h o g e . . .
----------------------------------------------------
                  |< 0.1s|

改良後 key-chord.el イメージ:

 \ カタカタ /      \ バン /     \ カタカタ /
h o g e h o g e      hj       h o g e h o g e . . .
----------------------------------------------------
                 |< 0.15s|
              |> 0.1s||> 0.25s|

(経験的に、 key-chord の前の待ち時間と後の待ち時間は別々に設定できるようにしたほうがよさそうでした)

この方法なら、たとえば final と入力しても in の間が十分短ければ、一連の入力の一部とみなされて fi の key-chord は発火しません。

自分の場合はこれでかなり誤発火が減りました。それでもまれにありますが… (今後やること 参照)。

実装

key-chord を受け付ける(直前のキー入力から 0.1s 以上離れている)状態かを保持する変数 を用意してあげて

  • 任意のコマンドの実行後 (post-command-hook) に nil にする
  • 0.1s のアイドルタイマーで定期的に t にする

ようにしました。

また、 key-chord 入力後も 0.25s だけ sit-for で待ってあげて、もし直後に何か入力があればやっぱり key-chord ではなかったものとみなします (sit-for の戻り値で、待っている間にキー入力があったかが取れます)。

やってみて

誤発火はかなり減りました、前後の待ち時間をいい感じに調整すると、むしろ同時押しの閾値はゆるゆるでも使えるので、同時押しするときも緊張しません。 va などごく普通に出てくるキーの並びにも平気でキーバインドして使っています。ただ、誤発火はまだゼロではなくて、ごくまれにあるので何とかしたさがあります。

今後できそうなこと

同じ手で続けて入力するとタイピングスピードが落ちるので、左右の手で交互で押せる key-chord と、同じ側の手に両方のキーがある key-chord で待ち時間を変えられるとさらに誤発火を減らせそうです。