refactor(picker): rename Picker to Model (#770)

This commit is contained in:
ayn2op 2026-03-17 19:31:07 -04:00 • committed by GitHub
parent 3f8a6533da
commit ee8439bc0a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 169 additions and 169 deletions

View file

@ -16,7 +16,7 @@ type attachmentItem struct {
}
type attachmentsPicker struct {
*picker.Picker
*picker.Model
cfg *config.Config
chatView *Model
items []attachmentItem
@ -26,7 +26,7 @@ var _ help.KeyMap = (*attachmentsPicker)(nil)
func newAttachmentsPicker(cfg *config.Config, chatView *Model) *attachmentsPicker {
ap := &attachmentsPicker{
Picker: picker.New(),
Model: picker.NewModel(),
cfg: cfg,
chatView: chatView,
}
@ -92,7 +92,7 @@ func (ap *attachmentsPicker) HandleEvent(event tcell.Event) tview.Command {
return nil
}
return ap.Picker.HandleEvent(event)
return ap.Model.HandleEvent(event)
}
func (ap *attachmentsPicker) ShortHelp() []keybind.Keybind {

View file

@ -15,14 +15,14 @@ import (
)
type channelsPicker struct {
*picker.Picker
*picker.Model
chatView *Model
}
var _ help.KeyMap = (*channelsPicker)(nil)
func newChannelsPicker(cfg *config.Config, chatView *Model) *channelsPicker {
cp := &channelsPicker{picker.New(), chatView}
cp := &channelsPicker{picker.NewModel(), chatView}
cp.Box = ui.ConfigureBox(tview.NewBox(), &cfg.Theme)
// When a child of the parent flex is focused, the parent layout itself is not reported as focused.
// Instead, the focused child (picker) is considered focused.
@ -84,7 +84,7 @@ func (cp *channelsPicker) HandleEvent(event tcell.Event) tview.Command {
cp.chatView.closePicker()
return nil
}
return cp.Picker.HandleEvent(event)
return cp.Model.HandleEvent(event)
}
func (cp *channelsPicker) update() {

View file

@ -14,10 +14,10 @@ func newSelectedEvent(item Item) *SelectedEvent {
return &SelectedEvent{Item: item}
}
func (p *Picker) _select() tview.Command {
index := p.list.Cursor()
if index >= 0 && index < len(p.filtered) {
item := p.filtered[index]
func (m *Model) _select() tview.Command {
index := m.list.Cursor()
if index >= 0 && index < len(m.filtered) {
item := m.filtered[index]
return func() tcell.Event {
return newSelectedEvent(item)
}

159
pkg/picker/model.go Normal file
View file

@ -0,0 +1,159 @@
package picker
import (
"github.com/ayn2op/tview"
"github.com/ayn2op/tview/flex"
"github.com/ayn2op/tview/keybind"
"github.com/ayn2op/tview/list"
"github.com/gdamore/tcell/v3"
"github.com/sahilm/fuzzy"
)
// bottom border + value
const inputHeight = 2
type Model struct {
*flex.Model
input *tview.InputField
list *list.Model
keyMap *KeyMap
items Items
filtered Items
}
func NewModel() *Model {
m := &Model{
Model: flex.NewModel(),
input: tview.NewInputField(),
list: list.NewModel(),
}
// Show a horizontal bottom border to visually separate input from list.
var borderSet tview.BorderSet
borderSet.Bottom = tview.BoxDrawingsLightHorizontal
borderSet.BottomLeft = borderSet.Bottom
borderSet.BottomRight = borderSet.Bottom
m.input.
SetChangedFunc(m.onInputChanged).
SetLabel("> ").
SetBorders(tview.BordersBottom).
SetBorderSet(borderSet).
SetBorderStyle(tcell.StyleDefault.Dim(true))
m.
SetDirection(flex.DirectionRow).
AddItem(m.input, inputHeight, 0, true).
AddItem(m.list, 0, 1, false)
m.Update()
return m
}
func (m *Model) setFilteredItems(filtered Items) {
m.filtered = filtered
m.list.SetBuilder(func(index int, cursor int) list.Item {
if index < 0 || index >= len(m.filtered) {
return nil
}
style := tcell.StyleDefault
if index == cursor {
style = style.Reverse(true)
}
return tview.NewTextView().
SetScrollable(false).
SetWrap(false).
SetWordWrap(false).
SetTextStyle(style).
SetLines([]tview.Line{{{Text: m.filtered[index].Text, Style: style}}})
})
if len(filtered) == 0 {
m.list.SetCursor(-1)
} else {
m.list.SetCursor(0)
}
}
func (m *Model) SetKeyMap(keyMap *KeyMap) {
m.keyMap = keyMap
}
// SetScrollBarVisibility sets when the picker's list scrollBar is rendered.
func (m *Model) SetScrollBarVisibility(visibility list.ScrollBarVisibility) {
m.list.SetScrollBarVisibility(visibility)
}
// SetScrollBar sets the scrollBar primitive used by the picker's list.
func (m *Model) SetScrollBar(scrollBar *tview.ScrollBar) {
m.list.SetScrollBar(scrollBar)
}
func (m *Model) ClearInput() {
m.input.SetText("")
}
func (m *Model) ClearList() {
m.filtered = nil
m.list.Clear()
}
func (m *Model) ClearItems() {
m.items = nil
m.filtered = nil
}
func (m *Model) AddItem(item Item) {
m.items = append(m.items, item)
}
func (m *Model) Update() {
m.ClearInput()
m.onInputChanged("")
}
func (m *Model) onInputChanged(text string) {
var fuzzied Items
if text == "" {
fuzzied = append(fuzzied, m.items...)
} else {
matches := fuzzy.FindFrom(text, m.items)
for _, match := range matches {
fuzzied = append(fuzzied, m.items[match.Index])
}
}
m.setFilteredItems(fuzzied)
}
func (m *Model) HandleEvent(event tcell.Event) tview.Command {
switch event := event.(type) {
case *tview.KeyEvent:
if m.keyMap != nil {
switch {
case keybind.Matches(event, m.keyMap.Up):
m.list.HandleEvent(tcell.NewEventKey(tcell.KeyUp, "", tcell.ModNone))
return nil
case keybind.Matches(event, m.keyMap.Down):
m.list.HandleEvent(tcell.NewEventKey(tcell.KeyDown, "", tcell.ModNone))
return nil
case keybind.Matches(event, m.keyMap.Top):
m.list.HandleEvent(tcell.NewEventKey(tcell.KeyHome, "", tcell.ModNone))
return nil
case keybind.Matches(event, m.keyMap.Bottom):
m.list.HandleEvent(tcell.NewEventKey(tcell.KeyEnd, "", tcell.ModNone))
return nil
case keybind.Matches(event, m.keyMap.Select):
return m._select()
case keybind.Matches(event, m.keyMap.Cancel):
return cancel()
}
}
return m.Model.HandleEvent(event)
}
return m.Model.HandleEvent(event)
}

View file

@ -1,159 +0,0 @@
package picker
import (
"github.com/ayn2op/tview"
"github.com/ayn2op/tview/flex"
"github.com/ayn2op/tview/keybind"
"github.com/ayn2op/tview/list"
"github.com/gdamore/tcell/v3"
"github.com/sahilm/fuzzy"
)
// bottom border + value
const inputHeight = 2
type Picker struct {
*flex.Model
input *tview.InputField
list *list.Model
keyMap *KeyMap
items Items
filtered Items
}
func New() *Picker {
p := &Picker{
Model: flex.NewModel(),
input: tview.NewInputField(),
list: list.NewModel(),
}
// Show a horizontal bottom border to visually separate input from list.
var borderSet tview.BorderSet
borderSet.Bottom = tview.BoxDrawingsLightHorizontal
borderSet.BottomLeft = borderSet.Bottom
borderSet.BottomRight = borderSet.Bottom
p.input.
SetChangedFunc(p.onInputChanged).
SetLabel("> ").
SetBorders(tview.BordersBottom).
SetBorderSet(borderSet).
SetBorderStyle(tcell.StyleDefault.Dim(true))
p.
SetDirection(flex.DirectionRow).
AddItem(p.input, inputHeight, 0, true).
AddItem(p.list, 0, 1, false)
p.Update()
return p
}
func (p *Picker) setFilteredItems(filtered Items) {
p.filtered = filtered
p.list.SetBuilder(func(index int, cursor int) list.Item {
if index < 0 || index >= len(p.filtered) {
return nil
}
style := tcell.StyleDefault
if index == cursor {
style = style.Reverse(true)
}
return tview.NewTextView().
SetScrollable(false).
SetWrap(false).
SetWordWrap(false).
SetTextStyle(style).
SetLines([]tview.Line{{{Text: p.filtered[index].Text, Style: style}}})
})
if len(filtered) == 0 {
p.list.SetCursor(-1)
} else {
p.list.SetCursor(0)
}
}
func (p *Picker) SetKeyMap(keyMap *KeyMap) {
p.keyMap = keyMap
}
// SetScrollBarVisibility sets when the picker's list scrollBar is rendered.
func (p *Picker) SetScrollBarVisibility(visibility list.ScrollBarVisibility) {
p.list.SetScrollBarVisibility(visibility)
}
// SetScrollBar sets the scrollBar primitive used by the picker's list.
func (p *Picker) SetScrollBar(scrollBar *tview.ScrollBar) {
p.list.SetScrollBar(scrollBar)
}
func (p *Picker) ClearInput() {
p.input.SetText("")
}
func (p *Picker) ClearList() {
p.filtered = nil
p.list.Clear()
}
func (p *Picker) ClearItems() {
p.items = nil
p.filtered = nil
}
func (p *Picker) AddItem(item Item) {
p.items = append(p.items, item)
}
func (p *Picker) Update() {
p.ClearInput()
p.onInputChanged("")
}
func (p *Picker) onInputChanged(text string) {
var fuzzied Items
if text == "" {
fuzzied = append(fuzzied, p.items...)
} else {
matches := fuzzy.FindFrom(text, p.items)
for _, match := range matches {
fuzzied = append(fuzzied, p.items[match.Index])
}
}
p.setFilteredItems(fuzzied)
}
func (p *Picker) HandleEvent(event tcell.Event) tview.Command {
switch event := event.(type) {
case *tview.KeyEvent:
if p.keyMap != nil {
switch {
case keybind.Matches(event, p.keyMap.Up):
p.list.HandleEvent(tcell.NewEventKey(tcell.KeyUp, "", tcell.ModNone))
return nil
case keybind.Matches(event, p.keyMap.Down):
p.list.HandleEvent(tcell.NewEventKey(tcell.KeyDown, "", tcell.ModNone))
return nil
case keybind.Matches(event, p.keyMap.Top):
p.list.HandleEvent(tcell.NewEventKey(tcell.KeyHome, "", tcell.ModNone))
return nil
case keybind.Matches(event, p.keyMap.Bottom):
p.list.HandleEvent(tcell.NewEventKey(tcell.KeyEnd, "", tcell.ModNone))
return nil
case keybind.Matches(event, p.keyMap.Select):
return p._select()
case keybind.Matches(event, p.keyMap.Cancel):
return cancel()
}
}
return p.Model.HandleEvent(event)
}
return p.Model.HandleEvent(event)
}