Documentation
¶
Overview ¶
Package input decodes the terminal's raw byte stream into typed events: keys with modifiers, bracketed pastes, and focus changes. It is the layer that guarantees the session never loses a keystroke to framing: escape sequences are reassembled across read-chunk boundaries, a paste arrives as one event rather than a burst of keys, and multi-byte characters split by the read buffer are held until complete.
The decoder is a pure state machine over bytes: no reader, no timers, no terminal. That keeps it exhaustively testable (every sequence can be fed whole and byte-by-byte and must decode identically) and leaves policy with the caller. The one inherent ambiguity in terminal input, a lone Escape press versus the first byte of an escape sequence, is resolved by the caller's read loop: when a read returns and the decoder reports a pending partial sequence, the caller waits briefly for more bytes and calls Flush if none arrive, which resolves the pending Escape as the Escape key.
The decoder understands the kitty keyboard protocol (CSI u) and falls back to the legacy encodings (CSI and SS3 function keys, modifier parameters, control bytes, Alt as an Escape prefix), so one event vocabulary covers both modern terminals and every ConPTY, tmux, and legacy emulator behind the same interface.
Index ¶
Constants ¶
const ( KeyEnter rune = keyBase + iota KeyEsc KeyTab KeyBackspace KeyUp KeyDown KeyLeft KeyRight KeyHome KeyEnd KeyPageUp KeyPageDown KeyInsert KeyDelete KeyF1 KeyF2 KeyF3 KeyF4 KeyF5 KeyF6 KeyF7 KeyF8 KeyF9 KeyF10 KeyF11 KeyF12 )
The functional keys every supported encoding (kitty, legacy CSI, SS3, control bytes) normalizes onto.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Decoder ¶
type Decoder struct {
// contains filtered or unexported fields
}
Decoder turns raw terminal bytes into events. Feed as many or as few bytes as each read produced; partial sequences are buffered and complete on a later Feed. The zero value is ready to use.
func (*Decoder) Feed ¶
Feed consumes the next chunk of the input stream and returns the events it completes. Bytes that end mid-sequence stay buffered for the next Feed.
func (*Decoder) Flush ¶
Flush resolves whatever is buffered as final: a pending lone Escape becomes the Escape key, an incomplete sequence is surfaced as Unknown, an unterminated paste is delivered with what arrived, and an incomplete multi-byte character is dropped. The caller invokes it when the stream ends, or after a short idle wait while Pending, which is what turns the Escape-versus-sequence ambiguity into a decision.
type Event ¶
type Event interface {
// contains filtered or unexported methods
}
Event is one decoded input event: a Key, a Paste, a Focus change, or an Unknown sequence.
type Focus ¶
type Focus struct{ Gained bool }
Focus reports the terminal gaining or losing focus (mode 1004 events).
type Key ¶
Key is one key press. Printable keys carry their rune in Code; functional keys use the Key* constants (private-use runes, so they can never collide with real text).
func (Key) IsFunctional ¶
IsFunctional reports whether the key is one of the Key* functional keys rather than text. The check is an exact range: code points above the functional block (emoji, supplementary-plane text) are ordinary text.
type Paste ¶
type Paste struct{ Text string }
Paste is one complete bracketed paste, however many reads it arrived in. Line endings are normalized to \n.
type Reader ¶
type Reader struct {
// contains filtered or unexported fields
}
Reader pumps a raw terminal byte stream through a Decoder and delivers events on a channel. It supplies the one policy the pure decoder cannot: a lone Escape byte is indistinguishable from the start of an escape sequence until more bytes arrive or enough time passes, so when a read leaves the decoder holding a partial sequence, the reader arms an idle timer and flushes if nothing follows. The timer never arms mid-paste: a large paste may stall between chunks, and flushing there would split it in two.
The reader can also be paused, parking its read goroutine off the input stream so another process (an external editor) can own the terminal exclusively, then resumed. Reads are token-gated to make that possible: the pump issues one token per read, so pausing withholds the next token instead of racing a blocked read for the stream's bytes.
Time comes from the injected clock.Timing, so the escape delay is testable without real sleeps and consistent with the runtime's determinism rules.
func NewReader ¶
NewReader starts pumping r. escDelay is how long a partial sequence may dangle before it is flushed (traditional terminals use tens of milliseconds; too low misreads fast sequences on slow links as Escape presses, too high makes the Escape key feel laggy).
func (*Reader) Events ¶
Events is the decoded event stream. It closes when the input reaches EOF or errors, after a final flush, so a consumer ranges over it and exits cleanly with the terminal.
func (*Reader) Pause ¶
func (r *Reader) Pause(poke func()) <-chan struct{}
Pause parks the reader off its input and returns a channel that closes once no read is in flight, so the caller can hand the stream to another process without the two racing for its bytes. When a read is already blocked, poke runs exactly once; the caller uses it to elicit bytes on the stream (a terminal answers a cursor position query) that complete the read. Bytes read between Pause and the park are discarded, as is the poke's answer. The pump handles the request at its next iteration, so a caller waiting on the returned channel must keep draining Events until it closes. Pause and Resume alternate strictly, from one goroutine.
func (*Reader) Resume ¶
func (r *Reader) Resume()
Resume lifts a pause: the pump issues the next read token and decoding starts fresh, dropping any partial escape sequence from before the pause.
func (*Reader) Stop ¶
func (r *Reader) Stop()
Stop abandons the pump and waits for it to exit. The underlying read goroutine may stay blocked in its current Read until the input closes; that is inherent to blocking terminal reads, and callers close or restore the terminal right after Stop, which unblocks it.