simple-list.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. package utilComponents
  2. import (
  3. "github.com/charmbracelet/bubbles/key"
  4. tea "github.com/charmbracelet/bubbletea"
  5. "github.com/charmbracelet/lipgloss"
  6. "github.com/sst/opencode/internal/layout"
  7. "github.com/sst/opencode/internal/styles"
  8. "github.com/sst/opencode/internal/theme"
  9. )
  10. type SimpleListItem interface {
  11. Render(selected bool, width int) string
  12. }
  13. type SimpleList[T SimpleListItem] interface {
  14. tea.Model
  15. layout.Bindings
  16. SetMaxWidth(maxWidth int)
  17. GetSelectedItem() (item T, idx int)
  18. SetItems(items []T)
  19. GetItems() []T
  20. }
  21. type simpleListCmp[T SimpleListItem] struct {
  22. fallbackMsg string
  23. items []T
  24. selectedIdx int
  25. maxWidth int
  26. maxVisibleItems int
  27. useAlphaNumericKeys bool
  28. width int
  29. height int
  30. }
  31. type simpleListKeyMap struct {
  32. Up key.Binding
  33. Down key.Binding
  34. UpAlpha key.Binding
  35. DownAlpha key.Binding
  36. }
  37. var simpleListKeys = simpleListKeyMap{
  38. Up: key.NewBinding(
  39. key.WithKeys("up"),
  40. key.WithHelp("↑", "previous list item"),
  41. ),
  42. Down: key.NewBinding(
  43. key.WithKeys("down"),
  44. key.WithHelp("↓", "next list item"),
  45. ),
  46. UpAlpha: key.NewBinding(
  47. key.WithKeys("k"),
  48. key.WithHelp("k", "previous list item"),
  49. ),
  50. DownAlpha: key.NewBinding(
  51. key.WithKeys("j"),
  52. key.WithHelp("j", "next list item"),
  53. ),
  54. }
  55. func (c *simpleListCmp[T]) Init() tea.Cmd {
  56. return nil
  57. }
  58. func (c *simpleListCmp[T]) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
  59. switch msg := msg.(type) {
  60. case tea.KeyMsg:
  61. switch {
  62. case key.Matches(msg, simpleListKeys.Up) || (c.useAlphaNumericKeys && key.Matches(msg, simpleListKeys.UpAlpha)):
  63. if c.selectedIdx > 0 {
  64. c.selectedIdx--
  65. }
  66. return c, nil
  67. case key.Matches(msg, simpleListKeys.Down) || (c.useAlphaNumericKeys && key.Matches(msg, simpleListKeys.DownAlpha)):
  68. if c.selectedIdx < len(c.items)-1 {
  69. c.selectedIdx++
  70. }
  71. return c, nil
  72. }
  73. }
  74. return c, nil
  75. }
  76. func (c *simpleListCmp[T]) BindingKeys() []key.Binding {
  77. return layout.KeyMapToSlice(simpleListKeys)
  78. }
  79. func (c *simpleListCmp[T]) GetSelectedItem() (T, int) {
  80. if len(c.items) > 0 {
  81. return c.items[c.selectedIdx], c.selectedIdx
  82. }
  83. var zero T
  84. return zero, -1
  85. }
  86. func (c *simpleListCmp[T]) SetItems(items []T) {
  87. c.selectedIdx = 0
  88. c.items = items
  89. }
  90. func (c *simpleListCmp[T]) GetItems() []T {
  91. return c.items
  92. }
  93. func (c *simpleListCmp[T]) SetMaxWidth(width int) {
  94. c.maxWidth = width
  95. }
  96. func (c *simpleListCmp[T]) View() string {
  97. t := theme.CurrentTheme()
  98. baseStyle := styles.BaseStyle()
  99. items := c.items
  100. maxWidth := c.maxWidth
  101. maxVisibleItems := min(c.maxVisibleItems, len(items))
  102. startIdx := 0
  103. if len(items) <= 0 {
  104. return baseStyle.
  105. Background(t.Background()).
  106. Padding(0, 1).
  107. Width(maxWidth).
  108. Render(c.fallbackMsg)
  109. }
  110. if len(items) > maxVisibleItems {
  111. halfVisible := maxVisibleItems / 2
  112. if c.selectedIdx >= halfVisible && c.selectedIdx < len(items)-halfVisible {
  113. startIdx = c.selectedIdx - halfVisible
  114. } else if c.selectedIdx >= len(items)-halfVisible {
  115. startIdx = len(items) - maxVisibleItems
  116. }
  117. }
  118. endIdx := min(startIdx+maxVisibleItems, len(items))
  119. listItems := make([]string, 0, maxVisibleItems)
  120. for i := startIdx; i < endIdx; i++ {
  121. item := items[i]
  122. title := item.Render(i == c.selectedIdx, maxWidth)
  123. listItems = append(listItems, title)
  124. }
  125. return lipgloss.JoinVertical(lipgloss.Left, listItems...)
  126. }
  127. func NewSimpleList[T SimpleListItem](items []T, maxVisibleItems int, fallbackMsg string, useAlphaNumericKeys bool) SimpleList[T] {
  128. return &simpleListCmp[T]{
  129. fallbackMsg: fallbackMsg,
  130. items: items,
  131. maxVisibleItems: maxVisibleItems,
  132. useAlphaNumericKeys: useAlphaNumericKeys,
  133. selectedIdx: 0,
  134. }
  135. }