Documentation
¶
Overview ¶
Package snapshot captures what an emulator holds, packs it for a wire or a file, and brings another emulator to the same state.
Capture reads a vt.Terminal into a State: the screen and the newest scrollback rows as cells, and everything that decides how later output is painted and is not in the cells, such as the modes, the pen, the scroll region, the tab stops and a sequence the parser is part way through. Apply is the reverse. Pack and Unpack switch the cells between a plain form and a packed form that is a fraction of the size. Replay turns a State into the bytes that paint it on any xterm-compatible emulator. CaptureHistory and ApplyHistory keep a pane's history across a restart.
A State encodes with encoding/gob and encoding/json. Its fields are only added to, so a peer of an older build reads a newer State and the other way round.
Index ¶
Constants ¶
const DefaultScrollback = 1000
DefaultScrollback is how many scrollback lines a state request carries when it does not ask for a number.
const PackedFormatVersion = 1
PackedFormatVersion names the packed layout below. Bump it with any change to what pack writes or walkPacked reads: saved pane history (scrollback_persist.go) stores packed rows on disk and takes its version from this one.
Variables ¶
This section is empty.
Functions ¶
func Apply ¶
Apply brings an emulator to the state a snapshot describes. It is the reading half of the wire contract Capture writes, and it is the whole of what rehydration does to an emulator: the caller owns the locking, the window-level flags and the stream that resumes afterwards.
The emulator may be fresh or may be one that survived a workspace switch and already holds most of this, so every step is written to be idempotent.
A part of the snapshot that cannot be read is skipped and the rest is applied. The error names the parts that were skipped, and is nil when none was.
func ApplyHistory ¶
ApplyHistory lays a saved history (History.State) into a new emulator: the saved history rows go into its history, the saved screen's used rows onto its screen, and the cursor under them, where the caller writes a divider. It returns the error of a state that does not unpack, and then changes nothing.
The emulator is at the saved size while this happens, and the caller resizes it afterwards, so a pane that comes back at another width reflows the restored screen through the emulator's own reflow rather than being cut. The pure emulator keeps the older history at its saved width.
func Replay ¶
Replay turns a State into the bytes that paint it on a fresh xterm-compatible emulator of the same size. A client that is not written in Go cannot read a State, and every terminal emulator can read bytes. In tuios it is the SNAP frame of stream-pane.
What it reproduces:
- The scrollback rows the state carries, above the screen they belong to, by printing them from the top and letting them scroll off.
- Every cell's text, colours and attributes. A palette colour stays a palette index and a truecolour stays truecolour, so the client's theme applies the way it does to live output. Hyperlinks travel as OSC 8.
- Soft wraps: a wrapped row is printed to its last column and the next row continues it, so the client emulator marks the row as wrapped too.
- The alternate screen: the main screen is painted first, then 1049 switches to the alternate one and paints it, so quitting the program brings the shell's screen back.
- The cursor position, its shape and its visibility, a pending wrap, the pen, the scroll region (top and bottom margins), the character sets, the kitty keyboard flags, modifyOtherKeys, and the DEC modes that change what the client must send: application cursor keys, the keypad mode, bracketed paste, focus reports, every mouse tracking mode and its encodings, and alternate scroll.
- The tab stops, the window title, insert mode (IRM) and newline mode (LNM).
- The sequence the daemon's parser was in the middle of, last, so the stream that follows the snapshot finishes it.
The tuios-gpui renderer primes each pane with the same bytes, through tuios gui-bridge (Replay), so one encoder serves both.
What it does not: left and right margins, the cursor DECSC saved, protected cells, the character REP repeats, the title stack and the colours the guest set with OSC 4, 10, 11 and 12. They decide how later output is painted, and a phone that misses them paints a rare sequence differently until the next snapshot; the input it sends is the same.
The state may be packed, as one read off the wire is. It is not changed.
Types ¶
type CellState ¶
type CellState struct {
Content string `json:"c,omitempty"` // Cell content (character or grapheme)
Width int `json:"w,omitempty"` // Cell width (1 for normal, 2 for wide chars, 0 for continuation)
StyleState
}
CellState represents a single terminal cell with full styling information.
The attributes travel as the emulator's own bitmask rather than as a bool per attribute. Spelling them out one at a time is what left blink, conceal and strikethrough off the wire entirely, and collapsed the five underline styles into one.
type GuestColorState ¶
type GuestColorState struct {
Palette []PaletteColorState `json:"palette,omitempty"` // OSC 4 slots the guest set
Fg string `json:"fg,omitempty"` // OSC 10 default foreground
Bg string `json:"bg,omitempty"` // OSC 11 default background
Cursor string `json:"cursor,omitempty"` // OSC 12 cursor colour
}
GuestColorState is vt.GuestColors on the wire, each colour encoded by colorToWire. Palette lists only the slots the guest set.
type History ¶
type History struct {
// contains filtered or unexported fields
}
History is a pane's history as read from its emulator for saving, before it is packed. The screen rows are copies cut to their used cells. The history rows are a copy of the emulator's encoded lines, a byte or so a cell, which state decodes a row at a time after the emulator's lock is released.
They were decoded under the lock into rows of 112-byte cells: 1000 rows of a 200-column pane were 26 MB and 12 ms of a pane's output held up, and a save of 20 busy panes allocated 562 MB.
func CaptureHistory ¶
CaptureHistory reads the main screen and up to lines history rows, for a history saved to disk. The caller holds the emulator's lock for this call only. See History.State for the rest.
A full-screen program on the alternate screen at the moment of the save is left out. Its screen is gone the moment the program is, and what the user had before it, the shell's screen, is the main one.
type PaletteColorState ¶
type PaletteColorState struct {
Index int `json:"i"` // palette slot, 0 to 255
Color string `json:"c"` // colour, encoded by colorToWire
}
PaletteColorState is one palette slot the guest set.
type SavedCursorState ¶
type SavedCursorState struct {
X int `json:"x,omitempty"` // cursor column, zero-based
Y int `json:"y,omitempty"` // cursor row, zero-based
Pen *StyleState `json:"pen,omitempty"` // rendition and hyperlink
PendingWrap bool `json:"pending_wrap,omitempty"` // wrap pending at the last column
Origin bool `json:"origin,omitempty"` // origin mode (DECOM)
Protected bool `json:"protected,omitempty"` // DECSCA protection
// Charsets is the character set selection DECSC saved, in the layout of
// State.Charsets.
Charsets []int `json:"charsets,omitempty"`
}
SavedCursorState is a saved cursor on the wire (vt.SavedCursor).
type State ¶
type State struct {
// Seq is the stream position this snapshot was taken at: the emulator here
// has consumed exactly the first Seq bytes the pane ever produced. A client
// restoring this state subscribes from Seq, so it receives what came after
// the snapshot and not what the snapshot already shows.
Seq int64 `json:"seq,omitempty"`
Width int `json:"width"` // screen width in cells
Height int `json:"height"` // screen height in cells
CursorX int `json:"cursor_x"` // cursor column, zero-based
CursorY int `json:"cursor_y"` // cursor row, zero-based
// PendingWrap says the guest has just printed into the last column and
// the cursor waits there with a wrap pending, so the next character it
// prints starts the next row. Without it a snapshot taken at that moment
// restored a cursor that printed over the last cell, and everything the
// stream delivered after the snapshot landed one column out. Absent from
// an older daemon, which reads as no wrap pending, as before.
PendingWrap bool `json:"pending_wrap,omitempty"`
// ScrollbackLen is the true length of the history, which can be more than
// the Scrollback rows carried.
ScrollbackLen int `json:"scrollback_len"`
IsAltScreen bool `json:"is_alt_screen,omitempty"` // Alternate screen buffer active (for mouse event forwarding)
Pen *StyleState `json:"pen,omitempty"` // Graphic rendition in force: what the guest's next output is painted with
// Margins is the scroll region, as x, y, width, height. A guest sets it
// once to hold a header or a status line out of the scrolling part of the
// screen.
Margins []int `json:"margins,omitempty"`
// Charsets names the character set selected into each of G0 to G3 by its
// designator byte, followed by the GL and GR slots. A program that draws
// boxes selects the DEC line-drawing set once and then sends the box
// characters as plain letters.
Charsets []int `json:"charsets,omitempty"`
// CursorShape is the shape the guest asked for, as the DECSCUSR parameter
// that asks for it: 1 blinking block, 2 steady block, and so on up to 6 for
// a steady bar. The guest's own spelling rather than a pair of fields
// because zero then means "this snapshot does not say", which is what a
// client restoring from an older daemon gets, and it leaves the pane on the
// default instead of forcing a blinking block onto it.
CursorShape int `json:"cursor_shape,omitempty"`
Modes map[int]bool `json:"modes,omitempty"` // Terminal modes (mouse tracking, bracketed paste, etc.)
KittyKbdStack []int `json:"kitty_kbd_stack,omitempty"` // Kitty keyboard protocol flag stack, base entry first
// KittyKbdMainStack is the main screen's kitty keyboard flag stack,
// carried only while the alternate screen is active, when KittyKbdStack
// is the alternate screen's.
KittyKbdMainStack []int `json:"kitty_kbd_main_stack,omitempty"`
// protocol, because its PTY is a Windows ConPTY. A client applies it to
// its own emulator, so a guest that pushes flags without a query does not
// switch the client to CSI u keys. False from an older daemon. See
// vt.Emulator.DisableKittyKeyboardProtocol.
KittyKbdUnavailable bool `json:"kitty_kbd_unavailable,omitempty"`
// KittyImages is the ids the daemon gave the guest's kitty image numbers
// (I=), and which ids hold an image. A client restoring this gives the
// same ids from here on, so an image the guest places by the id it was
// told is the same image on every client. Nil from an older daemon.
KittyImages *vt.KittyGuestImagesState `json:"kitty_images,omitempty"`
// ModifyOtherKeys is the xterm modifyOtherKeys level the guest set with
// XTMODKEYS, 0 to 2. ModifyOtherKeysKnown says the daemon reported it:
// a level of 0 is then the guest turning it off, which a client emulator
// that kept an older level has to take, and not an older daemon that
// says nothing.
ModifyOtherKeys int `json:"modify_other_keys,omitempty"`
ModifyOtherKeysKnown bool `json:"modify_other_keys_known,omitempty"` // see ModifyOtherKeys
// Screen is the active screen, row by row.
Screen [][]CellState `json:"screen"`
// Scrollback is the newest history rows the snapshot carries, oldest first.
Scrollback [][]CellState `json:"scrollback,omitempty"`
// MainScreen is the normal screen, carried only while the alternate one is
// active. It is the shell's screen underneath a full-screen program, which
// quitting that program puts back on display. The alternate screen needs no
// such treatment: entering it clears it, so what it held before is never
// seen again.
MainScreen [][]CellState `json:"main_screen,omitempty"`
// The three grids above in packed form, sent instead of them when the
// request asked (GetTerminalStatePayload.Packed). Styles is the table the
// packed cells index, and is never empty when the cells are packed. See
// snapshot_pack.go for the layout and why it exists.
Styles []StyleState `json:"styles,omitempty"`
PackedScreen []byte `json:"packed_screen,omitempty"` // Screen, packed
PackedScrollback []byte `json:"packed_scrollback,omitempty"` // Scrollback, packed
PackedMain []byte `json:"packed_main,omitempty"` // MainScreen, packed
// ScreenWraps and ScrollbackWraps are the soft-wrap flags, one bit per
// row (bit i of byte i/8, low bit first): the active screen's rows, and
// the scrollback rows this snapshot carries, in the order it carries
// them. A set bit says the row carries on to the next one because the
// emulator wrapped it, where a row that is merely full ended with a
// newline. A peer from before these fields sends neither, which reads as
// no row wrapped: the safe answer, since a wrongly joined row glues two
// lines into one link. See vt.Terminal.RestoreSoftWraps.
ScreenWraps []byte `json:"screen_wraps,omitempty"`
ScrollbackWraps []byte `json:"scrollback_wraps,omitempty"` // soft-wrap flags of the Scrollback rows
// ScreenPads and ScrollbackPads are, in the same layout, the rows that
// wrapped a column early because a double-width character did not fit
// in the last column, so that column is padding, not a typed space. A
// reflow drops it when it joins the row to the next; without the flag
// it keeps it, and the client and the daemon then lay the line out
// apart. A peer from before these fields sends neither, which reads as
// no padding: the line keeps a blank, and no text is lost.
ScreenPads []byte `json:"screen_pads,omitempty"`
ScrollbackPads []byte `json:"scrollback_pads,omitempty"` // padding flags of the Scrollback rows
// PenProtected says DECSCA protects what the guest prints next from a
// selective erase (DECSED, DECSEL). Protected lists the cells of the
// active screen it protected, as runs of three ints: row, column and
// length. MainProtected is the same for the screen under an active
// alternate one. A client without them erased protected cells the next
// time the guest sent a selective erase. A peer from before these fields
// sends none of them, which reads as nothing protected.
PenProtected bool `json:"pen_protected,omitempty"`
Protected []int `json:"protected,omitempty"` // protected runs of the active screen
MainProtected []int `json:"main_protected,omitempty"` // protected runs of the main screen under the alternate one
// LastPrinted is the character REP (CSI b) repeats, as the guest sent
// it. LastPrintedKnown says the daemon reported it, so an empty one
// means nothing has been printed since a reset, and not an older daemon
// that says nothing.
LastPrinted string `json:"last_printed,omitempty"`
LastPrintedKnown bool `json:"last_printed_known,omitempty"` // see LastPrinted
// SavedCursor is what DECSC saved on the active screen, which DECRC puts
// back. MainSavedCursor is the main screen's, carried while the
// alternate one is active: entering it with 1049 saved the shell's
// cursor there, and leaving puts it back. A peer from before these
// fields sends neither, and the client keeps the saved cursor it has.
SavedCursor *SavedCursorState `json:"saved_cursor,omitempty"`
MainSavedCursor *SavedCursorState `json:"main_saved_cursor,omitempty"` // see SavedCursor
// TabStops is the tab stop table, one bit per column in the layout of
// ScreenWraps. It is never empty from a daemon that reports it, even
// with every stop cleared, so a peer from before the field, which
// sends none, reads as saying nothing, and the client keeps its own.
TabStops []byte `json:"tab_stops,omitempty"`
// Title, IconName and TitleStack are the window title, the icon name
// and what XTWINOPS 22 saved, oldest first. A program saves the shell's
// title when it starts and puts it back when it quits, so a client
// restored without the stack keeps the program's title after it has
// gone. TitlesKnown says the daemon reported them, so an empty title is
// one the guest set and not an older daemon that says nothing.
Title string `json:"title,omitempty"`
IconName string `json:"icon_name,omitempty"` // see Title
TitleStack []TitleEntryState `json:"title_stack,omitempty"` // see Title
TitlesKnown bool `json:"titles_known,omitempty"` // see Title
// GuestColors is what the guest set with OSC 4, 10, 11 and 12. A palette
// entry decides how the guest's next SGR in that slot is painted, and
// the three defaults answer its queries. Nil from an older daemon, and
// the client keeps its own.
GuestColors *GuestColorState `json:"guest_colors,omitempty"`
// ANSIModes is insert mode (4) and newline mode (20), by number, which
// Modes cannot carry: the two kinds of mode share its numbers. Nil from
// an older daemon, and the client keeps its own.
ANSIModes map[int]bool `json:"ansi_modes,omitempty"`
// PendingInput is the input the daemon's parser is part way through
// when the snapshot is taken: the start of an escape sequence or of a
// UTF-8 character, at most vt.MaxPendingInput bytes. The rest of it
// arrives on the stream after Seq, and a client at the ground state
// would print that rest as text. See vt.Terminal.PendingInput.
PendingInput []byte `json:"pending_input,omitempty"`
}
State represents the serializable state of a terminal.
func Capture ¶
Capture serializes everything a client needs to arrive at the picture this emulator holds. It is one half of the wire contract, and Apply is the other. docs/REHYDRATION.md in the tuios repository states the contract.
Width and height are the pane's, which the caller knows. The emulator's own size lags a resize the program has not acknowledged yet.
maxScrollback bounds the scrollback rows the state carries: zero means DefaultScrollback and a negative number means none. The rows carried are the newest ones. have is how many scrollback rows the receiving emulator already holds. Only the rows past it are serialized, because only those can be used. ScrollbackLen in the state is always the true length.
func CapturePacked ¶
CapturePacked is Capture with the cells packed as they are read: the result is the same as Capture followed by Pack, without building every cell first. It returns in two halves. It reads the emulator and returns the state with finish, which completes it without reading the emulator again, so a caller can release the emulator's lock before it calls finish. The state is complete only once finish has run.
func (*State) CheckPacked ¶
CheckPacked reports whether every packed grid decodes, without keeping the cells. A client runs it on receipt, so a malformed snapshot fails the request instead of being applied up to its first bad byte. A state that is not packed passes.
func (*State) Pack ¶
func (st *State) Pack()
Pack moves the snapshot's cells into their packed form, if they are not there already. Only the cell grids move; every scalar field stays where the older reader expects it.
type StyleState ¶
type StyleState struct {
FgColor string `json:"fg,omitempty"` // Foreground color, encoded by colorToWire
BgColor string `json:"bg,omitempty"` // Background color
UlColor string `json:"uc,omitempty"` // Underline color (SGR 58)
Attrs uint8 `json:"a,omitempty"` // uv.Attr* bitmask: bold, faint, italic, blink, reverse, conceal, strikethrough
Underline uint8 `json:"u,omitempty"` // ansi.Underline style: none, single, double, curly, dotted, dashed
LinkURL string `json:"l,omitempty"` // OSC 8 hyperlink target
LinkParams string `json:"lp,omitempty"` // OSC 8 hyperlink parameters
}
StyleState is a graphic rendition on the wire: how something is painted, separate from what it holds. It describes both a cell and the pen, which are the same rendition seen at two moments.
type TitleEntryState ¶
type TitleEntryState struct {
Title string `json:"title,omitempty"` // saved window title
Icon string `json:"icon,omitempty"` // saved icon name
HasTitle bool `json:"has_title,omitempty"` // whether the entry holds a title
HasIcon bool `json:"has_icon,omitempty"` // whether the entry holds an icon name
}
TitleEntryState is one entry of the title stack on the wire (vt.TitleEntry).