Version 3 of Command completion in the iFile console

Updated 2005-08-15 21:41:35

if {0} {

 [fr]
 a prototype of word completion
 instant command completion in the [iFile 1.1] console
 completion works on first word only in ComboBox entry
 the string appended is the longest common beginning of all possible endings
 clickable continuations are displayed in a toplevel widget
 start typing a character, then repeatedly invoke the created buttons to get the wished command

 usage:
 append this to the iFile script
 or save this as q.tcl in same dir as iFile.tcl
 append following line to iFile.tcl
 PC:
 source [file join [file dirname [info script]] q.tcl]
 on PDA (WinCE):
 source q.tcl
 additional requirements:
 font_ce, puts_ce
 known bug:
 continuations not up to date when using the keyboard (e.g. tk_messageBox, open, option)
 }

 namespace eval ::q {
   variable tracedvar
   variable entry
   variable pp ""
   variable pattern ""
   variable temp [list]
   variable suffixlist [list]
   variable packages [lsort [package names]]
   variable added_prefixes [list]
 }
 proc ::q::qcoco {entryname varname x mode} {
 #        puts varname=$varname; puts x=$x; puts mode=$mode
   # completion for the first word
   if {$::q::ok} {
           set v ::
           append v $varname\($x\)
           set v [subst $$v]
           if {![regexp {^\S+\s} $v]} {
                   set pos 0
                   # only first word is assumed to be a command
                   set ns ""
                   set colon ""
                   set ::q::pattern ""
                   regexp -nocase {(^[^:]*)(::)*([a-z0-9_:]*)} $v {&1&2&3} ns colon ::q::pattern
                   if {$colon == ""} {
                           set ::q::pattern $ns
                           set ns ::
                   } else {
                           # learn used package names
                           if {[lsearch -sorted $::q::added_prefixes $ns] < 0} {
                                   if {[lsearch -sorted $::q::packages $ns] >=0} {
                                           lappend ::q::added_prefixes $ns
                                           set ::q::added_prefixes [lsort $::q::added_prefixes]
                                   }
                           } 
                   }
                   incr pos [string length $::q::pattern]
                   set pa ^$::q::pattern
                   set ::q::pp $::q::pattern*
                   set l [list]
                   if {[::q::isnopackage $ns]} {
                           namespace eval $ns {set ::q::commands [info command $::q::pp]}
                           foreach x $::q::commands {
                                   lappend l [string range $x $pos end]
                           }
                           foreach idx [lsearch -all -sorted -regexp $::q::added_prefixes $pa] {
                                   lappend l [string range [lindex $::q::added_prefixes $idx] $pos end]
                           }
                           if {0} {
                           # package names
                                   foreach x $::q::packages {
                                           if {[regexp $pa $x]} {
                                                   lappend l [string range $x $pos end]
                                           }
                                   }
                           }
                   } else {
                           set n ::
                           append n $ns
                           append n ::
                           append n $::q::pattern
                           set len [string length $n]
                           append n *
                           foreach x [info proc $n] {
                                   lappend l [string range $x $len end]
                           }
                   }
                   ::q::conti [lsort -unique -dictionary $l]
                   set add ""
                   foreach ff [split [lindex $::q::suffixlist 0] {}] ll [split [lindex $::q::suffixlist end] {}] {
                           expr {$ff==$ll ? [append add $ff] : [break] }
                   }
                   append v $add
                   set xl [llength $l]
                   if {[llength $l] == 1} {
                           # no alternatives exist
                        #append add { }
                           $entryname insert insert $add
                           set ::q::suffixlist [list]  ;# no need to display
                   } else {
                           if {[llength $l]==0} {
                           # invalid character entered
                                   if {![string equal $v :]} {
                                           set prev [$entryname index insert]
                                           incr prev -1
                                           $entryname selection range $prev end
                                   }
                           } else {
                                   $entryname insert insert $add
                                   # display continuations as buttons
                           }
                   }
           } else {
                   set ::q::text ""
                   set ::q::suffixlist [list]
           }
   } else {
           set ::q::ok 1
   }
   ::q::topcon $entryname $varname
 }
 proc ::q::insertpos {entryname x y} {
   # todo
   # calculate position below insert mark of entry
   # add +y positions of .n (notebook).n.f5 (frame) .n.f5.e (ComboBox)
   # plus height of ComboBox
   upvar $x X
   upvar $y Y
   set X 20
   set Y 110
 }

 proc ::q::topcon {w var} {
   set name .qcon
   append name $w
   regsub -all -start 1 {\.} $name _ name
   #catch {destroy $name}
   #destroy & toplevel -> no more winhandle map slots
   set len [llength $::q::suffixlist]
   catch {wm withdraw $name}
   if {$len==0} {return}
   if {$len==1 && [lindex $::q::suffixlist] == { } } {return}
   if {![winfo exists $name]} {
           set widget [toplevel $name]
   } else {
           foreach x [winfo children $name.f] {
                   destroy $x
           }
           destroy $name.f
           set widget $name
   }
   ::q::insertpos $w px py
   wm geometry $widget +$px+$py
   wm overrideredirect $widget 1

   set sw $widget
   set sf [frame $sw.f]
   $sf configure -bg bisque
   set ii 0
   foreach x $::q::suffixlist {
           set j [button $sf.b$ii -text $x -justify left -border 0 -bg white -fg black -activebackground skyblue -activeforeground white -takefocus 0]
           pack $j -side top -padx 2 -pady 1 -anchor w
           $j configure -command [list ::q::button_invoke $w $x]
           incr ii
   }
   pack $sf -fill both
   wm deiconify $name
 }
 proc ::q::button_invoke {entryname suffix} {
   global g
   set t [$entryname cget -text]
   append t $suffix
   $entryname insert insert $suffix
   focus $entryname
   set g(cmd) $t
 }
 proc ::q::isnopackage {v} {
   if {$v==""} {return 1}
   set pattern [format "^%s::" $v]
   foreach x $::q::packages {
           if {[regexp $pattern $x]} {
                   return 0
           }
   }
   return 1
 }
 proc ::q::off {} {
   #set k [trace info variable $::q::tracedvar]
   # trace vdelete ::g(cmd) [lindex $k {0 1}]
   trace vdelete $::q::tracedvar w [list ::q::qcoco $::q::entry]
   set ::q::text "::q::on ;# instant command" 
   bind $::q::entry <Key-BackSpace> {}
 }        
 proc ::q::about {} {
   set t {
 iFile command completion
 basic prototype
 Roland Frank, Aalen 2005
 www.deltadatentechnik.de
 }
   catch {destroy.qab}
   toplevel .qab
   wm title .qab contributors
   label .qab.l -justify center -text $t
   pack .qab.l -pady 10
 }

 proc ::q::on {{entryname .n.f5.e.e}} {
   # activate command completion in "iFile console"
   global g
   set g(cmd) ""
   set ::q::entry $entryname
   set ::q::tracedvar ::[$entryname cget -textvariable]
   set ::q::ok 1
   bind $entryname <Key-BackSpace> {
           set ::q::ok 0
           set ::q::suffixlist [list]
   }
   trace add variable $::q::tracedvar write [list ::q::qcoco $entryname]
   $entryname selection range 0 end
 }
 proc ::q::conti {inlist} {
   set ::q::temp [list]
   foreach x $inlist {
           if {$x == ""} {
                   set x " "
           }
           ::q::merge $x 0
   }
   set ::q::suffixlist $::q::temp
   set text ""
   set n 0
   foreach x $::q::temp {
           incr n [string length $x]
           if {$n>30} {
                   append text \n
                   set n 0
           } else {
                   append text " "
                   incr n
           }
           append text $x
   }
   set ::q::text $text
 }
 proc ::q::merge {value minlen} {
   # merge value to common prefix with last element of list
   set last [lindex $::q::temp end]
   if {$last != ""} {
           set lastlen [string length $last]
           set c ""
           foreach t1 [split $last ""] t2 [split $value ""] {
                   expr {$t1==$t2 ? [append c $t1] : [break]}
           }
           if {$c!=""} {
                   set clen [string length $c]
                   if {$clen>$minlen} {
                           if {$clen==$lastlen} {
                                   # skip
                                   #lappend ::q::temp $value
                           } else {
                                   lset ::q::temp end $c
                           }
                   } else {
                           lappend ::q::temp $value
                   }
           } else {
                   lappend ::q::temp $value
           }
   } else {
           set ::q::temp [list $value]
   }
 }

 .n.f5.e.e configure -text "::q::on ;# instant command, ::q::off" 
 $5.l configure -textvariable ::q::text