overview_stack_test.go RawHistoryFolder

1package core
2
3import (
4	"slices"
5	"testing"
6)
7
8func stackMonitor() *Monitor {
9	m := overviewMonitor()
10	m.SetBorder(2)
11	m.AddFloating(9, 300, 200)
12	return m
13}
14
15func TestOverviewCoveringStackNavigation(t *testing.T) {
16	for _, columns := range []bool{false, true} {
17		m := stackMonitor()
18		w := m.Current()
19		if columns {
20			w.FocusID(3)
21		}
22		before := slices.Clone(order(w))
23		m.ToggleOverview()
24		ps := m.Layout()
25		f, c := previewOf(t, ps, 9), previewOf(t, ps, 3)
26		if f.Peek != columns || c.Peek == columns {
27			t.Fatalf("front float %+v columns %+v", f, c)
28		}
29		if !slices.Equal(order(w), before) {
30			t.Fatal("opening changed float order")
31		}
32		if columns {
33			m.OverviewMove(0, -1)
34		} else {
35			m.OverviewMove(-1, 0)
36		}
37		if columns {
38			if p := previewOf(t, m.Layout(), 9); p.Peek || !p.Focused {
39				t.Fatalf("float %+v", p)
40			}
41		} else {
42			if p := previewOf(t, m.Layout(), 3); p.Peek || !p.Focused {
43				t.Fatalf("column %+v", p)
44			}
45		}
46		m.ToggleOverview()
47		if columns {
48			wantOrder(t, w, 1, 2, 3, 9)
49		} else {
50			wantOrder(t, w, 9, 1, 2, 3)
51		}
52	}
53}
54
55func TestOverviewStackLeftWithoutStashRotatesBackward(t *testing.T) {
56	m := stackMonitor()
57	w := m.Current()
58	m.ToggleOverview()
59	m.OverviewMove(-1, 0)
60	if p := previewOf(t, m.Layout(), 3); p.Peek || !p.Focused {
61		t.Fatalf("left did not rotate to columns: %+v", p)
62	}
63	m.CancelOverview()
64	if id, _ := w.Focused(); id != 9 {
65		t.Fatalf("cancel focus %d", id)
66	}
67}
68
69func TestOverviewStackEscapePreservesFocusAndOrder(t *testing.T) {
70	for _, columns := range []bool{false, true} {
71		m := stackMonitor()
72		w := m.Current()
73		if columns {
74			w.FocusID(3)
75		}
76		before := slices.Clone(order(w))
77		focused, _ := w.Focused()
78		m.ToggleOverview()
79		m.OverviewMove(1, 0)
80		m.CancelOverview()
81		if id, _ := w.Focused(); id != focused || !slices.Equal(order(w), before) {
82			t.Fatalf("columns %v: focus %d want %d, order %v want %v", columns, id, focused, order(w), before)
83		}
84	}
85	// When the focused float is not last (a dialog sits over it), Escape
86	// must restore its position too; FocusID alone would raise it.
87	m := stackMonitor()
88	w := m.Current()
89	w.AddFloating(10, 20, 20)
90	w.FocusID(9)
91	before := slices.Clone(order(w))
92	m.ToggleOverview()
93	m.OverviewMove(1, 0)
94	m.CancelOverview()
95	if id, _ := w.Focused(); id != 9 || !slices.Equal(order(w), before) {
96		t.Fatalf("float focus %d order %v want %v", id, order(w), before)
97	}
98}
99
100func TestOverviewStackClickAndHitPriority(t *testing.T) {
101	for _, frontColumn := range []bool{false, true} {
102		m := stackMonitor()
103		w := m.Current()
104		if frontColumn {
105			w.FocusID(3)
106		}
107		m.ToggleOverview()
108		ps := m.Layout()
109		front, back := previewOf(t, ps, 9), previewOf(t, ps, 3)
110		if frontColumn {
111			front, back = back, front
112		}
113		// The overlap with the peeking column tile must hit the front.
114		x, y := back.Rect.X+back.Rect.W/2, max(front.Rect.Y, back.Rect.Y)+back.Rect.H/2
115		if frontColumn {
116			x = front.Rect.X + front.Rect.W/2
117		}
118		if got := overviewIn(m.Layout(), float64(x), float64(y)); got != front.ID {
119			t.Fatalf("overlap hit %d want %d (front %+v back %+v)", got, front.ID, front.Rect, back.Rect)
120		}
121		// The up/right exposed corner belongs to the peeking card.
122		x, y = back.Rect.X+back.Rect.W-1, back.Rect.Y+1
123		if got := overviewIn(m.Layout(), float64(x), float64(y)); got != back.ID {
124			t.Fatalf("peek hit %d want %d", got, back.ID)
125		}
126		m.OverviewPick(back.ID)
127		if id, _ := w.Focused(); id != back.ID || m.ov.open {
128			t.Fatalf("picked %d, focus %d", back.ID, id)
129		}
130	}
131}
132
133func TestOverviewStackStashDialogNeighbor(t *testing.T) {
134	m := stackMonitor()
135	w := m.Current()
136	w.AddFloating(10, 20, 20)
137	w.FocusID(9)
138	w.AddWindow(11)
139	w.FocusID(11)
140	w.ToggleWindowStash()
141	w.FocusID(9)
142	m.ToggleOverview()
143	if p := previewOf(t, m.Layout(), 10); !p.Hidden {
144		t.Fatalf("dialog %+v", p)
145	}
146	m.OverviewMove(-1, 0) // single-window float sends behind before stash
147	if m.stackFront(w).kind != stackColumns || m.cardAt(w) >= 0 {
148		t.Fatal("float did not send behind")
149	}
150	m.OverviewMove(-1, 0)
151	m.OverviewMove(-1, 0)
152	m.OverviewMove(-1, 0)
153	if m.cardAt(w) < 0 {
154		t.Fatal("left from first column did not enter stash")
155	}
156	m.OverviewMove(1, 0)
157	if m.cardAt(w) >= 0 {
158		t.Fatal("stash exit did not return to card")
159	}
160	m.CancelOverview()
161	m = stackMonitor()
162	m.Focus(1)
163	m.AddFloating(12, 300, 200)
164	m.Focus(0)
165	m.ToggleOverview()
166	if p, q := previewOf(t, m.Layout(), 12), previewOf(t, m.Layout(), 4); p.Hidden || q.Hidden || !p.Peek || !q.Peek || p.Focused || q.Focused {
167		t.Fatalf("neighbor %+v %+v", p, q)
168	}
169}
170
171func TestOverviewMultipleCoveringCards(t *testing.T) {
172	m := stackMonitor()
173	w := m.Current()
174	w.AddFloating(8, 300, 200)
175	w.AddFloating(7, 300, 200)
176	w.AddFloating(6, 300, 200)
177	m.ToggleOverview()
178	if p := previewOf(t, m.Layout(), 6); p.Peek {
179		t.Fatalf("front %+v", p)
180	}
181	if p := previewOf(t, m.Layout(), 7); !p.Peek {
182		t.Fatalf("peek %+v", p)
183	}
184	if p := previewOf(t, m.Layout(), 9); !p.Hidden {
185		t.Fatalf("far card %+v", p)
186	}
187	m.OverviewMove(0, -1)
188	if p := previewOf(t, m.Layout(), 7); p.Peek || !p.Focused {
189		t.Fatalf("up %+v", p)
190	}
191	if p := previewOf(t, m.Layout(), 6); !p.Peek || p.Rect.Y <= previewOf(t, m.Layout(), 7).Rect.Y {
192		t.Fatalf("passed card %+v", p)
193	}
194	m.OverviewMove(0, 1)
195	if p := previewOf(t, m.Layout(), 6); p.Peek {
196		t.Fatal("down did not return to screen front")
197	}
198	m.CancelOverview()
199}
200
201func TestOverviewFloatWithoutColumns(t *testing.T) {
202	m := monitor()
203	m.SetOutput(300, 200)
204	m.SetBorder(2)
205	w := m.Current()
206	w.AddFloating(9, 300, 200)
207	w.AddFloating(8, 300, 200)
208	m.ToggleOverview()
209	if p := previewOf(t, m.Layout(), 8); p.Peek || p.Hidden {
210		t.Fatalf("front float %+v", p)
211	}
212	m.OverviewMove(-1, 0)
213	if p := previewOf(t, m.Layout(), 9); p.Peek || p.Hidden {
214		t.Fatalf("back float %+v", p)
215	}
216	m.ToggleOverview()
217	if id, _ := w.Focused(); id != 9 {
218		t.Fatalf("focus %d", id)
219	}
220}
221
222func TestOverviewStackRowChangeResetsRotation(t *testing.T) {
223	m := stackMonitor()
224	m.ToggleOverview()
225	m.OverviewMove(0, -1)
226	if m.stackFront(m.Current()).kind != stackColumns {
227		t.Fatal("stack did not move to columns")
228	}
229	m.OverviewMove(0, -1) // at stack end: workspace above, if present
230	m.OverviewMove(0, 1)
231	if m.stackFront(m.Current()) != (stackItem{stackFloat, 9}) {
232		t.Fatal("row change retained cursor")
233	}
234	m.CancelOverview()
235}
236
237func TestOverviewStackInvalidatedOnRemoval(t *testing.T) {
238	m := stackMonitor()
239	w := m.Current()
240	m.ToggleOverview()
241	m.OverviewMove(1, 0)
242	w.RemoveWindow(9)
243	m.ToggleOverview()
244	if id, _ := w.Focused(); id != 3 {
245		t.Fatalf("removed selection focused %d", id)
246	}
247}
248
249// Stash navigation returns to the current front card without changing its cursor.
250func TestOverviewStashRightBringsColumnsFront(t *testing.T) {
251	m := stackMonitor()
252	w := m.Current()
253	w.FocusID(1)
254	w.ToggleWindowStash()
255	w.FocusID(9)
256	m.ToggleOverview()
257	m.OverviewMove(-1, 0) // float to columns
258	m.OverviewMove(-1, 0) // first column to stash
259	if m.cardAt(w) < 0 {
260		t.Fatal("not in stash")
261	}
262	m.OverviewMove(1, 0)
263	if m.cardAt(w) >= 0 || m.stackFront(w).kind != stackColumns {
264		t.Fatal("not returned to front column card")
265	}
266	m.ToggleOverview()
267	if id, _ := w.Focused(); id != 2 {
268		t.Fatalf("focus %d", id)
269	}
270}
271
272// Stack previews are not floats: the overview draws no float veil.
273func TestOverviewStackNoFloatVeil(t *testing.T) {
274	m := stackMonitor()
275	m.ToggleOverview()
276	if d := floatDim(m.Layout(), Rect{W: 300, H: 200}, 0.3); d != 0 {
277		t.Fatalf("veil %v in the overview", d)
278	}
279}
280
281// l leaves the pile for the front card, even with no columns.
282func TestOverviewPileRightToFloatCard(t *testing.T) {
283	m := monitor()
284	m.SetOutput(300, 200)
285	m.SetBorder(2)
286	w := m.Current()
287	m.AddWindow(1)
288	w.ToggleWindowStash()
289	w.ToggleStashVisible()
290	w.AddFloating(9, 300, 200)
291	m.ToggleOverview()
292	m.OverviewMove(-1, 0)
293	if m.card() != 1 {
294		t.Fatalf("h did not enter the pile: card %d", m.card())
295	}
296	m.OverviewMove(1, 0)
297	if m.card() != 0 || m.overviewTarget() != 9 {
298		t.Fatalf("l stayed in the pile: card %d target %d", m.card(), m.overviewTarget())
299	}
300}

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 25

Tags 10

↑↓ moveenter openesc close