refactor(ui/chat): align discordo with latest tview list/picker API (#772)
This commit is contained in:
parent
6167d7d3d8
commit
993cc53609
12 changed files with 35 additions and 264 deletions
2
go.mod
2
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
|
||||
|
|
|
|||
4
go.sum
4
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=
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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{}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue