gitstack

grindlemire/go-tui code browser

12.8 KB markdown 456 lines 2026-03-23 · a47141f 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
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
# Terminal Reference

## Overview

The `Terminal` interface abstracts all communication with the user's terminal. It handles screen rendering, cursor management, raw mode, alternate screen buffers, and mouse event reporting. go-tui ships with two implementations: `ANSITerminal` for real terminals and `MockTerminal` for testing.

Most applications never interact with `Terminal` directly; the `App` type handles terminal setup and teardown. You'll use these types when you need custom terminal behavior, direct rendering, or test verification.

## Terminal Interface

```go
type Terminal interface {
    Size() (width, height int)
    Flush(changes []CellChange)
    Clear()
    ClearToEnd()
    SetCursor(x, y int)
    HideCursor()
    ShowCursor()
    EnterRawMode() error
    ExitRawMode() error
    EnterAltScreen()
    ExitAltScreen()
    EnableMouse()
    DisableMouse()
    Caps() Capabilities
    WriteDirect([]byte) (int, error)
}
```

### Size

```go
Size() (width, height int)
```

Returns the terminal dimensions in cells (columns and rows).

### Flush

```go
Flush(changes []CellChange)
```

Writes a batch of cell changes to the terminal. Changes should be in row-major order for best performance. The implementation optimizes cursor movement and only emits style escape codes when the style differs from the previous cell.

### Clear

```go
Clear()
```

Clears the entire terminal screen, including scrollback on real terminals.

### ClearToEnd

```go
ClearToEnd()
```

Clears from the current cursor position to the end of the screen.

### SetCursor

```go
SetCursor(x, y int)
```

Moves the cursor to the given position. Coordinates are 0-indexed, with (0, 0) at the top-left corner.

### HideCursor / ShowCursor

```go
HideCursor()
ShowCursor()
```

Controls cursor visibility. `HideCursor` is typically called during rendering to prevent flicker, and `ShowCursor` restores visibility afterward.

### EnterRawMode / ExitRawMode

```go
EnterRawMode() error
ExitRawMode() error
```

Raw mode disables terminal line buffering so the application can read individual keystrokes. `EnterRawMode` saves the current terminal state; `ExitRawMode` restores it. Both return an error if the terminal state cannot be changed.

### EnterAltScreen / ExitAltScreen

```go
EnterAltScreen()
ExitAltScreen()
```

The alternate screen buffer is a separate display area that preserves the user's original terminal content. Full-screen applications use it so that quitting restores the previous scrollback. Inline-mode applications skip it.

### EnableMouse / DisableMouse

```go
EnableMouse()
DisableMouse()
```

Enables or disables SGR mouse event reporting. When enabled, mouse clicks, drags, and wheel scrolls generate `MouseEvent` values through the `EventReader`. Always call `DisableMouse` before exiting to restore normal terminal behavior.

### Caps

```go
Caps() Capabilities
```

Returns the terminal's detected capabilities: color support level, Unicode, true color, and alternate screen availability. See [Capabilities](#capabilities) below.

### WriteDirect

```go
WriteDirect([]byte) (int, error)
```

Writes raw bytes to the terminal output, bypassing the cell-based rendering pipeline. Use this for escape sequences or raw content that the higher-level API does not cover.

## ANSITerminal

`ANSITerminal` is the production implementation of `Terminal`. It communicates with real terminal emulators through ANSI escape sequences.

### Creating an ANSITerminal

#### NewANSITerminal

```go
func NewANSITerminal(out io.Writer, in io.Reader) (*ANSITerminal, error)
```

Creates a terminal with auto-detected capabilities. The `out` parameter is typically `os.Stdout` and `in` is `os.Stdin`. File descriptors are extracted from the writers if they implement `*os.File`, which enables raw mode and terminal size queries.

```go
import (
    "os"
    tui "github.com/grindlemire/go-tui"
)

term, err := tui.NewANSITerminal(os.Stdout, os.Stdin)
if err != nil {
    // handle error
}
```

#### NewANSITerminalWithCaps

```go
func NewANSITerminalWithCaps(out io.Writer, in io.Reader, caps Capabilities) *ANSITerminal
```

Creates a terminal with explicit capabilities instead of auto-detecting them. Use this when you know the target environment or want to force specific behavior.

```go
caps := tui.Capabilities{
    Colors:    tui.ColorTrue,
    Unicode:   true,
    TrueColor: true,
    AltScreen: true,
}
term := tui.NewANSITerminalWithCaps(os.Stdout, os.Stdin, caps)
```

### Additional Methods

`ANSITerminal` provides methods beyond the `Terminal` interface:

#### SetCaps

```go
func (t *ANSITerminal) SetCaps(caps Capabilities)
```

Updates the terminal's capabilities after creation. You might call this if you detect capabilities at runtime through a terminal query/response sequence.

#### ResetStyle

```go
func (t *ANSITerminal) ResetStyle()
```

Resets internal style tracking, forcing the next `Flush` call to emit style escape codes regardless of whether the style changed. Call this after writing directly to the terminal output to re-sync the style state.

#### Writer

```go
func (t *ANSITerminal) Writer() io.Writer
```

Returns the underlying output writer. Be careful: writes through this bypass the terminal's style optimization and cursor tracking.

#### BeginSyncUpdate / EndSyncUpdate

```go
func (t *ANSITerminal) BeginSyncUpdate()
func (t *ANSITerminal) EndSyncUpdate()
```

Brackets a synchronized update. Terminal emulators that support this protocol buffer output between `Begin` and `End`, then display everything at once. This prevents tearing during complex frame updates. Terminals that don't recognize the sequence just ignore it.

### Size Defaults

`ANSITerminal.Size()` returns 80x24 if the terminal dimensions cannot be determined (e.g., when stdout is not a TTY).

## BufferedWriter

A write buffer that batches output before sending it to an underlying writer.

```go
type BufferedWriter struct {
    // contains unexported fields
}
```

### NewBufferedWriter

```go
func NewBufferedWriter(out io.Writer) *BufferedWriter
```

Creates a `BufferedWriter` wrapping the given writer.

### Write

```go
func (w *BufferedWriter) Write(p []byte) (int, error)
```

Appends bytes to the internal buffer. Does not write to the underlying writer.

### Flush

```go
func (w *BufferedWriter) Flush() error
```

Writes the buffered content to the underlying writer and clears the buffer.

```go
bw := tui.NewBufferedWriter(os.Stdout)
bw.Write([]byte("hello "))
bw.Write([]byte("world"))
bw.Flush() // writes "hello world" to stdout in one call
```

## EventReader

The `EventReader` interface reads terminal input events for the application's event loop.

```go
type EventReader interface {
    PollEvent(timeout time.Duration) (Event, bool)
    Close() error
}
```

### PollEvent

```go
PollEvent(timeout time.Duration) (Event, bool)
```

Reads the next input event within the given timeout. Returns `(event, true)` when an event is available, or `(nil, false)` on timeout. Timeout behavior:

- **Positive duration**: waits up to that long for an event
- **Zero**: non-blocking check, returns immediately
- **Negative**: blocks indefinitely until an event arrives

Resize events are handled at the App level, not by the reader. The App catches SIGWINCH signals and dispatches `ResizeEvent` through the event queue. The reader only handles keyboard and mouse input.

### Close

```go
Close() error
```

Releases resources held by the reader (signal handlers, file descriptors). Must be called when the reader is no longer needed.

### NewEventReader

```go
func NewEventReader(in *os.File) (EventReader, error)
```

Creates an `EventReader` for the given input file (typically `os.Stdin`). The terminal should already be in raw mode before creating the reader. The reader handles keyboard and mouse input only; resize events (SIGWINCH) are handled at the App level.

```go
import (
    "os"
    tui "github.com/grindlemire/go-tui"
)

reader, err := tui.NewEventReader(os.Stdin)
if err != nil {
    // handle error
}
defer reader.Close()

event, ok := reader.PollEvent(100 * time.Millisecond)
if ok {
    // process event
}
```

## InterruptibleReader

Extends `EventReader` with the ability to wake up a blocking `PollEvent` call from another goroutine.

```go
type InterruptibleReader interface {
    EventReader

    EnableInterrupt() error
    Interrupt() error
}
```

### EnableInterrupt

```go
EnableInterrupt() error
```

Sets up the interrupt mechanism (a self-pipe on Unix). The framework calls this automatically during App initialization. Calling it multiple times is safe; subsequent calls are no-ops.

### Interrupt

```go
Interrupt() error
```

Wakes up a blocking `PollEvent` call. The blocked call returns `(nil, false)`. Safe to call even when `PollEvent` is not currently blocking.

The `App` uses `InterruptibleReader` internally to wake the event loop when state changes are queued via `QueueUpdate`.

## Capabilities

`Capabilities` describes what features the terminal supports.

```go
type Capabilities struct {
    Colors    ColorCapability
    Unicode   bool
    TrueColor bool
    AltScreen bool
}
```

| Field       | Type              | Description                                          |
|-------------|-------------------|------------------------------------------------------|
| `Colors`    | `ColorCapability` | Level of color support (none, 16, 256, or true color) |
| `Unicode`   | `bool`            | Whether the terminal renders Unicode characters       |
| `TrueColor` | `bool`            | Whether 24-bit RGB colors are supported               |
| `AltScreen` | `bool`            | Whether the alternate screen buffer is available      |

### DetectCapabilities

```go
func DetectCapabilities() Capabilities
```

Determines terminal capabilities from environment variables. Detection checks, in order:

1. `COLORTERM` — `truecolor` or `24bit` indicates true color support
2. Terminal-specific environment variables — `WT_SESSION` (Windows Terminal), `ITERM_SESSION_ID` (iTerm2), `KITTY_WINDOW_ID` (Kitty), `KONSOLE_VERSION` (Konsole), `VTE_VERSION` (GNOME Terminal, Tilix)
3. `TERM` — `dumb` disables all features; `256color` suffix enables 256-color; `truecolor` enables true color

Default capabilities when detection finds nothing specific: 16-color, Unicode enabled, no true color, alternate screen available.

```go
caps := tui.DetectCapabilities()
fmt.Println(caps) // e.g., "true-color, unicode, altscreen"
```

### ColorCapability Constants

```go
const (
    ColorNone ColorCapability = iota  // Monochrome, no color
    Color16                           // Standard 16 ANSI colors
    Color256                          // 256-color palette
    ColorTrue                         // 24-bit RGB true color
)
```

### SupportsColor

```go
func (c Capabilities) SupportsColor(color Color) bool
```

Reports whether the terminal can display the given color. ANSI colors require at least `Color16` support; RGB colors require `TrueColor`.

```go
caps := tui.DetectCapabilities()
color := tui.RGBColor(255, 128, 0) // orange

if caps.SupportsColor(color) {
    // use the color directly
} else {
    // fall back to an ANSI approximation
}
```

### EffectiveColor

```go
func (c Capabilities) EffectiveColor(color Color) Color
```

Returns the best color the terminal can actually display. If the terminal supports the color type, returns the original. Otherwise, RGB colors are approximated to the nearest ANSI color, and ANSI colors on colorless terminals fall back to `DefaultColor()`.

```go
caps := tui.DetectCapabilities()
requested := tui.RGBColor(0, 200, 100)
actual := caps.EffectiveColor(requested) // RGB on true-color, ANSI approximation otherwise
```

### String

```go
func (c Capabilities) String() string
```

Returns a human-readable summary like `"true-color, unicode, altscreen"` or `"16-color, ascii"`.

## Render Functions

Package-level functions that `App` uses internally for rendering. They're also available if you're building a custom render loop.

### Render

```go
func Render(term Terminal, buf *Buffer)
```

Diff-based render. Computes the cells that changed since the last `Swap()` and calls `term.Flush()` with only those changes. This is the standard render path and minimizes bytes sent to the terminal.

### RenderFull

```go
func RenderFull(term Terminal, buf *Buffer)
```

Full redraw. Sends every non-empty cell to the terminal regardless of whether it changed. Use this after operations that may have corrupted the terminal state (e.g., a child process writing to stdout).

## Cross-References

- [Testing Reference](testing.md) — `MockTerminal` and `MockEventReader` test utilities
- [Buffer Reference](buffer.md) — `Buffer`, `Cell`, and `CellChange` types used by `Flush`
- [Events Reference](events.md) — `Event`, `KeyEvent`, `MouseEvent` types returned by `PollEvent`
- [Styling Reference](styling.md) — `Style`, `Color`, and `Capabilities` color handling
- [App Reference](app.md) — `App` manages `Terminal` and `EventReader` lifecycle