diff --git a/src/modules/punk/ansi-999999.0a1.0.tm b/src/modules/punk/ansi-999999.0a1.0.tm index 877e2df8..c161ed99 100644 --- a/src/modules/punk/ansi-999999.0a1.0.tm +++ b/src/modules/punk/ansi-999999.0a1.0.tm @@ -95,7 +95,11 @@ namespace eval punk::ansi::class { } if {$o_rendered_what ne $o_raw || $dimensions ne $o_render_dimensions} { set b [textblock::block $w $h " "] - #some ansi art/layout relies on wrapping at the width-dimension to display properly + #some ansi layout/art relies on wrapping at the width-dimension to display properly + #this includes cursor movements ie right arrow can move cursor to columns in lines below + #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 -appendlines 1 $b $o_raw] #set o_rendered_what $o_raw set o_render_dimensions $dimensions @@ -113,6 +117,9 @@ namespace eval punk::ansi::class { method viewchars {} { return [punk::ansi::stripansiraw $o_raw] } + method viewstyle {} { + return [ansistring VIEWSTYLE $o_raw] + } } } @@ -175,21 +182,47 @@ namespace eval punk::ansi { #review - We have file possibly encoded directly in another codepage such as 437 - or utf8,utf16 etc, but then still needing post conversion to e.g cp437? - proc readfile {fname} { + proc readfile {fname {encoding cp437}} { #todo #1- look for BOM - read according to format given by BOM #2- assume utf-8 #3- if errors - assume cp437? - set data [fcat $fname] - if {[file extension $fname] eq ".ans"} { - set ansidata [encoding convertfrom cp437 $data] - } else { - set ansidata $data - } + set ansidata [fcat -encoding $encoding $fname] set obj [punk::ansi::class::class_ansi new $ansidata] return $obj } + proc ansicat {fname args} { + set encnames [encoding names] + set encoding "" + set dimensions "" + foreach a $args { + if {$a in $encnames} { + set encoding $a + } else { + if {[regexp {[0-9]+(?:x|X)[0-9]+} $a]} { + set dimensions $a + } + } + } + if {$encoding eq ""} { + set encoding cp437 + } + if {$dimensions eq ""} { + set dimensions 80x26 + } + + set ansidata [fcat -encoding $encoding $fname] + set obj [punk::ansi::class::class_ansi new $ansidata] + $obj render $dimensions + } + #utf-8/ascii encoded cp437 + proc ansicat2 {fname {encoding utf-8}} { + set data [fcat -encoding $encoding $fname] + set ansidata [encoding convertfrom cp437 $data] + set obj [punk::ansi::class::class_ansi new $ansidata] + $obj render + } proc is_utf8_char {char} { regexp {(?x) # Expanded regexp syntax, so I can put in comments :-) [\x00-\x7F] | # Single-byte chars (ASCII range) @@ -931,8 +964,47 @@ namespace eval punk::ansi { #[para] DECRC return \x1b8 } - # -- --- --- --- --- + + #DECAWM - automatic line wrapping + proc enable_line_wrap {} { + #*** !doctools + #[call [fun enable_line_wrap]] + #[para] enable automatic line wrapping when characters entered beyond rightmost column + #[para] This will also allow forward movements to move to subsequent lines + #[para] This is DECAWM - and is the same sequence output by 'tput smam' + return \x1b\[?7h + } + proc disable_line_wrap {} { + #*** !doctools + #[call [fun disable_line_wrap]] + #[para] disable automatic line wrapping + #[para] reset DECAWM - same sequence output by 'tput rmam' + #tput rmam + return \x1b\[?7l + } + #DECRQM to query line-wrap state + # \x1b\[?7\$p + #DECRPM responses e.g: + # \x1b\[?7\;1\$y + # \x1b\[?7\;2\$y + #where 1 = set, 2 = unset. (0 = mode not recognised, 3 = permanently set, 4 = permanently unset) + + + #Alt screen buffer + proc enable_alt_screen {} { + #tput smcup outputs "\x1b\[?1049h\x1b\[22\;0\;0t" second esc sequence - DECSLPP? setting page height one less than main screen? + #\x1b\[?1049h ;#xterm + return \x1b\[?47h + } + proc disable_alt_screen {} { + #tput rmcup outputs \x1b\[?1049l\x1b\[23\;0\;0t] + #\x1b\[?1049l + return \x1b\[?47l + } + + # -- --- --- + proc erase_line {} { #*** !doctools #[call [fun erase_line]] @@ -1334,14 +1406,17 @@ namespace eval punk::ansi { dict set codestate_empty nosupersub "" ;#75 # -- - dict set codestate_empty fgbright "" ;#90-97 - keeping separate to fg ie unmerged with it. unsure how it interacts or is used. REVIEW. - dict set codestate_empty bgbright "" ;#100-107 as above - - dict set codestate_empty fg "" - dict set codestate_empty bg "" + dict set codestate_empty fg "" ;#30-37 + 90-97 + dict set codestate_empty bg "" ;#40-47 + 100-107 - proc sgr_merge {args} { + #as a common case optimisation - it will not merge a single element list, even if that code contains redundant elements + proc sgr_merge_list {args} { + if {[llength $args] == 0} { + return "" + } elseif {[llength $args] == 1} { + return [lindex $args 0] + } variable codestate_empty set othercodes [list] @@ -1619,11 +1694,10 @@ namespace eval punk::ansi { dict set codestate subcript "" } 90 - 91 - 92 - 93 - 94 - 95 - 96 - 97 { - #does this belong with fg? REVIEW - dict set codestate fgbright $p + dict set codestate fg $p } 100 - 101 - 102 - 103 - 104 - 105 - 106 - 107 { - dict set codestate bgbright $p + dict set codestate bg $p } } @@ -1933,7 +2007,7 @@ namespace eval punk::ansi::ansistring { namespace path [list ::punk::ansi ::punk::ansi::ta] namespace ensemble create - namespace export length length1 trim trimleft trimright index VIEW VIEWCODES INDEXABSOLUTE INDEXCOLUMNS COLUMNINDEX + namespace export length length1 trim trimleft trimright index VIEW VIEWCODES VIEWSTYLE INDEXABSOLUTE INDEXCOLUMNS COLUMNINDEX #todo - expose _splits_ methods so caller can work efficiently with the splits themselves #we need to consider whether these can be agnostic towards splits from split_codes vs split_codes_single @@ -2353,7 +2427,7 @@ namespace eval punk::ansi::ansistring { switch -regexp -matchvar matchinfo -- $code\ $re_row_move { set displaycode [ansistring VIEW $code] - set displaycode [string map [list A "C$arrow_up" B "D$arrow_down"] $displaycode] + set displaycode [string map [list A "A$arrow_up" B "B$arrow_down"] $displaycode] append output ${cyanb}$displaycode$RST }\ $re_col_move { @@ -2391,6 +2465,45 @@ namespace eval punk::ansi::ansistring { } return $output } + #an attempt to show the codes and colour/style of the *input* + #ie we aren't looking at the row/column positioning - but we do want to keep track of cursor attribute saves and restores + proc VIEWSTYLE {string} { + set splits [punk::ansi::ta::split_codes_single $string] + set output "" + set codestack [list] + set gx_stack [list] ;#not actually a stack + set cursor_saved "" + foreach {pt code} $splits { + append output [punk::ansi::codetype::sgr_merge_list {*}$codestack]$pt + if {$code ne ""} { + append output [a][VIEW $code] + if {[punk::ansi::codetype::is_sgr_reset $code]} { + set codestack [list] + } elseif {[punk::ansi::codetype::has_sgr_leadingreset $code]} { + set codestack [list $code] + } elseif {[punk::ansi::codetype::is_sgr $code]} { + #basic simplification first.. straight dups + set dup_posns [lsearch -all -exact $codestack $code] ;#-exact because of square-bracket glob chars + set codestack [lremove $codestack {*}$dup_posns] + lappend codestack $code + } elseif {[regexp {\x1b7|\x1b\[s} $code]} { + #cursor_save + set cursor_saved [punk::ansi::codetype::sgr_merge_list {*}$codestack] + } elseif {[regexp {\x1b8|\x1b\[u} $code]} { + #cursor_restore + set codestack [list $cursor_saved] + } else { + #leave SGR stack as is + if {[punk::ansi::codetype::is_gx_open $code]} { + set gx_stack [list gx0_on] ;#we'd better use a placeholder - or debugging will probably get into a big mess + } elseif {[punk::ansi::codetype::is_gx_close $code]} { + set gx_stack [list] + } + } + } + } + return $output + } proc length {string} { #*** !doctools diff --git a/src/modules/punk/lib-999999.0a1.0.tm b/src/modules/punk/lib-999999.0a1.0.tm index ecc0aebd..5a5d99c6 100644 --- a/src/modules/punk/lib-999999.0a1.0.tm +++ b/src/modules/punk/lib-999999.0a1.0.tm @@ -937,7 +937,7 @@ namespace eval punk::lib { } #set newreplay [join $codestack ""] - set newreplay [punk::ansi::codetype::sgr_merge {*}$codestack] + set newreplay [punk::ansi::codetype::sgr_merge_list {*}$codestack] if {$line_has_sgr && $newreplay ne $replaycodes} { #adjust if it doesn't already does a reset at start diff --git a/src/modules/punk/repl-0.1.tm b/src/modules/punk/repl-0.1.tm index 009c766c..66fdc71e 100644 --- a/src/modules/punk/repl-0.1.tm +++ b/src/modules/punk/repl-0.1.tm @@ -213,6 +213,7 @@ if {$::tcl_platform(platform) eq "windows"} { #expermental terminal alt screens +#alternatives are \x1b\[?47h ans \x1b[?\47l proc ::repl::term::screen_push_alt {} { #tput smcup puts -nonewline stderr "\033\[?1049h" @@ -1139,7 +1140,7 @@ namespace eval punk::repl::class { set result [dict get $mergedinfo result] set o_insert_mode [dict get $mergedinfo insert_mode] set result_col [dict get $mergedinfo cursor_column] - set cmove [dict get $mergedinfo cursor_row_change] + set cmove [dict get $mergedinfo cursor_row] set overflow_right [dict get $mergedinfo overflow_right] ;#should be empty if no \v set unapplied [dict get $mergedinfo unapplied] set insert_lines_below [dict get $mergedinfo insert_lines_below] @@ -1220,7 +1221,7 @@ namespace eval punk::repl::class { set result [dict get $mergedinfo result] set o_insert_mode [dict get $mergedinfo insert_mode] set o_cursor_col [dict get $mergedinfo cursor_column] - set cmove [dict get $mergedinfo cursor_row_change] + set cmove [dict get $mergedinfo cursor_row] set overflow_right [dict get $mergedinfo overflow_right] ;#should be empty if no \v set unapplied [dict get $mergedinfo unapplied] set insert_lines_below [dict get $mergedinfo insert_lines_below] diff --git a/src/vendormodules/overtype-1.5.9.tm b/src/vendormodules/overtype-1.5.9.tm index 8159131c..4308e194 100644 --- a/src/vendormodules/overtype-1.5.9.tm +++ b/src/vendormodules/overtype-1.5.9.tm @@ -219,6 +219,7 @@ proc overtype::left {args} { lassign [lrange $args end-1 end] underblock overblock set defaults [dict create\ -bias ignored\ + -width \uFFEF\ -wrap 0\ -ellipsis 0\ -ellipsistext $default_ellipsis_horizontal\ @@ -233,7 +234,7 @@ proc overtype::left {args} { set argsflags [lrange $args 0 end-2] dict for {k v} $argsflags { switch -- $k { - -bias - -wrap - -ellipsis - -ellipsistext - -ellipsiswhitespace - -overflow - -appendlines - -transparent - -exposed1 - -exposed2 {} + -width - -bias - -wrap - -ellipsis - -ellipsistext - -ellipsiswhitespace - -overflow - -appendlines - -transparent - -exposed1 - -exposed2 {} default { set known_opts [dict keys $defaults] error "overtype::left unknown option '$k'. Known options: $known_opts" @@ -243,8 +244,12 @@ proc overtype::left {args} { set opts [dict merge $defaults $argsflags] # -- --- --- --- --- --- set opt_overflow [dict get $opts -overflow] + ##### + # review -wrap should map onto DECAWM terminal mode - the wrap 2 idea may not fit in with this?. set opt_wrap [dict get $opts -wrap] ;#wrap 1 is hard wrap cutting word at exact column, or 1 column early for 2w-glyph, wrap 2 is for language-based word-wrap algorithm (todo) + ##### #for repl - standard output line indicator is a dash - todo, add a different indicator for a continued line. + set opt_width [dict get $opts -width] set opt_appendlines [dict get $opts -appendlines] set opt_transparent [dict get $opts -transparent] set opt_ellipsistext [dict get $opts -ellipsistext] @@ -253,25 +258,31 @@ proc overtype::left {args} { set opt_exposed2 [dict get $opts -exposed2] ;#widechar_exposed_right - todo # -- --- --- --- --- --- + #modes + set insert_mode 0 ;#can be toggled by insert key or ansi IRM sequence ESC [ 4 h|l + set autowrap_mode $opt_wrap + set reverse_mode 0 + + set norm [list \r\n \n] set underblock [string map $norm $underblock] set overblock [string map $norm $overblock] #set underlines [split $underblock \n] + + #underblock is a 'rendered' block - so width height make sense + if {$opt_width eq "\uFFEF"} { + lassign [blocksize $underblock] _w colwidth _h colheight + } else { + set colwidth $opt_width + } set underlines [lines_as_list -ansiresets 1 $underblock] - #set colwidth [tcl::mathfunc::max {*}[lmap v $underlines {punk::ansi::printing_length $v}]] - lassign [blocksize $underblock] _w colwidth _h colheight + set overlines [split $overblock \n] - #set overblock_width [tcl::mathfunc::max {*}[lmap v $overlines {punk::ansi::printing_length $v}]] - lassign [blocksize $overblock] _w overblock_width _h overblock_height - set under_exposed_max [expr {$colwidth - $overblock_width}] - set right_exposed $under_exposed_max + #overblock height/width isn't useful in the presence of an ansi input overlay with movements. The number of lines may bear little relationship to the output height + #lassign [blocksize $overblock] _w overblock_width _h overblock_height + - #if {[punk::ansi::ta::detect_sgr [lindex $underlines 0]]} { - # set replay_codes "[punk::ansi::a]" - #} else { - # set replay_codes "" - #} set replay_codes_underlay [dict create 1 ""] lappend replay_codes_overlay "" set unapplied "" @@ -290,28 +301,7 @@ proc overtype::left {args} { set undertext [lindex $outputlines [expr {$row -1}]] set renderedrow $row - set pad 0 - if {$pad} { - set undertext_printlen [punk::ansi::printing_length $undertext] - if {$undertext_printlen < $colwidth} { - set udiff [expr {$colwidth - $undertext_printlen}] - append undertext [string repeat { } $udiff] - } - } - #padding of new empties needed for auto line adding to work - testing with ansi-art - if {$undertext eq ""} { - set undertext [string repeat " " $colwidth] - } else { - set undertext_printlen [punk::ansi::printing_length $undertext] - if {$undertext_printlen < $colwidth} { - set udiff [expr {$colwidth - $undertext_printlen}] - append undertext [string repeat { } $udiff] - } - } - - #examining printing length of a potentially ansi-movement-laden line in isolation makes no sense - #set overtext_printlen [punk::ansi::printing_length $overtext] - #set overflowlength [expr {$overtext_printlen - $colwidth}] + #renderline pads each underaly line to width with spaces and should track where end of data is set overtext [string cat [lindex $replay_codes_overlay $overidx] $overtext] @@ -320,14 +310,18 @@ proc overtype::left {args} { lappend underlay_resets [list $row [dict get $replay_codes_underlay $row]] } #review insert_mode. As an 'overtype' function whose main function is not interactive keystrokes - insert is secondary - - #but even if we didn't want it as an option to the function call - to process ansi fully it may need to be supported (how widely supported are ansi insert-mode toggles?) - set rinfo [renderline -info 1 -insert_mode 0 -transparent $opt_transparent -width $colwidth -exposed1 $opt_exposed1 -exposed2 $opt_exposed2 -overflow $opt_overflow -cursor_column $col -cursor_row $row $undertext $overtext] + #but even if we didn't want it as an option to the function call - to process ansi adequately we need to support IRM (insertion-replacement mode) ESC [ 4 h|l + set rinfo [renderline -info 1 -insert_mode $insert_mode -autowrap_mode $autowrap_mode -transparent $opt_transparent -width $colwidth -exposed1 $opt_exposed1 -exposed2 $opt_exposed2 -overflow $opt_overflow -cursor_column $col -cursor_row $row $undertext $overtext] set instruction [dict get $rinfo instruction] + set insert_mode [dict get $rinfo insert_mode] + set autowrap_mode [dict get $rinfo autowrap_mode] ;# + #set reverse_mode [dict get $rinfo reverse_mode];#how to support in rendered linelist? we need to examine all pt/code blocks and flip each SGR stack? set rendered [dict get $rinfo result] set overflow_right [dict get $rinfo overflow_right] + set overflow_right_column [dict get $rinfo overflow_right_column] set unapplied [dict get $rinfo unapplied] - set render_col [dict get $rinfo cursor_column] - set render_row [dict get $rinfo cursor_row_change] + set post_render_col [dict get $rinfo cursor_column] + set post_render_row [dict get $rinfo cursor_row] set c_saved_pos [dict get $rinfo cursor_saved_position] set c_saved_attributes [dict get $rinfo cursor_saved_attributes] set visualwidth [dict get $rinfo visualwidth] @@ -336,6 +330,13 @@ proc overtype::left {args} { dict set replay_codes_underlay [expr {$renderedrow+1}] [dict get $rinfo replay_codes_underlay] lset replay_codes_overlay [expr $overidx+1] [dict get $rinfo replay_codes_overlay] + #-- todo - detect looping properly + if {$row > 1 && $overtext ne "" && $unapplied eq $overtext && $post_render_row == $row} { + puts stderr "overtype::left loop?" + break + } + #-- + if {[dict size $c_saved_pos] >= 1} { set cursor_saved_position $c_saved_pos set cursor_saved_attributes $c_saved_attributes @@ -345,15 +346,9 @@ proc overtype::left {args} { #background line is narrower than data in line set overflow_handled 0 - if {!$opt_overflow} { - #not allowed to overflow column therefore we get overflow data to either truncate or wrap - if {$opt_wrap} { - #wrap by returning unapplied and overflow_right - if {$instruction eq ""} { - set instruction wrap - } - - } elseif {[dict get $opts -ellipsis]} { + if {!$opt_overflow && !$autowrap_mode} { + #not allowed to overflow column or wrap therefore we get overflow data to truncate + if {[dict get $opts -ellipsis]} { set show_ellipsis 1 if {!$opt_ellipsiswhitespace} { #we don't want ellipsis if only whitespace was lost @@ -388,7 +383,8 @@ proc overtype::left {args} { if {$opt_appendlines} { lappend outputlines $rendered } else { - #lset outputlines [expr {$renderedrow-1}] $rendered + #? + lset outputlines [expr {$renderedrow-1}] $rendered } } @@ -410,7 +406,7 @@ proc overtype::left {args} { #should move to home position and reset ansi SGR? #puts stderr "overtype::left cursor_restore without save data available" } - #If we were inserting prior to hitting the cursor_restore - there could be overflow_right data - generally the overtype functions aren't for inserting - but ansi could enable it + #If we were inserting prior to hitting the cursor_restore - there could be overflow_right data - generally the overtype functions aren't for inserting - but ansi can enable it #if we were already in overflow when cursor_restore was hit - it shouldn't have been processed as an action - just stored. if {!$overflow_handled && $overflow_right ne ""} { #wrap before restore? - possible effect on saved cursor position @@ -441,26 +437,41 @@ proc overtype::left {args} { } up { #renderline already knows not to go above l - set row $render_row - set col $render_col + #Note that an ansi up sequence after last column going up to a previous line and also beyond the last column, will result in the next grapheme going onto the following line. + #this seems correct - as the column remains beyond the right margin so subsequent chars wrap (?) review + #puts stderr "up $post_render_row" + #puts stderr "$rinfo" + set row $post_render_row + set rowdata [lindex $outputlines [expr {$row -1}]] + set len [punk::ansi::printing_length $rowdata] + if {$len+1 < $post_render_col} { + set col [expr {$len+1}] + } else { + set col $post_render_col + } } down { #renderline doesn't know how far down we can go.. - if {$render_row > [llength $outputlines]} { + if {$post_render_row > [llength $outputlines]} { + #if {$opt_appendlines} { + # set diff [expr {$post_render_row - [llength $outputlines]}] + # if {$diff > 0} { + # lappend outputlines {*}[lrepeat $diff ""] + # } + #} set row [llength $outputlines] } else { - set row $render_row + set row $post_render_row } - - set col $render_col + set col $post_render_col } move { - if {$render_row > [llength $outputlines]} { + if {$post_render_row > [llength $outputlines]} { set row [llength $outputlines] } else { - set row $render_row + set row $post_render_row } - set col $render_col + set col $post_render_col #overflow + unapplied? } newline_above - newline_below { @@ -468,13 +479,62 @@ proc overtype::left {args} { } wrap { #hard wraps in this context. + #note that cursor_forward may move deep into the next line - or even span multiple lines !TODO + if {$overflow_right_column eq ""} { + #so why are we getting a wrap instruction? + puts stderr "overtype::left wrap instruction when no overflow_right_column\n$rinfo" + incr row + set col 1 + } else { + if {$post_render_col >= $overflow_right_column} { + #review - check printing_length of each following underlay line and move appropriately? + #puts "post_render_col: $post_render_col" + #puts "overflow_right_column: $overflow_right_column" + set c $overflow_right_column + set i $c + while {$i <= $post_render_col} { + if {($i-1) % $colwidth == 0} { + incr row + set c 1 + } else { + incr c + } + incr i + } + set col $c + #incr row + #set col [expr {1+ ($post_render_col - $overflow_right_column)}] + } else { + incr row + set col 1 + } + } + } + overflow { + #normal single-width grapheme overflow + incr row + set col 1 ;#whether wrap or not - next data is at column 1 + if {!$autowrap_mode} { + set overflow_handled 1 + set unapplied "" + #handled by dropping it.. + } + } + overflow_splitchar { + #2nd half of grapheme would overflow - grapheme returned in unapplied. There may also be overflow_right from earlier inserts + #todo - consider various options .. re-render a single trailing space or placeholder on same output line, etc incr row - set col 1 + if {$autowrap_mode} { + set col 1 + } else { + set overflow_handled 1 + #handled by dropping it.. + } } vt { #can vt add a line like a linefeed can? - set row $render_row - set col $render_col + set row $post_render_row + set col $post_render_col } default { puts stderr "overtype::left unhandled renderline instruction '$instruction'" @@ -505,7 +565,7 @@ proc overtype::left {args} { #dict set replay_codes_underlay [expr {$renderedrow+1}] [dict get $rinfo replay_codes_underlay] #lset replay_codes_overlay [expr $overidx+1] [dict get $rinfo replay_codes_overlay] - set prevrow $row + set prevrow $renderedrow } #puts stdout $underlay_resets return [join $outputlines \n] @@ -873,6 +933,8 @@ proc overtype::renderline {args} { -cursor_column 1\ -cursor_row ""\ -insert_mode 1\ + -autowrap_mode 1\ + -reverse_mode 0\ -info 0\ -exposed1 \uFFFD\ -exposed2 \uFFFD\ @@ -886,7 +948,7 @@ proc overtype::renderline {args} { set argsflags [lrange $args 0 end-2] dict for {k v} $argsflags { switch -- $k { - -width - -overflow - -transparent - -startcolumn - -cursor_column - -cursor_row - -insert_mode - -info - -exposed1 - -exposed2 {} + -width - -overflow - -transparent - -startcolumn - -cursor_column - -cursor_row - -insert_mode - -autowrap_mode - -reverse_mode - -info - -exposed1 - -exposed2 {} default { set known_opts [dict keys $defaults] error "overtype::renderline unknown option '$k'. Known options: $known_opts" @@ -906,9 +968,17 @@ proc overtype::renderline {args} { error "overtype::renderline -cursor_row must be empty for unspecified/unknown or a non-zero positive integer. received: '$opt_row_context'" } } + # -- --- --- --- --- --- --- --- --- --- --- --- + #The _mode flags correspond to terminal modes that can be set/reset via escape sequences (e.g DECAWM wraparound mode) set opt_insert_mode [dict get $opts -insert_mode];#should usually be 1 for each new line in editor mode but must be initialised to 1 externally (review) #default is for overtype # -- --- --- --- --- --- --- --- --- --- --- --- + set opt_autowrap_mode [dict get $opts -autowrap_mode] ;#DECAWM - char or movement can go beyond leftmost/rightmost col to prev/next line + set opt_reverse_mode [dict get $opts -reverse_mode] ;#DECSNM + # -- --- --- --- --- --- --- --- --- --- --- --- + + + set opt_transparent [dict get $opts -transparent] if {$opt_transparent eq "0"} { set do_transparency 0 @@ -925,11 +995,6 @@ proc overtype::renderline {args} { set opt_exposed2 [dict get $opts -exposed2] # -- --- --- --- --- --- --- --- --- --- --- --- - #if {$opt_row_context eq ""} { - # set cursor_row 0 ;#we aren't allowed to make assumptions about our context. zero represents cursor_row_change - not an absolute row (for which zero is invalid anyway) - #} else { - # set cursor_row "=$opt_row_context" ;#we are at this row number in the greater context - allow moves that explicitly refer to this row without returning prematurely - #} if {$opt_row_context eq ""} { set cursor_row 1 } else { @@ -1040,15 +1105,33 @@ proc overtype::renderline {args} { #consider also if there are other codes that should be stacked..? } + #fill columns to width with spaces, and carry over stacks - we will have to keep track of where the underlying data ends manually - TODO + if {$opt_width ne "\uFFef"} { + if {[llength $understacks]} { + set cs $u_codestack + set gs $u_gx_stack + } else { + set cs [list] + set gs [list] + } + if {[llength $undercols]< $opt_width} { + set diff [expr {$opt_width- [llength $undercols]}] + if {$diff > 0} { + lappend undercols {*}[lrepeat $diff " "] + lappend understacks {*}[lrepeat $diff $cs] + lappend understacks_gx {*}[lrepeat $diff $gs] + } + } + } #trailing codes in effect for underlay #replay code for last overlay position in input line # whether or not we get that far - we need to return it for possible replay on next line - if {[llength $undermap]} { + if {[llength $understacks]} { #dict set understacks [expr {$i_u + 1}] $u_codestack ;#This is one column higher than our input lappend understacks $u_codestack #set replay_codes_underlay [join $u_codestack ""] - set replay_codes_underlay [punk::ansi::codetype::sgr_merge {*}$u_codestack] + set replay_codes_underlay [punk::ansi::codetype::sgr_merge_list {*}$u_codestack] # For gx we need the column after the data too ? #dict set understacks_gx [expr {$i_u +1}] $u_gx_stack @@ -1136,7 +1219,7 @@ proc overtype::renderline {args} { lappend overstacks_gx $o_gxstack #set replay_codes_overlay [join $o_codestack ""] - set replay_codes_overlay [punk::ansi::codetype::sgr_merge {*}$o_codestack] + set replay_codes_overlay [punk::ansi::codetype::sgr_merge_list {*}$o_codestack] #if {[dict exists $overstacks $max_overlay_grapheme_index]} { # set replay_codes_overlay [join [dict get $overstacks $max_overlay_grapheme_index] ""] @@ -1156,7 +1239,7 @@ proc overtype::renderline {args} { } else { #overflow zero - we can't grow beyond our column width - so we get ellipsis or truncation if {$opt_width ne "\uFFEF"} { - set overflow_idx $opt_width + set overflow_idx [expr {$opt_width}] } else { #review - this is also the cursor position when adding a char at end of line? set overflow_idx [expr {[llength $undercols]}] ;#index at which we would be *in* overflow a row move may still override it @@ -1183,7 +1266,10 @@ proc overtype::renderline {args} { #idx is the per column output index set idx [expr {$opt_colcursor -1}] ;#don't use opt_colstart here - we have padded and won't start emitting until idx reaches opt_colstart-1 + #cursor_column is usually one above idx - but we have opt_colstart which is like a margin - todo: remove cursor_column from the following loop and calculate it's offset when breaking or at end. + #(for now we are incrementing/decrementing both in sync - which is a bit silly) set cursor_column $opt_colcursor + #idx_over is the per grapheme overlay index set idx_over -1 @@ -1193,7 +1279,9 @@ proc overtype::renderline {args} { #renderline -overflow 1 "" data #foreach {pt code} $overmap {} set insert_mode $opt_insert_mode ;#default 1 - set in_overflow 0 + set autowrap_mode $opt_autowrap_mode ;#default 1 + + #puts "-->$overlay_grapheme_control_list<--" #puts "-->overflow_idx: $overflow_idx" for {set gci 0} {$gci < [llength $overlay_grapheme_control_list]} {incr gci} { @@ -1207,296 +1295,273 @@ proc overtype::renderline {args} { g { set ch $item incr idx_over; #idx_over (until unapplied reached anyway) is per *grapheme* in the overlay - not per col. - + set within_undercols [expr {$idx <= [llength $undercols]-1}] ;#within our original data width - if {$in_overflow} { - #render any char - even \b\v\r into outcols - will become part of overflow - #no stacks added from here on - raw codes go into overflow/remainder - priv::render_addchar $idx $ch [list] [list] $insert_mode - incr idx ;#width doesn't matter from here on - idx once in overflow no longer represents columns - } else { - if {$overflow_idx != -1} { - #review - how to check arbitrary length item such as tab is going to overflow .. before we get to overflow_idx? - #call grapheme_width_cached on each ch, or look for tab specifically as it's currently the only known reason to have a grapheme width > 2? - #we need to decide what a tab spanning the overflow_idx means and how it affects wrap etc etc - if {$idx == $overflow_idx-1} { - set owidth [grapheme_width_cached $ch] - if {$owidth == 2} { - #review split 2w overflow? - #we don't want to split a 2w into replacement characters at end of line and beginning of next line - #better to consider the overlay char as unable to be applied to the line - #render empty string to column - and reduce overlay grapheme index by one so that the current ch goes into unapplied - #we have the option here to put the 2w in the normal overflow and continue, instead of adding to unapplied and throwing back to caller - #throwing back to caller with instruction complicates its job - but might be necessary to avoid making decsions for it here. - #REVIEW - decide one way or another, update callers as necessary and chop unused branch. - set continue 1 - if $continue { - priv::render_addchar $idx "" [lindex $overstacks $idx_over] [lindex $overstacks_gx $idx_over] $insert_mode - set overflow_idx $idx - #incr idx - set in_overflow 1 - incr gci -1 - #priv::render_addchar $idx $ch [lindex $overstacks $idx_over] [lindex $overstacks_gx $idx_over] $insert_mode - continue - } else { - priv::render_addchar $idx "" [lindex $overstacks $idx_over] [lindex $overstacks_gx $idx_over] $insert_mode - #change the overflow_idx - set overflow_idx $idx - incr idx - incr idx_over -1 ;#set overlay grapheme index back one so that current ch is considered unapplied - priv::render_unapplied $overlay_grapheme_control_list [expr {$gci-1}] ;#note $gci-1 instead of just gci - #throw back to caller's loop - add instruction to caller as this is not the usual case - set instruction overflow_splitchar - break - } - } elseif {$owidth > 2} { - #? tab? - #TODO! - puts stderr "overtype::renderline long overtext grapheme '[ansistring VIEW -lf 1 -vt 1 $ch]' not handled" - #tab of some length dependent on tabstops/elastic tabstop settings? - } - } elseif {$idx == $overflow_idx} { - #don't incr cursor_column beyond the overflow_idx - set in_overflow 1 - priv::render_addchar $idx $ch [lindex $overstacks $idx_over] [lindex $overstacks_gx $idx_over] $insert_mode + if {$overflow_idx != -1} { + #review - how to check arbitrary length item such as tab is going to overflow .. before we get to overflow_idx? + #call grapheme_width_cached on each ch, or look for tab specifically as it's currently the only known reason to have a grapheme width > 2? + #we need to decide what a tab spanning the overflow_idx means and how it affects wrap etc etc + if {$idx == $overflow_idx-1} { + set owidth [grapheme_width_cached $ch] + if {$owidth == 2} { + #review split 2w overflow? + #we don't want to make the decision here to split a 2w into replacement characters at end of line and beginning of next line + #better to consider the overlay char as unable to be applied to the line + #render empty string to column(?) - and reduce overlay grapheme index by one so that the current ch goes into unapplied + #throwing back to caller with instruction complicates its job - but is necessary to avoid making decsions for it here. + priv::render_addchar $idx "" [lindex $overstacks $idx_over] [lindex $overstacks_gx $idx_over] $insert_mode + #change the overflow_idx + set overflow_idx $idx incr idx - continue + incr idx_over -1 ;#set overlay grapheme index back one so that sgr stack from previous overlay grapheme used + priv::render_unapplied $overlay_grapheme_control_list [expr {$gci-1}] ;#note $gci-1 instead of just gci + #throw back to caller's loop - add instruction to caller as this is not the usual case + #caller may for example choose to render a single replacement char to this line and omit the grapheme, or wrap it to the next line + set instruction overflow_splitchar + break + } elseif {$owidth > 2} { + #? tab? + #TODO! + puts stderr "overtype::renderline long overtext grapheme '[ansistring VIEW -lf 1 -vt 1 $ch]' not handled" + #tab of some length dependent on tabstops/elastic tabstop settings? } + } elseif {$idx == $overflow_idx} { + #don't incr idx beyond the overflow_idx + #idx_over already incremented - decrement so current overlay grapheme stacks go to unapplied + incr idx_over -1 + priv::render_unapplied $overlay_grapheme_control_list [expr {$gci-1}] ;#back one index here too + set instruction overflow + break } + } - if {($idx < ($opt_colstart -1))} { + if {($idx < ($opt_colstart -1))} { + incr idx + } elseif {($do_transparency && [regexp $opt_transparent $ch])} { + #pre opt_colstart is effectively transparent (we have applied padding of required number of columns to left of overlay) + if {$idx > [llength $outcols]-1} { + lappend outcols " " + #dict set understacks $idx [list] ;#review - use idx-1 codestack? + lset understacks $idx [list] incr idx - } elseif {($do_transparency && [regexp $opt_transparent $ch])} { - #pre opt_colstart is effectively transparent (we have applied padding of required number of columns to left of overlay) - if {$idx > [llength $outcols]-1} { - lappend outcols " " - #dict set understacks $idx [list] ;#review - use idx-1 codestack? - lset understacks $idx [list] + incr cursor_column + } else { + #todo - punk::char::char_width + set g [lindex $outcols $idx] + set uwidth [grapheme_width_cached $g] + if {[lindex $outcols $idx] eq ""} { + #2nd col of 2-wide char in underlay incr idx incr cursor_column - } else { - #todo - punk::char::char_width - set g [lindex $outcols $idx] - set uwidth [grapheme_width_cached $g] - if {[lindex $outcols $idx] eq ""} { - #2nd col of 2-wide char in underlay - incr idx - incr cursor_column - } elseif {$uwidth == 0} { - #e.g control char ? combining diacritic ? - incr idx - incr cursor_column - } elseif {$uwidth == 1} { - set owidth [grapheme_width_cached $ch] + } elseif {$uwidth == 0} { + #e.g control char ? combining diacritic ? + incr idx + incr cursor_column + } elseif {$uwidth == 1} { + set owidth [grapheme_width_cached $ch] + incr idx + incr cursor_column + if {$owidth > 1} { incr idx incr cursor_column - if {$owidth > 1} { - incr idx - incr cursor_column - } - } elseif {$uwidth > 1} { - if {[grapheme_width_cached $ch] == 1} { - if {!$insert_mode} { - #normal singlewide transparent overlay onto double-wide underlay - set next_pt_overchar [string index $pt_overchars $idx_over+1] ;#lookahead of next plain-text char in overlay - if {$next_pt_overchar eq ""} { - #special-case trailing transparent - no next_pt_overchar + } + } elseif {$uwidth > 1} { + if {[grapheme_width_cached $ch] == 1} { + if {!$insert_mode} { + #normal singlewide transparent overlay onto double-wide underlay + set next_pt_overchar [string index $pt_overchars $idx_over+1] ;#lookahead of next plain-text char in overlay + if {$next_pt_overchar eq ""} { + #special-case trailing transparent - no next_pt_overchar + incr idx + incr cursor_column + } else { + if {[regexp $opt_transparent $next_pt_overchar]} { incr idx incr cursor_column } else { - if {[regexp $opt_transparent $next_pt_overchar]} { - incr idx - incr cursor_column - } else { - #next overlay char is not transparent.. first-half of underlying 2wide char is exposed - #priv::render_addchar $idx $opt_exposed1 [dict get $overstacks $idx_over] [dict get $overstacks_gx $idx_over] $insert_mode - priv::render_addchar $idx $opt_exposed1 [lindex $overstacks $idx_over] [lindex $overstacks_gx $idx_over] $insert_mode - incr idx - incr cursor_column - } + #next overlay char is not transparent.. first-half of underlying 2wide char is exposed + #priv::render_addchar $idx $opt_exposed1 [dict get $overstacks $idx_over] [dict get $overstacks_gx $idx_over] $insert_mode + priv::render_addchar $idx $opt_exposed1 [lindex $overstacks $idx_over] [lindex $overstacks_gx $idx_over] $insert_mode + incr idx + incr cursor_column } - } else { - #? todo - decide what transparency even means for insert mode - incr idx - incr cursor_column } } else { - #2wide transparency over 2wide in underlay - review + #? todo - decide what transparency even means for insert mode incr idx incr cursor_column } + } else { + #2wide transparency over 2wide in underlay - review + incr idx + incr cursor_column } } - } else { - set chtest [string map [list \n \b \r \v \x7f ] $ch] - switch -- $chtest { - "" { - incr cursor_row + } + } else { + set chtest [string map [list \n \b \r \v \x7f ] $ch] + switch -- $chtest { + "" { + incr cursor_row + + #override overflow_idx even if it was set to -1 due to opt_overflow = 1|2 + set overflow_idx $idx + #idx_over already incremented + priv::render_unapplied $overlay_grapheme_control_list $gci + + if {$idx == 0} { + set insert_lines_above 1 ;#keep for consistency with ansi sequence that requests insertion of line(s)? + set instruction newline_above + } else { + set insert_lines_below 1 + set instruction newline_below + } + break + #set cursor_column 1 + } + "" { + set idx [expr {$opt_colstart -1}] + set cursor_column $opt_colstart ;#? + } + "" { + #literal backspace char - not necessarily from keyboard + #review - backspace effect on double-width chars - we are taking a column-editing perspective in overtype + #(important for -transparent option - hence replacement chars for half-exposed etc) + #review - overstrike support as per nroff/less (generally considered an old technology replaced by unicode mechanisms and/or ansi SGR) + if {$idx > ($opt_colstart -1)} { + incr idx -1 + incr cursor_column -1 + } else { + set flag 0 + if $flag { + #review - conflicting requirements? Need a different sequence for destructive interactive backspace? + priv::render_unapplied $overlay_grapheme_control_list $gci + set instruction backspace_at_start + break + } + } + } + "" { + #literal del character - some terminals send just this for what is generally expected to be a destructive backspace + #We instead treat this as a pure delete at current cursor position - it is up to the repl or terminal to remap backspace key to a sequence that has the desired effect. + priv::render_delchar $idx + } + "" { + #end processing this overline. rest of line is remainder. cursor for column as is. + incr cursor_row + set overflow_idx $idx + #idx_over has already been incremented as this is both a movement-control and in some sense a grapheme + priv::render_unapplied $overlay_grapheme_control_list $gci + set instruction vt + break + } + default { + + #non-transparent char in overlay + set uwidth [grapheme_width_cached [lindex $outcols $idx]] - #override overflow_idx even if it was set to -1 due to opt_overflow = 1|2 - set overflow_idx $idx - #idx_over already incremented - priv::render_unapplied $overlay_grapheme_control_list $gci - - if {$idx == 0} { - set insert_lines_above 1 ;#keep for consistency with ansi sequence that requests insertion of line(s)? - set instruction newline_above + if {$within_undercols && [lindex $outcols $idx] eq ""} { + #2nd col of 2wide char in underlay + if {!$insert_mode} { + priv::render_addchar $idx $ch [lindex $overstacks $idx_over] [lindex $overstacks_gx $idx_over] 0 + #JMN - this has to expose if our startposn chopped an underlay - but not if we already overwrote the first half of the widechar underlay grapheme + #e.g renderline \uFF21\uFF21--- a\uFF23\uFF23 + #vs + # renderline -startcolumn 2 \uFF21---- \uFF23 + if {[lindex $outcols $idx-1] != ""} { + #verified it's an empty following a filled - so it's a legit underlay remnant (REVIEW - when would it not be??) + #reset previous to an exposed 1st-half - but leave understacks code as is + priv::render_addchar [expr {$idx-1}] $opt_exposed1 [lindex $understacks $idx-1] [lindex $understacks_gx $idx-1] 0 + } + incr idx + incr cursor_column } else { - set insert_lines_below 1 - set instruction newline_below + set prevcolinfo [lindex $outcols $idx-1] + #for insert mode - first replace the empty 2ndhalf char with exposed2 before shifting it right + #REVIEW - this leaves a replacement character permanently in our columns.. but it is consistent regarding length (?) + #The alternative is to disallow insertion at a column cursor that is at 2nd half of 2wide char + #perhaps by inserting after the char - this may be worthwhile - but may cause other surprises + #It is perhaps best avoided at another level and try to make renderline do exactly as it's told + #the advantage of this 2w splitting method is that the inserted character ends up in exactly the column we expect. + priv::render_addchar $idx $opt_exposed2 [lindex $overstacks $idx_over] [lindex $overstacks_gx $idx_over] 0 ;#replace not insert + priv::render_addchar $idx $ch [lindex $overstacks $idx_over] [lindex $overstacks_gx $idx_over] 1 ;#insert - same index + if {$prevcolinfo ne ""} { + #we've split the 2wide - it may already have been rendered as an exposed1 - but not for example if our startcolumn was current idx + priv::render_addchar [expr {$idx-1}] $opt_exposed1 [lindex $understacks $idx-1] [lindex $understacks_gx $idx-1] 0 ;#replace not insert + } ;# else?? + incr idx + incr cursor_column } - break - #set cursor_column 1 - } - "" { - set idx [expr {$opt_colstart -1}] - set cursor_column 1 ;#? - } - "" { - #literal backspace char - not necessarily from keyboard - #review - backspace effect on double-width chars - we are taking a column-editing perspective in overtype - #(important for -transparent option - hence replacement chars for half-exposed etc) - #review - overstrike support as per nroff/less (generally considered an old technology replaced by unicode mechanisms and/or ansi SGR) - if {$idx > ($opt_colstart -1)} { - incr idx -1 - incr cursor_column -1 + + } elseif {$uwidth == 0} { + if {$within_undercols} { + #e.g combining diacritic - increment before over char REVIEW + #arguably the previous overchar should have done this - ie lookahead for combiners? + priv::render_addchar $idx "" [lindex $overstacks $idx_over] [lindex $overstacks_gx $idx_over] $insert_mode + incr idx + priv::render_addchar $idx $ch [lindex $overstacks $idx_over] [lindex $overstacks_gx $idx_over] $insert_mode + incr idx + incr cursor_column 2 } else { - set flag 0 - if $flag { - #review - conflicting requirements? Need a different sequence for destructive interactive backspace? - priv::render_unapplied $overlay_grapheme_control_list $gci - set instruction backspace_at_start - break + #overflow + priv::render_addchar $idx $ch [lindex $overstacks $idx_over] [lindex $overstacks_gx $idx_over] $insert_mode + incr idx + incr cursor_column + } + } elseif {$uwidth == 1} { + set owidth [grapheme_width_cached $ch] + if {$owidth == 1} { + priv::render_addchar $idx $ch [lindex $overstacks $idx_over] [lindex $overstacks_gx $idx_over] $insert_mode + incr idx + incr cursor_column + } else { + priv::render_addchar $idx $ch [lindex $overstacks $idx_over] [lindex $overstacks_gx $idx_over] $insert_mode + incr idx + priv::render_addchar $idx "" [lindex $overstacks $idx_over] [lindex $overstacks_gx $idx_over] $insert_mode + #if next column in underlay empty - we've overwritten first half of underlying 2wide grapheme + #replace with rhs exposure in case there are no more overlay graphemes coming - use underlay's stack + if {([llength $outcols] >= $idx +2) && [lindex $outcols $idx+1] eq ""} { + priv::render_addchar [expr {$idx+1}] $opt_exposed2 [lindex $understacks $idx+1] [lindex $understacks_gx $idx+1] $insert_mode } + incr idx + incr cursor_column } - } - "" { - #literal del character - some terminals send just this for what is generally expected to be a destructive backspace - #We instead treat this as a pure delete at current cursor position - it is up to the repl or terminal to remap backspace key to a sequence that has the desired effect. - priv::render_delchar $idx - } - "" { - #end processing this overline. rest of line is remainder. cursor for column as is. - incr cursor_row - set overflow_idx $idx - #idx_over has already been incremented as this is both a movement-control and in some sense a grapheme - priv::render_unapplied $overlay_grapheme_control_list $gci - set instruction vt - break - } - default { - - #non-transparent char in overlay - set uwidth [grapheme_width_cached [lindex $outcols $idx]] - - if {$within_undercols && [lindex $outcols $idx] eq ""} { - #2nd col of 2wide char in underlay + } elseif {$uwidth > 1} { + set owidth [grapheme_width_cached $ch] + if {$owidth == 1} { + #1wide over 2wide in underlay if {!$insert_mode} { - priv::render_addchar $idx $ch [lindex $overstacks $idx_over] [lindex $overstacks_gx $idx_over] 0 - #JMN - this has to expose if our startposn chopped an underlay - but not if we already overwrote the first half of the widechar underlay grapheme - #e.g renderline \uFF21\uFF21--- a\uFF23\uFF23 - #vs - # renderline -startcolumn 2 \uFF21---- \uFF23 - if {[lindex $outcols $idx-1] != ""} { - #verified it's an empty following a filled - so it's a legit underlay remnant (REVIEW - when would it not be??) - #reset previous to an exposed 1st-half - but leave understacks code as is - priv::render_addchar [expr {$idx-1}] $opt_exposed1 [lindex $understacks $idx-1] [lindex $understacks_gx $idx-1] 0 - } - incr idx - incr cursor_column - } else { - set prevcolinfo [lindex $outcols $idx-1] - #for insert mode - first replace the empty 2ndhalf char with exposed2 before shifting it right - #REVIEW - this leaves a replacement character permanently in our columns.. but it is consistent regarding length (?) - #The alternative is to disallow insertion at a column cursor that is at 2nd half of 2wide char - #perhaps by inserting after the char - this may be worthwhile - but may cause other surprises - #It is perhaps best avoided at another level and try to make renderline do exactly as it's told - #the advantage of this 2w splitting method is that the inserted character ends up in exactly the column we expect. - priv::render_addchar $idx $opt_exposed2 [lindex $overstacks $idx_over] [lindex $overstacks_gx $idx_over] 0 ;#replace not insert - priv::render_addchar $idx $ch [lindex $overstacks $idx_over] [lindex $overstacks_gx $idx_over] 1 ;#insert - same index - if {$prevcolinfo ne ""} { - #we've split the 2wide - it may already have been rendered as an exposed1 - but not for example if our startcolumn was current idx - priv::render_addchar [expr {$idx-1}] $opt_exposed1 [lindex $understacks $idx-1] [lindex $understacks_gx $idx-1] 0 ;#replace not insert - } ;# else?? - incr idx - incr cursor_column - } - - } elseif {$uwidth == 0} { - if {$within_undercols} { - #e.g combining diacritic - increment before over char REVIEW - #arguably the previous overchar should have done this - ie lookahead for combiners? - priv::render_addchar $idx "" [lindex $overstacks $idx_over] [lindex $overstacks_gx $idx_over] $insert_mode - incr idx - priv::render_addchar $idx $ch [lindex $overstacks $idx_over] [lindex $overstacks_gx $idx_over] $insert_mode - incr idx - incr cursor_column 2 - } else { - #overflow - priv::render_addchar $idx $ch [lindex $overstacks $idx_over] [lindex $overstacks_gx $idx_over] $insert_mode - incr idx - incr cursor_column - } - } elseif {$uwidth == 1} { - set owidth [grapheme_width_cached $ch] - if {$owidth == 1} { priv::render_addchar $idx $ch [lindex $overstacks $idx_over] [lindex $overstacks_gx $idx_over] $insert_mode incr idx incr cursor_column + priv::render_addchar $idx $opt_exposed2 [lindex $overstacks $idx_over] [lindex $overstacks_gx $idx_over] $insert_mode + #don't incr idx - we are just putting a broken-indication in the underlay - which may get overwritten by next overlay char } else { + #insert mode just pushes all to right - no exposition char here priv::render_addchar $idx $ch [lindex $overstacks $idx_over] [lindex $overstacks_gx $idx_over] $insert_mode incr idx - priv::render_addchar $idx "" [lindex $overstacks $idx_over] [lindex $overstacks_gx $idx_over] $insert_mode - #if next column in underlay empty - we've overwritten first half of underlying 2wide grapheme - #replace with rhs exposure in case there are no more overlay graphemes coming - use underlay's stack - if {([llength $outcols] >= $idx +2) && [lindex $outcols $idx+1] eq ""} { - priv::render_addchar [expr {$idx+1}] $opt_exposed2 [lindex $understacks $idx+1] [lindex $understacks_gx $idx+1] $insert_mode - } - incr idx incr cursor_column } - } elseif {$uwidth > 1} { - set owidth [grapheme_width_cached $ch] - if {$owidth == 1} { - #1wide over 2wide in underlay - if {!$insert_mode} { - priv::render_addchar $idx $ch [lindex $overstacks $idx_over] [lindex $overstacks_gx $idx_over] $insert_mode - incr idx - incr cursor_column - priv::render_addchar $idx $opt_exposed2 [lindex $overstacks $idx_over] [lindex $overstacks_gx $idx_over] $insert_mode - #don't incr idx - we are just putting a broken-indication in the underlay - which may get overwritten by next overlay char - } else { - #insert mode just pushes all to right - no exposition char here - priv::render_addchar $idx $ch [lindex $overstacks $idx_over] [lindex $overstacks_gx $idx_over] $insert_mode - incr idx - incr cursor_column - } - } else { - #2wide over 2wide - priv::render_addchar $idx $ch [lindex $overstacks $idx_over] [lindex $overstacks_gx $idx_over] $insert_mode - incr idx 2 - incr cursor_column 2 - } + } else { + #2wide over 2wide + priv::render_addchar $idx $ch [lindex $overstacks $idx_over] [lindex $overstacks_gx $idx_over] $insert_mode + incr idx 2 + incr cursor_column 2 } } - } ;# end switch - } + } + } ;# end switch } } other { set code $item - if {$in_overflow} { - #render controls into overflow/remainder output - priv::render_addchar $idx $code [list] [list] $insert_mode - incr idx ;#take up a column for each control sequence too - continue - } #since this element isn't a grapheme - advance idx_over to next grapheme overlay when about to fill 'unapplied' - + + set re_mode {\x1b\[\?([0-9]*)(h|l)} ;#e.g DECAWM set re_col_move {\x1b\[([0-9]*)(C|D|G)$} set re_row_move {\x1b\[([0-9]*)(A|B)$} set re_both_move {\x1b\[([0-9]*)(?:;){0,1}([0-9]*)H$} ;# or "f" ? @@ -1512,7 +1577,8 @@ proc overtype::renderline {args} { lassign $matchinfo _match num type switch -- $type { D { - #left-arrow/move-back + #cursor back + #left-arrow/move-back when ltr mode if {$num eq ""} {set num 1} incr idx -$num incr cursor_column -$num @@ -1523,24 +1589,42 @@ proc overtype::renderline {args} { } } C { + #todo - consider right-to-left cursor mode (e.g Hebrew).. some day. + #cursor forward #right-arrow/move forward if {$num eq ""} {set num 1} #todo - retrict to moving 1 position past datalen? restrict to column width? #should ideally wrap to next line when interactive and not on last row + #(some ansi art seems to expect this behaviour) + #This presumably depends on the terminal's wrap mode + #e.g DECAWM autowrap mode + # CSI ? 7 h - set: autowrap (also tput smam) + # CSI ? 7 l - reset: no autowrap (also tput rmam) set version 2 if {$version eq "2"} { set max [llength $outcols] - if {!$opt_overflow} { - } else { + if {$opt_overflow} { incr max } - if {($cursor_column + $num) > $max} { - set cursor_column $max - set idx [expr {$cursor_column -1}] - } else { + if {($cursor_column + $num) <= $max} { incr idx $num incr cursor_column $num + } else { + if {$opt_autowrap_mode} { + #horizontal movement beyond line extent needs to wrap - throw back to caller + #we may have both overflow_rightand unapplied data + #(can have overflow_right if we were in insert_mode and processed chars prior to this movement) + #leave row as is - caller will need to determine how many rows the column-movement has consumed + incr cursor_column $num ;#give our caller the necessary info + incr idx_over + priv::render_unapplied $overlay_grapheme_control_list $gci + set instruction wrap + break + } else { + set cursor_column $max + set idx [expr {$cursor_column -1}] + } } } else { if {!$opt_overflow || ($cursor_column + $num) <= [llength $outcols+1]} { @@ -1773,7 +1857,7 @@ proc overtype::renderline {args} { } } #append cursor_saved_attributes [join $sgr_stack ""] - append cursor_saved_attributes [punk::ansi::codetype::sgr_merge {*}$sgr_stack] + append cursor_saved_attributes [punk::ansi::codetype::sgr_merge_list {*}$sgr_stack] if 0 { @@ -1829,41 +1913,56 @@ proc overtype::renderline {args} { set instruction move set cursor_restore_required 1 break - } - } - sgr { - #prior to overflow - we have our sgr codes already in stacks - #post-overflow we need to keep them in order along with non sgr codes and graphemes - if {$in_overflow} { - set code $item - #render controls into output - will become overflow/remainder - priv::render_addchar $idx $code [list] [list] $insert_mode - incr idx ;#take up an overflow column for each control sequence too - } - } - gx0 { - if {$in_overflow} { - set code $item - if {$code eq "gx0_on"} { - set actual "\x1b(0" - } else { - set actual "\x1b(B" + }\ + $re_mode { + switch -- $num { + 5 { + #DECSNM - reverse video + #How we simulate this to render within a block of text is an open question. + #track all SGR stacks and constantly flip based on the current SGR reverse state? + #It is the job of the calling loop to do this - so at this stage we'll just set the states + #DECAWM autowrap + if {$type eq "h"} { + #set (enable) + set reverse_mode 1 + } else { + #reset (disable) + set reverse_mode 0 + } + + } + 7 { + #DECAWM autowrap + if {$type eq "h"} { + #set (enable) + set autowrap_mode 1 + if {$opt_width ne "\uFFEF"} { + set overflow_idx $opt_width + } else { + #review - this is also the cursor position when adding a char at end of line? + set overflow_idx [expr {[llength $undercols]}] ;#index at which we would be *in* overflow a row move may still override it + } + #review - can idx ever be beyond overflow_idx limit when we change e.g with a width setting and cursor movements? presume not - but sanity check for now. + if {$idx >= $overflow_idx} { + puts stderr "renderline error - idx '$idx' >= overflow_idx '$overflow_idx' - unexpected" + } + } else { + #reset (disable) + set autowrap_mode 0 + set overflow_idx -1 + } + } } - priv::render_addchar $idx $actual [list] [list] $insert_mode - incr idx } - + } + default { + #don't need to handle sgr or gx0 types + #we have our sgr gx0 codes already in stacks for each overlay grapheme } } } #-------------- - #if {$in_overflow} { - # #set cursor_column [expr {$overflow_idx -1}] - # set cursor_column [expr {$overflow_idx +1}] - #} else { - # set cursor_column [expr {$idx + 1}] - #} if {$opt_overflow == 0} { @@ -1887,6 +1986,10 @@ proc overtype::renderline {args} { set prev_g0 [list] #note overflow_idx may already have been set lower if we had a row move above due to \v or ANSI moves set in_overflow 0 ;#used to stop char-width scanning once in overflow + if {$overflow_idx == 0} { + #how does caller avoid an infinite loop if they have autowrap on and keep throwing graphemes to the next line? REVIEW + set in_overflow 1 + } foreach ch $outcols { #puts "---- [ansistring VIEW $ch]" @@ -1905,7 +2008,7 @@ proc overtype::renderline {args} { if {$i < [llength $understacks]} { set cstack [lindex $understacks $i] #append overflow_right [join $cstack ""] - append overflow_right [punk::ansi::codetype::sgr_merge {*}$cstack] + append overflow_right [punk::ansi::codetype::sgr_merge_list {*}$cstack] } } append overflow_right $ch @@ -1944,7 +2047,7 @@ proc overtype::renderline {args} { append outstring \033\[m } #append outstring [join $cstack ""] - append outstring [punk::ansi::codetype::sgr_merge {*}$cstack] + append outstring [punk::ansi::codetype::sgr_merge_list {*}$cstack] } set prevstack $cstack } else { @@ -1965,7 +2068,7 @@ proc overtype::renderline {args} { } if {$tail_idx-1 < [llength $understacks]} { #set replay_codes [join [lindex $understacks $tail_idx-1] ""] ;#tail replay codes - set replay_codes [punk::ansi::codetype::sgr_merge [lindex $understacks $tail_idx-1]] ;#tail replay codes + set replay_codes [punk::ansi::codetype::sgr_merge_list {*}[lindex $understacks $tail_idx-1]] ;#tail replay codes } if {$tail_idx-1 < [llength $understacks_gx]} { set gx0 [lindex $understacks_gx $tail_idx-1] @@ -1987,22 +2090,30 @@ proc overtype::renderline {args} { #replay_codes_underlay is the set of codes in effect at the very end of the original underlay #replay_codes_overlay is the set of codes in effect at the very end of the original overlay (even if not all overlay was applied) #todo - replay_codes for gx0 mode + + #overflow_idx may change during ansi & character processing + if {$overflow_idx == -1} { + set overflow_right_column "" + } else { + set overflow_right_column [expr {$overflow_idx+1}] + } return [list\ result $outstring\ visualwidth [punk::ansi::printing_length $outstring]\ instruction $instruction\ stringlen [string length $outstring]\ - overflow_idx $overflow_idx\ + overflow_right_column $overflow_right_column\ overflow_right $overflow_right\ unapplied $unapplied\ insert_mode $insert_mode\ + autowrap_mode $autowrap_mode\ insert_lines_above $insert_lines_above\ insert_lines_below $insert_lines_below\ cursor_saved_position $cursor_saved_position\ cursor_saved_attributes $cursor_saved_attributes\ cursor_restore_required $cursor_restore_required\ - cursor_column $cursor_column\ - cursor_row_change $cursor_row\ + cursor_column $cursor_column\ + cursor_row $cursor_row\ opt_overflow $opt_overflow\ replay_codes $replay_codes\ replay_codes_underlay $replay_codes_underlay\ @@ -2075,7 +2186,8 @@ namespace eval overtype::priv { #set unapplied [join [lrange $overlay_grapheme_control_list $gci+1 end]] set unapplied "" - append unapplied [join [lindex $overstacks $idx_over] ""] + #append unapplied [join [lindex $overstacks $idx_over] ""] + append unapplied [punk::ansi::codetype::sgr_merge_list {*}[lindex $overstacks $idx_over]] switch -- [lindex $overstacks_gx $idx_over] { "gx0_on" { append unapplied "\x1b(0" @@ -2097,7 +2209,6 @@ namespace eval overtype::priv { } else { append unapplied $item } - #incr idx_over } } proc render_delchar {i} {