diff options
| author | adamdottv <[email protected]> | 2025-06-13 15:56:30 -0500 |
|---|---|---|
| committer | adamdottv <[email protected]> | 2025-06-13 15:56:33 -0500 |
| commit | 67023bb00710b6a40836800da2eb5cdacc1ee9c1 (patch) | |
| tree | 4c6fce2842956f214b8e39a7465d4444b37d39b0 /packages/tui/internal/components/list | |
| parent | a316aed4fe973682667a19e6ba550270cf1a9df4 (diff) | |
| download | opencode-67023bb00710b6a40836800da2eb5cdacc1ee9c1.tar.gz opencode-67023bb00710b6a40836800da2eb5cdacc1ee9c1.zip | |
wip: refactoring tui
Diffstat (limited to 'packages/tui/internal/components/list')
| -rw-r--r-- | packages/tui/internal/components/list/list.go | 157 |
1 files changed, 157 insertions, 0 deletions
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, + } +} |
