From 4acc392e42d653ca5543d1904c6e807f2f982f30 Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Mon, 6 Jan 2025 16:44:32 -0500 Subject: [PATCH] chore(viewport): make horizontal scroll API better match vertical scroll API --- viewport/viewport.go | 46 ++++++++++++++++--------------- viewport/viewport_test.go | 58 ++++++++++++++++++++------------------- 2 files changed, 54 insertions(+), 50 deletions(-) diff --git a/viewport/viewport.go b/viewport/viewport.go index f0939cf..0ae1163 100644 --- a/viewport/viewport.go +++ b/viewport/viewport.go @@ -39,6 +39,13 @@ type Model struct { // YOffset is the vertical scroll position. YOffset int + // xOffset is the horizontal scroll position. + xOffset int + + // horizontalStep is the number of columns we move left or right during a + // default horizontal scroll. + horizontalStep int + // YPosition is the position of the viewport in relation to the terminal // window. It's used in high performance rendering only. YPosition int @@ -59,10 +66,6 @@ type Model struct { // Deprecated: high performance rendering is now deprecated in Bubble Tea. HighPerformanceRendering bool - // horizontal step represents the step of indent we add with one move left or right. - horizontalStep int - - indent int initialized bool lines []string longestLineWidth int @@ -138,8 +141,8 @@ func (m Model) visibleLines() (lines []string) { cutLines := make([]string, len(lines)) for i, line := range lines { - if m.indent > 0 { - line = ansi.TruncateLeft(line, m.indent, "") + if m.xOffset > 0 { + line = ansi.TruncateLeft(line, m.xOffset, "") } line = ansi.Truncate(line, m.Width, "") cutLines[i] = line @@ -311,9 +314,9 @@ func ViewUp(m Model, lines []string) tea.Cmd { return tea.ScrollUp(lines, top, bottom) } -// SetHorizontalStep is a setter for `horizontalStep`. -// Must be set before `MoveLeft` or `MoveRight` is used. -// If 0 or negative, left/right movement doesn't work. +// SetHorizontalStep sets the amount of cells that the viewport moves in the +// default viewport keymapping. If set to 0 or less, horizontal scrolling is +// disabled. func (m *Model) SetHorizontalStep(n int) { if n < 0 { n = 0 @@ -322,27 +325,26 @@ func (m *Model) SetHorizontalStep(n int) { m.horizontalStep = n } -// MoveLeft moves all lines to set runes left. -// If current indent is 0, it doesn't work. -func (m *Model) MoveLeft() { - m.indent -= m.horizontalStep - if m.indent < 0 { - m.indent = 0 +// MoveLeft moves the viewport to the left by the given number of columns. +func (m *Model) MoveLeft(cols int) { + m.xOffset -= cols + if m.xOffset < 0 { + m.xOffset = 0 } } -// MoveRight moves all lines to set runes right. -func (m *Model) MoveRight() { +// MoveRight moves viewport to the right by the given number of columns. +func (m *Model) MoveRight(cols int) { // prevents over scrolling to the right - if m.indent >= m.longestLineWidth-m.Width { + if m.xOffset >= m.longestLineWidth-m.Width { return } - m.indent += m.horizontalStep + m.xOffset += cols } // Resets lines indent to zero. func (m *Model) ResetIndent() { - m.indent = 0 + m.xOffset = 0 } // Update handles standard message-based viewport updates. @@ -401,10 +403,10 @@ func (m Model) updateAsModel(msg tea.Msg) (Model, tea.Cmd) { } case key.Matches(msg, m.KeyMap.Left): - m.MoveLeft() + m.MoveLeft(m.horizontalStep) case key.Matches(msg, m.KeyMap.Right): - m.MoveRight() + m.MoveRight(m.horizontalStep) } case tea.MouseMsg: diff --git a/viewport/viewport_test.go b/viewport/viewport_test.go index b618ac9..c90499a 100644 --- a/viewport/viewport_test.go +++ b/viewport/viewport_test.go @@ -91,28 +91,28 @@ func TestMoveLeft(t *testing.T) { t.Parallel() m := New(10, 10) - if m.indent != zeroPosition { - t.Errorf("default indent should be %d, got %d", zeroPosition, m.indent) + if m.xOffset != zeroPosition { + t.Errorf("default indent should be %d, got %d", zeroPosition, m.xOffset) } - m.MoveLeft() - if m.indent != zeroPosition { - t.Errorf("indent should be %d, got %d", zeroPosition, m.indent) + m.MoveLeft(m.horizontalStep) + if m.xOffset != zeroPosition { + t.Errorf("indent should be %d, got %d", zeroPosition, m.xOffset) } }) t.Run("move", func(t *testing.T) { t.Parallel() m := New(10, 10) - if m.indent != zeroPosition { - t.Errorf("default indent should be %d, got %d", zeroPosition, m.indent) + if m.xOffset != zeroPosition { + t.Errorf("default indent should be %d, got %d", zeroPosition, m.xOffset) } - m.indent = defaultHorizontalStep * 2 - m.MoveLeft() + m.xOffset = defaultHorizontalStep * 2 + m.MoveLeft(m.horizontalStep) newIndent := defaultHorizontalStep - if m.indent != newIndent { - t.Errorf("indent should be %d, got %d", newIndent, m.indent) + if m.xOffset != newIndent { + t.Errorf("indent should be %d, got %d", newIndent, m.xOffset) } }) } @@ -127,14 +127,14 @@ func TestMoveRight(t *testing.T) { m := New(10, 10) m.SetContent("Some line that is longer than width") - if m.indent != zeroPosition { - t.Errorf("default indent should be %d, got %d", zeroPosition, m.indent) + if m.xOffset != zeroPosition { + t.Errorf("default indent should be %d, got %d", zeroPosition, m.xOffset) } - m.MoveRight() + m.MoveRight(m.horizontalStep) newIndent := defaultHorizontalStep - if m.indent != newIndent { - t.Errorf("indent should be %d, got %d", newIndent, m.indent) + if m.xOffset != newIndent { + t.Errorf("indent should be %d, got %d", newIndent, m.xOffset) } }) } @@ -148,11 +148,11 @@ func TestResetIndent(t *testing.T) { zeroPosition := 0 m := New(10, 10) - m.indent = 500 + m.xOffset = 500 m.ResetIndent() - if m.indent != zeroPosition { - t.Errorf("indent should be %d, got %d", zeroPosition, m.indent) + if m.xOffset != zeroPosition { + t.Errorf("indent should be %d, got %d", zeroPosition, m.xOffset) } }) } @@ -196,7 +196,7 @@ func TestVisibleLines(t *testing.T) { m := New(10, 10) list := m.visibleLines() - m.indent = 5 + m.xOffset = 5 if len(list) != 0 { t.Errorf("list should be empty, got %d", len(list)) @@ -274,10 +274,10 @@ func TestVisibleLines(t *testing.T) { } // move right - m.MoveRight() + m.MoveRight(m.horizontalStep) list = m.visibleLines() - newPrefix := perceptPrefix[m.indent:] + newPrefix := perceptPrefix[m.xOffset:] if !strings.HasPrefix(list[0], newPrefix) { t.Errorf("first list item has to have prefix %s, get %s", newPrefix, list[0]) } @@ -287,7 +287,7 @@ func TestVisibleLines(t *testing.T) { } // move left - m.MoveLeft() + m.MoveLeft(m.horizontalStep) list = m.visibleLines() if !strings.HasPrefix(list[0], perceptPrefix) { t.Errorf("first list item has to have prefix %s", perceptPrefix) @@ -301,6 +301,8 @@ func TestVisibleLines(t *testing.T) { t.Run("list: with 2 cells symbols: horizontal scroll", func(t *testing.T) { t.Parallel() + const horizontalStep = 5 + initList := []string{ "あいうえお", "Aあいうえお", @@ -327,7 +329,7 @@ func TestVisibleLines(t *testing.T) { } // move right - m.MoveRight() + m.MoveRight(horizontalStep) list = m.visibleLines() for i := range list { @@ -338,7 +340,7 @@ func TestVisibleLines(t *testing.T) { } // move left - m.MoveLeft() + m.MoveLeft(horizontalStep) list = m.visibleLines() for i := range list { if list[i] != initList[i] { @@ -347,8 +349,8 @@ func TestVisibleLines(t *testing.T) { } // move left second times do not change lites if indent == 0 - m.indent = 0 - m.MoveLeft() + m.xOffset = 0 + m.MoveLeft(horizontalStep) list = m.visibleLines() for i := range list { if list[i] != initList[i] { @@ -368,7 +370,7 @@ func TestRightOverscroll(t *testing.T) { m.SetContent(content) for i := 0; i < 10; i++ { - m.MoveRight() + m.MoveRight(m.horizontalStep) } visibleLines := m.visibleLines()