refactor(chat): extract mentions list
This commit is contained in:
parent
6c3727cb7c
commit
37a6394826
6 changed files with 159 additions and 126 deletions
6
go.mod
6
go.mod
|
|
@ -10,9 +10,9 @@ require (
|
|||
github.com/andybalholm/brotli v1.2.2
|
||||
github.com/ayn2op/arikawa/v3 v3.0.0-20260619211231-9a7da0c518de
|
||||
github.com/ayn2op/ningen/v3 v3.0.1-0.20260619214224-6a89f2062a83
|
||||
github.com/ayn2op/tview v0.0.0-20260703004135-684c7bd3e9af
|
||||
github.com/ayn2op/tview v0.0.0-20260708025551-16f85f97975e
|
||||
github.com/deckarep/gosx-notifier v0.0.0-20180201035817-e127226297fb
|
||||
github.com/gdamore/tcell/v3 v3.4.1-0.20260629051246-fe2979ab8bd1
|
||||
github.com/gdamore/tcell/v3 v3.4.1-0.20260703160154-d85b1fbd48b8
|
||||
github.com/gen2brain/beeep v0.11.2
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/google/uuid v1.6.0
|
||||
|
|
@ -59,6 +59,6 @@ require (
|
|||
golang.org/x/mobile v0.0.0-20260611195102-4dd8f1dbf5d2 // indirect
|
||||
golang.org/x/sys v0.46.0 // indirect
|
||||
golang.org/x/term v0.44.0 // indirect
|
||||
golang.org/x/text v0.38.0 // indirect
|
||||
golang.org/x/text v0.39.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
)
|
||||
|
|
|
|||
12
go.sum
12
go.sum
|
|
@ -16,8 +16,8 @@ github.com/ayn2op/arikawa/v3 v3.0.0-20260619211231-9a7da0c518de h1:j5qhTMT9t09g7
|
|||
github.com/ayn2op/arikawa/v3 v3.0.0-20260619211231-9a7da0c518de/go.mod h1:96vOKCn1rSdu7xJrCrKdwYzlgDZ5NmV9cMtPAeGHlGM=
|
||||
github.com/ayn2op/ningen/v3 v3.0.1-0.20260619214224-6a89f2062a83 h1:Dqap/VgsdiL0CNSBd+H845ZG5y2K3SJ8ji3F5oMTYIE=
|
||||
github.com/ayn2op/ningen/v3 v3.0.1-0.20260619214224-6a89f2062a83/go.mod h1:uMv/8i6wn0p4paqT2Tf1S4nn5dCoNIs4w1UWvODwvf0=
|
||||
github.com/ayn2op/tview v0.0.0-20260703004135-684c7bd3e9af h1:TbQH/N3lTftvAL9NPELlIcp8BsVm25lpp6kFF78gHJU=
|
||||
github.com/ayn2op/tview v0.0.0-20260703004135-684c7bd3e9af/go.mod h1:kBq8YDymQUJsGxUfhatCnwc1XU0z1tXjSLNpGpzcMp8=
|
||||
github.com/ayn2op/tview v0.0.0-20260708025551-16f85f97975e h1:WVf0fqBMetR5F5VoA1lMvRpSX25YUyekaNwaWXieW7c=
|
||||
github.com/ayn2op/tview v0.0.0-20260708025551-16f85f97975e/go.mod h1:kBq8YDymQUJsGxUfhatCnwc1XU0z1tXjSLNpGpzcMp8=
|
||||
github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
|
||||
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
|
||||
|
|
@ -39,8 +39,8 @@ github.com/esiqveland/notify v0.14.0 h1:LgIT/vYnziiw3d4ykaeyytc7jBUhD+Nvv3U7GIuP
|
|||
github.com/esiqveland/notify v0.14.0/go.mod h1:hesw/IRYTO0x99u1JPweAl4+5mwXJibQVUcP0Iu5ORE=
|
||||
github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
|
||||
github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
|
||||
github.com/gdamore/tcell/v3 v3.4.1-0.20260629051246-fe2979ab8bd1 h1:vWuBP/aOVrgFN81SPA8ZEOmVIMs0WQ7OZKlEPg0eZyQ=
|
||||
github.com/gdamore/tcell/v3 v3.4.1-0.20260629051246-fe2979ab8bd1/go.mod h1:Ev/2PFhL0QtVmu6XPZG9NEuITAZ6XH7i7/BF3wupBdw=
|
||||
github.com/gdamore/tcell/v3 v3.4.1-0.20260703160154-d85b1fbd48b8 h1:4RhCPcOmn5eM1eD0vdjd1l1IC8WqmGj/R0vVUVJ60XA=
|
||||
github.com/gdamore/tcell/v3 v3.4.1-0.20260703160154-d85b1fbd48b8/go.mod h1:Ev/2PFhL0QtVmu6XPZG9NEuITAZ6XH7i7/BF3wupBdw=
|
||||
github.com/gen2brain/beeep v0.11.2 h1:+KfiKQBbQCuhfJFPANZuJ+oxsSKAYNe88hIpJuyKWDA=
|
||||
github.com/gen2brain/beeep v0.11.2/go.mod h1:jQVvuwnLuwOcdctHn/uyh8horSBNJ8uGb9Cn2W4tvoc=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
|
|
@ -155,8 +155,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
||||
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
||||
golang.org/x/text v0.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus=
|
||||
golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import (
|
|||
"github.com/ayn2op/discordo/internal/config"
|
||||
"github.com/ayn2op/discordo/internal/consts"
|
||||
"github.com/ayn2op/discordo/internal/ui"
|
||||
"github.com/ayn2op/discordo/internal/ui/chat/mentionslist"
|
||||
"github.com/ayn2op/ningen/v3"
|
||||
"github.com/ayn2op/ningen/v3/discordmd"
|
||||
"github.com/ayn2op/tview"
|
||||
|
|
@ -53,7 +54,7 @@ type composer struct {
|
|||
edit bool
|
||||
sendMessageData *api.SendMessageData
|
||||
cache *cache.Cache
|
||||
mentionsList *mentionsList
|
||||
mentionsList *mentionslist.Model
|
||||
lastSearch time.Time
|
||||
|
||||
typingTimerMu sync.Mutex
|
||||
|
|
@ -71,7 +72,7 @@ func newComposer(cfg *config.Config, chat *Model) *composer {
|
|||
chat: chat,
|
||||
sendMessageData: &api.SendMessageData{},
|
||||
cache: cache.NewCache(),
|
||||
mentionsList: newMentionsList(cfg),
|
||||
mentionsList: mentionslist.NewModel(cfg),
|
||||
}
|
||||
c.Box = ui.ConfigureBox(c.Box, &cfg.Theme)
|
||||
c.
|
||||
|
|
@ -487,10 +488,10 @@ func (c *composer) tabComplete() tview.Cmd {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
if c.mentionsList.itemCount() == 0 {
|
||||
if c.mentionsList.ItemCount() == 0 {
|
||||
return nil
|
||||
}
|
||||
name, ok = c.mentionsList.selectedInsertText()
|
||||
name, ok = c.mentionsList.SelectedInsertText()
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -509,7 +510,7 @@ func (c *composer) tabSuggest() tview.Cmd {
|
|||
}
|
||||
gID := selectedChannel.GuildID
|
||||
cID := selectedChannel.ID
|
||||
c.mentionsList.clear()
|
||||
c.mentionsList.Clear()
|
||||
|
||||
var shown map[string]struct{}
|
||||
var userDone struct{}
|
||||
|
|
@ -577,16 +578,16 @@ func (c *composer) tabSuggest() tview.Cmd {
|
|||
break
|
||||
}
|
||||
}
|
||||
if c.mentionsList.itemCount() == 0 {
|
||||
if c.mentionsList.ItemCount() == 0 {
|
||||
return tview.Batch(c.stopTabCompletion(), searchCmd)
|
||||
}
|
||||
}
|
||||
|
||||
if c.mentionsList.itemCount() == 0 {
|
||||
if c.mentionsList.ItemCount() == 0 {
|
||||
return c.stopTabCompletion()
|
||||
}
|
||||
|
||||
c.mentionsList.rebuild()
|
||||
c.mentionsList.Rebuild()
|
||||
return c.showMentionsList()
|
||||
}
|
||||
|
||||
|
|
@ -687,14 +688,14 @@ func (c *composer) showMentionsList() tview.Cmd {
|
|||
if t := int(c.cfg.Theme.MentionsList.MaxHeight); t != 0 {
|
||||
maxH = min(maxH, t)
|
||||
}
|
||||
count := c.mentionsList.itemCount() + borders
|
||||
count := c.mentionsList.ItemCount() + borders
|
||||
h := min(count, maxH) + borders + c.cfg.Theme.Border.Padding[1]
|
||||
y -= h
|
||||
w := int(c.cfg.Theme.MentionsList.MinWidth)
|
||||
if w == 0 {
|
||||
w = maxW
|
||||
} else {
|
||||
w = max(w, c.mentionsList.maxDisplayWidth())
|
||||
w = max(w, c.mentionsList.MaxDisplayWidth())
|
||||
|
||||
w = min(w+borders*2, maxW)
|
||||
_, col, _, _ := c.GetCursor()
|
||||
|
|
@ -734,12 +735,12 @@ func (c *composer) addMentionMember(gID discord.GuildID, m *discord.Member) bool
|
|||
style = style.Dim(true)
|
||||
}
|
||||
|
||||
c.mentionsList.append(mentionsListItem{
|
||||
insertText: m.User.Username,
|
||||
displayText: name,
|
||||
style: style,
|
||||
c.mentionsList.Append(mentionslist.Item{
|
||||
InsertText: m.User.Username,
|
||||
DisplayText: name,
|
||||
Style: style,
|
||||
})
|
||||
return c.mentionsList.itemCount() > int(c.cfg.AutocompleteLimit)
|
||||
return c.mentionsList.ItemCount() > int(c.cfg.AutocompleteLimit)
|
||||
}
|
||||
|
||||
func (c *composer) addMentionUser(user *discord.User) {
|
||||
|
|
@ -756,10 +757,10 @@ func (c *composer) addMentionUser(user *discord.User) {
|
|||
style = style.Dim(true)
|
||||
}
|
||||
|
||||
c.mentionsList.append(mentionsListItem{
|
||||
insertText: user.Username,
|
||||
displayText: name,
|
||||
style: style,
|
||||
c.mentionsList.Append(mentionslist.Item{
|
||||
InsertText: user.Username,
|
||||
DisplayText: name,
|
||||
Style: style,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -772,7 +773,7 @@ func (c *composer) removeMentionsList() {
|
|||
|
||||
func (c *composer) stopTabCompletion() tview.Cmd {
|
||||
if c.cfg.AutocompleteLimit > 0 {
|
||||
c.mentionsList.clear()
|
||||
c.mentionsList.Clear()
|
||||
c.removeMentionsList()
|
||||
return tview.SetFocus(c)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,97 +0,0 @@
|
|||
package chat
|
||||
|
||||
import (
|
||||
"github.com/ayn2op/discordo/internal/config"
|
||||
"github.com/ayn2op/discordo/internal/ui"
|
||||
"github.com/ayn2op/tview"
|
||||
"github.com/ayn2op/tview/list"
|
||||
"github.com/gdamore/tcell/v3"
|
||||
"github.com/rivo/uniseg"
|
||||
)
|
||||
|
||||
type mentionsListItem struct {
|
||||
insertText string
|
||||
displayText string
|
||||
style tcell.Style
|
||||
}
|
||||
|
||||
type mentionsList struct {
|
||||
*list.Model
|
||||
items []mentionsListItem
|
||||
}
|
||||
|
||||
func newMentionsList(cfg *config.Config) *mentionsList {
|
||||
m := &mentionsList{
|
||||
Model: list.NewModel(),
|
||||
}
|
||||
m.SetSelectedStyle(tcell.StyleDefault.Reverse(true))
|
||||
m.SetKeybinds(list.Keybinds{
|
||||
SelectUp: cfg.Keybinds.MentionsList.SelectUp.Keybind,
|
||||
SelectDown: cfg.Keybinds.MentionsList.SelectDown.Keybind,
|
||||
SelectTop: cfg.Keybinds.MentionsList.SelectTop.Keybind,
|
||||
SelectBottom: cfg.Keybinds.MentionsList.SelectBottom.Keybind,
|
||||
})
|
||||
|
||||
m.Box = ui.ConfigureBox(m.Box, &cfg.Theme)
|
||||
m.SetSnapToItems(true).SetTitle("Mentions")
|
||||
|
||||
b := m.BorderSet()
|
||||
b.BottomLeft, b.BottomRight = b.BottomT, b.BottomT
|
||||
m.SetBorderSet(b)
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *mentionsList) append(item mentionsListItem) {
|
||||
m.items = append(m.items, item)
|
||||
}
|
||||
|
||||
func (m *mentionsList) clear() {
|
||||
m.items = nil
|
||||
m.Clear()
|
||||
}
|
||||
|
||||
func (m *mentionsList) rebuild() {
|
||||
m.SetBuilder(func(index int) list.Item {
|
||||
if index < 0 || index >= len(m.items) {
|
||||
return nil
|
||||
}
|
||||
|
||||
item := m.items[index]
|
||||
style := item.style
|
||||
line := tview.NewLine(tview.NewSegment(item.displayText, style))
|
||||
|
||||
return tview.NewTextView().
|
||||
SetScrollable(false).
|
||||
SetWrap(false).
|
||||
SetWordWrap(false).
|
||||
SetTextStyle(style).
|
||||
SetLines([]tview.Line{line})
|
||||
})
|
||||
|
||||
if len(m.items) == 0 {
|
||||
m.SetCursor(-1)
|
||||
return
|
||||
}
|
||||
m.SetCursor(0)
|
||||
}
|
||||
|
||||
func (m *mentionsList) itemCount() int {
|
||||
return len(m.items)
|
||||
}
|
||||
|
||||
func (m *mentionsList) selectedInsertText() (string, bool) {
|
||||
index := m.Cursor()
|
||||
if index < 0 || index >= len(m.items) {
|
||||
return "", false
|
||||
}
|
||||
return m.items[index].insertText, true
|
||||
}
|
||||
|
||||
func (m *mentionsList) maxDisplayWidth() int {
|
||||
width := 0
|
||||
for _, item := range m.items {
|
||||
width = max(width, uniseg.StringWidth(item.displayText))
|
||||
}
|
||||
return width
|
||||
}
|
||||
9
internal/ui/chat/mentionslist/item.go
Normal file
9
internal/ui/chat/mentionslist/item.go
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
package mentionslist
|
||||
|
||||
import "github.com/gdamore/tcell/v3"
|
||||
|
||||
type Item struct {
|
||||
InsertText string
|
||||
DisplayText string
|
||||
Style tcell.Style
|
||||
}
|
||||
120
internal/ui/chat/mentionslist/model.go
Normal file
120
internal/ui/chat/mentionslist/model.go
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
package mentionslist
|
||||
|
||||
import (
|
||||
"github.com/ayn2op/discordo/internal/config"
|
||||
"github.com/ayn2op/discordo/internal/ui"
|
||||
"github.com/ayn2op/tview"
|
||||
"github.com/ayn2op/tview/list"
|
||||
"github.com/gdamore/tcell/v3"
|
||||
"github.com/rivo/uniseg"
|
||||
)
|
||||
|
||||
type Model struct {
|
||||
list *list.Model
|
||||
items []Item
|
||||
}
|
||||
|
||||
func NewModel(cfg *config.Config) *Model {
|
||||
l := list.NewModel()
|
||||
l.Box = ui.ConfigureBox(l.Box, &cfg.Theme)
|
||||
l.
|
||||
SetSelectedStyle(tcell.StyleDefault.Reverse(true)).
|
||||
SetSnapToItems(true).
|
||||
SetTitle("Mentions")
|
||||
|
||||
kbs := cfg.Keybinds.MentionsList
|
||||
l.SetKeybinds(list.Keybinds{
|
||||
SelectUp: kbs.SelectUp.Keybind,
|
||||
SelectDown: kbs.SelectDown.Keybind,
|
||||
SelectTop: kbs.SelectTop.Keybind,
|
||||
SelectBottom: kbs.SelectBottom.Keybind,
|
||||
})
|
||||
|
||||
b := l.BorderSet()
|
||||
b.BottomLeft, b.BottomRight = b.BottomT, b.BottomT
|
||||
l.SetBorderSet(b)
|
||||
|
||||
return &Model{list: l}
|
||||
}
|
||||
|
||||
func (m *Model) Append(item Item) {
|
||||
m.items = append(m.items, item)
|
||||
}
|
||||
|
||||
func (m *Model) Clear() {
|
||||
m.items = nil
|
||||
m.list.Clear()
|
||||
}
|
||||
|
||||
func (m *Model) ItemCount() int {
|
||||
return len(m.items)
|
||||
}
|
||||
|
||||
func (m *Model) SelectedInsertText() (string, bool) {
|
||||
index := m.list.Cursor()
|
||||
if index < 0 || index >= len(m.items) {
|
||||
return "", false
|
||||
}
|
||||
return m.items[index].InsertText, true
|
||||
}
|
||||
|
||||
func (m *Model) MaxDisplayWidth() int {
|
||||
width := 0
|
||||
for _, item := range m.items {
|
||||
width = max(width, uniseg.StringWidth(item.DisplayText))
|
||||
}
|
||||
return width
|
||||
}
|
||||
|
||||
func (m *Model) Rebuild() {
|
||||
m.list.SetBuilder(func(index int) list.Item {
|
||||
if index < 0 || index >= len(m.items) {
|
||||
return nil
|
||||
}
|
||||
item := m.items[index]
|
||||
style := item.Style
|
||||
line := tview.NewLine(tview.NewSegment(item.DisplayText, style))
|
||||
return tview.NewTextView().
|
||||
SetScrollable(false).
|
||||
SetWrap(false).
|
||||
SetWordWrap(false).
|
||||
SetTextStyle(style).
|
||||
SetLines([]tview.Line{line})
|
||||
})
|
||||
|
||||
if len(m.items) == 0 {
|
||||
m.list.SetCursor(-1)
|
||||
return
|
||||
}
|
||||
m.list.SetCursor(0)
|
||||
}
|
||||
|
||||
var _ tview.Model = (*Model)(nil)
|
||||
|
||||
func (m *Model) Blur() {
|
||||
m.list.Blur()
|
||||
}
|
||||
|
||||
func (m *Model) Focus(delegate func(tview.Model)) {
|
||||
m.list.Focus(delegate)
|
||||
}
|
||||
|
||||
func (m *Model) HasFocus() bool {
|
||||
return m.list.HasFocus()
|
||||
}
|
||||
|
||||
func (m *Model) Rect() (int, int, int, int) {
|
||||
return m.list.Rect()
|
||||
}
|
||||
|
||||
func (m *Model) SetRect(x int, y int, width int, height int) {
|
||||
m.list.SetRect(x, y, width, height)
|
||||
}
|
||||
|
||||
func (m *Model) Update(msg tview.Msg) tview.Cmd {
|
||||
return m.list.Update(msg)
|
||||
}
|
||||
|
||||
func (m *Model) View(screen tcell.Screen) {
|
||||
m.list.View(screen)
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue