package highlight
import (
"strings"
"testing"
)
// The window carries only the lines asked for. Chroma emits a span per
// token, so a whole-file render of build.c was 40,083 DOM nodes; a
// screenful is a hundred or so.
func TestWindowHoldsOnlyItsLines(t *testing.T) {
src := []byte(strings.Repeat("int linex = 1;\n", 500))
lines, total, ok := Lines(src, "", "a.c", 0, 50)
if !ok {
t.Fatal("a C file was not coloured")
}
if len(lines) != 50 {
t.Errorf("window holds %d lines, want 50", len(lines))
}
// Reaching the end reports the total; stopping early does not
// pretend to know it.
if total != 0 {
t.Errorf("a window that stopped early reported total=%d, want 0", total)
}
// A final newline ends the last line; it does not start another.
if lines, total, _ = Lines(src, "", "a.c", 490, 10000); total != 500 || len(lines) != 10 {
t.Errorf("total = %d with %d lines, want 500 and 10", total, len(lines))
}
if _, total, _ = Lines([]byte("a\nb"), "", "a.c", 0, 10); total != 2 {
t.Errorf("no final newline: total = %d, want 2", total)
}
if lines, total, _ = Lines(nil, "", "a.c", 0, 10); total != 1 || len(lines) != 1 {
t.Errorf("empty file: total = %d lines %d, want one empty line", total, len(lines))
}
}
// A window opening inside a block comment must still be coloured as a
// comment. This is why lexing starts at byte zero even though only the
// window is emitted — highlighting a slice in isolation would call the
// comment body ordinary code.
func TestWindowKeepsLexerContext(t *testing.T) {
src := []byte("int a = 1;\n/* opening a long comment\n" +
strings.Repeat("still inside the comment\n", 40) +
"*/\nint b = 2;\n")
lines, _, _ := Lines(src, "", "a.c", 20, 5)
// chroma's comment classes begin with "c" (c, c1, cm, ...). The
// window sits deep inside the comment, so its content must carry
// one of them rather than being lexed as bare text.
if !strings.Contains(lines[0], `class="c`) {
t.Errorf("a window inside a block comment lost its context:\n%s", lines[0])
}
}
// Windows must tile: consecutive windows reproduce the file's lines in
// order, with nothing dropped or repeated.
func TestWindowsTile(t *testing.T) {
var b strings.Builder
for i := 0; i < 30; i++ {
b.WriteString("x := ")
b.WriteString(strings.Repeat("9", i%5+1))
b.WriteString("\n")
}
src := []byte(b.String())
var joined []string
for from := 0; from < 30; from += 7 {
lines, _, _ := Lines(src, "", "a.go", from, 7)
for _, l := range lines {
joined = append(joined, strip(l))
}
}
if got := strings.Join(joined, "\n") + "\n"; got != string(src) {
t.Errorf("tiled windows do not reproduce the file:\ngot: %q\nwant: %q", got, src)
}
}
// A file no lexer claims still gets its lines, uncoloured, so it can be
// numbered like any other.
func TestLinesForAnUnknownFile(t *testing.T) {
lines, total, coloured := Lines([]byte("one <two>\nthree\n"), "", "NOTES.zzz", 0, 10)
if coloured || total != 2 || len(lines) != 2 || lines[0] != "one <two>" {
t.Errorf("coloured=%v total=%d lines=%q", coloured, total, lines)
}
}
// strip removes tags, leaving the text a reader would see.
func strip(html string) string {
var out strings.Builder
depth := 0
for _, r := range html {
switch {
case r == '<':
depth++
case r == '>':
depth--
case depth == 0:
out.WriteRune(r)
}
}
return strings.NewReplacer(""", "\"", "<", "<", ">", ">", "&", "&").Replace(out.String())
}