diff --git a/internal/ui/chat/events.go b/internal/ui/chat/events.go index ab765c4..e0032ad 100644 --- a/internal/ui/chat/events.go +++ b/internal/ui/chat/events.go @@ -9,7 +9,7 @@ import ( type LogoutEvent struct{ tcell.EventTime } -func (v *Model) logout() tview.Command { +func (m *Model) logout() tview.Command { return func() tcell.Event { return &LogoutEvent{} } @@ -17,11 +17,13 @@ func (v *Model) logout() tview.Command { type QuitEvent struct{ tcell.EventTime } -func (v *Model) closeState() tview.Command { +func (m *Model) closeState() tview.Command { return func() tcell.Event { - if err := v.CloseState(); err != nil { - slog.Error("failed to close the session", "err", err) - return tcell.NewEventError(err) + if m.state != nil { + if err := m.state.Close(); err != nil { + slog.Error("failed to close the session", "err", err) + return tcell.NewEventError(err) + } } return nil } diff --git a/internal/ui/chat/keybinds.go b/internal/ui/chat/keybinds.go index e0ea8e1..1bfa33b 100644 --- a/internal/ui/chat/keybinds.go +++ b/internal/ui/chat/keybinds.go @@ -7,59 +7,59 @@ import ( var _ help.KeyMap = (*Model)(nil) -func (v *Model) ShortHelp() []keybind.Keybind { +func (m *Model) ShortHelp() []keybind.Keybind { short := make([]keybind.Keybind, 0, 16) - if active := v.activeKeyMap(); active != nil { + if active := m.activeKeyMap(); active != nil { short = append(short, active.ShortHelp()...) } - short = append(short, v.baseShortHelp()...) + short = append(short, m.baseShortHelp()...) return short } -func (v *Model) FullHelp() [][]keybind.Keybind { +func (m *Model) FullHelp() [][]keybind.Keybind { full := make([][]keybind.Keybind, 0, 8) - if active := v.activeKeyMap(); active != nil { + if active := m.activeKeyMap(); active != nil { full = append(full, active.FullHelp()...) } - full = append(full, v.baseFullHelp()...) + full = append(full, m.baseFullHelp()...) return full } -func (v *Model) activeKeyMap() help.KeyMap { - if v.GetVisible(channelsPickerLayerName) { - return v.channelsPicker +func (m *Model) activeKeyMap() help.KeyMap { + if m.GetVisible(channelsPickerLayerName) { + return m.channelsPicker } - if v.app == nil { + if m.app == nil { return nil } - switch v.app.Focused() { - case v.guildsTree: - return v.guildsTree - case v.messagesList: - return v.messagesList - case v.messageInput: - return v.messageInput + switch m.app.Focused() { + case m.guildsTree: + return m.guildsTree + case m.messagesList: + return m.messagesList + case m.messageInput: + return m.messageInput default: return nil } } -func (v *Model) baseShortHelp() []keybind.Keybind { - cfg := v.cfg.Keybinds +func (m *Model) baseShortHelp() []keybind.Keybind { + cfg := m.cfg.Keybinds short := []keybind.Keybind{cfg.FocusGuildsTree.Keybind, cfg.FocusMessagesList.Keybind} - if !v.messageInput.GetDisabled() { + if !m.messageInput.GetDisabled() { short = append(short, cfg.FocusMessageInput.Keybind) } short = append(short, cfg.ToggleGuildsTree.Keybind, cfg.ToggleChannelsPicker.Keybind) return short } -func (v *Model) baseFullHelp() [][]keybind.Keybind { - cfg := v.cfg.Keybinds +func (m *Model) baseFullHelp() [][]keybind.Keybind { + cfg := m.cfg.Keybinds focus := []keybind.Keybind{cfg.FocusGuildsTree.Keybind, cfg.FocusMessagesList.Keybind} - if !v.messageInput.GetDisabled() { + if !m.messageInput.GetDisabled() { focus = append(focus, cfg.FocusMessageInput.Keybind) } return [][]keybind.Keybind{ diff --git a/internal/ui/chat/model.go b/internal/ui/chat/model.go index fe8e4c2..cfb81f0 100644 --- a/internal/ui/chat/model.go +++ b/internal/ui/chat/model.go @@ -81,131 +81,131 @@ func NewView(app *tview.Application, cfg *config.Config, token string) *Model { return v } -func (v *Model) SelectedChannel() *discord.Channel { - v.selectedChannelMu.RLock() - defer v.selectedChannelMu.RUnlock() - return v.selectedChannel +func (m *Model) SelectedChannel() *discord.Channel { + m.selectedChannelMu.RLock() + defer m.selectedChannelMu.RUnlock() + return m.selectedChannel } -func (v *Model) SetSelectedChannel(channel *discord.Channel) { - v.selectedChannelMu.Lock() - v.selectedChannel = channel - v.selectedChannelMu.Unlock() +func (m *Model) SetSelectedChannel(channel *discord.Channel) { + m.selectedChannelMu.Lock() + m.selectedChannel = channel + m.selectedChannelMu.Unlock() } -func (v *Model) buildLayout() { - v.Clear() - v.rightFlex.Clear() - v.mainFlex.Clear() +func (m *Model) buildLayout() { + m.Clear() + m.rightFlex.Clear() + m.mainFlex.Clear() - v.rightFlex. + m.rightFlex. SetDirection(flex.DirectionRow). - AddItem(v.messagesList, 0, 1, false). - AddItem(v.messageInput, 3, 1, false) + AddItem(m.messagesList, 0, 1, false). + AddItem(m.messageInput, 3, 1, false) // The guilds tree is always focused first at start-up. - v.mainFlex. - AddItem(v.guildsTree, 0, 1, true). - AddItem(v.rightFlex, 0, 4, false) + m.mainFlex. + AddItem(m.guildsTree, 0, 1, true). + AddItem(m.rightFlex, 0, 4, false) - v.AddLayer(v.mainFlex, layers.WithName(flexLayerName), layers.WithResize(true), layers.WithVisible(true)) - v.AddLayer(v.messageInput.mentionsList, layers.WithName(mentionsListLayerName), layers.WithResize(false), layers.WithVisible(false)) + m.AddLayer(m.mainFlex, layers.WithName(flexLayerName), layers.WithResize(true), layers.WithVisible(true)) + m.AddLayer(m.messageInput.mentionsList, layers.WithName(mentionsListLayerName), layers.WithResize(false), layers.WithVisible(false)) } -func (v *Model) togglePicker() { - if v.HasLayer(channelsPickerLayerName) { - v.closePicker() +func (m *Model) togglePicker() { + if m.HasLayer(channelsPickerLayerName) { + m.closePicker() } else { - v.openPicker() + m.openPicker() } } -func (v *Model) openPicker() { - v.AddLayer( - ui.Centered(v.channelsPicker, v.cfg.Picker.Width, v.cfg.Picker.Height), +func (m *Model) openPicker() { + m.AddLayer( + ui.Centered(m.channelsPicker, m.cfg.Picker.Width, m.cfg.Picker.Height), layers.WithName(channelsPickerLayerName), layers.WithResize(true), layers.WithVisible(true), layers.WithOverlay(), ).SendToFront(channelsPickerLayerName) - v.channelsPicker.update() + m.channelsPicker.update() } -func (v *Model) closePicker() { - v.RemoveLayer(channelsPickerLayerName) - v.channelsPicker.Update() +func (m *Model) closePicker() { + m.RemoveLayer(channelsPickerLayerName) + m.channelsPicker.Update() } -func (v *Model) toggleGuildsTree() { +func (m *Model) toggleGuildsTree() { // The guilds tree is visible if the number of items is two. - if v.mainFlex.GetItemCount() == 2 { - v.mainFlex.RemoveItem(v.guildsTree) - if v.guildsTree.HasFocus() { - v.app.SetFocus(v.mainFlex) + if m.mainFlex.GetItemCount() == 2 { + m.mainFlex.RemoveItem(m.guildsTree) + if m.guildsTree.HasFocus() { + m.app.SetFocus(m.mainFlex) } } else { - v.buildLayout() - v.app.SetFocus(v.guildsTree) + m.buildLayout() + m.app.SetFocus(m.guildsTree) } } -func (v *Model) focusGuildsTree() bool { +func (m *Model) focusGuildsTree() bool { // The guilds tree is not hidden if the number of items is two. - if v.mainFlex.GetItemCount() == 2 { - v.app.SetFocus(v.guildsTree) + if m.mainFlex.GetItemCount() == 2 { + m.app.SetFocus(m.guildsTree) return true } return false } -func (v *Model) focusMessageInput() bool { - if !v.messageInput.GetDisabled() { - v.app.SetFocus(v.messageInput) +func (m *Model) focusMessageInput() bool { + if !m.messageInput.GetDisabled() { + m.app.SetFocus(m.messageInput) return true } return false } -func (v *Model) focusPrevious() { - switch v.app.Focused() { - case v.messagesList: // Handle both a.messagesList and a.flex as well as other edge cases (if there is). - if v.focusGuildsTree() { +func (m *Model) focusPrevious() { + switch m.app.Focused() { + case m.messagesList: // Handle both a.messagesList and a.flex as well as other edge cases (if there is). + if m.focusGuildsTree() { return } fallthrough - case v.guildsTree: - if v.focusMessageInput() { + case m.guildsTree: + if m.focusMessageInput() { return } fallthrough - case v.messageInput: - v.app.SetFocus(v.messagesList) + case m.messageInput: + m.app.SetFocus(m.messagesList) } } -func (v *Model) focusNext() { - switch v.app.Focused() { - case v.messagesList: - if v.focusMessageInput() { +func (m *Model) focusNext() { + switch m.app.Focused() { + case m.messagesList: + if m.focusMessageInput() { return } fallthrough - case v.messageInput: // Handle both a.messageInput and a.flex as well as other edge cases (if there is). - if v.focusGuildsTree() { + case m.messageInput: // Handle both a.messageInput and a.flex as well as other edge cases (if there is). + if m.focusGuildsTree() { return } fallthrough - case v.guildsTree: - v.app.SetFocus(v.messagesList) + case m.guildsTree: + m.app.SetFocus(m.messagesList) } } -func (v *Model) HandleEvent(event tcell.Event) tview.Command { +func (m *Model) HandleEvent(event tcell.Event) tview.Command { switch event := event.(type) { case *tview.InitEvent: return func() tcell.Event { - if err := v.OpenState(v.token); err != nil { + if err := m.OpenState(m.token); err != nil { slog.Error("failed to open chat state", "err", err) return tcell.NewEventError(err) } @@ -213,18 +213,18 @@ func (v *Model) HandleEvent(event tcell.Event) tview.Command { } case *QuitEvent: return tview.Batch( - v.closeState(), + m.closeState(), tview.Quit(), ) case *tview.ModalDoneEvent: - if v.HasLayer(confirmModalLayerName) { - v.RemoveLayer(confirmModalLayerName) - if v.confirmModalPreviousFocus != nil { - v.app.SetFocus(v.confirmModalPreviousFocus) + if m.HasLayer(confirmModalLayerName) { + m.RemoveLayer(confirmModalLayerName) + if m.confirmModalPreviousFocus != nil { + m.app.SetFocus(m.confirmModalPreviousFocus) } - onDone := v.confirmModalDone - v.confirmModalDone = nil - v.confirmModalPreviousFocus = nil + onDone := m.confirmModalDone + m.confirmModalDone = nil + m.confirmModalPreviousFocus = nil if onDone != nil { onDone(event.ButtonLabel) } @@ -232,49 +232,49 @@ func (v *Model) HandleEvent(event tcell.Event) tview.Command { } case *tview.KeyEvent: switch { - case keybind.Matches(event, v.cfg.Keybinds.FocusGuildsTree.Keybind): - v.messageInput.removeMentionsList() - v.focusGuildsTree() + case keybind.Matches(event, m.cfg.Keybinds.FocusGuildsTree.Keybind): + m.messageInput.removeMentionsList() + m.focusGuildsTree() return nil - case keybind.Matches(event, v.cfg.Keybinds.FocusMessagesList.Keybind): - v.messageInput.removeMentionsList() - v.app.SetFocus(v.messagesList) + case keybind.Matches(event, m.cfg.Keybinds.FocusMessagesList.Keybind): + m.messageInput.removeMentionsList() + m.app.SetFocus(m.messagesList) return nil - case keybind.Matches(event, v.cfg.Keybinds.FocusMessageInput.Keybind): - v.focusMessageInput() + case keybind.Matches(event, m.cfg.Keybinds.FocusMessageInput.Keybind): + m.focusMessageInput() return nil - case keybind.Matches(event, v.cfg.Keybinds.FocusPrevious.Keybind): - v.focusPrevious() + case keybind.Matches(event, m.cfg.Keybinds.FocusPrevious.Keybind): + m.focusPrevious() return nil - case keybind.Matches(event, v.cfg.Keybinds.FocusNext.Keybind): - v.focusNext() + case keybind.Matches(event, m.cfg.Keybinds.FocusNext.Keybind): + m.focusNext() return nil - case keybind.Matches(event, v.cfg.Keybinds.Logout.Keybind): - return tview.Batch(v.closeState(), v.logout()) - case keybind.Matches(event, v.cfg.Keybinds.ToggleGuildsTree.Keybind): - v.toggleGuildsTree() + case keybind.Matches(event, m.cfg.Keybinds.Logout.Keybind): + return tview.Batch(m.closeState(), m.logout()) + case keybind.Matches(event, m.cfg.Keybinds.ToggleGuildsTree.Keybind): + m.toggleGuildsTree() return nil - case keybind.Matches(event, v.cfg.Keybinds.ToggleChannelsPicker.Keybind): - v.togglePicker() + case keybind.Matches(event, m.cfg.Keybinds.ToggleChannelsPicker.Keybind): + m.togglePicker() return nil } case *closeLayerEvent: - if v.HasLayer(event.name) { - v.HideLayer(event.name) + if m.HasLayer(event.name) { + m.HideLayer(event.name) } return nil } - return v.Layers.HandleEvent(event) + return m.Layers.HandleEvent(event) } -func (v *Model) showConfirmModal(prompt string, buttons []string, onDone func(label string)) { - v.confirmModalPreviousFocus = v.app.Focused() - v.confirmModalDone = onDone +func (m *Model) showConfirmModal(prompt string, buttons []string, onDone func(label string)) { + m.confirmModalPreviousFocus = m.app.Focused() + m.confirmModalDone = onDone modal := tview.NewModal(). SetText(prompt). AddButtons(buttons) - v. + m. AddLayer( ui.Centered(modal, 0, 0), layers.WithName(confirmModalLayerName), @@ -285,75 +285,75 @@ func (v *Model) showConfirmModal(prompt string, buttons []string, onDone func(la SendToFront(confirmModalLayerName) } -func (v *Model) onReadUpdate(event *read.UpdateEvent) { - v.app.QueueUpdateDraw(func() { +func (m *Model) onReadUpdate(event *read.UpdateEvent) { + m.app.QueueUpdateDraw(func() { // Use indexed node lookup to avoid walking the whole tree on every read // event. This runs frequently while reading/typing across channels. if event.GuildID.IsValid() { - if guildNode := v.guildsTree.findNodeByReference(event.GuildID); guildNode != nil { - v.guildsTree.setNodeLineStyle(guildNode, v.guildsTree.getGuildNodeStyle(event.GuildID)) + if guildNode := m.guildsTree.findNodeByReference(event.GuildID); guildNode != nil { + m.guildsTree.setNodeLineStyle(guildNode, m.guildsTree.getGuildNodeStyle(event.GuildID)) } } // Channel style is always updated for the target channel regardless of // whether it's in a guild or DM. - if channelNode := v.guildsTree.findNodeByReference(event.ChannelID); channelNode != nil { - v.guildsTree.setNodeLineStyle(channelNode, v.guildsTree.getChannelNodeStyle(event.ChannelID)) + if channelNode := m.guildsTree.findNodeByReference(event.ChannelID); channelNode != nil { + m.guildsTree.setNodeLineStyle(channelNode, m.guildsTree.getChannelNodeStyle(event.ChannelID)) } }) } -func (v *Model) clearTypers() { - v.typersMu.Lock() - for _, timer := range v.typers { +func (m *Model) clearTypers() { + m.typersMu.Lock() + for _, timer := range m.typers { timer.Stop() } - clear(v.typers) - v.typersMu.Unlock() - v.updateFooter() + clear(m.typers) + m.typersMu.Unlock() + m.updateFooter() } -func (v *Model) addTyper(userID discord.UserID) { - v.typersMu.Lock() - typer, ok := v.typers[userID] +func (m *Model) addTyper(userID discord.UserID) { + m.typersMu.Lock() + typer, ok := m.typers[userID] if ok { typer.Reset(typingDuration) } else { - v.typers[userID] = time.AfterFunc(typingDuration, func() { - v.removeTyper(userID) + m.typers[userID] = time.AfterFunc(typingDuration, func() { + m.removeTyper(userID) }) } - v.typersMu.Unlock() - v.updateFooter() + m.typersMu.Unlock() + m.updateFooter() } -func (v *Model) removeTyper(userID discord.UserID) { - v.typersMu.Lock() - if typer, ok := v.typers[userID]; ok { +func (m *Model) removeTyper(userID discord.UserID) { + m.typersMu.Lock() + if typer, ok := m.typers[userID]; ok { typer.Stop() - delete(v.typers, userID) + delete(m.typers, userID) } - v.typersMu.Unlock() - v.updateFooter() + m.typersMu.Unlock() + m.updateFooter() } -func (v *Model) updateFooter() { - selectedChannel := v.SelectedChannel() +func (m *Model) updateFooter() { + selectedChannel := m.SelectedChannel() if selectedChannel == nil { return } guildID := selectedChannel.GuildID - v.typersMu.RLock() - defer v.typersMu.RUnlock() + m.typersMu.RLock() + defer m.typersMu.RUnlock() var footer string - if len(v.typers) > 0 { + if len(m.typers) > 0 { var names []string - for userID := range v.typers { + for userID := range m.typers { var name string if guildID.IsValid() { - member, err := v.state.Cabinet.Member(guildID, userID) + member, err := m.state.Cabinet.Member(guildID, userID) if err != nil { slog.Error("failed to get member from state", "err", err, "guild_id", guildID, "user_id", userID) continue @@ -390,5 +390,5 @@ func (v *Model) updateFooter() { } } - go v.app.QueueUpdateDraw(func() { v.messagesList.SetFooter(footer) }) + go m.app.QueueUpdateDraw(func() { m.messagesList.SetFooter(footer) }) } diff --git a/internal/ui/chat/state.go b/internal/ui/chat/state.go index 1a317a1..8a02531 100644 --- a/internal/ui/chat/state.go +++ b/internal/ui/chat/state.go @@ -20,11 +20,11 @@ import ( "github.com/diamondburned/ningen/v3" ) -func (v *Model) OpenState(token string) error { +func (m *Model) OpenState(token string) error { identifyProps := http.IdentifyProperties() gateway.DefaultIdentity = identifyProps gateway.DefaultPresence = &gateway.UpdatePresenceCommand{ - Status: v.cfg.Status, + Status: m.cfg.Status, } id := gateway.DefaultIdentifier(token) @@ -32,38 +32,31 @@ func (v *Model) OpenState(token string) error { session := session.NewCustom(id, http.NewClient(token), handler.New()) state := state.NewFromSession(session, defaultstore.New()) - v.state = ningen.FromState(state) + m.state = ningen.FromState(state) // Handlers - v.state.AddHandler(v.onRaw) - v.state.AddHandler(v.onReady) - v.state.AddHandler(v.onMessageCreate) - v.state.AddHandler(v.onMessageUpdate) - v.state.AddHandler(v.onMessageDelete) - v.state.AddHandler(v.onReadUpdate) - v.state.AddHandler(v.onGuildMembersChunk) - v.state.AddHandler(v.onGuildMemberRemove) + m.state.AddHandler(m.onRaw) + m.state.AddHandler(m.onReady) + m.state.AddHandler(m.onMessageCreate) + m.state.AddHandler(m.onMessageUpdate) + m.state.AddHandler(m.onMessageDelete) + m.state.AddHandler(m.onReadUpdate) + m.state.AddHandler(m.onGuildMembersChunk) + m.state.AddHandler(m.onGuildMemberRemove) - if v.cfg.TypingIndicator.Receive { - v.state.AddHandler(v.onTypingStart) + if m.cfg.TypingIndicator.Receive { + m.state.AddHandler(m.onTypingStart) } - v.state.StateLog = func(err error) { + m.state.StateLog = func(err error) { slog.Error("state log", "err", err) } - v.state.OnRequest = append(v.state.OnRequest, httputil.WithHeaders(http.Headers()), v.onRequest) - return v.state.Open(context.Background()) + m.state.OnRequest = append(m.state.OnRequest, httputil.WithHeaders(http.Headers()), m.onRequest) + return m.state.Open(context.Background()) } -func (v *Model) CloseState() error { - if v.state == nil { - return nil - } - return v.state.Close() -} - -func (v *Model) onRequest(r httpdriver.Request) error { +func (m *Model) onRequest(r httpdriver.Request) error { if req, ok := r.(*httpdriver.DefaultRequest); ok { slog.Debug("new HTTP request", "method", req.Method, "url", req.URL) } @@ -71,7 +64,7 @@ func (v *Model) onRequest(r httpdriver.Request) error { return nil } -func (v *Model) onRaw(event *ws.RawEvent) { +func (m *Model) onRaw(event *ws.RawEvent) { slog.Debug( "new raw event", "code", event.OriginalCode, @@ -80,16 +73,16 @@ func (v *Model) onRaw(event *ws.RawEvent) { ) } -func (v *Model) onReady(event *gateway.ReadyEvent) { - v.app.QueueUpdateDraw(func() { +func (m *Model) onReady(event *gateway.ReadyEvent) { + m.app.QueueUpdateDraw(func() { // Rebuild indexes from scratch so reconnects and account switches do not // retain pointers to detached tree nodes. - v.guildsTree.resetNodeIndex() + m.guildsTree.resetNodeIndex() dmNode := tview.NewTreeNode("Direct Messages").SetReference(dmNode{}).SetExpandable(true).SetExpanded(false) - v.guildsTree.dmRootNode = dmNode + m.guildsTree.dmRootNode = dmNode - root := v.guildsTree. + root := m.guildsTree. GetRoot(). ClearChildren(). AddChild(dmNode) @@ -127,7 +120,7 @@ func (v *Model) onReady(event *gateway.ReadyEvent) { // Orphan guild - add directly to root in order if guildEvent, ok := guildsByID[guildID]; ok { - v.guildsTree.createGuildNode(root, guildEvent.Guild) + m.guildsTree.createGuildNode(root, guildEvent.Guild) } } @@ -135,59 +128,59 @@ func (v *Model) onReady(event *gateway.ReadyEvent) { for _, folder := range event.UserSettings.GuildFolders { if folder.ID == 0 && len(folder.GuildIDs) == 1 { if guild, ok := guildsByID[folder.GuildIDs[0]]; ok { - v.guildsTree.createGuildNode(root, guild.Guild) + m.guildsTree.createGuildNode(root, guild.Guild) } } else { - v.guildsTree.createFolderNode(folder, guildsByID) + m.guildsTree.createFolderNode(folder, guildsByID) } } - v.guildsTree.SetCurrentNode(root) - v.app.SetFocus(v.guildsTree) + m.guildsTree.SetCurrentNode(root) + m.app.SetFocus(m.guildsTree) }) } -func (v *Model) onMessageCreate(message *gateway.MessageCreateEvent) { - selectedChannel := v.SelectedChannel() +func (m *Model) onMessageCreate(message *gateway.MessageCreateEvent) { + selectedChannel := m.SelectedChannel() if selectedChannel != nil && selectedChannel.ID == message.ChannelID { - v.removeTyper(message.Author.ID) - v.app.QueueUpdateDraw(func() { - v.messagesList.addMessage(message.Message) + m.removeTyper(message.Author.ID) + m.app.QueueUpdateDraw(func() { + m.messagesList.addMessage(message.Message) }) } else { - if err := notifications.Notify(v.state, message, v.cfg); err != nil { + if err := notifications.Notify(m.state, message, m.cfg); err != nil { slog.Error("failed to notify", "err", err, "channel_id", message.ChannelID, "message_id", message.ID) } } } -func (v *Model) onMessageUpdate(message *gateway.MessageUpdateEvent) { - if selected := v.SelectedChannel(); selected != nil && selected.ID == message.ChannelID { - index := slices.IndexFunc(v.messagesList.messages, func(m discord.Message) bool { +func (m *Model) onMessageUpdate(message *gateway.MessageUpdateEvent) { + if selected := m.SelectedChannel(); selected != nil && selected.ID == message.ChannelID { + index := slices.IndexFunc(m.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) + m.app.QueueUpdateDraw(func() { + m.messagesList.setMessage(index, message.Message) }) } } -func (v *Model) onMessageDelete(message *gateway.MessageDeleteEvent) { - if selected := v.SelectedChannel(); selected != nil && selected.ID == message.ChannelID { - prevCursor := v.messagesList.Cursor() - deletedIndex := slices.IndexFunc(v.messagesList.messages, func(m discord.Message) bool { +func (m *Model) onMessageDelete(message *gateway.MessageDeleteEvent) { + if selected := m.SelectedChannel(); selected != nil && selected.ID == message.ChannelID { + prevCursor := m.messagesList.Cursor() + deletedIndex := slices.IndexFunc(m.messagesList.messages, func(m discord.Message) bool { return m.ID == message.ID }) if deletedIndex < 0 { return } - v.app.QueueUpdateDraw(func() { - v.messagesList.deleteMessage(deletedIndex) + m.app.QueueUpdateDraw(func() { + m.messagesList.deleteMessage(deletedIndex) }) // Keep cursor stable when possible after removal. @@ -196,7 +189,7 @@ func (v *Model) onMessageDelete(message *gateway.MessageDeleteEvent) { // Prefer previous item; fall forward if we deleted the first. newCursor = deletedIndex - 1 if newCursor < 0 { - if deletedIndex < len(v.messagesList.messages) { + if deletedIndex < len(m.messagesList.messages) { newCursor = deletedIndex } else { newCursor = -1 @@ -208,23 +201,23 @@ func (v *Model) onMessageDelete(message *gateway.MessageDeleteEvent) { } if newCursor != prevCursor { // Avoid redundant cursor updates if nothing changed. - v.app.QueueUpdateDraw(func() { - v.messagesList.SetCursor(newCursor) + m.app.QueueUpdateDraw(func() { + m.messagesList.SetCursor(newCursor) }) } } } -func (v *Model) onGuildMembersChunk(event *gateway.GuildMembersChunkEvent) { - v.messagesList.setFetchingChunk(false, uint(len(event.Members))) +func (m *Model) onGuildMembersChunk(event *gateway.GuildMembersChunkEvent) { + m.messagesList.setFetchingChunk(false, uint(len(event.Members))) } -func (v *Model) onGuildMemberRemove(event *gateway.GuildMemberRemoveEvent) { - v.messageInput.cache.Invalidate(event.GuildID.String()+" "+event.User.Username, v.state.MemberState.SearchLimit) +func (m *Model) onGuildMemberRemove(event *gateway.GuildMemberRemoveEvent) { + m.messageInput.cache.Invalidate(event.GuildID.String()+" "+event.User.Username, m.state.MemberState.SearchLimit) } -func (v *Model) onTypingStart(event *gateway.TypingStartEvent) { - selectedChannel := v.SelectedChannel() +func (m *Model) onTypingStart(event *gateway.TypingStartEvent) { + selectedChannel := m.SelectedChannel() if selectedChannel == nil { return } @@ -233,10 +226,10 @@ func (v *Model) onTypingStart(event *gateway.TypingStartEvent) { return } - me, _ := v.state.Cabinet.Me() + me, _ := m.state.Cabinet.Me() if event.UserID == me.ID { return } - v.addTyper(event.UserID) + m.addTyper(event.UserID) }