From 3f8a461fd3bb4fd2f8348e597fdd67269acc6a7c Mon Sep 17 00:00:00 2001 From: Noboru Saito Date: Tue, 20 Aug 2024 00:57:42 +0900 Subject: [PATCH 1/4] Add align converter Add a converter to add spaces to align delimited columns. --- oviewer/convert_align.go | 56 ++++++++++++++++++++++++++++++++++++++++ oviewer/document.go | 5 ++++ oviewer/prepare_draw.go | 55 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+) create mode 100644 oviewer/convert_align.go diff --git a/oviewer/convert_align.go b/oviewer/convert_align.go new file mode 100644 index 0000000..5122232 --- /dev/null +++ b/oviewer/convert_align.go @@ -0,0 +1,56 @@ +package oviewer + +import "github.com/mattn/go-runewidth" + +type align struct { + Columns []int // column width + delimiter []rune + delmCount int + count int + columnNum int +} + +func newAlignConverter() *align { + return &align{ + count: 0, + columnNum: 0, + } +} + +// convert converts only escape sequence codes to display characters and returns them as is. +// Returns true if it is an escape sequence and a non-printing character. +func (a *align) convert(st *parseState) bool { + if len(st.lc) == 0 { + a.count = 0 + a.columnNum = 0 + a.delmCount = 0 + } + + a.count += 1 + if runewidth.RuneWidth(st.mainc) > 1 { + a.count += 1 + } + + if len(a.Columns) <= a.columnNum { + return false + } + if len(a.delimiter) > a.delmCount && st.mainc == a.delimiter[a.delmCount] { + a.delmCount += 1 + } else { + a.delmCount = 0 + return false + } + if len(a.delimiter) < a.delmCount { + return false + } + // Add space to align columns. + for ; a.count < a.Columns[a.columnNum]; a.count++ { + c := DefaultContent + st.lc = append(st.lc, c) + } + a.count = 0 + a.columnNum++ + a.delmCount = 0 + + return false +} diff --git a/oviewer/document.go b/oviewer/document.go index 4dfa847..8287e4f 100644 --- a/oviewer/document.go +++ b/oviewer/document.go @@ -55,6 +55,8 @@ type Document struct { // conv is an interface that converts escape sequences, etc. conv Converter + // alignConv is an interface that converts alignment. + alignConv *align // fileName is the file name to display. FileName string @@ -222,6 +224,7 @@ func NewDocument() (*Document, error) { if err := m.NewCache(); err != nil { return nil, err } + m.alignConv = newAlignConverter() m.conv = m.converterType(m.general.Converter) return m, nil } @@ -244,6 +247,8 @@ func (m *Document) converterType(name string) Converter { return newRawConverter() case "es": return newESConverter() + case "align": + return m.alignConv } return defaultConverter } diff --git a/oviewer/prepare_draw.go b/oviewer/prepare_draw.go index 4ab6de2..44ff0c6 100644 --- a/oviewer/prepare_draw.go +++ b/oviewer/prepare_draw.go @@ -5,6 +5,7 @@ import ( "errors" "log" "math" + "reflect" "sort" "strconv" "time" @@ -81,14 +82,68 @@ func (root *Root) prepareDraw(ctx context.Context) { root.Doc.topLX, root.Doc.topLN = tX, tN-root.scr.headerEnd root.Doc.showGotoF = false } + if root.Doc.ColumnWidth && len(root.Doc.columnWidths) == 0 { root.Doc.setColumnWidths() } + if root.Doc.Converter == "align" { + root.Doc.alignConv.delimiter = []rune(root.Doc.ColumnDelimiter) + root.setAlignColumnWidths() + } + // Prepare the lines. root.scr.lines = root.prepareLines(root.scr.lines) } +func (root *Root) setAlignColumnWidths() { + m := root.Doc + columnWidth := make([]int, 0, len(m.alignConv.Columns)) + + for ln := root.scr.headerLN; ln < root.scr.headerEnd; ln++ { + columnWidth = m.maxColumnWidths(columnWidth, ln) + } + for ln := root.scr.sectionHeaderLN; ln < root.scr.sectionHeaderEnd; ln++ { + columnWidth = m.maxColumnWidths(columnWidth, ln) + } + startLN := m.topLN + m.firstLine() + endLN := startLN + root.scr.vHeight + for ln := startLN; ln < endLN; ln++ { + columnWidth = m.maxColumnWidths(columnWidth, ln) + } + + if !reflect.DeepEqual(m.alignConv.Columns, columnWidth) { + m.alignConv.Columns = columnWidth + m.ClearCache() + log.Println("columns", m.alignConv.Columns) + } +} + +func (m *Document) maxColumnWidths(columnWidth []int, lN int) []int { + str, err := m.LineStr(lN) + if err != nil { + return columnWidth + } + lc := StrToContents(str, m.TabWidth) + str, pos := ContentsToStr(lc) + indexes := allIndex(str, m.ColumnDelimiter, m.ColumnDelimiterReg) + if len(indexes) == 0 { + return columnWidth + } + s := 0 + for i := 0; i < len(indexes); i++ { + e := pos.x(indexes[i][1]) + width := e - s + if len(columnWidth) <= i { + columnWidth = append(columnWidth, width) + } else { + columnWidth[i] = max(width, columnWidth[i]) + } + s = e + } + return columnWidth +} + // shiftBody shifts the section header so that it is not hidden by it. func (m *Document) shiftBody(lX int, lN int, shStart int, shEnd int) (int, int) { if m.jumpTargetHeight != 0 { From 7690017c66092c078cecbf57bb23daf1a807c3d3 Mon Sep 17 00:00:00 2001 From: Noboru Saito Date: Thu, 22 Aug 2024 11:11:06 +0900 Subject: [PATCH 2/4] Add support aligning text to the width of the screen Add escape sequence support for align --- go.mod | 2 +- go.sum | 4 +- oviewer/content.go | 3 ++ oviewer/convert_align.go | 87 ++++++++++++++++++++++++++++++++-------- oviewer/convert_es.go | 2 +- oviewer/document.go | 2 +- oviewer/prepare_draw.go | 59 ++++++++++++++++++++++----- 7 files changed, 128 insertions(+), 31 deletions(-) diff --git a/go.mod b/go.mod index ff8f7f3..60d4909 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.19 require ( code.rocketnine.space/tslocum/cbind v0.1.5 github.com/atotto/clipboard v0.1.4 - github.com/creack/pty v1.1.21 + github.com/creack/pty v1.1.23 github.com/fsnotify/fsnotify v1.7.0 github.com/gdamore/tcell/v2 v2.7.4 github.com/hashicorp/golang-lru/v2 v2.0.7 diff --git a/go.sum b/go.sum index 212e983..5c1a605 100644 --- a/go.sum +++ b/go.sum @@ -3,8 +3,8 @@ code.rocketnine.space/tslocum/cbind v0.1.5/go.mod h1:LtfqJTzM7qhg88nAvNhx+VnTjZ0 github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= -github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0= +github.com/creack/pty v1.1.23/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= diff --git a/oviewer/content.go b/oviewer/content.go index fde486d..5c2e752 100644 --- a/oviewer/content.go +++ b/oviewer/content.go @@ -96,6 +96,9 @@ func parseString(conv Converter, str string, tabWidth int) contents { } st.parseChar(st.mainc, st.combc) } + st.mainc = '\n' + st.combc = nil + conv.convert(st) return st.lc } diff --git a/oviewer/convert_align.go b/oviewer/convert_align.go index 5122232..7825119 100644 --- a/oviewer/convert_align.go +++ b/oviewer/convert_align.go @@ -1,19 +1,30 @@ package oviewer -import "github.com/mattn/go-runewidth" +import ( + "log" + + "github.com/mattn/go-runewidth" +) type align struct { - Columns []int // column width - delimiter []rune - delmCount int - count int - columnNum int + es *escapeSequence + Columns []int // column width + orgColumns []int + WidthF bool + overF bool + delimiter []rune + delmCount int + count int + fullCount int + columnNum int } -func newAlignConverter() *align { +func newAlignConverter(widthF bool) *align { return &align{ + es: newESConverter(), count: 0, columnNum: 0, + WidthF: widthF, } } @@ -21,36 +32,78 @@ func newAlignConverter() *align { // Returns true if it is an escape sequence and a non-printing character. func (a *align) convert(st *parseState) bool { if len(st.lc) == 0 { - a.count = 0 - a.columnNum = 0 - a.delmCount = 0 + a.reset() + } + if a.es.convert(st) { + return true } + if a.columnNum >= len(a.Columns) { + return false + } a.count += 1 if runewidth.RuneWidth(st.mainc) > 1 { a.count += 1 } - if len(a.Columns) <= a.columnNum { - return false + if a.WidthF { + return a.convertWidth(st) } - if len(a.delimiter) > a.delmCount && st.mainc == a.delimiter[a.delmCount] { + return a.convertDelm(st) +} + +func (a *align) reset() { + a.fullCount = 0 + a.count = 0 + a.columnNum = 0 + a.delmCount = 0 + a.overF = false +} + +func (a *align) convertDelm(st *parseState) bool { + if a.delmCount < len(a.delimiter) && st.mainc == a.delimiter[a.delmCount] { a.delmCount += 1 } else { a.delmCount = 0 return false } - if len(a.delimiter) < a.delmCount { + if a.delmCount > len(a.delimiter) { return false } // Add space to align columns. for ; a.count < a.Columns[a.columnNum]; a.count++ { - c := DefaultContent - st.lc = append(st.lc, c) + st.lc = append(st.lc, DefaultContent) } - a.count = 0 a.columnNum++ + a.count = 0 a.delmCount = 0 return false } + +func (a *align) convertWidth(st *parseState) bool { + if st.mainc != '\n' { + return false + } + s := 0 + lc := make(contents, 0, len(st.lc)) + for i := 0; i < len(a.orgColumns); i++ { + e := findColumnEnd(st.lc, a.orgColumns, i) + 1 + e = min(e, len(st.lc)) + width := e - s + if s >= e { + break + } + lc = append(lc, st.lc[s:e]...) + for ; width <= a.Columns[i]; width++ { + c := DefaultContent + c.mainc = '_' + lc = append(lc, c) + } + s = e + } + lc = append(lc, st.lc[s:]...) + log.Println("len", len(st.lc), len(lc)) + st.lc = lc + return false +} diff --git a/oviewer/convert_es.go b/oviewer/convert_es.go index a01e7db..d0481ea 100644 --- a/oviewer/convert_es.go +++ b/oviewer/convert_es.go @@ -149,7 +149,7 @@ func (es *escapeSequence) convert(st *parseState) bool { es.state = ansiEscape return true case '\n': - return true + return false } return false } diff --git a/oviewer/document.go b/oviewer/document.go index 8287e4f..aac224f 100644 --- a/oviewer/document.go +++ b/oviewer/document.go @@ -224,7 +224,7 @@ func NewDocument() (*Document, error) { if err := m.NewCache(); err != nil { return nil, err } - m.alignConv = newAlignConverter() + m.alignConv = newAlignConverter(m.ColumnWidth) m.conv = m.converterType(m.general.Converter) return m, nil } diff --git a/oviewer/prepare_draw.go b/oviewer/prepare_draw.go index 44ff0c6..13f9709 100644 --- a/oviewer/prepare_draw.go +++ b/oviewer/prepare_draw.go @@ -88,7 +88,6 @@ func (root *Root) prepareDraw(ctx context.Context) { } if root.Doc.Converter == "align" { - root.Doc.alignConv.delimiter = []rune(root.Doc.ColumnDelimiter) root.setAlignColumnWidths() } @@ -98,24 +97,29 @@ func (root *Root) prepareDraw(ctx context.Context) { func (root *Root) setAlignColumnWidths() { m := root.Doc - columnWidth := make([]int, 0, len(m.alignConv.Columns)) + m.alignConv.WidthF = m.ColumnWidth + if !m.alignConv.WidthF { + root.Doc.alignConv.delimiter = []rune(root.Doc.ColumnDelimiter) + } + alignWidth := make([]int, 0, len(m.alignConv.Columns)) for ln := root.scr.headerLN; ln < root.scr.headerEnd; ln++ { - columnWidth = m.maxColumnWidths(columnWidth, ln) + alignWidth = m.maxColumnWidths(alignWidth, ln) } for ln := root.scr.sectionHeaderLN; ln < root.scr.sectionHeaderEnd; ln++ { - columnWidth = m.maxColumnWidths(columnWidth, ln) + alignWidth = m.maxColumnWidths(alignWidth, ln) } startLN := m.topLN + m.firstLine() endLN := startLN + root.scr.vHeight for ln := startLN; ln < endLN; ln++ { - columnWidth = m.maxColumnWidths(columnWidth, ln) + alignWidth = m.maxColumnWidths(alignWidth, ln) } + log.Println("columnWidths", m.columnWidths, "alignWidth", alignWidth) - if !reflect.DeepEqual(m.alignConv.Columns, columnWidth) { - m.alignConv.Columns = columnWidth + m.alignConv.orgColumns = m.columnWidths + if !reflect.DeepEqual(m.alignConv.Columns, alignWidth) { + m.alignConv.Columns = alignWidth m.ClearCache() - log.Println("columns", m.alignConv.Columns) } } @@ -124,6 +128,13 @@ func (m *Document) maxColumnWidths(columnWidth []int, lN int) []int { if err != nil { return columnWidth } + if m.ColumnWidth { + return m.maxColumnWidthsWidth(str, columnWidth) + } + return m.maxColumnWidthsDelm(str, columnWidth) +} + +func (m *Document) maxColumnWidthsDelm(str string, columnWidth []int) []int { lc := StrToContents(str, m.TabWidth) str, pos := ContentsToStr(lc) indexes := allIndex(str, m.ColumnDelimiter, m.ColumnDelimiterReg) @@ -144,6 +155,27 @@ func (m *Document) maxColumnWidths(columnWidth []int, lN int) []int { return columnWidth } +func (m *Document) maxColumnWidthsWidth(str string, columnWidth []int) []int { + indexes := m.columnWidths + if len(indexes) == 0 { + return columnWidth + } + + lc := StrToContents(str, m.TabWidth) + s := 0 + for i := 0; i < len(indexes); i++ { + e := findColumnEnd(lc, indexes, i) + 1 + width := e - s + if len(columnWidth) <= i { + columnWidth = append(columnWidth, width) + } else { + columnWidth[i] = max(width, columnWidth[i]) + } + s = e + } + return columnWidth +} + // shiftBody shifts the section header so that it is not hidden by it. func (m *Document) shiftBody(lX int, lN int, shStart int, shEnd int) (int, int) { if m.jumpTargetHeight != 0 { @@ -422,7 +454,16 @@ func (root *Root) columnWidthHighlight(line LineC) { start, end := -1, -1 for c := 0; c < len(indexes)+1; c++ { start = end + 1 - end = findColumnEnd(line.lc, indexes, c) + if m.Converter == "align" { + l := len(line.lc) + if c < len(m.alignConv.Columns) { + l = m.alignConv.Columns[c] + } + end = start + l + end = min(end, len(line.lc)) + } else { + end = findColumnEnd(line.lc, indexes, c) + } if m.ColumnRainbow { RangeStyle(line.lc, start, end, root.StyleColumnRainbow[c%numC]) From e0d04b450494f1c84aa6cace511924d3de5bf7b8 Mon Sep 17 00:00:00 2001 From: Noboru Saito Date: Fri, 23 Aug 2024 09:43:39 +0900 Subject: [PATCH 3/4] Refactored column-width alignment Add definition of spaceContent. Fixed line length by omitting '\n'. Remove unnecessary variables. --- oviewer/content.go | 8 ++++++++ oviewer/convert_align.go | 42 +++++++++++++++++----------------------- oviewer/move_vertical.go | 11 +++++++++-- oviewer/prepare_draw.go | 38 +++++++++++++++++++----------------- 4 files changed, 55 insertions(+), 44 deletions(-) diff --git a/oviewer/content.go b/oviewer/content.go index 5c2e752..0120ea4 100644 --- a/oviewer/content.go +++ b/oviewer/content.go @@ -28,6 +28,14 @@ var DefaultContent = content{ style: tcell.StyleDefault, } +// SpaceContent is a space character. +var SpaceContent = content{ + mainc: ' ', + combc: nil, + width: 1, + style: tcell.StyleDefault, +} + // EOFC is the EOF character. const EOFC rune = '~' diff --git a/oviewer/convert_align.go b/oviewer/convert_align.go index 7825119..1a4f8f4 100644 --- a/oviewer/convert_align.go +++ b/oviewer/convert_align.go @@ -1,22 +1,18 @@ package oviewer import ( - "log" - "github.com/mattn/go-runewidth" ) type align struct { - es *escapeSequence - Columns []int // column width - orgColumns []int - WidthF bool - overF bool - delimiter []rune - delmCount int - count int - fullCount int - columnNum int + es *escapeSequence + maxWidths []int // column max width + orgWidths []int + WidthF bool + delimiter []rune + delmCount int + count int + columnNum int } func newAlignConverter(widthF bool) *align { @@ -38,7 +34,7 @@ func (a *align) convert(st *parseState) bool { return true } - if a.columnNum >= len(a.Columns) { + if a.columnNum >= len(a.maxWidths) { return false } a.count += 1 @@ -53,13 +49,12 @@ func (a *align) convert(st *parseState) bool { } func (a *align) reset() { - a.fullCount = 0 a.count = 0 a.columnNum = 0 a.delmCount = 0 - a.overF = false } +// convertWidth accumulates one line and then adds spaces to align the column widths. func (a *align) convertDelm(st *parseState) bool { if a.delmCount < len(a.delimiter) && st.mainc == a.delimiter[a.delmCount] { a.delmCount += 1 @@ -71,8 +66,8 @@ func (a *align) convertDelm(st *parseState) bool { return false } // Add space to align columns. - for ; a.count < a.Columns[a.columnNum]; a.count++ { - st.lc = append(st.lc, DefaultContent) + for ; a.count < a.maxWidths[a.columnNum]; a.count++ { + st.lc = append(st.lc, SpaceContent) } a.columnNum++ a.count = 0 @@ -81,29 +76,28 @@ func (a *align) convertDelm(st *parseState) bool { return false } +// convertDelm aligns the column widths by adding spaces when it reaches a delimiter. func (a *align) convertWidth(st *parseState) bool { if st.mainc != '\n' { return false } s := 0 lc := make(contents, 0, len(st.lc)) - for i := 0; i < len(a.orgColumns); i++ { - e := findColumnEnd(st.lc, a.orgColumns, i) + 1 + for i := 0; i < len(a.orgWidths); i++ { + e := findColumnEnd(st.lc, a.orgWidths, i) + 1 e = min(e, len(st.lc)) width := e - s if s >= e { break } lc = append(lc, st.lc[s:e]...) - for ; width <= a.Columns[i]; width++ { - c := DefaultContent - c.mainc = '_' - lc = append(lc, c) + // Add space to align columns. + for ; width <= a.maxWidths[i]; width++ { + lc = append(lc, SpaceContent) } s = e } lc = append(lc, st.lc[s:]...) - log.Println("len", len(st.lc), len(lc)) st.lc = lc return false } diff --git a/oviewer/move_vertical.go b/oviewer/move_vertical.go index 6afee09..f871bc0 100644 --- a/oviewer/move_vertical.go +++ b/oviewer/move_vertical.go @@ -236,9 +236,16 @@ func leftX(width int, lc contents) []int { if width <= 0 { return []int{0} } - listX := make([]int, 0, (len(lc)/width)+1) + if len(lc) == 0 { + return []int{0} + } + end := len(lc) + if lc[len(lc)-1].mainc == '\n' { + end-- + } + listX := make([]int, 0, (end/width)+1) listX = append(listX, 0) - for n := width; n < len(lc); n += width { + for n := width; n < end; n += width { if lc[n-1].width == 2 { n-- } diff --git a/oviewer/prepare_draw.go b/oviewer/prepare_draw.go index 13f9709..019820f 100644 --- a/oviewer/prepare_draw.go +++ b/oviewer/prepare_draw.go @@ -87,6 +87,7 @@ func (root *Root) prepareDraw(ctx context.Context) { root.Doc.setColumnWidths() } + // Sets the maximum width of a column. if root.Doc.Converter == "align" { root.setAlignColumnWidths() } @@ -95,6 +96,7 @@ func (root *Root) prepareDraw(ctx context.Context) { root.scr.lines = root.prepareLines(root.scr.lines) } +// setAlignColumnWidths sets the maximum width of the column. func (root *Root) setAlignColumnWidths() { m := root.Doc m.alignConv.WidthF = m.ColumnWidth @@ -102,40 +104,41 @@ func (root *Root) setAlignColumnWidths() { root.Doc.alignConv.delimiter = []rune(root.Doc.ColumnDelimiter) } - alignWidth := make([]int, 0, len(m.alignConv.Columns)) + maxWidths := make([]int, 0, len(m.alignConv.maxWidths)) for ln := root.scr.headerLN; ln < root.scr.headerEnd; ln++ { - alignWidth = m.maxColumnWidths(alignWidth, ln) + maxWidths = m.maxWidths(maxWidths, ln) } for ln := root.scr.sectionHeaderLN; ln < root.scr.sectionHeaderEnd; ln++ { - alignWidth = m.maxColumnWidths(alignWidth, ln) + maxWidths = m.maxWidths(maxWidths, ln) } startLN := m.topLN + m.firstLine() endLN := startLN + root.scr.vHeight for ln := startLN; ln < endLN; ln++ { - alignWidth = m.maxColumnWidths(alignWidth, ln) + maxWidths = m.maxWidths(maxWidths, ln) } - log.Println("columnWidths", m.columnWidths, "alignWidth", alignWidth) - m.alignConv.orgColumns = m.columnWidths - if !reflect.DeepEqual(m.alignConv.Columns, alignWidth) { - m.alignConv.Columns = alignWidth + if !reflect.DeepEqual(m.alignConv.maxWidths, maxWidths) { + m.alignConv.orgWidths = m.columnWidths + m.alignConv.maxWidths = maxWidths m.ClearCache() } } -func (m *Document) maxColumnWidths(columnWidth []int, lN int) []int { +// maxWidths returns the maximum width of the column. +func (m *Document) maxWidths(columnWidth []int, lN int) []int { str, err := m.LineStr(lN) if err != nil { return columnWidth } + lc := StrToContents(str, m.TabWidth) if m.ColumnWidth { - return m.maxColumnWidthsWidth(str, columnWidth) + return m.maxWidthsWidth(lc, columnWidth) } - return m.maxColumnWidthsDelm(str, columnWidth) + return m.maxWidthsDelm(lc, columnWidth) } -func (m *Document) maxColumnWidthsDelm(str string, columnWidth []int) []int { - lc := StrToContents(str, m.TabWidth) +// maxWidthsDelm returns the maximum width of the column. +func (m *Document) maxWidthsDelm(lc contents, columnWidth []int) []int { str, pos := ContentsToStr(lc) indexes := allIndex(str, m.ColumnDelimiter, m.ColumnDelimiterReg) if len(indexes) == 0 { @@ -155,13 +158,12 @@ func (m *Document) maxColumnWidthsDelm(str string, columnWidth []int) []int { return columnWidth } -func (m *Document) maxColumnWidthsWidth(str string, columnWidth []int) []int { +// maxWidthsWidth returns the maximum width of the column. +func (m *Document) maxWidthsWidth(lc contents, columnWidth []int) []int { indexes := m.columnWidths if len(indexes) == 0 { return columnWidth } - - lc := StrToContents(str, m.TabWidth) s := 0 for i := 0; i < len(indexes); i++ { e := findColumnEnd(lc, indexes, i) + 1 @@ -456,8 +458,8 @@ func (root *Root) columnWidthHighlight(line LineC) { start = end + 1 if m.Converter == "align" { l := len(line.lc) - if c < len(m.alignConv.Columns) { - l = m.alignConv.Columns[c] + if c < len(m.alignConv.maxWidths) { + l = m.alignConv.maxWidths[c] } end = start + l end = min(end, len(line.lc)) From a16d4ea7f68a705c4041921785cf30984b167fec Mon Sep 17 00:00:00 2001 From: Noboru Saito Date: Sat, 24 Aug 2024 14:33:44 +0900 Subject: [PATCH 4/4] Quote support Change dlimiter processing to line-by-line --- oviewer/convert_align.go | 73 +++++++++++++++++++++------------------- oviewer/prepare_draw.go | 3 +- oviewer/utils.go | 7 ++++ oviewer/utils_test.go | 22 ++++++++++++ 4 files changed, 70 insertions(+), 35 deletions(-) diff --git a/oviewer/convert_align.go b/oviewer/convert_align.go index 1a4f8f4..305cd4a 100644 --- a/oviewer/convert_align.go +++ b/oviewer/convert_align.go @@ -1,26 +1,28 @@ package oviewer import ( + "regexp" + "github.com/mattn/go-runewidth" ) +// align is a converter that aligns columns. +// It is used to align columns when the delimiter is reached or to align columns by adding spaces to the end of the line. type align struct { - es *escapeSequence - maxWidths []int // column max width - orgWidths []int - WidthF bool - delimiter []rune - delmCount int - count int - columnNum int + es *escapeSequence + maxWidths []int // column max width + orgWidths []int + WidthF bool + delimiter string + delimiterReg *regexp.Regexp + count int } func newAlignConverter(widthF bool) *align { return &align{ - es: newESConverter(), - count: 0, - columnNum: 0, - WidthF: widthF, + es: newESConverter(), + count: 0, + WidthF: widthF, } } @@ -34,7 +36,7 @@ func (a *align) convert(st *parseState) bool { return true } - if a.columnNum >= len(a.maxWidths) { + if len(a.maxWidths) == 0 { return false } a.count += 1 @@ -42,6 +44,10 @@ func (a *align) convert(st *parseState) bool { a.count += 1 } + if st.mainc != '\n' { + return false + } + if a.WidthF { return a.convertWidth(st) } @@ -50,37 +56,36 @@ func (a *align) convert(st *parseState) bool { func (a *align) reset() { a.count = 0 - a.columnNum = 0 - a.delmCount = 0 } -// convertWidth accumulates one line and then adds spaces to align the column widths. +// convertDelm aligns the column widths by adding spaces when it reaches a delimiter. +// convertDelm works line by line. func (a *align) convertDelm(st *parseState) bool { - if a.delmCount < len(a.delimiter) && st.mainc == a.delimiter[a.delmCount] { - a.delmCount += 1 - } else { - a.delmCount = 0 + str, pos := ContentsToStr(st.lc) + indexes := allIndex(str, a.delimiter, a.delimiterReg) + if len(indexes) == 0 { return false } - if a.delmCount > len(a.delimiter) { - return false - } - // Add space to align columns. - for ; a.count < a.maxWidths[a.columnNum]; a.count++ { - st.lc = append(st.lc, SpaceContent) + s := 0 + lc := make(contents, 0, len(st.lc)) + for c := 0; c < len(indexes); c++ { + e := pos.x(indexes[c][0]) + lc = append(lc, st.lc[s:e]...) + width := e - s + // Add space to align columns. + for ; width < a.maxWidths[c]; width++ { + lc = append(lc, SpaceContent) + } + s = e } - a.columnNum++ - a.count = 0 - a.delmCount = 0 - + lc = append(lc, st.lc[s:]...) + st.lc = lc return false } -// convertDelm aligns the column widths by adding spaces when it reaches a delimiter. +// convertWidth accumulates one line and then adds spaces to align the column widths. +// convertWidth works line by line. func (a *align) convertWidth(st *parseState) bool { - if st.mainc != '\n' { - return false - } s := 0 lc := make(contents, 0, len(st.lc)) for i := 0; i < len(a.orgWidths); i++ { diff --git a/oviewer/prepare_draw.go b/oviewer/prepare_draw.go index 019820f..752ba40 100644 --- a/oviewer/prepare_draw.go +++ b/oviewer/prepare_draw.go @@ -101,7 +101,8 @@ func (root *Root) setAlignColumnWidths() { m := root.Doc m.alignConv.WidthF = m.ColumnWidth if !m.alignConv.WidthF { - root.Doc.alignConv.delimiter = []rune(root.Doc.ColumnDelimiter) + root.Doc.alignConv.delimiter = m.ColumnDelimiter + root.Doc.alignConv.delimiterReg = m.ColumnDelimiterReg } maxWidths := make([]int, 0, len(m.alignConv.maxWidths)) diff --git a/oviewer/utils.go b/oviewer/utils.go index 0577161..8eb3abb 100644 --- a/oviewer/utils.go +++ b/oviewer/utils.go @@ -95,6 +95,13 @@ func allStringIndex(s string, substr string) [][]int { s = s[pos+width:] result = append(result, []int{pos + offSet, pos + offSet + width}) offSet += pos + width + + if len(s) > 0 && s[0] == '"' { + qpos := strings.Index(s[1:], `"`) + s = s[qpos+2:] + offSet += qpos + 2 + } + pos = strings.Index(s, substr) } return result diff --git a/oviewer/utils_test.go b/oviewer/utils_test.go index 51aa15b..b9533d1 100644 --- a/oviewer/utils_test.go +++ b/oviewer/utils_test.go @@ -520,6 +520,28 @@ func Test_allStringIndex(t *testing.T) { }, want: nil, }, + { + name: "testDoubleQuote", + args: args{ + s: `a,"b,c",d`, + substr: ",", + }, + want: [][]int{ + {1, 2}, + {7, 8}, + }, + }, + { + name: "testDoubleQuote2", + args: args{ + s: `a,"060 ",d`, + substr: ",", + }, + want: [][]int{ + {1, 2}, + {9, 10}, + }, + }, } for _, tt := range tests { tt := tt