From c7f889e364e15dc5b08a8c799e80164031847ab9 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Wed, 26 Mar 2025 17:07:04 -0300 Subject: [PATCH 1/5] fix(viewport): normalize method names --- textarea/textarea.go | 4 +- textarea/textarea_test.go | 2 +- viewport/viewport.go | 145 ++++++++++++++++++++++++-------------- viewport/viewport_test.go | 20 +++--- 4 files changed, 105 insertions(+), 66 deletions(-) diff --git a/textarea/textarea.go b/textarea/textarea.go index 88bc979..9af8402 100644 --- a/textarea/textarea.go +++ b/textarea/textarea.go @@ -857,9 +857,9 @@ func (m *Model) repositionView() { maximum := minimum + m.viewport.Height - 1 if row := m.cursorLineNumber(); row < minimum { - m.viewport.LineUp(minimum - row) + m.viewport.ScrollUp(minimum - row) } else if row > maximum { - m.viewport.LineDown(row - maximum) + m.viewport.ScrollDown(row - maximum) } } diff --git a/textarea/textarea_test.go b/textarea/textarea_test.go index 41d626c..9b272c5 100644 --- a/textarea/textarea_test.go +++ b/textarea/textarea_test.go @@ -43,7 +43,7 @@ func TestVerticalScrolling(t *testing.T) { "the text area.", } for _, line := range lines { - textarea.viewport.LineDown(1) + textarea.viewport.ScrollDown(1) view = textarea.View() if !strings.Contains(view, line) { t.Log(view) diff --git a/viewport/viewport.go b/viewport/viewport.go index 0a818c0..dc61e74 100644 --- a/viewport/viewport.go +++ b/viewport/viewport.go @@ -10,9 +10,7 @@ import ( "github.com/charmbracelet/x/ansi" ) -const ( - defaultHorizontalStep = 6 -) +const defaultHorizontalStep = 6 // New returns a new model with the given width and height as well as default // key mappings. @@ -168,7 +166,7 @@ func (m Model) visibleLines() (lines []string) { // scrollArea returns the scrollable boundaries for high performance rendering. // -// XXX: high performance rendering is deprecated in Bubble Tea. +// Deprecated: high performance rendering is deprecated in Bubble Tea. func (m Model) scrollArea() (top, bottom int) { top = max(0, m.YPosition) bottom = max(top, top+m.Height) @@ -183,45 +181,81 @@ func (m *Model) SetYOffset(n int) { m.YOffset = clamp(n, 0, m.maxYOffset()) } -// ViewDown moves the view down by the number of lines in the viewport. +// PageDown moves the view down by the number of lines in the viewport. // Basically, "page down". +// +// Deprecated: use [Model.PageDown] instead. func (m *Model) ViewDown() []string { + return m.PageDown() +} + +// PageDown moves the view down by the number of lines in the viewport. +func (m *Model) PageDown() []string { if m.AtBottom() { return nil } - return m.LineDown(m.Height) + return m.ScrollDown(m.Height) } -// ViewUp moves the view up by one height of the viewport. Basically, "page up". +// ViewUp moves the view up by one height of the viewport. +// Basically, "page up". +// +// Deprecated: use [Model.PageUp] instead. func (m *Model) ViewUp() []string { + return m.PageUp() +} + +// PageUp moves the view up by one height of the viewport. +func (m *Model) PageUp() []string { if m.AtTop() { return nil } - return m.LineUp(m.Height) + return m.ScrollUp(m.Height) } // HalfViewDown moves the view down by half the height of the viewport. +// +// Deprecated: use [Model.HalfPageDown] instead. func (m *Model) HalfViewDown() (lines []string) { + return m.HalfPageDown() +} + +// HalfPageDown moves the view down by half the height of the viewport. +func (m *Model) HalfPageDown() (lines []string) { if m.AtBottom() { return nil } - return m.LineDown(m.Height / 2) //nolint:mnd + return m.ScrollDown(m.Height / 2) //nolint:mnd } // HalfViewUp moves the view up by half the height of the viewport. +// +// Deprecated: use [Model.HalfPageUp] instead. func (m *Model) HalfViewUp() (lines []string) { + return m.HalfPageUp() +} + +// HalfPageUp moves the view up by half the height of the viewport. +func (m *Model) HalfPageUp() (lines []string) { if m.AtTop() { return nil } - return m.LineUp(m.Height / 2) //nolint:mnd + return m.ScrollUp(m.Height / 2) //nolint:mnd } // LineDown moves the view down by the given number of lines. +// +// Deprecated: use [Model.ScrollDown] instead. func (m *Model) LineDown(n int) (lines []string) { + return m.ScrollDown(n) +} + +// ScrollDown moves the view down by the given number of lines. +func (m *Model) ScrollDown(n int) (lines []string) { if m.AtBottom() || n == 0 || len(m.lines) == 0 { return nil } @@ -241,7 +275,15 @@ func (m *Model) LineDown(n int) (lines []string) { // LineUp moves the view down by the given number of lines. Returns the new // lines to show. +// +// Deprecated: use [Model.ScrollUp] instead. func (m *Model) LineUp(n int) (lines []string) { + return m.ScrollUp(n) +} + +// ScrollUp moves the view down by the given number of lines. Returns the new +// lines to show. +func (m *Model) ScrollUp(n int) (lines []string) { if m.AtTop() || n == 0 || len(m.lines) == 0 { return nil } @@ -258,6 +300,32 @@ func (m *Model) LineUp(n int) (lines []string) { return m.lines[top:bottom] } +// SetHorizontalStep sets the default amount of columns to scroll left or right +// with the default viewport key map. +// If set to 0 or less, horizontal scrolling is disabled. +func (m *Model) SetHorizontalStep(n int) { + if n < 0 { + n = 0 + } + + m.horizontalStep = n +} + +// SetXOffset sets the X offset. +func (m *Model) SetXOffset(n int) { + m.xOffset = clamp(n, 0, m.longestLineWidth-m.Width) +} + +// ScrollLeft moves the viewport to the left by the given number of columns. +func (m *Model) ScrollLeft(n int) { + m.SetXOffset(m.xOffset - n) +} + +// ScrollRight moves viewport to the right by the given number of columns. +func (m *Model) ScrollRight(n int) { + m.SetXOffset(m.xOffset + n) +} + // TotalLineCount returns the total number of lines (both hidden and visible) within the viewport. func (m Model) TotalLineCount() int { return len(m.lines) @@ -305,6 +373,8 @@ func Sync(m Model) tea.Cmd { // // lines := model.ViewDown(1) // cmd := ViewDown(m, lines) +// +// Deprecated: high performance rendering is deprecated in Bubble Tea. func ViewDown(m Model, lines []string) tea.Cmd { if len(lines) == 0 { return nil @@ -319,6 +389,8 @@ func ViewDown(m Model, lines []string) tea.Cmd { // ViewUp is a high performance command the moves the viewport down by a given // number of lines height. Use Model.ViewUp to get the lines that should be // rendered. +// +// Deprecated: high performance rendering is deprecated in Bubble Tea. func ViewUp(m Model, lines []string) tea.Cmd { if len(lines) == 0 { return nil @@ -330,39 +402,6 @@ func ViewUp(m Model, lines []string) tea.Cmd { return tea.ScrollUp(lines, top, bottom) //nolint:staticcheck } -// 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 - } - - m.horizontalStep = n -} - -// 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 viewport to the right by the given number of columns. -func (m *Model) MoveRight(cols int) { - // prevents over scrolling to the right - if m.xOffset >= m.longestLineWidth-m.Width { - return - } - m.xOffset += cols -} - -// Resets lines indent to zero. -func (m *Model) ResetIndent() { - m.xOffset = 0 -} - // Update handles standard message-based viewport updates. func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { var cmd tea.Cmd @@ -383,46 +422,46 @@ func (m Model) updateAsModel(msg tea.Msg) (Model, tea.Cmd) { case tea.KeyMsg: switch { case key.Matches(msg, m.KeyMap.PageDown): - lines := m.ViewDown() + lines := m.PageDown() if m.HighPerformanceRendering { cmd = ViewDown(m, lines) } case key.Matches(msg, m.KeyMap.PageUp): - lines := m.ViewUp() + lines := m.PageUp() if m.HighPerformanceRendering { cmd = ViewUp(m, lines) } case key.Matches(msg, m.KeyMap.HalfPageDown): - lines := m.HalfViewDown() + lines := m.HalfPageDown() if m.HighPerformanceRendering { cmd = ViewDown(m, lines) } case key.Matches(msg, m.KeyMap.HalfPageUp): - lines := m.HalfViewUp() + lines := m.HalfPageUp() if m.HighPerformanceRendering { cmd = ViewUp(m, lines) } case key.Matches(msg, m.KeyMap.Down): - lines := m.LineDown(1) + lines := m.ScrollDown(1) if m.HighPerformanceRendering { cmd = ViewDown(m, lines) } case key.Matches(msg, m.KeyMap.Up): - lines := m.LineUp(1) + lines := m.ScrollUp(1) if m.HighPerformanceRendering { cmd = ViewUp(m, lines) } case key.Matches(msg, m.KeyMap.Left): - m.MoveLeft(m.horizontalStep) + m.ScrollLeft(m.horizontalStep) case key.Matches(msg, m.KeyMap.Right): - m.MoveRight(m.horizontalStep) + m.ScrollRight(m.horizontalStep) } case tea.MouseMsg: @@ -431,13 +470,13 @@ func (m Model) updateAsModel(msg tea.Msg) (Model, tea.Cmd) { } switch msg.Button { //nolint:exhaustive case tea.MouseButtonWheelUp: - lines := m.LineUp(m.MouseWheelDelta) + lines := m.ScrollUp(m.MouseWheelDelta) if m.HighPerformanceRendering { cmd = ViewUp(m, lines) } case tea.MouseButtonWheelDown: - lines := m.LineDown(m.MouseWheelDelta) + lines := m.ScrollDown(m.MouseWheelDelta) if m.HighPerformanceRendering { cmd = ViewDown(m, lines) } diff --git a/viewport/viewport_test.go b/viewport/viewport_test.go index ef70a54..b29da88 100644 --- a/viewport/viewport_test.go +++ b/viewport/viewport_test.go @@ -95,7 +95,7 @@ func TestMoveLeft(t *testing.T) { t.Errorf("default indent should be %d, got %d", zeroPosition, m.xOffset) } - m.MoveLeft(m.horizontalStep) + m.ScrollLeft(m.horizontalStep) if m.xOffset != zeroPosition { t.Errorf("indent should be %d, got %d", zeroPosition, m.xOffset) } @@ -109,7 +109,7 @@ func TestMoveLeft(t *testing.T) { } m.xOffset = defaultHorizontalStep * 2 - m.MoveLeft(m.horizontalStep) + m.ScrollLeft(m.horizontalStep) newIndent := defaultHorizontalStep if m.xOffset != newIndent { t.Errorf("indent should be %d, got %d", newIndent, m.xOffset) @@ -131,7 +131,7 @@ func TestMoveRight(t *testing.T) { t.Errorf("default indent should be %d, got %d", zeroPosition, m.xOffset) } - m.MoveRight(m.horizontalStep) + m.ScrollRight(m.horizontalStep) newIndent := defaultHorizontalStep if m.xOffset != newIndent { t.Errorf("indent should be %d, got %d", newIndent, m.xOffset) @@ -150,7 +150,7 @@ func TestResetIndent(t *testing.T) { m := New(10, 10) m.xOffset = 500 - m.ResetIndent() + m.SetXOffset(0) if m.xOffset != zeroPosition { t.Errorf("indent should be %d, got %d", zeroPosition, m.xOffset) } @@ -274,7 +274,7 @@ func TestVisibleLines(t *testing.T) { } // move right - m.MoveRight(m.horizontalStep) + m.ScrollRight(m.horizontalStep) list = m.visibleLines() newPrefix := perceptPrefix[m.xOffset:] @@ -287,7 +287,7 @@ func TestVisibleLines(t *testing.T) { } // move left - m.MoveLeft(m.horizontalStep) + m.ScrollLeft(m.horizontalStep) list = m.visibleLines() if !strings.HasPrefix(list[0], perceptPrefix) { t.Errorf("first list item has to have prefix %s", perceptPrefix) @@ -329,7 +329,7 @@ func TestVisibleLines(t *testing.T) { } // move right - m.MoveRight(horizontalStep) + m.ScrollRight(horizontalStep) list = m.visibleLines() for i := range list { @@ -340,7 +340,7 @@ func TestVisibleLines(t *testing.T) { } // move left - m.MoveLeft(horizontalStep) + m.ScrollLeft(horizontalStep) list = m.visibleLines() for i := range list { if list[i] != initList[i] { @@ -350,7 +350,7 @@ func TestVisibleLines(t *testing.T) { // move left second times do not change lites if indent == 0 m.xOffset = 0 - m.MoveLeft(horizontalStep) + m.ScrollLeft(horizontalStep) list = m.visibleLines() for i := range list { if list[i] != initList[i] { @@ -370,7 +370,7 @@ func TestRightOverscroll(t *testing.T) { m.SetContent(content) for i := 0; i < 10; i++ { - m.MoveRight(m.horizontalStep) + m.ScrollRight(m.horizontalStep) } visibleLines := m.visibleLines() From f2434c374bd0f55ac0d5b3e8261161b97ea41a30 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Wed, 26 Mar 2025 17:07:26 -0300 Subject: [PATCH 2/5] Revert "fix(viewport): normalize method names" This reverts commit c7f889e364e15dc5b08a8c799e80164031847ab9. accidental push to master --- textarea/textarea.go | 4 +- textarea/textarea_test.go | 2 +- viewport/viewport.go | 145 ++++++++++++++------------------------ viewport/viewport_test.go | 20 +++--- 4 files changed, 66 insertions(+), 105 deletions(-) diff --git a/textarea/textarea.go b/textarea/textarea.go index 9af8402..88bc979 100644 --- a/textarea/textarea.go +++ b/textarea/textarea.go @@ -857,9 +857,9 @@ func (m *Model) repositionView() { maximum := minimum + m.viewport.Height - 1 if row := m.cursorLineNumber(); row < minimum { - m.viewport.ScrollUp(minimum - row) + m.viewport.LineUp(minimum - row) } else if row > maximum { - m.viewport.ScrollDown(row - maximum) + m.viewport.LineDown(row - maximum) } } diff --git a/textarea/textarea_test.go b/textarea/textarea_test.go index 9b272c5..41d626c 100644 --- a/textarea/textarea_test.go +++ b/textarea/textarea_test.go @@ -43,7 +43,7 @@ func TestVerticalScrolling(t *testing.T) { "the text area.", } for _, line := range lines { - textarea.viewport.ScrollDown(1) + textarea.viewport.LineDown(1) view = textarea.View() if !strings.Contains(view, line) { t.Log(view) diff --git a/viewport/viewport.go b/viewport/viewport.go index dc61e74..0a818c0 100644 --- a/viewport/viewport.go +++ b/viewport/viewport.go @@ -10,7 +10,9 @@ import ( "github.com/charmbracelet/x/ansi" ) -const defaultHorizontalStep = 6 +const ( + defaultHorizontalStep = 6 +) // New returns a new model with the given width and height as well as default // key mappings. @@ -166,7 +168,7 @@ func (m Model) visibleLines() (lines []string) { // scrollArea returns the scrollable boundaries for high performance rendering. // -// Deprecated: high performance rendering is deprecated in Bubble Tea. +// XXX: high performance rendering is deprecated in Bubble Tea. func (m Model) scrollArea() (top, bottom int) { top = max(0, m.YPosition) bottom = max(top, top+m.Height) @@ -181,81 +183,45 @@ func (m *Model) SetYOffset(n int) { m.YOffset = clamp(n, 0, m.maxYOffset()) } -// PageDown moves the view down by the number of lines in the viewport. +// ViewDown moves the view down by the number of lines in the viewport. // Basically, "page down". -// -// Deprecated: use [Model.PageDown] instead. func (m *Model) ViewDown() []string { - return m.PageDown() -} - -// PageDown moves the view down by the number of lines in the viewport. -func (m *Model) PageDown() []string { if m.AtBottom() { return nil } - return m.ScrollDown(m.Height) + return m.LineDown(m.Height) } -// ViewUp moves the view up by one height of the viewport. -// Basically, "page up". -// -// Deprecated: use [Model.PageUp] instead. +// ViewUp moves the view up by one height of the viewport. Basically, "page up". func (m *Model) ViewUp() []string { - return m.PageUp() -} - -// PageUp moves the view up by one height of the viewport. -func (m *Model) PageUp() []string { if m.AtTop() { return nil } - return m.ScrollUp(m.Height) + return m.LineUp(m.Height) } // HalfViewDown moves the view down by half the height of the viewport. -// -// Deprecated: use [Model.HalfPageDown] instead. func (m *Model) HalfViewDown() (lines []string) { - return m.HalfPageDown() -} - -// HalfPageDown moves the view down by half the height of the viewport. -func (m *Model) HalfPageDown() (lines []string) { if m.AtBottom() { return nil } - return m.ScrollDown(m.Height / 2) //nolint:mnd + return m.LineDown(m.Height / 2) //nolint:mnd } // HalfViewUp moves the view up by half the height of the viewport. -// -// Deprecated: use [Model.HalfPageUp] instead. func (m *Model) HalfViewUp() (lines []string) { - return m.HalfPageUp() -} - -// HalfPageUp moves the view up by half the height of the viewport. -func (m *Model) HalfPageUp() (lines []string) { if m.AtTop() { return nil } - return m.ScrollUp(m.Height / 2) //nolint:mnd + return m.LineUp(m.Height / 2) //nolint:mnd } // LineDown moves the view down by the given number of lines. -// -// Deprecated: use [Model.ScrollDown] instead. func (m *Model) LineDown(n int) (lines []string) { - return m.ScrollDown(n) -} - -// ScrollDown moves the view down by the given number of lines. -func (m *Model) ScrollDown(n int) (lines []string) { if m.AtBottom() || n == 0 || len(m.lines) == 0 { return nil } @@ -275,15 +241,7 @@ func (m *Model) ScrollDown(n int) (lines []string) { // LineUp moves the view down by the given number of lines. Returns the new // lines to show. -// -// Deprecated: use [Model.ScrollUp] instead. func (m *Model) LineUp(n int) (lines []string) { - return m.ScrollUp(n) -} - -// ScrollUp moves the view down by the given number of lines. Returns the new -// lines to show. -func (m *Model) ScrollUp(n int) (lines []string) { if m.AtTop() || n == 0 || len(m.lines) == 0 { return nil } @@ -300,32 +258,6 @@ func (m *Model) ScrollUp(n int) (lines []string) { return m.lines[top:bottom] } -// SetHorizontalStep sets the default amount of columns to scroll left or right -// with the default viewport key map. -// If set to 0 or less, horizontal scrolling is disabled. -func (m *Model) SetHorizontalStep(n int) { - if n < 0 { - n = 0 - } - - m.horizontalStep = n -} - -// SetXOffset sets the X offset. -func (m *Model) SetXOffset(n int) { - m.xOffset = clamp(n, 0, m.longestLineWidth-m.Width) -} - -// ScrollLeft moves the viewport to the left by the given number of columns. -func (m *Model) ScrollLeft(n int) { - m.SetXOffset(m.xOffset - n) -} - -// ScrollRight moves viewport to the right by the given number of columns. -func (m *Model) ScrollRight(n int) { - m.SetXOffset(m.xOffset + n) -} - // TotalLineCount returns the total number of lines (both hidden and visible) within the viewport. func (m Model) TotalLineCount() int { return len(m.lines) @@ -373,8 +305,6 @@ func Sync(m Model) tea.Cmd { // // lines := model.ViewDown(1) // cmd := ViewDown(m, lines) -// -// Deprecated: high performance rendering is deprecated in Bubble Tea. func ViewDown(m Model, lines []string) tea.Cmd { if len(lines) == 0 { return nil @@ -389,8 +319,6 @@ func ViewDown(m Model, lines []string) tea.Cmd { // ViewUp is a high performance command the moves the viewport down by a given // number of lines height. Use Model.ViewUp to get the lines that should be // rendered. -// -// Deprecated: high performance rendering is deprecated in Bubble Tea. func ViewUp(m Model, lines []string) tea.Cmd { if len(lines) == 0 { return nil @@ -402,6 +330,39 @@ func ViewUp(m Model, lines []string) tea.Cmd { return tea.ScrollUp(lines, top, bottom) //nolint:staticcheck } +// 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 + } + + m.horizontalStep = n +} + +// 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 viewport to the right by the given number of columns. +func (m *Model) MoveRight(cols int) { + // prevents over scrolling to the right + if m.xOffset >= m.longestLineWidth-m.Width { + return + } + m.xOffset += cols +} + +// Resets lines indent to zero. +func (m *Model) ResetIndent() { + m.xOffset = 0 +} + // Update handles standard message-based viewport updates. func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { var cmd tea.Cmd @@ -422,46 +383,46 @@ func (m Model) updateAsModel(msg tea.Msg) (Model, tea.Cmd) { case tea.KeyMsg: switch { case key.Matches(msg, m.KeyMap.PageDown): - lines := m.PageDown() + lines := m.ViewDown() if m.HighPerformanceRendering { cmd = ViewDown(m, lines) } case key.Matches(msg, m.KeyMap.PageUp): - lines := m.PageUp() + lines := m.ViewUp() if m.HighPerformanceRendering { cmd = ViewUp(m, lines) } case key.Matches(msg, m.KeyMap.HalfPageDown): - lines := m.HalfPageDown() + lines := m.HalfViewDown() if m.HighPerformanceRendering { cmd = ViewDown(m, lines) } case key.Matches(msg, m.KeyMap.HalfPageUp): - lines := m.HalfPageUp() + lines := m.HalfViewUp() if m.HighPerformanceRendering { cmd = ViewUp(m, lines) } case key.Matches(msg, m.KeyMap.Down): - lines := m.ScrollDown(1) + lines := m.LineDown(1) if m.HighPerformanceRendering { cmd = ViewDown(m, lines) } case key.Matches(msg, m.KeyMap.Up): - lines := m.ScrollUp(1) + lines := m.LineUp(1) if m.HighPerformanceRendering { cmd = ViewUp(m, lines) } case key.Matches(msg, m.KeyMap.Left): - m.ScrollLeft(m.horizontalStep) + m.MoveLeft(m.horizontalStep) case key.Matches(msg, m.KeyMap.Right): - m.ScrollRight(m.horizontalStep) + m.MoveRight(m.horizontalStep) } case tea.MouseMsg: @@ -470,13 +431,13 @@ func (m Model) updateAsModel(msg tea.Msg) (Model, tea.Cmd) { } switch msg.Button { //nolint:exhaustive case tea.MouseButtonWheelUp: - lines := m.ScrollUp(m.MouseWheelDelta) + lines := m.LineUp(m.MouseWheelDelta) if m.HighPerformanceRendering { cmd = ViewUp(m, lines) } case tea.MouseButtonWheelDown: - lines := m.ScrollDown(m.MouseWheelDelta) + lines := m.LineDown(m.MouseWheelDelta) if m.HighPerformanceRendering { cmd = ViewDown(m, lines) } diff --git a/viewport/viewport_test.go b/viewport/viewport_test.go index b29da88..ef70a54 100644 --- a/viewport/viewport_test.go +++ b/viewport/viewport_test.go @@ -95,7 +95,7 @@ func TestMoveLeft(t *testing.T) { t.Errorf("default indent should be %d, got %d", zeroPosition, m.xOffset) } - m.ScrollLeft(m.horizontalStep) + m.MoveLeft(m.horizontalStep) if m.xOffset != zeroPosition { t.Errorf("indent should be %d, got %d", zeroPosition, m.xOffset) } @@ -109,7 +109,7 @@ func TestMoveLeft(t *testing.T) { } m.xOffset = defaultHorizontalStep * 2 - m.ScrollLeft(m.horizontalStep) + m.MoveLeft(m.horizontalStep) newIndent := defaultHorizontalStep if m.xOffset != newIndent { t.Errorf("indent should be %d, got %d", newIndent, m.xOffset) @@ -131,7 +131,7 @@ func TestMoveRight(t *testing.T) { t.Errorf("default indent should be %d, got %d", zeroPosition, m.xOffset) } - m.ScrollRight(m.horizontalStep) + m.MoveRight(m.horizontalStep) newIndent := defaultHorizontalStep if m.xOffset != newIndent { t.Errorf("indent should be %d, got %d", newIndent, m.xOffset) @@ -150,7 +150,7 @@ func TestResetIndent(t *testing.T) { m := New(10, 10) m.xOffset = 500 - m.SetXOffset(0) + m.ResetIndent() if m.xOffset != zeroPosition { t.Errorf("indent should be %d, got %d", zeroPosition, m.xOffset) } @@ -274,7 +274,7 @@ func TestVisibleLines(t *testing.T) { } // move right - m.ScrollRight(m.horizontalStep) + m.MoveRight(m.horizontalStep) list = m.visibleLines() newPrefix := perceptPrefix[m.xOffset:] @@ -287,7 +287,7 @@ func TestVisibleLines(t *testing.T) { } // move left - m.ScrollLeft(m.horizontalStep) + m.MoveLeft(m.horizontalStep) list = m.visibleLines() if !strings.HasPrefix(list[0], perceptPrefix) { t.Errorf("first list item has to have prefix %s", perceptPrefix) @@ -329,7 +329,7 @@ func TestVisibleLines(t *testing.T) { } // move right - m.ScrollRight(horizontalStep) + m.MoveRight(horizontalStep) list = m.visibleLines() for i := range list { @@ -340,7 +340,7 @@ func TestVisibleLines(t *testing.T) { } // move left - m.ScrollLeft(horizontalStep) + m.MoveLeft(horizontalStep) list = m.visibleLines() for i := range list { if list[i] != initList[i] { @@ -350,7 +350,7 @@ func TestVisibleLines(t *testing.T) { // move left second times do not change lites if indent == 0 m.xOffset = 0 - m.ScrollLeft(horizontalStep) + m.MoveLeft(horizontalStep) list = m.visibleLines() for i := range list { if list[i] != initList[i] { @@ -370,7 +370,7 @@ func TestRightOverscroll(t *testing.T) { m.SetContent(content) for i := 0; i < 10; i++ { - m.ScrollRight(m.horizontalStep) + m.MoveRight(m.horizontalStep) } visibleLines := m.visibleLines() From 39668ec6291e1fc3e574f30c650e1ba0801e1f6d Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Thu, 27 Mar 2025 16:11:34 -0300 Subject: [PATCH 3/5] fix(viewport): normalize method names (#763) * Reapply "fix(viewport): normalize method names" This reverts commit f2434c374bd0f55ac0d5b3e8261161b97ea41a30. * test: fixes * fix: lint * fix: disable horizontal scroll on v1 it might be a breaking change. let's enable it by default on v2, and remove the deprecated methods as well. cc/ @meowgorithm * fix: simplify --- textarea/textarea.go | 4 +- textarea/textarea_test.go | 2 +- viewport/viewport.go | 149 +++++++++++++++++++++++--------------- viewport/viewport_test.go | 55 ++++++++------ 4 files changed, 128 insertions(+), 82 deletions(-) diff --git a/textarea/textarea.go b/textarea/textarea.go index 88bc979..9af8402 100644 --- a/textarea/textarea.go +++ b/textarea/textarea.go @@ -857,9 +857,9 @@ func (m *Model) repositionView() { maximum := minimum + m.viewport.Height - 1 if row := m.cursorLineNumber(); row < minimum { - m.viewport.LineUp(minimum - row) + m.viewport.ScrollUp(minimum - row) } else if row > maximum { - m.viewport.LineDown(row - maximum) + m.viewport.ScrollDown(row - maximum) } } diff --git a/textarea/textarea_test.go b/textarea/textarea_test.go index 41d626c..9b272c5 100644 --- a/textarea/textarea_test.go +++ b/textarea/textarea_test.go @@ -43,7 +43,7 @@ func TestVerticalScrolling(t *testing.T) { "the text area.", } for _, line := range lines { - textarea.viewport.LineDown(1) + textarea.viewport.ScrollDown(1) view = textarea.View() if !strings.Contains(view, line) { t.Log(view) diff --git a/viewport/viewport.go b/viewport/viewport.go index 0a818c0..4dc1c68 100644 --- a/viewport/viewport.go +++ b/viewport/viewport.go @@ -10,10 +10,6 @@ import ( "github.com/charmbracelet/x/ansi" ) -const ( - defaultHorizontalStep = 6 -) - // New returns a new model with the given width and height as well as default // key mappings. func New(width, height int) (m Model) { @@ -76,7 +72,6 @@ func (m *Model) setInitialValues() { m.MouseWheelEnabled = true m.MouseWheelDelta = 3 m.initialized = true - m.horizontalStep = defaultHorizontalStep } // Init exists to satisfy the tea.Model interface for composability purposes. @@ -168,7 +163,7 @@ func (m Model) visibleLines() (lines []string) { // scrollArea returns the scrollable boundaries for high performance rendering. // -// XXX: high performance rendering is deprecated in Bubble Tea. +// Deprecated: high performance rendering is deprecated in Bubble Tea. func (m Model) scrollArea() (top, bottom int) { top = max(0, m.YPosition) bottom = max(top, top+m.Height) @@ -183,45 +178,81 @@ func (m *Model) SetYOffset(n int) { m.YOffset = clamp(n, 0, m.maxYOffset()) } -// ViewDown moves the view down by the number of lines in the viewport. +// PageDown moves the view down by the number of lines in the viewport. // Basically, "page down". +// +// Deprecated: use [Model.PageDown] instead. func (m *Model) ViewDown() []string { + return m.PageDown() +} + +// PageDown moves the view down by the number of lines in the viewport. +func (m *Model) PageDown() []string { if m.AtBottom() { return nil } - return m.LineDown(m.Height) + return m.ScrollDown(m.Height) } -// ViewUp moves the view up by one height of the viewport. Basically, "page up". +// ViewUp moves the view up by one height of the viewport. +// Basically, "page up". +// +// Deprecated: use [Model.PageUp] instead. func (m *Model) ViewUp() []string { + return m.PageUp() +} + +// PageUp moves the view up by one height of the viewport. +func (m *Model) PageUp() []string { if m.AtTop() { return nil } - return m.LineUp(m.Height) + return m.ScrollUp(m.Height) } // HalfViewDown moves the view down by half the height of the viewport. +// +// Deprecated: use [Model.HalfPageDown] instead. func (m *Model) HalfViewDown() (lines []string) { + return m.HalfPageDown() +} + +// HalfPageDown moves the view down by half the height of the viewport. +func (m *Model) HalfPageDown() (lines []string) { if m.AtBottom() { return nil } - return m.LineDown(m.Height / 2) //nolint:mnd + return m.ScrollDown(m.Height / 2) //nolint:mnd } // HalfViewUp moves the view up by half the height of the viewport. +// +// Deprecated: use [Model.HalfPageUp] instead. func (m *Model) HalfViewUp() (lines []string) { + return m.HalfPageUp() +} + +// HalfPageUp moves the view up by half the height of the viewport. +func (m *Model) HalfPageUp() (lines []string) { if m.AtTop() { return nil } - return m.LineUp(m.Height / 2) //nolint:mnd + return m.ScrollUp(m.Height / 2) //nolint:mnd } // LineDown moves the view down by the given number of lines. +// +// Deprecated: use [Model.ScrollDown] instead. func (m *Model) LineDown(n int) (lines []string) { + return m.ScrollDown(n) +} + +// ScrollDown moves the view down by the given number of lines. +func (m *Model) ScrollDown(n int) (lines []string) { if m.AtBottom() || n == 0 || len(m.lines) == 0 { return nil } @@ -241,7 +272,15 @@ func (m *Model) LineDown(n int) (lines []string) { // LineUp moves the view down by the given number of lines. Returns the new // lines to show. +// +// Deprecated: use [Model.ScrollUp] instead. func (m *Model) LineUp(n int) (lines []string) { + return m.ScrollUp(n) +} + +// ScrollUp moves the view down by the given number of lines. Returns the new +// lines to show. +func (m *Model) ScrollUp(n int) (lines []string) { if m.AtTop() || n == 0 || len(m.lines) == 0 { return nil } @@ -258,6 +297,31 @@ func (m *Model) LineUp(n int) (lines []string) { return m.lines[top:bottom] } +// SetHorizontalStep sets the default amount of columns to scroll left or right +// with the default viewport key map. +// +// If set to 0 or less, horizontal scrolling is disabled. +// +// On v1, horizontal scrolling is disabled by default. +func (m *Model) SetHorizontalStep(n int) { + m.horizontalStep = max(n, 0) +} + +// SetXOffset sets the X offset. +func (m *Model) SetXOffset(n int) { + m.xOffset = clamp(n, 0, m.longestLineWidth-m.Width) +} + +// ScrollLeft moves the viewport to the left by the given number of columns. +func (m *Model) ScrollLeft(n int) { + m.SetXOffset(m.xOffset - n) +} + +// ScrollRight moves viewport to the right by the given number of columns. +func (m *Model) ScrollRight(n int) { + m.SetXOffset(m.xOffset + n) +} + // TotalLineCount returns the total number of lines (both hidden and visible) within the viewport. func (m Model) TotalLineCount() int { return len(m.lines) @@ -305,6 +369,8 @@ func Sync(m Model) tea.Cmd { // // lines := model.ViewDown(1) // cmd := ViewDown(m, lines) +// +// Deprecated: high performance rendering is deprecated in Bubble Tea. func ViewDown(m Model, lines []string) tea.Cmd { if len(lines) == 0 { return nil @@ -313,12 +379,14 @@ func ViewDown(m Model, lines []string) tea.Cmd { // XXX: high performance rendering is deprecated in Bubble Tea. In a v2 we // won't need to return a command here. - return tea.ScrollDown(lines, top, bottom) //nolint:staticcheck + return tea.ScrollDown(lines, top, bottom) } // ViewUp is a high performance command the moves the viewport down by a given // number of lines height. Use Model.ViewUp to get the lines that should be // rendered. +// +// Deprecated: high performance rendering is deprecated in Bubble Tea. func ViewUp(m Model, lines []string) tea.Cmd { if len(lines) == 0 { return nil @@ -327,40 +395,7 @@ func ViewUp(m Model, lines []string) tea.Cmd { // XXX: high performance rendering is deprecated in Bubble Tea. In a v2 we // won't need to return a command here. - return tea.ScrollUp(lines, top, bottom) //nolint:staticcheck -} - -// 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 - } - - m.horizontalStep = n -} - -// 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 viewport to the right by the given number of columns. -func (m *Model) MoveRight(cols int) { - // prevents over scrolling to the right - if m.xOffset >= m.longestLineWidth-m.Width { - return - } - m.xOffset += cols -} - -// Resets lines indent to zero. -func (m *Model) ResetIndent() { - m.xOffset = 0 + return tea.ScrollUp(lines, top, bottom) } // Update handles standard message-based viewport updates. @@ -383,46 +418,46 @@ func (m Model) updateAsModel(msg tea.Msg) (Model, tea.Cmd) { case tea.KeyMsg: switch { case key.Matches(msg, m.KeyMap.PageDown): - lines := m.ViewDown() + lines := m.PageDown() if m.HighPerformanceRendering { cmd = ViewDown(m, lines) } case key.Matches(msg, m.KeyMap.PageUp): - lines := m.ViewUp() + lines := m.PageUp() if m.HighPerformanceRendering { cmd = ViewUp(m, lines) } case key.Matches(msg, m.KeyMap.HalfPageDown): - lines := m.HalfViewDown() + lines := m.HalfPageDown() if m.HighPerformanceRendering { cmd = ViewDown(m, lines) } case key.Matches(msg, m.KeyMap.HalfPageUp): - lines := m.HalfViewUp() + lines := m.HalfPageUp() if m.HighPerformanceRendering { cmd = ViewUp(m, lines) } case key.Matches(msg, m.KeyMap.Down): - lines := m.LineDown(1) + lines := m.ScrollDown(1) if m.HighPerformanceRendering { cmd = ViewDown(m, lines) } case key.Matches(msg, m.KeyMap.Up): - lines := m.LineUp(1) + lines := m.ScrollUp(1) if m.HighPerformanceRendering { cmd = ViewUp(m, lines) } case key.Matches(msg, m.KeyMap.Left): - m.MoveLeft(m.horizontalStep) + m.ScrollLeft(m.horizontalStep) case key.Matches(msg, m.KeyMap.Right): - m.MoveRight(m.horizontalStep) + m.ScrollRight(m.horizontalStep) } case tea.MouseMsg: @@ -431,13 +466,13 @@ func (m Model) updateAsModel(msg tea.Msg) (Model, tea.Cmd) { } switch msg.Button { //nolint:exhaustive case tea.MouseButtonWheelUp: - lines := m.LineUp(m.MouseWheelDelta) + lines := m.ScrollUp(m.MouseWheelDelta) if m.HighPerformanceRendering { cmd = ViewUp(m, lines) } case tea.MouseButtonWheelDown: - lines := m.LineDown(m.MouseWheelDelta) + lines := m.ScrollDown(m.MouseWheelDelta) if m.HighPerformanceRendering { cmd = ViewDown(m, lines) } diff --git a/viewport/viewport_test.go b/viewport/viewport_test.go index ef70a54..6f8cb37 100644 --- a/viewport/viewport_test.go +++ b/viewport/viewport_test.go @@ -5,6 +5,8 @@ import ( "testing" ) +const defaultHorizontalStep = 6 + func TestNew(t *testing.T) { t.Parallel() @@ -12,6 +14,7 @@ func TestNew(t *testing.T) { t.Parallel() m := New(10, 10) + m.horizontalStep = defaultHorizontalStep // remove on v2 if !m.initialized { t.Errorf("on create by New, Model should be initialized") @@ -38,6 +41,7 @@ func TestSetInitialValues(t *testing.T) { t.Parallel() m := Model{} + m.horizontalStep = defaultHorizontalStep // remove on v2 m.setInitialValues() if m.horizontalStep != defaultHorizontalStep { @@ -53,6 +57,7 @@ func TestSetHorizontalStep(t *testing.T) { t.Parallel() m := New(10, 10) + m.horizontalStep = defaultHorizontalStep // remove on v2 if m.horizontalStep != defaultHorizontalStep { t.Errorf("default horizontalStep should be %d, got %d", defaultHorizontalStep, m.horizontalStep) @@ -69,6 +74,7 @@ func TestSetHorizontalStep(t *testing.T) { t.Parallel() m := New(10, 10) + m.horizontalStep = defaultHorizontalStep // remove on v2 if m.horizontalStep != defaultHorizontalStep { t.Errorf("default horizontalStep should be %d, got %d", defaultHorizontalStep, m.horizontalStep) @@ -82,7 +88,7 @@ func TestSetHorizontalStep(t *testing.T) { }) } -func TestMoveLeft(t *testing.T) { +func TestScrollLeft(t *testing.T) { t.Parallel() zeroPosition := 0 @@ -91,25 +97,28 @@ func TestMoveLeft(t *testing.T) { t.Parallel() m := New(10, 10) + m.longestLineWidth = 100 if m.xOffset != zeroPosition { t.Errorf("default indent should be %d, got %d", zeroPosition, m.xOffset) } - m.MoveLeft(m.horizontalStep) + m.ScrollLeft(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.Run("scroll", func(t *testing.T) { t.Parallel() m := New(10, 10) + m.horizontalStep = defaultHorizontalStep // remove on v2 + m.longestLineWidth = 100 if m.xOffset != zeroPosition { t.Errorf("default indent should be %d, got %d", zeroPosition, m.xOffset) } m.xOffset = defaultHorizontalStep * 2 - m.MoveLeft(m.horizontalStep) + m.ScrollLeft(m.horizontalStep) newIndent := defaultHorizontalStep if m.xOffset != newIndent { t.Errorf("indent should be %d, got %d", newIndent, m.xOffset) @@ -117,21 +126,22 @@ func TestMoveLeft(t *testing.T) { }) } -func TestMoveRight(t *testing.T) { +func TestScrollRight(t *testing.T) { t.Parallel() - t.Run("move", func(t *testing.T) { + t.Run("scroll", func(t *testing.T) { t.Parallel() zeroPosition := 0 m := New(10, 10) + m.SetHorizontalStep(defaultHorizontalStep) m.SetContent("Some line that is longer than width") if m.xOffset != zeroPosition { t.Errorf("default indent should be %d, got %d", zeroPosition, m.xOffset) } - m.MoveRight(m.horizontalStep) + m.ScrollRight(m.horizontalStep) newIndent := defaultHorizontalStep if m.xOffset != newIndent { t.Errorf("indent should be %d, got %d", newIndent, m.xOffset) @@ -150,7 +160,7 @@ func TestResetIndent(t *testing.T) { m := New(10, 10) m.xOffset = 500 - m.ResetIndent() + m.SetXOffset(0) if m.xOffset != zeroPosition { t.Errorf("indent should be %d, got %d", zeroPosition, m.xOffset) } @@ -253,8 +263,9 @@ func TestVisibleLines(t *testing.T) { numberOfLines := 10 m := New(10, numberOfLines) - m.lines = defaultList - m.YOffset = 7 + m.horizontalStep = defaultHorizontalStep // remove on v2 + m.SetContent(strings.Join(defaultList, "\n")) + m.SetYOffset(7) // default list list := m.visibleLines() @@ -273,8 +284,8 @@ func TestVisibleLines(t *testing.T) { t.Errorf("first list item has to have prefix %s", perceptPrefix) } - // move right - m.MoveRight(m.horizontalStep) + // scroll right + m.ScrollRight(m.horizontalStep) list = m.visibleLines() newPrefix := perceptPrefix[m.xOffset:] @@ -282,12 +293,12 @@ func TestVisibleLines(t *testing.T) { t.Errorf("first list item has to have prefix %s, get %s", newPrefix, list[0]) } - if list[lastItem] != "..." { + if list[lastItem] != "" { t.Errorf("last item should be empty, got %s", list[lastItem]) } - // move left - m.MoveLeft(m.horizontalStep) + // scroll left + m.ScrollLeft(m.horizontalStep) list = m.visibleLines() if !strings.HasPrefix(list[0], perceptPrefix) { t.Errorf("first list item has to have prefix %s", perceptPrefix) @@ -328,8 +339,8 @@ func TestVisibleLines(t *testing.T) { t.Errorf("%dth list item should the the same as %dth default list item", lastItemIdx, initLastItem) } - // move right - m.MoveRight(horizontalStep) + // scroll right + m.ScrollRight(horizontalStep) list = m.visibleLines() for i := range list { @@ -339,8 +350,8 @@ func TestVisibleLines(t *testing.T) { } } - // move left - m.MoveLeft(horizontalStep) + // scroll left + m.ScrollLeft(horizontalStep) list = m.visibleLines() for i := range list { if list[i] != initList[i] { @@ -348,9 +359,9 @@ func TestVisibleLines(t *testing.T) { } } - // move left second times do not change lites if indent == 0 + // scroll left second times do not change lites if indent == 0 m.xOffset = 0 - m.MoveLeft(horizontalStep) + m.ScrollLeft(horizontalStep) list = m.visibleLines() for i := range list { if list[i] != initList[i] { @@ -370,7 +381,7 @@ func TestRightOverscroll(t *testing.T) { m.SetContent(content) for i := 0; i < 10; i++ { - m.MoveRight(m.horizontalStep) + m.ScrollRight(m.horizontalStep) } visibleLines := m.visibleLines() From b1cef26c7deb337c8fe0cbbe87c8d958e99a09e4 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Thu, 27 Mar 2025 16:34:10 -0300 Subject: [PATCH 4/5] fix: normalize yoffset --- table/table.go | 27 ++++--- textarea/textarea.go | 8 +- viewport/viewport.go | 156 ++++++++++++++++++-------------------- viewport/viewport_test.go | 27 +++---- 4 files changed, 106 insertions(+), 112 deletions(-) diff --git a/table/table.go b/table/table.go index f1b49b2..edb6f22 100644 --- a/table/table.go +++ b/table/table.go @@ -350,14 +350,17 @@ func (m *Model) SetCursor(n int) { // It can not go above the first row. func (m *Model) MoveUp(n int) { m.cursor = clamp(m.cursor-n, 0, len(m.rows)-1) + + offset := m.viewport.YOffset() switch { case m.start == 0: - m.viewport.SetYOffset(clamp(m.viewport.YOffset, 0, m.cursor)) + offset = clamp(offset, 0, m.cursor) case m.start < m.viewport.Height(): - m.viewport.YOffset = (clamp(clamp(m.viewport.YOffset+n, 0, m.cursor), 0, m.viewport.Height())) - case m.viewport.YOffset >= 1: - m.viewport.YOffset = clamp(m.viewport.YOffset+n, 1, m.viewport.Height()) + offset = clamp(clamp(offset+n, 0, m.cursor), 0, m.viewport.Height()) + case offset >= 1: + offset = clamp(offset+n, 1, m.viewport.Height()) } + m.viewport.SetYOffset(offset) m.UpdateViewport() } @@ -367,15 +370,17 @@ func (m *Model) MoveDown(n int) { m.cursor = clamp(m.cursor+n, 0, len(m.rows)-1) m.UpdateViewport() + offset := m.viewport.YOffset() switch { - case m.end == len(m.rows) && m.viewport.YOffset > 0: - m.viewport.SetYOffset(clamp(m.viewport.YOffset-n, 1, m.viewport.Height())) - case m.cursor > (m.end-m.start)/2 && m.viewport.YOffset > 0: - m.viewport.SetYOffset(clamp(m.viewport.YOffset-n, 1, m.cursor)) - case m.viewport.YOffset > 1: - case m.cursor > m.viewport.YOffset+m.viewport.Height()-1: - m.viewport.SetYOffset(clamp(m.viewport.YOffset+1, 0, 1)) + case m.end == len(m.rows) && offset > 0: + offset = clamp(offset-n, 1, m.viewport.Height()) + case m.cursor > (m.end-m.start)/2 && offset > 0: + offset = clamp(offset-n, 1, m.cursor) + case offset > 1: + case m.cursor > offset+m.viewport.Height()-1: + offset = clamp(offset+1, 0, 1) } + m.viewport.SetYOffset(offset) } // GotoTop moves the selection to the first row. diff --git a/textarea/textarea.go b/textarea/textarea.go index f9d212f..4090ba9 100644 --- a/textarea/textarea.go +++ b/textarea/textarea.go @@ -943,7 +943,7 @@ func (m Model) LineInfo() LineInfo { // repositionView repositions the view of the viewport based on the defined // scrolling behavior. func (m *Model) repositionView() { - minimum := m.viewport.YOffset + minimum := m.viewport.YOffset() maximum := minimum + m.viewport.Height() - 1 if row := m.cursorLineNumber(); row < minimum { m.viewport.ScrollUp(minimum - row) @@ -1268,7 +1268,7 @@ func (m Model) View() string { // Always show at least `m.Height` lines at all times. // To do this we can simply pad out a few extra new lines in the view. - for i := 0; i < m.height; i++ { + for range m.height { s.WriteString(m.promptView(displayLine)) displayLine++ @@ -1446,7 +1446,7 @@ func (m Model) Cursor() *tea.Cursor { baseStyle.GetBorderLeftSize() yOffset := m.cursorLineNumber() - - m.viewport.YOffset + + m.viewport.YOffset() + baseStyle.GetMarginTop() + baseStyle.GetPaddingTop() + baseStyle.GetBorderTopSize() @@ -1472,7 +1472,7 @@ func (m Model) memoizedWrap(runes []rune, width int) [][]rune { // This accounts for soft wrapped lines. func (m Model) cursorLineNumber() int { line := 0 - for i := 0; i < m.row; i++ { + for i := range m.row { // Calculate the number of lines that the current line will be split // into. line += len(m.memoizedWrap(m.value[i], m.width)) diff --git a/viewport/viewport.go b/viewport/viewport.go index 30ee512..4478cde 100644 --- a/viewport/viewport.go +++ b/viewport/viewport.go @@ -66,8 +66,8 @@ type Model struct { // The number of lines the mouse wheel will scroll. By default, this is 3. MouseWheelDelta int - // YOffset is the vertical scroll position. - YOffset int + // yOffset is the vertical scroll position. + yOffset int // xOffset is the horizontal scroll position. xOffset int @@ -172,19 +172,19 @@ func (m *Model) SetWidth(w int) { // AtTop returns whether or not the viewport is at the very top position. func (m Model) AtTop() bool { - return m.YOffset <= 0 + return m.YOffset() <= 0 } // AtBottom returns whether or not the viewport is at or past the very bottom // position. func (m Model) AtBottom() bool { - return m.YOffset >= m.maxYOffset() + return m.YOffset() >= m.maxYOffset() } // PastBottom returns whether or not the viewport is scrolled beyond the last // line. This can happen when adjusting the viewport height. func (m Model) PastBottom() bool { - return m.YOffset > m.maxYOffset() + return m.YOffset() > m.maxYOffset() } // ScrollPercent returns the amount scrolled as a float between 0 and 1. @@ -193,7 +193,7 @@ func (m Model) ScrollPercent() float64 { if m.Height() >= count { return 1.0 } - y := float64(m.YOffset) + y := float64(m.YOffset()) h := float64(m.Height()) t := float64(count) v := y / (t - h) @@ -233,7 +233,7 @@ func (m *Model) SetContentLines(lines []string) { m.longestLineWidth = maxLineWidth(m.lines) m.ClearHighlights() - if m.YOffset > m.maxYOffset() { + if m.YOffset() > m.maxYOffset() { m.GotoBottom() } } @@ -326,7 +326,7 @@ func (m Model) visibleLines() (lines []string) { maxWidth := m.maxWidth() if m.lineCount() > 0 { - pos := m.lineToIndex(m.YOffset) + pos := m.lineToIndex(m.YOffset()) top := max(0, pos) bottom := clamp(pos+maxHeight, top, len(m.lines)) lines = make([]string, bottom-top) @@ -406,7 +406,7 @@ func (m Model) softWrap(lines []string, maxWidth int) []string { truncatedLine := ansi.Cut(line, idx, maxWidth+idx) if m.LeftGutterFunc != nil { truncatedLine = m.LeftGutterFunc(GutterContext{ - Index: i + m.YOffset, + Index: i + m.YOffset(), TotalLines: total, Soft: idx > 0, }) + truncatedLine @@ -424,7 +424,7 @@ func (m Model) setupGutter(lines []string) []string { return lines } - offset := max(0, m.lineToIndex(m.YOffset)) + offset := max(0, m.lineToIndex(m.YOffset())) total := m.TotalLineCount() result := make([]string, len(lines)) for i := range lines { @@ -443,17 +443,11 @@ func (m Model) setupGutter(lines []string) []string { // SetYOffset sets the Y offset. func (m *Model) SetYOffset(n int) { - m.YOffset = clamp(n, 0, m.maxYOffset()) + m.yOffset = clamp(n, 0, m.maxYOffset()) } -// SetXOffset sets the X offset. -// No-op when soft wrap is enabled. -func (m *Model) SetXOffset(n int) { - if m.SoftWrap { - return - } - m.xOffset = clamp(n, 0, m.maxXOffset()) -} +// YOffset returns the current Y offset - the vertical scroll position. +func (m *Model) YOffset() int { return m.yOffset } // EnsureVisible ensures that the given line and column are in the viewport. func (m *Model) EnsureVisible(line, colstart, colend int) { @@ -464,52 +458,51 @@ func (m *Model) EnsureVisible(line, colstart, colend int) { m.SetXOffset(colstart - m.horizontalStep) // put one step to the left, feels more natural } - if line < m.YOffset || line >= m.YOffset+m.maxHeight() { + if line < m.YOffset() || line >= m.YOffset()+m.maxHeight() { m.SetYOffset(line) } m.visibleLines() } -// ViewDown moves the view down by the number of lines in the viewport. -// Basically, "page down". -func (m *Model) ViewDown() { +// PageDown moves the view down by the number of lines in the viewport. +func (m *Model) PageDown() { if m.AtBottom() { return } - m.LineDown(m.Height()) + m.ScrollDown(m.Height()) } -// ViewUp moves the view up by one height of the viewport. Basically, "page up". -func (m *Model) ViewUp() { +// PageUp moves the view up by one height of the viewport. +func (m *Model) PageUp() { if m.AtTop() { return } - m.LineUp(m.Height()) + m.ScrollUp(m.Height()) } -// HalfViewDown moves the view down by half the height of the viewport. -func (m *Model) HalfViewDown() { +// HalfPageDown moves the view down by half the height of the viewport. +func (m *Model) HalfPageDown() { if m.AtBottom() { return } - m.LineDown(m.Height() / 2) //nolint:mnd + m.ScrollDown(m.Height() / 2) //nolint:mnd } -// HalfViewUp moves the view up by half the height of the viewport. -func (m *Model) HalfViewUp() { +// HalfPageUp moves the view up by half the height of the viewport. +func (m *Model) HalfPageUp() { if m.AtTop() { return } - m.LineUp(m.Height() / 2) //nolint:mnd + m.ScrollUp(m.Height() / 2) //nolint:mnd } -// LineDown moves the view down by the given number of lines. -func (m *Model) LineDown(n int) { +// ScrollDown moves the view down by the given number of lines. +func (m *Model) ScrollDown(n int) { if m.AtBottom() || n == 0 || len(m.lines) == 0 { return } @@ -517,23 +510,52 @@ func (m *Model) LineDown(n int) { // Make sure the number of lines by which we're going to scroll isn't // greater than the number of lines we actually have left before we reach // the bottom. - m.SetYOffset(m.YOffset + n) + m.SetYOffset(m.YOffset() + n) m.hiIdx = m.findNearedtMatch() } -// LineUp moves the view down by the given number of lines. Returns the new +// ScrollUp moves the view down by the given number of lines. Returns the new // lines to show. -func (m *Model) LineUp(n int) { +func (m *Model) ScrollUp(n int) { if m.AtTop() || n == 0 || len(m.lines) == 0 { return } // Make sure the number of lines by which we're going to scroll isn't // greater than the number of lines we are from the top. - m.SetYOffset(m.YOffset - n) + m.SetYOffset(m.YOffset() - n) m.hiIdx = m.findNearedtMatch() } +// 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) { + m.horizontalStep = max(0, n) +} + +// XOffset returns the current X offset - the horizontal scroll position. +func (m *Model) XOffset() int { return m.xOffset } + +// SetXOffset sets the X offset. +// No-op when soft wrap is enabled. +func (m *Model) SetXOffset(n int) { + if m.SoftWrap { + return + } + m.xOffset = clamp(n, 0, m.maxXOffset()) +} + +// ScrollLeft moves the viewport to the left by the given number of columns. +func (m *Model) ScrollLeft(n int) { + m.SetXOffset(m.xOffset - n) +} + +// ScrollRight moves viewport to the right by the given number of columns. +func (m *Model) ScrollRight(n int) { + m.SetXOffset(m.xOffset + n) +} + // TotalLineCount returns the total number of lines (both hidden and visible) within the viewport. func (m Model) TotalLineCount() int { return m.lineCount() @@ -562,40 +584,6 @@ func (m *Model) GotoBottom() (lines []string) { return m.visibleLines() } -// 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 - } - - m.horizontalStep = n -} - -// 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 viewport to the right by the given number of columns. -func (m *Model) MoveRight(cols int) { - // prevents over scrolling to the right - w := m.maxWidth() - if m.xOffset > m.longestLineWidth-w { - return - } - m.xOffset += cols -} - -// Resets lines indent to zero. -func (m *Model) ResetIndent() { - m.xOffset = 0 -} - // SetHighlights sets ranges of characters to highlight. // For instance, `[]int{[]int{2, 10}, []int{20, 30}}` will highlight characters // 2 to 10 and 20 to 30. @@ -649,7 +637,7 @@ func (m *Model) HighlightPrevious() { func (m Model) findNearedtMatch() int { for i, match := range m.highlights { - if match.lineStart >= m.YOffset { + if match.lineStart >= m.YOffset() { return i } } @@ -673,28 +661,28 @@ func (m Model) updateAsModel(msg tea.Msg) Model { case tea.KeyPressMsg: switch { case key.Matches(msg, m.KeyMap.PageDown): - m.ViewDown() + m.PageDown() case key.Matches(msg, m.KeyMap.PageUp): - m.ViewUp() + m.PageUp() case key.Matches(msg, m.KeyMap.HalfPageDown): - m.HalfViewDown() + m.HalfPageDown() case key.Matches(msg, m.KeyMap.HalfPageUp): - m.HalfViewUp() + m.HalfPageUp() case key.Matches(msg, m.KeyMap.Down): - m.LineDown(1) + m.ScrollDown(1) case key.Matches(msg, m.KeyMap.Up): - m.LineUp(1) + m.ScrollUp(1) case key.Matches(msg, m.KeyMap.Left): - m.MoveLeft(m.horizontalStep) + m.ScrollLeft(m.horizontalStep) case key.Matches(msg, m.KeyMap.Right): - m.MoveRight(m.horizontalStep) + m.ScrollRight(m.horizontalStep) } case tea.MouseWheelMsg: @@ -704,10 +692,10 @@ func (m Model) updateAsModel(msg tea.Msg) Model { switch msg.Button { case tea.MouseWheelDown: - m.LineDown(m.MouseWheelDelta) + m.ScrollDown(m.MouseWheelDelta) case tea.MouseWheelUp: - m.LineUp(m.MouseWheelDelta) + m.ScrollUp(m.MouseWheelDelta) } } diff --git a/viewport/viewport_test.go b/viewport/viewport_test.go index 1e10838..b64e9fd 100644 --- a/viewport/viewport_test.go +++ b/viewport/viewport_test.go @@ -99,7 +99,7 @@ func TestMoveLeft(t *testing.T) { t.Errorf("default indent should be %d, got %d", zeroPosition, m.xOffset) } - m.MoveLeft(m.horizontalStep) + m.ScrollLeft(m.horizontalStep) if m.xOffset != zeroPosition { t.Errorf("indent should be %d, got %d", zeroPosition, m.xOffset) } @@ -108,12 +108,13 @@ func TestMoveLeft(t *testing.T) { t.Run("move", func(t *testing.T) { t.Parallel() m := New(WithHeight(10), WithWidth(10)) + m.longestLineWidth = 100 if m.xOffset != zeroPosition { t.Errorf("default indent should be %d, got %d", zeroPosition, m.xOffset) } m.xOffset = defaultHorizontalStep * 2 - m.MoveLeft(m.horizontalStep) + m.ScrollLeft(m.horizontalStep) newIndent := defaultHorizontalStep if m.xOffset != newIndent { t.Errorf("indent should be %d, got %d", newIndent, m.xOffset) @@ -135,7 +136,7 @@ func TestMoveRight(t *testing.T) { t.Errorf("default indent should be %d, got %d", zeroPosition, m.xOffset) } - m.MoveRight(m.horizontalStep) + m.ScrollRight(m.horizontalStep) newIndent := defaultHorizontalStep if m.xOffset != newIndent { t.Errorf("indent should be %d, got %d", newIndent, m.xOffset) @@ -154,7 +155,7 @@ func TestResetIndent(t *testing.T) { m := New(WithHeight(10), WithWidth(10)) m.xOffset = 500 - m.ResetIndent() + m.SetXOffset(0) if m.xOffset != zeroPosition { t.Errorf("indent should be %d, got %d", zeroPosition, m.xOffset) } @@ -233,7 +234,7 @@ func TestVisibleLines(t *testing.T) { m := New(WithHeight(numberOfLines), WithWidth(10)) m.SetContent(strings.Join(defaultList, "\n")) - m.YOffset = 5 + m.SetYOffset(5) list := m.visibleLines() if len(list) != numberOfLines { @@ -246,7 +247,7 @@ func TestVisibleLines(t *testing.T) { lastItemIdx := numberOfLines - 1 // we trim line if it doesn't fit to width of the viewport - shouldGet := defaultList[m.YOffset+lastItemIdx][:m.Width()] + shouldGet := defaultList[m.YOffset()+lastItemIdx][:m.Width()] if list[lastItemIdx] != shouldGet { t.Errorf(`%dth list item should be '%s', got '%s'`, lastItemIdx, shouldGet, list[lastItemIdx]) } @@ -258,7 +259,7 @@ func TestVisibleLines(t *testing.T) { m := New(WithHeight(numberOfLines), WithWidth(10)) m.lines = defaultList - m.YOffset = 7 + m.SetYOffset(7) // default list list := m.visibleLines() @@ -278,7 +279,7 @@ func TestVisibleLines(t *testing.T) { } // move right - m.MoveRight(m.horizontalStep) + m.ScrollRight(m.horizontalStep) list = m.visibleLines() newPrefix := perceptPrefix[m.xOffset:] @@ -291,7 +292,7 @@ func TestVisibleLines(t *testing.T) { } // move left - m.MoveLeft(m.horizontalStep) + m.ScrollLeft(m.horizontalStep) list = m.visibleLines() if !strings.HasPrefix(list[0], perceptPrefix) { t.Errorf("first list item has to have prefix %s", perceptPrefix) @@ -333,7 +334,7 @@ func TestVisibleLines(t *testing.T) { } // move right - m.MoveRight(horizontalStep) + m.ScrollRight(horizontalStep) list = m.visibleLines() for i := range list { @@ -344,7 +345,7 @@ func TestVisibleLines(t *testing.T) { } // move left - m.MoveLeft(horizontalStep) + m.ScrollLeft(horizontalStep) list = m.visibleLines() for i := range list { if list[i] != initList[i] { @@ -354,7 +355,7 @@ func TestVisibleLines(t *testing.T) { // move left second times do not change lites if indent == 0 m.xOffset = 0 - m.MoveLeft(horizontalStep) + m.ScrollLeft(horizontalStep) list = m.visibleLines() for i := range list { if list[i] != initList[i] { @@ -374,7 +375,7 @@ func TestRightOverscroll(t *testing.T) { m.SetContent(content) for i := 0; i < 10; i++ { - m.MoveRight(m.horizontalStep) + m.ScrollRight(m.horizontalStep) } visibleLines := m.visibleLines() From 6f4a536421f3a487454c7858371c4d1464ebd544 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Thu, 27 Mar 2025 16:51:54 -0300 Subject: [PATCH 5/5] Update viewport/viewport.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- viewport/viewport.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/viewport/viewport.go b/viewport/viewport.go index 4478cde..4e6de76 100644 --- a/viewport/viewport.go +++ b/viewport/viewport.go @@ -514,7 +514,7 @@ func (m *Model) ScrollDown(n int) { m.hiIdx = m.findNearedtMatch() } -// ScrollUp moves the view down by the given number of lines. Returns the new +// ScrollUp moves the view up by the given number of lines. Returns the new // lines to show. func (m *Model) ScrollUp(n int) { if m.AtTop() || n == 0 || len(m.lines) == 0 {