@ -80,6 +80,12 @@ namespace eval punk::console {
internal::abort_if_loop
internal::abort_if_loop
tailcall enableVirtualTerminal
tailcall enableVirtualTerminal
}
}
proc disableVirtualTerminal {} {
#loopavoidancetoken (don't remove)
internal::define_windows_procs
internal::abort_if_loop
tailcall disableVirtualTerminal
}
} else {
} else {
proc enableAnsi {} {
proc enableAnsi {} {
#todo?
#todo?
@ -232,6 +238,19 @@ namespace eval punk::console {
twapi::SetConsoleMode $h_in $newmode_in
twapi::SetConsoleMode $h_in $newmode_in
return [list stdout [list from $oldmode_out to $newmode_out] stdin [list from $oldmode_in to $newmode_in]]
return [list stdout [list from $oldmode_out to $newmode_out] stdin [list from $oldmode_in to $newmode_in]]
}
}
proc [namespace parent]::disableVirtualTerminal {} {
set h_out [twapi::get_console_handle stdout]
set oldmode_out [twapi::GetConsoleMode $h_out]
set newmode_out [expr {$oldmode_out & ~4}]
twapi::SetConsoleMode $h_out $newmode_out
set h_in [twapi::get_console_handle stdin]
set oldmode_in [twapi::GetConsoleMode $h_in]
set newmode_in [expr {$oldmode_in & ~0x200}]
twapi::SetConsoleMode $h_in $newmode_in
return [list stdout [list from $oldmode_out to $newmode_out] stdin [list from $oldmode_in to $newmode_in]]
}
proc [namespace parent]::enableProcessedInput {} {
proc [namespace parent]::enableProcessedInput {} {
set h_in [twapi::get_console_handle stdin]
set h_in [twapi::get_console_handle stdin]
@ -250,6 +269,27 @@ namespace eval punk::console {
proc [namespace parent]::enableRaw {{channel stdin}} {
proc [namespace parent]::enableRaw {{channel stdin}} {
variable is_raw
#review - change to modify_console_input_mode
#set console_handle [twapi::GetStdHandle -10]
set console_handle [twapi::get_console_handle stdin]
#returns dictionary
#e.g -processedinput 1 -lineinput 1 -echoinput 1 -windowinput 0 -mouseinput 0 -insertmode 1 -quickeditmode 1 -extendedmode 1 -autoposition 0
set oldmode [twapi::get_console_input_mode]
twapi::modify_console_input_mode $console_handle -lineinput 0 -echoinput 0
# Turn off the echo and line-editing bits
#set newmode [dict merge $oldmode [dict create -lineinput 0 -echoinput 0]]
set newmode [twapi::get_console_input_mode]
set is_raw 1
#don't disable handler - it will detect is_raw
### twapi::set_console_control_handler {}
return [list stdin [list from $oldmode to $newmode]]
}
#sometimes gives invalid handle.. (after stdin reopened?)
proc [namespace parent]::enableRaw1 {{channel stdin}} {
variable is_raw
variable is_raw
#review - change to modify_console_input_mode
#review - change to modify_console_input_mode
set console_handle [twapi::GetStdHandle -10]
set console_handle [twapi::GetStdHandle -10]
@ -262,6 +302,19 @@ namespace eval punk::console {
return [list stdin [list from $oldmode to $newmode]]
return [list stdin [list from $oldmode to $newmode]]
}
}
proc [namespace parent]::disableRaw {{channel stdin}} {
proc [namespace parent]::disableRaw {{channel stdin}} {
variable is_raw
set console_handle [twapi::get_console_handle stdin]
set oldmode [twapi::get_console_input_mode]
# Turn on the echo and line-editing bits
twapi::modify_console_input_mode $console_handle -lineinput 1 -echoinput 1
set newmode [twapi::get_console_input_mode]
set is_raw 0
return [list stdin [list from $oldmode to $newmode]]
}
proc [namespace parent]::disableRaw1 {{channel stdin}} {
variable is_raw
variable is_raw
set console_handle [twapi::GetStdHandle -10]
set console_handle [twapi::GetStdHandle -10]
set oldmode [twapi::GetConsoleMode $console_handle]
set oldmode [twapi::GetConsoleMode $console_handle]
@ -289,7 +342,10 @@ namespace eval punk::console {
}
}
}
}
#review - 1 byte at a time seems inefficient..
#review - 1 byte at a time seems inefficient... but we don't have a way to peek or put back chars (?)
#todo - timeout - what if terminal doesn't put data on stdin?
#review - what if we slurp in data meant for main loop? Main loop probably needs to detect these responses and store them for lookup *instead* of this handler
#we may still need this handler if such a loop doesn't exist.
proc ansi_response_handler {chan accumulatorvar waitvar} {
proc ansi_response_handler {chan accumulatorvar waitvar} {
set status [catch {read $chan 1} bytes]
set status [catch {read $chan 1} bytes]
if { $status != 0 } {
if { $status != 0 } {
@ -457,9 +513,14 @@ namespace eval punk::console {
set accumulator ::punk::console::chunk
set accumulator ::punk::console::chunk
set waitvar ::punk::console::chunkdone
set waitvar ::punk::console::chunkdone
set existing_handler [fileevent stdin readable]
set existing_handler [fileevent stdin readable] ;#review!
set $waitvar ""
set $waitvar ""
#todo - test and save rawstate so we don't disableRaw if terminal was already raw
set stdin_state [fconfigure stdin]
#todo - only use own handler if an existing stdin handler not present.. (or console is in line mode)
#todo - test and save rawstate so we don't disableRaw if console was already raw
if {!$::punk::console::is_raw} {
if {!$::punk::console::is_raw} {
set was_raw 0
set was_raw 0
enableRaw
enableRaw
@ -467,8 +528,7 @@ namespace eval punk::console {
set was_raw 1
set was_raw 1
}
}
fconfigure stdin -blocking 0
fconfigure stdin -blocking 0
#review
#
#fconfigure stdin -blocking 0 -inputmode raw
fileevent stdin readable [list ::punk::console::internal::ansi_response_handler stdin $accumulator $waitvar]
fileevent stdin readable [list ::punk::console::internal::ansi_response_handler stdin $accumulator $waitvar]
# - stderr vs stdout
# - stderr vs stdout
@ -479,31 +539,46 @@ namespace eval punk::console {
puts -nonewline stdout \033\[6n ;flush stdout
puts -nonewline stdout \033\[6n ;flush stdout
after 0 {update idletasks}
after 0 {update idletasks}
#response from terminal
#e.g \033\[46;1R
#e.g \033\[46;1R
#todo - reset
#todo - make timeout configurable?
set cancel_timeout_id [after 1500 {set $waitvar timedout}]
set info ""
set info ""
if {[set $waitvar] eq ""} {
if {[set $waitvar] eq ""} {
vwait $waitvar
vwait $waitvar
}
}
if {$waitvar ne "timedout"} {
after cancel $cancel_timeout_id
} else {
return ""
}
if {$was_raw == 0} {
if {$was_raw == 0} {
disableRaw
disableRaw
}
}
#fconfigure stdin -inputmode normal
if {[string length $existing_handler]} {
if {[string length $existing_handler]} {
fileevent stdin readable $existing_handler
fileevent stdin readable $existing_handler
}
}
#response handler automatically removes it's own fileevent
#response handler automatically removes it's own fileevent
#restore stdin state
fconfigure stdin -blocking [dict get $stdin_state -blocking]
set info [set $accumulator]
set info [set $accumulator]
set start [string first \x1b $info]
set start [string first \x1b $info]
if {$start > 0} {
if {$start > 0} {
set other [string range $info 0 $start-1]
set other [string range $info 0 $start-1]
#!!!!! TODO
#!!!!! TODO
# Log this someh were? Work out how to stop it happening?
# Log this somewh ere? Work out how to stop it happening?
#puts stderr "Warning - get_cursor_pos read extra data at start - '$other'"
#puts stderr "Warning - get_cursor_pos read extra data at start - '$other'"
set info [string range $info $start end]
set info [string range $info $start end]
}
}
#set punk::console::chunk ""
#set punk::console::chunk ""
set data [string range $info 2 end-1]
set data [string range $info 2 end-1]
return $data
return $data
@ -523,12 +598,21 @@ namespace eval punk::console {
if {[catch {
if {[catch {
lassign [split [punk::console::get_cursor_pos] ";"] _row1 col1
lassign [split [punk::console::get_cursor_pos] ";"] _row1 col1
} errM]} {
} errM]} {
puts stderr "Cannot test_char_width - may be no console? Error message from get_cursor_pos: $errM"
puts stderr "Cannot test_char_width for '[punk::ansi::ansistring VIEW $char_or_string]' - may be no console? Error message from get_cursor_pos: $errM"
return
}
if {![string is integer -strict $col1]} {
puts stderr "Could not get response from get_cursor_pos"
return
return
}
}
puts -nonewline stdout $char_or_string
puts -nonewline stdout $char_or_string
lassign [split [punk::console::get_cursor_pos] ";"] _row2 col2
lassign [split [punk::console::get_cursor_pos] ";"] _row2 col2
if {![string is integer -strict $col2]} {
puts stderr "Could not get response from get_cursor_pos"
return
}
if {!$emit} {
if {!$emit} {
puts -nonewline stdout \033\[2K\033\[1G
puts -nonewline stdout \033\[2K\033\[1G
}
}
@ -654,19 +738,25 @@ namespace eval punk::console {
}
}
move $orig_row $orig_col
move $orig_row $orig_col
}
}
proc save_cursor {} {
puts -nonewline stdout [punk::ansi::save_cursor]
}
proc restore_cursor {} {
puts -nonewline stdout [punk::ansi::restore_cursor]
}
proc scroll_up {n} {
proc scroll_up {n} {
puts -nonewline stdout [punk::ansi::scroll_up]
puts -nonewline stdout [punk::ansi::scroll_up]
}
}
proc scroll_down {n} {
proc scroll_down {n} {
puts -nonewline stdout [punk::ansi::scroll_down]
puts -nonewline stdout [punk::ansi::scroll_down]
}
}
#review - worth the extra microseconds to inline? might be
#review - worth the extra microseconds to inline? might be if used in for example prompt on every keypress.
#caller should build as much as possible using the punk::ansi versions to avoid extra puts calls
proc save_cursor {} {
#*** !doctools
#[call [fun save_cursor]]
puts -nonewline \x1b\[s
}
proc restore_cursor {} {
#*** !doctools
#[call [fun restore_cursor]]
puts -nonewline \x1b\[u
}
proc insert_spaces {count} {
proc insert_spaces {count} {
puts -nonewline stdout \x1b\[${count}@
puts -nonewline stdout \x1b\[${count}@
}
}
@ -712,7 +802,8 @@ namespace eval punk::console {
#puts -nonewline [punk::ansi::erase_eol]$blanks;move_emit_return this $col $text
#puts -nonewline [punk::ansi::erase_eol]$blanks;move_emit_return this $col $text
#puts -nonewline [move_emit_return this $col [punk::ansi::insert_spaces 150]$text]
#puts -nonewline [move_emit_return this $col [punk::ansi::insert_spaces 150]$text]
save_cursor
save_cursor
move_emit_return this $col [punk::ansi::move_forward 50][punk::ansi::insert_spaces 150][punk::ansi::move_back 50][punk::ansi::move_forward $col]$text
#move_emit_return this $col [punk::ansi::move_forward 50][punk::ansi::insert_spaces 150][punk::ansi::move_back 50][punk::ansi::move_forward $col]$text
puts -nonewline [punk::ansi::insert_spaces 150][punk::ansi::move_column $col]$text
restore_cursor
restore_cursor
}
}
proc move_emit_return {row col data args} {
proc move_emit_return {row col data args} {