# @c can/should we use something like this?: 'format "%-*s" $len $overtext
# @c can/should we use something like this?: 'format "%-*s" $len $overtext
variable default_ellipsis_horizontal
variable default_ellipsis_horizontal
@ -268,6 +272,7 @@ proc overtype::left {args} {
set underblock [string map $norm $underblock]
set underblock [string map $norm $underblock]
set overblock [string map $norm $overblock]
set overblock [string map $norm $overblock]
#set underlines [split $underblock \n]
#set underlines [split $underblock \n]
#underblock is a 'rendered' block - so width height make sense
#underblock is a 'rendered' block - so width height make sense
@ -276,7 +281,11 @@ proc overtype::left {args} {
} else {
} else {
set colwidth $opt_width
set colwidth $opt_width
}
}
if {$underblock eq ""} {
set underlines [list "\x1b\[0m\x1b\[0m"]
} else {
set underlines [lines_as_list -ansiresets 1 $underblock]
set underlines [lines_as_list -ansiresets 1 $underblock]
}
set overlines [split $overblock \n]
set overlines [split $overblock \n]
#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
#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
set overtext [lindex $overlines $overidx]; lset overlines $overidx ""
set overtext [lindex $overlines $overidx]; lset overlines $overidx ""
set undertext [lindex $outputlines [expr {$row -1}]]
set undertext [lindex $outputlines [expr {$row -1}]]
set renderedrow $row
set renderedrow $row
@ -311,6 +324,7 @@ proc overtype::left {args} {
}
}
#review insert_mode. As an 'overtype' function whose main function is not interactive keystrokes - insert is secondary -
#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 adequately we need to support IRM (insertion-replacement mode) ESC [ 4 h|l
#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
dict set replay_codes_underlay [expr {$renderedrow+1}] [dict get $rinfo replay_codes_underlay]
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]
lset replay_codes_overlay [expr $overidx+1] [dict get $rinfo replay_codes_overlay]
#-- todo - detect looping properly
#-- todo - detect looping properly
if {$row > 1 && $overtext ne "" && $unapplied eq $overtext && $post_render_row == $row} {
if {$row > 1 && $overtext ne "" && $unapplied eq $overtext && $post_render_row == $row} {
puts stderr "overtype::left loop?"
puts stderr "overtype::left loop?"
@ -341,7 +357,6 @@ proc overtype::left {args} {
set cursor_saved_position $c_saved_pos
set cursor_saved_position $c_saved_pos
set cursor_saved_attributes $c_saved_attributes
set cursor_saved_attributes $c_saved_attributes
}
}
set cursor_restore_required [dict get $rinfo cursor_restore_required]
#background line is narrower than data in line
#background line is narrower than data in line
@ -391,7 +406,59 @@ proc overtype::left {args} {
set nextprefix ""
set nextprefix ""
if {$cursor_restore_required} {
#todo - handle potential insertion mode as above for cursor restore?
#keeping separate branches for debugging - review and merge as appropriate when stable
switch -- $instruction {
{} {
flush stdout
if {$unapplied eq "" && [ansistring length $rendered]} {
#consumed all overlay - no instruction
set col 1
incr row
} else {
set col 1
incr row
}
}
up {
#renderline knows it's own line number, and knows not to go above row l
#it knows that a move whilst 1-beyond the width conflicts with the linefeed and reduces the move by one accordingly.
#row returned should be correct.
#column may be the overflow column - as it likes to report that to the caller.
#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"
#puts stdout "1 row:$row col $col"
set row $post_render_row
set col $post_render_col
#puts stdout "2 row:$row col $col"
#puts stdout "-----------------------"
#puts stdout $rinfo
#flush stdout
}
down {
#renderline doesn't know how far down we can go..
if {$post_render_row > [llength $outputlines]} {
if {$opt_appendlines} {
set diff [expr {$post_render_row - [llength $outputlines]}]
if {$diff > 0} {
lappend outputlines {*}[lrepeat $diff ""]
}
lappend outputlines ""
}
}
set row $post_render_row
set col $post_render_col
}
restore_cursor {
#testfile belinda.ans uses this
#puts stdout "[a+ blue bold]CURSOR_RESTORE[a]"
if {[dict exists $cursor_saved_position row]} {
if {[dict exists $cursor_saved_position row]} {
set row [dict get $cursor_saved_position row]
set row [dict get $cursor_saved_position row]
set col [dict get $cursor_saved_position column]
set col [dict get $cursor_saved_position column]
@ -413,88 +480,66 @@ proc overtype::left {args} {
#this overflow data has previously been rendered so has no cursor movements or further save/restore operations etc
#this overflow data has previously been rendered so has no cursor movements or further save/restore operations etc
#we can just insert another call to renderline to solve this.. ?
#we can just insert another call to renderline to solve this.. ?
#It would perhaps be more properly handled as a queue of instructions from our initial renderline call
#It would perhaps be more properly handled as a queue of instructions from our initial renderline call
#we don't need to worry about overflow next call- but we should carry forward our gx and ansi stacks
#we don't need to worry about overflow next call (?)- but we should carry forward our gx and ansi stacks
set blank [string repeat " " [punk::ansi::printing_length $overflow_right]] ;#use size of rendered overflow rather than colwidth which would add trailing space
set foldline [overtype::renderline $blank $overflow_right]
set sub_info [overtype::renderline -info 1 -width $colwidth -insert_mode $insert_mode -autowrap_mode $autowrap_mode -overflow [dict get $opts -overflow] "" $overflow_right]
set foldline [dict get $sub_info result]
set insert_mode [dict get $sub_info insert_mode] ;#probably not needed..
set autowrap_mode [dict get $sub_info autowrap_mode] ;#nor this..
linsert outputlines $renderedrow $foldline
linsert outputlines $renderedrow $foldline
#review - row & col set by restore - but not if there was no save..
#review - row & col set by restore - but not if there was no save..
}
}
set overflow_handled 1
set overflow_handled 1
} else {
#todo - handle potential insertion mode as above for cursor restore?
#keeping separate branches for debugging - review and merge as appropriate when stable
switch -- $instruction {
"" {
if {$unapplied eq ""} {
#consumed all overlay - no instruction
set col 1
incr row
} else {
set col 1
incr row
}
}
move {
########
#Ansi moves need to create new lines
if {$post_render_row > [llength $outputlines]} {
if {$opt_appendlines} {
set diff [expr {$post_render_row - [llength $outputlines]}]
if {$diff > 0} {
lappend outputlines {*}[lrepeat $diff ""]
}
}
up {
#renderline already knows not to go above l
#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 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 {
} else {
set col $post_render_col
}
}
down {
#renderline doesn't know how far down we can go..
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]
set row [llength $outputlines]
}
} else {
} else {
set row $post_render_row
set row $post_render_row
}
}
#######
set col $post_render_col
set col $post_render_col
#overflow + unapplied?
}
}
move {
newlines_above {
if {$post_render_row > [llength $outputlines]} {
#renderline doesn't advance the row for us - the caller has the choice to implement or not
set row [llength $outputlines]
} else {
set row $post_render_row
set row $post_render_row
}
set col $post_render_col
set col $post_render_col
#overflow + unapplied?
if {$new_lines_above > 0} {
set outputlines [linsert $outputlines $row [lrepeat $new_lines_above ""]]
incr row $new_lines_above ;#we should end up on the same line of text (at a different index), with new empties inserted above
}
}
}
newline_above - newline_below {
newlines_below {
#todo
puts newlines_below
}
}
wrap {
wrapmoveforward {
#hard wraps in this context.
#doesn't seem to be used by fruit.ans testfile
#used by dzds.ans
#note that cursor_forward may move deep into the next line - or even span multiple lines !TODO
#note that cursor_forward may move deep into the next line - or even span multiple lines !TODO
if {$overflow_right_column eq ""} {
set c $colwidth
#so why are we getting a wrap instruction?
set r $post_render_row
puts stderr "overtype::left wrap instruction when no overflow_right_column\n$rinfo"
if {$post_render_col > $colwidth} {
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?
#-returnextra enables returning of overflow and length
#review - use punk::ansi::ta::detect to short-circuit processing and do simpler string calcs as an optimisation?
#review - use punk::ansi::ta::detect to short-circuit processing and do simpler string calcs as an optimisation?
#review - DECSWL/DECDWL double width line codes - very difficult/impossible to align and compose with other elements
#review - DECSWL/DECDWL double width line codes - very difficult/impossible to align and compose with other elements
#(could render it by faking it with sixels and a lot of work - find/make a sixel font and ensure it's exactly 2 cols per char)
#todo - review transparency issues with single/double width characters
#todo - review transparency issues with single/double width characters
#bidi - need a base direction and concept of directional runs for RTL vs LTR - may be best handled at another layer?
#bidi - need a base direction and concept of directional runs for RTL vs LTR - may be best handled at another layer?
proc overtype::renderline {args} {
proc overtype::renderline {args} {
#*** !doctools
#[call [fun overtype::renderline] [arg args] ]
#[para] renderline is the core engine for overtype string processing (frames & textblocks), and the raw mode commandline repl for the Tcl Punk Shell
#[para] It is also a central part of an ansi (micro) virtual terminal-emulator of sorts
#[para] This system does a half decent job at rendering 90's ANSI art to manipulable colour text blocks that can be joined & framed for layout display within a unix or windows terminal
#[para] Renderline helps maintain ANSI text styling reset/replay codes so that the styling of one block doesn't affect another.
#[para] Calling on the punk::ansi library - it can coalesce codes to keep the size down.
#[para] It is a giant mess of doing exactly what common wisdom says not to do... lots at once.
#[para] renderline is part of the Unicode and ANSI aware Overtype system which 'renders' a block of text onto a static underlay
#[para] The underlay is generally expected to be an ordered set of lines or a rectangular text block analogous to a terminal screen - but it can also be ragged in line length, or just blank.
#[para] The overlay couuld be similar - in which case it may often be used to overwrite a column or section of the underlay.
#[para] The overlay could however be a sequence of ANSI-laden text that jumps all over the place.
#
#[para] renderline itself only deals with a single line - or sometimes a single character. It is generally called from a loop that does further terminal-like or textblock processing.
#[para] By suppyling the -info 1 option - it can return various fields indicating the state of the render.
#[para] The main 3 are the result, overflow_right, and unapplied.
#[para] Renderline handles cursor movements from either keystrokes or ANSI sequences but for a full system the aforementioned loop will need to be in place to manage the set of lines under manipulation.
#an ugly hack to serve *some* common case ascii quickly with byte-compiled literal switch - feels dirty.
#an ugly hack to serve *some* common case ascii quickly with byte-compiled literal switch - feels dirty.
#.. but even 0.5uS per char (grapheme_width_cached) adds up quickly when stitching lots of lines together.
#.. but even 0.5uS per char (grapheme_width_cached) adds up quickly when stitching lots of lines together.
switch -- $grapheme {
switch -- $grapheme {
" " - - - _ - ! - @ - # - $ - % - ^ - & - * - = - + - : - . - , - / - | - ? - a - b - c - d - e - f - g - h - i - j - k - l - m - n - o - p - q - r - s - t - u - v - w - x - y - z - A - B - C - D - E - F - G - H - I - J - K - L - M - N - O - P - Q - R - S - T - U - V - W - X - Y - Z - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 {
set insert_lines_above 1 ;#keep for consistency with ansi sequence that requests insertion of line(s)?
set insert_lines_above 1 ;#keep for consistency with ansi sequence that requests insertion of line(s)?
set instruction newline_above
set instruction newlines_above
} else {
} else {
set insert_lines_below 1
set insert_lines_below 1
set instruction newline_below
set instruction newlines_below
}
}
break
break
#set cursor_column 1
}
}
}
"<cr>" {
"<cr>" {
#consider also the old space-carriagereturn softwrap convention used in some terminals.
#In the context of rendering to a block of text - this works similarly in that the space gets eaten so programs emitting space-cr at the terminal width col will pretty much get what they expect.
#a real terminal would not be able to know the state of the underlay.. so we should probably ignore it.
#a real terminal would not be able to know the state of the underlay.. so we should probably ignore it.
#set sgr_stack [lindex $understacks $idx]
#set sgr_stack [lindex $understacks $idx]
#set gx_stack [lindex $understacks_gx $idx] ;#not actually a stack - just a boolean state (for now?)
#set gx_stack [lindex $understacks_gx $idx] ;#not actually a stack - just a boolean state (for now?)
set sgr_stack [list]
set sgr_stack [list]
set gx_stack [list]
set gx_stack [list]
#we shouldn't need to scan for intermediate cursor save/restores - as restores would throw-back to the calling loop - so our overlay 'line' is since those.
#The overlay_grapheme_control_list had leading resets from previous lines - so we go back to the beginning not just the first grapheme.