Squash merge commands into main

This commit is contained in:
ayn2op 2025-06-25 20:59:59 -04:00
parent bbc4eeab4a
commit a58ee99917
9 changed files with 201 additions and 203 deletions

View file

@ -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)

View file

@ -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))

View file

@ -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)
}

View file

@ -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
}

View file

@ -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()
}
}

View file

@ -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

View file

@ -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"`

View file

@ -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"`
}
)

View file

@ -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 {