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
|
//nolint:unused,revive,nolintlint
package input
import (
"bytes"
"io"
"unicode/utf8"
"github.com/muesli/cancelreader"
)
// Logger is a simple logger interface.
type Logger interface {
Printf(format string, v ...any)
}
// win32InputState is a state machine for parsing key events from the Windows
// Console API into escape sequences and utf8 runes, and keeps track of the last
// control key state to determine modifier key changes. It also keeps track of
// the last mouse button state and window size changes to determine which mouse
// buttons were released and to prevent multiple size events from firing.
type win32InputState struct {
ansiBuf [256]byte
ansiIdx int
utf16Buf [2]rune
utf16Half bool
lastCks uint32 // the last control key state for the previous event
lastMouseBtns uint32 // the last mouse button state for the previous event
lastWinsizeX, lastWinsizeY int16 // the last window size for the previous event to prevent multiple size events from firing
}
// Reader represents an input event reader. It reads input events and parses
// escape sequences from the terminal input buffer and translates them into
// human-readable events.
type Reader struct {
rd cancelreader.CancelReader
table map[string]Key // table is a lookup table for key sequences.
term string // term is the terminal name $TERM.
// paste is the bracketed paste mode buffer.
// When nil, bracketed paste mode is disabled.
paste []byte
buf [256]byte // do we need a larger buffer?
// keyState keeps track of the current Windows Console API key events state.
// It is used to decode ANSI escape sequences and utf16 sequences.
keyState win32InputState
parser Parser
logger Logger
}
// NewReader returns a new input event reader. The reader reads input events
// from the terminal and parses escape sequences into human-readable events. It
// supports reading Terminfo databases. See [Parser] for more information.
//
// Example:
//
// r, _ := input.NewReader(os.Stdin, os.Getenv("TERM"), 0)
// defer r.Close()
// events, _ := r.ReadEvents()
// for _, ev := range events {
// log.Printf("%v", ev)
// }
func NewReader(r io.Reader, termType string, flags int) (*Reader, error) {
d := new(Reader)
cr, err := newCancelreader(r, flags)
if err != nil {
return nil, err
}
d.rd = cr
d.table = buildKeysTable(flags, termType)
d.term = termType
d.parser.flags = flags
return d, nil
}
// SetLogger sets a logger for the reader.
func (d *Reader) SetLogger(l Logger) {
d.logger = l
}
// Read implements [io.Reader].
func (d *Reader) Read(p []byte) (int, error) {
return d.rd.Read(p) //nolint:wrapcheck
}
// Cancel cancels the underlying reader.
func (d *Reader) Cancel() bool {
return d.rd.Cancel()
}
// Close closes the underlying reader.
func (d *Reader) Close() error {
return d.rd.Close() //nolint:wrapcheck
}
func (d *Reader) readEvents() ([]Event, error) {
nb, err := d.rd.Read(d.buf[:])
if err != nil {
return nil, err //nolint:wrapcheck
}
var events []Event
buf := d.buf[:nb]
// Lookup table first
if bytes.HasPrefix(buf, []byte{'\x1b'}) {
if k, ok := d.table[string(buf)]; ok {
if d.logger != nil {
d.logger.Printf("input: %q", buf)
}
events = append(events, KeyPressEvent(k))
return events, nil
}
}
var i int
for i < len(buf) {
nb, ev := d.parser.parseSequence(buf[i:])
if d.logger != nil {
d.logger.Printf("input: %q", buf[i:i+nb])
}
// Handle bracketed-paste
if d.paste != nil {
if _, ok := ev.(PasteEndEvent); !ok {
d.paste = append(d.paste, buf[i])
i++
continue
}
}
switch ev.(type) {
case UnknownEvent:
// If the sequence is not recognized by the parser, try looking it up.
if k, ok := d.table[string(buf[i:i+nb])]; ok {
ev = KeyPressEvent(k)
}
case PasteStartEvent:
d.paste = []byte{}
case PasteEndEvent:
// Decode the captured data into runes.
var paste []rune
for len(d.paste) > 0 {
r, w := utf8.DecodeRune(d.paste)
if r != utf8.RuneError {
paste = append(paste, r)
}
d.paste = d.paste[w:]
}
d.paste = nil // reset the buffer
events = append(events, PasteEvent(paste))
case nil:
i++
continue
}
if mevs, ok := ev.(MultiEvent); ok {
events = append(events, []Event(mevs)...)
} else {
events = append(events, ev)
}
i += nb
}
return events, nil
}
|