okami.mom/ultraviolet
git clone https://git.nkp.pet/okami.mom/ultraviolet
0b88c25
upstream
1package uv 2 3import ( 4"bytes" 5"context" 6"errors" 7"fmt" 8"io" 9"strconv" 10"strings" 11"sync" 12"time" 13"unicode" 14"unicode/utf16" 15"unicode/utf8" 16 17"github.com/charmbracelet/x/ansi" 18"github.com/muesli/cancelreader" 19"github.com/rivo/uniseg" 20) 21 22// ErrReaderNotStarted is returned when the reader has not been started yet. 23var ErrReaderNotStarted = fmt .Errorf ("reader not started" ) 24 25// DefaultEscTimeout is the default timeout at which the [TerminalReader] will 26// process ESC sequences. It is set to 50 milliseconds. 27const DefaultEscTimeout = 50 * time .Millisecond 28 29// TerminalReader represents an input event loop that reads input events from 30// a reader and parses them into human-readable events. It supports 31// reading escape sequences, mouse events, and bracketed paste mode. 32type TerminalReader struct { 33EventDecoder 34 35// MouseMode determines whether mouse events are enabled or not. This is a 36// platform-specific feature and is only available on Windows. When this is 37// true, the reader will be initialized to read mouse events using the 38// Windows Console API. 39MouseMode * MouseMode 40 41// EscTimeout is the escape character timeout duration. Most escape 42// sequences start with an escape character [ansi.ESC] and are followed by 43// one or more characters. If the next character is not received within 44// this timeout, the reader will assume that the escape sequence is 45// complete and will process the received characters as a complete escape 46// sequence. 47// 48// By default, this is set to [DefaultEscTimeout] (50 milliseconds). 49EscTimeout time.Duration 50 51r io.Reader 52table map [string ]Key // table is a lookup table for key sequences. 53 54term string // term is the terminal name $TERM. 55 56// paste is the bracketed paste mode buffer. 57// When nil, bracketed paste mode is disabled. 58paste []byte 59 60lookup bool // lookup indicates whether to use the lookup table for key sequences. 61 62// vtInput indicates whether we're using Windows Console API VT input mode. 63//nolint:unused,nolintlint 64vtInput bool 65 66eventScanner * eventScanner 67 68// We use these buffers to decode UTF-16 sequences and graphemes from the 69// Windows Console API and Win32-Input-Mode events. 70utf16Half [2 ]bool // 0 key up, 1 key down 71utf16Buf [2 ][2 ]rune // 0 key up, 1 key down 72graphemeBuf [2 ][]rune // 0 key up, 1 key down 73//nolint:unused,nolintlint 74lastMouseBtns uint32 // the last mouse button state for the previous event 75//nolint:unused,nolintlint 76lastWinsizeX ,lastWinsizeY int16 // the last window size for the previous event to prevent multiple size events from firing 77 78logger Logger // The logger to use for debugging. 79} 80 81// NewTerminalReader returns a new input event reader. The reader streams input 82// events from the terminal and parses escape sequences into human-readable 83// events. It supports reading Terminfo databases. 84// 85// Use [TerminalReader.UseTerminfo] to use Terminfo defined key sequences. 86// Use [TerminalReader.Legacy] to control legacy key encoding behavior. 87// 88// Example: 89// 90// ```go 91// var cr cancelreader.CancelReader 92// var evc chan Event 93// sc := NewTerminalReader(cr, os.Getenv("TERM")) 94// go sc.StreamEvents(ctx, evc) 95// ``` 96func NewTerminalReader (r io.Reader ,termType string )* TerminalReader { 97d := & TerminalReader { 98EscTimeout :DefaultEscTimeout , 99r :r , 100term :termType , 101lookup :true ,// Use lookup table by default. 102 } 103d .r = r 104if d .table == nil { 105d .table = buildKeysTable (d .Legacy ,d .term ,d .UseTerminfo ) 106 } 107evs := newEventScanner () 108evs .EventDecoder = d .EventDecoder 109evs .table = d .table 110evs .lookup = d .lookup 111evs .setLogger (d .logger ) 112d .eventScanner = evs 113return d 114} 115 116// readBufSize is the size of the read buffer used to read input events at a time. 117const readBufSize = 4096 118 119// sendBytes reads data from the reader and sends it to the provided channel. 120// It stops when an error occurs or when the context is closed. 121func (d * TerminalReader )sendBytes (ctx context.Context ,readc chan []byte )error { 122for { 123var readBuf [readBufSize ]byte 124n ,err := d .r .Read (readBuf [:]) 125if err != nil { 126return err //nolint:wrapcheck 127 } 128 129select { 130case <- ctx .Done (): 131return nil 132case readc <- readBuf [:n ]: 133 } 134 } 135} 136 137// StreamEvents sends events to the provided channel. It stops when the context 138// is closed or when an error occurs. 139func (d * TerminalReader )StreamEvents (ctx context.Context ,eventc chan <- Event )error { 140var buf bytes.Buffer 141errc := make (chan error ,1 ) 142readc := make (chan []byte ) 143timeout := time .NewTimer (d .EscTimeout ) 144ttimeout := time .Now ().Add (d .EscTimeout ) 145 146var wg sync.WaitGroup 147wg .Add (1 ) 148 149go func () { 150defer wg .Done () 151if err := d .streamData (ctx ,readc );err != nil { 152if errors .Is (err ,io .EOF )|| errors .Is (err ,cancelreader .ErrCanceled ) { 153errc <- nil 154return 155 } 156errc <- err 157return 158 } 159 }() 160 161for { 162select { 163case <- ctx .Done (): 164d .sendEvents (eventc ,buf .Bytes (),true ) 165wg .Wait () 166return nil 167case err := <- errc : 168d .sendEvents (eventc ,buf .Bytes (),true ) 169wg .Wait () 170return err // return the first error encountered 171case <- timeout .C : 172d .logf ("timeout reached" ) 173 174// Timeout reached process the buffer including any incomplete sequences. 175var n int // n is the number of bytes processed 176timedout := time .Now ().After (ttimeout ) 177if buf .Len ()> 0 && timedout { 178d .logf ("timeout expired, processing buffer" ) 179n = d .sendEvents (eventc ,buf .Bytes (),true ) 180 } 181 182if n > 0 { 183buf .Next (n ) 184 } 185 186if buf .Len ()> 0 { 187if ! timeout .Stop () { 188// drain the channel if it was already running 189select { 190case <- timeout .C : 191default : 192 } 193 } 194 195d .logf ("resetting timeout for remaining buffer" ) 196timeout .Reset (d .EscTimeout ) 197 } 198 199case read := <- readc : 200d .logf ("input: %q" ,read ) 201buf .Write (read ) 202ttimeout = time .Now ().Add (d .EscTimeout ) 203n := d .sendEvents (eventc ,buf .Bytes (),false ) 204if ! timeout .Stop () { 205// drain the channel if it was already running 206select { 207case <- timeout .C : 208default : 209 } 210 } 211 212if n > 0 { 213d .logf ("processed %d bytes from buffer" ,n ) 214buf .Next (n ) 215 } 216 217if buf .Len ()> 0 { 218d .logf ("resetting timeout for remaining buffer after parse" ) 219timeout .Reset (d .EscTimeout ) 220 } 221 } 222 } 223} 224 225// SetLogger sets the logger to use for debugging. If nil, no logging will be 226// performed. 227func (d * TerminalReader )SetLogger (logger Logger ) { 228d .logger = logger 229} 230 231func (d * TerminalReader )sendEvents (eventc chan <- Event ,buf []byte ,expired bool )int { 232n ,events := d .eventScanner .scanEvents (buf ,expired ) 233for _ ,event := range events { 234eventc <- event 235 } 236return n 237} 238 239// eventScanner scans the buffer for events and sends them to the event channel. 240type eventScanner struct { 241EventDecoder 242 243utf16Half [2 ]bool // 0 key up, 1 key down 244utf16Buf [2 ][2 ]rune // 0 key up, 1 key down 245graphemeBuf [2 ][]rune // 0 key up, 1 key down 246paste []byte 247table map [string ]Key 248lookup bool 249logger Logger 250} 251 252// newEventScanner creates a new event scanner. 253func newEventScanner ()* eventScanner { 254return & eventScanner {} 255} 256 257// setLogger sets the logger to use for debugging. If nil, no logging will be 258// performed. 259func (d * eventScanner )setLogger (logger Logger ) { 260d .logger = logger 261} 262 263func (d * eventScanner )logf (format string ,v ... interface {}) { 264logf (d .logger ,format ,v ... ) 265} 266 267func (d * eventScanner )scanEvents (buf []byte ,expired bool ) (total int ,events []Event ) { 268if len (buf )== 0 { 269return 0 ,nil 270 } 271 272var dn int 273d .logf ("processing buf %d %q" ,len (buf ),buf ) 274dn ,buf = d .deserializeWin32Input (buf ) 275total += dn 276 277// Lookup table first 278if d .lookup && len (buf )> 2 && buf [0 ]== ansi .ESC { 279if k ,ok := d .table [string (buf )];ok { 280return len (buf ), []Event {KeyPressEvent (k )} 281 } 282 } 283 284// total is the total number of bytes processed 285for len (buf )> 0 { 286esc := buf [0 ]== ansi .ESC 287n ,event := d .Decode (buf ) 288 289// Handle bracketed-paste 290if d .paste != nil {//nolint:nestif 291if _ ,ok := event .(PasteEndEvent );! ok { 292switch event := event .(type ) { 293case KeyPressEvent : 294if len (event .Text )> 0 { 295d .paste = append (d .paste ,event .Text ... ) 296 }else { 297seq := string (buf [:n ]) 298isWin32 := strings .HasPrefix (seq ,"\x1b[" )&& strings .HasSuffix (seq ,"_" ) 299switch { 300case isWin32 && event .Code == KeyEnter && event .Code == event .BaseCode : 301// This handles special cases where 302// win32-input-mode encodes newlines and other keys 303// as keypress events. We need to encode them as 304// their respective values. 305d .paste = append (d .paste ,'\n' ) 306case isWin32 && unicode .IsControl (event .Code )&& event .Code == event .BaseCode : 307// This handles other cases such as tabs, escapes, etc. 308d .paste = append (d .paste ,string (event .Code )... ) 309case ! isWin32 : 310// We ignore all other non-text win32-input-mode events. 311if esc && n <= 2 && ! expired { 312// If the event is an escape sequence and we 313// are not expired, we need to wait for more 314// input. 315return total ,events 316 } 317d .paste = append (d .paste ,seq ... ) 318 } 319 } 320case UnknownEvent : 321if ! expired { 322// If the event is unknown and we are not expired, we 323// return need to try to decode the buffer again. 324return total ,events 325 } 326default : 327// Everything else is ignored... 328 } 329buf = buf [n :] 330total += n 331continue 332 } 333 } 334 335var isUnknown bool 336switch event .(type ) { 337case ignoredEvent : 338// ignore this event 339event = nil 340case UnknownEvent : 341isUnknown = true 342// Try to look up the event in the table. 343if ! expired { 344return total ,events 345 } 346 347if k ,ok := d .table [string (buf [:n ])];ok { 348events = append (events ,KeyPressEvent (k )) 349return total + n ,events 350 } 351 352events = append (events ,event ) 353case PasteStartEvent : 354d .paste = []byte {}// reset the paste buffer 355case PasteEndEvent : 356var paste []rune 357for len (d .paste )> 0 { 358r ,w := utf8 .DecodeRune (d .paste ) 359if r != utf8 .RuneError { 360paste = append (paste ,r ) 361 } 362d .paste = d .paste [w :] 363 } 364d .paste = nil // reset the paste buffer 365events = append (events ,PasteEvent {string (paste )}) 366 } 367 368if ! isUnknown && event != nil { 369if esc && n <= 2 && ! expired { 370// Wait for more input 371return total ,events 372 } 373 374if m ,ok := event .(MultiEvent );ok { 375// If the event is a MultiEvent, append all events to the queue. 376events = append (events ,m ... ) 377 }else { 378// Otherwise, just append the event to the queue. 379events = append (events ,event ) 380 } 381 } 382 383buf = buf [n :] 384total += n 385 } 386 387return total ,events 388} 389 390func (d * eventScanner )encodeGraphemeBufs () []byte { 391var b []byte 392for kd := range d .graphemeBuf { 393if len (d .graphemeBuf [kd ])> 0 { 394switch kd { 395case 1 : 396b = append (b ,string (d .graphemeBuf [kd ])... ) 397case 0 : 398// Encode the release grapheme as Kitty Keyboard to get the release event. 399grs := uniseg .NewGraphemes (string (d .graphemeBuf [kd ])) 400for grs .Next () { 401var codepoints string 402gr := grs .Str () 403for i ,r := range gr { 404if r == 0 { 405continue 406 } 407if i > 0 { 408codepoints += ":" 409 } 410codepoints += strconv .FormatInt (int64 (r ),10 ) 411 } 412// This is dark :) 413// During serializing/deserializing of win32 input events, the 414// API will split a grapheme into runes and send them as a 415// keydown/keyup sequences. We collect the runes, decoded them 416// from UTF-16 just fine. However, [EventDecoder.Decode] will 417// always decode graphemes as [KeyPressEvent]s. Thus, to 418// workaround that and the existing API, while we are 419// intercepting win32 input events, we encode the grapheme 420// release events as Kitty Keyboard sequences so that 421// [EventDecoder.Decode] can properly decode them as 422// [KeyReleaseEvent]s. 423seq := fmt .Sprintf ("\x1b[%d;1:3;%su" ,d .graphemeBuf [kd ][0 ],codepoints ) 424b = append (b ,seq ... ) 425 } 426 } 427d .graphemeBuf [kd ]= d .graphemeBuf [kd ][:0 ]// reset the buffer 428 } 429 } 430return b 431} 432 433func (d * eventScanner )storeGraphemeRune (kd int ,r rune ) { 434if d .utf16Half [kd ] { 435// We have a half pair that needs to be decoded. 436d .utf16Half [kd ]= false 437d .utf16Buf [kd ][1 ]= r 438r := utf16 .DecodeRune (d .utf16Buf [kd ][0 ],d .utf16Buf [kd ][1 ]) 439d .graphemeBuf [kd ]= append (d .graphemeBuf [kd ],r ) 440 }else if utf16 .IsSurrogate (r ) { 441// This is the first half of a UTF-16 surrogate pair. 442d .utf16Half [kd ]= true 443d .utf16Buf [kd ][0 ]= r 444 }else { 445// This should be a single UTF-16 that can be converted 446// to UTF-8. 447d .graphemeBuf [kd ]= append (d .graphemeBuf [kd ],r ) 448 } 449} 450 451// deserializeWin32Input deserializes the Win32 input events converting 452// KeyEventRecrods to bytes. Before returning the bytes, it will also try to 453// decode any UTF-16 pairs that might be present in the input buffer. 454func (d * eventScanner )deserializeWin32Input (buf []byte ) (int , []byte ) { 455p := ansi .GetParser () 456defer ansi .PutParser (p ) 457 458var processed int 459var state byte 460des := make ([]byte ,0 ,len (buf )) 461 462for len (buf )> 0 { 463seq ,width ,n ,newState := ansi .DecodeSequence (buf ,state ,p ) 464switch width { 465case 0 : 466if p .Command ()== '_' {// Win32 Input Mode 467vk ,_ := p .Param (0 ,0 ) 468if vk == 0 { 469// This is either a serialized KeyEventRecord or a UTF-16 470// pair. 471uc ,_ := p .Param (2 ,0 ) 472kd ,_ := p .Param (3 ,0 ) 473kd = clamp (kd ,0 ,1 )// kd is the key down state (0 or 1) 474d .storeGraphemeRune (kd ,rune (uc )) 475processed += n 476break 477 } 478 } 479fallthrough 480default : 481des = append (des ,d .encodeGraphemeBufs ()... ) 482des = append (des ,seq ... ) 483 } 484 485state = newState 486buf = buf [n :] 487 } 488 489des = append (des ,d .encodeGraphemeBufs ()... ) 490 491return processed ,des 492} 493 494func (d * TerminalReader )logf (format string ,v ... interface {}) { 495logf (d .logger ,format ,v ... ) 496} 497 498func logf (logger Logger ,format string ,v ... interface {}) { 499if logger == nil { 500return 501 } 502logger .Printf (format ,v ... ) 503}