okami.mom/ultraviolet
git clone https://git.nkp.pet/okami.mom/ultraviolet
6d3a341
upstream
1package uv 2 3import ( 4"fmt" 5"image/color" 6"io" 7 8"github.com/charmbracelet/x/ansi" 9"github.com/lucasb-eyer/go-colorful" 10) 11 12var ( 13// ErrNotTerminal is an error that indicates that the file is not a terminal. 14ErrNotTerminal = fmt .Errorf ("not a terminal" ) 15// ErrPlatformNotSupported is an error that indicates that the platform is not supported. 16ErrPlatformNotSupported = fmt .Errorf ("platform not supported" ) 17) 18 19// Drawable represents a drawable component on a [Screen]. 20type Drawable interface { 21// Draw renders the component on the screen for the given area. 22Draw (scr Screen ,area Rectangle ) 23} 24 25// DrawableFunc is a function that implements the [Drawable] interface. 26type DrawableFunc func (scr Screen ,rect Rectangle ) 27 28var _ Drawable = DrawableFunc (nil ) 29 30// Draw implements the [Drawable] interface. 31func (f DrawableFunc )Draw (scr Screen ,rect Rectangle ) { 32f (scr ,rect ) 33} 34 35// WidthMethod determines how many columns a grapheme occupies on the screen. 36type WidthMethod interface { 37StringWidth (s string )int 38} 39 40// Screen represents a screen that can be drawn to. 41type Screen interface { 42// Bounds returns the bounds of the screen. This is the rectangle that 43// includes the start and end points of the screen. 44Bounds ()Rectangle 45 46// CellAt returns the cell at the given position. If the position is out of 47// bounds, it returns nil. Otherwise, it always returns a cell, even if it 48// is empty (i.e., a cell with a space character and a width of 1). 49CellAt (x ,y int )* Cell 50 51// SetCell sets the cell at the given position. A nil cell is treated as an 52// empty cell with a space character and a width of 1. 53SetCell (x ,y int ,c * Cell ) 54 55// WidthMethod returns the width method used by the screen. 56WidthMethod ()WidthMethod 57} 58 59// Cursor represents a cursor on the terminal screen. 60type Cursor struct { 61// Position is a [Position] that determines the cursor's position on the 62// screen relative to the top left corner of the frame. 63Position 64 65// Color is a [color.Color] that determines the cursor's color. 66Color color.Color 67 68// Shape is a [CursorShape] that determines the cursor's shape. 69Shape CursorShape 70 71// Blink is a boolean that determines whether the cursor should blink. 72Blink bool 73 74// Hidden is a boolean that determines whether the cursor is hidden. You 75// can use this if you want to hide the cursor but still want to change its 76// position. 77Hidden bool 78} 79 80// NewCursor returns a new cursor with the default settings and the given 81// position. 82func NewCursor (x ,y int )* Cursor { 83return & Cursor { 84Position :Position {X :x ,Y :y }, 85Color :nil , 86Shape :CursorBlock , 87Blink :true , 88 } 89} 90 91// ProgressBarState represents the state of the progress bar. 92type ProgressBarState int 93 94// Progress bar states. 95const ( 96ProgressBarNone ProgressBarState = iota 97ProgressBarDefault 98ProgressBarError 99ProgressBarIndeterminate 100ProgressBarWarning 101) 102 103// String return a human-readable value for the given [ProgressBarState]. 104func (s ProgressBarState )String ()string { 105return [... ]string { 106"None" , 107"Default" , 108"Error" , 109"Indeterminate" , 110"Warning" , 111 }[s ] 112} 113 114// ProgressBar represents the terminal progress bar. 115// 116// Support depends on the terminal. 117// 118// See https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences 119type ProgressBar struct { 120// State is the current state of the progress bar. It can be one of 121// [ProgressBarNone], [ProgressBarDefault], [ProgressBarError], 122// [ProgressBarIndeterminate], and [ProgressBarWarning]. 123State ProgressBarState 124// Value is the current value of the progress bar. It should be between 125// 0 and 100. 126Value int 127} 128 129// NewProgressBar returns a new progress bar with the given state and value. 130// The value is ignored if the state is [ProgressBarNone] or 131// [ProgressBarIndeterminate]. 132func NewProgressBar (state ProgressBarState ,value int )* ProgressBar { 133return & ProgressBar { 134State :state , 135Value :min (max (value ,0 ),100 ), 136 } 137} 138 139// KeyboardEnhancements defines different keyboard enhancement features that 140// can be requested from the terminal. 141type KeyboardEnhancements struct { 142// DisambiguateEscapeCodes requests the terminal to report ambiguous keys 143// such as Ctrl+i and Tab, and Ctrl+m and Enter, and others as distinct key 144// code sequences. 145// If supported, your program will receive distinct [KeyPressEvent]s for 146// these keys. 147DisambiguateEscapeCodes bool 148 149// ReportEventTypes requests the terminal to report key repeat and release 150// events. 151// If supported, your program will receive [KeyReleaseEvent]s and 152// [KeyPressEvent] with the [Key.IsRepeat] field set indicating that this 153// is a it's part of a key repeat sequence. 154ReportEventTypes bool 155 156// ReportAlternateKeys requests the terminal to report alternate key values 157// in addition to the main ones. 158// Note that only key events represented as escape codes will affected by 159// this enhancement. 160ReportAlternateKeys bool 161 162// ReportAllKeysAsEscapeCodes requests the terminal to report all key 163// events, including plain text keys, as escape codes. 164// When this is enabled, text won't be sent as plain text but instead as 165// escape codes that encode the key value and modifiers. 166ReportAllKeysAsEscapeCodes bool 167 168// ReportAssociatedText requests the terminal to report the text associated 169// with key events. 170// Note that this is an enhancement to 171// [KeyboardEnhancements.ReportAllKeysAsEscapeCodes] and only has an effect 172// if that is enabled. 173ReportAssociatedText bool 174} 175 176// NewKeyboardEnhancements returns a new [KeyboardEnhancements] with the given 177// options as flags. 178// 179// A zero, or negative, flags value is treated as no enhancements. 180// 181// See [ansi.KittyKeyboard] for more details on the supported keyboard enhancements. 182func NewKeyboardEnhancements (flags int )* KeyboardEnhancements { 183if flags <= 0 { 184return & KeyboardEnhancements {} 185 } 186 187return & KeyboardEnhancements { 188DisambiguateEscapeCodes :flags & ansi .KittyDisambiguateEscapeCodes != 0 , 189ReportEventTypes :flags & ansi .KittyReportEventTypes != 0 , 190 } 191} 192 193// Flags returns the keyboard enhancements as bits that can be used to set the 194// appropriate terminal modes. 195func (ke KeyboardEnhancements )Flags ()int { 196bits := 0 197if ke .DisambiguateEscapeCodes { 198bits |= ansi .KittyDisambiguateEscapeCodes 199 } 200if ke .ReportEventTypes { 201bits |= ansi .KittyReportEventTypes 202 } 203if ke .ReportAlternateKeys { 204bits |= ansi .KittyReportAlternateKeys 205 } 206if ke .ReportAllKeysAsEscapeCodes { 207bits |= ansi .KittyReportAllKeysAsEscapeCodes 208 } 209if ke .ReportAssociatedText { 210bits |= ansi .KittyReportAssociatedKeys 211 } 212 213return bits 214} 215 216// EncodeBackgroundColor encodes the background color to the given writer. Use 217// nil to reset the background color to the default. 218func EncodeBackgroundColor (w io.Writer ,c color.Color )error { 219var seq string 220if c == nil { 221seq = ansi .ResetBackgroundColor 222 }else if col ,ok := colorful .MakeColor (c );ok { 223seq = ansi .SetBackgroundColor (col .Hex ()) 224 }else { 225return fmt .Errorf ("invalid color: %v" ,c ) 226 } 227 228_ ,err := io .WriteString (w ,seq ) 229if err != nil { 230return fmt .Errorf ("failed to set background color: %w" ,err ) 231 } 232 233return nil 234} 235 236// EncodeForegroundColor encodes the foreground color to the given writer. Use 237// nil to reset the foreground color to the default. 238func EncodeForegroundColor (w io.Writer ,c color.Color )error { 239var seq string 240if c == nil { 241seq = ansi .ResetForegroundColor 242 }else if col ,ok := colorful .MakeColor (c );ok { 243seq = ansi .SetForegroundColor (col .Hex ()) 244 }else { 245return fmt .Errorf ("invalid color: %v" ,c ) 246 } 247 248_ ,err := io .WriteString (w ,seq ) 249if err != nil { 250return fmt .Errorf ("failed to set foreground color: %w" ,err ) 251 } 252 253return nil 254} 255 256// EncodeCursorColor encodes the cursor color to the given writer. Use nil to 257// reset the cursor color to the default. 258func EncodeCursorColor (w io.Writer ,c color.Color )error { 259var seq string 260if c == nil { 261seq = ansi .ResetCursorColor 262 }else if col ,ok := colorful .MakeColor (c );ok { 263seq = ansi .SetCursorColor (col .Hex ()) 264 }else { 265return fmt .Errorf ("invalid color: %v" ,c ) 266 } 267 268_ ,err := io .WriteString (w ,seq ) 269if err != nil { 270return fmt .Errorf ("failed to set cursor color: %w" ,err ) 271 } 272 273return nil 274} 275 276// EncodeCursorStyle encodes the cursor style to the given writer. 277func EncodeCursorStyle (w io.Writer ,shape CursorShape ,blink bool )error { 278seq := ansi .SetCursorStyle (shape .Encode (blink )) 279_ ,err := io .WriteString (w ,seq ) 280if err != nil { 281return fmt .Errorf ("failed to set cursor style: %w" ,err ) 282 } 283 284return nil 285} 286 287// EncodeBracketedPaste encodes the bracketed paste mode to the given writer. 288func EncodeBracketedPaste (w io.Writer ,enable bool )error { 289var seq string 290if enable { 291seq = ansi .SetModeBracketedPaste 292 }else { 293seq = ansi .ResetModeBracketedPaste 294 } 295 296_ ,err := io .WriteString (w ,seq ) 297if err != nil { 298return fmt .Errorf ("failed to set bracketed paste mode: %w" ,err ) 299 } 300 301return nil 302} 303 304// EncodeMouseMode encodes the mouse tracking mode to the given writer. 305func EncodeMouseMode (w io.Writer ,mode MouseMode )error { 306var seq string 307switch mode { 308case MouseModeNone : 309seq = ansi .ResetModeMouseX10 + 310ansi .ResetModeMouseNormal + 311ansi .ResetModeMouseButtonEvent + 312ansi .ResetModeMouseAnyEvent 313case MouseModePress : 314seq = ansi .SetModeMouseX10 315case MouseModeClick : 316seq = ansi .SetModeMouseNormal 317case MouseModeDrag : 318seq = ansi .SetModeMouseButtonEvent 319case MouseModeMotion : 320seq = ansi .SetModeMouseAnyEvent 321 } 322 323_ ,err := io .WriteString (w ,seq )//nolint:errcheck 324if err != nil { 325return fmt .Errorf ("failed to set mouse mode: %w" ,err ) 326 } 327 328return nil 329} 330 331// EncodeMouseEncoding encodes the mouse encoding mode to the given writer. 332// When enc is [MouseEncodingLegacy], all extended encodings are reset. 333func EncodeMouseEncoding (w io.Writer ,enc MouseEncoding )error { 334var seq string 335switch enc { 336case MouseEncodingLegacy : 337seq = ansi .ResetModeMouseExtSgr + 338ansi .ResetModeMouseExtUrxvt + 339ansi .ResetModeMouseExtSgrPixel 340case MouseEncodingSGR : 341seq = ansi .SetModeMouseExtSgr 342case MouseEncodingSGRPixel : 343seq = ansi .SetModeMouseExtSgrPixel 344 } 345 346_ ,err := io .WriteString (w ,seq )//nolint:errcheck 347if err != nil { 348return fmt .Errorf ("failed to set mouse encoding: %w" ,err ) 349 } 350 351return nil 352} 353 354// EncodeProgressBar encodes the progress bar to the given writer. 355func EncodeProgressBar (w io.Writer ,pb * ProgressBar )error { 356if pb == nil { 357pb = & ProgressBar {State :ProgressBarNone } 358 } 359 360var seq string 361percent := clamp (pb .Value ,0 ,100 ) 362switch pb .State { 363case ProgressBarNone : 364seq = ansi .ResetProgressBar 365case ProgressBarDefault : 366seq = ansi .SetProgressBar (percent ) 367case ProgressBarError : 368seq = ansi .SetErrorProgressBar (percent ) 369case ProgressBarIndeterminate : 370seq = ansi .SetIndeterminateProgressBar 371case ProgressBarWarning : 372seq = ansi .SetWarningProgressBar (percent ) 373default : 374return fmt .Errorf ("invalid progress bar state: %d" ,pb .State ) 375 } 376 377_ ,err := io .WriteString (w ,seq ) 378if err != nil { 379return fmt .Errorf ("failed to set progress bar: %w" ,err ) 380 } 381 382return nil 383} 384 385// EncodeKeyboardEnhancements encodes the keyboard enhancements to the given 386// writer. 387func EncodeKeyboardEnhancements (w io.Writer ,ke * KeyboardEnhancements )error { 388var flags int 389if ke != nil { 390flags = ke .Flags () 391 } 392_ ,err := io .WriteString (w ,ansi .KittyKeyboard (flags ,1 )) 393if err != nil { 394return fmt .Errorf ("failed to set keyboard enhancements: %w" ,err ) 395 } 396return nil 397} 398 399// EncodeWindowTitle encodes the window title to the given writer. 400func EncodeWindowTitle (w io.Writer ,title string )error { 401_ ,err := io .WriteString (w ,ansi .SetWindowTitle (title )) 402if err != nil { 403return fmt .Errorf ("failed to set window title: %w" ,err ) 404 } 405return nil 406}