Improved metadirective handling
[tcl-tlc.git] / scripts / console.itcl
blob1dd953746880a89352e8af85939b7d1afaa35594
1 # vim: ft=tcl foldmethod=marker foldmarker=<<<,>>> shiftwidth=4 ts=4
3 class tlc::Console {
4 inherit tlc::Baselog
6 constructor {args} {}
7 destructor {}
9 public {
10 variable enable 0
11 variable prompt_cb ""
12 variable root_context "::"
13 variable appname ""
14 variable printres 1
16 method register_custom_command {cmd handler}
17 method deregister_custom_command {cmd}
20 private {
21 variable dominos
22 variable context ""
23 variable obj_priv ""
24 variable partial ""
25 variable history
26 variable initialized 0
27 variable custom_commands
29 method check_enable {}
30 method get_cmds {}
31 method prompt1 {}
32 method run_cmd {cmd}
33 method build_ns_context {}
34 method custom_completer {to_complete w_start w_end line}
35 method _eval {cmd}
36 method c {args}
37 method cx_handler {cmd}
38 method ls_handler {cmd}
43 configbody tlc::Console::enable { #<<<1
44 log debug
45 $dominos(check_enable) tip
49 configbody tlc::Console::prompt_cb { #<<<1
50 log debug "prompt_cb: ($prompt_cb)"
51 if {$prompt_cb == ""} {
52 set prompt_cb [code $this prompt1]
53 log debug "overrode blank prompt_cb to default: ([code $this prompt1])"
58 body tlc::Console::constructor {args} { #<<<1
59 array set custom_commands {}
61 log debug
63 array set dominos {}
65 tlc::Domino #auto dominos(check_enable) -name "$this check_enable"
66 $dominos(check_enable) attach_output [code $this check_enable]
68 eval configure $args
70 if {$prompt_cb == ""} {
71 set prompt_cb [code $this prompt1]
74 register_custom_command "cx" [code $this cx_handler]
75 register_custom_command "ls" [code $this ls_handler]
79 body tlc::Console::destructor {} { #<<<1
80 log debug
84 body tlc::Console::check_enable {} { #<<<1
85 log debug
86 if {$enable} {
87 if {!($initialized)} {
88 package require tclreadline
90 if {$appname != ""} {
91 set basedir [file join $::env(HOME) .tlc console_history]
92 set history [file join $basedir $appname]
93 if {![file isdirectory $basedir]} {
94 file mkdir $basedir
96 if {[file exists $history]} {
97 tclreadline::readline initialize $history
101 tclreadline::readline customcompleter [code $this custom_completer]
103 set initialized 1
105 get_cmds
110 body tlc::Console::get_cmds {} { #<<<1
111 log debug
112 while {$enable} {
113 set prompt [uplevel #0 $prompt_cb]
114 log debug "prompt: ($prompt)"
115 set cmd $partial
116 if {$cmd != ""} {
117 append cmd "\n"
119 set new [tclreadline::readline read $prompt]
120 if {[regsub {^after#[0-9]+(.*)$} $new {\1} kludge]} {
121 log warning "Detected bizarre tclreadline bug: removing junk from start of ($new)"
122 set new $kludge
124 append cmd $new
125 log debug "partial: ($partial) new: ($new) cmd: ($cmd)"
126 if {[tclreadline::readline complete $cmd]} {
127 set partial ""
128 run_cmd $cmd
129 } else {
130 set partial $cmd
136 body tlc::Console::prompt1 {} { #<<<1
137 log debug
138 if {$partial != ""} {
139 return "> "
141 if {[info exists appname]} {
142 set app $appname
143 } else {
144 set app "#undef"
146 if {$obj_priv != ""} {
147 if {![itcl::is object $obj_priv]} {
148 return "$app (#invalid# $obj_priv) "
150 set class [$obj_priv info class]
151 return "$app ($class $obj_priv) "
152 } else {
153 return "$app [build_ns_context] "
158 body tlc::Console::run_cmd {cmd} { #<<<1
159 log debug
160 set first [lindex $cmd 0]
162 if {[info exists custom_commands($first)]} {
163 uplevel #0 $custom_commands($first) [list $cmd]
164 } else {
165 try {
166 set res [_eval $cmd]
167 } onok {
168 if {$printres} {
169 puts $res
171 } onerr {
172 default {
173 log error "\n$::errorInfo"
174 set partial ""
179 if {[info exists history]} {
180 tclreadline::readline write $history
185 body tlc::Console::build_ns_context {} { #<<<1
186 if {$root_context == "::"} {
187 set a ""
188 } else {
189 set a $root_context
191 set b [string trimleft $context :]
192 return [join [list $a $b] ::]
196 body tlc::Console::custom_completer {to_complete w_start w_end line} { #<<<1
197 log debug
198 set custom_cmds {
203 set matches {}
204 if {$w_start == 0} {
205 foreach cc $custom_cmds {
206 if {[string match "$to_complete*" cc]} {
207 lappend matches $cc
210 } else {
211 foreach cc $custom_cmds {
212 if {[string match "$cc *" $line]} {
213 set objs {}
214 foreach obj [_eval [list find objects "${to_complete}*"]] {
215 lappend objs $obj
217 set matches [concat $matches $objs]
218 # TODO: assemble ns and obj matches
222 log debug "\npossible matches:\n\t[join $matches \n\t]"
224 if {[llength $matches] > 0} {
225 foreach match $matches {
226 if {![info exists longest]} {
227 set longest $match
228 log debug "initialized longest to ($longest)"
229 } else {
230 while {![string match "${longest}*" $match]} {
231 set longest [string range $longest 0 end-1]
233 log debug "trimmed longest to ($longest) to match ($matches)"
236 log debug "longest: ($longest)"
237 return [concat [list $longest] $matches]
238 } else {
239 return [list $to_complete]
244 body tlc::Console::_eval {cmd} { #<<<1
245 if {$obj_priv != ""} {
246 if {![itcl::is object $obj_priv]} {
247 error "Current context is not an object: ($obj_priv)"
249 set class [$obj_priv info class]
250 set testmeth [lindex $cmd 0]
251 set use_method 0
252 switch -- $testmeth {
253 "scope" -
254 "info" -
255 "code" {
256 set use_method 1
259 default {
260 if {[string range $cmd 0 1] == "::"} {
261 foreach method [namespace inscope $class [list $obj_priv info function]] {
262 if {$testmeth == $method} {
263 set use_method 1
264 break
267 } else {
268 foreach method [namespace inscope $class [list $obj_priv info function]] {
269 if {$testmeth == [namespace tail $method]} {
270 set use_method 1
271 break
277 if {$use_method} {
278 return [namespace inscope $class [concat $obj_priv $cmd]]
279 } else {
280 return [namespace inscope $class $cmd]
282 } else {
283 set fqcontext [build_ns_context]
284 return [namespace inscope $fqcontext $cmd]
289 body tlc::Console::c {args} { #<<<1
290 set build ""
291 foreach name $args {
292 switch -- [string tolower $name] {
293 "black" {append build "\e\[30m"}
294 "red" {append build "\e\[31m"}
295 "green" {append build "\e\[32m"}
296 "yellow" {append build "\e\[33m"}
297 "blue" {append build "\e\[34m"}
298 "purple" {append build "\e\[35m"}
299 "cyan" {append build "\e\[36m"}
300 "white" {append build "\e\[37m"}
301 "bg_black" {append build "\e\[40m"}
302 "bg_red" {append build "\e\[41m"}
303 "bg_green" {append build "\e\[42m"}
304 "bg_yellow" {append build "\e\[43m"}
305 "bg_blue" {append build "\e\[44m"}
306 "bg_purple" {append build "\e\[45m"}
307 "bg_cyan" {append build "\e\[46m"}
308 "bg_white" {append build "\e\[47m"}
309 "inverse" {append build "\e\[7m"}
310 "bold" {append build "\e\[5m"}
311 "underline" {append build "\e\[4m"}
312 "bright" {append build "\e\[1m"}
313 "norm" {append build "\e\[0m"}
314 default {append build ""}
318 return $build
322 body tlc::Console::cx_handler {cmd} { #<<<1
323 if {[llength $cmd] != 2} {
324 log error "wrong # of args: cx <new_ns_context>"
326 set new [lindex $cmd 1]
327 if {[itcl::is object $new]} {
328 set obj_priv [_eval [list namespace origin $new]]
329 log debug "context is now the private scope of $obj_priv"
330 } else {
331 set obj_priv ""
332 set context [string trimright $new :]
333 log debug "context is now the namespace $context"
338 body tlc::Console::ls_handler {cmd} { #<<<1
339 if {$obj_priv != ""} {
340 set showall [expr {[lindex $cmd 1] == "-a"}]
342 set class [$obj_priv info class]
343 set public_v {}
344 set protected_v {}
345 set private_v {}
346 set public_m {}
347 set protected_m {}
348 set private_m {}
350 foreach var [_eval {info variable}] {
351 if {[string match "${class}::*" $var]} {
352 set var [namespace tail $var]
353 } elseif {!($showall)} {
354 continue
356 if {$var == "this"} continue
357 set full [$obj_priv info variable $var]
358 if {[llength $full] == 5} {
359 foreach {prlev type fqname default current} $full break
360 } else {
361 foreach {prlev type fqname default handler current} $full break
364 if {$current == "<undefined>"} {
365 set current ""
366 } else {
367 set current "[c red]\"$current\"[c norm]"
370 set scopename [list @itcl $obj_priv $fqname]
371 if {[string range $var 0 1] == "::"} {
372 if {[_eval [list array exists $fqname]]} {
373 set current "\[array set"
374 foreach {key val} [_eval [list array get $fqname]] {
375 if {[llength $key] > 1} {
376 set key "\"$key\""
378 if {[llength $val] > 1} {
379 set val "\"$val\""
381 append current "\n\t\t\t[c red]$key[c norm]\t[c red]$val[c norm]"
383 append current "\n\t\t\]"
385 } else {
386 if {[array exists $scopename]} {
387 set current "\[array set"
388 foreach {key val} [array get $scopename] {
389 if {[llength $key] > 1} {
390 set key "\"$key\""
392 if {[llength $val] > 1} {
393 set val "\"$val\""
395 append current "\n\t\t\t[c red]$key[c norm]\t[c red]$val[c norm]"
397 append current "\n\t\t\]"
401 lappend ${prlev}_v $type $var $current
404 foreach method [_eval {info function}] {
405 if {[string match "${class}::*" $method]} {
406 set method [namespace tail $method]
407 } elseif {!($showall)} {
408 continue
410 set full [$obj_priv info function $method]
411 foreach {prlev type fqname arglist body} $full break
412 if {$method == "constructor"} {
413 set constructor_args $arglist
414 continue
416 if {$method == "destructor"} {
417 set destructor_exists 1
418 continue
420 lappend ${prlev}_m $type $method $arglist
423 puts "[c green]class[c norm] $class {"
424 puts "\t[c green]inherits[c norm] [$obj_priv info heritage]"
425 puts ""
426 set flag 0
427 if {[info exists constructor_args]} {
428 puts "\t[c green]constructor[c norm] {$constructor_args} {}"
429 set flag 1
431 if {[info exists destructor_exists]} {
432 puts "\t[c green]destructor[c norm] {}"
433 set flag 1
435 if {[info exists flag]} {
436 puts ""
438 set flag 0
439 foreach prlev {public protected private} {
440 set vars [set ${prlev}_v]
441 set methods [set ${prlev}_m]
442 if {[llength $vars] == 0 && [llength $methods] == 0} {
443 continue
445 if {$flag} {
446 puts ""
448 set flag 1
449 puts "\t[c blue]$prlev[c norm] {"
450 foreach {type name current} $vars {
451 if {$current == "<undefined>"} {
452 puts "\t\t[c yellow]$type[c norm] $name"
453 } else {
454 puts "\t\t[c yellow]$type[c norm] $name\t$current"
457 if {[llength $vars] > 0 && [llength $methods] > 0} {
458 puts ""
460 foreach {type name arglist} $methods {
461 puts "\t\t[c green]$type[c norm] $name {$arglist}"
463 puts "\t}"
465 puts "}"
467 # List sub widgets if we are a widget
468 if {[catch {
469 set is_widget [$obj_priv isa itk::Archetype]
470 } errmsg]} {
471 set is_widget 0
473 if {$is_widget} {
474 foreach child [winfo children [$obj_priv info variable w -value]] {
475 puts "$child"
478 # TODO: list sub namespaces and relative objects
479 } else {
484 body tlc::Console::register_custom_command {cmd handler} { #<<<1
485 set custom_commands($cmd) $handler
489 body tlc::Console::deregister_custom_command {cmd} { #<<<1
490 array unset custom_commands $cmd