feat/hints a471fa4

expel_test.go RawHistoryFolder

1package core
2
3import (
4	"reflect"
5	"testing"
6)
7
8// cols builds a workspace with the given columns, focus on the last window
9// of column focus.
10func cols(overflow Overflow, max, focus int, columns ...[]WindowID) *Workspace {
11	w := &Workspace{Overflow: overflow, MaxColumns: max}
12	w.SetOutput(300, 100)
13	for _, c := range columns {
14		w.Columns = append(w.Columns, Column{Windows: c, Focus: len(c) - 1})
15	}
16	w.Focus = focus
17	return w
18}
19
20func ids(w *Workspace) [][]WindowID {
21	var out [][]WindowID
22	for _, c := range w.Columns {
23		out = append(out, c.Windows)
24	}
25	return out
26}
27
28func TestConsumeOrExpel(t *testing.T) {
29	for _, tc := range []struct {
30		name     string
31		w        *Workspace
32		dir      int
33		want     [][]WindowID
34		stays    bool
35		focusCol int
36	}{
37		{"consume right", cols(OverflowScroll, 2, 0, []WindowID{1}, []WindowID{2}), 1, [][]WindowID{{2, 1}}, true, 0},
38		{"consume left", cols(OverflowScroll, 2, 1, []WindowID{1}, []WindowID{2}), -1, [][]WindowID{{1, 2}}, true, 0},
39		{"consume at the edge does nothing", cols(OverflowFixed, 3, 1, []WindowID{1}, []WindowID{2}), 1, [][]WindowID{{1}, {2}}, true, 1},
40		{"expel right in scroll", cols(OverflowScroll, 1, 0, []WindowID{1, 2}, []WindowID{3}), 1, [][]WindowID{{1}, {2}, {3}}, true, 1},
41		{"expel left in scroll", cols(OverflowScroll, 1, 1, []WindowID{1}, []WindowID{2, 3}), -1, [][]WindowID{{1}, {3}, {2}}, true, 1},
42		{"expel in fixed under the limit", cols(OverflowFixed, 3, 0, []WindowID{1, 2}, []WindowID{3}), 1, [][]WindowID{{1}, {2}, {3}}, true, 1},
43		{"full fixed stacks into the next column", cols(OverflowFixed, 3, 0, []WindowID{1, 2}, []WindowID{3}, []WindowID{4}), 1, [][]WindowID{{1}, {3, 2}, {4}}, true, 1},
44		{"full fixed at the edge leaves", cols(OverflowFixed, 3, 2, []WindowID{1}, []WindowID{2}, []WindowID{3, 4}), 1, [][]WindowID{{1}, {2}, {3, 4}}, false, 2},
45		{"past the limit counts as full", cols(OverflowFixed, 2, 0, []WindowID{1, 2}, []WindowID{3}, []WindowID{4}), 1, [][]WindowID{{1}, {3, 2}, {4}}, true, 1},
46	} {
47		t.Run(tc.name, func(t *testing.T) {
48			stays := tc.w.ConsumeOrExpel(tc.dir)
49			if got := ids(tc.w); !reflect.DeepEqual(got, tc.want) || stays != tc.stays || tc.w.Focus != tc.focusCol {
50				t.Fatal(got, stays, tc.w.Focus)
51			}
52		})
53	}
54}
55
56func TestConsumeOrExpelFocusFollows(t *testing.T) {
57	w := cols(OverflowFixed, 3, 0, []WindowID{1, 2}, []WindowID{3}, []WindowID{4})
58	w.ConsumeOrExpel(1)
59	if id, _ := w.Focused(); id != 2 {
60		t.Fatal(id)
61	}
62}
63
64func TestConsumeOrExpelSkips(t *testing.T) {
65	t.Run("floating focus", func(t *testing.T) {
66		w := cols(OverflowScroll, 2, 0, []WindowID{1, 2})
67		w.AddFloating(9, 10, 10)
68		w.FocusID(9)
69		w.ConsumeOrExpel(1)
70		if got := ids(w); !reflect.DeepEqual(got, [][]WindowID{{1, 2}}) {
71			t.Fatal(got)
72		}
73	})
74	t.Run("fullscreen", func(t *testing.T) {
75		w := cols(OverflowFixed, 3, 0, []WindowID{1, 2})
76		w.fullscreen = 2
77		w.ConsumeOrExpel(1)
78		if got := ids(w); !reflect.DeepEqual(got, [][]WindowID{{1, 2}}) {
79			t.Fatal(got)
80		}
81	})
82}
83
84func TestConsumeIntoFullscreenColumnShows(t *testing.T) {
85	w := cols(OverflowScroll, 2, 0, []WindowID{1}, []WindowID{2})
86	w.fullscreen = 2
87	w.ConsumeOrExpel(1)
88	if id, _ := w.Focused(); !reflect.DeepEqual(ids(w), [][]WindowID{{2, 1}}) || id != 1 || w.fullscreen != 0 {
89		t.Fatal(ids(w), id, w.fullscreen)
90	}
91}
92
93func TestConsumeOrExpelSlots(t *testing.T) {
94	slots := func(w *Workspace) []int {
95		var out []int
96		for _, c := range w.Columns {
97			out = append(out, c.Slot)
98		}
99		return out
100	}
101	t.Run("the slot follows its window", func(t *testing.T) {
102		w := cols(OverflowScroll, 1, 0, []WindowID{1, 2})
103		w.Columns[0].Slot, w.Columns[0].Cells = 1, 2
104		w.Columns[0].Focus = 0
105		w.ConsumeOrExpel(1)
106		if got := ids(w); !reflect.DeepEqual(got, [][]WindowID{{2}, {1}}) || !reflect.DeepEqual(slots(w), []int{0, 1}) || w.Columns[1].Cells != 2 || w.Columns[0].Cells != 0 {
107			t.Fatal(got, slots(w), w.Columns)
108		}
109	})
110	t.Run("another window leaves the slot", func(t *testing.T) {
111		w := cols(OverflowScroll, 1, 0, []WindowID{1, 2})
112		w.Columns[0].Slot = 1
113		w.ConsumeOrExpel(1)
114		if !reflect.DeepEqual(slots(w), []int{1, 0}) {
115			t.Fatal(slots(w))
116		}
117	})
118	t.Run("stacking the slot window releases it", func(t *testing.T) {
119		w := cols(OverflowFixed, 2, 0, []WindowID{1, 2}, []WindowID{3})
120		w.Columns[0].Slot, w.Columns[0].Focus = 1, 0
121		w.ConsumeOrExpel(1)
122		if got := ids(w); !reflect.DeepEqual(got, [][]WindowID{{2}, {3, 1}}) || !reflect.DeepEqual(slots(w), []int{0, 0}) {
123			t.Fatal(got, slots(w))
124		}
125	})
126}
127
128func TestExpelTo(t *testing.T) {
129	t.Run("new edge column", func(t *testing.T) {
130		w := cols(OverflowFixed, 3, 0, []WindowID{1}, []WindowID{2})
131		w.expelTo(9, 1)
132		if got := ids(w); !reflect.DeepEqual(got, [][]WindowID{{9}, {1}, {2}}) || w.Focus != 0 {
133			t.Fatal(got)
134		}
135	})
136	t.Run("full stacks into the first column", func(t *testing.T) {
137		w := cols(OverflowFixed, 2, 1, []WindowID{1}, []WindowID{2})
138		w.expelTo(9, 1)
139		if id, _ := w.Focused(); !reflect.DeepEqual(ids(w), [][]WindowID{{1, 9}, {2}}) || id != 9 {
140			t.Fatal(ids(w), id)
141		}
142	})
143	t.Run("full from the right stacks into the last column", func(t *testing.T) {
144		w := cols(OverflowFixed, 2, 0, []WindowID{1}, []WindowID{2}, []WindowID{3})
145		w.expelTo(9, -1)
146		if id, _ := w.Focused(); !reflect.DeepEqual(ids(w), [][]WindowID{{1}, {2}, {3, 9}}) || id != 9 {
147			t.Fatal(ids(w), id)
148		}
149	})
150	t.Run("empty workspace", func(t *testing.T) {
151		w := cols(OverflowFixed, 2, 0)
152		w.expelTo(9, -1)
153		if !reflect.DeepEqual(ids(w), [][]WindowID{{9}}) {
154			t.Fatal(ids(w))
155		}
156	})
157}

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