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 }