diff --git a/oviewer/action.go b/oviewer/action.go index 04f531a8..9f15ff2d 100644 --- a/oviewer/action.go +++ b/oviewer/action.go @@ -36,6 +36,7 @@ func (root *Root) toggleColumnMode(context.Context) { root.Doc.ColumnMode = !root.Doc.ColumnMode if root.Doc.ColumnMode { + root.prepareLines(root.scr.lines) root.Doc.columnCursor = root.Doc.optimalCursor(root.scr, root.Doc.columnCursor) } root.setMessagef("Set ColumnMode %t", root.Doc.ColumnMode) @@ -516,8 +517,8 @@ func (root *Root) esFormat(ctx context.Context) { // setDelimiter sets the delimiter string. func (root *Root) setDelimiter(input string) { root.Doc.setDelimiter(input) - root.Doc.optimalCursor(root.scr, root.Doc.columnCursor) - root.Doc.columnCursor = max(root.Doc.columnStart, root.Doc.columnCursor) + root.prepareLines(root.scr.lines) + root.Doc.columnCursor = root.Doc.optimalCursor(root.scr, root.Doc.columnCursor) root.setMessagef("Set delimiter %s", input) } diff --git a/oviewer/document.go b/oviewer/document.go index 98cb3310..f6fd5f85 100644 --- a/oviewer/document.go +++ b/oviewer/document.go @@ -121,9 +121,6 @@ type Document struct { // columnCursor is the number of columns. columnCursor int - // columnStart is the starting position of the column 0 or 1. - columnStart int - // lastSearchLN is the last search line number. lastSearchLN int // showGotoF displays the specified line if it is true. diff --git a/oviewer/move.go b/oviewer/move.go index 202458aa..f314aad2 100644 --- a/oviewer/move.go +++ b/oviewer/move.go @@ -186,7 +186,7 @@ func (root *Root) moveHfRight(context.Context) { // moveBeginLeft moves to the beginning of the line. func (root *Root) moveBeginLeft(context.Context) { - root.Doc.moveBeginLeft() + root.Doc.moveBeginLeft(root.scr) } // moveEndRight moves to the end of the line. diff --git a/oviewer/move_lateral.go b/oviewer/move_lateral.go index b93f1052..9a7bae37 100644 --- a/oviewer/move_lateral.go +++ b/oviewer/move_lateral.go @@ -1,65 +1,87 @@ package oviewer -import ( - "errors" - "regexp" -) - // columnMargin is the number of characters in the left and right margins of the screen. const columnMargin = 2 -// TargetLineDelimiter covers from line 1 to this line, -// because there are headers and separators. -const TargetLineDelimiter = 10 +// moveHfLeft moves to the left half screen. +func (m *Document) moveHfLeft() { + hfSize := (m.width / 2) + if m.x > 0 && (m.x-hfSize) <= 0 { + m.x = 0 + return + } + m.x -= hfSize +} + +// moveHfRight moves to the right half screen. +func (m *Document) moveHfRight() { + if m.x <= 0 { + m.x = 0 + return + } + m.x += (m.width / 2) +} + +// moveNormalLeft moves to the left. +func (m *Document) moveNormalLeft(n int) { + m.x -= n +} + +// moveNormalRight moves to the right. +func (m *Document) moveNormalRight(n int) { + m.x += n +} -// moveBeginLeft move to the left edge of the screen. -func (m *Document) moveBeginLeft() { +// moveBeginLeft moves the document view to the left edge of the screen. +// It sets the horizontal position to 0 and determines the starting column. +func (m *Document) moveBeginLeft(scr SCR) { m.x = 0 - m.columnCursor = 0 + m.columnCursor = determineColumnStart(scr.lines) } -// moveEndRight move to the right edge of the screen. +// moveEndRight moves to the right edge of the screen. func (m *Document) moveEndRight(scr SCR) { - m.x = m.endRight(scr) - m.columnCursor = m.rightmostColumn(scr) + m.x, m.columnCursor = rightEdge(scr.lines, m.width) } -// targetLine returns the target line that contains columns. -func (m *Document) targetLine(scr SCR) (LineC, error) { - for lN := m.topLN + m.firstLine(); lN < m.topLN+m.firstLine()+TargetLineDelimiter; lN++ { - lineC, ok := scr.lines[lN] - if !ok || !lineC.valid { - continue - } - if len(lineC.columnRanges) == 0 { +// rightEdge returns the right edge of the screen. +func rightEdge(lines map[int]LineC, width int) (int, int) { + maxX := 0 + maxColumn := 0 + for _, lineC := range lines { + if !lineC.valid { continue } - return lineC, nil + maxX = max(maxX, len(lineC.lc)-width) + maxColumn = max(maxColumn, len(lineC.columnRanges)-1) } - return LineC{}, ErrNoColumn + return maxX, maxColumn } // optimalCursor returns the optimal cursor position from the current x position. // This function is used to set the column cursor to the column displayed on the screen when in columnMode. func (m *Document) optimalCursor(scr SCR, cursor int) int { - lineC, err := m.targetLine(scr) + lineC, err := targetLineWithColumns(scr) if err != nil { return cursor } - left := m.x - right := m.x + (scr.vWidth - scr.startX) - cursor = max(0, cursor) + cursor = max(cursor, determineColumnStart(scr.lines)) columns := lineC.columnRanges if len(columns)-1 < cursor { + // If the cursor is out of range, set it to the last column. return len(columns) - 1 } + left := m.x + right := m.x + (scr.vWidth - scr.startX) curPos := columns[cursor].start if curPos > left && curPos < right { + // If the cursor is within the range, return cursor } + // If the cursor is out of range, move to the nearest column. if curPos < left { for n := 0; n < len(columns); n++ { if columns[n].start > left { @@ -68,7 +90,6 @@ func (m *Document) optimalCursor(scr SCR, cursor int) int { } return len(columns) - 1 } - if curPos > right { for n := len(columns) - 1; n >= 0; n-- { if columns[n].end < right { @@ -89,249 +110,176 @@ func (m *Document) optimalX(scr SCR, cursor int) (int, error) { if cursor < m.HeaderColumn { return 0, nil } - lineC, err := m.targetLine(scr) + + lineC, err := targetLineWithColumns(scr) if err != nil { return 0, err } - right := m.x + (scr.vWidth - scr.startX) columns := lineC.columnRanges - m.setColumnStart(columns) if cursor >= len(columns) { - return m.x, nil + return m.x, ErrOverScreen } - if columns[cursor].end < right { + + vh := m.vHeaderWidth(lineC) + // Move if on screen. + if columns[cursor].start > m.x+vh && columns[cursor].end < m.x+(scr.vWidth-scr.startX) { return m.x, nil } - // Move if off screen - vh := m.vHeaderWidth(lineC) + // Move if off screen. x := (columns[cursor].start - vh) - columnMargin - end := len(lineC.lc) - if end-x < scr.vWidth { - x = end - (scr.vWidth - columnMargin) + rightEdge := len(lineC.lc) + if rightEdge-x < scr.vWidth { + x = rightEdge - (scr.vWidth - columnMargin) } return x, nil } -// moveTo returns x and cursor when moving right and left. -// If the cursor is out of range, it returns an error. -// moveTo is positive for right(+1) and negative for left(-1). -func (m *Document) moveTo(scr SCR, moveTo int) (int, int, error) { - cursor := max(0, m.columnCursor+moveTo) - if (cursor == 0) || (cursor < m.HeaderColumn) { - return 0, cursor, nil - } - - lineC, err := m.targetLine(scr) +// moveColumnLeft moves to the left column. +func (m *Document) moveColumnLeft(n int, scr SCR, cycle bool) error { + lineC, err := targetLineWithColumns(scr) if err != nil { - return 0, m.columnCursor, err - } - columns := lineC.columnRanges - m.setColumnStart(columns) - if cursor >= len(columns) { - return m.x, cursor, ErrOverScreen - } - cl, cr := 0, 0 - if cursor > 0 && cursor < len(columns) { - cl = columns[cursor].start - cr = columns[cursor].end - 1 - } - vh := 0 - if m.HeaderColumn > 0 && m.HeaderColumn < len(columns) { - vh = columns[m.HeaderColumn-1].end - } - mx, cursor, err := screenAdjustX(m.x+vh, m.x+scr.vWidth, cl, cr, columns, cursor) - return mx - vh, cursor, err -} - -// screenAdjustX returns x and cursor when moving left and right. -// If it moves too much, adjust the position and return. -// Returns an error when the end is reached. -func screenAdjustX(left int, right int, cl int, cr int, columns []columnRange, cursor int) (int, int, error) { - // |left cl cr right| - // |<---width--->|<---width--->|<---width--->| widths - // cursor - width := (right - left) - columnMargin - // move cursor without scrolling - if left <= cl && cr < right { - return left, cursor, nil - } - - // right edge + 1 - if cursor > len(columns)-1 { - if cr > right { - return cr - width + columnMargin, cursor - 1, nil - } - // don't scroll - if left <= cl && cr < right { - return left, cursor - 1, ErrOverScreen - } - if columns[len(columns)-1].end > right { - return right, cursor - 1, nil - } - return left, cursor - 1, ErrOverScreen + return err } - // 0 ~ right edge - // left scroll - if cl < left { - // Don't move the cursor. - if cr < left { - return max(0, left-width), cursor + 1, nil - } - - // Move the cursor. Position at the beginning of the column. - move := (left - cl) - if move < right-left { - return max(0, cl-columnMargin), cursor, nil - } - - // Move the cursor. Position at the end of the column. - if cl < left-width { - return left - width, cursor, nil + vh := m.vHeaderWidth(lineC) + cursor := m.columnCursor + if !m.WrapMode && isValidCursor(lineC, cursor) { + cl := lineC.columnRanges[cursor].start + if m.x > cl { + // Movement of x only. + move := ((m.x - vh) - (scr.vWidth + columnMargin)) * n + m.x = max(move, cl) + return nil } - - return left - width, cursor + 1, nil } - // right scroll - // cr >= right - move := (cl - left) - if move > width { - if columns[len(columns)-1].end < right { - return cr - (right - columnMargin), cursor - 1, nil + columnStart := determineColumnStart(scr.lines) + if m.columnCursor <= columnStart { + if cycle { + m.moveEndRight(scr) + return nil } - return right - width, cursor - 1, nil + return ErrOverScreen } - // little scroll. - if left+(cr-cl) < cl { - return left + (cr - cl) + columnMargin, cursor, nil + // Move to the previous column. + cursor = m.columnCursor - n + if cursor < m.HeaderColumn { + m.x = 0 + m.columnCursor = cursor + return nil } + m.columnCursor = cursor - return cl - columnMargin, cursor, nil -} + // Movement x. -// splitByDelimiter return a slice split by delimiter. -func splitByDelimiter(str string, delimiter string, delimiterReg *regexp.Regexp) []int { - indexes := allIndex(str, delimiter, delimiterReg) - if len(indexes) == 0 { + cl := lineC.columnRanges[cursor].start + cr := lineC.columnRanges[cursor].end + x := max(0, m.x) + if cl > x+vh && cr < x+scr.vWidth { + // Movement of cursor only (x is within the range). return nil } - widths := make([]int, 0, len(indexes)+1) - - for i := 0; i < len(indexes); i++ { - widths = append(widths, indexes[i][0]) - } - // Add the last width. - lastDelmEnd := indexes[len(indexes)-1][1] - if lastDelmEnd < len(str) { - widths = append(widths, len(str)) - } - return widths -} -// moveHfLeft moves to the left half screen. -func (m *Document) moveHfLeft() { - hfSize := (m.width / 2) - if m.x > 0 && (m.x-hfSize) < 0 { + if cr < scr.vWidth-columnMargin { + // Set m.x to 0 because the right edge of the cursor fits within the screen + // even when displayed from the beginning. m.x = 0 - return + return nil } - m.x -= hfSize + m.x = max(cl-columnMargin, cr-(scr.vWidth-columnMargin)) - vh + return nil } -// moveHfRight moves to the right half screen. -func (m *Document) moveHfRight() { - if m.x < 0 { - m.x = 0 - return +// moveColumnRight moves to the right column. +func (m *Document) moveColumnRight(n int, scr SCR, cycle bool) error { + lineC, err := targetLineWithColumns(scr) + if err != nil { + return err } - m.x += (m.width / 2) -} - -// moveNormalLeft moves to the left. -func (m *Document) moveNormalLeft(n int) { - m.x -= n -} -// moveNormalRight moves to the right. -func (m *Document) moveNormalRight(n int) { - m.x += n -} + vh := m.vHeaderWidth(lineC) + cursor := m.columnCursor + if !m.WrapMode && isValidCursor(lineC, cursor) { + cr := lineC.columnRanges[cursor].end + if m.x+scr.vWidth < cr { + // Movement of x only. + move := ((m.x - vh) + (scr.vWidth - columnMargin)) * n + m.x = min(move, cr-scr.vWidth) + return nil + } + } -// moveColumnLeft moves to the left column. -func (m *Document) moveColumnLeft(n int, scr SCR, cycle bool) error { - if m.columnCursor <= m.columnStart && m.x <= columnMargin { + // Move to the next column. + cursor = m.columnCursor + n + if !isValidCursor(lineC, cursor) { if cycle { - m.columnCursor = m.rightmostColumn(scr) - m.x = max(0, m.endRight(scr)) + m.moveBeginLeft(scr) return nil } + return ErrOverScreen } + m.columnCursor = cursor + rightEdge := (m.x + scr.vWidth) - columnMargin - var err error - m.x, m.columnCursor, err = m.moveTo(scr, -n) - return err -} - -// moveColumnRight moves to the right column. -func (m *Document) moveColumnRight(n int, scr SCR, cycle bool) error { - x, cursor, err := m.moveTo(scr, n) - if err != nil { - if !errors.Is(err, ErrOverScreen) || !cycle { - return err - } - m.x = 0 - m.columnCursor = m.columnStart + cl := lineC.columnRanges[cursor].start + cr := lineC.columnRanges[cursor].end + if cr < rightEdge { + // Movement of cursor only. return nil } - m.x = x - m.columnCursor = cursor - return nil -} + // Movement x. -// endRight returns x when the content displayed on the current screen is right edge. -func (m *Document) endRight(scr SCR) int { - x := m.rightmost(scr) - x = max(0, x-(m.width-1)) - return x + if len(lineC.lc) < rightEdge { + // Set m.x to the right edge of the screen because the line is shorter than the screen width. + m.x = len(lineC.lc) - (scr.vWidth + columnMargin) + return nil + } + m.x = min(cr+columnMargin, cl+(scr.vWidth-columnMargin)) - scr.vWidth + return nil } -// rightmost of the content displayed on the screen. -func (m *Document) rightmost(scr SCR) int { - maxLen := 0 - for _, line := range scr.lines { - if !line.valid { +// targetLineWithColumns returns the target line that contains columns. +// It iterates through the lines in the screen range and returns the first valid line with columns. +func targetLineWithColumns(scr SCR) (LineC, error) { + for lN := scr.bodyLN; lN < scr.bodyEnd; lN++ { + lineC, ok := scr.lines[lN] + if !ok || !lineC.valid { continue } - lc := line.lc - maxLen = max(maxLen, len(lc)-1) + if len(lineC.columnRanges) == 0 { + continue + } + return lineC, nil } - return maxLen + return LineC{}, ErrNoColumn } -// rightmostColumn returns the number of rightmost columns. -func (m *Document) rightmostColumn(scr SCR) int { - if m.ColumnWidth { - return len(m.columnWidths) - } +// isValidCursor checks if the given cursor position is within the valid range of column ranges in the provided LineC. +// It returns true if the cursor is within the range, otherwise false. +func isValidCursor(lineC LineC, cursor int) bool { + return cursor >= 0 && cursor < len(lineC.columnRanges) +} - maxColumn := 0 - for _, line := range scr.lines { - if !line.valid { +// determineColumnStart determines the start index of the column. +// If the column starts with a delimiter, it returns 1. Otherwise, it returns 0. +// This function checks all lines to ensure that a CSV file starting with a comma +// is correctly interpreted as having an empty first value. +func determineColumnStart(lines map[int]LineC) int { + for _, lineC := range lines { + if !lineC.valid { continue } - widths := splitByDelimiter(line.str, m.ColumnDelimiter, m.ColumnDelimiterReg) - maxColumn = max(maxColumn, len(widths)-1) - } - return maxColumn -} - -func (m *Document) setColumnStart(columns []columnRange) { - if len(columns) > 0 && columns[0].end == 0 { - m.columnStart = 1 + columns := lineC.columnRanges + if len(columns) == 0 { + continue + } + if columns[0].end > 0 { + return 0 + } } + return 1 } diff --git a/oviewer/move_lateral_test.go b/oviewer/move_lateral_test.go index aa5b175e..181349ac 100644 --- a/oviewer/move_lateral_test.go +++ b/oviewer/move_lateral_test.go @@ -3,82 +3,11 @@ package oviewer import ( "context" "path/filepath" - "reflect" - "regexp" "testing" "github.com/gdamore/tcell/v2" ) -func TestDocument_moveBeginLeft(t *testing.T) { - tests := []struct { - name string - wantX int - }{ - { - name: "test1", - wantX: 0, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - m := docHelper(t, "test") - m.moveBeginLeft() - if m.x != tt.wantX { - t.Errorf("screenAdjustX() gotX = %v, wantX %v", m.x, tt.wantX) - } - }) - } -} - -func Test_splitDelimiter(t *testing.T) { - type args struct { - str string - delimiter string - delimiterReg *regexp.Regexp - } - tests := []struct { - name string - args args - want []int - }{ - { - name: "blank", - args: args{ - str: "", - delimiter: ",", - delimiterReg: nil, - }, - want: nil, - }, - { - name: "abc", - args: args{ - str: "a,b,c", - delimiter: ",", - delimiterReg: nil, - }, - want: []int{1, 3, 5}, - }, - { - name: "abc2", - args: args{ - str: "|aa|bb|cc|", - delimiter: "|", - delimiterReg: nil, - }, - want: []int{0, 3, 6, 9}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := splitByDelimiter(tt.args.str, tt.args.delimiter, tt.args.delimiterReg); !reflect.DeepEqual(got, tt.want) { - t.Errorf("splitDelimiter() = %v, want %v", got, tt.want) - } - }) - } -} - func TestDocument_optimalCursor(t *testing.T) { tcellNewScreen = fakeScreen defer func() { @@ -221,7 +150,7 @@ func TestRoot_moveColumnWithLeft(t *testing.T) { n: 1, cycle: false, }, - wantErr: false, + wantErr: true, wantCursor: 0, }, } @@ -400,7 +329,7 @@ func TestDocument_moveColumnDelimiterLeft(t *testing.T) { n: 1, cycle: false, }, - wantErr: false, + wantErr: true, wantCursor: 0, }, } diff --git a/oviewer/move_test.go b/oviewer/move_test.go index 7226a64e..37707de5 100644 --- a/oviewer/move_test.go +++ b/oviewer/move_test.go @@ -343,7 +343,19 @@ func TestRoot_moveColumn(t *testing.T) { }, want: want{ leftOne: 7, - rightOne: 2, + rightOne: 1, + }, + }, + { + name: "Test MOCK_DATA3", + fields: fields{ + fileName: filepath.Join(testdata, "MOCK_DATA.csv"), + delimiter: ",", + columnCursor: 2, + }, + want: want{ + leftOne: 1, + rightOne: 3, }, }, { @@ -354,7 +366,7 @@ func TestRoot_moveColumn(t *testing.T) { columnCursor: 1, }, want: want{ - leftOne: 0, + leftOne: 4, rightOne: 2, }, }, diff --git a/oviewer/oviewer.go b/oviewer/oviewer.go index 858f75aa..fed79408 100644 --- a/oviewer/oviewer.go +++ b/oviewer/oviewer.go @@ -99,6 +99,10 @@ type SCR struct { sectionHeaderLN int // sectionHeaderEnd is the end of the section header. sectionHeaderEnd int + // bodyLN is the number of body lines. + bodyLN int + // bodyEnd is the end of the body. + bodyEnd int // x1, y1, x2, y2 are the coordinates selected by the mouse. x1 int diff --git a/oviewer/prepare_draw.go b/oviewer/prepare_draw.go index 50b45119..151f0b76 100644 --- a/oviewer/prepare_draw.go +++ b/oviewer/prepare_draw.go @@ -60,11 +60,10 @@ func (root *Root) ViewSync(context.Context) { // prepareDraw prepares the screen for drawing. func (root *Root) prepareDraw(ctx context.Context) { - // Set the columncursor at the first run. + // Set the columnCursor at the first run. if len(root.scr.lines) == 0 { defer func() { - root.Doc.optimalCursor(root.scr, root.Doc.columnCursor) - root.Doc.columnCursor = max(root.Doc.columnStart, root.Doc.columnCursor) + root.Doc.columnCursor = root.Doc.optimalCursor(root.scr, root.Doc.columnCursor) }() } @@ -72,7 +71,7 @@ func (root *Root) prepareDraw(ctx context.Context) { root.scr.headerLN = root.Doc.SkipLines root.scr.headerEnd = root.Doc.firstLine() // Set the header height. - root.Doc.headerHeight = root.Doc.getHeight(root.scr.headerLN, root.scr.headerEnd) + root.Doc.headerHeight = min(root.scr.vHeight, root.Doc.getHeight(root.scr.headerLN, root.scr.headerEnd)) // Section header. root.scr.sectionHeaderLN = -1 @@ -81,7 +80,7 @@ func (root *Root) prepareDraw(ctx context.Context) { if root.Doc.SectionHeader { root.scr.sectionHeaderLN, root.scr.sectionHeaderEnd = root.sectionHeader(ctx, root.Doc.topLN+root.scr.headerEnd-root.Doc.SectionStartPosition, sectionTimeOut) // Set the section header height. - root.Doc.sectionHeaderHeight = root.Doc.getHeight(root.scr.sectionHeaderLN, root.scr.sectionHeaderEnd) + root.Doc.sectionHeaderHeight = min(root.scr.vHeight, root.Doc.getHeight(root.scr.sectionHeaderLN, root.scr.sectionHeaderEnd)) } // Shift the initial position of the body to the displayed position. @@ -100,6 +99,8 @@ func (root *Root) prepareDraw(ctx context.Context) { if root.Doc.Converter == convAlign { root.setAlignConverter() } + root.scr.bodyLN = root.Doc.topLN + root.Doc.firstLine() + root.scr.bodyEnd = root.scr.bodyLN + root.scr.vHeight // vHeight is the max line of logical lines. // Prepare the lines. root.scr.lines = root.prepareLines(root.scr.lines) @@ -323,9 +324,7 @@ func (root *Root) prepareLines(lines map[int]LineC) map[int]LineC { // Section header. lines = root.setLines(lines, root.scr.sectionHeaderLN, root.scr.sectionHeaderEnd) // Body. - startLN := root.Doc.topLN + root.Doc.firstLine() - endLN := startLN + root.scr.vHeight // vHeight is the max line of logical lines. - lines = root.setLines(lines, startLN, endLN) + lines = root.setLines(lines, root.scr.bodyLN, root.scr.bodyEnd) if root.Doc.SectionHeader { lines = root.sectionNum(lines) @@ -335,21 +334,27 @@ func (root *Root) prepareLines(lines map[int]LineC) map[int]LineC { // setLines sets the lines of the specified range. func (root *Root) setLines(lines map[int]LineC, startLN int, endLN int) map[int]LineC { - m := root.Doc for lN := startLN; lN < endLN; lN++ { - lineC := m.getLineC(lN) - if lineC.valid { - if root.Doc.ColumnMode { - lineC = root.columnRanges(lineC) - } - RangeStyle(lineC.lc, 0, len(lineC.lc), root.StyleBody) - root.styleContent(lineC) - } - lines[lN] = lineC + lines[lN] = root.lineContent(lN) } return lines } +// lineContent returns the contents of the specified line. +func (root *Root) lineContent(lN int) LineC { + m := root.Doc + lineC := m.getLineC(lN) + if !lineC.valid { + return lineC + } + if m.ColumnMode { + lineC = m.columnRanges(lineC) + } + RangeStyle(lineC.lc, 0, len(lineC.lc), root.StyleBody) + root.styleContent(lineC) + return lineC +} + // sectionNum sets the section number. func (root *Root) sectionNum(lines map[int]LineC) map[int]LineC { m := root.Doc @@ -457,18 +462,17 @@ func (root *Root) searchHighlight(lineC LineC) { } // columnRanges sets the column ranges. -func (root *Root) columnRanges(lineC LineC) LineC { - if root.Doc.ColumnWidth { - lineC.columnRanges = root.columnWidthRanges(lineC) +func (m Document) columnRanges(lineC LineC) LineC { + if m.ColumnWidth { + lineC.columnRanges = m.columnWidthRanges(lineC) } else { - lineC.columnRanges = root.columnDelimiterRange(lineC) + lineC.columnRanges = m.columnDelimiterRange(lineC) } return lineC } // columnDelimiterRange returns the ranges of the columns. -func (root *Root) columnDelimiterRange(lineC LineC) []columnRange { - m := root.Doc +func (m Document) columnDelimiterRange(lineC LineC) []columnRange { indexes := allIndex(lineC.str, m.ColumnDelimiter, m.ColumnDelimiterReg) if len(indexes) == 0 { return nil @@ -484,13 +488,14 @@ func (root *Root) columnDelimiterRange(lineC LineC) []columnRange { } // The last column. end := lineC.pos.x(len(lineC.str)) - columnRanges = append(columnRanges, columnRange{start: start, end: end}) + if start < end { + columnRanges = append(columnRanges, columnRange{start: start, end: end}) + } return columnRanges } // columnWidthRanges returns the ranges of the columns. -func (root *Root) columnWidthRanges(lineC LineC) []columnRange { - m := root.Doc +func (m Document) columnWidthRanges(lineC LineC) []columnRange { indexes := m.columnWidths if len(indexes) == 0 { return nil diff --git a/oviewer/prepare_draw_test.go b/oviewer/prepare_draw_test.go index c479b340..4a1a773e 100644 --- a/oviewer/prepare_draw_test.go +++ b/oviewer/prepare_draw_test.go @@ -504,6 +504,8 @@ func TestRoot_prepareLines(t *testing.T) { m.setSectionDelimiter(tt.fields.sectionDelimiter) m.SectionHeaderNum = tt.fields.sectionHeaderNum root.scr.lines = make(map[int]LineC) + root.scr.bodyLN = root.Doc.topLN + root.Doc.firstLine() + root.scr.bodyEnd = root.scr.bodyLN + root.scr.vHeight root.prepareLines(root.scr.lines) if len(root.scr.lines) != tt.want.num { t.Errorf("screen lines len got: %d, want: %d", len(root.scr.lines), tt.want.num) @@ -741,14 +743,14 @@ func TestRoot_columnDelimiterHighlight(t *testing.T) { m.ColumnDelimiterReg = condRegexpCompile(m.ColumnDelimiter) m.columnCursor = tt.fields.columnCursor root.StyleColumnHighlight = OVStyle{Bold: true} - line := root.Doc.getLineC(tt.args.lineNum) - line.columnRanges = root.columnDelimiterRange(line) - root.columnHighlight(line) - if line.str != tt.want.str { - t.Errorf("\nline: %v\nwant: %v\n", line.str, tt.want.str) + lineC := root.Doc.getLineC(tt.args.lineNum) + lineC.columnRanges = root.Doc.columnDelimiterRange(lineC) + root.columnHighlight(lineC) + if lineC.str != tt.want.str { + t.Errorf("\nline: %v\nwant: %v\n", lineC.str, tt.want.str) } - if line.lc[tt.want.start].style != columnHighlight { - t.Errorf("style got: %v want: %v", line.lc[tt.want.start].style, columnHighlight) + if lineC.lc[tt.want.start].style != columnHighlight { + t.Errorf("style got: %v want: %v", lineC.lc[tt.want.start].style, columnHighlight) } }) } @@ -815,19 +817,19 @@ func TestRoot_columnWidthHighlight(t *testing.T) { m.setColumnWidths() t.Log(m.columnWidths) m.columnCursor = tt.fields.columnCursor - line := root.Doc.getLineC(tt.args.lineNum) - line.columnRanges = root.columnWidthRanges(line) - root.columnHighlight(line) - if line.str != tt.want.str { - t.Errorf("\nline: %v\nwant: %v\n", line.str, tt.want.str) + lineC := root.Doc.getLineC(tt.args.lineNum) + lineC.columnRanges = root.Doc.columnWidthRanges(lineC) + root.columnHighlight(lineC) + if lineC.str != tt.want.str { + t.Errorf("\nline: %v\nwant: %v\n", lineC.str, tt.want.str) } - if line.lc[tt.want.start].style != columnHighlight { + if lineC.lc[tt.want.start].style != columnHighlight { v := bytes.Buffer{} - for i, l := range line.lc { + for i, l := range lineC.lc { v.WriteString(fmt.Sprintf("%d:%v", i, l.style)) } t.Logf("style: %v", v.String()) - t.Errorf("style got: %v want: %v", line.lc[tt.want.start].style, columnHighlight) + t.Errorf("style got: %v want: %v", lineC.lc[tt.want.start].style, columnHighlight) } }) }