diff --git a/go.mod b/go.mod index b2f8392..8e32a9e 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/Xuanwo/go-locale v0.2.0 github.com/alecthomas/chroma v0.7.3 github.com/diamondburned/cchat v0.0.42 - github.com/diamondburned/cchat-discord v0.0.0-20200708091545-601e8abeb23b + github.com/diamondburned/cchat-discord v0.0.0-20200709041349-1e137df6de2c github.com/diamondburned/cchat-mock v0.0.0-20200704044009-f587c4904aa3 github.com/diamondburned/imgutil v0.0.0-20200708012333-53c9e45dd28b github.com/goodsign/monday v1.0.0 diff --git a/go.sum b/go.sum index d6904b7..26ffd07 100644 --- a/go.sum +++ b/go.sum @@ -60,6 +60,10 @@ github.com/diamondburned/cchat-discord v0.0.0-20200708083530-d0e43cc63b03 h1:Xx4 github.com/diamondburned/cchat-discord v0.0.0-20200708083530-d0e43cc63b03/go.mod h1:hjaCeQfhSqANH+ZtxXPMWzpgobb4syy2VcqDK4PXcPU= github.com/diamondburned/cchat-discord v0.0.0-20200708091545-601e8abeb23b h1:c01OHXPE/HXcZdaPDgNNzmz3xN7MC0yIyL5i+6qILAc= github.com/diamondburned/cchat-discord v0.0.0-20200708091545-601e8abeb23b/go.mod h1:IDBm0RqdQUASjPGP/RQkhmC1nGhGTTFoaHn9NbD/l7I= +github.com/diamondburned/cchat-discord v0.0.0-20200708212314-e08c31b895a6 h1:Nc9B6i7siInzh/uu3bTu7mcw7ckcFDTu4TtSNoxUMis= +github.com/diamondburned/cchat-discord v0.0.0-20200708212314-e08c31b895a6/go.mod h1:QHPtnxNrnMFCYB/b9kUP93D30Kf3AuGmkM91tScIpB8= +github.com/diamondburned/cchat-discord v0.0.0-20200709041349-1e137df6de2c h1:4F7IJqMfpF02/j6ZbVO9x29rWExQncLHFbxZrsAhhvM= +github.com/diamondburned/cchat-discord v0.0.0-20200709041349-1e137df6de2c/go.mod h1:QHPtnxNrnMFCYB/b9kUP93D30Kf3AuGmkM91tScIpB8= github.com/diamondburned/cchat-mock v0.0.0-20200704044009-f587c4904aa3 h1:xr07/2cwINyrMqh92pQQJVDfQqG0u6gHAK+ZcGfpSew= github.com/diamondburned/cchat-mock v0.0.0-20200704044009-f587c4904aa3/go.mod h1:SRu3OOeggELFr2Wd3/+SpYV1eNcvSk2LBhM70NOZSG8= github.com/diamondburned/gotk3 v0.0.0-20200630065217-97aeb06d705d h1:Ha/I6PMKi+B4hpWclwlXj0tUMehR7Q0TNxPczzBwzPI= @@ -72,6 +76,8 @@ github.com/diamondburned/ningen v0.1.1-0.20200621014632-6babb812b249 h1:yP7kJ+xC github.com/diamondburned/ningen v0.1.1-0.20200621014632-6babb812b249/go.mod h1:xW9hpBZsGi8KpAh10TyP+YQlYBo+Xc+2w4TR6N0951A= github.com/diamondburned/ningen v0.1.1-0.20200708090333-227e90d19851 h1:xf1aLPnwK/Yn2z7dBIgQROSVOEc2wtivgnnwBItdEVM= github.com/diamondburned/ningen v0.1.1-0.20200708090333-227e90d19851/go.mod h1:FNezDLQIhoDS+RkXLSQ7dJNrt6BW/nVl1krzDgWMQwg= +github.com/diamondburned/ningen v0.1.1-0.20200708211706-57c712372ede h1:qRmfQCOS+ZnH4G0+8O09PUx3HQTdQwzsDoo1ucTgm2E= +github.com/diamondburned/ningen v0.1.1-0.20200708211706-57c712372ede/go.mod h1:FNezDLQIhoDS+RkXLSQ7dJNrt6BW/nVl1krzDgWMQwg= github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk= diff --git a/internal/c/README.md b/internal/c/README.md deleted file mode 100644 index b332c35..0000000 --- a/internal/c/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# c - -**HERE BE DRAGONS!!** - -This package and all its subpackages contain either C code or Cgo code. This -bugs me and I hate it. PROCEED WITH CAUTION! diff --git a/internal/c/labelutils/labelutils.go b/internal/c/labelutils/labelutils.go deleted file mode 100644 index f87da96..0000000 --- a/internal/c/labelutils/labelutils.go +++ /dev/null @@ -1,101 +0,0 @@ -package labelutils - -// #cgo pkg-config: gdk-3.0 gio-2.0 glib-2.0 gobject-2.0 gtk+-3.0 -// #include -// #include -// #include -import "C" - -import ( - "unsafe" - - "github.com/gotk3/gotk3/gtk" - "github.com/gotk3/gotk3/pango" -) - -func gbool(b bool) C.gboolean { - if b { - return C.gboolean(C.TRUE) - } else { - return C.gboolean(C.FALSE) - } -} - -type Attribute = func() *C.PangoAttribute - -func InsertHyphens(hyphens bool) Attribute { - return func() *C.PangoAttribute { - return C.pango_attr_insert_hyphens_new(gbool(hyphens)) - } -} - -func Scale(factor float64) Attribute { - return func() *C.PangoAttribute { - return C.pango_attr_scale_new(C.double(factor)) - } -} - -func Underline(underline pango.Underline) Attribute { - return func() *C.PangoAttribute { - return C.pango_attr_underline_new(C.PangoUnderline(underline)) - } -} - -func Strikethrough(strikethrough bool) Attribute { - return func() *C.PangoAttribute { - return C.pango_attr_strikethrough_new(gbool(strikethrough)) - } -} - -const u16divu8 = 65535 / 255 - -func rgb(hex uint32) (r, g, b uint16) { - r = uint16(hex>>16&255) * u16divu8 - g = uint16(hex>>8&255) * u16divu8 - b = uint16(hex&255) * u16divu8 - return -} - -func Background(hex uint32) Attribute { - r, g, b := rgb(hex) - - return func() *C.PangoAttribute { - return C.pango_attr_background_new(C.guint16(r), C.guint16(g), C.guint16(b)) - } -} - -func Foreground(hex uint32) Attribute { - r, g, b := rgb(hex) - - return func() *C.PangoAttribute { - return C.pango_attr_foreground_new(C.guint16(r), C.guint16(g), C.guint16(b)) - } -} - -func Style(style pango.Style) Attribute { - return func() *C.PangoAttribute { - return C.pango_attr_style_new(C.PangoStyle(style)) - } -} - -func Family(family string) Attribute { - return func() *C.PangoAttribute { - str := C.CString(family) - defer C.free(unsafe.Pointer(str)) - return C.pango_attr_family_new(str) - } -} - -func AddAttr(l *gtk.Label, attrs ...Attribute) { - attrlist := C.pango_attr_list_new() - defer C.pango_attr_list_unref(attrlist) - - for _, attr := range attrs { - // attr() should not unref; insert is transfer-full - // https://discourse.gnome.org/t/pango-how-to-turn-off-hyphenation-for-char-wrapping/2101/2 - C.pango_attr_list_insert(attrlist, attr()) - } - - v := (*C.GtkLabel)(unsafe.Pointer(l.Native())) - C.gtk_label_set_attributes(v, attrlist) -} diff --git a/internal/ui/imgview/imgview.go b/internal/ui/imgview/imgview.go index 69ebf6e..406ed8b 100644 --- a/internal/ui/imgview/imgview.go +++ b/internal/ui/imgview/imgview.go @@ -7,7 +7,6 @@ import ( "path" "strings" - "github.com/diamondburned/cchat-gtk/internal/c/labelutils" "github.com/diamondburned/cchat-gtk/internal/gts/httputil" "github.com/diamondburned/cchat-gtk/internal/log" "github.com/diamondburned/cchat-gtk/internal/ui/dialog" @@ -87,7 +86,7 @@ func BindTooltip(connector WidgetConnector) { } const urlPrompt = `This link leads to the following URL: -%[1]s +%[1]s Click Open to proceed.` var warnLabelCSS = primitives.PrepareCSS(` @@ -109,9 +108,6 @@ func PromptOpen(uri string) { // Style the label. primitives.AttachCSS(l, warnLabelCSS) - // Disable hyphens on line wraps. - labelutils.AddAttr(l, labelutils.InsertHyphens(false)) - open := func() { if err := open.Start(uri); err != nil { log.Error(errors.Wrap(err, "Failed to open URL after confirm")) diff --git a/internal/ui/messages/input/completion/completion.go b/internal/ui/messages/input/completion/completion.go index e9da1eb..a8119d9 100644 --- a/internal/ui/messages/input/completion/completion.go +++ b/internal/ui/messages/input/completion/completion.go @@ -1,16 +1,21 @@ package completion import ( + "fmt" + "github.com/diamondburned/cchat" "github.com/diamondburned/cchat-gtk/internal/gts/httputil" "github.com/diamondburned/cchat-gtk/internal/ui/primitives/completion" "github.com/diamondburned/cchat-gtk/internal/ui/rich" + "github.com/diamondburned/cchat-gtk/internal/ui/rich/parser" + "github.com/diamondburned/cchat/text" "github.com/diamondburned/imgutil" "github.com/gotk3/gotk3/gtk" ) const ( - ImageSize = 25 + ImageSmall = 25 + ImageLarge = 40 ImagePadding = 6 ) @@ -51,30 +56,53 @@ func (v *View) Update(words []string, i int) []gtk.IWidget { var widgets = make([]gtk.IWidget, len(v.entries)) for i, entry := range v.entries { + // Container that holds the label. + lbox, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0) + lbox.SetVAlign(gtk.ALIGN_CENTER) + lbox.Show() + + // Label for the primary text. l := rich.NewLabel(entry.Text) l.Show() + lbox.PackStart(l, false, false, 0) - img, _ := gtk.ImageNew() + // Get the iamge size so we can change and use if needed. The default + var size = ImageSmall + if !entry.Secondary.Empty() { + size = ImageLarge + + s := rich.NewLabel(text.Rich{}) + s.SetMarkup(fmt.Sprintf( + `%s`, + parser.RenderMarkup(entry.Secondary), + )) + s.Show() + + lbox.PackStart(s, false, false, 0) + } + + b, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0) + b.PackEnd(lbox, true, true, ImagePadding) + b.Show() // Do we have an icon? if entry.IconURL != "" { + img, _ := gtk.ImageNew() img.SetMarginStart(ImagePadding) - img.SetSizeRequest(ImageSize, ImageSize) + img.SetSizeRequest(size, size) img.Show() + // Prepend the image into the box. + b.PackEnd(img, false, false, 0) + var pps []imgutil.Processor if !entry.Image { pps = ppIcon } - httputil.AsyncImageSized(img, entry.IconURL, ImageSize, ImageSize, pps...) + httputil.AsyncImageSized(img, entry.IconURL, size, size, pps...) } - b, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0) - b.PackStart(img, false, false, 0) // image has pad left - b.PackStart(l, true, true, ImagePadding) - b.Show() - widgets[i] = b } diff --git a/internal/ui/messages/message/message.go b/internal/ui/messages/message/message.go index 61a55b5..ec6817e 100644 --- a/internal/ui/messages/message/message.go +++ b/internal/ui/messages/message/message.go @@ -4,7 +4,6 @@ import ( "time" "github.com/diamondburned/cchat" - "github.com/diamondburned/cchat-gtk/internal/c/labelutils" "github.com/diamondburned/cchat-gtk/internal/humanize" "github.com/diamondburned/cchat-gtk/internal/ui/imgview" "github.com/diamondburned/cchat-gtk/internal/ui/primitives" @@ -91,7 +90,6 @@ func NewEmptyContainer() *GenericContainer { ts.SetEllipsize(pango.ELLIPSIZE_MIDDLE) ts.SetXAlign(1) // right align ts.SetVAlign(gtk.ALIGN_END) - // ts.SetSelectable(true) ts.Show() user, _ := gtk.LabelNew("") @@ -100,7 +98,6 @@ func NewEmptyContainer() *GenericContainer { user.SetLineWrapMode(pango.WRAP_WORD_CHAR) user.SetXAlign(1) // right align user.SetVAlign(gtk.ALIGN_START) - // user.SetSelectable(true) user.Show() content, _ := gtk.LabelNew("") @@ -110,8 +107,14 @@ func NewEmptyContainer() *GenericContainer { content.SetSelectable(true) content.Show() - // Never insert hyphens on line breaks in the message content. - labelutils.AddAttr(content, labelutils.InsertHyphens(false)) + // content.Connect("grab-notify", func(l *gtk.Label, grabbed bool) { + // if grabbed { + // // Hack to stop the label from selecting everything after being + // // refocused. + // content.SetSelectable(false) + // gts.ExecAsync(func() { content.SetSelectable(true) }) + // } + // }) // Add CSS classes. primitives.AddClass(ts, "message-time") diff --git a/internal/ui/messages/view.go b/internal/ui/messages/view.go index c53d85b..0abe2c2 100644 --- a/internal/ui/messages/view.go +++ b/internal/ui/messages/view.go @@ -193,7 +193,10 @@ func (v *View) AddPresendMessage(msg input.PresendMessage) func(error) { // AuthorEvent should be called on message create/update/delete. func (v *View) AuthorEvent(author cchat.MessageAuthor) { - v.Typing.RemoveAuthor(author) + // Remove the author from the typing list if it's not nil. + if author != nil { + v.Typing.RemoveAuthor(author) + } } // LatestMessageFrom returns the last message ID with that author. diff --git a/internal/ui/rich/parser/attrmap/attrmap.go b/internal/ui/rich/parser/attrmap/attrmap.go index 7a36d63..efe2f47 100644 --- a/internal/ui/rich/parser/attrmap/attrmap.go +++ b/internal/ui/rich/parser/attrmap/attrmap.go @@ -3,56 +3,82 @@ package attrmap import ( "fmt" "sort" + "strings" ) type AppendMap struct { - appended map[int]string - indices []int + appended map[int]string // for opening tags + prepended map[int]string // for closing tags + indices []int } func NewAppendedMap() AppendMap { return AppendMap{ - appended: make(map[int]string), - indices: []int{}, + appended: map[int]string{}, + prepended: map[int]string{}, + indices: []int{}, } } -func (a *AppendMap) Span(start, end int, attr string) { - a.Add(start, ``) - a.Add(end, "") +func (a *AppendMap) appendIndex(ind int) { + // Backwards search which should make things faster. + for i := len(a.indices) - 1; i >= 0; i-- { + if a.indices[i] == ind { + return + } + } + + a.indices = append(a.indices, ind) +} + +func (a *AppendMap) Span(start, end int, attrs ...string) { + a.Openf(start, "", strings.Join(attrs, " ")) + a.Close(end, "") } func (a *AppendMap) Pair(start, end int, open, close string) { - a.Add(start, open) - a.Add(end, close) + a.Open(start, open) + a.Close(end, close) } -func (a *AppendMap) Addf(ind int, f string, argv ...interface{}) { - a.Add(ind, fmt.Sprintf(f, argv...)) +func (a *AppendMap) Openf(ind int, f string, argv ...interface{}) { + a.Open(ind, fmt.Sprintf(f, argv...)) } -func (a *AppendMap) Pad(ind int) { - a.Add(ind, "\n") -} - -func (a *AppendMap) Add(ind int, attr string) { - if _, ok := a.appended[ind]; ok { - a.appended[ind] += attr +func (a *AppendMap) Open(ind int, attr string) { + if str, ok := a.appended[ind]; ok { + a.appended[ind] = str + attr // append return } a.appended[ind] = attr - a.indices = append(a.indices, ind) + a.appendIndex(ind) } -func (a AppendMap) Get(ind int) string { - return a.appended[ind] +func (a *AppendMap) Close(ind int, attr string) { + if str, ok := a.prepended[ind]; ok { + a.prepended[ind] = attr + str // prepend + return + } + + a.prepended[ind] = attr + a.appendIndex(ind) +} + +func (a AppendMap) Get(ind int) (tags string) { + if t, ok := a.appended[ind]; ok { + tags += t + } + if t, ok := a.prepended[ind]; ok { + tags += t + } + return } func (a *AppendMap) Finalize(strlen int) []int { // make sure there's always a closing tag at the end so the entire string // gets flushed. - a.Add(strlen, "") + a.Close(strlen, "") sort.Ints(a.indices) return a.indices } diff --git a/internal/ui/rich/parser/hl/hl.go b/internal/ui/rich/parser/hl/hl.go index b875209..c60803c 100644 --- a/internal/ui/rich/parser/hl/hl.go +++ b/internal/ui/rich/parser/hl/hl.go @@ -42,7 +42,11 @@ func Tokenize(language, source string) chroma.Iterator { func Segments(appendmap *attrmap.AppendMap, src string, seg text.Codeblocker) { var start, end = seg.Bounds() - appendmap.Span(start, end, `font_family="monospace"`) + appendmap.Span( + start, end, + `font_family="monospace"`, + `insert_hyphens="false"`, // all my homies hate hyphens + ) if i := Tokenize(seg.CodeblockLanguage(), src[start:end]); i != nil { fmtter.segments(appendmap, start, i) @@ -92,13 +96,13 @@ func (f *formatter) segments(appendmap *attrmap.AppendMap, offset int, iter chro attr := f.styleAttr(token.Type) if attr != "" { - appendmap.Addf(offset, ``, attr) + appendmap.Openf(offset, ``, attr) } offset += len(token.Value) if attr != "" { - appendmap.Add(offset, "") + appendmap.Close(offset, "") } } } diff --git a/internal/ui/rich/parser/markup.go b/internal/ui/rich/parser/markup.go index 27d0bb2..8d34f47 100644 --- a/internal/ui/rich/parser/markup.go +++ b/internal/ui/rich/parser/markup.go @@ -65,17 +65,26 @@ func RenderMarkup(content text.Rich) string { start, end := segment.Bounds() if segment, ok := segment.(text.Linker); ok { - appended.Addf(start, ``, html.EscapeString(segment.Link())) - appended.Add(end, "") + appended.Openf(start, ``, html.EscapeString(segment.Link())) + appended.Close(end, "") } if segment, ok := segment.(text.Imager); ok { // Ends don't matter with images. - appended.Add(start, composeImageMarkup(segment)) + appended.Open(start, composeImageMarkup(segment)) } if segment, ok := segment.(text.Colorer); ok { - appended.Span(start, end, fmt.Sprintf(`color="#%06X"`, segment.Color())) + var attrs = []string{fmt.Sprintf(`color="#%06X"`, segment.Color())} + // If the color segment only covers a segment, then add some more + // formatting. + if start > 0 && end < len(content.Content) { + attrs = append(attrs, + `bgalpha="10%"`, + fmt.Sprintf(`bgcolor="#%06X"`, segment.Color()), + ) + } + appended.Span(start, end, attrs...) } if segment, ok := segment.(text.Attributor); ok { diff --git a/internal/ui/rich/parser/markup_test.go b/internal/ui/rich/parser/markup_test.go new file mode 100644 index 0000000..c2a481b --- /dev/null +++ b/internal/ui/rich/parser/markup_test.go @@ -0,0 +1,57 @@ +package parser + +import ( + "testing" + + "github.com/diamondburned/cchat-mock/segments" + "github.com/diamondburned/cchat/text" +) + +func TestRenderMarkup(t *testing.T) { + content := text.Rich{Content: "astolfo is the best trap"} + content.Segments = []text.Segment{ + segments.NewColored(content.Content, 0x55CDFC), + } + expect := `` + content.Content + "" + + if text := RenderMarkup(content); text != expect { + t.Fatal("Unexpected text:", text) + } +} + +// Test no longer works, and should not work anyway. + +// func TestRenderMarkupPartial(t *testing.T) { +// content := text.Rich{Content: "random placeholder text go brrr"} +// content.Segments = []text.Segment{ +// // This is absolutely jankery that should not work at all, but we'll try +// // it anyway. +// coloredSegment{0, 4, 0x55CDFC}, +// coloredSegment{2, 6, 0xFFFFFF}, // naive parsing, so spans close unexpectedly. +// coloredSegment{4, 6, 0xF7A8B8}, +// } +// const expect = "" + +// random +// `rand` + +// `om` + +// if text := RenderMarkup(content); !strings.HasPrefix(text, expect) { +// t.Fatal("Unexpected text:", text) +// } +// } + +type coloredSegment struct { + start int + end int + color uint32 +} + +var _ text.Colorer = (*coloredSegment)(nil) + +func (c coloredSegment) Bounds() (start, end int) { + return c.start, c.end +} + +func (c coloredSegment) Color() uint32 { + return c.color +} diff --git a/internal/ui/rich/parser/parser.go b/internal/ui/rich/parser/parser.go deleted file mode 100644 index 6b60026..0000000 --- a/internal/ui/rich/parser/parser.go +++ /dev/null @@ -1,170 +0,0 @@ -package parser - -import ( - "fmt" - "sort" - "time" - - "github.com/diamondburned/cchat-gtk/internal/log" - "github.com/diamondburned/cchat-gtk/internal/ui/primitives" - "github.com/diamondburned/cchat/text" - "github.com/gotk3/gotk3/gdk" - "github.com/gotk3/gotk3/gtk" - "github.com/gotk3/gotk3/pango" - "github.com/pkg/errors" - "github.com/skratchdot/open-golang/open" -) - -func AppendEditBadge(b *gtk.TextBuffer, editedAt time.Time) { - r := newRenderCtx(b) - - t := r.createTag(map[string]interface{}{ - "scale": 0.84, - "scale-set": true, - "foreground": "#808080", // blue-ish URL color - }) - - bindClicker(t, func(_ *gtk.TextView, ev *gdk.Event) { - switch ev := gdk.EventMotionNewFromEvent(ev); ev.Type() { - case gdk.EVENT_PROXIMITY_IN: - log.Println("Proximity in") - case gdk.EVENT_PROXIMITY_OUT: - log.Println("Proximity out") - } - }) - - b.InsertWithTag(b.GetEndIter(), " (edited)", t) -} - -func RenderTextBuffer(b *gtk.TextBuffer, content text.Rich) { - r := newRenderCtx(b) - b.SetText(content.Content) - - // Sort so that all starting points are sorted incrementally. - sort.Slice(content.Segments, func(i, j int) bool { - i, _ = content.Segments[i].Bounds() - j, _ = content.Segments[j].Bounds() - return i < j - }) - - for _, segment := range content.Segments { - start, end := segment.Bounds() - - switch segment := segment.(type) { - case text.Attributor: - r.tagAttr(start, end, segment.Attribute()) - - case text.Colorer: - color := fmt.Sprintf("#%06X", segment.Color()) - r.applyProps(start, end, map[string]interface{}{ - "foreground": color, - }) - - case text.Codeblocker: - r.applyProps(start, end, map[string]interface{}{ - "family": "Monospace", - }) - } - } -} - -type renderCtx struct { - b *gtk.TextBuffer - t *gtk.TextTagTable -} - -func newRenderCtx(b *gtk.TextBuffer) *renderCtx { - t, _ := b.GetTagTable() - return &renderCtx{b, t} -} - -type OnClicker func(tv *gtk.TextView, ev *gdk.Event) - -func bindClicker(v primitives.Connector, fn OnClicker) { - v.Connect("event", func(_ *gtk.TextTag, tv *gtk.TextView, ev *gdk.Event) { - evButton := gdk.EventButtonNewFromEvent(ev) - if evButton.Type() != gdk.EVENT_BUTTON_RELEASE || evButton.Button() != gdk.BUTTON_PRIMARY { - return - } - - fn(tv, ev) - }) -} - -func (r *renderCtx) applyHyperlink(start, end int, url string) { - t := r.createTag(map[string]interface{}{ - "underline": pango.UNDERLINE_SINGLE, - "foreground": "#3F7CE0", // blue-ish URL color - }) - - bindClicker(t, func(*gtk.TextView, *gdk.Event) { - if err := open.Start(url); err != nil { - log.Error(errors.Wrap(err, "Failed to open image URL")) - } - }) -} - -func (r *renderCtx) applyProps(start, end int, props map[string]interface{}) { - tag := r.createTag(props) - r.applyTag(start, end, tag) -} - -func (r *renderCtx) applyTag(start, end int, tag *gtk.TextTag) { - istart, iend := r.iters(start, end) - r.b.ApplyTag(tag, istart, iend) -} - -func (r *renderCtx) createTag(props map[string]interface{}) *gtk.TextTag { - t, _ := gtk.TextTagNew("") - r.t.Add(t) - - if props != nil { - for k, v := range props { - t.SetProperty(k, v) - } - } - - return t -} - -func (r *renderCtx) iters(start, end int) (is, ie *gtk.TextIter) { - return r.b.GetIterAtOffset(start), r.b.GetIterAtOffset(end) -} - -func (r *renderCtx) tagAttr(start, end int, attr text.Attribute) { - var props = tagAttrMap(attr) - if props == nil { - return - } - - r.applyTag(start, end, r.createTag(props)) -} - -func tagAttrMap(attr text.Attribute) map[string]interface{} { - if attr == 0 { - return nil - } - - var props = make(map[string]interface{}, 1) - - if attr.Has(text.AttrBold) { - props["weight"] = pango.WEIGHT_BOLD - } - if attr.Has(text.AttrItalics) { - props["style"] = pango.STYLE_ITALIC - } - if attr.Has(text.AttrUnderline) { - props["underline"] = pango.UNDERLINE_SINGLE - } - if attr.Has(text.AttrStrikethrough) { - props["strikethrough"] = true - } - if attr.Has(text.AttrSpoiler) { - props["foreground"] = "#808080" - } - if attr.Has(text.AttrMonospace) { - props["family"] = "Monospace" - } - - return props -} diff --git a/internal/ui/rich/parser/parser_test.go b/internal/ui/rich/parser/parser_test.go deleted file mode 100644 index 995164c..0000000 --- a/internal/ui/rich/parser/parser_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package parser - -import ( - "strings" - "testing" - - "github.com/diamondburned/cchat-mock/segments" - "github.com/diamondburned/cchat/text" -) - -func TestRenderMarkup(t *testing.T) { - content := text.Rich{Content: "astolfo is the best trap"} - content.Segments = []text.Segment{ - segments.NewColored(content.Content, 0x55CDFC), - } - expect := `` + content.Content + "" - - if text := RenderMarkup(content); text != expect { - t.Fatal("Unexpected text:", text) - } -} - -func TestRenderMarkupPartial(t *testing.T) { - content := text.Rich{Content: "random placeholder text go brrr"} - content.Segments = []text.Segment{ - // This is absolutely jankery that should not work at all, but we'll try - // it anyway. - coloredSegment{0, 4, 0x55CDFC}, - coloredSegment{2, 6, 0xFFFFFF}, // naive parsing, so spans close unexpectedly. - coloredSegment{4, 6, 0xF7A8B8}, - } - const expect = "" + - `rand` + - `om` - - if text := RenderMarkup(content); !strings.HasPrefix(text, expect) { - t.Fatal("Unexpected text:", text) - } -} - -type coloredSegment struct { - start int - end int - color uint32 -} - -var _ text.Colorer = (*coloredSegment)(nil) - -func (c coloredSegment) Bounds() (start, end int) { - return c.start, c.end -} - -func (c coloredSegment) Color() uint32 { - return c.color -}