navigator.go RawHistoryFolder

1package copy
2
3// Navigator owns cursor movement policy over a Document. Its positions are
4// always normalized glyph heads after a successful movement.
5type Navigator struct {
6	Pos          Pos
7	PreferredCol int
8}
9
10// NewNavigator creates a navigator at at. Set can normalize a position when a
11// document is available.
12func NewNavigator(at Pos) Navigator {
13	return Navigator{Pos: at, PreferredCol: at.Col}
14}
15
16// Left moves to the previous glyph on the current physical row.
17func (n *Navigator) Left(doc *Document) bool {
18	if doc == nil {
19		return false
20	}
21	pos, ok := doc.PrevGlyph(n.Pos)
22	if !ok {
23		return false
24	}
25	return n.set(pos, true)
26}
27
28// Right moves to the next glyph on the current physical row.
29func (n *Navigator) Right(doc *Document) bool {
30	if doc == nil {
31		return false
32	}
33	pos, ok := doc.NextGlyph(n.Pos)
34	if !ok {
35		return false
36	}
37	return n.set(pos, true)
38}
39
40// Up moves to the preceding physical row while retaining PreferredCol.
41func (n *Navigator) Up(doc *Document) bool {
42	if doc == nil {
43		return false
44	}
45	return n.moveVertical(doc, n.Pos.Row-1)
46}
47
48// Down moves to the following physical row while retaining PreferredCol.
49func (n *Navigator) Down(doc *Document) bool {
50	if doc == nil {
51		return false
52	}
53	return n.moveVertical(doc, n.Pos.Row+1)
54}
55
56// WordNext moves to the start of the next word, possibly on another row.
57func (n *Navigator) WordNext(doc *Document) bool {
58	if doc == nil {
59		return false
60	}
61	pos, ok := doc.NextWordStart(n.Pos)
62	if !ok {
63		return false
64	}
65	return n.set(pos, true)
66}
67
68// WordBackward moves to the start of the current or preceding word.
69func (n *Navigator) WordBackward(doc *Document) bool {
70	if doc == nil {
71		return false
72	}
73	pos, ok := doc.PreviousWordStart(n.Pos)
74	if !ok {
75		return false
76	}
77	return n.set(pos, true)
78}
79
80// WordEnd moves to the end of the current or next word.
81func (n *Navigator) WordEnd(doc *Document) bool {
82	if doc == nil {
83		return false
84	}
85	pos, ok := doc.NextWordEnd(n.Pos)
86	if !ok {
87		return false
88	}
89	return n.set(pos, true)
90}
91
92// Top moves to the first physical row at the current column and resets the
93// preferred column to the normalized destination.
94func (n *Navigator) Top(doc *Document) bool {
95	if doc == nil || doc.Len() == 0 {
96		return false
97	}
98	return n.Set(doc, Pos{Row: 0, Col: n.Pos.Col})
99}
100
101// Bottom moves to the last physical row at the current column and resets the
102// preferred column to the normalized destination.
103func (n *Navigator) Bottom(doc *Document) bool {
104	if doc == nil || doc.Len() == 0 {
105		return false
106	}
107	return n.Set(doc, Pos{Row: doc.Len() - 1, Col: n.Pos.Col})
108}
109
110// Page moves rows physical rows while retaining PreferredCol. Its destination
111// clamps to the first or last physical row.
112func (n *Navigator) Page(doc *Document, rows int) bool {
113	if doc == nil || doc.Len() == 0 {
114		return false
115	}
116	return n.moveVertical(doc, min(max(n.Pos.Row+rows, 0), doc.Len()-1))
117}
118
119// Set moves to pos after normalizing it through doc and updates PreferredCol.
120func (n *Navigator) Set(doc *Document, pos Pos) bool {
121	if doc == nil {
122		return false
123	}
124	pos, ok := doc.Normalize(pos)
125	if !ok {
126		return false
127	}
128	return n.set(pos, true)
129}
130
131func (n *Navigator) moveVertical(doc *Document, row int) bool {
132	if row < 0 || row >= doc.Len() {
133		return false
134	}
135	pos, ok := navigatorPosOnRow(doc, row, n.PreferredCol)
136	if !ok {
137		return false
138	}
139	return n.set(pos, false)
140}
141
142func navigatorPosOnRow(doc *Document, row, col int) (Pos, bool) {
143	width := doc.RowWidth(row)
144	if width == 0 {
145		return doc.Normalize(Pos{Row: row, Col: 0})
146	}
147	col = min(max(col, 0), width-1)
148	return doc.Normalize(Pos{Row: row, Col: col})
149}
150
151func (n *Navigator) set(pos Pos, setPreferred bool) bool {
152	moved := n.Pos != pos
153	n.Pos = pos
154	if setPreferred {
155		n.PreferredCol = pos.Col
156	}
157	return moved
158}

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 12

Tags 11

↑↓ moveenter openesc close