From a04e510ee205c8f137636f0bd47263a64b047730 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Fri, 22 Aug 2025 19:29:41 +0300 Subject: lsp: refine ';;text;' detection to require non-empty, non-space content and closing ';'; ensure bare ';;' and ';;;' do not auto-trigger; add tests --- internal/lsp/completion_prefix_strip_test.go | 7 +++ internal/lsp/handlers.go | 69 +++++++++++++++++----------- 2 files changed, 49 insertions(+), 27 deletions(-) (limited to 'internal/lsp') diff --git a/internal/lsp/completion_prefix_strip_test.go b/internal/lsp/completion_prefix_strip_test.go index 84f0568..43eb608 100644 --- a/internal/lsp/completion_prefix_strip_test.go +++ b/internal/lsp/completion_prefix_strip_test.go @@ -73,3 +73,10 @@ func TestTryLLMCompletion_DoubleSemicolonEmpty_DoesNotAutoTrigger(t *testing.T) if len(items) != 0 { t.Fatalf("expected no items when inline ';;' is empty") } if fake.calls != 0 { t.Fatalf("LLM should not be called; calls=%d", fake.calls) } } + +func TestHasDoubleSemicolonTrigger_Variants(t *testing.T) { + if hasDoubleSemicolonTrigger(";;") { t.Fatalf("bare ';;' should not trigger") } + if hasDoubleSemicolonTrigger(";; ;") { t.Fatalf("';;' followed by space should not trigger") } + if hasDoubleSemicolonTrigger(";;;") { t.Fatalf("';;;' should not trigger (no content)") } + if !hasDoubleSemicolonTrigger(";;x;") { t.Fatalf("expected trigger for ';;x;' pattern") } +} diff --git a/internal/lsp/handlers.go b/internal/lsp/handlers.go index 9d58a44..f630eb5 100644 --- a/internal/lsp/handlers.go +++ b/internal/lsp/handlers.go @@ -781,16 +781,21 @@ func (s *Server) tryLLMCompletion(p CompletionParams, above, current, below, fun logging.Logf("lsp ", "%scompletion skip=busy another LLM request in flight%s", logging.AnsiYellow, logging.AnsiBase) return []CompletionItem{}, false, true } - sysPrompt, userPrompt := buildPrompts(inParams, p, above, current, below, funcCtx) - messages := []llm.Message{ - {Role: "system", Content: sysPrompt}, - {Role: "user", Content: userPrompt}, - } + sysPrompt, userPrompt := buildPrompts(inParams, p, above, current, below, funcCtx) + messages := []llm.Message{ + {Role: "system", Content: sysPrompt}, + {Role: "user", Content: userPrompt}, + } if hasExtra && extraText != "" { messages = append(messages, llm.Message{Role: "user", Content: "Additional context:\n" + extraText}) } - // Compute total sent context size (sum of message contents) + // If an inline prompt marker is present, make the instruction stricter: code only. + if inlinePrompt { + messages[0].Content = "You are a precise code completion/refactoring engine. Output only the code to insert with no prose, no comments, and no backticks. Return raw code only." + } + + // Compute total sent context size (sum of message contents) var sentSize int for _, m := range messages { sentSize += len(m.Content) @@ -1057,27 +1062,37 @@ func promptRemovalEditsForLine(line string, lineNum int) []TextEdit { } func hasDoubleSemicolonTrigger(line string) bool { - pos := 0 - for pos < len(line) { - j := strings.Index(line[pos:], ";;") - if j < 0 { - return false - } - j += pos - if j+2 < len(line) && line[j+2] != ' ' { - if k := strings.Index(line[j+2:], ";"); k >= 0 { - closeIdx := j + 2 + k - if closeIdx-1 >= 0 && line[closeIdx-1] != ' ' { - return true - } - pos = closeIdx + 1 - continue - } - return false - } - pos = j + 2 - } - return false + pos := 0 + for pos < len(line) { + j := strings.Index(line[pos:], ";;") + if j < 0 { + return false + } + j += pos + contentStart := j + 2 + if contentStart >= len(line) { + return false // nothing after ';;' + } + // First content char cannot be space or another ';' + first := line[contentStart] + if first == ' ' || first == ';' { + pos = contentStart + 1 + continue + } + // Require at least one content char before a closing ';' + k := strings.Index(line[contentStart+1:], ";") + if k < 0 { + return false + } + closeIdx := contentStart + 1 + k + // Disallow trailing space before closing ';' + if closeIdx-1 >= 0 && line[closeIdx-1] == ' ' { + pos = closeIdx + 1 + continue + } + return true + } + return false } func collectSemicolonMarkers(line string, lineNum int) []TextEdit { -- cgit v1.2.3