Detailed changes
@@ -12,9 +12,8 @@ require (
github.com/bmatcuk/doublestar/v4 v4.9.0
github.com/charlievieth/fastwalk v1.0.11
github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250716191546-1e2ffbbcf5c5
- github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20250717140350-bb75e8f6b6ac
- github.com/charmbracelet/catwalk v0.3.1
github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20250724172607-5ba56e2bec69
+ github.com/charmbracelet/catwalk v0.3.1
github.com/charmbracelet/fang v0.3.1-0.20250711140230-d5ebb8c1d674
github.com/charmbracelet/glamour/v2 v2.0.0-20250516160903-6f1e2c8f9ebe
github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3.0.20250716211347-10c048e36112
@@ -70,12 +70,10 @@ github.com/charlievieth/fastwalk v1.0.11 h1:5sLT/q9+d9xMdpKExawLppqvXFZCVKf6JHnr
github.com/charlievieth/fastwalk v1.0.11/go.mod h1:yGy1zbxog41ZVMcKA/i8ojXLFsuayX5VvwhQVoj9PBI=
github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250716191546-1e2ffbbcf5c5 h1:GTcMIfDQJKyNKS+xVt7GkNIwz+tBuQtIuiP50WpzNgs=
github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250716191546-1e2ffbbcf5c5/go.mod h1:6HamsBKWqEC/FVHuQMHgQL+knPyvHH55HwJDHl/adMw=
-github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20250717140350-bb75e8f6b6ac h1:murtkvFYxZ/73vk4Z/tpE4biB+WDZcFmmBp8je/yV6M=
-github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20250717140350-bb75e8f6b6ac/go.mod h1:m240IQxo1/eDQ7klblSzOCAUyc3LddHcV3Rc/YEGAgw=
-github.com/charmbracelet/catwalk v0.3.1 h1:MkGWspcMyE659zDkqS+9wsaCMTKRFEDBFY2A2sap6+U=
-github.com/charmbracelet/catwalk v0.3.1/go.mod h1:gUUCqqZ8bk4D7ZzGTu3I77k7cC2x4exRuJBN1H2u2pc=
github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20250724172607-5ba56e2bec69 h1:nXLMl4ows2qogDXhuEtDNgFNXQiU+PJer+UEBsQZuns=
github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20250724172607-5ba56e2bec69/go.mod h1:XIQ1qQfRph6Z5o2EikCydjumo0oDInQySRHuPATzbZc=
+github.com/charmbracelet/catwalk v0.3.1 h1:MkGWspcMyE659zDkqS+9wsaCMTKRFEDBFY2A2sap6+U=
+github.com/charmbracelet/catwalk v0.3.1/go.mod h1:gUUCqqZ8bk4D7ZzGTu3I77k7cC2x4exRuJBN1H2u2pc=
github.com/charmbracelet/colorprofile v0.3.1 h1:k8dTHMd7fgw4bnFd7jXTLZrSU/CQrKnL3m+AxCzDz40=
github.com/charmbracelet/colorprofile v0.3.1/go.mod h1:/GkGusxNs8VB/RSOh3fu0TJmQ4ICMMPApIIVn0KszZ0=
github.com/charmbracelet/fang v0.3.1-0.20250711140230-d5ebb8c1d674 h1:+Cz+VfxD5DO+JT1LlswXWhre0HYLj6l2HW8HVGfMuC0=
@@ -122,25 +122,23 @@ func (c *completionsCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
c.list = d.(listModel)
return c, cmd
case key.Matches(msg, c.keyMap.UpInsert):
- selectedItemInx := c.list.SelectedIndex() - 1
- items := c.list.Items()
- if selectedItemInx == list.NoSelection || selectedItemInx < 0 {
- return c, nil // No item selected, do nothing
+ s := c.list.SelectedItem()
+ if s == nil {
+ return c, nil
}
- selectedItem := items[selectedItemInx].(CompletionItem).Value()
- c.list.SetSelected(selectedItemInx)
+ selectedItem := *s
+ c.list.SetSelected(selectedItem.ID())
return c, util.CmdHandler(SelectCompletionMsg{
Value: selectedItem,
Insert: true,
})
case key.Matches(msg, c.keyMap.DownInsert):
- selectedItemInx := c.list.SelectedIndex() + 1
- items := c.list.Items()
- if selectedItemInx == list.NoSelection || selectedItemInx >= len(items) {
- return c, nil // No item selected, do nothing
+ s := c.list.SelectedItem()
+ if s == nil {
+ return c, nil
}
- selectedItem := items[selectedItemInx].(CompletionItem).Value()
- c.list.SetSelected(selectedItemInx)
+ selectedItem := *s
+ c.list.SetSelected(selectedItem.ID())
return c, util.CmdHandler(SelectCompletionMsg{
Value: selectedItem,
Insert: true,
@@ -157,7 +157,7 @@ func (m *ModelListComponent) SetModelType(modelType int) tea.Cmd {
Section: section,
}
for _, model := range configProvider.Models {
- item := list.NewCompletionItem(model.Model, ModelOption{
+ item := list.NewCompletionItem(model.Name, ModelOption{
Provider: configProvider,
Model: model,
},
@@ -195,14 +195,14 @@ func (m *ModelListComponent) SetModelType(modelType int) tea.Cmd {
}
section := list.NewItemSection(name)
- if _, ok := cfg.Providers[string(provider.ID)]; ok {
+ if _, ok := cfg.Providers.Get(string(provider.ID)); ok {
section.SetInfo(configured)
}
group := list.Group[list.CompletionItem[ModelOption]]{
Section: section,
}
for _, model := range provider.Models {
- item := list.NewCompletionItem(model.Model, ModelOption{
+ item := list.NewCompletionItem(model.Name, ModelOption{
Provider: provider,
Model: model,
},
@@ -2,6 +2,7 @@ package list
import (
tea "github.com/charmbracelet/bubbletea/v2"
+ "github.com/charmbracelet/crush/internal/csync"
"github.com/charmbracelet/crush/internal/tui/components/core/layout"
"github.com/charmbracelet/crush/internal/tui/util"
)
@@ -37,8 +38,8 @@ func NewGroupedList[T Item](groups []Group[T], opts ...ListOption) GroupedList[T
keyMap: DefaultKeyMap(),
focused: true,
},
- indexMap: make(map[string]int),
- renderedItems: map[string]renderedItem{},
+ indexMap: csync.NewMap[string, int](),
+ renderedItems: csync.NewMap[string, renderedItem](),
}
for _, opt := range opts {
opt(list.confOptions)
@@ -6,6 +6,7 @@ import (
"github.com/charmbracelet/bubbles/v2/key"
tea "github.com/charmbracelet/bubbletea/v2"
+ "github.com/charmbracelet/crush/internal/csync"
"github.com/charmbracelet/crush/internal/tui/components/anim"
"github.com/charmbracelet/crush/internal/tui/components/core/layout"
"github.com/charmbracelet/crush/internal/tui/styles"
@@ -84,10 +85,10 @@ type list[T Item] struct {
offset int
- indexMap map[string]int
+ indexMap *csync.Map[string, int]
items []T
- renderedItems map[string]renderedItem
+ renderedItems *csync.Map[string, renderedItem]
rendered string
@@ -164,8 +165,8 @@ func New[T Item](items []T, opts ...ListOption) List[T] {
focused: true,
},
items: items,
- indexMap: make(map[string]int),
- renderedItems: map[string]renderedItem{},
+ indexMap: csync.NewMap[string, int](),
+ renderedItems: csync.NewMap[string, renderedItem](),
}
for _, opt := range opts {
opt(list.confOptions)
@@ -175,7 +176,7 @@ func New[T Item](items []T, opts ...ListOption) List[T] {
if i, ok := any(item).(Indexable); ok {
i.SetIndex(inx)
}
- list.indexMap[item.ID()] = inx
+ list.indexMap.Set(item.ID(), inx)
}
return list
}
@@ -267,13 +268,13 @@ func (l *list[T]) viewPosition() (int, int) {
func (l *list[T]) recalculateItemPositions() {
currentContentHeight := 0
for _, item := range l.items {
- rItem, ok := l.renderedItems[item.ID()]
+ rItem, ok := l.renderedItems.Get(item.ID())
if !ok {
continue
}
rItem.start = currentContentHeight
rItem.end = currentContentHeight + rItem.height - 1
- l.renderedItems[item.ID()] = rItem
+ l.renderedItems.Set(item.ID(), rItem)
currentContentHeight = rItem.end + 1 + l.gap
}
}
@@ -337,7 +338,7 @@ func (l *list[T]) setDefaultSelected() {
}
func (l *list[T]) scrollToSelection() {
- rItem, ok := l.renderedItems[l.selectedItem]
+ rItem, ok := l.renderedItems.Get(l.selectedItem)
if !ok {
l.selectedItem = ""
l.setDefaultSelected()
@@ -395,7 +396,7 @@ func (l *list[T]) scrollToSelection() {
}
func (l *list[T]) changeSelectionWhenScrolling() tea.Cmd {
- rItem, ok := l.renderedItems[l.selectedItem]
+ rItem, ok := l.renderedItems.Get(l.selectedItem)
if !ok {
return nil
}
@@ -414,13 +415,16 @@ func (l *list[T]) changeSelectionWhenScrolling() tea.Cmd {
if itemMiddle < start {
// select the first item in the viewport
// the item is most likely an item coming after this item
- inx := l.indexMap[rItem.id]
+ inx, ok := l.indexMap.Get(rItem.id)
+ if !ok {
+ return nil
+ }
for {
inx = l.firstSelectableItemBelow(inx)
if inx == ItemNotFound {
return nil
}
- item, ok := l.renderedItems[l.items[inx].ID()]
+ item, ok := l.renderedItems.Get(l.items[inx].ID())
if !ok {
continue
}
@@ -439,13 +443,16 @@ func (l *list[T]) changeSelectionWhenScrolling() tea.Cmd {
} else if itemMiddle > end {
// select the first item in the viewport
// the item is most likely an item coming after this item
- inx := l.indexMap[rItem.id]
+ inx, ok := l.indexMap.Get(rItem.id)
+ if !ok {
+ return nil
+ }
for {
inx = l.firstSelectableItemAbove(inx)
if inx == ItemNotFound {
return nil
}
- item, ok := l.renderedItems[l.items[inx].ID()]
+ item, ok := l.renderedItems.Get(l.items[inx].ID())
if !ok {
continue
}
@@ -512,10 +519,10 @@ func (l *list[T]) focusSelectedItem() tea.Cmd {
if f, ok := any(item).(layout.Focusable); ok {
if item.ID() == l.selectedItem && !f.IsFocused() {
cmds = append(cmds, f.Focus())
- delete(l.renderedItems, item.ID())
+ l.renderedItems.Del(item.ID())
} else if item.ID() != l.selectedItem && f.IsFocused() {
cmds = append(cmds, f.Blur())
- delete(l.renderedItems, item.ID())
+ l.renderedItems.Del(item.ID())
}
}
}
@@ -531,7 +538,7 @@ func (l *list[T]) blurSelectedItem() tea.Cmd {
if f, ok := any(item).(layout.Focusable); ok {
if item.ID() == l.selectedItem && f.IsFocused() {
cmds = append(cmds, f.Blur())
- delete(l.renderedItems, item.ID())
+ l.renderedItems.Del(item.ID())
}
}
}
@@ -555,13 +562,13 @@ func (l *list[T]) renderIterator(startInx int, limitHeight bool) int {
item := l.items[inx]
var rItem renderedItem
- if cache, ok := l.renderedItems[item.ID()]; ok {
+ if cache, ok := l.renderedItems.Get(item.ID()); ok {
rItem = cache
} else {
rItem = l.renderItem(item)
rItem.start = currentContentHeight
rItem.end = currentContentHeight + rItem.height - 1
- l.renderedItems[item.ID()] = rItem
+ l.renderedItems.Set(item.ID(), rItem)
}
gap := l.gap + 1
if inx == len(l.items)-1 {
@@ -596,9 +603,9 @@ func (l *list[T]) AppendItem(item T) tea.Cmd {
}
l.items = append(l.items, item)
- l.indexMap = make(map[string]int)
+ l.indexMap = csync.NewMap[string, int]()
for inx, item := range l.items {
- l.indexMap[item.ID()] = inx
+ l.indexMap.Set(item.ID(), inx)
}
if l.width > 0 && l.height > 0 {
cmd = item.SetSize(l.width, l.height)
@@ -617,12 +624,14 @@ func (l *list[T]) AppendItem(item T) tea.Cmd {
cmds = append(cmds, cmd)
}
} else {
- newItem := l.renderedItems[item.ID()]
- newLines := newItem.height
- if len(l.items) > 1 {
- newLines += l.gap
+ newItem, ok := l.renderedItems.Get(item.ID())
+ if ok {
+ newLines := newItem.height
+ if len(l.items) > 1 {
+ newLines += l.gap
+ }
+ l.offset = min(lipgloss.Height(l.rendered)-1, l.offset+newLines)
}
- l.offset = min(lipgloss.Height(l.rendered)-1, l.offset+newLines)
}
}
return tea.Sequence(cmds...)
@@ -636,11 +645,14 @@ func (l *list[T]) Blur() tea.Cmd {
// DeleteItem implements List.
func (l *list[T]) DeleteItem(id string) tea.Cmd {
- inx := l.indexMap[id]
+ inx, ok := l.indexMap.Get(id)
+ if !ok {
+ return nil
+ }
l.items = slices.Delete(l.items, inx, inx+1)
- delete(l.renderedItems, id)
+ l.renderedItems.Del(id)
for inx, item := range l.items {
- l.indexMap[item.ID()] = inx
+ l.indexMap.Set(item.ID(), inx)
}
if l.selectedItem == id {
@@ -753,9 +765,9 @@ func (l *list[T]) PrependItem(item T) tea.Cmd {
item.Init(),
}
l.items = append([]T{item}, l.items...)
- l.indexMap = make(map[string]int)
+ l.indexMap = csync.NewMap[string, int]()
for inx, item := range l.items {
- l.indexMap[item.ID()] = inx
+ l.indexMap.Set(item.ID(), inx)
}
if l.width > 0 && l.height > 0 {
cmds = append(cmds, item.SetSize(l.width, l.height))
@@ -768,12 +780,14 @@ func (l *list[T]) PrependItem(item T) tea.Cmd {
cmds = append(cmds, cmd)
}
} else {
- newItem := l.renderedItems[item.ID()]
- newLines := newItem.height
- if len(l.items) > 1 {
- newLines += l.gap
+ newItem, ok := l.renderedItems.Get(item.ID())
+ if ok {
+ newLines := newItem.height
+ if len(l.items) > 1 {
+ newLines += l.gap
+ }
+ l.offset = min(lipgloss.Height(l.rendered)-1, l.offset+newLines)
}
- l.offset = min(lipgloss.Height(l.rendered)-1, l.offset+newLines)
}
}
return tea.Batch(cmds...)
@@ -781,7 +795,7 @@ func (l *list[T]) PrependItem(item T) tea.Cmd {
// SelectItemAbove implements List.
func (l *list[T]) SelectItemAbove() tea.Cmd {
- inx, ok := l.indexMap[l.selectedItem]
+ inx, ok := l.indexMap.Get(l.selectedItem)
if !ok {
return nil
}
@@ -815,7 +829,7 @@ func (l *list[T]) SelectItemAbove() tea.Cmd {
// SelectItemBelow implements List.
func (l *list[T]) SelectItemBelow() tea.Cmd {
- inx, ok := l.indexMap[l.selectedItem]
+ inx, ok := l.indexMap.Get(l.selectedItem)
if !ok {
return nil
}
@@ -833,7 +847,7 @@ func (l *list[T]) SelectItemBelow() tea.Cmd {
// SelectedItem implements List.
func (l *list[T]) SelectedItem() *T {
- inx, ok := l.indexMap[l.selectedItem]
+ inx, ok := l.indexMap.Get(l.selectedItem)
if !ok {
return nil
}
@@ -869,10 +883,10 @@ func (l *list[T]) reset(selectedItem string) tea.Cmd {
l.rendered = ""
l.offset = 0
l.selectedItem = selectedItem
- l.indexMap = make(map[string]int)
- l.renderedItems = make(map[string]renderedItem)
+ l.indexMap = csync.NewMap[string, int]()
+ l.renderedItems = csync.NewMap[string, renderedItem]()
for inx, item := range l.items {
- l.indexMap[item.ID()] = inx
+ l.indexMap.Set(item.ID(), inx)
if l.width > 0 && l.height > 0 {
cmds = append(cmds, item.SetSize(l.width, l.height))
}
@@ -896,22 +910,22 @@ func (l *list[T]) SetSize(width int, height int) tea.Cmd {
// UpdateItem implements List.
func (l *list[T]) UpdateItem(id string, item T) tea.Cmd {
var cmds []tea.Cmd
- if inx, ok := l.indexMap[id]; ok {
+ if inx, ok := l.indexMap.Get(id); ok {
l.items[inx] = item
- oldItem := l.renderedItems[id]
+ oldItem, hasOldItem := l.renderedItems.Get(id)
oldPosition := l.offset
if l.direction == DirectionBackward {
oldPosition = (lipgloss.Height(l.rendered) - 1) - l.offset
}
- delete(l.renderedItems, id)
+ l.renderedItems.Del(id)
cmd := l.render()
// need to check for nil because of sequence not handling nil
if cmd != nil {
cmds = append(cmds, cmd)
}
- if l.direction == DirectionBackward {
+ if hasOldItem && l.direction == DirectionBackward {
// if we are the last item and there is no offset
// make sure to go to the bottom
if inx == len(l.items)-1 && l.offset == 0 {
@@ -921,14 +935,18 @@ func (l *list[T]) UpdateItem(id string, item T) tea.Cmd {
}
// if the item is at least partially below the viewport
} else if oldPosition < oldItem.end {
- newItem := l.renderedItems[item.ID()]
+ newItem, ok := l.renderedItems.Get(item.ID())
+ if ok {
+ newLines := newItem.height - oldItem.height
+ l.offset = util.Clamp(l.offset+newLines, 0, lipgloss.Height(l.rendered)-1)
+ }
+ }
+ } else if hasOldItem && l.offset > oldItem.start {
+ newItem, ok := l.renderedItems.Get(item.ID())
+ if ok {
newLines := newItem.height - oldItem.height
l.offset = util.Clamp(l.offset+newLines, 0, lipgloss.Height(l.rendered)-1)
}
- } else if l.offset > oldItem.start {
- newItem := l.renderedItems[item.ID()]
- newLines := newItem.height - oldItem.height
- l.offset = util.Clamp(l.offset+newLines, 0, lipgloss.Height(l.rendered)-1)
}
}
return tea.Sequence(cmds...)
@@ -29,17 +29,19 @@ func TestList(t *testing.T) {
// should select the last item
assert.Equal(t, items[0].ID(), l.selectedItem)
assert.Equal(t, 0, l.offset)
- require.Len(t, l.indexMap, 5)
+ require.Equal(t, 5, l.indexMap.Len())
require.Len(t, l.items, 5)
- require.Len(t, l.renderedItems, 5)
+ require.Equal(t, 5, l.renderedItems.Len())
assert.Equal(t, 5, lipgloss.Height(l.rendered))
assert.NotEqual(t, "\n", string(l.rendered[len(l.rendered)-1]), "should not end in newline")
start, end := l.viewPosition()
assert.Equal(t, 0, start)
assert.Equal(t, 4, end)
for i := range 5 {
- assert.Equal(t, i, l.renderedItems[items[i].ID()].start)
- assert.Equal(t, i, l.renderedItems[items[i].ID()].end)
+ item, ok := l.renderedItems.Get(items[i].ID())
+ require.True(t, ok)
+ assert.Equal(t, i, item.start)
+ assert.Equal(t, i, item.end)
}
golden.RequireEqual(t, []byte(l.View()))
@@ -57,17 +59,19 @@ func TestList(t *testing.T) {
// should select the last item
assert.Equal(t, items[4].ID(), l.selectedItem)
assert.Equal(t, 0, l.offset)
- require.Len(t, l.indexMap, 5)
+ require.Equal(t, 5, l.indexMap.Len())
require.Len(t, l.items, 5)
- require.Len(t, l.renderedItems, 5)
+ require.Equal(t, 5, l.renderedItems.Len())
assert.Equal(t, 5, lipgloss.Height(l.rendered))
assert.NotEqual(t, "\n", string(l.rendered[len(l.rendered)-1]), "should not end in newline")
start, end := l.viewPosition()
assert.Equal(t, 0, start)
assert.Equal(t, 4, end)
for i := range 5 {
- assert.Equal(t, i, l.renderedItems[items[i].ID()].start)
- assert.Equal(t, i, l.renderedItems[items[i].ID()].end)
+ item, ok := l.renderedItems.Get(items[i].ID())
+ require.True(t, ok)
+ assert.Equal(t, i, item.start)
+ assert.Equal(t, i, item.end)
}
golden.RequireEqual(t, []byte(l.View()))
@@ -86,17 +90,19 @@ func TestList(t *testing.T) {
// should select the last item
assert.Equal(t, items[0].ID(), l.selectedItem)
assert.Equal(t, 0, l.offset)
- require.Len(t, l.indexMap, 30)
+ require.Equal(t, 30, l.indexMap.Len())
require.Len(t, l.items, 30)
- require.Len(t, l.renderedItems, 30)
+ require.Equal(t, 30, l.renderedItems.Len())
assert.Equal(t, 30, lipgloss.Height(l.rendered))
assert.NotEqual(t, "\n", string(l.rendered[len(l.rendered)-1]), "should not end in newline")
start, end := l.viewPosition()
assert.Equal(t, 0, start)
assert.Equal(t, 9, end)
for i := range 30 {
- assert.Equal(t, i, l.renderedItems[items[i].ID()].start)
- assert.Equal(t, i, l.renderedItems[items[i].ID()].end)
+ item, ok := l.renderedItems.Get(items[i].ID())
+ require.True(t, ok)
+ assert.Equal(t, i, item.start)
+ assert.Equal(t, i, item.end)
}
golden.RequireEqual(t, []byte(l.View()))
@@ -114,17 +120,19 @@ func TestList(t *testing.T) {
// should select the last item
assert.Equal(t, items[29].ID(), l.selectedItem)
assert.Equal(t, 0, l.offset)
- require.Len(t, l.indexMap, 30)
+ require.Equal(t, 30, l.indexMap.Len())
require.Len(t, l.items, 30)
- require.Len(t, l.renderedItems, 30)
+ require.Equal(t, 30, l.renderedItems.Len())
assert.Equal(t, 30, lipgloss.Height(l.rendered))
assert.NotEqual(t, "\n", string(l.rendered[len(l.rendered)-1]), "should not end in newline")
start, end := l.viewPosition()
assert.Equal(t, 20, start)
assert.Equal(t, 29, end)
for i := range 30 {
- assert.Equal(t, i, l.renderedItems[items[i].ID()].start)
- assert.Equal(t, i, l.renderedItems[items[i].ID()].end)
+ item, ok := l.renderedItems.Get(items[i].ID())
+ require.True(t, ok)
+ assert.Equal(t, i, item.start)
+ assert.Equal(t, i, item.end)
}
golden.RequireEqual(t, []byte(l.View()))
@@ -145,9 +153,9 @@ func TestList(t *testing.T) {
// should select the last item
assert.Equal(t, items[0].ID(), l.selectedItem)
assert.Equal(t, 0, l.offset)
- require.Len(t, l.indexMap, 30)
+ require.Equal(t, 30, l.indexMap.Len())
require.Len(t, l.items, 30)
- require.Len(t, l.renderedItems, 30)
+ require.Equal(t, 30, l.renderedItems.Len())
expectedLines := 0
for i := range 30 {
expectedLines += (i + 1) * 1
@@ -159,7 +167,8 @@ func TestList(t *testing.T) {
assert.Equal(t, 9, end)
currentPosition := 0
for i := range 30 {
- rItem := l.renderedItems[items[i].ID()]
+ rItem, ok := l.renderedItems.Get(items[i].ID())
+ require.True(t, ok)
assert.Equal(t, currentPosition, rItem.start)
assert.Equal(t, currentPosition+i, rItem.end)
currentPosition += i + 1
@@ -182,9 +191,9 @@ func TestList(t *testing.T) {
// should select the last item
assert.Equal(t, items[29].ID(), l.selectedItem)
assert.Equal(t, 0, l.offset)
- require.Len(t, l.indexMap, 30)
+ require.Equal(t, 30, l.indexMap.Len())
require.Len(t, l.items, 30)
- require.Len(t, l.renderedItems, 30)
+ require.Equal(t, 30, l.renderedItems.Len())
expectedLines := 0
for i := range 30 {
expectedLines += (i + 1) * 1
@@ -196,7 +205,8 @@ func TestList(t *testing.T) {
assert.Equal(t, expectedLines-1, end)
currentPosition := 0
for i := range 30 {
- rItem := l.renderedItems[items[i].ID()]
+ rItem, ok := l.renderedItems.Get(items[i].ID())
+ require.True(t, ok)
assert.Equal(t, currentPosition, rItem.start)
assert.Equal(t, currentPosition+i, rItem.end)
currentPosition += i + 1
@@ -1,6 +1,10 @@
[38;2;223;219;221m[38;2;104;255;214m> [m[38;2;96;95;107mT[m[38;2;96;95;107mype to filter[m[38;2;96;95;107m [m[m
-│Item 0
-Item 1
-Item 2
-Item 3
-Item 4
+[38;2;223;219;221m│Item 0 [m
+[38;2;223;219;221mItem 1 [m
+[38;2;223;219;221mItem 2 [m
+[38;2;223;219;221mItem 3 [m
+[38;2;223;219;221mItem 4 [m
+
+
+
+
@@ -1,10 +1,10 @@
-│Item 10
-│Item 10
-│Item 10
-│Item 10
-│Item 10
-│Item 10
-│Item 10
-│Item 10
-│Item 10
-│Item 10
+[38;2;223;219;221m│Item 10[m
+[38;2;223;219;221m│Item 10[m
+[38;2;223;219;221m│Item 10[m
+[38;2;223;219;221m│Item 10[m
+[38;2;223;219;221m│Item 10[m
+[38;2;223;219;221m│Item 10[m
+[38;2;223;219;221m│Item 10[m
+[38;2;223;219;221m│Item 10[m
+[38;2;223;219;221m│Item 10[m
+[38;2;223;219;221m│Item 10[m
@@ -1,10 +1,10 @@
-│Item 10
-│Item 10
-│Item 10
-│Item 10
-│Item 10
-│Item 10
-│Item 10
-│Item 10
-│Item 10
-│Item 10
+[38;2;223;219;221m│Item 10[m
+[38;2;223;219;221m│Item 10[m
+[38;2;223;219;221m│Item 10[m
+[38;2;223;219;221m│Item 10[m
+[38;2;223;219;221m│Item 10[m
+[38;2;223;219;221m│Item 10[m
+[38;2;223;219;221m│Item 10[m
+[38;2;223;219;221m│Item 10[m
+[38;2;223;219;221m│Item 10[m
+[38;2;223;219;221m│Item 10[m
@@ -1,10 +1,10 @@
-│Item 0
-Item 1
-Item 2
-Item 3
-Item 4
-Item 5
-Item 6
-Item 7
-Item 8
-Item 9
+[38;2;223;219;221m│Item 0[m
+[38;2;223;219;221mItem 1[m
+[38;2;223;219;221mItem 2[m
+[38;2;223;219;221mItem 3[m
+[38;2;223;219;221mItem 4[m
+[38;2;223;219;221mItem 5[m
+[38;2;223;219;221mItem 6[m
+[38;2;223;219;221mItem 7[m
+[38;2;223;219;221mItem 8[m
+[38;2;223;219;221mItem 9[m
@@ -1,10 +1,10 @@
-│Item 0
-Item 1
-Item 1
-Item 2
-Item 2
-Item 2
-Item 3
-Item 3
-Item 3
-Item 3
+[38;2;223;219;221m│Item 0[m
+[38;2;223;219;221mItem 1[m
+[38;2;223;219;221mItem 1[m
+[38;2;223;219;221mItem 2[m
+[38;2;223;219;221mItem 2[m
+[38;2;223;219;221mItem 2[m
+[38;2;223;219;221mItem 3[m
+[38;2;223;219;221mItem 3[m
+[38;2;223;219;221mItem 3[m
+[38;2;223;219;221mItem 3[m
@@ -1,10 +1,10 @@
-│Item 29
-│Item 29
-│Item 29
-│Item 29
-│Item 29
-│Item 29
-│Item 29
-│Item 29
-│Item 29
-│Item 29
+[38;2;223;219;221m│Item 29[m
+[38;2;223;219;221m│Item 29[m
+[38;2;223;219;221m│Item 29[m
+[38;2;223;219;221m│Item 29[m
+[38;2;223;219;221m│Item 29[m
+[38;2;223;219;221m│Item 29[m
+[38;2;223;219;221m│Item 29[m
+[38;2;223;219;221m│Item 29[m
+[38;2;223;219;221m│Item 29[m
+[38;2;223;219;221m│Item 29[m
@@ -1,10 +1,10 @@
-Item 20
-Item 21
-Item 22
-Item 23
-Item 24
-Item 25
-Item 26
-Item 27
-Item 28
-│Item 29
+[38;2;223;219;221mItem 20[m
+[38;2;223;219;221mItem 21[m
+[38;2;223;219;221mItem 22[m
+[38;2;223;219;221mItem 23[m
+[38;2;223;219;221mItem 24[m
+[38;2;223;219;221mItem 25[m
+[38;2;223;219;221mItem 26[m
+[38;2;223;219;221mItem 27[m
+[38;2;223;219;221mItem 28[m
+[38;2;223;219;221m│Item 29[m
@@ -1,5 +1,20 @@
-│Item 0
-Item 1
-Item 2
-Item 3
-Item 4
+[38;2;223;219;221m│Item 0[m
+[38;2;223;219;221mItem 1[m
+[38;2;223;219;221mItem 2[m
+[38;2;223;219;221mItem 3[m
+[38;2;223;219;221mItem 4[m
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1,5 +1,20 @@
-Item 0
-Item 1
-Item 2
-Item 3
-│Item 4
+[38;2;223;219;221mItem 0[m
+[38;2;223;219;221mItem 1[m
+[38;2;223;219;221mItem 2[m
+[38;2;223;219;221mItem 3[m
+[38;2;223;219;221m│Item 4[m
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1,10 +1,10 @@
-Item 6
-Item 6
-Item 6
-│Item 7
-│Item 7
-│Item 7
-│Item 7
-│Item 7
-│Item 7
-│Item 7
+[38;2;223;219;221mItem 6[m
+[38;2;223;219;221mItem 6[m
+[38;2;223;219;221mItem 6[m
+[38;2;223;219;221m│Item 7[m
+[38;2;223;219;221m│Item 7[m
+[38;2;223;219;221m│Item 7[m
+[38;2;223;219;221m│Item 7[m
+[38;2;223;219;221m│Item 7[m
+[38;2;223;219;221m│Item 7[m
+[38;2;223;219;221m│Item 7[m
@@ -1,10 +1,10 @@
-Item 0
-Item 1
-Item 1
-Item 2
-Item 2
-Item 2
-│Item 3
-│Item 3
-│Item 3
-│Item 3
+[38;2;223;219;221mItem 0[m
+[38;2;223;219;221mItem 1[m
+[38;2;223;219;221mItem 1[m
+[38;2;223;219;221mItem 2[m
+[38;2;223;219;221mItem 2[m
+[38;2;223;219;221mItem 2[m
+[38;2;223;219;221m│Item 3[m
+[38;2;223;219;221m│Item 3[m
+[38;2;223;219;221m│Item 3[m
+[38;2;223;219;221m│Item 3[m
@@ -1,10 +1,10 @@
-│Item 28
-│Item 28
-│Item 28
-│Item 28
-│Item 28
-Item 29
-Item 29
-Item 29
-Item 29
-Item 29
+[38;2;223;219;221m│Item 28[m
+[38;2;223;219;221m│Item 28[m
+[38;2;223;219;221m│Item 28[m
+[38;2;223;219;221m│Item 28[m
+[38;2;223;219;221m│Item 28[m
+[38;2;223;219;221mItem 29[m
+[38;2;223;219;221mItem 29[m
+[38;2;223;219;221mItem 29[m
+[38;2;223;219;221mItem 29[m
+[38;2;223;219;221mItem 29[m
@@ -1,10 +1,10 @@
-│Item 29
-│Item 29
-│Item 29
-│Item 29
-│Item 29
-│Item 29
-│Item 29
-│Item 29
-│Item 29
-│Item 29
+[38;2;223;219;221m│Item 29[m
+[38;2;223;219;221m│Item 29[m
+[38;2;223;219;221m│Item 29[m
+[38;2;223;219;221m│Item 29[m
+[38;2;223;219;221m│Item 29[m
+[38;2;223;219;221m│Item 29[m
+[38;2;223;219;221m│Item 29[m
+[38;2;223;219;221m│Item 29[m
+[38;2;223;219;221m│Item 29[m
+[38;2;223;219;221m│Item 29[m
@@ -1,10 +1,10 @@
-Item 29
-Item 29
-Item 29
-Item 29
-Item 29
-Item 29
-Item 29
-Item 29
-Item 29
-│Testing
+[38;2;223;219;221mItem 29[m
+[38;2;223;219;221mItem 29[m
+[38;2;223;219;221mItem 29[m
+[38;2;223;219;221mItem 29[m
+[38;2;223;219;221mItem 29[m
+[38;2;223;219;221mItem 29[m
+[38;2;223;219;221mItem 29[m
+[38;2;223;219;221mItem 29[m
+[38;2;223;219;221mItem 29[m
+[38;2;223;219;221m│Testing [m
@@ -1,10 +1,10 @@
-│Testing
-Item 0
-Item 1
-Item 1
-Item 2
-Item 2
-Item 2
-Item 3
-Item 3
-Item 3
+[38;2;223;219;221m│Testing [m
+[38;2;223;219;221mItem 0[m
+[38;2;223;219;221mItem 1[m
+[38;2;223;219;221mItem 1[m
+[38;2;223;219;221mItem 2[m
+[38;2;223;219;221mItem 2[m
+[38;2;223;219;221mItem 2[m
+[38;2;223;219;221mItem 3[m
+[38;2;223;219;221mItem 3[m
+[38;2;223;219;221mItem 3[m
@@ -1,10 +1,10 @@
-│Item 2
-Item 3
-Item 4
-Item 5
-Item 6
-Item 7
-Item 8
-Item 9
-Item 10
-Item 11
+[38;2;223;219;221m│Item 2[m
+[38;2;223;219;221mItem 3[m
+[38;2;223;219;221mItem 4[m
+[38;2;223;219;221mItem 5[m
+[38;2;223;219;221mItem 6[m
+[38;2;223;219;221mItem 7[m
+[38;2;223;219;221mItem 8[m
+[38;2;223;219;221mItem 9[m
+[38;2;223;219;221mItem 10[m
+[38;2;223;219;221mItem 11[m
@@ -1,10 +1,10 @@
-Item 18
-Item 19
-Item 20
-Item 21
-Item 22
-Item 23
-Item 24
-Item 25
-Item 26
-│Item 27
+[38;2;223;219;221mItem 18[m
+[38;2;223;219;221mItem 19[m
+[38;2;223;219;221mItem 20[m
+[38;2;223;219;221mItem 21[m
+[38;2;223;219;221mItem 22[m
+[38;2;223;219;221mItem 23[m
+[38;2;223;219;221mItem 24[m
+[38;2;223;219;221mItem 25[m
+[38;2;223;219;221mItem 26[m
+[38;2;223;219;221m│Item 27[m
@@ -1,10 +1,10 @@
-│Item 2
-Item 3
-Item 4
-Item 5
-Item 6
-Item 7
-Item 8
-Item 9
-Item 10
-Item 11
+[38;2;223;219;221m│Item 2[m
+[38;2;223;219;221mItem 3[m
+[38;2;223;219;221mItem 4[m
+[38;2;223;219;221mItem 5[m
+[38;2;223;219;221mItem 6[m
+[38;2;223;219;221mItem 7[m
+[38;2;223;219;221mItem 8[m
+[38;2;223;219;221mItem 9[m
+[38;2;223;219;221mItem 10[m
+[38;2;223;219;221mItem 11[m
@@ -1,10 +1,10 @@
-Item 18
-Item 19
-Item 20
-Item 21
-Item 22
-Item 23
-Item 24
-Item 25
-Item 26
-│Item 27
+[38;2;223;219;221mItem 18[m
+[38;2;223;219;221mItem 19[m
+[38;2;223;219;221mItem 20[m
+[38;2;223;219;221mItem 21[m
+[38;2;223;219;221mItem 22[m
+[38;2;223;219;221mItem 23[m
+[38;2;223;219;221mItem 24[m
+[38;2;223;219;221mItem 25[m
+[38;2;223;219;221mItem 26[m
+[38;2;223;219;221m│Item 27[m
@@ -1,10 +1,10 @@
-│Item 0
-Item 1
-Item 2
-Item 3
-Item 4
-Item 5
-Item 6
-Item 7
-Item 8
-Item 9
+[38;2;223;219;221m│Item 0[m
+[38;2;223;219;221mItem 1[m
+[38;2;223;219;221mItem 2[m
+[38;2;223;219;221mItem 3[m
+[38;2;223;219;221mItem 4[m
+[38;2;223;219;221mItem 5[m
+[38;2;223;219;221mItem 6[m
+[38;2;223;219;221mItem 7[m
+[38;2;223;219;221mItem 8[m
+[38;2;223;219;221mItem 9[m
@@ -1,10 +1,10 @@
-Item 18
-Item 19
-Item 20
-Item 21
-Item 22
-Item 23
-Item 24
-Item 25
-Item 26
-│Item 27
+[38;2;223;219;221mItem 18[m
+[38;2;223;219;221mItem 19[m
+[38;2;223;219;221mItem 20[m
+[38;2;223;219;221mItem 21[m
+[38;2;223;219;221mItem 22[m
+[38;2;223;219;221mItem 23[m
+[38;2;223;219;221mItem 24[m
+[38;2;223;219;221mItem 25[m
+[38;2;223;219;221mItem 26[m
+[38;2;223;219;221m│Item 27[m
@@ -1,10 +1,10 @@
-│Item 2
-Item 3
-Item 4
-Item 5
-Item 6
-Item 7
-Item 8
-Item 9
-Item 10
-Item 11
+[38;2;223;219;221m│Item 2[m
+[38;2;223;219;221mItem 3[m
+[38;2;223;219;221mItem 4[m
+[38;2;223;219;221mItem 5[m
+[38;2;223;219;221mItem 6[m
+[38;2;223;219;221mItem 7[m
+[38;2;223;219;221mItem 8[m
+[38;2;223;219;221mItem 9[m
+[38;2;223;219;221mItem 10[m
+[38;2;223;219;221mItem 11[m
@@ -1,10 +1,10 @@
-Item 21
-Item 22
-Item 23
-Item 24
-Item 25
-Item 26
-Item 27
-Item 28
-│Item 29
-Item 30
+[38;2;223;219;221mItem 21[m
+[38;2;223;219;221mItem 22[m
+[38;2;223;219;221mItem 23[m
+[38;2;223;219;221mItem 24[m
+[38;2;223;219;221mItem 25[m
+[38;2;223;219;221mItem 26[m
+[38;2;223;219;221mItem 27[m
+[38;2;223;219;221mItem 28[m
+[38;2;223;219;221m│Item 29[m
+[38;2;223;219;221mItem 30[m
@@ -1,10 +1,10 @@
-Item 18
-Item 19
-Item 20
-Item 21
-Item 22
-Item 23
-Item 24
-Item 25
-Item 26
-│Item 27
+[38;2;223;219;221mItem 18[m
+[38;2;223;219;221mItem 19[m
+[38;2;223;219;221mItem 20[m
+[38;2;223;219;221mItem 21[m
+[38;2;223;219;221mItem 22[m
+[38;2;223;219;221mItem 23[m
+[38;2;223;219;221mItem 24[m
+[38;2;223;219;221mItem 25[m
+[38;2;223;219;221mItem 26[m
+[38;2;223;219;221m│Item 27[m
@@ -1,10 +1,10 @@
-│Item 2
-Item 3
-Item 4
-Item 5
-Item 6
-Item 7
-Item 8
-Item 9
-Item 10
-Item 11
+[38;2;223;219;221m│Item 2[m
+[38;2;223;219;221mItem 3[m
+[38;2;223;219;221mItem 4[m
+[38;2;223;219;221mItem 5[m
+[38;2;223;219;221mItem 6[m
+[38;2;223;219;221mItem 7[m
+[38;2;223;219;221mItem 8[m
+[38;2;223;219;221mItem 9[m
+[38;2;223;219;221mItem 10[m
+[38;2;223;219;221mItem 11[m