summaryrefslogtreecommitdiff
path: root/internal/lsp/handlers.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-08-22 17:33:36 +0300
committerPaul Buetow <paul@buetow.org>2025-08-22 17:33:36 +0300
commit8aba4a99359b59fa11498a3bd13cb9c6cf0ac354 (patch)
treef3ba0f939d0a6d0784c2f823743cfa6fd276df22 /internal/lsp/handlers.go
parentda350b02d395d0135d9193015f969706c7d257a7 (diff)
tests(lsp): add duplicate-prefix and manual-invoke tests; fix cache key to ignore trailing whitespace; guard compCache init
Diffstat (limited to 'internal/lsp/handlers.go')
-rw-r--r--internal/lsp/handlers.go44
1 files changed, 39 insertions, 5 deletions
diff --git a/internal/lsp/handlers.go b/internal/lsp/handlers.go
index cb8b0e4..69ee7ab 100644
--- a/internal/lsp/handlers.go
+++ b/internal/lsp/handlers.go
@@ -822,9 +822,8 @@ func (s *Server) tryLLMCompletion(p CompletionParams, above, current, below, fun
}
}
}
- if cleaned != "" {
- cleaned = stripDuplicateAssignmentPrefix(current[:p.Position.Character], cleaned)
- }
+ if cleaned != "" { cleaned = stripDuplicateAssignmentPrefix(current[:p.Position.Character], cleaned) }
+ if cleaned != "" { cleaned = stripDuplicateGeneralPrefix(current[:p.Position.Character], cleaned) }
if cleaned == "" {
return nil, false, false
}
@@ -863,7 +862,7 @@ func (s *Server) completionCacheKey(p CompletionParams, above, current, below, f
model,
temp,
p.TextDocument.URI,
- fmt.Sprintf("%d:%d", p.Position.Line, p.Position.Character),
+ fmt.Sprintf("%d:%d", p.Position.Line, len(left)),
above,
left,
right,
@@ -889,6 +888,9 @@ func (s *Server) completionCacheGet(key string) (string, bool) {
func (s *Server) completionCachePut(key, value string) {
s.mu.Lock()
defer s.mu.Unlock()
+ if s.compCache == nil {
+ s.compCache = make(map[string]string)
+ }
if _, exists := s.compCache[key]; !exists {
s.compCacheOrder = append(s.compCacheOrder, key)
s.compCache[key] = value
@@ -1179,7 +1181,7 @@ func computeWordStart(current string, at int) int {
}
func isIdentChar(ch byte) bool {
- return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_'
+ return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_'
}
// stripDuplicateAssignmentPrefix removes a duplicated assignment prefix (e.g.,
@@ -1225,6 +1227,38 @@ func stripDuplicateAssignmentPrefix(prefixBeforeCursor, suggestion string) strin
return suggestion
}
+// stripDuplicateGeneralPrefix removes any already-typed prefix that the model repeated
+// at the beginning of its suggestion. It compares the entire text to the left of the
+// cursor (prefixBeforeCursor) against the suggestion, trimming whitespace appropriately,
+// and strips the longest sensible overlap. This prevents cases like:
+// prefix: "func New "
+// suggestion:"func New() *Type"
+// resulting in duplicates like "func New func New() *Type".
+func stripDuplicateGeneralPrefix(prefixBeforeCursor, suggestion string) string {
+ if suggestion == "" { return suggestion }
+ s := strings.TrimLeft(suggestion, " \t")
+ p := strings.TrimRight(prefixBeforeCursor, " \t")
+ // Exact prefix overlap: remove the full typed prefix
+ if p != "" && strings.HasPrefix(s, p) {
+ return strings.TrimLeft(s[len(p):], " \t")
+ }
+ // Otherwise, try the longest token-aligned suffix of p that prefixes s
+ // Prefer boundaries where the char before the suffix is not an identifier char
+ for k := len(p) - 1; k > 0; k-- {
+ if !isIdentBoundary(p[k-1]) { continue }
+ suf := strings.TrimLeft(p[k:], " \t")
+ if suf == "" { continue }
+ if strings.HasPrefix(s, suf) {
+ return strings.TrimLeft(s[len(suf):], " \t")
+ }
+ }
+ return suggestion
+}
+
+func isIdentBoundary(ch byte) bool {
+ return !((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_')
+}
+
// stripCodeFences removes surrounding Markdown code fences from a model
// response when the entire output is wrapped, e.g. starting with "```go" or
// "```" and ending with "```". It returns the inner content unchanged.