v0.1.0 af61ae3

row_id_test.go RawHistoryFolder

1package vt
2
3import "testing"
4
5func TestScreenRowIDsRotateWithRowsAndHistory(t *testing.T) {
6	s := NewScreenWithHistory(4, 3, HistoryConfig{MaxRows: 8, ChunkRows: 2})
7	initial := s.RowIDs()
8	assertUniqueNonzeroRowIDs(t, initial)
9
10	s.Write([]byte("AAAA\r\nBBBB\r\nCCCC\r\n"))
11
12	live := s.RowIDs()
13	if got, want := live, []RowID{initial[1], initial[2], live[2]}; len(got) != len(want) || got[0] != want[0] || got[1] != want[1] {
14		t.Fatalf("live row IDs = %v, want rows moved from %v with a fresh blank row", got, initial)
15	}
16	if live[2] == initial[0] || live[2] == initial[1] || live[2] == initial[2] {
17		t.Fatalf("new blank row reused a row ID: live=%v initial=%v", live, initial)
18	}
19	assertUniqueNonzeroRowIDs(t, live)
20
21	view := s.History().SealAndView()
22	if got, want := view.RowID(0), initial[0]; got != want {
23		t.Fatalf("evicted row ID = %d, want %d", got, want)
24	}
25	if got := view.FindRowID(initial[0]); got != 0 {
26		t.Fatalf("FindRowID(%d) = %d, want 0", initial[0], got)
27	}
28}
29
30func TestScreenRowIDsDoNotAppendInteriorOrAlternateScrollHistory(t *testing.T) {
31	s := NewScreenWithHistory(4, 4, HistoryConfig{MaxRows: 8})
32	s.Write([]byte("AAAA\r\nBBBB\r\nCCCC\r\nDDDD"))
33	s.Write([]byte("\x1b[2;4r\x1b[4;1H\n"))
34	if got := s.History().Len(); got != 0 {
35		t.Fatalf("interior scroll history rows = %d, want 0", got)
36	}
37	assertUniqueNonzeroRowIDs(t, s.RowIDs())
38	primary := s.RowIDs()
39
40	s.Write([]byte("\x1b[?1049h1111\r\n2222\r\n3333\r\n4444\r\n"))
41	if got := s.History().Len(); got != 0 {
42		t.Fatalf("alternate scroll history rows = %d, want 0", got)
43	}
44	s.Write([]byte("\x1b[?1049l"))
45	for i, id := range primary {
46		if got := s.RowID(i); got != id {
47			t.Fatalf("restored primary row %d ID = %d, want %d", i, got, id)
48		}
49	}
50}
51
52func TestScreenRowIDRefreshesOnSnapExpandedFullClear(t *testing.T) {
53	tests := []struct {
54		name        string
55		x0, x1      int
56		wantRefresh bool
57	}{
58		{name: "snap-expanded full clear", x0: 1, x1: 4, wantRefresh: true},
59		{name: "partial clear", x0: 0, x1: 1, wantRefresh: false},
60	}
61
62	for _, tt := range tests {
63		t.Run(tt.name, func(t *testing.T) {
64			s := NewScreen(4, 1)
65			s.Write([]byte("界"))
66			before := s.RowID(0)
67
68			s.clearRow(0, tt.x0, tt.x1)
69
70			if got := s.RowID(0) != before; got != tt.wantRefresh {
71				t.Fatalf("row ID refreshed = %t, want %t", got, tt.wantRefresh)
72			}
73		})
74	}
75}
76
77func TestScreenRowIDsRefreshOnClearResetAndAlternateClone(t *testing.T) {
78	s := NewScreen(4, 3)
79	before := s.RowIDs()
80	s.Write([]byte("AAAA\x1b[2J"))
81	cleared := s.RowIDs()
82	assertFreshRowIDs(t, before, cleared)
83
84	s.Write([]byte("\x1bc"))
85	reset := s.RowIDs()
86	assertFreshRowIDs(t, cleared, reset)
87
88	s.Write([]byte("BBBB\x1b[?1049h"))
89	alternate := s.RowIDs()
90	assertFreshRowIDs(t, reset, alternate)
91	s.Write([]byte("CCCC\x1b[?1049l"))
92	for i, id := range reset {
93		if got := s.RowID(i); got != id {
94			t.Fatalf("cloned primary row %d ID = %d, want %d", i, got, id)
95		}
96	}
97}
98
99func TestResizeReflowCarriesSourceRowIDsIntoHistoryAndViewport(t *testing.T) {
100	b := newBuffer(8, 3)
101	copy(b.frame.Row(0), historyRow("abcdefgh"))
102	copy(b.frame.Row(1), historyRow("ijkl"))
103	copy(b.frame.Row(2), historyRow("mnop"))
104	b.boundaries[0] = LineBound{End: 8, Soft: true}
105	b.boundaries[1] = LineBound{End: 4}
106	b.boundaries[2] = LineBound{End: 4}
107	b.rowIDs = []RowID{11, 12, 13}
108
109	active := &bufferCursor{row: 2, col: 4}
110	evicted, bounds, ids := b.resize(4, 2, active, nil)
111
112	if len(evicted) != 2 || bounds[0].End != 4 || ids[0] != 11 || ids[1] != 0 {
113		t.Fatalf("reflow eviction = rows=%d bound=%v IDs=%v, want two rows with source ID 11 then a generated ID", len(evicted), bounds, ids)
114	}
115	if got, want := b.rowIDs, []RowID{0, 13}; got[1] != want[1] || len(got) != len(want) {
116		t.Fatalf("reflow viewport IDs = %v, want generated continuation then source ID %d", got, want[1])
117	}
118}
119
120func TestHistoryRowIDsRemainStableAcrossViewsAndEviction(t *testing.T) {
121	h := NewHistory(HistoryConfig{MaxRows: 3, ChunkRows: 2})
122	for i, text := range []string{"one", "two"} {
123		if err := h.AppendWithID(historyRow(text), LineBound{End: len(text)}, RowID(i+1)); err != nil {
124			t.Fatal(err)
125		}
126	}
127	before := h.SealAndView()
128	if got, want := before.RowID(0), RowID(1); got != want {
129		t.Fatalf("sealed row ID = %d, want %d", got, want)
130	}
131
132	if err := h.AppendWithID(historyRow("three"), LineBound{End: 5}, RowID(3)); err != nil {
133		t.Fatal(err)
134	}
135	if got := before.FindRowID(2); got != 1 {
136		t.Fatalf("retained view FindRowID(2) = %d, want 1", got)
137	}
138	after := h.SealAndView()
139	if got := after.FindRowID(3); got != 2 {
140		t.Fatalf("new view FindRowID(3) = %d, want 2", got)
141	}
142
143	if err := h.AppendWithID(historyRow("four"), LineBound{End: 4}, RowID(4)); err != nil {
144		t.Fatal(err)
145	}
146	if got := before.FindRowID(1); got != 0 {
147		t.Fatalf("eviction mutated retained view FindRowID(1) = %d, want 0", got)
148	}
149	if got := h.View().FindRowID(1); got != -1 {
150		t.Fatalf("evicted current FindRowID(1) = %d, want -1", got)
151	}
152}
153
154func assertUniqueNonzeroRowIDs(t *testing.T, ids []RowID) {
155	t.Helper()
156	seen := make(map[RowID]struct{}, len(ids))
157	for _, id := range ids {
158		if id == 0 {
159			t.Fatal("row ID is zero")
160		}
161		if _, ok := seen[id]; ok {
162			t.Fatalf("duplicate row ID %d in %v", id, ids)
163		}
164		seen[id] = struct{}{}
165	}
166}
167
168func assertFreshRowIDs(t *testing.T, before, after []RowID) {
169	t.Helper()
170	assertUniqueNonzeroRowIDs(t, after)
171	seen := make(map[RowID]struct{}, len(before))
172	for _, id := range before {
173		seen[id] = struct{}{}
174	}
175	for _, id := range after {
176		if _, ok := seen[id]; ok {
177			t.Fatalf("row ID %d was reused: before=%v after=%v", id, before, after)
178		}
179	}
180}

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