summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-08-19 21:32:09 +0300
committerPaul Buetow <paul@buetow.org>2025-08-19 21:32:09 +0300
commit526e40a54ba325a6f75f35817799614d7b5997b7 (patch)
treeba25459399835d99b5785872cf9da8c2d64ed136
parent041196eab701b7a7ac79baf486a7c11fc11913e3 (diff)
lsp: strip inline spans for completions\n\n- Add stripInlineCodeSpan helper to extract first inline backtick span\n- Apply only in completion path after fence stripping\n- Add comprehensive unit tests for inline span handling
-rw-r--r--internal/lsp/handlers.go34
-rw-r--r--internal/lsp/handlers_helpers_test.go21
2 files changed, 54 insertions, 1 deletions
diff --git a/internal/lsp/handlers.go b/internal/lsp/handlers.go
index d140ba1..cfd71ea 100644
--- a/internal/lsp/handlers.go
+++ b/internal/lsp/handlers.go
@@ -482,7 +482,17 @@ func (s *Server) tryLLMCompletion(p CompletionParams, above, current, below, fun
// Update response counters (received)
s.incRecvCounters(len(text))
s.logLLMStats()
- cleaned := stripCodeFences(strings.TrimSpace(text))
+ cleaned := stripCodeFences(strings.TrimSpace(text))
+ // For code completion responses, also strip inline single-backtick code spans
+ // when the model returns prose like: "Use `expr` here".
+ if cleaned != "" {
+ if strings.ContainsRune(cleaned, '`') {
+ inline := stripInlineCodeSpan(cleaned)
+ if strings.TrimSpace(inline) != "" {
+ cleaned = inline
+ }
+ }
+ }
if cleaned != "" {
cleaned = stripDuplicateAssignmentPrefix(current[:p.Position.Character], cleaned)
}
@@ -780,6 +790,28 @@ func stripCodeFences(s string) string {
return t
}
+// stripInlineCodeSpan returns only the contents of the first inline backtick
+// code span if present, e.g., "some text `x := y()` more" -> "x := y()".
+// If no matching pair of backticks exists, it returns the input unchanged.
+// This is intended for code completion responses where the model may wrap a
+// small snippet in single backticks among prose.
+func stripInlineCodeSpan(s string) string {
+ t := strings.TrimSpace(s)
+ if t == "" {
+ return t
+ }
+ i := strings.IndexByte(t, '`')
+ if i < 0 {
+ return t
+ }
+ jrel := strings.IndexByte(t[i+1:], '`')
+ if jrel < 0 {
+ return t
+ }
+ j := i + 1 + jrel
+ return t[i+1 : j]
+}
+
func labelForCompletion(cleaned, filter string) string {
label := trimLen(firstLine(cleaned))
if filter != "" && !strings.HasPrefix(strings.ToLower(label), strings.ToLower(filter)) {
diff --git a/internal/lsp/handlers_helpers_test.go b/internal/lsp/handlers_helpers_test.go
index f9ed18a..11fe29f 100644
--- a/internal/lsp/handlers_helpers_test.go
+++ b/internal/lsp/handlers_helpers_test.go
@@ -69,3 +69,24 @@ func TestStripCodeFences(t *testing.T) {
}
}
}
+
+func TestStripInlineCodeSpan(t *testing.T) {
+ cases := []struct{
+ name string
+ in string
+ want string
+ }{
+ {"no backticks", "return x + y", "return x + y"},
+ {"single inline", "Use `foo(bar)` here", "foo(bar)"},
+ {"just inline", "`x := y()`", "x := y()"},
+ {"unmatched start", "use `foo(bar) without end", "use `foo(bar) without end"},
+ {"multiple spans picks first", "`a` and also `b`", "a"},
+ {"leading/trailing spaces", " text ` z ` ", " z "},
+ }
+ for _, tc := range cases {
+ got := stripInlineCodeSpan(tc.in)
+ if got != tc.want {
+ t.Fatalf("%s: got %q want %q", tc.name, got, tc.want)
+ }
+ }
+}