Applied one other trick to use the Tk command "font measure" to
[xcircuit.git] / lib / tcl / tkcon.tcl
blob1e6c221d045154138c44397b4fd494b121366af3
1 #!/bin/sh
2 # \
3 exec ${XCIRCUIT_WISH:=wish} "$0" ${1+"$@"}
6 ## tkcon.tcl
7 ## Enhanced Tk Console, part of the VerTcl system
8 ##
9 ## Originally based off Brent Welch's Tcl Shell Widget
10 ## (from "Practical Programming in Tcl and Tk")
12 ## Thanks to the following (among many) for early bug reports & code ideas:
13 ## Steven Wahl <steven@indra.com>, Jan Nijtmans <nijtmans@nici.kun.nl>
14 ## Crimmins <markcrim@umich.edu>, Wart <wart@ugcs.caltech.edu>
16 ## Copyright 1995-2001 Jeffrey Hobbs
17 ## Initiated: Thu Aug 17 15:36:47 PDT 1995
19 ## jeff.hobbs@acm.org, jeff@hobbs.org
21 ## source standard_disclaimer.tcl
22 ## source bourbon_ware.tcl
25 # Proxy support for retrieving the current version of Tkcon.
27 # Mon Jun 25 12:19:56 2001 - Pat Thoyts <Pat.Thoyts@bigfoot.com>
29 # In your tkcon.cfg or .tkconrc file put your proxy details into the
30 # `proxy' member of the `PRIV' array. e.g.:
32 # set ::tkcon::PRIV(proxy) wwwproxy:8080
34 # If you want to be prompted for proxy authentication details (eg for
35 # an NT proxy server) make the second element of this variable non-nil - eg:
37 # set ::tkcon::PRIV(proxy) {wwwproxy:8080 1}
39 # Or you can set the above variable from within tkcon by calling
41 # tkcon master set ::tkcon:PRIV(proxy) wwwproxy:8080
44 if {$tcl_version < 8.0} {
45 return -code error "tkcon requires at least Tcl/Tk8"
46 } else {
47 package require Tk $tcl_version
50 catch {package require bogus-package-name}
51 foreach pkg [info loaded {}] {
52 set file [lindex $pkg 0]
53 set name [lindex $pkg 1]
54 if {![catch {set version [package require $name]}]} {
55 if {[string match {} [package ifneeded $name $version]]} {
56 package ifneeded $name $version [list load $file $name]
60 catch {unset pkg file name version}
62 # Tk 8.4 makes previously exposed stuff private.
63 # FIX: Update tkcon to not rely on the private Tk code.
65 if {![llength [info globals tkPriv]]} {
66 ::tk::unsupported::ExposePrivateVariable tkPriv
68 foreach cmd {SetCursor UpDownLine Transpose ScrollPages} {
69 if {![llength [info commands tkText$cmd]]} {
70 ::tk::unsupported::ExposePrivateCommand tkText$cmd
74 # Initialize the ::tkcon namespace
76 namespace eval ::tkcon {
77 # The OPT variable is an array containing most of the optional
78 # info to configure. COLOR has the color data.
79 variable OPT
80 variable COLOR
82 # PRIV is used for internal data that only tkcon should fiddle with.
83 variable PRIV
84 set PRIV(WWW) [info exists embed_args]
87 ## ::tkcon::Init - inits tkcon
89 # Calls: ::tkcon::InitUI
90 # Outputs: errors found in tkcon's resource file
92 proc ::tkcon::Init {} {
93 variable OPT
94 variable COLOR
95 variable PRIV
96 global tcl_platform env argc argv tcl_interactive errorInfo
98 if {![info exists argv]} {
99 set argv {}
100 set argc 0
103 set tcl_interactive 1
105 if {[info exists PRIV(name)]} {
106 set title $PRIV(name)
107 } else {
108 MainInit
109 # some main initialization occurs later in this proc,
110 # to go after the UI init
111 set MainInit 1
112 set title Main
116 ## When setting up all the default values, we always check for
117 ## prior existence. This allows users who embed tkcon to modify
118 ## the initial state before tkcon initializes itself.
121 # bg == {} will get bg color from the main toplevel (in InitUI)
122 foreach {key default} {
123 bg {}
124 blink \#FFFF00
125 cursor \#000000
126 disabled \#4D4D4D
127 proc \#008800
128 var \#FFC0D0
129 prompt \#8F4433
130 stdin \#000000
131 stdout \#0000FF
132 stderr \#FF0000
134 if {![info exists COLOR($key)]} { set COLOR($key) $default }
137 foreach {key default} {
138 autoload {}
139 blinktime 500
140 blinkrange 1
141 buffer 512
142 calcmode 0
143 cols 80
144 debugPrompt {(level \#$level) debug [history nextid] > }
145 dead {}
146 expandorder {Pathname Variable Procname}
147 font {}
148 history 48
149 hoterrors 1
150 library {}
151 lightbrace 1
152 lightcmd 1
153 maineval {}
154 maxmenu 15
155 nontcl 0
156 prompt1 {ignore this, it's set below}
157 rows 20
158 scrollypos right
159 showmenu 1
160 showmultiple 1
161 showstatusbar 0
162 slaveeval {}
163 slaveexit close
164 subhistory 1
165 gc-delay 60000
166 gets {congets}
167 usehistory 1
169 exec slave
171 if {![info exists OPT($key)]} { set OPT($key) $default }
174 foreach {key default} {
175 app {}
176 appname {}
177 apptype slave
178 namesp ::
179 cmd {}
180 cmdbuf {}
181 cmdsave {}
182 event 1
183 deadapp 0
184 deadsock 0
185 debugging 0
186 displayWin .
187 histid 0
188 find {}
189 find,case 0
190 find,reg 0
191 errorInfo {}
192 showOnStartup 1
193 slavealias { edit more less tkcon }
194 slaveprocs {
195 alias clear dir dump echo idebug lremove
196 tkcon_puts tkcon_gets observe observe_var unalias which what
198 version 2.3
199 RCS {RCS: @(#) $Id: tkcon.tcl,v 1.1.1.1 2011/04/10 21:15:05 tim Exp $}
200 HEADURL {http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/tkcon/tkcon/tkcon.tcl?rev=HEAD}
201 docs "http://tkcon.sourceforge.net/"
202 email {jeff@hobbs.org}
203 root .
205 if {![info exists PRIV($key)]} { set PRIV($key) $default }
208 ## NOTES FOR STAYING IN PRIMARY INTERPRETER:
210 ## If you set ::tkcon::OPT(exec) to {}, then instead of a multiple
211 ## interp model, you get tkcon operating in the main interp by default.
212 ## This can be useful when attaching to programs that like to operate
213 ## in the main interpter (for example, based on special wish'es).
214 ## You can set this from the command line with -exec ""
215 ## A side effect is that all tkcon command line args will be used
216 ## by the first console only.
217 #set OPT(exec) {}
219 if {$PRIV(WWW)} {
220 lappend PRIV(slavealias) history
221 set OPT(prompt1) {[history nextid] % }
222 } else {
223 lappend PRIV(slaveprocs) tcl_unknown unknown
224 set OPT(prompt1) {([file tail [pwd]]) [history nextid] % }
227 ## If we are using the default '.' toplevel, and there appear to be
228 ## children of '.', then make sure we use a disassociated toplevel.
229 if {$PRIV(root) == "." && [llength [winfo children .]]} {
230 set PRIV(root) .tkcon
233 ## Do platform specific configuration here, other than defaults
234 ### Use tkcon.cfg filename for resource filename on non-unix systems
235 ### Determine what directory the resource file should be in
236 switch $tcl_platform(platform) {
237 macintosh {
238 if {![interp issafe]} {cd [file dirname [info script]]}
239 set envHome PREF_FOLDER
240 set rcfile tkcon.cfg
241 set histfile xcircuit_tkcon.hst
242 catch {console hide}
244 windows {
245 set envHome HOME
246 set rcfile tkcon.cfg
247 set histfile xcircuit_tkcon.hst
249 unix {
250 set envHome HOME
251 set rcfile .tkconrc
252 set histfile .xcircuit_tkcon_hst
255 if {[info exists env($envHome)]} {
256 if {![info exists PRIV(rcfile)]} {
257 set PRIV(rcfile) [file join $env($envHome) $rcfile]
259 if {![info exists PRIV(histfile)]} {
260 set PRIV(histfile) [file join $env($envHome) $histfile]
264 ## Handle command line arguments before sourcing resource file to
265 ## find if resource file is being specified (let other args pass).
266 if {[set i [lsearch -exact $argv -rcfile]] != -1} {
267 set PRIV(rcfile) [lindex $argv [incr i]]
270 if {!$PRIV(WWW) && [file exists $PRIV(rcfile)]} {
271 set code [catch {uplevel \#0 [list source $PRIV(rcfile)]} err]
274 if {[info exists env(TK_CON_LIBRARY)]} {
275 lappend ::auto_path $env(TK_CON_LIBRARY)
276 } else {
277 lappend ::auto_path $OPT(library)
280 if {![info exists ::tcl_pkgPath]} {
281 set dir [file join [file dirname [info nameofexec]] lib]
282 if {[llength [info commands @scope]]} {
283 set dir [file join $dir itcl]
285 catch {source [file join $dir pkgIndex.tcl]}
287 catch {tclPkgUnknown dummy-name dummy-version}
289 ## Handle rest of command line arguments after sourcing resource file
290 ## and slave is created, but before initializing UI or setting packages.
291 set slaveargs {}
292 set slavefiles {}
293 set truth {^(1|yes|true|on)$}
294 for {set i 0} {$i < $argc} {incr i} {
295 set arg [lindex $argv $i]
296 if {[string match {-*} $arg]} {
297 set val [lindex $argv [incr i]]
298 ## Handle arg based options
299 switch -glob -- $arg {
300 -- - -argv {
301 set argv [concat -- [lrange $argv $i end]]
302 set argc [llength $argv]
303 break
305 -color-* { set COLOR([string range $arg 7 end]) $val }
306 -exec { set OPT(exec) $val }
307 -main - -e - -eval { append OPT(maineval) \n$val\n }
308 -package - -load { lappend OPT(autoload) $val }
309 -slave { append OPT(slaveeval) \n$val\n }
310 -nontcl { set OPT(nontcl) [regexp -nocase $truth $val]}
311 -root { set PRIV(root) $val }
312 -font { set OPT(font) $val }
313 -rcfile {}
314 default { lappend slaveargs $arg; incr i -1 }
316 } elseif {[file isfile $arg]} {
317 lappend slavefiles $arg
318 } else {
319 lappend slaveargs $arg
323 ## Create slave executable
324 if {[string compare {} $OPT(exec)]} {
325 uplevel \#0 ::tkcon::InitSlave $OPT(exec) $slaveargs
326 } else {
327 set argc [llength $slaveargs]
328 set argv $slaveargs
329 uplevel \#0 $slaveargs
332 ## Attach to the slave, EvalAttached will then be effective
333 Attach $PRIV(appname) $PRIV(apptype)
334 InitUI $title
336 ## swap puts and gets with the tkcon versions to make sure all
337 ## input and output is handled by tkcon
338 if {![catch {rename ::puts ::tkcon_tcl_puts}]} {
339 interp alias {} ::puts {} ::tkcon_puts
341 if {($OPT(gets) != "") && ![catch {rename ::gets ::tkcon_tcl_gets}]} {
342 interp alias {} ::gets {} ::tkcon_gets
345 EvalSlave history keep $OPT(history)
346 if {[info exists MainInit]} {
347 # Source history file only for the main console, as all slave
348 # consoles will adopt from the main's history, but still
349 # keep separate histories
350 if {!$PRIV(WWW) && $OPT(usehistory) && [file exists $PRIV(histfile)]} {
351 puts -nonewline "loading history file ... "
352 # The history file is built to be loaded in and
353 # understood by tkcon
354 if {[catch {uplevel \#0 [list source $PRIV(histfile)]} herr]} {
355 puts stderr "error:\n$herr"
356 append PRIV(errorInfo) $errorInfo\n
358 set PRIV(event) [EvalSlave history nextid]
359 puts "[expr {$PRIV(event)-1}] events added"
363 ## Autoload specified packages in slave
364 set pkgs [EvalSlave package names]
365 foreach pkg $OPT(autoload) {
366 puts -nonewline "autoloading package \"$pkg\" ... "
367 if {[lsearch -exact $pkgs $pkg]>-1} {
368 if {[catch {EvalSlave package require [list $pkg]} pkgerr]} {
369 puts stderr "error:\n$pkgerr"
370 append PRIV(errorInfo) $errorInfo\n
371 } else { puts "OK" }
372 } else {
373 puts stderr "error: package does not exist"
377 ## Evaluate maineval in slave
378 if {[string compare {} $OPT(maineval)] && \
379 [catch {uplevel \#0 $OPT(maineval)} merr]} {
380 puts stderr "error in eval:\n$merr"
381 append PRIV(errorInfo) $errorInfo\n
384 ## Source extra command line argument files into slave executable
385 foreach fn $slavefiles {
386 puts -nonewline "slave sourcing \"$fn\" ... "
387 if {[catch {EvalSlave source [list $fn]} fnerr]} {
388 puts stderr "error:\n$fnerr"
389 append PRIV(errorInfo) $errorInfo\n
390 } else { puts "OK" }
393 ## Evaluate slaveeval in slave
394 if {[string compare {} $OPT(slaveeval)] && \
395 [catch {interp eval $OPT(exec) $OPT(slaveeval)} serr]} {
396 puts stderr "error in slave eval:\n$serr"
397 append PRIV(errorInfo) $errorInfo\n
399 ## Output any error/output that may have been returned from rcfile
400 if {[info exists code] && $code && [string compare {} $err]} {
401 puts stderr "error in $PRIV(rcfile):\n$err"
402 append PRIV(errorInfo) $errorInfo
404 if {[string compare {} $OPT(exec)]} {
405 StateCheckpoint [concat $PRIV(name) $OPT(exec)] slave
407 StateCheckpoint $PRIV(name) slave
409 Prompt "$title console display active (Tcl$::tcl_patchLevel / Tk$::tk_patchLevel)\n"
412 ## ::tkcon::InitSlave - inits the slave by placing key procs and aliases in it
413 ## It's arg[cv] are based on passed in options, while argv0 is the same as
414 ## the master. tcl_interactive is the same as the master as well.
415 # ARGS: slave - name of slave to init. If it does not exist, it is created.
416 # args - args to pass to a slave as argv/argc
418 proc ::tkcon::InitSlave {slave args} {
419 variable OPT
420 variable COLOR
421 variable PRIV
422 global argv0 tcl_interactive tcl_library env auto_path
424 if {[string match {} $slave]} {
425 return -code error "Don't init the master interpreter, goofball"
427 if {![interp exists $slave]} { interp create $slave }
428 if {[interp eval $slave info command source] == ""} {
429 $slave alias source SafeSource $slave
430 $slave alias load SafeLoad $slave
431 $slave alias open SafeOpen $slave
432 $slave alias file file
433 interp eval $slave [dump var -nocomplain tcl_library auto_path env]
434 interp eval $slave { catch {source [file join $tcl_library init.tcl]} }
435 interp eval $slave { catch unknown }
437 $slave alias exit exit
438 interp eval $slave {
439 # Do package require before changing around puts/gets
440 catch {package require bogus-package-name}
441 catch {rename ::puts ::tkcon_tcl_puts}
443 foreach cmd $PRIV(slaveprocs) { $slave eval [dump proc $cmd] }
444 foreach cmd $PRIV(slavealias) { $slave alias $cmd $cmd }
445 interp alias $slave ::ls $slave ::dir -full
446 interp alias $slave ::puts $slave ::tkcon_puts
447 if {$OPT(gets) != ""} {
448 interp eval $slave { catch {rename ::gets ::tkcon_tcl_gets} }
449 interp alias $slave ::gets $slave ::tkcon_gets
451 if {[info exists argv0]} {interp eval $slave [list set argv0 $argv0]}
452 interp eval $slave set tcl_interactive $tcl_interactive \; \
453 set auto_path [list $auto_path] \; \
454 set argc [llength $args] \; \
455 set argv [list $args] \; {
456 if {![llength [info command bgerror]]} {
457 proc bgerror err {
458 global errorInfo
459 set body [info body bgerror]
460 rename ::bgerror {}
461 if {[auto_load bgerror]} { return [bgerror $err] }
462 proc bgerror err $body
463 tkcon bgerror $err $errorInfo
468 foreach pkg [lremove [package names] Tcl] {
469 foreach v [package versions $pkg] {
470 interp eval $slave [list package ifneeded $pkg $v \
471 [package ifneeded $pkg $v]]
476 ## ::tkcon::InitInterp - inits an interpreter by placing key
477 ## procs and aliases in it.
478 # ARGS: name - interp name
479 # type - interp type (slave|interp)
481 proc ::tkcon::InitInterp {name type} {
482 variable OPT
483 variable PRIV
485 ## Don't allow messing up a local master interpreter
486 if {[string match namespace $type] || ([string match slave $type] && \
487 [regexp {^([Mm]ain|Slave[0-9]+)$} $name])} return
488 set old [Attach]
489 set oldname $PRIV(namesp)
490 catch {
491 Attach $name $type
492 EvalAttached { catch {rename ::puts ::tkcon_tcl_puts} }
493 foreach cmd $PRIV(slaveprocs) { EvalAttached [dump proc $cmd] }
494 switch -exact $type {
495 slave {
496 foreach cmd $PRIV(slavealias) {
497 Main interp alias $name ::$cmd $PRIV(name) ::$cmd
500 interp {
501 set thistkcon [tk appname]
502 foreach cmd $PRIV(slavealias) {
503 EvalAttached "proc $cmd args { send [list $thistkcon] $cmd \$args }"
507 ## Catch in case it's a 7.4 (no 'interp alias') interp
508 EvalAttached {
509 catch {interp alias {} ::ls {} ::dir -full}
510 if {[catch {interp alias {} ::puts {} ::tkcon_puts}]} {
511 catch {rename ::tkcon_puts ::puts}
514 if {$OPT(gets) != ""} {
515 EvalAttached {
516 catch {rename ::gets ::tkcon_tcl_gets}
517 if {[catch {interp alias {} ::gets {} ::tkcon_gets}]} {
518 catch {rename ::tkcon_gets ::gets}
522 return
523 } {err}
524 eval Attach $old
525 AttachNamespace $oldname
526 if {[string compare {} $err]} { return -code error $err }
529 ## ::tkcon::InitUI - inits UI portion (console) of tkcon
530 ## Creates all elements of the console window and sets up the text tags
531 # ARGS: root - widget pathname of the tkcon console root
532 # title - title for the console root and main (.) windows
533 # Calls: ::tkcon::InitMenus, ::tkcon::Prompt
535 proc ::tkcon::InitUI {title} {
536 variable OPT
537 variable PRIV
538 variable COLOR
540 set root $PRIV(root)
541 if {[string match . $root]} { set w {} } else { set w [toplevel $root] }
542 if {!$PRIV(WWW)} {
543 wm withdraw $root
544 wm protocol $root WM_DELETE_WINDOW exit
546 set PRIV(base) $w
548 ## Text Console
549 set PRIV(console) [set con $w.text]
550 text $con -wrap char -yscrollcommand [list $w.sy set] \
551 -foreground $COLOR(stdin) \
552 -insertbackground $COLOR(cursor)
553 $con mark set output 1.0
554 $con mark set limit 1.0
555 if {[string compare {} $COLOR(bg)]} {
556 $con configure -background $COLOR(bg)
558 set COLOR(bg) [$con cget -background]
559 if {[string compare {} $OPT(font)]} {
560 ## Set user-requested font, if any
561 $con configure -font $OPT(font)
562 } else {
563 ## otherwise make sure the font is monospace
564 set font [$con cget -font]
565 if {![font metrics $font -fixed]} {
566 font create tkconfixed -family Courier -size 12
567 $con configure -font tkconfixed
570 set OPT(font) [$con cget -font]
571 if {!$PRIV(WWW)} {
572 $con configure -setgrid 1 -width $OPT(cols) -height $OPT(rows)
574 bindtags $con [list $con TkConsole TkConsolePost $root all]
575 ## Menus
576 ## catch against use in plugin
577 if {[catch {menu $w.mbar} PRIV(menubar)]} {
578 set PRIV(menubar) [frame $w.mbar -relief raised -bd 1]
580 ## Scrollbar
581 set PRIV(scrolly) [scrollbar $w.sy -takefocus 0 -bd 1 \
582 -command [list $con yview]]
584 InitMenus $PRIV(menubar) $title
585 Bindings
587 if {$OPT(showmenu)} {
588 $root configure -menu $PRIV(menubar)
590 pack $w.sy -side $OPT(scrollypos) -fill y
591 pack $con -fill both -expand 1
593 set PRIV(statusbar) [set sbar [frame $w.sbar]]
594 label $sbar.attach -relief sunken -bd 1 -anchor w \
595 -textvariable ::tkcon::PRIV(StatusAttach)
596 label $sbar.mode -relief sunken -bd 1 -anchor w \
597 -textvariable ::tkcon::PRIV(StatusMode)
598 label $sbar.cursor -relief sunken -bd 1 -anchor w -width 6 \
599 -textvariable ::tkcon::PRIV(StatusCursor)
600 grid $sbar.attach $sbar.mode $sbar.cursor -sticky news -padx 1
601 grid columnconfigure $sbar 0 -weight 1
602 grid columnconfigure $sbar 1 -weight 1
603 grid columnconfigure $sbar 2 -weight 0
605 if {$OPT(showstatusbar)} {
606 pack $sbar -side bottom -fill x -before $::tkcon::PRIV(scrolly)
609 foreach col {prompt stdout stderr stdin proc} {
610 $con tag configure $col -foreground $COLOR($col)
612 $con tag configure var -background $COLOR(var)
613 $con tag raise sel
614 $con tag configure blink -background $COLOR(blink)
615 $con tag configure find -background $COLOR(blink)
617 if {!$PRIV(WWW)} {
618 wm title $root "tkcon $PRIV(version) $title"
619 bind $con <Configure> {
620 scan [wm geometry [winfo toplevel %W]] "%%dx%%d" \
621 ::tkcon::OPT(cols) ::tkcon::OPT(rows)
623 if {$PRIV(showOnStartup)} { wm deiconify $root }
625 if {$PRIV(showOnStartup)} { focus -force $PRIV(console) }
626 if {$OPT(gc-delay)} {
627 after $OPT(gc-delay) ::tkcon::GarbageCollect
631 ## ::tkcon::GarbageCollect - do various cleanup ops periodically to our setup
633 proc ::tkcon::GarbageCollect {} {
634 variable OPT
635 variable PRIV
637 set w $PRIV(console)
638 ## Remove error tags that no longer span anything
639 ## Make sure the tag pattern matches the unique tag prefix
640 foreach tag [$w tag names] {
641 if {[string match _tag* $tag] && ![llength [$w tag ranges $tag]]} {
642 $w tag delete $tag
645 if {$OPT(gc-delay)} {
646 after $OPT(gc-delay) ::tkcon::GarbageCollect
650 ## ::tkcon::Eval - evaluates commands input into console window
651 ## This is the first stage of the evaluating commands in the console.
652 ## They need to be broken up into consituent commands (by ::tkcon::CmdSep) in
653 ## case a multiple commands were pasted in, then each is eval'ed (by
654 ## ::tkcon::EvalCmd) in turn. Any uncompleted command will not be eval'ed.
655 # ARGS: w - console text widget
656 # Calls: ::tkcon::CmdGet, ::tkcon::CmdSep, ::tkcon::EvalCmd
658 proc ::tkcon::Eval {w} {
659 set incomplete [CmdSep [CmdGet $w] cmds last]
660 $w mark set insert end-1c
661 $w insert end \n
662 if {[llength $cmds]} {
663 foreach c $cmds {EvalCmd $w $c}
664 $w insert insert $last {}
665 } elseif {!$incomplete} {
666 EvalCmd $w $last
668 $w see insert
671 ## ::tkcon::EvalCmd - evaluates a single command, adding it to history
672 # ARGS: w - console text widget
673 # cmd - the command to evaluate
674 # Calls: ::tkcon::Prompt
675 # Outputs: result of command to stdout (or stderr if error occured)
676 # Returns: next event number
678 proc ::tkcon::EvalCmd {w cmd} {
679 variable OPT
680 variable PRIV
682 $w mark set output end
683 if {[string compare {} $cmd]} {
684 set code 0
685 if {$OPT(subhistory)} {
686 set ev [EvalSlave history nextid]
687 incr ev -1
688 if {[string match !! $cmd]} {
689 set code [catch {EvalSlave history event $ev} cmd]
690 if {!$code} {$w insert output $cmd\n stdin}
691 } elseif {[regexp {^!(.+)$} $cmd dummy event]} {
692 ## Check last event because history event is broken
693 set code [catch {EvalSlave history event $ev} cmd]
694 if {!$code && ![string match ${event}* $cmd]} {
695 set code [catch {EvalSlave history event $event} cmd]
697 if {!$code} {$w insert output $cmd\n stdin}
698 } elseif {[regexp {^\^([^^]*)\^([^^]*)\^?$} $cmd dummy old new]} {
699 set code [catch {EvalSlave history event $ev} cmd]
700 if {!$code} {
701 regsub -all -- $old $cmd $new cmd
702 $w insert output $cmd\n stdin
704 } elseif {$OPT(calcmode) && ![catch {expr $cmd} err]} {
705 EvalSlave history add $cmd
706 set cmd $err
707 set code -1
710 if {$code} {
711 $w insert output $cmd\n stderr
712 } else {
713 ## We are about to evaluate the command, so move the limit
714 ## mark to ensure that further <Return>s don't cause double
715 ## evaluation of this command - for cases like the command
716 ## has a vwait or something in it
717 $w mark set limit end
718 if {$OPT(nontcl) && [string match interp $PRIV(apptype)]} {
719 set code [catch {EvalSend $cmd} res]
720 if {$code == 1} {
721 set PRIV(errorInfo) "Non-Tcl errorInfo not available"
723 } elseif {[string match socket $PRIV(apptype)]} {
724 set code [catch {EvalSocket $cmd} res]
725 if {$code == 1} {
726 set PRIV(errorInfo) "Socket-based errorInfo not available"
728 } else {
729 set code [catch {EvalAttached $cmd} res]
730 if {$code == 1} {
731 if {[catch {EvalAttached [list set errorInfo]} err]} {
732 set PRIV(errorInfo) "Error getting errorInfo:\n$err"
733 } else {
734 set PRIV(errorInfo) $err
738 EvalSlave history add $cmd
739 if {$code} {
740 if {$OPT(hoterrors)} {
741 set tag [UniqueTag $w]
742 $w insert output $res [list stderr $tag] \n stderr
743 $w tag bind $tag <Enter> \
744 [list $w tag configure $tag -underline 1]
745 $w tag bind $tag <Leave> \
746 [list $w tag configure $tag -underline 0]
747 $w tag bind $tag <ButtonRelease-1> \
748 "if {!\[info exists tkPriv(mouseMoved)\] || !\$tkPriv(mouseMoved)} \
749 {[list edit -attach [Attach] -type error -- $PRIV(errorInfo)]}"
750 } else {
751 $w insert output $res\n stderr
753 } elseif {[string compare {} $res]} {
754 $w insert output $res\n stdout
758 Prompt
759 set PRIV(event) [EvalSlave history nextid]
762 ## ::tkcon::EvalSlave - evaluates the args in the associated slave
763 ## args should be passed to this procedure like they would be at
764 ## the command line (not like to 'eval').
765 # ARGS: args - the command and args to evaluate
767 proc ::tkcon::EvalSlave args {
768 interp eval $::tkcon::OPT(exec) $args
771 ## ::tkcon::EvalOther - evaluate a command in a foreign interp or slave
772 ## without attaching to it. No check for existence is made.
773 # ARGS: app - interp/slave name
774 # type - (slave|interp)
776 proc ::tkcon::EvalOther { app type args } {
777 if {[string compare slave $type]==0} {
778 return [Slave $app $args]
779 } else {
780 return [uplevel 1 send [list $app] $args]
784 ## ::tkcon::EvalSend - sends the args to the attached interpreter
785 ## Varies from 'send' by determining whether attachment is dead
786 ## when an error is received
787 # ARGS: cmd - the command string to send across
788 # Returns: the result of the command
790 proc ::tkcon::EvalSend cmd {
791 variable OPT
792 variable PRIV
794 if {$PRIV(deadapp)} {
795 if {[lsearch -exact [winfo interps] $PRIV(app)]<0} {
796 return
797 } else {
798 set PRIV(appname) [string range $PRIV(appname) 5 end]
799 set PRIV(deadapp) 0
800 Prompt "\n\"$PRIV(app)\" alive\n" [CmdGet $PRIV(console)]
803 set code [catch {send -displayof $PRIV(displayWin) $PRIV(app) $cmd} result]
804 if {$code && [lsearch -exact [winfo interps] $PRIV(app)]<0} {
805 ## Interpreter disappeared
806 if {[string compare leave $OPT(dead)] && \
807 ([string match ignore $OPT(dead)] || \
808 [tk_dialog $PRIV(base).dead "Dead Attachment" \
809 "\"$PRIV(app)\" appears to have died.\
810 \nReturn to primary slave interpreter?" questhead 0 OK No])} {
811 set PRIV(appname) "DEAD:$PRIV(appname)"
812 set PRIV(deadapp) 1
813 } else {
814 set err "Attached Tk interpreter \"$PRIV(app)\" died."
815 Attach {}
816 set PRIV(deadapp) 0
817 EvalSlave set errorInfo $err
819 Prompt \n [CmdGet $PRIV(console)]
821 return -code $code $result
824 ## ::tkcon::EvalSocket - sends the string to an interpreter attached via
825 ## a tcp/ip socket
827 ## In the EvalSocket case, ::tkcon::PRIV(app) is the socket id
829 ## Must determine whether socket is dead when an error is received
830 # ARGS: cmd - the data string to send across
831 # Returns: the result of the command
833 proc ::tkcon::EvalSocket cmd {
834 variable OPT
835 variable PRIV
836 global tcl_version
838 if {$PRIV(deadapp)} {
839 if {![info exists PRIV(app)] || \
840 [catch {eof $PRIV(app)} eof] || $eof} {
841 return
842 } else {
843 set PRIV(appname) [string range $PRIV(appname) 5 end]
844 set PRIV(deadapp) 0
845 Prompt "\n\"$PRIV(app)\" alive\n" [CmdGet $PRIV(console)]
848 # Sockets get \'s interpreted, so that users can
849 # send things like \n\r or explicit hex values
850 set cmd [subst -novariables -nocommands $cmd]
851 #puts [list $PRIV(app) $cmd]
852 set code [catch {puts $PRIV(app) $cmd ; flush $PRIV(app)} result]
853 if {$code && [eof $PRIV(app)]} {
854 ## Interpreter died or disappeared
855 puts "$code eof [eof $PRIV(app)]"
856 EvalSocketClosed
858 return -code $code $result
861 ## ::tkcon::EvalSocketEvent - fileevent command for an interpreter attached
862 ## via a tcp/ip socket
863 ## Must determine whether socket is dead when an error is received
864 # ARGS: args - the args to send across
865 # Returns: the result of the command
867 proc ::tkcon::EvalSocketEvent {} {
868 variable PRIV
870 if {[gets $PRIV(app) line] == -1} {
871 if {[eof $PRIV(app)]} {
872 EvalSocketClosed
874 return
876 puts $line
879 ## ::tkcon::EvalSocketClosed - takes care of handling a closed eval socket
881 # ARGS: args - the args to send across
882 # Returns: the result of the command
884 proc ::tkcon::EvalSocketClosed {} {
885 variable OPT
886 variable PRIV
888 catch {close $PRIV(app)}
889 if {[string compare leave $OPT(dead)] && \
890 ([string match ignore $OPT(dead)] || \
891 [tk_dialog $PRIV(base).dead "Dead Attachment" \
892 "\"$PRIV(app)\" appears to have died.\
893 \nReturn to primary slave interpreter?" questhead 0 OK No])} {
894 set PRIV(appname) "DEAD:$PRIV(appname)"
895 set PRIV(deadapp) 1
896 } else {
897 set err "Attached Tk interpreter \"$PRIV(app)\" died."
898 Attach {}
899 set PRIV(deadapp) 0
900 EvalSlave set errorInfo $err
902 Prompt \n [CmdGet $PRIV(console)]
905 ## ::tkcon::EvalNamespace - evaluates the args in a particular namespace
906 ## This is an override for ::tkcon::EvalAttached for when the user wants
907 ## to attach to a particular namespace of the attached interp
908 # ARGS: attached
909 # namespace the namespace to evaluate in
910 # args the args to evaluate
911 # RETURNS: the result of the command
913 proc ::tkcon::EvalNamespace { attached namespace args } {
914 if {[llength $args]} {
915 uplevel \#0 $attached \
916 [list [concat [list namespace eval $namespace] $args]]
921 ## ::tkcon::Namespaces - return all the namespaces descendent from $ns
925 proc ::tkcon::Namespaces {{ns ::} {l {}}} {
926 if {[string compare {} $ns]} { lappend l $ns }
927 foreach i [EvalAttached [list namespace children $ns]] {
928 set l [Namespaces $i $l]
930 return $l
933 ## ::tkcon::CmdGet - gets the current command from the console widget
934 # ARGS: w - console text widget
935 # Returns: text which compromises current command line
937 proc ::tkcon::CmdGet w {
938 if {![llength [$w tag nextrange prompt limit end]]} {
939 $w tag add stdin limit end-1c
940 return [$w get limit end-1c]
944 ## ::tkcon::CmdSep - separates multiple commands into a list and remainder
945 # ARGS: cmd - (possible) multiple command to separate
946 # list - varname for the list of commands that were separated.
947 # last - varname of any remainder (like an incomplete final command).
948 # If there is only one command, it's placed in this var.
949 # Returns: constituent command info in varnames specified by list & rmd.
951 proc ::tkcon::CmdSep {cmd list last} {
952 upvar 1 $list cmds $last inc
953 set inc {}
954 set cmds {}
955 foreach c [split [string trimleft $cmd] \n] {
956 if {[string compare $inc {}]} {
957 append inc \n$c
958 } else {
959 append inc [string trimleft $c]
961 if {[info complete $inc] && ![regexp {[^\\]\\$} $inc]} {
962 if {[regexp "^\[^#\]" $inc]} {lappend cmds $inc}
963 set inc {}
966 set i [string compare $inc {}]
967 if {!$i && [string compare $cmds {}] && ![string match *\n $cmd]} {
968 set inc [lindex $cmds end]
969 set cmds [lreplace $cmds end end]
971 return $i
974 ## ::tkcon::CmdSplit - splits multiple commands into a list
975 # ARGS: cmd - (possible) multiple command to separate
976 # Returns: constituent commands in a list
978 proc ::tkcon::CmdSplit {cmd} {
979 set inc {}
980 set cmds {}
981 foreach cmd [split [string trimleft $cmd] \n] {
982 if {[string compare {} $inc]} {
983 append inc \n$cmd
984 } else {
985 append inc [string trimleft $cmd]
987 if {[info complete $inc] && ![regexp {[^\\]\\$} $inc]} {
988 #set inc [string trimright $inc]
989 if {[regexp "^\[^#\]" $inc]} {lappend cmds $inc}
990 set inc {}
993 if {[regexp "^\[^#\]" $inc]} {lappend cmds $inc}
994 return $cmds
997 ## ::tkcon::UniqueTag - creates a uniquely named tag, reusing names
998 ## Called by ::tkcon::EvalCmd
999 # ARGS: w - text widget
1000 # Outputs: tag name guaranteed unique in the widget
1002 proc ::tkcon::UniqueTag {w} {
1003 set tags [$w tag names]
1004 set idx 0
1005 while {[lsearch -exact $tags _tag[incr idx]] != -1} {}
1006 return _tag$idx
1009 ## ::tkcon::ConstrainBuffer - This limits the amount of data in the text widget
1010 ## Called by ::tkcon::Prompt and in tkcon proc buffer/console switch cases
1011 # ARGS: w - console text widget
1012 # size - # of lines to constrain to
1013 # Outputs: may delete data in console widget
1015 proc ::tkcon::ConstrainBuffer {w size} {
1016 if {[$w index end] > $size} {
1017 $w delete 1.0 [expr {int([$w index end])-$size}].0
1021 ## ::tkcon::Prompt - displays the prompt in the console widget
1022 # ARGS: w - console text widget
1023 # Outputs: prompt (specified in ::tkcon::OPT(prompt1)) to console
1025 proc ::tkcon::Prompt {{pre {}} {post {}} {prompt {}}} {
1026 variable OPT
1027 variable PRIV
1029 set w $PRIV(console)
1030 if {[string compare {} $pre]} { $w insert end $pre stdout }
1031 set i [$w index end-1c]
1032 if {!$OPT(showstatusbar)} {
1033 if {[string compare {} $PRIV(appname)]} {
1034 $w insert end ">$PRIV(appname)< " prompt
1036 if {[string compare :: $PRIV(namesp)]} {
1037 $w insert end "<$PRIV(namesp)> " prompt
1040 if {[string compare {} $prompt]} {
1041 $w insert end $prompt prompt
1042 } else {
1043 $w insert end [EvalSlave subst $OPT(prompt1)] prompt
1045 $w mark set output $i
1046 $w mark set insert end
1047 $w mark set limit insert
1048 $w mark gravity limit left
1049 if {[string compare {} $post]} { $w insert end $post stdin }
1050 ConstrainBuffer $w $OPT(buffer)
1051 set ::tkcon::PRIV(StatusCursor) [$w index insert]
1052 $w see end
1055 ## ::tkcon::About - gives about info for tkcon
1057 proc ::tkcon::About {} {
1058 variable OPT
1059 variable PRIV
1060 variable COLOR
1062 set w $PRIV(base).about
1063 if {[winfo exists $w]} {
1064 wm deiconify $w
1065 } else {
1066 global tk_patchLevel tcl_patchLevel tcl_version
1067 toplevel $w
1068 wm title $w "About tkcon v$PRIV(version)"
1069 button $w.b -text Dismiss -command [list wm withdraw $w]
1070 text $w.text -height 9 -bd 1 -width 60 \
1071 -foreground $COLOR(stdin) \
1072 -background $COLOR(bg) \
1073 -font $OPT(font)
1074 pack $w.b -fill x -side bottom
1075 pack $w.text -fill both -side left -expand 1
1076 $w.text tag config center -justify center
1077 $w.text tag config title -justify center -font {Courier -18 bold}
1078 # strip down the RCS info displayed in the about box
1079 regexp {,v ([0-9\./: ]*)} $PRIV(RCS) -> RCS
1080 $w.text insert 1.0 "About tkcon v$PRIV(version)" title \
1081 "\n\nCopyright 1995-2001 Jeffrey Hobbs, $PRIV(email)\
1082 \nRelease Info: v$PRIV(version), CVS v$RCS\
1083 \nDocumentation available at:\n$PRIV(docs)\
1084 \nUsing: Tcl v$tcl_patchLevel / Tk v$tk_patchLevel" center
1085 $w.text config -state disabled
1089 ## ::tkcon::InitMenus - inits the menubar and popup for the console
1090 # ARGS: w - console text widget
1092 proc ::tkcon::InitMenus {w title} {
1093 variable OPT
1094 variable PRIV
1095 variable COLOR
1096 global tcl_platform
1098 if {[catch {menu $w.pop -tearoff 0}]} {
1099 label $w.label -text "Menus not available in plugin mode"
1100 pack $w.label
1101 return
1103 menu $w.context -tearoff 0 -disabledforeground $COLOR(disabled)
1104 set PRIV(context) $w.context
1105 set PRIV(popup) $w.pop
1107 proc MenuButton {w m l} {
1108 $w add cascade -label $m -underline 0 -menu $w.$l
1109 return $w.$l
1112 foreach m [list File Console Edit Interp Prefs History Help] {
1113 set l [string tolower $m]
1114 MenuButton $w $m $l
1115 $w.pop add cascade -label $m -underline 0 -menu $w.pop.$l
1118 ## File Menu
1120 foreach m [list [menu $w.file -disabledforeground $COLOR(disabled)] \
1121 [menu $w.pop.file -disabledforeground $COLOR(disabled)]] {
1122 $m add command -label "Load File" -underline 0 -command ::tkcon::Load
1123 $m add cascade -label "Save ..." -underline 0 -menu $m.save
1124 $m add separator
1125 $m add command -label "Quit" -underline 0 -accel Ctrl-q -command exit
1127 ## Save Menu
1129 set s $m.save
1130 menu $s -disabledforeground $COLOR(disabled) -tearoff 0
1131 $s add command -label "All" -underline 0 \
1132 -command {::tkcon::Save {} all}
1133 $s add command -label "History" -underline 0 \
1134 -command {::tkcon::Save {} history}
1135 $s add command -label "Stdin" -underline 3 \
1136 -command {::tkcon::Save {} stdin}
1137 $s add command -label "Stdout" -underline 3 \
1138 -command {::tkcon::Save {} stdout}
1139 $s add command -label "Stderr" -underline 3 \
1140 -command {::tkcon::Save {} stderr}
1143 ## Console Menu
1145 foreach m [list [menu $w.console -disabledfore $COLOR(disabled)] \
1146 [menu $w.pop.console -disabledfore $COLOR(disabled)]] {
1147 $m add command -label "$title Console" -state disabled
1148 $m add command -label "New Console" -underline 0 -accel Ctrl-N \
1149 -command ::tkcon::New
1150 $m add command -label "Close Console" -underline 0 -accel Ctrl-w \
1151 -command ::tkcon::Destroy
1152 $m add command -label "Clear Console" -underline 1 -accel Ctrl-l \
1153 -command { clear; ::tkcon::Prompt }
1154 if {[string match unix $tcl_platform(platform)]} {
1155 $m add separator
1156 $m add command -label "Make Xauth Secure" -und 5 \
1157 -command ::tkcon::XauthSecure
1159 $m add separator
1160 $m add cascade -label "Attach To ..." -underline 0 -menu $m.attach
1162 ## Attach Console Menu
1164 set sub [menu $m.attach -disabledforeground $COLOR(disabled)]
1165 $sub add cascade -label "Interpreter" -underline 0 -menu $sub.apps
1166 $sub add cascade -label "Namespace" -underline 1 -menu $sub.name
1167 $sub add cascade -label "Socket" -underline 1 -menu $sub.sock \
1168 -state [expr {([info tclversion] < 8.3)?"disabled":"normal"}]
1170 ## Attach Console Menu
1172 menu $sub.apps -disabledforeground $COLOR(disabled) \
1173 -postcommand [list ::tkcon::AttachMenu $sub.apps]
1175 ## Attach Namespace Menu
1177 menu $sub.name -disabledforeground $COLOR(disabled) -tearoff 0 \
1178 -postcommand [list ::tkcon::NamespaceMenu $sub.name]
1180 if {$::tcl_version >= 8.3} {
1181 # This uses [file channels] to create the menu, so we only
1182 # want it for newer versions of Tcl.
1184 ## Attach Socket Menu
1186 menu $sub.sock -disabledforeground $COLOR(disabled) -tearoff 0 \
1187 -postcommand [list ::tkcon::SocketMenu $sub.sock]
1190 ## Attach Display Menu
1192 if {![string compare "unix" $tcl_platform(platform)]} {
1193 $sub add cascade -label "Display" -und 1 -menu $sub.disp
1194 menu $sub.disp -disabledforeground $COLOR(disabled) \
1195 -tearoff 0 \
1196 -postcommand [list ::tkcon::DisplayMenu $sub.disp]
1200 ## Edit Menu
1202 set text $PRIV(console)
1203 foreach m [list [menu $w.edit] [menu $w.pop.edit]] {
1204 $m add command -label "Cut" -underline 2 -accel Ctrl-x \
1205 -command [list ::tkcon::Cut $text]
1206 $m add command -label "Copy" -underline 0 -accel Ctrl-c \
1207 -command [list ::tkcon::Copy $text]
1208 $m add command -label "Paste" -underline 0 -accel Ctrl-v \
1209 -command [list ::tkcon::Paste $text]
1210 $m add separator
1211 $m add command -label "Find" -underline 0 -accel Ctrl-F \
1212 -command [list ::tkcon::FindBox $text]
1215 ## Interp Menu
1217 foreach m [list $w.interp $w.pop.interp] {
1218 menu $m -disabledforeground $COLOR(disabled) \
1219 -postcommand [list ::tkcon::InterpMenu $m]
1222 ## Prefs Menu
1224 foreach m [list [menu $w.prefs] [menu $w.pop.prefs]] {
1225 $m add check -label "Brace Highlighting" \
1226 -underline 0 -variable ::tkcon::OPT(lightbrace)
1227 $m add check -label "Command Highlighting" \
1228 -underline 0 -variable ::tkcon::OPT(lightcmd)
1229 $m add check -label "History Substitution" \
1230 -underline 0 -variable ::tkcon::OPT(subhistory)
1231 $m add check -label "Hot Errors" \
1232 -underline 0 -variable ::tkcon::OPT(hoterrors)
1233 $m add check -label "Non-Tcl Attachments" \
1234 -underline 0 -variable ::tkcon::OPT(nontcl)
1235 $m add check -label "Calculator Mode" \
1236 -underline 1 -variable ::tkcon::OPT(calcmode)
1237 $m add check -label "Show Multiple Matches" \
1238 -underline 0 -variable ::tkcon::OPT(showmultiple)
1239 $m add check -label "Show Menubar" \
1240 -underline 5 -variable ::tkcon::OPT(showmenu) \
1241 -command {$::tkcon::PRIV(root) configure -menu [expr \
1242 {$::tkcon::OPT(showmenu) ? $::tkcon::PRIV(menubar) : {}}]}
1243 $m add check -label "Show Statusbar" \
1244 -underline 5 -variable ::tkcon::OPT(showstatusbar) \
1245 -command {
1246 if {$::tkcon::OPT(showstatusbar)} {
1247 pack $::tkcon::PRIV(statusbar) -side bottom -fill x \
1248 -before $::tkcon::PRIV(scrolly)
1249 } else { pack forget $::tkcon::PRIV(statusbar) }
1251 $m add cascade -label "Scrollbar" -underline 2 -menu $m.scroll
1253 ## Scrollbar Menu
1255 set m [menu $m.scroll -tearoff 0]
1256 $m add radio -label "Left" -value left \
1257 -variable ::tkcon::OPT(scrollypos) \
1258 -command { pack config $::tkcon::PRIV(scrolly) -side left }
1259 $m add radio -label "Right" -value right \
1260 -variable ::tkcon::OPT(scrollypos) \
1261 -command { pack config $::tkcon::PRIV(scrolly) -side right }
1264 ## History Menu
1266 foreach m [list $w.history $w.pop.history] {
1267 menu $m -disabledforeground $COLOR(disabled) \
1268 -postcommand [list ::tkcon::HistoryMenu $m]
1271 ## Help Menu
1273 foreach m [list [menu $w.help] [menu $w.pop.help]] {
1274 $m add command -label "About " -underline 0 -accel Ctrl-A \
1275 -command ::tkcon::About
1276 $m add command -label "Retrieve Latest Version" -underline 0 \
1277 -command ::tkcon::Retrieve
1281 ## ::tkcon::HistoryMenu - dynamically build the menu for attached interpreters
1283 # ARGS: m - menu widget
1285 proc ::tkcon::HistoryMenu m {
1286 variable PRIV
1288 if {![winfo exists $m]} return
1289 set id [EvalSlave history nextid]
1290 if {$PRIV(histid)==$id} return
1291 set PRIV(histid) $id
1292 $m delete 0 end
1293 while {($id>1) && ($id>$PRIV(histid)-10) && \
1294 ![catch {EvalSlave history event [incr id -1]} tmp]} {
1295 set lbl $tmp
1296 if {[string len $lbl]>32} { set lbl [string range $tmp 0 28]... }
1297 $m add command -label "$id: $lbl" -command "
1298 $::tkcon::PRIV(console) delete limit end
1299 $::tkcon::PRIV(console) insert limit [list $tmp]
1300 $::tkcon::PRIV(console) see end
1301 ::tkcon::Eval $::tkcon::PRIV(console)"
1305 ## ::tkcon::InterpMenu - dynamically build the menu for attached interpreters
1307 # ARGS: w - menu widget
1309 proc ::tkcon::InterpMenu w {
1310 variable OPT
1311 variable PRIV
1312 variable COLOR
1314 if {![winfo exists $w]} return
1315 $w delete 0 end
1316 foreach {app type} [Attach] break
1317 $w add command -label "[string toupper $type]: $app" -state disabled
1318 if {($OPT(nontcl) && [string match interp $type]) || $PRIV(deadapp)} {
1319 $w add separator
1320 $w add command -state disabled -label "Communication disabled to"
1321 $w add command -state disabled -label "dead or non-Tcl interps"
1322 return
1325 ## Show Last Error
1327 $w add separator
1328 $w add command -label "Show Last Error" \
1329 -command [list tkcon error $app $type]
1331 ## Packages Cascaded Menu
1333 $w add separator
1334 $w add cascade -label Packages -underline 0 -menu $w.pkg
1335 set m $w.pkg
1336 if {![winfo exists $m]} {
1337 menu $m -tearoff no -disabledforeground $COLOR(disabled) \
1338 -postcommand [list ::tkcon::PkgMenu $m $app $type]
1341 ## State Checkpoint/Revert
1343 $w add separator
1344 $w add command -label "Checkpoint State" \
1345 -command [list ::tkcon::StateCheckpoint $app $type]
1346 $w add command -label "Revert State" \
1347 -command [list ::tkcon::StateRevert $app $type]
1348 $w add command -label "View State Change" \
1349 -command [list ::tkcon::StateCompare $app $type]
1351 ## Init Interp
1353 $w add separator
1354 $w add command -label "Send tkcon Commands" \
1355 -command [list ::tkcon::InitInterp $app $type]
1358 ## ::tkcon::PkgMenu - fill in in the applications sub-menu
1359 ## with a list of all the applications that currently exist.
1361 proc ::tkcon::PkgMenu {m app type} {
1362 # just in case stuff has been added to the auto_path
1363 # we have to make sure that the errorInfo doesn't get screwed up
1364 EvalAttached {
1365 set __tkcon_error $errorInfo
1366 catch {package require bogus-package-name}
1367 set errorInfo ${__tkcon_error}
1368 unset __tkcon_error
1370 $m delete 0 end
1371 foreach pkg [EvalAttached [list info loaded {}]] {
1372 set loaded([lindex $pkg 1]) [package provide $pkg]
1374 foreach pkg [lremove [EvalAttached {package names}] Tcl] {
1375 set version [EvalAttached [list package provide $pkg]]
1376 if {[string compare {} $version]} {
1377 set loaded($pkg) $version
1378 } elseif {![info exists loaded($pkg)]} {
1379 set loadable($pkg) [list package require $pkg]
1382 foreach pkg [EvalAttached {info loaded}] {
1383 set pkg [lindex $pkg 1]
1384 if {![info exists loaded($pkg)] && ![info exists loadable($pkg)]} {
1385 set loadable($pkg) [list load {} $pkg]
1388 set npkg 0
1389 foreach pkg [lsort -dictionary [array names loadable]] {
1390 foreach v [EvalAttached [list package version $pkg]] {
1391 set brkcol [expr {([incr npkg]%16)==0}]
1392 $m add command -label "Load $pkg ($v)" -command \
1393 "::tkcon::EvalOther [list $app] $type $loadable($pkg) $v" \
1394 -columnbreak $brkcol
1397 if {[info exists loaded] && [info exists loadable]} {
1398 $m add separator
1400 foreach pkg [lsort -dictionary [array names loaded]] {
1401 $m add command -label "${pkg}$loaded($pkg) Loaded" -state disabled
1405 ## ::tkcon::AttachMenu - fill in in the applications sub-menu
1406 ## with a list of all the applications that currently exist.
1408 proc ::tkcon::AttachMenu m {
1409 variable OPT
1410 variable PRIV
1412 array set interps [set tmp [Interps]]
1413 foreach {i j} $tmp { set tknames($j) {} }
1415 $m delete 0 end
1416 set cmd {::tkcon::Prompt \n [::tkcon::CmdGet $::tkcon::PRIV(console)]}
1417 $m add radio -label {None (use local slave) } -accel Ctrl-1 \
1418 -variable ::tkcon::PRIV(app) \
1419 -value [concat $::tkcon::PRIV(name) $::tkcon::OPT(exec)] \
1420 -command "::tkcon::Attach {}; $cmd"
1421 $m add separator
1422 $m add command -label "Foreign Tk Interpreters" -state disabled
1423 foreach i [lsort [lremove [winfo interps] [array names tknames]]] {
1424 $m add radio -label $i -variable ::tkcon::PRIV(app) -value $i \
1425 -command "::tkcon::Attach [list $i] interp; $cmd"
1427 $m add separator
1429 $m add command -label "tkcon Interpreters" -state disabled
1430 foreach i [lsort [array names interps]] {
1431 if {[string match {} $interps($i)]} { set interps($i) "no Tk" }
1432 if {[regexp {^Slave[0-9]+} $i]} {
1433 set opts [list -label "$i ($interps($i))" \
1434 -variable ::tkcon::PRIV(app) -value $i \
1435 -command "::tkcon::Attach [list $i] slave; $cmd"]
1436 if {[string match $PRIV(name) $i]} {
1437 append opts " -accel Ctrl-2"
1439 eval $m add radio $opts
1440 } else {
1441 set name [concat Main $i]
1442 if {[string match Main $name]} {
1443 $m add radio -label "$name ($interps($i))" -accel Ctrl-3 \
1444 -variable ::tkcon::PRIV(app) -value Main \
1445 -command "::tkcon::Attach [list $name] slave; $cmd"
1446 } else {
1447 $m add radio -label "$name ($interps($i))" \
1448 -variable ::tkcon::PRIV(app) -value $i \
1449 -command "::tkcon::Attach [list $name] slave; $cmd"
1455 ## Displays Cascaded Menu
1457 proc ::tkcon::DisplayMenu m {
1458 $m delete 0 end
1459 set cmd {::tkcon::Prompt \n [::tkcon::CmdGet $::tkcon::PRIV(console)]}
1461 $m add command -label "New Display" -command ::tkcon::NewDisplay
1462 foreach disp [Display] {
1463 $m add separator
1464 $m add command -label $disp -state disabled
1465 set res [Display $disp]
1466 set win [lindex $res 0]
1467 foreach i [lsort [lindex $res 1]] {
1468 $m add radio -label $i -variable ::tkcon::PRIV(app) -value $i \
1469 -command "::tkcon::Attach [list $i] [list dpy:$win]; $cmd"
1474 ## Sockets Cascaded Menu
1476 proc ::tkcon::SocketMenu m {
1477 $m delete 0 end
1478 set cmd {::tkcon::Prompt \n [::tkcon::CmdGet $::tkcon::PRIV(console)]}
1480 $m add command -label "Create Connection" \
1481 -command "::tkcon::NewSocket; $cmd"
1482 foreach sock [file channels sock*] {
1483 $m add radio -label $sock -variable ::tkcon::PRIV(app) -value $sock \
1484 -command "::tkcon::Attach $sock socket; $cmd"
1488 ## Namepaces Cascaded Menu
1490 proc ::tkcon::NamespaceMenu m {
1491 variable PRIV
1492 variable OPT
1494 $m delete 0 end
1495 if {($PRIV(deadapp) || [string match socket $PRIV(apptype)] || \
1496 ($OPT(nontcl) && [string match interp $PRIV(apptype)]))} {
1497 $m add command -label "No Namespaces" -state disabled
1498 return
1501 ## Same command as for ::tkcon::AttachMenu items
1502 set cmd {::tkcon::Prompt \n [::tkcon::CmdGet $::tkcon::PRIV(console)]}
1504 set names [lsort [Namespaces ::]]
1505 if {[llength $names] > $OPT(maxmenu)} {
1506 $m add command -label "Attached to $PRIV(namesp)" -state disabled
1507 $m add command -label "List Namespaces" \
1508 -command [list ::tkcon::NamespacesList $names]
1509 } else {
1510 foreach i $names {
1511 if {[string match :: $i]} {
1512 $m add radio -label "Main" -value $i \
1513 -variable ::tkcon::PRIV(namesp) \
1514 -command "::tkcon::AttachNamespace [list $i]; $cmd"
1515 } else {
1516 $m add radio -label $i -value $i \
1517 -variable ::tkcon::PRIV(namesp) \
1518 -command "::tkcon::AttachNamespace [list $i]; $cmd"
1524 ## Namepaces List
1526 proc ::tkcon::NamespacesList {names} {
1527 variable PRIV
1529 set f $PRIV(base).namespaces
1530 catch {destroy $f}
1531 toplevel $f
1532 listbox $f.names -width 30 -height 15 -selectmode single \
1533 -yscrollcommand [list $f.scrollv set] \
1534 -xscrollcommand [list $f.scrollh set]
1535 scrollbar $f.scrollv -command [list $f.names yview]
1536 scrollbar $f.scrollh -command [list $f.names xview] -orient horizontal
1537 frame $f.buttons
1538 button $f.cancel -text "Cancel" -command [list destroy $f]
1540 grid $f.names $f.scrollv -sticky nesw
1541 grid $f.scrollh -sticky ew
1542 grid $f.buttons -sticky nesw
1543 grid $f.cancel -in $f.buttons -pady 6
1545 grid columnconfigure $f 0 -weight 1
1546 grid rowconfigure $f 0 -weight 1
1547 #fill the listbox
1548 foreach i $names {
1549 if {[string match :: $i]} {
1550 $f.names insert 0 Main
1551 } else {
1552 $f.names insert end $i
1555 #Bindings
1556 bind $f.names <Double-1> {
1557 ## Catch in case the namespace disappeared on us
1558 catch { ::tkcon::AttachNamespace [%W get [%W nearest %y]] }
1559 ::tkcon::Prompt "\n" [::tkcon::CmdGet $::tkcon::PRIV(console)]
1560 destroy [winfo toplevel %W]
1564 # ::tkcon::XauthSecure --
1566 # This removes all the names in the xhost list, and secures
1567 # the display for Tk send commands. Of course, this prevents
1568 # what might have been otherwise allowable X connections
1570 # Arguments:
1571 # none
1572 # Results:
1573 # Returns nothing
1575 proc ::tkcon::XauthSecure {} {
1576 global tcl_platform
1578 if {[string compare unix $tcl_platform(platform)]} {
1579 # This makes no sense outside of Unix
1580 return
1582 set hosts [exec xhost]
1583 # the first line is info only
1584 foreach host [lrange [split $hosts \n] 1 end] {
1585 exec xhost -$host
1587 exec xhost -
1588 tk_messageBox -title "Xhost secured" -message "Xhost secured" -icon info
1591 ## ::tkcon::FindBox - creates minimal dialog interface to ::tkcon::Find
1592 # ARGS: w - text widget
1593 # str - optional seed string for ::tkcon::PRIV(find)
1595 proc ::tkcon::FindBox {w {str {}}} {
1596 variable PRIV
1598 set base $PRIV(base).find
1599 if {![winfo exists $base]} {
1600 toplevel $base
1601 wm withdraw $base
1602 wm title $base "tkcon Find"
1604 pack [frame $base.f] -fill x -expand 1
1605 label $base.f.l -text "Find:"
1606 entry $base.f.e -textvariable ::tkcon::PRIV(find)
1607 pack [frame $base.opt] -fill x
1608 checkbutton $base.opt.c -text "Case Sensitive" \
1609 -variable ::tkcon::PRIV(find,case)
1610 checkbutton $base.opt.r -text "Use Regexp" -variable ::tkcon::PRIV(find,reg)
1611 pack $base.f.l -side left
1612 pack $base.f.e $base.opt.c $base.opt.r -side left -fill both -expand 1
1613 pack [frame $base.sep -bd 2 -relief sunken -height 4] -fill x
1614 pack [frame $base.btn] -fill both
1615 button $base.btn.fnd -text "Find" -width 6
1616 button $base.btn.clr -text "Clear" -width 6
1617 button $base.btn.dis -text "Dismiss" -width 6
1618 eval pack [winfo children $base.btn] -padx 4 -pady 2 \
1619 -side left -fill both
1621 focus $base.f.e
1623 bind $base.f.e <Return> [list $base.btn.fnd invoke]
1624 bind $base.f.e <Escape> [list $base.btn.dis invoke]
1626 $base.btn.fnd config -command "::tkcon::Find [list $w] \$::tkcon::PRIV(find) \
1627 -case \$::tkcon::PRIV(find,case) -reg \$::tkcon::PRIV(find,reg)"
1628 $base.btn.clr config -command "
1629 [list $w] tag remove find 1.0 end
1630 set ::tkcon::PRIV(find) {}
1632 $base.btn.dis config -command "
1633 [list $w] tag remove find 1.0 end
1634 wm withdraw [list $base]
1636 if {[string compare {} $str]} {
1637 set PRIV(find) $str
1638 $base.btn.fnd invoke
1641 if {[string compare normal [wm state $base]]} {
1642 wm deiconify $base
1643 } else { raise $base }
1644 $base.f.e select range 0 end
1647 ## ::tkcon::Find - searches in text widget $w for $str and highlights it
1648 ## If $str is empty, it just deletes any highlighting
1649 # ARGS: w - text widget
1650 # str - string to search for
1651 # -case TCL_BOOLEAN whether to be case sensitive DEFAULT: 0
1652 # -regexp TCL_BOOLEAN whether to use $str as pattern DEFAULT: 0
1654 proc ::tkcon::Find {w str args} {
1655 $w tag remove find 1.0 end
1656 set truth {^(1|yes|true|on)$}
1657 set opts {}
1658 foreach {key val} $args {
1659 switch -glob -- $key {
1660 -c* { if {[regexp -nocase $truth $val]} { set case 1 } }
1661 -r* { if {[regexp -nocase $truth $val]} { lappend opts -regexp } }
1662 default { return -code error "Unknown option $key" }
1665 if {![info exists case]} { lappend opts -nocase }
1666 if {[string match {} $str]} return
1667 $w mark set findmark 1.0
1668 while {[string compare {} [set ix [eval $w search $opts -count numc -- \
1669 [list $str] findmark end]]]} {
1670 $w tag add find $ix ${ix}+${numc}c
1671 $w mark set findmark ${ix}+1c
1673 $w tag configure find -background $::tkcon::COLOR(blink)
1674 catch {$w see find.first}
1675 return [expr {[llength [$w tag ranges find]]/2}]
1678 ## ::tkcon::Attach - called to attach tkcon to an interpreter
1679 # ARGS: name - application name to which tkcon sends commands
1680 # This is either a slave interperter name or tk appname.
1681 # type - (slave|interp) type of interpreter we're attaching to
1682 # slave means it's a tkcon interpreter
1683 # interp means we'll need to 'send' to it.
1684 # Results: ::tkcon::EvalAttached is recreated to evaluate in the
1685 # appropriate interpreter
1687 proc ::tkcon::Attach {{name <NONE>} {type slave}} {
1688 variable PRIV
1689 variable OPT
1691 if {[llength [info level 0]] == 1} {
1692 # no args were specified, return the attach info instead
1693 if {[string match {} $PRIV(appname)]} {
1694 return [list [concat $PRIV(name) $OPT(exec)] $PRIV(apptype)]
1695 } else {
1696 return [list $PRIV(appname) $PRIV(apptype)]
1699 set path [concat $PRIV(name) $OPT(exec)]
1701 set PRIV(displayWin) .
1702 if {[string match namespace $type]} {
1703 return [uplevel 1 ::tkcon::AttachNamespace $name]
1704 } elseif {[string match dpy:* $type]} {
1705 set PRIV(displayWin) [string range $type 4 end]
1706 } elseif {[string match sock* $type]} {
1707 global tcl_version
1708 if {[catch {eof $name} res]} {
1709 return -code error "No known channel \"$name\""
1710 } elseif {$res} {
1711 catch {close $name}
1712 return -code error "Channel \"$name\" returned EOF"
1714 set app $name
1715 set type socket
1716 } elseif {[string compare {} $name]} {
1717 array set interps [Interps]
1718 if {[string match {[Mm]ain} [lindex $name 0]]} {
1719 set name [lrange $name 1 end]
1721 if {[string match $path $name]} {
1722 set name {}
1723 set app $path
1724 set type slave
1725 } elseif {[info exists interps($name)]} {
1726 if {[string match {} $name]} { set name Main; set app Main }
1727 set type slave
1728 } elseif {[interp exists $name]} {
1729 set name [concat $PRIV(name) $name]
1730 set type slave
1731 } elseif {[interp exists [concat $OPT(exec) $name]]} {
1732 set name [concat $path $name]
1733 set type slave
1734 } elseif {[lsearch -exact [winfo interps] $name] > -1} {
1735 if {[EvalSlave info exists tk_library] \
1736 && [string match $name [EvalSlave tk appname]]} {
1737 set name {}
1738 set app $path
1739 set type slave
1740 } elseif {[set i [lsearch -exact \
1741 [Main set ::tkcon::PRIV(interps)] $name]] != -1} {
1742 set name [lindex [Main set ::tkcon::PRIV(slaves)] $i]
1743 if {[string match {[Mm]ain} $name]} { set app Main }
1744 set type slave
1745 } else {
1746 set type interp
1748 } else {
1749 return -code error "No known interpreter \"$name\""
1751 } else {
1752 set app $path
1754 if {![info exists app]} { set app $name }
1755 array set PRIV [list app $app appname $name apptype $type deadapp 0]
1757 ## ::tkcon::EvalAttached - evaluates the args in the attached interp
1758 ## args should be passed to this procedure as if they were being
1759 ## passed to the 'eval' procedure. This procedure is dynamic to
1760 ## ensure evaluation occurs in the right interp.
1761 # ARGS: args - the command and args to evaluate
1763 switch -glob -- $type {
1764 slave {
1765 if {[string match {} $name]} {
1766 interp alias {} ::tkcon::EvalAttached {} \
1767 ::tkcon::EvalSlave uplevel \#0
1768 } elseif {[string match Main $PRIV(app)]} {
1769 interp alias {} ::tkcon::EvalAttached {} ::tkcon::Main
1770 } elseif {[string match $PRIV(name) $PRIV(app)]} {
1771 interp alias {} ::tkcon::EvalAttached {} uplevel \#0
1772 } else {
1773 interp alias {} ::tkcon::EvalAttached {} \
1774 ::tkcon::Slave $::tkcon::PRIV(app)
1777 sock* {
1778 interp alias {} ::tkcon::EvalAttached {} \
1779 ::tkcon::EvalSlave uplevel \#0
1780 # The file event will just puts whatever data is found
1781 # into the interpreter
1782 fconfigure $name -buffering line -blocking 0
1783 fileevent $name readable ::tkcon::EvalSocketEvent
1785 dpy:* -
1786 interp {
1787 if {$OPT(nontcl)} {
1788 interp alias {} ::tkcon::EvalAttached {} ::tkcon::EvalSlave
1789 set PRIV(namesp) ::
1790 } else {
1791 interp alias {} ::tkcon::EvalAttached {} ::tkcon::EvalSend
1794 default {
1795 return -code error "[lindex [info level 0] 0] did not specify\
1796 a valid type: must be slave or interp"
1799 if {[string match slave $type] || \
1800 (!$OPT(nontcl) && [regexp {^(interp|dpy)} $type])} {
1801 set PRIV(namesp) ::
1803 set PRIV(StatusAttach) "$PRIV(app) ($PRIV(apptype))"
1804 return
1807 ## ::tkcon::AttachNamespace - called to attach tkcon to a namespace
1808 # ARGS: name - namespace name in which tkcon should eval commands
1809 # Results: ::tkcon::EvalAttached will be modified
1811 proc ::tkcon::AttachNamespace { name } {
1812 variable PRIV
1813 variable OPT
1815 if {($OPT(nontcl) && [string match interp $PRIV(apptype)]) \
1816 || [string match socket $PRIV(apptype)] \
1817 || $PRIV(deadapp)} {
1818 return -code error "can't attach to namespace in attached environment"
1820 if {[string match Main $name]} {set name ::}
1821 if {[string compare {} $name] && \
1822 [lsearch [Namespaces ::] $name] == -1} {
1823 return -code error "No known namespace \"$name\""
1825 if {[regexp {^(|::)$} $name]} {
1826 ## If name=={} || ::, we want the primary namespace
1827 set alias [interp alias {} ::tkcon::EvalAttached]
1828 if {[string match ::tkcon::EvalNamespace* $alias]} {
1829 eval [list interp alias {} ::tkcon::EvalAttached {}] \
1830 [lindex $alias 1]
1832 set name ::
1833 } else {
1834 interp alias {} ::tkcon::EvalAttached {} ::tkcon::EvalNamespace \
1835 [interp alias {} ::tkcon::EvalAttached] [list $name]
1837 set PRIV(namesp) $name
1838 set PRIV(StatusAttach) "$PRIV(app) $PRIV(namesp) ($PRIV(apptype))"
1841 ## ::tkcon::NewSocket - called to create a socket to connect to
1842 # ARGS: none
1843 # Results: It will create a socket, and attach if requested
1845 proc ::tkcon::NewSocket {} {
1846 variable PRIV
1848 set t $PRIV(base).newsock
1849 if {![winfo exists $t]} {
1850 toplevel $t
1851 wm withdraw $t
1852 wm title $t "tkcon Create Socket"
1853 label $t.lhost -text "Host: "
1854 entry $t.host -width 20
1855 label $t.lport -text "Port: "
1856 entry $t.port -width 4
1857 button $t.ok -text "OK" -command {set ::tkcon::PRIV(grab) 1}
1858 bind $t.host <Return> [list focus $t.port]
1859 bind $t.port <Return> [list focus $t.ok]
1860 bind $t.ok <Return> [list $t.ok invoke]
1861 grid $t.lhost $t.host $t.lport $t.port -sticky ew
1862 grid $t.ok - - - -sticky ew
1863 grid columnconfig $t 1 -weight 1
1864 grid rowconfigure $t 1 -weight 1
1865 wm transient $t $PRIV(root)
1866 wm geometry $t +[expr {([winfo screenwidth $t]-[winfo \
1867 reqwidth $t]) / 2}]+[expr {([winfo \
1868 screenheight $t]-[winfo reqheight $t]) / 2}]
1870 #$t.host delete 0 end
1871 #$t.port delete 0 end
1872 wm deiconify $t
1873 raise $t
1874 grab $t
1875 focus $t.host
1876 vwait ::tkcon::PRIV(grab)
1877 grab release $t
1878 wm withdraw $t
1879 set host [$t.host get]
1880 set port [$t.port get]
1881 if {$host == ""} { return }
1882 if {[catch {
1883 set sock [socket $host $port]
1884 } err]} {
1885 tk_messageBox -title "Socket Connection Error" \
1886 -message "Unable to connect to \"$host:$port\":\n$err" \
1887 -icon error -type ok
1888 } else {
1889 Attach $sock socket
1893 ## ::tkcon::Load - sources a file into the console
1894 ## The file is actually sourced in the currently attached's interp
1895 # ARGS: fn - (optional) filename to source in
1896 # Returns: selected filename ({} if nothing was selected)
1898 proc ::tkcon::Load { {fn ""} } {
1899 set types {
1900 {{Tcl Files} {.tcl .tk}}
1901 {{Text Files} {.txt}}
1902 {{All Files} *}
1904 if {
1905 [string match {} $fn] &&
1906 ([catch {tk_getOpenFile -filetypes $types \
1907 -title "Source File"} fn] || [string match {} $fn])
1908 } { return }
1909 EvalAttached [list source $fn]
1912 ## ::tkcon::Save - saves the console or other widget buffer to a file
1913 ## This does not eval in a slave because it's not necessary
1914 # ARGS: w - console text widget
1915 # fn - (optional) filename to save to
1917 proc ::tkcon::Save { {fn ""} {type ""} {opt ""} {mode w} } {
1918 variable PRIV
1920 if {![regexp -nocase {^(all|history|stdin|stdout|stderr|widget)$} $type]} {
1921 array set s { 0 All 1 History 2 Stdin 3 Stdout 4 Stderr 5 Cancel }
1922 ## Allow user to specify what kind of stuff to save
1923 set type [tk_dialog $PRIV(base).savetype "Save Type" \
1924 "What part of the text do you want to save?" \
1925 questhead 0 $s(0) $s(1) $s(2) $s(3) $s(4) $s(5)]
1926 if {$type == 5 || $type == -1} return
1927 set type $s($type)
1929 if {[string match {} $fn]} {
1930 set types {
1931 {{Tcl Files} {.tcl .tk}}
1932 {{Text Files} {.txt}}
1933 {{All Files} *}
1935 if {[catch {tk_getSaveFile -defaultextension .tcl -filetypes $types \
1936 -title "Save $type"} fn] || [string match {} $fn]} return
1938 set type [string tolower $type]
1939 switch $type {
1940 stdin - stdout - stderr {
1941 set data {}
1942 foreach {first last} [$PRIV(console) tag ranges $type] {
1943 lappend data [$PRIV(console) get $first $last]
1945 set data [join $data \n]
1947 history { set data [tkcon history] }
1948 all - default { set data [$PRIV(console) get 1.0 end-1c] }
1949 widget {
1950 set data [$opt get 1.0 end-1c]
1953 if {[catch {open $fn $mode} fid]} {
1954 return -code error "Save Error: Unable to open '$fn' for writing\n$fid"
1956 puts -nonewline $fid $data
1957 close $fid
1960 ## ::tkcon::MainInit
1961 ## This is only called for the main interpreter to include certain procs
1962 ## that we don't want to include (or rather, just alias) in slave interps.
1964 proc ::tkcon::MainInit {} {
1965 variable PRIV
1967 if {![info exists PRIV(slaves)]} {
1968 array set PRIV [list slave 0 slaves Main name {} \
1969 interps [list [tk appname]]]
1971 interp alias {} ::tkcon::Main {} ::tkcon::InterpEval Main
1972 interp alias {} ::tkcon::Slave {} ::tkcon::InterpEval
1974 proc ::tkcon::GetSlaveNum {} {
1975 set i -1
1976 while {[interp exists Slave[incr i]]} {
1977 # oh my god, an empty loop!
1979 return $i
1982 ## ::tkcon::New - create new console window
1983 ## Creates a slave interpreter and sources in this script.
1984 ## All other interpreters also get a command to eval function in the
1985 ## new interpreter.
1987 proc ::tkcon::New {} {
1988 variable PRIV
1989 global argv0 argc argv
1991 set tmp [interp create Slave[GetSlaveNum]]
1992 lappend PRIV(slaves) $tmp
1993 load {} Tk $tmp
1994 lappend PRIV(interps) [$tmp eval [list tk appname \
1995 "[tk appname] $tmp"]]
1996 if {[info exist argv0]} {$tmp eval [list set argv0 $argv0]}
1997 $tmp eval set argc $argc
1998 $tmp eval [list set argv $argv]
1999 $tmp eval [list namespace eval ::tkcon {}]
2000 $tmp eval [list set ::tkcon::PRIV(name) $tmp]
2001 $tmp eval [list set ::tkcon::PRIV(SCRIPT) $::tkcon::PRIV(SCRIPT)]
2002 $tmp alias exit ::tkcon::Exit $tmp
2003 $tmp alias ::tkcon::Destroy ::tkcon::Destroy $tmp
2004 $tmp alias ::tkcon::New ::tkcon::New
2005 $tmp alias ::tkcon::Main ::tkcon::InterpEval Main
2006 $tmp alias ::tkcon::Slave ::tkcon::InterpEval
2007 $tmp alias ::tkcon::Interps ::tkcon::Interps
2008 $tmp alias ::tkcon::NewDisplay ::tkcon::NewDisplay
2009 $tmp alias ::tkcon::Display ::tkcon::Display
2010 $tmp alias ::tkcon::StateCheckpoint ::tkcon::StateCheckpoint
2011 $tmp alias ::tkcon::StateCleanup ::tkcon::StateCleanup
2012 $tmp alias ::tkcon::StateCompare ::tkcon::StateCompare
2013 $tmp alias ::tkcon::StateRevert ::tkcon::StateRevert
2014 $tmp eval {
2015 if [catch {source -rsrc tkcon}] { source $::tkcon::PRIV(SCRIPT) }
2017 return $tmp
2020 ## ::tkcon::Exit - full exit OR destroy slave console
2021 ## This proc should only be called in the main interpreter from a slave.
2022 ## The master determines whether we do a full exit or just kill the slave.
2024 proc ::tkcon::Exit {slave args} {
2025 variable PRIV
2026 variable OPT
2028 ## Slave interpreter exit request
2029 if {[string match exit $OPT(slaveexit)]} {
2030 ## Only exit if it specifically is stated to do so
2031 uplevel 1 exit $args
2033 ## Otherwise we will delete the slave interp and associated data
2034 set name [InterpEval $slave]
2035 set PRIV(interps) [lremove $PRIV(interps) [list $name]]
2036 set PRIV(slaves) [lremove $PRIV(slaves) [list $slave]]
2037 interp delete $slave
2038 StateCleanup $slave
2039 return
2042 ## ::tkcon::Destroy - destroy console window
2043 ## This proc should only be called by the main interpreter. If it is
2044 ## called from there, it will ask before exiting tkcon. All others
2045 ## (slaves) will just have their slave interpreter deleted, closing them.
2047 proc ::tkcon::Destroy {{slave {}}} {
2048 variable PRIV
2050 if {[string match {} $slave]} {
2051 ## Main interpreter close request
2052 if {[tk_dialog $PRIV(base).destroyme {Quit tkcon?} \
2053 {Closing the Main console will quit tkcon} \
2054 warning 0 "Don't Quit" "Quit tkcon"]} exit
2055 } else {
2056 ## Slave interpreter close request
2057 set name [InterpEval $slave]
2058 set PRIV(interps) [lremove $PRIV(interps) [list $name]]
2059 set PRIV(slaves) [lremove $PRIV(slaves) [list $slave]]
2060 interp delete $slave
2062 StateCleanup $slave
2063 return
2066 ## We want to do a couple things before exiting...
2067 if {[catch {rename ::exit ::tkcon::FinalExit} err]} {
2068 puts stderr "tkcon might panic:\n$err"
2070 proc ::exit args {
2071 if {$::tkcon::OPT(usehistory)} {
2072 if {[catch {open $::tkcon::PRIV(histfile) w} fid]} {
2073 puts stderr "unable to save history file:\n$fid"
2074 # pause a moment, because we are about to die finally...
2075 after 1000
2076 } else {
2077 set max [::tkcon::EvalSlave history nextid]
2078 set id [expr {$max - $::tkcon::OPT(history)}]
2079 if {$id < 1} { set id 1 }
2080 ## FIX: This puts history in backwards!!
2081 while {($id < $max) && \
2082 ![catch {::tkcon::EvalSlave history event $id} cmd]} {
2083 if {[string compare {} $cmd]} {
2084 puts $fid "::tkcon::EvalSlave history add [list $cmd]"
2086 incr id
2088 close $fid
2091 uplevel 1 ::tkcon::FinalExit $args
2094 ## ::tkcon::InterpEval - passes evaluation to another named interpreter
2095 ## If the interpreter is named, but no args are given, it returns the
2096 ## [tk appname] of that interps master (not the associated eval slave).
2098 proc ::tkcon::InterpEval {{slave {}} args} {
2099 variable PRIV
2101 if {[string match {} $slave]} {
2102 return $PRIV(slaves)
2103 } elseif {[string match {[Mm]ain} $slave]} {
2104 set slave {}
2106 if {[llength $args]} {
2107 return [interp eval $slave uplevel \#0 $args]
2108 } else {
2109 return [interp eval $slave tk appname]
2113 proc ::tkcon::Interps {{ls {}} {interp {}}} {
2114 if {[string match {} $interp]} { lappend ls {} [tk appname] }
2115 foreach i [interp slaves $interp] {
2116 if {[string compare {} $interp]} { set i "$interp $i" }
2117 if {[string compare {} [interp eval $i package provide Tk]]} {
2118 lappend ls $i [interp eval $i tk appname]
2119 } else {
2120 lappend ls $i {}
2122 set ls [Interps $ls $i]
2124 return $ls
2127 proc ::tkcon::Display {{disp {}}} {
2128 variable DISP
2130 set res {}
2131 if {$disp != ""} {
2132 if {![info exists DISP($disp)]} { return }
2133 return [list $DISP($disp) [winfo interps -displayof $DISP($disp)]]
2135 return [lsort -dictionary [array names DISP]]
2138 proc ::tkcon::NewDisplay {} {
2139 variable PRIV
2140 variable DISP
2142 set t $PRIV(base).newdisp
2143 if {![winfo exists $t]} {
2144 toplevel $t
2145 wm withdraw $t
2146 wm title $t "tkcon Attach to Display"
2147 label $t.gets -text "New Display: "
2148 entry $t.data -width 32
2149 button $t.ok -text "OK" -command {set ::tkcon::PRIV(grab) 1}
2150 bind $t.data <Return> [list $t.ok invoke]
2151 bind $t.ok <Return> [list $t.ok invoke]
2152 grid $t.gets $t.data -sticky ew
2153 grid $t.ok - -sticky ew
2154 grid columnconfig $t 1 -weight 1
2155 grid rowconfigure $t 1 -weight 1
2156 wm transient $t $PRIV(root)
2157 wm geometry $t +[expr {([winfo screenwidth $t]-[winfo \
2158 reqwidth $t]) / 2}]+[expr {([winfo \
2159 screenheight $t]-[winfo reqheight $t]) / 2}]
2161 $t.data delete 0 end
2162 wm deiconify $t
2163 raise $t
2164 grab $t
2165 focus $t.data
2166 vwait ::tkcon::PRIV(grab)
2167 grab release $t
2168 wm withdraw $t
2169 set disp [$t.data get]
2170 if {$disp == ""} { return }
2171 regsub -all {\.} [string tolower $disp] ! dt
2172 set dt $PRIV(base).$dt
2173 destroy $dt
2174 if {[catch {
2175 toplevel $dt -screen $disp
2176 set interps [winfo interps -displayof $dt]
2177 if {![llength $interps]} {
2178 error "No other Tk interpreters on $disp"
2180 send -displayof $dt [lindex $interps 0] [list info tclversion]
2181 } err]} {
2182 global env
2183 if {[info exists env(DISPLAY)]} {
2184 set myd $env(DISPLAY)
2185 } else {
2186 set myd "myDisplay:0"
2188 tk_messageBox -title "Display Connection Error" \
2189 -message "Unable to connect to \"$disp\":\n$err\
2190 \nMake sure you have xauth-based permissions\
2191 (xauth add $myd . `mcookie`), and xhost is disabled\
2192 (xhost -) on \"$disp\"" \
2193 -icon error -type ok
2194 destroy $dt
2195 return
2197 set DISP($disp) $dt
2198 wm withdraw $dt
2199 bind $dt <Destroy> [subst {catch {unset ::tkcon::DISP($disp)}}]
2200 tk_messageBox -title "$disp Connection" \
2201 -message "Connected to \"$disp\", found:\n[join $interps \n]" \
2202 -type ok
2206 ## The following state checkpoint/revert procedures are very sketchy
2207 ## and prone to problems. They do not track modifications to currently
2208 ## existing procedures/variables, and they can really screw things up
2209 ## if you load in libraries (especially Tk) between checkpoint and
2210 ## revert. Only with this knowledge in mind should you use these.
2213 ## ::tkcon::StateCheckpoint - checkpoints the current state of the system
2214 ## This allows you to return to this state with ::tkcon::StateRevert
2215 # ARGS:
2217 proc ::tkcon::StateCheckpoint {app type} {
2218 variable CPS
2219 variable PRIV
2221 if {[info exists CPS($type,$app,cmd)] && \
2222 [tk_dialog $PRIV(base).warning "Overwrite Previous State?" \
2223 "Are you sure you want to lose previously checkpointed\
2224 state of $type \"$app\"?" questhead 1 "Do It" "Cancel"]} return
2225 set CPS($type,$app,cmd) [EvalOther $app $type info commands *]
2226 set CPS($type,$app,var) [EvalOther $app $type info vars *]
2227 return
2230 ## ::tkcon::StateCompare - compare two states and output difference
2231 # ARGS:
2233 proc ::tkcon::StateCompare {app type {verbose 0}} {
2234 variable CPS
2235 variable PRIV
2236 variable OPT
2237 variable COLOR
2239 if {![info exists CPS($type,$app,cmd)]} {
2240 return -code error \
2241 "No previously checkpointed state for $type \"$app\""
2243 set w $PRIV(base).compare
2244 if {[winfo exists $w]} {
2245 $w.text config -state normal
2246 $w.text delete 1.0 end
2247 } else {
2248 toplevel $w
2249 frame $w.btn
2250 scrollbar $w.sy -takefocus 0 -bd 1 -command [list $w.text yview]
2251 text $w.text -yscrollcommand [list $w.sy set] -height 12 \
2252 -foreground $COLOR(stdin) \
2253 -background $COLOR(bg) \
2254 -insertbackground $COLOR(cursor) \
2255 -font $OPT(font)
2256 pack $w.btn -side bottom -fill x
2257 pack $w.sy -side right -fill y
2258 pack $w.text -fill both -expand 1
2259 button $w.btn.close -text "Dismiss" -width 11 \
2260 -command [list destroy $w]
2261 button $w.btn.check -text "Recheckpoint" -width 11
2262 button $w.btn.revert -text "Revert" -width 11
2263 button $w.btn.expand -text "Verbose" -width 11
2264 button $w.btn.update -text "Update" -width 11
2265 pack $w.btn.check $w.btn.revert $w.btn.expand $w.btn.update \
2266 $w.btn.close -side left -fill x -padx 4 -pady 2 -expand 1
2267 $w.text tag config red -foreground red
2269 wm title $w "Compare State: $type [list $app]"
2271 $w.btn.check config \
2272 -command "::tkcon::StateCheckpoint [list $app] $type; \
2273 ::tkcon::StateCompare [list $app] $type $verbose"
2274 $w.btn.revert config \
2275 -command "::tkcon::StateRevert [list $app] $type; \
2276 ::tkcon::StateCompare [list $app] $type $verbose"
2277 $w.btn.update config -command [info level 0]
2278 if {$verbose} {
2279 $w.btn.expand config -text Brief \
2280 -command [list ::tkcon::StateCompare $app $type 0]
2281 } else {
2282 $w.btn.expand config -text Verbose \
2283 -command [list ::tkcon::StateCompare $app $type 1]
2285 ## Don't allow verbose mode unless 'dump' exists in $app
2286 ## We're assuming this is tkcon's dump command
2287 set hasdump [llength [EvalOther $app $type info commands dump]]
2288 if {$hasdump} {
2289 $w.btn.expand config -state normal
2290 } else {
2291 $w.btn.expand config -state disabled
2294 set cmds [lremove [EvalOther $app $type info commands *] \
2295 $CPS($type,$app,cmd)]
2296 set vars [lremove [EvalOther $app $type info vars *] \
2297 $CPS($type,$app,var)]
2299 if {$hasdump && $verbose} {
2300 set cmds [EvalOther $app $type eval dump c -nocomplain $cmds]
2301 set vars [EvalOther $app $type eval dump v -nocomplain $vars]
2303 $w.text insert 1.0 "NEW COMMANDS IN \"$app\":\n" red \
2304 $cmds {} "\n\nNEW VARIABLES IN \"$app\":\n" red $vars {}
2306 raise $w
2307 $w.text config -state disabled
2310 ## ::tkcon::StateRevert - reverts interpreter to previous state
2311 # ARGS:
2313 proc ::tkcon::StateRevert {app type} {
2314 variable CPS
2315 variable PRIV
2317 if {![info exists CPS($type,$app,cmd)]} {
2318 return -code error \
2319 "No previously checkpointed state for $type \"$app\""
2321 if {![tk_dialog $PRIV(base).warning "Revert State?" \
2322 "Are you sure you want to revert the state in $type \"$app\"?"\
2323 questhead 1 "Do It" "Cancel"]} {
2324 foreach i [lremove [EvalOther $app $type info commands *] \
2325 $CPS($type,$app,cmd)] {
2326 catch {EvalOther $app $type rename $i {}}
2328 foreach i [lremove [EvalOther $app $type info vars *] \
2329 $CPS($type,$app,var)] {
2330 catch {EvalOther $app $type unset $i}
2335 ## ::tkcon::StateCleanup - cleans up state information in master array
2338 proc ::tkcon::StateCleanup {args} {
2339 variable CPS
2341 if {![llength $args]} {
2342 foreach state [array names CPS slave,*] {
2343 if {![interp exists [string range $state 6 end]]} {
2344 unset CPS($state)
2347 } else {
2348 set app [lindex $args 0]
2349 set type [lindex $args 1]
2350 if {[regexp {^(|slave)$} $type]} {
2351 foreach state [array names CPS "slave,$app\[, \]*"] {
2352 if {![interp exists [string range $state 6 end]]} {
2353 unset CPS($state)
2356 } else {
2357 catch {unset CPS($type,$app)}
2363 ## ::tkcon::Event - get history event, search if string != {}
2364 ## look forward (next) if $int>0, otherwise look back (prev)
2365 # ARGS: W - console widget
2367 proc ::tkcon::Event {int {str {}}} {
2368 if {!$int} return
2370 variable PRIV
2371 set w $PRIV(console)
2373 set nextid [EvalSlave history nextid]
2374 if {[string compare {} $str]} {
2375 ## String is not empty, do an event search
2376 set event $PRIV(event)
2377 if {$int < 0 && $event == $nextid} { set PRIV(cmdbuf) $str }
2378 set len [string len $PRIV(cmdbuf)]
2379 incr len -1
2380 if {$int > 0} {
2381 ## Search history forward
2382 while {$event < $nextid} {
2383 if {[incr event] == $nextid} {
2384 $w delete limit end
2385 $w insert limit $PRIV(cmdbuf)
2386 break
2387 } elseif {
2388 ![catch {EvalSlave history event $event} res] &&
2389 [set p [string first $PRIV(cmdbuf) $res]] > -1
2391 set p2 [expr {$p + [string length $PRIV(cmdbuf)]}]
2392 $w delete limit end
2393 $w insert limit $res
2394 Blink $w "limit + $p c" "limit + $p2 c"
2395 break
2398 set PRIV(event) $event
2399 } else {
2400 ## Search history reverse
2401 while {![catch {EvalSlave history event [incr event -1]} res]} {
2402 if {[set p [string first $PRIV(cmdbuf) $res]] > -1} {
2403 set p2 [expr {$p + [string length $PRIV(cmdbuf)]}]
2404 $w delete limit end
2405 $w insert limit $res
2406 set PRIV(event) $event
2407 Blink $w "limit + $p c" "limit + $p2 c"
2408 break
2412 } else {
2413 ## String is empty, just get next/prev event
2414 if {$int > 0} {
2415 ## Goto next command in history
2416 if {$PRIV(event) < $nextid} {
2417 $w delete limit end
2418 if {[incr PRIV(event)] == $nextid} {
2419 $w insert limit $PRIV(cmdbuf)
2420 } else {
2421 $w insert limit [EvalSlave history event $PRIV(event)]
2424 } else {
2425 ## Goto previous command in history
2426 if {$PRIV(event) == $nextid} {
2427 set PRIV(cmdbuf) [CmdGet $w]
2429 if {[catch {EvalSlave history event [incr PRIV(event) -1]} res]} {
2430 incr PRIV(event)
2431 } else {
2432 $w delete limit end
2433 $w insert limit $res
2437 $w mark set insert end
2438 $w see end
2441 ## ::tkcon::ErrorHighlight - magic error highlighting
2442 ## beware: voodoo included
2443 # ARGS:
2445 proc ::tkcon::ErrorHighlight w {
2446 variable COLOR
2448 ## do voodoo here
2449 set app [Attach]
2450 # we have to pull the text out, because text regexps are screwed on \n's.
2451 set info [$w get 1.0 end-1c]
2452 # Check for specific line error in a proc
2453 set exp(proc) "\"(\[^\"\]+)\"\n\[\t \]+\\\(procedure \"(\[^\"\]+)\""
2454 # Check for too few args to a proc
2455 set exp(param) "parameter \"(\[^\"\]+)\" to \"(\[^\"\]+)\""
2456 set start 1.0
2457 while {
2458 [regexp -indices -- $exp(proc) $info junk what cmd] ||
2459 [regexp -indices -- $exp(param) $info junk what cmd]
2461 foreach {w0 w1} $what {c0 c1} $cmd {break}
2462 set what [string range $info $w0 $w1]
2463 set cmd [string range $info $c0 $c1]
2464 if {[string match *::* $cmd]} {
2465 set res [uplevel 1 ::tkcon::EvalOther $app namespace eval \
2466 [list [namespace qualifiers $cmd] \
2467 [list info procs [namespace tail $cmd]]]]
2468 } else {
2469 set res [uplevel 1 ::tkcon::EvalOther $app info procs [list $cmd]]
2471 if {[llength $res]==1} {
2472 set tag [UniqueTag $w]
2473 $w tag add $tag $start+${c0}c $start+1c+${c1}c
2474 $w tag configure $tag -foreground $COLOR(stdout)
2475 $w tag bind $tag <Enter> [list $w tag configure $tag -underline 1]
2476 $w tag bind $tag <Leave> [list $w tag configure $tag -underline 0]
2477 $w tag bind $tag <ButtonRelease-1> "if {!\$tkPriv(mouseMoved)} \
2478 {[list edit -attach $app -type proc -find $what -- $cmd]}"
2480 set info [string range $info $c1 end]
2481 set start [$w index $start+${c1}c]
2483 ## Next stage, check for procs that start a line
2484 set start 1.0
2485 set exp(cmd) "^\"\[^\" \t\n\]+"
2486 while {
2487 [string compare {} [set ix \
2488 [$w search -regexp -count numc -- $exp(cmd) $start end]]]
2490 set start [$w index $ix+${numc}c]
2491 # +1c to avoid the first quote
2492 set cmd [$w get $ix+1c $start]
2493 if {[string match *::* $cmd]} {
2494 set res [uplevel 1 ::tkcon::EvalOther $app namespace eval \
2495 [list [namespace qualifiers $cmd] \
2496 [list info procs [namespace tail $cmd]]]]
2497 } else {
2498 set res [uplevel 1 ::tkcon::EvalOther $app info procs [list $cmd]]
2500 if {[llength $res]==1} {
2501 set tag [UniqueTag $w]
2502 $w tag add $tag $ix+1c $start
2503 $w tag configure $tag -foreground $COLOR(proc)
2504 $w tag bind $tag <Enter> [list $w tag configure $tag -underline 1]
2505 $w tag bind $tag <Leave> [list $w tag configure $tag -underline 0]
2506 $w tag bind $tag <ButtonRelease-1> "if {!\$tkPriv(mouseMoved)} \
2507 {[list edit -attach $app -type proc -- $cmd]}"
2512 ## tkcon - command that allows control over the console
2513 ## This always exists in the main interpreter, and is aliased into
2514 ## other connected interpreters
2515 # ARGS: totally variable, see internal comments
2517 proc tkcon {cmd args} {
2518 global errorInfo
2520 switch -glob -- $cmd {
2521 buf* {
2522 ## 'buffer' Sets/Query the buffer size
2523 if {[llength $args]} {
2524 if {[regexp {^[1-9][0-9]*$} $args]} {
2525 set ::tkcon::OPT(buffer) $args
2526 # catch in case the console doesn't exist yet
2527 catch {::tkcon::ConstrainBuffer $::tkcon::PRIV(console) \
2528 $::tkcon::OPT(buffer)}
2529 } else {
2530 return -code error "buffer must be a valid integer"
2533 return $::tkcon::OPT(buffer)
2535 bg* {
2536 ## 'bgerror' Brings up an error dialog
2537 set errorInfo [lindex $args 1]
2538 bgerror [lindex $args 0]
2540 cl* {
2541 ## 'close' Closes the console
2542 ::tkcon::Destroy
2544 cons* {
2545 ## 'console' - passes the args to the text widget of the console.
2546 set result [uplevel 1 $::tkcon::PRIV(console) $args]
2547 ::tkcon::ConstrainBuffer $::tkcon::PRIV(console) \
2548 $::tkcon::OPT(buffer)
2549 return $result
2551 congets {
2552 ## 'congets' a replacement for [gets stdin]
2553 # Use the 'gets' alias of 'tkcon_gets' command instead of
2554 # calling the *get* methods directly for best compatability
2555 if {[llength $args] > 1} {
2556 return -code error "wrong # args: must be \"tkcon congets [pfix]\""
2558 tkcon show
2559 set old [bind TkConsole <<TkCon_Eval>>]
2560 bind TkConsole <<TkCon_Eval>> { set ::tkcon::PRIV(wait) 0 }
2561 set w $::tkcon::PRIV(console)
2562 # Make sure to move the limit to get the right data
2563 $w mark set insert end
2564 if {[llength $args]} {
2565 $w mark set limit insert
2566 $w insert end $args
2567 } else {
2568 $w mark set limit insert
2570 $w see end
2571 vwait ::tkcon::PRIV(wait)
2572 set line [::tkcon::CmdGet $w]
2573 $w insert end \n
2574 bind TkConsole <<TkCon_Eval>> $old
2575 return $line
2577 getc* {
2578 ## 'getcommand' a replacement for [gets stdin]
2579 ## This forces a complete command to be input though
2580 if {[llength $args]} {
2581 return -code error "wrong # args: must be \"tkcon getcommand\""
2583 tkcon show
2584 set old [bind TkConsole <<TkCon_Eval>>]
2585 bind TkConsole <<TkCon_Eval>> { set ::tkcon::PRIV(wait) 0 }
2586 set w $::tkcon::PRIV(console)
2587 # Make sure to move the limit to get the right data
2588 $w mark set insert end
2589 $w mark set limit insert
2590 $w see end
2591 vwait ::tkcon::PRIV(wait)
2592 set line [::tkcon::CmdGet $w]
2593 $w insert end \n
2594 while {![info complete $line] || [regexp {[^\\]\\$} $line]} {
2595 vwait ::tkcon::PRIV(wait)
2596 set line [::tkcon::CmdGet $w]
2597 $w insert end \n
2598 $w see end
2600 bind TkConsole <<TkCon_Eval>> $old
2601 return $line
2603 get - gets {
2604 ## 'gets' - a replacement for [gets stdin]
2605 ## This pops up a text widget to be used for stdin (local grabbed)
2606 if {[llength $args]} {
2607 return -code error "wrong # args: should be \"tkcon gets\""
2609 set t $::tkcon::PRIV(base).gets
2610 if {![winfo exists $t]} {
2611 toplevel $t
2612 wm withdraw $t
2613 wm title $t "tkcon gets stdin request"
2614 label $t.gets -text "\"gets stdin\" request:"
2615 text $t.data -width 32 -height 5 -wrap none \
2616 -xscrollcommand [list $t.sx set] \
2617 -yscrollcommand [list $t.sy set]
2618 scrollbar $t.sx -orient h -takefocus 0 -highlightthick 0 \
2619 -command [list $t.data xview]
2620 scrollbar $t.sy -orient v -takefocus 0 -highlightthick 0 \
2621 -command [list $t.data yview]
2622 button $t.ok -text "OK" -command {set ::tkcon::PRIV(grab) 1}
2623 bind $t.ok <Return> { %W invoke }
2624 grid $t.gets - -sticky ew
2625 grid $t.data $t.sy -sticky news
2626 grid $t.sx -sticky ew
2627 grid $t.ok - -sticky ew
2628 grid columnconfig $t 0 -weight 1
2629 grid rowconfig $t 1 -weight 1
2630 wm transient $t $::tkcon::PRIV(root)
2631 wm geometry $t +[expr {([winfo screenwidth $t]-[winfo \
2632 reqwidth $t]) / 2}]+[expr {([winfo \
2633 screenheight $t]-[winfo reqheight $t]) / 2}]
2635 $t.data delete 1.0 end
2636 wm deiconify $t
2637 raise $t
2638 grab $t
2639 focus $t.data
2640 vwait ::tkcon::PRIV(grab)
2641 grab release $t
2642 wm withdraw $t
2643 return [$t.data get 1.0 end-1c]
2645 err* {
2646 ## Outputs stack caused by last error.
2647 ## error handling with pizazz (but with pizza would be nice too)
2648 if {[llength $args]==2} {
2649 set app [lindex $args 0]
2650 set type [lindex $args 1]
2651 if {[catch {::tkcon::EvalOther $app $type set errorInfo} info]} {
2652 set info "error getting info from $type $app:\n$info"
2654 } else {
2655 set info $::tkcon::PRIV(errorInfo)
2657 if {[string match {} $info]} { set info "errorInfo empty" }
2658 ## If args is empty, the -attach switch just ignores it
2659 edit -attach $args -type error -- $info
2661 fi* {
2662 ## 'find' string
2663 ::tkcon::Find $::tkcon::PRIV(console) $args
2665 fo* {
2666 ## 'font' ?fontname? - gets/sets the font of the console
2667 if {[llength $args]} {
2668 if {[info exists ::tkcon::PRIV(console)] && \
2669 [winfo exists $::tkcon::PRIV(console)]} {
2670 $::tkcon::PRIV(console) config -font $args
2671 set ::tkcon::OPT(font) [$::tkcon::PRIV(console) cget -font]
2672 } else {
2673 set ::tkcon::OPT(font) $args
2676 return $::tkcon::OPT(font)
2678 hid* - with* {
2679 ## 'hide' 'withdraw' - hides the console.
2680 wm withdraw $::tkcon::PRIV(root)
2682 his* {
2683 ## 'history'
2684 set sub {\2}
2685 if {[string match -new* $args]} { append sub "\n"}
2686 set h [::tkcon::EvalSlave history]
2687 regsub -all "( *\[0-9\]+ |\t)(\[^\n\]*\n?)" $h $sub h
2688 return $h
2690 ico* {
2691 ## 'iconify' - iconifies the console with 'iconify'.
2692 wm iconify $::tkcon::PRIV(root)
2694 mas* - eval {
2695 ## 'master' - evals contents in master interpreter
2696 uplevel \#0 $args
2698 set {
2699 ## 'set' - set (or get, or unset) simple vars (not whole arrays)
2700 ## from the master console interpreter
2701 ## possible formats:
2702 ## tkcon set <var>
2703 ## tkcon set <var> <value>
2704 ## tkcon set <var> <interp> <var1> <var2> w
2705 ## tkcon set <var> <interp> <var1> <var2> u
2706 ## tkcon set <var> <interp> <var1> <var2> r
2707 if {[llength $args]==5} {
2708 ## This is for use w/ 'tkcon upvar' and only works with slaves
2709 foreach {var i var1 var2 op} $args break
2710 if {[string compare {} $var2]} { append var1 "($var2)" }
2711 switch $op {
2712 u { uplevel \#0 [list unset $var] }
2714 return [uplevel \#0 [list set $var \
2715 [interp eval $i [list set $var1]]]]
2718 return [interp eval $i [list set $var1 \
2719 [uplevel \#0 [list set $var]]]]
2722 } elseif {[llength $args] == 1} {
2723 upvar \#0 [lindex $args 0] var
2724 if {[array exists var]} {
2725 return [array get var]
2726 } else {
2727 return $var
2730 return [uplevel \#0 set $args]
2732 append {
2733 ## Modify a var in the master environment using append
2734 return [uplevel \#0 append $args]
2736 lappend {
2737 ## Modify a var in the master environment using lappend
2738 return [uplevel \#0 lappend $args]
2740 sh* - dei* {
2741 ## 'show|deiconify' - deiconifies the console.
2742 wm deiconify $::tkcon::PRIV(root)
2743 raise $::tkcon::PRIV(root)
2744 focus -force $::tkcon::PRIV(console)
2746 ti* {
2747 ## 'title' ?title? - gets/sets the console's title
2748 if {[llength $args]} {
2749 return [wm title $::tkcon::PRIV(root) [join $args]]
2750 } else {
2751 return [wm title $::tkcon::PRIV(root)]
2754 upv* {
2755 ## 'upvar' masterVar slaveVar
2756 ## link slave variable slaveVar to the master variable masterVar
2757 ## only works masters<->slave
2758 set masterVar [lindex $args 0]
2759 set slaveVar [lindex $args 1]
2760 if {[info exists $masterVar]} {
2761 interp eval $::tkcon::OPT(exec) \
2762 [list set $slaveVar [set $masterVar]]
2763 } else {
2764 catch {interp eval $::tkcon::OPT(exec) [list unset $slaveVar]}
2766 interp eval $::tkcon::OPT(exec) \
2767 [list trace variable $slaveVar rwu \
2768 [list tkcon set $masterVar $::tkcon::OPT(exec)]]
2769 return
2771 v* {
2772 return $::tkcon::PRIV(version)
2774 default {
2775 ## tries to determine if the command exists, otherwise throws error
2776 set new ::tkcon::[string toupper \
2777 [string index $cmd 0]][string range $cmd 1 end]
2778 if {[llength [info command $new]]} {
2779 uplevel \#0 $new $args
2780 } else {
2781 return -code error "bad option \"$cmd\": must be\
2782 [join [lsort [list attach close console destroy \
2783 font hide iconify load main master new save show \
2784 slave deiconify version title bgerror]] {, }]"
2791 ## Some procedures to make up for lack of built-in shell commands
2794 ## tkcon_puts -
2795 ## This allows me to capture all stdout/stderr to the console window
2796 ## This will be renamed to 'puts' at the appropriate time during init
2798 # ARGS: same as usual
2799 # Outputs: the string with a color-coded text tag
2801 proc tkcon_puts args {
2802 set len [llength $args]
2803 foreach {arg1 arg2 arg3} $args { break }
2805 if {$len == 1} {
2806 set sarg $arg1
2807 set nl 1
2808 set farg stdout
2809 } elseif {$len == 2} {
2810 if {![string compare $arg1 -nonewline]} {
2811 set sarg $arg2
2812 set farg stdout
2813 set nl 0
2814 } elseif {![string compare $arg1 stdout] \
2815 || ![string compare $arg1 stderr]} {
2816 set sarg $arg2
2817 set farg $arg1
2818 set nl 1
2819 } else {
2820 set len 0
2822 } elseif {$len == 3} {
2823 if {![string compare $arg1 -nonewline] \
2824 && (![string compare $arg2 stdout] \
2825 || ![string compare $arg2 stderr])} {
2826 set sarg $arg3
2827 set farg $arg2
2828 set nl 0
2829 } elseif {(![string compare $arg1 stdout] \
2830 || ![string compare $arg1 stderr]) \
2831 && ![string compare $arg3 nonewline]} {
2832 set sarg $arg2
2833 set farg $arg1
2834 set nl 0
2835 } else {
2836 set len 0
2838 } else {
2839 set len 0
2842 ## $len == 0 means it wasn't handled by tkcon above.
2845 if {$len != 0} {
2847 ## "poor man's" \r substitution---erase everything on the output
2848 ## line and print from character after the \r
2850 set rpt [string last \r $sarg]
2851 if {$rpt >= 0} {
2852 tkcon console delete "insert linestart" "insert lineend"
2853 set sarg [string range $sarg [expr {$rpt + 1}] end]
2856 set bpt [string first \b $sarg]
2857 if {$bpt >= 0} {
2858 set narg [string range $sarg [expr {$bpt + 1}] end]
2859 set sarg [string range $sarg 0 [expr {$bpt - 1}]]
2860 set nl 0
2864 if {$nl == 0} {
2865 tkcon console insert output $sarg $farg
2866 } else {
2867 tkcon console insert output "$sarg\n" $farg
2870 if {$bpt >= 0} {
2871 tkcon console delete "insert -1 char" insert
2872 if {$nl == 0} {
2873 tkcon_puts $farg $narg nonewline
2874 } else {
2875 tkcon_puts $farg $narg
2879 } else {
2880 global errorCode errorInfo
2881 if {[catch "tkcon_tcl_puts $args" msg]} {
2882 regsub tkcon_tcl_puts $msg puts msg
2883 regsub -all tkcon_tcl_puts $errorInfo puts errorInfo
2884 return -code error $msg
2886 return $msg
2889 ## WARNING: This update should behave well because it uses idletasks,
2890 ## however, if there are weird looping problems with events, or
2891 ## hanging in waits, try commenting this out.
2892 if {$len} {
2893 tkcon console see output
2894 update idletasks
2898 ## tkcon_gets -
2899 ## This allows me to capture all stdin input without needing to stdin
2900 ## This will be renamed to 'gets' at the appropriate time during init
2902 # ARGS: same as gets
2903 # Outputs: same as gets
2905 proc tkcon_gets args {
2906 set len [llength $args]
2907 if {$len != 1 && $len != 2} {
2908 return -code error \
2909 "wrong # args: should be \"gets channelId ?varName?\""
2911 if {[string compare stdin [lindex $args 0]]} {
2912 return [uplevel 1 tkcon_tcl_gets $args]
2914 set gtype [tkcon set ::tkcon::OPT(gets)]
2915 if {$gtype == ""} { set gtype congets }
2916 set data [tkcon $gtype]
2917 if {$len == 2} {
2918 upvar 1 [lindex $args 1] var
2919 set var $data
2920 return [string length $data]
2922 return $data
2925 ## edit - opens a file/proc/var for reading/editing
2927 # Arguments:
2928 # type proc/file/var
2929 # what the actual name of the item
2930 # Returns: nothing
2932 proc edit {args} {
2933 array set opts {-find {} -type {} -attach {}}
2934 while {[string match -* [lindex $args 0]]} {
2935 switch -glob -- [lindex $args 0] {
2936 -f* { set opts(-find) [lindex $args 1] }
2937 -a* { set opts(-attach) [lindex $args 1] }
2938 -t* { set opts(-type) [lindex $args 1] }
2939 -- { set args [lreplace $args 0 0]; break }
2940 default {return -code error "unknown option \"[lindex $args 0]\""}
2942 set args [lreplace $args 0 1]
2944 # determine who we are dealing with
2945 if {[llength $opts(-attach)]} {
2946 foreach {app type} $opts(-attach) {break}
2947 } else {
2948 foreach {app type} [tkcon attach] {break}
2951 set word [lindex $args 0]
2952 if {[string match {} $opts(-type)]} {
2953 if {[llength [::tkcon::EvalOther $app $type info commands [list $word]]]} {
2954 set opts(-type) "proc"
2955 } elseif {[llength [::tkcon::EvalOther $app $type info vars [list $word]]]} {
2956 set opts(-type) "var"
2957 } elseif {[::tkcon::EvalOther $app $type file isfile [list $word]]} {
2958 set opts(-type) "file"
2961 if {[string compare $opts(-type) {}]} {
2962 # Create unique edit window toplevel
2963 set w $::tkcon::PRIV(base).__edit
2964 set i 0
2965 while {[winfo exists $w[incr i]]} {}
2966 append w $i
2967 toplevel $w
2968 wm withdraw $w
2969 if {[string length $word] > 12} {
2970 wm title $w "tkcon Edit: [string range $word 0 9]..."
2971 } else {
2972 wm title $w "tkcon Edit: $word"
2975 text $w.text -wrap none \
2976 -xscrollcommand [list $w.sx set] \
2977 -yscrollcommand [list $w.sy set] \
2978 -foreground $::tkcon::COLOR(stdin) \
2979 -background $::tkcon::COLOR(bg) \
2980 -insertbackground $::tkcon::COLOR(cursor) \
2981 -font $::tkcon::OPT(font)
2982 scrollbar $w.sx -orient h -takefocus 0 -bd 1 \
2983 -command [list $w.text xview]
2984 scrollbar $w.sy -orient v -takefocus 0 -bd 1 \
2985 -command [list $w.text yview]
2987 set menu [menu $w.mbar]
2988 $w configure -menu $menu
2990 ## File Menu
2992 set m [menu [::tkcon::MenuButton $menu File file]]
2993 $m add command -label "Save As..." -underline 0 \
2994 -command [list ::tkcon::Save {} widget $w.text]
2995 $m add command -label "Append To..." -underline 0 \
2996 -command [list ::tkcon::Save {} widget $w.text a+]
2997 $m add separator
2998 $m add command -label "Dismiss" -underline 0 -accel "Ctrl-w" \
2999 -command [list destroy $w]
3000 bind $w <Control-w> [list destroy $w]
3001 bind $w <$::tkcon::PRIV(meta)-w> [list destroy $w]
3003 ## Edit Menu
3005 set text $w.text
3006 set m [menu [::tkcon::MenuButton $menu Edit edit]]
3007 $m add command -label "Cut" -underline 2 \
3008 -command [list tk_textCut $text]
3009 $m add command -label "Copy" -underline 0 \
3010 -command [list tk_textCopy $text]
3011 $m add command -label "Paste" -underline 0 \
3012 -command [list tk_textPaste $text]
3013 $m add separator
3014 $m add command -label "Find" -underline 0 \
3015 -command [list ::tkcon::FindBox $text]
3017 ## Send To Menu
3019 set m [menu [::tkcon::MenuButton $menu "Send To..." send]]
3020 $m add command -label "Send To $app" -underline 0 \
3021 -command "::tkcon::EvalOther [list $app] $type \
3022 eval \[$w.text get 1.0 end-1c\]"
3023 set other [tkcon attach]
3024 if {[string compare $other [list $app $type]]} {
3025 $m add command -label "Send To [lindex $other 0]" \
3026 -command "::tkcon::EvalOther $other \
3027 eval \[$w.text get 1.0 end-1c\]"
3030 grid $w.text - $w.sy -sticky news
3031 grid $w.sx - -sticky ew
3032 grid columnconfigure $w 0 -weight 1
3033 grid columnconfigure $w 1 -weight 1
3034 grid rowconfigure $w 0 -weight 1
3035 } else {
3036 return -code error "unrecognized type '$word'"
3038 switch -glob -- $opts(-type) {
3039 proc* {
3040 $w.text insert 1.0 \
3041 [::tkcon::EvalOther $app $type dump proc [list $word]]
3043 var* {
3044 $w.text insert 1.0 \
3045 [::tkcon::EvalOther $app $type dump var [list $word]]
3047 file {
3048 $w.text insert 1.0 [::tkcon::EvalOther $app $type eval \
3049 [subst -nocommands {
3050 set __tkcon(fid) [open $word r]
3051 set __tkcon(data) [read \$__tkcon(fid)]
3052 close \$__tkcon(fid)
3053 after 1000 unset __tkcon
3054 return \$__tkcon(data)
3058 error* {
3059 $w.text insert 1.0 [join $args \n]
3060 ::tkcon::ErrorHighlight $w.text
3062 default {
3063 $w.text insert 1.0 [join $args \n]
3066 wm deiconify $w
3067 focus $w.text
3068 if {[string compare $opts(-find) {}]} {
3069 ::tkcon::Find $w.text $opts(-find) -case 1
3072 interp alias {} ::more {} ::edit
3073 interp alias {} ::less {} ::edit
3075 ## echo
3076 ## Relaxes the one string restriction of 'puts'
3077 # ARGS: any number of strings to output to stdout
3079 proc echo args { puts [concat $args] }
3081 ## clear - clears the buffer of the console (not the history though)
3082 ## This is executed in the parent interpreter
3084 proc clear {{pcnt 100}} {
3085 if {![regexp {^[0-9]*$} $pcnt] || $pcnt < 1 || $pcnt > 100} {
3086 return -code error \
3087 "invalid percentage to clear: must be 1-100 (100 default)"
3088 } elseif {$pcnt == 100} {
3089 tkcon console delete 1.0 end
3090 } else {
3091 set tmp [expr {$pcnt/100.0*[tkcon console index end]}]
3092 tkcon console delete 1.0 "$tmp linestart"
3096 ## alias - akin to the csh alias command
3097 ## If called with no args, then it dumps out all current aliases
3098 ## If called with one arg, returns the alias of that arg (or {} if none)
3099 # ARGS: newcmd - (optional) command to bind alias to
3100 # args - command and args being aliased
3102 proc alias {{newcmd {}} args} {
3103 if {[string match {} $newcmd]} {
3104 set res {}
3105 foreach a [interp aliases] {
3106 lappend res [list $a -> [interp alias {} $a]]
3108 return [join $res \n]
3109 } elseif {![llength $args]} {
3110 interp alias {} $newcmd
3111 } else {
3112 eval interp alias [list {} $newcmd {}] $args
3116 ## unalias - unaliases an alias'ed command
3117 # ARGS: cmd - command to unbind as an alias
3119 proc unalias {cmd} {
3120 interp alias {} $cmd {}
3123 ## dump - outputs variables/procedure/widget info in source'able form.
3124 ## Accepts glob style pattern matching for the names
3126 # ARGS: type - type of thing to dump: must be variable, procedure, widget
3128 # OPTS: -nocomplain
3129 # don't complain if no items of the specified type are found
3130 # -filter pattern
3131 # specifies a glob filter pattern to be used by the variable
3132 # method as an array filter pattern (it filters down for
3133 # nested elements) and in the widget method as a config
3134 # option filter pattern
3135 # -- forcibly ends options recognition
3137 # Returns: the values of the requested items in a 'source'able form
3139 proc dump {type args} {
3140 set whine 1
3141 set code ok
3142 if {![llength $args]} {
3143 ## If no args, assume they gave us something to dump and
3144 ## we'll try anything
3145 set args $type
3146 set type any
3148 while {[string match -* [lindex $args 0]]} {
3149 switch -glob -- [lindex $args 0] {
3150 -n* { set whine 0; set args [lreplace $args 0 0] }
3151 -f* { set fltr [lindex $args 1]; set args [lreplace $args 0 1] }
3152 -- { set args [lreplace $args 0 0]; break }
3153 default {return -code error "unknown option \"[lindex $args 0]\""}
3156 if {$whine && ![llength $args]} {
3157 return -code error "wrong \# args: [lindex [info level 0] 0] type\
3158 ?-nocomplain? ?-filter pattern? ?--? pattern ?pattern ...?"
3160 set res {}
3161 switch -glob -- $type {
3162 c* {
3163 # command
3164 # outputs commands by figuring out, as well as possible, what it is
3165 # this does not attempt to auto-load anything
3166 foreach arg $args {
3167 if {[llength [set cmds [info commands $arg]]]} {
3168 foreach cmd [lsort $cmds] {
3169 if {[lsearch -exact [interp aliases] $cmd] > -1} {
3170 append res "\#\# ALIAS: $cmd =>\
3171 [interp alias {} $cmd]\n"
3172 } elseif {
3173 [llength [info procs $cmd]] ||
3174 ([string match *::* $cmd] &&
3175 [llength [namespace eval [namespace qual $cmd] \
3176 info procs [namespace tail $cmd]]])
3178 if {[catch {dump p -- $cmd} msg] && $whine} {
3179 set code error
3181 append res $msg\n
3182 } else {
3183 append res "\#\# COMMAND: $cmd\n"
3186 } elseif {$whine} {
3187 append res "\#\# No known command $arg\n"
3188 set code error
3192 v* {
3193 # variable
3194 # outputs variables value(s), whether array or simple.
3195 if {![info exists fltr]} { set fltr * }
3196 foreach arg $args {
3197 if {![llength [set vars [uplevel 1 info vars [list $arg]]]]} {
3198 if {[uplevel 1 info exists $arg]} {
3199 set vars $arg
3200 } elseif {$whine} {
3201 append res "\#\# No known variable $arg\n"
3202 set code error
3203 continue
3204 } else { continue }
3206 foreach var [lsort $vars] {
3207 if {[uplevel 1 [list info locals $var]] == ""} {
3208 # use the proper scope of the var, but
3209 # namespace which won't id locals correctly
3210 set var [uplevel 1 \
3211 [list namespace which -variable $var]]
3213 upvar 1 $var v
3214 if {[array exists v] || [catch {string length $v}]} {
3215 set nst {}
3216 append res "array set [list $var] \{\n"
3217 if {[array size v]} {
3218 foreach i [lsort [array names v $fltr]] {
3219 upvar 0 v\($i\) __a
3220 if {[array exists __a]} {
3221 append nst "\#\# NESTED ARRAY ELEM: $i\n"
3222 append nst "upvar 0 [list $var\($i\)] __a;\
3223 [dump v -filter $fltr __a]\n"
3224 } else {
3225 append res " [list $i]\t[list $v($i)]\n"
3228 } else {
3229 ## empty array
3230 append res " empty array\n"
3231 append nst "unset [list $var](empty)\n"
3233 append res "\}\n$nst"
3234 } else {
3235 append res [list set $var $v]\n
3240 p* {
3241 # procedure
3242 foreach arg $args {
3243 if {
3244 ![llength [set procs [info proc $arg]]] &&
3245 ([string match *::* $arg] &&
3246 [llength [set ps [namespace eval \
3247 [namespace qualifier $arg] \
3248 info procs [namespace tail $arg]]]])
3250 set procs {}
3251 set namesp [namespace qualifier $arg]
3252 foreach p $ps {
3253 lappend procs ${namesp}::$p
3256 if {[llength $procs]} {
3257 foreach p [lsort $procs] {
3258 set as {}
3259 foreach a [info args $p] {
3260 if {[info default $p $a tmp]} {
3261 lappend as [list $a $tmp]
3262 } else {
3263 lappend as $a
3266 append res [list proc $p $as [info body $p]]\n
3268 } elseif {$whine} {
3269 append res "\#\# No known proc $arg\n"
3270 set code error
3274 w* {
3275 # widget
3276 ## The user should have Tk loaded
3277 if {![llength [info command winfo]]} {
3278 return -code error "winfo not present, cannot dump widgets"
3280 if {![info exists fltr]} { set fltr .* }
3281 foreach arg $args {
3282 if {[llength [set ws [info command $arg]]]} {
3283 foreach w [lsort $ws] {
3284 if {[winfo exists $w]} {
3285 if {[catch {$w configure} cfg]} {
3286 append res "\#\# Widget $w\
3287 does not support configure method"
3288 set code error
3289 } else {
3290 append res "\#\# [winfo class $w]\
3291 $w\n$w configure"
3292 foreach c $cfg {
3293 if {[llength $c] != 5} continue
3294 ## Check to see that the option does
3295 ## not match the default, then check
3296 ## the item against the user filter
3297 if {[string compare [lindex $c 3] \
3298 [lindex $c 4]] && \
3299 [regexp -nocase -- $fltr $c]} {
3300 append res " \\\n\t[list [lindex $c 0]\
3301 [lindex $c 4]]"
3304 append res \n
3308 } elseif {$whine} {
3309 append res "\#\# No known widget $arg\n"
3310 set code error
3314 a* {
3315 ## see if we recognize it, other complain
3316 if {[regexp {(var|com|proc|widget)} \
3317 [set types [uplevel 1 what $args]]]} {
3318 foreach type $types {
3319 if {[regexp {(var|com|proc|widget)} $type]} {
3320 append res "[uplevel 1 dump $type $args]\n"
3323 } else {
3324 set res "dump was unable to resolve type for \"$args\""
3325 set code error
3328 default {
3329 return -code error "bad [lindex [info level 0] 0] option\
3330 \"$type\": must be variable, command, procedure,\
3331 or widget"
3334 return -code $code [string trimright $res \n]
3337 ## idebug - interactive debugger
3339 # idebug body ?level?
3341 # Prints out the body of the command (if it is a procedure) at the
3342 # specified level. <i>level</i> defaults to the current level.
3344 # idebug break
3346 # Creates a breakpoint within a procedure. This will only trigger
3347 # if idebug is on and the id matches the pattern. If so, TkCon will
3348 # pop to the front with the prompt changed to an idebug prompt. You
3349 # are given the basic ability to observe the call stack an query/set
3350 # variables or execute Tcl commands at any level. A separate history
3351 # is maintained in debugging mode.
3353 # idebug echo|{echo ?id?} ?args?
3355 # Behaves just like "echo", but only triggers when idebug is on.
3356 # You can specify an optional id to further restrict triggering.
3357 # If no id is specified, it defaults to the name of the command
3358 # in which the call was made.
3360 # idebug id ?id?
3362 # Query or set the idebug id. This id is used by other idebug
3363 # methods to determine if they should trigger or not. The idebug
3364 # id can be a glob pattern and defaults to *.
3366 # idebug off
3368 # Turns idebug off.
3370 # idebug on ?id?
3372 # Turns idebug on. If 'id' is specified, it sets the id to it.
3374 # idebug puts|{puts ?id?} args
3376 # Behaves just like "puts", but only triggers when idebug is on.
3377 # You can specify an optional id to further restrict triggering.
3378 # If no id is specified, it defaults to the name of the command
3379 # in which the call was made.
3381 # idebug show type ?level? ?VERBOSE?
3383 # 'type' must be one of vars, locals or globals. This method
3384 # will output the variables/locals/globals present in a particular
3385 # level. If VERBOSE is added, then it actually 'dump's out the
3386 # values as well. 'level' defaults to the level in which this
3387 # method was called.
3389 # idebug trace ?level?
3391 # Prints out the stack trace from the specified level up to the top
3392 # level. 'level' defaults to the current level.
3395 proc idebug {opt args} {
3396 global IDEBUG
3398 if {![info exists IDEBUG(on)]} {
3399 array set IDEBUG { on 0 id * debugging 0 }
3401 set level [expr {[info level]-1}]
3402 switch -glob -- $opt {
3403 on {
3404 if {[llength $args]} { set IDEBUG(id) $args }
3405 return [set IDEBUG(on) 1]
3407 off { return [set IDEBUG(on) 0] }
3408 id {
3409 if {![llength $args]} {
3410 return $IDEBUG(id)
3411 } else { return [set IDEBUG(id) $args] }
3413 break {
3414 if {!$IDEBUG(on) || $IDEBUG(debugging) || \
3415 ([llength $args] && \
3416 ![string match $IDEBUG(id) $args]) || [info level]<1} {
3417 return
3419 set IDEBUG(debugging) 1
3420 puts stderr "idebug at level \#$level: [lindex [info level -1] 0]"
3421 set tkcon [llength [info command tkcon]]
3422 if {$tkcon} {
3423 tkcon master eval set ::tkcon::OPT(prompt2) \$::tkcon::OPT(prompt1)
3424 tkcon master eval set ::tkcon::OPT(prompt1) \$::tkcon::OPT(debugPrompt)
3425 set slave [tkcon set ::tkcon::OPT(exec)]
3426 set event [tkcon set ::tkcon::PRIV(event)]
3427 tkcon set ::tkcon::OPT(exec) [tkcon master interp create debugger]
3428 tkcon set ::tkcon::PRIV(event) 1
3430 set max $level
3431 while 1 {
3432 set err {}
3433 if {$tkcon} {
3434 # tkcon's overload of gets is advanced enough to not need
3435 # this, but we get a little better control this way.
3436 tkcon evalSlave set level $level
3437 tkcon prompt
3438 set line [tkcon getcommand]
3439 tkcon console mark set output end
3440 } else {
3441 puts -nonewline stderr "(level \#$level) debug > "
3442 gets stdin line
3443 while {![info complete $line]} {
3444 puts -nonewline "> "
3445 append line "\n[gets stdin]"
3448 if {[string match {} $line]} continue
3449 set key [lindex $line 0]
3450 if {![regexp {^([#-]?[0-9]+)} [lreplace $line 0 0] lvl]} {
3451 set lvl \#$level
3453 set res {}; set c 0
3454 switch -- $key {
3456 ## Allow for jumping multiple levels
3457 if {$level < $max} {
3458 idebug trace [incr level] $level 0 VERBOSE
3462 ## Allow for jumping multiple levels
3463 if {$level > 1} {
3464 idebug trace [incr level -1] $level 0 VERBOSE
3467 . { set c [catch {idebug trace $level $level 0 VERBOSE} res] }
3468 v { set c [catch {idebug show vars $lvl } res] }
3469 V { set c [catch {idebug show vars $lvl VERBOSE} res] }
3470 l { set c [catch {idebug show locals $lvl } res] }
3471 L { set c [catch {idebug show locals $lvl VERBOSE} res] }
3472 g { set c [catch {idebug show globals $lvl } res] }
3473 G { set c [catch {idebug show globals $lvl VERBOSE} res] }
3474 t { set c [catch {idebug trace 1 $max $level } res] }
3475 T { set c [catch {idebug trace 1 $max $level VERBOSE} res]}
3476 b { set c [catch {idebug body $lvl} res] }
3477 o { set res [set IDEBUG(on) [expr {!$IDEBUG(on)}]] }
3478 h - ? {
3479 puts stderr " + Move down in call stack
3480 - Move up in call stack
3481 . Show current proc name and params
3483 v Show names of variables currently in scope
3484 V Show names of variables currently in scope with values
3485 l Show names of local (transient) variables
3486 L Show names of local (transient) variables with values
3487 g Show names of declared global variables
3488 G Show names of declared global variables with values
3489 t Show a stack trace
3490 T Show a verbose stack trace
3492 b Show body of current proc
3493 o Toggle on/off any further debugging
3494 c,q Continue regular execution (Quit debugger)
3495 h,? Print this help
3496 default Evaluate line at current level (\#$level)"
3498 c - q break
3499 default { set c [catch {uplevel \#$level $line} res] }
3501 if {$tkcon} {
3502 tkcon set ::tkcon::PRIV(event) \
3503 [tkcon evalSlave eval history add [list $line]\
3504 \; history nextid]
3506 if {$c} {
3507 puts stderr $res
3508 } elseif {[string compare {} $res]} {
3509 puts $res
3512 set IDEBUG(debugging) 0
3513 if {$tkcon} {
3514 tkcon master interp delete debugger
3515 tkcon master eval set ::tkcon::OPT(prompt1) \$::tkcon::OPT(prompt2)
3516 tkcon set ::tkcon::OPT(exec) $slave
3517 tkcon set ::tkcon::PRIV(event) $event
3518 tkcon prompt
3521 bo* {
3522 if {[regexp {^([#-]?[0-9]+)} $args level]} {
3523 return [uplevel $level {dump c -no [lindex [info level 0] 0]}]
3526 t* {
3527 if {[llength $args]<2} return
3528 set min [set max [set lvl $level]]
3529 set exp {^#?([0-9]+)? ?#?([0-9]+) ?#?([0-9]+)? ?(VERBOSE)?}
3530 if {![regexp $exp $args junk min max lvl verbose]} return
3531 for {set i $max} {
3532 $i>=$min && ![catch {uplevel \#$i info level 0} info]
3533 } {incr i -1} {
3534 if {$i==$lvl} {
3535 puts -nonewline stderr "* \#$i:\t"
3536 } else {
3537 puts -nonewline stderr " \#$i:\t"
3539 set name [lindex $info 0]
3540 if {[string compare VERBOSE $verbose] || \
3541 ![llength [info procs $name]]} {
3542 puts $info
3543 } else {
3544 puts "proc $name {[info args $name]} { ... }"
3545 set idx 0
3546 foreach arg [info args $name] {
3547 if {[string match args $arg]} {
3548 puts "\t$arg = [lrange $info [incr idx] end]"
3549 break
3550 } else {
3551 puts "\t$arg = [lindex $info [incr idx]]"
3557 s* {
3558 #var, local, global
3559 set level \#$level
3560 if {![regexp {^([vgl][^ ]*) ?([#-]?[0-9]+)? ?(VERBOSE)?} \
3561 $args junk type level verbose]} return
3562 switch -glob -- $type {
3563 v* { set vars [uplevel $level {lsort [info vars]}] }
3564 l* { set vars [uplevel $level {lsort [info locals]}] }
3565 g* { set vars [lremove [uplevel $level {info vars}] \
3566 [uplevel $level {info locals}]] }
3568 if {[string match VERBOSE $verbose]} {
3569 return [uplevel $level dump var -nocomplain $vars]
3570 } else {
3571 return $vars
3574 e* - pu* {
3575 if {[llength $opt]==1 && [catch {lindex [info level -1] 0} id]} {
3576 set id [lindex [info level 0] 0]
3577 } else {
3578 set id [lindex $opt 1]
3580 if {$IDEBUG(on) && [string match $IDEBUG(id) $id]} {
3581 if {[string match e* $opt]} {
3582 puts [concat $args]
3583 } else { eval puts $args }
3586 default {
3587 return -code error "bad [lindex [info level 0] 0] option \"$opt\",\
3588 must be: [join [lsort [list on off id break print body\
3589 trace show puts echo]] {, }]"
3594 ## observe - like trace, but not
3595 # ARGS: opt - option
3596 # name - name of variable or command
3598 proc observe {opt name args} {
3599 global tcl_observe
3600 switch -glob -- $opt {
3601 co* {
3602 if {[regexp {^(catch|lreplace|set|puts|for|incr|info|uplevel)$} \
3603 $name]} {
3604 return -code error "cannot observe \"$name\":\
3605 infinite eval loop will occur"
3607 set old ${name}@
3608 while {[llength [info command $old]]} { append old @ }
3609 rename $name $old
3610 set max 4
3611 regexp {^[0-9]+} $args max
3612 ## idebug trace could be used here
3613 proc $name args "
3614 for {set i \[info level\]; set max \[expr \[info level\]-$max\]} {
3615 \$i>=\$max && !\[catch {uplevel \#\$i info level 0} info\]
3616 } {incr i -1} {
3617 puts -nonewline stderr \" \#\$i:\t\"
3618 puts \$info
3620 uplevel \[lreplace \[info level 0\] 0 0 $old\]
3622 set tcl_observe($name) $old
3624 cd* {
3625 if {[info exists tcl_observe($name)] && [catch {
3626 rename $name {}
3627 rename $tcl_observe($name) $name
3628 unset tcl_observe($name)
3629 } err]} { return -code error $err }
3631 ci* {
3632 ## What a useless method...
3633 if {[info exists tcl_observe($name)]} {
3634 set i $tcl_observe($name)
3635 set res "\"$name\" observes true command \"$i\""
3636 while {[info exists tcl_observe($i)]} {
3637 append res "\n\"$name\" observes true command \"$i\""
3638 set i $tcl_observe($name)
3640 return $res
3643 va* - vd* {
3644 set type [lindex $args 0]
3645 set args [lrange $args 1 end]
3646 if {![regexp {^[rwu]} $type type]} {
3647 return -code error "bad [lindex [info level 0] 0] $opt type\
3648 \"$type\", must be: read, write or unset"
3650 if {![llength $args]} { set args observe_var }
3651 uplevel 1 [list trace $opt $name $type $args]
3653 vi* {
3654 uplevel 1 [list trace vinfo $name]
3656 default {
3657 return -code error "bad [lindex [info level 0] 0] option\
3658 \"[lindex $args 0]\", must be: [join [lsort \
3659 [list command cdelete cinfo variable vdelete vinfo]] {, }]"
3664 ## observe_var - auxilary function for observing vars, called by trace
3665 ## via observe
3666 # ARGS: name - variable name
3667 # el - array element name, if any
3668 # op - operation type (rwu)
3670 proc observe_var {name el op} {
3671 if {[string match u $op]} {
3672 if {[string compare {} $el]} {
3673 puts "unset \"${name}($el)\""
3674 } else {
3675 puts "unset \"$name\""
3677 } else {
3678 upvar 1 $name $name
3679 if {[info exists ${name}($el)]} {
3680 puts [dump v ${name}($el)]
3681 } else {
3682 puts [dump v $name]
3687 ## which - tells you where a command is found
3688 # ARGS: cmd - command name
3689 # Returns: where command is found (internal / external / unknown)
3691 proc which cmd {
3692 ## This tries to auto-load a command if not recognized
3693 set types [uplevel 1 [list what $cmd 1]]
3694 if {[llength $types]} {
3695 set out {}
3697 foreach type $types {
3698 switch -- $type {
3699 alias { set res "$cmd: aliased to [alias $cmd]" }
3700 procedure { set res "$cmd: procedure" }
3701 command { set res "$cmd: internal command" }
3702 executable { lappend out [auto_execok $cmd] }
3703 variable { lappend out "$cmd: $type" }
3705 if {[info exists res]} {
3706 global auto_index
3707 if {[info exists auto_index($cmd)]} {
3708 ## This tells you where the command MIGHT have come from -
3709 ## not true if the command was redefined interactively or
3710 ## existed before it had to be auto_loaded. This is just
3711 ## provided as a hint at where it MAY have come from
3712 append res " ($auto_index($cmd))"
3714 lappend out $res
3715 unset res
3718 return [join $out \n]
3719 } else {
3720 return -code error "$cmd: command not found"
3724 ## what - tells you what a string is recognized as
3725 # ARGS: str - string to id
3726 # Returns: id types of command as list
3728 proc what {str {autoload 0}} {
3729 set types {}
3730 if {[llength [info commands $str]] || ($autoload && \
3731 [auto_load $str] && [llength [info commands $str]])} {
3732 if {[lsearch -exact [interp aliases] $str] > -1} {
3733 lappend types "alias"
3734 } elseif {
3735 [llength [info procs $str]] ||
3736 ([string match *::* $str] &&
3737 [llength [namespace eval [namespace qualifier $str] \
3738 info procs [namespace tail $str]]])
3740 lappend types "procedure"
3741 } else {
3742 lappend types "command"
3745 if {[llength [uplevel 1 info vars $str]]} {
3746 upvar 1 $str var
3747 if {[array exists var]} {
3748 lappend types array variable
3749 } else {
3750 lappend types scalar variable
3753 if {[file isdirectory $str]} {
3754 lappend types "directory"
3756 if {[file isfile $str]} {
3757 lappend types "file"
3759 if {[llength [info commands winfo]] && [winfo exists $str]} {
3760 lappend types "widget"
3762 if {[string compare {} [auto_execok $str]]} {
3763 lappend types "executable"
3765 return $types
3768 ## dir - directory list
3769 # ARGS: args - names/glob patterns of directories to list
3770 # OPTS: -all - list hidden files as well (Unix dot files)
3771 # -long - list in full format "permissions size date filename"
3772 # -full - displays / after directories and link paths for links
3773 # Returns: a directory listing
3775 proc dir {args} {
3776 array set s {
3777 all 0 full 0 long 0
3778 0 --- 1 --x 2 -w- 3 -wx 4 r-- 5 r-x 6 rw- 7 rwx
3780 while {[string match \-* [lindex $args 0]]} {
3781 set str [lindex $args 0]
3782 set args [lreplace $args 0 0]
3783 switch -glob -- $str {
3784 -a* {set s(all) 1} -f* {set s(full) 1}
3785 -l* {set s(long) 1} -- break
3786 default {
3787 return -code error "unknown option \"$str\",\
3788 should be one of: -all, -full, -long"
3792 set sep [string trim [file join . .] .]
3793 if {![llength $args]} { set args . }
3794 if {$::tcl_version >= 8.3} {
3795 # Newer glob args allow safer dir processing. The user may still
3796 # want glob chars, but really only for file matching.
3797 foreach arg $args {
3798 if {[file isdirectory $arg]} {
3799 if {$s(all)} {
3800 lappend out [list $arg [lsort \
3801 [glob -nocomplain -directory $arg .* *]]]
3802 } else {
3803 lappend out [list $arg [lsort \
3804 [glob -nocomplain -directory $arg *]]]
3806 } else {
3807 set dir [file dirname $arg]
3808 lappend out [list $dir$sep [lsort \
3809 [glob -nocomplain -directory $dir [file tail $arg]]]]
3812 } else {
3813 foreach arg $args {
3814 if {[file isdirectory $arg]} {
3815 set arg [string trimright $arg $sep]$sep
3816 if {$s(all)} {
3817 lappend out [list $arg [lsort [glob -nocomplain -- $arg.* $arg*]]]
3818 } else {
3819 lappend out [list $arg [lsort [glob -nocomplain -- $arg*]]]
3821 } else {
3822 lappend out [list [file dirname $arg]$sep \
3823 [lsort [glob -nocomplain -- $arg]]]
3827 if {$s(long)} {
3828 set old [clock scan {1 year ago}]
3829 set fmt "%s%9d %s %s\n"
3830 foreach o $out {
3831 set d [lindex $o 0]
3832 append res $d:\n
3833 foreach f [lindex $o 1] {
3834 file lstat $f st
3835 set f [file tail $f]
3836 if {$s(full)} {
3837 switch -glob $st(type) {
3838 d* { append f $sep }
3839 l* { append f "@ -> [file readlink $d$sep$f]" }
3840 default { if {[file exec $d$sep$f]} { append f * } }
3843 if {[string match file $st(type)]} {
3844 set mode -
3845 } else {
3846 set mode [string index $st(type) 0]
3848 foreach j [split [format %03o [expr {$st(mode)&0777}]] {}] {
3849 append mode $s($j)
3851 if {$st(mtime)>$old} {
3852 set cfmt {%b %d %H:%M}
3853 } else {
3854 set cfmt {%b %d %Y}
3856 append res [format $fmt $mode $st(size) \
3857 [clock format $st(mtime) -format $cfmt] $f]
3859 append res \n
3861 } else {
3862 foreach o $out {
3863 set d [lindex $o 0]
3864 append res "$d:\n"
3865 set i 0
3866 foreach f [lindex $o 1] {
3867 if {[string len [file tail $f]] > $i} {
3868 set i [string len [file tail $f]]
3871 set i [expr {$i+2+$s(full)}]
3872 set j 80
3873 ## This gets the number of cols in the tkcon console widget
3874 if {[llength [info commands tkcon]]} {
3875 set j [expr {[tkcon master set ::tkcon::OPT(cols)]/$i}]
3877 set k 0
3878 foreach f [lindex $o 1] {
3879 set f [file tail $f]
3880 if {$s(full)} {
3881 switch -glob [file type $d$sep$f] {
3882 d* { append f $sep }
3883 l* { append f @ }
3884 default { if {[file exec $d$sep$f]} { append f * } }
3887 append res [format "%-${i}s" $f]
3888 if {$j == 0 || [incr k]%$j == 0} {
3889 set res [string trimright $res]\n
3892 append res \n\n
3895 return [string trimright $res]
3897 interp alias {} ::ls {} ::dir -full
3899 ## lremove - remove items from a list
3900 # OPTS:
3901 # -all remove all instances of each item
3902 # -glob remove all instances matching glob pattern
3903 # -regexp remove all instances matching regexp pattern
3904 # ARGS: l a list to remove items from
3905 # args items to remove (these are 'join'ed together)
3907 proc lremove {args} {
3908 array set opts {-all 0 pattern -exact}
3909 while {[string match -* [lindex $args 0]]} {
3910 switch -glob -- [lindex $args 0] {
3911 -a* { set opts(-all) 1 }
3912 -g* { set opts(pattern) -glob }
3913 -r* { set opts(pattern) -regexp }
3914 -- { set args [lreplace $args 0 0]; break }
3915 default {return -code error "unknown option \"[lindex $args 0]\""}
3917 set args [lreplace $args 0 0]
3919 set l [lindex $args 0]
3920 foreach i [join [lreplace $args 0 0]] {
3921 if {[set ix [lsearch $opts(pattern) $l $i]] == -1} continue
3922 set l [lreplace $l $ix $ix]
3923 if {$opts(-all)} {
3924 while {[set ix [lsearch $opts(pattern) $l $i]] != -1} {
3925 set l [lreplace $l $ix $ix]
3929 return $l
3932 if {!$::tkcon::PRIV(WWW)} {;
3934 ## Unknown changed to get output into tkcon window
3935 # unknown:
3936 # Invoked automatically whenever an unknown command is encountered.
3937 # Works through a list of "unknown handlers" that have been registered
3938 # to deal with unknown commands. Extensions can integrate their own
3939 # handlers into the 'unknown' facility via 'unknown_handler'.
3941 # If a handler exists that recognizes the command, then it will
3942 # take care of the command action and return a valid result or a
3943 # Tcl error. Otherwise, it should return "-code continue" (=2)
3944 # and responsibility for the command is passed to the next handler.
3946 # Arguments:
3947 # args - A list whose elements are the words of the original
3948 # command, including the command name.
3950 proc unknown args {
3951 global unknown_handler_order unknown_handlers errorInfo errorCode
3954 # Be careful to save error info now, and restore it later
3955 # for each handler. Some handlers generate their own errors
3956 # and disrupt handling.
3958 set savedErrorCode $errorCode
3959 set savedErrorInfo $errorInfo
3961 if {![info exists unknown_handler_order] || \
3962 ![info exists unknown_handlers]} {
3963 set unknown_handlers(tcl) tcl_unknown
3964 set unknown_handler_order tcl
3967 foreach handler $unknown_handler_order {
3968 set status [catch {uplevel 1 $unknown_handlers($handler) $args} result]
3970 if {$status == 1} {
3972 # Strip the last five lines off the error stack (they're
3973 # from the "uplevel" command).
3975 set new [split $errorInfo \n]
3976 set new [join [lrange $new 0 [expr {[llength $new]-6}]] \n]
3977 return -code $status -errorcode $errorCode \
3978 -errorinfo $new $result
3980 } elseif {$status != 4} {
3981 return -code $status $result
3984 set errorCode $savedErrorCode
3985 set errorInfo $savedErrorInfo
3988 set name [lindex $args 0]
3989 return -code error "invalid command name \"$name\""
3992 # tcl_unknown:
3993 # Invoked when a Tcl command is invoked that doesn't exist in the
3994 # interpreter:
3996 # 1. See if the autoload facility can locate the command in a
3997 # Tcl script file. If so, load it and execute it.
3998 # 2. If the command was invoked interactively at top-level:
3999 # (a) see if the command exists as an executable UNIX program.
4000 # If so, "exec" the command.
4001 # (b) see if the command requests csh-like history substitution
4002 # in one of the common forms !!, !<number>, or ^old^new. If
4003 # so, emulate csh's history substitution.
4004 # (c) see if the command is a unique abbreviation for another
4005 # command. If so, invoke the command.
4007 # Arguments:
4008 # args - A list whose elements are the words of the original
4009 # command, including the command name.
4011 proc tcl_unknown args {
4012 global auto_noexec auto_noload env unknown_pending tcl_interactive
4013 global errorCode errorInfo
4015 # If the command word has the form "namespace inscope ns cmd"
4016 # then concatenate its arguments onto the end and evaluate it.
4018 set cmd [lindex $args 0]
4019 if {[regexp "^namespace\[ \t\n\]+inscope" $cmd] && [llength $cmd] == 4} {
4020 set arglist [lrange $args 1 end]
4021 set ret [catch {uplevel 1 $cmd $arglist} result]
4022 if {$ret == 0} {
4023 return $result
4024 } else {
4025 return -code $ret -errorcode $errorCode $result
4029 # CAD tools special:
4030 # Check for commands which were renamed to tcl_(command)
4032 if {[lsearch [info commands] tcl_$cmd] >= 0} {
4033 set arglist [concat tcl_$cmd [lrange $args 1 end]]
4034 set ret [catch {eval $arglist} result]
4035 if {$ret == 0} {
4036 return $result
4037 } else {
4038 return -code $ret -errorcode $errorCode $result
4042 # Save the values of errorCode and errorInfo variables, since they
4043 # may get modified if caught errors occur below. The variables will
4044 # be restored just before re-executing the missing command.
4046 set savedErrorCode $errorCode
4047 set savedErrorInfo $errorInfo
4048 set name [lindex $args 0]
4049 if {![info exists auto_noload]} {
4051 # Make sure we're not trying to load the same proc twice.
4053 if {[info exists unknown_pending($name)]} {
4054 return -code error "self-referential recursion in \"unknown\" for command \"$name\""
4056 set unknown_pending($name) pending
4057 if {[llength [info args auto_load]]==1} {
4058 set ret [catch {auto_load $name} msg]
4059 } else {
4060 set ret [catch {auto_load $name [uplevel 1 {namespace current}]} msg]
4062 unset unknown_pending($name)
4063 if {$ret} {
4064 return -code $ret -errorcode $errorCode \
4065 "error while autoloading \"$name\": $msg"
4068 # Avoid problems with renaming "array"! (for tcl-based magic only)
4070 set arraycmd array
4071 if {[lsearch [info commands] tcl_array] >= 0} {set arraycmd tcl_array}
4073 if {![$arraycmd size unknown_pending]} { unset unknown_pending }
4074 if {$msg} {
4075 set errorCode $savedErrorCode
4076 set errorInfo $savedErrorInfo
4077 set code [catch {uplevel 1 $args} msg]
4078 if {$code == 1} {
4080 # Strip the last five lines off the error stack (they're
4081 # from the "uplevel" command).
4084 set new [split $errorInfo \n]
4085 set new [join [lrange $new 0 [expr {[llength $new]-6}]] \n]
4086 return -code error -errorcode $errorCode \
4087 -errorinfo $new $msg
4088 } else {
4089 return -code $code $msg
4093 if {[info level] == 1 && [string match {} [info script]] \
4094 && [info exists tcl_interactive] && $tcl_interactive} {
4095 if {![info exists auto_noexec]} {
4096 set new [auto_execok $name]
4097 if {[string compare {} $new]} {
4098 set errorCode $savedErrorCode
4099 set errorInfo $savedErrorInfo
4100 return [uplevel 1 exec $new [lrange $args 1 end]]
4101 #return [uplevel exec >&@stdout <@stdin $new [lrange $args 1 end]]
4104 set errorCode $savedErrorCode
4105 set errorInfo $savedErrorInfo
4107 ## History substitution moved into ::tkcon::EvalCmd
4109 set ret [catch {set cmds [info commands $name*]} msg]
4110 if {[string compare $name "::"] == 0} {
4111 set name ""
4113 if {$ret != 0} {
4114 return -code $ret -errorcode $errorCode \
4115 "error in unknown while checking if \"$name\" is a unique command abbreviation: $msg"
4117 set cmds [info commands $name*]
4118 if {[llength $cmds] == 1} {
4119 return [uplevel 1 [lreplace $args 0 0 $cmds]]
4121 if {[llength $cmds]} {
4122 if {$name == ""} {
4123 return -code error "empty command name \"\""
4124 } else {
4125 return -code error \
4126 "ambiguous command name \"$name\": [lsort $cmds]"
4129 ## We've got nothing so far
4130 ## Check and see if Tk wasn't loaded, but it appears to be a Tk cmd
4131 if {![uplevel \#0 info exists tk_version]} {
4132 lappend tkcmds bell bind bindtags button \
4133 canvas checkbutton clipboard destroy \
4134 entry event focus font frame grab grid image \
4135 label listbox lower menu menubutton message \
4136 option pack place radiobutton raise \
4137 scale scrollbar selection send spinbox \
4138 text tk tkwait toplevel winfo wm
4139 if {[lsearch -exact $tkcmds $name] >= 0 && \
4140 [tkcon master tk_messageBox -icon question -parent . \
4141 -title "Load Tk?" -type retrycancel -default retry \
4142 -message "This appears to be a Tk command, but Tk\
4143 has not yet been loaded. Shall I retry the command\
4144 with loading Tk first?"] == "retry"} {
4145 return [uplevel 1 "load {} Tk; $args"]
4149 return -code continue
4152 } ; # end exclusionary code for WWW
4154 proc ::tkcon::Bindings {} {
4155 variable PRIV
4156 global tcl_platform tk_version
4158 #-----------------------------------------------------------------------
4159 # Elements of tkPriv that are used in this file:
4161 # char - Character position on the line; kept in order
4162 # to allow moving up or down past short lines while
4163 # still remembering the desired position.
4164 # mouseMoved - Non-zero means the mouse has moved a significant
4165 # amount since the button went down (so, for example,
4166 # start dragging out a selection).
4167 # prevPos - Used when moving up or down lines via the keyboard.
4168 # Keeps track of the previous insert position, so
4169 # we can distinguish a series of ups and downs, all
4170 # in a row, from a new up or down.
4171 # selectMode - The style of selection currently underway:
4172 # char, word, or line.
4173 # x, y - Last known mouse coordinates for scanning
4174 # and auto-scanning.
4175 #-----------------------------------------------------------------------
4177 switch -glob $tcl_platform(platform) {
4178 win* { set PRIV(meta) Alt }
4179 mac* { set PRIV(meta) Command }
4180 default { set PRIV(meta) Meta }
4183 ## Get all Text bindings into TkConsole
4184 foreach ev [bind Text] { bind TkConsole $ev [bind Text $ev] }
4185 ## We really didn't want the newline insertion
4186 bind TkConsole <Control-Key-o> {}
4188 ## in 8.6b3, the virtual events <<NextLine>> and <<PrevLine>>
4189 # mess up our history feature
4190 bind TkConsole <<NextLine>> {}
4191 bind TkConsole <<PrevLine>> {}
4193 ## Now make all our virtual event bindings
4194 foreach {ev key} [subst -nocommand -noback {
4195 <<TkCon_Exit>> <Control-q>
4196 <<TkCon_New>> <Control-N>
4197 <<TkCon_Close>> <Control-w>
4198 <<TkCon_About>> <Control-A>
4199 <<TkCon_Help>> <Control-H>
4200 <<TkCon_Find>> <Control-F>
4201 <<TkCon_Slave>> <Control-Key-1>
4202 <<TkCon_Master>> <Control-Key-2>
4203 <<TkCon_Main>> <Control-Key-3>
4204 <<TkCon_Expand>> <Key-Tab>
4205 <<TkCon_ExpandFile>> <Key-Escape>
4206 <<TkCon_ExpandProc>> <Control-P>
4207 <<TkCon_ExpandVar>> <Control-V>
4208 <<TkCon_Tab>> <Control-i>
4209 <<TkCon_Tab>> <$PRIV(meta)-i>
4210 <<TkCon_Newline>> <Control-o>
4211 <<TkCon_Newline>> <$PRIV(meta)-o>
4212 <<TkCon_Newline>> <Control-Key-Return>
4213 <<TkCon_Newline>> <Control-Key-KP_Enter>
4214 <<TkCon_Eval>> <Return>
4215 <<TkCon_Eval>> <KP_Enter>
4216 <<TkCon_Clear>> <Control-l>
4217 <<TkCon_Previous>> <Up>
4218 <<TkCon_PreviousImmediate>> <Control-p>
4219 <<TkCon_PreviousSearch>> <Control-r>
4220 <<TkCon_Next>> <Down>
4221 <<TkCon_NextImmediate>> <Control-n>
4222 <<TkCon_NextSearch>> <Control-s>
4223 <<TkCon_Transpose>> <Control-t>
4224 <<TkCon_ClearLine>> <Control-u>
4225 <<TkCon_SaveCommand>> <Control-z>
4226 <<TkCon_Popup>> <Button-3>
4227 }] {
4228 event add $ev $key
4229 ## Make sure the specific key won't be defined
4230 bind TkConsole $key {}
4233 ## Make the ROOT bindings
4234 bind $PRIV(root) <<TkCon_Exit>> exit
4235 bind $PRIV(root) <<TkCon_New>> { ::tkcon::New }
4236 bind $PRIV(root) <<TkCon_Close>> { ::tkcon::Destroy }
4237 bind $PRIV(root) <<TkCon_About>> { ::tkcon::About }
4238 bind $PRIV(root) <<TkCon_Help>> { ::tkcon::Help }
4239 bind $PRIV(root) <<TkCon_Find>> { ::tkcon::FindBox $::tkcon::PRIV(console) }
4240 bind $PRIV(root) <<TkCon_Slave>> {
4241 ::tkcon::Attach {}
4242 ::tkcon::Prompt "\n" [::tkcon::CmdGet $::tkcon::PRIV(console)]
4244 bind $PRIV(root) <<TkCon_Master>> {
4245 if {[string compare {} $::tkcon::PRIV(name)]} {
4246 ::tkcon::Attach $::tkcon::PRIV(name)
4247 } else {
4248 ::tkcon::Attach Main
4250 ::tkcon::Prompt "\n" [::tkcon::CmdGet $::tkcon::PRIV(console)]
4252 bind $PRIV(root) <<TkCon_Main>> {
4253 ::tkcon::Attach Main
4254 ::tkcon::Prompt "\n" [::tkcon::CmdGet $::tkcon::PRIV(console)]
4256 bind $PRIV(root) <<TkCon_Popup>> {
4257 ::tkcon::PopupMenu %X %Y
4260 ## Menu items need null TkConsolePost bindings to avoid the TagProc
4262 foreach ev [bind $PRIV(root)] {
4263 bind TkConsolePost $ev {
4264 # empty
4269 # ::tkcon::ClipboardKeysyms --
4270 # This procedure is invoked to identify the keys that correspond to
4271 # the copy, cut, and paste functions for the clipboard.
4273 # Arguments:
4274 # copy - Name of the key (keysym name plus modifiers, if any,
4275 # such as "Meta-y") used for the copy operation.
4276 # cut - Name of the key used for the cut operation.
4277 # paste - Name of the key used for the paste operation.
4279 proc ::tkcon::ClipboardKeysyms {copy cut paste} {
4280 bind TkConsole <$copy> {::tkcon::Copy %W}
4281 bind TkConsole <$cut> {::tkcon::Cut %W}
4282 bind TkConsole <$paste> {::tkcon::Paste %W}
4285 proc ::tkcon::GetSelection {w} {
4286 if {
4287 ![catch {selection get -displayof $w -type UTF8_STRING} txt] ||
4288 ![catch {selection get -displayof $w} txt] ||
4289 ![catch {selection get -displayof $w -selection CLIPBOARD} txt]
4291 return $txt
4293 return -code error "could not find default selection"
4296 proc ::tkcon::Cut w {
4297 if {[string match $w [selection own -displayof $w]]} {
4298 clipboard clear -displayof $w
4299 catch {
4300 set txt [selection get -displayof $w]
4301 clipboard append -displayof $w $txt
4302 if {[$w compare sel.first >= limit]} {
4303 $w delete sel.first sel.last
4308 proc ::tkcon::Copy w {
4309 if {[string match $w [selection own -displayof $w]]} {
4310 clipboard clear -displayof $w
4311 catch {
4312 set txt [selection get -displayof $w]
4313 clipboard append -displayof $w $txt
4317 proc ::tkcon::Paste w {
4318 if {![catch {GetSelection $w} txt]} {
4319 if {[$w compare insert < limit]} { $w mark set insert end }
4320 $w insert insert $txt
4321 $w see insert
4322 if {[string match *\n* $txt]} { ::tkcon::Eval $w }
4326 ## Redefine for TkConsole what we need
4328 event delete <<Paste>> <Control-V>
4329 ::tkcon::ClipboardKeysyms <Copy> <Cut> <Paste>
4331 bind TkConsole <Insert> {
4332 catch { ::tkcon::Insert %W [::tkcon::GetSelection %W] }
4335 bind TkConsole <Triple-1> {+
4336 catch {
4337 eval %W tag remove sel [%W tag nextrange prompt sel.first sel.last]
4338 eval %W tag remove sel sel.last-1c
4339 %W mark set insert sel.first
4343 ## binding editor needed
4344 ## binding <events> for .tkconrc
4346 bind TkConsole <<TkCon_ExpandFile>> {
4347 if {[%W compare insert > limit]} {::tkcon::Expand %W path}
4348 break
4350 bind TkConsole <<TkCon_ExpandProc>> {
4351 if {[%W compare insert > limit]} {::tkcon::Expand %W proc}
4353 bind TkConsole <<TkCon_ExpandVar>> {
4354 if {[%W compare insert > limit]} {::tkcon::Expand %W var}
4356 bind TkConsole <<TkCon_Expand>> {
4357 if {[%W compare insert > limit]} {::tkcon::Expand %W}
4359 bind TkConsole <<TkCon_Tab>> {
4360 if {[%W compare insert >= limit]} {
4361 ::tkcon::Insert %W \t
4364 bind TkConsole <<TkCon_Newline>> {
4365 if {[%W compare insert >= limit]} {
4366 ::tkcon::Insert %W \n
4369 bind TkConsole <<TkCon_Eval>> {
4370 ::tkcon::Eval %W
4372 bind TkConsole <Delete> {
4373 if {[llength [%W tag nextrange sel 1.0 end]] \
4374 && [%W compare sel.first >= limit]} {
4375 %W delete sel.first sel.last
4376 } elseif {[%W compare insert >= limit]} {
4377 %W delete insert
4378 %W see insert
4381 bind TkConsole <BackSpace> {
4382 if {[llength [%W tag nextrange sel 1.0 end]] \
4383 && [%W compare sel.first >= limit]} {
4384 %W delete sel.first sel.last
4385 } elseif {[%W compare insert != 1.0] && [%W compare insert > limit]} {
4386 %W delete insert-1c
4387 %W see insert
4390 bind TkConsole <Control-h> [bind TkConsole <BackSpace>]
4392 bind TkConsole <KeyPress> {
4393 ::tkcon::Insert %W %A
4396 bind TkConsole <Control-a> {
4397 if {[%W compare {limit linestart} == {insert linestart}]} {
4398 tkTextSetCursor %W limit
4399 } else {
4400 tkTextSetCursor %W {insert linestart}
4403 bind TkConsole <Key-Home> [bind TkConsole <Control-a>]
4404 bind TkConsole <Control-d> {
4405 if {[%W compare insert < limit]} break
4406 %W delete insert
4408 bind TkConsole <Control-k> {
4409 if {[%W compare insert < limit]} break
4410 if {[%W compare insert == {insert lineend}]} {
4411 %W delete insert
4412 } else {
4413 %W delete insert {insert lineend}
4416 bind TkConsole <<TkCon_Clear>> {
4417 ## Clear console buffer, without losing current command line input
4418 set ::tkcon::PRIV(tmp) [::tkcon::CmdGet %W]
4419 clear
4420 ::tkcon::Prompt {} $::tkcon::PRIV(tmp)
4422 bind TkConsole <<TkCon_Previous>> {
4423 if {[%W compare {insert linestart} != {limit linestart}]} {
4424 tkTextSetCursor %W [tkTextUpDownLine %W -1]
4425 } else {
4426 ::tkcon::Event -1
4429 bind TkConsole <<TkCon_Next>> {
4430 if {[%W compare {insert linestart} != {end-1c linestart}]} {
4431 tkTextSetCursor %W [tkTextUpDownLine %W 1]
4432 } else {
4433 ::tkcon::Event 1
4436 bind TkConsole <<TkCon_NextImmediate>> { ::tkcon::Event 1 }
4437 bind TkConsole <<TkCon_PreviousImmediate>> { ::tkcon::Event -1 }
4438 bind TkConsole <<TkCon_PreviousSearch>> {
4439 ::tkcon::Event -1 [::tkcon::CmdGet %W]
4441 bind TkConsole <<TkCon_NextSearch>> {
4442 ::tkcon::Event 1 [::tkcon::CmdGet %W]
4444 bind TkConsole <<TkCon_Transpose>> {
4445 ## Transpose current and previous chars
4446 if {[%W compare insert > "limit+1c"]} { tkTextTranspose %W }
4448 bind TkConsole <<TkCon_ClearLine>> {
4449 ## Clear command line (Unix shell staple)
4450 %W delete limit end
4452 bind TkConsole <<TkCon_SaveCommand>> {
4453 ## Save command buffer (swaps with current command)
4454 set ::tkcon::PRIV(tmp) $::tkcon::PRIV(cmdsave)
4455 set ::tkcon::PRIV(cmdsave) [::tkcon::CmdGet %W]
4456 if {[string match {} $::tkcon::PRIV(cmdsave)]} {
4457 set ::tkcon::PRIV(cmdsave) $::tkcon::PRIV(tmp)
4458 } else {
4459 %W delete limit end-1c
4461 ::tkcon::Insert %W $::tkcon::PRIV(tmp)
4462 %W see end
4464 catch {bind TkConsole <Key-Page_Up> { tkTextScrollPages %W -1 }}
4465 catch {bind TkConsole <Key-Prior> { tkTextScrollPages %W -1 }}
4466 catch {bind TkConsole <Key-Page_Down> { tkTextScrollPages %W 1 }}
4467 catch {bind TkConsole <Key-Next> { tkTextScrollPages %W 1 }}
4468 bind TkConsole <$PRIV(meta)-d> {
4469 if {[%W compare insert >= limit]} {
4470 %W delete insert {insert wordend}
4473 bind TkConsole <$PRIV(meta)-BackSpace> {
4474 if {[%W compare {insert -1c wordstart} >= limit]} {
4475 %W delete {insert -1c wordstart} insert
4478 bind TkConsole <$PRIV(meta)-Delete> {
4479 if {[%W compare insert >= limit]} {
4480 %W delete insert {insert wordend}
4483 bind TkConsole <ButtonRelease-2> {
4484 if {
4485 (!$tkPriv(mouseMoved) || $tk_strictMotif) &&
4486 ![catch {::tkcon::GetSelection %W} ::tkcon::PRIV(tmp)]
4488 if {[%W compare @%x,%y < limit]} {
4489 %W insert end $::tkcon::PRIV(tmp)
4490 } else {
4491 %W insert @%x,%y $::tkcon::PRIV(tmp)
4493 if {[string match *\n* $::tkcon::PRIV(tmp)]} {::tkcon::Eval %W}
4498 ## End TkConsole bindings
4502 ## Bindings for doing special things based on certain keys
4504 bind TkConsolePost <Key-parenright> {
4505 if {$::tkcon::OPT(lightbrace) && $::tkcon::OPT(blinktime)>99 && \
4506 [string compare \\ [%W get insert-2c]]} {
4507 ::tkcon::MatchPair %W \( \) limit
4509 set ::tkcon::PRIV(StatusCursor) [%W index insert]
4511 bind TkConsolePost <Key-bracketright> {
4512 if {$::tkcon::OPT(lightbrace) && $::tkcon::OPT(blinktime)>99 && \
4513 [string compare \\ [%W get insert-2c]]} {
4514 ::tkcon::MatchPair %W \[ \] limit
4516 set ::tkcon::PRIV(StatusCursor) [%W index insert]
4518 bind TkConsolePost <Key-braceright> {
4519 if {$::tkcon::OPT(lightbrace) && $::tkcon::OPT(blinktime)>99 && \
4520 [string compare \\ [%W get insert-2c]]} {
4521 ::tkcon::MatchPair %W \{ \} limit
4523 set ::tkcon::PRIV(StatusCursor) [%W index insert]
4525 bind TkConsolePost <Key-quotedbl> {
4526 if {$::tkcon::OPT(lightbrace) && $::tkcon::OPT(blinktime)>99 && \
4527 [string compare \\ [%W get insert-2c]]} {
4528 ::tkcon::MatchQuote %W limit
4530 set ::tkcon::PRIV(StatusCursor) [%W index insert]
4533 bind TkConsolePost <KeyPress> {
4534 if {$::tkcon::OPT(lightcmd) && [string compare {} %A]} {
4535 ::tkcon::TagProc %W
4537 set ::tkcon::PRIV(StatusCursor) [%W index insert]
4540 bind TkConsolePost <Button-1> {
4541 set ::tkcon::PRIV(StatusCursor) [%W index insert]
4543 bind TkConsolePost <B1-Motion> {
4544 set ::tkcon::PRIV(StatusCursor) [%W index insert]
4550 # ::tkcon::PopupMenu - what to do when the popup menu is requested
4552 proc ::tkcon::PopupMenu {X Y} {
4553 variable PRIV
4555 set w $PRIV(console)
4556 if {[string compare $w [winfo containing $X $Y]]} {
4557 tk_popup $PRIV(popup) $X $Y
4558 return
4560 set x [expr {$X-[winfo rootx $w]}]
4561 set y [expr {$Y-[winfo rooty $w]}]
4562 if {[llength [set tags [$w tag names @$x,$y]]]} {
4563 if {[lsearch -exact $tags "proc"] >= 0} {
4564 lappend type "proc"
4565 foreach {first last} [$w tag prevrange proc @$x,$y] {
4566 set word [$w get $first $last]; break
4569 if {[lsearch -exact $tags "var"] >= 0} {
4570 lappend type "var"
4571 foreach {first last} [$w tag prevrange var @$x,$y] {
4572 set word [$w get $first $last]; break
4576 if {![info exists type]} {
4577 set exp "(^|\[^\\\\\]\[ \t\n\r\])"
4578 set exp2 "\[\[\\\\\\?\\*\]"
4579 set i [$w search -backwards -regexp $exp @$x,$y "@$x,$y linestart"]
4580 if {[string compare {} $i]} {
4581 if {![string match *.0 $i]} {append i +2c}
4582 if {[string compare {} \
4583 [set j [$w search -regexp $exp $i "$i lineend"]]]} {
4584 append j +1c
4585 } else {
4586 set j "$i lineend"
4588 regsub -all $exp2 [$w get $i $j] {\\\0} word
4589 set word [string trim $word {\"$[]{}',?#*}]
4590 if {[llength [EvalAttached [list info commands $word]]]} {
4591 lappend type "proc"
4593 if {[llength [EvalAttached [list info vars $word]]]} {
4594 lappend type "var"
4596 if {[EvalAttached [list file isfile $word]]} {
4597 lappend type "file"
4601 if {![info exists type] || ![info exists word]} {
4602 tk_popup $PRIV(popup) $X $Y
4603 return
4605 $PRIV(context) delete 0 end
4606 $PRIV(context) add command -label "$word" -state disabled
4607 $PRIV(context) add separator
4608 set app [Attach]
4609 if {[lsearch $type proc] != -1} {
4610 $PRIV(context) add command -label "View Procedure" \
4611 -command [list edit -attach $app -type proc -- $word]
4613 if {[lsearch $type var] != -1} {
4614 $PRIV(context) add command -label "View Variable" \
4615 -command [list edit -attach $app -type var -- $word]
4617 if {[lsearch $type file] != -1} {
4618 $PRIV(context) add command -label "View File" \
4619 -command [list edit -attach $app -type file -- $word]
4621 tk_popup $PRIV(context) $X $Y
4624 ## ::tkcon::TagProc - tags a procedure in the console if it's recognized
4625 ## This procedure is not perfect. However, making it perfect wastes
4626 ## too much CPU time...
4628 proc ::tkcon::TagProc w {
4629 set exp "\[^\\\\\]\[\[ \t\n\r\;{}\"\$\]"
4630 set i [$w search -backwards -regexp $exp insert-1c limit-1c]
4631 if {[string compare {} $i]} {append i +2c} else {set i limit}
4632 regsub -all "\[\[\\\\\\?\\*\]" [$w get $i "insert-1c wordend"] {\\\0} c
4633 if {[llength [EvalAttached [list info commands $c]]]} {
4634 $w tag add proc $i "insert-1c wordend"
4635 } else {
4636 $w tag remove proc $i "insert-1c wordend"
4638 if {[llength [EvalAttached [list info vars $c]]]} {
4639 $w tag add var $i "insert-1c wordend"
4640 } else {
4641 $w tag remove var $i "insert-1c wordend"
4645 ## ::tkcon::MatchPair - blinks a matching pair of characters
4646 ## c2 is assumed to be at the text index 'insert'.
4647 ## This proc is really loopy and took me an hour to figure out given
4648 ## all possible combinations with escaping except for escaped \'s.
4649 ## It doesn't take into account possible commenting... Oh well. If
4650 ## anyone has something better, I'd like to see/use it. This is really
4651 ## only efficient for small contexts.
4652 # ARGS: w - console text widget
4653 # c1 - first char of pair
4654 # c2 - second char of pair
4655 # Calls: ::tkcon::Blink
4657 proc ::tkcon::MatchPair {w c1 c2 {lim 1.0}} {
4658 if {[string compare {} [set ix [$w search -back $c1 insert $lim]]]} {
4659 while {
4660 [string match {\\} [$w get $ix-1c]] &&
4661 [string compare {} [set ix [$w search -back $c1 $ix-1c $lim]]]
4662 } {}
4663 set i1 insert-1c
4664 while {[string compare {} $ix]} {
4665 set i0 $ix
4666 set j 0
4667 while {[string compare {} [set i0 [$w search $c2 $i0 $i1]]]} {
4668 append i0 +1c
4669 if {[string match {\\} [$w get $i0-2c]]} continue
4670 incr j
4672 if {!$j} break
4673 set i1 $ix
4674 while {$j && [string compare {} \
4675 [set ix [$w search -back $c1 $ix $lim]]]} {
4676 if {[string match {\\} [$w get $ix-1c]]} continue
4677 incr j -1
4680 if {[string match {} $ix]} { set ix [$w index $lim] }
4681 } else { set ix [$w index $lim] }
4682 if {$::tkcon::OPT(blinkrange)} {
4683 Blink $w $ix [$w index insert]
4684 } else {
4685 Blink $w $ix $ix+1c [$w index insert-1c] [$w index insert]
4689 ## ::tkcon::MatchQuote - blinks between matching quotes.
4690 ## Blinks just the quote if it's unmatched, otherwise blinks quoted string
4691 ## The quote to match is assumed to be at the text index 'insert'.
4692 # ARGS: w - console text widget
4693 # Calls: ::tkcon::Blink
4695 proc ::tkcon::MatchQuote {w {lim 1.0}} {
4696 set i insert-1c
4697 set j 0
4698 while {[string compare [set i [$w search -back \" $i $lim]] {}]} {
4699 if {[string match {\\} [$w get $i-1c]]} continue
4700 if {!$j} {set i0 $i}
4701 incr j
4703 if {$j&1} {
4704 if {$::tkcon::OPT(blinkrange)} {
4705 Blink $w $i0 [$w index insert]
4706 } else {
4707 Blink $w $i0 $i0+1c [$w index insert-1c] [$w index insert]
4709 } else {
4710 Blink $w [$w index insert-1c] [$w index insert]
4714 ## ::tkcon::Blink - blinks between n index pairs for a specified duration.
4715 # ARGS: w - console text widget
4716 # i1 - start index to blink region
4717 # i2 - end index of blink region
4718 # dur - duration in usecs to blink for
4719 # Outputs: blinks selected characters in $w
4721 proc ::tkcon::Blink {w args} {
4722 eval [list $w tag add blink] $args
4723 after $::tkcon::OPT(blinktime) [list $w] tag remove blink $args
4724 return
4728 ## ::tkcon::Insert
4729 ## Insert a string into a text console at the point of the insertion cursor.
4730 ## If there is a selection in the text, and it covers the point of the
4731 ## insertion cursor, then delete the selection before inserting.
4732 # ARGS: w - text window in which to insert the string
4733 # s - string to insert (usually just a single char)
4734 # Outputs: $s to text widget
4736 proc ::tkcon::Insert {w s} {
4737 if {[string match {} $s] || [string match disabled [$w cget -state]]} {
4738 return
4740 if {[$w comp insert < limit]} {
4741 $w mark set insert end
4743 if {[llength [$w tag ranges sel]] && \
4744 [$w comp sel.first <= insert] && [$w comp sel.last >= insert]} {
4745 $w delete sel.first sel.last
4747 $w insert insert $s
4748 $w see insert
4751 ## ::tkcon::Expand -
4752 # ARGS: w - text widget in which to expand str
4753 # type - type of expansion (path / proc / variable)
4754 # Calls: ::tkcon::Expand(Pathname|Procname|Variable)
4755 # Outputs: The string to match is expanded to the longest possible match.
4756 # If ::tkcon::OPT(showmultiple) is non-zero and the user longest
4757 # match equaled the string to expand, then all possible matches
4758 # are output to stdout. Triggers bell if no matches are found.
4759 # Returns: number of matches found
4761 proc ::tkcon::Expand {w {type ""}} {
4762 set exp "\[^\\\\\]\[\[ \t\n\r\\\{\"$\]"
4763 set tmp [$w search -backwards -regexp $exp insert-1c limit-1c]
4764 if {[string compare {} $tmp]} {append tmp +2c} else {set tmp limit}
4765 if {[$w compare $tmp >= insert]} return
4766 set str [$w get $tmp insert]
4767 switch -glob $type {
4768 pa* { set res [ExpandPathname $str] }
4769 pr* { set res [ExpandProcname $str] }
4770 v* { set res [ExpandVariable $str] }
4771 default {
4772 set res {}
4773 foreach t $::tkcon::OPT(expandorder) {
4774 if {![catch {Expand$t $str} res] && \
4775 [string compare {} $res]} break
4779 set len [llength $res]
4780 if {$len} {
4781 $w delete $tmp insert
4782 $w insert $tmp [lindex $res 0]
4783 if {$len > 1} {
4784 if {$::tkcon::OPT(showmultiple) && \
4785 ![string compare [lindex $res 0] $str]} {
4786 puts stdout [lsort [lreplace $res 0 0]]
4789 } else { bell }
4790 return [incr len -1]
4793 ## ::tkcon::ExpandPathname - expand a file pathname based on $str
4794 ## This is based on UNIX file name conventions
4795 # ARGS: str - partial file pathname to expand
4796 # Calls: ::tkcon::ExpandBestMatch
4797 # Returns: list containing longest unique match followed by all the
4798 # possible further matches
4800 proc ::tkcon::ExpandPathname str {
4801 set pwd [EvalAttached pwd]
4802 # Cause a string like {C:/Program\ Files/} to become "C:/Program Files/"
4803 regsub -all {\\([][ ])} $str {\1} str
4804 if {[catch {EvalAttached [list cd [file dirname $str]]} err]} {
4805 return -code error $err
4807 set dir [file tail $str]
4808 ## Check to see if it was known to be a directory and keep the trailing
4809 ## slash if so (file tail cuts it off)
4810 if {[string match */ $str]} { append dir / }
4811 # Create a safely glob-able name
4812 regsub -all {([][])} $dir {\\\1} safedir
4813 if {[catch {lsort [EvalAttached [list glob $safedir*]]} m]} {
4814 set match {}
4815 } else {
4816 if {[llength $m] > 1} {
4817 global tcl_platform
4818 if {[string match windows $tcl_platform(platform)]} {
4819 ## Windows is screwy because it's case insensitive
4820 set tmp [ExpandBestMatch [string tolower $m] \
4821 [string tolower $dir]]
4822 ## Don't change case if we haven't changed the word
4823 if {[string length $dir]==[string length $tmp]} {
4824 set tmp $dir
4826 } else {
4827 set tmp [ExpandBestMatch $m $dir]
4829 if {[string match */* $str]} {
4830 set tmp [string trimright [file dirname $str] /]/$tmp
4832 regsub -all {([^\\])([][ ])} $tmp {\1\\\2} tmp
4833 set match [linsert $m 0 $tmp]
4834 } else {
4835 ## This may look goofy, but it handles spaces in path names
4836 eval append match $m
4837 if {[file isdirectory $match]} {append match /}
4838 if {[string match */* $str]} {
4839 set match [string trimright [file dirname $str] /]/$match
4841 regsub -all {([^\\])([][ ])} $match {\1\\\2} match
4842 ## Why is this one needed and the ones below aren't!!
4843 set match [list $match]
4846 EvalAttached [list cd $pwd]
4847 return $match
4850 ## ::tkcon::ExpandProcname - expand a tcl proc name based on $str
4851 # ARGS: str - partial proc name to expand
4852 # Calls: ::tkcon::ExpandBestMatch
4853 # Returns: list containing longest unique match followed by all the
4854 # possible further matches
4856 proc ::tkcon::ExpandProcname str {
4857 set match [EvalAttached [list info commands $str*]]
4858 if {[llength $match] == 0} {
4859 set ns [EvalAttached \
4860 "namespace children \[namespace current\] [list $str*]"]
4861 if {[llength $ns]==1} {
4862 set match [EvalAttached [list info commands ${ns}::*]]
4863 } else {
4864 set match $ns
4867 if {[llength $match] > 1} {
4868 regsub -all {([^\\]) } [ExpandBestMatch $match $str] {\1\\ } str
4869 set match [linsert $match 0 $str]
4870 } else {
4871 regsub -all {([^\\]) } $match {\1\\ } match
4873 return $match
4876 ## ::tkcon::ExpandVariable - expand a tcl variable name based on $str
4877 # ARGS: str - partial tcl var name to expand
4878 # Calls: ::tkcon::ExpandBestMatch
4879 # Returns: list containing longest unique match followed by all the
4880 # possible further matches
4882 proc ::tkcon::ExpandVariable str {
4883 if {[regexp {([^\(]*)\((.*)} $str junk ary str]} {
4884 ## Looks like they're trying to expand an array.
4885 set match [EvalAttached [list array names $ary $str*]]
4886 if {[llength $match] > 1} {
4887 set vars $ary\([ExpandBestMatch $match $str]
4888 foreach var $match {lappend vars $ary\($var\)}
4889 return $vars
4890 } else {set match $ary\($match\)}
4891 ## Space transformation avoided for array names.
4892 } else {
4893 set match [EvalAttached [list info vars $str*]]
4894 if {[llength $match] > 1} {
4895 regsub -all {([^\\]) } [ExpandBestMatch $match $str] {\1\\ } str
4896 set match [linsert $match 0 $str]
4897 } else {
4898 regsub -all {([^\\]) } $match {\1\\ } match
4901 return $match
4904 ## ::tkcon::ExpandBestMatch2 - finds the best unique match in a list of names
4905 ## Improves upon the speed of the below proc only when $l is small
4906 ## or $e is {}. $e is extra for compatibility with proc below.
4907 # ARGS: l - list to find best unique match in
4908 # Returns: longest unique match in the list
4910 proc ::tkcon::ExpandBestMatch2 {l {e {}}} {
4911 set s [lindex $l 0]
4912 if {[llength $l]>1} {
4913 set i [expr {[string length $s]-1}]
4914 foreach l $l {
4915 while {$i>=0 && [string first $s $l]} {
4916 set s [string range $s 0 [incr i -1]]
4920 return $s
4923 ## ::tkcon::ExpandBestMatch - finds the best unique match in a list of names
4924 ## The extra $e in this argument allows us to limit the innermost loop a
4925 ## little further. This improves speed as $l becomes large or $e becomes long.
4926 # ARGS: l - list to find best unique match in
4927 # e - currently best known unique match
4928 # Returns: longest unique match in the list
4930 proc ::tkcon::ExpandBestMatch {l {e {}}} {
4931 set ec [lindex $l 0]
4932 if {[llength $l]>1} {
4933 set e [string length $e]; incr e -1
4934 set ei [string length $ec]; incr ei -1
4935 foreach l $l {
4936 while {$ei>=$e && [string first $ec $l]} {
4937 set ec [string range $ec 0 [incr ei -1]]
4941 return $ec
4944 # Here is a group of functions that is only used when Tkcon is
4945 # executed in a safe interpreter. It provides safe versions of
4946 # missing functions. For example:
4948 # - "tk appname" returns "tkcon.tcl" but cannot be set
4949 # - "toplevel" is equivalent to 'frame', only it is automatically
4950 # packed.
4951 # - The 'source', 'load', 'open', 'file' and 'exit' functions are
4952 # mapped to corresponding functions in the parent interpreter.
4954 # Further on, Tk cannot be really loaded. Still the safe 'load'
4955 # provedes a speciall case. The Tk can be divided into 4 groups,
4956 # that each has a safe handling procedure.
4958 # - "::tkcon::SafeItem" handles commands like 'button', 'canvas' ......
4959 # Each of these functions has the window name as first argument.
4960 # - "::tkcon::SafeManage" handles commands like 'pack', 'place', 'grid',
4961 # 'winfo', which can have multiple window names as arguments.
4962 # - "::tkcon::SafeWindow" handles all windows, such as '.'. For every
4963 # window created, a new alias is formed which also is handled by
4964 # this function.
4965 # - Other (e.g. bind, bindtag, image), which need their own function.
4967 ## These functions courtesy Jan Nijtmans (nijtmans@nici.kun.nl)
4969 if {[string compare [info command tk] tk]} {
4970 proc tk {option args} {
4971 if {![string match app* $option]} {
4972 error "wrong option \"$option\": should be appname"
4974 return "tkcon.tcl"
4978 if {[string compare [info command toplevel] toplevel]} {
4979 proc toplevel {name args} {
4980 eval frame $name $args
4981 pack $name
4985 proc ::tkcon::SafeSource {i f} {
4986 set fd [open $f r]
4987 set r [read $fd]
4988 close $fd
4989 if {[catch {interp eval $i $r} msg]} {
4990 error $msg
4994 proc ::tkcon::SafeOpen {i f {m r}} {
4995 set fd [open $f $m]
4996 interp transfer {} $fd $i
4997 return $fd
5000 proc ::tkcon::SafeLoad {i f p} {
5001 global tk_version tk_patchLevel tk_library auto_path
5002 if {[string compare $p Tk]} {
5003 load $f $p $i
5004 } else {
5005 foreach command {button canvas checkbutton entry frame label
5006 listbox message radiobutton scale scrollbar spinbox text toplevel} {
5007 $i alias $command ::tkcon::SafeItem $i $command
5009 $i alias image ::tkcon::SafeImage $i
5010 foreach command {pack place grid destroy winfo} {
5011 $i alias $command ::tkcon::SafeManage $i $command
5013 if {[llength [info command event]]} {
5014 $i alias event ::tkcon::SafeManage $i $command
5016 frame .${i}_dot -width 300 -height 300 -relief raised
5017 pack .${i}_dot -side left
5018 $i alias tk tk
5019 $i alias bind ::tkcon::SafeBind $i
5020 $i alias bindtags ::tkcon::SafeBindtags $i
5021 $i alias . ::tkcon::SafeWindow $i {}
5022 foreach var {tk_version tk_patchLevel tk_library auto_path} {
5023 $i eval set $var [list [set $var]]
5025 $i eval {
5026 package provide Tk $tk_version
5027 if {[lsearch -exact $auto_path $tk_library] < 0} {
5028 lappend auto_path $tk_library
5031 return ""
5035 proc ::tkcon::SafeSubst {i a} {
5036 set arg1 ""
5037 foreach {arg value} $a {
5038 if {![string compare $arg -textvariable] ||
5039 ![string compare $arg -variable]} {
5040 set newvalue "[list $i] $value"
5041 global $newvalue
5042 if {[interp eval $i info exists $value]} {
5043 set $newvalue [interp eval $i set $value]
5044 } else {
5045 catch {unset $newvalue}
5047 $i eval trace variable $value rwu \{[list tkcon set $newvalue $i]\}
5048 set value $newvalue
5049 } elseif {![string compare $arg -command]} {
5050 set value [list $i eval $value]
5052 lappend arg1 $arg $value
5054 return $arg1
5057 proc ::tkcon::SafeItem {i command w args} {
5058 set args [::tkcon::SafeSubst $i $args]
5059 set code [catch "$command [list .${i}_dot$w] $args" msg]
5060 $i alias $w ::tkcon::SafeWindow $i $w
5061 regsub -all .${i}_dot $msg {} msg
5062 return -code $code $msg
5065 proc ::tkcon::SafeManage {i command args} {
5066 set args1 ""
5067 foreach arg $args {
5068 if {[string match . $arg]} {
5069 set arg .${i}_dot
5070 } elseif {[string match .* $arg]} {
5071 set arg ".${i}_dot$arg"
5073 lappend args1 $arg
5075 set code [catch "$command $args1" msg]
5076 regsub -all .${i}_dot $msg {} msg
5077 return -code $code $msg
5081 # FIX: this function doesn't work yet if the binding starts with '+'.
5083 proc ::tkcon::SafeBind {i w args} {
5084 if {[string match . $w]} {
5085 set w .${i}_dot
5086 } elseif {[string match .* $w]} {
5087 set w ".${i}_dot$w"
5089 if {[llength $args] > 1} {
5090 set args [list [lindex $args 0] \
5091 "[list $i] eval [list [lindex $args 1]]"]
5093 set code [catch "bind $w $args" msg]
5094 if {[llength $args] <2 && $code == 0} {
5095 set msg [lindex $msg 3]
5097 return -code $code $msg
5100 proc ::tkcon::SafeImage {i option args} {
5101 set code [catch "image $option $args" msg]
5102 if {[string match cr* $option]} {
5103 $i alias $msg $msg
5105 return -code $code $msg
5108 proc ::tkcon::SafeBindtags {i w {tags {}}} {
5109 if {[string match . $w]} {
5110 set w .${i}_dot
5111 } elseif {[string match .* $w]} {
5112 set w ".${i}_dot$w"
5114 set newtags {}
5115 foreach tag $tags {
5116 if {[string match . $tag]} {
5117 lappend newtags .${i}_dot
5118 } elseif {[string match .* $tag]} {
5119 lappend newtags ".${i}_dot$tag"
5120 } else {
5121 lappend newtags $tag
5124 if {[string match $tags {}]} {
5125 set code [catch {bindtags $w} msg]
5126 regsub -all \\.${i}_dot $msg {} msg
5127 } else {
5128 set code [catch {bindtags $w $newtags} msg]
5130 return -code $code $msg
5133 proc ::tkcon::SafeWindow {i w option args} {
5134 if {[string match conf* $option] && [llength $args] > 1} {
5135 set args [::tkcon::SafeSubst $i $args]
5136 } elseif {[string match itemco* $option] && [llength $args] > 2} {
5137 set args "[list [lindex $args 0]] [::tkcon::SafeSubst $i [lrange $args 1 end]]"
5138 } elseif {[string match cr* $option]} {
5139 if {[llength $args]%2} {
5140 set args "[list [lindex $args 0]] [::tkcon::SafeSubst $i [lrange $args 1 end]]"
5141 } else {
5142 set args [::tkcon::SafeSubst $i $args]
5144 } elseif {[string match bi* $option] && [llength $args] > 2} {
5145 set args [list [lindex $args 0] [lindex $args 1] "[list $i] eval [list [lindex $args 2]]"]
5147 set code [catch ".${i}_dot$w $option $args" msg]
5148 if {$code} {
5149 regsub -all .${i}_dot $msg {} msg
5150 } elseif {[string match conf* $option] || [string match itemco* $option]} {
5151 if {[llength $args] == 1} {
5152 switch -- $args {
5153 -textvariable - -variable {
5154 set msg "[lrange $msg 0 3] [list [lrange [lindex $msg 4] 1 end]]"
5156 -command - updatecommand {
5157 set msg "[lrange $msg 0 3] [list [lindex [lindex $msg 4] 2]]"
5160 } elseif {[llength $args] == 0} {
5161 set args1 ""
5162 foreach el $msg {
5163 switch -- [lindex $el 0] {
5164 -textvariable - -variable {
5165 set el "[lrange $el 0 3] [list [lrange [lindex $el 4] 1 end]]"
5167 -command - updatecommand {
5168 set el "[lrange $el 0 3] [list [lindex [lindex $el 4] 2]]"
5171 lappend args1 $el
5173 set msg $args1
5175 } elseif {[string match cg* $option] || [string match itemcg* $option]} {
5176 switch -- $args {
5177 -textvariable - -variable {
5178 set msg [lrange $msg 1 end]
5180 -command - updatecommand {
5181 set msg [lindex $msg 2]
5184 } elseif {[string match bi* $option]} {
5185 if {[llength $args] == 2 && $code == 0} {
5186 set msg [lindex $msg 2]
5189 return -code $code $msg
5192 proc ::tkcon::RetrieveFilter {host} {
5193 variable PRIV
5194 set result {}
5195 if {[info exists PRIV(proxy)]} {
5196 if {![regexp "^(localhost|127\.0\.0\.1)" $host]} {
5197 set result [lrange [split [lindex $PRIV(proxy) 0] :] 0 1]
5200 return $result
5203 proc ::tkcon::RetrieveAuthentication {} {
5204 package require Tk
5205 if {[catch {package require base64}]} {
5206 if {[catch {package require Trf}]} {
5207 error "base64 support not available"
5208 } else {
5209 set local64 "base64 -mode enc"
5211 } else {
5212 set local64 "base64::encode"
5215 set dlg [toplevel .auth]
5216 wm title $dlg "Authenticating Proxy Configuration"
5217 set f1 [frame ${dlg}.f1]
5218 set f2 [frame ${dlg}.f2]
5219 button $f2.b -text "OK" -command "destroy $dlg"
5220 pack $f2.b -side right
5221 label $f1.l2 -text "Username"
5222 label $f1.l3 -text "Password"
5223 entry $f1.e2 -textvariable "[namespace current]::conf_userid"
5224 entry $f1.e3 -textvariable "[namespace current]::conf_passwd" -show *
5225 grid $f1.l2 -column 0 -row 0 -sticky e
5226 grid $f1.l3 -column 0 -row 1 -sticky e
5227 grid $f1.e2 -column 1 -row 0 -sticky news
5228 grid $f1.e3 -column 1 -row 1 -sticky news
5229 grid columnconfigure $f1 1 -weight 1
5230 pack $f2 -side bottom -fill x
5231 pack $f1 -side top -anchor n -fill both -expand 1
5232 tkwait window $dlg
5233 set result {}
5234 if {[info exists [namespace current]::conf_userid]} {
5235 set data [subst $[namespace current]::conf_userid]
5236 append data : [subst $[namespace current]::conf_passwd]
5237 set data [$local64 $data]
5238 set result [list "Proxy-Authorization" "Basic $data"]
5240 unset [namespace current]::conf_passwd
5241 return $result
5244 proc ::tkcon::Retrieve {} {
5245 # A little bit'o'magic to grab the latest tkcon from CVS and
5246 # save it locally. It doesn't support proxies though...
5247 variable PRIV
5249 set defExt ""
5250 if {[string match "windows" $::tcl_platform(platform)]} {
5251 set defExt ".tcl"
5253 set file [tk_getSaveFile -title "Save Latest tkcon to ..." \
5254 -defaultextension $defExt \
5255 -initialdir [file dirname $PRIV(SCRIPT)] \
5256 -initialfile [file tail $PRIV(SCRIPT)] \
5257 -parent $PRIV(root) \
5258 -filetypes {{"Tcl Files" {.tcl .tk}} {"All Files" {*.*}}}]
5259 if {[string compare $file ""]} {
5260 package require http 2
5261 set token [::http::geturl $PRIV(HEADURL) -timeout 30000]
5262 ::http::wait $token
5263 set code [catch {
5264 if {[::http::status $token] == "ok"} {
5265 set fid [open $file w]
5266 # We don't want newline mode to change
5267 fconfigure $fid -translation binary
5268 set data [::http::data $token]
5269 puts -nonewline $fid $data
5270 close $fid
5271 regexp {Id: tkcon.tcl,v (\d+\.\d+)} $data -> rcsVersion
5272 regexp {version\s+(\d+\.\d[^\n]*)} $data -> tkconVersion
5274 } err]
5275 ::http::cleanup $token
5276 if {$code} {
5277 return -code error $err
5278 } elseif {[tk_messageBox -type yesno -icon info -parent $PRIV(root) \
5279 -title "Retrieved tkcon v$tkconVersion, RCS $rcsVersion" \
5280 -message "Successfully retrieved tkcon v$tkconVersion,\
5281 RCS $rcsVersion. Shall I resource (not restart) this\
5282 version now?"] == "yes"} {
5283 set PRIV(SCRIPT) $file
5284 set PRIV(version) $tkconVersion.$rcsVersion
5285 ::tkcon::Resource
5290 ## ::tkcon::Resource - re'source's this script into current console
5291 ## Meant primarily for my development of this program. It follows
5292 ## links until the ultimate source is found.
5294 set ::tkcon::PRIV(SCRIPT) [info script]
5295 if {!$::tkcon::PRIV(WWW) && [string compare $::tkcon::PRIV(SCRIPT) {}]} {
5296 # we use a catch here because some wrap apps choke on 'file type'
5297 # because TclpLstat wasn't wrappable until 8.4.
5298 catch {
5299 while {[string match link [file type $::tkcon::PRIV(SCRIPT)]]} {
5300 set link [file readlink $::tkcon::PRIV(SCRIPT)]
5301 if {[string match relative [file pathtype $link]]} {
5302 set ::tkcon::PRIV(SCRIPT) \
5303 [file join [file dirname $::tkcon::PRIV(SCRIPT)] $link]
5304 } else {
5305 set ::tkcon::PRIV(SCRIPT) $link
5308 catch {unset link}
5309 if {[string match relative [file pathtype $::tkcon::PRIV(SCRIPT)]]} {
5310 set ::tkcon::PRIV(SCRIPT) [file join [pwd] $::tkcon::PRIV(SCRIPT)]
5315 proc ::tkcon::Resource {} {
5316 uplevel \#0 {
5317 if {[catch {source -rsrc tkcon}]} { source $::tkcon::PRIV(SCRIPT) }
5319 Bindings
5320 InitSlave $::tkcon::OPT(exec)
5323 ## Initialize only if we haven't yet
5325 if {![info exists ::tkcon::PRIV(root)] || \
5326 ![winfo exists $::tkcon::PRIV(root)]} {
5327 ::tkcon::Init