Emacsで1〜3ストロークで画面上の任意の場所に移動するための設定

タイトルで1〜3と書きましたが、実際は2ストロークが平均的です。運が良ければ1ストローク、たまに3ストローク。4ストローク以上もありえますが、普通のソースコード上ならまず無いです。
ace-jump-modeを使います。
これはvimのEasyMotionというプラグインにインスパイアされて作られたみたいです。どういう機能かというのは
http://d.hatena.ne.jp/syohex/20120304/1330822993
が参考になります。
実際に動いてるデモは公式のものがあります。
http://dl.dropbox.com/u/3254819/AceJumpModeDemo/AceJumpDemo.htm

で、実際便利なのですが、一度ace-jump-modeを起動して、飛びたい文字を指定して、とやるのもなんだか面倒になってきて、あまり使っていませんでした。(ace-jump-modeの起動をC-c SPCに設定していたのもあります。)
そんな中、テスト駆動JavaScriptの著者であるChristian Johansen氏の.emacs.dをGitHubで眺めていると、このace-jump-modeを大胆な設定で使っているのを発見しました。*1
これはいいと思ったので自分ごのみに改変して自分のところにも取り込んで利用しています。 元ネタはこちら

どんなもんかと簡単に説明すると、H-a 〜 H-z, H-0 〜 H-9をすべてace-jump-modeに割り当て、例えばH-c通したら即座に画面上の文字cにジャンプしようとするようにします。画面上に文字cが1つしか現れていなければH-cで即座にジャンプしますし、複数現れていればH-c bみたいな感じで2ストロークで飛べます。候補が多すぎる場合はそれに応じてストローク数は増えますが、大体2-3回です。Hって何って人は後述の修飾キーの説明をどうぞ。

こういう時に便利

例えばこういうソース。

(put 'if-match 'scheme-indent-function 2)
(put 'let*-values 'scheme-indent-function 1)
(put 'let-args 'scheme-indent-function 2)
(put 'let-keywords* 'scheme-indent-function 2)
(put 'let-match 'scheme-indent-function 2)
(put 'let-optionals* 'scheme-indent-function 2)
(put 'let-syntax 'scheme-indent-function 1)
(put 'let-values 'scheme-indent-function 1)
(put 'let/cc 'scheme-indent-function 1)
(put 'let1 'scheme-indent-function 2)
(put 'letrec-syntax 'scheme-indent-function 1)
(put 'make 'scheme-indent-function 1)
(put 'multiple-value-bind 'scheme-indent-function 2)
(put 'match 'scheme-indent-function 1)
(put 'parameterize 'scheme-indent-function 1)
(put 'parse-options 'scheme-indent-function 1)
(put 'receive 'scheme-indent-function 2)
(put 'rxmatch-case 'scheme-indent-function 1)
(put 'rxmatch-cond 'scheme-indent-function 0)
(put 'rxmatch-if  'scheme-indent-function 2)
(put 'rxmatch-let 'scheme-indent-function 2)
(put 'syntax-rules 'scheme-indent-function 1)
(put 'unless 'scheme-indent-function 1)

このうち(put 'parameterize 'scheme-indent-function 1)を(put 'parameterize 'scheme-indent-function 2)に変えたいなーと思ったときにどう移動するか。
多分isearchとか使ってC-s para C-e C-b C-h 2とかですか?(僕はこのへんのキーバインドすら変更しまくっているので一般的なキーバインドがうろ覚えなのですが)
これをace-jump-modeならC-c SPC 1 h C-d 2で出来ます。*2
さらに、今回紹介する設定をすると、H-1 i C-d 2で出来るよ、っていうはなしです。移動までならH-1 iってことですね。慣れれば相当便利です。
isearchを利用した移動はよくやりますが、今回みたいに似たようなワードばかり出るシーンだと時間かかっちゃうことあるんで。。

インストール

普通にhttps://github.com/winterTTr/ace-jump-modeからとってきてパスの通ったところにおいて

(require 'ace-jump-mode)

してください。package.elが使える環境なら、MarmaladeかMELPA経由でも入れられるはずです。ELPAは入れてないので知りません。

設定

本来ならここで

(define-key global-map (kbd "C-c SPC") 'ace-jump-mode)

 とでもしてキーバインドの設定をしておわりなんですが今回はそれはせずに以下の様な設定を足します。

(defun add-keys-to-ace-jump-mode (prefix c &optional mode)
  (define-key global-map
    (read-kbd-macro (concat prefix (string c)))
    `(lambda ()
       (interactive)
       (funcall (if (eq ',mode 'word)
                    #'ace-jump-word-mode
                  #'ace-jump-char-mode) ,c))))

(loop for c from ?0 to ?9 do (add-keys-to-ace-jump-mode "H-" c))
(loop for c from ?a to ?z do (add-keys-to-ace-jump-mode "H-" c))
(loop for c from ?0 to ?9 do (add-keys-to-ace-jump-mode "H-M-" c 'word))
(loop for c from ?a to ?z do (add-keys-to-ace-jump-mode "H-M-" c 'word))

これでH-0〜H-9、H-a〜H-zで任意のところに飛べます。
H-M-a〜zはword移動にしてあるので単語の先頭の文字だけがジャンプ先候補になります。
例えば、global-linum-modeという文字列に対してH-mをおすとglobal-linum-modelがジャンプ先候補になりますが、H-M-mとしたらglobal-linum-modeがジャンプ先候補になります。
僕は単語の先頭かどうかってことをいちいち考えるのが面倒なので、候補数が増えてもいいから基本はjump-char-modeを使うようにしています。
これが合わなければラスト4行を

(loop for c from ?0 to ?9 do (add-keys-to-ace-jump-mode "H-" c 'word))
(loop for c from ?a to ?z do (add-keys-to-ace-jump-mode "H-" c 'word))

とすればH-a〜zもjump-word-modeになります。ちなみに参考元の設定では単語モードのみになっていました。
HyperじゃなくてAltがいいとかなら"H-"を"A-"にすればOKです。
あとはまぁ記号にも飛びたいとかであれば適宜マッピングしてください。
自分は最近記号も飛びたくなってきたので

(loop for c from ?! to ?~ do (add-keys-to-ace-jump-mode "H-" c))
(loop for c from ?! to ?~ do (add-keys-to-ace-jump-mode "H-M-" c 'word))

として主要な記号も含めてほぼすべてマッピングしてしまっています。
どうせほとんどHyper潰しちゃうんだから個人的にはここまでやっちゃっていいんじゃないかなーとおもいます。

おまけ:修飾キーについて

ここではHyperを使っていますが、別に他の修飾キーで構いません。と言ってもControlとMetaを潰す訳にはいかないので、Super, Hyper, Altあたりが候補に上がりますが、Superはすでにそれなりに使うコマンドで予約済みの人も多いはず。
僕はHyperとAltはがら空きだったのでHyperを潰しました。
SuperとかHyperとか何?、AltってMetaじゃないの?って人はこのへんとか。
僕はMacユーザーなので、右コマンドをHyperにしています。デスクトップではKinesisキーボードを使っていて、右のSpaceがそうなるようになっています。ホームポジションで右手の親指が自然にのっかる位置なので非常に押しやすい。
MacBook Airでは右コマンドそのままです。そこそこ押しやすい。
僕の設定は以下の通り。(Lion, Emacs24)

(setq ns-command-modifier (quote meta))
(setq ns-right-command-modifier (quote hyper))
(setq ns-alternate-modifier (quote super))
(setq ns-right-alternate-modifier (quote alt))

あとCentOSを使うこともありますがそれはここを参考に、CapsLockとCtrlを入れ替えたあと、CapsLockにHyperをマッピングしています。

おわりに

なんか大したこと書いてないのに長くなってしまった。
でも久しぶりに使い勝手に大きな変化が現れたので自分の中で相当大きいです。
あとこれ書いてる途中にjaunte.elというのを知りましたけど、あれも便利そうですね。
似てるようで若干違うようですが、複数ウィンドウまたいで飛べるのはいいですね。char-modeでそれやっちゃうとさすがにウィンドウの状況によっては検索候補多すぎるかもしれないけど。。
時間ができたらフォーク版作るのも良いかもしれないなー

Emacsは好きですが、やはりカーソル移動を始めとする編集機能はVimとかに比べると弱いので、こういった改造はすごく魅力的に感じます。
僕はC-n,p,f,bという配置もすごく不満に感じたので、それもリマップしています。このへんはあたらしいelisp入れると設定を上書きされることも多いんで色々面倒なのですが。。まぁこのあたりのネタはそのうち。

*1:ただしこの設定をした人はコミットログを見る限り別の方のようです。というかほとんどのコミットが別の方のようです。Emacs Rockというスクリーンキャストサイトの関係だと思うんですがよくわかりません。

*2:ace-jump-modeの起動をC-c SPCにしている場合