sistema_progs

Programas para customizar o meu entorno de traballo nos meus equipos persoais
Log | Files | Refs

history.sh (80669B)


      1 #!/bin/bash
      2 
      3 ## @bleopt history_limit_length
      4 ##   履歴に登録するコマンドの最大文字数を指定します。
      5 ##   この値を超える長さのコマンドは履歴に登録されません。
      6 bleopt/declare -v history_limit_length 10000
      7 
      8 #==============================================================================
      9 # ble/history:bash                                                @history.bash
     10 
     11 ## @arr _ble_history
     12 ##   コマンド履歴項目を保持する。
     13 ##
     14 ## @arr _ble_history_edit
     15 ## @arr _ble_history_dirt
     16 ##   _ble_history_edit 編集されたコマンド履歴項目を保持する。
     17 ##   _ble_history の各項目と対応し、必ず同じ数・添字の要素を持つ。
     18 ##   _ble_history_dirt は編集されたかどうかを保持する。
     19 ##   _ble_history の各項目と対応し、変更のあったい要素にのみ値 1 を持つ。
     20 ##
     21 ## @var _ble_history_index
     22 ##   現在の履歴項目の番号
     23 ##
     24 _ble_history=()
     25 _ble_history_edit=()
     26 _ble_history_dirt=()
     27 _ble_history_index=0
     28 
     29 ## @var _ble_history_count
     30 ##   現在の履歴項目の総数
     31 ##
     32 ## これらの変数はコマンド履歴を対象としているときにのみ用いる。
     33 ##
     34 _ble_history_count=
     35 
     36 function ble/builtin/history/is-empty {
     37   # Note #D1629: 以前の実装 (#D1120) では ! builtin history -p '!!' を使ってい
     38   #   たが、状況によって history -p で履歴項目が減少するのでサブシェルの中で評
     39   #   価する必要がある。サブシェルの中に既にいる時にはこの fork は省略できると
     40   #   考えていたが、サブシェルの中にいる場合でも後で履歴を使う為に履歴項目が変
     41   #   化すると困るので、結局この手法だと常にサブシェルを起動する必要がある。代
     42   #   わりに history 1 の出力を確認する実装に変更する事にした。
     43   ! ble/util/assign.has-output 'builtin history 1'
     44 }
     45 
     46 ## @fn ble/builtin/history/.check-timestamp-sigsegv status
     47 ##   #D1831: Bash 4.4 以下では履歴ファイル (HISTFILE) に不正な timestamp
     48 ##   (0x7FFFFFFF+1900年より後を指す巨大な unix time) が含まれていると segfault
     49 ##   する。実際に SIGSEGV で終了した時に履歴ファイルを確認して問題の行番号を出
     50 ##   力する。
     51 if ((_ble_bash>=50000)); then
     52   function ble/builtin/history/.check-timestamp-sigsegv { :; }
     53 else
     54   function ble/builtin/history/.check-timestamp-sigsegv {
     55     local stat=$1
     56     ((stat)) || return 0
     57 
     58     local ret=11
     59     ble/builtin/trap/sig#resolve SIGSEGV
     60     ((stat==128+ret)) || return 0
     61 
     62     local msg="bash: SIGSEGV: suspicious timestamp in HISTFILE"
     63 
     64     local histfile=${HISTFILE-}
     65     if [[ -s $histfile ]]; then
     66       msg="$msg='$histfile'"
     67       local rex_broken_timestamp='^#[0-9]\{12\}'
     68       ble/util/assign line 'ble/bin/grep -an "$rex_broken_timestamp" "$histfile"'
     69       ble/string#split line : "$line"
     70       [[ $line =~ ^[0-9]+$ ]] && msg="$msg (line $line)"
     71     fi
     72 
     73     if [[ ${_ble_edit_io_fname2-} ]]; then
     74       ble/util/print $'\n'"$msg" >> "$_ble_edit_io_fname2"
     75     else
     76       ble/util/print "$msg" >&2
     77     fi
     78   }
     79 fi
     80 
     81 ## @fn ble/builtin/history/.dump args...
     82 ##   #D1831: timestamp に不正な値が含まれていた時のメッセージを検出する為、一時
     83 ##   的に LC_MESSAGES を設定して builtin history を呼び出します。更にこの状況で、
     84 ##   bash-3.2 以下で無限ループになる問題を回避する為に、bash-3.2 以下では
     85 ##   conditional-sync 経由で呼び出します。
     86 if ((_ble_bash<40000)); then
     87   # Note (#D1831): bash-3.2 以下では不正な timestamp が history に含まれている
     88   #   と無限ループになるので timeout=3000 で強制終了する。然し、実際に確認して
     89   #   みると、conditional-sync 経由で呼び出した時には無限ループにならずに
     90   #   timeout する前に SIGSEGV になる様である
     91   function ble/builtin/history/.dump.proc {
     92     local LC_ALL= LC_MESSAGES=C 2>/dev/null
     93     builtin history "${args[@]}"
     94     ble/util/unlocal LC_ALL LC_MESSAGES 2>/dev/null
     95   }
     96   function ble/builtin/history/.dump {
     97     local -a args; args=("$@")
     98     ble/util/conditional-sync \
     99       ble/builtin/history/.dump.proc \
    100       true 100 progressive-weight:timeout=3000:SIGKILL
    101     local ext=$?
    102     if ((ext==142)); then
    103       printf 'ble.sh: timeout: builtin history %s' "$*" >&"$_ble_util_fd_stderr"
    104       local ret=11
    105       ble/builtin/trap/sig#resolve SIGSEGV
    106       ((ext=128+ret))
    107     fi
    108     ble/builtin/history/.check-timestamp-sigsegv "$ext"
    109     return "$ext"
    110   }
    111 else
    112   function ble/builtin/history/.dump {
    113     local LC_ALL= LC_MESSAGES=C 2>/dev/null
    114     builtin history "$@"
    115     ble/util/unlocal LC_ALL LC_MESSAGES 2>/dev/null
    116   }
    117 fi
    118 
    119 if ((_ble_bash<40000)); then
    120   function ble/builtin/history/.get-min {
    121     ble/util/assign-words min 'ble/builtin/history/.dump | head -1'
    122     min=${min/'*'}
    123   }
    124 else
    125   function ble/builtin/history/.get-min {
    126     ble/util/assign-words min 'builtin history | head -1'
    127     min=${min/'*'}
    128   }
    129 fi
    130 function ble/builtin/history/.get-max {
    131   ble/util/assign-words max 'builtin history 1'
    132   max=${max/'*'}
    133 }
    134 
    135 #------------------------------------------------------------------------------
    136 # initialize _ble_history                                    @history.bash.load
    137 
    138 ## @var _ble_history_load_done
    139 _ble_history_load_done=
    140 
    141 # @hook history_reset_background (defined in def.sh)
    142 function ble/history:bash/clear-background-load {
    143   blehook/invoke history_reset_background
    144 }
    145 
    146 ## @fn ble/history:bash/load
    147 if ((_ble_bash>=40000)); then
    148   # _ble_bash>=40000 で利用できる以下の機能に依存する
    149   #   ble/util/is-stdin-ready (via ble/util/idle/IS_IDLE)
    150   #   ble/util/mapfile
    151 
    152   _ble_history_load_resume=0
    153   _ble_history_load_bgpid=
    154 
    155   # history > tmp
    156   ## @fn ble/history:bash/load/.background-initialize
    157   ##   @var[in] arg_count
    158   ##   @var[in] load_strategy
    159   function ble/history:bash/load/.background-initialize {
    160     if ble/builtin/history/is-empty; then
    161       # Note: rcfile から呼び出すと history が未ロードなのでロードする。
    162       #
    163       # Note: 当初は親プロセスで history -n にした方が二度手間にならず効率的と考えたが
    164       #   以下の様な問題が生じたので、やはりサブシェルの中で history -n する事にした。
    165       #
    166       #   問題1: bashrc の謎の遅延 (memo.txt#D0702)
    167       #     shopt -s histappend の状態で親シェルで history -n を呼び出すと、
    168       #     bashrc を抜けてから Bash 本体によるプロンプトが表示されて、
    169       #     入力を受け付けられる様になる迄に、謎の遅延が発生する。
    170       #     特に履歴項目の数が HISTSIZE の丁度半分より多い時に起こる様である。
    171       #
    172       #     history -n を呼び出す瞬間だけ shopt -u histappend して
    173       #     直後に shopt -s histappend とすると、遅延は解消するが、
    174       #     実際の動作を観察すると histappend が無効になってしまっている。
    175       #
    176       #     対策として、一時的に HISTSIZE を大きくして bashrc を抜けて、
    177       #     最初のユーザからの入力の時に HISTSIZE を復元する事にした。
    178       #     これで遅延は解消できる様である。
    179       #
    180       #   問題2: 履歴の数が倍加する問題 (memo.txt#D0732)
    181       #     親シェルで history -n を実行すると、
    182       #     shopt -s histappend の状態だと履歴項目の数が2倍になってしまう。
    183       #     bashrc を抜ける直前から最初にユーザの入力を受けるまでに倍加する。
    184       #     bashrc から抜けた後に Readline が独自に履歴を読み取るのだろう。
    185       #     一方で shopt -u histappend の状態だとシェルが動作している内は問題ないが、
    186       #     シェルを終了した時に2倍に .bash_history の内容が倍になってしまう。
    187       #
    188       #     これの解決方法は不明。(HISTFILE 等を弄ったりすれば可能かもれないが試していない)
    189       #
    190       builtin history -n
    191     fi
    192     local HISTTIMEFORMAT=__ble_ext__
    193     local -x INDEX_FILE=$history_indfile
    194 
    195     local -x opt_source= opt_null=
    196     if [[ $load_strategy == source ]]; then
    197       opt_source=1
    198     elif [[ $load_strategy == mapfile ]]; then
    199       opt_null=1
    200     fi
    201 
    202     # from ble/util/writearray
    203     if [[ ! $_ble_util_writearray_rawbytes ]]; then
    204       local IFS=$_ble_term_IFS __ble_tmp; __ble_tmp=('\'{2,3}{0..7}{0..7})
    205       builtin eval "local _ble_util_writearray_rawbytes=\$'${__ble_tmp[*]}'"
    206     fi
    207     local -x __ble_rawbytes=$_ble_util_writearray_rawbytes # used by _ble_bin_awk_libES
    208     local -x fname_stderr=${_ble_edit_io_fname2:-}
    209 
    210     local apos=\'
    211     # 482ms for 37002 entries
    212     ble/builtin/history/.dump ${arg_count:+"$arg_count"} | ble/bin/awk -v apos="$apos" -v arg_offset="$arg_offset" -v _ble_bash="$_ble_bash" '
    213       '"$_ble_bin_awk_libES"'
    214 
    215       BEGIN {
    216         es_initialize();
    217 
    218         INDEX_FILE = ENVIRON["INDEX_FILE"];
    219         opt_null = ENVIRON["opt_null"];
    220         opt_source = ENVIRON["opt_source"];
    221         if (!opt_null && !opt_source)
    222           printf("") > INDEX_FILE; # create file
    223 
    224         fname_stderr = ENVIRON["fname_stderr"];
    225         fname_stderr_count = 0;
    226 
    227         n = 0;
    228         hindex = arg_offset;
    229       }
    230 
    231       function flush_line() {
    232         if (n < 1) return;
    233 
    234         if (opt_null) {
    235           if (t ~ /^eval -- \$'"$apos"'([^'"$apos"'\\]|\\.)*'"$apos"'$/)
    236             t = es_unescape(substr(t, 11, length(t) - 11));
    237           printf("%s%c", t, 0);
    238 
    239         } else if (opt_source) {
    240           if (t ~ /^eval -- \$'"$apos"'([^'"$apos"'\\]|\\.)*'"$apos"'$/)
    241             t = es_unescape(substr(t, 11, length(t) - 11));
    242           gsub(/'"$apos"'/, "'"$apos"'\\'"$apos$apos"'", t);
    243           print "_ble_history[" hindex "]=" apos t apos;
    244 
    245         } else {
    246           if (n == 1) {
    247             if (t ~ /^eval -- \$'"$apos"'([^'"$apos"'\\]|\\.)*'"$apos"'$/)
    248               print hindex > INDEX_FILE;
    249           } else {
    250             gsub(/['"$apos"'\\]/, "\\\\&", t);
    251             gsub(/\n/, "\\n", t);
    252             print hindex > INDEX_FILE;
    253             t = "eval -- $" apos t apos;
    254           }
    255           print t;
    256         }
    257 
    258         hindex++;
    259         n = 0;
    260         t = "";
    261       }
    262 
    263       function check_invalid_timestamp(line) {
    264         if (line ~ /^ *[0-9]+\*? +.+: invalid timestamp/ && fname_stderr != "") {
    265           sub(/^ *0*/, "bash: history !", line);
    266           sub(/: invalid timestamp.*$/, ": invalid timestamp", line);
    267           if (fname_stderr_count++ == 0)
    268             print "" >> fname_stderr;
    269           print line >> fname_stderr;
    270         }
    271       }
    272 
    273       {
    274         # Note: In Bash 5.0+, the error message of "invalid timestamp"
    275         # goes into "stdout" instead of "stderr".
    276         check_invalid_timestamp($0);
    277         if (sub(/^ *[0-9]+\*? +(__ble_ext__|\?\?|.+: invalid timestamp)/, "", $0))
    278           flush_line();
    279         t = ++n == 1 ? $0 : t "\n" $0;
    280       }
    281 
    282       END { flush_line(); }
    283     ' >| "$history_tmpfile.part"
    284     ble/builtin/history/.check-timestamp-sigsegv "${PIPESTATUS[0]}"
    285     ble/bin/mv -f "$history_tmpfile.part" "$history_tmpfile"
    286   }
    287 
    288   ## @fn ble/history:bash/load opts
    289   ##   @param[in] opts
    290   ##     async
    291   ##       非同期で読み取ります。
    292   ##     append
    293   ##       現在読み込み済みの履歴情報に追加します。
    294   ##     count=NUMBER
    295   ##       最近の NUMBER 項目だけ読み取ります。
    296   function ble/history:bash/load {
    297     local opts=$1
    298     local opt_async=; [[ :$opts: == *:async:* ]] && opt_async=1
    299 
    300     local load_strategy=mapfile
    301     if [[ $OSTYPE == cygwin* || $OSTYPE == msys* ]]; then
    302       load_strategy=source
    303     elif ((_ble_bash<50200)); then
    304       load_strategy=nlfix
    305     fi
    306 
    307     local arg_count= arg_offset=0
    308     [[ :$opts: == *:append:* ]] &&
    309       arg_offset=${#_ble_history[@]}
    310     local rex=':count=([0-9]+):'; [[ :$opts: =~ $rex ]] && arg_count=${BASH_REMATCH[1]}
    311 
    312     local history_tmpfile=$_ble_base_run/$$.history.load
    313     local history_indfile=$_ble_base_run/$$.history.multiline-index
    314     [[ $opt_async || :$opts: == *:init:* ]] || _ble_history_load_resume=0
    315 
    316     [[ ! $opt_async ]] && ((_ble_history_load_resume<6)) &&
    317       blehook/invoke history_message "loading history ..."
    318     while :; do
    319       case $_ble_history_load_resume in
    320 
    321       # 42ms 履歴の読み込み
    322       (0) # 履歴ファイル生成を Background で開始
    323           if [[ $_ble_history_load_bgpid ]]; then
    324             builtin kill -9 "$_ble_history_load_bgpid" &>/dev/null
    325             _ble_history_load_bgpid=
    326           fi
    327 
    328           : >| "$history_tmpfile"
    329           if [[ $opt_async ]]; then
    330             _ble_history_load_bgpid=$(ble/util/nohup 'ble/history:bash/load/.background-initialize' print-bgpid)
    331 
    332             function ble/history:bash/load/.background-initialize-completed {
    333               local history_tmpfile=$_ble_base_run/$$.history.load
    334               [[ -s $history_tmpfile ]] || ! builtin kill -0 "$_ble_history_load_bgpid"
    335             } &>/dev/null
    336 
    337             ((_ble_history_load_resume++))
    338           else
    339             ble/history:bash/load/.background-initialize
    340             ((_ble_history_load_resume+=3))
    341           fi ;;
    342 
    343       # 515ms ble/history:bash/load/.background-initialize 待機
    344       (1) if [[ $opt_async ]] && ble/util/is-running-in-idle; then
    345             ble/util/idle.wait-condition ble/history:bash/load/.background-initialize-completed
    346             ((_ble_history_load_resume++))
    347             return 147
    348           fi
    349           ((_ble_history_load_resume++)) ;;
    350 
    351       # Note: async でバックグラウンドプロセスを起動した後に、直接 (sync で)
    352       #   呼び出された時、未だ処理が完了していなくても次のステップに進んでしまうので、
    353       #   此処で条件が満たされるのを待つ (#D0745)
    354       (2) while ! ble/history:bash/load/.background-initialize-completed; do
    355             ble/util/msleep 50
    356             [[ $opt_async ]] && ! ble/util/idle/IS_IDLE && return 148
    357           done
    358           ((_ble_history_load_resume++)) ;;
    359 
    360       # 47ms _ble_history 初期化 (37000項目)
    361       (3) _ble_history_load_bgpid=
    362           ((arg_offset==0)) && _ble_history=()
    363           if [[ $load_strategy == source ]]; then
    364             # Cygwin #D0701 #D1605
    365             #   620ms 99000項目 @ #D0701
    366             source "$history_tmpfile"
    367           elif [[ $load_strategy == nlfix ]]; then
    368             builtin mapfile -O "$arg_offset" -t _ble_history < "$history_tmpfile"
    369           else
    370             builtin mapfile -O "$arg_offset" -t -d '' _ble_history < "$history_tmpfile"
    371           fi
    372           ble/builtin/history/erasedups/update-base
    373           ((_ble_history_load_resume++)) ;;
    374 
    375       # 47ms _ble_history_edit 初期化 (37000項目)
    376       (4) ((arg_offset==0)) && _ble_history_edit=()
    377           if [[ $load_strategy == source ]]; then
    378             # 504ms Cygwin (99000項目)
    379             _ble_history_edit=("${_ble_history[@]}")
    380           elif [[ $load_strategy == nlfix ]]; then
    381             builtin mapfile -O "$arg_offset" -t _ble_history_edit < "$history_tmpfile"
    382           else
    383             builtin mapfile -O "$arg_offset" -t -d '' _ble_history_edit < "$history_tmpfile"
    384           fi
    385           : >| "$history_tmpfile"
    386 
    387           if [[ $load_strategy != nlfix ]]; then
    388             ((_ble_history_load_resume+=3))
    389             continue
    390           else
    391             ((_ble_history_load_resume++))
    392           fi ;;
    393 
    394       # 11ms 複数行履歴修正 (107/37000項目)
    395       (5) local -a indices_to_fix
    396           ble/util/mapfile indices_to_fix < "$history_indfile"
    397           local i rex='^eval -- \$'\''([^\'\'']|\\.)*'\''$'
    398           for i in "${indices_to_fix[@]}"; do
    399             [[ ${_ble_history[i]} =~ $rex ]] &&
    400               builtin eval "_ble_history[i]=${_ble_history[i]:8}"
    401           done
    402           ((_ble_history_load_resume++)) ;;
    403 
    404       # 11ms 複数行履歴修正 (107/37000項目)
    405       (6) local -a indices_to_fix
    406           [[ ${indices_to_fix+set} ]] ||
    407             ble/util/mapfile indices_to_fix < "$history_indfile"
    408           local i
    409           for i in "${indices_to_fix[@]}"; do
    410             [[ ${_ble_history_edit[i]} =~ $rex ]] &&
    411               builtin eval "_ble_history_edit[i]=${_ble_history_edit[i]:8}"
    412           done
    413           ((_ble_history_load_resume++)) ;;
    414 
    415       (7) [[ $opt_async ]] || blehook/invoke history_message
    416           ((_ble_history_load_resume++))
    417           return 0 ;;
    418 
    419       (*) return 1 ;;
    420       esac
    421 
    422       [[ $opt_async ]] && ! ble/util/idle/IS_IDLE && return 148
    423     done
    424   }
    425   blehook history_reset_background!=_ble_history_load_resume=0
    426 else
    427   function ble/history:bash/load/.generate-source {
    428     if ble/builtin/history/is-empty; then
    429       # rcfile として起動すると history が未だロードされていない。
    430       builtin history -n
    431     fi
    432     local HISTTIMEFORMAT=__ble_ext__
    433 
    434     # 285ms for 16437 entries
    435     local apos="'"
    436     ble/builtin/history/.dump ${arg_count:+"$arg_count"} | ble/bin/awk -v apos="'" '
    437       BEGIN { n = ""; }
    438 
    439 #%    # 何故かタイムスタンプがコマンドとして読み込まれてしまう
    440       /^ *[0-9]+\*? +(__ble_ext__|\?\?)#[0-9]/ { next; }
    441 
    442 #%    # ※rcfile として読み込むと HISTTIMEFORMAT が ?? に化ける。
    443       /^ *[0-9]+\*? +(__ble_ext__|\?\?|.+: invalid timestamp)/ {
    444         if (n != "") {
    445           n = "";
    446           print "  " apos t apos;
    447         }
    448 
    449         n = $1; t = "";
    450         sub(/^ *[0-9]+\*? +(__ble_ext__|\?\?|.+: invalid timestamp)/, "", $0);
    451       }
    452       {
    453         line = $0;
    454         if (line ~ /^eval -- \$'"$apos"'([^'"$apos"'\\]|\\.)*'"$apos"'$/)
    455           line = apos substr(line, 9) apos;
    456         else
    457           gsub(apos, apos "\\" apos apos, line);
    458 
    459 #%      # 対策 #D1241: bash-3.2 以前では ^A, ^? が ^A^A, ^A^? に化ける
    460         gsub(/\001/, "'"$apos"'${_ble_term_SOH}'"$apos"'", line);
    461         gsub(/\177/, "'"$apos"'${_ble_term_DEL}'"$apos"'", line);
    462 
    463 #%      # 対策 #D1270: MSYS2 で ^M を代入すると消える
    464         gsub(/\015/, "'"$apos"'${_ble_term_CR}'"$apos"'", line);
    465 
    466         t = t != "" ? t "\n" line : line;
    467       }
    468       END {
    469         if (n != "") {
    470           n = "";
    471           print "  " apos t apos;
    472         }
    473       }
    474     '
    475   }
    476 
    477   function ble/history:bash/load {
    478     local opts=$1
    479     local opt_append=
    480     [[ :$opts: == *:append:* ]] && opt_append=1
    481 
    482     local arg_count= rex=':count=([0-9]+):'
    483     [[ :$opts: =~ $rex ]] && arg_count=${BASH_REMATCH[1]}
    484 
    485     blehook/invoke history_message "loading history..."
    486 
    487     # * プロセス置換にしてもファイルに書き出しても大した違いはない。
    488     #   270ms for 16437 entries (generate-source の時間は除く)
    489     # * プロセス置換×source は bash-3 で動かない。eval に変更する。
    490     local result=$(ble/history:bash/load/.generate-source)
    491     local IFS=$_ble_term_IFS
    492     if [[ $opt_append ]]; then
    493       if ((_ble_bash>=30100)); then
    494         builtin eval -- "_ble_history+=($result)"
    495         builtin eval -- "_ble_history_edit+=($result)"
    496       else
    497         local -a A; builtin eval -- "A=($result)"
    498         _ble_history=("${_ble_history[@]}" "${A[@]}")
    499         _ble_history_edit=("${_ble_history[@]}" "${A[@]}")
    500       fi
    501     else
    502       builtin eval -- "_ble_history=($result)"
    503       _ble_history_edit=("${_ble_history[@]}")
    504     fi
    505     ble/builtin/history/erasedups/update-base
    506     ble/util/unlocal IFS
    507 
    508     blehook/invoke history_message
    509   }
    510 fi
    511 
    512 function ble/history:bash/initialize {
    513   [[ $_ble_history_load_done ]] && return 0
    514   ble/history:bash/load "init:$@"; local ext=$?
    515   ((ext)) && return "$ext"
    516 
    517   local old_count=$_ble_history_count new_count=${#_ble_history[@]}
    518   _ble_history_load_done=1
    519   _ble_history_count=$new_count
    520   _ble_history_index=$_ble_history_count
    521   ble/history/.update-position
    522 
    523   # Note: 追加読み込みをした際に対応するデータを shift (history_share)
    524   local delta=$((new_count-old_count))
    525   ((delta>0)) && blehook/invoke history_change insert "$old_count" "$delta"
    526 }
    527 
    528 #------------------------------------------------------------------------------
    529 # Bash history resolve-multiline                            @history.bash.mlfix
    530 
    531 if ((_ble_bash>=30100)); then
    532   # Note: Bash 3.0 では history -s がまともに動かないので
    533   # 複数行の履歴項目を builtin history に追加する方法が今の所不明である。
    534 
    535   _ble_history_mlfix_done=
    536   _ble_history_mlfix_resume=0
    537   _ble_history_mlfix_bgpid=
    538 
    539   ## @fn ble/history:bash/resolve-multiline/.awk reason
    540   ##
    541   ##   @param[in] reason
    542   ##     呼び出しの用途を指定する文字列です。
    543   ##
    544   ##     resolve ... 初期化時の history 再構築
    545   ##       history コマンドの出力形式で標準入力を解析します。
    546   ##       各行は '番号 HISTTIMEFORMATコマンド' の形式をしている。
    547   ##
    548   ##     read    ... history -r によるファイルからの読み出し
    549   ##       履歴ファイルの形式で標準入力を解析します。
    550   ##       各行は '#%s' または 'コマンド' の形式をしている。
    551   ##       ble.sh では先頭行が '#%s' の時の複数行モードには対応しない。
    552   ##
    553   ##   @var[in] tmpfile_base
    554   function ble/history:bash/resolve-multiline/.awk {
    555     if ((_ble_bash>=50000)); then
    556       local -x epoch=$EPOCHSECONDS
    557     elif ((_ble_bash>=40400)); then
    558       local -x epoch
    559       ble/util/strftime -v epoch %s
    560     fi
    561 
    562     local -x reason=$1
    563     local apos=\'
    564     ble/bin/awk -v apos="$apos" -v _ble_bash="$_ble_bash" '
    565       BEGIN {
    566         q = apos;
    567         Q = apos "\\" apos apos;
    568         reason = ENVIRON["reason"];
    569         is_resolve = reason == "resolve";
    570 
    571         TMPBASE = ENVIRON["tmpfile_base"];
    572         filename_source = TMPBASE ".part";
    573         if (is_resolve)
    574           print "builtin history -c" > filename_source
    575 
    576         entry_nline = 0;
    577         entry_text = "";
    578         entry_time = "";
    579         if (_ble_bash >= 40400)
    580           entry_time = ENVIRON["epoch"];
    581 
    582         command_count = 0;
    583 
    584         multiline_count = 0;
    585         modification_count = 0;
    586         read_section_count = 0;
    587       }
    588   
    589       function write_flush(_, i, filename_section, t, c) {
    590         if (command_count == 0) return;
    591         if (command_count >= 2 || entry_time) {
    592           filename_section = TMPBASE "." read_section_count++ ".part";
    593           for (i = 0; i < command_count; i++) {
    594             t = command_time[i];
    595             c = command_text[i];
    596             if (t) print "#" t > filename_section;
    597             print c > filename_section;
    598           }
    599 #%        # Note: HISTTIMEFORMAT を指定するのは bash-4.4 で複数行読み取りを有効にする為。
    600           print "HISTTIMEFORMAT=%s builtin history -r " filename_section > filename_source;
    601         } else {
    602           for (i = 0; i < command_count; i++) {
    603             c = command_text[i];
    604             gsub(/'"$apos"'/, Q, c);
    605             print "builtin history -s -- " q c q > filename_source;
    606           }
    607         }
    608         command_count = 0;
    609       }
    610       function write_complex(value) {
    611         write_flush();
    612         print "builtin history -s -- " value > filename_source;
    613       }
    614 
    615       function register_command(cmd) {
    616         command_time[command_count] = entry_time;
    617         command_text[command_count] = cmd;
    618         command_count++;
    619       }
    620 
    621       function is_escaped_command(cmd) {
    622         return cmd ~ /^eval -- \$'"$apos"'([^'"$apos"'\\]|\\[\\'"$apos"'nt])*'"$apos"'$/;
    623       }
    624       function unescape_command(cmd) {
    625         cmd = substr(cmd, 11, length(cmd) - 11);
    626         gsub(/\\\\/, "\\q", cmd);
    627         gsub(/\\n/, "\n", cmd);
    628         gsub(/\\t/, "\t", cmd);
    629         gsub(/\\'"$apos"'/, "'"$apos"'", cmd);
    630         gsub(/\\q/, "\\", cmd);
    631         return cmd;
    632       }
    633       function register_escaped_command(cmd) {
    634         multiline_count++;
    635         modification_count++;
    636         if (_ble_bash >= 40400) {
    637           register_command(unescape_command(cmd));
    638         } else {
    639           write_complex(substr(cmd, 9));
    640         }
    641       }
    642 
    643       function register_multiline_command(cmd) {
    644         multiline_count++;
    645         if (_ble_bash >= 40040) {
    646           register_command(cmd);
    647         } else {
    648           gsub(/'"$apos"'/, Q, cmd);
    649           write_complex(q cmd q);
    650         }
    651       }
    652   
    653       function flush_entry() {
    654         if (entry_nline < 1) return;
    655 
    656         if (is_escaped_command(entry_text)) {
    657           register_escaped_command(entry_text)
    658         } else if (entry_nline > 1) {
    659           register_multiline_command(entry_text);
    660         } else {
    661           register_command(entry_text);
    662         }
    663   
    664         entry_nline = 0;
    665         entry_text = "";
    666       }
    667 
    668       function save_timestamp(line) {
    669         if (is_resolve) {
    670           # "history" format
    671           if (line ~ /^ *[0-9]+\*? +__ble_time_[0-9]+__/) {
    672             sub(/^ *[0-9]+\*? +__ble_time_/, "", line);
    673             sub(/__.*$/, "", line);
    674             entry_time = line;
    675           }
    676         } else {
    677           # "history -w" format
    678           if (line ~ /^#[0-9]/) {
    679             sub(/^#/, "", line);
    680             sub(/[^0-9].*$/, "", line);
    681             entry_time = line;
    682           }
    683         }
    684       }
    685   
    686       {
    687         if (is_resolve) {
    688           save_timestamp($0);
    689           if (sub(/^ *[0-9]+\*? +(__ble_time_[0-9]+__|\?\?|.+: invalid timestamp)/, "", $0))
    690             flush_entry();
    691           entry_text = ++entry_nline == 1 ? $0 : entry_text "\n" $0;
    692         } else {
    693           if ($0 ~ /^#[0-9]/) {
    694             save_timestamp($0);
    695             next;
    696           } else {
    697             flush_entry();
    698             entry_text = $0;
    699             entry_nline = 1;
    700           }
    701         }
    702       }
    703   
    704       END {
    705         flush_entry();
    706         write_flush();
    707         if (is_resolve)
    708           print "builtin history -a /dev/null" > filename_source
    709         print "multiline_count=" multiline_count;
    710         print "modification_count=" modification_count;
    711       }
    712     '
    713   }
    714   ## @fn ble/history:bash/resolve-multiline/.cleanup
    715   ##   @var[in] tmpfile_base
    716   function ble/history:bash/resolve-multiline/.cleanup {
    717     local file
    718     for file in "$tmpfile_base".*; do : >| "$file"; done
    719   }
    720   function ble/history:bash/resolve-multiline/.worker {
    721     local HISTTIMEFORMAT=__ble_time_%s__
    722     local -x tmpfile_base=$_ble_base_run/$$.history.mlfix
    723     local multiline_count=0 modification_count=0
    724     builtin eval -- "$(ble/builtin/history/.dump | ble/history:bash/resolve-multiline/.awk resolve 2>/dev/null)"
    725     if ((modification_count)); then
    726       ble/bin/mv -f "$tmpfile_base.part" "$tmpfile_base.sh"
    727     else
    728       ble/util/print : >| "$tmpfile_base.sh"
    729     fi
    730   }
    731   function ble/history:bash/resolve-multiline/.load {
    732     local tmpfile_base=$_ble_base_run/$$.history.mlfix
    733     local HISTCONTROL= HISTSIZE= HISTIGNORE=
    734     source "$tmpfile_base.sh"
    735     ble/history:bash/resolve-multiline/.cleanup
    736   }
    737 
    738   ## @fn ble/history:bash/resolve-multiline opts
    739   ##   @param[in] opts
    740   ##     async
    741   ##       非同期で読み取ります。
    742   function ble/history:bash/resolve-multiline.impl {
    743     local opts=$1
    744     local opt_async=; [[ :$opts: == *:async:* ]] && opt_async=1
    745 
    746     local history_tmpfile=$_ble_base_run/$$.history.mlfix.sh
    747     [[ $opt_async || :$opts: == *:init:* ]] || _ble_history_mlfix_resume=0
    748 
    749     [[ ! $opt_async ]] && ((_ble_history_mlfix_resume<=4)) &&
    750       blehook/invoke history_message "resolving multiline history ..."
    751     while :; do
    752       case $_ble_history_mlfix_resume in
    753 
    754       (0) if [[ $opt_async ]] && ble/builtin/history/is-empty; then
    755             # Note: bashrc の中では resolve-multiline はしない。
    756             #   一旦 bash が履歴を読み込んだ後に再度試す。
    757             ble/util/idle.wait-user-input
    758             ((_ble_history_mlfix_resume++))
    759             return 147
    760           fi
    761           ((_ble_history_mlfix_resume++)) ;;
    762 
    763       (1) # 履歴ファイル生成を Background で開始
    764         if [[ $_ble_history_mlfix_bgpid ]]; then
    765           builtin kill -9 "$_ble_history_mlfix_bgpid" &>/dev/null
    766           _ble_history_mlfix_bgpid=
    767         fi
    768 
    769         : >| "$history_tmpfile"
    770         if [[ $opt_async ]]; then
    771           _ble_history_mlfix_bgpid=$(ble/util/nohup 'ble/history:bash/resolve-multiline/.worker' print-bgpid)
    772 
    773           function ble/history:bash/resolve-multiline/.worker-completed {
    774             local history_tmpfile=$_ble_base_run/$$.history.mlfix.sh
    775             [[ -s $history_tmpfile ]] || ! builtin kill -0 "$_ble_history_mlfix_bgpid"
    776           } &>/dev/null
    777 
    778           ((_ble_history_mlfix_resume++))
    779         else
    780           ble/history:bash/resolve-multiline/.worker
    781           ((_ble_history_mlfix_resume+=3))
    782         fi ;;
    783 
    784       (2) if [[ $opt_async ]] && ble/util/is-running-in-idle; then
    785             ble/util/idle.wait-condition ble/history:bash/resolve-multiline/.worker-completed
    786             ((_ble_history_mlfix_resume++))
    787             return 147
    788           fi
    789           ((_ble_history_mlfix_resume++)) ;;
    790 
    791       # Note: async でバックグラウンドプロセスを起動した後に、直接 (sync で)
    792       #   呼び出された時、未だ処理が完了していなくても次のステップに進んでしまうので、
    793       #   此処で条件が満たされるのを待つ (#D0745)
    794       (3) while ! ble/history:bash/resolve-multiline/.worker-completed; do
    795             ble/util/msleep 50
    796             [[ $opt_async ]] && ! ble/util/idle/IS_IDLE && return 148
    797           done
    798           ((_ble_history_mlfix_resume++)) ;;
    799 
    800       # 80ms history 再構築 (47000項目)
    801       (4) _ble_history_mlfix_bgpid=
    802           ble/history:bash/resolve-multiline/.load
    803           [[ $opt_async ]] || blehook/invoke history_message
    804           ((_ble_history_mlfix_resume++))
    805           return 0 ;;
    806 
    807       (*) return 1 ;;
    808       esac
    809 
    810       [[ $opt_async ]] && ! ble/util/idle/IS_IDLE && return 148
    811     done
    812   }
    813 
    814   function ble/history:bash/resolve-multiline {
    815     [[ $_ble_history_mlfix_done ]] && return 0
    816     if [[ $1 == sync ]]; then
    817       ((_ble_bash>=40000)) && [[ $BASHPID != $$ ]] && return 0
    818       ble/builtin/history/is-empty && return 0
    819     fi
    820 
    821     ble/history:bash/resolve-multiline.impl "$@"; local ext=$?
    822     ((ext)) && return "$ext"
    823     _ble_history_mlfix_done=1
    824     return 0
    825   }
    826   ((_ble_bash>=40000)) &&
    827     ble/util/idle.push 'ble/history:bash/resolve-multiline async'
    828 
    829   blehook history_reset_background!=_ble_history_mlfix_resume=0
    830 
    831   function ble/history:bash/resolve-multiline/readfile {
    832     local filename=$1
    833     local -x tmpfile_base=$_ble_base_run/$$.history.read
    834     ble/history:bash/resolve-multiline/.awk read < "$filename" &>/dev/null
    835     source "$tmpfile_base.part"
    836     ble/history:bash/resolve-multiline/.cleanup
    837   }
    838 else
    839   function ble/history:bash/resolve-multiline/readfile { builtin history -r "$filename"; }
    840   function ble/history:bash/resolve-multiline { ((1)); }
    841 fi
    842 
    843 # Note: 複数行コマンドは eval -- $'' の形に変換して
    844 #   書き込みたいので自前で処理する。
    845 function ble/history:bash/unload.hook {
    846   ble/util/is-running-in-subshell && return 0
    847   if shopt -q histappend &>/dev/null; then
    848     ble/builtin/history -a
    849   else
    850     ble/builtin/history -w
    851   fi
    852 }
    853 blehook unload!=ble/history:bash/unload.hook
    854 
    855 function ble/history:bash/reset {
    856   if ((_ble_bash>=40000)); then
    857     _ble_history_load_done=
    858     ble/history:bash/clear-background-load
    859     ble/util/idle.push 'ble/history:bash/initialize async'
    860   elif ((_ble_bash>=30100)) && [[ $bleopt_history_lazyload ]]; then
    861     _ble_history_load_done=
    862   else
    863     # * history-load は initialize ではなく attach で行う。
    864     #   detach してから attach する間に
    865     #   追加されたエントリがあるかもしれないので。
    866     # * bash-3.0 では history -s は最近の履歴項目を置換するだけなので、
    867     #   履歴項目は全て自分で処理する必要がある。
    868     #   つまり、初めから load しておかなければならない。
    869     ble/history:bash/initialize
    870   fi
    871 }
    872 
    873 
    874 #------------------------------------------------------------------------------
    875 # ble/builtin/history                                     @history.bash.builtin
    876 
    877 function ble/builtin/history/.touch-histfile {
    878   local touch=$_ble_base_run/$$.history.touch
    879   : >| "$touch"
    880 }
    881 
    882 # in def.sh
    883 # @hook history_change
    884 
    885 # Note: #D1126 一度置き換えたら戻せない。二回は初期化しない。
    886 if [[ ! ${_ble_builtin_history_initialized+set} ]]; then
    887   _ble_builtin_history_initialized=
    888   _ble_builtin_history_histnew_count=0
    889   _ble_builtin_history_histapp_count=0
    890   ## @var _ble_builtin_history_wskip
    891   ##   履歴のどの行までがファイルに書き込み済みの行かを管理する変数です。
    892   ## @var _ble_builtin_history_prevmax
    893   ##   最後の ble/builtin/history における builtin history の項目番号
    894   _ble_builtin_history_wskip=0
    895   _ble_builtin_history_prevmax=0
    896 
    897   ##
    898   ## 以下の関数は各ファイルに関して何処まで読み取ったかを記録します。
    899   ##
    900   ## @fn ble/builtin/history/.get-rskip file
    901   ##   @param[in] file
    902   ##   @var[out] rskip
    903   ## @fn ble/builtin/history/.set-rskip file value
    904   ##   @param[in] file
    905   ## @fn ble/builtin/history/.add-rskip file delta
    906   ##   @param[in] file
    907   ##
    908   builtin eval -- "${_ble_util_gdict_declare//NAME/_ble_builtin_history_rskip_dict}"
    909   function ble/builtin/history/.get-rskip {
    910     local file=$1 ret
    911     ble/gdict#get _ble_builtin_history_rskip_dict "$file"
    912     rskip=$ret
    913   }
    914   function ble/builtin/history/.set-rskip {
    915     local file=$1
    916     ble/gdict#set _ble_builtin_history_rskip_dict "$file" "$2"
    917   }
    918   function ble/builtin/history/.add-rskip {
    919     local file=$1 ret
    920     # Note: 当初 ((dict[\$file]+=$2)) の形式を使っていたが、これは
    921     #   shopt -s assoc_expand_once の場合に動作しない事が判明したので、
    922     #   一旦、別の変数で計算してから代入する事にする。
    923     ble/gdict#get _ble_builtin_history_rskip_dict "$file"
    924     ((ret+=$2))
    925     ble/gdict#set _ble_builtin_history_rskip_dict "$file" "$ret"
    926   }
    927 fi
    928 
    929 ## @fn ble/builtin/history/.initialize opts
    930 ##   @param[in] opts
    931 ##     skip0 ... Bash 初期化処理 (bashrc) を抜け出ていると判定できない状態で、
    932 ##               履歴が一件も読み込まれていない時はスキップします。
    933 function ble/builtin/history/.initialize {
    934   [[ $_ble_builtin_history_initialized ]] && return 0
    935   local line; ble/util/assign line 'builtin history 1'
    936   [[ ! $_ble_decode_hook_count && ! $line && :$1: == *:skip0:* ]] && return 1
    937   _ble_builtin_history_initialized=1
    938 
    939   local histnew=$_ble_base_run/$$.history.new
    940   : >| "$histnew"
    941 
    942   if [[ $line ]]; then
    943     # Note: #D1126 ble.sh ロード前に追加された履歴項目があれば保存する。
    944     local histini=$_ble_base_run/$$.history.ini
    945     local histapp=$_ble_base_run/$$.history.app
    946     HISTTIMEFORMAT=1 builtin history -a "$histini"
    947     if [[ -s $histini ]]; then
    948       ble/bin/sed '/^#\([0-9].*\)/{s//    0  __ble_time_\1__/;N;s/\n//;}' "$histini" >> "$histapp"
    949       : >| "$histini"
    950     fi
    951   else
    952     # 履歴が読み込まれていなければ強制的に読み込む
    953     ble/builtin/history/option:r
    954   fi
    955 
    956   local histfile=${HISTFILE-} rskip=0
    957   [[ -e $histfile ]] && rskip=$(ble/bin/wc -l "$histfile" 2>/dev/null)
    958   ble/string#split-words rskip "$rskip"
    959   local min; ble/builtin/history/.get-min
    960   local max; ble/builtin/history/.get-max
    961   ((max&&max-min+1<rskip&&(rskip=max-min+1)))
    962   _ble_builtin_history_wskip=$max
    963   _ble_builtin_history_prevmax=$max
    964   ble/builtin/history/.set-rskip "$histfile" "$rskip"
    965   return 0
    966 }
    967 ## @fn ble/builtin/history/.delete-range beg end
    968 function ble/builtin/history/.delete-range {
    969   local beg=$1 end=${2:-$1}
    970   if ((_ble_bash>=50000&&beg<end)); then
    971     builtin history -d "$beg-$end"
    972   else
    973     local i
    974     for ((i=end;i>=beg;i--)); do
    975       builtin history -d "$i"
    976     done
    977   fi
    978 }
    979 ## @fn ble/builtin/history/.check-uncontrolled-change [filename opts]
    980 ##   ble/builtin/history の管理外で履歴が読み込まれた時、
    981 ##   それを history -a の対象から除外する為に wskip を更新する。
    982 function ble/builtin/history/.check-uncontrolled-change {
    983   [[ $_ble_decode_bind_state == none ]] && return 0
    984   local filename=${1-} opts=${2-} prevmax=$_ble_builtin_history_prevmax
    985   local max; ble/builtin/history/.get-max
    986   if ((max!=prevmax)); then
    987     if [[ $filename && :$opts: == *:append:* ]] && ((_ble_builtin_history_wskip<prevmax&&prevmax<max)); then
    988       # 最後に管理下で追加された事を確認した範囲 wskip..prevmax を書き込む。
    989       (
    990         ble/builtin/history/.delete-range "$((prevmax+1))" "$max"
    991         ble/builtin/history/.write "$filename" "$_ble_builtin_history_wskip" append:fetch
    992       )
    993     fi
    994     _ble_builtin_history_wskip=$max
    995     _ble_builtin_history_prevmax=$max
    996   fi
    997 }
    998 ## @fn ble/builtin/history/.load-recent-entries count
    999 ##   history の最新 count 件を配列 _ble_history に読み込みます。
   1000 function ble/builtin/history/.load-recent-entries {
   1001   [[ $_ble_decode_bind_state == none ]] && return 0
   1002 
   1003   local delta=$1
   1004   ((delta>0)) || return 0
   1005 
   1006   if [[ ! $_ble_history_load_done ]]; then
   1007     # history load が完了していなければ読み途中のデータを破棄して戻る
   1008     ble/history:bash/clear-background-load
   1009     _ble_history_count=
   1010     return 0
   1011   fi
   1012 
   1013   # 追加項目が大量にある場合には background で完全再初期化する
   1014   if ((_ble_bash>=40000&&delta>=10000)); then
   1015     ble/history:bash/reset
   1016     return 0
   1017   fi
   1018 
   1019   ble/history:bash/load append:count=$delta
   1020 
   1021   local ocount=$_ble_history_count ncount=${#_ble_history[@]}
   1022   ((_ble_history_index==_ble_history_count)) && _ble_history_index=$ncount
   1023   _ble_history_count=$ncount
   1024   ble/history/.update-position
   1025   blehook/invoke history_change insert "$ocount" "$delta"
   1026 }
   1027 ## @fn ble/builtin/history/.read file [skip [fetch]]
   1028 function ble/builtin/history/.read {
   1029   local file=$1 skip=${2:-0} fetch=$3
   1030   local -x histnew=$_ble_base_run/$$.history.new
   1031   if [[ -s $file ]]; then
   1032     local script=$(ble/bin/awk -v skip="$skip" '
   1033       BEGIN { histnew = ENVIRON["histnew"]; count = 0; }
   1034       NR <= skip { next; }
   1035       { print $0 >> histnew; count++; }
   1036       END {
   1037         print "ble/builtin/history/.set-rskip \"$file\" " NR;
   1038         print "((_ble_builtin_history_histnew_count+=" count "))";
   1039       }
   1040     ' "$file")
   1041     builtin eval -- "$script"
   1042   else
   1043     ble/builtin/history/.set-rskip "$file" 0
   1044   fi
   1045   if [[ ! $fetch && -s $histnew ]]; then
   1046     local nline=$_ble_builtin_history_histnew_count
   1047     ble/history:bash/resolve-multiline/readfile "$histnew"
   1048     : >| "$histnew"
   1049     _ble_builtin_history_histnew_count=0
   1050     ble/builtin/history/.load-recent-entries "$nline"
   1051     local max; ble/builtin/history/.get-max
   1052     _ble_builtin_history_wskip=$max
   1053     _ble_builtin_history_prevmax=$max
   1054   fi
   1055 }
   1056 ## @fn ble/builtin/history/.write file [skip [opts]]
   1057 function ble/builtin/history/.write {
   1058   local -x file=$1 skip=${2:-0} opts=$3
   1059   local -x histapp=$_ble_base_run/$$.history.app
   1060   declare -p HISTTIMEFORMAT &>/dev/null
   1061   local -x flag_timestamp=$(($?==0))
   1062 
   1063   local min; ble/builtin/history/.get-min
   1064   local max; ble/builtin/history/.get-max
   1065   ((skip<min-1&&(skip=min-1)))
   1066   local delta=$((max-skip))
   1067   if ((delta>0)); then
   1068     local HISTTIMEFORMAT=__ble_time_%s__
   1069     if [[ :$opts: == *:append:* ]]; then
   1070       ble/builtin/history/.dump "$delta" >> "$histapp"
   1071       ((_ble_builtin_history_histapp_count+=delta))
   1072     else
   1073       ble/builtin/history/.dump "$delta" >| "$histapp"
   1074       _ble_builtin_history_histapp_count=$delta
   1075     fi
   1076   fi
   1077 
   1078   if [[ ! -e $file ]]; then
   1079     (umask 077; : >| "$file")
   1080   elif [[ :$opts: != *:append:* ]]; then
   1081     : >| "$file"
   1082   fi
   1083 
   1084   if [[ :$opts: != *:fetch:* && -s $histapp ]]; then
   1085     local apos=\'
   1086     < "$histapp" ble/bin/awk '
   1087       BEGIN {
   1088         file = ENVIRON["file"];
   1089         flag_timestamp = ENVIRON["flag_timestamp"];
   1090         timestamp = "";
   1091         mode = 0;
   1092       }
   1093       function flush_line() {
   1094         if (!mode) return;
   1095         mode = 0;
   1096         if (text ~ /\n/) {
   1097           gsub(/['"$apos"'\\]/, "\\\\&", text);
   1098           gsub(/\n/, "\\n", text);
   1099           gsub(/\t/, "\\t", text);
   1100           text = "eval -- $'"$apos"'" text "'"$apos"'"
   1101         }
   1102 
   1103         if (timestamp != "")
   1104           print timestamp >> file;
   1105         print text >> file;
   1106       }
   1107 
   1108       function extract_timestamp(line) {
   1109         if (!sub(/^ *[0-9]+\*? +__ble_time_/, "", line)) return "";
   1110         if (!sub(/__.*$/, "", line)) return "";
   1111         if (!(line ~ /^[0-9]+$/)) return "";
   1112         return "#" line;
   1113       }
   1114 
   1115       /^ *[0-9]+\*? +(__ble_time_[0-9]+__|\?\?|.+: invalid timestamp)?/ {
   1116         flush_line();
   1117 
   1118         mode = 1;
   1119         text = "";
   1120         if (flag_timestamp)
   1121           timestamp = extract_timestamp($0);
   1122 
   1123         sub(/^ *[0-9]+\*? +(__ble_time_[0-9]+__|\?\?|.+: invalid timestamp)?/, "", $0);
   1124       }
   1125       { text = text != "" ? text "\n" $0 : $0; }
   1126       END { flush_line(); }
   1127     '
   1128     ble/builtin/history/.add-rskip "$file" "$_ble_builtin_history_histapp_count"
   1129     : >| "$histapp"
   1130     _ble_builtin_history_histapp_count=0
   1131   fi
   1132   _ble_builtin_history_wskip=$max
   1133   _ble_builtin_history_prevmax=$max
   1134 }
   1135 
   1136 ## @fn ble/builtin/history/array#delete-hindex array_name index...
   1137 ##   @param[in] index
   1138 ##     昇順に並んでいる事と重複がない事を仮定する。
   1139 function ble/builtin/history/array#delete-hindex {
   1140   local array_name=$1; shift
   1141   local script='
   1142     local -a out=()
   1143     local i shift=0
   1144     for i in "${!ARR[@]}"; do
   1145       local delete=
   1146       while (($#)); do
   1147         if [[ $1 == *-* ]]; then
   1148           local b=${1%-*} e=${1#*-}
   1149           ((i<b)) && break
   1150           if ((i<e)); then
   1151             delete=1 # delete
   1152             break
   1153           else
   1154             ((shift+=e-b))
   1155             shift
   1156           fi
   1157         else
   1158           ((i<$1)) && break
   1159           ((i==$1)) && delete=1
   1160           ((shift++))
   1161           shift
   1162         fi
   1163       done
   1164       [[ ! $delete ]] &&
   1165         out[i-shift]=${ARR[i]}
   1166     done
   1167     ARR=()
   1168     for i in "${!out[@]}"; do ARR[i]=${out[i]}; done'
   1169   builtin eval -- "${script//ARR/$array_name}"
   1170 }
   1171 ## @fn ble/builtin/history/array#insert-range array_name beg len
   1172 function ble/builtin/history/array#insert-range {
   1173   local array_name=$1 beg=$2 len=$3
   1174   local script='
   1175     local -a out=()
   1176     local i
   1177     for i in "${!ARR[@]}"; do
   1178       out[i<beg?beg:i+len]=${ARR[i]}
   1179     done
   1180     ARR=()
   1181     for i in "${!out[@]}"; do ARR[i]=${out[i]}; done'
   1182   builtin eval -- "${script//ARR/$array_name}"
   1183 }
   1184 blehook history_change!=ble/builtin/history/change.hook
   1185 function ble/builtin/history/change.hook {
   1186   local kind=$1; shift
   1187   case $kind in
   1188   (delete)
   1189     ble/builtin/history/array#delete-hindex _ble_history_dirt "$@" ;;
   1190   (clear)
   1191     _ble_history_dirt=() ;;
   1192   (insert)
   1193     # Note: _ble_history, _ble_history_edit は別に更新される
   1194     ble/builtin/history/array#insert-range _ble_history_dirt "$@" ;;
   1195   esac
   1196 }
   1197 ## @fn ble/builtin/history/option:c
   1198 function ble/builtin/history/option:c {
   1199   ble/builtin/history/.initialize skip0 || return "$?"
   1200   builtin history -c
   1201   _ble_builtin_history_wskip=0
   1202   _ble_builtin_history_prevmax=0
   1203   if [[ $_ble_decode_bind_state != none ]]; then
   1204     if [[ $_ble_history_load_done ]]; then
   1205       _ble_history=()
   1206       _ble_history_edit=()
   1207       _ble_history_count=0
   1208       _ble_history_index=0
   1209     else
   1210       # history load が完了していなければ読み途中のデータを破棄して戻る
   1211       ble/history:bash/clear-background-load
   1212       _ble_history_count=
   1213     fi
   1214     ble/history/.update-position
   1215     blehook/invoke history_change clear
   1216   fi
   1217 }
   1218 ## @fn ble/builtin/history/option:d index
   1219 function ble/builtin/history/option:d {
   1220   ble/builtin/history/.initialize skip0 || return "$?"
   1221   local rex='^(-?[0-9]+)-(-?[0-9]+)$'
   1222   if [[ $1 =~ $rex ]]; then
   1223     local beg=${BASH_REMATCH[1]} end=${BASH_REMATCH[2]}
   1224   else
   1225     local beg=$(($1))
   1226     local end=$beg
   1227   fi
   1228   local min; ble/builtin/history/.get-min
   1229   local max; ble/builtin/history/.get-max
   1230   ((beg<0)) && ((beg+=max+1)); ((beg<min?(beg=min):(beg>max&&(beg=max))))
   1231   ((end<0)) && ((end+=max+1)); ((end<min?(end=min):(end>max&&(end=max))))
   1232   ((beg<=end)) || return 0
   1233 
   1234   ble/builtin/history/.delete-range "$beg" "$end"
   1235   if ((_ble_builtin_history_wskip>=end)); then
   1236     ((_ble_builtin_history_wskip-=end-beg+1))
   1237   elif ((_ble_builtin_history_wskip>beg-1)); then
   1238     ((_ble_builtin_history_wskip=beg-1))
   1239   fi
   1240 
   1241   if [[ $_ble_decode_bind_state != none ]]; then
   1242     if [[ $_ble_history_load_done ]]; then
   1243       local N=${#_ble_history[@]}
   1244       local b=$((beg-1+N-max)) e=$((end+N-max))
   1245       blehook/invoke history_change delete "$b-$e"
   1246       if ((_ble_history_index>=e)); then
   1247         ((_ble_history_index-=e-b))
   1248       elif ((_ble_history_index>=b)); then
   1249         _ble_history_index=$b
   1250       fi
   1251       _ble_history=("${_ble_history[@]::b}" "${_ble_history[@]:e}")
   1252       _ble_history_edit=("${_ble_history_edit[@]::b}" "${_ble_history_edit[@]:e}")
   1253       _ble_history_count=${#_ble_history[@]}
   1254     else
   1255       # history load が完了していなければ読み途中のデータを破棄して戻る
   1256       ble/history:bash/clear-background-load
   1257       _ble_history_count=
   1258     fi
   1259     ble/history/.update-position
   1260   fi
   1261   local max; ble/builtin/history/.get-max
   1262   _ble_builtin_history_prevmax=$max
   1263 }
   1264 function ble/builtin/history/.get-histfile {
   1265   histfile=${1:-${HISTFILE-}}
   1266   if [[ ! $histfile ]]; then
   1267     local opt=-a
   1268     [[ ${FUNCNAME[1]} == *:[!:] ]] && opt=-${FUNCNAME[1]##*:}
   1269     if [[ ${1+set} ]]; then
   1270       ble/util/print "ble/builtin/history $opt: the history filename is empty." >&2
   1271     else
   1272       ble/util/print "ble/builtin/history $opt: the history file is not specified." >&2
   1273     fi
   1274     return 1
   1275   fi
   1276 }
   1277 ## @fn ble/builtin/history/option:a [filename]
   1278 function ble/builtin/history/option:a {
   1279   ble/builtin/history/.initialize skip0 || return "$?"
   1280   local histfile; ble/builtin/history/.get-histfile "$@" || return "$?"
   1281   ble/builtin/history/.check-uncontrolled-change "$histfile" append
   1282   local rskip; ble/builtin/history/.get-rskip "$histfile"
   1283   ble/builtin/history/.write "$histfile" "$_ble_builtin_history_wskip" append:fetch
   1284   [[ -r $histfile ]] && ble/builtin/history/.read "$histfile" "$rskip" fetch
   1285   ble/builtin/history/.write "$histfile" "$_ble_builtin_history_wskip" append
   1286   builtin history -a /dev/null # Bash 終了時に書き込まない
   1287 }
   1288 ## @fn ble/builtin/history/option:n [filename]
   1289 function ble/builtin/history/option:n {
   1290   # HISTFILE が更新されていなければスキップ
   1291   local histfile; ble/builtin/history/.get-histfile "$@" || return "$?"
   1292   if [[ $histfile == ${HISTFILE-} ]]; then
   1293     local touch=$_ble_base_run/$$.history.touch
   1294     [[ $touch -nt ${HISTFILE-} ]] && return 0
   1295     : >| "$touch"
   1296   fi
   1297 
   1298   ble/builtin/history/.initialize
   1299   local rskip; ble/builtin/history/.get-rskip "$histfile"
   1300   ble/builtin/history/.read "$histfile" "$rskip"
   1301 }
   1302 ## @fn ble/builtin/history/option:w [filename]
   1303 function ble/builtin/history/option:w {
   1304   ble/builtin/history/.initialize skip0 || return "$?"
   1305   local histfile; ble/builtin/history/.get-histfile "$@" || return "$?"
   1306   local rskip; ble/builtin/history/.get-rskip "$histfile"
   1307   [[ -r $histfile ]] && ble/builtin/history/.read "$histfile" "$rskip" fetch
   1308   ble/builtin/history/.write "$histfile" 0
   1309   builtin history -a /dev/null # Bash 終了時に書き込まない
   1310 }
   1311 ## @fn ble/builtin/history/option:r [histfile]
   1312 function ble/builtin/history/option:r {
   1313   local histfile; ble/builtin/history/.get-histfile "$@" || return "$?"
   1314   ble/builtin/history/.initialize
   1315   ble/builtin/history/.read "$histfile" 0
   1316 }
   1317 ## @fn ble/builtin/history/option:p
   1318 ##   Workaround for bash-3.0 -- 5.0 bug
   1319 ##   (See memo.txt #D0233, #D0801, #D1091)
   1320 function ble/builtin/history/option:p {
   1321   # Note: auto-complete .search-history-light や
   1322   #   magic-space 等経由で history -p が呼び出されて、
   1323   #   その時に resolve-multiline が sync されると引っ掛かる。
   1324   #   従って history -p では sync しない事に決めた (#D1121)
   1325   # Note: bash-3 では background load ができないので
   1326   #   最初に history -p が呼び出されるタイミングで初期化する事にする (#D1122)
   1327   ((_ble_bash>=40000)) || ble/builtin/history/is-empty ||
   1328     ble/history:bash/resolve-multiline sync
   1329 
   1330   # Note: history -p '' によって 履歴項目が減少するかどうかをチェックし、
   1331   #   もし履歴項目が減る状態になっている場合は履歴項目を増やしてから history -p を実行する。
   1332   #   嘗てはサブシェルで評価していたが、そうすると置換指示子が記録されず
   1333   #   :& が正しく実行されないことになるのでこちらの実装に切り替える。
   1334   local line1= line2=
   1335   ble/util/assign line1 'HISTTIMEFORMAT= builtin history 1'
   1336   builtin history -p -- '' &>/dev/null
   1337   ble/util/assign line2 'HISTTIMEFORMAT= builtin history 1'
   1338   if [[ $line1 != "$line2" ]]; then
   1339     local rex_head='^[[:space:]]*[0-9]+\*?[[:space:]]*'
   1340     [[ $line1 =~ $rex_head ]] &&
   1341       line1=${line1:${#BASH_REMATCH}}
   1342 
   1343     if ((_ble_bash<30100)); then
   1344       # Note: history -r するとそれまでの履歴項目が終了時に
   1345       #   .bash_history に反映されなくなるが、
   1346       #   Bash 3.0 では明示的に書き込んでいるので問題ない。
   1347       local tmp=$_ble_base_run/$$.history.tmp
   1348       printf '%s\n' "$line1" "$line1" >| "$tmp"
   1349       builtin history -r "$tmp"
   1350     else
   1351       builtin history -s -- "$line1"
   1352       builtin history -s -- "$line1"
   1353     fi
   1354   fi
   1355 
   1356   builtin history -p -- "$@"
   1357 }
   1358 
   1359 bleopt/declare -v history_erasedups_limit 0
   1360 : "${_ble_history_erasedups_base=}"
   1361 
   1362 function ble/builtin/history/erasedups/update-base {
   1363   if [[ ! ${_ble_history_erasedups_base:-} ]]; then
   1364     _ble_history_erasedups_base=${#_ble_history[@]}
   1365   else
   1366     local value=${#_ble_history[@]}
   1367     ((value<_ble_history_erasedups_base&&(_ble_history_erasedups_base=value)))
   1368   fi
   1369 }
   1370 ## @fn ble/builtin/history/erasedups/.impl-for cmd
   1371 ## @fn ble/builtin/history/erasedups/.impl-awk cmd
   1372 ## @fn ble/builtin/history/erasedups/.impl-ranged cmd beg
   1373 ##   @param[in] cmd
   1374 ##   @var[in] N
   1375 ##   @var[in] HISTINDEX_NEXT
   1376 ##   @var[in] _ble_builtin_history_wskip
   1377 ##   @arr[out] delete_indices
   1378 ##   @var[out] shift_histindex_next
   1379 ##   @var[out] shift_wskip
   1380 function ble/builtin/history/erasedups/.impl-for {
   1381   local cmd=$1
   1382   delete_indices=()
   1383   shift_histindex_next=0
   1384   shift_wskip=0
   1385 
   1386   local i
   1387   for ((i=0;i<N-1;i++)); do
   1388     if [[ ${_ble_history[i]} == "$cmd" ]]; then
   1389       builtin unset -v '_ble_history[i]'
   1390       builtin unset -v '_ble_history_edit[i]'
   1391       ble/array#push delete_indices "$i"
   1392       ((i<_ble_builtin_history_wskip&&shift_wskip++))
   1393       ((i<HISTINDEX_NEXT&&shift_histindex_next++))
   1394     fi
   1395   done
   1396   if ((${#delete_indices[@]})); then
   1397     _ble_history=("${_ble_history[@]}")
   1398     _ble_history_edit=("${_ble_history_edit[@]}")
   1399   fi
   1400 }
   1401 function ble/builtin/history/erasedups/.impl-awk {
   1402   local cmd=$1
   1403   delete_indices=()
   1404   shift_histindex_next=0
   1405   shift_wskip=0
   1406   ((N)) || return 0
   1407 
   1408   # select the fastest awk implementation
   1409   local -x erasedups_nlfix_read=
   1410   local awk writearray_options
   1411   if ble/bin/awk0.available; then
   1412     erasedups_nlfix_read=
   1413     writearray_options=(-d '')
   1414     awk=ble/bin/awk0
   1415   else
   1416     erasedups_nlfix_read=1
   1417     writearray_options=(--nlfix)
   1418     if ble/is-function ble/bin/mawk; then
   1419       awk=ble/bin/mawk
   1420     elif ble/is-function ble/bin/gawk; then
   1421       awk=ble/bin/gawk
   1422     else
   1423       ble/builtin/history/erasedups/.impl-for "$@"
   1424       return "$?"
   1425     fi
   1426   fi
   1427 
   1428   local _ble_local_tmpfile
   1429   ble/util/assign/mktmp; local otmp1=$_ble_local_tmpfile
   1430   ble/util/assign/mktmp; local otmp2=$_ble_local_tmpfile
   1431   ble/util/assign/mktmp; local itmp1=$_ble_local_tmpfile
   1432   ble/util/assign/mktmp; local itmp2=$_ble_local_tmpfile
   1433 
   1434   # Note: ジョブを無効にする為 subshell で実行
   1435   ( ble/util/writearray "${writearray_options[@]}" _ble_history      >| "$itmp1" & local pid1=$!
   1436     ble/util/writearray "${writearray_options[@]}" _ble_history_edit >| "$itmp2"
   1437     wait "$pid1" )
   1438 
   1439   local -x erasedups_cmd=$cmd
   1440   local -x erasedups_out1=$otmp1
   1441   local -x erasedups_out2=$otmp2
   1442   local -x erasedups_histindex_next=$HISTINDEX_NEXT
   1443   local -x erasedups_wskip=$_ble_builtin_history_wskip
   1444   local awk_script='
   1445     '"$_ble_bin_awk_libES"'
   1446     '"$_ble_bin_awk_libNLFIX"'
   1447 
   1448     BEGIN {
   1449       NLFIX_READ     = ENVIRON["erasedups_nlfix_read"] != "";
   1450       cmd            = ENVIRON["erasedups_cmd"];
   1451       out1           = ENVIRON["erasedups_out1"];
   1452       out2           = ENVIRON["erasedups_out2"];
   1453       histindex_next = ENVIRON["erasedups_histindex_next"];
   1454       wskip          = ENVIRON["erasedups_wskip"];
   1455 
   1456       if (NLFIX_READ)
   1457         es_initialize();
   1458       else
   1459         RS = "\0";
   1460 
   1461       NLFIX_WRITE = _ble_bash < 50200;
   1462       if (NLFIX_WRITE) nlfix_begin();
   1463 
   1464       hist_index = 0;
   1465       edit_index = 0;
   1466       delete_count = 0;
   1467       shift_histindex_next = 0;
   1468       shift_wskip = 0;
   1469     }
   1470 
   1471     function process_hist(elem) {
   1472       if (hist_index < N - 1 && elem == cmd) {
   1473         delete_indices[delete_count++] = hist_index;
   1474         delete_table[hist_index] = 1;
   1475         if (hist_index < wskip         ) shift_wskip++;
   1476         if (hist_index < histindex_next) shift_histindex_next++;
   1477       } else {
   1478         if (NLFIX_WRITE)
   1479           nlfix_push(elem, out1);
   1480         else
   1481           printf("%s%c", elem, 0) > out1;
   1482       }
   1483       hist_index++;
   1484     }
   1485 
   1486     function process_edit(elem) {
   1487       if (delete_count == 0) exit;
   1488       if (NLFIX_WRITE) {
   1489         if (edit_index == 0) {
   1490           nlfix_end(out1);
   1491           nlfix_begin();
   1492         }
   1493         if (!delete_table[edit_index++])
   1494           nlfix_push(elem, out2);
   1495       } else {
   1496         if (!delete_table[edit_index++])
   1497           printf("%s%c", elem, 0) > out2;
   1498       }
   1499     }
   1500 
   1501     mode == "edit" {
   1502       if (NLFIX_READ) {
   1503         edit[edit_index++] = $0;
   1504       } else {
   1505         process_edit($0);
   1506       }
   1507       next;
   1508     }
   1509     {
   1510       if (NLFIX_READ)
   1511         hist[hist_index++] = $0;
   1512       else
   1513         process_hist($0);
   1514     }
   1515 
   1516     END {
   1517       if (NLFIX_READ) {
   1518         n = split(hist[hist_index - 1], indices)
   1519         for (i = 1; i <= n; i++) {
   1520           elem = hist[indices[i]];
   1521           if (elem ~ /^\$'\''.*'\''/)
   1522             hist[indices[i]] = es_unescape(substr(elem, 3, length(elem) - 3));
   1523         }
   1524         n = hist_index - 1;
   1525         hist_index = 0;
   1526         for (i = 0; i < n; i++)
   1527           process_hist(hist[i]);
   1528 
   1529         n = split(edit[edit_index - 1], indices)
   1530         for (i = 1; i <= n; i++) {
   1531           elem = edit[indices[i]];
   1532           if (elem ~ /^\$'\''.*'\''/)
   1533             edit[indices[i]] = es_unescape(substr(elem, 3, length(elem) - 3));
   1534         }
   1535         n = edit_index - 1;
   1536         edit_index = 0;
   1537         for (i = 0; i < n; i++)
   1538           process_edit(edit[i]);
   1539       }
   1540 
   1541       if (NLFIX_WRITE) nlfix_end(out2);
   1542 
   1543       line = "delete_indices=("
   1544       for (i = 0; i < delete_count; i++) {
   1545         if (i != 0) line = line " ";
   1546         line = line delete_indices[i];
   1547       }
   1548       line = line ")";
   1549       print line;
   1550       print "shift_wskip=" shift_wskip;
   1551       print "shift_histindex_next=" shift_histindex_next;
   1552     }
   1553   '
   1554   local awk_result
   1555   ble/util/assign awk_result '"$awk" -v _ble_bash="$_ble_bash" -v N="$N" "$awk_script" "$itmp1" mode=edit "$itmp2"'
   1556   builtin eval -- "$awk_result"
   1557   if ((${#delete_indices[@]})); then
   1558     if ((_ble_bash<50200)); then
   1559       ble/util/readarray --nlfix _ble_history      < "$otmp1"
   1560       ble/util/readarray --nlfix _ble_history_edit < "$otmp2"
   1561     else
   1562       mapfile -d '' -t _ble_history      < "$otmp1"
   1563       mapfile -d '' -t _ble_history_edit < "$otmp2"
   1564     fi
   1565   fi
   1566   _ble_local_tmpfile=$itmp2 ble/util/assign/rmtmp
   1567   _ble_local_tmpfile=$itmp1 ble/util/assign/rmtmp
   1568   _ble_local_tmpfile=$otmp2 ble/util/assign/rmtmp
   1569   _ble_local_tmpfile=$otmp1 ble/util/assign/rmtmp
   1570 }
   1571 function ble/builtin/history/erasedups/.impl-ranged {
   1572   local cmd=$1 beg=$2
   1573   delete_indices=()
   1574   shift_histindex_next=0
   1575   shift_wskip=0
   1576 
   1577   # Note: 自前で history -d を行って重複を削除するので erasedups は除去しておく。
   1578   # 但し、一番最後の一致する要素だけは自分では削除しないので、後のhistory -s で
   1579   # 余分な履歴項目が追加されない様に ignoredups を付加する。
   1580   ble/path#remove HISTCONTROL erasedups
   1581   HISTCONTROL=$HISTCONTROL:ignoredups
   1582 
   1583   local i j=$beg
   1584   for ((i=beg;i<N;i++)); do
   1585     if ((i<N-1)) && [[ ${_ble_history[i]} == "$cmd" ]]; then
   1586       ble/array#push delete_indices "$i"
   1587       ((i<_ble_builtin_history_wskip&&shift_wskip++))
   1588       ((i<HISTINDEX_NEXT&&shift_histindex_next++))
   1589     else
   1590       if ((i!=j)); then
   1591         _ble_history[j]=${_ble_history[i]}
   1592         _ble_history_edit[j]=${_ble_history_edit[i]}
   1593       fi
   1594       ((j++))
   1595     fi
   1596   done
   1597   for ((;j<N;j++)); do
   1598     builtin unset -v '_ble_history[j]'
   1599     builtin unset -v '_ble_history_edit[j]'
   1600   done
   1601 
   1602   if ((${#delete_indices[@]})); then
   1603     local max; ble/builtin/history/.get-max
   1604     local max_index=$((N-1))
   1605     for ((i=${#delete_indices[@]}-1;i>=0;i--)); do
   1606       builtin history -d "$((delete_indices[i]-max_index+max))"
   1607     done
   1608   fi
   1609 }
   1610 ## @fn ble/builtin/history/erasedups cmd
   1611 ##   指定したコマンドに一致する履歴項目を削除します。この呼出の後に history -s
   1612 ##   を呼び出す事を想定しています。但し、一番最後の一致する要素は削除しません。
   1613 ##
   1614 ##   @var[in,out] HISTCONTROL
   1615 ##   @exit 9
   1616 ##     重複する要素が一番最後の要素のみの時に終了ステータス 9 を返します。この
   1617 ##     時、履歴追加を行っても履歴に変化は発生しないので、後続の history -s の呼
   1618 ##     び出しを省略してそのまま処理を終えても問題ありません。
   1619 function ble/builtin/history/erasedups {
   1620   local cmd=$1
   1621 
   1622   local beg=0 N=${#_ble_history[@]}
   1623   if [[ $bleopt_history_erasedups_limit ]]; then
   1624     local limit=$((bleopt_history_erasedups_limit))
   1625     if ((limit<=0)); then
   1626       ((beg=_ble_history_erasedups_base+limit))
   1627     else
   1628       ((beg=N-1-limit))
   1629     fi
   1630     ((beg<0)) && beg=0
   1631   fi
   1632 
   1633   local delete_indices shift_histindex_next shift_wskip
   1634   if ((beg>=N)); then
   1635     ble/path#remove HISTCONTROL erasedups
   1636     return 0
   1637   elif ((beg>0)); then
   1638     ble/builtin/history/erasedups/.impl-ranged "$cmd" "$beg"
   1639   else
   1640     if ((_ble_bash>=40000&&N>=20000)); then
   1641       ble/builtin/history/erasedups/.impl-awk "$cmd"
   1642     else
   1643       ble/builtin/history/erasedups/.impl-for "$cmd"
   1644     fi
   1645   fi
   1646 
   1647   if ((${#delete_indices[@]})); then
   1648     blehook/invoke history_change delete "${delete_indices[@]}"
   1649     ((_ble_builtin_history_wskip-=shift_wskip))
   1650     [[ ${HISTINDEX_NEXT+set} ]] && ((HISTINDEX_NEXT-=shift_histindex_next))
   1651   else
   1652     # 単に今回の history/option:s を無視すれば良いだけの時
   1653     ((N)) && [[ ${_ble_history[N-1]} == "$cmd" ]] && return 9
   1654   fi
   1655 }
   1656 
   1657 ## @fn ble/builtin/history/option:s
   1658 function ble/builtin/history/option:s {
   1659   ble/builtin/history/.initialize
   1660   if [[ $_ble_decode_bind_state == none ]]; then
   1661     builtin history -s -- "$@"
   1662     return 0
   1663   fi
   1664 
   1665   local cmd=$1
   1666   if [[ $HISTIGNORE ]]; then
   1667     local pats pat
   1668     ble/string#split pats : "$HISTIGNORE"
   1669     for pat in "${pats[@]}"; do
   1670       [[ $cmd == $pat ]] && return 0
   1671     done
   1672     # Note: 以降の処理では HISTIGNORE は無視する。trim した後のコマンドに対して
   1673     # 改めて作用するのを防ぐ為。
   1674     local HISTIGNORE=
   1675   fi
   1676 
   1677   # Note: ble/builtin/history/erasedups によって後の builtin history -s の為に
   1678   # 時的に erasedups を除去する場合がある為ローカル変数に変えておく。また、
   1679   # ignoreboth の処理の便宜の為にも内部的に書き換える。
   1680   local HISTCONTROL=$HISTCONTROL
   1681 
   1682   # Note: HISTIGNORE 及び ignorespace は trim 前に処理する。何故なら行頭の空白
   1683   # などに意味を持たせたいから。ignoredups 及び erasedups は trim 後に作用させ
   1684   # る。何故なら実際に履歴に登録されたコマンドと比較したいから。
   1685   if [[ $HISTCONTROL ]]; then
   1686     [[ :$HISTCONTROL: == *:ignoreboth:* ]] &&
   1687       HISTCONTROL=$HISTCONTROL:ignorespace:ignoredups
   1688     if [[ :$HISTCONTROL: == *:ignorespace:* ]]; then
   1689       [[ $cmd == [' 	']* ]] && return 0
   1690     fi
   1691 
   1692     if [[ :$HISTCONTROL: == *:strip:* ]]; then
   1693       local ret
   1694       ble/string#rtrim "$cmd"
   1695       ble/string#match "$ret" $'^[ \t]*(\n([ \t]*\n)*)?'
   1696       cmd=${ret:${#BASH_REMATCH}}
   1697       [[ $BASH_REMATCH == *$'\n'* && $cmd == *$'\n'* ]] && cmd=$'\n'$cmd
   1698     fi
   1699   fi
   1700 
   1701   local use_bash300wa=
   1702   if [[ $_ble_history_load_done ]]; then
   1703     if [[ $HISTCONTROL ]]; then
   1704       if [[ :$HISTCONTROL: == *:ignoredups:* ]]; then
   1705         # Note: plain Bash では ignoredups を検出した時には erasedups は発生し
   1706         # ない様なのでそれに倣う。
   1707         local lastIndex=$((${#_ble_history[@]}-1))
   1708         ((lastIndex>=0)) && [[ $cmd == "${_ble_history[lastIndex]}" ]] && return 0
   1709       fi
   1710       if [[ :$HISTCONTROL: == *:erasedups:* ]]; then
   1711         ble/builtin/history/erasedups "$cmd"
   1712         (($?==9)) && return 0
   1713       fi
   1714     fi
   1715     local topIndex=${#_ble_history[@]}
   1716     _ble_history[topIndex]=$cmd
   1717     _ble_history_edit[topIndex]=$cmd
   1718     _ble_history_count=$((topIndex+1))
   1719     _ble_history_index=$_ble_history_count
   1720 
   1721     # _ble_bash<30100 の時は必ずここを通る。
   1722     # 初期化時に _ble_history_load_done=1 になるので。
   1723     ((_ble_bash<30100)) && use_bash300wa=1
   1724   else
   1725     if [[ $HISTCONTROL ]]; then
   1726       # 未だ履歴が初期化されていない場合は取り敢えず history -s に渡す。
   1727       # history -s でも HISTCONTROL に対するフィルタはされる。
   1728       # history -s で項目が追加されたかどうかはスクリプトからは分からないので
   1729       # _ble_history_count をクリアして再計算する
   1730       _ble_history_count=
   1731     else
   1732       # HISTCONTROL がなければ多分 history -s で必ず追加される。
   1733       # _ble_history_count 取得済ならば更新。
   1734       [[ $_ble_history_count ]] &&
   1735         ((_ble_history_count++))
   1736     fi
   1737   fi
   1738   ble/history/.update-position
   1739 
   1740   if [[ $use_bash300wa ]]; then
   1741     # bash < 3.1 workaround
   1742     if [[ $cmd == *$'\n'* ]]; then
   1743       # Note: 改行を含む場合は %q は常に $'' の形式になる。
   1744       ble/util/sprintf cmd 'eval -- %q' "$cmd"
   1745     fi
   1746     local tmp=$_ble_base_run/$$.history.tmp
   1747     [[ ${HISTFILE-} && ! $bleopt_history_share ]] &&
   1748       ble/util/print "$cmd" >> "${HISTFILE-}"
   1749     ble/util/print "$cmd" >| "$tmp"
   1750     builtin history -r "$tmp"
   1751   else
   1752     ble/history:bash/clear-background-load
   1753     builtin history -s -- "$cmd"
   1754   fi
   1755   local max; ble/builtin/history/.get-max
   1756   _ble_builtin_history_prevmax=$max
   1757 }
   1758 function ble/builtin/history {
   1759   local set shopt; ble/base/.adjust-bash-options set shopt
   1760   local opt_d= flag_error=
   1761   local opt_c= opt_p= opt_s=
   1762   local opt_a= flags=
   1763   while [[ $1 == -* ]]; do
   1764     local arg=$1; shift
   1765     [[ $arg == -- ]] && break
   1766 
   1767     if [[ $arg == --help ]]; then
   1768       flags=h$flags
   1769       continue
   1770     fi
   1771 
   1772     local i n=${#arg}
   1773     for ((i=1;i<n;i++)); do
   1774       local c=${arg:i:1}
   1775       case $c in
   1776       (c) opt_c=1 ;;
   1777       (s) opt_s=1 ;;
   1778       (p) opt_p=1 ;;
   1779       (d)
   1780         if ((!$#)); then
   1781           ble/util/print 'ble/builtin/history: missing option argument for "-d".' >&2
   1782           flag_error=1
   1783         elif ((i+1<n)); then
   1784           opt_d=${arg:i+1}; i=$n
   1785         else
   1786           opt_d=$1; shift
   1787         fi ;;
   1788       ([anwr])
   1789         if [[ $opt_a && $c != $opt_a ]]; then
   1790           ble/util/print 'ble/builtin/history: cannot use more than one of "-anrw".' >&2
   1791           flag_error=1
   1792         elif ((i+1<n)); then
   1793           opt_a=$c
   1794           set -- "${arg:i+1}" "$@"
   1795         else
   1796           opt_a=$c
   1797         fi ;;
   1798       (*)
   1799         ble/util/print "ble/builtin/history: unknown option \"-$c\"." >&2
   1800         flag_error=1 ;;
   1801       esac
   1802     done
   1803   done
   1804   if [[ $flag_error ]]; then
   1805     builtin history --usage 2>&1 1>/dev/null | ble/bin/grep ^history >&2
   1806     ble/base/.restore-bash-options set shopt
   1807     return 2
   1808   fi
   1809 
   1810   if [[ $flags == *h* ]]; then
   1811     builtin history --help
   1812     local ext=$?
   1813     ble/base/.restore-bash-options set shopt
   1814     return "$ext"
   1815   fi
   1816 
   1817   [[ ! $_ble_attached || $_ble_edit_exec_inside_userspace ]] &&
   1818     ble/base/adjust-BASH_REMATCH
   1819 
   1820   # -cdanwr
   1821   local flag_processed=
   1822   if [[ $opt_c ]]; then
   1823     ble/builtin/history/option:c
   1824     flag_processed=1
   1825   fi
   1826   if [[ $opt_s ]]; then
   1827     local IFS=$_ble_term_IFS
   1828     ble/builtin/history/option:s "$*"
   1829     flag_processed=1
   1830   elif [[ $opt_d ]]; then
   1831     ble/builtin/history/option:d "$opt_d"
   1832     flag_processed=1
   1833   elif [[ $opt_a ]]; then
   1834     ble/builtin/history/option:"$opt_a" "$@"
   1835     flag_processed=1
   1836   fi
   1837   if [[ $flag_processed ]]; then
   1838     ble/base/.restore-bash-options set shopt
   1839     return 0
   1840   fi
   1841 
   1842   # -p
   1843   if [[ $opt_p ]]; then
   1844     ble/builtin/history/option:p "$@"
   1845   else
   1846     builtin history "$@"
   1847   fi; local ext=$?
   1848 
   1849   [[ ! $_ble_attached || $_ble_edit_exec_inside_userspace ]] &&
   1850     ble/base/restore-BASH_REMATCH
   1851   ble/base/.restore-bash-options set shopt
   1852   return "$ext"
   1853 }
   1854 function history { ble/builtin/history "$@"; }
   1855 
   1856 #==============================================================================
   1857 # ble/history                                                          @history
   1858 
   1859 ## @var _ble_history_prefix
   1860 ##
   1861 ##   現在どの履歴を対象としているかを保持する。
   1862 ##   空文字列の時、コマンド履歴を対象とする。以下の変数を用いる。
   1863 ##
   1864 ##     _ble_history
   1865 ##     _ble_history_index
   1866 ##     _ble_history_edit
   1867 ##     _ble_history_dirt
   1868 ##
   1869 ##   空でない文字列 prefix のとき、以下の変数を操作対象とする。
   1870 ##
   1871 ##     ${prefix}_history
   1872 ##     ${prefix}_history_index
   1873 ##     ${prefix}_history_edit
   1874 ##     ${prefix}_history_dirt
   1875 ##
   1876 ##   何れの関数も _ble_history_prefix を適切に処理する必要がある。
   1877 ##
   1878 ##   実装のために配列 _ble_history_edit などを
   1879 ##   ローカルに定義して処理するときは、以下の注意点を守る必要がある。
   1880 ##
   1881 ##   - その関数自身またはそこから呼び出される関数が、
   1882 ##     履歴項目に対して副作用を持ってはならない。
   1883 ##
   1884 ##   この要請の下で、各関数は呼び出し元のすり替えを意識せずに動作できる。
   1885 ##
   1886 _ble_history_prefix=
   1887 
   1888 function ble/history/set-prefix {
   1889   _ble_history_prefix=$1
   1890   ble/history/.update-position
   1891 }
   1892 
   1893 _ble_history_COUNT=
   1894 _ble_history_INDEX=
   1895 function ble/history/.update-position {
   1896   if [[ $_ble_history_prefix ]]; then
   1897     builtin eval -- "_ble_history_COUNT=\${#${_ble_history_prefix}_history[@]}"
   1898     ((_ble_history_INDEX=${_ble_history_prefix}_history_index))
   1899   else
   1900     # 履歴読込完了前の時
   1901     if [[ ! $_ble_history_load_done ]]; then
   1902       if [[ ! $_ble_history_count ]]; then
   1903         local min max
   1904         ble/builtin/history/.get-min
   1905         ble/builtin/history/.get-max
   1906         ((_ble_history_count=max-min+1))
   1907       fi
   1908       _ble_history_index=$_ble_history_count
   1909     fi
   1910 
   1911     _ble_history_COUNT=$_ble_history_count
   1912     _ble_history_INDEX=$_ble_history_index
   1913   fi
   1914 }
   1915 function ble/history/update-position {
   1916   [[ $_ble_history_prefix$_ble_history_load_done ]] ||
   1917     ble/history/.update-position
   1918 }
   1919 
   1920 ## @hook history_leave (defined in def.sh)
   1921 
   1922 function ble/history/onleave.fire {
   1923   blehook/invoke history_leave "$@"
   1924 }
   1925 
   1926 ## called by ble-edit/initialize in Bash 3
   1927 function ble/history/initialize {
   1928   [[ ! $_ble_history_prefix ]] &&
   1929     ble/history:bash/initialize
   1930 }
   1931 function ble/history/get-count {
   1932   local _ble_local_var=count
   1933   [[ $1 == -v ]] && { _ble_local_var=$2; shift 2; }
   1934   ble/history/.update-position
   1935   (($_ble_local_var=_ble_history_COUNT))
   1936 }
   1937 function ble/history/get-index {
   1938   local _ble_local_var=index
   1939   [[ $1 == -v ]] && { _ble_local_var=$2; shift 2; }
   1940   ble/history/.update-position
   1941   (($_ble_local_var=_ble_history_INDEX))
   1942 }
   1943 function ble/history/set-index {
   1944   _ble_history_INDEX=$1
   1945   ((${_ble_history_prefix:-_ble}_history_index=_ble_history_INDEX))
   1946 }
   1947 function ble/history/get-entry {
   1948   local _ble_local_var=entry
   1949   [[ $1 == -v ]] && { _ble_local_var=$2; shift 2; }
   1950   if [[ $_ble_history_prefix$_ble_history_load_done ]]; then
   1951     builtin eval -- "$_ble_local_var=\${${_ble_history_prefix:-_ble}_history[\$1]}"
   1952   else
   1953     builtin eval -- "$_ble_local_var="
   1954   fi
   1955 }
   1956 function ble/history/get-edited-entry {
   1957   local _ble_local_var=entry
   1958   [[ $1 == -v ]] && { _ble_local_var=$2; shift 2; }
   1959   if [[ $_ble_history_prefix$_ble_history_load_done ]]; then
   1960     builtin eval -- "$_ble_local_var=\${${_ble_history_prefix:-_ble}_history_edit[\$1]}"
   1961   else
   1962     builtin eval -- "$_ble_local_var=\$_ble_edit_str"
   1963   fi
   1964 }
   1965 ## @fn ble/history/set-edited-entry index str
   1966 function ble/history/set-edited-entry {
   1967   ble/history/initialize
   1968   local index=$1 str=$2
   1969   local code='
   1970     # store
   1971     if [[ ! ${PREFIX_history_edit[index]+set} || ${PREFIX_history_edit[index]} != "$str" ]]; then
   1972       PREFIX_history_edit[index]=$str
   1973       PREFIX_history_dirt[index]=1
   1974     fi'
   1975   builtin eval -- "${code//PREFIX/${_ble_history_prefix:-_ble}}"
   1976 }
   1977 
   1978 ## @fn ble/history/.add-command-history command
   1979 ## @var[in,out] HISTINDEX_NEXT
   1980 ##   used by ble/widget/accept-and-next to get modified next-entry positions
   1981 function ble/history/.add-command-history {
   1982   # 注意: bash-3.2 未満では何故か bind -x の中では常に history off になっている。
   1983   [[ -o history ]] || ((_ble_bash<30200)) || return 1
   1984 
   1985   # Note: mc (midnight commander) が初期化スクリプトを送ってくる #D1392
   1986   [[ $MC_SID == $$ && $_ble_edit_LINENO -le 2 && ( $1 == *PROMPT_COMMAND=* || $1 == *PS1=* ) ]] && return 1
   1987 
   1988   if [[ $_ble_history_load_done ]]; then
   1989     # 登録・不登録に拘わらず取り敢えず初期化
   1990     _ble_history_index=${#_ble_history[@]}
   1991     ble/history/.update-position
   1992 
   1993     # _ble_history_edit を未編集状態に戻す
   1994     local index
   1995     for index in "${!_ble_history_dirt[@]}"; do
   1996       _ble_history_edit[index]=${_ble_history[index]}
   1997     done
   1998     _ble_history_dirt=()
   1999 
   2000     # 同時に _ble_edit_undo も初期化する。
   2001     ble-edit/undo/clear-all
   2002   fi
   2003 
   2004   if [[ $bleopt_history_share ]]; then
   2005     ble/builtin/history/option:n
   2006     ble/builtin/history/option:s "$1"
   2007     ble/builtin/history/option:a
   2008     ble/builtin/history/.touch-histfile
   2009   else
   2010     ble/builtin/history/option:s "$1"
   2011   fi
   2012 }
   2013 
   2014 function ble/history/add {
   2015   local command=$1
   2016   ((bleopt_history_limit_length>0&&${#command}>bleopt_history_limit_length)) && return 1
   2017 
   2018   if [[ $_ble_history_prefix ]]; then
   2019     local code='
   2020       # PREFIX_history_edit を未編集状態に戻す
   2021       local index
   2022       for index in "${!PREFIX_history_dirt[@]}"; do
   2023         PREFIX_history_edit[index]=${PREFIX_history[index]}
   2024       done
   2025       PREFIX_history_dirt=()
   2026 
   2027       local topIndex=${#PREFIX_history[@]}
   2028       PREFIX_history[topIndex]=$command
   2029       PREFIX_history_edit[topIndex]=$command
   2030       PREFIX_history_index=$((++topIndex))
   2031       _ble_history_COUNT=$topIndex
   2032       _ble_history_INDEX=$topIndex'
   2033     builtin eval -- "${code//PREFIX/$_ble_history_prefix}"
   2034   else
   2035     blehook/invoke ADDHISTORY "$command" &&
   2036       ble/history/.add-command-history "$command"
   2037   fi
   2038 }
   2039 
   2040 #------------------------------------------------------------------------------
   2041 # ble/history/search                                            @history.search
   2042 
   2043 ## @fn ble/history/isearch-forward opts
   2044 ## @fn ble/history/isearch-backward opts
   2045 ## @fn ble/history/isearch-backward-blockwise opts
   2046 ##
   2047 ##   backward-search-history-blockwise does blockwise search
   2048 ##   as a workaround for bash slow array access
   2049 ##
   2050 ##   @param[in] opts
   2051 ##     コロン区切りのオプションです。
   2052 ##
   2053 ##     regex     正規表現による検索を行います。
   2054 ##     glob      グロブパターンによる一致を試みます。
   2055 ##     head      固定文字列に依る先頭一致を試みます。
   2056 ##     tail      固定文字列に依る終端一致を試みます。
   2057 ##     condition 述語コマンドを評価 (eval) して一致を試みます。
   2058 ##     predicate 述語関数を呼び出して一致を試みます。
   2059 ##       これらの内の何れか一つを指定します。
   2060 ##       何も指定しない場合は固定文字列の部分一致を試みます。
   2061 ##
   2062 ##     stop_check
   2063 ##       ユーザの入力があった時に終了ステータス 148 で中断します。
   2064 ##
   2065 ##     progress
   2066 ##       検索の途中経過を表示します。
   2067 ##       後述の isearch_progress_callback 変数に指定された関数を呼び出します。
   2068 ##
   2069 ##     backward
   2070 ##       内部使用のオプションです。
   2071 ##       forward-search-history に対して指定して、後方検索を行う事を指定します。
   2072 ##
   2073 ##     cyclic
   2074 ##       履歴の端まで達した時、履歴の反対側の端から検索を続行します。
   2075 ##       一致が見つからずに start の直前の要素まで達した時に失敗します。
   2076 ##
   2077 ##   @var[in] _ble_history_edit
   2078 ##     検索対象の配列と全体の検索開始位置を指定します。
   2079 ##   @var[in] start
   2080 ##     全体の検索開始位置を指定します。
   2081 ##
   2082 ##   @var[in] needle
   2083 ##     検索文字列を指定します。
   2084 ##
   2085 ##     opts に regex または glob を指定した場合は、
   2086 ##     それぞれ正規表現またはグロブパターンを指定します。
   2087 ##
   2088 ##     opts に condition を指定した場合は needle を述語コマンドと解釈します。
   2089 ##     変数 LINE 及び INDEX にそれぞれ行の内容と履歴番号を設定して eval されます。
   2090 ##
   2091 ##     opts に predicate を指定した場合は needle を述語関数の関数名と解釈します。
   2092 ##     指定する述語関数は検索が一致した時に成功し、それ以外の時に失敗する関数です。
   2093 ##     第1引数と第2引数に行の内容と履歴番号を指定して関数が呼び出されます。
   2094 ##
   2095 ##   @var[in,out] index
   2096 ##     今回の呼び出しの検索開始位置を指定します。
   2097 ##     一致が成功したとき見つかった位置を返します。
   2098 ##     一致が中断されたとき次の位置 (再開時に最初に検査する位置) を返します。
   2099 ##
   2100 ##   @var[in,out] isearch_time
   2101 ##
   2102 ##   @var[in] isearch_progress_callback
   2103 ##     progress の表示時に呼び出す関数名を指定します。
   2104 ##     第一引数には現在の検索位置 (history index) を指定します。
   2105 ##
   2106 ##   @exit
   2107 ##     見つかったときに 0 を返します。
   2108 ##     見つからなかったときに 1 を返します。
   2109 ##     中断された時に 148 を返します。
   2110 ##
   2111 function ble/history/.read-isearch-options {
   2112   local opts=$1
   2113 
   2114   search_type=fixed
   2115   case :$opts: in
   2116   (*:regex:*)     search_type=regex ;;
   2117   (*:glob:*)      search_type=glob  ;;
   2118   (*:head:*)      search_type=head ;;
   2119   (*:tail:*)      search_type=tail ;;
   2120   (*:condition:*) search_type=condition ;;
   2121   (*:predicate:*) search_type=predicate ;;
   2122   esac
   2123 
   2124   [[ :$opts: != *:stop_check:* ]]; has_stop_check=$?
   2125   [[ :$opts: != *:progress:* ]]; has_progress=$?
   2126   [[ :$opts: != *:backward:* ]]; has_backward=$?
   2127 }
   2128 function ble/history/isearch-backward-blockwise {
   2129   local opts=$1
   2130   local search_type has_stop_check has_progress has_backward
   2131   ble/history/.read-isearch-options "$opts"
   2132 
   2133   ble/history/initialize
   2134   if [[ $_ble_history_prefix ]]; then
   2135     local -a _ble_history_edit
   2136     builtin eval "_ble_history_edit=(\"\${${_ble_history_prefix}_history_edit[@]}\")"
   2137   fi
   2138 
   2139   local isearch_block=1000 # 十分高速なのでこれぐらい大きくてOK
   2140   local isearch_quantum=$((isearch_block*2)) # 倍数である必要有り
   2141   local irest block j i=$index
   2142   index=
   2143 
   2144   local flag_icase=; [[ :$opts: == *:ignore-case:* ]] && flag_icase=1
   2145 
   2146   local flag_cycled= range_min range_max
   2147   while :; do
   2148     if ((i<=start)); then
   2149       range_min=0 range_max=$start
   2150     else
   2151       flag_cycled=1
   2152       range_min=$((start+1)) range_max=$i
   2153     fi
   2154 
   2155     while ((i>=range_min)); do
   2156       ((block=range_max-i,
   2157         block<5&&(block=5),
   2158         block>i+1-range_min&&(block=i+1-range_min),
   2159         irest=isearch_block-isearch_time%isearch_block,
   2160         block>irest&&(block=irest)))
   2161 
   2162       [[ $flag_icase ]] && shopt -s nocasematch
   2163       case $search_type in
   2164       (regex)     for ((j=i-block;++j<=i;)); do
   2165                     [[ ${_ble_history_edit[j]} =~ $needle ]] && index=$j
   2166                   done ;;
   2167       (glob)      for ((j=i-block;++j<=i;)); do
   2168                     [[ ${_ble_history_edit[j]} == $needle ]] && index=$j
   2169                   done ;;
   2170       (head)      for ((j=i-block;++j<=i;)); do
   2171                     [[ ${_ble_history_edit[j]} == "$needle"* ]] && index=$j
   2172                   done ;;
   2173       (tail)      for ((j=i-block;++j<=i;)); do
   2174                     [[ ${_ble_history_edit[j]} == *"$needle" ]] && index=$j
   2175                   done ;;
   2176       (condition) builtin eval "function ble-edit/isearch/.search-block.proc {
   2177                     local LINE INDEX
   2178                     for ((j=i-block;++j<=i;)); do
   2179                       LINE=\${_ble_history_edit[j]} INDEX=\$j
   2180                       { $needle; } && index=\$j
   2181                     done
   2182                   }"
   2183                   ble-edit/isearch/.search-block.proc ;;
   2184       (predicate) for ((j=i-block;++j<=i;)); do
   2185                     "$needle" "${_ble_history_edit[j]}" "$j" && index=$j
   2186                   done ;;
   2187       (*)         for ((j=i-block;++j<=i;)); do
   2188                     [[ ${_ble_history_edit[j]} == *"$needle"* ]] && index=$j
   2189                   done ;;
   2190       esac
   2191       [[ $flag_icase ]] && shopt -u nocasematch
   2192 
   2193       ((isearch_time+=block))
   2194       [[ $index ]] && return 0
   2195 
   2196       ((i-=block))
   2197       if ((has_stop_check&&isearch_time%isearch_block==0)) && ble/decode/has-input; then
   2198         index=$i
   2199         return 148
   2200       elif ((has_progress&&isearch_time%isearch_quantum==0)); then
   2201         "$isearch_progress_callback" "$i"
   2202       fi
   2203     done
   2204 
   2205     if [[ ! $flag_cycled && :$opts: == *:cyclic:* ]]; then
   2206       ((i=${#_ble_history_edit[@]}-1))
   2207       ((start<i)) || return 1
   2208     else
   2209       return 1
   2210     fi
   2211   done
   2212 }
   2213 function ble/history/isearch-forward.impl {
   2214   local opts=$1
   2215   local search_type has_stop_check has_progress has_backward
   2216   ble/history/.read-isearch-options "$opts"
   2217 
   2218   ble/history/initialize
   2219   if [[ $_ble_history_prefix ]]; then
   2220     local -a _ble_history_edit
   2221     builtin eval "_ble_history_edit=(\"\${${_ble_history_prefix}_history_edit[@]}\")"
   2222   fi
   2223 
   2224   local flag_icase=; [[ :$opts: == *:ignore-case:* ]] && flag_icase=1
   2225 
   2226   while :; do
   2227     local flag_cycled= expr_cond expr_incr
   2228     if ((has_backward)); then
   2229       if ((index<=start)); then
   2230         expr_cond='index>=0' expr_incr='index--'
   2231       else
   2232         expr_cond='index>start' expr_incr='index--' flag_cycled=1
   2233       fi
   2234     else
   2235       if ((index>=start)); then
   2236         expr_cond="index<${#_ble_history_edit[@]}" expr_incr='index++'
   2237       else
   2238         expr_cond="index<start" expr_incr='index++' flag_cycled=1
   2239       fi
   2240     fi
   2241 
   2242     [[ $flag_icase ]] && shopt -s nocasematch
   2243     case $search_type in
   2244     (regex)
   2245 #%define search_loop
   2246       for ((;expr_cond;expr_incr)); do
   2247         ((isearch_time++,has_stop_check&&isearch_time%100==0)) &&
   2248           ble/decode/has-input && return 148
   2249         @ && return 0
   2250         ((has_progress&&isearch_time%1000==0)) &&
   2251           "$isearch_progress_callback" "$index"
   2252       done ;;
   2253 #%end
   2254 #%expand search_loop.r/@/[[ ${_ble_history_edit[index]} =~ $needle ]]/
   2255     (glob)
   2256 #%expand search_loop.r/@/[[ ${_ble_history_edit[index]} == $needle ]]/
   2257     (head)
   2258 #%expand search_loop.r/@/[[ ${_ble_history_edit[index]} == "$needle"* ]]/
   2259     (tail)
   2260 #%expand search_loop.r/@/[[ ${_ble_history_edit[index]} == *"$needle" ]]/
   2261     (condition)
   2262 #%expand search_loop.r/@/LINE=${_ble_history_edit[index]} INDEX=$index builtin eval -- "$needle"/
   2263     (predicate)
   2264 #%expand search_loop.r/@/"$needle" "${_ble_history_edit[index]}" "$index"/
   2265     (*)
   2266 #%expand search_loop.r/@/[[ ${_ble_history_edit[index]} == *"$needle"* ]]/
   2267     esac
   2268     [[ $flag_icase ]] && shopt -u nocasematch
   2269 
   2270     if [[ ! $flag_cycled && :$opts: == *:cyclic:* ]]; then
   2271       if ((has_backward)); then
   2272         ((index=${#_ble_history_edit[@]}-1))
   2273         ((index>start)) || return 1
   2274       else
   2275         ((index=0))
   2276         ((index<start)) || return 1
   2277       fi
   2278     else
   2279       return 1
   2280     fi
   2281   done
   2282 }
   2283 function ble/history/isearch-forward {
   2284   ble/history/isearch-forward.impl "$1"
   2285 }
   2286 function ble/history/isearch-backward {
   2287   ble/history/isearch-forward.impl "$1:backward"
   2288 }