From dd2bfa76c517189055131ed820396602990bd51e Mon Sep 17 00:00:00 2001 From: Julian Noble Date: Tue, 6 Feb 2024 06:59:33 +1100 Subject: [PATCH] raw mode repl fixes, ansi fixes --- src/modules/punk-0.1.tm | 5 +- src/modules/punk/ansi-999999.0a1.0.tm | 69 +++++++-- src/modules/punk/char-999999.0a1.0.tm | 23 ++- src/modules/punk/console-999999.0a1.0.tm | 125 +++++++++++++--- src/modules/punk/lib-999999.0a1.0.tm | 19 ++- .../mix/commandset/loadedlib-999999.0a1.0.tm | 15 +- src/modules/punk/mix/util-999999.0a1.0.tm | 4 + src/modules/punk/repl-0.1.tm | 133 +++++++++++++----- src/modules/punk/repo-999999.0a1.0.tm | 3 + 9 files changed, 308 insertions(+), 88 deletions(-) diff --git a/src/modules/punk-0.1.tm b/src/modules/punk-0.1.tm index ffe16808..2e62740d 100644 --- a/src/modules/punk-0.1.tm +++ b/src/modules/punk-0.1.tm @@ -6972,11 +6972,12 @@ namespace eval punk { proc mode {raw_or_line} { set raw_or_line [string tolower $raw_or_line] if {$raw_or_line eq "raw"} { - punk::console::enableVirtualTerminal punk::console::enableRaw + punk::console::enableVirtualTerminal } elseif {$raw_or_line eq "line"} { + #review -order. disableRaw has memory from enableRaw.. but but for line mode we want vt disabled - so call it after disableRaw (?) punk::console::disableRaw - #vt disable? + punk::console::disableVirtualTerminal } else { error "punk::mode expected 'raw' or 'line' } diff --git a/src/modules/punk/ansi-999999.0a1.0.tm b/src/modules/punk/ansi-999999.0a1.0.tm index 064c146e..7ddf415a 100644 --- a/src/modules/punk/ansi-999999.0a1.0.tm +++ b/src/modules/punk/ansi-999999.0a1.0.tm @@ -598,12 +598,13 @@ namespace eval punk::ansi { proc move_column {col} { #*** !doctools #[call [fun move_column] [arg col]] - return \x1b\[${col}g + return \x1b\[${col}G } proc move_row {row} { #*** !doctools #[call [fun move_row] [arg row]] - return \x1b\[${row}G + #[para]VPA - Vertical Line Position Absolute + return \x1b\[${row}d } # -- --- --- --- --- @@ -686,6 +687,22 @@ namespace eval punk::ansi { return \033\[6n } + proc request_cursor_information {} { + #*** !doctools + #[call [fun request_cursor_information]] + #[para]DECRQPSR (DEC Request Presentation State Report) for DECCCIR Cursor Information report + #[para]When written to the terminal, this sequence causes the terminal to emit cursor information to stdin + #[para]A stdin readloop will need to be in place to read this information + return \x1b\[1\$w + } + proc request_tabstops {} { + #*** !doctools + #[call [fun request_tabstops]] + #[para]DECRQPSR (DEC Request Presentation State Report) for DECTABSR Tab stop report + #[para]When written to the terminal, this sequence causes the terminal to emit tabstop information to stdin + return \x1b\[2\$w + } + #alternative to string terminator is \007 - proc titleset {windowtitle} { @@ -1254,7 +1271,7 @@ namespace eval punk::ansi::ansistring { APC [list \x9f \ue03f]\ ] #it turns out we need pretty much everything for debugging - set visuals [dict create\ + set visuals_c0 [dict create\ NUL [list \x00 \u2400]\ SOH [list \x01 \u2401]\ STX [list \x02 \u2402]\ @@ -1282,41 +1299,71 @@ namespace eval punk::ansi::ansistring { RS [list \x1e \u241e]\ US [list \x1f \u241f]\ DEL [list \x7f \u2421]\ + ] + set visuals_c1 [dict create\ + BPH [list \x82 \ue022]\ + NBH [list \x83 \ue023]\ + IND [list \x84 \ue024]\ + NEL [list \x85 \ue025]\ + SSA [list \x86 \ue026]\ + ESA [list \x87 \ue027]\ + HTS [list \x88 \ue028]\ + HTJ [list \x89 \ue029]\ + VTS [list \x8a \ue02a]\ + PLD [list \x8b \ue02a]\ + PLU [list \x8c \ue02c]\ + RI [list \x8d \ue02d]\ + SS2 [list \x8e \ue02e]\ + SS3 [list \x8f \ue02f]\ + DCS [list \x90 \ue030]\ + PU1 [list \x91 \ue031]\ + PU2 [list \x92 \ue032]\ + STS [list \x93 \ue033]\ + CCH [list \x94 \ue034]\ + MW [list \x95 \ue035]\ + SPA [list \x96 \ue036]\ + EPA [list \x97 \ue037]\ SOS [list \x98 \ue038]\ + SCI [list \x9a \ue03a]\ CSI [list \x9b \ue03b]\ ST [list \x9c \ue03c]\ + OSC [list \x9d \ue03d]\ PM [list \x9e \ue03e]\ APC [list \x9f \ue03f]\ ] + + set visuals_opt [dict create] if {$opt_esc} { - dict set visuals VT [list \x1b \u241b] + dict set visuals_opt ESC [list \x1b \u241b] } if {$opt_cr} { - dict set visuals CR [list \x0d \u240d] + dict set visuals_opt CR [list \x0d \u240d] } if {$opt_lf} { - dict set visuals LF [list \x0a \u240a] + dict set visuals_opt LF [list \x0a \u240a] } if {$opt_vt} { - dict set visuals VT [list \x0b \u240b] + dict set visuals_opt VT [list \x0b \u240b] } if {$opt_ht} { - dict set visuals HT [list \x09 \u2409] + dict set visuals_opt HT [list \x09 \u2409] } if {$opt_bs} { - dict set visuals BS [list \x08 \u2408] + dict set visuals_opt BS [list \x08 \u2408] } if {$opt_sp} { - dict set visuals SP [list \x20 \u2420] + dict set visuals_opt SP [list \x20 \u2420] } + set visuals [dict merge $visuals_opt $visuals_c0 $visuals_c1] set charmap [list] dict for {nm chars} $visuals { lappend charmap {*}$chars } return [string map $charmap $string] - #ISO2047 - 7bit - limited set, limited support + + #test of ISO2047 - 7bit - limited set, limited support, somewhat obscure glyphs #return [string map [list \033 \U2296 \007 \U237E] $string] } diff --git a/src/modules/punk/char-999999.0a1.0.tm b/src/modules/punk/char-999999.0a1.0.tm index 237df9ad..7cdd7a2d 100644 --- a/src/modules/punk/char-999999.0a1.0.tm +++ b/src/modules/punk/char-999999.0a1.0.tm @@ -1796,12 +1796,21 @@ namespace eval punk::char { #review - what about \r \t \b ? proc string_width {text} { #review is detecting \033 enough? what about 8-bit escapes? + if {[string first \n $text] >= 0} { error "string_width accepts only a single line" } - if {[string first \033 $text] >= 0} { - error "string_width doesn't accept ansi escape sequences. Use punk::ansi::stripansi first" - } + + + #we can c0 control characters after or while processing ansi escapes. + #we need to map remaining control characters to zero-width (under assumption we're not using a font/codepage that displays them - review!) + #anyway - we must allow things like raw ESC,DEL, NUL etc to pass through without error + #if {[string first \033 $text] >= 0} { + # error "string_width doesn't accept ansi escape sequences. Use punk::ansi::stripansi first" + #} + set re_ascii_c0 {[\U0000-\U001F]} + set text [regsub -all $re_ascii_c0 $text ""] + #todo - check double-width chars in unicode blocks.. try to do reasonably quicky #short-circuit basic cases if {![regexp {[\uFF-\U10FFFF]} $text]} { @@ -1858,13 +1867,13 @@ namespace eval punk::char { #This shouldn't be called on text containing ansi codes! proc strip_nonprinting_ascii {str} { - #review - some single-byte 'control' chars have visual representations e.g ETX as heart + #review - some single-byte 'control' chars have visual representations e.g ETX as heart depending on font/codepage #It is currently used for screen display width calculations #equivalent for various unicode combining chars etc? set map [list\ - \007 ""\ - [format %c 0] ""\ - [format %c 0x7f] ""\ + \x00 ""\ + \x07 ""\ + \x7f ""\ ] return [string map $map $str] } diff --git a/src/modules/punk/console-999999.0a1.0.tm b/src/modules/punk/console-999999.0a1.0.tm index c7fe0db8..e6c69d68 100644 --- a/src/modules/punk/console-999999.0a1.0.tm +++ b/src/modules/punk/console-999999.0a1.0.tm @@ -80,6 +80,12 @@ namespace eval punk::console { internal::abort_if_loop tailcall enableVirtualTerminal } + proc disableVirtualTerminal {} { + #loopavoidancetoken (don't remove) + internal::define_windows_procs + internal::abort_if_loop + tailcall disableVirtualTerminal + } } else { proc enableAnsi {} { #todo? @@ -232,6 +238,19 @@ namespace eval punk::console { 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]::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 {} { set h_in [twapi::get_console_handle stdin] @@ -250,6 +269,27 @@ namespace eval punk::console { 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 #review - change to modify_console_input_mode set console_handle [twapi::GetStdHandle -10] @@ -262,6 +302,19 @@ namespace eval punk::console { return [list stdin [list from $oldmode to $newmode]] } 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 set console_handle [twapi::GetStdHandle -10] 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} { set status [catch {read $chan 1} bytes] if { $status != 0 } { @@ -457,9 +513,14 @@ namespace eval punk::console { set accumulator ::punk::console::chunk set waitvar ::punk::console::chunkdone - set existing_handler [fileevent stdin readable] + set existing_handler [fileevent stdin readable] ;#review! 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} { set was_raw 0 enableRaw @@ -467,8 +528,7 @@ namespace eval punk::console { set was_raw 1 } fconfigure stdin -blocking 0 - #review - #fconfigure stdin -blocking 0 -inputmode raw + # fileevent stdin readable [list ::punk::console::internal::ansi_response_handler stdin $accumulator $waitvar] # - stderr vs stdout @@ -479,31 +539,46 @@ namespace eval punk::console { puts -nonewline stdout \033\[6n ;flush stdout after 0 {update idletasks} + + + #response from terminal #e.g \033\[46;1R - #todo - reset + + #todo - make timeout configurable? + set cancel_timeout_id [after 1500 {set $waitvar timedout}] + set info "" if {[set $waitvar] eq ""} { vwait $waitvar } + if {$waitvar ne "timedout"} { + after cancel $cancel_timeout_id + } else { + return "" + } + if {$was_raw == 0} { disableRaw } - #fconfigure stdin -inputmode normal if {[string length $existing_handler]} { fileevent stdin readable $existing_handler } #response handler automatically removes it's own fileevent + #restore stdin state + fconfigure stdin -blocking [dict get $stdin_state -blocking] + set info [set $accumulator] set start [string first \x1b $info] if {$start > 0} { set other [string range $info 0 $start-1] #!!!!! TODO - # Log this somehwere? Work out how to stop it happening? + # Log this somewhere? Work out how to stop it happening? #puts stderr "Warning - get_cursor_pos read extra data at start - '$other'" set info [string range $info $start end] } + #set punk::console::chunk "" set data [string range $info 2 end-1] return $data @@ -523,12 +598,21 @@ namespace eval punk::console { if {[catch { lassign [split [punk::console::get_cursor_pos] ";"] _row1 col1 } 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 } puts -nonewline stdout $char_or_string 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} { puts -nonewline stdout \033\[2K\033\[1G } @@ -654,19 +738,25 @@ namespace eval punk::console { } 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} { puts -nonewline stdout [punk::ansi::scroll_up] } proc scroll_down {n} { 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} { 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 [move_emit_return this $col [punk::ansi::insert_spaces 150]$text] 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 } proc move_emit_return {row col data args} { diff --git a/src/modules/punk/lib-999999.0a1.0.tm b/src/modules/punk/lib-999999.0a1.0.tm index 56c9461b..09ec4e3b 100644 --- a/src/modules/punk/lib-999999.0a1.0.tm +++ b/src/modules/punk/lib-999999.0a1.0.tm @@ -488,9 +488,11 @@ namespace eval punk::lib { proc askuser {question} { #*** !doctools #[call [fun askuser] [arg question]] - #[para]A very basic utility to read an answer from stdin + #[para]A basic utility to read an answer from stdin #[para]The prompt is written to the terminal and then it waits for a user to type something #[para]stdin is temporarily configured to blocking and then put back in its original state in case it wasn't already so. + #[para]If the terminal is using punk::console and is in raw mode - the terminal will temporarily be put in line mode. + #[para](Generic terminal raw vs linemode detection not yet present) #[para]The user must hit enter to submit the response #[para]The return value is the string if any that was typed prior to hitting enter. #[para]The question argument can be manually colourised using the various punk::ansi funcitons @@ -505,9 +507,22 @@ namespace eval punk::lib { puts stdout $question flush stdout set stdin_state [fconfigure stdin] + if {[catch { + package require punk::console + set console_raw [set ::punk::console::is_raw] + } err_console]} { + #assume normal line mode + set console_raw 0 + } try { fconfigure stdin -blocking 1 - set answer [gets stdin] + if {$console_raw} { + punk::console::disableRaw + set answer [gets stdin] + punk::console::enableRaw + } else { + set answer [gets stdin] + } } finally { fconfigure stdin -blocking [dict get $stdin_state -blocking] } diff --git a/src/modules/punk/mix/commandset/loadedlib-999999.0a1.0.tm b/src/modules/punk/mix/commandset/loadedlib-999999.0a1.0.tm index 97602c7f..e2c8e425 100644 --- a/src/modules/punk/mix/commandset/loadedlib-999999.0a1.0.tm +++ b/src/modules/punk/mix/commandset/loadedlib-999999.0a1.0.tm @@ -18,6 +18,7 @@ ## Requirements ##e.g package require frobz package require punk::ns +package require punk::lib @@ -463,11 +464,8 @@ namespace eval punk::mix::commandset::loadedlib { puts stdout "---" puts stdout "$loadinfo" puts stdout "---" - puts stdout "Proceed to create ${pkgtail}-${ver}.tm module? Y|N" - set stdin_state [fconfigure stdin] - fconfigure stdin -blocking 1 - set answer [string tolower [gets stdin]] - fconfigure stdin -blocking [dict get $stdin_state -blocking] + set question "Proceed to create ${pkgtail}-${ver}.tm module? Y|N" + set answer [punk::lib::askuser $question] ;#takes account of previous stdin state and terminal raw vs line state if {$answer ne "y"} { puts stderr "mix libcopy.asmodule aborting due to user response '$answer' (required Y|y to proceed) use -askme 0 to avoid prompts." return @@ -486,11 +484,8 @@ namespace eval punk::mix::commandset::loadedlib { if {[file exists $target_path]} { puts stdout "WARNING - module already exists at $target_path" if {$opt_askme} { - puts stdout "Copy anyway? Y|N" - set stdin_state [fconfigure stdin] - fconfigure stdin -blocking 1 - set answer [string tolower [gets stdin]] - fconfigure stdin -blocking [dict get $stdin_state -blocking] + set question "Copy anyway? Y|N" + set answer [punk::lib::askuser $question] if {$answer ne "y"} { puts stderr "mix libcopy.asmodule aborting due to user response '$answer' (required Y|y to proceed) use -askme 0 to avoid prompts." return diff --git a/src/modules/punk/mix/util-999999.0a1.0.tm b/src/modules/punk/mix/util-999999.0a1.0.tm index 8b369758..09707d98 100644 --- a/src/modules/punk/mix/util-999999.0a1.0.tm +++ b/src/modules/punk/mix/util-999999.0a1.0.tm @@ -182,6 +182,9 @@ namespace eval punk::mix::util { } proc askuser {question} { + if {![catch {package require punk::lib}]} { + return [punk::lib::askuser $question] ;#takes account of terminal mode raw vs line (if punk::console used) + } puts stdout $question flush stdout set stdin_state [fconfigure stdin] @@ -191,6 +194,7 @@ namespace eval punk::mix::util { return $answer } + #review - can be surprising if caller unaware it uses try proc do_in_path {path script} { #from ::kettle::path::in set here [pwd] diff --git a/src/modules/punk/repl-0.1.tm b/src/modules/punk/repl-0.1.tm index 4aabc028..c6fe1e21 100644 --- a/src/modules/punk/repl-0.1.tm +++ b/src/modules/punk/repl-0.1.tm @@ -146,8 +146,10 @@ if {$::tcl_platform(platform) eq "windows"} { incr ::repl::signal_control_c #rputs stderr "* console_control: $args" if {$::punk::console::is_raw} { - #how to let rawmode loop handle it? It doesn't seem to get through - return 0 + #how to let rawmode loop handle it? It doesn't seem to get through if we return 0 + puts stderr "ctrl-c while in raw mode" + flush stderr + return 42 } #note - returning 0 means pass event to other handlers including OS default handler if {$::repl::signal_control_c <= 2} { @@ -652,6 +654,7 @@ proc repl::doprompt {prompt {col {green bold}}} { } #this sort of works - but steals some of our stdin data ? review + # #lassign [punk::console::get_cursor_pos_list] column row #if {$row != 1} { # set c "\n" @@ -1101,11 +1104,11 @@ proc repl::repl_handler {inputchan prompt_config} { while {[string length [set chunk [read $inputchan 1024]]] >= 0 && $lc < $linemax & $numreads < $maxreads} { set chunklen [string length $chunk] if {$chunklen > 0} { - set info1 "chunk $chunklen bytes->[ansistring VIEW -lf 1 -vt 1 $chunk]" + set info1 "read $chunklen bytes->[ansistring VIEW -lf 1 -vt 1 $chunk]" #it's strange - but apparently terminals use a lone cr to represent enter #You can insert an lf using ctrl-j - and of course stdin could have crlf or lf #pasting from notepad++ with mixed line endings seems to paste everything ok - #we don't really know the source of input - and whether a read has potentially chopped a crl in half.. + #we don't really know the source of input keyboard vs paste vs pipe - and whether a read has potentially chopped a crl in half.. #possibly no real way to determine that. We could wait a small time to see if there's more data coming.. and potentially impact performance. #Instead we'll try to make sense of it here. @@ -1124,19 +1127,42 @@ proc repl::repl_handler {inputchan prompt_config} { #could be a sequence of cr's from holding enter key } + #esc or ctrl-lb + if {$chunk eq "\x1b"} { + #return + set readingchunk "" + set stdinlines [list "\x1b"] + set commandstr "" + set chunk "" + screen_last_char_add \x1b stdin escape + break + } + if {$chunk eq "\x1b\[D"} { + rputs stderr "${debugprompt}arrow-left D" + #set commandstr "" + #punk::console::move_back 1 + } + #if we get just ctrl-c in one chunk + #ctrl-c if {$chunk eq "\x03"} { - ::repl::term::handler_console_control "ctrl-c_via_rawloop" + #::repl::term::handler_console_control "ctrl-c_via_rawloop" return } #for now - exit with small delay for tidyup + #ctrl-z if {$chunk eq "\x1a"} { - ::repl::term::handler_console_control "ctrl-z_via_rawloop" + #::repl::term::handler_console_control "ctrl-z_via_rawloop" + punk::mode line after 1000 exit return } - #try to brutally terminate process + #ctrl-bslash if {$chunk eq "\x1c"} { + #try to brutally terminate process + #attempt to leave terminal in a reasonable state + punk::mode line + after 200 exit 42 } append readingchunk $chunk @@ -1184,8 +1210,50 @@ proc repl::repl_handler {inputchan prompt_config} { set lastoutchar "" set lasterrchar "" - set pad [string repeat " " [string length $line]] - set line [overtype::renderline $pad $line] + + #consider \x1b as text on console vs \x1b the character + #review - if we're getting these actual escape characters in line mode.. something is off - let's emit something instead of trying to interpret as a command and failing. + #This tends to happen when some sort of readline not avaialbe ie on unix or mintty in windows + #this only captures leading escape.. as an aid to diagnosis e.g won't be caught and the user will need to close the right bracket to complete the bogus command + #we may need to think about legitimate raw escapes in commands e.g from pipes or script files, vs via console? + + #esc key or ctrl-lb followed by enter + if {$line eq "\x1b"} { + #abort current command + if {$linenum == 0} { + doprompt "E% " {yellow bold} + #screen_last_char_add " " empty empty + } else { + doprompt "\nE% " {yellow bold} + #screen_last_char_add "\n" empty empty ;#add \n to indicate noclearance required + } + incr linenum + continue + } else { + if {$line eq "\x1b\[C"} { + rputs stderr "${debugprompt}arrow-right C" + #set commandstr "" + } + if {$line eq "\x1b\[D"} { + #rputs stderr "${debugprompt}arrow-left D" + #set commandstr "" + #punk::console::move_back 1 + } + if {$line eq "\x1b\[A"} { + rputs stderr "${debugprompt}arrow-up A" + } + if {$line eq "\x1b\[B"} { + rputs stderr "arrow-down B" + } + if {[string match "\x1b*" $line]} { + rputs stderr "${debugprompt}esc - '[punk::ansi::ansistring::VIEW $line]'" + #set commandstr [punk::ansi::stripansi $commandstr] + } + } + + if {$commandstr ne ""} { + append commandstr \n + } set stdinconf [fconfigure stdin] if {$::tcl_platform(platform) eq "windows" && [dict get $stdinconf -encoding] ni [list unicode utf-16]} { @@ -1201,49 +1269,32 @@ proc repl::repl_handler {inputchan prompt_config} { #puts "--stdin> [fconfigure stdin]" - append commandstr $line - #puts "1=============>[string length $commandstr] bytes , [string map [list \r -r- \n -n-] $commandstr] , info complete:[info complete $line]" + + append commandstr $line + puts "1=============>[string length $commandstr] bytes , [ansistring VIEW $commandstr] , info complete:[info complete $line]" set commandstr [string range $commandstr 0 end-3] - set commandstr [encoding convertfrom utf-16be $commandstr] ;#This is weird - but it seemt to be big endian! + set commandstr [encoding convertfrom utf-16be $commandstr] ;#This is weird - but it seems to be big endian? set commandstr [string trimright $commandstr] #puts "2=============>[string length $commandstr] bytes , [string map [list \r -r- \n -n-] $commandstr] , info complete:[info complete $line]" - append commandstr \n } else { #append commandstr $line #puts "0=============>[string length $commandstr] bytes , [string map [list \r -r- \n -n-] $commandstr] , info complete:[info complete $line]" - append commandstr $line\n + append commandstr $line } #puts "=============>[string length $commandstr] bytes , [string map [list \r -r- \n -n-] $commandstr] , info complete:[info complete $line]" set ::repl::last_repl_char "\n" ;#this is actually the eol from stdin screen_last_char_add "\n" stdin $line - #consider \x1b as text on console vs \x1b the character - #review - if we're getting these actual escape characters in line mode.. something is off - let's emit something instead of trying to interpret as a command and failing. - #This tends to happen when some sort of readline not avaialbe ie on unix or mintty in windows - #this only captures leading escape.. as an aid to diagnosis e.g won't be caught and the user will need to close the right bracket to complete the bogus command - #we may need to think about legitimate raw escapes in commands e.g from pipes or script files, vs via console? - if {$commandstr eq "\x1b\[C\n"} { - rputs stderr "${debugprompt}arrow-right C" - set commandstr "" - } - if {$commandstr eq "\x1b\[D\n"} { - #rputs stderr "${debugprompt}arrow-left D" - #set commandstr "" - punk::console::move_back 1 - } - if {$commandstr eq "\x1b\[A\n"} { - rputs stderr "${debugprompt}arrow-up A" - set commandstr "" - } - if {$commandstr eq "\x1b\[B\n"} { - rputs stderr "arrow-down B" - } - if {[string match "\x1b*" $commandstr]} { - rputs stderr "${debugprompt}esc - '[punk::ansi::ansistring::VIEW $commandstr]'" - set commandstr [punk::ansi::stripansi $commandstr] - } + + + #append commandstr \n + + if {[info complete $commandstr]} { + #set commandstr [overtype::renderline -overflow 1 "" $commandstr] + + set ::repl::output_stdout "" set ::repl::output_stderr "" set outstack [list] @@ -1346,6 +1397,7 @@ proc repl::repl_handler {inputchan prompt_config} { if {$weirdns} { uplevel 1 {punk::ns::nseval $punk::ns::ns_current $run_command_string} } else { + #puts stderr "--> [ansistring VIEW -lf 1 -vt 1 $run_command_string] <--" uplevel 1 {namespace inscope $punk::ns::ns_current $run_command_string} } } raw_result] @@ -1651,6 +1703,9 @@ proc repl::repl_handler {inputchan prompt_config} { } repl_error]} { puts stderr "error in repl_handler: $repl_error" + puts stderr "-------------" + puts stderr "$::errorInfo" + puts stderr "-------------" set stdinreader [fileevent $inputchan readable] if {![string length $stdinreader]} { puts stderr "*> stdin reader inactive" diff --git a/src/modules/punk/repo-999999.0a1.0.tm b/src/modules/punk/repo-999999.0a1.0.tm index 656ce478..a2560387 100644 --- a/src/modules/punk/repo-999999.0a1.0.tm +++ b/src/modules/punk/repo-999999.0a1.0.tm @@ -139,6 +139,9 @@ namespace eval punk::repo { } proc askuser {question} { + if {![catch {package require punk::lib}]} { + return [punk::lib::askuser $question] ;#takes account of punk::console raw vs line + } puts stdout $question flush stdout set stdin_state [fconfigure stdin]