blob: 63a2baf44a13d9b2872962b86eac709fd5006e3c [file] [log] [blame]
# Console window for Insight
# Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003 Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License (GPL) as published by
# the Free Software Foundation; either version 2 of the License, or (at
# your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
itcl::body Console::constructor {args} {
global gdbtk_state
window_name "Console Window"
debug "$args"
_build_win
eval itk_initialize $args
add_hook gdb_no_inferior_hook [list $this idle dummy]
# There are a bunch of console prefs that have no UI
# for the user to modify them. In the event that the user
# really wants to change them, they will have to be modified
# in prefs.tcl or by editing .gdbtkinit. When these prefs
# gain a prefs UI, the user may change them dynamically
# and the console window will need notification that they
# have changed. Add them to the following list and
# Console::_update_option.
foreach option {gdb/console/wrap} {
pref add_hook $option [code $this _update_option]
}
set gdbtk_state(console) $this
}
itcl::body Console::destructor {} {
global gdbtk_state
set gdbtk_state(console) ""
remove_hook gdb_no_inferior_hook [list $this idle dummy]
}
itcl::body Console::_build_win {} {
iwidgets::scrolledtext $itk_interior.stext \
-vscrollmode dynamic -textbackground white
set _twin [$itk_interior.stext component text]
_set_wrap [pref get gdb/console/wrap]
$_twin tag configure prompt_tag -foreground [pref get gdb/console/prompt_fg]
$_twin tag configure err_tag -foreground [pref get gdb/console/error_fg]
$_twin tag configure log_tag -foreground [pref get gdb/console/log_fg]
$_twin tag configure target_tag -foreground [pref get gdb/console/target_fg]
$_twin configure -font [pref get gdb/console/font] \
-bg $::Colors(textbg) -fg $::Colors(textfg)
#
# bind editing keys for console window
#
bind $_twin <Return> "$this invoke; break"
bind_plain_key $_twin Control-m "$this invoke; break"
bind_plain_key $_twin Control-j "$this invoke; break"
# History control.
bind_plain_key $_twin Control-p "[code $this _previous]; break"
bind $_twin <Up> "[code $this _previous]; break"
bind_plain_key $_twin Control-n "[code $this _next]; break"
bind $_twin <Down> "[code $this _next]; break"
bind $_twin <Meta-less> "[code $this _first]; break"
bind $_twin <Home> "[code $this _first]; break"
bind $_twin <Meta-greater> "[code $this _last]; break"
bind $_twin <End> "[code $this _last]; break"
bind_plain_key $_twin Control-o "[code $this _operate_and_get_next]; break"
# Tab completion
bind_plain_key $_twin KeyPress-Tab "[code $this _complete]; break"
# Don't let left arrow or ^B go over the prompt
bind_plain_key $_twin Control-b {
if {[%W compare insert <= {cmdmark + 1 char}]} {
break
}
}
bind $_twin <Left> [bind $_twin <Control-b>]
# Don't let Control-h, Delete, or Backspace back up over the prompt.
bind_plain_key $_twin Control-h "[code $this _delete]; break"
bind $_twin <BackSpace> "[code $this _delete]; break"
bind $_twin <Delete> "[code $this _delete 1]; break"
# Control-a moves to start of line.
bind_plain_key $_twin Control-a {
%W mark set insert {cmdmark + 1 char}
%W see {insert linestart}
break
}
# Control-u deletes to start of line.
bind_plain_key $_twin Control-u {
%W delete {cmdmark + 1 char} insert
%W see {insert linestart}
}
# Control-w deletes previous word.
bind_plain_key $_twin Control-w {
if {[%W compare {insert -1c wordstart} > cmdmark]} {
%W delete {insert -1c wordstart} insert
%W see insert
}
}
bind $_twin <Control-Up> "[code $this _search_history]; break"
bind $_twin <Shift-Up> "[code $this _search_history]; break"
bind $_twin <Control-Down> "[code $this _rsearch_history]; break"
bind $_twin <Shift-Down> "[code $this _rsearch_history]; break"
# Don't allow key motion to move insertion point outside the command
# area. This is done by fixing up the insertion point after any key
# movement. We only need to do this after events we do not
# explicitly override. Note that since the edit line is always the
# last line, we can't possibly go past it, so we don't bother
# checking that. Note also that we check for a binding which is
# simply `;'; this lets us handle keys already bound via
# bind_plain_key.
foreach event [bind Text] {
if {[string match *Key* $event]
&& ([bind $_twin $event] == ""
|| [bind $_twin $event] == ";")} {
bind $_twin $event [bind Text $event]
bind $_twin $event {+
if {[%W compare insert <= {cmdmark + 1 char}]} {
%W mark set insert {cmdmark + 1 char}
}
break
}
}
}
# Don't allow mouse to put cursor outside command line. For some
# events we do this by noticing when the cursor is outside the
# range, and then saving the insertion point. For others we notice
# the saved insertion point.
set pretag pre-$_twin
bind $_twin <1> [format {
if {[%%W compare [tk::TextClosestGap %%W %%x %%y] <= cmdmark]} {
%s _insertion [%%W index insert]
} else {
%s _insertion {}
}
} $this $this]
bind $_twin <B1-Motion> [format {
if {[%s _insertion] != ""} {
%%W mark set insert [%s _insertion]
}
} $this $this $this]
# FIXME: has inside information.
bind $_twin <ButtonRelease-1> [format {
tk::CancelRepeat
if {[%s _insertion] != ""} {
%%W mark set insert [%s _insertion]
}
%s _insertion {}
break
} $this $this $this]
# Don't allow inserting text outside the command line. FIXME:
# requires inside information.
# Also make it a little easier to paste by making the button
# drags a little "fuzzy".
bind $_twin <B2-Motion> {
if {!$tk_strictMotif} {
if {($tk::Priv(x) - 2 < %x < $tk::Priv(x) + 2) \
|| ($tk::Priv(y) - 2 < %y < $tk::Priv(y) + 2)} {
set tk::Priv(mouseMoved) 1
}
if {$tk::Priv(mouseMoved)} {
%W scan dragto %x %y
}
}
break
}
bind $_twin <ButtonRelease-2> [format {
if {!$tk::Priv(mouseMoved) || $tk_strictMotif} {
%s
break
}
} [code $this _paste 1]]
bind $_twin <<Paste>> "[code $this _paste 0]; break"
bind $_twin <<PasteSelection>> "[code $this _paste 0]; break"
bind_plain_key $_twin Control-c "event generate $_twin <<Copy>>"
bind_plain_key $_twin Control-v "[code $this _paste 1]; break"
_setprompt
pack $itk_interior.stext -expand yes -fill both
focus $_twin
}
itcl::body Console::idle {event} {
set _running 0
$_top configure -cursor {}
}
# ------------------------------------------------------------------
# METHOD: busy - busy event handler
# ------------------------------------------------------------------
itcl::body Console::busy {event} {
set _running 1
$_top configure -cursor watch
}
# ------------------------------------------------------------------
# METHOD: insert - insert new text in the text widget
# ------------------------------------------------------------------
itcl::body Console::insert {line {tag ""}} {
if {$_needNL} {
$_twin insert {insert linestart} "\n"
}
# Remove all \r characters from line.
set line [join [split $line \r] {}]
$_twin insert {insert -1 line lineend} $line $tag
set nlines [lindex [split [$_twin index end] .] 0]
if {$nlines > $throttle} {
set delta [expr {$nlines - $throttle}]
$_twin delete 1.0 ${delta}.0
}
$_twin see insert
set _needNL 0
::update idletasks
}
# ------------------------------------------------------------------
# NAME: ConsoleWin::_operate_and_get_next
# DESCRIPTION: Invokes the current command and, if this
# command came from the history, arrange for
# the next history command to be inserted once this
# command is finished.
#
# ARGUMENTS: None
# RETURNS: Nothing
# ------------------------------------------------------------------
itcl::body Console::_operate_and_get_next {} {
if {$_histElement >= 0} {
# _pendingHistElement will be used after the new history element
# is pushed. So we must increment it.
set _pendingHistElement [expr {$_histElement + 1}]
}
invoke
}
#-------------------------------------------------------------------
# METHOD: _previous - recall the previous command
# ------------------------------------------------------------------
itcl::body Console::_previous {} {
if {$_histElement == -1} {
# Save partial command.
set _partialCommand [$_twin get {cmdmark + 1 char} {cmdmark lineend}]
}
incr _histElement
set text [lindex $_history $_histElement]
if {$text == ""} {
# No dice.
incr _histElement -1
# FIXME flash window.
} else {
$_twin delete {cmdmark + 1 char} {cmdmark lineend}
$_twin insert {cmdmark + 1 char} $text
}
}
#-------------------------------------------------------------------
# METHOD: _search_history - search history for match
# ------------------------------------------------------------------
itcl::body Console::_search_history {} {
set str [$_twin get {cmdmark + 1 char} {cmdmark lineend}]
if {$_histElement == -1} {
# Save partial command.
set _partialCommand $str
set ix [lsearch $_history ${str}*]
} else {
set str $_partialCommand
set num [expr $_histElement + 1]
set ix [lsearch [lrange $_history $num end] ${str}*]
incr ix $num
}
set text [lindex $_history $ix]
if {$text != ""} {
set _histElement $ix
$_twin delete {cmdmark + 1 char} {cmdmark lineend}
$_twin insert {cmdmark + 1 char} $text
}
}
#-------------------------------------------------------------------
# METHOD: _rsearch_history - search history in reverse for match
# ------------------------------------------------------------------
itcl::body Console::_rsearch_history {} {
if {$_histElement != -1} {
set str $_partialCommand
set num [expr $_histElement - 1]
set ix $num
while {$ix >= 0} {
if {[string match ${str}* [lindex $_history $ix]]} {
break
}
incr ix -1
}
set text ""
if {$ix >= 0} {
set text [lindex $_history $ix]
set _histElement $ix
} else {
set text $_partialCommand
set _histElement -1
}
$_twin delete {cmdmark + 1 char} {cmdmark lineend}
$_twin insert {cmdmark + 1 char} $text
}
}
#-------------------------------------------------------------------
# METHOD: _next - recall the next command (scroll forward)
# ------------------------------------------------------------------
itcl::body Console::_next {} {
if {$_histElement == -1} {
# FIXME flash window.
return
}
incr _histElement -1
if {$_histElement == -1} {
set text $_partialCommand
} else {
set text [lindex $_history $_histElement]
}
$_twin delete {cmdmark + 1 char} {cmdmark lineend}
$_twin insert {cmdmark + 1 char} $text
}
#-------------------------------------------------------------------
# METHOD: _last - get the last history element
# ------------------------------------------------------------------
itcl::body Console::_last {} {
set _histElement 0
_next
}
#-------------------------------------------------------------------
# METHOD: _first - get the first (earliest) history element
# ------------------------------------------------------------------
itcl::body Console::_first {} {
set _histElement [expr {[llength $_history] - 1}]
_previous
}
#-------------------------------------------------------------------
# METHOD: _setprompt - put a prompt at the beginning of a line
# ------------------------------------------------------------------
itcl::body Console::_setprompt {{prompt {}}} {
if {$prompt == ""} {
#set prompt [pref get gdb/console/prompt]
set prompt [gdb_prompt]
} elseif {$prompt == "none"} {
set prompt ""
}
$_twin delete {insert linestart} {insert lineend}
$_twin insert {insert linestart} $prompt prompt_tag
$_twin mark set cmdmark "insert -1 char"
$_twin see insert
if {$_pendingHistElement >= 0} {
set _histElement $_pendingHistElement
set _pendingHistElement -1
_next
}
}
#-------------------------------------------------------------------
# METHOD: gets - get a line of input from the console
# ------------------------------------------------------------------
itcl::body Console::gets {} {
set _input_mode 1
# _setprompt "(input) "
_setprompt none
$_twin delete insert end
$_twin mark set cmdmark {insert -1 char}
bind_plain_key $_twin Control-d "$this invoke 1; break"
bind_plain_key $_twin Control-c "[code $this _cancel]; break"
vwait [scope _input_result]
set _input_mode 0
bind_plain_key $_twin Control-c "event generate $_twin <<Copy>>"
activate
if {$_input_error} {
set _input_error 0
return -code error ""
}
return $_input_result
}
#-------------------------------------------------------------------
# METHOD: cancel - cancel input when ^C is hit
# ------------------------------------------------------------------
itcl::body Console::_cancel {} {
if {$_input_mode} {
set _needNL 1
$_twin mark set insert {insert lineend}
$_twin insert {insert lineend} "^C\n"
incr _invoking
set _input_error 1
set _input_result ""
}
}
#-------------------------------------------------------------------
# METHOD: activate - run this after a command is run
# ------------------------------------------------------------------
itcl::body Console::activate {{prompt {}}} {
if {$_invoking > 0} {
incr _invoking -1
_setprompt $prompt
}
}
#-------------------------------------------------------------------
# METHOD: invoke - invoke a command
# ------------------------------------------------------------------
itcl::body Console::invoke {{controld 0}} {
global gdbtk_state
set text [$_twin get {cmdmark + 1 char} end ]
if { "[string range $text 0 1]" == "tk" } {
if {! [info complete $text] } {
$_twin insert {insert lineend} " \\\n"
$_twin see insert
return
}
}
incr _invoking
set text [string trimright $text \n]
if {$text == ""} {
set text [lindex $_history 0]
$_twin insert {insert lineend} $text
}
$_twin mark set insert {insert lineend}
$_twin insert {insert lineend} "\n"
set ok 0
if {$_running} {
if {[string index $text 0] == "!"} {
set text [string range $text 1 end]
set ok 1
}
}
if {$_input_mode} {
if {!$controld} {append text \n}
set _input_result $text
set _needNL 1
return
}
# Only push new nonempty history items.
if {$text != "" && [lindex $_history 0] != $text} {
lvarpush _history $text
}
set index [$_twin index insert]
# Clear current history element, and current partial element.
set _histElement -1
set _partialCommand ""
# Need a newline before next insert.
set _needNL 1
# run command
if {$gdbtk_state(readline)} {
set gdbtk_state(readline_response) $text
return
}
if {!$_running || $ok} {
set result [catch {gdb_immediate "$text" 1} message]
} else {
set result 1
set message "The debugger is busy."
}
# gdb_immediate may take a while to finish. Exit if
# our window has gone away.
if {![winfo exists $_twin]} { return }
if {$result} {
global errorInfo
dbug W "Error: $errorInfo\n"
$_twin insert end "Error: $message\n" err_tag
} elseif {$message != ""} {
$_twin insert $index "$message\n"
}
# Make the prompt visible again.
activate
# Make sure the insertion point is visible.
$_twin see insert
}
#-------------------------------------------------------------------
# PRIVATE METHOD: _delete - Handle a Delete of some sort.
# ------------------------------------------------------------------
itcl::body Console::_delete {{right 0}} {
# If we are deleting to the right, and we have this turned off,
# delete to the right.
if {$right && ![pref get gdb/console/deleteLeft]} {
set right 0
}
if {!$right} {
set insert_valid [$_twin compare insert > {cmdmark + 1 char}]
set delete_loc "insert-1c"
} else {
set insert_valid [$_twin compare insert > cmdmark]
set delete_loc "insert"
}
# If there is a selection on the command line, delete it,
# If there is a selection above the command line, do a
# regular delete, but don't delete the prompt.
# If there is no selection, do the delete.
if {![catch {$_twin index sel.first}]} {
if {[$_twin compare sel.first <= cmdmark]} {
if {$insert_valid} {
$_twin delete $delete_loc
}
} else {
$_twin delete sel.first sel.last
}
} elseif {$insert_valid} {
$_twin delete $delete_loc
}
}
#-------------------------------------------------------------------
# PRIVATE METHOD: _insertion - Set or get saved insertion point
# ------------------------------------------------------------------
itcl::body Console::_insertion {args} {
if {! [llength $args]} {
return $_saved_insertion
} else {
set _saved_insertion [lindex $args 0]
}
}
# ------------------------------------------------------------------
# METHOD: _paste - paste the selection into the console window
# ------------------------------------------------------------------
itcl::body Console::_paste {{check_primary 1}} {
set sel {}
if {!$check_primary || [catch {selection get} sel] || $sel == ""} {
if {[catch {selection get -selection CLIPBOARD} sel] || $sel == ""} {
return
}
}
#if there is a selection, insert over it:
if {![catch {$_twin index sel.first}]
&& [$_twin compare sel.first > {cmdmark + 1 char}]} {
set point [$_twin index sel.first]
$_twin delete sel.first sel.last
$_twin insert $point $sel
} else {
$_twin insert insert $sel
}
}
# ------------------------------------------------------------------
# METHOD: _find_lcp - Return the longest common prefix in SLIST.
# Can be empty string.
# ------------------------------------------------------------------
itcl::body Console::_find_lcp {slist} {
# Handle trivial cases where list is empty or length 1
if {[llength $slist] <= 1} {return [lindex $slist 0]}
set prefix [lindex $slist 0]
set prefixlast [expr [string length $prefix] - 1]
foreach str [lrange $slist 1 end] {
set test_str [string range $str 0 $prefixlast]
while {[string compare $test_str $prefix] != 0} {
incr prefixlast -1
set prefix [string range $prefix 0 $prefixlast]
set test_str [string range $str 0 $prefixlast]
}
if {$prefixlast < 0} break
}
return $prefix
}
# ------------------------------------------------------------------
# METHOD: _find_completion - Look through COMPLETIONS to generate
# the suffix needed to do command
# ------------------------------------------------------------------
itcl::body Console::_find_completion {cmd completions} {
# Get longest common prefix
set lcp [_find_lcp $completions]
set cmd_len [string length $cmd]
# Return suffix beyond end of cmd
return [string range $lcp $cmd_len end]
}
# ------------------------------------------------------------------
# METHOD: _complete - Command line completion
# ------------------------------------------------------------------
itcl::body Console::_complete {} {
set command_line [$_twin get {cmdmark + 1 char} {cmdmark lineend}]
set choices [gdb_cmd "complete $command_line" 1]
set choices [string trimright $choices \n]
set choices [split $choices \n]
# Just do completion if this is the first tab
if {!$_saw_tab} {
set _saw_tab 1
set completion [_find_completion $command_line $choices]
# Here is where the completion is actually done. If there
# is one match, complete the command and print a space.
# If two or more matches, complete the command and beep.
# If no match, just beep.
switch [llength $choices] {
0 {}
1 {
$_twin insert end "$completion "
set _saw_tab 0
return
}
default {
$_twin insert end $completion
}
}
bell
$_twin see end
bind $_twin <KeyPress> [code $this _reset_tab]
} else {
# User hit another consecutive tab. List the choices.
# Note that at this point, choices may contain commands
# with spaces. We have to lop off everything before (and
# including) the last space so that the completion list
# only shows the possibilities for the last token.
set choices [lsort $choices]
if {[regexp ".* " $command_line prefix]} {
regsub -all $prefix $choices {} choices
}
if {[llength choices] != 0} {
insert "\nCompletions:\n[join $choices \ ]\n"
$_twin see end
bind $_twin <KeyPress> [code $this _reset_tab]
}
}
}
# ------------------------------------------------------------------
# METHOD: _reset_tab - Helper method for tab completion. Used
# to reset the tab when a key is pressed.
# ------------------------------------------------------------------
itcl::body Console::_reset_tab {} {
bind $_twin <KeyPress> {}
set _saw_tab 0
}
# ------------------------------------------------------------------
# METHOD: _set_wrap - Set wrap mode
# ------------------------------------------------------------------
itcl::body Console::_set_wrap {wrap} {
if { $wrap } {
set hsm none
set wv char
} else {
set hsm dynamic
set wv none
}
$itk_interior.stext configure -hscrollmode $hsm
$_twin configure -wrap $wv
}
# ------------------------------------------------------------------
# METHOD: _update_option - Update in response to preference change
# ------------------------------------------------------------------
itcl::body Console::_update_option {name value} {
switch -- $name {
gdb/console/wrap {
_set_wrap $value
}
gdb/console/prompt_fg {
$_twin tag configure prompt_tag -foreground $value
}
gdb/console/error_fg {
$_twin tag configure err_tag -foreground $value
}
}
}
# ------------------------------------------------------------------
# NAME: public method Console::test
# DESCRIPTION: Executes the given command
#
# ARGUMENTS: Command to run
# RETURNS: Return value of command
#
# NOTES: This will only run if env(GDBTK_TEST_RUNNING)==1.
# FOR TESTING ONLY
# ------------------------------------------------------------------
itcl::body Console::test {args} {
global env
if {[info exists env(GDBTK_TEST_RUNNING)] && $env(GDBTK_TEST_RUNNING) == 1} {
return [eval $args]
}
}