input

package
v0.0.0-...-64e189b Latest Latest
Warning

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

Go to latest
Published: Oct 3, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Package input decodes raw terminal bytes into structured key events, including multi-byte ANSI escape sequences (arrows, function keys, etc.) and UTF-8 runes, without any external terminfo/termcap dependency.

Index

Examples

Constants

View Source
const (

	// PasteIdleTimeout is how long a paste may go without a new byte before
	// the Reader gives up on its end marker and delivers what it has.
	PasteIdleTimeout = 250 * time.Millisecond

	// MaxPasteBytes is the most paste text one PasteEvent holds.
	MaxPasteBytes = 16 << 20
)
View Source
const DefaultChordTimeout = 500 * time.Millisecond

DefaultChordTimeout is how long ChordMatcher waits for the next key of a still-possible chord before giving up on it. Vim-style multi-key sequences (g g, d d) and Emacs-style prefix bindings (ctrl+x ctrl+s) both use a window in roughly this range.

View Source
const DefaultEscTimeout = 30 * time.Millisecond

DefaultEscTimeout is how long a lone ESC waits for the rest of an escape sequence before it is reported as the Escape key.

Variables

This section is empty.

Functions

This section is empty.

Types

type BackgroundColorEvent

type BackgroundColorEvent struct {
	R, G, B uint8
}

BackgroundColorEvent is the terminal's answer to an OSC 11 background query (ansi.QueryBackgroundColor), with each channel scaled to 8 bits.

func (BackgroundColorEvent) BackgroundRGB

func (e BackgroundColorEvent) BackgroundRGB() (r, g, b uint8)

BackgroundRGB returns the colour; theme.Detect looks for this method.

type BackgroundUnknownMsg

type BackgroundUnknownMsg struct{}

BackgroundUnknownMsg is sent once, to Update, when background detection is on and the terminal did not answer the background-colour query within the timeout. tui.BackgroundUnknownMsg is an alias of it. It lives here, below the root package, so theme.Detect can recognise it (through BackgroundUnknown) without importing either.

func (BackgroundUnknownMsg) BackgroundUnknown

func (BackgroundUnknownMsg) BackgroundUnknown()

BackgroundUnknown marks m as the "no answer" message; theme.Detect looks for this method.

type ChordDef

type ChordDef struct {
	Name string
	Keys []string
}

ChordDef configures one multi-key chord ChordMatcher recognizes. Keys is the ordered sequence of Key.String() forms (e.g. "g", "ctrl+x") that must arrive one after another for the chord to fire.

type ChordMatcher

type ChordMatcher struct {

	// Timeout overrides DefaultChordTimeout; the zero value (before any
	// Feed call sets it) means DefaultChordTimeout.
	Timeout time.Duration
	// contains filtered or unexported fields
}

ChordMatcher accumulates Key events against a set of configured ChordDefs. It has no framework dependency: an application feeds it every Key event from tui.Update and acts on the returned ChordResult, so it works the same whether or not the caller happens to be a tui.Program.

ChordMatcher is not goroutine-safe; use it from a single Update-driven call site, the same assumption every other stateful piece of this library makes.

func NewChordMatcher

func NewChordMatcher(defs ...ChordDef) *ChordMatcher

NewChordMatcher returns a ChordMatcher recognizing defs, using DefaultChordTimeout.

func (*ChordMatcher) Expire

func (c *ChordMatcher) Expire(now time.Time) []Key

Expire flushes a pending prefix that has timed out even though no further key has arrived to trigger Feed's own expiry check — e.g. driven by an application's own idle/tick timer. It returns nil if nothing is pending or the pending prefix hasn't timed out yet.

func (*ChordMatcher) Feed

func (c *ChordMatcher) Feed(k Key, now time.Time) ChordResult

Feed advances the matcher by one Key, arriving at time now. now is a parameter (rather than time.Now()) so callers, and this package's own tests, can drive it deterministically.

type ChordMsg

type ChordMsg struct{ Name string }

ChordMsg is delivered by ChordMatcher.Feed when a configured chord completes: Name is whichever ChordDef.Name matched.

type ChordResult

type ChordResult struct {
	// Msg is non-nil when k completed a configured chord.
	Msg *ChordMsg
	// Pending is true when k extends a still-possible chord prefix: the
	// caller should wait rather than act on k directly. If the chord never
	// completes, a later Feed or Expire call returns the buffered keys via
	// Flush.
	Pending bool
	// Flush holds keys that are NOT part of any completed chord — a
	// previously buffered prefix that turned out to be a dead end, or (when
	// no chord could ever match) k itself — in the order they should be
	// delivered to the application, as if chord matching had never seen
	// them.
	Flush []Key
}

ChordResult is Feed's outcome for one Key.

type Event

type Event = any

Event is whatever ReadEvent decodes next: a Key, a MouseEvent, or a PasteEvent. There's no exported interface to implement — a type switch on the concrete type is the intended way to handle it, the same way tui.Msg is handled in a Model's Update.

type FocusEvent

type FocusEvent struct {
	Focused bool
}

FocusEvent reports the terminal window gaining (Focused true, ESC[I) or losing (Focused false, ESC[O) focus. Terminals only send it after focus reporting is enabled (DECSET 1004).

type Key

type Key struct {
	Type KeyType
	// Text is what a KeyRunes or KeySpace key typed: one character, or the
	// whole text when the terminal reports a key with associated text (a
	// grapheme cluster, a composed character). It is empty for every other
	// type. A Text of one ASCII character shares a table, so decoding it
	// allocates nothing.
	Text string
	// Code is the first rune of Text for a KeyRunes or KeySpace key, and the
	// letter (or space, \\, ], ^, _) of a KeyCtrl key; 0 for every other type.
	Code rune
	Mod  Mod
	// Action is KeyPress (the zero value) unless the Reader was asked to
	// report kitty event types, when it can also be KeyRepeat or KeyRelease.
	Action KeyAction
	// Keypad is true when the key came from the numeric keypad, as reported
	// by the kitty protocol (always with flag 8, KeyboardReportAll, and
	// usually with flag 1). Keypad Enter is {Type: KeyEnter, Keypad: true};
	// keypad digits and operators are KeyRunes with the character in Text;
	// keypad arrows, Home/End, PgUp/PgDn, Insert and Delete use the
	// matching Type. Legacy terminals cannot tell keypad keys apart.
	Keypad bool
}

Key is a single decoded key event.

func (Key) String

func (k Key) String() string

String renders k as a human-readable name, e.g. "ctrl+alt+shift+right" or "a", prefixed with any held modifiers.

type KeyAction

type KeyAction uint8

KeyAction is the kind of a Key event.

const (
	KeyPress KeyAction = iota
	KeyRepeat
	KeyRelease
)

The key actions. Only KeyPress is ever delivered unless release/repeat reporting is on (Reader.SetReportEvents, tui.WithKeyboard with KeyboardReportEvents).

func (KeyAction) String

func (a KeyAction) String() string

String names the action: "press", "repeat" or "release".

type KeyType

type KeyType int

KeyType identifies which key a Key event represents.

const (
	KeyRunes KeyType = iota // printable input; see Key.Text
	KeyUp
	KeyDown
	KeyLeft
	KeyRight
	KeyEnter
	KeyEsc
	KeyTab
	KeyBackspace
	KeyDelete
	KeySpace
	KeyHome
	KeyEnd
	KeyPgUp
	KeyPgDown
	KeyF1
	KeyF2
	KeyF3
	KeyF4
	KeyCtrlC
	KeyCtrl // generic ctrl+letter; the letter is in Key.Code
	KeyUnknown

	// Appended after KeyUnknown so existing values are unchanged. KeyF5..KeyF24
	// are contiguous.
	KeyF5
	KeyF6
	KeyF7
	KeyF8
	KeyF9
	KeyF10
	KeyF11
	KeyF12
	KeyF13
	KeyF14
	KeyF15
	KeyF16
	KeyF17
	KeyF18
	KeyF19
	KeyF20
	KeyF21
	KeyF22
	KeyF23
	KeyF24
	KeyInsert

	// Kitty media keys (private-use codepoints 57428-57440), decoded by
	// the kitty CSI-u path. They carry no runes.
	KeyMediaPlay
	KeyMediaPause
	KeyMediaPlayPause
	KeyMediaReverse
	KeyMediaStop
	KeyMediaFastForward
	KeyMediaRewind
	KeyMediaTrackNext
	KeyMediaTrackPrevious
	KeyMediaRecord
	KeyMediaVolumeDown
	KeyMediaVolumeUp
	KeyMediaMute
)

The key types. KeyRunes carries printable input in Key.Text; the navigation, editing and function keys carry no runes. KeyCtrl is a generic ctrl+letter with the letter in Key.Code, and Ctrl+C is reported separately as KeyCtrlC. KeyUnknown is an escape sequence the reader consumed but does not recognise.

type Mod

type Mod uint8

Mod is a bitmask of modifier keys held down alongside a Key.

Plain Ctrl+letter arrives from the terminal as its own control byte (e.g. Ctrl+A is literally byte 0x01) and is represented by KeyCtrl, not Mod — Mod exists for the keys that have no such direct byte encoding and are only distinguishable via an xterm CSI modifier parameter, chiefly Ctrl/Shift/Alt combined with arrows, Home/End, PgUp/PgDn, and Alt+rune.

const (
	ModShift Mod = 1 << iota
	ModAlt
	ModCtrl
	// ModSuper is only ever set by the kitty keyboard protocol (see
	// parseCSIu) — no legacy xterm CSI-modifier sequence distinguishes it.
	ModSuper
)

The modifier bits of a Mod, combined with |. ModSuper below is set only by the kitty keyboard protocol.

const ModNone Mod = 0

ModNone is the zero Mod: no modifier held.

func (Mod) Alt

func (m Mod) Alt() bool

Alt reports whether Alt was held.

func (Mod) Ctrl

func (m Mod) Ctrl() bool

Ctrl reports whether Ctrl was held.

func (Mod) Shift

func (m Mod) Shift() bool

Shift reports whether Shift was held.

func (Mod) String

func (m Mod) String() string

String renders the held modifiers joined by "+", e.g. "ctrl+alt", or "" for ModNone.

func (Mod) Super

func (m Mod) Super() bool

Super reports whether Super (the Windows/Cmd/Meta key) was held. Only ever true when decoded from a kitty-keyboard-protocol sequence.

type MouseAction

type MouseAction int

MouseAction is what happened to MouseButton.

const (
	MouseActionPress MouseAction = iota
	MouseActionRelease
	MouseActionMotion
)

The mouse actions: a button pressed or released, or the pointer moved (a drag while a button is held, or free movement, depending on the tracking mode the Program enabled).

type MouseButton

type MouseButton int

MouseButton identifies which mouse button (or wheel direction) a MouseEvent refers to.

const (
	MouseButtonNone MouseButton = iota
	MouseButtonLeft
	MouseButtonMiddle
	MouseButtonRight
	MouseButtonWheelUp
	MouseButtonWheelDown
	MouseButtonWheelLeft
	MouseButtonWheelRight
	MouseButtonBack    // SGR button 8 (Cb 128)
	MouseButtonForward // SGR button 9 (Cb 129)
	MouseButton10      // SGR button 10 (Cb 130)
	MouseButton11      // SGR button 11 (Cb 131)
)

The mouse buttons. The wheel is reported as four buttons, one per direction, with MouseActionPress. Back, Forward, MouseButton10 and MouseButton11 are the extra buttons of SGR buttons 8-11.

type MouseEvent

type MouseEvent struct {
	X, Y   int
	Button MouseButton
	Action MouseAction
	Mod    Mod
}

MouseEvent is a decoded SGR mouse report. X and Y are 0-indexed terminal cell coordinates.

type PaletteColorEvent

type PaletteColorEvent struct {
	Index   uint8 // 0-255, the palette slot
	R, G, B uint8
}

PaletteColorEvent is the terminal's answer to an OSC 4 palette query (ansi.QueryPalette) for one colour, with each channel scaled to 8 bits.

func (PaletteColorEvent) PaletteColor

func (e PaletteColorEvent) PaletteColor() (index, r, g, b uint8)

PaletteColor returns the slot and colour; theme.Palette.Observe looks for this method.

type PasteEvent

type PasteEvent struct {
	Text string
	// Incomplete is true when the end marker never arrived: the stream went
	// idle for PasteIdleTimeout or ended. Text is what was received.
	Incomplete bool
	// Truncated is true when the paste was longer than MaxPasteBytes. Text
	// holds the first MaxPasteBytes bytes (never a partial character) and the
	// rest, up to the end marker, was discarded.
	Truncated bool
}

PasteEvent carries the full text of a bracketed paste (ESC[200~...ESC[201~), delivered as one event rather than as individual key presses.

type Reader

type Reader struct {
	// contains filtered or unexported fields
}

Reader decodes a raw byte stream (a terminal in raw mode) into Events.

Example

A Reader turns the raw bytes of a terminal in raw mode into events. It is a pure decoder over an io.Reader, so it needs no TTY.

package main

import (
	"fmt"
	"strings"

	"github.com/ows4444/tui/input"
)

func main() {
	rd := input.NewReader(strings.NewReader("a\x1b[A\x1b[1;5C"))
	for {
		ev, err := rd.ReadEvent()
		if err != nil {
			break
		}
		fmt.Println(ev.(input.Key))
	}
}
Output:
a
up
ctrl+right

func NewReader

func NewReader(r io.Reader) *Reader

NewReader wraps r, buffering reads for decoding. If r supports SetReadDeadline (an *os.File, net.Conn) the ESC timeout uses it; otherwise a single background peek is used, so any io.Reader works.

func (*Reader) EscTimeout

func (rd *Reader) EscTimeout() time.Duration

EscTimeout reports the current ESC timeout.

func (*Reader) ReadEvent

func (rd *Reader) ReadEvent() (Event, error)

ReadEvent blocks until the next event is available.

func (*Reader) SetEscTimeout

func (rd *Reader) SetEscTimeout(d time.Duration)

SetEscTimeout sets how long a lone ESC waits for a following byte. Zero or negative disables waiting (a bare ESC with nothing buffered is Escape).

Example
package main

import (
	"fmt"
	"strings"

	"github.com/ows4444/tui/input"
)

func main() {
	rd := input.NewReader(strings.NewReader("\x1b"))
	rd.SetEscTimeout(0) // a bare ESC with nothing buffered is Escape, with no wait
	ev, _ := rd.ReadEvent()
	fmt.Println(ev.(input.Key))
}
Output:
esc

func (*Reader) SetReportCSIReplies

func (rd *Reader) SetReportCSIReplies(on bool)

SetReportCSIReplies chooses how a CSI reply to a terminal query (DA1 "ESC[?62;22c", DECRPM "ESC[?2026;1$y", a kitty keyboard flags answer "ESC[?0u", ...) is decoded. Off (the default) it is consumed as KeyUnknown. On it is a ReplyEvent with Kind '[' and Data the bytes between "ESC[" and the final byte inclusive (e.g. "?2026;1$y"). The capability probe turns it on; ordinary keys are unaffected either way.

func (*Reader) SetReportEvents

func (rd *Reader) SetReportEvents(on bool)

SetReportEvents chooses how a kitty-protocol key event type is decoded. Off (the default), a key release decodes as KeyUnknown and a repeat as a plain press, exactly as before the option existed. On, they decode as the key itself with Key.Action set to KeyRelease or KeyRepeat.

type ReplyEvent

type ReplyEvent struct {
	Kind byte
	Data string
}

ReplyEvent is a terminal string-sequence reply that is not otherwise decoded (a DCS XTGETTCAP answer, an APC kitty graphics answer, a non-11 OSC reply, ...). Kind is the introducer's final byte: ']' OSC, 'P' DCS, 'X' SOS, '^' PM, '_' APC. Data is the payload without introducer or terminator, truncated to 64 KiB.

Jump to

Keyboard shortcuts

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