zshからanything.elを利用してシームレスな履歴検索を
冬のはずなのにまだまだ薄着で過ごしています。というかセーターとかマフラーとか持ってないし。
道行く人達は冬の装いが多いですが、私は寒さに対してはやっぱり鈍感なようです。暖房代がかからないのは助かります(違
さて、つい先日、id:rubikitch さんが「anything.elを使ってzshの履歴検索をする - http://rubikitch.com/に移転しました」にて、anything.elをフル活用してzshの履歴検索を行う方法を提示されていました。
なんか参考として私のはてダのエントリーが貼られていますが、完成度とか速度は比べものになりませんです。はい。
しかし私個人としては、ターミナルにいるのに毎回Emacsのフレームが出てくるのもあれかなぁ、と思いまして、なんとかzshの中だけで完結できないものかなぁ、となんとかやってみました。
色々試してみたところ、anything.elの基盤を利用することで、かなり満足のいく動作をするようになりました。ので、ログ代わりに貼り付けておきます。elispの関数名とか見ればよくわかると思いますが、↑の記事をがっつり参考にしています。
ちなみに私のEmacsは
(emacs-version) → "GNU Emacs 23.1.50.1 (i686-pc-linux-gnu, GTK+ Version 2.14.7)"
という感じです。
以下ソースと使い方です。
Emacs lisp
まずはelisp側の準備です。anything.elが前提なので、インストールしていない方は是非インストールしましょう
M-x install-elisp-from-emacswiki anything.el
M-x install-elisp-from-emacswiki anything-match-plugin.el
M-x install-elisp-from-emacswiki anything-complete.el
M-x install-elisp-from-emacswiki shell-history.el
↑これはanything-complete-shell-historyで使用しますので、インストールしておきます。
で、以下を.emacs.elとかに貼り付けます。色々作法とか間違っている気がしないでもないですが、気にしない。
;; zshからanythingを利用するための設定。 (require 'anything-complete) (defvar azhzle/history-list nil) (defvar azhzle/cache nil) (defvar azhzle/tmp-file "/tmp/.azh-tmp-file") (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))) (anything-initialize) (unless azhzle/history-list (setq azhzle/history-list (azhzle/get-history-list))) (setq azhzle/cache azhzle/history-list)) ) (defun azhzle/input (str) (interactive) (let* ((anything-pattern str) (lists (anything-compute-matches `(,@anything-c-source-complete-shell-history ,(cons 'candidates 'azhzle/cache))))) (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 (car line)) "\n") do (message (car line))) (set-buffer-file-coding-system 'utf-8-unix t) (write-region (point-min) (point-max) azhzle/tmp-file)) (setq azhzle/cache lists) )) (defun azhzle/get-history-list () (let ((lists '())) (with-current-buffer (shell-history-buffer) (loop for i from 1 upto (1- (count-lines (point-min) (point-max))) initially (goto-char (point-min)) initially (setq zsh-p t) do (add-to-list 'lists (list (acsh-get-line (line-beginning-position) (line-end-position)))) do (next-line))) lists )) (defun azhzle/dicision-history (history-num) (interactive) (with-temp-buffer (erase-buffer) (insert (substring-no-properties (car (nth (- history-num 1) azhzle/cache)))) (write-region (point-min) (point-max) azhzle/tmp-file) )) (defun azhzle/reset-history () (setq azhzle/history-list (azhzle/get-history-list)) (setq azhzle/cache azhzle/history-list))
zshrc
そして上だけじゃなく、.zshrcに以下のコードを貼り付けてみてください。
## 各固定値 typeset -A HISTORY_DICISION_KEYS set -A HISTORY_DICISION_KEYS A 1 S 2 D 3 F 4 G 5 H 6 J 7 K 8 L 9 Q 10 \ W 11 E 12 R 13 T 14 Y 15 U 16 I 17 O 18 P 19 Z 20 X 21 C 22 V 23 B 24 N 25 M 26 ISHR_MENU_LENGTH=26 ISHR_FILENAME="/tmp/.azh-tmp-file" function ishr-search-history-from-anything() { emulate -L zsh local key=$1 # 特定キーが押されたら、該当する位置の履歴をバッファに表示する。 if [[ -n "${HISTORY_DICISION_KEYS[$key]}" && -n "$BUFFER" ]]; then zle -A .self-insert self-insert emacsclient --eval "(azhzle/dicision-history ${HISTORY_DICISION_KEYS[$key]})" BUFFER=`cat $ISHR_FILENAME` zle -R -c zle accept-line return 1 fi return 0 } function ishr-update-status() { emacsclient --eval "(azhzle/input \"$BUFFER\")" &> /dev/null zle -M "`cat $ISHR_FILENAME`" zle -R } function ishr-self-insert() { emulate -L zsh LBUFFER+=${KEYS[-1]} ishr-search-history-from-anything ${KEYS[-1]} (( ! $? )) && ishr-update-status } function ishr-backward-delete-char() { emulate -L zsh zle .backward-delete-char ishr-update-status } function ishr-reset-history() { emacsclient --eval "(azhzle/reset-history)" ishr-update-status } zle -N incremental-search-history-menu function incremental-search-history-menu() { # インクリメンタル履歴検索を行えるように準備等を行う。 emulate -L zsh integer stat # 各種必要な変数の初期化。 ishr-init-variables emacsclient --eval "(anything-zsh-history-from-zle-init)" &> /dev/null emacsclient --eval "(azhzle/input \"$BUFFER\")" &> /dev/null zle -N self-insert ishr-self-insert zle -N backward-delete-char ishr-backward-delete-char zle recursive-edit stat=$? zle -A .self-insert self-insert zle -A .backward-delete-char backward-delete-char rm -f $ISHR_FILENAME zle -R -c (( stat )) && zle send-break return $? } bindkey "^[@" incremental-search-history-menu
bindkey はお好みでどうぞ。本格的に利用するなら ^R とかでもよろしいかと思いますが、色々制限とかありますので気をつけてください。
履歴の選択は、各履歴の左端にA: 〜とかS: 〜 とか出ますので、対応した大文字を入力すれば履歴が登録されます。
で、実際の動作速度ですが、emacsclient の起動とか実行とかがやたらと早いため、最初(履歴をリストにします)以外は、zshオンリーでやっていたときとは比較にならないくらいサクサクです。
大体 400K 弱の履歴ファイルを作成して試してみましたが、それでもかなりサクサクいけるようです。
以下制限です。
anything-compute-matches を利用しているため、スペース区切りで複数単語での検索とかもバッチリいけます。
でも多分環境依存とかは酷いような気がしますので(ぉぃ)、もし試された方で「動かんぞぉ!」という方がいらっしゃいましたら連絡下さい。