diff options
| author | Paul Buetow <paul@buetow.org> | 2025-08-19 21:32:09 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2025-08-19 21:32:09 +0300 |
| commit | 526e40a54ba325a6f75f35817799614d7b5997b7 (patch) | |
| tree | ba25459399835d99b5785872cf9da8c2d64ed136 | |
| parent | 041196eab701b7a7ac79baf486a7c11fc11913e3 (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.go | 34 | ||||
| -rw-r--r-- | internal/lsp/handlers_helpers_test.go | 21 |
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) + } + } +} |
