Repository navigation
Expand file tree
/
Copy pathuv.go
More file actions
452 lines (386 loc) Β· 12.8 KB
/
Copy pathuv.go
File metadata and controls
452 lines (386 loc) Β· 12.8 KB
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
package uv
import (
"fmt"
"image/color"
"io"
"github.com/charmbracelet/x/ansi"
"github.com/lucasb-eyer/go-colorful"
)
var (
// ErrNotTerminal is an error that indicates that the file is not a terminal.
ErrNotTerminal = fmt.Errorf("not a terminal")
// ErrPlatformNotSupported is an error that indicates that the platform is not supported.
ErrPlatformNotSupported = fmt.Errorf("platform not supported")
)
// Drawable represents a drawable component on a [Screen].
type Drawable interface {
// Draw renders the component on the screen for the given area.
Draw(scr Screen, area Rectangle)
}
// DrawableFunc is a function that implements the [Drawable] interface.
type DrawableFunc func(scr Screen, rect Rectangle)
var _ Drawable = DrawableFunc(nil)
// Draw implements the [Drawable] interface.
func (f DrawableFunc) Draw(scr Screen, rect Rectangle) {
f(scr, rect)
}
// WidthMethod determines how many columns a grapheme occupies on the screen.
type WidthMethod interface {
StringWidth(s string) int
}
// Screen represents a screen that can be drawn to.
type Screen interface {
// Bounds returns the bounds of the screen. This is the rectangle that
// includes the start and end points of the screen.
Bounds() Rectangle
// CellAt returns the cell at the given position. If the position is out of
// bounds, it returns nil. Otherwise, it always returns a cell, even if it
// is empty (i.e., a cell with a space character and a width of 1).
CellAt(x, y int) *Cell
// SetCell sets the cell at the given position. A nil cell is treated as an
// empty cell with a space character and a width of 1.
SetCell(x, y int, c *Cell)
// WidthMethod returns the width method used by the screen.
WidthMethod() WidthMethod
}
// Cursor represents a cursor on the terminal screen.
type Cursor struct {
// Position is a [Position] that determines the cursor's position on the
// screen relative to the top left corner of the frame.
Position
// Color is a [color.Color] that determines the cursor's color.
Color color.Color
// Shape is a [CursorShape] that determines the cursor's shape.
Shape CursorShape
// Blink is a boolean that determines whether the cursor should blink.
Blink bool
// Hidden is a boolean that determines whether the cursor is hidden. You
// can use this if you want to hide the cursor but still want to change its
// position.
Hidden bool
}
// NewCursor returns a new cursor with the default settings and the given
// position.
func NewCursor(x, y int) *Cursor {
return &Cursor{
Position: Position{X: x, Y: y},
Color: nil,
Shape: CursorBlock,
Blink: true,
}
}
// ProgressBarState represents the state of the progress bar.
type ProgressBarState int
// Progress bar states.
const (
ProgressBarNone ProgressBarState = iota
ProgressBarDefault
ProgressBarError
ProgressBarIndeterminate
ProgressBarWarning
)
// String return a human-readable value for the given [ProgressBarState].
func (s ProgressBarState) String() string {
return [...]string{
"None",
"Default",
"Error",
"Indeterminate",
"Warning",
}[s]
}
// ProgressBar represents the terminal progress bar.
//
// Support depends on the terminal.
//
// See https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences
type ProgressBar struct {
// State is the current state of the progress bar. It can be one of
// [ProgressBarNone], [ProgressBarDefault], [ProgressBarError],
// [ProgressBarIndeterminate], and [ProgressBarWarning].
State ProgressBarState
// Value is the current value of the progress bar. It should be between
// 0 and 100.
Value int
}
// NewProgressBar returns a new progress bar with the given state and value.
// The value is ignored if the state is [ProgressBarNone] or
// [ProgressBarIndeterminate].
func NewProgressBar(state ProgressBarState, value int) *ProgressBar {
return &ProgressBar{
State: state,
Value: min(max(value, 0), 100),
}
}
// KeyboardEnhancements defines different keyboard enhancement features that
// can be requested from the terminal.
type KeyboardEnhancements struct {
// DisambiguateEscapeCodes requests the terminal to report ambiguous keys
// such as Ctrl+i and Tab, and Ctrl+m and Enter, and others as distinct key
// code sequences.
// If supported, your program will receive distinct [KeyPressEvent]s for
// these keys.
DisambiguateEscapeCodes bool
// ReportEventTypes requests the terminal to report key repeat and release
// events.
// If supported, your program will receive [KeyReleaseEvent]s and
// [KeyPressEvent] with the [Key.IsRepeat] field set indicating that this
// is a it's part of a key repeat sequence.
ReportEventTypes bool
// ReportAlternateKeys requests the terminal to report alternate key values
// in addition to the main ones.
// Note that only key events represented as escape codes will affected by
// this enhancement.
ReportAlternateKeys bool
// ReportAllKeysAsEscapeCodes requests the terminal to report all key
// events, including plain text keys, as escape codes.
// When this is enabled, text won't be sent as plain text but instead as
// escape codes that encode the key value and modifiers.
ReportAllKeysAsEscapeCodes bool
// ReportAssociatedText requests the terminal to report the text associated
// with key events.
// Note that this is an enhancement to
// [KeyboardEnhancements.ReportAllKeysAsEscapeCodes] and only has an effect
// if that is enabled.
ReportAssociatedText bool
}
// NewKeyboardEnhancements returns a new [KeyboardEnhancements] with the given
// options as flags.
//
// A zero, or negative, flags value is treated as no enhancements.
//
// See [ansi.KittyKeyboard] for more details on the supported keyboard enhancements.
func NewKeyboardEnhancements(flags int) *KeyboardEnhancements {
if flags <= 0 {
return &KeyboardEnhancements{}
}
return &KeyboardEnhancements{
DisambiguateEscapeCodes: flags&ansi.KittyDisambiguateEscapeCodes != 0,
ReportEventTypes: flags&ansi.KittyReportEventTypes != 0,
}
}
// Flags returns the keyboard enhancements as bits that can be used to set the
// appropriate terminal modes.
func (ke KeyboardEnhancements) Flags() int {
bits := 0
if ke.DisambiguateEscapeCodes {
bits |= ansi.KittyDisambiguateEscapeCodes
}
if ke.ReportEventTypes {
bits |= ansi.KittyReportEventTypes
}
if ke.ReportAlternateKeys {
bits |= ansi.KittyReportAlternateKeys
}
if ke.ReportAllKeysAsEscapeCodes {
bits |= ansi.KittyReportAllKeysAsEscapeCodes
}
if ke.ReportAssociatedText {
bits |= ansi.KittyReportAssociatedKeys
}
return bits
}
// EncodeBackgroundColor encodes the background color to the given writer. Use
// nil to reset the background color to the default.
func EncodeBackgroundColor(w io.Writer, c color.Color) error {
var seq string
if c == nil {
seq = ansi.ResetBackgroundColor
} else if col, ok := colorful.MakeColor(c); ok {
seq = ansi.SetBackgroundColor(col.Hex())
} else {
return fmt.Errorf("invalid color: %v", c)
}
_, err := io.WriteString(w, seq)
if err != nil {
return fmt.Errorf("failed to set background color: %w", err)
}
return nil
}
// EncodeForegroundColor encodes the foreground color to the given writer. Use
// nil to reset the foreground color to the default.
func EncodeForegroundColor(w io.Writer, c color.Color) error {
var seq string
if c == nil {
seq = ansi.ResetForegroundColor
} else if col, ok := colorful.MakeColor(c); ok {
seq = ansi.SetForegroundColor(col.Hex())
} else {
return fmt.Errorf("invalid color: %v", c)
}
_, err := io.WriteString(w, seq)
if err != nil {
return fmt.Errorf("failed to set foreground color: %w", err)
}
return nil
}
// EncodeCursorColor encodes the cursor color to the given writer. Use nil to
// reset the cursor color to the default.
func EncodeCursorColor(w io.Writer, c color.Color) error {
var seq string
if c == nil {
seq = ansi.ResetCursorColor
} else if col, ok := colorful.MakeColor(c); ok {
seq = ansi.SetCursorColor(col.Hex())
} else {
return fmt.Errorf("invalid color: %v", c)
}
_, err := io.WriteString(w, seq)
if err != nil {
return fmt.Errorf("failed to set cursor color: %w", err)
}
return nil
}
// EncodeCursorStyle encodes the cursor style to the given writer.
func EncodeCursorStyle(w io.Writer, shape CursorShape, blink bool) error {
seq := ansi.SetCursorStyle(shape.Encode(blink))
_, err := io.WriteString(w, seq)
if err != nil {
return fmt.Errorf("failed to set cursor style: %w", err)
}
return nil
}
// EncodeBracketedPaste encodes the bracketed paste mode to the given writer.
func EncodeBracketedPaste(w io.Writer, enable bool) error {
var seq string
if enable {
seq = ansi.SetModeBracketedPaste
} else {
seq = ansi.ResetModeBracketedPaste
}
_, err := io.WriteString(w, seq)
if err != nil {
return fmt.Errorf("failed to set bracketed paste mode: %w", err)
}
return nil
}
// EncodeMouseMode encodes the mouse tracking mode to the given writer.
func EncodeMouseMode(w io.Writer, mode MouseMode) error {
var seq string
switch mode {
case MouseModeNone:
seq = ansi.ResetModeMouseX10 +
ansi.ResetModeMouseNormal +
ansi.ResetModeMouseButtonEvent +
ansi.ResetModeMouseAnyEvent
case MouseModePress:
seq = ansi.SetModeMouseX10
case MouseModeClick:
seq = ansi.SetModeMouseNormal
case MouseModeDrag:
seq = ansi.SetModeMouseButtonEvent
case MouseModeMotion:
seq = ansi.SetModeMouseAnyEvent
}
_, err := io.WriteString(w, seq) //nolint:errcheck
if err != nil {
return fmt.Errorf("failed to set mouse mode: %w", err)
}
return nil
}
// EncodeMouseEncoding encodes the mouse encoding mode to the given writer.
// When enc is [MouseEncodingLegacy], all extended encodings are reset.
func EncodeMouseEncoding(w io.Writer, enc MouseEncoding) error {
var seq string
switch enc {
case MouseEncodingLegacy:
seq = ansi.ResetModeMouseExtSgr +
ansi.ResetModeMouseExtUrxvt +
ansi.ResetModeMouseExtSgrPixel
case MouseEncodingSGR:
seq = ansi.SetModeMouseExtSgr
case MouseEncodingSGRPixel:
seq = ansi.SetModeMouseExtSgrPixel
}
_, err := io.WriteString(w, seq) //nolint:errcheck
if err != nil {
return fmt.Errorf("failed to set mouse encoding: %w", err)
}
return nil
}
// EncodeProgressBar encodes the progress bar to the given writer.
func EncodeProgressBar(w io.Writer, pb *ProgressBar) error {
if pb == nil {
pb = &ProgressBar{State: ProgressBarNone}
}
var seq string
percent := clamp(pb.Value, 0, 100)
switch pb.State {
case ProgressBarNone:
seq = ansi.ResetProgressBar
case ProgressBarDefault:
seq = ansi.SetProgressBar(percent)
case ProgressBarError:
seq = ansi.SetErrorProgressBar(percent)
case ProgressBarIndeterminate:
seq = ansi.SetIndeterminateProgressBar
case ProgressBarWarning:
seq = ansi.SetWarningProgressBar(percent)
default:
return fmt.Errorf("invalid progress bar state: %d", pb.State)
}
_, err := io.WriteString(w, seq)
if err != nil {
return fmt.Errorf("failed to set progress bar: %w", err)
}
return nil
}
// ProgramStatus is a Program Status Protocol (OSC 7501) report.
//
// See: https://www.superlogical.com/rex/docs/build/program-status
type ProgramStatus = ansi.ProgramStatus
// ProgramState is the state of a [ProgramStatus].
type ProgramState = ansi.ProgramState
// ProgramStatusKind says what a blocked program waits for.
type ProgramStatusKind = ansi.ProgramStatusKind
// Program states.
const (
ProgramStateIdle = ansi.ProgramStateIdle
ProgramStateWorking = ansi.ProgramStateWorking
ProgramStateDone = ansi.ProgramStateDone
ProgramStateBlocked = ansi.ProgramStateBlocked
ProgramStateError = ansi.ProgramStateError
ProgramStateClear = ansi.ProgramStateClear
)
// Program status kinds.
const (
ProgramStatusKindPermission = ansi.ProgramStatusKindPermission
ProgramStatusKindQuestion = ansi.ProgramStatusKindQuestion
ProgramStatusKindAuth = ansi.ProgramStatusKindAuth
)
// EncodeProgramStatus encodes the program status to the given writer. A nil
// status removes every program status record on the terminal.
func EncodeProgramStatus(w io.Writer, ps *ProgramStatus) error {
seq := ansi.ClearProgramStatus
if ps != nil {
seq = ansi.SetProgramStatus(*ps)
if seq == "" {
return fmt.Errorf("invalid program status: state=%q id=%q", ps.State, ps.ID)
}
}
if _, err := io.WriteString(w, seq); err != nil {
return fmt.Errorf("failed to set program status: %w", err)
}
return nil
}
// EncodeKeyboardEnhancements encodes the keyboard enhancements to the given
// writer.
func EncodeKeyboardEnhancements(w io.Writer, ke *KeyboardEnhancements) error {
var flags int
if ke != nil {
flags = ke.Flags()
}
_, err := io.WriteString(w, ansi.KittyKeyboard(flags, 1))
if err != nil {
return fmt.Errorf("failed to set keyboard enhancements: %w", err)
}
return nil
}
// EncodeWindowTitle encodes the window title to the given writer.
func EncodeWindowTitle(w io.Writer, title string) error {
_, err := io.WriteString(w, ansi.SetWindowTitle(title))
if err != nil {
return fmt.Errorf("failed to set window title: %w", err)
}
return nil
}