You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2976 lines
135 KiB
2976 lines
135 KiB
|
|
#punk linerepl |
|
#todo - make repls configurable/pluggable packages |
|
|
|
|
|
#list/string-rep bug |
|
global run_commandstr "" |
|
|
|
set stdin_info [chan configure stdin] |
|
if {[dict exists $stdin_info -inputmode]} { |
|
#this is the only way I currently know to detect console on windows.. doesn't work on Alma linux. |
|
# tcl_interactive used by repl to determine if stderr output prompt to be printed. |
|
# (that way, piping commands into stdin should not produce prompts for each command) |
|
set tcl_interactive 1 |
|
} |
|
#however, the -mode option only seems to appear on linux when a terminal exists.. |
|
if {[dict exists $stdin_info -mode]} { |
|
set tcl_interactive 1 |
|
} |
|
#give up for now |
|
set tcl_interactive 1 |
|
|
|
|
|
|
|
|
|
|
|
|
|
package require Thread |
|
package require shellfilter |
|
#package require shellrun |
|
#package require punk |
|
package require punk::lib |
|
package require punk::aliascore |
|
if {[catch {punk::aliascore::init} errM]} { |
|
puts stderr "punk::aliascore::init error: $errM" |
|
} |
|
package require punk::config |
|
package require punk::ns |
|
package require punk::ansi |
|
package require punk::console |
|
package require textblock |
|
|
|
|
|
|
|
|
|
|
|
if {![info exists ::env(SHELL)]} { |
|
set ::env(SHELL) punk86 |
|
} |
|
if {![info exists ::env(TERM)]} { |
|
# tset -r seems to rely on env(TERM) - so this doesn't seem to work |
|
#if {![catch {exec tset -r} result]} { |
|
# #e.g Terminal type is xterm-256color. |
|
# set t [string trimright [lindex $result end] .] |
|
# set ::env(TERM) $t |
|
#} else { |
|
#fake it ? |
|
#set ::env(TERM) vt100 |
|
set ::env(TERM) xterm-256color |
|
#} |
|
} |
|
|
|
#todo - move to less generic namespace ie punk::repl |
|
namespace eval repl { |
|
variable codethread |
|
if {![info exists codethread]} { |
|
set codethread "" |
|
} |
|
variable codethread_cond |
|
|
|
variable screen_last_chars "" ;#a small sliding append buffer for last char of any screen output to detect \n vs string |
|
variable screen_last_char_list [list] |
|
|
|
#variable last_unknown "" |
|
tsv::set repl last_unknown "" |
|
variable output "" |
|
#important not to initialize - as it can be preset by cooperating package before app-punk has been package required |
|
#(this is an example of a deaddrop) |
|
variable post_script |
|
} |
|
|
|
namespace eval punk::repl { |
|
tsv::set repl runid 0 |
|
tsv::set repl runchunks-0 [list] ;#last_run_display |
|
|
|
|
|
variable debug_repl 0 |
|
variable signal_control_c 0 |
|
variable signal_control_c_msg "" |
|
variable prompt_reset_flag 0 ;#trigger repl to re-retrieve prompt settings |
|
|
|
proc todo {} { |
|
puts "tcl History" |
|
puts "repltelemetry package" |
|
puts "deaddrop package for a consistent way for modules to leave small notes to others that may come later." |
|
} |
|
|
|
#since we are targeting Tcl 8.6+ - we should be using 'interp bgerror .' |
|
#todo - make optional/configurable? |
|
proc bgerror2 {args} { |
|
puts stderr "====================" |
|
puts stderr "punk::repl::bgerror" |
|
puts stderr "====================" |
|
puts stderr "[lindex $args 0]" |
|
puts stderr "-------------------" |
|
puts stderr "[lrange $args 1 end]" |
|
puts stderr "====================" |
|
puts stderr "^^^^^^^^^^^^^^^^^^^" |
|
} |
|
proc bgerror {args} { |
|
set message [lindex $args 0] |
|
set errdict [lindex $args 1] |
|
puts stderr "\n*> repl background error: '$message'" |
|
#puts stderr "*> [set ::errorInfo]" |
|
puts stderr "*> errorinfo: [dict get $errdict -errorinfo]" |
|
set stdinreader [fileevent stdin readable] |
|
if {![string length $stdinreader]} { |
|
puts stderr "*> stdin reader inactive" |
|
} else { |
|
puts stderr "*> stdin reader active" |
|
} |
|
flush stderr |
|
} |
|
|
|
if {![llength [info commands ::bgerror]]} { |
|
#interp alias {} bgerror {} ::punk::repl::bgerror |
|
} |
|
interp bgerror "" ::punk::repl::bgerror |
|
|
|
} |
|
namespace eval repl { |
|
|
|
} |
|
|
|
proc ::punk::repl::init_signal_handlers {} { |
|
if {$::tcl_platform(platform) eq "windows"} { |
|
#puts stdout "===============repl loading twapi===========" |
|
if {![catch {package require twapi}]} { |
|
|
|
#If script launched with windows batch file - we have to be careful to stop a ctrl-c from eventually reaching the batch file when the program terminates, even if fully handled here. |
|
#This is done from within the launching batch file |
|
proc ::punk::repl::handler_console_control {args} { |
|
variable signal_control_c |
|
flush stderr |
|
variable signal_control_c_msg |
|
switch -- [lindex $args 0] { |
|
ctrl-c { |
|
#puts stderr "->event $args" |
|
flush stderr |
|
incr signal_control_c |
|
#rputs stderr "* console_control: $args" |
|
if {$::punk::console::is_raw} { |
|
if {[lindex $::errorCode 0] eq "CHILDKILLED"} { |
|
#rputs stderr "\n|repl> ctrl-c errorCode: $::errorCode" |
|
#avoid spurious triggers after interrupting a command.. |
|
#review - dodgy.. we just want to interrupt child processes but then still be able to interrupt repl |
|
set ::punk::repl::signal_control_c 0 |
|
set preverr [string map {"child killed" "child_killed"} $::errorInfo] |
|
catch {error $preverr} ;#for errorInfo display |
|
return 42 |
|
} else { |
|
#how to let rawmode loop handle it? It doesn't seem to get through if we return 0 |
|
#puts stderr "signal ctrl-c while in raw mode" |
|
#flush stderr |
|
set signal_control_c_msg "signal ctrl-c $signal_control_c rawmode" |
|
if {[catch { |
|
lassign [punk::console::get_size] _w console_width _h console_height |
|
} errM]} { |
|
puts stderr "signal ctrl-c error get_size error:$errM" |
|
} |
|
|
|
if {$signal_control_c < 3} { |
|
set remaining [expr {3 - $signal_control_c}] |
|
if {[catch { |
|
punk::repl::console_controlnotification "[a+ web-orange]ctrl-c ($remaining more to quit, enter to continue)[a]" $console_width $console_height |
|
} errM]} { |
|
puts stderr "signal ctrl-c error console_controlnotification error:$errM" |
|
} |
|
} elseif {$signal_control_c == 3} { |
|
#puts stderr "signal ctrl-c x3 received - quitting." |
|
if {[catch { |
|
punk::repl::console_controlnotification "ctrl-c x3 received - quitting punk shell" $console_width $console_height |
|
} errM]} { |
|
puts stderr "signal ctrl-c error console_controlnotification error:$errM" |
|
} |
|
flush stderr |
|
after 25 |
|
quit |
|
return 1 |
|
} elseif {$signal_control_c > 5} { |
|
#fallback if quit didn't work |
|
#puts stderr "signal ctrl-c $signal_control_c received - sending to default handler" |
|
if {[catch { |
|
punk::repl::console_controlnotification "ctrl-c $signal_control_c received - sending to default handler" $console_width $console_height |
|
} errM]} { |
|
puts stderr "signal ctrl-c error console_controlnotification error:$errM" |
|
} |
|
flush stderr |
|
punk::console::mode line |
|
return 0 |
|
} |
|
|
|
return 1 |
|
#after 200 {exit 42} ;#temp |
|
#return 42 |
|
} |
|
} |
|
|
|
|
|
if {[lindex $::errorCode 0] eq "CHILDKILLED"} { |
|
set signal_control_c 0 |
|
set preverr [string map {"child killed" "child_killed"} $::errorInfo] |
|
catch {error $preverr} ;#for errorInfo display |
|
return 42 |
|
} |
|
if {[catch { |
|
lassign [punk::console::get_size] _w console_width _h console_height |
|
} errM]} { |
|
puts stderr "signal ctrl-c error get_size error:$errM" |
|
} |
|
|
|
#note - returning 0 means pass event to other handlers including OS default handler |
|
if {$signal_control_c <= 2} { |
|
set remaining [expr {3 - $signal_control_c}] |
|
#puts stderr "signal ctrl-c (perform $remaining more to quit, enter to return to repl)" |
|
#flush stderr |
|
if {[catch { |
|
punk::repl::console_controlnotification "ctrl-c ($remaining more to quit, enter to continue)" $console_width $console_height |
|
} errM]} { |
|
puts stderr "signal ctrl-c error console_controlnotification error:$errM" |
|
} |
|
return 1 |
|
} elseif {$signal_control_c == 3} { |
|
#puts stderr "signal ctrl-c x3 received - quitting." |
|
if {[catch { |
|
punk::repl::console_controlnotification "ctrl-c x3 received - quitting punk shell" $console_width $console_height |
|
} errM]} { |
|
puts stderr "signal ctrl-c error console_controlnotification error:$errM" |
|
} |
|
flush stderr |
|
after 25 |
|
quit |
|
return 1 |
|
} elseif {$signal_control_c == 4} { |
|
puts stderr "signal ctrl-c x4 received - one more to hard exit" |
|
flush stderr |
|
return 1 |
|
} elseif {$signal_control_c >= 5} { |
|
#a script that allows events to be processed could still be running |
|
puts stderr "signal ctrl-c x5 received - hard exit" |
|
flush stderr |
|
after 25 |
|
exit 499 ;# HTTP 'client closed request' - just for the hell of it. |
|
} else { |
|
puts stderr "signal ctrl-c $signal_control_c received" |
|
flush stderr |
|
#return 0 to fall through to default handler |
|
return 0 |
|
} |
|
|
|
} |
|
default { |
|
puts stderr "unhandled console signal $args" |
|
return 1 |
|
} |
|
} |
|
} |
|
twapi::set_console_control_handler ::punk::repl::handler_console_control |
|
#we can't yet emit from an event with proper prompt handling - |
|
#repl::rputs stdout "twapi loaded" |
|
} else { |
|
#repl::rputs stderr " Failed to load twapi" |
|
} |
|
} else { |
|
#TODO |
|
} |
|
} |
|
|
|
#console handler may already be set, but in another thread/interp - so we can't use existence of proc to test |
|
#we're ok with an existing handler - just catch for now. REVIEW we should make sure it didn't fail the first time |
|
catch {punk::repl::init_signal_handlers} |
|
|
|
|
|
# moved to punk package.. |
|
#set outdevice [shellfilter::stack::new punkout -settings [list -tag "punkout" -buffering none -raw 1 -syslog [dict get $::punk::config::running syslog_stdout] -file [dict get $::punk::config::running logfile_stdout]]] |
|
#set out [dict get $outdevice localchan] |
|
#set errdevice [shellfilter::stack::new punkerr -settings [list -tag "punkerr" -buffering none -raw 1 -syslog [dict get $::punk::config::running syslog_stderr] -file [dict get $::punk::config::running logfile_stderr]]] |
|
#set err [dict get $errdevice localchan] |
|
|
|
|
|
|
|
# |
|
#set indevice [shellfilter::stack::new commandin -settings {-tag "commandin" -readbuffering line -writebuffering none -raw 1 -direction in}] |
|
#set program_read_stdin_pipe [dict get $indevice localchan] |
|
|
|
|
|
|
|
|
|
|
|
proc punk::repl::reset_prompt {} { |
|
variable prompt_reset_flag |
|
set prompt_reset_flag 1 |
|
} |
|
|
|
#aliases c and clear to this by ::punk |
|
proc punk::repl::reset_terminal {} { |
|
set prompt_reset_flag 1 |
|
#clear ;#call to external executable which may not be available |
|
puts -nonewline stdout [::punk::ansi::reset] |
|
} |
|
|
|
proc punk::repl::get_prompt_config {} { |
|
if {$::tcl_interactive} { |
|
set RST [a] |
|
set resultprompt "[a green bold]-$RST " |
|
set nlprompt "[a green bold].$RST " |
|
set infoprompt "[a green bold]*$RST " |
|
set debugprompt "[a purple bold]~$RST " |
|
} else { |
|
set resultprompt "" |
|
set nlprompt "" |
|
set infoprompt "" |
|
set debugprompt "" |
|
} |
|
return [list resultprompt $resultprompt nlprompt $nlprompt infoprompt $infoprompt debugprompt $debugprompt] |
|
} |
|
|
|
proc repl::start {inchan args} { |
|
puts stderr "-->repl::start $inchan $args" |
|
variable codethread |
|
#review |
|
if {$codethread eq ""} { |
|
error "start - no codethread. call init first. (options -safe 0|1)" |
|
} |
|
variable commandstr |
|
|
|
# --- |
|
variable editbuf |
|
variable editbuf_list ;#command history |
|
variable editbuf_linenum_submitted |
|
variable editbuf_active_index |
|
# --- |
|
|
|
variable reading |
|
variable done |
|
set done 0 |
|
variable startinstance |
|
variable loopinstance |
|
if {[namespace exists ::punkapp]} { |
|
#review - document ? |
|
if {[dict exists $args -defaultresult]} { |
|
set ::punkapp::default_result [dict get $args -defaultresult] |
|
} |
|
} |
|
incr startinstance |
|
set loopinstance 0 |
|
thread::send $codethread { |
|
#set ::punk::repl::codethread::running 1 |
|
|
|
#the interp in which commands such as d/ run |
|
#we need to namespace eval for the -safe interp which may not have the packages loaded (or be able to) but still needs default values |
|
#punk::repl::codethread::running is required whether safe or not. |
|
interp eval code { |
|
namespace eval ::punk::repl::codethread {} |
|
set ::punk::repl::codethread::running 1 |
|
namespace eval ::punk::ns::ns_current {} |
|
set ::punk::ns::ns_current :: |
|
} |
|
} |
|
set commandstr "" |
|
|
|
# --- |
|
set editbuf [punk::repl::class::class_editbuf new {}] |
|
lappend editbuf_list $editbuf ;#current editbuf is always in the history |
|
set editbuf_linenum_submitted 0 |
|
set editbuf_active_index 0 |
|
# --- |
|
|
|
if {$::punk::console::ansi_wanted == 2} { |
|
if {[::punk::console::test_can_ansi]} { |
|
set ::punk::console::ansi_wanted 1 |
|
} else { |
|
set ::punk::console::ansi_wanted -1 |
|
} |
|
} |
|
set prompt_config [punk::repl::get_prompt_config] |
|
doprompt "P% " |
|
fileevent $inchan readable [list [namespace current]::repl_handler $inchan $prompt_config] |
|
set reading 1 |
|
|
|
catch { |
|
#set punk::console::tabwidth [punk::console::get_tabstop_apparent_width] |
|
} |
|
vwait [namespace current]::done |
|
fileevent $inchan readable {} |
|
|
|
|
|
#puts stderr "-->start done = $::repl::done" |
|
#todo - override exit? |
|
#after 0 ::repl::post_operations |
|
after idle ::repl::post_operations |
|
vwait repl::post_operations_done |
|
#puts stderr "-->start post_operations_done = $::repl::post_operations_done" |
|
if {[namespace exists ::punkapp]} { |
|
#todo check and get punkapp::result array - but what key? |
|
if {[info exists ::punkapp::result(shell)]} { |
|
set temp $::punkapp::result(shell) |
|
unset ::punkapp::result(shell) |
|
return $temp |
|
} elseif {[info exists ::punkapp::default_result]} { |
|
set temp $::punkapp::default_result |
|
unset ::punkapp::default_result |
|
return $temp |
|
} |
|
} |
|
|
|
|
|
variable codethread_cond |
|
if {[catch { |
|
tsv::unset codethread_$codethread |
|
} errM]} { |
|
puts stderr " repl::start temp warning - $errM" |
|
} |
|
thread::cancel $codethread |
|
thread::cond destroy $codethread_cond ;#race if we destroy cond before child thread has exited - as it can send a -async quit |
|
set codethread "" |
|
set codethread_cond "" |
|
punk::console::mode line ;#review - revert to line mode on final exit - but we may be exiting a nested repl |
|
puts "end repl::start" |
|
return 0 |
|
} |
|
proc repl::post_operations {} { |
|
if {[info exists ::repl::post_script] && [string length $::repl::post_script]} { |
|
#put aside post_script so the script has the option to add another post_script and restart the repl |
|
set ::repl::running_script $::repl::post_script |
|
set ::repl::post_script "" |
|
uplevel #0 {eval $::repl::running_script} |
|
} |
|
#todo - tidyup so repl could be restarted |
|
set ::repl::post_operations_done 0 |
|
} |
|
|
|
|
|
proc repl::reopen_stdin {} { |
|
#variable reopen_stdin_attempts |
|
if {$::tcl_platform(platform) eq "windows"} { |
|
puts stderr "|repl> Attempting reconnection of console to stdin by opening 'CON'" |
|
} else { |
|
puts stderr "|repl> Attempting reconnection of console to stdin by opening '/dev/tty'" |
|
} |
|
#puts stderr "channels:[chan names]" |
|
#flush stderr |
|
#catch {chan close stdin} |
|
chan close stdin |
|
|
|
if {$::tcl_platform(platform) eq "windows"} { |
|
#set s [open "CON" r] |
|
set s [open {CONIN$} r] |
|
if {[package provide twapi] ne ""} { |
|
set h [twapi::get_tcl_channel_handle $s in] |
|
twapi::SetStdHandle -10 $h |
|
} |
|
puts stderr "restarting repl on inputchannel:$s" |
|
return [repl::start $s -title "reopen_stdin a"] |
|
} else { |
|
#/dev/tty - reference to the controlling terminal for a process |
|
#review/test |
|
set s [open "/dev/tty" r] |
|
} |
|
|
|
repl::start stdin -title "reopen_stdin b" |
|
} |
|
|
|
#todo - avoid putting this in gobal namespace? |
|
#collisions with other libraries apps? |
|
proc punk::repl::quit {args} { |
|
set ::repl::done "quit {*}$args" |
|
#puts stderr "quit called" |
|
return "" ;#make sure to return nothing so "quit" doesn't land on stdout |
|
} |
|
|
|
#just a failed experiment.. tried various things |
|
proc repl::reopen_stdinX {} { |
|
#windows - todo unix |
|
package require twapi |
|
|
|
if 0 { |
|
if {[catch {package require Memchan} errM]} { |
|
#package require tcl::chan::fifo2 |
|
#lassign [tcl::chan::fifo2] a b |
|
package require tcl::chan::fifo |
|
set x [tcl::chan::fifo] |
|
} else { |
|
#lassign [fifo2] a b |
|
set x [fifo] |
|
} |
|
#first channel opened after stdin closed becomes stdin |
|
#use a fifo or fifo2 because [chan pipe] assigns the wrong end first! |
|
#a will be stdin |
|
} |
|
#these can't replace proper stdin (filehandle 0) because they're not 'file backed' or 'os level' |
|
#try opening a named pipe server to become stdin |
|
set pipename {\\.\pipe\stdin_%id%} |
|
set pipename [string map [list %id% [pid]] $pipename] |
|
|
|
|
|
|
|
package require tcl::chan::fifo |
|
|
|
chan close stdin |
|
lassign [tcl::chan::fifo] a |
|
|
|
|
|
puts stderr "newchan: $a" |
|
puts stderr "|test> $a [chan conf $a]" |
|
|
|
#set server [twapi::namedpipe_server $pipename] |
|
#set client [twapi::namedpipe_client $pipename] ;#open a client and connect to the server we just made |
|
|
|
puts stderr "chan names: [chan names]" |
|
|
|
#by now $server not valid? |
|
#set server stdin |
|
|
|
#chan configure $server -buffering line -encoding unicode |
|
#chan configure $client -buffering line -encoding unicode |
|
|
|
#puts stderr "|test>ns-server $server [chan conf $server]" |
|
#puts stderr "|test>ns-client $client [chan conf $client]" |
|
|
|
set conin [twapi::get_console_handle stdin] |
|
twapi::set_standard_handle stdin $conin |
|
|
|
set h_in [twapi::get_standard_handle stdin] |
|
|
|
puts stderr "|test> $a [chan conf $a]" |
|
|
|
#chan configure $client -blocking 0 |
|
after 2 repl::start $a |
|
|
|
} |
|
|
|
#add to sliding buffer of last x chars emmitted to screen by repl |
|
#(we could maintain only one char - more kept merely for debug assistance) |
|
#will not detect emissions from exec with stdout redirected and presumably some extensions etc |
|
proc repl::screen_last_char_add {c what {why ""}} { |
|
variable screen_last_chars |
|
variable screen_last_char_list |
|
if {![string length $c]} { |
|
return [string index $screen_last_chars end] |
|
} |
|
if {[string length $screen_last_chars] > 10} { |
|
set screen_last_chars [string range $screen_last_chars 1 end] ;#evict first char |
|
set screen_last_char_list [lrange $screen_last_char_list 1 end] |
|
} |
|
append screen_last_chars $c |
|
lappend screen_last_char_list [list $c $what $why] |
|
#return [string index $screen_last_chars end] |
|
#return [lindex $screen_last_char_list 0 0] |
|
return [lindex $screen_last_char_list end 0] |
|
} |
|
proc repl::screen_last_char_get {} { |
|
variable screen_last_char_list |
|
return [lindex $screen_last_char_list end 0] |
|
} |
|
proc repl::screen_last_char_getinfo {} { |
|
variable screen_last_char_list |
|
return [lindex $screen_last_char_list end] |
|
} |
|
|
|
#-------------------------------------- |
|
#another experiment |
|
proc repl::newout {} { |
|
namespace eval ::replout { |
|
namespace ensemble create -map { |
|
initialize init |
|
finalize close |
|
watch watch |
|
write write |
|
} |
|
} |
|
proc ::replout::init {id mode} { |
|
return {initialize finalize watch write} |
|
} |
|
proc ::replout::close {id} { |
|
|
|
} |
|
proc ::replout::watch {id spec} { |
|
|
|
} |
|
proc ::replout::write {id data} { |
|
puts -nonewline stderr $data |
|
return [string length $data] |
|
} |
|
|
|
close stdout |
|
set fd [chan create write ::replout] |
|
chan configure $fd -buffering none |
|
return $fd |
|
} |
|
interp alias {} newout {} repl::newout |
|
proc repl::newout2 {} { |
|
close stdout |
|
set s [open "CON" w] |
|
chan configure $s -buffering none |
|
} |
|
#-------------------------------------- |
|
|
|
proc repl::doprompt {prompt {col {green bold}}} { |
|
#prompt to stderr. |
|
#We can pipe commands into repl's stdin without the prompt interfering with the output. |
|
#Although all command output for each line goes to stdout - not just what is emmited with puts |
|
|
|
if {$::tcl_interactive} { |
|
set last_char_info [screen_last_char_getinfo] |
|
if {![llength $last_char_info]} { |
|
set needs_clearance 1 |
|
} else { |
|
lassign $last_char_info c what why |
|
if {$why eq "prompt"} { |
|
set needs_clearance 0 |
|
} else { |
|
set needs_clearance [screen_needs_clearance] |
|
#puts -nonewline "-->$needs_clearance $last_char_info" |
|
} |
|
} |
|
if {$needs_clearance == 1} { |
|
set c \n |
|
} else { |
|
set c "" |
|
} |
|
set pre "" |
|
if {[string first \n $prompt] >=0} { |
|
set plines [split $prompt \n] |
|
set pre [join [lrange $plines 0 end-1] \n]\n |
|
set prompt [lindex $plines end] |
|
} |
|
|
|
#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" |
|
#} |
|
|
|
set o [a {*}$col] |
|
set r [a] |
|
puts -nonewline stderr $c$pre$o$prompt$r |
|
screen_last_char_add " " "prompt-stderr" prompt |
|
flush stderr |
|
} |
|
} |
|
|
|
#use rputs in repl_handler instead of puts |
|
# - to help ensure we don't emit extra blank lines in info or debug output |
|
#rputs expects the standard tcl 'puts' command to be in place. |
|
# all bets are off if this has been redefined with some other api |
|
# rputs deliberately doesn't check screen_last_chars before emitting data (unless reporting an error in rputs itself) |
|
proc repl::rputs {args} { |
|
variable screen_last_chars |
|
variable last_out_was_newline |
|
variable last_repl_char |
|
|
|
set pseudo_map [dict create\ |
|
debug stderr\ |
|
debugreport stderr\ |
|
] |
|
|
|
if {[::tcl::mathop::<= 1 [llength $args] 3]} { |
|
set out [lindex $args end] |
|
append out ""; #copy on write |
|
if {([llength $args] > 1) && [lindex $args 0] ne "-nonewline"} { |
|
set this_tail \n |
|
set rputschan [lindex $args 0] |
|
#map pseudo-channels to real |
|
if {$rputschan in [dict keys $pseudo_map]} { |
|
lset args 0 [dict get $pseudo_map $rputschan] |
|
} |
|
} elseif {[llength $args] == 1} { |
|
set this_tail \n |
|
set rputschan "stdout" |
|
} else { |
|
#>1 arg with -nonewline |
|
set this_tail [string index $out end] |
|
set rputschan [lindex $args 1] |
|
#map pseudo-channels to real |
|
if {$rputschan in [dict keys $pseudo_map]} { |
|
lset args 0 [dict get $pseudo_map $rputschan] |
|
} |
|
} |
|
set last_char_info_width 60 |
|
#review - string shouldn't be truncated prior to stripcodes - could chop ansi codes! |
|
#set summary "[::shellfilter::ansi::stripcodes [string range $out 0 $last_char_info_width]]" |
|
set out_plain_text [punk::ansi::ansistrip $out] |
|
set summary [string range $out_plain_text 0 $last_char_info_width] |
|
if {[string length $summary] > $last_char_info_width} { |
|
append summary " ..." |
|
} |
|
|
|
#make sure we use supplied rputschan in the screen_las_char_add 'what' - which may not be the underlying chan if it was a pseudo |
|
screen_last_char_add $this_tail repl-$rputschan $summary |
|
|
|
try { |
|
puts {*}$args |
|
} on error {repl_error erropts} { |
|
#possible error depending on -encoding and -profile of the channel |
|
#(e.g -profile strict) |
|
|
|
#REVIEW |
|
#TODO - something better |
|
#failure case: |
|
#set x \ud83c\udf1e |
|
#(2 surrogate pairs - treated as single char in tcl8 - fixed in 9 but won't/can't be backported) - |
|
#see also: https://core.tcl-lang.org/tips/doc/trunk/tip/619.md |
|
puts stderr "$repl_error" |
|
} |
|
} else { |
|
#looks like an invalid puts call - use the normal error produced by the puts command |
|
#This should only occur if the repl itself is being rewritten/debugged, |
|
#so we will use red "!" and not worry about the extra newlines before and after |
|
if {[catch { puts {*}$args } err]} { |
|
set c [a yellow bold] |
|
set n [a] |
|
#possibly non punk-compliant output because we're assuming the repl was the most recent emitter |
|
#could be wrong, in which case we may emit an extra newline |
|
#- shouldn't matter in this case |
|
#set last_char [string range $screen_last_chars end] |
|
set last_char [screen_last_char_get] |
|
if {$last_char eq "\n"} { |
|
set clear "" |
|
} else { |
|
set clear "\n" |
|
} |
|
puts -nonewline stderr "$clear[a red bold]! REPL ERROR IN rputs $c$err$n\n" |
|
screen_last_char_add "\n" replerror "rputs err: '$err'" |
|
return |
|
} else { |
|
#?? shouldn't happen with standard puts command |
|
#do our best and assume final arg is still the data being emitted |
|
#worst that will happen is we won't detect a trailing newline and will later emit an extra blank line. |
|
set out [lindex $args end] |
|
set this_tail [string index $out end] |
|
screen_last_char_add $this_tail replunknown "rputs $args" |
|
return |
|
} |
|
} |
|
} |
|
#whether we need a newline as clearance from previous output |
|
#review - race with copy pasted data, hold-down of enter key |
|
# and data from external process or background script that doesn't go through our stdout filter |
|
#we probably can't use get_cursor_pos - as that needs to emit to stdout and read-loop on stdin which will possibly? make things worse |
|
proc repl::screen_needs_clearance {} { |
|
variable screen_last_chars |
|
|
|
#set last_char [string index $screen_last_chars end] |
|
set last_char_info [screen_last_char_getinfo] |
|
if {![llength $last_char_info]} { |
|
#assumption |
|
return 1 |
|
} |
|
lassign $last_char_info c what why |
|
switch -- $what { |
|
stdout - stderr - stdout/stderr { |
|
return 1 |
|
} |
|
} |
|
return [expr {$c ne "\n"}] |
|
} |
|
|
|
namespace eval repl { |
|
variable startinstance 0 |
|
variable loopinstance 0 |
|
variable in_repl_handler [list] |
|
variable last_controlc_count 0 |
|
} |
|
|
|
namespace eval punk::repl::class { |
|
oo::class create class_bufman { |
|
|
|
} |
|
|
|
#multiline editing buffer |
|
oo::class create class_editbuf { |
|
variable o_context |
|
variable o_config |
|
|
|
variable o_rendered_lines |
|
variable o_remaining ;#? |
|
|
|
#o_chunk_list & o_chunk_info should make timed viewing of replays possible |
|
variable o_chunk_list |
|
variable o_chunk_info ;#arrival timing etc |
|
variable o_cursor_row |
|
variable o_cursor_col |
|
variable o_insert_mode |
|
constructor {configdict {contextdict {}}} { |
|
my clear |
|
set o_config $configdict |
|
if {[dict exists $configdict rendered_initialchunk]} { |
|
#pre-rendered initial chunk |
|
#-- |
|
set o_chunk_list "" ;#replace empty chunk from 'clear' call |
|
set o_chunk_info [dict create] |
|
#-- |
|
set ch [dict get $configdict rendered_initialchunk] |
|
my add_rendered_chunk $ch |
|
} |
|
|
|
set o_context $contextdict |
|
#error "[self class].constructor Unable to interpret config '$o_config'" |
|
} |
|
method cursor_row {} { |
|
return $o_cursor_row |
|
} |
|
method cursor_column {} { |
|
return $o_cursor_col |
|
} |
|
method insert_mode {} { |
|
return $o_insert_mode |
|
} |
|
method clear {} { |
|
set o_rendered_lines [list ""] |
|
set o_chunk_list [list] |
|
set o_chunk_info [dict create] |
|
set o_cursor_row 1 |
|
set o_cursor_col 1 |
|
set o_insert_mode 1 ;#default to insert mode |
|
lappend o_chunk_list "" |
|
dict set o_chunk_info 0 [dict create micros [clock microseconds] type rendered] |
|
} |
|
method add_chunk {chunk} { |
|
#we still split on lf - but each physical line may contain horizontal or vertical movements so we need to feed each line in and possibly get an overflow_right and unapplied and cursor-movent return info |
|
lappend o_chunk_list $chunk ;#may contain newlines,horizontal/vertical movements etc - all ok |
|
dict set o_chunk_info [expr {[llength $o_chunk_list] -1}] [dict create micros [clock microseconds] type raw] |
|
if {$chunk eq ""} { |
|
return |
|
} |
|
|
|
set firstnl [string first \n $chunk] |
|
set newparts [split $chunk \n] |
|
#attempt to render new 'lines' into the editbuffer - taking account of active cursor row & col & insertmode |
|
|
|
#merge based on current cursor row and col |
|
#set lastrline [lindex $o_rendered_lines end] |
|
#set n0 [lindex $newparts 0] |
|
#set merged0 [string cat $lastrline $n0] |
|
|
|
#we should merge first row of newparts differently in case our chunks split a grapheme-combination? |
|
# |
|
if {$o_cursor_row < 1} { |
|
puts stderr "add_chunk warning cursor_row < 1 - changing to minimum value 1" |
|
set o_cursor_row 1 |
|
} |
|
set cursor_row_idx [expr {$o_cursor_row -1}] |
|
set activeline [lindex $o_rendered_lines $cursor_row_idx] |
|
set new0 [lindex $newparts 0] |
|
#set combined [string cat $activeline $new0] |
|
#use -cursor_row to tell renderline it's row context. |
|
if {$firstnl >=0} { |
|
#append combined \n |
|
append new0 \n |
|
} |
|
set underlay [punk::ansi::ansistrip $activeline] |
|
set line_nextchar_col [expr {[punk::char::string_width $underlay] + 1}] |
|
if {$o_cursor_col > $line_nextchar_col} { |
|
set o_cursor_col $line_nextchar_col |
|
} |
|
|
|
set mergedinfo [overtype::renderline -info 1 -expand_right 1 -insert_mode $o_insert_mode -cursor_column $o_cursor_col -cursor_row $o_cursor_row $underlay $new0] |
|
|
|
set result [dict get $mergedinfo result] |
|
set o_insert_mode [dict get $mergedinfo insert_mode] |
|
set result_col [dict get $mergedinfo cursor_column] |
|
set result_row [dict get $mergedinfo cursor_row] |
|
set overflow_right [dict get $mergedinfo overflow_right] ;#should be empty if no \v |
|
set unapplied [dict get $mergedinfo unapplied] |
|
set instruction [dict get $mergedinfo instruction] |
|
|
|
set insert_lines_below [dict get $mergedinfo insert_lines_below] |
|
set insert_lines_above [dict get $mergedinfo insert_lines_above] |
|
|
|
# -- --- --- --- --- --- |
|
set debug_first_row 2 |
|
#puts "merged: $mergedinfo" |
|
set debug "add_chunk0" |
|
append debug \n $mergedinfo |
|
append debug \n "input:[ansistring VIEW -lf 1 -vt 1 $new0] before row:$o_cursor_row after row: $result_row before col:$o_cursor_col after col:$result_col" |
|
package require textblock |
|
set debug [textblock::frame -buildcache 0 $debug] |
|
catch {punk::console::move_emitblock_return $debug_first_row 1 $debug} |
|
# -- --- --- --- --- --- |
|
|
|
set o_cursor_col $result_col |
|
set cursor_row_idx [expr {$o_cursor_row-1}] |
|
lset o_rendered_lines $cursor_row_idx $result |
|
|
|
set nextrow $result_row |
|
switch -- $instruction { |
|
lf_start { |
|
#for normal commandline - we just add a line below |
|
lappend o_rendered_lines "" |
|
incr nextrow |
|
set o_cursor_col 1 |
|
} |
|
} |
|
|
|
if {$insert_lines_below == 1} { |
|
if {[string length $overflow_right]} { |
|
lappend o_rendered_lines $overflow_right |
|
set o_cursor_col [expr {[punk::ansi::printing_length $overflow_right] +1}] |
|
} else { |
|
lappend o_rendered_lines "" |
|
set o_cursor_col 1 |
|
} |
|
} elseif {$insert_lines_above == 1} { |
|
#for {set i 0} {$i < $insert_lines_above} {incr i} { |
|
# set o_rendered_lines [linsert $o_rendered_lines $cursor_row_idx ""] |
|
# incr nextrow -1 |
|
#} |
|
set o_rendered_lines [linsert $o_rendered_lines $cursor_row_idx ""] |
|
set o_cursor_col 1 |
|
} |
|
|
|
set o_cursor_row $nextrow |
|
set cursor_row_idx [expr {$o_cursor_row-1}] |
|
if {$cursor_row_idx < [llength $o_rendered_lines]} { |
|
set activeline [lindex $o_rendered_lines $cursor_row_idx] |
|
} else { |
|
lappend o_rendered_lines "" |
|
set activeline "" |
|
} |
|
|
|
|
|
set i 1 |
|
foreach p [lrange $newparts 1 end] { |
|
if {$i < [llength $newparts]-1} { |
|
append p \n |
|
} else { |
|
if {$p eq ""} { |
|
break |
|
} |
|
} |
|
#puts stderr "overtype::renderline -info 1 -expand_right 1 -insert_mode $o_insert_mode -cursor_column $o_cursor_col -cursor_row $o_cursor_row $activeline '$p'" |
|
set underlay $activeline |
|
set line_nextchar_col [expr {[punk::char::string_width $underlay] + 1}] |
|
if {$o_cursor_col > $line_nextchar_col} { |
|
set o_cursor_col $line_nextchar_col |
|
} |
|
set mergedinfo [overtype::renderline -info 1 -expand_right 1 -insert_mode $o_insert_mode -cursor_column $o_cursor_col -cursor_row $o_cursor_row $underlay $p] |
|
set debug "add_chunk$i" |
|
append debug \n $mergedinfo |
|
append debug \n "input:[ansistring VIEW -lf 1 -vt 1 $p]" |
|
set debug [textblock::frame -buildcache 0 $debug] |
|
#catch {punk::console::move_emitblock_return [expr {$debug_first_row + ($i * 6)}] 1 $debug} |
|
|
|
set result [dict get $mergedinfo result] |
|
set o_insert_mode [dict get $mergedinfo insert_mode] |
|
set o_cursor_col [dict get $mergedinfo cursor_column] |
|
set cmove [dict get $mergedinfo cursor_row] |
|
set overflow_right [dict get $mergedinfo overflow_right] ;#should be empty if no \v |
|
set unapplied [dict get $mergedinfo unapplied] |
|
set insert_lines_below [dict get $mergedinfo insert_lines_below] |
|
if {[string is integer -strict $cmove]} { |
|
if {$cmove == 0} { |
|
set nextrow [expr {$o_cursor_row + 1}] |
|
set o_cursor_col 1 |
|
} elseif {$cmove == 1} { |
|
#check for overflow_right and unapplied |
|
#leave cursor_column |
|
} elseif {$cmove >= 1} { |
|
|
|
} |
|
|
|
} else { |
|
# =<int> - absolute |
|
set nextrow [string range $cmove 1 end] |
|
} |
|
if {$nextrow eq $o_cursor_row} { |
|
incr nextrow |
|
} |
|
set o_cursor_row $nextrow |
|
if {$insert_lines_below} { |
|
|
|
} |
|
|
|
set cursor_row_idx [expr {$o_cursor_row-1}] |
|
if {$cursor_row_idx < [llength $o_rendered_lines]} { |
|
set activeline [lindex $o_rendered_lines $cursor_row_idx] |
|
} else { |
|
lappend o_rendered_lines "" |
|
set activeline "" |
|
} |
|
lset o_rendered_lines $cursor_row_idx $result |
|
|
|
incr i |
|
} |
|
|
|
} |
|
method add_rendered_chunk {rchunk} { |
|
#split only on lf newlines - movement codes and \b \v \r not expected |
|
#check only for \v \r as chars we don't expect/want in rendered lines |
|
#chunk as been pre-rendered (or is known to be plain ascii without ANSI or \b \v \r) |
|
#but we don't yet have grapheme split info for it |
|
|
|
if {[regexp {[\v\b\r]} $rchunk]} { |
|
error "[self class].add_rendered_chunk chunk contains \\v or \\b or \\r. Rendered chunk shouldn't contain these characters or ANSI movement codes" |
|
} |
|
lappend o_chunk_list $rchunk ;#rchunk may contain newlines - that's ok |
|
dict set o_chunk_info [expr {[llength $o_chunk_list] -1}] [dict create micros [clock microseconds] type rendered] |
|
|
|
set newparts [split $rchunk \n] |
|
#lappend o_chunk_list $rchunk |
|
set lastrline [lindex $o_rendered_lines end] |
|
|
|
#in renderedlines list merge last line of old with first line of new |
|
#we can't just cat the newpart on to existing rendered line - the chunk could have split a grapheme (e.g char+combiner(s)) |
|
#we |
|
#todo - redo grapheme split on merged line |
|
set merged [string cat $lastrline [lindex $newparts 0]] |
|
lset o_rendered_lines end $merged |
|
|
|
#todo |
|
#each newpart needs its grapheme split info to be stored |
|
set o_rendered_lines [concat $o_rendered_lines [lrange $newparts 1 end]] |
|
|
|
} |
|
method linecount {} { |
|
return [llength $o_rendered_lines] |
|
} |
|
method line {idx} { |
|
if {[string is integer -strict $idx]} { |
|
incr idx -1 |
|
} |
|
return [lindex $o_rendered_lines $idx] |
|
} |
|
method lines {args} { |
|
switch -- [llength $args] { |
|
0 {return $o_rendered_lines} |
|
1 { |
|
set idx [lindex $args 0] |
|
if {[string is integer -strict $idx]} { |
|
incr idx -1 |
|
} |
|
return [list [lindex $o_rendered_lines $idx]] |
|
} |
|
2 { |
|
lassign $args idx1 idx2 |
|
if {[string is integer -strict $idx1]} { |
|
incr idx1 -1 |
|
} |
|
if {[string is integer -strict $idx2]} { |
|
incr idx2 -1 |
|
} |
|
return [lrange $o_rendered_lines $idx1 $idx2] |
|
} |
|
default {error "lines expected 0,1 or 2 indices"} |
|
} |
|
} |
|
|
|
#todo - index base??? |
|
method lines_numbered {args} { |
|
#build a paired list so we don't have to do various calcs on end+ end- etc checking llength |
|
#punk::lib::range will use lseq if available - else use it's own slower code |
|
set max [llength $o_rendered_lines] ;#assume >=1 |
|
set nums [punk::lib::range 1 $max] |
|
set numline_list [list] |
|
foreach n $nums ln $o_rendered_lines { |
|
lappend numline_list [list $n $ln] |
|
} |
|
|
|
switch -- [llength $args] { |
|
0 {return $numline_list} |
|
1 {return [lindex $numline_list [lindex $args 0]]} |
|
2 {return [lrange $numline_list {*}$args]} |
|
default {error "lines expected 0,1 or 2 indices"} |
|
} |
|
} |
|
|
|
#1-based |
|
method delete_line {linenum} { |
|
error "unimplemented" |
|
if {$linenum eq "end"} { |
|
set linenum [llength $o_rendered_lines] |
|
} |
|
if {![string is integer -strict $linenum]} { |
|
error "delete_line accepts only end or an integer from 1 to linecount" |
|
} |
|
if {$linenum == 0} { |
|
error "minimum line is 1" |
|
} |
|
set o_rendered_lines [lreplace $o_rendered_lines $index $index] |
|
} |
|
#clear data from last line only |
|
method clear_tail {} { |
|
set o_cursor_row [llength $o_rendered_lines] |
|
set o_cursor_col 1 |
|
lset o_rendered_lines end "" |
|
} |
|
#1-based |
|
method view_lines {args} { |
|
set llist [my lines {*}$args] |
|
return [join $llist \n] |
|
} |
|
method view_lines_numbered {args} { |
|
set ANSI_linenum [a+ green] |
|
set RST [a] |
|
set llist [my lines_numbered {*}$args] |
|
set nums [lsearch -all -inline -index 0 -subindices $llist *] |
|
lset nums $o_cursor_row-1 "[a+ bold underline]$o_cursor_row${RST}$ANSI_linenum" |
|
set lines [lsearch -all -inline -index 1 -subindices $llist *] |
|
|
|
set cursorline [lindex $lines $o_cursor_row-1] |
|
set charindex_at_cursor [ansistring COLUMNINDEX $cursorline $o_cursor_col] |
|
if {$charindex_at_cursor ne ""} { |
|
lassign [ansistring INDEXCOLUMNS $cursorline $charindex_at_cursor] col0 col1 |
|
#we now have the column extents of the possibly double-wide character at the cursor |
|
#we can apply ansi just to those columns using a transparent overtype |
|
set prefix [string repeat " " [expr {$col0 -1}]] |
|
set linecols [punk::ansi::printing_length $cursorline] |
|
set suffix [string repeat " " [expr {$linecols -$col1}]] |
|
#capitalised INDEX - for grapheme/control-char index e.g a with diacritic a\u0300 has a single index |
|
set char_at_cursor [ansistring INDEX $cursorline $charindex_at_cursor] ;#this is the char with appropriate ansireset codes |
|
set rawchar [punk::ansi::ansistrip $char_at_cursor] |
|
if {$rawchar eq " "} { |
|
set charhighlight "[punk::ansi::a+ White]_[a]" |
|
} else { |
|
set charhighlight [punk::ansi::a+ reverse]$char_at_cursor[a] |
|
} |
|
set cursorline [overtype::renderline -transparent 1 -insert_mode 0 -expand_right 0 $cursorline $prefix$charhighlight$suffix] |
|
lset lines $o_cursor_row-1 $cursorline |
|
} |
|
|
|
set numcol "$ANSI_linenum[join $nums \n][a]" |
|
set linecol [join $lines \n] |
|
return [textblock::join -- $numcol " " $linecol] |
|
} |
|
method debugview_lines {} { |
|
set result "" |
|
foreach ln $o_rendered_lines { |
|
append result [ansistring VIEW -lf 1 -vt 1 $ln] \n ;#should be no lf or vt - but if there is.. we'd better show it |
|
} |
|
append result \n "cursor row: $o_cursor_row col: $o_cursor_col" |
|
return $result |
|
} |
|
method last_char {} { |
|
return [string index [lindex $o_chunk_list end] end] |
|
} |
|
|
|
#more strictly - last non-ansi? |
|
method last_grapheme {} { |
|
set lastchunk [lindex $o_chunk_list end] |
|
set plaintext_parts [punk::ansi::ta::split_at_codes $lastchunk] |
|
set pt [lindex $plaintext_parts end] |
|
if {$pt eq ""} { |
|
set pt [lindex $plaintext_parts end-1] |
|
} |
|
set graphemes [punk::char::grapheme_split $pt] |
|
return [lindex $graphemes end] |
|
} |
|
method last_ansi {} { |
|
set lastchunk [lindex $o_chunk_list end] |
|
set parts [punk::ansi::ta::split_codes_single $lastchunk] |
|
set lastcode [lindex $parts end-1] |
|
return $lastcode |
|
#return [ansistring VIEW -lf 1 $lastcode] |
|
} |
|
method chunks {args} { |
|
switch -- [llength $args] { |
|
0 {return $o_chunk_list} |
|
1 {return [lindex $o_chunk_list [lindex $args 0]]} |
|
2 {return [lrange $o_chunk_list {*}$args]} |
|
default {error "chunks expected 0,1 or 2 arguments (index or range)"} |
|
} |
|
} |
|
method view_chunks {} { |
|
set result "" |
|
set dashes [string repeat - 20] |
|
foreach arrival_chunk $o_chunk_list chunk_id [dict keys $o_chunk_info] { |
|
set chunk_info [dict get $o_chunk_info $chunk_id] |
|
append result $dashes \n |
|
set micros [dict get $chunk_info micros] |
|
append result "$chunk_id arrival: [clock format [expr {$micros / 1000000}] -format "%Y-%m-%d %H:%M:%S"] ($micros)" \n |
|
append result $dashes \n |
|
append result $arrival_chunk \n |
|
} |
|
return $result |
|
} |
|
|
|
method debugview_chunks {} { |
|
set result "" |
|
foreach ln $o_chunk_list { |
|
append result [ansistring VIEW -lf 1 -vt 1 $ln] \n |
|
} |
|
append result \n "cursor row: $o_cursor_row col: $o_cursor_col" |
|
return $result |
|
} |
|
method view_raw {} { |
|
return [join $o_chunk_list ""] |
|
} |
|
method debugview_raw {} { |
|
set sublf [ansistring VIEW -lf 1 \n] |
|
#set subvt [ansistring VIEW -lvt 1 \v] ;#vt replacement with $subvt\v will not align accurately.. todo ? |
|
return [string map [list $sublf $sublf\n] [ansistring VIEW -lf 1 -vt 0 [join $o_chunk_list ""]]] |
|
} |
|
} |
|
|
|
} |
|
|
|
proc ::punk::repl::repl_handler_checkchannel {inputchan} { |
|
if {[catch {chan eof $inputchan} is_eof]} { |
|
::repl::rputs stderr "\n|repl> repl_handler_checkchannel error on $inputchan. (closed?) msg:$is_eof" |
|
} else { |
|
if {$is_eof} { |
|
if {$::tcl_interactive} { |
|
::repl::rputs stderr "\n|repl> repl_handler_checkchannel EOF on $inputchan." |
|
} |
|
} |
|
} |
|
} |
|
proc ::punk::repl::repl_handler_checkcontrolsignal_linemode {inputchan} { |
|
#todo - what? |
|
return |
|
variable signal_control_c |
|
if {$signal_control_c > 0} { |
|
if {$::tcl_interactive} { |
|
::repl::rputs stderr "\n|repl> repl_handler_checkcontrolsignal_linemode ctrl-c errorCode 0: [lindex $::errorCode 0]" |
|
} |
|
} |
|
} |
|
|
|
#This is not called from the signal handler - so we can't affect the signal handling with return |
|
# |
|
proc ::punk::repl::repl_handler_checkcontrolsignal_rawmode {inputchan} { |
|
variable signal_control_c |
|
variable signal_control_c_msg |
|
if {$signal_control_c > 0 && $signal_control_c_msg ne "" } { |
|
#if {$::tcl_interactive} { |
|
# ::repl::rputs stderr "\n|repl> repl_handler_checkcontrolsignal_rawmode ctrl-c errorCode 0: [lindex $::errorCode 0]" |
|
#} |
|
set msg $signal_control_c_msg |
|
set signal_control_c_msg "" |
|
} else { |
|
set msg "" |
|
} |
|
return [list count $signal_control_c msg $msg] |
|
} |
|
|
|
|
|
proc punk::repl::repl_handler_restorechannel_if_not_eof {inputchan previous_input_state} { |
|
if {$inputchan ni [chan names] || [eof $inputchan]} { |
|
return |
|
} |
|
if {[chan conf $inputchan] ne $previous_input_state} { |
|
set restore_input_conf [dict remove $previous_input_state -inputmode] ;#Attempting to set input mode often gives permission denied on windows - why? |
|
if {[catch { |
|
chan conf $inputchan {*}$restore_input_conf |
|
} errM]} { |
|
rputs stderr "|repl>original: [ansistring VIEW $previous_input_state]" |
|
rputs stderr "|repl>current : [ansistring VIEW [chan conf $inputchan]]" |
|
rputs stderr "\n|repl> Failed to return $inputchan to original state" |
|
rputs stderr "|repl>ERR: $errM" |
|
} |
|
} |
|
return [chan conf $inputchan] |
|
} |
|
proc repl::repl_handler {inputchan prompt_config} { |
|
# -- review |
|
variable in_repl_handler |
|
set in_repl_handler [list $inputchan $prompt_config] |
|
# -- |
|
variable last_controlc_count |
|
|
|
upvar ::punk::repl::prompt_reset_flag prompt_reset_flag |
|
if {$prompt_reset_flag == 1} { |
|
set prompt_config [punk::repl::get_prompt_config] |
|
set prompt_reset_flag 0 |
|
} |
|
|
|
fileevent $inputchan readable {} |
|
upvar ::punk::console::input_chunks_waiting input_chunks_waiting |
|
#note -inputmode not available in Tcl 8.6 for chan configure! |
|
#According to DKF - -buffering option doesn't affect input channels |
|
set rawmode 0 |
|
set original_input_conf [chan configure $inputchan] ;#whether repl is in line or raw mode - we restore the inputchan (stdin) state |
|
if {[dict exists $original_input_conf -inputmode]} { |
|
if {[dict get $original_input_conf -inputmode] eq "raw"} { |
|
#user or script has apparently put stdin into raw mode - update punk::console::is_raw to match |
|
set rawmode 1 |
|
set ::punk::console::is_raw 1 |
|
} else { |
|
set ::punk::console::is_raw 0 |
|
} |
|
#what about enable/disable virtualTerminal ? |
|
#using stdin -inputmode to switch modes won't set virtualterminal input state appropriately |
|
#we expect the state of -inputmode to be 'normal' even though we flip it during the read part of our repl loop |
|
#if it's been set to raw - assume it is deliberately done this way as the user could have alternatively called punk::mode raw or punk::console::enableVirtualTerminal |
|
#by not doing this automatically - we assume the caller has a reason. |
|
} else { |
|
#JMN FIX! |
|
#this returns 0 in rawmode on 8.6 after repl thread changes |
|
set rawmode [set ::punk::console::is_raw] |
|
} |
|
|
|
if {!$rawmode} { |
|
#linemode |
|
|
|
#stdin with line-mode readable events (at least on windows for Tcl 8.7a6 to 9.0a) can get stuck with bytes pending when input longer than 100chars - even though there is a linefeed further on than that. |
|
#This potentially affects a reasonable number of Tcl8.7 kit/tclsh binaries out in the wild. |
|
#see bug https://core.tcl-lang.org/tcl/tktview/bda99f2393 (gets stdin problem when non-blocking - Windows) |
|
#when in non-blocking mode we will have to read that in to get further - but we don't know if that was the end of line or if there is more - and we may not get a newline even though one was present originally on stdin. |
|
#presence of 8.7 buffering bug will result in unrecoverable situation - even switching to raw and using read will not be able to retrieve tail data. |
|
#the readable event only gives us 200 bytes (same problem may be at 4k/8k in other versions) |
|
#This occurs whether we use gets or read - |
|
set stdinlines [list] |
|
if {[dict get $original_input_conf -blocking] ne "0"} { |
|
chan configure $inputchan -blocking 0 |
|
} |
|
|
|
set waitingchunk "" |
|
#review - input_chunks_waiting in line mode - |
|
if {[info exists input_chunks_waiting($inputchan)] && [llength $input_chunks_waiting($inputchan)]} { |
|
#puts stderr "repl_handler input_chunks_waiting($inputchan) while in line mode. Had data:[ansistring VIEW -lf 1 $input_chunks_waiting($inputchan)]" |
|
set allwaiting [join $input_chunks_waiting($inputchan) ""] |
|
set input_chunks_waiting($inputchan) [list] |
|
set yellow [punk::ansi::a+ yellow bold] |
|
set waitinglines [split $allwaiting \n] |
|
foreach ln [lrange $waitinglines 0 end-1] { |
|
lappend stdinlines $ln |
|
} |
|
set waitingchunk [lindex $waitinglines end] |
|
# -- |
|
#set chunksize [gets $inputchan chunk] |
|
set chunk [read $inputchan] |
|
set chunksize [string length $chunk] |
|
# -- |
|
if {$chunksize > 0} { |
|
if {[string index $chunk end] eq "\n"} { |
|
lappend stdinlines $waitingchunk[string range $chunk 0 end-1] |
|
#punk::console::cursorsave_move_emitblock_return 30 30 "repl_handler num_stdinlines [llength $stdinlines] chunk:$yellow[ansistring VIEW -lf 1 $chunk][a] fblocked:[fblocked $inputchan] pending:[chan pending input stdin]" |
|
|
|
punk::repl::repl_handler_restorechannel_if_not_eof $inputchan $original_input_conf |
|
uplevel #0 [list repl::repl_process_data $inputchan line "" $stdinlines $prompt_config] |
|
} else { |
|
set input_chunks_waiting($inputchan) [list $allwaiting] |
|
lappend input_chunks_waiting($inputchan) $chunk |
|
} |
|
} else { |
|
if {[fblocked $inputchan]} { |
|
#REVIEW - need to und |
|
#todo - figure out why we're here. |
|
#can we even put a spinner so we don't keep emitting lines? We probably can't use any ansi functions that need to get a response on stdin..(like get_cursor_pos) |
|
#punk::console::get_size is problematic if -winsize not available on the stdout channel - which is the case for certain 8.6 versions at least.. platform variances? |
|
## can't do this: set screeninfo [punk::console::get_size]; lassign $screeninfo _c cols _r rows |
|
set outconf [chan configure stdout] |
|
set RED [punk::ansi::a+ red bold]; set RST [punk::ansi::a] |
|
if {"windows" eq $::tcl_platform(platform)} { |
|
set msg "${RED}$inputchan fblocked is true. (line-length Tcl windows channel bug?)$RST \{$allwaiting\}" |
|
} else { |
|
set msg "${RED}$inputchan fblocked is true.$RST \{$allwaiting\}" |
|
} |
|
set cols "" |
|
set rows "" |
|
if {[dict exists $outconf -winsize]} { |
|
lassign [dict get $outconf -winsize] cols rows |
|
} else { |
|
#fallback - try external executable. Which is a bit ugly |
|
#tput can't seem to get dimensions (on FreeBSD at least) when not run interactively - ie via exec. (always returns 80x24 no matter if run with <@stdin) |
|
|
|
#bizarrely - tput can work with exec on windows if it's installed e.g from msys2 |
|
#but can be *slow* compared to unix e.g 400ms+ vs <2ms on FreeBSD ! |
|
#stty -a is 400ms+ vs 500us+ on FreeBSD |
|
|
|
if {"windows" eq $::tcl_platform(platform)} { |
|
set tputcmd [auto_execok tput] |
|
if {$tputcmd ne ""} { |
|
if {![catch {exec {*}$tputcmd cols lines} values]} { |
|
lassign $values cols rows |
|
} |
|
} |
|
} |
|
|
|
if {![string is integer -strict $cols] || ![string is integer -strict $rows]} { |
|
#same for all platforms? tested on windows, wsl, FreeBSD |
|
#exec stty -a gives a result on the first line like: |
|
#speed xxxx baud; rows rr; columns cc; |
|
#review - more robust parsing - do we know it's first line? |
|
set sttycmd [auto_execok stty] |
|
if {$sttycmd ne ""} { |
|
#the more parseable: stty -g doesn't give rows/columns |
|
if {![catch {exec {*}$sttycmd -a} result]} { |
|
lassign [split $result \n] firstline |
|
set lineparts [split $firstline {;}] ;#we seem to get segments that look well behaved enough to be treated as tcl lists - review - regex? |
|
set rowinfo [lsearch -index end -inline $lineparts rows] |
|
if {[llength $rowinfo] == 2} { |
|
set rows [lindex $rowinfo 0] |
|
} |
|
set colinfo [lsearch -index end -inline $lineparts columns] |
|
if {[llength $colinfo] == 2} { |
|
set cols [lindex $colinfo 0] |
|
} |
|
} |
|
} |
|
} |
|
} |
|
if {[string is integer -strict $cols] && [string is integer -strict $rows]} { |
|
#got_dimensions - todo - try spinner? |
|
#puts -nonewline stdout [punk::ansi::move $rows 4]$msg |
|
#use cursorsave_ version which avoids get_cursor_pos_list call |
|
set msglen [ansistring length $msg] |
|
punk::console::cursorsave_move_emitblock_return $rows [expr {$cols - $msglen -1}] $msg |
|
} else { |
|
#no mechanism to get console dimensions |
|
#we are reduced to continuously spewing lines. |
|
puts stderr $msg |
|
} |
|
|
|
after 100 |
|
} |
|
set input_chunks_waiting($inputchan) [list $allwaiting] |
|
} |
|
|
|
} else { |
|
punk::repl::repl_handler_checkchannel $inputchan |
|
punk::repl::repl_handler_checkcontrolsignal_linemode $inputchan |
|
# -- --- --- |
|
#set chunksize [gets $inputchan chunk] |
|
# -- --- --- |
|
set chunk [read $inputchan] |
|
set chunksize [string length $chunk] |
|
# -- --- --- |
|
if {$chunksize > 0} { |
|
#punk::console::cursorsave_move_emitblock_return 35 120 "chunk: [ansistring VIEW -lf 1 "...[string range $chunk end-10 end]"]" |
|
set ln $chunk ;#temp |
|
#punk::console::cursorsave_move_emitblock_return 25 30 [textblock::frame -title line "[a+ green]$waitingchunk[a][a+ red][ansistring VIEW -lf 1 $ln][a+ green]pending:[chan pending input stdin][a]"] |
|
if {[string index $ln end] eq "\n"} { |
|
lappend stdinlines [string range $ln 0 end-1] |
|
punk::repl::repl_handler_restorechannel_if_not_eof $inputchan $original_input_conf |
|
uplevel #0 [list repl::repl_process_data $inputchan line "" $stdinlines $prompt_config] |
|
} else { |
|
lappend input_chunks_waiting($inputchan) $ln |
|
} |
|
} |
|
} |
|
|
|
} else { |
|
#rawmode |
|
if {[info exists input_chunks_waiting($inputchan)] && [llength $input_chunks_waiting($inputchan)]} { |
|
#we could concat and process as if one chunk - but for now at least - we want to preserve the 'chunkiness' |
|
set chunkwaiting_zero [lpop input_chunks_waiting($inputchan) 0] ;#pop off lhs of wait list (tcl 8.6 is tcl imp of lpop - a little slower) |
|
uplevel #0 [list repl::repl_process_data $inputchan raw-waiting $chunkwaiting_zero [list] $prompt_config] |
|
} else { |
|
|
|
punk::repl::repl_handler_checkchannel $inputchan |
|
set signalinfo [punk::repl::repl_handler_checkcontrolsignal_rawmode $inputchan] |
|
if {[dict get $signalinfo count] > $last_controlc_count} { |
|
set continue 0 |
|
set last_controlc_count [dict get $signalinfo count] |
|
} else { |
|
set continue 1 |
|
} |
|
|
|
if {$continue} { |
|
if {[dict get $original_input_conf -blocking] ne "0" || [dict get $original_input_conf -translation] ne "lf"} { |
|
chan configure $inputchan -blocking 0 |
|
chan configure $inputchan -translation lf |
|
} |
|
set chunk [read $inputchan] |
|
|
|
punk::repl::repl_handler_restorechannel_if_not_eof $inputchan $original_input_conf |
|
uplevel #0 [list repl::repl_process_data $inputchan raw-read $chunk [list] $prompt_config] |
|
while {[llength $input_chunks_waiting($inputchan)]} { |
|
set chunkzero [lpop input_chunks_waiting($inputchan) 0] |
|
if {$chunkzero eq ""} {continue} ;#why empty waiting - and is there any point passing on? |
|
uplevel #0 [list repl::repl_process_data $inputchan raw-waiting $chunkzero [list] $prompt_config] |
|
} |
|
} |
|
} |
|
} |
|
|
|
if {![chan eof $inputchan]} { |
|
################################################################################## |
|
#Re-enable channel read handler only if no waiting chunks - must process in order |
|
################################################################################## |
|
if {![llength $input_chunks_waiting($inputchan)]} { |
|
fileevent $inputchan readable [list ::repl::repl_handler $inputchan $prompt_config] |
|
} else { |
|
after idle [list ::repl::repl_handler $inputchan $prompt_config] |
|
} |
|
#################################################### |
|
} else { |
|
#repl_handler_checkchannel $inputchan |
|
fileevent $inputchan readable {} |
|
set reading 0 |
|
thread::send -async $::repl::codethread {set ::punk::repl::codethread::running 0} |
|
if {$::tcl_interactive} { |
|
rputs stderr "\nrepl_handler EOF inputchannel:[chan conf $inputchan]" |
|
#rputs stderr "\n|repl> ctrl-c EOF on $inputchan." |
|
} |
|
set [namespace current]::done 1 |
|
after 1 [list repl::reopen_stdin] |
|
} |
|
set in_repl_handler [list] |
|
} |
|
|
|
proc punk::repl::editbuf {index args} { |
|
set editbuf [lindex $::repl::editbuf_list $index] |
|
if {$editbuf ne ""} { |
|
$editbuf {*}$args |
|
} else { |
|
return "No such index in editbuf list" |
|
} |
|
} |
|
interp alias {} editbuf {} ::punk::repl::editbuf |
|
|
|
|
|
proc punk::repl::console_debugview {editbuf consolewidth args} { |
|
package require textblock |
|
variable debug_repl |
|
if {$debug_repl <= 0} { |
|
return [dict create width 0 height 0 topleft {}] |
|
} |
|
set defaults {-row 10 -rightmargin 2 -chunktype raw-read} |
|
#dict for {k v} $args { |
|
# switch -- $k { |
|
# -row - -chunktype {} |
|
# default { |
|
# error "console_debugview unrecognised option '$k'. Known-options [dict keys $defaults]" |
|
# } |
|
# } |
|
#} |
|
set opts [dict merge $defaults $args] |
|
set opt_row [dict get $opts -row] |
|
set opt_chunktype [dict get $opts -chunktype] |
|
set opt_rightmargin [dict get $opts -rightmargin] |
|
|
|
#debugview_raw frame |
|
set RST [a] |
|
if {[catch { |
|
set info [$editbuf debugview_raw] |
|
if {$opt_chunktype eq "raw-waiting"} { |
|
set info [a+ bold yellow]$info$RST |
|
} else { |
|
set info [a+ green]$info$RST |
|
} |
|
#set lines [lines_as_list -ansireplays 1 $info] |
|
set lines [lines_as_list -ansireplays 0 $info] |
|
if {[llength $lines] > 20} { |
|
set lines [lrange $lines end-19 end] |
|
set info [::join $lines \n] |
|
} |
|
set debug_height [expr {[llength $lines]+2}] ;#framed height |
|
} errM]} { |
|
set info [textblock::frame -buildcache 0 -title "[a red]error$RST" $errM] |
|
set debug_height [textblock::height $info] |
|
} else { |
|
#treat as ephemeral (unreusable) frames due to varying width & height - therefore set -buildcache 0 |
|
set info [textblock::frame -buildcache 0 -ansiborder [a+ bold green] -title "[a cyan]debugview_raw$RST" $info] |
|
} |
|
|
|
set debug_width [textblock::widthtopline $info] |
|
set patch_height [expr {2 + $debug_height + 2}] |
|
set spacepatch [textblock::block $debug_width $patch_height " "] |
|
puts -nonewline [punk::ansi::cursor_off] |
|
#use non cursorsave versions - cursor save/restore will interfere with any concurrent ansi rendering that uses save/restore - because save/restore is a single item, not a stack. |
|
set debug_offset [[expr {$consolewidth - $debug_width - $opt_rightmargin}]] |
|
set row_clear [expr {$opt_row -2}] |
|
punk::console::move_emitblock_return $row_clear $debug_offset $spacepatch |
|
punk::console::move_emitblock_return $opt_row $debug_offset $info |
|
set topleft [list $debug_offset $opt_row] ;#col,row REVIEW |
|
puts -nonewline [punk::ansi::cursor_on] |
|
flush stdout |
|
|
|
return [dict create width $debug_width height $debug_height topleft $topleft] |
|
} |
|
proc punk::repl::console_editbufview {editbuf consolewidth args} { |
|
package require textblock |
|
upvar ::repl::editbuf_list editbuf_list |
|
|
|
set defaults {-row 10 -rightmargin 0} |
|
set opts [dict merge $defaults $args] |
|
set opt_row [dict get $opts -row] |
|
set opt_rightmargin [dict get $opts -rightmargin] |
|
|
|
if {[catch { |
|
set info [$editbuf view_lines_numbered] |
|
set lines [lines_as_list -ansireplays 1 $info] |
|
if {[llength $lines] > 20} { |
|
set lines [lrange $lines end-19 end] |
|
set info [punk::lib::list_as_lines $lines] |
|
} |
|
} editbuf_error]} { |
|
set info [textblock::frame -buildcache 0 -title "[a red]error[a]" "$editbuf_error\n$::errorInfo"] |
|
} else { |
|
set title "[a cyan]editbuf [expr {[llength $editbuf_list]-1}] lines [$editbuf linecount][a]" |
|
append title "[a+ yellow bold] col:[format %3s [$editbuf cursor_column]] row:[$editbuf cursor_row][a]" |
|
set row1 " lastchar:[ansistring VIEW -lf 1 [$editbuf last_char]] lastgrapheme:[ansistring VIEW -lf 1 [$editbuf last_grapheme]]" |
|
set row2 " lastansi:[ansistring VIEW -lf 1 [$editbuf last_ansi]]" |
|
set info [a+ green bold]$row1\n$row2[a]\n$info |
|
set info [textblock::frame -buildcache 0 -ansiborder [a+ green bold] -title $title $info] |
|
} |
|
set editbuf_width [textblock::widthtopline $info] |
|
set spacepatch [textblock::block $editbuf_width 2 " "] |
|
|
|
#set editbuf_offset [expr {$consolewidth - $editbuf_width - $debug_width - 2}] |
|
set editbuf_offset [expr {$consolewidth - $editbuf_width - $opt_rightmargin}] |
|
|
|
set row_clear [expr {$opt_row -2}] |
|
punk::console::cursorsave_move_emitblock_return $row_clear $editbuf_offset $spacepatch |
|
punk::console::cursorsave_move_emitblock_return $opt_row $editbuf_offset $info |
|
|
|
return [dict create width $editbuf_width] |
|
} |
|
proc punk::repl::console_controlnotification {message consolewidth consoleheight args} { |
|
package require textblock |
|
set defaults {-bottommargin 0 -rightmargin 0} |
|
set opts [dict merge $defaults $args] |
|
set opt_bottommargin [dict get $opts -bottommargin] |
|
set opt_rightmargin [dict get $opts -rightmargin] |
|
set messagelines [split $message \n] |
|
set message [lindex $messagelines 0] ;#only allow single line |
|
set info "[a+ bold red]$message[a]" |
|
set hlt [dict get [textblock::framedef light] hlt] |
|
set box [textblock::frame -boxmap [list tlc $hlt trc $hlt] -title $message -height 1] |
|
set notification_width [textblock::widthtopline $info] |
|
set box_offset [expr {$consolewidth - $notification_width - $opt_rightmargin}] |
|
set row [expr {$consoleheight - $opt_bottommargin}] |
|
punk::console::cursorsave_move_emitblock_return $row $box_offset $info |
|
return [dict create width $notification_width] |
|
} |
|
|
|
proc repl::repl_process_data {inputchan chunktype chunk stdinlines prompt_config} { |
|
variable loopinstance |
|
incr loopinstance |
|
upvar ::punk::console::input_chunks_waiting input_chunks_waiting |
|
|
|
upvar ::punk::repl::prompt_reset_flag prompt_reset_flag |
|
|
|
variable last_repl_char "" ;#last char emitted by this handler to stdout/stderr |
|
variable lastoutchar "" |
|
variable lasterrchar "" |
|
variable commandstr |
|
# --- |
|
variable editbuf |
|
variable editbuf_list |
|
variable editbuf_linenum_submitted |
|
|
|
# --- |
|
variable reading |
|
variable id_outstack |
|
upvar ::punk::config::running running_config |
|
|
|
try { |
|
#catch {puts stderr "xx--->[rep $::arglej]"} |
|
if {$prompt_reset_flag == 1} { |
|
set prompt_config [punk::repl::get_prompt_config] |
|
set prompt_reset_flag 0 |
|
} |
|
|
|
set resultprompt [dict get $prompt_config resultprompt] |
|
set nlprompt [dict get $prompt_config nlprompt] |
|
set infoprompt [dict get $prompt_config infoprompt] |
|
set debugprompt [dict get $prompt_config debugprompt] |
|
|
|
|
|
set rawmode [set ::punk::console::is_raw] |
|
if {!$rawmode} { |
|
#puts stderr "-->got [ansistring VIEW -lf 1 $stdinlines]<--" |
|
|
|
} else { |
|
#raw |
|
set chunklen [string length $chunk] |
|
set onetime 1 |
|
#single loop while to allow break on escape |
|
while {$onetime && [string length $chunk] >= 0 } { |
|
set onetime 0 |
|
#punk::console::move_emitblock_return 20 120 $chunklen-->[chan conf stdin]<-- |
|
|
|
#if {$chunklen == 0} { |
|
# #document examples of when we expect zero-byte chunk |
|
# #1) ctrl-z |
|
# #review |
|
# rputs stderr "->0byte read stdin" |
|
# if {[chan eof $inputchan]} { |
|
# fileevent $inputchan readable {} |
|
# set reading 0 |
|
# #set running 0 |
|
# if {$::tcl_interactive} { |
|
# rputs stderr "\n|repl> EOF on $inputchan." |
|
# } |
|
# set [namespace current]::done 1 |
|
# #test |
|
# #JMN |
|
# #tailcall repl::reopen_stdin |
|
# } |
|
# break |
|
#} |
|
|
|
#set info1 "read $chunklen bytes->[ansistring VIEW -lf 1 -vt 1 $chunk]" |
|
#consider also the terminal linefeed mode. |
|
#https://vt100.net/docs/vt510-rm/LNM.html |
|
# terminals (by default) generally use a lone cr to represent enter (LNM reset ie CSI 20l) |
|
#(as per above doc: "For compatibility with Digital's software you should keep LNM reset (line feed)") |
|
|
|
#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 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. |
|
|
|
if {$chunklen == 1} { |
|
#presume it's a keypress from terminal |
|
set chunk [string map {\r \n} $chunk] |
|
} else { |
|
#maybe a paste? (or stdin to active shell loop - possibly with no terminal ? ) |
|
#we'd better check for crlf and/or plain lf. If found - presume any lone CR is to be left as is. |
|
if {[string first \n $chunk] < 0} { |
|
set chunk [string map {\r \n} $chunk] |
|
} |
|
#else - |
|
#has lf - but what if last char is cr? |
|
#It may require user to hit enter - probably ok. |
|
#could be a sequence of cr's from holding enter key |
|
} |
|
|
|
#review - we can receive chars such as escapes or arrow inline with other data even from keyboard if keys are pushed quickly (or automated?) |
|
# - so we probably shouldn't really rely on whether a char arrives alone in a chunk as a factor in its behaviour |
|
#On the other hand - timing of keystrokes could be legitimate indications of intention in a cli ? |
|
|
|
#esc or ctrl-lb |
|
if {$chunk eq "\x1b"} { |
|
#return |
|
set stdinlines [list "\x1b"] |
|
set commandstr "" |
|
set chunk "" |
|
$editbuf clear_tail |
|
screen_last_char_add \x1b stdin escape |
|
break |
|
} |
|
|
|
#if ProcessedInput is disabled - we can get ctrl-c, but then we wouldn't be in raw mode and wouldn't be here. |
|
#e.g with punk::console::disableProcessedInput |
|
#if we get just ctrl-c in one chunk |
|
#ctrl-c |
|
if {$chunk eq "\x03"} { |
|
#::punk::repl::handler_console_control "ctrl-c_via_rawloop" |
|
error "character 03 -> ctrl-c" |
|
} |
|
|
|
|
|
#review - configurable? |
|
#translate raw del to backspace del for those terminals that send plain del |
|
if {$chunk eq "\x7f"} { |
|
set chunk "\b\x7f" |
|
} |
|
#ctrl-bslash |
|
if {$chunk eq "\x1c"} { |
|
#try to brutally terminate process |
|
#attempt to leave terminal in a reasonable state |
|
punk::mode line |
|
after 250 {exit 42} |
|
return |
|
} |
|
#for now - exit with small delay for tidyup |
|
#ctrl-z |
|
if {$chunk eq "\x1a"} { |
|
#::punk::repl::handler_console_control "ctrl-z_via_rawloop" |
|
punk::mode line |
|
after 1000 {exit 43} |
|
return |
|
} |
|
|
|
#we *could* intercept arrow keys here before they are handled in the editbuf |
|
#but there should only be the need to do so for situations where we aren't editing a commandline |
|
#if {$chunk eq "\x1b\[D"} { |
|
# #rputs stderr "${debugprompt}arrow-left D" |
|
# #set commandstr "" |
|
# #punk::console::move_back 1 ;#terminal does it anyway? |
|
#} |
|
#if {$chunk eq "\x1b\[C"} { |
|
#} |
|
|
|
$editbuf add_chunk $chunk |
|
|
|
#-------------------------- |
|
# editbuf and debugview rhs frames |
|
if {[set ::punk::console::ansi_available]} { |
|
#experimental - use punk::console::get_size to determine current visible width. |
|
#This should ideally be using sigwinch or some equivalent to set a value somewhere. |
|
#testing each time is very inefficient (1+ms) |
|
#unfortunately there isn't an easy way to get such an event on windows console based systems - REVIEW. |
|
set do_checkwidth 1 ;#make configurable if performance hit is too severe? TODO |
|
set consolewidth 132 |
|
if {$do_checkwidth} { |
|
if {[catch {set consolewidth [dict get [punk::console::get_size] columns]} errM]} { |
|
puts stderr "repl_process_data failed on call to punk::console::get_size :$errM" |
|
} |
|
} |
|
set debug_width 0 |
|
set rightmargin 0 |
|
set space_occupied [punk::repl::console_debugview $editbuf $consolewidth -row 10 -chunktype $chunktype -rightmargin $rightmargin] ;#contains cursor movements |
|
set debug_width [dict get $space_occupied width] |
|
set clearance [expr {$debug_width + $rightmargin}] |
|
set space_occupied [punk::repl::console_editbufview $editbuf $consolewidth -row 10 -rightmargin $clearance] |
|
} |
|
#-------------------------- |
|
|
|
|
|
set lines_unsubmitted [expr {[$editbuf linecount] - $editbuf_linenum_submitted}] |
|
#there is always one 'line' unsubmitted - although it may be the current one being built, which may be empty string |
|
if {$lines_unsubmitted < 1} { |
|
puts stderr "repl editbuf_linenum_submitted out of sync with editbuf" |
|
} |
|
|
|
#set trailing_line_index [expr {[$editbuf linecount] -1}] |
|
set last_line_num [$editbuf linecount] |
|
#set nextsubmit_index [expr {$editbuf_lineindex_submitted + 1}] |
|
set nextsubmit_line_num [expr {$editbuf_linenum_submitted + 1}] |
|
|
|
set cursor_row [$editbuf cursor_row] |
|
set cursor_index [expr {$cursor_row -1}] |
|
set lastansi [$editbuf last_ansi] |
|
if {$lastansi eq "\x1b\[A"} { |
|
if {$cursor_row > 1} { |
|
puts -nonewline stdout "\x1b\[A" |
|
} |
|
} elseif {$lastansi eq "\x1b\[B"} { |
|
puts -nonewline stdout "\x1b\[B" |
|
} |
|
flush stdout |
|
|
|
|
|
|
|
set leftmargin 3 |
|
puts -nonewline stdout [a+ cyan][punk::ansi::move_column [expr {$leftmargin +1}]][punk::ansi::erase_eol][$editbuf line $cursor_row][a][punk::ansi::move_column [expr {$leftmargin + [$editbuf cursor_column]}]] |
|
#puts -nonewline stdout $chunk |
|
flush stdout |
|
if {[$editbuf last_char] eq "\n"} { |
|
set linelen [punk::ansi::printing_length [$editbuf line $nextsubmit_line_num]] |
|
puts -nonewline stdout [a+ cyan bold][punk::ansi::move_column [expr {$leftmargin +1}]][$editbuf line $nextsubmit_line_num][a][punk::ansi::move_column [expr {$leftmargin + $linelen +1}]] |
|
#screen_last_char_add "\n" input inputline |
|
puts -nonewline stdout [punk::ansi::erase_eol]\n |
|
#puts -nonewline stdout \n |
|
screen_last_char_add "\n" input inputline |
|
set waiting [$editbuf line end] |
|
if {[string length $waiting] > 0} { |
|
set waiting [a+ yellow bold]$waiting[a] |
|
#puts stderr "waiting $waiting" |
|
$editbuf clear_tail |
|
lappend input_chunks_waiting($inputchan) $waiting |
|
} |
|
} |
|
if {$editbuf_linenum_submitted == 0} { |
|
#(there is no line 0 - lines start at 1) |
|
if {[$editbuf last_char] eq "\n"} { |
|
lappend stdinlines [$editbuf line 1] |
|
set editbuf_linenum_submitted 1 |
|
} |
|
} else { |
|
if {$nextsubmit_line_num < $last_line_num} { |
|
foreach ln [$editbuf lines $nextsubmit_line_num end-1] { |
|
lappend stdinlines $ln |
|
incr editbuf_linenum_submitted |
|
} |
|
} |
|
} |
|
set last_cursor_column [$editbuf cursor_column] |
|
} |
|
} |
|
|
|
} trap {POSIX} {e eopts} { |
|
rputs stderr "trap1 POSIX '$e' eopts:'$eopts" |
|
flush stderr |
|
} on error {repl_error erropts} { |
|
rputs stderr "error1 in repl_handler: $repl_error" |
|
rputs stderr "-------------" |
|
rputs stderr "$::errorInfo" |
|
rputs stderr "-------------" |
|
set stdinreader [fileevent $inputchan readable] |
|
if {![string length $stdinreader]} { |
|
rputs stderr "*> $inputchan reader inactive" |
|
} else { |
|
rputs stderr "*> $inputchan reader active" |
|
} |
|
if {[chan eof $inputchan]} { |
|
rputs stderr "todo - attempt restart of repl on input channel: $inputchan in next loop" |
|
catch {set ::punk::ns::ns_current "::"} |
|
#todo set flag to restart repl ? |
|
} else { |
|
rputs stderr "continuing.." |
|
} |
|
flush stderr |
|
} |
|
|
|
|
|
try { |
|
set maxlinenum [expr {[llength $stdinlines] -1}] |
|
set linenum 0 |
|
foreach line $stdinlines { |
|
#puts stderr "----->line: [ansistring VIEW -lf 1 $line] commandstr:[ansistring VIEW -lf 1 $commandstr]" |
|
set last_repl_char "" ;#last char emitted by this handler to stdout/stderr |
|
set lastoutchar "" |
|
set lasterrchar "" |
|
|
|
|
|
#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 <sp><right-arrow> 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} |
|
set line "" |
|
#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::ansistrip $commandstr] |
|
} |
|
} |
|
|
|
if {$commandstr ne ""} { |
|
append commandstr \n |
|
} |
|
|
|
set stdinconf [fconfigure $inputchan] |
|
if {$::tcl_platform(platform) eq "windows" && [dict get $stdinconf -encoding] ni [list unicode utf-16 utf-8]} { |
|
#some long console inputs are split weirdly when -encoding and -translation are left at defaults - requiring extra enter-key to get repl to process. |
|
#experiment to see if using iso8859-1 (raw bytes) and handling line endings manually gives insight. |
|
# - do: chan conf stdin -encoding iso859-1 -translation lf |
|
|
|
#first command after configuring stdin this way seems to be interpreted with wrong encoding - subsequent commands work - review |
|
|
|
#this branch only works on tcl8.7+ |
|
#It seems to fix the issue with holding down enter-key and getting extra blank lines, but |
|
# it breaks copy-paste (encoding issue?) |
|
|
|
|
|
#puts "--inputchan:$inputchan> [fconfigure $inputchan]" |
|
|
|
append commandstr $line |
|
puts "1=============>[string length $commandstr] bytes , [ansistring VIEW $commandstr] , info complete:[info complete $line] stdinconf:$stdinconf" |
|
set commandstr [string range $commandstr 0 end-3] |
|
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]" |
|
} 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 |
|
} |
|
|
|
#puts "=============>[string length $commandstr] bytes , [string map [list \r -r- \n -n-] $commandstr] , info complete:[info complete $line]" |
|
set last_repl_char "\n" ;#this is actually the eol from stdin |
|
screen_last_char_add "\n" stdin $line |
|
|
|
|
|
|
|
#append commandstr \n |
|
|
|
|
|
if {[info complete $commandstr] && [string index $commandstr end] ne "\\"} { |
|
#set commandstr [overtype::renderline -expand_right 1 "" $commandstr] |
|
|
|
|
|
set ::repl::output_stdout "" |
|
set ::repl::output_stderr "" |
|
set outstack [list] |
|
set errstack [list] |
|
|
|
|
|
#oneshot repl debug |
|
set wordparts [regexp -inline -all {\S+} $commandstr] |
|
lassign $wordparts cmd_firstword cmd_secondword |
|
if {$cmd_firstword eq "debugrepl"} { |
|
if {$cmd_secondword in [list 0 cancel]} { |
|
set ::punk::repl::debug_repl 0 |
|
} else { |
|
if {[string is integer -strict $cmd_secondword]} { |
|
incr ::punk::repl::debug_repl $cmd_secondword |
|
} else { |
|
incr ::punk::repl::debug_repl |
|
} |
|
} |
|
#set commandstr "set ::punk::repl::debug_repl" |
|
set commandstr "" |
|
} |
|
if {$::punk::repl::debug_repl > 100} { |
|
proc debug_repl_emit {msg} [string map [list %p% [list $debugprompt]] { |
|
set p %p% |
|
#don't auto-append \n even if missing. |
|
#we may want to use debug_repl_emit with multiple calls for one output line |
|
#if {[string index $msg end] ne "\n"} { |
|
# set msg "$msg\n" |
|
#} |
|
#set last_char [string index $::repl::screen_last_chars end] |
|
set last_char [screen_last_char_get] |
|
if {$last_char ne "\n"} { |
|
set clearance "\n" |
|
} else { |
|
set clearance "" |
|
} |
|
#use pseudo-channel debugreport |
|
rputs debugreport $clearance$p[string map [list \n \n$p] $msg] |
|
}] |
|
set info "" |
|
append info "repl loopinstance: $loopinstance debugrepl remaining: [expr {[set ::punk::repl::debug_repl]-1}]\n" |
|
append info "commandstr: [punk::ansi::ansistring::VIEW $commandstr]\n" |
|
set lastrunchunks [tsv::get repl runchunks-[tsv::get repl runid]] |
|
append info "lastrunchunks\n" |
|
append info "chunks: [llength $lastrunchunks]\n" |
|
append info "namespace: $::punk::ns::ns_current" |
|
debug_repl_emit $info |
|
} else { |
|
proc debug_repl_emit {msg} {return} |
|
} |
|
|
|
#----------------------------------------- |
|
#review! |
|
#work around weird behaviour in tcl 8.6 & 8.7a5 (at least) part1 |
|
#https://wiki.tcl-lang.org/page/representation |
|
#/scriptlib/tests/listrep_bug.tcl |
|
#after the uplevel #0 $commandstr call |
|
# vars within the script that were set to a list, and have no string-rep, will generate a string-rep once the script (commandstr) is unset, or set to another value |
|
global run_command_string |
|
set run_command_string "$commandstr\n" ;#add anything that won't affect script. |
|
global run_command_cache |
|
#----------------------------------------- |
|
|
|
set repl_runid [tsv::incr repl runid] |
|
tsv::set repl runchunks-$repl_runid [list] ;#last_run_display |
|
catch { |
|
tsv::unset repl runchunks-[expr {$repl_runid - 10}] |
|
} |
|
|
|
#set ::repl::last_unknown "" |
|
tsv::set repl last_unknown "" |
|
#*********************************************************** |
|
#don't use puts,rputs or debug_repl_emit in this block |
|
#*********************************************************** |
|
|
|
#if {[string length [dict get $running_config color_stdout]] && [punk::console::colour]} { |
|
# lappend outstack [shellfilter::stack::add stdout ansiwrap -settings [list -colour [dict get $running_config color_stdout]]] |
|
#} |
|
#lappend outstack [shellfilter::stack::add stdout tee_to_var -settings {-varname ::repl::output_stdout}] |
|
#if {[string length [dict get $running_config color_stderr]] && [punk::console::colour]} { |
|
# lappend errstack [shellfilter::stack::add stderr ansiwrap -settings [list -colour [dict get $running_config color_stderr]]] |
|
#} |
|
|
|
variable codethread |
|
variable codethread_cond |
|
variable codethread_mutex |
|
|
|
lappend errstack [shellfilter::stack::add stderr tee_to_var -settings {-varname ::repl::output_stderr}] |
|
#thread::transfer $codethread stderr |
|
|
|
#chan configure stdout -buffering none |
|
#JMN |
|
fileevent $inputchan readable {} |
|
set reading 0 |
|
#don't let unknown use 'args' to convert commandstr to list |
|
#=============================================================================== |
|
#Actual command call |
|
#puts "____>[rep $commandstr]" |
|
#=============================================================================== |
|
if {[string equal -length [string length "repl_runraw "] "repl_runraw " $commandstr]} { |
|
#pass unevaluated command to runraw |
|
set status [catch {uplevel #0 [list runraw $commandstr]} raw_result] |
|
} else { |
|
#puts stderr "repl uplevel 0 '$run_command_string'" |
|
#JMN |
|
#puts stderr "sending to codethread::runscript $run_command_string" |
|
tsv::set codethread_$codethread status -1 |
|
thread::send -async $codethread [list punk::repl::codethread::runscript $run_command_string] |
|
thread::mutex lock $codethread_mutex |
|
while {[set status [tsv::get codethread_$codethread status]] == -1} { |
|
thread::cond wait $codethread_cond $codethread_mutex 50 |
|
update ;#we need a full update here to allow interrupts to be processed |
|
#While update is often considered risky - here we know our input channel readable event has been disabled - so re-entrancy shouldn't be possible. |
|
#however - child thread can send quit - transferring processing from here back to repl::start - which then ends - making a mess (child not yet finished when trying to tidy up) |
|
#we should give the child a way to quit by setting a tsv we pick up here *after the while loop* - and then we can set done. |
|
} |
|
thread::mutex unlock $codethread_mutex |
|
set raw_result [tsv::get codethread_$codethread result] |
|
lassign [tsv::get codethread_$codethread info] _o lastoutchar_codethread _e lasterrchar_codethread |
|
|
|
#set status [catch { |
|
# thread::send $ |
|
# uplevel 1 {namespace inscope $::punk::ns::ns_current $run_command_string} |
|
#} raw_result] |
|
|
|
} |
|
#puts stderr "repl raw_result: $raw_result" |
|
#set result $raw_result |
|
#append result ""; #copy on write |
|
#copy on write |
|
|
|
#append result $raw_result "" |
|
set result [string cat $raw_result ""] |
|
#puts stderr "-->>$result<--" |
|
#=============================================================================== |
|
|
|
flush stdout |
|
flush stderr |
|
#foreach s [lreverse $outstack] { |
|
# shellfilter::stack::remove stdout $s |
|
#} |
|
#foreach s [lreverse $errstack] { |
|
# shellfilter::stack::remove stderr $s |
|
#} |
|
|
|
#----------------------------------------- |
|
#list/string-rep bug workaround part 2 |
|
#todo - set flag based on punk::lib::system::has_script_var_bug |
|
lappend run_command_cache $run_command_string |
|
#puts stderr "run_command_string rep: [rep $run_command_string]" |
|
if {[llength $run_command_cache] > 2000} { |
|
set run_command_cache [lrange $run_command_cache 1750 end] |
|
} |
|
#----------------------------------------- |
|
|
|
#screen_last_char_add [string index $lastoutchar_codethread$lasterrchar_codethread end] "stdout/stderr" |
|
|
|
|
|
#set lastoutchar [string index [punk::ansi::ansistrip $::repl::output_stdout] end] |
|
#set lasterrchar [string index [punk::ansi::ansistrip $::repl::output_stderr] end] |
|
|
|
#to determine whether cursor is back at col0 of newline |
|
#screen_last_char_add [string index $lastoutchar$lasterrchar end] "stdout/stderr" |
|
|
|
|
|
#??? |
|
#screen_last_char_add [string index $lastoutchar$lastoutchar_codethread$lasterrchar$lasterrchar_codethread end] "stdout/stderr" |
|
screen_last_char_add [string index $lastoutchar_codethread$lasterrchar_codethread end] "stdout/stderr" |
|
|
|
|
|
set result_is_chunk_list 0 |
|
#------ |
|
#todo - fix. It doesn't make much sense to only detect if the unknown command occurred in first word. |
|
#e.g set x [something arg] not detected vs something arg |
|
#also - unknown commands aren't the only things that can write directly to the os handles stderr & stdout |
|
set last_unknown [tsv::get repl last_unknown] |
|
if { |
|
[string length $last_unknown] && \ |
|
[string equal -length [string length $last_unknown] $last_unknown $line] |
|
} { |
|
#can't currently detect stdout/stderr writes from unknown's call to exec |
|
#add a clearance newline for direct unknown calls for now |
|
#there is usually output anyway - but we will get an extra blank line now even for a call that only had an exit code |
|
# |
|
# |
|
set unknown_clearance "\n* repl newline" |
|
screen_last_char_add "\uFFFF" clearance "clearance after direct unknown call" |
|
if {[tsv::llength repl runchunks-$repl_runid]} { |
|
if {$status == 0} { |
|
set result [tsv::get repl runchunks-$repl_runid] ;#last_run_display |
|
} else { |
|
|
|
} |
|
set result_is_chunk_list 1 |
|
} |
|
} |
|
#------ |
|
#ok to use repl::screen_needs_clearance from here down.. (code smell - proc only valid use in narrow context) |
|
#*********************************************************** |
|
#rputs -nonewline stderr $unknown_clearance |
|
if {$::punk::repl::debug_repl > 0} { |
|
set lastcharinfo "\n" |
|
set whatcol [string repeat " " 12] |
|
foreach cinfo $::repl::screen_last_char_list { |
|
lassign $cinfo c whatinfo whyinfo |
|
set cdisplay [punk::ansi::ansistring::VIEW -lf 1 -vt 1 $c] |
|
#assertion cdisplay has no raw newlines |
|
if {[punk::char::ansifreestring_width $cdisplay] == 1} { |
|
set cdisplay "$cdisplay " ;#make 2 wide |
|
} |
|
if {[string match repl-debugreport* $whatinfo]} { |
|
#exclude noise debug_repl_emit - but still show the last_char |
|
set whysummary "" |
|
} else { |
|
#set whysummary [string map [list \n "-n-"] $whyinfo] |
|
set whysummary [punk::ansi::ansistring::VIEW -lf 1 -vt 1 $whyinfo] |
|
} |
|
set whatinfo [string range $whatinfo$whatcol 0 [string length $whatcol]] |
|
append lastcharinfo "$cdisplay $whatinfo $whysummary\n" |
|
} |
|
debug_repl_emit "screen_last_chars: $lastcharinfo" |
|
} |
|
|
|
debug_repl_emit "lastoutchar:'$lastoutchar' lasterrchar: '$lasterrchar'" |
|
if {$status == 0} { |
|
debug_repl_emit "command call status: $status OK" |
|
} else { |
|
debug_repl_emit "command call status: $status ERR" |
|
} |
|
|
|
|
|
|
|
|
|
#puts stderr "<output>'$::repl::output_stdout' lastoutchar:'$lastoutchar' result:'$result'" |
|
#$command is an unevaluated script at this point |
|
# so may not be a well formed list e.g 'set x [list a "b"]' |
|
#- lindex $command would sometimes fail |
|
#if {[lindex $command 0] eq "runx"} {} |
|
|
|
|
|
if { |
|
[string equal -length [string length "d/ "] "d/ " $commandstr] || \ |
|
[string equal "d/\n" $commandstr] || \ |
|
[string equal -length [string length "dd/ "] "dd/ " $commandstr] || \ |
|
[string equal "dd/\n" $commandstr] || \ |
|
[string equal -length [string length "./ "] "./ " $commandstr] || \ |
|
[string equal "./\n" $commandstr] || \ |
|
[string equal -length [string length "../ "] "../ " $commandstr] || \ |
|
[string equal "../\n" $commandstr] || \ |
|
[string equal -length [string length "runx "] "runx " $commandstr] || \ |
|
[string equal -length [string length "sh_runx "] "sh_runx " $commandstr] || \ |
|
[string equal -length [string length "runout "] "runout " $commandstr] || \ |
|
[string equal -length [string length "sh_runout "] "sh_runout " $commandstr] || \ |
|
[string equal -length [string length "runerr "] "runerr " $commandstr] || \ |
|
[string equal -length [string length "sh_runerr "] "sh_runerr " $commandstr] |
|
|
|
} { |
|
if {[tsv::llength repl runchunks-$repl_runid]} { |
|
set result [tsv::get repl runchunks-$repl_runid] ;#last_run_display |
|
set result_is_chunk_list 1 |
|
} |
|
} |
|
|
|
# -- --- --- --- --- --- --- --- --- --- |
|
##an attempt to preserve underlying rep |
|
##this is not for performance - just to be less disruptive to underlying rep to aid in learning/debugging |
|
# -- --- --- --- --- --- --- --- --- --- |
|
# JN 2023 - The lrange operation is destructive to path internal representation |
|
# The lrange operation is destructive to strings with leading/trailing newlines |
|
# -- --- --- --- --- --- --- --- --- --- |
|
#set saved_errorCode $::errorCode |
|
#set saved_errorInfo $::errorInfo |
|
#if {[catch {lrange $result 0 end} result_as_list]} { |
|
# set is_result_empty [expr {$result eq ""}] |
|
# set ::errorCode $saved_errorCode |
|
# set ::errorInfo $saved_errorInfo |
|
#} else { |
|
# set is_result_empty [expr {[llength $result_as_list] == 0}] |
|
#} |
|
# -- --- --- --- --- --- --- --- --- --- |
|
#set resultrep [::tcl::unsupported::representation $result] |
|
|
|
set is_result_empty [expr {$result eq ""}] |
|
|
|
#catch {puts stderr "yy--->[rep $::arglej]"} |
|
|
|
set reading 1 |
|
if {!$is_result_empty} { |
|
if {$status == 0} { |
|
if {[screen_needs_clearance]} { |
|
rputs -nonewline stderr \n |
|
} |
|
if {$result_is_chunk_list} { |
|
foreach c $result { |
|
lassign $c termchan text |
|
if {[string length $text]} { |
|
switch -- $termchan { |
|
result { |
|
#rputs stdout $resultprompt[string map [list \n "\n$resultprompt"] $text] |
|
set h [textblock::height $text] |
|
set promptcol [string repeat $resultprompt\n $h] |
|
set promptcol [string range $promptcol 0 end-1] |
|
rputs [textblock::join_basic -- $promptcol $text] |
|
|
|
#puts -nonewline stdout $text |
|
} |
|
resulterr { |
|
rputs stderr $resultprompt[string map [list \n "\n$resultprompt"] $text] |
|
} |
|
info { |
|
rputs stderr $infoprompt[string map [list \n "\n$infoprompt"] $text] |
|
} |
|
default { |
|
#rputs -nonewline $termchan $text |
|
set chanprompt "_ " |
|
rputs $termchan ${chanprompt}[string map [list \n "\n${chanprompt}"] $text] |
|
} |
|
} |
|
} |
|
} |
|
|
|
} else { |
|
#----------------------------------------------------------- |
|
# avoid repl forcing string rep of simple results. This is just to aid analysis using tcl::unsupported::representation |
|
#set rparts [split $result {}] |
|
#if {[lsearch $rparts \n] < 0} { |
|
# #type of $result unaffected |
|
# rputs "$resultprompt $result" |
|
#} else { |
|
# #$result will be a string due to use of string map |
|
# rputs $resultprompt[string map [list \n "\n$resultprompt"] $result] |
|
#} |
|
#----------------------------------------------------------- |
|
|
|
#we have copied rawresult using append with empty string - so our string interaction with result var here shouldn't affect the returned value |
|
#empty-string result handled in other branch |
|
if {![tsv::llength repl runchunks-$repl_runid]} { |
|
#write back to tsv var for use by punk::get_runchunks (underscore command) |
|
tsv::set repl runchunks-$repl_runid [list [list result $result]] |
|
} |
|
set flat [string map [list \r\n "" \n ""] $result] |
|
if {[string length $flat] == [string length $result]} { |
|
#no line-endings in data |
|
rputs "$resultprompt$result" |
|
} else { |
|
#if {[string index $result end] eq "\n"} { |
|
# set result [string range $result 0 end-1] |
|
#} |
|
if {[string length $flat] == 0} { |
|
if {[string range $result end-1 end] eq "\r\n"} { |
|
set result [string range $result 0 end-2] |
|
} else { |
|
set result [string range $result 0 end-1] |
|
} |
|
} |
|
#NOTE - textblock::height is the line height - not reflective of height of data with ansi-moves or things like sixels |
|
set h [textblock::height $result] |
|
set promptcol [string repeat $resultprompt\n $h] |
|
set promptcol [string range $promptcol 0 end-1] |
|
#promptcol is uniform-width lines, result may not be. We are ok to join with ragged rhs col here, so use join_basic instead of join |
|
rputs [textblock::join_basic -- $promptcol $result] |
|
|
|
#orig |
|
#rputs $resultprompt[string map [list \r\n "\n$resultprompt" \n "\n$resultprompt"] $result] |
|
} |
|
} |
|
doprompt "P% " |
|
} else { |
|
#tcl err |
|
if {$result_is_chunk_list} { |
|
foreach c [tsv::get repl runchunks-$repl_runid] { |
|
#last_run_display |
|
lassign $c termchan text |
|
if {[string length $text]} { |
|
switch -- $termchan { |
|
result { |
|
rputs stdout $resultprompt[string map [list \n "\n$resultprompt"] $text] |
|
#puts -nonewline stdout $text |
|
} |
|
resulterr { |
|
rputs stderr $resultprompt[string map [list \n "\n$resultprompt"] $text] |
|
} |
|
info { |
|
rputs stderr $infoprompt[string map [list \n "\n$infoprompt"] $text] |
|
} |
|
default { |
|
rputs -nonewline $termchan $text |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
set c [a yellow bold] |
|
set n [a] |
|
rputs stderr $c$result$n |
|
#tcl err hint prompt - lowercase |
|
doprompt "p% " |
|
} |
|
} else { |
|
#doprompt "P% " "green normal" |
|
if {$linenum == 0} { |
|
doprompt "P% " "green normal" |
|
screen_last_char_add " " empty empty |
|
} else { |
|
doprompt "\nP% " "green normal" |
|
screen_last_char_add "\n" empty empty ;#add \n to indicate noclearance required |
|
} |
|
} |
|
#catch {puts stderr "zz1--->[rep $::arglej]"} |
|
#puts stderr "??? $commandstr" |
|
if {$::punk::repl::debug_repl > 0} { |
|
incr ::punk::repl::debug_repl -1 |
|
} |
|
set commandstr "" |
|
#catch {puts stderr "zz2---->[rep $::arglej]"} |
|
|
|
set lines [$editbuf lines] |
|
set buf_has_data 0 |
|
foreach ln $lines { |
|
if {[string trim $ln] ne ""} { |
|
set buf_has_data 1 |
|
} |
|
} |
|
if {$buf_has_data} { |
|
set editbufnext [punk::repl::class::class_editbuf new {}] |
|
lappend editbuf_list $editbufnext |
|
set editbuf_linenum_submitted 0 |
|
set editbuf $editbufnext |
|
} |
|
#editbuf |
|
|
|
} else { |
|
#append commandstr \n |
|
if {$::punk::repl::signal_control_c} { |
|
set ::punk::repl::signal_control_c 0 |
|
fileevent $inputchan readable {} |
|
rputs stderr "* console_control: control-c" |
|
flush stderr |
|
set c [a yellow bold] |
|
set n [a] |
|
rputs stderr "${c}repl interrupted$n" |
|
#set commandstr [list error "repl interrupted"] |
|
set commandstr "" |
|
doprompt ">_ " |
|
flush stdout |
|
|
|
} else { |
|
#Incomplete command |
|
# parse and determine outermost unclosed quote/bracket and include in prompt |
|
if {$linenum == $maxlinenum} { |
|
if {$rawmode} { |
|
#review |
|
#we haven't put the data following last le into commandstr - but we want to display proper completion status prior to enter being hit or more data coming in. |
|
#this could give spurious results for large pastes where buffering chunks it in odd places.? |
|
#it does however give sensible output for the common case of a small paste where the last line ending wasn't included |
|
set waiting [punk::lib::system::incomplete $commandstr[$editbuf line end]] |
|
} else { |
|
set waiting [punk::lib::system::incomplete $commandstr] |
|
} |
|
if {[llength $waiting]} { |
|
set c [lindex $waiting end] |
|
} else { |
|
#set c " " |
|
set c \u240a |
|
} |
|
doprompt ">$c " |
|
} |
|
} |
|
} |
|
|
|
incr linenum |
|
} |
|
|
|
|
|
if {$maxlinenum == -1} { |
|
#when in raw mode - no linefeed yet received |
|
#rputs stderr "repl: no complete input line: $commandstr" |
|
#screen_last_char_add "\n" empty empty |
|
|
|
screen_last_char_add [string index [$editbuf line end] end] stdinchunk stdinchunk |
|
|
|
} |
|
|
|
|
|
#fileevent $inputchan readable [list repl::repl_handler $inputchan $prompt_config] |
|
#catch {puts stderr "zend--->[rep $::arglej]"} |
|
|
|
|
|
} trap {POSIX} {e eopts} { |
|
rputs stderr "trap POSIX '$e' eopts:'$eopts" |
|
flush stderr |
|
} on error {repl_error erropts} { |
|
rputs stderr "error in repl_handler: $repl_error" |
|
rputs stderr "-------------" |
|
rputs stderr "$::errorInfo" |
|
rputs stderr "-------------" |
|
set stdinreader [fileevent $inputchan readable] |
|
if {![string length $stdinreader]} { |
|
rputs stderr "*> $inputchan reader inactive" |
|
} else { |
|
rputs stderr "*> $inputchan reader active" |
|
} |
|
if {[chan eof $inputchan]} { |
|
rputs stderr "todo - attempt restart of repl on input channel: $inputchan in next loop" |
|
catch {set ::punk::ns::ns_current "::"} |
|
#todo set flag to restart repl ? |
|
} else { |
|
rputs stderr "continuing.." |
|
} |
|
flush stderr |
|
} |
|
} |
|
|
|
proc repl::completion {context ebuf} { |
|
|
|
} |
|
|
|
namespace eval repl { |
|
proc init {args} { |
|
if {![info exists ::argv0]} { |
|
#error out before we create a thread - punk requires this - review |
|
error "::argv0 not set" |
|
} |
|
#in case -callback_interp wasn't explicitly defined - we make a guess based on how init was called as to whether this is being launched from a 'code' or root ("") interp. |
|
if {[catch {info level -1} caller]} { |
|
puts "repl::init from: global" |
|
set default_callback_interp "" |
|
} else { |
|
#puts "repl::init from: $caller" |
|
set default_callback_interp "code" |
|
} |
|
variable codethread |
|
variable codethread_cond |
|
variable codethread_mutex |
|
|
|
set opts [list -force 0 -safe 0 -safelog 0 -paths {} -callback_interp $default_callback_interp] |
|
foreach {k v} $args { |
|
switch -- $k { |
|
-force - -safe - -safelog - -paths - -callback_interp { |
|
dict set opts $k $v |
|
} |
|
default { |
|
error "repl::init unknown option '$k'. Known-options: [dict keys $opts]" |
|
} |
|
} |
|
} |
|
set opt_force [dict get $opts -force] |
|
set opt_safe [dict get $opts -safe] |
|
set opt_safelog [dict get $opts -safelog] |
|
if {$opt_safelog eq "0"} { |
|
set opt_safelog "" |
|
} |
|
if {[string is boolean -strict $opt_safelog]} { |
|
if {$opt_safelog} { |
|
set opt_safelog ::repl::interpextras::safe_msg |
|
} |
|
} |
|
dict set opts -safelog $opt_safelog |
|
|
|
#If we are launching a repl from within an interp - we need to tell the childthread how to call-back to the parent repl |
|
set opt_callback_interp [dict get $opts -callback_interp] |
|
|
|
if {$codethread ne "" && !$opt_force && [thread::exists $codethread] } { |
|
error "repl:init codethread: $codethread already exists. use -force 1 to override" |
|
} |
|
set codethread [thread::create -preserved] |
|
|
|
#review - naming of the possibly 2 cond variables parent and child thread |
|
set codethread_cond [thread::cond create] ;#repl::codethread_cond held by parent(repl) vs punk::repl::codethread::replthread_cond held by child(codethread) |
|
set codethread_mutex [thread::mutex create] |
|
|
|
thread::send $codethread [string map [list %args% [list $opts]\ |
|
%argv0% [list $::argv0]\ |
|
%argv% [list $::argv]\ |
|
%argc% [list $::argc]\ |
|
%replthread% [thread::id]\ |
|
%replthread_cond% $codethread_cond\ |
|
%replthread_interp% [list $opt_callback_interp]\ |
|
%tmlist% [list [tcl::tm::list]]\ |
|
%autopath% [list $::auto_path]\ |
|
] { |
|
set ::argv0 %argv0% |
|
set ::argv %argv% |
|
set ::argc %argc% |
|
tcl::tm::remove {*}[tcl::tm::list] |
|
tcl::tm::add {*}[lreverse %tmlist%] ;#Must be added in reverse order to get same order as original list! |
|
|
|
#this sets the auto_path in the thread but outside of the code interp that will be created. |
|
#It will also need to be added in that interp |
|
set ::auto_path %autopath% |
|
set tclmajorv [lindex [split [tcl::info::tclversion] .] 0] |
|
#jmn2 |
|
#puts stdout "CODETHREAD tm list" |
|
#puts stdout [join [tcl::tm::list] \n] |
|
#puts stdout "====================" |
|
#flush stdout |
|
#puts stdout "CODETHREAD autopath" |
|
#puts stdout [join $::auto_path \n] |
|
#puts stdout "====================" |
|
#flush stdout |
|
#if {[llength [info commands tcl::zipfs::root]]} { |
|
# set zipbase [file join [tcl::zipfs::root] app] ;#zipfs root has trailing slash - but file join does the right thing |
|
# if {"$zipbase" in [tcl::zipfs::mount]} { |
|
# puts stdout "//zipfs:/app/modules_tcl$tclmajorv exists: [file exists //zipfs:/app/modules_tcl$tclmajorv]" |
|
# } |
|
#} |
|
#puts stdout "====================" |
|
|
|
package require punk::console |
|
package require punk::repl::codethread |
|
package require shellfilter |
|
#package require shellrun |
|
package require textblock |
|
|
|
#md5 uses open so can't be directly called in a safe interp |
|
#it will need to delegate to a call here in the main interp of the codethread using an installed alias |
|
set md5version [package require md5] |
|
#we also need to 'package provide md5 $md5version' in the safe interp itself so that it won't override |
|
|
|
|
|
#punk::configure_unknown ;#must be called because we hacked the tcl 'unknown' proc |
|
|
|
#child codethread (outside of code interp) needs to know details of the calling repl |
|
set ::punk::repl::codethread::replthread %replthread% ;#point to thread id of parent thread (repl) |
|
set ::punk::repl::codethread::replthread_cond %replthread_cond% |
|
set ::punk::repl::codethread::replthread_interp %replthread_interp% |
|
|
|
# -- --- --- --- |
|
#procs to alias into the codethread interp |
|
#as we are doing string substitution on the whole block anyway, and these values are contant for the life of the thread, we may as well substitute hard values for things like replthread into these procs where possible |
|
# -- --- --- --- |
|
namespace eval ::repl::interphelpers { |
|
proc quit {args} { |
|
#child codethreads run in a 'code' interp therefore if they started another repl - it is within the 'code' interp in that thread |
|
# whereas the first repl launched in the process runs in root interp "" |
|
thread::send -async %replthread% [list interp eval %replthread_interp% ::punk::repl::quit] |
|
} |
|
proc editbuf args { |
|
thread::send %replthread% [list punk::repl::editbuf {*}$args] |
|
} |
|
proc escapeeval {script} { |
|
eval $script |
|
} |
|
proc do_after {args} { |
|
if {[llength $args] == 1} { |
|
return after {*}$args |
|
} |
|
set scr [lindex $args 1] |
|
after [lindex $args 0] [list punk::repl::codethread::runscript $scr] |
|
} |
|
proc repl_ensemble_unknown args { |
|
puts $args |
|
if {[llength $args] == 1} { |
|
return [namespace ensemble configure ::repl::interphelpers::repl_ensemble] |
|
} |
|
} |
|
proc colour args { |
|
set colour_state [thread::send %replthread% [list punk::console::colour]] |
|
if {[llength $args]} { |
|
#colour call was not a query |
|
set new_state [thread::send %replthread% [list punk::console::colour {*}$args]] |
|
if {[expr {$new_state}] ne [expr {$colour_state}]} { |
|
interp eval code [list punk::console::colour {*}$args] ;#align code interp's view of colour state with repl thread |
|
interp eval code [string map [list <cstate> $new_state] { |
|
#adjust channel transform stack |
|
set docolour [expr {<cstate>}] |
|
if {!$docolour} { |
|
set s [lindex $::codeinterp::outstack end] |
|
if {$s ne ""} { |
|
shellfilter::stack::remove stdout $s |
|
} |
|
set s [lindex $::codeinterp::errstack end] |
|
if {$s ne ""} { |
|
shellfilter::stack::remove stderr $s |
|
} |
|
} else { |
|
set running_config $::punk::config::running |
|
if {[string length [dict get $running_config color_stdout]]} { |
|
lappend ::codeinterp::outstack [shellfilter::stack::add stdout ansiwrap -settings [list -colour [dict get $running_config color_stdout]]] |
|
} |
|
if {[string length [dict get $running_config color_stderr]]} { |
|
lappend ::codeinterp::errstack [shellfilter::stack::add stderr ansiwrap -settings [list -colour [dict get $running_config color_stderr]]] |
|
} |
|
|
|
} |
|
}] |
|
} |
|
return $new_state |
|
} else { |
|
return $colour_state |
|
} |
|
#todo - add/remove shellfilter stacked ansiwrap |
|
} |
|
proc mode args { |
|
thread::send %replthread% [list punk::console::mode {*}$args] |
|
interp eval code [list ::punk::console::mode {*}$args] |
|
} |
|
proc cmdtype cmd { |
|
code invokehidden tcl:info:cmdtype $cmd |
|
} |
|
|
|
#punk repl tsv wrappers |
|
proc set_repl_last_unknown args { |
|
tsv::set repl last_unknown {*}$args |
|
} |
|
proc get_repl_runid args { |
|
if {[tsv::exists repl runid]} { |
|
return [tsv::get repl runid] |
|
} else { |
|
return 0 |
|
} |
|
} |
|
proc md5 args { |
|
::md5::md5 {*}$args |
|
} |
|
} |
|
namespace eval ::repl::interpextras { |
|
#install using safe::setLogCmd |
|
proc safe_msg {msg} { |
|
puts stderr "safebase: $msg" |
|
} |
|
} |
|
|
|
namespace eval ::repl::interphelpers::repl_ensemble { |
|
namespace export {[a-z]*} |
|
namespace ensemble create |
|
namespace ensemble configure [namespace current] -unknown ::repl::interphelpers::repl_ensemble_unknown |
|
variable replinfo |
|
set replinfo [dict create thread %replthread% interp %replthread_interp%] |
|
proc thread {} { |
|
return %replthread% |
|
} |
|
proc info {} { |
|
variable replinfo |
|
return $replinfo |
|
} |
|
proc eval {script} { |
|
thread::send %replthread% $script |
|
} |
|
proc stack {} { |
|
set iname %replthread_interp% |
|
set tid %replthread% |
|
lappend stack [list thread $tid interp $iname] |
|
while {$iname eq "code"} { |
|
set iname [thread::send $tid {set ::punk::repl::codethread::replthread_interp}] |
|
set tid [thread::send $tid {set ::punk::repl::codethread::replthread}] |
|
lappend stack [list thread $tid interp $iname] |
|
} |
|
return $stack |
|
} |
|
} |
|
namespace eval ::repl::interphelpers::subshell_ensemble { |
|
namespace export {[a-z]*} |
|
namespace ensemble create |
|
proc punk {} { |
|
interp eval code { |
|
package require punk::repl |
|
repl::init |
|
repl::start stdin |
|
} |
|
} |
|
proc safe {args} { |
|
interp eval code { |
|
package require punk::repl |
|
} |
|
interp eval code [list repl::init -safe 1 {*}$args] |
|
interp eval code [list repl::start stdin] |
|
} |
|
proc safebase {args} { |
|
interp eval code { |
|
package require punk::repl |
|
} |
|
interp eval code [list repl::init -safe 2 {*}$args] |
|
interp eval code [list repl::start stdin] |
|
} |
|
} |
|
# -- --- --- --- --- |
|
|
|
#puts "codethread:[thread::id] parent replthread:%replthread%" |
|
#flush stdout |
|
|
|
set args %args% |
|
set safe [dict get $args -safe] |
|
set safelog [dict get $args -safelog] |
|
set paths [list] |
|
if {[dict exists $args -paths]} { |
|
set paths [dict get $args -paths] |
|
} |
|
|
|
if {$safe == 1} { |
|
interp create -safe -- code |
|
if {[llength $paths]} { |
|
package require punk::island |
|
foreach p $paths { |
|
punk::island::add code $p |
|
} |
|
} |
|
|
|
#review argv0,argv,argc |
|
interp eval code { |
|
namespace eval ::codeinterp { |
|
variable errstack {} |
|
variable outstack {} |
|
} |
|
set ::argv0 %argv0% |
|
set ::auto_path %autopath% |
|
#puts stdout "safe interp" |
|
#flush stdout |
|
} |
|
interp eval code [list set ::tcl_platform(os) $::tcl_platform(os)] |
|
interp eval code [list set ::tcl_platform(osVersion) $::tcl_platform(osVersion)] |
|
interp eval code [list set ::tcl_platform(machine) $::tcl_platform(machine)] |
|
if {"stdout" in [chan names]} { |
|
interp share {} stdout code |
|
} else { |
|
interp share {} [shellfilter::stack::item_tophandle stdout] code |
|
} |
|
if {"stderr" in [chan names]} { |
|
interp share {} stderr code |
|
} else { |
|
interp share {} [shellfilter::stack::item_tophandle stderr] code |
|
} |
|
|
|
code alias ::md5::md5 ::repl::interphelpers::md5 |
|
code alias exit ::repl::interphelpers::quit |
|
} elseif {$safe == 2} { |
|
safe::interpCreate code -nested 1 |
|
#safe::setLogCmd ::repl::interpextras::safe_msg ;#using setLogcmd early will show the auto_path notice - which is *verbose* |
|
#while it may conceivably be useful in debugging safe itself - auto_path and tcl::tm::list can be inspected to show these values in the safe interp itself anyway - so early logging is of limited utility here. |
|
if {[llength $paths]} { |
|
package require punk::island |
|
foreach p $paths { |
|
punk::island::add code $p |
|
} |
|
} |
|
interp eval code { |
|
set ::argv0 %argv0% |
|
set ::argc 0 |
|
set ::argv {} |
|
set ::auto_path %autopath% |
|
#puts stdout "safe interp" |
|
#flush stdout |
|
namespace eval ::codeinterp { |
|
variable errstack {} |
|
variable outstack {} |
|
} |
|
} |
|
interp eval code [list set ::tcl_platform(os) $::tcl_platform(os)] |
|
interp eval code [list set ::tcl_platform(osVersion) $::tcl_platform(osVersion)] |
|
interp eval code [list set ::tcl_platform(machine) $::tcl_platform(machine)] |
|
|
|
#code invokehidden package require punk::lib |
|
if {"stdout" in [chan names]} { |
|
interp share {} stdout code |
|
} else { |
|
interp share {} [shellfilter::stack::item_tophandle stdout] code |
|
} |
|
if {"stderr" in [chan names]} { |
|
interp share {} stderr code |
|
} else { |
|
interp share {} [shellfilter::stack::item_tophandle stderr] code |
|
} |
|
#work around bug in safe base which won't load Tcl libs that have deeper nesting |
|
#(also affects tcllib page/plugins folder) |
|
set termversions [package versions term] |
|
set termv [lindex $termversions end] |
|
if {$termv ne ""} { |
|
set path [lindex [package ifneeded term $termv] end] ;#assuming path at end of something like "source .../term.tcl" |
|
set termbase [file dirname $path] |
|
safe::interpAddToAccessPath code [file join $termbase ansi] |
|
safe::interpAddToAccessPath code [file join $termbase ansi code] |
|
} |
|
#safe::interpAddToAccessPath code NUL |
|
if {$safelog ne ""} { |
|
#setting setLogCmd here gives some feedback for potentially interesting feedback regarding behaviour of things such as glob |
|
safe::setLogCmd $safelog |
|
} |
|
#code invokehidden source c:/repo/jn/shellspy/modules/punk/lib-0.1.1.tm |
|
|
|
code alias detok ::safe::DetokPath code |
|
|
|
#review - exit should do something slightly different |
|
# see ::safe::interpDelete |
|
code alias exit ::repl::interphelpers::quit |
|
|
|
code alias ::md5::md5 ::repl::interphelpers::md5 |
|
interp eval code [list package provide md5 $md5version] |
|
} else { |
|
interp create code |
|
interp eval code { |
|
#safe !=1 and safe !=2, tmlist: %tmlist% |
|
set ::argv0 %argv0% |
|
set ::argv %argv% |
|
set ::argc %argc% |
|
set ::auto_path %autopath% |
|
tcl::tm::remove {*}[tcl::tm::list] |
|
tcl::tm::add {*}[lreverse %tmlist%] |
|
puts "code interp chan names-->[chan names]" |
|
namespace eval ::codeinterp { |
|
variable errstack {} |
|
variable outstack {} |
|
} |
|
|
|
# -- --- |
|
#review |
|
#we have to blow some time on a rescan to provide more deterministic ordering (match behaviour of initial thread regarding pkg precedence) |
|
#review - can we speed that scan up? |
|
##catch {package require flobrudder-nonexistant} |
|
# -- --- |
|
|
|
if {[catch { |
|
package require vfs |
|
package require vfs::zip |
|
} errM]} { |
|
puts stderr "repl code interp can't load vfs,vfs::zip" |
|
} |
|
|
|
#puts stderr ----- |
|
#puts stderr [join $::auto_path \n] |
|
#puts stderr ----- |
|
|
|
if {[catch { |
|
package require punk::config |
|
package require punk::ns |
|
#puts stderr "loading natsort" |
|
#natsort has 'application mode' which can exit. |
|
#Requiring it shouldn't trigger application - but zipfs/vfs interactions confused it in some early versions |
|
package require natsort |
|
#catch {package require packageTrace} |
|
package require punk |
|
package require shellrun |
|
|
|
package require shellfilter |
|
set running_config $::punk::config::running |
|
if {[string length [dict get $running_config color_stderr]] && [punk::console::colour]} { |
|
lappend ::codeinterp::errstack [shellfilter::stack::add stderr ansiwrap -settings [list -colour [dict get $running_config color_stderr]]] |
|
} |
|
if {[string length [dict get $running_config color_stdout]] && [punk::console::colour]} { |
|
lappend ::codeinterp::outstack [shellfilter::stack::add stdout ansiwrap -settings [list -colour [dict get $running_config color_stdout]]] |
|
} |
|
|
|
package require textblock |
|
} errM]} { |
|
puts stderr "========================" |
|
puts stderr "code interp error:" |
|
puts stderr $errM |
|
puts stderr $::errorInfo |
|
puts stderr "========================" |
|
error "$errM" |
|
} |
|
} |
|
} |
|
code alias repl ::repl::interphelpers::repl_ensemble |
|
code alias subshell ::repl::interphelpers::subshell_ensemble |
|
code alias quit ::repl::interphelpers::quit |
|
code alias editbuf ::repl::interphelpers::editbuf |
|
code alias colour ::repl::interphelpers::colour |
|
code alias mode ::repl::interphelpers::mode |
|
#code alias after ::repl::interphelpers::do_after |
|
|
|
code alias ::punk::set_repl_last_unknown ::repl::interphelpers::set_repl_last_unknown |
|
code alias ::punk::get_repl_runid ::repl::interphelpers::get_repl_runid |
|
|
|
#JMN |
|
#code alias cmdtype ::repl::interphelpers::cmdtype |
|
#temporary debug aliases - deliberate violation of safety provided by safe interp |
|
code alias escapeeval ::repl::interphelpers::escapeeval |
|
|
|
#puts stderr "returning threadid" |
|
#puts stderr [thread::id] |
|
|
|
return [thread::id] |
|
}] |
|
} |
|
#init - don't auto init - require init with possible options e.g -safe |
|
} |
|
package provide punk::repl [namespace eval punk::repl { |
|
variable version |
|
set version 0.1 |
|
}] |
|
|
|
#repl::start $program_read_stdin_pipe |
|
|
|
|
|
|
|
|