v0.7.1 ff50ee4

delta_plan_test.go RawHistoryFolder

1package ansi
2
3import (
4	"reflect"
5	"testing"
6)
7
8func testFrame(rows ...string) Frame {
9	frame := NewFrame(len([]rune(rows[0])), len(rows))
10	fillFrame(frame, rows)
11	return frame
12}
13
14func TestPlanDelta(t *testing.T) {
15	scrollCommitted := testFrame("0000", "1111", "2222", "3333", "4444", "5555", "6666", "7777")
16	scrolled := testFrame("0000", "2222", "3333", "4444", "5555", "6666", "new!", "7777")
17	largeFrame := NewFrame(1, maxPlannedDamageSpans+1)
18	styledCommitted := NewFrame(10, 1)
19	styledCommitted.Set(3, 0, Cell{Rune: ' ', Style: Style{Bold: true, Foreground: -1, Background: -1}})
20	styledFrame := styledCommitted.Clone()
21	styledFrame.Set(0, 0, Cell{Rune: 'a', Style: DefaultStyle()})
22	styledFrame.Set(9, 0, Cell{Rune: 'z', Style: DefaultStyle()})
23
24	tests := []struct {
25		name      string
26		frame     Frame
27		damage    []Damage
28		committed Frame
29		reset     bool
30		want      DeltaPlan
31	}{
32		{
33			name:      "reset snapshots",
34			frame:     NewFrame(4, 2),
35			committed: NewFrame(4, 2),
36			reset:     true,
37			want:      DeltaPlan{Snapshot: true},
38		},
39		{
40			name:      "dimension mismatch snapshots",
41			frame:     NewFrame(4, 2),
42			committed: NewFrame(3, 2),
43			want:      DeltaPlan{Snapshot: true},
44		},
45		{
46			name:      "unchanged same size has no spans",
47			frame:     NewFrame(4, 2),
48			committed: NewFrame(4, 2),
49			want:      DeltaPlan{},
50		},
51		{
52			name:      "unchanged full redraw has no spans",
53			frame:     NewFrame(4, 2),
54			damage:    []Damage{FullRedraw()},
55			committed: NewFrame(4, 2),
56			want:      DeltaPlan{},
57		},
58		{
59			name:      "changed row without damage becomes a full width span",
60			frame:     testFrame("    ", "edit", "    "),
61			committed: NewFrame(4, 3),
62			want:      DeltaPlan{Spans: []Span{{Y: 1, X: 0, Width: 4}}},
63		},
64		{
65			name:  "adjacent and overlapping damage is ordered and merged",
66			frame: testFrame("          ", "aaaaaaaaa ", "          "),
67			damage: []Damage{
68				{Kind: DamageText, X: 4, Y: 1, Width: 5, Height: 1},
69				{Kind: DamageClear, X: 0, Y: 1, Width: 5, Height: 1},
70				{Kind: DamageText, X: 2, Y: 1, Width: 2, Height: 1},
71			},
72			committed: NewFrame(10, 3),
73			want:      DeltaPlan{Spans: []Span{{Y: 1, X: 0, Width: 9}}},
74		},
75		{
76			name:  "safe full width scroll retains exposed span",
77			frame: scrolled,
78			damage: []Damage{
79				{Kind: DamageScrollUp, X: 0, Y: 1, Width: 4, Height: 6, Count: 1},
80				{Kind: DamageText, X: 0, Y: 6, Width: 4, Height: 1},
81			},
82			committed: scrollCommitted,
83			want: DeltaPlan{
84				Scroll: Scroll{Y: 1, Height: 6, Count: 1},
85				Spans:  []Span{{Y: 6, X: 0, Width: 4}},
86			},
87		},
88		{
89			name:  "safe full width scroll with expensive delta snapshots",
90			frame: testFrame("1111", "new!"),
91			damage: []Damage{
92				{Kind: DamageScrollUp, X: 0, Y: 0, Width: 4, Height: 2, Count: 1},
93				{Kind: DamageText, X: 0, Y: 0, Width: 4, Height: 2},
94			},
95			committed: testFrame("0000", "1111"),
96			want:      DeltaPlan{Snapshot: true},
97		},
98		{
99			name:  "rectangular scroll snapshots",
100			frame: scrolled,
101			damage: []Damage{
102				{Kind: DamageScrollUp, X: 1, Y: 1, Width: 3, Height: 6, Count: 1},
103			},
104			committed: scrollCommitted,
105			want:      DeltaPlan{Snapshot: true},
106		},
107		{
108			name:  "unsafe scroll content snapshots",
109			frame: testFrame("0000", "2222", "xxxx", "4444", "5555", "6666", "new!", "7777"),
110			damage: []Damage{
111				{Kind: DamageScrollUp, X: 0, Y: 1, Width: 4, Height: 6, Count: 1},
112				{Kind: DamageText, X: 0, Y: 6, Width: 4, Height: 1},
113			},
114			committed: scrollCommitted,
115			want:      DeltaPlan{Snapshot: true},
116		},
117		{
118			name:      "more than 4096 canonical spans snapshots",
119			frame:     largeFrame,
120			damage:    []Damage{{Kind: DamageText, X: 0, Y: 0, Width: 1, Height: maxPlannedDamageSpans + 1}},
121			committed: NewFrame(1, maxPlannedDamageSpans+1),
122			want:      DeltaPlan{Snapshot: true},
123		},
124		{
125			name:      "delta cost equal to snapshot cost snapshots",
126			frame:     testFrame("changed!"),
127			damage:    []Damage{{Kind: DamageText, X: 0, Y: 0, Width: 8, Height: 1}},
128			committed: NewFrame(8, 1),
129			want:      DeltaPlan{Snapshot: true, Spans: []Span{{Y: 0, X: 0, Width: 8}}},
130		},
131		{
132			name:      "single broad damage retains spans for snapshot encoding",
133			frame:     testFrame("aaaa", "bbbb"),
134			damage:    []Damage{{Kind: DamageText, X: 0, Y: 0, Width: 4, Height: 2}},
135			committed: NewFrame(4, 2),
136			want: DeltaPlan{
137				Snapshot: true,
138				Spans:    []Span{{Y: 0, X: 0, Width: 4}, {Y: 1, X: 0, Width: 4}},
139			},
140		},
141		{
142			name:  "snapshot cost includes style runs",
143			frame: styledFrame,
144			damage: []Damage{
145				{Kind: DamageText, X: 0, Y: 0, Width: 1, Height: 1},
146				{Kind: DamageText, X: 9, Y: 0, Width: 1, Height: 1},
147			},
148			committed: styledCommitted,
149			want:      DeltaPlan{Spans: []Span{{Y: 0, X: 0, Width: 1}, {Y: 0, X: 9, Width: 1}}},
150		},
151	}
152
153	for _, tt := range tests {
154		t.Run(tt.name, func(t *testing.T) {
155			candidate, err := PlanDelta(tt.frame, tt.damage, tt.committed, tt.reset)
156			if err != nil {
157				t.Fatalf("PlanDelta() error = %v", err)
158			}
159			if !reflect.DeepEqual(candidate.Plan, tt.want) {
160				t.Fatalf("PlanDelta() = %#v, want %#v", candidate.Plan, tt.want)
161			}
162		})
163	}
164}
165
166func TestNoOpDeltaCandidateDoesNotCloneFrame(t *testing.T) {
167	frame := testFrame("abcd", "efgh")
168	candidate, err := PlanDelta(frame, nil, frame, false)
169	if err != nil {
170		t.Fatalf("PlanDelta() error = %v", err)
171	}
172	if candidate.frame.Width != 0 || candidate.frame.Height != 0 {
173		t.Fatal("no-op candidate owns frame storage")
174	}
175
176	committed := frame.Clone()
177	before := committed.Clone()
178	candidate.Commit(&committed)
179	if !reflect.DeepEqual(committed, before) {
180		t.Fatal("no-op candidate changed committed frame")
181	}
182}
183
184func TestPlanDeltaDoesNotMutateCommitted(t *testing.T) {
185	committed := testFrame("0000", "1111", "2222", "3333", "4444")
186	committed.ScrollUp(1, 3, 1)
187	before := committed.Clone()
188	frame := testFrame("0000", "2222", "3333", "new!", "4444")
189	damage := []Damage{
190		{Kind: DamageScrollUp, X: 0, Y: 1, Width: 4, Height: 3, Count: 1},
191		{Kind: DamageText, X: 0, Y: 3, Width: 4, Height: 1},
192	}
193
194	if _, err := PlanDelta(frame, damage, committed, false); err != nil {
195		t.Fatalf("PlanDelta() error = %v", err)
196	}
197
198	for y := range committed.Height {
199		if !reflect.DeepEqual(committed.Row(y), before.Row(y)) {
200			t.Fatalf("PlanDelta mutated committed row %d", y)
201		}
202	}
203}
204
205func TestDeltaCandidateCommitCopiesLogicalRows(t *testing.T) {
206	frame := testFrame("aaaa", "bbbb", "cccc", "dddd")
207	frame.ScrollUp(0, 3, 1)
208	fillFrameRow(frame, 3, "new!")
209	committed := NewFrame(4, 4)
210
211	candidate, err := PlanDelta(frame, nil, committed, true)
212	if err != nil {
213		t.Fatalf("PlanDelta() error = %v", err)
214	}
215	candidate.Commit(&committed)
216
217	for y := range frame.Height {
218		if !reflect.DeepEqual(committed.Row(y), frame.Row(y)) {
219			t.Fatalf("committed row %d = %#v, want %#v", y, committed.Row(y), frame.Row(y))
220		}
221	}
222	if err := committed.CheckInvariants(); err != nil {
223		t.Fatalf("committed frame invariants: %v", err)
224	}
225}
226
227func TestPlanDeltaRejectsInvalidFrame(t *testing.T) {
228	frame := Frame{Width: 2, Height: 1}
229	if _, err := PlanDelta(frame, nil, Frame{}, false); err == nil {
230		t.Fatal("PlanDelta accepted an invalid frame")
231	}
232}
233
234func TestDeltaCandidateCommitAppliesScrollToLogicalRows(t *testing.T) {
235	committed := testFrame("0000", "1111", "2222", "3333", "4444", "5555", "6666", "7777")
236	frame := committed.Clone()
237	frame.ScrollUp(1, 6, 1)
238	fillFrameRow(frame, 6, "new!")
239	damage := []Damage{
240		{Kind: DamageScrollUp, X: 0, Y: 1, Width: 4, Height: 6, Count: 1},
241		{Kind: DamageText, X: 0, Y: 6, Width: 4, Height: 1},
242	}
243
244	candidate, err := PlanDelta(frame, damage, committed, false)
245	if err != nil {
246		t.Fatalf("PlanDelta() error = %v", err)
247	}
248	if candidate.Plan.Scroll.Height == 0 {
249		t.Fatal("PlanDelta dropped safe scroll")
250	}
251	candidate.Commit(&committed)
252
253	for y := range frame.Height {
254		if !reflect.DeepEqual(committed.Row(y), frame.Row(y)) {
255			t.Fatalf("committed row %d = %#v, want %#v", y, committed.Row(y), frame.Row(y))
256		}
257	}
258}
259
260func fillFrameRow(frame Frame, y int, row string) {
261	for x, r := range row {
262		frame.Set(x, y, Cell{Rune: r, Style: DefaultStyle()})
263	}
264}

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