sistema_progs

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

core-complete.sh (328516B)


      1 #!/bin/bash
      2 
      3 ble/util/import "$_ble_base/lib/core-syntax.sh"
      4 
      5 ## @fn ble/complete/string#search-longest-suffix-in needle haystack
      6 ##   @var[out] ret
      7 function ble/complete/string#search-longest-suffix-in {
      8   local needle=$1 haystack=$2
      9   local l=0 u=${#needle}
     10   while ((l<u)); do
     11     local m=$(((l+u)/2))
     12     if [[ $haystack == *"${needle:m}"* ]]; then
     13       u=$m
     14     else
     15       l=$((m+1))
     16     fi
     17   done
     18   ret=${needle:l}
     19 }
     20 ## @fn ble/complete/string#common-suffix-prefix lhs rhs
     21 ##   @var[out] ret
     22 function ble/complete/string#common-suffix-prefix {
     23   local lhs=$1 rhs=$2
     24   if ((${#lhs}<${#rhs})); then
     25     local i n=${#lhs}
     26     for ((i=0;i<n;i++)); do
     27       ret=${lhs:i}
     28       [[ $rhs == "$ret"* ]] && return 0
     29     done
     30     ret=
     31   else
     32     local j m=${#rhs}
     33     for ((j=m;j>0;j--)); do
     34       ret=${rhs::j}
     35       [[ $lhs == *"$ret" ]] && return 0
     36     done
     37     ret=
     38   fi
     39 }
     40 
     41 ## @fn ble/complete/string#match-patterns str patterns...
     42 ##   指定した文字列が patterns 集合の何れかのパターンに一致するか検査します。
     43 ##   @param[in] str
     44 ##   @param[in] patterns
     45 ##   @exit
     46 function ble/complete/string#match-patterns {
     47   local s=$1 found= pattern; shift
     48   for pattern; do
     49     if [[ $s == $pattern ]]; then
     50       return 0
     51     fi
     52   done
     53   return 1
     54 }
     55 
     56 ## @fn ble/complete/get-wordbreaks
     57 ##   @var[out] wordbreaks
     58 function ble/complete/get-wordbreaks {
     59   wordbreaks=$_ble_term_IFS$COMP_WORDBREAKS
     60   [[ $wordbreaks == *'('* ]] && wordbreaks=${wordbreaks//['()']}'()'
     61   [[ $wordbreaks == *']'* ]] && wordbreaks=']'${wordbreaks//']'}
     62   [[ $wordbreaks == *'-'* ]] && wordbreaks=${wordbreaks//'-'}'-'
     63 }
     64 
     65 # 
     66 #==============================================================================
     67 # 選択インターフェイス (ble/complete/menu)
     68 
     69 ## @arr _ble_complete_menu_icons
     70 ##
     71 ##   各要素は以下の形式の文字列である。
     72 ##
     73 ##   x0,y0,x1,y1,${#pack},${#esc1}[,bbox]:$pack$esc1
     74 ##
     75 ##   * x0,y0 と x1,y1 は menu 項目の描画開始点と終了点。
     76 ##   * esc1 は実際に出力する描画シーケンス。
     77 ##   * bbox は "x y cols lines" の形式をしていて、描画シーケンスを生成する際に
     78 ##     使った bbox の情報を格納する。これは特に truncate が起こった時に、選択状
     79 ##     態の描画を同じ条件で実行する時に参照する。
     80 
     81 _ble_complete_menu_items=()
     82 _ble_complete_menu_class=
     83 _ble_complete_menu_param=
     84 _ble_complete_menu_version=
     85 _ble_complete_menu_page_style=
     86 _ble_complete_menu_ipage=
     87 _ble_complete_menu_offset=
     88 _ble_complete_menu_icons=()
     89 _ble_complete_menu_info_data=()
     90 _ble_complete_menu_selected=-1
     91 
     92 function ble/complete/menu#check-cancel {
     93   ((menu_iloop++%menu_interval==0)) &&
     94     [[ :$comp_type: != *:sync:* ]] &&
     95     ble/decode/has-input
     96 }
     97 
     98 ## @fn ble/complete/menu-style:$menu_style/construct-page
     99 ##   候補一覧メニューの表示・配置を計算します。
    100 ##
    101 ##   @var[out] x y esc
    102 ##   @var[in] menu_style
    103 ##   @arr[in] menu_items
    104 ##   @var[in] menu_class menu_param
    105 ##   @var[in] cols lines
    106 ##
    107 ## @fn ble/complete/menu-style:$menu_style/guess
    108 ##   scroll 番目の候補がどのページにいるかを予測します。
    109 ##   可能性のある最初のページ番号 ipage を返します。
    110 ##
    111 ##   @var[in] scroll
    112 ##   @var[out] ipage begin end
    113 ##   @var[in] cols lines
    114 ##
    115 
    116 _ble_complete_menu_style_measure=()
    117 _ble_complete_menu_style_icons=()
    118 _ble_complete_menu_style_pages=()
    119 
    120 #
    121 # ble/complete/menu-style:align
    122 #
    123 
    124 ## @fn ble/complete/menu#render-item item opts
    125 ##   @var[in] cols lines
    126 ##     Note: "$menu_class"/render-item の中で用いる。
    127 ##   @var[out] x y ret
    128 function ble/complete/menu#render-item {
    129   # use custom renderer
    130   if ble/is-function "$menu_class"/render-item; then
    131     "$menu_class"/render-item "$@"
    132     return "$?"
    133   fi
    134 
    135   local item=$1 opts=$2
    136   #g=0 lc=0 lg=0 LINES=$lines COLUMNS=$cols ble/canvas/trace "$item" truncate:ellipsis
    137 
    138   local sgr0=$_ble_term_sgr0 sgr1=$_ble_term_rev
    139   [[ :$opts: == *:selected:* ]] && local sgr0=$sgr1 sgr1=$sgr0
    140   ble/canvas/trace-text "$item" nonewline:external-sgr
    141   ret=$sgr0$ret$_ble_term_sgr0
    142 }
    143 
    144 ## @fn ble/complete/menu#get-prefix-width format column_width
    145 ##   @param[in] format
    146 ##   @param[in] column_width
    147 ##   @var[out] prefix_width
    148 ##   @var[out] prefix_format
    149 function ble/complete/menu#get-prefix-width {
    150   prefix_width=0
    151   prefix_format=${1:-$bleopt_menu_prefix}
    152   if [[ $prefix_format ]]; then
    153     local prefix1 column_width=$2
    154     ble/util/sprintf prefix1 "$prefix_format" "${#menu_items[@]}"
    155     local x1 y1 x2 y2
    156     LINES=1 COLUMNS=$column_width x=0 y=0 ble/canvas/trace "$prefix1" truncate:measure-bbox
    157     if ((x2<=column_width/2)); then
    158       prefix_width=$x2
    159       ble/string#reserve-prototype "$prefix_width"
    160     fi
    161   fi
    162 }
    163 
    164 ## @fn ble/complete/menu#render-prefix index
    165 ##   @param[in] index
    166 ##   @param[in,opt] column_width
    167 ##   @var[in] prefix_width
    168 ##   @var[in] prefix_format
    169 ##   @var[out] prefix_esc
    170 function ble/complete/menu#render-prefix {
    171   prefix_esc=
    172   local index=$1
    173   if ((prefix_width)); then
    174     local prefix1; ble/util/sprintf prefix1 "$prefix_format" "$((index+1))"
    175     local x=0 y=0
    176     LINES=1 COLUMNS=$prefix_width ble/canvas/trace "$prefix1" truncate:relative:measure-bbox
    177     prefix_esc=$ret$_ble_term_sgr0
    178     if ((x<prefix_width)); then
    179       prefix_esc=${_ble_string_prototype::prefix_width-x}$prefix_esc
    180     fi
    181   fi
    182 }
    183 
    184 
    185 ## @fn ble/complete/menu-style:align/construct/.measure-candidates-in-page
    186 ##   その頁に入り切る範囲で候補の幅を計測する
    187 ##   @var[in] begin
    188 ##     その頁の一番最初に表示する候補を指定します。
    189 ##   @var[out] end
    190 ##     その頁に表示する候補の範囲の終端を返します。
    191 ##     実際には描画の際に全角文字などの文字送りによって
    192 ##     ここまで表示できるとは限りません。
    193 ##   @var[out] wcell
    194 ##     その頁を描画する時のセル幅を返します。
    195 ##   @arr[in,out] _ble_complete_menu_style_measure
    196 ##     計測結果をキャッシュしておく配列です。
    197 ##
    198 ##   @var[in] lines cols menu_iloop
    199 function ble/complete/menu-style:align/construct/.measure-candidates-in-page {
    200   local max_wcell=$bleopt_menu_align_max; ((max_wcell>cols&&(max_wcell=cols)))
    201   ((wcell=bleopt_menu_align_min,wcell<2&&(wcell=2)))
    202   local ncell=0 index=$begin
    203   local item ret esc1 w
    204   for item in "${menu_items[@]:begin}"; do
    205     ble/complete/menu#check-cancel && return 148
    206     local wcell_old=$wcell
    207 
    208     # 候補の表示幅 w を計算
    209     local w=${_ble_complete_menu_style_measure[index]%%:*}
    210     if [[ ! $w ]]; then
    211       local prefix_esc
    212       ble/complete/menu#render-prefix "$index"
    213       local x=$prefix_width y=0
    214       ble/complete/menu#render-item "$item"; esc1=$ret
    215       local w=$((y*cols+x))
    216       _ble_complete_menu_style_measure[index]=$w:${#item},${#esc1}:$item$esc1$prefix_esc
    217     fi
    218 
    219     # wcell, ncell 更新
    220     local wcell_request=$((w++,w<=max_wcell?w:max_wcell))
    221     ((wcell<wcell_request)) && wcell=$wcell_request
    222 
    223     # 新しい ncell
    224     local line_ncell=$((cols/wcell))
    225     local cand_ncell=$(((w+wcell-1)/wcell))
    226     if [[ $menu_style == align-nowrap ]]; then
    227       # Note: nowrap が起こるのはすでに wcell == max_wcell の時なので、
    228       # 改行処理が終わった後に wcell が変化するという事はない。
    229       local x1=$((ncell%line_ncell*wcell))
    230       local ncell_eol=$(((ncell/line_ncell+1)*line_ncell))
    231       if ((x1>0&&x1+w>=cols)); then
    232         # 行送り
    233         ((ncell=ncell_eol+cand_ncell))
    234       elif ((x1+w<cols)); then
    235         # 余白に収まる場合
    236         ((ncell+=cand_ncell))
    237         ((ncell>ncell_eol&&(ncell=ncell_eol)))
    238       else
    239         ((ncell+=cand_ncell))
    240       fi
    241     else
    242       ((ncell+=cand_ncell))
    243     fi
    244 
    245     local max_ncell=$((line_ncell*lines))
    246     ((index&&ncell>max_ncell)) && { wcell=$wcell_old; break; }
    247     ((index++))
    248   done
    249   end=$index
    250 }
    251 
    252 ## @fn ble/complete/menu-style:align/construct-page
    253 ##   @var[in,out] begin end x y esc
    254 ##   @arr[out] _ble_complete_menu_style_icons
    255 ##
    256 ##   @var[in,out] cols lines menu_iloop
    257 function ble/complete/menu-style:align/construct-page {
    258   x=0 y=0 esc=
    259 
    260   local prefix_width prefix_format
    261   ble/complete/menu#get-prefix-width "$bleopt_menu_align_prefix" "$bleopt_menu_align_max"
    262 
    263   local wcell=2
    264   ble/complete/menu-style:align/construct/.measure-candidates-in-page
    265   (($?==148)) && return 148
    266 
    267   local ncell=$((cols/wcell))
    268   local index=$begin entry
    269   for entry in "${_ble_complete_menu_style_measure[@]:begin:end-begin}"; do
    270     ble/complete/menu#check-cancel && return 148
    271 
    272     local w=${entry%%:*}; entry=${entry#*:}
    273     local s=${entry%%:*}; entry=${entry#*:}
    274     local len; ble/string#split len , "$s"
    275     local item=${entry::len[0]} esc1=${entry:len[0]:len[1]} prefix_esc=${entry:len[0]+len[1]}
    276 
    277     local x0=$x y0=$y
    278     if ((x==0||x+w<cols)); then
    279       ((x+=w%cols,y+=w/cols))
    280       ((y>=lines&&(x=x0,y=y0,1))) && break
    281     else
    282       if [[ $menu_style == align-nowrap ]]; then
    283         ((y+1>=lines)) && break
    284         esc=$esc$'\n'
    285         ((x0=x=0,y0=++y))
    286         ((x=w%cols,y+=w/cols))
    287         ((y>=lines&&(x=x0,y=y0,1))) && break
    288       else
    289         ((x+=prefix_width))
    290         ble/complete/menu#render-item "$item" ||
    291           ((begin==index)) || # [Note: 少なくとも1個ははみ出ても表示する]
    292           { x=$x0 y=$y0; break; }; esc1=$ret
    293       fi
    294     fi
    295 
    296     _ble_complete_menu_style_icons[index]=$((x0+prefix_width)),$y0,$x,$y,${#item},${#esc1}:$item$esc1
    297     esc=$esc$prefix_esc$esc1
    298 
    299     # 候補と候補の間の空白
    300     if ((++index<end)); then
    301       local icell=$((x==0?0:(x+wcell)/wcell))
    302       if ((icell<ncell)); then
    303         # 次の升目
    304         local pad=$((icell*wcell-x))
    305         ble/string#reserve-prototype "$pad"
    306         esc=$esc${_ble_string_prototype::pad}
    307         ((x=icell*wcell))
    308       else
    309         # 次の行
    310         ((y+1>=lines)) && break
    311         esc=$esc$'\n'
    312         ((x=0,++y))
    313       fi
    314     fi
    315   done
    316   end=$index
    317 }
    318 function ble/complete/menu-style:align-nowrap/construct-page {
    319   ble/complete/menu-style:align/construct-page "$@"
    320 }
    321 
    322 #
    323 # ble/complete/menu-style:dense
    324 #
    325 
    326 ## @fn ble/complete/menu-style:dense/construct-page
    327 ##   @var[in,out] begin end x y esc
    328 ##   @var[in,out] cols lines menu_iloop
    329 function ble/complete/menu-style:dense/construct-page {
    330 
    331   local prefix_width prefix_format
    332   ble/complete/menu#get-prefix-width "$bleopt_menu_dense_prefix" "$cols"
    333 
    334   x=0 y=0 esc=
    335   local item index=$begin N=${#menu_items[@]}
    336   for item in "${menu_items[@]:begin}"; do
    337     ble/complete/menu#check-cancel && return 148
    338 
    339     local x0=$x y0=$y
    340 
    341     local prefix_esc esc1
    342     ble/complete/menu#render-prefix "$index"
    343     ((x+=prefix_width,x>cols&&(y+=x/cols,x%=cols)))
    344     ble/complete/menu#render-item "$item" ||
    345       ((index==begin)) ||
    346       { x=$x0 y=$y0; break; }; esc1=$ret
    347 
    348     if [[ $menu_style == dense-nowrap ]]; then
    349       if ((y>y0&&x>0||y>y0+1)); then
    350         ((++y0>=lines)) && break
    351         esc=$esc$'\n'
    352         ((y=y0,x0=0,x=prefix_width))
    353         ble/complete/menu#render-item "$item" ||
    354           ((begin==index)) ||
    355           { x=$x0 y=$y0; break; }; esc1=$ret
    356       fi
    357     fi
    358 
    359     local x1=$((x0+prefix_width)) y1=$y0
    360     ((x1>=cols)) && ((y1+=x1/cols,x1%=cols))
    361     _ble_complete_menu_style_icons[index]=$x1,$y1,$x,$y,${#item},${#esc1}:$item$esc1
    362     esc=$esc$prefix_esc$esc1
    363 
    364     # 候補と候補の間の空白
    365     if ((++index<N)); then
    366       if [[ $menu_style == dense-nowrap ]] && ((x==0)); then
    367         : skip
    368       elif ((x+1<cols)); then
    369         esc=$esc' '
    370         ((x++))
    371       else
    372         ((y+1>=lines)) && break
    373         esc=$esc$'\n'
    374         ((x=0,++y))
    375       fi
    376     fi
    377   done
    378   end=$index
    379 }
    380 ## @fn ble/complete/menu-style:dense/construct opts
    381 ##   complete_menu_style=align{,-nowrap} に対して候補を配置します。
    382 function ble/complete/menu-style:dense-nowrap/construct-page {
    383   ble/complete/menu-style:dense/construct-page "$@"
    384 }
    385 
    386 #
    387 # ble/complete/menu-style:linewise
    388 #
    389 
    390 ## @fn ble/complete/menu-style:linewise/construct-page opts
    391 ##   @var[in,out] begin end x y esc
    392 function ble/complete/menu-style:linewise/construct-page {
    393   local opts=$1 ret
    394   local max_icon_width=$((cols-1))
    395 
    396   local prefix_width prefix_format
    397   ble/complete/menu#get-prefix-width "$bleopt_menu_linewise_prefix" "$max_icon_width"
    398 
    399   local item x0 y0 esc1 index=$begin
    400   end=$begin x=0 y=0 esc=
    401   for item in "${menu_items[@]:begin:lines}"; do
    402     ble/complete/menu#check-cancel && return 148
    403 
    404     local prefix_esc=
    405     ble/complete/menu#render-prefix "$index" "$max_icon_width"
    406     esc=$esc$prefix_esc
    407     ((x=prefix_width))
    408 
    409     ((x0=x,y0=y))
    410     local lines1=1 cols1=$max_icon_width
    411     lines=$lines1 cols=$cols1 y=0 ble/complete/menu#render-item "$item"; esc1=$ret
    412     _ble_complete_menu_style_icons[index]=$x0,$y0,$x,$y,${#item},${#esc1},"$x0 0 $cols1 $lines1":$item$esc1
    413     ((index++))
    414     esc=$esc$esc1
    415 
    416     ((y+1>=lines)) && break
    417     ((x=0,++y))
    418     esc=$esc$'\n'
    419   done
    420   end=$index
    421 }
    422 function ble/complete/menu-style:linewise/guess {
    423   ((ipage=scroll/lines,
    424     begin=ipage*lines,
    425     end=begin))
    426 }
    427 
    428 #
    429 # ble/complete/menu-style:desc
    430 #
    431 
    432 _ble_complete_menu_desc_pageheight=()
    433 
    434 ## @fn ble/complete/menu-style:desc/construct-page opts
    435 ##   @var[in,out] begin end x y esc
    436 ##   @var[in] ipage
    437 function ble/complete/menu-style:desc/construct-page {
    438   local opts=$1 ret
    439   local opt_raw=; [[ $menu_style != desc-text ]] && opt_raw=1
    440 
    441   # 失敗時・エラー時の既定値
    442   end=$begin esc= x=0 y=0
    443 
    444   local colsep=' | '
    445   local desc_sgr0=$'\e[m'
    446   ble/color/face2sgr-ansi syntax_quoted; local desc_sgrq=$ret
    447   ble/color/face2sgr-ansi syntax_delimiter; local desc_sgrt=$ret
    448 
    449   local ncolumn=1 nline=$lines
    450   local nrest_item=$((${#menu_items[@]}-begin))
    451   if [[ $bleopt_menu_desc_multicolumn_width ]]; then
    452     ncolumn=$((cols/bleopt_menu_desc_multicolumn_width))
    453     if ((ncolumn<1)); then
    454       ncolumn=1
    455     elif ((ncolumn>nrest_item)); then
    456       ncolumn=$nrest_item
    457     fi
    458   fi
    459   ((nline=(${#menu_items[@]}-begin+ncolumn-1)/ncolumn,
    460     nline>lines&&(nline=lines)))
    461   local ncolumn_max=$(((nrest_item+nline-1)/nline))
    462   ((ncolumn>ncolumn_max&&(ncolumn=ncolumn_max)))
    463 
    464   # Note #D1727: 相対移動の時は、右端に接すると端末による振る舞いの違
    465   #   いが問題になるので、右端に接しない様に col-1 にする。一部の端末
    466   #   については右端に接しても相対移動が壊れないと分かっているので、
    467   #   white list で右端に接する事を許可する。
    468   local available_width=$cols
    469   case $_ble_term_TERM in
    470   (screen:*|tmux:*|kitty:*|contra:*) ;;
    471   (*) ((available_width--)) ;;
    472   esac
    473 
    474   local wcolumn=$(((available_width-${#colsep}*(ncolumn-1))/ncolumn))
    475 
    476   local prefix_width prefix_format
    477   ble/complete/menu#get-prefix-width "$bleopt_menu_desc_prefix" "$wcolumn"
    478   ((wcolumn>=prefix_width+15)) || prefix_width=0
    479 
    480   local wcand_limit=$(((wcolumn-prefix_width+1)*2/3))
    481   ((wcand_limit<10&&(wcand_limit=wcolumn-prefix_width)))
    482 
    483   local -a DRAW_BUFF=()
    484   local index=$begin icolumn ymax=0
    485   for ((icolumn=0;icolumn<ncolumn;icolumn++)); do
    486 
    487     # 各候補を描画して幅を計算する
    488     local measure; measure=()
    489     local pack w esc1 max_width=0
    490     for pack in "${menu_items[@]:index:nline}"; do
    491       ble/complete/menu#check-cancel && return 148
    492 
    493       x=0 y=0
    494       lines=1 cols=$wcand_limit ble/complete/menu#render-item "$pack"; esc1=$ret
    495       ((w=y*wcand_limit+x,w>max_width&&(max_width=w)))
    496 
    497       ble/array#push measure "$w:${#pack}:$pack$esc1"
    498     done
    499 
    500     local cand_width=$max_width
    501     local desc_x=$((prefix_width+cand_width+1)); ((desc_x>wcolumn&&(desc_x=wcolumn)))
    502     local desc_prefix=; ((wcolumn-prefix_width-desc_x>30)) && desc_prefix=': '
    503 
    504     local xcolumn=$((icolumn*(wcolumn+${#colsep})))
    505 
    506     x=0 y=0
    507     local entry w s pack esc1 x0 y0 pad
    508     for entry in "${measure[@]}"; do
    509       ble/complete/menu#check-cancel && return 148
    510 
    511       w=${entry%%:*} entry=${entry#*:}
    512       s=${entry%%:*} entry=${entry#*:}
    513       pack=${entry::s} esc1=${entry:s}
    514 
    515       local prefix_esc
    516       ble/complete/menu#render-prefix "$index"
    517       ble/canvas/put.draw "$prefix_esc"
    518       ((x+=prefix_width))
    519 
    520       # 候補表示
    521       ((x0=x,y0=y,x+=w))
    522       _ble_complete_menu_style_icons[index]=$((xcolumn+x0)),$y0,$((xcolumn+x)),$y,${#pack},${#esc1},"0 0 $wcand_limit 1":$pack$esc1
    523       ((index++))
    524       ble/canvas/put.draw "$esc1"
    525 
    526       # 余白
    527       ble/canvas/put-spaces.draw "$((pad=desc_x-x))"
    528       ble/canvas/put.draw "$desc_prefix"
    529       ((x+=pad+${#desc_prefix}))
    530 
    531       # 説明表示
    532       local desc=$desc_sgrt'(no description)'$desc_sgr0
    533       ble/function#try "$menu_class"/get-desc "$pack"
    534       if [[ $opt_raw ]]; then
    535         y=0 g=0 lc=0 lg=0 LINES=1 COLUMNS=$wcolumn ble/canvas/trace.draw "$desc" truncate:relative:ellipsis
    536       else
    537         y=0 lines=1 cols=$wcolumn ble/canvas/trace-text "$desc" nonewline
    538         ble/canvas/put.draw "$ret"
    539       fi
    540       ble/canvas/put.draw "$_ble_term_sgr0"
    541       ((y+1>=nline)) && break
    542       ble/canvas/put-move.draw "$((-x))" 1
    543       ((x=0,++y))
    544     done
    545     ((y>ymax)) && ymax=$y
    546 
    547     if ((icolumn+1<ncolumn)); then
    548       # カラム仕切りを出力 (最後に次のカラムの先頭に移動)
    549       ble/canvas/put-move.draw "$((wcolumn-x))" "$((-y))"
    550       for ((y=0;y<=ymax;y++)); do
    551         ble/canvas/put.draw "$colsep"
    552         if ((y<ymax)); then
    553           ble/canvas/put-move.draw -${#colsep} 1
    554         else
    555           ble/canvas/put-move-y.draw "$((-y))"
    556         fi
    557       done
    558     else
    559       ((y<ymax)) && ble/canvas/put-move-y.draw "$((ymax-y))"
    560       ((x+=xcolumn,y=ymax))
    561     fi
    562   done
    563 
    564   _ble_complete_menu_desc_pageheight[ipage]=$nline
    565   end=$index
    566   ble/canvas/sflush.draw -v esc
    567 }
    568 function ble/complete/menu-style:desc/guess {
    569   local ncolumn=1
    570   if [[ $bleopt_menu_desc_multicolumn_width ]]; then
    571     ncolumn=$((cols/bleopt_menu_desc_multicolumn_width))
    572     ((ncolumn<1)) && ncolumn=1
    573   fi
    574   local nitem_per_page=$((ncolumn*lines))
    575   ((ipage=scroll/nitem_per_page,
    576     begin=ipage*nitem_per_page,
    577     end=begin))
    578 }
    579 function ble/complete/menu-style:desc/locate {
    580   local type=$1 osel=$2
    581   local ipage=$_ble_complete_menu_ipage
    582   local nline=${_ble_complete_menu_desc_pageheight[ipage]:-1}
    583 
    584   case $type in
    585   (right) ((ret=osel+nline)) ;;
    586   (left)  ((ret=osel-nline)) ;;
    587   (down)  ((ret=osel+1)) ;;
    588   (up)    ((ret=osel-1)) ;;
    589   (*) return 1 ;;
    590   esac
    591 
    592   local beg=$_ble_complete_menu_offset
    593   local end=$((beg+${#_ble_complete_menu_icons[@]}))
    594   if ((ret<beg)); then
    595     ((ret=beg-1))
    596   elif ((ret>end)); then
    597     ((ret=end))
    598   fi
    599   return 0
    600 }
    601 
    602 function ble/complete/menu-style:desc-text/construct-page { ble/complete/menu-style:desc/construct-page "$@"; }
    603 function ble/complete/menu-style:desc-text/guess { ble/complete/menu-style:desc/guess; }
    604 function ble/complete/menu-style:desc-text/locate { ble/complete/menu-style:desc/locate "$@"; }
    605 
    606 # Obsolete menu_style (now synonym to "desc")
    607 function ble/complete/menu-style:desc-raw/construct-page { ble/complete/menu-style:desc/construct-page "$@"; }
    608 function ble/complete/menu-style:desc-raw/guess { ble/complete/menu-style:desc/guess; }
    609 function ble/complete/menu-style:desc-raw/locate { ble/complete/menu-style:desc/locate "$@"; }
    610 
    611 ## @fn ble/complete/menu#construct/.initialize-size
    612 ##   @var[out] cols lines
    613 function ble/complete/menu#construct/.initialize-size {
    614   ble/edit/info/.initialize-size
    615   local maxlines=$((bleopt_complete_menu_maxlines))
    616   ((maxlines>0&&lines>maxlines)) && lines=$maxlines
    617 }
    618 ## @fn ble/complete/menu#construct menu_opts
    619 ##   実装分離の adapter 部分
    620 ##
    621 ##   @var[in] menu_style
    622 ##
    623 ##   @arr[in] menu_items
    624 ##     項目のリストを指定します。
    625 ##
    626 ##   @var[in] menu_class menu_param
    627 ##     以下に掲げる様々な callback を呼び出す為の変数です。
    628 ##
    629 ##   @fn[in,opt] $menu_class/render-item item opts
    630 ##     各項目に対応する描画内容を決定する renderer 関数を指定します。
    631 ##     @param[in] item
    632 ##       描画される項目を指定します。
    633 ##     @param[in] opts
    634 ##       selected
    635 ##         選択されている項目の描画を行う事を示します。
    636 ##     @var[in] lines cols
    637 ##       描画範囲の行数と列数を指定します。
    638 ##     @var[in,out] x y
    639 ##       描画開始位置を指定します。終了位置を返します。
    640 ##     @var[out] ret
    641 ##       描画に用いるシーケンスを返します。
    642 ##
    643 ##   @fn[in,opt] $menu_class/onselect nsel osel
    644 ##     項目が選択された時に呼び出される callback を指定します。
    645 ##     @param[in] nsel osel
    646 ##
    647 ##   @fn[in,opt] $menu_class/get-desc item
    648 ##     項目の説明を取得します。
    649 ##     @param[out] desc
    650 ##
    651 ##   @fn[in,opt] $menu_class/onaccept nsel [item]
    652 ##   @fn[in,opt] $menu_class/oncancel nsel
    653 ##
    654 function ble/complete/menu#construct {
    655   local menu_opts=$1
    656   local menu_iloop=0
    657   local menu_interval=$bleopt_complete_polling_cycle
    658 
    659   local cols lines
    660   ble/complete/menu#construct/.initialize-size
    661   local nitem=${#menu_items[@]}
    662   local version=$nitem:$lines:$cols
    663 
    664   # 項目がない時の特別表示
    665   if ((nitem==0)); then
    666     _ble_complete_menu_version=$version
    667     _ble_complete_menu_items=()
    668     _ble_complete_menu_page_style=
    669     _ble_complete_menu_ipage=0
    670     _ble_complete_menu_offset=0
    671     _ble_complete_menu_icons=()
    672     _ble_complete_menu_info_data=(ansi $'\e[38;5;242m(no items)\e[m')
    673     _ble_complete_menu_selected=-1
    674     return 0
    675   fi
    676 
    677   # 表示したい項目の指定
    678   local scroll=0 rex=':scroll=([0-9]+):' use_cache=
    679   if [[ :$menu_opts: =~ $rex ]]; then
    680     scroll=${BASH_REMATCH[1]}
    681     ((nitem&&(scroll%=nitem)))
    682     [[ $_ble_complete_menu_version == $version ]] && use_cache=1
    683   fi
    684   if [[ ! $use_cache ]]; then
    685     _ble_complete_menu_style_measure=()
    686     _ble_complete_menu_style_icons=()
    687     _ble_complete_menu_style_pages=()
    688   fi
    689 
    690   local begin=0 end=0 ipage=0 x y esc
    691   ble/function#try ble/complete/menu-style:"$menu_style"/guess
    692   while ((end<nitem)); do
    693     ((scroll<begin)) && return 1
    694     local page_data=${_ble_complete_menu_style_pages[ipage]}
    695     if [[ $page_data ]]; then
    696       # キャッシュがある時はキャッシュから読み取り
    697       local fields; ble/string#split fields , "${page_data%%:*}"
    698       begin=${fields[0]} end=${fields[1]}
    699       if ((begin<=scroll&&scroll<end)); then
    700         x=${fields[2]} y=${fields[3]} esc=${page_data#*:}
    701         break
    702       fi
    703     else
    704       # キャッシュがない時は頁を構築
    705       ble/complete/menu-style:"$menu_style"/construct-page "$menu_opts" || return "$?"
    706       _ble_complete_menu_style_pages[ipage]=$begin,$end,$x,$y:$esc
    707       ((begin<=scroll&&scroll<end)) && break
    708     fi
    709     begin=$end
    710     ((ipage++))
    711   done
    712 
    713   _ble_complete_menu_version=$version
    714   _ble_complete_menu_items=("${menu_items[@]}")
    715   _ble_complete_menu_class=$menu_class
    716   _ble_complete_menu_param=$menu_param
    717   _ble_complete_menu_page_style=$menu_style
    718   _ble_complete_menu_ipage=$ipage
    719   _ble_complete_menu_offset=$begin
    720   _ble_complete_menu_icons=("${_ble_complete_menu_style_icons[@]:begin:end-begin}")
    721   _ble_complete_menu_info_data=(store "$x" "$y" "$esc")
    722   _ble_complete_menu_selected=-1
    723   return 0
    724 }
    725 
    726 function ble/complete/menu#show {
    727   ble/edit/info/immediate-show "${_ble_complete_menu_info_data[@]}"
    728 }
    729 function ble/complete/menu#clear {
    730   ble/edit/info/default
    731 }
    732 
    733 
    734 ## @fn ble/complete/menu#select index [opts]
    735 ##   @param[in] opts
    736 ##     goto-page-top
    737 ##       指定した項目を含む頁に移動した後に、
    738 ##       その頁の一番上の項目に移動する事を指定します。
    739 function ble/complete/menu#select {
    740   local menu_class=$_ble_complete_menu_class
    741   local menu_param=$_ble_complete_menu_param
    742   local osel=$_ble_complete_menu_selected nsel=$1 opts=$2
    743   local ncand=${#_ble_complete_menu_items[@]}
    744   ((0<=osel&&osel<ncand)) || osel=-1
    745   ((0<=nsel&&nsel<ncand)) || nsel=-1
    746   ((osel==nsel)) && return 0
    747 
    748   local infox infoy
    749   ble/canvas/panel#get-origin "$_ble_edit_info_panel" --prefix=info
    750 
    751   # ページ更新
    752   local visible_beg=$_ble_complete_menu_offset
    753   local visible_end=$((visible_beg+${#_ble_complete_menu_icons[@]}))
    754   if ((nsel>=0&&!(visible_beg<=nsel&&nsel<visible_end))); then
    755     ble/complete/menu/show filter:load-filtered-data:scroll="$nsel"; local ext=$?
    756     ((ext)) && return "$ext"
    757 
    758     if [[ $_ble_complete_menu_ipage ]]; then
    759       local ipage=$_ble_complete_menu_ipage
    760       ble/term/visible-bell "menu: Page $((ipage+1))" persistent
    761     else
    762       ble/term/visible-bell "menu: Offset $_ble_complete_menu_offset/$ncand" persistent
    763     fi
    764 
    765     visible_beg=$_ble_complete_menu_offset
    766     visible_end=$((visible_beg+${#_ble_complete_menu_icons[@]}))
    767 
    768     # スクロールに対応していない menu_style や、スクロールしすぎた時の為。
    769     ((visible_end<=nsel&&(nsel=visible_end-1)))
    770     ((nsel<=visible_beg&&(nsel=visible_beg)))
    771     ((visible_beg<=osel&&osel<visible_end)) || osel=-1
    772   fi
    773 
    774   local -a DRAW_BUFF=()
    775   local ret; ble/canvas/panel/save-position; local pos0=$ret
    776   if ((osel>=0)); then
    777     # 消去
    778     local entry=${_ble_complete_menu_icons[osel-visible_beg]}
    779     local fields text=${entry#*:}
    780     ble/string#split fields , "${entry%%:*}"
    781 
    782     if ((fields[3]<_ble_canvas_panel_height[_ble_edit_info_panel])); then
    783       # Note: 編集文字列の内容の変化により info panel が削れている事がある。
    784       # 現在の項目がちゃんと info panel の中にある時にだけ描画する。(#D0880)
    785 
    786       ble/canvas/panel#goto.draw "$_ble_edit_info_panel" "${fields[@]::2}"
    787       ble/canvas/put.draw "${text:fields[4]}"
    788       _ble_canvas_x=${fields[2]} _ble_canvas_y=$((infoy+fields[3]))
    789     fi
    790   fi
    791 
    792   local value=
    793   if ((nsel>=0)); then
    794     [[ :$opts: == *:goto-page-top:* ]] && nsel=$visible_beg
    795     local entry=${_ble_complete_menu_icons[nsel-visible_beg]}
    796     local fields text=${entry#*:}
    797     ble/string#split fields , "${entry%%:*}"
    798 
    799     local x=${fields[0]} y=${fields[1]}
    800     local item=${text::fields[4]}
    801 
    802     # construct reverted candidate
    803     local ret
    804     if [[ ${fields[6]} ]]; then
    805       local box cols lines
    806       ble/string#split-words box "${fields[6]}"
    807       x=${box[0]} y=${box[1]} cols=${box[2]} lines=${box[3]}
    808       ble/complete/menu#render-item "$item" selected
    809       ((x+=fields[0]-box[0]))
    810       ((y+=fields[1]-box[1]))
    811     else
    812       local cols lines
    813       ble/complete/menu#construct/.initialize-size
    814       ble/complete/menu#render-item "$item" selected
    815     fi
    816 
    817     if ((y<_ble_canvas_panel_height[_ble_edit_info_panel])); then
    818       # Note: 編集文字列の内容の変化により info panel が削れている事がある。
    819       # 現在の項目がちゃんと info panel の中にある時にだけ描画する。(#D0880)
    820 
    821       ble/canvas/panel#goto.draw "$_ble_edit_info_panel" "${fields[@]::2}"
    822       ble/canvas/put.draw "$ret"
    823       _ble_canvas_x=$x _ble_canvas_y=$((infoy+y))
    824     fi
    825 
    826     _ble_complete_menu_selected=$nsel
    827   else
    828     _ble_complete_menu_selected=-1
    829     value=$_ble_complete_menu_original
    830   fi
    831   ble/canvas/panel/load-position.draw "$pos0"
    832   ble/canvas/bflush.draw
    833 
    834   ble/function#try "$menu_class"/onselect "$nsel" "$osel"
    835   return 0
    836 }
    837 
    838 # widgets
    839 
    840 function ble/widget/menu/forward {
    841   local opts=$1
    842   local nsel=$((_ble_complete_menu_selected+1))
    843   local ncand=${#_ble_complete_menu_items[@]}
    844   if ((nsel>=ncand)); then
    845     if [[ :$opts: == *:cyclic:* ]] && ((ncand>=2)); then
    846       nsel=0
    847     else
    848       ble/widget/.bell "menu: no more candidates"
    849       return 1
    850     fi
    851   fi
    852   ble/complete/menu#select "$nsel"
    853 }
    854 function ble/widget/menu/backward {
    855   local opts=$1
    856   local nsel=$((_ble_complete_menu_selected-1))
    857   if ((nsel<0)); then
    858     local ncand=${#_ble_complete_menu_items[@]}
    859     if [[ :$opts: == *:cyclic:* ]] && ((ncand>=2)); then
    860       ((nsel=ncand-1))
    861     else
    862       ble/widget/.bell "menu: no more candidates"
    863       return 1
    864     fi
    865   fi
    866   ble/complete/menu#select "$nsel"
    867 }
    868 
    869 function ble/widget/menu/forward-column {
    870   local osel=$((_ble_complete_menu_selected))
    871   if local ret; ble/function#try ble/complete/menu-style:"$_ble_complete_menu_page_style"/locate right "$osel"; then
    872     local nsel=$ret ncand=${#_ble_complete_menu_items[@]}
    873     if ((0<=nsel&&nsel<ncand&&nsel!=osel)); then
    874       ble/complete/menu#select "$nsel"
    875     else
    876       ble/widget/.bell "menu: no more candidates"
    877     fi
    878   else
    879     ble/widget/menu/forward
    880   fi
    881 }
    882 function ble/widget/menu/backward-column {
    883   local osel=$((_ble_complete_menu_selected))
    884   if local ret; ble/function#try ble/complete/menu-style:"$_ble_complete_menu_page_style"/locate left "$osel"; then
    885     local nsel=$ret ncand=${#_ble_complete_menu_items[@]}
    886     if ((0<=nsel&&nsel<ncand&&nsel!=osel)); then
    887       ble/complete/menu#select "$nsel"
    888     else
    889       ble/widget/.bell "menu: no more candidates"
    890     fi
    891   else
    892     ble/widget/menu/backward
    893   fi
    894 }
    895 
    896 _ble_complete_menu_lastcolumn=
    897 ## @fn ble/widget/menu/.check-last-column
    898 ##   @var[in,out] ox
    899 function ble/widget/menu/.check-last-column {
    900   if [[ $_ble_complete_menu_lastcolumn ]]; then
    901     local lastwidget=${LASTWIDGET%%' '*}
    902     if [[ $lastwidget == ble/widget/menu/forward-line ||
    903             $lastwidget == ble/widget/menu/backward-line ]]
    904     then
    905       ox=$_ble_complete_menu_lastcolumn
    906       return 0
    907     fi
    908   fi
    909   _ble_complete_menu_lastcolumn=$ox
    910 }
    911 ## @fn ble/widget/menu/.goto-column column
    912 ##   現在行の中で指定した列に対応する要素に移動する。
    913 ##   @param[in] column
    914 function ble/widget/menu/.goto-column {
    915   local column=$1
    916   local offset=$_ble_complete_menu_offset
    917   local osel=$_ble_complete_menu_selected
    918   ((osel>=0)) || return 1
    919   local entry=${_ble_complete_menu_icons[osel-offset]}
    920   local fields; ble/string#split fields , "${entry%%:*}"
    921   local ox=${fields[0]} oy=${fields[1]}
    922   local nsel=-1
    923   if ((ox<column)); then
    924     # forward search within the line
    925     nsel=$osel
    926     for entry in "${_ble_complete_menu_icons[@]:osel+1-offset}"; do
    927       ble/string#split fields , "${entry%%:*}"
    928       local x=${fields[0]} y=${fields[1]}
    929       ((y==oy&&x<=column)) || break
    930       ((nsel++))
    931     done
    932   elif ((ox>column)); then
    933     # backward search within the line
    934     local i=$osel
    935     while ((--i>=offset)); do
    936       entry=${_ble_complete_menu_icons[i-offset]}
    937       ble/string#split fields , "${entry%%:*}"
    938       local x=${fields[0]} y=${fields[1]}
    939       ((y<oy||x<=column&&(nsel=i,1))) && break
    940     done
    941   fi
    942   ((nsel>=0&&nsel!=osel)) &&
    943     ble/complete/menu#select "$nsel"
    944 }
    945 function ble/widget/menu/forward-line {
    946   local offset=$_ble_complete_menu_offset
    947   local osel=$_ble_complete_menu_selected
    948   ((osel>=0)) || return 1
    949 
    950   local nsel=-1 goto_column=
    951   if local ret; ble/function#try ble/complete/menu-style:"$_ble_complete_menu_page_style"/locate down "$osel"; then
    952     nsel=$ret
    953   else
    954     local entry=${_ble_complete_menu_icons[osel-offset]}
    955     local fields; ble/string#split fields , "${entry%%:*}"
    956     local ox=${fields[0]} oy=${fields[1]}
    957     ble/widget/menu/.check-last-column
    958     local i=$osel nsel=-1 is_next_page=
    959     for entry in "${_ble_complete_menu_icons[@]:osel+1-offset}"; do
    960       ble/string#split fields , "${entry%%:*}"
    961       local x=${fields[0]} y=${fields[1]}
    962       ((y<=oy||y==oy+1&&x<=ox||nsel<0)) || break
    963       ((++i,y>oy&&(nsel=i)))
    964     done
    965     ((nsel<0&&(is_next_page=1,nsel=offset+${#_ble_complete_menu_icons[@]})))
    966     ((is_next_page)) && goto_column=$ox
    967   fi
    968 
    969   local ncand=${#_ble_complete_menu_items[@]}
    970   if ((0<=nsel&&nsel<ncand)); then
    971     ble/complete/menu#select "$nsel"
    972     [[ $goto_column ]] && ble/widget/menu/.goto-column "$goto_column"
    973     return 0
    974   else
    975     ble/widget/.bell 'menu: no more candidates'
    976     return 1
    977   fi
    978 }
    979 function ble/widget/menu/backward-line {
    980   local offset=$_ble_complete_menu_offset
    981   local osel=$_ble_complete_menu_selected
    982   ((osel>=0)) || return 1
    983 
    984   local nsel=-1 goto_column=
    985   if local ret; ble/function#try ble/complete/menu-style:"$_ble_complete_menu_page_style"/locate up "$osel"; then
    986     nsel=$ret
    987   else
    988     local entry=${_ble_complete_menu_icons[osel-offset]}
    989     local fields; ble/string#split fields , "${entry%%:*}"
    990     local ox=${fields[0]} oy=${fields[1]}
    991     ble/widget/menu/.check-last-column
    992     local nsel=$osel
    993     while ((--nsel>=offset)); do
    994       entry=${_ble_complete_menu_icons[nsel-offset]}
    995       ble/string#split fields , "${entry%%:*}"
    996       local x=${fields[0]} y=${fields[1]}
    997       ((y<oy-1||y==oy-1&&x<=ox)) && break
    998     done
    999     ((0<=nsel&&nsel<offset)) && goto_column=$ox
   1000   fi
   1001 
   1002   local ncand=${#_ble_complete_menu_items[@]}
   1003   if ((0<=nsel&&nsel<ncand)); then
   1004     ble/complete/menu#select "$nsel"
   1005     [[ $goto_column ]] && ble/widget/menu/.goto-column "$goto_column"
   1006   else
   1007     ble/widget/.bell 'menu: no more candidates'
   1008     return 1
   1009   fi
   1010 }
   1011 function ble/widget/menu/backward-page {
   1012   if ((_ble_complete_menu_offset>0)); then
   1013     ble/complete/menu#select "$((_ble_complete_menu_offset-1))" goto-page-top
   1014   else
   1015     ble/widget/.bell "menu: this is the first page."
   1016     return 1
   1017   fi
   1018 }
   1019 function ble/widget/menu/forward-page {
   1020   local next=$((_ble_complete_menu_offset+${#_ble_complete_menu_icons[@]}))
   1021   if ((next<${#_ble_complete_menu_items[@]})); then
   1022     ble/complete/menu#select "$next"
   1023   else
   1024     ble/widget/.bell "menu: this is the last page."
   1025     return 1
   1026   fi
   1027 }
   1028 function ble/widget/menu/beginning-of-page {
   1029   ble/complete/menu#select "$_ble_complete_menu_offset"
   1030 }
   1031 function ble/widget/menu/end-of-page {
   1032   local nicon=${#_ble_complete_menu_icons[@]}
   1033   ((nicon)) && ble/complete/menu#select "$((_ble_complete_menu_offset+nicon-1))"
   1034 }
   1035 
   1036 function ble/widget/menu/cancel {
   1037   ble/decode/keymap/pop
   1038   ble/complete/menu#clear
   1039   "$_ble_complete_menu_class"/oncancel
   1040 }
   1041 function ble/widget/menu/accept {
   1042   ble/decode/keymap/pop
   1043   ble/complete/menu#clear
   1044   local nsel=$_ble_complete_menu_selected
   1045   local hook=$_ble_complete_menu_accept_hook
   1046   _ble_complete_menu_accept_hook=
   1047   if ((nsel>=0)); then
   1048     "$_ble_complete_menu_class"/onaccept "$nsel" "${_ble_complete_menu_items[nsel]}"
   1049   else
   1050     "$_ble_complete_menu_class"/onaccept "$nsel"
   1051   fi
   1052 }
   1053 
   1054 function ble-decode/keymap:menu/define {
   1055   # ble-bind -f __defchar__ menu_complete/self-insert
   1056   # ble-bind -f __default__ 'menu_complete/exit-default'
   1057   ble-bind -f __default__ 'bell'
   1058   ble-bind -f __line_limit__ nop
   1059   ble-bind -f C-m         'menu/accept'
   1060   ble-bind -f RET         'menu/accept'
   1061   ble-bind -f C-g         'menu/cancel'
   1062   ble-bind -f 'C-x C-g'   'menu/cancel'
   1063   ble-bind -f 'C-M-g'     'menu/cancel'
   1064   ble-bind -f C-f         'menu/forward-column'
   1065   ble-bind -f right       'menu/forward-column'
   1066   ble-bind -f C-i         'menu/forward cyclic'
   1067   ble-bind -f TAB         'menu/forward cyclic'
   1068   ble-bind -f C-b         'menu/backward-column'
   1069   ble-bind -f left        'menu/backward-column'
   1070   ble-bind -f C-S-i       'menu/backward cyclic'
   1071   ble-bind -f S-TAB       'menu/backward cyclic'
   1072   ble-bind -f C-n         'menu/forward-line'
   1073   ble-bind -f down        'menu/forward-line'
   1074   ble-bind -f C-p         'menu/backward-line'
   1075   ble-bind -f up          'menu/backward-line'
   1076   ble-bind -f prior       'menu/backward-page'
   1077   ble-bind -f next        'menu/forward-page'
   1078   ble-bind -f home        'menu/beginning-of-page'
   1079   ble-bind -f end         'menu/end-of-page'
   1080 }
   1081 
   1082 # sample implementation
   1083 function ble/complete/menu.class/onaccept {
   1084   local hook=$_ble_complete_menu_accept_hook
   1085   _ble_complete_menu_accept_hook=
   1086   "$hook" "$@"
   1087 }
   1088 function ble/complete/menu.class/oncancel {
   1089   local hook=$_ble_complete_menu_cancel_hook
   1090   _ble_complete_menu_cancel_hook=
   1091   "$hook" "$@"
   1092 }
   1093 function ble/complete/menu#start {
   1094   _ble_complete_menu_accept_hook=$1; shift
   1095   _ble_complete_menu_cancel_hook=
   1096 
   1097   local menu_style=linewise
   1098   local menu_items; menu_items=("$@")
   1099   local menu_class=ble/complete/menu.class menu_param=
   1100   ble/complete/menu#construct sync || return "$?"
   1101   ble/complete/menu#show
   1102   ble/complete/menu#select 0
   1103   ble/decode/keymap/push menu
   1104   return 147
   1105 }
   1106 
   1107 # 
   1108 #==============================================================================
   1109 # 候補源 (context, source, action)
   1110 
   1111 ## ble/complete 内で共通で使われるローカル変数
   1112 ##
   1113 ## @var COMP1 COMP2 COMPS COMPV
   1114 ##   COMP1-COMP2 は補完対象の範囲を指定します。
   1115 ##   COMPS は COMP1-COMP2 にある文字列を表し、
   1116 ##   COMPV は COMPS の評価値 (クォート除去、簡単なパラメータ展開をした値) を表します。
   1117 ##   COMPS に複雑な構造が含まれていて即時評価ができない場合は
   1118 ##   COMPV は unset になります。必要な場合は [[ $comps_flags == *v* ]] で判定して下さい。
   1119 ##   ※ [[ -v COMPV ]] は bash-4.2 以降です。
   1120 ##
   1121 ## @var comp_type
   1122 ##   候補生成の方法を制御します。
   1123 ##   以下のオプションのコロン区切りの組み合わせからなる文字列です。
   1124 ##
   1125 ##   a 曖昧補完に用いる候補を生成する。
   1126 ##     曖昧一致するかどうかは呼び出し元で判定されるので、
   1127 ##     曖昧一致する可能性のある候補をできるだけ多く生成すれば良い。
   1128 ##   m 曖昧補完 (中間部分に一致)
   1129 ##   A 曖昧補完 (部分列・最初の文字も一致しなくて良い)
   1130 ##
   1131 ##   i (rlvar completion-ignore-case)
   1132 ##     大文字小文字を区別しない補完候補生成を行う。
   1133 ##   vstat (rlvar visible-stats)
   1134 ##     ファイル名末尾にファイルの種類を示す記号を付加する。
   1135 ##   markdir (rlvar mark-directories)
   1136 ##     ディレクトリ名の補完後に / を付加する。
   1137 ##
   1138 ##   sync
   1139 ##     ユーザの入力があっても中断しない事を表す。
   1140 ##   raw
   1141 ##     COMPV としてシェル評価前の文字列を使用します。
   1142 ##
   1143 
   1144 function ble/complete/check-cancel {
   1145   [[ :$comp_type: != *:sync:* ]] && ble/decode/has-input
   1146 }
   1147 
   1148 #------------------------------------------------------------------------------
   1149 # action
   1150 
   1151 ## 既存の action
   1152 ##
   1153 ##   ble/complete/action:plain
   1154 ##   ble/complete/action:word
   1155 ##   ble/complete/action:file
   1156 ##   ble/complete/action:progcomp
   1157 ##   ble/complete/action:command
   1158 ##   ble/complete/action:variable
   1159 ##
   1160 ## action の実装
   1161 ##
   1162 ## @fn ble/complete/action:$ACTION/initialize
   1163 ##   基本的に INSERT を設定すれば良い
   1164 ##   @var[in    ] CAND
   1165 ##   @var[in,out] ACTION
   1166 ##   @var[in,out] DATA
   1167 ##   @var[in,out] INSERT
   1168 ##     COMP1-COMP2 を置き換える文字列を指定します
   1169 ##
   1170 ##   @var[in] COMP1 COMP2 COMPS COMPV comp_type
   1171 ##
   1172 ##   @var[in    ] COMP_PREFIX
   1173 ##
   1174 ##   @var[in    ] comps_flags
   1175 ##     以下のフラグ文字からなる文字列です。
   1176 ##
   1177 ##     p パラメータ展開の直後に於ける補完である事を表します。
   1178 ##       直後に識別子を構成する文字を追記する時に対処が必要です。
   1179 ##
   1180 ##     v COMPV が利用可能である事を表します。
   1181 ##     f failglob で COMPV 評価が失敗した事を表します。
   1182 ##
   1183 ##     S クォート ''  の中にいる事を表します。
   1184 ##     E クォート $'' の中にいる事を表します。
   1185 ##     D クォート ""  の中にいる事を表します。
   1186 ##     I クォート $"" の中にいる事を表します。
   1187 ##     B クォート \   の直後にいる事を表します。
   1188 ##     x ブレース展開の中にいる事を表します。
   1189 ##
   1190 ##     Note: shopt -s nocaseglob のため、フラグ文字は
   1191 ##       大文字・小文字でも重複しないように定義する必要がある。
   1192 ##
   1193 ##   @var[in    ] comps_fixed
   1194 ##     補完対象がブレース展開を含む場合に ibrace:value の形式になります。
   1195 ##     それ以外の場合は空文字列です。
   1196 ##     ibrace はブレース展開の構造を保持するのに必要な COMPS 接頭辞の長さです。
   1197 ##     value は ${COMPS::ibrace} のブレース展開を実行した結果の最後の単語の評価結果です。
   1198 ##
   1199 ## @fn ble/complete/action:$ACTION/complete
   1200 ##   一意確定時に、挿入文字列・範囲に対する加工を行います。
   1201 ##   例えばディレクトリ名の場合に / を後に付け加える等です。
   1202 ##
   1203 ##   @var[in] CAND
   1204 ##   @var[in] ACTION
   1205 ##   @var[in] DATA
   1206 ##   @var[in] COMP1 COMP2 COMPS COMPV comp_type comps_flags
   1207 ##
   1208 ##   @var[in,out] insert suffix
   1209 ##     補完によって挿入される文字列を指定します。
   1210 ##     加工後の挿入する文字列を返します。
   1211 ##
   1212 ##   @var[in] insert_beg insert_end
   1213 ##     補完によって置換される範囲を指定します。
   1214 ##
   1215 ##   @var[in,out] insert_flags
   1216 ##     以下のフラグ文字の組み合わせの文字列です。
   1217 ##
   1218 ##     r   [in] 既存の部分を保持したまま補完が実行される事を表します。
   1219 ##         それ以外の時、既存の入力部分も含めて置換されます。
   1220 ##     m   [out] 候補一覧 (menu) の表示を要求する事を表します。
   1221 ##     n   [out] 再度補完を試み (確定せずに) 候補一覧を表示する事を要求します。
   1222 ##
   1223 
   1224 function ble/complete/string#escape-for-completion-context {
   1225   local str=$1 escape_flags=$2
   1226   case $comps_flags in
   1227   (*S*)    ble/string#escape-for-bash-single-quote "$str"  ;;
   1228   (*E*)    ble/string#escape-for-bash-escape-string "$str" ;;
   1229   (*[DI]*) ble/string#escape-for-bash-double-quote "$str"  ;;
   1230   (*)
   1231     if [[ $comps_fixed ]]; then
   1232       ble/string#escape-for-bash-specialchars "$str" "b$escape_flags"
   1233     else
   1234       ble/string#escape-for-bash-specialchars "$str" "$escape_flags"
   1235     fi ;;
   1236   esac
   1237 }
   1238 
   1239 function ble/complete/action/complete.addtail {
   1240   suffix=$suffix$1
   1241 }
   1242 function ble/complete/action/complete.mark-directory {
   1243   [[ :$comp_type: == *:markdir:* && $CAND != */ ]] &&
   1244     [[ ! -h $CAND || ( $insert == "$COMPS" || :$comp_type: == *:marksymdir:* ) ]] &&
   1245     ble/complete/action/complete.addtail /
   1246 }
   1247 function ble/complete/action/complete.close-quotation {
   1248   case $comps_flags in
   1249   (*[SE]*) ble/complete/action/complete.addtail \' ;;
   1250   (*[DI]*) ble/complete/action/complete.addtail \" ;;
   1251   esac
   1252 }
   1253 
   1254 ## @fn ble/complete/action/quote-insert.initialize action
   1255 ##   @var[out] ${_ble_complete_quote_insert_varnames[@]}
   1256 ##
   1257 ## @fn ble/complete/action/quote-insert action
   1258 ##   @var[ref] INSERT
   1259 ##   @var[in] ${_ble_complete_quote_insert_varnames[@]}
   1260 ##
   1261 ## Note: quote-insert を呼び出す前に予め quote-insert.initialize を呼び出して
   1262 ## quote_... 変数を初期化しておく必要があります。
   1263 ##
   1264 ## Example:
   1265 ##
   1266 ##   local "${_ble_complete_quote_insert_varnames[@]/%/=}" # WA #D1570 checked
   1267 ##   ble/complete/action/quote-insert.initialize "$action"
   1268 ##   for INSERT; do
   1269 ##     ble/complete/action/quote-insert "$action"
   1270 ##     : do something with INSERT
   1271 ##   done
   1272 ##
   1273 
   1274 _ble_complete_quote_insert_varnames=(
   1275   quote_action
   1276   quote_escape_flags
   1277   quote_cont_cutbackslash
   1278   quote_paramx_comps
   1279   quote_trav_prefix
   1280   quote_fixed_comps
   1281   quote_fixed_compv
   1282   quote_fixed_comps_len
   1283   quote_fixed_compv_len)
   1284 
   1285 function ble/complete/action/quote-insert.initialize {
   1286   quote_action=$1
   1287 
   1288   quote_escape_flags=c
   1289   if [[ $quote_action == command ]]; then
   1290     quote_escape_flags=
   1291   elif [[ $quote_action == progcomp ]]; then
   1292     # #D1362 Bash は "compopt -o filenames" が指定されている時、
   1293     # '~' で始まる補完候補と同名のファイルがある時にのみチルダをクォートする。
   1294     # [[ $CAND == '~'* && ! ( $comp_opts == *:filenames:* && -e $CAND ) ]] &&
   1295     #   quote_escape_flags=T$quote_escape_flags
   1296     # #D1434 = 及び : は filenames がついていない限りは quote しない事にする。
   1297     #    bash-complete が unquoted =, : を生成する可能性があるので。
   1298     [[ $comp_opts != *:filenames:* ]] &&
   1299       quote_escape_flags=${quote_escape_flags//c}
   1300   fi
   1301   [[ $comps_fixed ]] && quote_escape_flags=b$quote_escape_flags
   1302 
   1303   # 孤立 backslash が前置している時は二重クォートを防ぐ為に削除
   1304   quote_cont_cutbackslash=
   1305   [[ $comps_flags == *B* && $COMPS == *'\' ]] &&
   1306     quote_cont_cutbackslash=1
   1307 
   1308   # 直前にパラメータ展開があればエスケープ
   1309   quote_paramx_comps=$COMPS
   1310   if [[ $comps_flags == *p* ]]; then
   1311     # Note: 安全策 (本来 comps_flags に p がある時点で '\' では終わらない筈)
   1312     [[ $comps_flags == *B* && $quote_paramx_comps == *'\' ]] &&
   1313       quote_paramx_comps=${quote_paramx_comps%'\'}
   1314 
   1315     case $comps_flags in
   1316     (*[DI]*)
   1317       if [[ $COMPS =~ $rex_raw_paramx ]]; then
   1318         local rematch1=${BASH_REMATCH[1]}
   1319         quote_paramx_comps=$rematch1'${'${COMPS:${#rematch1}+1}'}'
   1320       else
   1321         # Note: 安全策 (本来上で一致する筈)
   1322         quote_paramx_comps=$quote_paramx_comps'""'
   1323       fi ;;
   1324     (*)
   1325       quote_paramx_comps=$quote_paramx_comps'\' ;;
   1326     esac
   1327   fi
   1328 
   1329   # 遡って書き換えた時に文脈を復元
   1330   quote_trav_prefix=
   1331   case $comps_flags in
   1332   (*S*) quote_trav_prefix=\' ;;
   1333   (*E*) quote_trav_prefix=\$\' ;;
   1334   (*D*) quote_trav_prefix=\" ;;
   1335   (*I*) quote_trav_prefix=\$\" ;;
   1336   esac
   1337 
   1338   # 遡って書き換える時に comps_fixed には注意する。
   1339   quote_fixed_comps=('')
   1340   quote_fixed_compv=('')
   1341   quote_fixed_comps_len=('')
   1342   quote_fixed_compv_len=('')
   1343   if [[ $comps_fixed ]]; then
   1344     quote_fixed_compv=${comps_fixed#*:}
   1345     quote_fixed_compv_len=${#quote_fixed_compv}
   1346     quote_fixed_comps_len=${comps_fixed%%:*}
   1347     quote_fixed_comps=${COMPS::quote_fixed_comps_len}
   1348   fi
   1349 
   1350   # 遡って書き換える時に '/' 区切りでできるだけ元の展開を保持する。
   1351   # comps_fixed[1] 以降に '/' 区切りで展開した結果を短い順に格納する。
   1352   local i v
   1353   for ((i=1;i<${#comps_fixed[@]};i++)); do
   1354     v=${comps_fixed[i]#*:}
   1355     quote_fixed_compv[i]=$v
   1356     quote_fixed_compv_len[i]=${#v}
   1357     quote_fixed_comps_len[i]=${comps_fixed[i]%%:*}
   1358     quote_fixed_comps[i]=${COMPS::quote_fixed_comps_len[i]}
   1359   done
   1360 }
   1361 
   1362 ## @fn ble/complete/action/quote-insert
   1363 # Note: この関数の処理は ble/complete/action/quote-insert.batch/awk と一貫して
   1364 # いる必要がある。この関数を変更する時には quote-insert.batch/awk にも等価の変
   1365 # 更を適用する必要がある。
   1366 function ble/complete/action/quote-insert {
   1367   if [[ ! $quote_action ]]; then
   1368     local "${_ble_complete_quote_insert_varnames[@]/%/=}" # WA #D1570 checked
   1369     ble/complete/action/quote-insert.initialize "${1:-plain}"
   1370   fi
   1371 
   1372   local escape_flags=$quote_escape_flags
   1373   if [[ $quote_action == command ]]; then
   1374     # Note (#D1715,#D1978): "*:noquote:*" の判定について。action=command
   1375     #   DATA=:noquote: は alias 生成のみで使われる。そして alias 生成は
   1376     #   yield.batch を使わずに直接 yield を呼び出して行われる。なので :noquote:
   1377     #   の判定は awk batch の側では行わなくて良い。
   1378     [[ $DATA == *:noquote:* || $COMPS == "$COMPV" && ( $CAND == '[[' || $CAND == '!' ) ]] && return 0
   1379   elif [[ $quote_action == progcomp ]]; then
   1380     [[ $comp_opts == *:noquote:* ]] && return 0
   1381     [[ $comp_opts == *:ble/syntax-raw:* && $comp_opts != *:filenames:* ]] && return 0
   1382 
   1383     # bash-completion には compopt -o nospace として、
   1384     # 自分でスペースを付加する補完関数がある。この時クォートすると問題。
   1385     [[ $comp_opts == *:nospace:* && $CAND == *' ' && ! -f $CAND ]] && return 0
   1386 
   1387     # #D1362 Bash は "compopt -o filenames" が指定されていてかつ
   1388     # '~' で始まる補完候補と同名のファイルがある時にのみチルダをクォートする。
   1389     [[ $CAND == '~'* && ! ( $comp_opts == *:filenames:* && -e $CAND ) ]] &&
   1390       escape_flags=T$escape_flags
   1391   fi
   1392 
   1393   # 入力済み文字列への追記の場合、元の単語を保持する。
   1394   if [[ $comps_flags == *v* && $CAND == "$COMPV"* ]]; then
   1395     local ins ret
   1396     ble/complete/string#escape-for-completion-context "${CAND:${#COMPV}}" "$escape_flags"; ins=$ret
   1397     if [[ $comps_flags == *p* && $ins == [_a-zA-Z0-9]* ]]; then
   1398       INSERT=$quote_paramx_comps$ins
   1399     else
   1400       [[ $quote_cont_cutbackslash ]] && ins=${ins#'\'}
   1401       INSERT=$COMPS$ins;
   1402     fi
   1403     return 0
   1404   fi
   1405 
   1406   # 遡って書き換わる場合には単語内のできるだけ長い部分パスを保持する。
   1407   local i=${#quote_fixed_comps[@]}
   1408   while ((--i>=0)); do
   1409     if [[ ${quote_fixed_comps[i]} && $CAND == "${quote_fixed_compv[i]}"* ]]; then
   1410       local ret; ble/complete/string#escape-for-completion-context "${CAND:quote_fixed_compv_len[i]}" "$escape_flags"
   1411       INSERT=${quote_fixed_comps[i]}$quote_trav_prefix$ret
   1412       return 0
   1413     fi
   1414   done
   1415 
   1416   # 既存の物に一致しない場合、完全に書き換える。
   1417   local ret; ble/complete/string#escape-for-completion-context "$CAND" "$escape_flags"
   1418   INSERT=$quote_trav_prefix$ret
   1419 }
   1420 
   1421 function ble/complete/action/quote-insert.batch/awk {
   1422   local q=\'
   1423   local -x comp_opts=$comp_opts
   1424   local -x comps=$COMPS
   1425   local -x compv=$COMPV
   1426   local -x comps_flags=$comps_flags
   1427   local -x quote_action=$quote_action
   1428   local -x quote_escape_flags=$quote_escape_flags
   1429   local -x quote_paramx_comps=$quote_paramx_comps
   1430   local -x quote_cont_cutbackslash=$quote_cont_cutbackslash
   1431   local -x quote_trav_prefix=$quote_trav_prefix
   1432 
   1433   local -x quote_fixed_count=${#quote_fixed_comps[@]}
   1434   local i
   1435   for ((i=0;i<quote_fixed_count;i++)); do
   1436     local -x "quote_fixed_comps$i=${quote_fixed_comps[i]}"
   1437     local -x "quote_fixed_compv$i=${quote_fixed_compv[i]}"
   1438   done
   1439 
   1440   "$quote_batch_awk" -v quote_batch_nulsep="$quote_batch_nulsep" -v q="$q" '
   1441     function exists(filename) { return substr($0, 1, 1) == "1"; }
   1442     function is_file(filename) { return substr($0, 2, 1) == "1"; }
   1443 
   1444     function initialize(_, flags, comp_opts, tmp, i) {
   1445       IS_XPG4 = AWKTYPE == "xpg4";
   1446       REP_SL = "\\";
   1447       if (IS_XPG4) REP_SL = "\\\\";
   1448 
   1449       REP_DBL_SL = "\\\\"; # gawk, nawk
   1450       sub(/.*/, REP_DBL_SL, tmp);
   1451       if (tmp == "\\") REP_DBL_SL = "\\\\\\\\"; # mawk, xpg4
   1452 
   1453       Q = q "\\" q q;
   1454 
   1455       DELIM = 10;
   1456       if (quote_batch_nulsep != "") {
   1457         RS = "\0";
   1458         DELIM = 0;
   1459       }
   1460 
   1461       quote_action = ENVIRON["quote_action"];
   1462 
   1463       comps = ENVIRON["comps"];
   1464       compv = ENVIRON["compv"];
   1465       compv_len = length(compv);
   1466 
   1467       comps_flags = ENVIRON["comps_flags"];
   1468       escape_type = 0;
   1469       if (comps_flags ~ /S/)
   1470         escape_type = 1;
   1471       else if (comps_flags ~ /E/)
   1472         escape_type = 2;
   1473       else if (comps_flags ~ /[DI]/)
   1474         escape_type = 3;
   1475       else
   1476         escape_type = 4;
   1477       comps_v = (comps_flags ~ /v/);
   1478       comps_p = (comps_flags ~ /p/);
   1479 
   1480       comp_opts = ENVIRON["comp_opts"];
   1481       is_noquote = comp_opts ~ /:noquote:/;
   1482       is_nospace = comp_opts ~ /:nospace:/;
   1483       is_syntaxraw = comp_opts ~ /:ble\/syntax-raw:/ && comp_opts !~ /:filenames:/;
   1484 
   1485       flags = ENVIRON["quote_escape_flags"];
   1486       escape_c = (flags ~ /c/);
   1487       escape_b = (flags ~ /b/);
   1488       escape_tilde_always = 1;
   1489       escape_tilde_exists = 0;
   1490       if (quote_action == "progcomp") {
   1491         escape_tilde_always = 0;
   1492         escape_tilde_exists = (comp_opts ~ /:filenames:/);
   1493       }
   1494 
   1495       quote_cont_cutbackslash   = ENVIRON["quote_cont_cutbackslash"] != "";
   1496       quote_paramx_comps        = ENVIRON["quote_paramx_comps"];
   1497       quote_trav_prefix     = ENVIRON["quote_trav_prefix"];
   1498 
   1499       quote_fixed_count = ENVIRON["quote_fixed_count"];
   1500       for (i = 0; i < quote_fixed_count; i++) {
   1501         quote_fixed_comps[i]     = ENVIRON["quote_fixed_comps" i];
   1502         quote_fixed_compv[i]     = ENVIRON["quote_fixed_compv" i];
   1503         quote_fixed_comps_len[i] = length(quote_fixed_comps[i]);
   1504         quote_fixed_compv_len[i] = length(quote_fixed_compv[i]);
   1505       }
   1506     }
   1507     BEGIN { initialize(); }
   1508 
   1509     function escape_for_completion_context(text) {
   1510       if (escape_type == 1) {
   1511         # single quote
   1512         gsub(/'$q'/, Q, text);
   1513       } else if (escape_type == 2) {
   1514         # escape string
   1515         if (text ~ /[\\'$q'\a\b\t\n\v\f\r\033]/) {
   1516           gsub(/\\/  , REP_DBL_SL, text);
   1517           gsub(/'$q'/, REP_SL q  , text);
   1518           gsub(/\007/, REP_SL "a", text);
   1519           gsub(/\010/, REP_SL "b", text);
   1520           gsub(/\011/, REP_SL "t", text);
   1521           gsub(/\012/, REP_SL "n", text);
   1522           gsub(/\013/, REP_SL "v", text);
   1523           gsub(/\014/, REP_SL "f", text);
   1524           gsub(/\015/, REP_SL "r", text);
   1525           gsub(/\033/, REP_SL "e", text);
   1526         }
   1527       } else if (escape_type == 3) {
   1528         # double quote
   1529         gsub(/[\\"$`]/, "\\\\&", text); # Note: All awks behaves the same for "\\\\&"
   1530       } else if (escape_type == 4) {
   1531         # bash specialchars
   1532         gsub(/[]\\ "'$q'`$|&;<>()!^*?[]/, "\\\\&", text);
   1533         if (escape_c) gsub(/[=:]/, "\\\\&", text);
   1534         if (escape_b) gsub(/[{,}]/, "\\\\&", text);
   1535         if (ret ~ /^~/ && (escape_tilde_always || escape_tilde_exists && exists(cand)))
   1536           text = "\\" text;
   1537         gsub(/\n/, "$" q REP_SL "n" q, text);
   1538         gsub(/\t/, "$" q REP_SL "t" q, text);
   1539       }
   1540       return text;
   1541     }
   1542 
   1543     function quote_insert(cand, _, i) {
   1544       # progcomp 特有
   1545       if (quote_action == "command") {
   1546         if (comps == compv && cand ~ /^(\[\[|]]|!)$/) return cand;
   1547       } else if (quote_action == "progcomp") {
   1548         if (is_noquote || is_syntaxraw) return cand;
   1549         if (is_nospace && cand ~ / $/ && !is_file(cand)) return cand;
   1550       }
   1551 
   1552       if (comps_v && substr(cand, 1, compv_len) == compv) {
   1553         ins = escape_for_completion_context(substr(cand, compv_len + 1));
   1554         if (comps_p && ins ~ /^[_a-zA-Z0-9]/) {
   1555           return quote_paramx_comps ins;
   1556         } else {
   1557           if (quote_cont_cutbackslash) sub(/^\\/, "", ins);
   1558           return comps ins;
   1559         }
   1560       }
   1561 
   1562       for (i = quote_fixed_count; --i >= 0; ) {
   1563         if (quote_fixed_comps_len[i] && substr(cand, 1, quote_fixed_compv_len[i]) == quote_fixed_compv[i]) {
   1564           ins = substr(cand, quote_fixed_compv_len[i] + 1);
   1565           return quote_fixed_comps[i] quote_trav_prefix escape_for_completion_context(ins);
   1566         }
   1567       }
   1568 
   1569       return quote_trav_prefix escape_for_completion_context(cand);
   1570     }
   1571 
   1572     {
   1573       cand = substr($0, 3);
   1574       insert = quote_insert(cand);
   1575       printf("%s%c", insert, DELIM);
   1576     }
   1577   '
   1578 }
   1579 function ble/complete/action/quote-insert.batch/proc {
   1580   local _ble_local_tmpfile; ble/util/assign/mktmp
   1581 
   1582   local delim='\n'
   1583   [[ $quote_batch_nulsep ]] && delim='\0'
   1584   if [[ $quote_action == progcomp ]]; then
   1585     local cand file exist
   1586     for cand in "${cands[@]}"; do
   1587       ((cand_iloop++%bleopt_complete_polling_cycle==0)) && ble/complete/check-cancel && return 148
   1588       f=0 e=0
   1589       [[ -e $cand ]] && e=1
   1590       [[ -f $cand ]] && f=1
   1591       printf "$e$f%s$delim" "$cand"
   1592     done
   1593   else
   1594     printf "00%s$delim" "${cands[@]}"
   1595   fi >| "$_ble_local_tmpfile"
   1596 
   1597   local fname_cands=$_ble_local_tmpfile
   1598   ble/util/conditional-sync \
   1599     'ble/complete/action/quote-insert.batch/awk < "$fname_cands"' \
   1600     '! ble/complete/check-cancel <&"$_ble_util_fd_stdin"' '' progressive-weight
   1601   local ext=$?
   1602 
   1603   ble/util/assign/rmtmp
   1604   return "$ext"
   1605 }
   1606 ## @fn ble/complete/action/quote-insert.batch
   1607 ##   @arr[in] cands
   1608 ##   @arr[out] inserts
   1609 function ble/complete/action/quote-insert.batch {
   1610   local opts=$1
   1611 
   1612   local quote_batch_nulsep=
   1613   local quote_batch_awk=ble/bin/awk
   1614   if [[ :$opts: != *:newline:* ]]; then
   1615     if ((_ble_bash>=40400)); then
   1616       if [[ $_ble_bin_awk_type == [mg]awk ]]; then
   1617         quote_batch_nulsep=1
   1618       elif ble/bin#has mawk; then
   1619         quote_batch_nulsep=1
   1620         quote_batch_awk=mawk
   1621       elif ble/bin#has gawk; then
   1622         quote_batch_nulsep=1
   1623         quote_batch_awk=gawk
   1624       fi
   1625     fi
   1626     [[ ! $quote_batch_nulsep ]] &&
   1627       [[ "${cands[*]}" == *$'\n'* ]] &&
   1628       return 1
   1629   fi
   1630 
   1631   if [[ $quote_batch_nulsep ]]; then
   1632     ble/util/assign-array0 inserts ble/complete/action/quote-insert.batch/proc
   1633   else
   1634     ble/util/assign-array inserts ble/complete/action/quote-insert.batch/proc
   1635   fi
   1636   return "$?"
   1637 }
   1638 
   1639 ## @fn ble/complete/action/requote-final-insert
   1640 ##   @var[ref] insert insert_flags
   1641 function ble/complete/action/requote-final-insert {
   1642   local threshold=$((bleopt_complete_requote_threshold))
   1643   ((threshold>=0)) || return 0
   1644 
   1645   local comps_prefix= check_optarg=
   1646   if [[ $insert == "$COMPS"* ]]; then
   1647     [[ $comps_flags == *[SEDI]* ]] && return 0
   1648 
   1649     # Note: 以下の設定は遡って書き換える事を許す事になる
   1650     [[ $COMPS != *[!':/={,'] ]] && comps_prefix=$COMPS
   1651     check_optarg=$COMPS
   1652   else
   1653     # 遡って書き換える場合 (中途半端な quote 状態ではないと仮定)
   1654     check_optarg=$insert
   1655   fi
   1656 
   1657   # Note: --prefix='/usr/local', PREFIX='/usr/local', -L'/usr/local/share/lib'
   1658   # 等、オプション・変数代入の右辺などの quote は、その開始点と思われる箇所から
   1659   # 始める。
   1660   if [[ $check_optarg ]]; then
   1661     if ble/string#match "$check_optarg" '^([_a-zA-Z][_a-zA-Z0-9]*|-[-a-zA-Z0-9.]+)=(([^\'\''"`${}]*|\\.)*:)?'; then
   1662       # --prefix= や PREFIX=, PATH=xxxx: 等があった場合には = や : の直後から quote する。
   1663       comps_prefix=$BASH_REMATCH
   1664     elif [[ $COMP_PREFIX == -[!'-=:/\'\''"$`{};&|<>!^{}'] && $check_optarg == "$COMP_PREFIX"* ]]; then
   1665       # -L'/path/to/library' 等。COMP_PREFIX=-L かつ COMPS が -L で始まっている時のみ。
   1666       comps_prefix=${check_optarg::2}
   1667     fi
   1668   fi
   1669 
   1670   if [[ $comps_fixed ]]; then
   1671     local comps_fixed_part=${COMPS::${comps_fixed%%:*}}
   1672     [[ $comps_prefix == "$comps_fixed_part"* ]] ||
   1673       comps_prefix=$comps_fixed_part
   1674   fi
   1675 
   1676   if [[ $insert == "$comps_prefix"* && $comps_prefix != *[!':/={,'] ]]; then
   1677     local ret ins=${insert:${#comps_prefix}}
   1678     if ! ble/syntax:bash/simple-word/is-literal "$ins" &&
   1679         ble/syntax:bash/simple-word/is-simple "$ins" &&
   1680         ble/syntax:bash/simple-word/eval "$ins" &&
   1681         ((${#ret[@]}==1))
   1682     then
   1683       ble/string#quote-word "$ret" quote-empty
   1684       ((${#ret}+threshold<=${#ins})) || return 0
   1685       insert=$comps_prefix$ret
   1686       [[ $insert == "$COMPS"* ]] || insert_flags=r$insert_flags # 遡って書き換えた
   1687     fi
   1688   fi
   1689   return 0
   1690 }
   1691 
   1692 function ble/complete/action#inherit-from {
   1693   local dst=$1 src=$2
   1694   local member srcfunc dstfunc
   1695   for member in initialize{,.batch} complete getg get-desc; do
   1696     srcfunc=ble/complete/action:$src/$member
   1697     dstfunc=ble/complete/action:$dst/$member
   1698     ble/is-function "$srcfunc" && builtin eval "function $dstfunc { $srcfunc; }"
   1699   done
   1700 }
   1701 
   1702 # action:plain
   1703 function ble/complete/action:plain/initialize {
   1704   ble/complete/action/quote-insert
   1705 }
   1706 function ble/complete/action:plain/initialize.batch {
   1707   ble/complete/action/quote-insert.batch
   1708 }
   1709 function ble/complete/action:plain/complete {
   1710   ble/complete/action/requote-final-insert
   1711 }
   1712 
   1713 # action:literal-substr
   1714 function ble/complete/action:literal-substr/initialize { :; }
   1715 function ble/complete/action:literal-substr/initialize.batch { inserts=("${cands[@]}"); }
   1716 function ble/complete/action:literal-substr/complete { :; }
   1717 
   1718 # action:substr (equivalent to plain)
   1719 function ble/complete/action:substr/initialize {
   1720   ble/complete/action/quote-insert
   1721 }
   1722 function ble/complete/action:substr/initialize.batch {
   1723   ble/complete/action/quote-insert.batch
   1724 }
   1725 function ble/complete/action:substr/complete {
   1726   ble/complete/action/requote-final-insert
   1727 }
   1728 
   1729 # action:literal-word
   1730 function ble/complete/action:literal-word/initialize { :; }
   1731 function ble/complete/action:literal-word/initialize.batch { inserts=("${cands[@]}"); }
   1732 function ble/complete/action:literal-word/complete {
   1733   if [[ $comps_flags == *x* ]]; then
   1734     ble/complete/action/complete.addtail ','
   1735   else
   1736     ble/complete/action/complete.addtail ' '
   1737   fi
   1738 }
   1739 
   1740 # action:word
   1741 #
   1742 #   DATA ... 候補の説明として使用する文字列を指定します
   1743 #
   1744 function ble/complete/action:word/initialize {
   1745   ble/complete/action/quote-insert
   1746 }
   1747 function ble/complete/action:word/initialize.batch {
   1748   ble/complete/action/quote-insert.batch
   1749 }
   1750 function ble/complete/action:word/complete {
   1751   ble/complete/action/requote-final-insert
   1752   ble/complete/action/complete.close-quotation
   1753   ble/complete/action:literal-word/complete
   1754 }
   1755 function ble/complete/action:word/get-desc {
   1756   [[ $DATA ]] && desc=$DATA
   1757 }
   1758 
   1759 # action:file
   1760 # action:file_rhs (source:argument 内部使用)
   1761 
   1762 ## @fn ble/complete/action:file/.get-filename word
   1763 ##   "compopt -o ble/syntax-raw" の場合も考慮してファイル名を抽出する。
   1764 ##   Bash の振る舞いを見るとチルダ展開だけを実行する様だ。
   1765 ##   @var[in] CAND DATA
   1766 function ble/complete/action:file/.get-filename {
   1767   ret=$CAND
   1768   if [[ $ACTION == progcomp && :$DATA: == *:ble/syntax-raw:* && $ret == '~'* ]]; then
   1769     local tilde=${ret%%/*} chars='\ "'\''`$|&;<>()!^*?[=:{,}'
   1770     [[ $tilde == *["$chars"]* ]] && return 0
   1771     builtin eval "local expand=$tilde"
   1772     [[ $expand == "$tilde" ]] && return 0
   1773     ret=$expand${ret:${#tilde}}
   1774   fi
   1775 }
   1776 function ble/complete/action:file/initialize {
   1777   ble/complete/action/quote-insert
   1778 }
   1779 function ble/complete/action:file/initialize.batch {
   1780   ble/complete/action/quote-insert.batch
   1781 }
   1782 function ble/complete/action:file/complete {
   1783   ble/complete/action/requote-final-insert
   1784 
   1785   local ret
   1786   ble/complete/action:file/.get-filename
   1787   if [[ -e $ret || -h $ret ]]; then
   1788     if [[ -d $ret ]]; then
   1789       ble/complete/action/complete.mark-directory
   1790     else
   1791       ble/complete/action:word/complete
   1792     fi
   1793   fi
   1794 }
   1795 function ble/complete/action:file/init-menu-item {
   1796   local ret
   1797   ble/complete/action:file/.get-filename; local file=$ret
   1798   ble/syntax/highlight/getg-from-filename "$file"
   1799   [[ $g ]] || { local ret; ble/color/face2g filename_warning; g=$ret; }
   1800 
   1801   if [[ :$comp_type: == *:vstat:* ]]; then
   1802     if [[ -h $file ]]; then
   1803       suffix='@'
   1804     elif [[ -d $file ]]; then
   1805       suffix='/'
   1806     elif [[ -x $file ]]; then
   1807       suffix='*'
   1808     fi
   1809   fi
   1810 }
   1811 function ble/complete/action:file_rhs/initialize {
   1812   ble/complete/action:file/initialize
   1813 }
   1814 function ble/complete/action:file_rhs/initialize.batch {
   1815   ble/complete/action:file/initialize.batch
   1816 }
   1817 function ble/complete/action:file_rhs/complete {
   1818   CAND=${CAND:${#DATA}} ble/complete/action:file/complete
   1819 }
   1820 function ble/complete/action:file_rhs/init-menu-item {
   1821   CAND=${CAND:${#DATA}} ble/complete/action:file/init-menu-item
   1822 }
   1823 
   1824 _ble_complete_action_file_desc[_ble_attr_FILE_LINK]='symbolic link'
   1825 _ble_complete_action_file_desc[_ble_attr_FILE_ORPHAN]='symbolic link (orphan)'
   1826 _ble_complete_action_file_desc[_ble_attr_FILE_DIR]='directory'
   1827 _ble_complete_action_file_desc[_ble_attr_FILE_STICKY]='directory (sticky)'
   1828 _ble_complete_action_file_desc[_ble_attr_FILE_SETUID]='file (setuid)'
   1829 _ble_complete_action_file_desc[_ble_attr_FILE_SETGID]='file (setgid)'
   1830 _ble_complete_action_file_desc[_ble_attr_FILE_EXEC]='file (executable)'
   1831 _ble_complete_action_file_desc[_ble_attr_FILE_FILE]='file'
   1832 _ble_complete_action_file_desc[_ble_attr_FILE_CHR]='character device'
   1833 _ble_complete_action_file_desc[_ble_attr_FILE_FIFO]='named pipe'
   1834 _ble_complete_action_file_desc[_ble_attr_FILE_SOCK]='socket'
   1835 _ble_complete_action_file_desc[_ble_attr_FILE_BLK]='block device'
   1836 _ble_complete_action_file_desc[_ble_attr_FILE_URL]='URL'
   1837 function ble/complete/action:file/get-desc {
   1838   local type; ble/syntax/highlight/filetype "$CAND"
   1839   desc=${_ble_complete_action_file_desc[type]:-'file (???)'}
   1840 }
   1841 
   1842 # action:progcomp
   1843 #
   1844 #   DATA ... compopt 互換のオプションをコロン区切りで指定します
   1845 #
   1846 ## @fn ble/complete/action:progcomp/initialize/.reconstruct-from-noquote
   1847 ##   @var[in,out] INSERT CAND
   1848 ##   @var[in] progcomp_resolve_brace
   1849 function ble/complete/action:progcomp/initialize/.reconstruct-from-noquote {
   1850   local simple_flags simple_ibrace ret count
   1851   ble/syntax:bash/simple-word/is-simple-or-open-simple "$INSERT" &&
   1852     ble/syntax:bash/simple-word/reconstruct-incomplete-word "$INSERT" &&
   1853     ble/complete/source/eval-simple-word "$ret" single:count &&
   1854     ((count==1)) || return 0
   1855 
   1856   CAND=$ret
   1857 
   1858   # ブレース展開がある時は逆に INSERT を補正し返す。
   1859   if [[ $quote_fixed_comps && $CAND == "$quote_fixed_compv"* ]]; then
   1860     local ret; ble/complete/string#escape-for-completion-context "${CAND:quote_fixed_compv_len}" "$escape_flags"
   1861     INSERT=$quote_fixed_comps$quote_trav_prefix$ret
   1862     return 3
   1863   fi
   1864   return 0
   1865 }
   1866 
   1867 function ble/complete/action:progcomp/initialize {
   1868   if [[ :$DATA: == *:noquote:* ]]; then
   1869     local progcomp_resolve_brace=$quote_fixed_comps
   1870     [[ :$DATA: == *:ble/syntax-raw:* ]] && progcomp_resolve_brace=
   1871     ble/complete/action:progcomp/initialize/.reconstruct-from-noquote
   1872     return 0
   1873   else
   1874     ble/complete/action/quote-insert progcomp
   1875   fi
   1876 }
   1877 ## @fn ble/complete/action:progcomp/initialize.batch
   1878 ##   @arr[in] cands
   1879 ##   @arr[out] inserts
   1880 function ble/complete/action:progcomp/initialize.batch {
   1881   if [[ :$DATA: == *:noquote:* ]]; then
   1882     inserts=("${cands[@]}")
   1883 
   1884     # Note: 直接 comp_words に対して補完した時は意図的にブレース展開を潰してい
   1885     # ると解釈できるので、ブレース展開を復元する事はしない。
   1886     local progcomp_resolve_brace=$quote_fixed_comps
   1887     [[ :$DATA: == *:ble/syntax-raw:* ]] && progcomp_resolve_brace=
   1888 
   1889     cands=()
   1890     local INSERT simple_flags simple_ibrace ret count icand=0
   1891     for INSERT in "${inserts[@]}"; do
   1892       ((cand_iloop++%bleopt_complete_polling_cycle==0)) && ble/complete/check-cancel && return 148
   1893       local CAND=$INSERT
   1894       ble/complete/action:progcomp/initialize/.reconstruct-from-noquote ||
   1895         inserts[icand]=$INSERT # INSERT を上書きした時 ($?==3)
   1896       cands[icand++]=$CAND
   1897     done
   1898   else
   1899     ble/complete/action/quote-insert.batch newline
   1900   fi
   1901 }
   1902 
   1903 function ble/complete/action:progcomp/complete {
   1904   if [[ $DATA == *:filenames:* ]]; then
   1905     ble/complete/action:file/complete
   1906   else
   1907     if [[ $DATA != *:ble/no-mark-directories:* && -d $CAND ]]; then
   1908       ble/complete/action/requote-final-insert
   1909       ble/complete/action/complete.mark-directory
   1910     else
   1911       ble/complete/action:word/complete
   1912     fi
   1913   fi
   1914 
   1915   [[ $DATA == *:nospace:* ]] && suffix=${suffix%' '}
   1916   [[ $DATA == *:ble/no-mark-directories:* && -d $CAND ]] && suffix=${suffix%/}
   1917 }
   1918 function ble/complete/action:progcomp/init-menu-item {
   1919   if [[ $DATA == *:filenames:* ]]; then
   1920     ble/complete/action:file/init-menu-item
   1921   fi
   1922 }
   1923 function ble/complete/action:progcomp/get-desc {
   1924   if [[ $DATA == *:filenames:* ]]; then
   1925     ble/complete/action:file/get-desc
   1926   fi
   1927 }
   1928 
   1929 # action:command
   1930 
   1931 function ble/complete/action:command/initialize {
   1932   ble/complete/action/quote-insert command
   1933 }
   1934 function ble/complete/action:command/initialize.batch {
   1935   ble/complete/action/quote-insert.batch newline
   1936 }
   1937 function ble/complete/action:command/complete {
   1938   if [[ -d $CAND ]]; then
   1939     ble/complete/action/complete.mark-directory
   1940   elif ! type "$CAND" &>/dev/null; then
   1941     # 関数名について縮約されたもので一意確定した時。
   1942     #
   1943     # Note: 関数名について縮約されている時、
   1944     #   本来は一意確定でなくても一意確定として此処に来ることがある。
   1945     #   そのコマンドが存在していない時に、縮約されていると判定する。
   1946     #
   1947     if [[ $CAND == */ ]]; then
   1948       # 縮約されていると想定し続きの補完候補を出す。
   1949       insert_flags=${insert_flags}n
   1950     fi
   1951   else
   1952     ble/complete/action:word/complete
   1953   fi
   1954 }
   1955 function ble/complete/action:command/init-menu-item {
   1956   if [[ -d $CAND ]]; then
   1957     local ret; ble/color/face2g filename_directory; g=$ret
   1958   else
   1959     local type
   1960     if [[ $CAND != "$INSERT" ]]; then
   1961       ble/syntax/highlight/cmdtype "$CAND" "$INSERT"
   1962     else
   1963       # Note: ble/syntax/highlight/cmdtype はキャッシュ機能がついているが、
   1964       #   キーワードに対して呼び出さない前提なのでキーワードを渡すと
   1965       #   _ble_attr_ERR を返してしまう。
   1966       local type; ble/util/type type "$CAND"
   1967       ble/syntax/highlight/cmdtype1 "$type" "$CAND"
   1968     fi
   1969     if [[ $CAND == */ ]] && ((type==_ble_attr_ERR)); then
   1970       type=_ble_attr_CMD_FUNCTION
   1971     fi
   1972     ble/syntax/attr2g "$type"
   1973   fi
   1974 }
   1975 
   1976 _ble_complete_action_command_desc[_ble_attr_CMD_BOLD]=builtin
   1977 _ble_complete_action_command_desc[_ble_attr_CMD_BUILTIN]=builtin
   1978 _ble_complete_action_command_desc[_ble_attr_CMD_ALIAS]=alias
   1979 _ble_complete_action_command_desc[_ble_attr_CMD_FUNCTION]=function
   1980 _ble_complete_action_command_desc[_ble_attr_CMD_FILE]=file
   1981 _ble_complete_action_command_desc[_ble_attr_KEYWORD]=command
   1982 _ble_complete_action_command_desc[_ble_attr_CMD_JOBS]=job
   1983 _ble_complete_action_command_desc[_ble_attr_ERR]='command ???'
   1984 _ble_complete_action_command_desc[_ble_attr_CMD_DIR]=directory
   1985 function ble/complete/action:command/get-desc {
   1986   local title= value=
   1987   if [[ -d $CAND ]]; then
   1988     title=directory
   1989   else
   1990     local type; ble/util/type type "$CAND"
   1991     ble/syntax/highlight/cmdtype1 "$type" "$CAND"
   1992 
   1993     case $type in
   1994     ($_ble_attr_CMD_ALIAS)
   1995       local ret
   1996       ble/alias#expand "$CAND"
   1997       title=alias value=$ret ;;
   1998     ($_ble_attr_CMD_FILE)
   1999       local path; ble/util/assign path 'type -p -- "$CAND"'
   2000       [[ $path == ?*/"$CAND" ]] && path="from ${path%/"$CAND"}"
   2001       title=file value=$path ;;
   2002     ($_ble_attr_CMD_FUNCTION)
   2003 
   2004       local source lineno
   2005       ble/function#get-source-and-lineno "$CAND"
   2006 
   2007       local def; ble/function#getdef "$CAND"
   2008       ble/string#match "$def" '^[^()]*\(\)[[:space:]]*\{[[:space:]]+(.*[^[:space:]])[[:space:]]+\}[[:space:]]*$' &&
   2009         def=${BASH_REMATCH[1]} # 関数の中身を抽出する
   2010       local ret sgr0=$'\e[27m' sgr1=$'\e[7m' # Note: sgr-ansi で生成
   2011       lines=1 cols=${COLUMNS:-80} x=0 y=0 ble/canvas/trace-text "$def" external-sgr
   2012 
   2013       title=function value="${source##*/}:$lineno $desc_sgrq$ret" ;;
   2014     ($_ble_attr_CMD_JOBS)
   2015       ble/util/joblist.check
   2016       local job; ble/util/assign job 'jobs -- "$CAND" 2>/dev/null' || job='???'
   2017       title=job value=${job:-(ambiguous)} ;;
   2018     ($_ble_attr_ERR)
   2019       if [[ $CAND == */ ]]; then
   2020         title='function namespace'
   2021       else
   2022         title=${_ble_complete_action_command_desc[_ble_attr_ERR]}
   2023       fi ;;
   2024     (*)
   2025       title=${_ble_complete_action_command_desc[type]:-'???'} ;;
   2026     esac
   2027   fi
   2028   desc=${title:+$desc_sgrt($title)$desc_sgr0}${value:+ $value}
   2029 }
   2030 
   2031 # action:variable
   2032 #
   2033 #   DATA ... 変数名の文脈を指定します。
   2034 #     assignment braced word arithmetic の何れかです。
   2035 #
   2036 function ble/complete/action:variable/initialize { ble/complete/action/quote-insert; }
   2037 function ble/complete/action:variable/initialize.batch { ble/complete/action/quote-insert.batch newline; }
   2038 function ble/complete/action:variable/complete {
   2039   case $DATA in
   2040   (assignment)
   2041     # var= 等に於いて = を挿入
   2042     ble/complete/action/complete.addtail '=' ;;
   2043   (braced)
   2044     # ${var 等に於いて } を挿入
   2045     ble/complete/action/complete.addtail '}' ;;
   2046   (word)       ble/complete/action:word/complete ;;
   2047   (arithmetic|nosuffix) ;; # do nothing
   2048   esac
   2049 }
   2050 function ble/complete/action:variable/init-menu-item {
   2051   local ret; ble/color/face2g syntax_varname; g=$ret
   2052 }
   2053 function ble/complete/action:variable/get-desc {
   2054   local _ble_local_title=variable
   2055   if ble/is-array "$CAND"; then
   2056     _ble_local_title=array
   2057   elif ble/is-assoc "$CAND"; then
   2058     _ble_local_title=assoc
   2059   fi
   2060 
   2061   local _ble_local_value=
   2062   if [[ $_ble_local_title == array || $_ble_local_title == assoc ]]; then
   2063     builtin eval "local count=\${#$CAND[@]}"
   2064     if ((count==0)); then
   2065       count=empty
   2066     else
   2067       count="$count items"
   2068     fi
   2069     _ble_local_value=$'\e[94m['$count$']\e[m'
   2070   else
   2071     local ret; ble/string#quote-word "${!CAND}" ansi:sgrq="$desc_sgrq":quote-empty
   2072     _ble_local_value=$ret
   2073   fi
   2074   desc="$desc_sgrt($_ble_local_title)$desc_sgr0 $_ble_local_value"
   2075 }
   2076 
   2077 #------------------------------------------------------------------------------
   2078 # source
   2079 
   2080 ## @fn ble/complete/source/test-limit value
   2081 ##   Tests whether "value" exceeds the completion limit
   2082 ##   specified by bleopt complete_limit or complete_limit_auto.
   2083 ##
   2084 ##   @var[in] comp_type
   2085 ##   @var[in,out] cand_limit_reached
   2086 ##
   2087 function ble/complete/source/test-limit {
   2088   local value=$1 limit=
   2089   if [[ :$comp_type: == *:auto_menu:* && $bleopt_complete_limit_auto_menu ]]; then
   2090     limit=$bleopt_complete_limit_auto_menu
   2091   elif [[ :$comp_type: == *:auto:* && $bleopt_complete_limit_auto ]]; then
   2092     limit=$bleopt_complete_limit_auto
   2093   else
   2094     limit=$bleopt_complete_limit
   2095   fi
   2096 
   2097   if [[ $limit && value -gt limit ]]; then
   2098     cand_limit_reached=1
   2099 
   2100     # Note: #D1618 自動候補一覧表示で失敗した時は不完全なリストが生成
   2101     #   されるのを防ぐ為に補完全体をキャンセルする。
   2102     [[ :$comp_type: == *:auto_menu: ]] && cand_limit_reached=cancel
   2103     return 1
   2104   else
   2105     return 0
   2106   fi
   2107 }
   2108 
   2109 ## @fn ble/complete/source/eval-simple-word
   2110 ## @fn ble/complete/source/evaluate-path-spec
   2111 ##   補完用の中断設定・timeout設定(auto-complete時のみ)を指定して、
   2112 ##   それぞれ simple-word/{eval,evaluate-path-spec} を呼び出します。
   2113 ##
   2114 ## 注意: 現在の実装では、ユーザー入力による中断 148 及びtimeout による
   2115 ## 中断 142 の両方に対してこれらの関数は 148 を返す様に振る舞いを変更
   2116 ## している。呼び出し元で両方を区別なく取り扱うのに都合が良い為。
   2117 ##
   2118 function ble/complete/source/eval-simple-word {
   2119   local word=$1 opts=$2
   2120   if [[ :$comp_type: != *:sync:* && :$opts: != *:noglob:* ]]; then
   2121     opts=$opts:stopcheck:cached
   2122     [[ :$comp_type: == *:auto:* && $bleopt_complete_timeout_auto ]] &&
   2123       opts=$opts:timeout=$((bleopt_complete_timeout_auto))
   2124   fi
   2125   ble/syntax:bash/simple-word/eval "$word" "$opts"; local ext=$?
   2126   ((ext==142)) && return 148
   2127   return "$ext"
   2128 }
   2129 function ble/complete/source/evaluate-path-spec {
   2130   local word=$1 sep=$2 opts=$3
   2131   if [[ :$comp_type: != *:sync:* && :$opts: != *:noglob:* ]]; then
   2132     opts=$opts:stopcheck:cached:single
   2133     [[ :$comp_type: == *:auto:* && $bleopt_complete_timeout_auto ]] &&
   2134       opts=$opts:timeout=$((bleopt_complete_timeout_auto))
   2135   fi
   2136   ble/syntax:bash/simple-word/evaluate-path-spec "$word" "$sep" "$opts"; local ext=$?
   2137   ((ext==142)) && return 148
   2138   return "$ext"
   2139 }
   2140 
   2141 
   2142 ## @fn ble/complete/source/reduce-compv-for-ambiguous-match
   2143 ##   曖昧補完の為に擬似的な COMPV と COMPS を生成・設定します。
   2144 ##   @var[in,out] COMPS COMPV
   2145 function ble/complete/source/reduce-compv-for-ambiguous-match {
   2146   [[ :$comp_type: == *:[maA]:* ]] || return 0
   2147 
   2148   local comps=$COMPS compv=$COMPV
   2149   local comps_prefix= compv_prefix=
   2150   if [[ $comps_fixed ]]; then
   2151     comps_prefix=${comps::${comps_fixed%%:*}}
   2152     compv_prefix=${comps_fixed#*:}
   2153     compv=${COMPV:${#compv_prefix}}
   2154   fi
   2155 
   2156   case $comps_flags in
   2157   (*S*) comps_prefix=$comps_prefix\' ;;
   2158   (*E*) comps_prefix=$comps_prefix\$\' ;;
   2159   (*D*) comps_prefix=$comps_prefix\" ;;
   2160   (*I*) comps_prefix=$comps_prefix\$\" ;;
   2161   esac
   2162 
   2163   if [[ $compv && :$comp_type: == *:a:* ]]; then
   2164     compv=${compv::1}
   2165     ble/complete/string#escape-for-completion-context "$compv"
   2166     comps=$ret
   2167   else
   2168     compv= comps=
   2169   fi
   2170 
   2171   COMPV=$compv_prefix$compv
   2172   COMPS=$comps_prefix$comps
   2173 }
   2174 
   2175 
   2176 _ble_complete_yield_varnames=("${_ble_complete_quote_insert_varnames[@]}")
   2177 
   2178 ## @fn ble/complete/cand/yield.initialize action
   2179 function ble/complete/cand/yield.initialize {
   2180   ble/complete/action/quote-insert.initialize "$1"
   2181 }
   2182 
   2183 ## @fn ble/complete/cand/yield ACTION CAND DATA
   2184 ##   @param[in] ACTION
   2185 ##   @param[in] CAND
   2186 ##   @param[in] DATA
   2187 ##   @var[in] COMP_PREFIX
   2188 ##   @var[in] flag_force_fignore
   2189 ##   @var[in] flag_source_filter
   2190 function ble/complete/cand/yield {
   2191   local ACTION=$1 CAND=$2 DATA=$3
   2192   [[ $flag_force_fignore ]] && ! ble/complete/.fignore/filter "$CAND" && return 0
   2193   [[ $flag_source_filter ]] || ble/complete/candidates/filter#test "$CAND" || return 0
   2194 
   2195   local INSERT=$CAND
   2196   ble/complete/action:"$ACTION"/initialize || return "$?"
   2197 
   2198   local PREFIX_LEN=0
   2199   [[ $CAND == "$COMP_PREFIX"* ]] && PREFIX_LEN=${#COMP_PREFIX}
   2200 
   2201   local icand
   2202   ((icand=cand_count++))
   2203   cand_cand[icand]=$CAND
   2204   cand_word[icand]=$INSERT
   2205   cand_pack[icand]=$ACTION:${#CAND},${#INSERT},$PREFIX_LEN:$CAND$INSERT$DATA
   2206 }
   2207 
   2208 ## @fn ble/complete/cand/yield.batch action data
   2209 ##   @arr[in] cands
   2210 function ble/complete/cand/yield.batch {
   2211   local ACTION=$1 DATA=$2
   2212 
   2213   local inserts threshold=500
   2214   [[ $OSTYPE == cygwin* || $OSTYPE == msys* ]] && threshold=2000
   2215   if ((${#cands[@]}>=threshold)) && ble/function#try ble/complete/action:"$ACTION"/initialize.batch; then
   2216     local i n=${#cands[@]}
   2217     for ((i=0;i<n;i++)); do
   2218       ((cand_iloop++%bleopt_complete_polling_cycle==0)) && ble/complete/check-cancel && return 148
   2219       local CAND=${cands[i]} INSERT=${inserts[i]}
   2220 
   2221       [[ $flag_force_fignore ]] && ! ble/complete/.fignore/filter "$CAND" && continue
   2222       [[ $flag_source_filter ]] || ble/complete/candidates/filter#test "$CAND" || continue
   2223 
   2224       local PREFIX_LEN=0
   2225       [[ $CAND == "$COMP_PREFIX"* ]] && PREFIX_LEN=${#COMP_PREFIX}
   2226 
   2227       local icand
   2228       ((icand=cand_count++))
   2229       cand_cand[icand]=$CAND
   2230       cand_word[icand]=$INSERT
   2231       cand_pack[icand]=$ACTION:${#CAND},${#INSERT},$PREFIX_LEN:$CAND$INSERT$DATA
   2232     done
   2233   else
   2234     local cand
   2235     for cand in "${cands[@]}"; do
   2236       ((cand_iloop++%bleopt_complete_polling_cycle==0)) && ble/complete/check-cancel && return 148
   2237       ble/complete/cand/yield "$ACTION" "$cand" "$DATA"
   2238     done
   2239   fi
   2240 }
   2241 
   2242 function ble/complete/cand/yield-filenames {
   2243   local action=$1; shift
   2244 
   2245   local rex_hidden=
   2246   [[ :$comp_type: != *:match-hidden:* ]] &&
   2247     rex_hidden=${COMPV:+'.{'${#COMPV}'}'}'(^|/)\.[^/]*$'
   2248 
   2249   local -a cands=()
   2250   local cand icand=0
   2251   for cand; do
   2252     ((cand_iloop++%bleopt_complete_polling_cycle==0)) && ble/complete/check-cancel && return 148
   2253     [[ $rex_hidden && $cand =~ $rex_hidden ]] && continue
   2254     cands[icand++]=$cand
   2255   done
   2256 
   2257   [[ $FIGNORE ]] && local flag_force_fignore=1
   2258   local "${_ble_complete_yield_varnames[@]/%/=}" # WA #D1570 checked
   2259   ble/complete/cand/yield.initialize "$action"
   2260   ble/complete/cand/yield.batch "$action"
   2261 }
   2262 
   2263 _ble_complete_cand_varnames=(ACTION CAND INSERT DATA PREFIX_LEN)
   2264 
   2265 ## @fn ble/complete/cand/unpack data
   2266 ##   @param[in] data
   2267 ##     ACTION:ncand,ninsert,PREFIX_LEN:$CAND$INSERT$DATA
   2268 ##   @var[out] ACTION CAND INSERT DATA PREFIX_LEN
   2269 function ble/complete/cand/unpack {
   2270   local pack=$1
   2271   ACTION=${pack%%:*} pack=${pack#*:}
   2272   local text=${pack#*:}
   2273   IFS=, builtin eval 'pack=(${pack%%:*})'
   2274   CAND=${text::pack[0]}
   2275   INSERT=${text:pack[0]:pack[1]}
   2276   DATA=${text:pack[0]+pack[1]}
   2277   PREFIX_LEN=${pack[2]}
   2278 }
   2279 
   2280 ## 定義されている source
   2281 ##
   2282 ##   source:wordlist
   2283 ##   source:command
   2284 ##   source:file
   2285 ##   source:dir
   2286 ##   source:argument
   2287 ##   source:variable
   2288 ##
   2289 ## source の実装
   2290 ##
   2291 ## @fn ble/complete/source:$name args...
   2292 ##   @param[in] args...
   2293 ##     ble/syntax/completion-context/generate で設定されるユーザ定義の引数。
   2294 ##
   2295 ##   @var[in] COMP1 COMP2 COMPS COMPV comp_type
   2296 ##   @var[in] comp_filter_type
   2297 ##   @var[out] COMP_PREFIX
   2298 ##     ble/complete/cand/yield で参照される一時変数。
   2299 ##
   2300 ##   @var[in,out] cand_count cand_cand cand_word cand_pack
   2301 ##   @var[in,out] cand_limit_reached
   2302 
   2303 # source:none
   2304 function ble/complete/source:none { return 0; }
   2305 
   2306 # source:wordlist
   2307 #
   2308 #  -r 指定された単語をエスケープせずにそのまま挿入する
   2309 #  -W 補完完了時に空白を挿入しない
   2310 #  -s sabbrev 候補も一緒に生成する
   2311 #
   2312 function ble/complete/source:wordlist {
   2313   [[ $comps_flags == *v* ]] || return 1
   2314   local COMPS=$COMPS COMPV=$COMPV
   2315   ble/complete/source/reduce-compv-for-ambiguous-match
   2316   [[ $COMPV =~ ^.+/ ]] && COMP_PREFIX=${BASH_REMATCH[0]}
   2317 
   2318   # process options
   2319   local opt_raw= opt_noword= opt_sabbrev=
   2320   while (($#)) && [[ $1 == -* ]]; do
   2321     local arg=$1; shift
   2322     case $arg in
   2323     (--) break ;;
   2324     (--*) ;; # ignore
   2325     (-*)
   2326       local i iN=${#arg}
   2327       for ((i=1;i<iN;i++)); do
   2328         case ${arg:i:1} in
   2329         (r) opt_raw=1 ;;
   2330         (W) opt_noword=1 ;;
   2331         (s) opt_sabbrev=1 ;;
   2332         (*) ;; # ignore
   2333         esac
   2334       done ;;
   2335     esac
   2336   done
   2337 
   2338   [[ $opt_sabbrev ]] &&
   2339     ble/complete/source:sabbrev
   2340 
   2341   local action=word
   2342   [[ $opt_noword ]] && action=substr
   2343   [[ $opt_raw ]] && action=literal-$action
   2344 
   2345   local cand "${_ble_complete_yield_varnames[@]/%/=}" # WA #D1570 checked
   2346   ble/complete/cand/yield.initialize "$action"
   2347   for cand; do
   2348     [[ $cand == "$COMPV"* ]] && ble/complete/cand/yield "$action" "$cand"
   2349   done
   2350 }
   2351 
   2352 # source:command
   2353 
   2354 function ble/complete/source:command/.contract-by-slashes {
   2355   local slashes=${COMPV//[!'/']}
   2356   ble/bin/awk -F / -v baseNF="${#slashes}" '
   2357     function initialize_common() {
   2358       common_NF = NF;
   2359       for (i = 1; i <= NF; i++) common[i] = $i;
   2360       common_degeneracy = 1;
   2361       common0_NF = NF;
   2362       common0_str = $0;
   2363     }
   2364     function print_common(_, output) {
   2365       if (!common_NF) return;
   2366 
   2367       if (common_degeneracy == 1) {
   2368         print common0_str;
   2369         common_NF = 0;
   2370         return;
   2371       }
   2372 
   2373       output = common[1];
   2374       for (i = 2; i <= common_NF; i++)
   2375         output = output "/" common[i];
   2376 
   2377       # Note:
   2378       #   For candidates `a/b/c/1` and `a/b/c/2`, prints `a/b/c/`.
   2379       #   For candidates `a/b/c` and `a/b/c/1`, prints `a/b/c` and `a/b/c/1`.
   2380       if (common_NF == common0_NF) print output;
   2381       print output "/";
   2382 
   2383       common_NF = 0;
   2384     }
   2385 
   2386     {
   2387       if (NF <= baseNF + 1) {
   2388         print_common();
   2389         print $0;
   2390       } else if (!common_NF) {
   2391         initialize_common();
   2392       } else {
   2393         n = common_NF < NF ? common_NF : NF;
   2394         for (i = baseNF + 1; i <= n; i++)
   2395           if (common[i] != $i) break;
   2396         matched_length = i - 1;
   2397 
   2398         if (matched_length <= baseNF) {
   2399           print_common();
   2400           initialize_common();
   2401         } else {
   2402           common_NF = matched_length;
   2403           common_degeneracy++;
   2404         }
   2405       }
   2406     }
   2407 
   2408     END { print_common(); }
   2409   '
   2410 }
   2411 
   2412 ## @fn ble/complete/source:command/gen.1
   2413 function ble/complete/source:command/gen.1 {
   2414   # Note #D1922: パス名コマンドの曖昧補完は compgen -c ではなく自前で処理する。
   2415   # ディレクトリ名に関しては ble/complete/source:command/gen の側で生成されるの
   2416   # でここでは生成しない。
   2417   if [[ $COMPV == */* && :$comp_type: == *:[maA]:* ]]; then
   2418     local ret
   2419     ble/complete/source:file/.construct-pathname-pattern "$COMPV"
   2420     ble/complete/util/eval-pathname-expansion "$ret"; (($?==148)) && return 148
   2421     ble/complete/source/test-limit "${#ret[@]}" || return 1
   2422     ble/array#filter ret '[[ ! -d $1 && -x $1 ]]'
   2423     ((${#ret[@]})) && printf '%s\n' "${ret[@]}"
   2424 
   2425     local COMPS=$COMPS COMPV=$COMPV
   2426     ble/complete/source/reduce-compv-for-ambiguous-match
   2427 
   2428   else
   2429     local COMPS=$COMPS COMPV=$COMPV
   2430     ble/complete/source/reduce-compv-for-ambiguous-match
   2431 
   2432     # Note: cygwin では cyg,x86,i68 等で始まる場合にとても遅い。他の環境でも空
   2433     #   の補完を実行すると遅くなる可能性がある。
   2434     local slow_compgen=
   2435     if [[ ! $COMPV ]]; then
   2436       slow_compgen=1
   2437     elif [[ $OSTYPE == cygwin* ]]; then
   2438       case $COMPV in
   2439       (?|cy*|x8*|i6*)
   2440         slow_compgen=1 ;;
   2441       esac
   2442     fi
   2443 
   2444     # Note: 何故か compgen -A command はクォート除去が実行されない。compgen -A
   2445     #   function はクォート除去が実行される。従って、compgen -A command には直
   2446     #   接 COMPV を渡し、compgen -A function には compv_quoted を渡す。
   2447     if [[ $slow_compgen ]]; then
   2448       shopt -q no_empty_cmd_completion && return 0
   2449       ble/util/conditional-sync \
   2450         'builtin compgen -c -- "$COMPV"' \
   2451         '! ble/complete/check-cancel' 128 progressive-weight
   2452     else
   2453       builtin compgen -c -- "$COMPV"
   2454     fi
   2455   fi
   2456 
   2457   if [[ $COMPV == */* ]]; then
   2458     local q="'" Q="'\''"
   2459     local compv_quoted="'${COMPV//$q/$Q}'"
   2460     builtin compgen -A function -- "$compv_quoted"
   2461   fi
   2462 }
   2463 
   2464 function ble/complete/source:command/gen {
   2465   if [[ :$comp_type: != *:[maA]:* && $bleopt_complete_contract_function_names ]]; then
   2466     ble/complete/source:command/gen.1 |
   2467       ble/complete/source:command/.contract-by-slashes
   2468   else
   2469     ble/complete/source:command/gen.1
   2470   fi
   2471 
   2472   # ディレクトリ名列挙 (/ 付きで生成する)
   2473   #
   2474   #   Note: shopt -q autocd &>/dev/null かどうかに拘らず列挙する。
   2475   #
   2476   #   Note: compgen -A directory (以下のコード参照) はバグがあって、
   2477   #     bash-4.3 以降でクォート除去が実行されないので使わない (#D0714 #M0009)
   2478   #
   2479   #     [[ :$comp_type: == *:a:* ]] && local COMPS=${COMPS::1} COMPV=${COMPV::1}
   2480   #     compgen -A directory -S / -- "$compv_quoted"
   2481   #
   2482   if [[ $arg != *D* ]]; then
   2483     local ret
   2484     ble/complete/source:file/.construct-pathname-pattern "$COMPV"
   2485     ble/complete/util/eval-pathname-expansion "$ret/"; (($?==148)) && return 148
   2486     ble/complete/source/test-limit "${#ret[@]}" || return 1
   2487     ((${#ret[@]})) && printf '%s\n' "${ret[@]}"
   2488   fi
   2489 
   2490   # ジョブ名列挙
   2491   if [[ ! $COMPV || $COMPV == %* ]]; then
   2492     # %コマンド名
   2493     local q="'" Q="'\''"
   2494     local compv_quoted=${COMPV#'%'}
   2495     compv_quoted="'${compv_quoted//$q/$Q}'"
   2496     builtin compgen -j -P % -- "$compv_quoted"
   2497 
   2498     # %ジョブ番号
   2499     local i joblist; ble/util/joblist
   2500     local job_count=${#joblist[@]}
   2501     for i in "${!joblist[@]}"; do
   2502       if local rex='^\[([0-9]+)\]'; [[ ${joblist[i]} =~ $rex ]]; then
   2503         joblist[i]=%${BASH_REMATCH[1]}
   2504       else
   2505         builtin unset -v 'joblist[i]'
   2506       fi
   2507     done
   2508     joblist=("${joblist[@]}")
   2509 
   2510     # %% %+ %-
   2511     if ((job_count>0)); then
   2512       ble/array#push joblist %% %+
   2513       ((job_count>=2)) &&
   2514         ble/array#push joblist %-
   2515     fi
   2516 
   2517     builtin compgen -W '"${joblist[@]}"' -- "$compv_quoted"
   2518   fi
   2519 }
   2520 ## ble/complete/source:command arg
   2521 ##   @param[in] arg
   2522 ##     arg に D が含まれている時、
   2523 ##     ディレクトリ名の列挙を抑制する事を表します。
   2524 function ble/complete/source:command {
   2525   [[ $comps_flags == *v* ]] || return 1
   2526   [[ ! $COMPV ]] && shopt -q no_empty_cmd_completion && return 1
   2527   [[ $COMPV =~ ^.+/ ]] && COMP_PREFIX=${BASH_REMATCH[0]}
   2528   local arg=$1
   2529 
   2530   # Try progcomp by "complete -p -I" / "complete -p _InitialWorD_"
   2531   {
   2532     local old_cand_count=$cand_count
   2533 
   2534     local comp_opts=:
   2535     ble/complete/source:argument/.generate-user-defined-completion initial; local ext=$?
   2536     ((ext==148)) && return "$ext"
   2537     if ((ext==0)); then
   2538       ((cand_count>old_cand_count)) && return "$ext"
   2539     fi
   2540   }
   2541 
   2542   ble/complete/source:sabbrev
   2543 
   2544   local arr
   2545   local compgen
   2546   ble/util/assign compgen 'ble/complete/source:command/gen "$arg"'
   2547   [[ $compgen ]] || return 1
   2548   ble/util/assign-array arr 'ble/bin/sort -u <<< "$compgen"' # 1 fork/exec
   2549 
   2550   ble/complete/source/test-limit "${#arr[@]}" || return 1
   2551 
   2552   local action=command "${_ble_complete_yield_varnames[@]/%/=}" # WA #D1570 checked
   2553   ble/complete/cand/yield.initialize "$action"
   2554 
   2555   # 無効 keyword, alias 判定用
   2556   local is_quoted=
   2557   [[ $COMPS != "$COMPV" ]] && is_quoted=1
   2558   local rex_keyword='^(if|then|else|elif|fi|case|esac|for|select|while|until|do|done|function|time|[!{}]|\[\[|coproc|\]\]|in)$'
   2559   local expand_aliases=
   2560   shopt -q expand_aliases && expand_aliases=1
   2561 
   2562   local cand icand=0 cands
   2563   for cand in "${arr[@]}"; do
   2564     ((cand_iloop++%bleopt_complete_polling_cycle==0)) && ble/complete/check-cancel && return 148
   2565 
   2566     # workaround: 何故か compgen -c -- "$compv_quoted" で
   2567     #   厳密一致のディレクトリ名が混入するので削除する。
   2568     [[ $cand != */ && -d $cand ]] && ! type "$cand" &>/dev/null && continue
   2569 
   2570     if [[ $is_quoted ]]; then
   2571       local disable_count=
   2572       # #D1691 keyword は quote されている場合には無効
   2573       [[ $cand =~ $rex_keyword ]] && ((disable_count++))
   2574       # #D1715 alias も quote されている場合には無効
   2575       [[ $expand_aliases ]] && ble/is-alias "$cand" && ((disable_count++))
   2576       if [[ $disable_count ]]; then
   2577         local type; ble/util/type type "$cand"
   2578         ((${#type[@]}>disable_count)) || continue
   2579       fi
   2580     else
   2581       # 'in' と ']]' は alias でない限り常にエラー
   2582       [[ $cand == ']]' || $cand == in ]] &&
   2583         ! { [[ $expand_aliases ]] && ble/is-alias "$cand"; } &&
   2584         continue
   2585 
   2586       if [[ ! $expand_aliases ]]; then
   2587         # #D1715 expand_aliases が無効でも compgen -c は alias を列挙してしまうので、
   2588         # ここで alias は除外 (type は expand_aliases をちゃんと考慮してくれる)。
   2589         ble/is-alias "$cand" && ! type "$cand" &>/dev/null && continue
   2590       fi
   2591 
   2592       # alias は quote されては困るので、quote される可能性のある文字を含んでい
   2593       # る場合は個別に :noquote: 指定で yield する [ Note: alias 内で許される特
   2594       # 殊文字は !#%-~^[]{}+*:@,.?_ である。更にその中で escape/quote の対象と
   2595       # なり得る文字は、[*?]{,}!^~#: だけである。_.@+%- は quote されない ]。
   2596       if ble/string#match "$cand" '[][*?{,}!^~#]' && ble/is-alias "$cand"; then
   2597         ble/complete/cand/yield "$action" "$cand" :noquote:
   2598         continue
   2599       fi
   2600     fi
   2601 
   2602     cands[icand++]=$cand
   2603   done
   2604   ble/complete/cand/yield.batch "$action"
   2605 }
   2606 
   2607 # source:file, source:dir
   2608 
   2609 function ble/complete/util/eval-pathname-expansion/.print-def {
   2610   local pattern=$1 ret
   2611   IFS= builtin eval "ret=($pattern)" 2>/dev/null
   2612   ble/string#quote-words "${ret[@]}"
   2613   ble/util/print "ret=($ret)"
   2614 }
   2615 
   2616 ## @fn ble/complete/util/eval-pathname-expansion pattern
   2617 ##   @var[out] ret
   2618 function ble/complete/util/eval-pathname-expansion {
   2619   local pattern=$1
   2620 
   2621   local -a dtor=()
   2622 
   2623   if [[ -o noglob ]]; then
   2624     set +f
   2625     ble/array#push dtor 'set -f'
   2626   fi
   2627 
   2628   if ! shopt -q nullglob; then
   2629     shopt -s nullglob
   2630     ble/array#push dtor 'shopt -u nullglob'
   2631   fi
   2632 
   2633   if ! shopt -q dotglob; then
   2634     shopt -s dotglob
   2635     ble/array#push dtor 'shopt -u dotglob'
   2636   else
   2637     # GLOBIGNORE に触ると設定が変わるので
   2638     # dotglob は明示的に保存・復元する。
   2639     ble/array#push dtor 'shopt -s dotglob'
   2640   fi
   2641 
   2642   if ! shopt -q extglob; then
   2643     shopt -s extglob
   2644     ble/array#push dtor 'shopt -u extglob'
   2645   fi
   2646 
   2647   if [[ :$comp_type: == *:i:* ]]; then
   2648     if ! shopt -q nocaseglob; then
   2649       shopt -s nocaseglob
   2650       ble/array#push dtor 'shopt -u nocaseglob'
   2651     fi
   2652   else
   2653     if shopt -q nocaseglob; then
   2654       shopt -u nocaseglob
   2655       ble/array#push dtor 'shopt -s nocaseglob'
   2656     fi
   2657   fi
   2658 
   2659   if ble/util/is-cygwin-slow-glob "$pattern"; then # Note: #D1168
   2660     if shopt -q failglob &>/dev/null || shopt -q nullglob &>/dev/null; then
   2661       pattern=
   2662     else
   2663       set -f
   2664       ble/array#push dtor 'set +f'
   2665     fi
   2666   fi
   2667 
   2668   if [[ $GLOBIGNORE ]]; then
   2669     local GLOBIGNORE_save=$GLOBIGNORE
   2670     GLOBIGNORE=
   2671     ble/array#push dtor 'GLOBIGNORE=$GLOBIGNORE_save'
   2672   fi
   2673 
   2674   ble/array#reverse dtor
   2675 
   2676   ret=()
   2677   if [[ :$comp_type: == *:sync:* ]]; then
   2678     IFS= builtin eval "ret=($pattern)" 2>/dev/null
   2679   else
   2680     local sync_command='ble/complete/util/eval-pathname-expansion/.print-def "$pattern"'
   2681     local sync_opts=progressive-weight
   2682     [[ :$comp_type: == *:auto:* && $bleopt_complete_timeout_auto ]] &&
   2683       sync_opts=$sync_opts:timeout=$((bleopt_complete_timeout_auto))
   2684 
   2685     local def
   2686     ble/util/assign def 'ble/util/conditional-sync "$sync_command" "" "" "$sync_opts"' &>/dev/null; local ext=$?
   2687     if ((ext==148)) || ble/complete/check-cancel; then
   2688       ble/util/invoke-hook dtor
   2689       return 148
   2690     fi
   2691     builtin eval -- "$def"
   2692   fi 2>&"$_ble_util_fd_stderr"
   2693 
   2694   ble/util/invoke-hook dtor
   2695   return 0
   2696 }
   2697 
   2698 ## @fn ble/complete/source:file/.construct-ambiguous-pathname-pattern path
   2699 ##   指定された path に対応する曖昧一致パターンを生成します。
   2700 ##   例えば alpha/beta/gamma に対して a*/b*/g* でファイル名を生成します。
   2701 ##   但し "../" や "./" については (".*.*/" や ".*/" 等に変換せず) そのままにします。
   2702 ##
   2703 ##   @param[in] path
   2704 ##   @var[out] ret
   2705 ##
   2706 ##   @remarks
   2707 ##     当初は a*/b*/g* で生成して、後のフィルタに一致しないものの除外を一任していたが遅い。
   2708 ##     従って、a*l*p*h*a*/b*e*t*a*/g*a*m*m*a* の様なパターンを生成する様に変更した。
   2709 ##
   2710 function ble/complete/source:file/.construct-ambiguous-pathname-pattern {
   2711   local path=$1 fixlen=${2:-1}
   2712   local pattern= i=0 j
   2713   local names; ble/string#split names / "$1"
   2714   local name
   2715   for name in "${names[@]}"; do
   2716     ((i++)) && pattern=$pattern/
   2717     if [[ $name == .. || $name == . && i -lt ${#names[@]} ]]; then
   2718       pattern=$pattern$name
   2719     elif [[ $name ]]; then
   2720       ble/string#quote-word "${name::fixlen}"
   2721       pattern=$pattern$ret*
   2722       for ((j=fixlen;j<${#name};j++)); do
   2723         ble/string#quote-word "${name:j:1}"
   2724         if [[ $_ble_bash -lt 50000 && $pattern == *\* ]]; then
   2725           # * を extglob *([!ch]) に変換 #D1389
   2726           pattern=$pattern'([!'$ret'])'
   2727         fi
   2728         pattern=$pattern$ret*
   2729       done
   2730     fi
   2731   done
   2732   [[ $pattern == *'*' ]] || pattern=$pattern*
   2733   ret=$pattern
   2734 }
   2735 ## @fn ble/complete/source:file/.construct-pathname-pattern path
   2736 ##   @param[in] path
   2737 ##   @var[out] ret
   2738 function ble/complete/source:file/.construct-pathname-pattern {
   2739   local path=$1 pattern
   2740   case :$comp_type: in
   2741   (*:a:*) ble/complete/source:file/.construct-ambiguous-pathname-pattern "$path"; pattern=$ret ;;
   2742   (*:A:*) ble/complete/source:file/.construct-ambiguous-pathname-pattern "$path" 0; pattern=$ret ;;
   2743   (*:m:*) ble/string#quote-word "$path"; pattern=*$ret* ;;
   2744   (*) ble/string#quote-word "$path"; pattern=$ret*
   2745   esac
   2746   ret=$pattern
   2747 }
   2748 
   2749 function ble/complete/source:file/.impl {
   2750   local opts=$1
   2751   [[ $comps_flags == *v* ]] || return 1
   2752   [[ :$comp_type: != *:[maA]:* && $COMPV =~ ^.+/ ]] && COMP_PREFIX=${BASH_REMATCH[0]}
   2753   # 入力文字列が空の場合は曖昧補完は本質的に通常の補完と同じなのでスキップ
   2754   [[ :$comp_type: == *:[maA]:* && ! $COMPV ]] && return 1
   2755 
   2756   #   Note: compgen -A file/directory (以下のコード参照) はバグがあって、
   2757   #     bash-4.0 と 4.1 でクォート除去が実行されないので使わない (#D0714 #M0009)
   2758   #
   2759   #     local q="'" Q="'\''"; local compv_quoted="'${COMPV//$q/$Q}'"
   2760   #     local candidates; ble/util/assign-array candidates 'builtin compgen -A file -- "$compv_quoted"'
   2761 
   2762   ble/complete/source:tilde; local ext=$?
   2763   ((ext==148||ext==0)) && return "$ext"
   2764 
   2765   local -a candidates=()
   2766   local ret
   2767   ble/complete/source:file/.construct-pathname-pattern "$COMPV"
   2768   [[ :$opts: == *:directory:* ]] && ret=$ret/
   2769   ble/complete/util/eval-pathname-expansion "$ret"; (($?==148)) && return 148
   2770   ble/complete/source/test-limit "${#ret[@]}" || return 1
   2771 
   2772   if [[ :$opts: == *:directory:* ]]; then
   2773     candidates=("${ret[@]%/}")
   2774   else
   2775     candidates=("${ret[@]}")
   2776   fi
   2777   [[ :$opts: == *:no-fd:* ]] &&
   2778     ble/array#remove-by-regex candidates '^[0-9]+-?$|^-$'
   2779 
   2780   local flag_source_filter=1
   2781   ble/complete/cand/yield-filenames file "${candidates[@]}"
   2782 }
   2783 
   2784 function ble/complete/source:file {
   2785   ble/complete/source:file/.impl "$1"
   2786 }
   2787 function ble/complete/source:dir {
   2788   ble/complete/source:file/.impl "directory:$1"
   2789 }
   2790 
   2791 # source:rhs
   2792 
   2793 function ble/complete/source:rhs { ble/complete/source:file; }
   2794 
   2795 #------------------------------------------------------------------------------
   2796 # source:tilde
   2797 
   2798 function ble/complete/action:tilde/initialize {
   2799   # チルダは quote しない
   2800   CAND=${CAND#\~} ble/complete/action/quote-insert
   2801   INSERT=\~$INSERT
   2802 
   2803   # Note: Windows 等でチルダ展開の無効なユーザー名があるのでチェック
   2804   local rex='^~[^/'\''"$`\!:]*$'; [[ $INSERT =~ $rex ]]
   2805 }
   2806 function ble/complete/action:tilde/complete {
   2807   ble/complete/action/complete.mark-directory
   2808 }
   2809 function ble/complete/action:tilde/init-menu-item {
   2810   local ret
   2811   ble/color/face2g filename_directory; g=$ret
   2812 }
   2813 function ble/complete/action:tilde/get-desc {
   2814   if [[ $CAND == '~+' ]]; then
   2815     desc='current directory (tilde expansion)'
   2816   elif [[ $CAND == '~-' ]]; then
   2817     desc='previous directory (tilde expansion)'
   2818   elif local rex='^~[0-9]$'; [[ $CAND =~ $rex ]]; then
   2819     desc='DIRSTACK directory (tilde expansion)'
   2820   else
   2821     desc='user directory (tilde expansion)'
   2822   fi
   2823 }
   2824 
   2825 function ble/complete/source:tilde/.generate {
   2826   # generate user directories
   2827   local pattern=${COMPS#\~}
   2828   [[ :$comp_type: == *:[maA]:* ]] && pattern=
   2829   builtin compgen -P \~ -u -- "$pattern"
   2830 
   2831   # generate special tilde expansions
   2832   printf '%s\n' '~' '~+' '~-'
   2833   local dirstack_max=$((${#DIRSTACK[@]}-1))
   2834   ((dirstack_max>=0)) &&
   2835     builtin eval "printf '%s\n' '~'{0..$dirstack_max}"
   2836 
   2837 }
   2838 
   2839 # tilde expansion
   2840 function ble/complete/source:tilde {
   2841   local rex='^~[^/'\''"$`\!:]*$'; [[ $COMPS =~ $rex ]] || return 1
   2842 
   2843   # Generate candidates
   2844   #   Note: Windows で同じユーザ名が compgen によって
   2845   #   複数回列挙されるので sort -u を実行する。
   2846   local compgen candidates
   2847   ble/util/assign compgen ble/complete/source:tilde/.generate
   2848   [[ $compgen ]] || return 1
   2849   ble/util/assign-array candidates 'ble/bin/sort -u <<< "$compgen"'
   2850 
   2851   # COMPS を用いて自前でフィルタ
   2852   local flag_source_filter=1
   2853   if [[ $COMPS == '~'?* ]]; then
   2854     local filter_type=$comp_filter_type
   2855     [[ $filter_type == none ]] && filter_type=head
   2856     local comp_filter_type
   2857     local comp_filter_pattern
   2858     ble/complete/candidates/filter#init "$filter_type" "$COMPS"
   2859     ble/array#filter candidates ble/complete/candidates/filter#test
   2860   fi
   2861 
   2862   ((${#candidates[@]})) || return 1
   2863 
   2864   local old_cand_count=$cand_count
   2865   ble/complete/cand/yield-filenames tilde "${candidates[@]}"; local ext=$?
   2866   return "$((ext?ext:cand_count>old_cand_count))"
   2867 }
   2868 
   2869 function ble/complete/source:fd {
   2870   IFS=: builtin eval 'local fdlist=":${_ble_util_openat_fdlist[*]}:"'
   2871 
   2872   [[ $comp_filter_type == none ]] &&
   2873     local comp_filter_type=head
   2874 
   2875   local old_cand_count=$cand_count
   2876   local action=word "${_ble_complete_yield_varnames[@]/%/=}" # WA #D1570 checked
   2877   ble/complete/cand/yield.initialize "$action"
   2878   ble/complete/cand/yield "$action" -
   2879   if [[ -d /proc/self/fd ]]; then
   2880     local ret
   2881     ble/complete/util/eval-pathname-expansion '/proc/self/fd/*'
   2882 
   2883     local fd
   2884     for fd in "${ret[@]}"; do
   2885       fd=${fd#/proc/self/fd/}
   2886       [[ ${fd//[0-9]} ]] && continue
   2887       [[ $fdlist == *:"$fd":* ]] && continue
   2888       ble/complete/cand/yield "$action" "$fd"
   2889       ble/complete/cand/yield "$action" "$fd-"
   2890     done
   2891   else
   2892     local fd
   2893     for ((fd=0;fd<10;fd++)); do
   2894       ble/fd#is-open "$fd" || continue
   2895       ble/complete/cand/yield "$action" "$fd"
   2896       ble/complete/cand/yield "$action" "$fd-"
   2897     done
   2898   fi
   2899 
   2900   return "$((cand_count>old_cand_count))"
   2901 }
   2902 
   2903 #------------------------------------------------------------------------------
   2904 # progcomp
   2905 
   2906 # progcomp/.compgen
   2907 
   2908 ## @fn ble/complete/progcomp/.compvar-initialize-wordbreaks
   2909 ##   @var[out] wordbreaks
   2910 function ble/complete/progcomp/.compvar-initialize-wordbreaks {
   2911   local ifs=$_ble_term_IFS q=\'\" delim=';&|<>()' glob='[*?' hist='!^{' esc='`$\'
   2912   local escaped=$ifs$q$delim$glob$hist$esc
   2913   wordbreaks=${COMP_WORDBREAKS//[$escaped]} # =:
   2914 }
   2915 ## @fn ble/complete/progcomp/.compvar-perform-wordbreaks word
   2916 ##   @var[in] wordbreaks
   2917 ##   @arr[out] ret
   2918 function ble/complete/progcomp/.compvar-perform-wordbreaks {
   2919   local word=$1
   2920   if [[ ! $word ]]; then
   2921     ret=('')
   2922     return 0
   2923   fi
   2924 
   2925   ret=()
   2926   while local head=${word%%["$wordbreaks"]*}; [[ $head != $word ]]; do
   2927     # Note: #D1094 bash の動作に倣って wordbreaks の連続は一つにまとめる。
   2928     ble/array#push ret "$head"
   2929     word=${word:${#head}}
   2930     head=${word%%[!"$wordbreaks"]*}
   2931     ble/array#push ret "$head"
   2932     word=${word:${#head}}
   2933   done
   2934 
   2935   # Note: #D1094 $word が空の時でも ret に push する。
   2936   #   $word が空の時は wordbreaks で終わっている事を意味するが、
   2937   #   その場合には wordbreaks の次に新しい単語を開始していると考える。
   2938   ble/array#push ret "$word"
   2939 }
   2940 function ble/complete/progcomp/.compvar-eval-word {
   2941   local opts=$2:single
   2942   if [[ :$opts: == *:noglob:* ]]; then
   2943     ble/syntax:bash/simple-word/eval "$1" "$opts"
   2944   else
   2945     [[ $bleopt_complete_timeout_compvar ]] &&
   2946       opts=timeout=$((bleopt_complete_timeout_compvar)):retry-noglob-on-timeout:$opts
   2947     ble/complete/source/eval-simple-word "$1" "$opts"
   2948   fi
   2949 }
   2950 
   2951 ## @fn ble/complete/progcomp/.compvar-generate-subwords/impl1 word
   2952 ##   $wordbreaks で分割してから評価する戦略。
   2953 ##
   2954 ##   @param word
   2955 ##   @arr[out] words
   2956 ##   @var[in,out] point
   2957 ##   @var[in] wordbreaks
   2958 ##   @exit
   2959 ##     単純単語として処理できなかった場合に失敗します。
   2960 ##     それ以外の場合は 0 を返します。
   2961 function ble/complete/progcomp/.compvar-generate-subwords/impl1 {
   2962   local word=$1 ret simple_flags simple_ibrace
   2963   if [[ $point ]]; then
   2964     # point で単語を前半と後半に分割
   2965     local left=${word::point} right=${word:point}
   2966   else
   2967     local left=$word right=
   2968     local point= # hide
   2969   fi
   2970 
   2971   ble/syntax:bash/simple-word/reconstruct-incomplete-word "$left" || return 1
   2972   left=$ret
   2973   if [[ $right ]]; then
   2974     case $simple_flags in
   2975     (*I*) right=\$\"$right ;;
   2976     (*D*) right=\"$right ;;
   2977     (*E*) right=\$\'$right ;;
   2978     (*S*) right=\'$right ;;
   2979     (*B*) right=\\$right ;;
   2980     esac
   2981     ble/syntax:bash/simple-word/reconstruct-incomplete-word "$right" || return 1
   2982     right=$ret
   2983   fi
   2984 
   2985   point=0 words=()
   2986 
   2987   # 単語毎に評価 (前半)
   2988   local eval_opts=noglob
   2989   ((${#ret[@]}==1)) && eval_opts=
   2990   ble/syntax:bash/simple-word#break-word "$left" "$wordbreaks"
   2991   local subword
   2992   for subword in "${ret[@]}"; do
   2993     ble/complete/progcomp/.compvar-eval-word "$subword" "$eval_opts"
   2994     ble/array#push words "$ret"
   2995     ((point+=${#ret}))
   2996   done
   2997 
   2998   # 単語毎に評価 (後半)
   2999   if [[ $right ]]; then
   3000     ble/syntax:bash/simple-word#break-word "$right" "$wordbreaks"
   3001     local subword isfirst=1
   3002     for subword in "${ret[@]}"; do
   3003       ble/complete/progcomp/.compvar-eval-word "$subword" noglob
   3004       if [[ $isfirst ]]; then
   3005         isfirst=
   3006         local iword=${#words[@]}; ((iword&&iword--))
   3007         words[iword]=${words[iword]}$ret
   3008       else
   3009         ble/array#push words "$ret"
   3010       fi
   3011     done
   3012   fi
   3013   return 0
   3014 }
   3015 ## @fn ble/complete/progcomp/.compvar-generate-subwords/impl2 word
   3016 ##   評価してから $wordbreaks で分割する戦略。
   3017 ##
   3018 ##   @param word
   3019 ##   @arr[out] words
   3020 ##   @var[in,out] point
   3021 ##   @var[in] wordbreaks
   3022 ##   @exit
   3023 ##     単純単語として処理できなかった場合に失敗します。
   3024 ##     それ以外の場合は 0 を返します。
   3025 function ble/complete/progcomp/.compvar-generate-subwords/impl2 {
   3026   local word=$1
   3027   ble/syntax:bash/simple-word/reconstruct-incomplete-word "$word" || return 1
   3028 
   3029   ble/complete/progcomp/.compvar-eval-word "$ret"; (($?==148)) && return 148; local value1=$ret
   3030   if [[ $point ]]; then
   3031     if ((point==${#word})); then
   3032       point=${#value1}
   3033     elif ble/syntax:bash/simple-word/reconstruct-incomplete-word "${word::point}"; then
   3034       ble/complete/progcomp/.compvar-eval-word "$ret"; (($?==148)) && return 148
   3035       point=${#ret}
   3036     fi
   3037   fi
   3038 
   3039   ble/complete/progcomp/.compvar-perform-wordbreaks "$value1"; words=("${ret[@]}")
   3040   return 0
   3041 }
   3042 ## @fn ble/complete/progcomp/.compvar-generate-subwords word1
   3043 ##   word1 を COMP_WORDBREAKS で分割します。
   3044 ##
   3045 ##   @arr[out] words
   3046 ##     分割して得られた単語片を格納します。
   3047 ##
   3048 ##   @var[in,out] subword_flags
   3049 ##     E が含まれる時、単語の展開・分割が実施された事を示す。
   3050 ##       全体が単純単語になっている時、先に eval して COMP_WORDBREAKS で分割する。
   3051 ##       そうでない時に先に COMP_WORDBREAKS で分割して、
   3052 ##       各単語片に対して単純単語 eval を試みる。
   3053 ##
   3054 ##     Q が含まれている時、後続の処理における展開・クォートを抑制し、
   3055 ##       補完関数に対してそのままの形で単語を渡す事を示す。
   3056 ##       これはチルダ ~ に対してユーザ名を補完させるのに使う。
   3057 ##
   3058 ##   @var[in,out] point
   3059 ##   @var[in] wordbreaks
   3060 ##
   3061 ## Note: 全体が単純単語になっている時には先に eval して COMP_WORDBREAKS で分割する。
   3062 ##   この時 subword_flags=E を設定する。
   3063 ##
   3064 function ble/complete/progcomp/.compvar-generate-subwords {
   3065   local word1=$1 ret simple_flags simple_ibrace
   3066   if [[ ! $word1 ]]; then
   3067     # Note: 空文字列に対して正しい単語とする為に '' とすると git の補完関数が動かなくなる。
   3068     #   仕方がないので空文字列のままで登録する事にする。
   3069     subword_flags=E
   3070     words=('')
   3071   elif [[ $word1 == '~' ]]; then
   3072     # #D1362: ~ は展開するとユーザ名を補完できなくので特別にそのまま渡す。
   3073     subword_flags=Q
   3074     words=('~')
   3075   elif ble/complete/progcomp/.compvar-generate-subwords/impl1 "$word1"; then
   3076     # 初めに、先に分割してから評価する戦略を試す。
   3077     subword_flags=E
   3078   elif ble/complete/progcomp/.compvar-generate-subwords/impl2 "$word1"; then
   3079     # 次に、評価してから分割する戦略を試す。
   3080     subword_flags=E
   3081   else
   3082     ble/complete/progcomp/.compvar-perform-wordbreaks "$word1"; words=("${ret[@]}")
   3083   fi
   3084 }
   3085 ## @fn ble/complete/progcomp/.compvar-quote-subword word
   3086 ##   @var[in] index subword_flags
   3087 ##   @var[out] ret
   3088 ##   @var[in,out] p
   3089 function ble/complete/progcomp/.compvar-quote-subword {
   3090   local word=$1 to_quote= is_evaluated= is_quoted=
   3091   if [[ $subword_flags == *[EQ]* ]]; then
   3092     [[ $subword_flags == *E* ]] && to_quote=1
   3093   elif ble/syntax:bash/simple-word/reconstruct-incomplete-word "$word"; then
   3094     is_evaluated=1
   3095     ble/complete/progcomp/.compvar-eval-word "$ret"; (($?==148)) && return 148; word=$ret
   3096     to_quote=1
   3097   fi
   3098 
   3099   # コマンド名以外は再クォート
   3100   if [[ $to_quote ]]; then
   3101     local shell_specialchars=']\ ["'\''`$|&;<>()*?{}!^'$'\n\t' q="'" Q="'\''" qq="''"
   3102     if ((index>0)) && [[ $word == *["$shell_specialchars"]* || $word == [#~]* ]]; then
   3103       is_quoted=1
   3104       word="'${w//$q/$Q}'" word=${word#"$qq"} word=${word%"$qq"}
   3105     fi
   3106   fi
   3107 
   3108   # 単語片が補正されている時、p も補正する
   3109   if [[ $p && $word != "$1" ]]; then
   3110     if ((p==${#1})); then
   3111       p=${#word}
   3112     else
   3113       local left=${word::p}
   3114       if [[ $is_evaluated ]]; then
   3115         if ble/syntax:bash/simple-word/reconstruct-incomplete-word "$left"; then
   3116           ble/complete/progcomp/.compvar-eval-word "$ret"; (($?==148)) && return 148; left=$ret
   3117         fi
   3118       fi
   3119       if [[ $is_quoted ]]; then
   3120         left="'${left//$q/$Q}" left=${left#"$qq"}
   3121       fi
   3122       p=${#left}
   3123     fi
   3124   fi
   3125 
   3126   ret=$word
   3127 }
   3128 
   3129 ## @fn ble/complete/progcomp/.compvar-reduce-cur current_subword
   3130 ##   @param[in] current_subword
   3131 ##   @var[out] cur
   3132 builtin unset -v _ble_complete_progcomp_cur_wordbreaks
   3133 _ble_complete_progcomp_cur_rex_simple=
   3134 _ble_complete_progcomp_cur_rex_break=
   3135 function ble/complete/progcomp/.compvar-reduce-cur {
   3136   # 正規表現の更新
   3137   if [[ ! ${_ble_complete_progcomp_cur_wordbreaks+set} || $COMP_WORDBREAKS != "$_ble_complete_progcomp_cur_wordbreaks" ]]; then
   3138     _ble_complete_progcomp_cur_wordbreaks=$COMP_WORDBREAKS
   3139     _ble_complete_progcomp_cur_rex_simple='^([^\"'\'']|\\.|"([^\"]|\\.)*"|'\''[^'\'']*'\'')*'
   3140     local chars=${COMP_WORDBREAKS//[\'\"]/} rex_break=
   3141     [[ $chars == *\\* ]] && chars=${chars//\\/} rex_break='\\(.|$)'
   3142     [[ $chars == *\$* ]] && chars=${chars//\$/} rex_break+=${rex_break:+'|'}'\$([^$'\'${rex_break:+\\}']|$)'
   3143     if [[ $chars == '^' ]]; then
   3144       rex_break+=${rex_break:+'|'}'\^'
   3145     elif [[ $chars ]]; then
   3146       [[ $chars == ?*']'* ]] && chars=']'${chars//']'/}
   3147       [[ $chars == '^'* ]] && chars=${chars:1}${chars::1}
   3148       [[ $chars == *'-'*? ]] && chars=${chars//'-'/}'-'
   3149       rex_break+=${rex_break:+'|'}[$chars]
   3150     fi
   3151     _ble_complete_progcomp_cur_rex_break='^([^\"'\''$]|\$*\\.|\$*"([^\"]|\\.)*"|'\''[^'\'']*'\''|\$+'\''([^'\''\]|\\.)*'\''|\$+([^'\'']|$))*\$*('${rex_break:-'^$'}')'
   3152   fi
   3153 
   3154   cur=$1
   3155   if [[ $cur =~ $_ble_complete_progcomp_cur_rex_simple && ${cur:${#BASH_REMATCH}} == [\'\"]* ]]; then
   3156     cur=${cur:${#BASH_REMATCH}+1}
   3157   elif [[ $cur =~ $_ble_complete_progcomp_cur_rex_break ]]; then
   3158     cur=${cur:${#BASH_REMATCH}}
   3159     case ${BASH_REMATCH[5]} in (\$*|@|\\?) cur=${BASH_REMATCH[5]#\\}$cur ;; esac
   3160   fi
   3161 }
   3162 
   3163 ## @fn ble/complete/progcomp/.compvar-initialize
   3164 ##   プログラム補完で提供される変数を構築します。
   3165 ##   @var[in]  comp_words comp_cword comp_line comp_point
   3166 ##   @var[out] COMP_WORDS COMP_CWORD COMP_LINE COMP_POINT COMP_KEY COMP_TYPE
   3167 ##   @var[out] cmd cur prev
   3168 ##     補完関数に渡す引数を格納します。cmd は COMP_WORDBREAKS による分割前のコ
   3169 ##     マンド名を保持します。cur は現在の単語のカーソル前の部分を保持します。但
   3170 ##     し、閉じていない引用符がある時は引用符の中身を、COMP_WORDBREAKS の文字が
   3171 ##     含まれる場合にはそれによって分割された後の最後の単語を返します。
   3172 ##   @var[out] progcomp_prefix
   3173 function ble/complete/progcomp/.compvar-initialize {
   3174   COMP_TYPE=9
   3175   COMP_KEY=9
   3176   ((${#KEYS[@]})) && COMP_KEY=${KEYS[${#KEYS[@]}-1]:-9} # KEYS defined in ble-decode/widget/.call-keyseq
   3177 
   3178   # Note: 以降の処理は基本的には comp_words, comp_line, comp_point, comp_cword を
   3179   #   COMP_WORDS COMP_LINE COMP_POINT COMP_CWORD にコピーする。
   3180   #   (1) 但し、直接代入する場合。$'' などがあると bash-completion が正しく動かないので、
   3181   #   エスケープを削除して適当に処理する。
   3182   #   (2) シェルの特殊文字以外の COMP_WORDBREAKS に含まれる文字で単語を分割する。
   3183 
   3184   local wordbreaks
   3185   ble/complete/progcomp/.compvar-initialize-wordbreaks
   3186 
   3187   progcomp_prefix=
   3188   COMP_CWORD=
   3189   COMP_POINT=
   3190   COMP_LINE=
   3191   COMP_WORDS=()
   3192   cmd=${comp_words[0]}
   3193   cur= prev=
   3194   local ret simple_flags simple_ibrace
   3195   local word1 index=0 offset=0 sep=
   3196   for word1 in "${comp_words[@]}"; do
   3197     # @var offset_dst
   3198     #   現在の単語の COMP_LINE 内部に於ける開始位置
   3199     local offset_dst=${#COMP_LINE}
   3200     # @var point
   3201     #   word が現在の単語の時、word 内のカーソル位置を保持する。
   3202     #   それ以外の時は空文字列。
   3203     local point=$((comp_point-offset))
   3204     ((0<=point&&point<=${#word1})) || point=
   3205     ((offset+=${#word1}))
   3206 
   3207     local words subword_flags=
   3208     ble/complete/progcomp/.compvar-generate-subwords "$word1"
   3209 
   3210     local w wq i=0 o=0 p
   3211     for w in "${words[@]}"; do
   3212       # @var p
   3213       #   現在の単語片の内部におけるカーソルの位置。
   3214       #   現在の単語片の内部にカーソルがない場合は空文字列。
   3215       p=
   3216       if [[ $point ]]; then
   3217         ((p=point-o))
   3218         # Note: #D1094 境界上にいる場合には偶数番目の単語片
   3219         #   (非 wordbreaks) に属させる。
   3220         ((i%2==0?p<=${#w}:p<${#w})) || p=
   3221         ((o+=${#w},i++))
   3222       fi
   3223       # カーソルが subword の境界にある時は左側の subword に属させる。
   3224       # 右側の subword で処理が行われない様に point をクリア。
   3225       [[ $p ]] && point=
   3226       [[ $point ]] && progcomp_prefix=$progcomp_prefix$w
   3227 
   3228       # Note: w -> wq の修正に伴ってここで p も修正される。
   3229       ble/complete/progcomp/.compvar-quote-subword "$w"; local wq=$ret
   3230 
   3231       # 単語登録
   3232       if [[ $p ]]; then
   3233         COMP_CWORD=${#COMP_WORDS[*]}
   3234         ((COMP_POINT=${#COMP_LINE}+${#sep}+p))
   3235         ble/complete/progcomp/.compvar-reduce-cur "${COMP_LINE:offset_dst}${wq::p}"
   3236         prev=${COMP_WORDS[COMP_CWORD-1]}
   3237       fi
   3238       ble/array#push COMP_WORDS "$wq"
   3239       COMP_LINE=$COMP_LINE$sep$wq
   3240       sep=
   3241     done
   3242 
   3243     sep=' '
   3244     ((offset++))
   3245     ((index++))
   3246   done
   3247 }
   3248 function ble/complete/progcomp/.compgen-helper-prog {
   3249   if [[ $comp_prog ]]; then
   3250     local COMP_WORDS COMP_CWORD cmd cur prev
   3251     local -x COMP_LINE COMP_POINT COMP_TYPE COMP_KEY
   3252     ble/complete/progcomp/.compvar-initialize
   3253 
   3254     if [[ $comp_opts == *:ble/prog-trim:* ]]; then
   3255       # WA: aws_completer
   3256       local compreply
   3257       ble/util/assign compreply '"$comp_prog" "$cmd" "$cur" "$prev" < /dev/null'
   3258       ble/bin/sed "s/[[:space:]]\{1,\}\$//" <<< "$compreply"
   3259     else
   3260       "$comp_prog" "$cmd" "$cur" "$prev" < /dev/null
   3261     fi
   3262   fi
   3263 }
   3264 ## @fn ble/complete/progcomp/compopt [-o OPTION|+o OPTION]
   3265 ##   compopt を上書きして -o/+o option を読み取る為の関数です。
   3266 ##
   3267 ##   OPTION
   3268 ##     ble/syntax-raw
   3269 ##       生成した候補をそのまま挿入する事を示します。
   3270 ##
   3271 ##     ble/no-default
   3272 ##       ble.sh の既定の候補生成 (候補が生成されなかった時の既定の候補生成、お
   3273 ##       よび、sabbrev 候補生成) を抑制します。
   3274 ##
   3275 function ble/complete/progcomp/compopt {
   3276   # Note: Bash補完以外から builtin compopt を呼び出しても
   3277   #  エラーになるので呼び出さない事にした (2019-02-05)
   3278   #builtin compopt "$@" 2>/dev/null; local ext=$?
   3279   local ext=0
   3280 
   3281   local -a ospec
   3282   while (($#)); do
   3283     local arg=$1; shift
   3284     case $arg in
   3285     (-*)
   3286       local ic c
   3287       for ((ic=1;ic<${#arg};ic++)); do
   3288         c=${arg:ic:1}
   3289         case $c in
   3290         (o)    ospec[${#ospec[@]}]="-$1"; shift ;;
   3291         ([DE]) fDefault=1; break 2 ;;
   3292         (*)    ((ext==0&&(ext=1))) ;;
   3293         esac
   3294       done ;;
   3295     (+o) ospec[${#ospec[@]}]="+$1"; shift ;;
   3296     (*)
   3297       # 特定のコマンドに対する compopt 指定
   3298       return "$ext" ;;
   3299     esac
   3300   done
   3301 
   3302   local s
   3303   for s in "${ospec[@]}"; do
   3304     case $s in
   3305     (-*) comp_opts=${comp_opts//:"${s:1}":/:}${s:1}: ;;
   3306     (+*) comp_opts=${comp_opts//:"${s:1}":/:} ;;
   3307     esac
   3308   done
   3309 
   3310   return "$ext"
   3311 }
   3312 function ble/complete/progcomp/.check-limits {
   3313   # user-input check
   3314   ((cand_iloop++%bleopt_complete_polling_cycle==0)) &&
   3315     [[ ! -t 0 ]] && ble/complete/check-cancel <&"$_ble_util_fd_stdin" &&
   3316     return 148
   3317   ble/complete/source/test-limit "$((progcomp_read_count++))"
   3318   return "$?"
   3319 }
   3320 function ble/complete/progcomp/.compgen-helper-func {
   3321   [[ $comp_func ]] || return 1
   3322   local -a COMP_WORDS
   3323   local COMP_LINE COMP_POINT COMP_CWORD COMP_TYPE COMP_KEY cmd cur prev
   3324   ble/complete/progcomp/.compvar-initialize
   3325 
   3326   local progcomp_read_count=0
   3327   local _ble_builtin_read_hook='ble/complete/progcomp/.check-limits || { ble/bash/read "$@" < /dev/null; return 148; }'
   3328 
   3329   local fDefault=
   3330   ble/function#push compopt 'ble/complete/progcomp/compopt "$@"'
   3331 
   3332   # WA (#D1807): A workaround for blocking scp/ssh
   3333   ble/function#push ssh '
   3334     local IFS=$_ble_term_IFS
   3335     if [[ " ${FUNCNAME[*]} " == *" ble/complete/progcomp/.compgen "* ]]; then
   3336       local -a args; args=("$@")
   3337       ble/util/conditional-sync "exec ssh \"\${args[@]}\"" \
   3338         "! ble/complete/check-cancel <&$_ble_util_fd_stdin" 128 progressive-weight:killall
   3339     else
   3340       ble/function#push/call-top "$@"
   3341     fi'
   3342 
   3343   # WA (#D1834): Suppress invocation of "command_not_found_handle" in the
   3344   #   completion functions
   3345   ble/function#push command_not_found_handle
   3346 
   3347   builtin eval '"$comp_func" "$cmd" "$cur" "$prev"' < /dev/null >&"$_ble_util_fd_stdout" 2>&"$_ble_util_fd_stderr"; local ret=$?
   3348 
   3349   ble/function#pop command_not_found_handle
   3350   ble/function#pop ssh
   3351   ble/function#pop compopt
   3352 
   3353   [[ $ret == 124 ]] && progcomp_retry=1
   3354   return 0
   3355 }
   3356 
   3357 ## @fn ble/complete/progcomp/.parse-complete/next
   3358 ##   @var[out] optarg
   3359 ##   @var[in,out] compdef
   3360 ##   @var[in] rex
   3361 function ble/complete/progcomp/.parse-complete/next {
   3362   if [[ $compdef =~ $rex ]]; then
   3363     builtin eval "arg=$BASH_REMATCH"
   3364     compdef=${compdef:${#BASH_REMATCH}}
   3365     return 0
   3366   elif [[ ${compdef%%' '*} ]]; then
   3367     # 本来此処には来ない筈
   3368     arg=${compdef%%' '*}
   3369     compdef=${compdef#*' '}
   3370     return 0
   3371   else
   3372     return 1
   3373   fi
   3374 }
   3375 function ble/complete/progcomp/.parse-complete/optarg {
   3376   optarg=
   3377   if ((ic+1<${#arg})); then
   3378     optarg=${arg:ic+1}
   3379     ic=${#arg}
   3380     return 0
   3381   elif [[ $compdef =~ $rex ]]; then
   3382     builtin eval "optarg=$BASH_REMATCH"
   3383     compdef=${compdef:${#BASH_REMATCH}}
   3384     return 0
   3385   else
   3386     return 2
   3387   fi
   3388 }
   3389 ## @fn ble/complete/progcomp/.parse-complete compdef
   3390 ##   @param[in] compdef
   3391 ##   @var[in,out] comp_opts
   3392 ##   @var[out] compoptions comp_prog comp_func flag_noquote
   3393 function ble/complete/progcomp/.parse-complete {
   3394   compoptions=()
   3395   comp_prog=
   3396   comp_func=
   3397   flag_noquote=
   3398   local compdef=${1#'complete '}
   3399 
   3400   local arg optarg rex='^([^][*?;&|[:space:]<>()\`$"'\''{}#^!]|\\.|'\''[^'\'']*'\'')+[[:space:]]+' # #D1709 safe (WA gawk 4.0.2)
   3401   while ble/complete/progcomp/.parse-complete/next; do
   3402     case $arg in
   3403     (-*)
   3404       local ic c
   3405       for ((ic=1;ic<${#arg};ic++)); do
   3406         c=${arg:ic:1}
   3407         case $c in
   3408         ([abcdefgjksuvE])
   3409           # Note: workaround #D0714 #M0009 #D0870
   3410           case $c in
   3411           (c) flag_noquote=1 ;;
   3412           (d) ((_ble_bash>=40300)) && flag_noquote=1 ;;
   3413           (f) ((40000<=_ble_bash&&_ble_bash<40200)) && flag_noquote=1 ;;
   3414           esac
   3415           ble/array#push compoptions "-$c" ;;
   3416         ([pr])
   3417           ;; # 無視 (-p 表示 -r 削除)
   3418         ([AGWXPS])
   3419           # Note: workaround #D0714 #M0009 #D0870
   3420           ble/complete/progcomp/.parse-complete/optarg || break 2
   3421           if [[ $c == A ]]; then
   3422             case $optarg in
   3423             (command) flag_noquote=1 ;;
   3424             (directory) ((_ble_bash>=40300)) && flag_noquote=1 ;;
   3425             (file) ((40000<=_ble_bash&&_ble_bash<40200)) && flag_noquote=1 ;;
   3426             esac
   3427           fi
   3428           ble/array#push compoptions "-$c" "$optarg" ;;
   3429         (o)
   3430           ble/complete/progcomp/.parse-complete/optarg || break 2
   3431           comp_opts=${comp_opts//:"$optarg":/:}$optarg:
   3432           ble/array#push compoptions "-$c" "$optarg" ;;
   3433         (C)
   3434           if ((_ble_bash<40000)); then
   3435             # bash-3.2以下では -C は一番最後に出力される (unquoted)
   3436             comp_prog=${compdef%' '}
   3437             compdef=
   3438           else
   3439             # bash-4.0以降では -C は quoted
   3440             ble/complete/progcomp/.parse-complete/optarg || break 2
   3441             comp_prog=$optarg
   3442           fi
   3443           ble/array#push compoptions "-$c" ble/complete/progcomp/.compgen-helper-prog ;;
   3444         (F)
   3445           # unquoted optarg (bash-3.2 以下では続きに unquoted -C prog が来得る)
   3446           if ((_ble_bash<40000)) && [[ $compdef == *' -C '* ]]; then
   3447             comp_prog=${compdef#*' -C '}
   3448             comp_prog=${comp_prog%' '}
   3449             ble/array#push compoptions '-C' ble/complete/progcomp/.compgen-helper-prog
   3450             comp_func=${compdef%%' -C '*}
   3451           else
   3452             comp_func=${compdef%' '}
   3453           fi
   3454           compdef=
   3455 
   3456           ble/array#push compoptions "-$c" ble/complete/progcomp/.compgen-helper-func ;;
   3457         (*)
   3458           # -D, -I, etc. just discard
   3459         esac
   3460       done ;;
   3461     (*)
   3462       ;; # 無視
   3463     esac
   3464   done
   3465 }
   3466 
   3467 ## @fn ble/complete/progcomp/.filter-and-split-compgen arr
   3468 ##   filter/sort/uniq candidates
   3469 ##
   3470 ##   @var[out] $arr, flag_mandb
   3471 ##   @var[in] compgen
   3472 ##   @var[in] COMPV compcmd comp_words
   3473 ##   @var[in] comp_opts use_workaround_for_git
   3474 function ble/complete/progcomp/.filter-and-split-compgen {
   3475   flag_mandb=
   3476 
   3477   # 1. sed (sort 前処理)
   3478   local sed_script=
   3479   {
   3480     # $comp_opts == *:ble/filter-by-prefix:*
   3481     #
   3482     # Note: "$COMPV" で始まる単語だけを sed /^$rex_compv/ でフィルタする。
   3483     #   それで候補が一つもなくなる場合にはフィルタ無しで単語を列挙する。
   3484     #
   3485     #   2019-02-03 実は、現在の実装ではわざわざフィルタする必要はないかもしれない。
   3486     #   以前 compgen に -- "$COMPV" を渡してもフィルタしてくれなかったのは、
   3487     #   #D0245 cdd38598 で ble/complete/progcomp/.compgen-helper-func に於いて、
   3488     #   "$comp_func" に引数を渡し忘れていたのが原因と思われる。
   3489     #   これは 1929132b に於いて修正されたが念のためにフィルタを残していた気がする。
   3490     if [[ $comp_opts == *:ble/filter-by-prefix:* ]]; then
   3491       local ret; ble/string#escape-for-sed-regex "$COMPV"; local rex_compv=$ret
   3492       sed_script='!/^'$rex_compv'/d'
   3493     fi
   3494 
   3495     [[ $use_workaround_for_git ]] &&
   3496       sed_script=${sed_script:+$sed_script;}'s/[[:space:]]\{1,\}$//'
   3497   }
   3498   local out=
   3499   [[ $sed_script ]] && ble/util/assign out 'ble/bin/sed "$sed_script;/^\$/d" <<< "$compgen"'
   3500   [[ $out ]] || out=$compgen
   3501 
   3502   # 2. sort
   3503   local require_awk=
   3504   if [[ $comp_opts != *:nosort:* ]]; then
   3505     ble/util/assign out 'ble/bin/sort -u <<< "$out"'
   3506   else
   3507     require_awk=1 # for uniq
   3508   fi
   3509 
   3510   # 3. awk (sort 後処理)
   3511   local -a args_mandb=()
   3512   if [[ $compcmd == "${comp_words[0]}" && $COMPV != [!-]* ]]; then
   3513     if local ret; ble/complete/mandb/generate-cache "$compcmd"; then
   3514       require_awk=1
   3515       args_mandb=(mode=mandb "$ret")
   3516     fi
   3517   fi
   3518   if [[ $require_awk ]]; then
   3519     local awk_script='
   3520       BEGIN { mandb_count = 0; }
   3521       mode == "mandb" {
   3522         name = $0
   3523         sub(/'"$_ble_term_FS"'.*/, "", name);
   3524         if (!mandb[name]) mandb[name] = $0;
   3525         next;
   3526       }
   3527 
   3528       function register_mandb_entry(name, display, entry) {
   3529         if (name2index[name] != "") {
   3530           # Remove duplicates after removing trailing /=$/.  If the new
   3531           # "display" is longer, overwrite the existing one.
   3532           if (length(display) <= length(name2display[name])) return;
   3533           name2display[name] = display;
   3534           entries[name2index[name]] = entry;
   3535         } else {
   3536           name2index[name] = mandb_count;
   3537           name2display[name] = display;
   3538           entries[mandb_count++] = entry;
   3539         }
   3540       }
   3541 
   3542       !hash[$0]++ {
   3543         if (/^$/) next;
   3544 
   3545         name = $0
   3546         sub(/=$/, "", name);
   3547         if (mandb[name]) {
   3548           register_mandb_entry(name, $0, mandb[name]);
   3549           next;
   3550         } else if (sub(/^--no-/, "--", name)) {
   3551 
   3552           # Synthesize description of "--no-OPTION"
   3553           if ((entry = mandb[name]) || (entry = mandb[substr(name, 2)])) {
   3554             split(entry, record, FS);
   3555             if ((desc = record[4])) {
   3556               desc = "\033[1mReverse[\033[m " desc " \033[;1m]\033[m";
   3557               if (match($0, /['"$_ble_term_space"']*[:=[]/)) {
   3558                 option = substr($0, 1, RSTART - 1);
   3559                 optarg = substr($0, RSTART);
   3560                 suffix = substr($0, RSTART, 1);
   3561                 if (suffix == "[") suffix = "";
   3562               } else {
   3563                 option = $0;
   3564                 optarg = "";
   3565                 suffix = " ";
   3566               }
   3567               register_mandb_entry(name, $0, option FS optarg FS suffix FS desc);
   3568             }
   3569             next;
   3570           }
   3571 
   3572         }
   3573 
   3574         print $0;
   3575       }
   3576 
   3577       END {
   3578         if (mandb_count) {
   3579           for (i = 0; i < mandb_count; i++)
   3580             print entries[i];
   3581           exit 10;
   3582         }
   3583       }
   3584     '
   3585     ble/util/assign-array "$1" 'ble/bin/awk -F "$_ble_term_FS" "$awk_script" "${args_mandb[@]}" mode=compgen - <<< "$out"'
   3586     (($?==10)) && flag_mandb=1
   3587   else
   3588     ble/string#split-lines "$1" "$out"
   3589   fi
   3590   return 0
   3591 } 2>/dev/null
   3592 
   3593 function ble/complete/progcomp/.cobraV2.patch {
   3594   local cobra_version=$1
   3595   if ((cobra_version<10500)); then
   3596     local -a completions
   3597     completions=("${out[@]}")
   3598   fi
   3599 
   3600   local prefix=$cur
   3601   [[ $comps_flags == *v* ]] && prefix=$COMPV
   3602   local unprocessed has_desc=
   3603   unprocessed=()
   3604   local lines line cand desc
   3605   for lines in "${out[@]}"; do
   3606     ble/string#split-lines lines "$lines"
   3607     for line in "${lines[@]}"; do
   3608       if [[ $line == *$'\t'* ]]; then
   3609         cand=${line%%$'\t'*}
   3610         desc=${line#*$'\t'}
   3611         [[ $cand == "$prefix"* ]] || continue
   3612         ble/complete/cand/yield word "$cand" "$desc"
   3613         has_desc=1
   3614       elif [[ $line ]]; then
   3615         ble/array#push unprocessed "$line"
   3616       fi
   3617     done
   3618   done
   3619 
   3620   [[ $has_desc ]] && bleopt complete_menu_style=desc
   3621   if ((${#unprocessed[@]})); then
   3622     if ((cobra_version>=10500)); then
   3623       completions=("${unprocessed[@]}")
   3624     else
   3625       out=("${unprocessed[@]}")
   3626     fi
   3627     ble/function#advice/do
   3628   fi
   3629 }
   3630 
   3631 ## @fn ble/complete/progcomp/.compgen opts
   3632 ##
   3633 ##   @param[in] opts
   3634 ##     コロン区切りのオプションリストです。
   3635 ##
   3636 ##     initial ... 最初の単語 (コマンド名) の補完に用いる関数を指定します。
   3637 ##
   3638 ##   @param[in,opt] cmd
   3639 ##     プログラム補完規則を検索するのに使う名前を指定します。
   3640 ##     省略した場合 ${comp_words[0]} が使われます。
   3641 ##
   3642 ##   @var[out] comp_opts
   3643 ##
   3644 ##   @var[in] COMP1 COMP2 COMPV COMPS comp_type
   3645 ##     ble/complete/source の標準的な変数たち。
   3646 ##
   3647 ##   @var[in] comp_words comp_line comp_point comp_cword
   3648 ##     ble/syntax:bash/extract-command によって生成される変数たち。
   3649 ##
   3650 ##   @var[in] 他色々
   3651 ##   @exit 入力がある時に 148 を返します。
   3652 function ble/complete/progcomp/.compgen {
   3653   local opts=$1
   3654 
   3655   local compcmd= is_special_completion=
   3656   local -a alias_args=()
   3657   if [[ :$opts: == *:initial:* ]]; then
   3658     if ((_ble_bash>=50000)); then
   3659       is_special_completion=1
   3660       compcmd='-I'
   3661     else
   3662       compcmd=_InitialWorD_
   3663     fi
   3664   elif [[ :$opts: == *:default:* ]]; then
   3665     if ((_ble_bash>=40100)); then
   3666       builtin complete -p -D &>/dev/null || return 1
   3667       is_special_completion=1
   3668       compcmd='-D'
   3669     else
   3670       builtin complete -p _DefaultCmD_ &>/dev/null || return 1
   3671       compcmd=_DefaultCmD_
   3672     fi
   3673   else
   3674     compcmd=${comp_words[0]}
   3675   fi
   3676 
   3677   local compdef
   3678   if [[ $is_special_completion ]]; then
   3679     # -I, -D, etc.
   3680     ble/util/assign compdef 'builtin complete -p "$compcmd" 2>/dev/null'
   3681   elif ble/syntax:bash/simple-word/is-simple "$compcmd"; then
   3682     # 既に呼び出し元で quote されている想定
   3683     ble/util/assign compdef "builtin complete -p -- $compcmd 2>/dev/null"
   3684     local ret; ble/syntax:bash/simple-word/eval "$compcmd"; compcmd=$ret
   3685   else
   3686     ble/util/assign compdef 'builtin complete -p -- "$compcmd" 2>/dev/null'
   3687   fi
   3688   # strip -I, -D, or command_name
   3689   # #D1579 bash-5.1 では空コマンドに限り '' と出力する様である。
   3690   compdef=${compdef%"${compcmd:-''}"}
   3691   compdef=${compdef%' '}' '
   3692 
   3693   local comp_prog comp_func compoptions flag_noquote
   3694   ble/complete/progcomp/.parse-complete "$compdef"
   3695 
   3696   # WA: Workarounds for third-party plugins
   3697   if [[ $comp_func ]]; then
   3698     # fzf
   3699     [[ $comp_func == _fzf_* ]] &&
   3700       ble-import -f contrib/integration/fzf-completion
   3701 
   3702     # bash_completion
   3703     if ble/is-function _comp_initialize; then
   3704       # bash-completion 2.12
   3705       ble/complete/mandb:bash-completion/inject
   3706     elif ble/is-function _quote_readline_by_ref; then
   3707       # https://github.com/scop/bash-completion/pull/492 (fixed in bash-completion 2.12)
   3708       function _quote_readline_by_ref {
   3709         if [[ $1 == \'* ]]; then
   3710           printf -v "$2" %s "${1:1}"
   3711         else
   3712           printf -v "$2" %q "$1"
   3713           [[ ${!2} == \$* ]] && builtin eval "$2=${!2}"
   3714         fi
   3715       }
   3716       ble/function#suppress-stderr _filedir 2>/dev/null
   3717 
   3718       # https://github.com/scop/bash-completion/issues/509 (fixed in bash-completion 2.12)
   3719       ble/function#suppress-stderr _find 2>/dev/null
   3720 
   3721       # https://github.com/scop/bash-completion/pull/556 (fixed in bash-completion 2.12)
   3722       ble/function#suppress-stderr _scp_remote_files 2>/dev/null
   3723 
   3724       # https://github.com/scop/bash-completion/pull/773 (fixed in bash-completion 2.12)
   3725       ble/function#suppress-stderr _function 2>/dev/null
   3726 
   3727       ble/complete/mandb:bash-completion/inject
   3728     fi
   3729 
   3730     # cobra GenBashCompletionV2
   3731     if [[ $comp_func == __start_* ]]; then
   3732       local target=__${comp_func#__start_}_handle_completion_types
   3733       if ble/is-function "$target"; then
   3734         local cobra_version=
   3735         if ble/is-function "__${comp_func#__start_}_extract_activeHelp"; then
   3736           cobra_version=10500 # v1.5.0 (Release 2022-06-21)
   3737         fi
   3738         ble/function#advice around "$target" "ble/complete/progcomp/.cobraV2.patch $cobra_version"
   3739       fi
   3740     fi
   3741 
   3742     # WA for dnf completion
   3743     ble/function#advice around _dnf_commands_helper '
   3744       ble/util/conditional-sync \
   3745         ble/function#advice/do \
   3746         "! ble/complete/check-cancel <&$_ble_util_fd_stdin" 128 progressive-weight:killall' 2>/dev/null
   3747 
   3748     # WA for zoxide TAB
   3749     if [[ $comp_func == _z ]]; then
   3750       ble-import -f contrib/integration/zoxide
   3751       ble/contrib/integration:zoxide/adjust
   3752     fi
   3753 
   3754     # WA for _complete_nix
   3755     if [[ $comp_func == _complete_nix ]]; then
   3756       ble-import -f integration/nix-completion
   3757       ble/contrib/integration:nix-completion/adjust
   3758     fi
   3759 
   3760     # https://github.com/akinomyoga/ble.sh/issues/292 (Android Debug Bridge)
   3761     ble/function#suppress-stderr _adb 2>/dev/null
   3762   fi
   3763   if [[ $comp_prog ]]; then
   3764     # aws
   3765     if [[ $comp_prog == aws_completer ]]; then
   3766       comp_opts=${comp_opts}ble/no-mark-directories:ble/prog-trim:
   3767     fi
   3768   fi
   3769 
   3770 
   3771   ble/complete/check-cancel && return 148
   3772 
   3773   # Note: 一旦 compgen だけで ble/util/assign するのは、compgen をサブシェルではなく元のシェルで評価する為である。
   3774   #   補完関数が遅延読込になっている場合などに、読み込まれた補完関数が次回から使える様にする為に必要である。
   3775   local compgen compgen_compv=$COMPV
   3776   if [[ ! $flag_noquote && :$comp_opts: != *:noquote:* ]]; then
   3777     local q="'" Q="'\''"
   3778     compgen_compv="'${compgen_compv//$q/$Q}'"
   3779   fi
   3780   # WA #D1682: libvirt の virsh 用の補完が勝手に変数 IFS 及び word を書き換えて
   3781   # そのまま放置して抜けてしまう。仕方がないので tmpenv で変数の内容を復元する
   3782   # 事にする。
   3783   local progcomp_prefix= progcomp_retry=
   3784   IFS=$IFS word= ble/util/assign compgen 'builtin compgen "${compoptions[@]}" -- "$compgen_compv" 2>/dev/null'
   3785 
   3786   # Note #D0534: complete -D 補完仕様に従った補完関数が 124 を返したとき再度始
   3787   #   めから補完を行う。ble/complete/progcomp/.compgen-helper-func 関数内で補間
   3788   #   関数の終了ステータスを確認し、もし 124 だった場合には
   3789   #   progcomp_retry に retry を設定する。
   3790   # Note #D1760: complete -D 以外の時でも 124 が返された時再試行する。
   3791   if [[ $progcomp_retry && ! $_ble_complete_retry_guard ]]; then
   3792     local _ble_complete_retry_guard=1
   3793     opts=:$opts:
   3794     opts=${opts//:default:/:}
   3795     ble/complete/progcomp/.compgen "$opts"
   3796     return "$?"
   3797   fi
   3798 
   3799   [[ $compgen ]] || return 1
   3800 
   3801   # WA: git の補完関数など勝手に末尾に space をつけ -o nospace を指定する物が存在する。
   3802   #   単語の後にスペースを挿入する事を意図していると思われるが、
   3803   #   通常 compgen (例: compgen -f) で生成される候補に含まれるスペースは、
   3804   #   挿入時のエスケープ対象であるので末尾の space もエスケープされてしまう。
   3805   #
   3806   #   仕方がないので sed で各候補の末端の [[:space:]]+ を除去する。
   3807   #   これだとスペースで終わるファイル名を挿入できないという実害が発生するが、
   3808   #   そのような変な補完関数を作るのが悪いのである。
   3809   local use_workaround_for_git=
   3810   if [[ $comp_func == __git* && $comp_opts == *:nospace:* ]]; then
   3811     use_workaround_for_git=1
   3812     comp_opts=${comp_opts//:nospace:/:}
   3813   fi
   3814 
   3815   local cands flag_mandb=
   3816   ble/complete/progcomp/.filter-and-split-compgen cands # compgen (comp_opts, etc) -> cands, flag_mandb
   3817 
   3818   ble/complete/source/test-limit "${#cands[@]}" || return 1
   3819 
   3820   # determine COMP_PREFIX for filenames
   3821   if [[ $comp_opts == *:filenames:* ]]; then
   3822     if [[ $comp_opts == *:ble/syntax-raw:* ]]; then
   3823       [[ $COMPS == */* ]] && COMP_PREFIX=${COMPS%/*}/
   3824     else
   3825       [[ $COMPV == */* ]] && COMP_PREFIX=${COMPV%/*}/
   3826     fi
   3827   fi
   3828 
   3829   local old_cand_count=$cand_count
   3830 
   3831   local action=progcomp "${_ble_complete_yield_varnames[@]/%/=}" # WA #D1570 checked
   3832   ble/complete/cand/yield.initialize "$action"
   3833   if [[ $flag_mandb ]]; then
   3834     local -a entries; entries=("${cands[@]}")
   3835     cands=()
   3836     local fs=$_ble_term_FS has_desc= icand=0 entry
   3837     for entry in "${entries[@]}"; do
   3838       ((cand_iloop++%bleopt_complete_polling_cycle==0)) && ble/complete/check-cancel && return 148
   3839       if [[ $entry == -*"$fs"*"$fs"*"$fs"* ]]; then
   3840         local cand=${entry%%"$fs"*}
   3841         ble/complete/cand/yield mandb "$cand" "$entry"
   3842         [[ $entry == *"$fs"*"$fs"*"$fs"?* ]] && has_desc=1
   3843       else
   3844         cands[icand++]=$progcomp_prefix$entry
   3845       fi
   3846     done
   3847     [[ $has_desc ]] && bleopt complete_menu_style=desc
   3848   else
   3849     [[ $progcomp_prefix ]] &&
   3850       if ((_ble_bash>=40300)) && ! shopt -q compat42; then
   3851         cands=("${cands[@]/#/"$progcomp_prefix"}") # WA #D1570 #D1751 checked
   3852       else
   3853         cands=("${cands[@]/#/$progcomp_prefix}") # WA #D1570 #D1738 checked
   3854       fi
   3855   fi
   3856   ble/complete/cand/yield.batch "$action" "$comp_opts"
   3857 
   3858   # plusdirs の時はディレクトリ名も候補として列挙
   3859   # Note: 重複候補や順序については考えていない
   3860   [[ $comp_opts == *:plusdirs:* ]] && ble/complete/source:dir
   3861 
   3862   ((cand_count>old_cand_count))
   3863 }
   3864 
   3865 ## @fn ble/complete/progcomp/.compline-rewrite-command cmd [args...]
   3866 ##   alias 展開等によるコマンド名の変更に対応して、
   3867 ##   補完対象のコマンド名を指定の物に書き換えます。
   3868 ##
   3869 ##   @var[in,out] comp_line comp_words comp_point comp_cword
   3870 ##
   3871 function ble/complete/progcomp/.compline-rewrite-command {
   3872   local ocmd=${comp_words[0]}
   3873   [[ $1 != "$ocmd" ]] || (($#>=2)) || return 1
   3874   local IFS=$_ble_term_IFS
   3875   local ins="$*"
   3876   if (($#==0)); then
   3877     # コマンド除去 (aliasで空に展開された時)
   3878     local ret; ble/string#ltrim "${comp_line:${#ocmd}}"
   3879     ((comp_point-=${#comp_line}-${#ret}))
   3880     comp_line=$ret
   3881   else
   3882     comp_line=$ins${comp_line:${#ocmd}}
   3883     ((comp_point-=${#ocmd}))
   3884   fi
   3885   ((comp_point<0&&(comp_point=0),comp_point+=${#ins}))
   3886   comp_words=("$@" "${comp_words[@]:1}")
   3887   ((comp_cword&&(comp_cword+=$#-1)))
   3888 }
   3889 
   3890 function ble/complete/progcomp/.split-alias-words {
   3891   local tail=$1
   3892   local rex_redir='^'$_ble_syntax_bash_RexRedirect
   3893   local rex_word='^'$_ble_syntax_bash_simple_rex_element'+'
   3894   local rex_delim=$'^[\n;|&]'
   3895   local rex_spaces=$'^[ \t]+'
   3896   local rex_misc='^[<>()]+'
   3897 
   3898   local -a words=()
   3899   while [[ $tail ]]; do
   3900     if [[ $tail =~ $rex_redir && $tail != ['<>']'('* ]]; then
   3901       ble/array#push words "$BASH_REMATCH"
   3902       tail=${tail:${#BASH_REMATCH}}
   3903     elif [[ $tail =~ $rex_word ]]; then
   3904       local w=$BASH_REMATCH
   3905       tail=${tail:${#w}}
   3906       if [[ $tail && $tail != ["$_ble_term_IFS;|&<>()"]* ]]; then
   3907         local s=${tail%%["$_ble_term_IFS"]*}
   3908         tail=${tail:${#s}}
   3909         w=$w$s
   3910       fi
   3911       ble/array#push words "$w"
   3912     elif [[ $tail =~ $rex_delim ]]; then
   3913       words=()
   3914       tail=${tail:${#BASH_REMATCH}}
   3915     elif [[ $tail =~ $rex_spaces ]]; then
   3916       tail=${tail:${#BASH_REMATCH}}
   3917     elif [[ $tail =~ $rex_misc ]]; then
   3918       ble/array#push words "$BASH_REMATCH"
   3919       tail=${tail:${#BASH_REMATCH}}
   3920     else
   3921       local w=${tail%%["$_ble_term_IFS"]*}
   3922       ble/array#push words "$w"
   3923       tail=${tail:${#w}}
   3924     fi
   3925   done
   3926 
   3927   # skip assignments/redirections
   3928   local i=0 rex_assign='^[_a-zA-Z0-9]+(\['$_ble_syntax_bash_simple_rex_element'*\])?\+?='
   3929   while ((i<${#words[@]})); do
   3930    if [[ ${words[i]} =~ $rex_assign ]]; then
   3931      ((i++))
   3932    elif [[ ${words[i]} =~ $rex_redir && ${words[i]} != ['<>']'('* ]]; then
   3933      ((i+=2))
   3934    else
   3935      break
   3936    fi
   3937   done
   3938 
   3939   ret=("${words[@]:i}")
   3940 }
   3941 
   3942 ## @fn ble/complete/progcomp/.try-load-completion cmd
   3943 ##   bash-completion の loader を呼び出して遅延補完設定をチェックする。
   3944 function ble/complete/progcomp/.try-load-completion {
   3945   ble/is-function __load_completion || return 1
   3946 
   3947   ble/function#push command_not_found_handle
   3948   __load_completion "$1" < /dev/null &>/dev/null; local ext=$?
   3949   ble/function#pop command_not_found_handle
   3950   ((ext==0)) || return "$ext"
   3951 
   3952   builtin complete -p -- "$1" &>/dev/null
   3953 }
   3954 
   3955 ## @fn ble/complete/progcomp cmd opts
   3956 ##   補完指定を検索して対応する補完関数を呼び出します。
   3957 ##   @var[in] comp_line comp_words comp_point comp_cword
   3958 function ble/complete/progcomp {
   3959   local cmd=${1-${comp_words[0]}} opts=$2
   3960 
   3961   # copy compline variables
   3962   local orig_comp_words orig_comp_cword=$comp_cword orig_comp_line=$comp_line orig_comp_point=$comp_point
   3963   orig_comp_words=("${comp_words[@]}")
   3964   local comp_words comp_cword=$comp_cword comp_line=$comp_line comp_point=$comp_point
   3965   comp_words=("${orig_comp_words[@]}")
   3966   [[ $cmd == "${orig_comp_words[0]}" ]] ||
   3967     ble/complete/progcomp/.compline-rewrite-command "$cmd"
   3968 
   3969   local orig_qcmds_set=
   3970   local -a orig_qcmds=()
   3971   local -a alias_args=()
   3972   [[ :$opts: == *:__recursive__:* ]] ||
   3973     local alias_checked=' '
   3974   while :; do
   3975 
   3976     # @var cmd   ... 元のコマンド名
   3977     # @var ucmd  ... simple-word/eval したコマンド名
   3978     # @var qcmds ... simple-word/eval x quote-word したコマンド
   3979     local ret ucmd qcmds
   3980     ucmd=$cmd qcmds=("$cmd")
   3981     if ble/syntax:bash/simple-word/is-simple "$cmd"; then
   3982       if ble/syntax:bash/simple-word/eval "$cmd" noglob &&
   3983           [[ $ret != "$cmd" || ${#ret[@]} -ne 1 ]]; then
   3984 
   3985         ucmd=${ret[0]} qcmds=()
   3986         local word
   3987         for word in "${ret[@]}"; do
   3988           ble/string#quote-word "$word" quote-empty
   3989           ble/array#push qcmds "$ret"
   3990         done
   3991       else
   3992         ble/string#quote-word "$cmd" quote-empty
   3993         qcmds=("$ret")
   3994       fi
   3995 
   3996       [[ $cmd == "${orig_comp_words[0]}" ]] &&
   3997         orig_qcmds_set=1 orig_qcmds=("${qcmds[@]}")
   3998     fi
   3999 
   4000     if ble/is-function "ble/cmdinfo/complete:$ucmd"; then
   4001       ble/complete/progcomp/.compline-rewrite-command "${qcmds[@]}" "${alias_args[@]}"
   4002       "ble/cmdinfo/complete:$ucmd" "$opts"
   4003       return "$?"
   4004     elif [[ $ucmd == */?* ]] && ble/is-function "ble/cmdinfo/complete:${ucmd##*/}"; then
   4005       ble/string#quote-word "${ucmd##*/}"; qcmds[0]=$ret
   4006       ble/complete/progcomp/.compline-rewrite-command "${qcmds[@]}" "${alias_args[@]}"
   4007       "ble/cmdinfo/complete:${ucmd##*/}" "$opts"
   4008       return "$?"
   4009     elif builtin complete -p -- "$ucmd" &>/dev/null; then
   4010       ble/complete/progcomp/.compline-rewrite-command "${qcmds[@]}" "${alias_args[@]}"
   4011       ble/complete/progcomp/.compgen "$opts"
   4012       return "$?"
   4013     elif [[ $ucmd == */?* ]] && builtin complete -p -- "${ucmd##*/}" &>/dev/null; then
   4014       ble/string#quote-word "${ucmd##*/}"; qcmds[0]=$ret
   4015       ble/complete/progcomp/.compline-rewrite-command "${qcmds[@]}" "${alias_args[@]}"
   4016       ble/complete/progcomp/.compgen "$opts"
   4017       return "$?"
   4018     elif ble/complete/progcomp/.try-load-completion "${ucmd##*/}"; then
   4019       ble/string#quote-word "${ucmd##*/}"; qcmds[0]=$ret
   4020       ble/complete/progcomp/.compline-rewrite-command "${qcmds[@]}" "${alias_args[@]}"
   4021       ble/complete/progcomp/.compgen "$opts"
   4022       return "$?"
   4023     fi
   4024     alias_checked=$alias_checked$cmd' '
   4025 
   4026     # progcomp_alias が有効でなければ break
   4027     ((_ble_bash<50000)) || shopt -q progcomp_alias || break
   4028 
   4029     local ret
   4030     ble/alias#expand "$cmd"
   4031     [[ $ret == "$cmd" ]] && break
   4032     ble/complete/progcomp/.split-alias-words "$ret"
   4033     if ((${#ret[@]}==0)); then
   4034       # alias 展開により内容が消滅した時は次の単語をコマンドとして再度展開を繰り返す
   4035       ble/complete/progcomp/.compline-rewrite-command "${alias_args[@]}"
   4036       if ((${#comp_words[@]})); then
   4037         if ((comp_cword==0)); then
   4038           ble/complete/source:command
   4039         else
   4040           ble/complete/progcomp "${comp_words[0]}" "__recursive__:$opts"
   4041         fi
   4042       fi
   4043       return "$?"
   4044     fi
   4045 
   4046     [[ $alias_checked != *" $ret "* ]] || break
   4047     cmd=$ret
   4048     ((${#ret[@]}>=2)) &&
   4049       alias_args=("${ret[@]:1}" "${alias_args[@]}")
   4050   done
   4051 
   4052   # comp_words の再構築
   4053   comp_words=("${orig_comp_words[@]}")
   4054   comp_cword=$orig_comp_cword
   4055   comp_line=$orig_comp_line
   4056   comp_point=$orig_comp_point
   4057   [[ $orig_qcmds_set ]] &&
   4058     ble/complete/progcomp/.compline-rewrite-command "${orig_qcmds[@]}"
   4059   ble/complete/progcomp/.compgen "default:$opts"
   4060 }
   4061 
   4062 #------------------------------------------------------------------------------
   4063 # mandb
   4064 
   4065 # オプション名に現れる事を許す文字の集合 (- と + を除く)
   4066 # Exclude non-ASCII or symbols /[][()<>{}="'\''`]/
   4067 # Note: awk の正規表現内部で使っても大丈夫な様に \ と / をエスケープしている。
   4068 # Note (#D2039): @ は cd -@ で使われている
   4069 _ble_complete_option_chars='_!#$%&:;.,^~|\\?\/*a-zA-Z0-9@'
   4070 
   4071 # action:mandb
   4072 #
   4073 #   DATA ... cmd FS menu_suffix FS insert_suffix FS desc
   4074 #
   4075 function ble/complete/action:mandb/initialize {
   4076   ble/complete/action/quote-insert
   4077 }
   4078 function ble/complete/action:mandb/initialize.batch {
   4079   ble/complete/action/quote-insert.batch newline
   4080 }
   4081 function ble/complete/action:mandb/complete {
   4082   ble/complete/action/complete.close-quotation
   4083   local fields
   4084   ble/string#split fields "$_ble_term_FS" "$DATA"
   4085   local tail=${fields[2]}
   4086   [[ $tail == ' ' && $comps_flags == *x* ]] && tail=','
   4087   ble/complete/action/complete.addtail "$tail"
   4088 }
   4089 function ble/complete/action:mandb/init-menu-item {
   4090   local ret; ble/color/face2g argument_option; g=$ret
   4091 
   4092   local fields
   4093   ble/string#split fields "$_ble_term_FS" "$DATA"
   4094   suffix=${fields[1]}
   4095 }
   4096 function ble/complete/action:mandb/get-desc {
   4097   local fields
   4098   ble/string#split fields "$_ble_term_FS" "$DATA"
   4099   desc=${fields[3]}
   4100 }
   4101 
   4102 function ble/complete/mandb/load-mandb-conf {
   4103   [[ -s $1 ]] || return 0
   4104   local line words
   4105   while ble/bash/read line || [[ $line ]]; do
   4106     ble/string#split-words words "${line%%'#'*}"
   4107     case ${words[0]} in
   4108     (MANDATORY_MANPATH)
   4109       [[ -d ${words[1]} ]] &&
   4110         ble/array#push manpath_mandatory "${words[1]}" ;;
   4111     (MANPATH_MAP)
   4112       ble/dict#set manpath_map "${words[1]}" "${words[2]}" ;;
   4113     esac
   4114   done < "$1"
   4115 }
   4116 
   4117 _ble_complete_mandb_default_manpath=()
   4118 function ble/complete/mandb/initialize-manpath {
   4119   ((${#_ble_complete_mandb_default_manpath[@]})) && return 0
   4120   local manpath
   4121   MANPATH= ble/util/assign manpath 'manpath || ble/bin/man -w' 2>/dev/null
   4122   ble/string#split manpath : "$manpath"
   4123   if ((${#manpath[@]}==0)); then
   4124     local -a manpath_mandatory=()
   4125     builtin eval -- "${_ble_util_dict_declare//NAME/manpath_map}"
   4126     ble/complete/mandb/load-mandb-conf /etc/man_db.conf
   4127     ble/complete/mandb/load-mandb-conf ~/.manpath
   4128 
   4129     # default mandatory manpath
   4130     if ((${#manpath_mandatory[@]}==0)); then
   4131       local ret
   4132       ble/complete/util/eval-pathname-expansion '~/*/share/man'
   4133       ble/array#push manpath_mandatory "${ret[@]}"
   4134       ble/complete/util/eval-pathname-expansion '~/@(opt|.opt)/*/share/man'
   4135       ble/array#push manpath_mandatory "${ret[@]}"
   4136       for ret in /usr/local/share/man /usr/local/man /usr/share/man; do
   4137         [[ -d $ret ]] && ble/array#push manpath_mandatory "$ret"
   4138       done
   4139     fi
   4140 
   4141     builtin eval -- "${_ble_util_dict_declare//NAME/mark}"
   4142 
   4143     local paths path ret
   4144     ble/string#split paths : "$PATH"
   4145     for path in "${paths[@]}"; do
   4146       [[ -d $path ]] || continue
   4147       [[ $path == *?/ ]] && path=${path%/}
   4148       if ble/dict#get manpath_map "$path"; then
   4149         path=$ret
   4150       else
   4151         path=${path%/bin}/share/man
   4152       fi
   4153       if [[ -d $path ]] && ! ble/set#contains mark "$path"; then
   4154         ble/set#add mark "$path"
   4155         ble/array#push manpath "$path"
   4156       fi
   4157     done
   4158 
   4159     for path in "${manpath_mandatory[@]}"; do
   4160       if [[ -d $path ]] && ! ble/set#contains mark "$path"; then
   4161         ble/set#add mark "$path"
   4162         ble/array#push manpath "$path"
   4163       fi
   4164     done
   4165   fi
   4166   _ble_complete_mandb_default_manpath=("${manpath[@]}")
   4167 }
   4168 
   4169 function ble/complete/mandb/search-file/.extract-path {
   4170   local command=$1
   4171   [[ $_ble_complete_mandb_lang ]] &&
   4172     local LC_ALL=$$_ble_complete_mandb_lang
   4173   ble/util/assign path 'ble/bin/man -w "$command"' 2>/dev/null
   4174 }
   4175 ble/function#suppress-stderr ble/complete/mandb/search-file/.extract-path
   4176 
   4177 function ble/complete/mandb/search-file/.check {
   4178   local path=$1
   4179   if [[ $path && -s $path ]]; then
   4180     ret=$path
   4181     return 0
   4182   else
   4183     return 1
   4184   fi
   4185 }
   4186 ## @fn ble/complete/mandb/search-file command
   4187 ##   指定したコマンドに対応する man ページのファイルを検索します。
   4188 ##   @var[out] ret
   4189 ##     見つかったファイルへのパスを格納します。
   4190 ##   @exit
   4191 ##     該当するファイルが見つかった時に成功します。
   4192 function ble/complete/mandb/search-file {
   4193   local command=$1
   4194 
   4195   local path
   4196   ble/complete/mandb/search-file/.extract-path "$command"
   4197   ble/complete/mandb/search-file/.check "$path" && return 0
   4198 
   4199   # Get manpaths
   4200   ble/string#split ret : "$MANPATH"
   4201 
   4202   # Replace empty paths with the default manpaths
   4203   ((${#ret[@]})) || ret=('')
   4204   local -a manpath=()
   4205   for path in "${ret[@]}"; do
   4206     if [[ $path ]]; then
   4207       ble/array#push manpath "$path"
   4208     else
   4209       # system manpath
   4210       ble/complete/mandb/initialize-manpath
   4211       ble/array#push manpath "${_ble_complete_mandb_default_manpath[@]}"
   4212     fi
   4213   done
   4214 
   4215   local path
   4216   for path in "${manpath[@]}"; do
   4217     [[ -d $path ]] || continue
   4218     ble/complete/mandb/search-file/.check "$path/man1/$command.1" && return 0
   4219     ble/complete/mandb/search-file/.check "$path/man1/$command.8" && return 0
   4220     if ble/is-function ble/bin/gzip; then
   4221       ble/complete/mandb/search-file/.check "$path/man1/$command.1.gz" && return 0
   4222       ble/complete/mandb/search-file/.check "$path/man1/$command.8.gz" && return 0
   4223     fi
   4224     if ble/is-function ble/bin/bzcat; then
   4225       ble/complete/mandb/search-file/.check "$path/man1/$command.1.bz" && return 0
   4226       ble/complete/mandb/search-file/.check "$path/man1/$command.1.bz2" && return 0
   4227       ble/complete/mandb/search-file/.check "$path/man1/$command.8.bz" && return 0
   4228       ble/complete/mandb/search-file/.check "$path/man1/$command.8.bz2" && return 0
   4229     fi
   4230     if ble/is-function ble/bin/xzcat; then
   4231       ble/complete/mandb/search-file/.check "$path/man1/$command.1.xz" && return 0
   4232       ble/complete/mandb/search-file/.check "$path/man1/$command.8.xz" && return 0
   4233     fi
   4234     if ble/is-function ble/bin/lzcat; then
   4235       ble/complete/mandb/search-file/.check "$path/man1/$command.1.lzma" && return 0
   4236       ble/complete/mandb/search-file/.check "$path/man1/$command.8.lzma" && return 0
   4237     fi
   4238   done
   4239   return 1
   4240 }
   4241 
   4242 if ble/bin#freeze-utility-path preconv; then
   4243   function ble/complete/mandb/.preconv { ble/bin/preconv; }
   4244 else
   4245   # macOS では preconv がない
   4246   function ble/complete/mandb/.preconv {
   4247     ble/bin/od -A n -t u1 -v | ble/bin/awk '
   4248       BEGIN {
   4249         ECHAR = 65533; # U+FFFD
   4250 
   4251         # Initialize table
   4252         byte = 0;
   4253         for (i = 0; byte < 128; byte++) { mtable[byte] = 0; vtable[byte] = i++; }
   4254         for (i = 0; byte < 192; byte++) { mtable[byte] = 0; vtable[byte] = ECHAR; }
   4255         for (i = 0; byte < 224; byte++) { mtable[byte] = 1; vtable[byte] = i++; }
   4256         for (i = 0; byte < 240; byte++) { mtable[byte] = 2; vtable[byte] = i++; }
   4257         for (i = 0; byte < 248; byte++) { mtable[byte] = 3; vtable[byte] = i++; }
   4258         for (i = 0; byte < 252; byte++) { mtable[byte] = 4; vtable[byte] = i++; }
   4259         for (i = 0; byte < 254; byte++) { mtable[byte] = 5; vtable[byte] = i++; }
   4260         for (i = 0; byte < 256; byte++) { mtable[byte] = 0; vtable[byte] = ECHAR; }
   4261 
   4262         M = 0; C = 0;
   4263       }
   4264       function put_uchar(uchar) {
   4265         if (uchar < 128)
   4266           printf("%c", uchar);
   4267         else
   4268           printf("\\[u%04X]", uchar);
   4269       }
   4270       function process_byte(byte) {
   4271         if (M) {
   4272           if (128 <= byte && byte < 192) {
   4273             C = C * 64 + byte % 64;
   4274             if (--M == 0) put_uchar(C);
   4275             return;
   4276           } else {
   4277             # while (M--) C *= 64; put_uchar(C);
   4278             put_uchar(ECHAR);
   4279             M = 0;
   4280           }
   4281         }
   4282 
   4283         M = mtable[byte];
   4284         C = vtable[byte];
   4285         if (M == 0) put_uchar(C);
   4286       }
   4287       { for (i = 1; i <= NF; i++) process_byte($i); }
   4288     '
   4289   }
   4290 fi
   4291 
   4292 _ble_complete_mandb_lang=
   4293 if ble/is-function ble/bin/groff; then
   4294   # ENCODING: UTF-8
   4295   _ble_complete_mandb_convert_type=man
   4296   function ble/complete/mandb/convert-mandoc {
   4297     if [[ $_ble_util_locale_encoding == UTF-8 ]]; then
   4298       ble/bin/groff -k -Tutf8 -man
   4299     else
   4300       ble/bin/groff -Tascii -man
   4301     fi
   4302   }
   4303 
   4304   # Note #D1551: macOS (groff-1.19.2) では groff -k も preconv も既定では存在しない
   4305   if [[ $OSTYPE == darwin* ]] && ! ble/bin/groff -k -Tutf8 -man &>/dev/null <<< 'α'; then
   4306     if ble/bin/groff -T utf8 -m man &>/dev/null <<< '\[u03B1]'; then
   4307       function ble/complete/mandb/convert-mandoc {
   4308         if [[ $_ble_util_locale_encoding == UTF-8 ]]; then
   4309           ble/complete/mandb/.preconv | ble/bin/groff -T utf8 -m man
   4310         else
   4311           ble/bin/groff -T ascii -m man
   4312         fi
   4313       }
   4314     else
   4315       _ble_complete_mandb_lang=C
   4316       function ble/complete/mandb/convert-mandoc {
   4317         ble/bin/groff -T ascii -m man
   4318       }
   4319     fi
   4320   fi
   4321 elif ble/is-function ble/bin/nroff; then
   4322   _ble_complete_mandb_convert_type=man
   4323   function ble/complete/mandb/convert-mandoc {
   4324     if [[ $_ble_util_locale_encoding == UTF-8 ]]; then
   4325       ble/bin/nroff -Tutf8 -man
   4326     else
   4327       ble/bin/groff -Tascii -man
   4328     fi
   4329   }
   4330 elif ble/is-function ble/bin/mandoc; then
   4331   # bsd
   4332   _ble_complete_mandb_convert_type=mdoc
   4333   function ble/complete/mandb/convert-mandoc {
   4334     ble/bin/mandoc -mdoc
   4335   }
   4336 fi
   4337 
   4338 function ble/complete/mandb/.generate-cache-from-man {
   4339   ble/is-function ble/bin/man &&
   4340     ble/is-function ble/complete/mandb/convert-mandoc || return 1
   4341 
   4342   local command=$1
   4343   local ret
   4344   ble/complete/mandb/search-file "$command" || return 1
   4345   local LC_ALL= LC_COLLATE=C 2>/dev/null
   4346   local path=$ret
   4347   case $ret in
   4348   (*.gz)       ble/bin/gzip -cd "$path" ;;
   4349   (*.bz|*.bz2) ble/bin/bzcat "$path" ;;
   4350   (*.lzma)     ble/bin/lzcat "$path" ;;
   4351   (*.xz)       ble/bin/xzcat "$path" ;;
   4352   (*)          ble/bin/cat "$path" ;;
   4353   esac | ble/bin/awk -v type="$_ble_complete_mandb_convert_type" '
   4354     BEGIN {
   4355       g_keys_count = 0;
   4356       g_desc = "";
   4357       if (type == "man") {
   4358         print ".TH __ble_ignore__ 1 __ble_ignore__ __ble_ignore__";
   4359         print ".ll 9999"
   4360         topic_start = ".TP";
   4361       }
   4362       mode = "begin";
   4363 
   4364       fmt3_state = "";
   4365       fmt5_state = "";
   4366       fmt6_state = "";
   4367     }
   4368     function output_pair(key, desc) {
   4369       print "";
   4370       print "__ble_key__";
   4371       if (topic_start != "") print topic_start;
   4372       print key;
   4373       print "";
   4374       print "__ble_desc__";
   4375       print "";
   4376       print desc;
   4377     }
   4378     function flush_topic(_, i) {
   4379       if (g_keys_count != 0) {
   4380         for (i = 0; i < g_keys_count; i++)
   4381           output_pair(g_keys[i], g_desc);
   4382       }
   4383       g_keys_count = 0;
   4384       g_desc = "";
   4385 
   4386       fmt3_flush();
   4387       fmt5_state = "";
   4388       fmt6_flush();
   4389     }
   4390 
   4391     # ".Dd" seems to be the include directive for macros?
   4392     # ".Nm" (in mdoc) specifies the name of the target the man page describes
   4393     mode == "begin" && /^\.(Dd|Nm)['"$_ble_term_space"']/ {
   4394       if (type == "man" && /^\.Dd['"$_ble_term_space"']+\$Mdoc/) topic_start = "";
   4395       print $0;
   4396     }
   4397 
   4398     function register_key(key) {
   4399       g_keys[g_keys_count++] = key;
   4400       g_desc = "";
   4401     }
   4402 
   4403     # Comment: [.ig \n comments \n ..]
   4404     /^\.ig/ { mode = "ignore"; next; }
   4405     mode == "ignore" {
   4406       if (/^\.\.['"$_ble_term_space"']*/) mode = "none";
   4407       next;
   4408     }
   4409 
   4410     {
   4411       sub(/['"$_ble_term_space"']+$/, "");
   4412       REQ = match($0, /^\.[_a-zA-Z0-9]+/) ? substr($0, 2, RLENGTH - 1) : "";
   4413     }
   4414 
   4415     REQ ~ /^(S[Ss]|S[Hh]|Pp)$/ { flush_topic(); next; }
   4416 
   4417     #--------------------------------------------------------------------------
   4418     # Format #5: [.PP \n key \n .RS \n desc \n .RE]
   4419     # used by "ping".
   4420 
   4421     REQ == "PP" {
   4422       flush_topic();
   4423       fmt5_state = "key";
   4424       fmt5_key = "";
   4425       fmt5_desc = "";
   4426       next;
   4427     }
   4428 
   4429     fmt5_state {
   4430       if (fmt5_state == "key") {
   4431         if (/^\.RS([^_a-zA-Z0-9]|$)/)
   4432           fmt5_state = "desc";
   4433         else if (/^\.RE([^_a-zA-Z0-9]|$)/)
   4434           fmt5_state = "none";
   4435         else
   4436           fmt5_key = (fmt5_key ? "\n" : "") $0;
   4437       } else if (fmt5_state == "desc") {
   4438         if (/^\.RE([^_a-zA-Z0-9]|$)/) {
   4439           register_key(fmt5_key);
   4440           g_desc = fmt5_desc;
   4441           flush_topic();
   4442           fmt5_state = "";
   4443         } else
   4444           fmt5_desc = (fmt5_desc ? "\n" : "") $0;
   4445       }
   4446     }
   4447 
   4448     #--------------------------------------------------------------------------
   4449     # Format #3: [.HP \n keys \n .IP \n desc]
   4450     # GNU sed seems to use this format.
   4451     # GNU coreutils mv seems to contain [.HP \n key      desc ] (for option "-b")
   4452 
   4453     REQ == "HP" {
   4454       flush_topic();
   4455       fmt3_state = "key";
   4456       fmt3_key_count = 0;
   4457       fmt3_desc = "";
   4458       next;
   4459     }
   4460 
   4461     function fmt3_process(_, key) {
   4462       if (REQ == "TP") { fmt3_flush(); return; }
   4463       if (REQ == "PD") return;
   4464 
   4465       if (fmt3_state == "key") {
   4466         if (REQ == "IP") { fmt3_state = "desc"; return; }
   4467         if (match($0, /(	|    )['"$_ble_term_space"']*/)) {
   4468           fmt3_keys[fmt3_key_count++] = substr($0, 1, RSTART - 1);
   4469           fmt3_desc = substr($0, RSTART + RLENGTH);
   4470           fmt3_state = "desc";
   4471         } else {
   4472           fmt3_keys[fmt3_key_count++] = $0;
   4473         }
   4474       } else if (fmt3_state == "desc") {
   4475         if (fmt3_desc != "") fmt3_desc = fmt3_desc "\n";
   4476         fmt3_desc = fmt3_desc $0;
   4477       }
   4478     }
   4479     function fmt3_flush(_, i) {
   4480       if (fmt3_state == "desc" && fmt3_key_count > 0) {
   4481         for (i = 0; i < fmt3_key_count; i++)
   4482           register_key(fmt3_keys[i]);
   4483         g_desc = fmt3_desc;
   4484       }
   4485       fmt3_state = "";
   4486       fmt3_key_count = 0;
   4487       fmt3_desc = "";
   4488     }
   4489 
   4490     fmt3_state { fmt3_process(); }
   4491 
   4492     #--------------------------------------------------------------------------
   4493     # Format #4: [[.IP "key" 4 \n .IX Item "..."]+ \n .PD \n desc]
   4494     # This format is used by "wget".
   4495 
   4496     /^\.IP['"$_ble_term_space"']+".*"(['"$_ble_term_space"']+[0-9]+)?$/ && fmt3_state != "key" {
   4497       fmt6_init();
   4498       fmt4_init();
   4499       next;
   4500     }
   4501 
   4502     function fmt4_init() {
   4503       if (mode != "fmt4_desc")
   4504         if (!(g_keys_count && g_desc == "")) flush_topic();
   4505 
   4506       gsub(/^\.IP['"$_ble_term_space"']+"|"(['"$_ble_term_space"']+[0-9]+)?$/, "");
   4507       register_key($0);
   4508       mode = "fmt4_desc";
   4509     }
   4510     mode == "fmt4_desc" {
   4511       if ($0 == "") { flush_topic(); mode = "none"; next; }
   4512 
   4513       # fish has a special format of [.IP "\(bu" 2 \n keys desc]
   4514       if (g_keys_count == 1 && g_keys[0] == "\\(bu" && match($0, /^\\fC[^\\]+\\fP( or \\fC[^\\]+\\fP)?/) > 0) {
   4515         _key = substr($0, 1, RLENGTH);
   4516         _desc = substr($0, RLENGTH + 1);
   4517         if (match(_key, / or \\fC[^\\]+\\fP/) > 0)
   4518           _key = substr(_key, 1, RSTART - 1) ", " substr(_key, RSTART + 4);
   4519         g_keys[0] = _key;
   4520         g_desc = _desc;
   4521         next;
   4522       }
   4523 
   4524       if (REQ == "PD") next;
   4525       if (/^\.IX['"$_ble_term_space"']+Item['"$_ble_term_space"']+/) next;
   4526 
   4527       if (g_desc != "") g_desc = g_desc "\n";
   4528       g_desc = g_desc $0;
   4529     }
   4530 
   4531     #--------------------------------------------------------------------------
   4532     # Format #6: [[.IP "key" \n desc .IP]
   4533     # This format is used by "rsync".
   4534 
   4535     function fmt6_init() {
   4536       fmt6_flush();
   4537       fmt6_state = "desc"
   4538       fmt6_key = $0;
   4539       fmt6_desc = "";
   4540     }
   4541     fmt6_state {
   4542       if (REQ == "IX") {
   4543         # Exclude fmt4 case
   4544         fmt6_state = "";
   4545       } else if (REQ == "IP") {
   4546         fmt6_flush();
   4547       } else {
   4548         fmt6_desc = fmt6_desc $0 "\n";
   4549       }
   4550     }
   4551     function fmt6_flush() {
   4552       if (!fmt6_state) return;
   4553       fmt6_state = "";
   4554       if (fmt6_desc)
   4555         output_pair(fmt6_key, fmt6_desc);
   4556     }
   4557 
   4558     #--------------------------------------------------------------------------
   4559     # Format #2: [.It Fl key \n desc] or [.It Fl Xo \n key \n .Xc desc]
   4560     # This form was found in both "mdoc" and "man"
   4561     /^\.It Fl([^_a-zA-Z0-9]|$)/ {
   4562       if (g_keys_count && g_desc != "") flush_topic();
   4563       sub(/^\.It Fl/, ".Fl");
   4564       if ($0 ~ / Xo$/) {
   4565         g_current_key = $0;
   4566         mode = "fmt2_keyc"
   4567       } else {
   4568         register_key($0);
   4569         mode = "desc";
   4570       }
   4571       next;
   4572     }
   4573     mode == "fmt2_keyc" {
   4574       if (/^\.PD['"$_ble_term_space"']*([0-9]+['"$_ble_term_space"']*)?$/) next;
   4575       g_current_key = g_current_key "\n" $0;
   4576       if (REQ == "Xc") {
   4577         register_key(g_current_key);
   4578         mode = "desc";
   4579       }
   4580       next;
   4581     }
   4582     #--------------------------------------------------------------------------
   4583     # Format #1: [.TP \n key \n desc]
   4584     # Format #1: [.TP \n key   desc \n desc...]
   4585     # This is the typical format in "man".
   4586     type == "man" && REQ == "TP" {
   4587       if (g_keys_count && g_desc != "") flush_topic();
   4588       mode = "key1";
   4589       next;
   4590     }
   4591     mode == "key1" {
   4592       if (/^\.PD['"$_ble_term_space"']*([0-9]+['"$_ble_term_space"']*)?$/) next;
   4593 
   4594       # In Japanese version of "man ls", key and desc is separated by multiple
   4595       # spaces, where the number of spaces seem to vary from 5 to more than 10
   4596       # spaces.
   4597       if (match($0, /['"$_ble_term_space"']['"$_ble_term_space"']['"$_ble_term_space"']/) > 0) {
   4598         register_key(substr($0, 1, RSTART - 1));
   4599         g_desc = substr($0, RSTART);
   4600         sub(/^['"$_ble_term_space"']+/, "", g_desc);
   4601       } else {
   4602         register_key($0);
   4603       }
   4604 
   4605       mode = "desc";
   4606       next;
   4607     }
   4608     mode == "desc" {
   4609       if (REQ == "PD") next;
   4610 
   4611       if (g_desc != "") g_desc = g_desc "\n";
   4612       g_desc = g_desc $0;
   4613     }
   4614     #--------------------------------------------------------------------------
   4615 
   4616     END { flush_topic(); }
   4617   ' | ble/complete/mandb/convert-mandoc 2>/dev/null | ble/bin/awk -F "$_ble_term_FS" '
   4618     function flush_pair(_, i, desc, prev_opt) {
   4619       if (g_option_count) {
   4620         gsub(/\034/, "\x1b[7m^\\\x1b[27m", g_desc);
   4621         sub(/(\.  |; ).*/, ".", g_desc); # Long descriptions are truncated.
   4622 
   4623         for (i = 0; i < g_option_count; i++) {
   4624           desc = g_desc;
   4625 
   4626           # show a short option
   4627           if (i > 0 && g_options[i] ~ /^--/) {
   4628             prev_opt = g_options[i - 1];
   4629             sub(/\034.*/, "", prev_opt);
   4630             if (prev_opt ~ /^-[^-]$/)
   4631               desc = "\033[1m[\033[0;36m" prev_opt "\033[0;1m]\033[m " desc;
   4632           }
   4633 
   4634           print g_options[i] FS desc;
   4635         }
   4636       }
   4637       g_option_count = 0;
   4638       g_desc = "";
   4639     }
   4640 
   4641     function process_key(line, _, n, specs, i, spec, option, optarg, suffix) {
   4642       gsub(/^['"$_ble_term_space"']+|['"$_ble_term_space"']+$/, "", line);
   4643       if (line == "") return;
   4644 
   4645       gsub(/\x1b\[[ -?]*[@-~]/, "", line); # CSI seq
   4646       gsub(/\x1b[ -\/]*[0-~]/, "", line); # ESC seq
   4647       gsub(/\t/, "    ", line); # HT
   4648       gsub(/.\x08/, "", line); # CHAR BS
   4649       gsub(/\x0E/, "", line); # SO
   4650       gsub(/\x0F/, "", line); # SI
   4651       gsub(/[\x00-\x1F]/, "", line); # Give up all the other control chars
   4652       gsub(/^['"$_ble_term_space"']*|['"$_ble_term_space"']*$/, "", line);
   4653       gsub(/['"$_ble_term_space"']+/, " ", line);
   4654       if (line !~ /^[-+]./) return;
   4655 
   4656       n = split(line, specs, /,(['"$_ble_term_space"']+|$)| or /);
   4657       prev_optarg = "";
   4658       for (i = n; i > 0; i--) {
   4659         spec = specs[i];
   4660         sub(/,['"$_ble_term_space"']+$/, "", spec);
   4661 
   4662         # Exclude non-options.
   4663         # Exclude FS (\034) because it is used for separators in the cache format.
   4664         if (spec !~ /^[-+]/ || spec ~ /\034/) { specs[i] = ""; continue; }
   4665 
   4666         if (match(spec, /\[[:=]?|[:='"$_ble_term_space"']/)) {
   4667           option = substr(spec, 1, RSTART - 1);
   4668           optarg = substr(spec, RSTART);
   4669           suffix = substr(spec, RSTART + RLENGTH - 1, 1);
   4670           if (suffix == "[") suffix = "";
   4671           prev_optarg = optarg;
   4672         } else {
   4673           option = spec;
   4674           optarg = "";
   4675           suffix = " ";
   4676 
   4677           # Carry previous optarg
   4678           if (prev_optarg ~ /[A-Z]|<.+>/) {
   4679             optarg = prev_optarg;
   4680             if (option ~ /^[-+].$/) {
   4681               sub(/^\[=/, "[", optarg);
   4682               sub(/^=/, "", optarg);
   4683               sub(/^[^'"$_ble_term_space"'[]/, " &", optarg);
   4684             } else {
   4685               if (optarg ~ /^\[[^:=]/)
   4686                 sub(/^\[/, "[=", optarg);
   4687               else if (optarg ~ /^[^:='"$_ble_term_space"'[]/)
   4688                 optarg = " " optarg;
   4689             }
   4690 
   4691             if (match(optarg, /^\[[:=]?|^[:='"$_ble_term_space"']/)) {
   4692               suffix = substr(optarg, RSTART + RLENGTH - 1, 1);
   4693               if (suffix == "[") suffix = "";
   4694             }
   4695           }
   4696         }
   4697 
   4698         specs[i] = option FS optarg FS suffix;
   4699       }
   4700 
   4701       for (i = 1; i <= n; i++) {
   4702         if (specs[i] == "") continue;
   4703         option = substr(specs[i], 1, index(specs[i], FS) - 1);
   4704         if (!g_hash[option]++)
   4705           g_options[g_option_count++] = specs[i];
   4706       }
   4707     }
   4708 
   4709     function process_desc(line) {
   4710       gsub(/^['"$_ble_term_space"']*|['"$_ble_term_space"']*$/, "", line);
   4711       if (line == "") {
   4712         if (g_desc != "") return 0;
   4713         return 1;
   4714       }
   4715 
   4716       gsub(/['"$_ble_term_space"']['"$_ble_term_space"']+/, " ", line);
   4717       if (g_desc != "") g_desc = g_desc " ";
   4718       g_desc = g_desc line;
   4719       return 1;
   4720     }
   4721 
   4722     function process_string_fragment(str) {
   4723       if (mode == "key") {
   4724         process_key(str);
   4725       } else if (mode == "desc") {
   4726         if (!process_desc(str)) mode = "";
   4727       }
   4728     }
   4729 
   4730     function process_line(line, _, head, m0) {
   4731       while (match(line, /__ble_(key|desc)__/) > 0) {
   4732         head = substr(line, 1, RSTART - 1);
   4733         m0 = substr(line, RSTART, RLENGTH);
   4734         line = substr(line, RSTART + RLENGTH);
   4735 
   4736         process_string_fragment(head);
   4737 
   4738         if (m0 == "__ble_key__") {
   4739           flush_pair();
   4740           mode = "key";
   4741         } else {
   4742           mode = "desc";
   4743         }
   4744       }
   4745 
   4746       process_string_fragment(line);
   4747     }
   4748 
   4749     { process_line($0); }
   4750     END { flush_pair(); }
   4751   ' | ble/bin/sort -t "$_ble_term_FS" -k 1
   4752   ble/util/unlocal LC_COLLATE LC_ALL 2>/dev/null
   4753 }
   4754 
   4755 ## @fn ble/complete/mandb:help/generate-cache [opts]
   4756 function ble/complete/mandb:help/generate-cache {
   4757   local opts=$1
   4758   local -x cfg_usage= cfg_help=1 cfg_plus= cfg_plus_generate=
   4759   [[ :$opts: == *:mandb-help-usage:* ]] && cfg_usage=1
   4760   [[ :$opts: == *:mandb-usage:* ]] && cfg_usage=1 cfg_help=
   4761   ble/string#match ":$opts:" ':plus-options(=[^:]+)?:' &&
   4762     cfg_plus=1 cfg_plus_generate=${BASH_REMATCH[1]:1}
   4763 
   4764   local space=$' \t' # for #D1709 (WA gawk 4.0.2)
   4765   local rex_argsep='(\[?[:=]|  ?|\[)'
   4766   local rex_option='[-+](,|[^]:='$space',[]+)('$rex_argsep'(<[^<>]+>|\([^()]+\)|\[[^][]+\]|[^-'"$_ble_term_space"'、。][^'"$_ble_term_space"'、。]*))?([,'"$_ble_term_space"']|$)'
   4767   local LC_ALL= LC_COLLATE=C 2>/dev/null
   4768   ble/bin/awk -F "$_ble_term_FS" '
   4769     BEGIN {
   4770       cfg_help = ENVIRON["cfg_help"];
   4771       g_help_indent = -1;
   4772       g_help_score = -1; # score based on indent and the interval between the
   4773                          # option and desc. smaller is better.
   4774       g_help_keys_count = 0;
   4775       g_help_desc = "";
   4776 
   4777       cfg_usage = ENVIRON["cfg_usage"];
   4778       g_usage_count = 0;
   4779 
   4780       cfg_plus_generate = ENVIRON["cfg_plus_generate"];
   4781       cfg_plus = ENVIRON["cfg_plus"] cfg_plus_generate;
   4782 
   4783       entries_init();
   4784     }
   4785 
   4786     #--------------------------------------------------------------------------
   4787     # entries
   4788 
   4789     function entries_init() {
   4790       entries_count = 0;
   4791     }
   4792 
   4793     function entries_register(entry, score, _, name, ientry) {
   4794       name = entry;
   4795       sub(/'"$_ble_term_FS"'.*$/, "", name);
   4796       if (name ~ /^\+/ && !cfg_plus) return;
   4797 
   4798       if (entries_index[name] != "") {
   4799         if (score >= entries_score[name]) return;
   4800         ientry = entries_index[name];
   4801       } else {
   4802         ientry = entries_count++;
   4803         entries_keys[ientry] = name;
   4804       }
   4805 
   4806       entries_index[name] = ientry;
   4807       entries_entry[name] = entry;
   4808       entries_score[name] = score;
   4809     }
   4810 
   4811     function entries_dump(_, ientry, name) {
   4812       for (ientry = 0; ientry < entries_count; ientry++) {
   4813         name = entries_keys[ientry];
   4814         print entries_entry[name];
   4815       }
   4816     }
   4817 
   4818     #--------------------------------------------------------------------------
   4819 
   4820     function split_option_optarg_suffix(optspec, _, key, suffix, optarg) {
   4821       # Note: Skip options that contain FS (due to the limitation by the cache format)
   4822       if (index(optspec, FS) != 0) return "";
   4823 
   4824       if ((pos = match(optspec, /'"$rex_argsep"'/)) > 0) {
   4825         key = substr(optspec, 1, pos - 1);
   4826         suffix = substr(optspec, pos + RLENGTH - 1, 1);
   4827         if (suffix == "[") suffix = "";
   4828         optarg = substr(optspec, pos);
   4829       } else {
   4830         key = optspec;
   4831         optarg = "";
   4832         suffix = " ";
   4833       }
   4834 
   4835       # Note: Exclude option names containing non-option characters
   4836       if (key ~ /[^-+'"$_ble_complete_option_chars"']/) return "";
   4837 
   4838       return key FS optarg FS suffix;
   4839     }
   4840 
   4841     {
   4842       gsub(/\x1b\[[ -?]*[@-~]/, ""); # CSI seq
   4843       gsub(/\x1b[ -\/]*[0-~]/, ""); # ESC seq
   4844       gsub(/\t/, "    "); # HT
   4845       gsub(/[\x00-\x1F]/, ""); # Remove all the other C0 chars
   4846     }
   4847 
   4848     #--------------------------------------------------------------------------
   4849     # Generate + options without descriptions
   4850 
   4851     function generate_plus(_, i, n) {
   4852       if (!cfg_plus_generate) return;
   4853       n = length(cfg_plus_generate);
   4854       for (i = 1; i <= n; i++)
   4855         entries_register("+" substr(cfg_plus_generate, i, 1) FS FS FS, 999);
   4856     }
   4857 
   4858     #--------------------------------------------------------------------------
   4859     # Extract usage [-DEI] [-f[helo] | --prefix=PATH]
   4860 
   4861     function usage_parse(line, _, optspec, optspec1, option, optarg, n, i, o) {
   4862       while (match(line, /\[['"$_ble_term_space"']*([^][]|\[[^][]*\])+['"$_ble_term_space"']*\]/)) {
   4863         optspec = substr(line, RSTART + 1, RLENGTH - 2);
   4864         line = substr(line, RSTART + RLENGTH);
   4865 
   4866         # optspec: " -DEI | --prefix=PATH | ... ", etc.
   4867         while (match(optspec, /([^][|]|\[[^][]*\])+/)) {
   4868           optspec1 = substr(optspec, RSTART, RLENGTH);
   4869           optspec = substr(optspec, RSTART + RLENGTH);
   4870           gsub(/^['"$_ble_term_space"']+|['"$_ble_term_space"']+$/, "", optspec1);
   4871 
   4872           # optspec1: "--option optarg", "-f[optarg]", "-xzvf", etc.
   4873           if (match(optspec1, /^[-+][^]:='"$space"'[]+/)) {
   4874             option = substr(optspec1, RSTART, RLENGTH);
   4875             optarg = substr(optspec1, RSTART + RLENGTH);
   4876             n = RLENGTH;
   4877             if (option ~ /^-.*-/) {
   4878               if ((keyinfo = split_option_optarg_suffix(optspec1)) != "")
   4879                 g_usage[g_usage_count++] = keyinfo;
   4880             } else {
   4881               o = substr(option, 1, 1);
   4882               for (i = 2; i <= n; i++)
   4883                 if ((keyinfo = split_option_optarg_suffix(o substr(option, i, 1) optarg)) != "")
   4884                   g_usage[g_usage_count++] = keyinfo;
   4885             }
   4886           }
   4887         }
   4888       }
   4889     }
   4890     function usage_generate(_, i) {
   4891       for (i = 0; i < g_usage_count; i++)
   4892         entries_register(g_usage[i] FS, 999);
   4893     }
   4894 
   4895     cfg_usage {
   4896       if (NR <= 20 && (g_usage_start || $0 ~ /^[_a-zA-Z0-9]|^[^-'"$_ble_term_space"'][^'"$_ble_term_space"']*(: |:)/) ) {
   4897         g_usage_start = 1;
   4898         usage_parse($0);
   4899       } else if (/^['"$_ble_term_space"']*$/)
   4900         cfg_usage = 0;
   4901     }
   4902 
   4903     #--------------------------------------------------------------------------
   4904     # Extract option descriptions
   4905 
   4906     function get_indent(text, _, i, n, ret) {
   4907       ret = 0;
   4908       n = length(text);
   4909       for (i = 1; i <= n; i++) {
   4910         c = substr(text, i, 1);
   4911         if (c == " ")
   4912           ret++;
   4913         else if (c == "\t")
   4914           ret = (int(ret / 8) + 1) * 8;
   4915         else
   4916           break;
   4917       }
   4918       return ret;
   4919     }
   4920     function help_flush(_, i, desc, prev_opt) {
   4921       if (g_help_indent < 0) return;
   4922       for (i = 0; i < g_help_keys_count; i++) {
   4923         desc = g_help_desc;
   4924 
   4925         # show a short option
   4926         if (i > 0 && g_help_keys[i] ~ /^--/) {
   4927           prev_opt = g_help_keys[i - 1];
   4928           sub(/\034.*/, "", prev_opt);
   4929           if (prev_opt ~ /^-[^-]$/) {
   4930             # Note: This particular form of desc is used by
   4931             # ble/complete/mandb:bash-completion/_parse_help.advice.  When we
   4932             # change the format, the function also needs to be updated.
   4933             desc = "\033[1m[\033[0;36m" prev_opt "\033[0;1m]\033[m " desc;
   4934           }
   4935         }
   4936 
   4937         entries_register(g_help_keys[i] FS desc, g_help_score);
   4938       }
   4939       g_help_indent = -1;
   4940       g_help_keys_count = 0;
   4941       g_help_desc = "";
   4942     }
   4943     function help_start(keydef, _, key, keyinfo, keys, nkey, i, optarg) {
   4944       if (g_help_desc != "") help_flush();
   4945       g_help_indent = get_indent(keydef);
   4946       g_help_score = g_help_indent;
   4947 
   4948       nkey = 0;
   4949       for (;;) {
   4950         sub(/^,?['"$_ble_term_space"']+/, "", keydef);
   4951 
   4952         if (match(keydef, /^'"$rex_option"'/) <= 0) break;
   4953         key = substr(keydef, 1, RLENGTH);
   4954         keydef = substr(keydef, RLENGTH + 1);
   4955 
   4956         sub(/[,'"$_ble_term_space"']$/, "", key);
   4957         keys[nkey++] = key;
   4958       }
   4959 
   4960       # Copy optarg "-A, --accept=LIST" => "-A LIST, --accept=LIST"
   4961       if (nkey >= 2) {
   4962         optarg = "";
   4963         for (i = nkey; --i >= 0; ) {
   4964           if (match(keys[i], /'"$rex_argsep"'/) > 0) {
   4965             optarg = substr(keys[i], RSTART);
   4966             sub(/^['"$_ble_term_space"']+/, "", optarg);
   4967             if (optarg !~ /[A-Z]|<.+>/) optarg = "";
   4968           } else if (optarg != ""){
   4969             if (keys[i] ~ /^[-+].$/) {
   4970               optarg2 = optarg;
   4971               sub(/^\[=/, "[", optarg2);
   4972               sub(/^=/, "", optarg2);
   4973               sub(/^[^'"$_ble_term_space"'[]/, " &", optarg2);
   4974               keys[i] = keys[i] optarg2;
   4975             } else {
   4976               optarg2 = optarg;
   4977               if (optarg2 ~ /^\[[^:=]/)
   4978                 sub(/^\[/, "[=", optarg2);
   4979               else if (optarg2 ~ /^[^:='"$_ble_term_space"'[]/)
   4980                 optarg2 = " " optarg2;
   4981               keys[i] = keys[i] optarg2;
   4982             }
   4983           }
   4984         }
   4985       }
   4986 
   4987       for (i = 0; i < nkey; i++)
   4988         if ((keyinfo = split_option_optarg_suffix(keys[i])) != "")
   4989           g_help_keys[g_help_keys_count++] = keyinfo;
   4990     }
   4991     function help_append_desc(desc) {
   4992       gsub(/^['"$_ble_term_space"']+|['"$_ble_term_space"']$/, "", desc);
   4993       if (desc == "") return;
   4994       if (g_help_desc == "")
   4995         g_help_desc = desc;
   4996       else
   4997         g_help_desc = g_help_desc " " desc;
   4998     }
   4999 
   5000     # Note (#D1847): We here restrict the number of spaces between synonymous
   5001     # options within 2 or 3.  Note that "rex_option" already contains the
   5002     # trailing comma or space.
   5003     cfg_help && match($0, /^['"$_ble_term_space"']*'"$rex_option"'((['"$_ble_term_space"']['"$_ble_term_space"']?)?'"$rex_option"')*/) {
   5004       key = substr($0, 1, RLENGTH);
   5005       desc = substr($0, RLENGTH + 1);
   5006       if (desc ~ /^,/) next;
   5007       help_start(key);
   5008       help_append_desc(desc);
   5009       if (desc !~ /^['"$_ble_term_space"']/) g_help_score += 100;
   5010       next;
   5011     }
   5012     g_help_indent >= 0 {
   5013       sub(/['"$_ble_term_space"']+$/, "");
   5014       indent = get_indent($0);
   5015       if (indent <= g_help_indent)
   5016         help_flush();
   5017       else
   5018         help_append_desc($0);
   5019     }
   5020 
   5021     #--------------------------------------------------------------------------
   5022 
   5023     END {
   5024       help_flush();
   5025       usage_generate();
   5026       generate_plus();
   5027       entries_dump();
   5028     }
   5029   ' | ble/bin/sort -t "$_ble_term_FS" -k 1
   5030   ble/util/unlocal LC_COLLATE LC_ALL 2>/dev/null
   5031 }
   5032 
   5033 function ble/complete/mandb:bash-completion/inject {
   5034   if ble/is-function _comp_compgen_help; then
   5035     # bash-completion 2.12
   5036     ble/function#advice after _comp_compgen_help__get_help_lines 'ble/complete/mandb:bash-completion/_get_help_lines.advice' &&
   5037       ble/function#advice before _comp_longopt 'ble/complete/mandb:bash-completion/_parse_help.advice "${ADVICE_WORDS[1]}"' &&
   5038       function ble/complete/mandb:bash-completion/inject { return 0; }
   5039   elif ble/is-function _parse_help; then
   5040     ble/function#advice before _parse_help 'ble/complete/mandb:bash-completion/_parse_help.advice "${ADVICE_WORDS[1]}" "${ADVICE_WORDS[2]}"' &&
   5041       ble/function#advice before _longopt 'ble/complete/mandb:bash-completion/_parse_help.advice "${ADVICE_WORDS[1]}"' &&
   5042       ble/function#advice before _parse_usage 'ble/complete/mandb:bash-completion/_parse_help.advice "${ADVICE_WORDS[1]}" "${ADVICE_WORDS[2]}"' &&
   5043       function ble/complete/mandb:bash-completion/inject { return 0; }
   5044   fi
   5045 } 2>/dev/null # _parse_help が別の枠組みで定義されている事がある? #D1900
   5046 
   5047 ## @fn ble/string#hash-pjw text [size shift]
   5048 ##   @var[out] ret
   5049 function ble/string#hash-pjw {
   5050   local size=${2:-32}
   5051   local S=${3:-$(((size+7)/8))} # shift    4
   5052   local C=$((size-2*S))         # co-shift 24
   5053   local M=$(((1<<size-S)-1))    # mask     0x0FFFFFFF
   5054   local N=$(((1<<S)-1<<S))      # mask2    0x000000F0
   5055 
   5056   ble/util/s2bytes "$1"
   5057   local c h=0
   5058   for c in "${ret[@]}"; do
   5059     ((h=(h<<S)+c,h=(h^h>>C&N)&M))
   5060   done
   5061   ret=$h
   5062 }
   5063 
   5064 ## @fn ble/complete/mandb:bash-completion/.alloc-subcache command hash [opts]
   5065 ##   @var[out] ret
   5066 function ble/complete/mandb:bash-completion/.alloc-subcache {
   5067   ret=
   5068   [[ $_ble_attached ]] || return 1
   5069 
   5070   local command=$1 hash=$2 opts=$3
   5071   if [[ :$opts: == *:dequote:* ]]; then
   5072     ble/syntax:bash/simple-word/is-simple "$command" &&
   5073       ble/syntax:bash/simple-word/eval "$command" noglob &&
   5074       command=$ret
   5075   fi
   5076   [[ $command ]] || return 1
   5077 
   5078   [[ $command == ble*/* ]] || command=${1##*/}
   5079   ble/string#hash-pjw "$args" 64; local hash=$ret
   5080   local lc_messages=${LC_ALL:-${LC_MESSAGES:-${LANG:-C}}}
   5081   local mandb_cache_dir=$_ble_base_cache/complete.mandb/${lc_messages//'/'/%}
   5082   ble/util/sprintf ret '%s.%014x' "$mandb_cache_dir/_parse_help.d/$command" "$hash"
   5083 
   5084   [[ -s $ret && $ret -nt $_ble_base/lib/core-complete.sh ]] && return 1
   5085 
   5086   ble/util/mkd "${ret%/*}"
   5087 }
   5088 
   5089 ## @fn ble/complete/mandb:bash-completion/_parse_help.advice command args
   5090 function ble/complete/mandb:bash-completion/_parse_help.advice {
   5091   local cmd=$1 args=$2 func=$ADVICE_FUNCNAME
   5092   # 現在のコマンド名。 Note: ADVICE_WORDS には実際に現在補完しようとしているコ
   5093   # マンドとは異なるものが指定される場合があるので (例えば help や - 等) 信用で
   5094   # きない。
   5095   local command=${COMP_WORDS[0]-} hash="${ADVICE_WORDS[*]}" ret
   5096   ble/complete/mandb:bash-completion/.alloc-subcache "$command" "$hash" dequote || return 0
   5097   local subcache=$ret
   5098 
   5099   local default_option=--help help_opts=
   5100   [[ $func == _parse_usage ]] &&
   5101     default_option=--usage help_opts=mandb-usage
   5102 
   5103   if [[ ( $func == _parse_help || $func == _parse_usage ) && $cmd == - ]]; then
   5104     # 標準入力からの読み取り
   5105     ble/complete/mandb:help/generate-cache "$help_opts" >| "$subcache"
   5106 
   5107     # Note: _parse_help が読み取る筈だった内容を横取りしたので抽出した内容を標
   5108     # 準出力に出力する。但し、対応する long option がある short option は除外す
   5109     # る。
   5110     LC_ALL= LC_COLLATE=C ble/bin/awk -F "$_ble_term_FS" '
   5111       BEGIN { entry_count = 0; }
   5112       {
   5113         entries[entry_count++] = $1;
   5114 
   5115         # Assumption: the descriptions of long options have the form
   5116         # "[short_opt] desc".  The format is defined by
   5117         # ble/complete/mandb:help/generate-cache.
   5118         desc = $4;
   5119         gsub(/\033\[[ -?]*[@-~]/, "", desc);
   5120         if (match(desc, /^\[[^]'"$_ble_term_space"'[]*\] /) > 0) { # #D1709 safe
   5121           short_opt = substr(desc, 2, RLENGTH - 3);
   5122           excludes[short_opt] =1;
   5123         }
   5124       }
   5125       END {
   5126         for (i = 0; i < entry_count; i++)
   5127           if (!excludes[entries[i]])
   5128             print entries[i];
   5129       }
   5130     ' "$subcache" 2>/dev/null # suppress locale error #D1440
   5131   else
   5132     local cmd_args
   5133     ble/string#split-words cmd_args "${args:-$default_option}"
   5134     "$cmd" "${cmd_args[@]}" 2>&1 | ble/complete/mandb:help/generate-cache "$help_opts" >| "$subcache"
   5135   fi
   5136 }
   5137 
   5138 function ble/complete/mandb:bash-completion/_get_help_lines.advice {
   5139   ((${#_lines[@]})) || return 0
   5140 
   5141   # @var cmd
   5142   #   現在のコマンド名。Note: _comp_command_offset 等によって別のコマンドの補完
   5143   #   を呼び出している場合があるので ble.sh の用意する comp_words は信用できな
   5144   #   い。bash-completion の使っている _comp_args[0] または bash-completion が
   5145   #   上書きしている COMP_WORDS を参照する。
   5146   local cmd=${_comp_args[0]-${COMP_WORDS[0]-}} hash="${ADVICE_WORDS[*]}"
   5147   ble/complete/mandb:bash-completion/.alloc-subcache "$cmd" "$hash" dequote || return 0
   5148   local subcache=$ret
   5149 
   5150   local help_opts=
   5151   [[ ${ADVICE_FUNCNAME[1]} == *_usage ]] && help_opts=mandb-usage
   5152   printf '%s\n' "${_lines[@]}" | ble/complete/mandb:help/generate-cache "$help_opts" >| "$subcache"
   5153 }
   5154 
   5155 ## @fn ble/complete/mandb/generate-cache cmdname
   5156 ##   @var[out] ret
   5157 ##     キャッシュファイル名を返します。
   5158 function ble/complete/mandb/generate-cache {
   5159   local command=${1##*/}
   5160   local lc_messages=${LC_ALL:-${LC_MESSAGES:-${LANG:-C}}}
   5161   local mandb_cache_dir=$_ble_base_cache/complete.mandb/${lc_messages//'/'/%}
   5162   local fcache=$mandb_cache_dir/$command
   5163 
   5164   local cmdspec_opts; ble/cmdspec/opts#load "$command"
   5165   [[ :$cmdspec_opts: == *:no-options:* ]] && return 1
   5166 
   5167   # fcache_help
   5168   if ble/opts#extract-all-optargs "$cmdspec_opts" mandb-help --help; then
   5169     local -a helpspecs; helpspecs=("${ret[@]}")
   5170     local subcache=$mandb_cache_dir/help.d/$command
   5171     if ! [[ -s $subcache && $subcache -nt $_ble_base/lib/core-complete.sh ]]; then
   5172       ble/util/mkd "${subcache%/*}"
   5173       local helpspec
   5174       for helpspec in "${helpspecs[@]}"; do
   5175         if [[ $helpspec == %* ]]; then
   5176           builtin eval -- "${helpspec:1}"
   5177         elif [[ $helpspec == @* ]]; then
   5178           ble/util/print "${helpspec:1}"
   5179         else
   5180           ble/string#split-words helpspec "${helpspec#+}"
   5181           "$command" "${helpspec[@]}" 2>&1
   5182         fi
   5183       done | ble/complete/mandb:help/generate-cache "$cmdspec_opts" >| "$subcache"
   5184     fi
   5185   fi
   5186 
   5187   # fcache_man
   5188   if [[ :$cmdspec_opts: != *:mandb-disable-man:* ]] && ble/bin#has "$1"; then
   5189     local subcache=$mandb_cache_dir/man.d/$command
   5190     if ! [[ -s $subcache && $subcache -nt $_ble_base/lib/core-complete.sh ]]; then
   5191       ble/util/mkd "${subcache%/*}"
   5192       ble/complete/mandb/.generate-cache-from-man "$command" >| "$subcache"
   5193     fi
   5194   fi
   5195 
   5196   # collect available caches
   5197   local -a subcaches=()
   5198   local subcache update=
   5199   ble/complete/util/eval-pathname-expansion '"$mandb_cache_dir"/_parse_help.d/"$command".??????????????'
   5200   for subcache in "${ret[@]}" "$mandb_cache_dir"/{help,man}.d/"$command"; do
   5201     if [[ -s $subcache && $subcache -nt $_ble_base/lib/core-complete.sh ]]; then
   5202       ble/array#push subcaches "$subcache"
   5203       [[ $fcache -nt $subcache ]] || update=1
   5204     fi
   5205   done
   5206 
   5207   if [[ $update ]]; then
   5208     local -x exclude=
   5209     ble/opts#extract-last-optarg "$cmdspec_opts" mandb-exclude && exclude=$ret
   5210 
   5211     local fs=$_ble_term_FS
   5212     ble/bin/awk -F "$_ble_term_FS" '
   5213       BEGIN {
   5214         plus_count = 0;
   5215         nodesc_count = 0;
   5216         exclude = ENVIRON["exclude"];
   5217       }
   5218       function emit(name, entry) {
   5219         hash[name] = entry;
   5220         if (exclude != "" && name ~ exclude) return;
   5221         print entry;
   5222       }
   5223 
   5224       $4 == "" {
   5225         if ($1 ~ /^\+/) {
   5226           plus_name[plus_count] = $1;
   5227           plus_entry[plus_count] = $0;
   5228           plus_count++;
   5229         } else {
   5230           nodesc_name[nodesc_count] = $1;
   5231           nodesc_entry[nodesc_count] = $0;
   5232           nodesc_count++;
   5233         }
   5234         next;
   5235       }
   5236       !hash[$1] { emit($1, $0); }
   5237 
   5238       END {
   5239         # minus options
   5240         for (i = 0; i < nodesc_count; i++)
   5241           if (!hash[nodesc_name[i]])
   5242             emit(nodesc_name[i], nodesc_entry[i]);
   5243 
   5244         # plus options
   5245         for (i = 0; i < plus_count; i++) {
   5246           name = plus_name[i];
   5247           if (hash[name]) continue;
   5248 
   5249           split(plus_entry[i], record, FS);
   5250           optarg = record[2];
   5251           suffix = record[3];
   5252           desc = "";
   5253 
   5254           mname = name;
   5255           sub(/^\+/, "-", mname);
   5256           if (hash[mname]) {
   5257             if (!optarg) {
   5258               split(hash[mname], record, FS);
   5259               optarg = record[2];
   5260               suffix = record[3];
   5261             }
   5262 
   5263             desc = hash[mname];
   5264             sub(/^[^'$fs']*'$fs'[^'$fs']*'$fs'[^'$fs']*'$fs'/, "", desc);
   5265             if (desc) desc = "\033[1mReverse[\033[m " desc " \033[;1m]\033[m";
   5266           }
   5267 
   5268           if (!desc) desc = "reverse of \033[4m" mname "\033[m";
   5269           emit(name, name FS optarg FS suffix FS desc);
   5270         }
   5271       }
   5272     ' "${subcaches[@]}" >| "$fcache"
   5273   fi
   5274 
   5275   ret=$fcache
   5276   [[ -s $fcache ]]
   5277 }
   5278 function ble/complete/mandb/load-cache {
   5279   ret=()
   5280   ble/complete/mandb/generate-cache "$@" &&
   5281     ble/util/mapfile ret < "$ret"
   5282 }
   5283 
   5284 ## @fn ble/complete/source:option/.is-option-context args...
   5285 ##   args... に "--" などのオプション解釈を停止する様な引数が含まれて
   5286 ##   いないか判定します。
   5287 ##
   5288 ##   @param[in] args...
   5289 ##   @var[in] cmdspec_opts
   5290 ##
   5291 function ble/complete/source:option/.is-option-context {
   5292   #(($#)) || return 0
   5293 
   5294   local rexrej rexreq stopat
   5295   ble/progcolor/stop-option#init "$cmdspec_opts"
   5296   if [[ $stopat ]] && ((stopat<=$#)); then
   5297     return 1
   5298   elif [[ ! $rexrej$rexreq ]]; then
   5299     return 0
   5300   fi
   5301 
   5302   local word ret
   5303   for word; do
   5304     ble/syntax:bash/simple-word/is-simple "$word" &&
   5305       ble/syntax:bash/simple-word/eval "$word" noglob &&
   5306       ble/progcolor/stop-option#test "$ret" &&
   5307       return 1
   5308   done
   5309   return 0
   5310 }
   5311 
   5312 function ble/complete/source:option {
   5313   local opts=$1
   5314   if [[ :$opts: == *:empty:* ]]; then
   5315     # 空文字列に対する補完を明示的に実行
   5316     [[ ! $COMPV ]] || return 0
   5317   else
   5318     # /^[-+].*/ の時にだけ候補生成 (曖昧補完で最初の /^[-+]/ は補わない)
   5319     local rex='^-[-+'$_ble_complete_option_chars']*$|^\+[_'$_ble_complete_option_chars']*$'
   5320     [[ $COMPV =~ $rex ]] || return 0
   5321   fi
   5322 
   5323   local COMPS=$COMPS COMPV=$COMPV
   5324   ble/complete/source/reduce-compv-for-ambiguous-match
   5325   [[ :$comp_type: == *:[maA]:* ]] && local COMP2=$COMP1
   5326 
   5327   local comp_words comp_line comp_point comp_cword
   5328   ble/syntax:bash/extract-command "$COMP2" || return 1
   5329 
   5330   ble/complete/source:option/generate-for-command "${comp_words[@]::comp_cword}"
   5331 }
   5332 
   5333 ## @fn ble/complete/source:option/generate-for-command command prev_args...
   5334 ##   This function generates the option names based on man pages.
   5335 ##
   5336 ##   @param[in] command
   5337 ##     The command name
   5338 ##   @param[in] prev_args
   5339 ##     The previous arguments before the word we currently try to complete.
   5340 ##
   5341 ##   For example, when one would like to generate the option
   5342 ##   candidates for "cmd abc def ghi -xx[TAB]", command is "cmd", and
   5343 ##   prev_args are "abc" "def" "ghi".
   5344 ##
   5345 ##   @var[in] COMP1 COMP2 COMPV COMPS comp_type
   5346 ##     These variables carry the information on the completion
   5347 ##     context. [COMP1, COMP2] specifies the range of the complete
   5348 ##     target in the command-line text. COMPS is the word to
   5349 ##     complete. COMPV is, if available, its current value after
   5350 ##     evaluation. The variable "comp_type" contains additional flags
   5351 ##     for the completion context.
   5352 ##   @var[ref] cand_iloop
   5353 ##
   5354 function ble/complete/source:option/generate-for-command {
   5355   local cmd=$1 prev_args
   5356   prev_args=("${@:2}")
   5357 
   5358   local alias_checked=' '
   5359   while local ret; ! ble/complete/mandb/load-cache "$cmd"; do
   5360     alias_checked=$alias_checked$cmd' '
   5361     ble/alias#expand "$cmd" || return 1
   5362     local words; ble/string#split-words ret "$ret"; words=("${ret[@]}")
   5363 
   5364     # 変数代入は読み飛ばし
   5365     local iword=0 rex='^[_a-zA-Z][_a-zA-Z0-9]*\+?='
   5366     while [[ ${words[iword]} =~ $rex ]]; do ((iword++)); done
   5367     [[ ${words[iword]} && $alias_checked != *" ${words[iword]} "* ]] || return 1
   5368     prev_args=("${words[@]:iword+1}" "${prev_args[@]}")
   5369     cmd=${words[iret]}
   5370   done
   5371   local -a entries; entries=("${ret[@]}")
   5372 
   5373   local ret cmdspec_opts=
   5374   ble/syntax:bash/simple-word/is-simple "$cmd" &&
   5375     ble/syntax:bash/simple-word/eval "$cmd" noglob &&
   5376     ble/cmdspec/opts#load "$ret"
   5377   # "--" や非オプション引数など、オプション無効化条件をチェック
   5378   ble/complete/source:option/.is-option-context "${prev_args[@]}" || return 1
   5379 
   5380   local "${_ble_complete_yield_varnames[@]/%/=}" # WA #D1570 checked
   5381   ble/complete/cand/yield.initialize mandb
   5382   local entry fs=$_ble_term_FS has_desc=
   5383   for entry in "${entries[@]}"; do
   5384     ((cand_iloop++%bleopt_complete_polling_cycle==0)) &&
   5385       ble/complete/check-cancel && return 148
   5386     local CAND=${entry%%$fs*}
   5387     [[ $CAND == "$COMPV"* ]] || continue
   5388     ble/complete/cand/yield mandb "$CAND" "$entry"
   5389     [[ $entry == *"$fs"*"$fs"*"$fs"?* ]] && has_desc=1
   5390   done
   5391 
   5392   [[ $has_desc && :$opts: != *:empty:* ]] && bleopt complete_menu_style=desc
   5393 }
   5394 
   5395 #------------------------------------------------------------------------------
   5396 # source:argument
   5397 
   5398 ## @fn ble/complete/source:argument/.generate-user-defined-completion opts
   5399 ##   ユーザ定義の補完を実行します。ble/cmdinfo/complete:コマンド名
   5400 ##   という関数が定義されている場合はそれを使います。
   5401 ##   それ以外の場合は complete によって登録されているプログラム補完が使用されます。
   5402 ##
   5403 ##   @param[in] opts
   5404 ##     コロン区切りのオプションリストを指定します。
   5405 ##     initial ... 最初の単語(コマンド名)の補完である事を示します。
   5406 ##   @var[in] COMP1 COMP2
   5407 ##   @var[in] (variables set by ble/syntax/parse)
   5408 ##
   5409 function ble/complete/source:argument/.generate-user-defined-completion {
   5410   shopt -q progcomp || return 1
   5411 
   5412   [[ :$comp_type: == *:[maA]:* ]] && local COMP2=$COMP1
   5413 
   5414   local comp_words comp_line comp_point comp_cword
   5415   ble/syntax:bash/extract-command "$COMP2" || return 1
   5416 
   5417   # @var comp2_in_word 単語内のカーソルの位置
   5418   # @var comp1_in_word 単語内の補完開始点
   5419   local forward_words=
   5420   ((comp_cword)) && IFS=' ' builtin eval 'forward_words="${comp_words[*]::comp_cword} "'
   5421   local comp2_in_word=$((comp_point-${#forward_words}))
   5422   local comp1_in_word=$((comp2_in_word-(COMP2-COMP1)))
   5423 
   5424   # 単語の途中に補完開始点がある時、単語を分割する
   5425   if ((comp1_in_word>0)); then
   5426     local w=${comp_words[comp_cword]}
   5427     comp_words=("${comp_words[@]::comp_cword}" "${w::comp1_in_word}" "${w:comp1_in_word}" "${comp_words[@]:comp_cword+1}")
   5428     IFS=' ' builtin eval 'comp_line="${comp_words[*]}"'
   5429     ((comp_cword++,comp_point++))
   5430     ((comp2_in_word=COMP2-COMP1,comp1_in_word=0))
   5431   fi
   5432 
   5433   # 曖昧補完の場合は単語の内容を reduce する #D1413
   5434   if [[ $COMPV && :$comp_type: == *:[maA]:* ]]; then
   5435     local oword=${comp_words[comp_cword]::comp2_in_word} ins
   5436     local ins=; [[ :$comp_type: == *:a:* ]] && ins=${COMPV::1}
   5437 
   5438     # escape ins
   5439     local ret comps_flags= comps_fixed= # referenced in ble/complete/string#escape-for-completion-context
   5440     if [[ $oword ]]; then
   5441       # Note: 実は曖昧補完の時は COMP2=$COMP1 としていて、
   5442       #   更に COMP1 で単語分割しているのでここには入らない筈。
   5443       local simple_flags simple_ibrace
   5444       ble/syntax:bash/simple-word/reconstruct-incomplete-word "$oword" || return 1
   5445       comps_flags=v$simple_flags
   5446       ((${simple_ibrace%:*})) && comps_fixed=1
   5447     fi
   5448     ble/complete/string#escape-for-completion-context "$ins" c; ins=$ret
   5449     ble/util/unlocal comps_flags comps_fixed
   5450 
   5451     # rewrite
   5452     ((comp_point+=${#ins}))
   5453     comp_words=("${comp_words[@]::comp_cword}" "$oword$ins" "${comp_words[@]:comp_cword+1}")
   5454     IFS=' ' builtin eval 'comp_line="${comp_words[*]}"'
   5455     ((comp2_in_word+=${#ins}))
   5456   fi
   5457 
   5458   local opts=$1
   5459   if [[ :$opts: == *:initial:* ]]; then
   5460     ble/complete/progcomp/.compgen initial
   5461   else
   5462     ble/complete/progcomp "${comp_words[0]}"
   5463   fi
   5464 }
   5465 
   5466 function ble/complete/source:argument/generate {
   5467   local old_cand_count=$cand_count
   5468 
   5469   #----------------------------------------------------------------------------
   5470   # 1. Attempt user-defined completion
   5471   ble/complete/source:argument/.generate-user-defined-completion; local ext=$?
   5472   ((ext==148||cand_count>old_cand_count)) && return "$ext"
   5473   [[ $comp_opts == *:ble/no-default:* ]] && return "$ext"
   5474 
   5475   #----------------------------------------------------------------------------
   5476   # 2. Attempt built-in argument completion
   5477 
   5478   # "-option" の時は complete options based on mandb
   5479   ble/complete/source:option; local ext=$?
   5480   ((ext==148)) && return "$ext"
   5481 
   5482   # 候補が見付からない場合 (または曖昧補完で COMPV に / が含まれる場合)
   5483   if [[ $comp_opts == *:dirnames:* ]]; then
   5484     ble/complete/source:dir
   5485   else
   5486     # filenames, default, bashdefault
   5487     ble/complete/source:file
   5488   fi; local ext=$?
   5489   ((ext==148)) && return "$ext"
   5490 
   5491   # 空文字列に対するオプション生成はファイル名よりも後で試みる
   5492   ble/complete/source:option empty; local ext=$?
   5493   ((ext==148||cand_count>old_cand_count)) && return "$ext"
   5494 
   5495   #----------------------------------------------------------------------------
   5496   # 3. Attempt rhs completion
   5497 
   5498   if local rex='^/?[-_a-zA-Z0-9.]+\+?[:=]|^-[^-/=:]'; [[ $COMPV =~ $rex ]]; then
   5499     # var=filename --option=filename /I:filename など。
   5500     local prefix=$BASH_REMATCH value=${COMPV:${#BASH_REMATCH}}
   5501     local COMP_PREFIX=$prefix
   5502     [[ :$comp_type: != *:[maA]:* && $value =~ ^.+/ ]] &&
   5503       COMP_PREFIX=$prefix${BASH_REMATCH[0]}
   5504 
   5505     local ret cand "${_ble_complete_yield_varnames[@]/%/=}" # WA #D1570 checked
   5506     ble/complete/source:file/.construct-pathname-pattern "$value"
   5507     ble/complete/util/eval-pathname-expansion "$ret"; (($?==148)) && return 148
   5508     ble/complete/source/test-limit "${#ret[@]}" || return 1
   5509     ble/complete/cand/yield.initialize file_rhs
   5510     for cand in "${ret[@]}"; do
   5511       ((cand_iloop++%bleopt_complete_polling_cycle==0)) && ble/complete/check-cancel && return 148
   5512       [[ -e $cand || -h $cand ]] || continue
   5513       [[ $FIGNORE ]] && ! ble/complete/.fignore/filter "$cand" && continue
   5514       ble/complete/cand/yield file_rhs "$prefix$cand" "$prefix"
   5515     done
   5516   fi
   5517 
   5518   ((cand_count>old_cand_count))
   5519 }
   5520 
   5521 function ble/complete/source:argument {
   5522   local comp_opts=:
   5523 
   5524   # failglob で展開に失敗した時は * を付加して再度展開を試みる
   5525   if [[ $comps_flags == *f* && $COMPS != *\* && :$comp_type: != *:[maA]:* ]]; then
   5526     local ret simple_flags simple_ibrace
   5527     ble/syntax:bash/simple-word/reconstruct-incomplete-word "$COMPS"
   5528     ble/complete/source/eval-simple-word "$ret*" && ((${#ret[*]})) &&
   5529       ble/complete/cand/yield-filenames file "${ret[@]}"
   5530     (($?==148)) && return 148
   5531   fi
   5532 
   5533   ble/complete/source:argument/generate
   5534   local ext=$?
   5535   ((ext==148)) && return 148
   5536   [[ $comp_opts == *:ble/no-default:* ]] && return "$ext"
   5537 
   5538   ble/complete/source:sabbrev
   5539 }
   5540 
   5541 # source:variable
   5542 # source:user
   5543 # source:hostname
   5544 
   5545 function ble/complete/source/compgen {
   5546   [[ $comps_flags == *v* ]] || return 1
   5547   local COMPS=$COMPS COMPV=$COMPV
   5548   ble/complete/source/reduce-compv-for-ambiguous-match
   5549 
   5550   local compgen_action=$1
   5551   local action=$2
   5552   local data=$3
   5553 
   5554   local q="'" Q="'\''"
   5555   local compv_quoted="'${COMPV//$q/$Q}'"
   5556   local arr
   5557   ble/util/assign-array arr 'builtin compgen -A "$compgen_action" -- "$compv_quoted"'
   5558 
   5559   ble/complete/source/test-limit "${#arr[@]}" || return 1
   5560 
   5561   # 既に完全一致している場合は、より前の起点から補完させるために省略
   5562   [[ $1 != '=' && ${#arr[@]} == 1 && $arr == "$COMPV" ]] && return 0
   5563 
   5564   local cand "${_ble_complete_yield_varnames[@]/%/=}" # WA #D1570 checked
   5565   ble/complete/cand/yield.initialize "$action"
   5566   for cand in "${arr[@]}"; do
   5567     ((cand_iloop++%bleopt_complete_polling_cycle==0)) && ble/complete/check-cancel && return 148
   5568     ble/complete/cand/yield "$action" "$cand" "$data"
   5569   done
   5570 }
   5571 
   5572 function ble/complete/source:variable {
   5573   local data=
   5574   case $1 in
   5575   ('=') data=assignment ;;
   5576   ('b') data=braced ;;
   5577   ('a') data=arithmetic ;;
   5578   ('n') data=nosuffix ;;
   5579   ('w'|*) data=word ;;
   5580   esac
   5581   ble/complete/source/compgen variable variable "$data"
   5582 }
   5583 function ble/complete/source:user {
   5584   ble/complete/source/compgen user word
   5585 }
   5586 function ble/complete/source:hostname {
   5587   ble/complete/source/compgen hostname word
   5588 }
   5589 
   5590 #------------------------------------------------------------------------------
   5591 # context
   5592 
   5593 ## @fn  ble/complete/complete/determine-context-from-opts opts
   5594 ##   @param[in] opts
   5595 ##   @var[out] context
   5596 function ble/complete/complete/determine-context-from-opts {
   5597   local opts=$1
   5598   context=syntax
   5599   if local rex=':context=([^:]+):'; [[ :$opts: =~ $rex ]]; then
   5600     local rematch1=${BASH_REMATCH[1]}
   5601     if ble/is-function ble/complete/context:"$rematch1"/generate-sources; then
   5602       context=$rematch1
   5603     else
   5604       ble/util/print "ble/widget/complete: unknown context '$rematch1'" >&2
   5605     fi
   5606   fi
   5607 }
   5608 ## @fn ble/complete/context/filter-prefix-sources
   5609 ##   @var[in] comp_text comp_index
   5610 ##   @var[in,out] sources
   5611 function ble/complete/context/filter-prefix-sources {
   5612   # 現在位置より前に始まる補完文脈だけを選択する
   5613   local -a filtered_sources=()
   5614   local src asrc
   5615   for src in "${sources[@]}"; do
   5616     ble/string#split-words asrc "$src"
   5617     local comp1=${asrc[1]}
   5618     ((comp1<comp_index)) &&
   5619       ble/array#push filtered_sources "$src"
   5620   done
   5621   sources=("${filtered_sources[@]}")
   5622   ((${#sources[@]}))
   5623 }
   5624 ## @fn ble/complete/context/overwrite-sources source
   5625 ##   @param[in] source
   5626 ##   @var[in] comp_text comp_index
   5627 ##   @var[in,out] comp_type
   5628 ##   @var[in,out] sources
   5629 function ble/complete/context/overwrite-sources {
   5630   local source_name=$1
   5631   local -a new_sources=()
   5632   local src asrc mark
   5633   for src in "${sources[@]}"; do
   5634     ble/string#split-words asrc "$src"
   5635     [[ ${mark[asrc[1]]} ]] && continue
   5636     ble/array#push new_sources "$source_name ${asrc[1]}"
   5637     mark[asrc[1]]=1
   5638   done
   5639   ((${#new_sources[@]})) ||
   5640     ble/array#push new_sources "$source_name $comp_index"
   5641   sources=("${new_sources[@]}")
   5642 }
   5643 
   5644 ## @fn ble/complete/context:syntax/generate-sources comp_text comp_index
   5645 ##   @var[in] comp_text comp_index
   5646 ##   @var[out] sources
   5647 function ble/complete/context:syntax/generate-sources {
   5648   ble/syntax/import
   5649   ble-edit/content/update-syntax
   5650   ble/cmdspec/initialize # load user configruation
   5651   ble/syntax/completion-context/generate "$comp_text" "$comp_index"
   5652   ((${#sources[@]}))
   5653 }
   5654 function ble/complete/context:filename/generate-sources {
   5655   ble/complete/context:syntax/generate-sources || return "$?"
   5656   ble/complete/context/overwrite-sources file
   5657 }
   5658 function ble/complete/context:command/generate-sources {
   5659   ble/complete/context:syntax/generate-sources || return "$?"
   5660   ble/complete/context/overwrite-sources command
   5661 }
   5662 function ble/complete/context:variable/generate-sources {
   5663   ble/complete/context:syntax/generate-sources || return "$?"
   5664   ble/complete/context/overwrite-sources variable
   5665 }
   5666 function ble/complete/context:username/generate-sources {
   5667   ble/complete/context:syntax/generate-sources || return "$?"
   5668   ble/complete/context/overwrite-sources user
   5669 }
   5670 function ble/complete/context:hostname/generate-sources {
   5671   ble/complete/context:syntax/generate-sources || return "$?"
   5672   ble/complete/context/overwrite-sources hostname
   5673 }
   5674 
   5675 function ble/complete/context:glob/generate-sources {
   5676   comp_type=$comp_type:raw
   5677   ble/complete/context:syntax/generate-sources || return "$?"
   5678   ble/complete/context/overwrite-sources glob
   5679 }
   5680 function ble/complete/source:glob {
   5681   [[ $comps_flags == *v* ]] || return 1
   5682   [[ :$comp_type: == *:[maA]:* ]] && return 1
   5683 
   5684   local pattern=$COMPV
   5685   ble/complete/source/eval-simple-word "$pattern"; (($?==148)) && return 148
   5686   if ((!${#ret[@]})) && [[ $pattern != *'*' ]]; then
   5687     ble/complete/source/eval-simple-word "$pattern*"; (($?==148)) && return 148
   5688   fi
   5689 
   5690   local cand action=file "${_ble_complete_yield_varnames[@]/%/=}" # WA #D1570 checked
   5691   ble/complete/cand/yield.initialize "$action"
   5692   for cand in "${ret[@]}"; do
   5693     ((cand_iloop++%bleopt_complete_polling_cycle==0)) && ble/complete/check-cancel && return 148
   5694     ble/complete/cand/yield "$action" "$cand"
   5695   done
   5696 }
   5697 
   5698 function ble/complete/context:dynamic-history/generate-sources {
   5699   comp_type=$comp_type:raw
   5700   ble/complete/context:syntax/generate-sources || return "$?"
   5701   ble/complete/context/overwrite-sources dynamic-history
   5702 }
   5703 function ble/complete/source:dynamic-history {
   5704   [[ $comps_flags == *v* ]] || return 1
   5705   [[ :$comp_type: == *:[maA]:* ]] && return 1
   5706   [[ $COMPV ]] || return 1
   5707 
   5708   local wordbreaks; ble/complete/get-wordbreaks
   5709   wordbreaks=${wordbreaks//$'\n'}
   5710 
   5711   local ret; ble/string#escape-for-extended-regex "$COMPV"
   5712   local rex_needle='(^|['$wordbreaks'])'$ret'[^'$wordbreaks']+'
   5713   local rex_wordbreaks='['$wordbreaks']'
   5714   ble/util/assign-array ret 'HISTTIMEFORMAT= builtin history | ble/bin/grep -Eo "$rex_needle" | ble/bin/sed "s/^$rex_wordbreaks//" | ble/bin/sort -u'
   5715 
   5716   local cand action=literal-word "${_ble_complete_yield_varnames[@]/%/=}" # WA #D1570 checked
   5717   ble/complete/cand/yield.initialize "$action"
   5718   for cand in "${ret[@]}"; do
   5719     ((cand_iloop++%bleopt_complete_polling_cycle==0)) && ble/complete/check-cancel && return 148
   5720     ble/complete/cand/yield "$action" "$cand"
   5721   done
   5722 }
   5723 
   5724 # 
   5725 #==============================================================================
   5726 # 候補生成
   5727 
   5728 ## @var[out] cand_count
   5729 ##   候補の数
   5730 ## @arr[out] cand_cand
   5731 ##   候補文字列
   5732 ## @arr[out] cand_word
   5733 ##   挿入文字列 (~ エスケープされた候補文字列)
   5734 ##
   5735 ## @arr[out] cand_pack
   5736 ##   補完候補のデータを一つの配列に纏めたもの。
   5737 ##   要素を使用する際は以下の様に変数に展開して使う。
   5738 ##
   5739 ##     local "${_ble_complete_cand_varnames[@]/%/=}" # WA #D1570 checked
   5740 ##     ble/complete/cand/unpack "${cand_pack[0]}"
   5741 ##
   5742 ##   先頭に ACTION が格納されているので
   5743 ##   ACTION だけ参照する場合には以下の様にする。
   5744 ##
   5745 ##     local ACTION=${cand_pack[0]%%:*}
   5746 ##
   5747 
   5748 ## @fn ble/complete/util/construct-ambiguous-regex text fixlen
   5749 ##   曖昧一致に使う正規表現を生成します。
   5750 ##   @param[in] text
   5751 ##   @param[in,out] fixlen=1
   5752 ##   @var[in] comp_type
   5753 ##   @var[out] ret
   5754 function ble/complete/util/construct-ambiguous-regex {
   5755   local text=$1 fixlen=${2:-1}
   5756   local opt_icase=; [[ :$comp_type: == *:i:* ]] && opt_icase=1
   5757   local -a buff=()
   5758   local i=0 n=${#text} ch=
   5759   for ((i=0;i<n;i++)); do
   5760     ((i>=fixlen)) && ble/array#push buff '.*'
   5761     ch=${text:i:1}
   5762     if [[ $ch == [a-zA-Z] ]]; then
   5763       if [[ $opt_icase ]]; then
   5764         ble/string#toggle-case "$ch"
   5765         ch=[$ch$ret]
   5766       fi
   5767     else
   5768       ble/string#escape-for-extended-regex "$ch"; ch=$ret
   5769     fi
   5770     ble/array#push buff "$ch"
   5771   done
   5772   IFS= builtin eval 'ret="${buff[*]}"'
   5773 }
   5774 ## @fn ble/complete/util/construct-glob-pattern text
   5775 ##   部分一致に使うグロブを生成します。
   5776 function ble/complete/util/construct-glob-pattern {
   5777   local text=$1
   5778   if [[ :$comp_type: == *:i:* ]]; then
   5779     local i n=${#text} c
   5780     local -a buff=()
   5781     for ((i=0;i<n;i++)); do
   5782       c=${text:i:1}
   5783       if [[ $c == [a-zA-Z] ]]; then
   5784         ble/string#toggle-case "$c"
   5785         c=[$c$ret]
   5786       else
   5787         ble/string#escape-for-bash-glob "$c"; c=$ret
   5788       fi
   5789       ble/array#push buff "$c"
   5790     done
   5791     IFS= builtin eval 'ret="${buff[*]}"'
   5792   else
   5793     ble/string#escape-for-bash-glob "$1"
   5794   fi
   5795 }
   5796 
   5797 
   5798 function ble/complete/.fignore/prepare {
   5799   comp_fignore=()
   5800   local i=0 leaf tmp
   5801   ble/string#split tmp ':' "$FIGNORE"
   5802   for leaf in "${tmp[@]}"; do
   5803     [[ $leaf ]] && comp_fignore[i++]="$leaf"
   5804   done
   5805 }
   5806 function ble/complete/.fignore/filter {
   5807   local pat
   5808   for pat in "${comp_fignore[@]}"; do
   5809     [[ $1 == *"$pat" ]] && return 1
   5810   done
   5811   return 0
   5812 }
   5813 
   5814 ## @fn ble/complete/candidates/.pick-nearest-sources
   5815 ##   一番開始点に近い補完源の一覧を求めます。
   5816 ##
   5817 ##   @var[in] comp_index
   5818 ##   @arr[in,out] remaining_sources
   5819 ##   @arr[out]    nearest_sources
   5820 ##   @var[out] COMP1 COMP2
   5821 ##     補完範囲
   5822 ##   @var[out] COMPS
   5823 ##     補完範囲の (クオートが含まれうる) コマンド文字列
   5824 ##   @var[out] COMPV
   5825 ##     補完範囲のコマンド文字列が意味する実際の文字列
   5826 ##   @var[out] comps_flags comps_fixed
   5827 function ble/complete/candidates/.pick-nearest-sources {
   5828   COMP1= COMP2=$comp_index
   5829   nearest_sources=()
   5830 
   5831   local -a unused_sources=()
   5832   local src asrc
   5833   for src in "${remaining_sources[@]}"; do
   5834     ble/string#split-words asrc "$src"
   5835     if ((COMP1<asrc[1])); then
   5836       COMP1=${asrc[1]}
   5837       ble/array#push unused_sources "${nearest_sources[@]}"
   5838       nearest_sources=("$src")
   5839     elif ((COMP1==asrc[1])); then
   5840       ble/array#push nearest_sources "$src"
   5841     else
   5842       ble/array#push unused_sources "$src"
   5843     fi
   5844   done
   5845   remaining_sources=("${unused_sources[@]}")
   5846 
   5847   COMPS=${comp_text:COMP1:COMP2-COMP1}
   5848   comps_flags=
   5849   comps_fixed=('')
   5850 
   5851   if [[ ! $COMPS ]]; then
   5852     comps_flags=${comps_flags}v COMPV=
   5853   elif local ret simple_flags simple_ibrace; ble/syntax:bash/simple-word/reconstruct-incomplete-word "$COMPS"; then
   5854     local reconstructed=$ret
   5855     if [[ :$comp_type: == *:raw:* ]]; then
   5856       # 展開前の値を COMPV に格納する。ブレース展開内部の場合は失敗
   5857       if ((${simple_ibrace%:*})); then
   5858         COMPV=
   5859       else
   5860         comps_flags=$comps_flags${simple_flags}v
   5861         COMPV=$reconstructed
   5862       fi
   5863     elif ble/complete/source/eval-simple-word "$reconstructed"; local ext=$?; ((ext==148)) && return 148; ((ext==0)); then
   5864       # 展開後の値を COMPV に格納する (既定)
   5865       COMPV=("${ret[@]}")
   5866       comps_flags=$comps_flags${simple_flags}v
   5867 
   5868       if ((${simple_ibrace%:*})); then
   5869         ble/complete/source/eval-simple-word "${reconstructed::${simple_ibrace#*:}}" single; (($?==148)) && return 148
   5870         comps_fixed=${simple_ibrace%:*}:$ret
   5871         comps_flags=${comps_flags}x
   5872       fi
   5873 
   5874       local path spec i s
   5875       ble/syntax:bash/simple-word/evaluate-path-spec "$reconstructed" '' noglob:fixlen="${simple_ibrace#*:}"
   5876       for ((i=0;i<${#spec[@]};i++)); do
   5877         s=${spec[i]}
   5878         [[ $s == "$comps_fixed" || $s == "$reconstructed" ]] && continue
   5879         ble/array#push comps_fixed "${#s}:${path[i]}"
   5880       done
   5881     else
   5882       # Note: failglob により simple-word/eval が失敗した時にここに来る。
   5883       COMPV=
   5884       comps_flags=$comps_flags${simple_flags}f
   5885     fi
   5886     [[ $COMPS =~ $rex_raw_paramx ]] && comps_flags=${comps_flags}p
   5887 
   5888   else
   5889     COMPV=
   5890   fi
   5891 }
   5892 
   5893 function ble/complete/candidates/clear {
   5894   cand_count=0
   5895   cand_cand=()
   5896   cand_word=()
   5897   cand_pack=()
   5898 }
   5899 
   5900 ## @fn ble/complete/candidates/filter-by-command command [start]
   5901 ##   生成された候補 (cand_*) に対して指定したコマンドを実行し、
   5902 ##   成功した候補のみを残して他を削除します。
   5903 ##   @param[in] command
   5904 ##   @param[in,opt] start
   5905 ##   @var[in,out] cand_count
   5906 ##   @arr[in,out] cand_{prop,cand,word,show,data}
   5907 ##   @exit
   5908 ##     ユーザ入力によって中断された時に 148 を返します。
   5909 function ble/complete/candidates/filter-by-command {
   5910   local command=$1 start=${2:-0}
   5911   # todo: 複数の配列に触る非効率な実装だが後で考える
   5912   local i j=$start
   5913   local -a prop=() cand=() word=() show=() data=()
   5914   for ((i=start;i<cand_count;i++)); do
   5915     ((i%bleopt_complete_polling_cycle==0)) && ble/complete/check-cancel && return 148
   5916     builtin eval -- "$command" || continue
   5917     cand[j]=${cand_cand[i]}
   5918     word[j]=${cand_word[i]}
   5919     data[j]=${cand_pack[i]}
   5920     ((j++))
   5921   done
   5922   cand_count=$j
   5923   cand_cand=("${cand[@]}")
   5924   cand_word=("${word[@]}")
   5925   cand_pack=("${data[@]}")
   5926 }
   5927 ## @fn ble/complete/candidates/.filter-by-regex rex_filter
   5928 ##   生成された候補 (cand_*) において指定した正規表現に一致する物だけを残します。
   5929 ##   @param[in] rex_filter
   5930 ##   @var[in,out] cand_count
   5931 ##   @arr[in,out] cand_{prop,cand,word,show,data}
   5932 ##   @exit
   5933 ##     ユーザ入力によって中断された時に 148 を返します。
   5934 function ble/complete/candidates/.filter-by-regex {
   5935   local rex_filter=$1
   5936   ble/complete/candidates/filter-by-command '[[ ${cand_cand[i]} =~ $rex_filter ]]'
   5937 }
   5938 function ble/complete/candidates/.filter-by-glob {
   5939   local globpat=$1
   5940   ble/complete/candidates/filter-by-command '[[ ${cand_cand[i]} == $globpat ]]'
   5941 }
   5942 function ble/complete/candidates/.filter-word-by-prefix {
   5943   local prefix=$1
   5944   ble/complete/candidates/filter-by-command '[[ ${cand_word[i]} == "$prefix"* ]]'
   5945 }
   5946 
   5947 function ble/complete/candidates/.initialize-rex_raw_paramx {
   5948   local element=$_ble_syntax_bash_simple_rex_element
   5949   local open_dquot=$_ble_syntax_bash_simple_rex_open_dquot
   5950   rex_raw_paramx='^('$element'*('$open_dquot')?)\$[_a-zA-Z][_a-zA-Z0-9]*$'
   5951 }
   5952 
   5953 ## 候補フィルタ (candidate filters) は以下の関数を通して実装される。
   5954 ##
   5955 ##   @fn ble/complete/candidates/filter:FILTER_TYPE/init compv
   5956 ##   @fn ble/complete/candidates/filter:FILTER_TYPE/test cand
   5957 ##     @var[in] comp_filter_type
   5958 ##     @var[in,out] comp_filter_pattern
   5959 ##
   5960 ##   @fn ble/complete/candidates/filter:FILTER_TYPE/match needle text
   5961 ##     @param[in] needle text
   5962 ##
   5963 ##   関数 ble/complete/candidates/filter:FILTER_TYPE/count-match-chars value
   5964 ##     @var[in] COMPV
   5965 ##
   5966 ## 使用するときには以下の関数を通して呼び出す (match, count-match-chars は直接呼び出す)。
   5967 ##
   5968 ##   @fn ble/complete/candidates/filter#init type compv
   5969 ##   @fn ble/complete/candidates/filter#test value
   5970 ##     @var[in,out] comp_filter_type
   5971 ##     @var[in,out] comp_filter_pattern
   5972 ##
   5973 function ble/complete/candidates/filter#init {
   5974   comp_filter_type=$1
   5975   comp_filter_pattern=
   5976   ble/complete/candidates/filter:"$comp_filter_type"/init "$2"
   5977 }
   5978 function ble/complete/candidates/filter#test {
   5979   ble/complete/candidates/filter:"$comp_filter_type"/test "$1"
   5980 }
   5981 
   5982 function ble/complete/candidates/filter:none/init { ble/complete/candidates/filter:head/init "$@"; }
   5983 function ble/complete/candidates/filter:none/test { true; }
   5984 function ble/complete/candidates/filter:none/count-match-chars { ble/complete/candidates/filter:head/count-match-chars "$@"; }
   5985 function ble/complete/candidates/filter:none/match { ble/complete/candidates/filter:head/match "$@"; }
   5986 
   5987 function ble/complete/candidates/filter:head/init {
   5988   local ret; ble/complete/util/construct-glob-pattern "$1"
   5989   comp_filter_pattern=$ret*
   5990 }
   5991 function ble/complete/candidates/filter:head/count-match-chars { # unused but for completeness
   5992   local value=$1 compv=$COMPV
   5993   if [[ :$comp_type: == *:i:* ]]; then
   5994     ble/string#tolower "$value"; value=$ret
   5995     ble/string#tolower "$compv"; compv=$ret
   5996   fi
   5997 
   5998   if [[ $value == "$compv"* ]]; then
   5999     ret=${#compv}
   6000   elif [[ $compv == "$value"* ]]; then
   6001     ret=${#value}
   6002   else
   6003     ret=0
   6004   fi
   6005 }
   6006 function ble/complete/candidates/filter:head/test { [[ $1 == $comp_filter_pattern ]]; }
   6007 
   6008 ## @fn ble/complete/candidates/filter:head/match needle text
   6009 ##   @arr[out] ret
   6010 function ble/complete/candidates/filter:head/match {
   6011   local needle=$1 text=$2
   6012   if [[ :$comp_type: == *:i:* ]]; then
   6013     ble/string#tolower "$needle"; needle=$ret
   6014     ble/string#tolower "$text"; text=$ret
   6015   fi
   6016 
   6017   if [[ ! $needle || ! $text ]]; then
   6018     ret=()
   6019   elif [[ $text == "$needle"* ]]; then
   6020     ret=(0 "${#needle}")
   6021     return 0
   6022   elif [[ $text == "${needle::${#text}}" ]]; then
   6023     ret=(0 "${#text}")
   6024     return 0
   6025   else
   6026     ret=()
   6027     return 1
   6028   fi
   6029 }
   6030 
   6031 function ble/complete/candidates/filter:substr/init {
   6032   local ret; ble/complete/util/construct-glob-pattern "$1"
   6033   comp_filter_pattern=*$ret*
   6034 }
   6035 function ble/complete/candidates/filter:substr/count-match-chars {
   6036   local value=$1 compv=$COMPV
   6037   if [[ :$comp_type: == *:i:* ]]; then
   6038     ble/string#tolower "$value"; value=$ret
   6039     ble/string#tolower "$compv"; compv=$ret
   6040   fi
   6041 
   6042   if [[ $value == *"$compv"* ]]; then
   6043     ret=${#compv}
   6044     return 0
   6045   fi
   6046   ble/complete/string#common-suffix-prefix "$value" "$compv"
   6047   ret=${#ret}
   6048 }
   6049 function ble/complete/candidates/filter:substr/test { [[ $1 == $comp_filter_pattern ]]; }
   6050 function ble/complete/candidates/filter:substr/match {
   6051   local needle=$1 text=$2
   6052   if [[ :$comp_type: == *:i:* ]]; then
   6053     ble/string#tolower "$needle"; needle=$ret
   6054     ble/string#tolower "$text"; text=$ret
   6055   fi
   6056 
   6057   if [[ ! $needle ]]; then
   6058     ret=()
   6059   elif [[ $text == *"$needle"* ]]; then
   6060     text=${text%%"$needle"*}
   6061     local beg=${#text}
   6062     local end=$((beg+${#needle}))
   6063     ret=("$beg" "$end")
   6064   elif ble/complete/string#common-suffix-prefix "$text" "$needle"; ((${#ret})); then
   6065     local end=${#text}
   6066     local beg=$((end-${#ret}))
   6067     ret=("$beg" "$end")
   6068   else
   6069     ret=()
   6070   fi
   6071 }
   6072 
   6073 function ble/complete/candidates/filter:hsubseq/.determine-fixlen {
   6074   fixlen=${1:-1}
   6075   if [[ $comps_fixed ]]; then
   6076     local compv_fixed_part=${comps_fixed#*:}
   6077     [[ $compv_fixed_part ]] && fixlen=${#compv_fixed_part}
   6078   fi
   6079 }
   6080 ## @fn ble/complete/candidates/filter:hsubseq/init compv [fixlen]
   6081 ##   @param[in] compv
   6082 ##   @param[in,opt] fixlen
   6083 ##   @var[in] comps_fixed
   6084 ##   @var[out] comp_filter_pattern
   6085 function ble/complete/candidates/filter:hsubseq/init {
   6086   local fixlen; ble/complete/candidates/filter:hsubseq/.determine-fixlen "$2"
   6087   local ret; ble/complete/util/construct-ambiguous-regex "$1" "$fixlen"
   6088   comp_filter_pattern=^$ret
   6089 }
   6090 ## @fn ble/complete/candidates/filter:hsubseq/count-match-chars value [fixlen]
   6091 ##   指定した文字列が COMPV の何処まで一致するかを返します。
   6092 ##   @var[out] ret
   6093 function ble/complete/candidates/filter:hsubseq/count-match-chars {
   6094   local value=$1 compv=$COMPV
   6095   if [[ :$comp_type: == *:i:* ]]; then
   6096     ble/string#tolower "$value"; value=$ret
   6097     ble/string#tolower "$compv"; compv=$ret
   6098   fi
   6099 
   6100   local fixlen
   6101   ble/complete/candidates/filter:hsubseq/.determine-fixlen "$2"
   6102   [[ $value == "${compv::fixlen}"* ]] || return 1
   6103 
   6104   value=${value:fixlen}
   6105   local i n=${#COMPV}
   6106   for ((i=fixlen;i<n;i++)); do
   6107     local a=${value%%"${compv:i:1}"*}
   6108     [[ $a == "$value" ]] && { ret=$i; return 0; }
   6109     value=${value:${#a}+1}
   6110   done
   6111   ret=$n
   6112 }
   6113 function ble/complete/candidates/filter:hsubseq/test { [[ $1 =~ $comp_filter_pattern ]]; }
   6114 function ble/complete/candidates/filter:hsubseq/match {
   6115   local needle=$1 text=$2
   6116   if [[ :$comp_type: == *:i:* ]]; then
   6117     ble/string#tolower "$needle"; needle=$ret
   6118     ble/string#tolower "$text"; text=$ret
   6119   fi
   6120 
   6121   local fixlen; ble/complete/candidates/filter:hsubseq/.determine-fixlen "$3"
   6122 
   6123   local prefix=${needle::fixlen}
   6124   if [[ $text != "$prefix"* ]]; then
   6125     if [[ $text && $text == "${prefix::${#text}}" ]]; then
   6126       ret=(0 "${#text}")
   6127     else
   6128       ret=()
   6129     fi
   6130     return 0
   6131   fi
   6132 
   6133   local pN=${#text} iN=${#needle}
   6134   local first=1
   6135   ret=()
   6136   while :; do
   6137     if [[ $first ]]; then
   6138       first=
   6139       local p0=0 p=${#prefix} i=${#prefix}
   6140     else
   6141       ((i<iN)) || return 0
   6142 
   6143       while ((p<pN)) && [[ ${text:p:1} != "${needle:i:1}" ]]; do
   6144         ((p++))
   6145       done
   6146       ((p<pN)) || return 1
   6147       p0=$p
   6148     fi
   6149 
   6150     while ((i<iN&&p<pN)) && [[ ${text:p:1} == "${needle:i:1}" ]]; do
   6151       ((p++,i++))
   6152     done
   6153     ((p0<p)) && ble/array#push ret "$p0" "$p"
   6154   done
   6155 }
   6156 
   6157 ## @fn ble/complete/candidates/filter:subseq/init compv
   6158 ##   @param[in] compv
   6159 ##   @var[in] comps_fixed
   6160 ##   @var[out] comp_filter_pattern
   6161 function ble/complete/candidates/filter:subseq/init {
   6162   [[ $comps_fixed ]] && return 1
   6163   ble/complete/candidates/filter:hsubseq/init "$1" 0
   6164 }
   6165 function ble/complete/candidates/filter:subseq/count-match-chars {
   6166   ble/complete/candidates/filter:hsubseq/count-match-chars "$1" 0
   6167 }
   6168 function ble/complete/candidates/filter:subseq/test { [[ $1 =~ $comp_filter_pattern ]]; }
   6169 function ble/complete/candidates/filter:subseq/match {
   6170   ble/complete/candidates/filter:hsubseq/match "$1" "$2" 0
   6171 }
   6172 
   6173 function ble/complete/candidates/generate-with-filter {
   6174   local filter_type=$1 opts=$2
   6175   local -a remaining_sources nearest_sources
   6176   remaining_sources=("${sources[@]}")
   6177 
   6178   local src asrc source
   6179   while ((${#remaining_sources[@]})); do
   6180     nearest_sources=()
   6181     ble/complete/candidates/.pick-nearest-sources; (($?==148)) && return 148
   6182 
   6183     [[ ! $COMPV && :$opts: == *:no-empty:* ]] && continue
   6184     local comp_filter_type
   6185     local comp_filter_pattern
   6186     ble/complete/candidates/filter#init "$filter_type" "$COMPV" || continue
   6187 
   6188     for src in "${nearest_sources[@]}"; do
   6189       ble/string#split-words asrc "$src"
   6190       ble/string#split source : "${asrc[0]}"
   6191 
   6192       local COMP_PREFIX= # 既定値 (yield-candidate で参照)
   6193       ble/complete/source:"${source[@]}"
   6194       ble/complete/check-cancel && return 148
   6195     done
   6196 
   6197     [[ $comps_fixed ]] &&
   6198       ble/complete/candidates/.filter-word-by-prefix "${COMPS::${comps_fixed%%:*}}"
   6199     ((cand_count)) && return 0
   6200   done
   6201   return 0
   6202 }
   6203 
   6204 function ble/complete/candidates/comp_type#read-rl-variables {
   6205   local _ble_local_rlvars; ble/util/rlvar#load
   6206   ble/util/rlvar#test completion-ignore-case 0 && comp_type=${comp_type}:i
   6207   ble/util/rlvar#test visible-stats 0 && comp_type=${comp_type}:vstat
   6208   ble/util/rlvar#test mark-directories 1 && comp_type=${comp_type}:markdir
   6209   ble/util/rlvar#test mark-symlinked-directories 1 && comp_type=${comp_type}:marksymdir
   6210   ble/util/rlvar#test match-hidden-files 1 && comp_type=${comp_type}:match-hidden
   6211   ble/util/rlvar#test menu-complete-display-prefix 0 && comp_type=${comp_type}:menu-show-prefix
   6212 
   6213   # color settings are always enabled
   6214   comp_type=$comp_type${bleopt_complete_menu_color:+:menu-color}
   6215   comp_type=$comp_type${bleopt_complete_menu_color_match:+:menu-color-match}
   6216 }
   6217 
   6218 ## @fn ble/complete/candidates/generate opts
   6219 ##   @param[in] opts
   6220 ##   @var[in] comp_text comp_index
   6221 ##   @arr[in] sources
   6222 ##   @var[out] COMP1 COMP2 COMPS COMPV
   6223 ##   @var[out] comp_type comps_flags comps_fixed
   6224 ##   @var[out] cand_count cand_cand cand_word cand_pack
   6225 ##   @var[in,out] cand_limit_reached
   6226 function ble/complete/candidates/generate {
   6227   local opts=$1
   6228   local flag_force_fignore=
   6229   local flag_source_filter=
   6230   local -a comp_fignore=()
   6231   if [[ $FIGNORE ]]; then
   6232     ble/complete/.fignore/prepare
   6233     ((${#comp_fignore[@]})) && shopt -q force_fignore && flag_force_fignore=1
   6234   fi
   6235 
   6236   local rex_raw_paramx
   6237   ble/complete/candidates/.initialize-rex_raw_paramx
   6238   ble/complete/candidates/comp_type#read-rl-variables
   6239 
   6240   local cand_iloop=0
   6241   ble/complete/candidates/clear
   6242   # #D1416 filter:none にするのは ~[TAB] の時など COMPV ではなく COMPS で補完したい事がある為
   6243   ble/complete/candidates/generate-with-filter none "$opts" || return "$?"
   6244   ((cand_count)) && return 0
   6245 
   6246   if [[ $bleopt_complete_ambiguous && $COMPV ]]; then
   6247     local original_comp_type=$comp_type
   6248     comp_type=${original_comp_type}:m
   6249     ble/complete/candidates/generate-with-filter substr "$opts" || return "$?"
   6250     ((cand_count)) && return 0
   6251     comp_type=${original_comp_type}:a
   6252     ble/complete/candidates/generate-with-filter hsubseq "$opts" || return "$?"
   6253     ((cand_count)) && return 0
   6254     comp_type=${original_comp_type}:A
   6255     ble/complete/candidates/generate-with-filter subseq "$opts" || return "$?"
   6256     ((cand_count)) && return 0
   6257     comp_type=$original_comp_type
   6258   fi
   6259 
   6260   return 0
   6261 }
   6262 
   6263 ## @fn ble/complete/candidates/determine-common-prefix/.apply-partial-comps
   6264 ##   @var[in] COMPS
   6265 ##   @var[in] comps_fixed
   6266 ##   @var[in,out] common
   6267 function ble/complete/candidates/determine-common-prefix/.apply-partial-comps {
   6268   local word0=$COMPS word1=$common fixed=
   6269   if [[ $comps_fixed ]]; then
   6270     local fixlen=${comps_fixed%%:*}
   6271     fixed=${word0::fixlen}
   6272     word0=${word0:fixlen}
   6273     word1=${word1:fixlen}
   6274   fi
   6275 
   6276   local ret spec path spec0 path0 spec1 path1
   6277   ble/complete/source/evaluate-path-spec "$word0"; (($?==148)) && return 148; spec0=("${spec[@]}") path0=("${path[@]}")
   6278   ble/complete/source/evaluate-path-spec "$word1"; (($?==148)) && return 148; spec1=("${spec[@]}") path1=("${path[@]}")
   6279   local i=${#path1[@]}
   6280   while ((i--)); do
   6281     if ble/array#last-index path0 "${path1[i]}"; then
   6282       local elem=${spec1[i]} # workaround bash-3.1 ${#arr[i]} bug
   6283       word1=${spec0[ret]}${word1:${#elem}}
   6284       break
   6285     fi
   6286   done
   6287   common=$fixed$word1
   6288 }
   6289 
   6290 # Note (#D1978): progcomp (syntax-raw) による単一確定の場合には遡って書き換わっ
   6291 #   ている場合でも、元の単語の部分を復元しようとはしない。
   6292 function ble/completion/candidates/determine-common-prefix/.is-progcomp-raw {
   6293   ((cand_count==1)) && [[ ${cand_pack[0]} == progcomp:*:ble/syntax-raw:* ]] || return 0
   6294 
   6295   # 念の為、本当に DATA に :ble/syntax-raw: が含まれている事を確認する
   6296   local "${_ble_complete_cand_varnames[@]/%/=}" # WA #D1570 checked
   6297   ble/complete/cand/unpack "${cand_pack[0]}"
   6298   [[ $DATA == *:ble/syntax-raw:* ]]
   6299 }
   6300 
   6301 ## @fn ble/complete/candidates/determine-common-prefix
   6302 ##   cand_* を元に common prefix を算出します。
   6303 ##   @var[in] cand_*
   6304 ##   @var[out] ret
   6305 function ble/complete/candidates/determine-common-prefix {
   6306   # 共通部分
   6307   local common=${cand_word[0]}
   6308   local clen=${#common}
   6309   if ((cand_count>1)); then
   6310     # set up ignore case
   6311     local unset_nocasematch= flag_tolower=
   6312     if [[ :$comp_type: == *:i:* ]]; then
   6313       if ((_ble_bash<30100)); then
   6314         flag_tolower=1
   6315         ble/string#tolower "$common"; common=$ret
   6316       else
   6317         unset_nocasematch=1
   6318         shopt -s nocasematch
   6319       fi
   6320     fi
   6321 
   6322     local word loop=0
   6323     for word in "${cand_word[@]:1}"; do
   6324       ((loop++%bleopt_complete_polling_cycle==0)) && ble/complete/check-cancel && break
   6325 
   6326       if [[ $flag_tolower ]]; then
   6327         ble/string#tolower "$word"; word=$ret
   6328       fi
   6329 
   6330       ((clen>${#word}&&(clen=${#word})))
   6331       while [[ ${word::clen} != "${common::clen}" ]]; do
   6332         ((clen--))
   6333       done
   6334       common=${common::clen}
   6335     done
   6336 
   6337     [[ $unset_nocasematch ]] && shopt -u nocasematch
   6338     ble/complete/check-cancel && return 148
   6339 
   6340     [[ $flag_tolower ]] && common=${cand_word[0]::${#common}}
   6341   fi
   6342 
   6343   if [[ $common != "$COMPS"* && ! ( $cand_count -eq 1 && $comp_type == *:i:* ) ]]; then
   6344     if ! ble/completion/candidates/determine-common-prefix/.is-progcomp-raw; then
   6345       # common を部分的に COMPS に置換する試み
   6346       # Note: ignore-case で一意確定の時は case を候補に合わせたいので COMPS に
   6347       #   は置換しない。
   6348       ble/complete/candidates/determine-common-prefix/.apply-partial-comps
   6349     fi
   6350   fi
   6351 
   6352   if ((cand_count>1)) && [[ $common != "$COMPS"* ]]; then
   6353     local common0=$common
   6354     common=$COMPS # 取り敢えず補完挿入をキャンセル
   6355 
   6356     if [[ :$comp_type: == *:[maAi]:* ]]; then
   6357       # 曖昧一致の時は遡って書き換えを起こし得る、
   6358       # 一致する部分までを置換し一致しなかった部分を末尾に追加する。
   6359 
   6360       local simple_flags simple_ibrace
   6361       if ble/syntax:bash/simple-word/reconstruct-incomplete-word "$common0"; then
   6362         local common_reconstructed=$ret
   6363         local value=$ret filter_type=head
   6364         case :$comp_type: in
   6365         (*:m:*) filter_type=substr ;;
   6366         (*:a:*) filter_type=hsubseq ;;
   6367         (*:A:*) filter_type=subseq ;;
   6368         esac
   6369 
   6370         local is_processed=
   6371         ble/complete/source/eval-simple-word "$common_reconstructed" single; local ext=$?
   6372         ((ext==148)) && return 148
   6373         if ((ext==0)) && ble/complete/candidates/filter:"$filter_type"/count-match-chars "$ret"; then
   6374           if [[ $filter_type == head ]] && ((ret<${#COMPV})); then
   6375             is_processed=1
   6376             # Note: #D1181 ここに来たという事は外部の枠組みで
   6377             #   生成された先頭一致しない候補があるという事。
   6378             #   入力済み文字列が失われてしまう危険性を承知の上と思われるので書き換えを許可する。
   6379             [[ $bleopt_complete_allow_reduction ]] && common=$common0
   6380           elif ((ret)); then
   6381             is_processed=1
   6382             ble/string#escape-for-bash-specialchars "${COMPV:ret}" c
   6383             common=$common0$ret
   6384           fi
   6385         fi
   6386 
   6387         # #D1417 チルダ展開やパス名展開など途中で切ると全く異なる展開になる物について
   6388         #   より正しく処理する為に、完全解ではないが notilde, noglob でも部分一致を調べる。
   6389         #
   6390         #   例えば既に ~nouser と入力して共通一致部分が ~ だった時に
   6391         #   ~ の何処までが ~nouser に部分一致するか調べる時、チルダ展開が有効だと
   6392         #   ~ が /home/user に展開されてから部分一致が調べられる為、
   6393         #   一致が起こらずに "~nouser" の全てが追加で挿入されて "~~nouser" になってしまう。
   6394         #   なのでチルダ展開・パス名展開を無効にして部分一致を試みる必要がある。
   6395         if [[ ! $is_processed ]] &&
   6396              local notilde=\'\' &&
   6397              ble/syntax:bash/simple-word/reconstruct-incomplete-word "$COMPS" &&
   6398              ble/syntax:bash/simple-word/eval "$notilde$ret" noglob &&
   6399              local compv_notilde=$ret &&
   6400              ble/syntax:bash/simple-word/eval "$notilde$common_reconstructed" noglob &&
   6401              local commonv_notilde=$ret &&
   6402              COMPV=$compv_notilde ble/complete/candidates/filter:"$filter_type"/count-match-chars "$commonv_notilde"
   6403         then
   6404           if [[ $filter_type == head ]] && ((ret<${#COMPV})); then
   6405             is_processed=1
   6406             [[ $bleopt_complete_allow_reduction ]] && common=$common0
   6407           elif ((ret)); then
   6408             # Note: 今の実装では展開結果に含まれている *?[ は全て glob として取
   6409             #   り扱う事になっている。つまり 'a*b' が曖昧部分一致した時には元々
   6410             #   の quote が外れて a*b になってしまうという事。これは現在の実装
   6411             #   の制限である。
   6412             is_processed=1
   6413             ble/string#escape-for-bash-specialchars "${compv_notilde:ret}" TG
   6414             common=$common0$ret
   6415           fi
   6416         fi
   6417 
   6418         [[ $is_processed ]] || common=$common0$COMPS
   6419       fi
   6420 
   6421     else
   6422       # Note: #D0768 文法的に単純であれば (構造を破壊しなければ) 遡って書き換えが起こることを許す。
   6423       # Note: #D1181 外部の枠組みで生成された先頭一致しない共通部分の時でも書き換えを許す。
   6424       if ble/syntax:bash/simple-word/is-simple-or-open-simple "$common"; then
   6425         local flag_reduction=
   6426         if [[ $bleopt_complete_allow_reduction ]]; then
   6427           flag_reduction=1
   6428         else
   6429           local simple_flags simple_ibrace
   6430           ble/syntax:bash/simple-word/reconstruct-incomplete-word "$common0" &&
   6431             ble/complete/source/eval-simple-word "$ret" single &&
   6432             [[ $ret == "$COMPV"* ]] &&
   6433             flag_reduction=1
   6434           (($?==148)) && return 148
   6435         fi
   6436 
   6437         [[ $flag_reduction ]] && common=$common0
   6438       fi
   6439     fi
   6440   fi
   6441 
   6442   ret=$common
   6443 }
   6444 
   6445 # 
   6446 #==============================================================================
   6447 # 候補一覧
   6448 
   6449 _ble_complete_menu_active=
   6450 _ble_complete_menu_style=
   6451 _ble_complete_menu0_beg=
   6452 _ble_complete_menu0_end=
   6453 _ble_complete_menu0_str=
   6454 _ble_complete_menu_common_part=
   6455 _ble_complete_menu0_comp=()
   6456 _ble_complete_menu0_pack=()
   6457 _ble_complete_menu_comp=()
   6458 
   6459 ## @fn ble/complete/menu-complete.class/render-item pack opts
   6460 ##   @param[in] pack
   6461 ##     cand_pack の要素と同様の形式の文字列です。
   6462 ##   @param[in] opts
   6463 ##     コロン区切りのオプションです。
   6464 ##     selected
   6465 ##       選択されている候補の描画シーケンスを生成します。
   6466 ##   @var[in,out] x y
   6467 ##   @var[out] ret
   6468 ##   @var[in] cols lines
   6469 ##   @var[in] _ble_complete_menu_common_part
   6470 function ble/complete/menu-complete.class/render-item {
   6471   local opts=$2
   6472 
   6473   # Note: select は menu 表示の文脈ではないので、
   6474   #   補完文脈を復元しなければ参照できない。
   6475   if [[ :$opts: == *:selected:* ]]; then
   6476     local COMP1=${_ble_complete_menu_comp[0]}
   6477     local COMP2=${_ble_complete_menu_comp[1]}
   6478     local COMPS=${_ble_complete_menu_comp[2]}
   6479     local COMPV=${_ble_complete_menu_comp[3]}
   6480     local comp_type=${_ble_complete_menu_comp[4]}
   6481     local comps_flags=${_ble_complete_menu0_comp[5]}
   6482     local comps_fixed=${_ble_complete_menu0_comp[6]}
   6483     local menu_common_part=$_ble_complete_menu_common_part
   6484   fi
   6485 
   6486   local "${_ble_complete_cand_varnames[@]/%/=}" # WA #D1570 checked
   6487   ble/complete/cand/unpack "$1"
   6488 
   6489   local prefix_len=$PREFIX_LEN
   6490   [[ :$comp_type: == *:menu-show-prefix:* ]] && prefix_len=0
   6491 
   6492   local filter_target=${CAND:prefix_len}
   6493   if [[ ! $filter_target ]]; then
   6494     ret=
   6495     return 0
   6496   fi
   6497 
   6498   # 色の設定・表示内容・前置詞・後置詞を取得
   6499   local g=0 show=$filter_target suffix= prefix=
   6500   ble/function#try ble/complete/action:"$ACTION"/init-menu-item
   6501   local g0=$g; [[ :$comp_type: == *:menu-color:* ]] || g0=0
   6502 
   6503   # 一致部分の抽出
   6504   local m
   6505   if [[ :$comp_type: == *:menu-color-match:* && $_ble_complete_menu_common_part && $show == *"$filter_target"* ]]; then
   6506     local filter_type=head
   6507     case :$comp_type: in
   6508     (*:m:*) filter_type=substr ;;
   6509     (*:a:*) filter_type=hsubseq ;;
   6510     (*:A:*) filter_type=subseq ;;
   6511     esac
   6512 
   6513     local needle=${_ble_complete_menu_common_part:prefix_len}
   6514     ble/complete/candidates/filter:"$filter_type"/match "$needle" "$filter_target"; m=("${ret[@]}")
   6515 
   6516     # 表示文字列の部分文字列で絞り込みが起こっている場合
   6517     if [[ $show != "$filter_target" ]]; then
   6518       local show_prefix=${show%%"$filter_target"*}
   6519       local offset=${#show_prefix}
   6520       local i n=${#m[@]}
   6521       for ((i=0;i<n;i++)); do ((m[i]+=offset)); done
   6522     fi
   6523   else
   6524     m=()
   6525   fi
   6526 
   6527   # 基本色の初期化 (Note: 高速化の為、直接 _ble_color_g2sgr を参照する)
   6528   local sgrN0= sgrN1= sgrB0= sgrB1=
   6529   [[ :$opts: == *:selected:* ]] && ((g0^=_ble_color_gflags_Revert))
   6530   ret=${_ble_color_g2sgr[g=g0]}
   6531   [[ $ret ]] || ble/color/g2sgr "$g"; sgrN0=$ret
   6532   ret=${_ble_color_g2sgr[g=g0^_ble_color_gflags_Revert]}
   6533   [[ $ret ]] || ble/color/g2sgr "$g"; sgrN1=$ret
   6534   if ((${#m[@]})); then
   6535     # 一致色の初期化
   6536     ret=${_ble_color_g2sgr[g=g0|_ble_color_gflags_Bold]}
   6537     [[ $ret ]] || ble/color/g2sgr "$g"; sgrB0=$ret
   6538     ret=${_ble_color_g2sgr[g=(g0|_ble_color_gflags_Bold)^_ble_color_gflags_Revert]}
   6539     [[ $ret ]] || ble/color/g2sgr "$g"; sgrB1=$ret
   6540   fi
   6541 
   6542   # 前置部分の出力
   6543   local out= flag_overflow= p0=0
   6544   if [[ $prefix ]]; then
   6545     ble/canvas/trace-text "$prefix" nonewline || flag_overflow=1
   6546     out=$out$_ble_term_sgr0$ret
   6547   fi
   6548 
   6549   # 一致部分の出力
   6550   if ((${#m[@]})); then
   6551     local i iN=${#m[@]} p p0=0
   6552     for ((i=0;i<iN;i++)); do
   6553       ((p=m[i]))
   6554       if ((p0<p)); then
   6555         if ((i%2==0)); then
   6556           local sgr0=$sgrN0 sgr1=$sgrN1
   6557         else
   6558           local sgr0=$sgrB0 sgr1=$sgrB1
   6559         fi
   6560         ble/canvas/trace-text "${show:p0:p-p0}" nonewline:external-sgr || flag_overflow=1
   6561         out=$out$sgr0$ret
   6562       fi
   6563       p0=$p
   6564     done
   6565   fi
   6566 
   6567   # 残りの出力
   6568   if ((p0<${#show})); then
   6569     local sgr0=$sgrN0 sgr1=$sgrN1
   6570     ble/canvas/trace-text "${show:p0}" nonewline:external-sgr || flag_overflow=1
   6571     out=$out$sgr0$ret
   6572   fi
   6573 
   6574   # 後置部分の出力
   6575   if [[ $suffix ]]; then
   6576     ble/canvas/trace-text "$suffix" nonewline || flag_overflow=1
   6577     out=$out$_ble_term_sgr0$ret
   6578   fi
   6579 
   6580   ret=$out$_ble_term_sgr0
   6581   [[ ! $flag_overflow ]]
   6582 }
   6583 
   6584 function ble/complete/menu-complete.class/get-desc {
   6585   local item=$1
   6586   local "${_ble_complete_cand_varnames[@]/%/=}" # WA #D1570 checked
   6587   ble/complete/cand/unpack "$item"
   6588   desc="$desc_sgrt(action:$ACTION)$desc_sgr0"
   6589   ble/function#try ble/complete/action:"$ACTION"/get-desc
   6590 }
   6591 
   6592 function ble/complete/menu-complete.class/onselect {
   6593   local nsel=$1 osel=$2
   6594   local insert=${_ble_complete_menu_original:-${_ble_complete_menu_comp[2]}}
   6595   if ((nsel>=0)); then
   6596     local "${_ble_complete_cand_varnames[@]/%/=}" # WA #D1570 checked
   6597     ble/complete/cand/unpack "${_ble_complete_menu_items[nsel]}"
   6598     insert=$INSERT
   6599   fi
   6600 
   6601   if [[ :$bleopt_complete_menu_complete_opts: == *:insert-selection:* ]]; then
   6602     ble-edit/content/replace-limited "$_ble_complete_menu0_beg" "$_ble_edit_ind" "$insert"
   6603     ((_ble_edit_ind=_ble_complete_menu0_beg+${#insert}))
   6604   else
   6605     ((_ble_edit_ind=_ble_complete_menu0_beg))
   6606   fi
   6607 }
   6608 
   6609 function ble/complete/menu/clear {
   6610   if [[ $_ble_complete_menu_active ]]; then
   6611     _ble_complete_menu_active=
   6612     ble/complete/menu#clear
   6613     [[ $_ble_highlight_layer_menu_filter_beg ]] &&
   6614       ble/textarea#invalidate str # layer:menu_filter 解除 (#D0995)
   6615   fi
   6616   return 0
   6617 }
   6618 blehook widget_bell!=ble/complete/menu/clear
   6619 blehook history_leave!=ble/complete/menu/clear
   6620 
   6621 ## @fn ble/complete/menu/get-footprint
   6622 ##   @var[out] footprint
   6623 function ble/complete/menu/get-footprint {
   6624   footprint=$_ble_edit_ind:$_ble_edit_mark_active:${_ble_edit_mark_active:+$_ble_edit_mark}:$_ble_edit_overwrite_mode:$_ble_edit_str
   6625 }
   6626 
   6627 ## @fn ble/complete/menu/show opts
   6628 ##   @param[in] opts
   6629 ##     filter
   6630 ##     menu-source
   6631 ##     offset=NUMBER
   6632 ##   @var[in] comp_type
   6633 ##   @var[in] COMP1 COMP2 COMPS COMPV comps_flags comps_fixed
   6634 ##   @arr[in] cand_pack
   6635 ##   @var[in] menu_common_part
   6636 ##
   6637 function ble/complete/menu/show {
   6638   local opts=$1
   6639 
   6640   if [[ :$opts: == *:load-filtered-data:* ]]; then
   6641     local COMP1=${_ble_complete_menu_comp[0]}
   6642     local COMP2=${_ble_complete_menu_comp[1]}
   6643     local COMPS=${_ble_complete_menu_comp[2]}
   6644     local COMPV=${_ble_complete_menu_comp[3]}
   6645     local comp_type=${_ble_complete_menu_comp[4]}
   6646     local comps_flags=${_ble_complete_menu0_comp[5]}
   6647     local comps_fixed=${_ble_complete_menu0_comp[6]}
   6648     local cand_pack; cand_pack=("${_ble_complete_menu_items[@]}")
   6649     local menu_common_part=$_ble_complete_menu_common_part
   6650   fi
   6651 
   6652   # settings
   6653   local menu_style=$bleopt_complete_menu_style
   6654   [[ :$opts: == *:filter:* && $_ble_complete_menu_style ]] &&
   6655     menu_style=$_ble_complete_menu_style
   6656   local menu_items; menu_items=("${cand_pack[@]}")
   6657 
   6658   _ble_complete_menu_common_part=$menu_common_part
   6659   local menu_class=ble/complete/menu-complete.class menu_param=
   6660 
   6661   local menu_opts=$opts
   6662   [[ :$comp_type: == *:sync:* ]] && menu_opts=$menu_opts:sync
   6663 
   6664   ble/complete/menu#construct "$menu_opts" || return "$?"
   6665   ble/complete/menu#show
   6666 
   6667   if [[ :$opts: == *:menu-source:* ]]; then
   6668     # menu に既に表示されている内容を元にした補完後のメニュー再表示。
   6669     # 補完開始時の情報を保持したまま調整を行う。
   6670 
   6671     # 編集領域左側の文字列が曖昧補完によって書き換わる可能性がある
   6672     local left0=${_ble_complete_menu0_str::_ble_complete_menu0_end}
   6673     local left1=${_ble_edit_str::_ble_edit_ind}
   6674     local ret; ble/string#common-prefix "$left0" "$left1"; left0=$ret
   6675 
   6676     # 編集領域右側の文字列が吸収されて書き換わる可能性がある
   6677     local right0=${_ble_complete_menu0_str:_ble_complete_menu0_end}
   6678     local right1=${_ble_edit_str:_ble_edit_ind}
   6679     local ret; ble/string#common-suffix "$right0" "$right1"; right0=$ret
   6680 
   6681     local footprint; ble/complete/menu/get-footprint
   6682     _ble_complete_menu0_str=$left0$right0
   6683     _ble_complete_menu0_end=${#left0}
   6684     _ble_complete_menu_footprint=$footprint
   6685   elif [[ :$opts: != *:filter:* ]]; then
   6686     local beg=$COMP1 end=$_ble_edit_ind # COMP2 でなく補完挿入後の位置
   6687     local str=$_ble_edit_str
   6688     [[ $_ble_decode_keymap == auto_complete ]] &&
   6689       str=${str::_ble_edit_ind}${str:_ble_edit_mark}
   6690     local footprint; ble/complete/menu/get-footprint
   6691     _ble_complete_menu_active=1
   6692     _ble_complete_menu_style=$menu_style
   6693     _ble_complete_menu0_beg=$beg
   6694     _ble_complete_menu0_end=$end
   6695     _ble_complete_menu0_str=$str
   6696     _ble_complete_menu0_comp=("$COMP1" "$COMP2" "$COMPS" "$COMPV" "$comp_type" "$comps_flags" "$comps_fixed")
   6697     _ble_complete_menu0_pack=("${cand_pack[@]}")
   6698     _ble_complete_menu_selected=-1
   6699     _ble_complete_menu_comp=("$COMP1" "$COMP2" "$COMPS" "$COMPV" "$comp_type")
   6700     _ble_complete_menu_footprint=$footprint
   6701   fi
   6702   return 0
   6703 }
   6704 
   6705 function ble/complete/menu/redraw {
   6706   if [[ $_ble_complete_menu_active ]]; then
   6707     ble/complete/menu#show
   6708   fi
   6709 }
   6710 
   6711 ## ble/complete/menu/get-active-range [str [ind]]
   6712 ##   @param[in,opt] str ind
   6713 ##   @var[out] beg end
   6714 function ble/complete/menu/get-active-range {
   6715   [[ $_ble_complete_menu_active ]] || return 1
   6716 
   6717   local str=${1-$_ble_edit_str} ind=${2-$_ble_edit_ind}
   6718   local mbeg=$_ble_complete_menu0_beg
   6719   local mend=$_ble_complete_menu0_end
   6720   local left=${_ble_complete_menu0_str::mend}
   6721   local right=${_ble_complete_menu0_str:mend}
   6722   if [[ ${str::_ble_edit_ind} == "$left"* && ${str:_ble_edit_ind} == *"$right" ]]; then
   6723     ((beg=mbeg,end=${#str}-${#right}))
   6724     return 0
   6725   else
   6726     ble/complete/menu/clear
   6727     return 1
   6728   fi
   6729 }
   6730 
   6731 ## @fn ble/complete/menu/generate-candidates-from-menu
   6732 ##   現在表示されている menu 内容から候補を再抽出します。
   6733 ##   @var[out] COMP1 COMP2 COMPS COMPV comp_type comps_flags comps_fixed
   6734 ##   @var[out] cand_count cand_cand cand_word cand_pack
   6735 function ble/complete/menu/generate-candidates-from-menu {
   6736   # completion context information
   6737   COMP1=${_ble_complete_menu_comp[0]}
   6738   COMP2=${_ble_complete_menu_comp[1]}
   6739   COMPS=${_ble_complete_menu_comp[2]}
   6740   COMPV=${_ble_complete_menu_comp[3]}
   6741   comp_type=${_ble_complete_menu_comp[4]}
   6742   comps_flags=${_ble_complete_menu0_comp[5]}
   6743   comps_fixed=${_ble_complete_menu0_comp[6]}
   6744 
   6745   # remaining candidates
   6746   cand_count=${#_ble_complete_menu_items[@]}
   6747   cand_cand=() cand_word=() cand_pack=()
   6748   local pack "${_ble_complete_cand_varnames[@]/%/=}" # WA #D1570 checked
   6749   for pack in "${_ble_complete_menu_items[@]}"; do
   6750     ble/complete/cand/unpack "$pack"
   6751     ble/array#push cand_cand "$CAND"
   6752     ble/array#push cand_word "$INSERT"
   6753     ble/array#push cand_pack "$pack"
   6754   done
   6755   ((cand_count))
   6756 }
   6757 
   6758 # 
   6759 #==============================================================================
   6760 # 補完
   6761 
   6762 ## @fn ble/complete/generate-candidates-from-opts opts
   6763 ##   @var[out] COMP1 COMP2 COMPS COMPV comp_type comps_flags comps_fixed
   6764 ##   @var[out] cand_count cand_cand cand_word cand_pack
   6765 ##   @var[in,out] cand_limit_reached
   6766 function ble/complete/generate-candidates-from-opts {
   6767   local opts=$1
   6768 
   6769   # 文脈の決定
   6770   local context; ble/complete/complete/determine-context-from-opts "$opts"
   6771 
   6772   # 補完源の生成
   6773   comp_type=
   6774   [[ :$opts: == *:auto_menu:* ]] && comp_type=auto_menu
   6775   local comp_text=$_ble_edit_str comp_index=$_ble_edit_ind
   6776   local sources
   6777   ble/complete/context:"$context"/generate-sources "$comp_text" "$comp_index" || return "$?"
   6778 
   6779   ble/complete/candidates/generate "$opts"
   6780 }
   6781 
   6782 ## @fn ble/complete/insert insert_beg insert_end insert suffix
   6783 function ble/complete/insert {
   6784   local insert_beg=$1 insert_end=$2
   6785   local insert=$3 suffix=$4
   6786   local original_text=${_ble_edit_str:insert_beg:insert_end-insert_beg}
   6787   local ret
   6788 
   6789   # 編集範囲の最小化
   6790   local insert_replace=
   6791   if [[ $insert == "$original_text"* ]]; then
   6792     # 既存部分の置換がない場合
   6793     insert=${insert:insert_end-insert_beg}
   6794     ((insert_beg=insert_end))
   6795   else
   6796     # 既存部分の置換がある場合
   6797     ble/string#common-prefix "$insert" "$original_text"
   6798     if [[ $ret ]]; then
   6799       insert=${insert:${#ret}}
   6800       ((insert_beg+=${#ret}))
   6801     fi
   6802   fi
   6803 
   6804   if [[ $bleopt_complete_skip_matched ]]; then
   6805     # カーソルの右のテキストの吸収
   6806     if [[ $insert ]]; then
   6807       local right_text=${_ble_edit_str:insert_end}
   6808       right_text=${right_text%%[$IFS]*}
   6809       if ble/string#common-prefix "$insert" "$right_text"; [[ $ret ]]; then
   6810         # カーソルの右に先頭一致する場合に吸収
   6811         ((insert_end+=${#ret}))
   6812       elif ble/complete/string#common-suffix-prefix "$insert" "$right_text"; [[ $ret ]]; then
   6813         # カーソルの右に末尾一致する場合に吸収
   6814         ((insert_end+=${#ret}))
   6815       fi
   6816     fi
   6817 
   6818     # suffix の吸収
   6819     if [[ $suffix ]]; then
   6820       local right_text=${_ble_edit_str:insert_end}
   6821       if ble/string#common-prefix "$suffix" "$right_text"; [[ $ret ]]; then
   6822         ((insert_end+=${#ret}))
   6823       elif ble/complete/string#common-suffix-prefix "$suffix" "$right_text"; [[ $ret ]]; then
   6824         ((insert_end+=${#ret}))
   6825       fi
   6826     fi
   6827   fi
   6828 
   6829   local ins=$insert$suffix
   6830   ble/widget/.replace-range "$insert_beg" "$insert_end" "$ins"
   6831   ((_ble_edit_ind=insert_beg+${#ins},
   6832     _ble_edit_ind>${#_ble_edit_str}&&
   6833       (_ble_edit_ind=${#_ble_edit_str})))
   6834 }
   6835 
   6836 ## @fn ble/complete/insert-common
   6837 ##   @var[out] COMP1 COMP2 COMPS COMPV comp_type comps_flags comps_fixed
   6838 ##   @var[out] cand_count cand_cand cand_word cand_pack
   6839 function ble/complete/insert-common {
   6840   local ret
   6841   ble/complete/candidates/determine-common-prefix; (($?==148)) && return 148
   6842   local insert=$ret suffix=
   6843   local insert_beg=$COMP1 insert_end=$COMP2
   6844   local insert_flags=
   6845   [[ $insert == "$COMPS"* ]] || insert_flags=r
   6846 
   6847   if ((cand_count==1)); then
   6848     # 一意確定の時
   6849     local ACTION=${cand_pack[0]%%:*}
   6850     if ble/is-function ble/complete/action:"$ACTION"/complete; then
   6851       local "${_ble_complete_cand_varnames[@]/%/=}" # WA #D1570 checked
   6852       ble/complete/cand/unpack "${cand_pack[0]}"
   6853       ble/complete/action:"$ACTION"/complete
   6854       (($?==148)) && return 148
   6855     fi
   6856   else
   6857     # 候補が複数ある時
   6858     insert_flags=${insert_flags}m
   6859   fi
   6860 
   6861   local do_insert=1
   6862   if ((cand_count>1)) && [[ $insert_flags == *r* ]]; then
   6863     # 既存部分を置換し、かつ一意確定でない場合は置換しない。
   6864     # 曖昧補完の時は determine-common-prefix 内で調整されるので挿入する。
   6865     if [[ :$comp_type: != *:[maAi]:* ]]; then
   6866       do_insert=
   6867     fi
   6868   elif [[ $insert$suffix == "$COMPS" ]]; then
   6869     # 何も変化がない時は、挿入しない。
   6870     do_insert=
   6871   fi
   6872   if [[ $do_insert ]]; then
   6873     ble/complete/insert "$insert_beg" "$insert_end" "$insert" "$suffix"
   6874     blehook/invoke complete_insert
   6875   fi
   6876 
   6877   if [[ $insert_flags == *m* ]]; then
   6878     # menu_common_part (メニュー強調文字列)
   6879     #   もし insert が単純単語の場合には
   6880     #   menu_common_part を挿入後の評価値とする。
   6881     #   そうでなければ仕方がないので挿入前の値 COMPV とする。
   6882     local menu_common_part=$COMPV
   6883     local ret simple_flags simple_ibrace
   6884     if ble/syntax:bash/simple-word/reconstruct-incomplete-word "$insert"; then
   6885       ble/complete/source/eval-simple-word "$ret" single
   6886       (($?==148)) && return 148
   6887       menu_common_part=$ret
   6888     fi
   6889     ble/complete/menu/show "$menu_show_opts" || return "$?"
   6890   elif [[ $insert_flags == *n* ]]; then
   6891     ble/widget/complete show_menu:regenerate || return "$?"
   6892   else
   6893     _ble_complete_state=complete
   6894     ble/complete/menu/clear
   6895   fi
   6896   return 0
   6897 }
   6898 
   6899 ## @fn ble/complete/insert-all
   6900 ##   @var[out] COMP1 COMP2 COMPS COMPV comp_type comps_flags comps_fixed
   6901 ##   @var[out] cand_count cand_cand cand_word cand_pack
   6902 function ble/complete/insert-all {
   6903   local "${_ble_complete_cand_varnames[@]/%/=}" # WA #D1570 checked
   6904   local pack beg=$COMP1 end=$COMP2 insert= suffix= insert_flags= index=0
   6905   for pack in "${cand_pack[@]}"; do
   6906     ble/complete/cand/unpack "$pack"
   6907     insert=$INSERT suffix= insert_flags=
   6908 
   6909     if ble/is-function ble/complete/action:"$ACTION"/complete; then
   6910       ble/complete/action:"$ACTION"/complete
   6911       (($?==148)) && return 148
   6912     fi
   6913     [[ $suffix != *' ' ]] && suffix="$suffix "
   6914 
   6915     ble/complete/insert "$beg" "$end" "$insert" "$suffix"
   6916     blehook/invoke complete_insert
   6917     beg=$_ble_edit_ind end=$_ble_edit_ind
   6918     ((index++))
   6919   done
   6920 
   6921   _ble_complete_state=complete
   6922   ble/complete/menu/clear
   6923   return 0
   6924 }
   6925 
   6926 ## @fn ble/complete/insert-braces/.compose words...
   6927 ##   指定した単語をブレース展開に圧縮します。
   6928 ##   @var[in] comp_type
   6929 ##   @stdout
   6930 ##     圧縮したブレース展開を返します。
   6931 function ble/complete/insert-braces/.compose {
   6932   # Note: awk が RS = "\0" に対応していれば \0 で区切る。
   6933   #   それ以外の場合には \x1E (ASCII RS) で区切る。
   6934   if ble/bin/awk0.available; then
   6935     local printf_format='%s\0' char_RS='"\0"' awk=ble/bin/awk0
   6936   else
   6937     local printf_format='%s\x1E' char_RS='"\x1E"' awk=ble/bin/awk
   6938   fi
   6939 
   6940   local q=\'
   6941   local -x rex_atom='^(\\.|[0-9]+|.)' del_close= del_open= quote_type=
   6942   local -x COMPS=$COMPS
   6943   if [[ :$comp_type: != *:[maAi]:* ]]; then
   6944     local rex_brace='[,{}]|\{[-a-zA-Z0-9]+\.\.[-a-zA-Z0-9]+\}'
   6945     case $comps_flags in
   6946     (*S*)    rex_atom='^('$q'(\\'$q'|'$rex_brace')'$q'|[0-9]+|.)' # '...'
   6947              del_close=\' del_open=\' quote_type=S ;;
   6948     (*E*)    rex_atom='^(\\.|'$q'('$rex_brace')\$'$q'|[0-9]+|.)'  # $'...'
   6949              del_close=\' del_open=\$\' quote_type=E ;;
   6950     (*[DI]*) rex_atom='^(\\[\"$`]|"('$rex_brace')"|[0-9]+|.)'     # "...", $"..."
   6951              del_close=\" del_open=\" quote_type=D ;;
   6952     esac
   6953   fi
   6954 
   6955   printf "$printf_format" "$@" | "$awk" '
   6956     function starts_with(str, head) {
   6957       return substr(str, 1, length(head)) == head;
   6958     }
   6959 
   6960     BEGIN {
   6961       RS = '"$char_RS"';
   6962       rex_atom = ENVIRON["rex_atom"];
   6963       del_close = ENVIRON["del_close"];
   6964       del_open = ENVIRON["del_open"];
   6965       quote_type = ENVIRON["quote_type"];
   6966       COMPS = ENVIRON["COMPS"];
   6967 
   6968       BRACE_OPEN = del_close "{" del_open;
   6969       BRACE_CLOS = del_close "}" del_open;
   6970     }
   6971 
   6972     function to_atoms(str, arr, _, chr, atom, level, count, rex) {
   6973       count = 0;
   6974       while (match(str, rex_atom) > 0) {
   6975         chr = substr(str, 1, RLENGTH);
   6976         str = substr(str, RLENGTH + 1);
   6977         if (chr == BRACE_OPEN) {
   6978           atom = chr;
   6979           level = 1;
   6980           while (match(str, rex_atom) > 0) {
   6981             chr = substr(str, 1, RLENGTH);
   6982             str = substr(str, RLENGTH + 1);
   6983             atom = atom chr;
   6984             if (chr == BRACE_OPEN)
   6985               level++;
   6986             else if (chr == BRACE_CLOS && --level==0)
   6987               break;
   6988           }
   6989         } else {
   6990           atom = chr;
   6991         }
   6992         arr[count++] = atom;
   6993       }
   6994       return count;
   6995     }
   6996 
   6997     function remove_empty_quote(str, _, rex_quote_first, rex_quote, out, empty, m) {
   6998       if (quote_type == "S" || quote_type == "E") {
   6999         rex_quote_first = "^[^'$q']*'$q'";
   7000         rex_quote = "'$q'[^'$q']*'$q'|(\\\\.|[^'$q'])+";
   7001       } else if (quote_type == "D") {
   7002         rex_quote_first = "^[^\"]*\"";
   7003         rex_quote = "\"([^\\\"]|\\\\.)*\"|(\\\\.|[^\"])+";
   7004       } else return str;
   7005       empty = del_open del_close;
   7006 
   7007       out = "";
   7008 
   7009       if (starts_with(str, COMPS)) {
   7010         out = COMPS;
   7011         str = substr(str, length(COMPS) + 1);
   7012         if (match(str, rex_quote_first) > 0) {
   7013           out = out substr(str, 1, RLENGTH);
   7014           str = substr(str, RLENGTH + 1);
   7015         }
   7016       }
   7017 
   7018       while (match(str, rex_quote) > 0) {
   7019         m = substr(str, 1, RLENGTH);
   7020         if (m != empty) out = out m;
   7021         str = substr(str, RLENGTH + 1);
   7022       }
   7023 
   7024       if (str == del_open)
   7025         return out;
   7026       else
   7027         return out str del_close;
   7028     }
   7029 
   7030     function zpad(value, width, _, wpad, i, pad) {
   7031       wpad = width - length(value);
   7032       pad = "";
   7033       for (i = 0; i < wpad; i++) pad = "0" pad;
   7034       if (value < 0)
   7035         return "-" pad (-value);
   7036       else
   7037         return pad value;
   7038     }
   7039     function zpad_remove(value) {
   7040       if (value ~ /^0+$/)
   7041         value = "0";
   7042       else if (value ~ /^-/)
   7043         sub(/^-0+/, "-", value);
   7044       else
   7045         sub(/^0+/, "", value);
   7046       return value;
   7047     }
   7048     function zpad_a2i(text) {
   7049       sub(/^-0+/, "-", text) || sub(/^0+/, "", text);
   7050       return 0 + text;
   7051     }
   7052 
   7053     function range_contract(arr, len, _, i, value, alpha, lower, upper, keys, ikey, dict, b, e, beg, end, tmp) {
   7054       lower = "abcdefghijklmnopqrstuvwxyz";
   7055       upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
   7056       for (i = 0; i < len; i++) {
   7057         value = arr[i];
   7058         if (dict[value]) {
   7059           dict[value]++;
   7060         } else {
   7061           keys[ikey++] = value;
   7062           dict[value] = 1;
   7063         }
   7064       }
   7065 
   7066       len = 0;
   7067 
   7068       for (i = 0; i < ikey; i++) {
   7069         while (dict[value = keys[i]]--) {
   7070           if (value ~ /^([a-zA-Z])$/) {
   7071             alpha = (value ~ /^[a-z]$/) ? lower : upper;
   7072             beg = end = value;
   7073             b = e = index(alpha, value);
   7074             while (b > 1 && dict[tmp = substr(alpha, b - 1, 1)]) {
   7075               dict[beg = tmp]--;
   7076               b--;
   7077             }
   7078             while (e < 26 && dict[tmp = substr(alpha, e + 1, 1)]) {
   7079               dict[end = tmp]--;
   7080               e++;
   7081             }
   7082 
   7083             if (e == b) {
   7084               arr[len++] = beg;
   7085             } else if (e == b + 1) {
   7086               arr[len++] = beg;
   7087               arr[len++] = end;
   7088             } else {
   7089               arr[len++] = del_close "{" beg ".." end "}" del_open;
   7090             }
   7091 
   7092           } else if (value ~ /^(0+|-?0*[1-9][0-9]*)$/) {
   7093             beg = end = value;
   7094             b = e = zpad_a2i(value);
   7095             wmax = wmin = length(value);
   7096 
   7097             # range extension for normal numbers
   7098             if (value ~ /^(0|-?[1-9][0-9]*)$/) {
   7099               while (dict[b - 1]) dict[--b]--;
   7100               while (dict[e + 1]) dict[++e]--;
   7101 
   7102               tmp = length(beg = "" b);
   7103               if (tmp < wmin) wmin = tmp;
   7104               else if (tmp > wmax) wmax = tmp;
   7105 
   7106               tmp = length(end = "" e);
   7107               if (tmp < wmin) wmin = tmp;
   7108               else if (tmp > wmax) wmax = tmp;
   7109             }
   7110 
   7111             # try range extension for zpad numbers
   7112             if (wmax == wmin) {
   7113               while (length(tmp = zpad(b - 1, wmin)) == wmin && dict[tmp]) { dict[tmp]--; --b; }
   7114               while (length(tmp = zpad(e + 1, wmin)) == wmin && dict[tmp]) { dict[tmp]--; ++e; }
   7115               beg = zpad(b, wmin);
   7116               end = zpad(e, wmin);
   7117             }
   7118 
   7119             if (e == b) {
   7120               arr[len++] = beg;
   7121             } else if (e == b + 1) {
   7122               arr[len++] = beg;
   7123               arr[len++] = end;
   7124             } else if (b < 0 && e < 0) {
   7125               # if all the numbers are negative, factorize -
   7126               arr[len++] = del_close "-{" substr(end, 2) ".." substr(beg, 2) "}" del_open;
   7127             } else {
   7128               arr[len++] = del_close "{" beg ".." end "}" del_open;
   7129             }
   7130 
   7131           } else {
   7132             arr[len++] = value;
   7133           }
   7134         }
   7135       }
   7136       return len;
   7137     }
   7138 
   7139     function simple_brace(arr, len, _, ret, i) {
   7140       if (len == 0) return "";
   7141 
   7142       len = range_contract(arr, len);
   7143       if (len == 1) return arr[0];
   7144 
   7145       ret = BRACE_OPEN arr[0];
   7146       for (i = 1; i < len; i++)
   7147         ret = ret del_close "," del_open arr[i];
   7148       return ret BRACE_CLOS;
   7149     }
   7150 
   7151     #--------------------------------------------------------------------------
   7152     # right factorization
   7153 
   7154     function rfrag_strlen_common(a, b, _, la, lb, tmp, i, n) {
   7155       ret = 0;
   7156       alen = to_atoms(a, abuf);
   7157       blen = to_atoms(b, bbuf);
   7158       while (alen > 0 && blen > 0) {
   7159         if (abuf[alen - 1] != bbuf[blen - 1]) break;
   7160         ret += length(abuf[alen - 1]);
   7161         alen--;
   7162         blen--;
   7163       }
   7164       return ret;
   7165     }
   7166     function rfrag_get_level(str, _, len, i, rfrag0, rfrag0len, rfrag1) {
   7167       len = length(str);
   7168       rfrag_matching_offset = len;
   7169       for (i = 0; i < rfrag_depth - 1; i++) {
   7170         rfrag0 = rfrag[i];
   7171         rfrag0len = length(rfrag0);
   7172         rfrag1 = substr(str, len - rfrag0len + 1);
   7173         str = substr(str, 1, len -= rfrag0len);
   7174         if (rfrag0 != rfrag1) break;
   7175         rfrag_matching_offset -= rfrag0len;
   7176       }
   7177       while (i && rfrag[i - 1] == "") i--; # empty fragment
   7178       return i;
   7179     }
   7180     function rfrag_reduce(new_depth, _, c, i, brace, frags) {
   7181       while (rfrag_depth > new_depth) {
   7182         rfrag_depth--;
   7183         c = rfrag_count[rfrag_depth];
   7184         for (i = 0; i < c; i++)
   7185           frags[i] = rfrag[rfrag_depth, i];
   7186         frags[c] = rfrag[rfrag_depth];
   7187         brace = simple_brace(frags, c + 1);
   7188 
   7189         if (rfrag_depth == 0)
   7190           return brace;
   7191         else
   7192           rfrag[rfrag_depth - 1] = brace rfrag[rfrag_depth - 1];
   7193       }
   7194     }
   7195     function rfrag_register(str, level, _, rfrag0, rfrag1, len) {
   7196       if (level == rfrag_depth) {
   7197         rfrag_depth = level + 1;
   7198         rfrag[level] = "";
   7199         rfrag_count[level] = 0;
   7200       } else if (rfrag_depth != level + 1) {
   7201         print "ERR(rfrag)";
   7202       }
   7203 
   7204       rfrag0 = rfrag[level];
   7205       rfrag1 = substr(str, 1, rfrag_matching_offset);
   7206       len = rfrag_strlen_common(rfrag0, rfrag1);
   7207       if (len == 0) {
   7208         rfrag[level, rfrag_count[level]++] = rfrag0;
   7209         rfrag[level] = rfrag1;
   7210       } else {
   7211         rfrag[level] = substr(rfrag0, length(rfrag0) - len + 1);
   7212         rfrag[level + 1, 0] = substr(rfrag0, 1, length(rfrag0) - len);
   7213         rfrag[level + 1] = substr(rfrag1, 1, length(rfrag1) - len);
   7214         rfrag_count[level + 1] = 1;
   7215         rfrag_depth++;
   7216       }
   7217     }
   7218     function rfrag_dump(_, i, j, prefix) {
   7219       print "depth = " rfrag_depth;
   7220       for (i = 0; i < rfrag_depth; i++) {
   7221         prefix = "";
   7222         for (j = 0; j < i; j++) prefix = prefix "  ";
   7223         for (j = 0; j < rfrag_count[i]; j++)
   7224           print prefix "rfrag[" i "," j "] = " rfrag[i,j];
   7225         print prefix "rfrag[" i "] = " rfrag[i];
   7226       }
   7227     }
   7228     function rfrag_brace(arr, len, _, i, level) {
   7229       if (len == 0) return "";
   7230       if (len == 1) return arr[0];
   7231 
   7232       rfrag_depth = 1;
   7233       rfrag[0] = arr[0];
   7234       rfrag_count[0] = 0;
   7235       for (i = 1; i < len; i++) {
   7236         level = rfrag_get_level(arr[i]);
   7237         rfrag_reduce(level + 1);
   7238         rfrag_register(arr[i], level);
   7239       }
   7240 
   7241       return rfrag_reduce(0);
   7242     }
   7243 
   7244     #--------------------------------------------------------------------------
   7245     # left factorization
   7246 
   7247     function lfrag_strlen_common(a, b, _, ret, abuf, bbuf, alen, blen, ia, ib) {
   7248       ret = 0;
   7249       alen = to_atoms(a, abuf);
   7250       blen = to_atoms(b, bbuf);
   7251       for (ia = ib = 0; ia < alen && ib < blen; ia++ + ib++) {
   7252         if (abuf[ia] != bbuf[ib]) break;
   7253         ret += length(abuf[ia]);
   7254       }
   7255       return ret;
   7256     }
   7257     function lfrag_get_level(str, _, i, frag0, frag0len, frag1) {
   7258       lfrag_matching_offset = 0;
   7259       for (i = 0; i < lfrag_depth - 1; i++) {
   7260         frag0 = frag[i]
   7261         frag0len = length(frag0);
   7262         frag1 = substr(str, lfrag_matching_offset + 1, frag0len);
   7263         if (frag0 != frag1) break;
   7264         lfrag_matching_offset += frag0len;
   7265       }
   7266       while (i && frag[i - 1] == "") i--; # empty fragment
   7267       return i;
   7268     }
   7269     function lfrag_reduce(new_depth, _, c, i, brace, frags) {
   7270       while (lfrag_depth > new_depth) {
   7271         lfrag_depth--;
   7272         c = frag_count[lfrag_depth];
   7273         for (i = 0; i < c; i++)
   7274           frags[i] = frag[lfrag_depth, i];
   7275         frags[c] = frag[lfrag_depth];
   7276         brace = rfrag_brace(frags, c + 1);
   7277 
   7278         if (lfrag_depth == 0)
   7279           return brace;
   7280         else
   7281           frag[lfrag_depth - 1] = frag[lfrag_depth - 1] brace;
   7282       }
   7283     }
   7284     function lfrag_register(str, level, _, frag0, frag1, len) {
   7285       if (lfrag_depth == level) {
   7286         lfrag_depth = level + 1;
   7287         frag[level] = "";
   7288         frag_count[level] = 0;
   7289       } else if (lfrag_depth != level + 1) {
   7290         print "ERR";
   7291       }
   7292 
   7293       frag0 = frag[level];
   7294       frag1 = substr(str, lfrag_matching_offset + 1);
   7295       len = lfrag_strlen_common(frag0, frag1);
   7296       if (len == 0) {
   7297         frag[level, frag_count[level]++] = frag0;
   7298         frag[level] = frag1;
   7299       } else {
   7300         frag[level] = substr(frag0, 1, len);
   7301         frag[level + 1, 0] = substr(frag0, len + 1);
   7302         frag[level + 1] = substr(frag1, len + 1);
   7303         frag_count[level + 1] = 1;
   7304         lfrag_depth++;
   7305       }
   7306     }
   7307 
   7308     function lfrag_dump(_, i, j, prefix) {
   7309       print "depth = " lfrag_depth;
   7310       for (i = 0; i < lfrag_depth; i++) {
   7311         prefix = "";
   7312         for (j = 0; j < i; j++) prefix = prefix "  ";
   7313         for (j = 0; j < frag_count[i]; j++)
   7314           print prefix "frag[" i "," j "] = " frag[i,j];
   7315         print prefix "frag[" i "] = " frag[i];
   7316       }
   7317     }
   7318 
   7319     NR == 1 {
   7320       lfrag_depth = 1;
   7321       frag[0] = $0;
   7322       frag_count[0] = 0;
   7323       #lfrag_dump();
   7324       next
   7325     }
   7326     {
   7327       level = lfrag_get_level($0);
   7328       lfrag_reduce(level + 1);
   7329       lfrag_register($0, level);
   7330       #lfrag_dump();
   7331     }
   7332 
   7333     END {
   7334       result = lfrag_reduce(0);
   7335       result = remove_empty_quote(result);
   7336       print result;
   7337     }
   7338   '
   7339 }
   7340 
   7341 ## @fn ble/complete/insert-braces
   7342 ##   @var[out] COMP1 COMP2 COMPS COMPV comp_type comps_flags comps_fixed
   7343 ##   @var[out] cand_count cand_cand cand_word cand_pack
   7344 function ble/complete/insert-braces {
   7345   if ((cand_count==1)); then
   7346     ble/complete/insert-common; return "$?"
   7347   fi
   7348 
   7349   local comps_len=${#COMPS} loop=0
   7350   local -a tails=()
   7351 
   7352   # 共通部分 (大文字・小文字は区別する)
   7353   local common=${cand_word[0]}
   7354   ble/array#push tails "${common:comps_len}"
   7355   local word clen=${#common}
   7356   for word in "${cand_word[@]:1}"; do
   7357     ((loop++%bleopt_complete_polling_cycle==0)) && ble/complete/check-cancel && return 148
   7358 
   7359     # 共通部分
   7360     ((clen>${#word}&&(clen=${#word})))
   7361     while [[ ${word::clen} != "${common::clen}" ]]; do
   7362       ((clen--))
   7363     done
   7364     common=${common::clen}
   7365 
   7366     # COMPS 以降の部分
   7367     ble/array#push tails "${word:comps_len}"
   7368   done
   7369 
   7370   local fixed=$COMPS
   7371   if [[ $common != "$COMPS"* ]]; then
   7372     # 遡って書き換えが起こる場合
   7373     tails=()
   7374 
   7375     # 前方固定部分
   7376     local fixed= fixval=
   7377     {
   7378       # comps_fixed 迄は確実に固定する
   7379       [[ $comps_fixed ]] &&
   7380         fixed=${COMPS::${comps_fixed%%:*}} fixval=${comps_fixed#*:}
   7381 
   7382       # もし COMPS を部分的に適用できればそれを用いる
   7383       local ret simple_flags simple_ibrace
   7384       ble/complete/candidates/determine-common-prefix/.apply-partial-comps # var[in,out] common
   7385       if ble/syntax:bash/simple-word/reconstruct-incomplete-word "$common"; then
   7386         ble/complete/source/eval-simple-word "$ret" single
   7387         (($?==148)) && return 148
   7388         fixed=$common fixval=$ret
   7389       fi
   7390     }
   7391 
   7392     # cand_cand から cand_word を再構築
   7393     local cand ret fixval_len=${#fixval}
   7394     for cand in "${cand_cand[@]}"; do
   7395       ((loop++%bleopt_complete_polling_cycle==0)) && ble/complete/check-cancel && return 148
   7396       [[ $cand == "$fixval"* ]] || continue
   7397 
   7398       ble/complete/string#escape-for-completion-context "${cand:fixval_len}"
   7399       case $comps in
   7400       (*S*) cand=\'$ret\'   ;;
   7401       (*E*) cand=\$\'$ret\' ;;
   7402       (*D*) cand=\"$ret\"   ;;
   7403       (*I*) cand=\$\"$ret\" ;;
   7404       (*)   cand=$ret ;;
   7405       esac
   7406 
   7407       ble/array#push tails "$cand"
   7408     done
   7409   fi
   7410 
   7411   local tail; ble/util/assign tail 'ble/complete/insert-braces/.compose "${tails[@]}"'
   7412   local beg=$COMP1 end=$COMP2 insert=$fixed$tail suffix=
   7413 
   7414   if [[ $comps_flags == *x* ]]; then
   7415     ble/complete/action/complete.addtail ','
   7416   else
   7417     ble/complete/action/complete.addtail ' '
   7418   fi
   7419 
   7420   ble/complete/insert "$beg" "$end" "$insert" "$suffix"
   7421   blehook/invoke complete_insert
   7422   _ble_complete_state=complete
   7423   ble/complete/menu/clear
   7424   return 0
   7425 }
   7426 
   7427 _ble_complete_state=
   7428 
   7429 ## @widget complete opts
   7430 ##   @param[in] opts
   7431 ##     コロン区切りのリストです。
   7432 ##     以下は動作を指定するオプションです。
   7433 ##
   7434 ##     insert_common (既定)
   7435 ##       共通一致部分を挿入します。
   7436 ##     insert_all
   7437 ##       候補を全て挿入します。
   7438 ##     insert_braces
   7439 ##       候補をブレース展開にまとめて挿入します。
   7440 ##     insert_unique
   7441 ##       候補が一意のときメニュー補完に入らずに挿入します。
   7442 ##     show_menu
   7443 ##       メニューを表示します。
   7444 ##     enter_menu
   7445 ##       メニュー補完に入ります。
   7446 ##
   7447 ##     context=*
   7448 ##       候補生成の文脈を指定します。
   7449 ##     backward
   7450 ##       メニュー補完に入る時に最後の候補に移動します。
   7451 ##     no-empty
   7452 ##       空の COMPV による補完を抑制します。
   7453 ##     no-bell
   7454 ##       候補が存在しなかった時のベルを発生させません。
   7455 ##
   7456 ##     auto_menu
   7457 ##       auto-menu 経由で呼び出されている事を指定します。
   7458 ##       補完候補数の制限に complete_limit_auto_menu を使います。
   7459 ##       一部の補完源で complete_limit に達した時に補完全体を中止します。
   7460 ##
   7461 function ble/widget/complete {
   7462   local opts=$1
   7463   ble-edit/content/clear-arg
   7464 
   7465   local state=$_ble_complete_state
   7466   _ble_complete_state=start
   7467 
   7468   local menu_show_opts=
   7469 
   7470   if [[ :$opts: != *:insert_*:* && :$opts: != *:show_menu:* ]]; then
   7471     if [[ :$opts: == *:enter_menu:* ]]; then
   7472       [[ $_ble_complete_menu_active && :$opts: != *:context=*:* ]] &&
   7473         ble/complete/menu-complete/enter "$opts" && return 0
   7474     elif [[ $bleopt_complete_menu_complete ]]; then
   7475       if [[ $_ble_complete_menu_active && :$opts: != *:context=*:* ]]; then
   7476         local footprint; ble/complete/menu/get-footprint
   7477         [[ $footprint == "$_ble_complete_menu_footprint" ]] &&
   7478           ble/complete/menu-complete/enter "$opts" && return 0
   7479       fi
   7480       [[ $WIDGET == "$LASTWIDGET" && $state != complete ]] && opts=$opts:enter_menu
   7481     fi
   7482   fi
   7483 
   7484   local COMP1 COMP2 COMPS COMPV
   7485   local comp_type comps_flags comps_fixed
   7486   local cand_count cand_cand cand_word cand_pack
   7487   ble/complete/candidates/clear
   7488   local cand_limit_reached=
   7489   if [[ $_ble_complete_menu_active && :$opts: != *:regenerate:* &&
   7490           :$opts: != *:context=*:* && ${#_ble_complete_menu_icons[@]} -gt 0 ]]
   7491   then
   7492     if [[ $_ble_complete_menu_filter_enabled && $bleopt_complete_menu_filter ]] || {
   7493          ble/complete/menu-filter; local ext=$?
   7494          ((ext==148)) && return 148
   7495          ((ext==0)); }; then
   7496       ble/complete/menu/generate-candidates-from-menu; local ext=$?
   7497       ((ext==148)) && return 148
   7498       if ((ext==0&&cand_count)); then
   7499         local bleopt_complete_menu_style=$_ble_complete_menu_style
   7500         menu_show_opts=$menu_show_opts:menu-source # 既存の filter 前候補を保持する
   7501       fi
   7502     fi
   7503   fi
   7504   if ((cand_count==0)); then
   7505     local bleopt_complete_menu_style=$bleopt_complete_menu_style # source 等に一次変更を認める。
   7506     ble/complete/generate-candidates-from-opts "$opts"; local ext=$?
   7507     if ((ext==148)); then
   7508       return 148
   7509     fi
   7510     if [[ $cand_limit_reached ]]; then
   7511       [[ :$opts: != *:no-bell:* ]] &&
   7512         ble/widget/.bell 'complete: limit reached'
   7513       if [[ $cand_limit_reached == cancel ]]; then
   7514         ble/edit/info/default
   7515         return 1
   7516       fi
   7517     fi
   7518     if ((ext!=0||cand_count==0)); then
   7519       [[ :$opts: != *:no-bell:* && ! $cand_limit_reached ]] &&
   7520         ble/widget/.bell 'complete: no completions'
   7521       ble/edit/info/default
   7522       return 1
   7523     fi
   7524   fi
   7525 
   7526   if [[ :$opts: == *:insert_common:* || :$opts: == *:insert_unique:* && cand_count -eq 1 ]]; then
   7527     ble/complete/insert-common; return "$?"
   7528 
   7529   elif [[ :$opts: == *:insert_braces:* ]]; then
   7530     ble/complete/insert-braces; return "$?"
   7531 
   7532   elif [[ :$opts: == *:insert_all:* ]]; then
   7533     ble/complete/insert-all; return "$?"
   7534 
   7535   elif [[ :$opts: == *:enter_menu:* ]]; then
   7536     local menu_common_part=$COMPV
   7537     ble/complete/menu/show "$menu_show_opts" || return "$?"
   7538     ble/complete/menu-complete/enter "$opts"; local ext=$?
   7539     ((ext==148)) && return 148
   7540     ((ext)) && [[ :$opts: != *:no-bell:* ]] &&
   7541       ble/widget/.bell 'menu-complete: no completions'
   7542     return 0
   7543 
   7544   elif [[ :$opts: == *:show_menu:* ]]; then
   7545     local menu_common_part=$COMPV
   7546     ble/complete/menu/show "$menu_show_opts"
   7547     return "$?" # exit status of ble/complete/menu/show
   7548 
   7549   fi
   7550 
   7551   ble/complete/insert-common; return "$?"
   7552 }
   7553 
   7554 function ble/widget/complete-insert {
   7555   local original=$1 insert=$2 suffix=$3
   7556   [[ ${_ble_edit_str::_ble_edit_ind} == *"$original" ]] || return 1
   7557 
   7558   local insert_beg=$((_ble_edit_ind-${#original}))
   7559   local insert_end=$_ble_edit_ind
   7560   ble/complete/insert "$insert_beg" "$insert_end" "$insert" "$suffix"
   7561 }
   7562 
   7563 function ble/widget/menu-complete {
   7564   local opts=$1
   7565   ble/widget/complete enter_menu:insert_unique:$opts
   7566 }
   7567 
   7568 function ble/widget/complete/.select-menu-with-arg {
   7569   [[ $bleopt_complete_menu_complete && $_ble_complete_menu_active ]] || return 1
   7570 
   7571   local footprint; ble/complete/menu/get-footprint
   7572   [[ $footprint == "$_ble_complete_menu_footprint" ]] || return 1
   7573 
   7574   local arg_opts= opts=$1
   7575   [[ :$opts: == *:enter-menu:* ]] && arg_opts=always
   7576   [[ :$opts: == *:nobell:* ]] && arg_opts=$arg_opts:nobell
   7577 
   7578   # 現在のキーが実際に引数の一部として解釈され得る時のみ menu に入る
   7579   ble/widget/menu/append-arg/.is-argument "$arg_opts" || return 1
   7580   ble/complete/menu-complete/enter
   7581   ble/widget/menu/append-arg "$arg_opts"
   7582   return 0
   7583 }
   7584 
   7585 #------------------------------------------------------------------------------
   7586 # menu-filter
   7587 
   7588 ## @fn ble/complete/menu-filter/.filter-candidates
   7589 ##   @var[in,out] comp_type
   7590 ##   @var[out] cand_pack
   7591 function ble/complete/menu-filter/.filter-candidates {
   7592   cand_pack=()
   7593 
   7594   local iloop=0 interval=$bleopt_complete_polling_cycle
   7595   local filter_type pack "${_ble_complete_cand_varnames[@]/%/=}" # WA #D1570 checked
   7596   for filter_type in head substr hsubseq subseq; do
   7597     ble/path#remove-glob comp_type '[maA]'
   7598     case $filter_type in
   7599     (substr)  comp_type=${comp_type}:m ;;
   7600     (hsubseq) comp_type=${comp_type}:a ;;
   7601     (subseq)  comp_type=${comp_type}:A ;;
   7602     esac
   7603 
   7604     local comp_filter_type
   7605     local comp_filter_pattern
   7606     ble/complete/candidates/filter#init "$filter_type" "$COMPV"
   7607     for pack in "${_ble_complete_menu0_pack[@]}"; do
   7608       ((iloop++%interval==0)) && ble/complete/check-cancel && return 148
   7609       ble/complete/cand/unpack "$pack"
   7610       ble/complete/candidates/filter#test "$CAND" &&
   7611         ble/array#push cand_pack "$pack"
   7612     done
   7613     ((${#cand_pack[@]}!=0)) && return 0
   7614   done
   7615 }
   7616 function ble/complete/menu-filter/.get-filter-target {
   7617   if [[ $_ble_decode_keymap == emacs || $_ble_decode_keymap == vi_[ic]map ]]; then
   7618     ret=$_ble_edit_str
   7619   elif [[ $_ble_decode_keymap == auto_complete ]]; then
   7620     ret=${_ble_edit_str::_ble_edit_ind}${_ble_edit_str:_ble_edit_mark}
   7621   else
   7622     return 1
   7623   fi
   7624 }
   7625 function ble/complete/menu-filter {
   7626   [[ $_ble_decode_keymap == menu_complete ]] && return 0
   7627   local ret; ble/complete/menu-filter/.get-filter-target || return 1; local str=$ret
   7628 
   7629   local beg end; ble/complete/menu/get-active-range "$str" "$_ble_edit_ind" || return 1
   7630   local input=${str:beg:end-beg}
   7631   [[ $input == "${_ble_complete_menu_comp[2]}" ]] && return 0
   7632 
   7633   local simple_flags simple_ibrace
   7634   if ! ble/syntax:bash/simple-word/reconstruct-incomplete-word "$input"; then
   7635     ble/syntax:bash/simple-word/is-never-word "$input" && return 1
   7636     return 0
   7637   fi
   7638   [[ $simple_ibrace ]] && ((${simple_ibrace%%:*}>10#0${_ble_complete_menu0_comp[6]%%:*})) && return 1 # 別のブレース展開要素に入った時
   7639   ble/syntax:bash/simple-word/eval "$ret" single; (($?==148)) && return 148
   7640   local COMPV=$ret
   7641 
   7642   local comp_type=${_ble_complete_menu0_comp[4]} cand_pack
   7643   ble/complete/menu-filter/.filter-candidates; (($?==148)) && return 148
   7644 
   7645   local menu_common_part=$COMPV
   7646   ble/complete/menu/show filter || return "$?"
   7647   _ble_complete_menu_comp=("$beg" "$end" "$input" "$COMPV" "$comp_type")
   7648   return 0
   7649 }
   7650 
   7651 function ble/complete/menu-filter.idle {
   7652   ble/util/idle.wait-user-input
   7653   [[ $bleopt_complete_menu_filter ]] || return 1
   7654   [[ $_ble_complete_menu_active ]] || return 1
   7655   ble/complete/menu-filter; local ext=$?
   7656   ((ext==148)) && return 148
   7657   ((ext)) && ble/complete/menu/clear
   7658   return 0
   7659 }
   7660 
   7661 # ble/highlight/layer:menu_filter
   7662 
   7663 ## @fn ble/highlight/layer/buff#operate-gflags name beg end mask gflags
   7664 function ble/highlight/layer/buff#operate-gflags {
   7665   local BUFF=$1 beg=$2 end=$3 mask=$4 gflags=$5
   7666   ((beg<end)) || return 1
   7667 
   7668   if [[ $mask == auto ]]; then
   7669     mask=0
   7670     ((gflags&(_ble_color_gflags_FgIndexed|_ble_color_gflags_FgMask))) &&
   7671       ((mask|=_ble_color_gflags_FgIndexed|_ble_color_gflags_FgMask))
   7672     ((gflags&(_ble_color_gflags_BgIndexed|_ble_color_gflags_BgMask))) &&
   7673       ((mask|=_ble_color_gflags_BgIndexed|_ble_color_gflags_BgMask))
   7674   fi
   7675 
   7676   local i g ret
   7677   for ((i=beg;i<end;i++)); do
   7678     ble/highlight/layer/update/getg "$i"
   7679     ((g=g&~mask|gflags))
   7680     ble/color/g2sgr "$g"
   7681     builtin eval -- "$BUFF[$i]=\$ret\${_ble_highlight_layer_plain_buff[$i]}"
   7682   done
   7683 }
   7684 ## @fn ble/highlight/layer/buff#set-explicit-sgr name index
   7685 function ble/highlight/layer/buff#set-explicit-sgr {
   7686   local BUFF=$1 index=$2
   7687   builtin eval "((index<\${#$BUFF[@]}))" || return 1
   7688   local g; ble/highlight/layer/update/getg "$index"
   7689   local ret; ble/color/g2sgr "$g"
   7690   builtin eval "$BUFF[index]=\$ret\${_ble_highlight_layer_plain_buff[index]}"
   7691 }
   7692 
   7693 _ble_highlight_layer_menu_filter_buff=()
   7694 _ble_highlight_layer_menu_filter_beg=
   7695 _ble_highlight_layer_menu_filter_end=
   7696 function ble/highlight/layer:menu_filter/update {
   7697   local text=$1 player=$2
   7698 
   7699   # shift
   7700   local obeg=$_ble_highlight_layer_menu_filter_beg
   7701   local oend=$_ble_highlight_layer_menu_filter_end
   7702   if [[ $obeg ]] && ((DMIN>=0)); then
   7703     ((DMAX0<=obeg?(obeg+=DMAX-DMAX0):(DMIN<obeg&&(obeg=DMIN)),
   7704       DMAX0<=oend?(oend+=DMAX-DMAX0):(DMIN<oend&&(oend=DMIN))))
   7705   fi
   7706   _ble_highlight_layer_menu_filter_beg=$obeg
   7707   _ble_highlight_layer_menu_filter_end=$oend
   7708 
   7709   # determine range
   7710   local beg= end= ret
   7711   if [[ $bleopt_complete_menu_filter && $_ble_complete_menu_active && ${#_ble_complete_menu_icons[@]} -gt 0 ]]; then
   7712     ble/complete/menu-filter/.get-filter-target && local str=$ret &&
   7713       ble/complete/menu/get-active-range "$str" "$_ble_edit_ind" &&
   7714       [[ ${str:beg:end-beg} != "${_ble_complete_menu0_comp[2]}" ]] || beg= end=
   7715   fi
   7716 
   7717   # 変更のない場合スキップ
   7718   [[ ! $obeg && ! $beg ]] && return 0
   7719   ((PREV_UMIN<0)) && [[ $beg == "$obeg" && $end == "$oend" ]] &&
   7720     PREV_BUFF=_ble_highlight_layer_menu_filter_buff && return 0
   7721 
   7722   local umin=$PREV_UMIN umax=$PREV_UMAX
   7723   if [[ $beg ]]; then
   7724     ble/color/face2g menu_filter_fixed; local gF=$ret
   7725     ble/color/face2g menu_filter_input; local gI=$ret
   7726     local mid=$_ble_complete_menu0_end
   7727     ((mid<beg?(mid=beg):(end<mid&&(mid=end))))
   7728 
   7729     local buff_name=_ble_highlight_layer_menu_filter_buff
   7730     builtin eval "$buff_name=(\"\${$PREV_BUFF[@]}\")"
   7731     ble/highlight/layer/buff#operate-gflags "$buff_name" "$beg" "$mid" auto "$gF"
   7732     ble/highlight/layer/buff#operate-gflags "$buff_name" "$mid" "$end" auto "$gI"
   7733     ble/highlight/layer/buff#set-explicit-sgr "$buff_name" "$end"
   7734     PREV_BUFF=$buff_name
   7735 
   7736     if [[ $obeg ]]; then :
   7737       ble/highlight/layer:{selection}/.invalidate "$beg" "$obeg"
   7738       ble/highlight/layer:{selection}/.invalidate "$end" "$oend"
   7739     else
   7740       ble/highlight/layer:{selection}/.invalidate "$beg" "$end"
   7741     fi
   7742   else
   7743     if [[ $obeg ]]; then
   7744       ble/highlight/layer:{selection}/.invalidate "$obeg" "$oend"
   7745     fi
   7746   fi
   7747   _ble_highlight_layer_menu_filter_beg=$beg
   7748   _ble_highlight_layer_menu_filter_end=$end
   7749   ((PREV_UMIN=umin,PREV_UMAX=umax))
   7750 }
   7751 function ble/highlight/layer:menu_filter/getg {
   7752   local index=$1
   7753   local obeg=$_ble_highlight_layer_menu_filter_beg
   7754   local oend=$_ble_highlight_layer_menu_filter_end
   7755   local mid=$_ble_complete_menu0_end
   7756   if [[ $obeg ]] && ((obeg<=index&&index<oend)); then
   7757     local ret
   7758     if ((index<mid)); then
   7759       ble/color/face2g menu_filter_fixed; local g0=$ret
   7760     else
   7761       ble/color/face2g menu_filter_input; local g0=$ret
   7762     fi
   7763     ble/highlight/layer/update/getg "$index"
   7764     ble/color/g.append "$g0"
   7765   fi
   7766 }
   7767 
   7768 _ble_complete_menu_filter_enabled=
   7769 if ble/is-function ble/util/idle.push-background; then
   7770   _ble_complete_menu_filter_enabled=1
   7771   ble/util/idle.push -n 9999 ble/complete/menu-filter.idle
   7772   ble/array#insert-before _ble_highlight_layer_list region menu_filter
   7773 fi
   7774 
   7775 #------------------------------------------------------------------------------
   7776 #
   7777 # menu-complete
   7778 #
   7779 
   7780 ## メニュー補完では以下の変数を参照する
   7781 ##
   7782 ##   @var[in] _ble_complete_menu0_beg
   7783 ##   @var[in] _ble_complete_menu0_end
   7784 ##   @var[in] _ble_complete_menu_original
   7785 ##   @var[in] _ble_complete_menu_selected
   7786 ##   @var[in] _ble_complete_menu_common_part
   7787 ##   @arr[in] _ble_complete_menu_icons
   7788 ##
   7789 ## 更に以下の変数を使用する
   7790 ##
   7791 ##   @var[in,out] _ble_complete_menu_original=
   7792 
   7793 _ble_complete_menu_original=
   7794 
   7795 ## @fn ble/complete/menu-complete/select index [opts]
   7796 function ble/complete/menu-complete/select {
   7797   ble/complete/menu#select "$@"
   7798 }
   7799 
   7800 ## @fn ble/complete/menu-complete/enter [opts]
   7801 ##   @var[in,opt] opts
   7802 ##     backward
   7803 ##     insert_unique
   7804 function ble/complete/menu-complete/enter {
   7805   ((${#_ble_complete_menu_icons[@]}>=1)) || return 1
   7806   local beg end; ble/complete/menu/get-active-range || return 1
   7807 
   7808   local opts=$1
   7809 
   7810   _ble_edit_mark=$beg
   7811   _ble_edit_ind=$end
   7812   local comps_fixed=${_ble_complete_menu0_comp[6]}
   7813   if [[ $comps_fixed ]]; then
   7814     local comps_fixed_length=${comps_fixed%%:*}
   7815     ((_ble_edit_mark+=comps_fixed_length))
   7816   fi
   7817 
   7818   # 一意確定時。menu の処理も含めて menu-complete の枠組みの中で確定を実行する。
   7819   if [[ :$opts: == *:insert_unique:* ]] && ((${#_ble_complete_menu_items[@]}==1)); then
   7820     ble/complete/menu#select 0
   7821     ble/decode/keymap/push menu_complete
   7822     ble/widget/menu_complete/exit complete
   7823     return 0
   7824   fi
   7825 
   7826   _ble_complete_menu_original=${_ble_edit_str:beg:end-beg}
   7827   ble/complete/menu/redraw
   7828 
   7829   if [[ :$opts: == *:backward:* ]]; then
   7830     ble/complete/menu#select "$((${#_ble_complete_menu_items[@]}-1))"
   7831   else
   7832     ble/complete/menu#select 0
   7833   fi
   7834 
   7835   _ble_edit_mark_active=insert
   7836   ble/decode/keymap/push menu_complete
   7837   return 0
   7838 }
   7839 
   7840 function ble/widget/menu_complete/exit {
   7841   local opts=$1
   7842   ble/decode/keymap/pop
   7843 
   7844   if ((_ble_complete_menu_selected>=0)); then
   7845     # 置換情報を再構成
   7846     local new=${_ble_edit_str:_ble_complete_menu0_beg:_ble_edit_ind-_ble_complete_menu0_beg}
   7847     if [[ :$bleopt_complete_menu_complete_opts: != *:insert-selection:* ]]; then
   7848       local "${_ble_complete_cand_varnames[@]/%/=}" # WA #D1570 checked
   7849       ble/complete/cand/unpack "${_ble_complete_menu_items[_ble_complete_menu_selected]}"
   7850       new=$INSERT
   7851     fi
   7852     local old=$_ble_complete_menu_original
   7853     local comp_text=${_ble_edit_str::_ble_complete_menu0_beg}$old${_ble_edit_str:_ble_edit_ind}
   7854     local insert_beg=$_ble_complete_menu0_beg
   7855     local insert_end=$((_ble_complete_menu0_beg+${#old}))
   7856     local insert=$new
   7857     local insert_flags=
   7858 
   7859     # suffix の決定と挿入
   7860     local suffix=
   7861     if [[ :$opts: == *:complete:* ]]; then
   7862       local icon=${_ble_complete_menu_icons[_ble_complete_menu_selected-_ble_complete_menu_offset]}
   7863       local icon_data=${icon#*:} icon_fields
   7864       ble/string#split icon_fields , "${icon%%:*}"
   7865       local pack=${icon_data::icon_fields[4]}
   7866 
   7867       local ACTION=${pack%%:*}
   7868       if ble/is-function ble/complete/action:"$ACTION"/complete; then
   7869         # 補完文脈の復元
   7870         local COMP1=${_ble_complete_menu0_comp[0]}
   7871         local COMP2=${_ble_complete_menu0_comp[1]}
   7872         local COMPS=${_ble_complete_menu0_comp[2]}
   7873         local COMPV=${_ble_complete_menu0_comp[3]}
   7874         local comp_type=${_ble_complete_menu0_comp[4]}
   7875         local comps_flags=${_ble_complete_menu0_comp[5]}
   7876         local comps_fixed=${_ble_complete_menu0_comp[6]}
   7877 
   7878         # 補完候補のロード
   7879         local "${_ble_complete_cand_varnames[@]/%/=}" # WA #D1570 checked
   7880         ble/complete/cand/unpack "$pack"
   7881 
   7882         ble/complete/action:"$ACTION"/complete
   7883       fi
   7884       ble/complete/insert "$_ble_complete_menu0_beg" "$_ble_edit_ind" "$insert" "$suffix"
   7885     fi
   7886 
   7887     # 通知
   7888     blehook/invoke complete_insert
   7889   fi
   7890 
   7891   ble/complete/menu/clear
   7892   _ble_edit_mark_active=
   7893   _ble_complete_menu_original=
   7894 }
   7895 function ble/widget/menu_complete/cancel {
   7896   ble/decode/keymap/pop
   7897   ble/complete/menu#select -1
   7898   _ble_edit_mark_active=
   7899   _ble_complete_menu_original=
   7900 }
   7901 function ble/widget/menu_complete/accept {
   7902   ble/widget/menu_complete/exit complete
   7903 }
   7904 function ble/widget/menu_complete/exit-default {
   7905   ble/widget/menu_complete/exit
   7906   ble/decode/widget/skip-lastwidget
   7907   ble/decode/widget/redispatch-by-keys "${KEYS[@]}"
   7908 }
   7909 
   7910 function ble-decode/keymap:menu_complete/define {
   7911   # ble-bind -f __defchar__ menu_complete/self-insert
   7912   ble-bind -f __default__ 'menu_complete/exit-default'
   7913   ble-bind -f __line_limit__ nop
   7914   ble-bind -f C-m         'menu_complete/accept'
   7915   ble-bind -f RET         'menu_complete/accept'
   7916   ble-bind -f C-g         'menu_complete/cancel'
   7917   ble-bind -f 'C-x C-g'   'menu_complete/cancel'
   7918   ble-bind -f 'C-M-g'     'menu_complete/cancel'
   7919   ble-bind -f C-f         'menu/forward-column'
   7920   ble-bind -f right       'menu/forward-column'
   7921   ble-bind -f C-i         'menu/forward cyclic'
   7922   ble-bind -f TAB         'menu/forward cyclic'
   7923   ble-bind -f C-b         'menu/backward-column'
   7924   ble-bind -f left        'menu/backward-column'
   7925   ble-bind -f C-S-i       'menu/backward cyclic'
   7926   ble-bind -f S-TAB       'menu/backward cyclic'
   7927   ble-bind -f C-n         'menu/forward-line'
   7928   ble-bind -f down        'menu/forward-line'
   7929   ble-bind -f C-p         'menu/backward-line'
   7930   ble-bind -f up          'menu/backward-line'
   7931   ble-bind -f prior       'menu/backward-page'
   7932   ble-bind -f next        'menu/forward-page'
   7933   ble-bind -f home        'menu/beginning-of-page'
   7934   ble-bind -f end         'menu/end-of-page'
   7935 
   7936   local key
   7937   for key in {,M-,C-}{0..9}; do
   7938     ble-bind -f "$key" 'menu/append-arg'
   7939   done
   7940 }
   7941 
   7942 _ble_complete_menu_arg=
   7943 ## @fn ble/widget/menu/append-arg [opts]
   7944 ##   @param[in,opt] opts
   7945 ##     A colon-separated list of the options:
   7946 ##
   7947 ##     always
   7948 ##       When a numeric argument is not started, the normal digit is by default
   7949 ##       treated as normal user input.  This option makes the normal digit
   7950 ##       always start a numeric argument.
   7951 ##     nobell
   7952 ##       Do not ring edit bell when no corresponding item is found.
   7953 ##
   7954 function ble/widget/menu/append-arg {
   7955   [[ ${LASTWIDGET%%' '*} == */append-arg ]] || _ble_complete_menu_arg=
   7956 
   7957   # 引数入力が開始されていなくて (修飾なしの) 数字キーの時はそのまま通常の数字
   7958   # 入力として扱う。
   7959   local i=${#KEYS[@]}; ((i&&i--))
   7960   local flag=$((KEYS[i]&_ble_decode_MaskFlag))
   7961   if ! [[ :$1: == *:always:* || flag -ne 0 || $_ble_complete_menu_arg ]]; then
   7962     ble/widget/menu_complete/exit-default
   7963     return "$?"
   7964   fi
   7965 
   7966   local code=$((KEYS[i]&_ble_decode_MaskChar))
   7967   ((48<=code&&code<=57)) || return 1
   7968   local ret; ble/util/c2s "$code"; local ch=$ret
   7969   ((_ble_complete_menu_arg=10#0$_ble_complete_menu_arg$ch))
   7970 
   7971   # 番号が範囲内になければ頭から数字を削っていく
   7972   local count=${#_ble_complete_menu_items[@]}
   7973   while ((_ble_complete_menu_arg>count)); do
   7974     ((_ble_complete_menu_arg=10#0${_ble_complete_menu_arg:1}))
   7975   done
   7976   if ! ((_ble_complete_menu_arg)); then
   7977     [[ :$1: == *:nobell:* ]] ||
   7978       ble/widget/.bell 'menu: out of range'
   7979     return 0
   7980   fi
   7981 
   7982   # 移動
   7983   ble/complete/menu#select "$((_ble_complete_menu_arg-1))"
   7984 }
   7985 
   7986 ## @fn ble/widget/menu/append-arg/.is-argument [opts]
   7987 ##   @param[in,opt] opts
   7988 function ble/widget/menu/append-arg/.is-argument {
   7989   local i=${#KEYS[@]}; ((i&&i--))
   7990   local flag=$((KEYS[i]&_ble_decode_MaskFlag))
   7991   local code=$((KEYS[i]&_ble_decode_MaskChar))
   7992   [[ :$1: == *:always:* ]] || ((flag)) || return 1
   7993   ((48<=code&&code<=57))
   7994 }
   7995 
   7996 #------------------------------------------------------------------------------
   7997 #
   7998 # auto-complete
   7999 #
   8000 
   8001 function ble/complete/auto-complete/initialize {
   8002   local ret
   8003   ble-decode-kbd/generate-keycode auto_complete_enter
   8004   _ble_complete_KCODE_ENTER=$ret
   8005 }
   8006 ble/complete/auto-complete/initialize
   8007 
   8008 function ble/highlight/layer:region/mark:auto_complete/get-face {
   8009   face=auto_complete
   8010 }
   8011 
   8012 _ble_complete_ac_type=
   8013 _ble_complete_ac_comp1=
   8014 _ble_complete_ac_cand=
   8015 _ble_complete_ac_word=
   8016 _ble_complete_ac_insert=
   8017 _ble_complete_ac_suffix=
   8018 
   8019 ## @fn ble/complete/auto-complete/enter type comp1 suggest cand word [insert suffix]
   8020 ##   @param[in] type
   8021 ##     c ... 接頭辞補完
   8022 ##     h ... 履歴による接頭辞補完。c と同じ取り扱い
   8023 ##     m ... 部分文字列補完
   8024 ##     a ... 曖昧補完(1文字目確定)
   8025 ##     A ... 曖昧補完
   8026 ##   @param[in] comp1
   8027 ##     補完開始点
   8028 ##   @param[in] suggest
   8029 ##     提示文字列
   8030 ##   @param[in] cand
   8031 ##     元の単語
   8032 ##   @param[in] word
   8033 ##     挿入文字列(確定前)
   8034 ##   @param[in,opt] insert
   8035 ##     挿入文字列(確定時)。省略時は word と同じと見做されます。
   8036 ##   @param[in] suffix
   8037 ##     接尾挿入文字列。省略時は空文字列と見做されます。
   8038 ##
   8039 ##   @var[in] _ble_edit_ind
   8040 ##     提示文字列挿入位置を指定します。
   8041 ##   @var[out] _ble_edit_mark
   8042 ##     提示文字列の終端点を返します。
   8043 ##
   8044 function ble/complete/auto-complete/enter {
   8045   local type=$1 COMP1=$2 suggest=$3 cand=$4 word=$5 insert1=${6-$5} suffix=${7-}
   8046 
   8047   local limit=$((bleopt_line_limit_length))
   8048   if ((limit&&${#_ble_edit_str}+${#suggest}>limit)); then
   8049     # 文字数制限に引っかかる場合には単純に auto-complete は失敗する
   8050     return 1
   8051   fi
   8052 
   8053   # 提示
   8054   local insert; ble-edit/content/replace-limited "$_ble_edit_ind" "$_ble_edit_ind" "$suggest" nobell
   8055   ((_ble_edit_mark=_ble_edit_ind+${#suggest}))
   8056 
   8057   _ble_complete_ac_type=$type
   8058   _ble_complete_ac_comp1=$COMP1
   8059   _ble_complete_ac_cand=$cand
   8060   _ble_complete_ac_word=$word
   8061   _ble_complete_ac_insert=$insert1
   8062   _ble_complete_ac_suffix=$suffix
   8063 
   8064   _ble_edit_mark_active=auto_complete
   8065   ble/decode/keymap/push auto_complete
   8066   ble-decode-key "$_ble_complete_KCODE_ENTER" # dummy key input to record keyboard macros
   8067   return 0
   8068 }
   8069 
   8070 ## @fn ble/complete/auto-complete/source:history/.search-light text
   8071 ##   !string もしくは !?string を用いて履歴の検索を行います
   8072 ##   @param[in] text
   8073 ##   @var[out] ret
   8074 function ble/complete/auto-complete/source:history/.search-light {
   8075   [[ $_ble_history_prefix ]] && return 1
   8076 
   8077   local text=$1
   8078   [[ ! $text ]] && return 1
   8079 
   8080   # !string による一致を試みる
   8081   #   string には [$wordbreaks] は含められない。? はOK
   8082   local wordbreaks="<>();&|:$_ble_term_IFS"
   8083   local word= expand
   8084   if [[ $text != [-0-9#?!]* ]]; then
   8085     word=${text%%[$wordbreaks]*}
   8086     command='!'$word ble/util/assign expand 'ble/edit/hist_expanded/.core' &>/dev/null || return 1
   8087     if [[ $expand == "$text"* ]]; then
   8088       ret=$expand
   8089       return 0
   8090     fi
   8091   fi
   8092 
   8093   # !?string による一致を試みる
   8094   #   string には "?" は含められない
   8095   if [[ $word != "$text" ]]; then
   8096     # ? を含まない最長一致部分
   8097     local fragments; ble/string#split fragments '?' "$text"
   8098     local frag longest_fragments len=0; longest_fragments=('')
   8099     for frag in "${fragments[@]}"; do
   8100       local len1=${#frag}
   8101       ((len1>len&&(len=len1))) && longest_fragments=()
   8102       ((len1==len)) && ble/array#push longest_fragments "$frag"
   8103     done
   8104 
   8105     for frag in "${longest_fragments[@]}"; do
   8106       command='!?'$frag ble/util/assign expand 'ble/edit/hist_expanded/.core' &>/dev/null || return 1
   8107       [[ $expand == "$text"* ]] || continue
   8108       ret=$expand
   8109       return 0
   8110     done
   8111   fi
   8112 
   8113   return 1
   8114 }
   8115 
   8116 _ble_complete_ac_history_needle=
   8117 _ble_complete_ac_history_index=
   8118 _ble_complete_ac_history_start=
   8119 ## @fn ble/complete/auto-complete/source:history/.search-heavy text
   8120 ##   @var[out] ret
   8121 function ble/complete/auto-complete/source:history/.search-heavy {
   8122   local text=$1
   8123 
   8124   local count; ble/history/get-count -v count
   8125   local start=$((count-1))
   8126   local index=$((count-1))
   8127   local needle=$text
   8128 
   8129   # 途中からの検索再開
   8130   ((start==_ble_complete_ac_history_start)) &&
   8131     [[ $needle == "$_ble_complete_ac_history_needle"* ]] &&
   8132     index=$_ble_complete_ac_history_index
   8133 
   8134   local isearch_time=0 isearch_ntask=1
   8135   local isearch_opts=head
   8136   [[ :$comp_type: == *:sync:* ]] || isearch_opts=$isearch_opts:stop_check
   8137   ble/history/isearch-backward-blockwise "$isearch_opts"; local ext=$?
   8138   _ble_complete_ac_history_start=$start
   8139   _ble_complete_ac_history_index=$index
   8140   _ble_complete_ac_history_needle=$needle
   8141   ((ext)) && return "$ext"
   8142 
   8143   ble/history/get-edited-entry -v ret "$index"
   8144   return 0
   8145 }
   8146 
   8147 ## @fn ble/complete/auto-complete/source:history/.impl opts
   8148 ##   @param[in] opts
   8149 ##   @var[in] comp_type comp_text comp_index
   8150 function ble/complete/auto-complete/source:history/.impl {
   8151   local opts=$1
   8152   local searcher=.search-heavy
   8153   [[ :$opts: == *:light:*  ]] && searcher=.search-light
   8154 
   8155   local ret
   8156   ((_ble_edit_ind==${#_ble_edit_str})) || return 1
   8157   ble/complete/auto-complete/source:history/"$searcher" "$_ble_edit_str" || return "$?" # 0, 1 or 148
   8158   local command=$ret
   8159   [[ $command == "$_ble_edit_str" ]] && return 1
   8160   ble/complete/auto-complete/enter h 0 "${command:${#_ble_edit_str}}" '' "$command"
   8161 }
   8162 function ble/complete/auto-complete/source:history {
   8163   [[ $bleopt_complete_auto_history ]] || return 1
   8164   ble/complete/auto-complete/source:history/.impl light; local ext=$?
   8165   ((ext==0||ext==148)) && return "$ext"
   8166 
   8167   [[ $_ble_history_prefix || $_ble_history_load_done ]] &&
   8168     ble/complete/auto-complete/source:history/.impl; local ext=$?
   8169   ((ext==0||ext==148)) && return "$ext"
   8170 }
   8171 
   8172 ## @fn ble/complete/auto-complete/source:syntax
   8173 ##   @var[in] comp_type comp_text comp_index
   8174 function ble/complete/auto-complete/source:syntax {
   8175   local sources
   8176   ble/complete/context:syntax/generate-sources "$comp_text" "$comp_index" &&
   8177     ble/complete/context/filter-prefix-sources || return 1
   8178 
   8179   # ble/complete/candidates/generate 設定
   8180   local bleopt_complete_contract_function_names=
   8181   local bleopt_complete_menu_style=$bleopt_complete_menu_style # source local settings
   8182   ((bleopt_complete_polling_cycle>25)) &&
   8183     local bleopt_complete_polling_cycle=25
   8184   local COMP1 COMP2 COMPS COMPV
   8185   local comps_flags comps_fixed
   8186   local cand_count cand_cand cand_word cand_pack
   8187   local cand_limit_reached=
   8188   ble/complete/candidates/generate; local ext=$?
   8189   [[ $COMPV ]] || return 1
   8190   ((ext)) && return "$ext"
   8191 
   8192   ((cand_count)) || return 1
   8193 
   8194   local word=${cand_word[0]} cand=${cand_cand[0]}
   8195   [[ $word == "$COMPS" ]] && return 1
   8196 
   8197   # addtail 等の修飾
   8198   local insert=$word suffix=
   8199   local ACTION=${cand_pack[0]%%:*}
   8200   if ble/is-function ble/complete/action:"$ACTION"/complete; then
   8201     local "${_ble_complete_cand_varnames[@]/%/=}" # WA #D1570 checked
   8202     ble/complete/cand/unpack "${cand_pack[0]}"
   8203     ble/complete/action:"$ACTION"/complete
   8204   fi
   8205 
   8206   local type= suggest=
   8207   if [[ $insert == "$COMPS"* ]]; then
   8208     # 入力候補が既に続きに入力されている時は提示しない
   8209     [[ ${comp_text:COMP1} == "$insert"* ]] && return 1
   8210 
   8211     type=c
   8212     suggest="${insert:${#COMPS}}"
   8213   else
   8214     case :$comp_type: in
   8215     (*:a:*) type=a ;;
   8216     (*:m:*) type=m ;;
   8217     (*:A:*) type=A ;;
   8218     (*)   type=r ;;
   8219     esac
   8220     suggest=" [$insert] "
   8221   fi
   8222   ble/complete/auto-complete/enter "$type" "$COMP1" "$suggest" "$cand" "$word" "$insert" "$suffix"
   8223 }
   8224 
   8225 _ble_complete_auto_source=(history syntax)
   8226 
   8227 ## @fn ble/complete/auto-complete.impl opts
   8228 ##   @param[in] opts
   8229 #      コロン区切りのオプションのリストです。
   8230 ##     sync   ユーザ入力があっても処理を中断しない事を指定します。
   8231 function ble/complete/auto-complete.impl {
   8232   local opts=$1
   8233   local comp_type=auto
   8234   [[ :$opts: == *:sync:* ]] && comp_type=${comp_type}:sync
   8235 
   8236   local comp_text=$_ble_edit_str comp_index=$_ble_edit_ind
   8237   [[ $comp_text ]] || return 0
   8238 
   8239   # menu-filter 編集領域内部では auto-complete は抑制する
   8240   if local beg end; ble/complete/menu/get-active-range "$_ble_edit_str" "$_ble_edit_ind"; then
   8241     ((_ble_edit_ind<end)) && return 0
   8242   fi
   8243 
   8244   local source
   8245   for source in "${_ble_complete_auto_source[@]}"; do
   8246     ble/complete/auto-complete/source:"$source"; local ext=$?
   8247     ((ext==0)) && break
   8248     ((ext==148)) && return "$ext"
   8249   done
   8250 }
   8251 
   8252 ## 背景関数 ble/complete/auto-complete.idle
   8253 function ble/complete/auto-complete.idle {
   8254   # ※特に上書きしなければ常に wait-user-input で抜ける。
   8255   ble/util/idle.wait-user-input
   8256 
   8257   [[ $bleopt_complete_auto_complete ]] || return 1
   8258   [[ $_ble_decode_keymap == emacs || $_ble_decode_keymap == vi_[ic]map ]] || return 0
   8259 
   8260   case $_ble_decode_widget_last in
   8261   (ble/widget/self-insert|ble/widget/magic-space|ble/widget/magic-slash) ;;
   8262   (ble/widget/complete|ble/widget/vi_imap/complete)
   8263     [[ :$bleopt_complete_auto_complete_opts: == *:suppress-after-complete:* ]] && return 0 ;;
   8264   (*) return 0 ;;
   8265   esac
   8266 
   8267   [[ $_ble_edit_str ]] || return 0
   8268 
   8269   # bleopt_complete_auto_delay だけ経過してから処理
   8270   ble/util/idle.sleep-until "$((_ble_idle_clock_start+bleopt_complete_auto_delay))" checked && return 0
   8271 
   8272   ble/complete/auto-complete.impl
   8273 }
   8274 
   8275 ## 背景関数 ble/complete/auto-menu.idle
   8276 function ble/complete/auto-menu.idle {
   8277   ble/util/idle.wait-user-input
   8278   [[ $_ble_complete_menu_active ]] && return 0
   8279   ((bleopt_complete_auto_menu>0)) || return 1
   8280 
   8281   case $_ble_decode_widget_last in
   8282   (ble/widget/self-insert|ble/widget/magic-slash) ;;
   8283   (ble/widget/complete) ;;
   8284   (ble/widget/vi_imap/complete) ;;
   8285   (ble/widget/auto_complete/self-insert) ;;
   8286   (*) return 0 ;;
   8287   esac
   8288 
   8289   [[ $_ble_edit_str ]] || return 0
   8290 
   8291   # bleopt_complete_auto_delay だけ経過してから処理
   8292   local until=$((_ble_idle_clock_start+bleopt_complete_auto_menu))
   8293   ble/util/idle.sleep-until "$until" checked && return 0
   8294 
   8295   ble/widget/complete auto_menu:show_menu:no-empty:no-bell
   8296 }
   8297 
   8298 ble/function#try ble/util/idle.push-background ble/complete/auto-complete.idle
   8299 ble/function#try ble/util/idle.push-background ble/complete/auto-menu.idle
   8300 
   8301 ## @widget auto-complete-enter
   8302 ##
   8303 ##   Note:
   8304 ##     キーボードマクロで自動補完を明示的に起動する時に用いる編集関数です。
   8305 ##     auto-complete.idle に於いて ble-decode-key を用いて
   8306 ##     キー auto_complete_enter を発生させ、
   8307 ##     再生時にはこのキーを通して自動補完が起動されます。
   8308 ##
   8309 function ble/widget/auto-complete-enter {
   8310   ble/complete/auto-complete.impl sync
   8311 }
   8312 function ble/widget/auto_complete/cancel {
   8313   ble/decode/keymap/pop
   8314   ble-edit/content/replace "$_ble_edit_ind" "$_ble_edit_mark" ''
   8315   _ble_edit_mark=$_ble_edit_ind
   8316   _ble_edit_mark_active=
   8317   _ble_complete_ac_insert=
   8318   _ble_complete_ac_suffix=
   8319 }
   8320 function ble/widget/auto_complete/insert {
   8321   ble/decode/keymap/pop
   8322   ble-edit/content/replace "$_ble_edit_ind" "$_ble_edit_mark" ''
   8323   _ble_edit_mark=$_ble_edit_ind
   8324 
   8325   local comp_text=$_ble_edit_str
   8326   local insert_beg=$_ble_complete_ac_comp1
   8327   local insert_end=$_ble_edit_ind
   8328   local insert=$_ble_complete_ac_insert
   8329   local suffix=$_ble_complete_ac_suffix
   8330   ble/complete/insert "$insert_beg" "$insert_end" "$insert" "$suffix"
   8331   blehook/invoke complete_insert
   8332 
   8333   _ble_edit_mark_active=
   8334   _ble_complete_ac_insert=
   8335   _ble_complete_ac_suffix=
   8336   ble/complete/menu/clear
   8337   ble-edit/content/clear-arg
   8338   return 0
   8339 }
   8340 function ble/widget/auto_complete/cancel-default {
   8341   ble/widget/auto_complete/cancel
   8342   ble/decode/widget/skip-lastwidget
   8343   ble/decode/widget/redispatch-by-keys "${KEYS[@]}"
   8344 }
   8345 
   8346 ## @fn ble/widget/auto_complete/self-insert/.is-magic-space
   8347 ##   @var[in] KEYS
   8348 ##   現在のキー入力が親 keymap で magic-space に対応するかどうかを判定します。
   8349 
   8350 function ble/widget/auto_complete/self-insert/.is-magic-space {
   8351   ((${#KEYS[@]}==1)) || return 1
   8352 
   8353   local ikeymap=$((${#_ble_decode_keymap_stack[@]}-1))
   8354   ((ikeymap>=0)) || return 1
   8355 
   8356   local dicthead=_ble_decode_${_ble_decode_keymap_stack[ikeymap]}_kmap_
   8357   builtin eval "local ent=\${$dicthead$_ble_decode_key__seq[KEYS[0]]-}"
   8358   local command=${ent#*:}
   8359   [[ $command == ble/widget/magic-space || $command == ble/widget/magic-slash ]]
   8360 }
   8361 
   8362 function ble/widget/auto_complete/self-insert {
   8363   if [[ $_ble_edit_overwrite_mode ]] || ble/widget/auto_complete/self-insert/.is-magic-space; then
   8364     ble/widget/auto_complete/cancel-default
   8365     return "$?"
   8366   fi
   8367 
   8368   local code; ble/widget/self-insert/.get-code
   8369   ((code==0)) && return 0
   8370 
   8371   local ret
   8372 
   8373   # もし挿入によって現在の候補が変わらないのであれば、
   8374   # 候補を表示したまま挿入を実行する。
   8375   ble/util/c2s "$code"; local ins=$ret
   8376   local comps_cur=${_ble_edit_str:_ble_complete_ac_comp1:_ble_edit_ind-_ble_complete_ac_comp1}
   8377   local comps_new=$comps_cur$ins
   8378   local processed=
   8379   if [[ $_ble_complete_ac_type == [ch] ]]; then
   8380     # c: 入力済み部分が補完結果の先頭に含まれる場合
   8381     #   挿入した後でも補完結果の先頭に含まれる場合、その文字数だけ確定。
   8382     if [[ $_ble_complete_ac_word == "$comps_new"* ]]; then
   8383       ((_ble_edit_ind+=${#ins}))
   8384 
   8385       # Note: 途中で完全一致した場合は tail を挿入せずに終了する事にする
   8386       [[ $_ble_complete_ac_word == "$comps_new" ]] && ble/widget/auto_complete/cancel
   8387       processed=1
   8388     fi
   8389   elif [[ $_ble_complete_ac_type == [rmaA] && $ins != [{,}] ]]; then
   8390     if local ret simple_flags simple_ibrace; ble/syntax:bash/simple-word/reconstruct-incomplete-word "$comps_new"; then
   8391       if ble/complete/source/eval-simple-word "$ret" single && local compv_new=$ret; then
   8392         # r: 遡って書き換わる時
   8393         #   挿入しても展開後に一致する時、そのまま挿入。
   8394         #   元から展開後に一致していない場合もあるが、その場合は一旦候補を消してやり直し。
   8395         # a: 曖昧一致の時
   8396         #   文字を挿入後に展開してそれが曖昧一致する時、そのまま挿入。
   8397 
   8398         local filter_type=head
   8399         case $_ble_complete_ac_type in
   8400         (*m*) filter_type=substr  ;;
   8401         (*a*) filter_type=hsubseq ;;
   8402         (*A*) filter_type=subseq  ;;
   8403         esac
   8404 
   8405         local comps_fixed=
   8406         local comp_filter_type
   8407         local comp_filter_pattern
   8408         ble/complete/candidates/filter#init "$filter_type" "$compv_new"
   8409         if ble/complete/candidates/filter#test "$_ble_complete_ac_cand"; then
   8410           local insert; ble-edit/content/replace-limited "$_ble_edit_ind" "$_ble_edit_ind" "$ins"
   8411           ((_ble_edit_ind+=${#insert},_ble_edit_mark+=${#insert}))
   8412           [[ $_ble_complete_ac_cand == "$compv_new" ]] &&
   8413             ble/widget/auto_complete/cancel
   8414           processed=1
   8415         fi
   8416       fi
   8417     fi
   8418   fi
   8419 
   8420   if [[ $processed ]]; then
   8421     # notify dummy insertion
   8422     local comp_text= insert_beg=0 insert_end=0 insert=$ins suffix=
   8423     blehook/invoke complete_insert
   8424     return 0
   8425   else
   8426     ble/widget/auto_complete/cancel
   8427     ble/decode/widget/skip-lastwidget
   8428     ble/decode/widget/redispatch-by-keys "${KEYS[@]}"
   8429   fi
   8430 }
   8431 
   8432 function ble/widget/auto_complete/insert-on-end {
   8433   if ((_ble_edit_mark==${#_ble_edit_str})); then
   8434     ble/widget/auto_complete/insert
   8435   else
   8436     ble/widget/auto_complete/cancel-default
   8437   fi
   8438 }
   8439 function ble/widget/auto_complete/insert-word {
   8440   local breaks=${bleopt_complete_auto_wordbreaks:-$_ble_term_IFS}
   8441   local rex='^['$breaks']*([^'$breaks']+['$breaks']*)?'
   8442   if [[ $_ble_complete_ac_type == [ch] ]]; then
   8443     local ins=${_ble_edit_str:_ble_edit_ind:_ble_edit_mark-_ble_edit_ind}
   8444     [[ $ins =~ $rex ]]
   8445     if [[ $BASH_REMATCH == "$ins" ]]; then
   8446       ble/widget/auto_complete/insert
   8447       return 0
   8448     else
   8449       local ins=$BASH_REMATCH
   8450 
   8451       # Note: 以下の様に _ble_edit_ind だけずらす。
   8452       #   <C>he<I>llo world<M> → <C>hello <I>world<M>
   8453       #   (<C> = comp1, <I> = _ble_edit_ind, <M> = _ble_edit_mark)
   8454       ((_ble_edit_ind+=${#ins}))
   8455 
   8456       # 通知
   8457       local comp_text=$_ble_edit_str
   8458       local insert_beg=$_ble_complete_ac_comp1
   8459       local insert_end=$_ble_edit_ind
   8460       local insert=${_ble_edit_str:insert_beg:insert_end-insert_beg}$ins
   8461       local suffix=
   8462       blehook/invoke complete_insert
   8463       return 0
   8464     fi
   8465   elif [[ $_ble_complete_ac_type == [rmaA] ]]; then
   8466     local ins=$_ble_complete_ac_insert
   8467     [[ $ins =~ $rex ]]
   8468     if [[ $BASH_REMATCH == "$ins" ]]; then
   8469       ble/widget/auto_complete/insert
   8470       return 0
   8471     else
   8472       local ins=$BASH_REMATCH
   8473 
   8474       # Note: 以下の様に内容を書き換える。
   8475       #   <C>hll<I> [hello world] <M> → <C>hello <I>world<M>
   8476       #   (<C> = comp1, <I> = _ble_edit_ind, <M> = _ble_edit_mark)
   8477       _ble_complete_ac_type=c
   8478       # Note: 内容としては短くなるので replace-limited は使わなくて良い。
   8479       ble-edit/content/replace "$_ble_complete_ac_comp1" "$_ble_edit_mark" "$_ble_complete_ac_insert"
   8480       ((_ble_edit_ind=_ble_complete_ac_comp1+${#ins},
   8481         _ble_edit_mark=_ble_complete_ac_comp1+${#_ble_complete_ac_insert}))
   8482 
   8483       # 通知
   8484       local comp_text=$_ble_edit_str
   8485       local insert_beg=$_ble_complete_ac_comp1
   8486       local insert_end=$_ble_edit_ind
   8487       local insert=$ins
   8488       local suffix=
   8489       blehook/invoke complete_insert
   8490 
   8491       return 0
   8492     fi
   8493   fi
   8494   return 1
   8495 }
   8496 function ble/widget/auto_complete/accept-line {
   8497   ble/widget/auto_complete/insert
   8498   ble-decode-key 13
   8499 }
   8500 function ble/widget/auto_complete/notify-enter {
   8501   ble/decode/widget/skip-lastwidget
   8502 }
   8503 function ble-decode/keymap:auto_complete/define {
   8504   ble-bind -f __defchar__ auto_complete/self-insert
   8505   ble-bind -f __default__ auto_complete/cancel-default
   8506   ble-bind -f __line_limit__ nop
   8507   ble-bind -f 'C-g'       auto_complete/cancel
   8508   ble-bind -f 'C-x C-g'   auto_complete/cancel
   8509   ble-bind -f 'C-M-g'     auto_complete/cancel
   8510   ble-bind -f S-RET       auto_complete/insert
   8511   ble-bind -f S-C-m       auto_complete/insert
   8512   ble-bind -f C-f         auto_complete/insert-on-end
   8513   ble-bind -f right       auto_complete/insert-on-end
   8514   ble-bind -f C-e         auto_complete/insert-on-end
   8515   ble-bind -f end         auto_complete/insert-on-end
   8516   ble-bind -f M-f         auto_complete/insert-word
   8517   ble-bind -f M-right     auto_complete/insert-word
   8518   ble-bind -f C-j         auto_complete/accept-line
   8519   ble-bind -f C-RET       auto_complete/accept-line
   8520   ble-bind -f auto_complete_enter auto_complete/notify-enter
   8521 }
   8522 
   8523 #------------------------------------------------------------------------------
   8524 #
   8525 # sabbrev
   8526 #
   8527 
   8528 # The following are variables defined in core-complete-def.sh:
   8529 #
   8530 # @var _ble_complete_sabbrev_wordwise
   8531 # @var _ble_complete_sabbrev_literal
   8532 
   8533 function ble/complete/sabbrev/.initialize-print {
   8534   sgr0= sgr1= sgr2= sgr3= sgro=
   8535   if [[ $flags == *c* || $flags != *n* && -t 1 ]]; then
   8536     local ret
   8537     ble/color/face2sgr command_function; sgr1=$ret
   8538     ble/color/face2sgr syntax_varname; sgr2=$ret
   8539     ble/color/face2sgr syntax_quoted; sgr3=$ret
   8540     ble/color/face2sgr argument_option; sgro=$ret
   8541     sgr0=$_ble_term_sgr0
   8542   fi
   8543 }
   8544 function ble/complete/sabbrev/.print-definition {
   8545   local key=$1 type=${2%%:*} value=${2#*:}
   8546   local option=
   8547   [[ $type != w ]] && option=$sgro'-'$type$sgr0' '
   8548 
   8549   local ret
   8550   ble/string#quote-word "$key" quote-empty:sgrq="$sgr3":sgr0="$sgr2"
   8551   key=$sgr2$ret$sgr0
   8552   ble/string#quote-word "$value" sgrq="$sgr3":sgr0="$sgr0"
   8553   value=$ret
   8554   ble/util/print "${sgr1}ble-sabbrev$sgr0 $option$key=$value"
   8555 }
   8556 
   8557 ## @fn ble/complete/sabbrev/register key value
   8558 ##   静的略語展開を登録します。
   8559 ##   @param[in] key value
   8560 ##
   8561 ## @fn ble/complete/sabbrev/list type [keys...]
   8562 ##   登録されている静的略語展開の一覧を表示します。
   8563 ##   @var[in] flags
   8564 ##
   8565 ## @fn ble/complete/sabbrev/reset type [keys...]
   8566 ##   登録されている静的略語展開を削除します。
   8567 ##   @var[in] flags
   8568 ##
   8569 ## @fn ble/complete/sabbrev/wordwise.get key
   8570 ##   静的略語展開の展開値を取得します。
   8571 ##   @param[in] key
   8572 ##   @var[out] ret
   8573 ##
   8574 
   8575 # Note: _ble_complete_sabbrev_wordwise は core-complete-def.sh で定義
   8576 function ble/complete/sabbrev/register {
   8577   local key=$1 value=$2
   8578   if [[ $value == [il]:* ]]; then
   8579     ble/gdict#set _ble_complete_sabbrev_literal "$key" "$value"
   8580     ble/gdict#unset _ble_complete_sabbrev_wordwise "$key"
   8581   else
   8582     ble/gdict#set _ble_complete_sabbrev_wordwise "$key" "$value"
   8583     ble/gdict#unset _ble_complete_sabbrev_literal "$key"
   8584   fi
   8585 }
   8586 function ble/complete/sabbrev/list {
   8587   local type=$1; shift
   8588   local keys ret; keys=("$@")
   8589   if ((${#keys[@]}==0)); then
   8590     if [[ $type ]]; then
   8591       # type が指定されている時は、その type の sabbrev だけ表示する
   8592       local dict=_ble_complete_sabbrev_wordwise
   8593       case $type in
   8594       ([wm]) dict=_ble_complete_sabbrev_wordwise ;;
   8595       ([il]) dict=_ble_complete_sabbrev_literal ;;
   8596       esac
   8597 
   8598       local ret key
   8599       ble/gdict#keys "$dict"
   8600       for key in "${ret[@]}"; do
   8601         ble/gdict#get "$dict" "$key" && [[ $ret == "$type":* ]] || continue
   8602         ble/array#push keys "$key"
   8603       done
   8604     else
   8605       ble/gdict#keys _ble_complete_sabbrev_wordwise
   8606       keys=("${ret[@]}")
   8607       ble/gdict#keys _ble_complete_sabbrev_literal
   8608       ble/array#push keys "${ret[@]}"
   8609     fi
   8610     ((${#keys[@]})) || return 0
   8611   fi
   8612 
   8613   local sgr0 sgr1 sgr2 sgr3 sgro
   8614   ble/complete/sabbrev/.initialize-print
   8615 
   8616   local key ext=0
   8617   for key in "${keys[@]}"; do
   8618     if ble/gdict#get _ble_complete_sabbrev_wordwise "$key"; then
   8619       ble/complete/sabbrev/.print-definition "$key" "$ret"
   8620     elif ble/gdict#get _ble_complete_sabbrev_literal "$key"; then
   8621       ble/complete/sabbrev/.print-definition "$key" "$ret"
   8622     else
   8623       ble/util/print "ble-sabbrev: $key: not found." >&2
   8624       ext=1
   8625     fi
   8626   done
   8627 
   8628   return "$ext"
   8629 }
   8630 function ble/complete/sabbrev/reset {
   8631   local type=$1; shift
   8632   if (($#)); then
   8633     local key
   8634     for key; do
   8635       ble/gdict#unset _ble_complete_sabbrev_wordwise "$key"
   8636       ble/gdict#unset _ble_complete_sabbrev_literal "$key"
   8637     done
   8638   elif [[ $type ]]; then
   8639     # type が指定されている時は、その type の sabbrev だけ削除する
   8640 
   8641     local dict=_ble_complete_sabbrev_wordwise
   8642     case $type in
   8643     ([wm]) dict=_ble_complete_sabbrev_wordwise ;;
   8644     ([il]) dict=_ble_complete_sabbrev_literal ;;
   8645     esac
   8646 
   8647     local ret key
   8648     ble/gdict#keys "$dict"
   8649     for key in "${ret[@]}"; do
   8650       ble/gdict#get "$dict" "$key" && [[ $ret == "$type":* ]] || continue
   8651       ble/gdict#unset "$dict" "$key"
   8652     done
   8653   else
   8654     ble/gdict#clear _ble_complete_sabbrev_wordwise
   8655     ble/gdict#clear _ble_complete_sabbrev_literal
   8656   fi
   8657   return 0
   8658 }
   8659 function ble/complete/sabbrev/wordwise.get {
   8660   local key=$1
   8661   ble/gdict#get _ble_complete_sabbrev_wordwise "$key"
   8662 }
   8663 function ble/complete/sabbrev/wordwise.get-keys {
   8664   local ret
   8665   ble/gdict#keys _ble_complete_sabbrev_wordwise
   8666   keys=("${ret[@]}")
   8667 }
   8668 
   8669 ## @fn ble/complete/sabbrev/literal.find str [opts]
   8670 ##   最長一致するリテラル略語とその値を取得します。
   8671 ##   @param[in] str
   8672 ##   @param[in,opt] opts
   8673 ##     コロン区切りのオプションです。
   8674 ##
   8675 ##     filter-by-patterns
   8676 ##       patterns 配列に指定されているパターンに一致する sabbrev だけを一致対象
   8677 ##       とします。
   8678 ##       @arr[in] patterns
   8679 ##
   8680 ##   @var[out] key
   8681 ##   @var[out] ret
   8682 ##
   8683 function ble/complete/sabbrev/literal.find {
   8684   key=
   8685   local ent= opts=$2 key1 ent1
   8686   ble/gdict#keys _ble_complete_sabbrev_literal
   8687   for key1 in "${ret[@]}"; do
   8688     ((${#key1}>${#key})) || continue
   8689 
   8690     ble/gdict#get _ble_complete_sabbrev_literal "$key1" || continue; ent1=$ret
   8691     [[ $1 == *"$key1" ]] || continue
   8692     if [[ $ent1 == l:* ]]; then
   8693       ble/string#match "${1%"$key1"}" $'(^|\n)[ \t]*$' || continue
   8694     fi
   8695 
   8696     [[ :$opts: == *:filter-by-patterns:* ]] &&
   8697       ((${#patterns[@]})) &&
   8698       ! ble/complete/string#match-patterns "$key1" "${patterns[@]}" &&
   8699       continue
   8700 
   8701     key=$key1 ent=$ent1
   8702   done
   8703 
   8704   ret=$ent
   8705   [[ $key ]]
   8706 }
   8707 
   8708 ## @fn ble/complete/sabbrev/read-arguments/.set-type opt
   8709 ##   @var[in,out] flags type
   8710 function ble/complete/sabbrev/read-arguments/.set-type {
   8711   local new_type
   8712   case $1 in
   8713   (--type=wordwise | -w) new_type=w ;;
   8714   (--type=dynamic  | -m) new_type=m ;;
   8715   (--type=inline   | -i) new_type=i ;;
   8716   (--type=linewise | -l) new_type=l ;;
   8717   (*)
   8718     ble/util/print "ble-sabbrev: unknown sabbrev type '${1#--type=}'." >&2
   8719     flags=E$flags
   8720     return  1 ;;
   8721   esac
   8722 
   8723   if [[ $type && $type != "$new_type" ]]; then
   8724     ble/util/print "ble-sabbrev: arg $1: a conflicting sabbrev type (-$type) has already been specified." >&2
   8725     flags=E$flags
   8726   fi
   8727   type=$new_type
   8728 }
   8729 
   8730 ## @fn ble/complete/sabbrev/read-arguments args...
   8731 ##   @arr[out] specs print
   8732 ##   @var[out] flags type
   8733 function ble/complete/sabbrev/read-arguments {
   8734   specs=() print=()
   8735   flags= type=
   8736   while (($#)); do
   8737     local arg=$1; shift
   8738     if [[ $flags != L && $arg == -* ]]; then
   8739       case $arg in
   8740       (--)
   8741         flags=L$flags ;;
   8742       (--help)
   8743         flags=H$flags ;;
   8744       (--reset)
   8745         flags=r$flags
   8746       (--color|--color=always)
   8747         flags=c${flags//[cn]} ;;
   8748       (--color=never)
   8749         flags=n${flags//[cn]} ;;
   8750       (--color=auto)
   8751         flags=${flags//[cn]} ;;
   8752       (--color=*)
   8753         ble/util/print "ble-sabbrev: unknown color type '$arg'." >&2
   8754         flags=E$flags ;;
   8755       (--type=*)
   8756         ble/complete/sabbrev/read-arguments/.set-type "$arg" ;;
   8757       (--type)
   8758         if ((!$#)); then
   8759           ble/util/print "ble-sabbrev: option argument for '$arg' is missing" >&2
   8760           flags=E$flags
   8761         else
   8762           ble/complete/sabbrev/read-arguments/.set-type "--type=$1"; shift
   8763         fi ;;
   8764       (--*)
   8765         ble/util/print "ble-sabbrev: unknown option '$arg'." >&2
   8766         flags=E$flags ;;
   8767       (-*)
   8768         local i n=${#arg} c
   8769         for ((i=1;i<n;i++)); do
   8770           c=${arg:i:1}
   8771           case $c in
   8772           ([wmil]) ble/complete/sabbrev/read-arguments/.set-type "-$c" ;;
   8773           (r) flags=r$flags ;;
   8774           (*)
   8775             ble/util/print "ble-sabbrev: unknown option '-$c'." >&2
   8776             flags=E$flags ;;
   8777           esac
   8778         done ;;
   8779       esac
   8780     else
   8781       if [[ $arg == ?*=* ]]; then
   8782         ble/array#push specs "$arg"
   8783       else
   8784         ble/array#push print "$arg"
   8785       fi
   8786     fi
   8787   done
   8788   return 0
   8789 }
   8790 
   8791 ## @fn ble-sabbrev key=value
   8792 ##   静的略語展開を登録します。
   8793 function ble-sabbrev {
   8794   local flags type specs print
   8795   ble/complete/sabbrev/read-arguments "$@"
   8796   if [[ $flags == *H* || $flags == *E* ]]; then
   8797     [[ $flags == *E* ]] && ble/util/print
   8798     ble/util/print-lines \
   8799       'usage: ble-sabbrev [--type=TYPE|-wmil] [KEY=VALUE]...' \
   8800       'usage: ble-sabbrev [-r|--reset] [--type=TYPE|-wmil|KEY...]' \
   8801       'usage: ble-sabbrev [--color[=auto|always|never]] [--type=TYPE|-wmil|KEY...]' \
   8802       'usage: ble-sabbrev --help' \
   8803       '     Register sabbrev expansion.' \
   8804       '' \
   8805       'OPTIONS' \
   8806       '  -w, --type=wordwise   replace matching word.' \
   8807       '  -m, --type=dynamic    run command and replace matching word.' \
   8808       '  -i, --type=inline     replace matching suffix.' \
   8809       '  -l, --type=linewise   replace matching line.' \
   8810       '' \
   8811       '  -r, --reset           remove specified set of sabbrev.' \
   8812       '' \
   8813       '  --color=always         enable color output.' \
   8814       '  --color=never          disable color output.' \
   8815       '  --color, --color=auto  automatically determine color output (default).' \
   8816       ''
   8817     [[ ! $flags == *E* ]]; return "$?"
   8818   fi
   8819 
   8820   local ext=0
   8821   if ((${#specs[@]}==0||${#print[@]})); then
   8822     if [[ $flags == *r* ]]; then
   8823       ble/complete/sabbrev/reset "$type" "${print[@]}"
   8824     else
   8825       ble/complete/sabbrev/list "$type" "${print[@]}"
   8826     fi || ext=$?
   8827   fi
   8828 
   8829   local spec key value
   8830   for spec in "${specs[@]}"; do
   8831     # spec は key=value の形式
   8832     key=${spec%%=*} value=${spec#*=}
   8833     ble/complete/sabbrev/register "$key" "${type:-w}:$value"
   8834   done
   8835   return "$ext"
   8836 }
   8837 
   8838 ## @fn ble/complete/sabbrev/locate-key rex_source_type
   8839 ## @var[out] pos
   8840 ## @var[in] comp_index comp_text
   8841 function ble/complete/sabbrev/locate-key {
   8842   pos=$comp_index
   8843   local rex_source_type='^('$1')$'
   8844   local sources src asrc
   8845   ble/complete/context:syntax/generate-sources
   8846   for src in "${sources[@]}"; do
   8847     ble/string#split-words asrc "$src"
   8848     [[ ${asrc[0]} =~ $rex_source_type ]] || continue
   8849 
   8850     if [[ ${asrc[0]} == argument ]]; then
   8851       # source:argument かつ変数代入形式の時は右辺を sabbrev の対象とする。
   8852       # wtype を (恰も declare の引数の様に) ATTR_VAR にして find-rhs を呼び出
   8853       # す。
   8854       local wtype=$_ble_attr_VAR wbeg=${asrc[1]} wlen=$((comp_index-asrc[1])) ret
   8855       ble/syntax:bash/find-rhs "$wtype" "$wbeg" "$wlen" long-option &&
   8856         asrc[0]=rhs asrc[1]=$ret
   8857     fi
   8858 
   8859     if [[ ${asrc[0]} == rhs ]]; then
   8860       # 変数代入形式の右辺では : で区切った最後のフィールドを対象とする。最後の
   8861       # unquoted colon まで読み飛ばす。[Note: 文法情報の参照をすればより厳密に
   8862       # 決定できるかもしれないが今は実装しない]
   8863       local rex_element
   8864       ble/syntax:bash/simple-word/get-rex_element :
   8865       local rex='^:*('$rex_element':+)'
   8866       [[ ${_ble_edit_str:asrc[1]:comp_index-asrc[1]} =~ $rex ]] &&
   8867         ((asrc[1]+=${#BASH_REMATCH}))
   8868     fi
   8869 
   8870     ((asrc[1]<pos)) && pos=${asrc[1]}
   8871   done
   8872   ((pos<comp_index))
   8873 }
   8874 
   8875 ## @fn ble/complete/sabbrev/expand [opts]
   8876 ##   @param[in,opt] opts
   8877 ##     コロン区切りのオプションです。
   8878 ##
   8879 ##     wordwise
   8880 ##     literal
   8881 ##       それぞれ wordwise sabbrev および literal sabbrev (line, inline) の展開
   8882 ##       を実行します。どちらも指定されていない場合は両方実行します。
   8883 ##
   8884 ##     pattern=PATTERN
   8885 ##       これが一つ以上指定されていた時は何れかの PATTERN で指定された名前を持
   8886 ##       つ sabbrev だけ有効にします。
   8887 ##
   8888 ##     strip-slash
   8889 ##       展開後の末尾に含まれる / を削除します。
   8890 ##
   8891 ##     type-status
   8892 ##       実行した sabbrev の種類を終了ステータスで返します。
   8893 ##
   8894 function ble/complete/sabbrev/expand {
   8895   local opts=$1
   8896   local comp_index=$_ble_edit_ind comp_text=$_ble_edit_str
   8897 
   8898   [[ :$opts: == *:wordwise:* || :$opts: == *:literal:* ]] ||
   8899     opts=$opts:wordwise:literal
   8900 
   8901   local -a patterns=()
   8902   local ret
   8903   ble/opts#extract-all-optargs "$opts" pattern &&
   8904     patterns=("${ret[@]}")
   8905 
   8906   # wordwise sabbrev と literal sabbrev を両方検索しより長い一致を選択する
   8907   local key1= ent1= key2= ent2=
   8908   if [[ :$opts: == *:wordwise:* ]]; then
   8909     local pos key ret
   8910     ble/complete/sabbrev/locate-key 'file|command|argument|variable:w|wordlist:.*|sabbrev|rhs' &&
   8911       key=${_ble_edit_str:pos:comp_index-pos} &&
   8912       ble/complete/sabbrev/wordwise.get "$key" &&
   8913       { ((${#patterns[@]}==0)) || ble/complete/string#match-patterns "$key" "${patterns[@]}"; } &&
   8914       key1=$key ent1=$ret
   8915   fi
   8916   if [[ :$opts: == *:literal:* ]]; then
   8917     local key ret
   8918     ble/complete/sabbrev/literal.find "${_ble_edit_str::comp_index}" filter-by-patterns &&
   8919       key2=$key ent2=$ret
   8920   fi
   8921   if ((${#key1}>=${#key2})); then
   8922     local key=$key1 ent=$ent1
   8923   else
   8924     local key=$key2 ent=$ent2
   8925   fi
   8926   [[ $key ]] || return 1
   8927 
   8928   local type=${ent%%:*} value=${ent#*:}
   8929 
   8930   local exit=0
   8931   if [[ :$opts: == *:type-status:* ]]; then
   8932     local ret
   8933     ble/util/s2c "$type"
   8934     exit=$ret
   8935   fi
   8936 
   8937   case $type in
   8938   ([wil])
   8939     [[ :$opts: == *:strip-slash:* ]] && value=${value%/}
   8940     local pos=$((comp_index-${#key}))
   8941     ble/widget/.replace-range "$pos" "$comp_index" "$value"
   8942     ((_ble_edit_ind=pos+${#value})) ;;
   8943   (m)
   8944     # prepare completion context
   8945     local comp_type= comps_flags= comps_fixed=
   8946     local COMP1=$pos COMP2=$pos COMPS=$key COMPV=
   8947     ble/complete/candidates/comp_type#read-rl-variables
   8948 
   8949     local flag_force_fignore=
   8950     local flag_source_filter=1
   8951 
   8952     # construct cand_pack
   8953     local cand_count cand_cand cand_word cand_pack
   8954     ble/complete/candidates/clear
   8955     local COMP_PREFIX=
   8956 
   8957     # local settings
   8958     local bleopt_sabbrev_menu_style=$bleopt_complete_menu_style
   8959     local bleopt_sabbrev_menu_opts=
   8960 
   8961     # generate candidates
   8962     #   COMPREPLY に候補を追加してもらうか、
   8963     #   或いは手動で ble/complete/cand/yield 等を呼び出してもらう。
   8964     local -a COMPREPLY=()
   8965     builtin eval -- "$value"
   8966 
   8967     local cand action=word "${_ble_complete_yield_varnames[@]/%/=}" # WA #D1570 checked
   8968     ble/complete/cand/yield.initialize "$action"
   8969     for cand in "${COMPREPLY[@]}"; do
   8970       ble/complete/cand/yield "$action" "$cand" ""
   8971     done
   8972 
   8973     if ((cand_count==0)); then
   8974       return 1
   8975     elif ((cand_count==1)); then
   8976       local value=${cand_word[0]}
   8977       [[ :$opts: == *:strip-slash:* ]] && value=${value%/}
   8978       ble/widget/.replace-range "$pos" "$comp_index" "$value"
   8979       ((_ble_edit_ind=pos+${#value}))
   8980       return "$exit"
   8981     fi
   8982 
   8983     # Note: 既存の内容 (key) は削除する
   8984     ble/widget/.replace-range "$pos" "$comp_index" ''
   8985 
   8986     local bleopt_complete_menu_style=$bleopt_sabbrev_menu_style
   8987     local menu_common_part=
   8988     ble/complete/menu/show || return "$?"
   8989     [[ :$bleopt_sabbrev_menu_opts: == *:enter_menu:* ]] &&
   8990       ble/complete/menu-complete/enter "$bleopt_sabbrev_menu_opts"
   8991     return 147 ;;
   8992   (*) return 1 ;;
   8993   esac
   8994   return "$exit"
   8995 }
   8996 function ble/widget/sabbrev-expand {
   8997   if ! ble/complete/sabbrev/expand; then
   8998     ble/widget/.bell
   8999     return 1
   9000   fi
   9001 }
   9002 
   9003 # sabbrev の補完候補
   9004 function ble/complete/action:sabbrev/initialize { CAND=$value; }
   9005 function ble/complete/action:sabbrev/complete { :; }
   9006 function ble/complete/action:sabbrev/init-menu-item {
   9007   local ret; ble/color/face2g command_alias; g=$ret
   9008   show=$INSERT
   9009 }
   9010 function ble/complete/action:sabbrev/get-desc {
   9011   local ret; ble/complete/sabbrev/wordwise.get "$INSERT"
   9012   desc="$desc_sgrt(sabbrev)$desc_sgr0 $ret"
   9013 }
   9014 function ble/complete/source:sabbrev {
   9015   local opts=$bleopt_complete_source_sabbrev_opts
   9016   [[ ! $COMPS && :$opts: == *:no-empty-completion:* ]] && return 1
   9017 
   9018   local keys; ble/complete/sabbrev/wordwise.get-keys "$opts"
   9019 
   9020   local filter_type=$comp_filter_type
   9021   [[ $filter_type == none ]] && filter_type=head
   9022   local comps_fixed=
   9023 
   9024   # フィルタリング用設定を COMPS で再初期化
   9025   local comp_filter_type
   9026   local comp_filter_pattern
   9027   ble/complete/candidates/filter#init "$filter_type" "$COMPS"
   9028   local cand action=sabbrev "${_ble_complete_yield_varnames[@]/%/=}" # WA #D1570 checked
   9029   ble/complete/cand/yield.initialize "$action"
   9030   for cand in "${keys[@]}"; do
   9031     ble/complete/candidates/filter#test "$cand" || continue
   9032     ble/complete/string#match-patterns "$cand" "${_ble_complete_source_sabbrev_ignore[@]}" && continue
   9033 
   9034     # filter で除外されない為に cand には評価後の値を入れる必要がある。
   9035     local ret simple_flags simple_ibrace
   9036     ble/syntax:bash/simple-word/reconstruct-incomplete-word "$cand" &&
   9037       ble/complete/source/eval-simple-word "$ret" single || continue
   9038 
   9039     local value=$ret # referenced in "ble/complete/action:sabbrev/initialize"
   9040     local flag_source_filter=1
   9041     ble/complete/cand/yield "$action" "$cand"
   9042   done
   9043 }
   9044 
   9045 function ble/complete/alias/expand {
   9046   local pos comp_index=$_ble_edit_ind comp_text=$_ble_edit_str
   9047   ble/complete/sabbrev/locate-key 'command'
   9048   ((pos<comp_index)) || return 1
   9049 
   9050   local word=${_ble_edit_str:pos:comp_index-pos}
   9051   local ret; ble/alias#expand "$word"
   9052   [[ $ret != "$word" ]] || return 1
   9053   ble/widget/.replace-range "$pos" "$comp_index" "$ret"
   9054   return 0
   9055 }
   9056 
   9057 #------------------------------------------------------------------------------
   9058 #
   9059 # dabbrev
   9060 #
   9061 
   9062 _ble_complete_dabbrev_original=
   9063 _ble_complete_dabbrev_regex1=
   9064 _ble_complete_dabbrev_regex2=
   9065 _ble_complete_dabbrev_index=
   9066 _ble_complete_dabbrev_pos=
   9067 _ble_complete_dabbrev_stack=()
   9068 
   9069 function ble/complete/dabbrev/.show-status.fib {
   9070   local index='!'$((_ble_complete_dabbrev_index+1))
   9071   local nmatch=${#_ble_complete_dabbrev_stack[@]}
   9072   local needle=$_ble_complete_dabbrev_original
   9073   local text="(dabbrev#$nmatch: << $index) \`$needle'"
   9074 
   9075   local pos=$1
   9076   if [[ $pos ]]; then
   9077     local count; ble/history/get-count
   9078     local percentage=$((count?pos*1000/count:1000))
   9079     text="$text searching... @$pos ($((percentage/10)).$((percentage%10))%)"
   9080   fi
   9081 
   9082   ((fib_ntask)) && text="$text *$fib_ntask"
   9083 
   9084   ble/edit/info/show text "$text"
   9085 }
   9086 function ble/complete/dabbrev/show-status {
   9087   local fib_ntask=${#_ble_util_fiberchain[@]}
   9088   ble/complete/dabbrev/.show-status.fib
   9089 }
   9090 function ble/complete/dabbrev/erase-status {
   9091   ble/edit/info/default
   9092 }
   9093 
   9094 ## @fn ble/complete/dabbrev/initialize-variables
   9095 function ble/complete/dabbrev/initialize-variables {
   9096   # Note: _ble_term_IFS を前置しているので ! や ^ が先頭に来ない事は保証される
   9097   local wordbreaks; ble/complete/get-wordbreaks
   9098   _ble_complete_dabbrev_wordbreaks=$wordbreaks
   9099 
   9100   local left=${_ble_edit_str::_ble_edit_ind}
   9101   local original=${left##*[$wordbreaks]}
   9102   local p1=$((_ble_edit_ind-${#original})) p2=$_ble_edit_ind
   9103   _ble_edit_mark=$p1
   9104   _ble_edit_ind=$p2
   9105   _ble_complete_dabbrev_original=$original
   9106 
   9107   local ret; ble/string#escape-for-extended-regex "$original"
   9108   local needle='(^|['$wordbreaks'])'$ret
   9109   _ble_complete_dabbrev_regex1=$needle
   9110   _ble_complete_dabbrev_regex2='('$needle'[^'$wordbreaks']*).*'
   9111 
   9112   local index; ble/history/get-index
   9113   _ble_complete_dabbrev_index=$index
   9114   _ble_complete_dabbrev_pos=${#_ble_edit_str}
   9115 
   9116   _ble_complete_dabbrev_stack=()
   9117 }
   9118 
   9119 function ble/complete/dabbrev/reset {
   9120   local original=$_ble_complete_dabbrev_original
   9121   ble-edit/content/replace "$_ble_edit_mark" "$_ble_edit_ind" "$original"
   9122   ((_ble_edit_ind=_ble_edit_mark+${#original}))
   9123   _ble_edit_mark_active=
   9124 }
   9125 
   9126 ## @fn ble/complete/dabbrev/search-in-history-entry line index
   9127 ##   @param[in] line
   9128 ##     検索対象の内容を指定します。
   9129 ##   @param[in] index
   9130 ##     検索対象の履歴番号を指定します。
   9131 ##   @var[in] dabbrev_current_match
   9132 ##     現在の一致内容を指定します。
   9133 ##   @var[in] dabbrev_pos
   9134 ##     履歴項目内の検索開始位置を指定します。
   9135 ##   @var[out] dabbrev_match
   9136 ##     一致した場合に、一致した内容を返します。
   9137 ##   @var[out] dabbrev_match_pos
   9138 ##     一致した場合に、一致範囲の最後の位置を返します。
   9139 ##     これは次の検索開始位置に対応します。
   9140 function ble/complete/dabbrev/search-in-history-entry {
   9141   local line=$1 index=$2
   9142 
   9143   # 現在編集している行自身には一致させない。
   9144   local index_editing; ble/history/get-index -v index_editing
   9145   if ((index!=index_editing)); then
   9146     local pos=$dabbrev_pos
   9147     while [[ ${line:pos} && ${line:pos} =~ $_ble_complete_dabbrev_regex2 ]]; do
   9148       local rematch1=${BASH_REMATCH[1]} rematch2=${BASH_REMATCH[2]}
   9149       local match=${rematch1:${#rematch2}}
   9150       if [[ $match && $match != "$dabbrev_current_match" ]]; then
   9151         dabbrev_match=$match
   9152         dabbrev_match_pos=$((${#line}-${#BASH_REMATCH}+${#match}))
   9153         return 0
   9154       else
   9155         ((pos++))
   9156       fi
   9157     done
   9158   fi
   9159 
   9160   return 1
   9161 }
   9162 
   9163 function ble/complete/dabbrev/.search.fib {
   9164   if [[ ! $fib_suspend ]]; then
   9165     local start=$_ble_complete_dabbrev_index
   9166     local index=$_ble_complete_dabbrev_index
   9167     local pos=$_ble_complete_dabbrev_pos
   9168 
   9169     # Note: start は最初に backward-history-search が呼ばれる時の index。
   9170     #   backward-history-search が呼び出される前に index-- されるので、
   9171     #   start は最初から 1 減らして定義しておく。
   9172     #   これにより cyclic 検索で再度自分に一致する事が保証される。
   9173     # Note: start がこれで負になった時は "履歴項目の数" を設定する。
   9174     #   未だ "履歴" に登録されていない最新の項目 (_ble_history_edit
   9175     #   には格納されている) も検索の対象とするため。
   9176     ((--start>=0)) || ble/history/get-count -v start
   9177   else
   9178     local start index pos; builtin eval -- "$fib_suspend"
   9179     fib_suspend=
   9180   fi
   9181 
   9182   local dabbrev_match=
   9183   local dabbrev_pos=$pos
   9184   local dabbrev_current_match=${_ble_edit_str:_ble_edit_mark:_ble_edit_ind-_ble_edit_mark}
   9185 
   9186   local line; ble/history/get-edited-entry -v line "$index"
   9187   if ! ble/complete/dabbrev/search-in-history-entry "$line" "$index"; then
   9188     ((index--,dabbrev_pos=0))
   9189 
   9190     local isearch_time=0
   9191     local isearch_opts=stop_check:cyclic
   9192 
   9193     # 条件による一致判定の設定
   9194     isearch_opts=$isearch_opts:condition
   9195     local dabbrev_original=$_ble_complete_dabbrev_original
   9196     local dabbrev_regex1=$_ble_complete_dabbrev_regex1
   9197     local needle='[[ $LINE =~ $dabbrev_regex1 ]] && ble/complete/dabbrev/search-in-history-entry "$LINE" "$INDEX"'
   9198     # Note: glob で先に枝刈りした方が速い。
   9199     [[ $dabbrev_original ]] && needle='[[ $LINE == *"$dabbrev_original"* ]] && '$needle
   9200 
   9201     # 検索進捗の表示
   9202     isearch_opts=$isearch_opts:progress
   9203     local isearch_progress_callback=ble/complete/dabbrev/.show-status.fib
   9204 
   9205     ble/history/isearch-backward-blockwise "$isearch_opts"; local ext=$?
   9206     ((ext==148)) && fib_suspend="start=$start index=$index pos=$pos"
   9207     if ((ext)); then
   9208       if ((${#_ble_complete_dabbrev_stack[@]})); then
   9209         ble/widget/.bell # 周回したので鳴らす
   9210         return 0
   9211       else
   9212         # 一つも見つからない場合
   9213         return "$ext"
   9214       fi
   9215     fi
   9216   fi
   9217 
   9218   local rec=$_ble_complete_dabbrev_index,$_ble_complete_dabbrev_pos,$_ble_edit_ind,$_ble_edit_mark
   9219   ble/array#push _ble_complete_dabbrev_stack "$rec:$_ble_edit_str"
   9220   local insert; ble-edit/content/replace-limited "$_ble_edit_mark" "$_ble_edit_ind" "$dabbrev_match"
   9221   ((_ble_edit_ind=_ble_edit_mark+${#insert}))
   9222 
   9223   ((index>_ble_complete_dabbrev_index)) &&
   9224     ble/widget/.bell # 周回
   9225   _ble_complete_dabbrev_index=$index
   9226   _ble_complete_dabbrev_pos=$dabbrev_match_pos
   9227 
   9228   ble/textarea#redraw
   9229 }
   9230 function ble/complete/dabbrev/next.fib {
   9231   ble/complete/dabbrev/.search.fib; local ext=$?
   9232   if ((ext==0)); then
   9233     _ble_edit_mark_active=insert
   9234     ble/complete/dabbrev/.show-status.fib
   9235   elif ((ext==148)); then
   9236     ble/complete/dabbrev/.show-status.fib
   9237   else
   9238     ble/widget/.bell
   9239     ble/widget/dabbrev/exit
   9240     ble/complete/dabbrev/reset
   9241     fib_kill=1
   9242   fi
   9243   return "$ext"
   9244 }
   9245 function ble/widget/dabbrev-expand {
   9246   ble/complete/dabbrev/initialize-variables
   9247   ble/decode/keymap/push dabbrev
   9248   ble/util/fiberchain#initialize ble/complete/dabbrev
   9249   ble/util/fiberchain#push next
   9250   ble/util/fiberchain#resume
   9251 }
   9252 function ble/widget/dabbrev/next {
   9253   ble/util/fiberchain#push next
   9254   ble/util/fiberchain#resume
   9255 }
   9256 function ble/widget/dabbrev/prev {
   9257   if ((${#_ble_util_fiberchain[@]})); then
   9258     # 処理中の物がある時はひとつずつ取り消す
   9259     local ret; ble/array#pop _ble_util_fiberchain
   9260     if ((${#_ble_util_fiberchain[@]})); then
   9261       ble/util/fiberchain#resume
   9262     else
   9263       ble/complete/dabbrev/show-status
   9264     fi
   9265   elif ((${#_ble_complete_dabbrev_stack[@]})); then
   9266     # 前の一致がある時は遡る
   9267     local ret; ble/array#pop _ble_complete_dabbrev_stack
   9268     local rec str=${ret#*:}
   9269     ble/string#split rec , "${ret%%:*}"
   9270     ble-edit/content/reset-and-check-dirty "$str"
   9271     _ble_edit_ind=${rec[2]}
   9272     _ble_edit_mark=${rec[3]}
   9273     _ble_complete_dabbrev_index=${rec[0]}
   9274     _ble_complete_dabbrev_pos=${rec[1]}
   9275     ble/complete/dabbrev/show-status
   9276   else
   9277     ble/widget/.bell
   9278     return 1
   9279   fi
   9280 }
   9281 function ble/widget/dabbrev/cancel {
   9282   if ((${#_ble_util_fiberchain[@]})); then
   9283     ble/util/fiberchain#clear
   9284     ble/complete/dabbrev/show-status
   9285   else
   9286     ble/widget/dabbrev/exit
   9287     ble/complete/dabbrev/reset
   9288   fi
   9289 }
   9290 function ble/widget/dabbrev/exit {
   9291   ble/decode/keymap/pop
   9292   _ble_edit_mark_active=
   9293   ble/complete/dabbrev/erase-status
   9294 }
   9295 function ble/widget/dabbrev/exit-default {
   9296   ble/widget/dabbrev/exit
   9297   ble/decode/widget/skip-lastwidget
   9298   ble/decode/widget/redispatch-by-keys "${KEYS[@]}"
   9299 }
   9300 function ble/widget/dabbrev/accept-line {
   9301   ble/widget/dabbrev/exit
   9302   ble-decode-key 13
   9303 }
   9304 function ble-decode/keymap:dabbrev/define {
   9305   ble-bind -f __default__ 'dabbrev/exit-default'
   9306   ble-bind -f __line_limit__ nop
   9307   ble-bind -f 'C-g'       'dabbrev/cancel'
   9308   ble-bind -f 'C-x C-g'   'dabbrev/cancel'
   9309   ble-bind -f 'C-M-g'     'dabbrev/cancel'
   9310   ble-bind -f C-r         'dabbrev/next'
   9311   ble-bind -f C-s         'dabbrev/prev'
   9312   ble-bind -f RET         'dabbrev/exit'
   9313   ble-bind -f C-m         'dabbrev/exit'
   9314   ble-bind -f C-RET       'dabbrev/accept-line'
   9315   ble-bind -f C-j         'dabbrev/accept-line'
   9316 }
   9317 
   9318 #------------------------------------------------------------------------------
   9319 # default cmdinfo/complete
   9320 
   9321 ## @fn ble/cmdinfo/complete/yield-flag cmd flags [opts]
   9322 ##   "-${flags}X" の X を補完する。
   9323 ##   @param[in] cmd
   9324 ##     mandb 検索に用いるコマンド名
   9325 ##   @param[in] flags
   9326 ##     可能なオプション文字の一覧
   9327 ##   @param[in,opt] opts
   9328 ##     コロン区切りのリスト
   9329 ##
   9330 ##     dedup[=XFLAGS]
   9331 ##       既に指定されている排他的フラグは除外します。XFLAGS には排他的フラグの
   9332 ##       集合を指定します。省略または空文字列を指定した場合は全てのフラグが排他
   9333 ##       的であると見なします。
   9334 ##
   9335 ##     cancel-on-empty
   9336 ##       候補のフラグがもうない場合に補完候補生成をキャンセルします。既定では、
   9337 ##       候補のフラグがもうない場合には現在入力済みの内容で補完確定します。
   9338 ##
   9339 ##     hasarg=AFLAGS
   9340 ##       オプション引数を持つフラグの集合を指定します。この文字集合に含まれる文
   9341 ##       字が既に COMPV に指定されている場合にはオプションは補完しません。
   9342 ##
   9343 ##   @var[in] COMPV
   9344 
   9345 ble/complete/action#inherit-from mandb.flag mandb
   9346 function ble/complete/action:mandb.flag/initialize {
   9347   ble/complete/action:mandb/initialize "$@"
   9348 }
   9349 function ble/complete/action:mandb.flag/init-menu-item {
   9350   ble/complete/action:mandb/init-menu-item
   9351   prefix=${CAND::!!PREFIX_LEN}
   9352 }
   9353 
   9354 function ble/cmdinfo/complete/yield-flag {
   9355   local cmd=$1 flags=$2 opts=$3
   9356   [[ $COMPV != [!-]* && $COMPV != --* && $flags ]] || return 1
   9357 
   9358   local "${_ble_complete_yield_varnames[@]/%/=}" # WA #D1570 checked
   9359   ble/complete/cand/yield.initialize mandb
   9360 
   9361   # opts dedup
   9362   local ret
   9363   if [[ ${COMPV:1} ]] && ble/opts#extract-last-optarg "$opts" dedup "$flags"; then
   9364     local specified_flags=${ret//[!"${COMPV:1}"]}
   9365     flags=${flags//["$specified_flags"]}
   9366   fi
   9367 
   9368   if ble/opts#extract-last-optarg "$opts" hasarg; then
   9369     [[ $COMPV == -*["$ret"]* ]] && return 1
   9370   fi
   9371 
   9372   if [[ ! $flags ]]; then
   9373     [[ :$opts: == *:cancel-on-empty:* ]] && return 1
   9374 
   9375     # 候補のフラグがもうない場合は現在の内容で一意確定
   9376     local "${_ble_complete_yield_varnames[@]/%/=}" # WA #D1570 checked
   9377     ble/complete/cand/yield.initialize word
   9378     ble/complete/cand/yield word "$COMPV"
   9379     return "$?"
   9380   fi
   9381 
   9382   local COMP_PREFIX=$COMPV
   9383 
   9384   # desc が mandb に見つかればそれを適用する
   9385   local has_desc=
   9386   if local ret; ble/complete/mandb/load-cache "$cmd"; then
   9387     local entry fs=$_ble_term_FS
   9388     for entry in "${ret[@]}"; do
   9389       ((cand_iloop++%bleopt_complete_polling_cycle==0)) &&
   9390         ble/complete/check-cancel && return 148
   9391       local option=${entry%%$fs*}
   9392       [[ $option == -? && ${option:1} == ["$flags"] ]] || continue
   9393       ble/complete/cand/yield mandb.flag "$COMPV${option:1}" "$entry"
   9394       [[ $entry == *"$fs"*"$fs"*"$fs"?* ]] && has_desc=1
   9395       flags=${flags//${option:1}}
   9396     done
   9397     [[ $has_desc ]] && bleopt complete_menu_style=desc
   9398   fi
   9399 
   9400   # 見つからない場合には説明なしで生成する
   9401   local i
   9402   for ((i=0;i<${#flags};i++)); do
   9403     ble/complete/cand/yield mandb.flag "$COMPV${flags:i:1}"
   9404   done
   9405 }
   9406 
   9407 
   9408 # action:cdpath (action:file を修正)
   9409 
   9410 function ble/complete/action:cdpath/initialize {
   9411   DATA=$cdpath_basedir
   9412   ble/complete/action:file/initialize
   9413 }
   9414 function ble/complete/action:cdpath/complete {
   9415   CAND=$DATA$CAND ble/complete/action:file/complete
   9416 }
   9417 function ble/complete/action:cdpath/init-menu-item {
   9418   ble/color/face2g cmdinfo_cd_cdpath; g=$ret
   9419   if [[ :$comp_type: == *:vstat:* ]]; then
   9420     if [[ -h $CAND ]]; then
   9421       suffix='@'
   9422     elif [[ -d $CAND ]]; then
   9423       suffix='/'
   9424     fi
   9425   fi
   9426 }
   9427 function ble/complete/action:cdpath/get-desc {
   9428   local sgr0=$_ble_term_sgr0 sgr1= sgr2=
   9429   local g ret g1 g2
   9430   ble/syntax/highlight/getg-from-filename "$DATA$CAND"; g1=$g
   9431   [[ $g1 ]] || { ble/color/face2g filename_warning; g1=$ret; }
   9432   ((g2=g1^_ble_color_gflags_Revert))
   9433   ble/color/g2sgr "$g1"; sgr1=$ret
   9434   ble/color/g2sgr "$g2"; sgr2=$ret
   9435   ble/string#escape-for-display "$DATA$CAND" sgr1="$sgr2":sgr0="$sgr1"
   9436   local filename=$sgr1$ret$sgr0
   9437 
   9438   CAND=$DATA$CAND ble/complete/action:file/get-desc
   9439   desc="CDPATH $filename ($desc)"
   9440 }
   9441 
   9442 ## @fn ble/cmdinfo/complete:cd/.impl
   9443 ##   @remarks
   9444 ##     この実装は ble/complete/source:file/.impl を元にしている。
   9445 ##     実装に関する注意点はこの元の実装も参照の事。
   9446 function ble/cmdinfo/complete:cd/.impl {
   9447   local type=$1
   9448   [[ $comps_flags == *v* ]] || return 1
   9449 
   9450   case $type in
   9451   (pushd|popd|dirs)
   9452     # todo: -- より後の [-+]* は処理しない
   9453     # todo: 実は -N/+N はオプションではなく通常引数
   9454     if [[ $COMPV == [-+]* ]]; then
   9455       local old_cand_count=$cand_count
   9456 
   9457       # yield options
   9458       local flags=n
   9459       [[ $type == dirs ]] && flags=clpv
   9460       ble/cmdinfo/complete/yield-flag "$type" "$flags" dedup:hasarg=0123456789:cancel-on-empty
   9461 
   9462       local "${_ble_complete_yield_varnames[@]/%/=}" # WA #D1570 checked
   9463       ble/complete/cand/yield.initialize word
   9464       local ret
   9465       ble/color/face2sgr-ansi filename_directory
   9466       local sgr1=$ret sgr0=$'\e[m'
   9467 
   9468       # yield -N/+N
   9469       local i n=${#DIRSTACK[@]}
   9470       for ((i=0;i<n;i++)); do
   9471         local cand=${COMPV::1}$i
   9472         [[ $cand == "$COMPV"* ]] || continue
   9473         local j=$i; [[ $COMPV == -* ]] && j=$((n-1-i))
   9474         ble/complete/cand/yield word "$cand" "DIRSTACK[$j] $sgr1${DIRSTACK[j]}$sgr0"
   9475       done
   9476 
   9477       # yield - and -- for pushd
   9478       if [[ $type == pushd ]]; then
   9479         [[ ${OLDPWD:-} && $COMPV == - ]] &&
   9480           ble/complete/cand/yield word - "OLDPWD $sgr1$OLDPWD$sgr0"
   9481         [[ -- == "$COMPV"* ]] &&
   9482           ble/complete/cand/yield word -- '(indicate the end of options)'
   9483       fi
   9484 
   9485       ((cand_count!=old_cand_count)) && return 0
   9486     fi
   9487     [[ $type == pushd ]] || return 0 ;;
   9488   (*)
   9489     # todo: -- より後の [-+]* は処理しない
   9490     if [[ $COMPV == -* ]]; then
   9491       local list=LP
   9492       ((_ble_bash>=40200)) && list=${list}e
   9493       ((_ble_bash>=40300)) && list=${list}@
   9494       ble/cmdinfo/complete/yield-flag cd "$list" dedup
   9495 
   9496       local "${_ble_complete_yield_varnames[@]/%/=}" # WA #D1570 checked
   9497       ble/complete/cand/yield.initialize word
   9498       if [[ ${OLDPWD:-} && $COMPV == - ]]; then
   9499         local ret
   9500         ble/color/face2sgr-ansi filename_directory
   9501         local sgr1=$ret sgr0=$'\e[m'
   9502         ble/complete/cand/yield word - "OLDPWD $sgr1$OLDPWD$sgr0"
   9503       fi
   9504       [[ -- == "$COMPV"* ]] &&
   9505         ble/complete/cand/yield word -- '(indicate the end of options)'
   9506 
   9507       return 0
   9508     fi
   9509   esac
   9510 
   9511   [[ :$comp_type: != *:[maA]:* && $COMPV =~ ^.+/ ]] && COMP_PREFIX=${BASH_REMATCH[0]}
   9512   [[ :$comp_type: == *:[maA]:* && ! $COMPV ]] && return 1
   9513 
   9514   if [[ ! $CDPATH ]]; then
   9515     ble/complete/source:dir
   9516     return "$?"
   9517   fi
   9518 
   9519   ble/complete/source:tilde; local ext=$?
   9520   ((ext==148||ext==0)) && return "$ext"
   9521 
   9522   local is_pwd_visited= is_cdpath_generated=
   9523   "${_ble_util_set_declare[@]//NAME/visited}" # WA #D1570 checked
   9524 
   9525   # Check CDPATH first
   9526   local name names; ble/string#split names : "$CDPATH"
   9527   for name in "${names[@]}"; do
   9528     [[ $name ]] || continue
   9529     name=${name%/}/
   9530 
   9531     # カレントディレクトリが CDPATH に含まれている時は action=file で登録
   9532     local action=cdpath
   9533     [[ ${name%/} == . || ${name%/} == "${PWD%/}" ]] &&
   9534       is_pwd_visited=1 action=file
   9535 
   9536     local -a candidates=()
   9537     local ret cand
   9538     ble/complete/source:file/.construct-pathname-pattern "$COMPV"
   9539     ble/complete/util/eval-pathname-expansion "$name$ret"; (($?==148)) && return 148
   9540     ble/complete/source/test-limit "${#ret[@]}" || return 1
   9541     for cand in "${ret[@]}"; do
   9542       ((cand_iloop++%bleopt_complete_polling_cycle==0)) &&
   9543         ble/complete/check-cancel && return 148
   9544       [[ $cand && -d $cand ]] || continue
   9545       [[ $cand == / ]] || cand=${cand%/}
   9546       cand=${cand#"$name"}
   9547 
   9548       ble/set#contains visited "$cand" && continue
   9549       ble/set#add visited "$cand"
   9550       ble/array#push candidates "$cand"
   9551     done
   9552     ((${#candidates[@]})) || continue
   9553 
   9554     local flag_source_filter=1
   9555     local cdpath_basedir=$name
   9556     ble/complete/cand/yield-filenames "$action" "${candidates[@]}"
   9557     [[ $action == cdpath ]] && is_cdpath_generated=1
   9558   done
   9559   [[ $is_cdpath_generated ]] &&
   9560       bleopt complete_menu_style=desc
   9561 
   9562   # Check PWD next
   9563   # カレントディレクトリが CDPATH に含まれていなかった時に限り通常の候補生成
   9564   if [[ ! $is_pwd_visited ]]; then
   9565     local -a candidates=()
   9566     local ret cand
   9567     ble/complete/source:file/.construct-pathname-pattern "$COMPV"
   9568     ble/complete/util/eval-pathname-expansion "${ret%/}/"; (($?==148)) && return 148
   9569     ble/complete/source/test-limit "${#ret[@]}" || return 1
   9570     for cand in "${ret[@]}"; do
   9571       ((cand_iloop++%bleopt_complete_polling_cycle==0)) &&
   9572         ble/complete/check-cancel && return 148
   9573       [[ -d $cand ]] || continue
   9574       [[ $cand == / ]] || cand=${cand%/}
   9575       ble/set#contains visited "$cand" && continue
   9576       ble/array#push candidates "$cand"
   9577     done
   9578     local flag_source_filter=1
   9579     ble/complete/cand/yield-filenames file "${candidates[@]}"
   9580   fi
   9581 }
   9582 function ble/cmdinfo/complete:cd {
   9583   ble/cmdinfo/complete:cd/.impl cd
   9584 }
   9585 function ble/cmdinfo/complete:pushd {
   9586   ble/cmdinfo/complete:cd/.impl pushd
   9587 }
   9588 function ble/cmdinfo/complete:popd {
   9589   ble/cmdinfo/complete:cd/.impl popd
   9590 }
   9591 function ble/cmdinfo/complete:dirs {
   9592   ble/cmdinfo/complete:cd/.impl dirs
   9593 }
   9594 
   9595 blehook/invoke complete_load
   9596 blehook complete_load=
   9597 return 0