From 7fd49bb2e28bdde58122720b735c482e541b645a Mon Sep 17 00:00:00 2001 From: Julian Noble Date: Fri, 24 May 2024 06:44:19 +1000 Subject: [PATCH] ansi colour,textblock::table,textblock::frame, and repl fixes --- .../{oolib-0.1.1.tm => oolib-0.1.2.tm} | 67 +- src/modules/punk/ansi-999999.0a1.0.tm | 412 ++++++- src/modules/punk/basictelnet-999999.0a1.0.tm | 4 +- src/modules/punk/repl-0.1.tm | 50 +- src/modules/textblock-999999.0a1.0.tm | 1083 ++++++++++------- 5 files changed, 1107 insertions(+), 509 deletions(-) rename src/modules/{oolib-0.1.1.tm => oolib-0.1.2.tm} (80%) diff --git a/src/modules/oolib-0.1.1.tm b/src/modules/oolib-0.1.2.tm similarity index 80% rename from src/modules/oolib-0.1.1.tm rename to src/modules/oolib-0.1.2.tm index ecf2cca..af5da52 100644 --- a/src/modules/oolib-0.1.1.tm +++ b/src/modules/oolib-0.1.2.tm @@ -2,13 +2,13 @@ # package provide oolib [namespace eval oolib { variable version - set version 0.1.1 + set version 0.1.2 }] namespace eval oolib { oo::class create collection { variable o_data ;#dict - variable o_alias + #variable o_alias constructor {} { set o_data [dict create] } @@ -103,37 +103,38 @@ namespace eval oolib { } } #review - see patternlib. Is the intention for aliases to be configurable independent of whether the target exists? - method alias {newAlias existingKeyOrAlias} { - if {[string is integer -strict $newAlias]} { - error "[self object] collection key alias cannot be integer" - } - if {[string length $existingKeyOrAlias]} { - set o_alias($newAlias) $existingKeyOrAlias - } else { - unset o_alias($newAlias) - } - } - method aliases {{key ""}} { - if {[string length $key]} { - set result [list] - foreach {n v} [array get o_alias] { - if {$v eq $key} { - lappend result $n $v - } - } - return $result - } else { - return [array get o_alias] - } - } - #if the supplied index is an alias, return the underlying key; else return the index supplied. - method realKey {idx} { - if {[catch {set o_alias($idx)} key]} { - return $idx - } else { - return $key - } - } + #review - what is the point of alias anyway? - why slow down other operations when a variable can hold a keyname perfectly well? + #method alias {newAlias existingKeyOrAlias} { + # if {[string is integer -strict $newAlias]} { + # error "[self object] collection key alias cannot be integer" + # } + # if {[string length $existingKeyOrAlias]} { + # set o_alias($newAlias) $existingKeyOrAlias + # } else { + # unset o_alias($newAlias) + # } + #} + #method aliases {{key ""}} { + # if {[string length $key]} { + # set result [list] + # foreach {n v} [array get o_alias] { + # if {$v eq $key} { + # lappend result $n $v + # } + # } + # return $result + # } else { + # return [array get o_alias] + # } + #} + ##if the supplied index is an alias, return the underlying key; else return the index supplied. + #method realKey {idx} { + # if {[catch {set o_alias($idx)} key]} { + # return $idx + # } else { + # return $key + # } + #} method add {value key} { if {[string is integer -strict $key]} { error "[self object] collection key must not be an integer. Use another structure if integer keys required" diff --git a/src/modules/punk/ansi-999999.0a1.0.tm b/src/modules/punk/ansi-999999.0a1.0.tm index 0534fc5..d2b80fe 100644 --- a/src/modules/punk/ansi-999999.0a1.0.tm +++ b/src/modules/punk/ansi-999999.0a1.0.tm @@ -106,7 +106,7 @@ namespace eval punk::ansi::class { #overflow is a different concept - perhaps not particularly congruent with the idea of the textblock as a mini terminal emulator. #overflow effectively auto-expands the block(terminal?) width #overflow and wrap both being true won't make sense unless we implement a max_overflow concept - set o_rendered [overtype::left -overflow 0 -wrap 1 -width $w -height $h -appendlines 1 "" [$o_ansistringobj get]] + set o_rendered [overtype::renderspace -overflow 0 -wrap 1 -width $w -height $h -appendlines 1 "" [$o_ansistringobj get]] if {$cksum eq "not-done"} { #if dimensions changed - the checksum won't have been done set o_rendered_what [$o_ansistringobj checksum] @@ -129,7 +129,7 @@ namespace eval punk::ansi::class { set o_dimensions $dimensions - set rendered [overtype::left -experimental {test_mode} -overflow 0 -wrap 1 -width $w -height $h -appendlines 1 "" [$o_ansistringobj get]] + set rendered [overtype::renderspace -experimental {test_mode} -overflow 0 -wrap 1 -width $w -height $h -appendlines 1 "" [$o_ansistringobj get]] return $rendered } method render_to_input_line {args} { @@ -175,7 +175,7 @@ namespace eval punk::ansi::class { if {$opt_minus ne "0"} { set chunk [string range $chunk 0 end-$opt_minus] } - set rendered [overtype::left -experimental {test_mode} -overflow 0 -wrap 1 -width $w -height $h -appendlines 1 "" $chunk] + set rendered [overtype::renderspace -experimental {test_mode} -overflow 0 -wrap 1 -width $w -height $h -appendlines 1 "" $chunk] set marker "" for {set i 1} {$i <= $w} {incr i} { if {$i % 10 == 0} { @@ -190,13 +190,13 @@ namespace eval punk::ansi::class { set xline [lindex $rlines $x]\n set xlinev [ansistring VIEWSTYLE $xline] set xlinev [string map $maplf $xlinev] - set xlinedisplay [overtype::left -wrap 1 -width $w -height 1 "" $xlinev] + set xlinedisplay [overtype::renderspace -wrap 1 -width $w -height 1 "" $xlinev] ::append rendered \n $xlinedisplay set chunk [ansistring VIEWSTYLE $chunk] set chunk [string map $maplf $chunk] #keep chunkdisplay narrower - leave at 80 or it will get unwieldy for larger image widths - set chunkdisplay [overtype::left -wrap 1 -width 80 -height 1 "" $chunk] + set chunkdisplay [overtype::renderspace -wrap 1 -width 80 -height 1 "" $chunk] set renderheight [llength [split $rendered \n]] set chunkdisplay_lines [split $chunkdisplay \n] set chunkdisplay_tail [lrange $chunkdisplay_lines end-$renderheight end] @@ -215,14 +215,87 @@ namespace eval punk::ansi::class { method viewlines {} { return [ansistring VIEW [$o_ansistringobj get]] } - method viewcodes {} { - return [ansistring VIEWCODES [$o_ansistringobj get]] + method viewcodes {args} { + set defaults [list\ + -lf 0\ + -vt 0\ + -width "auto"\ + ] + foreach {k v} $args { + switch -- $k { + -lf - -vt - -width {} + default { + error "viewcodes unrecognised option '$k'. Known options [dict keys $defaults]" + } + } + } + set opts [dict merge $defaults $args] + set opts_lf [dict get $opts -lf] + set opts_vt [dict get $opts -vt] + set opts_width [dict get $opts -width] + if {$opts_width eq ""} { + return [ansistring VIEWCODES -lf $opts_lf -vt $opts_vt [$o_ansistringobj get]] + } elseif {$opts_width eq "auto"} { + lassign [punk::console::get_size] _cols columns _rows rows + set displaycols [expr {$columns -4}] ;#review + return [overtype::renderspace -width $displaycols -wrap 1 "" [ansistring VIEWCODES -lf $opts_lf -vt $opts_vt [$o_ansistringobj get]]] + } elseif {[string is integer -strict $opts_width] && $opts_width > 0} { + return [overtype::renderspace -width $opts_width -wrap 1 "" [ansistring VIEWCODES -lf $opts_lf -vt $opts_vt [$o_ansistringobj get]]] + } else { + error "viewcodes unrecognised value for -width. Try auto or a positive integer" + } } - method viewchars {} { - return [punk::ansi::stripansiraw [$o_ansistringobj get]] + method viewchars {args} { + set defaults [list\ + -width "auto"\ + ] + foreach {k v} $args { + switch -- $k { + -width {} + default { + error "viewchars unrecognised option '$k'. Known options [dict keys $defaults]" + } + } + } + set opts [dict merge $defaults $args] + set opts_width [dict get $opts -width] + if {$opts_width eq ""} { + return [punk::ansi::stripansiraw [$o_ansistringobj get]] + } elseif {$opts_width eq "auto"} { + lassign [punk::console::get_size] _cols columns _rows rows + set displaycols [expr {$columns -4}] ;#review + return [overtype::renderspace -width $displaycols -wrap 1 "" [punk::ansi::stripansiraw [$o_ansistringobj get]]] + } elseif {[string is integer -strict $opts_width] && $opts_width > 0} { + return [overtype::renderspace -width $opts_width -wrap 1 "" [punk::ansi::stripansiraw [$o_ansistringobj get]]] + } else { + error "viewchars unrecognised value for -width. Try auto or a positive integer" + } } - method viewstyle {} { - return [ansistring VIEWSTYLE [$o_ansistringobj get]] + method viewstyle {args} { + set defaults [list\ + -width "auto"\ + ] + foreach {k v} $args { + switch -- $k { + -width {} + default { + error "viewstyle unrecognised option '$k'. Known options [dict keys $defaults]" + } + } + } + set opts [dict merge $defaults $args] + set opts_width [dict get $opts -width] + if {$opts_width eq ""} { + return [ansistring VIEWSTYLE [$o_ansistringobj get]] + } elseif {$opts_width eq "auto"} { + lassign [punk::console::get_size] _cols columns _rows rows + set displaycols [expr {$columns -4}] ;#review + return [overtype::renderspace -width $displaycols -wrap 1 "" [ansistring VIEWSTYLE [$o_ansistringobj get]]] + } elseif {[string is integer -strict $opts_width] && $opts_width > 0} { + return [overtype::renderspace -width $opts_width -wrap 1 "" [ansistring VIEWSTYLE [$o_ansistringobj get]]] + } else { + error "viewstyle unrecognised value for -width. Try auto or a positive integer" + } } method append_noreturn {ansistring} { $o_ansistringobj append $ansistring @@ -456,7 +529,7 @@ namespace eval punk::ansi { set ansidata [fcat -encoding $encoding $fname] set obj [punk::ansi::class::class_ansi new $ansidata] - if {$test_mode} { + if {$encoding eq "cp437"} { set result [$obj rendertest $dimensions] } else { set result [$obj render $dimensions] @@ -600,7 +673,6 @@ namespace eval punk::ansi { #[para]Alternate graphics modes will be stripped - exposing the raw characters as they appear without graphics mode. #[para]ie instead of a horizontal line you may see: qqqqqq - join [::punk::ansi::ta::split_at_codes $text] "" } proc stripansi1 {text} { @@ -1061,7 +1133,7 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu dodgerblue2\ green4\ springgreen4\ - turquise4\ + turquoise4\ deepskyblue3\ deepskyblue3\ dodgerblue1\ @@ -1459,7 +1531,9 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu append out [a] return [string trimleft $out \n] } - proc colourtable_216_names {} { + + #x6 is reasonable from a width (124 screen cols) and colour viewing perspective + proc colourtable_216_names {{cols 6}} { set out "" #use the reverse lookup dict - the original xterm_names list has duplicates - we want the disambiguated (potentially suffixed) names variable TERM_colour_map_reverse @@ -1470,7 +1544,7 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu $t configure -show_seps 0 -show_edge 0 for {set i 16} {$i <=231} {incr i} { set cname [dict get $TERM_colour_map_reverse $i] ;#use term-cname etc instead of term$i - may as well let a+ cache the call by name as the preferred? option - if {[llength $row]== 8} { + if {[llength $row]== $cols} { lappend rows $row set row [list] } @@ -1492,6 +1566,112 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu append out [a] return [string trimleft $out \n] } + proc colourtable_term_pastel {} { + set out "" + set rows [list] + #see https://www.hackitu.de/termcolor256/ + lappend rows {59 95 131 167 174 181 188} + lappend rows {59 95 131 173 180 187 188} + lappend rows {59 95 137 179 186 187 188} + lappend rows {59 101 143 185 186 187 188} + lappend rows {59 65 107 149 186 187 188} + lappend rows {59 65 71 113 150 187 188} + lappend rows {59 65 71 77 114 151 188} + lappend rows {59 65 71 78 115 152 188} + lappend rows {59 65 72 79 116 152 188} + lappend rows {59 66 73 80 116 152 188} + lappend rows {59 60 67 74 116 152 188} + lappend rows {59 60 61 68 110 152 188} + lappend rows {59 60 61 62 104 146 188} + lappend rows {59 60 61 98 140 182 188} + lappend rows {59 60 97 134 176 182 188} + lappend rows {59 96 133 170 176 182 188} + lappend rows {59 95 132 169 176 182 188} + lappend rows {59 95 131 168 175 182 188} + + set t [textblock::class::table new] + $t configure -show_seps 0 -show_edge 0 + set fg "web-black" + foreach r $rows { + set rowcells [list] + foreach cnum $r { + lappend rowcells "[a+ $fg Term-$cnum][format %3s $cnum] " + } + $t add_row $rowcells + } + append out [$t print] + $t destroy + set pastel8 [list 102 138 144 108 109 103 139 145] + set p8 "" + foreach cnum $pastel8 { + append p8 "[a+ $fg Term-$cnum][format %3s $cnum] " + } + append p8 [a]\n + append out \n $p8 + + return $out + } + proc colourtable_term_rainbow {} { + set out "" + set rows [list] + set fgwhite [list 16 52 88 124 160 22 17 18 19 20 21 57 56 93 55 92 54 91 53 90 89 126 88 125 124 160] + #see https://www.hackitu.de/termcolor256/ + lappend rows {16 52 88 124 160 196 203 210 217 224 231} + lappend rows {16 52 88 124 160 202 209 216 223 230 231} + lappend rows {16 52 88 124 166 208 215 222 229 230 231} + lappend rows {16 52 88 130 172 214 221 228 229 230 231} + lappend rows {16 52 94 136 178 220 227 227 228 230 231} + + lappend rows {16 58 100 142 184 226 227 228 228 230 231} + + lappend rows {16 22 64 106 148 190 227 228 229 230 231} + lappend rows {16 22 28 70 112 154 191 228 229 230 231} + lappend rows {16 22 28 34 76 118 155 192 229 230 231} + lappend rows {16 22 28 34 40 82 119 156 193 230 231} + lappend rows {16 22 28 34 40 46 83 120 157 194 231} + lappend rows {16 22 28 34 40 47 84 121 158 195 231} + lappend rows {16 22 28 34 41 48 85 122 158 195 231} + lappend rows {16 22 28 35 42 49 86 123 159 195 231} + lappend rows {16 22 29 36 43 50 87 123 159 195 231} + + lappend rows {16 23 30 37 44 51 87 123 159 195 231} + + lappend rows {16 17 24 31 38 45 87 123 159 195 231} + lappend rows {16 17 18 25 32 39 81 123 159 195 231} + lappend rows {16 17 18 19 26 33 75 117 159 195 231} + lappend rows {16 17 18 19 20 27 69 111 153 195 231} + lappend rows {16 17 18 19 20 21 63 105 147 189 231} + lappend rows {16 17 18 19 20 57 99 141 183 225 231} + lappend rows {16 17 18 19 56 93 135 177 219 225 231} + lappend rows {16 17 18 55 92 129 171 213 219 225 231} + lappend rows {16 17 54 91 128 165 207 213 219 225 231} + + lappend rows {16 53 90 127 164 201 207 213 219 225 231} + + lappend rows {16 52 89 126 163 200 207 213 219 225 231} + lappend rows {16 52 88 125 162 199 206 213 219 225 231} + lappend rows {16 52 88 124 161 198 205 212 219 225 231} + lappend rows {16 52 88 124 160 197 204 211 218 225 231} + + + set t [textblock::class::table new] + $t configure -show_seps 0 -show_edge 0 + foreach r $rows { + set rowcells [list] + foreach cnum $r { + if {$cnum in $fgwhite} { + set fg "web-white" + } else { + set fg "web-black" + } + lappend rowcells "[a+ $fg Term-$cnum][format %3s $cnum] " + } + $t add_row $rowcells + } + append out [$t print] + $t destroy + return $out + } #24 greys of 256 proc colourblock_24 {} { set out "" @@ -1548,7 +1728,8 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu # $WEB_colour_map_gray\ #] proc colourtable_web {{groups *}} { - set all_groupnames [list basic pink red orange yellow brown purple blue cyan green white gray] + #set all_groupnames [list basic pink red orange yellow brown purple blue cyan green white gray] + set all_groupnames [list basic brown yellow red pink orange purple blue cyan green white gray] switch -- $groups { "" - * { set show_groups $all_groupnames @@ -1696,7 +1877,7 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu set map2 [colourmap2 $bgname] set map2 [overtype::centre -transparent 1 $map2 "[a black $bgname]High-intensity colours[a]"] append out [textblock::join $indent [textblock::join $map1 $map2]] \n - append out "[a+ web-white]216 colours of 256 terminal colours (To see names, use: a? term)[a]" \n + append out "[a+ web-white]216 colours of 256 terminal colours (To see names, use: a? term ?pastel? ?rainbow?)[a]" \n append out [textblock::join $indent [colourblock_216]] \n append out "[a+ web-white]24 Greyscale colours[a]" \n append out [textblock::join $indent [colourblock_24]] \n @@ -1709,6 +1890,7 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu #dict set WEB_colour_map mediumvioletred 199-21-133 ;# #C71585 append out [textblock::join $indent "Example: \[a+ rgb-199-21-133\]text\[a] -> [a+ rgb-199-21-133]text[a]"] \n append out [textblock::join $indent "Example: \[a+ Rgb#C71585\]text\[a] -> [a+ Rgb#C71585]text[a]"] \n + append out [textblock::join $indent "Examine a sequence: a? bold rgb-46-139-87 Rgb#C71585 "] \n append out \n append out "[a+ web-white]Web colours[a]" \n append out [textblock::join $indent "To see all names use: a? web"] \n @@ -1730,12 +1912,34 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu } else { switch -- [lindex $args 0] { term { + set termargs [lrange $args 1 end] + foreach ta $termargs { + switch -- $ta { + pastel - rainbow {} + default {error "unrecognised term option '$ta'. Known values: pastel rainbow"} + } + } set out "16 basic colours\n" append out [colourtable_16_names] \n append out "216 colours\n" append out [colourtable_216_names] \n append out "24 greyscale colours\n" append out [colourtable_24_names] + foreach ta $termargs { + switch -- $ta { + pastel { + append out \n + append out "Pastel Colour Space (punk::ansi::colourtable_term_pastel)\n" + append out [colourtable_term_pastel] + } + rainbow { + append out \n + append out "Rainbow Colours (punk::ansi::colourtable_term_rainbow)\n" + append out [colourtable_term_rainbow] + } + } + } + append out "\nNote: The 256 term colours especially 0-15 may be altered by terminal pallette settings or ansi OSC 4 codes, so specific RGB values are unavailable" return $out } web { @@ -1748,20 +1952,130 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu return $out } } - set result [list] - set map [dict merge $SGR_setting_map $SGR_colour_map] - set rmap [lreverse $map] + + variable WEB_colour_map + variable X11_colour_map + variable TERM_colour_map + variable TERM_colour_map_reverse + variable SGR_map + + set t [textblock::class::table new] + $t configure -show_edge 0 -show_seps 1 -show_header 0 + + set resultlist [list] foreach i $args { - if {[string is integer -strict $i]} { - if {[dict exists $rmap $i]} { - lappend result $i [dict get $rmap $i] + set f4 [string range $i 0 3] + set s [a+ $i]sample + switch -- $f4 { + web- - Web- - WEB- { + set tail [string tolower [string trim [string range $i 4 end] -]] + if {[dict exists $WEB_colour_map $tail]} { + set dec [dict get $WEB_colour_map $tail] + set hex [colour_dec2hex $dec] + set descr "$hex $dec" + } else { + set descr "UNKNOWN colour for web" + } + $t add_row [list $i $descr $s [ansistring VIEW $s]] } - } else { - if {[dict exists $map $i]} { - lappend result $i [dict get $map $i] + term - Term { + set tail [string trim [string range $i 4 end] -] + if {[string is integer -strict $tail]} { + if {$tail < 256} { + set descr "[dict get $TERM_colour_map_reverse $tail]" + } else { + set descr "Invalid (> 255)" + } + } else { + set tail [string tolower $tail] + if {[dict exists $TERM_colour_map $tail]} { + set descr [dict get $TERM_colour_map $tail] + } else { + set descr "UNKNOWN colour for term" + } + } + $t add_row [list $i $descr $s [ansistring VIEW $s]] + } + x11- - X11- { + set tail [string tolower [string trim [string range $i 4 end] -]] + if {[dict exists $X11_colour_map $tail]} { + set dec [dict get $X11_colour_map $tail] + set hex [colour_dec2hex $dec] + set descr "$hex $dec" + } else { + set descr "UNKNOWN colour for x11" + } + $t add_row [list $i $descr $s [ansistring VIEW $s]] } + rgb- - Rgb- - RGB- - + rgb0 - rgb1 - rgb2 - rgb3 - rgb4 - rgb5 - rgb6 - rgb7 - rgb8 - rgb9 - + Rgb0 - Rgb1 - Rgb2 - Rgb3 - Rgb4 - Rgb5 - Rgb6 - Rgb7 - Rgb8 - Rgb9 - + RGB0 - RGB1 - RGB2 - RGB3 - RGB4 - RGB5 - RGB6 - RGB7 - RGB8 - RGB9 - + rgb# - Rgb# - RGB# { + if {[string index $i 3] eq "#"} { + set tail [string range $i 4 end] + set hex $tail + set dec [colour_hex2dec $hex] + set info $dec ;#show opposite type as first line of info col + } else { + set tail [string trim [string range $i 3 end] -] + set dec $tail + set hex [colour_dec2hex $dec] + set info $hex + } + + set webcolours_i [lsearch -all $WEB_colour_map $dec] + set webcolours [list] + foreach ci $webcolours_i { + lappend webcolours [lindex $WEB_colour_map $ci-1] + } + set x11colours [list] + set x11colours_i [lsearch -all $X11_colour_map $dec] + foreach ci $x11colours_i { + set c [lindex $X11_colour_map $ci-1] + if {$c ni $webcolours} { + lappend x11colours $c + } + } + foreach c $webcolours { + append info \n web-$c + } + foreach c $x11colours { + append info \n x11-$c + } + $t add_row [list $i "$info" $s [ansistring VIEW $s]] + } + default { + if {[string is integer -strict $i]} { + set rmap [lreverse $SGR_map] + $t add_row [list $i "SGR [dict get $rmap $i]" $s [ansistring VIEW $s]] + } else { + if {[dict exists $SGR_map $i]} { + $t add_row [list $i "SGR [dict get $SGR_map $i]" $s [ansistring VIEW $s]] + } else { + $t add_row [list $i UNKNOWN $s [ansistring VIEW $s]] + } + } + } + } + } + set ansi [a+ {*}$args] + set s ${ansi}sample + set merged [punk::ansi::codetype::sgr_merge_singles [list $ansi]] + set s2 ${merged}sample + #lappend resultlist "RESULT: [a+ {*}$args]sample[a]" + $t add_row [list RESULT "" $s [ansistring VIEW $s]] + if {$ansi ne $merged} { + if {[string length $merged] < [string length $ansi]} { + #only refer to redundancies if shorter - merge may reorder - REVIEW + set warning "[a+ web-red Web-yellow]REDUNDANCIES FOUND" + } else { + set warning "" } + $t add_row [list MERGED $warning $s2 [ansistring VIEW $s2]] } + set result [$t print] + $t destroy return $result } } @@ -1788,13 +2102,33 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu set sgr_cache [dict create] return "sgr_cache cleared" } + if {[catch { + set termwidth [dict get [punk::console::get_size] columns] + } errM]} { + set termwidth 80 + } + set termwidth [expr [$termwidth -3]] set out "" + set linelen 0 set RST [a] + set lines [list] + set line "" #todo - terminal width? table? dict for {key ansi} $sgr_cache { - append out "$ansi$key$RST " + set thislen [expr {[string length $key]+1}] + if {$linelen + $thislen >= $termwidth-1} { + lappend lines $line + set line "$ansi$key$RST " + set linelen $thislen + } else { + append line "$ansi$key$RST " + incr linelen $thislen + } } - return $out\n + if {[string length $line]} { + lappend lines $line + } + return [join $lines \n] } proc a+ {args} { @@ -1922,7 +2256,7 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu switch -- $i { Brightblack {lappend t 100} Brightred {lappend t 101} - Brightgreen {lappend t 101} + Brightgreen {lappend t 102} Brightyellow {lappend t 103} Brightblue {lappend t 104} Brightpurple {lappend t 105} @@ -2154,7 +2488,7 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu switch -- $i { Brightblack {lappend t 100} Brightred {lappend t 101} - Brightgreen {lappend t 101} + Brightgreen {lappend t 102} Brightyellow {lappend t 103} Brightblue {lappend t 104} Brightpurple {lappend t 105} @@ -2953,10 +3287,11 @@ namespace eval punk::ansi { set defaults [dict create\ -filter_fg 0\ -filter_bg 0\ + -filter_reset 0\ ] dict for {k v} $args { switch -- $k { - -filter_fg - -filter_bg {} + -filter_fg - -filter_bg - -filter_reset {} default { error "sgr_merge unknown option '$k'. Known options [dict keys $defaults]" } @@ -3015,8 +3350,10 @@ namespace eval punk::ansi { set codeint [string trimleft [lindex $paramsplit 0] 0] switch -- $codeint { "" - 0 { - set codestate $codestate_initial - set did_reset 1 + if {![dict get $opts -filter_reset]} { + set codestate $codestate_initial + set did_reset 1 + } } 1 { #bold @@ -3086,7 +3423,7 @@ namespace eval punk::ansi { } 21 { #ECMA-48 double underline - some terminals use as not-bold. For now we won't support that. - dict set doubleunderline 21 + dict set codestate doubleunderline 21 } 22 { #normal intensity @@ -4866,9 +5203,12 @@ namespace eval punk::ansi::ansistring { if {$opt_cr} { dict set visuals_opt CR [list \x0d \u240d] } - if {$opt_lf} { + if {$opt_lf == 1} { dict set visuals_opt LF [list \x0a \u240a] } + if {$opt_lf == 2} { + dict set visuals_opt LF [list \x0a \u240a\n] + } if {$opt_vt} { dict set visuals_opt VT [list \x0b \u240b] } diff --git a/src/modules/punk/basictelnet-999999.0a1.0.tm b/src/modules/punk/basictelnet-999999.0a1.0.tm index bb47789..bb8cc27 100644 --- a/src/modules/punk/basictelnet-999999.0a1.0.tm +++ b/src/modules/punk/basictelnet-999999.0a1.0.tm @@ -528,7 +528,7 @@ namespace eval punk::basictelnet { # -- --- --- --- set tailinfo "" if {[string length $nextwaiting]} { - set waitingdisplay [overtype::left -wrap 1 -width 77 -height 1 "" [ansistring VIEW -lf 1 -vt 1 $nextwaiting]] + set waitingdisplay [overtype::renderspace -wrap 1 -width 77 -height 1 "" [ansistring VIEW -lf 1 -vt 1 $nextwaiting]] set tailinfo "[a+ red]from waiting:\n $waitingdisplay[a]" } ::punk::basictelnet::add_debug "[a+ Yellow black]from stdin sending: [ansistring VIEW -lf 1 -vt 1 $chunk][a]\n$tailinfo\n" stdin $sock @@ -626,7 +626,7 @@ namespace eval punk::basictelnet { #set rawview [ansistring VIEW -lf 1 -vt 1 [encoding convertfrom $encoding_guess $data]] set rawview [ansistring VIEW -lf 1 -vt 1 $data] #set viewblock [overtype::left -wrap 1 -width 78 -height 4 "" $rawview] - set viewblock [overtype::left -experimental test_mode -wrap 1 -width 78 -height 4 "" $rawview] + set viewblock [overtype::renderspace -experimental test_mode -wrap 1 -width 78 -height 4 "" $rawview] set lines [split $viewblock \n] if {[llength $lines] > 4} { append debug_info [join [list {*}[lrange $lines 0 1] "...<[expr {[llength $lines] -4}] lines undisplayed>..." {*}[lrange $lines end-1 end]] \n] diff --git a/src/modules/punk/repl-0.1.tm b/src/modules/punk/repl-0.1.tm index deaf631..1599788 100644 --- a/src/modules/punk/repl-0.1.tm +++ b/src/modules/punk/repl-0.1.tm @@ -658,6 +658,7 @@ proc repl::start {inchan args} { variable editbuf variable editbuf_list ;#command history variable editbuf_linenum_submitted + variable editbuf_active_index # --- variable running @@ -681,6 +682,7 @@ proc repl::start {inchan args} { set editbuf [punk::repl::class::class_editbuf new {}] lappend editbuf_list $editbuf ;#current editbuf is always in the history set editbuf_linenum_submitted 0 + set editbuf_active_index 0 # --- if {$::punk::console::ansi_wanted == 2} { @@ -1050,6 +1052,11 @@ namespace eval repl { } namespace eval punk::repl::class { + oo::class create class_bufman { + + } + + #multiline editing buffer oo::class create class_editbuf { variable o_context @@ -1157,7 +1164,7 @@ namespace eval punk::repl::class { append debug \n $mergedinfo append debug \n "input:[ansistring VIEW -lf 1 -vt 1 $new0] before row:$o_cursor_row after row: $result_row before col:$o_cursor_col after col:$result_col" package require textblock - set debug [textblock::frame $debug] + set debug [textblock::frame -buildcache 0 $debug] catch {punk::console::move_emitblock_return $debug_first_row 1 $debug} # -- --- --- --- --- --- @@ -1222,7 +1229,7 @@ namespace eval punk::repl::class { append debug \n $mergedinfo append debug \n "input:[ansistring VIEW -lf 1 -vt 1 $p]" package require textblock - set debug [textblock::frame $debug] + set debug [textblock::frame -buildcache 0 $debug] #catch {punk::console::move_emitblock_return [expr {$debug_first_row + ($i * 6)}] 1 $debug} set result [dict get $mergedinfo result] @@ -1682,9 +1689,14 @@ proc repl::repl_handler {inputchan prompt_config} { } set in_repl_handler [list] } -proc repl::editbuf {args} { - variable editbuf - $editbuf {*}$args +proc repl::editbuf {index args} { + variable editbuf_list + set editbuf [lindex $editbuf_list $index] + if {$editbuf ne ""} { + $editbuf {*}$args + } else { + return "No such index in editbuf list" + } } interp alias {} editbuf {} ::repl::editbuf proc repl::repl_process_data {inputchan type chunk stdinlines prompt_config} { @@ -1873,11 +1885,11 @@ proc repl::repl_process_data {inputchan type chunk stdinlines prompt_config} { set info [list_as_lines $lines] } } errM]} { - set info [textblock::frame -title "[a red]error[a]" $errM] + set info [textblock::frame -buildcache 0 -title "[a red]error[a]" $errM] } else { - set info [textblock::frame -ansiborder [a+ green bold] -title "[a cyan]debugview_raw[a]" $info] + set info [textblock::frame -buildcache 0 -ansiborder [a+ green bold] -title "[a cyan]debugview_raw[a]" $info] } - set debug_width [textblock::width $info] + set debug_width [textblock::widthtopline $info] set spacepatch [textblock::block $debug_width 2 " "] puts -nonewline [punk::ansi::cursor_off] #use non cursorsave versions - cursor save/restore will interfere with any concurrent ansi rendering that uses save/restore - because save/restore is a single item, not a stack. @@ -1895,16 +1907,16 @@ proc repl::repl_process_data {inputchan type chunk stdinlines prompt_config} { set info [list_as_lines $lines] } } editbuf_error]} { - set info [textblock::frame -title "[a red]error[a]" "$editbuf_error\n$::errorInfo"] + set info [textblock::frame -buildcache 0 -title "[a red]error[a]" "$editbuf_error\n$::errorInfo"] } else { - set title "[a cyan]editbuf lines [$editbuf linecount][a]" + set title "[a cyan]editbuf [expr {[llength $editbuf_list]-1}] lines [$editbuf linecount][a]" append title "[a+ yellow bold] col:[format %3s [$editbuf cursor_column]] row:[$editbuf cursor_row][a]" set row1 " lastchar:[ansistring VIEW -lf 1 [$editbuf last_char]] lastgrapheme:[ansistring VIEW -lf 1 [$editbuf last_grapheme]]" set row2 " lastansi:[ansistring VIEW -lf 1 [$editbuf last_ansi]]" set info [a+ green bold]$row1\n$row2[a]\n$info - set info [textblock::frame -ansiborder [a+ green bold] -title $title $info] + set info [textblock::frame -buildcache 0 -ansiborder [a+ green bold] -title $title $info] } - set editbuf_width [textblock::width $info] + set editbuf_width [textblock::widthtopline $info] set spacepatch [textblock::block $editbuf_width 2 " "] set editbuf_offset [expr {$consolewidth - $debug_width - $editbuf_width - 2}] @@ -2475,7 +2487,19 @@ proc repl::repl_process_data {inputchan type chunk stdinlines prompt_config} { set commandstr "" #catch {puts stderr "zz2---->[rep $::arglej]"} - + set lines [$editbuf lines] + set buf_has_data 0 + foreach ln $lines { + if {[string trim $ln] ne ""} { + set buf_has_data 1 + } + } + if {$buf_has_data} { + set editbufnext [punk::repl::class::class_editbuf new {}] + lappend editbuf_list $editbufnext + set editbuf_linenum_submitted 0 + set editbuf $editbufnext + } #editbuf } else { diff --git a/src/modules/textblock-999999.0a1.0.tm b/src/modules/textblock-999999.0a1.0.tm index e54cd46..79f96ac 100644 --- a/src/modules/textblock-999999.0a1.0.tm +++ b/src/modules/textblock-999999.0a1.0.tm @@ -231,6 +231,7 @@ namespace eval textblock { variable o_opts_table_defaults variable o_opts_column_defaults variable o_opts_row_defaults + variable TSUB constructor {args} { #*** !doctools #[call class::table [method constructor] [arg args]] @@ -258,6 +259,7 @@ namespace eval textblock { set o_rowdefs [dict create] ;#user requested row data e.g -minheight -maxheight set o_rowstates [dict create] ;#actual row data such as -minheight and -maxheight detected from supplied row data + set TSUB \uF111 ;#should be BMP PUA code to show as either replacement char or nerdfont glyph. See FSUB for comments regarding choices. } method Get_seps {} { set requested_seps [dict get $o_opts_table -show_seps] @@ -594,6 +596,8 @@ namespace eval textblock { -ansireset "\uFFEF"\ -minwidth ""\ -maxwidth ""\ + -blockalign centre\ + -textalign left\ ] #initialise -ansireset with replacement char so we can keep in appropriate dict position for initial configure and then treat as read-only set o_opts_column_defaults $defaults @@ -769,6 +773,16 @@ namespace eval textblock { error "textblock::table::configure_column -ansireset is read-only. It is present only to prevent unwanted colourised output in configure commands" } } + -blockalign - -textalign { + switch -- $v { + left - right { + lappend checked_opts $k $v + } + centre - centre { + lappend checked_opts $k centre + } + } + } default { lappend checked_opts $k $v } @@ -1269,11 +1283,16 @@ namespace eval textblock { set botseps_v [dict get $sep_elements_vertical bottom$opt_posn] set onlyseps_v [dict get $sep_elements_vertical only$opt_posn] + #top should work for all header rows regarding vseps - as we only use it to reduce the box elements, and lower headers have same except for tlc which isn't present anyway set headerseps_v [dict get $sep_elements_vertical top$opt_posn] lassign [my Get_seps] _h show_seps_h _v show_seps_v set return_headerheight 0 set return_headerwidth 0 + set cidx [lindex [dict keys $o_columndefs] $index_expression] + set colwidth [my column_width $cidx] + set col_blockalign [dict get $o_columndefs $cidx -blockalign] + if {$do_show_header} { #puts "boxlimitsinfo header $opt_posn: -- boxlimits $header_boxlimits -- boxmap $hdrmap" set ansibase_header [dict get $o_opts_table -ansibase_header] ;#merged to single during configure @@ -1284,9 +1303,7 @@ namespace eval textblock { } else { set ansiborder_final $ansibase_header$ansiborder_header } - set cidx [lindex [dict keys $o_columndefs] $index_expression] set RST [punk::ansi::a] - set colwidth [my column_width $cidx] set hcell_line_blank [string repeat " " $colwidth] set h 0 @@ -1304,6 +1321,8 @@ namespace eval textblock { set column_width_cache [dict create] + #used for colspan-zero header frames + set framesub_map [list hl $TSUB vll $TSUB vlr $TSUB tlc $TSUB blc $TSUB trc $TSUB brc $TSUB] ;# a debug test foreach header $header_list { set headerspans [dict get $all_colspans $h] @@ -1361,21 +1380,33 @@ namespace eval textblock { } } + #supporting wrapping in headers might be a step too difficult for little payoff. + #we would need a flag to enable/disable - plus language-based wrapping alg (see tcllib) + #The interaction with colspans and width calculations makes it likely to have a heavy performance impact and make the code even more complex. + #May be better to require user to pre-wrap as needed + ##set hval [textblock::renderspace -width $colwidth -wrap 1 "" $hval] + #review - contentwidth of hval can be greater than $colwidth+2 - if so frame function will detect and frame_cache will not be used - set header_cell_startspan [textblock::frame -usecache 1 -width [expr {$colwidth+2}] -type [dict get $ftypes header]\ + #This ellipsis 0 makes a difference (unwanted ellipsis at rhs of header column that starts span) + + # -width is always +2 - as the boxlimits take into account show_vseps and show_edge + set header_cell_startspan [textblock::frame -ellipsis 0 -usecache 1 -width [expr {$colwidth+2}] -type [dict get $ftypes header]\ -ansibase $ansibase_header -ansiborder $ansiborder_final\ -boxlimits $hlims -boxmap $startmap -joins $header_joins $hval\ ] + #JMN + #puts "===>\n$header_cell_startspan\n<===" set spanned_parts [list $header_cell_startspan] if {$this_span ne "1"} { - #more parts to append + #assert this_span == "all" or >1 ie a header that spans other columns + #therefore more parts to append #set remaining_cols [lrange [dict keys $o_columndefs] $cidx end] set remaining_spans [lrange $headerspans $cidx+1 end] #puts ">> remaining_spans: $remaining_spans" set spancol [expr {$cidx + 1}] set h_lines [lrepeat $rowh ""] - set hcell_blank [::join $h_lines \n] + set hcell_blank [::join $h_lines \n] ;#todo - just use -height option of frame? @@ -1441,22 +1472,26 @@ namespace eval textblock { dict set column_width_cache $spancol headerwidth $hwidth } else { set bwidth [dict get $column_width_cache $spancol bodywidth] + set hwidth [dict get $column_width_cache $spancol headerwidth] } - - if {$next_posn eq "right"} { - #This is an unintuitive edge case - review - #spans at tail end are too long when edges are shown if we use bwidth+1 (vlr extends right beyond table) - #spans at tail end are too short if edges are hidden and we use bwidth (short lower horizontal bar) - if {![dict get $o_opts_table -show_edge]} { - set spanwidth [expr {$bwidth+1}] - } else { - set spanwidth $bwidth - } + #subsequent headers may also span columns - so we will get too wide if we use the headers directly + #but if we don't take into account header widths - they may get truncated. + + #This is an unintuitive edge case - review + #spans at tail end are too long when edges are shown if we use bwidth (vlr extends right beyond table) + #spans at tail end are too short if edges are hidden and we use bwidth-1 (short lower horizontal bar) + #test JMN + if {$next_posn eq "right" && [dict get $o_opts_table -show_edge]} { + set spanwidth [expr {$bwidth -1}] } else { - set spanwidth [expr {$bwidth+1}] + set spanwidth [expr {$bwidth }] } - set header_cell [textblock::frame -width $spanwidth -type [dict get $ftypes header]\ + #JMN - review + set framewidth $spanwidth + incr framewidth 1 + + set header_cell [textblock::frame -ellipsis 0 -width $framewidth -type [dict get $ftypes header]\ -ansibase $ansibase_header -ansiborder $ansiborder_final\ -boxlimits $hlims -boxmap $this_span_map -joins $header_joins $hcell_blank\ ] @@ -1485,8 +1520,9 @@ namespace eval textblock { set spacemap [list hl " " vl " " tlc " " blc " " trc " " brc " "] #set spacemap [list hl "\uFFFF" vl "\uFFFF" tlc "\uFFFF" blc "\uFFFF" trc "\uFFFF " brc "\uFFFF"] ;# a debug test #-usecache 1 ok - set hblock [textblock::frame -type $spacemap -boxlimits $hlims -ansibase $ansibase_header $hval] - set spanned_frame [overtype::left -experimental test_mode -transparent 1 $spanned_frame $hblock] + set hblock [textblock::frame -ellipsis 0 -type $spacemap -boxlimits $hlims -ansibase $ansibase_header $hval] + #set spanned_frame [overtype::renderspace -experimental test_mode -transparent 1 $spanned_frame $hblock] + set spanned_frame [overtype::block -blockalign left -transparent 1 $spanned_frame $hblock] } @@ -1495,49 +1531,50 @@ namespace eval textblock { } else { #zero span header - #JMN - if 0 { - #old version - sort of works - set h_lines [lrepeat $rowh ""] - set hcell_blank [join $h_lines \n] - set spacemap [list hl " " vl " " tlc " " blc " " trc " " brc " "] - set spacemap [list hl "\uFFFF" vl "\uFFFF" tlc "\uFFFF" blc "\uFFFF" trc "\uFFFF " brc "\uFFFF"] ;# a debug test - set header_frame [textblock::frame -width 0 -type [dict get $ftypes header]\ - -ansibase $ansibase_header \ - -boxlimits $hlims -boxmap $spacemap $hcell_blank\ - ] - append part_header $header_frame\n - } else { - #test version - set hw1 [dict get $o_columnstates $cidx maxwidthheaderseen] ;#headers may be masked by spans, or empty - width may depend more on spans than headers in current column - set hw2 [textblock::width $part_header] ;#widest so far - set hw3 [expr {max($hw1,$hw2)}] - set bw [dict get $o_columnstates $cidx maxwidthbodyseen] - set padwidth [expr {max($hw3,$bw)}] - if {[dict exists $column_width_cache $cidx]} { - set hwidth [dict get $column_width_cache $cidx headerwidth] - set padwidth [expr {max($padwidth,$hwidth)}] + #test hack - wider helps stop the breaks - but leaves junk spaces and ansiresets beyond the rhs border of table + #print function overflow 0 fixes? + #set padwidth 20 + + #This sort of works - but doesn't cater for colspans that don't strictly decrease in size as we go down the header list + #we end up with breaks in some situations + #we don't know the width here, because we would need to look-ahead to see the widest section of frame + #We will adjust the padding below. + #we need the column data width as a minimum or we'll cut lines above from earlier columns + set padwidth [my column_datawidth $cidx -headers 0 -data 1 -cached 1] + + + #under assumption we are building table using L frame method and that horizontal borders are only ever 1 high + # - we can avoid using a frame - but we potentially need to manually adjust for show_hpos show_edge etc + #avoiding frame here is faster.. but not by much (<10%? on textblock::spantest2 ) + if 0 { + #breaks -show_edge 0 + if {$rowpos eq "top" && [dict get $o_opts_table -show_edge]} { + set padheight [expr {$rowh + 2}] + } else { + set padheight [expr {$rowh + 1}] } - - #test hack - wider helps stop the breaks - but leaves junk spaces and ansiresets beyond the rhs border of table - #print function overflow 0 fixes? - set padwidth 20 - - - #set bline [string repeat \uFFFF $colwidth] - set bline [string repeat \uFFFF $padwidth] + set bline [string repeat $TSUB [expr {$padwidth +1}]] + set h_lines [lrepeat $padheight $bline] + set hcell_blank [::join $h_lines \n] + set header_frame $hcell_blank + } else { + set bline [string repeat $TSUB $padwidth] set h_lines [lrepeat $rowh $bline] set hcell_blank [::join $h_lines \n] - set spacemap [list hl "\uFFFF" vll "\uFFFF" vlr "\uFFFF" tlc "\uFFFF" blc "\uFFFF" trc "\uFFFF " brc "\uFFFF"] ;# a debug test # -usecache 1 ok - set header_frame [textblock::frame -width [expr {$padwidth+2}] -type [dict get $ftypes header]\ - -ansibase $ansibase_header \ - -boxlimits $hlims -boxmap $spacemap $hcell_blank\ + #set header_frame [textblock::frame -ellipsis 0 -width [expr {$padwidth+2}] -type [dict get $ftypes header]\ + # -ansibase $ansibase_header \ + # -boxlimits $hlims -boxmap $framesub_map $hcell_blank\ + # ] + #frame borders will never display - so use the simplest frametype and don't apply any ansi + set header_frame [textblock::frame -ellipsis 0 -width [expr {$padwidth+2}] -type ascii\ + -boxlimits $hlims -boxmap $framesub_map $hcell_blank\ ] - append part_header $header_frame\n + } - } + append part_header $header_frame\n + } incr h } @@ -1558,9 +1595,21 @@ namespace eval textblock { ] append part_header $header_frame\n } + set part_header [string trimright $part_header \n] lassign [textblock::size $part_header] _w return_headerwidth _h return_headerheight + + set padline [string repeat $TSUB $return_headerwidth] + set adjusted_lines [list] + foreach ln [split $part_header \n] { + if {[string first $TSUB $ln] >=0} { + lappend adjusted_lines $padline + } else { + lappend adjusted_lines $ln + } + } + set part_header [join $adjusted_lines \n] } - append output $part_header + append output $part_header \n set r 0 set rmax [expr {[llength $cells]-1}] @@ -1589,10 +1638,10 @@ namespace eval textblock { set colidx [lindex [dict keys $o_columndefs] $index_expression] ;#convert possible end-1,2+2 etc expression to >= 0 integer in dict range set opt_col_ansibase [dict get $o_columndefs $colidx -ansibase] ;#ordinary merge of codes already done in configure_column + #set colwidth [my column_width $colidx] set body_ansibase [dict get $o_opts_table -ansibase_body] #set ansibase [punk::ansi::codetype::sgr_merge_singles [list $body_ansibase $opt_col_ansibase]] ;#allow col to override body - set ansibase $body_ansibase$opt_col_ansibase set body_ansiborder [dict get $o_opts_table -ansiborder_body] if {[dict get $o_opts_table -frametype] eq "block"} { #block is the only style where bg colour can fill the frame content area exactly if the L-shaped border elements are styled @@ -1603,44 +1652,65 @@ namespace eval textblock { set border_ansi $body_ansibase$body_ansiborder } set r 0 + set ftblock [expr {[dict get $o_opts_table -frametype] eq "block"}] foreach c $cells { + set ansibase $body_ansibase$opt_col_ansibase set row_ansibase [dict get $o_rowdefs $r -ansibase] #todo - joinleft,joinright,joindown based on opts in args #append output [textblock::frame -boxlimits {vll blc hlb} $c]\n - if {[dict get $o_opts_table -frametype] eq "block"} { - set row_ansibase [dict get $o_rowdefs $r -ansibase] - set row_bg "" - if {$row_ansibase ne ""} { - set row_bg [punk::ansi::codetype::sgr_merge_singles [list $row_ansibase] -filter_fg 1] + set cell_ansibase "" + + set row_bg "" + if {$row_ansibase ne ""} { + set row_bg [punk::ansi::codetype::sgr_merge_singles [list $row_ansibase] -filter_fg 1] + } + set ansiborder_body_col_row $border_ansi$row_bg + set ansiborder_final $ansiborder_body_col_row + #$c will always have ansi resets due to overtype behaviour ? + #todo - review overtype + if {[punk::ansi::ta::detect $c]} { + #if {[textblock::widthtopline $c] == $colwidth} {} + + #use only the last ansi sequence in the cell value + #Filter out foreground and use background for ansiborder override + set parts [punk::ansi::ta::split_codes_single $c] + #we have detected ansi - so there will always be at least 3 parts beginning and ending with pt pt,ansi,pt,ansi...,pt + set codes [list] + foreach {pt cd} $parts { + if {$cd ne ""} { + lappend codes $cd + } } - set ansiborder_body_col_row $border_ansi$row_bg - set ansiborder_final $ansiborder_body_col_row - if 1 { - #$c will always have ansi resets due to overtype::left behaviour - #todo - review overtype - if {[punk::ansi::ta::detect $c]} { - #use only the last ansi sequence in the cell value - #Filter out foreground and use background for ansiborder override - set parts [punk::ansi::ta::split_codes_single $c] - #we have detected ansi - so there will always be at least 3 parts beginning and ending with pt pt,ansi,pt,ansi...,pt - set codes [list] - foreach {pt cd} $parts { - lappend codes $cd + #set takebg [lindex $parts end-1] + #set cell_bg [punk::ansi::codetype::sgr_merge_singles [list $takebg] -filter_fg 1] + set cell_bg [punk::ansi::codetype::sgr_merge_singles $codes -filter_fg 1 -filter_reset 1] + #puts --->[ansistring VIEW $codes] + + if {[punk::ansi::codetype::is_sgr_reset [lindex $codes end]]} { + if {[punk::ansi::codetype::is_sgr_reset [lindex $codes end-1]]} { + #special case double reset at end of content + set cell_ansi_tail [punk::ansi::codetype::sgr_merge_singles $codes] ;#no filters + set ansibase "" + set row_ansibase "" + if {$ftblock} { + set ansiborder_final [punk::ansi::codetype::sgr_merge [list $ansiborder_body_col_row] -filter_bg 1] } - #set takebg [lindex $parts end-1] - #set cell_bg [punk::ansi::codetype::sgr_merge_singles [list $takebg] -filter_fg 1] - set cell_bg [punk::ansi::codetype::sgr_merge_singles $codes -filter_fg 1] - - #puts "-->>> [ansistring VIEW $cell_bg] <<<--" + set cell_ansibase $cell_ansi_tail + } else { + #single trailing reset in content + set cell_ansibase "" ;#cell doesn't contribute to frame's ansibase + } + } else { + if {$ftblock} { + #no resets use cells bg to extend to the border - only for block frames set ansiborder_final $ansiborder_body_col_row$cell_bg - #JMN } + set cell_ansibase $cell_bg } - } else { - set ansiborder_body_col_row $border_ansi - set ansiborder_final $ansiborder_body_col_row } - set ansibase_final $ansibase$row_ansibase + + + set ansibase_final $ansibase$row_ansibase$cell_ansibase if {$r == 0} { if {$r == $rmax} { @@ -1665,7 +1735,7 @@ namespace eval textblock { set blims [struct::set difference $blims [dict get $::textblock::class::table_edge_parts top$opt_posn] ] } } - set rowframe [textblock::frame -type [dict get $ftypes body] -ansibase $ansibase_final -ansiborder $ansiborder_final -boxlimits $blims -boxmap $bmap -joins $joins $c] + set rowframe [textblock::frame -type [dict get $ftypes body] -width [expr {$colwidth+2}] -blockalign $col_blockalign -ansibase $ansibase_final -ansiborder $ansiborder_final -boxlimits $blims -boxmap $bmap -joins $joins $c] set return_bodywidth [textblock::width $rowframe] append part_body $rowframe \n } else { @@ -1683,7 +1753,7 @@ namespace eval textblock { set blims [struct::set difference $blims [dict get $::textblock::class::table_edge_parts middle$opt_posn] ] } } - append part_body [textblock::frame -type [dict get $ftypes body] -ansibase $ansibase_final -ansiborder $ansiborder_final -boxlimits $blims -boxmap $bmap -joins $joins $c]\n + append part_body [textblock::frame -type [dict get $ftypes body] -width [expr {$colwidth+2}] -blockalign $col_blockalign -ansibase $ansibase_final -ansiborder $ansiborder_final -boxlimits $blims -boxmap $bmap -joins $joins $c]\n } incr r } @@ -1691,7 +1761,6 @@ namespace eval textblock { if {![llength $cells]} { set joins [lremove $joins [lsearch $joins down*]] #we need to know the width of the column to setup the empty cell properly - #(we didn't need it above because get_column_cells_by_index returned values of the correct width) #even if no header displayed - we should take account of any defined column widths set colwidth [my column_width $index_expression] @@ -1751,6 +1820,14 @@ namespace eval textblock { set RST [punk::ansi::a] set ansibase_body [dict get $o_opts_table -ansibase_body] set ansibase_col [dict get $cdef -ansibase] + set textalign [dict get $cdef -textalign] + switch -- $textalign { + left {set pad right} + right {set pad left} + default { + set pad "centre" ;#todo? + } + } set ansibase_header [dict get $o_opts_table -ansibase_header] @@ -1799,15 +1876,18 @@ namespace eval textblock { set header_underlay [lrepeat $header_maxdataheight $hdr_line_blank] set header_underlay $ansibase_header[join $header_underlay \n] if {$hdr ne ""} { - dict lappend output headers [overtype::left -experimental test_mode $header_underlay $ansibase_header$hdr] + dict lappend output headers [overtype::renderspace -experimental test_mode $header_underlay $ansibase_header$hdr] } else { dict lappend output headers $header_underlay } } - set colwidth [my column_width $cidx] - set cell_line_blank [string repeat " " $colwidth] + #set colwidth [my column_width $cidx] + #set cell_line_blank [string repeat " " $colwidth] + set datawidth [my column_datawidth $cidx -headers 0 -footers 0 -data 1 -cached 1] + set cell_line_blank [string repeat " " $datawidth] + set items [dict get $o_columndata $cidx] @@ -1858,10 +1938,12 @@ namespace eval textblock { set cval_block [join $cval_lines \n] #TODO! fix overtype library - #set cell [overtype::left -experimental test_mode $cell_ansibase$cell_blank$RST $cval_block] - #set cell [overtype::left -experimental test_mode $cell_blank $cval_block] + #set cell [overtype::renderspace -experimental test_mode $cell_ansibase$cell_blank$RST $cval_block] + #set cell [overtype::renderspace -experimental test_mode $cell_blank $cval_block] + + #set cell [textblock::pad $cval_block -width $colwidth -padchar " " -within_ansi 0 -which right] - set cell [textblock::pad $cval_block -width $colwidth -padchar " " -within_ansi 0 -which right] + set cell [textblock::pad $cval_block -width $datawidth -padchar " " -within_ansi 1 -which $pad] #set cell [textblock::pad $cval_block -width $colwidth -padchar " " -within_ansi 0 -which left] dict lappend output cells $cell @@ -2077,10 +2159,11 @@ namespace eval textblock { -headers 0\ -footers 0\ -data 1\ + -cached 1\ ] dict for {k v} $args { switch -- $k { - -headers - -footers - -data {} + -headers - -footers - -data - -cached {} default { error "column_datawidth unrecognised flag '$k'. Known flags: [dict keys $defaults]" } @@ -2092,6 +2175,24 @@ namespace eval textblock { if {$cidx eq ""} { return } + + if {[dict get $opts -cached]} { + set hwidest 0 + set bwidest 0 + set fwidest 0 + if {[dict get $opts -headers]} { + set hwidest [dict get $o_columnstates $cidx maxwidthheaderseen] + } + if {[dict get $opts -data]} { + set bwidest [dict get $o_columnstates $cidx maxwidthbodyseen] + } + if {[dict get $opts -footers]} { + #TODO! + #set bwidest [dict get $o_columnstates $cidx maxwidthfooterseen] + } + return [expr {max($hwidest,$bwidest,$fwidest)}] + } + #assert cidx is >=0 integer in valid range of keys for o_columndefs set values [list] if {[dict get $opts -headers]} { @@ -2213,12 +2314,12 @@ namespace eval textblock { set table $nextcol set height [textblock::height $table] ;#only need to get height once at start } else { - set nextcol [textblock::join [textblock::block $padwidth $height "\uFFFF"] $nextcol] - set table [overtype::left -overflow 1 -experimental test_mode -transparent \uFFFF $table $nextcol] + set nextcol [textblock::join [textblock::block $padwidth $height $TSUB] $nextcol] + set table [overtype::renderspace -overflow 1 -experimental test_mode -transparent $TSUB $table $nextcol] #JMN #set nextcol [textblock::join [textblock::block $padwidth $height "\uFFFF"] $nextcol] - #set table [overtype::left -overflow 1 -experimental test_mode -transparent \uFFFF $table $nextcol] + #set table [overtype::renderspace -overflow 1 -experimental test_mode -transparent \uFFFF $table $nextcol] } incr padwidth $bodywidth incr colposn @@ -2266,6 +2367,21 @@ namespace eval textblock { $t configure -show_header 1 -ansiborder_header [a+ cyan] return $t } + + #more complex colspans + proc spantest2 {} { + set t [list_as_table 5 {a b c d e aa bb cc dd ee X Y} -return object] + $t configure_column 0 -headers {span3 span4 span5/5 "span-all etc blah 123 hmmmmm" span2} + $t configure_column 0 -header_colspans {3 4 1 all 1} + $t configure_column 2 -headers {"" "" "" "" c2span2} + $t configure_column 1 -header_colspans {0 0 2 0 1} + $t configure_column 2 -header_colspans {0 0 0 0 2} + $t configure -show_header 1 -ansiborder_header [a+ cyan] + return $t + } + + + proc periodic {args} { #For an impressive interactive terminal app (javascript) # see: https://github.com/spirometaxas/periodic-table-cli @@ -2607,6 +2723,19 @@ namespace eval textblock { } return [punk::char::ansifreestring_width $textblock] } + #when we know the block is uniform in width - just examine topline + proc widthtopline {textblock} { + set firstnl [string first \n $textblock] + if {$firstnl >= 0} { + set tl [string range $textblock 0 $firstnl] + } else { + set tl $textblock + } + if {[punk::ansi::ta::detect $tl]} { + set tl [punk::ansi::stripansi $tl] + } + return [punk::char::ansifreestring_width $tl] + } #uses tcl's string length on each line. Length will include chars in ansi codes etc - this is not a 'width' determining function. proc string_length_line_max textblock { tcl::mathfunc::max {*}[lmap v [split $textblock \n] {string length $v}] @@ -2945,7 +3074,7 @@ namespace eval textblock { return $t } - proc pad_test2 {blocklist} { + proc pad_test_blocklist {blocklist} { set b 0 set blockinfo [dict create] foreach block $blocklist { @@ -2989,7 +3118,7 @@ namespace eval textblock { set b2 "[a+ green bold][textblock::block 4 4 x]\n[a+ Green]" set b3 "[textblock::testblock 4 rainbow]\n[a]" set b4 "[textblock::testblock 4 rainbow]\n[a+ Green]" - set t [textblock::pad_test2 [list $b1 $b2 $b3 $b4]] + set t [textblock::pad_test_blocklist [list $b1 $b2 $b3 $b4]] } @@ -3039,6 +3168,7 @@ namespace eval textblock { } #for joining 'rendered' blocks of plain or ansi text. Being 'rendered' means they are without ansi movement sequences as these have been processed + #they may however still be 'ragged' ie differing line lengths proc ::textblock::join {args} { #lassign [punk::lib::opts_values { # blocks -type string -multiple 1 @@ -4396,9 +4526,27 @@ namespace eval textblock { } variable frame_cache set out "" + if {[catch { + set termwidth [dict get [punk::console::get_size] columns] + }]} { + set termwidth 80 + } + dict for {k v} $frame_cache { lassign $v _f frame _used used - append out [textblock::join $k " " $frame " " $used]\n + #set fwidth [textblock::widthtopline $frame] + #review - are cached frames uniform width lines? + set fwidth [textblock::width $frame] + set frameinfo "$k used:$used " + set allinone_width [expr {[string length $frameinfo] + $fwidth}] + if {$allinone_width >= $termwidth} { + #split across 2 lines + append out "$frameinfo\n" + append out $frame \n + } else { + append out [textblock::join $frameinfo $frame]\n + } + append out \n ;# frames used to build tables often have joins - keep a line in between for clarity } if {$action eq "clear"} { set frame_cache [dict create] @@ -4455,16 +4603,18 @@ namespace eval textblock { -height ""\ -ansiborder ""\ -ansibase ""\ - -align "left"\ + -blockalign "centre"\ + -textalign "left"\ -ellipsis 1\ -usecache 1\ -buildcache 1\ ] + #todo -blockalignbias -textalignbias? #use -buildcache 1 with -usecache 0 for debugging cache issues so we can inspect using textblock::frame_cache set opts [dict merge $defaults $arglist] foreach {k v} $opts { switch -- $k { - -etabs - -type - -boxlimits - -boxmap - -joins - -title - -subtitle - -width - -height - -ansiborder - -ansibase - -align - -ellipsis - -usecache - -buildcache {} + -etabs - -type - -boxlimits - -boxmap - -joins - -title - -subtitle - -width - -height - -ansiborder - -ansibase - -blockalign - -textalign - -ellipsis - -usecache - -buildcache {} default { error "frame option '$k' not understood. Valid options are [dict keys $defaults]" } @@ -4477,7 +4627,7 @@ namespace eval textblock { set opt_joins [dict get $opts -joins] set opt_boxmap [dict get $opts -boxmap] set opt_usecache [dict get $opts -usecache] - set opt_buildcache [dict get $opts -buildcache] + set opt_buildcache [dict get $opts -buildcache] set usecache $opt_usecache ;#may need to override set buildcache $opt_buildcache set custom_keys [list hl hlt hlb vl vll vlr tlc trc blc brc] @@ -4584,16 +4734,24 @@ namespace eval textblock { set opt_width [dict get $opts -width] set opt_height [dict get $opts -height] # -- --- --- --- --- --- - set opt_align [dict get $opts -align] - set opt_align [string tolower $opt_align] - switch -- $opt_align { + set opt_blockalign [dict get $opts -blockalign] + switch -- $opt_blockalign { left - right - centre - center {} default { - error "frame option -align must be left|right|centre|center - received: $opt_align" + error "frame option -blockalign must be left|right|centre|center - received: $opt_blockalign" } } #these are all valid commands for overtype:: # -- --- --- --- --- --- + set opt_textalign [dict get $opts -textalign] + switch -- $opt_textalign { + left - right - centre - center {} + default { + error "frame option -textalign must be left|right|centre|center - received: $opt_textalign" + } + } + # -- --- --- --- --- --- + set opt_ansiborder [dict get $opts -ansiborder] set opt_ansibase [dict get $opts -ansibase] ;#experimental set opt_ellipsis [dict get $opts -ellipsis] @@ -4611,7 +4769,7 @@ namespace eval textblock { } } set contents [string map [list \r\n \n] $contents] - set actual_contentwidth [textblock::width $contents] + set actual_contentwidth [textblock::width $contents] ;#length of longest line in contents (contents can be ragged) set actual_contentheight [textblock::height $contents] } else { set actual_contentwidth 0 @@ -4627,396 +4785,471 @@ namespace eval textblock { } if {$opt_width eq ""} { - set contentwidth $content_or_title_width + set frame_inner_width $content_or_title_width } else { - set contentwidth [expr {max(0,$opt_width - 2)}] ;#default + set frame_inner_width [expr {max(0,$opt_width - 2)}] ;#default } if {$opt_height eq ""} { - set contentheight $actual_contentheight + set frame_inner_height $actual_contentheight } else { - set contentheight [expr {max(0,$opt_height -2)}] ;#default + set frame_inner_height [expr {max(0,$opt_height -2)}] ;#default } - if {$contentheight == 0 && $contentwidth == 0} { + if {$frame_inner_height == 0 && $frame_inner_width == 0} { set has_contents 0 } #todo - render it with vertical overflow so we can process ansi moves? #set linecount [textblock::height $contents] - set linecount $contentheight + set linecount $frame_inner_height # -- --- --- --- --- --- --- --- --- variable frame_cache - #review - custom frame affects contentwidth - exclude from caching? - #set cache_key [concat $arglist $contentwidth $contentheight] - set hashables [concat $arglist $contentwidth $contentheight] + #review - custom frame affects frame_inner_width - exclude from caching? + #set cache_key [concat $arglist $frame_inner_width $frame_inner_height] + set hashables [concat $arglist $frame_inner_width $frame_inner_height] package require md5 set hash [md5::md5 -hex $hashables] - set cache_key "$hash-$contentwidth-$contentheight-actualcontentwidth:$actual_contentwidth" - set TSUB \u1FFF; #needs to be different to that used in table construction + set cache_key "$hash-$frame_inner_width-$frame_inner_height-actualcontentwidth:$actual_contentwidth" + #should be in a unicode private range different to that used in table construction + #e.g BMP PUA U+E000 -> U+F8FF - although this is commonly used for example by nerdfonts + #also supplementary private use blocks + #however these display double wide on for example cmd terminal despite having wcswidth 1 (makes layout debugging difficult) + #U+F0000 -> U+FFFD + #U+100000 -> U+10FFFD + #FSUB options: \uf0ff \uF1FF \uf2ff (no nerdfont glyphs - should look like standard replacement char) \uF2DD (circular thingy) + #should be something someone is unlikely to use as part of a custom frame character. + #ideally a glyph that doesn't auto-expand into whitespace and is countable when in a string (narrower is better) + #As nerdfont glyphs tend to be mostly equal height & width - circular glyphs tend to be more distinguishable in a string + #terminal settings may need to be adjusted to stop auto glyph resizing - a rather annoying misfeature that some people seem to think they like. + #e.g in wezterm config: allow_square_glyphs_to_overflow_width = "Never" + #review - we could consider wasting a few cycles to check for a conflict and use a different FSUB + set FSUB \uF2DD + #this occurs commonly in table building with colspans - review - if {$actual_contentwidth > $contentwidth || $actual_contentheight != $contentheight} { + if {$actual_contentwidth > $frame_inner_width || $actual_contentheight != $frame_inner_height} { set usecache 0 #set buildcache 0 ;#comment out for debug/analysis so we can see - set cache_key [a+ Web-red]$cache_key[a] + #puts "--->> frame_inner_width:$frame_inner_width actual_contentwidth:$actual_contentwidth contents: '$contents'" + set cache_key [a+ Web-red web-white]$cache_key[a] } - if {$buildcache && $actual_contentwidth < $contentwidth} { + if {$buildcache && $actual_contentwidth < $frame_inner_width} { #colourise cache_key to warn if {$actual_contentwidth == 0} { - #we can still substitue with right length + #we can still substitute with right length set cache_key [a+ Web-steelblue web-black]$cache_key[a] } else { - #actual_contentwidth is shorter - rather than choose an alignment and pad - we will opt out of caching - set usecache 0 - set cache_key [a+ Web-orange web-black]$cache_key[a] + #actual_contentwidth is narrower than frame - check template's patternwidth + if {[dict exists $frame_cache $cache_key]} { + set cache_patternwidth [dict get $frame_cache $cache_key patternwidth] + } else { + set cache_patternwidth [$actual_contentwidth] + } + if {$actual_contentwidth < $cache_patternwidth} { + set usecache 0 + set cache_key [a+ Web-orange web-black]$cache_key[a] + } elseif {$actual_contentwidth == $cache_patternwidth} { + #set usecache 1 + } else { + #actual_contentwidth > pattern + set usecache 0 + set cache_key [a+ Web-red web-black]$cache_key[a] + } } } + + #JMN debug + #set usecache 0 + + set is_cached 0 if {$usecache && [dict exists $frame_cache $cache_key]} { + set cache_patternwidth [dict get $frame_cache $cache_key patternwidth] set template [dict get $frame_cache $cache_key frame] set used [dict get $frame_cache $cache_key used] - dict set frame_cache $cache_key used [expr {$used+1}] + dict set frame_cache $cache_key used [expr {$used+1}] ;#update existing record + set is_cached 1 - set resultlines [list] - set overwritable [string repeat $TSUB $contentwidth] - set blankset [string repeat " " $contentwidth] - set contentindex 0 - set clines [split $contents \n] - if {$actual_contentwidth == 0} { - foreach tline [split $template \n] { - if {[string first $TSUB $tline] >= 0} { - lappend resultlines [string map [list $overwritable $blankset] $tline] - incr contentindex - } else { - lappend resultlines $tline - } - } - } else { - foreach tline [split $template \n] { - if {[string first $TSUB $tline] >= 0} { - #set sublen [string length [lindex [regexp -inline "\[^$TSUB]*($TSUB*).*" $tline] 1]] - #set overwritable [string repeat $TSUB $sublen] - lappend resultlines [string map [list $overwritable [lindex $clines $contentindex]] $tline] - incr contentindex - } else { - lappend resultlines $tline - } - } - } - return [::join $resultlines \n] } # -- --- --- --- --- --- --- --- --- - - set rst [a] - #set column [string repeat " " $contentwidth] ;#default - may need to override for custom frame - set underlayline [string repeat " " $contentwidth] - set underlay [::join [lrepeat $linecount $underlayline] \n] - set cache_underlayline [string repeat $TSUB $contentwidth] - set cache_underlay [::join [lrepeat $linecount $cache_underlayline] \n] - - set vll_width 1 ;#default for all except custom (printing width) - set vlr_width 1 - - set framedef [textblock::framedef $framedef -joins $opt_joins] - dict with framedef {} ;#extract vll,hlt,tlc etc vars - - #puts "---> $opt_boxmap" - dict for {boxelement sub} $opt_boxmap { - if {$boxelement eq "vl"} { - set vll $sub - set vlr $sub - set hl $sub - } elseif {$boxelement eq "hl"} { - set hlt $sub - set hlb $sub - set hl $sub - } else { - set $boxelement $sub + if {!$is_cached} { + set rst [a] + #set column [string repeat " " $frame_inner_width] ;#default - may need to override for custom frame + set underlayline [string repeat " " $frame_inner_width] + set underlay [::join [lrepeat $linecount $underlayline] \n] + + + set vll_width 1 ;#default for all except custom (printing width) + set vlr_width 1 + + set framedef [textblock::framedef $framedef -joins $opt_joins] + dict with framedef {} ;#extract vll,hlt,tlc etc vars + + #puts "---> $opt_boxmap" + dict for {boxelement sub} $opt_boxmap { + if {$boxelement eq "vl"} { + set vll $sub + set vlr $sub + set hl $sub + } elseif {$boxelement eq "hl"} { + set hlt $sub + set hlb $sub + set hl $sub + } else { + set $boxelement $sub + } } - } - switch -- $frameset { - custom { + switch -- $frameset { + custom { - #if no ansi, these widths are reasonable to maintain in grapheme_width_cached indefinitely - set vll_width [punk::ansi::printing_length $vll] - set hlb_width [punk::ansi::printing_length $hlb] - set hlt_width [punk::ansi::printing_length $hlt] + #if no ansi, these widths are reasonable to maintain in grapheme_width_cached indefinitely + set vll_width [punk::ansi::printing_length $vll] + set hlb_width [punk::ansi::printing_length $hlb] + set hlt_width [punk::ansi::printing_length $hlt] - set vlr_width [punk::ansi::printing_length $vlr] + set vlr_width [punk::ansi::printing_length $vlr] - set tlc_width [punk::ansi::printing_length $tlc] - set trc_width [punk::ansi::printing_length $trc] - set blc_width [punk::ansi::printing_length $blc] - set brc_width [punk::ansi::printing_length $brc] + set tlc_width [punk::ansi::printing_length $tlc] + set trc_width [punk::ansi::printing_length $trc] + set blc_width [punk::ansi::printing_length $blc] + set brc_width [punk::ansi::printing_length $brc] - set framewidth [expr {$contentwidth + 2}] ;#reverse default assumption - if {$opt_width eq ""} { - #width wasn't specified - so user is expecting frame to adapt to title/contents - #content shouldn't truncate because of extra wide frame - set contentwidth $content_or_title_width - set tbarwidth [expr {$content_or_title_width + 2 - $tlc_width - $trc_width - 2 + $vll_width + $vlr_width}] ;#+/2's for difference between border element widths and standard element single-width - set bbarwidth [expr {$content_or_title_width + 2 - $blc_width - $brc_width - 2 + $vll_width + $vlr_width}] - } else { - set contentwidth [expr $opt_width - $vll_width - $vlr_width] ;#content may be truncated - set tbarwidth [expr {$opt_width - $tlc_width - $trc_width}] - set bbarwidth [expr {$opt_width - $blc_width - $brc_width}] - } - #set column [string repeat " " $contentwidth] - set underlayline [string repeat " " $contentwidth] - set underlay [::join [lrepeat $linecount $underlayline] \n] - #cache? + set framewidth [expr {$frame_inner_width + 2}] ;#reverse default assumption + if {$opt_width eq ""} { + #width wasn't specified - so user is expecting frame to adapt to title/contents + #content shouldn't truncate because of extra wide frame + #review - punk::console::get_size ? wrapping? quite hard to support with colspans + set frame_inner_width $content_or_title_width + set tbarwidth [expr {$content_or_title_width + 2 - $tlc_width - $trc_width - 2 + $vll_width + $vlr_width}] ;#+/2's for difference between border element widths and standard element single-width + set bbarwidth [expr {$content_or_title_width + 2 - $blc_width - $brc_width - 2 + $vll_width + $vlr_width}] + } else { + set frame_inner_width [expr $opt_width - $vll_width - $vlr_width] ;#content may be truncated + set tbarwidth [expr {$opt_width - $tlc_width - $trc_width}] + set bbarwidth [expr {$opt_width - $blc_width - $brc_width}] + } + #set column [string repeat " " $frame_inner_width] + set underlayline [string repeat " " $frame_inner_width] + set underlay [::join [lrepeat $linecount $underlayline] \n] + #cache? - if {$hlt_width == 1} { - set tbar [string repeat $hlt $tbarwidth] - } else { - #possibly mixed width chars that make up hlt - string range won't get width right - set blank [string repeat " " $tbarwidth] - if {$hlt_width > 0} { - set count [expr {($tbarwidth / $hlt_width) + 1}] + if {$hlt_width == 1} { + set tbar [string repeat $hlt $tbarwidth] } else { - set count 0 + #possibly mixed width chars that make up hlt - string range won't get width right + set blank [string repeat " " $tbarwidth] + if {$hlt_width > 0} { + set count [expr {($tbarwidth / $hlt_width) + 1}] + } else { + set count 0 + } + set tbar [string repeat $hlt $count] + #set tbar [string range $tbar 0 $tbarwidth-1] + set tbar [overtype::left -overflow 0 -exposed1 " " -exposed2 " " $blank $tbar];#spaces for exposed halves of 2w chars instead of default replacement character } - set tbar [string repeat $hlt $count] - #set tbar [string range $tbar 0 $tbarwidth-1] - set tbar [overtype::left -overflow 0 -exposed1 " " -exposed2 " " $blank $tbar];#spaces for exposed halves of 2w chars instead of default replacement character - } - if {$hlb_width == 1} { - set bbar [string repeat $hlb $bbarwidth] - } else { - set blank [string repeat " " $bbarwidth] - if {$hlb_width > 0} { - set count [expr {($bbarwidth / $hlb_width) + 1}] + if {$hlb_width == 1} { + set bbar [string repeat $hlb $bbarwidth] } else { - set count 0 + set blank [string repeat " " $bbarwidth] + if {$hlb_width > 0} { + set count [expr {($bbarwidth / $hlb_width) + 1}] + } else { + set count 0 + } + set bbar [string repeat $hlb $count] + #set bbar [string range $bbar 0 $bbarwidth-1] + set bbar [overtype::left -overflow 0 -exposed1 " " -exposed2 " " $blank $bbar] } - set bbar [string repeat $hlb $count] - #set bbar [string range $bbar 0 $bbarwidth-1] - set bbar [overtype::left -overflow 0 -exposed1 " " -exposed2 " " $blank $bbar] + } + altg { + set tbar [string repeat $hlt $frame_inner_width] + set tbar [cd::groptim $tbar] + set bbar [string repeat $hlb $frame_inner_width] + set bbar [cd::groptim $bbar] + } + default { + set tbar [string repeat $hlt $frame_inner_width] + set bbar [string repeat $hlb $frame_inner_width] + } } - altg { - set tbar [string repeat $hlt $contentwidth] - set tbar [cd::groptim $tbar] - set bbar [string repeat $hlb $contentwidth] - set bbar [cd::groptim $bbar] + + set leftborder 0 + set rightborder 0 + set topborder 0 + set bottomborder 0 + # hl - hlt - hlb - vl - vll - vlr - tlc - trc - blc - brc {} + #puts "----->$exact_boxlimits" + foreach lim $exact_boxlimits { + switch -- $lim { + hlt { + set topborder 1 + } + hlb { + set bottomborder 1 + } + vll { + set leftborder 1 + } + vlr { + set rightborder 1 + } + tlc { + set topborder 1 + set leftborder 1 + } + trc { + set topborder 1 + set rightborder 1 + } + blc { + set bottomborder 1 + set leftborder 1 + } + brc { + set bottomborder 1 + set rightborder 1 + } + } } - default { - set tbar [string repeat $hlt $contentwidth] - set bbar [string repeat $hlb $contentwidth] - + if {$opt_width ne "" && $opt_width < 2} { + set rightborder 0 + } + #keep lhs/rhs separate? can we do vertical text on sidebars? + set lhs [string repeat $vll\n $linecount] + set lhs [string range $lhs 0 end-1] + set rhs [string repeat $vlr\n $linecount] + set rhs [string range $rhs 0 end-1] + + + if {$opt_ansiborder ne ""} { + set tbar $opt_ansiborder$tbar$rst + set bbar $opt_ansiborder$bbar$rst + set tlc $opt_ansiborder$tlc$rst + set trc $opt_ansiborder$trc$rst + set blc $opt_ansiborder$blc$rst + set brc $opt_ansiborder$brc$rst + set lhs $opt_ansiborder$lhs$rst ;#wrap the whole block and let textblock::join figure it out + set rhs $opt_ansiborder$rhs$rst } - } - set leftborder 0 - set rightborder 0 - set topborder 0 - set bottomborder 0 - # hl - hlt - hlb - vl - vll - vlr - tlc - trc - blc - brc {} - #puts "----->$exact_boxlimits" - foreach lim $exact_boxlimits { - switch -- $lim { - hlt { - set topborder 1 - } - hlb { - set bottomborder 1 - } - vll { - set leftborder 1 - } - vlr { - set rightborder 1 - } - tlc { - set topborder 1 - set leftborder 1 - } - trc { - set topborder 1 - set rightborder 1 - } - blc { - set bottomborder 1 - set leftborder 1 - } - brc { - set bottomborder 1 - set rightborder 1 + #boxlimits used for partial borders in table generation + set all_exact_boxlimits [list vll vlr hlt hlb tlc blc trc brc] + set unspecified_limits [struct::set difference $all_exact_boxlimits $exact_boxlimits] + foreach lim $unspecified_limits { + switch -- $lim { + vll { + set blank_vll [string repeat " " $vll_width] + set lhs [string repeat $blank_vll\n $linecount] + set lhs [string range $lhs 0 end-1] + } + vlr { + set blank_vlr [string repeat " " $vlr_width] + set rhs [string repeat $blank_vlr\n $linecount] + set rhs [string range $rhs 0 end-1] + } + hlt { + set bar_width [punk::ansi::printing_length $tbar] + set tbar [string repeat " " $bar_width] + } + tlc { + set tlc_width [punk::ansi::printing_length $tlc] + set tlc [string repeat " " $tlc_width] + } + trc { + set trc_width [punk::ansi::printing_length $trc] + set trc [string repeat " " $trc_width] + } + hlb { + set bar_width [punk::ansi::printing_length $bbar] + set bbar [string repeat " " $bar_width] + } + blc { + set blc_width [punk::ansi::printing_length $blc] + set blc [string repeat " " $blc_width] + } + brc { + set brc_width [punk::ansi::printing_length $brc] + set brc [string repeat " " $brc_width] + } } } - } - if {$opt_width ne "" && $opt_width < 2} { - set rightborder 0 - } - #keep lhs/rhs separate? can we do vertical text on sidebars? - set lhs [string repeat $vll\n $linecount] - set lhs [string range $lhs 0 end-1] - set rhs [string repeat $vlr\n $linecount] - set rhs [string range $rhs 0 end-1] - - - if {$opt_ansiborder ne ""} { - set tbar $opt_ansiborder$tbar$rst - set bbar $opt_ansiborder$bbar$rst - set tlc $opt_ansiborder$tlc$rst - set trc $opt_ansiborder$trc$rst - set blc $opt_ansiborder$blc$rst - set brc $opt_ansiborder$brc$rst - set lhs $opt_ansiborder$lhs$rst ;#wrap the whole block and let textblock::join figure it out - set rhs $opt_ansiborder$rhs$rst - } - #boxlimits used for partial borders in table generation - set all_exact_boxlimits [list vll vlr hlt hlb tlc blc trc brc] - set unspecified_limits [struct::set difference $all_exact_boxlimits $exact_boxlimits] - foreach lim $unspecified_limits { - switch -- $lim { - vll { - set blank_vll [string repeat " " $vll_width] - set lhs [string repeat $blank_vll\n $linecount] - set lhs [string range $lhs 0 end-1] - } - vlr { - set blank_vlr [string repeat " " $vlr_width] - set rhs [string repeat $blank_vlr\n $linecount] - set rhs [string range $rhs 0 end-1] - } - hlt { - set bar_width [punk::ansi::printing_length $tbar] - set tbar [string repeat " " $bar_width] + if {$opt_title ne ""} { + set topbar [overtype::centre -overflow 0 -exposed1 " " -exposed2 " " -ellipsis 1 -bias left $tbar $opt_title] ;#overtype supports gx0 on/off + } else { + set topbar $tbar + } + if {$opt_subtitle ne ""} { + set bottombar [overtype::centre -overflow 0 -exposed1 " " -exposed2 " " -ellipsis 1 -bias left $bbar $opt_subtitle] ;#overtype supports gx0 on/off + } else { + set bottombar $bbar + } + if {$opt_ansibase eq ""} { + set rstbase [a] + } else { + set rstbase [a]$opt_ansibase + } + + if {$opt_title ne ""} { + #title overrides -boxlimits for topborder + set topborder 1 + } + set fs "" + set fscached "" + set cache_patternwidth 0 + #todo - output nothing except maybe newlines depending on if opt_height 0 and/or opt_width 0? + if {$topborder} { + if {$leftborder && $rightborder} { + append fs $tlc$topbar$trc + } else { + if {$leftborder} { + append fs $tlc$topbar + } elseif {$rightborder} { + append fs $topbar$trc + } else { + append fs $topbar + } } - tlc { - set tlc_width [punk::ansi::printing_length $tlc] - set tlc [string repeat " " $tlc_width] + } + append fscached $fs + if {$has_contents || $opt_height > 2} { + #if {$topborder && $fs ne "xx"} { + # append fs \n + #} + if {$topborder} { + append fs \n + append fscached \n } - trc { - set trc_width [punk::ansi::printing_length $trc] - set trc [string repeat " " $trc_width] + switch -- $opt_textalign { + right {set pad "left"} + left {set pad "right"} + default {set pad $opt_textalign} } - hlb { - set bar_width [punk::ansi::printing_length $bbar] - set bbar [string repeat " " $bar_width] + #set textaligned_contents [textblock::pad $contents -width $actual_contentwidth -which $pad -within_ansi 1] + #set inner [overtype::block -blockalign $opt_blockalign -ellipsis $opt_ellipsis $opt_ansibase$underlay$rstbase $opt_ansibase$textaligned_contents] + + set cache_contentline [string repeat $FSUB $actual_contentwidth] + set cache_patternwidth $actual_contentwidth + set cache_contentpattern [::join [lrepeat $linecount $cache_contentline] \n] + set cache_inner [overtype::block -blockalign $opt_blockalign -ellipsis $opt_ellipsis $opt_ansibase$underlay$rstbase $opt_ansibase$cache_contentpattern] + #after overtype::block - our actual patternwidth may be less + set cache_patternwidth [string length [lindex [regexp -inline "\[^$FSUB]*(\[$FSUB]*).*" $cache_inner] 1]] + + if {$leftborder && $rightborder} { + #set bodyparts [list $lhs $inner $rhs] + set cache_bodyparts [list $lhs $cache_inner $rhs] + } else { + if {$leftborder} { + #set bodyparts [list $lhs $inner] + set cache_bodyparts [list $lhs $cache_inner] + } elseif {$rightborder} { + #set bodyparts [list $inner $rhs] + set cache_bodyparts [list $cache_inner $rhs] + } else { + #set bodyparts [list $inner] + set cache_bodyparts [list $cache_inner] + } } - blc { - set blc_width [punk::ansi::printing_length $blc] - set blc [string repeat " " $blc_width] + #set body [textblock::join -- {*}$bodyparts] + set cache_body [textblock::join -- {*}$cache_bodyparts] + append fscached $cache_body + #append fs $body + } + + if {$opt_height eq "" || $opt_height > 1} { + if {$opt_subtitle ne ""} { + #subtitle overrides boxlimits for bottomborder + set bottomborder 1 } - brc { - set brc_width [punk::ansi::printing_length $brc] - set brc [string repeat " " $brc_width] + if {$bottomborder} { + if {($topborder & $fs ne "xx" ) || ($has_contents || $opt_height > 2)} { + #append fs \n + append fscached \n + } + if {$leftborder && $rightborder} { + #append fs $blc$bottombar$brc + append fscached $blc$bottombar$brc + } else { + if {$leftborder} { + #append fs $blc$bottombar + append fscached $blc$bottombar + } elseif {$rightborder} { + #append fs $bottombar$brc + append fscached $bottombar$brc + } else { + #append fs $bottombar + append fscached $bottombar + } + } } } - } + set template $fscached + ;#end !$is_cached + } - if {$opt_title ne ""} { - set topbar [overtype::centre -overflow 0 -exposed1 " " -exposed2 " " -ellipsis 1 -bias left $tbar $opt_title] ;#overtype supports gx0 on/off + #use the same mechanism to build the final frame - whether from cache or template + if {$actual_contentwidth == 0} { + set fs [string map [list $FSUB " "] $template] } else { - set topbar $tbar - } - if {$opt_subtitle ne ""} { - set bottombar [overtype::centre -overflow 0 -exposed1 " " -exposed2 " " -ellipsis 1 -bias left $bbar $opt_subtitle] ;#overtype supports gx0 on/off - } else { - set bottombar $bbar - } - if {$opt_ansibase eq ""} { - set rstbase [a] - } else { - set rstbase [a]$opt_ansibase - } + set resultlines [list] + set overwritable [string repeat $FSUB $cache_patternwidth] + set contentindex 0 + switch -- $opt_textalign { + left {set pad right} + right {set pad left} + default {set pad $opt_textalign} + } - if {$opt_title ne ""} { - #title overrides -boxlimits for topborder - set topborder 1 - } - set fs "" - set fscached "" - #todo - output nothing except maybe newlines depending on if opt_height 0 and/or opt_width 0? - if {$topborder} { - if {$leftborder && $rightborder} { - append fs $tlc$topbar$trc - } else { - if {$leftborder} { - append fs $tlc$topbar - } elseif {$rightborder} { - append fs $topbar$trc - } else { - append fs $topbar - } + #review + if {[string is integer -strict $opt_height] && $actual_contentheight < ($opt_height -2)} { + set diff [expr {($opt_height -2) - $actual_contentheight}] + append contents [::join [lrepeat $diff \n] ""] } - } - append fscached $fs - if {$has_contents || $opt_height > 2} { - #if {$topborder && $fs ne "xx"} { - # append fs \n - #} - if {$topborder} { - append fs \n - append fscached \n + + set paddedcontents [textblock::pad $contents -which $pad -within_ansi 1] ;#autowidth to width of content (should match cache_patternwidth) + set paddedwidth [textblock::widthtopline $paddedcontents] + + #review - horizontal truncation + if {$paddedwidth > $cache_patternwidth} { + set paddedcontents [overtype::renderspace -width $cache_patternwidth "" $paddedcontents] } - #set inner [overtype::$opt_align -ellipsis $opt_ellipsis $opt_ansibase$underlay$rstbase $opt_ansibase$contents$rstbase] - set inner [overtype::$opt_align -ellipsis $opt_ellipsis $opt_ansibase$underlay$rstbase $contents] + set contentblock [textblock::join $paddedcontents] ;#make sure each line has ansi replays - #set cache_inner [overtype::$opt_align -ellipsis $opt_ellipsis $opt_ansibase$cache_underlay$rstbase $contents] - #review - set cache_inner $opt_ansibase$cache_underlay$rstbase + set tlines [split $template \n] - if {$leftborder && $rightborder} { - set bodyparts [list $lhs $opt_ansibase$inner$rstbase $rhs] - set cache_bodyparts [list $lhs $opt_ansibase$cache_inner$rstbase $rhs] - } else { - if {$leftborder} { - set bodyparts [list $lhs $opt_ansibase$inner$rstbase] - set cache_bodyparts [list $lhs $opt_ansibase$cache_inner$rstbase] - } elseif {$rightborder} { - set bodyparts [list $opt_ansibase$inner$rstbase $rhs] - set cache_bodyparts [list $opt_ansibase$cache_inner$rstbase $rhs] + #we will need to strip off the leading reset on each line when stitching together with template lines so that ansibase can come into play too. + #after textblock::join the reset will be a separate code ie should be exactly ESC[0m + set R [a] + set rlen [string length $R] + set clines [split $contentblock \n] + + foreach tline $tlines { + if {[string first $FSUB $tline] >= 0} { + set content_line [lindex $clines $contentindex] + if {[string first $R $content_line] == 0} { + set content_line [string range $content_line $rlen end] + } + #make sure to replay opt_ansibase to the right of the replacement + lappend resultlines [string map [list $overwritable $content_line$opt_ansibase] $tline] + incr contentindex } else { - set bodyparts [list $opt_ansibase$inner$rstbase] - set cache_bodyparts [list $opt_ansibase$cache_inner$rstbase] + lappend resultlines $tline } } - set body [textblock::join -- {*}$bodyparts] - if {$buildcache} { - set cache_body [textblock::join -- {*}$cache_bodyparts] - append fscached $cache_body - } - append fs $body + set fs [::join $resultlines \n] } - if {$opt_height eq "" || $opt_height > 1} { - if {$opt_subtitle ne ""} { - #subtitle overrides boxlimits for bottomborder - set bottomborder 1 - } - if {$bottomborder} { - if {($topborder & $fs ne "xx" ) || ($has_contents || $opt_height > 2)} { - append fs \n - append fscached \n - } - if {$leftborder && $rightborder} { - append fs $blc$bottombar$brc - append fscached $blc$bottombar$brc - } else { - if {$leftborder} { - append fs $blc$bottombar - append fscached $blc$bottombar - } elseif {$rightborder} { - append fs $bottombar$brc - append fscached $bottombar$brc - } else { - append fs $bottombar - append fscached $bottombar - } - } + + if {$is_cached} { + return $fs + } else { + if {$buildcache} { + dict set frame_cache $cache_key [list frame $template used 0 patternwidth $cache_patternwidth] } + return $fs } - if {$buildcache} { - dict set frame_cache $cache_key [list frame $fscached used 0] - } - return $fs - } proc gcross {{size 1} args} { if {$size == 0} {