charmbracelet / bubbles

TUI components for Bubble Tea 🫧

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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)
}

266444965-fa022f25-e8de-481a-8f6d-e5067b2072fb

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.

bubbles/table/table.go

// 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.