sistema_progs

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

histdb.bash (22441B)


      1 # blesh/contrib/histdb (C) 2023, Koichi Murase <myoga.murase@gmail.com>
      2 
      3 ble/util/assign _ble_histdb_sqlite3 'builtin type -P sqlite3 2>/dev/null'
      4 if [[ ! -x $_ble_histdb_sqlite3 ]]; then
      5   ble/util/print 'contrib/histdb: "sqlite3" is required for this module.' >&2
      6   return 1
      7 fi
      8 
      9 ble-import util.bgproc
     10 
     11 _ble_histdb_version=3
     12 
     13 #------------------------------------------------------------------------------
     14 # histdb_remarks
     15 
     16 bleopt/declare -v histdb_remarks 'git: \q{contrib/histdb-remarks-git}'
     17 
     18 _ble_histdb_remarks_data=()
     19 function ble/prompt/unit:_ble_histdb_remarks/update {
     20   ble/prompt/unit/add-hash '$bleopt_histdb_remarks'
     21   ble/prompt/unit:{section}/update _ble_histdb_remarks "$bleopt_histdb_remarks" no-trace
     22 }
     23 
     24 ## @fn ble/histdb/.get-remarks
     25 ##   @var[out] remarks
     26 function ble/histdb/.get-remarks {
     27   remarks=
     28   if [[ $bleopt_histdb_remarks ]]; then
     29     local ret
     30     ble/prompt/unit#update _ble_histdb_remarks "$bleopt_histdb_remarks"
     31     ble/prompt/unit:{section}/get _ble_histdb_remarks
     32     remarks=$ret
     33   fi
     34 }
     35 
     36 function ble/prompt/backslash:contrib/histdb-remarks-git {
     37   ble-import prompt-git
     38 
     39   function ble/prompt/backslash:contrib/histdb-remarks-git {
     40     local "${_ble_contrib_prompt_git_vars[@]/%/=}" # WA #D1570 checked
     41     ble/contrib/prompt-git/initialize || return 0
     42     ble/contrib/prompt-git/update-head-information
     43     local state; ble/contrib/prompt-git/get-state
     44     local dirty_mark; ble/contrib/prompt-git/get-dirty-mark
     45 
     46     local path=${git_base%.git}
     47     path=${path%/}
     48     path=${path##*?/}
     49     [[ $PWD == "$git_base"/?* ]] &&
     50       path="$path/${PWD#$git_base/}"
     51 
     52     ble/prompt/print "git: ${branch:-(detached)} ${hash:-(orphan)}$dirty_mark${state:+ $state} @ ${path:-/}"
     53   } && "$FUNCNAME"
     54 }
     55 
     56 #------------------------------------------------------------------------------
     57 # background sqlite3 process
     58 
     59 bleopt/declare -v histdb_ignore ''
     60 bleopt/declare -v histdb_file ''
     61 
     62 function ble/histdb/.get-filename {
     63   local basename=${bleopt_histdb_file%.sqlite3}
     64   local hostname=${HOSTNAME:-$_ble_base_env_HOSTNAME}
     65   hostname=${hostname//'/'/'%'} # filename cannot contian /
     66   ret=${basename:-$_ble_base_state/history}${hostname:+@$hostname}.sqlite3
     67 }
     68 function ble/histdb/.get-time {
     69   if ((_ble_bash>=50000)); then
     70     time=$EPOCHSECONDS
     71   elif ((_ble_bash>=40200)); then
     72     printf -v time '%(%s)T' -1
     73   else
     74     ((time=${_ble_base_session%%[./]*}+SECONDS))
     75   fi
     76 }
     77 
     78 ## @var _ble_histdb_file
     79 _ble_histdb_file=
     80 
     81 ## @arr _ble_histdb_bgproc=(fd_response fd_request - bgpid)
     82 ##
     83 _ble_histdb_bgproc=()
     84 _ble_histdb_bgproc_fname=()
     85 _ble_histdb_timeout=5000
     86 _ble_histdb_keepalive=1800 # sec
     87 
     88 function ble/histdb/escape-for-sqlite-glob {
     89   ret=$1
     90   if [[ $ret == *["[*?"]* ]]; then
     91     local a b
     92     for a in '[' '*' '?'; do
     93       b=[$a] ret=${ret//"$a"/"$b"}
     94     done
     95   fi
     96 }
     97 
     98 function ble/histdb/sqlite3.flush {
     99   if [[ ${_ble_histdb_bgproc[0]} && _ble_bash -ge 40000 ]]; then
    100     local line IFS=
    101     ble/bash/read-timeout 0.001 -r -d '' line <&"${_ble_histdb_bgproc[0]}"
    102   fi
    103 }
    104 function ble/histdb/sqlite3.request {
    105   ble/histdb/sqlite3.open
    106   if [[ ${_ble_histdb_bgproc[1]} ]]; then
    107     ble/histdb/sqlite3.flush
    108     ble/util/print "$1" >&"${_ble_histdb_bgproc[1]}"
    109   else
    110     [[ $1 == .exit ]] && return 0
    111     "$_ble_histdb_sqlite3" -quote "$_ble_histdb_file" ".timeout $_ble_histdb_timeout" "$1"
    112   fi
    113 }
    114 
    115 function ble/histdb/read-single-value {
    116   local line nl=$'\n' q=\' qq=\'\' Q="'\''"
    117   local IFS=
    118   if ble/bash/read line && [[ $line == \'* ]]; then
    119     local out=$line ext=0
    120     while ((ext==0)) && ! ble/string#match "$out" '^'"$q"'([^'\'']|'"$qq"')*'"$q"'$'; do
    121       ble/bash/read line; ext=$?
    122       out=$out$nl$line
    123     done
    124     line=$out
    125   fi
    126   line=${line#$q}
    127   line=${line%$q}
    128   line=${line//$qq/$q}
    129   line=$q${line//$q/$Q}$q
    130   ble/util/print "ret=$line"
    131 }
    132 
    133 function ble/histdb/sqlite3.request-single-value {
    134   local query=$1 opts=$2
    135   ble/histdb/sqlite3.open
    136   if [[ ${_ble_histdb_bgproc[1]} && _ble_bash -ge 40000 ]]; then
    137     ble/histdb/sqlite3.flush
    138     ble/util/print "$query" >&"${_ble_histdb_bgproc[1]}"
    139 
    140     local sync_command='ble/histdb/read-single-value <&"${_ble_histdb_bgproc[0]}"'
    141     local sync_condition='((sync_elapsed<200)) || ! ble/complete/check-cancel'
    142     local sync_opts=progressive-weight
    143     [[ :$opts: == *:auto:* && $bleopt_complete_timeout_auto ]] &&
    144       sync_opts=$sync_opts:timeout=$((bleopt_complete_timeout_auto))
    145     ble/util/assign ret 'ble/util/conditional-sync "$sync_command" "$sync_condition" "" "$sync_opts"' &>/dev/null; local ext=$?
    146     builtin eval -- "$ret"
    147     ((ext==142)) && ext=148
    148     return "$ext"
    149   else
    150     local out q=\' qq=\'\'
    151     ble/util/assign out '"$_ble_histdb_sqlite3" -quote "$_ble_histdb_file" ".timeout $_ble_histdb_timeout" "$query"'
    152     out=${out#$q}
    153     out=${out%$q}
    154     ret=${out//$qq/$q}
    155   fi
    156 }
    157 
    158 function ble/histdb/sqlite3.proc {
    159   exec "$_ble_histdb_sqlite3" -quote -cmd "-- [ble: $BLE_SESSION_ID]" "$_ble_histdb_file"
    160 }
    161 
    162 _ble_histdb_keepalive_enabled=
    163 ble/is-function ble/util/idle.push &&
    164   _ble_histdb_keepalive_enabled=1
    165 
    166 ## @fn ble/histdb/sqlite3.open
    167 ##   @var[ref] _ble_histdb_file
    168 ##   @arr[out] _ble_histdb_bgproc
    169 ##   @arr[out] _ble_histdb_bgproc_fname
    170 function ble/histdb/sqlite3.open {
    171   if [[ $_ble_histdb_file ]]; then
    172     # background process に依らないモードの時は OK
    173     ble/util/bgproc#opened _ble_histdb || return 0
    174 
    175     ble/util/bgproc#alive _ble_histdb; local state=$?
    176     if ((state==0)); then
    177       ble/util/bgproc#keepalive _ble_histdb
    178       return 0
    179     else
    180       if ((state==3)); then
    181         # background process が死んでいる時は再度開き直す。
    182         ble/util/print 'histdb: background sqlite3 is inactive.' >&2
    183       fi
    184       ble/util/bgproc#start _ble_histdb
    185       return "$?"
    186     fi
    187   fi
    188 
    189   local ret; ble/histdb/.get-filename
    190   _ble_histdb_file=$ret
    191 
    192   local session_id=$_ble_base_session
    193   local start_time=${session_id%%/*}
    194 
    195   IFS=, builtin eval 'local groups="${GROUPS[*]}"'
    196   local user=${USER:-$_ble_base_env_USER}
    197   local hostname=${HOSTNAME:-$_ble_base_env_HOSTNAME}
    198 
    199   local screen_info= ssh_info=
    200   [[ $STY ]] && screen_info="$STY $WINDOW"
    201   [[ $SSH_TTY ]] && ssh_info="$SSH_TTY $SSH_CONNECTION"
    202 
    203   local bgproc_opts=owner-close-on-unload:kill-timeout=60000
    204   [[ $_ble_histdb_keepalive_enabled ]] &&
    205     bgproc_opts=$bgproc_opts:timeout=$((_ble_histdb_keepalive*1000))
    206   if ble/util/bgproc#open _ble_histdb ble/histdb/sqlite3.proc "$bgproc_opts"; (($?!=0&&$?!=3)); then
    207     # Note: システムが元々サポートしていない場合 ($? == 3) はエラーメッセージは
    208     # 出さない。
    209     local msg='[ble histdb: background sqlite3 failed to start]'
    210     ble/util/print "${_ble_term_setaf[9]}$msg$_ble_term_sgr0" >&2
    211   fi
    212 
    213   local ret q=\' qq=\'\'
    214   ble/histdb/sqlite3.request-single-value "
    215     BEGIN IMMEDIATE TRANSACTION;
    216     CREATE TABLE IF NOT EXISTS misc(key TEXT PRIMARY KEY, value INTEGER);
    217     INSERT OR IGNORE INTO misc values('version', $_ble_histdb_version);
    218     CREATE TABLE IF NOT EXISTS sessions(
    219       session_id TEXT PRIMARY KEY, pid INTEGER, ppid INTEGER,
    220       hostname TEXT, user TEXT, uid INTEGER, euid INTEGER, groups TEXT,
    221       start_time INTEGER, start_wd TEXT,
    222       bash_path TEXT, bash_version TEXT, shlvl INTEGER,
    223       blesh_path TEXT, blesh_version TEXT,
    224       term TEXT, lang TEXT, display TEXT, screen_info TEXT, ssh_info TEXT,
    225       tty TEXT, last_time INTEGER, last_wd TEXT);
    226     CREATE TABLE IF NOT EXISTS command_history(
    227       session_id TEXT, command_id INTEGER,
    228       lineno INTEGER, history_index INTEGER,
    229       command TEXT, cwd TEXT, inode INTEGER, issue_time INTEGER,
    230       status             INTEGER,
    231       pipestatus         TEXT,
    232       lastarg            TEXT,
    233       bgpid              INTEGER,
    234       exec_time          INTEGER,
    235       exec_time_beg      INTEGER,
    236       exec_time_end      INTEGER,
    237       exec_time_real     INTEGER,
    238       exec_time_usr      INTEGER,
    239       exec_time_sys      INTEGER,
    240       exec_time_cpu      INTEGER,
    241       exec_time_usr_self INTEGER,
    242       exec_time_sys_self INTEGER,
    243       exec_time_usr_chld INTEGER,
    244       exec_time_sys_chld INTEGER,
    245       remarks            TEXT,
    246       PRIMARY KEY (session_id, command_id));
    247     CREATE TABLE IF NOT EXISTS words(
    248       word TEXT PRIMARY KEY, count INTEGER, ctime INTEGER, mtime INTEGER);
    249     INSERT OR IGNORE INTO
    250       sessions(
    251         session_id, pid, ppid,
    252         hostname, user, uid, euid, groups,
    253         start_time, start_wd,
    254         bash_path, bash_version, shlvl,
    255         blesh_path, blesh_version,
    256         term, lang, display, screen_info, ssh_info,
    257         tty)
    258       VALUES(
    259         '${session_id//$q/$qq}', '${$//$q/$qq}', '${PPID//$q/$qq}',
    260         '${hostname//$q/$q}', '${user//$q/$qq}', '${UID//$q/$qq}', '${EUID/$q/$qq}', '${groups//$q/$qq}',
    261         '${start_time//$q/$qq}', '${PWD//$q/$qq}',
    262         '${BASH//$q/$qq}', '${BASH_VERSION//$q/$qq}', '${SHLVL//$q/$qq}',
    263         '${_ble_base_blesh//$q/$qq}', '${BLE_VERSION//$q/$qq}',
    264         '${TERM//$q/$qq}', '${LANG//$q/$qq}', '${DISPLAY//$q/$qq}', '${screen_info//$q/$qq}', '${ssh_info//$q/$qq}',
    265         '${_ble_prompt_const_l//$q/$qq}');
    266     COMMIT;
    267     SELECT VALUE FROM misc WHERE key = 'version';"
    268   version=$ret
    269 
    270   if [[ $version ]] && ((version<_ble_histdb_version)); then
    271     local query=
    272     ((version<2)) &&
    273       query=$query$_ble_term_nl"ALTER TABLE command_history ADD COLUMN inode INTEGER;"
    274     ((version<3)) &&
    275       query=$query$_ble_term_nl"ALTER TABLE command_history ADD COLUMN remarks TEXT;"
    276     query=$query$_ble_term_nl"UPDATE misc SET value = $_ble_histdb_version WHERE key = 'version';"
    277     ble/histdb/sqlite3.request "$query"
    278   fi
    279 }
    280 
    281 function ble/util/bgproc/onstart:_ble_histdb {
    282   ble/util/bgproc#post _ble_histdb ".timeout $_ble_histdb_timeout"
    283 }
    284 
    285 function ble/util/bgproc/onstop:_ble_histdb {
    286   ble/util/bgproc#post _ble_histdb '.exit'
    287 }
    288 
    289 function ble/histdb/sqlite3.close {
    290   [[ $_ble_histdb_file ]] || return 0
    291   local session_id=$_ble_base_session
    292   local time; ble/histdb/.get-time
    293   ble/histdb/sqlite3.request "
    294     UPDATE sessions SET
    295       last_time = '${time//$q/$qq}',
    296       last_wd = '${PWD//$q/$qq}'
    297       WHERE session_id = '${session_id//$q/$qq}';"
    298 
    299   ble/util/bgproc#close _ble_histdb
    300   _ble_histdb_file=
    301 }
    302 
    303 _ble_histdb_exec_ignore=()
    304 _ble_histdb_exec_cwd=
    305 _ble_histdb_exec_cwd_inode=
    306 
    307 function ble/histdb/update-cwd-inode {
    308   [[ $PWD == "$_ble_histdb_exec_cwd" ]] && return 0
    309 
    310   _ble_histdb_exec_cwd=$PWD
    311   _ble_histdb_exec_cwd_inode=
    312   if local ret; ble/file#inode "$PWD" && ble/string#match "$ret" '^[0-9]+$'; then
    313     _ble_histdb_exec_cwd_inode=$ret
    314   fi
    315 }
    316 
    317 function ble/histdb/collect-words.proc {
    318   [[ $wtype && ! ${wtype//[0-9]} ]] &&
    319     ble/array#push collect_words "${_ble_edit_str:wbeg:wlen}"
    320 }
    321 function ble/histdb/collect-words {
    322   ble-edit/content/update-syntax
    323   local -a collect_words=()
    324   ble/syntax/tree-enumerate-in-range 0 "${#_ble_edit_str}" ble/histdb/collect-words.proc
    325   ble/array#reverse collect_words
    326 
    327   ret=()
    328   local word
    329   "${_ble_util_set_declare[@]//NAME/mark}" # WA #D1570 checked
    330   for word in "${collect_words[@]}"; do
    331     # Note (#D1958): Even if the same words appeared in a single command, we
    332     #   only count 1 for one command.  This is because the "words" table is
    333     #   used for the word completion, where the count is used as an importance
    334     #   measure.  We count the number of commands where the word appears
    335     #   because the same word can easily appear in a single command multiple
    336     #   times.
    337     ble/set#contains mark "$word" && return 0
    338 
    339     ble/set#add mark "$word"
    340     ble/array#push ret "$word"
    341   done
    342 }
    343 
    344 ## @fn ble/histdb/exec_register.hook command
    345 ##   @var[in] command_id
    346 ##   @var[in] lineno
    347 ##     これらの変数は exec_register の側で用意される。
    348 function ble/histdb/exec_register.hook {
    349   local command=$1
    350   if [[ $bleopt_histdb_ignore ]]; then
    351     local patterns pattern
    352     ble/string#split patterns : "$bleopt_histdb_ignore"
    353     for pattern in "${patterns[@]}"; do
    354       if [[ $command == $pattern ]]; then
    355         _ble_histdb_exec_ignore[command_id]=1
    356         return 0
    357       fi
    358     done
    359   fi
    360   builtin unset -v '_ble_histdb_exec_ignore[command_id]'
    361 
    362   local q=\' qq=\'\'
    363   local session_id=$_ble_base_session
    364   local time; ble/histdb/.get-time; local issue_time=$time
    365 
    366   # @var history_index ... history index: The current command might not be
    367   # registered to the command history, but we always pick up the index of the
    368   # last entry because there is no way to check it reliably.  We could compare
    369   # the top element with BASH_COMMAND, but the history entry might be
    370   # transformed by HISTCONTROL=strip, etc.
    371   local history_index; ble/history/get-count -v history_index
    372   ((history_index++))
    373 
    374   # Expand "bleopt histdb_remarks"
    375   local remarks
    376   ble/histdb/.get-remarks
    377 
    378   local ret word extra_query=
    379   ble/histdb/collect-words
    380   for word in "${ret[@]}"; do
    381     extra_query=$extra_query"INSERT OR REPLACE INTO words(word, count, ctime, mtime)
    382       VALUES('${word//$q/$qq}',
    383       coalesce((SELECT count FROM words WHERE word = '${word//$q/$qq}'), 0) + 1,
    384       coalesce((SELECT ctime FROM words WHERE word = '${word//$q/$qq}'), $issue_time),
    385       $issue_time);"
    386   done
    387 
    388   ble/histdb/update-cwd-inode
    389   local inode=$_ble_histdb_exec_cwd_inode
    390 
    391   ble/histdb/sqlite3.request "
    392     BEGIN IMMEDIATE TRANSACTION;
    393     UPDATE sessions SET
    394       last_time = '${issue_time//$q/$qq}',
    395       last_wd = '${PWD//$q/$qq}'
    396       WHERE session_id = '${session_id//$q/$qq}';
    397     -- Update duplicate command_id
    398     UPDATE command_history SET
    399       command_id = min(0, (SELECT command_id FROM command_history WHERE session_id = '${session_id//$q/$qq}')) - 1
    400       WHERE session_id = '${session_id//$q/$qq}' AND command_id = $command_id;
    401     -- Add command_history entry
    402     INSERT INTO command_history(
    403         session_id, command_id,
    404         lineno, history_index,
    405         command, cwd, inode, issue_time, remarks)
    406       VALUES(
    407         '${session_id//$q/$qq}', '${command_id//$q/$qq}',
    408         '${lineno//$q/$qq}', '${history_index//$q/$qq}',
    409         '${command//$q/$qq}', '${PWD//$q/$qq}', ${inode:-None}, '${issue_time//$q/$qq}', '${remarks//$q/$qq}');
    410     $extra_query
    411     COMMIT;"
    412 }
    413 
    414 function ble/histdb/postexec.hook {
    415   local session_id=$_ble_base_session
    416   local command_id=$_ble_edit_exec_command_id
    417   if [[ ${_ble_histdb_exec_ignore[command_id]+set} ]]; then
    418     builtin unset -v '_ble_histdb_exec_ignore[command_id]'
    419     return 0
    420   fi
    421 
    422   local time; ble/histdb/.get-time; local last_time=$time
    423 
    424   IFS=, builtin eval 'local pipestatus="${_ble_edit_exec_PIPESTATUS[*]}"'
    425   local lastarg=$_ble_edit_exec_lastarg
    426   ((${#lastarg}>=1000)) && lastarg=${lastarg::997}...
    427 
    428   local real=$_ble_exec_time_tot
    429   local usr=$_ble_exec_time_usr
    430   local sys=$_ble_exec_time_sys
    431   local cpu=$((real>0?(usr+sys)/real:0))
    432   local usr_chld=$((usr-_ble_exec_time_usr_self))
    433   local sys_chld=$((sys-_ble_exec_time_sys_self))
    434 
    435   local q=\' qq=\'\'
    436   ble/histdb/sqlite3.request "
    437     UPDATE sessions SET
    438       last_time = '${last_time//$q/$qq}',
    439       last_wd = '${PWD//$q/$qq}'
    440       WHERE session_id = '${session_id//$q/$qq}';
    441     UPDATE command_history SET
    442       status             = '${_ble_edit_exec_lastexit//$q/$qq}',
    443       pipestatus         = '${pipestatus//$q/$qq}',
    444       lastarg            = '${lastarg//$q/$qq}',
    445       bgpid              = '${!//$q/$qq}',
    446       exec_time          = '${_ble_exec_time_ata//$q/$qq}',
    447       exec_time_beg      = '${_ble_exec_time_beg//$q/$qq}',
    448       exec_time_end      = '${_ble_exec_time_end//$q/$qq}',
    449       exec_time_real     = '${real//$q/$qq}',
    450       exec_time_usr      = '${usr//$q/$qq}',
    451       exec_time_sys      = '${sys//$q/$qq}',
    452       exec_time_cpu      = '${cpu//$q/$qq}',
    453       exec_time_usr_self = '${_ble_exec_time_usr_self//$q/$qq}',
    454       exec_time_sys_self = '${_ble_exec_time_sys_self//$q/$qq}',
    455       exec_time_usr_chld = '${usr_chld//$q/$qq}',
    456       exec_time_sys_chld = '${sys_chld//$q/$qq}'
    457       WHERE session_id = '${session_id//$q/$qq}' AND command_id = $command_id;"
    458 }
    459 
    460 function ble/histdb/backup {
    461   local file=$1
    462   ble/bin#freeze-utility-path -n gzip || return 1
    463 
    464   local backup=${file%.sqlite3}.backup.sqlite3 q=\' qq=\'\'
    465   if [[ -s $backup.gz ]]; then
    466     # If there is already an up-to-date backup file, we skip the backup.
    467     local ret now
    468     ble/file#mtime "$backup.gz" || return 1
    469     ble/util/strftime -v now %s
    470     ((now>ret+24*3600)) || return 1
    471   fi
    472 
    473   "$_ble_histdb_sqlite3" "$file" \
    474     ".timeout $_ble_histdb_timeout" \
    475     "PRAGMA quick_check;" \
    476     ".backup '${backup//$q/$qq}'" \
    477     "ATTACH DATABASE '${backup//$q/$qq}' AS backup;
    478     PRAGMA backup.quick_check;" &&
    479     ble/bin/gzip -c "$backup" >| "$backup.gz.part" &&
    480     [[ -s $backup.gz.part ]] &&
    481     ble/bin/mv -f "$backup.gz.part" "$backup.gz" &&
    482     ble/bin/rm -f "$backup"
    483 }
    484 
    485 function ble/histdb/unload.hook {
    486   if [[ $_ble_histdb_file ]]; then
    487     local file=$_ble_histdb_file
    488     ble/histdb/sqlite3.close
    489     ble/histdb/backup "$file"
    490   fi
    491 }
    492 
    493 # 設定が変化して記録先の history.sqlite3 のパスが変わったら現在のファイルは閉じ
    494 # る。必要になった時に初めて新しいファイルを開く。
    495 function ble/histdb/exec_end.hook {
    496   local ret; ble/histdb/.get-filename
    497   [[ $ret == "$_ble_histdb_file" ]] ||
    498     ((${#_ble_edit_exec_lines[@]})) ||
    499     ble/histdb/sqlite3.close
    500 }
    501 
    502 blehook exec_register!=ble/histdb/exec_register.hook
    503 blehook POSTEXEC!=ble/histdb/postexec.hook
    504 blehook exec_end!=ble/histdb/exec_end.hook
    505 blehook unload!=ble/histdb/unload.hook
    506 
    507 #------------------------------------------------------------------------------
    508 # auto-complete
    509 
    510 function ble/complete/auto-complete/source:histdb-history {
    511   local limit=$((bleopt_line_limit_length)) limit_condition=
    512   if ((limit)); then
    513     ((limit-=${#_ble_edit_str},limit>0)) || return 1
    514     limit_condition=" AND length(command) <= $limit"
    515   fi
    516 
    517   local ret
    518   ble/histdb/escape-for-sqlite-glob "$_ble_edit_str"; local pat=$ret?*
    519 
    520   ble/histdb/update-cwd-inode
    521   local inode=$_ble_histdb_exec_cwd_inode
    522 
    523   local q=\' qq=\'\'
    524   ble/histdb/sqlite3.request-single-value "
    525     SELECT coalesce(
    526       (SELECT command FROM (SELECT command, max(issue_time) FROM command_history WHERE command GLOB '${pat//$q/$qq}' AND cwd = '${PWD//$q/$qq}$limit_condition')),
    527       ${inode:+(SELECT command FROM (SELECT command, max(issue_time) FROM command_history WHERE command GLOB '${pat//$q/$qq}' AND inode = $inode$limit_condition)),}
    528       '');"
    529   [[ $ret == "$_ble_edit_str"?* ]] || return 1
    530   ble/complete/auto-complete/enter h 0 "${ret:${#_ble_edit_str}}" '' "$ret"
    531 }
    532 
    533 function ble/complete/auto-complete/source:histdb-word {
    534   local iN=${#_ble_edit_str}
    535   ((_ble_edit_ind>0)) || return 1
    536 
    537   local limit=$((bleopt_line_limit_length)) limit_condition=
    538   if ((limit)); then
    539     ((limit-=iN,limit>0)) || return 1
    540     limit_condition=" AND length(word) <= $limit"
    541   fi
    542 
    543   local -a wbegins=()
    544   if ((_ble_edit_ind<iN)); then
    545     # 行中にいる時は現在位置で完結している単語だけを対象にする。
    546     if [[ ${_ble_syntax_tree[_ble_edit_ind-1]} ]]; then
    547       local tree
    548       ble/string#split-words tree "${_ble_syntax_tree[_ble_edit_ind-1]}"
    549       local wtype=${tree[0]} wlen=${tree[1]}
    550       [[ $wtype && ! ${wtype//[0-9]} && wlen -ge 0 ]] &&
    551         ble/array#push wbegins "$((_ble_edit_ind-wlen))"
    552     elif local ret; ble/syntax/completion-context/.search-last-istat "$((_ble_edit_ind-1))"; then
    553       local istat=$ret stat wlen
    554       ble/string#split-words stat "${_ble_syntax_stat[istat]}"
    555       if (((wlen=stat[1])>=0)); then
    556         ble/array#push wbegins "$((istat-wlen))"
    557       elif [[ ${_ble_syntax_bash_command_EndWtype[stat[0]]:-} ]]; then
    558         # 単語の始まりの時 (_ble_syntax_bash_command_EndWtype で判定できるのかは実は非自明)
    559         ble/array#push wbegins "$istat"
    560       fi
    561     fi
    562 
    563   else
    564     # 閉じていない入れ子に対する列挙
    565 
    566     local -a stat nest tree
    567     ble/string#split-words stat "${_ble_syntax_stat[iN]}"
    568     local wlen tclen
    569     if (((wlen=stat[1])>=0)); then
    570       ble/array#push wbegins "$((iN-wlen))"
    571     elif (((tclen=stat[4])>=0)); then
    572       local wend=$((iN-tclen))
    573       ble/string#split-words tree "${_ble_syntax_tree[wend-1]}"
    574       local wtype=${tree[0]} wlen=${tree[1]}
    575       [[ $wtype && ! ${wtype//[0-9]} && wlen -ge 0 ]] &&
    576         ble/array#push wbegins "$((wend-wlen))"
    577     fi
    578 
    579     local inest=$iN nlen=${stat[3]}
    580     while ((nlen>0)); do
    581       ((inest-=nlen))
    582       ble/string#split-words nest "${_ble_syntax_nest[inest]}"
    583       (((wlen=nest[1])>=0)) &&
    584         ble/array#push wbegins "$((inest-wlen))"
    585       nlen=${nest[3]}
    586     done
    587   fi
    588   ((${#wbegins[@]})) || return 1
    589 
    590   local -a sqls=()
    591 
    592   local i q=\' qq=\'\'
    593   for i in "${wbegins[@]}"; do
    594     local word=${_ble_edit_str:i:_ble_edit_ind-i}
    595     [[ $word ]] || continue
    596     local ret; ble/histdb/escape-for-sqlite-glob "$word"
    597     local pat=$ret?*
    598     ble/array#push sqls "SELECT '$i:' || word FROM (SELECT word, max(mtime) FROM words WHERE word GLOB '${pat//$q/$qq}'$limit_condition)"
    599   done
    600   ((${#sqls[@]})) || return 1
    601 
    602   ble/array#reverse sqls
    603   sqls=("${sqls[@]/#/(}") # WA #D1570 checked
    604   sqls=("${sqls[@]/%/)}") # WA #D1570 checked
    605   ble/array#push sqls "''"
    606   IFS=, builtin eval 'local query="select coalesce(${sqls[*]});"'
    607 
    608   local ret
    609   ble/histdb/sqlite3.request-single-value "$query" auto || return 1
    610   [[ $ret == *:* ]] || return 1
    611 
    612   local index=${ret%%:*} insert=${ret#*:}
    613   if local comps=${_ble_edit_str:index:_ble_edit_ind-index}; [[ $insert == "$comps"* ]]; then
    614     local ins=${insert:_ble_edit_ind-index}
    615     ble/complete/auto-complete/enter c "$_ble_edit_ind" "$ins" "$insert" "$ins" "$ins" ' '
    616   else
    617     ble/complete/auto-complete/enter r "$index" " [$insert] " "$insert" "$insert" "$insert" ' '
    618   fi
    619 }
    620 ble/util/import/eval-after-load core-complete '
    621   ble/array#insert-before _ble_complete_auto_source history histdb-history
    622   ble/array#push _ble_complete_auto_source histdb-word'
    623 [[ $_ble_histdb_keepalive_enabled ]] &&
    624   ble/util/idle.push ble/histdb/sqlite3.open
    625 
    626 #------------------------------------------------------------------------------
    627 # ble histdb command
    628 
    629 function ble-histdb {
    630   case $1 in
    631   (query)
    632     local ret
    633     ble/histdb/.get-filename; local histdb_file=$ret
    634     "$_ble_histdb_sqlite3" "$histdb_file" '.timeout 1000' "${@:2}" ;;
    635   (*)
    636     ble-histdb query 'select command from command_history;' ;;
    637   esac
    638 }