Browse Source

overtype fixes, raw-mode repl fixes - literal tab handling, punk::ansi fixes

master
Julian Noble 3 months ago
parent
commit
e67566bfdf
  1. 1050
      src/bootsupport/modules/overtype-1.7.4.tm
  2. 1
      src/bootsupport/modules/punk/aliascore-0.1.0.tm
  3. 95
      src/bootsupport/modules/punk/ansi-0.1.1.tm
  4. 168
      src/bootsupport/modules/punk/console-0.1.1.tm
  5. 8
      src/bootsupport/modules/punk/lib-0.1.6.tm
  6. 112
      src/bootsupport/modules/punk/repl-0.1.2.tm
  7. 228
      src/modules/opunk/str-999999.0a1.0.tm
  8. 1050
      src/modules/overtype-999999.0a1.0.tm
  9. 1
      src/modules/punk/aliascore-999999.0a1.0.tm
  10. 95
      src/modules/punk/ansi-999999.0a1.0.tm
  11. 168
      src/modules/punk/console-999999.0a1.0.tm
  12. 8
      src/modules/punk/lib-999999.0a1.0.tm
  13. 112
      src/modules/punk/repl-999999.0a1.0.tm
  14. 1050
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/overtype-1.7.4.tm
  15. 1
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/aliascore-0.1.0.tm
  16. 95
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/ansi-0.1.1.tm
  17. 168
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/console-0.1.1.tm
  18. 8
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/lib-0.1.6.tm
  19. 112
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/repl-0.1.2.tm
  20. 1050
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/overtype-1.7.4.tm
  21. 1
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/aliascore-0.1.0.tm
  22. 95
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/ansi-0.1.1.tm
  23. 168
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/console-0.1.1.tm
  24. 8
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/lib-0.1.6.tm
  25. 112
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/repl-0.1.2.tm
  26. 26
      src/vfs/_vfscommon.vfs/lib/app-shellspy/shellspy.tcl
  27. 228
      src/vfs/_vfscommon.vfs/modules/opunk/str-0.1.0.tm
  28. 1050
      src/vfs/_vfscommon.vfs/modules/overtype-1.7.4.tm
  29. 1
      src/vfs/_vfscommon.vfs/modules/punk/aliascore-0.1.0.tm
  30. 95
      src/vfs/_vfscommon.vfs/modules/punk/ansi-0.1.1.tm
  31. 168
      src/vfs/_vfscommon.vfs/modules/punk/console-0.1.1.tm
  32. 8
      src/vfs/_vfscommon.vfs/modules/punk/lib-0.1.6.tm
  33. 112
      src/vfs/_vfscommon.vfs/modules/punk/repl-0.1.2.tm

1050
src/bootsupport/modules/overtype-1.7.4.tm

File diff suppressed because it is too large Load Diff

1
src/bootsupport/modules/punk/aliascore-0.1.0.tm

@ -123,6 +123,7 @@ tcl::namespace::eval punk::aliascore {
stripansi ::punk::ansi::ansistrip
ansiwrap ::punk::ansi::ansiwrap
grepstr ::punk::ansi::grepstr
untabify ::punk::ansi::untabify
colour ::punk::console::colour
color ::punk::console::colour
ansi8 ::punk::ansi8

95
src/bootsupport/modules/punk/ansi-0.1.1.tm

@ -726,19 +726,28 @@ tcl::namespace::eval punk::ansi {
} else {
set cols 80
}
set rows 24
if {[dict exists $sdict columns]} {
set c [dict get $sdict columns]
if {$c > 0} {
set cols $c
}
}
#set rows 24
set rows -1
if {[dict exists $sdict rows]} {
set r [dict get $sdict rows]
if {$r > 0} {
set rows $r
}
}
if {$rows == -1} {
set consize [punk::console::get_size]
if {$consize ne ""} {
set rows [expr {[dict get $consize rows] -1}]
} else {
set rows 24
}
}
set dimensions ${cols}x${rows}
}
lassign [split $dimensions x] cols rows
@ -1169,6 +1178,17 @@ tcl::namespace::eval punk::ansi {
a form where the ANSI only represents SGR styling (and perhaps other non-movement
codes) using something like overtype::renderline or overtype::rendertext.
Note that traditional terminals and most modern emulators treat the tab character as a
movement code
So even though the a regex such as {\s} will match various whitespace characters including tabs,
- ANSI SGR codes will not highlight, underline etc the tab characters themselves.
**********************************************************************************************
Also - tab characters are *currently* likely to cause output misalignment/truncation
especially if multiple rounds of grepstr are applied.
TODO - find a better way to handle tab characters in input.
For now - the input should be untabified first e.g with punk::ansi::untabify or similar.
**********************************************************************************************
Examples for returning matched lines only in the help output of the 'i' command:
@ -1359,6 +1379,9 @@ tcl::namespace::eval punk::ansi {
if {[dict exists $received -ansistrip]} {
set data [punk::ansi::ansistrip $data]
}
if {[string first \t $data] >= 0} {
puts stderr "Warning: grepstr does not currently handle tab characters in the input data. Tabs should be expanded to spaces first e.g with punk::ansi::untabify or similar."
}
set highlight [dict get $opts -highlight]
set opt_returnlines [dict get $opts -return]
set opt_invert [dict exists $received -invert-match]
@ -6229,40 +6252,44 @@ be as if this was off - ie lone CR.
#https://crates.io/crates/unicode-display-width
#JMN
set width 0
set sumwidth 0
foreach g $outchars {
set dec_codes [scan $g [tcl::string::repeat %c [string length $g]]]
set maxwidth 0
set max_component_width 0 ;#longest width of any single char in the grapheme cluster
set gwidth 0 ;#width of the whole grapheme cluster
foreach dec $dec_codes {
if {$dec <= 255} {
if {$dec >= 31 && $dec != 127} {
incr sumwidth 1
if {$maxwidth == 0} {
set maxwidth 1
incr gwidth 1
if {$max_component_width == 0} {
set max_component_width 1
}
}
} elseif {$c < 917504 || $c > 917631} {
#codepoint not in the zero-width unicode tag block - \UE0000-\UE000F
#set w [punk::char::char_width $dec]
set w [textutil::wcswidth_char $dec]
if {$w > $maxwidth} {
set maxwidth $w
}
if {$maxwidth == 2} {
#we can stop looking at this grapheme - it's already double width
break
#if {$max_component_width == 2 && $w == 2} {
# #we can stop looking at this grapheme - it's already double width
# break
#}
if {$w > $max_component_width} {
set max_component_width $w
}
incr sumwidth $w
incr gwidth $w
}
}
if {$maxwidth == 2} {
set width 2
} else {
set width $sumwidth
if {$max_component_width == 2} {
#if any char in the grapheme is double width - then the whole grapheme is double width
#This is a simplification that doesn't handle complex scripts such as arabic ligatures.
#review - find a more broadly accurate method of calculating grapheme width that handles complex cases such as ligatures and combiners affecting width.
#we could for example restrict this rule to cases where the base char is east asian width 2 - which would cover the common cases of double-width CJK chars and emojis - but would still allow for combiners and ligatures to affect width in other cases.
set gwidth 2
}
incr sumwidth $gwidth
}
return $width
return $sumwidth
#return [punk::char::ansifreestring_width [join $outchars ""]]
}
namespace eval internal {
@ -6705,7 +6732,8 @@ be as if this was off - ie lone CR.
set scopeinfo [dict create tabstops $tabstops columncount $ccount columnmaxwidths $colmaxwidths]
} else {
#same number of tabs as previous row - so same scope
set scope_key [expr {$r-1}]
#set scope_key [expr {$r-1}] ;#wrong - scope key is the first row in the scope - which is only necessarily the previous row if the previous row is the first row in the scope.
#just leave scope_key as it is from the previous iteration of the loop.
set scopeinfo [dict get $scopes $scope_key]
set update_colmaxwidths [dict get $scopeinfo columnmaxwidths]
set update_tstops [dict get $scopeinfo tabstops]
@ -6866,7 +6894,7 @@ be as if this was off - ie lone CR.
-with -type list|literal(spaces)|literal(unicode) -minsize 2 -maxsize 2 -default spaces -help\
"Whether to replace the tab characters with a custom pair of characters, a plain string of spaces, or to use
the default unicode characters to represent the tabs and following whitespace.
The character \U21E5 followed by a calculated number of \U00A0 (non-breaking space) characters is used when 'unicode'
The character \\U21E5 (\U21E5) followed by a calculated number of \\U00A0 (non-breaking space) characters is used when 'unicode'
is specified.
A 2 element list can be supplied to specify the character to use for the tab and the character to use for the
@ -6997,6 +7025,7 @@ be as if this was off - ie lone CR.
#puts stderr "tstops: $tstops maxdiff: $maxdiff"
set num [lindex $tstops 0]
set out [list]
set lines [list]
foreach line [split $textblock \n] {
#lappend out [untabifyLine $line $maxdiff]
set currPos 0
@ -7955,23 +7984,21 @@ tcl::namespace::eval punk::ansi {
#safe jumptable test
#dict for {k v} $codestate {}
tcl::dict::for {k v} $codestate {
switch -- $v {
"" {}
default {
switch -- $k {
underlinecolour {
append unmergeable "${v}\;"
}
underextended {
#review
append unmergeable "${v}\;"
}
default {
append codemerge "${v}\;"
}
if {$v ne ""} {
switch -- $k {
underlinecolour - underextended {
append unmergeable "${v}\;"
}
default {
append codemerge "${v}\;"
}
}
}
#switch -- $v {
# "" {}
# default {
# }
#}
}
}
set reset_prior 0

168
src/bootsupport/modules/punk/console-0.1.1.tm

@ -1445,15 +1445,15 @@ namespace eval punk::console {
set width [dict get $tsize columns]
set mod [expr {$width % $w}]
set max [expr {$width - $mod}]
set tstops ""
set tstop_ansi ""
set c 1
while {$c <= $max} {
append tstops [string repeat " " $w][punk::ansi::set_tabstop]
append tstop_ansi [string repeat " " $w][punk::ansi::set_tabstop]
incr c $w
}
set punk::console::tabwidth $w ;#we also attempt to read terminal's tabstops and set tabwidth to the apparent spacing of first non-1 value in tabstops list.
catch {textutil::tabify::untabify2 "" $w} ;#textutil tabify can end up uninitialised and raise errors like "can't read Spaces(<n>).." after a tabstop change This call seems to keep tabify happy - review.
puts -nonewline "[punk::ansi::clear_all_tabstops]\n[punk::ansi::set_tabstop]$tstops"
set ::punk::console::tabwidth $w ;#we also attempt to read terminal's tabstops and set tabwidth to the apparent spacing of first non-1 value in tabstops list.
#catch {textutil::tabify::untabify2 "" $w} ;#textutil tabify can end up uninitialised and raise errors like "can't read Spaces(<n>).." after a tabstop change This call seems to keep tabify happy - review.
puts -nonewline "[punk::ansi::clear_all_tabstops]\n[punk::ansi::set_tabstop]$tstop_ansi"
}
@ -2574,6 +2574,46 @@ namespace eval punk::console {
dict set DECRQSS_DICT $name $str
}
namespace eval argdoc {
proc decrqss_choices_and_help {} {
#return a string for use in the punk::args definition for ::punk::console::dec_request_setting that lists the valid choices for the name argument, and their help text.
#This requires a -choices and -choicelabels pair in the args definition, to allow the help text to be directly associated with the choices.
upvar ::punk::console::DECRQSS_DATA DECRQSS_DATA
set outstr "name -type string -choices \{"
foreach {desc name str} $DECRQSS_DATA {
append outstr " $name"
}
append outstr " \}"
append outstr " -choicecolumns 4 -choicelabels \{" \n
foreach {desc name str} $DECRQSS_DATA {
append outstr " $name \{ ${desc}\}" \n
}
append outstr " \} -help "
append outstr " {Dec setting name to query.}"
return $outstr
#append argdef " \} -choicecolumns $opt_columns -choicelabels \{" \n
#append argdef " $choicelabelsdict" \n
#append argdef " \} -choiceinfo \{$choiceinfodict\} -unindentedfields \{-choicelabels\}" \n
}
set DECRQSS_CHOICES [::punk::console::argdoc::decrqss_choices_and_help]
lappend PUNKARGS [list {
@id -id ::punk::console::dec_request_setting
@cmd -name punk::console::dec_request_setting\
-summary\
{Perform DECRQSS query to get DECRPSS response}\
-help\
{DECRQSS query for DEC Selection or Setting.
Return payload from console's DECRPSS response.
}
@opts
-console -type list -minsize 2 -default {stdin stdout}
@values -min 1 -max 1
${$DECRQSS_CHOICES}
}
]
}
#emit DECRQSS and get DECRPSS response
# ---------------------
#NOTE: https://vt100.net/docs/vt510-rm/DECRPSS.html
@ -2582,23 +2622,9 @@ namespace eval punk::console {
# shown at: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
#REVIEW
# ---------------------
#Note also on Freebsd xterm we seem to getresponse ': 1 $ r D...D ST'
#(no leading DCS) - this
#Note also on Freebsd xterm we seem to get response ': 1 $ r D...D ST'
#(no leading DCS) - review
lappend PUNKARGS [list {
@id -id ::punk::console::dec_request_setting
@cmd -name punk::console::dec_request_setting\
-summary\
{Perform DECRQSS query to get DECRPSS response}\
-help\
{DECRQSS query for DEC Selection or Setting.
Return payload from console's DECRPSS response.
}
@opts
-console -type list -minsize 2 -default {stdin stdout}
@values -min 1 -max 1
name -type string
}]
proc dec_request_setting {args} {
set argd [punk::args::parse $args -cache 1 withid ::punk::console::dec_request_setting]
lassign [dict values $argd] leaders opts values
@ -2631,6 +2657,106 @@ namespace eval punk::console {
return [string range $payload 3 end]
}
namespace eval argdoc {
lappend PUNKARGS [list {
@id -id ::punk::console::cursor_style
@cmd -name punk::console::cursor_style\
-summary\
{Get/Set cursor style}\
-help\
{Set cursor style using DECSCUSR.
Note that support for DECSCUSR may be inconsistent across terminals.
Some terminals may only support a subset of the possible styles.}
@opts
-console -type list -minsize 2 -default {stdin stdout}
@values -min 0 -max 1
style -type dict -optional 1 -choicerestricted 0 -choices {0 default 1 block-blink 2 block-steady 3 underline-blink 4 underline-steady 5 beam-blink 6 beam-steady} -choicecolumns 2 -help\
"Cursor style to set
May be an integer or name as in the choices or a dict of the form {<num> <name>}
as returned by a query of the current style.
e.g to set block-blink style, you could specify style as 1, block-blink or {1 block-blink}
After setting to 0 (default) - a subsequent query will not return {0 default} but rather the number and name
of the actual style that the terminal has reset to.
If not supplied, the current style will be queried and returned as a dict
where num is the key and name is the value, e.g. {2 block-steady}.
This allows querying the current style and then re-setting it after temporarily changing it."
}]
}
proc cursor_style {args} {
set argd [punk::args::parse $args -cache 1 withid ::punk::console::cursor_style]
lassign [dict values $argd] leaders opts values
set console [dict get $opts -console]
set style "" ;#if not supplied, will query current style instead of setting
set style_num_to_name [dict create {*}{
0 default
1 block-blink
2 block-steady
3 underline-blink
4 underline-steady
5 beam-blink
6 beam-steady
}]
set style_name_to_num [dict create {*}{
default 0
block-blink 1
block-steady 2
underline-blink 3
underline-steady 4
beam-blink 5
beam-steady 6
}]
if {[dict exists $values style]} {
set style [dict get $values style]
}
if {$style eq ""} {
#query current style using DECRQSS/DECRPSS (dec_request_setting DECSCUSR)
set resultnum [dec_request_setting -console $console DECSCUSR]
if {![string is integer -strict $resultnum]} {
error "cursor_style query for current style returned non-integer result '$resultnum'"
}
if {![dict exists $style_num_to_name $resultnum]} {
error "cursor_style query for current style returned unrecognised style number '$resultnum'. Known styles: [dict keys $style_num_to_name]"
}
set resultname [dict get $style_num_to_name $resultnum]
set result [dict create $resultnum $resultname]
return $result
}
if {[string is integer -strict $style]} {
set style_num $style
if {![dict exists $style_num_to_name $style_num]} {
error "cursor_style unrecognised style number '$style_num'. Known styles: [dict keys $style_num_to_name]"
}
} elseif {[::punk::args::lib::string_is_dict $style]} {
#allow setting cursor using the response from a previous call to query the current style.
if {[dict size $style] != 1} {
error "cursor_style style dict should have exactly one key-value pair {<num> <style>}. Got [dict size $style]"
}
dict for {style_num style_name} $style {} ;#extract the num and name from the dict.
if {![string is integer -strict $style_num]} {
error "cursor_style style dict should be of the form {<num> <value>}. Got '$style'"
}
if {![dict exists $style_num_to_name $style_num]} {
error "cursor_style unrecognised style number '$style_num'. Known styles: [dict keys $style_num_to_name]"
}
if {![dict exists $style_name_to_num $style_name]} {
error "cursor_style unrecognised style name '$style_name'. Known styles: [dict keys $style_name_to_num]"
}
} else {
if {![dict exists $style_name_to_num $style]} {
error "cursor_style unrecognised style '$style'. Known styles: [dict keys $style_name_to_num]"
}
set style_num [dict get $style_name_to_num $style]
}
#set cursor style using DECSCUSR
set request "\x1b\[${style_num} q"
set term_out [lindex $console 1]
puts -nonewline $term_out $request
flush $term_out
}
#terminals lie. This should be a reasonable (albeit relatively slow) test of actual width - but some terminals seem to miscalculate.
#todo - a visual interactive test/questionnaire to ask user if things are lining up or if the terminal is telling fibs about cursor position.
#todo - determine if these anomalies are independent of font

8
src/bootsupport/modules/punk/lib-0.1.6.tm

@ -297,6 +297,14 @@ tcl::namespace::eval punk::lib::compat {
#[list_begin definitions]
if {[catch {
::tcl::mathfunc::isinf 1
}]} {
#review - doesn't seem to cause int-rep of the value to shimmer - but does it reasonably emulate what tcl9's isinf does?
proc ::tcl::mathfunc::isinf {v} {
string match -nocase *inf* $v
}
}
if {"::lremove" ne [info commands ::lremove]} {

112
src/bootsupport/modules/punk/repl-0.1.2.tm

@ -1023,10 +1023,15 @@ namespace eval punk::repl::class {
append new0 \n
}
set underlay [punk::ansi::ansistrip $activeline]
set line_nextchar_col [expr {[punk::char::string_width $underlay] + 1}]
if {$o_cursor_col > $line_nextchar_col} {
set o_cursor_col $line_nextchar_col
}
#set line_nextchar_col [expr {[punk::char::string_width $underlay] + 1}]
set line_nextchar_col [expr {[punk::ansi::printing_length $underlay] + 1}]
#review
#if {$o_cursor_col > $line_nextchar_col} {
# set o_cursor_col $line_nextchar_col
#}
#set o_cursor_col $line_nextchar_col
set initial_insert_mode $o_insert_mode
set mergedinfo [overtype::renderline -info 1 -expand_right 1 -insert_mode $o_insert_mode -cursor_column $o_cursor_col -cursor_row $o_cursor_row $underlay $new0]
@ -1044,8 +1049,20 @@ namespace eval punk::repl::class {
# -- --- --- --- --- ---
set debug_first_row 2
#puts "merged: $mergedinfo"
set debug "add_chunk0"
append debug \n $mergedinfo
set debug "add_chunk[llength $o_chunk_list]"
#append debug \n $mergedinfo
set dinfo [dict create]
dict for {key val} $mergedinfo {
if {$key eq "result"} {
dict set dinfo $key $val
dict set dinfo resultview [ansistring VIEW -lf 1 -vt 1 $val]
} else {
if {$val ne ""} {
dict set dinfo $key $val
}
}
}
append debug \n [showdict $dinfo]
append debug \n "input:[ansistring VIEW -lf 1 -vt 1 $new0] before row:$o_cursor_row after row: $result_row before col:$o_cursor_col after col:$result_col"
package require textblock
set debug [textblock::frame -checkargs 0 -buildcache 0 $debug]
@ -1068,6 +1085,12 @@ namespace eval punk::repl::class {
incr nextrow
set o_cursor_col 1
}
lf_mid {
#for a chunk with embedded newlines - we need to insert lines below and move overflow_right down
#todo
lappend o_rendered_lines ""
set o_cursor_col 1
}
}
if {$insert_lines_below == 1} {
@ -1110,10 +1133,17 @@ namespace eval punk::repl::class {
}
#puts stderr "overtype::renderline -info 1 -expand_right 1 -insert_mode $o_insert_mode -cursor_column $o_cursor_col -cursor_row $o_cursor_row $activeline '$p'"
set underlay $activeline
set line_nextchar_col [expr {[punk::char::string_width $underlay] + 1}]
if {$o_cursor_col > $line_nextchar_col} {
set o_cursor_col $line_nextchar_col
}
#set line_nextchar_col [expr {[punk::char::string_width $underlay] + 1}]
set line_nextchar_col [expr {[punk::ansi::printing_length $underlay] + 1}]
#------------------
#review
#if {$o_cursor_col > $line_nextchar_col} {
# set o_cursor_col $line_nextchar_col
#}
#set o_cursor_col $line_nextchar_col
#------------------
set mergedinfo [overtype::renderline -info 1 -expand_right 1 -insert_mode $o_insert_mode -cursor_column $o_cursor_col -cursor_row $o_cursor_row $underlay $p]
set debug "add_chunk$i"
append debug \n $mergedinfo
@ -1121,13 +1151,14 @@ namespace eval punk::repl::class {
set debug [textblock::frame -checkargs 0 -buildcache 0 $debug]
#catch {punk::console::move_emitblock_return [expr {$debug_first_row + ($i * 6)}] 1 $debug}
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]
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]
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]
set overflow_right [dict get $mergedinfo overflow_right] ;#should be empty if no \v
set unapplied [dict get $mergedinfo unapplied]
set instruction [dict get $mergedinfo instruction]
set insert_lines_below [dict get $mergedinfo insert_lines_below]
if {[string is integer -strict $cmove]} {
if {$cmove == 0} {
set nextrow [expr {$o_cursor_row + 1}]
@ -1138,7 +1169,6 @@ namespace eval punk::repl::class {
} elseif {$cmove >= 1} {
}
} else {
# =<int> - absolute
set nextrow [string range $cmove 1 end]
@ -1147,6 +1177,12 @@ namespace eval punk::repl::class {
incr nextrow
}
set o_cursor_row $nextrow
if {$instruction eq "lf_mid"} {
set o_cursor_col 1
}
if {$insert_lines_below} {
}
@ -1164,6 +1200,17 @@ namespace eval punk::repl::class {
incr i
}
if {$o_insert_mode != $initial_insert_mode} {
set cursor_style_overtype {3 underline-blink}
set cursor_style_insert {5 beam-blink}
#-console ??
if {$o_insert_mode} {
punk::console::cursor_style $cursor_style_insert
} else {
punk::console::cursor_style $cursor_style_overtype
}
}
}
method add_rendered_chunk {rchunk} {
#split only on lf newlines - movement codes and \b \v \r not expected
@ -2110,26 +2157,43 @@ proc repl::repl_process_data {inputchan chunktype chunk stdinlines prompt_config
}
flush stdout
#move_column is more efficient than move since it doesn't require a response on stdin to determine current column,
#but doesn't seem to be universally supported (kermit95 vt modes for example)
#the Horizontal Position Absolute sequence ESC \[ n ` seems to be a possible alternative.
set leftmargin 3
# a+ cyan = CSI 36m
set active_line_colour [a+ cyan] ;# "\x1b\[36m"
#set space_colour "\x1b\[37m"
#set space_colour "\x1b\[38\;5\;244m" ;#term-grey46 - review - make configurable? May not be universal across terminals - but should be widely supported at least as a minimum for grey.
set space_colour [a+ brightblack] ;#"\x1b\[90m" ;#bright black - review - seems to be supported more widely than 256 colour greys]
set ebuf_line [untabify -with unicode [$editbuf line $cursor_row]]
set tail_col [expr {$leftmargin + [$editbuf cursor_column]}]
if {!$is_vt52} {
puts -nonewline stdout [a+ cyan][punk::ansi::move_column [expr {$leftmargin +1}]][punk::ansi::erase_eol][$editbuf line $cursor_row][a][punk::ansi::move_column [expr {$leftmargin + [$editbuf cursor_column]}]]
#puts -nonewline stdout [a+ cyan][punk::ansi::move_column [expr {$leftmargin +1}]][punk::ansi::erase_eol][$editbuf line $cursor_row][a][punk::ansi::move_column [expr {$leftmargin + [$editbuf cursor_column]}]]
set ebuf_line [string map [list { } "$space_colour.$active_line_colour"] $ebuf_line]
puts -nonewline stdout $active_line_colour[punk::ansi::move_column [expr {$leftmargin +1}]][punk::ansi::erase_eol]$ebuf_line[a][punk::ansi::move_column $tail_col]
} else {
puts -nonewline stdout [a+ cyan][punk::ansi::vt52move_column [expr {$leftmargin +1}]][punk::ansi::vt52erase_eol][$editbuf line $cursor_row][punk::ansi::vt52move_column [expr {$leftmargin + [$editbuf cursor_column]}]]
#puts -nonewline stdout [a+ cyan][punk::ansi::vt52move_column [expr {$leftmargin +1}]][punk::ansi::vt52erase_eol][$editbuf line $cursor_row][punk::ansi::vt52move_column [expr {$leftmargin + [$editbuf cursor_column]}]]
puts -nonewline stdout [punk::ansi::vt52move_column [expr {$leftmargin +1}]][punk::ansi::vt52erase_eol]$ebuf_line[punk::ansi::vt52move_column $tail_col]
}
#puts -nonewline stdout $chunk
flush stdout
if {[$editbuf last_char] eq "\n"} {
set linelen [punk::ansi::printing_length [$editbuf line $nextsubmit_line_num]]
set inactive_line_colour [a+ yellow bold]
set ebuf_next_line [untabify -with unicode [$editbuf line $nextsubmit_line_num]]
#set linelen [punk::ansi::printing_length [$editbuf line $nextsubmit_line_num]]
set linelen [punk::ansi::printing_length $ebuf_next_line]
if {!$is_vt52} {
puts -nonewline stdout [a+ cyan bold][punk::ansi::move_column [expr {$leftmargin +1}]][$editbuf line $nextsubmit_line_num][a][punk::ansi::move_column [expr {$leftmargin + $linelen +1}]]
set ebuf_next_line [string map [list { } "$space_colour.$inactive_line_colour"] $ebuf_next_line]
puts -nonewline stdout $inactive_line_colour[punk::ansi::move_column [expr {$leftmargin +1}]]$ebuf_next_line[a][punk::ansi::move_column [expr {$leftmargin + $linelen +1}]]
#screen_last_char_add "\n" input inputline
puts -nonewline stdout [punk::ansi::erase_eol]\n
} else {
puts -nonewline stdout [a+ cyan bold][punk::ansi::vt52move_column [expr {$leftmargin +1}]][$editbuf line $nextsubmit_line_num][punk::ansi::vt52move_column [expr {$leftmargin + $linelen +1}]]
puts -nonewline stdout [punk::ansi::vt52move_column [expr {$leftmargin +1}]]$ebuf_next_line[punk::ansi::vt52move_column [expr {$leftmargin + $linelen +1}]]
puts -nonewline stdout [punk::ansi::vt52erase_eol]\n
}

228
src/modules/opunk/str-999999.0a1.0.tm

@ -59,16 +59,18 @@ tcl::namespace::eval ::opunk::str {
lset this 2 {}
set o_ansisplits [lindex $this 3]
lset this 3 {}
set o_ptlist [lindex $this 4]
set o_elements [lindex $this 4]
lset this 4 {}
set o_elements [lindex $this 5]
set o_graphemes [lindex $this 5]
lset this 5 {}
set o_sgrstacks [lindex $this 6]
set o_ptindex [lindex $this 5]
lset this 6 {}
set o_gx0states [lindex $this 7]
set o_ptlist [lindex $this 7]
lset this 7 {}
set o_splitindex [lindex $this 8]
set o_sgrstacks [lindex $this 8]
lset this 8 {}
set o_gx0states [lindex $this 9]
lset this 9 {}
#---------------------------------------
}
@ -90,11 +92,12 @@ tcl::namespace::eval ::opunk::str {
lset this 1 $o_string
lset this 2 $o_count
lset this 3 $o_ansisplits
lset this 4 $o_ptlist
lset this 5 $o_elements
lset this 6 $o_sgrstacks
lset this 7 $o_gx0states
lset this 8 $o_splitindex
lset this 4 $o_elements
lset this 5 $o_graphemes
lset this 6 $o_ptindex
lset this 7 $o_ptlist
lset this 8 $o_sgrstacks
lset this 9 $o_gx0states
#---------------------------------------
}
@ -198,14 +201,19 @@ tcl::namespace::eval ::opunk::str {
string_t o_string ""
int_t o_count -1 ;#count first updated when string appended or a method causes my.MakeSplit to run (or by count method if constructor argument was empty string)
list_t o_ansisplits [list] ;#split version of string - odd number of elements - alternating plain text and ansicodestrings. Updated by my.makesplit
list_t o_ptlist [list]
list_t o_elements [list]
list_t o_graphemes [list]
list_t o_ptindex [list] ;#map graphemes to pt index
list_t o_ptlist [list]
list_t o_sgrstacks [list]
list_t o_gx0states [list]
list_t o_splitindex [list] ;#map from element index to index in ansisplits
method makesplit {} -upvar -update {o_string o_ansisplits o_ptlist o_elements o_sgrstacks o_gx0states o_splitindex o_count} {
#list_t o_splitindex [list]
method makesplit {} -upvar -update {o_string o_ansisplits o_elements o_graphemes o_ptindex o_ptlist o_sgrstacks o_gx0states o_count} {
#my.makesplit is called whenever string is modified (e.g by append) or when count method is called with an empty string argument (e.g constructor)
#my.makesplit should update count to be the number of plain text graphemes in the split version of string
@ -220,33 +228,45 @@ tcl::namespace::eval ::opunk::str {
set gx0_state [expr {false}] ;#default off
set current_split_index 0 ;#incremented for each pt block, incremented for each code
if {$o_count eq -1} {
set o_count 0
}
#if {$o_count eq -1} {
# set o_count 0
#}
set o_count 0
set o_elements [list]
set o_graphemes [list]
set o_ptindex [list]
set o_elements [list]
set o_ptlist [list]
set o_sgrstacks [list]
set o_gx0states [list]
set o_splitindex [list]
set o_count 0
set splitindex [list]
set codestack [list]
set ptindex -1
foreach {pt code} $o_ansisplits {
incr ptindex
lappend o_ptlist $pt
lappend o_sgrstacks $codestack
lappend o_gx0states $gx0_state
foreach grapheme [punk::char::grapheme_split $pt] {
lappend o_elements [list $etype_grapheme $grapheme]
lappend o_sgrstacks $codestack
lappend o_gx0states $gx0_state
lappend o_splitindex $current_split_index
lappend o_elements [list $etype_grapheme $grapheme]
lappend o_graphemes $grapheme
lappend o_ptindex $ptindex
lappend splitindex $current_split_index
incr o_count
}
#after handling the pt block - incr the current_split_index
incr current_split_index ;#increment for each pt block - whether empty string or not. Indices corresponding to empty PT blocks will therefore not be present in o_splitindex as there were no elements in that ansisplit entry
#we will only get an empty code at the very end of ansisplits (ansisplits is length 0 or odd length - always with pt at start and pt at end)
if {$code ne ""} {
lappend o_sgrstacks $codestack
lappend o_gx0states $gx0_state
lappend o_splitindex $current_split_index
#lappend o_sgrstacks $codestack
#lappend o_gx0states $gx0_state
#lappend o_splitindex $current_split_index
#maintenance warning - dup in append!
if {[punk::ansi::codetype::is_sgr_reset $code]} {
@ -282,12 +302,12 @@ tcl::namespace::eval ::opunk::str {
}
#assertion every grapheme and every individual code has been added to o_elements
#every element has an entry in my sgrstacks
#every element has an entry in my gx0states
#every grapheme has an entry in my sgrstacks
#every grapheme has an entry in my gx0states
#set num_splits [llength [my.get.o_ansisplits $this]]
assert {[llength $o_elements] == [llength $o_sgrstacks] && [llength $o_elements] == [llength $o_gx0states] && [llength $o_elements] == [llength $o_splitindex]}
assert {[llength $o_ptlist] == [llength $o_sgrstacks] && [llength $o_ptlist] == [llength $o_gx0states] && [llength $o_graphemes] == [llength $o_ptindex]}
}
@ -327,7 +347,7 @@ tcl::namespace::eval ::opunk::str {
set total_size 0
set idx 0
upvar o_elements idx_o_elements
upvar o_splitindex idx_o_splitindex
upvar o_ptindex idx_o_ptindex
foreach element $this {
if {$idx == $idx_o_elements} {
#o_elements
@ -342,7 +362,11 @@ tcl::namespace::eval ::opunk::str {
}
incr total_size 48
}
} elseif {$idx == $idx_o_splitindex} {
} elseif {$idx = $idx_o_graphemes} {
incr total_size 48
incr total_size 8
incr total_size 1
} elseif {$idx == $idx_o_ptindex} {
#o_splitindex
set count [llength $element]
incr total_size 48
@ -359,11 +383,12 @@ tcl::namespace::eval ::opunk::str {
#we can't use -update if we also use other helper methods that take 'this' as an argument and may modify any of the same fields
#- so we have to do the upvar-detach-try-finally dance ourselves.
method append {args} -upvar [string map [list %unshare% $::opunk::str::unshare_script_runtime %restore% $::opunk::str::restore_script_runtime] {
upvar $thisVar this
upvar o_string idx_o_string
if {![llength $args]} {
#nothing to append - just return current string
return $o_string
return [lindex $this $idx_o_string]
}
upvar $thisVar this
upvar o_ansisplits idx_o_ansisplits
@ -394,20 +419,20 @@ tcl::namespace::eval ::opunk::str {
if {![llength $o_ptlist]} {
#If the object was initialised with empty string - we can still have empty lists for o_ptlist and o_ansisplits
lappend o_ptlist $catstr
#assertion - if o_ptlist is empty so is o_ansisplits
lappend o_ansisplits $catstr
lappend o_sgrstacks [list]
lappend o_gx0states [expr {false}]
} else {
lset o_ptlist end [tcl::string::cat [lindex $o_ptlist end] $catstr]
lset o_ansisplits end [tcl::string::cat [lindex $o_ansisplits end] $catstr]
}
set last_codestack [lindex $o_sgrstacks end]
set last_gx0state [lindex $o_gx0states end]
set current_split_index [expr {[llength $o_ansisplits]-1}] ;#we are attaching to existing trailing pt - use its splitindex
set ptindex [expr {[llength $o_ptlist] - 1}]
foreach grapheme [punk::char::grapheme_split $catstr] {
lappend o_elements [list $etype_grapheme $grapheme]
lappend o_sgrstacks $last_codestack
lappend o_gx0states $last_gx0state
lappend o_splitindex $current_split_index
lappend o_graphemes $grapheme
lappend o_ptindex $ptindex
incr o_count
}
#incr o_count [my DoCount $catstr] ;#from before we were doing grapheme split.. review
@ -434,7 +459,7 @@ tcl::namespace::eval ::opunk::str {
my.makesplit this ;#update o_count
#set combined_plaintext [join $o_ptlist ""]
#set o_count [my DoCount $combined_plaintext]
#assert {[llength $o_elements] == [llength $o_sgrstacks] && [llength $o_elements] == [llength $o_gx0states] && [llength $o_elements] == [llength $o_splitindex]}
#assert {[llength $o_ptlist] == [llength $o_sgrstacks] && [llength $o_ptlist] == [llength $o_gx0states] && [llength $o_graphemes] == [llength $o_ptindex]}
return $o_string
}
@ -453,24 +478,29 @@ tcl::namespace::eval ::opunk::str {
set ptnew ""
set codestack [lindex $o_sgrstacks end]
set gx0_state [lindex $o_gx0states end]
set current_split_index [lindex $o_splitindex end]
set current_split_index [expr {[llength $o_ansisplits] -1}]
#first pt must be merged with last element of o_ptlist
set new_pt_list [list]
set new_sgrstacks [list]
set new_gx0states [list]
set ptindex [expr {[llength $o_ptlist] - 1}]
foreach {pt code} $newsplits {
lappend new_pt_list $pt
lappend new_sgrstacks $codestack
lappend new_gx0states $gx0_state
::append ptnew $pt
foreach grapheme [punk::char::grapheme_split $pt] {
lappend o_elements [list $etype_grapheme $grapheme]
lappend o_sgrstacks $codestack
lappend o_gx0states $gx0_state
lappend o_splitindex $current_split_index
lappend o_graphemes $grapheme
lappend o_ptindex $ptindex
incr o_count
}
incr current_split_index ;#increment 1 of 2 within each loop
if {$code ne ""} {
lappend o_sgrstacks $codestack
lappend o_gx0states $gx0_state
lappend o_splitindex $current_split_index
#maintenance - dup in MakeSplit!
if {[punk::ansi::codetype::is_sgr_reset $code]} {
set codestack [list $code] ;#pass through as is.
@ -494,15 +524,19 @@ tcl::namespace::eval ::opunk::str {
set gx0_state 0
lappend o_elements [list $etype_gx0 0] ;#don't store code - will complicate debugging if we spit it out and jump character sets
} else {
#lappend o_elements [list other $code]
lappend o_elements [list $etype_other $code]
}
}
incr current_split_index ;#increment 2 of 2
}
incr ptindex
}
lset o_ptlist end [tcl::string::cat [lindex $o_ptlist end] [lindex $new_pt_list 0]]
lappend o_ptlist {*}[lrange $new_pt_list 1 end]
lappend o_sgrstacks {*}[lrange $new_sgrstacks 1 end]
lappend o_gx0states {*}[lrange $new_gx0states 1 end]
lset o_ansisplits end [tcl::string::cat [lindex $o_ansisplits end] [lindex $newsplits 0]]
lappend o_ansisplits {*}[lrange $newsplits 1 end]
@ -513,7 +547,7 @@ tcl::namespace::eval ::opunk::str {
# incr o_count [my DoCount $ptnew]
#}
assert {[llength $o_elements] == [llength $o_sgrstacks] && [llength $o_elements] == [llength $o_gx0states] && [llength $o_elements] == [llength $o_splitindex]}
assert {[llength $o_ptlist] == [llength $o_sgrstacks] && [llength $o_ptlist] == [llength $o_gx0states] && [llength $o_graphemes] == [llength $o_ptindex]}
return $o_string
} finally {
%restore%
@ -566,24 +600,92 @@ tcl::namespace::eval ::opunk::str {
If the index is out of bounds, an empty string is returned."
}]
}
method INDEXCHAR -upvar {idx} {
set o_elements [my.get.o_elements $this]
if {![llength $o_elements]} {my.makesplit this}
#returns the character at the given index in the original string - including any ANSI codes. Not the same as the grapheme at that index in the split version of the string.
#return [string index [my.get.o_string $this] $idx]
set grapheme_positions [lsearch -all -exact -index 0 $o_elements g]
set required_posn [lindex $grapheme_positions $idx]
if {$required_posn eq ""} {
return ""
} else {
return [lindex $o_elements $required_posn 1]
#this is not the same as the character at that index in the original string, or even in the ansistripped string - which may be an ANSI code or *part of a grapheme cluster*
set o_graphemes [my.get.o_graphemes $this]
if {![llength $o_graphemes]} {
my.makesplit this
set o_ographemes [my.get.o_graphemes $this]
}
return [lindex $o_graphemes $idx]
#experimental.
#set grapheme_positions [lsearch -all -exact -index 0 $o_elements g] ;#slow on large strings.
#set required_posn [lindex $grapheme_positions $idx]
#if {$required_posn eq ""} {
# puts stderr "o_elements: $o_elements grapheme_positions: $grapheme_positions required_posn: $required_posn"
# return ""
#} else {
# return [lindex $o_elements $required_posn 1]
#}
}
method INDEX -upvar {idx} {
#returns the index in the original string of the character at the given grapheme index - i.e the index of the start of the grapheme cluster in the original string
upvar o_graphemes idx_o_graphemes
set o_graphemes [lindex $this $idx_o_graphemes]
if {![llength $o_graphemes]} {
my.makesplit this
set o_graphemes [lindex $this $idx_o_graphemes]
}
upvar o_sgrstacks idx_o_sgrstacks
upvar o_ptindex idx_o_ptindex
set ptindex [lindex $this $idx_o_ptindex]
set ptidx [lindex $ptindex $idx]
set stack [lindex $this $idx_o_sgrstacks $ptidx]
set ansi [punk::ansi::codetype::sgr_merge $stack]
return $ansi[lindex $o_graphemes $idx]
}
namespace eval argdoc {
variable PUNKARGS
lappend PUNKARGS [list {
@id -id ::opunk::Str::INDEXCODE
@cmd -name opunk::Str::INDEXCODE -summary\
"Get ANSI SGR code in effect at index or after last index"\
-help\
"Returns the ANSI SGR code at the position of the character returned by INDEX, or empty string
if no code at that position.
Retrieves the ANSI SGR code in effect after the entire string if index is ${$B}end+1${$N} or numerical equivalent.
If there are multiple codes in effect at that position, the returned code will be a merge of
those codes - so for example if there is a red foreground and then a bold later, the returned
code will be a single code with both red and bold in it.
This is effectively the ANSI code that would need to be applied to the character returned by
INDEX to get the same visual effect as the original string at that position."
@values -min 2 -max 2
string -type string -help\
"String to index into, which may contain ANSI codes."
index -type indexexpression -help\
"Index of character to get code for, or as a special case:
the index ${$B}end+1${$N} or numerical equivalent to get the code in effect after the last character (if any).
For example end will give the code in effect at the last character, and end+1 will give the code in
effect after the last character."
}]
}
method INDEXCODE -upvar {idx} {
#TODO - special cases for pure ANSI string and end+1 equivalent index to get code in effect after last character.
upvar o_graphemes idx_o_graphemes
set o_graphemes [lindex $this $idx_o_graphemes]
if {![llength $o_graphemes]} {
my.makesplit this
set o_graphemes [lindex $this $idx_o_graphemes]
}
upvar o_sgrstacks idx_o_sgrstacks
upvar o_ptindex idx_o_ptindex
set ptindex [lindex $this $idx_o_ptindex]
set ptidx [lindex $ptindex $idx]
set stack [lindex $this $idx_o_sgrstacks $ptidx]
set ansi [punk::ansi::codetype::sgr_merge $stack]
return $ansi
}
}
constructor {initial_string} {
return [list "opunk::Str" $initial_string -1 [list] [list] [list] [list] [list] [list]]
return [list "opunk::Str" $initial_string -1 [list] [list] [list] [list] [list] [list] [list]]
}
}
}

1050
src/modules/overtype-999999.0a1.0.tm

File diff suppressed because it is too large Load Diff

1
src/modules/punk/aliascore-999999.0a1.0.tm

@ -123,6 +123,7 @@ tcl::namespace::eval punk::aliascore {
stripansi ::punk::ansi::ansistrip
ansiwrap ::punk::ansi::ansiwrap
grepstr ::punk::ansi::grepstr
untabify ::punk::ansi::untabify
colour ::punk::console::colour
color ::punk::console::colour
ansi8 ::punk::ansi8

95
src/modules/punk/ansi-999999.0a1.0.tm

@ -726,19 +726,28 @@ tcl::namespace::eval punk::ansi {
} else {
set cols 80
}
set rows 24
if {[dict exists $sdict columns]} {
set c [dict get $sdict columns]
if {$c > 0} {
set cols $c
}
}
#set rows 24
set rows -1
if {[dict exists $sdict rows]} {
set r [dict get $sdict rows]
if {$r > 0} {
set rows $r
}
}
if {$rows == -1} {
set consize [punk::console::get_size]
if {$consize ne ""} {
set rows [expr {[dict get $consize rows] -1}]
} else {
set rows 24
}
}
set dimensions ${cols}x${rows}
}
lassign [split $dimensions x] cols rows
@ -1169,6 +1178,17 @@ tcl::namespace::eval punk::ansi {
a form where the ANSI only represents SGR styling (and perhaps other non-movement
codes) using something like overtype::renderline or overtype::rendertext.
Note that traditional terminals and most modern emulators treat the tab character as a
movement code
So even though the a regex such as {\s} will match various whitespace characters including tabs,
- ANSI SGR codes will not highlight, underline etc the tab characters themselves.
**********************************************************************************************
Also - tab characters are *currently* likely to cause output misalignment/truncation
especially if multiple rounds of grepstr are applied.
TODO - find a better way to handle tab characters in input.
For now - the input should be untabified first e.g with punk::ansi::untabify or similar.
**********************************************************************************************
Examples for returning matched lines only in the help output of the 'i' command:
@ -1359,6 +1379,9 @@ tcl::namespace::eval punk::ansi {
if {[dict exists $received -ansistrip]} {
set data [punk::ansi::ansistrip $data]
}
if {[string first \t $data] >= 0} {
puts stderr "Warning: grepstr does not currently handle tab characters in the input data. Tabs should be expanded to spaces first e.g with punk::ansi::untabify or similar."
}
set highlight [dict get $opts -highlight]
set opt_returnlines [dict get $opts -return]
set opt_invert [dict exists $received -invert-match]
@ -6229,40 +6252,44 @@ be as if this was off - ie lone CR.
#https://crates.io/crates/unicode-display-width
#JMN
set width 0
set sumwidth 0
foreach g $outchars {
set dec_codes [scan $g [tcl::string::repeat %c [string length $g]]]
set maxwidth 0
set max_component_width 0 ;#longest width of any single char in the grapheme cluster
set gwidth 0 ;#width of the whole grapheme cluster
foreach dec $dec_codes {
if {$dec <= 255} {
if {$dec >= 31 && $dec != 127} {
incr sumwidth 1
if {$maxwidth == 0} {
set maxwidth 1
incr gwidth 1
if {$max_component_width == 0} {
set max_component_width 1
}
}
} elseif {$c < 917504 || $c > 917631} {
#codepoint not in the zero-width unicode tag block - \UE0000-\UE000F
#set w [punk::char::char_width $dec]
set w [textutil::wcswidth_char $dec]
if {$w > $maxwidth} {
set maxwidth $w
}
if {$maxwidth == 2} {
#we can stop looking at this grapheme - it's already double width
break
#if {$max_component_width == 2 && $w == 2} {
# #we can stop looking at this grapheme - it's already double width
# break
#}
if {$w > $max_component_width} {
set max_component_width $w
}
incr sumwidth $w
incr gwidth $w
}
}
if {$maxwidth == 2} {
set width 2
} else {
set width $sumwidth
if {$max_component_width == 2} {
#if any char in the grapheme is double width - then the whole grapheme is double width
#This is a simplification that doesn't handle complex scripts such as arabic ligatures.
#review - find a more broadly accurate method of calculating grapheme width that handles complex cases such as ligatures and combiners affecting width.
#we could for example restrict this rule to cases where the base char is east asian width 2 - which would cover the common cases of double-width CJK chars and emojis - but would still allow for combiners and ligatures to affect width in other cases.
set gwidth 2
}
incr sumwidth $gwidth
}
return $width
return $sumwidth
#return [punk::char::ansifreestring_width [join $outchars ""]]
}
namespace eval internal {
@ -6705,7 +6732,8 @@ be as if this was off - ie lone CR.
set scopeinfo [dict create tabstops $tabstops columncount $ccount columnmaxwidths $colmaxwidths]
} else {
#same number of tabs as previous row - so same scope
set scope_key [expr {$r-1}]
#set scope_key [expr {$r-1}] ;#wrong - scope key is the first row in the scope - which is only necessarily the previous row if the previous row is the first row in the scope.
#just leave scope_key as it is from the previous iteration of the loop.
set scopeinfo [dict get $scopes $scope_key]
set update_colmaxwidths [dict get $scopeinfo columnmaxwidths]
set update_tstops [dict get $scopeinfo tabstops]
@ -6866,7 +6894,7 @@ be as if this was off - ie lone CR.
-with -type list|literal(spaces)|literal(unicode) -minsize 2 -maxsize 2 -default spaces -help\
"Whether to replace the tab characters with a custom pair of characters, a plain string of spaces, or to use
the default unicode characters to represent the tabs and following whitespace.
The character \U21E5 followed by a calculated number of \U00A0 (non-breaking space) characters is used when 'unicode'
The character \\U21E5 (\U21E5) followed by a calculated number of \\U00A0 (non-breaking space) characters is used when 'unicode'
is specified.
A 2 element list can be supplied to specify the character to use for the tab and the character to use for the
@ -6997,6 +7025,7 @@ be as if this was off - ie lone CR.
#puts stderr "tstops: $tstops maxdiff: $maxdiff"
set num [lindex $tstops 0]
set out [list]
set lines [list]
foreach line [split $textblock \n] {
#lappend out [untabifyLine $line $maxdiff]
set currPos 0
@ -7955,23 +7984,21 @@ tcl::namespace::eval punk::ansi {
#safe jumptable test
#dict for {k v} $codestate {}
tcl::dict::for {k v} $codestate {
switch -- $v {
"" {}
default {
switch -- $k {
underlinecolour {
append unmergeable "${v}\;"
}
underextended {
#review
append unmergeable "${v}\;"
}
default {
append codemerge "${v}\;"
}
if {$v ne ""} {
switch -- $k {
underlinecolour - underextended {
append unmergeable "${v}\;"
}
default {
append codemerge "${v}\;"
}
}
}
#switch -- $v {
# "" {}
# default {
# }
#}
}
}
set reset_prior 0

168
src/modules/punk/console-999999.0a1.0.tm

@ -1445,15 +1445,15 @@ namespace eval punk::console {
set width [dict get $tsize columns]
set mod [expr {$width % $w}]
set max [expr {$width - $mod}]
set tstops ""
set tstop_ansi ""
set c 1
while {$c <= $max} {
append tstops [string repeat " " $w][punk::ansi::set_tabstop]
append tstop_ansi [string repeat " " $w][punk::ansi::set_tabstop]
incr c $w
}
set punk::console::tabwidth $w ;#we also attempt to read terminal's tabstops and set tabwidth to the apparent spacing of first non-1 value in tabstops list.
catch {textutil::tabify::untabify2 "" $w} ;#textutil tabify can end up uninitialised and raise errors like "can't read Spaces(<n>).." after a tabstop change This call seems to keep tabify happy - review.
puts -nonewline "[punk::ansi::clear_all_tabstops]\n[punk::ansi::set_tabstop]$tstops"
set ::punk::console::tabwidth $w ;#we also attempt to read terminal's tabstops and set tabwidth to the apparent spacing of first non-1 value in tabstops list.
#catch {textutil::tabify::untabify2 "" $w} ;#textutil tabify can end up uninitialised and raise errors like "can't read Spaces(<n>).." after a tabstop change This call seems to keep tabify happy - review.
puts -nonewline "[punk::ansi::clear_all_tabstops]\n[punk::ansi::set_tabstop]$tstop_ansi"
}
@ -2574,6 +2574,46 @@ namespace eval punk::console {
dict set DECRQSS_DICT $name $str
}
namespace eval argdoc {
proc decrqss_choices_and_help {} {
#return a string for use in the punk::args definition for ::punk::console::dec_request_setting that lists the valid choices for the name argument, and their help text.
#This requires a -choices and -choicelabels pair in the args definition, to allow the help text to be directly associated with the choices.
upvar ::punk::console::DECRQSS_DATA DECRQSS_DATA
set outstr "name -type string -choices \{"
foreach {desc name str} $DECRQSS_DATA {
append outstr " $name"
}
append outstr " \}"
append outstr " -choicecolumns 4 -choicelabels \{" \n
foreach {desc name str} $DECRQSS_DATA {
append outstr " $name \{ ${desc}\}" \n
}
append outstr " \} -help "
append outstr " {Dec setting name to query.}"
return $outstr
#append argdef " \} -choicecolumns $opt_columns -choicelabels \{" \n
#append argdef " $choicelabelsdict" \n
#append argdef " \} -choiceinfo \{$choiceinfodict\} -unindentedfields \{-choicelabels\}" \n
}
set DECRQSS_CHOICES [::punk::console::argdoc::decrqss_choices_and_help]
lappend PUNKARGS [list {
@id -id ::punk::console::dec_request_setting
@cmd -name punk::console::dec_request_setting\
-summary\
{Perform DECRQSS query to get DECRPSS response}\
-help\
{DECRQSS query for DEC Selection or Setting.
Return payload from console's DECRPSS response.
}
@opts
-console -type list -minsize 2 -default {stdin stdout}
@values -min 1 -max 1
${$DECRQSS_CHOICES}
}
]
}
#emit DECRQSS and get DECRPSS response
# ---------------------
#NOTE: https://vt100.net/docs/vt510-rm/DECRPSS.html
@ -2582,23 +2622,9 @@ namespace eval punk::console {
# shown at: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
#REVIEW
# ---------------------
#Note also on Freebsd xterm we seem to getresponse ': 1 $ r D...D ST'
#(no leading DCS) - this
#Note also on Freebsd xterm we seem to get response ': 1 $ r D...D ST'
#(no leading DCS) - review
lappend PUNKARGS [list {
@id -id ::punk::console::dec_request_setting
@cmd -name punk::console::dec_request_setting\
-summary\
{Perform DECRQSS query to get DECRPSS response}\
-help\
{DECRQSS query for DEC Selection or Setting.
Return payload from console's DECRPSS response.
}
@opts
-console -type list -minsize 2 -default {stdin stdout}
@values -min 1 -max 1
name -type string
}]
proc dec_request_setting {args} {
set argd [punk::args::parse $args -cache 1 withid ::punk::console::dec_request_setting]
lassign [dict values $argd] leaders opts values
@ -2631,6 +2657,106 @@ namespace eval punk::console {
return [string range $payload 3 end]
}
namespace eval argdoc {
lappend PUNKARGS [list {
@id -id ::punk::console::cursor_style
@cmd -name punk::console::cursor_style\
-summary\
{Get/Set cursor style}\
-help\
{Set cursor style using DECSCUSR.
Note that support for DECSCUSR may be inconsistent across terminals.
Some terminals may only support a subset of the possible styles.}
@opts
-console -type list -minsize 2 -default {stdin stdout}
@values -min 0 -max 1
style -type dict -optional 1 -choicerestricted 0 -choices {0 default 1 block-blink 2 block-steady 3 underline-blink 4 underline-steady 5 beam-blink 6 beam-steady} -choicecolumns 2 -help\
"Cursor style to set
May be an integer or name as in the choices or a dict of the form {<num> <name>}
as returned by a query of the current style.
e.g to set block-blink style, you could specify style as 1, block-blink or {1 block-blink}
After setting to 0 (default) - a subsequent query will not return {0 default} but rather the number and name
of the actual style that the terminal has reset to.
If not supplied, the current style will be queried and returned as a dict
where num is the key and name is the value, e.g. {2 block-steady}.
This allows querying the current style and then re-setting it after temporarily changing it."
}]
}
proc cursor_style {args} {
set argd [punk::args::parse $args -cache 1 withid ::punk::console::cursor_style]
lassign [dict values $argd] leaders opts values
set console [dict get $opts -console]
set style "" ;#if not supplied, will query current style instead of setting
set style_num_to_name [dict create {*}{
0 default
1 block-blink
2 block-steady
3 underline-blink
4 underline-steady
5 beam-blink
6 beam-steady
}]
set style_name_to_num [dict create {*}{
default 0
block-blink 1
block-steady 2
underline-blink 3
underline-steady 4
beam-blink 5
beam-steady 6
}]
if {[dict exists $values style]} {
set style [dict get $values style]
}
if {$style eq ""} {
#query current style using DECRQSS/DECRPSS (dec_request_setting DECSCUSR)
set resultnum [dec_request_setting -console $console DECSCUSR]
if {![string is integer -strict $resultnum]} {
error "cursor_style query for current style returned non-integer result '$resultnum'"
}
if {![dict exists $style_num_to_name $resultnum]} {
error "cursor_style query for current style returned unrecognised style number '$resultnum'. Known styles: [dict keys $style_num_to_name]"
}
set resultname [dict get $style_num_to_name $resultnum]
set result [dict create $resultnum $resultname]
return $result
}
if {[string is integer -strict $style]} {
set style_num $style
if {![dict exists $style_num_to_name $style_num]} {
error "cursor_style unrecognised style number '$style_num'. Known styles: [dict keys $style_num_to_name]"
}
} elseif {[::punk::args::lib::string_is_dict $style]} {
#allow setting cursor using the response from a previous call to query the current style.
if {[dict size $style] != 1} {
error "cursor_style style dict should have exactly one key-value pair {<num> <style>}. Got [dict size $style]"
}
dict for {style_num style_name} $style {} ;#extract the num and name from the dict.
if {![string is integer -strict $style_num]} {
error "cursor_style style dict should be of the form {<num> <value>}. Got '$style'"
}
if {![dict exists $style_num_to_name $style_num]} {
error "cursor_style unrecognised style number '$style_num'. Known styles: [dict keys $style_num_to_name]"
}
if {![dict exists $style_name_to_num $style_name]} {
error "cursor_style unrecognised style name '$style_name'. Known styles: [dict keys $style_name_to_num]"
}
} else {
if {![dict exists $style_name_to_num $style]} {
error "cursor_style unrecognised style '$style'. Known styles: [dict keys $style_name_to_num]"
}
set style_num [dict get $style_name_to_num $style]
}
#set cursor style using DECSCUSR
set request "\x1b\[${style_num} q"
set term_out [lindex $console 1]
puts -nonewline $term_out $request
flush $term_out
}
#terminals lie. This should be a reasonable (albeit relatively slow) test of actual width - but some terminals seem to miscalculate.
#todo - a visual interactive test/questionnaire to ask user if things are lining up or if the terminal is telling fibs about cursor position.
#todo - determine if these anomalies are independent of font

8
src/modules/punk/lib-999999.0a1.0.tm

@ -297,6 +297,14 @@ tcl::namespace::eval punk::lib::compat {
#[list_begin definitions]
if {[catch {
::tcl::mathfunc::isinf 1
}]} {
#review - doesn't seem to cause int-rep of the value to shimmer - but does it reasonably emulate what tcl9's isinf does?
proc ::tcl::mathfunc::isinf {v} {
string match -nocase *inf* $v
}
}
if {"::lremove" ne [info commands ::lremove]} {

112
src/modules/punk/repl-999999.0a1.0.tm

@ -1023,10 +1023,15 @@ namespace eval punk::repl::class {
append new0 \n
}
set underlay [punk::ansi::ansistrip $activeline]
set line_nextchar_col [expr {[punk::char::string_width $underlay] + 1}]
if {$o_cursor_col > $line_nextchar_col} {
set o_cursor_col $line_nextchar_col
}
#set line_nextchar_col [expr {[punk::char::string_width $underlay] + 1}]
set line_nextchar_col [expr {[punk::ansi::printing_length $underlay] + 1}]
#review
#if {$o_cursor_col > $line_nextchar_col} {
# set o_cursor_col $line_nextchar_col
#}
#set o_cursor_col $line_nextchar_col
set initial_insert_mode $o_insert_mode
set mergedinfo [overtype::renderline -info 1 -expand_right 1 -insert_mode $o_insert_mode -cursor_column $o_cursor_col -cursor_row $o_cursor_row $underlay $new0]
@ -1044,8 +1049,20 @@ namespace eval punk::repl::class {
# -- --- --- --- --- ---
set debug_first_row 2
#puts "merged: $mergedinfo"
set debug "add_chunk0"
append debug \n $mergedinfo
set debug "add_chunk[llength $o_chunk_list]"
#append debug \n $mergedinfo
set dinfo [dict create]
dict for {key val} $mergedinfo {
if {$key eq "result"} {
dict set dinfo $key $val
dict set dinfo resultview [ansistring VIEW -lf 1 -vt 1 $val]
} else {
if {$val ne ""} {
dict set dinfo $key $val
}
}
}
append debug \n [showdict $dinfo]
append debug \n "input:[ansistring VIEW -lf 1 -vt 1 $new0] before row:$o_cursor_row after row: $result_row before col:$o_cursor_col after col:$result_col"
package require textblock
set debug [textblock::frame -checkargs 0 -buildcache 0 $debug]
@ -1068,6 +1085,12 @@ namespace eval punk::repl::class {
incr nextrow
set o_cursor_col 1
}
lf_mid {
#for a chunk with embedded newlines - we need to insert lines below and move overflow_right down
#todo
lappend o_rendered_lines ""
set o_cursor_col 1
}
}
if {$insert_lines_below == 1} {
@ -1110,10 +1133,17 @@ namespace eval punk::repl::class {
}
#puts stderr "overtype::renderline -info 1 -expand_right 1 -insert_mode $o_insert_mode -cursor_column $o_cursor_col -cursor_row $o_cursor_row $activeline '$p'"
set underlay $activeline
set line_nextchar_col [expr {[punk::char::string_width $underlay] + 1}]
if {$o_cursor_col > $line_nextchar_col} {
set o_cursor_col $line_nextchar_col
}
#set line_nextchar_col [expr {[punk::char::string_width $underlay] + 1}]
set line_nextchar_col [expr {[punk::ansi::printing_length $underlay] + 1}]
#------------------
#review
#if {$o_cursor_col > $line_nextchar_col} {
# set o_cursor_col $line_nextchar_col
#}
#set o_cursor_col $line_nextchar_col
#------------------
set mergedinfo [overtype::renderline -info 1 -expand_right 1 -insert_mode $o_insert_mode -cursor_column $o_cursor_col -cursor_row $o_cursor_row $underlay $p]
set debug "add_chunk$i"
append debug \n $mergedinfo
@ -1121,13 +1151,14 @@ namespace eval punk::repl::class {
set debug [textblock::frame -checkargs 0 -buildcache 0 $debug]
#catch {punk::console::move_emitblock_return [expr {$debug_first_row + ($i * 6)}] 1 $debug}
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]
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]
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]
set overflow_right [dict get $mergedinfo overflow_right] ;#should be empty if no \v
set unapplied [dict get $mergedinfo unapplied]
set instruction [dict get $mergedinfo instruction]
set insert_lines_below [dict get $mergedinfo insert_lines_below]
if {[string is integer -strict $cmove]} {
if {$cmove == 0} {
set nextrow [expr {$o_cursor_row + 1}]
@ -1138,7 +1169,6 @@ namespace eval punk::repl::class {
} elseif {$cmove >= 1} {
}
} else {
# =<int> - absolute
set nextrow [string range $cmove 1 end]
@ -1147,6 +1177,12 @@ namespace eval punk::repl::class {
incr nextrow
}
set o_cursor_row $nextrow
if {$instruction eq "lf_mid"} {
set o_cursor_col 1
}
if {$insert_lines_below} {
}
@ -1164,6 +1200,17 @@ namespace eval punk::repl::class {
incr i
}
if {$o_insert_mode != $initial_insert_mode} {
set cursor_style_overtype {3 underline-blink}
set cursor_style_insert {5 beam-blink}
#-console ??
if {$o_insert_mode} {
punk::console::cursor_style $cursor_style_insert
} else {
punk::console::cursor_style $cursor_style_overtype
}
}
}
method add_rendered_chunk {rchunk} {
#split only on lf newlines - movement codes and \b \v \r not expected
@ -2110,26 +2157,43 @@ proc repl::repl_process_data {inputchan chunktype chunk stdinlines prompt_config
}
flush stdout
#move_column is more efficient than move since it doesn't require a response on stdin to determine current column,
#but doesn't seem to be universally supported (kermit95 vt modes for example)
#the Horizontal Position Absolute sequence ESC \[ n ` seems to be a possible alternative.
set leftmargin 3
# a+ cyan = CSI 36m
set active_line_colour [a+ cyan] ;# "\x1b\[36m"
#set space_colour "\x1b\[37m"
#set space_colour "\x1b\[38\;5\;244m" ;#term-grey46 - review - make configurable? May not be universal across terminals - but should be widely supported at least as a minimum for grey.
set space_colour [a+ brightblack] ;#"\x1b\[90m" ;#bright black - review - seems to be supported more widely than 256 colour greys]
set ebuf_line [untabify -with unicode [$editbuf line $cursor_row]]
set tail_col [expr {$leftmargin + [$editbuf cursor_column]}]
if {!$is_vt52} {
puts -nonewline stdout [a+ cyan][punk::ansi::move_column [expr {$leftmargin +1}]][punk::ansi::erase_eol][$editbuf line $cursor_row][a][punk::ansi::move_column [expr {$leftmargin + [$editbuf cursor_column]}]]
#puts -nonewline stdout [a+ cyan][punk::ansi::move_column [expr {$leftmargin +1}]][punk::ansi::erase_eol][$editbuf line $cursor_row][a][punk::ansi::move_column [expr {$leftmargin + [$editbuf cursor_column]}]]
set ebuf_line [string map [list { } "$space_colour.$active_line_colour"] $ebuf_line]
puts -nonewline stdout $active_line_colour[punk::ansi::move_column [expr {$leftmargin +1}]][punk::ansi::erase_eol]$ebuf_line[a][punk::ansi::move_column $tail_col]
} else {
puts -nonewline stdout [a+ cyan][punk::ansi::vt52move_column [expr {$leftmargin +1}]][punk::ansi::vt52erase_eol][$editbuf line $cursor_row][punk::ansi::vt52move_column [expr {$leftmargin + [$editbuf cursor_column]}]]
#puts -nonewline stdout [a+ cyan][punk::ansi::vt52move_column [expr {$leftmargin +1}]][punk::ansi::vt52erase_eol][$editbuf line $cursor_row][punk::ansi::vt52move_column [expr {$leftmargin + [$editbuf cursor_column]}]]
puts -nonewline stdout [punk::ansi::vt52move_column [expr {$leftmargin +1}]][punk::ansi::vt52erase_eol]$ebuf_line[punk::ansi::vt52move_column $tail_col]
}
#puts -nonewline stdout $chunk
flush stdout
if {[$editbuf last_char] eq "\n"} {
set linelen [punk::ansi::printing_length [$editbuf line $nextsubmit_line_num]]
set inactive_line_colour [a+ yellow bold]
set ebuf_next_line [untabify -with unicode [$editbuf line $nextsubmit_line_num]]
#set linelen [punk::ansi::printing_length [$editbuf line $nextsubmit_line_num]]
set linelen [punk::ansi::printing_length $ebuf_next_line]
if {!$is_vt52} {
puts -nonewline stdout [a+ cyan bold][punk::ansi::move_column [expr {$leftmargin +1}]][$editbuf line $nextsubmit_line_num][a][punk::ansi::move_column [expr {$leftmargin + $linelen +1}]]
set ebuf_next_line [string map [list { } "$space_colour.$inactive_line_colour"] $ebuf_next_line]
puts -nonewline stdout $inactive_line_colour[punk::ansi::move_column [expr {$leftmargin +1}]]$ebuf_next_line[a][punk::ansi::move_column [expr {$leftmargin + $linelen +1}]]
#screen_last_char_add "\n" input inputline
puts -nonewline stdout [punk::ansi::erase_eol]\n
} else {
puts -nonewline stdout [a+ cyan bold][punk::ansi::vt52move_column [expr {$leftmargin +1}]][$editbuf line $nextsubmit_line_num][punk::ansi::vt52move_column [expr {$leftmargin + $linelen +1}]]
puts -nonewline stdout [punk::ansi::vt52move_column [expr {$leftmargin +1}]]$ebuf_next_line[punk::ansi::vt52move_column [expr {$leftmargin + $linelen +1}]]
puts -nonewline stdout [punk::ansi::vt52erase_eol]\n
}

1050
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/overtype-1.7.4.tm

File diff suppressed because it is too large Load Diff

1
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/aliascore-0.1.0.tm

@ -123,6 +123,7 @@ tcl::namespace::eval punk::aliascore {
stripansi ::punk::ansi::ansistrip
ansiwrap ::punk::ansi::ansiwrap
grepstr ::punk::ansi::grepstr
untabify ::punk::ansi::untabify
colour ::punk::console::colour
color ::punk::console::colour
ansi8 ::punk::ansi8

95
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/ansi-0.1.1.tm

@ -726,19 +726,28 @@ tcl::namespace::eval punk::ansi {
} else {
set cols 80
}
set rows 24
if {[dict exists $sdict columns]} {
set c [dict get $sdict columns]
if {$c > 0} {
set cols $c
}
}
#set rows 24
set rows -1
if {[dict exists $sdict rows]} {
set r [dict get $sdict rows]
if {$r > 0} {
set rows $r
}
}
if {$rows == -1} {
set consize [punk::console::get_size]
if {$consize ne ""} {
set rows [expr {[dict get $consize rows] -1}]
} else {
set rows 24
}
}
set dimensions ${cols}x${rows}
}
lassign [split $dimensions x] cols rows
@ -1169,6 +1178,17 @@ tcl::namespace::eval punk::ansi {
a form where the ANSI only represents SGR styling (and perhaps other non-movement
codes) using something like overtype::renderline or overtype::rendertext.
Note that traditional terminals and most modern emulators treat the tab character as a
movement code
So even though the a regex such as {\s} will match various whitespace characters including tabs,
- ANSI SGR codes will not highlight, underline etc the tab characters themselves.
**********************************************************************************************
Also - tab characters are *currently* likely to cause output misalignment/truncation
especially if multiple rounds of grepstr are applied.
TODO - find a better way to handle tab characters in input.
For now - the input should be untabified first e.g with punk::ansi::untabify or similar.
**********************************************************************************************
Examples for returning matched lines only in the help output of the 'i' command:
@ -1359,6 +1379,9 @@ tcl::namespace::eval punk::ansi {
if {[dict exists $received -ansistrip]} {
set data [punk::ansi::ansistrip $data]
}
if {[string first \t $data] >= 0} {
puts stderr "Warning: grepstr does not currently handle tab characters in the input data. Tabs should be expanded to spaces first e.g with punk::ansi::untabify or similar."
}
set highlight [dict get $opts -highlight]
set opt_returnlines [dict get $opts -return]
set opt_invert [dict exists $received -invert-match]
@ -6229,40 +6252,44 @@ be as if this was off - ie lone CR.
#https://crates.io/crates/unicode-display-width
#JMN
set width 0
set sumwidth 0
foreach g $outchars {
set dec_codes [scan $g [tcl::string::repeat %c [string length $g]]]
set maxwidth 0
set max_component_width 0 ;#longest width of any single char in the grapheme cluster
set gwidth 0 ;#width of the whole grapheme cluster
foreach dec $dec_codes {
if {$dec <= 255} {
if {$dec >= 31 && $dec != 127} {
incr sumwidth 1
if {$maxwidth == 0} {
set maxwidth 1
incr gwidth 1
if {$max_component_width == 0} {
set max_component_width 1
}
}
} elseif {$c < 917504 || $c > 917631} {
#codepoint not in the zero-width unicode tag block - \UE0000-\UE000F
#set w [punk::char::char_width $dec]
set w [textutil::wcswidth_char $dec]
if {$w > $maxwidth} {
set maxwidth $w
}
if {$maxwidth == 2} {
#we can stop looking at this grapheme - it's already double width
break
#if {$max_component_width == 2 && $w == 2} {
# #we can stop looking at this grapheme - it's already double width
# break
#}
if {$w > $max_component_width} {
set max_component_width $w
}
incr sumwidth $w
incr gwidth $w
}
}
if {$maxwidth == 2} {
set width 2
} else {
set width $sumwidth
if {$max_component_width == 2} {
#if any char in the grapheme is double width - then the whole grapheme is double width
#This is a simplification that doesn't handle complex scripts such as arabic ligatures.
#review - find a more broadly accurate method of calculating grapheme width that handles complex cases such as ligatures and combiners affecting width.
#we could for example restrict this rule to cases where the base char is east asian width 2 - which would cover the common cases of double-width CJK chars and emojis - but would still allow for combiners and ligatures to affect width in other cases.
set gwidth 2
}
incr sumwidth $gwidth
}
return $width
return $sumwidth
#return [punk::char::ansifreestring_width [join $outchars ""]]
}
namespace eval internal {
@ -6705,7 +6732,8 @@ be as if this was off - ie lone CR.
set scopeinfo [dict create tabstops $tabstops columncount $ccount columnmaxwidths $colmaxwidths]
} else {
#same number of tabs as previous row - so same scope
set scope_key [expr {$r-1}]
#set scope_key [expr {$r-1}] ;#wrong - scope key is the first row in the scope - which is only necessarily the previous row if the previous row is the first row in the scope.
#just leave scope_key as it is from the previous iteration of the loop.
set scopeinfo [dict get $scopes $scope_key]
set update_colmaxwidths [dict get $scopeinfo columnmaxwidths]
set update_tstops [dict get $scopeinfo tabstops]
@ -6866,7 +6894,7 @@ be as if this was off - ie lone CR.
-with -type list|literal(spaces)|literal(unicode) -minsize 2 -maxsize 2 -default spaces -help\
"Whether to replace the tab characters with a custom pair of characters, a plain string of spaces, or to use
the default unicode characters to represent the tabs and following whitespace.
The character \U21E5 followed by a calculated number of \U00A0 (non-breaking space) characters is used when 'unicode'
The character \\U21E5 (\U21E5) followed by a calculated number of \\U00A0 (non-breaking space) characters is used when 'unicode'
is specified.
A 2 element list can be supplied to specify the character to use for the tab and the character to use for the
@ -6997,6 +7025,7 @@ be as if this was off - ie lone CR.
#puts stderr "tstops: $tstops maxdiff: $maxdiff"
set num [lindex $tstops 0]
set out [list]
set lines [list]
foreach line [split $textblock \n] {
#lappend out [untabifyLine $line $maxdiff]
set currPos 0
@ -7955,23 +7984,21 @@ tcl::namespace::eval punk::ansi {
#safe jumptable test
#dict for {k v} $codestate {}
tcl::dict::for {k v} $codestate {
switch -- $v {
"" {}
default {
switch -- $k {
underlinecolour {
append unmergeable "${v}\;"
}
underextended {
#review
append unmergeable "${v}\;"
}
default {
append codemerge "${v}\;"
}
if {$v ne ""} {
switch -- $k {
underlinecolour - underextended {
append unmergeable "${v}\;"
}
default {
append codemerge "${v}\;"
}
}
}
#switch -- $v {
# "" {}
# default {
# }
#}
}
}
set reset_prior 0

168
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/console-0.1.1.tm

@ -1445,15 +1445,15 @@ namespace eval punk::console {
set width [dict get $tsize columns]
set mod [expr {$width % $w}]
set max [expr {$width - $mod}]
set tstops ""
set tstop_ansi ""
set c 1
while {$c <= $max} {
append tstops [string repeat " " $w][punk::ansi::set_tabstop]
append tstop_ansi [string repeat " " $w][punk::ansi::set_tabstop]
incr c $w
}
set punk::console::tabwidth $w ;#we also attempt to read terminal's tabstops and set tabwidth to the apparent spacing of first non-1 value in tabstops list.
catch {textutil::tabify::untabify2 "" $w} ;#textutil tabify can end up uninitialised and raise errors like "can't read Spaces(<n>).." after a tabstop change This call seems to keep tabify happy - review.
puts -nonewline "[punk::ansi::clear_all_tabstops]\n[punk::ansi::set_tabstop]$tstops"
set ::punk::console::tabwidth $w ;#we also attempt to read terminal's tabstops and set tabwidth to the apparent spacing of first non-1 value in tabstops list.
#catch {textutil::tabify::untabify2 "" $w} ;#textutil tabify can end up uninitialised and raise errors like "can't read Spaces(<n>).." after a tabstop change This call seems to keep tabify happy - review.
puts -nonewline "[punk::ansi::clear_all_tabstops]\n[punk::ansi::set_tabstop]$tstop_ansi"
}
@ -2574,6 +2574,46 @@ namespace eval punk::console {
dict set DECRQSS_DICT $name $str
}
namespace eval argdoc {
proc decrqss_choices_and_help {} {
#return a string for use in the punk::args definition for ::punk::console::dec_request_setting that lists the valid choices for the name argument, and their help text.
#This requires a -choices and -choicelabels pair in the args definition, to allow the help text to be directly associated with the choices.
upvar ::punk::console::DECRQSS_DATA DECRQSS_DATA
set outstr "name -type string -choices \{"
foreach {desc name str} $DECRQSS_DATA {
append outstr " $name"
}
append outstr " \}"
append outstr " -choicecolumns 4 -choicelabels \{" \n
foreach {desc name str} $DECRQSS_DATA {
append outstr " $name \{ ${desc}\}" \n
}
append outstr " \} -help "
append outstr " {Dec setting name to query.}"
return $outstr
#append argdef " \} -choicecolumns $opt_columns -choicelabels \{" \n
#append argdef " $choicelabelsdict" \n
#append argdef " \} -choiceinfo \{$choiceinfodict\} -unindentedfields \{-choicelabels\}" \n
}
set DECRQSS_CHOICES [::punk::console::argdoc::decrqss_choices_and_help]
lappend PUNKARGS [list {
@id -id ::punk::console::dec_request_setting
@cmd -name punk::console::dec_request_setting\
-summary\
{Perform DECRQSS query to get DECRPSS response}\
-help\
{DECRQSS query for DEC Selection or Setting.
Return payload from console's DECRPSS response.
}
@opts
-console -type list -minsize 2 -default {stdin stdout}
@values -min 1 -max 1
${$DECRQSS_CHOICES}
}
]
}
#emit DECRQSS and get DECRPSS response
# ---------------------
#NOTE: https://vt100.net/docs/vt510-rm/DECRPSS.html
@ -2582,23 +2622,9 @@ namespace eval punk::console {
# shown at: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
#REVIEW
# ---------------------
#Note also on Freebsd xterm we seem to getresponse ': 1 $ r D...D ST'
#(no leading DCS) - this
#Note also on Freebsd xterm we seem to get response ': 1 $ r D...D ST'
#(no leading DCS) - review
lappend PUNKARGS [list {
@id -id ::punk::console::dec_request_setting
@cmd -name punk::console::dec_request_setting\
-summary\
{Perform DECRQSS query to get DECRPSS response}\
-help\
{DECRQSS query for DEC Selection or Setting.
Return payload from console's DECRPSS response.
}
@opts
-console -type list -minsize 2 -default {stdin stdout}
@values -min 1 -max 1
name -type string
}]
proc dec_request_setting {args} {
set argd [punk::args::parse $args -cache 1 withid ::punk::console::dec_request_setting]
lassign [dict values $argd] leaders opts values
@ -2631,6 +2657,106 @@ namespace eval punk::console {
return [string range $payload 3 end]
}
namespace eval argdoc {
lappend PUNKARGS [list {
@id -id ::punk::console::cursor_style
@cmd -name punk::console::cursor_style\
-summary\
{Get/Set cursor style}\
-help\
{Set cursor style using DECSCUSR.
Note that support for DECSCUSR may be inconsistent across terminals.
Some terminals may only support a subset of the possible styles.}
@opts
-console -type list -minsize 2 -default {stdin stdout}
@values -min 0 -max 1
style -type dict -optional 1 -choicerestricted 0 -choices {0 default 1 block-blink 2 block-steady 3 underline-blink 4 underline-steady 5 beam-blink 6 beam-steady} -choicecolumns 2 -help\
"Cursor style to set
May be an integer or name as in the choices or a dict of the form {<num> <name>}
as returned by a query of the current style.
e.g to set block-blink style, you could specify style as 1, block-blink or {1 block-blink}
After setting to 0 (default) - a subsequent query will not return {0 default} but rather the number and name
of the actual style that the terminal has reset to.
If not supplied, the current style will be queried and returned as a dict
where num is the key and name is the value, e.g. {2 block-steady}.
This allows querying the current style and then re-setting it after temporarily changing it."
}]
}
proc cursor_style {args} {
set argd [punk::args::parse $args -cache 1 withid ::punk::console::cursor_style]
lassign [dict values $argd] leaders opts values
set console [dict get $opts -console]
set style "" ;#if not supplied, will query current style instead of setting
set style_num_to_name [dict create {*}{
0 default
1 block-blink
2 block-steady
3 underline-blink
4 underline-steady
5 beam-blink
6 beam-steady
}]
set style_name_to_num [dict create {*}{
default 0
block-blink 1
block-steady 2
underline-blink 3
underline-steady 4
beam-blink 5
beam-steady 6
}]
if {[dict exists $values style]} {
set style [dict get $values style]
}
if {$style eq ""} {
#query current style using DECRQSS/DECRPSS (dec_request_setting DECSCUSR)
set resultnum [dec_request_setting -console $console DECSCUSR]
if {![string is integer -strict $resultnum]} {
error "cursor_style query for current style returned non-integer result '$resultnum'"
}
if {![dict exists $style_num_to_name $resultnum]} {
error "cursor_style query for current style returned unrecognised style number '$resultnum'. Known styles: [dict keys $style_num_to_name]"
}
set resultname [dict get $style_num_to_name $resultnum]
set result [dict create $resultnum $resultname]
return $result
}
if {[string is integer -strict $style]} {
set style_num $style
if {![dict exists $style_num_to_name $style_num]} {
error "cursor_style unrecognised style number '$style_num'. Known styles: [dict keys $style_num_to_name]"
}
} elseif {[::punk::args::lib::string_is_dict $style]} {
#allow setting cursor using the response from a previous call to query the current style.
if {[dict size $style] != 1} {
error "cursor_style style dict should have exactly one key-value pair {<num> <style>}. Got [dict size $style]"
}
dict for {style_num style_name} $style {} ;#extract the num and name from the dict.
if {![string is integer -strict $style_num]} {
error "cursor_style style dict should be of the form {<num> <value>}. Got '$style'"
}
if {![dict exists $style_num_to_name $style_num]} {
error "cursor_style unrecognised style number '$style_num'. Known styles: [dict keys $style_num_to_name]"
}
if {![dict exists $style_name_to_num $style_name]} {
error "cursor_style unrecognised style name '$style_name'. Known styles: [dict keys $style_name_to_num]"
}
} else {
if {![dict exists $style_name_to_num $style]} {
error "cursor_style unrecognised style '$style'. Known styles: [dict keys $style_name_to_num]"
}
set style_num [dict get $style_name_to_num $style]
}
#set cursor style using DECSCUSR
set request "\x1b\[${style_num} q"
set term_out [lindex $console 1]
puts -nonewline $term_out $request
flush $term_out
}
#terminals lie. This should be a reasonable (albeit relatively slow) test of actual width - but some terminals seem to miscalculate.
#todo - a visual interactive test/questionnaire to ask user if things are lining up or if the terminal is telling fibs about cursor position.
#todo - determine if these anomalies are independent of font

8
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/lib-0.1.6.tm

@ -297,6 +297,14 @@ tcl::namespace::eval punk::lib::compat {
#[list_begin definitions]
if {[catch {
::tcl::mathfunc::isinf 1
}]} {
#review - doesn't seem to cause int-rep of the value to shimmer - but does it reasonably emulate what tcl9's isinf does?
proc ::tcl::mathfunc::isinf {v} {
string match -nocase *inf* $v
}
}
if {"::lremove" ne [info commands ::lremove]} {

112
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/repl-0.1.2.tm

@ -1023,10 +1023,15 @@ namespace eval punk::repl::class {
append new0 \n
}
set underlay [punk::ansi::ansistrip $activeline]
set line_nextchar_col [expr {[punk::char::string_width $underlay] + 1}]
if {$o_cursor_col > $line_nextchar_col} {
set o_cursor_col $line_nextchar_col
}
#set line_nextchar_col [expr {[punk::char::string_width $underlay] + 1}]
set line_nextchar_col [expr {[punk::ansi::printing_length $underlay] + 1}]
#review
#if {$o_cursor_col > $line_nextchar_col} {
# set o_cursor_col $line_nextchar_col
#}
#set o_cursor_col $line_nextchar_col
set initial_insert_mode $o_insert_mode
set mergedinfo [overtype::renderline -info 1 -expand_right 1 -insert_mode $o_insert_mode -cursor_column $o_cursor_col -cursor_row $o_cursor_row $underlay $new0]
@ -1044,8 +1049,20 @@ namespace eval punk::repl::class {
# -- --- --- --- --- ---
set debug_first_row 2
#puts "merged: $mergedinfo"
set debug "add_chunk0"
append debug \n $mergedinfo
set debug "add_chunk[llength $o_chunk_list]"
#append debug \n $mergedinfo
set dinfo [dict create]
dict for {key val} $mergedinfo {
if {$key eq "result"} {
dict set dinfo $key $val
dict set dinfo resultview [ansistring VIEW -lf 1 -vt 1 $val]
} else {
if {$val ne ""} {
dict set dinfo $key $val
}
}
}
append debug \n [showdict $dinfo]
append debug \n "input:[ansistring VIEW -lf 1 -vt 1 $new0] before row:$o_cursor_row after row: $result_row before col:$o_cursor_col after col:$result_col"
package require textblock
set debug [textblock::frame -checkargs 0 -buildcache 0 $debug]
@ -1068,6 +1085,12 @@ namespace eval punk::repl::class {
incr nextrow
set o_cursor_col 1
}
lf_mid {
#for a chunk with embedded newlines - we need to insert lines below and move overflow_right down
#todo
lappend o_rendered_lines ""
set o_cursor_col 1
}
}
if {$insert_lines_below == 1} {
@ -1110,10 +1133,17 @@ namespace eval punk::repl::class {
}
#puts stderr "overtype::renderline -info 1 -expand_right 1 -insert_mode $o_insert_mode -cursor_column $o_cursor_col -cursor_row $o_cursor_row $activeline '$p'"
set underlay $activeline
set line_nextchar_col [expr {[punk::char::string_width $underlay] + 1}]
if {$o_cursor_col > $line_nextchar_col} {
set o_cursor_col $line_nextchar_col
}
#set line_nextchar_col [expr {[punk::char::string_width $underlay] + 1}]
set line_nextchar_col [expr {[punk::ansi::printing_length $underlay] + 1}]
#------------------
#review
#if {$o_cursor_col > $line_nextchar_col} {
# set o_cursor_col $line_nextchar_col
#}
#set o_cursor_col $line_nextchar_col
#------------------
set mergedinfo [overtype::renderline -info 1 -expand_right 1 -insert_mode $o_insert_mode -cursor_column $o_cursor_col -cursor_row $o_cursor_row $underlay $p]
set debug "add_chunk$i"
append debug \n $mergedinfo
@ -1121,13 +1151,14 @@ namespace eval punk::repl::class {
set debug [textblock::frame -checkargs 0 -buildcache 0 $debug]
#catch {punk::console::move_emitblock_return [expr {$debug_first_row + ($i * 6)}] 1 $debug}
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]
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]
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]
set overflow_right [dict get $mergedinfo overflow_right] ;#should be empty if no \v
set unapplied [dict get $mergedinfo unapplied]
set instruction [dict get $mergedinfo instruction]
set insert_lines_below [dict get $mergedinfo insert_lines_below]
if {[string is integer -strict $cmove]} {
if {$cmove == 0} {
set nextrow [expr {$o_cursor_row + 1}]
@ -1138,7 +1169,6 @@ namespace eval punk::repl::class {
} elseif {$cmove >= 1} {
}
} else {
# =<int> - absolute
set nextrow [string range $cmove 1 end]
@ -1147,6 +1177,12 @@ namespace eval punk::repl::class {
incr nextrow
}
set o_cursor_row $nextrow
if {$instruction eq "lf_mid"} {
set o_cursor_col 1
}
if {$insert_lines_below} {
}
@ -1164,6 +1200,17 @@ namespace eval punk::repl::class {
incr i
}
if {$o_insert_mode != $initial_insert_mode} {
set cursor_style_overtype {3 underline-blink}
set cursor_style_insert {5 beam-blink}
#-console ??
if {$o_insert_mode} {
punk::console::cursor_style $cursor_style_insert
} else {
punk::console::cursor_style $cursor_style_overtype
}
}
}
method add_rendered_chunk {rchunk} {
#split only on lf newlines - movement codes and \b \v \r not expected
@ -2110,26 +2157,43 @@ proc repl::repl_process_data {inputchan chunktype chunk stdinlines prompt_config
}
flush stdout
#move_column is more efficient than move since it doesn't require a response on stdin to determine current column,
#but doesn't seem to be universally supported (kermit95 vt modes for example)
#the Horizontal Position Absolute sequence ESC \[ n ` seems to be a possible alternative.
set leftmargin 3
# a+ cyan = CSI 36m
set active_line_colour [a+ cyan] ;# "\x1b\[36m"
#set space_colour "\x1b\[37m"
#set space_colour "\x1b\[38\;5\;244m" ;#term-grey46 - review - make configurable? May not be universal across terminals - but should be widely supported at least as a minimum for grey.
set space_colour [a+ brightblack] ;#"\x1b\[90m" ;#bright black - review - seems to be supported more widely than 256 colour greys]
set ebuf_line [untabify -with unicode [$editbuf line $cursor_row]]
set tail_col [expr {$leftmargin + [$editbuf cursor_column]}]
if {!$is_vt52} {
puts -nonewline stdout [a+ cyan][punk::ansi::move_column [expr {$leftmargin +1}]][punk::ansi::erase_eol][$editbuf line $cursor_row][a][punk::ansi::move_column [expr {$leftmargin + [$editbuf cursor_column]}]]
#puts -nonewline stdout [a+ cyan][punk::ansi::move_column [expr {$leftmargin +1}]][punk::ansi::erase_eol][$editbuf line $cursor_row][a][punk::ansi::move_column [expr {$leftmargin + [$editbuf cursor_column]}]]
set ebuf_line [string map [list { } "$space_colour.$active_line_colour"] $ebuf_line]
puts -nonewline stdout $active_line_colour[punk::ansi::move_column [expr {$leftmargin +1}]][punk::ansi::erase_eol]$ebuf_line[a][punk::ansi::move_column $tail_col]
} else {
puts -nonewline stdout [a+ cyan][punk::ansi::vt52move_column [expr {$leftmargin +1}]][punk::ansi::vt52erase_eol][$editbuf line $cursor_row][punk::ansi::vt52move_column [expr {$leftmargin + [$editbuf cursor_column]}]]
#puts -nonewline stdout [a+ cyan][punk::ansi::vt52move_column [expr {$leftmargin +1}]][punk::ansi::vt52erase_eol][$editbuf line $cursor_row][punk::ansi::vt52move_column [expr {$leftmargin + [$editbuf cursor_column]}]]
puts -nonewline stdout [punk::ansi::vt52move_column [expr {$leftmargin +1}]][punk::ansi::vt52erase_eol]$ebuf_line[punk::ansi::vt52move_column $tail_col]
}
#puts -nonewline stdout $chunk
flush stdout
if {[$editbuf last_char] eq "\n"} {
set linelen [punk::ansi::printing_length [$editbuf line $nextsubmit_line_num]]
set inactive_line_colour [a+ yellow bold]
set ebuf_next_line [untabify -with unicode [$editbuf line $nextsubmit_line_num]]
#set linelen [punk::ansi::printing_length [$editbuf line $nextsubmit_line_num]]
set linelen [punk::ansi::printing_length $ebuf_next_line]
if {!$is_vt52} {
puts -nonewline stdout [a+ cyan bold][punk::ansi::move_column [expr {$leftmargin +1}]][$editbuf line $nextsubmit_line_num][a][punk::ansi::move_column [expr {$leftmargin + $linelen +1}]]
set ebuf_next_line [string map [list { } "$space_colour.$inactive_line_colour"] $ebuf_next_line]
puts -nonewline stdout $inactive_line_colour[punk::ansi::move_column [expr {$leftmargin +1}]]$ebuf_next_line[a][punk::ansi::move_column [expr {$leftmargin + $linelen +1}]]
#screen_last_char_add "\n" input inputline
puts -nonewline stdout [punk::ansi::erase_eol]\n
} else {
puts -nonewline stdout [a+ cyan bold][punk::ansi::vt52move_column [expr {$leftmargin +1}]][$editbuf line $nextsubmit_line_num][punk::ansi::vt52move_column [expr {$leftmargin + $linelen +1}]]
puts -nonewline stdout [punk::ansi::vt52move_column [expr {$leftmargin +1}]]$ebuf_next_line[punk::ansi::vt52move_column [expr {$leftmargin + $linelen +1}]]
puts -nonewline stdout [punk::ansi::vt52erase_eol]\n
}

1050
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/overtype-1.7.4.tm

File diff suppressed because it is too large Load Diff

1
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/aliascore-0.1.0.tm

@ -123,6 +123,7 @@ tcl::namespace::eval punk::aliascore {
stripansi ::punk::ansi::ansistrip
ansiwrap ::punk::ansi::ansiwrap
grepstr ::punk::ansi::grepstr
untabify ::punk::ansi::untabify
colour ::punk::console::colour
color ::punk::console::colour
ansi8 ::punk::ansi8

95
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/ansi-0.1.1.tm

@ -726,19 +726,28 @@ tcl::namespace::eval punk::ansi {
} else {
set cols 80
}
set rows 24
if {[dict exists $sdict columns]} {
set c [dict get $sdict columns]
if {$c > 0} {
set cols $c
}
}
#set rows 24
set rows -1
if {[dict exists $sdict rows]} {
set r [dict get $sdict rows]
if {$r > 0} {
set rows $r
}
}
if {$rows == -1} {
set consize [punk::console::get_size]
if {$consize ne ""} {
set rows [expr {[dict get $consize rows] -1}]
} else {
set rows 24
}
}
set dimensions ${cols}x${rows}
}
lassign [split $dimensions x] cols rows
@ -1169,6 +1178,17 @@ tcl::namespace::eval punk::ansi {
a form where the ANSI only represents SGR styling (and perhaps other non-movement
codes) using something like overtype::renderline or overtype::rendertext.
Note that traditional terminals and most modern emulators treat the tab character as a
movement code
So even though the a regex such as {\s} will match various whitespace characters including tabs,
- ANSI SGR codes will not highlight, underline etc the tab characters themselves.
**********************************************************************************************
Also - tab characters are *currently* likely to cause output misalignment/truncation
especially if multiple rounds of grepstr are applied.
TODO - find a better way to handle tab characters in input.
For now - the input should be untabified first e.g with punk::ansi::untabify or similar.
**********************************************************************************************
Examples for returning matched lines only in the help output of the 'i' command:
@ -1359,6 +1379,9 @@ tcl::namespace::eval punk::ansi {
if {[dict exists $received -ansistrip]} {
set data [punk::ansi::ansistrip $data]
}
if {[string first \t $data] >= 0} {
puts stderr "Warning: grepstr does not currently handle tab characters in the input data. Tabs should be expanded to spaces first e.g with punk::ansi::untabify or similar."
}
set highlight [dict get $opts -highlight]
set opt_returnlines [dict get $opts -return]
set opt_invert [dict exists $received -invert-match]
@ -6229,40 +6252,44 @@ be as if this was off - ie lone CR.
#https://crates.io/crates/unicode-display-width
#JMN
set width 0
set sumwidth 0
foreach g $outchars {
set dec_codes [scan $g [tcl::string::repeat %c [string length $g]]]
set maxwidth 0
set max_component_width 0 ;#longest width of any single char in the grapheme cluster
set gwidth 0 ;#width of the whole grapheme cluster
foreach dec $dec_codes {
if {$dec <= 255} {
if {$dec >= 31 && $dec != 127} {
incr sumwidth 1
if {$maxwidth == 0} {
set maxwidth 1
incr gwidth 1
if {$max_component_width == 0} {
set max_component_width 1
}
}
} elseif {$c < 917504 || $c > 917631} {
#codepoint not in the zero-width unicode tag block - \UE0000-\UE000F
#set w [punk::char::char_width $dec]
set w [textutil::wcswidth_char $dec]
if {$w > $maxwidth} {
set maxwidth $w
}
if {$maxwidth == 2} {
#we can stop looking at this grapheme - it's already double width
break
#if {$max_component_width == 2 && $w == 2} {
# #we can stop looking at this grapheme - it's already double width
# break
#}
if {$w > $max_component_width} {
set max_component_width $w
}
incr sumwidth $w
incr gwidth $w
}
}
if {$maxwidth == 2} {
set width 2
} else {
set width $sumwidth
if {$max_component_width == 2} {
#if any char in the grapheme is double width - then the whole grapheme is double width
#This is a simplification that doesn't handle complex scripts such as arabic ligatures.
#review - find a more broadly accurate method of calculating grapheme width that handles complex cases such as ligatures and combiners affecting width.
#we could for example restrict this rule to cases where the base char is east asian width 2 - which would cover the common cases of double-width CJK chars and emojis - but would still allow for combiners and ligatures to affect width in other cases.
set gwidth 2
}
incr sumwidth $gwidth
}
return $width
return $sumwidth
#return [punk::char::ansifreestring_width [join $outchars ""]]
}
namespace eval internal {
@ -6705,7 +6732,8 @@ be as if this was off - ie lone CR.
set scopeinfo [dict create tabstops $tabstops columncount $ccount columnmaxwidths $colmaxwidths]
} else {
#same number of tabs as previous row - so same scope
set scope_key [expr {$r-1}]
#set scope_key [expr {$r-1}] ;#wrong - scope key is the first row in the scope - which is only necessarily the previous row if the previous row is the first row in the scope.
#just leave scope_key as it is from the previous iteration of the loop.
set scopeinfo [dict get $scopes $scope_key]
set update_colmaxwidths [dict get $scopeinfo columnmaxwidths]
set update_tstops [dict get $scopeinfo tabstops]
@ -6866,7 +6894,7 @@ be as if this was off - ie lone CR.
-with -type list|literal(spaces)|literal(unicode) -minsize 2 -maxsize 2 -default spaces -help\
"Whether to replace the tab characters with a custom pair of characters, a plain string of spaces, or to use
the default unicode characters to represent the tabs and following whitespace.
The character \U21E5 followed by a calculated number of \U00A0 (non-breaking space) characters is used when 'unicode'
The character \\U21E5 (\U21E5) followed by a calculated number of \\U00A0 (non-breaking space) characters is used when 'unicode'
is specified.
A 2 element list can be supplied to specify the character to use for the tab and the character to use for the
@ -6997,6 +7025,7 @@ be as if this was off - ie lone CR.
#puts stderr "tstops: $tstops maxdiff: $maxdiff"
set num [lindex $tstops 0]
set out [list]
set lines [list]
foreach line [split $textblock \n] {
#lappend out [untabifyLine $line $maxdiff]
set currPos 0
@ -7955,23 +7984,21 @@ tcl::namespace::eval punk::ansi {
#safe jumptable test
#dict for {k v} $codestate {}
tcl::dict::for {k v} $codestate {
switch -- $v {
"" {}
default {
switch -- $k {
underlinecolour {
append unmergeable "${v}\;"
}
underextended {
#review
append unmergeable "${v}\;"
}
default {
append codemerge "${v}\;"
}
if {$v ne ""} {
switch -- $k {
underlinecolour - underextended {
append unmergeable "${v}\;"
}
default {
append codemerge "${v}\;"
}
}
}
#switch -- $v {
# "" {}
# default {
# }
#}
}
}
set reset_prior 0

168
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/console-0.1.1.tm

@ -1445,15 +1445,15 @@ namespace eval punk::console {
set width [dict get $tsize columns]
set mod [expr {$width % $w}]
set max [expr {$width - $mod}]
set tstops ""
set tstop_ansi ""
set c 1
while {$c <= $max} {
append tstops [string repeat " " $w][punk::ansi::set_tabstop]
append tstop_ansi [string repeat " " $w][punk::ansi::set_tabstop]
incr c $w
}
set punk::console::tabwidth $w ;#we also attempt to read terminal's tabstops and set tabwidth to the apparent spacing of first non-1 value in tabstops list.
catch {textutil::tabify::untabify2 "" $w} ;#textutil tabify can end up uninitialised and raise errors like "can't read Spaces(<n>).." after a tabstop change This call seems to keep tabify happy - review.
puts -nonewline "[punk::ansi::clear_all_tabstops]\n[punk::ansi::set_tabstop]$tstops"
set ::punk::console::tabwidth $w ;#we also attempt to read terminal's tabstops and set tabwidth to the apparent spacing of first non-1 value in tabstops list.
#catch {textutil::tabify::untabify2 "" $w} ;#textutil tabify can end up uninitialised and raise errors like "can't read Spaces(<n>).." after a tabstop change This call seems to keep tabify happy - review.
puts -nonewline "[punk::ansi::clear_all_tabstops]\n[punk::ansi::set_tabstop]$tstop_ansi"
}
@ -2574,6 +2574,46 @@ namespace eval punk::console {
dict set DECRQSS_DICT $name $str
}
namespace eval argdoc {
proc decrqss_choices_and_help {} {
#return a string for use in the punk::args definition for ::punk::console::dec_request_setting that lists the valid choices for the name argument, and their help text.
#This requires a -choices and -choicelabels pair in the args definition, to allow the help text to be directly associated with the choices.
upvar ::punk::console::DECRQSS_DATA DECRQSS_DATA
set outstr "name -type string -choices \{"
foreach {desc name str} $DECRQSS_DATA {
append outstr " $name"
}
append outstr " \}"
append outstr " -choicecolumns 4 -choicelabels \{" \n
foreach {desc name str} $DECRQSS_DATA {
append outstr " $name \{ ${desc}\}" \n
}
append outstr " \} -help "
append outstr " {Dec setting name to query.}"
return $outstr
#append argdef " \} -choicecolumns $opt_columns -choicelabels \{" \n
#append argdef " $choicelabelsdict" \n
#append argdef " \} -choiceinfo \{$choiceinfodict\} -unindentedfields \{-choicelabels\}" \n
}
set DECRQSS_CHOICES [::punk::console::argdoc::decrqss_choices_and_help]
lappend PUNKARGS [list {
@id -id ::punk::console::dec_request_setting
@cmd -name punk::console::dec_request_setting\
-summary\
{Perform DECRQSS query to get DECRPSS response}\
-help\
{DECRQSS query for DEC Selection or Setting.
Return payload from console's DECRPSS response.
}
@opts
-console -type list -minsize 2 -default {stdin stdout}
@values -min 1 -max 1
${$DECRQSS_CHOICES}
}
]
}
#emit DECRQSS and get DECRPSS response
# ---------------------
#NOTE: https://vt100.net/docs/vt510-rm/DECRPSS.html
@ -2582,23 +2622,9 @@ namespace eval punk::console {
# shown at: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
#REVIEW
# ---------------------
#Note also on Freebsd xterm we seem to getresponse ': 1 $ r D...D ST'
#(no leading DCS) - this
#Note also on Freebsd xterm we seem to get response ': 1 $ r D...D ST'
#(no leading DCS) - review
lappend PUNKARGS [list {
@id -id ::punk::console::dec_request_setting
@cmd -name punk::console::dec_request_setting\
-summary\
{Perform DECRQSS query to get DECRPSS response}\
-help\
{DECRQSS query for DEC Selection or Setting.
Return payload from console's DECRPSS response.
}
@opts
-console -type list -minsize 2 -default {stdin stdout}
@values -min 1 -max 1
name -type string
}]
proc dec_request_setting {args} {
set argd [punk::args::parse $args -cache 1 withid ::punk::console::dec_request_setting]
lassign [dict values $argd] leaders opts values
@ -2631,6 +2657,106 @@ namespace eval punk::console {
return [string range $payload 3 end]
}
namespace eval argdoc {
lappend PUNKARGS [list {
@id -id ::punk::console::cursor_style
@cmd -name punk::console::cursor_style\
-summary\
{Get/Set cursor style}\
-help\
{Set cursor style using DECSCUSR.
Note that support for DECSCUSR may be inconsistent across terminals.
Some terminals may only support a subset of the possible styles.}
@opts
-console -type list -minsize 2 -default {stdin stdout}
@values -min 0 -max 1
style -type dict -optional 1 -choicerestricted 0 -choices {0 default 1 block-blink 2 block-steady 3 underline-blink 4 underline-steady 5 beam-blink 6 beam-steady} -choicecolumns 2 -help\
"Cursor style to set
May be an integer or name as in the choices or a dict of the form {<num> <name>}
as returned by a query of the current style.
e.g to set block-blink style, you could specify style as 1, block-blink or {1 block-blink}
After setting to 0 (default) - a subsequent query will not return {0 default} but rather the number and name
of the actual style that the terminal has reset to.
If not supplied, the current style will be queried and returned as a dict
where num is the key and name is the value, e.g. {2 block-steady}.
This allows querying the current style and then re-setting it after temporarily changing it."
}]
}
proc cursor_style {args} {
set argd [punk::args::parse $args -cache 1 withid ::punk::console::cursor_style]
lassign [dict values $argd] leaders opts values
set console [dict get $opts -console]
set style "" ;#if not supplied, will query current style instead of setting
set style_num_to_name [dict create {*}{
0 default
1 block-blink
2 block-steady
3 underline-blink
4 underline-steady
5 beam-blink
6 beam-steady
}]
set style_name_to_num [dict create {*}{
default 0
block-blink 1
block-steady 2
underline-blink 3
underline-steady 4
beam-blink 5
beam-steady 6
}]
if {[dict exists $values style]} {
set style [dict get $values style]
}
if {$style eq ""} {
#query current style using DECRQSS/DECRPSS (dec_request_setting DECSCUSR)
set resultnum [dec_request_setting -console $console DECSCUSR]
if {![string is integer -strict $resultnum]} {
error "cursor_style query for current style returned non-integer result '$resultnum'"
}
if {![dict exists $style_num_to_name $resultnum]} {
error "cursor_style query for current style returned unrecognised style number '$resultnum'. Known styles: [dict keys $style_num_to_name]"
}
set resultname [dict get $style_num_to_name $resultnum]
set result [dict create $resultnum $resultname]
return $result
}
if {[string is integer -strict $style]} {
set style_num $style
if {![dict exists $style_num_to_name $style_num]} {
error "cursor_style unrecognised style number '$style_num'. Known styles: [dict keys $style_num_to_name]"
}
} elseif {[::punk::args::lib::string_is_dict $style]} {
#allow setting cursor using the response from a previous call to query the current style.
if {[dict size $style] != 1} {
error "cursor_style style dict should have exactly one key-value pair {<num> <style>}. Got [dict size $style]"
}
dict for {style_num style_name} $style {} ;#extract the num and name from the dict.
if {![string is integer -strict $style_num]} {
error "cursor_style style dict should be of the form {<num> <value>}. Got '$style'"
}
if {![dict exists $style_num_to_name $style_num]} {
error "cursor_style unrecognised style number '$style_num'. Known styles: [dict keys $style_num_to_name]"
}
if {![dict exists $style_name_to_num $style_name]} {
error "cursor_style unrecognised style name '$style_name'. Known styles: [dict keys $style_name_to_num]"
}
} else {
if {![dict exists $style_name_to_num $style]} {
error "cursor_style unrecognised style '$style'. Known styles: [dict keys $style_name_to_num]"
}
set style_num [dict get $style_name_to_num $style]
}
#set cursor style using DECSCUSR
set request "\x1b\[${style_num} q"
set term_out [lindex $console 1]
puts -nonewline $term_out $request
flush $term_out
}
#terminals lie. This should be a reasonable (albeit relatively slow) test of actual width - but some terminals seem to miscalculate.
#todo - a visual interactive test/questionnaire to ask user if things are lining up or if the terminal is telling fibs about cursor position.
#todo - determine if these anomalies are independent of font

8
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/lib-0.1.6.tm

@ -297,6 +297,14 @@ tcl::namespace::eval punk::lib::compat {
#[list_begin definitions]
if {[catch {
::tcl::mathfunc::isinf 1
}]} {
#review - doesn't seem to cause int-rep of the value to shimmer - but does it reasonably emulate what tcl9's isinf does?
proc ::tcl::mathfunc::isinf {v} {
string match -nocase *inf* $v
}
}
if {"::lremove" ne [info commands ::lremove]} {

112
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/repl-0.1.2.tm

@ -1023,10 +1023,15 @@ namespace eval punk::repl::class {
append new0 \n
}
set underlay [punk::ansi::ansistrip $activeline]
set line_nextchar_col [expr {[punk::char::string_width $underlay] + 1}]
if {$o_cursor_col > $line_nextchar_col} {
set o_cursor_col $line_nextchar_col
}
#set line_nextchar_col [expr {[punk::char::string_width $underlay] + 1}]
set line_nextchar_col [expr {[punk::ansi::printing_length $underlay] + 1}]
#review
#if {$o_cursor_col > $line_nextchar_col} {
# set o_cursor_col $line_nextchar_col
#}
#set o_cursor_col $line_nextchar_col
set initial_insert_mode $o_insert_mode
set mergedinfo [overtype::renderline -info 1 -expand_right 1 -insert_mode $o_insert_mode -cursor_column $o_cursor_col -cursor_row $o_cursor_row $underlay $new0]
@ -1044,8 +1049,20 @@ namespace eval punk::repl::class {
# -- --- --- --- --- ---
set debug_first_row 2
#puts "merged: $mergedinfo"
set debug "add_chunk0"
append debug \n $mergedinfo
set debug "add_chunk[llength $o_chunk_list]"
#append debug \n $mergedinfo
set dinfo [dict create]
dict for {key val} $mergedinfo {
if {$key eq "result"} {
dict set dinfo $key $val
dict set dinfo resultview [ansistring VIEW -lf 1 -vt 1 $val]
} else {
if {$val ne ""} {
dict set dinfo $key $val
}
}
}
append debug \n [showdict $dinfo]
append debug \n "input:[ansistring VIEW -lf 1 -vt 1 $new0] before row:$o_cursor_row after row: $result_row before col:$o_cursor_col after col:$result_col"
package require textblock
set debug [textblock::frame -checkargs 0 -buildcache 0 $debug]
@ -1068,6 +1085,12 @@ namespace eval punk::repl::class {
incr nextrow
set o_cursor_col 1
}
lf_mid {
#for a chunk with embedded newlines - we need to insert lines below and move overflow_right down
#todo
lappend o_rendered_lines ""
set o_cursor_col 1
}
}
if {$insert_lines_below == 1} {
@ -1110,10 +1133,17 @@ namespace eval punk::repl::class {
}
#puts stderr "overtype::renderline -info 1 -expand_right 1 -insert_mode $o_insert_mode -cursor_column $o_cursor_col -cursor_row $o_cursor_row $activeline '$p'"
set underlay $activeline
set line_nextchar_col [expr {[punk::char::string_width $underlay] + 1}]
if {$o_cursor_col > $line_nextchar_col} {
set o_cursor_col $line_nextchar_col
}
#set line_nextchar_col [expr {[punk::char::string_width $underlay] + 1}]
set line_nextchar_col [expr {[punk::ansi::printing_length $underlay] + 1}]
#------------------
#review
#if {$o_cursor_col > $line_nextchar_col} {
# set o_cursor_col $line_nextchar_col
#}
#set o_cursor_col $line_nextchar_col
#------------------
set mergedinfo [overtype::renderline -info 1 -expand_right 1 -insert_mode $o_insert_mode -cursor_column $o_cursor_col -cursor_row $o_cursor_row $underlay $p]
set debug "add_chunk$i"
append debug \n $mergedinfo
@ -1121,13 +1151,14 @@ namespace eval punk::repl::class {
set debug [textblock::frame -checkargs 0 -buildcache 0 $debug]
#catch {punk::console::move_emitblock_return [expr {$debug_first_row + ($i * 6)}] 1 $debug}
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]
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]
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]
set overflow_right [dict get $mergedinfo overflow_right] ;#should be empty if no \v
set unapplied [dict get $mergedinfo unapplied]
set instruction [dict get $mergedinfo instruction]
set insert_lines_below [dict get $mergedinfo insert_lines_below]
if {[string is integer -strict $cmove]} {
if {$cmove == 0} {
set nextrow [expr {$o_cursor_row + 1}]
@ -1138,7 +1169,6 @@ namespace eval punk::repl::class {
} elseif {$cmove >= 1} {
}
} else {
# =<int> - absolute
set nextrow [string range $cmove 1 end]
@ -1147,6 +1177,12 @@ namespace eval punk::repl::class {
incr nextrow
}
set o_cursor_row $nextrow
if {$instruction eq "lf_mid"} {
set o_cursor_col 1
}
if {$insert_lines_below} {
}
@ -1164,6 +1200,17 @@ namespace eval punk::repl::class {
incr i
}
if {$o_insert_mode != $initial_insert_mode} {
set cursor_style_overtype {3 underline-blink}
set cursor_style_insert {5 beam-blink}
#-console ??
if {$o_insert_mode} {
punk::console::cursor_style $cursor_style_insert
} else {
punk::console::cursor_style $cursor_style_overtype
}
}
}
method add_rendered_chunk {rchunk} {
#split only on lf newlines - movement codes and \b \v \r not expected
@ -2110,26 +2157,43 @@ proc repl::repl_process_data {inputchan chunktype chunk stdinlines prompt_config
}
flush stdout
#move_column is more efficient than move since it doesn't require a response on stdin to determine current column,
#but doesn't seem to be universally supported (kermit95 vt modes for example)
#the Horizontal Position Absolute sequence ESC \[ n ` seems to be a possible alternative.
set leftmargin 3
# a+ cyan = CSI 36m
set active_line_colour [a+ cyan] ;# "\x1b\[36m"
#set space_colour "\x1b\[37m"
#set space_colour "\x1b\[38\;5\;244m" ;#term-grey46 - review - make configurable? May not be universal across terminals - but should be widely supported at least as a minimum for grey.
set space_colour [a+ brightblack] ;#"\x1b\[90m" ;#bright black - review - seems to be supported more widely than 256 colour greys]
set ebuf_line [untabify -with unicode [$editbuf line $cursor_row]]
set tail_col [expr {$leftmargin + [$editbuf cursor_column]}]
if {!$is_vt52} {
puts -nonewline stdout [a+ cyan][punk::ansi::move_column [expr {$leftmargin +1}]][punk::ansi::erase_eol][$editbuf line $cursor_row][a][punk::ansi::move_column [expr {$leftmargin + [$editbuf cursor_column]}]]
#puts -nonewline stdout [a+ cyan][punk::ansi::move_column [expr {$leftmargin +1}]][punk::ansi::erase_eol][$editbuf line $cursor_row][a][punk::ansi::move_column [expr {$leftmargin + [$editbuf cursor_column]}]]
set ebuf_line [string map [list { } "$space_colour.$active_line_colour"] $ebuf_line]
puts -nonewline stdout $active_line_colour[punk::ansi::move_column [expr {$leftmargin +1}]][punk::ansi::erase_eol]$ebuf_line[a][punk::ansi::move_column $tail_col]
} else {
puts -nonewline stdout [a+ cyan][punk::ansi::vt52move_column [expr {$leftmargin +1}]][punk::ansi::vt52erase_eol][$editbuf line $cursor_row][punk::ansi::vt52move_column [expr {$leftmargin + [$editbuf cursor_column]}]]
#puts -nonewline stdout [a+ cyan][punk::ansi::vt52move_column [expr {$leftmargin +1}]][punk::ansi::vt52erase_eol][$editbuf line $cursor_row][punk::ansi::vt52move_column [expr {$leftmargin + [$editbuf cursor_column]}]]
puts -nonewline stdout [punk::ansi::vt52move_column [expr {$leftmargin +1}]][punk::ansi::vt52erase_eol]$ebuf_line[punk::ansi::vt52move_column $tail_col]
}
#puts -nonewline stdout $chunk
flush stdout
if {[$editbuf last_char] eq "\n"} {
set linelen [punk::ansi::printing_length [$editbuf line $nextsubmit_line_num]]
set inactive_line_colour [a+ yellow bold]
set ebuf_next_line [untabify -with unicode [$editbuf line $nextsubmit_line_num]]
#set linelen [punk::ansi::printing_length [$editbuf line $nextsubmit_line_num]]
set linelen [punk::ansi::printing_length $ebuf_next_line]
if {!$is_vt52} {
puts -nonewline stdout [a+ cyan bold][punk::ansi::move_column [expr {$leftmargin +1}]][$editbuf line $nextsubmit_line_num][a][punk::ansi::move_column [expr {$leftmargin + $linelen +1}]]
set ebuf_next_line [string map [list { } "$space_colour.$inactive_line_colour"] $ebuf_next_line]
puts -nonewline stdout $inactive_line_colour[punk::ansi::move_column [expr {$leftmargin +1}]]$ebuf_next_line[a][punk::ansi::move_column [expr {$leftmargin + $linelen +1}]]
#screen_last_char_add "\n" input inputline
puts -nonewline stdout [punk::ansi::erase_eol]\n
} else {
puts -nonewline stdout [a+ cyan bold][punk::ansi::vt52move_column [expr {$leftmargin +1}]][$editbuf line $nextsubmit_line_num][punk::ansi::vt52move_column [expr {$leftmargin + $linelen +1}]]
puts -nonewline stdout [punk::ansi::vt52move_column [expr {$leftmargin +1}]]$ebuf_next_line[punk::ansi::vt52move_column [expr {$leftmargin + $linelen +1}]]
puts -nonewline stdout [punk::ansi::vt52erase_eol]\n
}

26
src/vfs/_vfscommon.vfs/lib/app-shellspy/shellspy.tcl

@ -1106,19 +1106,21 @@ dict with prevglobal {}
puts -nonewline stderr "arg$i: '$a'\n"
incr i
}
set argdefinitions [list \
-caller punkshell_dispatcher \
-debugargs 0 \
-debugargsonerror 2 \
-return all \
-soloflags {} \
-defaults [list] \
-required {none} \
-extras {all} \
-commandprocessors $commands \
-values $::argv ]
set argdefinitions [list {*}{
-caller punkshell_dispatcher
-debugargs 0
-debugargsonerror 2
-return all
-soloflags {}
-defaults {}
-required {none}
-extras {all}
} -commandprocessors $commands {*}{
} -values $::argv {*}{
}
]
set is_call_error 0

228
src/vfs/_vfscommon.vfs/modules/opunk/str-0.1.0.tm

@ -59,16 +59,18 @@ tcl::namespace::eval ::opunk::str {
lset this 2 {}
set o_ansisplits [lindex $this 3]
lset this 3 {}
set o_ptlist [lindex $this 4]
set o_elements [lindex $this 4]
lset this 4 {}
set o_elements [lindex $this 5]
set o_graphemes [lindex $this 5]
lset this 5 {}
set o_sgrstacks [lindex $this 6]
set o_ptindex [lindex $this 5]
lset this 6 {}
set o_gx0states [lindex $this 7]
set o_ptlist [lindex $this 7]
lset this 7 {}
set o_splitindex [lindex $this 8]
set o_sgrstacks [lindex $this 8]
lset this 8 {}
set o_gx0states [lindex $this 9]
lset this 9 {}
#---------------------------------------
}
@ -90,11 +92,12 @@ tcl::namespace::eval ::opunk::str {
lset this 1 $o_string
lset this 2 $o_count
lset this 3 $o_ansisplits
lset this 4 $o_ptlist
lset this 5 $o_elements
lset this 6 $o_sgrstacks
lset this 7 $o_gx0states
lset this 8 $o_splitindex
lset this 4 $o_elements
lset this 5 $o_graphemes
lset this 6 $o_ptindex
lset this 7 $o_ptlist
lset this 8 $o_sgrstacks
lset this 9 $o_gx0states
#---------------------------------------
}
@ -198,14 +201,19 @@ tcl::namespace::eval ::opunk::str {
string_t o_string ""
int_t o_count -1 ;#count first updated when string appended or a method causes my.MakeSplit to run (or by count method if constructor argument was empty string)
list_t o_ansisplits [list] ;#split version of string - odd number of elements - alternating plain text and ansicodestrings. Updated by my.makesplit
list_t o_ptlist [list]
list_t o_elements [list]
list_t o_graphemes [list]
list_t o_ptindex [list] ;#map graphemes to pt index
list_t o_ptlist [list]
list_t o_sgrstacks [list]
list_t o_gx0states [list]
list_t o_splitindex [list] ;#map from element index to index in ansisplits
method makesplit {} -upvar -update {o_string o_ansisplits o_ptlist o_elements o_sgrstacks o_gx0states o_splitindex o_count} {
#list_t o_splitindex [list]
method makesplit {} -upvar -update {o_string o_ansisplits o_elements o_graphemes o_ptindex o_ptlist o_sgrstacks o_gx0states o_count} {
#my.makesplit is called whenever string is modified (e.g by append) or when count method is called with an empty string argument (e.g constructor)
#my.makesplit should update count to be the number of plain text graphemes in the split version of string
@ -220,33 +228,45 @@ tcl::namespace::eval ::opunk::str {
set gx0_state [expr {false}] ;#default off
set current_split_index 0 ;#incremented for each pt block, incremented for each code
if {$o_count eq -1} {
set o_count 0
}
#if {$o_count eq -1} {
# set o_count 0
#}
set o_count 0
set o_elements [list]
set o_graphemes [list]
set o_ptindex [list]
set o_elements [list]
set o_ptlist [list]
set o_sgrstacks [list]
set o_gx0states [list]
set o_splitindex [list]
set o_count 0
set splitindex [list]
set codestack [list]
set ptindex -1
foreach {pt code} $o_ansisplits {
incr ptindex
lappend o_ptlist $pt
lappend o_sgrstacks $codestack
lappend o_gx0states $gx0_state
foreach grapheme [punk::char::grapheme_split $pt] {
lappend o_elements [list $etype_grapheme $grapheme]
lappend o_sgrstacks $codestack
lappend o_gx0states $gx0_state
lappend o_splitindex $current_split_index
lappend o_elements [list $etype_grapheme $grapheme]
lappend o_graphemes $grapheme
lappend o_ptindex $ptindex
lappend splitindex $current_split_index
incr o_count
}
#after handling the pt block - incr the current_split_index
incr current_split_index ;#increment for each pt block - whether empty string or not. Indices corresponding to empty PT blocks will therefore not be present in o_splitindex as there were no elements in that ansisplit entry
#we will only get an empty code at the very end of ansisplits (ansisplits is length 0 or odd length - always with pt at start and pt at end)
if {$code ne ""} {
lappend o_sgrstacks $codestack
lappend o_gx0states $gx0_state
lappend o_splitindex $current_split_index
#lappend o_sgrstacks $codestack
#lappend o_gx0states $gx0_state
#lappend o_splitindex $current_split_index
#maintenance warning - dup in append!
if {[punk::ansi::codetype::is_sgr_reset $code]} {
@ -282,12 +302,12 @@ tcl::namespace::eval ::opunk::str {
}
#assertion every grapheme and every individual code has been added to o_elements
#every element has an entry in my sgrstacks
#every element has an entry in my gx0states
#every grapheme has an entry in my sgrstacks
#every grapheme has an entry in my gx0states
#set num_splits [llength [my.get.o_ansisplits $this]]
assert {[llength $o_elements] == [llength $o_sgrstacks] && [llength $o_elements] == [llength $o_gx0states] && [llength $o_elements] == [llength $o_splitindex]}
assert {[llength $o_ptlist] == [llength $o_sgrstacks] && [llength $o_ptlist] == [llength $o_gx0states] && [llength $o_graphemes] == [llength $o_ptindex]}
}
@ -327,7 +347,7 @@ tcl::namespace::eval ::opunk::str {
set total_size 0
set idx 0
upvar o_elements idx_o_elements
upvar o_splitindex idx_o_splitindex
upvar o_ptindex idx_o_ptindex
foreach element $this {
if {$idx == $idx_o_elements} {
#o_elements
@ -342,7 +362,11 @@ tcl::namespace::eval ::opunk::str {
}
incr total_size 48
}
} elseif {$idx == $idx_o_splitindex} {
} elseif {$idx = $idx_o_graphemes} {
incr total_size 48
incr total_size 8
incr total_size 1
} elseif {$idx == $idx_o_ptindex} {
#o_splitindex
set count [llength $element]
incr total_size 48
@ -359,11 +383,12 @@ tcl::namespace::eval ::opunk::str {
#we can't use -update if we also use other helper methods that take 'this' as an argument and may modify any of the same fields
#- so we have to do the upvar-detach-try-finally dance ourselves.
method append {args} -upvar [string map [list %unshare% $::opunk::str::unshare_script_runtime %restore% $::opunk::str::restore_script_runtime] {
upvar $thisVar this
upvar o_string idx_o_string
if {![llength $args]} {
#nothing to append - just return current string
return $o_string
return [lindex $this $idx_o_string]
}
upvar $thisVar this
upvar o_ansisplits idx_o_ansisplits
@ -394,20 +419,20 @@ tcl::namespace::eval ::opunk::str {
if {![llength $o_ptlist]} {
#If the object was initialised with empty string - we can still have empty lists for o_ptlist and o_ansisplits
lappend o_ptlist $catstr
#assertion - if o_ptlist is empty so is o_ansisplits
lappend o_ansisplits $catstr
lappend o_sgrstacks [list]
lappend o_gx0states [expr {false}]
} else {
lset o_ptlist end [tcl::string::cat [lindex $o_ptlist end] $catstr]
lset o_ansisplits end [tcl::string::cat [lindex $o_ansisplits end] $catstr]
}
set last_codestack [lindex $o_sgrstacks end]
set last_gx0state [lindex $o_gx0states end]
set current_split_index [expr {[llength $o_ansisplits]-1}] ;#we are attaching to existing trailing pt - use its splitindex
set ptindex [expr {[llength $o_ptlist] - 1}]
foreach grapheme [punk::char::grapheme_split $catstr] {
lappend o_elements [list $etype_grapheme $grapheme]
lappend o_sgrstacks $last_codestack
lappend o_gx0states $last_gx0state
lappend o_splitindex $current_split_index
lappend o_graphemes $grapheme
lappend o_ptindex $ptindex
incr o_count
}
#incr o_count [my DoCount $catstr] ;#from before we were doing grapheme split.. review
@ -434,7 +459,7 @@ tcl::namespace::eval ::opunk::str {
my.makesplit this ;#update o_count
#set combined_plaintext [join $o_ptlist ""]
#set o_count [my DoCount $combined_plaintext]
#assert {[llength $o_elements] == [llength $o_sgrstacks] && [llength $o_elements] == [llength $o_gx0states] && [llength $o_elements] == [llength $o_splitindex]}
#assert {[llength $o_ptlist] == [llength $o_sgrstacks] && [llength $o_ptlist] == [llength $o_gx0states] && [llength $o_graphemes] == [llength $o_ptindex]}
return $o_string
}
@ -453,24 +478,29 @@ tcl::namespace::eval ::opunk::str {
set ptnew ""
set codestack [lindex $o_sgrstacks end]
set gx0_state [lindex $o_gx0states end]
set current_split_index [lindex $o_splitindex end]
set current_split_index [expr {[llength $o_ansisplits] -1}]
#first pt must be merged with last element of o_ptlist
set new_pt_list [list]
set new_sgrstacks [list]
set new_gx0states [list]
set ptindex [expr {[llength $o_ptlist] - 1}]
foreach {pt code} $newsplits {
lappend new_pt_list $pt
lappend new_sgrstacks $codestack
lappend new_gx0states $gx0_state
::append ptnew $pt
foreach grapheme [punk::char::grapheme_split $pt] {
lappend o_elements [list $etype_grapheme $grapheme]
lappend o_sgrstacks $codestack
lappend o_gx0states $gx0_state
lappend o_splitindex $current_split_index
lappend o_graphemes $grapheme
lappend o_ptindex $ptindex
incr o_count
}
incr current_split_index ;#increment 1 of 2 within each loop
if {$code ne ""} {
lappend o_sgrstacks $codestack
lappend o_gx0states $gx0_state
lappend o_splitindex $current_split_index
#maintenance - dup in MakeSplit!
if {[punk::ansi::codetype::is_sgr_reset $code]} {
set codestack [list $code] ;#pass through as is.
@ -494,15 +524,19 @@ tcl::namespace::eval ::opunk::str {
set gx0_state 0
lappend o_elements [list $etype_gx0 0] ;#don't store code - will complicate debugging if we spit it out and jump character sets
} else {
#lappend o_elements [list other $code]
lappend o_elements [list $etype_other $code]
}
}
incr current_split_index ;#increment 2 of 2
}
incr ptindex
}
lset o_ptlist end [tcl::string::cat [lindex $o_ptlist end] [lindex $new_pt_list 0]]
lappend o_ptlist {*}[lrange $new_pt_list 1 end]
lappend o_sgrstacks {*}[lrange $new_sgrstacks 1 end]
lappend o_gx0states {*}[lrange $new_gx0states 1 end]
lset o_ansisplits end [tcl::string::cat [lindex $o_ansisplits end] [lindex $newsplits 0]]
lappend o_ansisplits {*}[lrange $newsplits 1 end]
@ -513,7 +547,7 @@ tcl::namespace::eval ::opunk::str {
# incr o_count [my DoCount $ptnew]
#}
assert {[llength $o_elements] == [llength $o_sgrstacks] && [llength $o_elements] == [llength $o_gx0states] && [llength $o_elements] == [llength $o_splitindex]}
assert {[llength $o_ptlist] == [llength $o_sgrstacks] && [llength $o_ptlist] == [llength $o_gx0states] && [llength $o_graphemes] == [llength $o_ptindex]}
return $o_string
} finally {
%restore%
@ -566,24 +600,92 @@ tcl::namespace::eval ::opunk::str {
If the index is out of bounds, an empty string is returned."
}]
}
method INDEXCHAR -upvar {idx} {
set o_elements [my.get.o_elements $this]
if {![llength $o_elements]} {my.makesplit this}
#returns the character at the given index in the original string - including any ANSI codes. Not the same as the grapheme at that index in the split version of the string.
#return [string index [my.get.o_string $this] $idx]
set grapheme_positions [lsearch -all -exact -index 0 $o_elements g]
set required_posn [lindex $grapheme_positions $idx]
if {$required_posn eq ""} {
return ""
} else {
return [lindex $o_elements $required_posn 1]
#this is not the same as the character at that index in the original string, or even in the ansistripped string - which may be an ANSI code or *part of a grapheme cluster*
set o_graphemes [my.get.o_graphemes $this]
if {![llength $o_graphemes]} {
my.makesplit this
set o_ographemes [my.get.o_graphemes $this]
}
return [lindex $o_graphemes $idx]
#experimental.
#set grapheme_positions [lsearch -all -exact -index 0 $o_elements g] ;#slow on large strings.
#set required_posn [lindex $grapheme_positions $idx]
#if {$required_posn eq ""} {
# puts stderr "o_elements: $o_elements grapheme_positions: $grapheme_positions required_posn: $required_posn"
# return ""
#} else {
# return [lindex $o_elements $required_posn 1]
#}
}
method INDEX -upvar {idx} {
#returns the index in the original string of the character at the given grapheme index - i.e the index of the start of the grapheme cluster in the original string
upvar o_graphemes idx_o_graphemes
set o_graphemes [lindex $this $idx_o_graphemes]
if {![llength $o_graphemes]} {
my.makesplit this
set o_graphemes [lindex $this $idx_o_graphemes]
}
upvar o_sgrstacks idx_o_sgrstacks
upvar o_ptindex idx_o_ptindex
set ptindex [lindex $this $idx_o_ptindex]
set ptidx [lindex $ptindex $idx]
set stack [lindex $this $idx_o_sgrstacks $ptidx]
set ansi [punk::ansi::codetype::sgr_merge $stack]
return $ansi[lindex $o_graphemes $idx]
}
namespace eval argdoc {
variable PUNKARGS
lappend PUNKARGS [list {
@id -id ::opunk::Str::INDEXCODE
@cmd -name opunk::Str::INDEXCODE -summary\
"Get ANSI SGR code in effect at index or after last index"\
-help\
"Returns the ANSI SGR code at the position of the character returned by INDEX, or empty string
if no code at that position.
Retrieves the ANSI SGR code in effect after the entire string if index is ${$B}end+1${$N} or numerical equivalent.
If there are multiple codes in effect at that position, the returned code will be a merge of
those codes - so for example if there is a red foreground and then a bold later, the returned
code will be a single code with both red and bold in it.
This is effectively the ANSI code that would need to be applied to the character returned by
INDEX to get the same visual effect as the original string at that position."
@values -min 2 -max 2
string -type string -help\
"String to index into, which may contain ANSI codes."
index -type indexexpression -help\
"Index of character to get code for, or as a special case:
the index ${$B}end+1${$N} or numerical equivalent to get the code in effect after the last character (if any).
For example end will give the code in effect at the last character, and end+1 will give the code in
effect after the last character."
}]
}
method INDEXCODE -upvar {idx} {
#TODO - special cases for pure ANSI string and end+1 equivalent index to get code in effect after last character.
upvar o_graphemes idx_o_graphemes
set o_graphemes [lindex $this $idx_o_graphemes]
if {![llength $o_graphemes]} {
my.makesplit this
set o_graphemes [lindex $this $idx_o_graphemes]
}
upvar o_sgrstacks idx_o_sgrstacks
upvar o_ptindex idx_o_ptindex
set ptindex [lindex $this $idx_o_ptindex]
set ptidx [lindex $ptindex $idx]
set stack [lindex $this $idx_o_sgrstacks $ptidx]
set ansi [punk::ansi::codetype::sgr_merge $stack]
return $ansi
}
}
constructor {initial_string} {
return [list "opunk::Str" $initial_string -1 [list] [list] [list] [list] [list] [list]]
return [list "opunk::Str" $initial_string -1 [list] [list] [list] [list] [list] [list] [list]]
}
}
}

1050
src/vfs/_vfscommon.vfs/modules/overtype-1.7.4.tm

File diff suppressed because it is too large Load Diff

1
src/vfs/_vfscommon.vfs/modules/punk/aliascore-0.1.0.tm

@ -123,6 +123,7 @@ tcl::namespace::eval punk::aliascore {
stripansi ::punk::ansi::ansistrip
ansiwrap ::punk::ansi::ansiwrap
grepstr ::punk::ansi::grepstr
untabify ::punk::ansi::untabify
colour ::punk::console::colour
color ::punk::console::colour
ansi8 ::punk::ansi8

95
src/vfs/_vfscommon.vfs/modules/punk/ansi-0.1.1.tm

@ -726,19 +726,28 @@ tcl::namespace::eval punk::ansi {
} else {
set cols 80
}
set rows 24
if {[dict exists $sdict columns]} {
set c [dict get $sdict columns]
if {$c > 0} {
set cols $c
}
}
#set rows 24
set rows -1
if {[dict exists $sdict rows]} {
set r [dict get $sdict rows]
if {$r > 0} {
set rows $r
}
}
if {$rows == -1} {
set consize [punk::console::get_size]
if {$consize ne ""} {
set rows [expr {[dict get $consize rows] -1}]
} else {
set rows 24
}
}
set dimensions ${cols}x${rows}
}
lassign [split $dimensions x] cols rows
@ -1169,6 +1178,17 @@ tcl::namespace::eval punk::ansi {
a form where the ANSI only represents SGR styling (and perhaps other non-movement
codes) using something like overtype::renderline or overtype::rendertext.
Note that traditional terminals and most modern emulators treat the tab character as a
movement code
So even though the a regex such as {\s} will match various whitespace characters including tabs,
- ANSI SGR codes will not highlight, underline etc the tab characters themselves.
**********************************************************************************************
Also - tab characters are *currently* likely to cause output misalignment/truncation
especially if multiple rounds of grepstr are applied.
TODO - find a better way to handle tab characters in input.
For now - the input should be untabified first e.g with punk::ansi::untabify or similar.
**********************************************************************************************
Examples for returning matched lines only in the help output of the 'i' command:
@ -1359,6 +1379,9 @@ tcl::namespace::eval punk::ansi {
if {[dict exists $received -ansistrip]} {
set data [punk::ansi::ansistrip $data]
}
if {[string first \t $data] >= 0} {
puts stderr "Warning: grepstr does not currently handle tab characters in the input data. Tabs should be expanded to spaces first e.g with punk::ansi::untabify or similar."
}
set highlight [dict get $opts -highlight]
set opt_returnlines [dict get $opts -return]
set opt_invert [dict exists $received -invert-match]
@ -6229,40 +6252,44 @@ be as if this was off - ie lone CR.
#https://crates.io/crates/unicode-display-width
#JMN
set width 0
set sumwidth 0
foreach g $outchars {
set dec_codes [scan $g [tcl::string::repeat %c [string length $g]]]
set maxwidth 0
set max_component_width 0 ;#longest width of any single char in the grapheme cluster
set gwidth 0 ;#width of the whole grapheme cluster
foreach dec $dec_codes {
if {$dec <= 255} {
if {$dec >= 31 && $dec != 127} {
incr sumwidth 1
if {$maxwidth == 0} {
set maxwidth 1
incr gwidth 1
if {$max_component_width == 0} {
set max_component_width 1
}
}
} elseif {$c < 917504 || $c > 917631} {
#codepoint not in the zero-width unicode tag block - \UE0000-\UE000F
#set w [punk::char::char_width $dec]
set w [textutil::wcswidth_char $dec]
if {$w > $maxwidth} {
set maxwidth $w
}
if {$maxwidth == 2} {
#we can stop looking at this grapheme - it's already double width
break
#if {$max_component_width == 2 && $w == 2} {
# #we can stop looking at this grapheme - it's already double width
# break
#}
if {$w > $max_component_width} {
set max_component_width $w
}
incr sumwidth $w
incr gwidth $w
}
}
if {$maxwidth == 2} {
set width 2
} else {
set width $sumwidth
if {$max_component_width == 2} {
#if any char in the grapheme is double width - then the whole grapheme is double width
#This is a simplification that doesn't handle complex scripts such as arabic ligatures.
#review - find a more broadly accurate method of calculating grapheme width that handles complex cases such as ligatures and combiners affecting width.
#we could for example restrict this rule to cases where the base char is east asian width 2 - which would cover the common cases of double-width CJK chars and emojis - but would still allow for combiners and ligatures to affect width in other cases.
set gwidth 2
}
incr sumwidth $gwidth
}
return $width
return $sumwidth
#return [punk::char::ansifreestring_width [join $outchars ""]]
}
namespace eval internal {
@ -6705,7 +6732,8 @@ be as if this was off - ie lone CR.
set scopeinfo [dict create tabstops $tabstops columncount $ccount columnmaxwidths $colmaxwidths]
} else {
#same number of tabs as previous row - so same scope
set scope_key [expr {$r-1}]
#set scope_key [expr {$r-1}] ;#wrong - scope key is the first row in the scope - which is only necessarily the previous row if the previous row is the first row in the scope.
#just leave scope_key as it is from the previous iteration of the loop.
set scopeinfo [dict get $scopes $scope_key]
set update_colmaxwidths [dict get $scopeinfo columnmaxwidths]
set update_tstops [dict get $scopeinfo tabstops]
@ -6866,7 +6894,7 @@ be as if this was off - ie lone CR.
-with -type list|literal(spaces)|literal(unicode) -minsize 2 -maxsize 2 -default spaces -help\
"Whether to replace the tab characters with a custom pair of characters, a plain string of spaces, or to use
the default unicode characters to represent the tabs and following whitespace.
The character \U21E5 followed by a calculated number of \U00A0 (non-breaking space) characters is used when 'unicode'
The character \\U21E5 (\U21E5) followed by a calculated number of \\U00A0 (non-breaking space) characters is used when 'unicode'
is specified.
A 2 element list can be supplied to specify the character to use for the tab and the character to use for the
@ -6997,6 +7025,7 @@ be as if this was off - ie lone CR.
#puts stderr "tstops: $tstops maxdiff: $maxdiff"
set num [lindex $tstops 0]
set out [list]
set lines [list]
foreach line [split $textblock \n] {
#lappend out [untabifyLine $line $maxdiff]
set currPos 0
@ -7955,23 +7984,21 @@ tcl::namespace::eval punk::ansi {
#safe jumptable test
#dict for {k v} $codestate {}
tcl::dict::for {k v} $codestate {
switch -- $v {
"" {}
default {
switch -- $k {
underlinecolour {
append unmergeable "${v}\;"
}
underextended {
#review
append unmergeable "${v}\;"
}
default {
append codemerge "${v}\;"
}
if {$v ne ""} {
switch -- $k {
underlinecolour - underextended {
append unmergeable "${v}\;"
}
default {
append codemerge "${v}\;"
}
}
}
#switch -- $v {
# "" {}
# default {
# }
#}
}
}
set reset_prior 0

168
src/vfs/_vfscommon.vfs/modules/punk/console-0.1.1.tm

@ -1445,15 +1445,15 @@ namespace eval punk::console {
set width [dict get $tsize columns]
set mod [expr {$width % $w}]
set max [expr {$width - $mod}]
set tstops ""
set tstop_ansi ""
set c 1
while {$c <= $max} {
append tstops [string repeat " " $w][punk::ansi::set_tabstop]
append tstop_ansi [string repeat " " $w][punk::ansi::set_tabstop]
incr c $w
}
set punk::console::tabwidth $w ;#we also attempt to read terminal's tabstops and set tabwidth to the apparent spacing of first non-1 value in tabstops list.
catch {textutil::tabify::untabify2 "" $w} ;#textutil tabify can end up uninitialised and raise errors like "can't read Spaces(<n>).." after a tabstop change This call seems to keep tabify happy - review.
puts -nonewline "[punk::ansi::clear_all_tabstops]\n[punk::ansi::set_tabstop]$tstops"
set ::punk::console::tabwidth $w ;#we also attempt to read terminal's tabstops and set tabwidth to the apparent spacing of first non-1 value in tabstops list.
#catch {textutil::tabify::untabify2 "" $w} ;#textutil tabify can end up uninitialised and raise errors like "can't read Spaces(<n>).." after a tabstop change This call seems to keep tabify happy - review.
puts -nonewline "[punk::ansi::clear_all_tabstops]\n[punk::ansi::set_tabstop]$tstop_ansi"
}
@ -2574,6 +2574,46 @@ namespace eval punk::console {
dict set DECRQSS_DICT $name $str
}
namespace eval argdoc {
proc decrqss_choices_and_help {} {
#return a string for use in the punk::args definition for ::punk::console::dec_request_setting that lists the valid choices for the name argument, and their help text.
#This requires a -choices and -choicelabels pair in the args definition, to allow the help text to be directly associated with the choices.
upvar ::punk::console::DECRQSS_DATA DECRQSS_DATA
set outstr "name -type string -choices \{"
foreach {desc name str} $DECRQSS_DATA {
append outstr " $name"
}
append outstr " \}"
append outstr " -choicecolumns 4 -choicelabels \{" \n
foreach {desc name str} $DECRQSS_DATA {
append outstr " $name \{ ${desc}\}" \n
}
append outstr " \} -help "
append outstr " {Dec setting name to query.}"
return $outstr
#append argdef " \} -choicecolumns $opt_columns -choicelabels \{" \n
#append argdef " $choicelabelsdict" \n
#append argdef " \} -choiceinfo \{$choiceinfodict\} -unindentedfields \{-choicelabels\}" \n
}
set DECRQSS_CHOICES [::punk::console::argdoc::decrqss_choices_and_help]
lappend PUNKARGS [list {
@id -id ::punk::console::dec_request_setting
@cmd -name punk::console::dec_request_setting\
-summary\
{Perform DECRQSS query to get DECRPSS response}\
-help\
{DECRQSS query for DEC Selection or Setting.
Return payload from console's DECRPSS response.
}
@opts
-console -type list -minsize 2 -default {stdin stdout}
@values -min 1 -max 1
${$DECRQSS_CHOICES}
}
]
}
#emit DECRQSS and get DECRPSS response
# ---------------------
#NOTE: https://vt100.net/docs/vt510-rm/DECRPSS.html
@ -2582,23 +2622,9 @@ namespace eval punk::console {
# shown at: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
#REVIEW
# ---------------------
#Note also on Freebsd xterm we seem to getresponse ': 1 $ r D...D ST'
#(no leading DCS) - this
#Note also on Freebsd xterm we seem to get response ': 1 $ r D...D ST'
#(no leading DCS) - review
lappend PUNKARGS [list {
@id -id ::punk::console::dec_request_setting
@cmd -name punk::console::dec_request_setting\
-summary\
{Perform DECRQSS query to get DECRPSS response}\
-help\
{DECRQSS query for DEC Selection or Setting.
Return payload from console's DECRPSS response.
}
@opts
-console -type list -minsize 2 -default {stdin stdout}
@values -min 1 -max 1
name -type string
}]
proc dec_request_setting {args} {
set argd [punk::args::parse $args -cache 1 withid ::punk::console::dec_request_setting]
lassign [dict values $argd] leaders opts values
@ -2631,6 +2657,106 @@ namespace eval punk::console {
return [string range $payload 3 end]
}
namespace eval argdoc {
lappend PUNKARGS [list {
@id -id ::punk::console::cursor_style
@cmd -name punk::console::cursor_style\
-summary\
{Get/Set cursor style}\
-help\
{Set cursor style using DECSCUSR.
Note that support for DECSCUSR may be inconsistent across terminals.
Some terminals may only support a subset of the possible styles.}
@opts
-console -type list -minsize 2 -default {stdin stdout}
@values -min 0 -max 1
style -type dict -optional 1 -choicerestricted 0 -choices {0 default 1 block-blink 2 block-steady 3 underline-blink 4 underline-steady 5 beam-blink 6 beam-steady} -choicecolumns 2 -help\
"Cursor style to set
May be an integer or name as in the choices or a dict of the form {<num> <name>}
as returned by a query of the current style.
e.g to set block-blink style, you could specify style as 1, block-blink or {1 block-blink}
After setting to 0 (default) - a subsequent query will not return {0 default} but rather the number and name
of the actual style that the terminal has reset to.
If not supplied, the current style will be queried and returned as a dict
where num is the key and name is the value, e.g. {2 block-steady}.
This allows querying the current style and then re-setting it after temporarily changing it."
}]
}
proc cursor_style {args} {
set argd [punk::args::parse $args -cache 1 withid ::punk::console::cursor_style]
lassign [dict values $argd] leaders opts values
set console [dict get $opts -console]
set style "" ;#if not supplied, will query current style instead of setting
set style_num_to_name [dict create {*}{
0 default
1 block-blink
2 block-steady
3 underline-blink
4 underline-steady
5 beam-blink
6 beam-steady
}]
set style_name_to_num [dict create {*}{
default 0
block-blink 1
block-steady 2
underline-blink 3
underline-steady 4
beam-blink 5
beam-steady 6
}]
if {[dict exists $values style]} {
set style [dict get $values style]
}
if {$style eq ""} {
#query current style using DECRQSS/DECRPSS (dec_request_setting DECSCUSR)
set resultnum [dec_request_setting -console $console DECSCUSR]
if {![string is integer -strict $resultnum]} {
error "cursor_style query for current style returned non-integer result '$resultnum'"
}
if {![dict exists $style_num_to_name $resultnum]} {
error "cursor_style query for current style returned unrecognised style number '$resultnum'. Known styles: [dict keys $style_num_to_name]"
}
set resultname [dict get $style_num_to_name $resultnum]
set result [dict create $resultnum $resultname]
return $result
}
if {[string is integer -strict $style]} {
set style_num $style
if {![dict exists $style_num_to_name $style_num]} {
error "cursor_style unrecognised style number '$style_num'. Known styles: [dict keys $style_num_to_name]"
}
} elseif {[::punk::args::lib::string_is_dict $style]} {
#allow setting cursor using the response from a previous call to query the current style.
if {[dict size $style] != 1} {
error "cursor_style style dict should have exactly one key-value pair {<num> <style>}. Got [dict size $style]"
}
dict for {style_num style_name} $style {} ;#extract the num and name from the dict.
if {![string is integer -strict $style_num]} {
error "cursor_style style dict should be of the form {<num> <value>}. Got '$style'"
}
if {![dict exists $style_num_to_name $style_num]} {
error "cursor_style unrecognised style number '$style_num'. Known styles: [dict keys $style_num_to_name]"
}
if {![dict exists $style_name_to_num $style_name]} {
error "cursor_style unrecognised style name '$style_name'. Known styles: [dict keys $style_name_to_num]"
}
} else {
if {![dict exists $style_name_to_num $style]} {
error "cursor_style unrecognised style '$style'. Known styles: [dict keys $style_name_to_num]"
}
set style_num [dict get $style_name_to_num $style]
}
#set cursor style using DECSCUSR
set request "\x1b\[${style_num} q"
set term_out [lindex $console 1]
puts -nonewline $term_out $request
flush $term_out
}
#terminals lie. This should be a reasonable (albeit relatively slow) test of actual width - but some terminals seem to miscalculate.
#todo - a visual interactive test/questionnaire to ask user if things are lining up or if the terminal is telling fibs about cursor position.
#todo - determine if these anomalies are independent of font

8
src/vfs/_vfscommon.vfs/modules/punk/lib-0.1.6.tm

@ -297,6 +297,14 @@ tcl::namespace::eval punk::lib::compat {
#[list_begin definitions]
if {[catch {
::tcl::mathfunc::isinf 1
}]} {
#review - doesn't seem to cause int-rep of the value to shimmer - but does it reasonably emulate what tcl9's isinf does?
proc ::tcl::mathfunc::isinf {v} {
string match -nocase *inf* $v
}
}
if {"::lremove" ne [info commands ::lremove]} {

112
src/vfs/_vfscommon.vfs/modules/punk/repl-0.1.2.tm

@ -1023,10 +1023,15 @@ namespace eval punk::repl::class {
append new0 \n
}
set underlay [punk::ansi::ansistrip $activeline]
set line_nextchar_col [expr {[punk::char::string_width $underlay] + 1}]
if {$o_cursor_col > $line_nextchar_col} {
set o_cursor_col $line_nextchar_col
}
#set line_nextchar_col [expr {[punk::char::string_width $underlay] + 1}]
set line_nextchar_col [expr {[punk::ansi::printing_length $underlay] + 1}]
#review
#if {$o_cursor_col > $line_nextchar_col} {
# set o_cursor_col $line_nextchar_col
#}
#set o_cursor_col $line_nextchar_col
set initial_insert_mode $o_insert_mode
set mergedinfo [overtype::renderline -info 1 -expand_right 1 -insert_mode $o_insert_mode -cursor_column $o_cursor_col -cursor_row $o_cursor_row $underlay $new0]
@ -1044,8 +1049,20 @@ namespace eval punk::repl::class {
# -- --- --- --- --- ---
set debug_first_row 2
#puts "merged: $mergedinfo"
set debug "add_chunk0"
append debug \n $mergedinfo
set debug "add_chunk[llength $o_chunk_list]"
#append debug \n $mergedinfo
set dinfo [dict create]
dict for {key val} $mergedinfo {
if {$key eq "result"} {
dict set dinfo $key $val
dict set dinfo resultview [ansistring VIEW -lf 1 -vt 1 $val]
} else {
if {$val ne ""} {
dict set dinfo $key $val
}
}
}
append debug \n [showdict $dinfo]
append debug \n "input:[ansistring VIEW -lf 1 -vt 1 $new0] before row:$o_cursor_row after row: $result_row before col:$o_cursor_col after col:$result_col"
package require textblock
set debug [textblock::frame -checkargs 0 -buildcache 0 $debug]
@ -1068,6 +1085,12 @@ namespace eval punk::repl::class {
incr nextrow
set o_cursor_col 1
}
lf_mid {
#for a chunk with embedded newlines - we need to insert lines below and move overflow_right down
#todo
lappend o_rendered_lines ""
set o_cursor_col 1
}
}
if {$insert_lines_below == 1} {
@ -1110,10 +1133,17 @@ namespace eval punk::repl::class {
}
#puts stderr "overtype::renderline -info 1 -expand_right 1 -insert_mode $o_insert_mode -cursor_column $o_cursor_col -cursor_row $o_cursor_row $activeline '$p'"
set underlay $activeline
set line_nextchar_col [expr {[punk::char::string_width $underlay] + 1}]
if {$o_cursor_col > $line_nextchar_col} {
set o_cursor_col $line_nextchar_col
}
#set line_nextchar_col [expr {[punk::char::string_width $underlay] + 1}]
set line_nextchar_col [expr {[punk::ansi::printing_length $underlay] + 1}]
#------------------
#review
#if {$o_cursor_col > $line_nextchar_col} {
# set o_cursor_col $line_nextchar_col
#}
#set o_cursor_col $line_nextchar_col
#------------------
set mergedinfo [overtype::renderline -info 1 -expand_right 1 -insert_mode $o_insert_mode -cursor_column $o_cursor_col -cursor_row $o_cursor_row $underlay $p]
set debug "add_chunk$i"
append debug \n $mergedinfo
@ -1121,13 +1151,14 @@ namespace eval punk::repl::class {
set debug [textblock::frame -checkargs 0 -buildcache 0 $debug]
#catch {punk::console::move_emitblock_return [expr {$debug_first_row + ($i * 6)}] 1 $debug}
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]
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]
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]
set overflow_right [dict get $mergedinfo overflow_right] ;#should be empty if no \v
set unapplied [dict get $mergedinfo unapplied]
set instruction [dict get $mergedinfo instruction]
set insert_lines_below [dict get $mergedinfo insert_lines_below]
if {[string is integer -strict $cmove]} {
if {$cmove == 0} {
set nextrow [expr {$o_cursor_row + 1}]
@ -1138,7 +1169,6 @@ namespace eval punk::repl::class {
} elseif {$cmove >= 1} {
}
} else {
# =<int> - absolute
set nextrow [string range $cmove 1 end]
@ -1147,6 +1177,12 @@ namespace eval punk::repl::class {
incr nextrow
}
set o_cursor_row $nextrow
if {$instruction eq "lf_mid"} {
set o_cursor_col 1
}
if {$insert_lines_below} {
}
@ -1164,6 +1200,17 @@ namespace eval punk::repl::class {
incr i
}
if {$o_insert_mode != $initial_insert_mode} {
set cursor_style_overtype {3 underline-blink}
set cursor_style_insert {5 beam-blink}
#-console ??
if {$o_insert_mode} {
punk::console::cursor_style $cursor_style_insert
} else {
punk::console::cursor_style $cursor_style_overtype
}
}
}
method add_rendered_chunk {rchunk} {
#split only on lf newlines - movement codes and \b \v \r not expected
@ -2110,26 +2157,43 @@ proc repl::repl_process_data {inputchan chunktype chunk stdinlines prompt_config
}
flush stdout
#move_column is more efficient than move since it doesn't require a response on stdin to determine current column,
#but doesn't seem to be universally supported (kermit95 vt modes for example)
#the Horizontal Position Absolute sequence ESC \[ n ` seems to be a possible alternative.
set leftmargin 3
# a+ cyan = CSI 36m
set active_line_colour [a+ cyan] ;# "\x1b\[36m"
#set space_colour "\x1b\[37m"
#set space_colour "\x1b\[38\;5\;244m" ;#term-grey46 - review - make configurable? May not be universal across terminals - but should be widely supported at least as a minimum for grey.
set space_colour [a+ brightblack] ;#"\x1b\[90m" ;#bright black - review - seems to be supported more widely than 256 colour greys]
set ebuf_line [untabify -with unicode [$editbuf line $cursor_row]]
set tail_col [expr {$leftmargin + [$editbuf cursor_column]}]
if {!$is_vt52} {
puts -nonewline stdout [a+ cyan][punk::ansi::move_column [expr {$leftmargin +1}]][punk::ansi::erase_eol][$editbuf line $cursor_row][a][punk::ansi::move_column [expr {$leftmargin + [$editbuf cursor_column]}]]
#puts -nonewline stdout [a+ cyan][punk::ansi::move_column [expr {$leftmargin +1}]][punk::ansi::erase_eol][$editbuf line $cursor_row][a][punk::ansi::move_column [expr {$leftmargin + [$editbuf cursor_column]}]]
set ebuf_line [string map [list { } "$space_colour.$active_line_colour"] $ebuf_line]
puts -nonewline stdout $active_line_colour[punk::ansi::move_column [expr {$leftmargin +1}]][punk::ansi::erase_eol]$ebuf_line[a][punk::ansi::move_column $tail_col]
} else {
puts -nonewline stdout [a+ cyan][punk::ansi::vt52move_column [expr {$leftmargin +1}]][punk::ansi::vt52erase_eol][$editbuf line $cursor_row][punk::ansi::vt52move_column [expr {$leftmargin + [$editbuf cursor_column]}]]
#puts -nonewline stdout [a+ cyan][punk::ansi::vt52move_column [expr {$leftmargin +1}]][punk::ansi::vt52erase_eol][$editbuf line $cursor_row][punk::ansi::vt52move_column [expr {$leftmargin + [$editbuf cursor_column]}]]
puts -nonewline stdout [punk::ansi::vt52move_column [expr {$leftmargin +1}]][punk::ansi::vt52erase_eol]$ebuf_line[punk::ansi::vt52move_column $tail_col]
}
#puts -nonewline stdout $chunk
flush stdout
if {[$editbuf last_char] eq "\n"} {
set linelen [punk::ansi::printing_length [$editbuf line $nextsubmit_line_num]]
set inactive_line_colour [a+ yellow bold]
set ebuf_next_line [untabify -with unicode [$editbuf line $nextsubmit_line_num]]
#set linelen [punk::ansi::printing_length [$editbuf line $nextsubmit_line_num]]
set linelen [punk::ansi::printing_length $ebuf_next_line]
if {!$is_vt52} {
puts -nonewline stdout [a+ cyan bold][punk::ansi::move_column [expr {$leftmargin +1}]][$editbuf line $nextsubmit_line_num][a][punk::ansi::move_column [expr {$leftmargin + $linelen +1}]]
set ebuf_next_line [string map [list { } "$space_colour.$inactive_line_colour"] $ebuf_next_line]
puts -nonewline stdout $inactive_line_colour[punk::ansi::move_column [expr {$leftmargin +1}]]$ebuf_next_line[a][punk::ansi::move_column [expr {$leftmargin + $linelen +1}]]
#screen_last_char_add "\n" input inputline
puts -nonewline stdout [punk::ansi::erase_eol]\n
} else {
puts -nonewline stdout [a+ cyan bold][punk::ansi::vt52move_column [expr {$leftmargin +1}]][$editbuf line $nextsubmit_line_num][punk::ansi::vt52move_column [expr {$leftmargin + $linelen +1}]]
puts -nonewline stdout [punk::ansi::vt52move_column [expr {$leftmargin +1}]]$ebuf_next_line[punk::ansi::vt52move_column [expr {$leftmargin + $linelen +1}]]
puts -nonewline stdout [punk::ansi::vt52erase_eol]\n
}

Loading…
Cancel
Save