コンテキストに応じてコマンドを変える

coffee-modeの pull requestで Emacs本体を調べる必要があって,
ソースを眺めていると elec-pair.elに奇妙な記述を見つけた.

(defvar electric-pair-mode-map
  (let ((map (make-sparse-keymap)))
    (define-key map "\177"
      `(menu-item
        "" electric-pair-delete-pair
        :filter
        ,(lambda (cmd)
           (let* ((prev (char-before))
                  (next (char-after))
                  (syntax-info (and prev
                                    (electric-pair-syntax-info prev)))
                  (syntax (car syntax-info))
                  (pair (cadr syntax-info)))
             (and next pair
                  (memq syntax '(?\( ?\" ?\$))
                  (eq pair next)
                  (if (functionp electric-pair-delete-adjacent-pairs)
                      (funcall electric-pair-delete-adjacent-pairs)
                    electric-pair-delete-adjacent-pairs)
                  cmd)))))
    map)
  "Keymap used by `electric-pair-mode'.")

初めは単に lambdaを登録しているだけかと思ったのですが, 空括弧内と
そうでないときで, describe-keyの結果が変わっていてなんじゃこりゃと
思ってみていると, menu-itemと :filterを組み合わせることでコンテキストに
応じてコマンドを返ることができるようです.

例 行頭のときは back-to-indentation, それ以外は行頭へ移動

以下のようになります.

(global-set-key (kbd "C-a")
                `(menu-item
                  "" move-beginning-of-line
                  :filter
                  ,(lambda (cmd)
                     (if (not (bolp))
                         cmd
                       'back-to-indentation))))

上記を評価し, 行頭とそれ以外で describe-key C-a と実行すると
異なる結果が得られることがわかります.


もちろんこのようなコマンドを新規に作成することは容易なのですが,
その場合 defadvice等の設定が重複する可能性があったりするので,
場合によりありかと思いました.


あと lambdaで nilを返したら, fallbackさせるので(minor-mode-map->global-map),
そのような場合にも使えるのかなと思いました.

おわりに

elec-pairの変更は最近入ったものだったので, 最近加わった機能なのかなって
思ったんですけど, Emacs23でも使えました. まだまだよくわからないことが
たくさんあるものです.