Unbreak things
This commit is contained in:
		@@ -86,26 +86,26 @@ func cmdHeapProfile(cmd *Command) {
 | 
			
		||||
func cmdRainbow(cmd *Command) {
 | 
			
		||||
	text := strings.Join(cmd.Args, " ")
 | 
			
		||||
	var html strings.Builder
 | 
			
		||||
	fmt.Fprint(&html, "**🌈** ")
 | 
			
		||||
	_, _ = fmt.Fprint(&html, "**🌈** ")
 | 
			
		||||
	for i, char := range text {
 | 
			
		||||
		if unicode.IsSpace(char) {
 | 
			
		||||
			html.WriteRune(char)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		color := rainbow.GetInterpolatedColorFor(float64(i) / float64(len(text))).Hex()
 | 
			
		||||
		fmt.Fprintf(&html, "<font color=\"%s\">%c</font>", color, char)
 | 
			
		||||
		_, _ = fmt.Fprintf(&html, "<font color=\"%s\">%c</font>", color, char)
 | 
			
		||||
	}
 | 
			
		||||
	go cmd.Room.SendMessage("m.text", html.String())
 | 
			
		||||
	cmd.UI.Render()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cmdQuit(cmd *Command) {
 | 
			
		||||
	cmd.Gomuks.Stop()
 | 
			
		||||
	cmd.Gomuks.Stop(true)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cmdClearCache(cmd *Command) {
 | 
			
		||||
	cmd.Config.Clear()
 | 
			
		||||
	cmd.Gomuks.Stop()
 | 
			
		||||
	cmd.Gomuks.Stop(false)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cmdUnknownCommand(cmd *Command) {
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,7 @@ import (
 | 
			
		||||
	"github.com/mattn/go-runewidth"
 | 
			
		||||
	sync "github.com/sasha-s/go-deadlock"
 | 
			
		||||
 | 
			
		||||
	"maunium.net/go/mautrix"
 | 
			
		||||
	"maunium.net/go/mauview"
 | 
			
		||||
	"maunium.net/go/tcell"
 | 
			
		||||
 | 
			
		||||
@@ -58,11 +59,13 @@ type MessageView struct {
 | 
			
		||||
	prevPrefs     config.UserPreferences
 | 
			
		||||
 | 
			
		||||
	messageIDLock sync.RWMutex
 | 
			
		||||
	messageIDs    map[string]messages.UIMessage
 | 
			
		||||
	messageIDs    map[string]*messages.UIMessage
 | 
			
		||||
	messagesLock  sync.RWMutex
 | 
			
		||||
	messages      []messages.UIMessage
 | 
			
		||||
	messages      []*messages.UIMessage
 | 
			
		||||
	msgBufferLock sync.RWMutex
 | 
			
		||||
	msgBuffer     []messages.UIMessage
 | 
			
		||||
	msgBuffer     []*messages.UIMessage
 | 
			
		||||
 | 
			
		||||
	initialHistoryLoaded bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewMessageView(parent *RoomView) *MessageView {
 | 
			
		||||
@@ -74,9 +77,9 @@ func NewMessageView(parent *RoomView) *MessageView {
 | 
			
		||||
		TimestampWidth: len(messages.TimeFormat),
 | 
			
		||||
		ScrollOffset:   0,
 | 
			
		||||
 | 
			
		||||
		messages:   make([]messages.UIMessage, 0),
 | 
			
		||||
		messageIDs: make(map[string]messages.UIMessage),
 | 
			
		||||
		msgBuffer:  make([]messages.UIMessage, 0),
 | 
			
		||||
		messages:   make([]*messages.UIMessage, 0),
 | 
			
		||||
		messageIDs: make(map[string]*messages.UIMessage),
 | 
			
		||||
		msgBuffer:  make([]*messages.UIMessage, 0),
 | 
			
		||||
 | 
			
		||||
		_width:        80,
 | 
			
		||||
		_widestSender: 5,
 | 
			
		||||
@@ -108,20 +111,22 @@ func (view *MessageView) AddMessage(ifcMessage ifc.Message, direction MessageDir
 | 
			
		||||
	if ifcMessage == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	message, ok := ifcMessage.(messages.UIMessage)
 | 
			
		||||
	if !ok {
 | 
			
		||||
	message, ok := ifcMessage.(*messages.UIMessage)
 | 
			
		||||
	if !ok || message == nil {
 | 
			
		||||
		debug.Print("[Warning] Passed non-UIMessage ifc.Message object to AddMessage().")
 | 
			
		||||
		debug.PrintStack()
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var oldMsg messages.UIMessage
 | 
			
		||||
	if oldMsg = view.getMessageByID(message.ID()); oldMsg != nil {
 | 
			
		||||
	var oldMsg *messages.UIMessage
 | 
			
		||||
	if oldMsg = view.getMessageByID(message.EventID); oldMsg != nil {
 | 
			
		||||
		view.replaceMessage(oldMsg, message)
 | 
			
		||||
		direction = IgnoreMessage
 | 
			
		||||
	} else if oldMsg = view.getMessageByID(message.TxnID()); oldMsg != nil {
 | 
			
		||||
	} else if oldMsg = view.getMessageByID(message.TxnID); oldMsg != nil {
 | 
			
		||||
		view.replaceMessage(oldMsg, message)
 | 
			
		||||
		view.deleteMessageID(message.TxnID())
 | 
			
		||||
		view.deleteMessageID(message.TxnID)
 | 
			
		||||
		direction = IgnoreMessage
 | 
			
		||||
	} else if oldMsg = view.getMessageByID(message.Relation.EventID); oldMsg != nil {
 | 
			
		||||
		direction = IgnoreMessage
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -134,7 +139,7 @@ func (view *MessageView) AddMessage(ifcMessage ifc.Message, direction MessageDir
 | 
			
		||||
	}
 | 
			
		||||
	message.CalculateBuffer(view.config.Preferences, width)
 | 
			
		||||
 | 
			
		||||
	makeDateChange := func() messages.UIMessage {
 | 
			
		||||
	makeDateChange := func() *messages.UIMessage {
 | 
			
		||||
		dateChange := messages.NewDateChangeMessage(
 | 
			
		||||
			fmt.Sprintf("Date changed to %s", message.FormatDate()))
 | 
			
		||||
		dateChange.CalculateBuffer(view.config.Preferences, width)
 | 
			
		||||
@@ -157,9 +162,9 @@ func (view *MessageView) AddMessage(ifcMessage ifc.Message, direction MessageDir
 | 
			
		||||
	} else if direction == PrependMessage {
 | 
			
		||||
		view.messagesLock.Lock()
 | 
			
		||||
		if len(view.messages) > 0 && !view.messages[0].SameDate(message) {
 | 
			
		||||
			view.messages = append([]messages.UIMessage{message, makeDateChange()}, view.messages...)
 | 
			
		||||
			view.messages = append([]*messages.UIMessage{message, makeDateChange()}, view.messages...)
 | 
			
		||||
		} else {
 | 
			
		||||
			view.messages = append([]messages.UIMessage{message}, view.messages...)
 | 
			
		||||
			view.messages = append([]*messages.UIMessage{message}, view.messages...)
 | 
			
		||||
		}
 | 
			
		||||
		view.messagesLock.Unlock()
 | 
			
		||||
	} else if oldMsg != nil {
 | 
			
		||||
@@ -174,7 +179,7 @@ func (view *MessageView) AddMessage(ifcMessage ifc.Message, direction MessageDir
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (view *MessageView) replaceMessage(original messages.UIMessage, new messages.UIMessage) {
 | 
			
		||||
func (view *MessageView) replaceMessage(original *messages.UIMessage, new *messages.UIMessage) {
 | 
			
		||||
	if len(new.ID()) > 0 {
 | 
			
		||||
		view.setMessageID(new)
 | 
			
		||||
	}
 | 
			
		||||
@@ -187,7 +192,10 @@ func (view *MessageView) replaceMessage(original messages.UIMessage, new message
 | 
			
		||||
	view.messagesLock.Unlock()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (view *MessageView) getMessageByID(id string) messages.UIMessage {
 | 
			
		||||
func (view *MessageView) getMessageByID(id string) *messages.UIMessage {
 | 
			
		||||
	if id == "" {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	view.messageIDLock.RLock()
 | 
			
		||||
	defer view.messageIDLock.RUnlock()
 | 
			
		||||
	msg, ok := view.messageIDs[id]
 | 
			
		||||
@@ -198,31 +206,37 @@ func (view *MessageView) getMessageByID(id string) messages.UIMessage {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (view *MessageView) deleteMessageID(id string) {
 | 
			
		||||
	if id == "" {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	view.messageIDLock.Lock()
 | 
			
		||||
	delete(view.messageIDs, id)
 | 
			
		||||
	view.messageIDLock.Unlock()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (view *MessageView) setMessageID(message messages.UIMessage) {
 | 
			
		||||
func (view *MessageView) setMessageID(message *messages.UIMessage) {
 | 
			
		||||
	if message.ID() == "" {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	view.messageIDLock.Lock()
 | 
			
		||||
	view.messageIDs[message.ID()] = message
 | 
			
		||||
	view.messageIDLock.Unlock()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (view *MessageView) appendBuffer(message messages.UIMessage) {
 | 
			
		||||
func (view *MessageView) appendBuffer(message *messages.UIMessage) {
 | 
			
		||||
	view.msgBufferLock.Lock()
 | 
			
		||||
	view.appendBufferUnlocked(message)
 | 
			
		||||
	view.msgBufferLock.Unlock()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (view *MessageView) appendBufferUnlocked(message messages.UIMessage) {
 | 
			
		||||
func (view *MessageView) appendBufferUnlocked(message *messages.UIMessage) {
 | 
			
		||||
	for i := 0; i < message.Height(); i++ {
 | 
			
		||||
		view.msgBuffer = append(view.msgBuffer, message)
 | 
			
		||||
	}
 | 
			
		||||
	view.prevMsgCount++
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (view *MessageView) replaceBuffer(original messages.UIMessage, new messages.UIMessage) {
 | 
			
		||||
func (view *MessageView) replaceBuffer(original *messages.UIMessage, new *messages.UIMessage) {
 | 
			
		||||
	start := -1
 | 
			
		||||
	end := -1
 | 
			
		||||
	view.msgBufferLock.RLock()
 | 
			
		||||
@@ -240,7 +254,7 @@ func (view *MessageView) replaceBuffer(original messages.UIMessage, new messages
 | 
			
		||||
 | 
			
		||||
	if start == -1 {
 | 
			
		||||
		debug.Print("Called replaceBuffer() with message that was not in the buffer:", original)
 | 
			
		||||
		debug.PrintStack()
 | 
			
		||||
		//debug.PrintStack()
 | 
			
		||||
		view.appendBuffer(new)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
@@ -280,7 +294,7 @@ func (view *MessageView) recalculateBuffers() {
 | 
			
		||||
		if !prefs.BareMessageView {
 | 
			
		||||
			width -= view.TimestampWidth + TimestampSenderGap + view.widestSender() + SenderMessageGap
 | 
			
		||||
		}
 | 
			
		||||
		view.msgBuffer = []messages.UIMessage{}
 | 
			
		||||
		view.msgBuffer = []*messages.UIMessage{}
 | 
			
		||||
		view.prevMsgCount = 0
 | 
			
		||||
		for i, message := range view.messages {
 | 
			
		||||
			if message == nil {
 | 
			
		||||
@@ -299,17 +313,17 @@ func (view *MessageView) recalculateBuffers() {
 | 
			
		||||
	view.prevPrefs = prefs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (view *MessageView) handleMessageClick(message messages.UIMessage) bool {
 | 
			
		||||
	switch message := message.(type) {
 | 
			
		||||
func (view *MessageView) handleMessageClick(message *messages.UIMessage) bool {
 | 
			
		||||
	switch msg := message.Renderer.(type) {
 | 
			
		||||
	case *messages.ImageMessage:
 | 
			
		||||
		open.Open(message.Path())
 | 
			
		||||
	case messages.UIMessage:
 | 
			
		||||
		open.Open(msg.Path())
 | 
			
		||||
	default:
 | 
			
		||||
		debug.Print("Message clicked:", message)
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (view *MessageView) handleUsernameClick(message messages.UIMessage, prevMessage messages.UIMessage) bool {
 | 
			
		||||
func (view *MessageView) handleUsernameClick(message *messages.UIMessage, prevMessage *messages.UIMessage) bool {
 | 
			
		||||
	if prevMessage != nil && prevMessage.Sender() == message.Sender() {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
@@ -317,7 +331,7 @@ func (view *MessageView) handleUsernameClick(message messages.UIMessage, prevMes
 | 
			
		||||
	if len(message.Sender()) == 0 {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	sender := fmt.Sprintf("[%s](https://matrix.to/#/%s)", message.Sender(), message.SenderID())
 | 
			
		||||
	sender := fmt.Sprintf("[%s](https://matrix.to/#/%s)", message.Sender(), message.SenderID)
 | 
			
		||||
 | 
			
		||||
	cursorPos := view.parent.input.GetCursorOffset()
 | 
			
		||||
	text := view.parent.input.GetText()
 | 
			
		||||
@@ -363,7 +377,7 @@ func (view *MessageView) OnMouseEvent(event mauview.MouseEvent) bool {
 | 
			
		||||
 | 
			
		||||
		view.msgBufferLock.RLock()
 | 
			
		||||
		message := view.msgBuffer[line]
 | 
			
		||||
		var prevMessage messages.UIMessage
 | 
			
		||||
		var prevMessage *messages.UIMessage
 | 
			
		||||
		if y != 0 && line > 0 {
 | 
			
		||||
			prevMessage = view.msgBuffer[line-1]
 | 
			
		||||
		}
 | 
			
		||||
@@ -496,7 +510,7 @@ func (view *MessageView) getIndexOffset(screen mauview.Screen, height, messageX
 | 
			
		||||
func (view *MessageView) CapturePlaintext(height int) string {
 | 
			
		||||
	var buf strings.Builder
 | 
			
		||||
	indexOffset := view.TotalHeight() - view.ScrollOffset - height
 | 
			
		||||
	var prevMessage messages.UIMessage
 | 
			
		||||
	var prevMessage *messages.UIMessage
 | 
			
		||||
	view.msgBufferLock.RLock()
 | 
			
		||||
	for line := 0; line < height; line++ {
 | 
			
		||||
		index := indexOffset + line
 | 
			
		||||
@@ -504,14 +518,13 @@ func (view *MessageView) CapturePlaintext(height int) string {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		meta := view.msgBuffer[index]
 | 
			
		||||
		message, ok := meta.(messages.UIMessage)
 | 
			
		||||
		if ok && message != prevMessage {
 | 
			
		||||
		message := view.msgBuffer[index]
 | 
			
		||||
		if message != prevMessage {
 | 
			
		||||
			var sender string
 | 
			
		||||
			if len(message.Sender()) > 0 {
 | 
			
		||||
				sender = fmt.Sprintf(" <%s>", message.Sender())
 | 
			
		||||
			} else if message.Type() == "m.emote" {
 | 
			
		||||
				sender = fmt.Sprintf(" * %s", message.RealSender())
 | 
			
		||||
			} else if message.Type == mautrix.MsgEmote {
 | 
			
		||||
				sender = fmt.Sprintf(" * %s", message.SenderName)
 | 
			
		||||
			}
 | 
			
		||||
			fmt.Fprintf(&buf, "%s%s %s\n", message.FormatTime(), sender, message.PlainText())
 | 
			
		||||
			prevMessage = message
 | 
			
		||||
@@ -561,7 +574,7 @@ func (view *MessageView) Draw(screen mauview.Screen) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var prevMsg messages.UIMessage
 | 
			
		||||
	var prevMsg *messages.UIMessage
 | 
			
		||||
	view.msgBufferLock.RLock()
 | 
			
		||||
	for line := viewStart; line < height && indexOffset+line < len(view.msgBuffer); line++ {
 | 
			
		||||
		index := indexOffset + line
 | 
			
		||||
 
 | 
			
		||||
@@ -27,44 +27,60 @@ import (
 | 
			
		||||
	"maunium.net/go/tcell"
 | 
			
		||||
 | 
			
		||||
	"maunium.net/go/gomuks/interface"
 | 
			
		||||
	"maunium.net/go/gomuks/ui/messages/tstring"
 | 
			
		||||
	"maunium.net/go/gomuks/ui/widget"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type BaseMessage struct {
 | 
			
		||||
	MsgID          string
 | 
			
		||||
	MsgTxnID       string
 | 
			
		||||
	MsgType        mautrix.MessageType
 | 
			
		||||
	MsgSenderID    string
 | 
			
		||||
	MsgSender      string
 | 
			
		||||
	MsgSenderColor tcell.Color
 | 
			
		||||
	MsgTimestamp   time.Time
 | 
			
		||||
	MsgState       mautrix.OutgoingEventState
 | 
			
		||||
	MsgIsHighlight bool
 | 
			
		||||
	MsgIsService   bool
 | 
			
		||||
	MsgSource      json.RawMessage
 | 
			
		||||
	ReplyTo        UIMessage
 | 
			
		||||
	buffer         []tstring.TString
 | 
			
		||||
type MessageRenderer interface {
 | 
			
		||||
	Draw(screen mauview.Screen)
 | 
			
		||||
	NotificationContent() string
 | 
			
		||||
	PlainText() string
 | 
			
		||||
	CalculateBuffer(prefs config.UserPreferences, width int, msg *UIMessage)
 | 
			
		||||
	RegisterMatrix(matrix ifc.MatrixContainer)
 | 
			
		||||
	Height() int
 | 
			
		||||
	Clone() MessageRenderer
 | 
			
		||||
	String() string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newBaseMessage(event *mautrix.Event, displayname string) BaseMessage {
 | 
			
		||||
type UIMessage struct {
 | 
			
		||||
	EventID            string
 | 
			
		||||
	TxnID              string
 | 
			
		||||
	Relation           mautrix.RelatesTo
 | 
			
		||||
	Type               mautrix.MessageType
 | 
			
		||||
	SenderID           string
 | 
			
		||||
	SenderName         string
 | 
			
		||||
	DefaultSenderColor tcell.Color
 | 
			
		||||
	Timestamp          time.Time
 | 
			
		||||
	State              mautrix.OutgoingEventState
 | 
			
		||||
	IsHighlight        bool
 | 
			
		||||
	IsService          bool
 | 
			
		||||
	Source             json.RawMessage
 | 
			
		||||
	ReplyTo            *UIMessage
 | 
			
		||||
	Renderer           MessageRenderer
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const DateFormat = "January _2, 2006"
 | 
			
		||||
const TimeFormat = "15:04:05"
 | 
			
		||||
 | 
			
		||||
func newUIMessage(event *mautrix.Event, displayname string, renderer MessageRenderer) *UIMessage {
 | 
			
		||||
	msgtype := event.Content.MsgType
 | 
			
		||||
	if len(msgtype) == 0 {
 | 
			
		||||
		msgtype = mautrix.MessageType(event.Type.String())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return BaseMessage{
 | 
			
		||||
		MsgSenderID:    event.Sender,
 | 
			
		||||
		MsgSender:      displayname,
 | 
			
		||||
		MsgTimestamp:   unixToTime(event.Timestamp),
 | 
			
		||||
		MsgSenderColor: widget.GetHashColor(event.Sender),
 | 
			
		||||
		MsgType:        msgtype,
 | 
			
		||||
		MsgID:          event.ID,
 | 
			
		||||
		MsgTxnID:       event.Unsigned.TransactionID,
 | 
			
		||||
		MsgState:       event.Unsigned.OutgoingState,
 | 
			
		||||
		MsgIsHighlight: false,
 | 
			
		||||
		MsgIsService:   false,
 | 
			
		||||
		MsgSource:      event.Content.VeryRaw,
 | 
			
		||||
	return &UIMessage{
 | 
			
		||||
		SenderID:           event.Sender,
 | 
			
		||||
		SenderName:         displayname,
 | 
			
		||||
		Timestamp:          unixToTime(event.Timestamp),
 | 
			
		||||
		DefaultSenderColor: widget.GetHashColor(event.Sender),
 | 
			
		||||
		Type:               msgtype,
 | 
			
		||||
		EventID:            event.ID,
 | 
			
		||||
		TxnID:              event.Unsigned.TransactionID,
 | 
			
		||||
		Relation:           *event.Content.GetRelatesTo(),
 | 
			
		||||
		State:              event.Unsigned.OutgoingState,
 | 
			
		||||
		IsHighlight:        false,
 | 
			
		||||
		IsService:          false,
 | 
			
		||||
		Source:             event.Content.VeryRaw,
 | 
			
		||||
		Renderer:           renderer,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -76,44 +92,38 @@ func unixToTime(unix int64) time.Time {
 | 
			
		||||
	return timestamp
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *BaseMessage) RegisterMatrix(matrix ifc.MatrixContainer) {}
 | 
			
		||||
 | 
			
		||||
// Sender 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 (msg *BaseMessage) Sender() string {
 | 
			
		||||
	switch msg.MsgState {
 | 
			
		||||
func (msg *UIMessage) Sender() string {
 | 
			
		||||
	switch msg.State {
 | 
			
		||||
	case mautrix.EventStateLocalEcho:
 | 
			
		||||
		return "Sending..."
 | 
			
		||||
	case mautrix.EventStateSendFail:
 | 
			
		||||
		return "Error"
 | 
			
		||||
	}
 | 
			
		||||
	switch msg.MsgType {
 | 
			
		||||
	switch msg.Type {
 | 
			
		||||
	case "m.emote":
 | 
			
		||||
		// Emotes don't show a separate sender, it's included in the buffer.
 | 
			
		||||
		return ""
 | 
			
		||||
	default:
 | 
			
		||||
		return msg.MsgSender
 | 
			
		||||
		return msg.SenderName
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *BaseMessage) SenderID() string {
 | 
			
		||||
	return msg.MsgSenderID
 | 
			
		||||
func (msg *UIMessage) NotificationSenderName() string {
 | 
			
		||||
	return msg.SenderName
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *BaseMessage) RealSender() string {
 | 
			
		||||
	return msg.MsgSender
 | 
			
		||||
func (msg *UIMessage) NotificationContent() string {
 | 
			
		||||
	return msg.Renderer.NotificationContent()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *BaseMessage) NotificationSenderName() string {
 | 
			
		||||
	return msg.MsgSender
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *BaseMessage) getStateSpecificColor() tcell.Color {
 | 
			
		||||
	switch msg.MsgState {
 | 
			
		||||
func (msg *UIMessage) getStateSpecificColor() tcell.Color {
 | 
			
		||||
	switch msg.State {
 | 
			
		||||
	case mautrix.EventStateLocalEcho:
 | 
			
		||||
		return tcell.ColorGray
 | 
			
		||||
	case mautrix.EventStateSendFail:
 | 
			
		||||
@@ -132,31 +142,31 @@ func (msg *BaseMessage) getStateSpecificColor() tcell.Color {
 | 
			
		||||
//
 | 
			
		||||
// 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 (msg *BaseMessage) SenderColor() tcell.Color {
 | 
			
		||||
func (msg *UIMessage) SenderColor() tcell.Color {
 | 
			
		||||
	stateColor := msg.getStateSpecificColor()
 | 
			
		||||
	switch {
 | 
			
		||||
	case stateColor != tcell.ColorDefault:
 | 
			
		||||
		return stateColor
 | 
			
		||||
	case msg.MsgType == "m.room.member":
 | 
			
		||||
		return widget.GetHashColor(msg.MsgSender)
 | 
			
		||||
	case msg.MsgIsService:
 | 
			
		||||
	case msg.Type == "m.room.member":
 | 
			
		||||
		return widget.GetHashColor(msg.SenderName)
 | 
			
		||||
	case msg.IsService:
 | 
			
		||||
		return tcell.ColorGray
 | 
			
		||||
	default:
 | 
			
		||||
		return msg.MsgSenderColor
 | 
			
		||||
		return msg.DefaultSenderColor
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TextColor returns the color the actual content of the message should be shown in.
 | 
			
		||||
func (msg *BaseMessage) TextColor() tcell.Color {
 | 
			
		||||
func (msg *UIMessage) TextColor() tcell.Color {
 | 
			
		||||
	stateColor := msg.getStateSpecificColor()
 | 
			
		||||
	switch {
 | 
			
		||||
	case stateColor != tcell.ColorDefault:
 | 
			
		||||
		return stateColor
 | 
			
		||||
	case msg.MsgIsService, msg.MsgType == "m.notice":
 | 
			
		||||
	case msg.IsService, msg.Type == "m.notice":
 | 
			
		||||
		return tcell.ColorGray
 | 
			
		||||
	case msg.MsgIsHighlight:
 | 
			
		||||
	case msg.IsHighlight:
 | 
			
		||||
		return tcell.ColorYellow
 | 
			
		||||
	case msg.MsgType == "m.room.member":
 | 
			
		||||
	case msg.Type == "m.room.member":
 | 
			
		||||
		return tcell.ColorGreen
 | 
			
		||||
	default:
 | 
			
		||||
		return tcell.ColorDefault
 | 
			
		||||
@@ -169,14 +179,14 @@ func (msg *BaseMessage) TextColor() tcell.Color {
 | 
			
		||||
// gray and red respectively.
 | 
			
		||||
//
 | 
			
		||||
// However, other messages are the default color instead of a color stored in the struct.
 | 
			
		||||
func (msg *BaseMessage) TimestampColor() tcell.Color {
 | 
			
		||||
	if msg.MsgIsService {
 | 
			
		||||
func (msg *UIMessage) TimestampColor() tcell.Color {
 | 
			
		||||
	if msg.IsService {
 | 
			
		||||
		return tcell.ColorGray
 | 
			
		||||
	}
 | 
			
		||||
	return msg.getStateSpecificColor()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *BaseMessage) ReplyHeight() int {
 | 
			
		||||
func (msg *UIMessage) ReplyHeight() int {
 | 
			
		||||
	if msg.ReplyTo != nil {
 | 
			
		||||
		return 1 + msg.ReplyTo.Height()
 | 
			
		||||
	}
 | 
			
		||||
@@ -184,102 +194,76 @@ func (msg *BaseMessage) ReplyHeight() int {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Height returns the number of rows in the computed buffer (see Buffer()).
 | 
			
		||||
func (msg *BaseMessage) Height() int {
 | 
			
		||||
	return msg.ReplyHeight() + len(msg.buffer)
 | 
			
		||||
func (msg *UIMessage) Height() int {
 | 
			
		||||
	return msg.ReplyHeight() + msg.Renderer.Height()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Timestamp returns the full timestamp when the message was sent.
 | 
			
		||||
func (msg *BaseMessage) Timestamp() time.Time {
 | 
			
		||||
	return msg.MsgTimestamp
 | 
			
		||||
func (msg *UIMessage) Time() time.Time {
 | 
			
		||||
	return msg.Timestamp
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FormatTime returns the formatted time when the message was sent.
 | 
			
		||||
func (msg *BaseMessage) FormatTime() string {
 | 
			
		||||
	return msg.MsgTimestamp.Format(TimeFormat)
 | 
			
		||||
func (msg *UIMessage) FormatTime() string {
 | 
			
		||||
	return msg.Timestamp.Format(TimeFormat)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FormatDate returns the formatted date when the message was sent.
 | 
			
		||||
func (msg *BaseMessage) FormatDate() string {
 | 
			
		||||
	return msg.MsgTimestamp.Format(DateFormat)
 | 
			
		||||
func (msg *UIMessage) FormatDate() string {
 | 
			
		||||
	return msg.Timestamp.Format(DateFormat)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *BaseMessage) SameDate(message UIMessage) bool {
 | 
			
		||||
	year1, month1, day1 := msg.Timestamp().Date()
 | 
			
		||||
	year2, month2, day2 := message.Timestamp().Date()
 | 
			
		||||
func (msg *UIMessage) SameDate(message *UIMessage) bool {
 | 
			
		||||
	year1, month1, day1 := msg.Timestamp.Date()
 | 
			
		||||
	year2, month2, day2 := message.Timestamp.Date()
 | 
			
		||||
	return day1 == day2 && month1 == month2 && year1 == year2
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *BaseMessage) ID() string {
 | 
			
		||||
	if len(msg.MsgID) == 0 {
 | 
			
		||||
		return msg.MsgTxnID
 | 
			
		||||
func (msg *UIMessage) ID() string {
 | 
			
		||||
	if len(msg.EventID) == 0 {
 | 
			
		||||
		return msg.TxnID
 | 
			
		||||
	}
 | 
			
		||||
	return msg.MsgID
 | 
			
		||||
	return msg.EventID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *BaseMessage) SetID(id string) {
 | 
			
		||||
	msg.MsgID = id
 | 
			
		||||
func (msg *UIMessage) SetID(id string) {
 | 
			
		||||
	msg.EventID = id
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *BaseMessage) TxnID() string {
 | 
			
		||||
	return msg.MsgTxnID
 | 
			
		||||
func (msg *UIMessage) SetIsHighlight(isHighlight bool) {
 | 
			
		||||
	// TODO Textmessage cache needs to be cleared
 | 
			
		||||
	msg.IsHighlight = isHighlight
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *BaseMessage) Type() mautrix.MessageType {
 | 
			
		||||
	return msg.MsgType
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *BaseMessage) State() mautrix.OutgoingEventState {
 | 
			
		||||
	return msg.MsgState
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *BaseMessage) SetState(state mautrix.OutgoingEventState) {
 | 
			
		||||
	msg.MsgState = state
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *BaseMessage) IsHighlight() bool {
 | 
			
		||||
	return msg.MsgIsHighlight
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *BaseMessage) SetIsHighlight(isHighlight bool) {
 | 
			
		||||
	msg.MsgIsHighlight = isHighlight
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *BaseMessage) Source() json.RawMessage {
 | 
			
		||||
	return msg.MsgSource
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *BaseMessage) SetReplyTo(event UIMessage) {
 | 
			
		||||
	msg.ReplyTo = event
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *BaseMessage) Draw(screen mauview.Screen) {
 | 
			
		||||
func (msg *UIMessage) Draw(screen mauview.Screen) {
 | 
			
		||||
	screen = msg.DrawReply(screen)
 | 
			
		||||
	for y, line := range msg.buffer {
 | 
			
		||||
		line.Draw(screen, 0, y)
 | 
			
		||||
	}
 | 
			
		||||
	msg.Renderer.Draw(screen)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *BaseMessage) clone() BaseMessage {
 | 
			
		||||
func (msg *UIMessage) Clone() *UIMessage {
 | 
			
		||||
	clone := *msg
 | 
			
		||||
	clone.buffer = nil
 | 
			
		||||
	return clone
 | 
			
		||||
	clone.Renderer = clone.Renderer.Clone()
 | 
			
		||||
	return &clone
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *BaseMessage) CalculateReplyBuffer(preferences config.UserPreferences, width int) {
 | 
			
		||||
func (msg *UIMessage) CalculateReplyBuffer(preferences config.UserPreferences, width int) {
 | 
			
		||||
	if msg.ReplyTo == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	msg.ReplyTo.CalculateBuffer(preferences, width-1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *BaseMessage) DrawReply(screen mauview.Screen) mauview.Screen {
 | 
			
		||||
func (msg *UIMessage) CalculateBuffer(preferences config.UserPreferences, width int) {
 | 
			
		||||
	msg.Renderer.CalculateBuffer(preferences, width-1, msg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *UIMessage) DrawReply(screen mauview.Screen) mauview.Screen {
 | 
			
		||||
	if msg.ReplyTo == nil {
 | 
			
		||||
		return screen
 | 
			
		||||
	}
 | 
			
		||||
	width, height := screen.Size()
 | 
			
		||||
	replyHeight := msg.ReplyTo.Height()
 | 
			
		||||
	widget.WriteLineSimpleColor(screen, "In reply to", 1, 0, tcell.ColorGreen)
 | 
			
		||||
	widget.WriteLineSimpleColor(screen, msg.ReplyTo.RealSender(), 13, 0, msg.ReplyTo.SenderColor())
 | 
			
		||||
	widget.WriteLineSimpleColor(screen, msg.ReplyTo.SenderName, 13, 0, msg.ReplyTo.SenderColor())
 | 
			
		||||
	for y := 0; y < 1+replyHeight; y++ {
 | 
			
		||||
		screen.SetCell(0, y, tcell.StyleDefault, '▊')
 | 
			
		||||
	}
 | 
			
		||||
@@ -288,16 +272,21 @@ func (msg *BaseMessage) DrawReply(screen mauview.Screen) mauview.Screen {
 | 
			
		||||
	return mauview.NewProxyScreen(screen, 0, replyHeight+1, width, height-replyHeight-1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *BaseMessage) String() string {
 | 
			
		||||
	return fmt.Sprintf(`&messages.BaseMessage{
 | 
			
		||||
func (msg *UIMessage) String() string {
 | 
			
		||||
	return fmt.Sprintf(`&messages.UIMessage{
 | 
			
		||||
    ID="%s", TxnID="%s",
 | 
			
		||||
    Type="%s", Timestamp=%s,
 | 
			
		||||
    Sender={ID="%s", Name="%s", Color=#%X},
 | 
			
		||||
    IsService=%t, IsHighlight=%t,
 | 
			
		||||
    Renderer=%s,
 | 
			
		||||
}`,
 | 
			
		||||
		msg.MsgID, msg.MsgTxnID,
 | 
			
		||||
		msg.MsgType, msg.MsgTimestamp.String(),
 | 
			
		||||
		msg.MsgSenderID, msg.MsgSender, msg.MsgSenderColor.Hex(),
 | 
			
		||||
		msg.MsgIsService, msg.MsgIsHighlight,
 | 
			
		||||
		msg.EventID, msg.TxnID,
 | 
			
		||||
		msg.Type, msg.Timestamp.String(),
 | 
			
		||||
		msg.SenderID, msg.SenderName, msg.DefaultSenderColor.Hex(),
 | 
			
		||||
		msg.IsService, msg.IsHighlight, msg.Renderer.String(),
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *UIMessage) PlainText() string {
 | 
			
		||||
	return msg.Renderer.PlainText()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -17,9 +17,12 @@
 | 
			
		||||
package messages
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	ifc "maunium.net/go/gomuks/interface"
 | 
			
		||||
	"maunium.net/go/mautrix"
 | 
			
		||||
	"maunium.net/go/mauview"
 | 
			
		||||
	"maunium.net/go/tcell"
 | 
			
		||||
 | 
			
		||||
	"maunium.net/go/gomuks/config"
 | 
			
		||||
@@ -27,55 +30,63 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ExpandedTextMessage struct {
 | 
			
		||||
	BaseMessage
 | 
			
		||||
	MsgText tstring.TString
 | 
			
		||||
	Text   tstring.TString
 | 
			
		||||
	buffer []tstring.TString
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewExpandedTextMessage creates a new ExpandedTextMessage object with the provided values and the default state.
 | 
			
		||||
func NewExpandedTextMessage(event *mautrix.Event, displayname string, text tstring.TString) UIMessage {
 | 
			
		||||
	return &ExpandedTextMessage{
 | 
			
		||||
		BaseMessage: newBaseMessage(event, displayname),
 | 
			
		||||
		MsgText:     text,
 | 
			
		||||
	}
 | 
			
		||||
func NewExpandedTextMessage(event *mautrix.Event, displayname string, text tstring.TString) *UIMessage {
 | 
			
		||||
	return newUIMessage(event, displayname, &ExpandedTextMessage{
 | 
			
		||||
		Text: text,
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewDateChangeMessage(text string) UIMessage {
 | 
			
		||||
func NewDateChangeMessage(text string) *UIMessage {
 | 
			
		||||
	midnight := time.Now()
 | 
			
		||||
	midnight = time.Date(midnight.Year(), midnight.Month(), midnight.Day(),
 | 
			
		||||
		0, 0, 0, 0,
 | 
			
		||||
		midnight.Location())
 | 
			
		||||
	return &ExpandedTextMessage{
 | 
			
		||||
		BaseMessage: BaseMessage{
 | 
			
		||||
			MsgSenderID:  "*",
 | 
			
		||||
			MsgSender:    "*",
 | 
			
		||||
			MsgTimestamp: midnight,
 | 
			
		||||
			MsgIsService: true,
 | 
			
		||||
	return &UIMessage{
 | 
			
		||||
		SenderID:   "*",
 | 
			
		||||
		SenderName: "*",
 | 
			
		||||
		Timestamp:  midnight,
 | 
			
		||||
		IsService:  true,
 | 
			
		||||
		Renderer: &ExpandedTextMessage{
 | 
			
		||||
			Text: tstring.NewColorTString(text, tcell.ColorGreen),
 | 
			
		||||
		},
 | 
			
		||||
		MsgText: tstring.NewColorTString(text, tcell.ColorGreen),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func (msg *ExpandedTextMessage) Clone() UIMessage {
 | 
			
		||||
func (msg *ExpandedTextMessage) Clone() MessageRenderer {
 | 
			
		||||
	return &ExpandedTextMessage{
 | 
			
		||||
		BaseMessage: msg.BaseMessage.clone(),
 | 
			
		||||
		MsgText:     msg.MsgText.Clone(),
 | 
			
		||||
		Text: msg.Text.Clone(),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *ExpandedTextMessage) GenerateText() tstring.TString {
 | 
			
		||||
	return msg.MsgText
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *ExpandedTextMessage) NotificationContent() string {
 | 
			
		||||
	return msg.MsgText.String()
 | 
			
		||||
	return msg.Text.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *ExpandedTextMessage) PlainText() string {
 | 
			
		||||
	return msg.MsgText.String()
 | 
			
		||||
	return msg.Text.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *ExpandedTextMessage) CalculateBuffer(prefs config.UserPreferences, width int) {
 | 
			
		||||
	msg.CalculateReplyBuffer(prefs, width)
 | 
			
		||||
	msg.calculateBufferWithText(prefs, msg.MsgText, width)
 | 
			
		||||
func (msg *ExpandedTextMessage) String() string {
 | 
			
		||||
	return fmt.Sprintf(`&messages.ExpandedTextMessage{Text="%s"}`, msg.Text.String())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *ExpandedTextMessage) CalculateBuffer(prefs config.UserPreferences, width int, uiMsg *UIMessage) {
 | 
			
		||||
	msg.buffer = calculateBufferWithText(prefs, msg.Text, width, uiMsg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *ExpandedTextMessage) Height() int {
 | 
			
		||||
	return len(msg.buffer)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *ExpandedTextMessage) Draw(screen mauview.Screen) {
 | 
			
		||||
	for y, line := range msg.buffer {
 | 
			
		||||
		line.Draw(screen, 0, y)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *ExpandedTextMessage) RegisterMatrix(matrix ifc.MatrixContainer) {}
 | 
			
		||||
 
 | 
			
		||||
@@ -17,9 +17,7 @@
 | 
			
		||||
package messages
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	ifc "maunium.net/go/gomuks/interface"
 | 
			
		||||
	"maunium.net/go/mautrix"
 | 
			
		||||
	"maunium.net/go/mauview"
 | 
			
		||||
	"maunium.net/go/tcell"
 | 
			
		||||
@@ -29,30 +27,27 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type HTMLMessage struct {
 | 
			
		||||
	BaseMessage
 | 
			
		||||
 | 
			
		||||
	Root      html.Entity
 | 
			
		||||
	FocusedBg tcell.Color
 | 
			
		||||
	focused   bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewHTMLMessage(event *mautrix.Event, displayname string, root html.Entity) UIMessage {
 | 
			
		||||
	return &HTMLMessage{
 | 
			
		||||
		BaseMessage: newBaseMessage(event, displayname),
 | 
			
		||||
		Root:        root,
 | 
			
		||||
	}
 | 
			
		||||
func NewHTMLMessage(event *mautrix.Event, displayname string, root html.Entity) *UIMessage {
 | 
			
		||||
	return newUIMessage(event, displayname, &HTMLMessage{
 | 
			
		||||
		Root: root,
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (hw *HTMLMessage) Clone() UIMessage {
 | 
			
		||||
func (hw *HTMLMessage) RegisterMatrix(matrix ifc.MatrixContainer) {}
 | 
			
		||||
 | 
			
		||||
func (hw *HTMLMessage) Clone() MessageRenderer {
 | 
			
		||||
	return &HTMLMessage{
 | 
			
		||||
		BaseMessage: hw.BaseMessage.clone(),
 | 
			
		||||
		Root:        hw.Root.Clone(),
 | 
			
		||||
		FocusedBg:   hw.FocusedBg,
 | 
			
		||||
		Root:      hw.Root.Clone(),
 | 
			
		||||
		FocusedBg: hw.FocusedBg,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (hw *HTMLMessage) Draw(screen mauview.Screen) {
 | 
			
		||||
	screen = hw.DrawReply(screen)
 | 
			
		||||
	if hw.focused {
 | 
			
		||||
		screen.SetStyle(tcell.StyleDefault.Background(hw.FocusedBg))
 | 
			
		||||
	}
 | 
			
		||||
@@ -80,18 +75,17 @@ func (hw *HTMLMessage) OnPasteEvent(event mauview.PasteEvent) bool {
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (hw *HTMLMessage) CalculateBuffer(preferences config.UserPreferences, width int) {
 | 
			
		||||
func (hw *HTMLMessage) CalculateBuffer(preferences config.UserPreferences, width int, msg *UIMessage) {
 | 
			
		||||
	if width < 2 {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	hw.CalculateReplyBuffer(preferences, width)
 | 
			
		||||
	// TODO account for bare messages in initial startX
 | 
			
		||||
	startX := 0
 | 
			
		||||
	hw.Root.CalculateBuffer(width, startX, preferences.BareMessageView)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (hw *HTMLMessage) Height() int {
 | 
			
		||||
	return hw.ReplyHeight() + hw.Root.Height()
 | 
			
		||||
	return hw.Root.Height()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (hw *HTMLMessage) PlainText() string {
 | 
			
		||||
@@ -103,8 +97,5 @@ func (hw *HTMLMessage) NotificationContent() string {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (hw *HTMLMessage) String() string {
 | 
			
		||||
	return fmt.Sprintf("&messages.HTMLMessage{\n" +
 | 
			
		||||
		"    Base=%s,\n" +
 | 
			
		||||
		"    Root=||\n%s\n" +
 | 
			
		||||
		"}", strings.Replace(hw.BaseMessage.String(), "\n", "\n    ", -1), hw.Root.String())
 | 
			
		||||
	return hw.Root.String()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,7 @@ import (
 | 
			
		||||
	"image/color"
 | 
			
		||||
 | 
			
		||||
	"maunium.net/go/mautrix"
 | 
			
		||||
	"maunium.net/go/mauview"
 | 
			
		||||
	"maunium.net/go/tcell"
 | 
			
		||||
 | 
			
		||||
	"maunium.net/go/gomuks/config"
 | 
			
		||||
@@ -32,32 +33,30 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ImageMessage struct {
 | 
			
		||||
	BaseMessage
 | 
			
		||||
	Body       string
 | 
			
		||||
	Homeserver string
 | 
			
		||||
	FileID     string
 | 
			
		||||
	data       []byte
 | 
			
		||||
	buffer     []tstring.TString
 | 
			
		||||
 | 
			
		||||
	matrix ifc.MatrixContainer
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewImageMessage creates a new ImageMessage object with the provided values and the default state.
 | 
			
		||||
func NewImageMessage(matrix ifc.MatrixContainer, event *mautrix.Event, displayname string, body, homeserver, fileID string, data []byte) UIMessage {
 | 
			
		||||
	return &ImageMessage{
 | 
			
		||||
		newBaseMessage(event, displayname),
 | 
			
		||||
		body,
 | 
			
		||||
		homeserver,
 | 
			
		||||
		fileID,
 | 
			
		||||
		data,
 | 
			
		||||
		matrix,
 | 
			
		||||
	}
 | 
			
		||||
func NewImageMessage(matrix ifc.MatrixContainer, event *mautrix.Event, displayname string, body, homeserver, fileID string, data []byte) *UIMessage {
 | 
			
		||||
	return newUIMessage(event, displayname, &ImageMessage{
 | 
			
		||||
		Body: body,
 | 
			
		||||
		Homeserver: homeserver,
 | 
			
		||||
		FileID: fileID,
 | 
			
		||||
		data: data,
 | 
			
		||||
		matrix: matrix,
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *ImageMessage) Clone() UIMessage {
 | 
			
		||||
func (msg *ImageMessage) Clone() MessageRenderer {
 | 
			
		||||
	data := make([]byte, len(msg.data))
 | 
			
		||||
	copy(data, msg.data)
 | 
			
		||||
	return &ImageMessage{
 | 
			
		||||
		BaseMessage: msg.BaseMessage.clone(),
 | 
			
		||||
		Body:        msg.Body,
 | 
			
		||||
		Homeserver:  msg.Homeserver,
 | 
			
		||||
		FileID:      msg.FileID,
 | 
			
		||||
@@ -70,7 +69,7 @@ func (msg *ImageMessage) RegisterMatrix(matrix ifc.MatrixContainer) {
 | 
			
		||||
	msg.matrix = matrix
 | 
			
		||||
 | 
			
		||||
	if len(msg.data) == 0 {
 | 
			
		||||
		go msg.updateData()
 | 
			
		||||
		//FIXME go msg.updateData()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -82,6 +81,10 @@ func (msg *ImageMessage) PlainText() string {
 | 
			
		||||
	return fmt.Sprintf("%s: %s", msg.Body, msg.matrix.GetDownloadURL(msg.Homeserver, msg.FileID))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *ImageMessage) String() string {
 | 
			
		||||
	return fmt.Sprintf(`&messages.ImageMessage{Body="%s", Homeserver="%s", FileID="%s"}`, msg.Body, msg.Homeserver, msg.FileID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *ImageMessage) updateData() {
 | 
			
		||||
	defer debug.Recover()
 | 
			
		||||
	debug.Print("Loading image:", msg.Homeserver, msg.FileID)
 | 
			
		||||
@@ -101,14 +104,13 @@ func (msg *ImageMessage) Path() string {
 | 
			
		||||
// 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 (msg *ImageMessage) CalculateBuffer(prefs config.UserPreferences, width int) {
 | 
			
		||||
func (msg *ImageMessage) CalculateBuffer(prefs config.UserPreferences, width int, uiMsg *UIMessage) {
 | 
			
		||||
	if width < 2 {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	msg.CalculateReplyBuffer(prefs, width)
 | 
			
		||||
 | 
			
		||||
	if prefs.BareMessageView || prefs.DisableImages {
 | 
			
		||||
		msg.calculateBufferWithText(prefs, tstring.NewTString(msg.PlainText()), width)
 | 
			
		||||
		msg.buffer = calculateBufferWithText(prefs, tstring.NewTString(msg.PlainText()), width, uiMsg)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -121,3 +123,13 @@ func (msg *ImageMessage) CalculateBuffer(prefs config.UserPreferences, width int
 | 
			
		||||
 | 
			
		||||
	msg.buffer = image.Render()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *ImageMessage) Height() int {
 | 
			
		||||
	return len(msg.buffer)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *ImageMessage) Draw(screen mauview.Screen) {
 | 
			
		||||
	for y, line := range msg.buffer {
 | 
			
		||||
		line.Draw(screen, 0, y)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,53 +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 (
 | 
			
		||||
	"maunium.net/go/gomuks/config"
 | 
			
		||||
	"maunium.net/go/gomuks/interface"
 | 
			
		||||
	"maunium.net/go/mautrix"
 | 
			
		||||
	"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
 | 
			
		||||
 | 
			
		||||
	Type() mautrix.MessageType
 | 
			
		||||
	Sender() string
 | 
			
		||||
	SenderColor() tcell.Color
 | 
			
		||||
	TextColor() tcell.Color
 | 
			
		||||
	TimestampColor() tcell.Color
 | 
			
		||||
	FormatTime() string
 | 
			
		||||
	FormatDate() string
 | 
			
		||||
	SameDate(message UIMessage) bool
 | 
			
		||||
 | 
			
		||||
	SetReplyTo(message UIMessage)
 | 
			
		||||
	CalculateBuffer(preferences config.UserPreferences, width int)
 | 
			
		||||
	Draw(screen mauview.Screen)
 | 
			
		||||
	Height() int
 | 
			
		||||
	PlainText() string
 | 
			
		||||
 | 
			
		||||
	Clone() UIMessage
 | 
			
		||||
 | 
			
		||||
	RealSender() string
 | 
			
		||||
	RegisterMatrix(matrix ifc.MatrixContainer)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const DateFormat = "January _2, 2006"
 | 
			
		||||
const TimeFormat = "15:04:05"
 | 
			
		||||
@@ -31,10 +31,10 @@ import (
 | 
			
		||||
	"maunium.net/go/gomuks/ui/widget"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func getCachedEvent(mainView ifc.MainView, roomID, eventID string) UIMessage {
 | 
			
		||||
func getCachedEvent(mainView ifc.MainView, roomID, eventID string) *UIMessage {
 | 
			
		||||
	if roomView := mainView.GetRoom(roomID); roomView != nil {
 | 
			
		||||
		if replyToIfcMsg := roomView.GetEvent(eventID); replyToIfcMsg != nil {
 | 
			
		||||
			if replyToMsg, ok := replyToIfcMsg.(UIMessage); ok && replyToMsg != nil {
 | 
			
		||||
			if replyToMsg, ok := replyToIfcMsg.(*UIMessage); ok && replyToMsg != nil {
 | 
			
		||||
				return replyToMsg
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
@@ -42,24 +42,19 @@ func getCachedEvent(mainView ifc.MainView, roomID, eventID string) UIMessage {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ParseEvent(matrix ifc.MatrixContainer, mainView ifc.MainView, room *rooms.Room, evt *mautrix.Event) UIMessage {
 | 
			
		||||
func ParseEvent(matrix ifc.MatrixContainer, mainView ifc.MainView, room *rooms.Room, evt *mautrix.Event) *UIMessage {
 | 
			
		||||
	msg := directParseEvent(matrix, room, evt)
 | 
			
		||||
	if msg == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	if len(evt.Content.GetReplyTo()) > 0 {
 | 
			
		||||
		replyToRoom := room
 | 
			
		||||
		if len(evt.Content.RelatesTo.InReplyTo.RoomID) > 0 {
 | 
			
		||||
			replyToRoom = matrix.GetRoom(evt.Content.RelatesTo.InReplyTo.RoomID)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if replyToMsg := getCachedEvent(mainView, replyToRoom.ID, evt.Content.GetReplyTo()); replyToMsg != nil {
 | 
			
		||||
		if replyToMsg := getCachedEvent(mainView, room.ID, evt.Content.GetReplyTo()); replyToMsg != nil {
 | 
			
		||||
			replyToMsg = replyToMsg.Clone()
 | 
			
		||||
			replyToMsg.SetReplyTo(nil)
 | 
			
		||||
			msg.SetReplyTo(replyToMsg)
 | 
			
		||||
		} else if replyToEvt, _ := matrix.GetEvent(replyToRoom, evt.Content.GetReplyTo()); replyToEvt != nil {
 | 
			
		||||
			if replyToMsg := directParseEvent(matrix, replyToRoom, replyToEvt); replyToMsg != nil {
 | 
			
		||||
				msg.SetReplyTo(replyToMsg)
 | 
			
		||||
			replyToMsg.ReplyTo = nil
 | 
			
		||||
			msg.ReplyTo = replyToMsg
 | 
			
		||||
		} else if replyToEvt, _ := matrix.GetEvent(room, evt.Content.GetReplyTo()); replyToEvt != nil {
 | 
			
		||||
			if replyToMsg := directParseEvent(matrix, room, replyToEvt); replyToMsg != nil {
 | 
			
		||||
				msg.ReplyTo = replyToMsg
 | 
			
		||||
			} else {
 | 
			
		||||
				// TODO add unrenderable reply header
 | 
			
		||||
			}
 | 
			
		||||
@@ -70,15 +65,22 @@ func ParseEvent(matrix ifc.MatrixContainer, mainView ifc.MainView, room *rooms.R
 | 
			
		||||
	return msg
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func directParseEvent(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.Event) UIMessage {
 | 
			
		||||
func directParseEvent(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.Event) *UIMessage {
 | 
			
		||||
	displayname := evt.Sender
 | 
			
		||||
	member := room.GetMember(evt.Sender)
 | 
			
		||||
	if member != nil {
 | 
			
		||||
		displayname = member.Displayname
 | 
			
		||||
	}
 | 
			
		||||
	switch evt.Type {
 | 
			
		||||
	case mautrix.EventSticker:
 | 
			
		||||
		evt.Content.MsgType = mautrix.MsgImage
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case mautrix.EventMessage:
 | 
			
		||||
		return ParseMessage(matrix, room, evt)
 | 
			
		||||
		return ParseMessage(matrix, room, evt, displayname)
 | 
			
		||||
	case mautrix.EventEncrypted:
 | 
			
		||||
		return NewExpandedTextMessage(evt, displayname, tstring.NewStyleTString("Encrypted messages are not yet supported", tcell.StyleDefault.Italic(true)))
 | 
			
		||||
	case mautrix.StateTopic, mautrix.StateRoomName, mautrix.StateAliases, mautrix.StateCanonicalAlias:
 | 
			
		||||
		return ParseStateEvent(matrix, room, evt)
 | 
			
		||||
		return ParseStateEvent(evt, displayname)
 | 
			
		||||
	case mautrix.StateMember:
 | 
			
		||||
		return ParseMembershipEvent(room, evt)
 | 
			
		||||
	}
 | 
			
		||||
@@ -86,12 +88,7 @@ func directParseEvent(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ParseStateEvent(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.Event) UIMessage {
 | 
			
		||||
	displayname := evt.Sender
 | 
			
		||||
	member := room.GetMember(evt.Sender)
 | 
			
		||||
	if member != nil {
 | 
			
		||||
		displayname = member.Displayname
 | 
			
		||||
	}
 | 
			
		||||
func ParseStateEvent(evt *mautrix.Event, displayname string) *UIMessage {
 | 
			
		||||
	text := tstring.NewColorTString(displayname, widget.GetHashColor(evt.Sender))
 | 
			
		||||
	switch evt.Type {
 | 
			
		||||
	case mautrix.StateTopic:
 | 
			
		||||
@@ -124,15 +121,13 @@ func ParseStateEvent(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.
 | 
			
		||||
	return NewExpandedTextMessage(evt, displayname, text)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ParseMessage(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.Event) UIMessage {
 | 
			
		||||
	displayname := evt.Sender
 | 
			
		||||
	member := room.GetMember(evt.Sender)
 | 
			
		||||
	if member != nil {
 | 
			
		||||
		displayname = member.Displayname
 | 
			
		||||
	}
 | 
			
		||||
func ParseMessage(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.Event, displayname string) *UIMessage {
 | 
			
		||||
	if len(evt.Content.GetReplyTo()) > 0 {
 | 
			
		||||
		evt.Content.RemoveReplyFallback()
 | 
			
		||||
	}
 | 
			
		||||
	if evt.Content.GetRelatesTo().Type == mautrix.RelReplace && evt.Content.NewContent != nil {
 | 
			
		||||
		evt.Content = *evt.Content.NewContent
 | 
			
		||||
	}
 | 
			
		||||
	switch evt.Content.MsgType {
 | 
			
		||||
	case "m.text", "m.notice", "m.emote":
 | 
			
		||||
		if evt.Content.Format == mautrix.FormatHTML {
 | 
			
		||||
@@ -224,7 +219,7 @@ func getMembershipEventContent(room *rooms.Room, evt *mautrix.Event) (sender str
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ParseMembershipEvent(room *rooms.Room, evt *mautrix.Event) UIMessage {
 | 
			
		||||
func ParseMembershipEvent(room *rooms.Room, evt *mautrix.Event) *UIMessage {
 | 
			
		||||
	displayname, text := getMembershipEventContent(room, evt)
 | 
			
		||||
	if len(text) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
 
 | 
			
		||||
@@ -52,12 +52,12 @@ func matchBoundaryPattern(bare bool, extract tstring.TString) tstring.TString {
 | 
			
		||||
// 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 (msg *BaseMessage) calculateBufferWithText(prefs config.UserPreferences, text tstring.TString, width int) {
 | 
			
		||||
func calculateBufferWithText(prefs config.UserPreferences, text tstring.TString, width int, msg *UIMessage) []tstring.TString {
 | 
			
		||||
	if width < 2 {
 | 
			
		||||
		return
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	msg.buffer = []tstring.TString{}
 | 
			
		||||
	var buffer []tstring.TString
 | 
			
		||||
 | 
			
		||||
	if prefs.BareMessageView {
 | 
			
		||||
		newText := tstring.NewTString(msg.FormatTime())
 | 
			
		||||
@@ -74,7 +74,7 @@ func (msg *BaseMessage) calculateBufferWithText(prefs config.UserPreferences, te
 | 
			
		||||
	newlines := 0
 | 
			
		||||
	for _, str := range forcedLinebreaks {
 | 
			
		||||
		if len(str) == 0 && newlines < 1 {
 | 
			
		||||
			msg.buffer = append(msg.buffer, tstring.TString{})
 | 
			
		||||
			buffer = append(buffer, tstring.TString{})
 | 
			
		||||
			newlines++
 | 
			
		||||
		} else {
 | 
			
		||||
			newlines = 0
 | 
			
		||||
@@ -88,8 +88,9 @@ func (msg *BaseMessage) calculateBufferWithText(prefs config.UserPreferences, te
 | 
			
		||||
				}
 | 
			
		||||
				extract = matchBoundaryPattern(prefs.BareMessageView, extract)
 | 
			
		||||
			}
 | 
			
		||||
			msg.buffer = append(msg.buffer, extract)
 | 
			
		||||
			buffer = append(buffer, extract)
 | 
			
		||||
			str = str[len(extract):]
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return buffer
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -20,72 +20,82 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	ifc "maunium.net/go/gomuks/interface"
 | 
			
		||||
	"maunium.net/go/mautrix"
 | 
			
		||||
	"maunium.net/go/mauview"
 | 
			
		||||
 | 
			
		||||
	"maunium.net/go/gomuks/config"
 | 
			
		||||
	"maunium.net/go/gomuks/ui/messages/tstring"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type TextMessage struct {
 | 
			
		||||
	BaseMessage
 | 
			
		||||
	cache   tstring.TString
 | 
			
		||||
	MsgText string
 | 
			
		||||
	cache  tstring.TString
 | 
			
		||||
	buffer []tstring.TString
 | 
			
		||||
	Text   string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewTextMessage creates a new UITextMessage object with the provided values and the default state.
 | 
			
		||||
func NewTextMessage(event *mautrix.Event, displayname string, text string) UIMessage {
 | 
			
		||||
	return &TextMessage{
 | 
			
		||||
		BaseMessage: newBaseMessage(event, displayname),
 | 
			
		||||
		MsgText:     text,
 | 
			
		||||
	}
 | 
			
		||||
func NewTextMessage(event *mautrix.Event, displayname string, text string) *UIMessage {
 | 
			
		||||
	return newUIMessage(event, displayname, &TextMessage{
 | 
			
		||||
		Text: text,
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewServiceMessage(text string) UIMessage {
 | 
			
		||||
	return &TextMessage{
 | 
			
		||||
		BaseMessage: BaseMessage{
 | 
			
		||||
			MsgSenderID:  "*",
 | 
			
		||||
			MsgSender:    "*",
 | 
			
		||||
			MsgTimestamp: time.Now(),
 | 
			
		||||
			MsgIsService: true,
 | 
			
		||||
func NewServiceMessage(text string) *UIMessage {
 | 
			
		||||
	return &UIMessage{
 | 
			
		||||
		SenderID:   "*",
 | 
			
		||||
		SenderName: "*",
 | 
			
		||||
		Timestamp:  time.Now(),
 | 
			
		||||
		IsService:  true,
 | 
			
		||||
		Renderer: &TextMessage{
 | 
			
		||||
			Text: text,
 | 
			
		||||
		},
 | 
			
		||||
		MsgText: text,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *TextMessage) Clone() UIMessage {
 | 
			
		||||
func (msg *TextMessage) Clone() MessageRenderer {
 | 
			
		||||
	return &TextMessage{
 | 
			
		||||
		BaseMessage: msg.BaseMessage.clone(),
 | 
			
		||||
		MsgText:     msg.MsgText,
 | 
			
		||||
		Text: msg.Text,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *TextMessage) getCache() tstring.TString {
 | 
			
		||||
func (msg *TextMessage) getCache(uiMsg *UIMessage) tstring.TString {
 | 
			
		||||
	if msg.cache == nil {
 | 
			
		||||
		switch msg.MsgType {
 | 
			
		||||
		switch uiMsg.Type {
 | 
			
		||||
		case "m.emote":
 | 
			
		||||
			msg.cache = tstring.NewColorTString(fmt.Sprintf("* %s %s", msg.MsgSender, msg.MsgText), msg.TextColor())
 | 
			
		||||
			msg.cache.Colorize(0, len(msg.MsgSender)+2, msg.SenderColor())
 | 
			
		||||
			msg.cache = tstring.NewColorTString(fmt.Sprintf("* %s %s", uiMsg.SenderName, msg.Text), uiMsg.TextColor())
 | 
			
		||||
			msg.cache.Colorize(0, len(uiMsg.SenderName)+2, uiMsg.SenderColor())
 | 
			
		||||
		default:
 | 
			
		||||
			msg.cache = tstring.NewColorTString(msg.MsgText, msg.TextColor())
 | 
			
		||||
			msg.cache = tstring.NewColorTString(msg.Text, uiMsg.TextColor())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return msg.cache
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *TextMessage) SetIsHighlight(isHighlight bool) {
 | 
			
		||||
	msg.BaseMessage.SetIsHighlight(isHighlight)
 | 
			
		||||
	msg.cache = nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *TextMessage) NotificationContent() string {
 | 
			
		||||
	return msg.MsgText
 | 
			
		||||
	return msg.Text
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *TextMessage) PlainText() string {
 | 
			
		||||
	return msg.MsgText
 | 
			
		||||
	return msg.Text
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *TextMessage) CalculateBuffer(prefs config.UserPreferences, width int) {
 | 
			
		||||
	msg.CalculateReplyBuffer(prefs, width)
 | 
			
		||||
	msg.calculateBufferWithText(prefs, msg.getCache(), width)
 | 
			
		||||
func (msg *TextMessage) String() string {
 | 
			
		||||
	return fmt.Sprintf(`&messages.TextMessage{Text="%s"}`, msg.Text)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *TextMessage) CalculateBuffer(prefs config.UserPreferences, width int, uiMsg *UIMessage) {
 | 
			
		||||
	msg.buffer = calculateBufferWithText(prefs, msg.getCache(uiMsg), width, uiMsg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *TextMessage) Height() int {
 | 
			
		||||
	return len(msg.buffer)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *TextMessage) Draw(screen mauview.Screen) {
 | 
			
		||||
	for y, line := range msg.buffer {
 | 
			
		||||
		line.Draw(screen, 0, y)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (msg *TextMessage) RegisterMatrix(matrix ifc.MatrixContainer) {}
 | 
			
		||||
 
 | 
			
		||||
@@ -236,6 +236,10 @@ func (list *RoomList) AddScrollOffset(offset int) {
 | 
			
		||||
func (list *RoomList) First() (string, *rooms.Room) {
 | 
			
		||||
	list.RLock()
 | 
			
		||||
	defer list.RUnlock()
 | 
			
		||||
	return list.first()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (list *RoomList) first() (string, *rooms.Room) {
 | 
			
		||||
	for _, tag := range list.tags {
 | 
			
		||||
		trl := list.items[tag]
 | 
			
		||||
		if trl.HasVisibleRooms() {
 | 
			
		||||
@@ -248,6 +252,10 @@ func (list *RoomList) First() (string, *rooms.Room) {
 | 
			
		||||
func (list *RoomList) Last() (string, *rooms.Room) {
 | 
			
		||||
	list.RLock()
 | 
			
		||||
	defer list.RUnlock()
 | 
			
		||||
	return list.last()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (list *RoomList) last() (string, *rooms.Room) {
 | 
			
		||||
	for tagIndex := len(list.tags) - 1; tagIndex >= 0; tagIndex-- {
 | 
			
		||||
		tag := list.tags[tagIndex]
 | 
			
		||||
		trl := list.items[tag]
 | 
			
		||||
@@ -273,7 +281,7 @@ func (list *RoomList) Previous() (string, *rooms.Room) {
 | 
			
		||||
	if len(list.items) == 0 {
 | 
			
		||||
		return "", nil
 | 
			
		||||
	} else if list.selected == nil {
 | 
			
		||||
		return list.First()
 | 
			
		||||
		return list.first()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	trl := list.items[list.selectedTag]
 | 
			
		||||
@@ -295,11 +303,11 @@ func (list *RoomList) Previous() (string, *rooms.Room) {
 | 
			
		||||
				return prevTag, prevTRL.LastVisible()
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return list.Last()
 | 
			
		||||
		return list.last()
 | 
			
		||||
	} else if index >= 0 {
 | 
			
		||||
		return list.selectedTag, trl.Visible()[index+1].Room
 | 
			
		||||
	}
 | 
			
		||||
	return list.First()
 | 
			
		||||
	return list.first()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (list *RoomList) Next() (string, *rooms.Room) {
 | 
			
		||||
@@ -308,7 +316,7 @@ func (list *RoomList) Next() (string, *rooms.Room) {
 | 
			
		||||
	if len(list.items) == 0 {
 | 
			
		||||
		return "", nil
 | 
			
		||||
	} else if list.selected == nil {
 | 
			
		||||
		return list.First()
 | 
			
		||||
		return list.first()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	trl := list.items[list.selectedTag]
 | 
			
		||||
@@ -330,11 +338,11 @@ func (list *RoomList) Next() (string, *rooms.Room) {
 | 
			
		||||
				return nextTag, nextTRL.FirstVisible()
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return list.First()
 | 
			
		||||
		return list.first()
 | 
			
		||||
	} else if index > 0 {
 | 
			
		||||
		return list.selectedTag, trl.Visible()[index-1].Room
 | 
			
		||||
	}
 | 
			
		||||
	return list.Last()
 | 
			
		||||
	return list.last()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NextWithActivity Returns next room with activity.
 | 
			
		||||
 
 | 
			
		||||
@@ -57,6 +57,8 @@ type RoomView struct {
 | 
			
		||||
	ulBorderScreen *mauview.ProxyScreen
 | 
			
		||||
	ulScreen       *mauview.ProxyScreen
 | 
			
		||||
 | 
			
		||||
	userListLoaded bool
 | 
			
		||||
 | 
			
		||||
	prevScreen mauview.Screen
 | 
			
		||||
 | 
			
		||||
	parent *MainView
 | 
			
		||||
@@ -99,7 +101,6 @@ func NewRoomView(parent *MainView, room *rooms.Room) *RoomView {
 | 
			
		||||
		SetTabCompleteFunc(view.InputTabComplete)
 | 
			
		||||
 | 
			
		||||
	view.topic.
 | 
			
		||||
		SetText(strings.Replace(room.GetTopic(), "\n", " ", -1)).
 | 
			
		||||
		SetTextColor(tcell.ColorWhite).
 | 
			
		||||
		SetBackgroundColor(tcell.ColorDarkGreen)
 | 
			
		||||
 | 
			
		||||
@@ -385,11 +386,11 @@ func (view *RoomView) SendMessage(msgtype mautrix.MessageType, text string) {
 | 
			
		||||
		text = emoji.Sprint(text)
 | 
			
		||||
	}
 | 
			
		||||
	evt := view.parent.matrix.PrepareMarkdownMessage(view.Room.ID, msgtype, text)
 | 
			
		||||
	msg := view.ParseEvent(evt)
 | 
			
		||||
	msg := view.parseEvent(evt)
 | 
			
		||||
	view.AddMessage(msg)
 | 
			
		||||
	eventID, err := view.parent.matrix.SendEvent(evt)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		msg.SetState(mautrix.EventStateSendFail)
 | 
			
		||||
		msg.State = mautrix.EventStateSendFail
 | 
			
		||||
		// Show shorter version if available
 | 
			
		||||
		if httpErr, ok := err.(mautrix.HTTPError); ok {
 | 
			
		||||
			err = httpErr
 | 
			
		||||
@@ -401,7 +402,10 @@ func (view *RoomView) SendMessage(msgtype mautrix.MessageType, text string) {
 | 
			
		||||
		view.parent.parent.Render()
 | 
			
		||||
	} else {
 | 
			
		||||
		debug.Print("Event ID received:", eventID)
 | 
			
		||||
		//view.MessageView().UpdateMessageID(msg, eventID)
 | 
			
		||||
		msg.EventID = eventID
 | 
			
		||||
		msg.State = mautrix.EventStateDefault
 | 
			
		||||
		view.MessageView().setMessageID(msg)
 | 
			
		||||
		view.parent.parent.Render()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -413,12 +417,20 @@ func (view *RoomView) MxRoom() *rooms.Room {
 | 
			
		||||
	return view.Room
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (view *RoomView) Update() {
 | 
			
		||||
	view.topic.SetText(strings.Replace(view.Room.GetTopic(), "\n", " ", -1))
 | 
			
		||||
	if !view.userListLoaded {
 | 
			
		||||
		view.UpdateUserList()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (view *RoomView) UpdateUserList() {
 | 
			
		||||
	pls := &mautrix.PowerLevels{}
 | 
			
		||||
	if plEvent := view.Room.GetStateEvent(mautrix.StatePowerLevels, ""); plEvent != nil {
 | 
			
		||||
		pls = plEvent.Content.GetPowerLevels()
 | 
			
		||||
	}
 | 
			
		||||
	view.userList.Update(view.Room.GetMembers(), pls)
 | 
			
		||||
	view.userListLoaded = true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (view *RoomView) AddServiceMessage(text string) {
 | 
			
		||||
@@ -429,10 +441,18 @@ func (view *RoomView) AddMessage(message ifc.Message) {
 | 
			
		||||
	view.content.AddMessage(message, AppendMessage)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (view *RoomView) ParseEvent(evt *mautrix.Event) ifc.Message {
 | 
			
		||||
func (view *RoomView) parseEvent(evt *mautrix.Event) *messages.UIMessage {
 | 
			
		||||
	return messages.ParseEvent(view.parent.matrix, view.parent, view.Room, evt)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (view *RoomView) ParseEvent(evt *mautrix.Event) ifc.Message {
 | 
			
		||||
	msg := view.parseEvent(evt)
 | 
			
		||||
	if msg == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return msg
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (view *RoomView) GetEvent(eventID string) ifc.Message {
 | 
			
		||||
	message, ok := view.content.messageIDs[eventID]
 | 
			
		||||
	if !ok {
 | 
			
		||||
 
 | 
			
		||||
@@ -74,7 +74,7 @@ func (ui *GomuksUI) NewLoginView() mauview.Component {
 | 
			
		||||
	view.username.SetText(ui.gmx.Config().UserID)
 | 
			
		||||
	view.password.SetMaskCharacter('*')
 | 
			
		||||
 | 
			
		||||
	view.quitButton.SetOnClick(ui.gmx.Stop).SetBackgroundColor(tcell.ColorDarkCyan)
 | 
			
		||||
	view.quitButton.SetOnClick(func() { ui.gmx.Stop(true) }).SetBackgroundColor(tcell.ColorDarkCyan)
 | 
			
		||||
	view.loginButton.SetOnClick(view.Login).SetBackgroundColor(tcell.ColorDarkCyan)
 | 
			
		||||
 | 
			
		||||
	view.SetColumns([]int{1, 10, 1, 9, 1, 9, 1, 10, 1})
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,7 @@ import (
 | 
			
		||||
 | 
			
		||||
	sync "github.com/sasha-s/go-deadlock"
 | 
			
		||||
 | 
			
		||||
	"maunium.net/go/gomuks/ui/messages"
 | 
			
		||||
	"maunium.net/go/mauview"
 | 
			
		||||
	"maunium.net/go/tcell"
 | 
			
		||||
 | 
			
		||||
@@ -256,6 +257,7 @@ func (view *MainView) switchRoom(tag string, room *rooms.Room, lock bool) {
 | 
			
		||||
	if room == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	room.Load()
 | 
			
		||||
 | 
			
		||||
	roomView, ok := view.getRoomView(room.ID, lock)
 | 
			
		||||
	if !ok {
 | 
			
		||||
@@ -263,12 +265,15 @@ func (view *MainView) switchRoom(tag string, room *rooms.Room, lock bool) {
 | 
			
		||||
		debug.Print(tag, room)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	roomView.Update()
 | 
			
		||||
	view.roomView.SetInnerComponent(roomView)
 | 
			
		||||
	view.currentRoom = roomView
 | 
			
		||||
	view.MarkRead(roomView)
 | 
			
		||||
	view.roomList.SetSelected(tag, room)
 | 
			
		||||
	view.parent.Render()
 | 
			
		||||
	if len(roomView.MessageView().messages) == 0 {
 | 
			
		||||
 | 
			
		||||
	if msgView := roomView.MessageView(); len(msgView.messages) < 20 && !msgView.initialHistoryLoaded {
 | 
			
		||||
		msgView.initialHistoryLoaded = true
 | 
			
		||||
		go view.LoadHistory(room.ID)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -278,12 +283,6 @@ func (view *MainView) addRoomPage(room *rooms.Room) *RoomView {
 | 
			
		||||
		roomView := NewRoomView(view, room).
 | 
			
		||||
			SetInputChangedFunc(view.InputChanged)
 | 
			
		||||
		view.rooms[room.ID] = roomView
 | 
			
		||||
		roomView.UpdateUserList()
 | 
			
		||||
 | 
			
		||||
		// TODO make sure this works
 | 
			
		||||
		if len(roomView.MessageView().messages) == 0 {
 | 
			
		||||
			go view.LoadHistory(room.ID)
 | 
			
		||||
		}
 | 
			
		||||
		return roomView
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
@@ -292,7 +291,7 @@ func (view *MainView) addRoomPage(room *rooms.Room) *RoomView {
 | 
			
		||||
func (view *MainView) GetRoom(roomID string) ifc.RoomView {
 | 
			
		||||
	room, ok := view.getRoomView(roomID, true)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return view.addRoom(view.matrix.GetRoom(roomID))
 | 
			
		||||
		return view.addRoom(view.matrix.GetOrCreateRoom(roomID))
 | 
			
		||||
	}
 | 
			
		||||
	return room
 | 
			
		||||
}
 | 
			
		||||
@@ -348,11 +347,11 @@ func (view *MainView) addRoom(room *rooms.Room) *RoomView {
 | 
			
		||||
	return roomView
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (view *MainView) SetRooms(rooms map[string]*rooms.Room) {
 | 
			
		||||
func (view *MainView) SetRooms(rooms *rooms.RoomCache) {
 | 
			
		||||
	view.roomList.Clear()
 | 
			
		||||
	view.roomsLock.Lock()
 | 
			
		||||
	view.rooms = make(map[string]*RoomView)
 | 
			
		||||
	for _, room := range rooms {
 | 
			
		||||
	for _, room := range rooms.Map {
 | 
			
		||||
		if room.HasLeft {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
@@ -390,7 +389,8 @@ func sendNotification(room *rooms.Room, sender, text string, critical, sound boo
 | 
			
		||||
 | 
			
		||||
func (view *MainView) NotifyMessage(room *rooms.Room, message ifc.Message, should pushrules.PushActionArrayShould) {
 | 
			
		||||
	view.roomList.Bump(room)
 | 
			
		||||
	if message.SenderID() == view.config.UserID {
 | 
			
		||||
	uiMsg, ok := message.(*messages.UIMessage)
 | 
			
		||||
	if ok && uiMsg.SenderID == view.config.UserID {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	// Whether or not the room where the message came is the currently shown room.
 | 
			
		||||
@@ -420,16 +420,6 @@ func (view *MainView) NotifyMessage(room *rooms.Room, message ifc.Message, shoul
 | 
			
		||||
	message.SetIsHighlight(should.Highlight)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (view *MainView) InitialSyncDone() {
 | 
			
		||||
	view.roomList.Clear()
 | 
			
		||||
	view.roomsLock.RLock()
 | 
			
		||||
	for _, room := range view.rooms {
 | 
			
		||||
		view.roomList.Add(room.Room)
 | 
			
		||||
		room.UpdateUserList()
 | 
			
		||||
	}
 | 
			
		||||
	view.roomsLock.RUnlock()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (view *MainView) LoadHistory(roomID string) {
 | 
			
		||||
	defer debug.Recover()
 | 
			
		||||
	roomView, ok := view.getRoomView(roomID, true)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user