main ab6686c

README.md RawHistoryFolder

vev-vt

A Go library for reading terminal output and keeping track of what should appear on screen. It powers vev.

Feed it the bytes from a shell or command. It handles text, colors, cursor movement, scrolling and resizing. You can then read the screen, save its history, or use the ansi package to draw it in a terminal. The html package prepares typed browser updates, and html/browser provides a safe interactive DOM adapter without owning transport or terminal-input policy.

This library does not start processes or manage a PTY. Your application does that and passes the output to vev-vt.

Install

Requires Go 1.27 or newer.

go get github.com/bnema/vev-vt

Alpha software: APIs and saved-history formats can change between versions. The current history format does not read older VTH3 data.

Read terminal output

package main

import (
    "fmt"

    vt "github.com/bnema/vev-vt"
)

func main() {
    screen := vt.NewScreen(80, 24)
    screen.Write([]byte("Hello, \x1b[31mworld\x1b[0m!"))

    // Colors and escape sequences are processed, not included in the text.
    for x := 0; x < 13; x++ {
        fmt.Printf("%c", screen.Cell(x, 0).Rune)
    }
    fmt.Println() // Hello, world!
}

Use screen.Resize(columns, rows) when the terminal size changes. Use screen.Snapshot() when you need a copy that stays unchanged as new output arrives.

Keep scrollback

Scrollback is the text that has moved above the visible screen.

// Your application chooses these limits; vev-vt has no default budget.
config := vt.HistoryConfig{
    MaxBytes: 20_000_000, // At most 20 MB of history data.
    MaxRows:  5_000,      // Optional: also keep at most 5,000 lines.
}
screen := vt.NewScreenWithHistory(80, 24, config)

vev-vt enforces the limits your application supplies. The PTY transports bytes; it does not store scrollback. The oldest lines are removed when a supplied limit is reached. Limits apply to each screen separately and exclude the visible screen. The byte limit measures uncompressed history data, not total process memory.

History guide →: limit settings, saving/restoring history and optional compression during idle time.

A few rules

  • Serialize Write, Resize, Snapshot, reads and history mutations in one goroutine, or protect them with your own lock.
  • Rows returned by the API are copies. Changing one does not change the screen.
  • Use vt.DefaultStyle() for terminal-default colors, not vt.Style{}.
  • Callbacks run during screen.Write; keep them short.

API ownership and styles →

Packages

Package Use it for
vev-vt Parse terminal output, read the screen and manage history.
vev-vt/core Work with cells, styles and writable grids.
vev-vt/ansi Render a screen or grid as ANSI terminal output.
vev-vt/html Prepare transactional browser updates, structural CSS and terminal themes.
vev-vt/html/browser Embed the DOM runtime and decode neutral browser events.
vev-vt/graphics Read supported terminal images and their positions.

Supported image features →

HTML frontend

The HTML renderer compares every row with its committed shadow; damage values are non-authoritative hints. Prepare permits one outstanding draw and requires an explicit Commit or Abort. Reset invalidates retained prepared draws. Updates are immutable JSON-compatible values at schema version 2; browser events share that version, so a host must serve the matching terminal.js. Complete-row replacement preserves wide-cell atomicity, and scroll damage uses a safe snapshot fallback. Rows are encoded as text runs: adjacent printable ASCII cells with one style form a single entry, while wide and non-ASCII characters stay single entries so column alignment never depends on fallback fonts. html.DefaultLimits() documents the default 1,000,000-cell, 10,000-row, 64 MiB generated-update, and 65,536-style bounds.

The browser adapter builds DOM nodes with textContent and fixed classes. It provides a labeled input proxy, synchronized plain-text accessible output, typed CSS themes, IME-aware text input, keys, paste, pointer, wheel, resize, and focus events. A synchronous consumer callback decides default prevention. applyAll validates a queue of updates and merges them before touching the DOM, so a consumer that falls behind rebuilds each changed row once. Text runs assume a monospace primary font whose ASCII advance is 1ch, and maxTextBytes bounds one run, so keep it at least as large as the widest row. Consumers remain responsible for transport and mapping events to terminal bytes or application actions. Clipboard text is preserved unchanged, including control bytes, so consumers forwarding paste events to a PTY must apply their required framing or filtering policy. browser.DefaultEventLimits() documents the default 8 MiB event, 64 KiB text, 1 MiB paste, and 10,000×10,000 geometry bounds.

renderer, err := html.New(html.Options{})
if err != nil {
    return err
}
prepared, err := renderer.Prepare(frame, damage, reset, html.Cursor{
    Row: cursorRow, Column: cursorColumn, Visible: true,
})
if err != nil {
    return err
}
if err := send(prepared.JSON()); err != nil {
    _ = prepared.Abort()
    return err
}
return prepared.Commit()

Embed or serve html.Stylesheet() and browser.JavaScript() from a consumer-owned application. The runtime supports a self-only style-src and script-src CSP in the pinned browser harness; dynamic colors are set through validated numeric DOM style properties. The current core model drops combining marks and does not coalesce ZWJ sequences, so rendered output inherits those limits even though browser IME input remains composition-aware.

Development

go test ./...
go test ./... -race
go vet ./...
npm ci
npm run test:browser:install
npm run test:browser
# Reproducible three-engine fallback on unsupported Linux hosts:
npm run test:browser:docker

TestDifferentialOracle replays seeded terminal workloads through the public VT, history, graphics, ANSI and HTML APIs and compares one digest per layer with testdata/oracle/golden.txt. Keep it unchanged for refactors and performance work. For a deliberate behavior change, regenerate it on a full run with go test -run TestDifferentialOracle -oracle.update . and explain the changed columns. To locate a divergence, run -oracle.dump=DIR on both trees and diff the scenario transcripts.

Playwright 1.62.1 and its Chromium, Firefox, and WebKit revisions are pinned by package-lock.json. The matching Playwright container provides the reproducible fallback when host libraries cannot run one of those browser builds. Production Go packages retain the module's standard-library-only dependency boundary apart from vev-vt/core.

Storage benchmarks and design decisions →

Measuring and profiling performance changes →

source
1# vev-vt
2
3A Go library for reading terminal output and keeping track of what should appear
4on screen. It powers [vev](https://github.com/bnema/vev).
5
6Feed it the bytes from a shell or command. It handles text, colors, cursor
7movement, scrolling and resizing. You can then read the screen, save its history,
8or use the `ansi` package to draw it in a terminal.
9The `html` package prepares typed browser updates, and `html/browser` provides a
10safe interactive DOM adapter without owning transport or terminal-input policy.
11
12This library does **not** start processes or manage a PTY. Your application does
13that and passes the output to vev-vt.
14
15## Install
16
17Requires Go 1.27 or newer.
18
19```sh
20go get github.com/bnema/vev-vt
21```
22
23**Alpha software:** APIs and saved-history formats can change between versions.
24The current history format does not read older VTH3 data.
25
26## Read terminal output
27
28```go
29package main
30
31import (
32    "fmt"
33
34    vt "github.com/bnema/vev-vt"
35)
36
37func main() {
38    screen := vt.NewScreen(80, 24)
39    screen.Write([]byte("Hello, \x1b[31mworld\x1b[0m!"))
40
41    // Colors and escape sequences are processed, not included in the text.
42    for x := 0; x < 13; x++ {
43        fmt.Printf("%c", screen.Cell(x, 0).Rune)
44    }
45    fmt.Println() // Hello, world!
46}
47```
48
49Use `screen.Resize(columns, rows)` when the terminal size changes.
50Use `screen.Snapshot()` when you need a copy that stays unchanged as new output
51arrives.
52
53## Keep scrollback
54
55Scrollback is the text that has moved above the visible screen.
56
57```go
58// Your application chooses these limits; vev-vt has no default budget.
59config := vt.HistoryConfig{
60    MaxBytes: 20_000_000, // At most 20 MB of history data.
61    MaxRows:  5_000,      // Optional: also keep at most 5,000 lines.
62}
63screen := vt.NewScreenWithHistory(80, 24, config)
64```
65
66vev-vt enforces the limits your application supplies. The PTY transports bytes;
67it does not store scrollback. The oldest lines are removed when a supplied limit
68is reached. Limits apply to each
69screen separately and exclude the visible screen. The byte limit measures
70uncompressed history data, **not total process memory**.
71
72[History guide →](docs/history.md): limit settings, saving/restoring history and
73optional compression during idle time.
74
75## A few rules
76
77- Serialize `Write`, `Resize`, `Snapshot`, reads and history mutations in one
78  goroutine, or protect them with your own lock.
79- Rows returned by the API are copies. Changing one does not change the screen.
80- Use `vt.DefaultStyle()` for terminal-default colors, not `vt.Style{}`.
81- Callbacks run during `screen.Write`; keep them short.
82
83[API ownership and styles →](docs/ownership.md)
84
85## Packages
86
87| Package | Use it for |
88| --- | --- |
89| `vev-vt` | Parse terminal output, read the screen and manage history. |
90| `vev-vt/core` | Work with cells, styles and writable grids. |
91| `vev-vt/ansi` | Render a screen or grid as ANSI terminal output. |
92| `vev-vt/html` | Prepare transactional browser updates, structural CSS and terminal themes. |
93| `vev-vt/html/browser` | Embed the DOM runtime and decode neutral browser events. |
94| `vev-vt/graphics` | Read supported terminal images and their positions. |
95
96[Supported image features →](docs/graphics.md)
97
98## HTML frontend
99
100The HTML renderer compares every row with its committed shadow; damage values are
101non-authoritative hints. `Prepare` permits one outstanding draw and requires an
102explicit `Commit` or `Abort`. `Reset` invalidates retained prepared draws.
103Updates are immutable JSON-compatible values at schema version 2; browser
104events share that version, so a host must serve the matching `terminal.js`.
105Complete-row
106replacement preserves wide-cell atomicity, and scroll damage uses a safe
107snapshot fallback. Rows are encoded as text runs: adjacent printable ASCII
108cells with one style form a single entry, while wide and non-ASCII characters
109stay single entries so column alignment never depends on fallback fonts. `html.DefaultLimits()` documents the default 1,000,000-cell,
11010,000-row, 64 MiB generated-update, and 65,536-style bounds.
111
112The browser adapter builds DOM nodes with `textContent` and fixed classes. It
113provides a labeled input proxy, synchronized plain-text accessible output,
114typed CSS themes, IME-aware text input, keys, paste, pointer, wheel, resize, and
115focus events. A synchronous consumer callback decides default prevention.
116`applyAll` validates a queue of updates and merges them before touching the
117DOM, so a consumer that falls behind rebuilds each changed row once. Text runs
118assume a monospace primary font whose ASCII advance is `1ch`, and
119`maxTextBytes` bounds one run, so keep it at least as large as the widest row.
120Consumers remain responsible for transport and mapping events to terminal bytes
121or application actions. Clipboard text is preserved unchanged, including
122control bytes, so consumers forwarding paste events to a PTY must apply their
123required framing or filtering policy. `browser.DefaultEventLimits()` documents
124the default 8 MiB event, 64 KiB text, 1 MiB paste, and 10,000×10,000 geometry
125bounds.
126
127```go
128renderer, err := html.New(html.Options{})
129if err != nil {
130    return err
131}
132prepared, err := renderer.Prepare(frame, damage, reset, html.Cursor{
133    Row: cursorRow, Column: cursorColumn, Visible: true,
134})
135if err != nil {
136    return err
137}
138if err := send(prepared.JSON()); err != nil {
139    _ = prepared.Abort()
140    return err
141}
142return prepared.Commit()
143```
144
145Embed or serve `html.Stylesheet()` and `browser.JavaScript()` from a
146consumer-owned application. The runtime supports a self-only `style-src` and
147`script-src` CSP in the pinned browser harness; dynamic colors are set through
148validated numeric DOM style properties. The current core model drops combining
149marks and does not coalesce ZWJ sequences, so rendered output inherits those
150limits even though browser IME input remains composition-aware.
151
152## Development
153
154```sh
155go test ./...
156go test ./... -race
157go vet ./...
158npm ci
159npm run test:browser:install
160npm run test:browser
161# Reproducible three-engine fallback on unsupported Linux hosts:
162npm run test:browser:docker
163```
164
165`TestDifferentialOracle` replays seeded terminal workloads through the public
166VT, history, graphics, ANSI and HTML APIs and compares one digest per layer with
167`testdata/oracle/golden.txt`. Keep it unchanged for refactors and performance
168work. For a deliberate behavior change, regenerate it on a full run with
169`go test -run TestDifferentialOracle -oracle.update .` and explain the changed
170columns. To locate a divergence, run `-oracle.dump=DIR` on both trees and diff
171the scenario transcripts.
172
173Playwright 1.62.1 and its Chromium, Firefox, and WebKit revisions are pinned by
174`package-lock.json`. The matching Playwright container provides the reproducible
175fallback when host libraries cannot run one of those browser builds. Production
176Go packages retain the module's standard-library-only dependency boundary apart
177from `vev-vt/core`.
178
179[Storage benchmarks and design decisions →](docs/storage-optimization.md)
180
181[Measuring and profiling performance changes →](docs/performance.md)

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 13

↑↓ moveenter openesc close