From c676eedbb522c7ebdc3880854a12d06c3681ba77 Mon Sep 17 00:00:00 2001 From: Noboru Saito Date: Thu, 7 Nov 2024 17:59:31 +0900 Subject: [PATCH] Add support of ANSI escape sequence for clear line ov(and other pagers) cannot be erased. Actually, set the style from the end of the line. This solves #650 . --- oviewer/content.go | 16 ++++++++++++---- oviewer/convert_es.go | 18 +++++++++++------- oviewer/document.go | 22 ++++++++++++++++++---- oviewer/draw.go | 42 +++++++++++++++++++++--------------------- 4 files changed, 62 insertions(+), 36 deletions(-) diff --git a/oviewer/content.go b/oviewer/content.go index 8d4505cc..16c0e152 100644 --- a/oviewer/content.go +++ b/oviewer/content.go @@ -71,6 +71,7 @@ type parseState struct { mainc rune combc []rune style tcell.Style + eolStyle tcell.Style bsContent content tabWidth int tabx int @@ -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, @@ -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. diff --git a/oviewer/convert_es.go b/oviewer/convert_es.go index d0481eab..0e0a6957 100644 --- a/oviewer/convert_es.go +++ b/oviewer/convert_es.go @@ -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 diff --git a/oviewer/document.go b/oviewer/document.go index 354da395..4544ea87 100644 --- a/oviewer/document.go +++ b/oviewer/document.go @@ -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" @@ -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. @@ -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 { @@ -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 @@ -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) diff --git a/oviewer/draw.go b/oviewer/draw.go index ec2fddcc..f12ad4ca 100644 --- a/oviewer/draw.go +++ b/oviewer/draw.go @@ -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) } @@ -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) @@ -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. @@ -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 } @@ -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) } @@ -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.