okami.mom/ultraviolet

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

by22Jyfix: wait for internal goroutine in StreamEvents before returning (#94)0b88c25

upstream
13.6 KiB503 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// 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 {
33	EventDecoder
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.
39	MouseMode *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).
49	EscTimeout time.Duration
50
51	r     io.Reader
52	table map[string]Key // table is a lookup table for key sequences.
53
54	term string // term is the terminal name $TERM.
55
56	// paste is the bracketed paste mode buffer.
57	// When nil, bracketed paste mode is disabled.
58	paste []byte
59
60	lookup 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
64	vtInput bool
65
66	eventScanner *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.
70	utf16Half   [2]bool    // 0 key up, 1 key down
71	utf16Buf    [2][2]rune // 0 key up, 1 key down
72	graphemeBuf [2][]rune  // 0 key up, 1 key down
73	//nolint:unused,nolintlint
74	lastMouseBtns uint32 // the last mouse button state for the previous event
75	//nolint:unused,nolintlint
76	lastWinsizeX, lastWinsizeY int16 // the last window size for the previous event to prevent multiple size events from firing
77
78	logger 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 {
97	d := &TerminalReader{
98		EscTimeout: DefaultEscTimeout,
99		r:          r,
100		term:       termType,
101		lookup:     true, // Use lookup table by default.
102	}
103	d.r = r
104	if d.table == nil {
105		d.table = buildKeysTable(d.Legacy, d.term, d.UseTerminfo)
106	}
107	evs := newEventScanner()
108	evs.EventDecoder = d.EventDecoder
109	evs.table = d.table
110	evs.lookup = d.lookup
111	evs.setLogger(d.logger)
112	d.eventScanner = evs
113	return 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 {
122	for {
123		var readBuf [readBufSize]byte
124		n, err := d.r.Read(readBuf[:])
125		if err != nil {
126			return err //nolint:wrapcheck
127		}
128
129		select {
130		case <-ctx.Done():
131			return nil
132		case 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 {
140	var buf bytes.Buffer
141	errc := make(chan error, 1)
142	readc := make(chan []byte)
143	timeout := time.NewTimer(d.EscTimeout)
144	ttimeout := time.Now().Add(d.EscTimeout)
145
146	var wg sync.WaitGroup
147	wg.Add(1)
148
149	go func() {
150		defer wg.Done()
151		if err := d.streamData(ctx, readc); err != nil {
152			if errors.Is(err, io.EOF) || errors.Is(err, cancelreader.ErrCanceled) {
153				errc <- nil
154				return
155			}
156			errc <- err
157			return
158		}
159	}()
160
161	for {
162		select {
163		case <-ctx.Done():
164			d.sendEvents(eventc, buf.Bytes(), true)
165			wg.Wait()
166			return nil
167		case err := <-errc:
168			d.sendEvents(eventc, buf.Bytes(), true)
169			wg.Wait()
170			return err // return the first error encountered
171		case <-timeout.C:
172			d.logf("timeout reached")
173
174			// Timeout reached process the buffer including any incomplete sequences.
175			var n int // n is the number of bytes processed
176			timedout := time.Now().After(ttimeout)
177			if buf.Len() > 0 && timedout {
178				d.logf("timeout expired, processing buffer")
179				n = d.sendEvents(eventc, buf.Bytes(), true)
180			}
181
182			if n > 0 {
183				buf.Next(n)
184			}
185
186			if buf.Len() > 0 {
187				if !timeout.Stop() {
188					// drain the channel if it was already running
189					select {
190					case <-timeout.C:
191					default:
192					}
193				}
194
195				d.logf("resetting timeout for remaining buffer")
196				timeout.Reset(d.EscTimeout)
197			}
198
199		case read := <-readc:
200			d.logf("input: %q", read)
201			buf.Write(read)
202			ttimeout = time.Now().Add(d.EscTimeout)
203			n := d.sendEvents(eventc, buf.Bytes(), false)
204			if !timeout.Stop() {
205				// drain the channel if it was already running
206				select {
207				case <-timeout.C:
208				default:
209				}
210			}
211
212			if n > 0 {
213				d.logf("processed %d bytes from buffer", n)
214				buf.Next(n)
215			}
216
217			if buf.Len() > 0 {
218				d.logf("resetting timeout for remaining buffer after parse")
219				timeout.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) {
228	d.logger = logger
229}
230
231func (d *TerminalReader) sendEvents(eventc chan<- Event, buf []byte, expired bool) int {
232	n, events := d.eventScanner.scanEvents(buf, expired)
233	for _, event := range events {
234		eventc <- event
235	}
236	return n
237}
238
239// eventScanner scans the buffer for events and sends them to the event channel.
240type eventScanner struct {
241	EventDecoder
242
243	utf16Half   [2]bool    // 0 key up, 1 key down
244	utf16Buf    [2][2]rune // 0 key up, 1 key down
245	graphemeBuf [2][]rune  // 0 key up, 1 key down
246	paste       []byte
247	table       map[string]Key
248	lookup      bool
249	logger      Logger
250}
251
252// newEventScanner creates a new event scanner.
253func newEventScanner() *eventScanner {
254	return &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) {
260	d.logger = logger
261}
262
263func (d *eventScanner) logf(format string, v ...interface{}) {
264	logf(d.logger, format, v...)
265}
266
267func (d *eventScanner) scanEvents(buf []byte, expired bool) (total int, events []Event) {
268	if len(buf) == 0 {
269		return 0, nil
270	}
271
272	var dn int
273	d.logf("processing buf %d %q", len(buf), buf)
274	dn, buf = d.deserializeWin32Input(buf)
275	total += dn
276
277	// Lookup table first
278	if d.lookup && len(buf) > 2 && buf[0] == ansi.ESC {
279		if k, ok := d.table[string(buf)]; ok {
280			return len(buf), []Event{KeyPressEvent(k)}
281		}
282	}
283
284	// total is the total number of bytes processed
285	for len(buf) > 0 {
286		esc := buf[0] == ansi.ESC
287		n, event := d.Decode(buf)
288
289		// Handle bracketed-paste
290		if d.paste != nil { //nolint:nestif
291			if _, ok := event.(PasteEndEvent); !ok {
292				switch event := event.(type) {
293				case KeyPressEvent:
294					if len(event.Text) > 0 {
295						d.paste = append(d.paste, event.Text...)
296					} else {
297						seq := string(buf[:n])
298						isWin32 := strings.HasPrefix(seq, "\x1b[") && strings.HasSuffix(seq, "_")
299						switch {
300						case 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.
305							d.paste = append(d.paste, '\n')
306						case isWin32 && unicode.IsControl(event.Code) && event.Code == event.BaseCode:
307							// This handles other cases such as tabs, escapes, etc.
308							d.paste = append(d.paste, string(event.Code)...)
309						case !isWin32:
310							// We ignore all other non-text win32-input-mode events.
311							if 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.
315								return total, events
316							}
317							d.paste = append(d.paste, seq...)
318						}
319					}
320				case UnknownEvent:
321					if !expired {
322						// If the event is unknown and we are not expired, we
323						// return need to try to decode the buffer again.
324						return total, events
325					}
326				default:
327					// Everything else is ignored...
328				}
329				buf = buf[n:]
330				total += n
331				continue
332			}
333		}
334
335		var isUnknown bool
336		switch event.(type) {
337		case ignoredEvent:
338			// ignore this event
339			event = nil
340		case UnknownEvent:
341			isUnknown = true
342			// Try to look up the event in the table.
343			if !expired {
344				return total, events
345			}
346
347			if k, ok := d.table[string(buf[:n])]; ok {
348				events = append(events, KeyPressEvent(k))
349				return total + n, events
350			}
351
352			events = append(events, event)
353		case PasteStartEvent:
354			d.paste = []byte{} // reset the paste buffer
355		case PasteEndEvent:
356			var paste []rune
357			for len(d.paste) > 0 {
358				r, w := utf8.DecodeRune(d.paste)
359				if r != utf8.RuneError {
360					paste = append(paste, r)
361				}
362				d.paste = d.paste[w:]
363			}
364			d.paste = nil // reset the paste buffer
365			events = append(events, PasteEvent{string(paste)})
366		}
367
368		if !isUnknown && event != nil {
369			if esc && n <= 2 && !expired {
370				// Wait for more input
371				return total, events
372			}
373
374			if m, ok := event.(MultiEvent); ok {
375				// If the event is a MultiEvent, append all events to the queue.
376				events = append(events, m...)
377			} else {
378				// Otherwise, just append the event to the queue.
379				events = append(events, event)
380			}
381		}
382
383		buf = buf[n:]
384		total += n
385	}
386
387	return total, events
388}
389
390func (d *eventScanner) encodeGraphemeBufs() []byte {
391	var b []byte
392	for kd := range d.graphemeBuf {
393		if len(d.graphemeBuf[kd]) > 0 {
394			switch kd {
395			case 1:
396				b = append(b, string(d.graphemeBuf[kd])...)
397			case 0:
398				// Encode the release grapheme as Kitty Keyboard to get the release event.
399				grs := uniseg.NewGraphemes(string(d.graphemeBuf[kd]))
400				for grs.Next() {
401					var codepoints string
402					gr := grs.Str()
403					for i, r := range gr {
404						if r == 0 {
405							continue
406						}
407						if i > 0 {
408							codepoints += ":"
409						}
410						codepoints += 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.
423					seq := fmt.Sprintf("\x1b[%d;1:3;%su", d.graphemeBuf[kd][0], codepoints)
424					b = append(b, seq...)
425				}
426			}
427			d.graphemeBuf[kd] = d.graphemeBuf[kd][:0] // reset the buffer
428		}
429	}
430	return b
431}
432
433func (d *eventScanner) storeGraphemeRune(kd int, r rune) {
434	if d.utf16Half[kd] {
435		// We have a half pair that needs to be decoded.
436		d.utf16Half[kd] = false
437		d.utf16Buf[kd][1] = r
438		r := utf16.DecodeRune(d.utf16Buf[kd][0], d.utf16Buf[kd][1])
439		d.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.
442		d.utf16Half[kd] = true
443		d.utf16Buf[kd][0] = r
444	} else {
445		// This should be a single UTF-16 that can be converted
446		// to UTF-8.
447		d.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) {
455	p := ansi.GetParser()
456	defer ansi.PutParser(p)
457
458	var processed int
459	var state byte
460	des := make([]byte, 0, len(buf))
461
462	for len(buf) > 0 {
463		seq, width, n, newState := ansi.DecodeSequence(buf, state, p)
464		switch width {
465		case 0:
466			if p.Command() == '_' { // Win32 Input Mode
467				vk, _ := p.Param(0, 0)
468				if vk == 0 {
469					// This is either a serialized KeyEventRecord or a UTF-16
470					// pair.
471					uc, _ := p.Param(2, 0)
472					kd, _ := p.Param(3, 0)
473					kd = clamp(kd, 0, 1) // kd is the key down state (0 or 1)
474					d.storeGraphemeRune(kd, rune(uc))
475					processed += n
476					break
477				}
478			}
479			fallthrough
480		default:
481			des = append(des, d.encodeGraphemeBufs()...)
482			des = append(des, seq...)
483		}
484
485		state = newState
486		buf = buf[n:]
487	}
488
489	des = append(des, d.encodeGraphemeBufs()...)
490
491	return processed, des
492}
493
494func (d *TerminalReader) logf(format string, v ...interface{}) {
495	logf(d.logger, format, v...)
496}
497
498func logf(logger Logger, format string, v ...interface{}) {
499	if logger == nil {
500		return
501	}
502	logger.Printf(format, v...)
503}