v0.1.0 7deb448

damage_plan.go RawHistoryFolder

1package renderer
2
3import (
4	"cmp"
5	"slices"
6)
7
8const maxPlannedDamageSpans = 4096
9
10type damageSpan struct {
11	y, x, width int
12}
13
14// buildDamagePlan creates the bounded, canonical view of text and clear damage
15// used for terminal emission. The source damage remains untouched because the
16// scroll and shadow paths need its original structural information.
17func buildDamagePlan(frame Frame, damage []Damage, skip *Damage) ([]damageSpan, bool) {
18	spans := make([]damageSpan, 0)
19	for _, d := range damage {
20		if skip != nil && sameDamage(d, *skip) {
21			continue
22		}
23		if d.Kind != DamageText && d.Kind != DamageClear {
24			continue
25		}
26		x, y, width, height, ok := clampRect(frame, d.X, d.Y, d.Width, d.Height)
27		if !ok {
28			continue
29		}
30		for row := y; row < y+height; row++ {
31			if len(spans) == maxPlannedDamageSpans {
32				return nil, true
33			}
34			spans = append(spans, damageSpan{y: row, x: x, width: width})
35		}
36	}
37
38	slices.SortFunc(spans, func(a, b damageSpan) int {
39		if c := cmp.Compare(a.y, b.y); c != 0 {
40			return c
41		}
42		return cmp.Compare(a.x, b.x)
43	})
44
45	return mergeDamageSpans(spans), false
46}
47
48func mergeDamageSpans(spans []damageSpan) []damageSpan {
49	if len(spans) < 2 {
50		return spans
51	}
52
53	out := spans[:0]
54	for _, span := range spans {
55		if len(out) == 0 {
56			out = append(out, span)
57			continue
58		}
59		last := &out[len(out)-1]
60		// Both endpoints are bounded by frame.Width after clampRect, so these
61		// additions cannot overflow. The comparison includes adjacency.
62		lastEnd := last.x + last.width
63		spanEnd := span.x + span.width
64		if span.y != last.y || span.x > lastEnd {
65			out = append(out, span)
66			continue
67		}
68		if spanEnd > lastEnd {
69			last.width = spanEnd - last.x
70		}
71	}
72	return out
73}

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 12

Tags 11

↑↓ moveenter openesc close