proc un_ansi {data} {
# --------------------------------------------------------------------------
# remove ansi escape sequences
# --------------------------------------------------------------------------
set txt {}
while {[string length ${data}]} {
set match { }
switch -regexp -- ${data} {
{^\x1b(\[|\(|\))[;?0-9]*[0-9A-Za-z]} {
regexp -- {^\x1b(\[|\(|\))[;?0-9]*[0-9A-Za-z]} ${data} match
append txt "\n"
}
{^(.+?)\x1b} {
regexp -- {^(.+?)\x1b} ${data} UNUSED match
# handle special escape sequences
regsub -all -- {\\([\\\[\]])} ${match} {\1} raw_match
append txt ${raw_match}
}
{^\x1b} {
# do nothing
}
default {
set match ${data}
append txt ${match}
}
}
set data [string range ${data} [string length ${match}] end]
}
# remove white spaces not needed anymore
regsub -all -- "\t+" ${txt} { } txt
regsub -all -- " +" ${txt} { } txt
regsub -all -nocase -- "\n+" [string trim ${txt}] "\n" txt
return ${txt}
}There are devices building the screen not line by line. I even found devices that try to be intelligent and speed up the terminal output by just replacing the changing parts of the screen in any order. Therefore something like a virtual terminal is useful:
proc un_ansi_vt {data {mode raw} {bounding {0 25 0 80 0 0 0 1}}} {
# --------------------------------------------------------------------------
# simulation of a virtual terminal to create line by line output
# --------------------------------------------------------------------------
global terminal
foreach {row_min row_max col_min col_max linewrap pagewrap status listmode} ${bounding} {
break
}
set col_last 0
set row_last 0
set linewrap_last ${linewrap}
if {${status} && [info exists terminal(status)]} {
foreach {row_last col_last linewrap_last} $terminal(status) {
break
}
}
switch -glob -- ${mode} {
delta -
diff* -
raw {
catch {unset terminal}
}
reset* {
catch {unset terminal}
# FRINK: set row, col by for
for {set row ${row_min}} {${row} < ${row_max}} {incr row} {
for {set col ${col_min}} {${col} < ${col_max}} {incr col} {
set terminal(${row},${col}) { }
}
}
set col_last 0
set row_last 0
set linewrap_last ${linewrap}
}
default {
# an append/overwrite mode
if {![array size terminal]} {
for {set row ${row_min}} {${row} < ${row_max}} {incr row} {
for {set col ${col_min}} {${col} < ${col_max}} {incr col} {
set terminal(${row},${col}) { }
}
}
}
}
}
set col ${row_last}
set row ${col_last}
set linewrap ${linewrap_last}
while {[string length ${data}]} {
set match { }
switch -regexp -- ${data} {
{^\x1b\[([0-9]*);([0-9]*)[Hf]} {
# position cursor
regexp -- {^\x1b\[0*([0-9]*);0*([0-9]*)[Hf]} ${data} match row col
if {![llength ${row}]} {
set row 0
}
if {![llength ${col}]} {
set col 0
}
}
{^\x1b\[[0-9]*[A-D]} {
# move cursor
regexp -- {^\x1b\[0*([0-9]*)([A-D])} ${data} match cnt chr
if {![llength ${cnt}]} {
set cnt 1
}
switch -exact -- ${chr} {
A {
# moves the cursor up by 'cnt' rows
incr row -${cnt}
}
B {
# moves the cursor down by 'cnt' rows
incr row ${cnt}
}
C {
# moves the cursor forward by 'cnt' columns
incr col ${cnt}
}
D {
# moves the cursor backward by 'cnt' columns
incr col -${cnt}
}
}
if {(${col} < ${col_min}) ||(${col_max} < ${col})} {
if {${linewrap}} {
set col ${col_min}
incr row
} else {
puts stderr "line position\t'${match}' at (${row},${col})"
}
}
if {(${row} < ${row_min}) ||(${row_max} < ${row})} {
if {${pagewrap}} {
set row ${row_min}
} else {
puts stderr "page bounding\t'${match}' at (${row},${col})"
}
}
}
{^\x1b\[7[hl]} {
regexp -- {^\x1b\[7([hl])} ${data} match chr
# h/l: enable/disable line wrap
set linewrap [string match {h} ${chr}]
}
{^\x1b\[[;0-9]*m} {
# reset attributes
regexp -- {^\x1b\[[;0-9]*m} ${data} match
set terminal(${row},${col}) { }
}
{^\x1b\[[12]J} {
# erase line
regexp -- {^\x1b\[([12])J} ${data} match chr
switch -exact -- ${chr} {
2 {
# erases the screen with the background colour and moves the\
cursor to home.
catch {unset terminal}
if {[string compare {raw} ${mode}]} {
for {set row ${row_min}} {${row} < ${row_max}} {incr row} {
for {set col ${col_min}} {${col} < ${col_max}} {incr col} {
set terminal(${row},${col}) { }
}
}
}
set row 0
set col 0
}
1 {
# erases the screen from the current line up to the top of the\
screen.
foreach pos [array names *,*] {
if {[lindex [split ${pos} {,}] 0] <= ${row}} {
unset terminal(${pos})
}
}
}
default {
# erases the screen from the current line down to the bottom of\
the screen.
foreach pos [array names *,*] {
if {${row} <= [lindex [split ${pos} {,}] 0]} {
unset terminal(${pos})
}
}
}
}
}
{^\x1b\[[12]K} {
# erase column
regexp -- {^\x1b\[([12])K} ${data} match chr
switch -exact -- ${chr} {
2 {
# erases the entire current line.
foreach pos [array names "[set row],*"] {
set terminal(${pos}) { }
}
}
1 {
# erases from the current cursor position to the start of the\
current line.
foreach pos [array names "[set row],*"] {
if {[lindex [split ${pos} {,}] end] <= ${col}} {
set terminal(${pos}) { }
}
}
}
default {
# erases from the current cursor position to the end of the\
current line.
foreach pos [array names "[set row],*"] {
if {${col} <= [lindex [split ${pos} {,}] end]} {
set terminal(${pos}) { }
}
}
}
}
}
{^\x1b\[\?*[0-9]*[A-Za-z]} {
regexp -- {^\x1b\[\?*[0-9;]*[A-Za-z]} ${data} match
puts stderr "ignore sequence\t'${match}'"
}
{^\x1b[\(\)][0-9A-Za-z]?} {
# set fonts
regexp -- {^\x1b[\(\)][0-9A-Za-z]?} ${data} match
}
{^\x1b} {
# raw escape
regexp -- {^\x1b} ${data} match
}
{^(.+?)\x1b} {
regexp -- {^(.+?)\x1b} ${data} UNUSED match
# handle special escape sequences
regsub -all -- {\\([\\\[\]])} ${match} {\1} raw_match
foreach ch [split ${raw_match} {}] {
if {${col_max} < ${col}} {
if {${linewrap}} {
set col ${col_min}
incr row
} else {
puts stderr "line overrun\t'[string range ${data} 0 20]'"
break
}
}
if {${row_max} < ${row}} {
if {${pagewrap}} {
set row ${row_min}
} else {
puts stderr "page overrun\t'[string range ${data} 0 20]'"
break
}
}
set terminal(${row},${col}) ${ch}
incr col
}
}
default {
set match ${data}
}
}
set data [string range ${data} [string length ${match}] end]
}
set terminal(status) [list ${row} ${col} ${linewrap}]
set res {}
set row_last [lindex [split [lsort -dictionary [array names terminal *,*]] {,}] 0]
foreach pos [lsort -dictionary [array names terminal *,*]] {
set row [lindex [split ${pos} {,}] 0]
if {${listmode} &&(${row} != ${row_last})} {
append res "\n"
set row_last ${row}
}
append res $terminal(${pos})
}
if {${listmode}} {
set res [split ${res} "\n"]
}
return ${res}
}By setting the bounding you are able to change to different sizes of terminals. Using mode will allow to switch between current changes (mode: raw, diff, delta) and new screen (mode: reset) or keeping previous screen (mode: default) making changes there. Of cause erasing screen in last mode will have the same effect as reset mode.Category Example Category Expect

