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}