Fix bug where viewport wouldn't render final lines

This previously went unnoticed because we all seemed to have newlines at
the end of our viewport input.

This update introduces the Model.SetYOffset method.
This commit is contained in:
Christian Rocha 2021-09-16 11:04:08 -04:00
parent a7ea1bddbf
commit 8c03905dbe

View File

@ -45,13 +45,13 @@ func (m Model) AtTop() bool {
// AtBottom returns whether or not the viewport is at or past the very bottom // AtBottom returns whether or not the viewport is at or past the very bottom
// position. // position.
func (m Model) AtBottom() bool { func (m Model) AtBottom() bool {
return m.YOffset >= len(m.lines)-1-m.Height return m.YOffset >= len(m.lines)-m.Height
} }
// PastBottom returns whether or not the viewport is scrolled beyond the last // PastBottom returns whether or not the viewport is scrolled beyond the last
// line. This can happen when adjusting the viewport height. // line. This can happen when adjusting the viewport height.
func (m Model) PastBottom() bool { func (m Model) PastBottom() bool {
return m.YOffset > len(m.lines)-1-m.Height return m.YOffset > len(m.lines)-m.Height
} }
// ScrollPercent returns the amount scrolled as a float between 0 and 1. // ScrollPercent returns the amount scrolled as a float between 0 and 1.
@ -87,6 +87,11 @@ func (m Model) visibleLines() (lines []string) {
return lines return lines
} }
// SetYOffset sets the Y offset.
func (m *Model) SetYOffset(n int) {
m.YOffset = clamp(n, 0, len(m.lines)-m.Height)
}
// ViewDown 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". // Basically, "page down".
func (m *Model) ViewDown() []string { func (m *Model) ViewDown() []string {
@ -94,11 +99,7 @@ func (m *Model) ViewDown() []string {
return nil return nil
} }
m.YOffset = min( m.SetYOffset(m.YOffset + m.Height)
m.YOffset+m.Height, // target
len(m.lines)-1-m.Height, // fallback
)
return m.visibleLines() return m.visibleLines()
} }
@ -108,11 +109,7 @@ func (m *Model) ViewUp() []string {
return nil return nil
} }
m.YOffset = max( m.SetYOffset(m.YOffset - m.Height)
m.YOffset-m.Height, // target
0, // fallback
)
return m.visibleLines() return m.visibleLines()
} }
@ -122,18 +119,8 @@ func (m *Model) HalfViewDown() (lines []string) {
return nil return nil
} }
m.YOffset = min( m.SetYOffset(m.YOffset + m.Height/2)
m.YOffset+m.Height/2, // target return m.visibleLines()
len(m.lines)-1-m.Height, // fallback
)
if len(m.lines) > 0 {
top := max(m.YOffset+m.Height/2, 0)
bottom := clamp(m.YOffset+m.Height, top, len(m.lines)-1)
lines = m.lines[top:bottom]
}
return lines
} }
// HalfViewUp moves the view up by half the height of the viewport. // HalfViewUp moves the view up by half the height of the viewport.
@ -142,18 +129,8 @@ func (m *Model) HalfViewUp() (lines []string) {
return nil return nil
} }
m.YOffset = max( m.SetYOffset(m.YOffset - m.Height/2)
m.YOffset-m.Height/2, // target return m.visibleLines()
0, // fallback
)
if len(m.lines) > 0 {
top := max(m.YOffset, 0)
bottom := clamp(m.YOffset+m.Height/2, top, len(m.lines)-1)
lines = m.lines[top:bottom]
}
return lines
} }
// LineDown moves the view down by the given number of lines. // LineDown moves the view down by the given number of lines.
@ -165,21 +142,8 @@ func (m *Model) LineDown(n int) (lines []string) {
// Make sure the number of lines by which we're going to scroll isn't // 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 // greater than the number of lines we actually have left before we reach
// the bottom. // the bottom.
maxDelta := (len(m.lines) - 1) - (m.YOffset + m.Height) // number of lines - viewport bottom edge m.SetYOffset(m.YOffset + n)
n = min(n, maxDelta) return m.visibleLines()
m.YOffset = min(
m.YOffset+n, // target
len(m.lines)-1-m.Height, // fallback
)
if len(m.lines) > 0 {
top := max(m.YOffset+m.Height-n, 0)
bottom := clamp(m.YOffset+m.Height, top, len(m.lines)-1)
lines = m.lines[top:bottom]
}
return lines
} }
// LineUp moves the view down by the given number of lines. Returns the new // LineUp moves the view down by the given number of lines. Returns the new
@ -191,17 +155,8 @@ func (m *Model) LineUp(n int) (lines []string) {
// Make sure the number of lines by which we're going to scroll isn't // 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. // greater than the number of lines we are from the top.
n = min(n, m.YOffset) m.SetYOffset(m.YOffset - n)
return m.visibleLines()
m.YOffset = max(m.YOffset-n, 0)
if len(m.lines) > 0 {
top := max(0, m.YOffset)
bottom := clamp(m.YOffset+n, top, len(m.lines)-1)
lines = m.lines[top:bottom]
}
return lines
} }
// GotoTop sets the viewport to the top position. // GotoTop sets the viewport to the top position.
@ -210,28 +165,14 @@ func (m *Model) GotoTop() (lines []string) {
return nil return nil
} }
m.YOffset = 0 m.SetYOffset(0)
return m.visibleLines()
if len(m.lines) > 0 {
top := m.YOffset
bottom := clamp(m.YOffset+m.Height, top, len(m.lines)-1)
lines = m.lines[top:bottom]
}
return lines
} }
// GotoBottom sets the viewport to the bottom position. // GotoBottom sets the viewport to the bottom position.
func (m *Model) GotoBottom() (lines []string) { func (m *Model) GotoBottom() (lines []string) {
m.YOffset = max(len(m.lines)-1-m.Height, 0) m.SetYOffset(len(m.lines) - 1 - m.Height)
return m.visibleLines()
if len(m.lines) > 0 {
top := m.YOffset
bottom := max(len(m.lines)-1, 0)
lines = m.lines[top:bottom]
}
return lines
} }
// COMMANDS // COMMANDS
@ -360,8 +301,8 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
// View renders the viewport into a string. // View renders the viewport into a string.
func (m Model) View() string { func (m Model) View() string {
if m.HighPerformanceRendering { if m.HighPerformanceRendering {
// Just send newlines since we're doing to be rendering the actual // Just send newlines since we're going to be rendering the actual
// content seprately. We still need send something that equals the // content seprately. We still need to send something that equals the
// height of this view so that the Bubble Tea standard renderer can // height of this view so that the Bubble Tea standard renderer can
// position anything below this view properly. // position anything below this view properly.
return strings.Repeat("\n", m.Height-1) return strings.Repeat("\n", m.Height-1)
@ -372,7 +313,7 @@ func (m Model) View() string {
// Fill empty space with newlines // Fill empty space with newlines
extraLines := "" extraLines := ""
if len(lines) < m.Height { if len(lines) < m.Height {
extraLines = strings.Repeat("\n", m.Height-len(lines)) extraLines = strings.Repeat("\n", max(0, m.Height-len(lines)))
} }
return strings.Join(lines, "\n") + extraLines return strings.Join(lines, "\n") + extraLines