1
0
Fork 0
mirror of https://github.com/diamondburned/cchat-gtk.git synced 2024-10-31 19:44:23 +00:00

bumped discord; partial message reference support

This commit is contained in:
diamondburned 2020-12-30 19:00:00 -08:00
parent 744f59cf38
commit c8f5446710
10 changed files with 192 additions and 104 deletions

4
go.mod
View file

@ -4,8 +4,8 @@ go 1.14
replace github.com/gotk3/gotk3 => github.com/diamondburned/gotk3 v0.0.0-20201230071527-a77c32eb3876 replace github.com/gotk3/gotk3 => github.com/diamondburned/gotk3 v0.0.0-20201230071527-a77c32eb3876
// replace github.com/diamondburned/gotk3-tcmalloc => ../../gotk3-tcmalloc
// replace github.com/diamondburned/cchat-discord => ../cchat-discord // replace github.com/diamondburned/cchat-discord => ../cchat-discord
// replace github.com/diamondburned/gotk3-tcmalloc => ../../gotk3-tcmalloc
// replace github.com/diamondburned/ningen/v2 => ../../ningen // replace github.com/diamondburned/ningen/v2 => ../../ningen
// replace github.com/diamondburned/arikawa/v2 => ../../arikawa // replace github.com/diamondburned/arikawa/v2 => ../../arikawa
@ -13,7 +13,7 @@ require (
github.com/Xuanwo/go-locale v1.0.0 github.com/Xuanwo/go-locale v1.0.0
github.com/alecthomas/chroma v0.7.3 github.com/alecthomas/chroma v0.7.3
github.com/diamondburned/cchat v0.3.15 github.com/diamondburned/cchat v0.3.15
github.com/diamondburned/cchat-discord v0.0.0-20201227035212-6beff5225092 github.com/diamondburned/cchat-discord v0.0.0-20201231025836-96e97aa11705
github.com/diamondburned/cchat-mock v0.0.0-20201115033644-df8d1b10f9db github.com/diamondburned/cchat-mock v0.0.0-20201115033644-df8d1b10f9db
github.com/diamondburned/gspell v0.0.0-20201229064336-e43698fd5828 github.com/diamondburned/gspell v0.0.0-20201229064336-e43698fd5828
github.com/diamondburned/handy v0.0.0-20201229063418-ec23c1370374 github.com/diamondburned/handy v0.0.0-20201229063418-ec23c1370374

2
go.sum
View file

@ -66,6 +66,8 @@ github.com/diamondburned/cchat-discord v0.0.0-20201227023505-c4e360010fb8 h1:eyK
github.com/diamondburned/cchat-discord v0.0.0-20201227023505-c4e360010fb8/go.mod h1:i3y8dyAFrtigpGOwunBdoJK/phwt9Gp/wfpVJb4imV0= github.com/diamondburned/cchat-discord v0.0.0-20201227023505-c4e360010fb8/go.mod h1:i3y8dyAFrtigpGOwunBdoJK/phwt9Gp/wfpVJb4imV0=
github.com/diamondburned/cchat-discord v0.0.0-20201227035212-6beff5225092 h1:oxY7APUclLgaWjaTK++7kHBdl0GdVyqOvHQv68TcpHw= github.com/diamondburned/cchat-discord v0.0.0-20201227035212-6beff5225092 h1:oxY7APUclLgaWjaTK++7kHBdl0GdVyqOvHQv68TcpHw=
github.com/diamondburned/cchat-discord v0.0.0-20201227035212-6beff5225092/go.mod h1:rFBGZYLq0g6Pb/WGN/K0++kXrhCYlQQ1nc2FX4r8CO0= github.com/diamondburned/cchat-discord v0.0.0-20201227035212-6beff5225092/go.mod h1:rFBGZYLq0g6Pb/WGN/K0++kXrhCYlQQ1nc2FX4r8CO0=
github.com/diamondburned/cchat-discord v0.0.0-20201231025836-96e97aa11705 h1:g0hwnUpeJ3yo7WaVZjWBQw875tnKVjCz4YofnamE9Fg=
github.com/diamondburned/cchat-discord v0.0.0-20201231025836-96e97aa11705/go.mod h1:rFBGZYLq0g6Pb/WGN/K0++kXrhCYlQQ1nc2FX4r8CO0=
github.com/diamondburned/cchat-mock v0.0.0-20201115033644-df8d1b10f9db h1:VQI2PdbsdsRJ7d669kp35GbCUO44KZ0Xfqdu4o/oqVg= github.com/diamondburned/cchat-mock v0.0.0-20201115033644-df8d1b10f9db h1:VQI2PdbsdsRJ7d669kp35GbCUO44KZ0Xfqdu4o/oqVg=
github.com/diamondburned/cchat-mock v0.0.0-20201115033644-df8d1b10f9db/go.mod h1:M87kjNzWVPlkZycFNzpGPKQXzkHNnZphuwMf3E9ckgc= github.com/diamondburned/cchat-mock v0.0.0-20201115033644-df8d1b10f9db/go.mod h1:M87kjNzWVPlkZycFNzpGPKQXzkHNnZphuwMf3E9ckgc=
github.com/diamondburned/gotk3 v0.0.0-20201209182406-e7291341a091 h1:lQpSWzbi3rQf66aMSip/rIypasIFwqCqF0Wfn5og6gw= github.com/diamondburned/gotk3 v0.0.0-20201209182406-e7291341a091 h1:lQpSWzbi3rQf66aMSip/rIypasIFwqCqF0Wfn5og6gw=

View file

@ -1,9 +1,6 @@
package cozy package cozy
import ( import (
"context"
"runtime/pprof"
"github.com/diamondburned/cchat" "github.com/diamondburned/cchat"
"github.com/diamondburned/cchat-gtk/internal/gts" "github.com/diamondburned/cchat-gtk/internal/gts"
"github.com/diamondburned/cchat-gtk/internal/ui/messages/container" "github.com/diamondburned/cchat-gtk/internal/ui/messages/container"
@ -85,7 +82,7 @@ func (c *Container) NewMessage(msg cchat.MessageCreate) container.GridMessage {
func (c *Container) NewPresendMessage(msg input.PresendMessage) container.PresendGridMessage { func (c *Container) NewPresendMessage(msg input.PresendMessage) container.PresendGridMessage {
// We can do the check here since we're never using NewPresendMessage for // We can do the check here since we're never using NewPresendMessage for
// backlog messages. // backlog messages.
if c.lastMessageIsAuthor(msg.AuthorID(), 0) { if c.lastMessageIsAuthor(msg.AuthorID(), msg.Author().String(), 0) {
return NewCollapsedSendingMessage(msg) return NewCollapsedSendingMessage(msg)
} }
@ -123,18 +120,20 @@ func (c *Container) reuseAvatar(authorID, avatarURL string, full *FullMessage) {
} }
} }
func (c *Container) lastMessageIsAuthor(id string, offset int) bool { func (c *Container) lastMessageIsAuthor(id cchat.ID, name string, offset int) bool {
// Get the offfsetth message from last. // Get the offfsetth message from last.
var last = c.GridStore.NthMessage((c.GridStore.MessagesLen() - 1) + offset) var last = c.GridStore.NthMessage((c.GridStore.MessagesLen() - 1) + offset)
return last != nil && last.AuthorID() == id return gridMessageIsAuthor(last, id, name)
} }
var createMessageLabel = pprof.Labels("cozy", "createMessage") func gridMessageIsAuthor(gridMsg container.GridMessage, id cchat.ID, name string) bool {
return gridMsg != nil &&
gridMsg.AuthorID() == id &&
gridMsg.AuthorName() == name
}
func (c *Container) CreateMessage(msg cchat.MessageCreate) { func (c *Container) CreateMessage(msg cchat.MessageCreate) {
gts.ExecAsync(func() { gts.ExecAsync(func() {
pprof.Do(context.Background(), createMessageLabel, func(context.Context) {
// Create the message in the parent's handler. This handler will also // Create the message in the parent's handler. This handler will also
// wipe old messages. // wipe old messages.
c.GridContainer.CreateMessageUnsafe(msg) c.GridContainer.CreateMessageUnsafe(msg)
@ -151,7 +150,8 @@ func (c *Container) CreateMessage(msg cchat.MessageCreate) {
// Should we collapse this message? Yes, if the current message is // Should we collapse this message? Yes, if the current message is
// inserted at the end and its author is the same as the last author. // inserted at the end and its author is the same as the last author.
case c.GridContainer.LastMessage().ID(): case c.GridContainer.LastMessage().ID():
if c.lastMessageIsAuthor(msg.Author().ID(), -1) { author := msg.Author()
if c.lastMessageIsAuthor(author.ID(), author.Name().String(), -1) {
c.compact(c.GridContainer.LastMessage()) c.compact(c.GridContainer.LastMessage())
} }
@ -159,17 +159,13 @@ func (c *Container) CreateMessage(msg cchat.MessageCreate) {
// second message. // second message.
case c.GridContainer.FirstMessage().ID(): case c.GridContainer.FirstMessage().ID():
if sec := c.NthMessage(1); sec != nil { if sec := c.NthMessage(1); sec != nil {
// If the author isn't the same, then ignore.
if sec.AuthorID() != msg.Author().ID() {
return
}
// The author is the same; collapse. // The author is the same; collapse.
author := msg.Author()
if gridMessageIsAuthor(sec, author.ID(), author.Name().String()) {
c.compact(sec) c.compact(sec)
} }
} }
}
})
}) })
} }

View file

@ -34,6 +34,8 @@ func WrapCollapsedMessage(gc *message.GenericContainer) *CollapsedMessage {
// Set Content's padding accordingly to FullMessage's main box. // Set Content's padding accordingly to FullMessage's main box.
gc.Content.ToWidget().SetMarginEnd(container.ColumnSpacing * 2) gc.Content.ToWidget().SetMarginEnd(container.ColumnSpacing * 2)
gc.Username.SetMaxWidthChars(30)
return &CollapsedMessage{ return &CollapsedMessage{
GenericContainer: gc, GenericContainer: gc,
} }

View file

@ -78,6 +78,8 @@ func WrapFullMessage(gc *message.GenericContainer) *FullMessage {
gc.Timestamp.SetVAlign(gtk.ALIGN_END) // bottom-align gc.Timestamp.SetVAlign(gtk.ALIGN_END) // bottom-align
gc.Timestamp.SetMarginStart(0) // clear margins gc.Timestamp.SetMarginStart(0) // clear margins
gc.Username.SetMaxWidthChars(75)
// Attach the class and CSS for the left avatar. // Attach the class and CSS for the left avatar.
avatarCSS(avatar) avatarCSS(avatar)

View file

@ -364,6 +364,10 @@ func NewMember(member cchat.ListMember) *Member {
return m return m
} }
var noMentionLinks = markup.RenderConfig{
NoMentionLinks: true,
}
func (m *Member) Update(member cchat.ListMember) { func (m *Member) Update(member cchat.ListMember) {
m.ListBoxRow.SetName(member.Name().Content) m.ListBoxRow.SetName(member.Name().Content)
@ -371,7 +375,7 @@ func (m *Member) Update(member cchat.ListMember) {
m.Avatar.AsyncSetIconer(iconer, "Failed to get member list icon") m.Avatar.AsyncSetIconer(iconer, "Failed to get member list icon")
} }
m.output = markup.RenderCmplxWithConfig(member.Name(), markup.NoMentionLinks) m.output = markup.RenderCmplxWithConfig(member.Name(), noMentionLinks)
txt := strings.Builder{} txt := strings.Builder{}
txt.WriteString(fmt.Sprintf( txt.WriteString(fmt.Sprintf(
`<span color="#%06X">●</span> %s`, `<span color="#%06X">●</span> %s`,

View file

@ -19,6 +19,7 @@ type Container interface {
ID() string ID() string
Time() time.Time Time() time.Time
AuthorID() string AuthorID() string
AuthorName() string
AvatarURL() string // avatar AvatarURL() string // avatar
Nonce() string Nonce() string
@ -50,6 +51,7 @@ type GenericContainer struct {
id string id string
time time.Time time time.Time
authorID string authorID string
authorName string
avatarURL string // avatar avatarURL string // avatar
nonce string nonce string
@ -94,7 +96,6 @@ func NewEmptyContainer() *GenericContainer {
ts.Show() ts.Show()
user := labeluri.NewLabel(text.Rich{}) user := labeluri.NewLabel(text.Rich{})
user.SetMaxWidthChars(35)
user.SetLineWrap(true) user.SetLineWrap(true)
user.SetLineWrapMode(pango.WRAP_WORD_CHAR) user.SetLineWrapMode(pango.WRAP_WORD_CHAR)
user.SetXAlign(1) // right align user.SetXAlign(1) // right align
@ -168,6 +169,10 @@ func (m *GenericContainer) AuthorID() string {
return m.authorID return m.authorID
} }
func (m *GenericContainer) AuthorName() string {
return m.authorName
}
func (m *GenericContainer) AvatarURL() string { func (m *GenericContainer) AvatarURL() string {
return m.avatarURL return m.avatarURL
} }
@ -189,8 +194,11 @@ func (m *GenericContainer) UpdateAuthor(author cchat.Author) {
} }
func (m *GenericContainer) UpdateAuthorName(name text.Rich) { func (m *GenericContainer) UpdateAuthorName(name text.Rich) {
var out = markup.RenderCmplxWithConfig(name, markup.NoMentionLinks) cfg := markup.RenderConfig{}
m.Username.SetOutput(out) cfg.SetForegroundAnchor(m.ContentBody)
m.authorName = name.String()
m.Username.SetOutput(markup.RenderCmplxWithConfig(name, cfg))
} }
func (m *GenericContainer) UpdateContent(content text.Rich, edited bool) { func (m *GenericContainer) UpdateContent(content text.Rich, edited bool) {

View file

@ -1,6 +1,7 @@
package attrmap package attrmap
import ( import (
"bytes"
"fmt" "fmt"
"html" "html"
"sort" "sort"
@ -10,15 +11,15 @@ import (
) )
type AppendMap struct { type AppendMap struct {
appended map[int]string // for opening tags appended map[int][]byte // for opening tags
prepended map[int]string // for closing tags prepended map[int][]byte // for closing tags
indices []int indices []int
} }
func NewAppendedMap() AppendMap { func NewAppendedMap() AppendMap {
return AppendMap{ return AppendMap{
appended: map[int]string{}, appended: map[int][]byte{},
prepended: map[int]string{}, prepended: map[int][]byte{},
indices: []int{}, indices: []int{},
} }
} }
@ -40,7 +41,7 @@ func (a *AppendMap) Anchor(start, end int, href string) {
// AnchorNU makes a new <a> tag without underlines and colors. // AnchorNU makes a new <a> tag without underlines and colors.
func (a *AppendMap) AnchorNU(start, end int, href string) { func (a *AppendMap) AnchorNU(start, end int, href string) {
a.Openf(start, `<a href="`+html.EscapeString(href)+`%s">`) a.Openf(start, `<a href="`+html.EscapeString(href)+`">`)
a.Close(end, "</a>") a.Close(end, "</a>")
// a.Anchor(start, end, href) // a.Anchor(start, end, href)
a.Span(start, end, `underline="none"`) a.Span(start, end, `underline="none"`)
@ -63,7 +64,7 @@ func (a *AppendMap) Pad(start, end int) {
} }
} }
func posHaveSpace(tags map[int]string, pos int) bool { func posHaveSpace(tags map[int][]byte, pos int) bool {
tg, ok := tags[pos] tg, ok := tags[pos]
if !ok || len(tg) == 0 { if !ok || len(tg) == 0 {
return false return false
@ -78,7 +79,7 @@ func posHaveSpace(tags map[int]string, pos int) bool {
} }
// Check spaces mid-tag. This works because strings are always escaped. // Check spaces mid-tag. This works because strings are always escaped.
return strings.Contains(tg, "> <") return bytes.Contains(tg, []byte("> <"))
} }
func (a *AppendMap) Pair(start, end int, open, close string) { func (a *AppendMap) Pair(start, end int, open, close string) {
@ -92,32 +93,31 @@ func (a *AppendMap) Openf(ind int, f string, argv ...interface{}) {
func (a *AppendMap) Open(ind int, attr string) { func (a *AppendMap) Open(ind int, attr string) {
if str, ok := a.appended[ind]; ok { if str, ok := a.appended[ind]; ok {
a.appended[ind] = str + attr // append a.appended[ind] = append(str, []byte(attr)...) // append
return return
} }
a.appended[ind] = attr a.appended[ind] = []byte(attr)
a.appendIndex(ind) a.appendIndex(ind)
} }
func (a *AppendMap) Close(ind int, attr string) { func (a *AppendMap) Close(ind int, attr string) {
if str, ok := a.prepended[ind]; ok { if str, ok := a.prepended[ind]; ok {
a.prepended[ind] = attr + str // prepend a.prepended[ind] = append([]byte(attr), str...) // prepend
return return
} }
a.prepended[ind] = attr a.prepended[ind] = []byte(attr)
a.appendIndex(ind) a.appendIndex(ind)
} }
func (a AppendMap) Get(ind int) (tags string) { func (a AppendMap) Get(ind int) (tags string) {
if t, ok := a.appended[ind]; ok { appended := a.appended[ind]
tags += t prepended := a.prepended[ind]
}
if t, ok := a.prepended[ind]; ok { // Borrowing appended's backing array to add prepended is probably fine, as
tags += t // the length of the actual appended slice is going to stay the same.
} return string(append(appended, prepended...))
return
} }
func (a *AppendMap) Finalize(strlen int) []int { func (a *AppendMap) Finalize(strlen int) []int {

View file

@ -1,3 +1,4 @@
// Package hl provides a syntax highlighted renderer for the markup API.
package hl package hl
import ( import (

View file

@ -9,10 +9,12 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
"github.com/diamondburned/cchat-gtk/internal/ui/rich/parser/attrmap" "github.com/diamondburned/cchat-gtk/internal/ui/rich/parser/attrmap"
"github.com/diamondburned/cchat-gtk/internal/ui/rich/parser/hl" "github.com/diamondburned/cchat-gtk/internal/ui/rich/parser/hl"
"github.com/diamondburned/cchat/text" "github.com/diamondburned/cchat/text"
"github.com/diamondburned/imgutil" "github.com/diamondburned/imgutil"
"github.com/gotk3/gotk3/gtk"
) )
// Hyphenate controls whether or not texts should have hyphens on wrap. // Hyphenate controls whether or not texts should have hyphens on wrap.
@ -27,6 +29,7 @@ type RenderOutput struct {
Markup string Markup string
Input string // useless to keep parts, as Go will keep all alive anyway Input string // useless to keep parts, as Go will keep all alive anyway
Mentions []MentionSegment Mentions []MentionSegment
References []ReferenceSegment
} }
// MentionSegment is a type that satisfies both Segment and Mentioner. // MentionSegment is a type that satisfies both Segment and Mentioner.
@ -35,24 +38,41 @@ type MentionSegment struct {
text.Mentioner text.Mentioner
} }
// ReferenceSegment is a type that satisfies both Segment and MessageReferencer.
type ReferenceSegment struct {
text.Segment
text.MessageReferencer
}
const (
// f_Mention is used to print and parse mention URIs. // f_Mention is used to print and parse mention URIs.
const f_Mention = "cchat://mention/%d" // %d == Mentions[i] f_Mention = "cchat://mention/%d" // %d == Mentions[i]
f_Reference = "cchat://reference/%d" // %d == References[i]
)
// IsMention returns the mention if the URI is correct, or nil if none. // IsMention returns the mention if the URI is correct, or nil if none.
func (r RenderOutput) IsMention(uri string) text.Segment { func (r RenderOutput) IsMention(uri string) text.Segment {
var i int var i int
if _, err := fmt.Sscanf(uri, f_Mention, &i); err != nil { _, err := fmt.Sscanf(uri, f_Mention, &i)
return nil if err != nil || i >= len(r.Mentions) {
}
if i >= len(r.Mentions) {
return nil return nil
} }
return r.Mentions[i] return r.Mentions[i]
} }
func (r RenderOutput) IsReference(uri string) text.Segment {
var i int
_, err := fmt.Sscanf(uri, f_Reference, &i)
if err != nil || i >= len(r.References) {
return nil
}
return r.References[i]
}
func Render(content text.Rich) string { func Render(content text.Rich) string {
return RenderCmplx(content).Markup return RenderCmplx(content).Markup
} }
@ -63,15 +83,32 @@ func RenderCmplx(content text.Rich) RenderOutput {
} }
type RenderConfig struct { type RenderConfig struct {
// NoMentionLinks prevents the renderer from wrapping mentions with a // NoMentionLinks, if true, will not render any mentions.
// hyperlink. This prevents invalid colors.
NoMentionLinks bool NoMentionLinks bool
// AnchorColor forces all anchors to be of a certain color. This is used if
// the boolean is true. Else, all mention links will not work and regular
// links will be of the default color.
AnchorColor struct {
uint32
bool
}
} }
// NoMentionLinks is the config to render author names. It disables author // SetForegroundAnchor sets the AnchorColor of the render config to be that of
// mention links, as there's no way to make normal names not appear blue. // the regular text foreground color.
var NoMentionLinks = RenderConfig{ func (c *RenderConfig) SetForegroundAnchor(styler primitives.StyleContexter) {
NoMentionLinks: true, styleCtx, _ := styler.GetStyleContext()
if rgba := styleCtx.GetColor(gtk.STATE_FLAG_NORMAL); rgba != nil {
var color uint32
for _, v := range rgba.Floats() { // [0.0, 1.0]
color = (color << 8) + uint32(v*0xFF)
}
c.AnchorColor.bool = true
c.AnchorColor.uint32 = color
}
} }
func RenderCmplxWithConfig(content text.Rich, cfg RenderConfig) RenderOutput { func RenderCmplxWithConfig(content text.Rich, cfg RenderConfig) RenderOutput {
@ -104,15 +141,21 @@ func RenderCmplxWithConfig(content text.Rich, cfg RenderConfig) RenderOutput {
// map to append strings to indices // map to append strings to indices
var appended = attrmap.NewAppendedMap() var appended = attrmap.NewAppendedMap()
// map to store mentions // map to store mentions and references
var mentions []MentionSegment var mentions []MentionSegment
var references []ReferenceSegment
// Parse all segments. // Parse all segments.
for _, segment := range content.Segments { for _, segment := range content.Segments {
start, end := segment.Bounds() start, end := segment.Bounds()
// hasAnchor is used to determine if the current segment has inserted
// any anchor tags; it is used for AnchorColor.
var hasAnchor bool
if linker := segment.AsLinker(); linker != nil { if linker := segment.AsLinker(); linker != nil {
appended.Anchor(start, end, linker.Link()) appended.Anchor(start, end, linker.Link())
hasAnchor = true
} }
// Only inline images if start == end per specification. // Only inline images if start == end per specification.
@ -127,19 +170,14 @@ func RenderCmplxWithConfig(content text.Rich, cfg RenderConfig) RenderOutput {
} }
} }
if colorer := segment.AsColorer(); colorer != nil {
appended.Span(start, end, colorAttrs(colorer.Color(), false)...)
}
// Mentioner needs to be before colorer, as we'd want the below color // Mentioner needs to be before colorer, as we'd want the below color
// segment to also highlight the full mention as well as make the // segment to also highlight the full mention as well as make the
// padding part of the hyperlink. // padding part of the hyperlink.
if mentioner := segment.AsMentioner(); mentioner != nil { if mentioner := segment.AsMentioner(); mentioner != nil && !cfg.NoMentionLinks {
// Render the mention into "cchat://mention:0" or such. Other // Render the mention into "cchat://mention:0" or such. Other
// components will take care of showing the information. // components will take care of showing the information.
if !cfg.NoMentionLinks {
appended.AnchorNU(start, end, fmt.Sprintf(f_Mention, len(mentions))) appended.AnchorNU(start, end, fmt.Sprintf(f_Mention, len(mentions)))
} hasAnchor = true
// Add the mention segment into the list regardless of hyperlinks. // Add the mention segment into the list regardless of hyperlinks.
mentions = append(mentions, MentionSegment{ mentions = append(mentions, MentionSegment{
@ -147,15 +185,44 @@ func RenderCmplxWithConfig(content text.Rich, cfg RenderConfig) RenderOutput {
Mentioner: mentioner, Mentioner: mentioner,
}) })
// TODO: figure out a way to readd Pad. Right now, backend
// implementations can arbitrarily add multiple mentions onto the
// author for overloading, which we don't want to break.
// // Determine if the mention segment covers the entire label.
// // Only pad the name and add a dimmed background if the bounds do
// // not cover the whole segment.
// var cover = (start == 0) && (end == len(content.Content))
// if !cover {
// appended.Pad(start, end)
// }
// // If we don't have a mention color for this segment, then try to
// // use our own AnchorColor.
// if !hasColor && cfg.AnchorColor.bool {
// appended.Span(start, end, colorAttrs(cfg.AnchorColor.uint32, false)...)
// }
}
if colorer := segment.AsColorer(); colorer != nil { if colorer := segment.AsColorer(); colorer != nil {
// Only pad the name and add a dimmed background if the bounds appended.Span(start, end, colorAttrs(colorer.Color(), false)...)
// do not cover the whole segment. } else if hasAnchor && cfg.AnchorColor.bool {
var cover = (start == 0) && (end == len(content.Content)) appended.Span(start, end, colorAttrs(cfg.AnchorColor.uint32, false)...)
appended.Span(start, end, colorAttrs(colorer.Color(), !cover)...)
if !cover {
appended.Pad(start, end)
}
} }
// Don't use AnchorColor for the link, as we're technically just
// borrowing the anchor tag for its use. We should also prefer the
// username popover (Mention) over this.
if reference := segment.AsMessageReferencer(); !hasAnchor && reference != nil {
// Render the mention into "cchat://reference:0" or such. Other
// components will take care of showing the information.
appended.AnchorNU(start, end, fmt.Sprintf(f_Reference, len(references)))
// Add the mention segment into the list regardless of hyperlinks.
references = append(references, ReferenceSegment{
Segment: segment,
MessageReferencer: reference,
})
} }
if attributor := segment.AsAttributor(); attributor != nil { if attributor := segment.AsAttributor(); attributor != nil {
@ -165,7 +232,12 @@ func RenderCmplxWithConfig(content text.Rich, cfg RenderConfig) RenderOutput {
if codeblocker := segment.AsCodeblocker(); codeblocker != nil { if codeblocker := segment.AsCodeblocker(); codeblocker != nil {
start, end := segment.Bounds() start, end := segment.Bounds()
// Syntax highlight the codeblock. // Syntax highlight the codeblock.
hl.Segments(&appended, content.Content, start, end, codeblocker.CodeblockLanguage()) hl.Segments(
&appended,
content.Content,
start, end,
codeblocker.CodeblockLanguage(),
)
} }
// TODO: make this not shit. Maybe make it somehow not rely on green // TODO: make this not shit. Maybe make it somehow not rely on green
@ -190,6 +262,7 @@ func RenderCmplxWithConfig(content text.Rich, cfg RenderConfig) RenderOutput {
Markup: hyphenate(buf.String()), Markup: hyphenate(buf.String()),
Input: content.Content, Input: content.Content,
Mentions: mentions, Mentions: mentions,
References: references,
} }
} }