@@ -1,8 +1,8 @@ #!/usr/locanl/bin/tclsh -# FILE: "/diska/home/joze/src/tclreadline/tclreadlineCompleter.tcl" -# LAST MODIFICATION: "Wed Sep 8 17:30:20 1999 (joze)" +# FILE: "/home/joze/src/tclreadline/tclreadlineCompleter.tcl" +# LAST MODIFICATION: "Fri Sep 10 03:06:31 1999 (joze)" # (C) 1998, 1999 by Johannes Zellner, # $Id$ # --- # # tclreadline -- gnu readline for tcl @@ -27,10 +27,11 @@ # # ================================================================== # done: # +# - after # - append # - array # - bgerror # - binary # - break @@ -114,10 +115,104 @@ # - while # TODO # namespace eval tclreadline { + +# TryFromList will return an empty string, if +# the text typed so far does not match any of the +# elements in list. This might be used to allow +# subsequent filename completion by the builtin +# completer. +# +proc TryFromList {text lst} { + + # puts stderr "(CompleteFromList) \ntext=|$text|" + # puts stderr "(CompleteFromList) lst=|$lst|" + set pre [GetQuotedPrefix ${text}] + set matches [MatchesFromList $text $lst] + + # puts stderr "(CompleteFromList) matches=|$matches|" + if {1 == [llength $matches]} { ; # unique match + # puts stderr \nunique=$matches\n + # puts stderr "\n|${pre}${matches}[Right ${pre}]|\n" + return [string trim ${pre}${matches}[Right ${pre}]] + } elseif {"" != ${matches}} { + # puts stderr \nmore=$matches\n + set longest [CompleteLongest ${matches}] + # puts stderr longest=|$longest| + if {"" == $longest} { + return [string trim "[list $text] ${matches}"] + } else { + return [string trim "${pre}${longest} ${matches}"] + } + } else { + return ""; # nothing to complete + } +} + +# CompleteFromList will never return an empty string. +# completes, if a completion can be done, or ring +# the bell if not. +# +proc CompleteFromList {text lst} { + set result [TryFromList ${text} ${lst}] + if {![llength ${result}]} { + Alert + # return [string trim [list ${text}] ${lst}"] + return [string trim "${text} ${lst}"] + } else { + return ${result} + } +} + +proc MenuFromList {text lst} { + return [CompleteFromList $text $lst] +} +# ??????? +# +# proc MenuFromList {text lst} { +# if {![llength ${text}]} { +# return [string trim "{} ${lst}"] +# } else { +# return [TryFromList ${text} ${lst}] +# } +# } +# + + +#** +# never return an empty string, never complete. +# This is useful for showing options lists for example. +# +proc DisplayHints {lst} { + return [string trim "{} ${lst}"] +} + +#** +# find (partial) matches for `text' in `lst'. +# Ring the bell and return the whole list, if +# the user tries to complete ?..? options or +# <..> hints. +# +proc MatchesFromList {text lst} { + set result "" + set text [StripPrefix $text] + set null [string index $text 0] + if {"<" == $null || "?" == $null} { + Alert + return $lst + } + # puts stderr "(MatchesFromList) text=$text" + # puts stderr "(MatchesFromList) lst=$lst" + foreach word $lst { + if {[string match ${text}* ${word}]} { + lappend result ${word} + } + } + return [string trim $result] +} proc FirstNonOption {line} { set expr_pos 1 foreach word [lrange ${line} 1 end] {; # 0 is the command itself if {"-" != [string index ${word} 0]} { @@ -147,63 +242,16 @@ proc Alert {} { puts -nonewline \a flush stdout } -# AttemptFromList will return an empty string, if -# the text typed so far does not match any of the -# elements in list. This might be used to allow -# subsequent filename completion by the builtin -# completer. -# -proc AttemptFromList {text lst} { - return [string trim [Format [FindInList $text $lst] $text]] -} - -# CompleteFromList will never return an empty string. -# -proc CompleteFromList {text lst} { - set result [AttemptFromList ${text} ${lst}] - if {![llength ${result}]} { - Alert - return [string trim "${text} ${lst}"] - } else { - return ${result} - } -} - -# ??????? -proc MenuFromList {text lst} { - if {![llength ${text}]} { - return [string trim "{} ${lst}"] - } else { - return [AttemptFromList ${text} ${lst}] - } -} - -# never return an empty string -# -proc Menu {lst} { - return [string trim "{} ${lst}"] -} - -proc FindInList {text lst} { - set result "" - foreach word $lst { - if {[string match ${text}* ${word}]} { - lappend result ${word} - } - } - return [string trim $result] -} - # get the longest common completion # e.g. str == {tcl_version tclreadline_version tclreadline_library} -# --> [GetCommon ${str}] == "tcl" +# --> [CompleteLongest ${str}] == "tcl" # -proc GetCommon {str} { +proc CompleteLongest {str} { # puts stderr str=$str set match0 [lindex ${str} 0] set len0 [string length $match0] set no_matches [llength ${str}] set part "" @@ -222,13 +270,13 @@ } # puts stderr part=$part return ${part} } -proc SubCmd {start line} { +proc SplitLine {start line} { set depth 0 - # puts stderr SubCmd + # puts stderr SplitLine for {set i $start} {$i >= 0} {incr i -1} { set c [string index $line $i] if {{;} == $c} { incr i; # discard command break character return [list [expr $start - $i] [string range $line $i end]] @@ -286,26 +334,40 @@ return [ChannelId ${text} outChannel] } proc ChannelId {text {descript channelId} {chs ""}} { if {"" == ${chs}} { + # the `file channels' command is present + # only in pretty new versions. + # if [catch {set chs [file channels]}] { set chs {stdin stdout stderr} } } - if {[llength [set channel [MenuFromList ${text} ${chs}]]]} { + if {[llength [set channel [MatchesFromList ${text} ${chs}]]]} { return ${channel} } else { - return [Menu ${descript}] + return [DisplayHints ${descript}] } } proc QuoteQuotes {line} { regsub -all -- \" $line {\"} line regsub -all -- \{ $line {\{} line; # \}\} (keep the editor happy) return $line } + +proc Trace {varT} { + if {![info exists ::tclreadline::Debug]} {return} + upvar $varT var + if {![info exists var]} { + puts $varT= + } else { + puts $varT=|$var| + } + # puts $var +} #** # get the word position. # @return the word position # @note will returned modified values. @@ -323,73 +385,110 @@ # proc PartPosition {partT startT endT lineT} { upvar $partT part $startT start $endT end $lineT line EventuallyEvaluateFirst part start end line - - # puts stderr "(PartPosition) line\[start\]=[string index $line $start]" - # puts stderr "(PartPosition) part=|$part|" - incr start -1 - if {"\"" == [string index $line $start]} { - incr start -1 - } - # puts stderr "(PartPosition) line=|$line|" - # puts stderr "(PartPosition) start=$start" - set line [string range $line 0 $start] - set line [QuoteQuotes $line] - # puts stderr "(PartPosition) line=|$line|" - set result [llength $line] - # puts stderr $result - return $result + return [Llength [string range $line 0 [expr $start - 1]]] + +# +# set local_start [expr $start - 1] +# set local_start_chr [string index $line $local_start] +# if {"\"" == $local_start_chr || "\{" == $local_start_chr} { +# incr local_start -1 +# } +# +# set pre_text [QuoteQuotes [string range $line 0 $local_start]] +# return [llength $pre_text] +# } proc Right {left} { + # puts left=$left if {"\"" == $left} { - return "" + return {\"} + } elseif {"\\\"" == $left} { + return "\\\"" } elseif {"\{" == $left} { return "\}" } elseif {"\\\{" == $left} { return "\\\}" } return "" } -proc GetPrefix {text} { +proc GetQuotedPrefix {text} { + set null [string index $text 0] + if {"\"" == $null || "\{" == $null} { + return \\$null + } else { + return {} + } +} + +proc CountChar {line char} { + # puts stderr char=|$char| + set found 0 + set pos 0 + while {-1 != [set pos [string first $char $line $pos]]} { + incr pos + incr found + } + return $found +} + +#** +# save `lindex'. works also for non-complete lines +# with opening parentheses or quotes. +# usage as `lindex'. +# +proc Lindex {line pos} { + if {[catch [list set sub [lindex $line $pos]]]} { + set diff [expr [CountChar $line \{] - [CountChar $line \}]] + # puts stderr diff=$diff + for {set i 0} {$i < $diff} {incr i} { ; # \{ keep the editor happy + append line \} + } + # puts stderr line=$line + if {!$diff || [catch [list set sub [lindex $line $pos]]]} { + if {[expr [CountChar $line \"] % 2]} { append line \" } + } + if {[catch [list set sub [lindex $line $pos]]]} { return {} } + } + return $sub +} + +#** +# save `llength' (see above). +# +proc Llength {line} { + set diff 0 + if {[catch [list set len [llength $line]]]} { + set diff [expr [CountChar $line \{] - [CountChar $line \}]] + # puts stderr diff=$diff + for {set i 0} {$i < $diff} {incr i} { ; # \{ keep the editor happy + append line \} + } + if {$diff < 0} { + set diff 0 + } + # puts stderr line=$line + if {!$diff || [catch [list set len [llength $line]]]} { + incr diff + if {[expr [CountChar $line \"] % 2]} { append line \" } + } + if {[catch [list set len [llength $line]]]} { return {} } + } + return [expr $len - $diff] +} + +proc StripPrefix {text} { + # puts "(StripPrefix) text=|$text|" set null [string index $text 0] - # puts null=|$null| - if {"\"" == $null} { - # puts stderr \neins\n - set pre "\\\"" - } elseif {"\{" == $null} { - # puts stderr \nzwei\n - set pre "\\\{" - } else { - # puts stderr \ndrei\n - set pre "" - } - return ${pre} -} - -proc Format {matches {part {}}} { - # puts matches=|$matches| - # puts stderr \npart=|$part|\n - set pre [GetPrefix ${part}] - if {1 == [llength $matches]} { ; # unique match - # puts stderr \nunique=$matches\n - # puts stderr "\n|${pre}${matches}[Right ${pre}]|\n" - return [string trim ${pre}${matches}[Right ${pre}]] - } elseif {"" != ${matches}} { - # puts stderr \nmore=$matches\n - set common [GetCommon ${matches}] - # puts stderr common=|$common| - if {"" == $common} { - return [string trim "[list $part] ${matches}"] - } else { - return [string trim "${pre}${common} ${matches}"] - } - } else { - return ""; # nothing to complete + if {"\"" == $null || "\{" == $null} { + return [string range $text 1 end] + } else { + return $text } } proc ListCompletion {text {level -1}} { # TODO @@ -401,26 +500,23 @@ if {-1 == ${level}} { set level [info level] } else { incr level } - set pre [GetPrefix ${text}] - - if {"" == ${pre}} { - set var ${text} - } else { - set var [string range ${text} 1 end] - } + set pre [GetQuotedPrefix ${text}] + set var [StripPrefix ${text}] + # puts stderr "(VarCompletion) pre=|$pre|" + # puts stderr "(VarCompletion) var=|$var|" # arrays # if {[regexp {([^(]*)\((.*)} ${var} all array name]} { set names [uplevel ${level} array names ${array} ${name}*] if {1 == [llength $names]} { ; # unique match return "${array}(${names})" } elseif {"" != ${names}} { - return "${array}([GetCommon ${names}] ${names}" + return "${array}([CompleteLongest ${names}] ${names}" } else { return ""; # nothing to complete } } @@ -448,16 +544,21 @@ return [VarCompletion ${matches}( ${level}]; # recursion } else { return ${pre}${matches}[Right ${pre}] } } elseif {"" != $matches} { ; # more than one match - set common [GetCommon ${matches}] - if {"" == ${common}} { - return [Format ${matches} ${text}] - } else { - return [string trim "${pre}${common} ${matches}"] - } + #puts stderr "(VarComletion) matches=|$matches|" + #puts stderr "(VarComletion) text=|$text|" +# +# set common [CompleteLongest ${matches}] +# if {"" == ${common}} { +# return [Format ${matches} ${text}] +# } else { +# return [string trim "${pre}${common} ${matches}"] +# } +# + return [CompleteFromList ${text} ${matches}] } else { return ""; # nothing to complete } } @@ -479,10 +580,13 @@ proc CommandsOnlyCompletion {cmd} { return [CommandCompletion ${cmd} commands] } proc CommandCompletion {cmd {action both} {spc ::}} { + # puts stderr "(CommandCompletion) cmd=|$cmd|" + # puts stderr "(CommandCompletion) action=|$action|" + # puts stderr "(CommandCompletion) spc=|$spc|" set quali [namespace qualifiers ${cmd}] if {[llength ${quali}]} { set rec [CommandCompletion [namespace tail ${cmd}] ${action} ${quali}] return [FullQualifiedMatches ${quali} ${rec}] } @@ -539,12 +643,14 @@ # unset new # set matches [FullQualifiedMatches ${namespaces} ${matches}] set namespaces "" } + return [string trim "${matches} ${namespaces}"] + } else { + return [string trim "${matches} ${namespaces}"] } - return [string trim "${matches} ${namespaces}"] } #** # check, if the first argument starts with a '[' # and must be evaluated before continuing. @@ -631,32 +737,34 @@ # % puts bla; put $b # part == put # start == 10 # end == 13 # line == "puts bla; put $b" - # [SubCmd] --> {1 " put $b"} == sub + # [SplitLine] --> {1 " put $b"} == sub # new_start = [lindex $sub 0] == 1 # new_end = [expr $end - ($start - $new_start)] == 4 # new_part == $part == put # new_line = [lindex $sub 1] == " put $b" # - } elseif {"" != [set sub [SubCmd $start $line]]} { + } elseif {"" != [set sub [SplitLine $start $line]]} { set new_start [lindex $sub 0] set new_end [expr $end - ($start - $new_start)] set new_line [lindex $sub 1] - # puts stderr "(SubCmd) $new_start $new_end $new_line" + # puts stderr "(SplitLine) $new_start $new_end $new_line" return [ScriptCompleter $part $new_start $new_end $new_line] } elseif {0 == [set pos [PartPosition part start end line]]} { # puts stderr "(PartPosition) $part $start $end $line" # set matches [array names known_cmds "[string trim ${part}]*"] set all [CommandCompletion ${part}] + # puts stderr "(ScriptCompleter) all=$all" #puts \nmatches=$matches\n - return [Format $all $part] + # return [Format $all $part] + return [TryFromList $part $all] } else { # try to use $pos further ... # puts stderr |$line| - if {"." == [string index [lindex ${line} 0] 0]} { + if {"." == [string index [string trim ${line}] 0]} { set alias WIDGET } else { set alias [lindex [QuoteQuotes ${line}] 0] } foreach cmd [list ${alias} tclreadline_complete_unknown] { @@ -674,24 +782,20 @@ arguments, but should take exactly 6.] } # remove leading quotes # - if {"\"" == [string index $part 0] \ - || "\{" == [string index $part 0] - } { - set mod [string range $part 1 end] - } else { - set mod $part - } + set mod [StripPrefix $part] + # puts stderr mod=$mod if {[catch [list set script_result \ [complete(${cmd}) $part \ $start $end $line $pos $mod]] ::tclreadline::errorMsg] } { error "error during evaluation of `complete(${cmd})'" } + # puts stderr \nscript_result=|${script_result}| return ${script_result} } } # no specific command completer found. if {"" != [array names known_cmds $cmd]} { @@ -713,31 +817,159 @@ # # ------------------------------------- # TCL # ------------------------------------- + +proc complete(after) {text start end line pos mod} { + set sub [Lindex $line 1] + # puts \npos=$pos + switch -- $pos { + 1 { + return [CompleteFromList ${text} { cancel idle info}] + } + 2 { + switch -- $sub { + cancel { + set after_info [after info] + if {![llength $after_info]} { + return [DisplayHints