v0.5.1 8e4df5c

main.go RawHistoryFolder

1// Command graphics-harness renders Kitty graphics terminal input to a PNG for
2// local development inspection. It is not a terminal emulator.
3package main
4
5import (
6	"flag"
7	"fmt"
8	"image"
9	"image/png"
10	"os"
11
12	vt "github.com/bnema/vev-vt"
13	"github.com/bnema/vev-vt/internal/graphicsharness"
14)
15
16func main() {
17	inputPath := flag.String("input", "", "path to a file containing raw terminal bytes")
18	outputPath := flag.String("output", "graphics-harness.png", "path for the rendered PNG")
19	cols := flag.Int("cols", 80, "terminal column count")
20	rows := flag.Int("rows", 24, "terminal row count")
21	pixelWidth := flag.Int("pixel-width", 640, "terminal viewport width in pixels")
22	pixelHeight := flag.Int("pixel-height", 384, "terminal viewport height in pixels")
23	scale := flag.Int("scale", 1, "nearest-neighbor output scale for inspection")
24	flag.Parse()
25
26	if *inputPath == "" {
27		fail("-input is required")
28	}
29	input, err := os.ReadFile(*inputPath)
30	if err != nil {
31		fail("read input: %v", err)
32	}
33	frame, err := graphicsharness.RenderTerminal(input, vt.Geometry{
34		Cols: *cols, Rows: *rows, PixelWidth: *pixelWidth, PixelHeight: *pixelHeight,
35	})
36	if err != nil {
37		fail("render input: %v", err)
38	}
39	frame, err = scaleFrame(frame, *scale)
40	if err != nil {
41		fail("scale output: %v", err)
42	}
43	output, err := os.Create(*outputPath)
44	if err != nil {
45		fail("create output: %v", err)
46	}
47	if err := png.Encode(output, frame); err != nil {
48		_ = output.Close()
49		fail("encode output: %v", err)
50	}
51	if err := output.Close(); err != nil {
52		fail("close output: %v", err)
53	}
54}
55
56func scaleFrame(source *image.RGBA, scale int) (*image.RGBA, error) {
57	if scale < 1 {
58		return nil, fmt.Errorf("scale must be positive")
59	}
60	width, height := source.Bounds().Dx(), source.Bounds().Dy()
61	maxInt := int(^uint(0) >> 1)
62	if width > 0 && scale > maxInt/width {
63		return nil, fmt.Errorf("scaled frame exceeds 67108864 pixels")
64	}
65	if height > 0 && scale > maxInt/height {
66		return nil, fmt.Errorf("scaled frame exceeds 67108864 pixels")
67	}
68	scaledWidth, scaledHeight := width*scale, height*scale
69	const maxScaledPixels = uint64(64 << 20)
70	if scaledWidth > 0 && uint64(scaledHeight) > maxScaledPixels/uint64(scaledWidth) {
71		return nil, fmt.Errorf("scaled frame exceeds 67108864 pixels")
72	}
73	result := image.NewRGBA(image.Rect(0, 0, scaledWidth, scaledHeight))
74	for y := range height {
75		for x := range width {
76			pixel := source.RGBAAt(x, y)
77			for dy := range scale {
78				for dx := range scale {
79					result.SetRGBA(x*scale+dx, y*scale+dy, pixel)
80				}
81			}
82		}
83	}
84	return result, nil
85}
86
87func fail(format string, args ...any) {
88	fmt.Fprintf(os.Stderr, "graphics-harness: "+format+"\n", args...)
89	os.Exit(1)
90}

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