Start refactoring various things in message rendering and sending

This commit is contained in:
Tulir Asokan 2019-04-09 18:45:41 +03:00
parent 2b6c435e50
commit dbee49476d
13 changed files with 178 additions and 320 deletions

View File

@ -19,11 +19,9 @@ package ifc
import ( import (
"time" "time"
"maunium.net/go/mautrix"
"maunium.net/go/tcell"
"maunium.net/go/gomuks/matrix/pushrules" "maunium.net/go/gomuks/matrix/pushrules"
"maunium.net/go/gomuks/matrix/rooms" "maunium.net/go/gomuks/matrix/rooms"
"maunium.net/go/mautrix"
) )
type UIProvider func(gmx Gomuks) GomuksUI type UIProvider func(gmx Gomuks) GomuksUI
@ -50,7 +48,6 @@ type MainView interface {
UpdateTags(room *rooms.Room) UpdateTags(room *rooms.Room)
SetTyping(roomID string, users []string) SetTyping(roomID string, users []string)
ParseEvent(roomView RoomView, evt *mautrix.Event) Message
NotifyMessage(room *rooms.Room, message Message, should pushrules.PushActionArrayShould) NotifyMessage(room *rooms.Room, message Message, should pushrules.PushActionArrayShould)
InitialSyncDone() InitialSyncDone()
@ -71,50 +68,17 @@ type RoomView interface {
SetTyping(users []string) SetTyping(users []string)
UpdateUserList() UpdateUserList()
NewTempMessage(msgtype mautrix.MessageType, text string) Message ParseEvent(evt *mautrix.Event) Message
AddMessage(message Message, direction MessageDirection) AppendMessage(message Message)
MarkMessageFailed(message Message)
AddServiceMessage(message string) AddServiceMessage(message string)
} }
type MessageMeta interface {
Sender() string
SenderColor() tcell.Color
TextColor() tcell.Color
TimestampColor() tcell.Color
Timestamp() time.Time
FormatTime() string
FormatDate() string
}
// 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
)
type Message interface { type Message interface {
MessageMeta
SetIsHighlight(isHighlight bool)
IsHighlight() bool
SetIsService(isService bool)
IsService() bool
SetID(id string)
ID() string ID() string
TxnID() string
SetType(msgtype mautrix.MessageType)
Type() mautrix.MessageType
NotificationContent() string
SetState(state MessageState)
State() MessageState
SenderID() string SenderID() string
Timestamp() time.Time
NotificationSenderName() string
NotificationContent() string
} }

View File

@ -303,6 +303,7 @@ func (c *Container) HandleMessage(source EventSource, evt *mautrix.Event) {
debug.Printf("Failed to add event %s to history: %v", evt.ID, err) debug.Printf("Failed to add event %s to history: %v", evt.ID, err)
} }
// TODO switch to roomView.AddEvent
message := mainView.ParseEvent(roomView, evt) message := mainView.ParseEvent(roomView, evt)
if message != nil { if message != nil {
roomView.AddMessage(message, ifc.AppendMessage) roomView.AddMessage(message, ifc.AppendMessage)
@ -494,10 +495,7 @@ func (c *Container) SendMessage(roomID string, msgtype mautrix.MessageType, text
var mentionRegex = regexp.MustCompile("\\[(.+?)]\\(https://matrix.to/#/@.+?:.+?\\)") var mentionRegex = regexp.MustCompile("\\[(.+?)]\\(https://matrix.to/#/@.+?:.+?\\)")
var roomRegex = regexp.MustCompile("\\[.+?]\\(https://matrix.to/#/(#.+?:[^/]+?)\\)") var roomRegex = regexp.MustCompile("\\[.+?]\\(https://matrix.to/#/(#.+?:[^/]+?)\\)")
// SendMarkdownMessage sends a message with the given text to the given room. // SendMarkdownMessage sends a message with the given markdown text to the given room.
//
// If the given text contains markdown formatting symbols, it will be rendered into HTML before sending.
// Otherwise, it will be sent as plain text.
func (c *Container) SendMarkdownMessage(roomID string, msgtype mautrix.MessageType, text string) (string, error) { func (c *Container) SendMarkdownMessage(roomID string, msgtype mautrix.MessageType, text string) (string, error) {
defer debug.Recover() defer debug.Recover()
@ -509,8 +507,23 @@ func (c *Container) SendMarkdownMessage(roomID string, msgtype mautrix.MessageTy
content.Body = roomRegex.ReplaceAllString(content.Body, "$1") content.Body = roomRegex.ReplaceAllString(content.Body, "$1")
c.SendTyping(roomID, false) c.SendTyping(roomID, false)
resp, err := c.client.SendMessageEvent(roomID, mautrix.EventMessage, content) roomView := c.ui.MainView().GetRoom(roomID)
txnID := c.client.TxnID()
localEcho := roomView.ParseEvent(&mautrix.Event{
Sender: c.config.UserID,
Type: mautrix.EventMessage,
Timestamp: time.Now().UnixNano() / 1e6,
RoomID: roomID,
Content: content,
Unsigned: mautrix.Unsigned{
TransactionID: txnID,
OutgoingState: mautrix.EventStateLocalEcho,
},
})
roomView.AppendMessage(localEcho)
resp, err := c.client.SendMessageEvent(roomID, mautrix.EventMessage, content, mautrix.ReqSendEvent{TransactionID: txnID})
if err != nil { if err != nil {
roomView.MarkMessageFailed(localEcho, err)
return "", err return "", err
} }
return resp.EventID, nil return resp.EventID, nil

View File

@ -31,7 +31,6 @@ import (
"maunium.net/go/gomuks/interface" "maunium.net/go/gomuks/interface"
"maunium.net/go/gomuks/lib/open" "maunium.net/go/gomuks/lib/open"
"maunium.net/go/gomuks/ui/messages" "maunium.net/go/gomuks/ui/messages"
"maunium.net/go/gomuks/ui/messages/tstring"
"maunium.net/go/gomuks/ui/widget" "maunium.net/go/gomuks/ui/widget"
) )
@ -57,8 +56,7 @@ type MessageView struct {
messageIDs map[string]messages.UIMessage messageIDs map[string]messages.UIMessage
messages []messages.UIMessage messages []messages.UIMessage
textBuffer []tstring.TString msgBuffer []messages.UIMessage
metaBuffer []ifc.MessageMeta
} }
func NewMessageView(parent *RoomView) *MessageView { func NewMessageView(parent *RoomView) *MessageView {
@ -72,8 +70,7 @@ func NewMessageView(parent *RoomView) *MessageView {
messages: make([]messages.UIMessage, 0), messages: make([]messages.UIMessage, 0),
messageIDs: make(map[string]messages.UIMessage), messageIDs: make(map[string]messages.UIMessage),
textBuffer: make([]tstring.TString, 0), msgBuffer: make([]messages.UIMessage, 0),
metaBuffer: make([]ifc.MessageMeta, 0),
width: 80, width: 80,
widestSender: 5, widestSender: 5,
@ -115,10 +112,16 @@ func (view *MessageView) AddMessage(ifcMessage ifc.Message, direction ifc.Messag
return return
} }
oldMsg, messageExists := view.messageIDs[message.ID()]
if messageExists { var oldMsg messages.UIMessage
var messageExists bool
if oldMsg, messageExists = view.messageIDs[message.ID()]; messageExists {
view.replaceMessage(oldMsg, message) view.replaceMessage(oldMsg, message)
direction = ifc.IgnoreMessage direction = ifc.IgnoreMessage
} else if oldMsg, messageExists = view.messageIDs[message.TxnID()]; messageExists {
view.replaceMessage(oldMsg, message)
delete(view.messageIDs, message.TxnID())
direction = ifc.IgnoreMessage
} }
view.updateWidestSender(message.Sender()) view.updateWidestSender(message.Sender())
@ -149,20 +152,19 @@ func (view *MessageView) AddMessage(ifcMessage ifc.Message, direction ifc.Messag
} }
func (view *MessageView) appendBuffer(message messages.UIMessage) { func (view *MessageView) appendBuffer(message messages.UIMessage) {
if len(view.metaBuffer) > 0 { if len(view.msgBuffer) > 0 {
prevMeta := view.metaBuffer[len(view.metaBuffer)-1] prevMeta := view.msgBuffer[len(view.msgBuffer)-1]
if prevMeta != nil && prevMeta.FormatDate() != message.FormatDate() { if prevMeta != nil && prevMeta.FormatDate() != message.FormatDate() {
view.textBuffer = append(view.textBuffer, tstring.NewColorTString( /* FIXME view.textBuffer = append(view.textBuffer, tstring.NewColorTString(
fmt.Sprintf("Date changed to %s", message.FormatDate()), fmt.Sprintf("Date changed to %s", message.FormatDate()),
tcell.ColorGreen)) tcell.ColorGreen))
view.metaBuffer = append(view.metaBuffer, &messages.BasicMeta{ view.msgBuffer = append(view.msgBuffer, &messages.BasicMeta{
BTimestampColor: tcell.ColorDefault, BTextColor: tcell.ColorGreen}) BTimestampColor: tcell.ColorDefault, BTextColor: tcell.ColorGreen})*/
} }
} }
for i := 0; i < message.Height(); i++ { for i := 0; i < message.Height(); i++ {
view.textBuffer = append(view.textBuffer, nil) view.msgBuffer = append(view.msgBuffer, message)
view.metaBuffer = append(view.metaBuffer, message)
} }
view.prevMsgCount++ view.prevMsgCount++
} }
@ -179,7 +181,7 @@ func (view *MessageView) replaceMessage(original messages.UIMessage, new message
func (view *MessageView) replaceBuffer(original messages.UIMessage, new messages.UIMessage) { func (view *MessageView) replaceBuffer(original messages.UIMessage, new messages.UIMessage) {
start := -1 start := -1
end := -1 end := -1
for index, meta := range view.metaBuffer { for index, meta := range view.msgBuffer {
if meta == original { if meta == original {
if start == -1 { if start == -1 {
start = index start = index
@ -197,7 +199,7 @@ func (view *MessageView) replaceBuffer(original messages.UIMessage, new messages
return return
} }
if len(view.textBuffer) > end { if len(view.msgBuffer) > end {
end++ end++
} }
@ -205,17 +207,15 @@ func (view *MessageView) replaceBuffer(original messages.UIMessage, new messages
new.CalculateBuffer(view.prevPrefs, view.prevWidth) new.CalculateBuffer(view.prevPrefs, view.prevWidth)
} }
textBuf := make([]tstring.TString, new.Height())
view.textBuffer = append(append(view.textBuffer[0:start], textBuf...), view.textBuffer[end:]...)
if new.Height() != end-start { if new.Height() != end-start {
metaBuffer := view.metaBuffer[0:start] metaBuffer := view.msgBuffer[0:start]
for i := 0; i < new.Height(); i++ { for i := 0; i < new.Height(); i++ {
metaBuffer = append(metaBuffer, new) metaBuffer = append(metaBuffer, new)
} }
view.metaBuffer = append(metaBuffer, view.metaBuffer[end:]...) view.msgBuffer = append(metaBuffer, view.msgBuffer[end:]...)
} else { } else {
for i := start; i < end; i++ { for i := start; i < end; i++ {
view.metaBuffer[i] = new view.msgBuffer[i] = new
} }
} }
} }
@ -230,8 +230,7 @@ func (view *MessageView) recalculateBuffers() {
if !prefs.BareMessageView { if !prefs.BareMessageView {
width -= view.TimestampWidth + TimestampSenderGap + view.widestSender + SenderMessageGap width -= view.TimestampWidth + TimestampSenderGap + view.widestSender + SenderMessageGap
} }
view.textBuffer = []tstring.TString{} view.msgBuffer = []messages.UIMessage{}
view.metaBuffer = []ifc.MessageMeta{}
view.prevMsgCount = 0 view.prevMsgCount = 0
for i, message := range view.messages { for i, message := range view.messages {
if message == nil { if message == nil {
@ -249,7 +248,7 @@ func (view *MessageView) recalculateBuffers() {
view.prevPrefs = prefs view.prevPrefs = prefs
} }
func (view *MessageView) handleMessageClick(message ifc.MessageMeta) bool { func (view *MessageView) handleMessageClick(message messages.UIMessage) bool {
switch message := message.(type) { switch message := message.(type) {
case *messages.ImageMessage: case *messages.ImageMessage:
open.Open(message.Path()) open.Open(message.Path())
@ -259,21 +258,15 @@ func (view *MessageView) handleMessageClick(message ifc.MessageMeta) bool {
return false return false
} }
func (view *MessageView) handleUsernameClick(message ifc.MessageMeta, prevMessage ifc.MessageMeta) bool { func (view *MessageView) handleUsernameClick(message messages.UIMessage, prevMessage messages.UIMessage) bool {
uiMessage, ok := message.(messages.UIMessage) if prevMessage != nil && prevMessage.Sender() == message.Sender() {
if !ok {
return false return false
} }
prevUIMessage, _ := prevMessage.(messages.UIMessage) if len(message.Sender()) == 0 {
if prevUIMessage != nil && prevUIMessage.Sender() == uiMessage.Sender() {
return false return false
} }
sender := fmt.Sprintf("[%s](https://matrix.to/#/%s)", message.Sender(), message.SenderID())
if len(uiMessage.Sender()) == 0 {
return false
}
sender := fmt.Sprintf("[%s](https://matrix.to/#/%s)", uiMessage.Sender(), uiMessage.SenderID())
cursorPos := view.parent.input.GetCursorOffset() cursorPos := view.parent.input.GetCursorOffset()
text := view.parent.input.GetText() text := view.parent.input.GetText()
@ -317,10 +310,10 @@ func (view *MessageView) OnMouseEvent(event mauview.MouseEvent) bool {
return false return false
} }
message := view.metaBuffer[line] message := view.msgBuffer[line]
var prevMessage ifc.MessageMeta var prevMessage messages.UIMessage
if y != 0 && line > 0 { if y != 0 && line > 0 {
prevMessage = view.metaBuffer[line-1] prevMessage = view.msgBuffer[line-1]
} }
usernameX := view.TimestampWidth + TimestampSenderGap usernameX := view.TimestampWidth + TimestampSenderGap
@ -358,12 +351,11 @@ func (view *MessageView) Height() int {
} }
func (view *MessageView) TotalHeight() int { func (view *MessageView) TotalHeight() int {
return len(view.textBuffer) return len(view.msgBuffer)
} }
func (view *MessageView) IsAtTop() bool { func (view *MessageView) IsAtTop() bool {
totalHeight := len(view.textBuffer) return view.ScrollOffset >= len(view.msgBuffer)-view.height+PaddingAtTop
return view.ScrollOffset >= totalHeight-view.height+PaddingAtTop
} }
const ( const (
@ -429,7 +421,7 @@ func (view *MessageView) CapturePlaintext(height int) string {
continue continue
} }
meta := view.metaBuffer[index] meta := view.msgBuffer[index]
message, ok := meta.(messages.UIMessage) message, ok := meta.(messages.UIMessage)
if ok && message != prevMessage { if ok && message != prevMessage {
var sender string var sender string
@ -456,7 +448,6 @@ func (view *MessageView) Draw(screen mauview.Screen) {
usernameX := view.TimestampWidth + TimestampSenderGap usernameX := view.TimestampWidth + TimestampSenderGap
messageX := usernameX + view.widestSender + SenderMessageGap messageX := usernameX + view.widestSender + SenderMessageGap
separatorX := usernameX + view.widestSender + SenderSeparatorGap
bareMode := view.config.Preferences.BareMessageView bareMode := view.config.Preferences.BareMessageView
if bareMode { if bareMode {
@ -465,20 +456,15 @@ func (view *MessageView) Draw(screen mauview.Screen) {
indexOffset := view.getIndexOffset(screen, view.height, messageX) indexOffset := view.getIndexOffset(screen, view.height, messageX)
if len(view.textBuffer) != len(view.metaBuffer) {
debug.Printf("Unexpected text/meta buffer length mismatch: %d != %d.", len(view.textBuffer), len(view.metaBuffer))
view.prevMsgCount = 0
return
}
scrollBarHeight, scrollBarPos := view.calculateScrollBar(view.height)
var prevMeta ifc.MessageMeta
viewStart := 0 viewStart := 0
if indexOffset < 0 { if indexOffset < 0 {
viewStart = -indexOffset viewStart = -indexOffset
} }
if !bareMode {
separatorX := usernameX + view.widestSender + SenderSeparatorGap
scrollBarHeight, scrollBarPos := view.calculateScrollBar(view.height)
for line := viewStart; line < view.height; line++ { for line := viewStart; line < view.height; line++ {
showScrollbar := line-viewStart >= scrollBarPos-scrollBarHeight && line-viewStart < scrollBarPos showScrollbar := line-viewStart >= scrollBarPos-scrollBarHeight && line-viewStart < scrollBarPos
isTop := line == viewStart && view.ScrollOffset+view.height >= view.TotalHeight() isTop := line == viewStart && view.ScrollOffset+view.height >= view.TotalHeight()
@ -486,38 +472,33 @@ func (view *MessageView) Draw(screen mauview.Screen) {
borderChar, borderStyle := getScrollbarStyle(showScrollbar, isTop, isBottom) borderChar, borderStyle := getScrollbarStyle(showScrollbar, isTop, isBottom)
if !bareMode {
screen.SetContent(separatorX, line, borderChar, nil, borderStyle) screen.SetContent(separatorX, line, borderChar, nil, borderStyle)
} }
} }
var prevMsg messages.UIMessage
for line := viewStart; line < view.height && indexOffset+line < view.TotalHeight(); line++ { for line := viewStart; line < view.height && indexOffset+line < view.TotalHeight(); line++ {
index := indexOffset + line index := indexOffset + line
text, meta := view.textBuffer[index], view.metaBuffer[index] msg := view.msgBuffer[index]
if meta != prevMeta { if msg != prevMsg {
if len(meta.FormatTime()) > 0 { if len(msg.FormatTime()) > 0 {
widget.WriteLineSimpleColor(screen, meta.FormatTime(), 0, line, meta.TimestampColor()) widget.WriteLineSimpleColor(screen, msg.FormatTime(), 0, line, msg.TimestampColor())
} }
// TODO hiding senders might not be that nice after all, maybe an option? (disabled for now) // TODO hiding senders might not be that nice after all, maybe an option? (disabled for now)
//if !bareMode && (prevMeta == nil || meta.Sender() != prevMeta.Sender()) { //if !bareMode && (prevMsg == nil || meta.Sender() != prevMsg.Sender()) {
widget.WriteLineColor( widget.WriteLineColor(
screen, mauview.AlignRight, meta.Sender(), screen, mauview.AlignRight, msg.Sender(),
usernameX, line, view.widestSender, usernameX, line, view.widestSender,
meta.SenderColor()) msg.SenderColor())
//} //}
prevMeta = meta prevMsg = msg
} }
message, ok := meta.(messages.UIMessage) for i := index - 1; i >= 0 && view.msgBuffer[i] == msg; i-- {
if ok {
for i := index - 1; i >= 0 && view.metaBuffer[i] == meta; i-- {
line-- line--
} }
message.Draw(mauview.NewProxyScreen(screen, messageX, line, view.width-messageX, message.Height())) msg.Draw(mauview.NewProxyScreen(screen, messageX, line, view.width-messageX, msg.Height()))
line += message.Height() - 1 line += msg.Height() - 1
} else {
text.Draw(screen, messageX, line)
}
} }
} }

View File

@ -17,7 +17,7 @@
package messages package messages
import ( import (
"encoding/gob" "encoding/json"
"time" "time"
"maunium.net/go/mautrix" "maunium.net/go/mautrix"
@ -29,38 +29,51 @@ import (
"maunium.net/go/gomuks/ui/widget" "maunium.net/go/gomuks/ui/widget"
) )
func init() {
gob.Register(&BaseMessage{})
}
type BaseMessage struct { type BaseMessage struct {
MsgID string MsgID string
MsgTxnID string
MsgType mautrix.MessageType MsgType mautrix.MessageType
MsgSenderID string MsgSenderID string
MsgSender string MsgSender string
MsgSenderColor tcell.Color MsgSenderColor tcell.Color
MsgTimestamp time.Time MsgTimestamp time.Time
MsgState ifc.MessageState MsgState mautrix.OutgoingEventState
MsgIsHighlight bool MsgIsHighlight bool
MsgIsService bool MsgIsService bool
MsgSource json.RawMessage
buffer []tstring.TString buffer []tstring.TString
plainBuffer []tstring.TString plainBuffer []tstring.TString
} }
func newBaseMessage(id, sender, displayname string, msgtype mautrix.MessageType, timestamp time.Time) BaseMessage { func newBaseMessage(event *mautrix.Event, displayname string) BaseMessage {
msgtype := event.Content.MsgType
if len(msgtype) == 0 {
msgtype = mautrix.MessageType(event.Type.String())
}
return BaseMessage{ return BaseMessage{
MsgSenderID: sender, MsgSenderID: event.Sender,
MsgSender: displayname, MsgSender: displayname,
MsgTimestamp: timestamp, MsgTimestamp: unixToTime(event.Timestamp),
MsgSenderColor: widget.GetHashColor(sender), MsgSenderColor: widget.GetHashColor(event.Sender),
MsgType: msgtype, MsgType: msgtype,
MsgID: id, MsgID: event.ID,
MsgState: ifc.MessageStateDefault, MsgTxnID: event.Unsigned.TransactionID,
MsgState: event.Unsigned.OutgoingState,
MsgIsHighlight: false, MsgIsHighlight: false,
MsgIsService: false, MsgIsService: false,
MsgSource: event.Content.VeryRaw,
} }
} }
func unixToTime(unix int64) time.Time {
timestamp := time.Now()
if unix != 0 {
timestamp = time.Unix(unix/1000, unix%1000*1000)
}
return timestamp
}
func (msg *BaseMessage) RegisterMatrix(matrix ifc.MatrixContainer) {} func (msg *BaseMessage) RegisterMatrix(matrix ifc.MatrixContainer) {}
// Sender gets the string that should be displayed as the sender of this message. // Sender gets the string that should be displayed as the sender of this message.
@ -71,9 +84,9 @@ func (msg *BaseMessage) RegisterMatrix(matrix ifc.MatrixContainer) {}
// In any other case, the sender is the display name of the user who sent the message. // In any other case, the sender is the display name of the user who sent the message.
func (msg *BaseMessage) Sender() string { func (msg *BaseMessage) Sender() string {
switch msg.MsgState { switch msg.MsgState {
case ifc.MessageStateSending: case mautrix.EventStateLocalEcho:
return "Sending..." return "Sending..."
case ifc.MessageStateFailed: case mautrix.EventStateSendFail:
return "Error" return "Error"
} }
switch msg.MsgType { switch msg.MsgType {
@ -93,13 +106,17 @@ func (msg *BaseMessage) RealSender() string {
return msg.MsgSender return msg.MsgSender
} }
func (msg *BaseMessage) NotificationSenderName() string {
return msg.MsgSender
}
func (msg *BaseMessage) getStateSpecificColor() tcell.Color { func (msg *BaseMessage) getStateSpecificColor() tcell.Color {
switch msg.MsgState { switch msg.MsgState {
case ifc.MessageStateSending: case mautrix.EventStateLocalEcho:
return tcell.ColorGray return tcell.ColorGray
case ifc.MessageStateFailed: case mautrix.EventStateSendFail:
return tcell.ColorRed return tcell.ColorRed
case ifc.MessageStateDefault: case mautrix.EventStateDefault:
fallthrough fallthrough
default: default:
return tcell.ColorDefault return tcell.ColorDefault
@ -154,17 +171,6 @@ func (msg *BaseMessage) TimestampColor() tcell.Color {
return msg.getStateSpecificColor() return msg.getStateSpecificColor()
} }
// 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 (msg *BaseMessage) Buffer() []tstring.TString {
return msg.buffer
}
// Height returns the number of rows in the computed buffer (see Buffer()). // Height returns the number of rows in the computed buffer (see Buffer()).
func (msg *BaseMessage) Height() int { func (msg *BaseMessage) Height() int {
return len(msg.buffer) return len(msg.buffer)
@ -197,15 +203,11 @@ func (msg *BaseMessage) Type() mautrix.MessageType {
return msg.MsgType return msg.MsgType
} }
func (msg *BaseMessage) SetType(msgtype mautrix.MessageType) { func (msg *BaseMessage) State() mautrix.OutgoingEventState {
msg.MsgType = msgtype
}
func (msg *BaseMessage) State() ifc.MessageState {
return msg.MsgState return msg.MsgState
} }
func (msg *BaseMessage) SetState(state ifc.MessageState) { func (msg *BaseMessage) SetState(state mautrix.OutgoingEventState) {
msg.MsgState = state msg.MsgState = state
} }
@ -225,6 +227,10 @@ func (msg *BaseMessage) SetIsService(isService bool) {
msg.MsgIsService = isService msg.MsgIsService = isService
} }
func (msg *BaseMessage) Source() json.RawMessage {
return msg.MsgSource
}
func (msg *BaseMessage) Draw(screen mauview.Screen) { func (msg *BaseMessage) Draw(screen mauview.Screen) {
for y, line := range msg.buffer { for y, line := range msg.buffer {
line.Draw(screen, 0, y) line.Draw(screen, 0, y)

View File

@ -17,28 +17,21 @@
package messages package messages
import ( import (
"encoding/gob"
"time"
"maunium.net/go/mautrix" "maunium.net/go/mautrix"
"maunium.net/go/gomuks/config" "maunium.net/go/gomuks/config"
"maunium.net/go/gomuks/ui/messages/tstring" "maunium.net/go/gomuks/ui/messages/tstring"
) )
func init() {
gob.Register(&ExpandedTextMessage{})
}
type ExpandedTextMessage struct { type ExpandedTextMessage struct {
BaseMessage BaseMessage
MsgText tstring.TString MsgText tstring.TString
} }
// NewExpandedTextMessage creates a new ExpandedTextMessage object with the provided values and the default state. // NewExpandedTextMessage creates a new ExpandedTextMessage object with the provided values and the default state.
func NewExpandedTextMessage(id, sender, displayname string, msgtype mautrix.MessageType, text tstring.TString, timestamp time.Time) UIMessage { func NewExpandedTextMessage(event *mautrix.Event, displayname string, text tstring.TString) UIMessage {
return &ExpandedTextMessage{ return &ExpandedTextMessage{
BaseMessage: newBaseMessage(id, sender, displayname, msgtype, timestamp), BaseMessage: newBaseMessage(event, displayname),
MsgText: text, MsgText: text,
} }
} }

View File

@ -17,13 +17,12 @@
package messages package messages
import ( import (
"maunium.net/go/gomuks/ui/messages/html"
"maunium.net/go/mautrix" "maunium.net/go/mautrix"
"maunium.net/go/mauview" "maunium.net/go/mauview"
"maunium.net/go/tcell" "maunium.net/go/tcell"
"maunium.net/go/gomuks/config" "maunium.net/go/gomuks/config"
"maunium.net/go/gomuks/ui/messages/html"
) )
type HTMLMessage struct { type HTMLMessage struct {
@ -36,7 +35,7 @@ type HTMLMessage struct {
func NewHTMLMessage(event *mautrix.Event, displayname string, root html.Entity) UIMessage { func NewHTMLMessage(event *mautrix.Event, displayname string, root html.Entity) UIMessage {
return &HTMLMessage{ return &HTMLMessage{
BaseMessage: newBaseMessage(event.ID, event.Sender, displayname, event.Content.MsgType, unixToTime(event.Timestamp)), BaseMessage: newBaseMessage(event, displayname),
Root: root, Root: root,
} }
} }

View File

@ -18,10 +18,8 @@ package messages
import ( import (
"bytes" "bytes"
"encoding/gob"
"fmt" "fmt"
"image/color" "image/color"
"time"
"maunium.net/go/mautrix" "maunium.net/go/mautrix"
"maunium.net/go/tcell" "maunium.net/go/tcell"
@ -33,10 +31,6 @@ import (
"maunium.net/go/gomuks/ui/messages/tstring" "maunium.net/go/gomuks/ui/messages/tstring"
) )
func init() {
gob.Register(&ImageMessage{})
}
type ImageMessage struct { type ImageMessage struct {
BaseMessage BaseMessage
Body string Body string
@ -48,9 +42,9 @@ type ImageMessage struct {
} }
// NewImageMessage creates a new ImageMessage object with the provided values and the default state. // NewImageMessage creates a new ImageMessage object with the provided values and the default state.
func NewImageMessage(matrix ifc.MatrixContainer, id, sender, displayname string, msgtype mautrix.MessageType, body, homeserver, fileID string, data []byte, timestamp time.Time) UIMessage { func NewImageMessage(matrix ifc.MatrixContainer, event *mautrix.Event, displayname string, body, homeserver, fileID string, data []byte) UIMessage {
return &ImageMessage{ return &ImageMessage{
newBaseMessage(id, sender, displayname, msgtype, timestamp), newBaseMessage(event, displayname),
body, body,
homeserver, homeserver,
fileID, fileID,

View File

@ -20,12 +20,20 @@ import (
"maunium.net/go/gomuks/config" "maunium.net/go/gomuks/config"
"maunium.net/go/gomuks/interface" "maunium.net/go/gomuks/interface"
"maunium.net/go/mauview" "maunium.net/go/mauview"
"maunium.net/go/tcell"
) )
// UIMessage is a wrapper for the content and metadata of a Matrix message intended to be displayed. // UIMessage is a wrapper for the content and metadata of a Matrix message intended to be displayed.
type UIMessage interface { type UIMessage interface {
ifc.Message ifc.Message
Sender() string
SenderColor() tcell.Color
TextColor() tcell.Color
TimestampColor() tcell.Color
FormatTime() string
FormatDate() string
CalculateBuffer(preferences config.UserPreferences, width int) CalculateBuffer(preferences config.UserPreferences, width int)
Draw(screen mauview.Screen) Draw(screen mauview.Screen)
Height() int Height() int

View File

@ -1,71 +0,0 @@
// gomuks - A terminal Matrix client written in Go.
// Copyright (C) 2019 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package messages
import (
"time"
"maunium.net/go/tcell"
)
// BasicMeta is a simple variable store implementation of MessageMeta.
type BasicMeta struct {
BSender string
BTimestamp time.Time
BSenderColor, BTextColor, BTimestampColor tcell.Color
}
// Sender gets the string that should be displayed as the sender of this message.
func (meta *BasicMeta) Sender() string {
return meta.BSender
}
func (meta *BasicMeta) SenderID() string {
return meta.BSender
}
// SenderColor returns the color the name of the sender should be shown in.
func (meta *BasicMeta) SenderColor() tcell.Color {
return meta.BSenderColor
}
// Timestamp returns the full time when the message was sent.
func (meta *BasicMeta) Timestamp() time.Time {
return meta.BTimestamp
}
// 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.
func (meta *BasicMeta) TextColor() tcell.Color {
return meta.BTextColor
}
// TimestampColor 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) TimestampColor() tcell.Color {
return meta.BTimestampColor
}

View File

@ -18,9 +18,7 @@ package messages
import ( import (
"fmt" "fmt"
"html"
"strings" "strings"
"time"
"maunium.net/go/mautrix" "maunium.net/go/mautrix"
"maunium.net/go/tcell" "maunium.net/go/tcell"
@ -28,12 +26,33 @@ import (
"maunium.net/go/gomuks/debug" "maunium.net/go/gomuks/debug"
"maunium.net/go/gomuks/interface" "maunium.net/go/gomuks/interface"
"maunium.net/go/gomuks/matrix/rooms" "maunium.net/go/gomuks/matrix/rooms"
"maunium.net/go/gomuks/ui/messages/html"
"maunium.net/go/gomuks/ui/messages/tstring" "maunium.net/go/gomuks/ui/messages/tstring"
"maunium.net/go/gomuks/ui/widget" "maunium.net/go/gomuks/ui/widget"
htmlp "maunium.net/go/gomuks/ui/messages/html" htmlp "maunium.net/go/gomuks/ui/messages/html"
) )
func ParseEvent(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.Event) UIMessage { func ParseEvent(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.Event) UIMessage {
msg := directParseEvent(matrix, room, evt)
if msg == nil {
return nil
}
if len(evt.Content.GetReplyTo()) > 0 {
roomID := evt.Content.RelatesTo.InReplyTo.RoomID
if len(roomID) == 0 {
roomID = room.ID
}
replyToEvt, _ := matrix.GetEvent(room, evt.Content.GetReplyTo())
if replyToEvt != nil {
// TODO add reply header
} else {
// TODO add unknown reply header
}
}
return msg
}
func directParseEvent(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.Event) UIMessage {
switch evt.Type { switch evt.Type {
case mautrix.EventSticker: case mautrix.EventSticker:
evt.Content.MsgType = mautrix.MsgImage evt.Content.MsgType = mautrix.MsgImage
@ -48,14 +67,6 @@ func ParseEvent(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.Event
return nil return nil
} }
func unixToTime(unix int64) time.Time {
timestamp := time.Now()
if unix != 0 {
timestamp = time.Unix(unix/1000, unix%1000*1000)
}
return timestamp
}
func ParseStateEvent(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.Event) UIMessage { func ParseStateEvent(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.Event) UIMessage {
displayname := evt.Sender displayname := evt.Sender
member := room.GetMember(evt.Sender) member := room.GetMember(evt.Sender)
@ -91,8 +102,7 @@ func ParseStateEvent(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.
case mautrix.StateAliases: case mautrix.StateAliases:
text = ParseAliasEvent(evt, displayname) text = ParseAliasEvent(evt, displayname)
} }
ts := unixToTime(evt.Timestamp) return NewExpandedTextMessage(evt, displayname, text)
return NewExpandedTextMessage(evt.ID, evt.Sender, displayname, mautrix.MessageType(evt.Type.Type), text, ts)
} }
func ParseMessage(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.Event) UIMessage { func ParseMessage(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.Event) UIMessage {
@ -103,39 +113,20 @@ func ParseMessage(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.Eve
} }
if len(evt.Content.GetReplyTo()) > 0 { if len(evt.Content.GetReplyTo()) > 0 {
evt.Content.RemoveReplyFallback() evt.Content.RemoveReplyFallback()
roomID := evt.Content.RelatesTo.InReplyTo.RoomID
if len(roomID) == 0 {
roomID = room.ID
} }
replyToEvt, _ := matrix.GetEvent(room, evt.Content.GetReplyTo())
if replyToEvt != nil {
replyToEvt.Content.RemoveReplyFallback()
if len(replyToEvt.Content.FormattedBody) == 0 {
replyToEvt.Content.FormattedBody = html.EscapeString(replyToEvt.Content.Body)
}
evt.Content.FormattedBody = fmt.Sprintf(
"In reply to <a href='https://matrix.to/#/%[1]s'>%[1]s</a><blockquote>%[2]s</blockquote><br/><br/>%[3]s",
replyToEvt.Sender, replyToEvt.Content.FormattedBody, evt.Content.FormattedBody)
} else {
evt.Content.FormattedBody = fmt.Sprintf(
"In reply to unknown event https://matrix.to/#/%[1]s/%[2]s<br/>%[3]s",
roomID, evt.Content.GetReplyTo(), evt.Content.FormattedBody)
}
}
ts := unixToTime(evt.Timestamp)
switch evt.Content.MsgType { switch evt.Content.MsgType {
case "m.text", "m.notice", "m.emote": case "m.text", "m.notice", "m.emote":
if evt.Content.Format == mautrix.FormatHTML { if evt.Content.Format == mautrix.FormatHTML {
return NewHTMLMessage(evt, displayname, htmlp.Parse(room, evt, displayname)) return NewHTMLMessage(evt, displayname, html.Parse(room, evt, displayname))
} }
evt.Content.Body = strings.Replace(evt.Content.Body, "\t", " ", -1) evt.Content.Body = strings.Replace(evt.Content.Body, "\t", " ", -1)
return NewTextMessage(evt.ID, evt.Sender, displayname, evt.Content.MsgType, evt.Content.Body, ts) return NewTextMessage(evt, displayname, evt.Content.Body)
case "m.image": case "m.image":
data, hs, id, err := matrix.Download(evt.Content.URL) data, hs, id, err := matrix.Download(evt.Content.URL)
if err != nil { if err != nil {
debug.Printf("Failed to download %s: %v", evt.Content.URL, err) debug.Printf("Failed to download %s: %v", evt.Content.URL, err)
} }
return NewImageMessage(matrix, evt.ID, evt.Sender, displayname, evt.Content.MsgType, evt.Content.Body, hs, id, data, ts) return NewImageMessage(matrix, evt, displayname, evt.Content.Body, hs, id, data)
} }
return nil return nil
} }
@ -220,8 +211,7 @@ func ParseMembershipEvent(room *rooms.Room, evt *mautrix.Event) UIMessage {
return nil return nil
} }
ts := unixToTime(evt.Timestamp) return NewExpandedTextMessage(evt, displayname, text)
return NewExpandedTextMessage(evt.ID, evt.Sender, displayname, "m.room.member", text, ts)
} }
func ParseAliasEvent(evt *mautrix.Event, displayname string) tstring.TString { func ParseAliasEvent(evt *mautrix.Event, displayname string) tstring.TString {

View File

@ -17,21 +17,14 @@
package messages package messages
import ( import (
"encoding/gob"
"fmt" "fmt"
"time"
"maunium.net/go/mautrix" "maunium.net/go/mautrix"
"maunium.net/go/gomuks/config" "maunium.net/go/gomuks/config"
"maunium.net/go/gomuks/interface"
"maunium.net/go/gomuks/ui/messages/tstring" "maunium.net/go/gomuks/ui/messages/tstring"
) )
func init() {
gob.Register(&TextMessage{})
}
type TextMessage struct { type TextMessage struct {
BaseMessage BaseMessage
cache tstring.TString cache tstring.TString
@ -39,9 +32,9 @@ type TextMessage struct {
} }
// NewTextMessage creates a new UITextMessage object with the provided values and the default state. // NewTextMessage creates a new UITextMessage object with the provided values and the default state.
func NewTextMessage(id, sender, displayname string, msgtype mautrix.MessageType, text string, timestamp time.Time) UIMessage { func NewTextMessage(event *mautrix.Event, displayname string, text string) UIMessage {
return &TextMessage{ return &TextMessage{
BaseMessage: newBaseMessage(id, sender, displayname, msgtype, timestamp), BaseMessage: newBaseMessage(event, displayname),
MsgText: text, MsgText: text,
} }
} }
@ -59,16 +52,6 @@ func (msg *TextMessage) getCache() tstring.TString {
return msg.cache return msg.cache
} }
func (msg *TextMessage) SetType(msgtype mautrix.MessageType) {
msg.BaseMessage.SetType(msgtype)
msg.cache = nil
}
func (msg *TextMessage) SetState(state ifc.MessageState) {
msg.BaseMessage.SetState(state)
msg.cache = nil
}
func (msg *TextMessage) SetIsHighlight(isHighlight bool) { func (msg *TextMessage) SetIsHighlight(isHighlight bool) {
msg.BaseMessage.SetIsHighlight(isHighlight) msg.BaseMessage.SetIsHighlight(isHighlight)
msg.cache = nil msg.cache = nil

View File

@ -438,3 +438,7 @@ func (view *RoomView) AddServiceMessage(text string) {
func (view *RoomView) AddMessage(message ifc.Message, direction ifc.MessageDirection) { func (view *RoomView) AddMessage(message ifc.Message, direction ifc.MessageDirection) {
view.content.AddMessage(message, direction) view.content.AddMessage(message, direction)
} }
func (view *RoomView) ParseEvent(evt *mautrix.Event) ifc.Message {
return messages.ParseEvent(view.parent.matrix, view.Room, evt)
}

View File

@ -35,7 +35,6 @@ import (
"maunium.net/go/gomuks/lib/notification" "maunium.net/go/gomuks/lib/notification"
"maunium.net/go/gomuks/matrix/pushrules" "maunium.net/go/gomuks/matrix/pushrules"
"maunium.net/go/gomuks/matrix/rooms" "maunium.net/go/gomuks/matrix/rooms"
"maunium.net/go/gomuks/ui/messages"
"maunium.net/go/gomuks/ui/widget" "maunium.net/go/gomuks/ui/widget"
) )
@ -166,11 +165,10 @@ func (view *MainView) InputSubmit(roomView *RoomView, text string) {
} }
func (view *MainView) SendMessage(roomView *RoomView, text string) { func (view *MainView) SendMessage(roomView *RoomView, text string) {
tempMessage := roomView.NewTempMessage("m.text", text) go view.goSendMessage(roomView, text)
go view.sendTempMessage(roomView, tempMessage, text)
} }
func (view *MainView) sendTempMessage(roomView *RoomView, tempMessage ifc.Message, text string) { func (view *MainView) goSendMessage(roomView *RoomView, text string) {
defer debug.Recover() defer debug.Recover()
debug.Print("Sending message", tempMessage.Type(), text) debug.Print("Sending message", tempMessage.Type(), text)
if !roomView.config.Preferences.DisableEmojis { if !roomView.config.Preferences.DisableEmojis {
@ -317,11 +315,10 @@ func (view *MainView) addRoomPage(room *rooms.Room) {
view.rooms[room.ID] = roomView view.rooms[room.ID] = roomView
roomView.UpdateUserList() roomView.UpdateUserList()
// FIXME // TODO make sure this works
/*_, err := roomView.LoadHistory(view.matrix, view.config.HistoryDir) if len(roomView.MessageView().messages) == 0 {
if err != nil { go view.LoadHistory(room.ID)
debug.Printf("Failed to load history of %s: %v", roomView.Room.GetTitle(), err) }
}*/
} }
} }
@ -428,10 +425,11 @@ func (view *MainView) NotifyMessage(room *rooms.Room, message ifc.Message, shoul
if shouldNotify && !recentlyFocused { if shouldNotify && !recentlyFocused {
// Push rules say notify and the terminal is not focused, send desktop notification. // Push rules say notify and the terminal is not focused, send desktop notification.
shouldPlaySound := should.PlaySound && should.SoundName == "default" shouldPlaySound := should.PlaySound && should.SoundName == "default"
sendNotification(room, message.Sender(), message.NotificationContent(), should.Highlight, shouldPlaySound) sendNotification(room, message.NotificationSenderName(), message.NotificationContent(), should.Highlight, shouldPlaySound)
} }
message.SetIsHighlight(should.Highlight) // TODO Make sure this happens somewhere else
//message.SetIsHighlight(should.Highlight)
} }
func (view *MainView) InitialSyncDone() { func (view *MainView) InitialSyncDone() {
@ -470,7 +468,7 @@ func (view *MainView) LoadHistory(room string) {
return return
} }
for _, evt := range history { for _, evt := range history {
message := view.ParseEvent(roomView, evt) message := roomView.ParseEvent(evt)
if message != nil { if message != nil {
roomView.AddMessage(message, ifc.PrependMessage) roomView.AddMessage(message, ifc.PrependMessage)
} }
@ -483,7 +481,3 @@ func (view *MainView) LoadHistory(room string) {
view.config.PutRoom(roomView.Room) view.config.PutRoom(roomView.Room)
view.parent.Render() view.parent.Render()
} }
func (view *MainView) ParseEvent(roomView ifc.RoomView, evt *mautrix.Event) ifc.Message {
return messages.ParseEvent(view.matrix, roomView.MxRoom(), evt)
}