@@ -31,42 +31,20 @@ type win32InputState struct {
31
31
32
32
// Reader represents an input event reader. It reads input events and parses
33
33
// escape sequences from the terminal input buffer and translates them into
34
- // human- readable events.
34
+ // human‑ readable events.
35
35
type Reader struct {
36
- rd cancelreader.CancelReader
37
- table map [string ]Key // table is a lookup table for key sequences.
38
-
39
- term string // term is the terminal name $TERM.
40
-
41
- // paste is the bracketed paste mode buffer.
42
- // When nil, bracketed paste mode is disabled.
43
- paste []byte
44
-
45
- buf [256 ]byte // do we need a larger buffer?
46
-
47
- // partialSeq holds incomplete escape sequences that need more data
48
- partialSeq []byte
49
-
50
- // keyState keeps track of the current Windows Console API key events state.
51
- // It is used to decode ANSI escape sequences and utf16 sequences.
52
- keyState win32InputState
53
-
54
- parser Parser
55
- logger Logger
36
+ rd cancelreader.CancelReader
37
+ table map [string ]Key // table is a lookup table for key sequences.
38
+ term string // $TERM
39
+ paste []byte // bracketed paste buffer; nil when disabled
40
+ buf [256 ]byte // read buffer
41
+ partialSeq []byte // holds incomplete escape sequences
42
+ keyState win32InputState
43
+ parser Parser
44
+ logger Logger
56
45
}
57
46
58
- // NewReader returns a new input event reader. The reader reads input events
59
- // from the terminal and parses escape sequences into human-readable events. It
60
- // supports reading Terminfo databases. See [Parser] for more information.
61
- //
62
- // Example:
63
- //
64
- // r, _ := input.NewReader(os.Stdin, os.Getenv("TERM"), 0)
65
- // defer r.Close()
66
- // events, _ := r.ReadEvents()
67
- // for _, ev := range events {
68
- // log.Printf("%v", ev)
69
- // }
47
+ // NewReader returns a new input event reader.
70
48
func NewReader (r io.Reader , termType string , flags int ) (* Reader , error ) {
71
49
d := new (Reader )
72
50
cr , err := newCancelreader (r , flags )
@@ -82,46 +60,38 @@ func NewReader(r io.Reader, termType string, flags int) (*Reader, error) {
82
60
}
83
61
84
62
// SetLogger sets a logger for the reader.
85
- func (d * Reader ) SetLogger (l Logger ) {
86
- d .logger = l
87
- }
63
+ func (d * Reader ) SetLogger (l Logger ) { d .logger = l }
88
64
89
- // Read implements [io.Reader].
90
- func (d * Reader ) Read (p []byte ) (int , error ) {
91
- return d .rd .Read (p ) //nolint:wrapcheck
92
- }
65
+ // Read implements io.Reader.
66
+ func (d * Reader ) Read (p []byte ) (int , error ) { return d .rd .Read (p ) }
93
67
94
68
// Cancel cancels the underlying reader.
95
- func (d * Reader ) Cancel () bool {
96
- return d .rd .Cancel ()
97
- }
69
+ func (d * Reader ) Cancel () bool { return d .rd .Cancel () }
98
70
99
71
// Close closes the underlying reader.
100
- func (d * Reader ) Close () error {
101
- return d .rd .Close () //nolint:wrapcheck
102
- }
72
+ func (d * Reader ) Close () error { return d .rd .Close () }
103
73
104
74
func (d * Reader ) readEvents () ([]Event , error ) {
105
75
nb , err := d .rd .Read (d .buf [:])
106
76
if err != nil {
107
- return nil , err //nolint:wrapcheck
77
+ return nil , err
108
78
}
109
79
110
80
var events []Event
111
81
112
- // Combine any partial sequence from previous read with new data
82
+ // Combine any partial sequence from previous read with new data.
113
83
var buf []byte
114
84
if len (d .partialSeq ) > 0 {
115
85
buf = make ([]byte , len (d .partialSeq )+ nb )
116
86
copy (buf , d .partialSeq )
117
87
copy (buf [len (d .partialSeq ):], d .buf [:nb ])
118
- d .partialSeq = nil // clear the partial sequence
88
+ d .partialSeq = nil
119
89
} else {
120
90
buf = d .buf [:nb ]
121
91
}
122
92
123
- // Lookup table first
124
- if bytes .HasPrefix (buf , []byte {'\x1b' }) {
93
+ // Fast path: direct lookup for simple escape sequences.
94
+ if bytes .HasPrefix (buf , []byte {0x1b }) {
125
95
if k , ok := d .table [string (buf )]; ok {
126
96
if d .logger != nil {
127
97
d .logger .Printf ("input: %q" , buf )
@@ -133,24 +103,23 @@ func (d *Reader) readEvents() ([]Event, error) {
133
103
134
104
var i int
135
105
for i < len (buf ) {
136
- nb , ev := d .parser .parseSequence (buf [i :])
137
- if d .logger != nil && nb > 0 {
138
- d .logger .Printf ("input: %q" , buf [i :i + nb ])
106
+ consumed , ev := d .parser .parseSequence (buf [i :])
107
+ if d .logger != nil && consumed > 0 {
108
+ d .logger .Printf ("input: %q" , buf [i :i + consumed ])
139
109
}
140
110
141
- // Handle incomplete sequences - when parseSequence returns (0, nil)
142
- // it means we need more data to complete the sequence
143
- if nb == 0 && ev == nil {
144
- // Store the remaining data for the next read
145
- remaining := len (buf ) - i
146
- if remaining > 0 {
147
- d .partialSeq = make ([]byte , remaining )
111
+ // Incomplete sequence – store remainder and exit.
112
+ if consumed == 0 && ev == nil {
113
+ rem := len (buf ) - i
114
+ if rem > 0 {
115
+ d .partialSeq = make ([]byte , rem )
148
116
copy (d .partialSeq , buf [i :])
149
117
}
150
118
break
151
119
}
152
120
153
- // Handle bracketed-paste
121
+ // Handle bracketed paste specially so we don’t emit a paste event for
122
+ // every byte.
154
123
if d .paste != nil {
155
124
if _ , ok := ev .(PasteEndEvent ); ! ok {
156
125
d .paste = append (d .paste , buf [i ])
@@ -160,15 +129,9 @@ func (d *Reader) readEvents() ([]Event, error) {
160
129
}
161
130
162
131
switch ev .(type ) {
163
- // case UnknownEvent:
164
- // // If the sequence is not recognized by the parser, try looking it up.
165
- // if k, ok := d.table[string(buf[i:i+nb])]; ok {
166
- // ev = KeyPressEvent(k)
167
- // }
168
132
case PasteStartEvent :
169
133
d .paste = []byte {}
170
134
case PasteEndEvent :
171
- // Decode the captured data into runes.
172
135
var paste []rune
173
136
for len (d .paste ) > 0 {
174
137
r , w := utf8 .DecodeRune (d .paste )
@@ -177,7 +140,7 @@ func (d *Reader) readEvents() ([]Event, error) {
177
140
}
178
141
d .paste = d .paste [w :]
179
142
}
180
- d .paste = nil // reset the buffer
143
+ d .paste = nil
181
144
events = append (events , PasteEvent (paste ))
182
145
case nil :
183
146
i ++
@@ -189,8 +152,41 @@ func (d *Reader) readEvents() ([]Event, error) {
189
152
} else {
190
153
events = append (events , ev )
191
154
}
192
- i += nb
155
+ i += consumed
193
156
}
194
157
158
+ // Collapse bursts of wheel/motion events into a single event each.
159
+ events = coalesceMouseEvents (events )
195
160
return events , nil
196
161
}
162
+
163
+ // coalesceMouseEvents reduces the volume of MouseWheelEvent and MouseMotionEvent
164
+ // objects that arrive in rapid succession by keeping only the most recent
165
+ // event in each contiguous run.
166
+ func coalesceMouseEvents (in []Event ) []Event {
167
+ if len (in ) < 2 {
168
+ return in
169
+ }
170
+
171
+ out := make ([]Event , 0 , len (in ))
172
+ for _ , ev := range in {
173
+ switch ev .(type ) {
174
+ case MouseWheelEvent :
175
+ if len (out ) > 0 {
176
+ if _ , ok := out [len (out )- 1 ].(MouseWheelEvent ); ok {
177
+ out [len (out )- 1 ] = ev // replace previous wheel event
178
+ continue
179
+ }
180
+ }
181
+ case MouseMotionEvent :
182
+ if len (out ) > 0 {
183
+ if _ , ok := out [len (out )- 1 ].(MouseMotionEvent ); ok {
184
+ out [len (out )- 1 ] = ev // replace previous motion event
185
+ continue
186
+ }
187
+ }
188
+ }
189
+ out = append (out , ev )
190
+ }
191
+ return out
192
+ }
0 commit comments