summaryrefslogtreecommitdiff
path: root/internal/lsp
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-09-06 10:56:27 +0300
committerPaul Buetow <paul@buetow.org>2025-09-06 10:56:27 +0300
commit320de746f7a2985b60c8564a0e65bdf231e840b7 (patch)
treee70bcf50813dba411afa2934e774383124bbc99e /internal/lsp
parent06247527d5170f329b454b42f59a3e4434ab1f4b (diff)
use gofumpt
Diffstat (limited to 'internal/lsp')
-rw-r--r--internal/lsp/build_prompts_table_test.go24
-rw-r--r--internal/lsp/chat_history_test.go46
-rw-r--r--internal/lsp/chat_no_double_answer_test.go29
-rw-r--r--internal/lsp/code_fences_table_test.go45
-rw-r--r--internal/lsp/codeaction_more_test.go151
-rw-r--r--internal/lsp/codeaction_test.go11
-rw-r--r--internal/lsp/codegen_helpers_test.go19
-rw-r--r--internal/lsp/completion_cache_test.go10
-rw-r--r--internal/lsp/completion_codex_path_test.go8
-rw-r--r--internal/lsp/completion_helpers_more_test.go60
-rw-r--r--internal/lsp/completion_messages_test.go116
-rw-r--r--internal/lsp/completion_prefix_strip_test.go97
-rw-r--r--internal/lsp/completion_provider_fallback_test.go59
-rw-r--r--internal/lsp/compute_textedit_table_test.go53
-rw-r--r--internal/lsp/context.go3
-rw-r--r--internal/lsp/debounce_throttle_more_test.go51
-rw-r--r--internal/lsp/debounce_throttle_test.go123
-rw-r--r--internal/lsp/diagnostics_action_test.go49
-rw-r--r--internal/lsp/document.go2
-rw-r--r--internal/lsp/document_handlers_test.go102
-rw-r--r--internal/lsp/document_test.go42
-rw-r--r--internal/lsp/fallback_items_test.go11
-rw-r--r--internal/lsp/gotest_append_test.go51
-rw-r--r--internal/lsp/handlers.go28
-rw-r--r--internal/lsp/handlers_codeaction.go575
-rw-r--r--internal/lsp/handlers_completion.go219
-rw-r--r--internal/lsp/handlers_document.go160
-rw-r--r--internal/lsp/handlers_end_to_end_test.go454
-rw-r--r--internal/lsp/handlers_execute.go53
-rw-r--r--internal/lsp/handlers_helpers_test.go56
-rw-r--r--internal/lsp/handlers_init.go3
-rw-r--r--internal/lsp/handlers_test.go66
-rw-r--r--internal/lsp/handlers_utils.go265
-rw-r--r--internal/lsp/helpers_inline_prompt_test.go82
-rw-r--r--internal/lsp/helpers_more_test.go188
-rw-r--r--internal/lsp/init_and_trigger_test.go104
-rw-r--r--internal/lsp/init_shutdown_test.go27
-rw-r--r--internal/lsp/instruction_table_test.go36
-rw-r--r--internal/lsp/label_filter_table_test.go21
-rw-r--r--internal/lsp/llm_stats_test.go9
-rw-r--r--internal/lsp/log_context_test.go15
-rw-r--r--internal/lsp/postprocess_indent_test.go14
-rw-r--r--internal/lsp/prefix_table_test.go35
-rw-r--r--internal/lsp/provider_native_success_test.go66
-rw-r--r--internal/lsp/rewrite_diagnostics_realism_test.go113
-rw-r--r--internal/lsp/server.go163
-rw-r--r--internal/lsp/testfakes_test.go5
-rw-r--r--internal/lsp/transport.go3
-rw-r--r--internal/lsp/transport_test.go71
-rw-r--r--internal/lsp/triggers_config_test.go118
-rw-r--r--internal/lsp/types.go32
51 files changed, 2247 insertions, 1896 deletions
diff --git a/internal/lsp/build_prompts_table_test.go b/internal/lsp/build_prompts_table_test.go
index b0092e2..7e8e5e7 100644
--- a/internal/lsp/build_prompts_table_test.go
+++ b/internal/lsp/build_prompts_table_test.go
@@ -3,14 +3,18 @@ package lsp
import "testing"
func TestBuildPrompts_Table(t *testing.T) {
- p := CompletionParams{TextDocument: TextDocumentIdentifier{URI: "file:///x.go"}, Position: Position{Line:5, Character:7}}
- cases := []struct{ name string; inParams bool }{
- {"generic", false},
- {"in_params", true},
- }
- for _, c := range cases {
- sys, user := buildPrompts(c.inParams, p, "above", "current", "below", "func ctx")
- if sys == "" || user == "" { t.Fatalf("%s: prompts empty", c.name) }
- }
+ p := CompletionParams{TextDocument: TextDocumentIdentifier{URI: "file:///x.go"}, Position: Position{Line: 5, Character: 7}}
+ cases := []struct {
+ name string
+ inParams bool
+ }{
+ {"generic", false},
+ {"in_params", true},
+ }
+ for _, c := range cases {
+ sys, user := buildPrompts(c.inParams, p, "above", "current", "below", "func ctx")
+ if sys == "" || user == "" {
+ t.Fatalf("%s: prompts empty", c.name)
+ }
+ }
}
-
diff --git a/internal/lsp/chat_history_test.go b/internal/lsp/chat_history_test.go
index 0e9fed5..b1cae80 100644
--- a/internal/lsp/chat_history_test.go
+++ b/internal/lsp/chat_history_test.go
@@ -3,25 +3,35 @@ package lsp
import "testing"
func TestStripTrailingTrigger(t *testing.T) {
- if got := stripTrailingTrigger("what?"); got != "what" { t.Fatalf("should remove trailing ?") }
- if got := stripTrailingTrigger("what?>"); got != "what?" { t.Fatalf("should drop trailing > when preceded by ?") }
- if got := stripTrailingTrigger("ok!>"); got != "ok!" { t.Fatalf("should drop > after !") }
- if got := stripTrailingTrigger("note:>"); got != "note:" { t.Fatalf("should drop > after :") }
- if got := stripTrailingTrigger("go;>"); got != "go;" { t.Fatalf("should drop > after ;") }
+ if got := stripTrailingTrigger("what?"); got != "what" {
+ t.Fatalf("should remove trailing ?")
+ }
+ if got := stripTrailingTrigger("what?>"); got != "what?" {
+ t.Fatalf("should drop trailing > when preceded by ?")
+ }
+ if got := stripTrailingTrigger("ok!>"); got != "ok!" {
+ t.Fatalf("should drop > after !")
+ }
+ if got := stripTrailingTrigger("note:>"); got != "note:" {
+ t.Fatalf("should drop > after :")
+ }
+ if got := stripTrailingTrigger("go;>"); got != "go;" {
+ t.Fatalf("should drop > after ;")
+ }
}
func TestBuildChatHistory_OrderAndLimit(t *testing.T) {
- s := newTestServer()
- uri := "file:///chat.txt"
- // Conversation: q1, > a1, blank, q2, > a2 lines, then current prompt
- doc := "q1\n> a1\n\nq2\n> a2\n\n"
- s.setDocument(uri, doc)
- msgs := s.buildChatHistory(uri, 5, "q3")
- // Expect: user q1, assistant a1, user q2, assistant a2, user q3
- if len(msgs) != 5 || msgs[0].Role != "user" || msgs[1].Role != "assistant" || msgs[2].Role != "user" || msgs[3].Role != "assistant" || msgs[4].Role != "user" {
- t.Fatalf("unexpected roles: %+v", msgs)
- }
- if msgs[0].Content != "q1" || msgs[1].Content != "a1" || msgs[2].Content != "q2" || msgs[3].Content != "a2" || msgs[4].Content != "q3" {
- t.Fatalf("unexpected contents: %+v", msgs)
- }
+ s := newTestServer()
+ uri := "file:///chat.txt"
+ // Conversation: q1, > a1, blank, q2, > a2 lines, then current prompt
+ doc := "q1\n> a1\n\nq2\n> a2\n\n"
+ s.setDocument(uri, doc)
+ msgs := s.buildChatHistory(uri, 5, "q3")
+ // Expect: user q1, assistant a1, user q2, assistant a2, user q3
+ if len(msgs) != 5 || msgs[0].Role != "user" || msgs[1].Role != "assistant" || msgs[2].Role != "user" || msgs[3].Role != "assistant" || msgs[4].Role != "user" {
+ t.Fatalf("unexpected roles: %+v", msgs)
+ }
+ if msgs[0].Content != "q1" || msgs[1].Content != "a1" || msgs[2].Content != "q2" || msgs[3].Content != "a2" || msgs[4].Content != "q3" {
+ t.Fatalf("unexpected contents: %+v", msgs)
+ }
}
diff --git a/internal/lsp/chat_no_double_answer_test.go b/internal/lsp/chat_no_double_answer_test.go
index 9898ad9..8821cd0 100644
--- a/internal/lsp/chat_no_double_answer_test.go
+++ b/internal/lsp/chat_no_double_answer_test.go
@@ -1,22 +1,21 @@
package lsp
import (
- "bytes"
- "io"
- "log"
- "testing"
+ "bytes"
+ "io"
+ "log"
+ "testing"
)
func TestDetectAndHandleChat_NoDoubleAnswer(t *testing.T) {
- var out bytes.Buffer
- s := &Server{logger: log.New(io.Discard, "", 0), docs: make(map[string]*document), out: &out}
- s.llmClient = fakeLLM{resp: "IGNORED"}
- uri := "file:///x.go"
- // Question line with trigger, followed by an existing answer line starting with '>'
- s.setDocument(uri, "What?>\n> already answered\n")
- s.detectAndHandleChat(uri)
- if out.Len() != 0 {
- t.Fatalf("expected no applyEdit request when answer exists; got %d bytes", out.Len())
- }
+ var out bytes.Buffer
+ s := &Server{logger: log.New(io.Discard, "", 0), docs: make(map[string]*document), out: &out}
+ s.llmClient = fakeLLM{resp: "IGNORED"}
+ uri := "file:///x.go"
+ // Question line with trigger, followed by an existing answer line starting with '>'
+ s.setDocument(uri, "What?>\n> already answered\n")
+ s.detectAndHandleChat(uri)
+ if out.Len() != 0 {
+ t.Fatalf("expected no applyEdit request when answer exists; got %d bytes", out.Len())
+ }
}
-
diff --git a/internal/lsp/code_fences_table_test.go b/internal/lsp/code_fences_table_test.go
index c217bce..340ed61 100644
--- a/internal/lsp/code_fences_table_test.go
+++ b/internal/lsp/code_fences_table_test.go
@@ -3,30 +3,29 @@ package lsp
import "testing"
func TestStripCodeFences_Table(t *testing.T) {
- cases := []struct{ name, in, want string }{
- {"no_fence", "return x", "return x"},
- {"plain_fence", "```\nA\nB\n```", "A\nB"},
- {"lang_fence", "```go\nfmt.Println()\n```", "fmt.Println()"},
- {"spaces", " \n```python\nprint('x')\n```\n ", "print('x')"},
- }
- for _, c := range cases {
- if got := stripCodeFences(c.in); got != c.want {
- t.Fatalf("%s: got %q want %q", c.name, got, c.want)
- }
- }
+ cases := []struct{ name, in, want string }{
+ {"no_fence", "return x", "return x"},
+ {"plain_fence", "```\nA\nB\n```", "A\nB"},
+ {"lang_fence", "```go\nfmt.Println()\n```", "fmt.Println()"},
+ {"spaces", " \n```python\nprint('x')\n```\n ", "print('x')"},
+ }
+ for _, c := range cases {
+ if got := stripCodeFences(c.in); got != c.want {
+ t.Fatalf("%s: got %q want %q", c.name, got, c.want)
+ }
+ }
}
func TestStripInlineCodeSpan_Table(t *testing.T) {
- cases := []struct{ name, in, want string }{
- {"no_ticks", "text", "text"},
- {"single_span", "Use `foo()` here", "foo()"},
- {"multiple", "`a` + `b`", "a"},
- {"unmatched", "`missing end", "`missing end"},
- }
- for _, c := range cases {
- if got := stripInlineCodeSpan(c.in); got != c.want {
- t.Fatalf("%s: got %q want %q", c.name, got, c.want)
- }
- }
+ cases := []struct{ name, in, want string }{
+ {"no_ticks", "text", "text"},
+ {"single_span", "Use `foo()` here", "foo()"},
+ {"multiple", "`a` + `b`", "a"},
+ {"unmatched", "`missing end", "`missing end"},
+ }
+ for _, c := range cases {
+ if got := stripInlineCodeSpan(c.in); got != c.want {
+ t.Fatalf("%s: got %q want %q", c.name, got, c.want)
+ }
+ }
}
-
diff --git a/internal/lsp/codeaction_more_test.go b/internal/lsp/codeaction_more_test.go
index 412d988..82972d8 100644
--- a/internal/lsp/codeaction_more_test.go
+++ b/internal/lsp/codeaction_more_test.go
@@ -1,86 +1,109 @@
package lsp
import (
- "encoding/json"
- "path/filepath"
- "strings"
- "testing"
- tut "codeberg.org/snonux/hexai/internal/testutil"
+ "encoding/json"
+ "path/filepath"
+ "strings"
+ "testing"
+
+ tut "codeberg.org/snonux/hexai/internal/testutil"
)
func TestBuildDocumentCodeAction_AndResolve(t *testing.T) {
- s := newTestServer()
- s.llmClient = fakeLLM{resp: tut.MultilineDocBlock()+"\n"+"func add(a,b int) int { return a+b }"}
- uri := "file:///doc.go"
- s.setDocument(uri, "package x\nfunc add(a,b int) int {return a+b}")
- p := CodeActionParams{TextDocument: TextDocumentIdentifier{URI: uri}, Range: Range{Start: Position{Line:1, Character:0}, End: Position{Line:1, Character:10}}}
- sel := "func add(a,b int) int {return a+b}"
- ca := s.buildDocumentCodeAction(p, sel)
- if ca == nil { t.Fatalf("expected document code action") }
- resolved, ok := s.resolveCodeAction(*ca)
- if !ok || resolved.Edit == nil { t.Fatalf("expected resolved edit") }
- edits := resolved.Edit.Changes[uri]
- if len(edits) != 1 || strings.TrimSpace(edits[0].NewText) == "" { t.Fatalf("expected replacement text") }
+ s := newTestServer()
+ s.llmClient = fakeLLM{resp: tut.MultilineDocBlock() + "\n" + "func add(a,b int) int { return a+b }"}
+ uri := "file:///doc.go"
+ s.setDocument(uri, "package x\nfunc add(a,b int) int {return a+b}")
+ p := CodeActionParams{TextDocument: TextDocumentIdentifier{URI: uri}, Range: Range{Start: Position{Line: 1, Character: 0}, End: Position{Line: 1, Character: 10}}}
+ sel := "func add(a,b int) int {return a+b}"
+ ca := s.buildDocumentCodeAction(p, sel)
+ if ca == nil {
+ t.Fatalf("expected document code action")
+ }
+ resolved, ok := s.resolveCodeAction(*ca)
+ if !ok || resolved.Edit == nil {
+ t.Fatalf("expected resolved edit")
+ }
+ edits := resolved.Edit.Changes[uri]
+ if len(edits) != 1 || strings.TrimSpace(edits[0].NewText) == "" {
+ t.Fatalf("expected replacement text")
+ }
}
func TestResolveCodeAction_Rewrite(t *testing.T) {
- s := newTestServer()
- s.llmClient = fakeLLM{resp: "rewritten"}
- uri := "file:///x.go"
- s.setDocument(uri, "package p\nvar a=1\n")
- payload := struct {
- Type string `json:"type"`
- URI string `json:"uri"`
- Range Range `json:"range"`
- Instruction string `json:"instruction"`
- Selection string `json:"selection"`
- }{Type: "rewrite", URI: uri, Range: Range{Start: Position{Line:1}, End: Position{Line:1, Character: 5}}, Instruction: "do it", Selection: "var a"}
- raw, _ := json.Marshal(payload)
- ca := CodeAction{Title: "Hexai: rewrite selection", Data: raw}
- if resolved, ok := s.resolveCodeAction(ca); !ok || resolved.Edit == nil { t.Fatalf("expected resolved rewrite edit") }
+ s := newTestServer()
+ s.llmClient = fakeLLM{resp: "rewritten"}
+ uri := "file:///x.go"
+ s.setDocument(uri, "package p\nvar a=1\n")
+ payload := struct {
+ Type string `json:"type"`
+ URI string `json:"uri"`
+ Range Range `json:"range"`
+ Instruction string `json:"instruction"`
+ Selection string `json:"selection"`
+ }{Type: "rewrite", URI: uri, Range: Range{Start: Position{Line: 1}, End: Position{Line: 1, Character: 5}}, Instruction: "do it", Selection: "var a"}
+ raw, _ := json.Marshal(payload)
+ ca := CodeAction{Title: "Hexai: rewrite selection", Data: raw}
+ if resolved, ok := s.resolveCodeAction(ca); !ok || resolved.Edit == nil {
+ t.Fatalf("expected resolved rewrite edit")
+ }
}
func TestBuildGoUnitTestCodeAction_AndResolveCreate(t *testing.T) {
- s := newTestServer()
- // place files under a temp dir to avoid collisions
- dir := t.TempDir()
- srcPath := filepath.Join(dir, "calc.go")
- uri := "file://" + srcPath
- src := "package calc\n\nfunc Sum(a, b int) int { return a+b }\n"
- s.setDocument(uri, src)
- // Offer action (not a _test.go)
- p := CodeActionParams{TextDocument: TextDocumentIdentifier{URI: uri}, Range: Range{Start: Position{Line:2}}}
- if a := s.buildGoUnitTestCodeAction(p); a == nil { t.Fatalf("expected go unit test action") }
- // Resolve should create new test file with package+import and a test function
- we, testURI, _, ok := s.resolveGoTest(uri, Position{Line:2})
- if !ok { t.Fatalf("resolveGoTest failed") }
- if len(we.DocumentChanges) != 2 { t.Fatalf("expected create + edits, got %d", len(we.DocumentChanges)) }
- if !strings.HasSuffix(testURI, "_test.go") { t.Fatalf("unexpected test URI: %s", testURI) }
+ s := newTestServer()
+ // place files under a temp dir to avoid collisions
+ dir := t.TempDir()
+ srcPath := filepath.Join(dir, "calc.go")
+ uri := "file://" + srcPath
+ src := "package calc\n\nfunc Sum(a, b int) int { return a+b }\n"
+ s.setDocument(uri, src)
+ // Offer action (not a _test.go)
+ p := CodeActionParams{TextDocument: TextDocumentIdentifier{URI: uri}, Range: Range{Start: Position{Line: 2}}}
+ if a := s.buildGoUnitTestCodeAction(p); a == nil {
+ t.Fatalf("expected go unit test action")
+ }
+ // Resolve should create new test file with package+import and a test function
+ we, testURI, _, ok := s.resolveGoTest(uri, Position{Line: 2})
+ if !ok {
+ t.Fatalf("resolveGoTest failed")
+ }
+ if len(we.DocumentChanges) != 2 {
+ t.Fatalf("expected create + edits, got %d", len(we.DocumentChanges))
+ }
+ if !strings.HasSuffix(testURI, "_test.go") {
+ t.Fatalf("unexpected test URI: %s", testURI)
+ }
}
func TestBuildGoUnitTestCodeAction_SkipOnTestFile(t *testing.T) {
- s := newTestServer()
- uri := "file:///tmp/x_test.go"
- s.setDocument(uri, "package p\nfunc T(){}")
- p := CodeActionParams{TextDocument: TextDocumentIdentifier{URI: uri}}
- if a := s.buildGoUnitTestCodeAction(p); a != nil { t.Fatalf("expected no action on _test.go") }
+ s := newTestServer()
+ uri := "file:///tmp/x_test.go"
+ s.setDocument(uri, "package p\nfunc T(){}")
+ p := CodeActionParams{TextDocument: TextDocumentIdentifier{URI: uri}}
+ if a := s.buildGoUnitTestCodeAction(p); a != nil {
+ t.Fatalf("expected no action on _test.go")
+ }
}
func TestDiagnosticsInRange(t *testing.T) {
- s := newTestServer()
- ctx := CodeActionContext{Diagnostics: []Diagnostic{
- {Range: Range{Start: Position{Line: 3}, End: Position{Line: 3, Character: 5}}, Message: "in"},
- {Range: Range{Start: Position{Line: 10}, End: Position{Line: 11}}, Message: "out"},
- }}
- raw, _ := json.Marshal(ctx)
- got := s.diagnosticsInRange(json.RawMessage(raw), Range{Start: Position{Line:2}, End: Position{Line:4}})
- if len(got) != 1 || got[0].Message != "in" { t.Fatalf("unexpected diags: %+v", got) }
+ s := newTestServer()
+ ctx := CodeActionContext{Diagnostics: []Diagnostic{
+ {Range: Range{Start: Position{Line: 3}, End: Position{Line: 3, Character: 5}}, Message: "in"},
+ {Range: Range{Start: Position{Line: 10}, End: Position{Line: 11}}, Message: "out"},
+ }}
+ raw, _ := json.Marshal(ctx)
+ got := s.diagnosticsInRange(json.RawMessage(raw), Range{Start: Position{Line: 2}, End: Position{Line: 4}})
+ if len(got) != 1 || got[0].Message != "in" {
+ t.Fatalf("unexpected diags: %+v", got)
+ }
}
func TestDocBeforeAfter(t *testing.T) {
- s := newTestServer()
- uri := "file:///d.go"
- s.setDocument(uri, "ab\ncd\nef")
- before, after := s.docBeforeAfter(uri, Position{Line:1, Character:1})
- if before != "ab\nc" || after != "d\nef" { t.Fatalf("before=%q after=%q", before, after) }
+ s := newTestServer()
+ uri := "file:///d.go"
+ s.setDocument(uri, "ab\ncd\nef")
+ before, after := s.docBeforeAfter(uri, Position{Line: 1, Character: 1})
+ if before != "ab\nc" || after != "d\nef" {
+ t.Fatalf("before=%q after=%q", before, after)
+ }
}
diff --git a/internal/lsp/codeaction_test.go b/internal/lsp/codeaction_test.go
index 4de0790..29cb416 100644
--- a/internal/lsp/codeaction_test.go
+++ b/internal/lsp/codeaction_test.go
@@ -1,10 +1,11 @@
package lsp
import (
- "context"
- "encoding/json"
- "codeberg.org/snonux/hexai/internal/llm"
- "testing"
+ "context"
+ "encoding/json"
+ "testing"
+
+ "codeberg.org/snonux/hexai/internal/llm"
)
type fakeLLM struct {
@@ -22,7 +23,7 @@ func TestBuildRewriteCodeAction_LazyAndResolves(t *testing.T) {
s := newTestServer()
s.llmClient = fakeLLM{resp: "REWRITTEN"}
p := CodeActionParams{TextDocument: TextDocumentIdentifier{URI: "file:///t.go"}, Range: Range{Start: Position{Line: 1, Character: 2}, End: Position{Line: 3, Character: 4}}}
- sel := ">rewrite>\nold code"
+ sel := ">rewrite>\nold code"
ca := s.buildRewriteCodeAction(p, sel)
if ca == nil {
t.Fatalf("expected code action")
diff --git a/internal/lsp/codegen_helpers_test.go b/internal/lsp/codegen_helpers_test.go
index d897953..de43b7d 100644
--- a/internal/lsp/codegen_helpers_test.go
+++ b/internal/lsp/codegen_helpers_test.go
@@ -3,13 +3,20 @@ package lsp
import "testing"
func TestParseGoPackageName(t *testing.T) {
- lines := []string{"// comment", "package mypkg // trailing"}
- if got := parseGoPackageName(lines); got != "mypkg" { t.Fatalf("got %q", got) }
- if got := parseGoPackageName([]string{"no package"}); got != "" { t.Fatalf("expected empty") }
+ lines := []string{"// comment", "package mypkg // trailing"}
+ if got := parseGoPackageName(lines); got != "mypkg" {
+ t.Fatalf("got %q", got)
+ }
+ if got := parseGoPackageName([]string{"no package"}); got != "" {
+ t.Fatalf("expected empty")
+ }
}
func TestDeriveGoFuncName(t *testing.T) {
- if got := deriveGoFuncName("func Sum(a int) int { return a }"); got != "Sum" { t.Fatalf("got %q", got) }
- if got := deriveGoFuncName("func (t *Type) Method(x int) {}"); got != "Method" { t.Fatalf("got %q", got) }
+ if got := deriveGoFuncName("func Sum(a int) int { return a }"); got != "Sum" {
+ t.Fatalf("got %q", got)
+ }
+ if got := deriveGoFuncName("func (t *Type) Method(x int) {}"); got != "Method" {
+ t.Fatalf("got %q", got)
+ }
}
-
diff --git a/internal/lsp/completion_cache_test.go b/internal/lsp/completion_cache_test.go
index 9ef0f00..65631f9 100644
--- a/internal/lsp/completion_cache_test.go
+++ b/internal/lsp/completion_cache_test.go
@@ -1,12 +1,12 @@
package lsp
import (
- "bytes"
- "log"
- "strings"
- "testing"
+ "bytes"
+ "log"
+ "strings"
+ "testing"
- "codeberg.org/snonux/hexai/internal/logging"
+ "codeberg.org/snonux/hexai/internal/logging"
)
func TestCompletionCache_IgnoresWhitespaceBeforeCursor(t *testing.T) {
diff --git a/internal/lsp/completion_codex_path_test.go b/internal/lsp/completion_codex_path_test.go
index 6030d92..bd3b3f4 100644
--- a/internal/lsp/completion_codex_path_test.go
+++ b/internal/lsp/completion_codex_path_test.go
@@ -1,11 +1,11 @@
package lsp
import (
- "context"
- "errors"
- "testing"
+ "context"
+ "errors"
+ "testing"
- "codeberg.org/snonux/hexai/internal/llm"
+ "codeberg.org/snonux/hexai/internal/llm"
)
// fakeCodeLLM implements both llm.Client and llm.CodeCompleter.
diff --git a/internal/lsp/completion_helpers_more_test.go b/internal/lsp/completion_helpers_more_test.go
index 02fe9f3..79d2523 100644
--- a/internal/lsp/completion_helpers_more_test.go
+++ b/internal/lsp/completion_helpers_more_test.go
@@ -1,35 +1,51 @@
package lsp
import (
- "encoding/json"
- "testing"
+ "encoding/json"
+ "testing"
)
func TestExtractTriggerInfo_ParseManualInvoke(t *testing.T) {
- // Compose a CompletionParams with a raw JSON context
- ctx := struct{ TriggerKind int `json:"triggerKind"`; TriggerCharacter string `json:"triggerCharacter"` }{TriggerKind: 1, TriggerCharacter: "."}
- raw, _ := json.Marshal(ctx)
- p := CompletionParams{Context: json.RawMessage(raw)}
- kind, ch := extractTriggerInfo(p)
- if kind != 1 || ch != "." { t.Fatalf("unexpected trigger info: %d %q", kind, ch) }
- if !parseManualInvoke(json.RawMessage(raw)) { t.Fatalf("expected manual invoke true") }
+ // Compose a CompletionParams with a raw JSON context
+ ctx := struct {
+ TriggerKind int `json:"triggerKind"`
+ TriggerCharacter string `json:"triggerCharacter"`
+ }{TriggerKind: 1, TriggerCharacter: "."}
+ raw, _ := json.Marshal(ctx)
+ p := CompletionParams{Context: json.RawMessage(raw)}
+ kind, ch := extractTriggerInfo(p)
+ if kind != 1 || ch != "." {
+ t.Fatalf("unexpected trigger info: %d %q", kind, ch)
+ }
+ if !parseManualInvoke(json.RawMessage(raw)) {
+ t.Fatalf("expected manual invoke true")
+ }
}
func TestShouldSuppressForChatTriggerEOL(t *testing.T) {
- s := newTestServer()
- p := CompletionParams{TextDocument: TextDocumentIdentifier{URI: "file:///x"}, Position: Position{Line:0, Character:10}}
- line := "say hi;>"
- if !s.shouldSuppressForChatTriggerEOL(line, p) { t.Fatalf("expected suppression when ;> at EOL") }
- if s.shouldSuppressForChatTriggerEOL("plain>", p) { t.Fatalf("should not suppress for plain >") }
+ s := newTestServer()
+ p := CompletionParams{TextDocument: TextDocumentIdentifier{URI: "file:///x"}, Position: Position{Line: 0, Character: 10}}
+ line := "say hi;>"
+ if !s.shouldSuppressForChatTriggerEOL(line, p) {
+ t.Fatalf("expected suppression when ;> at EOL")
+ }
+ if s.shouldSuppressForChatTriggerEOL("plain>", p) {
+ t.Fatalf("should not suppress for plain >")
+ }
}
func TestPrefixHeuristicAllows(t *testing.T) {
- s := newTestServer()
- // inline prompt allows zero prefix
- if !s.prefixHeuristicAllows(true, "", CompletionParams{Position: Position{Line:0, Character:0}}, false) { t.Fatalf("inline prompt should allow") }
- // structural triggers like '.' allow without prefix
- if !s.prefixHeuristicAllows(false, "fmt.", CompletionParams{Position: Position{Line:0, Character:4}}, false) { t.Fatalf("dot trigger should allow") }
- // otherwise need at least minimal prefix (default min=1)
- if s.prefixHeuristicAllows(false, " ", CompletionParams{Position: Position{Line:0, Character:0}}, false) { t.Fatalf("should not allow with no prefix") }
+ s := newTestServer()
+ // inline prompt allows zero prefix
+ if !s.prefixHeuristicAllows(true, "", CompletionParams{Position: Position{Line: 0, Character: 0}}, false) {
+ t.Fatalf("inline prompt should allow")
+ }
+ // structural triggers like '.' allow without prefix
+ if !s.prefixHeuristicAllows(false, "fmt.", CompletionParams{Position: Position{Line: 0, Character: 4}}, false) {
+ t.Fatalf("dot trigger should allow")
+ }
+ // otherwise need at least minimal prefix (default min=1)
+ if s.prefixHeuristicAllows(false, " ", CompletionParams{Position: Position{Line: 0, Character: 0}}, false) {
+ t.Fatalf("should not allow with no prefix")
+ }
}
-
diff --git a/internal/lsp/completion_messages_test.go b/internal/lsp/completion_messages_test.go
index e9ec3e5..28908d5 100644
--- a/internal/lsp/completion_messages_test.go
+++ b/internal/lsp/completion_messages_test.go