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.
@ -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.
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.
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.
#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.
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]
#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]}]
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
#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 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.
#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.
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.
@ -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.
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.
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.
#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.
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]
#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]}]
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.
@ -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.
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.
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.
#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.
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]
#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]}]
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.
@ -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.
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.
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.
#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.
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]
#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]}]
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
#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 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.
#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.
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.
@ -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.
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.
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.
#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.
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]
#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]}]