feat/hint-hybrid aed7ca4

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// A monitor on that side wins over an occupied workspace, so the opposite
54// key brings focus back; the workspace is reached only with no monitor.
55func TestCascadeFocusColumnGoesToMonitorBeforeWorkspace(t *testing.T) {
56	c, m := axisCore(t, true, OverflowCascade, 3)
57	m.Current().FocusID(23)
58	c.applyAction(ActionFocusColumnRight)
59	wantFocus(t, c, "R", 0)
60	wantActive(t, m, 1)
61	// Back with the opposite key.
62	c.applyAction(ActionFocusColumnLeft)
63	wantFocus(t, c, "M", 23)
64	wantActive(t, m, 1)
65	// The first column of a band: the monitor on the left.
66	m.Current().FocusID(24)
67	c.applyAction(ActionFocusColumnLeft)
68	wantFocus(t, c, "L", 0)
69	wantActive(t, m, 1)
70	// From the last occupied workspace: the monitor on the right, not the
71	// trailing empty workspace.
72	c.focusScreen = 1
73	m.Focus(2)
74	m.Current().FocusID(35)
75	c.applyAction(ActionFocusColumnRight)
76	wantFocus(t, c, "R", 0)
77	wantActive(t, m, 2)
78}
79
80// With no monitor on that side, the trailing empty workspace is the last
81// stop, and left comes back from it.
82func TestCascadeFocusColumnReachesTrailingWorkspaceWithoutMonitor(t *testing.T) {
83	c, m := axisCore(t, false, OverflowCascade, 3)
84	m.Focus(2)
85	m.Current().FocusID(35)
86	c.applyAction(ActionFocusColumnRight)
87	wantActive(t, m, 3)
88	if !m.Current().empty() {
89		t.Fatal("want the trailing empty workspace")
90	}
91	c.applyAction(ActionFocusColumnLeft)
92	wantActive(t, m, 2)
93}
94
95// One monitor on the left only. Right steps to the next workspace; left
96// from its empty band leaves for that monitor, which comes first.
97func TestCascadeStepRightThenLeftLeavesForMonitor(t *testing.T) {
98	c := layoutCore(1, tile{"L", 0, 0, 900, 600}, tile{"M", 900, 0, 900, 600})
99	m := c.cur().mon
100	m.SetOverflow(OverflowCascade)
101	m.SetMaxColumns(3)
102	m.AddWindow(1)
103	m.AddWindow(5)
104	c.applyAction(ActionFocusColumnRight)
105	wantActive(t, m, 1)
106	c.applyAction(ActionFocusColumnLeft)
107	wantFocus(t, c, "L", 0)
108	wantActive(t, m, 1)
109}
110
111// AC3: up/down cross bands and stop at the last, with no workspace change.
112func TestCascadeFocusWindowDownStaysOnWorkspace(t *testing.T) {
113	c, m := axisCore(t, false, OverflowCascade, 3)
114	m.Current().FocusID(22)
115	c.applyAction(ActionFocusWindowDown)
116	wantFocus(t, c, "M", 25)
117	wantActive(t, m, 1)
118	c.applyAction(ActionFocusWindowDown)
119	wantFocus(t, c, "M", 25)
120	wantActive(t, m, 1)
121}
122
123// AC4: scroll and fixed switch workspace with up/down at the column
124// bottom; left/right at the edge do nothing without a monitor.
125func TestScrollFixedWorkspaceAxisIsVertical(t *testing.T) {
126	for _, overflow := range []Overflow{OverflowScroll, OverflowFixed} {
127		t.Run(string(overflow), func(t *testing.T) {
128			c, m := axisCore(t, false, overflow, 3)
129			m.Current().FocusID(25)
130			c.applyAction(ActionFocusColumnRight)
131			wantActive(t, m, 1)
132			wantFocus(t, c, "M", 25)
133			c.applyAction(ActionFocusColumnLeft)
134			c.applyAction(ActionFocusColumnLeft)
135			c.applyAction(ActionFocusColumnLeft)
136			c.applyAction(ActionFocusColumnLeft)
137			c.applyAction(ActionFocusColumnLeft)
138			wantActive(t, m, 1)
139			c.applyAction(ActionFocusWindowDown)
140			wantActive(t, m, 2)
141			m.Focus(1)
142			m.Current().FocusID(21) // top of the first column
143			c.applyAction(ActionFocusWindowUp)
144			wantActive(t, m, 0)
145		})
146	}
147}
148
149// AC5: a shown named workspace is outside the workspace list.
150func TestCascadeNamedWorkspaceDoesNotStep(t *testing.T) {
151	c, m := axisCore(t, false, OverflowCascade, 3)
152	m.SetNamed([]NamedWorkspace{{Name: "n"}})
153	m.ToggleNamed("n")
154	shown := m.shown
155	if shown == nil {
156		t.Fatal("named workspace not shown")
157	}
158	shown.Overflow = OverflowCascade
159	shown.SetMaxColumns(3)
160	for id := WindowID(41); id <= 45; id++ {
161		m.AddWindow(id)
162	}
163	shown.FocusID(43)
164	c.applyAction(ActionFocusColumnRight)
165	if m.shown != shown || m.Active != 1 {
166		t.Fatalf("shown %p want %p, active %d", m.shown, shown, m.Active)
167	}
168	wantFocus(t, c, "M", 43)
169	shown.FocusID(41)
170	c.applyAction(ActionFocusColumnLeft)
171	if m.shown != shown || m.Active != 1 {
172		t.Fatalf("shown %p want %p, active %d", m.shown, shown, m.Active)
173	}
174	wantFocus(t, c, "M", 41)
175}
176
177// AC6: a covering fullscreen tile at the band edge steps the workspace and
178// stays fullscreen.
179func TestCascadeFullscreenBandEdgeStepsWorkspace(t *testing.T) {
180	c, m := axisCore(t, false, OverflowCascade, 3)
181	w := m.Current()
182	w.FocusID(23)
183	w.ToggleFullscreen()
184	c.applyAction(ActionFocusColumnRight)
185	wantActive(t, m, 2)
186	if w.fullscreen != 23 {
187		t.Fatalf("fullscreen %d, want 23", w.fullscreen)
188	}
189}
190
191// A covering fullscreen tile follows the same order: the monitor on that
192// side before the occupied workspace.
193func TestCascadeFullscreenGoesToMonitorBeforeWorkspace(t *testing.T) {
194	c, m := axisCore(t, true, OverflowCascade, 3)
195	w := m.Current()
196	w.FocusID(23)
197	w.ToggleFullscreen()
198	c.applyAction(ActionFocusColumnRight)
199	wantFocus(t, c, "R", 0)
200	wantActive(t, m, 1)
201	if w.fullscreen != 23 {
202		t.Fatalf("fullscreen %d, want 23", w.fullscreen)
203	}
204}
205
206// A workspace holding only a float is occupied: with no monitor on that
207// side, it comes before the trailing empty one.
208func TestCascadeFloatOnlyWorkspaceIsOccupied(t *testing.T) {
209	c, m := axisCore(t, false, OverflowCascade, 3)
210	m.Focus(3)
211	m.AddFloating(9, 100, 100)
212	m.Focus(2)
213	m.Current().FocusID(35)
214	c.applyAction(ActionFocusColumnRight)
215	wantActive(t, m, 3)
216	if got := c.cur().name(); got != "M" {
217		t.Fatalf("focused screen %q, want M", got)
218	}
219}
220
221// AC7: a lone native float, or an empty cascade workspace, is at the edge on
222// both sides.
223func TestCascadeLoneFloatAndEmptyWorkspaceStepWorkspace(t *testing.T) {
224	c, m := axisCore(t, false, OverflowCascade, 3)
225	m.Focus(3) // the trailing empty workspace
226	if !m.Current().empty() {
227		t.Fatal("workspace 4 not empty")
228	}
229	c.applyAction(ActionFocusColumnLeft)
230	wantActive(t, m, 2)
231
232	// A lone native float on its own workspace, past the trailing one.
233	for _, tc := range []struct {
234		a    Action
235		want int
236	}{{ActionFocusColumnLeft, 2}, {ActionFocusColumnRight, 4}} {
237		c, m = axisCore(t, false, OverflowCascade, 3)
238		m.Focus(3)
239		m.AddFloating(9, 100, 100)
240		if w := m.Current(); len(w.Columns) != 0 || !w.floatFocus {
241			t.Fatalf("columns %d floatFocus %v, want a lone focused float", len(w.Columns), w.floatFocus)
242		}
243		c.applyAction(tc.a)
244		wantActive(t, m, tc.want)
245	}
246}

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