mirror of
https://github.com/walles/moar.git
synced 2024-11-22 21:50:43 +03:00
edb3e51680
Should improve the performance situation reported here: https://github.com/walles/moar/issues/209
707 lines
21 KiB
Go
707 lines
21 KiB
Go
package m
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"regexp"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/alecthomas/chroma/v2"
|
|
"github.com/alecthomas/chroma/v2/formatters"
|
|
"github.com/alecthomas/chroma/v2/lexers"
|
|
"github.com/alecthomas/chroma/v2/styles"
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/walles/moar/m/linenumbers"
|
|
"github.com/walles/moar/m/textstyles"
|
|
"github.com/walles/moar/twin"
|
|
"gotest.tools/v3/assert"
|
|
)
|
|
|
|
//revive:disable:empty-block
|
|
|
|
const blueBackgroundClearToEol0 = "\x1b[44m\x1b[0K" // With 0 before the K, should clear to EOL
|
|
const blueBackgroundClearToEol = "\x1b[44m\x1b[K" // No 0 before the K, should also clear to EOL
|
|
|
|
func TestUnicodeRendering(t *testing.T) {
|
|
reader := NewReaderFromText("", "åäö")
|
|
|
|
var answers = []twin.Cell{
|
|
twin.NewCell('å', twin.StyleDefault),
|
|
twin.NewCell('ä', twin.StyleDefault),
|
|
twin.NewCell('ö', twin.StyleDefault),
|
|
}
|
|
|
|
contents := startPaging(t, reader).GetRow(0)
|
|
for pos, expected := range answers {
|
|
assertCellsEqual(t, expected, contents[pos])
|
|
}
|
|
}
|
|
|
|
func assertCellsEqual(t *testing.T, expected twin.Cell, actual twin.Cell) {
|
|
if actual.Rune == expected.Rune && actual.Style == expected.Style {
|
|
return
|
|
}
|
|
|
|
t.Errorf("Expected %v, got %v", expected, actual)
|
|
}
|
|
|
|
func TestFgColorRendering(t *testing.T) {
|
|
reader := NewReaderFromText("",
|
|
"\x1b[30ma\x1b[31mb\x1b[32mc\x1b[33md\x1b[34me\x1b[35mf\x1b[36mg\x1b[37mh\x1b[0mi")
|
|
|
|
var answers = []twin.Cell{
|
|
twin.NewCell('a', twin.StyleDefault.WithForeground(twin.NewColor16(0))),
|
|
twin.NewCell('b', twin.StyleDefault.WithForeground(twin.NewColor16(1))),
|
|
twin.NewCell('c', twin.StyleDefault.WithForeground(twin.NewColor16(2))),
|
|
twin.NewCell('d', twin.StyleDefault.WithForeground(twin.NewColor16(3))),
|
|
twin.NewCell('e', twin.StyleDefault.WithForeground(twin.NewColor16(4))),
|
|
twin.NewCell('f', twin.StyleDefault.WithForeground(twin.NewColor16(5))),
|
|
twin.NewCell('g', twin.StyleDefault.WithForeground(twin.NewColor16(6))),
|
|
twin.NewCell('h', twin.StyleDefault.WithForeground(twin.NewColor16(7))),
|
|
twin.NewCell('i', twin.StyleDefault),
|
|
}
|
|
|
|
contents := startPaging(t, reader).GetRow(0)
|
|
for pos, expected := range answers {
|
|
assertCellsEqual(t, expected, contents[pos])
|
|
}
|
|
}
|
|
|
|
func TestPageEmpty(t *testing.T) {
|
|
reader := NewReaderFromText("", "")
|
|
|
|
firstRowCells := startPaging(t, reader).GetRow(0)
|
|
|
|
// "---" is the eofSpinner of pager.go
|
|
assert.Equal(t, "---", rowToString(firstRowCells))
|
|
}
|
|
|
|
func TestBrokenUtf8(t *testing.T) {
|
|
// The broken UTF8 character in the middle is based on "©" = 0xc2a9
|
|
reader := NewReaderFromText("", "abc\xc2def")
|
|
|
|
var answers = []twin.Cell{
|
|
twin.NewCell('a', twin.StyleDefault),
|
|
twin.NewCell('b', twin.StyleDefault),
|
|
twin.NewCell('c', twin.StyleDefault),
|
|
twin.NewCell('?', twin.StyleDefault.WithForeground(twin.NewColor16(7)).WithBackground(twin.NewColor16(1))),
|
|
twin.NewCell('d', twin.StyleDefault),
|
|
twin.NewCell('e', twin.StyleDefault),
|
|
twin.NewCell('f', twin.StyleDefault),
|
|
}
|
|
|
|
contents := startPaging(t, reader).GetRow(0)
|
|
for pos, expected := range answers {
|
|
assertCellsEqual(t, expected, contents[pos])
|
|
}
|
|
}
|
|
|
|
func startPaging(t *testing.T, reader *Reader) *twin.FakeScreen {
|
|
err := reader._wait()
|
|
if err != nil {
|
|
t.Fatalf("Failed waiting for reader: %v", err)
|
|
}
|
|
|
|
screen := twin.NewFakeScreen(20, 10)
|
|
pager := NewPager(reader)
|
|
pager.ShowLineNumbers = false
|
|
|
|
// Tell our Pager to quit immediately
|
|
pager.Quit()
|
|
|
|
// Except for just quitting, this also associates our FakeScreen with the Pager
|
|
pager.StartPaging(screen, nil, nil)
|
|
|
|
// This makes sure at least one frame gets rendered
|
|
pager.redraw("")
|
|
|
|
return screen
|
|
}
|
|
|
|
// assertIndexOfFirstX verifies the (zero-based) index of the first 'x'
|
|
func assertIndexOfFirstX(t *testing.T, s string, expectedIndex int) {
|
|
reader := NewReaderFromText("", s)
|
|
|
|
contents := startPaging(t, reader).GetRow(0)
|
|
for pos, cell := range contents {
|
|
if cell.Rune != 'x' {
|
|
continue
|
|
}
|
|
|
|
if pos == expectedIndex {
|
|
// Success!
|
|
return
|
|
}
|
|
|
|
t.Errorf("Expected first 'x' to be at (zero-based) index %d, but was at %d: \"%s\"",
|
|
expectedIndex, pos, strings.ReplaceAll(s, "\x09", "<TAB>"))
|
|
return
|
|
}
|
|
|
|
panic("No 'x' found")
|
|
}
|
|
|
|
func TestTabHandling(t *testing.T) {
|
|
assertIndexOfFirstX(t, "x", 0)
|
|
|
|
assertIndexOfFirstX(t, "\x09x", 4)
|
|
assertIndexOfFirstX(t, "\x09\x09x", 8)
|
|
|
|
assertIndexOfFirstX(t, "J\x09x", 4)
|
|
assertIndexOfFirstX(t, "Jo\x09x", 4)
|
|
assertIndexOfFirstX(t, "Joh\x09x", 4)
|
|
assertIndexOfFirstX(t, "Joha\x09x", 8)
|
|
assertIndexOfFirstX(t, "Johan\x09x", 8)
|
|
|
|
assertIndexOfFirstX(t, "\x09J\x09x", 8)
|
|
assertIndexOfFirstX(t, "\x09Jo\x09x", 8)
|
|
assertIndexOfFirstX(t, "\x09Joh\x09x", 8)
|
|
assertIndexOfFirstX(t, "\x09Joha\x09x", 12)
|
|
assertIndexOfFirstX(t, "\x09Johan\x09x", 12)
|
|
}
|
|
|
|
func TestCodeHighlighting(t *testing.T) {
|
|
// From: https://coderwall.com/p/_fmbug/go-get-path-to-current-file
|
|
_, filename, _, ok := runtime.Caller(0)
|
|
if !ok {
|
|
panic("Getting current filename failed")
|
|
}
|
|
|
|
reader, err := NewReaderFromFilename(filename, *styles.Get("native"), formatters.TTY16m, nil)
|
|
assert.NilError(t, err)
|
|
assert.NilError(t, reader._wait())
|
|
|
|
packageKeywordStyle := twin.StyleDefault.WithAttr(twin.AttrBold).WithForeground(twin.NewColorHex(0x6AB825))
|
|
packageNameStyle := twin.StyleDefault.WithForeground(twin.NewColorHex(0xD0D0D0))
|
|
var answers = []twin.Cell{
|
|
twin.NewCell('p', packageKeywordStyle),
|
|
twin.NewCell('a', packageKeywordStyle),
|
|
twin.NewCell('c', packageKeywordStyle),
|
|
twin.NewCell('k', packageKeywordStyle),
|
|
twin.NewCell('a', packageKeywordStyle),
|
|
twin.NewCell('g', packageKeywordStyle),
|
|
twin.NewCell('e', packageKeywordStyle),
|
|
twin.NewCell(' ', packageNameStyle),
|
|
twin.NewCell('m', packageNameStyle),
|
|
}
|
|
|
|
contents := startPaging(t, reader).GetRow(0)
|
|
for pos, expected := range answers {
|
|
assertCellsEqual(t, expected, contents[pos])
|
|
}
|
|
}
|
|
|
|
func TestCodeHighlight_compressed(t *testing.T) {
|
|
// Same as TestCodeHighlighting but with "compressed-markdown.md.gz"
|
|
reader, err := NewReaderFromFilename("../sample-files/compressed-markdown.md.gz", *styles.Get("native"), formatters.TTY16m, nil)
|
|
assert.NilError(t, err)
|
|
assert.NilError(t, reader._wait())
|
|
|
|
markdownHeading1Style := twin.StyleDefault.WithAttr(twin.AttrBold).WithForeground(twin.NewColorHex(0xffffff))
|
|
var answers = []twin.Cell{
|
|
twin.NewCell('#', markdownHeading1Style),
|
|
twin.NewCell(' ', markdownHeading1Style),
|
|
twin.NewCell('M', markdownHeading1Style),
|
|
twin.NewCell('a', markdownHeading1Style),
|
|
twin.NewCell('r', markdownHeading1Style),
|
|
twin.NewCell('k', markdownHeading1Style),
|
|
twin.NewCell('d', markdownHeading1Style),
|
|
twin.NewCell('o', markdownHeading1Style),
|
|
twin.NewCell('w', markdownHeading1Style),
|
|
twin.NewCell('n', markdownHeading1Style),
|
|
}
|
|
|
|
contents := startPaging(t, reader).GetRow(0)
|
|
for pos, expected := range answers {
|
|
assertCellsEqual(t, expected, contents[pos])
|
|
}
|
|
}
|
|
|
|
func TestUnicodePrivateUse(t *testing.T) {
|
|
// This character lives in a Private Use Area:
|
|
// https://codepoints.net/U+f244
|
|
//
|
|
// It's used by Font Awesome as "fa-battery-empty":
|
|
// https://fontawesome.com/v4/icon/battery-empty
|
|
char := '\uf244'
|
|
|
|
reader := NewReaderFromText("hello", string(char))
|
|
renderedCell := startPaging(t, reader).GetRow(0)[0]
|
|
|
|
// Make sure we display this character unmodified
|
|
assertCellsEqual(t, twin.NewCell(char, twin.StyleDefault), renderedCell)
|
|
}
|
|
|
|
func resetManPageFormat() {
|
|
textstyles.ManPageBold = twin.StyleDefault.WithAttr(twin.AttrBold)
|
|
textstyles.ManPageUnderline = twin.StyleDefault.WithAttr(twin.AttrUnderline)
|
|
}
|
|
|
|
func testManPageFormatting(t *testing.T, input string, expected twin.Cell) {
|
|
reader := NewReaderFromText("", input)
|
|
|
|
// Without these lines the man page tests will fail if either of these
|
|
// environment variables are set when the tests are run.
|
|
assert.NilError(t, os.Setenv("LESS_TERMCAP_md", ""))
|
|
assert.NilError(t, os.Setenv("LESS_TERMCAP_us", ""))
|
|
assert.NilError(t, os.Setenv("LESS_TERMCAP_so", ""))
|
|
resetManPageFormat()
|
|
|
|
contents := startPaging(t, reader).GetRow(0)
|
|
assertCellsEqual(t, expected, contents[0])
|
|
assert.Equal(t, contents[1].Rune, ' ')
|
|
}
|
|
|
|
func TestManPageFormatting(t *testing.T) {
|
|
testManPageFormatting(t, "n\x08n", twin.NewCell('n', twin.StyleDefault.WithAttr(twin.AttrBold)))
|
|
testManPageFormatting(t, "_\x08x", twin.NewCell('x', twin.StyleDefault.WithAttr(twin.AttrUnderline)))
|
|
|
|
// Non-breaking space UTF-8 encoded (0xc2a0) should render as a non-breaking unicode space (0xa0)
|
|
testManPageFormatting(t, string([]byte{0xc2, 0xa0}), twin.NewCell(rune(0xa0), twin.StyleDefault))
|
|
|
|
// Corner cases
|
|
testManPageFormatting(t, "\x08", twin.NewCell('<', twin.StyleDefault.WithForeground(twin.NewColor16(7)).WithBackground(twin.NewColor16(1))))
|
|
|
|
// FIXME: Test two consecutive backspaces
|
|
|
|
// FIXME: Test backspace between two uncombinable characters
|
|
}
|
|
|
|
func TestToPattern(t *testing.T) {
|
|
assert.Assert(t, toPattern("") == nil)
|
|
|
|
// Test regexp matching
|
|
assert.Assert(t, toPattern("G.*S").MatchString("GRIIIS"))
|
|
assert.Assert(t, !toPattern("G.*S").MatchString("gRIIIS"))
|
|
|
|
// Test case insensitive regexp matching
|
|
assert.Assert(t, toPattern("g.*s").MatchString("GRIIIS"))
|
|
assert.Assert(t, toPattern("g.*s").MatchString("gRIIIS"))
|
|
|
|
// Test non-regexp matching
|
|
assert.Assert(t, toPattern(")G").MatchString(")G"))
|
|
assert.Assert(t, !toPattern(")G").MatchString(")g"))
|
|
|
|
// Test case insensitive non-regexp matching
|
|
assert.Assert(t, toPattern(")g").MatchString(")G"))
|
|
assert.Assert(t, toPattern(")g").MatchString(")g"))
|
|
}
|
|
|
|
func TestFindFirstHitSimple(t *testing.T) {
|
|
reader := NewReaderFromText("TestFindFirstHitSimple", "AB")
|
|
pager := NewPager(reader)
|
|
pager.screen = twin.NewFakeScreen(40, 10)
|
|
|
|
assert.NilError(t, pager.reader._wait())
|
|
|
|
pager.searchPattern = toPattern("AB")
|
|
|
|
hit := pager.findFirstHit(linenumbers.LineNumber{}, nil, false)
|
|
assert.Assert(t, hit.internalDontTouch.lineNumber.IsZero())
|
|
assert.Equal(t, hit.internalDontTouch.deltaScreenLines, 0)
|
|
}
|
|
|
|
func TestFindFirstHitAnsi(t *testing.T) {
|
|
reader := NewReaderFromText("", "A\x1b[30mB")
|
|
pager := NewPager(reader)
|
|
pager.screen = twin.NewFakeScreen(40, 10)
|
|
|
|
assert.NilError(t, pager.reader._wait())
|
|
|
|
pager.searchPattern = toPattern("AB")
|
|
|
|
hit := pager.findFirstHit(linenumbers.LineNumber{}, nil, false)
|
|
assert.Assert(t, hit.internalDontTouch.lineNumber.IsZero())
|
|
assert.Equal(t, hit.internalDontTouch.deltaScreenLines, 0)
|
|
}
|
|
|
|
func TestFindFirstHitNoMatch(t *testing.T) {
|
|
reader := NewReaderFromText("TestFindFirstHitSimple", "AB")
|
|
pager := NewPager(reader)
|
|
pager.screen = twin.NewFakeScreen(40, 10)
|
|
|
|
assert.NilError(t, pager.reader._wait())
|
|
|
|
pager.searchPattern = toPattern("this pattern should not be found")
|
|
|
|
hit := pager.findFirstHit(linenumbers.LineNumber{}, nil, false)
|
|
assert.Assert(t, hit == nil)
|
|
}
|
|
|
|
func TestFindFirstHitNoMatchBackwards(t *testing.T) {
|
|
reader := NewReaderFromText("TestFindFirstHitSimple", "AB")
|
|
pager := NewPager(reader)
|
|
pager.screen = twin.NewFakeScreen(40, 10)
|
|
|
|
assert.NilError(t, pager.reader._wait())
|
|
|
|
pager.searchPattern = toPattern("this pattern should not be found")
|
|
theEnd := *linenumbers.LineNumberFromLength(reader.GetLineCount())
|
|
|
|
hit := pager.findFirstHit(theEnd, nil, true)
|
|
assert.Assert(t, hit == nil)
|
|
}
|
|
|
|
// Converts a cell row to a plain string and removes trailing whitespace.
|
|
func rowToString(row []twin.Cell) string {
|
|
rowString := ""
|
|
for _, cell := range row {
|
|
rowString += string(cell.Rune)
|
|
}
|
|
|
|
return strings.TrimRight(rowString, " ")
|
|
}
|
|
|
|
func TestScrollToBottomWrapNextToLastLine(t *testing.T) {
|
|
reader := NewReaderFromText("",
|
|
"first line\nline two will be wrapped\nhere's the last line")
|
|
|
|
// Heigh 3 = two lines of contents + one footer
|
|
screen := twin.NewFakeScreen(10, 3)
|
|
|
|
pager := NewPager(reader)
|
|
pager.WrapLongLines = true
|
|
pager.ShowLineNumbers = false
|
|
pager.screen = screen
|
|
|
|
assert.NilError(t, pager.reader._wait())
|
|
|
|
// This is what we're testing really
|
|
pager.scrollToEnd()
|
|
|
|
// Exit immediately
|
|
pager.Quit()
|
|
|
|
// Get contents onto our fake screen
|
|
pager.StartPaging(screen, nil, nil)
|
|
pager.redraw("")
|
|
|
|
actual := strings.Join([]string{
|
|
rowToString(screen.GetRow(0)),
|
|
rowToString(screen.GetRow(1)),
|
|
rowToString(screen.GetRow(2)),
|
|
}, "\n")
|
|
expected := strings.Join([]string{
|
|
"here's the",
|
|
"last line",
|
|
"3 lines 1", // "3 lines 100%" clipped after 10 characters (screen width)
|
|
}, "\n")
|
|
assert.Equal(t, actual, expected)
|
|
}
|
|
|
|
// Repro for https://github.com/walles/moar/issues/105
|
|
func TestScrollToEndLongInput(t *testing.T) {
|
|
const lineCount = 10100 // At least five digits
|
|
|
|
// "X" marks the spot
|
|
reader := NewReaderFromText("test", strings.Repeat(".\n", lineCount-1)+"X")
|
|
pager := NewPager(reader)
|
|
pager.ShowLineNumbers = true
|
|
|
|
// Tell our Pager to quit immediately
|
|
pager.Quit()
|
|
|
|
// Connect the pager with a screen
|
|
const screenHeight = 10
|
|
screen := twin.NewFakeScreen(20, screenHeight)
|
|
pager.StartPaging(screen, nil, nil)
|
|
|
|
// This is what we're really testing
|
|
pager.scrollToEnd()
|
|
|
|
// This makes sure at least one frame gets rendered
|
|
pager.redraw("")
|
|
|
|
// The last screen line holds the status field, and the next to last screen
|
|
// line holds the last contents line.
|
|
lastContentsLine := screen.GetRow(screenHeight - 2)
|
|
firstContentsColumn := len("10_100 ")
|
|
assertCellsEqual(t, twin.NewCell('X', twin.StyleDefault), lastContentsLine[firstContentsColumn])
|
|
}
|
|
|
|
func TestIsScrolledToEnd_LongFile(t *testing.T) {
|
|
// Six lines of contents
|
|
reader := NewReaderFromText("Testing", "a\nb\nc\nd\ne\nf\n")
|
|
|
|
// Three lines screen
|
|
screen := twin.NewFakeScreen(20, 3)
|
|
|
|
// Create the pager
|
|
pager := NewPager(reader)
|
|
pager.screen = screen
|
|
|
|
assert.Equal(t, false, pager.isScrolledToEnd())
|
|
|
|
pager.scrollToEnd()
|
|
assert.Equal(t, true, pager.isScrolledToEnd())
|
|
}
|
|
|
|
func TestIsScrolledToEnd_ShortFile(t *testing.T) {
|
|
// Three lines of contents
|
|
reader := NewReaderFromText("Testing", "a\nb\nc")
|
|
|
|
// Six lines screen
|
|
screen := twin.NewFakeScreen(20, 6)
|
|
|
|
// Create the pager
|
|
pager := NewPager(reader)
|
|
pager.screen = screen
|
|
|
|
assert.Equal(t, true, pager.isScrolledToEnd())
|
|
|
|
pager.scrollToEnd()
|
|
assert.Equal(t, true, pager.isScrolledToEnd())
|
|
}
|
|
|
|
func TestIsScrolledToEnd_ExactFile(t *testing.T) {
|
|
// Three lines of contents
|
|
reader := NewReaderFromText("Testing", "a\nb\nc")
|
|
|
|
// Three lines screen
|
|
screen := twin.NewFakeScreen(20, 3)
|
|
|
|
// Create the pager
|
|
pager := NewPager(reader)
|
|
pager.screen = screen
|
|
pager.ShowStatusBar = false
|
|
|
|
assert.Equal(t, true, pager.isScrolledToEnd())
|
|
|
|
pager.scrollToEnd()
|
|
assert.Equal(t, true, pager.isScrolledToEnd())
|
|
}
|
|
|
|
func TestIsScrolledToEnd_WrappedLastLine(t *testing.T) {
|
|
// Three lines of contents
|
|
reader := NewReaderFromText("Testing", "a\nb\nc d e f g h i j k l m n")
|
|
|
|
// Three lines screen
|
|
screen := twin.NewFakeScreen(5, 3)
|
|
|
|
// Create the pager
|
|
pager := NewPager(reader)
|
|
pager.screen = screen
|
|
pager.WrapLongLines = true
|
|
|
|
assert.Equal(t, false, pager.isScrolledToEnd())
|
|
|
|
pager.scrollToEnd()
|
|
assert.Equal(t, true, pager.isScrolledToEnd())
|
|
|
|
pager.mode.onKey(twin.KeyUp)
|
|
pager.redraw("XXX")
|
|
assert.Equal(t, false, pager.isScrolledToEnd())
|
|
}
|
|
|
|
func TestIsScrolledToEnd_EmptyFile(t *testing.T) {
|
|
// No contents
|
|
reader := NewReaderFromText("Testing", "")
|
|
|
|
// Three lines screen
|
|
screen := twin.NewFakeScreen(20, 3)
|
|
|
|
// Create the pager
|
|
pager := NewPager(reader)
|
|
pager.screen = screen
|
|
|
|
assert.Equal(t, true, pager.isScrolledToEnd())
|
|
|
|
pager.scrollToEnd()
|
|
assert.Equal(t, true, pager.isScrolledToEnd())
|
|
}
|
|
|
|
// Verify that we can page all files in ../sample-files/* without crashing
|
|
func TestPageSamples(t *testing.T) {
|
|
for _, fileName := range getTestFiles(t) {
|
|
t.Run(fileName, func(t *testing.T) {
|
|
file, err := os.Open(fileName)
|
|
if err != nil {
|
|
t.Errorf("Error opening file <%s>: %s", fileName, err.Error())
|
|
return
|
|
}
|
|
defer func() {
|
|
if err := file.Close(); err != nil {
|
|
panic(err)
|
|
}
|
|
}()
|
|
|
|
myReader := NewReaderFromStream(fileName, file, chroma.Style{}, nil, nil)
|
|
assert.NilError(t, myReader._wait())
|
|
|
|
pager := NewPager(myReader)
|
|
pager.WrapLongLines = false
|
|
pager.ShowLineNumbers = false
|
|
|
|
// Heigh 3 = two lines of contents + one footer
|
|
screen := twin.NewFakeScreen(10, 3)
|
|
|
|
// Exit immediately
|
|
pager.Quit()
|
|
|
|
// Get contents onto our fake screen
|
|
pager.StartPaging(screen, nil, nil)
|
|
pager.redraw("")
|
|
|
|
firstReaderLine := myReader.GetLine(linenumbers.LineNumber{})
|
|
if firstReaderLine == nil {
|
|
return
|
|
}
|
|
firstPagerLine := rowToString(screen.GetRow(0))
|
|
|
|
// Handle the case when first line is chopped off to the right
|
|
firstPagerLine = strings.TrimSuffix(firstPagerLine, ">")
|
|
|
|
assert.Assert(t,
|
|
strings.HasPrefix(firstReaderLine.Plain(nil), firstPagerLine),
|
|
"\nreader line = <%s>\npager line = <%s>",
|
|
firstReaderLine.Plain(nil), firstPagerLine,
|
|
)
|
|
})
|
|
}
|
|
}
|
|
|
|
// Validate rendering of https://en.wikipedia.org/wiki/ANSI_escape_code#EL
|
|
func TestClearToEndOfLine_ClearFromStart(t *testing.T) {
|
|
screen := startPaging(t, NewReaderFromText("TestClearToEol", blueBackgroundClearToEol))
|
|
|
|
screenWidth, _ := screen.Size()
|
|
var expected []twin.Cell
|
|
for len(expected) < screenWidth {
|
|
expected = append(expected,
|
|
twin.NewCell(' ', twin.StyleDefault.WithBackground(twin.NewColor16(4))),
|
|
)
|
|
}
|
|
|
|
actual := screen.GetRow(0)
|
|
assert.DeepEqual(t, actual, expected, cmp.AllowUnexported(twin.Style{}))
|
|
}
|
|
|
|
// Validate rendering of https://en.wikipedia.org/wiki/ANSI_escape_code#EL
|
|
func TestClearToEndOfLine_ClearFromNotStart(t *testing.T) {
|
|
screen := startPaging(t, NewReaderFromText("TestClearToEol", "a"+blueBackgroundClearToEol))
|
|
|
|
screenWidth, _ := screen.Size()
|
|
expected := []twin.Cell{
|
|
twin.NewCell('a', twin.StyleDefault),
|
|
}
|
|
for len(expected) < screenWidth {
|
|
expected = append(expected,
|
|
twin.NewCell(' ', twin.StyleDefault.WithBackground(twin.NewColor16(4))),
|
|
)
|
|
}
|
|
|
|
actual := screen.GetRow(0)
|
|
assert.DeepEqual(t, actual, expected, cmp.AllowUnexported(twin.Style{}))
|
|
}
|
|
|
|
// Validate rendering of https://en.wikipedia.org/wiki/ANSI_escape_code#EL
|
|
func TestClearToEndOfLine_ClearFromStartScrolledRight(t *testing.T) {
|
|
pager := NewPager(NewReaderFromText("TestClearToEol", blueBackgroundClearToEol0))
|
|
pager.ShowLineNumbers = false
|
|
|
|
// Tell our Pager to quit immediately
|
|
pager.Quit()
|
|
|
|
// Except for just quitting, this also associates a FakeScreen with the Pager
|
|
screen := twin.NewFakeScreen(3, 10)
|
|
pager.StartPaging(screen, nil, nil)
|
|
|
|
// Scroll right, this is what we're testing
|
|
pager.leftColumnZeroBased = 44
|
|
|
|
// This makes sure at least one frame gets rendered
|
|
pager.redraw("")
|
|
|
|
screenWidth, _ := screen.Size()
|
|
var expected []twin.Cell
|
|
for len(expected) < screenWidth {
|
|
expected = append(expected,
|
|
twin.NewCell(' ', twin.StyleDefault.WithBackground(twin.NewColor16(4))),
|
|
)
|
|
}
|
|
|
|
actual := screen.GetRow(0)
|
|
assert.DeepEqual(t, actual, expected, cmp.AllowUnexported(twin.Style{}))
|
|
}
|
|
|
|
func TestGetLineColorPrefix(t *testing.T) {
|
|
assert.Equal(t,
|
|
getLineColorPrefix(styles.Registry["gruvbox"], &formatters.TTY16m),
|
|
"\x1b[38;2;235;219;178m",
|
|
)
|
|
}
|
|
|
|
func TestInitStyle256(t *testing.T) {
|
|
assert.Equal(t,
|
|
getLineColorPrefix(
|
|
styles.Registry["catppuccin-macchiato"],
|
|
&formatters.TTY256), "\x1b[38;5;189m",
|
|
)
|
|
}
|
|
|
|
func benchmarkSearch(b *testing.B, highlighted bool) {
|
|
// Pick a go file so we get something with highlighting
|
|
_, sourceFilename, _, ok := runtime.Caller(0)
|
|
if !ok {
|
|
panic("Getting current filename failed")
|
|
}
|
|
|
|
sourceBytes, err := os.ReadFile(sourceFilename)
|
|
assert.NilError(b, err)
|
|
fileContents := string(sourceBytes)
|
|
|
|
// Read one copy of the example input
|
|
if highlighted {
|
|
highlightedSourceCode, err := highlight(fileContents, *styles.Get("native"), formatters.TTY16m, lexers.Get("go"))
|
|
assert.NilError(b, err)
|
|
if highlightedSourceCode == nil {
|
|
panic("Highlighting didn't want to, returned nil")
|
|
}
|
|
fileContents = *highlightedSourceCode
|
|
}
|
|
|
|
// Duplicate data N times
|
|
testString := ""
|
|
for n := 0; n < b.N; n++ {
|
|
testString += fileContents
|
|
}
|
|
|
|
reader := NewReaderFromText("hello", testString)
|
|
pager := NewPager(reader)
|
|
pager.screen = twin.NewFakeScreen(40, 10)
|
|
|
|
// The [] around the 't' is there to make sure it doesn't match, remember
|
|
// we're searching through this very file.
|
|
pager.searchPattern = regexp.MustCompile("This won'[t] match anything")
|
|
|
|
// I hope forcing a GC here will make numbers more predictable
|
|
runtime.GC()
|
|
|
|
b.ResetTimer()
|
|
|
|
// This test will search through all the N copies we made of our file
|
|
hit := pager.findFirstHit(linenumbers.LineNumber{}, nil, false)
|
|
|
|
if hit != nil {
|
|
panic(fmt.Errorf("This test is meant to scan the whole file without finding anything"))
|
|
}
|
|
}
|
|
|
|
// How long does it take to search a highlighted file for some regex?
|
|
//
|
|
// Run with: go test -run='^$' -bench=. . ./...
|
|
func BenchmarkHighlightedSearch(b *testing.B) {
|
|
benchmarkSearch(b, true)
|
|
}
|
|
|
|
// How long does it take to search a plain text file for some regex?
|
|
//
|
|
// Search performance was a problem for me when I had a 600MB file to search in.
|
|
//
|
|
// Run with: go test -run='^$' -bench=. . ./...
|
|
func BenchmarkPlainTextSearch(b *testing.B) {
|
|
benchmarkSearch(b, false)
|
|
}
|