cascade_test.go RawHistoryFolder

1package core
2
3import (
4	"slices"
5	"testing"
6)
7
8func cascadeWorkspace(n int) *Workspace {
9	w := &Workspace{Overflow: OverflowCascade, MaxColumns: 3}
10	w.SetOutput(900, 600)
11	for i := 1; i <= n; i++ {
12		w.AddWindow(WindowID(i))
13	}
14	return w
15}
16
17func TestCascadeBandsAndReveal(t *testing.T) {
18	for _, n := range []int{0, 1, 3, 4, 7} {
19		w := cascadeWorkspace(n)
20		ps := w.Layout()
21		if len(ps) != n {
22			t.Fatalf("n=%d placements=%d", n, len(ps))
23		}
24		for i, p := range ps {
25			want := Rect{X: i % 3 * 300, Y: (i/3)*600 - w.View, W: 300, H: 600}
26			if p.Rect != want {
27				t.Fatalf("n=%d window=%d rect=%v want=%v", n, p.ID, p.Rect, want)
28			}
29		}
30		if n > 0 && w.View != ((n-1)/3)*600 {
31			t.Fatalf("n=%d view=%d", n, w.View)
32		}
33	}
34}
35
36func TestCascadeCompactionAndNavigation(t *testing.T) {
37	w := cascadeWorkspace(7)
38	w.FocusID(2)
39	if !w.FocusWindow(1) {
40		t.Fatal("no lower band")
41	}
42	if id, _ := w.Focused(); id != 5 {
43		t.Fatalf("focus=%d", id)
44	}
45	w.RemoveWindow(1)
46	if w.View != 600 {
47		t.Fatalf("compacted view=%d", w.View)
48	}
49	w.SetMaxColumns(2)
50	if w.View != 600 {
51		t.Fatalf("changed max view=%d", w.View)
52	}
53	w.FocusID(2)
54	w.AddWindow(8)
55	if w.Columns[len(w.Columns)-1].Windows[0] != 8 {
56		t.Fatal("new column did not append")
57	}
58	w.SetUsable(Rect{X: 0, Y: 20, W: 900, H: 580})
59	if w.View != w.band(w.Focus)*580 {
60		t.Fatal("resize did not reveal band")
61	}
62	if !slices.Equal(w.snapPoints(), []float64{0, 580, 1160, 1740}) {
63		t.Fatalf("points=%v", w.snapPoints())
64	}
65}
66
67func TestCascadeCovers(t *testing.T) {
68	w := cascadeWorkspace(4)
69	w.FocusID(2)
70	w.ToggleFullWidth()
71	ps := w.Layout()
72	for _, p := range ps {
73		if p.ID == 2 && p.Rect.W != 900 {
74			t.Fatalf("full width=%v", p.Rect)
75		}
76		if (p.ID == 1 || p.ID == 3) && !p.Hidden {
77			t.Fatalf("sibling %d visible", p.ID)
78		}
79	}
80	w.ToggleFullWidth()
81	w.ToggleFullscreen()
82	for _, p := range w.Layout() {
83		if p.ID == 2 && p.Rect != w.Output {
84			t.Fatalf("fullscreen=%v", p.Rect)
85		}
86		if p.ID != 2 && !p.Hidden {
87			t.Fatalf("fullscreen sibling %d visible", p.ID)
88		}
89	}
90	w.ToggleFullscreen()
91	w.FocusID(4)
92	if w.View != 600 {
93		t.Fatal("fullscreen restore lost band reveal")
94	}
95}
96
97func TestCascadeClearsMaximizationOnBandChanges(t *testing.T) {
98	for _, move := range []struct {
99		name string
100		run  func(*Workspace)
101	}{
102		{"focus", func(w *Workspace) { w.FocusWindow(1) }},
103		{"append", func(w *Workspace) { w.AddWindow(5) }},
104		{"insert", func(w *Workspace) { w.insertColumn(4, Column{Windows: []WindowID{5}}) }},
105	} {
106		t.Run(move.name, func(t *testing.T) {
107			w := cascadeWorkspace(4)
108			w.FocusID(1)
109			w.ToggleFullWidth()
110			move.run(w)
111			for _, col := range w.Columns {
112				if col.FullWidth {
113					t.Fatal("stale maximization")
114				}
115			}
116		})
117	}
118}
119
120func TestCascadeReloadAndStashRestoreClearMaximization(t *testing.T) {
121	w := cascadeWorkspace(3)
122	w.FocusID(1)
123	w.ToggleFullWidth()
124	w.restore(4, &origPlace{stacked: []WindowID{2}, col: 1})
125	for _, c := range w.Columns {
126		if c.FullWidth {
127			t.Fatal("restore retained maximization")
128		}
129	}
130	m := NewMonitor()
131	m.SetOutput(900, 600)
132	for i := WindowID(1); i <= 3; i++ {
133		m.AddWindow(i)
134	}
135	m.Current().FocusID(1)
136	m.Current().ToggleFullWidth()
137	m.Current().FocusID(3)
138	m.SetOverflow(OverflowCascade)
139	for _, c := range m.Current().Columns {
140		if c.FullWidth {
141			t.Fatal("reload retained maximization")
142		}
143	}
144}
145
146func TestCascadeGestureValidityDoesNotAllocate(t *testing.T) {
147	m := NewMonitor()
148	m.SetOutput(900, 600)
149	m.SetOverflow(OverflowCascade)
150	for i := WindowID(1); i <= 7; i++ {
151		m.AddWindow(i)
152	}
153	g := &swipeGesture{ws: m.Current(), points: m.Current().snapPoints(), horizontal: false, opens: m.overviewOpens}
154	if n := testing.AllocsPerRun(100, func() {
155		if g.columnsChanged(m) {
156			t.Fatal("unchanged gesture invalidated")
157		}
158	}); n != 0 {
159		t.Fatalf("allocations=%v", n)
160	}
161}
162
163func TestCascadeGeometryReusesBuffer(t *testing.T) {
164	w := cascadeWorkspace(7)
165	buf := w.columnRectsInto(nil, false)
166	if n := testing.AllocsPerRun(100, func() { buf = w.columnRectsInto(buf, false) }); n != 0 {
167		t.Fatalf("allocations=%v", n)
168	}
169}
170
171func TestMixedLayoutSwitchKeepsSourceAxis(t *testing.T) {
172	m := NewMonitor()
173	m.SetOutput(900, 600)
174	m.Current().Overflow = OverflowCascade
175	m.AddWindow(1)
176	m.Focus(1)
177	m.AddWindow(2)
178	m.Current().Overflow = OverflowScroll
179	m.Focus(0)
180	m.switchAxis = m.Current().policy().workspace
181	m.switchView.off = .5
182	ps := m.layoutInto(nil)
183	for _, p := range ps {
184		if p.ID == 1 && (p.Rect.X != -450 || p.Rect.Y != 0) {
185			t.Fatalf("cascade source offset=%v", p.Rect)
186		}
187	}
188	m.stopSwitch()
189	m.Focus(1)
190	m.switchAxis = m.Current().policy().workspace
191	m.switchView.off = -.5
192	ps = m.layoutInto(nil)
193	for _, p := range ps {
194		if p.ID == 2 && (p.Rect.X != 0 || p.Rect.Y != 300) {
195			t.Fatalf("scroll source offset=%v", p.Rect)
196		}
197	}
198}
199
200func TestCascadeViewportSnap(t *testing.T) {
201	w := cascadeWorkspace(7)
202	w.FocusID(3)
203	if w.snapFocus(600, true) != 5 {
204		t.Fatalf("nearest cell=%d", w.snapFocus(600, true))
205	}
206	if w.snapFocus(1200, true) != 6 {
207		t.Fatal("short band neighbour")
208	}
209	w.SetUsable(Rect{})
210	if got := w.snapPoints(); len(got) != 1 || got[0] != 0 {
211		t.Fatalf("zero area=%v", got)
212	}
213}

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 23

Tags 10

↑↓ moveenter openesc close