gitstack

grindlemire/go-tui code browser

7.8 KB Go 332 lines 2026-06-03 ยท d15bb9f raw
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
package tui

import (
	"strings"
)

// MockTerminal is a mock implementation of Terminal for testing.
// It captures all operations and maintains an internal buffer for verification.
type MockTerminal struct {
	width, height int
	cells         []Cell
	cursorX       int
	cursorY       int
	cursorHidden  bool
	inRawMode     bool
	inAltScreen   bool
	mouseEnabled  bool
	altScroll     bool
	caps          Capabilities

	// Transition counters for testing screen mode switches
	altScreenEnterCount int
	altScreenExitCount  int
}

// Ensure MockTerminal implements Terminal.
var _ Terminal = (*MockTerminal)(nil)

// NewMockTerminal creates a new mock terminal with the given dimensions.
func NewMockTerminal(width, height int) *MockTerminal {
	size := width * height
	cells := make([]Cell, size)

	// Initialize with spaces
	defaultCell := NewCell(' ', NewStyle())
	for i := range cells {
		cells[i] = defaultCell
	}

	return &MockTerminal{
		width:  width,
		height: height,
		cells:  cells,
		caps: Capabilities{
			Colors:    Color256,
			Unicode:   true,
			TrueColor: true,
			AltScreen: true,
		},
	}
}

// Size returns the terminal dimensions.
func (m *MockTerminal) Size() (width, height int) {
	return m.width, m.height
}

// Flush applies the given cell changes to the mock terminal's buffer.
func (m *MockTerminal) Flush(changes []CellChange) {
	blank := NewCell(' ', NewStyle())
	for _, ch := range changes {
		if ch.Y < 0 || ch.Y >= m.height {
			continue
		}
		if ch.EraseToEOL {
			for x := ch.X; x < m.width; x++ {
				if x >= 0 {
					m.cells[ch.Y*m.width+x] = blank
				}
			}
			continue
		}
		if ch.X >= 0 && ch.X < m.width {
			idx := ch.Y*m.width + ch.X
			m.cells[idx] = ch.Cell
		}
	}
}

// Clear clears the entire terminal to spaces with default style.
func (m *MockTerminal) Clear() {
	defaultCell := NewCell(' ', NewStyle())
	for i := range m.cells {
		m.cells[i] = defaultCell
	}
	m.cursorX = 0
	m.cursorY = 0
}

// ClearToEnd clears from cursor position to end of screen.
func (m *MockTerminal) ClearToEnd() {
	defaultCell := NewCell(' ', NewStyle())
	// Start from current cursor position
	startIdx := m.cursorY*m.width + m.cursorX
	for i := startIdx; i < len(m.cells); i++ {
		m.cells[i] = defaultCell
	}
}

// SetCursor moves the cursor to the specified position.
func (m *MockTerminal) SetCursor(x, y int) {
	m.cursorX = x
	m.cursorY = y
}

// HideCursor makes the cursor invisible.
func (m *MockTerminal) HideCursor() {
	m.cursorHidden = true
}

// ShowCursor makes the cursor visible.
func (m *MockTerminal) ShowCursor() {
	m.cursorHidden = false
}

// EnterRawMode simulates entering raw mode.
func (m *MockTerminal) EnterRawMode() error {
	m.inRawMode = true
	return nil
}

// ExitRawMode simulates exiting raw mode.
func (m *MockTerminal) ExitRawMode() error {
	m.inRawMode = false
	return nil
}

// EnterAltScreen simulates entering the alternate screen buffer.
func (m *MockTerminal) EnterAltScreen() {
	m.inAltScreen = true
	m.altScreenEnterCount++
}

// ExitAltScreen simulates exiting the alternate screen buffer.
func (m *MockTerminal) ExitAltScreen() {
	m.inAltScreen = false
	m.altScreenExitCount++
}

// EnableMouse simulates enabling mouse event reporting.
func (m *MockTerminal) EnableMouse() {
	m.mouseEnabled = true
}

// DisableMouse simulates disabling mouse event reporting.
func (m *MockTerminal) DisableMouse() {
	m.mouseEnabled = false
}

// EnableAltScroll simulates enabling alternate-scroll mode.
func (m *MockTerminal) EnableAltScroll() {
	m.altScroll = true
}

// DisableAltScroll simulates disabling alternate-scroll mode.
func (m *MockTerminal) DisableAltScroll() {
	m.altScroll = false
}

// IsAltScrollEnabled returns whether alternate-scroll mode is enabled.
func (m *MockTerminal) IsAltScrollEnabled() bool {
	return m.altScroll
}

// NegotiateKittyKeyboard is a no-op for the mock terminal.
func (m *MockTerminal) NegotiateKittyKeyboard() bool {
	return false
}

// EnableKittyKeyboard is a no-op for the mock terminal.
func (m *MockTerminal) EnableKittyKeyboard() {
}

// DisableKittyKeyboard is a no-op for the mock terminal.
func (m *MockTerminal) DisableKittyKeyboard() {
}

// ResetStyle is a no-op for the mock terminal.
func (m *MockTerminal) ResetStyle() {
}

// Caps returns the terminal's capabilities.
func (m *MockTerminal) Caps() Capabilities {
	return m.caps
}

// WriteDirect is a no-op for the mock terminal.
// In tests, raw escape sequences are not processed.
func (m *MockTerminal) WriteDirect(b []byte) (int, error) {
	return len(b), nil
}

// SetCaps sets the terminal's capabilities for testing.
func (m *MockTerminal) SetCaps(caps Capabilities) {
	m.caps = caps
}

// --- Test helper methods ---

// CellAt returns the cell at the given position.
// Returns an empty Cell if out of bounds.
func (m *MockTerminal) CellAt(x, y int) Cell {
	if x < 0 || x >= m.width || y < 0 || y >= m.height {
		return Cell{}
	}
	return m.cells[y*m.width+x]
}

// String renders the terminal buffer to a string for snapshot testing.
// Each row is separated by a newline.
func (m *MockTerminal) String() string {
	var sb strings.Builder
	for y := 0; y < m.height; y++ {
		for x := 0; x < m.width; x++ {
			cell := m.cells[y*m.width+x]
			if cell.IsContinuation() {
				continue // Skip continuation cells
			}
			if cell.Rune == 0 {
				sb.WriteRune(' ')
			} else {
				sb.WriteRune(cell.Rune)
			}
		}
		if y < m.height-1 {
			sb.WriteRune('\n')
		}
	}
	return sb.String()
}

// StringTrimmed returns the terminal content with trailing spaces removed from each line.
func (m *MockTerminal) StringTrimmed() string {
	var sb strings.Builder
	for y := 0; y < m.height; y++ {
		var line strings.Builder
		for x := 0; x < m.width; x++ {
			cell := m.cells[y*m.width+x]
			if cell.IsContinuation() {
				continue
			}
			if cell.Rune == 0 {
				line.WriteRune(' ')
			} else {
				line.WriteRune(cell.Rune)
			}
		}
		sb.WriteString(strings.TrimRight(line.String(), " "))
		if y < m.height-1 {
			sb.WriteRune('\n')
		}
	}
	return sb.String()
}

// Cursor returns the current cursor position.
func (m *MockTerminal) Cursor() (x, y int) {
	return m.cursorX, m.cursorY
}

// IsCursorHidden returns whether the cursor is hidden.
func (m *MockTerminal) IsCursorHidden() bool {
	return m.cursorHidden
}

// IsInRawMode returns whether the terminal is in raw mode.
func (m *MockTerminal) IsInRawMode() bool {
	return m.inRawMode
}

// IsInAltScreen returns whether the terminal is using the alternate screen buffer.
func (m *MockTerminal) IsInAltScreen() bool {
	return m.inAltScreen
}

// AltScreenEnterCount returns the number of times EnterAltScreen was called.
func (m *MockTerminal) AltScreenEnterCount() int {
	return m.altScreenEnterCount
}

// AltScreenExitCount returns the number of times ExitAltScreen was called.
func (m *MockTerminal) AltScreenExitCount() int {
	return m.altScreenExitCount
}

// IsMouseEnabled returns whether mouse event reporting is enabled.
func (m *MockTerminal) IsMouseEnabled() bool {
	return m.mouseEnabled
}

// Reset resets the mock terminal to its initial state.
func (m *MockTerminal) Reset() {
	m.Clear()
	m.cursorHidden = false
	m.inRawMode = false
	m.inAltScreen = false
	m.mouseEnabled = false
	m.altScreenEnterCount = 0
	m.altScreenExitCount = 0
}

// Resize changes the terminal dimensions, preserving content where possible.
func (m *MockTerminal) Resize(width, height int) {
	newSize := width * height
	newCells := make([]Cell, newSize)

	defaultCell := NewCell(' ', NewStyle())
	for i := range newCells {
		newCells[i] = defaultCell
	}

	// Copy existing content
	copyWidth := min(width, m.width)
	copyHeight := min(height, m.height)

	for y := range copyHeight {
		for x := range copyWidth {
			newCells[y*width+x] = m.cells[y*m.width+x]
		}
	}

	m.width = width
	m.height = height
	m.cells = newCells
}

// SetCell directly sets a cell in the buffer (for test setup).
func (m *MockTerminal) SetCell(x, y int, c Cell) {
	if x >= 0 && x < m.width && y >= 0 && y < m.height {
		m.cells[y*m.width+x] = c
	}
}