1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
|
package editor
import (
"testing"
"glint/internal/spell"
"glint/internal/theme"
)
// grammarSpans returns, for each Wavy span in the built visual model, its text
// mapped to its undercurl color โ enough to tell grammar (green) from spell (red).
func grammarSpans(e *Editor) map[string]string {
got := map[string]string{}
for _, vr := range e.buildVisual() {
for _, sp := range vr.spans {
if sp.Wavy {
got[sp.Text] = string(sp.UnderColor)
}
}
}
return got
}
func grammarEditor(t *testing.T, content string) *Editor {
t.Helper()
e := New()
e.SetTheme(theme.FlexokiDark())
e.SetContent([]byte(content))
e.SetGrammar(true)
return e
}
func TestGrammarUnderlinesRange(t *testing.T) {
e := grammarEditor(t, "This is a a test")
// "a a" spans rune columns [8,11).
e.SetGrammarDiags(map[int][][2]int{0: {{8, 11}}})
green := string(theme.FlexokiDark().Grammar)
spans := grammarSpans(e)
if spans["a a"] != green {
t.Errorf("expected \"a a\" underlined green (%s); got wavy spans %v", green, spans)
}
}
func TestGrammarSpanAt(t *testing.T) {
e := grammarEditor(t, "This is a a test")
e.SetGrammarDiags(map[int][][2]int{0: {{8, 11}}})
// Inside and at both boundaries (end inclusive) resolves the span.
for _, col := range []int{8, 9, 11} {
s, en, ok := e.GrammarSpanAt(0, col)
if !ok || s != 8 || en != 11 {
t.Errorf("GrammarSpanAt(0,%d) = %d,%d,%v; want 8,11,true", col, s, en, ok)
}
}
// Outside the span, and when grammar is off.
if _, _, ok := e.GrammarSpanAt(0, 3); ok {
t.Error("col 3 should not be in a grammar span")
}
e.SetGrammar(false)
if _, _, ok := e.GrammarSpanAt(0, 9); ok {
t.Error("grammar off should report no span")
}
}
func TestReplaceRuneRangeSingleLine(t *testing.T) {
e := grammarEditor(t, "This is a a test")
e.ReplaceRuneRange(0, 8, 0, 11, "a") // collapse "a a" -> "a"
if got := string(e.Bytes()); got != "This is a test" {
t.Errorf("got %q, want %q", got, "This is a test")
}
if e.Cursor.Row != 0 || e.Cursor.Col != 9 {
t.Errorf("cursor = %d,%d; want 0,9 (after inserted text)", e.Cursor.Row, e.Cursor.Col)
}
if !e.Dirty {
t.Error("replacement should mark the buffer dirty")
}
}
func TestReplaceRuneRangeMultiLineInsert(t *testing.T) {
e := grammarEditor(t, "one two three")
// Replace "two" (runes [4,7)) with a two-line insertion.
e.ReplaceRuneRange(0, 4, 0, 7, "X\nY")
if got := string(e.Bytes()); got != "one X\nY three" {
t.Errorf("got %q, want %q", got, "one X\nY three")
}
if e.Cursor.Row != 1 || e.Cursor.Col != 1 {
t.Errorf("cursor = %d,%d; want 1,1", e.Cursor.Row, e.Cursor.Col)
}
}
func TestReplaceRuneRangeUndo(t *testing.T) {
e := grammarEditor(t, "This is a a test")
e.ReplaceRuneRange(0, 8, 0, 11, "a")
e.Undo()
if got := string(e.Bytes()); got != "This is a a test" {
t.Errorf("undo left %q, want original", got)
}
}
func TestRuneRangeText(t *testing.T) {
e := grammarEditor(t, "This is a a test")
if got := e.RuneRangeText(0, 8, 11); got != "a a" {
t.Errorf("RuneRangeText = %q, want %q", got, "a a")
}
if got := e.RuneRangeText(0, 8, 999); got != "a a test" {
t.Errorf("clamped RuneRangeText = %q", got)
}
if got := e.RuneRangeText(5, 0, 3); got != "" {
t.Errorf("out-of-range row = %q, want empty", got)
}
}
func TestGrammarInactiveWhenOff(t *testing.T) {
e := grammarEditor(t, "This is a a test")
e.SetGrammarDiags(map[int][][2]int{0: {{8, 11}}})
e.SetGrammar(false)
if s := grammarSpans(e); len(s) != 0 {
t.Errorf("grammar off should render no undercurl; got %v", s)
}
}
func TestGrammarInactiveOnCodeFile(t *testing.T) {
e := grammarEditor(t, "This is a a test")
e.SetLanguage("main.go") // code file: grammar skipped like spellcheck
e.SetGrammarDiags(map[int][][2]int{0: {{8, 11}}})
if s := grammarSpans(e); len(s) != 0 {
t.Errorf("grammar should skip code files; got %v", s)
}
}
// TestSpellingWinsOverGrammar asserts a misspelled word inside a grammar range
// keeps its red spell underline rather than being repainted green.
func TestSpellingWinsOverGrammar(t *testing.T) {
d, err := spell.Load()
if err != nil {
t.Fatalf("spell.Load: %v", err)
}
e := New()
e.SetTheme(theme.FlexokiDark())
e.SetContent([]byte("This recieve is wrong"))
e.SetDict(d)
e.SetSpell(true)
e.SetGrammar(true)
// "recieve" is a misspelling at rune columns [5,12); cover it with a grammar range.
e.SetGrammarDiags(map[int][][2]int{0: {{5, 12}}})
spans := grammarSpans(e)
red := string(theme.FlexokiDark().Spell)
if spans["recieve"] != red {
t.Errorf("misspelled \"recieve\" should stay red (%s), not grammar green; got %v", red, spans)
}
}
|