The following is taken from [AlphaTcl]. As everybody knows, AlphaTcl is a gold mine of clean and well-documented Tcl code. Note that the code uses [dict] from Tcl 8.5 and [lvarpop] from [TclX]. Pure Tcl 8.4 dict implementations can be found on this Wiki or in tcllib. An emulation of lvarpop is given at the bottom of this page. ############################################################ # histlist ############################################################# # # NAME # histlist - Create, maintain, and query history lists # # SYNOPSIS # histlist create hlName ?size? ?item item ...? # histlist size hlName # histlist clear hlName # histlist destroy hlName # histlist update hlName ?item item ...? # histlist read hlName # histlist back hlName ?pattern? # histlist forth hlName ?pattern? # # _________________________________________________________________ # # # DESCRIPTION # This command performs one of several operations on the variable # given by hlName, which is a history list. A history list is a list # where new entries are appended to the end, and eventual previous # occurrences of the item are deleted. When the list has reached a # specified maximal size, the oldest entry is deleted when a new one # is inserted. One can query this list with the commands ``back'' and # ``forth'', or ask for the whole list. Typical examples are familiar # notions like a ``recent items menu'' or the command history in a # shell. Except for the ``create'' sub-command, hlName must be the # name of an existing history list variable in the current namespace # (or a fully qualified name). The possible sub-commands (which may # be abbreviated) are: # # histlist create hlName size ?args? # Create a history list named hlName. This list is of size size, # and all subsequent arguments are interpreted as items to insert # into the list one by one. So for example the command # histlist create T 3 a b c d c # will create a history list whose content is {b d c}. If the # argument size is not given the default size 15 is assumed. # (Note: up to AlphaTcl 8.0.1, the created variable would be # in the callers namespace, and hence an error would be thrown # if a local variable of the same name already existed. The # present version creates the variable in accordance with # standard Tcl practice: it will be a local variable unless # fully qualified, and unless the variable has previously been # declared by a ``variable'' or ``global'' statement.) # # A history list is implemented as an dict with three entries: # size (the size of the history list) # content (a list of length <= size) # current (the read position: somewhere in the list, or just # outside its range). # # histlist size hlName ?num? # Set the size of the history list hlName to num. If the # argument num is not given, the current size is returned. # (In any case the present (previous) size is returned.) # # histlist clear hlName # Clear all entries in the history list hlName, but leave the # size of it alone. # # histlist destroy hlName # Permanently removes the whole history list hlName. # # histlist update hlName ?args? # Append the items args to the history list given by hlName, # one by one, removing any duplicates, and respecting the size # specification of hlName. It also sets the current read position # equal to the length of the list, so that a subsequent call to # histlist back will yield the last item. (If no arguments are # given after hlName, the current read position is simply reset.) # Examples: # histlist create T 3 a b c # --> a b c # histlist update T x # --> b c x # histlist update c # --> b x c # # histlist read hlName # Return the list content of the history list hlName. # # histlist back hlName ?pattern? # Return the last (previous) entry of hlName, relative to the # read position, and moves the read position one step back. # (If the previous call to histlist was update or create, then # the read position is the length of the list, and histlist back # will then return the previously inserted item. If the previous # call to histlist was back then another call to back will return # the second to last item, etc.) If there are no previous entries # in the list, the empty string is returned. If pattern is # given, only entries matching pattern are considered (glob matching). # That is, the ``back'' step is taken inside the sublist of matching # items. # # histlist forth hlName ?pattern? # Return the next entry of the history list hlName, relative # to the read position. if there is no next item, an empty string # is returned. Since the calls ``histlist update'' and ``histlist # create'' set the read position equal to the length of the list, # ``histlist forth'' is only useful after one some calls to # ``histlist back'', and works mainly as an ``undo'' for back. # The optional pattern argument is treated just as for the back # sub-command. # # EXAMPLES: # histlist create T 6 abc b aa c a # histlist read T # --> abc b aa c a # histlist back T # --> a # histlist back T a* # --> aa # histlist forth T # --> c # histlist forth T c* # --> "" # histlist update T x # histlist update T y # histlist back T # --> y # histlist read T # --> b aa c a x y # histlist destroy T # # REMARKS: # Obviously there is some fine tuning to do, for example with respect # to return values and error messages. If there is any need for it, # a future version of back and forth might also accept -regexp, # -nocase, and -exact, etc... # proc histlist { subCmd hist args } { # uplevel 1 [list variable $hist] if { $subCmd == "create" } { # Create array in namespace of calling proc and initialise parameters: uplevel 1 [list dict set $hist content ""] if { [string is integer -strict [lindex $args 0]] && [lindex $args 0] > 0 } { uplevel 1 [list dict set $hist size [lvarpop args 0]] # (Here we modified args, so that the remaining entries will # be appended to the content when we come into the update switch) } else { uplevel 1 [list dict set $hist size 15] } } upvar 1 $hist A switch -- $subCmd { "update" - "create" { foreach item $args { # If the item is already in the list, delete it: if { [set rep [lsearch -exact [dict get $A content] $item]] > -1 } { set L [dict get $A content] lvarpop L $rep dict set A content $L } # Insert the item in the list: dict lappend A content $item # Truncate: if { [llength [dict get $A content]] > [dict get $A size] } { set L [dict get $A content] lvarpop L 0 dict set A content $L } } # Reset the read position: dict set A current [llength [dict get $A content]] } "back" { set newCurrent [expr {[dict get $A current] - 1}] if { [llength $args] } { # Find occurrences: set indices [lsearch -all -glob [lrange [dict get $A content] 0 $newCurrent] [lindex $args 0]] # Pick the last one: set newCurrent [lindex $indices end] } if { $newCurrent < 0 || $newCurrent == "" } { dict set A current -1 return "" } dict set A current $newCurrent return [lindex [dict get $A content] [dict get $A current]] } "forth" { set newCurrent [expr [dict get $A current] + 1] if { [llength $args] } { # Find next match: set newCurrent [lsearch -glob -start $newCurrent [dict get $A content] [lindex $args 0]] } if { $newCurrent >= [llength [dict get $A content]] || $newCurrent == -1 } { dict set A current [llength [dict get $A content]] return "" } dict set A current $newCurrent return [lindex [dict get $A content] [dict get $A current]] } "read" { return [dict get $A content] } "size" { set oldSize [dict get $A size] set newSize [lindex $args 0] if { [string is integer -strict $newSize] && $newSize > 0 } { dict set A size $newSize } return $oldSize } "clear" { dict set A content "" dict set A current 0 } "destroy" { uplevel 1 [list unset $hist] } default { error "Unknown sub-command to histlist" } } return "" } # If TclX is loaded (if the user has installed a batteries-included # Tcl distribution) then we will have 'lvarpop', but otherwise, we need # to define it here. AlphaTcl doesn't require TclX, so we must # include this to be sure this package will work. This implementation # can probably be made more efficient using 'lset'. if {![llength [info commands lvarpop]]} { proc lvarpop { listname {index ""} {newentry ""} } { # set listname [uplevel 1 [list namespace which -variable $listname]] upvar 1 $listname L if { ![string length $index] } { set index 0 } elseif { $index == "len" } { set index [llength $L] } elseif { $index == "end" } { set index [expr [llength $L] - 1] } set res [lindex $L $index] if { [string length $newentry] } { set L [lreplace $L $index $index $newentry] } else { set L [lreplace $L $index $index] } return $res } }