okami.mom/ultraviolet

git clone https://git.nkp.pet/okami.mom/ultraviolet

nekomimi.petfix(input): keep control strings whole when they span reads7bfbaab

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