hotty

package module
v0.0.0-...-f458973 Latest Latest
Warning

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

Go to latest
Published: Oct 14, 2026 License: Apache-2.0 Imports: 13 Imported by: 10

README

hotty-go

hotty-go is the Go SDK for HOTTY, HTML Over The TTY. With it, a terminal program shows small HTML documents, called surfaces, on rectangles of cells, and hears what the user does in them. A surface can be a card left in the scrollback, a chart that streams, a form that asks, or the panels of a full-screen Bubble Tea program.

It speaks HOTTY 0.2: a host's replies and events carry msgpack bodies (SPEC §3.3), and its query lists the version it speaks (a=q:n=1:v=0.2, SPEC §4). A terminal of another HOTTY version, a 0.1 one included, is no host to it, and the program draws in cells. A program written for 0.1 ports as hotty's CHANGES.md says: a clicked link is Event.Link, not a field.

The SDK:

  • encodes the protocol, and cuts it out of a byte stream, for a program that reads its terminal raw or a relay that reads another's output;
  • finds out whether the terminal is a HOTTY host;
  • keeps surfaces on screen while a full-screen frame changes;
  • edits a text field in cells as a host edits it on a surface, with the keymap the program gives both (hotty.TerminalKeys, hottyedit);
  • converts Markdown, tables and images into documents;
  • tests programs against a host that runs inside go test.

Every program has three renditions (SPEC §14): surfaces on a host, cells on any other terminal, and plain data into a pipe. The examples show all three.

t, err := hottyterm.Open("hello") // the terminal, whatever stdout is
if err != nil {
	fmt.Println("Hello.") // no terminal: a pipe, a cron job
	return
}
defer t.Close()
if !t.Detect(ctx) {
	fmt.Println("Hello.") // a terminal that is not a host
	return
}
_ = t.LineStart(ctx)
_, _ = t.Print("hello", `<p>Hello, <b>HTML</b> in the terminal.</p>`, hotty.Placement{Cols: 40, Rows: 1})
_, _ = t.Fence(ctx) // the host's replies, before the shell reads the terminal

Testing it needs no terminal:

h := hottytest.New(t)
run(ctx, hottyterm.New(h, h, "hello", h.TermSize, nil))
h.Surface("hello-hello").Text() // "Hello, HTML in the terminal."

The reference is in docs/api and on pkg.go.dev.

Install

The SDK is a few modules, so that a program takes on only the dependencies of what it imports:

module packages needs
github.com/neuroplastio/hotty-go hotty, chart, form, series, braille, blocks the standard library, and tinylib/msgp's runtime for a host's msgpack bodies
github.com/neuroplastio/hotty-go/hottyterm hottyterm ultraviolet, for the terminal's input
github.com/neuroplastio/hotty-go/hottytea hottytea Bubble Tea
github.com/neuroplastio/hotty-go/hottyedit hottyedit uax29, Unicode's grapheme segmentation
github.com/neuroplastio/hotty-go/hottytest hottytest x/net/html, hottyedit
github.com/neuroplastio/hotty-go/hottyvt hottyvt, asciicast x/vt, a terminal emulator; asciicast, the standard library
go get github.com/neuroplastio/hotty-go github.com/neuroplastio/hotty-go/hottyterm

Packages

package what it is
hotty Package hotty speaks HOTTY, HTML Over The TTY (https://github.com/neuroplastio/hotty), from a Go program.
blocks Package blocks draws bars in terminal cells with the block elements (U+2581–U+2588): a column's height in eighths of a row, as a bar chart or a histogram in text has it.
braille Package braille draws in terminal cells with the Unicode braille patterns (U+2800–U+28FF): each cell is two dots across and four down, so a line chart in text has four times the rows and twice the columns of its cells.
chart Package chart draws line charts for HOTTY surfaces, as inline SVG that a program changes with deltas as values arrive, and in cells for a terminal that is not a host (Spark).
form Package form is a form for a HOTTY surface: a spec, the document it makes (real HTML controls, with ids a program can focus and change with deltas), and what a submit event brings back, read into typed answers.
hottyedit Package hottyedit edits a text field as a HOTTY host edits one (SPEC §10.2), for a program that draws its fields in cells: the same actions, the same words and lines, the same caret.
hottytea Package hottytea is HOTTY for a full-screen Bubble Tea program: it finds out whether the terminal is a HOTTY host, keeps the program's surfaces on screen as its frame changes, and turns what the host sends into messages.
hottyterm Package hottyterm is HOTTY for a program that is not a full-screen Bubble Tea program: a command that prints documents and exits, one that asks a question, a chart that streams.
hottytest Package hottytest is a HOTTY host that runs inside a test.
hottyvt Package hottyvt shows a terminal's screen on a HOTTY surface: what a program would write to a terminal goes in, and HTML comes out, first as an element of a document and then as deltas for the rows that changed.
hottyvt/asciicast Package asciicast reads terminal recordings in asciinema's asciicast format, versions 2 and 3: a header with the terminal's size, then what the recorded program wrote, each piece at the time it wrote it, with markers a recording may carry between them.
series Package series reads numbers from a stream of text, as plotting tools such as youplot and asciigraph do: lines of numbers separated by spaces, tabs or commas, a column a series, and a first line with no numbers naming them.

Examples

Each example is a program for one use case. It works in a HOTTY host, in any other terminal, and into a pipe, and its tests run it in each.

The examples are a module of their own (examples/go.mod), built against the SDK beside them: what they import is never the SDK's dependency. Run one from there:

cd examples
go run ./dashboard https://example.com
example use case what it shows
ask Ask asks for what a deploy needs with a form, and prints the answers as JSON for the script that ran it. It shows a surface the program reads events from: the form is the program's while it asks, takes the keyboard, reports a submit, and shows what is wrong without a round trip per key; then a detached summary replaces it, so nothing is left that reports to the shell (SPEC §5.5, §10).
card Card prints the result of a deploy as a card that stays in the scrollback, as a box of text where the terminal is not a HOTTY host, and as one line of data into a pipe. It shows the three renditions every HOTTY program has (SPEC §14): which one a run takes, and how each is made.
dashboard Dashboard watches HTTP endpoints, full screen: whether each is up, and its latency charted as the probes come back. It is a Bubble Tea program, and each endpoint is a card on a surface that hottytea keeps in place as the frame changes.
hello Hello prints a line of HTML in the terminal, and a line of text where the terminal cannot show HTML. It is the smallest HOTTY program: open the terminal, ask whether it is a host, print a document at the cursor, and read the host's replies before exiting, so that none is left for the shell.
livechart Livechart charts the numbers a command prints, live, below the command line, and leaves the chart in the scrollback when the command ends. series.Parser reads the numbers: a column a series, named by a first line with no numbers, or with -key the number after KEY=.
markdown Markdown shows a Markdown file among a command's output, its images and tables included, and leaves it in the scrollback. The SDK has no Markdown in it: the program renders with a Markdown library (goldmark) and sends the HTML, the way it would any other.
progress Progress shows a task's progress as a bar that moves in place, and leaves its last state in the scrollback. It shows the cheap way to change a surface many times a second: a custom property moves the bar (hotty.SetVar) and text deltas change the labels (hotty.SetText), a few dozen bytes each, in synchronized output so the host shows them together (SPEC §6).
replay Replay plays a terminal session back on a surface, at the size it was recorded and scaled to fit, and leaves its last frame in the scrollback. It shows a terminal inside a document: hottyvt keeps a terminal emulator of the session's size, apart from the terminal the program runs in, and sends its screen as HTML, then a delta per row that changes, in synchronized output so the host shows each frame whole (SPEC §6).

Documentation

The API reference in docs/api is written from the code by internal/docgen: the doc comments, the Example functions, and the examples' package comments. make docs writes it again, as does go generate ./.... The tables above are written the same way, between their markers; the rest of this file is not. CI fails when what is checked in is not what make docs would write, and when an exported identifier has no doc comment.

Development

The toolchain is in mise.toml: run mise install first.

  • make check is the gate: formatting, tidy modules, vet, staticcheck, the tests with the race detector, coverage, and the documentation being current, in every module.
  • The modules build against each other as they are in the repository: each nested go.mod replaces the others with their directories. What a program that uses the SDK gets are the requirements between them, which make pin REV=… sets: before a release to a pushed commit, and for a release to its version (make pin REV=v0.1.0), committed, and then every module tagged at that commit (v0.1.0, hottyterm/v0.1.0, …).
  • make cover runs the tests with coverage, prints a table by package, and fails when a public package is under COVER_MIN (85%).
  • make docs writes the documentation.
  • make vectors copies the conformance vectors from a checkout of neuroplastio/hotty (HOTTY_DIR, by default ../../hotty/main).

Licensed under Apache-2.0.

Documentation

Overview

Package hotty speaks HOTTY, HTML Over The TTY (https://github.com/neuroplastio/hotty), from a Go program.

A HOTTY host is a terminal that shows surfaces: small HTML documents placed on rectangles of cells. A program sends documents, placements and deltas as escape sequences in its ordinary output, and hears replies and the user's events on its input.

This package is the wire, and does no I/O:

  • The command functions (Doc, Place, Delta, …) return escape sequences as strings. Write them to the terminal like any other output; in a Bubble Tea program, through tea.Raw, so they stay in order with its frames.
  • A Decoder turns the OSC sequences the program reads back into Messages: replies (ReplyOf) and events (EventOf), their msgpack bodies read by the types in wire.go.

The packages beside it do the I/O: term for a command that prints and exits or asks a question, hottytea for a full-screen Bubble Tea program, and hottytest for testing either against a host that runs in the test.

Replies

Every command asks for the replies that are usually wanted: Doc and Place are answered on error (EQUOTA, ENOENT), everything else never. The options change that: Q sets the quiet level, and N numbers a command and asks for its reply. Replies arrive on the program's input, mixed with keys. A program that exits leaves unread replies to whatever reads the terminal next, a shell for instance, which reads them as typing.

Names and ids

A surface name is 1 to 64 of A–Z, a–z, 0–9, '_' and '-' (SurfaceName makes one). Other control values, such as element ids, may be any printable ASCII but ':', ';' and '='. Encode replaces each character outside those with '_', as the specification has hosts do (SPEC §3.2), so a value can never end a command early or add a key to it.

Index

Constants

View Source
const (
	RevealNearest = ""      // as little as brings it into view
	RevealStart   = "start" // its top at the top of each box that holds it
	RevealEnd     = "end"   // its bottom at the bottom
)

Where Reveal brings an element (SPEC §6.1).

View Source
const (
	DetectTimeout    = 1500 * time.Millisecond
	DetectAfterDA1   = 150 * time.Millisecond
	DetectAfterReply = 300 * time.Millisecond
)

The Detector's times (SPEC §4): how long to wait for any answer at all; after a DA1 answer that came before any reply, for a reply that may still be on its way, since the DA1 may answer a question asked before the query; and after the host's reply, for the DA1 answer behind it, which would otherwise be left for whoever reads the terminal next.

View Source
const (
	// Number is HOTTY's OSC number: the ASCII codes of H and O.
	Number = "7279"
	// Chunk is the most payload bytes one OSC carries (SPEC §3.4).
	Chunk = 4096
	// MaxSize is the most columns or rows a surface has (SPEC §5.2).
	MaxSize = 1000
	// MaxName is the longest a surface name is (SPEC §3.5).
	MaxName = 64
	// Version is the protocol version this package speaks: what its query
	// lists, and what a host's capabilities must name (SPEC §4).
	Version = "0.2"
)
View Source
const (
	EventClick  = "click"  // a button, link, summary or data-on~=click element was activated
	EventChange = "change" // a control's value was committed, or a box toggled
	EventInput  = "input"  // an edit of a control with data-on~=input
	EventSubmit = "submit" // a form was submitted; the detail is its fields
	EventPress  = "press"  // a press anywhere in a placement made with Press
	EventFocus  = "focus"  // the user focused an element; Target names it (SPEC §10.1)
	EventBlur   = "blur"   // the surface gave the keyboard back
	EventResize = "resize" // the surface's pixel size changed, its cells did not
	EventFit    = "fit"    // the rows the document needs changed, on a placement made with Fit
	EventHover  = "hover"  // the element under the pointer changed, or the pointer left, on a placement made with Hover
	EventView   = "view"   // which elements with data-on~=view are in view, or where, changed (SPEC §9.5)

	// The terminal's events, which belong to no surface: Surface and
	// Target are empty.
	EventCommand = "command" // an edit command the program takes, by its key or a menu (SPEC §10.6)
	EventCaps    = "caps"    // the capabilities mod, edit or keys changed (SPEC §4)

	// A drag (SPEC §9.1), on an element with data-on~=drag. In the host's
	// events (Drags), EventDrag stands for all three.
	EventDragStart = "dragstart" // the primary button pressed on the element
	EventDrag      = "drag"      // the element under the pointer changed
	EventDragEnd   = "dragend"   // the button released, or the drag cut short
)

Event kinds (SPEC §9). A program ignores kinds it does not know: later versions add some.

View Source
const (
	EINVAL    = "EINVAL"    // an unknown action or op, a bad name, a missing key, a value out of range
	ENOENT    = "ENOENT"    // no surface of that name
	ENOTARGET = "ENOTARGET" // no element with that id; the detail is the id, or ids comma-separated
	EDETACHED = "EDETACHED" // the surface is detached (SPEC §5.5)
	EQUOTA    = "EQUOTA"    // over one of the host's limits (SPEC §13)
	EBUDGET   = "EBUDGET"   // the host gave up on work over its time budget
	EVERSION  = "EVERSION"  // a query lists no version the host speaks; the detail is those it speaks (SPEC §4)
)

Error codes, in a reply's Code (SPEC §3.6).

View Source
const DefaultMax = 16 << 20

DefaultMax is a Decoder's Max when it sets none: 16 MiB, enough for a document with its images inline, and a bound on what a peer can make a Decoder hold.

View Source
const MaxDepth = 32

MaxDepth is how deep a body may nest, its map being the first level (SDK.md §3.9). A body nested deeper does not decode, so that no body can exhaust a stack: TinyGo's is 64 KB.

View Source
const TerminalKeys = "ArrowLeft=char-backward Control+b=char-backward ArrowRight=char-forward Control+f=char-forward " +
	"Alt+ArrowLeft=word-backward Control+ArrowLeft=word-backward Alt+b=word-backward " +
	"Alt+ArrowRight=word-forward Control+ArrowRight=word-forward Alt+f=word-forward " +
	"Home=line-start End=line-end Control+e=line-end " +
	"Backspace=delete-char-backward Control+h=delete-char-backward " +
	"Delete=delete-char-forward Control+d=delete-char-forward " +
	"Alt+Backspace=delete-word-backward Control+w=delete-word-backward Control+Backspace=delete-word-backward " +
	"Alt+Delete=delete-word-forward Alt+d=delete-word-forward Control+Delete=delete-word-forward " +
	"Control+u=delete-to-line-start Control+k=delete-to-line-end " +
	"ArrowUp=line-previous Control+p=line-previous ArrowDown=line-next Control+n=line-next " +
	"PageUp=page-up PageDown=page-down " +
	"Alt+<=input-start Control+Home=input-start Alt+>=input-end Control+End=input-end " +
	"Mod+a=select-all " +
	"Control+m=newline"

TerminalKeys is the SDK's keymap (SDK.md §3.10), as a data-keys value: the keys of Bubble Tea's text input and text area (bubbles), but for Mod+a, which selects all, as in a GUI field and SPEC §10.2's default keymap: Control+a on Linux, while on macOS Control+a stays the line's start. A program puts it in the data-keys of an element that holds its fields, and edits its fields in cells with Resolve(platform, multiline, TerminalKeys), so that they edit the same on a surface and in cells. It leaves Enter to SPEC §10.2's default.

Variables

This section is empty.

Functions

func Blur

func Blur(surface string, opts ...ReplyOption) string

Blur takes the keyboard back from a surface. Its focused control commits its value first, so a change event may come before the blur event.

func CellCSS

func CellCSS(c Caps) (w, h float64)

CellCSS is a cell's size in CSS pixels: what a document's layout and an SVG's viewBox are measured in. Before a host has said, or if it said nothing, it is a usual 9×18.

func CursorBelow

func CursorBelow(rows int) string

CursorBelow is the cursor's move a placement without KeepCursor makes (SPEC §5.2): rows index operations (IND) and a carriage return, to the start of the line below the placement. A relay that keeps a placement from the screen it emulates feeds it this instead.

func DecodeKeys

func DecodeKeys(input []byte) []string

DecodeKeys reads input from the terminal, the bytes a program reads, as SPEC §10.4 names the keys in it: one entry for each key, its canonical name, or "" for input that is no key it names (a mouse report, a sequence it does not know, a key's release). It reads the input whole: an ESC at its end is Escape.

func DefaultEdit

func DefaultEdit(mod string) map[string][]string

DefaultEdit is the default edit shortcuts for a Mod (SPEC §10.2), "Control" or "Meta", the keys canonical with Mod read.

func Del

func Del(surface string, opts ...ReplyOption) string

Del deletes a surface and its placement (SPEC §5.4).

func DelAll

func DelAll(opts ...ReplyOption) string

DelAll deletes every surface.

func DelRes

func DelRes(id string, opts ...ReplyOption) string

DelRes deletes a resource.

func Delta

func Delta(surface string, op Op, target, key string, payload []byte, opts ...ReplyOption) string

Delta changes one surface's document (SPEC §6). target is an element id, "" for a morph by top-level ids; key names the attribute or the custom property. It is never answered, unless an option asks.

func Detach

func Detach(surface string, opts ...ReplyOption) string

Detach gives a surface up (SPEC §5.5): it stays on the screen as text does, placed, changed by deltas and deleted as before, but sends no more events and never has the keyboard; if it has it, the keyboard goes back to the terminal with no blur. A program that leaves surfaces on the screen when it exits detaches them first, unless it sent them Detached. The next Doc sent without Detached makes the surface the program's again.

It is never answered, unless an option asks: a host older than §5.5 refuses it (EINVAL), and a reply nobody reads would reach the shell as typing.

func Doc

func Doc(surface, html string, opts ...DocOption) string

Doc creates a surface, or replaces its document (SPEC §5.1). The surface is the program's, even one it had detached: it reports what the user does in it, and takes the keyboard on the program's behalf (SPEC §5.5), unless the document is sent Detached. It is answered on error: EQUOTA when the host holds no more surfaces.

func Drags

func Drags(c Caps) bool

Drags reports whether the host sends drags (SPEC §9.1): drag in its events, which stands for dragstart, drag and dragend. Unlike Sends, a host that lists no kinds is taken not to, since drags came after the first hosts: a program offers another way to do what its drags do.

func Edit

func Edit(names []string, opts ...ReplyOption) string

Edit takes the edit commands named (SPEC §10.6): "select-all", "copy", "cut", "paste", "undo", "redo". Their keys, the host's edit shortcuts (Caps.Edit), then reach the program as EventCommand in place of their bytes, and the terminal's menus send them with no key. Each Edit replaces the last; none takes nothing. It holds for the screen it is sent on: a full-screen program sends it after switching to the alternate screen, and leaving it drops it.

func Encode

func Encode(ctl Control, payload []byte) string

Encode returns one command: the control and the payload, compressed when that makes it smaller, base64-encoded, and split into chunks of at most Chunk bytes (SPEC §3.3, §3.4). Keys are sent as they are; values are cleaned (Sanitize). The keys o and m are Encode's to set: a control given with them goes out without them.

func EncodeEvent

func EncodeEvent(e Event) string

EncodeEvent is the event as a host sends it (SPEC §9): what a relay writes to a program, the surface named as the program knows it, or a host's own. The detail is e.Detail when it has one, as it came, and otherwise what e carries for its kind, each field as its type. Never compressed (SPEC §3.3).

func EncodePlain

func EncodePlain(ctl Control, payload []byte) string

EncodePlain is Encode without compression: what a host, or a relay speaking as one, writes to a program, since hosts never compress (SPEC §3.3).

func Err

func Err(r Reply) error

Err is the reply as an error: nil when OK, else an *Error.

func Focus

func Focus(surface, target string, opts ...ReplyOption) string

Focus gives a surface the keyboard (SPEC §10.1), at an element if target is not empty; else its focused element keeps focus, or its first focusable element takes it.

func Get

func Get(c Control, k string) string

Get returns a control's value for a key, "" when it lacks the key.

func Has

func Has(c Control, k string) bool

Has reports whether a control has a key.

func Hide

func Hide(surface string, opts ...ReplyOption) string

Hide removes a surface's placement and keeps its document, to place it again without sending it (SPEC §5.4). Deltas still apply to it.

func Hovers

func Hovers(c Caps) bool

Hovers reports whether the host sends hover (SPEC §9.4). Like Drags, a host that lists no kinds is taken not to: without it a program never hears the pointer leave for a surface, and clears what it lit on the next key or press instead.

func Light

func Light(c Caps) bool

Light reports whether the terminal's colour scheme is light.

func Lookup

func Lookup(c Control, k string) (string, bool)

Lookup returns a control's value for a key, and whether it has the key.

func Menu(level *MenuLevel, opts ...ReplyOption) string

Menu shows the which-key menu of a program's key sequence while it waits (SPEC §10.6), or with nil hides it. A host shows it only when it reports Caps.Menu.

func MorphTo

func MorphTo(surface, target, html string, opts ...ReplyOption) string

MorphTo morphs an element into html, or with no target, each top-level element of html into the document's element with its id. Morphing keeps what the user is doing in what stays: focus, the text being typed, an open <details> (SPEC §6.2).

func ParseKey

func ParseKey(name string) (canonical string, ok bool)

ParseKey reads a key's name as SPEC §10.4 writes it, its modifiers in any order ("Shift+Control+a", "Control+ "), and returns its canonical name ("Control+A", "Control+Space"): the modifiers in the order Mod, Control, Alt, Meta, Shift; Shift shown in a letter where it can be; Space for a space. A binding's Mod is kept: a Keymap reads it as its Platform's. ok is false for a name that does not parse.

func Place

func Place(surface string, p Placement, opts ...ReplyOption) string

Place places a surface at the cursor (SPEC §5.2). Placing a surface that is placed moves it. It is answered on error: ENOENT when the host has no such surface (it may have dropped it), EINVAL for a size or window out of range. A reply to a placement with Rows 0 carries the rows chosen.

func PlaceAt

func PlaceAt(surface string, x, y int, p Placement, opts ...ReplyOption) string

PlaceAt places a surface with its window's top-left corner at cell (x, y), counted from 0 at the screen's top-left, and leaves the cursor where it was: the way a full-screen program lays surfaces out without disturbing its own drawing. It saves the cursor (DECSC), moves it, places with C=1, and restores it (DECRC).

func Query

func Query(n int, opts ...QueryOption) string

Query asks whether the terminal is a HOTTY host that speaks Version, fenced by Primary Device Attributes (SPEC §4): a host replies to the query, numbered n, before the DA1 answer every terminal sends, in Version or with EVERSION. A DA1 answer with no reply before it means there is no host. Late asks for a late answer as well.

func RemoveAttr

func RemoveAttr(surface, target, name string, opts ...ReplyOption) string

RemoveAttr removes an attribute from an element.

func ReplyCaps

func ReplyCaps(n string, caps Caps) string

ReplyCaps is a host's answer to a query numbered n (SPEC §4): caps.Raw as it is when caps has one, so that a relay passes on the fields it does not know, and caps written otherwise.

func ReplyErr

func ReplyErr(n, surface, re, code, detail string) string

ReplyErr is a host's error reply (SPEC §3.6): the code and detail as its body.

func ReplyOK

func ReplyOK(n, surface, re string, extra Control, body []byte) string

ReplyOK is a host's success reply (SPEC §3.6), as a host or a relay answering for one writes it to a program: n and surface echo the command's, each left out when "", re names the action answered, extra keys follow it (a placement's c and r), and body is its msgpack, if any. Never compressed (SPEC §3.3).

func ReplyQuery

func ReplyQuery(query Control, caps Caps) string

ReplyQuery is a host's answer to a query (SPEC §4), for a host that speaks only Version: caps, as ReplyCaps writes them, when the query lists Version, and an EVERSION error naming Version when it does not. caps.V is Version.

func Res

func Res(id, mime string, data []byte, opts ...ReplyOption) string

Res stores a resource that documents refer to as cid:<id> (SPEC §7.1): a stylesheet shared by several surfaces, an image, a font. Sending it again replaces it, and redraws every surface that refers to it.

func Reveal

func Reveal(surface, target, where string, opts ...ReplyOption) string

Reveal scrolls an element into view (SPEC §6.1): each box that holds it, the innermost first, along the axes the document scrolls (Scroll). where is RevealStart, RevealEnd or RevealNearest. It moves no focus, so a list beside the document keeps the keyboard: a table of contents that jumps to a section. Elements that report whether they are in view report it after (EventView).

func Sanitize

func Sanitize(v string) string

Sanitize makes v a control value (SPEC §3.2): each character a value may not hold becomes '_', one for each character, not each byte, and one for each byte that is not UTF-8. Above 0x7e is not merely untidy: UTF-8 can hold 0x9c, which a terminal that reads C1 controls takes for ST, ending the sequence early.

func Sends

func Sends(c Caps, kind string) bool

Sends reports whether the host sends an event kind. A host that lists no kinds is taken to send them all. Drag in its events stands for dragstart, drag and dragend (SPEC §4).

func SetAttr

func SetAttr(surface, target, name, value string, opts ...ReplyOption) string

SetAttr sets an attribute on an element.

func SetText

func SetText(surface, target, text string, opts ...ReplyOption) string

SetText replaces an element's children with one text node: a clock, a count. Hosts make it cheap (SPEC §6.1).

func SetVar

func SetVar(surface, target, name, value string, opts ...ReplyOption) string

SetVar sets the custom property --name on an element: the cheap way to move a bar or a needle every frame, with CSS that reads it.

func Speaks

func Speaks(query Control) bool

Speaks reports whether a query lists Version among the versions the program speaks (v, SPEC §4). A host that speaks only Version answers it with its capabilities, and any other query with EVERSION: ReplyQuery.

func Supports

func Supports(c Caps, op Op) bool

Supports reports whether the host supports a delta op. A host that lists no ops is taken to support them all.

func SurfaceName

func SurfaceName(s string) string

SurfaceName makes s a valid surface name: each character a name may not hold becomes '_', and the name is cut to 64 bytes. An empty s is "_".

func Sync

func Sync(cmds ...string) string

Sync wraps commands in synchronized output (DEC mode 2026, SPEC §6.3): the host shows the state after all of them, never one in between.

func ValidName

func ValidName(s string) bool

ValidName reports whether s is a surface name a host accepts: 1 to 64 of A–Z, a–z, 0–9, '_' and '-' (SPEC §3.5).

func Views

func Views(c Caps) bool

Views reports whether the host sends view (SPEC §9.5). Like Drags, a host that lists no kinds is taken not to: without it a program that needs to know what is shown draws that part in cells, or takes the element it last revealed as the one shown.

func WithdrawLate

func WithdrawLate() string

WithdrawLate withdraws a query that asked for a late answer (SPEC §4): a query that wants no answer (a=q:q=2), which takes the held one's place. It has no fence, and nothing answers it.

Types

type Action

type Action string

Action is what a text field does with a key (SPEC §10.2): a keymap binds keys to actions, and Lookup says which one a key does.

const (
	CharBackward       Action = "char-backward"
	CharForward        Action = "char-forward"
	WordBackward       Action = "word-backward"
	WordForward        Action = "word-forward"
	LineStart          Action = "line-start"
	LineEnd            Action = "line-end"
	DeleteCharBackward Action = "delete-char-backward"
	DeleteCharForward  Action = "delete-char-forward"
	DeleteWordBackward Action = "delete-word-backward"
	DeleteWordForward  Action = "delete-word-forward"
	DeleteToLineStart  Action = "delete-to-line-start"
	DeleteToLineEnd    Action = "delete-to-line-end"
	LinePrevious       Action = "line-previous"
	LineNext           Action = "line-next"
	PageUp             Action = "page-up"
	PageDown           Action = "page-down"
	InputStart         Action = "input-start"
	InputEnd           Action = "input-end"
	SelectAll          Action = "select-all"
	Newline            Action = "newline"
	Submit             Action = "submit"
	// Program binds a key to nothing: it reaches the program.
	Program Action = "program"
	// Copy, Cut, Paste, Undo and Redo are the field's clipboard and
	// history (SPEC §10.2). A hottyedit.Field keeps neither, and does
	// nothing with them.
	Copy  Action = "copy"
	Cut   Action = "cut"
	Paste Action = "paste"
	Undo  Action = "undo"
	Redo  Action = "redo"
	// The scroll actions (SPEC §10.2, scrolling keys): outside a text field,
	// a key bound to one, which the element does not use, scrolls the
	// nearest element that scrolls, from the focused one outward; a text
	// field's keymap leaves them out.
	ScrollUp           Action = "scroll-up"
	ScrollDown         Action = "scroll-down"
	ScrollLeft         Action = "scroll-left"
	ScrollRight        Action = "scroll-right"
	ScrollPageUp       Action = "scroll-page-up"
	ScrollPageDown     Action = "scroll-page-down"
	ScrollHalfPageUp   Action = "scroll-half-page-up"
	ScrollHalfPageDown Action = "scroll-half-page-down"
	ScrollStart        Action = "scroll-start"
	ScrollEnd          Action = "scroll-end"
	// Insert is what Lookup returns for a character the field types. A
	// keymap does not bind it.
	Insert Action = "insert"
)

The actions a keymap binds (SPEC §10.2).

func (Action) Moves

func (a Action) Moves() bool

Moves reports whether the action is a move, which selects when done with Shift (SPEC §10.2, Shift selects; Keymap.Selects).

func (Action) Multiline

func (a Action) Multiline() bool

Multiline reports whether only a multi-line field (a textarea, an editing host) has the action. From an input, a key bound to one reaches the program.

func (Action) Scrolls

func (a Action) Scrolls() bool

Scrolls reports whether the action is a scroll action, which only an element that is not a text field has (SPEC §10.2).

type Area

type Area struct {
	Col int `msg:"c"`
	Row int `msg:"r"`
	W   int `msg:"w"`
	H   int `msg:"h"`
}

Area is the cells an element covers (SPEC §9), counted as a drag's are: Col and Row are the first column and row it touches, from 0 at the surface's top left (not its window's), and W and H the columns and rows it spans. An element partly clipped or scrolled away has its whole area, so Col and Row may be negative or past the surface.

func (*Area) DecodeMsg

func (z *Area) DecodeMsg(dc *msgp.Reader) (err error)

DecodeMsg implements msgp.Decodable

func (*Area) EncodeMsg

func (z *Area) EncodeMsg(en *msgp.Writer) (err error)

EncodeMsg implements msgp.Encodable

func (*Area) Msgsize

func (z *Area) Msgsize() (s int)

Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message

type Axes

type Axes int

Axes are the directions a document scrolls in (SPEC §5.1): a bitmask of ScrollVertical and ScrollHorizontal.

const (
	ScrollVertical   Axes = 1
	ScrollHorizontal Axes = 2
)

The axes of Scroll.

type Caps

type Caps struct {
	// V is the protocol version the host implements: "0.2".
	V string `msg:"v"`
	// Ops are the delta ops it supports.
	Ops []string `msg:"ops,omitempty"`
	// Events are the event kinds it sends.
	Events []string `msg:"events,omitempty"`
	// Cell is a cell's size in device pixels; nil when the host did not
	// say.
	Cell *Cell `msg:"cell,omitempty"`
	// Scale is device pixels per CSS pixel.
	Scale float64 `msg:"scale,omitempty"`
	// Scheme is the terminal's colour scheme: "dark" or "light".
	Scheme string `msg:"scheme,omitempty"`
	// Limits are the host's limits, such as "surfaces" (a count) and
	// "resources" (bytes) (SPEC §13).
	Limits map[string]int `msg:"limits,omitempty"`
	// Net is the host's network policy, from directive to the sources it
	// allows (SPEC §7.2): empty when it fetches nothing.
	Net map[string][]string `msg:"net,omitempty"`
	// Scroll is true when a document can ask to scroll (Scroll, SPEC §5.1);
	// a host that does not says nothing, and clips.
	Scroll bool `msg:"scroll,omitempty"`
	// Passthrough is true when the pointer passes through the parts of a
	// surface that take no pointer (SPEC §9.3); a host that does not says
	// nothing, and every window takes the pointer wherever it is.
	Passthrough bool `msg:"passthrough,omitempty"`
	// Steps is true when a drag of an element with data-steps says where
	// in the element the pointer is (Drag.X and Drag.Y, SPEC §9.1); a host
	// that does not says nothing, and its drags carry cells only.
	Steps bool `msg:"steps,omitempty"`
	// Drop is true when an element with drop in its data-on can be a
	// drag's t without being dragged, and a drag says where in it the
	// pointer is when it has data-steps (Drag.TX and Drag.TY, SPEC §9.1); a host
	// that does not says nothing, and only an element that opts in to
	// drags is ever t.
	Drop bool `msg:"drop,omitempty"`
	// Count is true when a repeated click says its place in its run of
	// clicks (Event.Count, SPEC §9); a host that does not says nothing, and
	// every click reads as a single one.
	Count bool `msg:"count,omitempty"`
	// Mod is the modifier Mod stands for in bindings, "Control" or "Meta"
	// (SPEC §10.4); "" when the host did not say, which is Control.
	Mod string `msg:"mod,omitempty"`
	// Edit is the host's edit shortcuts, from each action to its keys
	// (SPEC §10.2); a host that reports them takes edit commands (Edit,
	// SPEC §10.6).
	Edit map[string][]string `msg:"edit,omitempty"`
	// Keys is the user's leader and sequence timeout (SPEC §10.5); nil
	// when the host reads no key sequences.
	Keys *Keys `msg:"keys,omitempty"`
	// Menu is true when the host shows a program's which-key menus (Menu,
	// SPEC §10.6).
	Menu bool `msg:"menu,omitempty"`
	// Host names the implementation, if it says.
	Host string `msg:"host,omitempty"`
	// Version is the implementation's version, with Host: dot-separated
	// numbers, compared one by one ("0.0.10" is after "0.0.9").
	Version string `msg:"version,omitempty"`

	// Raw is the body the capabilities were read from, fields this package
	// does not know included: what a relay announces to the programs
	// behind it (SPEC §2.7). Writing Caps writes the fields above, not Raw.
	Raw []byte `msg:"-"`
}

Caps is what a host says about itself in its reply to a query (SPEC §4).

func CapsOf

func CapsOf(body []byte) (Caps, bool)

CapsOf reads capabilities from the body of a reply to a query (SPEC §4), and reports whether it decoded: what a relay that stored or was handed them reads them with. Raw is a copy of body.

func Merge

func Merge(c, changed Caps) Caps

Merge is c with the fields a caps event changed (Event.Changed) in place of its own (SPEC §4): those whose keys changed's Raw has. Its Raw is c's with those fields replaced, so a relay announces the capabilities as they are now.

func (*Caps) DecodeMsg

func (z *Caps) DecodeMsg(dc *msgp.Reader) (err error)

DecodeMsg implements msgp.Decodable

func (*Caps) EncodeMsg

func (z *Caps) EncodeMsg(en *msgp.Writer) (err error)

EncodeMsg implements msgp.Encodable

func (*Caps) Msgsize

func (z *Caps) Msgsize() (s int)

Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message

type Cell

type Cell struct {
	W int `msg:"w"`
	H int `msg:"h"`
}

Cell is a cell's size in device pixels.

func (*Cell) DecodeMsg

func (z *Cell) DecodeMsg(dc *msgp.Reader) (err error)

DecodeMsg implements msgp.Decodable

func (Cell) EncodeMsg

func (z Cell) EncodeMsg(en *msgp.Writer) (err error)

EncodeMsg implements msgp.Encodable

func (Cell) Msgsize

func (z Cell) Msgsize() (s int)

Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message

type Command

type Command struct {
	// Name is the command: "copy", "cut", "paste", "undo", "redo",
	// "select-all".
	Name string
	// Key is the key that sent it, named as SPEC §10.4 names it; empty for
	// a command from the terminal's menu.
	Key string
	// Text is a paste's text, and HasText whether there is one: a paste
	// with nothing on the clipboard, or from a host that cannot read it,
	// has none.
	Text    string
	HasText bool
}

Command is an edit command (SPEC §10.6): the program decides what it means, and with nothing to do takes it as its Key, as a text field does.

type Control

type Control []KV

Control is a command's or a message's keys, in the order they are sent.

func With

func With(c Control, k, v string) Control

With returns a copy of a control with a key set: in place if the control has it, at the end if not (SDK.md §3.2).

func Without

func Without(c Control, keys ...string) Control

Without returns a copy of a control without the keys given: what a relay forwards with the keys it owns taken off.

type Decoder

type Decoder struct {

	// Max is the most bytes a message's payload may decode to, after
	// base64 and zlib; one that would decode to more is Invalid, and its
	// chunks are not kept (SPEC §3.4 lets a host limit a message). Zero is
	// DefaultMax. A host sets it to what it accepts from a program, at
	// least 1 MiB.
	Max int
	// Invalid counts the malformed messages dropped so far, aborted
	// chunked messages included.
	Invalid int
	// contains filtered or unexported fields
}

Decoder turns OSC sequences into messages, joining chunked ones (SPEC §3.4). Its zero value is ready to use. It is not safe for concurrent use.

func (*Decoder) Feed

func (d *Decoder) Feed(seq string) (m Message, r Result)

Feed takes one complete OSC sequence, as a terminal-input parser delivers it: ESC ] … terminated by ST or BEL. When the result is Complete, m is the message.

A malformed message (a control that does not parse, a payload that is not base64 or zlib) is Invalid. So is a chunked message that another message interrupts; the interrupting one is then decoded as usual, and its own result returned.

type DetectState

type DetectState uint8

DetectState is what a Detector has found.

const (
	// Detecting: nothing is known yet.
	Detecting DetectState = iota
	// Native: the terminal is a HOTTY host.
	Native
	// Text: it is not; the program draws in cells.
	Text
)

The states of a Detector.

func (DetectState) String

func (s DetectState) String() string

String names the state: "detecting", "native" or "text".

type Detector

type Detector struct {
	// N numbers the query: a reply that does not echo it answers someone
	// else's. 0 is 1.
	N int
	// Late asks for a late answer (Late, SPEC §4).
	Late bool

	// State is what the Detector has found, and Caps the host's
	// capabilities, when Native.
	State DetectState
	Caps  Caps
	// Decided is whether State is final; Done, whether detection is over.
	Decided, Done bool
	// Deadline is when to call Tick next; zero before Start and once Done.
	Deadline time.Time
	// contains filtered or unexported fields
}

Detector decides whether the terminal is a HOTTY host (SDK.md §3.8). It is a state machine with the time passed in, so it behaves the same however the program reads the terminal: the program sends what Start returns, gives it each DA1 answer and each reply it reads, calls Tick at Deadline, and End when the input ends or it gives up.

The terminal is a host when it answers the query with capabilities that decode and name Version. Any other answer, an error (EVERSION, from a host that speaks other versions) or capabilities this package cannot read (a 0.1 host's JSON), makes the State Text at once: a host, but not one this program can use (SPEC §4).

Every DA1 answer from the start until Done is detection's, and the program swallows it: it answers the query's fence, or a question asked before, and is no key. A reply that answers the query, ok or an error, is detection's whenever it comes.

With Late, the query asks for a late answer (SPEC §4): the first reply that answers it once the State is Text, and makes the terminal a host, makes the State Native, with the host's capabilities, Decided and Done as it was. The program then starts using HOTTY.

Decided and Done differ only when the terminal answers the query: what it is is known when its reply arrives, and detection is over when the DA1 answer behind it does. A program that must not wait acts when Decided; one that hands the terminal on, when Done.

Its zero value is ready to use. It is not safe for concurrent use.

func (*Detector) DA1

func (d *Detector) DA1(now time.Time) bool

DA1 takes a DA1 answer that arrived at now, and reports whether it was detection's.

func (*Detector) End

func (d *Detector) End(now time.Time)

End ends detection at now: the input ended, or the program gave up. A terminal that has not answered is not a host.

func (*Detector) Reply

func (d *Detector) Reply(r Reply, now time.Time) bool

Reply takes a reply that arrived at now, and reports whether it answers the query: re=q and the query's n, ok or an error. One that comes after the Detector decided changes nothing, unless the query asked for a late answer (Late): then the first one that comes once the State is Text and makes the terminal a host makes it Native.

func (*Detector) Start

func (d *Detector) Start(now time.Time) string

Start starts detection at now, and returns the query to send.

func (*Detector) Tick

func (d *Detector) Tick(now time.Time)

Tick tells the Detector the time is now.

type DocOption

type DocOption interface {
	// contains filtered or unexported methods
}

DocOption is an option of Doc: a ReplyOption, Detached, or Scroll.

func Detached

func Detached() DocOption

Detached sends a document the program only shows (d=1, SPEC §5.5): the surface is created detached, or its document replaced and the surface detached, in the one command. It reports nothing, never takes the keyboard, and its controls act disabled; hover, selection, <details> and hyperlinks still work, and it is placed, changed by deltas, hidden and deleted as before.

It is what a command prints among its output, which outlives it: whatever reads the terminal next, a shell, would read the surface's events as typing. A host older than §5.5 ignores d=1.

func Scroll

func Scroll(axes Axes) DocOption

Scroll lets the document scroll along the axes given (scroll=<axes>, SPEC §5.1, §5.3), as a page does in a browser: its overflow scrolls, with scrollbars in its pixels, never its cells, and a gesture it cannot take further goes on to the terminal unless the document's CSS overscroll-behavior stops it. Scrolling is local: the program hears nothing of it, and a delta keeps the offsets.

Like Detached, it belongs to the document: one sent without it does not scroll. 0, the default, sends no key; any other value goes out as given, for the host to judge. A host that does not scroll (Caps.Scroll false) ignores it and clips, as it clips every other document.

type Drag

type Drag struct {
	// Col and Row are the surface's cell under the pointer, from 0 at its
	// top left (not its window's). They go on past its edges: negative
	// above it and to its left, its size or more below and to its right.
	Col, Row int
	// Keys are the modifier keys held: "shift", "ctrl", "alt" and "meta",
	// in that order.
	Keys []string
	// X and Y are where in the dragged element the pointer is, for an
	// element with data-steps (SPEC §9.1): a step from 0 to its count,
	// along its width and down its height, measured against the element
	// the drag started on wherever the pointer is. HasX and HasY say
	// whether the detail has them: an element with no steps along an axis
	// has none, and 0 is a step like any other.
	X, Y       int
	HasX, HasY bool
	// TX and TY are where in t the pointer is, for a t with drop in its
	// data-on and data-steps (SPEC §9.1), in drag and dragend: measured as X and Y are, against
	// t's box. HasTX and HasTY say whether the detail has them.
	TX, TY       int
	HasTX, HasTY bool
}

Drag is where a drag's pointer is, and the modifier keys held (SPEC §9.1).

type Error

type Error struct {
	Code    string // EINVAL, ENOENT, …
	Detail  string
	Re      string // the action answered
	Surface string
}

Error is an error reply (SPEC §3.6).

func (*Error) Error

func (e *Error) Error() string

Error says what was refused and why: "hotty: delta card: ENOTARGET (go)".

type Event

type Event struct {
	Surface string
	// Kind is one of the Event kinds, or one this package does not know.
	Kind string
	// Target is the id of the element that reported; empty for focus,
	// blur and resize, for a link without one (its href is in the
	// detail), and for a press on nothing with an id.
	Target string

	// Value is a control's value (change, input), or the value attribute
	// of what was clicked (click).
	Value string
	// Checked is a checkbox's or a radio button's state, in a change.
	Checked *bool
	// Fields are a submitted form's fields by name.
	Fields map[string]string
	// Link is a clicked link's.
	Link *Link
	// Size is a resize's new size, in CSS pixels.
	Size *Size
	// FitRows is the rows a fit says the document needs now.
	FitRows int
	// Drag is a dragstart's, a drag's or a dragend's.
	Drag *Drag
	// Hover is a hover's (SPEC §9.4).
	Hover *Hover
	// View is a view's (SPEC §9.5).
	View *View
	// Area is the cells of the element a click or a press reports, a
	// keyboard click's too: what a program places something next to the
	// element by, as a browser places a select's list or a menu by its
	// control.
	Area *Area
	// Count is a click's place in its run of clicks on its element, as
	// the user's platform counts them (SPEC §9): 2 for the second click of
	// a double click, 1 for a single click or the keyboard's. It is 0 for
	// any other kind.
	Count int
	// Command is a command's (SPEC §10.6).
	Command *Command
	// Changed is a caps's: the capabilities that changed, with only the
	// fields the detail has (its Raw), for Merge.
	Changed *Caps

	// Detail is the detail as it came, msgpack: nil when the event has
	// none. It is what a relay passes on (EncodeEvent).
	Detail []byte
}

Event is what the user did in a surface (SPEC §9).

func EventOf

func EventOf(m Message) (Event, bool)

EventOf returns the message as an event, if it is one, its detail read by its kind.

type Hover

type Hover struct {
	// Out is true when the pointer left the window: onto the cells or
	// another surface, through a part that lets it through, out of the
	// terminal, or pressed with Alt. Col and Row are then 0.
	Out bool
	// Col and Row are the surface's cell under the pointer when the
	// element changed, from 0 at its top left (not its window's).
	Col, Row int
}

Hover is where a hover event says the pointer is (SPEC §9.4); the element is the event's Target, empty over nothing with an id and when Out.

type KV

type KV struct{ K, V string }

KV is one control key and its value.

type Keymap

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

Keymap binds keys to actions (SPEC §10.2), read on a Platform. ParseKeymap reads one from a data-keys value; Resolve makes the one a field uses, whose Lookup says what the field does with a key, and Selects whether it selects as it moves. Program says whether any focused element's keymap gives a key to the program, and Scroll which scroll action it binds a key to.

func ParseKeymap

func ParseKeymap(p Platform, value string) *Keymap

ParseKeymap reads a data-keys value as SPEC §10.2 has hosts read it: bindings separated by ASCII white space, each key=action, split at its last '='. It drops the bindings a host ignores: a key that does not parse, an action it does not know, and Tab, Shift+Tab and Escape. Mod reads as the Platform's: on Control, Mod+a and Control+a are one key.

func Resolve

func Resolve(p Platform, multiline bool, values ...string) *Keymap

Resolve makes a field's keymap (SPEC §10.2): the default keymap for the Platform's Mod, with its edit shortcuts, for an input or for a multi-line field, then each data-keys value in turn, the root's first, each overriding the bindings before it key by key. It leaves out each binding to a scroll action where it stands, in its own value too: one neither acts nor overrides an earlier binding of its key.

func (*Keymap) Bind

func (m *Keymap) Bind(name string, a Action) bool

Bind binds a key to an action, and reports whether it did: a host ignores the bindings ParseKeymap drops, and so does Bind.

func (*Keymap) Format

func (m *Keymap) Format() string

Format writes the keymap as a data-keys value: each key once, where it was first bound, as its last binding wrote it (Mod kept), with its last action.

func (*Keymap) Lookup

func (m *Keymap) Lookup(name string) Action

Lookup says what a field with this keymap does with a key (SPEC §10.2): an action; Insert for a character it types; or "" when the key is not the field's, and reaches the program (or, for Tab, moves focus). A key with Shift that no binding names is looked up without Shift.

func (*Keymap) Program

func (m *Keymap) Program(name string) bool

Program reports whether the keymap gives a key to the program: binds it to program, or, for a key with Shift that no binding names, binds it without Shift (SPEC §10.2, keys for the program).

Every focused element has a keymap, and outside a text field program and the scroll actions are the only actions it gives. A host reads the element's as a field's, from the data-keys values of the elements from the root down to it, but with its scroll actions and no default keymap: ParseKeymap(platform, strings.Join(values, " ")). It gives a key the keymap binds to program to the program before the element uses it, and before the surface scrolls with it; a nearer binding of the key to another action takes it back. In a field, Lookup says as much.

func (*Keymap) Scroll

func (m *Keymap) Scroll(name string) Action

Scroll is the scroll action the keymap binds a key to, or "" for none: the key's own binding, or, for a key with Shift that no binding names, the binding without Shift (SPEC §10.2, scrolling keys).

A host asks it of the keymap it asks Program of, for a key the focused element does not use (Space on a button, a character on a select are the element's). A key bound to one scrolls the nearest element that scrolls, from the focused one outward, never the terminal; along an axis the document does not scroll (Scroll, SPEC §5.1), it goes on as if the keymap did not bind it.

func (*Keymap) Selects

func (m *Keymap) Selects(name string) bool

Selects reports whether a field with this keymap selects with a key (SPEC §10.2, Shift selects): Lookup returns a move for it (Action.Moves), and its canonical name has Shift, bound so or looked up without it ("Shift+ArrowLeft", "Control+Shift+End", not "Alt+<"). The field then keeps its anchor and moves the caret (hottyedit.Field.Extend).

type Keys

type Keys struct {
	// Leader is the user's leader, a key sequence: what <leader> stands for.
	Leader string `msg:"leader,omitempty"`
	// Timeout is how long a sequence waits for its next key, in
	// milliseconds.
	Timeout int `msg:"timeout,omitempty"`
}

Keys is how a host reads key sequences (SPEC §10.5).

func (*Keys) DecodeMsg

func (z *Keys) DecodeMsg(dc *msgp.Reader) (err error)

DecodeMsg implements msgp.Decodable

func (Keys) EncodeMsg

func (z Keys) EncodeMsg(en *msgp.Writer) (err error)

EncodeMsg implements msgp.Encodable

func (Keys) Msgsize

func (z Keys) Msgsize() (s int)

Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message

type Link struct{ Href, URL string }

Link is a clicked link: its href, as the document has it, and its URL resolved against the document's base (SPEC §7.3), "" when the href resolves to nothing outside the document.

type MenuItem struct {
	// Key is the key, named as SPEC §10.4 names it.
	Key string `msg:"key"`
	// Label is what it does; empty leaves it to the host.
	Label string `msg:"label,omitempty"`
	// Group is set when more keys follow it.
	Group bool `msg:"group,omitempty"`
	// Enabled is set when it does anything now.
	Enabled bool `msg:"enabled"`
}

MenuItem is a key that can follow a menu's sequence.

func (z *MenuItem) DecodeMsg(dc *msgp.Reader) (err error)

DecodeMsg implements msgp.Decodable

func (z *MenuItem) EncodeMsg(en *msgp.Writer) (err error)

EncodeMsg implements msgp.Encodable

func (z *MenuItem) Msgsize() (s int)

Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message

type MenuLevel struct {
	// Seq is the keys typed so far, named as SPEC §10.4 names them.
	Seq []string `msg:"seq"`
	// Label is their group's label: "+file".
	Label string `msg:"label,omitempty"`
	// Items are the keys that can follow them.
	Items []MenuItem `msg:"items"`
}

MenuLevel is a which-key level a program shows while its key sequence waits (Menu, SPEC §10.6), in myelin's names and order: a myelin Menu encodes as one.

func (z *MenuLevel) DecodeMsg(dc *msgp.Reader) (err error)

DecodeMsg implements msgp.Decodable

func (z *MenuLevel) EncodeMsg(en *msgp.Writer) (err error)

EncodeMsg implements msgp.Encodable

func (z *MenuLevel) Msgsize() (s int)

Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message

type Message

type Message struct {
	Control Control
	Payload []byte
}

Message is one HOTTY message: a reply or an event from the host, or a command from the program (what a host, a relay or a test decodes). Its control is in the order the keys came, so a relay that forwards it sends the same bytes.

type Op

type Op string

Op is a delta operation (SPEC §6.1).

const (
	OpMorph   Op = "morph"   // morph the target into the payload; without a target, top-level elements by id
	OpInner   Op = "inner"   // morph the target's children into the payload's nodes
	OpReplace Op = "replace" // replace the target with the payload's nodes, without morphing
	OpAppend  Op = "append"  // add the payload's nodes as the target's last children
	OpPrepend Op = "prepend" // add the payload's nodes as the target's first children
	OpBefore  Op = "before"  // insert the payload's nodes before the target
	OpAfter   Op = "after"   // insert the payload's nodes after the target
	OpRemove  Op = "remove"  // remove the target
	OpAttr    Op = "attr"    // set the target's attribute named by the key
	OpUnattr  Op = "unattr"  // remove the target's attribute named by the key
	OpText    Op = "text"    // replace the target's children with one text node
	OpVar     Op = "var"     // set the custom property --key on the target
	OpReveal  Op = "reveal"  // scroll the target into view; the payload says where (Reveal)
)

The delta operations.

type Placement

type Placement struct {
	// Cols and Rows are the surface's size in cells, 1 to 1000. Rows 0 is
	// auto: the host lays the document out Cols wide and takes the rows
	// its content needs, and says how many in the reply.
	Cols, Rows int
	// Window, if not zero, is the part of the surface shown: a full-screen
	// program scrolling a surface out of view shows what is still in it.
	Window Window
	// Z stacks overlapping placements, from -1000 to 1000: greater is
	// above, and 0 is the usual.
	Z int
	// Press asks for a press event wherever the user presses in the
	// placement, on text and empty space too (p=1).
	Press bool
	// Fit asks for a fit event whenever the rows the document needs at
	// this width change from the rows last heard, starting from this
	// placement's (f=1): an image or font arrived, a resource was
	// replaced, a delta landed. The placement keeps its size; placing the
	// surface again is the program's to do (SPEC §5.2).
	Fit bool
	// Hover asks for a hover event each time the element with an id under
	// the pointer changes, and when the pointer leaves the window (v=1):
	// for a hint of the program's own, or to clear what it lit outside
	// the surface when the pointer goes onto it (SPEC §9.4).
	Hover bool
	// KeepCursor leaves the cursor where it was (C=1). Otherwise the host
	// moves it to the start of the line below the placement, as text would.
	KeepCursor bool
}

Placement is where and how a surface is shown (SPEC §5.2).

func PlacementOf

func PlacementOf(m Message) (Placement, error)

Placement reads a place command (SPEC §5.2), as a host or a relay does: Rows 0 for r=auto or none, and the window as the command gives it, its defaults filled in: w reaches the surface's right edge, and h its bottom, which with Rows 0 is the host's to find (H 0). A command out of range is an *Error with Code EINVAL, as a host answers it.

type Platform

type Platform struct {
	// Mod is "Control" or "Meta" (SPEC §10.4); "" is Control.
	Mod string
	// Edit is the host's edit shortcuts, from each action to its keys
	// (SPEC §10.2); nil is DefaultEdit(Mod).
	Edit map[string][]string
}

Platform is how a host reads bindings (SDK.md §3.10): the modifier Mod stands for, and its edit shortcuts. The zero Platform is Control with the default edit shortcuts.

func PlatformOf

func PlatformOf(c Caps) Platform

PlatformOf is the Platform of a host's capabilities: its Mod and Edit, the zero Platform's where it reports none.

type QueryOption

type QueryOption interface {
	// contains filtered or unexported methods
}

QueryOption is an option of Query: Late.

func Late

func Late() QueryOption

Late asks for a late answer (late=1, SPEC §4). What stands between the program and the terminal and has no host yet, such as a multiplexer running the program's pane with no terminal attached, may hold the query, and answer it once there is a host, whenever that is. A program that asks takes that answer as detection finding a host: it can start using HOTTY after it fell back to cells. A host answers at once, as it answers any query.

A program that asked, and will no longer take the answer, such as one about to exit, sends WithdrawLate, or the answer would reach whatever reads the terminal after it.

type Quiet

type Quiet int

Quiet says which replies the host sends for a command (SPEC §3.5).

const (
	ReplyAlways  Quiet = 0 // reply whatever the outcome
	ReplyOnError Quiet = 1 // reply only on error
	NoReply      Quiet = 2 // never reply
)

The quiet levels.

type Reply

type Reply struct {
	OK bool
	// Re is the action answered: "doc", "place", "q", ….
	Re string
	// N is the request number, if the command had one (option N).
	N int
	// Surface is the surface the command named, if any.
	Surface string
	// Cols and Rows are a placement's size, in a reply to place: with
	// Placement.Rows 0, the rows the host chose.
	Cols, Rows int
	// Code and Detail say what went wrong, when not OK: empty when the
	// error's body does not decode.
	Code, Detail string
	// Caps are the capabilities an ok reply to a query carries: nil when
	// it carries none, or they do not decode.
	Caps *Caps
	// Message is the reply as it came.
	Message Message
}

Reply is the host's answer to a command (SPEC §3.6).

func ReplyOf

func ReplyOf(m Message) (Reply, bool)

ReplyOf returns the message as a reply, if it is one, its body read.

type ReplyOption

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

ReplyOption sets the reply a command asks for (SPEC §3.5): every command takes one, Doc as a DocOption. N and Q make them, in either order.

func N

func N(n int) ReplyOption

N numbers the command and asks for its reply whatever the outcome (ReplyAlways): the reply echoes n, so the program can tell which command it answers. A Q given with it asks for less.

func Q

func Q(q Quiet) ReplyOption

Q sets the command's quiet level. It wins over the level N implies, whichever comes first.

type Result

type Result int

Result is what a Decoder made of one sequence.

const (
	// NotHotty: the sequence is not HOTTY's, and the caller handles it.
	NotHotty Result = iota
	// Partial: a chunk of a message that is not complete yet (SPEC §3.4).
	Partial
	// Complete: the message is complete.
	Complete
	// Invalid: the sequence was malformed, or aborted a chunked message,
	// and is dropped (SPEC §3.7).
	Invalid
)

The results of Decoder.Feed.

func (Result) String

func (r Result) String() string

String names the result: "complete", "invalid", ….

type Scanner

type Scanner struct {
	// DA1 makes the Scanner cut out answers to Primary Device Attributes
	// as SegmentDA1, for a program that reads them itself (SDK.md §4.1).
	// Without it they pass.
	DA1 bool
	// Invalid counts the HOTTY sequences dropped so far.
	Invalid int
	// contains filtered or unexported fields
}

Scanner cuts HOTTY sequences out of a byte stream, however the reads split it (SDK.md §3.7): a terminal's input, read raw, or a program's output, for a relay. Everything else goes out as it came, in order, in the Feed that brought it; the Scanner holds only what may still become a segment of its own, the start of ESC ] 7279 ; (or of a DA1 answer, with DA1) and a HOTTY sequence in progress. So the segments do not depend on how the stream was split, and a key is never kept waiting.

An ESC inside a HOTTY sequence that does not begin its ST ends the sequence unfinished: it is dropped as malformed, and the ESC begins what comes next. A sequence longer than 64 KiB is dropped up to its terminator.

Its zero value is ready to use. It is not safe for concurrent use.

func (*Scanner) Feed

func (s *Scanner) Feed(p []byte) []Segment

Feed takes the next bytes of the stream and returns its segments, in order. Adjacent SegmentPass segments are one.

func (*Scanner) Flush

func (s *Scanner) Flush() []Segment

Flush ends the stream: what is held goes out as SegmentPass, except a HOTTY sequence in progress, which is dropped as malformed.

A program reading keys flushes a lone ESC that a read ended with, as the Escape key (SDK.md §4.1), at once or when no more bytes follow soon:

segs := s.Feed(buf[:n])
if h := s.Holding(); len(h) == 1 && !s.InSequence() {
	segs = append(segs, s.Flush()...) // a lone ESC
}

func (*Scanner) Holding

func (s *Scanner) Holding() []byte

Holding returns the bytes the Scanner holds for the next Feed: the start of a segment, or a HOTTY sequence in progress. They are good until the next Feed or Flush.

func (*Scanner) InSequence

func (s *Scanner) InSequence() bool

InSequence reports whether a HOTTY sequence is in progress, or one too long is being dropped: the next Feed goes on with it, and Flush would drop it. Otherwise what Holding returns is the start of a segment, which a program reading keys may flush after a moment, as typing (an ESC, then ], is Alt+]).

type Segment

type Segment struct {
	Kind SegmentKind
	// Data is the segment's bytes. It may share memory with the bytes
	// given to Feed, so it is good until the caller changes those: a
	// caller that reuses its read buffer copies what it keeps.
	Data []byte
}

Segment is a piece of a scanned stream.

type SegmentKind

type SegmentKind uint8

SegmentKind is what a Segment holds.

const (
	// SegmentPass: bytes that are not HOTTY's, unchanged: keys, mouse
	// reports, text, other escape sequences, other OSCs.
	SegmentPass SegmentKind = iota
	// SegmentOSC: one complete HOTTY sequence, from ESC ] 7279 ; to its
	// terminator (ST or BEL), ready for a Decoder.
	SegmentOSC
	// SegmentDA1: an answer to Primary Device Attributes, CSI ? <digits
	// and ;> c, from a Scanner made to look for them (Scanner.DA1).
	SegmentDA1
)

The kinds of Segment.

func (SegmentKind) String

func (k SegmentKind) String() string

String names the kind: "pass", "osc" or "da1".

type Size

type Size struct {
	W float64 `msg:"w"`
	H float64 `msg:"h"`
}

Size is a surface's size in CSS pixels, in a resize event.

func (*Size) DecodeMsg

func (z *Size) DecodeMsg(dc *msgp.Reader) (err error)

DecodeMsg implements msgp.Decodable

func (Size) EncodeMsg

func (z Size) EncodeMsg(en *msgp.Writer) (err error)

EncodeMsg implements msgp.Encodable

func (Size) Msgsize

func (z Size) Msgsize() (s int)

Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message

type View

type View struct {
	// Areas are the elements in view by id, each with its whole area,
	// however little of it is shown. One whose area lacks a field is left
	// out.
	Areas map[string]Area
	// AtEnd is true when the document's root is scrolled as far down as it
	// goes, as a root that does not scroll down always is.
	AtEnd bool
}

View is what a view event reports (SPEC §9.5): every element with data-on~=view and an id that is in view, not only what changed.

type Window

type Window struct{ X, Y, W, H int }

Window is the part of a surface a placement shows, in cells from the surface's top-left corner (SPEC §5.2). The zero Window shows all of it.

Directories

Path Synopsis
Package blocks draws bars in terminal cells with the block elements (U+2581–U+2588): a column's height in eighths of a row, as a bar chart or a histogram in text has it.
Package blocks draws bars in terminal cells with the block elements (U+2581–U+2588): a column's height in eighths of a row, as a bar chart or a histogram in text has it.
Package braille draws in terminal cells with the Unicode braille patterns (U+2800–U+28FF): each cell is two dots across and four down, so a line chart in text has four times the rows and twice the columns of its cells.
Package braille draws in terminal cells with the Unicode braille patterns (U+2800–U+28FF): each cell is two dots across and four down, so a line chart in text has four times the rows and twice the columns of its cells.
Package chart draws line charts for HOTTY surfaces, as inline SVG that a program changes with deltas as values arrive, and in cells for a terminal that is not a host (Spark).
Package chart draws line charts for HOTTY surfaces, as inline SVG that a program changes with deltas as values arrive, and in cells for a terminal that is not a host (Spark).
Package form is a form for a HOTTY surface: a spec, the document it makes (real HTML controls, with ids a program can focus and change with deltas), and what a submit event brings back, read into typed answers.
Package form is a form for a HOTTY surface: a spec, the document it makes (real HTML controls, with ids a program can focus and change with deltas), and what a submit event brings back, read into typed answers.
hottyedit module
hottytea module
hottyterm module
hottytest module
hottyvt module
internal
docgen command
Command docgen writes hotty-go's API reference from the code: a page per public package in docs/api, an index of them, and the tables of packages and examples in README.md, between its docgen markers.
Command docgen writes hotty-go's API reference from the code: a page per public package in docs/api, an index of them, and the tables of packages and examples in README.md, between its docgen markers.
Package series reads numbers from a stream of text, as plotting tools such as youplot and asciigraph do: lines of numbers separated by spaces, tabs or commas, a column a series, and a first line with no numbers naming them.
Package series reads numbers from a stream of text, as plotting tools such as youplot and asciigraph do: lines of numbers separated by spaces, tabs or commas, a column a series, and a first line with no numbers naming them.

Jump to

Keyboard shortcuts

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