Documentation ¶
Overview ¶
Package zzterm efficiently reads and decodes terminal input keys and mouse events without any memory allocation. It is intended to be used with a terminal set in raw mode as its io.Reader. Setting the terminal in raw mode is not handled by this package, there are a number of Go packages that can do this (see the example).
Basic usage ¶
Set the terminal in raw mode, use NewInput to create the input key reader and read from the terminal:
func main() { // set the terminal in raw mode, e.g. with github.com/pkg/term t, err := term.Open("/dev/tty", term.RawMode) if err != nil { log.Panic(err) } defer t.Restore() input := zzterm.NewInput() for { k, err := input.ReadKey(t) if err != nil { log.Panic(err) } switch k.Type() { case zzterm.KeyRune: // k.Rune() returns the rune case zzterm.KeyESC, zzterm.KeyCtrlC: // quit on ESC or Ctrl-C return } } }
Mouse and focus events ¶
Mouse events are supported through the Xterm X11 mouse protocol in SGR mode, which is a complex way to call the "modern" handling of mouse events [1] (beyond the limits of 223 for mouse position coordinates in the old protocol). This should be widely supported by modern terminals, but the tracking of mouse events must be enabled on the terminal so that the escape sequences get sent to zzterm. It is the responsibility of the caller to enable this (with SGR mode) before using Input.ReadKey, but as a convenience zzterm provides the EnableMouse and DisableMouse functions:
t, err := term.Open("/dev/tty", term.RawMode) // ... defer t.Restore() // Mouse events can be enabled only to report button presses (zzterm.MouseButton) // or any mouse event (including mouse moves, zzterm.MouseAny). zzterm.EnableMouse(t, zzterm.MouseAny) defer zzterm.DisableMouse(t, zzterm.MouseAny)
And then mouse events will be reported (if supported by the terminal):
// The WithMouse option must be set to decode the mouse events, otherwise // they would be reported as uninterpreted KeyESCSeq (escape sequence). input := zzterm.NewInput(zzterm.WithMouse()) for { // ... switch k.Type() { case zzterm.KeyRune: // k.Rune() returns the rune case zzterm.KeyMouse: // k.Mod() returns the modifier flags (e.g. Shift) pressed during the event // input.Mouse() returns the mouse information, coordinates 1,1 is top-left // ... } }
It works similarly to enable reporting focus in/out of the terminal:
zzterm.EnableFocus(t) defer zzterm.DisableFocus(t) // The WithFocus option must be set to decode the focus events, otherwise // they would be reported as uninterpreted KeyESCSeq (escape sequence). input := zzterm.NewInput(zzterm.WithMouse(), zzterm.WithFocus()) for { // ... switch k.Type() { // ... case zzterm.KeyFocusIn, zzterm.KeyFocusOut: // terminal has gained/lost focus // ... } }
Terminfo ¶
Different terminals sometimes understand different escape sequences to interpret special keys such as function keys (F1, F2, etc.) and arrows. That configuration is part of the terminfo database (at least on Unix-like systems). While zzterm does not read the terminfo database itself, it supports specifying a map of values where the key is the name of the special key and the value is the escape sequence that should map to this key.
escSeq := map[string]string{"KeyDown": "\x1b[B"} input := zzterm.NewInput(zzterm.WithESCSeq(escSeq))
The github.com/gdamore/tcell repository has a good number of terminal configurations described in a Go struct and accessible via terminfo.LookupTermInfo [2]. To enable reuse of this, zzterm provides the FromTerminfo function to convert from those structs to the supported map format. It is the responsibility of the caller to detect the right terminfo to use for the terminal.
ti, err := terminfo.LookupTerminfo("termite") // handle error input := zzterm.NewInput(zzterm.WithESCSeq(zzterm.FromTerminfo(ti)))
Note, however, that the tcell package patches those terminfo descriptions before use due to some inconsistencies in behaviour - using the raw terminfo definitions may not always work as expected [3].
When no WithESCSeq option is provided (or if a nil map is passed), then a default mapping is used. If a non-nil but empty map is provided, then any escape sequence translation will be disabled (except for mouse and focus events if enabled), and all such sequences will be read as keys of type KeyESCSeq. The input.Bytes method can then be called to inspect the raw bytes of the sequence.
[1]: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking [2]: https://godoc.org/github.com/gdamore/tcell/terminfo#LookupTerminfo [3]: https://github.com/gdamore/tcell/blob/8ec73b6fa6c543d5d067722c0444b07f7607ba2f/tscreen.go#L337-L367
Index ¶
- Constants
- func DisableFocus(w io.Writer) error
- func DisableMouse(w io.Writer, eventType MouseEventType) error
- func EnableFocus(w io.Writer) error
- func EnableMouse(w io.Writer, eventType MouseEventType) error
- func FromTerminfo(v interface{}) map[string]string
- type Input
- type Key
- type KeyType
- type Mod
- type MouseEvent
- type MouseEventType
- type Option
Constants ¶
const ErrTimeout = timeoutError("zzterm: timetout")
ErrTimeout is the error returned when ReadKey fails to return a key due to the read timeout expiring.
Variables ¶
This section is empty.
Functions ¶
func DisableFocus ¶
DisableFocus sends the Control Sequence Introducer (CSI) function to w to disable sending focus escape sequences.
func DisableMouse ¶
func DisableMouse(w io.Writer, eventType MouseEventType) error
DisableMouse sends the Control Sequence Introducer (CSI) function to w to disable tracking of the specified mouse event type and to disable SGR mode.
func EnableFocus ¶
EnableFocus sends the Control Sequence Introducer (CSI) function to w to enable sending focus escape sequences.
func EnableMouse ¶
func EnableMouse(w io.Writer, eventType MouseEventType) error
EnableMouse sends the Control Sequence Introducer (CSI) function to w to enable tracking of the specified mouse event type in SGR mode.
func FromTerminfo ¶
FromTerminfo returns a terminfo map that can be used in the call to NewInput. The value v should be a tcell/terminfo.Terminfo struct, a pointer to such a struct, or a value that marshals to JSON with an equivalent structure.
It first marshals v to JSON and then unmarshals it in a map. It makes no validation that v is a valid terminfo, and it returns nil if there is any error when converting to and from the intermediate JSON representations.
Types ¶
type Input ¶
type Input struct {
// contains filtered or unexported fields
}
Input reads input keys from a reader and returns the key pressed.
func NewInput ¶
NewInput creates an Input ready to use. Call Input.ReadKey to read a single key from an io.Reader - typically a terminal file descriptor set in raw mode. The translation of escape sequences to special keys is controlled by the WithESCSeq option.
func (*Input) Bytes ¶
Bytes returns the uninterpreted bytes from the last key read. The bytes are valid only until the next call to ReadKey and should not be modified.
func (*Input) Mouse ¶
func (i *Input) Mouse() MouseEvent
Mouse returns the mouse event corresponding to the last key of type KeyMouse. It should be called only after a key of type KeyMouse has been received from ReadKey, and before any other call to ReadKey.
func (*Input) ReadKey ¶
ReadKey reads a key from r which should be the reader of a terminal set in raw mode. It is recommended to set a read timeout on the raw terminal so that a Read does not block indefinitely. In that case, if a call to ReadKey times out witout data for a key, it returns the zero-value of Key and ErrTimeout.
type Key ¶
type Key uint32
Key represents a single key. It contains the key type, the key modifier flags and the rune itself in a compact form. Use the Rune, Type and Mod methods to get information on the key.
type KeyType ¶
type KeyType byte
KeyType represents the type of key.
const ( KeyNUL KeyType = iota KeySOH KeySTX KeyETX KeyEOT KeyENQ KeyACK KeyBEL KeyBS KeyTAB KeyLF KeyVT KeyFF KeyCR KeySO KeySI KeyDLE KeyDC1 KeyDC2 KeyDC3 KeyDC4 KeyNAK KeySYN KeyETB KeyCAN KeyEM KeySUB KeyESC KeyFS KeyGS KeyRS KeyUS KeyRune // covers ASCII 32-126 + any other unicode code point - from this point on the key type does not match ASCII values KeyLeft KeyRight KeyUp KeyDown KeyInsert KeyBacktab KeyDelete KeyHome KeyEnd KeyPgUp KeyPgDn KeyF1 KeyF2 KeyF3 KeyF4 KeyF5 KeyF6 KeyF7 KeyF8 KeyF9 KeyF10 KeyF11 KeyF12 KeyF13 KeyF14 KeyF15 KeyF16 KeyF17 KeyF18 KeyF19 KeyF20 KeyF21 KeyF22 KeyF23 KeyF24 KeyF25 KeyF26 KeyF27 KeyF28 KeyF29 KeyF30 KeyF31 KeyF32 KeyF33 KeyF34 KeyF35 KeyF36 KeyF37 KeyF38 KeyF39 KeyF40 KeyF41 KeyF42 KeyF43 KeyF44 KeyF45 KeyF46 KeyF47 KeyF48 KeyF49 KeyF50 KeyF51 KeyF52 KeyF53 KeyF54 KeyF55 KeyF56 KeyF57 KeyF58 KeyF59 KeyF60 KeyF61 KeyF62 KeyF63 KeyF64 KeyHelp KeyExit KeyClear KeyCancel KeyPrint KeyESCSeq KeyMouse KeyFocusIn KeyFocusOut // 116 KeyDEL KeyType = 127 )
List of supported key types.
const ( KeyCtrlSpace KeyType = iota KeyCtrlA KeyCtrlB KeyCtrlC KeyCtrlD KeyCtrlE KeyCtrlF KeyCtrlG KeyCtrlH KeyCtrlI KeyCtrlJ KeyCtrlK KeyCtrlL KeyCtrlM KeyCtrlN KeyCtrlO KeyCtrlP KeyCtrlQ KeyCtrlR KeyCtrlS KeyCtrlT KeyCtrlU KeyCtrlV KeyCtrlW KeyCtrlX KeyCtrlY KeyCtrlZ KeyCtrlLeftSq KeyCtrlBackslash KeyCtrlRightSq KeyCtrlCarat KeyCtrlUnderscore KeyBackspace = KeyBS KeyEscape = KeyESC KeyEnter = KeyCR )
List of some aliases to the key types. The KeyCtrl... constants match the ASCII keys at the same position (e.g. KeyCtrlSpace is KeyNUL, KeyCtrlLeftSq is KeyESC, etc.).
type Mod ¶
type Mod byte
Mod represents a key modifier such as pressing alt or ctrl. Detection of such flags is limited.
const ( ModAlt Mod // 2 ModShift // 4 ModMeta // 8 ModCtrl // 16 ModNone Mod = 0 )
List of modifier flags. Values of Shift, Meta and Ctrl are the same as for the xterm mouse tracking. See https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Normal-tracking-mode
type MouseEvent ¶
type MouseEvent struct {
// contains filtered or unexported fields
}
MouseEvent describes a KeyMouse key type. While the Key returned by Input.ReadKey has the modifier flags information, the mouse-related properties are defined by the MouseEvent type.
func (MouseEvent) ButtonID ¶
func (m MouseEvent) ButtonID() int
ButtonID returns the button pressed during the mouse event, starting at 1. A ButtonID of 0 means that no button was pressed - i.e. this is a mouse move event without any button pressed. Up to 11 buttons are supported by the X11 mouse protocol.
func (MouseEvent) ButtonPressed ¶
func (m MouseEvent) ButtonPressed() bool
ButtonPressed returns true if the button identified by ButtonID was pressed during the event. It returns false if instead it was released. If ButtonID is 0 (no button for this mouse event), then ButtonPressed returns true as this is how the xterm X11 mouse tracking reports it.
func (MouseEvent) Coords ¶
func (m MouseEvent) Coords() (x, y int)
Coords returns the screen coordinates of the mouse for this event. The upper left character position on the terminal is denoted as 1,1.
func (MouseEvent) String ¶
func (m MouseEvent) String() string
String returns the string representation of a mouse event.
type MouseEventType ¶
type MouseEventType int
MouseEventType represents a type of mouse events.
const ( MouseButton MouseEventType = iota + 1 // CSI ? 1000 h MouseAny // CSI ? 1003 h )
List of supported mouse event types.
type Option ¶
type Option func(*Input)
Option defines the function signatures for options to apply when creating a new Input.
func WithESCSeq ¶
WithESCSeq sets the terminfo-like map that defines the interpretation of escape sequences as special keys. The map has the same field names as those used in the github.com/gdamore/tcell/terminfo package for the Terminfo struct. Only the fields starting with "Key" are supported, and only the key sequences starting with ESC (0x1b) are considered.
If nil is passed (or if the option is not specified), common default values are used. To prevent any translation of escape sequences to special keys, pass a non-nil empty map. All escape sequences will be returned as KeyESCSeq and the raw bytes of the sequence can be retrieved by calling Input.Bytes.
If you want to use tcell's terminfo definitions directly, you can use the helper function FromTerminfo that accepts an interface{} and returns a map[string]string that can be used here, in order to avoid adding tcell as a dependency, and to support any value that marshals to JSON the same way as tcell/terminfo. Note, however, that tcell manually patches some escape sequences in its code, overriding the terminfo definitions in some cases. It is up to the caller to ensure the mappings are correct, zzterm does not apply any patching.
See https://github.com/gdamore/tcell/blob/8ec73b6fa6c543d5d067722c0444b07f7607ba2f/tscreen.go#L337-L367
func WithFocus ¶
func WithFocus() Option
WithFocus enables reporting of focus in and focus out events when the terminal gets and loses focus. Such events will be reported as a key with type KeyFocusIn or KeyFocusOut. It is the responsibility of the caller to enable focus tracking for the terminal represented by the io.Reader passed to ReadKey. As a convenience, the package provides the EnableFocus and DisableFocus functions to enable and disable focus tracking on a terminal represented by an io.Writer. See https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-FocusIn_FocusOut
func WithMouse ¶
func WithMouse() Option
WithMouse enables mouse event reporting. Such events will be reported as a key with type KeyMouse, and the mouse information can be retrieved by calling Input.Mouse before the next call to Input.RedKey. It is the responsibility of the caller to enable mouse tracking for the terminal represented by the io.Reader passed to ReadKey, and SGR Mouse Mode must be enabled. Not all tracking modes are supported, see MouseEventType constants for supported modes. As a convenience, the package provides the EnableMouse and DisableMouse functions to enable and disable mouse tracking on a terminal represented by an io.Writer.
Only X11 xterm mouse protocol in SGR mouse mode is supported. This should be widely supported by any recent terminal with mouse support. See https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking