summaryrefslogtreecommitdiff
path: root/internal/lsp/handlers_test.go
blob: bde9b824423c999ca0df9ac621f3e12d58535fee (plain)
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
package lsp

import (
    "strings"
    "testing"
)

func TestInParamList(t *testing.T) {
    line := "func foo(a int, b string) int {"
    if !inParamList(line, 15) { // inside params
        t.Fatalf("expected inParamList true for cursor inside params")
    }
    if inParamList(line, 2) { // before 'func'
        t.Fatalf("expected inParamList false for cursor before params")
    }
    if inParamList(line, len(line)) { // after ')'
        t.Fatalf("expected inParamList false for cursor after params")
    }
}

func TestComputeWordStart(t *testing.T) {
    current := "fmt.Prin"
    // Cursor after the word (index 8)
    got := computeWordStart(current, 8)
    // should stop after the dot at index 4
    if want := 4; got != want {
        t.Fatalf("computeWordStart got %d want %d", got, want)
    }
}

func TestComputeTextEditAndFilter_InParams(t *testing.T) {
    current := "func foo(a int, b string) {" // ')' at index 26
    p := CompletionParams{Position: Position{Line: 10, Character: 20}}
    te, filter := computeTextEditAndFilter("x int, y string", true, current, p)

    if te == nil {
        t.Fatalf("expected TextEdit")
    }
    // left should be after '(' which is at index 8
    if te.Range.Start.Line != 10 || te.Range.Start.Character != 9 {
        t.Fatalf("start got line=%d char=%d want line=10 char=9", te.Range.Start.Line, te.Range.Start.Character)
    }
    // right should clamp to cursor (20)
    if te.Range.End.Line != 10 || te.Range.End.Character != 20 {
        t.Fatalf("end got line=%d char=%d want line=10 char=20", te.Range.End.Line, te.Range.End.Character)
    }
    if filter == "" {
        t.Fatalf("expected non-empty filter inside params")
    }
}

func TestComputeTextEditAndFilter_Word(t *testing.T) {
    current := "fmt.Prin"
    p := CompletionParams{Position: Position{Line: 2, Character: len(current)}}
    te, filter := computeTextEditAndFilter("Println", false, current, p)
    if te == nil {
        t.Fatalf("expected TextEdit")
    }
    if te.Range.Start.Character != 4 || te.Range.End.Character != len(current) {
        t.Fatalf("range chars got %d..%d want 4..%d", te.Range.Start.Character, te.Range.End.Character, len(current))
    }
    if filter != "Prin" {
        t.Fatalf("filter got %q want %q", filter, "Prin")
    }
}

func TestLabelForCompletion(t *testing.T) {
    if got := labelForCompletion("Println", "Pri"); got != "Println" {
        t.Fatalf("label mismatch got %q want %q", got, "Println")
    }
    if got := labelForCompletion("Println", "X"); got != "X" {
        t.Fatalf("label mismatch with filter got %q want %q", got, "X")
    }
    if got := labelForCompletion("Println\nmore", ""); got != "Println" {
        t.Fatalf("label firstLine got %q want %q", got, "Println")
    }
}

func TestBuildPrompts_InParams(t *testing.T) {
    p := CompletionParams{TextDocument: TextDocumentIdentifier{URI: "file:///t.go"}, Position: Position{Line: 1, Character: 12}}
    sys, user := buildPrompts(true, p, "above", "func foo(", "below", "func foo(")
    if sys == "" || user == "" {
        t.Fatalf("expected non-empty prompts")
    }
    if want := "function signatures"; !contains(sys, want) {
        t.Fatalf("system prompt missing %q: %q", want, sys)
    }
    if want := "parameter list"; !contains(user, want) {
        t.Fatalf("user prompt missing %q: %q", want, user)
    }
}

func TestBuildPrompts_Outside(t *testing.T) {
    p := CompletionParams{TextDocument: TextDocumentIdentifier{URI: "file:///t.go"}, Position: Position{Line: 1, Character: 5}}
    sys, user := buildPrompts(false, p, "ab", "cur", "be", "fnctx")
    if sys == "" || user == "" {
        t.Fatalf("expected non-empty prompts")
    }
    if want := "completion engine"; !contains(sys, want) {
        t.Fatalf("system prompt missing %q: %q", want, sys)
    }
    if want := "Provide the next likely code"; !contains(user, want) {
        t.Fatalf("user prompt missing %q: %q", want, user)
    }
}

func TestComputeTextEditAndFilter_NoParensFallback(t *testing.T) {
    current := "func foo bar" // no parentheses
    cursor := len(current)
    p := CompletionParams{Position: Position{Line: 0, Character: cursor}}
    te, filter := computeTextEditAndFilter("baz", true, current, p)
    if te == nil {
        t.Fatalf("expected TextEdit from fallback path")
    }
    // fallback should behave like word edit; start at last space + 1
    lastSpace := strings.LastIndex(current, " ")
    if te.Range.Start.Character != lastSpace+1 || te.Range.End.Character != cursor {
        t.Fatalf("range got %d..%d want %d..%d", te.Range.Start.Character, te.Range.End.Character, lastSpace+1, cursor)
    }
    if filter != "bar" {
        t.Fatalf("filter got %q want %q", filter, "bar")
    }
}

// small helper to avoid importing strings
func contains(s, sub string) bool { return len(s) >= len(sub) && (func() bool { i := 0; for i+len(sub) <= len(s) { if s[i:i+len(sub)] == sub { return true }; i++ }; return false })() }