Skip to content

Commit

Permalink
Add support of ANSI escape sequence for clear line
Browse files Browse the repository at this point in the history
ov(and other pagers) cannot be erased.
Actually, set the style from the end of the line.
This solves #650 .
  • Loading branch information
noborus committed Nov 7, 2024
1 parent 0fa90de commit c676eed
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 36 deletions.
16 changes: 12 additions & 4 deletions oviewer/content.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ type parseState struct {
mainc rune
combc []rune
style tcell.Style
eolStyle tcell.Style
bsContent content
tabWidth int
tabx int
Expand Down Expand Up @@ -98,13 +99,20 @@ func RawStrToContents(str string, tabWidth int) contents {
return parseString(newRawConverter(), str, tabWidth)
}

// parseString converts a string to lineContents.
// parseString is converted character by character by Converter.
// If tabwidth is set to -1, \t is displayed instead of functioning as a tab.
// parseString converts a string to contents.
// This function wraps parseLine and is used when line styles are not needed.
func parseString(conv Converter, str string, tabWidth int) contents {
lc, _ := parseLine(conv, str, tabWidth)
return lc
}

// parseLine converts a string to lineContents and eolStyle, and returns them.
// If tabWidth is set to -1, \t is displayed instead of functioning as a tab.
func parseLine(conv Converter, str string, tabWidth int) (contents, tcell.Style) {
st := &parseState{
lc: make(contents, 0, len(str)),
style: tcell.StyleDefault,
eolStyle: tcell.StyleDefault,
tabWidth: tabWidth,
tabx: 0,
bsFlag: false,
Expand All @@ -125,7 +133,7 @@ func parseString(conv Converter, str string, tabWidth int) contents {
st.mainc = '\n'
st.combc = nil
conv.convert(st)
return st.lc
return st.lc, st.eolStyle
}

// parseChar parses a single character.
Expand Down
18 changes: 11 additions & 7 deletions oviewer/convert_es.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,19 @@ func (es *escapeSequence) convert(st *parseState) bool {
}
return true
case ansiControlSequence:
if mainc == 'm' {
switch {
case mainc == 'm':
st.style = csToStyle(st.style, es.parameter.String())
} else if mainc >= 'A' && mainc <= 'T' {
// Ignore.
} else {
if mainc >= 0x30 && mainc <= 0x3f {
es.parameter.WriteRune(mainc)
return true
case mainc == 'K':
if es.parameter.String() != "1" {
// Clear line.
st.eolStyle = st.style
}
case mainc >= 'A' && mainc <= 'T':
// Ignore.
case mainc >= '0' && mainc <= 'f':
es.parameter.WriteRune(mainc)
return true
}
es.state = ansiText
return true
Expand Down
22 changes: 18 additions & 4 deletions oviewer/document.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"sync/atomic"
"time"

"github.com/gdamore/tcell/v2"
lru "github.com/hashicorp/golang-lru/v2"
"github.com/jwalton/gchalk"
"github.com/noborus/guesswidth"
Expand Down Expand Up @@ -204,6 +205,8 @@ type LineC struct {
section int
// Line number within a section.
sectionNm int
// eolStyle is the style of the end of the line.
eolStyle tcell.Style
}

// NewDocument returns Document.
Expand Down Expand Up @@ -442,6 +445,16 @@ func (m *Document) contents(lN int) (contents, error) {
return parseString(m.conv, str, m.TabWidth), err
}

func (m *Document) contentsLine(lN int) (contents, tcell.Style, error) {
if lN < 0 || lN >= m.BufEndNum() {
return nil, tcell.StyleDefault, ErrOutOfRange
}

str, err := m.LineStr(lN)
lc, style := parseLine(m.conv, str, m.TabWidth)
return lc, style, err
}

// getLineC returns contents from line number and tabWidth.
// If the line number does not exist, EOF content is returned.
func (m *Document) getLineC(lN int) LineC {
Expand All @@ -453,7 +466,7 @@ func (m *Document) getLineC(lN int) LineC {
return line
}

org, err := m.contents(lN)
org, style, err := m.contentsLine(lN)
if err != nil && errors.Is(err, ErrOutOfRange) {
lc := make(contents, 1)
lc[0] = EOFContent
Expand All @@ -466,9 +479,10 @@ func (m *Document) getLineC(lN int) LineC {
}
str, pos := ContentsToStr(org)
line := LineC{
lc: org,
str: str,
pos: pos,
lc: org,
str: str,
pos: pos,
eolStyle: style,
}
if err == nil {
m.cache.Add(lN, line)
Expand Down
42 changes: 21 additions & 21 deletions oviewer/draw.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func (root *Root) drawBody(lX int, lN int) (int, int) {
root.scr.numbers[y] = newLineNumber(lN, wrapNum)
root.drawLineNumber(lN, y, line.valid)

nextLX, nextLN := root.drawLine(y, lX, lN, line.lc)
nextLX, nextLN := root.drawLine(y, lX, lN, line)
if line.valid {
root.coordinatesStyle(lN, y)
}
Expand Down Expand Up @@ -93,14 +93,14 @@ func (root *Root) drawHeader() {
lX := 0
lN := root.scr.headerLN
for y := 0; y < m.headerHeight && lN < root.scr.headerEnd; y++ {
line, ok := root.scr.lines[lN]
lineC, ok := root.scr.lines[lN]
if !ok {
panic(fmt.Sprintf("line is not found %d", lN))
}
root.scr.numbers[y] = newLineNumber(lN, wrapNum)
root.blankLineNumber(y)

lX, lN = root.drawLine(y, lX, lN, line.lc)
lX, lN = root.drawLine(y, lX, lN, lineC)
// header style.
root.applyStyleToLine(y, root.StyleHeader)

Expand All @@ -120,14 +120,14 @@ func (root *Root) drawSectionHeader() {
lX := 0
lN := root.scr.sectionHeaderLN
for y := m.headerHeight; y < m.headerHeight+m.sectionHeaderHeight && lN < root.scr.sectionHeaderEnd; y++ {
line, ok := root.scr.lines[lN]
lineC, ok := root.scr.lines[lN]
if !ok {
panic(fmt.Sprintf("line is not found %d", lN))
}
root.scr.numbers[y] = newLineNumber(lN, wrapNum)
root.drawLineNumber(lN, y, line.valid)
root.drawLineNumber(lN, y, lineC.valid)

nextLX, nextLN := root.drawLine(y, lX, lN, line.lc)
nextLX, nextLN := root.drawLine(y, lX, lN, lineC)
// section header style.
root.applyStyleToLine(y, root.StyleSectionLine)
// markstyle is displayed above the section header.
Expand All @@ -149,33 +149,33 @@ func (root *Root) drawSectionHeader() {
}

// drawWrapLine wraps and draws the contents and returns the next drawing position.
func (root *Root) drawLine(y int, lX int, lN int, lc contents) (int, int) {
func (root *Root) drawLine(y int, lX int, lN int, lineC LineC) (int, int) {
if root.Doc.WrapMode {
return root.drawWrapLine(y, lX, lN, lc)
return root.drawWrapLine(y, lX, lN, lineC)
}

return root.drawNoWrapLine(y, root.Doc.x, lN, lc)
return root.drawNoWrapLine(y, root.Doc.x, lN, lineC)
}

// drawWrapLine wraps and draws the contents and returns the next drawing position.
func (root *Root) drawWrapLine(y int, lX int, lN int, lc contents) (int, int) {
func (root *Root) drawWrapLine(y int, lX int, lN int, lineC LineC) (int, int) {
if lX < 0 {
log.Printf("Illegal lX:%d", lX)
return 0, 0
}

for x := 0; ; x++ {
if lX+x >= len(lc) {
if lX+x >= len(lineC.lc) {
// EOL
root.clearEOL(root.scr.startX+x, y)
root.clearEOL(root.scr.startX+x, y, lineC.eolStyle)
lX = 0
lN++
break
}
content := lc[lX+x]
content := lineC.lc[lX+x]
if x+root.scr.startX+content.width > root.scr.vWidth {
// Right edge.
root.clearEOL(root.scr.startX+x, y)
root.clearEOL(root.scr.startX+x, y, tcell.StyleDefault)
lX += x
break
}
Expand All @@ -186,17 +186,17 @@ func (root *Root) drawWrapLine(y int, lX int, lN int, lc contents) (int, int) {
}

// drawNoWrapLine draws contents without wrapping and returns the next drawing position.
func (root *Root) drawNoWrapLine(y int, startX int, lN int, lc contents) (int, int) {
func (root *Root) drawNoWrapLine(y int, startX int, lN int, lineC LineC) (int, int) {
startX = max(startX, root.minStartX)
for x := 0; root.scr.startX+x < root.scr.vWidth; x++ {
if startX+x >= len(lc) {
if startX+x >= len(lineC.lc) {
// EOL
root.clearEOL(root.scr.startX+x, y)
root.clearEOL(root.scr.startX+x, y, lineC.eolStyle)
break
}
content := DefaultContent
if startX+x >= 0 {
content = lc[startX+x]
content = lineC.lc[startX+x]
}
root.Screen.SetContent(root.scr.startX+x, y, content.mainc, content.combc, content.style)
}
Expand Down Expand Up @@ -258,15 +258,15 @@ func (root *Root) setContentString(vx int, vy int, lc contents) {
}

// clearEOL clears from the specified position to the right end.
func (root *Root) clearEOL(x int, y int) {
func (root *Root) clearEOL(x int, y int, style tcell.Style) {
for ; x < root.scr.vWidth; x++ {
root.Screen.SetContent(x, y, ' ', nil, defaultStyle)
root.Screen.SetContent(x, y, ' ', nil, style)
}
}

// clearY clear the specified line.
func (root *Root) clearY(y int) {
root.clearEOL(0, y)
root.clearEOL(0, y, tcell.StyleDefault)
}

// coordinatesStyle applies the style of the coordinates.
Expand Down

0 comments on commit c676eed

Please sign in to comment.