From a58ee9991771a296906f9a975ecae8c39668eda4 Mon Sep 17 00:00:00 2001 From: ayn2op Date: Wed, 25 Jun 2025 20:59:59 -0400 Subject: [PATCH] Squash merge commands into main --- cmd/application.go | 10 +- cmd/guilds_tree.go | 6 +- cmd/message_input.go | 12 +- cmd/{messages_text.go => messages_list.go} | 318 ++++++++++----------- cmd/state.go | 8 +- internal/config/config.toml | 6 +- internal/config/keys.go | 6 +- internal/config/theme.go | 4 +- internal/markdown/renderer.go | 34 ++- 9 files changed, 201 insertions(+), 203 deletions(-) rename cmd/{messages_text.go => messages_list.go} (60%) diff --git a/cmd/application.go b/cmd/application.go index c47203b..2bb9336 100644 --- a/cmd/application.go +++ b/cmd/application.go @@ -24,7 +24,7 @@ type application struct { pages *tview.Pages flex *tview.Flex guildsTree *guildsTree - messagesText *messagesText + messagesList *messagesList messageInput *messageInput } @@ -36,7 +36,7 @@ func newApplication(cfg *config.Config) *application { pages: tview.NewPages(), flex: tview.NewFlex(), guildsTree: newGuildsTree(cfg), - messagesText: newMessagesText(cfg), + messagesList: newMessagesList(cfg), messageInput: newMessageInput(cfg), } @@ -82,7 +82,7 @@ func (a *application) init() { right := tview.NewFlex(). SetDirection(tview.FlexRow). - AddItem(a.messagesText, 0, 1, false). + AddItem(a.messagesList, 0, 1, false). AddItem(a.messageInput, 3, 1, false) // The guilds tree is always focused first at start-up. @@ -112,9 +112,9 @@ func (a *application) onFlexInputCapture(event *tcell.EventKey) *tcell.EventKey a.messageInput.removeMentionsList() a.SetFocus(app.guildsTree) return nil - case a.cfg.Keys.FocusMessagesText: + case a.cfg.Keys.FocusMessagesList: a.messageInput.removeMentionsList() - a.SetFocus(app.messagesText) + a.SetFocus(app.messagesList) return nil case a.cfg.Keys.FocusMessageInput: a.SetFocus(app.messageInput) diff --git a/cmd/guilds_tree.go b/cmd/guilds_tree.go index 1ea8960..fc92a6c 100644 --- a/cmd/guilds_tree.go +++ b/cmd/guilds_tree.go @@ -192,9 +192,9 @@ func (gt *guildsTree) onSelected(node *tview.TreeNode) { return } - app.messagesText.reset() - app.messagesText.drawMsgs(channel.ID) - app.messagesText. + app.messagesList.reset() + app.messagesList.drawMsgs(channel.ID) + app.messagesList. ScrollToEnd(). SetTitle(gt.channelToString(*channel)) diff --git a/cmd/message_input.go b/cmd/message_input.go index aea96ce..b10e7fb 100644 --- a/cmd/message_input.go +++ b/cmd/message_input.go @@ -160,8 +160,8 @@ func (mi *messageInput) send() { }() mi.reset() - app.messagesText.Highlight() - app.messagesText.ScrollToEnd() + app.messagesList.Highlight() + app.messagesList.ScrollToEnd() } func processText(cID discord.ChannelID, src []byte) string { @@ -331,10 +331,10 @@ func (mi *messageInput) searchMember(gID discord.GuildID, name string) { return } mi.lastSearch = time.Now() - app.messagesText.waitForChunkEvent() - app.messagesText.setFetchingChunk(true, 0) + app.messagesList.waitForChunkEvent() + app.messagesList.setFetchingChunk(true, 0) discordState.MemberState.SearchMember(gID, name) - mi.cache.Create(key, app.messagesText.waitForChunkEvent()) + mi.cache.Create(key, app.messagesList.waitForChunkEvent()) } func isValidUserRune(x rune) bool { @@ -352,7 +352,7 @@ func (mi *messageInput) showMentionList(col int) { l := mi.autocomplete x, _, _, _ := mi.GetInnerRect() _, y, _, _ := mi.GetRect() - _, _, maxW, maxH := app.messagesText.GetInnerRect() + _, _, maxW, maxH := app.messagesList.GetInnerRect() if t := int(mi.cfg.Theme.Autocomplete.MaxHeight); t != 0 { maxH = min(maxH, t) } diff --git a/cmd/messages_text.go b/cmd/messages_list.go similarity index 60% rename from cmd/messages_text.go rename to cmd/messages_list.go index 86b4ef1..06cb7cd 100644 --- a/cmd/messages_text.go +++ b/cmd/messages_list.go @@ -27,7 +27,7 @@ import ( "github.com/yuin/goldmark/text" ) -type messagesText struct { +type messagesList struct { *tview.TextView cfg *config.Config selectedMessageID discord.MessageID @@ -40,50 +40,50 @@ type messagesText struct { } } -func newMessagesText(cfg *config.Config) *messagesText { - mt := &messagesText{ +func newMessagesList(cfg *config.Config) *messagesList { + ml := &messagesList{ TextView: tview.NewTextView(), cfg: cfg, } - mt.Box = ui.ConfigureBox(mt.Box, &cfg.Theme) - mt. + ml.Box = ui.ConfigureBox(ml.Box, &cfg.Theme) + ml. SetDynamicColors(true). SetRegions(true). SetWordWrap(true). ScrollToEnd(). - SetHighlightedFunc(mt.onHighlighted). + SetHighlightedFunc(ml.onHighlighted). SetTextColor(tcell.ColorDefault). SetTitle("Messages"). - SetInputCapture(mt.onInputCapture) + SetInputCapture(ml.onInputCapture) - markdown.DefaultRenderer.AddOptions(renderer.WithOption("theme", cfg.Theme.MessagesText)) - return mt + markdown.DefaultRenderer.AddOptions(renderer.WithOption("theme", cfg.Theme.MessagesList)) + return ml } -func (mt *messagesText) drawMsgs(cID discord.ChannelID) { - msgs, err := discordState.Messages(cID, uint(mt.cfg.MessagesLimit)) +func (ml *messagesList) drawMsgs(cID discord.ChannelID) { + msgs, err := discordState.Messages(cID, uint(ml.cfg.MessagesLimit)) if err != nil { slog.Error("failed to get messages", "err", err, "channel_id", cID) return } - if app.cfg.Theme.MessagesText.ShowNicknames || app.cfg.Theme.MessagesText.ShowUsernameColors { + if app.cfg.Theme.MessagesList.ShowNicknames || app.cfg.Theme.MessagesList.ShowUsernameColors { if ch, _ := discordState.Cabinet.Channel(cID); ch.GuildID.IsValid() { - mt.requestGuildMembers(ch.GuildID, msgs) + ml.requestGuildMembers(ch.GuildID, msgs) } } for _, m := range slices.Backward(msgs) { - mt.createMsg(m) + ml.createMsg(m) } } -func (mt *messagesText) reset() { - mt.selectedMessageID = 0 +func (ml *messagesList) reset() { + ml.selectedMessageID = 0 app.messageInput.replyMessageID = 0 - mt. + ml. Clear(). Highlight(). SetTitle("") @@ -91,60 +91,60 @@ func (mt *messagesText) reset() { // 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 -func (mt *messagesText) startRegion(msgID discord.MessageID) { - fmt.Fprintf(mt, `["%s"]`, msgID) +func (ml *messagesList) startRegion(msgID discord.MessageID) { + fmt.Fprintf(ml, `["%s"]`, msgID) } // Tags with no region ID ([""]) don't start new regions. They can therefore be used to mark the end of a region. -func (mt *messagesText) endRegion() { - fmt.Fprint(mt, `[""]`) +func (ml *messagesList) endRegion() { + fmt.Fprint(ml, `[""]`) } -func (mt *messagesText) createMsg(msg discord.Message) { - mt.startRegion(msg.ID) - defer mt.endRegion() +func (ml *messagesList) createMsg(msg discord.Message) { + ml.startRegion(msg.ID) + defer ml.endRegion() - if mt.cfg.HideBlockedUsers { + if ml.cfg.HideBlockedUsers { isBlocked := discordState.UserIsBlocked(msg.Author.ID) if isBlocked { - io.WriteString(mt, "[:red:b]Blocked message[:-:-]") + io.WriteString(ml, "[:red:b]Blocked message[:-:-]") return } } // reset - io.WriteString(mt, "[-:-:-]") + io.WriteString(ml, "[-:-:-]") switch msg.Type { case discord.DefaultMessage: if msg.Reference != nil && msg.Reference.Type == discord.MessageReferenceTypeForward { - mt.createForwardedMsg(msg) + ml.createForwardedMsg(msg) } else { - mt.createDefaultMsg(msg) + ml.createDefaultMsg(msg) } case discord.InlinedReplyMessage: - mt.createReplyMsg(msg) + ml.createReplyMsg(msg) case discord.ChannelPinnedMessage: - fmt.Fprintf(mt, "%s pinned a message", msg.Author.DisplayOrUsername()) + fmt.Fprintf(ml, "%s pinned a message", msg.Author.DisplayOrUsername()) default: - mt.drawTimestamps(msg.Timestamp) - mt.drawAuthor(msg) + ml.drawTimestamps(msg.Timestamp) + ml.drawAuthor(msg) } - fmt.Fprintln(mt) + fmt.Fprintln(ml) } -func (mt *messagesText) formatTimestamp(ts discord.Timestamp) string { - return ts.Time().In(time.Local).Format(mt.cfg.Timestamps.Format) +func (ml *messagesList) formatTimestamp(ts discord.Timestamp) string { + return ts.Time().In(time.Local).Format(ml.cfg.Timestamps.Format) } -func (mt *messagesText) drawTimestamps(ts discord.Timestamp) { - fmt.Fprintf(mt, "[::d]%s[::D] ", mt.formatTimestamp(ts)) +func (ml *messagesList) drawTimestamps(ts discord.Timestamp) { + fmt.Fprintf(ml, "[::d]%s[::D] ", ml.formatTimestamp(ts)) } -func (mt *messagesText) drawAuthor(msg discord.Message) { +func (ml *messagesList) drawAuthor(msg discord.Message) { name := msg.Author.DisplayOrUsername() - style := mt.cfg.Theme.MessagesText.AuthorStyle + style := ml.cfg.Theme.MessagesList.AuthorStyle if msg.GuildID.IsValid() { member, err := discordState.Cabinet.Member(msg.GuildID, msg.Author.ID) @@ -153,11 +153,11 @@ func (mt *messagesText) drawAuthor(msg discord.Message) { return } - if app.cfg.Theme.MessagesText.ShowNicknames && member.Nick != "" { + if app.cfg.Theme.MessagesList.ShowNicknames && member.Nick != "" { name = member.Nick } - if app.cfg.Theme.MessagesText.ShowUsernameColors { + if app.cfg.Theme.MessagesList.ShowUsernameColors { color, ok := state.MemberColor(member, func(id discord.RoleID) *discord.Role { r, _ := discordState.Cabinet.Role(msg.GuildID, id) return r @@ -170,77 +170,77 @@ func (mt *messagesText) drawAuthor(msg discord.Message) { } fg, bg, _ := style.Decompose() - _, _ = fmt.Fprintf(mt, "[%s:%s]%s[-] ", fg.String(), bg.String(), name) + _, _ = fmt.Fprintf(ml, "[%s:%s]%s[-] ", fg.String(), bg.String(), name) } -func (mt *messagesText) drawContent(msg discord.Message) { +func (ml *messagesList) drawContent(msg discord.Message) { c := []byte(tview.Escape(msg.Content)) ast := discordmd.ParseWithMessage(c, *discordState.Cabinet, &msg, false) if app.cfg.Markdown { - markdown.DefaultRenderer.Render(mt, c, ast) + markdown.DefaultRenderer.Render(ml, c, ast) } else { - mt.Write(c) // write the content as is + ml.Write(c) // write the content as is } } -func (mt *messagesText) drawSnapshotContent(msg discord.MessageSnapshotMessage) { +func (ml *messagesList) drawSnapshotContent(msg discord.MessageSnapshotMessage) { c := []byte(tview.Escape(msg.Content)) // discordmd doesn't support MessageSnapshotMessage, so we just use write it as is. todo? - mt.Write(c) + ml.Write(c) } -func (mt *messagesText) createDefaultMsg(msg discord.Message) { - if mt.cfg.Timestamps.Enabled { - mt.drawTimestamps(msg.Timestamp) +func (ml *messagesList) createDefaultMsg(msg discord.Message) { + if ml.cfg.Timestamps.Enabled { + ml.drawTimestamps(msg.Timestamp) } - mt.drawAuthor(msg) - mt.drawContent(msg) + ml.drawAuthor(msg) + ml.drawContent(msg) if msg.EditedTimestamp.IsValid() { - io.WriteString(mt, " [::d](edited)[::D]") + io.WriteString(ml, " [::d](edited)[::D]") } for _, a := range msg.Attachments { - fmt.Fprintln(mt) + fmt.Fprintln(ml) - fg, bg, _ := mt.cfg.Theme.MessagesText.AttachmentStyle.Decompose() - if mt.cfg.ShowAttachmentLinks { - fmt.Fprintf(mt, "[%s:%s][%s]:\n%s[-]", fg, bg, a.Filename, a.URL) + fg, bg, _ := ml.cfg.Theme.MessagesList.AttachmentStyle.Decompose() + if ml.cfg.ShowAttachmentLinks { + fmt.Fprintf(ml, "[%s:%s][%s]:\n%s[-]", fg, bg, a.Filename, a.URL) } else { - fmt.Fprintf(mt, "[%s:%s][%s][-]", fg, bg, a.Filename) + fmt.Fprintf(ml, "[%s:%s][%s][-]", fg, bg, a.Filename) } } } -func (mt *messagesText) createReplyMsg(msg discord.Message) { +func (ml *messagesList) createReplyMsg(msg discord.Message) { // reply - fmt.Fprintf(mt, "[::d]%s ", mt.cfg.Theme.MessagesText.ReplyIndicator) + fmt.Fprintf(ml, "[::d]%s ", ml.cfg.Theme.MessagesList.ReplyIndicator) if refMsg := msg.ReferencedMessage; refMsg != nil { refMsg.GuildID = msg.GuildID - mt.drawAuthor(*refMsg) - mt.drawContent(*refMsg) + ml.drawAuthor(*refMsg) + ml.drawContent(*refMsg) } - io.WriteString(mt, tview.NewLine) + io.WriteString(ml, tview.NewLine) // main - mt.createDefaultMsg(msg) + ml.createDefaultMsg(msg) } -func (mt *messagesText) createForwardedMsg(msg discord.Message) { - mt.drawTimestamps(msg.Timestamp) - mt.drawAuthor(msg) - fmt.Fprintf(mt, "[::d]%s [::-]", mt.cfg.Theme.MessagesText.ForwardedIndicator) - mt.drawSnapshotContent(msg.MessageSnapshots[0].Message) - fmt.Fprintf(mt, " [::d](%s)[-:-:-] ", mt.formatTimestamp(msg.MessageSnapshots[0].Message.Timestamp)) +func (ml *messagesList) createForwardedMsg(msg discord.Message) { + ml.drawTimestamps(msg.Timestamp) + ml.drawAuthor(msg) + fmt.Fprintf(ml, "[::d]%s [::-]", ml.cfg.Theme.MessagesList.ForwardedIndicator) + ml.drawSnapshotContent(msg.MessageSnapshots[0].Message) + fmt.Fprintf(ml, " [::d](%s)[-:-:-] ", ml.formatTimestamp(msg.MessageSnapshots[0].Message.Timestamp)) } -func (mt *messagesText) selectedMsg() (*discord.Message, error) { - if !mt.selectedMessageID.IsValid() { +func (ml *messagesList) selectedMsg() (*discord.Message, error) { + if !ml.selectedMessageID.IsValid() { return nil, errors.New("no message is currently selected") } - msg, err := discordState.Cabinet.Message(app.guildsTree.selectedChannelID, mt.selectedMessageID) + msg, err := discordState.Cabinet.Message(app.guildsTree.selectedChannelID, ml.selectedMessageID) if err != nil { return nil, fmt.Errorf("could not retrieve selected message: %w", err) } @@ -248,14 +248,14 @@ func (mt *messagesText) selectedMsg() (*discord.Message, error) { return msg, nil } -func (mt *messagesText) selectedMsgIndex() (int, error) { +func (ml *messagesList) selectedMsgIndex() (int, error) { ms, err := discordState.Cabinet.Messages(app.guildsTree.selectedChannelID) if err != nil { return -1, err } for i, m := range ms { - if m.ID == mt.selectedMessageID { + if m.ID == ml.selectedMessageID { return i, nil } } @@ -263,89 +263,89 @@ func (mt *messagesText) selectedMsgIndex() (int, error) { return -1, nil } -func (mt *messagesText) onInputCapture(event *tcell.EventKey) *tcell.EventKey { +func (ml *messagesList) onInputCapture(event *tcell.EventKey) *tcell.EventKey { switch event.Name() { - case mt.cfg.Keys.MessagesText.Cancel: - mt.selectedMessageID = 0 + case ml.cfg.Keys.MessagesList.Cancel: + ml.selectedMessageID = 0 app.messageInput.replyMessageID = 0 - mt.Highlight() + ml.Highlight() - case mt.cfg.Keys.MessagesText.SelectPrevious, mt.cfg.Keys.MessagesText.SelectNext, mt.cfg.Keys.MessagesText.SelectFirst, mt.cfg.Keys.MessagesText.SelectLast, mt.cfg.Keys.MessagesText.SelectReply: - mt._select(event.Name()) - case mt.cfg.Keys.MessagesText.YankID: - mt.yankID() - case mt.cfg.Keys.MessagesText.YankContent: - mt.yankContent() - case mt.cfg.Keys.MessagesText.YankURL: - mt.yankURL() - case mt.cfg.Keys.MessagesText.Open: - mt.open() - case mt.cfg.Keys.MessagesText.Reply: - mt.reply(false) - case mt.cfg.Keys.MessagesText.ReplyMention: - mt.reply(true) - case mt.cfg.Keys.MessagesText.Delete: - mt.delete() + 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()) + case ml.cfg.Keys.MessagesList.YankID: + ml.yankID() + case ml.cfg.Keys.MessagesList.YankContent: + ml.yankContent() + case ml.cfg.Keys.MessagesList.YankURL: + ml.yankURL() + case ml.cfg.Keys.MessagesList.Open: + ml.open() + case ml.cfg.Keys.MessagesList.Reply: + ml.reply(false) + case ml.cfg.Keys.MessagesList.ReplyMention: + ml.reply(true) + case ml.cfg.Keys.MessagesList.Delete: + ml.delete() } return nil } -func (mt *messagesText) _select(name string) { +func (ml *messagesList) _select(name string) { ms, err := discordState.Cabinet.Messages(app.guildsTree.selectedChannelID) if err != nil { slog.Error("failed to get messages", "err", err, "channel_id", app.guildsTree.selectedChannelID) return } - msgIdx, err := mt.selectedMsgIndex() + msgIdx, err := ml.selectedMsgIndex() if err != nil { slog.Error("failed to get selected message", "err", err) return } switch name { - case mt.cfg.Keys.MessagesText.SelectPrevious: + case ml.cfg.Keys.MessagesList.SelectPrevious: // If no message is currently selected, select the latest message. - if len(mt.GetHighlights()) == 0 { - mt.selectedMessageID = ms[0].ID + if len(ml.GetHighlights()) == 0 { + ml.selectedMessageID = ms[0].ID } else if msgIdx < len(ms)-1 { - mt.selectedMessageID = ms[msgIdx+1].ID + ml.selectedMessageID = ms[msgIdx+1].ID } else { return } - case mt.cfg.Keys.MessagesText.SelectNext: + case ml.cfg.Keys.MessagesList.SelectNext: // If no message is currently selected, select the latest message. - if len(mt.GetHighlights()) == 0 { - mt.selectedMessageID = ms[0].ID + if len(ml.GetHighlights()) == 0 { + ml.selectedMessageID = ms[0].ID } else if msgIdx > 0 { - mt.selectedMessageID = ms[msgIdx-1].ID + ml.selectedMessageID = ms[msgIdx-1].ID } else { return } - case mt.cfg.Keys.MessagesText.SelectFirst: - mt.selectedMessageID = ms[len(ms)-1].ID - case mt.cfg.Keys.MessagesText.SelectLast: - mt.selectedMessageID = ms[0].ID - case mt.cfg.Keys.MessagesText.SelectReply: - if mt.selectedMessageID == 0 { + case ml.cfg.Keys.MessagesList.SelectFirst: + ml.selectedMessageID = ms[len(ms)-1].ID + case ml.cfg.Keys.MessagesList.SelectLast: + ml.selectedMessageID = ms[0].ID + case ml.cfg.Keys.MessagesList.SelectReply: + if ml.selectedMessageID == 0 { return } if ref := ms[msgIdx].ReferencedMessage; ref != nil { for _, m := range ms { if ref.ID == m.ID { - mt.selectedMessageID = m.ID + ml.selectedMessageID = m.ID } } } } - mt.Highlight(mt.selectedMessageID.String()) - mt.ScrollToHighlight() + ml.Highlight(ml.selectedMessageID.String()) + ml.ScrollToHighlight() } -func (mt *messagesText) onHighlighted(added, removed, remaining []string) { +func (ml *messagesList) onHighlighted(added, removed, remaining []string) { if len(added) > 0 { id, err := discord.ParseSnowflake(added[0]) if err != nil { @@ -353,12 +353,12 @@ func (mt *messagesText) onHighlighted(added, removed, remaining []string) { return } - mt.selectedMessageID = discord.MessageID(id) + ml.selectedMessageID = discord.MessageID(id) } } -func (mt *messagesText) yankID() { - msg, err := mt.selectedMsg() +func (ml *messagesList) yankID() { + msg, err := ml.selectedMsg() if err != nil { slog.Error("failed to get selected message", "err", err) return @@ -369,8 +369,8 @@ func (mt *messagesText) yankID() { } } -func (mt *messagesText) yankContent() { - msg, err := mt.selectedMsg() +func (ml *messagesList) yankContent() { + msg, err := ml.selectedMsg() if err != nil { slog.Error("failed to get selected message", "err", err) return @@ -381,8 +381,8 @@ func (mt *messagesText) yankContent() { } } -func (mt *messagesText) yankURL() { - msg, err := mt.selectedMsg() +func (ml *messagesList) yankURL() { + msg, err := ml.selectedMsg() if err != nil { slog.Error("failed to get selected message", "err", err) return @@ -393,8 +393,8 @@ func (mt *messagesText) yankURL() { } } -func (mt *messagesText) open() { - msg, err := mt.selectedMsg() +func (ml *messagesList) open() { + msg, err := ml.selectedMsg() if err != nil { slog.Error("failed to get selected message", "err", err) return @@ -416,7 +416,7 @@ func (mt *messagesText) open() { go openURL(msg.Attachments[0].URL) } } else { - mt.showAttachmentsList(urls, msg.Attachments) + ml.showAttachmentsList(urls, msg.Attachments) } } @@ -443,31 +443,31 @@ func extractURLs(content string) []string { return urls } -func (mt *messagesText) showAttachmentsList(urls []string, attachments []discord.Attachment) { +func (ml *messagesList) showAttachmentsList(urls []string, attachments []discord.Attachment) { list := tview.NewList(). SetWrapAround(true). SetHighlightFullLine(true). ShowSecondaryText(false). SetDoneFunc(func() { app.pages.RemovePage(attachmentsListPageName).SwitchToPage(flexPageName) - app.SetFocus(mt) + app.SetFocus(ml) }) list. SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { switch event.Name() { - case mt.cfg.Keys.MessagesText.SelectPrevious: + case ml.cfg.Keys.MessagesList.SelectPrevious: return tcell.NewEventKey(tcell.KeyUp, 0, tcell.ModNone) - case mt.cfg.Keys.MessagesText.SelectNext: + case ml.cfg.Keys.MessagesList.SelectNext: return tcell.NewEventKey(tcell.KeyDown, 0, tcell.ModNone) - case mt.cfg.Keys.MessagesText.SelectFirst: + case ml.cfg.Keys.MessagesList.SelectFirst: return tcell.NewEventKey(tcell.KeyHome, 0, tcell.ModNone) - case mt.cfg.Keys.MessagesText.SelectLast: + case ml.cfg.Keys.MessagesList.SelectLast: return tcell.NewEventKey(tcell.KeyEnd, 0, tcell.ModNone) } return event }) - list.Box = ui.ConfigureBox(list.Box, &mt.cfg.Theme) + list.Box = ui.ConfigureBox(list.Box, &ml.cfg.Theme) for i, a := range attachments { list.AddItem(a.Filename, "", rune('a'+i), func() { @@ -492,7 +492,7 @@ func openURL(url string) { } } -func (mt *messagesText) reply(mention bool) { +func (ml *messagesList) reply(mention bool) { var title string if mention { title += "[@] Replying to " @@ -500,7 +500,7 @@ func (mt *messagesText) reply(mention bool) { title += "Replying to " } - msg, err := mt.selectedMsg() + msg, err := ml.selectedMsg() if err != nil { slog.Error("failed to get selected message", "err", err) return @@ -515,19 +515,19 @@ func (mt *messagesText) reply(mention bool) { return } - if app.cfg.Theme.MessagesText.ShowNicknames && member.Nick != "" { + if app.cfg.Theme.MessagesList.ShowNicknames && member.Nick != "" { name = member.Nick } } title += name app.messageInput.SetTitle(title) - app.messageInput.replyMessageID = mt.selectedMessageID + app.messageInput.replyMessageID = ml.selectedMessageID app.SetFocus(app.messageInput) } -func (mt *messagesText) delete() { - msg, err := mt.selectedMsg() +func (ml *messagesList) delete() { + msg, err := ml.selectedMsg() if err != nil { slog.Error("failed to get selected message", "err", err) return @@ -554,9 +554,9 @@ func (mt *messagesText) delete() { return } - mt.selectedMessageID = 0 + ml.selectedMessageID = 0 app.messageInput.replyMessageID = 0 - mt.Highlight() + ml.Highlight() if err := discordState.MessageRemove(app.guildsTree.selectedChannelID, msg.ID); err != nil { slog.Error("failed to delete message", "err", err, "channel_id", app.guildsTree.selectedChannelID, "message_id", msg.ID) @@ -567,7 +567,7 @@ func (mt *messagesText) delete() { // its work after the event returns } -func (mt *messagesText) requestGuildMembers(gID discord.GuildID, ms []discord.Message) { +func (ml *messagesList) requestGuildMembers(gID discord.GuildID, ms []discord.Message) { var usersToFetch []discord.UserID for _, m := range ms { if member, _ := discordState.Cabinet.Member(gID, m.Author.ID); member == nil { @@ -585,37 +585,37 @@ func (mt *messagesText) requestGuildMembers(gID discord.GuildID, ms []discord.Me return } - mt.setFetchingChunk(true, 0) - mt.waitForChunkEvent() + ml.setFetchingChunk(true, 0) + ml.waitForChunkEvent() } } -func (mt *messagesText) setFetchingChunk(value bool, count uint) { - mt.fetchingMembers.mu.Lock() - defer mt.fetchingMembers.mu.Unlock() +func (ml *messagesList) setFetchingChunk(value bool, count uint) { + ml.fetchingMembers.mu.Lock() + defer ml.fetchingMembers.mu.Unlock() - if mt.fetchingMembers.value == value { + if ml.fetchingMembers.value == value { return } - mt.fetchingMembers.value = value + ml.fetchingMembers.value = value if value { - mt.fetchingMembers.done = make(chan struct{}) + ml.fetchingMembers.done = make(chan struct{}) } else { - mt.fetchingMembers.count = count - close(mt.fetchingMembers.done) + ml.fetchingMembers.count = count + close(ml.fetchingMembers.done) } } -func (mt *messagesText) waitForChunkEvent() uint { - mt.fetchingMembers.mu.Lock() - if !mt.fetchingMembers.value { - mt.fetchingMembers.mu.Unlock() +func (ml *messagesList) waitForChunkEvent() uint { + ml.fetchingMembers.mu.Lock() + if !ml.fetchingMembers.value { + ml.fetchingMembers.mu.Unlock() return 0 } - mt.fetchingMembers.mu.Unlock() + ml.fetchingMembers.mu.Unlock() - <-mt.fetchingMembers.done - return mt.fetchingMembers.count + <-ml.fetchingMembers.done + return ml.fetchingMembers.count } diff --git a/cmd/state.go b/cmd/state.go index 4cceaf2..dbeab2d 100644 --- a/cmd/state.go +++ b/cmd/state.go @@ -37,7 +37,7 @@ func openState(token string) error { discordState.AddHandler(onMessageDelete) discordState.AddHandler(func(event *gateway.GuildMembersChunkEvent) { - app.messagesText.setFetchingChunk(false, uint(len(event.Members))) + app.messagesList.setFetchingChunk(false, uint(len(event.Members))) }) discordState.AddHandler(func(event *gateway.GuildMemberRemoveEvent) { @@ -109,7 +109,7 @@ func onReady(r *gateway.ReadyEvent) { func onMessageCreate(msg *gateway.MessageCreateEvent) { if app.guildsTree.selectedChannelID.IsValid() && app.guildsTree.selectedChannelID == msg.ChannelID { - app.messagesText.createMsg(msg.Message) + app.messagesList.createMsg(msg.Message) app.Draw() } @@ -120,8 +120,8 @@ func onMessageCreate(msg *gateway.MessageCreateEvent) { func onMessageDelete(msg *gateway.MessageDeleteEvent) { if app.guildsTree.selectedChannelID == msg.ChannelID { - app.messagesText.reset() - app.messagesText.drawMsgs(msg.ChannelID) + app.messagesList.reset() + app.messagesList.drawMsgs(msg.ChannelID) app.Draw() } } diff --git a/internal/config/config.toml b/internal/config/config.toml index a792e07..1ba2e24 100644 --- a/internal/config/config.toml +++ b/internal/config/config.toml @@ -41,7 +41,7 @@ user_agent = "default" # Esc: Reset message selection or close the channel selection popup. [keys] focus_guilds_tree = "Ctrl+G" -focus_messages_text = "Ctrl+T" +focus_messages_list = "Ctrl+T" focus_message_input = "Ctrl+P" # Hide/show the guilds tree. toggle_guilds_tree = "Ctrl+B" @@ -63,7 +63,7 @@ collapse_parent_node = "Rune[-]" move_to_parent_node = "Rune[p]" # Only while focusing on sent messages -[keys.messages_text] +[keys.messages_list] select_previous = "Rune[k]" select_next = "Rune[j]" select_first = "Rune[g]" @@ -129,7 +129,7 @@ private_channel_style = { foreground = "default", background = "default" } guild_style = { foreground = "default", background = "default" } channel_style = { foreground = "default", background = "default" } -[theme.messages_text] +[theme.messages_list] # Set to false to show messages with usernames instead of nicknames show_user_nicks = true show_user_colors = true diff --git a/internal/config/keys.go b/internal/config/keys.go index 80e8d32..ff99d60 100644 --- a/internal/config/keys.go +++ b/internal/config/keys.go @@ -10,12 +10,12 @@ type ( Keys struct { FocusGuildsTree string `toml:"focus_guilds_tree"` - FocusMessagesText string `toml:"focus_messages_text"` + FocusMessagesList string `toml:"focus_messages_list"` FocusMessageInput string `toml:"focus_message_input"` ToggleGuildsTree string `toml:"toggle_guilds_tree"` GuildsTree GuildsTreeKeys `toml:"guilds_tree"` - MessagesText MessagesTextKeys `toml:"messages_text"` + MessagesList MessagesListKeys `toml:"messages_list"` MessageInput MessageInputKeys `toml:"message_input"` Autocomplete AutocompleteKeys `toml:"autocomplete"` @@ -32,7 +32,7 @@ type ( MoveToParentNode string `toml:"move_to_parent_node"` } - MessagesTextKeys struct { + MessagesListKeys struct { NavigationKeys SelectReply string `toml:"select_reply"` Reply string `toml:"reply"` diff --git a/internal/config/theme.go b/internal/config/theme.go index d65947a..c65ea9a 100644 --- a/internal/config/theme.go +++ b/internal/config/theme.go @@ -113,7 +113,7 @@ type ( ChannelStyle StyleWrapper `toml:"channel_style"` } - MessagesTextTheme struct { + MessagesListTheme struct { ShowNicknames bool `toml:"show_user_nicks"` ShowUsernameColors bool `toml:"show_user_colors"` @@ -142,7 +142,7 @@ type ( Border BorderTheme `toml:"border"` GuildsTree GuildsTreeTheme `toml:"guilds_tree"` - MessagesText MessagesTextTheme `toml:"messages_text"` + MessagesList MessagesListTheme `toml:"messages_list"` Autocomplete AutocompleteTheme `toml:"autocomplete"` } ) diff --git a/internal/markdown/renderer.go b/internal/markdown/renderer.go index 94b0883..ebd3172 100644 --- a/internal/markdown/renderer.go +++ b/internal/markdown/renderer.go @@ -1,3 +1,4 @@ +// Package markdown defines a renderer for tview style tags. package markdown import ( @@ -8,6 +9,7 @@ import ( "github.com/ayn2op/discordo/internal/config" "github.com/diamondburned/ningen/v3/discordmd" + "github.com/gdamore/tcell/v2" "github.com/yuin/goldmark/ast" gmr "github.com/yuin/goldmark/renderer" ) @@ -34,6 +36,7 @@ func (r *renderer) AddOptions(opts ...gmr.Option) { } func (r *renderer) Render(w io.Writer, source []byte, node ast.Node) error { + theme := r.config.Options["theme"].(config.MessagesListTheme) return ast.Walk(node, func(node ast.Node, entering bool) (ast.WalkStatus, error) { switch node := node.(type) { case *ast.Document: @@ -45,9 +48,9 @@ func (r *renderer) Render(w io.Writer, source []byte, node ast.Node) error { case *ast.FencedCodeBlock: r.renderFencedCodeBlock(w, node, entering, source) case *ast.AutoLink: - r.renderAutoLink(w, node, entering, source) + r.renderAutoLink(w, node, entering, source, theme.URLStyle.Style) case *ast.Link: - r.renderLink(w, node, entering) + r.renderLink(w, node, entering, theme.URLStyle.Style) case *ast.List: r.renderList(w, node, entering) case *ast.ListItem: @@ -56,9 +59,9 @@ func (r *renderer) Render(w io.Writer, source []byte, node ast.Node) error { case *discordmd.Inline: r.renderInline(w, node, entering) case *discordmd.Mention: - r.renderMention(w, node, entering) + r.renderMention(w, node, entering, theme.ShowNicknames, theme.MentionStyle.Style) case *discordmd.Emoji: - r.renderEmoji(w, node, entering) + r.renderEmoji(w, node, entering, theme.EmojiStyle.Style) } return ast.WalkContinue, nil @@ -95,10 +98,9 @@ func (r *renderer) renderFencedCodeBlock(w io.Writer, node *ast.FencedCodeBlock, } } -func (r *renderer) renderAutoLink(w io.Writer, node *ast.AutoLink, entering bool, source []byte) { +func (r *renderer) renderAutoLink(w io.Writer, node *ast.AutoLink, entering bool, source []byte, urlStyle tcell.Style) { if entering { - theme := r.config.Options["theme"].(config.MessagesTextTheme) - fg, bg, _ := theme.URLStyle.Decompose() + fg, bg, _ := urlStyle.Decompose() _, _ = fmt.Fprintf(w, "[%s:%s]", fg, bg) w.Write(node.URL(source)) } else { @@ -106,10 +108,9 @@ func (r *renderer) renderAutoLink(w io.Writer, node *ast.AutoLink, entering bool } } -func (r *renderer) renderLink(w io.Writer, node *ast.Link, entering bool) { +func (r *renderer) renderLink(w io.Writer, node *ast.Link, entering bool, urlStyle tcell.Style) { if entering { - theme := r.config.Options["theme"].(config.MessagesTextTheme) - fg, bg, _ := theme.URLStyle.Decompose() + fg, bg, _ := urlStyle.Decompose() _, _ = fmt.Fprintf(w, "[%s:%s::%s]", fg, bg, node.Destination) } else { io.WriteString(w, "[-:-::-]") @@ -189,10 +190,9 @@ func (r *renderer) renderInline(w io.Writer, node *discordmd.Inline, entering bo } } -func (r *renderer) renderMention(w io.Writer, node *discordmd.Mention, entering bool) { +func (r *renderer) renderMention(w io.Writer, node *discordmd.Mention, entering bool, showNicknames bool, mentionStyle tcell.Style) { if entering { - theme := r.config.Options["theme"].(config.MessagesTextTheme) - fg, bg, _ := theme.MentionStyle.Decompose() + fg, bg, _ := mentionStyle.Decompose() _, _ = fmt.Fprintf(w, "[%s:%s:b]", fg, bg) switch { @@ -200,8 +200,7 @@ func (r *renderer) renderMention(w io.Writer, node *discordmd.Mention, entering io.WriteString(w, "#"+node.Channel.Name) case node.GuildUser != nil: username := node.GuildUser.DisplayOrUsername() - theme := r.config.Options["theme"].(config.MessagesTextTheme) - if theme.ShowNicknames && node.GuildUser.Member != nil && node.GuildUser.Member.Nick != "" { + if showNicknames && node.GuildUser.Member != nil && node.GuildUser.Member.Nick != "" { username = node.GuildUser.Member.Nick } io.WriteString(w, "@"+username) @@ -213,10 +212,9 @@ func (r *renderer) renderMention(w io.Writer, node *discordmd.Mention, entering } } -func (r *renderer) renderEmoji(w io.Writer, node *discordmd.Emoji, entering bool) { +func (r *renderer) renderEmoji(w io.Writer, node *discordmd.Emoji, entering bool, emojiStyle tcell.Style) { if entering { - theme := r.config.Options["theme"].(config.MessagesTextTheme) - fg, bg, _ := theme.EmojiStyle.Decompose() + fg, bg, _ := emojiStyle.Decompose() fmt.Fprintf(w, "[%s:%s]", fg, bg) io.WriteString(w, ":"+node.Name+":") } else {