v0.5.0 31affde

scroll.go RawHistoryFolder

1package ansi
2
3import (
4	"bytes"
5	"strconv"
6)
7
8func findSafeScroll(frame CellSource, damage []Damage) (Damage, bool) {
9	for _, d := range damage {
10		if d.Kind == DamageScrollUp && isSafeScroll(frame, d) {
11			return d, true
12		}
13	}
14	return Damage{}, false
15}
16
17func isSafeScroll(frame CellSource, d Damage) bool {
18	return d.X == 0 && d.Width == frame.Columns() && d.Y >= 0 && d.Y <= frame.Rows() && d.Height > 0 && d.Height <= frame.Rows()-d.Y && d.Count > 0 && d.Count < d.Height
19}
20
21func emitScrollUp(out *bytes.Buffer, d Damage) {
22	out.WriteString("\x1b[0m")
23	// \x1b[%d;%dr — set scroll region
24	out.WriteString("\x1b[")
25	var b [16]byte
26	n := strconv.AppendInt(b[:0], int64(d.Y+1), 10)
27	out.Write(n)
28	out.WriteByte(';')
29	n = strconv.AppendInt(b[:0], int64(d.Y+d.Height), 10)
30	out.Write(n)
31	out.WriteByte('r')
32	// Position cursor at bottom of scroll region.
33	writeCursor(out, d.Y+d.Height-1, 0)
34	if d.Count == 1 {
35		out.WriteByte('\n')
36	} else {
37		out.WriteString("\x1b[")
38		n = strconv.AppendInt(b[:0], int64(d.Count), 10)
39		out.Write(n)
40		out.WriteByte('S')
41	}
42	out.WriteString("\x1b[r")
43}
44
45func canApplyScrollAgainst(frame CellSource, scroll Damage, damage []Damage, committed Frame) bool {
46	switch frame := frame.(type) {
47	case Frame:
48		return canApplyDenseScrollAgainst(frame, scroll, damage, committed)
49	case *Frame:
50		return canApplyDenseScrollAgainst(*frame, scroll, damage, committed)
51	}
52	for y := scroll.Y; y < scroll.Y+scroll.Height-scroll.Count; y++ {
53		for x := range scroll.Width {
54			column := scroll.X + x
55			committedCell, frameCell := committed.Cell(column, y+scroll.Count), frame.Cell(column, y)
56			if committedCell == frameCell || committedCell.Equal(frameCell) {
57				continue
58			}
59			if !damageCoversCell(damage, column, y) {
60				return false
61			}
62		}
63	}
64	return true
65}
66
67func canApplyDenseScrollAgainst(frame Frame, scroll Damage, damage []Damage, committed Frame) bool {
68	for y := scroll.Y; y < scroll.Y+scroll.Height-scroll.Count; y++ {
69		for x := range scroll.Width {
70			column := scroll.X + x
71			committedCell, frameCell := committed.At(column, y+scroll.Count), frame.At(column, y)
72			if committedCell == frameCell || committedCell.Equal(frameCell) {
73				continue
74			}
75			if !damageCoversCell(damage, column, y) {
76				return false
77			}
78		}
79	}
80	return true
81}
82
83func damageCoversCell(damage []Damage, x, y int) bool {
84	for _, d := range damage {
85		if (d.Kind == DamageText || d.Kind == DamageClear) && x >= d.X && x < d.X+d.Width && y >= d.Y && y < d.Y+d.Height {
86			return true
87		}
88	}
89	return false
90}

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 2

Tags 13

↑↓ moveenter openesc close