Expanding method names in tkcon

APN 2015-04-13: Doing more TclOO stuff, I wanted to be able to expand method names interactively in the same manner that proc names are expanded. Turned out the tkcon already had that for nx, xotcl, nsf so I patched in support for TclOO.

The patch also fixes some limitations in the original code. In particular, it also expands method names when the cursor is not at the end of the line, or within a multi command line like first command ; obj methodcall or when the object is accessed via a variable like $obj methodcall.

The patch below is against tkcon.tcl CVS file version 1.122 (that is the *CVS* file version, not the tkcon version). Tested with Tcl 8.5 and 8.6. Will fail on 8.4 (no apply command) but I cannot be bothered with old Tcl versions.

*** tkcon.tcl.orig        Sat Mar 21 07:44:37 2015
--- tkcon.tcl        Mon Apr 13 09:11:42 2015
*************** proc ::tkcon::Init {args} {
*** 119,125 ****
          if {![info exists COLOR($key)]} { set COLOR($key) $default }
      }
  
-     # expandorder could also include 'Methodname' for XOTcl/NSF methods
      foreach {key default} {
          autoload        {}
          blinktime        500
--- 119,124 ----
*************** proc ::tkcon::Init {args} {
*** 131,137 ****
          debugPrompt        {(level \#$level) debug [history nextid] > }
          dead                {}
          edit                edit
!         expandorder        {Pathname Variable Procname}
          font                {}
          history                48
          hoterrors        1
--- 130,136 ----
          debugPrompt        {(level \#$level) debug [history nextid] > }
          dead                {}
          edit                edit
!         expandorder        {Methodname Pathname Variable Procname}
          font                {}
          history                48
          hoterrors        1
*************** proc ::tkcon::Init {args} {
*** 323,332 ****
                  -main - -e - -eval        { append OPT(maineval) \n$val\n }
                  -package - -load        {
                      lappend OPT(autoload) $val
-                     if {$val eq "nsf" || $val eq "nx" || $val eq "XOTcl" } {
-                         # If xotcl is loaded, prepend expand order for it
-                         set OPT(expandorder) [concat Methodname $OPT(expandorder)]
-                     }
                  }
                  -slave                { append OPT(slaveeval) \n$val\n }
                  -nontcl                { set OPT(nontcl) [regexp -nocase $truth $val]}
--- 322,327 ----
*************** proc ::tkcon::ExpandProcname str {
*** 5836,5851 ****
  #                possible further matches
  ##
  proc ::tkcon::ExpandMethodname str {
  
-     # In a first step, obtain the typed-in cmd from the console
-     set typedCmd [::tkcon::CmdGet $::tkcon::PRIV(console)]
      set obj [lindex $typedCmd 0]
      if {$obj eq $typedCmd} {
          # just a single word, can't be a method expansion
          return -code continue
      }
      # Get the full string after the object
!     set sub [string trimleft [string range $typedCmd [string length [list $obj]] end]]
      if {[EvalAttached [list info exists ::nsf::version]]} {
          # Next Scripting Framework is loaded
          if {![EvalAttached [list ::nsf::object::exists $obj]]} {return -code continue}
--- 5831,5861 ----
  #                possible further matches
  ##
  proc ::tkcon::ExpandMethodname str {
+     # Locate the start of the current command looking back from the insert
+     # cursor to the end of the prompt (mark "limit"). Note the start of the
+     # command may be following a ";", "[", not necessarily the beginning.
+     set start [$::tkcon::PRIV(console) search -backwards -regexp {^|[;\[]\s*} insert-1c limit-1c]
+     if {[string compare {} $start]} {
+         append start +1c
+     } else {
+         set start limit
+     }
+     set typedCmd [string trimleft [$::tkcon::PRIV(console) get $start insert]]
  
      set obj [lindex $typedCmd 0]
      if {$obj eq $typedCmd} {
          # just a single word, can't be a method expansion
          return -code continue
      }
      # Get the full string after the object
!     set sub [string trimleft [string range $typedCmd [string length $obj] end]]
! 
!     # Deal with cases where the object is actually stored in a variable
!     # extract the real object name (ie. $x methodcall).
!     if {[string index $obj 0] eq "\$"} {
!         set obj [EvalAttached [list set [string range $obj 1 end]]]
!     }
! 
      if {[EvalAttached [list info exists ::nsf::version]]} {
          # Next Scripting Framework is loaded
          if {![EvalAttached [list ::nsf::object::exists $obj]]} {return -code continue}
*************** proc ::tkcon::ExpandMethodname str {
*** 5860,5870 ****
          # XOTcl < 2.* is loaded
          if {![EvalAttached [list ::xotcl::Object isobject $obj]]} {return -code continue}
          set cmd [list $obj info methods $sub*]
      } else {
          # No NSF/XOTcl loaded
          return -code continue
      }
- 
      set match [EvalAttached $cmd]
      if {[llength $match] > 1} {
          regsub -all {([^\\]) } [ExpandBestMatch $match $str] {\1\\ } bestMatch
--- 5870,5893 ----
          # XOTcl < 2.* is loaded
          if {![EvalAttached [list ::xotcl::Object isobject $obj]]} {return -code continue}
          set cmd [list $obj info methods $sub*]
+     } elseif {[llength [EvalAttached [list ::info commands oo::define]]]} {
+         if {![EvalAttached "::info object isa object $obj"]} {
+             return -code continue
+         }
+         set cmd [list apply {
+             {obj sub} {
+                 set matches {}
+                 foreach meth [::info object methods $obj -all] {
+                     if {[string match $sub* $meth]} {
+                         lappend matches $meth
+                     }
+                 }
+                 return $matches
+             }} $obj $sub]
      } else {
          # No NSF/XOTcl loaded
          return -code continue
      }
      set match [EvalAttached $cmd]
      if {[llength $match] > 1} {
          regsub -all {([^\\]) } [ExpandBestMatch $match $str] {\1\\ } bestMatch