Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
Go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221package tui
import ( "github.com/charmbracelet/x/ansi" "strings"
"github.com/charmbracelet/bubbles/cursor" "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea")
type PickerItem struct { ID string Label string Detail string}
type PickerSelectMsg struct { ID string}
type PickerCancelMsg struct{}
type PickerModel struct { Title string Items []PickerItem Filtered []PickerItem Cursor int SearchInput textinput.Model WithSearch bool Width int Height int Styles Styles}
func NewPickerModel(title string, items []PickerItem, withSearch bool, initialSelection string) PickerModel { ti := textinput.New() ti.Cursor.SetMode(cursor.CursorStatic) ti.Placeholder = "" ti.Focus() ti.Prompt = ""
m := PickerModel{ Title: title, Items: items, WithSearch: withSearch, SearchInput: ti, Styles: DefaultStyles, } m.applyFilter()
if initialSelection != "" { for i, item := range m.Filtered { if item.ID == initialSelection { m.Cursor = i break } } }
return m}
// Highlighted returns the item under the cursor.func (m PickerModel) Highlighted() (PickerItem, bool) { if m.Cursor < 0 || m.Cursor >= len(m.Filtered) { return PickerItem{}, false } return m.Filtered[m.Cursor], true}
func (m *PickerModel) SetSize(width, height int) { m.Width = width m.Height = height m.SearchInput.Width = max(1, width-8)}
func (m *PickerModel) applyFilter() { old := "" if m.Cursor >= 0 && m.Cursor < len(m.Filtered) { old = m.Filtered[m.Cursor].ID } tokens := strings.Fields(strings.ToLower(m.SearchInput.Value())) m.Filtered = nil for _, item := range m.Items { haystack := strings.ToLower(item.ID + " " + item.Label + " " + item.Detail) found := true for _, token := range tokens { if !strings.Contains(haystack, token) { found = false break } } if found { m.Filtered = append(m.Filtered, item) } } m.Cursor = 0 for i, item := range m.Filtered { if item.ID == old { m.Cursor = i break } }}
// selectableRows is a bottom-anchored list window with the selected row on// the selection surface.func inkWrap(text string, width int) string { if width <= 0 { return text } return ansi.Wrap(text, width, " ")}
func selectableRows(lines []string, selected, height, limit, width int, styles Styles) string { if height <= 0 { height = 24 } if limit <= 0 { limit = height } available := max(1, min(limit, height-8)) first := min(max(0, selected-available+1), max(0, len(lines)-available)) if len(lines) == 0 { return styles.Faint.Render("no matches") } var b strings.Builder for i := first; i < min(len(lines), first+available); i++ { prefix := " " if i == selected { prefix = selectBar() + " " } line := prefix + lines[i] if width > 0 { line = ansi.Truncate(line, width, "…") } if i == selected { line = selectedLine(line, width) } if i > first { b.WriteByte('\n') } b.WriteString(line) } return b.String()}
func pickerRow(item PickerItem, styles Styles) string { line := item.Label if item.Detail != "" { line += DefaultStyles.Faint.Render(" " + item.Detail) } return line}
func (m PickerModel) Init() tea.Cmd { if m.WithSearch { return textinput.Blink } return nil}
func (m PickerModel) Update(msg tea.Msg) (PickerModel, tea.Cmd) { var cmd tea.Cmd
switch msg := msg.(type) { case tea.KeyMsg: switch msg.Type { case tea.KeyEsc, tea.KeyCtrlC, tea.KeyCtrlD: return m, func() tea.Msg { return PickerCancelMsg{} } case tea.KeyEnter: if len(m.Filtered) > 0 && m.Cursor < len(m.Filtered) { selectedID := m.Filtered[m.Cursor].ID return m, func() tea.Msg { return PickerSelectMsg{ID: selectedID} } } return m, nil case tea.KeyUp, tea.KeyCtrlP: if m.Cursor > 0 { m.Cursor-- } return m, nil case tea.KeyDown, tea.KeyCtrlN: if m.Cursor < len(m.Filtered)-1 { m.Cursor++ } return m, nil } }
if m.WithSearch { var tiCmd tea.Cmd m.SearchInput, tiCmd = m.SearchInput.Update(msg) m.applyFilter() cmd = tea.Batch(cmd, tiCmd) }
return m, cmd}
func (m PickerModel) View() string { var b strings.Builder if m.Title != "" { b.WriteString(m.Title) b.WriteByte('\n') } lines := make([]string, len(m.Filtered)) for i, item := range m.Filtered { lines[i] = pickerRow(item, m.Styles) } b.WriteString(selectableRows(lines, m.Cursor, m.Height, m.Height, m.Width, m.Styles)) b.WriteByte('\n') if m.WithSearch { b.WriteString(promptLead()) b.WriteString(m.SearchInput.View()) } b.WriteByte('\n') b.WriteString(keyHints(hint{"↑↓", "select"}, hint{"enter", "choose"}, hint{"esc", "cancel"})) return b.String()}