1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
|
// Summary: Tests for LSP document model (line management, edits, and transformations).
package lsp
import (
"io"
"log"
"strings"
"testing"
"codeberg.org/snonux/hexai/internal/appconfig"
"codeberg.org/snonux/hexai/internal/llm"
)
func newTestServer() *Server {
cfg := appconfig.App{
InlineOpen: ">!",
InlineClose: ">",
ChatSuffix: ">",
ChatPrefixes: []string{"?", "!", ":", ";"},
PromptCompletionSystemParams: "You are a code completion engine for function signatures. Return only the parameter list contents (without parentheses), no braces, no prose. Prefer idiomatic names and types.",
PromptCompletionUserParams: "Cursor is inside the function parameter list. Suggest only the parameter list (no parentheses).\nFunction line: {{function}}\nCurrent line (cursor at {{char}}): {{current}}",
PromptCompletionSystemGeneral: "You are a terse code completion engine. Return only the code to insert, no surrounding prose or backticks. Only continue from the cursor; never repeat characters already present to the left of the cursor on the current line (e.g., if 'name :=' is already typed, only return the right-hand side expression).",
PromptCompletionUserGeneral: "Provide the next likely code to insert at the cursor.\nFile: {{file}}\nFunction/context: {{function}}\nAbove line: {{above}}\nCurrent line (cursor at character {{char}}): {{current}}\nBelow line: {{below}}\nOnly return the completion snippet.",
PromptCompletionSystemInline: "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.",
PromptCompletionExtraHeader: "Additional context:\n{{context}}",
PromptNativeCompletion: "// Path: {{path}}\n{{before}}",
PromptChatSystem: "You are a helpful coding assistant. Answer concisely and clearly.",
PromptCodeActionRewriteSystem: "You are a precise code refactoring engine. Rewrite the given code strictly according to the instruction. Return only the updated code with no prose or backticks. Preserve formatting where reasonable.",
PromptCodeActionDiagnosticsSystem: "You are a precise code fixer. Resolve the given diagnostics by editing only the selected code. Return only the corrected code with no prose or backticks. Keep behavior and style, and avoid unrelated changes.",
PromptCodeActionDocumentSystem: "You are a precise code documentation engine. Add idiomatic documentation comments to the given code. Preserve exact behavior and formatting as much as possible. Return only the updated code with comments, no prose or backticks.",
PromptCodeActionRewriteUser: "Instruction: {{instruction}}\n\nSelected code to transform:\n{{selection}}",
PromptCodeActionDiagnosticsUser: "Diagnostics to resolve (selection only):\n{{diagnostics}}\n\nSelected code:\n{{selection}}",
PromptCodeActionDocumentUser: "Add documentation comments to this code:\n{{selection}}",
PromptCodeActionGoTestSystem: "You are a precise Go unit test generator. Given a Go function, write one or more Test* functions using the testing package. Do NOT include package or imports, only the test function(s). Prefer table-driven tests. Keep it minimal and idiomatic.",
PromptCodeActionGoTestUser: "Function under test:\n{{function}}",
}
return &Server{
logger: log.New(io.Discard, "", 0),
docs: make(map[string]*document),
cfg: cfg,
altClients: make(map[string]llm.Client),
llmProvider: canonicalProvider(cfg.Provider),
}
}
func initServerDefaults(s *Server) {
cfg := s.cfg
if strings.TrimSpace(cfg.InlineOpen) == "" {
cfg.InlineOpen = ">!"
}
if strings.TrimSpace(cfg.InlineClose) == "" {
cfg.InlineClose = ">"
}
if strings.TrimSpace(cfg.ChatSuffix) == "" {
cfg.ChatSuffix = ">"
}
if len(cfg.ChatPrefixes) == 0 {
cfg.ChatPrefixes = []string{"?", "!", ":", ";"}
}
s.cfg = cfg
}
func TestSplitLines(t *testing.T) {
in := "a\r\nb\nc"
got := splitLines(in)
want := []string{"a", "b", "c"}
if len(got) != len(want) {
t.Fatalf("len mismatch: got %d want %d", len(got), len(want))
}
for i := range want {
if got[i] != want[i] {
t.Fatalf("line %d: got %q want %q", i, got[i], want[i])
}
}
}
func TestLineContext(t *testing.T) {
s := newTestServer()
src := "package main\n\nfunc add(a, b int) int {\n\treturn a + b\n}\n"
uri := "file:///test.go"
s.setDocument(uri, src)
// Position on the return line (line 3, zero-based)
above, current, below, funcCtx := s.lineContext(uri, Position{Line: 3, Character: 0})
if want := "func add(a, b int) int {"; funcCtx != want {
t.Fatalf("funcCtx got %q want %q", funcCtx, want)
}
if want := "func add(a, b int) int {"; above != want {
t.Fatalf("above got %q want %q", above, want)
}
if want := "\treturn a + b"; current != want {
t.Fatalf("current got %q want %q", current, want)
}
if want := "}"; below != want {
t.Fatalf("below got %q want %q", below, want)
}
}
func TestLineContext_EmptyDoc(t *testing.T) {
s := newTestServer()
a, c, b, f := s.lineContext("file:///missing.go", Position{Line: 0, Character: 0})
if a != "" || b != "" || c != "" || f != "" {
t.Fatalf("expected all empty for missing doc; got above=%q current=%q below=%q func=%q", a, c, b, f)
}
}
func TestDocBeforeAfter_ClampsIndices(t *testing.T) {
s := newTestServer()
uri := "file:///clamp.go"
s.setDocument(uri, "abc\nxyz")
// Position beyond document length should be clamped safely
before, after := s.docBeforeAfter(uri, Position{Line: 99, Character: 99})
if before == "" && after == "" {
t.Fatalf("expected some text with clamped indices")
}
}
func TestTrimLen(t *testing.T) {
long := strings.Repeat("a", 205)
got := trimLen(long)
want := strings.Repeat("a", 200) + "…"
if got != want {
t.Fatalf("trimLen got %q want %q", got, want)
}
}
func TestFirstLine(t *testing.T) {
s := "first line\r\nsecond line"
if got := firstLine(s); got != "first line" {
t.Fatalf("firstLine got %q want %q", got, "first line")
}
}
|