1#!/bin/sh
2# \
3exec ${IRSIM_WISH:=wish} "$0" ${1+"$@"}
4
5#
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")
11##
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>
15##
16## Copyright 1995-2001 Jeffrey Hobbs
17## Initiated: Thu Aug 17 15:36:47 PDT 1995
18##
19## jeff.hobbs@acm.org, jeff@hobbs.org
20##
21## source standard_disclaimer.tcl
22## source bourbon_ware.tcl
23##
24
25# Proxy support for retrieving the current version of Tkcon.
26#
27# Mon Jun 25 12:19:56 2001 - Pat Thoyts <Pat.Thoyts@bigfoot.com>
28#
29# In your tkcon.cfg or .tkconrc file put your proxy details into the
30# `proxy' member of the `PRIV' array. e.g.:
31#
32#    set ::tkcon::PRIV(proxy) wwwproxy:8080
33#
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:
36#
37#    set ::tkcon::PRIV(proxy) {wwwproxy:8080 1}
38#
39# Or you can set the above variable from within tkcon by calling
40#
41#    tkcon master set ::tkcon:PRIV(proxy) wwwproxy:8080
42#
43
44if {$tcl_version < 8.0} {
45    return -code error "tkcon requires at least Tcl/Tk8"
46} else {
47    # package require -exact Tk $tcl_version
48    package require Tk $tcl_version
49}
50
51catch {package require bogus-package-name}
52foreach pkg [info loaded {}] {
53    set file [lindex $pkg 0]
54    set name [lindex $pkg 1]
55    if {![catch {set version [package require $name]}]} {
56	if {[string match {} [package ifneeded $name $version]]} {
57	    package ifneeded $name $version [list load $file $name]
58	}
59    }
60}
61catch {unset pkg file name version}
62
63# Tk 8.4 makes previously exposed stuff private.
64# FIX: Update tkcon to not rely on the private Tk code.
65#
66if {![llength [info globals tkPriv]]} {
67    ::tk::unsupported::ExposePrivateVariable tkPriv
68}
69foreach cmd {SetCursor UpDownLine Transpose ScrollPages} {
70    if {![llength [info commands tkText$cmd]]} {
71        ::tk::unsupported::ExposePrivateCommand tkText$cmd
72    }
73}
74
75# Initialize the ::tkcon namespace
76#
77namespace eval ::tkcon {
78    # The OPT variable is an array containing most of the optional
79    # info to configure.  COLOR has the color data.
80    variable OPT
81    variable COLOR
82
83    # PRIV is used for internal data that only tkcon should fiddle with.
84    variable PRIV
85    set PRIV(WWW) [info exists embed_args]
86}
87
88## ::tkcon::Init - inits tkcon
89#
90# Calls:	::tkcon::InitUI
91# Outputs:	errors found in tkcon's resource file
92##
93proc ::tkcon::Init {} {
94    variable OPT
95    variable COLOR
96    variable PRIV
97    global tcl_platform env argc argv tcl_interactive errorInfo
98
99    if {![info exists argv]} {
100	set argv {}
101	set argc 0
102    }
103
104    set tcl_interactive 1
105
106    if {[info exists PRIV(name)]} {
107	set title $PRIV(name)
108    } else {
109	MainInit
110	# some main initialization occurs later in this proc,
111	# to go after the UI init
112	set MainInit 1
113	set title Main
114    }
115
116    ##
117    ## When setting up all the default values, we always check for
118    ## prior existence.  This allows users who embed tkcon to modify
119    ## the initial state before tkcon initializes itself.
120    ##
121
122    # bg == {} will get bg color from the main toplevel (in InitUI)
123    foreach {key default} {
124	bg		{}
125	blink		\#FFFF00
126	cursor		\#000000
127	disabled	\#4D4D4D
128	proc		\#008800
129	var		\#FFC0D0
130	prompt		\#8F4433
131	stdin		\#000000
132	stdout		\#0000FF
133	stderr		\#FF0000
134    } {
135	if {![info exists COLOR($key)]} { set COLOR($key) $default }
136    }
137
138    foreach {key default} {
139	autoload	{}
140	blinktime	500
141	blinkrange	1
142	buffer		512
143	calcmode	0
144	cols		80
145	debugPrompt	{(level \#$level) debug [history nextid] > }
146	dead		{}
147	expandorder	{Pathname Variable Procname}
148	font		{}
149	history		48
150	hoterrors	1
151	library		{}
152	lightbrace	1
153	lightcmd	1
154	maineval	{}
155	maxmenu		15
156	nontcl		0
157	prompt1		{ignore this, it's set below}
158	rows		20
159	scrollypos	right
160	showmenu	1
161	showmultiple	1
162	showstatusbar	0
163	slaveeval	{}
164	slaveexit	close
165	subhistory	1
166	gc-delay	60000
167	gets		{congets}
168	usehistory	1
169
170	exec		slave
171    } {
172	if {![info exists OPT($key)]} { set OPT($key) $default }
173    }
174
175    foreach {key default} {
176	app		{}
177	appname		{}
178	apptype		slave
179	namesp		::
180	cmd		{}
181	cmdbuf		{}
182	cmdsave		{}
183	event		1
184	deadapp		0
185	deadsock	0
186	debugging	0
187	displayWin	.
188	histid		0
189	find		{}
190	find,case	0
191	find,reg	0
192	errorInfo	{}
193	showOnStartup	1
194	slavealias	{ edit more less tkcon }
195	slaveprocs	{
196	    alias clear dir dump echo idebug lremove
197	    tkcon_puts tkcon_gets observe observe_var unalias which what
198	}
199	version		2.3
200	RCS		{RCS: @(#) $Id: tkcon.tcl,v 1.2 2008/04/18 16:28:13 tim Exp $}
201	HEADURL		{http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/tkcon/tkcon/tkcon.tcl?rev=HEAD}
202	docs		"http://tkcon.sourceforge.net/"
203	email		{jeff@hobbs.org}
204	root		.
205    } {
206	if {![info exists PRIV($key)]} { set PRIV($key) $default }
207    }
208
209    ## NOTES FOR STAYING IN PRIMARY INTERPRETER:
210    ##
211    ## If you set ::tkcon::OPT(exec) to {}, then instead of a multiple
212    ## interp model, you get tkcon operating in the main interp by default.
213    ## This can be useful when attaching to programs that like to operate
214    ## in the main interpter (for example, based on special wish'es).
215    ## You can set this from the command line with -exec ""
216    ## A side effect is that all tkcon command line args will be used
217    ## by the first console only.
218    #set OPT(exec) {}
219
220    if {$PRIV(WWW)} {
221	lappend PRIV(slavealias) history
222	set OPT(prompt1) {[history nextid] % }
223    } else {
224	lappend PRIV(slaveprocs) tcl_unknown unknown
225	set OPT(prompt1) {([file tail [pwd]]) [history nextid] % }
226    }
227
228    ## If we are using the default '.' toplevel, and there appear to be
229    ## children of '.', then make sure we use a disassociated toplevel.
230    if {$PRIV(root) == "." && [llength [winfo children .]]} {
231	set PRIV(root) .tkcon
232    }
233
234    ## Do platform specific configuration here, other than defaults
235    ### Use tkcon.cfg filename for resource filename on non-unix systems
236    ### Determine what directory the resource file should be in
237    switch $tcl_platform(platform) {
238	macintosh	{
239	    if {![interp issafe]} {cd [file dirname [info script]]}
240	    set envHome		PREF_FOLDER
241	    set rcfile		tkcon.cfg
242	    set histfile	irsim_tkcon.hst
243	    catch {console hide}
244	}
245	windows		{
246	    set envHome		HOME
247	    set rcfile		tkcon.cfg
248	    set histfile	irsim_tkcon.hst
249	}
250	unix		{
251	    set envHome		HOME
252	    set rcfile		.tkconrc
253	    set histfile	.irsim_tkcon_hst
254	}
255    }
256    if {[info exists env($envHome)]} {
257	if {![info exists PRIV(rcfile)]} {
258	    set PRIV(rcfile)	[file join $env($envHome) $rcfile]
259	}
260	if {![info exists PRIV(histfile)]} {
261	    set PRIV(histfile)	[file join $env($envHome) $histfile]
262	}
263    }
264
265    ## Handle command line arguments before sourcing resource file to
266    ## find if resource file is being specified (let other args pass).
267    if {[set i [lsearch -exact $argv -rcfile]] != -1} {
268	set PRIV(rcfile) [lindex $argv [incr i]]
269    }
270
271    if {!$PRIV(WWW) && [file exists $PRIV(rcfile)]} {
272	set code [catch {uplevel \#0 [list source $PRIV(rcfile)]} err]
273    }
274
275    if {[info exists env(TK_CON_LIBRARY)]} {
276	lappend ::auto_path $env(TK_CON_LIBRARY)
277    } else {
278	lappend ::auto_path $OPT(library)
279    }
280
281    if {![info exists ::tcl_pkgPath]} {
282	set dir [file join [file dirname [info nameofexec]] lib]
283	if {[llength [info commands @scope]]} {
284	    set dir [file join $dir itcl]
285	}
286	catch {source [file join $dir pkgIndex.tcl]}
287    }
288    catch {tclPkgUnknown dummy-name dummy-version}
289
290    ## Handle rest of command line arguments after sourcing resource file
291    ## and slave is created, but before initializing UI or setting packages.
292    set slaveargs {}
293    set slavefiles {}
294    set truth {^(1|yes|true|on)$}
295    for {set i 0} {$i < $argc} {incr i} {
296	set arg [lindex $argv $i]
297	if {[string match {-*} $arg]} {
298	    set val [lindex $argv [incr i]]
299	    ## Handle arg based options
300	    switch -glob -- $arg {
301		-- - -argv	{
302		    set argv [concat -- [lrange $argv $i end]]
303		    set argc [llength $argv]
304		    break
305		}
306		-color-*	{ set COLOR([string range $arg 7 end]) $val }
307		-exec		{ set OPT(exec) $val }
308		-main - -e - -eval	{ append OPT(maineval) \n$val\n }
309		-package - -load	{ lappend OPT(autoload) $val }
310		-slave		{ append OPT(slaveeval) \n$val\n }
311		-nontcl		{ set OPT(nontcl) [regexp -nocase $truth $val]}
312		-root		{ set PRIV(root) $val }
313		-font		{ set OPT(font) $val }
314		-rcfile	{}
315		default	{ lappend slaveargs $arg; incr i -1 }
316	    }
317	} elseif {[file isfile $arg]} {
318	    lappend slavefiles $arg
319	} else {
320	    lappend slaveargs $arg
321	}
322    }
323
324    ## Create slave executable
325    if {[string compare {} $OPT(exec)]} {
326	uplevel \#0 ::tkcon::InitSlave $OPT(exec) $slaveargs
327    } else {
328	set argc [llength $slaveargs]
329	set argv $slaveargs
330	uplevel \#0 $slaveargs
331    }
332
333    ## Attach to the slave, EvalAttached will then be effective
334    Attach $PRIV(appname) $PRIV(apptype)
335    InitUI $title
336
337    ## swap puts and gets with the tkcon versions to make sure all
338    ## input and output is handled by tkcon
339    if {![catch {rename ::puts ::tkcon_tcl_puts}]} {
340	interp alias {} ::puts {} ::tkcon_puts
341    }
342    if {($OPT(gets) != "") && ![catch {rename ::gets ::tkcon_tcl_gets}]} {
343	interp alias {} ::gets {} ::tkcon_gets
344    }
345
346    EvalSlave history keep $OPT(history)
347    if {[info exists MainInit]} {
348	# Source history file only for the main console, as all slave
349	# consoles will adopt from the main's history, but still
350	# keep separate histories
351	if {!$PRIV(WWW) && $OPT(usehistory) && [file exists $PRIV(histfile)]} {
352	    puts -nonewline "loading history file ... "
353	    # The history file is built to be loaded in and
354	    # understood by tkcon
355	    if {[catch {uplevel \#0 [list source $PRIV(histfile)]} herr]} {
356		puts stderr "error:\n$herr"
357		append PRIV(errorInfo) $errorInfo\n
358	    }
359	    set PRIV(event) [EvalSlave history nextid]
360	    puts "[expr {$PRIV(event)-1}] events added"
361	}
362    }
363
364    ## Autoload specified packages in slave
365    set pkgs [EvalSlave package names]
366    foreach pkg $OPT(autoload) {
367	puts -nonewline "autoloading package \"$pkg\" ... "
368	if {[lsearch -exact $pkgs $pkg]>-1} {
369	    if {[catch {EvalSlave package require [list $pkg]} pkgerr]} {
370		puts stderr "error:\n$pkgerr"
371		append PRIV(errorInfo) $errorInfo\n
372	    } else { puts "OK" }
373	} else {
374	    puts stderr "error: package does not exist"
375	}
376    }
377
378    ## Evaluate maineval in slave
379    if {[string compare {} $OPT(maineval)] && \
380	    [catch {uplevel \#0 $OPT(maineval)} merr]} {
381	puts stderr "error in eval:\n$merr"
382	append PRIV(errorInfo) $errorInfo\n
383    }
384
385    ## Source extra command line argument files into slave executable
386    foreach fn $slavefiles {
387	puts -nonewline "slave sourcing \"$fn\" ... "
388	if {[catch {EvalSlave source [list $fn]} fnerr]} {
389	    puts stderr "error:\n$fnerr"
390	    append PRIV(errorInfo) $errorInfo\n
391	} else { puts "OK" }
392    }
393
394    ## Evaluate slaveeval in slave
395    if {[string compare {} $OPT(slaveeval)] && \
396	    [catch {interp eval $OPT(exec) $OPT(slaveeval)} serr]} {
397	puts stderr "error in slave eval:\n$serr"
398	append PRIV(errorInfo) $errorInfo\n
399    }
400    ## Output any error/output that may have been returned from rcfile
401    if {[info exists code] && $code && [string compare {} $err]} {
402	puts stderr "error in $PRIV(rcfile):\n$err"
403	append PRIV(errorInfo) $errorInfo
404    }
405    if {[string compare {} $OPT(exec)]} {
406	StateCheckpoint [concat $PRIV(name) $OPT(exec)] slave
407    }
408    StateCheckpoint $PRIV(name) slave
409
410    Prompt "$title console display active (Tcl$::tcl_patchLevel / Tk$::tk_patchLevel)\n"
411}
412
413## ::tkcon::InitSlave - inits the slave by placing key procs and aliases in it
414## It's arg[cv] are based on passed in options, while argv0 is the same as
415## the master.  tcl_interactive is the same as the master as well.
416# ARGS:	slave	- name of slave to init.  If it does not exist, it is created.
417#	args	- args to pass to a slave as argv/argc
418##
419proc ::tkcon::InitSlave {slave args} {
420    variable OPT
421    variable COLOR
422    variable PRIV
423    global argv0 tcl_interactive tcl_library env auto_path
424
425    if {[string match {} $slave]} {
426	return -code error "Don't init the master interpreter, goofball"
427    }
428    if {![interp exists $slave]} { interp create $slave }
429    if {[interp eval $slave info command source] == ""} {
430	$slave alias source SafeSource $slave
431	$slave alias load SafeLoad $slave
432	$slave alias open SafeOpen $slave
433	$slave alias file file
434	interp eval $slave [dump var -nocomplain tcl_library auto_path env]
435	interp eval $slave { catch {source [file join $tcl_library init.tcl]} }
436	interp eval $slave { catch unknown }
437    }
438    $slave alias exit exit
439    interp eval $slave {
440	# Do package require before changing around puts/gets
441	catch {package require bogus-package-name}
442	catch {rename ::puts ::tkcon_tcl_puts}
443    }
444    foreach cmd $PRIV(slaveprocs) { $slave eval [dump proc $cmd] }
445    foreach cmd $PRIV(slavealias) { $slave alias $cmd $cmd }
446    interp alias $slave ::ls $slave ::dir -full
447    interp alias $slave ::puts $slave ::tkcon_puts
448    if {$OPT(gets) != ""} {
449	interp eval $slave { catch {rename ::gets ::tkcon_tcl_gets} }
450	interp alias $slave ::gets $slave ::tkcon_gets
451    }
452    if {[info exists argv0]} {interp eval $slave [list set argv0 $argv0]}
453    interp eval $slave set tcl_interactive $tcl_interactive \; \
454	    set auto_path [list $auto_path] \; \
455	    set argc [llength $args] \; \
456	    set argv  [list $args] \; {
457	if {![llength [info command bgerror]]} {
458	    proc bgerror err {
459		global errorInfo
460		set body [info body bgerror]
461		rename ::bgerror {}
462		if {[auto_load bgerror]} { return [bgerror $err] }
463		proc bgerror err $body
464		tkcon bgerror $err $errorInfo
465	    }
466	}
467    }
468
469    foreach pkg [lremove [package names] Tcl] {
470	foreach v [package versions $pkg] {
471	    interp eval $slave [list package ifneeded $pkg $v \
472		    [package ifneeded $pkg $v]]
473	}
474    }
475}
476
477## ::tkcon::InitInterp - inits an interpreter by placing key
478## procs and aliases in it.
479# ARGS: name	- interp name
480#	type	- interp type (slave|interp)
481##
482proc ::tkcon::InitInterp {name type} {
483    variable OPT
484    variable PRIV
485
486    ## Don't allow messing up a local master interpreter
487    if {[string match namespace $type] || ([string match slave $type] && \
488	    [regexp {^([Mm]ain|Slave[0-9]+)$} $name])} return
489    set old [Attach]
490    set oldname $PRIV(namesp)
491    catch {
492	Attach $name $type
493	EvalAttached { catch {rename ::puts ::tkcon_tcl_puts} }
494	foreach cmd $PRIV(slaveprocs) { EvalAttached [dump proc $cmd] }
495	switch -exact $type {
496	    slave {
497		foreach cmd $PRIV(slavealias) {
498		    Main interp alias $name ::$cmd $PRIV(name) ::$cmd
499		}
500	    }
501	    interp {
502		set thistkcon [tk appname]
503		foreach cmd $PRIV(slavealias) {
504		    EvalAttached "proc $cmd args { send [list $thistkcon] $cmd \$args }"
505		}
506	    }
507	}
508	## Catch in case it's a 7.4 (no 'interp alias') interp
509	EvalAttached {
510	    catch {interp alias {} ::ls {} ::dir -full}
511	    if {[catch {interp alias {} ::puts {} ::tkcon_puts}]} {
512		catch {rename ::tkcon_puts ::puts}
513	    }
514	}
515	if {$OPT(gets) != ""} {
516	    EvalAttached {
517		catch {rename ::gets ::tkcon_tcl_gets}
518		if {[catch {interp alias {} ::gets {} ::tkcon_gets}]} {
519		    catch {rename ::tkcon_gets ::gets}
520		}
521	    }
522	}
523	return
524    } {err}
525    eval Attach $old
526    AttachNamespace $oldname
527    if {[string compare {} $err]} { return -code error $err }
528}
529
530## ::tkcon::InitUI - inits UI portion (console) of tkcon
531## Creates all elements of the console window and sets up the text tags
532# ARGS:	root	- widget pathname of the tkcon console root
533#	title	- title for the console root and main (.) windows
534# Calls:	::tkcon::InitMenus, ::tkcon::Prompt
535##
536proc ::tkcon::InitUI {title} {
537    variable OPT
538    variable PRIV
539    variable COLOR
540
541    set root $PRIV(root)
542    if {[string match . $root]} { set w {} } else { set w [toplevel $root] }
543    if {!$PRIV(WWW)} {
544	wm withdraw $root
545	wm protocol $root WM_DELETE_WINDOW exit
546    }
547    set PRIV(base) $w
548
549    ## Text Console
550    set PRIV(console) [set con $w.text]
551    text $con -wrap char -yscrollcommand [list $w.sy set] \
552	    -foreground $COLOR(stdin) \
553	    -insertbackground $COLOR(cursor)
554    $con mark set output 1.0
555    $con mark set limit 1.0
556    if {[string compare {} $COLOR(bg)]} {
557	$con configure -background $COLOR(bg)
558    }
559    set COLOR(bg) [$con cget -background]
560    if {[string compare {} $OPT(font)]} {
561	## Set user-requested font, if any
562	$con configure -font $OPT(font)
563    } else {
564	## otherwise make sure the font is monospace
565	set font [$con cget -font]
566	if {![font metrics $font -fixed]} {
567	    font create tkconfixed -family Courier -size 12
568	    $con configure -font tkconfixed
569	}
570    }
571    set OPT(font) [$con cget -font]
572    if {!$PRIV(WWW)} {
573	$con configure -setgrid 1 -width $OPT(cols) -height $OPT(rows)
574    }
575    bindtags $con [list $con TkConsole TkConsolePost $root all]
576    ## Menus
577    ## catch against use in plugin
578    if {[catch {menu $w.mbar} PRIV(menubar)]} {
579	set PRIV(menubar) [frame $w.mbar -relief raised -bd 1]
580    }
581    ## Scrollbar
582    set PRIV(scrolly) [scrollbar $w.sy -takefocus 0 -bd 1 \
583	    -command [list $con yview]]
584
585    InitMenus $PRIV(menubar) $title
586    Bindings
587
588    if {$OPT(showmenu)} {
589	$root configure -menu $PRIV(menubar)
590    }
591    pack $w.sy -side $OPT(scrollypos) -fill y
592    pack $con -fill both -expand 1
593
594    set PRIV(statusbar) [set sbar [frame $w.sbar]]
595    label $sbar.attach -relief sunken -bd 1 -anchor w \
596	    -textvariable ::tkcon::PRIV(StatusAttach)
597    label $sbar.mode -relief sunken -bd 1 -anchor w  \
598	    -textvariable ::tkcon::PRIV(StatusMode)
599    label $sbar.cursor -relief sunken -bd 1 -anchor w -width 6 \
600	    -textvariable ::tkcon::PRIV(StatusCursor)
601    grid $sbar.attach $sbar.mode $sbar.cursor -sticky news -padx 1
602    grid columnconfigure $sbar 0 -weight 1
603    grid columnconfigure $sbar 1 -weight 1
604    grid columnconfigure $sbar 2 -weight 0
605
606    if {$OPT(showstatusbar)} {
607	pack $sbar -side bottom -fill x -before $::tkcon::PRIV(scrolly)
608    }
609
610    foreach col {prompt stdout stderr stdin proc} {
611	$con tag configure $col -foreground $COLOR($col)
612    }
613    $con tag configure var -background $COLOR(var)
614    $con tag raise sel
615    $con tag configure blink -background $COLOR(blink)
616    $con tag configure find -background $COLOR(blink)
617
618    if {!$PRIV(WWW)} {
619	wm title $root "tkcon $PRIV(version) $title"
620	bind $con <Configure> {
621	    scan [wm geometry [winfo toplevel %W]] "%%dx%%d" \
622		    ::tkcon::OPT(cols) ::tkcon::OPT(rows)
623	}
624	if {$PRIV(showOnStartup)} { wm deiconify $root }
625    }
626    if {$PRIV(showOnStartup)} { focus -force $PRIV(console) }
627    if {$OPT(gc-delay)} {
628	after $OPT(gc-delay) ::tkcon::GarbageCollect
629    }
630}
631
632## ::tkcon::GarbageCollect - do various cleanup ops periodically to our setup
633##
634proc ::tkcon::GarbageCollect {} {
635    variable OPT
636    variable PRIV
637
638    set w $PRIV(console)
639    ## Remove error tags that no longer span anything
640    ## Make sure the tag pattern matches the unique tag prefix
641    foreach tag [$w tag names] {
642	if {[string match _tag* $tag] && ![llength [$w tag ranges $tag]]} {
643	    $w tag delete $tag
644	}
645    }
646    if {$OPT(gc-delay)} {
647	after $OPT(gc-delay) ::tkcon::GarbageCollect
648    }
649}
650
651## ::tkcon::Eval - evaluates commands input into console window
652## This is the first stage of the evaluating commands in the console.
653## They need to be broken up into consituent commands (by ::tkcon::CmdSep) in
654## case a multiple commands were pasted in, then each is eval'ed (by
655## ::tkcon::EvalCmd) in turn.  Any uncompleted command will not be eval'ed.
656# ARGS:	w	- console text widget
657# Calls:	::tkcon::CmdGet, ::tkcon::CmdSep, ::tkcon::EvalCmd
658##
659proc ::tkcon::Eval {w} {
660    set incomplete [CmdSep [CmdGet $w] cmds last]
661    $w mark set insert end-1c
662    $w insert end \n
663    if {[llength $cmds]} {
664	foreach c $cmds {EvalCmd $w $c}
665	$w insert insert $last {}
666    } elseif {!$incomplete} {
667	EvalCmd $w $last
668    }
669    $w see insert
670}
671
672## ::tkcon::EvalCmd - evaluates a single command, adding it to history
673# ARGS:	w	- console text widget
674# 	cmd	- the command to evaluate
675# Calls:	::tkcon::Prompt
676# Outputs:	result of command to stdout (or stderr if error occured)
677# Returns:	next event number
678##
679proc ::tkcon::EvalCmd {w cmd} {
680    variable OPT
681    variable PRIV
682
683    $w mark set output end
684    if {[string compare {} $cmd]} {
685	set code 0
686	if {$OPT(subhistory)} {
687	    set ev [EvalSlave history nextid]
688	    incr ev -1
689	    if {[string match !! $cmd]} {
690		set code [catch {EvalSlave history event $ev} cmd]
691		if {!$code} {$w insert output $cmd\n stdin}
692	    } elseif {[regexp {^!(.+)$} $cmd dummy event]} {
693		## Check last event because history event is broken
694		set code [catch {EvalSlave history event $ev} cmd]
695		if {!$code && ![string match ${event}* $cmd]} {
696		    set code [catch {EvalSlave history event $event} cmd]
697		}
698		if {!$code} {$w insert output $cmd\n stdin}
699	    } elseif {[regexp {^\^([^^]*)\^([^^]*)\^?$} $cmd dummy old new]} {
700		set code [catch {EvalSlave history event $ev} cmd]
701		if {!$code} {
702		    regsub -all -- $old $cmd $new cmd
703		    $w insert output $cmd\n stdin
704		}
705	    } elseif {$OPT(calcmode) && ![catch {expr $cmd} err]} {
706		EvalSlave history add $cmd
707		set cmd $err
708		set code -1
709	    }
710	}
711	if {$code} {
712	    $w insert output $cmd\n stderr
713	} else {
714	    ## We are about to evaluate the command, so move the limit
715	    ## mark to ensure that further <Return>s don't cause double
716	    ## evaluation of this command - for cases like the command
717	    ## has a vwait or something in it
718	    $w mark set limit end
719	    if {$OPT(nontcl) && [string match interp $PRIV(apptype)]} {
720		set code [catch {EvalSend $cmd} res]
721		if {$code == 1} {
722		    set PRIV(errorInfo) "Non-Tcl errorInfo not available"
723		}
724	    } elseif {[string match socket $PRIV(apptype)]} {
725		set code [catch {EvalSocket $cmd} res]
726		if {$code == 1} {
727		    set PRIV(errorInfo) "Socket-based errorInfo not available"
728		}
729	    } else {
730		set code [catch {EvalAttached $cmd} res]
731		if {$code == 1} {
732		    if {[catch {EvalAttached [list set errorInfo]} err]} {
733			set PRIV(errorInfo) "Error getting errorInfo:\n$err"
734		    } else {
735			set PRIV(errorInfo) $err
736		    }
737		}
738	    }
739	    EvalSlave history add $cmd
740	    if {$code} {
741		if {$OPT(hoterrors)} {
742		    set tag [UniqueTag $w]
743		    $w insert output $res [list stderr $tag] \n stderr
744		    $w tag bind $tag <Enter> \
745			    [list $w tag configure $tag -underline 1]
746		    $w tag bind $tag <Leave> \
747			    [list $w tag configure $tag -underline 0]
748		    $w tag bind $tag <ButtonRelease-1> \
749			    "if {!\[info exists tkPriv(mouseMoved)\] || !\$tkPriv(mouseMoved)} \
750			    {[list edit -attach [Attach] -type error -- $PRIV(errorInfo)]}"
751		} else {
752		    $w insert output $res\n stderr
753		}
754	    } elseif {[string compare {} $res]} {
755		$w insert output $res\n stdout
756	    }
757	}
758    }
759    Prompt
760    set PRIV(event) [EvalSlave history nextid]
761}
762
763## ::tkcon::EvalSlave - evaluates the args in the associated slave
764## args should be passed to this procedure like they would be at
765## the command line (not like to 'eval').
766# ARGS:	args	- the command and args to evaluate
767##
768proc ::tkcon::EvalSlave args {
769    interp eval $::tkcon::OPT(exec) $args
770}
771
772## ::tkcon::EvalOther - evaluate a command in a foreign interp or slave
773## without attaching to it.  No check for existence is made.
774# ARGS:	app	- interp/slave name
775#	type	- (slave|interp)
776##
777proc ::tkcon::EvalOther { app type args } {
778    if {[string compare slave $type]==0} {
779	return [Slave $app $args]
780    } else {
781	return [uplevel 1 send [list $app] $args]
782    }
783}
784
785## ::tkcon::EvalSend - sends the args to the attached interpreter
786## Varies from 'send' by determining whether attachment is dead
787## when an error is received
788# ARGS:	cmd	- the command string to send across
789# Returns:	the result of the command
790##
791proc ::tkcon::EvalSend cmd {
792    variable OPT
793    variable PRIV
794
795    if {$PRIV(deadapp)} {
796	if {[lsearch -exact [winfo interps] $PRIV(app)]<0} {
797	    return
798	} else {
799	    set PRIV(appname) [string range $PRIV(appname) 5 end]
800	    set PRIV(deadapp) 0
801	    Prompt "\n\"$PRIV(app)\" alive\n" [CmdGet $PRIV(console)]
802	}
803    }
804    set code [catch {send -displayof $PRIV(displayWin) $PRIV(app) $cmd} result]
805    if {$code && [lsearch -exact [winfo interps] $PRIV(app)]<0} {
806	## Interpreter disappeared
807	if {[string compare leave $OPT(dead)] && \
808		([string match ignore $OPT(dead)] || \
809		[tk_dialog $PRIV(base).dead "Dead Attachment" \
810		"\"$PRIV(app)\" appears to have died.\
811		\nReturn to primary slave interpreter?" questhead 0 OK No])} {
812	    set PRIV(appname) "DEAD:$PRIV(appname)"
813	    set PRIV(deadapp) 1
814	} else {
815	    set err "Attached Tk interpreter \"$PRIV(app)\" died."
816	    Attach {}
817	    set PRIV(deadapp) 0
818	    EvalSlave set errorInfo $err
819	}
820	Prompt \n [CmdGet $PRIV(console)]
821    }
822    return -code $code $result
823}
824
825## ::tkcon::EvalSocket - sends the string to an interpreter attached via
826## a tcp/ip socket
827##
828## In the EvalSocket case, ::tkcon::PRIV(app) is the socket id
829##
830## Must determine whether socket is dead when an error is received
831# ARGS:	cmd	- the data string to send across
832# Returns:	the result of the command
833##
834proc ::tkcon::EvalSocket cmd {
835    variable OPT
836    variable PRIV
837    global tcl_version
838
839    if {$PRIV(deadapp)} {
840	if {![info exists PRIV(app)] || \
841		[catch {eof $PRIV(app)} eof] || $eof} {
842	    return
843	} else {
844	    set PRIV(appname) [string range $PRIV(appname) 5 end]
845	    set PRIV(deadapp) 0
846	    Prompt "\n\"$PRIV(app)\" alive\n" [CmdGet $PRIV(console)]
847	}
848    }
849    # Sockets get \'s interpreted, so that users can
850    # send things like \n\r or explicit hex values
851    set cmd [subst -novariables -nocommands $cmd]
852    #puts [list $PRIV(app) $cmd]
853    set code [catch {puts $PRIV(app) $cmd ; flush $PRIV(app)} result]
854    if {$code && [eof $PRIV(app)]} {
855	## Interpreter died or disappeared
856	puts "$code eof [eof $PRIV(app)]"
857	EvalSocketClosed
858    }
859    return -code $code $result
860}
861
862## ::tkcon::EvalSocketEvent - fileevent command for an interpreter attached
863## via a tcp/ip socket
864## Must determine whether socket is dead when an error is received
865# ARGS:	args	- the args to send across
866# Returns:	the result of the command
867##
868proc ::tkcon::EvalSocketEvent {} {
869    variable PRIV
870
871    if {[gets $PRIV(app) line] == -1} {
872	if {[eof $PRIV(app)]} {
873	    EvalSocketClosed
874	}
875	return
876    }
877    puts $line
878}
879
880## ::tkcon::EvalSocketClosed - takes care of handling a closed eval socket
881##
882# ARGS:	args	- the args to send across
883# Returns:	the result of the command
884##
885proc ::tkcon::EvalSocketClosed {} {
886    variable OPT
887    variable PRIV
888
889    catch {close $PRIV(app)}
890    if {[string compare leave $OPT(dead)] && \
891	    ([string match ignore $OPT(dead)] || \
892	    [tk_dialog $PRIV(base).dead "Dead Attachment" \
893	    "\"$PRIV(app)\" appears to have died.\
894	    \nReturn to primary slave interpreter?" questhead 0 OK No])} {
895	set PRIV(appname) "DEAD:$PRIV(appname)"
896	set PRIV(deadapp) 1
897    } else {
898	set err "Attached Tk interpreter \"$PRIV(app)\" died."
899	Attach {}
900	set PRIV(deadapp) 0
901	EvalSlave set errorInfo $err
902    }
903    Prompt \n [CmdGet $PRIV(console)]
904}
905
906## ::tkcon::EvalNamespace - evaluates the args in a particular namespace
907## This is an override for ::tkcon::EvalAttached for when the user wants
908## to attach to a particular namespace of the attached interp
909# ARGS:	attached
910#	namespace	the namespace to evaluate in
911#	args		the args to evaluate
912# RETURNS:	the result of the command
913##
914proc ::tkcon::EvalNamespace { attached namespace args } {
915    if {[llength $args]} {
916	uplevel \#0 $attached \
917		[list [concat [list namespace eval $namespace] $args]]
918    }
919}
920
921
922## ::tkcon::Namespaces - return all the namespaces descendent from $ns
923##
924#
925##
926proc ::tkcon::Namespaces {{ns ::} {l {}}} {
927    if {[string compare {} $ns]} { lappend l $ns }
928    foreach i [EvalAttached [list namespace children $ns]] {
929	set l [Namespaces $i $l]
930    }
931    return $l
932}
933
934## ::tkcon::CmdGet - gets the current command from the console widget
935# ARGS:	w	- console text widget
936# Returns:	text which compromises current command line
937##
938proc ::tkcon::CmdGet w {
939    if {![llength [$w tag nextrange prompt limit end]]} {
940	$w tag add stdin limit end-1c
941	return [$w get limit end-1c]
942    }
943}
944
945## ::tkcon::CmdSep - separates multiple commands into a list and remainder
946# ARGS:	cmd	- (possible) multiple command to separate
947# 	list	- varname for the list of commands that were separated.
948#	last	- varname of any remainder (like an incomplete final command).
949#		If there is only one command, it's placed in this var.
950# Returns:	constituent command info in varnames specified by list & rmd.
951##
952proc ::tkcon::CmdSep {cmd list last} {
953    upvar 1 $list cmds $last inc
954    set inc {}
955    set cmds {}
956    foreach c [split [string trimleft $cmd] \n] {
957	if {[string compare $inc {}]} {
958	    append inc \n$c
959	} else {
960	    append inc [string trimleft $c]
961	}
962	if {[info complete $inc] && ![regexp {[^\\]\\$} $inc]} {
963	    if {[regexp "^\[^#\]" $inc]} {lappend cmds $inc}
964	    set inc {}
965	}
966    }
967    set i [string compare $inc {}]
968    if {!$i && [string compare $cmds {}] && ![string match *\n $cmd]} {
969	set inc [lindex $cmds end]
970	set cmds [lreplace $cmds end end]
971    }
972    return $i
973}
974
975## ::tkcon::CmdSplit - splits multiple commands into a list
976# ARGS:	cmd	- (possible) multiple command to separate
977# Returns:	constituent commands in a list
978##
979proc ::tkcon::CmdSplit {cmd} {
980    set inc {}
981    set cmds {}
982    foreach cmd [split [string trimleft $cmd] \n] {
983	if {[string compare {} $inc]} {
984	    append inc \n$cmd
985	} else {
986	    append inc [string trimleft $cmd]
987	}
988	if {[info complete $inc] && ![regexp {[^\\]\\$} $inc]} {
989	    #set inc [string trimright $inc]
990	    if {[regexp "^\[^#\]" $inc]} {lappend cmds $inc}
991	    set inc {}
992	}
993    }
994    if {[regexp "^\[^#\]" $inc]} {lappend cmds $inc}
995    return $cmds
996}
997
998## ::tkcon::UniqueTag - creates a uniquely named tag, reusing names
999## Called by ::tkcon::EvalCmd
1000# ARGS:	w	- text widget
1001# Outputs:	tag name guaranteed unique in the widget
1002##
1003proc ::tkcon::UniqueTag {w} {
1004    set tags [$w tag names]
1005    set idx 0
1006    while {[lsearch -exact $tags _tag[incr idx]] != -1} {}
1007    return _tag$idx
1008}
1009
1010## ::tkcon::ConstrainBuffer - This limits the amount of data in the text widget
1011## Called by ::tkcon::Prompt and in tkcon proc buffer/console switch cases
1012# ARGS:	w	- console text widget
1013#	size	- # of lines to constrain to
1014# Outputs:	may delete data in console widget
1015##
1016proc ::tkcon::ConstrainBuffer {w size} {
1017    if {[$w index end] > $size} {
1018	$w delete 1.0 [expr {int([$w index end])-$size}].0
1019    }
1020}
1021
1022## ::tkcon::Prompt - displays the prompt in the console widget
1023# ARGS:	w	- console text widget
1024# Outputs:	prompt (specified in ::tkcon::OPT(prompt1)) to console
1025##
1026proc ::tkcon::Prompt {{pre {}} {post {}} {prompt {}}} {
1027    variable OPT
1028    variable PRIV
1029
1030    set w $PRIV(console)
1031    if {[string compare {} $pre]} { $w insert end $pre stdout }
1032    set i [$w index end-1c]
1033    if {!$OPT(showstatusbar)} {
1034	if {[string compare {} $PRIV(appname)]} {
1035	    $w insert end ">$PRIV(appname)< " prompt
1036	}
1037	if {[string compare :: $PRIV(namesp)]} {
1038	    $w insert end "<$PRIV(namesp)> " prompt
1039	}
1040    }
1041    if {[string compare {} $prompt]} {
1042	$w insert end $prompt prompt
1043    } else {
1044	$w insert end [EvalSlave subst $OPT(prompt1)] prompt
1045    }
1046    $w mark set output $i
1047    $w mark set insert end
1048    $w mark set limit insert
1049    $w mark gravity limit left
1050    if {[string compare {} $post]} { $w insert end $post stdin }
1051    ConstrainBuffer $w $OPT(buffer)
1052    set ::tkcon::PRIV(StatusCursor) [$w index insert]
1053    $w see end
1054}
1055
1056## ::tkcon::About - gives about info for tkcon
1057##
1058proc ::tkcon::About {} {
1059    variable OPT
1060    variable PRIV
1061    variable COLOR
1062
1063    set w $PRIV(base).about
1064    if {[winfo exists $w]} {
1065	wm deiconify $w
1066    } else {
1067	global tk_patchLevel tcl_patchLevel tcl_version
1068	toplevel $w
1069	wm title $w "About tkcon v$PRIV(version)"
1070	button $w.b -text Dismiss -command [list wm withdraw $w]
1071	text $w.text -height 9 -bd 1 -width 60 \
1072		-foreground $COLOR(stdin) \
1073		-background $COLOR(bg) \
1074		-font $OPT(font)
1075	pack $w.b -fill x -side bottom
1076	pack $w.text -fill both -side left -expand 1
1077	$w.text tag config center -justify center
1078	$w.text tag config title -justify center -font {Courier -18 bold}
1079	# strip down the RCS info displayed in the about box
1080	regexp {,v ([0-9\./: ]*)} $PRIV(RCS) -> RCS
1081	$w.text insert 1.0 "About tkcon v$PRIV(version)" title \
1082		"\n\nCopyright 1995-2001 Jeffrey Hobbs, $PRIV(email)\
1083		\nRelease Info: v$PRIV(version), CVS v$RCS\
1084		\nDocumentation available at:\n$PRIV(docs)\
1085		\nUsing: Tcl v$tcl_patchLevel / Tk v$tk_patchLevel" center
1086	$w.text config -state disabled
1087    }
1088}
1089
1090## ::tkcon::InitMenus - inits the menubar and popup for the console
1091# ARGS:	w	- console text widget
1092##
1093proc ::tkcon::InitMenus {w title} {
1094    variable OPT
1095    variable PRIV
1096    variable COLOR
1097    global tcl_platform
1098
1099    if {[catch {menu $w.pop -tearoff 0}]} {
1100	label $w.label -text "Menus not available in plugin mode"
1101	pack $w.label
1102	return
1103    }
1104    menu $w.context -tearoff 0 -disabledforeground $COLOR(disabled)
1105    set PRIV(context) $w.context
1106    set PRIV(popup) $w.pop
1107
1108    proc MenuButton {w m l} {
1109	$w add cascade -label $m -underline 0 -menu $w.$l
1110	return $w.$l
1111    }
1112
1113    foreach m [list File Console Edit Interp Prefs History Help] {
1114 	set l [string tolower $m]
1115 	MenuButton $w $m $l
1116 	$w.pop add cascade -label $m -underline 0 -menu $w.pop.$l
1117    }
1118
1119    ## File Menu
1120    ##
1121    foreach m [list [menu $w.file -disabledforeground $COLOR(disabled)] \
1122	    [menu $w.pop.file -disabledforeground $COLOR(disabled)]] {
1123	$m add command -label "Load File" -underline 0 -command ::tkcon::Load
1124	$m add cascade -label "Save ..."  -underline 0 -menu $m.save
1125	$m add separator
1126	$m add command -label "Quit" -underline 0 -accel Ctrl-q -command exit
1127
1128	## Save Menu
1129	##
1130	set s $m.save
1131	menu $s -disabledforeground $COLOR(disabled) -tearoff 0
1132	$s add command -label "All"	-underline 0 \
1133		-command {::tkcon::Save {} all}
1134	$s add command -label "History"	-underline 0 \
1135		-command {::tkcon::Save {} history}
1136	$s add command -label "Stdin"	-underline 3 \
1137		-command {::tkcon::Save {} stdin}
1138	$s add command -label "Stdout"	-underline 3 \
1139		-command {::tkcon::Save {} stdout}
1140	$s add command -label "Stderr"	-underline 3 \
1141		-command {::tkcon::Save {} stderr}
1142    }
1143
1144    ## Console Menu
1145    ##
1146    foreach m [list [menu $w.console -disabledfore $COLOR(disabled)] \
1147	    [menu $w.pop.console -disabledfore $COLOR(disabled)]] {
1148	$m add command -label "$title Console"	-state disabled
1149	$m add command -label "New Console"	-underline 0 -accel Ctrl-N \
1150		-command ::tkcon::New
1151	$m add command -label "Close Console"	-underline 0 -accel Ctrl-w \
1152		-command ::tkcon::Destroy
1153	$m add command -label "Clear Console"	-underline 1 -accel Ctrl-l \
1154		-command { clear; ::tkcon::Prompt }
1155	if {[string match unix $tcl_platform(platform)]} {
1156	    $m add separator
1157	    $m add command -label "Make Xauth Secure" -und 5 \
1158		    -command ::tkcon::XauthSecure
1159	}
1160	$m add separator
1161	$m add cascade -label "Attach To ..."	-underline 0 -menu $m.attach
1162
1163	## Attach Console Menu
1164	##
1165	set sub [menu $m.attach -disabledforeground $COLOR(disabled)]
1166	$sub add cascade -label "Interpreter"   -underline 0 -menu $sub.apps
1167	$sub add cascade -label "Namespace" -underline 1 -menu $sub.name
1168	$sub add cascade -label "Socket" -underline 1 -menu $sub.sock \
1169		-state [expr {([info tclversion] < 8.3)?"disabled":"normal"}]
1170
1171	## Attach Console Menu
1172	##
1173	menu $sub.apps -disabledforeground $COLOR(disabled) \
1174		-postcommand [list ::tkcon::AttachMenu $sub.apps]
1175
1176	## Attach Namespace Menu
1177	##
1178	menu $sub.name -disabledforeground $COLOR(disabled) -tearoff 0 \
1179		-postcommand [list ::tkcon::NamespaceMenu $sub.name]
1180
1181	if {$::tcl_version >= 8.3} {
1182	    # This uses [file channels] to create the menu, so we only
1183	    # want it for newer versions of Tcl.
1184
1185	    ## Attach Socket Menu
1186	    ##
1187	    menu $sub.sock -disabledforeground $COLOR(disabled) -tearoff 0 \
1188		    -postcommand [list ::tkcon::SocketMenu $sub.sock]
1189	}
1190
1191	## Attach Display Menu
1192	##
1193	if {![string compare "unix" $tcl_platform(platform)]} {
1194	    $sub add cascade -label "Display" -und 1 -menu $sub.disp
1195	    menu $sub.disp -disabledforeground $COLOR(disabled) \
1196		    -tearoff 0 \
1197		    -postcommand [list ::tkcon::DisplayMenu $sub.disp]
1198	}
1199    }
1200
1201    ## Edit Menu
1202    ##
1203    set text $PRIV(console)
1204    foreach m [list [menu $w.edit] [menu $w.pop.edit]] {
1205	$m add command -label "Cut"   -underline 2 -accel Ctrl-x \
1206		-command [list ::tkcon::Cut $text]
1207	$m add command -label "Copy"  -underline 0 -accel Ctrl-c \
1208		-command [list ::tkcon::Copy $text]
1209	$m add command -label "Paste" -underline 0 -accel Ctrl-v \
1210		 -command [list ::tkcon::Paste $text]
1211	$m add separator
1212	$m add command -label "Find"  -underline 0 -accel Ctrl-F \
1213		-command [list ::tkcon::FindBox $text]
1214    }
1215
1216    ## Interp Menu
1217    ##
1218    foreach m [list $w.interp $w.pop.interp] {
1219	menu $m -disabledforeground $COLOR(disabled) \
1220		-postcommand [list ::tkcon::InterpMenu $m]
1221    }
1222
1223    ## Prefs Menu
1224    ##
1225    foreach m [list [menu $w.prefs] [menu $w.pop.prefs]] {
1226	$m add check -label "Brace Highlighting" \
1227		-underline 0 -variable ::tkcon::OPT(lightbrace)
1228	$m add check -label "Command Highlighting" \
1229		-underline 0 -variable ::tkcon::OPT(lightcmd)
1230	$m add check -label "History Substitution" \
1231		-underline 0 -variable ::tkcon::OPT(subhistory)
1232	$m add check -label "Hot Errors" \
1233		-underline 0 -variable ::tkcon::OPT(hoterrors)
1234	$m add check -label "Non-Tcl Attachments" \
1235		-underline 0 -variable ::tkcon::OPT(nontcl)
1236	$m add check -label "Calculator Mode" \
1237		-underline 1 -variable ::tkcon::OPT(calcmode)
1238	$m add check -label "Show Multiple Matches" \
1239		-underline 0 -variable ::tkcon::OPT(showmultiple)
1240	$m add check -label "Show Menubar" \
1241		-underline 5 -variable ::tkcon::OPT(showmenu) \
1242		-command {$::tkcon::PRIV(root) configure -menu [expr \
1243		{$::tkcon::OPT(showmenu) ? $::tkcon::PRIV(menubar) : {}}]}
1244	$m add check -label "Show Statusbar" \
1245		-underline 5 -variable ::tkcon::OPT(showstatusbar) \
1246		-command {
1247	    if {$::tkcon::OPT(showstatusbar)} {
1248		pack $::tkcon::PRIV(statusbar) -side bottom -fill x \
1249			-before $::tkcon::PRIV(scrolly)
1250	    } else { pack forget $::tkcon::PRIV(statusbar) }
1251	}
1252	$m add cascade -label "Scrollbar" -underline 2 -menu $m.scroll
1253
1254	## Scrollbar Menu
1255	##
1256	set m [menu $m.scroll -tearoff 0]
1257	$m add radio -label "Left" -value left \
1258		-variable ::tkcon::OPT(scrollypos) \
1259		-command { pack config $::tkcon::PRIV(scrolly) -side left }
1260	$m add radio -label "Right" -value right \
1261		-variable ::tkcon::OPT(scrollypos) \
1262		-command { pack config $::tkcon::PRIV(scrolly) -side right }
1263    }
1264
1265    ## History Menu
1266    ##
1267    foreach m [list $w.history $w.pop.history] {
1268	menu $m -disabledforeground $COLOR(disabled) \
1269		-postcommand [list ::tkcon::HistoryMenu $m]
1270    }
1271
1272    ## Help Menu
1273    ##
1274    foreach m [list [menu $w.help] [menu $w.pop.help]] {
1275	$m add command -label "About " -underline 0 -accel Ctrl-A \
1276		-command ::tkcon::About
1277	$m add command -label "Retrieve Latest Version" -underline 0 \
1278		-command ::tkcon::Retrieve
1279    }
1280}
1281
1282## ::tkcon::HistoryMenu - dynamically build the menu for attached interpreters
1283##
1284# ARGS:	m	- menu widget
1285##
1286proc ::tkcon::HistoryMenu m {
1287    variable PRIV
1288
1289    if {![winfo exists $m]} return
1290    set id [EvalSlave history nextid]
1291    if {$PRIV(histid)==$id} return
1292    set PRIV(histid) $id
1293    $m delete 0 end
1294    while {($id>1) && ($id>$PRIV(histid)-10) && \
1295	    ![catch {EvalSlave history event [incr id -1]} tmp]} {
1296	set lbl $tmp
1297	if {[string len $lbl]>32} { set lbl [string range $tmp 0 28]... }
1298	$m add command -label "$id: $lbl" -command "
1299	$::tkcon::PRIV(console) delete limit end
1300	$::tkcon::PRIV(console) insert limit [list $tmp]
1301	$::tkcon::PRIV(console) see end
1302	::tkcon::Eval $::tkcon::PRIV(console)"
1303    }
1304}
1305
1306## ::tkcon::InterpMenu - dynamically build the menu for attached interpreters
1307##
1308# ARGS:	w	- menu widget
1309##
1310proc ::tkcon::InterpMenu w {
1311    variable OPT
1312    variable PRIV
1313    variable COLOR
1314
1315    if {![winfo exists $w]} return
1316    $w delete 0 end
1317    foreach {app type} [Attach] break
1318    $w add command -label "[string toupper $type]: $app" -state disabled
1319    if {($OPT(nontcl) && [string match interp $type]) || $PRIV(deadapp)} {
1320	$w add separator
1321	$w add command -state disabled -label "Communication disabled to"
1322	$w add command -state disabled -label "dead or non-Tcl interps"
1323	return
1324    }
1325
1326    ## Show Last Error
1327    ##
1328    $w add separator
1329    $w add command -label "Show Last Error" \
1330	    -command [list tkcon error $app $type]
1331
1332    ## Packages Cascaded Menu
1333    ##
1334    $w add separator
1335    $w add cascade -label Packages -underline 0 -menu $w.pkg
1336    set m $w.pkg
1337    if {![winfo exists $m]} {
1338	menu $m -tearoff no -disabledforeground $COLOR(disabled) \
1339		-postcommand [list ::tkcon::PkgMenu $m $app $type]
1340    }
1341
1342    ## State Checkpoint/Revert
1343    ##
1344    $w add separator
1345    $w add command -label "Checkpoint State" \
1346	    -command [list ::tkcon::StateCheckpoint $app $type]
1347    $w add command -label "Revert State" \
1348	    -command [list ::tkcon::StateRevert $app $type]
1349    $w add command -label "View State Change" \
1350	    -command [list ::tkcon::StateCompare $app $type]
1351
1352    ## Init Interp
1353    ##
1354    $w add separator
1355    $w add command -label "Send tkcon Commands" \
1356	    -command [list ::tkcon::InitInterp $app $type]
1357}
1358
1359## ::tkcon::PkgMenu - fill in  in the applications sub-menu
1360## with a list of all the applications that currently exist.
1361##
1362proc ::tkcon::PkgMenu {m app type} {
1363    # just in case stuff has been added to the auto_path
1364    # we have to make sure that the errorInfo doesn't get screwed up
1365    EvalAttached {
1366	set __tkcon_error $errorInfo
1367	catch {package require bogus-package-name}
1368	set errorInfo ${__tkcon_error}
1369	unset __tkcon_error
1370    }
1371    $m delete 0 end
1372    foreach pkg [EvalAttached [list info loaded {}]] {
1373	set loaded([lindex $pkg 1]) [package provide $pkg]
1374    }
1375    foreach pkg [lremove [EvalAttached {package names}] Tcl] {
1376	set version [EvalAttached [list package provide $pkg]]
1377	if {[string compare {} $version]} {
1378	    set loaded($pkg) $version
1379	} elseif {![info exists loaded($pkg)]} {
1380	    set loadable($pkg) [list package require $pkg]
1381	}
1382    }
1383    foreach pkg [EvalAttached {info loaded}] {
1384	set pkg [lindex $pkg 1]
1385	if {![info exists loaded($pkg)] && ![info exists loadable($pkg)]} {
1386	    set loadable($pkg) [list load {} $pkg]
1387	}
1388    }
1389    set npkg 0
1390    foreach pkg [lsort -dictionary [array names loadable]] {
1391	foreach v [EvalAttached [list package version $pkg]] {
1392	    set brkcol [expr {([incr npkg]%16)==0}]
1393	    $m add command -label "Load $pkg ($v)" -command \
1394		    "::tkcon::EvalOther [list $app] $type $loadable($pkg) $v" \
1395		    -columnbreak $brkcol
1396	}
1397    }
1398    if {[info exists loaded] && [info exists loadable]} {
1399	$m add separator
1400    }
1401    foreach pkg [lsort -dictionary [array names loaded]] {
1402	$m add command -label "${pkg}$loaded($pkg) Loaded" -state disabled
1403    }
1404}
1405
1406## ::tkcon::AttachMenu - fill in  in the applications sub-menu
1407## with a list of all the applications that currently exist.
1408##
1409proc ::tkcon::AttachMenu m {
1410    variable OPT
1411    variable PRIV
1412
1413    array set interps [set tmp [Interps]]
1414    foreach {i j} $tmp { set tknames($j) {} }
1415
1416    $m delete 0 end
1417    set cmd {::tkcon::Prompt \n [::tkcon::CmdGet $::tkcon::PRIV(console)]}
1418    $m add radio -label {None (use local slave) } -accel Ctrl-1 \
1419	    -variable ::tkcon::PRIV(app) \
1420	    -value [concat $::tkcon::PRIV(name) $::tkcon::OPT(exec)] \
1421	    -command "::tkcon::Attach {}; $cmd"
1422    $m add separator
1423    $m add command -label "Foreign Tk Interpreters" -state disabled
1424    foreach i [lsort [lremove [winfo interps] [array names tknames]]] {
1425	$m add radio -label $i -variable ::tkcon::PRIV(app) -value $i \
1426		-command "::tkcon::Attach [list $i] interp; $cmd"
1427    }
1428    $m add separator
1429
1430    $m add command -label "tkcon Interpreters" -state disabled
1431    foreach i [lsort [array names interps]] {
1432	if {[string match {} $interps($i)]} { set interps($i) "no Tk" }
1433	if {[regexp {^Slave[0-9]+} $i]} {
1434	    set opts [list -label "$i ($interps($i))" \
1435		    -variable ::tkcon::PRIV(app) -value $i \
1436		    -command "::tkcon::Attach [list $i] slave; $cmd"]
1437	    if {[string match $PRIV(name) $i]} {
1438		append opts " -accel Ctrl-2"
1439	    }
1440	    eval $m add radio $opts
1441	} else {
1442	    set name [concat Main $i]
1443	    if {[string match Main $name]} {
1444		$m add radio -label "$name ($interps($i))" -accel Ctrl-3 \
1445			-variable ::tkcon::PRIV(app) -value Main \
1446			-command "::tkcon::Attach [list $name] slave; $cmd"
1447	    } else {
1448		$m add radio -label "$name ($interps($i))" \
1449			-variable ::tkcon::PRIV(app) -value $i \
1450			-command "::tkcon::Attach [list $name] slave; $cmd"
1451	    }
1452	}
1453    }
1454}
1455
1456## Displays Cascaded Menu
1457##
1458proc ::tkcon::DisplayMenu m {
1459    $m delete 0 end
1460    set cmd {::tkcon::Prompt \n [::tkcon::CmdGet $::tkcon::PRIV(console)]}
1461
1462    $m add command -label "New Display" -command ::tkcon::NewDisplay
1463    foreach disp [Display] {
1464	$m add separator
1465	$m add command -label $disp -state disabled
1466	set res [Display $disp]
1467	set win [lindex $res 0]
1468	foreach i [lsort [lindex $res 1]] {
1469	    $m add radio -label $i -variable ::tkcon::PRIV(app) -value $i \
1470		    -command "::tkcon::Attach [list $i] [list dpy:$win]; $cmd"
1471	}
1472    }
1473}
1474
1475## Sockets Cascaded Menu
1476##
1477proc ::tkcon::SocketMenu m {
1478    $m delete 0 end
1479    set cmd {::tkcon::Prompt \n [::tkcon::CmdGet $::tkcon::PRIV(console)]}
1480
1481    $m add command -label "Create Connection" \
1482	    -command "::tkcon::NewSocket; $cmd"
1483    foreach sock [file channels sock*] {
1484	$m add radio -label $sock -variable ::tkcon::PRIV(app) -value $sock \
1485		-command "::tkcon::Attach $sock socket; $cmd"
1486    }
1487}
1488
1489## Namepaces Cascaded Menu
1490##
1491proc ::tkcon::NamespaceMenu m {
1492    variable PRIV
1493    variable OPT
1494
1495    $m delete 0 end
1496    if {($PRIV(deadapp) || [string match socket $PRIV(apptype)] || \
1497	    ($OPT(nontcl) && [string match interp $PRIV(apptype)]))} {
1498	$m add command -label "No Namespaces" -state disabled
1499	return
1500    }
1501
1502    ## Same command as for ::tkcon::AttachMenu items
1503    set cmd {::tkcon::Prompt \n [::tkcon::CmdGet $::tkcon::PRIV(console)]}
1504
1505    set names [lsort [Namespaces ::]]
1506    if {[llength $names] > $OPT(maxmenu)} {
1507	$m add command -label "Attached to $PRIV(namesp)" -state disabled
1508	$m add command -label "List Namespaces" \
1509		-command [list ::tkcon::NamespacesList $names]
1510    } else {
1511	foreach i $names {
1512	    if {[string match :: $i]} {
1513		$m add radio -label "Main" -value $i \
1514			-variable ::tkcon::PRIV(namesp) \
1515			-command "::tkcon::AttachNamespace [list $i]; $cmd"
1516	    } else {
1517		$m add radio -label $i -value $i \
1518			-variable ::tkcon::PRIV(namesp) \
1519			-command "::tkcon::AttachNamespace [list $i]; $cmd"
1520	    }
1521	}
1522    }
1523}
1524
1525## Namepaces List
1526##
1527proc ::tkcon::NamespacesList {names} {
1528    variable PRIV
1529
1530    set f $PRIV(base).namespaces
1531    catch {destroy $f}
1532    toplevel $f
1533    listbox $f.names -width 30 -height 15 -selectmode single \
1534	    -yscrollcommand [list $f.scrollv set] \
1535	    -xscrollcommand [list $f.scrollh set]
1536    scrollbar $f.scrollv -command [list $f.names yview]
1537    scrollbar $f.scrollh -command [list $f.names xview] -orient horizontal
1538    frame $f.buttons
1539    button $f.cancel -text "Cancel" -command [list destroy $f]
1540
1541    grid $f.names $f.scrollv -sticky nesw
1542    grid $f.scrollh -sticky ew
1543    grid $f.buttons -sticky nesw
1544    grid $f.cancel -in $f.buttons -pady 6
1545
1546    grid columnconfigure $f 0 -weight 1
1547    grid rowconfigure $f  0 -weight 1
1548    #fill the listbox
1549    foreach i $names {
1550	if {[string match :: $i]} {
1551	    $f.names insert 0 Main
1552	} else {
1553	    $f.names insert end $i
1554	}
1555    }
1556    #Bindings
1557    bind $f.names <Double-1> {
1558	## Catch in case the namespace disappeared on us
1559	catch { ::tkcon::AttachNamespace [%W get [%W nearest %y]] }
1560	::tkcon::Prompt "\n" [::tkcon::CmdGet $::tkcon::PRIV(console)]
1561	destroy [winfo toplevel %W]
1562    }
1563}
1564
1565# ::tkcon::XauthSecure --
1566#
1567#   This removes all the names in the xhost list, and secures
1568#   the display for Tk send commands.  Of course, this prevents
1569#   what might have been otherwise allowable X connections
1570#
1571# Arguments:
1572#   none
1573# Results:
1574#   Returns nothing
1575#
1576proc ::tkcon::XauthSecure {} {
1577    global tcl_platform
1578
1579    if {[string compare unix $tcl_platform(platform)]} {
1580	# This makes no sense outside of Unix
1581	return
1582    }
1583    set hosts [exec xhost]
1584    # the first line is info only
1585    foreach host [lrange [split $hosts \n] 1 end] {
1586	exec xhost -$host
1587    }
1588    exec xhost -
1589    tk_messageBox -title "Xhost secured" -message "Xhost secured" -icon info
1590}
1591
1592## ::tkcon::FindBox - creates minimal dialog interface to ::tkcon::Find
1593# ARGS:	w	- text widget
1594#	str	- optional seed string for ::tkcon::PRIV(find)
1595##
1596proc ::tkcon::FindBox {w {str {}}} {
1597    variable PRIV
1598
1599    set base $PRIV(base).find
1600    if {![winfo exists $base]} {
1601	toplevel $base
1602	wm withdraw $base
1603	wm title $base "tkcon Find"
1604
1605	pack [frame $base.f] -fill x -expand 1
1606	label $base.f.l -text "Find:"
1607	entry $base.f.e -textvariable ::tkcon::PRIV(find)
1608	pack [frame $base.opt] -fill x
1609	checkbutton $base.opt.c -text "Case Sensitive" \
1610		-variable ::tkcon::PRIV(find,case)
1611	checkbutton $base.opt.r -text "Use Regexp" -variable ::tkcon::PRIV(find,reg)
1612	pack $base.f.l -side left
1613	pack $base.f.e $base.opt.c $base.opt.r -side left -fill both -expand 1
1614	pack [frame $base.sep -bd 2 -relief sunken -height 4] -fill x
1615	pack [frame $base.btn] -fill both
1616	button $base.btn.fnd -text "Find" -width 6
1617	button $base.btn.clr -text "Clear" -width 6
1618	button $base.btn.dis -text "Dismiss" -width 6
1619	eval pack [winfo children $base.btn] -padx 4 -pady 2 \
1620		-side left -fill both
1621
1622	focus $base.f.e
1623
1624	bind $base.f.e <Return> [list $base.btn.fnd invoke]
1625	bind $base.f.e <Escape> [list $base.btn.dis invoke]
1626    }
1627    $base.btn.fnd config -command "::tkcon::Find [list $w] \$::tkcon::PRIV(find) \
1628	    -case \$::tkcon::PRIV(find,case) -reg \$::tkcon::PRIV(find,reg)"
1629    $base.btn.clr config -command "
1630    [list $w] tag remove find 1.0 end
1631    set ::tkcon::PRIV(find) {}
1632    "
1633    $base.btn.dis config -command "
1634    [list $w] tag remove find 1.0 end
1635    wm withdraw [list $base]
1636    "
1637    if {[string compare {} $str]} {
1638	set PRIV(find) $str
1639	$base.btn.fnd invoke
1640    }
1641
1642    if {[string compare normal [wm state $base]]} {
1643	wm deiconify $base
1644    } else { raise $base }
1645    $base.f.e select range 0 end
1646}
1647
1648## ::tkcon::Find - searches in text widget $w for $str and highlights it
1649## If $str is empty, it just deletes any highlighting
1650# ARGS: w	- text widget
1651#	str	- string to search for
1652#	-case	TCL_BOOLEAN	whether to be case sensitive	DEFAULT: 0
1653#	-regexp	TCL_BOOLEAN	whether to use $str as pattern	DEFAULT: 0
1654##
1655proc ::tkcon::Find {w str args} {
1656    $w tag remove find 1.0 end
1657    set truth {^(1|yes|true|on)$}
1658    set opts  {}
1659    foreach {key val} $args {
1660	switch -glob -- $key {
1661	    -c* { if {[regexp -nocase $truth $val]} { set case 1 } }
1662	    -r* { if {[regexp -nocase $truth $val]} { lappend opts -regexp } }
1663	    default { return -code error "Unknown option $key" }
1664	}
1665    }
1666    if {![info exists case]} { lappend opts -nocase }
1667    if {[string match {} $str]} return
1668    $w mark set findmark 1.0
1669    while {[string compare {} [set ix [eval $w search $opts -count numc -- \
1670	    [list $str] findmark end]]]} {
1671	$w tag add find $ix ${ix}+${numc}c
1672	$w mark set findmark ${ix}+1c
1673    }
1674    $w tag configure find -background $::tkcon::COLOR(blink)
1675    catch {$w see find.first}
1676    return [expr {[llength [$w tag ranges find]]/2}]
1677}
1678
1679## ::tkcon::Attach - called to attach tkcon to an interpreter
1680# ARGS:	name	- application name to which tkcon sends commands
1681#		  This is either a slave interperter name or tk appname.
1682#	type	- (slave|interp) type of interpreter we're attaching to
1683#		  slave means it's a tkcon interpreter
1684#		  interp means we'll need to 'send' to it.
1685# Results:	::tkcon::EvalAttached is recreated to evaluate in the
1686#		appropriate interpreter
1687##
1688proc ::tkcon::Attach {{name <NONE>} {type slave}} {
1689    variable PRIV
1690    variable OPT
1691
1692    if {[llength [info level 0]] == 1} {
1693	# no args were specified, return the attach info instead
1694	if {[string match {} $PRIV(appname)]} {
1695	    return [list [concat $PRIV(name) $OPT(exec)] $PRIV(apptype)]
1696	} else {
1697	    return [list $PRIV(appname) $PRIV(apptype)]
1698	}
1699    }
1700    set path [concat $PRIV(name) $OPT(exec)]
1701
1702    set PRIV(displayWin) .
1703    if {[string match namespace $type]} {
1704	return [uplevel 1 ::tkcon::AttachNamespace $name]
1705    } elseif {[string match dpy:* $type]} {
1706	set PRIV(displayWin) [string range $type 4 end]
1707    } elseif {[string match sock* $type]} {
1708	global tcl_version
1709	if {[catch {eof $name} res]} {
1710	    return -code error "No known channel \"$name\""
1711	} elseif {$res} {
1712	    catch {close $name}
1713	    return -code error "Channel \"$name\" returned EOF"
1714	}
1715	set app $name
1716	set type socket
1717    } elseif {[string compare {} $name]} {
1718	array set interps [Interps]
1719	if {[string match {[Mm]ain} [lindex $name 0]]} {
1720	    set name [lrange $name 1 end]
1721	}
1722	if {[string match $path $name]} {
1723	    set name {}
1724	    set app $path
1725	    set type slave
1726	} elseif {[info exists interps($name)]} {
1727	    if {[string match {} $name]} { set name Main; set app Main }
1728	    set type slave
1729	} elseif {[interp exists $name]} {
1730	    set name [concat $PRIV(name) $name]
1731	    set type slave
1732	} elseif {[interp exists [concat $OPT(exec) $name]]} {
1733	    set name [concat $path $name]
1734	    set type slave
1735	} elseif {[lsearch -exact [winfo interps] $name] > -1} {
1736	    if {[EvalSlave info exists tk_library] \
1737		    && [string match $name [EvalSlave tk appname]]} {
1738		set name {}
1739		set app $path
1740		set type slave
1741	    } elseif {[set i [lsearch -exact \
1742		    [Main set ::tkcon::PRIV(interps)] $name]] != -1} {
1743		set name [lindex [Main set ::tkcon::PRIV(slaves)] $i]
1744		if {[string match {[Mm]ain} $name]} { set app Main }
1745		set type slave
1746	    } else {
1747		set type interp
1748	    }
1749	} else {
1750	    return -code error "No known interpreter \"$name\""
1751	}
1752    } else {
1753	set app $path
1754    }
1755    if {![info exists app]} { set app $name }
1756    array set PRIV [list app $app appname $name apptype $type deadapp 0]
1757
1758    ## ::tkcon::EvalAttached - evaluates the args in the attached interp
1759    ## args should be passed to this procedure as if they were being
1760    ## passed to the 'eval' procedure.  This procedure is dynamic to
1761    ## ensure evaluation occurs in the right interp.
1762    # ARGS:	args	- the command and args to evaluate
1763    ##
1764    switch -glob -- $type {
1765	slave {
1766	    if {[string match {} $name]} {
1767		interp alias {} ::tkcon::EvalAttached {} \
1768			::tkcon::EvalSlave uplevel \#0
1769	    } elseif {[string match Main $PRIV(app)]} {
1770		interp alias {} ::tkcon::EvalAttached {} ::tkcon::Main
1771	    } elseif {[string match $PRIV(name) $PRIV(app)]} {
1772		interp alias {} ::tkcon::EvalAttached {} uplevel \#0
1773	    } else {
1774		interp alias {} ::tkcon::EvalAttached {} \
1775			::tkcon::Slave $::tkcon::PRIV(app)
1776	    }
1777	}
1778	sock* {
1779	    interp alias {} ::tkcon::EvalAttached {} \
1780		    ::tkcon::EvalSlave uplevel \#0
1781	    # The file event will just puts whatever data is found
1782	    # into the interpreter
1783	    fconfigure $name -buffering line -blocking 0
1784	    fileevent $name readable ::tkcon::EvalSocketEvent
1785	}
1786	dpy:* -
1787	interp {
1788	    if {$OPT(nontcl)} {
1789		interp alias {} ::tkcon::EvalAttached {} ::tkcon::EvalSlave
1790		set PRIV(namesp) ::
1791	    } else {
1792		interp alias {} ::tkcon::EvalAttached {} ::tkcon::EvalSend
1793	    }
1794	}
1795	default {
1796	    return -code error "[lindex [info level 0] 0] did not specify\
1797		    a valid type: must be slave or interp"
1798	}
1799    }
1800    if {[string match slave $type] || \
1801	    (!$OPT(nontcl) && [regexp {^(interp|dpy)} $type])} {
1802	set PRIV(namesp) ::
1803    }
1804    set PRIV(StatusAttach) "$PRIV(app) ($PRIV(apptype))"
1805    return
1806}
1807
1808## ::tkcon::AttachNamespace - called to attach tkcon to a namespace
1809# ARGS:	name	- namespace name in which tkcon should eval commands
1810# Results:	::tkcon::EvalAttached will be modified
1811##
1812proc ::tkcon::AttachNamespace { name } {
1813    variable PRIV
1814    variable OPT
1815
1816    if {($OPT(nontcl) && [string match interp $PRIV(apptype)]) \
1817	    || [string match socket $PRIV(apptype)] \
1818	    || $PRIV(deadapp)} {
1819	return -code error "can't attach to namespace in attached environment"
1820    }
1821    if {[string match Main $name]} {set name ::}
1822    if {[string compare {} $name] && \
1823	    [lsearch [Namespaces ::] $name] == -1} {
1824	return -code error "No known namespace \"$name\""
1825    }
1826    if {[regexp {^(|::)$} $name]} {
1827	## If name=={} || ::, we want the primary namespace
1828	set alias [interp alias {} ::tkcon::EvalAttached]
1829	if {[string match ::tkcon::EvalNamespace* $alias]} {
1830	    eval [list interp alias {} ::tkcon::EvalAttached {}] \
1831		    [lindex $alias 1]
1832	}
1833	set name ::
1834    } else {
1835	interp alias {} ::tkcon::EvalAttached {} ::tkcon::EvalNamespace \
1836		[interp alias {} ::tkcon::EvalAttached] [list $name]
1837    }
1838    set PRIV(namesp) $name
1839    set PRIV(StatusAttach) "$PRIV(app) $PRIV(namesp) ($PRIV(apptype))"
1840}
1841
1842## ::tkcon::NewSocket - called to create a socket to connect to
1843# ARGS:	none
1844# Results:	It will create a socket, and attach if requested
1845##
1846proc ::tkcon::NewSocket {} {
1847    variable PRIV
1848
1849    set t $PRIV(base).newsock
1850    if {![winfo exists $t]} {
1851	toplevel $t
1852	wm withdraw $t
1853	wm title $t "tkcon Create Socket"
1854	label $t.lhost -text "Host: "
1855	entry $t.host -width 20
1856	label $t.lport -text "Port: "
1857	entry $t.port -width 4
1858	button $t.ok -text "OK" -command {set ::tkcon::PRIV(grab) 1}
1859	bind $t.host <Return> [list focus $t.port]
1860	bind $t.port <Return> [list focus $t.ok]
1861	bind $t.ok   <Return> [list $t.ok invoke]
1862	grid $t.lhost $t.host $t.lport $t.port -sticky ew
1863	grid $t.ok	-	-	-	 -sticky ew
1864	grid columnconfig $t 1 -weight 1
1865	grid rowconfigure $t 1 -weight 1
1866	wm transient $t $PRIV(root)
1867	wm geometry $t +[expr {([winfo screenwidth $t]-[winfo \
1868		reqwidth $t]) / 2}]+[expr {([winfo \
1869		screenheight $t]-[winfo reqheight $t]) / 2}]
1870    }
1871    #$t.host delete 0 end
1872    #$t.port delete 0 end
1873    wm deiconify $t
1874    raise $t
1875    grab $t
1876    focus $t.host
1877    vwait ::tkcon::PRIV(grab)
1878    grab release $t
1879    wm withdraw $t
1880    set host [$t.host get]
1881    set port [$t.port get]
1882    if {$host == ""} { return }
1883    if {[catch {
1884	set sock [socket $host $port]
1885    } err]} {
1886	tk_messageBox -title "Socket Connection Error" \
1887		-message "Unable to connect to \"$host:$port\":\n$err" \
1888		-icon error -type ok
1889    } else {
1890	Attach $sock socket
1891    }
1892}
1893
1894## ::tkcon::Load - sources a file into the console
1895## The file is actually sourced in the currently attached's interp
1896# ARGS:	fn	- (optional) filename to source in
1897# Returns:	selected filename ({} if nothing was selected)
1898##
1899proc ::tkcon::Load { {fn ""} } {
1900    set types {
1901	{{Tcl Files}	{.tcl .tk}}
1902	{{Text Files}	{.txt}}
1903	{{All Files}	*}
1904    }
1905    if {
1906	[string match {} $fn] &&
1907	([catch {tk_getOpenFile -filetypes $types \
1908	    -title "Source File"} fn] || [string match {} $fn])
1909    } { return }
1910    EvalAttached [list source $fn]
1911}
1912
1913## ::tkcon::Save - saves the console or other widget buffer to a file
1914## This does not eval in a slave because it's not necessary
1915# ARGS:	w	- console text widget
1916# 	fn	- (optional) filename to save to
1917##
1918proc ::tkcon::Save { {fn ""} {type ""} {opt ""} {mode w} } {
1919    variable PRIV
1920
1921    if {![regexp -nocase {^(all|history|stdin|stdout|stderr|widget)$} $type]} {
1922	array set s { 0 All 1 History 2 Stdin 3 Stdout 4 Stderr 5 Cancel }
1923	## Allow user to specify what kind of stuff to save
1924	set type [tk_dialog $PRIV(base).savetype "Save Type" \
1925		"What part of the text do you want to save?" \
1926		questhead 0 $s(0) $s(1) $s(2) $s(3) $s(4) $s(5)]
1927	if {$type == 5 || $type == -1} return
1928	set type $s($type)
1929    }
1930    if {[string match {} $fn]} {
1931	set types {
1932	    {{Tcl Files}	{.tcl .tk}}
1933	    {{Text Files}	{.txt}}
1934	    {{All Files}	*}
1935	}
1936	if {[catch {tk_getSaveFile -defaultextension .tcl -filetypes $types \
1937		-title "Save $type"} fn] || [string match {} $fn]} return
1938    }
1939    set type [string tolower $type]
1940    switch $type {
1941	stdin -	stdout - stderr {
1942	    set data {}
1943	    foreach {first last} [$PRIV(console) tag ranges $type] {
1944		lappend data [$PRIV(console) get $first $last]
1945	    }
1946	    set data [join $data \n]
1947	}
1948	history		{ set data [tkcon history] }
1949	all - default	{ set data [$PRIV(console) get 1.0 end-1c] }
1950	widget		{
1951	    set data [$opt get 1.0 end-1c]
1952	}
1953    }
1954    if {[catch {open $fn $mode} fid]} {
1955	return -code error "Save Error: Unable to open '$fn' for writing\n$fid"
1956    }
1957    puts -nonewline $fid $data
1958    close $fid
1959}
1960
1961## ::tkcon::MainInit
1962## This is only called for the main interpreter to include certain procs
1963## that we don't want to include (or rather, just alias) in slave interps.
1964##
1965proc ::tkcon::MainInit {} {
1966    variable PRIV
1967
1968    if {![info exists PRIV(slaves)]} {
1969	array set PRIV [list slave 0 slaves Main name {} \
1970		interps [list [tk appname]]]
1971    }
1972    interp alias {} ::tkcon::Main {} ::tkcon::InterpEval Main
1973    interp alias {} ::tkcon::Slave {} ::tkcon::InterpEval
1974
1975    proc ::tkcon::GetSlaveNum {} {
1976	set i -1
1977	while {[interp exists Slave[incr i]]} {
1978	    # oh my god, an empty loop!
1979	}
1980	return $i
1981    }
1982
1983    ## ::tkcon::New - create new console window
1984    ## Creates a slave interpreter and sources in this script.
1985    ## All other interpreters also get a command to eval function in the
1986    ## new interpreter.
1987    ##
1988    proc ::tkcon::New {} {
1989	variable PRIV
1990	global argv0 argc argv
1991
1992	set tmp [interp create Slave[GetSlaveNum]]
1993	lappend PRIV(slaves) $tmp
1994	load {} Tk $tmp
1995	lappend PRIV(interps) [$tmp eval [list tk appname \
1996		"[tk appname] $tmp"]]
1997	if {[info exist argv0]} {$tmp eval [list set argv0 $argv0]}
1998	$tmp eval set argc $argc
1999	$tmp eval [list set argv $argv]
2000	$tmp eval [list namespace eval ::tkcon {}]
2001	$tmp eval [list set ::tkcon::PRIV(name) $tmp]
2002	$tmp eval [list set ::tkcon::PRIV(SCRIPT) $::tkcon::PRIV(SCRIPT)]
2003	$tmp alias exit				::tkcon::Exit $tmp
2004	$tmp alias ::tkcon::Destroy		::tkcon::Destroy $tmp
2005	$tmp alias ::tkcon::New			::tkcon::New
2006	$tmp alias ::tkcon::Main		::tkcon::InterpEval Main
2007	$tmp alias ::tkcon::Slave		::tkcon::InterpEval
2008	$tmp alias ::tkcon::Interps		::tkcon::Interps
2009	$tmp alias ::tkcon::NewDisplay		::tkcon::NewDisplay
2010	$tmp alias ::tkcon::Display		::tkcon::Display
2011	$tmp alias ::tkcon::StateCheckpoint	::tkcon::StateCheckpoint
2012	$tmp alias ::tkcon::StateCleanup	::tkcon::StateCleanup
2013	$tmp alias ::tkcon::StateCompare	::tkcon::StateCompare
2014	$tmp alias ::tkcon::StateRevert		::tkcon::StateRevert
2015	$tmp eval {
2016	    if [catch {source -rsrc tkcon}] { source $::tkcon::PRIV(SCRIPT) }
2017	}
2018	return $tmp
2019    }
2020
2021    ## ::tkcon::Exit - full exit OR destroy slave console
2022    ## This proc should only be called in the main interpreter from a slave.
2023    ## The master determines whether we do a full exit or just kill the slave.
2024    ##
2025    proc ::tkcon::Exit {slave args} {
2026	variable PRIV
2027	variable OPT
2028
2029	## Slave interpreter exit request
2030	if {[string match exit $OPT(slaveexit)]} {
2031	    ## Only exit if it specifically is stated to do so
2032	    uplevel 1 exit $args
2033	}
2034	## Otherwise we will delete the slave interp and associated data
2035	set name [InterpEval $slave]
2036	set PRIV(interps) [lremove $PRIV(interps) [list $name]]
2037	set PRIV(slaves)  [lremove $PRIV(slaves) [list $slave]]
2038	interp delete $slave
2039	StateCleanup $slave
2040	return
2041    }
2042
2043    ## ::tkcon::Destroy - destroy console window
2044    ## This proc should only be called by the main interpreter.  If it is
2045    ## called from there, it will ask before exiting tkcon.  All others
2046    ## (slaves) will just have their slave interpreter deleted, closing them.
2047    ##
2048    proc ::tkcon::Destroy {{slave {}}} {
2049	variable PRIV
2050
2051	if {[string match {} $slave]} {
2052	    ## Main interpreter close request
2053	    if {[tk_dialog $PRIV(base).destroyme {Quit tkcon?} \
2054		    {Closing the Main console will quit tkcon} \
2055		    warning 0 "Don't Quit" "Quit tkcon"]} exit
2056	} else {
2057	    ## Slave interpreter close request
2058	    set name [InterpEval $slave]
2059	    set PRIV(interps) [lremove $PRIV(interps) [list $name]]
2060	    set PRIV(slaves)  [lremove $PRIV(slaves) [list $slave]]
2061	    interp delete $slave
2062	}
2063	StateCleanup $slave
2064	return
2065    }
2066
2067    ## We want to do a couple things before exiting...
2068    if {[catch {rename ::exit ::tkcon::FinalExit} err]} {
2069	puts stderr "tkcon might panic:\n$err"
2070    }
2071    proc ::exit args {
2072	if {$::tkcon::OPT(usehistory)} {
2073	    if {[catch {open $::tkcon::PRIV(histfile) w} fid]} {
2074		puts stderr "unable to save history file:\n$fid"
2075		# pause a moment, because we are about to die finally...
2076		after 1000
2077	    } else {
2078		set max [::tkcon::EvalSlave history nextid]
2079		set id [expr {$max - $::tkcon::OPT(history)}]
2080		if {$id < 1} { set id 1 }
2081		## FIX: This puts history in backwards!!
2082		while {($id < $max) && \
2083			![catch {::tkcon::EvalSlave history event $id} cmd]} {
2084		    if {[string compare {} $cmd]} {
2085			puts $fid "::tkcon::EvalSlave history add [list $cmd]"
2086		    }
2087		    incr id
2088		}
2089		close $fid
2090	    }
2091	}
2092	uplevel 1 ::tkcon::FinalExit $args
2093    }
2094
2095    ## ::tkcon::InterpEval - passes evaluation to another named interpreter
2096    ## If the interpreter is named, but no args are given, it returns the
2097    ## [tk appname] of that interps master (not the associated eval slave).
2098    ##
2099    proc ::tkcon::InterpEval {{slave {}} args} {
2100	variable PRIV
2101
2102	if {[string match {} $slave]} {
2103	    return $PRIV(slaves)
2104	} elseif {[string match {[Mm]ain} $slave]} {
2105	    set slave {}
2106	}
2107	if {[llength $args]} {
2108	    return [interp eval $slave uplevel \#0 $args]
2109	} else {
2110	    return [interp eval $slave tk appname]
2111	}
2112    }
2113
2114    proc ::tkcon::Interps {{ls {}} {interp {}}} {
2115	if {[string match {} $interp]} { lappend ls {} [tk appname] }
2116	foreach i [interp slaves $interp] {
2117	    if {[string compare {} $interp]} { set i "$interp $i" }
2118	    if {[string compare {} [interp eval $i package provide Tk]]} {
2119		lappend ls $i [interp eval $i tk appname]
2120	    } else {
2121		lappend ls $i {}
2122	    }
2123	    set ls [Interps $ls $i]
2124	}
2125	return $ls
2126    }
2127
2128    proc ::tkcon::Display {{disp {}}} {
2129	variable DISP
2130
2131	set res {}
2132	if {$disp != ""} {
2133	    if {![info exists DISP($disp)]} { return }
2134	    return [list $DISP($disp) [winfo interps -displayof $DISP($disp)]]
2135	}
2136	return [lsort -dictionary [array names DISP]]
2137    }
2138
2139    proc ::tkcon::NewDisplay {} {
2140	variable PRIV
2141	variable DISP
2142
2143	set t $PRIV(base).newdisp
2144	if {![winfo exists $t]} {
2145	    toplevel $t
2146	    wm withdraw $t
2147	    wm title $t "tkcon Attach to Display"
2148	    label $t.gets -text "New Display: "
2149	    entry $t.data -width 32
2150	    button $t.ok -text "OK" -command {set ::tkcon::PRIV(grab) 1}
2151	    bind $t.data <Return> [list $t.ok invoke]
2152	    bind $t.ok   <Return> [list $t.ok invoke]
2153	    grid $t.gets $t.data -sticky ew
2154	    grid $t.ok   -	 -sticky ew
2155	    grid columnconfig $t 1 -weight 1
2156	    grid rowconfigure $t 1 -weight 1
2157	    wm transient $t $PRIV(root)
2158	    wm geometry $t +[expr {([winfo screenwidth $t]-[winfo \
2159		    reqwidth $t]) / 2}]+[expr {([winfo \
2160		    screenheight $t]-[winfo reqheight $t]) / 2}]
2161	}
2162	$t.data delete 0 end
2163	wm deiconify $t
2164	raise $t
2165	grab $t
2166	focus $t.data
2167	vwait ::tkcon::PRIV(grab)
2168	grab release $t
2169	wm withdraw $t
2170	set disp [$t.data get]
2171	if {$disp == ""} { return }
2172	regsub -all {\.} [string tolower $disp] ! dt
2173	set dt $PRIV(base).$dt
2174	destroy $dt
2175	if {[catch {
2176	    toplevel $dt -screen $disp
2177	    set interps [winfo interps -displayof $dt]
2178	    if {![llength $interps]} {
2179		error "No other Tk interpreters on $disp"
2180	    }
2181	    send -displayof $dt [lindex $interps 0] [list info tclversion]
2182	} err]} {
2183	    global env
2184	    if {[info exists env(DISPLAY)]} {
2185		set myd $env(DISPLAY)
2186	    } else {
2187		set myd "myDisplay:0"
2188	    }
2189	    tk_messageBox -title "Display Connection Error" \
2190		    -message "Unable to connect to \"$disp\":\n$err\
2191		    \nMake sure you have xauth-based permissions\
2192		    (xauth add $myd . `mcookie`), and xhost is disabled\
2193		    (xhost -) on \"$disp\"" \
2194		    -icon error -type ok
2195	    destroy $dt
2196	    return
2197	}
2198	set DISP($disp) $dt
2199	wm withdraw $dt
2200	bind $dt <Destroy> [subst {catch {unset ::tkcon::DISP($disp)}}]
2201	tk_messageBox -title "$disp Connection" \
2202		-message "Connected to \"$disp\", found:\n[join $interps \n]" \
2203		-type ok
2204    }
2205
2206    ##
2207    ## The following state checkpoint/revert procedures are very sketchy
2208    ## and prone to problems.  They do not track modifications to currently
2209    ## existing procedures/variables, and they can really screw things up
2210    ## if you load in libraries (especially Tk) between checkpoint and
2211    ## revert.  Only with this knowledge in mind should you use these.
2212    ##
2213
2214    ## ::tkcon::StateCheckpoint - checkpoints the current state of the system
2215    ## This allows you to return to this state with ::tkcon::StateRevert
2216    # ARGS:
2217    ##
2218    proc ::tkcon::StateCheckpoint {app type} {
2219	variable CPS
2220	variable PRIV
2221
2222	if {[info exists CPS($type,$app,cmd)] && \
2223		[tk_dialog $PRIV(base).warning "Overwrite Previous State?" \
2224		"Are you sure you want to lose previously checkpointed\
2225		state of $type \"$app\"?" questhead 1 "Do It" "Cancel"]} return
2226	set CPS($type,$app,cmd) [EvalOther $app $type info commands *]
2227	set CPS($type,$app,var) [EvalOther $app $type info vars *]
2228	return
2229    }
2230
2231    ## ::tkcon::StateCompare - compare two states and output difference
2232    # ARGS:
2233    ##
2234    proc ::tkcon::StateCompare {app type {verbose 0}} {
2235	variable CPS
2236	variable PRIV
2237	variable OPT
2238	variable COLOR
2239
2240	if {![info exists CPS($type,$app,cmd)]} {
2241	    return -code error \
2242		    "No previously checkpointed state for $type \"$app\""
2243	}
2244	set w $PRIV(base).compare
2245	if {[winfo exists $w]} {
2246	    $w.text config -state normal
2247	    $w.text delete 1.0 end
2248	} else {
2249	    toplevel $w
2250	    frame $w.btn
2251	    scrollbar $w.sy -takefocus 0 -bd 1 -command [list $w.text yview]
2252	    text $w.text -yscrollcommand [list $w.sy set] -height 12 \
2253		    -foreground $COLOR(stdin) \
2254		    -background $COLOR(bg) \
2255		    -insertbackground $COLOR(cursor) \
2256		    -font $OPT(font)
2257	    pack $w.btn -side bottom -fill x
2258	    pack $w.sy -side right -fill y
2259	    pack $w.text -fill both -expand 1
2260	    button $w.btn.close -text "Dismiss" -width 11 \
2261		    -command [list destroy $w]
2262	    button $w.btn.check  -text "Recheckpoint" -width 11
2263	    button $w.btn.revert -text "Revert" -width 11
2264	    button $w.btn.expand -text "Verbose" -width 11
2265	    button $w.btn.update -text "Update" -width 11
2266	    pack $w.btn.check $w.btn.revert $w.btn.expand $w.btn.update \
2267		    $w.btn.close -side left -fill x -padx 4 -pady 2 -expand 1
2268	    $w.text tag config red -foreground red
2269	}
2270	wm title $w "Compare State: $type [list $app]"
2271
2272	$w.btn.check config \
2273		-command "::tkcon::StateCheckpoint [list $app] $type; \
2274		::tkcon::StateCompare [list $app] $type $verbose"
2275	$w.btn.revert config \
2276		-command "::tkcon::StateRevert [list $app] $type; \
2277		::tkcon::StateCompare [list $app] $type $verbose"
2278	$w.btn.update config -command [info level 0]
2279	if {$verbose} {
2280	    $w.btn.expand config -text Brief \
2281		    -command [list ::tkcon::StateCompare $app $type 0]
2282	} else {
2283	    $w.btn.expand config -text Verbose \
2284		    -command [list ::tkcon::StateCompare $app $type 1]
2285	}
2286	## Don't allow verbose mode unless 'dump' exists in $app
2287	## We're assuming this is tkcon's dump command
2288	set hasdump [llength [EvalOther $app $type info commands dump]]
2289	if {$hasdump} {
2290	    $w.btn.expand config -state normal
2291	} else {
2292	    $w.btn.expand config -state disabled
2293	}
2294
2295	set cmds [lremove [EvalOther $app $type info commands *] \
2296		$CPS($type,$app,cmd)]
2297	set vars [lremove [EvalOther $app $type info vars *] \
2298		$CPS($type,$app,var)]
2299
2300	if {$hasdump && $verbose} {
2301	    set cmds [EvalOther $app $type eval dump c -nocomplain $cmds]
2302	    set vars [EvalOther $app $type eval dump v -nocomplain $vars]
2303	}
2304	$w.text insert 1.0 "NEW COMMANDS IN \"$app\":\n" red \
2305		$cmds {} "\n\nNEW VARIABLES IN \"$app\":\n" red $vars {}
2306
2307	raise $w
2308	$w.text config -state disabled
2309    }
2310
2311    ## ::tkcon::StateRevert - reverts interpreter to previous state
2312    # ARGS:
2313    ##
2314    proc ::tkcon::StateRevert {app type} {
2315	variable CPS
2316	variable PRIV
2317
2318	if {![info exists CPS($type,$app,cmd)]} {
2319	    return -code error \
2320		    "No previously checkpointed state for $type \"$app\""
2321	}
2322	if {![tk_dialog $PRIV(base).warning "Revert State?" \
2323		"Are you sure you want to revert the state in $type \"$app\"?"\
2324		questhead 1 "Do It" "Cancel"]} {
2325	    foreach i [lremove [EvalOther $app $type info commands *] \
2326		    $CPS($type,$app,cmd)] {
2327		catch {EvalOther $app $type rename $i {}}
2328	    }
2329	    foreach i [lremove [EvalOther $app $type info vars *] \
2330		    $CPS($type,$app,var)] {
2331		catch {EvalOther $app $type unset $i}
2332	    }
2333	}
2334    }
2335
2336    ## ::tkcon::StateCleanup - cleans up state information in master array
2337    #
2338    ##
2339    proc ::tkcon::StateCleanup {args} {
2340	variable CPS
2341
2342	if {![llength $args]} {
2343	    foreach state [array names CPS slave,*] {
2344		if {![interp exists [string range $state 6 end]]} {
2345		    unset CPS($state)
2346		}
2347	    }
2348	} else {
2349	    set app  [lindex $args 0]
2350	    set type [lindex $args 1]
2351	    if {[regexp {^(|slave)$} $type]} {
2352		foreach state [array names CPS "slave,$app\[, \]*"] {
2353		    if {![interp exists [string range $state 6 end]]} {
2354			unset CPS($state)
2355		    }
2356		}
2357	    } else {
2358		catch {unset CPS($type,$app)}
2359	    }
2360	}
2361    }
2362}
2363
2364## ::tkcon::Event - get history event, search if string != {}
2365## look forward (next) if $int>0, otherwise look back (prev)
2366# ARGS:	W	- console widget
2367##
2368proc ::tkcon::Event {int {str {}}} {
2369    if {!$int} return
2370
2371    variable PRIV
2372    set w $PRIV(console)
2373
2374    set nextid [EvalSlave history nextid]
2375    if {[string compare {} $str]} {
2376	## String is not empty, do an event search
2377	set event $PRIV(event)
2378	if {$int < 0 && $event == $nextid} { set PRIV(cmdbuf) $str }
2379	set len [string len $PRIV(cmdbuf)]
2380	incr len -1
2381	if {$int > 0} {
2382	    ## Search history forward
2383	    while {$event < $nextid} {
2384		if {[incr event] == $nextid} {
2385		    $w delete limit end
2386		    $w insert limit $PRIV(cmdbuf)
2387		    break
2388		} elseif {
2389		    ![catch {EvalSlave history event $event} res] &&
2390		    [set p [string first $PRIV(cmdbuf) $res]] > -1
2391		} {
2392		    set p2 [expr {$p + [string length $PRIV(cmdbuf)]}]
2393		    $w delete limit end
2394		    $w insert limit $res
2395		    Blink $w "limit + $p c" "limit + $p2 c"
2396		    break
2397		}
2398	    }
2399	    set PRIV(event) $event
2400	} else {
2401	    ## Search history reverse
2402	    while {![catch {EvalSlave history event [incr event -1]} res]} {
2403		if {[set p [string first $PRIV(cmdbuf) $res]] > -1} {
2404		    set p2 [expr {$p + [string length $PRIV(cmdbuf)]}]
2405		    $w delete limit end
2406		    $w insert limit $res
2407		    set PRIV(event) $event
2408		    Blink $w "limit + $p c" "limit + $p2 c"
2409		    break
2410		}
2411	    }
2412	}
2413    } else {
2414	## String is empty, just get next/prev event
2415	if {$int > 0} {
2416	    ## Goto next command in history
2417	    if {$PRIV(event) < $nextid} {
2418		$w delete limit end
2419		if {[incr PRIV(event)] == $nextid} {
2420		    $w insert limit $PRIV(cmdbuf)
2421		} else {
2422		    $w insert limit [EvalSlave history event $PRIV(event)]
2423		}
2424	    }
2425	} else {
2426	    ## Goto previous command in history
2427	    if {$PRIV(event) == $nextid} {
2428		set PRIV(cmdbuf) [CmdGet $w]
2429	    }
2430	    if {[catch {EvalSlave history event [incr PRIV(event) -1]} res]} {
2431		incr PRIV(event)
2432	    } else {
2433		$w delete limit end
2434		$w insert limit $res
2435	    }
2436	}
2437    }
2438    $w mark set insert end
2439    $w see end
2440}
2441
2442## ::tkcon::ErrorHighlight - magic error highlighting
2443## beware: voodoo included
2444# ARGS:
2445##
2446proc ::tkcon::ErrorHighlight w {
2447    variable COLOR
2448
2449    ## do voodoo here
2450    set app [Attach]
2451    # we have to pull the text out, because text regexps are screwed on \n's.
2452    set info [$w get 1.0 end-1c]
2453    # Check for specific line error in a proc
2454    set exp(proc) "\"(\[^\"\]+)\"\n\[\t \]+\\\(procedure \"(\[^\"\]+)\""
2455    # Check for too few args to a proc
2456    set exp(param) "parameter \"(\[^\"\]+)\" to \"(\[^\"\]+)\""
2457    set start 1.0
2458    while {
2459	[regexp -indices -- $exp(proc) $info junk what cmd] ||
2460	[regexp -indices -- $exp(param) $info junk what cmd]
2461    } {
2462	foreach {w0 w1} $what {c0 c1} $cmd {break}
2463	set what [string range $info $w0 $w1]
2464	set cmd  [string range $info $c0 $c1]
2465	if {[string match *::* $cmd]} {
2466	    set res [uplevel 1 ::tkcon::EvalOther $app namespace eval \
2467		    [list [namespace qualifiers $cmd] \
2468		    [list info procs [namespace tail $cmd]]]]
2469	} else {
2470	    set res [uplevel 1 ::tkcon::EvalOther $app info procs [list $cmd]]
2471	}
2472	if {[llength $res]==1} {
2473	    set tag [UniqueTag $w]
2474	    $w tag add $tag $start+${c0}c $start+1c+${c1}c
2475	    $w tag configure $tag -foreground $COLOR(stdout)
2476	    $w tag bind $tag <Enter> [list $w tag configure $tag -underline 1]
2477	    $w tag bind $tag <Leave> [list $w tag configure $tag -underline 0]
2478	    $w tag bind $tag <ButtonRelease-1> "if {!\$tkPriv(mouseMoved)} \
2479		    {[list edit -attach $app -type proc -find $what -- $cmd]}"
2480	}
2481	set info [string range $info $c1 end]
2482	set start [$w index $start+${c1}c]
2483    }
2484    ## Next stage, check for procs that start a line
2485    set start 1.0
2486    set exp(cmd) "^\"\[^\" \t\n\]+"
2487    while {
2488	[string compare {} [set ix \
2489		[$w search -regexp -count numc -- $exp(cmd) $start end]]]
2490    } {
2491	set start [$w index $ix+${numc}c]
2492	# +1c to avoid the first quote
2493	set cmd [$w get $ix+1c $start]
2494	if {[string match *::* $cmd]} {
2495	    set res [uplevel 1 ::tkcon::EvalOther $app namespace eval \
2496		    [list [namespace qualifiers $cmd] \
2497		    [list info procs [namespace tail $cmd]]]]
2498	} else {
2499	    set res [uplevel 1 ::tkcon::EvalOther $app info procs [list $cmd]]
2500	}
2501	if {[llength $res]==1} {
2502	    set tag [UniqueTag $w]
2503	    $w tag add $tag $ix+1c $start
2504	    $w tag configure $tag -foreground $COLOR(proc)
2505	    $w tag bind $tag <Enter> [list $w tag configure $tag -underline 1]
2506	    $w tag bind $tag <Leave> [list $w tag configure $tag -underline 0]
2507	    $w tag bind $tag <ButtonRelease-1> "if {!\$tkPriv(mouseMoved)} \
2508		    {[list edit -attach $app -type proc -- $cmd]}"
2509	}
2510    }
2511}
2512
2513## tkcon - command that allows control over the console
2514## This always exists in the main interpreter, and is aliased into
2515## other connected interpreters
2516# ARGS:	totally variable, see internal comments
2517##
2518proc tkcon {cmd args} {
2519    global errorInfo
2520
2521    switch -glob -- $cmd {
2522	buf* {
2523	    ## 'buffer' Sets/Query the buffer size
2524	    if {[llength $args]} {
2525		if {[regexp {^[1-9][0-9]*$} $args]} {
2526		    set ::tkcon::OPT(buffer) $args
2527		    # catch in case the console doesn't exist yet
2528		    catch {::tkcon::ConstrainBuffer $::tkcon::PRIV(console) \
2529			    $::tkcon::OPT(buffer)}
2530		} else {
2531		    return -code error "buffer must be a valid integer"
2532		}
2533	    }
2534	    return $::tkcon::OPT(buffer)
2535	}
2536	bg* {
2537	    ## 'bgerror' Brings up an error dialog
2538	    set errorInfo [lindex $args 1]
2539	    bgerror [lindex $args 0]
2540	}
2541	cl* {
2542	    ## 'close' Closes the console
2543	    ::tkcon::Destroy
2544	}
2545	cons* {
2546	    ## 'console' - passes the args to the text widget of the console.
2547	    set result [uplevel 1 $::tkcon::PRIV(console) $args]
2548	    ::tkcon::ConstrainBuffer $::tkcon::PRIV(console) \
2549		    $::tkcon::OPT(buffer)
2550	    return $result
2551	}
2552	congets {
2553	    ## 'congets' a replacement for [gets stdin]
2554	    # Use the 'gets' alias of 'tkcon_gets' command instead of
2555	    # calling the *get* methods directly for best compatability
2556	    if {[llength $args] > 1} {
2557		return -code error "wrong # args: must be \"tkcon congets [pfix]\""
2558	    }
2559	    tkcon show
2560	    set old [bind TkConsole <<TkCon_Eval>>]
2561	    bind TkConsole <<TkCon_Eval>> { set ::tkcon::PRIV(wait) 0 }
2562	    set w $::tkcon::PRIV(console)
2563	    # Make sure to move the limit to get the right data
2564	    $w mark set insert end
2565 	    if {[llength $args]} {
2566		$w mark set limit insert
2567		$w insert end $args
2568	    } else {
2569	        $w mark set limit insert
2570	    }
2571	    $w see end
2572	    vwait ::tkcon::PRIV(wait)
2573	    set line [::tkcon::CmdGet $w]
2574	    $w insert end \n
2575	    bind TkConsole <<TkCon_Eval>> $old
2576	    return $line
2577	}
2578	getc* {
2579	    ## 'getcommand' a replacement for [gets stdin]
2580	    ## This forces a complete command to be input though
2581	    if {[llength $args]} {
2582		return -code error "wrong # args: must be \"tkcon getcommand\""
2583	    }
2584	    tkcon show
2585	    set old [bind TkConsole <<TkCon_Eval>>]
2586	    bind TkConsole <<TkCon_Eval>> { set ::tkcon::PRIV(wait) 0 }
2587	    set w $::tkcon::PRIV(console)
2588	    # Make sure to move the limit to get the right data
2589	    $w mark set insert end
2590	    $w mark set limit insert
2591	    $w see end
2592	    vwait ::tkcon::PRIV(wait)
2593	    set line [::tkcon::CmdGet $w]
2594	    $w insert end \n
2595	    while {![info complete $line] || [regexp {[^\\]\\$} $line]} {
2596		vwait ::tkcon::PRIV(wait)
2597		set line [::tkcon::CmdGet $w]
2598		$w insert end \n
2599		$w see end
2600	    }
2601	    bind TkConsole <<TkCon_Eval>> $old
2602	    return $line
2603	}
2604	get - gets {
2605	    ## 'gets' - a replacement for [gets stdin]
2606	    ## This pops up a text widget to be used for stdin (local grabbed)
2607	    if {[llength $args]} {
2608		return -code error "wrong # args: should be \"tkcon gets\""
2609	    }
2610	    set t $::tkcon::PRIV(base).gets
2611	    if {![winfo exists $t]} {
2612		toplevel $t
2613		wm withdraw $t
2614		wm title $t "tkcon gets stdin request"
2615		label $t.gets -text "\"gets stdin\" request:"
2616		text $t.data -width 32 -height 5 -wrap none \
2617			-xscrollcommand [list $t.sx set] \
2618			-yscrollcommand [list $t.sy set]
2619		scrollbar $t.sx -orient h -takefocus 0 -highlightthick 0 \
2620			-command [list $t.data xview]
2621		scrollbar $t.sy -orient v -takefocus 0 -highlightthick 0 \
2622			-command [list $t.data yview]
2623		button $t.ok -text "OK" -command {set ::tkcon::PRIV(grab) 1}
2624		bind $t.ok <Return> { %W invoke }
2625		grid $t.gets -		-sticky ew
2626		grid $t.data $t.sy	-sticky news
2627		grid $t.sx		-sticky ew
2628		grid $t.ok   -		-sticky ew
2629		grid columnconfig $t 0 -weight 1
2630		grid rowconfig    $t 1 -weight 1
2631		wm transient $t $::tkcon::PRIV(root)
2632		wm geometry $t +[expr {([winfo screenwidth $t]-[winfo \
2633			reqwidth $t]) / 2}]+[expr {([winfo \
2634			screenheight $t]-[winfo reqheight $t]) / 2}]
2635	    }
2636	    $t.data delete 1.0 end
2637	    wm deiconify $t
2638	    raise $t
2639	    grab $t
2640	    focus $t.data
2641	    vwait ::tkcon::PRIV(grab)
2642	    grab release $t
2643	    wm withdraw $t
2644	    return [$t.data get 1.0 end-1c]
2645	}
2646	err* {
2647	    ## Outputs stack caused by last error.
2648	    ## error handling with pizazz (but with pizza would be nice too)
2649	    if {[llength $args]==2} {
2650		set app  [lindex $args 0]
2651		set type [lindex $args 1]
2652		if {[catch {::tkcon::EvalOther $app $type set errorInfo} info]} {
2653		    set info "error getting info from $type $app:\n$info"
2654		}
2655	    } else {
2656		set info $::tkcon::PRIV(errorInfo)
2657	    }
2658	    if {[string match {} $info]} { set info "errorInfo empty" }
2659	    ## If args is empty, the -attach switch just ignores it
2660	    edit -attach $args -type error -- $info
2661	}
2662	fi* {
2663	    ## 'find' string
2664	    ::tkcon::Find $::tkcon::PRIV(console) $args
2665	}
2666	fo* {
2667	    ## 'font' ?fontname? - gets/sets the font of the console
2668	    if {[llength $args]} {
2669		if {[info exists ::tkcon::PRIV(console)] && \
2670			[winfo exists $::tkcon::PRIV(console)]} {
2671		    $::tkcon::PRIV(console) config -font $args
2672		    set ::tkcon::OPT(font) [$::tkcon::PRIV(console) cget -font]
2673		} else {
2674		    set ::tkcon::OPT(font) $args
2675		}
2676	    }
2677	    return $::tkcon::OPT(font)
2678	}
2679	hid* - with* {
2680	    ## 'hide' 'withdraw' - hides the console.
2681	    wm withdraw $::tkcon::PRIV(root)
2682	}
2683	his* {
2684	    ## 'history'
2685	    set sub {\2}
2686	    if {[string match -new* $args]} { append sub "\n"}
2687	    set h [::tkcon::EvalSlave history]
2688	    regsub -all "( *\[0-9\]+  |\t)(\[^\n\]*\n?)" $h $sub h
2689	    return $h
2690	}
2691	ico* {
2692	    ## 'iconify' - iconifies the console with 'iconify'.
2693	    wm iconify $::tkcon::PRIV(root)
2694	}
2695	mas* - eval {
2696	    ## 'master' - evals contents in master interpreter
2697	    uplevel \#0 $args
2698	}
2699	set {
2700	    ## 'set' - set (or get, or unset) simple vars (not whole arrays)
2701	    ## from the master console interpreter
2702	    ## possible formats:
2703	    ##    tkcon set <var>
2704	    ##    tkcon set <var> <value>
2705	    ##    tkcon set <var> <interp> <var1> <var2> w
2706	    ##    tkcon set <var> <interp> <var1> <var2> u
2707	    ##    tkcon set <var> <interp> <var1> <var2> r
2708	    if {[llength $args]==5} {
2709		## This is for use w/ 'tkcon upvar' and only works with slaves
2710		foreach {var i var1 var2 op} $args break
2711		if {[string compare {} $var2]} { append var1 "($var2)" }
2712		switch $op {
2713		    u { uplevel \#0 [list unset $var] }
2714		    w {
2715			return [uplevel \#0 [list set $var \
2716				[interp eval $i [list set $var1]]]]
2717		    }
2718		    r {
2719			return [interp eval $i [list set $var1 \
2720				[uplevel \#0 [list set $var]]]]
2721		    }
2722		}
2723	    } elseif {[llength $args] == 1} {
2724		upvar \#0 [lindex $args 0] var
2725		if {[array exists var]} {
2726		    return [array get var]
2727		} else {
2728		    return $var
2729		}
2730	    }
2731	    return [uplevel \#0 set $args]
2732	}
2733	append {
2734	    ## Modify a var in the master environment using append
2735	    return [uplevel \#0 append $args]
2736	}
2737	lappend {
2738	    ## Modify a var in the master environment using lappend
2739	    return [uplevel \#0 lappend $args]
2740	}
2741	sh* - dei* {
2742	    ## 'show|deiconify' - deiconifies the console.
2743	    wm deiconify $::tkcon::PRIV(root)
2744	    raise $::tkcon::PRIV(root)
2745	    focus -force $::tkcon::PRIV(console)
2746	}
2747	ti* {
2748	    ## 'title' ?title? - gets/sets the console's title
2749	    if {[llength $args]} {
2750		return [wm title $::tkcon::PRIV(root) [join $args]]
2751	    } else {
2752		return [wm title $::tkcon::PRIV(root)]
2753	    }
2754	}
2755	upv* {
2756	    ## 'upvar' masterVar slaveVar
2757	    ## link slave variable slaveVar to the master variable masterVar
2758	    ## only works masters<->slave
2759	    set masterVar [lindex $args 0]
2760	    set slaveVar  [lindex $args 1]
2761	    if {[info exists $masterVar]} {
2762		interp eval $::tkcon::OPT(exec) \
2763			[list set $slaveVar [set $masterVar]]
2764	    } else {
2765		catch {interp eval $::tkcon::OPT(exec) [list unset $slaveVar]}
2766	    }
2767	    interp eval $::tkcon::OPT(exec) \
2768		    [list trace variable $slaveVar rwu \
2769		    [list tkcon set $masterVar $::tkcon::OPT(exec)]]
2770	    return
2771	}
2772	v* {
2773	    return $::tkcon::PRIV(version)
2774	}
2775	default {
2776	    ## tries to determine if the command exists, otherwise throws error
2777	    set new ::tkcon::[string toupper \
2778		    [string index $cmd 0]][string range $cmd 1 end]
2779	    if {[llength [info command $new]]} {
2780		uplevel \#0 $new $args
2781	    } else {
2782		return -code error "bad option \"$cmd\": must be\
2783			[join [lsort [list attach close console destroy \
2784			font hide iconify load main master new save show \
2785			slave deiconify version title bgerror]] {, }]"
2786	    }
2787	}
2788    }
2789}
2790
2791##
2792## Some procedures to make up for lack of built-in shell commands
2793##
2794
2795## tkcon_puts -
2796## This allows me to capture all stdout/stderr to the console window
2797## This will be renamed to 'puts' at the appropriate time during init
2798##
2799# ARGS:	same as usual
2800# Outputs:	the string with a color-coded text tag
2801##
2802proc tkcon_puts args {
2803    set len [llength $args]
2804    foreach {arg1 arg2 arg3} $args { break }
2805
2806    if {$len == 1} {
2807	set sarg $arg1
2808	set nl 1
2809	set farg stdout
2810    } elseif {$len == 2} {
2811	if {![string compare $arg1 -nonewline]} {
2812	    set sarg $arg2
2813	    set farg stdout
2814	    set nl 0
2815	} elseif {![string compare $arg1 stdout] \
2816		|| ![string compare $arg1 stderr]} {
2817	    set sarg $arg2
2818	    set farg $arg1
2819	    set nl 1
2820	} else {
2821	    set len 0
2822	}
2823    } elseif {$len == 3} {
2824	if {![string compare $arg1 -nonewline] \
2825		&& (![string compare $arg2 stdout] \
2826		|| ![string compare $arg2 stderr])} {
2827	    set sarg $arg3
2828	    set farg $arg2
2829	    set nl 0
2830	} elseif {(![string compare $arg1 stdout] \
2831		|| ![string compare $arg1 stderr]) \
2832		&& ![string compare $arg3 nonewline]} {
2833	    set sarg $arg2
2834	    set farg $arg1
2835	    set nl 0
2836	} else {
2837	    set len 0
2838	}
2839    } else {
2840	set len 0
2841    }
2842
2843    ## $len == 0 means it wasn't handled by tkcon above.
2844    ##
2845
2846    if {$len != 0} {
2847
2848	## "poor man's" \r substitution---erase everything on the output
2849	## line and print from character after the \r
2850
2851	set rpt [string last \r $sarg]
2852	if {$rpt >= 0} {
2853	    tkcon console delete "insert linestart" "insert lineend"
2854	    set sarg [string range $sarg [expr {$rpt + 1}] end]
2855	}
2856
2857	set bpt [string first \b $sarg]
2858	if {$bpt >= 0} {
2859	    set narg [string range $sarg [expr {$bpt + 1}] end]
2860	    set sarg [string range $sarg 0 [expr {$bpt - 1}]]
2861	    set nl 0
2862	}
2863
2864	if {$nl == 0} {
2865	    tkcon console insert output $sarg $farg
2866	} else {
2867	    tkcon console insert output "$sarg\n" $farg
2868	}
2869
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
2876	    }
2877	}
2878
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
2885	}
2886	return $msg
2887    }
2888
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
2895    }
2896}
2897
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
2901##
2902# ARGS:		same as gets
2903# Outputs:	same as gets
2904##
2905proc 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?\""
2910    }
2911    if {[string compare stdin [lindex $args 0]]} {
2912	return [uplevel 1 tkcon_tcl_gets $args]
2913    }
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]
2921    }
2922    return $data
2923}
2924
2925## edit - opens a file/proc/var for reading/editing
2926##
2927# Arguments:
2928#   type	proc/file/var
2929#   what	the actual name of the item
2930# Returns:	nothing
2931##
2932proc 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]\""}
2941	}
2942	set args [lreplace $args 0 1]
2943    }
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}
2949    }
2950
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"
2959	}
2960    }
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"
2973	}
2974
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]
2986
2987	set menu [menu $w.mbar]
2988	$w configure -menu $menu
2989
2990	## File Menu
2991	##
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]
3002
3003	## Edit Menu
3004	##
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]
3016
3017	## Send To Menu
3018	##
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\]"
3028	}
3029
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'"
3037    }
3038    switch -glob -- $opts(-type) {
3039	proc*	{
3040	    $w.text insert 1.0 \
3041		    [::tkcon::EvalOther $app $type dump proc [list $word]]
3042	}
3043	var*	{
3044	    $w.text insert 1.0 \
3045		    [::tkcon::EvalOther $app $type dump var [list $word]]
3046	}
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)
3055	    }
3056	    ]]
3057	}
3058	error*	{
3059	    $w.text insert 1.0 [join $args \n]
3060	    ::tkcon::ErrorHighlight $w.text
3061	}
3062	default	{
3063	    $w.text insert 1.0 [join $args \n]
3064	}
3065    }
3066    wm deiconify $w
3067    focus $w.text
3068    if {[string compare $opts(-find) {}]} {
3069	::tkcon::Find $w.text $opts(-find) -case 1
3070    }
3071}
3072interp alias {} ::more {} ::edit
3073interp alias {} ::less {} ::edit
3074
3075## echo
3076## Relaxes the one string restriction of 'puts'
3077# ARGS:	any number of strings to output to stdout
3078##
3079proc echo args { puts [concat $args] }
3080
3081## clear - clears the buffer of the console (not the history though)
3082## This is executed in the parent interpreter
3083##
3084proc 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"
3093    }
3094}
3095
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
3101##
3102proc alias {{newcmd {}} args} {
3103    if {[string match {} $newcmd]} {
3104	set res {}
3105	foreach a [interp aliases] {
3106	    lappend res [list $a -> [interp alias {} $a]]
3107	}
3108	return [join $res \n]
3109    } elseif {![llength $args]} {
3110	interp alias {} $newcmd
3111    } else {
3112	eval interp alias [list {} $newcmd {}] $args
3113    }
3114}
3115
3116## unalias - unaliases an alias'ed command
3117# ARGS:	cmd	- command to unbind as an alias
3118##
3119proc unalias {cmd} {
3120    interp alias {} $cmd {}
3121}
3122
3123## dump - outputs variables/procedure/widget info in source'able form.
3124## Accepts glob style pattern matching for the names
3125#
3126# ARGS:	type	- type of thing to dump: must be variable, procedure, widget
3127#
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
3136#
3137# Returns:	the values of the requested items in a 'source'able form
3138##
3139proc 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
3147    }
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]\""}
3154	}
3155    }
3156    if {$whine && ![llength $args]} {
3157	return -code error "wrong \# args: [lindex [info level 0] 0] type\
3158		?-nocomplain? ?-filter pattern? ?--? pattern ?pattern ...?"
3159    }
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]]])
3177			} {
3178			    if {[catch {dump p -- $cmd} msg] && $whine} {
3179				set code error
3180			    }
3181			    append res $msg\n
3182			} else {
3183			    append res "\#\# COMMAND: $cmd\n"
3184			}
3185		    }
3186		} elseif {$whine} {
3187		    append res "\#\# No known command $arg\n"
3188		    set code error
3189		}
3190	    }
3191	}
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 }
3205		}
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]]
3212		    }
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"
3226				}
3227			    }
3228			} else {
3229			    ## empty array
3230			    append res "    empty array\n"
3231			    append nst "unset [list $var](empty)\n"
3232			}
3233			append res "\}\n$nst"
3234		    } else {
3235			append res [list set $var $v]\n
3236		    }
3237		}
3238	    }
3239	}
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]]]])
3249		} {
3250		    set procs {}
3251		    set namesp [namespace qualifier $arg]
3252		    foreach p $ps {
3253			lappend procs ${namesp}::$p
3254		    }
3255		}
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
3264			    }
3265			}
3266			append res [list proc $p $as [info body $p]]\n
3267		    }
3268		} elseif {$whine} {
3269		    append res "\#\# No known proc $arg\n"
3270		    set code error
3271		}
3272	    }
3273	}
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"
3279	    }
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]]"
3302				    }
3303				}
3304				append res \n
3305			    }
3306			}
3307		    }
3308		} elseif {$whine} {
3309		    append res "\#\# No known widget $arg\n"
3310		    set code error
3311		}
3312	    }
3313	}
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"
3321		    }
3322		}
3323	    } else {
3324		set res "dump was unable to resolve type for \"$args\""
3325		set code error
3326	    }
3327	}
3328	default {
3329	    return -code error "bad [lindex [info level 0] 0] option\
3330		    \"$type\": must be variable, command, procedure,\
3331		    or widget"
3332	}
3333    }
3334    return -code $code [string trimright $res \n]
3335}
3336
3337## idebug - interactive debugger
3338#
3339# idebug body ?level?
3340#
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.
3343#
3344# idebug break
3345#
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.
3352#
3353# idebug echo|{echo ?id?} ?args?
3354#
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.
3359#
3360# idebug id ?id?
3361#
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 *.
3365#
3366# idebug off
3367#
3368#	Turns idebug off.
3369#
3370# idebug on ?id?
3371#
3372#	Turns idebug on.  If 'id' is specified, it sets the id to it.
3373#
3374# idebug puts|{puts ?id?} args
3375#
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.
3380#
3381# idebug show type ?level? ?VERBOSE?
3382#
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.
3388#
3389# idebug trace ?level?
3390#
3391#	Prints out the stack trace from the specified level up to the top
3392#	level.  'level' defaults to the current level.
3393#
3394##
3395proc idebug {opt args} {
3396    global IDEBUG
3397
3398    if {![info exists IDEBUG(on)]} {
3399	array set IDEBUG { on 0 id * debugging 0 }
3400    }
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]
3406	}
3407	off	{ return [set IDEBUG(on) 0] }
3408	id  {
3409	    if {![llength $args]} {
3410		return $IDEBUG(id)
3411	    } else { return [set IDEBUG(id) $args] }
3412	}
3413	break {
3414	    if {!$IDEBUG(on) || $IDEBUG(debugging) || \
3415		    ([llength $args] && \
3416		    ![string match $IDEBUG(id) $args]) || [info level]<1} {
3417		return
3418	    }
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
3429	    }
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]"
3446		    }
3447		}
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
3452		}
3453		set res {}; set c 0
3454		switch -- $key {
3455		    + {
3456			## Allow for jumping multiple levels
3457			if {$level < $max} {
3458			    idebug trace [incr level] $level 0 VERBOSE
3459			}
3460		    }
3461		    - {
3462			## Allow for jumping multiple levels
3463			if {$level > 1} {
3464			    idebug trace [incr level -1] $level 0 VERBOSE
3465			}
3466		    }
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
3482
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
3491
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)"
3497		    }
3498		    c - q break
3499		    default { set c [catch {uplevel \#$level $line} res] }
3500		}
3501		if {$tkcon} {
3502		    tkcon set ::tkcon::PRIV(event) \
3503			    [tkcon evalSlave eval history add [list $line]\
3504			    \; history nextid]
3505		}
3506		if {$c} {
3507		    puts stderr $res
3508		} elseif {[string compare {} $res]} {
3509		    puts $res
3510		}
3511	    }
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
3519	    }
3520	}
3521	bo* {
3522	    if {[regexp {^([#-]?[0-9]+)} $args level]} {
3523		return [uplevel $level {dump c -no [lindex [info level 0] 0]}]
3524	    }
3525	}
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"
3538		}
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]]"
3552			}
3553		    }
3554		}
3555	    }
3556	}
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}]] }
3567	    }
3568	    if {[string match VERBOSE $verbose]} {
3569		return [uplevel $level dump var -nocomplain $vars]
3570	    } else {
3571		return $vars
3572	    }
3573	}
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]
3579	    }
3580	    if {$IDEBUG(on) && [string match $IDEBUG(id) $id]} {
3581		if {[string match e* $opt]} {
3582		    puts [concat $args]
3583		} else { eval puts $args }
3584	    }
3585	}
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]] {, }]"
3590	}
3591    }
3592}
3593
3594## observe - like trace, but not
3595# ARGS:	opt	- option
3596#	name	- name of variable or command
3597##
3598proc 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"
3606	    }
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
3619	    }
3620	    uplevel \[lreplace \[info level 0\] 0 0 $old\]
3621	    "
3622	    set tcl_observe($name) $old
3623	}
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 }
3630	}
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)
3639		}
3640		return $res
3641	    }
3642	}
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"
3649	    }
3650	    if {![llength $args]} { set args observe_var }
3651	    uplevel 1 [list trace $opt $name $type $args]
3652	}
3653	vi* {
3654	    uplevel 1 [list trace vinfo $name]
3655	}
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]] {, }]"
3660	}
3661    }
3662}
3663
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)
3669##
3670proc 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\""
3676	}
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]
3683	}
3684    }
3685}
3686
3687## which - tells you where a command is found
3688# ARGS:	cmd	- command name
3689# Returns:	where command is found (internal / external / unknown)
3690##
3691proc 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 {}
3696
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" }
3704	    }
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))"
3713		}
3714		lappend out $res
3715		unset res
3716	    }
3717	}
3718	return [join $out \n]
3719    } else {
3720	return -code error "$cmd: command not found"
3721    }
3722}
3723
3724## what - tells you what a string is recognized as
3725# ARGS:	str	- string to id
3726# Returns:	id types of command as list
3727##
3728proc 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]]])
3739	} {
3740	    lappend types "procedure"
3741	} else {
3742	    lappend types "command"
3743	}
3744    }
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
3751	}
3752    }
3753    if {[file isdirectory $str]} {
3754	lappend types "directory"
3755    }
3756    if {[file isfile $str]} {
3757	lappend types "file"
3758    }
3759    if {[llength [info commands winfo]] && [winfo exists $str]} {
3760	lappend types "widget"
3761    }
3762    if {[string compare {} [auto_execok $str]]} {
3763	lappend types "executable"
3764    }
3765    return $types
3766}
3767
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
3774##
3775proc 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
3779    }
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"
3789	    }
3790	}
3791    }
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 *]]]
3805		}
3806	    } else {
3807		set dir [file dirname $arg]
3808		lappend out [list $dir$sep [lsort \
3809			[glob -nocomplain -directory $dir [file tail $arg]]]]
3810	    }
3811	}
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*]]]
3820		}
3821	    } else {
3822		lappend out [list [file dirname $arg]$sep \
3823			[lsort [glob -nocomplain -- $arg]]]
3824	    }
3825	}
3826    }
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 * } }
3841		    }
3842		}
3843		if {[string match file $st(type)]} {
3844		    set mode -
3845		} else {
3846		    set mode [string index $st(type) 0]
3847		}
3848		foreach j [split [format %03o [expr {$st(mode)&0777}]] {}] {
3849		    append mode $s($j)
3850		}
3851		if {$st(mtime)>$old} {
3852		    set cfmt {%b %d %H:%M}
3853		} else {
3854		    set cfmt {%b %d  %Y}
3855		}
3856		append res [format $fmt $mode $st(size) \
3857			[clock format $st(mtime) -format $cfmt] $f]
3858	    }
3859	    append res \n
3860	}
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]]
3869		}
3870	    }
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}]
3876	    }
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 * } }
3885		    }
3886		}
3887		append res [format "%-${i}s" $f]
3888		if {$j == 0 || [incr k]%$j == 0} {
3889		    set res [string trimright $res]\n
3890		}
3891	    }
3892	    append res \n\n
3893	}
3894    }
3895    return [string trimright $res]
3896}
3897interp alias {} ::ls {} ::dir -full
3898
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)
3906##
3907proc 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]\""}
3916	}
3917	set args [lreplace $args 0 0]
3918    }
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]
3926	    }
3927	}
3928    }
3929    return $l
3930}
3931
3932if {!$::tkcon::PRIV(WWW)} {;
3933
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'.
3940#
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.
3945#
3946# Arguments:
3947# args -	A list whose elements are the words of the original
3948#		command, including the command name.
3949
3950proc unknown args {
3951    global unknown_handler_order unknown_handlers errorInfo errorCode
3952
3953    #
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.
3957    #
3958    set savedErrorCode $errorCode
3959    set savedErrorInfo $errorInfo
3960
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
3965    }
3966
3967    foreach handler $unknown_handler_order {
3968        set status [catch {uplevel 1 $unknown_handlers($handler) $args} result]
3969
3970        if {$status == 1} {
3971            #
3972            # Strip the last five lines off the error stack (they're
3973            # from the "uplevel" command).
3974            #
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
3979
3980        } elseif {$status != 4} {
3981            return -code $status $result
3982        }
3983
3984        set errorCode $savedErrorCode
3985        set errorInfo $savedErrorInfo
3986    }
3987
3988    set name [lindex $args 0]
3989    return -code error "invalid command name \"$name\""
3990}
3991
3992# tcl_unknown:
3993# Invoked when a Tcl command is invoked that doesn't exist in the
3994# interpreter:
3995#
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.
4006#
4007# Arguments:
4008# args -	A list whose elements are the words of the original
4009#		command, including the command name.
4010
4011proc tcl_unknown args {
4012    global auto_noexec auto_noload env unknown_pending tcl_interactive
4013    global errorCode errorInfo
4014
4015    # If the command word has the form "namespace inscope ns cmd"
4016    # then concatenate its arguments onto the end and evaluate it.
4017
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
4026        }
4027    }
4028
4029    # CAD tools special:
4030    # Check for commands which were renamed to tcl_(command)
4031
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
4039	}
4040    }
4041
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.
4045
4046    set savedErrorCode $errorCode
4047    set savedErrorInfo $errorInfo
4048    set name [lindex $args 0]
4049    if {![info exists auto_noload]} {
4050	#
4051	# Make sure we're not trying to load the same proc twice.
4052	#
4053	if {[info exists unknown_pending($name)]} {
4054	    return -code error "self-referential recursion in \"unknown\" for command \"$name\""
4055	}
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]
4061	}
4062	unset unknown_pending($name)
4063	if {$ret} {
4064	    return -code $ret -errorcode $errorCode \
4065		    "error while autoloading \"$name\": $msg"
4066	}
4067	#
4068	# Avoid problems with renaming "array"! (for tcl-based magic only)
4069	#
4070	set arraycmd array
4071	if {[lsearch [info commands] tcl_array] >= 0} {set arraycmd tcl_array}
4072
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} {
4079		#
4080		# Strip the last five lines off the error stack (they're
4081		# from the "uplevel" command).
4082		#
4083
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
4090	    }
4091	}
4092    }
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]]
4102	    }
4103	}
4104	set errorCode $savedErrorCode
4105	set errorInfo $savedErrorInfo
4106	##
4107	## History substitution moved into ::tkcon::EvalCmd
4108	##
4109	set ret [catch {set cmds [info commands $name*]} msg]
4110	if {[string compare $name "::"] == 0} {
4111	    set name ""
4112	}
4113	if {$ret != 0} {
4114	    return -code $ret -errorcode $errorCode \
4115		"error in unknown while checking if \"$name\" is a unique command abbreviation: $msg"
4116	}
4117	set cmds [info commands $name*]
4118	if {[llength $cmds] == 1} {
4119	    return [uplevel 1 [lreplace $args 0 0 $cmds]]
4120	}
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]"
4127	    }
4128	}
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"]
4146	    }
4147	}
4148    }
4149    return -code continue
4150}
4151
4152} ; # end exclusionary code for WWW
4153
4154proc ::tkcon::Bindings {} {
4155    variable PRIV
4156    global tcl_platform tk_version
4157
4158    #-----------------------------------------------------------------------
4159    # Elements of tkPriv that are used in this file:
4160    #
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    #-----------------------------------------------------------------------
4176
4177    switch -glob $tcl_platform(platform) {
4178	win*	{ set PRIV(meta) Alt }
4179	mac*	{ set PRIV(meta) Command }
4180	default	{ set PRIV(meta) Meta }
4181    }
4182
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> {}
4187    bind TkConsole <<NextLine>> {}
4188    bind TkConsole <<PrevLine>> {}
4189
4190    ## Now make all our virtual event bindings
4191    foreach {ev key} [subst -nocommand -noback {
4192	<<TkCon_Exit>>		<Control-q>
4193	<<TkCon_New>>		<Control-N>
4194	<<TkCon_Close>>		<Control-w>
4195	<<TkCon_About>>		<Control-A>
4196	<<TkCon_Help>>		<Control-H>
4197	<<TkCon_Find>>		<Control-F>
4198	<<TkCon_Slave>>		<Control-Key-1>
4199	<<TkCon_Master>>	<Control-Key-2>
4200	<<TkCon_Main>>		<Control-Key-3>
4201	<<TkCon_Expand>>	<Key-Tab>
4202	<<TkCon_ExpandFile>>	<Key-Escape>
4203	<<TkCon_ExpandProc>>	<Control-P>
4204	<<TkCon_ExpandVar>>	<Control-V>
4205	<<TkCon_Tab>>		<Control-i>
4206	<<TkCon_Tab>>		<$PRIV(meta)-i>
4207	<<TkCon_Newline>>	<Control-o>
4208	<<TkCon_Newline>>	<$PRIV(meta)-o>
4209	<<TkCon_Newline>>	<Control-Key-Return>
4210	<<TkCon_Newline>>	<Control-Key-KP_Enter>
4211	<<TkCon_Eval>>		<Return>
4212	<<TkCon_Eval>>		<KP_Enter>
4213	<<TkCon_Clear>>		<Control-l>
4214	<<TkCon_Previous>>	<Up>
4215	<<TkCon_PreviousImmediate>>	<Control-p>
4216	<<TkCon_PreviousSearch>>	<Control-r>
4217	<<TkCon_Next>>		<Down>
4218	<<TkCon_NextImmediate>>	<Control-n>
4219	<<TkCon_NextSearch>>	<Control-s>
4220	<<TkCon_Transpose>>	<Control-t>
4221	<<TkCon_ClearLine>>	<Control-u>
4222	<<TkCon_SaveCommand>>	<Control-z>
4223	<<TkCon_Popup>>		<Button-3>
4224    }] {
4225	event add $ev $key
4226	## Make sure the specific key won't be defined
4227	bind TkConsole $key {}
4228    }
4229
4230    ## Make the ROOT bindings
4231    bind $PRIV(root) <<TkCon_Exit>>	exit
4232    bind $PRIV(root) <<TkCon_New>>	{ ::tkcon::New }
4233    bind $PRIV(root) <<TkCon_Close>>	{ ::tkcon::Destroy }
4234    bind $PRIV(root) <<TkCon_About>>	{ ::tkcon::About }
4235    bind $PRIV(root) <<TkCon_Help>>	{ ::tkcon::Help }
4236    bind $PRIV(root) <<TkCon_Find>>	{ ::tkcon::FindBox $::tkcon::PRIV(console) }
4237    bind $PRIV(root) <<TkCon_Slave>>	{
4238	::tkcon::Attach {}
4239	::tkcon::Prompt "\n" [::tkcon::CmdGet $::tkcon::PRIV(console)]
4240    }
4241    bind $PRIV(root) <<TkCon_Master>>	{
4242	if {[string compare {} $::tkcon::PRIV(name)]} {
4243	    ::tkcon::Attach $::tkcon::PRIV(name)
4244	} else {
4245	    ::tkcon::Attach Main
4246	}
4247	::tkcon::Prompt "\n" [::tkcon::CmdGet $::tkcon::PRIV(console)]
4248    }
4249    bind $PRIV(root) <<TkCon_Main>>	{
4250	::tkcon::Attach Main
4251	::tkcon::Prompt "\n" [::tkcon::CmdGet $::tkcon::PRIV(console)]
4252    }
4253    bind $PRIV(root) <<TkCon_Popup>> {
4254	::tkcon::PopupMenu %X %Y
4255    }
4256
4257    ## Menu items need null TkConsolePost bindings to avoid the TagProc
4258    ##
4259    foreach ev [bind $PRIV(root)] {
4260	bind TkConsolePost $ev {
4261	    # empty
4262	}
4263    }
4264
4265
4266    # ::tkcon::ClipboardKeysyms --
4267    # This procedure is invoked to identify the keys that correspond to
4268    # the copy, cut, and paste functions for the clipboard.
4269    #
4270    # Arguments:
4271    # copy -	Name of the key (keysym name plus modifiers, if any,
4272    #		such as "Meta-y") used for the copy operation.
4273    # cut -		Name of the key used for the cut operation.
4274    # paste -	Name of the key used for the paste operation.
4275
4276    proc ::tkcon::ClipboardKeysyms {copy cut paste} {
4277	bind TkConsole <$copy>	{::tkcon::Copy %W}
4278	bind TkConsole <$cut>	{::tkcon::Cut %W}
4279	bind TkConsole <$paste>	{::tkcon::Paste %W}
4280    }
4281
4282    proc ::tkcon::GetSelection {w} {
4283	if {
4284	    ![catch {selection get -displayof $w -type UTF8_STRING} txt] ||
4285	    ![catch {selection get -displayof $w} txt] ||
4286	    ![catch {selection get -displayof $w -selection CLIPBOARD} txt]
4287	} {
4288	    return $txt
4289	}
4290	return -code error "could not find default selection"
4291    }
4292
4293    proc ::tkcon::Cut w {
4294	if {[string match $w [selection own -displayof $w]]} {
4295	    clipboard clear -displayof $w
4296	    catch {
4297		set txt [selection get -displayof $w]
4298		clipboard append -displayof $w $txt
4299		if {[$w compare sel.first >= limit]} {
4300		    $w delete sel.first sel.last
4301		}
4302	    }
4303	}
4304    }
4305    proc ::tkcon::Copy w {
4306	if {[string match $w [selection own -displayof $w]]} {
4307	    clipboard clear -displayof $w
4308	    catch {
4309		set txt [selection get -displayof $w]
4310		clipboard append -displayof $w $txt
4311	    }
4312	}
4313    }
4314    proc ::tkcon::Paste w {
4315	if {![catch {GetSelection $w} txt]} {
4316	    if {[$w compare insert < limit]} { $w mark set insert end }
4317	    $w insert insert $txt
4318	    $w see insert
4319	    if {[string match *\n* $txt]} { ::tkcon::Eval $w }
4320	}
4321    }
4322
4323    ## Redefine for TkConsole what we need
4324    ##
4325    event delete <<Paste>> <Control-V>
4326    ::tkcon::ClipboardKeysyms <Copy> <Cut> <Paste>
4327
4328    bind TkConsole <Insert> {
4329	catch { ::tkcon::Insert %W [::tkcon::GetSelection %W] }
4330    }
4331
4332    bind TkConsole <Triple-1> {+
4333	catch {
4334	    eval %W tag remove sel [%W tag nextrange prompt sel.first sel.last]
4335	    eval %W tag remove sel sel.last-1c
4336	    %W mark set insert sel.first
4337	}
4338    }
4339
4340    ## binding editor needed
4341    ## binding <events> for .tkconrc
4342
4343    bind TkConsole <<TkCon_ExpandFile>> {
4344	if {[%W compare insert > limit]} {::tkcon::Expand %W path}
4345	break
4346    }
4347    bind TkConsole <<TkCon_ExpandProc>> {
4348	if {[%W compare insert > limit]} {::tkcon::Expand %W proc}
4349    }
4350    bind TkConsole <<TkCon_ExpandVar>> {
4351	if {[%W compare insert > limit]} {::tkcon::Expand %W var}
4352    }
4353    bind TkConsole <<TkCon_Expand>> {
4354	if {[%W compare insert > limit]} {::tkcon::Expand %W}
4355    }
4356    bind TkConsole <<TkCon_Tab>> {
4357	if {[%W compare insert >= limit]} {
4358	    ::tkcon::Insert %W \t
4359	}
4360    }
4361    bind TkConsole <<TkCon_Newline>> {
4362	if {[%W compare insert >= limit]} {
4363	    ::tkcon::Insert %W \n
4364	}
4365    }
4366    bind TkConsole <<TkCon_Eval>> {
4367	::tkcon::Eval %W
4368    }
4369    bind TkConsole <Delete> {
4370	if {[llength [%W tag nextrange sel 1.0 end]] \
4371		&& [%W compare sel.first >= limit]} {
4372	    %W delete sel.first sel.last
4373	} elseif {[%W compare insert >= limit]} {
4374	    %W delete insert
4375	    %W see insert
4376	}
4377    }
4378    bind TkConsole <BackSpace> {
4379	if {[llength [%W tag nextrange sel 1.0 end]] \
4380		&& [%W compare sel.first >= limit]} {
4381	    %W delete sel.first sel.last
4382	} elseif {[%W compare insert != 1.0] && [%W compare insert > limit]} {
4383	    %W delete insert-1c
4384	    %W see insert
4385	}
4386    }
4387    bind TkConsole <Control-h> [bind TkConsole <BackSpace>]
4388
4389    bind TkConsole <KeyPress> {
4390	::tkcon::Insert %W %A
4391    }
4392
4393    bind TkConsole <Control-a> {
4394	if {[%W compare {limit linestart} == {insert linestart}]} {
4395	    tkTextSetCursor %W limit
4396	} else {
4397	    tkTextSetCursor %W {insert linestart}
4398	}
4399    }
4400    bind TkConsole <Key-Home> [bind TkConsole <Control-a>]
4401    bind TkConsole <Control-d> {
4402	if {[%W compare insert < limit]} break
4403	%W delete insert
4404    }
4405    bind TkConsole <Control-k> {
4406	if {[%W compare insert < limit]} break
4407	if {[%W compare insert == {insert lineend}]} {
4408	    %W delete insert
4409	} else {
4410	    %W delete insert {insert lineend}
4411	}
4412    }
4413    bind TkConsole <<TkCon_Clear>> {
4414	## Clear console buffer, without losing current command line input
4415	set ::tkcon::PRIV(tmp) [::tkcon::CmdGet %W]
4416	clear
4417	::tkcon::Prompt {} $::tkcon::PRIV(tmp)
4418    }
4419    bind TkConsole <<TkCon_Previous>> {
4420	if {[%W compare {insert linestart} != {limit linestart}]} {
4421	    tkTextSetCursor %W [tkTextUpDownLine %W -1]
4422	} else {
4423	    ::tkcon::Event -1
4424	}
4425    }
4426    bind TkConsole <<TkCon_Next>> {
4427	if {[%W compare {insert linestart} != {end-1c linestart}]} {
4428	    tkTextSetCursor %W [tkTextUpDownLine %W 1]
4429	} else {
4430	    ::tkcon::Event 1
4431	}
4432    }
4433    bind TkConsole <<TkCon_NextImmediate>>  { ::tkcon::Event 1 }
4434    bind TkConsole <<TkCon_PreviousImmediate>> { ::tkcon::Event -1 }
4435    bind TkConsole <<TkCon_PreviousSearch>> {
4436	::tkcon::Event -1 [::tkcon::CmdGet %W]
4437    }
4438    bind TkConsole <<TkCon_NextSearch>>	    {
4439	::tkcon::Event 1 [::tkcon::CmdGet %W]
4440    }
4441    bind TkConsole <<TkCon_Transpose>>	{
4442	## Transpose current and previous chars
4443	if {[%W compare insert > "limit+1c"]} { tkTextTranspose %W }
4444    }
4445    bind TkConsole <<TkCon_ClearLine>> {
4446	## Clear command line (Unix shell staple)
4447	%W delete limit end
4448    }
4449    bind TkConsole <<TkCon_SaveCommand>> {
4450	## Save command buffer (swaps with current command)
4451	set ::tkcon::PRIV(tmp) $::tkcon::PRIV(cmdsave)
4452	set ::tkcon::PRIV(cmdsave) [::tkcon::CmdGet %W]
4453	if {[string match {} $::tkcon::PRIV(cmdsave)]} {
4454	    set ::tkcon::PRIV(cmdsave) $::tkcon::PRIV(tmp)
4455	} else {
4456	    %W delete limit end-1c
4457	}
4458	::tkcon::Insert %W $::tkcon::PRIV(tmp)
4459	%W see end
4460    }
4461    catch {bind TkConsole <Key-Page_Up>   { tkTextScrollPages %W -1 }}
4462    catch {bind TkConsole <Key-Prior>     { tkTextScrollPages %W -1 }}
4463    catch {bind TkConsole <Key-Page_Down> { tkTextScrollPages %W 1 }}
4464    catch {bind TkConsole <Key-Next>      { tkTextScrollPages %W 1 }}
4465    bind TkConsole <$PRIV(meta)-d> {
4466	if {[%W compare insert >= limit]} {
4467	    %W delete insert {insert wordend}
4468	}
4469    }
4470    bind TkConsole <$PRIV(meta)-BackSpace> {
4471	if {[%W compare {insert -1c wordstart} >= limit]} {
4472	    %W delete {insert -1c wordstart} insert
4473	}
4474    }
4475    bind TkConsole <$PRIV(meta)-Delete> {
4476	if {[%W compare insert >= limit]} {
4477	    %W delete insert {insert wordend}
4478	}
4479    }
4480    bind TkConsole <ButtonRelease-2> {
4481	if {
4482	    (!$tkPriv(mouseMoved) || $tk_strictMotif) &&
4483	    ![catch {::tkcon::GetSelection %W} ::tkcon::PRIV(tmp)]
4484	} {
4485	    if {[%W compare @%x,%y < limit]} {
4486		%W insert end $::tkcon::PRIV(tmp)
4487	    } else {
4488		%W insert @%x,%y $::tkcon::PRIV(tmp)
4489	    }
4490	    if {[string match *\n* $::tkcon::PRIV(tmp)]} {::tkcon::Eval %W}
4491	}
4492    }
4493
4494    ##
4495    ## End TkConsole bindings
4496    ##
4497
4498    ##
4499    ## Bindings for doing special things based on certain keys
4500    ##
4501    bind TkConsolePost <Key-parenright> {
4502	if {$::tkcon::OPT(lightbrace) && $::tkcon::OPT(blinktime)>99 && \
4503		[string compare \\ [%W get insert-2c]]} {
4504	    ::tkcon::MatchPair %W \( \) limit
4505	}
4506	set ::tkcon::PRIV(StatusCursor) [%W index insert]
4507    }
4508    bind TkConsolePost <Key-bracketright> {
4509	if {$::tkcon::OPT(lightbrace) && $::tkcon::OPT(blinktime)>99 && \
4510		[string compare \\ [%W get insert-2c]]} {
4511	    ::tkcon::MatchPair %W \[ \] limit
4512	}
4513	set ::tkcon::PRIV(StatusCursor) [%W index insert]
4514    }
4515    bind TkConsolePost <Key-braceright> {
4516	if {$::tkcon::OPT(lightbrace) && $::tkcon::OPT(blinktime)>99 && \
4517		[string compare \\ [%W get insert-2c]]} {
4518	    ::tkcon::MatchPair %W \{ \} limit
4519	}
4520	set ::tkcon::PRIV(StatusCursor) [%W index insert]
4521    }
4522    bind TkConsolePost <Key-quotedbl> {
4523	if {$::tkcon::OPT(lightbrace) && $::tkcon::OPT(blinktime)>99 && \
4524		[string compare \\ [%W get insert-2c]]} {
4525	    ::tkcon::MatchQuote %W limit
4526	}
4527	set ::tkcon::PRIV(StatusCursor) [%W index insert]
4528    }
4529
4530    bind TkConsolePost <KeyPress> {
4531	if {$::tkcon::OPT(lightcmd) && [string compare {} %A]} {
4532	    ::tkcon::TagProc %W
4533	}
4534	set ::tkcon::PRIV(StatusCursor) [%W index insert]
4535    }
4536
4537    bind TkConsolePost <Button-1> {
4538	set ::tkcon::PRIV(StatusCursor) [%W index insert]
4539    }
4540    bind TkConsolePost <B1-Motion> {
4541	set ::tkcon::PRIV(StatusCursor) [%W index insert]
4542    }
4543
4544}
4545
4546##
4547# ::tkcon::PopupMenu - what to do when the popup menu is requested
4548##
4549proc ::tkcon::PopupMenu {X Y} {
4550    variable PRIV
4551
4552    set w $PRIV(console)
4553    if {[string compare $w [winfo containing $X $Y]]} {
4554	tk_popup $PRIV(popup) $X $Y
4555	return
4556    }
4557    set x [expr {$X-[winfo rootx $w]}]
4558    set y [expr {$Y-[winfo rooty $w]}]
4559    if {[llength [set tags [$w tag names @$x,$y]]]} {
4560	if {[lsearch -exact $tags "proc"] >= 0} {
4561	    lappend type "proc"
4562	    foreach {first last} [$w tag prevrange proc @$x,$y] {
4563		set word [$w get $first $last]; break
4564	    }
4565	}
4566	if {[lsearch -exact $tags "var"] >= 0} {
4567	    lappend type "var"
4568	    foreach {first last} [$w tag prevrange var @$x,$y] {
4569		set word [$w get $first $last]; break
4570	    }
4571	}
4572    }
4573    if {![info exists type]} {
4574	set exp "(^|\[^\\\\\]\[ \t\n\r\])"
4575	set exp2 "\[\[\\\\\\?\\*\]"
4576	set i [$w search -backwards -regexp $exp @$x,$y "@$x,$y linestart"]
4577	if {[string compare {} $i]} {
4578	    if {![string match *.0 $i]} {append i +2c}
4579	    if {[string compare {} \
4580		    [set j [$w search -regexp $exp $i "$i lineend"]]]} {
4581		append j +1c
4582	    } else {
4583		set j "$i lineend"
4584	    }
4585	    regsub -all $exp2 [$w get $i $j] {\\\0} word
4586	    set word [string trim $word {\"$[]{}',?#*}]
4587	    if {[llength [EvalAttached [list info commands $word]]]} {
4588		lappend type "proc"
4589	    }
4590	    if {[llength [EvalAttached [list info vars $word]]]} {
4591		lappend type "var"
4592	    }
4593	    if {[EvalAttached [list file isfile $word]]} {
4594		lappend type "file"
4595	    }
4596	}
4597    }
4598    if {![info exists type] || ![info exists word]} {
4599	tk_popup $PRIV(popup) $X $Y
4600	return
4601    }
4602    $PRIV(context) delete 0 end
4603    $PRIV(context) add command -label "$word" -state disabled
4604    $PRIV(context) add separator
4605    set app [Attach]
4606    if {[lsearch $type proc] != -1} {
4607	$PRIV(context) add command -label "View Procedure" \
4608		-command [list edit -attach $app -type proc -- $word]
4609    }
4610    if {[lsearch $type var] != -1} {
4611	$PRIV(context) add command -label "View Variable" \
4612		-command [list edit -attach $app -type var -- $word]
4613    }
4614    if {[lsearch $type file] != -1} {
4615	$PRIV(context) add command -label "View File" \
4616		-command [list edit -attach $app -type file -- $word]
4617    }
4618    tk_popup $PRIV(context) $X $Y
4619}
4620
4621## ::tkcon::TagProc - tags a procedure in the console if it's recognized
4622## This procedure is not perfect.  However, making it perfect wastes
4623## too much CPU time...
4624##
4625proc ::tkcon::TagProc w {
4626    set exp "\[^\\\\\]\[\[ \t\n\r\;{}\"\$\]"
4627    set i [$w search -backwards -regexp $exp insert-1c limit-1c]
4628    if {[string compare {} $i]} {append i +2c} else {set i limit}
4629    regsub -all "\[\[\\\\\\?\\*\]" [$w get $i "insert-1c wordend"] {\\\0} c
4630    if {[llength [EvalAttached [list info commands $c]]]} {
4631	$w tag add proc $i "insert-1c wordend"
4632    } else {
4633	$w tag remove proc $i "insert-1c wordend"
4634    }
4635    if {[llength [EvalAttached [list info vars $c]]]} {
4636	$w tag add var $i "insert-1c wordend"
4637    } else {
4638	$w tag remove var $i "insert-1c wordend"
4639    }
4640}
4641
4642## ::tkcon::MatchPair - blinks a matching pair of characters
4643## c2 is assumed to be at the text index 'insert'.
4644## This proc is really loopy and took me an hour to figure out given
4645## all possible combinations with escaping except for escaped \'s.
4646## It doesn't take into account possible commenting... Oh well.  If
4647## anyone has something better, I'd like to see/use it.  This is really
4648## only efficient for small contexts.
4649# ARGS:	w	- console text widget
4650# 	c1	- first char of pair
4651# 	c2	- second char of pair
4652# Calls:	::tkcon::Blink
4653##
4654proc ::tkcon::MatchPair {w c1 c2 {lim 1.0}} {
4655    if {[string compare {} [set ix [$w search -back $c1 insert $lim]]]} {
4656	while {
4657	    [string match {\\} [$w get $ix-1c]] &&
4658	    [string compare {} [set ix [$w search -back $c1 $ix-1c $lim]]]
4659	} {}
4660	set i1 insert-1c
4661	while {[string compare {} $ix]} {
4662	    set i0 $ix
4663	    set j 0
4664	    while {[string compare {} [set i0 [$w search $c2 $i0 $i1]]]} {
4665		append i0 +1c
4666		if {[string match {\\} [$w get $i0-2c]]} continue
4667		incr j
4668	    }
4669	    if {!$j} break
4670	    set i1 $ix
4671	    while {$j && [string compare {} \
4672		    [set ix [$w search -back $c1 $ix $lim]]]} {
4673		if {[string match {\\} [$w get $ix-1c]]} continue
4674		incr j -1
4675	    }
4676	}
4677	if {[string match {} $ix]} { set ix [$w index $lim] }
4678    } else { set ix [$w index $lim] }
4679    if {$::tkcon::OPT(blinkrange)} {
4680	Blink $w $ix [$w index insert]
4681    } else {
4682	Blink $w $ix $ix+1c [$w index insert-1c] [$w index insert]
4683    }
4684}
4685
4686## ::tkcon::MatchQuote - blinks between matching quotes.
4687## Blinks just the quote if it's unmatched, otherwise blinks quoted string
4688## The quote to match is assumed to be at the text index 'insert'.
4689# ARGS:	w	- console text widget
4690# Calls:	::tkcon::Blink
4691##
4692proc ::tkcon::MatchQuote {w {lim 1.0}} {
4693    set i insert-1c
4694    set j 0
4695    while {[string compare [set i [$w search -back \" $i $lim]] {}]} {
4696	if {[string match {\\} [$w get $i-1c]]} continue
4697	if {!$j} {set i0 $i}
4698	incr j
4699    }
4700    if {$j&1} {
4701	if {$::tkcon::OPT(blinkrange)} {
4702	    Blink $w $i0 [$w index insert]
4703	} else {
4704	    Blink $w $i0 $i0+1c [$w index insert-1c] [$w index insert]
4705	}
4706    } else {
4707	Blink $w [$w index insert-1c] [$w index insert]
4708    }
4709}
4710
4711## ::tkcon::Blink - blinks between n index pairs for a specified duration.
4712# ARGS:	w	- console text widget
4713# 	i1	- start index to blink region
4714# 	i2	- end index of blink region
4715# 	dur	- duration in usecs to blink for
4716# Outputs:	blinks selected characters in $w
4717##
4718proc ::tkcon::Blink {w args} {
4719    eval [list $w tag add blink] $args
4720    after $::tkcon::OPT(blinktime) [list $w] tag remove blink $args
4721    return
4722}
4723
4724
4725## ::tkcon::Insert
4726## Insert a string into a text console at the point of the insertion cursor.
4727## If there is a selection in the text, and it covers the point of the
4728## insertion cursor, then delete the selection before inserting.
4729# ARGS:	w	- text window in which to insert the string
4730# 	s	- string to insert (usually just a single char)
4731# Outputs:	$s to text widget
4732##
4733proc ::tkcon::Insert {w s} {
4734    if {[string match {} $s] || [string match disabled [$w cget -state]]} {
4735	return
4736    }
4737    if {[$w comp insert < limit]} {
4738	$w mark set insert end
4739    }
4740    if {[llength [$w tag ranges sel]] && \
4741	    [$w comp sel.first <= insert] && [$w comp sel.last >= insert]} {
4742	$w delete sel.first sel.last
4743    }
4744    $w insert insert $s
4745    $w see insert
4746}
4747
4748## ::tkcon::Expand -
4749# ARGS:	w	- text widget in which to expand str
4750# 	type	- type of expansion (path / proc / variable)
4751# Calls:	::tkcon::Expand(Pathname|Procname|Variable)
4752# Outputs:	The string to match is expanded to the longest possible match.
4753#		If ::tkcon::OPT(showmultiple) is non-zero and the user longest
4754#		match equaled the string to expand, then all possible matches
4755#		are output to stdout.  Triggers bell if no matches are found.
4756# Returns:	number of matches found
4757##
4758proc ::tkcon::Expand {w {type ""}} {
4759    set exp "\[^\\\\\]\[\[ \t\n\r\\\{\"$\]"
4760    set tmp [$w search -backwards -regexp $exp insert-1c limit-1c]
4761    if {[string compare {} $tmp]} {append tmp +2c} else {set tmp limit}
4762    if {[$w compare $tmp >= insert]} return
4763    set str [$w get $tmp insert]
4764    switch -glob $type {
4765	pa* { set res [ExpandPathname $str] }
4766	pr* { set res [ExpandProcname $str] }
4767	v*  { set res [ExpandVariable $str] }
4768	default {
4769	    set res {}
4770	    foreach t $::tkcon::OPT(expandorder) {
4771		if {![catch {Expand$t $str} res] && \
4772			[string compare {} $res]} break
4773	    }
4774	}
4775    }
4776    set len [llength $res]
4777    if {$len} {
4778	$w delete $tmp insert
4779	$w insert $tmp [lindex $res 0]
4780	if {$len > 1} {
4781	    if {$::tkcon::OPT(showmultiple) && \
4782		    ![string compare [lindex $res 0] $str]} {
4783		puts stdout [lsort [lreplace $res 0 0]]
4784	    }
4785	}
4786    } else { bell }
4787    return [incr len -1]
4788}
4789
4790## ::tkcon::ExpandPathname - expand a file pathname based on $str
4791## This is based on UNIX file name conventions
4792# ARGS:	str	- partial file pathname to expand
4793# Calls:	::tkcon::ExpandBestMatch
4794# Returns:	list containing longest unique match followed by all the
4795#		possible further matches
4796##
4797proc ::tkcon::ExpandPathname str {
4798    set pwd [EvalAttached pwd]
4799    # Cause a string like {C:/Program\ Files/} to become "C:/Program Files/"
4800    regsub -all {\\([][ ])} $str {\1} str
4801    if {[catch {EvalAttached [list cd [file dirname $str]]} err]} {
4802	return -code error $err
4803    }
4804    set dir [file tail $str]
4805    ## Check to see if it was known to be a directory and keep the trailing
4806    ## slash if so (file tail cuts it off)
4807    if {[string match */ $str]} { append dir / }
4808    # Create a safely glob-able name
4809    regsub -all {([][])} $dir {\\\1} safedir
4810    if {[catch {lsort [EvalAttached [list glob $safedir*]]} m]} {
4811	set match {}
4812    } else {
4813	if {[llength $m] > 1} {
4814	    global tcl_platform
4815	    if {[string match windows $tcl_platform(platform)]} {
4816		## Windows is screwy because it's case insensitive
4817		set tmp [ExpandBestMatch [string tolower $m] \
4818			[string tolower $dir]]
4819		## Don't change case if we haven't changed the word
4820		if {[string length $dir]==[string length $tmp]} {
4821		    set tmp $dir
4822		}
4823	    } else {
4824		set tmp [ExpandBestMatch $m $dir]
4825	    }
4826	    if {[string match */* $str]} {
4827		set tmp [string trimright [file dirname $str] /]/$tmp
4828	    }
4829	    regsub -all {([^\\])([][ ])} $tmp {\1\\\2} tmp
4830	    set match [linsert $m 0 $tmp]
4831	} else {
4832	    ## This may look goofy, but it handles spaces in path names
4833	    eval append match $m
4834	    if {[file isdirectory $match]} {append match /}
4835	    if {[string match */* $str]} {
4836		set match [string trimright [file dirname $str] /]/$match
4837	    }
4838	    regsub -all {([^\\])([][ ])} $match {\1\\\2} match
4839	    ## Why is this one needed and the ones below aren't!!
4840	    set match [list $match]
4841	}
4842    }
4843    EvalAttached [list cd $pwd]
4844    return $match
4845}
4846
4847## ::tkcon::ExpandProcname - expand a tcl proc name based on $str
4848# ARGS:	str	- partial proc name to expand
4849# Calls:	::tkcon::ExpandBestMatch
4850# Returns:	list containing longest unique match followed by all the
4851#		possible further matches
4852##
4853proc ::tkcon::ExpandProcname str {
4854    set match [EvalAttached [list info commands $str*]]
4855    if {[llength $match] == 0} {
4856	set ns [EvalAttached \
4857		"namespace children \[namespace current\] [list $str*]"]
4858	if {[llength $ns]==1} {
4859	    set match [EvalAttached [list info commands ${ns}::*]]
4860	} else {
4861	    set match $ns
4862	}
4863    }
4864    if {[llength $match] > 1} {
4865	regsub -all {([^\\]) } [ExpandBestMatch $match $str] {\1\\ } str
4866	set match [linsert $match 0 $str]
4867    } else {
4868	regsub -all {([^\\]) } $match {\1\\ } match
4869    }
4870    return $match
4871}
4872
4873## ::tkcon::ExpandVariable - expand a tcl variable name based on $str
4874# ARGS:	str	- partial tcl var name to expand
4875# Calls:	::tkcon::ExpandBestMatch
4876# Returns:	list containing longest unique match followed by all the
4877#		possible further matches
4878##
4879proc ::tkcon::ExpandVariable str {
4880    if {[regexp {([^\(]*)\((.*)} $str junk ary str]} {
4881	## Looks like they're trying to expand an array.
4882	set match [EvalAttached [list array names $ary $str*]]
4883	if {[llength $match] > 1} {
4884	    set vars $ary\([ExpandBestMatch $match $str]
4885	    foreach var $match {lappend vars $ary\($var\)}
4886	    return $vars
4887	} else {set match $ary\($match\)}
4888	## Space transformation avoided for array names.
4889    } else {
4890	set match [EvalAttached [list info vars $str*]]
4891	if {[llength $match] > 1} {
4892	    regsub -all {([^\\]) } [ExpandBestMatch $match $str] {\1\\ } str
4893	    set match [linsert $match 0 $str]
4894	} else {
4895	    regsub -all {([^\\]) } $match {\1\\ } match
4896	}
4897    }
4898    return $match
4899}
4900
4901## ::tkcon::ExpandBestMatch2 - finds the best unique match in a list of names
4902## Improves upon the speed of the below proc only when $l is small
4903## or $e is {}.  $e is extra for compatibility with proc below.
4904# ARGS:	l	- list to find best unique match in
4905# Returns:	longest unique match in the list
4906##
4907proc ::tkcon::ExpandBestMatch2 {l {e {}}} {
4908    set s [lindex $l 0]
4909    if {[llength $l]>1} {
4910	set i [expr {[string length $s]-1}]
4911	foreach l $l {
4912	    while {$i>=0 && [string first $s $l]} {
4913		set s [string range $s 0 [incr i -1]]
4914	    }
4915	}
4916    }
4917    return $s
4918}
4919
4920## ::tkcon::ExpandBestMatch - finds the best unique match in a list of names
4921## The extra $e in this argument allows us to limit the innermost loop a
4922## little further.  This improves speed as $l becomes large or $e becomes long.
4923# ARGS:	l	- list to find best unique match in
4924# 	e	- currently best known unique match
4925# Returns:	longest unique match in the list
4926##
4927proc ::tkcon::ExpandBestMatch {l {e {}}} {
4928    set ec [lindex $l 0]
4929    if {[llength $l]>1} {
4930	set e  [string length $e]; incr e -1
4931	set ei [string length $ec]; incr ei -1
4932	foreach l $l {
4933	    while {$ei>=$e && [string first $ec $l]} {
4934		set ec [string range $ec 0 [incr ei -1]]
4935	    }
4936	}
4937    }
4938    return $ec
4939}
4940
4941# Here is a group of functions that is only used when Tkcon is
4942# executed in a safe interpreter. It provides safe versions of
4943# missing functions. For example:
4944#
4945# - "tk appname" returns "tkcon.tcl" but cannot be set
4946# - "toplevel" is equivalent to 'frame', only it is automatically
4947#   packed.
4948# - The 'source', 'load', 'open', 'file' and 'exit' functions are
4949#   mapped to corresponding functions in the parent interpreter.
4950#
4951# Further on, Tk cannot be really loaded. Still the safe 'load'
4952# provedes a speciall case. The Tk can be divided into 4 groups,
4953# that each has a safe handling procedure.
4954#
4955# - "::tkcon::SafeItem" handles commands like 'button', 'canvas' ......
4956#   Each of these functions has the window name as first argument.
4957# - "::tkcon::SafeManage" handles commands like 'pack', 'place', 'grid',
4958#   'winfo', which can have multiple window names as arguments.
4959# - "::tkcon::SafeWindow" handles all windows, such as '.'. For every
4960#   window created, a new alias is formed which also is handled by
4961#   this function.
4962# - Other (e.g. bind, bindtag, image), which need their own function.
4963#
4964## These functions courtesy Jan Nijtmans (nijtmans@nici.kun.nl)
4965##
4966if {[string compare [info command tk] tk]} {
4967    proc tk {option args} {
4968	if {![string match app* $option]} {
4969	    error "wrong option \"$option\": should be appname"
4970	}
4971	return "tkcon.tcl"
4972    }
4973}
4974
4975if {[string compare [info command toplevel] toplevel]} {
4976    proc toplevel {name args} {
4977	eval frame $name $args
4978	pack $name
4979    }
4980}
4981
4982proc ::tkcon::SafeSource {i f} {
4983    set fd [open $f r]
4984    set r [read $fd]
4985    close $fd
4986    if {[catch {interp eval $i $r} msg]} {
4987	error $msg
4988    }
4989}
4990
4991proc ::tkcon::SafeOpen {i f {m r}} {
4992    set fd [open $f $m]
4993    interp transfer {} $fd $i
4994    return $fd
4995}
4996
4997proc ::tkcon::SafeLoad {i f p} {
4998    global tk_version tk_patchLevel tk_library auto_path
4999    if {[string compare $p Tk]} {
5000	load $f $p $i
5001    } else {
5002	foreach command {button canvas checkbutton entry frame label
5003	listbox message radiobutton scale scrollbar spinbox text toplevel} {
5004	    $i alias $command ::tkcon::SafeItem $i $command
5005	}
5006	$i alias image ::tkcon::SafeImage $i
5007	foreach command {pack place grid destroy winfo} {
5008	    $i alias $command ::tkcon::SafeManage $i $command
5009	}
5010	if {[llength [info command event]]} {
5011	    $i alias event ::tkcon::SafeManage $i $command
5012	}
5013	frame .${i}_dot -width 300 -height 300 -relief raised
5014	pack .${i}_dot -side left
5015	$i alias tk tk
5016	$i alias bind ::tkcon::SafeBind $i
5017	$i alias bindtags ::tkcon::SafeBindtags $i
5018	$i alias . ::tkcon::SafeWindow $i {}
5019	foreach var {tk_version tk_patchLevel tk_library auto_path} {
5020	    $i eval set $var [list [set $var]]
5021	}
5022	$i eval {
5023	    package provide Tk $tk_version
5024	    if {[lsearch -exact $auto_path $tk_library] < 0} {
5025		lappend auto_path $tk_library
5026	    }
5027	}
5028	return ""
5029    }
5030}
5031
5032proc ::tkcon::SafeSubst {i a} {
5033    set arg1 ""
5034    foreach {arg value} $a {
5035	if {![string compare $arg -textvariable] ||
5036	![string compare $arg -variable]} {
5037	    set newvalue "[list $i] $value"
5038	    global $newvalue
5039	    if {[interp eval $i info exists $value]} {
5040		set $newvalue [interp eval $i set $value]
5041	    } else {
5042		catch {unset $newvalue}
5043	    }
5044	    $i eval trace variable $value rwu \{[list tkcon set $newvalue $i]\}
5045	    set value $newvalue
5046	} elseif {![string compare $arg -command]} {
5047	    set value [list $i eval $value]
5048	}
5049	lappend arg1 $arg $value
5050    }
5051    return $arg1
5052}
5053
5054proc ::tkcon::SafeItem {i command w args} {
5055    set args [::tkcon::SafeSubst $i $args]
5056    set code [catch "$command [list .${i}_dot$w] $args" msg]
5057    $i alias $w ::tkcon::SafeWindow $i $w
5058    regsub -all .${i}_dot $msg {} msg
5059    return -code $code $msg
5060}
5061
5062proc ::tkcon::SafeManage {i command args} {
5063    set args1 ""
5064    foreach arg $args {
5065	if {[string match . $arg]} {
5066	    set arg .${i}_dot
5067	} elseif {[string match .* $arg]} {
5068	    set arg ".${i}_dot$arg"
5069	}
5070	lappend args1 $arg
5071    }
5072    set code [catch "$command $args1" msg]
5073    regsub -all .${i}_dot $msg {} msg
5074    return -code $code $msg
5075}
5076
5077#
5078# FIX: this function doesn't work yet if the binding starts with '+'.
5079#
5080proc ::tkcon::SafeBind {i w args} {
5081    if {[string match . $w]} {
5082	set w .${i}_dot
5083    } elseif {[string match .* $w]} {
5084	set w ".${i}_dot$w"
5085    }
5086    if {[llength $args] > 1} {
5087	set args [list [lindex $args 0] \
5088		"[list $i] eval [list [lindex $args 1]]"]
5089    }
5090    set code [catch "bind $w $args" msg]
5091    if {[llength $args] <2 && $code == 0} {
5092	set msg [lindex $msg 3]
5093    }
5094    return -code $code $msg
5095}
5096
5097proc ::tkcon::SafeImage {i option args} {
5098    set code [catch "image $option $args" msg]
5099    if {[string match cr* $option]} {
5100	$i alias $msg $msg
5101    }
5102    return -code $code $msg
5103}
5104
5105proc ::tkcon::SafeBindtags {i w {tags {}}} {
5106    if {[string match . $w]} {
5107	set w .${i}_dot
5108    } elseif {[string match .* $w]} {
5109	set w ".${i}_dot$w"
5110    }
5111    set newtags {}
5112    foreach tag $tags {
5113	if {[string match . $tag]} {
5114	    lappend newtags .${i}_dot
5115	} elseif {[string match .* $tag]} {
5116	    lappend newtags ".${i}_dot$tag"
5117	} else {
5118	    lappend newtags $tag
5119	}
5120    }
5121    if {[string match $tags {}]} {
5122	set code [catch {bindtags $w} msg]
5123	regsub -all \\.${i}_dot $msg {} msg
5124    } else {
5125	set code [catch {bindtags $w $newtags} msg]
5126    }
5127    return -code $code $msg
5128}
5129
5130proc ::tkcon::SafeWindow {i w option args} {
5131    if {[string match conf* $option] && [llength $args] > 1} {
5132	set args [::tkcon::SafeSubst $i $args]
5133    } elseif {[string match itemco* $option] && [llength $args] > 2} {
5134	set args "[list [lindex $args 0]] [::tkcon::SafeSubst $i [lrange $args 1 end]]"
5135    } elseif {[string match cr* $option]} {
5136	if {[llength $args]%2} {
5137	    set args "[list [lindex $args 0]] [::tkcon::SafeSubst $i [lrange $args 1 end]]"
5138	} else {
5139	    set args [::tkcon::SafeSubst $i $args]
5140	}
5141    } elseif {[string match bi* $option] && [llength $args] > 2} {
5142	set args [list [lindex $args 0] [lindex $args 1] "[list $i] eval [list [lindex $args 2]]"]
5143    }
5144    set code [catch ".${i}_dot$w $option $args" msg]
5145    if {$code} {
5146	regsub -all .${i}_dot $msg {} msg
5147    } elseif {[string match conf* $option] || [string match itemco* $option]} {
5148	if {[llength $args] == 1} {
5149	    switch -- $args {
5150		-textvariable - -variable {
5151		    set msg "[lrange $msg 0 3] [list [lrange [lindex $msg 4] 1 end]]"
5152		}
5153		-command - updatecommand {
5154		    set msg "[lrange $msg 0 3] [list [lindex [lindex $msg 4] 2]]"
5155		}
5156	    }
5157	} elseif {[llength $args] == 0} {
5158	    set args1 ""
5159	    foreach el $msg {
5160		switch -- [lindex $el 0] {
5161		    -textvariable - -variable {
5162			set el "[lrange $el 0 3] [list [lrange [lindex $el 4] 1 end]]"
5163		    }
5164		    -command - updatecommand {
5165			set el "[lrange $el 0 3] [list [lindex [lindex $el 4] 2]]"
5166		    }
5167		}
5168		lappend args1 $el
5169	    }
5170	    set msg $args1
5171	}
5172    } elseif {[string match cg* $option] || [string match itemcg* $option]} {
5173	switch -- $args {
5174	    -textvariable - -variable {
5175		set msg [lrange $msg 1 end]
5176	    }
5177	    -command - updatecommand {
5178		set msg [lindex $msg 2]
5179	    }
5180	}
5181    } elseif {[string match bi* $option]} {
5182	if {[llength $args] == 2 && $code == 0} {
5183	    set msg [lindex $msg 2]
5184	}
5185    }
5186    return -code $code $msg
5187}
5188
5189proc ::tkcon::RetrieveFilter {host} {
5190    variable PRIV
5191    set result {}
5192    if {[info exists PRIV(proxy)]} {
5193	if {![regexp "^(localhost|127\.0\.0\.1)" $host]} {
5194	    set result [lrange [split [lindex $PRIV(proxy) 0] :] 0 1]
5195	}
5196    }
5197    return $result
5198}
5199
5200proc ::tkcon::RetrieveAuthentication {} {
5201    package require Tk
5202    if {[catch {package require base64}]} {
5203        if {[catch {package require Trf}]} {
5204            error "base64 support not available"
5205        } else {
5206            set local64 "base64 -mode enc"
5207        }
5208    } else {
5209        set local64 "base64::encode"
5210    }
5211
5212    set dlg [toplevel .auth]
5213    wm title $dlg "Authenticating Proxy Configuration"
5214    set f1 [frame ${dlg}.f1]
5215    set f2 [frame ${dlg}.f2]
5216    button $f2.b -text "OK" -command "destroy $dlg"
5217    pack $f2.b -side right
5218    label $f1.l2 -text "Username"
5219    label $f1.l3 -text "Password"
5220    entry $f1.e2 -textvariable "[namespace current]::conf_userid"
5221    entry $f1.e3 -textvariable "[namespace current]::conf_passwd" -show *
5222    grid $f1.l2 -column 0 -row 0 -sticky e
5223    grid $f1.l3 -column 0 -row 1 -sticky e
5224    grid $f1.e2 -column 1 -row 0 -sticky news
5225    grid $f1.e3 -column 1 -row 1 -sticky news
5226    grid columnconfigure $f1 1 -weight 1
5227    pack $f2 -side bottom -fill x
5228    pack $f1 -side top -anchor n -fill both -expand 1
5229    tkwait window $dlg
5230    set result {}
5231    if {[info exists [namespace current]::conf_userid]} {
5232	set data [subst $[namespace current]::conf_userid]
5233	append data : [subst $[namespace current]::conf_passwd]
5234	set data [$local64 $data]
5235	set result [list "Proxy-Authorization" "Basic $data"]
5236    }
5237    unset [namespace current]::conf_passwd
5238    return $result
5239}
5240
5241proc ::tkcon::Retrieve {} {
5242    # A little bit'o'magic to grab the latest tkcon from CVS and
5243    # save it locally.  It doesn't support proxies though...
5244    variable PRIV
5245
5246    set defExt ""
5247    if {[string match "windows" $::tcl_platform(platform)]} {
5248	set defExt ".tcl"
5249    }
5250    set file [tk_getSaveFile -title "Save Latest tkcon to ..." \
5251	    -defaultextension $defExt \
5252	    -initialdir  [file dirname $PRIV(SCRIPT)] \
5253	    -initialfile [file tail $PRIV(SCRIPT)] \
5254	    -parent $PRIV(root) \
5255	    -filetypes {{"Tcl Files" {.tcl .tk}} {"All Files" {*.*}}}]
5256    if {[string compare $file ""]} {
5257	package require http 2
5258	set token [::http::geturl $PRIV(HEADURL) -timeout 30000]
5259	::http::wait $token
5260	set code [catch {
5261	    if {[::http::status $token] == "ok"} {
5262		set fid [open $file w]
5263		# We don't want newline mode to change
5264		fconfigure $fid -translation binary
5265		set data [::http::data $token]
5266		puts -nonewline $fid $data
5267		close $fid
5268		regexp {Id: tkcon.tcl,v (\d+\.\d+)} $data -> rcsVersion
5269		regexp {version\s+(\d+\.\d[^\n]*)} $data -> tkconVersion
5270	    }
5271	} err]
5272	::http::cleanup $token
5273	if {$code} {
5274	    return -code error $err
5275	} elseif {[tk_messageBox -type yesno -icon info -parent $PRIV(root) \
5276		-title "Retrieved tkcon v$tkconVersion, RCS $rcsVersion" \
5277		-message "Successfully retrieved tkcon v$tkconVersion,\
5278		RCS $rcsVersion.  Shall I resource (not restart) this\
5279		version now?"] == "yes"} {
5280	    set PRIV(SCRIPT) $file
5281	    set PRIV(version) $tkconVersion.$rcsVersion
5282	    ::tkcon::Resource
5283	}
5284    }
5285}
5286
5287## ::tkcon::Resource - re'source's this script into current console
5288## Meant primarily for my development of this program.  It follows
5289## links until the ultimate source is found.
5290##
5291set ::tkcon::PRIV(SCRIPT) [info script]
5292if {!$::tkcon::PRIV(WWW) && [string compare $::tkcon::PRIV(SCRIPT) {}]} {
5293    # we use a catch here because some wrap apps choke on 'file type'
5294    # because TclpLstat wasn't wrappable until 8.4.
5295    catch {
5296	while {[string match link [file type $::tkcon::PRIV(SCRIPT)]]} {
5297	    set link [file readlink $::tkcon::PRIV(SCRIPT)]
5298	    if {[string match relative [file pathtype $link]]} {
5299		set ::tkcon::PRIV(SCRIPT) \
5300			[file join [file dirname $::tkcon::PRIV(SCRIPT)] $link]
5301	    } else {
5302		set ::tkcon::PRIV(SCRIPT) $link
5303	    }
5304	}
5305	catch {unset link}
5306	if {[string match relative [file pathtype $::tkcon::PRIV(SCRIPT)]]} {
5307	    set ::tkcon::PRIV(SCRIPT) [file join [pwd] $::tkcon::PRIV(SCRIPT)]
5308	}
5309    }
5310}
5311
5312proc ::tkcon::Resource {} {
5313    uplevel \#0 {
5314	if {[catch {source -rsrc tkcon}]} { source $::tkcon::PRIV(SCRIPT) }
5315    }
5316    Bindings
5317    InitSlave $::tkcon::OPT(exec)
5318}
5319
5320## Initialize only if we haven't yet
5321##
5322if {![info exists ::tkcon::PRIV(root)] || \
5323	![winfo exists $::tkcon::PRIV(root)]} {
5324    ::tkcon::Init
5325}
5326