Browse Source

ansi fixes - cursor movement

master
Julian Noble 9 months ago
parent
commit
c29cf8b146
  1. 151
      src/modules/punk/ansi-999999.0a1.0.tm
  2. 2
      src/modules/punk/lib-999999.0a1.0.tm
  3. 5
      src/modules/punk/repl-0.1.tm
  4. 405
      src/vendormodules/overtype-1.5.9.tm

151
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,20 +182,46 @@ 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]
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 {
set ansidata $data
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]
return $obj
$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 :-)
@ -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

2
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

5
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]

405
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]
set underlines [lines_as_list -ansiresets 1 $underblock]
#set colwidth [tcl::mathfunc::max {*}[lmap v $underlines {punk::ansi::printing_length $v}]]
#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 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
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} {
@ -1210,12 +1298,6 @@ proc overtype::renderline {args} {
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?
@ -1225,32 +1307,20 @@ proc overtype::renderline {args} {
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
#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
#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 {
#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
incr idx_over -1 ;#set overlay grapheme index back one so that current ch is considered unapplied
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!
@ -1258,11 +1328,12 @@ proc overtype::renderline {args} {
#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
incr idx
continue
#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
}
}
@ -1354,7 +1425,7 @@ proc overtype::renderline {args} {
}
"<cr>" {
set idx [expr {$opt_colstart -1}]
set cursor_column 1 ;#?
set cursor_column $opt_colstart ;#?
}
"<bs>" {
#literal backspace char - not necessarily from keyboard
@ -1486,17 +1557,11 @@ proc overtype::renderline {args} {
} ;# 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
}\
$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"
}
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
} else {
#reset (disable)
set autowrap_mode 0
set overflow_idx -1
}
}
gx0 {
if {$in_overflow} {
set code $item
if {$code eq "gx0_on"} {
set actual "\x1b(0"
} else {
set actual "\x1b(B"
}
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_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} {

Loading…
Cancel
Save