aboutsummaryrefslogtreecommitdiff
path: root/ui
diff options
context:
space:
mode:
Diffstat (limited to 'ui')
-rw-r--r--ui/message-view.go88
-rw-r--r--ui/messages/message.go158
-rw-r--r--ui/messages/meta.go21
-rw-r--r--ui/messages/textmessage.go56
4 files changed, 261 insertions, 62 deletions
diff --git a/ui/message-view.go b/ui/message-view.go
index f9d477b..7b18ad8 100644
--- a/ui/message-view.go
+++ b/ui/message-view.go
@@ -49,22 +49,20 @@ type MessageView struct {
messageIDs map[string]messages.UIMessage
messages []messages.UIMessage
- textBuffer []string
+ textBuffer []messages.UIString
metaBuffer []ifc.MessageMeta
}
func NewMessageView() *MessageView {
return &MessageView{
- Box: tview.NewBox(),
- MaxSenderWidth: 15,
- DateFormat: "January _2, 2006",
- TimestampFormat: "15:04:05",
- TimestampWidth: 8,
- ScrollOffset: 0,
+ Box: tview.NewBox(),
+ MaxSenderWidth: 15,
+ TimestampWidth: len(messages.TimeFormat),
+ ScrollOffset: 0,
messages: make([]messages.UIMessage, 0),
messageIDs: make(map[string]messages.UIMessage),
- textBuffer: make([]string, 0),
+ textBuffer: make([]messages.UIString, 0),
metaBuffer: make([]ifc.MessageMeta, 0),
widestSender: 5,
@@ -75,10 +73,7 @@ func NewMessageView() *MessageView {
}
func (view *MessageView) NewMessage(id, sender, msgtype, text string, timestamp time.Time) messages.UIMessage {
- return messages.NewMessage(id, sender, msgtype, text,
- timestamp.Format(view.TimestampFormat),
- timestamp.Format(view.DateFormat),
- widget.GetHashColor(sender))
+ return messages.NewMessage(id, sender, msgtype, text, timestamp, widget.GetHashColor(sender))
}
func (view *MessageView) SaveHistory(path string) error {
@@ -152,10 +147,10 @@ func (view *MessageView) AddMessage(ifcMessage ifc.Message, direction ifc.Messag
return
}
- msg, messageExists := view.messageIDs[message.ID()]
- if msg != nil && messageExists {
- msg.CopyFrom(message)
- message = msg
+ oldMsg, messageExists := view.messageIDs[message.ID()]
+ if messageExists {
+ oldMsg.CopyFrom(message)
+ message = oldMsg
direction = ifc.IgnoreMessage
}
@@ -173,6 +168,8 @@ func (view *MessageView) AddMessage(ifcMessage ifc.Message, direction ifc.Messag
view.appendBuffer(message)
} else if direction == ifc.PrependMessage {
view.messages = append([]messages.UIMessage{message}, view.messages...)
+ } else {
+ view.replaceBuffer(message)
}
view.messageIDs[message.ID()] = message
@@ -181,9 +178,12 @@ func (view *MessageView) AddMessage(ifcMessage ifc.Message, direction ifc.Messag
func (view *MessageView) appendBuffer(message messages.UIMessage) {
if len(view.metaBuffer) > 0 {
prevMeta := view.metaBuffer[len(view.metaBuffer)-1]
- if prevMeta != nil && prevMeta.Date() != message.Date() {
- view.textBuffer = append(view.textBuffer, fmt.Sprintf("Date changed to %s", message.Date()))
- view.metaBuffer = append(view.metaBuffer, &messages.BasicMeta{BTextColor: tcell.ColorGreen})
+ if prevMeta != nil && prevMeta.FormatDate() != message.FormatDate() {
+ view.textBuffer = append(view.textBuffer, messages.NewColorUIString(
+ fmt.Sprintf("Date changed to %s", message.FormatDate()),
+ tcell.ColorGreen))
+ view.metaBuffer = append(view.metaBuffer, &messages.BasicMeta{
+ BTimestampColor: tcell.ColorDefault, BTextColor: tcell.ColorGreen})
}
}
@@ -194,13 +194,42 @@ func (view *MessageView) appendBuffer(message messages.UIMessage) {
view.prevMsgCount++
}
+func (view *MessageView) replaceBuffer(message messages.UIMessage) {
+ start := -1
+ end := -1
+ for index, meta := range view.metaBuffer {
+ if meta == message {
+ if start == -1 {
+ start = index
+ }
+ end = index
+ } else if start != -1 {
+ break
+ }
+ }
+
+ if len(view.textBuffer) > end {
+ end++
+ }
+
+ view.textBuffer = append(append(view.textBuffer[0:start], message.Buffer()...), view.textBuffer[end:]...)
+ if len(message.Buffer()) != end - start + 1 {
+ debug.Print(end, "-", start, "!=", len(message.Buffer()))
+ metaBuffer := view.metaBuffer[0:start]
+ for range message.Buffer() {
+ metaBuffer = append(metaBuffer, message)
+ }
+ view.metaBuffer = append(metaBuffer, view.metaBuffer[end:]...)
+ }
+}
+
func (view *MessageView) recalculateBuffers() {
_, _, width, height := view.GetInnerRect()
width -= view.TimestampWidth + TimestampSenderGap + view.widestSender + SenderMessageGap
recalculateMessageBuffers := width != view.prevWidth
if height != view.prevHeight || recalculateMessageBuffers || len(view.messages) != view.prevMsgCount {
- view.textBuffer = []string{}
+ view.textBuffer = []messages.UIString{}
view.metaBuffer = []ifc.MessageMeta{}
view.prevMsgCount = 0
for _, message := range view.messages {
@@ -219,7 +248,7 @@ const PaddingAtTop = 5
func (view *MessageView) AddScrollOffset(diff int) {
_, _, _, height := view.GetInnerRect()
- totalHeight := len(view.textBuffer)
+ totalHeight := view.TotalHeight()
if diff >= 0 && view.ScrollOffset+diff >= totalHeight-height+PaddingAtTop {
view.ScrollOffset = totalHeight - height + PaddingAtTop
} else {
@@ -285,7 +314,7 @@ func (view *MessageView) Draw(screen tcell.Screen) {
x, y, _, height := view.GetInnerRect()
view.recalculateBuffers()
- if len(view.textBuffer) == 0 {
+ if view.TotalHeight() == 0 {
widget.WriteLineSimple(screen, "It's quite empty in here.", x, y+height)
return
}
@@ -294,7 +323,7 @@ func (view *MessageView) Draw(screen tcell.Screen) {
messageX := usernameX + view.widestSender + SenderMessageGap
separatorX := usernameX + view.widestSender + SenderSeparatorGap
- indexOffset := len(view.textBuffer) - view.ScrollOffset - height
+ indexOffset := view.TotalHeight() - view.ScrollOffset - height
if indexOffset <= -PaddingAtTop {
message := "Scroll up to load more messages."
if view.LoadingMessages {
@@ -312,7 +341,7 @@ func (view *MessageView) Draw(screen tcell.Screen) {
// Black magic (aka math) used to figure out where the scroll bar should be put.
{
viewportHeight := float64(height)
- contentHeight := float64(len(view.textBuffer))
+ contentHeight := float64(view.TotalHeight())
scrollBarHeight = int(math.Ceil(viewportHeight / (contentHeight / viewportHeight)))
@@ -328,12 +357,12 @@ func (view *MessageView) Draw(screen tcell.Screen) {
if index < 0 {
skippedLines++
continue
- } else if index >= len(view.textBuffer) {
+ } else if index >= view.TotalHeight() {
break
}
showScrollbar := line-skippedLines >= scrollBarPos-scrollBarHeight && line-skippedLines < scrollBarPos
- isTop := firstLine && view.ScrollOffset+height >= len(view.textBuffer)
+ isTop := firstLine && view.ScrollOffset+height >= view.TotalHeight()
isBottom := line == height-1 && view.ScrollOffset == 0
borderChar, borderStyle := getScrollbarStyle(showScrollbar, isTop, isBottom)
@@ -344,8 +373,8 @@ func (view *MessageView) Draw(screen tcell.Screen) {
text, meta := view.textBuffer[index], view.metaBuffer[index]
if meta != prevMeta {
- if len(meta.Timestamp()) > 0 {
- widget.WriteLineSimpleColor(screen, meta.Timestamp(), x, y+line, meta.TimestampColor())
+ if len(meta.FormatTime()) > 0 {
+ widget.WriteLineSimpleColor(screen, meta.FormatTime(), x, y+line, meta.TimestampColor())
}
if prevMeta == nil || meta.Sender() != prevMeta.Sender() {
widget.WriteLineColor(
@@ -355,6 +384,7 @@ func (view *MessageView) Draw(screen tcell.Screen) {
}
prevMeta = meta
}
- widget.WriteLineSimpleColor(screen, text, messageX, y+line, meta.TextColor())
+
+ text.Draw(screen, messageX, y+line)
}
}
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]]
}