括弧の自動挿入の挙動をオレオレ設定できるflex-autopair.elで夢を叶える

空気を読まずに4/1なのに本気エントリの投入です。今までいくつ作られてきたのか分からない、括弧を自動挿入する系のelispを作ったのでリリースします。
私はこれを使い始めてから、閉じ括弧とスペースを打つ回数が激減しました!さらに、怖いぐらい宝くじも当たり、長年の持病だった痔と水虫も治ったらいいなぁ。まずはデフォルト設定を一週間試してみてください。

紹介のためのスクリーンキャストを撮影しましたので、とりあえずご覧下さい。

試しに使ってみる!

スクリーンキャストを見て、面白いと思ったはずなので使ってみましょう。
インストールは

(auto-install-from-url "https://raw.github.com/uk-ar/flex-autopair/master/flex-autopair.el")

を評価するか、
marmaladeから M-x package-install flex-autopair でOKです。
インストール後に

(require 'flex-autopair)
(flex-autopair-mode 1)

とすると使えます。

手を加えず使ってみる

デフォルトの設定では以下の4つの動きになります。

  1. 開き括弧やクオートを打つとペアになる文字を自動挿入します
  2. 自動挿入が思い通りにいかなかったら、undoでキャンセルできます
  3. 間違って自分でペアを閉じようとした場合にカーソルを移動するだけにします
  4. 何か(urlや単語)の先頭で開き括弧やクオートを打つとその何かを囲います

c系のモードやlisp系のモードではより激しい挙動をしますが、詳しくは後述します。

条件によって動きを変えてみる

一週間のお試し期間が終わる頃にはデフォルトの設定ではいろいろと不満も出てくることでしょう。逆にそうなってからがスタートです。
このプラグインの特徴は括弧やクオートを押したときに何をするか(action)とどんな条件でするか(condition)を細かく設定できることです。
具体的にはそれぞれ、flex-autopair-actionsとflex-autopair-conditionsという変数を使って設定します。

flex-autopair-conditions

説明の都合で条件から先に説明します。この変数ではどんな条件で何をするかを(条件式 . アクション名)を並べた連想リスト(alist)を設定します。デフォルト設定から簡略化して抜き出すと

(setq flex-autopair-conditions
      `(;; Insert matching pair.
        (openp . pair)
        ;; Skip self.
        ((and closep
              (eq (char-after) last-command-event)) . skip)
        (closep . self)
        ))

となります。この連想リストを上から評価して、最初に真になった要素のアクション(ここではpair,skip,self)が実行されます。この設定の場合

  • 入力が開き括弧や開きクオートの場合はpair
  • 入力が閉じ括弧や閉じクオートでカーソル位置の文字と同じ場合にはskip
  • 入力が閉じ括弧や閉じクオートでそれ以外の場合はself

みたいな動きになってます。
(openpとclosepは括弧やクオート開いたかどうか表す変数です)
では、pairやskipのようなアクションは具体的何するの?という設定がflex-autopair-actionsです。

flex-autopair-actions

この変数ではflex-autopair-conditionsで指定したアクション名と実際の動作を対応させます。具体的にはflex-autopair-conditionsと似た感じで(アクション名 . 式)を並べた連想リスト(alist)を設定します。デフォルト設定から簡略化して抜き出すと

(setq flex-autopair-actions
      '((pair . (progn (call-interactively 'self-insert-command)
                       (save-excursion
                         (insert closer))))
        (skip . (forward-char 1))
        (self . (call-interactively 'self-insert-command))
        ))

となります。この連想リスト内から、アクション名をキーに探索し、見つかった要素の式が実行されます。この設定の場合

  1. pair: 押したキーと、対になるペアを挿入
  2. skip: カーソルを右に移動
  3. self: 押したキーを挿入

となってます。
設定を追加するには、flex-autopair-conditions の場合

(setq flex-autopair-user-conditions-high
      `((openp . hoge)
        (closep . fuga)))
(flex-autopair-reload-conditions)

とすれば、flex-autopair-conditionsにflex-autopair-user-conditions-highの内容がいい感じの優先度で反映されます。
flex-autopair-actionsに設定を追加する場合

(add-to-list
 'flex-autopair-actions
 '(hoge . (message "this is hoge"))
 )

とすればいいと思います。

カップリング(?)を増やしてみる

ちまたのEmacsユーザーの間ではカップリングが流行ってるみたいなので*1やってみましょう。
flex-autopairは文字のペアを自動判別してます*2。でも普段は文字をそのまま挿入してほしいけど時々ペアにしたい文字がありますよね?例えばc言語での<です。この場合は#includeの後だけカップリングしてくれると便利デスね?そんなわがままも叶えちゃいます!
手順は

  1. 文字のペアを追加
  2. 追加したペア用の条件を追加

となります。
c-modeに<を追加する例を解説します。

文字のペアを追加

c-mode-hookで<のペア(?\< . ?\>)をflex-autopair-pairsに追加します。flex-autopair-pairsはバッファローカル変数なのでc-modeだけで有効になります。

(defun my-hook-function ()
  (add-to-list 'flex-autopair-pairs '(?\< . ?\>)))

(add-hook 'c-mode-hook 'my-hook-function)
追加したペア用の条件を追加

文字のペアを追加しただけだと、開き括弧や開きクオートを入れると無条件でペアを挿入してしまいます。なので、<の場合

  • #includeがあればpair
  • #includeがなければself

の二つの設定を追加します。

(setq flex-autopair-user-conditions-high
      `(((and
          (eq major-mode 'c-mode)
          (eq last-command-event ?<)
          (save-excursion
            (re-search-backward "#include" (point-at-bol) t)))
         . pair)
        ((and
          (eq major-mode 'c-mode)
          (eq last-command-event ?<))
         . self)
        ))
(flex-autopair-reload-conditions)

以上で

あとがき

皆さんそろそろ金運が上がって宝くじが当たったことでしょうか?私はもうすぐ府中競馬場の近くに引っ越すので楽しみでしょうがありません。また、前から一度カップリングと言ってみたかったので今回のエントリでまた一つ夢が叶いました。
さも自分で作ったったみたいに解説してますが、アイデアとコードの大半はacp.el*3とelectric-pair-mode*4盛大にパクって参考にしています。acp.elを公開してくださったid:buzztaikiさんありがとうございます。