diff --git a/ui/types/message.go b/ui/types/message.go index 5cbc61d..e212a80 100644 --- a/ui/types/message.go +++ b/ui/types/message.go @@ -25,55 +25,176 @@ import ( "github.com/mattn/go-runewidth" ) +// MessageState is an enum to specify if a Message is being sent, failed to send or was successfully sent. +type MessageState int + +// Allowed MessageStates. +const ( + MessageStateSending MessageState = iota + MessageStateDefault + MessageStateFailed +) + +// Message is a wrapper for the content and metadata of a Matrix message intended to be displayed. type Message struct { - BasicMeta - Type string ID string + Type string + Sender string + SenderColor tcell.Color + Timestamp string + Date string Text string - sending bool + State MessageState buffer []string 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) *Message { return &Message{ - BasicMeta: BasicMeta{ - Sender: sender, - Timestamp: timestamp, - Date: date, - SenderColor: senderColor, - TextColor: tcell.ColorDefault, - TimestampColor: tcell.ColorDefault, - }, + Sender: sender, + Timestamp: timestamp, + Date: date, + SenderColor: senderColor, Type: msgtype, Text: text, ID: id, prevBufferWidth: 0, - sending: false, + State: MessageStateDefault, } } +// CopyTo copies the content of this message to the given message. +func (message *Message) CopyTo(to *Message) { + to.ID = message.ID + to.Type = message.Type + to.Sender = message.Sender + to.SenderColor = message.SenderColor + to.Timestamp = message.Timestamp + to.Date = message.Date + to.Text = message.Text + to.RecalculateBuffer() +} + +// GetSender gets the string that should be displayed as the sender of this message. +// +// If the message is being sent, the sender is "Sending...". +// If sending has failed, the sender is "Error". +// If the message is an emote, the sender is blank. +// In any other case, the sender is the display name of the user who sent the message. +func (message *Message) GetSender() string { + switch message.State { + case MessageStateSending: + return "Sending..." + case MessageStateFailed: + return "Error" + } + switch message.Type { + case "m.emote": + // Emotes don't show a separate sender, it's included in the buffer. + return "" + default: + return message.Sender + } +} + +// GetSenderColor returns the color the name of the sender should be shown in. +// +// If the message is being sent, the color is gray. +// If sending has failed, the color is red. +// +// In any other case, the color is whatever is specified in the Message struct. +// Usually that means it is the hash-based color of the sender (see ui/widget/color.go) +func (message *Message) GetSenderColor() tcell.Color { + switch message.State { + case MessageStateSending: + return tcell.ColorGray + case MessageStateFailed: + return tcell.ColorRed + case MessageStateDefault: + fallthrough + default: + return message.SenderColor + } +} + +// GetTextColor returns the color the actual content of the message should be shown in. +// +// As with GetSenderColor(), messages being sent and messages that failed to be sent are +// gray and red respectively. +// +// However, other messages are the default color instead of a color stored in the struct. +func (message *Message) GetTextColor() tcell.Color { + switch message.State { + case MessageStateSending: + return tcell.ColorGray + case MessageStateFailed: + return tcell.ColorRed + default: + return tcell.ColorDefault + } +} + +// GetTimestampColor returns the color the timestamp should be shown in. +// +// Currently, this simply calls GetTextColor(). +func (message *Message) GetTimestampColor() tcell.Color { + return message.GetTextColor() +} + +// RecalculateBuffer calculates the buffer again with the previously provided width. +func (message *Message) RecalculateBuffer() { + message.CalculateBuffer(message.prevBufferWidth) +} + +// Buffer returns the computed text buffer. +// +// The buffer contains the text of the message split into lines with a maximum +// width of whatever was provided to CalculateBuffer(). +// +// N.B. This will NOT automatically calculate the buffer if it hasn't been +// calculated already, as that requires the target width. +func (message *Message) Buffer() []string { + return message.buffer +} + +// Height returns the number of rows in the computed buffer (see Buffer()). +func (message *Message) Height() int { + return len(message.buffer) +} + +// GetTimestamp returns the formatted time when the message was sent. +func (message *Message) GetTimestamp() string { + return message.Timestamp +} + +// GetDate returns the formatted date when the message was sent. +func (message *Message) GetDate() string { + return message.Date +} + +// Regular expressions used to split lines when calculating the buffer. +// +// From tview/textview.go var ( boundaryPattern = regexp.MustCompile("([[:punct:]]\\s*|\\s+)") spacePattern = regexp.MustCompile(`\s+`) ) -func (message *Message) CopyTo(to *Message) { - to.BasicMeta = message.BasicMeta - to.ID = message.ID - to.Text = message.Text - to.RecalculateBuffer() -} - +// CalculateBuffer generates the internal buffer for this message that consists +// of the text of this message split into lines at most as wide as the width +// parameter. func (message *Message) CalculateBuffer(width int) { if width < 2 { return } + message.buffer = []string{} text := message.Text if message.Type == "m.emote" { text = fmt.Sprintf("* %s %s", message.Sender, message.Text) } + forcedLinebreaks := strings.Split(text, "\n") newlines := 0 for _, str := range forcedLinebreaks { @@ -102,31 +223,3 @@ func (message *Message) CalculateBuffer(width int) { } message.prevBufferWidth = width } - -func (message *Message) GetDisplaySender() string { - if message.sending { - return "Sending..." - } - switch message.Type { - case "m.emote": - return "" - default: - return message.Sender - } -} - -func (message *Message) SetIsSending(sending bool) { - message.sending = sending -} - -func (message *Message) RecalculateBuffer() { - message.CalculateBuffer(message.prevBufferWidth) -} - -func (message *Message) Buffer() []string { - return message.buffer -} - -func (message *Message) Height() int { - return len(message.buffer) -} diff --git a/ui/types/meta.go b/ui/types/meta.go index 03e18d6..fdc6dba 100644 --- a/ui/types/meta.go +++ b/ui/types/meta.go @@ -20,9 +20,11 @@ import ( "github.com/gdamore/tcell" ) +// MessageMeta is an interface to get the metadata of a message. +// +// See BasicMeta for a simple implementation and documentation of methods. type MessageMeta interface { GetSender() string - GetDisplaySender() string GetSenderColor() tcell.Color GetTextColor() tcell.Color GetTimestampColor() tcell.Color @@ -30,35 +32,40 @@ type MessageMeta interface { GetDate() string } +// BasicMeta is a simple variable store implementation of MessageMeta. type BasicMeta struct { Sender, Timestamp, Date string SenderColor, TextColor, TimestampColor tcell.Color } -func (meta *BasicMeta) GetDisplaySender() string { - return meta.Sender -} - +// GetSender gets the string that should be displayed as the sender of this message. func (meta *BasicMeta) GetSender() string { return meta.Sender } +// GetSenderColor returns the color the name of the sender should be shown in. func (meta *BasicMeta) GetSenderColor() tcell.Color { return meta.SenderColor } +// GetTimestamp returns the formatted time when the message was sent. func (meta *BasicMeta) GetTimestamp() string { return meta.Timestamp } +// GetDate returns the formatted date when the message was sent. func (meta *BasicMeta) GetDate() string { return meta.Date } +// GetTextColor returns the color the actual content of the message should be shown in. func (meta *BasicMeta) GetTextColor() tcell.Color { return meta.TextColor } +// GetTimestampColor returns the color the timestamp should be shown in. +// +// This usually does not apply to the date, as it is rendered separately from the message. func (meta *BasicMeta) GetTimestampColor() tcell.Color { return meta.TimestampColor } diff --git a/ui/widget/message-view.go b/ui/widget/message-view.go index a4ba236..444aa03 100644 --- a/ui/widget/message-view.go +++ b/ui/widget/message-view.go @@ -315,7 +315,6 @@ func (view *MessageView) Draw(screen tcell.Screen) { screen.SetContent(separatorX, separatorY, view.Separator, nil, tcell.StyleDefault) } - var prevMeta types.MessageMeta indexOffset := len(view.textBuffer) - view.ScrollOffset - height if indexOffset <= -PaddingAtTop { message := "Scroll up to load more messages." @@ -324,10 +323,13 @@ func (view *MessageView) Draw(screen tcell.Screen) { } view.writeLine(screen, message, x+messageOffsetX, y, tcell.ColorGreen) } + if len(view.textBuffer) != len(view.metaBuffer) { debug.ExtPrintf("Unexpected text/meta buffer length mismatch: %d != %d.", len(view.textBuffer), len(view.metaBuffer)) return } + + var prevMeta types.MessageMeta for line := 0; line < height; line++ { index := indexOffset + line if index < 0 { @@ -342,7 +344,7 @@ func (view *MessageView) Draw(screen tcell.Screen) { } if prevMeta == nil || meta.GetSender() != prevMeta.GetSender() { view.writeLineRight( - screen, meta.GetDisplaySender(), + screen, meta.GetSender(), x+usernameOffsetX, y+line, view.widestSender, meta.GetSenderColor()) }