mirror of
https://github.com/walles/moar.git
synced 2024-11-21 16:04:20 +03:00
Start telling screen cells from styled runes
One rune can cover multiple screen cells.
This commit is contained in:
parent
bac6d0d80e
commit
e0e9ee6610
18
m/line.go
18
m/line.go
@ -24,15 +24,15 @@ func NewLine(raw string) Line {
|
||||
|
||||
// Returns a representation of the string split into styled tokens. Any regexp
|
||||
// matches are highlighted. A nil regexp means no highlighting.
|
||||
func (line *Line) HighlightedTokens(linePrefix string, search *regexp.Regexp, lineNumber *linenumbers.LineNumber) textstyles.CellsWithTrailer {
|
||||
func (line *Line) HighlightedTokens(linePrefix string, search *regexp.Regexp, lineNumber *linenumbers.LineNumber) textstyles.StyledRunesWithTrailer {
|
||||
plain := line.Plain(lineNumber)
|
||||
matchRanges := getMatchRanges(&plain, search)
|
||||
|
||||
fromString := textstyles.CellsFromString(linePrefix, line.raw, lineNumber)
|
||||
returnCells := make([]twin.Cell, 0, len(fromString.Cells))
|
||||
for _, token := range fromString.Cells {
|
||||
fromString := textstyles.StyledRunesFromString(linePrefix, line.raw, lineNumber)
|
||||
returnRunes := make([]twin.StyledRune, 0, len(fromString.StyledRunes))
|
||||
for _, token := range fromString.StyledRunes {
|
||||
style := token.Style
|
||||
if matchRanges.InRange(len(returnCells)) {
|
||||
if matchRanges.InRange(len(returnRunes)) {
|
||||
if standoutStyle != nil {
|
||||
style = *standoutStyle
|
||||
} else {
|
||||
@ -42,15 +42,15 @@ func (line *Line) HighlightedTokens(linePrefix string, search *regexp.Regexp, li
|
||||
}
|
||||
}
|
||||
|
||||
returnCells = append(returnCells, twin.Cell{
|
||||
returnRunes = append(returnRunes, twin.StyledRune{
|
||||
Rune: token.Rune,
|
||||
Style: style,
|
||||
})
|
||||
}
|
||||
|
||||
return textstyles.CellsWithTrailer{
|
||||
Cells: returnCells,
|
||||
Trailer: fromString.Trailer,
|
||||
return textstyles.StyledRunesWithTrailer{
|
||||
StyledRunes: returnRunes,
|
||||
Trailer: fromString.Trailer,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,8 +25,8 @@ func TestHighlightedTokensWithManPageHeading(t *testing.T) {
|
||||
line := NewLine(manPageHeading)
|
||||
highlighted := line.HighlightedTokens(prefix, nil, nil)
|
||||
|
||||
assert.Equal(t, len(highlighted.Cells), len(headingText))
|
||||
for i, cell := range highlighted.Cells {
|
||||
assert.Equal(t, len(highlighted.StyledRunes), len(headingText))
|
||||
for i, cell := range highlighted.StyledRunes {
|
||||
assert.Equal(t, cell.Rune, rune(headingText[i]))
|
||||
assert.Equal(t, cell.Style, textstyles.ManPageHeading)
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
//revive:disable-next-line:var-naming
|
||||
const NO_BREAK_SPACE = '\xa0'
|
||||
|
||||
func getWrapWidth(line []twin.Cell, maxWrapWidth int) int {
|
||||
func getWrapWidth(line []twin.StyledRune, maxWrapWidth int) int {
|
||||
if len(line) <= maxWrapWidth {
|
||||
panic(fmt.Errorf("cannot compute wrap width when input isn't longer than max (%d<=%d)",
|
||||
len(line), maxWrapWidth))
|
||||
@ -49,16 +49,16 @@ func getWrapWidth(line []twin.Cell, maxWrapWidth int) int {
|
||||
return maxWrapWidth
|
||||
}
|
||||
|
||||
func wrapLine(width int, line []twin.Cell) [][]twin.Cell {
|
||||
func wrapLine(width int, line []twin.StyledRune) [][]twin.StyledRune {
|
||||
// Trailing space risks showing up by itself on a line, which would just
|
||||
// look weird.
|
||||
line = twin.TrimSpaceRight(line)
|
||||
|
||||
if len(line) == 0 {
|
||||
return [][]twin.Cell{{}}
|
||||
return [][]twin.StyledRune{{}}
|
||||
}
|
||||
|
||||
wrapped := make([][]twin.Cell, 0, len(line)/width)
|
||||
wrapped := make([][]twin.StyledRune, 0, len(line)/width)
|
||||
for len(line) > width {
|
||||
wrapWidth := getWrapWidth(line, width)
|
||||
firstPart := line[:wrapWidth]
|
||||
|
@ -7,12 +7,12 @@ import (
|
||||
"github.com/walles/moar/twin"
|
||||
)
|
||||
|
||||
func tokenize(input string) []twin.Cell {
|
||||
func tokenize(input string) []twin.StyledRune {
|
||||
line := NewLine(input)
|
||||
return line.HighlightedTokens("", nil, nil).Cells
|
||||
return line.HighlightedTokens("", nil, nil).StyledRunes
|
||||
}
|
||||
|
||||
func rowsToString(cellLines [][]twin.Cell) string {
|
||||
func rowsToString(cellLines [][]twin.StyledRune) string {
|
||||
returnMe := ""
|
||||
for _, cellLine := range cellLines {
|
||||
lineString := ""
|
||||
@ -33,7 +33,7 @@ func assertWrap(t *testing.T, input string, widthInScreenCells int, wrappedLines
|
||||
toWrap := tokenize(input)
|
||||
actual := wrapLine(widthInScreenCells, toWrap)
|
||||
|
||||
expected := [][]twin.Cell{}
|
||||
expected := [][]twin.StyledRune{}
|
||||
for _, wrappedLine := range wrappedLines {
|
||||
expected = append(expected, tokenize(wrappedLine))
|
||||
}
|
||||
|
12
m/pager.go
12
m/pager.go
@ -77,8 +77,8 @@ type Pager struct {
|
||||
QuitIfOneScreen bool
|
||||
|
||||
// Ref: https://github.com/walles/moar/issues/94
|
||||
ScrollLeftHint twin.Cell
|
||||
ScrollRightHint twin.Cell
|
||||
ScrollLeftHint twin.StyledRune
|
||||
ScrollRightHint twin.StyledRune
|
||||
|
||||
SideScrollAmount int // Should be positive
|
||||
|
||||
@ -184,8 +184,8 @@ func NewPager(r *Reader) *Pager {
|
||||
ShowStatusBar: true,
|
||||
DeInit: true,
|
||||
SideScrollAmount: 16,
|
||||
ScrollLeftHint: twin.NewCell('<', twin.StyleDefault.WithAttr(twin.AttrReverse)),
|
||||
ScrollRightHint: twin.NewCell('>', twin.StyleDefault.WithAttr(twin.AttrReverse)),
|
||||
ScrollLeftHint: twin.NewStyledRune('<', twin.StyleDefault.WithAttr(twin.AttrReverse)),
|
||||
ScrollRightHint: twin.NewStyledRune('>', twin.StyleDefault.WithAttr(twin.AttrReverse)),
|
||||
scrollPosition: newScrollPosition(name),
|
||||
}
|
||||
|
||||
@ -210,12 +210,12 @@ func (p *Pager) setFooter(footer string) {
|
||||
|
||||
pos := 0
|
||||
for _, token := range footer {
|
||||
p.screen.SetCell(pos, height-1, twin.NewCell(token, statusbarStyle))
|
||||
p.screen.SetCell(pos, height-1, twin.NewStyledRune(token, statusbarStyle))
|
||||
pos++
|
||||
}
|
||||
|
||||
for ; pos < width; pos++ {
|
||||
p.screen.SetCell(pos, height-1, twin.NewCell(' ', statusbarStyle))
|
||||
p.screen.SetCell(pos, height-1, twin.NewStyledRune(' ', statusbarStyle))
|
||||
}
|
||||
}
|
||||
|
||||
|
134
m/pager_test.go
134
m/pager_test.go
@ -27,19 +27,19 @@ const blueBackgroundClearToEol = "\x1b[44m\x1b[K" // No 0 before the K, should
|
||||
func TestUnicodeRendering(t *testing.T) {
|
||||
reader := NewReaderFromText("", "åäö")
|
||||
|
||||
var answers = []twin.Cell{
|
||||
twin.NewCell('å', twin.StyleDefault),
|
||||
twin.NewCell('ä', twin.StyleDefault),
|
||||
twin.NewCell('ö', twin.StyleDefault),
|
||||
var answers = []twin.StyledRune{
|
||||
twin.NewStyledRune('å', twin.StyleDefault),
|
||||
twin.NewStyledRune('ä', twin.StyleDefault),
|
||||
twin.NewStyledRune('ö', twin.StyleDefault),
|
||||
}
|
||||
|
||||
contents := startPaging(t, reader).GetRow(0)
|
||||
for pos, expected := range answers {
|
||||
assertCellsEqual(t, expected, contents[pos])
|
||||
assertRunesEqual(t, expected, contents[pos])
|
||||
}
|
||||
}
|
||||
|
||||
func assertCellsEqual(t *testing.T, expected twin.Cell, actual twin.Cell) {
|
||||
func assertRunesEqual(t *testing.T, expected twin.StyledRune, actual twin.StyledRune) {
|
||||
if actual.Rune == expected.Rune && actual.Style == expected.Style {
|
||||
return
|
||||
}
|
||||
@ -51,21 +51,21 @@ 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),
|
||||
var answers = []twin.StyledRune{
|
||||
twin.NewStyledRune('a', twin.StyleDefault.WithForeground(twin.NewColor16(0))),
|
||||
twin.NewStyledRune('b', twin.StyleDefault.WithForeground(twin.NewColor16(1))),
|
||||
twin.NewStyledRune('c', twin.StyleDefault.WithForeground(twin.NewColor16(2))),
|
||||
twin.NewStyledRune('d', twin.StyleDefault.WithForeground(twin.NewColor16(3))),
|
||||
twin.NewStyledRune('e', twin.StyleDefault.WithForeground(twin.NewColor16(4))),
|
||||
twin.NewStyledRune('f', twin.StyleDefault.WithForeground(twin.NewColor16(5))),
|
||||
twin.NewStyledRune('g', twin.StyleDefault.WithForeground(twin.NewColor16(6))),
|
||||
twin.NewStyledRune('h', twin.StyleDefault.WithForeground(twin.NewColor16(7))),
|
||||
twin.NewStyledRune('i', twin.StyleDefault),
|
||||
}
|
||||
|
||||
contents := startPaging(t, reader).GetRow(0)
|
||||
for pos, expected := range answers {
|
||||
assertCellsEqual(t, expected, contents[pos])
|
||||
assertRunesEqual(t, expected, contents[pos])
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,19 +78,19 @@ 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),
|
||||
var answers = []twin.StyledRune{
|
||||
twin.NewStyledRune('a', twin.StyleDefault),
|
||||
twin.NewStyledRune('b', twin.StyleDefault),
|
||||
twin.NewStyledRune('c', twin.StyleDefault),
|
||||
twin.NewStyledRune('?', twin.StyleDefault.WithForeground(twin.NewColor16(7)).WithBackground(twin.NewColor16(1))),
|
||||
twin.NewStyledRune('d', twin.StyleDefault),
|
||||
twin.NewStyledRune('e', twin.StyleDefault),
|
||||
twin.NewStyledRune('f', twin.StyleDefault),
|
||||
}
|
||||
|
||||
contents := startPaging(t, reader).GetRow(0)
|
||||
for pos, expected := range answers {
|
||||
assertCellsEqual(t, expected, contents[pos])
|
||||
assertRunesEqual(t, expected, contents[pos])
|
||||
}
|
||||
}
|
||||
|
||||
@ -171,21 +171,21 @@ func TestCodeHighlighting(t *testing.T) {
|
||||
|
||||
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),
|
||||
var answers = []twin.StyledRune{
|
||||
twin.NewStyledRune('p', packageKeywordStyle),
|
||||
twin.NewStyledRune('a', packageKeywordStyle),
|
||||
twin.NewStyledRune('c', packageKeywordStyle),
|
||||
twin.NewStyledRune('k', packageKeywordStyle),
|
||||
twin.NewStyledRune('a', packageKeywordStyle),
|
||||
twin.NewStyledRune('g', packageKeywordStyle),
|
||||
twin.NewStyledRune('e', packageKeywordStyle),
|
||||
twin.NewStyledRune(' ', packageNameStyle),
|
||||
twin.NewStyledRune('m', packageNameStyle),
|
||||
}
|
||||
|
||||
contents := startPaging(t, reader).GetRow(0)
|
||||
for pos, expected := range answers {
|
||||
assertCellsEqual(t, expected, contents[pos])
|
||||
assertRunesEqual(t, expected, contents[pos])
|
||||
}
|
||||
}
|
||||
|
||||
@ -196,22 +196,22 @@ func TestCodeHighlight_compressed(t *testing.T) {
|
||||
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),
|
||||
var answers = []twin.StyledRune{
|
||||
twin.NewStyledRune('#', markdownHeading1Style),
|
||||
twin.NewStyledRune(' ', markdownHeading1Style),
|
||||
twin.NewStyledRune('M', markdownHeading1Style),
|
||||
twin.NewStyledRune('a', markdownHeading1Style),
|
||||
twin.NewStyledRune('r', markdownHeading1Style),
|
||||
twin.NewStyledRune('k', markdownHeading1Style),
|
||||
twin.NewStyledRune('d', markdownHeading1Style),
|
||||
twin.NewStyledRune('o', markdownHeading1Style),
|
||||
twin.NewStyledRune('w', markdownHeading1Style),
|
||||
twin.NewStyledRune('n', markdownHeading1Style),
|
||||
}
|
||||
|
||||
contents := startPaging(t, reader).GetRow(0)
|
||||
for pos, expected := range answers {
|
||||
assertCellsEqual(t, expected, contents[pos])
|
||||
assertRunesEqual(t, expected, contents[pos])
|
||||
}
|
||||
}
|
||||
|
||||
@ -230,7 +230,7 @@ func TestCodeHighlightingIncludes(t *testing.T) {
|
||||
secondIncludeLine := screen.GetRow(3)
|
||||
|
||||
// Both should start with "#include" colored the same way
|
||||
assertCellsEqual(t, firstIncludeLine[0], secondIncludeLine[0])
|
||||
assertRunesEqual(t, firstIncludeLine[0], secondIncludeLine[0])
|
||||
}
|
||||
|
||||
func TestUnicodePrivateUse(t *testing.T) {
|
||||
@ -242,10 +242,10 @@ func TestUnicodePrivateUse(t *testing.T) {
|
||||
char := '\uf244'
|
||||
|
||||
reader := NewReaderFromText("hello", string(char))
|
||||
renderedCell := startPaging(t, reader).GetRow(0)[0]
|
||||
renderedRune := startPaging(t, reader).GetRow(0)[0]
|
||||
|
||||
// Make sure we display this character unmodified
|
||||
assertCellsEqual(t, twin.NewCell(char, twin.StyleDefault), renderedCell)
|
||||
assertRunesEqual(t, twin.NewStyledRune(char, twin.StyleDefault), renderedRune)
|
||||
}
|
||||
|
||||
func resetManPageFormat() {
|
||||
@ -253,7 +253,7 @@ func resetManPageFormat() {
|
||||
textstyles.ManPageUnderline = twin.StyleDefault.WithAttr(twin.AttrUnderline)
|
||||
}
|
||||
|
||||
func testManPageFormatting(t *testing.T, input string, expected twin.Cell) {
|
||||
func testManPageFormatting(t *testing.T, input string, expected twin.StyledRune) {
|
||||
reader := NewReaderFromText("", input)
|
||||
|
||||
// Without these lines the man page tests will fail if either of these
|
||||
@ -264,19 +264,19 @@ func testManPageFormatting(t *testing.T, input string, expected twin.Cell) {
|
||||
resetManPageFormat()
|
||||
|
||||
contents := startPaging(t, reader).GetRow(0)
|
||||
assertCellsEqual(t, expected, contents[0])
|
||||
assertRunesEqual(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)))
|
||||
testManPageFormatting(t, "n\x08n", twin.NewStyledRune('n', twin.StyleDefault.WithAttr(twin.AttrBold)))
|
||||
testManPageFormatting(t, "_\x08x", twin.NewStyledRune('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))
|
||||
testManPageFormatting(t, string([]byte{0xc2, 0xa0}), twin.NewStyledRune(rune(0xa0), twin.StyleDefault))
|
||||
|
||||
// Corner cases
|
||||
testManPageFormatting(t, "\x08", twin.NewCell('<', twin.StyleDefault.WithForeground(twin.NewColor16(7)).WithBackground(twin.NewColor16(1))))
|
||||
testManPageFormatting(t, "\x08", twin.NewStyledRune('<', twin.StyleDefault.WithForeground(twin.NewColor16(7)).WithBackground(twin.NewColor16(1))))
|
||||
|
||||
// FIXME: Test two consecutive backspaces
|
||||
|
||||
@ -359,7 +359,7 @@ func TestFindFirstHitNoMatchBackwards(t *testing.T) {
|
||||
}
|
||||
|
||||
// Converts a cell row to a plain string and removes trailing whitespace.
|
||||
func rowToString(row []twin.Cell) string {
|
||||
func rowToString(row []twin.StyledRune) string {
|
||||
rowString := ""
|
||||
for _, cell := range row {
|
||||
rowString += string(cell.Rune)
|
||||
@ -432,7 +432,7 @@ func TestScrollToEndLongInput(t *testing.T) {
|
||||
// line holds the last contents line.
|
||||
lastContentsLine := screen.GetRow(screenHeight - 2)
|
||||
firstContentsColumn := len("10_100 ")
|
||||
assertCellsEqual(t, twin.NewCell('X', twin.StyleDefault), lastContentsLine[firstContentsColumn])
|
||||
assertRunesEqual(t, twin.NewStyledRune('X', twin.StyleDefault), lastContentsLine[firstContentsColumn])
|
||||
}
|
||||
|
||||
func TestIsScrolledToEnd_LongFile(t *testing.T) {
|
||||
@ -581,10 +581,10 @@ func TestClearToEndOfLine_ClearFromStart(t *testing.T) {
|
||||
screen := startPaging(t, NewReaderFromText("TestClearToEol", blueBackgroundClearToEol))
|
||||
|
||||
screenWidth, _ := screen.Size()
|
||||
var expected []twin.Cell
|
||||
var expected []twin.StyledRune
|
||||
for len(expected) < screenWidth {
|
||||
expected = append(expected,
|
||||
twin.NewCell(' ', twin.StyleDefault.WithBackground(twin.NewColor16(4))),
|
||||
twin.NewStyledRune(' ', twin.StyleDefault.WithBackground(twin.NewColor16(4))),
|
||||
)
|
||||
}
|
||||
|
||||
@ -597,12 +597,12 @@ func TestClearToEndOfLine_ClearFromNotStart(t *testing.T) {
|
||||
screen := startPaging(t, NewReaderFromText("TestClearToEol", "a"+blueBackgroundClearToEol))
|
||||
|
||||
screenWidth, _ := screen.Size()
|
||||
expected := []twin.Cell{
|
||||
twin.NewCell('a', twin.StyleDefault),
|
||||
expected := []twin.StyledRune{
|
||||
twin.NewStyledRune('a', twin.StyleDefault),
|
||||
}
|
||||
for len(expected) < screenWidth {
|
||||
expected = append(expected,
|
||||
twin.NewCell(' ', twin.StyleDefault.WithBackground(twin.NewColor16(4))),
|
||||
twin.NewStyledRune(' ', twin.StyleDefault.WithBackground(twin.NewColor16(4))),
|
||||
)
|
||||
}
|
||||
|
||||
@ -629,10 +629,10 @@ func TestClearToEndOfLine_ClearFromStartScrolledRight(t *testing.T) {
|
||||
pager.redraw("")
|
||||
|
||||
screenWidth, _ := screen.Size()
|
||||
var expected []twin.Cell
|
||||
var expected []twin.StyledRune
|
||||
for len(expected) < screenWidth {
|
||||
expected = append(expected,
|
||||
twin.NewCell(' ', twin.StyleDefault.WithBackground(twin.NewColor16(4))),
|
||||
twin.NewStyledRune(' ', twin.StyleDefault.WithBackground(twin.NewColor16(4))),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -21,12 +21,12 @@ func (m *PagerModeGotoLine) drawFooter(_ string, _ string) {
|
||||
|
||||
pos := 0
|
||||
for _, token := range "Go to line number: " + m.gotoLineString {
|
||||
p.screen.SetCell(pos, height-1, twin.NewCell(token, twin.StyleDefault))
|
||||
p.screen.SetCell(pos, height-1, twin.NewStyledRune(token, twin.StyleDefault))
|
||||
pos++
|
||||
}
|
||||
|
||||
// Add a cursor
|
||||
p.screen.SetCell(pos, height-1, twin.NewCell(' ', twin.StyleDefault.WithAttr(twin.AttrReverse)))
|
||||
p.screen.SetCell(pos, height-1, twin.NewStyledRune(' ', twin.StyleDefault.WithAttr(twin.AttrReverse)))
|
||||
}
|
||||
|
||||
func (m *PagerModeGotoLine) onKey(key twin.KeyCode) {
|
||||
|
@ -18,7 +18,7 @@ func (m PagerModeJumpToMark) drawFooter(_ string, _ string) {
|
||||
|
||||
pos := 0
|
||||
for _, token := range m.getMarkPrompt() {
|
||||
p.screen.SetCell(pos, height-1, twin.NewCell(token, twin.StyleDefault))
|
||||
p.screen.SetCell(pos, height-1, twin.NewStyledRune(token, twin.StyleDefault))
|
||||
pos++
|
||||
}
|
||||
}
|
||||
|
@ -13,12 +13,12 @@ func (m PagerModeMark) drawFooter(_ string, _ string) {
|
||||
|
||||
pos := 0
|
||||
for _, token := range "Press any key to label your mark: " {
|
||||
p.screen.SetCell(pos, height-1, twin.NewCell(token, twin.StyleDefault))
|
||||
p.screen.SetCell(pos, height-1, twin.NewStyledRune(token, twin.StyleDefault))
|
||||
pos++
|
||||
}
|
||||
|
||||
// Add a cursor
|
||||
p.screen.SetCell(pos, height-1, twin.NewCell(' ', twin.StyleDefault.WithAttr(twin.AttrReverse)))
|
||||
p.screen.SetCell(pos, height-1, twin.NewStyledRune(' ', twin.StyleDefault.WithAttr(twin.AttrReverse)))
|
||||
}
|
||||
|
||||
func (m PagerModeMark) onKey(key twin.KeyCode) {
|
||||
|
@ -18,17 +18,17 @@ func (m PagerModeSearch) drawFooter(_ string, _ string) {
|
||||
|
||||
pos := 0
|
||||
for _, token := range "Search: " + m.pager.searchString {
|
||||
m.pager.screen.SetCell(pos, height-1, twin.NewCell(token, twin.StyleDefault))
|
||||
m.pager.screen.SetCell(pos, height-1, twin.NewStyledRune(token, twin.StyleDefault))
|
||||
pos++
|
||||
}
|
||||
|
||||
// Add a cursor
|
||||
m.pager.screen.SetCell(pos, height-1, twin.NewCell(' ', twin.StyleDefault.WithAttr(twin.AttrReverse)))
|
||||
m.pager.screen.SetCell(pos, height-1, twin.NewStyledRune(' ', twin.StyleDefault.WithAttr(twin.AttrReverse)))
|
||||
pos++
|
||||
|
||||
// Clear the rest of the line
|
||||
for pos < width {
|
||||
m.pager.screen.SetCell(pos, height-1, twin.NewCell(' ', twin.StyleDefault))
|
||||
m.pager.screen.SetCell(pos, height-1, twin.NewStyledRune(' ', twin.StyleDefault))
|
||||
pos++
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ type renderedLine struct {
|
||||
// will have a wrapIndex of 1.
|
||||
wrapIndex int
|
||||
|
||||
cells []twin.Cell
|
||||
cells []twin.StyledRune
|
||||
|
||||
// Used for rendering clear-to-end-of-line control sequences:
|
||||
// https://en.wikipedia.org/wiki/ANSI_escape_code#EL
|
||||
@ -38,7 +38,7 @@ func (p *Pager) redraw(spinner string) overflowState {
|
||||
p.longestLineLength = 0
|
||||
|
||||
lastUpdatedScreenLineNumber := -1
|
||||
var renderedScreenLines [][]twin.Cell
|
||||
var renderedScreenLines [][]twin.StyledRune
|
||||
renderedScreenLines, statusText, overflow := p.renderScreenLines()
|
||||
for screenLineNumber, row := range renderedScreenLines {
|
||||
lastUpdatedScreenLineNumber = screenLineNumber
|
||||
@ -54,7 +54,7 @@ func (p *Pager) redraw(spinner string) overflowState {
|
||||
// This happens when we're done
|
||||
eofSpinner = "---"
|
||||
}
|
||||
spinnerLine := textstyles.CellsFromString("", _EofMarkerFormat+eofSpinner, nil).Cells
|
||||
spinnerLine := textstyles.StyledRunesFromString("", _EofMarkerFormat+eofSpinner, nil).StyledRunes
|
||||
for column, cell := range spinnerLine {
|
||||
p.screen.SetCell(column, lastUpdatedScreenLineNumber+1, cell)
|
||||
}
|
||||
@ -71,14 +71,14 @@ func (p *Pager) redraw(spinner string) overflowState {
|
||||
//
|
||||
// The lines returned by this method are decorated with horizontal scroll
|
||||
// markers and line numbers and are ready to be output to the screen.
|
||||
func (p *Pager) renderScreenLines() (lines [][]twin.Cell, statusText string, overflow overflowState) {
|
||||
func (p *Pager) renderScreenLines() (lines [][]twin.StyledRune, statusText string, overflow overflowState) {
|
||||
renderedLines, statusText, overflow := p.renderLines()
|
||||
if len(renderedLines) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Construct the screen lines to return
|
||||
screenLines := make([][]twin.Cell, 0, len(renderedLines))
|
||||
screenLines := make([][]twin.StyledRune, 0, len(renderedLines))
|
||||
for _, renderedLine := range renderedLines {
|
||||
screenLines = append(screenLines, renderedLine.cells)
|
||||
|
||||
@ -90,7 +90,7 @@ func (p *Pager) renderScreenLines() (lines [][]twin.Cell, statusText string, ove
|
||||
screenWidth, _ := p.screen.Size()
|
||||
for len(screenLines[len(screenLines)-1]) < screenWidth {
|
||||
screenLines[len(screenLines)-1] =
|
||||
append(screenLines[len(screenLines)-1], twin.NewCell(' ', renderedLine.trailer))
|
||||
append(screenLines[len(screenLines)-1], twin.NewStyledRune(' ', renderedLine.trailer))
|
||||
}
|
||||
}
|
||||
|
||||
@ -214,14 +214,14 @@ func (p *Pager) renderLines() ([]renderedLine, string, overflowState) {
|
||||
// indent, and to (optionally) render the line number.
|
||||
func (p *Pager) renderLine(line *Line, lineNumber linenumbers.LineNumber, scrollPosition scrollPositionInternal) ([]renderedLine, overflowState) {
|
||||
highlighted := line.HighlightedTokens(p.linePrefix, p.searchPattern, &lineNumber)
|
||||
var wrapped [][]twin.Cell
|
||||
var wrapped [][]twin.StyledRune
|
||||
overflow := didFit
|
||||
if p.WrapLongLines {
|
||||
width, _ := p.screen.Size()
|
||||
wrapped = wrapLine(width-numberPrefixLength(p, scrollPosition), highlighted.Cells)
|
||||
wrapped = wrapLine(width-numberPrefixLength(p, scrollPosition), highlighted.StyledRunes)
|
||||
} else {
|
||||
// All on one line
|
||||
wrapped = [][]twin.Cell{highlighted.Cells}
|
||||
wrapped = [][]twin.StyledRune{highlighted.StyledRunes}
|
||||
}
|
||||
|
||||
if len(wrapped) > 1 {
|
||||
@ -260,9 +260,9 @@ func (p *Pager) renderLine(line *Line, lineNumber linenumbers.LineNumber, scroll
|
||||
// * Line number, or leading whitespace for wrapped lines
|
||||
// * Scroll left indicator
|
||||
// * Scroll right indicator
|
||||
func (p *Pager) decorateLine(lineNumberToShow *linenumbers.LineNumber, contents []twin.Cell, scrollPosition scrollPositionInternal) ([]twin.Cell, overflowState) {
|
||||
func (p *Pager) decorateLine(lineNumberToShow *linenumbers.LineNumber, contents []twin.StyledRune, scrollPosition scrollPositionInternal) ([]twin.StyledRune, overflowState) {
|
||||
width, _ := p.screen.Size()
|
||||
newLine := make([]twin.Cell, 0, width)
|
||||
newLine := make([]twin.StyledRune, 0, width)
|
||||
numberPrefixLength := numberPrefixLength(p, scrollPosition)
|
||||
newLine = append(newLine, createLinePrefix(lineNumberToShow, numberPrefixLength)...)
|
||||
overflow := didFit
|
||||
@ -282,7 +282,7 @@ func (p *Pager) decorateLine(lineNumberToShow *linenumbers.LineNumber, contents
|
||||
if len(newLine) == 0 {
|
||||
// Don't panic on short lines, this new Cell will be
|
||||
// overwritten with '<' right after this if statement
|
||||
newLine = append(newLine, twin.Cell{})
|
||||
newLine = append(newLine, twin.StyledRune{})
|
||||
}
|
||||
|
||||
// Add can-scroll-left marker
|
||||
@ -306,15 +306,15 @@ func (p *Pager) decorateLine(lineNumberToShow *linenumbers.LineNumber, contents
|
||||
// Generate a line number prefix of the given length.
|
||||
//
|
||||
// Can be empty or all-whitespace depending on parameters.
|
||||
func createLinePrefix(lineNumber *linenumbers.LineNumber, numberPrefixLength int) []twin.Cell {
|
||||
func createLinePrefix(lineNumber *linenumbers.LineNumber, numberPrefixLength int) []twin.StyledRune {
|
||||
if numberPrefixLength == 0 {
|
||||
return []twin.Cell{}
|
||||
return []twin.StyledRune{}
|
||||
}
|
||||
|
||||
lineNumberPrefix := make([]twin.Cell, 0, numberPrefixLength)
|
||||
lineNumberPrefix := make([]twin.StyledRune, 0, numberPrefixLength)
|
||||
if lineNumber == nil {
|
||||
for len(lineNumberPrefix) < numberPrefixLength {
|
||||
lineNumberPrefix = append(lineNumberPrefix, twin.Cell{Rune: ' '})
|
||||
lineNumberPrefix = append(lineNumberPrefix, twin.StyledRune{Rune: ' '})
|
||||
}
|
||||
return lineNumberPrefix
|
||||
}
|
||||
@ -331,7 +331,7 @@ func createLinePrefix(lineNumber *linenumbers.LineNumber, numberPrefixLength int
|
||||
break
|
||||
}
|
||||
|
||||
lineNumberPrefix = append(lineNumberPrefix, twin.NewCell(digit, lineNumbersStyle))
|
||||
lineNumberPrefix = append(lineNumberPrefix, twin.NewStyledRune(digit, lineNumbersStyle))
|
||||
}
|
||||
|
||||
return lineNumberPrefix
|
||||
|
@ -93,7 +93,7 @@ func TestSearchHighlight(t *testing.T) {
|
||||
{
|
||||
inputLine: linenumbers.LineNumber{},
|
||||
wrapIndex: 0,
|
||||
cells: []twin.Cell{
|
||||
cells: []twin.StyledRune{
|
||||
{Rune: 'x', Style: twin.StyleDefault},
|
||||
{Rune: '"', Style: twin.StyleDefault.WithAttr(twin.AttrReverse)},
|
||||
{Rune: '"', Style: twin.StyleDefault.WithAttr(twin.AttrReverse)},
|
||||
|
@ -53,7 +53,7 @@ func twinStyleFromChroma(chromaStyle *chroma.Style, chromaFormatter *chroma.Form
|
||||
}
|
||||
|
||||
formatted := stringBuilder.String()
|
||||
cells := textstyles.CellsFromString("", formatted, nil).Cells
|
||||
cells := textstyles.StyledRunesFromString("", formatted, nil).StyledRunes
|
||||
if len(cells) != 1 {
|
||||
log.Warnf("Chroma formatter didn't return exactly one cell: %#v", cells)
|
||||
return nil
|
||||
@ -151,7 +151,7 @@ func styleUI(chromaStyle *chroma.Style, chromaFormatter *chroma.Formatter, statu
|
||||
|
||||
func TermcapToStyle(termcap string) (twin.Style, error) {
|
||||
// Add a character to be sure we have one to take the format from
|
||||
cells := textstyles.CellsFromString("", termcap+"x", nil).Cells
|
||||
cells := textstyles.StyledRunesFromString("", termcap+"x", nil).StyledRunes
|
||||
if len(cells) != 1 {
|
||||
return twin.StyleDefault, fmt.Errorf("Expected styling only and no text")
|
||||
}
|
||||
|
@ -30,9 +30,9 @@ const _TabSize = 4
|
||||
|
||||
const BACKSPACE = '\b'
|
||||
|
||||
type CellsWithTrailer struct {
|
||||
Cells []twin.Cell
|
||||
Trailer twin.Style
|
||||
type StyledRunesWithTrailer struct {
|
||||
StyledRunes []twin.StyledRune
|
||||
Trailer twin.Style
|
||||
}
|
||||
|
||||
func isPlain(s string) bool {
|
||||
@ -110,13 +110,13 @@ func WithoutFormatting(s string, lineNumber *linenumbers.LineNumber) string {
|
||||
//
|
||||
// The prefix will be prepended to the string before parsing. The lineNumber is
|
||||
// used for error reporting.
|
||||
func CellsFromString(prefix string, s string, lineNumber *linenumbers.LineNumber) CellsWithTrailer {
|
||||
func StyledRunesFromString(prefix string, s string, lineNumber *linenumbers.LineNumber) StyledRunesWithTrailer {
|
||||
manPageHeading := manPageHeadingFromString(s)
|
||||
if manPageHeading != nil {
|
||||
return *manPageHeading
|
||||
}
|
||||
|
||||
var cells []twin.Cell
|
||||
var cells []twin.StyledRune
|
||||
|
||||
// Specs: https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit
|
||||
styleUnprintable := twin.StyleDefault.WithBackground(twin.NewColor16(1)).WithForeground(twin.NewColor16(7))
|
||||
@ -127,7 +127,7 @@ func CellsFromString(prefix string, s string, lineNumber *linenumbers.LineNumber
|
||||
|
||||
case '\x09': // TAB
|
||||
for {
|
||||
cells = append(cells, twin.Cell{
|
||||
cells = append(cells, twin.StyledRune{
|
||||
Rune: ' ',
|
||||
Style: style,
|
||||
})
|
||||
@ -140,12 +140,12 @@ func CellsFromString(prefix string, s string, lineNumber *linenumbers.LineNumber
|
||||
|
||||
case '<27>': // Go's broken-UTF8 marker
|
||||
if UnprintableStyle == UnprintableStyleHighlight {
|
||||
cells = append(cells, twin.Cell{
|
||||
cells = append(cells, twin.StyledRune{
|
||||
Rune: '?',
|
||||
Style: styleUnprintable,
|
||||
})
|
||||
} else if UnprintableStyle == UnprintableStyleWhitespace {
|
||||
cells = append(cells, twin.Cell{
|
||||
cells = append(cells, twin.StyledRune{
|
||||
Rune: '?',
|
||||
Style: twin.StyleDefault,
|
||||
})
|
||||
@ -154,7 +154,7 @@ func CellsFromString(prefix string, s string, lineNumber *linenumbers.LineNumber
|
||||
}
|
||||
|
||||
case BACKSPACE:
|
||||
cells = append(cells, twin.Cell{
|
||||
cells = append(cells, twin.StyledRune{
|
||||
Rune: '<',
|
||||
Style: styleUnprintable,
|
||||
})
|
||||
@ -162,12 +162,12 @@ func CellsFromString(prefix string, s string, lineNumber *linenumbers.LineNumber
|
||||
default:
|
||||
if !twin.Printable(token.Rune) {
|
||||
if UnprintableStyle == UnprintableStyleHighlight {
|
||||
cells = append(cells, twin.Cell{
|
||||
cells = append(cells, twin.StyledRune{
|
||||
Rune: '?',
|
||||
Style: styleUnprintable,
|
||||
})
|
||||
} else if UnprintableStyle == UnprintableStyleWhitespace {
|
||||
cells = append(cells, twin.Cell{
|
||||
cells = append(cells, twin.StyledRune{
|
||||
Rune: ' ',
|
||||
Style: twin.StyleDefault,
|
||||
})
|
||||
@ -181,14 +181,14 @@ func CellsFromString(prefix string, s string, lineNumber *linenumbers.LineNumber
|
||||
}
|
||||
})
|
||||
|
||||
return CellsWithTrailer{
|
||||
Cells: cells,
|
||||
Trailer: trailer,
|
||||
return StyledRunesWithTrailer{
|
||||
StyledRunes: cells,
|
||||
Trailer: trailer,
|
||||
}
|
||||
}
|
||||
|
||||
// Consume 'x<x', where '<' is backspace and the result is a bold 'x'
|
||||
func consumeBold(runes []rune, index int) (int, *twin.Cell) {
|
||||
func consumeBold(runes []rune, index int) (int, *twin.StyledRune) {
|
||||
if index+2 >= len(runes) {
|
||||
// Not enough runes left for a bold
|
||||
return index, nil
|
||||
@ -205,14 +205,14 @@ func consumeBold(runes []rune, index int) (int, *twin.Cell) {
|
||||
}
|
||||
|
||||
// We have a match!
|
||||
return index + 3, &twin.Cell{
|
||||
return index + 3, &twin.StyledRune{
|
||||
Rune: runes[index],
|
||||
Style: ManPageBold,
|
||||
}
|
||||
}
|
||||
|
||||
// Consume '_<x', where '<' is backspace and the result is an underlined 'x'
|
||||
func consumeUnderline(runes []rune, index int) (int, *twin.Cell) {
|
||||
func consumeUnderline(runes []rune, index int) (int, *twin.StyledRune) {
|
||||
if index+2 >= len(runes) {
|
||||
// Not enough runes left for a underline
|
||||
return index, nil
|
||||
@ -229,7 +229,7 @@ func consumeUnderline(runes []rune, index int) (int, *twin.Cell) {
|
||||
}
|
||||
|
||||
// We have a match!
|
||||
return index + 3, &twin.Cell{
|
||||
return index + 3, &twin.StyledRune{
|
||||
Rune: runes[index+2],
|
||||
Style: ManPageUnderline,
|
||||
}
|
||||
@ -238,7 +238,7 @@ func consumeUnderline(runes []rune, index int) (int, *twin.Cell) {
|
||||
// Consume '+<+<o<o' / '+<o', where '<' is backspace and the result is a unicode bullet.
|
||||
//
|
||||
// Used on man pages, try "man printf" on macOS for one example.
|
||||
func consumeBullet(runes []rune, index int) (int, *twin.Cell) {
|
||||
func consumeBullet(runes []rune, index int) (int, *twin.StyledRune) {
|
||||
patterns := [][]byte{[]byte("+\bo"), []byte("+\b+\bo\bo")}
|
||||
for _, pattern := range patterns {
|
||||
if index+len(pattern) > len(runes) {
|
||||
@ -259,7 +259,7 @@ func consumeBullet(runes []rune, index int) (int, *twin.Cell) {
|
||||
}
|
||||
|
||||
// We have a match!
|
||||
return index + len(pattern), &twin.Cell{
|
||||
return index + len(pattern), &twin.StyledRune{
|
||||
Rune: '•', // Unicode bullet point
|
||||
Style: twin.StyleDefault,
|
||||
}
|
||||
@ -293,7 +293,7 @@ func runesFromStyledString(styledString _StyledString) string {
|
||||
return returnMe.String()
|
||||
}
|
||||
|
||||
func tokensFromStyledString(styledString _StyledString) []twin.Cell {
|
||||
func tokensFromStyledString(styledString _StyledString) []twin.StyledRune {
|
||||
runes := []rune(styledString.String)
|
||||
|
||||
hasBackspace := false
|
||||
@ -304,11 +304,11 @@ func tokensFromStyledString(styledString _StyledString) []twin.Cell {
|
||||
}
|
||||
}
|
||||
|
||||
tokens := make([]twin.Cell, 0, len(runes))
|
||||
tokens := make([]twin.StyledRune, 0, len(runes))
|
||||
if !hasBackspace {
|
||||
// Shortcut when there's no backspace based formatting to worry about
|
||||
for _, runeValue := range runes {
|
||||
tokens = append(tokens, twin.Cell{
|
||||
tokens = append(tokens, twin.StyledRune{
|
||||
Rune: runeValue,
|
||||
Style: styledString.Style,
|
||||
})
|
||||
@ -339,7 +339,7 @@ func tokensFromStyledString(styledString _StyledString) []twin.Cell {
|
||||
continue
|
||||
}
|
||||
|
||||
tokens = append(tokens, twin.Cell{
|
||||
tokens = append(tokens, twin.StyledRune{
|
||||
Rune: runes[index],
|
||||
Style: styledString.Style,
|
||||
})
|
||||
|
@ -20,7 +20,7 @@ import (
|
||||
const samplesDir = "../../sample-files"
|
||||
|
||||
// Convert a cells array to a plain string
|
||||
func cellsToPlainString(cells []twin.Cell) string {
|
||||
func cellsToPlainString(cells []twin.StyledRune) string {
|
||||
returnMe := ""
|
||||
for _, cell := range cells {
|
||||
returnMe += string(cell.Rune)
|
||||
@ -74,7 +74,7 @@ func TestTokenize(t *testing.T) {
|
||||
var loglines strings.Builder
|
||||
log.SetOutput(&loglines)
|
||||
|
||||
tokens := CellsFromString("", line, lineNumber).Cells
|
||||
tokens := StyledRunesFromString("", line, lineNumber).StyledRunes
|
||||
plainString := WithoutFormatting(line, lineNumber)
|
||||
if len(tokens) != utf8.RuneCountInString(plainString) {
|
||||
t.Errorf("%s:%s: len(tokens)=%d, len(plainString)=%d for: <%s>",
|
||||
@ -127,43 +127,43 @@ func TestTokenize(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnderline(t *testing.T) {
|
||||
tokens := CellsFromString("", "a\x1b[4mb\x1b[24mc", nil).Cells
|
||||
tokens := StyledRunesFromString("", "a\x1b[4mb\x1b[24mc", nil).StyledRunes
|
||||
assert.Equal(t, len(tokens), 3)
|
||||
assert.Equal(t, tokens[0], twin.Cell{Rune: 'a', Style: twin.StyleDefault})
|
||||
assert.Equal(t, tokens[1], twin.Cell{Rune: 'b', Style: twin.StyleDefault.WithAttr(twin.AttrUnderline)})
|
||||
assert.Equal(t, tokens[2], twin.Cell{Rune: 'c', Style: twin.StyleDefault})
|
||||
assert.Equal(t, tokens[0], twin.StyledRune{Rune: 'a', Style: twin.StyleDefault})
|
||||
assert.Equal(t, tokens[1], twin.StyledRune{Rune: 'b', Style: twin.StyleDefault.WithAttr(twin.AttrUnderline)})
|
||||
assert.Equal(t, tokens[2], twin.StyledRune{Rune: 'c', Style: twin.StyleDefault})
|
||||
}
|
||||
|
||||
func TestManPages(t *testing.T) {
|
||||
// Bold
|
||||
tokens := CellsFromString("", "ab\bbc", nil).Cells
|
||||
tokens := StyledRunesFromString("", "ab\bbc", nil).StyledRunes
|
||||
assert.Equal(t, len(tokens), 3)
|
||||
assert.Equal(t, tokens[0], twin.Cell{Rune: 'a', Style: twin.StyleDefault})
|
||||
assert.Equal(t, tokens[1], twin.Cell{Rune: 'b', Style: twin.StyleDefault.WithAttr(twin.AttrBold)})
|
||||
assert.Equal(t, tokens[2], twin.Cell{Rune: 'c', Style: twin.StyleDefault})
|
||||
assert.Equal(t, tokens[0], twin.StyledRune{Rune: 'a', Style: twin.StyleDefault})
|
||||
assert.Equal(t, tokens[1], twin.StyledRune{Rune: 'b', Style: twin.StyleDefault.WithAttr(twin.AttrBold)})
|
||||
assert.Equal(t, tokens[2], twin.StyledRune{Rune: 'c', Style: twin.StyleDefault})
|
||||
|
||||
// Underline
|
||||
tokens = CellsFromString("", "a_\bbc", nil).Cells
|
||||
tokens = StyledRunesFromString("", "a_\bbc", nil).StyledRunes
|
||||
assert.Equal(t, len(tokens), 3)
|
||||
assert.Equal(t, tokens[0], twin.Cell{Rune: 'a', Style: twin.StyleDefault})
|
||||
assert.Equal(t, tokens[1], twin.Cell{Rune: 'b', Style: twin.StyleDefault.WithAttr(twin.AttrUnderline)})
|
||||
assert.Equal(t, tokens[2], twin.Cell{Rune: 'c', Style: twin.StyleDefault})
|
||||
assert.Equal(t, tokens[0], twin.StyledRune{Rune: 'a', Style: twin.StyleDefault})
|
||||
assert.Equal(t, tokens[1], twin.StyledRune{Rune: 'b', Style: twin.StyleDefault.WithAttr(twin.AttrUnderline)})
|
||||
assert.Equal(t, tokens[2], twin.StyledRune{Rune: 'c', Style: twin.StyleDefault})
|
||||
|
||||
// Bullet point 1, taken from doing this on my macOS system:
|
||||
// env PAGER="hexdump -C" man printf | moar
|
||||
tokens = CellsFromString("", "a+\b+\bo\bob", nil).Cells
|
||||
tokens = StyledRunesFromString("", "a+\b+\bo\bob", nil).StyledRunes
|
||||
assert.Equal(t, len(tokens), 3)
|
||||
assert.Equal(t, tokens[0], twin.Cell{Rune: 'a', Style: twin.StyleDefault})
|
||||
assert.Equal(t, tokens[1], twin.Cell{Rune: '•', Style: twin.StyleDefault})
|
||||
assert.Equal(t, tokens[2], twin.Cell{Rune: 'b', Style: twin.StyleDefault})
|
||||
assert.Equal(t, tokens[0], twin.StyledRune{Rune: 'a', Style: twin.StyleDefault})
|
||||
assert.Equal(t, tokens[1], twin.StyledRune{Rune: '•', Style: twin.StyleDefault})
|
||||
assert.Equal(t, tokens[2], twin.StyledRune{Rune: 'b', Style: twin.StyleDefault})
|
||||
|
||||
// Bullet point 2, taken from doing this using the "fish" shell on my macOS system:
|
||||
// man printf | hexdump -C | moar
|
||||
tokens = CellsFromString("", "a+\bob", nil).Cells
|
||||
tokens = StyledRunesFromString("", "a+\bob", nil).StyledRunes
|
||||
assert.Equal(t, len(tokens), 3)
|
||||
assert.Equal(t, tokens[0], twin.Cell{Rune: 'a', Style: twin.StyleDefault})
|
||||
assert.Equal(t, tokens[1], twin.Cell{Rune: '•', Style: twin.StyleDefault})
|
||||
assert.Equal(t, tokens[2], twin.Cell{Rune: 'b', Style: twin.StyleDefault})
|
||||
assert.Equal(t, tokens[0], twin.StyledRune{Rune: 'a', Style: twin.StyleDefault})
|
||||
assert.Equal(t, tokens[1], twin.StyledRune{Rune: '•', Style: twin.StyleDefault})
|
||||
assert.Equal(t, tokens[2], twin.StyledRune{Rune: 'b', Style: twin.StyleDefault})
|
||||
}
|
||||
|
||||
func TestManPageHeadings(t *testing.T) {
|
||||
@ -181,18 +181,18 @@ func TestManPageHeadings(t *testing.T) {
|
||||
}
|
||||
|
||||
// A line with only man page bold caps should be considered a heading
|
||||
for _, token := range CellsFromString("", manPageHeading, nil).Cells {
|
||||
for _, token := range StyledRunesFromString("", manPageHeading, nil).StyledRunes {
|
||||
assert.Equal(t, token.Style, ManPageHeading)
|
||||
}
|
||||
|
||||
// A line with only non-man-page bold caps should not be considered a heading
|
||||
wrongKindOfBold := "\x1b[1mJOHAN HELLO"
|
||||
for _, token := range CellsFromString("", wrongKindOfBold, nil).Cells {
|
||||
for _, token := range StyledRunesFromString("", wrongKindOfBold, nil).StyledRunes {
|
||||
assert.Equal(t, token.Style, twin.StyleDefault.WithAttr(twin.AttrBold))
|
||||
}
|
||||
|
||||
// A line with not all caps should not be considered a heading
|
||||
for _, token := range CellsFromString("", notAllCaps, nil).Cells {
|
||||
for _, token := range StyledRunesFromString("", notAllCaps, nil).StyledRunes {
|
||||
assert.Equal(t, token.Style, twin.StyleDefault.WithAttr(twin.AttrBold))
|
||||
}
|
||||
}
|
||||
@ -257,9 +257,9 @@ func TestRawUpdateStyle(t *testing.T) {
|
||||
func TestHyperlink_escBackslash(t *testing.T) {
|
||||
url := "http://example.com"
|
||||
|
||||
tokens := CellsFromString("", "a\x1b]8;;"+url+"\x1b\\bc\x1b]8;;\x1b\\d", nil).Cells
|
||||
tokens := StyledRunesFromString("", "a\x1b]8;;"+url+"\x1b\\bc\x1b]8;;\x1b\\d", nil).StyledRunes
|
||||
|
||||
assert.DeepEqual(t, tokens, []twin.Cell{
|
||||
assert.DeepEqual(t, tokens, []twin.StyledRune{
|
||||
{Rune: 'a', Style: twin.StyleDefault},
|
||||
{Rune: 'b', Style: twin.StyleDefault.WithHyperlink(&url)},
|
||||
{Rune: 'c', Style: twin.StyleDefault.WithHyperlink(&url)},
|
||||
@ -273,9 +273,9 @@ func TestHyperlink_escBackslash(t *testing.T) {
|
||||
func TestHyperlink_bell(t *testing.T) {
|
||||
url := "http://example.com"
|
||||
|
||||
tokens := CellsFromString("", "a\x1b]8;;"+url+"\x07bc\x1b]8;;\x07d", nil).Cells
|
||||
tokens := StyledRunesFromString("", "a\x1b]8;;"+url+"\x07bc\x1b]8;;\x07d", nil).StyledRunes
|
||||
|
||||
assert.DeepEqual(t, tokens, []twin.Cell{
|
||||
assert.DeepEqual(t, tokens, []twin.StyledRune{
|
||||
{Rune: 'a', Style: twin.StyleDefault},
|
||||
{Rune: 'b', Style: twin.StyleDefault.WithHyperlink(&url)},
|
||||
{Rune: 'c', Style: twin.StyleDefault.WithHyperlink(&url)},
|
||||
@ -286,7 +286,7 @@ func TestHyperlink_bell(t *testing.T) {
|
||||
// Test with some other ESC sequence than ESC-backslash
|
||||
func TestHyperlink_nonTerminatingEsc(t *testing.T) {
|
||||
complete := "a\x1b]8;;https://example.com\x1bbc"
|
||||
tokens := CellsFromString("", complete, nil).Cells
|
||||
tokens := StyledRunesFromString("", complete, nil).StyledRunes
|
||||
|
||||
// This should not be treated as any link
|
||||
for i := 0; i < len(complete); i++ {
|
||||
@ -295,7 +295,7 @@ func TestHyperlink_nonTerminatingEsc(t *testing.T) {
|
||||
// good enough.
|
||||
continue
|
||||
}
|
||||
assert.Equal(t, tokens[i], twin.Cell{Rune: rune(complete[i]), Style: twin.StyleDefault},
|
||||
assert.Equal(t, tokens[i], twin.StyledRune{Rune: rune(complete[i]), Style: twin.StyleDefault},
|
||||
"i=%d, c=%s, tokens=%v", i, string(complete[i]), tokens)
|
||||
}
|
||||
}
|
||||
@ -306,7 +306,7 @@ func TestHyperlink_incomplete(t *testing.T) {
|
||||
for l := len(complete) - 1; l >= 0; l-- {
|
||||
incomplete := complete[:l]
|
||||
t.Run(fmt.Sprintf("l=%d incomplete=<%s>", l, strings.ReplaceAll(incomplete, "\x1b", "ESC")), func(t *testing.T) {
|
||||
tokens := CellsFromString("", incomplete, nil).Cells
|
||||
tokens := StyledRunesFromString("", incomplete, nil).StyledRunes
|
||||
|
||||
for i := 0; i < l; i++ {
|
||||
if complete[i] == '\x1b' {
|
||||
@ -314,7 +314,7 @@ func TestHyperlink_incomplete(t *testing.T) {
|
||||
// that's good enough.
|
||||
continue
|
||||
}
|
||||
assert.Equal(t, tokens[i], twin.Cell{Rune: rune(complete[i]), Style: twin.StyleDefault})
|
||||
assert.Equal(t, tokens[i], twin.StyledRune{Rune: rune(complete[i]), Style: twin.StyleDefault})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -6,24 +6,24 @@ import (
|
||||
"github.com/walles/moar/twin"
|
||||
)
|
||||
|
||||
func manPageHeadingFromString(s string) *CellsWithTrailer {
|
||||
func manPageHeadingFromString(s string) *StyledRunesWithTrailer {
|
||||
// For great performance, first check the string without allocating any
|
||||
// memory.
|
||||
if !parseManPageHeading(s, func(_ twin.Cell) {}) {
|
||||
if !parseManPageHeading(s, func(_ twin.StyledRune) {}) {
|
||||
return nil
|
||||
}
|
||||
|
||||
cells := make([]twin.Cell, 0, len(s)/2)
|
||||
ok := parseManPageHeading(s, func(cell twin.Cell) {
|
||||
cells := make([]twin.StyledRune, 0, len(s)/2)
|
||||
ok := parseManPageHeading(s, func(cell twin.StyledRune) {
|
||||
cells = append(cells, cell)
|
||||
})
|
||||
if !ok {
|
||||
panic("man page heading state changed")
|
||||
}
|
||||
|
||||
return &CellsWithTrailer{
|
||||
Cells: cells,
|
||||
Trailer: twin.StyleDefault,
|
||||
return &StyledRunesWithTrailer{
|
||||
StyledRunes: cells,
|
||||
Trailer: twin.StyleDefault,
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ func manPageHeadingFromString(s string) *CellsWithTrailer {
|
||||
// A man page heading is all caps. Also, each character is encoded as
|
||||
// char+backspace+char, where both chars need to be the same. Whitespace is an
|
||||
// exception, they can be not bold.
|
||||
func parseManPageHeading(s string, reportCell func(twin.Cell)) bool {
|
||||
func parseManPageHeading(s string, reportStyledRune func(twin.StyledRune)) bool {
|
||||
if len(s) < 3 {
|
||||
// We don't want to match empty strings. Also, strings of length 1 and 2
|
||||
// cannot be man page headings since "char+backspace+char" is 3 bytes.
|
||||
@ -78,7 +78,7 @@ func parseManPageHeading(s string, reportCell func(twin.Cell)) bool {
|
||||
|
||||
if unicode.IsSpace(firstChar) {
|
||||
// Whitespace is an exception, it can be not bold
|
||||
reportCell(twin.Cell{Rune: firstChar, Style: ManPageHeading})
|
||||
reportStyledRune(twin.StyledRune{Rune: firstChar, Style: ManPageHeading})
|
||||
|
||||
// Assume what we got was a new first char
|
||||
firstChar = char
|
||||
@ -105,7 +105,7 @@ func parseManPageHeading(s string, reportCell func(twin.Cell)) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
reportCell(twin.Cell{Rune: char, Style: ManPageHeading})
|
||||
reportStyledRune(twin.StyledRune{Rune: char, Style: ManPageHeading})
|
||||
state = stateExpectingFirstChar
|
||||
|
||||
default:
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
func isManPageHeading(s string) bool {
|
||||
return parseManPageHeading(s, func(_ twin.Cell) {})
|
||||
return parseManPageHeading(s, func(_ twin.StyledRune) {})
|
||||
}
|
||||
|
||||
func TestIsManPageHeading(t *testing.T) {
|
||||
@ -34,10 +34,10 @@ func TestManPageHeadingFromString_NotBoldSpace(t *testing.T) {
|
||||
result := manPageHeadingFromString("A\bA B\bB")
|
||||
|
||||
assert.Assert(t, result != nil)
|
||||
assert.Equal(t, len(result.Cells), 3)
|
||||
assert.Equal(t, result.Cells[0], twin.Cell{Rune: 'A', Style: ManPageHeading})
|
||||
assert.Equal(t, result.Cells[1], twin.Cell{Rune: ' ', Style: ManPageHeading})
|
||||
assert.Equal(t, result.Cells[2], twin.Cell{Rune: 'B', Style: ManPageHeading})
|
||||
assert.Equal(t, len(result.StyledRunes), 3)
|
||||
assert.Equal(t, result.StyledRunes[0], twin.StyledRune{Rune: 'A', Style: ManPageHeading})
|
||||
assert.Equal(t, result.StyledRunes[1], twin.StyledRune{Rune: ' ', Style: ManPageHeading})
|
||||
assert.Equal(t, result.StyledRunes[2], twin.StyledRune{Rune: 'B', Style: ManPageHeading})
|
||||
}
|
||||
|
||||
func TestManPageHeadingFromString_WithBoldSpace(t *testing.T) {
|
||||
@ -47,8 +47,8 @@ func TestManPageHeadingFromString_WithBoldSpace(t *testing.T) {
|
||||
result := manPageHeadingFromString("A\bA \b B\bB")
|
||||
|
||||
assert.Assert(t, result != nil)
|
||||
assert.Equal(t, len(result.Cells), 3)
|
||||
assert.Equal(t, result.Cells[0], twin.Cell{Rune: 'A', Style: ManPageHeading})
|
||||
assert.Equal(t, result.Cells[1], twin.Cell{Rune: ' ', Style: ManPageHeading})
|
||||
assert.Equal(t, result.Cells[2], twin.Cell{Rune: 'B', Style: ManPageHeading})
|
||||
assert.Equal(t, len(result.StyledRunes), 3)
|
||||
assert.Equal(t, result.StyledRunes[0], twin.StyledRune{Rune: 'A', Style: ManPageHeading})
|
||||
assert.Equal(t, result.StyledRunes[1], twin.StyledRune{Rune: ' ', Style: ManPageHeading})
|
||||
assert.Equal(t, result.StyledRunes[2], twin.StyledRune{Rune: 'B', Style: ManPageHeading})
|
||||
}
|
||||
|
10
moar.go
10
moar.go
@ -365,15 +365,15 @@ func parseUnprintableStyle(styleOption string) (textstyles.UnprintableStyleT, er
|
||||
return 0, fmt.Errorf("Good ones are highlight or whitespace")
|
||||
}
|
||||
|
||||
func parseScrollHint(scrollHint string) (twin.Cell, error) {
|
||||
func parseScrollHint(scrollHint string) (twin.StyledRune, error) {
|
||||
scrollHint = strings.ReplaceAll(scrollHint, "ESC", "\x1b")
|
||||
hintAsLine := m.NewLine(scrollHint)
|
||||
parsedTokens := hintAsLine.HighlightedTokens("", nil, nil).Cells
|
||||
parsedTokens := hintAsLine.HighlightedTokens("", nil, nil).StyledRunes
|
||||
if len(parsedTokens) == 1 {
|
||||
return parsedTokens[0], nil
|
||||
}
|
||||
|
||||
return twin.Cell{}, fmt.Errorf("Expected exactly one (optionally highlighted) character. For example: 'ESC[2m…'")
|
||||
return twin.StyledRune{}, fmt.Errorf("Expected exactly one (optionally highlighted) character. For example: 'ESC[2m…'")
|
||||
}
|
||||
|
||||
func parseShiftAmount(shiftAmount string) (uint, error) {
|
||||
@ -574,10 +574,10 @@ func pagerFromArgs(
|
||||
unprintableStyle := flagSetFunc(flagSet, "render-unprintable", textstyles.UnprintableStyleHighlight,
|
||||
"How unprintable characters are rendered: highlight or whitespace", parseUnprintableStyle)
|
||||
scrollLeftHint := flagSetFunc(flagSet, "scroll-left-hint",
|
||||
twin.NewCell('<', twin.StyleDefault.WithAttr(twin.AttrReverse)),
|
||||
twin.NewStyledRune('<', twin.StyleDefault.WithAttr(twin.AttrReverse)),
|
||||
"Shown when view can scroll left. One character with optional ANSI highlighting.", parseScrollHint)
|
||||
scrollRightHint := flagSetFunc(flagSet, "scroll-right-hint",
|
||||
twin.NewCell('>', twin.StyleDefault.WithAttr(twin.AttrReverse)),
|
||||
twin.NewStyledRune('>', twin.StyleDefault.WithAttr(twin.AttrReverse)),
|
||||
"Shown when view can scroll right. One character with optional ANSI highlighting.", parseScrollHint)
|
||||
shift := flagSetFunc(flagSet, "shift", 16, "Horizontal scroll `amount` >=1, defaults to 16", parseShiftAmount)
|
||||
mouseMode := flagSetFunc(
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
func TestParseScrollHint(t *testing.T) {
|
||||
token, err := parseScrollHint("ESC[7m>")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, token, twin.Cell{
|
||||
assert.Equal(t, token, twin.StyledRune{
|
||||
Rune: '>',
|
||||
Style: twin.StyleDefault.WithAttr(twin.AttrReverse),
|
||||
})
|
||||
|
@ -7,13 +7,13 @@ package twin
|
||||
type FakeScreen struct {
|
||||
width int
|
||||
height int
|
||||
cells [][]Cell
|
||||
cells [][]StyledRune
|
||||
}
|
||||
|
||||
func NewFakeScreen(width int, height int) *FakeScreen {
|
||||
rows := make([][]Cell, height)
|
||||
rows := make([][]StyledRune, height)
|
||||
for i := 0; i < height; i++ {
|
||||
rows[i] = make([]Cell, width)
|
||||
rows[i] = make([]StyledRune, width)
|
||||
}
|
||||
|
||||
return &FakeScreen{
|
||||
@ -30,7 +30,7 @@ func (screen *FakeScreen) Close() {
|
||||
func (screen *FakeScreen) Clear() {
|
||||
// This method's contents has been copied from UnixScreen.Clear()
|
||||
|
||||
empty := NewCell(' ', StyleDefault)
|
||||
empty := NewStyledRune(' ', StyleDefault)
|
||||
|
||||
width, height := screen.Size()
|
||||
for row := 0; row < height; row++ {
|
||||
@ -40,7 +40,7 @@ func (screen *FakeScreen) Clear() {
|
||||
}
|
||||
}
|
||||
|
||||
func (screen *FakeScreen) SetCell(column int, row int, cell Cell) {
|
||||
func (screen *FakeScreen) SetCell(column int, row int, cell StyledRune) {
|
||||
// This method's contents has been copied from UnixScreen.Clear()
|
||||
|
||||
if column < 0 {
|
||||
@ -85,6 +85,6 @@ func (screen *FakeScreen) Events() chan Event {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (screen *FakeScreen) GetRow(row int) []Cell {
|
||||
func (screen *FakeScreen) GetRow(row int) []StyledRune {
|
||||
return screen.cells[row]
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ type Screen interface {
|
||||
|
||||
Clear()
|
||||
|
||||
SetCell(column int, row int, cell Cell)
|
||||
SetCell(column int, row int, cell StyledRune)
|
||||
|
||||
// Render our contents into the terminal window
|
||||
Show()
|
||||
@ -80,7 +80,7 @@ type interruptableReader interface {
|
||||
type UnixScreen struct {
|
||||
widthAccessFromSizeOnly int // Access from Size() method only
|
||||
heightAccessFromSizeOnly int // Access from Size() method only
|
||||
cells [][]Cell
|
||||
cells [][]StyledRune
|
||||
|
||||
// Note that the type here doesn't matter, we only want to know whether or
|
||||
// not this channel has been signalled
|
||||
@ -556,9 +556,9 @@ func (screen *UnixScreen) Size() (width int, height int) {
|
||||
return screen.widthAccessFromSizeOnly, screen.heightAccessFromSizeOnly
|
||||
}
|
||||
|
||||
newCells := make([][]Cell, height)
|
||||
newCells := make([][]StyledRune, height)
|
||||
for rowNumber := 0; rowNumber < height; rowNumber++ {
|
||||
newCells[rowNumber] = make([]Cell, width)
|
||||
newCells[rowNumber] = make([]StyledRune, width)
|
||||
}
|
||||
|
||||
// FIXME: Copy any existing contents over to the new, resized screen array
|
||||
@ -627,7 +627,7 @@ func parseTerminalBgColorResponse(responseBytes []byte) *Color {
|
||||
return &color
|
||||
}
|
||||
|
||||
func (screen *UnixScreen) SetCell(column int, row int, cell Cell) {
|
||||
func (screen *UnixScreen) SetCell(column int, row int, cell StyledRune) {
|
||||
if column < 0 {
|
||||
return
|
||||
}
|
||||
@ -646,7 +646,7 @@ func (screen *UnixScreen) SetCell(column int, row int, cell Cell) {
|
||||
}
|
||||
|
||||
func (screen *UnixScreen) Clear() {
|
||||
empty := NewCell(' ', StyleDefault)
|
||||
empty := NewStyledRune(' ', StyleDefault)
|
||||
|
||||
width, height := screen.Size()
|
||||
for row := 0; row < height; row++ {
|
||||
@ -658,7 +658,7 @@ func (screen *UnixScreen) Clear() {
|
||||
|
||||
// Returns the rendered line, plus how many information carrying cells went into
|
||||
// it
|
||||
func renderLine(row []Cell, terminalColorCount ColorCount) (string, int) {
|
||||
func renderLine(row []StyledRune, terminalColorCount ColorCount) (string, int) {
|
||||
// Strip trailing whitespace
|
||||
lastSignificantCellIndex := len(row) - 1
|
||||
for ; lastSignificantCellIndex >= 0; lastSignificantCellIndex-- {
|
||||
|
@ -54,7 +54,7 @@ func TestConsumeEncodedEventWithNoInput(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRenderLine(t *testing.T) {
|
||||
row := []Cell{
|
||||
row := []StyledRune{
|
||||
{
|
||||
Rune: '<',
|
||||
Style: StyleDefault.WithAttr(AttrReverse),
|
||||
@ -78,7 +78,7 @@ func TestRenderLine(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRenderLineEmpty(t *testing.T) {
|
||||
row := []Cell{}
|
||||
row := []StyledRune{}
|
||||
|
||||
rendered, count := renderLine(row, ColorCount16)
|
||||
assert.Equal(t, count, 0)
|
||||
@ -89,7 +89,7 @@ func TestRenderLineEmpty(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRenderLineLastReversed(t *testing.T) {
|
||||
row := []Cell{
|
||||
row := []StyledRune{
|
||||
{
|
||||
Rune: '<',
|
||||
Style: StyleDefault.WithAttr(AttrReverse),
|
||||
@ -107,7 +107,7 @@ func TestRenderLineLastReversed(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRenderLineLastNonSpace(t *testing.T) {
|
||||
row := []Cell{
|
||||
row := []StyledRune{
|
||||
{
|
||||
Rune: 'X',
|
||||
Style: StyleDefault,
|
||||
@ -124,7 +124,7 @@ func TestRenderLineLastNonSpace(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRenderLineLastReversedPlusTrailingSpace(t *testing.T) {
|
||||
row := []Cell{
|
||||
row := []StyledRune{
|
||||
{
|
||||
Rune: '<',
|
||||
Style: StyleDefault.WithAttr(AttrReverse),
|
||||
@ -146,7 +146,7 @@ func TestRenderLineLastReversedPlusTrailingSpace(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRenderLineOnlyTrailingSpaces(t *testing.T) {
|
||||
row := []Cell{
|
||||
row := []StyledRune{
|
||||
{
|
||||
Rune: ' ',
|
||||
Style: StyleDefault,
|
||||
@ -166,7 +166,7 @@ func TestRenderLineOnlyTrailingSpaces(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRenderLineLastReversedSpaces(t *testing.T) {
|
||||
row := []Cell{
|
||||
row := []StyledRune{
|
||||
{
|
||||
Rune: ' ',
|
||||
Style: StyleDefault.WithAttr(AttrReverse),
|
||||
@ -184,7 +184,7 @@ func TestRenderLineLastReversedSpaces(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRenderLineNonPrintable(t *testing.T) {
|
||||
row := []Cell{
|
||||
row := []StyledRune{
|
||||
{
|
||||
Rune: '',
|
||||
},
|
||||
@ -204,7 +204,7 @@ func TestRenderLineNonPrintable(t *testing.T) {
|
||||
|
||||
func TestRenderHyperlinkAtEndOfLine(t *testing.T) {
|
||||
url := "https://example.com/"
|
||||
row := []Cell{
|
||||
row := []StyledRune{
|
||||
{
|
||||
Rune: '*',
|
||||
Style: StyleDefault.WithHyperlink(&url),
|
||||
@ -221,7 +221,7 @@ func TestRenderHyperlinkAtEndOfLine(t *testing.T) {
|
||||
|
||||
func TestMultiCharHyperlink(t *testing.T) {
|
||||
url := "https://example.com/"
|
||||
row := []Cell{
|
||||
row := []StyledRune{
|
||||
{
|
||||
Rune: '-',
|
||||
Style: StyleDefault.WithHyperlink(&url),
|
||||
|
@ -5,25 +5,27 @@ import (
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// Cell is a rune with a style to be written to a cell on screen
|
||||
type Cell struct {
|
||||
// StyledRune is a rune with a style to be written to a one or more cells on the
|
||||
// screen. Note that a StyledRune may use more than one cell on the screen ('午'
|
||||
// for example).
|
||||
type StyledRune struct {
|
||||
Rune rune
|
||||
Style Style
|
||||
}
|
||||
|
||||
func NewCell(rune rune, style Style) Cell {
|
||||
return Cell{
|
||||
func NewStyledRune(rune rune, style Style) StyledRune {
|
||||
return StyledRune{
|
||||
Rune: rune,
|
||||
Style: style,
|
||||
}
|
||||
}
|
||||
|
||||
func (cell Cell) String() string {
|
||||
func (cell StyledRune) String() string {
|
||||
return fmt.Sprint("rune='", string(cell.Rune), "' ", cell.Style)
|
||||
}
|
||||
|
||||
// Returns a slice of cells with trailing whitespace cells removed
|
||||
func TrimSpaceRight(cells []Cell) []Cell {
|
||||
func TrimSpaceRight(cells []StyledRune) []StyledRune {
|
||||
for i := len(cells) - 1; i >= 0; i-- {
|
||||
cell := cells[i]
|
||||
if !unicode.IsSpace(cell.Rune) {
|
||||
@ -34,11 +36,11 @@ func TrimSpaceRight(cells []Cell) []Cell {
|
||||
}
|
||||
|
||||
// All whitespace, return empty
|
||||
return []Cell{}
|
||||
return []StyledRune{}
|
||||
}
|
||||
|
||||
// Returns a slice of cells with leading whitespace cells removed
|
||||
func TrimSpaceLeft(cells []Cell) []Cell {
|
||||
func TrimSpaceLeft(cells []StyledRune) []StyledRune {
|
||||
for i := 0; i < len(cells); i++ {
|
||||
cell := cells[i]
|
||||
if !unicode.IsSpace(cell.Rune) {
|
||||
@ -49,7 +51,7 @@ func TrimSpaceLeft(cells []Cell) []Cell {
|
||||
}
|
||||
|
||||
// All whitespace, return empty
|
||||
return []Cell{}
|
||||
return []StyledRune{}
|
||||
}
|
||||
|
||||
func Printable(char rune) bool {
|
@ -11,28 +11,28 @@ func TestTrimSpaceRight(t *testing.T) {
|
||||
// Empty
|
||||
assert.Assert(t, reflect.DeepEqual(
|
||||
TrimSpaceRight(
|
||||
[]Cell{},
|
||||
[]StyledRune{},
|
||||
),
|
||||
[]Cell{}))
|
||||
[]StyledRune{}))
|
||||
|
||||
// Single non-space
|
||||
assert.Assert(t, reflect.DeepEqual(
|
||||
TrimSpaceRight(
|
||||
[]Cell{{Rune: 'x'}},
|
||||
[]StyledRune{{Rune: 'x'}},
|
||||
),
|
||||
[]Cell{{Rune: 'x'}}))
|
||||
[]StyledRune{{Rune: 'x'}}))
|
||||
|
||||
// Single space
|
||||
assert.Assert(t, reflect.DeepEqual(
|
||||
TrimSpaceRight(
|
||||
[]Cell{{Rune: ' '}},
|
||||
[]StyledRune{{Rune: ' '}},
|
||||
),
|
||||
[]Cell{}))
|
||||
[]StyledRune{}))
|
||||
|
||||
// Non-space plus space
|
||||
assert.Assert(t, reflect.DeepEqual(
|
||||
TrimSpaceRight(
|
||||
[]Cell{{Rune: 'x'}, {Rune: ' '}},
|
||||
[]StyledRune{{Rune: 'x'}, {Rune: ' '}},
|
||||
),
|
||||
[]Cell{{Rune: 'x'}}))
|
||||
[]StyledRune{{Rune: 'x'}}))
|
||||
}
|
Loading…
Reference in New Issue
Block a user