refactor(ui/chat): use tview.ScrollList for messagesList (#708)
This commit is contained in:
parent
ef0b5bfe1f
commit
2015c7d9fe
5 changed files with 172 additions and 127 deletions
4
go.mod
4
go.mod
|
|
@ -7,7 +7,7 @@ go 1.25.3
|
|||
require (
|
||||
github.com/BurntSushi/toml v1.6.0
|
||||
github.com/andybalholm/brotli v1.2.0
|
||||
github.com/ayn2op/tview v0.0.0-20260109203839-9dc13ab4188e
|
||||
github.com/ayn2op/tview v0.0.0-20260116090706-6950371b84c4
|
||||
github.com/deckarep/gosx-notifier v0.0.0-20180201035817-e127226297fb
|
||||
github.com/diamondburned/arikawa/v3 v3.6.1-0.20250928004212-a891a653eb26
|
||||
github.com/diamondburned/ningen/v3 v3.0.1-0.20250920191746-98fbd92e134d
|
||||
|
|
@ -16,7 +16,7 @@ require (
|
|||
github.com/google/go-cmp v0.6.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/klauspost/compress v1.18.2
|
||||
github.com/klauspost/compress v1.18.3
|
||||
github.com/ncruces/zenity v0.10.14
|
||||
github.com/sahilm/fuzzy v0.1.1
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
|
|
|
|||
8
go.sum
8
go.sum
|
|
@ -8,8 +8,8 @@ github.com/akavel/rsrc v0.10.2 h1:Zxm8V5eI1hW4gGaYsJQUhxpjkENuG91ki8B4zCrvEsw=
|
|||
github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
github.com/ayn2op/tview v0.0.0-20260109203839-9dc13ab4188e h1:3BbJN6zXY3AskufYkkFerQF+n42MUyeYi6SLrqBaoJk=
|
||||
github.com/ayn2op/tview v0.0.0-20260109203839-9dc13ab4188e/go.mod h1:i95d/64QBCxhpf8Q7ufI/YTNX8J0y/8sWTbwTH4tjkY=
|
||||
github.com/ayn2op/tview v0.0.0-20260116090706-6950371b84c4 h1:+CTr6AUbwBPY7TcsDFFS9xkiX7W4RgV5/HYMP8bjeF8=
|
||||
github.com/ayn2op/tview v0.0.0-20260116090706-6950371b84c4/go.mod h1:i95d/64QBCxhpf8Q7ufI/YTNX8J0y/8sWTbwTH4tjkY=
|
||||
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=
|
||||
|
|
@ -50,8 +50,8 @@ github.com/jackmordaunt/icns/v3 v3.0.1 h1:xxot6aNuGrU+lNgxz5I5H0qSeCjNKp8uTXB1j8
|
|||
github.com/jackmordaunt/icns/v3 v3.0.1/go.mod h1:5sHL59nqTd2ynTnowxB/MDQFhKNqkK8X687uKNygaSQ=
|
||||
github.com/josephspurrier/goversioninfo v1.5.0 h1:9TJtORoyf4YMoWSOo/cXFN9A/lB3PniJ91OxIH6e7Zg=
|
||||
github.com/josephspurrier/goversioninfo v1.5.0/go.mod h1:6MoTvFZ6GKJkzcdLnU5T/RGYUbHQbKpYeNP0AgQLd2o=
|
||||
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
|
||||
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=
|
||||
github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
|
||||
|
|
|
|||
|
|
@ -220,7 +220,7 @@ func (mi *messageInput) send() {
|
|||
mi.typingTimer = nil
|
||||
}
|
||||
mi.reset()
|
||||
mi.chatView.messagesList.Highlight()
|
||||
mi.chatView.messagesList.clearSelection()
|
||||
mi.chatView.messagesList.ScrollToEnd()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,10 +34,10 @@ import (
|
|||
)
|
||||
|
||||
type messagesList struct {
|
||||
*tview.TextView
|
||||
cfg *config.Config
|
||||
chatView *View
|
||||
selectedMessageID discord.MessageID
|
||||
*tview.ScrollList
|
||||
cfg *config.Config
|
||||
chatView *View
|
||||
messages []discord.Message
|
||||
|
||||
renderer *markdown.Renderer
|
||||
|
||||
|
|
@ -51,29 +51,25 @@ type messagesList struct {
|
|||
|
||||
func newMessagesList(cfg *config.Config, chatView *View) *messagesList {
|
||||
ml := &messagesList{
|
||||
TextView: tview.NewTextView(),
|
||||
cfg: cfg,
|
||||
chatView: chatView,
|
||||
renderer: markdown.NewRenderer(cfg.Theme.MessagesList),
|
||||
ScrollList: tview.NewScrollList(),
|
||||
cfg: cfg,
|
||||
chatView: chatView,
|
||||
renderer: markdown.NewRenderer(cfg.Theme.MessagesList),
|
||||
}
|
||||
|
||||
ml.Box = ui.ConfigureBox(ml.Box, &cfg.Theme)
|
||||
ml.
|
||||
SetDynamicColors(true).
|
||||
SetRegions(true).
|
||||
SetWordWrap(true).
|
||||
ScrollToEnd().
|
||||
SetHighlightedFunc(ml.onHighlighted).
|
||||
SetTitle("Messages").
|
||||
SetInputCapture(ml.onInputCapture)
|
||||
ml.SetTitle("Messages")
|
||||
ml.SetBuilder(ml.buildItem)
|
||||
ml.SetTrackEnd(true)
|
||||
ml.SetInputCapture(ml.onInputCapture)
|
||||
return ml
|
||||
}
|
||||
|
||||
func (ml *messagesList) reset() {
|
||||
ml.selectedMessageID = 0
|
||||
ml.messages = nil
|
||||
ml.
|
||||
Clear().
|
||||
Highlight().
|
||||
SetBuilder(ml.buildItem).
|
||||
SetTitle("")
|
||||
}
|
||||
|
||||
|
|
@ -87,18 +83,60 @@ func (ml *messagesList) setTitle(channel discord.Channel) {
|
|||
}
|
||||
|
||||
func (ml *messagesList) drawMessages(messages []discord.Message) {
|
||||
writer := ml.BatchWriter()
|
||||
defer writer.Close()
|
||||
for _, m := range slices.Backward(messages) {
|
||||
ml.drawMessage(writer, m)
|
||||
}
|
||||
ml.messages = slices.Grow(ml.messages[:0], len(messages))
|
||||
ml.messages = ml.messages[:len(messages)]
|
||||
copy(ml.messages, messages)
|
||||
slices.Reverse(ml.messages)
|
||||
}
|
||||
|
||||
func (ml *messagesList) drawMessage(writer io.Writer, message discord.Message) {
|
||||
// Region tags are square brackets that contain a region ID in double quotes
|
||||
// https://pkg.go.dev/github.com/ayn2op/tview#hdr-Regions_and_Highlights
|
||||
fmt.Fprintf(writer, `["%s"]`, message.ID)
|
||||
func (ml *messagesList) addMessage(message discord.Message) {
|
||||
ml.messages = append(ml.messages, message)
|
||||
}
|
||||
|
||||
func (ml *messagesList) setMessage(index int, message discord.Message) {
|
||||
if index < 0 || index >= len(ml.messages) {
|
||||
return
|
||||
}
|
||||
|
||||
ml.messages[index] = message
|
||||
}
|
||||
|
||||
func (ml *messagesList) deleteMessage(index int) {
|
||||
if index < 0 || index >= len(ml.messages) {
|
||||
return
|
||||
}
|
||||
|
||||
ml.messages = append(ml.messages[:index], ml.messages[index+1:]...)
|
||||
}
|
||||
|
||||
func (ml *messagesList) clearSelection() {
|
||||
ml.SetCursor(-1)
|
||||
}
|
||||
|
||||
func (ml *messagesList) buildItem(index int, cursor int) tview.ScrollListItem {
|
||||
if index < 0 || index >= len(ml.messages) {
|
||||
return nil
|
||||
}
|
||||
|
||||
message := ml.messages[index]
|
||||
tv := tview.NewTextView().
|
||||
SetWrap(true).
|
||||
SetWordWrap(true).
|
||||
SetDynamicColors(true).
|
||||
SetText(ml.renderMessage(message))
|
||||
if index == cursor {
|
||||
tv.SetTextStyle(tcell.StyleDefault.Reverse(true))
|
||||
}
|
||||
return tv
|
||||
}
|
||||
|
||||
func (ml *messagesList) renderMessage(message discord.Message) string {
|
||||
var b strings.Builder
|
||||
ml.writeMessage(&b, message)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (ml *messagesList) writeMessage(writer io.Writer, message discord.Message) {
|
||||
if ml.cfg.HideBlockedUsers {
|
||||
isBlocked := ml.chatView.state.UserIsBlocked(message.Author.ID)
|
||||
if isBlocked {
|
||||
|
|
@ -129,9 +167,6 @@ func (ml *messagesList) drawMessage(writer io.Writer, message discord.Message) {
|
|||
ml.drawTimestamps(writer, message.Timestamp)
|
||||
ml.drawAuthor(writer, message)
|
||||
}
|
||||
|
||||
// Tags with no region ID ([""]) don't start new regions. They can therefore be used to mark the end of a region.
|
||||
io.WriteString(writer, "[\"\"]\n")
|
||||
}
|
||||
|
||||
func (ml *messagesList) formatTimestamp(ts discord.Timestamp) string {
|
||||
|
|
@ -239,138 +274,117 @@ func (ml *messagesList) drawPinnedMessage(w io.Writer, message discord.Message)
|
|||
}
|
||||
|
||||
func (ml *messagesList) selectedMessage() (*discord.Message, error) {
|
||||
if !ml.selectedMessageID.IsValid() {
|
||||
if len(ml.messages) == 0 {
|
||||
return nil, errors.New("no messages available")
|
||||
}
|
||||
|
||||
cursor := ml.Cursor()
|
||||
if cursor == -1 || cursor >= len(ml.messages) {
|
||||
return nil, errors.New("no message is currently selected")
|
||||
}
|
||||
|
||||
selected := ml.chatView.SelectedChannel()
|
||||
if selected == nil {
|
||||
return nil, errors.New("no channel is currently selected")
|
||||
}
|
||||
|
||||
m, err := ml.chatView.state.Cabinet.Message(selected.ID, ml.selectedMessageID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve selected message: %w", err)
|
||||
}
|
||||
|
||||
return m, nil
|
||||
return &ml.messages[cursor], nil
|
||||
}
|
||||
|
||||
func (ml *messagesList) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
|
||||
switch event.Name() {
|
||||
case ml.cfg.Keys.MessagesList.ScrollUp:
|
||||
return tcell.NewEventKey(tcell.KeyUp, "", tcell.ModNone)
|
||||
ml.ScrollUp()
|
||||
return nil
|
||||
case ml.cfg.Keys.MessagesList.ScrollDown:
|
||||
return tcell.NewEventKey(tcell.KeyDown, "", tcell.ModNone)
|
||||
ml.ScrollDown()
|
||||
return nil
|
||||
case ml.cfg.Keys.MessagesList.ScrollTop:
|
||||
return tcell.NewEventKey(tcell.KeyHome, "", tcell.ModNone)
|
||||
ml.SetCursor(0)
|
||||
return nil
|
||||
case ml.cfg.Keys.MessagesList.ScrollBottom:
|
||||
return tcell.NewEventKey(tcell.KeyEnd, "", tcell.ModNone)
|
||||
ml.ScrollToEnd()
|
||||
return nil
|
||||
|
||||
case ml.cfg.Keys.MessagesList.Cancel:
|
||||
ml.selectedMessageID = 0
|
||||
ml.Highlight()
|
||||
ml.clearSelection()
|
||||
return nil
|
||||
|
||||
case ml.cfg.Keys.MessagesList.SelectPrevious, ml.cfg.Keys.MessagesList.SelectNext, ml.cfg.Keys.MessagesList.SelectFirst, ml.cfg.Keys.MessagesList.SelectLast, ml.cfg.Keys.MessagesList.SelectReply:
|
||||
ml._select(event.Name())
|
||||
return nil
|
||||
case ml.cfg.Keys.MessagesList.YankID:
|
||||
ml.yankID()
|
||||
return nil
|
||||
case ml.cfg.Keys.MessagesList.YankContent:
|
||||
ml.yankContent()
|
||||
return nil
|
||||
case ml.cfg.Keys.MessagesList.YankURL:
|
||||
ml.yankURL()
|
||||
return nil
|
||||
case ml.cfg.Keys.MessagesList.Open:
|
||||
ml.open()
|
||||
return nil
|
||||
case ml.cfg.Keys.MessagesList.Reply:
|
||||
ml.reply(false)
|
||||
return nil
|
||||
case ml.cfg.Keys.MessagesList.ReplyMention:
|
||||
ml.reply(true)
|
||||
return nil
|
||||
case ml.cfg.Keys.MessagesList.Edit:
|
||||
ml.edit()
|
||||
return nil
|
||||
case ml.cfg.Keys.MessagesList.Delete:
|
||||
ml.delete()
|
||||
return nil
|
||||
case ml.cfg.Keys.MessagesList.DeleteConfirm:
|
||||
ml.confirmDelete()
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
return event
|
||||
}
|
||||
|
||||
func (ml *messagesList) _select(name string) {
|
||||
selectedChannel := ml.chatView.SelectedChannel()
|
||||
if selectedChannel == nil {
|
||||
return
|
||||
}
|
||||
|
||||
messages, err := ml.chatView.state.Cabinet.Messages(selectedChannel.ID)
|
||||
if err != nil {
|
||||
slog.Error("failed to get messages", "err", err, "channel_id", selectedChannel.ID)
|
||||
return
|
||||
}
|
||||
messages := ml.messages
|
||||
if len(messages) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
messageIdx := slices.IndexFunc(messages, func(m discord.Message) bool {
|
||||
return m.ID == ml.selectedMessageID
|
||||
})
|
||||
// Allow "no highlight yet" to fall through and pick the latest message.
|
||||
if len(ml.GetHighlights()) != 0 && messageIdx == -1 {
|
||||
return
|
||||
}
|
||||
cursor := ml.Cursor()
|
||||
|
||||
switch name {
|
||||
case ml.cfg.Keys.MessagesList.SelectPrevious:
|
||||
// If no message is currently selected, select the latest message.
|
||||
if len(ml.GetHighlights()) == 0 {
|
||||
ml.selectedMessageID = messages[0].ID
|
||||
} else if messageIdx < len(messages)-1 {
|
||||
ml.selectedMessageID = messages[messageIdx+1].ID
|
||||
} else {
|
||||
return
|
||||
switch {
|
||||
case cursor == -1:
|
||||
cursor = len(messages) - 1
|
||||
case cursor > 0:
|
||||
cursor--
|
||||
}
|
||||
case ml.cfg.Keys.MessagesList.SelectNext:
|
||||
// If no message is currently selected, select the latest message.
|
||||
if len(ml.GetHighlights()) == 0 {
|
||||
ml.selectedMessageID = messages[0].ID
|
||||
} else if messageIdx > 0 {
|
||||
ml.selectedMessageID = messages[messageIdx-1].ID
|
||||
} else {
|
||||
return
|
||||
switch {
|
||||
case cursor == -1:
|
||||
cursor = len(messages) - 1
|
||||
case cursor < len(messages)-1:
|
||||
cursor++
|
||||
}
|
||||
case ml.cfg.Keys.MessagesList.SelectFirst:
|
||||
ml.selectedMessageID = messages[len(messages)-1].ID
|
||||
cursor = 0
|
||||
case ml.cfg.Keys.MessagesList.SelectLast:
|
||||
ml.selectedMessageID = messages[0].ID
|
||||
cursor = len(messages) - 1
|
||||
case ml.cfg.Keys.MessagesList.SelectReply:
|
||||
if ml.selectedMessageID == 0 {
|
||||
if cursor == -1 || cursor >= len(messages) {
|
||||
return
|
||||
}
|
||||
|
||||
if ref := messages[messageIdx].ReferencedMessage; ref != nil {
|
||||
if ref := messages[cursor].ReferencedMessage; ref != nil {
|
||||
refIdx := slices.IndexFunc(messages, func(m discord.Message) bool {
|
||||
return m.ID == ref.ID
|
||||
})
|
||||
if refIdx != -1 {
|
||||
ml.selectedMessageID = messages[refIdx].ID
|
||||
cursor = refIdx
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ml.Highlight(ml.selectedMessageID.String())
|
||||
ml.ScrollToHighlight()
|
||||
}
|
||||
|
||||
func (ml *messagesList) onHighlighted(added, removed, remaining []string) {
|
||||
if len(added) > 0 {
|
||||
id, err := discord.ParseSnowflake(added[0])
|
||||
if err != nil {
|
||||
slog.Error("failed to parse region id as int to use as message id", "err", err)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
ml.selectedMessageID = discord.MessageID(id)
|
||||
}
|
||||
|
||||
ml.SetCursor(cursor)
|
||||
}
|
||||
|
||||
func (ml *messagesList) yankID() {
|
||||
|
|
@ -545,17 +559,17 @@ func (ml *messagesList) openURL(url string) {
|
|||
}
|
||||
|
||||
func (ml *messagesList) reply(mention bool) {
|
||||
msg, err := ml.selectedMessage()
|
||||
message, err := ml.selectedMessage()
|
||||
if err != nil {
|
||||
slog.Error("failed to get selected message", "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
name := msg.Author.DisplayOrUsername()
|
||||
if msg.GuildID.IsValid() {
|
||||
member, err := ml.chatView.state.Cabinet.Member(msg.GuildID, msg.Author.ID)
|
||||
name := message.Author.DisplayOrUsername()
|
||||
if message.GuildID.IsValid() {
|
||||
member, err := ml.chatView.state.Cabinet.Member(message.GuildID, message.Author.ID)
|
||||
if err != nil {
|
||||
slog.Error("failed to get member from state", "guild_id", msg.GuildID, "member_id", msg.Author.ID, "err", err)
|
||||
slog.Error("failed to get member from state", "guild_id", message.GuildID, "member_id", message.Author.ID, "err", err)
|
||||
} else {
|
||||
if member.Nick != "" {
|
||||
name = member.Nick
|
||||
|
|
@ -564,7 +578,7 @@ func (ml *messagesList) reply(mention bool) {
|
|||
}
|
||||
|
||||
data := ml.chatView.messageInput.sendMessageData
|
||||
data.Reference = &discord.MessageReference{MessageID: ml.selectedMessageID}
|
||||
data.Reference = &discord.MessageReference{MessageID: message.ID}
|
||||
data.AllowedMentions = &api.AllowedMentions{RepliedUser: option.False}
|
||||
|
||||
title := "Replying to "
|
||||
|
|
@ -646,16 +660,10 @@ func (ml *messagesList) delete() {
|
|||
return
|
||||
}
|
||||
|
||||
ml.selectedMessageID = 0
|
||||
ml.Highlight()
|
||||
|
||||
if err := ml.chatView.state.MessageRemove(selected.ID, msg.ID); err != nil {
|
||||
slog.Error("failed to delete message", "channel_id", selected.ID, "message_id", msg.ID, "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
// No need to redraw messages after deletion, onMessageDelete will do
|
||||
// its work after the event returns
|
||||
}
|
||||
|
||||
func (ml *messagesList) requestGuildMembers(guildID discord.GuildID, messages []discord.Message) {
|
||||
|
|
|
|||
|
|
@ -3,10 +3,12 @@ package chat
|
|||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"slices"
|
||||
|
||||
"github.com/ayn2op/discordo/internal/http"
|
||||
"github.com/ayn2op/discordo/internal/notifications"
|
||||
"github.com/ayn2op/tview"
|
||||
"github.com/diamondburned/arikawa/v3/discord"
|
||||
"github.com/diamondburned/arikawa/v3/gateway"
|
||||
"github.com/diamondburned/arikawa/v3/session"
|
||||
"github.com/diamondburned/arikawa/v3/state"
|
||||
|
|
@ -114,8 +116,9 @@ func (v *View) onMessageCreate(message *gateway.MessageCreateEvent) {
|
|||
selectedChannel := v.SelectedChannel()
|
||||
if selectedChannel != nil && selectedChannel.ID == message.ChannelID {
|
||||
v.removeTyper(message.Author.ID)
|
||||
v.messagesList.drawMessage(v.messagesList, message.Message)
|
||||
v.app.Draw()
|
||||
v.app.QueueUpdateDraw(func() {
|
||||
v.messagesList.addMessage(message.Message)
|
||||
})
|
||||
} else {
|
||||
if err := notifications.Notify(v.state, message, v.cfg); err != nil {
|
||||
slog.Error("failed to notify", "err", err, "channel_id", message.ChannelID, "message_id", message.ID)
|
||||
|
|
@ -125,21 +128,55 @@ func (v *View) onMessageCreate(message *gateway.MessageCreateEvent) {
|
|||
|
||||
func (v *View) onMessageUpdate(message *gateway.MessageUpdateEvent) {
|
||||
if selected := v.SelectedChannel(); selected != nil && selected.ID == message.ChannelID {
|
||||
v.onMessageDelete(&gateway.MessageDeleteEvent{ID: message.ID, ChannelID: message.ChannelID, GuildID: message.GuildID})
|
||||
index := slices.IndexFunc(v.messagesList.messages, func(m discord.Message) bool {
|
||||
return m.ID == message.ID
|
||||
})
|
||||
if index < 0 {
|
||||
return
|
||||
}
|
||||
|
||||
v.app.QueueUpdateDraw(func() {
|
||||
v.messagesList.setMessage(index, message.Message)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (v *View) onMessageDelete(message *gateway.MessageDeleteEvent) {
|
||||
if selected := v.SelectedChannel(); selected != nil && selected.ID == message.ChannelID {
|
||||
messages, err := v.state.Cabinet.Messages(message.ChannelID)
|
||||
if err != nil {
|
||||
slog.Error("failed to get messages from state", "err", err, "channel_id", message.ChannelID)
|
||||
prevCursor := v.messagesList.Cursor()
|
||||
deletedIndex := slices.IndexFunc(v.messagesList.messages, func(m discord.Message) bool {
|
||||
return m.ID == message.ID
|
||||
})
|
||||
if deletedIndex < 0 {
|
||||
return
|
||||
}
|
||||
|
||||
v.messagesList.reset()
|
||||
v.messagesList.drawMessages(messages)
|
||||
v.app.Draw()
|
||||
v.app.QueueUpdateDraw(func() {
|
||||
v.messagesList.deleteMessage(deletedIndex)
|
||||
})
|
||||
|
||||
// Keep cursor stable when possible after removal.
|
||||
newCursor := prevCursor
|
||||
if prevCursor == deletedIndex {
|
||||
// Prefer previous item; fall forward if we deleted the first.
|
||||
newCursor = deletedIndex - 1
|
||||
if newCursor < 0 {
|
||||
if deletedIndex < len(v.messagesList.messages) {
|
||||
newCursor = deletedIndex
|
||||
} else {
|
||||
newCursor = -1
|
||||
}
|
||||
}
|
||||
} else if prevCursor > deletedIndex {
|
||||
// Shift back since the list shrank before the cursor.
|
||||
newCursor = prevCursor - 1
|
||||
}
|
||||
if newCursor != prevCursor {
|
||||
// Avoid redundant cursor updates if nothing changed.
|
||||
v.app.QueueUpdateDraw(func() {
|
||||
v.messagesList.SetCursor(newCursor)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue