summaryrefslogtreecommitdiff
path: root/internal/lsp/completion_prefix_strip_test.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-08-29 00:22:39 +0300
committerPaul Buetow <paul@buetow.org>2025-08-29 00:22:39 +0300
commit0c2994f0065090a4884b28dc27eb760db2dfaab3 (patch)
tree687ecd00584feb634a5853f5964028621f0fa1d5 /internal/lsp/completion_prefix_strip_test.go
parentd35aaa0227334ab0269b0907491c0682841b9cd5 (diff)
lsp: refactor dispatch to handler map; split handlers into feature files (completion, codeaction, init, document); decompose completion logic into small helpers; update review checklist
Diffstat (limited to 'internal/lsp/completion_prefix_strip_test.go')
-rw-r--r--internal/lsp/completion_prefix_strip_test.go210
1 files changed, 124 insertions, 86 deletions
diff --git a/internal/lsp/completion_prefix_strip_test.go b/internal/lsp/completion_prefix_strip_test.go
index 9953714..64cca49 100644
--- a/internal/lsp/completion_prefix_strip_test.go
+++ b/internal/lsp/completion_prefix_strip_test.go
@@ -1,120 +1,158 @@
package lsp
import (
- "encoding/json"
- "testing"
+ "encoding/json"
+ "testing"
)
func TestStripDuplicateGeneralPrefix_ExactOverlap(t *testing.T) {
- prefix := "func New "
- sugg := "func New() *CustData"
- got := stripDuplicateGeneralPrefix(prefix, sugg)
- // We expect the already typed prefix to be removed from the suggestion.
- if got == sugg {
- t.Fatalf("expected duplicate prefix to be stripped; got unchanged: %q", got)
- }
- if got != "() *CustData" {
- t.Fatalf("got %q want %q", got, "() *CustData")
- }
+ prefix := "func New "
+ sugg := "func New() *CustData"
+ got := stripDuplicateGeneralPrefix(prefix, sugg)
+ // We expect the already typed prefix to be removed from the suggestion.
+ if got == sugg {
+ t.Fatalf("expected duplicate prefix to be stripped; got unchanged: %q", got)
+ }
+ if got != "() *CustData" {
+ t.Fatalf("got %q want %q", got, "() *CustData")
+ }
}
func TestStripDuplicateGeneralPrefix_TokenBoundarySuffix(t *testing.T) {
- prefix := "db."
- sugg := "db.Query()"
- got := stripDuplicateGeneralPrefix(prefix, sugg)
- if got != "Query()" {
- t.Fatalf("got %q want %q", got, "Query()")
- }
+ prefix := "db."
+ sugg := "db.Query()"
+ got := stripDuplicateGeneralPrefix(prefix, sugg)
+ if got != "Query()" {
+ t.Fatalf("got %q want %q", got, "Query()")
+ }
}
func TestStripDuplicateAssignmentPrefix_AssignAndWalrus(t *testing.T) {
- // walrus
- if out := stripDuplicateAssignmentPrefix("name := ", "name := compute()" ); out != "compute()" {
- t.Fatalf(":= expected compute(), got %q", out)
- }
- // equals
- if out := stripDuplicateAssignmentPrefix("x = ", "x = y+1" ); out != "y+1" {
- t.Fatalf("= expected y+1, got %q", out)
- }
+ // walrus
+ if out := stripDuplicateAssignmentPrefix("name := ", "name := compute()"); out != "compute()" {
+ t.Fatalf(":= expected compute(), got %q", out)
+ }
+ // equals
+ if out := stripDuplicateAssignmentPrefix("x = ", "x = y+1"); out != "y+1" {
+ t.Fatalf("= expected y+1, got %q", out)
+ }
}
func TestTryLLMCompletion_ManualInvokeAfterWhitespace_Allows(t *testing.T) {
- s := &Server{ maxTokens: 32, triggerChars: []string{".", ":", "/", "_"}, compCache: make(map[string]string) }
- s.llmClient = fakeLLM{resp: "() *CustData"}
- line := "func fib(i int) " // cursor after space
- p := CompletionParams{ Position: Position{ Line: 0, Character: len(line) }, TextDocument: TextDocumentIdentifier{URI: "file://x.go"} }
- // Simulate manual user invocation (TriggerKind=1)
- p.Context = json.RawMessage([]byte(`{"triggerKind":1}`))
- items, ok := s.tryLLMCompletion(p, "", line, "", "", "", false, "")
- if !ok { t.Fatalf("expected ok=true for manual invoke after whitespace") }
- if len(items) == 0 { t.Fatalf("expected at least one completion item") }
+ s := &Server{maxTokens: 32, triggerChars: []string{".", ":", "/", "_"}, compCache: make(map[string]string)}
+ s.llmClient = fakeLLM{resp: "() *CustData"}
+ line := "func fib(i int) " // cursor after space
+ p := CompletionParams{Position: Position{Line: 0, Character: len(line)}, TextDocument: TextDocumentIdentifier{URI: "file://x.go"}}
+ // Simulate manual user invocation (TriggerKind=1)
+ p.Context = json.RawMessage([]byte(`{"triggerKind":1}`))
+ items, ok := s.tryLLMCompletion(p, "", line, "", "", "", false, "")
+ if !ok {
+ t.Fatalf("expected ok=true for manual invoke after whitespace")
+ }
+ if len(items) == 0 {
+ t.Fatalf("expected at least one completion item")
+ }
}
func TestTryLLMCompletion_InlineSemicolonPromptAlwaysTriggers(t *testing.T) {
- s := &Server{ maxTokens: 32, triggerChars: []string{".", ":", "/", "_"}, compCache: make(map[string]string) }
- s.llmClient = fakeLLM{resp: "replacement"}
- line := "prefix ;do something; suffix"
- // No trigger char immediately before cursor; place cursor at end
- p := CompletionParams{ Position: Position{ Line: 0, Character: len(line) }, TextDocument: TextDocumentIdentifier{URI: "file://inline.go"} }
- items, ok := s.tryLLMCompletion(p, "", line, "", "", "", false, "")
- if !ok || len(items) == 0 { t.Fatalf("expected completion to trigger on inline ;text; prompt") }
+ s := &Server{maxTokens: 32, triggerChars: []string{".", ":", "/", "_"}, compCache: make(map[string]string)}
+ s.llmClient = fakeLLM{resp: "replacement"}
+ line := "prefix ;do something; suffix"
+ // No trigger char immediately before cursor; place cursor at end
+ p := CompletionParams{Position: Position{Line: 0, Character: len(line)}, TextDocument: TextDocumentIdentifier{URI: "file://inline.go"}}
+ items, ok := s.tryLLMCompletion(p, "", line, "", "", "", false, "")
+ if !ok || len(items) == 0 {
+ t.Fatalf("expected completion to trigger on inline ;text; prompt")
+ }
}
func TestTryLLMCompletion_DoubleSemicolonEmpty_DoesNotAutoTrigger(t *testing.T) {
- s := &Server{ maxTokens: 32, triggerChars: []string{".", ":", "/", "_"}, compCache: make(map[string]string) }
- fake := &countingLLM{}
- s.llmClient = fake
- line := ";; " // empty content after ';;' should not force-trigger
- p := CompletionParams{ Position: Position{ Line: 0, Character: len(line) }, TextDocument: TextDocumentIdentifier{URI: "file://empty-inline.go"} }
- items, ok := s.tryLLMCompletion(p, "", line, "", "", "", false, "")
- if !ok { t.Fatalf("expected ok=true for non-trigger path") }
- 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) }
+ s := &Server{maxTokens: 32, triggerChars: []string{".", ":", "/", "_"}, compCache: make(map[string]string)}
+ fake := &countingLLM{}
+ s.llmClient = fake
+ line := ";; " // empty content after ';;' should not force-trigger
+ p := CompletionParams{Position: Position{Line: 0, Character: len(line)}, TextDocument: TextDocumentIdentifier{URI: "file://empty-inline.go"}}
+ items, ok := s.tryLLMCompletion(p, "", line, "", "", "", false, "")
+ if !ok {
+ t.Fatalf("expected ok=true for non-trigger path")
+ }
+ 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") }
+ 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")
+ }
}
func TestBareDoubleSemicolonPreventsAutoTriggerEvenWithOtherTriggers(t *testing.T) {
- s := &Server{ maxTokens: 32, triggerChars: []string{".", ":", "/", "_"}, compCache: make(map[string]string) }
- fake := &countingLLM{}
- s.llmClient = fake
- // Place a '.' earlier but also include bare ';;' at end; should not auto-trigger
- line := "obj. call ;;"
- p := CompletionParams{ Position: Position{ Line: 0, Character: len(line) }, TextDocument: TextDocumentIdentifier{URI: "file://bare-ds.go"} }
- items, ok := s.tryLLMCompletion(p, "", line, "", "", "", false, "")
- if !ok { t.Fatalf("expected ok=true (handled), but not auto-triggering") }
- if len(items) != 0 { t.Fatalf("expected no items due to bare ';;'") }
- if fake.calls != 0 { t.Fatalf("LLM should not be called; calls=%d", fake.calls) }
+ s := &Server{maxTokens: 32, triggerChars: []string{".", ":", "/", "_"}, compCache: make(map[string]string)}
+ fake := &countingLLM{}
+ s.llmClient = fake
+ // Place a '.' earlier but also include bare ';;' at end; should not auto-trigger
+ line := "obj. call ;;"
+ p := CompletionParams{Position: Position{Line: 0, Character: len(line)}, TextDocument: TextDocumentIdentifier{URI: "file://bare-ds.go"}}
+ items, ok := s.tryLLMCompletion(p, "", line, "", "", "", false, "")
+ if !ok {
+ t.Fatalf("expected ok=true (handled), but not auto-triggering")
+ }
+ if len(items) != 0 {
+ t.Fatalf("expected no items due to bare ';;'")
+ }
+ if fake.calls != 0 {
+ t.Fatalf("LLM should not be called; calls=%d", fake.calls)
+ }
}
func TestBareDoubleSemicolonOnNextLine_PreventsAutoTrigger(t *testing.T) {
- s := &Server{ maxTokens: 32, triggerChars: []string{".", ":", "/", "_"}, compCache: make(map[string]string) }
- fake := &countingLLM{}
- s.llmClient = fake
- current := "expression := flag.String(\"expression\", \"\", \"Expression to evaluate\")"
- below := ";;"
- p := CompletionParams{ Position: Position{ Line: 0, Character: len(current) }, TextDocument: TextDocumentIdentifier{URI: "file://nextline.go"} }
- items, ok := s.tryLLMCompletion(p, "", current, below, "", "", false, "")
- if !ok { t.Fatalf("expected ok=true handled") }
- if len(items) != 0 { t.Fatalf("expected no items due to bare ';;' on next line") }
- if fake.calls != 0 { t.Fatalf("LLM should not be called; calls=%d", fake.calls) }
+ s := &Server{maxTokens: 32, triggerChars: []string{".", ":", "/", "_"}, compCache: make(map[string]string)}
+ fake := &countingLLM{}
+ s.llmClient = fake
+ current := "expression := flag.String(\"expression\", \"\", \"Expression to evaluate\")"
+ below := ";;"
+ p := CompletionParams{Position: Position{Line: 0, Character: len(current)}, TextDocument: TextDocumentIdentifier{URI: "file://nextline.go"}}
+ items, ok := s.tryLLMCompletion(p, "", current, below, "", "", false, "")
+ if !ok {
+ t.Fatalf("expected ok=true handled")
+ }
+ if len(items) != 0 {
+ t.Fatalf("expected no items due to bare ';;' on next line")
+ }
+ if fake.calls != 0 {
+ t.Fatalf("LLM should not be called; calls=%d", fake.calls)
+ }
}
func TestBareDoubleSemicolonPreventsManualInvoke(t *testing.T) {
- s := &Server{ maxTokens: 32, triggerChars: []string{".", ":", "/", "_"}, compCache: make(map[string]string) }
- fake := &countingLLM{}
- s.llmClient = fake
- line := ";;"
- p := CompletionParams{ Position: Position{ Line: 0, Character: len(line) }, TextDocument: TextDocumentIdentifier{URI: "file://bare-ds-manual.go"} }
- // Simulate manual invoke
- p.Context = json.RawMessage([]byte(`{"triggerKind":1}`))
- items, ok := s.tryLLMCompletion(p, "", line, "", "", "", false, "")
- if !ok { t.Fatalf("expected ok=true (handled)") }
- if len(items) != 0 { t.Fatalf("expected no items for bare ';;' even with manual invoke") }
- if fake.calls != 0 { t.Fatalf("LLM should not be called; calls=%d", fake.calls) }
+ s := &Server{maxTokens: 32, triggerChars: []string{".", ":", "/", "_"}, compCache: make(map[string]string)}
+ fake := &countingLLM{}
+ s.llmClient = fake
+ line := ";;"
+ p := CompletionParams{Position: Position{Line: 0, Character: len(line)}, TextDocument: TextDocumentIdentifier{URI: "file://bare-ds-manual.go"}}
+ // Simulate manual invoke
+ p.Context = json.RawMessage([]byte(`{"triggerKind":1}`))
+ items, ok := s.tryLLMCompletion(p, "", line, "", "", "", false, "")
+ if !ok {
+ t.Fatalf("expected ok=true (handled)")
+ }
+ if len(items) != 0 {
+ t.Fatalf("expected no items for bare ';;' even with manual invoke")
+ }
+ if fake.calls != 0 {
+ t.Fatalf("LLM should not be called; calls=%d", fake.calls)
+ }
}