diff --git a/interface/ui.go b/interface/ui.go index df9d959..c64c748 100644 --- a/interface/ui.go +++ b/interface/ui.go @@ -19,11 +19,9 @@ package ifc import ( "time" - "maunium.net/go/mautrix" - "maunium.net/go/tcell" - "maunium.net/go/gomuks/matrix/pushrules" "maunium.net/go/gomuks/matrix/rooms" + "maunium.net/go/mautrix" ) type UIProvider func(gmx Gomuks) GomuksUI @@ -50,7 +48,6 @@ type MainView interface { UpdateTags(room *rooms.Room) SetTyping(roomID string, users []string) - ParseEvent(roomView RoomView, evt *mautrix.Event) Message NotifyMessage(room *rooms.Room, message Message, should pushrules.PushActionArrayShould) InitialSyncDone() @@ -71,50 +68,17 @@ type RoomView interface { SetTyping(users []string) UpdateUserList() - NewTempMessage(msgtype mautrix.MessageType, text string) Message - AddMessage(message Message, direction MessageDirection) + ParseEvent(evt *mautrix.Event) Message + AppendMessage(message Message) + MarkMessageFailed(message Message) 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 { - MessageMeta - - SetIsHighlight(isHighlight bool) - IsHighlight() bool - - SetIsService(isService bool) - IsService() bool - - SetID(id string) ID() string - - SetType(msgtype mautrix.MessageType) - Type() mautrix.MessageType - - NotificationContent() string - - SetState(state MessageState) - State() MessageState - + TxnID() string SenderID() string + Timestamp() time.Time + NotificationSenderName() string + NotificationContent() string } diff --git a/matrix/matrix.go b/matrix/matrix.go index b9816f2..960e92b 100644 --- a/matrix/matrix.go +++ b/matrix/matrix.go @@ -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) } + // TODO switch to roomView.AddEvent message := mainView.ParseEvent(roomView, evt) if message != nil { 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 roomRegex = regexp.MustCompile("\\[.+?]\\(https://matrix.to/#/(#.+?:[^/]+?)\\)") -// SendMarkdownMessage sends a message with the given 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. +// SendMarkdownMessage sends a message with the given markdown text to the given room. func (c *Container) SendMarkdownMessage(roomID string, msgtype mautrix.MessageType, text string) (string, error) { defer debug.Recover() @@ -509,8 +507,23 @@ func (c *Container) SendMarkdownMessage(roomID string, msgtype mautrix.MessageTy content.Body = roomRegex.ReplaceAllString(content.Body, "$1") 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 { + roomView.MarkMessageFailed(localEcho, err) return "", err } return resp.EventID, nil diff --git a/ui/message-view.go b/ui/message-view.go index 748eec3..5eedabc 100644 --- a/ui/message-view.go +++ b/ui/message-view.go @@ -31,7 +31,6 @@ import ( "maunium.net/go/gomuks/interface" "maunium.net/go/gomuks/lib/open" "maunium.net/go/gomuks/ui/messages" - "maunium.net/go/gomuks/ui/messages/tstring" "maunium.net/go/gomuks/ui/widget" ) @@ -57,8 +56,7 @@ type MessageView struct { messageIDs map[string]messages.UIMessage messages []messages.UIMessage - textBuffer []tstring.TString - metaBuffer []ifc.MessageMeta + msgBuffer []messages.UIMessage } func NewMessageView(parent *RoomView) *MessageView { @@ -72,8 +70,7 @@ func NewMessageView(parent *RoomView) *MessageView { messages: make([]messages.UIMessage, 0), messageIDs: make(map[string]messages.UIMessage), - textBuffer: make([]tstring.TString, 0), - metaBuffer: make([]ifc.MessageMeta, 0), + msgBuffer: make([]messages.UIMessage, 0), width: 80, widestSender: 5, @@ -115,10 +112,16 @@ func (view *MessageView) AddMessage(ifcMessage ifc.Message, direction ifc.Messag 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) 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()) @@ -149,20 +152,19 @@ 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 len(view.msgBuffer) > 0 { + prevMeta := view.msgBuffer[len(view.msgBuffer)-1] 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()), tcell.ColorGreen)) - view.metaBuffer = append(view.metaBuffer, &messages.BasicMeta{ - BTimestampColor: tcell.ColorDefault, BTextColor: tcell.ColorGreen}) + view.msgBuffer = append(view.msgBuffer, &messages.BasicMeta{ + BTimestampColor: tcell.ColorDefault, BTextColor: tcell.ColorGreen})*/ } } for i := 0; i < message.Height(); i++ { - view.textBuffer = append(view.textBuffer, nil) - view.metaBuffer = append(view.metaBuffer, message) + view.msgBuffer = append(view.msgBuffer, message) } 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) { start := -1 end := -1 - for index, meta := range view.metaBuffer { + for index, meta := range view.msgBuffer { if meta == original { if start == -1 { start = index @@ -197,7 +199,7 @@ func (view *MessageView) replaceBuffer(original messages.UIMessage, new messages return } - if len(view.textBuffer) > end { + if len(view.msgBuffer) > end { end++ } @@ -205,17 +207,15 @@ func (view *MessageView) replaceBuffer(original messages.UIMessage, new messages 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 { - metaBuffer := view.metaBuffer[0:start] + metaBuffer := view.msgBuffer[0:start] for i := 0; i < new.Height(); i++ { metaBuffer = append(metaBuffer, new) } - view.metaBuffer = append(metaBuffer, view.metaBuffer[end:]...) + view.msgBuffer = append(metaBuffer, view.msgBuffer[end:]...) } else { 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 { width -= view.TimestampWidth + TimestampSenderGap + view.widestSender + SenderMessageGap } - view.textBuffer = []tstring.TString{} - view.metaBuffer = []ifc.MessageMeta{} + view.msgBuffer = []messages.UIMessage{} view.prevMsgCount = 0 for i, message := range view.messages { if message == nil { @@ -249,7 +248,7 @@ func (view *MessageView) recalculateBuffers() { view.prevPrefs = prefs } -func (view *MessageView) handleMessageClick(message ifc.MessageMeta) bool { +func (view *MessageView) handleMessageClick(message messages.UIMessage) bool { switch message := message.(type) { case *messages.ImageMessage: open.Open(message.Path()) @@ -259,21 +258,15 @@ func (view *MessageView) handleMessageClick(message ifc.MessageMeta) bool { return false } -func (view *MessageView) handleUsernameClick(message ifc.MessageMeta, prevMessage ifc.MessageMeta) bool { - uiMessage, ok := message.(messages.UIMessage) - if !ok { +func (view *MessageView) handleUsernameClick(message messages.UIMessage, prevMessage messages.UIMessage) bool { + if prevMessage != nil && prevMessage.Sender() == message.Sender() { return false } - prevUIMessage, _ := prevMessage.(messages.UIMessage) - if prevUIMessage != nil && prevUIMessage.Sender() == uiMessage.Sender() { + if len(message.Sender()) == 0 { return false } - - if len(uiMessage.Sender()) == 0 { - return false - } - sender := fmt.Sprintf("[%s](https://matrix.to/#/%s)", uiMessage.Sender(), uiMessage.SenderID()) + sender := fmt.Sprintf("[%s](https://matrix.to/#/%s)", message.Sender(), message.SenderID()) cursorPos := view.parent.input.GetCursorOffset() text := view.parent.input.GetText() @@ -317,10 +310,10 @@ func (view *MessageView) OnMouseEvent(event mauview.MouseEvent) bool { return false } - message := view.metaBuffer[line] - var prevMessage ifc.MessageMeta + message := view.msgBuffer[line] + var prevMessage messages.UIMessage if y != 0 && line > 0 { - prevMessage = view.metaBuffer[line-1] + prevMessage = view.msgBuffer[line-1] } usernameX := view.TimestampWidth + TimestampSenderGap @@ -358,12 +351,11 @@ func (view *MessageView) Height() int { } func (view *MessageView) TotalHeight() int { - return len(view.textBuffer) + return len(view.msgBuffer) } func (view *MessageView) IsAtTop() bool { - totalHeight := len(view.textBuffer) - return view.ScrollOffset >= totalHeight-view.height+PaddingAtTop + return view.ScrollOffset >= len(view.msgBuffer)-view.height+PaddingAtTop } const ( @@ -429,7 +421,7 @@ func (view *MessageView) CapturePlaintext(height int) string { continue } - meta := view.metaBuffer[index] + meta := view.msgBuffer[index] message, ok := meta.(messages.UIMessage) if ok && message != prevMessage { var sender string @@ -456,7 +448,6 @@ func (view *MessageView) Draw(screen mauview.Screen) { usernameX := view.TimestampWidth + TimestampSenderGap messageX := usernameX + view.widestSender + SenderMessageGap - separatorX := usernameX + view.widestSender + SenderSeparatorGap bareMode := view.config.Preferences.BareMessageView if bareMode { @@ -465,59 +456,49 @@ func (view *MessageView) Draw(screen mauview.Screen) { 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 if indexOffset < 0 { viewStart = -indexOffset } - for line := viewStart; line < view.height; line++ { - showScrollbar := line-viewStart >= scrollBarPos-scrollBarHeight && line-viewStart < scrollBarPos - isTop := line == viewStart && view.ScrollOffset+view.height >= view.TotalHeight() - isBottom := line == view.height-1 && view.ScrollOffset == 0 - borderChar, borderStyle := getScrollbarStyle(showScrollbar, isTop, isBottom) + if !bareMode { + separatorX := usernameX + view.widestSender + SenderSeparatorGap + scrollBarHeight, scrollBarPos := view.calculateScrollBar(view.height) + + for line := viewStart; line < view.height; line++ { + showScrollbar := line-viewStart >= scrollBarPos-scrollBarHeight && line-viewStart < scrollBarPos + isTop := line == viewStart && view.ScrollOffset+view.height >= view.TotalHeight() + isBottom := line == view.height-1 && view.ScrollOffset == 0 + + borderChar, borderStyle := getScrollbarStyle(showScrollbar, isTop, isBottom) - if !bareMode { screen.SetContent(separatorX, line, borderChar, nil, borderStyle) } } + var prevMsg messages.UIMessage for line := viewStart; line < view.height && indexOffset+line < view.TotalHeight(); line++ { index := indexOffset + line - text, meta := view.textBuffer[index], view.metaBuffer[index] - if meta != prevMeta { - if len(meta.FormatTime()) > 0 { - widget.WriteLineSimpleColor(screen, meta.FormatTime(), 0, line, meta.TimestampColor()) + msg := view.msgBuffer[index] + if msg != prevMsg { + if len(msg.FormatTime()) > 0 { + 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) - //if !bareMode && (prevMeta == nil || meta.Sender() != prevMeta.Sender()) { + //if !bareMode && (prevMsg == nil || meta.Sender() != prevMsg.Sender()) { widget.WriteLineColor( - screen, mauview.AlignRight, meta.Sender(), + screen, mauview.AlignRight, msg.Sender(), usernameX, line, view.widestSender, - meta.SenderColor()) + msg.SenderColor()) //} - prevMeta = meta + prevMsg = msg } - message, ok := meta.(messages.UIMessage) - if ok { - for i := index - 1; i >= 0 && view.metaBuffer[i] == meta; i-- { - line-- - } - message.Draw(mauview.NewProxyScreen(screen, messageX, line, view.width-messageX, message.Height())) - line += message.Height() - 1 - } else { - text.Draw(screen, messageX, line) + for i := index - 1; i >= 0 && view.msgBuffer[i] == msg; i-- { + line-- } + msg.Draw(mauview.NewProxyScreen(screen, messageX, line, view.width-messageX, msg.Height())) + line += msg.Height() - 1 } } diff --git a/ui/messages/base.go b/ui/messages/base.go index d045e42..b8b1694 100644 --- a/ui/messages/base.go +++ b/ui/messages/base.go @@ -17,7 +17,7 @@ package messages import ( - "encoding/gob" + "encoding/json" "time" "maunium.net/go/mautrix" @@ -29,38 +29,51 @@ import ( "maunium.net/go/gomuks/ui/widget" ) -func init() { - gob.Register(&BaseMessage{}) -} - type BaseMessage struct { MsgID string + MsgTxnID string MsgType mautrix.MessageType MsgSenderID string MsgSender string MsgSenderColor tcell.Color MsgTimestamp time.Time - MsgState ifc.MessageState + MsgState mautrix.OutgoingEventState MsgIsHighlight bool MsgIsService bool + MsgSource json.RawMessage buffer []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{ - MsgSenderID: sender, + MsgSenderID: event.Sender, MsgSender: displayname, - MsgTimestamp: timestamp, - MsgSenderColor: widget.GetHashColor(sender), + MsgTimestamp: unixToTime(event.Timestamp), + MsgSenderColor: widget.GetHashColor(event.Sender), MsgType: msgtype, - MsgID: id, - MsgState: ifc.MessageStateDefault, + MsgID: event.ID, + MsgTxnID: event.Unsigned.TransactionID, + MsgState: event.Unsigned.OutgoingState, MsgIsHighlight: 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) {} // 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. func (msg *BaseMessage) Sender() string { switch msg.MsgState { - case ifc.MessageStateSending: + case mautrix.EventStateLocalEcho: return "Sending..." - case ifc.MessageStateFailed: + case mautrix.EventStateSendFail: return "Error" } switch msg.MsgType { @@ -93,13 +106,17 @@ func (msg *BaseMessage) RealSender() string { return msg.MsgSender } +func (msg *BaseMessage) NotificationSenderName() string { + return msg.MsgSender +} + func (msg *BaseMessage) getStateSpecificColor() tcell.Color { switch msg.MsgState { - case ifc.MessageStateSending: + case mautrix.EventStateLocalEcho: return tcell.ColorGray - case ifc.MessageStateFailed: + case mautrix.EventStateSendFail: return tcell.ColorRed - case ifc.MessageStateDefault: + case mautrix.EventStateDefault: fallthrough default: return tcell.ColorDefault @@ -154,17 +171,6 @@ func (msg *BaseMessage) TimestampColor() tcell.Color { 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()). func (msg *BaseMessage) Height() int { return len(msg.buffer) @@ -197,15 +203,11 @@ func (msg *BaseMessage) Type() mautrix.MessageType { return msg.MsgType } -func (msg *BaseMessage) SetType(msgtype mautrix.MessageType) { - msg.MsgType = msgtype -} - -func (msg *BaseMessage) State() ifc.MessageState { +func (msg *BaseMessage) State() mautrix.OutgoingEventState { return msg.MsgState } -func (msg *BaseMessage) SetState(state ifc.MessageState) { +func (msg *BaseMessage) SetState(state mautrix.OutgoingEventState) { msg.MsgState = state } @@ -225,6 +227,10 @@ func (msg *BaseMessage) SetIsService(isService bool) { msg.MsgIsService = isService } +func (msg *BaseMessage) Source() json.RawMessage { + return msg.MsgSource +} + func (msg *BaseMessage) Draw(screen mauview.Screen) { for y, line := range msg.buffer { line.Draw(screen, 0, y) diff --git a/ui/messages/expandedtextmessage.go b/ui/messages/expandedtextmessage.go index 044613f..4a4ed24 100644 --- a/ui/messages/expandedtextmessage.go +++ b/ui/messages/expandedtextmessage.go @@ -17,28 +17,21 @@ package messages import ( - "encoding/gob" - "time" - "maunium.net/go/mautrix" "maunium.net/go/gomuks/config" "maunium.net/go/gomuks/ui/messages/tstring" ) -func init() { - gob.Register(&ExpandedTextMessage{}) -} - type ExpandedTextMessage struct { BaseMessage MsgText tstring.TString } // 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{ - BaseMessage: newBaseMessage(id, sender, displayname, msgtype, timestamp), + BaseMessage: newBaseMessage(event, displayname), MsgText: text, } } diff --git a/ui/messages/htmlmessage.go b/ui/messages/htmlmessage.go index ed8b7c1..ceee305 100644 --- a/ui/messages/htmlmessage.go +++ b/ui/messages/htmlmessage.go @@ -17,13 +17,12 @@ package messages import ( - "maunium.net/go/gomuks/ui/messages/html" - "maunium.net/go/mautrix" "maunium.net/go/mauview" "maunium.net/go/tcell" "maunium.net/go/gomuks/config" + "maunium.net/go/gomuks/ui/messages/html" ) type HTMLMessage struct { @@ -36,7 +35,7 @@ type HTMLMessage struct { func NewHTMLMessage(event *mautrix.Event, displayname string, root html.Entity) UIMessage { return &HTMLMessage{ - BaseMessage: newBaseMessage(event.ID, event.Sender, displayname, event.Content.MsgType, unixToTime(event.Timestamp)), + BaseMessage: newBaseMessage(event, displayname), Root: root, } } diff --git a/ui/messages/imagemessage.go b/ui/messages/imagemessage.go index 968f29e..9ce1a8e 100644 --- a/ui/messages/imagemessage.go +++ b/ui/messages/imagemessage.go @@ -18,10 +18,8 @@ package messages import ( "bytes" - "encoding/gob" "fmt" "image/color" - "time" "maunium.net/go/mautrix" "maunium.net/go/tcell" @@ -33,10 +31,6 @@ import ( "maunium.net/go/gomuks/ui/messages/tstring" ) -func init() { - gob.Register(&ImageMessage{}) -} - type ImageMessage struct { BaseMessage Body string @@ -48,9 +42,9 @@ type ImageMessage struct { } // 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{ - newBaseMessage(id, sender, displayname, msgtype, timestamp), + newBaseMessage(event, displayname), body, homeserver, fileID, diff --git a/ui/messages/message.go b/ui/messages/message.go index db93879..634bd11 100644 --- a/ui/messages/message.go +++ b/ui/messages/message.go @@ -20,12 +20,20 @@ import ( "maunium.net/go/gomuks/config" "maunium.net/go/gomuks/interface" "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. type UIMessage interface { ifc.Message + Sender() string + SenderColor() tcell.Color + TextColor() tcell.Color + TimestampColor() tcell.Color + FormatTime() string + FormatDate() string + CalculateBuffer(preferences config.UserPreferences, width int) Draw(screen mauview.Screen) Height() int diff --git a/ui/messages/meta.go b/ui/messages/meta.go deleted file mode 100644 index 0712be1..0000000 --- a/ui/messages/meta.go +++ /dev/null @@ -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 . - -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 -} diff --git a/ui/messages/parser.go b/ui/messages/parser.go index ae0606d..f45eea2 100644 --- a/ui/messages/parser.go +++ b/ui/messages/parser.go @@ -18,9 +18,7 @@ package messages import ( "fmt" - "html" "strings" - "time" "maunium.net/go/mautrix" "maunium.net/go/tcell" @@ -28,12 +26,33 @@ import ( "maunium.net/go/gomuks/debug" "maunium.net/go/gomuks/interface" "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/widget" htmlp "maunium.net/go/gomuks/ui/messages/html" ) 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 { case mautrix.EventSticker: evt.Content.MsgType = mautrix.MsgImage @@ -48,14 +67,6 @@ func ParseEvent(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.Event 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 { displayname := evt.Sender member := room.GetMember(evt.Sender) @@ -91,8 +102,7 @@ func ParseStateEvent(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix. case mautrix.StateAliases: text = ParseAliasEvent(evt, displayname) } - ts := unixToTime(evt.Timestamp) - return NewExpandedTextMessage(evt.ID, evt.Sender, displayname, mautrix.MessageType(evt.Type.Type), text, ts) + return NewExpandedTextMessage(evt, displayname, text) } 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 { 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 %[1]s
%[2]s


%[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
%[3]s", - roomID, evt.Content.GetReplyTo(), evt.Content.FormattedBody) - } } - ts := unixToTime(evt.Timestamp) switch evt.Content.MsgType { case "m.text", "m.notice", "m.emote": 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) - return NewTextMessage(evt.ID, evt.Sender, displayname, evt.Content.MsgType, evt.Content.Body, ts) + return NewTextMessage(evt, displayname, evt.Content.Body) case "m.image": data, hs, id, err := matrix.Download(evt.Content.URL) if err != nil { 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 } @@ -220,8 +211,7 @@ func ParseMembershipEvent(room *rooms.Room, evt *mautrix.Event) UIMessage { return nil } - ts := unixToTime(evt.Timestamp) - return NewExpandedTextMessage(evt.ID, evt.Sender, displayname, "m.room.member", text, ts) + return NewExpandedTextMessage(evt, displayname, text) } func ParseAliasEvent(evt *mautrix.Event, displayname string) tstring.TString { diff --git a/ui/messages/textmessage.go b/ui/messages/textmessage.go index 6364659..f7c54ca 100644 --- a/ui/messages/textmessage.go +++ b/ui/messages/textmessage.go @@ -17,21 +17,14 @@ package messages import ( - "encoding/gob" "fmt" - "time" "maunium.net/go/mautrix" "maunium.net/go/gomuks/config" - "maunium.net/go/gomuks/interface" "maunium.net/go/gomuks/ui/messages/tstring" ) -func init() { - gob.Register(&TextMessage{}) -} - type TextMessage struct { BaseMessage cache tstring.TString @@ -39,9 +32,9 @@ type TextMessage struct { } // 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{ - BaseMessage: newBaseMessage(id, sender, displayname, msgtype, timestamp), + BaseMessage: newBaseMessage(event, displayname), MsgText: text, } } @@ -59,16 +52,6 @@ func (msg *TextMessage) getCache() tstring.TString { 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) { msg.BaseMessage.SetIsHighlight(isHighlight) msg.cache = nil diff --git a/ui/room-view.go b/ui/room-view.go index 1a1ae78..2711338 100644 --- a/ui/room-view.go +++ b/ui/room-view.go @@ -438,3 +438,7 @@ func (view *RoomView) AddServiceMessage(text string) { func (view *RoomView) AddMessage(message ifc.Message, direction ifc.MessageDirection) { view.content.AddMessage(message, direction) } + +func (view *RoomView) ParseEvent(evt *mautrix.Event) ifc.Message { + return messages.ParseEvent(view.parent.matrix, view.Room, evt) +} diff --git a/ui/view-main.go b/ui/view-main.go index ff46c79..d9d9247 100644 --- a/ui/view-main.go +++ b/ui/view-main.go @@ -35,7 +35,6 @@ import ( "maunium.net/go/gomuks/lib/notification" "maunium.net/go/gomuks/matrix/pushrules" "maunium.net/go/gomuks/matrix/rooms" - "maunium.net/go/gomuks/ui/messages" "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) { - tempMessage := roomView.NewTempMessage("m.text", text) - go view.sendTempMessage(roomView, tempMessage, text) + go view.goSendMessage(roomView, text) } -func (view *MainView) sendTempMessage(roomView *RoomView, tempMessage ifc.Message, text string) { +func (view *MainView) goSendMessage(roomView *RoomView, text string) { defer debug.Recover() debug.Print("Sending message", tempMessage.Type(), text) if !roomView.config.Preferences.DisableEmojis { @@ -317,11 +315,10 @@ func (view *MainView) addRoomPage(room *rooms.Room) { view.rooms[room.ID] = roomView roomView.UpdateUserList() - // FIXME - /*_, err := roomView.LoadHistory(view.matrix, view.config.HistoryDir) - if err != nil { - debug.Printf("Failed to load history of %s: %v", roomView.Room.GetTitle(), err) - }*/ + // TODO make sure this works + if len(roomView.MessageView().messages) == 0 { + go view.LoadHistory(room.ID) + } } } @@ -428,10 +425,11 @@ func (view *MainView) NotifyMessage(room *rooms.Room, message ifc.Message, shoul if shouldNotify && !recentlyFocused { // Push rules say notify and the terminal is not focused, send desktop notification. 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() { @@ -470,7 +468,7 @@ func (view *MainView) LoadHistory(room string) { return } for _, evt := range history { - message := view.ParseEvent(roomView, evt) + message := roomView.ParseEvent(evt) if message != nil { roomView.AddMessage(message, ifc.PrependMessage) } @@ -483,7 +481,3 @@ func (view *MainView) LoadHistory(room string) { view.config.PutRoom(roomView.Room) view.parent.Render() } - -func (view *MainView) ParseEvent(roomView ifc.RoomView, evt *mautrix.Event) ifc.Message { - return messages.ParseEvent(view.matrix, roomView.MxRoom(), evt) -}