Documentation
¶
Overview ¶
Package input turns the bytes a terminal sends into events.
A terminal reports input as a stream that mixes plain text with escape sequences, and it splits that stream wherever the read happens to land: half a sequence in one read and half in the next is normal, not an error. Parser is therefore incremental — it is fed whatever arrived and returns whatever is now unambiguous.
Nothing here touches a terminal. The parser is a function of its bytes, which is what lets every sequence this package claims to understand be stated as a test.
Index ¶
- Constants
- type Action
- type Advance
- type Binding
- type Button
- type Chord
- type Code
- type DCS
- type DeviceAttributes
- type DeviceVersion
- type Event
- type FocusIn
- type FocusOut
- type Handler
- type Key
- type KeyboardFlags
- type Keymap
- type Keys
- type Mods
- type Mouse
- type MouseAction
- type OSC
- type Parser
- type Paste
- type Pending
- type Resize
- type Transition
- type Wheel
Examples ¶
Constants ¶
const ( // KittyDisambiguate makes every key arrive as an unambiguous code rather than as // whatever byte it historically produced. It is what makes Shift+Enter and // Ctrl+Enter tellable apart from Enter. KittyDisambiguate = 1 << iota // KittyReportEvents adds key releases and repeats. Without it a key going down is // all there is, and anything held cannot be known to have been let go. KittyReportEvents // KittyReportAlternates adds the key a different layout would have produced. KittyReportAlternates // KittyReportAllAsEscapes makes even plain letters arrive as sequences. KittyReportAllAsEscapes // KittyReportText adds the text a key produced, which the terminal knows and a // program guessing from a keycode does not. KittyReportText )
The Kitty keyboard protocol's progressive enhancements, as the bits a terminal reports them in.
const DefaultKeyTimeout = time.Second
DefaultKeyTimeout is how long a partly-typed sequence waits for the rest of itself when a keymap does not say.
A second, which is what a text editor with sequences in it has settled on. It is long enough that nobody types "g g" too slowly by accident and short enough that a "g" left over from a minute ago does not turn the next one into something else.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Action ¶ added in v0.0.2
type Action string
Action is one thing something can be asked to do, under a name.
Why a name and not a keystroke ¶
A widget that owns its keystrokes owns two things at once — what it can do, and what produces it — and can express neither of them on its own. There is nowhere to put a sequence like "g g", because a field holds one key; rebinding one chord means replacing the whole struct, because the struct is the binding; and the same widget in two places, where escape means "back" in one and "close" in the other, has to be told about both.
So a widget names what it can do and answers to the name, and a Keymap says which keystrokes produce it. The two halves can then be replaced independently, which is the whole point: a program keeps the widgets and rebinds the keys, or keeps the keys and swaps the widget.
Naming one ¶
A name is an identifier and reads as one: lowercase words joined by hyphens. "delete-word-back", "select-all". Anything is allowed — an action nothing else has heard of is exactly what a program binds its own keys to — and the only rule is that the two halves agree on the spelling, which is what makes a constant worth declaring for one.
func (Action) Does ¶ added in v0.0.2
Does is the action in the words a hint row shows it in: the name, with the hyphens that make it an identifier taken out.
There is no second field for a description, and no table mapping one to the other. A description held beside the name is a description that drifts from it, and the name already says what the thing does — that is what naming an action is. A program that wants other words, in another language or in its own house style, names its actions in them.
type Advance ¶ added in v0.0.2
type Advance struct {
// contains filtered or unexported fields
}
Advance turns a run of wheel reports into whole rows, keeping what is left over.
The remainder is the whole reason this is a type and not a division. On a terminal that sends three reports to a notch worth three rows, each report is worth exactly one and nothing is left; on one that sends three reports to a notch worth one row, each is worth a third, and rounding each to zero would mean the view never moved at all while the wheel turned.
Zero rows is an ordinary answer. A caller scrolls by what it gets and asks again on the next report.
func (*Advance) At ¶ added in v0.0.2
At is the same for a report that came with a time on it, which is what a terminal's reader stamps — see Mouse.At.
The time is what tells a finger from the wheel. Both send the same report, and only how fast they arrive is different: a wheel's notches come as far apart as a hand can turn them, and a finger's motion arrives as fast as the terminal can report it.
func (*Advance) By ¶ added in v0.0.2
By is how many rows n reports in one direction come to.
A negative count is upwards, which is what the caller already has: a wheel event is one report in a direction, so this is called with plus or minus one.
type Binding ¶ added in v0.0.2
Binding is one entry of a map: a sequence of chords, and what it does.
type Button ¶
type Button uint8
Button identifies which mouse button an action belongs to.
const ( // ButtonNone is the zero value, which is right for a bare move and for a // wheel: neither belongs to a button. ButtonNone Button = iota ButtonLeft ButtonMiddle ButtonRight )
The buttons a terminal reports. There is no fourth: the higher button numbers in the protocol are the wheel, which arrives as an action instead.
type Chord ¶ added in v0.0.2
Chord is one keystroke: a key with the modifiers held down with it.
It is a Key with everything that is not part of the identity taken off — which transition it was, what text the terminal said it produced, when it arrived. Those describe one occurrence of a keystroke; a chord describes which keystroke it was, so only a chord can be written down in advance and bound to something.
func ParseChord ¶ added in v0.0.2
ParseChord reads what Chord.String writes, and reports whether it was a keystroke this package can name.
It is the half of the round trip that makes keys configurable at run time: a keybinding read out of a file is a string, and turning it into something a keymap can hold is this. Nothing else in the repository needs it, which is why it is here rather than in whatever ends up reading the file.
func (Chord) MarshalText ¶ added in v0.0.2
MarshalText writes the chord as Chord.String does, so a keybinding survives being written to a configuration file and read back.
func (Chord) String ¶ added in v0.0.2
String writes the chord the way a keybinding is conventionally written, and the way ParseChord reads one back: the modifiers, then the key.
func (*Chord) UnmarshalText ¶ added in v0.0.2
UnmarshalText reads what MarshalText wrote.
It is here rather than left to whatever reads the file because it is the same parse either way, and because implementing it is what makes a keymap something any of the usual decoders can fill in without being told how.
type Code ¶
type Code int
Code identifies which key was pressed. Character means the key produced text, carried in Key.Rune.
const ( // Character is the zero value, so a Key literal with only a rune in it is a // character press — which is what most of them are. Character Code = iota Enter Esc Backspace Tab Up Down Left Right Home End PageUp PageDown Delete Insert F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 )
The keys a terminal can report: a character press, then the named keys in the order a keyboard is usually described — what finishes a line, what cancels, what edits, then movement, then the function row.
type DCS ¶ added in v0.0.2
type DCS struct{ Body string }
DCS is a device control string the terminal sent.
It is the other shape an answer comes in, and the one that carries a terminal's own name and version — the reply to the version query is ">|kitty(0.32.2)". There is no command number and no single grammar, so the body comes back as it was written and what it means is decided by whoever asked.
Only the shapes a terminal actually replies in are decoded as one. See the package's own notes on why: the introducer is also Alt+Shift+P.
type DeviceAttributes ¶ added in v0.0.2
type DeviceAttributes struct {
// Class is the terminal class the answer led with: 62 for a VT220, 64 for a
// VT420. Little depends on it, and terminals that emulate one of those are
// not otherwise alike.
Class int
// contains filtered or unexported fields
}
DeviceAttributes is a terminal's answer to being asked what it is.
Every terminal answers this one, which makes it useful for more than what it says. A question a terminal might not understand can be followed by this one, and this answer arriving without the other is how a terminal says it did not understand — which is not something any terminal says out loud.
func Attributes ¶ added in v0.0.2
func Attributes(class int, features ...int) DeviceAttributes
Attributes is an answer with the given class and extensions, for anything standing in for a terminal.
func (DeviceAttributes) Features ¶ added in v0.0.2
func (d DeviceAttributes) Features() []int
Features are the numbered extensions the terminal claimed. Sixel graphics is 4. There is no authority over the list and a terminal may claim what it does not do, so this is evidence rather than proof.
func (DeviceAttributes) Has ¶ added in v0.0.2
func (d DeviceAttributes) Has(n int) bool
Has reports whether the terminal claimed extension n.
It reads the claims rather than building the slice, because this is asked once per capability and a session asks about two of them.
type DeviceVersion ¶ added in v0.0.2
type DeviceVersion struct {
// Kind is the terminal class number, which says very little.
Kind int
// Version and Patch are what it reported.
Version, Patch int
}
DeviceVersion is a terminal's answer to being asked which version of itself it is.
It is the query for the terminals that answer nothing else. Alacritty exports no version in the environment and declines the version string on principle; this is what it does answer.
The numbers are as the terminal sent them, because what they mean is the terminal's convention and not a standard: most pack a version as major, minor and patch into DeviceVersion.Version, and reading it as anything is a bet on which terminal is being asked.
type Event ¶
type Event interface {
// contains filtered or unexported methods
}
Event is one thing the terminal reported. The set is closed by the unexported method: a consumer's switch over events is exhaustive by construction.
type Handler ¶
Handler is anything that answers an event, reporting whether it consumed it.
It lives here for the same reason [grid.Drawer] lives with the view: the word belongs to the layer that owns the thing being handled. Everything further up that answers input says so by embedding this, so "consumed" means one thing across the whole repository rather than one thing per layer that happens to line up.
An unconsumed event carries on to whatever else might want it, which is how a key can mean one thing inside a text field and another outside it without either side knowing about the other.
type Key ¶
type Key struct {
Code Code
Rune rune
Mods Mods
// Transition is Press unless the terminal speaks the Kitty keyboard protocol,
// which is the only way repeats and releases are ever reported.
Transition Transition
// Text is what the key produced, when the terminal was able to say. It can
// hold more than one code point, and is empty on terminals that do not report
// it — Rune is the fallback and the common case.
Text string
// At is when the keystroke arrived, as whatever read it saw. It is zero when
// nothing timed it, which is what a parser fed bytes directly produces.
//
// It is here for the same reason it is on [Mouse]: a key means different things
// depending on when it came. Two chords typed in one burst are a sequence and a
// terminal never says so; the same two with a pause between them are two
// keystrokes that happen to be adjacent. Only the goroutine that did the reading
// knows, so it is stamped there rather than left for every caller to supply a
// clock for a fact the library already had — see [Keymap.Lookup].
At time.Time
}
Key is a keyboard event.
A character key arrives as Character with the rune in Rune. Ctrl held with a letter also arrives as a character — the letter, lowercased, with Ctrl in Mods — because that is what the terminal actually sends and inventing a separate representation for it would mean two ways to ask the same question.
func (Key) Down ¶
Down reports whether the key is going down — pressed or auto-repeating. Most handlers want this rather than Press alone, or holding a key stops working on terminals that report repeats.
func (Key) Is ¶
Is reports whether the key is code with exactly mods held.
Exactly, not at least: a binding on Ctrl+C that also fired for Ctrl+Shift+C would swallow a keystroke its owner never claimed.
func (Key) String ¶
String names the keystroke the way a help line or a keybinding file writes it.
It is Chord.String: what a key event is called is what was pressed, and nothing about this particular occurrence of it.
type KeyboardFlags ¶ added in v0.0.2
type KeyboardFlags struct{ Flags int }
KeyboardFlags is a terminal's answer about which of the Kitty keyboard protocol's enhancements are turned on.
Asking is not the same as being answered, and being answered is not the same as having asked. A terminal may accept the request for unambiguous key codes and give nothing for key releases — the protocol is live, the teardown still owes a pop, and no release ever arrives. Nothing in the events themselves distinguishes that from a user who simply has not lifted a key, so the only way to know is to read back what took.
func (KeyboardFlags) Has ¶ added in v0.0.2
func (k KeyboardFlags) Has(flag int) bool
Has reports whether a flag is among those the terminal turned on.
type Keymap ¶ added in v0.0.2
type Keymap struct {
// Timeout is how long a partly-typed sequence waits for the rest of itself. Zero
// uses [DefaultKeyTimeout].
Timeout time.Duration
// contains filtered or unexported fields
}
Keymap says which keystrokes produce which actions.
It is a table and nothing else: it knows no widget, and every widget that reads keys through one answers the actions it recognises and ignores the rest. That is what lets one map serve a whole interface — a field, the container around it and the program's own keys, all in the same table — and what lets a program hand the same map to two widgets without either of them knowing.
Sequences ¶
A binding can be more than one chord long. The chords have to arrive within Keymap.Timeout of each other, judged by when the terminal's reader stamped them; a chord that does not continue what was being typed abandons it and is read on its own.
A binding that is a proper prefix of a longer one is not reached. Nothing here can wake an interface up after a pause, so a chord that might still be the start of something longer can only be decided by what comes next — and deciding it as the shorter binding is deciding that the longer one never fires. The longer one wins, and the shorter is a binding that exists and never happens.
The zero Keymap is empty and binds nothing. Use it by pointer: a map with bindings added to a copy of it would leave the original quietly sharing them.
Example ¶
package main
import (
"fmt"
"github.com/Tangerg/oolong/core/input"
)
func main() {
// A widget names what it can do; the map says which keystrokes produce the name.
// Neither knows the other, so either can be replaced without touching the other.
keys := &input.Keymap{}
keys.Bind("delete-word-back", input.Ctrl.Rune('w'))
keys.Bind("delete-word-back", input.Alt.With(input.Backspace))
keys.Bind("submit", input.Chord{Code: input.Enter})
var pending input.Pending
for _, key := range []input.Key{
{Code: input.Character, Rune: 'w', Mods: input.Ctrl},
{Code: input.Enter},
{Code: input.Character, Rune: 'q'},
} {
action, mine := keys.Lookup(key, &pending)
fmt.Printf("%-9s %-18s mine=%v\n", key, "\""+string(action)+"\"", mine)
}
}
Output: ctrl+w "delete-word-back" mine=true enter "submit" mine=true q "" mine=false
Example (Sequences) ¶
package main
import (
"fmt"
"github.com/Tangerg/oolong/core/input"
)
func main() {
// A binding can be more than one chord long. The first chord is the map's and
// names nothing yet, which the caller has to consume rather than pass on.
keys := &input.Keymap{}
keys.Bind("go-to-top", input.Chord{Rune: 'g'}, input.Chord{Rune: 'g'})
var pending input.Pending
for range 2 {
action, mine := keys.Lookup(input.Key{Rune: 'g'}, &pending)
fmt.Printf("%q taken=%v waiting=%q\n", action, mine, pending.Keys().String())
}
}
Output: "" taken=true waiting="g" "go-to-top" taken=true waiting=""
func (*Keymap) Action ¶ added in v0.0.2
Action is what a sequence names on its own, and whether it names anything.
It is the lookup with no sequence under way and nothing remembered afterwards, which is what asking a question about the map — rather than reading keys through it — wants.
func (*Keymap) Bind ¶ added in v0.0.2
Bind makes a sequence of chords produce an action, replacing whatever those chords produced before.
An action may have several sequences bound to it — Ctrl+W and Alt+Backspace both delete a word — and the order they are bound in is the order Keymap.Keys gives them back, so the first is the one a hint row shows.
func (*Keymap) Bindings ¶ added in v0.0.2
Bindings is every binding in the map, in the order it was made. It is what a program showing its own keybinding list, or writing one back out to a file, asks for.
func (*Keymap) Keys ¶ added in v0.0.2
Keys are the sequences bound to an action, in the order they were bound. Nothing bound to it is no sequences, which is what a hint row skips.
Example ¶
package main
import (
"fmt"
"github.com/Tangerg/oolong/core/input"
)
func main() {
// What a hint row asks: the keystrokes bound to an action, in the order they were
// bound, so the one that works everywhere can be put first.
keys := &input.Keymap{}
keys.Bind("delete-word-back", input.Ctrl.Rune('w'))
keys.Bind("delete-word-back", input.Alt.With(input.Backspace))
for _, bound := range keys.Keys("delete-word-back") {
fmt.Println(bound)
}
fmt.Println(input.Action("delete-word-back").Does())
}
Output: ctrl+w alt+backspace delete word back
func (*Keymap) Lookup ¶ added in v0.0.2
Lookup is what a keystroke means, following any sequence already under way.
It reports the action the keystroke completed, and whether the keystroke was this map's at all. Those are not the same question: a chord that begins a sequence is the map's without naming an action yet, and comes back as an empty action that was nonetheless taken. A caller that passed it on instead would let the first half of a sequence act somewhere else as well.
switch action, mine := keys.Lookup(key, &w.pending); {
case !mine:
return false // not ours; let it go past
case action == "":
return true // the start of a sequence, waiting for the rest
}
A nil Pending has nowhere to remember a half-typed sequence, so only what a chord names on its own is reachable through one.
Releases are not keystrokes and name nothing: a map answers a key going down.
type Keys ¶ added in v0.0.2
type Keys []Chord
Keys is a sequence of chords: one keystroke, or several typed one after another.
func ParseKeys ¶ added in v0.0.2
ParseKeys reads a sequence: chords separated by spaces. A chord that is itself the space bar is written "space", so there is nothing ambiguous to split on.
func (Keys) MarshalText ¶ added in v0.0.2
MarshalText writes the sequence as Keys.String does.
func (Keys) String ¶ added in v0.0.2
String writes the sequence with a space between the chords, which is how a keybinding file spells one and what ParseKeys reads back.
func (*Keys) UnmarshalText ¶ added in v0.0.2
UnmarshalText reads what MarshalText wrote.
type Mods ¶
type Mods uint8
Mods is the set of modifier keys held during an event.
const ( Shift Mods = 1 << iota Alt Ctrl // Super is the platform's own modifier — Command on macOS, the Windows key // elsewhere. Only terminals speaking the Kitty keyboard protocol report it. Super )
The modifiers a terminal can report. Super is last because it is the only one that needs the Kitty protocol to arrive at all.
func (Mods) Rune ¶ added in v0.0.2
Rune is the chord of a character key held with these modifiers:
input.Ctrl.Rune('w')
type Mouse ¶
type Mouse struct {
Pos image.Point
Action MouseAction
Button Button
Mods Mods
// At is when the report arrived, as whatever read it saw. It is zero when nothing
// timed it, which is what a parser fed bytes directly produces.
//
// A mouse report means different things depending on when it came. Two presses
// close together are a double-click and a terminal never says so; a run of wheel
// reports without a gap is a trackpad and not the wheel. Both questions are about
// arrival, and the only thing that knows the answer is the goroutine that did the
// reading — so it is stamped there rather than left for every caller to supply a
// clock for a fact the library already had.
At time.Time
}
Mouse is a mouse event, positioned in cells with the origin at the top left.
type MouseAction ¶
type MouseAction uint8
MouseAction is what the mouse did.
const ( MouseDown MouseAction = iota MouseUp MouseDrag MouseMove WheelUp WheelDown )
What the mouse did. Drag is a move with a button held, and the two wheel directions are actions rather than buttons because no button is involved.
type OSC ¶ added in v0.0.2
type OSC struct {
// Command is the number the sequence names itself by: 11 for the colour the
// terminal draws on, 52 for its clipboard.
Command int
// Params is everything after the command number and its semicolon, left as
// the terminal wrote it apart from invalid UTF-8 being replaced.
//
// What it means depends on the command, which this package deliberately does
// not work out. Reading a background colour belongs to whatever owns colours;
// this package owns bytes.
Params string
}
OSC is an operating system command the terminal sent.
This is how a terminal answers a question. A program writes a query, and the answer comes back on the input stream mixed in with whatever the user is typing — asking what colour the terminal draws on and reading its clipboard both work this way. A session that asks nothing never sees one.
type Parser ¶
type Parser struct {
// contains filtered or unexported fields
}
Parser decodes terminal bytes into events, incrementally.
Bytes are handed to Parser.Feed exactly as they arrived, at whatever boundaries the read produced. Anything not yet unambiguous stays buffered: escape sequences and multi-byte characters routinely arrive in pieces, and a decoder that assumed otherwise would drop keys under load.
One case cannot be resolved by waiting. A lone escape byte is either the Escape key or the start of a sequence whose remainder has not arrived, and only time tells the difference. Parser.Pending reports that something is waiting, and Parser.Flush declares the wait over.
Not safe for concurrent use: it belongs to whichever goroutine reads the terminal.
func (*Parser) Flush ¶
Flush resolves what only time could resolve and returns the result.
A buffered escape becomes the Escape key, and anything after it is re-read as ordinary input. A half-arrived character is dropped, since the rest is never coming. A paste in progress is left alone: it is incomplete rather than ambiguous, and cutting it short would corrupt the text.
func (*Parser) Pending ¶
Pending reports whether anything is waiting for more input to make sense of it: bytes that might yet become a sequence, or a runaway one still being dropped. It is what tells a loop to arm the timer that will call Parser.Flush, and the runaway counts because the state has to end somewhere — otherwise the next keystroke that happened to be a parameter byte would vanish into it.
type Paste ¶
type Paste struct{ Text string }
Paste is a block of text the terminal delivered as a paste rather than as keystrokes, so it can be inserted whole instead of being interpreted a character at a time.
type Pending ¶ added in v0.0.2
type Pending struct {
// contains filtered or unexported fields
}
Pending is how far into a sequence the chords typed so far have got.
It belongs to whoever is reading keys and not to the map they are read against. A map is a table, and tables are shared: two fields with the same bindings are two places a sequence can be half typed, and one of them finishing it must not finish the other's.
The zero value is nothing typed yet.
type Resize ¶
type Resize struct{ Width, Height int }
Resize reports the terminal's new size in cells.
type Transition ¶
type Transition uint8
Transition is what happened to a key.
const ( // Press is the zero value: an ordinary terminal only ever reports presses, // and a Key literal that says nothing about its transition means one. Press Transition = iota Repeat Release )
What happened to a key. Repeat and Release only ever arrive from a terminal speaking the Kitty keyboard protocol; everything else reports presses.
type Wheel ¶ added in v0.0.2
type Wheel struct {
// Reports is how many wheel events the terminal sends for one physical notch.
Reports int
// Rows is how far one notch should move a view.
Rows int
// Trackpad is how far a notch's worth of continuous scrolling should move it.
//
// A finger is not a notch. A terminal that coalesces a swipe into a few reports
// has to make each worth more, or a swipe crawls; one that reports every scrap of
// motion has to make each worth less, or a swipe flings. The two cannot be told
// apart from a report — only from how fast they arrive, which is why a mouse
// event carries when it came.
//
// It is divided by [trackpadReports] rather than by Reports, because a notch is
// what Reports counts and a finger does not have notches.
Trackpad int
}
Wheel says what a terminal's wheel reports are worth.
Why this is not a constant ¶
A wheel report carries a direction and no magnitude, and terminals disagree about how many reports one notch of the wheel is. Apple Terminal, kitty, Ghostty and alacritty send three; iTerm2 and WezTerm send one; an editor's embedded terminal sends one and means three rows by it. So the same code, scrolling a fixed number of rows per report, moves three times as far on one terminal as on another — and there is no way to ask, because the protocol does not carry it.
The zero value is the commoner arrangement: three reports to a notch, three rows to a notch, which comes to one row a report. It is a reasonable answer everywhere and the right one on most terminals.
func WheelFor ¶ added in v0.0.2
WheelFor is what a terminal does with its wheel.
name is what the terminal said it was when asked, and outranks everything else: an environment describes the terminal a session was started from, which over ssh, in a container, or under a multiplexer is not the terminal it is talking to. An empty name means nothing was asked, or nothing answered, and the environment is all there is.
Multiplexers ¶
A multiplexer reads the mouse reports and writes its own, so whatever the outer terminal batched is gone by the time the program sees anything: tmux, screen and zellij all forward one report per notch regardless of what arrived. Their answer therefore replaces the outer terminal's rather than being combined with it, and checking for them has to come first.
The lookup is passed in rather than read, for the same reason it is everywhere else in this library: this package is a function of its inputs, and a test that could not say what terminal it was in could not check any of these answers.
func (Wheel) Distance ¶ added in v0.0.2
Distance is how many rows one report of the wheel is worth, as a fraction of a row.
A fraction, because a report is very often worth less than a row and rounding each one to zero would stop the view moving at all — see Advance.
func (Wheel) TrackpadDistance ¶ added in v0.0.2
TrackpadDistance is how many rows one report of continuous scrolling is worth.
A terminal that says nothing about it is taken to treat a finger like the wheel, which is what nearly all of them do: the ones where it differs are the ones that coalesce a swipe hardest.