main ece7682

content_render_test.go RawHistoryFolder

1package copy
2
3import (
4	"strings"
5	"testing"
6
7	vt "github.com/bnema/vev-vt"
8	"github.com/stretchr/testify/require"
9)
10
11func TestRenderRowsSelectionHighlightsContentOnly(t *testing.T) {
12	wide := padRow("", 8)
13	wide[0] = vt.Cell{Rune: 'a'}
14	wide[1] = vt.Cell{Rune: '界'}
15	wide[2] = vt.Cell{Continuation: true}
16	tests := []struct {
17		name        string
18		rows        [][]vt.Cell
19		bounds      []vt.LineBound
20		end         Pos
21		granularity Granularity
22		want        [][]int
23	}{
24		{"hard rows", [][]vt.Cell{padRow("ab", 8), padRow("cd", 8)}, nil, Pos{1, 1}, Character, [][]int{{0, 1, 2}, {0, 1}}},
25		{"word rows", [][]vt.Cell{padRow("ab", 8), padRow("cd", 8)}, nil, Pos{1, 1}, Word, [][]int{{0, 1, 2}, {0, 1}}},
26		{"line mode", [][]vt.Cell{padRow("ab", 8), padRow("", 8), padRow("cd", 8)}, nil, Pos{2, 0}, Line, [][]int{{0, 1, 2}, {}, {0, 1, 2}}},
27		{"soft spaces", [][]vt.Cell{padRow("abc     ", 8), padRow("de", 8)}, []vt.LineBound{{End: 8, Soft: true}, {End: 2}}, Pos{1, 1}, Character, [][]int{{0, 1, 2, 3, 4, 5, 6, 7}, {0, 1}}},
28		{"soft partial", [][]vt.Cell{padRow("abc", 8), padRow("de", 8)}, []vt.LineBound{{End: 5, Soft: true}, {End: 2}}, Pos{1, 1}, Character, [][]int{{0, 1, 2, 3, 4}, {0, 1}}},
29		{"full hard row", [][]vt.Cell{padRow("abcdefgh", 8), padRow("ij", 8)}, nil, Pos{1, 0}, Character, [][]int{{0, 1, 2, 3, 4, 5, 6, 7}, {0}}},
30		{"wide glyph", [][]vt.Cell{wide}, nil, Pos{0, 0}, Line, [][]int{{0, 1, 2, 3}}},
31		{"end in padding", [][]vt.Cell{padRow("ab", 8)}, nil, Pos{0, 6}, Character, [][]int{{0, 1}}},
32	}
33	for _, tt := range tests {
34		t.Run(tt.name, func(t *testing.T) {
35			doc := NewDocument(NewSnapshotFromLines(tt.rows, tt.bounds, 8, len(tt.rows)), "")
36			m := NewMode(doc)
37			m.selection = Selection{Anchor: Pos{0, 0}, Active: tt.end, Granularity: tt.granularity, Enabled: true}
38			m.navigator.Pos = Pos{0, 0}
39			f := m.Render(vt.DefaultStyle(), vt.Style{HasBackgroundRGB: true, BackgroundRGB: vt.RGB{R: 1}})
40			var copied strings.Builder
41			bounds, _ := m.selection.bounds(doc)
42			for y, source := range tt.rows {
43				cols := []int{}
44				marker, hasMarker := bounds.newlineMarker(doc, y)
45				for x, cell := range source {
46					if f.At(x, y).Style.Inverse == cell.Style.Inverse {
47						continue
48					}
49					cols = append(cols, x)
50					if (hasMarker && x == marker) || cell.Continuation {
51						continue
52					}
53					if cell.Rune == 0 {
54						copied.WriteByte(' ')
55					} else {
56						copied.WriteRune(cell.Rune)
57					}
58				}
59				require.Equal(t, tt.want[y], cols)
60				if y < len(tt.rows)-1 && !doc.snapshot.Bound(y).Soft {
61					copied.WriteByte('\n')
62				}
63			}
64			require.Equal(t, copied.String(), m.SelectedText(), "highlighted glyphs match copied text")
65			for y, source := range tt.rows {
66				require.Equal(t, source, doc.Row(y))
67			}
68		})
69	}
70}
71
72func TestRenderRowsSelectionAfterHistoryResize(t *testing.T) {
73	screen := vt.NewScreenWithHistory(12, 2, vt.HistoryConfig{MaxRows: 64})
74	screen.Write([]byte("abc     xyz\r\nq\r\nr\r\ns"))
75	screen.Resize(8, 2)
76	doc := NewDocument(NewSnapshot(screen.History(), screen, screen.LineBounds(), nil), "")
77	m := NewMode(doc)
78	m.navigator.Pos = Pos{0, 0}
79	m.ViewportTop = 0
80	m.selection = Selection{Anchor: Pos{0, 0}, Active: Pos{1, 0}, Granularity: Character, Enabled: true}
81	require.Equal(t, "abc     xyz\nq", m.SelectedText())
82	for _, col := range []int{0, 10} {
83		m.navigator.Pos = Pos{0, col}
84		f := m.Render(vt.DefaultStyle(), vt.Style{HasBackgroundRGB: true})
85		for x := 0; x < 8; x++ {
86			require.True(t, f.At(x, 0).Style.Inverse, "cursor %d, visible content column %d", col, x)
87		}
88	}
89}
90
91func TestRenderRowsSelectionPreservesCellColors(t *testing.T) {
92	styles := []vt.Style{
93		{HasForegroundRGB: true, ForegroundRGB: vt.RGB{G: 255, B: 255}, Bold: true},
94		{Inverse: true, HasBackgroundRGB: true, BackgroundRGB: vt.RGB{R: 20}, Italic: true},
95	}
96	cells := []vt.Cell{{Rune: 'a', Style: styles[0]}, {Rune: 'b', Style: styles[1]}}
97	m := NewMode(NewDocument(NewSnapshotFromRows([][]vt.Cell{cells}, 2, 1), ""))
98	require.True(t, m.StartCharacterSelection(Pos{0, 0}))
99	require.True(t, m.ExtendCharacterSelection(Pos{0, 1}))
100	f := m.Render(vt.DefaultStyle(), vt.Style{HasBackgroundRGB: true, BackgroundRGB: vt.RGB{R: 1}})
101	for x, style := range styles {
102		style.Inverse = !style.Inverse
103		require.Equal(t, style, f.At(x, 0).Style)
104	}
105	require.Equal(t, cells, m.Document().Row(0))
106}

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