emacsで前置引数を活用する

emacsには前置引数でコマンドに定義する機能の幅が広がるのだが以外に知られていない。そこでこの前置引数を利用したelispの関数定義について紹介しようと思う。


まずはemacs上で「C-u 5 a」と入力してみてほしい。「aaaaa」と入力されるはずだ。この5が前置引数となる。ここでの「C-u 5」は入力 a を5回繰り返すという意味である。

次に「C-u a」、「C-u C-u a」、「C-u C-u C-u a」と入力してほしい。繰り返しが「4」「16」「64」と増えただろう。このように、数字を入力しなければ前置引数の値は「C-uの回数 * 4」となる。
文字入力の場合は前置引数は繰り返し回数となるが、前置引数の意味はコマンドによって違う。この値を上手く利用すると便利なキー定義ができる。

まずは関数で前置引数を利用できることを確認しよう。以下の関数を評価してみてほしい。

(defun p-test (arg)
  (interactive "p")
  (message (format "%i" arg)))

「C-u M-x p-test」とするとミニバッファに「4」と表示される。このように、関数先頭に(interactive "p")と記述すると、引数argに前置引数の値が代入されるのだ。

これを利用すると、以下のような関数定義ができる。

(defun my-def (arg)
  (interactive "p")
  (case arg
    (4  (コマンド実行前にC-uを押した時の処理))
    (16 (コマンド実行前にC-u C-uを押した時の処理))
    (64 (コマンド実行前にC-u C-u C-uを押した時の処理))
    ...
    (t  (C-uを押してないときの処理))))

例えばこのmy-defをC-tに割り当てると、C-t, C-u C-t, C-u C-u C-t, ... に別の機能を割り当てることができる。

この前置引数を用いた関数定義は、似たような機能をどちらもキーに割り当てたいが、どのキーに割り当てるか困ったときに非常に重宝する。既に定義済みのコマンドを拡張したような機能を追加するときも、既に定義しているキーの前にC-uを叩くだけなので覚えやすいという利点もある。

例として私が定義している関数を挙げてみよう。

;;;** 画面の分割と移動(分割されていたら移動)
(defun other-window-or-split ()
  (interactive)
  (when (one-window-p) (split-window-horizontally))
  (other-window 1))

;;;** 上に加えてC-uが付いていたら画面を閉じる
(defun other-window-or-split-or-close (arg)
  "画面が1つなら分割、2つ以上なら移動。
C-uをつけるとウィンドウを閉じる。"
  (interactive "p")
  (case arg
    (4  (delete-other-windows))
    (16 (delete-window))
    (t  (other-window-or-split))))

これはEmacsテクニックバイブルで紹介されていた「C-tに画面分割と移動の両方を定義する」というelispに前置引数を用いて拡張したものである。

C-t で画面分割ができるなら、分割した画面を閉じるコマンドも C-t に割り当てたいということで、「C-u C-t」にカーソルが合っていない画面を閉じる機能を、「C-u C-u C-t」にカーソルが合っている機能を追加した。あとは other-window-or-split-or-close を C-t に割り当てればよい。


似たような機能をいろんなキーに定義していて覚えきれなくなっている人はぜひ前置引数を活用して欲しい。