aok.elの紹介及び改善策の提案

はじめに

aok.elというパッケージがMELPAに登録されている. http://www.emacswiki.org/emacs-en/aok.el ざっくり言うと,multi-occurのユーザフレンドリーなラッパーである. 本稿ではこのaok.elの紹介およびその改善策について述べる. #color-moccur使えばええやん,というツッコミはとりあえず置いといて下さい.Elispの勉強ということで.

仕様

このパッケージではいくつかのコマンドが提供される. 以下,それぞれのコマンドの紹介と問題点について述べる.

all-occur

現在開いている全てのバッファ(buffer-list)を対象に,multi-occurを実行する. 問題点は,不必要なバッファ(バッファ名がアスタリスクから始まるものや空白文字から始まるもの)に対しても検索を行ってしまうところである.

type-occur

拡張子を指定してmulti-occurを実行する. このコマンドについては特に問題点を感じなかった.

mode-occur

メジャーモードを指定してmulti-occurを実行する. 問題点は,メジャーモード名の入力インタフェースに read-command を用いている点である. 補完対象が全てのコマンドというのは冗長すぎる.

occur-select

all-occurtype-occurmode-occur を選択的に使用できるよう,インタフェースが追加されている. ここでも,mode-occurの実行にあたって上記の問題点が存在する.

改善策

all-occurmode-occur,そして occur-select について上記問題点を全て解消した. https://github.com/tam17aki/aok

以下,詳細について述べる.

詳細

問題の本質は,いかにして buffer-list から不要なバッファを除外するかにあった. そこで筆者はまず,以下の変数を定義することにした.

  • 検索対象から外すバッファ名(の先頭文字)を表す正規表現 aok-buffer-name-exclusion-regexp

  • 例外的に検索対象に含めるバッファ名を表す正規表現 aok-buffer-name-inclusion-regexp

そして string-match による文字列マッチング機能を活用すれば,フィルタリングはさほど困難なく実現できると踏んだ. バッファのフィルタリングの実装は以下のようになった:

(defun aok-buffer-name-filter ()
  (let ((excludes aok-buffer-name-exclusion-regexp)
        (includes aok-buffer-name-inclusion-regexp))
    (remove-if (lambda (buf)
                 (let* ((name (buffer-name buf))
                        (include-p (if (= (length includes) 0) nil
                                     (numberp (string-match includes name))))
                        (exclude-p (if (= (length excludes) 0) nil
                                     (numberp (string-match excludes name))))
                        (filter (cond (include-p nil)
                                      ((not exclude-p) nil)
                                      (t t))))
                   filter))
               (buffer-list))))

(defun aok-get-buffer-list (&optional mode)
  (cond (mode
         (remove-if (lambda (buf)
                      (with-current-buffer buf
                        (not (eq major-mode mode))))
                    (aok-buffer-name-filter)))
        (t
         (aok-buffer-name-filter))))

#これでもフィルタリングとしてはまだ甘い なお aok-buffer-name-filter が本質であり,aok-get-buffer-list はメジャーモード指定機能をつけたにすぎない.

バッファのフィルタリングが実現できれば,残されたバッファに対してメジャーモード名(文字列)を収集することで,必要十分な補完候補が集まる:

(defun aok-get-major-mode-list ()
  (let ((major-mode-list nil))
    (loop for buf in (aok-get-buffer-list)
          for m = (format "%s" (with-current-buffer buf major-mode))
          unless (member m major-mode-list)
          do (push m major-mode-list))
    major-mode-list))

件の3つのコマンドはこれら関数を用いて再定義されたわけである.

おわりに

本稿ではaok.elを紹介し,そこで提供されるコマンド群に対して改善策を提案した. 車輪の再発明な部分は大いにあるだろうが,実際に書かないと身につかないのもまた事実であろう.

確かにcolor-moccurやhelm-c-moccurのような検索用パッケージは便利であり,筆者も常用している. それでも,今回紹介したようなコード片だけで随分と使い勝手は向上するものである.