feat(tcloo): add document-local TclOO completions, signature help and hints

Add conservative, document-local TclOO type inference and tooling so the server
can offer method completions, signature help, and inlay parameter hints for
statically resolvable TclOO receivers (including `new`/`create`, `my`, and
simple return-chains). Also surface class names as completion items and emit
semantic tokens for class declarations/references.

Notable changes:
- New tcloo_* tools: completion, symbols, and argument parsing; integrated into
  on_completion, signature_help, inlay hint generation and semantic token
  highlighting. Completions are returned early when an OO receiver context is
  detected.
- Use a completion-friendly parser fallback when the main AST fails (TclSyntaxError)
  so editing-in-progress code still yields useful completions.
- Add CompletionItemKind.Class to command kinds, exclude class items from the
  poco completion name cache, and include new unit tests for the TclOO helpers.
This commit is contained in:
Christoph Brandau
2026-09-11 23:38:42 +02:00
parent c28836933c
commit f88a50d4ab
12 changed files with 1007 additions and 42 deletions
+318 -38
View File
@@ -6,24 +6,24 @@ set te875st 11111
#set ::custom_flag(from_move,$::mom_path_name) 1
if {$main == 1 && 1 == 1} {
puts "main"
puts "main"
}
proc test {} {
puts "main"
proc llll {} {}
set rrrrrrr
puts "main"
proc llll {} {}
set rrrrrrr
}
LIB_GE_command_buffer_edit_insert MOM_tool_change_LIB TOOL_CHANGE_AUTO {CUSTOM_after_tool_change_call} mytag after @TOOL_CHANGE_AUTO
MOM_abort
namespace eval myns {
proc add {a b} {
set sum [expr {$a + $b}]
return $sum
}
set config "debug"
proc add {a b} {
set sum [expr {$a + $b}]
return $sum
}
set config "debug"
}
#_________________________________________________________________________________________________
@@ -31,7 +31,7 @@ namespace eval myns {
# Function to output a spacer line or empty line
#_________________________________________________________________________________________________
proc SERVICE_spacer_output {type {length 20} {line_num 0} {output 1}} {
LIB_GE_message [string repeat $type $length] "output_$output" $line_num
LIB_GE_message [string repeat $type $length] "output_$output" $line_num
}
@@ -42,8 +42,8 @@ SERVICE_spacer_output "*" 2 0 0
# Function to delete the file
#_________________________________________________________________________________________________
proc SERVICE_remove_file {file} {
if {![SERVICE_check_file_exists $file]} {return}
MOM_remove_file $file
if {![SERVICE_check_file_exists $file]} {return}
MOM_remove_file $file
}
#_________________________________________________________________________________________________
@@ -51,8 +51,8 @@ proc SERVICE_remove_file {file} {
# Function to check if the file exists
#_________________________________________________________________________________________________
proc SERVICE_check_file_exists {file} {
if {[file exists $file]} {return 1}
return 0
if {[file exists $file]} {return 1}
return 0
}
#_________________________________________________________________________________________________
@@ -60,19 +60,19 @@ proc SERVICE_check_file_exists {file} {
# Ask UDE Info for the Tool
#_________________________________________________________________________________________________
proc SERVICE_ask_ude_tool {pos ude_name tool_name} {
MOM_ask_ude_info $tool_name "tool" $pos
MOM_ask_ude_info $tool_name "tool" $pos
if {[lsearch $::mom_result $ude_name] != -1} {
return 1
}
return 0
if {[lsearch $::mom_result $ude_name] != -1} {
return 1
}
return 0
}
proc MOM_dummy_event_start {} {
global mom_new_item
global mom_new_item_end
global mom_new_item
global mom_new_item_end
#Put your UDE Handler Tcl here
#Put your UDE Handler Tcl here
}
#_________________________________________________________________________________________________
@@ -80,12 +80,12 @@ proc MOM_dummy_event_start {} {
# Ask UDE Info for the Operation
#_________________________________________________________________________________________________
proc SERVICE_ask_ude_operation {pos ude_name path_name} {
MOM_ask_ude_info $path_name "operation" $pos
MOM_ask_ude_info $path_name "operation" $pos
if {[lsearch $::mom_result $ude_name] != -1} {
return 1
}
return 0
if {[lsearch $::mom_result $ude_name] != -1} {
return 1
}
return 0
}
#_________________________________________________________________________________________________
@@ -96,7 +96,7 @@ proc SERVICE_ask_ude_operation {pos ude_name path_name} {
# restore
#_________________________________________________________________________________________________
proc SERVICE_output_handling {handler} {
set ::lib_ge(hidden_output) $handler
set ::lib_ge(hidden_output) $handler
}
#_________________________________________________________________________________________________
@@ -105,21 +105,301 @@ proc SERVICE_output_handling {handler} {
# this function is called in start of program
#_________________________________________________________________________________________________
proc SERVICE_get_tool_data {} {
global mom_tool_data
global mom_operation_info
global mom_tool_data
global mom_operation_info
set mom_tool_data(toollist) ""
set operations $::mom_operation_name_list
foreach operation $operations {
if {[lsearch -exact $mom_tool_data(toollist) $mom_operation_info($operation,tool_name)] == -1} {
lappend mom_tool_data(toollist) $mom_operation_info($operation,tool_name)
}
}
set mom_tool_data(toollist) ""
set operations $::mom_operation_name_list
foreach operation $operations {
if {[lsearch -exact $mom_tool_data(toollist) $mom_operation_info($operation,tool_name)] == -1} {
lappend mom_tool_data(toollist) $mom_operation_info($operation,tool_name)
}
}
}
LIB_GE_command_buffer_edit_replace MOM_end_of_program_LIB END_OF_PROGRAM @END_OF_PROG {
MOM_do_template "end_of_program_rewind"
MOM_do_template "end_of_program_rewind"
} EndOfProgramRewind
SERVICE_remove_file "test"
oo::class create MCS {
variable values
constructor {args} {
for {set i 0} {$i <= 11} {incr i} {
my initValue $i 0.
}
}
destructor {
#puts "[self] is now deleted"
}
method fformat {value {precision 7}} {return [expr {round( 10.0 ** $precision * $value) / (10.0 ** $precision)}]}
method radtodeg {rad} {return [expr {$rad*180.0/(4.0*atan(1.0))}]}
method degtorad {deg} {return [expr {$deg*(4.0*atan(1.0))/180.0}]}
method duplicate {args} {return [[MCS new] initMCS [self]]}
method type {args} {return "::MCS"}
method toArray {args} {
for {set i 0} {$i < [array size values]} {incr i} {
append arr [string cat $i " "] ; append arr [string cat $values($i) " "]
}
return [string trimright $arr]
}
method toLst {args} {
for {set i 0} {$i < [array size values]} {incr i} {
lappend lst $values($i)
}
return $lst
}
method toStr {args} {
for {set i 0} {$i < [array size values]} {incr i} {
if {$i>0} {set str [string cat $str ", "]}
append str $values($i)
}
return $str
}
method toStrF {{precision 7}} {
for {set i 0} {$i < [array size values]} {incr i} {
if {$i>0} {set str [string cat $str ", "]}
append str [my fformat $values($i) 10]
}
return $str
}
method initValue {i value} {set values($i) [expr double($value)] ; return [self]}
method initArray {a} {upvar $a arr ; for {set i 0} {$i < [array size arr]} {incr i} {my initValue $i $arr($i)} ; return [self]}
method initArrayLst {arr_lst} {array set arr $arr_lst ; for {set i 0} {$i < [array size arr]} {incr i} {my initValue $i $arr($i)} ; return [self]}
method initLst {lst} {for {set i 0} {$i < [llength $lst]} {incr i} {my initValue $i [lindex $lst $i]} ; return [self]}
method initMCS {mcs} {
my identity
set p [$mcs org] ; my initOrgP $p ; $p destroy
set v [$mcs xVct] ; my initXvctV $v ; $v destroy
set v [$mcs yVct] ; my initYvctV $v ; $v destroy
set v [$mcs zVct] ; my initZvctV $v ; $v destroy
return [self]
}
method initMTX {mtx} {
set p [$mtx org] ; my initOrg $p ; $p destroy
set v [$mtx xVct] ; my initXvct $v ; $v destroy
set v [$mtx yVct] ; my initYvct $v ; $v destroy
set v [$mtx zVct] ; my initZvct $v ; $v destroy
return [self]
}
method initOrg {dx dy dz} {my initValue 0 $dx ; my initValue 1 $dy ; my initValue 2 $dz ; return [self]}
method initOrgP {p} {return [my initOrg [$p x] [$p y] [$p z]]}
method initXvct {dx dy dz} {my initValue 3 $dx ; my initValue 4 $dy ; my initValue 5 $dz ; return [self]}
method initXvctV {v} {return [my initXvct [$v x] [$v y] [$v z]]}
method initYvct {dx dy dz} {my initValue 6 $dx ; my initValue 7 $dy ; my initValue 8 $dz ; return [self]}
method initYvctV {v} {return [my initYvct [$v x] [$v y] [$v z]]}
method initZvct {dx dy dz} {my initValue 9 $dx ; my initValue 10 $dy ; my initValue 11 $dz ; return [self]}
method initZvctV {v} {return [my initZvct [$v x] [$v y] [$v z]]}
method initOrgArray {a} {upvar $a arr ; return [my initOrg $arr(0) $arr(1) $arr(2)]}
method initVctsArray {a} {
upvar $a arr
my initXvct $arr(0) $arr(1) $arr(2)
my initYvct $arr(3) $arr(4) $arr(5)
my initZvct $arr(6) $arr(7) $arr(8)
return [self]
}
method value {i} {return $values($i)}
method org {args} {return [Point3D new $values(0) $values(1) $values(2)]}
method xVct {args} {return [Vector3D new $values(3) $values(4) $values(5)]}
method yVct {args} {return [Vector3D new $values(6) $values(7) $values(8)]}
method zVct {args} {return [Vector3D new $values(9) $values(10) $values(11)]}
method same {mcs {precision 7}} {
set org [[self] org] ; set org_1 [$mcs org]
set vx [[self] xVct] ; set vx_1 [$mcs xVct]
set vy [[self] yVct] ; set vy_1 [$mcs yVct]
set vz [[self] zVct] ; set vz_1 [$mcs zVct]
if {[$org same $org_1 $precision] && [$vx same $vx_1 $precision] && [$vy same $vy_1 $precision] && [$vz same $vz_1 $precision]} {
set value 1
} else {
set value 0
}
$org destroy ; $org_1 destroy
$vx destroy ; $vx_1 destroy
$vy destroy ; $vy_1 destroy
$vz destroy ; $vz_1 destroy
return $value
}
}
set point [Point3D new]
$point add 1 2 3
$custom_flag(from_move,$::mom_path_name)
oo::class create Point3D {
variable x 0. ; variable y 0. ; variable z 0.
constructor {{dx 0.} {dy 0.} {dz 0.}} {my initX $dx ; my initY $dy ; my initZ $dz}
destructor {
#puts "[self] is now deleted"
}
method fformat {value {precision 7}} {return [expr {round( 10.0 ** $precision * $value) / (10.0 ** $precision)}]}
method radtodeg {rad} {return [expr {$rad*180.0/(4.0*atan(1.0))}]}
method degtorad {deg} {return [expr {$deg*(4.0*atan(1.0))/180.0}]}
method duplicate {args} {return [[Point3D new] initP [self]]}
method type {args} {return "::Point3D"}
method toArray {{i 0}} {return [list [incr i 0] $x [incr i] $y [incr i] $z]}
method toLst {args} {return [list $x $y $z]}
method toStr {args} {return "$x, $y, $z"}
method toStrF {{precision 7}} {return "[my fformat $x $precision], [my fformat $y $precision], [my fformat $z $precision]"}
method x {args} {return $x}
method y {args} {return $y}
method z {args} {return $z}
method initX {dx} {set x [expr double($dx)]}
method initY {dy} {set y [expr double($dy)]}
method initZ {dz} {set z [expr double($dz)]}
method init {dx dy dz} {my initX $dx ; my initY $dy ; my initZ $dz ; return [self]}
method initArray {a} {upvar $a arr ; return [my init $arr(0) $arr(1) $arr(2)]}
method initArrayLst {arr_lst} {array set arr $arr_lst ; return [my init $arr(0) $arr(1) $arr(2)]}
method initLst {lst} {return [my init [lindex $lst 0] [lindex $lst 1] [lindex $lst 2]]}
method initP {p} {return [my init [$p x] [$p y] [$p z]]}
method reset {args} {my init 0. 0. 0. ; return [self]}
method add {dx dy dz} {my init [expr {$x + $dx}] [expr {$y + $dy}] [expr {$z + $dz}] ; return [self]}
method addArray {a} {upvar $a arr ; return [my add $arr(0) $arr(1) $arr(2)]}
method addArrayLst {arr_lst} {array set arr $arr_lst ; return [my add $arr(0) $arr(1) $arr(2)]}
method addLst {lst} {return [my add [lindex $lst 0] [lindex $lst 1] [lindex $lst 2]]}
method addP {p} {return [my add [$p x] [$p y] [$p z]]}
method sub {dx dy dz} {my init [expr {$x - $dx}] [expr {$y - $dy}] [expr {$z - $dz}] ; return [self]}
method subArray {a} {upvar $a arr ; return [my sub $arr(0) $arr(1) $arr(2)]}
method subArrayLst {arr_lst} {array set arr $arr_lst ; return [my sub $arr(0) $arr(1) $arr(2)]}
method subLst {lst} {return [my sub [lindex $lst 0] [lindex $lst 1] [lindex $lst 2]]}
method subP {p} {return [my sub [$p x] [$p y] [$p z]]}
method dist {dx dy dz} {return [expr {sqrt([expr {$x - $dx}]**2 + [expr {$y - $dy}]**2 + [expr {$z - $dz}]**2)}]}
method distArray {a} {upvar $a arr ; return [my dist $arr(0) $arr(1) $arr(2)]}
method distArrayLst {arr_lst} {array set arr $arr_lst ; return [my dist $arr(0) $arr(1) $arr(2)]}
method distLst {lst} {return [my dist [lindex $lst 0] [lindex $lst 1] [lindex $lst 2]]}
method distP {p} {return [my dist [$p x] [$p y] [$p z]]}
method dist_to_line {pl1 pl2} {
set pl [[$pl2 duplicate] subP $pl1]
set pp [[$pl1 duplicate] subP [self]]
set vl [[Vector3D new] initV $pl]
set vp [[Vector3D new] initV $pp]
set l [$vl magnitude]
if {[my fformat $l 5]==0} {
set value 0.
} else {
set vc [$vl cross $vp]
set value [expr [$vc magnitude]/$l]
$vc destroy
}
$pl destroy ; $vl destroy
$pp destroy ; $vp destroy
return $value
}
method dist_to_lineV {v} {
set p1 [[Point3D new] init 0. 0. 0.]
set p2 [[Point3D new] initP $v]
set value [my dist_to_line $p1 $p2]
$p1 destroy
$p2 destroy
return $value
}
#midpoint method to calculate the midpoint between this point and another point. This can be done by averaging the x, y, and z coordinates of the two points.
method midpoint {dx dy dz} {return [Point3D new [expr {double([[self] x] + $dx)/2}] [expr {double([[self] y] + $dy)/2}] [expr {double([[self] z] + $dz)/2}]]}
method midpointArr {a} {upvar $a arr ; return [my midpoint $arr(0) $arr(1) $arr(2)]}
method midpointArrLst {arr} {array set arr $arr_lst ; return [my midpoint $arr(0) $arr(1) $arr(2)]}
method midpointLst {lst} {return [my midpoint [lindex $lst 0] [lindex $lst 1] [lindex $lst 2]]}
method midpointP {p} {return [my midpoint [$p x] [$p y] [$p z]]}
method same {p {precision 7}} {
if {[my fformat [expr {[[self] x] - [$p x]}] $precision] == 0. && [my fformat [expr {[[self] y] - [$p y]}] $precision] == 0. && [my fformat [expr {[[self] z] - [$p z]}] $precision] == 0.} {return 1} else {return 0}
}
#This method takes three arguments: a vector object "axis" as the axis of rotation, an angle of rotation, and a point object "center" as the center of rotation.
#First, it translates the point by subtracting the center of rotation,
#then it applies the Rodrigues' rotation formula to calculate the new coordinates of the point, and finally,
#it translates back the point by adding the center of rotation.
#The center parameter is defined as an optional parameter with a default value of a new Point3D object, initialized with the values (0,0,0).
method rotAround {u v w angle {x0 0.} {y0 0.} {z0 0.}} {
set x1 [expr {double($x) - $x0}] ; set y1 [expr {double($y) - $y0}] ; set z1 [expr {double($z) - $z0}]
set u [expr {double($u)}] ; set v [expr {double($v)}] ; set w [expr {double($w)}]
set a [my degtorad $angle]
set coss [expr {cos($a)}]
set sinn [expr {sin($a)}]
set x2 [expr {$u*($u*$x1 + $v*$y1 + $w*$z1)*(1 - $coss) + $x1*$coss + (-$w*$y1 + $v*$z1)*$sinn + $x0}]
set y2 [expr {$v*($u*$x1 + $v*$y1 + $w*$z1)*(1 - $coss) + $y1*$coss + ($w*$x1 - $u*$z1)*$sinn + $y0}]
set z2 [expr {$w*($u*$x1 + $v*$y1 + $w*$z1)*(1 - $coss) + $z1*$coss + (-$v*$x1 + $u*$y1)*$sinn + $z0}]
my init $x2 $y2 $z2
return [self]
}
method rotAroundLst {aLst angle {cLst {0. 0. 0.}}} {
return [my rotAround [lindex $aLst 0] [lindex $aLst 1] [lindex $aLst 2] $angle [lindex $cLst 0] [lindex $cLst 1] [lindex $cLst 2]]
}
method rotAroundVP {axis angle {center {}}} {
if {[catch {set s [$center type]}] || (![string equal [$center type] "::Point3D"] && ![string equal [$center type] "::Vector3D"])} {
set center [Point3D new]
set b_center true
}
my rotAround [$axis x] [$axis y] [$axis z] $angle [$center x] [$center y] [$center z]
if {[info exists b_center]} {$center destroy}
return [self]
}
method transform {mtx_from mtx_to} {
if {[catch {set s [$mtx_from type]}] || ![string equal [$mtx_from type] "::Matrix4x4"]} {
set mtx_from [[Matrix4x4 new] identity]
set b_mtx_from true
}
if {[catch {set s [$mtx_to type]}] || ![string equal [$mtx_to type] "::Matrix4x4"]} {
set mtx_to [[Matrix4x4 new] identity]
set b_mtx_to true
}
set m [[Matrix4x4 new] identity]
$m initValue 0 [my x]
$m initValue 4 [my y]
$m initValue 8 [my z]
$m initValue 12 1.
set trs [[[[$mtx_from duplicate] inverse] multiply $mtx_to false] inverse]
$m multiply $trs true
my init [$m value 0] [$m value 4] [$m value 8]
$m destroy
$trs destroy
if {[info exists b_mtx_from]} {$mtx_from destroy}
if {[info exists b_mtx_to]} {$mtx_to destroy}
return [self]
}
}