履歴を絞りこみながら正規表現で検索する、その2

やる気とかそんなもの以前に眠気が取れないです。朝の電車が後2時間くらい長くなってもいいと思う。

前回、zshの履歴をインクリメンタルに正規表現で検索するzleを作成してみました。
そしたら↓で紹介してもらっていました。ありがとうございます。

「履歴を絞りこみながら正規表現で検索する。」を使ってみた - http://rubikitch.com/に移転しました

さて、この紹介を見て、改善点が挙げられています。

しかし、キーを押すたびに反応してしまうため重すぎるのが唯一の問題である。重さを解消する方法としては以下が考えられる。

  • キーを押して0.3秒間応答がなければ検索処理を開始する(anything方式)
  • 前回の検索処理の結果から絞り込む(QuickSilver方式)
  • .zsh_historyを高速に履歴検索する専用プログラムを作成し、パイプとかで通信する(コマンド起動がオーバーヘッドの場合)

確かに以前のものは、殆どコンセプト実装みたいなものでした。参考にさせていただいたスクリプトも、インクリメンタル検索を意識していないものでした。
まぁ私の.zsh_historyは、1000行も無い(何度かPCを強制再起動せざるを得なくなった時に何故か.zsh_historyがリセットorz)ため、あまり速度には頓着しておりませんでした。
確かに普通にzshを使っている人は、10000行とかは普通にありますよね。100000とか1000000とか。

なので、とりあえず上で挙げられた改善すべき点を何個か実装してみることにしました。
ちなみにこの変更の7割くらいは仕事中にこっそりやりました。だって同じことばっかりやってると飽きてしまいますから。

前回からの変更点としては、
・前回の検索クエリが今回のクエリ内に含まれる場合には、前回の検索結果から検索するように。
・同一のセッション内で同じクエリが利用されたら、一回目以外は、検索結果を使い回す。
・二文字目の入力から検索を開始する。(変更可能)

zleだけでは非同期の検索は辛そうでした。ので、とりあえずの案として、二文字目から検索するようにしてみました。

前回よりは速度的になんとかなるような感じです、がまだまだだと思いますので、もうちょっと練りたいと思います。こうした方がいいんじゃね?みたいなのがありましたら是非教えていただければと思います。

実際のソースは以下のような感じです。やたら長いです。数えたら126とかありました。もうちょっと何とかせな。

typeset -A HISTORY_INCREMENTAL_KEYS
set -A HISTORY_INCREMENTAL_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
ISHR_PREVIOUS_QUERY=""
typeset -A ISHR_PREVIOUS_RESULTS
ISHR_BASEFILE=/tmp/ishr.tmp
ISHR_MENU_LENGTH=19

# キー入力の前後で実行する関数の設定。
ISHR_JUDGE_SEARCH_HOOK=(ishr-judge-query-length)

# 検索結果を表示しはじめる検索を開始するバッファの文字列長。
ISHR_MIN_SEARCH_LENGTH=2

function ishr-get-target-history() {
    local query=$1
    local target_file=""

    (( $+ISHR_PREVIOUS_RESULTS[(e)$query] )) && target_file=$ISHR_PREVIOUS_RESULTS[$query]

    if [ -z "$target_file" ]; then
        if [ -n "$query" ]; then
            # クエリに対するファイルを割り振る。
            ISHR_PREVIOUS_RESULTS+=($query ${ISHR_BASEFILE}.`ls ${ISHR_BASEFILE}* | wc -l`)
            echo "$query $ISHR_PREVIOUS_RESULTS" >> test.txt

            if [ -n "$ISHR_PREVIOUS_QUERY" -a `echo "$query" | grep -c "$ISHR_PREVIOUS_QUERY"` -gt 0 ]; then
                # 前回候補が今回のqueryに含まれていたら、前回の結果から絞り込む。
                egrep -i "$query" $ISHR_PREVIOUS_RESULTS[$ISHR_PREVIOUS_QUERY] | uniq > $ISHR_PREVIOUS_RESULTS[$query]
            else
                # 前回候補と異なる場合、または初回検索の場合は、初回取得した全履歴から検索する。
                egrep -i "$query" $ISHR_BASEFILE | uniq | tac > $ISHR_PREVIOUS_RESULTS[$query]
            fi
            target_file=${ISHR_PREVIOUS_RESULTS[$query]}
        else
            target_file=${ISHR_BASEFILE}.`ls ${ISHR_BASEFILE}* | wc -l`
            tail -$ISHR_MENU_LENGTH $ISHR_BASEFILE | tac > $target_file
        fi
    fi

    zle -M  "`ruby -e '%w[A S D F G H J K L Q W E R T Y U I O P ].zip(open(ARGV[0]).readlines){|k,l| print %[#{k}: #{l}]}' =(tail -$ISHR_MENU_LENGTH $target_file)`"
    zle -R

}

function incremental-search-history-regexp() {
    local query=$1
    local key=$2

    ishr-enable-search $query
    if [ $? -eq 1 ]; then
    # 特定キーが押されたら、該当する位置の履歴をバッファに表示する。
        if [ -n "${HISTORY_INCREMENTAL_KEYS[$key]}" ]; then
            zle -A .self-insert self-insert
            local fname=$ISHR_PREVIOUS_RESULTS[$ISHR_PREVIOUS_QUERY]
            BUFFER="`head -${HISTORY_INCREMENTAL_KEYS[$key]} $fname | tail -1 | perl -pe 's/\\\\n/\\021\\n/g'`"
            zle -R
            zle accept-line
        else
            ishr-get-target-history $query
        fi
        ISHR_PREVIOUS_QUERY=$query
    fi
}

function ishr-enable-search() {
    local query=$1

    for i in $ISHR_JUDGE_SEARCH_HOOK; do
        case "`$i $query`" in
            "enable")
                return 1
                ;;
        esac
    done
    return 0
}

ishr-judge-query-length() {
    [[ ${#1} -ge $ISHR_MIN_SEARCH_LENGTH ]] && echo "enable" || echo "disable"
}

function ishr-self-insert() {
    LBUFFER+=${KEYS[-1]}
    incremental-search-history-regexp $BUFFER ${KEYS[-1]}
}

function ishr-backward-delete-char() {
    zle -A .backward-delete-char backward-delete-char
    zle backward-delete-char
    zle -N backward-delete-char ishr-backward-delete-char
    incremental-search-history-regexp $BUFFER
}

function incremental-search-history-menu() {
    # インクリメンタル履歴検索を行えるように準備等を行う。
    integer stat

    # 初回限定で、履歴全体をuniq化して保持しておく。
    history -n 1 | uniq > $ISHR_BASEFILE

    # 各固有変数のリセット
    typeset -A ISHR_PREVIOUS_RESULTS
    ISHR_PREVIOUS_RESULTS=()
    ISHR_PREVIOUS_QUERY=""

    # 正規表現によるインクリメンタル検索を行う。

    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

    zle -R -c

    rm ${ISHR_BASEFILE}*
    (( stat )) && zle send-break

    return $?
}

zle -N incremental-search-history-menu
bindkey "^[@" incremental-search-history-menu