diff --git a/list/list.go b/list/list.go index aace11c8..c4e2e3a6 100644 --- a/list/list.go +++ b/list/list.go @@ -87,7 +87,7 @@ type Rank struct { // DefaultFilter uses the sahilm/fuzzy to filter through the list. // This is set by default. func DefaultFilter(term string, targets []string) []Rank { - var ranks fuzzy.Matches = fuzzy.Find(term, targets) + var ranks = fuzzy.Find(term, targets) sort.Stable(ranks) result := make([]Rank, len(ranks)) for i, r := range ranks { @@ -129,6 +129,9 @@ type Model struct { showHelp bool filteringEnabled bool + itemNameSingular string + itemNamePlural string + Title string Styles Styles @@ -202,6 +205,8 @@ func New(items []Item, delegate ItemDelegate, width, height int) Model { showStatusBar: true, showPagination: true, showHelp: true, + itemNameSingular: "item", + itemNamePlural: "items", filteringEnabled: true, KeyMap: DefaultKeyMap(), Filter: DefaultFilter, @@ -286,6 +291,18 @@ func (m Model) ShowStatusBar() bool { return m.showStatusBar } +// SetStatusBarItemName defines a replacement for the items identifier. Defaults +// to item/items +func (m *Model) SetStatusBarItemName(singular, plural string) { + m.itemNameSingular = singular + m.itemNamePlural = plural +} + +// StatusBarItemName returns singular and plural status bar item names +func (m Model) StatusBarItemName() (string, string) { + return m.itemNameSingular, m.itemNamePlural +} + // ShowingPagination hides or shoes the paginator. Note that pagination will // still be active, it simply won't be displayed. func (m *Model) SetShowPagination(v bool) { @@ -1048,21 +1065,25 @@ func (m Model) statusView() string { totalItems := len(m.items) visibleItems := len(m.VisibleItems()) - plural := "" + var itemName string if visibleItems != 1 { - plural = "s" + itemName = m.itemNamePlural + } else { + itemName = m.itemNameSingular } + itemsDisplay := fmt.Sprintf("%d %s", visibleItems, itemName) + if m.filterState == Filtering { // Filter results if visibleItems == 0 { status = m.Styles.StatusEmpty.Render("Nothing matched") } else { - status = fmt.Sprintf("%d item%s", visibleItems, plural) + status = itemsDisplay } } else if len(m.items) == 0 { // Not filtering: no items. - status = m.Styles.StatusEmpty.Render("No items") + status = m.Styles.StatusEmpty.Render("No " + m.itemNamePlural) } else { // Normal filtered := m.FilterState() == FilterApplied @@ -1073,7 +1094,7 @@ func (m Model) statusView() string { status += fmt.Sprintf("ā€œ%sā€ ", f) } - status += fmt.Sprintf("%d item%s", visibleItems, plural) + status += itemsDisplay } numFiltered := totalItems - visibleItems @@ -1117,7 +1138,7 @@ func (m Model) populatedView() string { if m.filterState == Filtering { return "" } - return m.Styles.NoItems.Render("No items found.") + return m.Styles.NoItems.Render("No " + m.itemNamePlural + " found.") } if len(items) > 0 { diff --git a/list/list_test.go b/list/list_test.go new file mode 100644 index 00000000..2c925aab --- /dev/null +++ b/list/list_test.go @@ -0,0 +1,74 @@ +package list + +import ( + "fmt" + "io" + "strings" + "testing" + + tea "github.com/charmbracelet/bubbletea" +) + +type item string + +func (i item) FilterValue() string { return "" } + +type itemDelegate struct{} + +func (d itemDelegate) Height() int { return 1 } +func (d itemDelegate) Spacing() int { return 0 } +func (d itemDelegate) Update(msg tea.Msg, m *Model) tea.Cmd { return nil } +func (d itemDelegate) Render(w io.Writer, m Model, index int, listItem Item) { + i, ok := listItem.(item) + if !ok { + return + } + + str := fmt.Sprintf("%d. %s", index+1, i) + fmt.Fprint(w, m.Styles.TitleBar.Render(str)) +} + +func TestStatusBarItemName(t *testing.T) { + list := New([]Item{item("foo"), item("bar")}, itemDelegate{}, 10, 10) + expected := "2 items" + if !strings.Contains(list.statusView(), expected) { + t.Fatalf("Error: expected view to contain %s", expected) + } + + list.SetItems([]Item{item("foo")}) + expected = "1 item" + if !strings.Contains(list.statusView(), expected) { + t.Fatalf("Error: expected view to contain %s", expected) + } +} + +func TestStatusBarWithoutItems(t *testing.T) { + list := New([]Item{}, itemDelegate{}, 10, 10) + + expected := "No items" + if !strings.Contains(list.statusView(), expected) { + t.Fatalf("Error: expected view to contain %s", expected) + } +} + +func TestCustomStatusBarItemName(t *testing.T) { + list := New([]Item{item("foo"), item("bar")}, itemDelegate{}, 10, 10) + list.SetStatusBarItemName("connection", "connections") + + expected := "2 connections" + if !strings.Contains(list.statusView(), expected) { + t.Fatalf("Error: expected view to contain %s", expected) + } + + list.SetItems([]Item{item("foo")}) + expected = "1 connection" + if !strings.Contains(list.statusView(), expected) { + t.Fatalf("Error: expected view to contain %s", expected) + } + + list.SetItems([]Item{}) + expected = "No connections" + if !strings.Contains(list.statusView(), expected) { + t.Fatalf("Error: expected view to contain %s", expected) + } +}