[Emacs][知らんかった] 方向を指定して window 移動

Emacs では window 間を移動するキーを C-x o しか知らんかったのだけど、そのキーだけでは window が 3 つ以上あるときやたら面倒くさい思いをしてきた。三つ window がある時 C-x o は順繰りに移動するので、例えば「よし、右下へ移動しよう」と思ったとき何回打つかはなかなかパッと思いつかない。思いついたところで C-x o は連打しにくいので時間がかかってイラつく。(まあ使用頻度的には打ちにくいキーにしてある事は良い事なんだけど。)
そこで windmove なるものでもっと直接的に右行けとか下行けとか言えるらしいので、prefix map にまとめて C-z に突っ込んでみたら、やたら具合が良かった。もっと早く調べれば良かったと後悔し放題。

ついでに前から思ってた「C-x b みたいに一番最近の window に行くキー」も C-z C-z にバインド。これで編集ファイルと参照ファイルとコマンドシェルを同時にジャグリングするのが大分楽になりました。例えば Haskell で A.hs を眺めつつ GHCi で実験しながら B.hs にあるバグを修正したいとき。画像では分かりにくいけど、右上が GHCi のプロンプトで他二つが A.hs と B.hs。

こういう場合、A.hs, B.hs, GHCi の三つの表示を維持しながら B.hs と GHCi を往復することになるのだけど、これが C-x o だと、行き先と現在地の位置関係で打つ回数が変わるので、いちいち何回打つか考えながら打ったり、どこに移動したか確認しながら打つハメになる。そういうのは思考の流れを切るのですごく嫌い。
以下の設定の下では、まず B.hs の window を訪れてから GHCi に飛ぶ。すると後は C-z C-z する度に B.hs と GHCi だけを交互に行き来する事ができる。A.hs をちょっと弄りたくなったら、左行けだの右下行けだののコマンドで移動。

;; Convenient key bindings for moving around different windows in Emacs,
;; especially when there are 3 or more.  Alternate between a pair of
;; current windows by C-z C-z (unlike C-x o which cycles through), and
;; move around by directional keys to modify the current pair:
;; e.g. C-z C-h to go left, C-z C-k to go up, C-z C-o to go up and right.
;; See the define-keys at the bottom to see which key does what.

;; All window moving commands are prefixed with C-z.  Change this if you
;; want to minimize/suspend Emacs with C-z.
(define-prefix-command 'windmove-prefix-map)
(global-set-key "\C-z" 'windmove-prefix-map)

;; C-z C-z flips back and forth between a pair of windows (the two
;; most recently visited ones).
(defun my-select-most-recent-window ()
  (interactive)
  (let ((buf (cdr (buffer-list))) done)
    (while (and (consp buf) (not done))
      (if (get-buffer-window (car buf))
          (prog2 (select-window (get-buffer-window (car buf)))
              (setq done t))
        (setq buf (cdr buf))))))
(define-key windmove-prefix-map "\C-z" 'my-select-most-recent-window)

;; C-z C-j     go down
;; C-z C-k     go up
;; C-z C-i     go up
;; C-z C-o     go right & up
;; C-z C-u     go left & up
;; C-z C-n     go left & down
;; C-z C-.     go right & down
(defun my-make-windmove (dirs)
  `(lambda (&optional arg)
     (interactive "P")
     ,@(mapcar (lambda (x)
                 `(when (windmove-find-other-window ',x arg)
                    (windmove-do-window-select ',x arg)))
               dirs)))
(define-key windmove-prefix-map "\C-j" 'windmove-down)
(define-key windmove-prefix-map    "j" 'windmove-down)
(define-key windmove-prefix-map "\C-i" 'windmove-up)
(define-key windmove-prefix-map    "i" 'windmove-up)
(define-key windmove-prefix-map "\C-k" 'windmove-up)
(define-key windmove-prefix-map    "k" 'windmove-up)
(define-key windmove-prefix-map "\C-h" 'windmove-left)
(define-key windmove-prefix-map    "h" 'windmove-left)
(define-key windmove-prefix-map "\C-l" 'windmove-right)
(define-key windmove-prefix-map    "l" 'windmove-right)
(define-key windmove-prefix-map "\C-o" (my-make-windmove '(right up)))
(define-key windmove-prefix-map    "o" (my-make-windmove '(right up)))
(define-key windmove-prefix-map "\C-u" (my-make-windmove '(left up)))
(define-key windmove-prefix-map    "u" (my-make-windmove '(left up)))
(define-key windmove-prefix-map "\C-n" (my-make-windmove '(left down)))
(define-key windmove-prefix-map    "n" (my-make-windmove '(left down)))
(define-key windmove-prefix-map (kbd "C-.") (my-make-windmove '(right down)))
(define-key windmove-prefix-map "." (my-make-windmove '(right down)))

さて、こうなると window 分割/layout の方も視覚的なコマンドでやりたくなるけど、それはまあまた今度。