From 993cc5360912d317cec74446390ce46f957fda93 Mon Sep 17 00:00:00 2001 From: ayn2op Date: Wed, 18 Mar 2026 04:52:11 -0400 Subject: [PATCH] refactor(ui/chat): align discordo with latest tview list/picker API (#772) --- go.mod | 2 +- go.sum | 4 +- internal/ui/chat/attachments_picker.go | 15 ++- internal/ui/chat/channels_picker.go | 15 ++- internal/ui/chat/guilds_tree.go | 2 +- internal/ui/chat/mentions_list.go | 4 + internal/ui/chat/message_input.go | 13 +- internal/ui/chat/messages_list.go | 21 ++-- pkg/picker/events.go | 34 ------ pkg/picker/item.go | 17 --- pkg/picker/keymap.go | 13 -- pkg/picker/model.go | 159 ------------------------- 12 files changed, 35 insertions(+), 264 deletions(-) delete mode 100644 pkg/picker/events.go delete mode 100644 pkg/picker/item.go delete mode 100644 pkg/picker/keymap.go delete mode 100644 pkg/picker/model.go diff --git a/go.mod b/go.mod index 34fbfea..3927fdf 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/alecthomas/chroma/v2 v2.23.1 github.com/andybalholm/brotli v1.2.0 github.com/ayn2op/clipboard v0.0.0-20260308203959-c5ad7df3fc97 - github.com/ayn2op/tview v0.0.0-20260318010246-0993cedfe0c2 + github.com/ayn2op/tview v0.0.0-20260318080250-5bddfb2ac264 github.com/deckarep/gosx-notifier v0.0.0-20180201035817-e127226297fb github.com/diamondburned/arikawa/v3 v3.6.1-0.20260311205148-176ad9b9440f github.com/diamondburned/ningen/v3 v3.0.1-0.20260306213430-5a08d3a709b4 diff --git a/go.sum b/go.sum index 8b4f54c..36c197a 100644 --- a/go.sum +++ b/go.sum @@ -16,8 +16,8 @@ github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwTo github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/ayn2op/clipboard v0.0.0-20260308203959-c5ad7df3fc97 h1:WujETUV+v0DEJyZgjeLzQvihWyL80c0Tg4qf0dDo+Io= github.com/ayn2op/clipboard v0.0.0-20260308203959-c5ad7df3fc97/go.mod h1:3kFnpNCa3dF6WryzOMCDao7PfZ7DTCh+pievlfuwV80= -github.com/ayn2op/tview v0.0.0-20260318010246-0993cedfe0c2 h1:AMOGrvQ1+TnDRuKC2Jnr+H1WL4bq8GSNd2GDxvy3dig= -github.com/ayn2op/tview v0.0.0-20260318010246-0993cedfe0c2/go.mod h1:IXZrCv1VAZyWPdKDUJYs9ZYFiJ1GmCQUo9tLjQWoSnU= +github.com/ayn2op/tview v0.0.0-20260318080250-5bddfb2ac264 h1:UolGESVzxrK9JyGkpoc1sLZeIWDiMtCwRA8sRckqmrA= +github.com/ayn2op/tview v0.0.0-20260318080250-5bddfb2ac264/go.mod h1:xgkca32kuEswK7Kzkfk3Pk24WHcKPjqK4xGmDi9QqOY= github.com/danieljoos/wincred v1.2.3 h1:v7dZC2x32Ut3nEfRH+vhoZGvN72+dQ/snVXo/vMFLdQ= github.com/danieljoos/wincred v1.2.3/go.mod h1:6qqX0WNrS4RzPZ1tnroDzq9kY3fu1KwE7MRLQK4X0bs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/internal/ui/chat/attachments_picker.go b/internal/ui/chat/attachments_picker.go index dbcdc64..6379ac2 100644 --- a/internal/ui/chat/attachments_picker.go +++ b/internal/ui/chat/attachments_picker.go @@ -3,10 +3,11 @@ package chat import ( "github.com/ayn2op/discordo/internal/config" "github.com/ayn2op/discordo/internal/ui" - "github.com/ayn2op/discordo/pkg/picker" "github.com/ayn2op/tview" "github.com/ayn2op/tview/help" "github.com/ayn2op/tview/keybind" + "github.com/ayn2op/tview/list" + "github.com/ayn2op/tview/picker" ) type attachmentItem struct { @@ -39,12 +40,14 @@ func newAttachmentsPicker(cfg *config.Config, chatView *Model) *attachmentsPicke SetFooterStyle(cfg.Theme.Footer.ActiveStyle.Style) ap.SetTitle("Attachments") - ap.SetKeyMap(&picker.KeyMap{ + ap.SetKeybinds(picker.Keybinds{ Cancel: cfg.Keybinds.Picker.Cancel.Keybind, - Up: cfg.Keybinds.Picker.Up.Keybind, - Down: cfg.Keybinds.Picker.Down.Keybind, - Top: cfg.Keybinds.Picker.Top.Keybind, - Bottom: cfg.Keybinds.Picker.Bottom.Keybind, + Keybinds: list.Keybinds{ + SelectUp: cfg.Keybinds.Picker.Up.Keybind, + SelectDown: cfg.Keybinds.Picker.Down.Keybind, + SelectTop: cfg.Keybinds.Picker.Top.Keybind, + SelectBottom: cfg.Keybinds.Picker.Bottom.Keybind, + }, Select: cfg.Keybinds.Picker.Select.Keybind, }) ap.SetScrollBarVisibility(cfg.Theme.ScrollBar.Visibility.ScrollBarVisibility) diff --git a/internal/ui/chat/channels_picker.go b/internal/ui/chat/channels_picker.go index 1cb39ac..57753ca 100644 --- a/internal/ui/chat/channels_picker.go +++ b/internal/ui/chat/channels_picker.go @@ -6,10 +6,11 @@ import ( "github.com/ayn2op/discordo/internal/config" "github.com/ayn2op/discordo/internal/ui" - "github.com/ayn2op/discordo/pkg/picker" "github.com/ayn2op/tview" "github.com/ayn2op/tview/help" "github.com/ayn2op/tview/keybind" + "github.com/ayn2op/tview/list" + "github.com/ayn2op/tview/picker" "github.com/diamondburned/arikawa/v3/discord" ) @@ -40,12 +41,14 @@ func newChannelsPicker(cfg *config.Config, chatView *Model) *channelsPicker { SetTrackStyle(cfg.Theme.ScrollBar.TrackStyle.Style). SetThumbStyle(cfg.Theme.ScrollBar.ThumbStyle.Style). SetGlyphSet(cfg.Theme.ScrollBar.GlyphSet.GlyphSet)) - cp.SetKeyMap(&picker.KeyMap{ + cp.SetKeybinds(picker.Keybinds{ Cancel: cfg.Keybinds.Picker.Cancel.Keybind, - Up: cfg.Keybinds.Picker.Up.Keybind, - Down: cfg.Keybinds.Picker.Down.Keybind, - Top: cfg.Keybinds.Picker.Top.Keybind, - Bottom: cfg.Keybinds.Picker.Bottom.Keybind, + Keybinds: list.Keybinds{ + SelectUp: cfg.Keybinds.Picker.Up.Keybind, + SelectDown: cfg.Keybinds.Picker.Down.Keybind, + SelectTop: cfg.Keybinds.Picker.Top.Keybind, + SelectBottom: cfg.Keybinds.Picker.Bottom.Keybind, + }, Select: cfg.Keybinds.Picker.Select.Keybind, }) return cp diff --git a/internal/ui/chat/guilds_tree.go b/internal/ui/chat/guilds_tree.go index fd6e57e..0cc3093 100644 --- a/internal/ui/chat/guilds_tree.go +++ b/internal/ui/chat/guilds_tree.go @@ -350,7 +350,7 @@ func (gt *guildsTree) onSelected(node *tview.TreeNode) { gt.chat.messagesList.reset() gt.chat.messagesList.setTitle(*channel) gt.chat.messagesList.setMessages(messages) - gt.chat.messagesList.ScrollToEnd() + gt.chat.messagesList.ScrollBottom() hasNoPerm := channel.Type != discord.DirectMessage && channel.Type != discord.GroupDM && !gt.chat.state.HasPermissions(channel.ID, discord.PermissionSendMessages) gt.chat.messageInput.SetDisabled(hasNoPerm) diff --git a/internal/ui/chat/mentions_list.go b/internal/ui/chat/mentions_list.go index 437d811..fa0f179 100644 --- a/internal/ui/chat/mentions_list.go +++ b/internal/ui/chat/mentions_list.go @@ -23,6 +23,10 @@ func newMentionsList(cfg *config.Config) *mentionsList { m := &mentionsList{ Model: list.NewModel(), } + m.SetKeybinds(list.Keybinds{ + SelectUp: cfg.Keybinds.MentionsList.Up.Keybind, + SelectDown: cfg.Keybinds.MentionsList.Down.Keybind, + }) m.Box = ui.ConfigureBox(m.Box, &cfg.Theme) m.SetSnapToItems(true).SetTitle("Mentions") diff --git a/internal/ui/chat/message_input.go b/internal/ui/chat/message_input.go index e94ffda..cc81300 100644 --- a/internal/ui/chat/message_input.go +++ b/internal/ui/chat/message_input.go @@ -169,16 +169,7 @@ func (mi *messageInput) HandleEvent(event tview.Event) tview.Command { if mi.cfg.AutocompleteLimit > 0 { if mi.chat.GetVisible(mentionsListLayerName) { - switch { - case keybind.Matches(event, mi.cfg.Keybinds.MentionsList.Up.Keybind): - return mi.mentionsList.HandleEvent(tcell.NewEventKey(tcell.KeyUp, "", tcell.ModNone)) - case keybind.Matches(event, mi.cfg.Keybinds.MentionsList.Down.Keybind): - return mi.mentionsList.HandleEvent(tcell.NewEventKey(tcell.KeyDown, "", tcell.ModNone)) - case keybind.Matches(event, mi.cfg.Keybinds.MentionsList.Top.Keybind): - return mi.mentionsList.HandleEvent(tcell.NewEventKey(tcell.KeyHome, "", tcell.ModNone)) - case keybind.Matches(event, mi.cfg.Keybinds.MentionsList.Bottom.Keybind): - return mi.mentionsList.HandleEvent(tcell.NewEventKey(tcell.KeyEnd, "", tcell.ModNone)) - } + mi.mentionsList.HandleEvent(event) } go mi.chat.app.QueueUpdateDraw(func() { mi.tabSuggestion() }) @@ -248,7 +239,7 @@ func (mi *messageInput) send() { } mi.reset() mi.chat.messagesList.clearSelection() - mi.chat.messagesList.ScrollToEnd() + mi.chat.messagesList.ScrollBottom() } func (mi *messageInput) processText(channel *discord.Channel, src []byte) string { diff --git a/internal/ui/chat/messages_list.go b/internal/ui/chat/messages_list.go index 81499fc..2d48e74 100644 --- a/internal/ui/chat/messages_list.go +++ b/internal/ui/chat/messages_list.go @@ -95,6 +95,12 @@ func newMessagesList(cfg *config.Config, chatView *Model) *messagesList { ml.SetBuilder(ml.buildItem) ml.SetChangedFunc(ml.onRowCursorChanged) ml.SetTrackEnd(true) + ml.SetKeybinds(list.Keybinds{ + ScrollUp: cfg.Keybinds.MessagesList.ScrollUp.Keybind, + ScrollDown: cfg.Keybinds.MessagesList.ScrollDown.Keybind, + ScrollTop: cfg.Keybinds.MessagesList.ScrollTop.Keybind, + ScrollBottom: cfg.Keybinds.MessagesList.ScrollBottom.Keybind, + }) ml.SetScrollBarVisibility(cfg.Theme.ScrollBar.Visibility.ScrollBarVisibility) ml.SetScrollBar(tview.NewScrollBar(). SetTrackStyle(cfg.Theme.ScrollBar.TrackStyle.Style). @@ -842,18 +848,6 @@ func (ml *messagesList) HandleEvent(event tview.Event) tview.Command { switch event := event.(type) { case *tview.KeyEvent: switch { - case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.ScrollUp.Keybind): - ml.ScrollUp() - return nil - case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.ScrollDown.Keybind): - ml.ScrollDown() - return nil - case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.ScrollTop.Keybind): - ml.ScrollToStart() - return nil - case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.ScrollBottom.Keybind): - ml.ScrollToEnd() - return nil case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.Cancel.Keybind): ml.clearSelection() return nil @@ -896,8 +890,7 @@ func (ml *messagesList) HandleEvent(event tview.Event) tview.Command { ml.confirmDelete() return nil } - // Do not fall through to List defaults for unmatched keys. - return nil + return ml.Model.HandleEvent(event) } return ml.Model.HandleEvent(event) } diff --git a/pkg/picker/events.go b/pkg/picker/events.go deleted file mode 100644 index 9ffd4ea..0000000 --- a/pkg/picker/events.go +++ /dev/null @@ -1,34 +0,0 @@ -package picker - -import ( - "github.com/ayn2op/tview" - "github.com/gdamore/tcell/v3" -) - -type SelectedEvent struct { - tcell.EventTime - Item -} - -func newSelectedEvent(item Item) *SelectedEvent { - return &SelectedEvent{Item: item} -} - -func (m *Model) _select() tview.Command { - index := m.list.Cursor() - if index >= 0 && index < len(m.filtered) { - item := m.filtered[index] - return func() tview.Event { - return newSelectedEvent(item) - } - } - return nil -} - -type CancelEvent struct{ tcell.EventTime } - -func cancel() tview.Command { - return func() tview.Event { - return &CancelEvent{} - } -} diff --git a/pkg/picker/item.go b/pkg/picker/item.go deleted file mode 100644 index 1369210..0000000 --- a/pkg/picker/item.go +++ /dev/null @@ -1,17 +0,0 @@ -package picker - -type Item struct { - Text string - FilterText string - Reference any -} - -type Items []Item - -func (is Items) String(index int) string { - return is[index].FilterText -} - -func (is Items) Len() int { - return len(is) -} diff --git a/pkg/picker/keymap.go b/pkg/picker/keymap.go deleted file mode 100644 index 3c1db8a..0000000 --- a/pkg/picker/keymap.go +++ /dev/null @@ -1,13 +0,0 @@ -package picker - -import "github.com/ayn2op/tview/keybind" - -type KeyMap struct { - Cancel keybind.Keybind - - Up keybind.Keybind - Down keybind.Keybind - Top keybind.Keybind - Bottom keybind.Keybind - Select keybind.Keybind -} diff --git a/pkg/picker/model.go b/pkg/picker/model.go deleted file mode 100644 index 0b9d082..0000000 --- a/pkg/picker/model.go +++ /dev/null @@ -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 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 tview.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) -}