Skip to content

Commit cf35e89

Browse files
committed
🔍 refactor(definition): support qualified names and trim trailing () in symbol matching
- Accept queries like Class.method or ns::Class.method by matching containerName and member - Trim trailing parentheses in queries (e.g. foo()) - Preserve previous behavior for unqualified names and other servers
1 parent c8bdbf2 commit cf35e89

File tree

1 file changed

+87
-40
lines changed

1 file changed

+87
-40
lines changed

internal/tools/definition.go

Lines changed: 87 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,15 @@ import (
1010
)
1111

1212
func ReadDefinition(ctx context.Context, client *lsp.Client, symbolName string) (string, error) {
13-
symbolResult, err := client.Symbol(ctx, protocol.WorkspaceSymbolParams{
14-
Query: symbolName,
15-
})
13+
// Normalize common user inputs, e.g. "foo()" -> "foo"
14+
normalized := strings.TrimSpace(symbolName)
15+
if strings.HasSuffix(normalized, "()") {
16+
normalized = strings.TrimSuffix(normalized, "()")
17+
}
18+
19+
symbolResult, err := client.Symbol(ctx, protocol.WorkspaceSymbolParams{
20+
Query: normalized,
21+
})
1622
if err != nil {
1723
return "", fmt.Errorf("failed to fetch symbol: %v", err)
1824
}
@@ -23,43 +29,84 @@ func ReadDefinition(ctx context.Context, client *lsp.Client, symbolName string)
2329
}
2430

2531
var definitions []string
26-
for _, symbol := range results {
27-
kind := ""
28-
container := ""
29-
30-
// Skip symbols that we are not looking for. workspace/symbol may return
31-
// a large number of fuzzy matches.
32-
switch v := symbol.(type) {
33-
case *protocol.SymbolInformation:
34-
// SymbolInformation results have richer data.
35-
kind = fmt.Sprintf("Kind: %s\n", protocol.TableKindMap[v.Kind])
36-
if v.ContainerName != "" {
37-
container = fmt.Sprintf("Container Name: %s\n", v.ContainerName)
38-
}
39-
40-
// Handle different matching strategies based on the search term
41-
if strings.Contains(symbolName, ".") {
42-
// For qualified names like "Type.Method", require exact match
43-
if symbol.GetName() != symbolName {
44-
continue
45-
}
46-
} else {
47-
// For unqualified names like "Method"
48-
if v.Kind == protocol.Method {
49-
// For methods, only match if the method name matches exactly Type.symbolName or Type::symbolName or symbolName
50-
if !strings.HasSuffix(symbol.GetName(), "::"+symbolName) && !strings.HasSuffix(symbol.GetName(), "."+symbolName) && symbol.GetName() != symbolName {
51-
continue
52-
}
53-
} else if symbol.GetName() != symbolName {
54-
// For non-methods, exact match only
55-
continue
56-
}
57-
}
58-
default:
59-
if symbol.GetName() != symbolName {
60-
continue
61-
}
62-
}
32+
// Determine if the query is qualified (e.g., Class.method or ns::Class)
33+
isQualified := strings.Contains(normalized, ".") || strings.Contains(normalized, "::")
34+
// Extract container and member parts if qualified
35+
containerWanted := ""
36+
memberWanted := normalized
37+
if isQualified {
38+
// Split on last occurrence of '.' or '::'
39+
// Prefer the right-most separator to support nested containers
40+
if idx := strings.LastIndex(normalized, "::"); idx != -1 {
41+
containerWanted = normalized[:idx]
42+
memberWanted = normalized[idx+2:]
43+
} else if idx := strings.LastIndex(normalized, "."); idx != -1 {
44+
containerWanted = normalized[:idx]
45+
memberWanted = normalized[idx+1:]
46+
}
47+
}
48+
49+
for _, symbol := range results {
50+
kind := ""
51+
container := ""
52+
53+
// Skip symbols that we are not looking for. workspace/symbol may return
54+
// a large number of fuzzy matches.
55+
switch v := symbol.(type) {
56+
case *protocol.SymbolInformation:
57+
// SymbolInformation results have richer data.
58+
kind = fmt.Sprintf("Kind: %s\n", protocol.TableKindMap[v.Kind])
59+
if v.ContainerName != "" {
60+
container = fmt.Sprintf("Container Name: %s\n", v.ContainerName)
61+
}
62+
63+
// Matching strategy
64+
name := symbol.GetName()
65+
if isQualified {
66+
// Qualified lookup
67+
// Accept if:
68+
// - name equals memberWanted; and, if we know the container, it matches containerWanted (exact or suffix)
69+
// - or name equals the full normalized string (some servers include container in name)
70+
if name != memberWanted && name != normalized && !strings.HasSuffix(name, "::"+memberWanted) && !strings.HasSuffix(name, "."+memberWanted) {
71+
continue
72+
}
73+
if containerWanted != "" && v.ContainerName != "" {
74+
// containerName may be only the immediate container. Allow suffix match to tolerate nesting
75+
if v.ContainerName != containerWanted &&
76+
!strings.HasSuffix(v.ContainerName, "."+containerWanted) &&
77+
!strings.HasSuffix(v.ContainerName, "::"+containerWanted) &&
78+
!strings.HasSuffix(containerWanted, "."+v.ContainerName) &&
79+
!strings.HasSuffix(containerWanted, "::"+v.ContainerName) {
80+
continue
81+
}
82+
}
83+
} else {
84+
// Unqualified lookup
85+
if name != normalized {
86+
// For methods, also allow suffix match like *.method
87+
if v.Kind == protocol.Method {
88+
if !strings.HasSuffix(name, "."+normalized) && !strings.HasSuffix(name, "::"+normalized) {
89+
continue
90+
}
91+
} else {
92+
continue
93+
}
94+
}
95+
}
96+
default:
97+
if !isQualified {
98+
if symbol.GetName() != normalized {
99+
continue
100+
}
101+
} else {
102+
// For unknown result types, require exact match with full normalized string
103+
if symbol.GetName() != normalized &&
104+
!strings.HasSuffix(symbol.GetName(), "."+memberWanted) &&
105+
!strings.HasSuffix(symbol.GetName(), "::"+memberWanted) {
106+
continue
107+
}
108+
}
109+
}
63110

64111
toolsLogger.Debug("Found symbol: %s", symbol.GetName())
65112
loc := symbol.GetLocation()

0 commit comments

Comments
 (0)