Unknown changes that possibly fix and/or break things

This commit is contained in:
Tulir Asokan 2018-04-13 21:25:45 +03:00
parent 7e5e802e90
commit 69c163cfe9
10 changed files with 437 additions and 364 deletions

View File

@ -119,8 +119,7 @@ type Message interface {
SetType(msgtype string) SetType(msgtype string)
Type() string Type() string
SetText(text string) NotificationContent() string
Text() string
SetState(state MessageState) SetState(state MessageState)
State() MessageState State() MessageState

View File

@ -124,8 +124,8 @@ func (ai *ANSImage) Render() []tstring.TString {
rows := make([]tstring.TString, ai.h/2) rows := make([]tstring.TString, ai.h/2)
for y := 0; y < ai.h; y += ai.maxprocs { for y := 0; y < ai.h; y += ai.maxprocs {
ch := make(chan renderData, ai.maxprocs) ch := make(chan renderData, ai.maxprocs)
for n, r := 0, y+1; (n <= ai.maxprocs) && (2*r+1 < ai.h); n, r = n+1, y+n+1 { for n, row := 0, y; (n <= ai.maxprocs) && (2*row+1 < ai.h); n, row = n+1, y+n {
go func(r, y int) { go func(row, y int) {
str := make(tstring.TString, ai.w) str := make(tstring.TString, ai.w)
for x := 0; x < ai.w; x++ { for x := 0; x < ai.w; x++ {
topPixel := ai.pixmap[y][x] topPixel := ai.pixmap[y][x]
@ -139,10 +139,10 @@ func (ai *ANSImage) Render() []tstring.TString {
Style: tcell.StyleDefault.Background(topColor).Foreground(bottomColor), Style: tcell.StyleDefault.Background(topColor).Foreground(bottomColor),
} }
} }
ch <- renderData{row: r, render: str} ch <- renderData{row: row, render: str}
}(r, 2*r) }(row, 2*row)
} }
for n, r := 0, y+1; (n <= ai.maxprocs) && (2*r+1 < ai.h); n, r = n+1, y+n+1 { for n, row := 0, y; (n <= ai.maxprocs) && (2*row+1 < ai.h); n, row = n+1, y+n {
data := <-ch data := <-ch
rows[data.row] = data.render rows[data.row] = data.render
} }

View File

@ -265,11 +265,11 @@ func (view *MessageView) HandleClick(x, y int, button tcell.ButtonMask) {
} }
message := view.metaBuffer[line] message := view.metaBuffer[line]
imageMessage, ok := message.(*messages.UIImageMessage) imageMessage, ok := message.(*messages.ImageMessage)
if !ok { if !ok {
uiMessage, ok := message.(messages.UIMessage) uiMessage, ok := message.(messages.UIMessage)
if ok { if ok {
debug.Print("Message clicked:", uiMessage.Text()) debug.Print("Message clicked:", uiMessage.NotificationContent())
} }
return return
} }

236
ui/messages/base.go Normal file
View File

@ -0,0 +1,236 @@
// gomuks - A terminal Matrix client written in Go.
// Copyright (C) 2018 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package messages
import (
"encoding/gob"
"time"
"maunium.net/go/gomuks/interface"
"maunium.net/go/gomuks/ui/messages/tstring"
"maunium.net/go/gomuks/ui/widget"
"maunium.net/go/tcell"
)
func init() {
gob.Register(&BaseMessage{})
}
type BaseMessage struct {
MsgID string
MsgType string
MsgSender string
MsgSenderColor tcell.Color
MsgTimestamp time.Time
MsgState ifc.MessageState
MsgIsHighlight bool
MsgIsService bool
buffer []tstring.TString
prevBufferWidth int
}
func newBaseMessage(id, sender, msgtype string, timestamp time.Time) BaseMessage {
return BaseMessage{
MsgSender: sender,
MsgTimestamp: timestamp,
MsgSenderColor: widget.GetHashColor(sender),
MsgType: msgtype,
MsgID: id,
prevBufferWidth: 0,
MsgState: ifc.MessageStateDefault,
MsgIsHighlight: false,
MsgIsService: false,
}
}
func (msg *BaseMessage) RegisterGomuks(gmx ifc.Gomuks) {}
// CopyFrom replaces the content of this message object with the content of the given object.
func (msg *BaseMessage) CopyFrom(from ifc.MessageMeta) {
msg.MsgSender = from.Sender()
msg.MsgSenderColor = from.SenderColor()
fromMsg, ok := from.(UIMessage)
if ok {
msg.MsgSender = fromMsg.RealSender()
msg.MsgID = fromMsg.ID()
msg.MsgType = fromMsg.Type()
msg.MsgTimestamp = fromMsg.Timestamp()
msg.MsgState = fromMsg.State()
msg.MsgIsService = fromMsg.IsService()
msg.MsgIsHighlight = fromMsg.IsHighlight()
msg.buffer = nil
}
}
// 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 {
case ifc.MessageStateSending:
return "Sending..."
case ifc.MessageStateFailed:
return "Error"
}
switch msg.MsgType {
case "m.emote":
// Emotes don't show a separate sender, it's included in the buffer.
return ""
default:
return msg.MsgSender
}
}
func (msg *BaseMessage) RealSender() string {
return msg.MsgSender
}
func (msg *BaseMessage) getStateSpecificColor() tcell.Color {
switch msg.MsgState {
case ifc.MessageStateSending:
return tcell.ColorGray
case ifc.MessageStateFailed:
return tcell.ColorRed
case ifc.MessageStateDefault:
fallthrough
default:
return tcell.ColorDefault
}
}
// SenderColor returns the color the name of the sender should be shown in.
//
// If the message is being sent, the color is gray.
// If sending has failed, the color is red.
//
// In any other case, the color is whatever is specified in the Message struct.
// Usually that means it is the hash-based color of the sender (see ui/widget/color.go)
func (msg *BaseMessage) SenderColor() tcell.Color {
stateColor := msg.getStateSpecificColor()
switch {
case stateColor != tcell.ColorDefault:
return stateColor
case msg.MsgIsService:
return tcell.ColorGray
default:
return msg.MsgSenderColor
}
}
// TextColor returns the color the actual content of the message should be shown in.
func (msg *BaseMessage) TextColor() tcell.Color {
stateColor := msg.getStateSpecificColor()
switch {
case stateColor != tcell.ColorDefault:
return stateColor
case msg.MsgIsService:
fallthrough
case msg.MsgType == "m.notice":
return tcell.ColorGray
case msg.MsgIsHighlight:
return tcell.ColorYellow
case msg.MsgType == "m.room.member":
return tcell.ColorGreen
default:
return tcell.ColorDefault
}
}
// TimestampColor returns the color the timestamp should be shown in.
//
// As with SenderColor(), messages being sent and messages that failed to be sent are
// gray and red respectively.
//
// However, other messages are the default color instead of a color stored in the struct.
func (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)
}
// Timestamp returns the full timestamp when the message was sent.
func (msg *BaseMessage) Timestamp() time.Time {
return msg.MsgTimestamp
}
// FormatTime returns the formatted time when the message was sent.
func (msg *BaseMessage) FormatTime() string {
return msg.MsgTimestamp.Format(TimeFormat)
}
// FormatDate returns the formatted date when the message was sent.
func (msg *BaseMessage) FormatDate() string {
return msg.MsgTimestamp.Format(DateFormat)
}
func (msg *BaseMessage) ID() string {
return msg.MsgID
}
func (msg *BaseMessage) SetID(id string) {
msg.MsgID = id
}
func (msg *BaseMessage) Type() string {
return msg.MsgType
}
func (msg *BaseMessage) SetType(msgtype string) {
msg.MsgType = msgtype
}
func (msg *BaseMessage) State() ifc.MessageState {
return msg.MsgState
}
func (msg *BaseMessage) SetState(state ifc.MessageState) {
msg.MsgState = state
}
func (msg *BaseMessage) IsHighlight() bool {
return msg.MsgIsHighlight
}
func (msg *BaseMessage) SetIsHighlight(isHighlight bool) {
msg.MsgIsHighlight = isHighlight
}
func (msg *BaseMessage) IsService() bool {
return msg.MsgIsService
}
func (msg *BaseMessage) SetIsService(isService bool) {
msg.MsgIsService = isService
}

View File

@ -22,65 +22,50 @@ import (
"maunium.net/go/gomuks/interface" "maunium.net/go/gomuks/interface"
"maunium.net/go/gomuks/ui/messages/tstring" "maunium.net/go/gomuks/ui/messages/tstring"
"maunium.net/go/gomuks/ui/widget"
) )
func init() { func init() {
gob.Register(&UITextMessage{}) gob.Register(&ExpandedTextMessage{})
gob.Register(&UIExpandedTextMessage{})
} }
type UIExpandedTextMessage struct { type ExpandedTextMessage struct {
UITextMessage BaseTextMessage
MsgTStringText tstring.TString MsgText tstring.TString
} }
// NewExpandedTextMessage creates a new UIExpandedTextMessage object with the provided values and the default state. // NewExpandedTextMessage creates a new ExpandedTextMessage object with the provided values and the default state.
func NewExpandedTextMessage(id, sender, msgtype string, text tstring.TString, timestamp time.Time) UIMessage { func NewExpandedTextMessage(id, sender, msgtype string, text tstring.TString, timestamp time.Time) UIMessage {
return &UIExpandedTextMessage{ return &ExpandedTextMessage{
UITextMessage{ BaseTextMessage: newBaseTextMessage(id, sender, msgtype, timestamp),
MsgSender: sender, MsgText: text,
MsgTimestamp: timestamp,
MsgSenderColor: widget.GetHashColor(sender),
MsgType: msgtype,
MsgText: text.String(),
MsgID: id,
prevBufferWidth: 0,
MsgState: ifc.MessageStateDefault,
MsgIsHighlight: false,
MsgIsService: false,
},
text,
} }
} }
func (msg *UIExpandedTextMessage) GetTStringText() tstring.TString { func (msg *ExpandedTextMessage) GenerateText() tstring.TString {
return msg.MsgTStringText return msg.MsgText
} }
// CopyFrom replaces the content of this message object with the content of the given object. // CopyFrom replaces the content of this message object with the content of the given object.
func (msg *UIExpandedTextMessage) CopyFrom(from ifc.MessageMeta) { func (msg *ExpandedTextMessage) CopyFrom(from ifc.MessageMeta) {
msg.MsgSender = from.Sender() msg.BaseTextMessage.CopyFrom(from)
msg.MsgSenderColor = from.SenderColor()
fromMsg, ok := from.(UIMessage) fromExpandedMsg, ok := from.(*ExpandedTextMessage)
if ok { if ok {
msg.MsgSender = fromMsg.RealSender() msg.MsgText = fromExpandedMsg.MsgText
msg.MsgID = fromMsg.ID()
msg.MsgType = fromMsg.Type()
msg.MsgTimestamp = fromMsg.Timestamp()
msg.MsgState = fromMsg.State()
msg.MsgIsService = fromMsg.IsService()
msg.MsgIsHighlight = fromMsg.IsHighlight()
msg.buffer = nil
fromExpandedMsg, ok := from.(*UIExpandedTextMessage)
if ok {
msg.MsgTStringText = fromExpandedMsg.MsgTStringText
} else {
msg.MsgTStringText = tstring.NewColorTString(fromMsg.Text(), from.TextColor())
} }
msg.RecalculateBuffer() msg.RecalculateBuffer()
} }
func (msg *ExpandedTextMessage) NotificationContent() string {
return msg.MsgText.String()
}
func (msg *ExpandedTextMessage) CalculateBuffer(width int) {
msg.BaseTextMessage.calculateBufferWithText(msg.MsgText, width)
}
// RecalculateBuffer calculates the buffer again with the previously provided width.
func (msg *ExpandedTextMessage) RecalculateBuffer() {
msg.CalculateBuffer(msg.prevBufferWidth)
} }

View File

@ -28,16 +28,15 @@ import (
"maunium.net/go/gomuks/interface" "maunium.net/go/gomuks/interface"
"maunium.net/go/gomuks/lib/ansimage" "maunium.net/go/gomuks/lib/ansimage"
"maunium.net/go/gomuks/ui/messages/tstring" "maunium.net/go/gomuks/ui/messages/tstring"
"maunium.net/go/gomuks/ui/widget"
"maunium.net/go/tcell" "maunium.net/go/tcell"
) )
func init() { func init() {
gob.Register(&UIImageMessage{}) gob.Register(&ImageMessage{})
} }
type UIImageMessage struct { type ImageMessage struct {
UITextMessage BaseMessage
Homeserver string Homeserver string
FileID string FileID string
data []byte data []byte
@ -45,20 +44,10 @@ type UIImageMessage struct {
gmx ifc.Gomuks gmx ifc.Gomuks
} }
// NewImageMessage creates a new UIImageMessage object with the provided values and the default state. // NewImageMessage creates a new ImageMessage object with the provided values and the default state.
func NewImageMessage(gmx ifc.Gomuks, id, sender, msgtype, homeserver, fileID string, data []byte, timestamp time.Time) UIMessage { func NewImageMessage(gmx ifc.Gomuks, id, sender, msgtype, homeserver, fileID string, data []byte, timestamp time.Time) UIMessage {
return &UIImageMessage{ return &ImageMessage{
UITextMessage{ newBaseMessage(id, sender, msgtype, timestamp),
MsgSender: sender,
MsgTimestamp: timestamp,
MsgSenderColor: widget.GetHashColor(sender),
MsgType: msgtype,
MsgID: id,
prevBufferWidth: 0,
MsgState: ifc.MessageStateDefault,
MsgIsHighlight: false,
MsgIsService: false,
},
homeserver, homeserver,
fileID, fileID,
data, data,
@ -66,7 +55,7 @@ func NewImageMessage(gmx ifc.Gomuks, id, sender, msgtype, homeserver, fileID str
} }
} }
func (msg *UIImageMessage) RegisterGomuks(gmx ifc.Gomuks) { func (msg *ImageMessage) RegisterGomuks(gmx ifc.Gomuks) {
msg.gmx = gmx msg.gmx = gmx
debug.Print(len(msg.data), msg.data) debug.Print(len(msg.data), msg.data)
@ -78,7 +67,11 @@ func (msg *UIImageMessage) RegisterGomuks(gmx ifc.Gomuks) {
} }
} }
func (msg *UIImageMessage) updateData() { func (msg *ImageMessage) NotificationContent() string {
return "Sent an image"
}
func (msg *ImageMessage) updateData() {
debug.Print("Loading image:", msg.Homeserver, msg.FileID) debug.Print("Loading image:", msg.Homeserver, msg.FileID)
data, _, _, err := msg.gmx.Matrix().Download(fmt.Sprintf("mxc://%s/%s", msg.Homeserver, msg.FileID)) data, _, _, err := msg.gmx.Matrix().Download(fmt.Sprintf("mxc://%s/%s", msg.Homeserver, msg.FileID))
if err != nil { if err != nil {
@ -88,39 +81,26 @@ func (msg *UIImageMessage) updateData() {
msg.data = data msg.data = data
} }
func (msg *UIImageMessage) Path() string { func (msg *ImageMessage) Path() string {
return msg.gmx.Matrix().GetCachePath(msg.Homeserver, msg.FileID) return msg.gmx.Matrix().GetCachePath(msg.Homeserver, msg.FileID)
} }
// CopyFrom replaces the content of this message object with the content of the given object. // CopyFrom replaces the content of this message object with the content of the given object.
func (msg *UIImageMessage) CopyFrom(from ifc.MessageMeta) { func (msg *ImageMessage) CopyFrom(from ifc.MessageMeta) {
msg.MsgSender = from.Sender() msg.BaseMessage.CopyFrom(from)
msg.MsgSenderColor = from.SenderColor()
fromMsg, ok := from.(UIMessage) fromImgMsg, ok := from.(*ImageMessage)
if ok {
msg.MsgSender = fromMsg.RealSender()
msg.MsgID = fromMsg.ID()
msg.MsgType = fromMsg.Type()
msg.MsgTimestamp = fromMsg.Timestamp()
msg.MsgState = fromMsg.State()
msg.MsgIsService = fromMsg.IsService()
msg.MsgIsHighlight = fromMsg.IsHighlight()
msg.buffer = nil
fromImgMsg, ok := from.(*UIImageMessage)
if ok { if ok {
msg.data = fromImgMsg.data msg.data = fromImgMsg.data
} }
msg.RecalculateBuffer() msg.RecalculateBuffer()
}
} }
// CalculateBuffer generates the internal buffer for this message that consists // 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 // of the text of this message split into lines at most as wide as the width
// parameter. // parameter.
func (msg *UIImageMessage) CalculateBuffer(width int) { func (msg *ImageMessage) CalculateBuffer(width int) {
if width < 2 { if width < 2 {
return return
} }
@ -135,3 +115,9 @@ func (msg *UIImageMessage) CalculateBuffer(width int) {
msg.buffer = image.Render() msg.buffer = image.Render()
msg.prevBufferWidth = width msg.prevBufferWidth = width
} }
// RecalculateBuffer calculates the buffer again with the previously provided width.
func (msg *ImageMessage) RecalculateBuffer() {
msg.CalculateBuffer(msg.prevBufferWidth)
}

View File

@ -55,7 +55,7 @@ func ParseMessage(gmx ifc.Gomuks, evt *gomatrix.Event) UIMessage {
msgtype, _ := evt.Content["msgtype"].(string) msgtype, _ := evt.Content["msgtype"].(string)
ts := unixToTime(evt.Timestamp) ts := unixToTime(evt.Timestamp)
switch msgtype { switch msgtype {
case "m.text", "m.notice": case "m.text", "m.notice", "m.emote":
text, _ := evt.Content["body"].(string) text, _ := evt.Content["body"].(string)
return NewTextMessage(evt.ID, evt.Sender, msgtype, text, ts) return NewTextMessage(evt.ID, evt.Sender, msgtype, text, ts)
case "m.image": case "m.image":
@ -86,7 +86,7 @@ func getMembershipEventContent(evt *gomatrix.Event) (sender string, text tstring
switch membership { switch membership {
case "invite": case "invite":
sender = "---" sender = "---"
text = tstring.NewColorTString(fmt.Sprintf("%s invited %s.", evt.Sender, displayname), tcell.ColorYellow) text = tstring.NewColorTString(fmt.Sprintf("%s invited %s.", evt.Sender, displayname), tcell.ColorGreen)
text.Colorize(0, len(evt.Sender), widget.GetHashColor(evt.Sender)) text.Colorize(0, len(evt.Sender), widget.GetHashColor(evt.Sender))
text.Colorize(len(evt.Sender)+len(" invited "), len(displayname), widget.GetHashColor(displayname)) text.Colorize(len(evt.Sender)+len(" invited "), len(displayname), widget.GetHashColor(displayname))
case "join": case "join":

84
ui/messages/textbase.go Normal file
View File

@ -0,0 +1,84 @@
// gomuks - A terminal Matrix client written in Go.
// Copyright (C) 2018 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package messages
import (
"encoding/gob"
"regexp"
"time"
"maunium.net/go/gomuks/ui/messages/tstring"
)
func init() {
gob.Register(BaseTextMessage{})
}
type BaseTextMessage struct {
BaseMessage
}
func newBaseTextMessage(id, sender, msgtype string, timestamp time.Time) BaseTextMessage {
return BaseTextMessage{newBaseMessage(id, sender, msgtype, timestamp)}
}
// Regular expressions used to split lines when calculating the buffer.
//
// From tview/textview.go
var (
boundaryPattern = regexp.MustCompile("([[:punct:]]\\s*|\\s+)")
spacePattern = regexp.MustCompile(`\s+`)
)
// 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 *BaseTextMessage) calculateBufferWithText(text tstring.TString, width int) {
if width < 2 {
return
}
msg.buffer = []tstring.TString{}
forcedLinebreaks := text.Split('\n')
newlines := 0
for _, str := range forcedLinebreaks {
if len(str) == 0 && newlines < 1 {
msg.buffer = append(msg.buffer, tstring.TString{})
newlines++
} else {
newlines = 0
}
// Mostly from tview/textview.go#reindexBuffer()
for len(str) > 0 {
extract := str.Truncate(width)
if len(extract) < len(str) {
if spaces := spacePattern.FindStringIndex(str[len(extract):].String()); spaces != nil && spaces[0] == 0 {
extract = str[:len(extract)+spaces[1]]
}
matches := boundaryPattern.FindAllStringIndex(extract.String(), -1)
if len(matches) > 0 {
extract = extract[:matches[len(matches)-1][1]]
}
}
msg.buffer = append(msg.buffer, extract)
str = str[len(extract):]
}
}
msg.prevBufferWidth = width
}

View File

@ -19,302 +19,84 @@ package messages
import ( import (
"encoding/gob" "encoding/gob"
"fmt" "fmt"
"regexp"
"time" "time"
"maunium.net/go/gomuks/interface" "maunium.net/go/gomuks/interface"
"maunium.net/go/gomuks/ui/messages/tstring" "maunium.net/go/gomuks/ui/messages/tstring"
"maunium.net/go/gomuks/ui/widget"
"maunium.net/go/tcell"
) )
func init() { func init() {
gob.Register(&UITextMessage{}) gob.Register(&TextMessage{})
} }
type UITextMessage struct { type TextMessage struct {
MsgID string BaseTextMessage
MsgType string cache tstring.TString
MsgSender string
MsgSenderColor tcell.Color
MsgTimestamp time.Time
MsgText string MsgText string
MsgState ifc.MessageState
MsgIsHighlight bool
MsgIsService bool
text tstring.TString
buffer []tstring.TString
prevBufferWidth int
} }
// NewTextMessage creates a new UITextMessage object with the provided values and the default state. // NewTextMessage creates a new UITextMessage object with the provided values and the default state.
func NewTextMessage(id, sender, msgtype, text string, timestamp time.Time) UIMessage { func NewTextMessage(id, sender, msgtype, text string, timestamp time.Time) UIMessage {
return &UITextMessage{ return &TextMessage{
MsgSender: sender, BaseTextMessage: newBaseTextMessage(id, sender, msgtype, timestamp),
MsgTimestamp: timestamp,
MsgSenderColor: widget.GetHashColor(sender),
MsgType: msgtype,
MsgText: text, MsgText: text,
MsgID: id,
prevBufferWidth: 0,
MsgState: ifc.MessageStateDefault,
MsgIsHighlight: false,
MsgIsService: false,
} }
} }
func (msg *UITextMessage) RegisterGomuks(gmx ifc.Gomuks) {} func (msg *TextMessage) getCache() tstring.TString {
if msg.cache == nil {
// CopyFrom replaces the content of this message object with the content of the given object.
func (msg *UITextMessage) CopyFrom(from ifc.MessageMeta) {
msg.MsgSender = from.Sender()
msg.MsgSenderColor = from.SenderColor()
fromMsg, ok := from.(UIMessage)
if ok {
msg.MsgSender = fromMsg.RealSender()
msg.MsgID = fromMsg.ID()
msg.MsgType = fromMsg.Type()
msg.MsgTimestamp = fromMsg.Timestamp()
msg.MsgText = fromMsg.Text()
msg.MsgState = fromMsg.State()
msg.MsgIsService = fromMsg.IsService()
msg.MsgIsHighlight = fromMsg.IsHighlight()
msg.buffer = nil
msg.RecalculateBuffer()
}
}
// 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 *UITextMessage) Sender() string {
switch msg.MsgState {
case ifc.MessageStateSending:
return "Sending..."
case ifc.MessageStateFailed:
return "Error"
}
switch msg.MsgType { switch msg.MsgType {
case "m.emote": case "m.emote":
// Emotes don't show a separate sender, it's included in the buffer. msg.cache = tstring.NewColorTString(fmt.Sprintf("* %s %s", msg.MsgSender, msg.MsgText), msg.TextColor())
return "" msg.cache.Colorize(0, len(msg.MsgSender)+2, msg.SenderColor())
default: default:
return msg.MsgSender msg.cache = tstring.NewColorTString(msg.MsgText, msg.TextColor())
} }
}
func (msg *UITextMessage) RealSender() string {
return msg.MsgSender
}
func (msg *UITextMessage) getStateSpecificColor() tcell.Color {
switch msg.MsgState {
case ifc.MessageStateSending:
return tcell.ColorGray
case ifc.MessageStateFailed:
return tcell.ColorRed
case ifc.MessageStateDefault:
fallthrough
default:
return tcell.ColorDefault
} }
return msg.cache
} }
// SenderColor returns the color the name of the sender should be shown in. // CopyFrom replaces the content of this message object with the content of the given object.
// func (msg *TextMessage) CopyFrom(from ifc.MessageMeta) {
// If the message is being sent, the color is gray. msg.BaseTextMessage.CopyFrom(from)
// If sending has failed, the color is red.
// fromTextMsg, ok := from.(*TextMessage)
// In any other case, the color is whatever is specified in the Message struct. if ok {
// Usually that means it is the hash-based color of the sender (see ui/widget/color.go) msg.MsgText = fromTextMsg.MsgText
func (msg *UITextMessage) SenderColor() tcell.Color {
stateColor := msg.getStateSpecificColor()
switch {
case stateColor != tcell.ColorDefault:
return stateColor
case msg.MsgIsService:
return tcell.ColorGray
default:
return msg.MsgSenderColor
} }
msg.cache = nil
msg.RecalculateBuffer()
}
func (msg *TextMessage) SetType(msgtype string) {
msg.BaseTextMessage.SetType(msgtype)
msg.cache = nil
} }
// TextColor returns the color the actual content of the message should be shown in. func (msg *TextMessage) SetState(state ifc.MessageState) {
func (msg *UITextMessage) TextColor() tcell.Color { msg.BaseTextMessage.SetState(state)
stateColor := msg.getStateSpecificColor() msg.cache = nil
switch {
case stateColor != tcell.ColorDefault:
return stateColor
case msg.MsgIsService:
return tcell.ColorGray
case msg.MsgIsHighlight:
return tcell.ColorYellow
case msg.MsgType == "m.room.member":
return tcell.ColorGreen
default:
return tcell.ColorDefault
}
} }
// TimestampColor returns the color the timestamp should be shown in. func (msg *TextMessage) SetIsHighlight(isHighlight bool) {
// msg.BaseTextMessage.SetIsHighlight(isHighlight)
// As with SenderColor(), messages being sent and messages that failed to be sent are msg.cache = nil
// gray and red respectively.
//
// However, other messages are the default color instead of a color stored in the struct.
func (msg *UITextMessage) TimestampColor() tcell.Color {
return msg.getStateSpecificColor()
} }
// RecalculateBuffer calculates the buffer again with the previously provided width. func (msg *TextMessage) SetIsService(isService bool) {
func (msg *UITextMessage) RecalculateBuffer() { msg.BaseTextMessage.SetIsService(isService)
msg.CalculateBuffer(msg.prevBufferWidth) msg.cache = nil
} }
// Buffer returns the computed text buffer. func (msg *TextMessage) NotificationContent() string {
//
// 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 *UITextMessage) Buffer() []tstring.TString {
return msg.buffer
}
// Height returns the number of rows in the computed buffer (see Buffer()).
func (msg *UITextMessage) Height() int {
return len(msg.buffer)
}
// Timestamp returns the full timestamp when the message was sent.
func (msg *UITextMessage) Timestamp() time.Time {
return msg.MsgTimestamp
}
// FormatTime returns the formatted time when the message was sent.
func (msg *UITextMessage) FormatTime() string {
return msg.MsgTimestamp.Format(TimeFormat)
}
// FormatDate returns the formatted date when the message was sent.
func (msg *UITextMessage) FormatDate() string {
return msg.MsgTimestamp.Format(DateFormat)
}
func (msg *UITextMessage) ID() string {
return msg.MsgID
}
func (msg *UITextMessage) SetID(id string) {
msg.MsgID = id
}
func (msg *UITextMessage) Type() string {
return msg.MsgType
}
func (msg *UITextMessage) SetType(msgtype string) {
msg.MsgType = msgtype
msg.text = nil
}
func (msg *UITextMessage) Text() string {
return msg.MsgText return msg.MsgText
} }
func (msg *UITextMessage) SetText(text string) { func (msg *TextMessage) CalculateBuffer(width int) {
msg.MsgText = text msg.BaseTextMessage.calculateBufferWithText(msg.getCache(), width)
msg.text = nil
} }
func (msg *UITextMessage) State() ifc.MessageState { // RecalculateBuffer calculates the buffer again with the previously provided width.
return msg.MsgState func (msg *TextMessage) RecalculateBuffer() {
} msg.CalculateBuffer(msg.prevBufferWidth)
func (msg *UITextMessage) SetState(state ifc.MessageState) {
msg.MsgState = state
msg.text = nil
}
func (msg *UITextMessage) IsHighlight() bool {
return msg.MsgIsHighlight
}
func (msg *UITextMessage) SetIsHighlight(isHighlight bool) {
msg.MsgIsHighlight = isHighlight
msg.text = nil
}
func (msg *UITextMessage) IsService() bool {
return msg.MsgIsService
}
func (msg *UITextMessage) SetIsService(isService bool) {
msg.MsgIsService = isService
msg.text = nil
}
func (msg *UITextMessage) GetTStringText() tstring.TString {
if msg.text == nil || len(msg.text) == 0 {
msg.text = tstring.NewColorTString(msg.Text(), msg.TextColor())
}
return msg.text
}
// Regular expressions used to split lines when calculating the buffer.
//
// From tview/textview.go
var (
boundaryPattern = regexp.MustCompile("([[:punct:]]\\s*|\\s+)")
spacePattern = regexp.MustCompile(`\s+`)
)
// 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 *UITextMessage) CalculateBuffer(width int) {
if width < 2 {
return
}
msg.buffer = []tstring.TString{}
text := msg.GetTStringText()
if msg.MsgType == "m.emote" {
text = tstring.NewColorTString(fmt.Sprintf("* %s %s", msg.MsgSender, text.String()), msg.TextColor())
text.Colorize(0, len(msg.MsgSender), msg.SenderColor())
}
forcedLinebreaks := text.Split('\n')
newlines := 0
for _, str := range forcedLinebreaks {
if len(str) == 0 && newlines < 1 {
msg.buffer = append(msg.buffer, tstring.TString{})
newlines++
} else {
newlines = 0
}
// Mostly from tview/textview.go#reindexBuffer()
for len(str) > 0 {
extract := str.Truncate(width)
if len(extract) < len(str) {
if spaces := spacePattern.FindStringIndex(str[len(extract):].String()); spaces != nil && spaces[0] == 0 {
extract = str[:len(extract)+spaces[1]]
}
matches := boundaryPattern.FindAllStringIndex(extract.String(), -1)
if len(matches) > 0 {
extract = extract[:matches[len(matches)-1][1]]
}
}
msg.buffer = append(msg.buffer, extract)
str = str[len(extract):]
}
}
msg.prevBufferWidth = width
} }

View File

@ -134,12 +134,12 @@ func (view *MainView) InputSubmit(roomView *RoomView, text string) {
func (view *MainView) SendMessage(roomView *RoomView, text string) { func (view *MainView) SendMessage(roomView *RoomView, text string) {
tempMessage := roomView.NewTempMessage("m.text", text) tempMessage := roomView.NewTempMessage("m.text", text)
go view.sendTempMessage(roomView, tempMessage) go view.sendTempMessage(roomView, tempMessage, text)
} }
func (view *MainView) sendTempMessage(roomView *RoomView, tempMessage ifc.Message) { func (view *MainView) sendTempMessage(roomView *RoomView, tempMessage ifc.Message, text string) {
defer view.gmx.Recover() defer view.gmx.Recover()
eventID, err := view.matrix.SendMessage(roomView.Room.ID, tempMessage.Type(), tempMessage.Text()) eventID, err := view.matrix.SendMessage(roomView.Room.ID, tempMessage.Type(), text)
if err != nil { if err != nil {
tempMessage.SetState(ifc.MessageStateFailed) tempMessage.SetState(ifc.MessageStateFailed)
roomView.SetStatus(fmt.Sprintf("Failed to send message: %s", err)) roomView.SetStatus(fmt.Sprintf("Failed to send message: %s", err))
@ -153,8 +153,9 @@ func (view *MainView) HandleCommand(roomView *RoomView, command string, args []s
debug.Print("Handling command", command, args) debug.Print("Handling command", command, args)
switch command { switch command {
case "/me": case "/me":
tempMessage := roomView.NewTempMessage("m.emote", strings.Join(args, " ")) text := strings.Join(args, " ")
go view.sendTempMessage(roomView, tempMessage) tempMessage := roomView.NewTempMessage("m.emote", text)
go view.sendTempMessage(roomView, tempMessage, text)
view.parent.Render() view.parent.Render()
case "/quit": case "/quit":
view.gmx.Stop() view.gmx.Stop()
@ -411,7 +412,7 @@ func (view *MainView) NotifyMessage(room *rooms.Room, message ifc.Message, shoul
if shouldNotify && !isFocused { if shouldNotify && !isFocused {
// Push rules say notify and the terminal is not focused, send desktop notification. // Push rules say notify and the terminal is not focused, send desktop notification.
shouldPlaySound := should.PlaySound && should.SoundName == "default" shouldPlaySound := should.PlaySound && should.SoundName == "default"
sendNotification(room, message.Sender(), message.Text(), should.Highlight, shouldPlaySound) sendNotification(room, message.Sender(), message.NotificationContent(), should.Highlight, shouldPlaySound)
} }
message.SetIsHighlight(should.Highlight) message.SetIsHighlight(should.Highlight)