Skip to content

Commit

Permalink
Merge pull request #610 from noborus/align-width
Browse files Browse the repository at this point in the history
Add align converter
  • Loading branch information
noborus authored Aug 24, 2024
2 parents c1f7690 + a16d4ea commit de00f0e
Show file tree
Hide file tree
Showing 10 changed files with 266 additions and 7 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
11 changes: 11 additions & 0 deletions oviewer/content.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '~'

Expand Down Expand Up @@ -96,6 +104,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
}

Expand Down
108 changes: 108 additions & 0 deletions oviewer/convert_align.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
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 string
delimiterReg *regexp.Regexp
count int
}

func newAlignConverter(widthF bool) *align {
return &align{
es: newESConverter(),
count: 0,
WidthF: widthF,
}
}

// 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.reset()
}
if a.es.convert(st) {
return true
}

if len(a.maxWidths) == 0 {
return false
}
a.count += 1
if runewidth.RuneWidth(st.mainc) > 1 {
a.count += 1
}

if st.mainc != '\n' {
return false
}

if a.WidthF {
return a.convertWidth(st)
}
return a.convertDelm(st)
}

func (a *align) reset() {
a.count = 0
}

// 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 {
str, pos := ContentsToStr(st.lc)
indexes := allIndex(str, a.delimiter, a.delimiterReg)
if len(indexes) == 0 {
return false
}
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
}
lc = append(lc, st.lc[s:]...)
st.lc = lc
return false
}

// 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 {
s := 0
lc := make(contents, 0, len(st.lc))
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]...)
// Add space to align columns.
for ; width <= a.maxWidths[i]; width++ {
lc = append(lc, SpaceContent)
}
s = e
}
lc = append(lc, st.lc[s:]...)
st.lc = lc
return false
}
2 changes: 1 addition & 1 deletion oviewer/convert_es.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ func (es *escapeSequence) convert(st *parseState) bool {
es.state = ansiEscape
return true
case '\n':
return true
return false
}
return false
}
Expand Down
5 changes: 5 additions & 0 deletions oviewer/document.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -222,6 +224,7 @@ func NewDocument() (*Document, error) {
if err := m.NewCache(); err != nil {
return nil, err
}
m.alignConv = newAlignConverter(m.ColumnWidth)
m.conv = m.converterType(m.general.Converter)
return m, nil
}
Expand All @@ -244,6 +247,8 @@ func (m *Document) converterType(name string) Converter {
return newRawConverter()
case "es":
return newESConverter()
case "align":
return m.alignConv
}
return defaultConverter
}
Expand Down
11 changes: 9 additions & 2 deletions oviewer/move_vertical.go
Original file line number Diff line number Diff line change
Expand Up @@ -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--
}
Expand Down
101 changes: 100 additions & 1 deletion oviewer/prepare_draw.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"log"
"math"
"reflect"
"sort"
"strconv"
"time"
Expand Down Expand Up @@ -81,14 +82,103 @@ 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()
}

// Sets the maximum width of a column.
if root.Doc.Converter == "align" {
root.setAlignColumnWidths()
}

// Prepare the lines.
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
if !m.alignConv.WidthF {
root.Doc.alignConv.delimiter = m.ColumnDelimiter
root.Doc.alignConv.delimiterReg = m.ColumnDelimiterReg
}

maxWidths := make([]int, 0, len(m.alignConv.maxWidths))
for ln := root.scr.headerLN; ln < root.scr.headerEnd; ln++ {
maxWidths = m.maxWidths(maxWidths, ln)
}
for ln := root.scr.sectionHeaderLN; ln < root.scr.sectionHeaderEnd; ln++ {
maxWidths = m.maxWidths(maxWidths, ln)
}
startLN := m.topLN + m.firstLine()
endLN := startLN + root.scr.vHeight
for ln := startLN; ln < endLN; ln++ {
maxWidths = m.maxWidths(maxWidths, ln)
}

if !reflect.DeepEqual(m.alignConv.maxWidths, maxWidths) {
m.alignConv.orgWidths = m.columnWidths
m.alignConv.maxWidths = maxWidths
m.ClearCache()
}
}

// 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.maxWidthsWidth(lc, columnWidth)
}
return m.maxWidthsDelm(lc, columnWidth)
}

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

// 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
}
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 {
Expand Down Expand Up @@ -367,7 +457,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.maxWidths) {
l = m.alignConv.maxWidths[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])
Expand Down
7 changes: 7 additions & 0 deletions oviewer/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 22 additions & 0 deletions oviewer/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit de00f0e

Please sign in to comment.