wdb For certain uses I need a very minimalistic shell window where Tk is not yet loaded. Below my shell.tcl.
Purpose: (1) play around with commands, (2) with my OS (Linux) send commands to the shell. The app name is like filename: shell.tcl.
Example: to write letter a to console window I use the command send shell.tcl [list evalExpr "puts a"].
Key <Control-Up> sets cursor to previous command line.
Key <Enter> on previous command line copies line to end.
Update some fine-tuning such as busy cursor, double-click for braces, brackets, parens, international quotes, etc.
Keys <Control-Plus> and <Control-Minus> increase or decrease font size.
These predefined commands are available inside shell.tcl:
The commands tcl::mathop::* and tcl::mathfunc::* are imported inside shell.tcl so you cant enter [+ 3 4 [* 5 6]]] etc.
License (as always) OLL. Have fun!
#!/usr/bin/wish package require Tk tk appname shell.tcl bind [winfo class .] <Destroy> exit # create console window destroy .shell pack [frame .shell] -expand yes -fill both grid [text .shell.t\ -wrap none\ -font "Monospace 10"\ -yscrollcommand {.shell.v set}\ -xscrollcommand {.shell.h set}]\ [scrollbar .shell.v -orient vertical -command {.shell.t yview}]\ -sticky news grid [scrollbar .shell.h\ -orient horizontal\ -command {.shell.t xview}]\ -sticky news grid rowconfigure .shell 0 -weight 1 grid columnconfigure .shell 0 -weight 1 bind . <FocusIn> {focus .shell.t} event add <<ToggleWrap>> <Escape><Key-space> bind .shell.t <<ToggleWrap>> { if {[.shell.t cget -wrap] eq "none"} then { .shell.t configure -wrap char grid forget .shell.h } else { .shell.t configure -wrap none grid .shell.h -sticky news } after idle [list .shell.t see insert] break } bind .shell <<ToggleWrap>> [bind .shell.t <<ToggleWrap>>] bind . <<ToggleWrap>> [bind .shell.t <<ToggleWrap>>] after idle "event generate .shell.t <<ToggleWrap>>" .shell.t tag configure prompt -foreground blue .shell.t tag configure result -foreground green .shell.t tag configure error -foreground red bind .shell.t <Double-1> { if {[selectToClosingChar %W]} then break } bind .shell.t <Control-plus> { apply {win {lassign [$win cget -font] font size $win configure -font [list $font [incr size 2]]}} %W } bind .shell.t <Control-minus> { apply {win {lassign [$win cget -font] font size if {$size >= 8} then { $win configure -font [list $font [incr size -2]]}}} %W } bind .shell.t <BackSpace> { if {[%W tag ranges sel] ne ""} then continue if {([%W get insert-1chars] eq " " && "prompt" in [%W tag names insert-2chars]) || "prompt" in [%W tag names insert-1chars]} then break } bind .shell.t <Left> { if {[apply { win { if {[$win tag ranges sel] ne ""} then { $win mark set insert sel.first $win tag remove sel 1.0 end return true } elseif { "prompt" in [$win tag names insert-1chars] || "prompt" in [$win tag names insert-2chars] } then { lassign [$win tag prevrange prompt insert+1chars 1.0] start if {[$win compare $start != 1.0]} then { $win mark set insert $start-1chars } return true } return false } } %W]} then { %W see insert break } } bind .shell.t <Control-Left> continue bind .shell.t <Shift-Left> continue bind .shell.t <Delete> { if {[%W tag ranges sel] ne ""} then continue if {"prompt" in [%W tag names insert] || ("prompt" in [%W tag names insert-1chars] && [%W get insert] eq " ") } then break } bind .shell.t <Right> { if {[%W tag ranges sel] ne ""} then { %W mark set insert sel.last %W tag remove sel 1.0 end break } elseif {"prompt" in [%W tag names insert+1chars]} then { %W mark set insert [lindex [%W tag nextrange prompt insert] end]+1chars break } } bind .shell.t <Control-Right> continue bind .shell.t <Shift-Right> continue bind .shell.t <Key-Home> {if {[cursorHome %W]} then break} bind .shell.t <Key-Return> { if {[processReturnKey %W]} then break } bind .shell.t <Shift-Return> { %W insert insert \n break } bind .shell.t <Control-Up> { apply { win { lassign\ [$win tag prevrange prompt "insert linestart - 1chars"] - idx if {$idx eq ""} then { set idx 1.0 } elseif {[$win get $idx] eq " "} then { append idx +1chars } $win mark set insert $idx } } %W break } proc busyCursor {{win .shell.t}} { set cursor [$win cget -cursor] $win configure -cursor watch update $win configure -cursor $cursor } proc evalInputIfComplete {{win .shell.t}} { set src [inputText $win] if {![info complete $src]} then { return false } else { $win mark set insert [$win index end] if {[catch {set result [shell eval $src]} err]} then { message $err error } else { message $result } return true } } proc processReturnKey {{win .shell.t}} { busyCursor $win if {![copyCurrentLine $win]} then { $win insert "insert lineend" \n "" # $win see insert evalInputIfComplete $win after idle "$win see insert" } return true } proc message {msg {tag result} {win .shell.t}} { $win mark set insert end if {$msg ne ""} then { $win insert insert $msg $tag } prompt $win $win see insert } proc cursorHome {{win .shell.t}} { set range\ [$win tag prevrange prompt "insert lineend" "insert linestart"] if {$range eq ""} then { return false } else { lassign $range - start if {[$win get $start] eq " "} then { $win mark set insert $start+1chars } else { $win mark set insert $start } return true } } proc prompt {{win .shell.t}} { $win mark set insert end if {[$win compare "insert linestart" < "insert lineend"]} then { $win insert insert \n "" } $win insert insert "%" prompt " " $win see insert } prompt .shell.t delete 1.0 proc inputText {{win .shell.t}} { # content of last input line set promptRange [$win tag prevrange prompt end 1.0] lassign "$promptRange 0 1.0" - start if {[$win get $start] eq " "} then { set start $start+1chars } $win get $start end-1chars } proc copyCurrentLine {{win .shell.t}} { # copy current line to end of .shell.t set prev [$win tag prevrange prompt insert 1.0] if {$prev eq ""} then { set start 1.0 } else { lassign $prev - start if {[$win get $start] eq " "} then { set start $start+1chars } } set next [$win tag nextrange prompt $start end] if {$next eq ""} then { # at end of text window, so do nothing return false } else { lassign $next end0 set end $end0 set nextResultRange [$win tag nextrange result $start end] if {$nextResultRange ne ""} then { lassign $nextResultRange end1 if {[$win compare $end1 < $end]} then { set end $end1 } } set nextErrorRange [$win tag nextrange error $start end] if {$nextErrorRange ne ""} then { lassign $nextErrorRange end2 if {[$win compare $end2 < $end]} then { set end $end2 } } set line [string trim [$win get $start $end] \n] $win mark set insert end $win insert insert $line $win see insert return true } } proc shellPuts args { set t .shell.t # $t insert insert \n "" switch -exact -- [llength $args] { 1 { lassign $args arg $t insert end $arg\n result $t see insert } 2 { lassign $args how text switch -exact -- $how { -nonewline { $t insert end $text result } stdout { $t insert end $text\n result } stderr { $t insert end $text\n error } default { shell eval __puts__ $args } } } default { shell eval __puts__ $args } } } proc selectToClosingChar win { busyCursor $win set char [$win get insert] # international quotes set dict { „ “ ‚ ‘ “ ” ‘ ’ » « › ‹ « » ‹ › ( ) [ ] } set keys [dict keys $dict] # if {$char ni [list \u007b \u0022 {*}$keys]} then { return false } switch -exact -- $char { \" { set closeChar \" } \{ { set closeChar \} } default { set closeChar [dict get $dict $char] } } set start insert while true { set end [$win search $closeChar $start+1chars end] if {$end eq ""} then { return false } set txt [$win get insert $end+1chars] if {$char in $keys} then { lappend map \{ " " \} " " switch -exact -- $char { ( { lappend map ( \{ ) \} } \[ { lappend map \[ \{ \] \} } } set txt [string map $map $txt] } if {[info complete $txt]} then { $win tag remove sel 1.0 end $win tag add sel insert $end+1chars return true } set start $end } return false } interp create shell interp alias shell send "" send interp alias shell wm "" wm interp alias shell winfo "" winfo shell eval rename puts __puts__ shell alias puts shellPuts shell eval { proc echo args {puts $args} proc aloud args {putsFlat $args; uplevel $args} proc putsFlat str {puts [regsub -all {\n\s*} $str _]} proc putsVars args { puts [concat {*}[lmap var $args {list $var [uplevel set $var]}]] } proc sourceCode proc { # return source code of procedure $proc set proc [uplevel [list namespace origin $proc]] set arglist [list ] foreach arg [info args $proc] { if {[info default $proc $arg defaultvalue]} then { lappend arglist [list [list $arg] $defaultvalue] } else { lappend arglist [list $arg] } } list proc [namespace origin $proc] $arglist [info body $proc] } proc cat file { set chan [open $file r] set result [read $chan] close $chan set result } } proc evalExpr expr { if {[catch {shell eval $expr} result]} then { message $result error } else { message $result } } # main if {[info exists argv] && $argv ne ""} then { evalExpr [lindex $argv 0] } else { .shell.t delete 1.0 end catch {.shell.t insert end [exec fortune]\n\n result} .shell.t insert end\ "Hint: Key sequence Escape, Space toggles text wrap!\n" prompt prompt } shell eval namespace import tcl::mathop::* tcl::mathfunc::*