summaryrefslogtreecommitdiff
path: root/internal/lsp/chat_context_mode_test.go
blob: 85fa4a9faf74e70a3f2c8ec33cbd86c03229ffbf (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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package lsp

import (
	"bytes"
	"strings"
	"testing"
	"time"
)

// Ensure in-editor chat respects general.context_mode by adding window/full-file context.
func TestChat_RespectsContextModeWindow(t *testing.T) {
	s := newTestServer()
	// Configure window mode with small window
	s.contextMode = "window"
	s.windowLines = 2
	s.maxContextTokens = 2000
	cap := &captureLLM{}
	s.llmClient = cap
	var out bytes.Buffer
	s.out = &out

	uri := "file:///ctx.go"
	// Build a small file where the last line triggers chat
	src := "package main\nline2 context\nwhat?>\n"
	s.setDocument(uri, src)

	s.detectAndHandleChat(uri)
	// Wait briefly for async goroutine to call Chat
	for i := 0; i < 40 && len(cap.msgs) == 0; i++ {
		time.Sleep(10 * time.Millisecond)
	}
	if len(cap.msgs) == 0 {
		t.Fatalf("expected Chat to be called")
	}
	// Expect first system, then an extra context user message, then history ending with prompt
	if cap.msgs[0].Role != "system" {
		t.Fatalf("first message should be system, got %q", cap.msgs[0].Role)
	}
	if len(cap.msgs) < 3 {
		t.Fatalf("expected at least 3 messages (system, extra, user prompt), got %d", len(cap.msgs))
	}
	extra := cap.msgs[1]
	if extra.Role != "user" || !strings.HasPrefix(extra.Content, "Additional context:\n") {
		t.Fatalf("second message should be user extra context, got role=%q content=%q", extra.Role, extra.Content)
	}
	if !strings.Contains(extra.Content, "line2 context") {
		t.Fatalf("extra context should include window text; got %q", extra.Content)
	}
	last := cap.msgs[len(cap.msgs)-1]
	if last.Role != "user" || last.Content != "what?" {
		t.Fatalf("last message should be current prompt user, got %+v", last)
	}
}

func TestChat_ContextModeMinimal_NoExtra(t *testing.T) {
	s := newTestServer()
	s.contextMode = "minimal"
	s.maxContextTokens = 2000
	cap := &captureLLM{}
	s.llmClient = cap
	var out bytes.Buffer
	s.out = &out

	uri := "file:///ctx2.go"
	s.setDocument(uri, "package main\nhelp?>\n")
	s.detectAndHandleChat(uri)

	for i := 0; i < 40 && len(cap.msgs) == 0; i++ {
		time.Sleep(10 * time.Millisecond)
	}
	if len(cap.msgs) != 2 {
		t.Fatalf("expected exactly 2 messages (system + user prompt), got %d", len(cap.msgs))
	}
	if cap.msgs[0].Role != "system" || cap.msgs[1].Role != "user" || cap.msgs[1].Content != "help?" {
		t.Fatalf("unexpected messages: %+v", cap.msgs)
	}
}

func TestChat_ContextModeAlwaysFull_AddsExtra(t *testing.T) {
	s := newTestServer()
	s.contextMode = "always-full"
	s.maxContextTokens = 2000
	cap := &captureLLM{}
	s.llmClient = cap
	var out bytes.Buffer
	s.out = &out

	uri := "file:///ctx3.go"
	s.setDocument(uri, "package main\nline2\nhelp?>\n")
	s.detectAndHandleChat(uri)

	for i := 0; i < 40 && len(cap.msgs) == 0; i++ {
		time.Sleep(10 * time.Millisecond)
	}
	if len(cap.msgs) < 3 {
		t.Fatalf("expected >=3 messages (system, extra, user prompt), got %d", len(cap.msgs))
	}
	if cap.msgs[1].Role != "user" || !strings.HasPrefix(cap.msgs[1].Content, "Additional context:\n") {
		t.Fatalf("second message should be user extra context, got role=%q content=%q", cap.msgs[1].Role, cap.msgs[1].Content)
	}
	if !strings.Contains(cap.msgs[1].Content, "package main") {
		t.Fatalf("extra context should include full file, got %q", cap.msgs[1].Content)
	}
	if last := cap.msgs[len(cap.msgs)-1]; last.Role != "user" || last.Content != "help?" {
		t.Fatalf("last message should be the current prompt, got %+v", last)
	}
}

func TestChat_ContextModeFileOnNewFunc_NoExtraWithoutSignature(t *testing.T) {
	s := newTestServer()
	s.contextMode = "file-on-new-func"
	s.maxContextTokens = 2000
	cap := &captureLLM{}
	s.llmClient = cap
	var out bytes.Buffer
	s.out = &out

	uri := "file:///ctx4.go"
	s.setDocument(uri, "package main\nhelp?>\n")
	s.detectAndHandleChat(uri)

	for i := 0; i < 40 && len(cap.msgs) == 0; i++ {
		time.Sleep(10 * time.Millisecond)
	}
	if len(cap.msgs) != 2 {
		t.Fatalf("expected exactly 2 messages (system + user prompt), got %d", len(cap.msgs))
	}
}

func TestChat_ContextModeFileOnNewFunc_WithSignature_AddsExtra(t *testing.T) {
	s := newTestServer()
	s.contextMode = "file-on-new-func"
	s.maxContextTokens = 2000
	cap := &captureLLM{}
	s.llmClient = cap
	var out bytes.Buffer
	s.out = &out

	uri := "file:///ctx5.go"
	// Signature without '{' yet; chat prompt appears before the body, so newFunc=true
	src := "package main\n\nfunc add(x int) int\nhelp?>\n"
	s.setDocument(uri, src)
	s.detectAndHandleChat(uri)

	for i := 0; i < 40 && len(cap.msgs) == 0; i++ {
		time.Sleep(10 * time.Millisecond)
	}
	if len(cap.msgs) < 3 {
		t.Fatalf("expected >=3 messages (system, extra, user prompt), got %d", len(cap.msgs))
	}
	if cap.msgs[1].Role != "user" || !strings.HasPrefix(cap.msgs[1].Content, "Additional context:\n") {
		t.Fatalf("second message should be user extra context, got role=%q content=%q", cap.msgs[1].Role, cap.msgs[1].Content)
	}
	if !strings.Contains(cap.msgs[1].Content, "func add(x int) int") {
		t.Fatalf("extra context should include full file or signature, got %q", cap.msgs[1].Content)
	}
}