Julian Noble
5 months ago
41 changed files with 1925 additions and 11217 deletions
File diff suppressed because it is too large
Load Diff
@ -1,963 +0,0 @@ |
|||||||
# -*- tcl -*- |
|
||||||
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from <pkg>-buildversion.txt |
|
||||||
# |
|
||||||
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. |
|
||||||
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. |
|
||||||
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
||||||
# (C) Julian Noble 2003-2023 |
|
||||||
# |
|
||||||
# @@ Meta Begin |
|
||||||
# Application overtype 1.5.1 |
|
||||||
# Meta platform tcl |
|
||||||
# Meta license BSD |
|
||||||
# @@ Meta End |
|
||||||
|
|
||||||
|
|
||||||
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
||||||
# doctools header |
|
||||||
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
||||||
#*** !doctools |
|
||||||
#[manpage_begin overtype_module_overtype 0 1.5.1] |
|
||||||
#[copyright "2024"] |
|
||||||
#[titledesc {overtype text layout - ansi aware}] [comment {-- Name section and table of contents description --}] |
|
||||||
#[moddesc {overtype text layout}] [comment {-- Description at end of page heading --}] |
|
||||||
#[require overtype] |
|
||||||
#[keywords module text ansi] |
|
||||||
#[description] |
|
||||||
#[para] - |
|
||||||
|
|
||||||
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
||||||
|
|
||||||
#*** !doctools |
|
||||||
#[section Overview] |
|
||||||
#[para] overview of overtype |
|
||||||
#[subsection Concepts] |
|
||||||
#[para] - |
|
||||||
|
|
||||||
|
|
||||||
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
||||||
## Requirements |
|
||||||
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
||||||
|
|
||||||
#*** !doctools |
|
||||||
#[subsection dependencies] |
|
||||||
#[para] packages used by overtype |
|
||||||
#[list_begin itemized] |
|
||||||
|
|
||||||
package require Tcl 8.6 |
|
||||||
package require textutil |
|
||||||
package require punk::ansi ;#required to detect, split, strip and calculate lengths |
|
||||||
package require punk::char ;#box drawing - and also unicode character width determination for proper layout of text with double-column-width chars |
|
||||||
#*** !doctools |
|
||||||
#[item] [package {Tcl 8.6}] |
|
||||||
#[item] [package textutil] |
|
||||||
#[item] [package punk::ansi] |
|
||||||
#[para] - required to detect, split, strip and calculate lengths of text possibly containing ansi codes |
|
||||||
#[item] [package punk::char] |
|
||||||
#[para] - box drawing - and also unicode character width determination for proper layout of text with double-column-width chars |
|
||||||
|
|
||||||
# #package require frobz |
|
||||||
# #*** !doctools |
|
||||||
# #[item] [package {frobz}] |
|
||||||
|
|
||||||
#*** !doctools |
|
||||||
#[list_end] |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
||||||
#*** !doctools |
|
||||||
#[section API] |
|
||||||
|
|
||||||
|
|
||||||
#Julian Noble <julian@precisium.com.au> - 2003 |
|
||||||
#Released under standard 'BSD license' conditions. |
|
||||||
# |
|
||||||
#todo - ellipsis truncation indicator for center,right |
|
||||||
|
|
||||||
#v1.4 2023-07 - naive ansi color handling - todo - fix string range |
|
||||||
# - need to extract and replace ansi codes? |
|
||||||
|
|
||||||
namespace eval overtype { |
|
||||||
namespace export * |
|
||||||
variable default_ellipsis_horizontal "..." ;#fallback |
|
||||||
variable default_ellipsis_vertical "..." |
|
||||||
namespace eval priv { |
|
||||||
proc _init {} { |
|
||||||
upvar ::overtype::default_ellipsis_horizontal e_h |
|
||||||
upvar ::overtype::default_ellipsis_vertical e_v |
|
||||||
set e_h [format %c 0x2026] ;#Unicode Horizontal Ellipsis |
|
||||||
set e_v [format %c 0x22EE] |
|
||||||
#The unicode ellipsis looks more natural than triple-dash which is centred vertically whereas ellipsis is at floorline of text |
|
||||||
#Also - unicode ellipsis has semantic meaning that other processors can interpret |
|
||||||
#unicode does also provide a midline horizontal ellipsis 0x22EF |
|
||||||
|
|
||||||
#set e [format %c 0x2504] ;#punk::char::charshort boxd_ltdshhz - Box Drawings Light Triple Dash Horizontal |
|
||||||
#if {![catch {package require punk::char}]} { |
|
||||||
# set e [punk::char::charshort boxd_ltdshhz] |
|
||||||
#} |
|
||||||
} |
|
||||||
} |
|
||||||
priv::_init |
|
||||||
} |
|
||||||
proc overtype::about {} { |
|
||||||
return "Simple text formatting. Author JMN. BSD-License" |
|
||||||
} |
|
||||||
|
|
||||||
namespace eval overtype { |
|
||||||
variable escape_terminals |
|
||||||
#single "final byte" in the range 0x40–0x7E (ASCII @A–Z[\]^_`a–z{|}~). |
|
||||||
dict set escape_terminals CSI [list @ \\ ^ _ ` | ~ a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z "\{" "\}"] |
|
||||||
#dict set escape_terminals CSI [list J K m n A B C D E F G s u] ;#basic |
|
||||||
dict set escape_terminals OSC [list \007 \033\\] ;#note mix of 1 and 2-byte terminals |
|
||||||
|
|
||||||
#self-contained 2 byte ansi escape sequences - review more? |
|
||||||
variable ansi_2byte_codes_dict |
|
||||||
set ansi_2byte_codes_dict [dict create\ |
|
||||||
"reset_terminal" "\u001bc"\ |
|
||||||
"save_cursor_posn" "\u001b7"\ |
|
||||||
"restore_cursor_posn" "\u001b8"\ |
|
||||||
"cursor_up_one" "\u001bM"\ |
|
||||||
"NEL - Next Line" "\u001bE"\ |
|
||||||
"IND - Down one line" "\u001bD"\ |
|
||||||
"HTS - Set Tab Stop" "\u001bH"\ |
|
||||||
] |
|
||||||
|
|
||||||
#debatable whether strip should reveal the somethinghidden - some terminals don't hide it anyway. |
|
||||||
# "PM - Privacy Message" "\u001b^somethinghidden\033\\"\ |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
#proc overtype::stripansi {text} { |
|
||||||
# variable escape_terminals ;#dict |
|
||||||
# variable ansi_2byte_codes_dict |
|
||||||
# #important that we don't spend too much time on this for plain text that doesn't contain any escapes anyway |
|
||||||
# if {[string first \033 $text] <0 && [string first \009c $text] <0} { |
|
||||||
# #\033 same as \x1b |
|
||||||
# return $text |
|
||||||
# } |
|
||||||
# |
|
||||||
# set text [convert_g0 $text] |
|
||||||
# |
|
||||||
# #we process char by char - line-endings whether \r\n or \n should be processed as per any other character. |
|
||||||
# #line endings can theoretically occur within an ansi escape sequence (review e.g title?) |
|
||||||
# set inputlist [split $text ""] |
|
||||||
# set outputlist [list] |
|
||||||
# |
|
||||||
# set 2bytecodes [dict values $ansi_2byte_codes_dict] |
|
||||||
# |
|
||||||
# set in_escapesequence 0 |
|
||||||
# #assumption - undertext already 'rendered' - ie no backspaces or carriagereturns or other cursor movement controls |
|
||||||
# set i 0 |
|
||||||
# foreach u $inputlist { |
|
||||||
# set v [lindex $inputlist $i+1] |
|
||||||
# set uv ${u}${v} |
|
||||||
# if {$in_escapesequence eq "2b"} { |
|
||||||
# #2nd byte - done. |
|
||||||
# set in_escapesequence 0 |
|
||||||
# } elseif {$in_escapesequence != 0} { |
|
||||||
# set escseq [dict get $escape_terminals $in_escapesequence] |
|
||||||
# if {$u in $escseq} { |
|
||||||
# set in_escapesequence 0 |
|
||||||
# } elseif {$uv in $escseq} { |
|
||||||
# set in_escapseequence 2b ;#flag next byte as last in sequence |
|
||||||
# } |
|
||||||
# } else { |
|
||||||
# #handle both 7-bit and 8-bit CSI and OSC |
|
||||||
# if {[regexp {^(?:\033\[|\u009b)} $uv]} { |
|
||||||
# set in_escapesequence CSI |
|
||||||
# } elseif {[regexp {^(?:\033\]|\u009c)} $uv]} { |
|
||||||
# set in_escapesequence OSC |
|
||||||
# } elseif {$uv in $2bytecodes} { |
|
||||||
# #self-contained e.g terminal reset - don't pass through. |
|
||||||
# set in_escapesequence 2b |
|
||||||
# } else { |
|
||||||
# lappend outputlist $u |
|
||||||
# } |
|
||||||
# } |
|
||||||
# incr i |
|
||||||
# } |
|
||||||
# return [join $outputlist ""] |
|
||||||
#} |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
proc overtype::string_columns {text} { |
|
||||||
if {[punk::ansi::ta::detect $text]} { |
|
||||||
error "error string_columns is for calculating character length of string - ansi codes must be stripped/rendered first e.g with punk::ansi::stripansi. Alternatively try punk::ansi::printing_length" |
|
||||||
} |
|
||||||
return [punk::char::string_width $text] |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
#string range should generally be avoided for both undertext and overtext which contain ansi escapes and other cursor affecting chars such as \b and \r |
|
||||||
proc overtype::left {args} { |
|
||||||
# @c overtype starting at left (overstrike) |
|
||||||
# @c can/should we use something like this?: 'format "%-*s" $len $overtext |
|
||||||
variable default_ellipsis_horizontal |
|
||||||
|
|
||||||
if {[llength $args] < 2} { |
|
||||||
error {usage: ?-transparent [0|1]? ?-overflow [1|0]? ?-ellipsis [1|0]? ?-ellipsistext ...? undertext overtext} |
|
||||||
} |
|
||||||
lassign [lrange $args end-1 end] underblock overblock |
|
||||||
set defaults [dict create\ |
|
||||||
-ellipsis 0\ |
|
||||||
-ellipsistext $default_ellipsis_horizontal\ |
|
||||||
-overflow 0\ |
|
||||||
-transparent 0\ |
|
||||||
-exposed1 \uFFFD\ |
|
||||||
-exposed2 \uFFFD\ |
|
||||||
] |
|
||||||
set known_opts [dict keys $defaults] |
|
||||||
set argsflags [lrange $args 0 end-2] |
|
||||||
dict for {k v} $argsflags { |
|
||||||
if {$k ni $known_opts} { |
|
||||||
error "overtype::left unknown option '$k'. Known options: $known_opts" |
|
||||||
} |
|
||||||
} |
|
||||||
set opts [dict merge $defaults $argsflags] |
|
||||||
# -- --- --- --- --- --- |
|
||||||
set opt_transparent [dict get $opts -transparent] |
|
||||||
set opt_ellipsistext [dict get $opts -ellipsistext] |
|
||||||
set opt_exposed1 [dict get $opts -exposed1] |
|
||||||
set opt_exposed2 [dict get $opts -exposed2] |
|
||||||
# -- --- --- --- --- --- |
|
||||||
|
|
||||||
set norm [list \r\n \n] |
|
||||||
set underblock [string map $norm $underblock] |
|
||||||
set overblock [string map $norm $overblock] |
|
||||||
|
|
||||||
set underlines [split $underblock \n] |
|
||||||
set colwidth [tcl::mathfunc::max {*}[lmap v $underlines {punk::ansi::printing_length $v}]] |
|
||||||
set overlines [split $overblock \n] |
|
||||||
|
|
||||||
set outputlines [list] |
|
||||||
foreach undertext $underlines overtext $overlines { |
|
||||||
set undertext_printlen [punk::ansi::printing_length $undertext] |
|
||||||
set overlen [punk::ansi::printing_length $overtext] |
|
||||||
set diff [expr {$overlen - $colwidth}] |
|
||||||
|
|
||||||
#review |
|
||||||
#append overtext "\033\[0m" |
|
||||||
|
|
||||||
if {$diff > 0} { |
|
||||||
#background line is narrower |
|
||||||
set rendered [renderline -transparent $opt_transparent -exposed1 $opt_exposed1 -exposed2 $opt_exposed2 -overflow [dict get $opts -overflow] $undertext $overtext] |
|
||||||
if {![dict get $opts -overflow]} { |
|
||||||
#set overtext [string range $overtext 0 $colwidth-1] ;#string range won't be correct e.g if contains ansi codes or leading \r or \b etc |
|
||||||
if {[dict get $opts -ellipsis]} { |
|
||||||
set rendered [overtype::right $rendered $opt_ellipsistext] |
|
||||||
} |
|
||||||
} |
|
||||||
lappend outputlines $rendered |
|
||||||
} else { |
|
||||||
#we know overtext is shorter or equal |
|
||||||
lappend outputlines [renderline -transparent $opt_transparent -exposed1 $opt_exposed1 -exposed2 $opt_exposed2 $undertext $overtext] |
|
||||||
} |
|
||||||
} |
|
||||||
return [join $outputlines \n] |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
namespace eval overtype::piper { |
|
||||||
proc overcentre {args} { |
|
||||||
if {[llength $args] < 2} { |
|
||||||
error {usage: ?-bias left|right? ?-transparent [0|1|<regexp>]? ?-exposed1 <char>? ?-exposed2 <char>? ?-overflow [1|0]? overtext pipelinedata} |
|
||||||
} |
|
||||||
lassign [lrange $args end-1 end] over under |
|
||||||
set argsflags [lrange $args 0 end-2] |
|
||||||
tailcall overtype::centre {*}$argsflags $under $over |
|
||||||
} |
|
||||||
proc overleft {args} { |
|
||||||
if {[llength $args] < 2} { |
|
||||||
error {usage: ?-start <column>? ?-transparent [0|1|<regexp>]? ?-exposed1 <char>? ?-exposed2 <char>? ?-overflow [1|0]? overtext pipelinedata} |
|
||||||
} |
|
||||||
lassign [lrange $args end-1 end] over under |
|
||||||
set argsflags [lrange $args 0 end-2] |
|
||||||
tailcall overtype::left {*}$argsflags $under $over |
|
||||||
} |
|
||||||
} |
|
||||||
#todo - left-right ellipsis ? |
|
||||||
proc overtype::centre {args} { |
|
||||||
variable default_ellipsis_horizontal |
|
||||||
if {[llength $args] < 2} { |
|
||||||
error {usage: ?-transparent [0|1]? ?-bias [left|right]? ?-overflow [1|0]? undertext overtext} |
|
||||||
} |
|
||||||
|
|
||||||
foreach {underblock overblock} [lrange $args end-1 end] break |
|
||||||
|
|
||||||
set defaults [dict create\ |
|
||||||
-bias left\ |
|
||||||
-ellipsis 0\ |
|
||||||
-ellipsistext $default_ellipsis_horizontal\ |
|
||||||
-overflow 0\ |
|
||||||
-transparent 0\ |
|
||||||
-exposed1 \uFFFD\ |
|
||||||
-exposed2 \uFFFD\ |
|
||||||
] |
|
||||||
set known_opts [dict keys $defaults] |
|
||||||
set argsflags [lrange $args 0 end-2] |
|
||||||
dict for {k v} $argsflags { |
|
||||||
if {$k ni $known_opts} { |
|
||||||
error "overtype::centre unknown option '$k'. Known options: $known_opts" |
|
||||||
} |
|
||||||
} |
|
||||||
set opts [dict merge $defaults $argsflags] |
|
||||||
# -- --- --- --- --- --- |
|
||||||
set opt_transparent [dict get $opts -transparent] |
|
||||||
set opt_ellipsistext [dict get $opts -ellipsistext] |
|
||||||
set opt_exposed1 [dict get $opts -exposed1] |
|
||||||
set opt_exposed2 [dict get $opts -exposed2] |
|
||||||
# -- --- --- --- --- --- |
|
||||||
|
|
||||||
|
|
||||||
set norm [list \r\n \n] |
|
||||||
set underblock [string map $norm $underblock] |
|
||||||
set overblock [string map $norm $overblock] |
|
||||||
|
|
||||||
set underlines [split $underblock \n] |
|
||||||
set colwidth [tcl::mathfunc::max {*}[lmap v $underlines {punk::ansi::printing_length $v}]] |
|
||||||
set overlines [split $overblock \n] |
|
||||||
set overblock_width [tcl::mathfunc::max {*}[lmap v $overlines {punk::ansi::printing_length $v}]] |
|
||||||
|
|
||||||
set outputlines [list] |
|
||||||
foreach undertext $underlines overtext $overlines { |
|
||||||
#set olen [punk::ansi::printing_length $overtext] |
|
||||||
set ulen [punk::ansi::printing_length $undertext] |
|
||||||
if {$ulen < $colwidth} { |
|
||||||
set udiff [expr {$colwidth - $ulen}] |
|
||||||
set undertext "$undertext[string repeat { } $udiff]" |
|
||||||
} |
|
||||||
#review |
|
||||||
#append overtext "\033\[0m" |
|
||||||
|
|
||||||
set under_exposed [expr {$colwidth - $overblock_width}] |
|
||||||
if {$under_exposed > 0} { |
|
||||||
#background block is wider |
|
||||||
if {$under_exposed % 2 == 0} { |
|
||||||
#even left/right exposure |
|
||||||
set left_exposed [expr {$under_exposed / 2}] |
|
||||||
} else { |
|
||||||
set beforehalf [expr {$under_exposed / 2}] ;#1 less than half due to integer division |
|
||||||
if {[string tolower [dict get $opts -bias]] eq "left"} { |
|
||||||
set left_exposed $beforehalf |
|
||||||
} else { |
|
||||||
#bias to the right |
|
||||||
set left_exposed [expr {$beforehalf + 1}] |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if 0 { |
|
||||||
set rhs [expr {$diff - $half - 1}] |
|
||||||
set lhs [expr {$half - 1}] |
|
||||||
set rhsoffset [expr {$rhs +1}] |
|
||||||
set a [string range $undertext 0 $lhs] |
|
||||||
set background [string range $undertext $lhs+1 end-$rhsoffset] |
|
||||||
set b [renderline -transparent $opt_transparent $background $overtext] |
|
||||||
set c [string range $undertext end-$rhs end] |
|
||||||
lappend outputlines $a$b$c |
|
||||||
} |
|
||||||
lappend outputlines [renderline -start $left_exposed -transparent $opt_transparent -exposed1 $opt_exposed1 -exposed2 $opt_exposed2 $undertext $overtext] |
|
||||||
|
|
||||||
} else { |
|
||||||
#overlay wider or equal |
|
||||||
set rendered [renderline -transparent $opt_transparent -overflow [dict get $opts -overflow] $undertext $overtext] |
|
||||||
if {$under_exposed < 0} { |
|
||||||
#overlay is wider - trim if overflow not specified in opts |
|
||||||
if {![dict get $opts -overflow]} { |
|
||||||
#lappend outputlines [string range $overtext 0 [expr {$colwidth - 1}]] |
|
||||||
#set overtext [string range $overtext 0 $colwidth-1 ] |
|
||||||
if {[dict get $opts -ellipsis]} { |
|
||||||
set rendered [overtype::right $rendered $opt_ellipsistext] |
|
||||||
} |
|
||||||
} |
|
||||||
} else { |
|
||||||
#zero under_exposed - widths match |
|
||||||
} |
|
||||||
lappend outputlines $rendered |
|
||||||
#lappend outputlines [renderline -transparent $opt_transparent $undertext $overtext] |
|
||||||
} |
|
||||||
} |
|
||||||
return [join $outputlines \n] |
|
||||||
} |
|
||||||
|
|
||||||
proc overtype::right {args} { |
|
||||||
variable default_ellipsis_horizontal |
|
||||||
# @d !todo - implement overflow, length checks etc |
|
||||||
|
|
||||||
if {[llength $args] < 2} { |
|
||||||
error {usage: ?-overflow [1|0]? undertext overtext} |
|
||||||
} |
|
||||||
foreach {underblock overblock} [lrange $args end-1 end] break |
|
||||||
|
|
||||||
set defaults [dict create\ |
|
||||||
-bias left\ |
|
||||||
-ellipsis 0\ |
|
||||||
-ellipsistext $default_ellipsis_horizontal\ |
|
||||||
-overflow 0\ |
|
||||||
-transparent 0\ |
|
||||||
-exposed1 \uFFFD\ |
|
||||||
-exposed2 \uFFFD\ |
|
||||||
] |
|
||||||
set known_opts [dict keys $defaults] |
|
||||||
set argsflags [lrange $args 0 end-2] |
|
||||||
dict for {k v} $argsflags { |
|
||||||
if {$k ni $known_opts} { |
|
||||||
error "overtype::centre unknown option '$k'. Known options: $known_opts" |
|
||||||
} |
|
||||||
} |
|
||||||
set opts [dict merge $defaults $argsflags] |
|
||||||
# -- --- --- --- --- --- |
|
||||||
set opt_transparent [dict get $opts -transparent] |
|
||||||
set opt_ellipsis [dict get $opts -ellipsis] |
|
||||||
set opt_ellipsistext [dict get $opts -ellipsistext] |
|
||||||
set opt_overflow [dict get $opts -overflow] |
|
||||||
set opt_exposed1 [dict get $opts -exposed1] |
|
||||||
set opt_exposed2 [dict get $opts -exposed2] |
|
||||||
# -- --- --- --- --- --- |
|
||||||
|
|
||||||
set norm [list \r\n \n] |
|
||||||
set underblock [string map $norm $underblock] |
|
||||||
set overblock [string map $norm $overblock] |
|
||||||
|
|
||||||
set underlines [split $underblock \n] |
|
||||||
set colwidth [tcl::mathfunc::max {*}[lmap v $underlines {punk::ansi::printing_length $v}]] |
|
||||||
set overlines [split $overblock \n] |
|
||||||
|
|
||||||
set outputlines [list] |
|
||||||
foreach undertext $underlines overtext $overlines { |
|
||||||
set olen [punk::ansi::printing_length $overtext] |
|
||||||
set ulen [punk::ansi::printing_length $undertext] |
|
||||||
if {$ulen < $colwidth} { |
|
||||||
set udiff [expr {$colwidth - $ulen}] |
|
||||||
set undertext "$undertext[string repeat { } $udiff]" |
|
||||||
} |
|
||||||
#review |
|
||||||
#append overtext "\033\[0m" |
|
||||||
|
|
||||||
set overflowlength [expr {$olen - $colwidth}] |
|
||||||
if {$overflowlength > 0} { |
|
||||||
#overtext wider than undertext column |
|
||||||
set rendered [renderline -transparent $opt_transparent -exposed1 $opt_exposed1 -exposed2 $opt_exposed2 -overflow $opt_overflow -start 0 $undertext $overtext] |
|
||||||
if {!$opt_overflow} { |
|
||||||
if {$opt_ellipsis} { |
|
||||||
set rendered [overtype::right $rendered $opt_ellipsistext] |
|
||||||
} |
|
||||||
} |
|
||||||
lappend outputlines $rendered |
|
||||||
} else { |
|
||||||
#lappend outputlines [string range $undertext 0 end-$olen]$overtext |
|
||||||
lappend outputlines [renderline -transparent $opt_transparent -start [expr {$colwidth - $olen}] $undertext $overtext] |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return [join $outputlines \n] |
|
||||||
} |
|
||||||
|
|
||||||
# -- --- --- --- --- --- --- --- --- --- --- |
|
||||||
proc overtype::transparentline {args} { |
|
||||||
foreach {under over} [lrange $args end-1 end] break |
|
||||||
set argsflags [lrange $args 0 end-2] |
|
||||||
set defaults [dict create\ |
|
||||||
-transparent 1\ |
|
||||||
-exposed 1 " "\ |
|
||||||
-exposed 2 " "\ |
|
||||||
] |
|
||||||
set newargs [dict merge $defaults $argsflags] |
|
||||||
tailcall overtype::renderline {*}$newargs $under $over |
|
||||||
} |
|
||||||
#renderline may not make sense as it is in the long run for blocks of text - but is handy in the single-line-handling form anyway. |
|
||||||
# We are trying to handle ansi codes in a block of text which is acting like a mini-terminal in some sense. |
|
||||||
#We can process standard cursor moves such as \b \r - but no way to respond to other cursor movements e.g moving to other lines. |
|
||||||
# |
|
||||||
namespace eval overtype::piper { |
|
||||||
proc renderline {args} { |
|
||||||
if {[llength $args] < 2} { |
|
||||||
error {usage: ?-start <int>? ?-transparent [0|1|<regexp>]? ?-overflow [1|0]? overtext pipelinedata} |
|
||||||
} |
|
||||||
foreach {over under} [lrange $args end-1 end] break |
|
||||||
set argsflags [lrange $args 0 end-2] |
|
||||||
tailcall overtype::renderline {*}$argsflags $under $over |
|
||||||
} |
|
||||||
} |
|
||||||
interp alias "" piper_renderline "" overtype::piper::renderline |
|
||||||
|
|
||||||
#-returnextra to enable returning of overflow and length |
|
||||||
# todo - use punk::ansi::ta::detect to short-circuit processing and do simple string calcs as an optimisation? |
|
||||||
#review - DECSWL/DECDWL double width line codes - very difficult/impossible to align and compose with other elements |
|
||||||
#todo - review transparency issues with single/double width characters! |
|
||||||
proc overtype::renderline {args} { |
|
||||||
if {[llength $args] < 2} { |
|
||||||
error {usage: ?-start <int>? ?-transparent [0|1|<regexp>]? ?-overflow [1|0]? undertext overtext} |
|
||||||
} |
|
||||||
lassign [lrange $args end-1 end] under over |
|
||||||
if {[string first \n $under] >=0 || [string first \n $over] >= 0} { |
|
||||||
error "overtype::renderline not allowed to contain newlines" |
|
||||||
} |
|
||||||
set defaults [dict create\ |
|
||||||
-overflow 0\ |
|
||||||
-transparent 0\ |
|
||||||
-start 0\ |
|
||||||
-returnextra 0\ |
|
||||||
-exposed1 \uFFFD\ |
|
||||||
-exposed2 \uFFFD\ |
|
||||||
] |
|
||||||
#exposed1 and exposed2 for first and second col of underying 2wide char which is truncated by transparency or overflow |
|
||||||
|
|
||||||
set known_opts [dict keys $defaults] |
|
||||||
set argsflags [lrange $args 0 end-2] |
|
||||||
dict for {k v} $argsflags { |
|
||||||
if {$k ni $known_opts} { |
|
||||||
error "overtype::renderline unknown option '$k'. Known options: $known_opts" |
|
||||||
} |
|
||||||
} |
|
||||||
set opts [dict merge $defaults $argsflags] |
|
||||||
# -- --- --- --- --- --- --- --- --- --- --- --- |
|
||||||
set opt_overflow [dict get $opts -overflow] |
|
||||||
set opt_colstart [dict get $opts -start] |
|
||||||
# -- --- --- --- --- --- --- --- --- --- --- --- |
|
||||||
set opt_transparent [dict get $opts -transparent] |
|
||||||
if {$opt_transparent eq "0"} { |
|
||||||
set do_transparency 0 |
|
||||||
} else { |
|
||||||
set do_transparency 1 |
|
||||||
if {$opt_transparent eq "1"} { |
|
||||||
set opt_transparent {[\s]} |
|
||||||
} |
|
||||||
} |
|
||||||
# -- --- --- --- --- --- --- --- --- --- --- --- |
|
||||||
set opt_returnextra [dict get $opts -returnextra] |
|
||||||
# -- --- --- --- --- --- --- --- --- --- --- --- |
|
||||||
set opt_exposed1 [dict get $opts -exposed1] |
|
||||||
set opt_exposed2 [dict get $opts -exposed2] |
|
||||||
# -- --- --- --- --- --- --- --- --- --- --- --- |
|
||||||
|
|
||||||
#----- |
|
||||||
# |
|
||||||
if {[string first \t $under] >= 0} { |
|
||||||
#set under [textutil::tabify::untabify2 $under] |
|
||||||
set under [textutil::tabify::untabifyLine $under 8] ;#8 is default for untabify2 - review |
|
||||||
} |
|
||||||
set overdata $over |
|
||||||
if {[string first \t $over] >= 0} { |
|
||||||
#set overdata [textutil::tabify::untabify2 $over] |
|
||||||
set overdata [textutil::tabify::untabifyLine $over 8] |
|
||||||
} |
|
||||||
#------- |
|
||||||
|
|
||||||
#ta_detect ansi and do simpler processing? |
|
||||||
|
|
||||||
|
|
||||||
# -- --- --- --- --- --- --- --- |
|
||||||
set undermap [punk::ansi::ta::split_codes_single $under] |
|
||||||
set understacks [dict create] |
|
||||||
|
|
||||||
set i_u -1 |
|
||||||
set i_o 0 |
|
||||||
set out [list] |
|
||||||
set u_codestack [list] |
|
||||||
set pt_underchars "" ;#for string_columns length calculation for overflow 0 truncation |
|
||||||
set remainder [list] ;#for returnextra |
|
||||||
foreach {pt code} $undermap { |
|
||||||
#pt = plain text |
|
||||||
append pt_underchars $pt |
|
||||||
foreach ch [split $pt ""] { |
|
||||||
set width [punk::char::string_width $ch] |
|
||||||
incr i_u |
|
||||||
dict set understacks $i_u $u_codestack |
|
||||||
lappend out $ch |
|
||||||
if {$width > 1} { |
|
||||||
#presumably there are no triple-column or wider unicode chars.. until the aliens arrive.(?) |
|
||||||
incr i_u |
|
||||||
dict set understacks $i_u $u_codestack |
|
||||||
lappend out "" |
|
||||||
} |
|
||||||
} |
|
||||||
#underlay should already have been rendered and not have non-sgr codes - but let's check for and not stack them if other codes are here |
|
||||||
if {[priv::is_sgr $code]} { |
|
||||||
if {[priv::has_sgr_leadingreset $code]} { |
|
||||||
set u_codestack [list $code] |
|
||||||
} else { |
|
||||||
lappend u_codestack $code |
|
||||||
} |
|
||||||
} |
|
||||||
#consider also other codes that should be stacked..? |
|
||||||
} |
|
||||||
#trailing codes in effect for underlay |
|
||||||
if {[llength $undermap]} { |
|
||||||
dict set understacks [expr {$i_u + 1}] $u_codestack |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
# -- --- --- --- --- --- --- --- |
|
||||||
#### |
|
||||||
#if opt_colstart - we need to build a space (or any singlewidth char really) padding on the left of the right number of columns. |
|
||||||
#this will be processed as transparent - and handle doublewidth underlay characters appropriately |
|
||||||
set startpad [string repeat " " $opt_colstart] |
|
||||||
append startpad $overdata ;#overdata with left padding spaces based on col-start under will show through for left-padding portion regardless of -transparency |
|
||||||
set overmap [punk::ansi::ta::split_codes_single $startpad] |
|
||||||
#### |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
set overstacks [dict create] |
|
||||||
set o_codestack [list] |
|
||||||
set pt_overchars "" |
|
||||||
foreach {pt code} $overmap { |
|
||||||
append pt_overchars $pt |
|
||||||
foreach ch [split $pt ""] { |
|
||||||
dict set overstacks $i_o $o_codestack |
|
||||||
incr i_o |
|
||||||
} |
|
||||||
if {[priv::is_sgr $code]} { |
|
||||||
#only stack SGR (graphics rendition) codes - not title sets, cursor moves etc |
|
||||||
if {[priv::has_sgr_leadingreset $code]} { |
|
||||||
#m code which has sgr reset at start - no need to replay prior sgr codes |
|
||||||
set o_codestack [list $code] |
|
||||||
} else { |
|
||||||
lappend o_codestack $code |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
# -- --- --- --- --- --- --- --- |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
set bs [format %c 0x08] |
|
||||||
set idx 0 ;# line index (cursor - 1) |
|
||||||
set idx_over -1 |
|
||||||
foreach {pt code} $overmap { |
|
||||||
set ptchars [split $pt ""] ;#for lookahead |
|
||||||
#emit plaintext chars first using existing SGR codes from under/over stack as appropriate |
|
||||||
#then check if the following code is a cursor movement within the line and adjust index if so |
|
||||||
foreach ch $ptchars { |
|
||||||
incr idx_over |
|
||||||
if {$ch eq "\r"} { |
|
||||||
set idx $opt_colstart |
|
||||||
} elseif {$ch eq "\b"} { |
|
||||||
#review - backspace effect on double-width chars |
|
||||||
if {$idx > $opt_colstart} { |
|
||||||
incr idx -1 |
|
||||||
} |
|
||||||
} elseif {($idx < $opt_colstart)} { |
|
||||||
incr idx |
|
||||||
} elseif {($do_transparency && [regexp $opt_transparent $ch])} { |
|
||||||
#pre opt_colstart is effectively transparent (we have applied padding of required number of columns to left of overlay) |
|
||||||
set owidth [punk::char::string_width $ch] |
|
||||||
if {$idx > [llength $out]-1} { |
|
||||||
lappend out " " |
|
||||||
dict set understacks $idx [list] ;#review - use idx-1 codestack? |
|
||||||
incr idx |
|
||||||
} else { |
|
||||||
set uwidth [punk::char::string_width [lindex $out $idx]] |
|
||||||
if {[lindex $out $idx] eq ""} { |
|
||||||
#2nd col of 2-wide char in underlay |
|
||||||
incr idx |
|
||||||
} elseif {$uwidth == 0} { |
|
||||||
#e.g combining diacritic |
|
||||||
incr idx |
|
||||||
} elseif {$uwidth == 1} { |
|
||||||
incr idx |
|
||||||
if {$owidth > 1} { |
|
||||||
incr idx |
|
||||||
} |
|
||||||
} elseif {$uwidth > 1} { |
|
||||||
if {[punk::char::string_width $ch] == 1} { |
|
||||||
#normal singlewide transparency |
|
||||||
set next_pt_overchar [string index $pt_overchars $idx_over+1] ;#lookahead of next plain-text char in overlay |
|
||||||
if {$next_pt_overchar eq ""} { |
|
||||||
#special-case trailing transparent - no next_pt_overchar |
|
||||||
incr idx |
|
||||||
} else { |
|
||||||
if {[regexp $opt_transparent $next_pt_overchar]} { |
|
||||||
incr idx |
|
||||||
} else { |
|
||||||
#next overlay char is not transparent.. first-half of underlying 2wide char is exposed |
|
||||||
priv::render_addchar $idx $opt_exposed1 [dict get $overstacks $idx_over] |
|
||||||
incr idx |
|
||||||
} |
|
||||||
} |
|
||||||
} else { |
|
||||||
#2wide transparency over 2wide in underlay |
|
||||||
incr idx |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} else { |
|
||||||
#non-transparent char in overlay |
|
||||||
set owidth [punk::char::string_width $ch] |
|
||||||
set uwidth [punk::char::string_width [lindex $out $idx]] |
|
||||||
if {[lindex $out $idx] eq ""} { |
|
||||||
#2nd col of 2wide char in underlay |
|
||||||
priv::render_addchar $idx $ch [dict get $overstacks $idx_over] |
|
||||||
incr idx |
|
||||||
} elseif {$uwidth == 0} { |
|
||||||
#e.g combining diacritic - increment before over char REVIEW |
|
||||||
#arguably the previous overchar should have done this - ie lookahead for combiners? |
|
||||||
priv::render_addchar $idx "" [dict get $overstacks $idx_over] |
|
||||||
incr idx |
|
||||||
priv::render_addchar $idx $ch [dict get $overstacks $idx_over] |
|
||||||
incr idx |
|
||||||
|
|
||||||
} elseif {$uwidth == 1} { |
|
||||||
if {$owidth == 1} { |
|
||||||
priv::render_addchar $idx $ch [dict get $overstacks $idx_over] |
|
||||||
incr idx |
|
||||||
} else { |
|
||||||
priv::render_addchar $idx $ch [dict get $overstacks $idx_over] |
|
||||||
incr idx |
|
||||||
priv::render_addchar $idx "" [dict get $overstacks $idx_over] |
|
||||||
} |
|
||||||
} elseif {$uwidth > 1} { |
|
||||||
if {$owidth == 1} { |
|
||||||
priv::render_addchar $idx $ch [dict get $overstacks $idx_over] |
|
||||||
incr idx |
|
||||||
priv::render_addchar $idx $opt_exposed2 [dict get $overstacks $idx_over] |
|
||||||
#don't incr idx - we are just putting a broken-indication in the underlay - which may get overwritten by next overlay char |
|
||||||
} else { |
|
||||||
#2wide over 2wide |
|
||||||
priv::render_addchar $idx $ch [dict get $overstacks $idx_over] |
|
||||||
incr idx |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
#check following code |
|
||||||
if {![priv::is_sgr $code]} { |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if {$opt_overflow == 0} { |
|
||||||
#need to truncate to the width of the original undertext |
|
||||||
#review - string_width vs printing_length here. undertext requirement to be already rendered therefore punk::char::string_width ok? |
|
||||||
set num_under_columns [punk::char::string_width $pt_underchars] ;#plaintext underchars |
|
||||||
} |
|
||||||
|
|
||||||
#coalesce and replay codestacks for out char list |
|
||||||
set outstring "" |
|
||||||
set remstring "" ;#remainder after overflow point reached |
|
||||||
set i 0 |
|
||||||
set cstack [list] |
|
||||||
set prevstack [list] |
|
||||||
set out_rawchars ""; #for overflow counting |
|
||||||
set output_to "outstring" ;#var in effect depending on overflow |
|
||||||
set in_overflow 0 ;#used to stop char-width scanning once in overflow |
|
||||||
foreach ch $out { |
|
||||||
append out_rawchars $ch |
|
||||||
if {$opt_overflow == 0 && !$in_overflow} { |
|
||||||
if {[set nextvisualwidth [punk::char::string_width $out_rawchars]] < $num_under_columns} { |
|
||||||
} else { |
|
||||||
#todo - check if we overflowed with a double-width char ? |
|
||||||
#store visualwidth which may be short |
|
||||||
set in_overflow 1 |
|
||||||
} |
|
||||||
} |
|
||||||
set cstack [dict get $understacks $i] |
|
||||||
if {$cstack ne $prevstack} { |
|
||||||
if {[llength $prevstack]} { |
|
||||||
append $output_to \033\[m |
|
||||||
} |
|
||||||
foreach code $cstack { |
|
||||||
append $output_to $code |
|
||||||
} |
|
||||||
} |
|
||||||
append $output_to $ch |
|
||||||
set prevstack $cstack |
|
||||||
incr i |
|
||||||
if {$in_overflow} { |
|
||||||
set output_to "remstring" |
|
||||||
} |
|
||||||
} |
|
||||||
if {[dict size $understacks] > 0} { |
|
||||||
append $output_to [join [dict get $understacks [expr {[dict size $understacks]-1}]] ""] ;#tail codes |
|
||||||
} |
|
||||||
if {[string length $remstring]} { |
|
||||||
#puts stderr "remainder:$remstring" |
|
||||||
} |
|
||||||
#pdict $understacks |
|
||||||
if {$opt_returnextra} { |
|
||||||
return [list $outstring $visualwidth [string length $outstring] $remstring] |
|
||||||
} else { |
|
||||||
return $outstring |
|
||||||
} |
|
||||||
#return [join $out ""] |
|
||||||
} |
|
||||||
proc overtype::test_renderline {} { |
|
||||||
set t \uFF5E ;#2-wide tilde |
|
||||||
set u \uFF3F ;#2-wide underscore |
|
||||||
set missing \uFFFD |
|
||||||
return [list $t $u A${t}B] |
|
||||||
} |
|
||||||
namespace eval overtype::priv { |
|
||||||
#todo - move to punk::ansi::codetype |
|
||||||
proc is_sgr {code} { |
|
||||||
#SGR (Select Graphic Rendition) - codes ending in 'm' - e.g colour/underline |
|
||||||
#we will accept and pass through the less common colon separator (ITU Open Document Architecture) |
|
||||||
#Terminals should generally ignore it if they don't use it |
|
||||||
regexp {\033\[[0-9;:]*m$} $code |
|
||||||
} |
|
||||||
proc is_cursor_move_in_line {code} { |
|
||||||
#review - what about CSI n : m H where row n happens to be current line? |
|
||||||
regexp {\033\[[0-9]*(:?C|D|G)$} |
|
||||||
} |
|
||||||
#pure SGR reset |
|
||||||
proc is_sgr_reset {code} { |
|
||||||
#todo 8-bit csi |
|
||||||
regexp {\033\[0*m$} $code |
|
||||||
} |
|
||||||
#whether this code has 0 (or equivalently empty) parameter (but may set others) |
|
||||||
#if an SGR code as a reset in it - we don't need to carry forward any previous SGR codes |
|
||||||
#it generally only makes sense for the reset to be the first entry - otherwise the code has ineffective portions |
|
||||||
#However - detecting zero or empty parameter in other positions requires knowing all other codes that may allow zero or empty params. |
|
||||||
#We will only look at initial parameter as this is the well-formed normal case. |
|
||||||
#Review - consider normalizing sgr codes to remove other redundancies such as setting fg or bg color twice in same code |
|
||||||
proc has_sgr_leadingreset {code} { |
|
||||||
set params "" |
|
||||||
regexp {\033\[(.*)m} $code _match params |
|
||||||
set plist [split $params ";"] |
|
||||||
if {[string trim [lindex $plist 0] 0] eq ""} { |
|
||||||
#e.g \033\[m \033\[0\;...m \033\[0000...m |
|
||||||
return 1 |
|
||||||
} else { |
|
||||||
return 0 |
|
||||||
} |
|
||||||
} |
|
||||||
#has_sgr_reset - rather than support this - create an sgr normalize function that removes dead params and brings reset to front of param list |
|
||||||
proc render_addchar {i c stack} { |
|
||||||
upvar out o |
|
||||||
upvar understacks ustacks |
|
||||||
set nxt [llength $o] |
|
||||||
if {$i < $nxt} { |
|
||||||
lset o $i $c |
|
||||||
} else { |
|
||||||
lappend o $c |
|
||||||
} |
|
||||||
dict set ustacks $i $stack |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
# -- --- --- --- --- --- --- --- --- --- --- |
|
||||||
if 0 { |
|
||||||
namespace eval overtype::ta { |
|
||||||
namespace path ::overtype |
|
||||||
# *based* on but not identical to: |
|
||||||
#https://github.com/perlancar/perl-Text-ANSI-Util/blob/master/lib/Text/ANSI/BaseUtil.pm |
|
||||||
|
|
||||||
#handle both 7-bit and 8-bit csi |
|
||||||
#review - does codepage affect this? e.g ebcdic has 8bit csi in different position |
|
||||||
|
|
||||||
#CSI |
|
||||||
#variable re_csi_open {(?:\033\[|\u009b)[0-9;]+} ;#too specific - doesn't detect \033\[m |
|
||||||
variable re_csi_open {(?:\033\[|\u009b])} |
|
||||||
|
|
||||||
#colour and style |
|
||||||
variable re_csi_colour {(?:\033\[|\u009b)[0-9;]*m} ;#e.g \033\[31m \033\[m \033\[0m \033\[m0000m |
|
||||||
#single "final byte" in the range 0x40–0x7E (ASCII @A–Z[\]^_`a–z{|}~). |
|
||||||
variable re_csi_code {(?:\033\[|\u009b])[0-9;]*[a-zA-Z\\@^_|~`]} |
|
||||||
|
|
||||||
#OSC - termnate with BEL (\a \007) or ST (string terminator \033\\) |
|
||||||
#variable re_esc_osc1 {(?:\033\]|\u009c).*\007} |
|
||||||
#variable re_esc_osc2 {(?:\033\]|\u009c).*\033\\} |
|
||||||
|
|
||||||
#test - non-greedy |
|
||||||
variable re_esc_osc1 {(?:\033\]|\u009c).*?\007} |
|
||||||
variable re_esc_osc2 {(?:\033\]|\u009c).*?\033\\} |
|
||||||
|
|
||||||
variable re_ansi_detect "${re_csi_open}|${re_esc_osc1}|${re_esc_osc2}" |
|
||||||
|
|
||||||
#detect any ansi escapes |
|
||||||
#review - only detect 'complete' codes - or just use the opening escapes for performance? |
|
||||||
#proc detect {text} { |
|
||||||
# variable re_ansi_detect |
|
||||||
# #variable re_csi_open |
|
||||||
# #variable re_esc_osc1 |
|
||||||
# #variable re_esc_osc2 |
|
||||||
# #todo - other escape sequences |
|
||||||
# #expr {[regexp $re_csi_open $text] || [regexp $re_esc_osc1 $text] || [regexp $re_esc_osc2 $text]} |
|
||||||
# expr {[regexp $re_ansi_detect $text]} |
|
||||||
#} |
|
||||||
#not in perl ta |
|
||||||
#proc detect_csi {text} { |
|
||||||
# variable re_csi_colour |
|
||||||
# expr {[regexp $re_csi_colour $text]} |
|
||||||
#} |
|
||||||
proc strip {text} { |
|
||||||
tailcall punk::ansi::stripansi $text |
|
||||||
} |
|
||||||
#note this is character length after stripping ansi codes - not the printing length |
|
||||||
proc length {text} { |
|
||||||
string length [punk::ansi::stripansi $text] |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
# -- --- --- --- --- --- |
|
||||||
#Split $text to a list containing alternating ANSI color codes and text. |
|
||||||
#ANSI color codes are always on the second element, fourth, and so on. |
|
||||||
#(ie plaintext on odd list-indices ansi on even indices) |
|
||||||
# Example: |
|
||||||
#ta_split_codes "" # => "" |
|
||||||
#ta_split_codes "a" # => "a" |
|
||||||
#ta_split_codes "a\e[31m" # => {"a" "\e[31m"} |
|
||||||
#ta_split_codes "\e[31ma" # => {"" "\e[31m" "a"} |
|
||||||
#ta_split_codes "\e[31ma\e[0m" # => {"" "\e[31m" "a" "\e[0m"} |
|
||||||
#ta_split_codes "\e[31ma\e[0mb" # => {"" "\e[31m" "a" "\e[0m", "b"} |
|
||||||
#ta_split_codes "\e[31m\e[0mb" # => {"" "\e[31m\e[0m" "b"} |
|
||||||
# |
|
||||||
#proc split_codes {text} { |
|
||||||
# variable re_esc_osc1 |
|
||||||
# variable re_esc_osc2 |
|
||||||
# variable re_csi_code |
|
||||||
# set re "(?:${re_csi_code}|${re_esc_osc1}|${re_esc_osc2})+" |
|
||||||
# return [_perlish_split $re $text] |
|
||||||
#} |
|
||||||
##like split_codes - but each ansi-escape is split out separately (with empty string of plaintext between codes so odd/even plain ansi still holds) |
|
||||||
#proc split_codes_single {text} { |
|
||||||
# variable re_esc_osc1 |
|
||||||
# variable re_esc_osc2 |
|
||||||
# variable re_csi_code |
|
||||||
# set re "${re_csi_code}|${re_esc_osc1}|${re_esc_osc2}" |
|
||||||
# return [_perlish_split $re $text] |
|
||||||
#} |
|
||||||
|
|
||||||
##review - tcl greedy expressions may match multiple in one element |
|
||||||
#proc _perlish_split {re text} { |
|
||||||
# if {[string length $text] == 0} { |
|
||||||
# return {} |
|
||||||
# } |
|
||||||
# set list [list] |
|
||||||
# set start 0 |
|
||||||
# while {[regexp -start $start -indices -- $re $text match]} { |
|
||||||
# lassign $match matchStart matchEnd |
|
||||||
# lappend list [string range $text $start $matchStart-1] [string range $text $matchStart $matchEnd] |
|
||||||
# set start [expr {$matchEnd+1}] |
|
||||||
# } |
|
||||||
# lappend list [string range $text $start end] |
|
||||||
# return $list |
|
||||||
#} |
|
||||||
## -- --- --- --- --- --- |
|
||||||
|
|
||||||
} |
|
||||||
} ;# end if 0 |
|
||||||
|
|
||||||
# -- --- --- --- --- --- --- --- --- --- --- |
|
||||||
namespace eval overtype { |
|
||||||
interp alias {} ::overtype::center {} ::overtype::centre |
|
||||||
} |
|
||||||
|
|
||||||
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
||||||
## Ready |
|
||||||
package provide overtype [namespace eval overtype { |
|
||||||
variable version |
|
||||||
set version 1.5.1 |
|
||||||
}] |
|
||||||
return |
|
||||||
|
|
||||||
#*** !doctools |
|
||||||
#[manpage_end] |
|
File diff suppressed because it is too large
Load Diff
@ -1,928 +0,0 @@ |
|||||||
# -*- tcl -*- |
|
||||||
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from <pkg>-buildversion.txt |
|
||||||
# |
|
||||||
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. |
|
||||||
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. |
|
||||||
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
||||||
# (C) Julian Noble 2003-2023 |
|
||||||
# |
|
||||||
# @@ Meta Begin |
|
||||||
# Application overtype 1.5.6 |
|
||||||
# Meta platform tcl |
|
||||||
# Meta license BSD |
|
||||||
# @@ Meta End |
|
||||||
|
|
||||||
|
|
||||||
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
||||||
# doctools header |
|
||||||
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
||||||
#*** !doctools |
|
||||||
#[manpage_begin overtype_module_overtype 0 1.5.6] |
|
||||||
#[copyright "2024"] |
|
||||||
#[titledesc {overtype text layout - ansi aware}] [comment {-- Name section and table of contents description --}] |
|
||||||
#[moddesc {overtype text layout}] [comment {-- Description at end of page heading --}] |
|
||||||
#[require overtype] |
|
||||||
#[keywords module text ansi] |
|
||||||
#[description] |
|
||||||
#[para] - |
|
||||||
|
|
||||||
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
||||||
|
|
||||||
#*** !doctools |
|
||||||
#[section Overview] |
|
||||||
#[para] overview of overtype |
|
||||||
#[subsection Concepts] |
|
||||||
#[para] - |
|
||||||
|
|
||||||
|
|
||||||
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
||||||
## Requirements |
|
||||||
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
||||||
|
|
||||||
#*** !doctools |
|
||||||
#[subsection dependencies] |
|
||||||
#[para] packages used by overtype |
|
||||||
#[list_begin itemized] |
|
||||||
|
|
||||||
package require Tcl 8.6 |
|
||||||
package require textutil |
|
||||||
package require punk::lib ;#required for lines_as_list |
|
||||||
package require punk::ansi ;#required to detect, split, strip and calculate lengths |
|
||||||
package require punk::char ;#box drawing - and also unicode character width determination for proper layout of text with double-column-width chars |
|
||||||
#*** !doctools |
|
||||||
#[item] [package {Tcl 8.6}] |
|
||||||
#[item] [package textutil] |
|
||||||
#[item] [package punk::ansi] |
|
||||||
#[para] - required to detect, split, strip and calculate lengths of text possibly containing ansi codes |
|
||||||
#[item] [package punk::char] |
|
||||||
#[para] - box drawing - and also unicode character width determination for proper layout of text with double-column-width chars |
|
||||||
|
|
||||||
# #package require frobz |
|
||||||
# #*** !doctools |
|
||||||
# #[item] [package {frobz}] |
|
||||||
|
|
||||||
#*** !doctools |
|
||||||
#[list_end] |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
||||||
#*** !doctools |
|
||||||
#[section API] |
|
||||||
|
|
||||||
|
|
||||||
#Julian Noble <julian@precisium.com.au> - 2003 |
|
||||||
#Released under standard 'BSD license' conditions. |
|
||||||
# |
|
||||||
#todo - ellipsis truncation indicator for center,right |
|
||||||
|
|
||||||
#v1.4 2023-07 - naive ansi color handling - todo - fix string range |
|
||||||
# - need to extract and replace ansi codes? |
|
||||||
|
|
||||||
namespace eval overtype { |
|
||||||
namespace export * |
|
||||||
variable default_ellipsis_horizontal "..." ;#fallback |
|
||||||
variable default_ellipsis_vertical "..." |
|
||||||
namespace eval priv { |
|
||||||
proc _init {} { |
|
||||||
upvar ::overtype::default_ellipsis_horizontal e_h |
|
||||||
upvar ::overtype::default_ellipsis_vertical e_v |
|
||||||
set e_h [format %c 0x2026] ;#Unicode Horizontal Ellipsis |
|
||||||
set e_v [format %c 0x22EE] |
|
||||||
#The unicode ellipsis looks more natural than triple-dash which is centred vertically whereas ellipsis is at floorline of text |
|
||||||
#Also - unicode ellipsis has semantic meaning that other processors can interpret |
|
||||||
#unicode does also provide a midline horizontal ellipsis 0x22EF |
|
||||||
|
|
||||||
#set e [format %c 0x2504] ;#punk::char::charshort boxd_ltdshhz - Box Drawings Light Triple Dash Horizontal |
|
||||||
#if {![catch {package require punk::char}]} { |
|
||||||
# set e [punk::char::charshort boxd_ltdshhz] |
|
||||||
#} |
|
||||||
} |
|
||||||
} |
|
||||||
priv::_init |
|
||||||
} |
|
||||||
proc overtype::about {} { |
|
||||||
return "Simple text formatting. Author JMN. BSD-License" |
|
||||||
} |
|
||||||
|
|
||||||
namespace eval overtype { |
|
||||||
variable escape_terminals |
|
||||||
#single "final byte" in the range 0x40–0x7E (ASCII @A–Z[\]^_`a–z{|}~). |
|
||||||
dict set escape_terminals CSI [list @ \\ ^ _ ` | ~ a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z "\{" "\}"] |
|
||||||
#dict set escape_terminals CSI [list J K m n A B C D E F G s u] ;#basic |
|
||||||
dict set escape_terminals OSC [list \007 \033\\] ;#note mix of 1 and 2-byte terminals |
|
||||||
|
|
||||||
#self-contained 2 byte ansi escape sequences - review more? |
|
||||||
variable ansi_2byte_codes_dict |
|
||||||
set ansi_2byte_codes_dict [dict create\ |
|
||||||
"reset_terminal" "\u001bc"\ |
|
||||||
"save_cursor_posn" "\u001b7"\ |
|
||||||
"restore_cursor_posn" "\u001b8"\ |
|
||||||
"cursor_up_one" "\u001bM"\ |
|
||||||
"NEL - Next Line" "\u001bE"\ |
|
||||||
"IND - Down one line" "\u001bD"\ |
|
||||||
"HTS - Set Tab Stop" "\u001bH"\ |
|
||||||
] |
|
||||||
|
|
||||||
#debatable whether strip should reveal the somethinghidden - some terminals don't hide it anyway. |
|
||||||
# "PM - Privacy Message" "\u001b^somethinghidden\033\\"\ |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
#proc overtype::stripansi {text} { |
|
||||||
# variable escape_terminals ;#dict |
|
||||||
# variable ansi_2byte_codes_dict |
|
||||||
# #important that we don't spend too much time on this for plain text that doesn't contain any escapes anyway |
|
||||||
# if {[string first \033 $text] <0 && [string first \009c $text] <0} { |
|
||||||
# #\033 same as \x1b |
|
||||||
# return $text |
|
||||||
# } |
|
||||||
# |
|
||||||
# set text [convert_g0 $text] |
|
||||||
# |
|
||||||
# #we process char by char - line-endings whether \r\n or \n should be processed as per any other character. |
|
||||||
# #line endings can theoretically occur within an ansi escape sequence (review e.g title?) |
|
||||||
# set inputlist [split $text ""] |
|
||||||
# set outputlist [list] |
|
||||||
# |
|
||||||
# set 2bytecodes [dict values $ansi_2byte_codes_dict] |
|
||||||
# |
|
||||||
# set in_escapesequence 0 |
|
||||||
# #assumption - undertext already 'rendered' - ie no backspaces or carriagereturns or other cursor movement controls |
|
||||||
# set i 0 |
|
||||||
# foreach u $inputlist { |
|
||||||
# set v [lindex $inputlist $i+1] |
|
||||||
# set uv ${u}${v} |
|
||||||
# if {$in_escapesequence eq "2b"} { |
|
||||||
# #2nd byte - done. |
|
||||||
# set in_escapesequence 0 |
|
||||||
# } elseif {$in_escapesequence != 0} { |
|
||||||
# set escseq [dict get $escape_terminals $in_escapesequence] |
|
||||||
# if {$u in $escseq} { |
|
||||||
# set in_escapesequence 0 |
|
||||||
# } elseif {$uv in $escseq} { |
|
||||||
# set in_escapseequence 2b ;#flag next byte as last in sequence |
|
||||||
# } |
|
||||||
# } else { |
|
||||||
# #handle both 7-bit and 8-bit CSI and OSC |
|
||||||
# if {[regexp {^(?:\033\[|\u009b)} $uv]} { |
|
||||||
# set in_escapesequence CSI |
|
||||||
# } elseif {[regexp {^(?:\033\]|\u009c)} $uv]} { |
|
||||||
# set in_escapesequence OSC |
|
||||||
# } elseif {$uv in $2bytecodes} { |
|
||||||
# #self-contained e.g terminal reset - don't pass through. |
|
||||||
# set in_escapesequence 2b |
|
||||||
# } else { |
|
||||||
# lappend outputlist $u |
|
||||||
# } |
|
||||||
# } |
|
||||||
# incr i |
|
||||||
# } |
|
||||||
# return [join $outputlist ""] |
|
||||||
#} |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
proc overtype::string_columns {text} { |
|
||||||
if {[punk::ansi::ta::detect $text]} { |
|
||||||
#error "error string_columns is for calculating character length of string - ansi codes must be stripped/rendered first e.g with punk::ansi::stripansi. Alternatively try punk::ansi::printing_length" |
|
||||||
set text [punk::ansi::stripansi $text] |
|
||||||
} |
|
||||||
return [punk::char::string_width $text] |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
#string range should generally be avoided for both undertext and overtext which contain ansi escapes and other cursor affecting chars such as \b and \r |
|
||||||
proc overtype::left {args} { |
|
||||||
# @c overtype starting at left (overstrike) |
|
||||||
# @c can/should we use something like this?: 'format "%-*s" $len $overtext |
|
||||||
variable default_ellipsis_horizontal |
|
||||||
|
|
||||||
if {[llength $args] < 2} { |
|
||||||
error {usage: ?-transparent [0|1]? ?-overflow [1|0]? ?-ellipsis [1|0]? ?-ellipsistext ...? undertext overtext} |
|
||||||
} |
|
||||||
lassign [lrange $args end-1 end] underblock overblock |
|
||||||
set defaults [dict create\ |
|
||||||
-bias ignored\ |
|
||||||
-ellipsis 0\ |
|
||||||
-ellipsistext $default_ellipsis_horizontal\ |
|
||||||
-ellipsiswhitespace 0\ |
|
||||||
-overflow 0\ |
|
||||||
-transparent 0\ |
|
||||||
-exposed1 \uFFFD\ |
|
||||||
-exposed2 \uFFFD\ |
|
||||||
] |
|
||||||
set known_opts [dict keys $defaults] |
|
||||||
set argsflags [lrange $args 0 end-2] |
|
||||||
dict for {k v} $argsflags { |
|
||||||
if {$k ni $known_opts} { |
|
||||||
error "overtype::left unknown option '$k'. Known options: $known_opts" |
|
||||||
} |
|
||||||
} |
|
||||||
set opts [dict merge $defaults $argsflags] |
|
||||||
# -- --- --- --- --- --- |
|
||||||
set opt_transparent [dict get $opts -transparent] |
|
||||||
set opt_ellipsistext [dict get $opts -ellipsistext] |
|
||||||
set opt_ellipsiswhitespace [dict get $opts -ellipsiswhitespace] |
|
||||||
set opt_exposed1 [dict get $opts -exposed1] ;#widechar_exposed_left - todo |
|
||||||
set opt_exposed2 [dict get $opts -exposed2] ;#widechar_exposed_right - todo |
|
||||||
# -- --- --- --- --- --- |
|
||||||
|
|
||||||
set norm [list \r\n \n] |
|
||||||
set underblock [string map $norm $underblock] |
|
||||||
set overblock [string map $norm $overblock] |
|
||||||
|
|
||||||
set underlines [split $underblock \n] |
|
||||||
#set colwidth [tcl::mathfunc::max {*}[lmap v $underlines {punk::ansi::printing_length $v}]] |
|
||||||
lassign [blocksize $underblock] _w colwidth _h colheight |
|
||||||
set overlines [split $overblock \n] |
|
||||||
#set overblock_width [tcl::mathfunc::max {*}[lmap v $overlines {punk::ansi::printing_length $v}]] |
|
||||||
lassign [blocksize $overblock] _w overblock_width _h overblock_height |
|
||||||
set under_exposed_max [expr {$colwidth - $overblock_width}] |
|
||||||
set right_exposed $under_exposed_max |
|
||||||
|
|
||||||
set outputlines [list] |
|
||||||
foreach undertext $underlines overtext $overlines { |
|
||||||
set undertext_printlen [punk::ansi::printing_length $undertext] |
|
||||||
if {$undertext_printlen < $colwidth} { |
|
||||||
set udiff [expr {$colwidth - $undertext_printlen}] |
|
||||||
set undertext "$undertext[string repeat { } $udiff]" |
|
||||||
} |
|
||||||
set overtext_printlen [punk::ansi::printing_length $overtext] |
|
||||||
set overflowlength [expr {$overtext_printlen - $colwidth}] |
|
||||||
|
|
||||||
#review |
|
||||||
#append overtext "\033\[0m" |
|
||||||
|
|
||||||
|
|
||||||
if {$overflowlength > 0} { |
|
||||||
#background line is narrower than data in line |
|
||||||
set rendered [renderline -transparent $opt_transparent -exposed1 $opt_exposed1 -exposed2 $opt_exposed2 -overflow [dict get $opts -overflow] $undertext $overtext] |
|
||||||
if {![dict get $opts -overflow]} { |
|
||||||
#set overtext [string range $overtext 0 $colwidth-1] ;#string range won't be correct e.g if contains ansi codes or leading \r or \b etc |
|
||||||
if {[dict get $opts -ellipsis]} { |
|
||||||
set show_ellipsis 1 |
|
||||||
if {!$opt_ellipsiswhitespace} { |
|
||||||
#we don't want ellipsis if only whitespace was lost |
|
||||||
set lostdata [string range $overtext end-[expr {$overflowlength-1}] end] |
|
||||||
if {[string trim $lostdata] eq ""} { |
|
||||||
set show_ellipsis 0 |
|
||||||
} |
|
||||||
} |
|
||||||
if {$show_ellipsis} { |
|
||||||
set rendered [overtype::right $rendered $opt_ellipsistext] |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
lappend outputlines $rendered |
|
||||||
} else { |
|
||||||
#we know overtext data is shorter or equal (for this line) |
|
||||||
lappend outputlines [renderline -transparent $opt_transparent -exposed1 $opt_exposed1 -exposed2 $opt_exposed2 $undertext $overtext] |
|
||||||
} |
|
||||||
} |
|
||||||
return [join $outputlines \n] |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
namespace eval overtype::piper { |
|
||||||
proc overcentre {args} { |
|
||||||
if {[llength $args] < 2} { |
|
||||||
error {usage: ?-bias left|right? ?-transparent [0|1|<regexp>]? ?-exposed1 <char>? ?-exposed2 <char>? ?-overflow [1|0]? overtext pipelinedata} |
|
||||||
} |
|
||||||
lassign [lrange $args end-1 end] over under |
|
||||||
set argsflags [lrange $args 0 end-2] |
|
||||||
tailcall overtype::centre {*}$argsflags $under $over |
|
||||||
} |
|
||||||
proc overleft {args} { |
|
||||||
if {[llength $args] < 2} { |
|
||||||
error {usage: ?-start <column>? ?-transparent [0|1|<regexp>]? ?-exposed1 <char>? ?-exposed2 <char>? ?-overflow [1|0]? overtext pipelinedata} |
|
||||||
} |
|
||||||
lassign [lrange $args end-1 end] over under |
|
||||||
set argsflags [lrange $args 0 end-2] |
|
||||||
tailcall overtype::left {*}$argsflags $under $over |
|
||||||
} |
|
||||||
} |
|
||||||
#todo - left-right ellipsis ? |
|
||||||
proc overtype::centre {args} { |
|
||||||
variable default_ellipsis_horizontal |
|
||||||
if {[llength $args] < 2} { |
|
||||||
error {usage: ?-transparent [0|1]? ?-bias [left|right]? ?-overflow [1|0]? undertext overtext} |
|
||||||
} |
|
||||||
|
|
||||||
foreach {underblock overblock} [lrange $args end-1 end] break |
|
||||||
|
|
||||||
#todo - vertical vs horizontal overflow for blocks |
|
||||||
set defaults [dict create\ |
|
||||||
-bias left\ |
|
||||||
-ellipsis 0\ |
|
||||||
-ellipsistext $default_ellipsis_horizontal\ |
|
||||||
-ellipsiswhitespace 0\ |
|
||||||
-overflow 0\ |
|
||||||
-transparent 0\ |
|
||||||
-exposed1 \uFFFD\ |
|
||||||
-exposed2 \uFFFD\ |
|
||||||
] |
|
||||||
set known_opts [dict keys $defaults] |
|
||||||
set argsflags [lrange $args 0 end-2] |
|
||||||
dict for {k v} $argsflags { |
|
||||||
if {$k ni $known_opts} { |
|
||||||
error "overtype::centre unknown option '$k'. Known options: $known_opts" |
|
||||||
} |
|
||||||
} |
|
||||||
set opts [dict merge $defaults $argsflags] |
|
||||||
# -- --- --- --- --- --- |
|
||||||
set opt_transparent [dict get $opts -transparent] |
|
||||||
set opt_ellipsis [dict get $opts -ellipsis] |
|
||||||
set opt_ellipsistext [dict get $opts -ellipsistext] |
|
||||||
set opt_ellipsiswhitespace [dict get $opts -ellipsiswhitespace] |
|
||||||
set opt_exposed1 [dict get $opts -exposed1] |
|
||||||
set opt_exposed2 [dict get $opts -exposed2] |
|
||||||
# -- --- --- --- --- --- |
|
||||||
|
|
||||||
|
|
||||||
set norm [list \r\n \n] |
|
||||||
set underblock [string map $norm $underblock] |
|
||||||
set overblock [string map $norm $overblock] |
|
||||||
|
|
||||||
set underlines [split $underblock \n] |
|
||||||
#set colwidth [tcl::mathfunc::max {*}[lmap v $underlines {punk::ansi::printing_length $v}]] |
|
||||||
lassign [blocksize $underblock] _w colwidth _h colheight |
|
||||||
set overlines [split $overblock \n] |
|
||||||
#set overblock_width [tcl::mathfunc::max {*}[lmap v $overlines {punk::ansi::printing_length $v}]] |
|
||||||
lassign [blocksize $overblock] _w overblock_width _h overblock_height |
|
||||||
set under_exposed_max [expr {$colwidth - $overblock_width}] |
|
||||||
if {$under_exposed_max > 0} { |
|
||||||
#background block is wider |
|
||||||
if {$under_exposed_max % 2 == 0} { |
|
||||||
#even left/right exposure |
|
||||||
set left_exposed [expr {$under_exposed_max / 2}] |
|
||||||
} else { |
|
||||||
set beforehalf [expr {$under_exposed_max / 2}] ;#1 less than half due to integer division |
|
||||||
if {[string tolower [dict get $opts -bias]] eq "left"} { |
|
||||||
set left_exposed $beforehalf |
|
||||||
} else { |
|
||||||
#bias to the right |
|
||||||
set left_exposed [expr {$beforehalf + 1}] |
|
||||||
} |
|
||||||
} |
|
||||||
} else { |
|
||||||
set left_exposed 0 |
|
||||||
} |
|
||||||
|
|
||||||
set outputlines [list] |
|
||||||
foreach undertext $underlines overtext $overlines { |
|
||||||
set overtext_datalen [punk::ansi::printing_length $overtext] |
|
||||||
set ulen [punk::ansi::printing_length $undertext] |
|
||||||
if {$ulen < $colwidth} { |
|
||||||
set udiff [expr {$colwidth - $ulen}] |
|
||||||
set undertext "$undertext[string repeat { } $udiff]" |
|
||||||
} |
|
||||||
|
|
||||||
set overflowlength [expr {$overtext_datalen - $colwidth}] |
|
||||||
#review - right-to-left langs should elide on left! - extra option required |
|
||||||
|
|
||||||
if {$overflowlength > 0} { |
|
||||||
#overlay line wider or equal |
|
||||||
set rendered [renderline -transparent $opt_transparent -overflow [dict get $opts -overflow] -exposed1 $opt_exposed1 -exposed2 $opt_exposed2 $undertext $overtext] |
|
||||||
#overlay line data is wider - trim if overflow not specified in opts - and overtype an ellipsis at right if it was specified |
|
||||||
if {![dict get $opts -overflow]} { |
|
||||||
#lappend outputlines [string range $overtext 0 [expr {$colwidth - 1}]] |
|
||||||
#set overtext [string range $overtext 0 $colwidth-1 ] |
|
||||||
if {$opt_ellipsis} { |
|
||||||
set show_ellipsis 1 |
|
||||||
if {!$opt_ellipsiswhitespace} { |
|
||||||
#we don't want ellipsis if only whitespace was lost |
|
||||||
set lostdata [string range $overtext end-[expr {$overflowlength-1}] end] |
|
||||||
if {[string trim $lostdata] eq ""} { |
|
||||||
set show_ellipsis 0 |
|
||||||
} |
|
||||||
} |
|
||||||
if {$show_ellipsis} { |
|
||||||
set rendered [overtype::right $rendered $opt_ellipsistext] |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
lappend outputlines $rendered |
|
||||||
#lappend outputlines [renderline -transparent $opt_transparent $undertext $overtext] |
|
||||||
} else { |
|
||||||
#background block is wider than or equal to data for this line |
|
||||||
lappend outputlines [renderline -start $left_exposed -transparent $opt_transparent -exposed1 $opt_exposed1 -exposed2 $opt_exposed2 $undertext $overtext] |
|
||||||
} |
|
||||||
} |
|
||||||
return [join $outputlines \n] |
|
||||||
} |
|
||||||
|
|
||||||
proc overtype::right {args} { |
|
||||||
#NOT the same as align-right - which should be done to the overblock first if required |
|
||||||
variable default_ellipsis_horizontal |
|
||||||
# @d !todo - implement overflow, length checks etc |
|
||||||
|
|
||||||
if {[llength $args] < 2} { |
|
||||||
error {usage: ?-overflow [1|0]? undertext overtext} |
|
||||||
} |
|
||||||
foreach {underblock overblock} [lrange $args end-1 end] break |
|
||||||
|
|
||||||
set defaults [dict create\ |
|
||||||
-bias ignored\ |
|
||||||
-ellipsis 0\ |
|
||||||
-ellipsistext $default_ellipsis_horizontal\ |
|
||||||
-ellipsiswhitespace 0\ |
|
||||||
-overflow 0\ |
|
||||||
-transparent 0\ |
|
||||||
-exposed1 \uFFFD\ |
|
||||||
-exposed2 \uFFFD\ |
|
||||||
] |
|
||||||
set known_opts [dict keys $defaults] |
|
||||||
set argsflags [lrange $args 0 end-2] |
|
||||||
dict for {k v} $argsflags { |
|
||||||
if {$k ni $known_opts} { |
|
||||||
error "overtype::centre unknown option '$k'. Known options: $known_opts" |
|
||||||
} |
|
||||||
} |
|
||||||
set opts [dict merge $defaults $argsflags] |
|
||||||
# -- --- --- --- --- --- |
|
||||||
set opt_transparent [dict get $opts -transparent] |
|
||||||
set opt_ellipsis [dict get $opts -ellipsis] |
|
||||||
set opt_ellipsistext [dict get $opts -ellipsistext] |
|
||||||
set opt_ellipsiswhitespace [dict get $opts -ellipsiswhitespace] |
|
||||||
set opt_overflow [dict get $opts -overflow] |
|
||||||
set opt_exposed1 [dict get $opts -exposed1] |
|
||||||
set opt_exposed2 [dict get $opts -exposed2] |
|
||||||
# -- --- --- --- --- --- |
|
||||||
|
|
||||||
set norm [list \r\n \n] |
|
||||||
set underblock [string map $norm $underblock] |
|
||||||
set overblock [string map $norm $overblock] |
|
||||||
|
|
||||||
set underlines [split $underblock \n] |
|
||||||
#set colwidth [tcl::mathfunc::max {*}[lmap v $underlines {punk::ansi::printing_length $v}]] |
|
||||||
lassign [blocksize $underblock] _w colwidth _h colheight |
|
||||||
set overlines [split $overblock \n] |
|
||||||
#set overblock_width [tcl::mathfunc::max {*}[lmap v $overlines {punk::ansi::printing_length $v}]] |
|
||||||
lassign [blocksize $overblock] _w overblock_width _h overblock_height |
|
||||||
set under_exposed_max [expr {$colwidth - $overblock_width}] |
|
||||||
set left_exposed $under_exposed_max |
|
||||||
|
|
||||||
set outputlines [list] |
|
||||||
foreach undertext $underlines overtext $overlines { |
|
||||||
set overtext_datalen [punk::ansi::printing_length $overtext] |
|
||||||
set ulen [punk::ansi::printing_length $undertext] |
|
||||||
if {$ulen < $colwidth} { |
|
||||||
set udiff [expr {$colwidth - $ulen}] |
|
||||||
puts xxx |
|
||||||
set undertext "$undertext[string repeat { } $udiff]" |
|
||||||
} |
|
||||||
if {$overtext_datalen < $overblock_width} { |
|
||||||
set odiff [expr {$overblock_width - $overtext_datalen}] |
|
||||||
#padding always on right - if alignment is required it should be done to block beforehand - not here |
|
||||||
set overtextpadding "$overtext[string repeat { } $odiff]" |
|
||||||
} |
|
||||||
|
|
||||||
set overflowlength [expr {$overtext_datalen - $colwidth}] |
|
||||||
if {$overflowlength > 0} { |
|
||||||
#raw overtext wider than undertext column |
|
||||||
set rendered [renderline -transparent $opt_transparent -exposed1 $opt_exposed1 -exposed2 $opt_exposed2 -overflow $opt_overflow -start 0 $undertext $overtext] |
|
||||||
if {!$opt_overflow} { |
|
||||||
if {$opt_ellipsis} { |
|
||||||
set show_ellipsis 1 |
|
||||||
if {!$opt_ellipsiswhitespace} { |
|
||||||
#we don't want ellipsis if only whitespace was lost |
|
||||||
set lostdata [string range $overtext end-[expr {$overflowlength-1}] end] |
|
||||||
if {[string trim $lostdata] eq ""} { |
|
||||||
set show_ellipsis 0 |
|
||||||
} |
|
||||||
} |
|
||||||
if {$show_ellipsis} { |
|
||||||
set rendered [overtype::right $rendered $opt_ellipsistext] |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
lappend outputlines $rendered |
|
||||||
} else { |
|
||||||
#padded overtext |
|
||||||
lappend outputlines [renderline -transparent $opt_transparent -start $left_exposed $undertext $overtext] |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return [join $outputlines \n] |
|
||||||
} |
|
||||||
|
|
||||||
# -- --- --- --- --- --- --- --- --- --- --- |
|
||||||
proc overtype::transparentline {args} { |
|
||||||
foreach {under over} [lrange $args end-1 end] break |
|
||||||
set argsflags [lrange $args 0 end-2] |
|
||||||
set defaults [dict create\ |
|
||||||
-transparent 1\ |
|
||||||
-exposed 1 " "\ |
|
||||||
-exposed 2 " "\ |
|
||||||
] |
|
||||||
set newargs [dict merge $defaults $argsflags] |
|
||||||
tailcall overtype::renderline {*}$newargs $under $over |
|
||||||
} |
|
||||||
#renderline may not make sense as it is in the long run for blocks of text - but is handy in the single-line-handling form anyway. |
|
||||||
# We are trying to handle ansi codes in a block of text which is acting like a mini-terminal in some sense. |
|
||||||
#We can process standard cursor moves such as \b \r - but no way to respond to other cursor movements e.g moving to other lines. |
|
||||||
# |
|
||||||
namespace eval overtype::piper { |
|
||||||
proc renderline {args} { |
|
||||||
if {[llength $args] < 2} { |
|
||||||
error {usage: ?-start <int>? ?-transparent [0|1|<regexp>]? ?-overflow [1|0]? overtext pipelinedata} |
|
||||||
} |
|
||||||
foreach {over under} [lrange $args end-1 end] break |
|
||||||
set argsflags [lrange $args 0 end-2] |
|
||||||
tailcall overtype::renderline {*}$argsflags $under $over |
|
||||||
} |
|
||||||
} |
|
||||||
interp alias "" piper_renderline "" overtype::piper::renderline |
|
||||||
|
|
||||||
#-returnextra to enable returning of overflow and length |
|
||||||
# todo - use punk::ansi::ta::detect to short-circuit processing and do simple string calcs as an optimisation? |
|
||||||
#review - DECSWL/DECDWL double width line codes - very difficult/impossible to align and compose with other elements |
|
||||||
#todo - review transparency issues with single/double width characters! |
|
||||||
proc overtype::renderline {args} { |
|
||||||
if {[llength $args] < 2} { |
|
||||||
error {usage: ?-start <int>? ?-transparent [0|1|<regexp>]? ?-overflow [1|0]? undertext overtext} |
|
||||||
} |
|
||||||
lassign [lrange $args end-1 end] under over |
|
||||||
#should also rule out \v |
|
||||||
if {[string first \n $over] >=0 || [string first \n $under] >= 0} { |
|
||||||
error "overtype::renderline not allowed to contain newlines" |
|
||||||
} |
|
||||||
set defaults [dict create\ |
|
||||||
-overflow 0\ |
|
||||||
-transparent 0\ |
|
||||||
-start 0\ |
|
||||||
-returnextra 0\ |
|
||||||
-exposed1 \uFFFD\ |
|
||||||
-exposed2 \uFFFD\ |
|
||||||
] |
|
||||||
#exposed1 and exposed2 for first and second col of underying 2wide char which is truncated by transparency or overflow |
|
||||||
|
|
||||||
set known_opts [dict keys $defaults] |
|
||||||
set argsflags [lrange $args 0 end-2] |
|
||||||
dict for {k v} $argsflags { |
|
||||||
if {$k ni $known_opts} { |
|
||||||
error "overtype::renderline unknown option '$k'. Known options: $known_opts" |
|
||||||
} |
|
||||||
} |
|
||||||
set opts [dict merge $defaults $argsflags] |
|
||||||
# -- --- --- --- --- --- --- --- --- --- --- --- |
|
||||||
set opt_overflow [dict get $opts -overflow] |
|
||||||
set opt_colstart [dict get $opts -start] |
|
||||||
# -- --- --- --- --- --- --- --- --- --- --- --- |
|
||||||
set opt_transparent [dict get $opts -transparent] |
|
||||||
if {$opt_transparent eq "0"} { |
|
||||||
set do_transparency 0 |
|
||||||
} else { |
|
||||||
set do_transparency 1 |
|
||||||
if {$opt_transparent eq "1"} { |
|
||||||
set opt_transparent {[\s]} |
|
||||||
} |
|
||||||
} |
|
||||||
# -- --- --- --- --- --- --- --- --- --- --- --- |
|
||||||
set opt_returnextra [dict get $opts -returnextra] |
|
||||||
# -- --- --- --- --- --- --- --- --- --- --- --- |
|
||||||
set opt_exposed1 [dict get $opts -exposed1] |
|
||||||
set opt_exposed2 [dict get $opts -exposed2] |
|
||||||
# -- --- --- --- --- --- --- --- --- --- --- --- |
|
||||||
|
|
||||||
#----- |
|
||||||
# |
|
||||||
if {[string first \t $under] >= 0} { |
|
||||||
#set under [textutil::tabify::untabify2 $under] |
|
||||||
set under [textutil::tabify::untabifyLine $under 8] ;#8 is default for untabify2 - review |
|
||||||
} |
|
||||||
set overdata $over |
|
||||||
if {[string first \t $over] >= 0} { |
|
||||||
#set overdata [textutil::tabify::untabify2 $over] |
|
||||||
set overdata [textutil::tabify::untabifyLine $over 8] |
|
||||||
} |
|
||||||
#------- |
|
||||||
|
|
||||||
#ta_detect ansi and do simpler processing? |
|
||||||
|
|
||||||
|
|
||||||
# -- --- --- --- --- --- --- --- |
|
||||||
set undermap [punk::ansi::ta::split_codes_single $under] |
|
||||||
set understacks [dict create] |
|
||||||
|
|
||||||
set i_u -1 |
|
||||||
set i_o 0 |
|
||||||
set out [list] |
|
||||||
set u_codestack [list] |
|
||||||
set pt_underchars "" ;#for string_columns length calculation for overflow 0 truncation |
|
||||||
set remainder [list] ;#for returnextra |
|
||||||
foreach {pt code} $undermap { |
|
||||||
#pt = plain text |
|
||||||
append pt_underchars $pt |
|
||||||
foreach grapheme [punk::char::grapheme_split $pt] { |
|
||||||
set width [punk::char::string_width $grapheme] |
|
||||||
incr i_u |
|
||||||
dict set understacks $i_u $u_codestack |
|
||||||
lappend out $grapheme |
|
||||||
if {$width > 1} { |
|
||||||
incr i_u |
|
||||||
#presumably there are no triple-column or wider unicode chars.. until the aliens arrive.(?) |
|
||||||
#but what about emoji combinations etc - can they be wider than 2? |
|
||||||
dict set understacks $i_u $u_codestack |
|
||||||
lappend out "" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
#underlay should already have been rendered and not have non-sgr codes - but let's retain the check for them and not stack them if other codes are here |
|
||||||
|
|
||||||
#only stack SGR (graphics rendition) codes - not title sets, cursor moves etc |
|
||||||
#order of if-else based on assumptions: |
|
||||||
# that pure resets are fairly common - more so than leading resets with other info |
|
||||||
# that non-sgr codes are not that common, so ok to check for resets before verifying it is actually SGR at all. |
|
||||||
if {[punk::ansi::codetype::is_sgr_reset $code]} { |
|
||||||
set u_codestack [list] |
|
||||||
} elseif {[punk::ansi::codetype::has_sgr_leadingreset $code]} { |
|
||||||
set u_codestack [list $code] |
|
||||||
} elseif {[punk::ansi::codetype::is_sgr $code]} { |
|
||||||
lappend u_codestack $code |
|
||||||
} |
|
||||||
#consider also if there are other codes that should be stacked..? |
|
||||||
} |
|
||||||
#trailing codes in effect for underlay |
|
||||||
if {[llength $undermap]} { |
|
||||||
dict set understacks [expr {$i_u + 1}] $u_codestack |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
# -- --- --- --- --- --- --- --- |
|
||||||
#### |
|
||||||
#if opt_colstart - we need to build a space (or any singlewidth char really) padding on the left of the right number of columns. |
|
||||||
#this will be processed as transparent - and handle doublewidth underlay characters appropriately |
|
||||||
set startpad [string repeat " " $opt_colstart] |
|
||||||
append startpad $overdata ;#overdata with left padding spaces based on col-start under will show through for left-padding portion regardless of -transparency |
|
||||||
set overmap [punk::ansi::ta::split_codes_single $startpad] |
|
||||||
#### |
|
||||||
|
|
||||||
#set re_diacritics {[\u0300-\u036f]+|[\u1ab0-\u1aff]+|[\u1dc0-\u1dff]+|[\u20d0-\u20ff]+|[\ufe20-\ufe2f]+} |
|
||||||
#as at 2024-02 punk::char::grapheme_split uses these - not aware of more complex graphemes |
|
||||||
|
|
||||||
|
|
||||||
set overstacks [dict create] |
|
||||||
set o_codestack [list] |
|
||||||
set pt_overchars "" |
|
||||||
foreach {pt code} $overmap { |
|
||||||
append pt_overchars $pt |
|
||||||
foreach grapheme [punk::char::grapheme_split $pt] { |
|
||||||
dict set overstacks $i_o $o_codestack |
|
||||||
incr i_o |
|
||||||
} |
|
||||||
|
|
||||||
if {[punk::ansi::codetype::is_sgr $code]} { |
|
||||||
if {[punk::ansi::codetype::has_sgr_leadingreset $code]} { |
|
||||||
#m code which has sgr reset at start - no need to replay prior sgr codes |
|
||||||
set o_codestack [list $code] |
|
||||||
} else { |
|
||||||
lappend o_codestack $code |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
#only stack SGR (graphics rendition) codes - not title sets, cursor moves etc |
|
||||||
#order of if-else based on assumptions: |
|
||||||
# that pure resets are fairly common - more so than leading resets with other info |
|
||||||
# that non-sgr codes are not that common, so ok to check for resets before verifying it is actually SGR at all. |
|
||||||
if {[punk::ansi::codetype::is_sgr_reset $code]} { |
|
||||||
set o_codestack [list] |
|
||||||
} elseif {[punk::ansi::codetype::has_sgr_leadingreset $code]} { |
|
||||||
set o_codestack [list $code] |
|
||||||
} elseif {[punk::ansi::codetype::is_sgr $code]} { |
|
||||||
lappend o_codestack $code |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
# -- --- --- --- --- --- --- --- |
|
||||||
|
|
||||||
|
|
||||||
#potential problem - combinining diacritics directly following control chars like \r \b |
|
||||||
|
|
||||||
set bs [format %c 0x08] |
|
||||||
set idx 0 ;# line index (cursor - 1) |
|
||||||
set idx_over -1 |
|
||||||
foreach {pt code} $overmap { |
|
||||||
#set ptchars [split $pt ""] ;#for lookahead |
|
||||||
set graphemes [punk::char::grapheme_split $pt] |
|
||||||
#emit plaintext chars first using existing SGR codes from under/over stack as appropriate |
|
||||||
#then check if the following code is a cursor movement within the line and adjust index if so |
|
||||||
foreach ch $graphemes { |
|
||||||
incr idx_over |
|
||||||
if {$ch eq "\r"} { |
|
||||||
set idx $opt_colstart |
|
||||||
} elseif {$ch eq "\b"} { |
|
||||||
#review - backspace effect on double-width chars |
|
||||||
if {$idx > $opt_colstart} { |
|
||||||
incr idx -1 |
|
||||||
} |
|
||||||
} elseif {($idx < $opt_colstart)} { |
|
||||||
incr idx |
|
||||||
} elseif {($do_transparency && [regexp $opt_transparent $ch])} { |
|
||||||
#pre opt_colstart is effectively transparent (we have applied padding of required number of columns to left of overlay) |
|
||||||
set owidth [punk::char::string_width $ch] |
|
||||||
if {$idx > [llength $out]-1} { |
|
||||||
lappend out " " |
|
||||||
dict set understacks $idx [list] ;#review - use idx-1 codestack? |
|
||||||
incr idx |
|
||||||
} else { |
|
||||||
set uwidth [punk::char::string_width [lindex $out $idx]] |
|
||||||
if {[lindex $out $idx] eq ""} { |
|
||||||
#2nd col of 2-wide char in underlay |
|
||||||
incr idx |
|
||||||
} elseif {$uwidth == 0} { |
|
||||||
#e.g control char ? combining diacritic ? |
|
||||||
incr idx |
|
||||||
} elseif {$uwidth == 1} { |
|
||||||
incr idx |
|
||||||
if {$owidth > 1} { |
|
||||||
incr idx |
|
||||||
} |
|
||||||
} elseif {$uwidth > 1} { |
|
||||||
if {[punk::char::string_width $ch] == 1} { |
|
||||||
#normal singlewide transparency |
|
||||||
set next_pt_overchar [string index $pt_overchars $idx_over+1] ;#lookahead of next plain-text char in overlay |
|
||||||
if {$next_pt_overchar eq ""} { |
|
||||||
#special-case trailing transparent - no next_pt_overchar |
|
||||||
incr idx |
|
||||||
} else { |
|
||||||
if {[regexp $opt_transparent $next_pt_overchar]} { |
|
||||||
incr idx |
|
||||||
} else { |
|
||||||
#next overlay char is not transparent.. first-half of underlying 2wide char is exposed |
|
||||||
priv::render_addchar $idx $opt_exposed1 [dict get $overstacks $idx_over] |
|
||||||
incr idx |
|
||||||
} |
|
||||||
} |
|
||||||
} else { |
|
||||||
#2wide transparency over 2wide in underlay |
|
||||||
incr idx |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} else { |
|
||||||
#non-transparent char in overlay |
|
||||||
set owidth [punk::char::string_width $ch] |
|
||||||
set uwidth [punk::char::string_width [lindex $out $idx]] |
|
||||||
if {[lindex $out $idx] eq ""} { |
|
||||||
#2nd col of 2wide char in underlay |
|
||||||
priv::render_addchar $idx $ch [dict get $overstacks $idx_over] |
|
||||||
incr idx |
|
||||||
} elseif {$uwidth == 0} { |
|
||||||
#e.g combining diacritic - increment before over char REVIEW |
|
||||||
#arguably the previous overchar should have done this - ie lookahead for combiners? |
|
||||||
priv::render_addchar $idx "" [dict get $overstacks $idx_over] |
|
||||||
incr idx |
|
||||||
priv::render_addchar $idx $ch [dict get $overstacks $idx_over] |
|
||||||
incr idx |
|
||||||
|
|
||||||
} elseif {$uwidth == 1} { |
|
||||||
if {$owidth == 1} { |
|
||||||
priv::render_addchar $idx $ch [dict get $overstacks $idx_over] |
|
||||||
incr idx |
|
||||||
} else { |
|
||||||
priv::render_addchar $idx $ch [dict get $overstacks $idx_over] |
|
||||||
incr idx |
|
||||||
priv::render_addchar $idx "" [dict get $overstacks $idx_over] |
|
||||||
} |
|
||||||
} elseif {$uwidth > 1} { |
|
||||||
if {$owidth == 1} { |
|
||||||
priv::render_addchar $idx $ch [dict get $overstacks $idx_over] |
|
||||||
incr idx |
|
||||||
priv::render_addchar $idx $opt_exposed2 [dict get $overstacks $idx_over] |
|
||||||
#don't incr idx - we are just putting a broken-indication in the underlay - which may get overwritten by next overlay char |
|
||||||
} else { |
|
||||||
#2wide over 2wide |
|
||||||
priv::render_addchar $idx $ch [dict get $overstacks $idx_over] |
|
||||||
incr idx |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
#cursor movement? |
|
||||||
#if {![punk::ansi::codetype::is_sgr $code]} { |
|
||||||
# |
|
||||||
#} |
|
||||||
} |
|
||||||
|
|
||||||
if {$opt_overflow == 0} { |
|
||||||
#need to truncate to the width of the original undertext |
|
||||||
#review - string_width vs printing_length here. undertext requirement to be already rendered therefore punk::char::string_width ok? |
|
||||||
set num_under_columns [punk::char::string_width $pt_underchars] ;#plaintext underchars |
|
||||||
} |
|
||||||
|
|
||||||
#coalesce and replay codestacks for out char list |
|
||||||
set outstring "" |
|
||||||
set remstring "" ;#remainder after overflow point reached |
|
||||||
set i 0 |
|
||||||
set cstack [list] |
|
||||||
set prevstack [list] |
|
||||||
set out_rawchars ""; #for overflow counting |
|
||||||
set output_to "outstring" ;#var in effect depending on overflow |
|
||||||
set in_overflow 0 ;#used to stop char-width scanning once in overflow |
|
||||||
foreach ch $out { |
|
||||||
append out_rawchars $ch |
|
||||||
if {$opt_overflow == 0 && !$in_overflow} { |
|
||||||
if {[set nextvisualwidth [punk::char::string_width $out_rawchars]] < $num_under_columns} { |
|
||||||
} else { |
|
||||||
#todo - check if we overflowed with a double-width char ? |
|
||||||
#store visualwidth which may be short |
|
||||||
set in_overflow 1 |
|
||||||
} |
|
||||||
} |
|
||||||
set cstack [dict get $understacks $i] |
|
||||||
if {$cstack ne $prevstack} { |
|
||||||
if {[llength $prevstack]} { |
|
||||||
append $output_to \033\[m |
|
||||||
} |
|
||||||
foreach code $cstack { |
|
||||||
append $output_to $code |
|
||||||
} |
|
||||||
} |
|
||||||
append $output_to $ch |
|
||||||
set prevstack $cstack |
|
||||||
incr i |
|
||||||
if {$in_overflow} { |
|
||||||
set output_to "remstring" |
|
||||||
} |
|
||||||
} |
|
||||||
if {[dict size $understacks] > 0} { |
|
||||||
append $output_to [join [dict get $understacks [expr {[dict size $understacks]-1}]] ""] ;#tail codes |
|
||||||
} |
|
||||||
if {[string length $remstring]} { |
|
||||||
#puts stderr "remainder:$remstring" |
|
||||||
} |
|
||||||
#pdict $understacks |
|
||||||
if {$opt_returnextra} { |
|
||||||
return [list $outstring $visualwidth [string length $outstring] $remstring] |
|
||||||
} else { |
|
||||||
return $outstring |
|
||||||
} |
|
||||||
#return [join $out ""] |
|
||||||
} |
|
||||||
proc overtype::test_renderline {} { |
|
||||||
set t \uFF5E ;#2-wide tilde |
|
||||||
set u \uFF3F ;#2-wide underscore |
|
||||||
set missing \uFFFD |
|
||||||
return [list $t $u A${t}B] |
|
||||||
} |
|
||||||
|
|
||||||
#same as textblock::size - but we don't want that circular dependency |
|
||||||
proc overtype::blocksize {textblock} { |
|
||||||
if {$textblock eq ""} { |
|
||||||
return [dict create width 0 height 1] ;#no such thing as zero-height block - for consistency with non-empty strings having no line-endings |
|
||||||
} |
|
||||||
set textblock [textutil::tabify::untabify2 $textblock] |
|
||||||
#stripansi on entire block in one go rather than line by line - result should be the same - review - make tests |
|
||||||
set textblock [punk::ansi::stripansi $textblock] |
|
||||||
if {[string first \n $textblock] >= 0} { |
|
||||||
set width [tcl::mathfunc::max {*}[lmap v [punk::lib::lines_as_list -- $textblock] {::punk::char::string_width $v}]] |
|
||||||
} else { |
|
||||||
set width [punk::char::string_width $textblock] |
|
||||||
} |
|
||||||
set num_le [expr {[string length $textblock]-[string length [string map [list \n {}] $textblock]]}] ;#faster than splitting into single-char list |
|
||||||
#our concept of block-height is likely to be different to other line-counting mechanisms |
|
||||||
set height [expr {$num_le + 1}] ;# one line if no le - 2 if there is one trailing le even if no data follows le |
|
||||||
|
|
||||||
return [dict create width $width height $height] ;#maintain order in 'image processing' standard width then height - caller may use lassign [dict values [blocksize <data>]] width height |
|
||||||
} |
|
||||||
|
|
||||||
namespace eval overtype::priv { |
|
||||||
|
|
||||||
#is actually addgrapheme? |
|
||||||
proc render_addchar {i c stack} { |
|
||||||
upvar out o |
|
||||||
upvar understacks ustacks |
|
||||||
set nxt [llength $o] |
|
||||||
if {$i < $nxt} { |
|
||||||
lset o $i $c |
|
||||||
} else { |
|
||||||
lappend o $c |
|
||||||
} |
|
||||||
dict set ustacks $i $stack |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# -- --- --- --- --- --- --- --- --- --- --- |
|
||||||
namespace eval overtype { |
|
||||||
interp alias {} ::overtype::center {} ::overtype::centre |
|
||||||
} |
|
||||||
|
|
||||||
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
||||||
## Ready |
|
||||||
package provide overtype [namespace eval overtype { |
|
||||||
variable version |
|
||||||
set version 1.5.6 |
|
||||||
}] |
|
||||||
return |
|
||||||
|
|
||||||
#*** !doctools |
|
||||||
#[manpage_end] |
|
@ -1,998 +0,0 @@ |
|||||||
# -*- tcl -*- |
|
||||||
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from <pkg>-buildversion.txt |
|
||||||
# |
|
||||||
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. |
|
||||||
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. |
|
||||||
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
||||||
# (C) Julian Noble 2003-2023 |
|
||||||
# |
|
||||||
# @@ Meta Begin |
|
||||||
# Application overtype 1.5.7 |
|
||||||
# Meta platform tcl |
|
||||||
# Meta license BSD |
|
||||||
# @@ Meta End |
|
||||||
|
|
||||||
|
|
||||||
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
||||||
# doctools header |
|
||||||
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
||||||
#*** !doctools |
|
||||||
#[manpage_begin overtype_module_overtype 0 1.5.7] |
|
||||||
#[copyright "2024"] |
|
||||||
#[titledesc {overtype text layout - ansi aware}] [comment {-- Name section and table of contents description --}] |
|
||||||
#[moddesc {overtype text layout}] [comment {-- Description at end of page heading --}] |
|
||||||
#[require overtype] |
|
||||||
#[keywords module text ansi] |
|
||||||
#[description] |
|
||||||
#[para] - |
|
||||||
|
|
||||||
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
||||||
|
|
||||||
#*** !doctools |
|
||||||
#[section Overview] |
|
||||||
#[para] overview of overtype |
|
||||||
#[subsection Concepts] |
|
||||||
#[para] - |
|
||||||
|
|
||||||
|
|
||||||
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
||||||
## Requirements |
|
||||||
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
||||||
|
|
||||||
#*** !doctools |
|
||||||
#[subsection dependencies] |
|
||||||
#[para] packages used by overtype |
|
||||||
#[list_begin itemized] |
|
||||||
|
|
||||||
package require Tcl 8.6 |
|
||||||
package require textutil |
|
||||||
package require punk::lib ;#required for lines_as_list |
|
||||||
package require punk::ansi ;#required to detect, split, strip and calculate lengths |
|
||||||
package require punk::char ;#box drawing - and also unicode character width determination for proper layout of text with double-column-width chars |
|
||||||
#*** !doctools |
|
||||||
#[item] [package {Tcl 8.6}] |
|
||||||
#[item] [package textutil] |
|
||||||
#[item] [package punk::ansi] |
|
||||||
#[para] - required to detect, split, strip and calculate lengths of text possibly containing ansi codes |
|
||||||
#[item] [package punk::char] |
|
||||||
#[para] - box drawing - and also unicode character width determination for proper layout of text with double-column-width chars |
|
||||||
|
|
||||||
# #package require frobz |
|
||||||
# #*** !doctools |
|
||||||
# #[item] [package {frobz}] |
|
||||||
|
|
||||||
#*** !doctools |
|
||||||
#[list_end] |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
||||||
#*** !doctools |
|
||||||
#[section API] |
|
||||||
|
|
||||||
|
|
||||||
#Julian Noble <julian@precisium.com.au> - 2003 |
|
||||||
#Released under standard 'BSD license' conditions. |
|
||||||
# |
|
||||||
#todo - ellipsis truncation indicator for center,right |
|
||||||
|
|
||||||
#v1.4 2023-07 - naive ansi color handling - todo - fix string range |
|
||||||
# - need to extract and replace ansi codes? |
|
||||||
|
|
||||||
namespace eval overtype { |
|
||||||
namespace export * |
|
||||||
variable default_ellipsis_horizontal "..." ;#fallback |
|
||||||
variable default_ellipsis_vertical "..." |
|
||||||
namespace eval priv { |
|
||||||
proc _init {} { |
|
||||||
upvar ::overtype::default_ellipsis_horizontal e_h |
|
||||||
upvar ::overtype::default_ellipsis_vertical e_v |
|
||||||
set e_h [format %c 0x2026] ;#Unicode Horizontal Ellipsis |
|
||||||
set e_v [format %c 0x22EE] |
|
||||||
#The unicode ellipsis looks more natural than triple-dash which is centred vertically whereas ellipsis is at floorline of text |
|
||||||
#Also - unicode ellipsis has semantic meaning that other processors can interpret |
|
||||||
#unicode does also provide a midline horizontal ellipsis 0x22EF |
|
||||||
|
|
||||||
#set e [format %c 0x2504] ;#punk::char::charshort boxd_ltdshhz - Box Drawings Light Triple Dash Horizontal |
|
||||||
#if {![catch {package require punk::char}]} { |
|
||||||
# set e [punk::char::charshort boxd_ltdshhz] |
|
||||||
#} |
|
||||||
} |
|
||||||
} |
|
||||||
priv::_init |
|
||||||
} |
|
||||||
proc overtype::about {} { |
|
||||||
return "Simple text formatting. Author JMN. BSD-License" |
|
||||||
} |
|
||||||
|
|
||||||
namespace eval overtype { |
|
||||||
variable escape_terminals |
|
||||||
#single "final byte" in the range 0x40–0x7E (ASCII @A–Z[\]^_`a–z{|}~). |
|
||||||
dict set escape_terminals CSI [list @ \\ ^ _ ` | ~ a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z "\{" "\}"] |
|
||||||
#dict set escape_terminals CSI [list J K m n A B C D E F G s u] ;#basic |
|
||||||
dict set escape_terminals OSC [list \007 \033\\] ;#note mix of 1 and 2-byte terminals |
|
||||||
|
|
||||||
#self-contained 2 byte ansi escape sequences - review more? |
|
||||||
variable ansi_2byte_codes_dict |
|
||||||
set ansi_2byte_codes_dict [dict create\ |
|
||||||
"reset_terminal" "\u001bc"\ |
|
||||||
"save_cursor_posn" "\u001b7"\ |
|
||||||
"restore_cursor_posn" "\u001b8"\ |
|
||||||
"cursor_up_one" "\u001bM"\ |
|
||||||
"NEL - Next Line" "\u001bE"\ |
|
||||||
"IND - Down one line" "\u001bD"\ |
|
||||||
"HTS - Set Tab Stop" "\u001bH"\ |
|
||||||
] |
|
||||||
|
|
||||||
#debatable whether strip should reveal the somethinghidden - some terminals don't hide it anyway. |
|
||||||
# "PM - Privacy Message" "\u001b^somethinghidden\033\\"\ |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
#proc overtype::stripansi {text} { |
|
||||||
# variable escape_terminals ;#dict |
|
||||||
# variable ansi_2byte_codes_dict |
|
||||||
# #important that we don't spend too much time on this for plain text that doesn't contain any escapes anyway |
|
||||||
# if {[string first \033 $text] <0 && [string first \009c $text] <0} { |
|
||||||
# #\033 same as \x1b |
|
||||||
# return $text |
|
||||||
# } |
|
||||||
# |
|
||||||
# set text [convert_g0 $text] |
|
||||||
# |
|
||||||
# #we process char by char - line-endings whether \r\n or \n should be processed as per any other character. |
|
||||||
# #line endings can theoretically occur within an ansi escape sequence (review e.g title?) |
|
||||||
# set inputlist [split $text ""] |
|
||||||
# set outputlist [list] |
|
||||||
# |
|
||||||
# set 2bytecodes [dict values $ansi_2byte_codes_dict] |
|
||||||
# |
|
||||||
# set in_escapesequence 0 |
|
||||||
# #assumption - undertext already 'rendered' - ie no backspaces or carriagereturns or other cursor movement controls |
|
||||||
# set i 0 |
|
||||||
# foreach u $inputlist { |
|
||||||
# set v [lindex $inputlist $i+1] |
|
||||||
# set uv ${u}${v} |
|
||||||
# if {$in_escapesequence eq "2b"} { |
|
||||||
# #2nd byte - done. |
|
||||||
# set in_escapesequence 0 |
|
||||||
# } elseif {$in_escapesequence != 0} { |
|
||||||
# set escseq [dict get $escape_terminals $in_escapesequence] |
|
||||||
# if {$u in $escseq} { |
|
||||||
# set in_escapesequence 0 |
|
||||||
# } elseif {$uv in $escseq} { |
|
||||||
# set in_escapseequence 2b ;#flag next byte as last in sequence |
|
||||||
# } |
|
||||||
# } else { |
|
||||||
# #handle both 7-bit and 8-bit CSI and OSC |
|
||||||
# if {[regexp {^(?:\033\[|\u009b)} $uv]} { |
|
||||||
# set in_escapesequence CSI |
|
||||||
# } elseif {[regexp {^(?:\033\]|\u009c)} $uv]} { |
|
||||||
# set in_escapesequence OSC |
|
||||||
# } elseif {$uv in $2bytecodes} { |
|
||||||
# #self-contained e.g terminal reset - don't pass through. |
|
||||||
# set in_escapesequence 2b |
|
||||||
# } else { |
|
||||||
# lappend outputlist $u |
|
||||||
# } |
|
||||||
# } |
|
||||||
# incr i |
|
||||||
# } |
|
||||||
# return [join $outputlist ""] |
|
||||||
#} |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
proc overtype::string_columns {text} { |
|
||||||
if {[punk::ansi::ta::detect $text]} { |
|
||||||
#error "error string_columns is for calculating character length of string - ansi codes must be stripped/rendered first e.g with punk::ansi::stripansi. Alternatively try punk::ansi::printing_length" |
|
||||||
set text [punk::ansi::stripansi $text] |
|
||||||
} |
|
||||||
return [punk::char::string_width $text] |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
#string range should generally be avoided for both undertext and overtext which contain ansi escapes and other cursor affecting chars such as \b and \r |
|
||||||
proc overtype::left {args} { |
|
||||||
# @c overtype starting at left (overstrike) |
|
||||||
# @c can/should we use something like this?: 'format "%-*s" $len $overtext |
|
||||||
variable default_ellipsis_horizontal |
|
||||||
|
|
||||||
if {[llength $args] < 2} { |
|
||||||
error {usage: ?-transparent [0|1]? ?-overflow [1|0]? ?-ellipsis [1|0]? ?-ellipsistext ...? undertext overtext} |
|
||||||
} |
|
||||||
lassign [lrange $args end-1 end] underblock overblock |
|
||||||
set defaults [dict create\ |
|
||||||
-bias ignored\ |
|
||||||
-ellipsis 0\ |
|
||||||
-ellipsistext $default_ellipsis_horizontal\ |
|
||||||
-ellipsiswhitespace 0\ |
|
||||||
-overflow 0\ |
|
||||||
-transparent 0\ |
|
||||||
-exposed1 \uFFFD\ |
|
||||||
-exposed2 \uFFFD\ |
|
||||||
] |
|
||||||
set known_opts [dict keys $defaults] |
|
||||||
set argsflags [lrange $args 0 end-2] |
|
||||||
dict for {k v} $argsflags { |
|
||||||
if {$k ni $known_opts} { |
|
||||||
error "overtype::left unknown option '$k'. Known options: $known_opts" |
|
||||||
} |
|
||||||
} |
|
||||||
set opts [dict merge $defaults $argsflags] |
|
||||||
# -- --- --- --- --- --- |
|
||||||
set opt_transparent [dict get $opts -transparent] |
|
||||||
set opt_ellipsistext [dict get $opts -ellipsistext] |
|
||||||
set opt_ellipsiswhitespace [dict get $opts -ellipsiswhitespace] |
|
||||||
set opt_exposed1 [dict get $opts -exposed1] ;#widechar_exposed_left - todo |
|
||||||
set opt_exposed2 [dict get $opts -exposed2] ;#widechar_exposed_right - todo |
|
||||||
# -- --- --- --- --- --- |
|
||||||
|
|
||||||
set norm [list \r\n \n] |
|
||||||
set underblock [string map $norm $underblock] |
|
||||||
set overblock [string map $norm $overblock] |
|
||||||
|
|
||||||
set underlines [split $underblock \n] |
|
||||||
#set colwidth [tcl::mathfunc::max {*}[lmap v $underlines {punk::ansi::printing_length $v}]] |
|
||||||
lassign [blocksize $underblock] _w colwidth _h colheight |
|
||||||
set overlines [split $overblock \n] |
|
||||||
#set overblock_width [tcl::mathfunc::max {*}[lmap v $overlines {punk::ansi::printing_length $v}]] |
|
||||||
lassign [blocksize $overblock] _w overblock_width _h overblock_height |
|
||||||
set under_exposed_max [expr {$colwidth - $overblock_width}] |
|
||||||
set right_exposed $under_exposed_max |
|
||||||
|
|
||||||
set outputlines [list] |
|
||||||
foreach undertext $underlines overtext $overlines { |
|
||||||
set undertext_printlen [punk::ansi::printing_length $undertext] |
|
||||||
if {$undertext_printlen < $colwidth} { |
|
||||||
set udiff [expr {$colwidth - $undertext_printlen}] |
|
||||||
set undertext "$undertext[string repeat { } $udiff]" |
|
||||||
} |
|
||||||
set overtext_printlen [punk::ansi::printing_length $overtext] |
|
||||||
set overflowlength [expr {$overtext_printlen - $colwidth}] |
|
||||||
|
|
||||||
#review |
|
||||||
#append overtext "\033\[0m" |
|
||||||
|
|
||||||
|
|
||||||
if {$overflowlength > 0} { |
|
||||||
#background line is narrower than data in line |
|
||||||
set rendered [renderline -transparent $opt_transparent -exposed1 $opt_exposed1 -exposed2 $opt_exposed2 -overflow [dict get $opts -overflow] $undertext $overtext] |
|
||||||
if {![dict get $opts -overflow]} { |
|
||||||
#set overtext [string range $overtext 0 $colwidth-1] ;#string range won't be correct e.g if contains ansi codes or leading \r or \b etc |
|
||||||
if {[dict get $opts -ellipsis]} { |
|
||||||
set show_ellipsis 1 |
|
||||||
if {!$opt_ellipsiswhitespace} { |
|
||||||
#we don't want ellipsis if only whitespace was lost |
|
||||||
set lostdata [string range $overtext end-[expr {$overflowlength-1}] end] |
|
||||||
if {[string trim $lostdata] eq ""} { |
|
||||||
set show_ellipsis 0 |
|
||||||
} |
|
||||||
} |
|
||||||
if {$show_ellipsis} { |
|
||||||
set rendered [overtype::right $rendered $opt_ellipsistext] |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
lappend outputlines $rendered |
|
||||||
} else { |
|
||||||
#we know overtext data is shorter or equal (for this line) |
|
||||||
lappend outputlines [renderline -transparent $opt_transparent -exposed1 $opt_exposed1 -exposed2 $opt_exposed2 $undertext $overtext] |
|
||||||
} |
|
||||||
} |
|
||||||
return [join $outputlines \n] |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
namespace eval overtype::piper { |
|
||||||
proc overcentre {args} { |
|
||||||
if {[llength $args] < 2} { |
|
||||||
error {usage: ?-bias left|right? ?-transparent [0|1|<regexp>]? ?-exposed1 <char>? ?-exposed2 <char>? ?-overflow [1|0]? overtext pipelinedata} |
|
||||||
} |
|
||||||
lassign [lrange $args end-1 end] over under |
|
||||||
set argsflags [lrange $args 0 end-2] |
|
||||||
tailcall overtype::centre {*}$argsflags $under $over |
|
||||||
} |
|
||||||
proc overleft {args} { |
|
||||||
if {[llength $args] < 2} { |
|
||||||
error {usage: ?-startcolumn <column>? ?-transparent [0|1|<regexp>]? ?-exposed1 <char>? ?-exposed2 <char>? ?-overflow [1|0]? overtext pipelinedata} |
|
||||||
} |
|
||||||
lassign [lrange $args end-1 end] over under |
|
||||||
set argsflags [lrange $args 0 end-2] |
|
||||||
tailcall overtype::left {*}$argsflags $under $over |
|
||||||
} |
|
||||||
} |
|
||||||
#todo - left-right ellipsis ? |
|
||||||
proc overtype::centre {args} { |
|
||||||
variable default_ellipsis_horizontal |
|
||||||
if {[llength $args] < 2} { |
|
||||||
error {usage: ?-transparent [0|1]? ?-bias [left|right]? ?-overflow [1|0]? undertext overtext} |
|
||||||
} |
|
||||||
|
|
||||||
foreach {underblock overblock} [lrange $args end-1 end] break |
|
||||||
|
|
||||||
#todo - vertical vs horizontal overflow for blocks |
|
||||||
set defaults [dict create\ |
|
||||||
-bias left\ |
|
||||||
-ellipsis 0\ |
|
||||||
-ellipsistext $default_ellipsis_horizontal\ |
|
||||||
-ellipsiswhitespace 0\ |
|
||||||
-overflow 0\ |
|
||||||
-transparent 0\ |
|
||||||
-exposed1 \uFFFD\ |
|
||||||
-exposed2 \uFFFD\ |
|
||||||
] |
|
||||||
set known_opts [dict keys $defaults] |
|
||||||
set argsflags [lrange $args 0 end-2] |
|
||||||
dict for {k v} $argsflags { |
|
||||||
if {$k ni $known_opts} { |
|
||||||
error "overtype::centre unknown option '$k'. Known options: $known_opts" |
|
||||||
} |
|
||||||
} |
|
||||||
set opts [dict merge $defaults $argsflags] |
|
||||||
# -- --- --- --- --- --- |
|
||||||
set opt_transparent [dict get $opts -transparent] |
|
||||||
set opt_ellipsis [dict get $opts -ellipsis] |
|
||||||
set opt_ellipsistext [dict get $opts -ellipsistext] |
|
||||||
set opt_ellipsiswhitespace [dict get $opts -ellipsiswhitespace] |
|
||||||
set opt_exposed1 [dict get $opts -exposed1] |
|
||||||
set opt_exposed2 [dict get $opts -exposed2] |
|
||||||
# -- --- --- --- --- --- |
|
||||||
|
|
||||||
|
|
||||||
set norm [list \r\n \n] |
|
||||||
set underblock [string map $norm $underblock] |
|
||||||
set overblock [string map $norm $overblock] |
|
||||||
|
|
||||||
set underlines [split $underblock \n] |
|
||||||
#set colwidth [tcl::mathfunc::max {*}[lmap v $underlines {punk::ansi::printing_length $v}]] |
|
||||||
lassign [blocksize $underblock] _w colwidth _h colheight |
|
||||||
set overlines [split $overblock \n] |
|
||||||
#set overblock_width [tcl::mathfunc::max {*}[lmap v $overlines {punk::ansi::printing_length $v}]] |
|
||||||
lassign [blocksize $overblock] _w overblock_width _h overblock_height |
|
||||||
set under_exposed_max [expr {$colwidth - $overblock_width}] |
|
||||||
if {$under_exposed_max > 0} { |
|
||||||
#background block is wider |
|
||||||
if {$under_exposed_max % 2 == 0} { |
|
||||||
#even left/right exposure |
|
||||||
set left_exposed [expr {$under_exposed_max / 2}] |
|
||||||
} else { |
|
||||||
set beforehalf [expr {$under_exposed_max / 2}] ;#1 less than half due to integer division |
|
||||||
if {[string tolower [dict get $opts -bias]] eq "left"} { |
|
||||||
set left_exposed $beforehalf |
|
||||||
} else { |
|
||||||
#bias to the right |
|
||||||
set left_exposed [expr {$beforehalf + 1}] |
|
||||||
} |
|
||||||
} |
|
||||||
} else { |
|
||||||
set left_exposed 0 |
|
||||||
} |
|
||||||
|
|
||||||
set outputlines [list] |
|
||||||
foreach undertext $underlines overtext $overlines { |
|
||||||
set overtext_datalen [punk::ansi::printing_length $overtext] |
|
||||||
set ulen [punk::ansi::printing_length $undertext] |
|
||||||
if {$ulen < $colwidth} { |
|
||||||
set udiff [expr {$colwidth - $ulen}] |
|
||||||
set undertext "$undertext[string repeat { } $udiff]" |
|
||||||
} |
|
||||||
|
|
||||||
set overflowlength [expr {$overtext_datalen - $colwidth}] |
|
||||||
#review - right-to-left langs should elide on left! - extra option required |
|
||||||
|
|
||||||
if {$overflowlength > 0} { |
|
||||||
#overlay line wider or equal |
|
||||||
set rendered [renderline -transparent $opt_transparent -overflow [dict get $opts -overflow] -exposed1 $opt_exposed1 -exposed2 $opt_exposed2 $undertext $overtext] |
|
||||||
#overlay line data is wider - trim if overflow not specified in opts - and overtype an ellipsis at right if it was specified |
|
||||||
if {![dict get $opts -overflow]} { |
|
||||||
#lappend outputlines [string range $overtext 0 [expr {$colwidth - 1}]] |
|
||||||
#set overtext [string range $overtext 0 $colwidth-1 ] |
|
||||||
if {$opt_ellipsis} { |
|
||||||
set show_ellipsis 1 |
|
||||||
if {!$opt_ellipsiswhitespace} { |
|
||||||
#we don't want ellipsis if only whitespace was lost |
|
||||||
set lostdata [string range $overtext end-[expr {$overflowlength-1}] end] |
|
||||||
if {[string trim $lostdata] eq ""} { |
|
||||||
set show_ellipsis 0 |
|
||||||
} |
|
||||||
} |
|
||||||
if {$show_ellipsis} { |
|
||||||
set rendered [overtype::right $rendered $opt_ellipsistext] |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
lappend outputlines $rendered |
|
||||||
#lappend outputlines [renderline -transparent $opt_transparent $undertext $overtext] |
|
||||||
} else { |
|
||||||
#background block is wider than or equal to data for this line |
|
||||||
lappend outputlines [renderline -startcolumn [expr {$left_exposed + 1}] -transparent $opt_transparent -exposed1 $opt_exposed1 -exposed2 $opt_exposed2 $undertext $overtext] |
|
||||||
} |
|
||||||
} |
|
||||||
return [join $outputlines \n] |
|
||||||
} |
|
||||||
|
|
||||||
proc overtype::right {args} { |
|
||||||
#NOT the same as align-right - which should be done to the overblock first if required |
|
||||||
variable default_ellipsis_horizontal |
|
||||||
# @d !todo - implement overflow, length checks etc |
|
||||||
|
|
||||||
if {[llength $args] < 2} { |
|
||||||
error {usage: ?-overflow [1|0]? undertext overtext} |
|
||||||
} |
|
||||||
foreach {underblock overblock} [lrange $args end-1 end] break |
|
||||||
|
|
||||||
set defaults [dict create\ |
|
||||||
-bias ignored\ |
|
||||||
-ellipsis 0\ |
|
||||||
-ellipsistext $default_ellipsis_horizontal\ |
|
||||||
-ellipsiswhitespace 0\ |
|
||||||
-overflow 0\ |
|
||||||
-transparent 0\ |
|
||||||
-exposed1 \uFFFD\ |
|
||||||
-exposed2 \uFFFD\ |
|
||||||
] |
|
||||||
set known_opts [dict keys $defaults] |
|
||||||
set argsflags [lrange $args 0 end-2] |
|
||||||
dict for {k v} $argsflags { |
|
||||||
if {$k ni $known_opts} { |
|
||||||
error "overtype::centre unknown option '$k'. Known options: $known_opts" |
|
||||||
} |
|
||||||
} |
|
||||||
set opts [dict merge $defaults $argsflags] |
|
||||||
# -- --- --- --- --- --- |
|
||||||
set opt_transparent [dict get $opts -transparent] |
|
||||||
set opt_ellipsis [dict get $opts -ellipsis] |
|
||||||
set opt_ellipsistext [dict get $opts -ellipsistext] |
|
||||||
set opt_ellipsiswhitespace [dict get $opts -ellipsiswhitespace] |
|
||||||
set opt_overflow [dict get $opts -overflow] |
|
||||||
set opt_exposed1 [dict get $opts -exposed1] |
|
||||||
set opt_exposed2 [dict get $opts -exposed2] |
|
||||||
# -- --- --- --- --- --- |
|
||||||
|
|
||||||
set norm [list \r\n \n] |
|
||||||
set underblock [string map $norm $underblock] |
|
||||||
set overblock [string map $norm $overblock] |
|
||||||
|
|
||||||
set underlines [split $underblock \n] |
|
||||||
#set colwidth [tcl::mathfunc::max {*}[lmap v $underlines {punk::ansi::printing_length $v}]] |
|
||||||
lassign [blocksize $underblock] _w colwidth _h colheight |
|
||||||
set overlines [split $overblock \n] |
|
||||||
#set overblock_width [tcl::mathfunc::max {*}[lmap v $overlines {punk::ansi::printing_length $v}]] |
|
||||||
lassign [blocksize $overblock] _w overblock_width _h overblock_height |
|
||||||
set under_exposed_max [expr {$colwidth - $overblock_width}] |
|
||||||
set left_exposed $under_exposed_max |
|
||||||
|
|
||||||
set outputlines [list] |
|
||||||
foreach undertext $underlines overtext $overlines { |
|
||||||
set overtext_datalen [punk::ansi::printing_length $overtext] |
|
||||||
set ulen [punk::ansi::printing_length $undertext] |
|
||||||
if {$ulen < $colwidth} { |
|
||||||
set udiff [expr {$colwidth - $ulen}] |
|
||||||
puts xxx |
|
||||||
set undertext "$undertext[string repeat { } $udiff]" |
|
||||||
} |
|
||||||
if {$overtext_datalen < $overblock_width} { |
|
||||||
set odiff [expr {$overblock_width - $overtext_datalen}] |
|
||||||
#padding always on right - if alignment is required it should be done to block beforehand - not here |
|
||||||
set overtextpadding "$overtext[string repeat { } $odiff]" |
|
||||||
} |
|
||||||
|
|
||||||
set overflowlength [expr {$overtext_datalen - $colwidth}] |
|
||||||
if {$overflowlength > 0} { |
|
||||||
#raw overtext wider than undertext column |
|
||||||
set rendered [renderline -transparent $opt_transparent -exposed1 $opt_exposed1 -exposed2 $opt_exposed2 -overflow $opt_overflow -startcolumn 1 $undertext $overtext] |
|
||||||
if {!$opt_overflow} { |
|
||||||
if {$opt_ellipsis} { |
|
||||||
set show_ellipsis 1 |
|
||||||
if {!$opt_ellipsiswhitespace} { |
|
||||||
#we don't want ellipsis if only whitespace was lost |
|
||||||
set lostdata [string range $overtext end-[expr {$overflowlength-1}] end] |
|
||||||
if {[string trim $lostdata] eq ""} { |
|
||||||
set show_ellipsis 0 |
|
||||||
} |
|
||||||
} |
|
||||||
if {$show_ellipsis} { |
|
||||||
set rendered [overtype::right $rendered $opt_ellipsistext] |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
lappend outputlines $rendered |
|
||||||
} else { |
|
||||||
#padded overtext |
|
||||||
lappend outputlines [renderline -transparent $opt_transparent -startcolumn [expr {$left_exposed + 1}] $undertext $overtext] |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return [join $outputlines \n] |
|
||||||
} |
|
||||||
|
|
||||||
# -- --- --- --- --- --- --- --- --- --- --- |
|
||||||
proc overtype::transparentline {args} { |
|
||||||
foreach {under over} [lrange $args end-1 end] break |
|
||||||
set argsflags [lrange $args 0 end-2] |
|
||||||
set defaults [dict create\ |
|
||||||
-transparent 1\ |
|
||||||
-exposed 1 " "\ |
|
||||||
-exposed 2 " "\ |
|
||||||
] |
|
||||||
set newargs [dict merge $defaults $argsflags] |
|
||||||
tailcall overtype::renderline {*}$newargs $under $over |
|
||||||
} |
|
||||||
#renderline may not make sense as it is in the long run for blocks of text - but is handy in the single-line-handling form anyway. |
|
||||||
# We are trying to handle ansi codes in a block of text which is acting like a mini-terminal in some sense. |
|
||||||
#We can process standard cursor moves such as \b \r - but no way to respond to other cursor movements e.g moving to other lines. |
|
||||||
# |
|
||||||
namespace eval overtype::piper { |
|
||||||
proc renderline {args} { |
|
||||||
if {[llength $args] < 2} { |
|
||||||
error {usage: ?-start <int>? ?-transparent [0|1|<regexp>]? ?-overflow [1|0]? overtext pipelinedata} |
|
||||||
} |
|
||||||
foreach {over under} [lrange $args end-1 end] break |
|
||||||
set argsflags [lrange $args 0 end-2] |
|
||||||
tailcall overtype::renderline {*}$argsflags $under $over |
|
||||||
} |
|
||||||
} |
|
||||||
interp alias "" piper_renderline "" overtype::piper::renderline |
|
||||||
|
|
||||||
#-returnextra to enable returning of overflow and length |
|
||||||
# todo - use punk::ansi::ta::detect to short-circuit processing and do simple string calcs as an optimisation? |
|
||||||
#review - DECSWL/DECDWL double width line codes - very difficult/impossible to align and compose with other elements |
|
||||||
#todo - review transparency issues with single/double width characters! |
|
||||||
#bidi - need a base direction and concept of directional runs for RTL vs LTR - may be best handled at another layer? |
|
||||||
proc overtype::renderline {args} { |
|
||||||
if {[llength $args] < 2} { |
|
||||||
error {usage: ?-info 0|1? ?-startcolumn <int>? ?-transparent [0|1|<regexp>]? ?-overflow [1|0]? undertext overtext} |
|
||||||
} |
|
||||||
lassign [lrange $args end-1 end] under over |
|
||||||
#should also rule out \v |
|
||||||
if {[string first \n $over] >=0 || [string first \n $under] >= 0} { |
|
||||||
error "overtype::renderline not allowed to contain newlines" |
|
||||||
} |
|
||||||
set defaults [dict create\ |
|
||||||
-overflow 0\ |
|
||||||
-transparent 0\ |
|
||||||
-startcolumn 1\ |
|
||||||
-info 0\ |
|
||||||
-exposed1 \uFFFD\ |
|
||||||
-exposed2 \uFFFD\ |
|
||||||
] |
|
||||||
#exposed1 and exposed2 for first and second col of underying 2wide char which is truncated by transparency or overflow |
|
||||||
|
|
||||||
set known_opts [dict keys $defaults] |
|
||||||
set argsflags [lrange $args 0 end-2] |
|
||||||
dict for {k v} $argsflags { |
|
||||||
if {$k ni $known_opts} { |
|
||||||
error "overtype::renderline unknown option '$k'. Known options: $known_opts" |
|
||||||
} |
|
||||||
} |
|
||||||
set opts [dict merge $defaults $argsflags] |
|
||||||
# -- --- --- --- --- --- --- --- --- --- --- --- |
|
||||||
set opt_overflow [dict get $opts -overflow] |
|
||||||
set opt_colstart [dict get $opts -startcolumn] ;#start cursor column |
|
||||||
# -- --- --- --- --- --- --- --- --- --- --- --- |
|
||||||
set opt_transparent [dict get $opts -transparent] |
|
||||||
if {$opt_transparent eq "0"} { |
|
||||||
set do_transparency 0 |
|
||||||
} else { |
|
||||||
set do_transparency 1 |
|
||||||
if {$opt_transparent eq "1"} { |
|
||||||
set opt_transparent {[\s]} |
|
||||||
} |
|
||||||
} |
|
||||||
# -- --- --- --- --- --- --- --- --- --- --- --- |
|
||||||
set opt_returnextra [dict get $opts -info] |
|
||||||
# -- --- --- --- --- --- --- --- --- --- --- --- |
|
||||||
set opt_exposed1 [dict get $opts -exposed1] |
|
||||||
set opt_exposed2 [dict get $opts -exposed2] |
|
||||||
# -- --- --- --- --- --- --- --- --- --- --- --- |
|
||||||
|
|
||||||
#----- |
|
||||||
# |
|
||||||
if {[string first \t $under] >= 0} { |
|
||||||
#set under [textutil::tabify::untabify2 $under] |
|
||||||
set under [textutil::tabify::untabifyLine $under 8] ;#8 is default for untabify2 - review |
|
||||||
} |
|
||||||
set overdata $over |
|
||||||
if {[string first \t $over] >= 0} { |
|
||||||
#set overdata [textutil::tabify::untabify2 $over] |
|
||||||
set overdata [textutil::tabify::untabifyLine $over 8] |
|
||||||
} |
|
||||||
#------- |
|
||||||
|
|
||||||
#ta_detect ansi and do simpler processing? |
|
||||||
|
|
||||||
|
|
||||||
# -- --- --- --- --- --- --- --- |
|
||||||
set undermap [punk::ansi::ta::split_codes_single $under] |
|
||||||
set understacks [dict create] |
|
||||||
|
|
||||||
set i_u -1 |
|
||||||
set i_o 0 |
|
||||||
set outcols [list] |
|
||||||
set u_codestack [list] |
|
||||||
set pt_underchars "" ;#for string_columns length calculation for overflow 0 truncation |
|
||||||
set remainder [list] ;#for returnextra |
|
||||||
foreach {pt code} $undermap { |
|
||||||
#pt = plain text |
|
||||||
append pt_underchars $pt |
|
||||||
foreach grapheme [punk::char::grapheme_split $pt] { |
|
||||||
set width [punk::char::string_width $grapheme] |
|
||||||
incr i_u |
|
||||||
dict set understacks $i_u $u_codestack |
|
||||||
lappend outcols $grapheme |
|
||||||
if {$width > 1} { |
|
||||||
incr i_u |
|
||||||
#presumably there are no triple-column or wider unicode chars.. until the aliens arrive.(?) |
|
||||||
#but what about emoji combinations etc - can they be wider than 2? |
|
||||||
dict set understacks $i_u $u_codestack |
|
||||||
lappend outcols "" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
#underlay should already have been rendered and not have non-sgr codes - but let's retain the check for them and not stack them if other codes are here |
|
||||||
|
|
||||||
#only stack SGR (graphics rendition) codes - not title sets, cursor moves etc |
|
||||||
#order of if-else based on assumptions: |
|
||||||
# that pure resets are fairly common - more so than leading resets with other info |
|
||||||
# that non-sgr codes are not that common, so ok to check for resets before verifying it is actually SGR at all. |
|
||||||
if {[punk::ansi::codetype::is_sgr_reset $code]} { |
|
||||||
set u_codestack [list] |
|
||||||
} elseif {[punk::ansi::codetype::has_sgr_leadingreset $code]} { |
|
||||||
set u_codestack [list $code] |
|
||||||
} elseif {[punk::ansi::codetype::is_sgr $code]} { |
|
||||||
lappend u_codestack $code |
|
||||||
} |
|
||||||
#consider also if there are other codes that should be stacked..? |
|
||||||
} |
|
||||||
#trailing codes in effect for underlay |
|
||||||
if {[llength $undermap]} { |
|
||||||
dict set understacks [expr {$i_u + 1}] $u_codestack |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
# -- --- --- --- --- --- --- --- |
|
||||||
#### |
|
||||||
#if opt_colstart - we need to build a space (or any singlewidth char really) padding on the left of the right number of columns. |
|
||||||
#this will be processed as transparent - and handle doublewidth underlay characters appropriately |
|
||||||
set startpad [string repeat " " [expr {$opt_colstart -1}]] |
|
||||||
append startpad $overdata ;#overdata with left padding spaces based on col-start under will show through for left-padding portion regardless of -transparency |
|
||||||
set overmap [punk::ansi::ta::split_codes_single $startpad] |
|
||||||
#### |
|
||||||
set colcursor $opt_colstart |
|
||||||
|
|
||||||
#set re_diacritics {[\u0300-\u036f]+|[\u1ab0-\u1aff]+|[\u1dc0-\u1dff]+|[\u20d0-\u20ff]+|[\ufe20-\ufe2f]+} |
|
||||||
#as at 2024-02 punk::char::grapheme_split uses these - not aware of more complex graphemes |
|
||||||
|
|
||||||
|
|
||||||
set overstacks [dict create] |
|
||||||
set o_codestack [list] |
|
||||||
set pt_overchars "" |
|
||||||
foreach {pt code} $overmap { |
|
||||||
append pt_overchars $pt |
|
||||||
foreach grapheme [punk::char::grapheme_split $pt] { |
|
||||||
dict set overstacks $i_o $o_codestack |
|
||||||
incr i_o |
|
||||||
} |
|
||||||
|
|
||||||
if {[punk::ansi::codetype::is_sgr $code]} { |
|
||||||
if {[punk::ansi::codetype::has_sgr_leadingreset $code]} { |
|
||||||
#m code which has sgr reset at start - no need to replay prior sgr codes |
|
||||||
set o_codestack [list $code] |
|
||||||
} else { |
|
||||||
lappend o_codestack $code |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
#only stack SGR (graphics rendition) codes - not title sets, cursor moves etc |
|
||||||
#order of if-else based on assumptions: |
|
||||||
# that pure resets are fairly common - more so than leading resets with other info |
|
||||||
# that non-sgr codes are not that common, so ok to check for resets before verifying it is actually SGR at all. |
|
||||||
if {[punk::ansi::codetype::is_sgr_reset $code]} { |
|
||||||
set o_codestack [list] |
|
||||||
} elseif {[punk::ansi::codetype::has_sgr_leadingreset $code]} { |
|
||||||
set o_codestack [list $code] |
|
||||||
} elseif {[punk::ansi::codetype::is_sgr $code]} { |
|
||||||
lappend o_codestack $code |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
# -- --- --- --- --- --- --- --- |
|
||||||
|
|
||||||
|
|
||||||
#potential problem - combinining diacritics directly following control chars like \r \b |
|
||||||
|
|
||||||
set bs [format %c 0x08] |
|
||||||
set idx 0 ;# line index (cursor - 1) |
|
||||||
set idx_over -1 |
|
||||||
foreach {pt code} $overmap { |
|
||||||
#set ptchars [split $pt ""] ;#for lookahead |
|
||||||
set overlay_graphemes [punk::char::grapheme_split $pt] |
|
||||||
#emit plaintext chars first using existing SGR codes from under/over stack as appropriate |
|
||||||
#then check if the following code is a cursor movement within the line and adjust index if so |
|
||||||
foreach ch $overlay_graphemes { |
|
||||||
set within_undercols [expr {$idx <= [llength $outcols]-1}] |
|
||||||
incr idx_over |
|
||||||
if {$ch eq "\r"} { |
|
||||||
set idx [expr {$opt_colstart -1}] |
|
||||||
} elseif {$ch eq "\b"} { |
|
||||||
#review - backspace effect on double-width chars |
|
||||||
if {$idx > ($opt_colstart -1)} { |
|
||||||
incr idx -1 |
|
||||||
} |
|
||||||
} elseif {($idx < ($opt_colstart -1))} { |
|
||||||
incr idx |
|
||||||
} elseif {($do_transparency && [regexp $opt_transparent $ch])} { |
|
||||||
#pre opt_colstart is effectively transparent (we have applied padding of required number of columns to left of overlay) |
|
||||||
if {$idx > [llength $outcols]-1} { |
|
||||||
lappend outcols " " |
|
||||||
dict set understacks $idx [list] ;#review - use idx-1 codestack? |
|
||||||
incr idx |
|
||||||
} else { |
|
||||||
set uwidth [punk::char::string_width [lindex $outcols $idx]] |
|
||||||
if {[lindex $outcols $idx] eq ""} { |
|
||||||
#2nd col of 2-wide char in underlay |
|
||||||
incr idx |
|
||||||
} elseif {$uwidth == 0} { |
|
||||||
#e.g control char ? combining diacritic ? |
|
||||||
incr idx |
|
||||||
} elseif {$uwidth == 1} { |
|
||||||
set owidth [punk::char::string_width $ch] |
|
||||||
incr idx |
|
||||||
if {$owidth > 1} { |
|
||||||
incr idx |
|
||||||
} |
|
||||||
} elseif {$uwidth > 1} { |
|
||||||
if {[punk::char::string_width $ch] == 1} { |
|
||||||
#normal singlewide transparency |
|
||||||
set next_pt_overchar [string index $pt_overchars $idx_over+1] ;#lookahead of next plain-text char in overlay |
|
||||||
if {$next_pt_overchar eq ""} { |
|
||||||
#special-case trailing transparent - no next_pt_overchar |
|
||||||
incr idx |
|
||||||
} else { |
|
||||||
if {[regexp $opt_transparent $next_pt_overchar]} { |
|
||||||
incr idx |
|
||||||
} else { |
|
||||||
#next overlay char is not transparent.. first-half of underlying 2wide char is exposed |
|
||||||
priv::render_addchar $idx $opt_exposed1 [dict get $overstacks $idx_over] |
|
||||||
incr idx |
|
||||||
} |
|
||||||
} |
|
||||||
} else { |
|
||||||
#2wide transparency over 2wide in underlay |
|
||||||
incr idx |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} else { |
|
||||||
#non-transparent char in overlay |
|
||||||
set uwidth [punk::char::string_width [lindex $outcols $idx]] |
|
||||||
|
|
||||||
if {$within_undercols && [lindex $outcols $idx] eq ""} { |
|
||||||
#2nd col of 2wide char in underlay |
|
||||||
priv::render_addchar $idx $ch [dict get $overstacks $idx_over] |
|
||||||
#JMN - this has to expose if our startposn chopped an underlay - but not if we already overwrote the first half of the widechar underlay grapheme |
|
||||||
#e.g renderline \uFF21\uFF21--- a\uFF23\uFF23 |
|
||||||
#vs |
|
||||||
# renderline -startcolumn 2 \uFF21---- \uFF23 |
|
||||||
if {[lindex $outcols $idx-1] != ""} { |
|
||||||
#verified it's an empty following a filled - so it's a legit underlay remnant |
|
||||||
#reset previous to an exposed 1st-half - but leave understacks code as is |
|
||||||
priv::render_addchar [expr {$idx-1}] $opt_exposed1 [dict get $understacks [expr {$idx-1}]] |
|
||||||
} |
|
||||||
incr idx |
|
||||||
|
|
||||||
} elseif {$uwidth == 0} { |
|
||||||
if {$within_undercols} { |
|
||||||
#e.g combining diacritic - increment before over char REVIEW |
|
||||||
#arguably the previous overchar should have done this - ie lookahead for combiners? |
|
||||||
priv::render_addchar $idx "" [dict get $overstacks $idx_over] |
|
||||||
incr idx |
|
||||||
priv::render_addchar $idx $ch [dict get $overstacks $idx_over] |
|
||||||
incr idx |
|
||||||
} else { |
|
||||||
#overflow |
|
||||||
priv::render_addchar $idx $ch [dict get $overstacks $idx_over] |
|
||||||
incr idx |
|
||||||
} |
|
||||||
} elseif {$uwidth == 1} { |
|
||||||
set owidth [punk::char::string_width $ch] |
|
||||||
if {$owidth == 1} { |
|
||||||
priv::render_addchar $idx $ch [dict get $overstacks $idx_over] |
|
||||||
incr idx |
|
||||||
} else { |
|
||||||
priv::render_addchar $idx $ch [dict get $overstacks $idx_over] |
|
||||||
incr idx |
|
||||||
priv::render_addchar $idx "" [dict get $overstacks $idx_over] |
|
||||||
#if next column in underlay empty - we've overwritten first half of underlying 2wide grapheme |
|
||||||
#replace with rhs exposure in case there are no more overlay graphemes coming - use underlay's stack |
|
||||||
if {[llength $outcols] >= [expr {$idx +2}] && [lindex $outcols $idx+1] eq ""} { |
|
||||||
priv::render_addchar [expr {$idx+1}] $opt_exposed2 [dict get $understacks [expr {$idx+1}]] |
|
||||||
} |
|
||||||
incr idx |
|
||||||
} |
|
||||||
} elseif {$uwidth > 1} { |
|
||||||
set owidth [punk::char::string_width $ch] |
|
||||||
if {$owidth == 1} { |
|
||||||
priv::render_addchar $idx $ch [dict get $overstacks $idx_over] |
|
||||||
incr idx |
|
||||||
priv::render_addchar $idx $opt_exposed2 [dict get $overstacks $idx_over] |
|
||||||
#don't incr idx - we are just putting a broken-indication in the underlay - which may get overwritten by next overlay char |
|
||||||
} else { |
|
||||||
#2wide over 2wide |
|
||||||
priv::render_addchar $idx $ch [dict get $overstacks $idx_over] |
|
||||||
incr idx 2 |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
#cursor movement? |
|
||||||
#if {![punk::ansi::codetype::is_sgr $code]} { |
|
||||||
# |
|
||||||
#} |
|
||||||
if {[punk::ansi::codetype::is_cursor_move_in_line $code]} { |
|
||||||
} |
|
||||||
set re_col_move {\x1b\[([0-9]*)(C|D|G)} |
|
||||||
if {[regexp $re_col_move $code _match num type]} { |
|
||||||
if {$type eq "C"} { |
|
||||||
#left-arrow/move-back |
|
||||||
if {$num eq ""} {set num 1} |
|
||||||
incr idx -$num |
|
||||||
if {$idx < $opt_colstart} { |
|
||||||
set idx $opt_colstart |
|
||||||
} |
|
||||||
} elseif {$type eq "D"} { |
|
||||||
#right-arrow/move forward |
|
||||||
if {$num eq ""} {set num 1} |
|
||||||
if {!$opt_overflow || ($idx + $num) <= [llength $outcols]-1} { |
|
||||||
incr idx $num |
|
||||||
if {$idx > [llength $outcols]-1} { |
|
||||||
set idx [llength $outcols] -1 |
|
||||||
} |
|
||||||
} else { |
|
||||||
set idxstart $idx |
|
||||||
set idxend [expr {[llength $outcols]-1}] |
|
||||||
set moveend [expr {$idxend - $idxstart}] |
|
||||||
incr idx $moveend |
|
||||||
set stackinfo [dict get $understacks $idx] ;#use understack at end - which may or may not have already been replaced by stack from overtext |
|
||||||
#pad outcols |
|
||||||
set movemore [expr {$num - $moveend}] |
|
||||||
#assert movemore always at least 1 or we wouldn't be in this branch |
|
||||||
for {set m 1} {$m <= $movemore} {incr m} { |
|
||||||
incr idx |
|
||||||
priv::render_addchar $idx " " $stackinfo |
|
||||||
} |
|
||||||
} |
|
||||||
} elseif {$type eq "G"} { |
|
||||||
#move absolute column |
|
||||||
#adjust to colstart - as column 1 is within overlay |
|
||||||
#ie |
|
||||||
set num [expr {$num + $opt_colstart}] |
|
||||||
error "renderline absolute col move ESC G unimplemented" |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if {$opt_overflow == 0} { |
|
||||||
#need to truncate to the width of the original undertext |
|
||||||
#review - string_width vs printing_length here. undertext requirement to be already rendered therefore punk::char::string_width ok? |
|
||||||
set num_under_columns [punk::char::string_width $pt_underchars] ;#plaintext underchars |
|
||||||
} |
|
||||||
|
|
||||||
#coalesce and replay codestacks for outcols grapheme list |
|
||||||
set outstring "" |
|
||||||
set remstring "" ;#remainder after overflow point reached |
|
||||||
set i 0 |
|
||||||
set cstack [list] |
|
||||||
set prevstack [list] |
|
||||||
set out_rawchars ""; #for overflow counting |
|
||||||
set output_to "outstring" ;#var in effect depending on overflow |
|
||||||
set in_overflow 0 ;#used to stop char-width scanning once in overflow |
|
||||||
foreach ch $outcols { |
|
||||||
append out_rawchars $ch |
|
||||||
if {$opt_overflow == 0 && !$in_overflow} { |
|
||||||
if {[set nextvisualwidth [punk::char::string_width $out_rawchars]] > $num_under_columns} { |
|
||||||
#todo - check if we overflowed with a double-width char ? |
|
||||||
#store visualwidth which may be short |
|
||||||
set in_overflow 1 |
|
||||||
} |
|
||||||
} |
|
||||||
if {$in_overflow} { |
|
||||||
set output_to "remstring" |
|
||||||
} |
|
||||||
set cstack [dict get $understacks $i] |
|
||||||
if {$cstack ne $prevstack} { |
|
||||||
if {[llength $prevstack]} { |
|
||||||
append $output_to \033\[m |
|
||||||
} |
|
||||||
foreach code $cstack { |
|
||||||
append $output_to $code |
|
||||||
} |
|
||||||
} |
|
||||||
append $output_to $ch |
|
||||||
set prevstack $cstack |
|
||||||
incr i |
|
||||||
} |
|
||||||
if {[dict size $understacks] > 0} { |
|
||||||
append $output_to [join [dict get $understacks [expr {[dict size $understacks]-1}]] ""] ;#tail codes |
|
||||||
} |
|
||||||
if {[string length $remstring]} { |
|
||||||
#puts stderr "remainder:$remstring" |
|
||||||
} |
|
||||||
#pdict $understacks |
|
||||||
if {$opt_returnextra} { |
|
||||||
set cursorinfo "" |
|
||||||
return [list result $outstring visualwidth - stringlen [string length $outstring] remainder $remstring cursor [expr {$idx + 1}]] |
|
||||||
} else { |
|
||||||
return $outstring |
|
||||||
} |
|
||||||
#return [join $out ""] |
|
||||||
} |
|
||||||
proc overtype::test_renderline {} { |
|
||||||
set t \uFF5E ;#2-wide tilde |
|
||||||
set u \uFF3F ;#2-wide underscore |
|
||||||
set missing \uFFFD |
|
||||||
return [list $t $u A${t}B] |
|
||||||
} |
|
||||||
|
|
||||||
#maintenance warning |
|
||||||
#same as textblock::size - but we don't want that circular dependency |
|
||||||
#block width and height can be tricky. e.g \v handled differently on different terminal emulators and can affect both |
|
||||||
proc overtype::blocksize {textblock} { |
|
||||||
if {$textblock eq ""} { |
|
||||||
return [dict create width 0 height 1] ;#no such thing as zero-height block - for consistency with non-empty strings having no line-endings |
|
||||||
} |
|
||||||
set textblock [textutil::tabify::untabify2 $textblock] |
|
||||||
#stripansi on entire block in one go rather than line by line - result should be the same - review - make tests |
|
||||||
set textblock [punk::ansi::stripansi $textblock] |
|
||||||
if {[string first \n $textblock] >= 0} { |
|
||||||
set width [tcl::mathfunc::max {*}[lmap v [punk::lib::lines_as_list -- $textblock] {::punk::char::string_width $v}]] |
|
||||||
} else { |
|
||||||
set width [punk::char::string_width $textblock] |
|
||||||
} |
|
||||||
set num_le [expr {[string length $textblock]-[string length [string map [list \n {}] $textblock]]}] ;#faster than splitting into single-char list |
|
||||||
#our concept of block-height is likely to be different to other line-counting mechanisms |
|
||||||
set height [expr {$num_le + 1}] ;# one line if no le - 2 if there is one trailing le even if no data follows le |
|
||||||
|
|
||||||
return [dict create width $width height $height] ;#maintain order in 'image processing' standard width then height - caller may use lassign [dict values [blocksize <data>]] width height |
|
||||||
} |
|
||||||
|
|
||||||
namespace eval overtype::priv { |
|
||||||
|
|
||||||
#is actually addgrapheme? |
|
||||||
proc render_addchar {i c stack} { |
|
||||||
upvar outcols o |
|
||||||
upvar understacks ustacks |
|
||||||
set nxt [llength $o] |
|
||||||
if {$i < $nxt} { |
|
||||||
lset o $i $c |
|
||||||
} else { |
|
||||||
lappend o $c |
|
||||||
} |
|
||||||
dict set ustacks $i $stack |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# -- --- --- --- --- --- --- --- --- --- --- |
|
||||||
namespace eval overtype { |
|
||||||
interp alias {} ::overtype::center {} ::overtype::centre |
|
||||||
} |
|
||||||
|
|
||||||
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
||||||
## Ready |
|
||||||
package provide overtype [namespace eval overtype { |
|
||||||
variable version |
|
||||||
set version 1.5.7 |
|
||||||
}] |
|
||||||
return |
|
||||||
|
|
||||||
#*** !doctools |
|
||||||
#[manpage_end] |
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,276 @@ |
|||||||
|
# -*- tcl -*- |
||||||
|
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'pmix make' or bin/punkmake to update from <pkg>-buildversion.txt |
||||||
|
# module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.3.tm |
||||||
|
# |
||||||
|
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. |
||||||
|
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. |
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
# (C) 2024 |
||||||
|
# |
||||||
|
# @@ Meta Begin |
||||||
|
# Application poshinfo 999999.0a1.0 |
||||||
|
# Meta platform tcl |
||||||
|
# Meta license <unspecified> |
||||||
|
# @@ Meta End |
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
# doctools header |
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
#*** !doctools |
||||||
|
#[manpage_begin shellspy_module_poshinfo 0 999999.0a1.0] |
||||||
|
#[copyright "2024"] |
||||||
|
#[titledesc {Module API}] [comment {-- Name section and table of contents description --}] |
||||||
|
#[moddesc {-}] [comment {-- Description at end of page heading --}] |
||||||
|
#[require poshinfo] |
||||||
|
#[keywords module] |
||||||
|
#[description] |
||||||
|
#[para] - |
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
|
||||||
|
#*** !doctools |
||||||
|
#[section Overview] |
||||||
|
#[para] overview of poshinfo |
||||||
|
#[subsection Concepts] |
||||||
|
#[para] - |
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Requirements |
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
|
||||||
|
#*** !doctools |
||||||
|
#[subsection dependencies] |
||||||
|
#[para] packages used by poshinfo |
||||||
|
#[list_begin itemized] |
||||||
|
|
||||||
|
package require Tcl 8.6- |
||||||
|
package require punk::config |
||||||
|
package require json ;#tcllib |
||||||
|
#toml, yaml? |
||||||
|
|
||||||
|
#*** !doctools |
||||||
|
#[item] [package {Tcl 8.6}] |
||||||
|
#[item] [package {punk::config}] |
||||||
|
#[item] [package {json}] |
||||||
|
|
||||||
|
# #package require frobz |
||||||
|
# #*** !doctools |
||||||
|
# #[item] [package {frobz}] |
||||||
|
|
||||||
|
#*** !doctools |
||||||
|
#[list_end] |
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
|
||||||
|
#*** !doctools |
||||||
|
#[section API] |
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
# oo::class namespace |
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
#tcl::namespace::eval poshinfo::class { |
||||||
|
#*** !doctools |
||||||
|
#[subsection {Namespace poshinfo::class}] |
||||||
|
#[para] class definitions |
||||||
|
#if {[tcl::info::commands [tcl::namespace::current]::interface_sample1] eq ""} { |
||||||
|
#*** !doctools |
||||||
|
#[list_begin enumerated] |
||||||
|
|
||||||
|
# oo::class create interface_sample1 { |
||||||
|
# #*** !doctools |
||||||
|
# #[enum] CLASS [class interface_sample1] |
||||||
|
# #[list_begin definitions] |
||||||
|
|
||||||
|
# method test {arg1} { |
||||||
|
# #*** !doctools |
||||||
|
# #[call class::interface_sample1 [method test] [arg arg1]] |
||||||
|
# #[para] test method |
||||||
|
# puts "test: $arg1" |
||||||
|
# } |
||||||
|
|
||||||
|
# #*** !doctools |
||||||
|
# #[list_end] [comment {-- end definitions interface_sample1}] |
||||||
|
# } |
||||||
|
|
||||||
|
#*** !doctools |
||||||
|
#[list_end] [comment {--- end class enumeration ---}] |
||||||
|
#} |
||||||
|
#} |
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
# Base namespace |
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
tcl::namespace::eval poshinfo { |
||||||
|
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase |
||||||
|
#variable xyz |
||||||
|
|
||||||
|
#*** !doctools |
||||||
|
#[subsection {Namespace poshinfo}] |
||||||
|
#[para] Core API functions for poshinfo |
||||||
|
#[list_begin definitions] |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#proc sample1 {p1 n args} { |
||||||
|
# #*** !doctools |
||||||
|
# #[call [fun sample1] [arg p1] [arg n] [opt {option value...}]] |
||||||
|
# #[para]Description of sample1 |
||||||
|
# #[para] Arguments: |
||||||
|
# # [list_begin arguments] |
||||||
|
# # [arg_def tring p1] A description of string argument p1. |
||||||
|
# # [arg_def integer n] A description of integer argument n. |
||||||
|
# # [list_end] |
||||||
|
# return "ok" |
||||||
|
#} |
||||||
|
|
||||||
|
proc info_from_filename {fname} { |
||||||
|
#string based filename processing: we are deliberately avoiding test of file existence etc here |
||||||
|
if {$fname eq ""} { |
||||||
|
error "poshinfo::info_from_filename unable to determine name from empty string" |
||||||
|
} |
||||||
|
if {[string first . $fname] < 0} { |
||||||
|
#theoretically we could have a file without dots - but it's more likely an error in this context |
||||||
|
error "poshinfo::info_from_filename supplied value '$fname' doesn't look like a filename. Cowardly refusing to guess a shortname." |
||||||
|
} |
||||||
|
set ftail [file tail $fname] |
||||||
|
set rootname [file rootname $ftail] |
||||||
|
set format [string trimleft [file extension $ftail] .] |
||||||
|
set parts [split $rootname .] |
||||||
|
if {[lindex $parts end] eq "omp"} { |
||||||
|
set type omp |
||||||
|
set shortname [join [lrange $parts 0 end-1] .] |
||||||
|
} else { |
||||||
|
if {$rootname eq "schema"} { |
||||||
|
set type schema |
||||||
|
} else { |
||||||
|
set type unknown |
||||||
|
} |
||||||
|
set shortname $rootname |
||||||
|
} |
||||||
|
return [dict create shortname $shortname format $format type $type] |
||||||
|
} |
||||||
|
|
||||||
|
proc themes_dict {{globfor *}} { |
||||||
|
set running_config [punk::config::get running-config] |
||||||
|
set posh_themes_path [tcl::dict::get $running_config posh_themes_path] |
||||||
|
#posh_themes_path_extra ?? |
||||||
|
|
||||||
|
set themes [tcl::dict::create] |
||||||
|
|
||||||
|
if {[string length $posh_themes_path]} { |
||||||
|
if {[file exists $posh_themes_path]} { |
||||||
|
set files [glob -nocomplain -directory $posh_themes_path -tails $globfor] |
||||||
|
foreach ftail $files { |
||||||
|
set themeinfo [info_from_filename $ftail] |
||||||
|
set shortname [dict get $themeinfo shortname] |
||||||
|
dict set themeinfo path [file join $posh_themes_path $ftail] |
||||||
|
if {![dict exists $themes $shortname]} { |
||||||
|
dict set themes $shortname [list $themeinfo] |
||||||
|
} else { |
||||||
|
dict lappend themes $shortname $themeinfo |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return $themes |
||||||
|
} |
||||||
|
proc themes {{globfor *}} { |
||||||
|
set themes [themes_dict $globfor] |
||||||
|
set posh_theme [file normalize [punk::config::get_running_global posh_theme]] |
||||||
|
set t [textblock::class::table new "Posh Themes"] |
||||||
|
$t configure -show_header 1 -show_hseps 0 |
||||||
|
$t add_column -headers Shortname |
||||||
|
$t add_column -headers Path |
||||||
|
dict for {shortname themeinfolist} $themes { |
||||||
|
#hack - support just one for now |
||||||
|
set themeinfo [lindex $themeinfolist 0] |
||||||
|
|
||||||
|
set path [dict get $themeinfo path] |
||||||
|
$t add_row [list $shortname $path] |
||||||
|
set fg "" |
||||||
|
set bg "" |
||||||
|
switch -- [dict get $themeinfo type] { |
||||||
|
schema { |
||||||
|
set bg Web-orange |
||||||
|
} |
||||||
|
omp {} |
||||||
|
unknown { |
||||||
|
set bg Web-red |
||||||
|
} |
||||||
|
default { |
||||||
|
#we shouldn't be getting other values |
||||||
|
set bg Web-yellow |
||||||
|
} |
||||||
|
} |
||||||
|
if {$posh_theme eq [file normalize $path]} { |
||||||
|
set fg web-limegreen |
||||||
|
} |
||||||
|
if {"$fg$bg" ne ""} { |
||||||
|
$t configure_row [expr {[$t row_count]-1}] -ansibase [a+ {*}$fg {*}$bg] |
||||||
|
} |
||||||
|
} |
||||||
|
set result [$t print] |
||||||
|
$t destroy |
||||||
|
return $result |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
#*** !doctools |
||||||
|
#[list_end] [comment {--- end definitions namespace poshinfo ---}] |
||||||
|
} |
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
# Secondary API namespace |
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
tcl::namespace::eval poshinfo::lib { |
||||||
|
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase |
||||||
|
tcl::namespace::path [tcl::namespace::parent] |
||||||
|
#*** !doctools |
||||||
|
#[subsection {Namespace poshinfo::lib}] |
||||||
|
#[para] Secondary functions that are part of the API |
||||||
|
#[list_begin definitions] |
||||||
|
|
||||||
|
#proc utility1 {p1 args} { |
||||||
|
# #*** !doctools |
||||||
|
# #[call lib::[fun utility1] [arg p1] [opt {?option value...?}]] |
||||||
|
# #[para]Description of utility1 |
||||||
|
# return 1 |
||||||
|
#} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#*** !doctools |
||||||
|
#[list_end] [comment {--- end definitions namespace poshinfo::lib ---}] |
||||||
|
} |
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
#*** !doctools |
||||||
|
#[section Internal] |
||||||
|
#tcl::namespace::eval poshinfo::system { |
||||||
|
#*** !doctools |
||||||
|
#[subsection {Namespace poshinfo::system}] |
||||||
|
#[para] Internal functions that are not part of the API |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#} |
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Ready |
||||||
|
package provide poshinfo [tcl::namespace::eval poshinfo { |
||||||
|
variable pkg poshinfo |
||||||
|
variable version |
||||||
|
set version 999999.0a1.0 |
||||||
|
}] |
||||||
|
return |
||||||
|
|
||||||
|
#*** !doctools |
||||||
|
#[manpage_end] |
||||||
|
|
@ -0,0 +1,3 @@ |
|||||||
|
0.1.0 |
||||||
|
#First line must be a semantic version number |
||||||
|
#all other lines are ignored. |
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue