From 67023bb00710b6a40836800da2eb5cdacc1ee9c1 Mon Sep 17 00:00:00 2001 From: adamdottv <2363879+adamdottv@users.noreply.github.com> Date: Fri, 13 Jun 2025 15:56:30 -0500 Subject: wip: refactoring tui --- packages/tui/internal/components/list/list.go | 157 ++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 packages/tui/internal/components/list/list.go (limited to 'packages/tui/internal/components/list') diff --git a/packages/tui/internal/components/list/list.go b/packages/tui/internal/components/list/list.go new file mode 100644 index 000000000..8be3c7b01 --- /dev/null +++ b/packages/tui/internal/components/list/list.go @@ -0,0 +1,157 @@ +package list + +import ( + "github.com/charmbracelet/bubbles/v2/key" + tea "github.com/charmbracelet/bubbletea/v2" + "github.com/charmbracelet/lipgloss/v2" + "github.com/sst/opencode/internal/layout" +) + +type ListItem interface { + Render(selected bool, width int) string +} + +type List[T ListItem] interface { + layout.ModelWithView + SetMaxWidth(maxWidth int) + GetSelectedItem() (item T, idx int) + SetItems(items []T) + GetItems() []T + SetSelectedIndex(idx int) + IsEmpty() bool +} + +type listComponent[T ListItem] struct { + fallbackMsg string + items []T + selectedIdx int + maxWidth int + maxVisibleItems int + useAlphaNumericKeys bool + width int + height int +} + +type listKeyMap struct { + Up key.Binding + Down key.Binding + UpAlpha key.Binding + DownAlpha key.Binding +} + +var simpleListKeys = listKeyMap{ + Up: key.NewBinding( + key.WithKeys("up"), + key.WithHelp("↑", "previous list item"), + ), + Down: key.NewBinding( + key.WithKeys("down"), + key.WithHelp("↓", "next list item"), + ), + UpAlpha: key.NewBinding( + key.WithKeys("k"), + key.WithHelp("k", "previous list item"), + ), + DownAlpha: key.NewBinding( + key.WithKeys("j"), + key.WithHelp("j", "next list item"), + ), +} + +func (c *listComponent[T]) Init() tea.Cmd { + return nil +} + +func (c *listComponent[T]) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + switch { + case key.Matches(msg, simpleListKeys.Up) || (c.useAlphaNumericKeys && key.Matches(msg, simpleListKeys.UpAlpha)): + if c.selectedIdx > 0 { + c.selectedIdx-- + } + return c, nil + case key.Matches(msg, simpleListKeys.Down) || (c.useAlphaNumericKeys && key.Matches(msg, simpleListKeys.DownAlpha)): + if c.selectedIdx < len(c.items)-1 { + c.selectedIdx++ + } + return c, nil + } + } + + return c, nil +} + +func (c *listComponent[T]) GetSelectedItem() (T, int) { + if len(c.items) > 0 { + return c.items[c.selectedIdx], c.selectedIdx + } + + var zero T + return zero, -1 +} + +func (c *listComponent[T]) SetItems(items []T) { + c.selectedIdx = 0 + c.items = items +} + +func (c *listComponent[T]) GetItems() []T { + return c.items +} + +func (c *listComponent[T]) IsEmpty() bool { + return len(c.items) == 0 +} + +func (c *listComponent[T]) SetMaxWidth(width int) { + c.maxWidth = width +} + +func (c *listComponent[T]) SetSelectedIndex(idx int) { + if idx >= 0 && idx < len(c.items) { + c.selectedIdx = idx + } +} + +func (c *listComponent[T]) View() string { + items := c.items + maxWidth := c.maxWidth + maxVisibleItems := min(c.maxVisibleItems, len(items)) + startIdx := 0 + + if len(items) <= 0 { + return c.fallbackMsg + } + + if len(items) > maxVisibleItems { + halfVisible := maxVisibleItems / 2 + if c.selectedIdx >= halfVisible && c.selectedIdx < len(items)-halfVisible { + startIdx = c.selectedIdx - halfVisible + } else if c.selectedIdx >= len(items)-halfVisible { + startIdx = len(items) - maxVisibleItems + } + } + + endIdx := min(startIdx+maxVisibleItems, len(items)) + + listItems := make([]string, 0, maxVisibleItems) + + for i := startIdx; i < endIdx; i++ { + item := items[i] + title := item.Render(i == c.selectedIdx, maxWidth) + listItems = append(listItems, title) + } + + return lipgloss.JoinVertical(lipgloss.Left, listItems...) +} + +func NewListComponent[T ListItem](items []T, maxVisibleItems int, fallbackMsg string, useAlphaNumericKeys bool) List[T] { + return &listComponent[T]{ + fallbackMsg: fallbackMsg, + items: items, + maxVisibleItems: maxVisibleItems, + useAlphaNumericKeys: useAlphaNumericKeys, + selectedIdx: 0, + } +} -- cgit v1.2.3