package main import ( "fmt" "strings" "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" ) type listMode int const ( listBrowse listMode = iota listAdding listEditing ) type EditableList struct { title string items []string cursor int mode listMode input textinput.Model } func NewEditableList(title string, items []string) EditableList { ti := textinput.New() ti.Placeholder = "enter value..." ti.CharLimit = 200 return EditableList{ title: title, items: items, mode: listBrowse, input: ti, } } func (l EditableList) Items() []string { return l.items } func (l EditableList) IsEditing() bool { return l.mode != listBrowse } func (l EditableList) Update(msg tea.Msg) (EditableList, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: switch l.mode { case listBrowse: switch msg.String() { case "up", "k": if l.cursor > 0 { l.cursor-- } case "down", "j": if l.cursor < len(l.items)-1 { l.cursor++ } case "a": l.mode = listAdding l.input.SetValue("") l.input.Focus() return l, textinput.Blink case "x", "delete": if len(l.items) > 0 && l.cursor < len(l.items) { l.items = append(l.items[:l.cursor], l.items[l.cursor+1:]...) if l.cursor > len(l.items)-1 { l.cursor = len(l.items) - 1 } if l.cursor < 0 { l.cursor = 0 } } case "enter": if len(l.items) > 0 { l.mode = listEditing l.input.SetValue(l.items[l.cursor]) l.input.Focus() return l, textinput.Blink } } case listAdding, listEditing: switch msg.String() { case "enter": val := strings.TrimSpace(l.input.Value()) if val != "" { if l.mode == listAdding { l.items = append(l.items, val) l.cursor = len(l.items) - 1 } else { l.items[l.cursor] = val } } l.mode = listBrowse l.input.Blur() case "esc": l.mode = listBrowse l.input.Blur() default: var cmd tea.Cmd l.input, cmd = l.input.Update(msg) return l, cmd } } } return l, nil } func (l EditableList) View() string { var b strings.Builder switch l.mode { case listAdding: b.WriteString(fmt.Sprintf("%s (adding)\n", l.title)) b.WriteString(fmt.Sprintf("> %s\n", l.input.View())) b.WriteString(helpStyle.Render(" enter to confirm · esc to cancel")) case listEditing: b.WriteString(fmt.Sprintf("%s (editing)\n", l.title)) b.WriteString(fmt.Sprintf("> %s\n", l.input.View())) b.WriteString(helpStyle.Render(" enter to confirm · esc to cancel")) default: b.WriteString(l.title + "\n") if len(l.items) == 0 { b.WriteString(helpStyle.Render(" (empty)")) } for i, item := range l.items { cursor := " " if i == l.cursor { cursor = cursorStyle.Render("> ") } b.WriteString(fmt.Sprintf("%s%s\n", cursor, item)) } } content := b.String() style := boxStyle if l.mode != listBrowse { style = activeBoxStyle } return style.Render(strings.TrimRight(content, "\n")) }