Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support of ANSI escape sequence for clear line #651

Merged
merged 3 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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" {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is correct. This basically implements ESC[K and ESC[K0 but ESC[K1 erases from cursor to the beginning of line. And ESC[K2 erases the whole line.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your comment.
I didn't understand, what do you think is the best way to do this?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean that at least it shouldn't accept ESC[K2. But ideally implementing it and ESC[K1 properly. There are also ESCj commands which move cursor and probably worth to be supported as well.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So does that mean I only need to style spaces and 0's here?
The pager won't clear the lines in 2, but the styling will work similarly.
es

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You screenshot shows how it should work. Except for b3K which doesn't seem valid value.

So minimally we need K and 0K.

But ideally support 1K and 2K as well.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So minimally we need K and 0K.

I got it. Work only with K and 0k.

But ideally support 1K and 2K as well.

It is still unknown whether it is more correct to work as a pager.

Thank you.

// 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