sistema_progs

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

canvas.sh (115499B)


      1 #!/bin/bash
      2 
      3 ## @bleopt tab_width
      4 ##   タブの表示幅を指定します。
      5 ##
      6 ##   bleopt_tab_width= (既定)
      7 ##     空文字列を指定したときは $(tput it) を用います。
      8 ##   bleopt_tab_width=NUM
      9 ##     数字を指定したときはその値をタブの幅として用います。
     10 bleopt/declare -v tab_width ''
     11 function bleopt/check:tab_width {
     12   if [[ $value ]] && (((value=value)<=0)); then
     13     ble/util/print "bleopt: an empty string or a positive value is required for tab_width." >&2
     14     return 1
     15   fi
     16 }
     17 
     18 #------------------------------------------------------------------------------
     19 # ble/arithmetic
     20 
     21 ## ble/arithmetic/sum integer...
     22 ##   @var[out] ret
     23 function ble/arithmetic/sum {
     24   IFS=+ builtin eval 'let "ret=$*+0"'
     25 }
     26 
     27 #------------------------------------------------------------------------------
     28 # ble/util/c2w
     29 
     30 # ※注意 [ -~] の範囲の文字は全て幅1であるという事を仮定したコードが幾らかある
     31 #   もしこれらの範囲の文字を幅1以外で表示する端末が有ればそれらのコードを実装し
     32 #   直す必要がある。その様な変な端末があるとは思えないが。
     33 
     34 _ble_util_c2w=()
     35 _ble_util_c2w_cache=()
     36 function ble/util/c2w/clear-cache {
     37   _ble_util_c2w_cache=()
     38 }
     39 
     40 ## @bleopt char_width_mode
     41 ##   文字の表示幅の計算方法を指定します。
     42 ##     bleopt_char_width_mode=east
     43 ##       Unicode East_Asian_Width=A (Ambiguous) の文字幅を全て 2 とします
     44 ##     bleopt_char_width_mode=west
     45 ##       Unicode East_Asian_Width=A (Ambiguous) の文字幅を全て 1 とします
     46 ##     bleopt_char_width_mode=auto
     47 ##       east または west を自動判定します。
     48 ##     bleopt_char_width_mode=emacs
     49 ##       emacs で用いられている既定の文字幅の設定です
     50 ##     定義 ble/util/c2w:$bleopt_char_width_mode
     51 bleopt/declare -n char_width_mode auto
     52 function bleopt/check:char_width_mode {
     53   if ! ble/is-function "ble/util/c2w:$value"; then
     54     ble/util/print "bleopt: Invalid value char_width_mode='$value'. A function 'ble/util/c2w:$value' is not defined." >&2
     55     return 1
     56   fi
     57 
     58   case $value in
     59   (auto)
     60     _ble_unicode_c2w_ambiguous=1
     61     ble && ble/util/c2w:auto/test.buff first-line ;;
     62   (west) _ble_unicode_c2w_ambiguous=1 ;;
     63   (east) _ble_unicode_c2w_ambiguous=2 ;;
     64   esac
     65   ((_ble_prompt_version++))
     66   ble/util/c2w/clear-cache
     67 }
     68 
     69 ## @fn ble/util/c2w ccode
     70 ##   @var[out] ret
     71 function ble/util/c2w {
     72   ret=${_ble_util_c2w_cache[$1]:-${_ble_util_c2w[$1]}}
     73   if [[ ! $ret ]]; then
     74     "ble/util/c2w:$bleopt_char_width_mode" "$1"
     75     _ble_util_c2w_cache[$1]=$ret
     76   fi
     77 }
     78 ## @fn ble/util/c2w-edit ccode
     79 ##   編集画面での表示上の文字幅を返します。
     80 ##   @var[out] ret
     81 function ble/util/c2w-edit {
     82   local cs=${_ble_unicode_GraphemeCluster_ControlRepresentation[$1]}
     83   if [[ $cs ]]; then
     84     ret=${#cs}
     85   elif (($1<32||127<=$1&&$1<160)); then
     86     # 制御文字は ^? と表示される。
     87     ret=2
     88     # TAB は???
     89 
     90     # 128-159: M-^?
     91     ((128<=$1&&(ret=4)))
     92   else
     93     ble/util/c2w "$1"
     94   fi
     95 }
     96 ## @fn ble/util/s2w      text
     97 ## @fn ble/util/s2w-edit text [opts]
     98 ##   @param[in] text
     99 ##   @var[out] ret
    100 function ble/util/s2w-edit {
    101   local text=$1 iN=${#1} flags=$2 i
    102   ret=0
    103   for ((i=0;i<iN;i++)); do
    104     local c w cs cb extend
    105     ble/unicode/GraphemeCluster/match "$text" "$i" "$flags"
    106     ((ret+=w,i+=extend))
    107   done
    108 }
    109 function ble/util/s2w {
    110   ble/util/s2w-edit "$1" R
    111 }
    112 
    113 # ---- 文字種判定 ----
    114 
    115 #%< canvas.c2w.sh
    116 _ble_unicode_c2w_version=14
    117 _ble_unicode_c2w_ambiguous=1
    118 _ble_unicode_c2w_invalid=1
    119 _ble_unicode_c2w_custom=()
    120 
    121 bleopt/declare -n char_width_version auto
    122 function bleopt/check:char_width_version {
    123   if [[ $value == auto ]]; then
    124     ble && ble/util/c2w:auto/test.buff first-line
    125     ((_ble_prompt_version++))
    126     ble/util/c2w/clear-cache
    127     return 0
    128   elif local ret; ble/unicode/c2w/version2index "$value"; then
    129     _ble_unicode_c2w_version=$ret
    130     ((_ble_prompt_version++))
    131     ble/util/c2w/clear-cache
    132     return 0
    133   else
    134     ble/util/print "bleopt: char_width_version: invalid value '$value'." >&2
    135     return 1
    136   fi
    137 }
    138 
    139 # wcwdith 例外 (Unicode 特性からは予想できない値を持っている物)
    140 # この表は make/canvas.c2w.wcwidth.exe compare_eaw の出力より。
    141 _ble_unicode_c2w_custom[173]=1                    # U+00ad       Cf A SHY(soft-hyphen)
    142 let '_ble_unicode_c2w_custom['{1536..1541}']=1'   # U+0600..0605 Cf 1 アラブの数字?
    143 _ble_unicode_c2w_custom[1757]=1                   # U+06dd       Cf 1 ARABIC END OF AYAH
    144 _ble_unicode_c2w_custom[1807]=1                   # U+070f       Cf 1 SYRIAC ABBREVIATION MARK
    145 _ble_unicode_c2w_custom[2274]=1                   # U+08e2       Cf 1 ARABIC DISPUTED END OF AYAH
    146 _ble_unicode_c2w_custom[69821]=1                  # U+110bd      Cf 1 KAITHI NUMBER SIGN
    147 _ble_unicode_c2w_custom[69837]=1                  # U+110cd      Cf 1 KAITHI NUMBER SIGN ABOVE
    148 let '_ble_unicode_c2w_custom['{12872..12879}']=2' # U+3248..324f No A 囲み文字10-80 (8字)
    149 let '_ble_unicode_c2w_custom['{19904..19967}']=2' # U+4dc0..4dff So 1 易経記号 (6字)
    150 let '_ble_unicode_c2w_custom['{4448..4607}']=0'   # U+1160..11ff Lo 1 HANGUL JAMO (160字)
    151 let '_ble_unicode_c2w_custom['{55216..55238}']=0' # U+d7b0..d7c6 Lo 1 HANGUL JAMO EXTENDED-B (1) (23字)
    152 let '_ble_unicode_c2w_custom['{55243..55291}']=0' # U+d7cb..d7fb Lo 1 HANGUL JAMO EXTENDED-B (2) (49字)
    153 
    154 function ble/unicode/c2w {
    155   local c=$1
    156   ret=${_ble_unicode_c2w_custom[c]}
    157   [[ $ret ]] && return 0
    158 
    159   ret=${_ble_unicode_c2w[c]}
    160   if [[ ! $ret ]]; then
    161     ret=${_ble_unicode_c2w_index[c<0x20000?c>>8:((c>>12)-32+512)]}
    162     if [[ $ret == *:* ]]; then
    163       local l=${ret%:*} u=${ret#*:} m
    164       while ((l+1<u)); do
    165         ((m=(l+u)/2))
    166         if ((_ble_unicode_c2w_ranges[m]<=c)); then
    167           l=$m
    168         else
    169           u=$m
    170         fi
    171       done
    172       ret=${_ble_unicode_c2w[_ble_unicode_c2w_ranges[l]]}
    173     fi
    174   fi
    175   ret=${_ble_unicode_c2w_UnicodeVersionMapping[ret*_ble_unicode_c2w_UnicodeVersionCount+_ble_unicode_c2w_version]}
    176   ((ret<0)) && ret=${_ble_unicode_c2w_invalid:-$((-ret))}
    177   ((ret==3)) &&
    178     ret=${_ble_unicode_c2w_ambiguous:-1}
    179   return 0
    180 }
    181 
    182 
    183 ## @const _ble_unicode_EmojiStatus_*
    184 ##
    185 ## @var _ble_unicode_EmojiStatus_xmaybe
    186 ## @arr _ble_unicode_EmojiStatus
    187 ## @arr _ble_unicode_EmojiStatus_ranges
    188 ## @var _ble_unicode_EmojiStatus_version
    189 ## @bleopt emoji_version
    190 ##
    191 ##   ファイル src/canvas.emoji.sh は以下のコマンドで生成する。
    192 ##   $ ./make_command.sh update-emoji-database
    193 ##
    194 #%< canvas.emoji.sh
    195 
    196 bleopt/declare -v emoji_width 2
    197 bleopt/declare -v emoji_opts ri
    198 
    199 function bleopt/check:emoji_version {
    200   local ret
    201   if ! ble/unicode/EmojiStatus/version2index "$value"; then
    202     local rex='^0*([0-9]+)\.0*([0-9]+)$'
    203     if ! [[ $value =~ $rex ]]; then
    204       ble/util/print "bleopt: Invalid format for emoji_version: '$value'." >&2
    205       return 1
    206     else
    207       ble/util/print "bleopt: Unsupported emoji_version: '$value'." >&2
    208       return 1
    209     fi
    210   fi
    211 
    212   _ble_unicode_EmojiStatus_version=$ret
    213   ((_ble_prompt_version++))
    214   ble/util/c2w/clear-cache
    215   return 0
    216 }
    217 function bleopt/check:emoji_width { ble/util/c2w/clear-cache; }
    218 
    219 # 2021-06-18 unqualified は絵文字に含めない。多くの場合は既定では通常文字で
    220 # EPVS によって絵文字として表示する様である。component は肌の色(Extend) と髪
    221 # (Pictographic) の2種類がある。取り敢えず幅2で計算する。
    222 _ble_unicode_EmojiStatus_xIsEmoji='ret&&ret!=_ble_unicode_EmojiStatus_Unqualified'
    223 function bleopt/check:emoji_opts {
    224   _ble_unicode_EmojiStatus_xIsEmoji='ret'
    225   [[ :$value: != *:unqualified:* ]] &&
    226     _ble_unicode_EmojiStatus_xIsEmoji=$_ble_unicode_EmojiStatus_xIsEmoji'&&ret!=_ble_unicode_EmojiStatus_Unqualified'
    227   local rex=':min=U\+([0-9a-fA-F]+):'
    228   [[ :$value: =~ $rex ]] &&
    229     _ble_unicode_EmojiStatus_xIsEmoji=$_ble_unicode_EmojiStatus_xIsEmoji'&&code>=0x'${BASH_REMATCH[1]}
    230   ((_ble_prompt_version++))
    231   ble/util/c2w/clear-cache
    232   return 0
    233 }
    234 
    235 function ble/unicode/EmojiStatus {
    236   local code=$1 V=$_ble_unicode_EmojiStatus_version
    237   ret=${_ble_unicode_EmojiStatus[code]}
    238   if [[ ! $ret ]]; then
    239     ret=$_ble_unicode_EmojiStatus_None
    240     if ((_ble_unicode_EmojiStatus_xmaybe)); then
    241       local l=0 u=${#_ble_unicode_EmojiStatus_ranges[@]} m
    242       while ((l+1<u)); do
    243         ((_ble_unicode_EmojiStatus_ranges[m=(l+u)/2]<=code?(l=m):(u=m)))
    244       done
    245       ret=${_ble_unicode_EmojiStatus[_ble_unicode_EmojiStatus_ranges[l]]:-0}
    246     fi
    247     _ble_unicode_EmojiStatus[code]=$ret
    248   fi
    249   ((ret=ret))
    250   return 0
    251 }
    252 
    253 ## @fn ble/util/c2w/is-emoji code
    254 ##   @param[in] code
    255 function ble/util/c2w/is-emoji {
    256   local code=$1 ret
    257   ble/unicode/EmojiStatus "$code"
    258   ((_ble_unicode_EmojiStatus_xIsEmoji))
    259 }
    260 
    261 # ---- char_width_mode ----
    262 
    263 function ble/util/c2w:west {
    264   if [[ $bleopt_emoji_width ]] && ble/util/c2w/is-emoji "$1"; then
    265     ((ret=bleopt_emoji_width))
    266   else
    267     ble/unicode/c2w "$1"
    268   fi
    269 }
    270 
    271 function ble/util/c2w:east {
    272   if [[ $bleopt_emoji_width ]] && ble/util/c2w/is-emoji "$1"; then
    273     ((ret=bleopt_emoji_width))
    274   else
    275     ble/unicode/c2w "$1"
    276   fi
    277 }
    278 
    279 ## @fn ble/util/c2w:emacs
    280 ##   emacs-24.2.1 default char-width-table
    281 ##   @var[out] ret
    282 _ble_util_c2w_emacs_wranges=(
    283  162 164 167 169 172 173 176 178 180 181 182 183 215 216 247 248 272 273 276 279
    284  280 282 284 286 288 290 293 295 304 305 306 308 315 316 515 516 534 535 545 546
    285  555 556 608 618 656 660 722 723 724 725 768 769 770 772 775 777 779 780 785 787
    286  794 795 797 801 805 806 807 813 814 815 820 822 829 830 850 851 864 866 870 872
    287  874 876 898 900 902 904 933 934 959 960 1042 1043 1065 1067 1376 1396 1536 1540 1548 1549
    288  1551 1553 1555 1557 1559 1561 1563 1566 1568 1569 1571 1574 1576 1577 1579 1581 1583 1585 1587 1589
    289  1591 1593 1595 1597 1599 1600 1602 1603 1611 1612 1696 1698 1714 1716 1724 1726 1734 1736 1739 1740
    290  1742 1744 1775 1776 1797 1799 1856 1857 1858 1859 1898 1899 1901 1902 1903 1904)
    291 
    292 function ble/util/c2w:emacs {
    293   local code=$1
    294 
    295   # bash-4.0 bug workaround
    296   #   中で使用している変数に日本語などの文字列が入っているとエラーになる。
    297   #   その値を参照していなくても、その分岐に入らなくても関係ない。
    298   #   なので ret に予め適当な値を設定しておく事にする。
    299   ret=1
    300   ((code<0xA0)) && return 0
    301 
    302   if [[ $bleopt_emoji_width ]] && ble/util/c2w/is-emoji "$code"; then
    303     ((ret=bleopt_emoji_width))
    304     return 0
    305   fi
    306 
    307   # Note: ble/unicode/c2w を使うとずれる。考えてみれば emacs は各端末
    308   # で同じテーブルを使って実装しているので ble/unicode/c2w 等外部の物
    309   # を参照せずに実装するべきなのであった。
    310   #ble/unicode/c2w "$1"
    311   #((ret==3)) || return 0
    312 
    313   # 実は EastAsianWidth=A だけ考えれば良いので下の条件式は単純化できる筈
    314   local al=0 ah=0 tIndex=
    315   ((
    316     0x3100<=code&&code<0xA4D0||0xAC00<=code&&code<0xD7A4?(
    317       ret=2
    318     ):(0x2000<=code&&code<0x2700?(
    319       tIndex=0x0100+code-0x2000
    320     ):(
    321       al=code&0xFF,
    322       ah=code/256,
    323       ah==0x00?(
    324         tIndex=al
    325       ):(ah==0x03?(
    326         ret=0xFF&((al-0x91)&~0x20),
    327         ret=ret<25&&ret!=17?2:1
    328       ):(ah==0x04?(
    329         ret=al==1||0x10<=al&&al<=0x50||al==0x51?2:1
    330       ):(ah==0x11?(
    331         ret=al<0x60?2:1
    332       ):(ah==0x2e?(
    333         ret=al>=0x80?2:1
    334       ):(ah==0x2f?(
    335         ret=2
    336       ):(ah==0x30?(
    337         ret=al!=0x3f?2:1
    338       ):(ah==0xf9||ah==0xfa?(
    339         ret=2
    340       ):(ah==0xfe?(
    341         ret=0x30<=al&&al<0x70?2:1
    342       ):(ah==0xff?(
    343         ret=0x01<=al&&al<0x61||0xE0<=al&&al<=0xE7?2:1
    344       ):(ret=1))))))))))
    345     ))
    346   ))
    347 
    348   [[ $tIndex ]] || return 0
    349 
    350   if ((tIndex<_ble_util_c2w_emacs_wranges[0])); then
    351     ret=1
    352     return 0
    353   fi
    354 
    355   local l=0 u=${#_ble_util_c2w_emacs_wranges[@]} m
    356   while ((l+1<u)); do
    357     ((_ble_util_c2w_emacs_wranges[m=(l+u)/2]<=tIndex?(l=m):(u=m)))
    358   done
    359   ((ret=((l&1)==0)?2:1))
    360   return 0
    361 }
    362 
    363 #%< canvas.c2w.musl.sh
    364 
    365 function ble/util/c2w:musl {
    366   local code=$1
    367 
    368   ret=1
    369   ((code&&code<0x300)) && return 0
    370 
    371   if [[ $bleopt_emoji_width ]] && ble/util/c2w/is-emoji "$code"; then
    372     ((ret=bleopt_emoji_width))
    373     return 0
    374   fi
    375 
    376   local l=0 u=${#_ble_util_c2w_musl_ranges[@]} m
    377   while ((l+1<u)); do
    378     ((_ble_util_c2w_musl_ranges[m=(l+u)/2]<=code?(l=m):(u=m)))
    379   done
    380   ret=${_ble_util_c2w_musl[_ble_util_c2w_musl_ranges[l]]}
    381 }
    382 
    383 _ble_util_c2w_auto_update_x0=0
    384 _ble_util_c2w_auto_update_result=()
    385 _ble_util_c2w_auto_update_processing=0
    386 function ble/util/c2w:auto {
    387   if [[ $bleopt_emoji_width ]] && ble/util/c2w/is-emoji "$1"; then
    388     ((ret=bleopt_emoji_width))
    389   else
    390     ble/unicode/c2w "$1"
    391   fi
    392 }
    393 
    394 function ble/util/c2w:auto/check {
    395   [[ $bleopt_char_width_mode == auto || $bleopt_char_width_version == auto ]] &&
    396     ble/util/c2w:auto/test.buff
    397   return 0
    398 }
    399 
    400 function ble/util/c2w:auto/test.buff {
    401   local opts=$1
    402   local -a DRAW_BUFF=()
    403   local ret saved_pos=
    404 
    405   # 現在既に処理中の場合 DSR は省略。char_width_@=auto 等で一括して要
    406   # 求した時などに一回だけ実行する為。
    407   ((_ble_util_c2w_auto_update_processing)) && return 0
    408 
    409   [[ $_ble_attached ]] && { ble/canvas/panel/save-position goto-top-dock; saved_pos=$ret; }
    410   ble/canvas/put.draw "$_ble_term_sc"
    411   if ble/util/is-unicode-output; then
    412 
    413     local -a codes=(
    414       # index=0,1 [EastAsianWidth=A 判定]
    415       0x25bd 0x25b6
    416 
    417       # index=2..15 [Unicode version 判定] #D1645 #D1668
    418       #   判定用の文字コードは "source
    419       #   make/canvas.c2w.list-ucsver-detection-codes.sh" を用いて生
    420       #   成されたリストから選択した。新しい Unicode version が出たら
    421       #   再びこれを実行して判定コードを書く事になる。
    422       0x9FBC 0x9FC4 0x31B8 0xD7B0 0x3099
    423       0x9FCD 0x1F93B 0x312E 0x312F 0x16FE2
    424       0x32FF 0x31BB 0x9FFD 0x1B132)
    425 
    426     _ble_util_c2w_auto_update_processing=${#codes[@]}
    427     _ble_util_c2w_auto_update_result=()
    428     if [[ :$opts: == *:first-line:* ]]; then
    429       # 画面の右上で判定を行います。
    430       local cols=${COLUMNS:-80}
    431       local x0=$((cols-4)); ((x0<0)) && x0=0
    432       _ble_util_c2w_auto_update_x0=$x0
    433 
    434       local code index=0
    435       for code in "${codes[@]}"; do
    436         ble/canvas/put-cup.draw 1 "$((x0+1))"
    437         ble/canvas/put.draw "$_ble_term_el"
    438         ble/util/c2s "$((code))"
    439         ble/canvas/put.draw "$ret"
    440         ble/term/CPR/request.draw "ble/util/c2w/test.hook $((index++))"
    441       done
    442       ble/canvas/put-cup.draw 1 "$((x0+1))"
    443       ble/canvas/put.draw "$_ble_term_el"
    444     else
    445       _ble_util_c2w_auto_update_x0=2
    446       local code index=0
    447       for code in "${codes[@]}"; do
    448         ble/util/c2s "$((code))"
    449         ble/canvas/put.draw "$_ble_term_cr$_ble_term_el[$ret]"
    450         ble/term/CPR/request.draw "ble/util/c2w/test.hook $((index++))"
    451       done
    452       ble/canvas/put.draw "$_ble_term_cr$_ble_term_el"
    453     fi
    454   fi
    455   ble/canvas/put.draw "$_ble_term_rc"
    456   [[ $_ble_attached ]] && ble/canvas/panel/load-position.draw "$saved_pos"
    457   ble/canvas/bflush.draw
    458 }
    459 function ble/util/c2w/test.hook {
    460   local index=$1 l=$2 c=$3
    461   local w=$((c-1-_ble_util_c2w_auto_update_x0))
    462   _ble_util_c2w_auto_update_result[index]=$w
    463   ((index==_ble_util_c2w_auto_update_processing-1)) || return 0
    464   _ble_util_c2w_auto_update_processing=0
    465 
    466   local ws
    467   if [[ $bleopt_char_width_version == auto ]]; then
    468     ws=("${_ble_util_c2w_auto_update_result[@]:2}")
    469     if ((ws[13]==2)); then
    470       bleopt char_width_version=15.0
    471     elif ((ws[11]==2)); then
    472       if ((ws[12]==2)); then
    473         bleopt char_width_version=14.0
    474       else
    475         bleopt char_width_version=13.0
    476       fi
    477     elif ((ws[10]==2)); then
    478       bleopt char_width_version=12.1
    479     elif ((ws[9]==2)); then
    480       bleopt char_width_version=12.0
    481     elif ((ws[8]==2)); then
    482       bleopt char_width_version=11.0
    483     elif ((ws[7]==2)); then
    484       bleopt char_width_version=10.0
    485     elif ((ws[6]==2)); then
    486       bleopt char_width_version=9.0
    487     elif ((ws[4]==0)); then
    488       if ((ws[5]==2)); then
    489         bleopt char_width_version=8.0
    490       else
    491         bleopt char_width_version=7.0
    492       fi
    493     elif ((ws[3]==1&&ws[1]==2)); then
    494       bleopt char_width_version=6.3 # or 6.2
    495     elif ((ws[2]==2)); then
    496       bleopt char_width_version=6.1 # or 6.0
    497     elif ((ws[1]==2)); then
    498       bleopt char_width_version=5.2
    499     elif ((ws[0]==2)); then
    500       bleopt char_width_version=5.0
    501     else
    502       bleopt char_width_version=4.1
    503     fi
    504   fi
    505 
    506   # 先に char_width_version を確定してから musl の判定でそれを参照する。
    507   if [[ $bleopt_char_width_mode == auto ]]; then
    508     IFS=: builtin eval 'ws="${_ble_util_c2w_auto_update_result[*]::2}:${_ble_util_c2w_auto_update_result[*]:5:2}"'
    509     case $ws in
    510     (2:2:*:*) bleopt char_width_mode=east ;;
    511     (2:1:*:*) bleopt char_width_mode=emacs ;;
    512     (1:1:2:0)
    513       if [[ $bleopt_char_width_version == 10.0 ]]; then
    514         bleopt char_width_mode=musl
    515       else
    516         bleopt char_width_mode=west
    517       fi ;;
    518     (*) bleopt char_width_mode=west ;;
    519     esac
    520   fi
    521 
    522   return 0
    523 }
    524 
    525 bleopt/declare -v grapheme_cluster extended
    526 function bleopt/check:grapheme_cluster {
    527   case $value in
    528   (extended|legacy|'') return 0 ;;
    529   (*)
    530     ble/util/print "bleopt: invalid value for grapheme_cluster: '$value'." >&2
    531     return 1 ;;
    532   esac
    533 }
    534 
    535 #%< canvas.GraphemeClusterBreak.sh
    536 
    537 # Note #D2076: 多くの端末 (glibc の wcwidth/wcswidth を参照している端末) で以下
    538 # の文字は Unicode とは違う振る舞いで実装されている。kitty 及び RLogin では独自
    539 # に Unicode に従って実装している様だが、取り敢えずは大勢に合わせて
    540 # GraphemeClusterBreak を補正する。
    541 _ble_unicode_GraphemeClusterBreak_custom[0x1F3FB]=$_ble_unicode_GraphemeClusterBreak_Pictographic
    542 _ble_unicode_GraphemeClusterBreak_custom[0x1F3FC]=$_ble_unicode_GraphemeClusterBreak_Pictographic
    543 _ble_unicode_GraphemeClusterBreak_custom[0x1F3FD]=$_ble_unicode_GraphemeClusterBreak_Pictographic
    544 _ble_unicode_GraphemeClusterBreak_custom[0x1F3FE]=$_ble_unicode_GraphemeClusterBreak_Pictographic
    545 _ble_unicode_GraphemeClusterBreak_custom[0x1F3FF]=$_ble_unicode_GraphemeClusterBreak_Pictographic
    546 
    547 # Note #D2076: 半角カナの濁点と半濁点は Extended Lm だが、端末上の振る舞いは独
    548 # 立した文字として振る舞っている (xterm, lxterminal, terminology, kitty)。
    549 _ble_unicode_GraphemeClusterBreak_custom[0xFF9E]=$_ble_unicode_GraphemeClusterBreak_Other
    550 _ble_unicode_GraphemeClusterBreak_custom[0xFF9F]=$_ble_unicode_GraphemeClusterBreak_Other
    551 
    552 function ble/unicode/GraphemeCluster/c2break {
    553   local code=$1
    554   ret=${_ble_unicode_GraphemeClusterBreak_custom[code]}
    555   [[ $ret ]] && return 0
    556   ret=${_ble_unicode_GraphemeClusterBreak[code]}
    557   [[ $ret ]] && return 0
    558   ((ret>_ble_unicode_GraphemeClusterBreak_MaxCode)) && { ret=0; return 0; }
    559 
    560   local l=0 u=${#_ble_unicode_GraphemeClusterBreak_ranges[@]} m
    561   while ((l+1<u)); do
    562     ((_ble_unicode_GraphemeClusterBreak_ranges[m=(l+u)/2]<=code?(l=m):(u=m)))
    563   done
    564 
    565   ret=${_ble_unicode_GraphemeClusterBreak[_ble_unicode_GraphemeClusterBreak_ranges[l]]:-0}
    566   _ble_unicode_GraphemeClusterBreak[code]=$ret
    567   return 0
    568 }
    569 
    570 _ble_unicode_GraphemeCluster_bomlen=1
    571 _ble_unicode_GraphemeCluster_ucs4len=1
    572 function ble/unicode/GraphemeCluster/s2break/.initialize {
    573   local LC_ALL=C.UTF-8
    574   builtin eval "local v1=\$'\\uFE0F' v2=\$'\\U1F6D1'"
    575   _ble_unicode_GraphemeCluster_bomlen=${#v1}
    576   _ble_unicode_GraphemeCluster_ucs4len=${#v2}
    577   ble/util/unlocal LC_ALL
    578   builtin unset -f "$FUNCNAME"
    579 } 2>/dev/null # suppress locale error #D1440
    580 ble/unicode/GraphemeCluster/s2break/.initialize
    581 
    582 ## @fn ble/unicode/GraphemeCluster/s2break/.combine-surrogate code1 code2 str
    583 ##   @var[out] c
    584 function ble/unicode/GraphemeCluster/s2break/.combine-surrogate {
    585   local code1=$1 code2=$2 s=$3
    586   if ((0xDC00<=code2&&code2<=0xDFFF)); then
    587     ((c=0x10000+(code1-0xD800)*1024+(code2&0x3FF)))
    588   else
    589     local ret
    590     ble/util/s2bytes "$s"
    591     ble/encoding:UTF-8/b2c "${ret[@]}"
    592     c=$ret
    593   fi
    594 }
    595 ## @fn ble/unicode/GraphemeCluster/s2break/.wa-bash43bug-uFFFF code
    596 ##   (#D1881) Bash 4.3, 4.4 [sizeof(wchar_t) == 2] で $'\uE000'.. $'\uFFFF' が
    597 ##   壊れたサロゲートになるバグに対する対策。この時、前半サロゲートは不正な値
    598 ##   U+D7F8..D7FF になるが、これはハングル字母などと被る。U+D7F8..D7FF の時は、
    599 ##   次の文字が後半サロゲートの時に限り前半サロゲートとして取り扱う。
    600 ##
    601 ##   @param[in] code
    602 ##     壊れた前半サロゲータの可能性がある文字コード
    603 ##   @var[in,out] ret
    604 ##     調整前後の GraphemeClusterBreak 値
    605 ##   @exit
    606 ##     調整が行われた時に成功です (0)。それ以外の時は失敗 (1) です。
    607 ##
    608 if ((_ble_unicode_GraphemeCluster_bomlen==2&&40300<=_ble_bash&&_ble_bash<50000)); then
    609   function ble/unicode/GraphemeCluster/s2break/.wa-bash43bug-uFFFF {
    610     local code=$1
    611     ((0xD7F8<=code&&code<0xD800)) && ble/util/is-unicode-output &&
    612       ret=$_ble_unicode_GraphemeClusterBreak_HighSurrogate
    613   }
    614 else
    615   function ble/unicode/GraphemeCluster/s2break/.wa-bash43bug-uFFFF { ((0)); }
    616 fi
    617 ## @fn ble/unicode/GraphemeCluster/s2break/.wa-cygwin-LSG code
    618 ##   (#D1881) Cygwin では UCS-2 に入らないコードポイントの後半サロゲートをs2cで
    619 ##   取ろうとしても 0 になってしまう (Bash 5.0 以降では 4-byte UTF-8 の最後のバ
    620 ##   イト値) ので、後半について code == 0 の場合も前半サロゲートをチェックする。
    621 ##
    622 ##   @param[in] code
    623 ##     UCS-4 の後半サロゲートの可能性がある文字コード
    624 ##   @var[in,out] ret
    625 ##     調整前後の GraphemeClusterBreak 値
    626 ##   @exit
    627 ##     調整が行われた時に成功です (0)。それ以外の時は失敗 (1) です。
    628 ##
    629 if ((_ble_unicode_GraphemeCluster_ucs4len==2)); then
    630   if ((_ble_bash<50000)); then
    631     function ble/unicode/GraphemeCluster/s2break/.wa-cygwin-LSG {
    632       local code=$1
    633       ((code==0)) && ble/util/is-unicode-output &&
    634         ret=$_ble_unicode_GraphemeClusterBreak_LowSurrogate
    635     }
    636   else
    637     function ble/unicode/GraphemeCluster/s2break/.wa-cygwin-LSG {
    638       local code=$1
    639       ((0x80<=code&&code<0xC0)) && ble/util/is-unicode-output &&
    640         ret=$_ble_unicode_GraphemeClusterBreak_LowSurrogate
    641     }
    642   fi
    643 else
    644   function ble/unicode/GraphemeCluster/s2break/.wa-cygwin-LSG { ((0)); }
    645 fi
    646 
    647 ## @fn ble/unicode/GraphemeCluster/s2break-left str index [opts]
    648 ## @fn ble/unicode/GraphemeCluster/s2break-right str index [opts]
    649 ##   指定した文字列の指定した境界の左右の code point の GraphemeCulsterBreak 値
    650 ##   を求めます。単に bash の文字単位ではなく、サロゲートペアも考慮に入れたコー
    651 ##   ドポイント単位で処理を行います。
    652 ##
    653 ##   @param str
    654 ##   @param index
    655 ##   @param[opt] opts
    656 ##   @var[out] ret
    657 ##     GraphemeCulsterBreak 値を返します。
    658 ##   @var[out,opt] shift
    659 ##     opts に shift が指定された時に対象の code point の文字数を返します。
    660 ##     surrogate pair の時に 2 になります。それ以外の時は 1 です。
    661 ##   @var[out,opt] code
    662 ##     opts に code が指定された時に対象の code point を返します。
    663 ##
    664 ## * Note2 (#D1881): ${s:i-1:2} 等として 2 文字切り出すのは、Cygwin では
    665 ##   ${s:i-1:1} として最初の文字を切り出そうとすると UCS-2 に入らない code
    666 ##   point の文字が破壊されてしまって surrogate 前半すら取り出せなくなる為。少
    667 ##   なくとも wchar_t*2 の分だけ渡せば printf %d '$1 で surrogate 前半の code
    668 ##   point を取り出す事ができる。
    669 function ble/unicode/GraphemeCluster/s2break-left {
    670   ret=0
    671   local s=$1 N=${#1} i=$2 opts=$3 sh=1
    672   ((i>0)) && ble/util/s2c "${s:i-1:2}"; local c=$ret code2=$ret # Note2 (上述)
    673   ble/unicode/GraphemeCluster/c2break "$code2"; local break=$ret
    674 
    675   # process surrogate pairs
    676   ((i-1<N)) && ble/unicode/GraphemeCluster/s2break/.wa-cygwin-LSG "$code2"
    677   if ((i-2>=0&&ret==_ble_unicode_GraphemeClusterBreak_LowSurrogate)); then
    678     ble/util/s2c "${s:i-2:2}"; local code1=$ret # Note2 (上述)
    679     ble/unicode/GraphemeCluster/c2break "$code1"
    680     ble/unicode/GraphemeCluster/s2break/.wa-bash43bug-uFFFF "$code1"
    681     if ((ret==_ble_unicode_GraphemeClusterBreak_HighSurrogate)); then
    682       ble/unicode/GraphemeCluster/s2break/.combine-surrogate "$code1" "$code2" "${s:i-2:2}"
    683       ble/unicode/GraphemeCluster/c2break "$c"
    684       break=$ret
    685       sh=2
    686     fi
    687   elif ((i<N)) && ble/unicode/GraphemeCluster/s2break/.wa-bash43bug-uFFFF "$code2"; then
    688     # 壊れた前半サロゲートの可能性があるので次の文字を確認して break を確定する。
    689     # (Note: 壊れたサロゲートペアの場合には UTF-8 4B 表現になる事はないので
    690     # Cygwin で code_next==0 になる可能性は考えなくて良い。)
    691     ble/util/s2c "${s:i:1}"; local code_next=$ret
    692     ble/unicode/GraphemeCluster/c2break "$code_next"
    693     ((ret==_ble_unicode_GraphemeClusterBreak_LowSurrogate)) &&
    694       break=$_ble_unicode_GraphemeClusterBreak_HighSurrogate
    695   fi
    696 
    697   [[ :$opts: == *:shift:* ]] && shift=$sh
    698   [[ :$opts: == *:code:* ]] && code=$c
    699   ret=$break
    700 }
    701 function ble/unicode/GraphemeCluster/s2break-right {
    702   ret=0
    703   local s=$1 N=${#1} i=$2 opts=$3 sh=1
    704   ble/util/s2c "${s:i:2}"; local c=$ret code1=$ret # Note2 (上述)
    705   ble/unicode/GraphemeCluster/c2break "$code1"; local break=$ret
    706 
    707   # process surrogate pairs
    708   ble/unicode/GraphemeCluster/s2break/.wa-bash43bug-uFFFF "$code1"
    709   if ((i+1<N&&ret==_ble_unicode_GraphemeClusterBreak_HighSurrogate)); then
    710     ble/util/s2c "${s:i+1:1}"; local code2=$ret
    711     ble/unicode/GraphemeCluster/s2break/.wa-cygwin-LSG "$code2" ||
    712       ble/unicode/GraphemeCluster/c2break "$code2"
    713 
    714     if ((ret==_ble_unicode_GraphemeClusterBreak_LowSurrogate)); then
    715       ble/unicode/GraphemeCluster/s2break/.combine-surrogate "$code1" "$code2" "${s:i:2}"
    716       ble/unicode/GraphemeCluster/c2break "$c"
    717       break=$ret
    718       sh=2
    719     fi
    720   elif ((0<i&&i<N)) && ble/unicode/GraphemeCluster/s2break/.wa-cygwin-LSG "$code1"; then
    721     # Note #D1881: Cygwin では UCS-2 に入らない code point の surrogate 後半を
    722     # s2c で取ろうとしても 0 になってしまうので code1==0 の時は念入りに調べる。
    723     # 前に HighSurrogate がない時は通常文字と同様に取り扱って問題ない。
    724     ble/util/s2c "${s:i-1:1}"; local code_prev=$ret
    725     ble/unicode/GraphemeCluster/c2break "$code_prev"
    726     ble/unicode/GraphemeCluster/s2break/.wa-bash43bug-uFFFF "$code_prev"
    727     if ((ret==_ble_unicode_GraphemeClusterBreak_HighSurrogate)); then
    728       break=$_ble_unicode_GraphemeClusterBreak_LowSurrogate
    729       if [[ :$opts: == *:code:* ]]; then
    730         ble/util/s2bytes "${s:i-1:2}"
    731         ble/encoding:UTF-8/b2c "${ret[@]}"
    732         ((c=0xDC00|ret&0x3FF))
    733       else
    734         c=0
    735       fi
    736     fi
    737   fi
    738 
    739   [[ :$opts: == *:shift:* ]] && shift=$sh
    740   [[ :$opts: == *:code:* ]] && code=$c
    741   ret=$break
    742 }
    743 
    744 ## @fn ble/unicode/GraphemeCluster/find-previous-boundary/.ZWJ
    745 ##   @var[in] text i
    746 ##   @var[out] ret
    747 function ble/unicode/GraphemeCluster/find-previous-boundary/.ZWJ {
    748   if [[ :$bleopt_emoji_opts: != *:zwj:* ]]; then
    749     ((ret=i))
    750     return 0
    751   fi
    752 
    753   local j=$((i-1)) shift=1
    754   for ((j=i-1;j>0;j-=shift)); do
    755     ble/unicode/GraphemeCluster/s2break-left "$text" "$j" shift
    756     ((_ble_unicode_GraphemeClusterBreak_isExtend[ret])) || break
    757   done
    758 
    759   if ((j==0||ret!=_ble_unicode_GraphemeClusterBreak_Pictographic)); then
    760     #             sot | Extend* ZWJ | Pictographic
    761     # [^Pictographic] | Extend* ZWJ | Pictographic
    762     #                 ^--- j        ^--- i
    763     ((ret=i))
    764     return 0
    765   else
    766     #    Pictographic | Extend* ZWJ | Pictographic
    767     #                 ^--- j        ^--- i
    768     ((i=j-shift,b1=ret))
    769     return 1
    770   fi
    771 }
    772 ## @fn ble/unicode/GraphemeCluster/find-previous-boundary/.RI
    773 ##   @var[in] text i shift
    774 ##   @var[out] ret
    775 function ble/unicode/GraphemeCluster/find-previous-boundary/.RI {
    776   if [[ :$bleopt_emoji_opts: != *:ri:* ]]; then
    777     ((ret=i))
    778     return 0
    779   fi
    780   local j1=$((i-shift))
    781   local j shift=1 countRI=1
    782   for ((j=j1;j>0;j-=shift,countRI++)); do
    783     ble/unicode/GraphemeCluster/s2break-left "$text" "$j" shift
    784     ((ret==_ble_unicode_GraphemeClusterBreak_Regional_Indicator)) || break
    785   done
    786 
    787   if ((j==j1)); then
    788     ((i=j,b1=_ble_unicode_GraphemeClusterBreak_Regional_Indicator))
    789     return 1
    790   else
    791     ((ret=countRI%2==1?j1:i))
    792     return 0
    793   fi
    794 }
    795 ## @fn ble/unicode/GraphemeCluster/find-previous-boundary/.InCB
    796 ##   @var[in] text
    797 ##   @var[in,out] i
    798 ##     現在位置 i を指定します。Indic_Conjunct_Break を読み終わった新しい現在位
    799 ##     置を返します。
    800 ##   @var[in] shift
    801 ##     現在位置 i の左にある文字の UTF-8 文字数を指定します。通常は 1 です。未
    802 ##     解決のサロゲートペアがある場合に 2 になります。
    803 ##   @var[in] b1
    804 ##     現在位置 i の左側の GraphemeClusterBreak 値を指定します。
    805 ##   @var[out] ret
    806 ##     境界が見つかった時に境界の位置を返します。
    807 ##   @remarks
    808 ##     shift 及び b1 は現在位置 i に於いて
    809 ##     ble/unicode/GraphemeCluster/s2break-left を呼び出した状態である事を前提
    810 ##     とします。
    811 function ble/unicode/GraphemeCluster/find-previous-boundary/.InCB {
    812   # Grapheme Cluster with InCB is supported by Unicode >= 15.1.0
    813   if ((_ble_unicode_c2w_version<17)); then
    814     ret=$i
    815     return 0
    816   fi
    817 
    818   local out=$i j=$i count_linker=0
    819   local b1=$b1 shift=$shift
    820   while
    821     case $b1 in
    822     ("$_ble_unicode_GraphemeClusterBreak_InCB_Consonant")
    823       if ((count_linker)); then
    824         count_linker=0
    825         ((out=j-shift))
    826       fi ;;
    827     ("$_ble_unicode_GraphemeClusterBreak_InCB_Linker")
    828       ((count_linker++)) ;;
    829     ("$_ble_unicode_GraphemeClusterBreak_InCB_Extend"|"$_ble_unicode_GraphemeClusterBreak_ZWJ") ;;
    830     (*) break ;;
    831     esac
    832     ((j-=shift,j>0))
    833   do
    834     ble/unicode/GraphemeCluster/s2break-left "$text" "$j" shift
    835     b1=$ret
    836   done
    837 
    838   if ((out<i)); then
    839     i=$out
    840     return 1
    841   else
    842     ret=$out
    843     return 0
    844   fi
    845 }
    846 function ble/unicode/GraphemeCluster/find-previous-boundary {
    847   local text=$1 i=$2 shift
    848   if [[ $bleopt_grapheme_cluster ]] && ((i&&--i)); then
    849     ble/unicode/GraphemeCluster/s2break-right "$text" "$i" shift; local b1=$ret
    850     while ((i>0)); do
    851       local b2=$b1
    852       ble/unicode/GraphemeCluster/s2break-left "$text" "$i" shift; local b1=$ret
    853       case ${_ble_unicode_GraphemeClusterBreak_rule[b1*_ble_unicode_GraphemeClusterBreak_Count+b2]} in
    854       (0) break ;;
    855       (1) ((i-=shift)) ;;
    856       (2) [[ $bleopt_grapheme_cluster != extended ]] && break; ((i-=shift)) ;;
    857       (3) ble/unicode/GraphemeCluster/find-previous-boundary/.ZWJ && return 0 ;;
    858       (4) ble/unicode/GraphemeCluster/find-previous-boundary/.RI && return 0 ;;
    859       (6) ble/unicode/GraphemeCluster/find-previous-boundary/.InCB && return 0;;
    860       (5)
    861         # surrogate pair の間にいた時は GraphemeClusterBreak を取得し直す
    862         ((i-=shift))
    863         ble/unicode/GraphemeCluster/s2break-right "$text" "$i"; b1=$ret ;;
    864       esac
    865     done
    866   fi
    867   ret=$i
    868   return 0
    869 }
    870 
    871 _ble_unicode_GraphemeClusterBreak_isCore=()
    872 _ble_unicode_GraphemeClusterBreak_isCore[_ble_unicode_GraphemeClusterBreak_Other]=1
    873 _ble_unicode_GraphemeClusterBreak_isCore[_ble_unicode_GraphemeClusterBreak_Control]=1
    874 _ble_unicode_GraphemeClusterBreak_isCore[_ble_unicode_GraphemeClusterBreak_Regional_Indicator]=1
    875 _ble_unicode_GraphemeClusterBreak_isCore[_ble_unicode_GraphemeClusterBreak_L]=1
    876 _ble_unicode_GraphemeClusterBreak_isCore[_ble_unicode_GraphemeClusterBreak_V]=1
    877 _ble_unicode_GraphemeClusterBreak_isCore[_ble_unicode_GraphemeClusterBreak_T]=1
    878 _ble_unicode_GraphemeClusterBreak_isCore[_ble_unicode_GraphemeClusterBreak_LV]=1
    879 _ble_unicode_GraphemeClusterBreak_isCore[_ble_unicode_GraphemeClusterBreak_LVT]=1
    880 _ble_unicode_GraphemeClusterBreak_isCore[_ble_unicode_GraphemeClusterBreak_Pictographic]=1
    881 _ble_unicode_GraphemeClusterBreak_isCore[_ble_unicode_GraphemeClusterBreak_HighSurrogate]=1
    882 _ble_unicode_GraphemeClusterBreak_isCore[_ble_unicode_GraphemeClusterBreak_InCB_Consonant]=1
    883 
    884 _ble_unicode_GraphemeClusterBreak_isExtend=()
    885 _ble_unicode_GraphemeClusterBreak_isExtend[_ble_unicode_GraphemeClusterBreak_Extend]=1
    886 _ble_unicode_GraphemeClusterBreak_isExtend[_ble_unicode_GraphemeClusterBreak_InCB_Extend]=1
    887 _ble_unicode_GraphemeClusterBreak_isExtend[_ble_unicode_GraphemeClusterBreak_InCB_Linker]=1
    888 
    889 ## @fn ble/unicode/GraphemeCluster/extend-ascii text i
    890 ##   @var[out] extend
    891 function ble/unicode/GraphemeCluster/extend-ascii {
    892   extend=0
    893   [[ $_ble_util_locale_encoding != UTF-8 || ! $bleopt_grapheme_cluster ]] && return 1
    894   local text=$1 iN=${#1} i=$2 ret shift=1
    895   for ((;i<iN;i+=shift,extend+=shift)); do
    896     ble/unicode/GraphemeCluster/s2break-right "$text" "$i" shift
    897     if ((!_ble_unicode_GraphemeClusterBreak_isExtend[ret])); then
    898       case $ret in
    899       ("$_ble_unicode_GraphemeClusterBreak_SpacingMark")
    900         [[ $bleopt_grapheme_cluster == extended ]] || break ;;
    901       (*) break ;;
    902       esac
    903     fi
    904   done
    905   ((extend))
    906 }
    907 
    908 _ble_unicode_GraphemeCluster_ControlRepresentation=()
    909 function ble/unicode/GraphemeCluster/.get-ascii-rep {
    910   local c=$1
    911   cs=${_ble_unicode_GraphemeCluster_ControlRepresentation[c]}
    912   if [[ ! $cs ]]; then
    913     if ((c<32)); then
    914       ble/util/c2s "$((c+64))"
    915       cs=^$ret
    916     elif ((c==127)); then
    917       cs=^?
    918     elif ((128<=c&&c<160)); then
    919       ble/util/c2s "$((c-64))"
    920       cs=M-^$ret
    921     else
    922       ble/util/sprintf cs 'U+%X' "$c"
    923     fi
    924     _ble_unicode_GraphemeCluster_ControlRepresentation[c]=$cs
    925   fi
    926 }
    927 
    928 ## @fn ble/unicode/GraphemeCluster/match text i flags
    929 ##   @param[in] text i
    930 ##   @param[in] flags
    931 ##     R が含まれている時制御文字を (ASCII 表現ではなく) そのまま cs に格納しま
    932 ##     す。幅は 0 で換算されます。
    933 ##   @var[out] c w cs cb extend
    934 function ble/unicode/GraphemeCluster/match {
    935   local text=$1 iN=${#1} i=$2 j=$2 flags=$3 ret
    936   if ((i>=iN)); then
    937     c=0 w=0 cs= cb= extend=0
    938     return 1
    939   elif ! ble/util/is-unicode-output || [[ ! $bleopt_grapheme_cluster ]]; then
    940     cs=${text:i:1}
    941     ble/util/s2c "$cs"; c=$ret
    942     if [[ $flags != *R* ]] && {
    943          ble/unicode/GraphemeCluster/c2break "$c"
    944          ((ret==_ble_unicode_GraphemeClusterBreak_Control)); };  then
    945       ble/unicode/GraphemeCluster/.get-ascii-rep "$c"
    946       w=${#cs}
    947     else
    948       ble/util/c2w "$c"; w=$ret
    949     fi
    950     extend=0
    951     return 0
    952   fi
    953 
    954   local b0 b1 b2 c0 c2 shift code
    955   ble/unicode/GraphemeCluster/s2break-right "$text" "$i" code:shift; c0=$code b0=$ret
    956 
    957   local coreb= corec= npre=0 vs= ri= InCB_state=
    958   c2=$c0 b2=$b0
    959   while ((j<iN)); do
    960     if ((_ble_unicode_GraphemeClusterBreak_isCore[b2])); then
    961       [[ $coreb ]] || coreb=$b2 corec=$c2
    962     else
    963       if ((b2==_ble_unicode_GraphemeClusterBreak_Prepend)); then
    964         ((npre++))
    965       elif ((c2==0xFE0E)); then # Variation selector TPVS
    966         vs=tpvs
    967       elif ((c2==0xFE0F)); then # Variation selector EPVS
    968         vs=epvs
    969       fi
    970     fi
    971 
    972     # update InCB_state
    973     if ((b2==_ble_unicode_GraphemeClusterBreak_InCB_Consonant)); then
    974       InCB_state=0
    975     elif [[ $InCB_state ]]; then
    976       if ((b2==_ble_unicode_GraphemeClusterBreak_InCB_Linker)); then
    977         InCB_state=1
    978       elif ((b2!=_ble_unicode_GraphemeClusterBreak_InCB_Extend&&b2!=_ble_unicode_GraphemeClusterBreak_ZWJ)); then
    979         InCB_state=
    980       fi
    981     fi
    982 
    983     ((j+=shift))
    984     b1=$b2
    985     ble/unicode/GraphemeCluster/s2break-right "$text" "$j" code:shift; c2=$code b2=$ret
    986     case ${_ble_unicode_GraphemeClusterBreak_rule[b1*_ble_unicode_GraphemeClusterBreak_Count+b2]} in
    987     (0) break ;;
    988     (1) continue ;;
    989     (2) [[ $bleopt_grapheme_cluster != extended ]] && break ;;
    990     (3) [[ :$bleopt_emoji_opts: == *:zwj:* ]] &&
    991           ((coreb==_ble_unicode_GraphemeClusterBreak_Pictographic)) || break ;;
    992     (4) [[ :$bleopt_emoji_opts: == *:ri:* && ! $ri ]] || break; ri=1 ;;
    993     (6) ((_ble_unicode_c2w_version>=17&&InCB_state)) || break ;;
    994     (5)
    995       # surrogate pair の間にいた時は GraphemeClusterBreak を取得し直す
    996       ble/unicode/GraphemeCluster/s2break-left "$text" "$((j+shift))" code; c2=$code b2=$ret ;;
    997     esac
    998   done
    999 
   1000   c=$corec cb=$coreb cs=${text:i:j-i}
   1001   ((extend=j-i-1))
   1002   if [[ ! $corec ]]; then
   1003     if [[ $flags != *R* ]]; then
   1004       ((c=c0,cb=0,corec=0x25CC)) # 基底が存在しない時は点線円
   1005       ble/util/c2s "$corec"
   1006       cs=${text:i:npre}$ret${text:i+npre:j-i-npre}
   1007     else
   1008       local code
   1009       ble/unicode/GraphemeCluster/s2break-right "$cs" 0 code
   1010       c=$code corec=$code cb=$ret
   1011     fi
   1012   fi
   1013 
   1014   if ((cb==_ble_unicode_GraphemeClusterBreak_Control)); then
   1015     if [[ $flags != *R* ]]; then
   1016       ble/unicode/GraphemeCluster/.get-ascii-rep "$c"
   1017       w=${#cs}
   1018     else
   1019       # ToDo: 全ての制御文字が幅0とは限らない。というより色々処理が必要。
   1020       w=0
   1021     fi
   1022 
   1023   else
   1024     # 幅の計算 (Variation Selector を考慮に入れる)
   1025     if [[ $vs == tpvs && :$bleopt_emoji_opts: == *:tpvs:* ]]; then
   1026       bleopt_emoji_width= ble/util/c2w "$corec"; w=$ret
   1027     elif [[ $vs == epvs && :$bleopt_emoji_opts: == *:epvs:* ]]; then
   1028       w=${bleopt_emoji_width:-2}
   1029     else
   1030       ble/util/c2w "$corec"; w=$ret
   1031     fi
   1032   fi
   1033 
   1034   return 0
   1035 }
   1036 
   1037 #------------------------------------------------------------------------------
   1038 # ble/canvas/attach
   1039 
   1040 function ble/canvas/attach {
   1041   ble/util/c2w:auto/check
   1042 }
   1043 
   1044 #------------------------------------------------------------------------------
   1045 # ble/canvas
   1046 
   1047 function ble/canvas/put.draw {
   1048   DRAW_BUFF[${#DRAW_BUFF[*]}]=$1
   1049 }
   1050 function ble/canvas/put-ind.draw {
   1051   local count=${1-1} ind=$_ble_term_ind
   1052   [[ :$2: == *:true-ind:* ]] && ind=$'\eD'
   1053   local ret; ble/string#repeat "$ind" "$count"
   1054   DRAW_BUFF[${#DRAW_BUFF[*]}]=$ret
   1055 }
   1056 function ble/canvas/put-ri.draw {
   1057   local count=${1-1}
   1058   local ret; ble/string#repeat "$_ble_term_ri" "$count"
   1059   DRAW_BUFF[${#DRAW_BUFF[*]}]=$ret
   1060 }
   1061 ## @fn ble/canvas/put-il.draw [nline] [opts]
   1062 ## @fn ble/canvas/put-dl.draw [nline] [opts]
   1063 ##   @param[in,opt] nline
   1064 ##     消去・挿入する行数を指定します。
   1065 ##     省略した場合は 1 と解釈されます。
   1066 ##   @param[in,opt] opts
   1067 ##     panel
   1068 ##     vfill
   1069 ##     no-lastline
   1070 ##       Cygwin console 最終行バグ判定用の情報です。
   1071 function ble/canvas/put-il.draw {
   1072   local value=${1-1}
   1073   ((value>0)) || return 0
   1074   DRAW_BUFF[${#DRAW_BUFF[*]}]=${_ble_term_il//'%d'/$value}
   1075   DRAW_BUFF[${#DRAW_BUFF[*]}]=$_ble_term_el2 # Note #D1214: 最終行対策 cygwin, linux
   1076 }
   1077 function ble/canvas/put-dl.draw {
   1078   local value=${1-1}
   1079   ((value>0)) || return 0
   1080   DRAW_BUFF[${#DRAW_BUFF[*]}]=$_ble_term_el2 # Note #D1214: 最終行対策 cygwin, linux
   1081   DRAW_BUFF[${#DRAW_BUFF[*]}]=${_ble_term_dl//'%d'/$value}
   1082 }
   1083 # Cygwin console (pcon) では最終行で IL/DL すると画面全体がクリアされるバグの対策 (#D1482)
   1084 if ((_ble_bash>=40000)) && [[ ( $OSTYPE == cygwin || $OSTYPE == msys ) && $TERM == xterm-256color ]]; then
   1085   function ble/canvas/.is-il-workaround-required {
   1086     local value=$1 opts=$2
   1087 
   1088     # Cygwin console 以外の端末ではそもそも対策不要。
   1089     [[ ! $_ble_term_DA2R ]] || return 1
   1090 
   1091     # 複数行挿入・削除する場合は現在位置は最終行ではない筈。
   1092     ((value==1)) || return 1
   1093 
   1094     # 対策不要と明示されている場合は対策不要。
   1095     [[ :$opts: == *:vfill:* || :$opts: == *:no-lastline:* ]] && return 1
   1096 
   1097     # ble/canvas/panel 内部で移動中の時は opts=panel が指定される。
   1098     # panel 集合の最終行にいない場合は対策不要。
   1099     [[ :$opts: == *:panel:* ]] &&
   1100       ! ble/canvas/panel/is-last-line &&
   1101       return 1
   1102 
   1103     return 0
   1104   }
   1105 
   1106   function ble/canvas/put-il.draw {
   1107     local value=${1-1} opts=$2
   1108     ((value>0)) || return 0
   1109     if ble/canvas/.is-il-workaround-required "$value" "$2"; then
   1110       if [[ :$opts: == *:panel:* ]]; then
   1111         DRAW_BUFF[${#DRAW_BUFF[*]}]=$_ble_term_el2
   1112       else
   1113         DRAW_BUFF[${#DRAW_BUFF[*]}]=$'\e[S\e[A\e[L\e[B\e[T'
   1114       fi
   1115     else
   1116       DRAW_BUFF[${#DRAW_BUFF[*]}]=${_ble_term_il//'%d'/$value}
   1117       DRAW_BUFF[${#DRAW_BUFF[*]}]=$_ble_term_el2 # Note #D1214: 最終行対策 cygwin, linux
   1118     fi
   1119   }
   1120   function ble/canvas/put-dl.draw {
   1121     local value=${1-1} opts=$2
   1122     ((value>0)) || return 0
   1123     if ble/canvas/.is-il-workaround-required "$value" "$2"; then
   1124       if [[ :$opts: == *:panel:* ]]; then
   1125         DRAW_BUFF[${#DRAW_BUFF[*]}]=$_ble_term_el2
   1126       else
   1127         DRAW_BUFF[${#DRAW_BUFF[*]}]=$'\e[S\e[A\e[M\e[B\e[T'
   1128       fi
   1129     else
   1130       DRAW_BUFF[${#DRAW_BUFF[*]}]=$_ble_term_el2 # Note #D1214: 最終行対策 cygwin, linux
   1131       DRAW_BUFF[${#DRAW_BUFF[*]}]=${_ble_term_dl//'%d'/$value}
   1132     fi
   1133   }
   1134 fi
   1135 function ble/canvas/put-cuu.draw {
   1136   local value=${1-1}
   1137   DRAW_BUFF[${#DRAW_BUFF[*]}]=${_ble_term_cuu//'%d'/$value}
   1138 }
   1139 function ble/canvas/put-cud.draw {
   1140   local value=${1-1}
   1141   DRAW_BUFF[${#DRAW_BUFF[*]}]=${_ble_term_cud//'%d'/$value}
   1142 }
   1143 function ble/canvas/put-cuf.draw {
   1144   local value=${1-1}
   1145   DRAW_BUFF[${#DRAW_BUFF[*]}]=${_ble_term_cuf//'%d'/$value}
   1146 }
   1147 function ble/canvas/put-cub.draw {
   1148   local value=${1-1}
   1149   DRAW_BUFF[${#DRAW_BUFF[*]}]=${_ble_term_cub//'%d'/$value}
   1150 }
   1151 function ble/canvas/put-cup.draw {
   1152   local l=${1-1} c=${2-1}
   1153   local out=$_ble_term_cup
   1154   out=${out//'%l'/$l}
   1155   out=${out//'%c'/$c}
   1156   out=${out//'%y'/$((l-1))}
   1157   out=${out//'%x'/$((c-1))}
   1158   DRAW_BUFF[${#DRAW_BUFF[*]}]=$out
   1159 }
   1160 function ble/canvas/put-hpa.draw {
   1161   local c=${1-1}
   1162   local out=$_ble_term_hpa
   1163   out=${out//'%c'/$c}
   1164   out=${out//'%x'/$((c-1))}
   1165   DRAW_BUFF[${#DRAW_BUFF[*]}]=$out
   1166 }
   1167 function ble/canvas/put-vpa.draw {
   1168   local l=${1-1}
   1169   local out=$_ble_term_vpa
   1170   out=${out//'%l'/$l}
   1171   out=${out//'%y'/$((l-1))}
   1172   DRAW_BUFF[${#DRAW_BUFF[*]}]=$out
   1173 }
   1174 function ble/canvas/put-ech.draw {
   1175   local value=${1:-1} esc
   1176   if [[ $_ble_term_ech ]]; then
   1177     esc=${_ble_term_ech//'%d'/$value}
   1178   else
   1179     ble/string#reserve-prototype "$value"
   1180     esc=${_ble_string_prototype::value}${_ble_term_cub//'%d'/$value}
   1181   fi
   1182   DRAW_BUFF[${#DRAW_BUFF[*]}]=$esc
   1183 }
   1184 function ble/canvas/put-spaces.draw {
   1185   local value=${1:-1}
   1186   ble/string#reserve-prototype "$value"
   1187   DRAW_BUFF[${#DRAW_BUFF[*]}]=${_ble_string_prototype::value}
   1188 }
   1189 function ble/canvas/put-move-x.draw {
   1190   local dx=$1
   1191   ((dx)) || return 1
   1192   if ((dx>0)); then
   1193     ble/canvas/put-cuf.draw "$dx"
   1194   else
   1195     ble/canvas/put-cub.draw "$((-dx))"
   1196   fi
   1197 }
   1198 function ble/canvas/put-move-y.draw {
   1199   local dy=$1
   1200   ((dy)) || return 1
   1201   if ((dy>0)); then
   1202     if [[ $MC_SID == $$ ]]; then
   1203       # Note #D1392: mc (midnight commander) の中だと layout が破壊されるので、
   1204       #   必ずしも CUD で想定した行だけ移動できると限らない。
   1205       ble/canvas/put-ind.draw "$dy" true-ind
   1206     else
   1207       ble/canvas/put-cud.draw "$dy"
   1208     fi
   1209   else
   1210     ble/canvas/put-cuu.draw "$((-dy))"
   1211   fi
   1212 }
   1213 function ble/canvas/put-move.draw {
   1214   ble/canvas/put-move-x.draw "$1"
   1215   ble/canvas/put-move-y.draw "$2"
   1216 }
   1217 function ble/canvas/flush.draw {
   1218   IFS= builtin eval 'ble/util/put "${DRAW_BUFF[*]}"'
   1219   DRAW_BUFF=()
   1220 }
   1221 ## @fn ble/canvas/sflush.draw [-v var]
   1222 ##   @param[in] var
   1223 ##     出力先の変数名を指定します。
   1224 ##   @var[out] !var
   1225 function ble/canvas/sflush.draw {
   1226   local _ble_local_var=ret
   1227   [[ $1 == -v ]] && _ble_local_var=$2
   1228   IFS= builtin eval "$_ble_local_var=\"\${DRAW_BUFF[*]}\""
   1229   DRAW_BUFF=()
   1230 }
   1231 function ble/canvas/bflush.draw {
   1232   IFS= builtin eval 'ble/util/buffer "${DRAW_BUFF[*]}"'
   1233   DRAW_BUFF=()
   1234 }
   1235 
   1236 ## @fn ble/canvas/put-clear-lines.draw [old] [new] [opts]
   1237 ##   @param[in,opt] old new
   1238 ##     消去前と消去後の行数を指定します。
   1239 ##     old を省略した場合は 1 が使われます。
   1240 ##     new を省略した場合は old が使われます。
   1241 ##   @param[in,opt] opts
   1242 ##     panel
   1243 ##     vfill
   1244 ##     no-lastline
   1245 function ble/canvas/put-clear-lines.draw {
   1246   local old=${1:-1}
   1247   local new=${2:-$old}
   1248   if ((old==1&&new==1)); then
   1249     ble/canvas/put.draw "$_ble_term_el2"
   1250   else
   1251     ble/canvas/put-dl.draw "$old" "$3"
   1252     ble/canvas/put-il.draw "$new" "$3"
   1253   fi
   1254 }
   1255 
   1256 #------------------------------------------------------------------------------
   1257 # ble/canvas/trace.draw
   1258 # ble/canvas/trace
   1259 
   1260 ## @fn ble/canvas/trace.draw text [opts]
   1261 ## @fn ble/canvas/trace text [opts]
   1262 ##   制御シーケンスを含む文字列を出力すると共にカーソル位置の移動を計算します。
   1263 ##
   1264 ##   @param[in]   text
   1265 ##     出力する (制御シーケンスを含む) 文字列を指定します。
   1266 ##
   1267 ##   @param[in,opt] opts
   1268 ##     コロン区切りのオプションの列を指定します。
   1269 ##
   1270 ##     [配置制御]
   1271 ##
   1272 ##     truncate
   1273 ##       LINES COLUMNS で指定される範囲外に出た時、処理を中断します。
   1274 ##
   1275 ##     confine
   1276 ##       LINES COLUMNS の範囲外に文字出力・移動を行いません。
   1277 ##       制御シーケンスにより範囲内に戻る可能性もあります。
   1278 ##
   1279 ##     ellipsis
   1280 ##       LINES COLUMNS の範囲外に文字を出力しようとした時に、
   1281 ##       三点リーダを末尾に上書きします。
   1282 ##
   1283 ##     clip=X1xY1,X2xY2
   1284 ##     clip=XxY+WxH
   1285 ##       @param[in] X1 Y1 X2 Y2
   1286 ##       @param[in] X Y W H
   1287 ##       指定した矩形範囲内の描画内容だけを抽出します。
   1288 ##       矩形の左上の点が出力の描画開始点であると想定します。
   1289 ##
   1290 ##     justify
   1291 ##     justify=SEPSPEC
   1292 ##       横揃えを設定します。
   1293 ##
   1294 ##     [範囲計測]
   1295 ##
   1296 ##     measure-bbox
   1297 ##       @var[out] x1 x2 y1 y2
   1298 ##       カーソル移動範囲を x1 x2 y1 y2 に返します。
   1299 ##     measure-gbox
   1300 ##       @var[out] gx1 gx2 gy1 gy2
   1301 ##       描画範囲を x1 x2 y1 y2 に返します。
   1302 ##     left-char
   1303 ##       @var[in,out] lc lg
   1304 ##       bleopt_internal_suppress_bash_output= の時、
   1305 ##       出力開始時のカーソル左の文字コードを指定します。
   1306 ##       出力終了時のカーソル左の文字コードが分かる場合にそれを返します。
   1307 ##
   1308 ##     [出力制御機能]
   1309 ##
   1310 ##     relative
   1311 ##       x y を相対位置と考えて移動を行います。
   1312 ##       改行などの制御は全て座標に基づいた移動に変換されます。
   1313 ##     ansi
   1314 ##       ANSI制御シーケンスで出力を構築します。
   1315 ##       後で trace で再解析を行う場合などに指定できます。
   1316 ##     g0 face0
   1317 ##       背景色・既定属性として用いる属性値または描画設定を指定します。
   1318 ##       両方指定された場合は g0 を優先させます。
   1319 ##
   1320 ##     [その他]
   1321 ##
   1322 ##     terminfo
   1323 ##       ANSI制御シーケンスではなく現在の端末のシーケンスとして
   1324 ##       制御機能SGRを解釈します。
   1325 ##
   1326 ##   @var[in,out] DRAW_BUFF[]
   1327 ##     ble/canvas/trace.draw の出力先の配列です。
   1328 ##   @var[out] ret
   1329 ##     ble/canvas/trace の結果の格納先の変数です。
   1330 ##
   1331 ##   @var[in,out] x y g
   1332 ##     出力の開始位置を指定します。出力終了時の位置を返します。
   1333 ##
   1334 ##   以下のシーケンスを認識します
   1335 ##
   1336 ##   - Control Characters (C0 の文字 及び DEL)
   1337 ##     BS HT LF VT CR はカーソル位置の変更を行います。
   1338 ##     それ以外の文字はカーソル位置の変更は行いません。
   1339 ##
   1340 ##   - CSI Sequence (Control Sequence)
   1341 ##     | CUU   CSI A | CHB   CSI Z |
   1342 ##     | CUD   CSI B | HPR   CSI a |
   1343 ##     | CUF   CSI C | VPR   CSI e |
   1344 ##     | CUB   CSI D | HPA   CSI ` |
   1345 ##     | CNL   CSI E | VPA   CSI d |
   1346 ##     | CPL   CSI F | HVP   CSI f |
   1347 ##     | CHA   CSI G | SGR   CSI m |
   1348 ##     | CUP   CSI H | SCOSC CSI s |
   1349 ##     | CHT   CSI I | SCORC CSI u |
   1350 ##     上記のシーケンスはカーソル位置の計算に含め、
   1351 ##     また、端末 (TERM) に応じた出力を実施します。
   1352 ##     上記以外のシーケンスはカーソル位置を変更しません。
   1353 ##
   1354 ##   - SOS, DCS, SOS, PM, APC, ESC k ~ ESC \
   1355 ##   - ISO-2022 に含まれる 3 byte 以上のシーケンス
   1356 ##     これらはそのまま通します。位置計算の考慮には入れません。
   1357 ##
   1358 ##   - ESC Sequence
   1359 ##     DECSC DECRC IND RI NEL はカーソル位置の変更を行います。
   1360 ##     それ以外はカーソル位置の変更は行いません。
   1361 ##
   1362 ## 内部実装で用いている変数を整理する
   1363 ##
   1364 ##   @var[local] xinit yinit ginit
   1365 ##     初期カーソル状態を格納する。
   1366 ##
   1367 ##   @var x1 x2 y1 y2
   1368 ##     これは measure-bbox または justify を指定した時に描画範囲を追跡するのに使っている。
   1369 ##
   1370 ##   @var[local] cx cy cg
   1371 ##     clip 時に DRAW_BUFF 出力済みの内容のカーソル状態を追跡する変数。
   1372 ##     clip 時は x y g は仮想的に clip していない時のカーソル状態を追跡している。
   1373 ##   @var[local] cx1 cy1 cx2 cy2
   1374 ##     clip 範囲を保持する変数
   1375 ##
   1376 ##
   1377 
   1378 function ble/canvas/trace/.put-sgr.draw {
   1379   local ret g=$1
   1380   if ((g==0)); then
   1381     ble/canvas/put.draw "$opt_sgr0"
   1382   else
   1383     ble/color/g.compose "$opt_g0" "$g"
   1384     "$trace_g2sgr" "$g"
   1385     ble/canvas/put.draw "$ret"
   1386   fi
   1387 }
   1388 
   1389 function ble/canvas/trace/.measure-point {
   1390   if [[ $flag_bbox ]]; then
   1391     ((x<x1?(x1=x):(x2<x&&(x2=x))))
   1392     ((y<y1?(y1=y):(y2<y&&(y2=y))))
   1393   fi
   1394 }
   1395 ## @fn ble/canvas/trace/.goto x1 y1
   1396 ##   @var[in,out] x y
   1397 ##   Note: lc lg の面倒は呼び出し元で見る。
   1398 function ble/canvas/trace/.goto {
   1399   local dstx=$1 dsty=$2
   1400   if [[ ! $flag_clip ]]; then
   1401     if [[ $trace_flags == *[RJ]* ]]; then
   1402       ble/canvas/put-move.draw "$((dstx-x))" "$((dsty-y))"
   1403     else
   1404       ble/canvas/put-cup.draw "$((dsty+1))" "$((dstx+1))"
   1405     fi
   1406   fi
   1407   ((x=dstx,y=dsty))
   1408   ble/canvas/trace/.measure-point
   1409 }
   1410 
   1411 function ble/canvas/trace/.implicit-move {
   1412   local w=$1 type=$2
   1413   # gbox は開始点と終了点を記録する。bbox の開始点は既に記録されている
   1414   # 前提。終了点及び行折返しが発生した時の極値を此処で記録する。
   1415 
   1416   ((w>0)) || return 0
   1417 
   1418   if [[ $flag_gbox ]]; then
   1419     if [[ ! $gx1 ]]; then
   1420       ((gx1=gx2=x,gy1=gy2=y))
   1421     else
   1422       ((x<gx1?(gx1=x):(gx2<x&&(gx2=x))))
   1423       ((y<gy1?(gy1=y):(gy2<y&&(gy2=y))))
   1424     fi
   1425   fi
   1426   ((x+=w))
   1427 
   1428   if ((x<=cols)); then
   1429     # 行内に収まった時
   1430     [[ $flag_bbox ]] && ((x>x2)) && x2=$x
   1431     [[ $flag_gbox ]] && ((x>gx2)) && gx2=$x
   1432     if ((x==cols&&!xenl)); then
   1433       ((y++,x=0))
   1434       if [[ $flag_bbox ]]; then
   1435         ((x<x1)) && x1=0
   1436         ((y>y2)) && y2=$y
   1437       fi
   1438     fi
   1439   else
   1440     # 端末による折り返し
   1441     if [[ $type == atomic ]]; then
   1442       # [Note: 文字が横幅より大きい場合は取り敢えず次の行が一杯になると仮定して
   1443       # いるが端末による。端末によっては更に次の行にカーソルが移動するのではな
   1444       # いかとも思われる。]
   1445       ((y++,x=w<xlimit?w:xlimit))
   1446     else
   1447       ((y+=x/cols,x%=cols,
   1448         xenl&&x==0&&(y--,x=cols)))
   1449     fi
   1450     if [[ $flag_bbox ]]; then
   1451       ((x1>0&&(x1=0)))
   1452       ((x2<cols&&(x2=cols)))
   1453       ((y>y2)) && y2=$y
   1454     fi
   1455     if [[ $flag_gbox ]]; then
   1456       ((gx1>0&&(gx1=0)))
   1457       ((gx2<cols&&(gx2=cols)))
   1458       ((y>gy2)) && gy2=$y
   1459     fi
   1460   fi
   1461   ((x==0&&(lc=32,lg=0)))
   1462   return 0
   1463 }
   1464 
   1465 function ble/canvas/trace/.put-atomic.draw {
   1466   local c=$1 w=$2
   1467   if [[ $flag_clip ]]; then
   1468     ((cy1<=y&&y<cy2&&cx1<=x&&x<cx2&&x+w<=cx2)) || return 0
   1469     if [[ $cg != "$g" ]]; then
   1470       ble/canvas/trace/.put-sgr.draw "$g"
   1471       cg=$g
   1472     fi
   1473     ble/canvas/put-move.draw "$((x-cx))" "$((y-cy))"
   1474     ble/canvas/put.draw "$c"
   1475     ((cx+=x+w,cy=y))
   1476   else
   1477     ble/canvas/put.draw "$c"
   1478   fi
   1479 
   1480   ble/canvas/trace/.implicit-move "$w" atomic
   1481 }
   1482 function ble/canvas/trace/.put-ascii.draw {
   1483   local value=$1 w=${#1}
   1484   [[ $value ]] || return 0
   1485 
   1486   if [[ $flag_clip ]]; then
   1487     local xL=$x xR=$((x+w))
   1488     ((xR<=cx1||cx2<=xL||y+1<=cy1||cy2<=y)) && return 0
   1489     if [[ $cg != "$g" ]]; then
   1490       ble/canvas/trace/.put-sgr.draw "$g"
   1491       cg=$g
   1492     fi
   1493     ((xL<cx1)) && value=${value:cx1-xL} xL=$cx1
   1494     ((xR>cx2)) && value=${value::${#value}-(xR-cx2)} xR=$cx2
   1495     ble/canvas/put-move.draw "$((x-cx))" "$((y-cy))"
   1496     ble/canvas/put.draw "$value"
   1497     ((cx=xR,cy=y))
   1498   else
   1499     ble/canvas/put.draw "$value"
   1500   fi
   1501 
   1502   ble/canvas/trace/.implicit-move "$w"
   1503 }
   1504 function ble/canvas/trace/.process-overflow {
   1505   [[ :$opts: == *:truncate:* ]] && i=$iN # stop
   1506   if ((y+1==lines)) && [[ :$opts: == *:ellipsis:* ]]; then
   1507     local ellipsis=... w=3 wmax=$xlimit
   1508     ((w>wmax)) && ellipsis=${ellipsis::wmax} w=$wmax
   1509     if ble/util/is-unicode-output; then
   1510       local symbol='…' ret
   1511       ble/util/s2c "$symbol"
   1512       ble/util/c2w "$ret"
   1513       ((ret<=wmax)) && ellipsis=$symbol w=$ret
   1514     fi
   1515 
   1516     local ox=$x oy=$y
   1517     ble/canvas/trace/.goto "$((wmax-w))" "$((lines-1))"
   1518     ble/canvas/trace/.put-atomic.draw "$ellipsis" "$w"
   1519     ble/canvas/trace/.goto "$ox" "$oy"
   1520   fi
   1521 }
   1522 
   1523 #--------------------------------------
   1524 ## (trace 内部変数) justify 関連
   1525 ##
   1526 ##   @var[local] justify_sep
   1527 ##   @arr[local] justify_fields
   1528 ##   @arr[local] justify_buff
   1529 ##   @arr[local] justify_out
   1530 ##   @var[local] jx0 jy0
   1531 ##     各フィールドの開始カーソル位置を保持する。
   1532 ##   @var[local] jx1 jy1 jx2 jy2
   1533 ##     measure-bbox も指定されていた時に、
   1534 ##     justify 後の描画範囲追跡に用いている。
   1535 ##     justify 処理中は x1 y1 x2 y2 は align 前のフィールドの描画範囲追跡に使っている。
   1536 ##     関数の一番最後で jx1 jy1 jx2 jy2 で x1 y1 x2 y2 を上書きする。
   1537 ##
   1538 function ble/canvas/trace/.justify/inc-quote {
   1539   [[ $trace_flags == *J* ]] || return 0
   1540   ((trace_sclevel++))
   1541   flag_justify=
   1542 }
   1543 function ble/canvas/trace/.justify/dec-quote {
   1544   [[ $trace_flags == *J* ]] || return 0
   1545   ((--trace_sclevel)) || flag_justify=1
   1546 }
   1547 ## @fn ble/canvas/trace/.justify/begin-line
   1548 ##   @var[out] jx0 jy0 x1 y1 x2 y2
   1549 function ble/canvas/trace/.justify/begin-line {
   1550   ((jx0=x1=x2=x,jy0=y1=y2=y))
   1551   gx1= gx2= gy1= gy2=
   1552   [[ $justify_align == *[cr]* ]] &&
   1553     ble/canvas/trace/.justify/next-field
   1554 }
   1555 ## @fn ble/canvas/trace/.justify/next-field [sep]
   1556 ##   @param[in,opt] sep
   1557 ##     省略時は最後のフィールドを意味する。
   1558 ##   @var[out] jx0 jy0 x1 y1 x2 y2
   1559 ##   @var[in,out] DRAW_BUFF justify_fields
   1560 function ble/canvas/trace/.justify/next-field {
   1561   local sep=$1 wmin=0
   1562   local esc; ble/canvas/sflush.draw -v esc
   1563   [[ $sep == ' ' ]] && wmin=1
   1564   ble/array#push justify_fields "${sep:-\$}:$wmin:$jx0,$jy0,$x,$y:$x1,$y1,$x2,$y2:$gx1,$gy1,$gx2,$gy2:$esc"
   1565   ((x+=wmin,jx0=x1=x2=x,jy0=y1=y2=y))
   1566 }
   1567 ## @fn ble/canvas/trace/.justify/unpack packed_data
   1568 ##   @var[out] sep wmin xI yI xF yF x1 y1 x2 y2 esc
   1569 function ble/canvas/trace/.justify/unpack {
   1570   local data=$1 buff
   1571   sep=${data::1}; data=${data:2}
   1572   wmin=${data%%:*}; data=${data#*:}
   1573   ble/string#split buff , "${data%%:*}"; data=${data#*:}
   1574   xI=${buff[0]} yI=${buff[1]} xF=${buff[2]} yF=${buff[3]}
   1575   ble/string#split buff , "${data%%:*}"; data=${data#*:}
   1576   x1=${buff[0]} y1=${buff[1]} x2=${buff[2]} y2=${buff[3]}
   1577   ble/string#split buff , "${data%%:*}"; data=${data#*:}
   1578   gx1=${buff[0]} gy1=${buff[1]} gx2=${buff[2]} gy2=${buff[3]}
   1579   esc=$data
   1580 }
   1581 ## @fn ble/canvas/trace/.justify/end-line
   1582 ##   これまでに justify_fields に記録した各フィールドの esc を align しつつ結合
   1583 ##   する。
   1584 ##   @var[in,out] justify_fields DRAW_BUFF justify_buff
   1585 function ble/canvas/trace/.justify/end-line {
   1586   # Note: 行内容がなかった場合でも行の高さだけは記録する
   1587   # (NEL で新しい行が形成される事に注意)。
   1588   if [[ $trace_flags == *B* ]]; then
   1589     ((y<jy1&&(jy1=y)))
   1590     ((y>jy2&&(jy2=y)))
   1591   fi
   1592   ((${#justify_fields[@]}||${#DRAW_BUFF[@]})) || return 0
   1593 
   1594   # 最後のフィールドを justify_fields に移動。
   1595   ble/canvas/trace/.justify/next-field
   1596   [[ $justify_align == *c* ]] &&
   1597     ble/canvas/trace/.justify/next-field
   1598 
   1599   local i width=0 ispan=0 has_content=
   1600   for ((i=0;i<${#justify_fields[@]};i++)); do
   1601     local sep wmin xI yI xF yF x1 y1 x2 y2 gx1 gy1 gx2 gy2 esc
   1602     ble/canvas/trace/.justify/unpack "${justify_fields[i]}"
   1603 
   1604     ((width+=xF-xI))
   1605     [[ $esc ]] && has_content=1
   1606 
   1607     # Note: 最後の要素の次には余白はない。
   1608     ((i+1==${#justify_fields[@]})) && break
   1609 
   1610     ((width+=wmin))
   1611     ((ispan++))
   1612   done
   1613   if [[ ! $has_content ]]; then
   1614     justify_fields=()
   1615     return 0
   1616   fi
   1617 
   1618   local nspan=$ispan
   1619 
   1620   local -a DRAW_BUFF=()
   1621 
   1622   # fill に使える余白を計算する。
   1623   # Note: _ble_term_xenl 及び opt_relative の時には本当の端末の右端には接触しな
   1624   #   いと想定して範囲の右端まで使用する。
   1625   local xlimit=$cols
   1626   [[ $_ble_term_xenl$opt_relative ]] || ((xlimit--))
   1627   local span=$((xlimit-width))
   1628 
   1629   x= y=
   1630   local ispan=0 vx=0 spanx=0
   1631   for ((i=0;i<${#justify_fields[@]};i++)); do
   1632     local sep wmin xI yI xF yF x1 y1 x2 y2 gx1 gy1 gx2 gy2 esc
   1633     ble/canvas/trace/.justify/unpack "${justify_fields[i]}"
   1634 
   1635     if [[ ! $x ]]; then
   1636       x=$xI y=$yI
   1637       if [[ $justify_align == right ]]; then
   1638         ble/canvas/put-move-x.draw "$((cols-1-x))"
   1639         ((x=cols-1))
   1640       fi
   1641     fi
   1642 
   1643     if [[ $esc ]]; then
   1644       local delta=0
   1645       ((vx+x1-xI<0)) && ((delta=-(vx+x1-xI)))
   1646       ((vx+x2-xI>xlimit)) && ((delta=xlimit-(vx+x2-xI)))
   1647       ble/canvas/put-move-x.draw "$((vx+delta-x))"
   1648       ((x=vx+delta))
   1649       ble/canvas/put.draw "$esc"
   1650       if [[ $trace_flags == *B* ]]; then
   1651         ((x+x1-xI<jx1&&(jx1=x+x1-xI)))
   1652         ((y+y1-yI<jy1&&(jy1=y+y1-yI)))
   1653         ((x+x2-xI>jx2&&(jx2=x+x2-xI)))
   1654         ((y+y2-yI>jy2&&(jy2=y+y2-yI)))
   1655       fi
   1656       if [[ $flag_gbox && $gx1 ]]; then
   1657         ((gx1+=x-xI,gx2+=x-xI))
   1658         ((gy1+=y-yI,gy2+=y-yI))
   1659         if [[ ! $jgx1 ]]; then
   1660           ((jgx1=gx1,jgy1=gy1,jgx2=gx2,jgy2=gy2))
   1661         else
   1662           ((gx1<jgx1&&(jgx1=gx1)))
   1663           ((gy1<jgy1&&(jgy1=gy1)))
   1664           ((gx2>jgx2&&(jgx2=gx2)))
   1665           ((gy2>jgy2&&(jgy2=gy2)))
   1666         fi
   1667       fi
   1668       ((x+=xF-xI,y+=yF-yI,vx+=xF-xI))
   1669     fi
   1670 
   1671     ((i+1==${#justify_fields[@]})) && break
   1672 
   1673     local new_spanx=$((span*++ispan/nspan))
   1674     local wfill=$((wmin+new_spanx-spanx))
   1675     ((vx+=wfill,spanx=new_spanx))
   1676 
   1677     # fillchar: 取り敢えず現在の実装では空白で fill
   1678     if [[ $sep == ' ' ]]; then
   1679       ble/string#reserve-prototype "$wfill"
   1680       ble/canvas/put.draw "${_ble_string_prototype::wfill}"
   1681       ((x+=wfill))
   1682     fi
   1683   done
   1684 
   1685   local ret
   1686   ble/canvas/sflush.draw
   1687   ble/array#push justify_buff "$ret"
   1688   justify_fields=()
   1689 }
   1690 
   1691 #--------------------------------------
   1692 ## (trace 内部変数) sc/rc 関連
   1693 ##
   1694 ##   @arr[local] trace_decsc
   1695 ##   @arr[local] trace_scosc
   1696 ##   @arr[local] trace_brack
   1697 ##
   1698 function ble/canvas/trace/.decsc {
   1699   [[ ${trace_decsc[5]} ]] || ble/canvas/trace/.justify/inc-quote
   1700   trace_decsc=("$x" "$y" "$g" "$lc" "$lg" active)
   1701   if [[ ! $flag_clip ]]; then
   1702     [[ :$opts: == *:noscrc:* ]] ||
   1703       ble/canvas/put.draw "$_ble_term_sc"
   1704   fi
   1705 }
   1706 function ble/canvas/trace/.decrc {
   1707   [[ ${trace_decsc[5]} ]] && ble/canvas/trace/.justify/dec-quote
   1708   if [[ ! $flag_clip ]]; then
   1709     ble/canvas/trace/.put-sgr.draw "${trace_decsc[2]}" # g を明示的に復元。
   1710     if [[ :$opts: == *:noscrc:* ]]; then
   1711       ble/canvas/put-move.draw "$((trace_decsc[0]-x))" "$((trace_decsc[1]-y))"
   1712     else
   1713       ble/canvas/put.draw "$_ble_term_rc"
   1714     fi
   1715   fi
   1716   x=${trace_decsc[0]}
   1717   y=${trace_decsc[1]}
   1718   g=${trace_decsc[2]}
   1719   lc=${trace_decsc[3]}
   1720   lg=${trace_decsc[4]}
   1721   trace_decsc[5]=
   1722 }
   1723 function ble/canvas/trace/.scosc {
   1724   [[ ${trace_scosc[5]} ]] || ble/canvas/trace/.justify/inc-quote
   1725   trace_scosc=("$x" "$y" "$g" "$lc" "$lg" active)
   1726   if [[ ! $flag_clip ]]; then
   1727     [[ :$opts: == *:noscrc:* ]] ||
   1728       ble/canvas/put.draw "$_ble_term_sc"
   1729   fi
   1730 }
   1731 function ble/canvas/trace/.scorc {
   1732   [[ ${trace_scosc[5]} ]] && ble/canvas/trace/.justify/dec-quote
   1733   if [[ ! $flag_clip ]]; then
   1734     ble/canvas/trace/.put-sgr.draw "$g" # g は変わらない様に。
   1735     if [[ :$opts: == *:noscrc:* ]]; then
   1736       ble/canvas/put-move.draw "$((trace_scosc[0]-x))" "$((trace_scosc[1]-y))"
   1737     else
   1738       ble/canvas/put.draw "$_ble_term_rc"
   1739     fi
   1740   fi
   1741   x=${trace_scosc[0]}
   1742   y=${trace_scosc[1]}
   1743   lc=${trace_scosc[3]}
   1744   lg=${trace_scosc[4]}
   1745   trace_scosc[5]=
   1746 }
   1747 function ble/canvas/trace/.ps1sc {
   1748   ble/canvas/trace/.justify/inc-quote
   1749   trace_brack[${#trace_brack[*]}]="$x $y"
   1750 }
   1751 function ble/canvas/trace/.ps1rc {
   1752   local lastIndex=$((${#trace_brack[*]}-1))
   1753   if ((lastIndex>=0)); then
   1754     ble/canvas/trace/.justify/dec-quote
   1755     local -a scosc
   1756     ble/string#split-words scosc "${trace_brack[lastIndex]}"
   1757     ((x=scosc[0]))
   1758     ((y=scosc[1]))
   1759     builtin unset -v "trace_brack[$lastIndex]"
   1760   fi
   1761 }
   1762 
   1763 #--------------------------------------
   1764 function ble/canvas/trace/.NEL {
   1765   if [[ $opt_nooverflow ]] && ((y+1>=lines)); then
   1766     ble/canvas/trace/.process-overflow
   1767     return 1
   1768   fi
   1769 
   1770   [[ $flag_justify ]] &&
   1771     ble/canvas/trace/.justify/end-line
   1772   if [[ ! $flag_clip ]]; then
   1773     if [[ $opt_relative ]]; then
   1774       ((x)) && ble/canvas/put-cub.draw "$x"
   1775       ble/canvas/put-cud.draw 1
   1776     else
   1777       ble/canvas/put.draw "$_ble_term_cr"
   1778       ble/canvas/put.draw "$_ble_term_nl"
   1779     fi
   1780   fi
   1781   ((y++,x=0,lc=32,lg=0))
   1782   if [[ $flag_bbox ]]; then
   1783     ((x<x1)) && x1=$x
   1784     ((y>y2)) && y2=$y
   1785   fi
   1786   [[ $flag_justify ]] &&
   1787     ble/canvas/trace/.justify/begin-line
   1788   return 0
   1789 }
   1790 ## @fn ble/canvas/trace/.SGR
   1791 ##   @param[in] param seq
   1792 ##   @var[out] DRAW_BUFF
   1793 ##   @var[in,out] g
   1794 function ble/canvas/trace/.SGR {
   1795   local param=$1 seq=$2 specs i iN
   1796   if [[ ! $param ]]; then
   1797     g=0
   1798     [[ $flag_clip ]] || ble/canvas/put.draw "$opt_sgr0"
   1799     return 0
   1800   fi
   1801 
   1802   # update g
   1803   if [[ $opt_terminfo ]]; then
   1804     ble/color/read-sgrspec "$param"
   1805   else
   1806     ble/color/read-sgrspec "$param" ansi
   1807   fi
   1808   [[ $flag_clip ]] || ble/canvas/trace/.put-sgr.draw "$g"
   1809 }
   1810 function ble/canvas/trace/.process-csi-sequence {
   1811   local seq=$1 seq1=${1:2} rex
   1812   local char=${seq1:${#seq1}-1:1} param=${seq1::${#seq1}-1}
   1813   if [[ ! ${param//[0-9:;]} ]]; then
   1814     # CSI 数字引数 + 文字
   1815     case $char in
   1816     (m) # SGR
   1817       ble/canvas/trace/.SGR "$param" "$seq"
   1818       return 0 ;;
   1819     ([ABCDEFGIZ\`ade])
   1820       local arg=0
   1821       [[ $param =~ ^[0-9]+$ ]] && ((arg=10#0$param))
   1822       ((arg==0&&(arg=1)))
   1823 
   1824       local ox=$x oy=$y
   1825       if [[ $char == A ]]; then
   1826         # CUU "CSI A"
   1827         ((y-=arg,y<0&&(y=0)))
   1828         ((!flag_clip&&y<oy)) && ble/canvas/put-cuu.draw "$((oy-y))"
   1829       elif [[ $char == [Be] ]]; then
   1830         # CUD "CSI B"
   1831         # VPR "CSI e"
   1832         ((y+=arg,y>=lines&&(y=lines-1)))
   1833         ((!flag_clip&&y>oy)) && ble/canvas/put-cud.draw "$((y-oy))"
   1834       elif [[ $char == [Ca] ]]; then
   1835         # CUF "CSI C"
   1836         # HPR "CSI a"
   1837         ((x+=arg,x>=cols&&(x=cols-1)))
   1838         ((!flag_clip&&x>ox)) && ble/canvas/put-cuf.draw "$((x-ox))"
   1839       elif [[ $char == D ]]; then
   1840         # CUB "CSI D"
   1841         ((x-=arg,x<0&&(x=0)))
   1842         ((!flag_clip&&x<ox)) && ble/canvas/put-cub.draw "$((ox-x))"
   1843       elif [[ $char == E ]]; then
   1844         # CNL "CSI E"
   1845         ((y+=arg,y>=lines&&(y=lines-1),x=0))
   1846         if [[ ! $flag_clip ]]; then
   1847           ((y>oy)) && ble/canvas/put-cud.draw "$((y-oy))"
   1848           ble/canvas/put.draw "$_ble_term_cr"
   1849         fi
   1850       elif [[ $char == F ]]; then
   1851         # CPL "CSI F"
   1852         ((y-=arg,y<0&&(y=0),x=0))
   1853         if [[ ! $flag_clip ]]; then
   1854           ((y<oy)) && ble/canvas/put-cuu.draw "$((oy-y))"
   1855           ble/canvas/put.draw "$_ble_term_cr"
   1856         fi
   1857       elif [[ $char == [G\`] ]]; then
   1858         # CHA "CSI G"
   1859         # HPA "CSI `"
   1860         ((x=arg-1,x<0&&(x=0),x>=cols&&(x=cols-1)))
   1861         if [[ ! $flag_clip ]]; then
   1862           if [[ $opt_relative ]]; then
   1863             ble/canvas/put-move-x.draw "$((x-ox))"
   1864           else
   1865             ble/canvas/put-hpa.draw "$((x+1))"
   1866           fi
   1867         fi
   1868       elif [[ $char == d ]]; then
   1869         # VPA "CSI d"
   1870         ((y=arg-1,y<0&&(y=0),y>=lines&&(y=lines-1)))
   1871         if [[ ! $flag_clip ]]; then
   1872           if [[ $opt_relative ]]; then
   1873             ble/canvas/put-move-y.draw "$((y-oy))"
   1874           else
   1875             ble/canvas/put-vpa.draw "$((y+1))"
   1876           fi
   1877         fi
   1878       elif [[ $char == I ]]; then
   1879         # CHT "CSI I"
   1880         local tx
   1881         ((tx=(x/it+arg)*it,
   1882           tx>=cols&&(tx=cols-1)))
   1883         if ((tx>x)); then
   1884           [[ $flag_clip ]] || ble/canvas/put-cuf.draw "$((tx-x))"
   1885           ((x=tx))
   1886         fi
   1887       elif [[ $char == Z ]]; then
   1888         # CHB "CSI Z"
   1889         local tx
   1890         ((tx=((x+it-1)/it-arg)*it,
   1891           tx<0&&(tx=0)))
   1892         if ((tx<x)); then
   1893           [[ $flag_clip ]] || ble/canvas/put-cub.draw "$((x-tx))"
   1894           ((x=tx))
   1895         fi
   1896       fi
   1897       ble/canvas/trace/.measure-point
   1898       lc=-1 lg=0
   1899       return 0 ;;
   1900     ([Hf])
   1901       # CUP "CSI H"
   1902       # HVP "CSI f"
   1903       local -a params
   1904       ble/string#split-words params "${param//[^0-9]/ }"
   1905       params=("${params[@]/#/10#0}") # WA #D1570 checked (is-array)
   1906       local dstx dsty
   1907       ((dstx=params[1]-1))
   1908       ((dsty=params[0]-1))
   1909       ((dstx<0&&(dstx=0),dstx>=cols&&(dstx=cols-1),
   1910         dsty<0&&(dsty=0),dsty>=lines&&(dsty=lines-1)))
   1911       ble/canvas/trace/.goto "$dstx" "$dsty"
   1912       lc=-1 lg=0
   1913       return 0 ;;
   1914     ([su]) # SCOSC SCORC
   1915       if [[ $char == s ]]; then
   1916         ble/canvas/trace/.scosc
   1917       else
   1918         ble/canvas/trace/.scorc
   1919       fi
   1920       return 0 ;;
   1921     # ■その他色々?
   1922     # ([JPX@MKL]) # 挿入削除→カーソルの位置は不変 lc?
   1923     # ([hl]) # SM RM DECSM DECRM
   1924     esac
   1925   fi
   1926 
   1927   ble/canvas/put.draw "$seq"
   1928 }
   1929 function ble/canvas/trace/.process-esc-sequence {
   1930   local seq=$1 char=${1:1}
   1931   case $char in
   1932   (7) # DECSC
   1933     ble/canvas/trace/.decsc
   1934     return 0 ;;
   1935   (8) # DECRC
   1936     ble/canvas/trace/.decrc
   1937     return 0 ;;
   1938   (D) # IND
   1939     [[ $opt_nooverflow ]] && ((y+1>=lines)) && return 0
   1940     if [[ $flag_clip || $opt_relative || $flag_justify ]]; then
   1941       ((y+1>=lines)) && return 0
   1942       ((y++))
   1943       [[ $flag_clip ]] ||
   1944         ble/canvas/put-cud.draw 1
   1945     else
   1946       ((y++))
   1947       ble/canvas/put.draw "$_ble_term_ind"
   1948       [[ $_ble_term_ind != $'\eD' ]] &&
   1949         ble/canvas/put-hpa.draw "$((x+1))" # tput ind が唯の改行の時がある
   1950     fi
   1951     lc=-1 lg=0
   1952     ble/canvas/trace/.measure-point
   1953     return 0 ;;
   1954   (M) # RI
   1955     [[ $opt_nooverflow ]] && ((y==0)) && return 0
   1956     if [[ $flag_clip || $opt_relative || $flag_justify ]]; then
   1957       ((y==0)) && return 0
   1958       ((y--))
   1959       [[ $flag_clip ]] ||
   1960         ble/canvas/put-cuu.draw 1
   1961     else
   1962       ((y--))
   1963       ble/canvas/put.draw "$_ble_term_ri"
   1964     fi
   1965     lc=-1 lg=0
   1966     ble/canvas/trace/.measure-point
   1967     return 0 ;;
   1968   (E) # NEL
   1969     ble/canvas/trace/.NEL
   1970     return 0 ;;
   1971   # (H) # HTS 面倒だから無視。
   1972   # ([KL]) PLD PLU
   1973   #   上付き・下付き文字 (端末における実装は色々)
   1974   esac
   1975 
   1976   ble/canvas/put.draw "$seq"
   1977 }
   1978 
   1979 function ble/canvas/trace/.impl {
   1980   local text=$1 opts=$2
   1981 
   1982   # cygwin では LC_COLLATE=C にしないと
   1983   # 正規表現の range expression が期待通りに動かない。
   1984   local LC_ALL= LC_COLLATE=C
   1985 
   1986   # constants
   1987   local cols=${COLUMNS:-80} lines=${LINES:-25}
   1988   local it=${bleopt_tab_width:-$_ble_term_it} xenl=$_ble_term_xenl
   1989   ble/string#reserve-prototype "$it"
   1990 
   1991   # Note: 文字符号化方式によっては対応する文字が存在しない可能性がある。
   1992   #   その時は st='\u009C' になるはず。2文字以上のとき変換に失敗したと見做す。
   1993   local ret rex
   1994   ble/util/c2s 156; local st=$ret # œ (ST)
   1995   ((${#st}>=2)) && st=
   1996 
   1997   #-------------------------------------
   1998   # Options
   1999 
   2000   local xinit=$x yinit=$y ginit=$g
   2001 
   2002   # @var trace_flags
   2003   #   R relative
   2004   #   B measure-bbox
   2005   #   G measure-gbox
   2006   #   J justify, right, center
   2007   #   C clip
   2008   #
   2009   local trace_flags=
   2010 
   2011   local opt_nooverflow=; [[ :$opts: == *:truncate:* || :$opts: == *:confine:* ]] && opt_nooverflow=1
   2012   local opt_relative=; [[ :$opts: == *:relative:* ]] && trace_flags=R$trace_flags opt_relative=1
   2013   [[ :$opts: == *:measure-bbox:* ]] && trace_flags=B$trace_flags
   2014   [[ :$opts: == *:measure-gbox:* ]] && trace_flags=G$trace_flags
   2015   [[ :$opts: == *:left-char:* ]] && trace_flags=L$trace_flags
   2016   local opt_terminfo=; [[ :$opts: == *:terminfo:* ]] && opt_terminfo=1
   2017 
   2018   if local rex=':(justify(=[^:]+)?|center|right):'; [[ :$opts: =~ $rex ]]; then
   2019     trace_flags=J$trace_flags
   2020     local jx0=$x jy0=$y
   2021     local justify_sep= justify_align=
   2022     local -a justify_buff=()
   2023     local -a justify_fields=()
   2024     case ${BASH_REMATCH[1]} in
   2025     (justify*) justify_sep=${BASH_REMATCH[2]:1}${BASH_REMATCH[2]:-' '} ;;
   2026     (center)   justify_align=c ;;
   2027     (right)    justify_align=r ;;
   2028     esac
   2029   fi
   2030 
   2031   if local rex=':clip=([0-9]*),([0-9]*)([-+])([0-9]*),([0-9]*):'; [[ :$opts: =~ $rex ]]; then
   2032     local cx1 cy1 cx2 cy2 cx cy cg
   2033     trace_flags=C$trace_flags
   2034     cx1=${BASH_REMATCH[1]} cy1=${BASH_REMATCH[2]}
   2035     cx2=${BASH_REMATCH[4]} cy2=${BASH_REMATCH[5]}
   2036     [[ ${BASH_REMATCH[3]} == + ]] && ((cx2+=cx1,cy2+=cy1))
   2037     ((cx1<=cx2)) || local cx1=$cx2 cx2=$cx1
   2038     ((cy1<=cy2)) || local cy1=$cy2 cy2=$cy1
   2039     ((cx1<0)) && cx1=0
   2040     ((cy1<0)) && cy1=0
   2041     ((cols<cx2)) && cx2=$cols
   2042     ((lines<cy2)) && cy2=$lines
   2043     local cx=$cx1 cy=$cy1 cg=
   2044   fi
   2045 
   2046   local trace_g2sgr=ble/color/g2sgr
   2047   [[ :$opts: == *:ansi:* || $trace_flags == *C*J* ]] &&
   2048     trace_g2sgr=ble/color/g2sgr-ansi
   2049 
   2050   local opt_g0= opt_sgr0=$_ble_term_sgr0
   2051   if rex=':g0=([^:]+):'; [[ :$opts: =~ $rex ]]; then
   2052     opt_g0=${BASH_REMATCH[1]}
   2053   elif rex=':face0=([^:]+):'; [[ :$opts: =~ $rex ]]; then
   2054     ble/color/face2g "${BASH_REMATCH[1]}"; opt_g0=$ret
   2055   fi
   2056   if [[ $opt_g0 ]]; then
   2057     "$trace_g2sgr" "$opt_g0"; opt_sgr0=$ret
   2058     ble/canvas/put.draw "$opt_sgr0"
   2059     g=$opt_g0
   2060   fi
   2061 
   2062   #-------------------------------------
   2063 
   2064   # CSI
   2065   local rex_csi='^\[[ -?]*[@-~]'
   2066   # OSC, DCS, SOS, PM, APC Sequences + "GNU screen ESC k"
   2067   local rex_osc='^([]PX^_k])([^'$st']|+[^\'$st'])*(\\|'${st:+'|'}$st'|$)'
   2068   # ISO-2022 関係 (3byte以上の物)
   2069   local rex_2022='^[ -/]+[@-~]'
   2070   # ESC ?
   2071   local rex_esc='^[ -~]'
   2072 
   2073   # states
   2074   local trace_sclevel=0
   2075   local -a trace_brack=()
   2076   local -a trace_scosc=()
   2077   local -a trace_decsc=()
   2078 
   2079   local flag_lchar=
   2080   if [[ $trace_flags == *L* ]]; then
   2081     flag_lchar=1
   2082   else
   2083     local lc=32 lg=0
   2084   fi
   2085 
   2086   # prepare measure
   2087   local flag_bbox= flag_gbox=
   2088   if [[ $trace_flags == *[BJ]* ]]; then
   2089     flag_bbox=1
   2090     [[ $trace_flags != *B* ]] && local x1 x2 y1 y2
   2091     [[ $trace_flags != *G* ]] && local gx1= gx2= gy1= gy2=
   2092     ((x1=x2=x,y1=y2=y))
   2093 
   2094     [[ $trace_flags == *J*B* ]] &&
   2095       local jx1=$x jy1=$y jx2=$x jy2=$y
   2096   fi
   2097   if [[ $trace_flags == *G* ]]; then
   2098     ((flag_gbox=1))
   2099     gx1= gx2= gy1= gy2=
   2100     [[ $trace_flags == *J* ]] &&
   2101       local jgx1= jgy1= jgx2= jgy2=
   2102   fi
   2103 
   2104   # flag_clip: justify 処理が入っている時は後で clip を処理する。
   2105   local flag_clip=
   2106   [[ $trace_flags == *C* && $trace_flags != *J* ]] && flag_clip=1
   2107 
   2108   # opt_relative の時には右端に接触しない前提。justify の時には、後の再配置の時
   2109   # に xenl について処理するので、フィールド内追跡では xenl は気にしなくて良い。
   2110   local xenl=$_ble_term_xenl
   2111   [[ $opt_relative || $trace_flags == *J* ]] && xenl=1
   2112   local xlimit=$((xenl?cols:cols-1))
   2113 
   2114   local flag_justify=
   2115   if [[ $trace_flags == *J* ]]; then
   2116     flag_justify=1
   2117     ble/canvas/trace/.justify/begin-line
   2118   fi
   2119 
   2120   local i=0 iN=${#text}
   2121   while ((i<iN)); do
   2122     local tail=${text:i}
   2123     local is_overflow=
   2124     if [[ $flag_justify && $justify_sep && $tail == ["$justify_sep"]* ]]; then
   2125       ble/canvas/trace/.justify/next-field "${tail::1}"
   2126       ((i++))
   2127     elif [[ $tail == [-]* ]]; then
   2128       local s=${tail::1}
   2129       ((i++))
   2130       case $s in
   2131       ($'\e')
   2132         if [[ $tail =~ $rex_osc ]]; then
   2133           # 各種メッセージ (素通り)
   2134           s=$BASH_REMATCH
   2135           [[ ${BASH_REMATCH[3]} ]] || s="$s\\" # 終端の追加
   2136           ((i+=${#BASH_REMATCH}-1))
   2137           ble/canvas/trace/.put-atomic.draw "$s" 0
   2138         elif [[ $tail =~ $rex_csi ]]; then
   2139           # Control sequences
   2140           ((i+=${#BASH_REMATCH}-1))
   2141           ble/canvas/trace/.process-csi-sequence "$BASH_REMATCH"
   2142         elif [[ $tail =~ $rex_2022 ]]; then
   2143           # ISO-2022 (素通り)
   2144           ble/canvas/trace/.put-atomic.draw "$BASH_REMATCH" 0
   2145           ((i+=${#BASH_REMATCH}-1))
   2146         elif [[ $tail =~ $rex_esc ]]; then
   2147           ((i+=${#BASH_REMATCH}-1))
   2148           ble/canvas/trace/.process-esc-sequence "$BASH_REMATCH"
   2149         else
   2150           ble/canvas/trace/.put-atomic.draw "$s" 0
   2151         fi ;;
   2152       ($'\b') # BS
   2153         if ((x>0)); then
   2154           [[ $flag_clip ]] || ble/canvas/put.draw "$s"
   2155           ((x--,lc=32,lg=g))
   2156           ble/canvas/trace/.measure-point
   2157         fi ;;
   2158       ($'\t') # HT
   2159         local tx
   2160         ((tx=(x+it)/it*it,
   2161           tx>=cols&&(tx=cols-1)))
   2162         if ((x<tx)); then
   2163           ((lc=32,lg=g))
   2164           ble/canvas/trace/.put-ascii.draw "${_ble_string_prototype::tx-x}"
   2165         fi ;;
   2166       ($'\n') # LF = CR+LF
   2167         ble/canvas/trace/.NEL ;;
   2168       ($'\v') # VT
   2169         if ((y+1<lines||!opt_nooverflow)); then
   2170           if [[ $flag_clip || $opt_relative || $flag_justify ]]; then
   2171             if ((y+1<lines)); then
   2172               [[ $flag_clip ]] ||
   2173                 ble/canvas/put-cud.draw 1
   2174               ((y++,lc=32,lg=0))
   2175             fi
   2176           else
   2177             ble/canvas/put.draw "$_ble_term_cr"
   2178             ble/canvas/put.draw "$_ble_term_nl"
   2179             ((x)) && ble/canvas/put-cuf.draw "$x"
   2180             ((y++,lc=32,lg=0))
   2181           fi
   2182           ble/canvas/trace/.measure-point
   2183         fi ;;
   2184       ($'\r') # CR ^M
   2185         local ox=$x
   2186         ((x=0,lc=-1,lg=0))
   2187         if [[ ! $flag_clip ]]; then
   2188           if [[ $flag_justify ]]; then
   2189             ble/canvas/put-move-x.draw "$((jx0-ox))"
   2190             ((x=jx0))
   2191           elif [[ $opt_relative ]]; then
   2192             ble/canvas/put-cub.draw "$ox"
   2193           else
   2194             ble/canvas/put.draw "$_ble_term_cr"
   2195           fi
   2196         fi
   2197         ble/canvas/trace/.measure-point ;;
   2198       # Note: \001 (^A) 及び \002 (^B) は PS1 の処理で \[ \] を意味するそうだ。#D1074
   2199       ($'\001') [[ :$opts: == *:prompt:* ]] && ble/canvas/trace/.ps1sc ;;
   2200       ($'\002') [[ :$opts: == *:prompt:* ]] && ble/canvas/trace/.ps1rc ;;
   2201       # その他の制御文字は  (BEL)  (FF) も含めてゼロ幅と解釈する
   2202       (*) ble/canvas/put.draw "$s" ;;
   2203       esac
   2204     elif ble/util/isprint+ "$tail"; then
   2205       local s=$BASH_REMATCH
   2206       [[ $flag_justify && $justify_sep ]] && s=${s%%["$justify_sep"]*}
   2207       local w=${#s}
   2208       if [[ $opt_nooverflow ]]; then
   2209         local wmax=$((lines*cols-(y*cols+x)))
   2210         ((xenl||wmax--,wmax<0&&(wmax=0)))
   2211         ((w>wmax)) && w=$wmax is_overflow=1
   2212       fi
   2213 
   2214       local t=${s::w}
   2215       if [[ $flag_clip || $opt_relative || $flag_justify ]]; then
   2216         local tlen=$w len=$((cols-x))
   2217         if ((tlen>len)); then
   2218           while ((tlen>len)); do
   2219             ble/canvas/trace/.put-ascii.draw "${t::len}"
   2220             t=${t:len}
   2221             ((x=cols,tlen-=len,len=cols))
   2222             ble/canvas/trace/.NEL
   2223           done
   2224           w=${#t}
   2225         fi
   2226       fi
   2227 
   2228       if [[ $flag_lchar ]]; then
   2229         local ret
   2230         ble/util/s2c "${s:w-1:1}"
   2231         lc=$ret lg=$g
   2232       fi
   2233       ble/canvas/trace/.put-ascii.draw "$t"
   2234       ((i+=${#s}))
   2235 
   2236       if local extend; ble/unicode/GraphemeCluster/extend-ascii "$text" "$i"; then
   2237         ble/canvas/trace/.put-atomic.draw "${text:i:extend}" 0
   2238         ((i+=extend))
   2239       fi
   2240 
   2241     else
   2242       local c w cs cb extend
   2243       ble/unicode/GraphemeCluster/match "$text" "$i" R
   2244       if [[ $opt_nooverflow ]] && ! ((x+w<=xlimit||y+1<lines&&w<=cols)); then
   2245         is_overflow=1
   2246       else
   2247         if ((x+w>cols)); then
   2248           if [[ $flag_clip || $opt_relative || $flag_justify ]]; then
   2249             ble/canvas/trace/.NEL
   2250           else
   2251             # 行に入りきらない場合の調整
   2252             ble/canvas/trace/.put-ascii.draw "${_ble_string_prototype::cols-x}"
   2253           fi
   2254         fi
   2255         lc=$c lg=$g
   2256         ble/canvas/trace/.put-atomic.draw "$cs" "$w"
   2257       fi
   2258       ((i+=1+extend))
   2259     fi
   2260 
   2261     [[ $is_overflow ]] && ble/canvas/trace/.process-overflow
   2262   done
   2263 
   2264   if [[ $trace_flags == *J* ]]; then
   2265     if [[ ! $flag_justify ]]; then
   2266       # 各種 sc により一時的に justify が無効化されていたとしても、強制的に rc
   2267       # を出力して閉じる。
   2268       [[ ${trace_scosc[5]} ]] && ble/canvas/trace/.scorc
   2269       [[ ${trace_decsc[5]} ]] && ble/canvas/trace/.decrc
   2270       while [[ ${trace_brack[0]} ]]; do ble/canvas/trace/.ps1rc; done
   2271     fi
   2272     ble/canvas/trace/.justify/end-line
   2273     DRAW_BUFF=("${justify_buff[@]}")
   2274 
   2275     [[ $trace_flags == *B* ]] &&
   2276       ((x1=jx1,y1=jy1,x2=jx2,y2=jy2))
   2277     [[ $trace_flags == *G* ]] &&
   2278       gx1=$jgx1 gy1=$jgy1 gx2=$jgx2 gy2=$jgy2
   2279 
   2280     if [[ $trace_flags == *C* ]]; then
   2281       ble/canvas/sflush.draw
   2282       x=$xinit y=$yinit g=$ginit
   2283       local trace_opts=clip=$cx1,$cy1-$cx2,$cy2
   2284       [[ :$opts: == *:ansi:* ]] && trace_opts=$trace_opts:ansi
   2285       ble/canvas/trace/.impl "$ret" "$trace_opts"
   2286       cx=$x cy=$y cg=$g
   2287     fi
   2288   fi
   2289 
   2290   [[ $trace_flags == *B* ]] && ((y2++))
   2291   [[ $trace_flags == *G* ]] && ((gy2++))
   2292   if [[ $trace_flags == *C* ]]; then
   2293     x=$cx y=$cy g=$cg
   2294     if [[ $trace_flags == *B* ]]; then
   2295       ((x1<cx1)) && x1=$cx1
   2296       ((x1>cx2)) && x1=$cx2
   2297       ((x2<cx1)) && x2=$cx1
   2298       ((x2>cx2)) && x2=$cx2
   2299       ((y1<cy1)) && y1=$cy1
   2300       ((y1>cy2)) && y1=$cy2
   2301       ((y2<cy1)) && y2=$cy1
   2302       ((y2>cy2)) && y2=$cy2
   2303     fi
   2304     if [[ $trace_flags == *G* ]]; then
   2305       if ((gx2<=cx1||cx2<=gx1||gy2<=cy1||cy2<=gy1)); then
   2306         gx1= gx2= gy1= gy2=
   2307       else
   2308         ((gx1<cx1)) && gx1=$cx1
   2309         ((gx2>cx2)) && gx2=$cx2
   2310         ((gy1<cy1)) && gy1=$cy1
   2311         ((gy2>cy2)) && gy2=$cy2
   2312       fi
   2313     fi
   2314   fi
   2315 }
   2316 function ble/canvas/trace.draw {
   2317   ble/canvas/trace/.impl "$@" 2>/dev/null # Note: suppress LC_COLLATE errors #D1205 #D1341 #D1440
   2318 }
   2319 function ble/canvas/trace {
   2320   local -a DRAW_BUFF=()
   2321   ble/canvas/trace/.impl "$@" 2>/dev/null # Note: suppress LC_COLLATE errors #D1205 #D1341 #D1440
   2322   ble/canvas/sflush.draw # -> ret
   2323 }
   2324 
   2325 #------------------------------------------------------------------------------
   2326 # ble/canvas/construct-text
   2327 
   2328 ## @fn ble/canvas/trace-text/.put-atomic nchar text
   2329 ##   指定した文字列を out に追加しつつ、現在位置を更新します。
   2330 ##   文字列は幅 1 の文字で構成されていると仮定します。
   2331 ##   @var[in,out] x y out
   2332 ##   @var[in] cols lines
   2333 ##
   2334 function ble/canvas/trace-text/.put-simple {
   2335   local nchar=$1
   2336   ((nchar)) || return 0
   2337 
   2338   local nput=$((cols*lines-!_ble_term_xenl-(y*cols+x)))
   2339   ((nput>0)) || return 1
   2340   ((nput>nchar)) && nput=$nchar
   2341   out=$out${2::nput}
   2342   ((x+=nput,y+=x/cols,x%=cols))
   2343   ((_ble_term_xenl&&x==0&&(y--,x=cols)))
   2344   ((nput==nchar)); return "$?"
   2345 }
   2346 ## @fn x y cols out ; ble/canvas/trace-text/.put-atomic ( w char )+ ; x y out
   2347 ##   指定した文字を out に追加しつつ、現在位置を更新します。
   2348 ##   範囲に収まり切らない時に失敗します。
   2349 function ble/canvas/trace-text/.put-atomic {
   2350   local w=$1 c=$2
   2351 
   2352   # 収まらない時は skip
   2353   ((y*cols+x+w<=cols*lines-!_ble_term_xenl)) || return 1
   2354 
   2355   # その行に入りきらない文字は次の行へ (幅 w が2以上の文字)
   2356   if ((x<cols&&cols<x+w)); then
   2357     if [[ :$opts: == *:nonewline:* ]]; then
   2358       ble/string#reserve-prototype "$((cols-x))"
   2359       out=$out${_ble_string_prototype::cols-x}
   2360       ((x=cols))
   2361     else
   2362       out=$out$'\n'
   2363       ((y++,x=0))
   2364     fi
   2365   fi
   2366 
   2367   # w!=0 のとき行末にいたら次の行へ暗黙移動
   2368   ((w&&x==cols&&(y++,x=0)))
   2369 
   2370   # 改行しても尚行内に収まらない時は ## で代用
   2371   local limit=$((cols-(y+1==lines&&!_ble_term_xenl)))
   2372   if ((x+w>limit)); then
   2373     ble/string#reserve-prototype "$((limit-x))"
   2374     local pad=${_ble_string_prototype::limit-x}
   2375     out=$out$sgr1${pad//?/'#'}$sgr0
   2376     x=$limit
   2377     ((y+1<lines)); return "$?"
   2378   fi
   2379 
   2380   out=$out$c
   2381   ((x+=w,!_ble_term_xenl&&x==cols&&(y++,x=0)))
   2382   return 0
   2383 }
   2384 ## @fn x y cols out ; ble/canvas/trace-text/.put-nl-if-eol ; x y out
   2385 ##   行末にいる場合次の行へ移動します。
   2386 function ble/canvas/trace-text/.put-nl-if-eol {
   2387   if ((x==cols&&y+1<lines)); then
   2388     [[ :$opts: == *:nonewline:* ]] && return 0
   2389     ((_ble_term_xenl)) && out=$out$'\n'
   2390     ((y++,x=0))
   2391   fi
   2392 }
   2393 
   2394 ## @fn ble/canvas/trace-text text opts
   2395 ##   指定した文字列を表示する為の制御系列に変換します。
   2396 ##   @param[in] text
   2397 ##   @param[in] opts
   2398 ##     nonewline
   2399 ##
   2400 ##     external-sgr
   2401 ##       @var[in] sgr0 sgr1
   2402 ##       特殊文字の強調に用いる SGR シーケンスを外部から提供します。
   2403 ##       sgr0 に通常文字の表示に用いる SGR を、
   2404 ##       sgr1 に特殊文字の表示に用いる SGR を指定します。
   2405 ##
   2406 ##   @var[in] cols lines
   2407 ##   @var[in,out] x y
   2408 ##   @var[out] ret
   2409 ##   @exit
   2410 ##     指定した範囲に文字列が収まった時に成功します。
   2411 function ble/canvas/trace-text {
   2412   local LC_ALL= LC_COLLATE=C
   2413 
   2414   local out= glob='*[! -~]*'
   2415   local opts=$2 flag_overflow=
   2416   [[ :$opts: == *:external-sgr:* ]] ||
   2417     local sgr0=$_ble_term_sgr0 sgr1=$_ble_term_rev
   2418   if [[ $1 != $glob ]]; then
   2419     # G0 だけで構成された文字列は先に単純に処理する
   2420     ble/canvas/trace-text/.put-simple "${#1}" "$1"
   2421   else
   2422     local glob='[ -~]*' globx='[! -~]*'
   2423     local i iN=${#1} text=$1
   2424     for ((i=0;i<iN;)); do
   2425       local tail=${text:i}
   2426       if [[ $tail == $glob ]]; then
   2427         local span=${tail%%$globx}; ((i+=${#span}))
   2428         ble/canvas/trace-text/.put-simple "${#span}" "$span"
   2429         if local extend; ble/unicode/GraphemeCluster/extend-ascii "$text" "$i"; then
   2430           out=$out${text:i:extend}
   2431           ((i+=extend))
   2432         fi
   2433       else
   2434         local c w cs cb extend
   2435         ble/unicode/GraphemeCluster/match "$text" "$i"
   2436         ((i+=1+extend))
   2437         ((cb==_ble_unicode_GraphemeClusterBreak_Control)) &&
   2438           cs=$sgr1$cs$sgr0
   2439         ble/canvas/trace-text/.put-atomic "$w" "$cs"
   2440       fi && ((y*cols+x<lines*cols)) ||
   2441         { flag_overflow=1; break; }
   2442     done
   2443   fi
   2444 
   2445   ble/canvas/trace-text/.put-nl-if-eol
   2446   ret=$out
   2447 
   2448   # 収まったかどうか
   2449   [[ ! $flag_overflow ]]
   2450 }
   2451 # Note: suppress LC_COLLATE errors #D1205 #D1262 #1341 #D1440
   2452 ble/function#suppress-stderr ble/canvas/trace-text
   2453 
   2454 #------------------------------------------------------------------------------
   2455 # ble/textmap
   2456 
   2457 _ble_textmap_VARNAMES=(
   2458   _ble_textmap_cols
   2459   _ble_textmap_length
   2460   _ble_textmap_begx
   2461   _ble_textmap_begy
   2462   _ble_textmap_endx
   2463   _ble_textmap_endy
   2464 
   2465   _ble_textmap_pos
   2466   _ble_textmap_glyph
   2467   _ble_textmap_ichg
   2468 
   2469   _ble_textmap_dbeg
   2470   _ble_textmap_dend
   2471   _ble_textmap_dend0
   2472   _ble_textmap_umin
   2473   _ble_textmap_umax)
   2474 
   2475 ## 文字列の配置計算に関する情報
   2476 ##
   2477 ##   前回の配置計算の前提と結果を保持する変数群を以下に説明します。
   2478 ##   以下は配置計算の前提になる情報です。
   2479 ##
   2480 ##   @var _ble_textmap_cols
   2481 ##     配置幅を保持します。
   2482 ##   @var _ble_textmap_begx
   2483 ##   @var _ble_textmap_begy
   2484 ##     配置の開始位置を保持します。
   2485 ##   @var _ble_textmap_length
   2486 ##     配置文字列の長さを保持します。
   2487 ##
   2488 ##   以下は配置計算の結果を保持します。
   2489 ##
   2490 ##   @arr _ble_textmap_pos[]
   2491 ##     各文字の表示位置を保持します。
   2492 ##   @arr _ble_textmap_glyph[]
   2493 ##     各文字の表現を保持します。
   2494 ##     例えば、制御文字は ^C や M-^C などと表されます。
   2495 ##     タブは表示開始位置に応じて異なる個数の空白で表現されます。
   2496 ##     行送りされた全角文字は前にパディングの空白が付加されます。
   2497 ##   @arr _ble_textmap_ichg[]
   2498 ##     タブや行送りなどによって標準的な表現と異なる文字
   2499 ##     のインデックスのリストです。
   2500 ##     標準的な表現は ble/highlight/layer:plain/update/.getch で規定されます。
   2501 ##   @var _ble_textmap_endx
   2502 ##   @var _ble_textmap_endy
   2503 ##     最後の文字の右端の座標を保持します。
   2504 ##
   2505 ##   以下は前回の配置計算以降の更新範囲を保持する変数です。
   2506 ##   部分更新をするために使用します。
   2507 ##
   2508 ##   @var _ble_textmap_dbeg
   2509 ##   @var _ble_textmap_dend
   2510 ##   @var _ble_textmap_dend0
   2511 ##
   2512 _ble_textmap_cols=80
   2513 _ble_textmap_length=
   2514 _ble_textmap_begx=0
   2515 _ble_textmap_begy=0
   2516 _ble_textmap_endx=0
   2517 _ble_textmap_endy=0
   2518 _ble_textmap_pos=()
   2519 _ble_textmap_glyph=()
   2520 _ble_textmap_ichg=()
   2521 _ble_textmap_dbeg=-1
   2522 _ble_textmap_dend=-1
   2523 _ble_textmap_dend0=-1
   2524 _ble_textmap_umin=-1
   2525 _ble_textmap_umax=-1
   2526 
   2527 function ble/textmap#update-dirty-range {
   2528   ble/dirty-range#update --prefix=_ble_textmap_d "$@"
   2529 }
   2530 function ble/textmap#save {
   2531   local name prefix=$1
   2532   ble/util/save-vars "$prefix" "${_ble_textmap_VARNAMES[@]}"
   2533 }
   2534 function ble/textmap#restore {
   2535   local name prefix=$1
   2536   ble/util/restore-vars "$prefix" "${_ble_textmap_VARNAMES[@]}"
   2537 }
   2538 
   2539 ## @fn ble/textmap#update/.wrap
   2540 ##   @var[in,out] cs x y changed
   2541 function ble/textmap#update/.wrap {
   2542   if [[ :$opts: == *:relative:* ]]; then
   2543     ((x)) && cs=$cs${_ble_term_cub//'%d'/$x}
   2544     cs=$cs${_ble_term_cud//'%d'/1}
   2545     changed=1
   2546   elif ((xenl)); then
   2547     # Note #D1745: 自動改行は CR で表現する事にする。この CR は実際の
   2548     # 出力時に LF または空文字列に置換する。
   2549     cs=$cs$_ble_term_cr
   2550     changed=1
   2551   fi
   2552   ((y++,x=0))
   2553 }
   2554 
   2555 ## @fn ble/textmap#update text [opts]
   2556 ##   @param[in]     text
   2557 ##   @param[in,opt] opts
   2558 ##   @var[in,out]   x y
   2559 ##   @var[in,out]   _ble_textmap_*
   2560 function ble/textmap#update {
   2561   local IFS=$_ble_term_IFS
   2562   local dbeg dend dend0
   2563   ((dbeg=_ble_textmap_dbeg,
   2564     dend=_ble_textmap_dend,
   2565     dend0=_ble_textmap_dend0))
   2566   ble/dirty-range#clear --prefix=_ble_textmap_d
   2567 
   2568   local text=$1 opts=$2
   2569   local iN=${#text}
   2570 
   2571   # 初期位置 x y
   2572   local pos0="$x $y"
   2573   _ble_textmap_begx=$x
   2574   _ble_textmap_begy=$y
   2575 
   2576   # ※現在は COLUMNS で決定しているが将来的には変更可能にする?
   2577   local cols=${COLUMNS-80} xenl=$_ble_term_xenl
   2578   ((COLUMNS&&cols<COLUMNS&&(xenl=1)))
   2579   ble/string#reserve-prototype "$cols"
   2580   # local cols=80 xenl=1
   2581 
   2582   local it=${bleopt_tab_width:-$_ble_term_it}
   2583   ble/string#reserve-prototype "$it"
   2584 
   2585   if ((cols!=_ble_textmap_cols)); then
   2586     # 表示幅が変化したときは全部再計算
   2587     ((dbeg=0,dend0=_ble_textmap_length,dend=iN))
   2588     _ble_textmap_pos[0]=$pos0
   2589   elif [[ ${_ble_textmap_pos[0]} != "$pos0" ]]; then
   2590     # 初期位置の変更がある場合は初めから計算し直し
   2591     ((dbeg<0&&(dend=dend0=0),
   2592       dbeg=0))
   2593     _ble_textmap_pos[0]=$pos0
   2594   else
   2595     if ((dbeg<0)); then
   2596       # 表示幅も初期位置も内容も変更がない場合はOK
   2597       local pos
   2598       ble/string#split-words pos "${_ble_textmap_pos[iN]}"
   2599       ((x=pos[0]))
   2600       ((y=pos[1]))
   2601       _ble_textmap_endx=$x
   2602       _ble_textmap_endy=$y
   2603       return 0
   2604     elif ((dbeg>0)); then
   2605       # 途中から計算を再開
   2606       local ret
   2607       ble/unicode/GraphemeCluster/find-previous-boundary "$text" "$dbeg"; dbeg=$ret
   2608       local pos
   2609       ble/string#split-words pos "${_ble_textmap_pos[dbeg]}"
   2610       ((x=pos[0]))
   2611       ((y=pos[1]))
   2612     fi
   2613   fi
   2614 
   2615   _ble_textmap_cols=$cols
   2616   _ble_textmap_length=$iN
   2617 
   2618 #%if !release
   2619   ble/util/assert '((dbeg<0||(dbeg<=dend&&dbeg<=dend0)))' "($dbeg $dend $dend0) <- ($_ble_textmap_dbeg $_ble_textmap_dend $_ble_textmap_dend0)"
   2620 #%end
   2621 
   2622   # shift cached data
   2623   ble/array#reserve-prototype "$iN"
   2624   local -a old_pos old_ichg
   2625   old_pos=("${_ble_textmap_pos[@]:dend0:iN-dend+1}")
   2626   old_ichg=("${_ble_textmap_ichg[@]}")
   2627   _ble_textmap_pos=(
   2628     "${_ble_textmap_pos[@]::dbeg+1}"
   2629     "${_ble_array_prototype[@]::dend-dbeg}"
   2630     "${_ble_textmap_pos[@]:dend0+1:iN-dend}")
   2631   _ble_textmap_glyph=(
   2632     "${_ble_textmap_glyph[@]::dbeg}"
   2633     "${_ble_array_prototype[@]::dend-dbeg}"
   2634     "${_ble_textmap_glyph[@]:dend0:iN-dend}")
   2635   _ble_textmap_ichg=()
   2636 
   2637   ble/urange#shift --prefix=_ble_textmap_ "$dbeg" "$dend" "$dend0"
   2638 
   2639   local i extend
   2640   for ((i=dbeg;i<iN;)); do
   2641     if ble/util/isprint+ "${text:i}"; then
   2642       local w=${#BASH_REMATCH}
   2643       local n
   2644       for ((n=i+w;i<n;i++)); do
   2645         local cs=${text:i:1}
   2646         if ((++x==cols)); then
   2647           local changed=0
   2648           ble/textmap#update/.wrap
   2649           ((changed)) && ble/array#push _ble_textmap_ichg "$i"
   2650         fi
   2651         _ble_textmap_glyph[i]=$cs
   2652         _ble_textmap_pos[i+1]="$x $y 0"
   2653       done
   2654       ble/unicode/GraphemeCluster/extend-ascii "$text" "$i"
   2655     else
   2656       local c w cs cb extend changed=0
   2657       ble/unicode/GraphemeCluster/match "$text" "$i"
   2658       if ((c<32)); then
   2659         if ((c==9)); then
   2660           if ((x+1>=cols)); then
   2661             cs=' ' w=0
   2662             ble/textmap#update/.wrap
   2663           else
   2664             local x2
   2665             ((x2=(x/it+1)*it,
   2666               x2>=cols&&(x2=cols-1),
   2667               w=x2-x,
   2668               w!=it&&(changed=1)))
   2669             cs=${_ble_string_prototype::w}
   2670           fi
   2671         elif ((c==10)); then
   2672           w=0
   2673           if [[ :$opts: == *:relative:* ]]; then
   2674             local pad=$((cols-x)) eraser=
   2675             if ((pad)); then
   2676               if [[ $_ble_term_ech ]]; then
   2677                 eraser=${_ble_term_ech//'%d'/$pad}
   2678               else
   2679                 eraser=${_ble_string_prototype::pad}
   2680                 ((x=cols))
   2681               fi
   2682             fi
   2683             local move=${_ble_term_cub//'%d'/$x}${_ble_term_cud//'%d'/1}
   2684             cs=$eraser$move
   2685             changed=1
   2686           else
   2687             cs=$_ble_term_el$_ble_term_nl
   2688           fi
   2689           ((y++,x=0))
   2690         fi
   2691       fi
   2692 
   2693       local wrapping=0
   2694       if ((w>0)); then
   2695         if ((x<cols&&cols<x+w)); then
   2696           if [[ :$opts: == *:relative:* ]]; then
   2697             cs=${_ble_term_cub//'%d'/$cols}${_ble_term_cud//'%d'/1}$cs
   2698           elif ((xenl)); then
   2699             # Note #D1745: 自動改行は CR で表現する事にする。この CR
   2700             # は実際の出力時に LF または空文字列に置換する。
   2701             cs=$_ble_term_cr$cs
   2702           fi
   2703           local pad=$((cols-x))
   2704           if ((pad)); then
   2705             if [[ $_ble_term_ech ]]; then
   2706               cs=${_ble_term_ech//'%d'/$pad}$cs
   2707             else
   2708               cs=${_ble_string_prototype::pad}$cs
   2709             fi
   2710           fi
   2711           ((x=cols,changed=1,wrapping=1))
   2712         fi
   2713 
   2714         ((x+=w))
   2715         while ((x>cols)); do
   2716           ((y++,x-=cols))
   2717         done
   2718         if ((x==cols)); then
   2719           ble/textmap#update/.wrap
   2720         fi
   2721       fi
   2722 
   2723       _ble_textmap_glyph[i]=$cs
   2724       ((changed)) && ble/array#push _ble_textmap_ichg "$i"
   2725       _ble_textmap_pos[i+1]="$x $y $wrapping"
   2726       ((i++))
   2727     fi
   2728     while ((extend--)); do
   2729       _ble_textmap_glyph[i]=
   2730       _ble_textmap_pos[++i]="$x $y 0"
   2731     done
   2732 
   2733     if ((i>=dend)); then
   2734       # 後は同じなので計算を省略
   2735       [[ ${old_pos[i-dend]} == "${_ble_textmap_pos[i]}" ]] && break
   2736 
   2737       # x 座標が同じならば、以降は最後まで y 座標だけずらす
   2738       if [[ ${old_pos[i-dend]%%[$IFS]*} == "${_ble_textmap_pos[i]%%[$IFS]*}" ]]; then
   2739         local -a opos npos pos
   2740         opos=(${old_pos[i-dend]})
   2741         npos=(${_ble_textmap_pos[i]})
   2742         local ydelta=$((npos[1]-opos[1]))
   2743         while ((i<iN)); do
   2744           ((i++))
   2745           pos=(${_ble_textmap_pos[i]})
   2746           ((pos[1]+=ydelta))
   2747           _ble_textmap_pos[i]="${pos[*]}"
   2748         done
   2749         pos=(${_ble_textmap_pos[iN]})
   2750         x=${pos[0]} y=${pos[1]}
   2751         break
   2752       fi
   2753     fi
   2754   done
   2755 
   2756   if ((i<iN)); then
   2757     # 途中で一致して中断した場合は、前の iN 番目の位置を読む
   2758     local -a pos
   2759     pos=(${_ble_textmap_pos[iN]})
   2760     x=${pos[0]} y=${pos[1]}
   2761   fi
   2762 
   2763   # 前回までの文字修正位置を shift&add
   2764   local j jN ichg
   2765   for ((j=0,jN=${#old_ichg[@]};j<jN;j++)); do
   2766     if ((ichg=old_ichg[j],
   2767          (ichg>=dend0)&&(ichg+=dend-dend0),
   2768          (0<=ichg&&ichg<dbeg||dend<=i&&ichg<iN)))
   2769     then
   2770       ble/array#push _ble_textmap_ichg "$ichg"
   2771     fi
   2772   done
   2773 
   2774   ((dbeg<i)) && ble/urange#update --prefix=_ble_textmap_ "$dbeg" "$i"
   2775 
   2776   _ble_textmap_endx=$x
   2777   _ble_textmap_endy=$y
   2778 }
   2779 
   2780 function ble/textmap#is-up-to-date {
   2781   ((_ble_textmap_dbeg==-1))
   2782 }
   2783 ## @fn ble/textmap#assert-up-to-date
   2784 ##   編集文字列の文字の配置情報が最新であることを確認します。
   2785 ##   以下の変数を参照する場合に事前に呼び出します。
   2786 ##
   2787 ##   _ble_textmap_pos
   2788 ##   _ble_textmap_length
   2789 ##
   2790 function ble/textmap#assert-up-to-date {
   2791   ble/util/assert 'ble/textmap#is-up-to-date' 'dirty text positions'
   2792 }
   2793 
   2794 ## @fn ble/textmap#getxy.out index
   2795 ##   index 番目の文字の出力開始位置を取得します。
   2796 ##
   2797 ##   @var[out] x y
   2798 ##
   2799 ##   行末に収まらない文字の場合は行末のスペースを埋める為に
   2800 ##   配列 _ble_textmap_glyph において空白文字が文字本体の前に追加されます。
   2801 ##   その場合には、追加される空白文字の前の位置を返すことに注意して下さい。
   2802 ##   実用上は境界 index の左側の文字の終端位置と解釈できます。
   2803 ##
   2804 function ble/textmap#getxy.out {
   2805   ble/textmap#assert-up-to-date
   2806   local _ble_local_prefix=
   2807   if [[ $1 == --prefix=* ]]; then
   2808     _ble_local_prefix=${1#--prefix=}
   2809     shift
   2810   fi
   2811 
   2812   local -a pos
   2813   ble/string#split-words pos "${_ble_textmap_pos[$1]}"
   2814   ((${_ble_local_prefix}x=pos[0]))
   2815   ((${_ble_local_prefix}y=pos[1]))
   2816 }
   2817 
   2818 ## @fn ble/textmap#getxy.cur index
   2819 ##   index 番目の文字の表示開始位置を取得します。
   2820 ##
   2821 ##   @var[out] x y
   2822 ##
   2823 ##   ble/textmap#getxy.out の異なり前置される空白は考えずに、
   2824 ##   文字本体が開始する位置を取得します。
   2825 ##   実用上は境界 index の右側の文字の開始位置と解釈できます。
   2826 ##
   2827 function ble/textmap#getxy.cur {
   2828   ble/textmap#assert-up-to-date
   2829   local _ble_local_prefix=
   2830   if [[ $1 == --prefix=* ]]; then
   2831     _ble_local_prefix=${1#--prefix=}
   2832     shift
   2833   fi
   2834 
   2835   local -a pos
   2836   ble/string#split-words pos "${_ble_textmap_pos[$1]}"
   2837 
   2838   # 追い出しされたか check
   2839   if (($1<_ble_textmap_length)); then
   2840     local -a eoc
   2841     ble/string#split-words eoc "${_ble_textmap_pos[$1+1]}"
   2842     ((eoc[2])) && ((pos[0]=0,pos[1]++))
   2843   fi
   2844 
   2845   ((${_ble_local_prefix}x=pos[0]))
   2846   ((${_ble_local_prefix}y=pos[1]))
   2847 }
   2848 
   2849 ## @fn ble/textmap#get-index-at [-v varname] x y
   2850 ##   指定した位置 x y に対応する index を求めます。
   2851 function ble/textmap#get-index-at {
   2852   ble/textmap#assert-up-to-date
   2853   local __ble_var=index
   2854   if [[ $1 == -v ]]; then
   2855     __ble_var=$2
   2856     shift 2
   2857   fi
   2858 
   2859   local __ble_x=$1 __ble_y=$2
   2860   if ((__ble_y>_ble_textmap_endy)); then
   2861     (($__ble_var=_ble_textmap_length))
   2862   elif ((__ble_y<_ble_textmap_begy)); then
   2863     (($__ble_var=0))
   2864   else
   2865     # 2分法
   2866     local __ble_l=0 __ble_u=$((_ble_textmap_length+1))
   2867     local m mx my
   2868     while ((__ble_l+1<__ble_u)); do
   2869       ble/textmap#getxy.cur --prefix=m "$((m=(__ble_l+__ble_u)/2))"
   2870       (((__ble_y<my||__ble_y==my&&__ble_x<mx)?(__ble_u=m):(__ble_l=m)))
   2871     done
   2872     ble/util/unlocal m mx my
   2873     (($__ble_var=__ble_l))
   2874   fi
   2875 }
   2876 
   2877 ## @fn ble/textmap#hit/.getxy.out index
   2878 ## @fn ble/textmap#hit/.getxy.cur index
   2879 ##   @var[in,out] pos
   2880 function ble/textmap#hit/.getxy.out {
   2881   local a
   2882   ble/string#split-words a "${_ble_textmap_pos[$1]}"
   2883   x=${a[0]} y=${a[1]}
   2884 }
   2885 function ble/textmap#hit/.getxy.cur {
   2886   local index=$1 a
   2887   ble/string#split-words a "${_ble_textmap_pos[index]}"
   2888   x=${a[0]} y=${a[1]}
   2889   if ((index<_ble_textmap_length)); then
   2890     ble/string#split-words a "${_ble_textmap_pos[index+1]}"
   2891     ((a[2])) && ((x=0,y++))
   2892   fi
   2893 }
   2894 
   2895 ## @fn ble/textmap#hit type xh yh [beg [end]]
   2896 ##   指定した座標に対応する境界 index を取得します。
   2897 ##   指定した座標以前の最も近い境界を求めます。
   2898 ##   探索範囲に対応する境界がないときは最初の境界 beg を返します。
   2899 ##
   2900 ##   @param[in] type
   2901 ##     探索する点の種類を指定します。out または cur を指定します。
   2902 ##     out を指定したときは文字終端境界を探索します。
   2903 ##     cur を指定したときは文字開始境界(行送りを考慮に入れたもの)を探索します。
   2904 ##   @param[in] xh yh
   2905 ##     探索する点を指定します。
   2906 ##   @param[in] beg end
   2907 ##     探索する index の範囲を指定します。
   2908 ##     beg を省略したときは最初の境界位置が使用されます。
   2909 ##     end を省略したときは最後の境界位置が使用されます。
   2910 ##
   2911 ##   @var[out] index
   2912 ##     見つかった境界の番号を返します。
   2913 ##   @var[out] lx ly
   2914 ##     見つかった境界の座標を返します。
   2915 ##   @var[out] rx ry
   2916 ##     指定した座標以後の最も近い境界を返します。
   2917 ##     index が探索範囲の最後の境界のとき、または、
   2918 ##     lx ly が指定した座標と一致するとき lx ly と同一です。
   2919 ##
   2920 function ble/textmap#hit {
   2921   ble/textmap#assert-up-to-date
   2922   local getxy=ble/textmap#hit/.getxy.$1
   2923   local xh=$2 yh=$3 beg=${4:-0} end=${5:-$_ble_textmap_length}
   2924 
   2925   local -a pos
   2926   if "$getxy" "$end"; ((yh>y||yh==y&&xh>x)); then
   2927     index=$end
   2928     lx=$x ly=$y
   2929     rx=$x ry=$y
   2930   elif "$getxy" "$beg"; ((yh<y||yh==y&&xh<x)); then
   2931     index=$beg
   2932     lx=$x ly=$y
   2933     rx=$x ry=$y
   2934   else
   2935     # 2分法
   2936     local l=0 u=$((end+1)) m
   2937     while ((l+1<u)); do
   2938       "$getxy" "$((m=(l+u)/2))"
   2939       (((yh<y||yh==y&&xh<x)?(u=m):(l=m)))
   2940     done
   2941     "$getxy" "$((index=l))"
   2942     lx=$x ly=$y
   2943     (((ly<yh||ly==yh&&lx<xh)&&index<end)) && "$getxy" "$((index+1))"
   2944     rx=$x ry=$y
   2945   fi
   2946 }
   2947 
   2948 #------------------------------------------------------------------------------
   2949 # ble/canvas/goto.draw
   2950 
   2951 ## @var _ble_canvas_x
   2952 ## @var _ble_canvas_y
   2953 ##   現在の (描画の為に動き回る) カーソル位置を保持します。
   2954 _ble_canvas_x=0
   2955 _ble_canvas_y=0
   2956 _ble_canvas_excursion=
   2957 
   2958 ## @fn ble/canvas/goto.draw x y opts
   2959 ##   現在位置を指定した座標へ移動する制御系列を生成します。
   2960 ##   @param[in] x y
   2961 ##     移動先のカーソルの座標を指定します。
   2962 ##     プロンプト原点が x=0 y=0 に対応します。
   2963 function ble/canvas/goto.draw {
   2964   local x=$1 y=$2 opts=$3
   2965 
   2966   # Note #D1392: mc (midnight commander) は
   2967   #   sgr0 単体でもプロンプトと勘違いするので、
   2968   #   プロンプト更新もカーソル移動も不要の時は、
   2969   #   sgr0 も含めて何も出力しない。
   2970   [[ :$opts: != *:sgr0:* ]] &&
   2971     ((x==_ble_canvas_x&&y==_ble_canvas_y)) && return 0
   2972 
   2973   ble/canvas/put.draw "$_ble_term_sgr0"
   2974 
   2975   ble/canvas/put-move-y.draw "$((y-_ble_canvas_y))"
   2976 
   2977   local dx=$((x-_ble_canvas_x))
   2978   if ((dx!=0)); then
   2979     if ((x==0)); then
   2980       ble/canvas/put.draw "$_ble_term_cr"
   2981     else
   2982       ble/canvas/put-move-x.draw "$dx"
   2983     fi
   2984   fi
   2985 
   2986   _ble_canvas_x=$x _ble_canvas_y=$y
   2987 }
   2988 
   2989 _ble_canvas_excursion_x=
   2990 _ble_canvas_excursion_y=
   2991 function ble/canvas/excursion-start.draw {
   2992   [[ $_ble_canvas_excursion ]] && return 1
   2993   _ble_canvas_excursion=1
   2994   _ble_canvas_excursion_x=$_ble_canvas_x
   2995   _ble_canvas_excursion_y=$_ble_canvas_y
   2996   ble/canvas/put.draw "$_ble_term_sc"
   2997 }
   2998 function ble/canvas/excursion-end.draw {
   2999   [[ $_ble_canvas_excursion ]] || return 1
   3000   _ble_canvas_excursion=
   3001   ble/canvas/put.draw "$_ble_term_rc"
   3002   _ble_canvas_x=$_ble_canvas_excursion_x
   3003   _ble_canvas_y=$_ble_canvas_excursion_y
   3004 }
   3005 
   3006 #------------------------------------------------------------------------------
   3007 # ble/canvas/panel
   3008 
   3009 ## @arr _ble_canvas_panel_class
   3010 ##   各パネルを管理する関数接頭辞を保持する。
   3011 ##
   3012 ## @arr _ble_canvas_panel_height
   3013 ##   各パネルの高さを保持する。
   3014 ##   現在 panel 0 が textarea で panel 2 が info に対応する。
   3015 ##
   3016 ##   開始した瞬間にキー入力をすると画面に echo されてしまうので、
   3017 ##   それを削除するために最初の編集文字列の行数を 1 とする。
   3018 ##
   3019 ## @var _ble_canvas_panel_focus
   3020 ##   現在 focus のあるパネルの番号を保持する。
   3021 ##   端末の現在位置はこのパネルの render が設定した位置に置かれる。
   3022 ##
   3023 ## @var _ble_canvas_panel_vfill
   3024 ##   下部に寄せて表示されるパネルの開始番号を保持する。
   3025 ##   この変数が空文字列の時は全てのパネルは上部に表示される。
   3026 _ble_canvas_panel_class=()
   3027 _ble_canvas_panel_height=(1 0 0)
   3028 _ble_canvas_panel_focus=
   3029 _ble_canvas_panel_vfill=
   3030 _ble_canvas_panel_bottom= # 現在下部に居るかどうか
   3031 _ble_canvas_panel_tmargin='LINES!=1?1:0' # for visible-bell
   3032 
   3033 ## @fn ble/canvas/panel/layout/.extract-heights
   3034 ##   @arr[out] mins maxs
   3035 function ble/canvas/panel/layout/.extract-heights {
   3036   local i n=${#_ble_canvas_panel_class[@]}
   3037   for ((i=0;i<n;i++)); do
   3038     local height=0:0
   3039     ble/function#try "${_ble_canvas_panel_class[i]}#panel::getHeight" "$i"
   3040     mins[i]=${height%:*}
   3041     maxs[i]=${height#*:}
   3042   done
   3043 }
   3044 
   3045 ## @fn ble/canvas/panel/layout/.determine-heights
   3046 ##   最小高さ mins と希望高さ maxs から実際の高さ heights を決定します。
   3047 ##   @var[in] lines
   3048 ##   @arr[in] mins maxs
   3049 ##   @arr[out] heights
   3050 function ble/canvas/panel/layout/.determine-heights {
   3051   local i n=${#_ble_canvas_panel_class[@]} ret
   3052   ble/arithmetic/sum "${mins[@]}"; local min=$ret
   3053   ble/arithmetic/sum "${maxs[@]}"; local max=$ret
   3054   if ((max<=lines)); then
   3055     heights=("${maxs[@]}")
   3056   elif ((min<=lines)); then
   3057     local room=$((lines-min))
   3058     heights=("${mins[@]}")
   3059     while ((room)); do
   3060       local count=0 min_delta=-1 delta
   3061       for ((i=0;i<n;i++)); do
   3062         ((delta=maxs[i]-heights[i],delta>0)) || continue
   3063         ((count++))
   3064         ((min_delta<0||min_delta>delta)) && min_delta=$delta
   3065       done
   3066       ((count==0)) && break
   3067 
   3068       if ((count*min_delta<=room)); then
   3069         for ((i=0;i<n;i++)); do
   3070           ((maxs[i]-heights[i]>0)) || continue
   3071           ((heights[i]+=min_delta))
   3072         done
   3073         ((room-=count*min_delta))
   3074       else
   3075         local delta=$((room/count)) rem=$((room%count)) count=0
   3076         for ((i=0;i<n;i++)); do
   3077           ((maxs[i]-heights[i]>0)) || continue
   3078           ((heights[i]+=delta))
   3079           ((count++<rem)) && ((heights[i]++))
   3080         done
   3081         ((room=0))
   3082       fi
   3083     done
   3084   else
   3085     heights=("${mins[@]}")
   3086     local excess=$((min-lines))
   3087     for ((i=n-1;i>=0;i--)); do
   3088       local sub=$((heights[i]-heights[i]*lines/min))
   3089       if ((sub<excess)); then
   3090         ((excess-=sub))
   3091         ((heights[i]-=sub))
   3092       else
   3093         ((heights[i]-=excess))
   3094         break
   3095       fi
   3096     done
   3097   fi
   3098 }
   3099 
   3100 ## @fn ble/canvas/panel/layout/.get-available-height index
   3101 ##   @var[out] ret
   3102 function ble/canvas/panel/layout/.get-available-height {
   3103   local index=$1
   3104   local lines=$((${LINES:-25}-_ble_canvas_panel_tmargin))
   3105   local -a mins=() maxs=()
   3106   ble/canvas/panel/layout/.extract-heights
   3107   maxs[index]=${LINES:-25}
   3108   local -a heights=()
   3109   ble/canvas/panel/layout/.determine-heights
   3110   ret=${heights[index]}
   3111 }
   3112 
   3113 function ble/canvas/panel/reallocate-height.draw {
   3114   local lines=$((${LINES:-25}-_ble_canvas_panel_tmargin))
   3115 
   3116   local i n=${#_ble_canvas_panel_class[@]}
   3117   local -a mins=() maxs=()
   3118   ble/canvas/panel/layout/.extract-heights
   3119 
   3120   local -a heights=()
   3121   ble/canvas/panel/layout/.determine-heights
   3122 
   3123   # shrink
   3124   for ((i=0;i<n;i++)); do
   3125     ((heights[i]<_ble_canvas_panel_height[i])) &&
   3126       ble/canvas/panel#set-height.draw "$i" "${heights[i]}"
   3127   done
   3128 
   3129   # expand
   3130   for ((i=0;i<n;i++)); do
   3131     ((heights[i]>_ble_canvas_panel_height[i])) &&
   3132       ble/canvas/panel#set-height.draw "$i" "${heights[i]}"
   3133   done
   3134 }
   3135 function ble/canvas/panel/is-last-line {
   3136   local ret
   3137   ble/arithmetic/sum "${_ble_canvas_panel_height[@]}"
   3138   ((_ble_canvas_y==ret-1))
   3139 }
   3140 
   3141 function ble/canvas/panel/goto-bottom-dock.draw {
   3142   if [[ ! $_ble_canvas_panel_bottom ]]; then
   3143     _ble_canvas_panel_bottom=1
   3144     ble/canvas/excursion-start.draw
   3145     ble/canvas/put-cup.draw "$LINES" 0 # 一番下の行に移動
   3146     ble/arithmetic/sum "${_ble_canvas_panel_height[@]}"
   3147     ((_ble_canvas_x=0,_ble_canvas_y=ret-1))
   3148   fi
   3149 }
   3150 function ble/canvas/panel/goto-top-dock.draw {
   3151   if [[ $_ble_canvas_panel_bottom ]]; then
   3152     _ble_canvas_panel_bottom=
   3153     ble/canvas/excursion-end.draw
   3154   fi
   3155 }
   3156 function ble/canvas/panel/goto-vfill.draw {
   3157   ble/canvas/panel/has-bottom-dock || return 1
   3158   local ret
   3159   ble/canvas/panel/goto-top-dock.draw
   3160   ble/arithmetic/sum "${_ble_canvas_panel_height[@]::_ble_canvas_panel_vfill}"
   3161   ble/canvas/goto.draw 0 "$ret" sgr0
   3162   return 0
   3163 }
   3164 ## @fn ble/canvas/panel/save-position opts
   3165 ##   @var[out] ret
   3166 function ble/canvas/panel/save-position {
   3167   ret=$_ble_canvas_x:$_ble_canvas_y:$_ble_canvas_panel_bottom
   3168   [[ :$2: == *:goto-top-dock:* ]] &&
   3169     ble/canvas/panel/goto-top-dock.draw
   3170 }
   3171 ## @fn ble/canvas/panel/load-position x:y:bottom
   3172 ##   ble/canvas/panel/save-position で記録した情報を元に
   3173 ##   元の位置に戻ります。
   3174 function ble/canvas/panel/load-position {
   3175   local -a DRAW_BUFF=()
   3176   ble/canvas/panel/load-position.draw "$@"
   3177   ble/canvas/bflush.draw
   3178 }
   3179 function ble/canvas/panel/load-position.draw {
   3180   local data=$1
   3181   local x=${data%%:*}; data=${data#*:}
   3182   local y=${data%%:*}; data=${data#*:}
   3183   local bottom=$data
   3184   if [[ $bottom ]]; then
   3185     ble/canvas/panel/goto-bottom-dock.draw
   3186   else
   3187     ble/canvas/panel/goto-top-dock.draw
   3188   fi
   3189   ble/canvas/goto.draw "$x" "$y"
   3190 }
   3191 
   3192 function ble/canvas/panel/has-bottom-dock {
   3193   local ret; ble/canvas/panel/bottom-dock#height
   3194   ((ret))
   3195 }
   3196 function ble/canvas/panel/bottom-dock#height {
   3197   ret=0
   3198   [[ $_ble_canvas_panel_vfill && $_ble_term_rc ]] || return 0
   3199   ble/arithmetic/sum "${_ble_canvas_panel_height[@]:_ble_canvas_panel_vfill}"
   3200 }
   3201 function ble/canvas/panel/top-dock#height {
   3202   if [[ $_ble_canvas_panel_vfill && $_ble_term_rc ]]; then
   3203     ble/arithmetic/sum "${_ble_canvas_panel_height[@]::_ble_canvas_panel_vfill}"
   3204   else
   3205     ble/arithmetic/sum "${_ble_canvas_panel_height[@]}"
   3206   fi
   3207 }
   3208 ## @fn ble/canvas/panel/bottom-dock#invalidate
   3209 ##   Invalidate all bottom panels (with non-zero height)
   3210 function ble/canvas/panel/bottom-dock#invalidate {
   3211   [[ $_ble_canvas_panel_vfill && $_ble_term_rc ]] || return 0
   3212   local index n=${#_ble_canvas_panel_class[@]}
   3213   for ((index=_ble_canvas_panel_vfill;index<n;index++)); do
   3214     local panel_class=${_ble_canvas_panel_class[index]}
   3215     local panel_height=${_ble_canvas_panel_height[index]}
   3216     ((panel_height)) &&
   3217       ble/function#try "$panel_class#panel::invalidate" "$index" 0 "$panel_height"
   3218   done
   3219 }
   3220 function ble/canvas/panel#is-bottom {
   3221   [[ $_ble_canvas_panel_vfill && $_ble_term_rc ]] && (($1>=_ble_canvas_panel_vfill))
   3222 }
   3223 
   3224 ## @fn ble/canvas/panel#get-origin
   3225 ##   @var[out] x y
   3226 function ble/canvas/panel#get-origin {
   3227   local ret index=$1 prefix=
   3228   [[ $2 == --prefix=* ]] && prefix=${2#*=}
   3229   ble/arithmetic/sum "${_ble_canvas_panel_height[@]::index}"
   3230   ((${prefix}x=0,${prefix}y=ret))
   3231 }
   3232 function ble/canvas/panel#goto.draw {
   3233   local index=$1 x=${2-0} y=${3-0} opts=$4 ret
   3234   if ble/canvas/panel#is-bottom "$index"; then
   3235     ble/canvas/panel/goto-bottom-dock.draw
   3236   else
   3237     ble/canvas/panel/goto-top-dock.draw
   3238   fi
   3239   ble/arithmetic/sum "${_ble_canvas_panel_height[@]::index}"
   3240   ble/canvas/goto.draw "$x" "$((ret+y))" "$opts"
   3241 }
   3242 ## @fn ble/canvas/panel#put.draw panel text x y
   3243 function ble/canvas/panel#put.draw {
   3244   ble/canvas/put.draw "$2"
   3245   ble/canvas/panel#report-cursor-position "$1" "$3" "$4"
   3246 }
   3247 function ble/canvas/panel#report-cursor-position {
   3248   local index=$1 x=${2-0} y=${3-0} ret
   3249   ble/arithmetic/sum "${_ble_canvas_panel_height[@]::index}"
   3250   ((_ble_canvas_x=x,_ble_canvas_y=ret+y))
   3251 }
   3252 
   3253 function ble/canvas/panel/increase-total-height.draw {
   3254   local delta=$1
   3255   ((delta>0)) || return 1
   3256 
   3257   local ret
   3258   ble/canvas/panel/top-dock#height; local top_height=$ret
   3259   ble/canvas/panel/bottom-dock#height; local bottom_height=$ret
   3260   if ((bottom_height)); then
   3261     ble/canvas/panel/goto-top-dock.draw
   3262     if [[ $_ble_term_DECSTBM ]]; then
   3263       ble/canvas/excursion-start.draw
   3264       ble/canvas/put.draw $'\e[1;'$((LINES-bottom_height))'r'
   3265       ble/canvas/excursion-end.draw
   3266       ble/canvas/goto.draw 0 "$((top_height==0?0:top_height-1))" sgr0
   3267       ble/canvas/put-ind.draw "$((top_height-1+delta-_ble_canvas_y))"
   3268       ((_ble_canvas_y=top_height-1+delta))
   3269       ble/canvas/excursion-start.draw
   3270       ble/canvas/put.draw "$_ble_term_DECSTBM_reset"
   3271       ble/canvas/excursion-end.draw
   3272       return 0
   3273     else
   3274       ble/canvas/panel/bottom-dock#invalidate
   3275     fi
   3276   fi
   3277 
   3278   local old_height=$((top_height+bottom_height))
   3279   local new_height=$((old_height+delta))
   3280   ble/canvas/goto.draw 0 "$((top_height==0?0:top_height-1))" sgr0
   3281   ble/canvas/put-ind.draw "$((new_height-1-_ble_canvas_y))"; ((_ble_canvas_y=new_height-1))
   3282   ble/canvas/panel/goto-vfill.draw &&
   3283     ble/canvas/put-il.draw "$delta" vfill
   3284 }
   3285 
   3286 ## @fn ble/canvas/panel#set-height.draw panel height opts
   3287 ##   @param[in] opts
   3288 ##     shift ... 範囲の先頭で行を追加・削除します。
   3289 function ble/canvas/panel#set-height.draw {
   3290   local index=$1 new_height=$2 opts=$3
   3291   ((new_height<0)) && new_height=0
   3292   local old_height=${_ble_canvas_panel_height[index]}
   3293   local delta=$((new_height-old_height))
   3294 
   3295   if ((delta==0)); then
   3296     if [[ :$opts: == *:clear:* ]]; then
   3297       ble/canvas/panel#clear.draw "$index"
   3298       return "$?"
   3299     else
   3300       return 1
   3301     fi
   3302   elif ((delta>0)); then
   3303     # 新しく行を挿入
   3304     ble/canvas/panel/increase-total-height.draw "$delta"
   3305     ble/canvas/panel/goto-vfill.draw &&
   3306       ble/canvas/put-dl.draw "$delta" vfill
   3307     ((_ble_canvas_panel_height[index]=new_height))
   3308 
   3309     case :$opts: in
   3310     (*:clear:*)
   3311       ble/canvas/panel#goto.draw "$index" 0 0 sgr0
   3312       ble/canvas/put-clear-lines.draw "$old_height" "$new_height" panel ;;
   3313     (*:shift:*) # 先頭に行挿入
   3314       ble/canvas/panel#goto.draw "$index" 0 0 sgr0
   3315       ble/canvas/put-il.draw "$delta" panel ;;
   3316     (*) # 末尾に行挿入
   3317       ble/canvas/panel#goto.draw "$index" 0 "$old_height" sgr0
   3318       ble/canvas/put-il.draw "$delta" panel ;;
   3319     esac
   3320 
   3321   else
   3322     ((delta=-delta))
   3323 
   3324     case :$opts: in
   3325     (*:clear:*)
   3326       ble/canvas/panel#goto.draw "$index" 0 0 sgr0
   3327       ble/canvas/put-clear-lines.draw "$old_height" "$new_height" panel ;;
   3328     (*:shift:*) # 先頭を削除
   3329       ble/canvas/panel#goto.draw "$index" 0 0 sgr0
   3330       ble/canvas/put-dl.draw "$delta" panel ;;
   3331     (*) # 末尾を削除
   3332       ble/canvas/panel#goto.draw "$index" 0 "$new_height" sgr0
   3333       ble/canvas/put-dl.draw "$delta" panel ;;
   3334     esac
   3335 
   3336     ((_ble_canvas_panel_height[index]=new_height))
   3337     ble/canvas/panel/goto-vfill.draw &&
   3338       ble/canvas/put-il.draw "$delta" vfill
   3339   fi
   3340   ble/function#try "${_ble_canvas_panel_class[index]}#panel::onHeightChange" "$index"
   3341 
   3342   return 0
   3343 }
   3344 function ble/canvas/panel#increase-height.draw {
   3345   local index=$1 delta=$2 opts=$3
   3346   ble/canvas/panel#set-height.draw "$index" "$((_ble_canvas_panel_height[index]+delta))" "$opts"
   3347 }
   3348 
   3349 function ble/canvas/panel#set-height-and-clear.draw {
   3350   local index=$1 new_height=$2
   3351   ble/canvas/panel#set-height.draw "$index" "$new_height" clear
   3352 }
   3353 
   3354 function ble/canvas/panel#clear.draw {
   3355   local index=$1
   3356   local height=${_ble_canvas_panel_height[index]}
   3357   if ((height)); then
   3358     ble/canvas/panel#goto.draw "$index" 0 0 sgr0
   3359     ble/canvas/put-clear-lines.draw "$height"
   3360   fi
   3361 }
   3362 function ble/canvas/panel#clear-after.draw {
   3363   local index=$1 x=$2 y=$3
   3364   local height=${_ble_canvas_panel_height[index]}
   3365   ((y<height)) || return 1
   3366 
   3367   ble/canvas/panel#goto.draw "$index" "$x" "$y" sgr0
   3368   ble/canvas/put.draw "$_ble_term_el"
   3369   local rest_lines=$((height-(y+1)))
   3370   if ((rest_lines)); then
   3371     ble/canvas/put.draw "$_ble_term_ind"
   3372     [[ $_ble_term_ind != $'\eD' ]] &&
   3373       ble/canvas/put-hpa.draw "$((x+1))"
   3374     ble/canvas/put-clear-lines.draw "$rest_lines"
   3375     ble/canvas/put-cuu.draw 1
   3376   fi
   3377 }
   3378 
   3379 ## @fn ble/canvas/panel/invalidate
   3380 ##   Invalidate all panels (with non-zero height)
   3381 function ble/canvas/panel/clear {
   3382   local -a DRAW_BUFF=()
   3383   local index n=${#_ble_canvas_panel_class[@]}
   3384   for ((index=0;index<n;index++)); do
   3385     local panel_class=${_ble_canvas_panel_class[index]}
   3386     local panel_height=${_ble_canvas_panel_height[index]}
   3387     ((panel_height)) || continue
   3388     ble/canvas/panel#clear.draw "$index"
   3389     ble/function#try "$panel_class#panel::invalidate" "$index" 0 "$panel_height"
   3390   done
   3391   ble/canvas/bflush.draw
   3392 }
   3393 function ble/canvas/panel/invalidate {
   3394   local opts=$1
   3395   if [[ :$opts: == *:height:* ]]; then
   3396     local -a DRAW_BUFF=()
   3397     ble/canvas/excursion-end.draw
   3398     ble/canvas/put.draw "$_ble_term_cr$_ble_term_ed"
   3399     _ble_canvas_x=0 _ble_canvas_y=0
   3400     ble/array#fill-range _ble_canvas_panel_height 0 "${#_ble_canvas_panel_height[@]}" 0
   3401     ble/canvas/panel/reallocate-height.draw
   3402     ble/canvas/bflush.draw
   3403   fi
   3404 
   3405   local index n=${#_ble_canvas_panel_class[@]}
   3406   for ((index=0;index<n;index++)); do
   3407     local panel_class=${_ble_canvas_panel_class[index]}
   3408     local panel_height=${_ble_canvas_panel_height[index]}
   3409     ((panel_height)) || continue
   3410     ble/function#try "$panel_class#panel::invalidate" "$index" 0 "$panel_height"
   3411   done
   3412 }
   3413 function ble/canvas/panel/render {
   3414   local index n=${#_ble_canvas_panel_class[@]} pos=
   3415   for ((index=0;index<n;index++)); do
   3416     local panel_class=${_ble_canvas_panel_class[index]}
   3417     local panel_height=${_ble_canvas_panel_height[index]}
   3418     # Note: panel::render の中で高さを更新するので panel_height==0 で
   3419     # あっても panel::render を呼び出す。
   3420     ble/function#try "$panel_class#panel::render" "$index" 0 "$panel_height"
   3421     if [[ $_ble_canvas_panel_focus ]] && ((index==_ble_canvas_panel_focus)); then
   3422       local ret; ble/canvas/panel/save-position; local pos=$ret
   3423     fi
   3424   done
   3425   [[ $pos ]] && ble/canvas/panel/load-position "$pos"
   3426   return 0
   3427 }
   3428 ## @fn ble/canvas/panel/ensure-terminal-top-line
   3429 ##   visible-bell で使う為
   3430 function ble/canvas/panel/ensure-tmargin.draw {
   3431   local tmargin=$((_ble_canvas_panel_tmargin))
   3432   ((tmargin>LINES)) && tmargin=$LINES
   3433   ((tmargin>0)) || return 0
   3434 
   3435   local ret
   3436   ble/canvas/panel/save-position; local pos=$ret
   3437   ble/canvas/panel/goto-top-dock.draw
   3438 
   3439   ble/canvas/panel/top-dock#height; local top_height=$ret
   3440   ble/canvas/panel/bottom-dock#height; local bottom_height=$ret
   3441   if ((bottom_height)); then
   3442     if [[ $_ble_term_DECSTBM ]]; then
   3443       ble/canvas/excursion-start.draw
   3444       ble/canvas/put.draw $'\e[1;'$((LINES-bottom_height))'r'
   3445       ble/canvas/excursion-end.draw
   3446       ble/canvas/goto.draw 0 0 sgr0
   3447       if [[ $_ble_term_ri ]]; then
   3448         ble/canvas/put-ri.draw "$tmargin"
   3449         ble/canvas/put-cud.draw "$tmargin"
   3450       else
   3451         # RI がない時
   3452         ble/canvas/put-ind.draw "$((top_height-1+tmargin))"
   3453         ble/canvas/put-cuu.draw "$((top_height-1+tmargin))"
   3454         ble/canvas/excursion-start.draw
   3455         ble/canvas/put-cup.draw 1 1
   3456         ble/canvas/put-il.draw "$tmargin" no-lastline
   3457         ble/canvas/excursion-end.draw
   3458       fi
   3459       ble/canvas/excursion-start.draw
   3460       ble/canvas/put.draw "$_ble_term_DECSTBM_reset"
   3461       ble/canvas/excursion-end.draw
   3462       ble/canvas/panel/load-position.draw "$pos"
   3463       return 0
   3464     else
   3465       ble/canvas/panel/bottom-dock#invalidate
   3466     fi
   3467   fi
   3468 
   3469   ble/canvas/goto.draw 0 0 sgr0
   3470   if [[ $_ble_term_ri ]]; then
   3471     ble/canvas/put-ri.draw "$tmargin"
   3472     ble/canvas/put-cud.draw "$tmargin"
   3473   else
   3474     # RI がない時
   3475     local total_height=$((top_height+bottom_height))
   3476     ble/canvas/put-ind.draw "$((total_height-1+tmargin))"
   3477     ble/canvas/put-cuu.draw "$((total_height-1+tmargin))"
   3478     if [[ $_ble_term_rc ]]; then
   3479       ble/canvas/excursion-start.draw
   3480       ble/canvas/put-cup.draw 1 1
   3481       ble/canvas/put-il.draw "$tmargin" no-lastline
   3482       ble/canvas/excursion-end.draw
   3483     else
   3484       ble/canvas/put-il.draw "$tmargin" no-lastline
   3485     fi
   3486     ble/canvas/put-cud.draw "$tmargin"
   3487   fi
   3488   ble/canvas/panel/load-position.draw "$pos"
   3489 }