diff --git a/src/bootsupport/modules/punk/console-0.1.1.tm b/src/bootsupport/modules/punk/console-0.1.1.tm index 5271bbfb..a5cdb05f 100644 --- a/src/bootsupport/modules/punk/console-0.1.1.tm +++ b/src/bootsupport/modules/punk/console-0.1.1.tm @@ -1522,17 +1522,114 @@ namespace eval punk::console { } #todo - determine cursor on/off state before the call to restore properly. + variable get_size_mechanism + set get_size_mechanism [dict create] + proc get_size {{inoutchannels {stdin stdout}}} { lassign $inoutchannels in out - #we can't reliably use [chan names] for stdin,stdout. There could be stacked channels and they may have a names such as file22fb27fe810 - #chan eof is faster whether chan exists or not than - if {[catch {chan eof $out} is_eof]} { - error "punk::console::get_size output channel $out seems to be closed ([info level 1])" - } else { - if {$is_eof} { - error "punk::console::get_size eof on output channel $out ([info level 1])" + set tried_mechlist [list] + + #fastest mechanism if available - use Tcl's inbuilt -winsize key from chan configure if available - this is much faster than any ANSI mechanism + #unknown which platforms support this. + if {![catch {get_size_using_chanconfigure $inoutchannels} sizedict]} { + return $sizedict + } + lappend tried_mechlist "chanconfigure" + + + variable is_vt52 + if {$is_vt52} { + #vt52 doesn't support cursor save/restore or cursor position reports. + if {![catch {get_size_using_tput $inoutchannels} sizedict]} { + return $sizedict + } + lappend tried_mechlist "tput" + error "can't get console size. Tried mechanisms: $mechlist" + } + + variable get_size_mechanism ;#dict keyed on terminal ident. (currently just list of inoutchannels but may be something else in future such as terminal object or ident string) + if {![dict exists $get_size_mechanism $inoutchannels]} { + set try_order [list] + #call each mechanism once to see if it works - and to ensure we don't include initial run in our timings. + #we will also use the results for our initial return of the size. + set successful_mechs [list] + set sizedict [dict create] + if {![catch {get_size_using_cursorrestore $inoutchannels} result]} { + lappend successful_mechs "cursorrestore" + if {![dict size $sizedict]} { + set sizedict $result + } + } + if {![catch {get_size_using_cursormove $inoutchannels} result]} { + lappend successful_mechs "cursormove" + if {![dict size $sizedict]} { + set sizedict $result + } + } + if {![catch {get_size_using_tput $inoutchannels} result] } { + lappend successful_mechs "tput" + if {![dict size $sizedict]} { + set sizedict $result + } + } + set timings [list] + set t_ms 250 ;#default timerate is 1000ms - we are trying at least 3 mechanisms so 1000ms is a bit long for this test as it can slow down the first call to get_size significantly. + foreach sm $successful_mechs { + catch { + set timing_result [timerate {get_size_using_$sm $inoutchannels} $t_ms] + set micros [expr {int([lindex $timing_result 0])}] + lappend timings [list $micros $sm] + } + } + set sorted [lsort -integer -index 0 $timings] + if {[llength $sorted] > 0} { + set try_order [lmap t $sorted {lindex $t 1}] + } else { + set try_order [list] + } + dict set get_size_mechanism $inoutchannels $try_order + return $sizedict + } + + foreach mech [dict get $get_size_mechanism $inoutchannels] { + if {![catch {get_size_using_$mech $inoutchannels} sizedict]} { + return $sizedict + } + lappend tried_mechlist $mech + } + + #if {![catch {get_size_using_cursorrestore $inoutchannels} sizedict]} { + # return $sizedict + #} + #lappend tried_mechlist "cursorrestore" + + #if {![catch {get_size_using_cursormove $inoutchannels} sizedict]} { + # return $sizedict + #} + #lappend tried_mechlist "cursormove" + + #if {![catch {get_size_using_tput $inoutchannels} sizedict]} { + # return $sizedict + #} + #lappend tried_mechlist "tput" + + error "can't get console size. Tried mechanisms: $tried_mechlist" + } + + proc get_size_using_chanconfigure {{inoutchannels {stdin stdout}}} { + set out [lindex $inoutchannels 1] + set outconf [chan configure $out] + if {[dict exists $outconf -winsize]} { + #this mechanism is much faster than ansi cursor movements + #REVIEW check if any x-platform anomalies with this method? + #can -winsize key exist but contain erroneous info? We will check that we get 2 ints at least + lassign [dict get $outconf -winsize] cols lines + if {[string is integer -strict $cols] && [string is integer -strict $lines]} { + return [dict create columns $cols rows $lines] } } + error "chan configure method of getting console size not supported or failed to get valid size info" + #we don't need to care about the input channel if chan configure on the output can give us the info. #short circuit ansi cursor movement method if chan configure supports the -winsize value set outconf [chan configure $out] @@ -1542,43 +1639,45 @@ namespace eval punk::console { #can -winsize key exist but contain erroneous info? We will check that we get 2 ints at least lassign [dict get $outconf -winsize] cols lines if {[string is integer -strict $cols] && [string is integer -strict $lines]} { - return [list columns $cols rows $lines] + return [dict create columns $cols rows $lines] } #continue on to ansi mechanism if we didn't get 2 ints } - if {[catch {chan eof $in} is_eof]} { - error "punk::console::get_size input channel $in seems to be closed ([info level 1])" + } + + proc get_size_using_tput {{inoutchannels {stdin stdout}}} { + set tputcmd [auto_execok tput] + if {$tputcmd eq ""} { + error "tput command not found - cannot use tput method to get console size" + } + lassign [exec {*}$tputcmd lines cols] lines cols + return [dict create columns $cols rows $lines] + } + + proc get_size_using_cursormove {{inoutchannels {stdin stdout}}} { + set out [lindex $inoutchannels 1] + #we can't reliably use [chan names] for stdin,stdout. There could be stacked channels and they may have a names such as file22fb27fe810 + #chan eof is faster whether chan exists or not than + if {[catch {chan eof $out} is_eof]} { + error "punk::console::get_size_using_cursormove output channel $out seems to be closed ([info level 1])" } else { if {$is_eof} { - error "punk::console::get_size eof on input channel $in ([info level 1])" + error "punk::console::get_size_using_cursormove eof on output channel $out ([info level 1])" } } - #keep out of catch - no point in even trying a restore move if we can't get start position - just fail here. - #no vt52 equiv? may as well strip all vt52 from here? lassign [get_cursor_pos_list $inoutchannels] start_row start_col - variable is_vt52 - if {!$is_vt52} { - set movefunc "punk::ansi::move" - set func_coff "punk::ansi::cursor_off" - set func_con "punk::ansi::cursor_on" - } else { - set movefunc "punk::ansi::vt52move" - set func_coff "punk::ansi::vt52cursor_off" - set func_con "punk::ansi::vt52cursor_on" - } if {[catch { #some terminals (conemu on windows) scroll the viewport when we make a big move down like this - a move to 1 1 immediately after cursor_save doesn't seem to fix that. #This issue also occurs when switching back from the alternate screen buffer - so perhaps that needs to be addressed elsewhere. - - puts -nonewline $out [$func_coff][$movefunc 2000 2000] + puts -nonewline $out [punk::ansi::cursor_off][punk::ansi::move 2000 2000] lassign [get_cursor_pos_list $inoutchannels] lines cols - puts -nonewline $out [$movefunc $start_row $start_col][$func_con];flush stdout - set result [list columns $cols rows $lines] + puts -nonewline $out [punk::ansi::move $start_row $start_col][punk::ansi::cursor_on];flush stdout + set result [dict create columns $cols rows $lines] } errM]} { - puts -nonewline $out [$movefunc $start_row $start_col] - puts -nonewline $out [$func_con] + puts -nonewline $out [punk::ansi::move $start_row $start_col] + puts -nonewline $out [punk::ansi::cursor_on] error "$errM" } else { return $result @@ -1586,14 +1685,16 @@ namespace eval punk::console { } #faster than get_size when it is using ansi mechanism - but uses cursor_save - which we may want to avoid if calling during another operation which uses cursor save/restore - proc get_size_cursorrestore {{inoutchannels {stdin stdout}}} { + proc get_size_using_cursorrestore {{inoutchannels {stdin stdout}}} { lassign $inoutchannels in out - #we use the same shortcircuit mechanism as get_size to avoid ansi at all if the output channel will give us the info directly set outconf [chan configure $out] - if {[dict exists $outconf -winsize]} { - lassign [dict get $outconf -winsize] cols lines - if {[string is integer -strict $cols] && [string is integer -strict $lines]} { - return [list columns $cols rows $lines] + #don't use shortcut mechanisms - this function is intended to specificall use the cursor_save/restore method + + if {[catch {chan eof $out} is_eof]} { + error "punk::console::get_size_using_cursorrestore output channel $out seems to be closed ([info level 1])" + } else { + if {$is_eof} { + error "punk::console::get_size_using_cursorrestore eof on output channel $out ([info level 1])" } } @@ -1603,7 +1704,7 @@ namespace eval punk::console { puts -nonewline $out [punk::ansi::cursor_off][punk::ansi::cursor_save_dec][punk::ansi::move 2000 2000] lassign [get_cursor_pos_list $inoutchannels] lines cols puts -nonewline $out [punk::ansi::cursor_restore][punk::console::cursor_on];flush $out - set result [list columns $cols rows $lines] + set result [dict create columns $cols rows $lines] } errM]} { puts -nonewline $out [punk::ansi::cursor_restore_dec] puts -nonewline $out [punk::ansi::cursor_on] @@ -1612,10 +1713,15 @@ namespace eval punk::console { return $result } } + + proc get_dimensions {{inoutchannels {stdin stdout}}} { lassign [get_size $inoutchannels] _c cols _l lines return "${cols}x${lines}" } + + + #the (xterm?) CSI 18t query is supported by *some* terminals proc get_xterm_size {{inoutchannels {stdin stdout}}} { set capturingregex {(.*)(\x1b\[8;([0-9]+;[0-9]+)t)$} ;#must capture prefix,entire-response,response-payload diff --git a/src/modules/punk/console-999999.0a1.0.tm b/src/modules/punk/console-999999.0a1.0.tm index 090e3ec8..1745316b 100644 --- a/src/modules/punk/console-999999.0a1.0.tm +++ b/src/modules/punk/console-999999.0a1.0.tm @@ -1522,17 +1522,114 @@ namespace eval punk::console { } #todo - determine cursor on/off state before the call to restore properly. + variable get_size_mechanism + set get_size_mechanism [dict create] + proc get_size {{inoutchannels {stdin stdout}}} { lassign $inoutchannels in out - #we can't reliably use [chan names] for stdin,stdout. There could be stacked channels and they may have a names such as file22fb27fe810 - #chan eof is faster whether chan exists or not than - if {[catch {chan eof $out} is_eof]} { - error "punk::console::get_size output channel $out seems to be closed ([info level 1])" - } else { - if {$is_eof} { - error "punk::console::get_size eof on output channel $out ([info level 1])" + set tried_mechlist [list] + + #fastest mechanism if available - use Tcl's inbuilt -winsize key from chan configure if available - this is much faster than any ANSI mechanism + #unknown which platforms support this. + if {![catch {get_size_using_chanconfigure $inoutchannels} sizedict]} { + return $sizedict + } + lappend tried_mechlist "chanconfigure" + + + variable is_vt52 + if {$is_vt52} { + #vt52 doesn't support cursor save/restore or cursor position reports. + if {![catch {get_size_using_tput $inoutchannels} sizedict]} { + return $sizedict + } + lappend tried_mechlist "tput" + error "can't get console size. Tried mechanisms: $mechlist" + } + + variable get_size_mechanism ;#dict keyed on terminal ident. (currently just list of inoutchannels but may be something else in future such as terminal object or ident string) + if {![dict exists $get_size_mechanism $inoutchannels]} { + set try_order [list] + #call each mechanism once to see if it works - and to ensure we don't include initial run in our timings. + #we will also use the results for our initial return of the size. + set successful_mechs [list] + set sizedict [dict create] + if {![catch {get_size_using_cursorrestore $inoutchannels} result]} { + lappend successful_mechs "cursorrestore" + if {![dict size $sizedict]} { + set sizedict $result + } + } + if {![catch {get_size_using_cursormove $inoutchannels} result]} { + lappend successful_mechs "cursormove" + if {![dict size $sizedict]} { + set sizedict $result + } + } + if {![catch {get_size_using_tput $inoutchannels} result] } { + lappend successful_mechs "tput" + if {![dict size $sizedict]} { + set sizedict $result + } + } + set timings [list] + set t_ms 250 ;#default timerate is 1000ms - we are trying at least 3 mechanisms so 1000ms is a bit long for this test as it can slow down the first call to get_size significantly. + foreach sm $successful_mechs { + catch { + set timing_result [timerate {get_size_using_$sm $inoutchannels} $t_ms] + set micros [expr {int([lindex $timing_result 0])}] + lappend timings [list $micros $sm] + } + } + set sorted [lsort -integer -index 0 $timings] + if {[llength $sorted] > 0} { + set try_order [lmap t $sorted {lindex $t 1}] + } else { + set try_order [list] + } + dict set get_size_mechanism $inoutchannels $try_order + return $sizedict + } + + foreach mech [dict get $get_size_mechanism $inoutchannels] { + if {![catch {get_size_using_$mech $inoutchannels} sizedict]} { + return $sizedict + } + lappend tried_mechlist $mech + } + + #if {![catch {get_size_using_cursorrestore $inoutchannels} sizedict]} { + # return $sizedict + #} + #lappend tried_mechlist "cursorrestore" + + #if {![catch {get_size_using_cursormove $inoutchannels} sizedict]} { + # return $sizedict + #} + #lappend tried_mechlist "cursormove" + + #if {![catch {get_size_using_tput $inoutchannels} sizedict]} { + # return $sizedict + #} + #lappend tried_mechlist "tput" + + error "can't get console size. Tried mechanisms: $tried_mechlist" + } + + proc get_size_using_chanconfigure {{inoutchannels {stdin stdout}}} { + set out [lindex $inoutchannels 1] + set outconf [chan configure $out] + if {[dict exists $outconf -winsize]} { + #this mechanism is much faster than ansi cursor movements + #REVIEW check if any x-platform anomalies with this method? + #can -winsize key exist but contain erroneous info? We will check that we get 2 ints at least + lassign [dict get $outconf -winsize] cols lines + if {[string is integer -strict $cols] && [string is integer -strict $lines]} { + return [dict create columns $cols rows $lines] } } + error "chan configure method of getting console size not supported or failed to get valid size info" + #we don't need to care about the input channel if chan configure on the output can give us the info. #short circuit ansi cursor movement method if chan configure supports the -winsize value set outconf [chan configure $out] @@ -1542,43 +1639,45 @@ namespace eval punk::console { #can -winsize key exist but contain erroneous info? We will check that we get 2 ints at least lassign [dict get $outconf -winsize] cols lines if {[string is integer -strict $cols] && [string is integer -strict $lines]} { - return [list columns $cols rows $lines] + return [dict create columns $cols rows $lines] } #continue on to ansi mechanism if we didn't get 2 ints } - if {[catch {chan eof $in} is_eof]} { - error "punk::console::get_size input channel $in seems to be closed ([info level 1])" + } + + proc get_size_using_tput {{inoutchannels {stdin stdout}}} { + set tputcmd [auto_execok tput] + if {$tputcmd eq ""} { + error "tput command not found - cannot use tput method to get console size" + } + lassign [exec {*}$tputcmd lines cols] lines cols + return [dict create columns $cols rows $lines] + } + + proc get_size_using_cursormove {{inoutchannels {stdin stdout}}} { + set out [lindex $inoutchannels 1] + #we can't reliably use [chan names] for stdin,stdout. There could be stacked channels and they may have a names such as file22fb27fe810 + #chan eof is faster whether chan exists or not than + if {[catch {chan eof $out} is_eof]} { + error "punk::console::get_size_using_cursormove output channel $out seems to be closed ([info level 1])" } else { if {$is_eof} { - error "punk::console::get_size eof on input channel $in ([info level 1])" + error "punk::console::get_size_using_cursormove eof on output channel $out ([info level 1])" } } - #keep out of catch - no point in even trying a restore move if we can't get start position - just fail here. - #no vt52 equiv? may as well strip all vt52 from here? lassign [get_cursor_pos_list $inoutchannels] start_row start_col - variable is_vt52 - if {!$is_vt52} { - set movefunc "punk::ansi::move" - set func_coff "punk::ansi::cursor_off" - set func_con "punk::ansi::cursor_on" - } else { - set movefunc "punk::ansi::vt52move" - set func_coff "punk::ansi::vt52cursor_off" - set func_con "punk::ansi::vt52cursor_on" - } if {[catch { #some terminals (conemu on windows) scroll the viewport when we make a big move down like this - a move to 1 1 immediately after cursor_save doesn't seem to fix that. #This issue also occurs when switching back from the alternate screen buffer - so perhaps that needs to be addressed elsewhere. - - puts -nonewline $out [$func_coff][$movefunc 2000 2000] + puts -nonewline $out [punk::ansi::cursor_off][punk::ansi::move 2000 2000] lassign [get_cursor_pos_list $inoutchannels] lines cols - puts -nonewline $out [$movefunc $start_row $start_col][$func_con];flush stdout - set result [list columns $cols rows $lines] + puts -nonewline $out [punk::ansi::move $start_row $start_col][punk::ansi::cursor_on];flush stdout + set result [dict create columns $cols rows $lines] } errM]} { - puts -nonewline $out [$movefunc $start_row $start_col] - puts -nonewline $out [$func_con] + puts -nonewline $out [punk::ansi::move $start_row $start_col] + puts -nonewline $out [punk::ansi::cursor_on] error "$errM" } else { return $result @@ -1586,14 +1685,16 @@ namespace eval punk::console { } #faster than get_size when it is using ansi mechanism - but uses cursor_save - which we may want to avoid if calling during another operation which uses cursor save/restore - proc get_size_cursorrestore {{inoutchannels {stdin stdout}}} { + proc get_size_using_cursorrestore {{inoutchannels {stdin stdout}}} { lassign $inoutchannels in out - #we use the same shortcircuit mechanism as get_size to avoid ansi at all if the output channel will give us the info directly set outconf [chan configure $out] - if {[dict exists $outconf -winsize]} { - lassign [dict get $outconf -winsize] cols lines - if {[string is integer -strict $cols] && [string is integer -strict $lines]} { - return [list columns $cols rows $lines] + #don't use shortcut mechanisms - this function is intended to specificall use the cursor_save/restore method + + if {[catch {chan eof $out} is_eof]} { + error "punk::console::get_size_using_cursorrestore output channel $out seems to be closed ([info level 1])" + } else { + if {$is_eof} { + error "punk::console::get_size_using_cursorrestore eof on output channel $out ([info level 1])" } } @@ -1603,7 +1704,7 @@ namespace eval punk::console { puts -nonewline $out [punk::ansi::cursor_off][punk::ansi::cursor_save_dec][punk::ansi::move 2000 2000] lassign [get_cursor_pos_list $inoutchannels] lines cols puts -nonewline $out [punk::ansi::cursor_restore][punk::console::cursor_on];flush $out - set result [list columns $cols rows $lines] + set result [dict create columns $cols rows $lines] } errM]} { puts -nonewline $out [punk::ansi::cursor_restore_dec] puts -nonewline $out [punk::ansi::cursor_on] @@ -1612,10 +1713,15 @@ namespace eval punk::console { return $result } } + + proc get_dimensions {{inoutchannels {stdin stdout}}} { lassign [get_size $inoutchannels] _c cols _l lines return "${cols}x${lines}" } + + + #the (xterm?) CSI 18t query is supported by *some* terminals proc get_xterm_size {{inoutchannels {stdin stdout}}} { set capturingregex {(.*)(\x1b\[8;([0-9]+;[0-9]+)t)$} ;#must capture prefix,entire-response,response-payload