input

package
v0.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 7, 2026 License: MIT Imports: 7 Imported by: 0

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

View Source
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

func (d *Decoder) Feed(p []byte) []Event

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

func (d *Decoder) Flush() []Event

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.

func (*Decoder) MidPaste

func (d *Decoder) MidPaste() bool

MidPaste reports whether a bracketed paste is still streaming in. The read loop must not Flush on idle while it is true: a large paste can stall between chunks, and flushing would split it into two events.

func (*Decoder) Pending

func (d *Decoder) Pending() bool

Pending reports whether the decoder is holding an incomplete sequence. The read loop uses it to decide whether a short idle wait plus Flush is needed to resolve a lone Escape.

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

type Key struct {
	Code rune
	Mods Mod
}

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

func (k Key) IsFunctional() bool

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.

func (Key) Text

func (k Key) Text() string

Text returns the text this key inserts, or the empty string for a functional key or a modified key.

type Mod

type Mod uint8

Mod is a bitmask of key modifiers.

const (
	ModShift Mod = 1 << iota
	ModAlt
	ModCtrl
	ModSuper
)

The modifier bits, matching the bitmask the kitty and xterm encodings share on the wire.

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

func NewReader(r io.Reader, timing clock.Timing, escDelay time.Duration) *Reader

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

func (r *Reader) Events() <-chan Event

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.

type Unknown

type Unknown struct{ Seq string }

Unknown carries an escape sequence the decoder does not map. It is surfaced rather than dropped so a key diagnostic view can show exactly what the terminal sent.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL