mirror of
https://github.com/QB64-Phoenix-Edition/QB64pe.git
synced 2024-09-08 12:20:15 +00:00
1150 lines
32 KiB
Tcl
1150 lines
32 KiB
Tcl
# console.tcl --
|
|
#
|
|
# This code constructs the console window for an application. It
|
|
# can be used by non-unix systems that do not have built-in support
|
|
# for shells.
|
|
#
|
|
# Copyright (c) 1995-1997 Sun Microsystems, Inc.
|
|
# Copyright (c) 1998-2000 Ajuba Solutions.
|
|
# Copyright (c) 2007-2008 Daniel A. Steffen <das@users.sourceforge.net>
|
|
#
|
|
# See the file "license.terms" for information on usage and redistribution
|
|
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
|
#
|
|
|
|
# TODO: history - remember partially written command
|
|
|
|
namespace eval ::tk::console {
|
|
variable blinkTime 500 ; # msecs to blink braced range for
|
|
variable blinkRange 1 ; # enable blinking of the entire braced range
|
|
variable magicKeys 1 ; # enable brace matching and proc/var recognition
|
|
variable maxLines 600 ; # maximum # of lines buffered in console
|
|
variable showMatches 1 ; # show multiple expand matches
|
|
variable useFontchooser [llength [info command ::tk::fontchooser]]
|
|
variable inPlugin [info exists embed_args]
|
|
variable defaultPrompt ; # default prompt if tcl_prompt1 isn't used
|
|
|
|
if {$inPlugin} {
|
|
set defaultPrompt {subst {[history nextid] % }}
|
|
} else {
|
|
set defaultPrompt {subst {([file tail [pwd]]) [history nextid] % }}
|
|
}
|
|
}
|
|
|
|
# simple compat function for tkcon code added for this console
|
|
interp alias {} EvalAttached {} consoleinterp eval
|
|
|
|
# ::tk::ConsoleInit --
|
|
# This procedure constructs and configures the console windows.
|
|
#
|
|
# Arguments:
|
|
# None.
|
|
|
|
proc ::tk::ConsoleInit {} {
|
|
if {![consoleinterp eval {set tcl_interactive}]} {
|
|
wm withdraw .
|
|
}
|
|
|
|
if {[tk windowingsystem] eq "aqua"} {
|
|
set mod "Cmd"
|
|
} else {
|
|
set mod "Ctrl"
|
|
}
|
|
|
|
if {[catch {menu .menubar} err]} {
|
|
bgerror "INIT: $err"
|
|
}
|
|
AmpMenuArgs .menubar add cascade -label [mc &File] -menu .menubar.file
|
|
AmpMenuArgs .menubar add cascade -label [mc &Edit] -menu .menubar.edit
|
|
|
|
menu .menubar.file -tearoff 0
|
|
AmpMenuArgs .menubar.file add command -label [mc "&Source..."] \
|
|
-command {tk::ConsoleSource}
|
|
AmpMenuArgs .menubar.file add command -label [mc "&Hide Console"] \
|
|
-command {wm withdraw .}
|
|
AmpMenuArgs .menubar.file add command -label [mc "&Clear Console"] \
|
|
-command {.console delete 1.0 "promptEnd linestart"}
|
|
if {[tk windowingsystem] ne "aqua"} {
|
|
AmpMenuArgs .menubar.file add command -label [mc E&xit] -command {exit}
|
|
}
|
|
|
|
menu .menubar.edit -tearoff 0
|
|
AmpMenuArgs .menubar.edit add command -label [mc Cu&t] -accel "$mod+X"\
|
|
-command {event generate .console <<Cut>>}
|
|
AmpMenuArgs .menubar.edit add command -label [mc &Copy] -accel "$mod+C"\
|
|
-command {event generate .console <<Copy>>}
|
|
AmpMenuArgs .menubar.edit add command -label [mc P&aste] -accel "$mod+V"\
|
|
-command {event generate .console <<Paste>>}
|
|
|
|
if {[tk windowingsystem] ne "win32"} {
|
|
AmpMenuArgs .menubar.edit add command -label [mc Cl&ear] \
|
|
-command {event generate .console <<Clear>>}
|
|
} else {
|
|
AmpMenuArgs .menubar.edit add command -label [mc &Delete] \
|
|
-command {event generate .console <<Clear>>} -accel "Del"
|
|
|
|
AmpMenuArgs .menubar add cascade -label [mc &Help] -menu .menubar.help
|
|
menu .menubar.help -tearoff 0
|
|
AmpMenuArgs .menubar.help add command -label [mc &About...] \
|
|
-command tk::ConsoleAbout
|
|
}
|
|
|
|
AmpMenuArgs .menubar.edit add separator
|
|
if {$::tk::console::useFontchooser} {
|
|
if {[tk windowingsystem] eq "aqua"} {
|
|
.menubar.edit add command -label tk_choose_font_marker
|
|
set index [.menubar.edit index tk_choose_font_marker]
|
|
.menubar.edit entryconfigure $index \
|
|
-label [mc "Show Fonts"]\
|
|
-accelerator "$mod-T"\
|
|
-command [list ::tk::console::FontchooserToggle]
|
|
bind Console <<TkFontchooserVisibility>> \
|
|
[list ::tk::console::FontchooserVisibility $index]
|
|
::tk::console::FontchooserVisibility $index
|
|
} else {
|
|
AmpMenuArgs .menubar.edit add command -label [mc "&Font..."] \
|
|
-command [list ::tk::console::FontchooserToggle]
|
|
}
|
|
bind Console <FocusIn> [list ::tk::console::FontchooserFocus %W 1]
|
|
bind Console <FocusOut> [list ::tk::console::FontchooserFocus %W 0]
|
|
}
|
|
AmpMenuArgs .menubar.edit add command -label [mc "&Increase Font Size"] \
|
|
-accel "$mod++" -command {event generate .console <<Console_FontSizeIncr>>}
|
|
AmpMenuArgs .menubar.edit add command -label [mc "&Decrease Font Size"] \
|
|
-accel "$mod+-" -command {event generate .console <<Console_FontSizeDecr>>}
|
|
AmpMenuArgs .menubar.edit add command -label [mc "Fit To Screen Width"] \
|
|
-command {event generate .console <<Console_FitScreenWidth>>}
|
|
|
|
if {[tk windowingsystem] eq "aqua"} {
|
|
.menubar add cascade -label [mc Window] -menu [menu .menubar.window]
|
|
.menubar add cascade -label [mc Help] -menu [menu .menubar.help]
|
|
}
|
|
|
|
. configure -menu .menubar
|
|
|
|
# See if we can find a better font than the TkFixedFont
|
|
catch {font create TkConsoleFont {*}[font configure TkFixedFont]}
|
|
set families [font families]
|
|
switch -exact -- [tk windowingsystem] {
|
|
aqua { set preferred {Monaco 10} }
|
|
win32 { set preferred {ProFontWindows 8 Consolas 8} }
|
|
default { set preferred {} }
|
|
}
|
|
foreach {family size} $preferred {
|
|
if {[lsearch -exact $families $family] != -1} {
|
|
font configure TkConsoleFont -family $family -size $size
|
|
break
|
|
}
|
|
}
|
|
|
|
# Provide the right border for the text widget (platform dependent).
|
|
::ttk::style layout ConsoleFrame {
|
|
Entry.field -sticky news -border 1 -children {
|
|
ConsoleFrame.padding -sticky news
|
|
}
|
|
}
|
|
::ttk::frame .consoleframe -style ConsoleFrame
|
|
|
|
set con [text .console -yscrollcommand [list .sb set] -setgrid true \
|
|
-borderwidth 0 -highlightthickness 0 -font TkConsoleFont]
|
|
if {[tk windowingsystem] eq "aqua"} {
|
|
scrollbar .sb -command [list $con yview]
|
|
} else {
|
|
::ttk::scrollbar .sb -command [list $con yview]
|
|
}
|
|
pack .sb -in .consoleframe -fill both -side right -padx 1 -pady 1
|
|
pack $con -in .consoleframe -fill both -expand 1 -side left -padx 1 -pady 1
|
|
pack .consoleframe -fill both -expand 1 -side left
|
|
|
|
ConsoleBind $con
|
|
|
|
$con tag configure stderr -foreground red
|
|
$con tag configure stdin -foreground blue
|
|
$con tag configure prompt -foreground \#8F4433
|
|
$con tag configure proc -foreground \#008800
|
|
$con tag configure var -background \#FFC0D0
|
|
$con tag raise sel
|
|
$con tag configure blink -background \#FFFF00
|
|
$con tag configure find -background \#FFFF00
|
|
|
|
focus $con
|
|
|
|
# Avoid listing this console in [winfo interps]
|
|
if {[info command ::send] eq "::send"} {rename ::send {}}
|
|
|
|
wm protocol . WM_DELETE_WINDOW { wm withdraw . }
|
|
wm title . [mc "Console"]
|
|
flush stdout
|
|
$con mark set output [$con index "end - 1 char"]
|
|
tk::TextSetCursor $con end
|
|
$con mark set promptEnd insert
|
|
$con mark gravity promptEnd left
|
|
|
|
# A variant of ConsolePrompt to avoid a 'puts' call
|
|
set w $con
|
|
set temp [$w index "end - 1 char"]
|
|
$w mark set output end
|
|
if {![consoleinterp eval "info exists tcl_prompt1"]} {
|
|
set string [EvalAttached $::tk::console::defaultPrompt]
|
|
$w insert output $string stdout
|
|
}
|
|
$w mark set output $temp
|
|
::tk::TextSetCursor $w end
|
|
$w mark set promptEnd insert
|
|
$w mark gravity promptEnd left
|
|
|
|
if {[tk windowingsystem] ne "aqua"} {
|
|
# Subtle work-around to erase the '% ' that tclMain.c prints out
|
|
after idle [subst -nocommand {
|
|
if {[$con get 1.0 output] eq "% "} { $con delete 1.0 output }
|
|
}]
|
|
}
|
|
}
|
|
|
|
# ::tk::ConsoleSource --
|
|
#
|
|
# Prompts the user for a file to source in the main interpreter.
|
|
#
|
|
# Arguments:
|
|
# None.
|
|
|
|
proc ::tk::ConsoleSource {} {
|
|
set filename [tk_getOpenFile -defaultextension .tcl -parent . \
|
|
-title [mc "Select a file to source"] \
|
|
-filetypes [list \
|
|
[list [mc "Tcl Scripts"] .tcl] \
|
|
[list [mc "All Files"] *]]]
|
|
if {$filename ne ""} {
|
|
set cmd [list source $filename]
|
|
if {[catch {consoleinterp eval $cmd} result]} {
|
|
ConsoleOutput stderr "$result\n"
|
|
}
|
|
}
|
|
}
|
|
|
|
# ::tk::ConsoleInvoke --
|
|
# Processes the command line input. If the command is complete it
|
|
# is evaled in the main interpreter. Otherwise, the continuation
|
|
# prompt is added and more input may be added.
|
|
#
|
|
# Arguments:
|
|
# None.
|
|
|
|
proc ::tk::ConsoleInvoke {args} {
|
|
set ranges [.console tag ranges input]
|
|
set cmd ""
|
|
if {[llength $ranges]} {
|
|
set pos 0
|
|
while {[lindex $ranges $pos] ne ""} {
|
|
set start [lindex $ranges $pos]
|
|
set end [lindex $ranges [incr pos]]
|
|
append cmd [.console get $start $end]
|
|
incr pos
|
|
}
|
|
}
|
|
if {$cmd eq ""} {
|
|
ConsolePrompt
|
|
} elseif {[info complete $cmd]} {
|
|
.console mark set output end
|
|
.console tag delete input
|
|
set result [consoleinterp record $cmd]
|
|
if {$result ne ""} {
|
|
puts $result
|
|
}
|
|
ConsoleHistory reset
|
|
ConsolePrompt
|
|
} else {
|
|
ConsolePrompt partial
|
|
}
|
|
.console yview -pickplace insert
|
|
}
|
|
|
|
# ::tk::ConsoleHistory --
|
|
# This procedure implements command line history for the
|
|
# console. In general is evals the history command in the
|
|
# main interpreter to obtain the history. The variable
|
|
# ::tk::HistNum is used to store the current location in the history.
|
|
#
|
|
# Arguments:
|
|
# cmd - Which action to take: prev, next, reset.
|
|
|
|
set ::tk::HistNum 1
|
|
proc ::tk::ConsoleHistory {cmd} {
|
|
variable HistNum
|
|
|
|
switch $cmd {
|
|
prev {
|
|
incr HistNum -1
|
|
if {$HistNum == 0} {
|
|
set cmd {history event [expr {[history nextid] -1}]}
|
|
} else {
|
|
set cmd "history event $HistNum"
|
|
}
|
|
if {[catch {consoleinterp eval $cmd} cmd]} {
|
|
incr HistNum
|
|
return
|
|
}
|
|
.console delete promptEnd end
|
|
.console insert promptEnd $cmd {input stdin}
|
|
.console see end
|
|
}
|
|
next {
|
|
incr HistNum
|
|
if {$HistNum == 0} {
|
|
set cmd {history event [expr {[history nextid] -1}]}
|
|
} elseif {$HistNum > 0} {
|
|
set cmd ""
|
|
set HistNum 1
|
|
} else {
|
|
set cmd "history event $HistNum"
|
|
}
|
|
if {$cmd ne ""} {
|
|
catch {consoleinterp eval $cmd} cmd
|
|
}
|
|
.console delete promptEnd end
|
|
.console insert promptEnd $cmd {input stdin}
|
|
.console see end
|
|
}
|
|
reset {
|
|
set HistNum 1
|
|
}
|
|
}
|
|
}
|
|
|
|
# ::tk::ConsolePrompt --
|
|
# This procedure draws the prompt. If tcl_prompt1 or tcl_prompt2
|
|
# exists in the main interpreter it will be called to generate the
|
|
# prompt. Otherwise, a hard coded default prompt is printed.
|
|
#
|
|
# Arguments:
|
|
# partial - Flag to specify which prompt to print.
|
|
|
|
proc ::tk::ConsolePrompt {{partial normal}} {
|
|
set w .console
|
|
if {$partial eq "normal"} {
|
|
set temp [$w index "end - 1 char"]
|
|
$w mark set output end
|
|
if {[consoleinterp eval "info exists tcl_prompt1"]} {
|
|
consoleinterp eval "eval \[set tcl_prompt1\]"
|
|
} else {
|
|
puts -nonewline [EvalAttached $::tk::console::defaultPrompt]
|
|
}
|
|
} else {
|
|
set temp [$w index output]
|
|
$w mark set output end
|
|
if {[consoleinterp eval "info exists tcl_prompt2"]} {
|
|
consoleinterp eval "eval \[set tcl_prompt2\]"
|
|
} else {
|
|
puts -nonewline "> "
|
|
}
|
|
}
|
|
flush stdout
|
|
$w mark set output $temp
|
|
::tk::TextSetCursor $w end
|
|
$w mark set promptEnd insert
|
|
$w mark gravity promptEnd left
|
|
::tk::console::ConstrainBuffer $w $::tk::console::maxLines
|
|
$w see end
|
|
}
|
|
|
|
# Copy selected text from the console
|
|
proc ::tk::console::Copy {w} {
|
|
if {![catch {set data [$w get sel.first sel.last]}]} {
|
|
clipboard clear -displayof $w
|
|
clipboard append -displayof $w $data
|
|
}
|
|
}
|
|
# Copies selected text. If the selection is within the current active edit
|
|
# region then it will be cut, if not it is only copied.
|
|
proc ::tk::console::Cut {w} {
|
|
if {![catch {set data [$w get sel.first sel.last]}]} {
|
|
clipboard clear -displayof $w
|
|
clipboard append -displayof $w $data
|
|
if {[$w compare sel.first >= output]} {
|
|
$w delete sel.first sel.last
|
|
}
|
|
}
|
|
}
|
|
# Paste text from the clipboard
|
|
proc ::tk::console::Paste {w} {
|
|
catch {
|
|
set clip [::tk::GetSelection $w CLIPBOARD]
|
|
set list [split $clip \n\r]
|
|
tk::ConsoleInsert $w [lindex $list 0]
|
|
foreach x [lrange $list 1 end] {
|
|
$w mark set insert {end - 1c}
|
|
tk::ConsoleInsert $w "\n"
|
|
tk::ConsoleInvoke
|
|
tk::ConsoleInsert $w $x
|
|
}
|
|
}
|
|
}
|
|
|
|
# Fit TkConsoleFont to window width
|
|
proc ::tk::console::FitScreenWidth {w} {
|
|
set width [winfo screenwidth $w]
|
|
set cwidth [$w cget -width]
|
|
set s -50
|
|
set fit 0
|
|
array set fi [font configure TkConsoleFont]
|
|
while {$s < 0} {
|
|
set fi(-size) $s
|
|
set f [font create {*}[array get fi]]
|
|
set c [font measure $f "eM"]
|
|
font delete $f
|
|
if {$c * $cwidth < 1.667 * $width} {
|
|
font configure TkConsoleFont -size $s
|
|
break
|
|
}
|
|
incr s 2
|
|
}
|
|
}
|
|
|
|
# ::tk::ConsoleBind --
|
|
# This procedure first ensures that the default bindings for the Text
|
|
# class have been defined. Then certain bindings are overridden for
|
|
# the class.
|
|
#
|
|
# Arguments:
|
|
# None.
|
|
|
|
proc ::tk::ConsoleBind {w} {
|
|
bindtags $w [list $w Console PostConsole [winfo toplevel $w] all]
|
|
|
|
## Get all Text bindings into Console
|
|
foreach ev [bind Text] {
|
|
bind Console $ev [bind Text $ev]
|
|
}
|
|
## We really didn't want the newline insertion...
|
|
bind Console <Control-Key-o> {}
|
|
## ...or any Control-v binding (would block <<Paste>>)
|
|
bind Console <Control-Key-v> {}
|
|
|
|
# For the moment, transpose isn't enabled until the console
|
|
# gets and overhaul of how it handles input -- hobbs
|
|
bind Console <Control-Key-t> {}
|
|
|
|
# Ignore all Alt, Meta, and Control keypresses unless explicitly bound.
|
|
# Otherwise, if a widget binding for one of these is defined, the
|
|
# <Keypress> class binding will also fire and insert the character
|
|
# which is wrong.
|
|
|
|
bind Console <Alt-KeyPress> {# nothing }
|
|
bind Console <Meta-KeyPress> {# nothing}
|
|
bind Console <Control-KeyPress> {# nothing}
|
|
|
|
foreach {ev key} {
|
|
<<Console_NextImmediate>> <Control-Key-n>
|
|
<<Console_PrevImmediate>> <Control-Key-p>
|
|
<<Console_PrevSearch>> <Control-Key-r>
|
|
<<Console_NextSearch>> <Control-Key-s>
|
|
|
|
<<Console_Expand>> <Key-Tab>
|
|
<<Console_Expand>> <Key-Escape>
|
|
<<Console_ExpandFile>> <Control-Shift-Key-F>
|
|
<<Console_ExpandProc>> <Control-Shift-Key-P>
|
|
<<Console_ExpandVar>> <Control-Shift-Key-V>
|
|
<<Console_Tab>> <Control-Key-i>
|
|
<<Console_Tab>> <Meta-Key-i>
|
|
<<Console_Eval>> <Key-Return>
|
|
<<Console_Eval>> <Key-KP_Enter>
|
|
|
|
<<Console_Clear>> <Control-Key-l>
|
|
<<Console_KillLine>> <Control-Key-k>
|
|
<<Console_Transpose>> <Control-Key-t>
|
|
<<Console_ClearLine>> <Control-Key-u>
|
|
<<Console_SaveCommand>> <Control-Key-z>
|
|
<<Console_FontSizeIncr>> <Control-Key-plus>
|
|
<<Console_FontSizeDecr>> <Control-Key-minus>
|
|
} {
|
|
event add $ev $key
|
|
bind Console $key {}
|
|
}
|
|
if {[tk windowingsystem] eq "aqua"} {
|
|
foreach {ev key} {
|
|
<<Console_FontSizeIncr>> <Command-Key-plus>
|
|
<<Console_FontSizeDecr>> <Command-Key-minus>
|
|
} {
|
|
event add $ev $key
|
|
bind Console $key {}
|
|
}
|
|
if {$::tk::console::useFontchooser} {
|
|
bind Console <Command-Key-t> [list ::tk::console::FontchooserToggle]
|
|
}
|
|
}
|
|
bind Console <<Console_Expand>> {
|
|
if {[%W compare insert > promptEnd]} {
|
|
::tk::console::Expand %W
|
|
}
|
|
}
|
|
bind Console <<Console_ExpandFile>> {
|
|
if {[%W compare insert > promptEnd]} {
|
|
::tk::console::Expand %W path
|
|
}
|
|
}
|
|
bind Console <<Console_ExpandProc>> {
|
|
if {[%W compare insert > promptEnd]} {
|
|
::tk::console::Expand %W proc
|
|
}
|
|
}
|
|
bind Console <<Console_ExpandVar>> {
|
|
if {[%W compare insert > promptEnd]} {
|
|
::tk::console::Expand %W var
|
|
}
|
|
}
|
|
bind Console <<Console_Eval>> {
|
|
%W mark set insert {end - 1c}
|
|
tk::ConsoleInsert %W "\n"
|
|
tk::ConsoleInvoke
|
|
break
|
|
}
|
|
bind Console <Delete> {
|
|
if {{} ne [%W tag nextrange sel 1.0 end] \
|
|
&& [%W compare sel.first >= promptEnd]} {
|
|
%W delete sel.first sel.last
|
|
} elseif {[%W compare insert >= promptEnd]} {
|
|
%W delete insert
|
|
%W see insert
|
|
}
|
|
}
|
|
bind Console <BackSpace> {
|
|
if {{} ne [%W tag nextrange sel 1.0 end] \
|
|
&& [%W compare sel.first >= promptEnd]} {
|
|
%W delete sel.first sel.last
|
|
} elseif {[%W compare insert != 1.0] && \
|
|
[%W compare insert > promptEnd]} {
|
|
%W delete insert-1c
|
|
%W see insert
|
|
}
|
|
}
|
|
bind Console <Control-h> [bind Console <BackSpace>]
|
|
|
|
bind Console <<LineStart>> {
|
|
if {[%W compare insert < promptEnd]} {
|
|
tk::TextSetCursor %W {insert linestart}
|
|
} else {
|
|
tk::TextSetCursor %W promptEnd
|
|
}
|
|
}
|
|
bind Console <<LineEnd>> {
|
|
tk::TextSetCursor %W {insert lineend}
|
|
}
|
|
bind Console <Control-d> {
|
|
if {[%W compare insert < promptEnd]} {
|
|
break
|
|
}
|
|
%W delete insert
|
|
}
|
|
bind Console <<Console_KillLine>> {
|
|
if {[%W compare insert < promptEnd]} {
|
|
break
|
|
}
|
|
if {[%W compare insert == {insert lineend}]} {
|
|
%W delete insert
|
|
} else {
|
|
%W delete insert {insert lineend}
|
|
}
|
|
}
|
|
bind Console <<Console_Clear>> {
|
|
## Clear console display
|
|
%W delete 1.0 "promptEnd linestart"
|
|
}
|
|
bind Console <<Console_ClearLine>> {
|
|
## Clear command line (Unix shell staple)
|
|
%W delete promptEnd end
|
|
}
|
|
bind Console <Meta-d> {
|
|
if {[%W compare insert >= promptEnd]} {
|
|
%W delete insert {insert wordend}
|
|
}
|
|
}
|
|
bind Console <Meta-BackSpace> {
|
|
if {[%W compare {insert -1c wordstart} >= promptEnd]} {
|
|
%W delete {insert -1c wordstart} insert
|
|
}
|
|
}
|
|
bind Console <Meta-d> {
|
|
if {[%W compare insert >= promptEnd]} {
|
|
%W delete insert {insert wordend}
|
|
}
|
|
}
|
|
bind Console <Meta-BackSpace> {
|
|
if {[%W compare {insert -1c wordstart} >= promptEnd]} {
|
|
%W delete {insert -1c wordstart} insert
|
|
}
|
|
}
|
|
bind Console <Meta-Delete> {
|
|
if {[%W compare insert >= promptEnd]} {
|
|
%W delete insert {insert wordend}
|
|
}
|
|
}
|
|
bind Console <<PrevLine>> {
|
|
tk::ConsoleHistory prev
|
|
}
|
|
bind Console <<NextLine>> {
|
|
tk::ConsoleHistory next
|
|
}
|
|
bind Console <Insert> {
|
|
catch {tk::ConsoleInsert %W [::tk::GetSelection %W PRIMARY]}
|
|
}
|
|
bind Console <KeyPress> {
|
|
tk::ConsoleInsert %W %A
|
|
}
|
|
bind Console <F9> {
|
|
eval destroy [winfo child .]
|
|
source [file join $tk_library console.tcl]
|
|
}
|
|
if {[tk windowingsystem] eq "aqua"} {
|
|
bind Console <Command-q> {
|
|
exit
|
|
}
|
|
}
|
|
bind Console <<Cut>> { ::tk::console::Cut %W }
|
|
bind Console <<Copy>> { ::tk::console::Copy %W }
|
|
bind Console <<Paste>> { ::tk::console::Paste %W }
|
|
|
|
bind Console <<Console_FontSizeIncr>> {
|
|
set size [font configure TkConsoleFont -size]
|
|
if {$size < 0} {set sign -1} else {set sign 1}
|
|
set size [expr {(abs($size) + 1) * $sign}]
|
|
font configure TkConsoleFont -size $size
|
|
if {$::tk::console::useFontchooser} {
|
|
tk fontchooser configure -font TkConsoleFont
|
|
}
|
|
}
|
|
bind Console <<Console_FontSizeDecr>> {
|
|
set size [font configure TkConsoleFont -size]
|
|
if {abs($size) < 2} { return }
|
|
if {$size < 0} {set sign -1} else {set sign 1}
|
|
set size [expr {(abs($size) - 1) * $sign}]
|
|
font configure TkConsoleFont -size $size
|
|
if {$::tk::console::useFontchooser} {
|
|
tk fontchooser configure -font TkConsoleFont
|
|
}
|
|
}
|
|
bind Console <<Console_FitScreenWidth>> {
|
|
::tk::console::FitScreenWidth %W
|
|
}
|
|
|
|
##
|
|
## Bindings for doing special things based on certain keys
|
|
##
|
|
bind PostConsole <Key-parenright> {
|
|
if {"\\" ne [%W get insert-2c]} {
|
|
::tk::console::MatchPair %W \( \) promptEnd
|
|
}
|
|
}
|
|
bind PostConsole <Key-bracketright> {
|
|
if {"\\" ne [%W get insert-2c]} {
|
|
::tk::console::MatchPair %W \[ \] promptEnd
|
|
}
|
|
}
|
|
bind PostConsole <Key-braceright> {
|
|
if {"\\" ne [%W get insert-2c]} {
|
|
::tk::console::MatchPair %W \{ \} promptEnd
|
|
}
|
|
}
|
|
bind PostConsole <Key-quotedbl> {
|
|
if {"\\" ne [%W get insert-2c]} {
|
|
::tk::console::MatchQuote %W promptEnd
|
|
}
|
|
}
|
|
|
|
bind PostConsole <KeyPress> {
|
|
if {"%A" ne ""} {
|
|
::tk::console::TagProc %W
|
|
}
|
|
}
|
|
}
|
|
|
|
# ::tk::ConsoleInsert --
|
|
# Insert a string into a text at the point of the insertion cursor.
|
|
# If there is a selection in the text, and it covers the point of the
|
|
# insertion cursor, then delete the selection before inserting. Insertion
|
|
# is restricted to the prompt area.
|
|
#
|
|
# Arguments:
|
|
# w - The text window in which to insert the string
|
|
# s - The string to insert (usually just a single character)
|
|
|
|
proc ::tk::ConsoleInsert {w s} {
|
|
if {$s eq ""} {
|
|
return
|
|
}
|
|
catch {
|
|
if {[$w compare sel.first <= insert] \
|
|
&& [$w compare sel.last >= insert]} {
|
|
$w tag remove sel sel.first promptEnd
|
|
$w delete sel.first sel.last
|
|
}
|
|
}
|
|
if {[$w compare insert < promptEnd]} {
|
|
$w mark set insert end
|
|
}
|
|
$w insert insert $s {input stdin}
|
|
$w see insert
|
|
}
|
|
|
|
# ::tk::ConsoleOutput --
|
|
#
|
|
# This routine is called directly by ConsolePutsCmd to cause a string
|
|
# to be displayed in the console.
|
|
#
|
|
# Arguments:
|
|
# dest - The output tag to be used: either "stderr" or "stdout".
|
|
# string - The string to be displayed.
|
|
|
|
proc ::tk::ConsoleOutput {dest string} {
|
|
set w .console
|
|
$w insert output $string $dest
|
|
::tk::console::ConstrainBuffer $w $::tk::console::maxLines
|
|
$w see insert
|
|
}
|
|
|
|
# ::tk::ConsoleExit --
|
|
#
|
|
# This routine is called by ConsoleEventProc when the main window of
|
|
# the application is destroyed. Don't call exit - that probably already
|
|
# happened. Just delete our window.
|
|
#
|
|
# Arguments:
|
|
# None.
|
|
|
|
proc ::tk::ConsoleExit {} {
|
|
destroy .
|
|
}
|
|
|
|
# ::tk::ConsoleAbout --
|
|
#
|
|
# This routine displays an About box to show Tcl/Tk version info.
|
|
#
|
|
# Arguments:
|
|
# None.
|
|
|
|
proc ::tk::ConsoleAbout {} {
|
|
tk_messageBox -type ok -message "[mc {Tcl for Windows}]
|
|
|
|
Tcl $::tcl_patchLevel
|
|
Tk $::tk_patchLevel"
|
|
}
|
|
|
|
# ::tk::console::Fontchooser* --
|
|
# Let the user select the console font (TIP 324).
|
|
|
|
proc ::tk::console::FontchooserToggle {} {
|
|
if {[tk fontchooser configure -visible]} {
|
|
tk fontchooser hide
|
|
} else {
|
|
tk fontchooser show
|
|
}
|
|
}
|
|
proc ::tk::console::FontchooserVisibility {index} {
|
|
if {[tk fontchooser configure -visible]} {
|
|
.menubar.edit entryconfigure $index -label [msgcat::mc "Hide Fonts"]
|
|
} else {
|
|
.menubar.edit entryconfigure $index -label [msgcat::mc "Show Fonts"]
|
|
}
|
|
}
|
|
proc ::tk::console::FontchooserFocus {w isFocusIn} {
|
|
if {$isFocusIn} {
|
|
tk fontchooser configure -parent $w -font TkConsoleFont \
|
|
-command [namespace code [list FontchooserApply]]
|
|
} else {
|
|
tk fontchooser configure -parent $w -font {} -command {}
|
|
}
|
|
}
|
|
proc ::tk::console::FontchooserApply {font args} {
|
|
catch {font configure TkConsoleFont {*}[font actual $font]}
|
|
}
|
|
|
|
# ::tk::console::TagProc --
|
|
#
|
|
# Tags a procedure in the console if it's recognized
|
|
# This procedure is not perfect. However, making it perfect wastes
|
|
# too much CPU time...
|
|
#
|
|
# Arguments:
|
|
# w - console text widget
|
|
|
|
proc ::tk::console::TagProc w {
|
|
if {!$::tk::console::magicKeys} {
|
|
return
|
|
}
|
|
set exp "\[^\\\\\]\[\[ \t\n\r\;{}\"\$\]"
|
|
set i [$w search -backwards -regexp $exp insert-1c promptEnd-1c]
|
|
if {$i eq ""} {
|
|
set i promptEnd
|
|
} else {
|
|
append i +2c
|
|
}
|
|
regsub -all "\[\[\\\\\\?\\*\]" [$w get $i "insert-1c wordend"] {\\\0} c
|
|
if {[llength [EvalAttached [list info commands $c]]]} {
|
|
$w tag add proc $i "insert-1c wordend"
|
|
} else {
|
|
$w tag remove proc $i "insert-1c wordend"
|
|
}
|
|
if {[llength [EvalAttached [list info vars $c]]]} {
|
|
$w tag add var $i "insert-1c wordend"
|
|
} else {
|
|
$w tag remove var $i "insert-1c wordend"
|
|
}
|
|
}
|
|
|
|
# ::tk::console::MatchPair --
|
|
#
|
|
# Blinks a matching pair of characters
|
|
# c2 is assumed to be at the text index 'insert'.
|
|
# This proc is really loopy and took me an hour to figure out given
|
|
# all possible combinations with escaping except for escaped \'s.
|
|
# It doesn't take into account possible commenting... Oh well. If
|
|
# anyone has something better, I'd like to see/use it. This is really
|
|
# only efficient for small contexts.
|
|
#
|
|
# Arguments:
|
|
# w - console text widget
|
|
# c1 - first char of pair
|
|
# c2 - second char of pair
|
|
#
|
|
# Calls: ::tk::console::Blink
|
|
|
|
proc ::tk::console::MatchPair {w c1 c2 {lim 1.0}} {
|
|
if {!$::tk::console::magicKeys} {
|
|
return
|
|
}
|
|
if {{} ne [set ix [$w search -back $c1 insert $lim]]} {
|
|
while {
|
|
[string match {\\} [$w get $ix-1c]] &&
|
|
[set ix [$w search -back $c1 $ix-1c $lim]] ne {}
|
|
} {}
|
|
set i1 insert-1c
|
|
while {$ix ne {}} {
|
|
set i0 $ix
|
|
set j 0
|
|
while {[set i0 [$w search $c2 $i0 $i1]] ne {}} {
|
|
append i0 +1c
|
|
if {[string match {\\} [$w get $i0-2c]]} {
|
|
continue
|
|
}
|
|
incr j
|
|
}
|
|
if {!$j} {
|
|
break
|
|
}
|
|
set i1 $ix
|
|
while {$j && [set ix [$w search -back $c1 $ix $lim]] ne {}} {
|
|
if {[string match {\\} [$w get $ix-1c]]} {
|
|
continue
|
|
}
|
|
incr j -1
|
|
}
|
|
}
|
|
if {[string match {} $ix]} {
|
|
set ix [$w index $lim]
|
|
}
|
|
} else {
|
|
set ix [$w index $lim]
|
|
}
|
|
if {$::tk::console::blinkRange} {
|
|
Blink $w $ix [$w index insert]
|
|
} else {
|
|
Blink $w $ix $ix+1c [$w index insert-1c] [$w index insert]
|
|
}
|
|
}
|
|
|
|
# ::tk::console::MatchQuote --
|
|
#
|
|
# Blinks between matching quotes.
|
|
# Blinks just the quote if it's unmatched, otherwise blinks quoted string
|
|
# The quote to match is assumed to be at the text index 'insert'.
|
|
#
|
|
# Arguments:
|
|
# w - console text widget
|
|
#
|
|
# Calls: ::tk::console::Blink
|
|
|
|
proc ::tk::console::MatchQuote {w {lim 1.0}} {
|
|
if {!$::tk::console::magicKeys} {
|
|
return
|
|
}
|
|
set i insert-1c
|
|
set j 0
|
|
while {[set i [$w search -back \" $i $lim]] ne {}} {
|
|
if {[string match {\\} [$w get $i-1c]]} {
|
|
continue
|
|
}
|
|
if {!$j} {
|
|
set i0 $i
|
|
}
|
|
incr j
|
|
}
|
|
if {$j&1} {
|
|
if {$::tk::console::blinkRange} {
|
|
Blink $w $i0 [$w index insert]
|
|
} else {
|
|
Blink $w $i0 $i0+1c [$w index insert-1c] [$w index insert]
|
|
}
|
|
} else {
|
|
Blink $w [$w index insert-1c] [$w index insert]
|
|
}
|
|
}
|
|
|
|
# ::tk::console::Blink --
|
|
#
|
|
# Blinks between n index pairs for a specified duration.
|
|
#
|
|
# Arguments:
|
|
# w - console text widget
|
|
# i1 - start index to blink region
|
|
# i2 - end index of blink region
|
|
# dur - duration in usecs to blink for
|
|
#
|
|
# Outputs:
|
|
# blinks selected characters in $w
|
|
|
|
proc ::tk::console::Blink {w args} {
|
|
eval [list $w tag add blink] $args
|
|
after $::tk::console::blinkTime [list $w] tag remove blink $args
|
|
}
|
|
|
|
# ::tk::console::ConstrainBuffer --
|
|
#
|
|
# This limits the amount of data in the text widget
|
|
# Called by Prompt and ConsoleOutput
|
|
#
|
|
# Arguments:
|
|
# w - console text widget
|
|
# size - # of lines to constrain to
|
|
#
|
|
# Outputs:
|
|
# may delete data in console widget
|
|
|
|
proc ::tk::console::ConstrainBuffer {w size} {
|
|
if {[$w index end] > $size} {
|
|
$w delete 1.0 [expr {int([$w index end])-$size}].0
|
|
}
|
|
}
|
|
|
|
# ::tk::console::Expand --
|
|
#
|
|
# Arguments:
|
|
# ARGS: w - text widget in which to expand str
|
|
# type - type of expansion (path / proc / variable)
|
|
#
|
|
# Calls: ::tk::console::Expand(Pathname|Procname|Variable)
|
|
#
|
|
# Outputs: The string to match is expanded to the longest possible match.
|
|
# If ::tk::console::showMatches is non-zero and the longest match
|
|
# equaled the string to expand, then all possible matches are
|
|
# output to stdout. Triggers bell if no matches are found.
|
|
#
|
|
# Returns: number of matches found
|
|
|
|
proc ::tk::console::Expand {w {type ""}} {
|
|
set exp "\[^\\\\\]\[\[ \t\n\r\\\{\"\\\\\$\]"
|
|
set tmp [$w search -backwards -regexp $exp insert-1c promptEnd-1c]
|
|
if {$tmp eq ""} {
|
|
set tmp promptEnd
|
|
} else {
|
|
append tmp +2c
|
|
}
|
|
if {[$w compare $tmp >= insert]} {
|
|
return
|
|
}
|
|
set str [$w get $tmp insert]
|
|
switch -glob $type {
|
|
path* {
|
|
set res [ExpandPathname $str]
|
|
}
|
|
proc* {
|
|
set res [ExpandProcname $str]
|
|
}
|
|
var* {
|
|
set res [ExpandVariable $str]
|
|
}
|
|
default {
|
|
set res {}
|
|
foreach t {Pathname Procname Variable} {
|
|
if {![catch {Expand$t $str} res] && ($res ne "")} {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
set len [llength $res]
|
|
if {$len} {
|
|
set repl [lindex $res 0]
|
|
$w delete $tmp insert
|
|
$w insert $tmp $repl {input stdin}
|
|
if {($len > 1) && ($::tk::console::showMatches) && ($repl eq $str)} {
|
|
puts stdout [lsort [lreplace $res 0 0]]
|
|
}
|
|
} else {
|
|
bell
|
|
}
|
|
return [incr len -1]
|
|
}
|
|
|
|
# ::tk::console::ExpandPathname --
|
|
#
|
|
# Expand a file pathname based on $str
|
|
# This is based on UNIX file name conventions
|
|
#
|
|
# Arguments:
|
|
# str - partial file pathname to expand
|
|
#
|
|
# Calls: ::tk::console::ExpandBestMatch
|
|
#
|
|
# Returns: list containing longest unique match followed by all the
|
|
# possible further matches
|
|
|
|
proc ::tk::console::ExpandPathname str {
|
|
set pwd [EvalAttached pwd]
|
|
if {[catch {EvalAttached [list cd [file dirname $str]]} err opt]} {
|
|
return -options $opt $err
|
|
}
|
|
set dir [file tail $str]
|
|
## Check to see if it was known to be a directory and keep the trailing
|
|
## slash if so (file tail cuts it off)
|
|
if {[string match */ $str]} {
|
|
append dir /
|
|
}
|
|
if {[catch {lsort [EvalAttached [list glob $dir*]]} m]} {
|
|
set match {}
|
|
} else {
|
|
if {[llength $m] > 1} {
|
|
if { $::tcl_platform(platform) eq "windows" } {
|
|
## Windows is screwy because it's case insensitive
|
|
set tmp [ExpandBestMatch [string tolower $m] \
|
|
[string tolower $dir]]
|
|
## Don't change case if we haven't changed the word
|
|
if {[string length $dir]==[string length $tmp]} {
|
|
set tmp $dir
|
|
}
|
|
} else {
|
|
set tmp [ExpandBestMatch $m $dir]
|
|
}
|
|
if {[string match ?*/* $str]} {
|
|
set tmp [file dirname $str]/$tmp
|
|
} elseif {[string match /* $str]} {
|
|
set tmp /$tmp
|
|
}
|
|
regsub -all { } $tmp {\\ } tmp
|
|
set match [linsert $m 0 $tmp]
|
|
} else {
|
|
## This may look goofy, but it handles spaces in path names
|
|
eval append match $m
|
|
if {[file isdir $match]} {
|
|
append match /
|
|
}
|
|
if {[string match ?*/* $str]} {
|
|
set match [file dirname $str]/$match
|
|
} elseif {[string match /* $str]} {
|
|
set match /$match
|
|
}
|
|
regsub -all { } $match {\\ } match
|
|
## Why is this one needed and the ones below aren't!!
|
|
set match [list $match]
|
|
}
|
|
}
|
|
EvalAttached [list cd $pwd]
|
|
return $match
|
|
}
|
|
|
|
# ::tk::console::ExpandProcname --
|
|
#
|
|
# Expand a tcl proc name based on $str
|
|
#
|
|
# Arguments:
|
|
# str - partial proc name to expand
|
|
#
|
|
# Calls: ::tk::console::ExpandBestMatch
|
|
#
|
|
# Returns: list containing longest unique match followed by all the
|
|
# possible further matches
|
|
|
|
proc ::tk::console::ExpandProcname str {
|
|
set match [EvalAttached [list info commands $str*]]
|
|
if {[llength $match] == 0} {
|
|
set ns [EvalAttached \
|
|
"namespace children \[namespace current\] [list $str*]"]
|
|
if {[llength $ns]==1} {
|
|
set match [EvalAttached [list info commands ${ns}::*]]
|
|
} else {
|
|
set match $ns
|
|
}
|
|
}
|
|
if {[llength $match] > 1} {
|
|
regsub -all { } [ExpandBestMatch $match $str] {\\ } str
|
|
set match [linsert $match 0 $str]
|
|
} else {
|
|
regsub -all { } $match {\\ } match
|
|
}
|
|
return $match
|
|
}
|
|
|
|
# ::tk::console::ExpandVariable --
|
|
#
|
|
# Expand a tcl variable name based on $str
|
|
#
|
|
# Arguments:
|
|
# str - partial tcl var name to expand
|
|
#
|
|
# Calls: ::tk::console::ExpandBestMatch
|
|
#
|
|
# Returns: list containing longest unique match followed by all the
|
|
# possible further matches
|
|
|
|
proc ::tk::console::ExpandVariable str {
|
|
if {[regexp {([^\(]*)\((.*)} $str -> ary str]} {
|
|
## Looks like they're trying to expand an array.
|
|
set match [EvalAttached [list array names $ary $str*]]
|
|
if {[llength $match] > 1} {
|
|
set vars $ary\([ExpandBestMatch $match $str]
|
|
foreach var $match {
|
|
lappend vars $ary\($var\)
|
|
}
|
|
return $vars
|
|
} elseif {[llength $match] == 1} {
|
|
set match $ary\($match\)
|
|
}
|
|
## Space transformation avoided for array names.
|
|
} else {
|
|
set match [EvalAttached [list info vars $str*]]
|
|
if {[llength $match] > 1} {
|
|
regsub -all { } [ExpandBestMatch $match $str] {\\ } str
|
|
set match [linsert $match 0 $str]
|
|
} else {
|
|
regsub -all { } $match {\\ } match
|
|
}
|
|
}
|
|
return $match
|
|
}
|
|
|
|
# ::tk::console::ExpandBestMatch --
|
|
#
|
|
# Finds the best unique match in a list of names.
|
|
# The extra $e in this argument allows us to limit the innermost loop a little
|
|
# further. This improves speed as $l becomes large or $e becomes long.
|
|
#
|
|
# Arguments:
|
|
# l - list to find best unique match in
|
|
# e - currently best known unique match
|
|
#
|
|
# Returns: longest unique match in the list
|
|
|
|
proc ::tk::console::ExpandBestMatch {l {e {}}} {
|
|
set ec [lindex $l 0]
|
|
if {[llength $l]>1} {
|
|
set e [expr {[string length $e] - 1}]
|
|
set ei [expr {[string length $ec] - 1}]
|
|
foreach l $l {
|
|
while {$ei>=$e && [string first $ec $l]} {
|
|
set ec [string range $ec 0 [incr ei -1]]
|
|
}
|
|
}
|
|
}
|
|
return $ec
|
|
}
|
|
|
|
# now initialize the console
|
|
::tk::ConsoleInit
|