v0.8.0 446d1e3

workspace_axis_test.go RawHistoryFolder

1package core
2
3import "testing"
4
5// axisCore is a cascade core whose monitor "M" has three workspaces of five
6// windows (ids 11-15, 21-25, 31-35; bands of three) and a trailing empty
7// one. Workspace 2 is active. With sides, monitors L and R flank M.
8func axisCore(t *testing.T, sides bool, overflow Overflow, maxCols int) (*Core, *Monitor) {
9	t.Helper()
10	c, _ := cascadeMovesCore(t, 0, maxCols, sides)
11	m := c.cur().mon
12	m.SetOverflow(overflow)
13	m.SetMaxColumns(maxCols)
14	for ws := range 3 {
15		m.Focus(ws)
16		for i := 1; i <= 5; i++ {
17			m.AddWindow(WindowID((ws+1)*10 + i))
18		}
19	}
20	m.Focus(1)
21	return c, m
22}
23
24func wantActive(t *testing.T, m *Monitor, active int) {
25	t.Helper()
26	if m.Active != active {
27		t.Fatalf("active workspace %d, want %d", m.Active, active)
28	}
29}
30
31// AC1: past the band edge with no monitor, left/right step the workspace.
32func TestCascadeFocusColumnStepsWorkspaceAtBandEdge(t *testing.T) {
33	c, m := axisCore(t, false, OverflowCascade, 3)
34	m.Current().FocusID(23) // last column of band 1, not the last band
35	c.applyAction(ActionFocusColumnRight)
36	wantActive(t, m, 2)
37	m.Focus(1)
38	m.Current().FocusID(24) // first column of band 2
39	c.applyAction(ActionFocusColumnLeft)
40	wantActive(t, m, 0)
41	// The first workspace stays put at the start.
42	m.Current().FocusID(11)
43	c.applyAction(ActionFocusColumnLeft)
44	wantActive(t, m, 0)
45	// Inside the band, focus moves and the workspace stays.
46	c.applyAction(ActionFocusColumnRight)
47	wantActive(t, m, 0)
48	if id, _ := m.Current().Focused(); id != 12 {
49		t.Fatalf("focused %d, want 12", id)
50	}
51}
52
53// An occupied workspace wins over a monitor on that side, so the opposite
54// key brings focus back; the monitor wins over the trailing empty
55// workspace, so it stays reachable.
56func TestCascadeFocusColumnStepsWorkspaceBeforeMonitor(t *testing.T) {
57	c, m := axisCore(t, true, OverflowCascade, 3)
58	m.Current().FocusID(23)
59	c.applyAction(ActionFocusColumnRight)
60	wantActive(t, m, 2)
61	if got := c.cur().name(); got != "M" {
62		t.Fatalf("focused screen %q, want M", got)
63	}
64	// Back with the opposite key, from the first column of workspace 3.
65	m.Current().FocusID(31)
66	c.applyAction(ActionFocusColumnLeft)
67	wantActive(t, m, 1)
68	if got := c.cur().name(); got != "M" {
69		t.Fatalf("focused screen %q, want M", got)
70	}
71	// Past the first workspace: the monitor on the left.
72	m.Focus(0)
73	m.Current().FocusID(11)
74	c.applyAction(ActionFocusColumnLeft)
75	wantFocus(t, c, "L", 0)
76	wantActive(t, m, 0)
77	// From the last occupied workspace: the monitor on the right, not the
78	// trailing empty workspace.
79	c.focusScreen = 1
80	m.Focus(2)
81	m.Current().FocusID(35)
82	c.applyAction(ActionFocusColumnRight)
83	wantFocus(t, c, "R", 0)
84	wantActive(t, m, 2)
85}
86
87// With no monitor on that side, the trailing empty workspace is the last
88// stop, and left comes back from it.
89func TestCascadeFocusColumnReachesTrailingWorkspaceWithoutMonitor(t *testing.T) {
90	c, m := axisCore(t, false, OverflowCascade, 3)
91	m.Focus(2)
92	m.Current().FocusID(35)
93	c.applyAction(ActionFocusColumnRight)
94	wantActive(t, m, 3)
95	if !m.Current().empty() {
96		t.Fatal("want the trailing empty workspace")
97	}
98	c.applyAction(ActionFocusColumnLeft)
99	wantActive(t, m, 2)
100}
101
102// The live regression: one monitor on the left only. Right steps to the
103// next workspace, and left from its empty band comes back to the previous
104// workspace instead of leaving for that monitor.
105func TestCascadeStepRightThenLeftComesBack(t *testing.T) {
106	c := layoutCore(1, tile{"L", 0, 0, 900, 600}, tile{"M", 900, 0, 900, 600})
107	m := c.cur().mon
108	m.SetOverflow(OverflowCascade)
109	m.SetMaxColumns(3)
110	m.AddWindow(1)
111	m.AddWindow(5)
112	c.applyAction(ActionFocusColumnRight)
113	wantActive(t, m, 1)
114	c.applyAction(ActionFocusColumnLeft)
115	wantFocus(t, c, "M", 5)
116	wantActive(t, m, 0)
117}
118
119// AC3: up/down cross bands and stop at the last, with no workspace change.
120func TestCascadeFocusWindowDownStaysOnWorkspace(t *testing.T) {
121	c, m := axisCore(t, false, OverflowCascade, 3)
122	m.Current().FocusID(22)
123	c.applyAction(ActionFocusWindowDown)
124	wantFocus(t, c, "M", 25)
125	wantActive(t, m, 1)
126	c.applyAction(ActionFocusWindowDown)
127	wantFocus(t, c, "M", 25)
128	wantActive(t, m, 1)
129}
130
131// AC4: scroll and fixed switch workspace with up/down at the column
132// bottom; left/right at the edge do nothing without a monitor.
133func TestScrollFixedWorkspaceAxisIsVertical(t *testing.T) {
134	for _, overflow := range []Overflow{OverflowScroll, OverflowFixed} {
135		t.Run(string(overflow), func(t *testing.T) {
136			c, m := axisCore(t, false, overflow, 3)
137			m.Current().FocusID(25)
138			c.applyAction(ActionFocusColumnRight)
139			wantActive(t, m, 1)
140			wantFocus(t, c, "M", 25)
141			c.applyAction(ActionFocusColumnLeft)
142			c.applyAction(ActionFocusColumnLeft)
143			c.applyAction(ActionFocusColumnLeft)
144			c.applyAction(ActionFocusColumnLeft)
145			c.applyAction(ActionFocusColumnLeft)
146			wantActive(t, m, 1)
147			c.applyAction(ActionFocusWindowDown)
148			wantActive(t, m, 2)
149			m.Focus(1)
150			m.Current().FocusID(21) // top of the first column
151			c.applyAction(ActionFocusWindowUp)
152			wantActive(t, m, 0)
153		})
154	}
155}
156
157// AC5: a shown named workspace is outside the workspace list.
158func TestCascadeNamedWorkspaceDoesNotStep(t *testing.T) {
159	c, m := axisCore(t, false, OverflowCascade, 3)
160	m.SetNamed([]NamedWorkspace{{Name: "n"}})
161	m.ToggleNamed("n")
162	shown := m.shown
163	if shown == nil {
164		t.Fatal("named workspace not shown")
165	}
166	shown.Overflow = OverflowCascade
167	shown.SetMaxColumns(3)
168	for id := WindowID(41); id <= 45; id++ {
169		m.AddWindow(id)
170	}
171	shown.FocusID(43)
172	c.applyAction(ActionFocusColumnRight)
173	if m.shown != shown || m.Active != 1 {
174		t.Fatalf("shown %p want %p, active %d", m.shown, shown, m.Active)
175	}
176	wantFocus(t, c, "M", 43)
177	shown.FocusID(41)
178	c.applyAction(ActionFocusColumnLeft)
179	if m.shown != shown || m.Active != 1 {
180		t.Fatalf("shown %p want %p, active %d", m.shown, shown, m.Active)
181	}
182	wantFocus(t, c, "M", 41)
183}
184
185// AC6: a covering fullscreen tile at the band edge steps the workspace and
186// stays fullscreen.
187func TestCascadeFullscreenBandEdgeStepsWorkspace(t *testing.T) {
188	c, m := axisCore(t, false, OverflowCascade, 3)
189	w := m.Current()
190	w.FocusID(23)
191	w.ToggleFullscreen()
192	c.applyAction(ActionFocusColumnRight)
193	wantActive(t, m, 2)
194	if w.fullscreen != 23 {
195		t.Fatalf("fullscreen %d, want 23", w.fullscreen)
196	}
197}
198
199// A covering fullscreen tile follows the same order: the occupied workspace
200// before the monitor on that side, which comes back only past the last one.
201func TestCascadeFullscreenStepsWorkspaceBeforeMonitor(t *testing.T) {
202	c, m := axisCore(t, true, OverflowCascade, 3)
203	w := m.Current()
204	w.FocusID(23)
205	w.ToggleFullscreen()
206	c.applyAction(ActionFocusColumnRight)
207	wantActive(t, m, 2)
208	if got := c.cur().name(); got != "M" {
209		t.Fatalf("focused screen %q, want M", got)
210	}
211	if w.fullscreen != 23 {
212		t.Fatalf("fullscreen %d, want 23", w.fullscreen)
213	}
214	last := m.Current()
215	last.FocusID(35)
216	last.ToggleFullscreen()
217	c.applyAction(ActionFocusColumnRight)
218	wantFocus(t, c, "R", 0)
219	if last.fullscreen != 35 {
220		t.Fatalf("fullscreen %d, want 35", last.fullscreen)
221	}
222}
223
224// A workspace holding only a float is occupied: it comes before the monitor.
225func TestCascadeFloatOnlyWorkspaceIsOccupied(t *testing.T) {
226	c, m := axisCore(t, true, OverflowCascade, 3)
227	m.Focus(3)
228	m.AddFloating(9, 100, 100)
229	m.Focus(2)
230	m.Current().FocusID(35)
231	c.applyAction(ActionFocusColumnRight)
232	wantActive(t, m, 3)
233	if got := c.cur().name(); got != "M" {
234		t.Fatalf("focused screen %q, want M", got)
235	}
236}
237
238// AC7: a lone native float, or an empty cascade workspace, is at the edge on
239// both sides.
240func TestCascadeLoneFloatAndEmptyWorkspaceStepWorkspace(t *testing.T) {
241	c, m := axisCore(t, false, OverflowCascade, 3)
242	m.Focus(3) // the trailing empty workspace
243	if !m.Current().empty() {
244		t.Fatal("workspace 4 not empty")
245	}
246	c.applyAction(ActionFocusColumnLeft)
247	wantActive(t, m, 2)
248
249	// A lone native float on its own workspace, past the trailing one.
250	for _, tc := range []struct {
251		a    Action
252		want int
253	}{{ActionFocusColumnLeft, 2}, {ActionFocusColumnRight, 4}} {
254		c, m = axisCore(t, false, OverflowCascade, 3)
255		m.Focus(3)
256		m.AddFloating(9, 100, 100)
257		if w := m.Current(); len(w.Columns) != 0 || !w.floatFocus {
258			t.Fatalf("columns %d floatFocus %v, want a lone focused float", len(w.Columns), w.floatFocus)
259		}
260		c.applyAction(tc.a)
261		wantActive(t, m, tc.want)
262	}
263}

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