# history.tcl -- | |
# | |
# Implementation of the history command. | |
# | |
# Copyright (c) 1997 Sun Microsystems, Inc. | |
# | |
# See the file "license.terms" for information on usage and redistribution of | |
# this file, and for a DISCLAIMER OF ALL WARRANTIES. | |
# | |
# The tcl::history array holds the history list and some additional | |
# bookkeeping variables. | |
# | |
# nextid the index used for the next history list item. | |
# keep the max size of the history list | |
# oldest the index of the oldest item in the history. | |
namespace eval ::tcl { | |
variable history | |
if {![info exists history]} { | |
array set history { | |
nextid 0 | |
keep 20 | |
oldest -20 | |
} | |
} | |
namespace ensemble create -command ::tcl::history -map { | |
add ::tcl::HistAdd | |
change ::tcl::HistChange | |
clear ::tcl::HistClear | |
event ::tcl::HistEvent | |
info ::tcl::HistInfo | |
keep ::tcl::HistKeep | |
nextid ::tcl::HistNextID | |
redo ::tcl::HistRedo | |
} | |
} | |
# history -- | |
# | |
# This is the main history command. See the man page for its interface. | |
# This does some argument checking and calls the helper ensemble in the | |
# tcl namespace. | |
proc ::history {args} { | |
# If no command given, we're doing 'history info'. Can't be done with an | |
# ensemble unknown handler, as those don't fire when no subcommand is | |
# given at all. | |
if {![llength $args]} { | |
set args info | |
} | |
# Tricky stuff needed to make stack and errors come out right! | |
tailcall apply {arglist {tailcall history {*}$arglist} ::tcl} $args | |
} | |
# (unnamed) -- | |
# | |
# Callback when [::history] is destroyed. Destroys the implementation. | |
# | |
# Parameters: | |
# oldName what the command was called. | |
# newName what the command is now called (an empty string). | |
# op the operation (= delete). | |
# | |
# Results: | |
# none | |
# | |
# Side Effects: | |
# The implementation of the [::history] command ceases to exist. | |
trace add command ::history delete [list apply {{oldName newName op} { | |
variable history | |
unset -nocomplain history | |
foreach c [info procs ::tcl::Hist*] { | |
rename $c {} | |
} | |
rename ::tcl::history {} | |
} ::tcl}] | |
# tcl::HistAdd -- | |
# | |
# Add an item to the history, and optionally eval it at the global scope | |
# | |
# Parameters: | |
# event the command to add | |
# exec (optional) a substring of "exec" causes the command to | |
# be evaled. | |
# Results: | |
# If executing, then the results of the command are returned | |
# | |
# Side Effects: | |
# Adds to the history list | |
proc ::tcl::HistAdd {event {exec {}}} { | |
variable history | |
if { | |
[prefix longest {exec {}} $exec] eq "" | |
&& [llength [info level 0]] == 3 | |
} then { | |
return -code error "bad argument \"$exec\": should be \"exec\"" | |
} | |
# Do not add empty commands to the history | |
if {[string trim $event] eq ""} { | |
return "" | |
} | |
# Maintain the history | |
set history([incr history(nextid)]) $event | |
unset -nocomplain history([incr history(oldest)]) | |
# Only execute if 'exec' (or non-empty prefix of it) given | |
if {$exec eq ""} { | |
return "" | |
} | |
tailcall eval $event | |
} | |
# tcl::HistKeep -- | |
# | |
# Set or query the limit on the length of the history list | |
# | |
# Parameters: | |
# limit (optional) the length of the history list | |
# | |
# Results: | |
# If no limit is specified, the current limit is returned | |
# | |
# Side Effects: | |
# Updates history(keep) if a limit is specified | |
proc ::tcl::HistKeep {{count {}}} { | |
variable history | |
if {[llength [info level 0]] == 1} { | |
return $history(keep) | |
} | |
if {![string is integer -strict $count] || ($count < 0)} { | |
return -code error "illegal keep count \"$count\"" | |
} | |
set oldold $history(oldest) | |
set history(oldest) [expr {$history(nextid) - $count}] | |
for {} {$oldold <= $history(oldest)} {incr oldold} { | |
unset -nocomplain history($oldold) | |
} | |
set history(keep) $count | |
} | |
# tcl::HistClear -- | |
# | |
# Erase the history list | |
# | |
# Parameters: | |
# none | |
# | |
# Results: | |
# none | |
# | |
# Side Effects: | |
# Resets the history array, except for the keep limit | |
proc ::tcl::HistClear {} { | |
variable history | |
set keep $history(keep) | |
unset history | |
array set history [list \ | |
nextid 0 \ | |
keep $keep \ | |
oldest -$keep \ | |
] | |
} | |
# tcl::HistInfo -- | |
# | |
# Return a pretty-printed version of the history list | |
# | |
# Parameters: | |
# num (optional) the length of the history list to return | |
# | |
# Results: | |
# A formatted history list | |
proc ::tcl::HistInfo {{count {}}} { | |
variable history | |
if {[llength [info level 0]] == 1} { | |
set count [expr {$history(keep) + 1}] | |
} elseif {![string is integer -strict $count]} { | |
return -code error "bad integer \"$count\"" | |
} | |
set result {} | |
set newline "" | |
for {set i [expr {$history(nextid) - $count + 1}]} \ | |
{$i <= $history(nextid)} {incr i} { | |
if {![info exists history($i)]} { | |
continue | |
} | |
set cmd [string map [list \n \n\t] [string trimright $history($i) \ \n]] | |
append result $newline[format "%6d %s" $i $cmd] | |
set newline \n | |
} | |
return $result | |
} | |
# tcl::HistRedo -- | |
# | |
# Fetch the previous or specified event, execute it, and then replace | |
# the current history item with that event. | |
# | |
# Parameters: | |
# event (optional) index of history item to redo. Defaults to -1, | |
# which means the previous event. | |
# | |
# Results: | |
# Those of the command being redone. | |
# | |
# Side Effects: | |
# Replaces the current history list item with the one being redone. | |
proc ::tcl::HistRedo {{event -1}} { | |
variable history | |
set i [HistIndex $event] | |
if {$i == $history(nextid)} { | |
return -code error "cannot redo the current event" | |
} | |
set cmd $history($i) | |
HistChange $cmd 0 | |
tailcall eval $cmd | |
} | |
# tcl::HistIndex -- | |
# | |
# Map from an event specifier to an index in the history list. | |
# | |
# Parameters: | |
# event index of history item to redo. | |
# If this is a positive number, it is used directly. | |
# If it is a negative number, then it counts back to a previous | |
# event, where -1 is the most recent event. | |
# A string can be matched, either by being the prefix of a | |
# command or by matching a command with string match. | |
# | |
# Results: | |
# The index into history, or an error if the index didn't match. | |
proc ::tcl::HistIndex {event} { | |
variable history | |
if {![string is integer -strict $event]} { | |
for {set i [expr {$history(nextid)-1}]} {[info exists history($i)]} \ | |
{incr i -1} { | |
if {[string match $event* $history($i)]} { | |
return $i | |
} | |
if {[string match $event $history($i)]} { | |
return $i | |
} | |
} | |
return -code error "no event matches \"$event\"" | |
} elseif {$event <= 0} { | |
set i [expr {$history(nextid) + $event}] | |
} else { | |
set i $event | |
} | |
if {$i <= $history(oldest)} { | |
return -code error "event \"$event\" is too far in the past" | |
} | |
if {$i > $history(nextid)} { | |
return -code error "event \"$event\" hasn't occured yet" | |
} | |
return $i | |
} | |
# tcl::HistEvent -- | |
# | |
# Map from an event specifier to the value in the history list. | |
# | |
# Parameters: | |
# event index of history item to redo. See index for a description of | |
# possible event patterns. | |
# | |
# Results: | |
# The value from the history list. | |
proc ::tcl::HistEvent {{event -1}} { | |
variable history | |
set i [HistIndex $event] | |
if {![info exists history($i)]} { | |
return "" | |
} | |
return [string trimright $history($i) \ \n] | |
} | |
# tcl::HistChange -- | |
# | |
# Replace a value in the history list. | |
# | |
# Parameters: | |
# newValue The new value to put into the history list. | |
# event (optional) index of history item to redo. See index for a | |
# description of possible event patterns. This defaults to 0, | |
# which specifies the current event. | |
# | |
# Side Effects: | |
# Changes the history list. | |
proc ::tcl::HistChange {newValue {event 0}} { | |
variable history | |
set i [HistIndex $event] | |
set history($i) $newValue | |
} | |
# tcl::HistNextID -- | |
# | |
# Returns the number of the next history event. | |
# | |
# Parameters: | |
# None. | |
# | |
# Side Effects: | |
# None. | |
proc ::tcl::HistNextID {} { | |
variable history | |
return [expr {$history(nextid) + 1}] | |
} | |
return | |
# Local Variables: | |
# mode: tcl | |
# fill-column: 78 | |
# End: | |