From ee67c1446cbb3c446d59d4ebd9657a25bf0b702d Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 10 Apr 2018 16:07:16 +0300 Subject: Convert message buffer to use custom colorable strings --- ui/messages/message.go | 158 ++++++++++++++++++++++++++++++++++++++++++++- ui/messages/meta.go | 21 ++++-- ui/messages/textmessage.go | 56 +++++++++------- 3 files changed, 202 insertions(+), 33 deletions(-) (limited to 'ui/messages') diff --git a/ui/messages/message.go b/ui/messages/message.go index ef0966c..f9ad1f7 100644 --- a/ui/messages/message.go +++ b/ui/messages/message.go @@ -16,7 +16,13 @@ package messages -import "maunium.net/go/gomuks/interface" +import ( + "strings" + + "github.com/gdamore/tcell" + "github.com/mattn/go-runewidth" + "maunium.net/go/gomuks/interface" +) // Message is a wrapper for the content and metadata of a Matrix message intended to be displayed. type UIMessage interface { @@ -24,6 +30,154 @@ type UIMessage interface { CalculateBuffer(width int) RecalculateBuffer() - Buffer() []string + Buffer() []UIString Height() int + + RealSender() string +} + +type Cell struct { + Char rune + Style tcell.Style +} + +func NewStyleCell(char rune, style tcell.Style) Cell { + return Cell{char, style} +} + +func NewColorCell(char rune, color tcell.Color) Cell { + return Cell{char, tcell.StyleDefault.Foreground(color)} +} + +func NewCell(char rune) Cell { + return Cell{char, tcell.StyleDefault} +} + +func (cell Cell) RuneWidth() int { + return runewidth.RuneWidth(cell.Char) +} + +func (cell Cell) Draw(screen tcell.Screen, x, y int) (chWidth int) { + chWidth = cell.RuneWidth() + for runeWidthOffset := 0; runeWidthOffset < chWidth; runeWidthOffset++ { + screen.SetContent(x+runeWidthOffset, y, cell.Char, nil, cell.Style) + } + return +} + +type UIString []Cell + +func NewUIString(str string) UIString { + newStr := make([]Cell, len(str)) + for i, char := range str { + newStr[i] = NewCell(char) + } + return newStr +} + +func NewColorUIString(str string, color tcell.Color) UIString { + newStr := make([]Cell, len(str)) + for i, char := range str { + newStr[i] = NewColorCell(char, color) + } + return newStr +} + +func NewStyleUIString(str string, style tcell.Style) UIString { + newStr := make([]Cell, len(str)) + for i, char := range str { + newStr[i] = NewStyleCell(char, style) + } + return newStr +} + +func (str UIString) Colorize(from, to int, color tcell.Color) { + for i := from; i < to; i++ { + str[i].Style = str[i].Style.Foreground(color) + } +} + +func (str UIString) Draw(screen tcell.Screen, x, y int) { + offsetX := 0 + for _, cell := range str { + offsetX += cell.Draw(screen, x+offsetX, y) + } +} + +func (str UIString) RuneWidth() (width int) { + for _, cell := range str { + width += runewidth.RuneWidth(cell.Char) + } + return width +} + +func (str UIString) String() string { + var buf strings.Builder + for _, cell := range str { + buf.WriteRune(cell.Char) + } + return buf.String() +} + +// Truncate return string truncated with w cells +func (str UIString) Truncate(w int) UIString { + if str.RuneWidth() <= w { + return str[:] + } + width := 0 + i := 0 + for ; i < len(str); i++ { + cw := runewidth.RuneWidth(str[i].Char) + if width+cw > w { + break + } + width += cw + } + return str[0:i] +} + +func (str UIString) IndexFrom(r rune, from int) int { + for i := from; i < len(str); i++ { + if str[i].Char == r { + return i + } + } + return -1 +} + +func (str UIString) Index(r rune) int { + return str.IndexFrom(r, 0) } + +func (str UIString) Count(r rune) (counter int) { + index := 0 + for { + index = str.IndexFrom(r, index) + if index < 0 { + break + } + index++ + counter++ + } + return +} + +func (str UIString) Split(sep rune) []UIString { + a := make([]UIString, str.Count(sep)+1) + i := 0 + orig := str + for { + m := orig.Index(sep) + if m < 0 { + break + } + a[i] = orig[:m] + orig = orig[m+1:] + i++ + } + a[i] = orig + return a[:i+1] +} + +const DateFormat = "January _2, 2006" +const TimeFormat = "15:04:05" diff --git a/ui/messages/meta.go b/ui/messages/meta.go index 8cb6c1b..3f9c9ab 100644 --- a/ui/messages/meta.go +++ b/ui/messages/meta.go @@ -17,13 +17,16 @@ package messages import ( + "time" + "github.com/gdamore/tcell" "maunium.net/go/gomuks/interface" ) // BasicMeta is a simple variable store implementation of MessageMeta. type BasicMeta struct { - BSender, BTimestamp, BDate string + BSender string + BTimestamp time.Time BSenderColor, BTextColor, BTimestampColor tcell.Color } @@ -37,14 +40,19 @@ func (meta *BasicMeta) SenderColor() tcell.Color { return meta.BSenderColor } -// Timestamp returns the formatted time when the message was sent. -func (meta *BasicMeta) Timestamp() string { +// Timestamp returns the full time when the message was sent. +func (meta *BasicMeta) Timestamp() time.Time { return meta.BTimestamp } -// Date returns the formatted date when the message was sent. -func (meta *BasicMeta) Date() string { - return meta.BDate +// FormatTime returns the formatted time when the message was sent. +func (meta *BasicMeta) FormatTime() string { + return meta.BTimestamp.Format(TimeFormat) +} + +// FormatDate returns the formatted date when the message was sent. +func (meta *BasicMeta) FormatDate() string { + return meta.BTimestamp.Format(DateFormat) } // TextColor returns the color the actual content of the message should be shown in. @@ -63,7 +71,6 @@ func (meta *BasicMeta) TimestampColor() tcell.Color { func (meta *BasicMeta) CopyFrom(from ifc.MessageMeta) { meta.BSender = from.Sender() meta.BTimestamp = from.Timestamp() - meta.BDate = from.Date() meta.BSenderColor = from.SenderColor() meta.BTextColor = from.TextColor() meta.BTimestampColor = from.TimestampColor() diff --git a/ui/messages/textmessage.go b/ui/messages/textmessage.go index 1f37418..1a53c2b 100644 --- a/ui/messages/textmessage.go +++ b/ui/messages/textmessage.go @@ -20,10 +20,9 @@ import ( "encoding/gob" "fmt" "regexp" - "strings" + "time" "github.com/gdamore/tcell" - "github.com/mattn/go-runewidth" "maunium.net/go/gomuks/interface" ) @@ -36,22 +35,20 @@ type UITextMessage struct { MsgType string MsgSender string MsgSenderColor tcell.Color - MsgTimestamp string - MsgDate string + MsgTimestamp time.Time MsgText string MsgState ifc.MessageState MsgIsHighlight bool MsgIsService bool - buffer []string + buffer []UIString prevBufferWidth int } // NewMessage creates a new Message object with the provided values and the default state. -func NewMessage(id, sender, msgtype, text, timestamp, date string, senderColor tcell.Color) UIMessage { +func NewMessage(id, sender, msgtype, text string, timestamp time.Time, senderColor tcell.Color) UIMessage { return &UITextMessage{ MsgSender: sender, MsgTimestamp: timestamp, - MsgDate: date, MsgSenderColor: senderColor, MsgType: msgtype, MsgText: text, @@ -66,18 +63,19 @@ func NewMessage(id, sender, msgtype, text, timestamp, date string, senderColor t // CopyFrom replaces the content of this message object with the content of the given object. func (msg *UITextMessage) CopyFrom(from ifc.MessageMeta) { msg.MsgSender = from.Sender() - msg.MsgTimestamp = from.Timestamp() - msg.MsgDate = from.Date() msg.MsgSenderColor = from.SenderColor() fromMsg, ok := from.(UIMessage) if ok { + msg.MsgSender = fromMsg.RealSender() msg.MsgID = fromMsg.ID() msg.MsgType = fromMsg.Type() + msg.MsgTimestamp = fromMsg.Timestamp() msg.MsgText = fromMsg.Text() msg.MsgState = fromMsg.State() msg.MsgIsService = fromMsg.IsService() msg.MsgIsHighlight = fromMsg.IsHighlight() + msg.buffer = nil msg.RecalculateBuffer() } @@ -105,6 +103,10 @@ func (msg *UITextMessage) Sender() string { } } +func (msg *UITextMessage) RealSender() string { + return msg.MsgSender +} + func (msg *UITextMessage) getStateSpecificColor() tcell.Color { switch msg.MsgState { case ifc.MessageStateSending: @@ -176,7 +178,7 @@ func (msg *UITextMessage) RecalculateBuffer() { // // N.B. This will NOT automatically calculate the buffer if it hasn't been // calculated already, as that requires the target width. -func (msg *UITextMessage) Buffer() []string { +func (msg *UITextMessage) Buffer() []UIString { return msg.buffer } @@ -185,14 +187,19 @@ func (msg *UITextMessage) Height() int { return len(msg.buffer) } -// Timestamp returns the formatted time when the message was sent. -func (msg *UITextMessage) Timestamp() string { +// Timestamp returns the full timestamp when the message was sent. +func (msg *UITextMessage) Timestamp() time.Time { return msg.MsgTimestamp } -// Date returns the formatted date when the message was sent. -func (msg *UITextMessage) Date() string { - return msg.MsgDate +// FormatTime returns the formatted time when the message was sent. +func (msg *UITextMessage) FormatTime() string { + return msg.MsgTimestamp.Format(TimeFormat) +} + +// FormatDate returns the formatted date when the message was sent. +func (msg *UITextMessage) FormatDate() string { + return msg.MsgTimestamp.Format(DateFormat) } func (msg *UITextMessage) ID() string { @@ -259,30 +266,31 @@ func (msg *UITextMessage) CalculateBuffer(width int) { return } - msg.buffer = []string{} - text := msg.MsgText + msg.buffer = []UIString{} + text := NewColorUIString(msg.Text(), msg.TextColor()) if msg.MsgType == "m.emote" { - text = fmt.Sprintf("* %s %s", msg.MsgSender, msg.MsgText) + text = NewColorUIString(fmt.Sprintf("* %s %s", msg.MsgSender, msg.MsgText), msg.TextColor()) + text.Colorize(2, 2+len(msg.MsgSender), msg.SenderColor()) } - forcedLinebreaks := strings.Split(text, "\n") + forcedLinebreaks := text.Split('\n') newlines := 0 for _, str := range forcedLinebreaks { if len(str) == 0 && newlines < 1 { - msg.buffer = append(msg.buffer, "") + msg.buffer = append(msg.buffer, UIString{}) newlines++ } else { newlines = 0 } - // From tview/textview.go#reindexBuffer() + // Mostly from tview/textview.go#reindexBuffer() for len(str) > 0 { - extract := runewidth.Truncate(str, width, "") + extract := str.Truncate(width) if len(extract) < len(str) { - if spaces := spacePattern.FindStringIndex(str[len(extract):]); spaces != nil && spaces[0] == 0 { + if spaces := spacePattern.FindStringIndex(str[len(extract):].String()); spaces != nil && spaces[0] == 0 { extract = str[:len(extract)+spaces[1]] } - matches := boundaryPattern.FindAllStringIndex(extract, -1) + matches := boundaryPattern.FindAllStringIndex(extract.String(), -1) if len(matches) > 0 { extract = extract[:matches[len(matches)-1][1]] } -- cgit v1.2.3