Table is scrolling before selected row reaches the top
dzeleniak opened this issue · comments
This is a copy of an issue created on bubbletea.
"Here is a code sample that uses the table component.
Something I noticed is that when I press the down arrow key, going
from the first item to the last, everything works as expected.
Going in the reverse direction, that is, going from the last item
to the first one (via up arrow) the viewport is scrolled down
before the selected line matches the top of the viewport. Check
the animated gif on the entry labeled as Poland.
What can be done so that going from the first item to last item
and the opposite way (last to first) behaves exactly the same way?"
package main
import (
"os"
"fmt"
"strings"
"github.com/charmbracelet/bubbles/table"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
var selectedRepo string = ""
var baseStyle = lipgloss.NewStyle().
BorderStyle(lipgloss.DoubleBorder()).
BorderForeground(lipgloss.Color("56"))
type model struct {
table table.Model
width int
height int
}
func (m model) Init() tea.Cmd { return nil }
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.width = msg.Width
m.height = msg.Height
case tea.KeyMsg:
switch msg.String() {
case "esc", "q":
return m, tea.Quit
case "enter":
selectedRepo = m.table.SelectedRow()[0]
return m, tea.Quit
}
}
m.table, cmd = m.table.Update(msg)
return m, cmd
}
func (m model) View() string {
if m.width == 0 { return "" }
table := baseStyle.Render(m.table.View())
return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, table)
}
func center(s string, w int) string {
if len(s) >= w { return s }
n := w - len(s)
div := n / 2
return strings.Repeat(" ", div) + s + strings.Repeat(" ", div)
}
func main() {
centeredTitle := center("Press [ENTER] to choose repository or [ESC]/[Q] to quit", 71)
columns := []table.Column{
{Title: centeredTitle, Width: 71},
}
rows := []table.Row{
{" Brazil devuan.c3sl.ufpr.br"},
{" Bulgaria dev1.ipacct.com"},
{" Bulgaria devuan.ipacct.com/devuan"},
{" Canada mishka.snork.ca/devuan"},
{" Chile devuan.dcc.uchile.cl"},
{" Denmark mirrors.dotsrc.org/devuan"},
{" England devuan-mirror.thorcom.net"},
{" Finland devuan.packet-gain.de"},
{" France pkgmaster.devuan.org"},
{" Germany devuan.bio.lmu.de"},
{" Germany devuan.sedf.de"},
{" Germany dist-mirror.fem.tu-ilmenau.de/devuan"},
{" Germany ftp.fau.de/devuan"},
{" Germany mirror.checkdomain.de/devuan"},
{" Germany mirror.stinpriza.org/devuan"},
{" Hungary quantum-mirror.hu/mirrors/pub/devuan"},
{" India dev1.ipacct.in"},
{" India devuan.ipacct.in/devuan"},
{" Japan devuan.m10k.jp"},
{" Netherlands mirror.koddos.net/devuan"},
{" Netherlands mirror.vpgrp.io/devuan"},
{" Netherlands sledjhamr.org/devuan"},
{" New Zealand deb.devuan.nz"},
{" Poland devuan.krypto-it.pl/devuan/devuan"},
{" Poland devuan.sakamoto.pl/packages"},
{" Spain repo.ifca.es/devuan"},
{" Sweden devuan.keff.org"},
{" Switzerland devuan.planetcobalt.net"},
{" Switzerland mirror.ungleich.ch/mirror/packages/devuan"},
{" Taiwan tw1.mirror.blendbyte.net/devuan"},
{" Ukraine mirror.mirohost.net/devuan"},
{" United States of America dev.beard.ly/devuan"},
{" United States of America devuan.slipfox.xyz"},
{" United States of America mirrors.ocf.berkeley.edu/devuan"},
{" Uruguay espejito.fder.edu.uy/devuan"},
}
t := table.New(
table.WithColumns(columns),
table.WithRows(rows),
table.WithFocused(true),
table.WithHeight(13),
)
s := table.DefaultStyles()
s.Header = s.Header.
BorderStyle(lipgloss.NormalBorder()).
BorderForeground(lipgloss.Color("56")).
BorderBottom(true).
Bold(true)
s.Selected = s.Selected.
Foreground(lipgloss.Color("229")).
Background(lipgloss.Color("13")).
Bold(false)
t.SetStyles(s)
m := model{t, 0, 0}
if _, err := tea.NewProgram(m, tea.WithAltScreen()).Run(); err != nil {
fmt.Println("Error running program:", err)
os.Exit(1)
}
if selectedRepo == "" {
fmt.Printf("No repository was chosen\n")
os.Exit(0)
}
var repo string
var parts []string
repo = strings.TrimLeft(selectedRepo, " ")
parts = strings.Split(repo, " ")
repo = parts[1]
fmt.Printf("%s\n", repo)
}
I have done a little digging on this issue.
It looks like the problem is in the bubbles package on the move-up command. Upon investigation it looks like the error is in the MoveUp function.
The following code fixes the issue.
// MoveUp moves the selection up by any number of rows.
// 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)
switch {
case m.start == 0:
m.viewport.SetYOffset(clamp(m.viewport.YOffset, 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)
}
m.UpdateViewport()
}
Just as a note, #363 reports a similar problem but when scrolling down. I don't know if it is the same bug behind the scene, so just in case...
Thanks for the heads up! I'll take a look at this tomorrow.