zshの履歴検索 with anything

なんかやたらと疲れが溜まっています。主に目に。現場の環境がよくないんでしょうか。

かなり前に、zshからanythingを裏方で呼び出して、表面上はシームレスにanythingの履歴確認ができるようにしていました。
これです。

自分で作って、かなり長いこと使っていたのですが、ずっと不満だったことが一点ありました。
それは、anything-match-pluginで提供されている、空白区切りによる複数候補の検索ができなかったことでした。やろうやろうと思っていましたが、年末年始ということもあってすっかり忘れてしまっておりました。
ふと思いだしたので、anythingのソースとにらめっこをしながら、以前のソースを書き換えてみました。

以前は、anythingが内部で呼び出していたanything-compute-matchesという関数を直接呼び出し、毎回自分で行の取得とかまでわざわざやっていました。
ところで、anything + anything-match-pluginはどのようにして、複数候補によるマッチングを実現させているのか、私はよくわかりません。というわけでソースを覗いてみると、

(defun anything-compile-source--match-plugin (source)
  (let ((searchers (if (assoc 'search-from-end source)
                       anything-default-search-backward-functions
                     anything-default-search-functions)))
    `(,(if (or (assoc 'candidates-in-buffer source)
               (equal '(identity) (assoc-default 'match source)))
           '(match identity)
         `(match ,@anything-default-match-functions
                 ,@(assoc-default 'match source)))
      (search ,@searchers
              ,@(assoc-default 'search source))
      ,@source)))

こんな関数があって、この中で渡されたsourceに(search ,@searchers ,@(assoc-default 'search source))というのを追加して返しています。今回ソースを読んだことで、anythingのプラグイン機構が理解できるという副作用までありましたが、それは置いておいて。

つまりは、この,@searchersを利用するanything.elの関数を探せばええやろう、ということで探したところ、「anything-candidates-in-buffer」という関数が見付かりました。これも直接起動するようなものではなく、組み込みのanythingプラグインでしたが。

なにはともあれ、これを呼び出せば、anything-match-pluginの機能を利用してくれるということがわかったので、これを使って書き換えました。

;; zshからanythingを利用するための設定。
(require 'anything-complete)
(defvar azhzle/tmp-file "/tmp/.azh-tmp-file")
(defvar azhzle/cache nil)
(defvar azhzle/dicision-keys '("A" "S" "D" "F" "G" "H" "J" "K" "L"
                               "Q" "W" "E" "R" "T" "Y" "U" "I" "O" "P"))

(defun anything-zsh-history-from-zle-init ()
  (let ((anything-sources `((,@anything-c-source-complete-shell-history
                             ,(cons 'candidates 'lists)
                             (volatile)))))
    (anything-initialize)))

(defun azhzle/input (str)
  (interactive)
    (let* ((lists (azhzle/get-history-line str)))
      (with-temp-buffer
        (loop for i from 1 upto (if (<= (length lists) (length azhzle/dicision-keys))
                                    (length lists)
                                  (length azhzle/dicision-keys))
              for line in lists
              do (insert (nth (1- i) azhzle/dicision-keys) ":"
                         (substring-no-properties (if (listp line)
                                                      (car line)
                                                    line)) "\n"))
        (set-buffer-file-coding-system 'raw-text-unix t)
        (write-region (point-min) (point-max) azhzle/tmp-file))
      (setq azhzle/cache lists)
      ))

(defun azhzle/get-history-line (pattern)
  (with-current-buffer (shell-history-buffer)
    (let* ((anything-pattern pattern)
           (anything-sources `((,@anything-c-source-complete-shell-history
                               ,(cons 'candidates 'lists)
                               (volatile))))
           (source (car (anything-get-sources)))
           (anything-source-name (cdr (assoc 'name source)))
           (zsh-p t))
      (anything-candidates-in-buffer))))

(defun azhzle/dicision-history (history-num)
  (interactive)
  (with-temp-buffer
    (erase-buffer)
    (insert (substring-no-properties (nth (- history-num 1)
                                          azhzle/cache)))
    (write-region (point-min) (point-max) azhzle/tmp-file)
  ))

これが全部です。anything-candidates-in-bufferを利用するようにしたことで、かなりの行数削減ができました。かなりすっきりです。ちなみにzshrcへの記述内容は以前と変わっていません。

どうでもいいんですが、Emacserの方々はzshを {terminal, Emacs}のどちらから利用しているんでしょうか?私はmlterm + tmuxで利用しています。ansi-termとかshell-modeから利用したこともありますが、どうにも面倒な上、ログが溜まると重くなる一方だった記憶しかありません。

とりあえずこれで、より自前で利用するのが便利になりました。次は任意のsourceを利用するようにしてみたいんですが、zsh上でわざわざsourceを利用する意味がよくわかりません(ぉ