Use already parsed events for replies if possible

This commit is contained in:
Tulir Asokan 2019-04-10 21:06:19 +03:00
parent 9132e2b750
commit db0e24ccc2
18 changed files with 245 additions and 23 deletions

View File

@ -61,6 +61,7 @@ type RoomView interface {
UpdateUserList() UpdateUserList()
ParseEvent(evt *mautrix.Event) Message ParseEvent(evt *mautrix.Event) Message
GetEvent(eventID string) Message
AddMessage(message Message) AddMessage(message Message)
AddServiceMessage(message string) AddServiceMessage(message string)
} }

View File

@ -257,7 +257,7 @@ func (view *MessageView) handleMessageClick(message messages.UIMessage) bool {
case *messages.ImageMessage: case *messages.ImageMessage:
open.Open(message.Path()) open.Open(message.Path())
case messages.UIMessage: case messages.UIMessage:
debug.Print("Message clicked:", message.NotificationContent()) debug.Print("Message clicked:", message)
} }
return false return false
} }

View File

@ -44,7 +44,6 @@ type BaseMessage struct {
MsgSource json.RawMessage MsgSource json.RawMessage
ReplyTo UIMessage ReplyTo UIMessage
buffer []tstring.TString buffer []tstring.TString
plainBuffer []tstring.TString
} }
func newBaseMessage(event *mautrix.Event, displayname string) BaseMessage { func newBaseMessage(event *mautrix.Event, displayname string) BaseMessage {
@ -259,6 +258,12 @@ func (msg *BaseMessage) Draw(screen mauview.Screen) {
} }
} }
func (msg *BaseMessage) clone() BaseMessage {
clone := *msg
clone.buffer = nil
return clone
}
func (msg *BaseMessage) CalculateReplyBuffer(preferences config.UserPreferences, width int) { func (msg *BaseMessage) CalculateReplyBuffer(preferences config.UserPreferences, width int) {
if msg.ReplyTo == nil { if msg.ReplyTo == nil {
return return

View File

@ -55,6 +55,14 @@ func NewDateChangeMessage(text string) UIMessage {
} }
} }
func (msg *ExpandedTextMessage) Clone() UIMessage {
return &ExpandedTextMessage{
BaseMessage: msg.BaseMessage.clone(),
MsgText: msg.MsgText.Clone(),
}
}
func (msg *ExpandedTextMessage) GenerateText() tstring.TString { func (msg *ExpandedTextMessage) GenerateText() tstring.TString {
return msg.MsgText return msg.MsgText
} }

View File

@ -18,6 +18,7 @@ package html
import ( import (
"fmt" "fmt"
"strings"
"maunium.net/go/mauview" "maunium.net/go/mauview"
) )
@ -37,6 +38,10 @@ func NewBlockquoteEntity(children []Entity) *BlockquoteEntity {
}} }}
} }
func (be *BlockquoteEntity) Clone() Entity {
return &BlockquoteEntity{BaseEntity: be.BaseEntity.Clone().(*BaseEntity)}
}
func (be *BlockquoteEntity) Draw(screen mauview.Screen) { func (be *BlockquoteEntity) Draw(screen mauview.Screen) {
be.BaseEntity.Draw(screen) be.BaseEntity.Draw(screen)
for y := 0; y < be.height; y++ { for y := 0; y < be.height; y++ {
@ -44,6 +49,33 @@ func (be *BlockquoteEntity) Draw(screen mauview.Screen) {
} }
} }
func (be *BlockquoteEntity) PlainText() string {
if len(be.Children) == 0 {
return ""
}
var buf strings.Builder
newlined := false
for i, child := range be.Children {
if i != 0 && child.IsBlock() && !newlined {
buf.WriteRune('\n')
}
newlined = false
for i, row := range strings.Split(child.PlainText(), "\n") {
if i != 0 {
buf.WriteRune('\n')
}
buf.WriteRune('>')
buf.WriteRune(' ')
buf.WriteString(row)
}
if child.IsBlock() {
buf.WriteRune('\n')
newlined = true
}
}
return strings.TrimSpace(buf.String())
}
func (be *BlockquoteEntity) String() string { func (be *BlockquoteEntity) String() string {
return fmt.Sprintf("&html.BlockquoteEntity{%s},\n", be.BaseEntity) return fmt.Sprintf("&html.BlockquoteEntity{%s},\n", be.BaseEntity)
} }

View File

@ -26,3 +26,15 @@ func NewBreakEntity() *BreakEntity {
Block: true, Block: true,
}} }}
} }
func (be *BreakEntity) Clone() Entity {
return NewBreakEntity()
}
func (be *BreakEntity) PlainText() string {
return "\n"
}
func (be *BreakEntity) String() string {
return "&html.BreakEntity{},\n"
}

View File

@ -37,6 +37,13 @@ func NewCodeBlockEntity(children []Entity, background tcell.Style) *CodeBlockEnt
} }
} }
func (ce *CodeBlockEntity) Clone() Entity {
return &CodeBlockEntity{
BaseEntity: ce.BaseEntity.Clone().(*BaseEntity),
Background: ce.Background,
}
}
func (ce *CodeBlockEntity) Draw(screen mauview.Screen) { func (ce *CodeBlockEntity) Draw(screen mauview.Screen) {
screen.Fill(' ', ce.Background) screen.Fill(' ', ce.Background)
ce.BaseEntity.Draw(screen) ce.BaseEntity.Draw(screen)

View File

@ -165,17 +165,20 @@ func (he *BaseEntity) PlainText() string {
buf.WriteString(he.Text) buf.WriteString(he.Text)
newlined := false newlined := false
for _, child := range he.Children { for _, child := range he.Children {
if child.IsBlock() && !newlined { text := child.PlainText()
if !strings.HasPrefix(text, "\n") && child.IsBlock() && !newlined {
buf.WriteRune('\n') buf.WriteRune('\n')
} }
newlined = false newlined = false
buf.WriteString(child.PlainText()) buf.WriteString(text)
if child.IsBlock() { if child.IsBlock() {
buf.WriteRune('\n') if !strings.HasSuffix(text, "\n") {
buf.WriteRune('\n')
}
newlined = true newlined = true
} }
} }
return buf.String() return strings.TrimSpace(buf.String())
} }
// Draw draws this entity onto the given mauview Screen. // Draw draws this entity onto the given mauview Screen.

View File

@ -0,0 +1,56 @@
// 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 html
import (
"strings"
"maunium.net/go/mauview"
)
type HorizontalLineEntity struct {
*BaseEntity
}
const HorizontalLineChar = '━'
func NewHorizontalLineEntity() *HorizontalLineEntity {
return &HorizontalLineEntity{&BaseEntity{
Tag: "hr",
Block: true,
DefaultHeight: 1,
}}
}
func (he *HorizontalLineEntity) Clone() Entity {
return NewHorizontalLineEntity()
}
func (he *HorizontalLineEntity) Draw(screen mauview.Screen) {
width, _ := screen.Size()
for x := 0; x < width; x++ {
screen.SetContent(x, 0, HorizontalLineChar, nil, he.Style)
}
}
func (he *HorizontalLineEntity) PlainText() string {
return strings.Repeat(string(HorizontalLineChar), 5)
}
func (he *HorizontalLineEntity) String() string {
return "&html.HorizontalLineEntity{},\n"
}

View File

@ -56,6 +56,14 @@ func NewListEntity(ordered bool, start int, children []Entity) *ListEntity {
return entity return entity
} }
func (le *ListEntity) Clone() Entity {
return &ListEntity{
BaseEntity: le.BaseEntity.Clone().(*BaseEntity),
Ordered: le.Ordered,
Start: le.Start,
}
}
func (le *ListEntity) Draw(screen mauview.Screen) { func (le *ListEntity) Draw(screen mauview.Screen) {
width, _ := screen.Size() width, _ := screen.Size()
@ -75,6 +83,31 @@ func (le *ListEntity) Draw(screen mauview.Screen) {
} }
} }
func (le *ListEntity) PlainText() string {
if len(le.Children) == 0 {
return ""
}
var buf strings.Builder
for i, child := range le.Children {
indent := strings.Repeat(" ", le.Indent)
if le.Ordered {
number := le.Start + i
_, _ = fmt.Fprintf(&buf, "%d. %s", number, strings.Repeat(" ", le.Indent-2-digits(number)))
} else {
buf.WriteString("● ")
}
for j, row := range strings.Split(child.PlainText(), "\n") {
if j != 0 {
buf.WriteRune('\n')
buf.WriteString(indent)
}
buf.WriteString(row)
}
buf.WriteRune('\n')
}
return strings.TrimSpace(buf.String())
}
func (le *ListEntity) String() string { func (le *ListEntity) String() string {
return fmt.Sprintf("&html.ListEntity{Ordered=%t, Start=%d, Base=%s},\n", le.Ordered, le.Start, le.BaseEntity) return fmt.Sprintf("&html.ListEntity{Ordered=%t, Start=%d, Base=%s},\n", le.Ordered, le.Start, le.BaseEntity)
} }

View File

@ -111,7 +111,7 @@ func (parser *htmlParser) basicFormatToEntity(node *html.Node) Entity {
entity.AdjustStyle(AdjustStyleBold) entity.AdjustStyle(AdjustStyleBold)
case "i", "em": case "i", "em":
entity.AdjustStyle(AdjustStyleItalic) entity.AdjustStyle(AdjustStyleItalic)
case "s", "del": case "s", "del", "strike":
entity.AdjustStyle(AdjustStyleStrikethrough) entity.AdjustStyle(AdjustStyleStrikethrough)
case "u", "ins": case "u", "ins":
entity.AdjustStyle(AdjustStyleUnderline) entity.AdjustStyle(AdjustStyleUnderline)
@ -237,7 +237,7 @@ func (parser *htmlParser) syntaxHighlight(text, language string) Entity {
children := make([]Entity, len(tokens)) children := make([]Entity, len(tokens))
for i, token := range tokens { for i, token := range tokens {
if token.Value == "\n" { if token.Value == "\n" {
children[i] = &BaseEntity{Block: true, Tag: "br"} children[i] = NewBreakEntity()
} else { } else {
children[i] = &BaseEntity{ children[i] = &BaseEntity{
Tag: token.Type.String(), Tag: token.Type.String(),
@ -282,7 +282,7 @@ func (parser *htmlParser) tagNodeToEntity(node *html.Node) Entity {
return parser.headerToEntity(node) return parser.headerToEntity(node)
case "br": case "br":
return NewBreakEntity() return NewBreakEntity()
case "b", "strong", "i", "em", "s", "del", "u", "ins", "font": case "b", "strong", "i", "em", "s", "strike", "del", "u", "ins", "font":
return parser.basicFormatToEntity(node) return parser.basicFormatToEntity(node)
case "a": case "a":
return parser.linkToEntity(node) return parser.linkToEntity(node)
@ -290,6 +290,10 @@ func (parser *htmlParser) tagNodeToEntity(node *html.Node) Entity {
return parser.imageToEntity(node) return parser.imageToEntity(node)
case "pre": case "pre":
return parser.codeblockToEntity(node) return parser.codeblockToEntity(node)
case "hr":
return NewHorizontalLineEntity()
case "mx-reply":
return nil
default: default:
return &BaseEntity{ return &BaseEntity{
Tag: node.Data, Tag: node.Data,

View File

@ -40,6 +40,14 @@ func NewHTMLMessage(event *mautrix.Event, displayname string, root html.Entity)
} }
} }
func (hw *HTMLMessage) Clone() UIMessage {
return &HTMLMessage{
BaseMessage: hw.BaseMessage.clone(),
Root: hw.Root.Clone(),
FocusedBg: hw.FocusedBg,
}
}
func (hw *HTMLMessage) Draw(screen mauview.Screen) { func (hw *HTMLMessage) Draw(screen mauview.Screen) {
screen = hw.DrawReply(screen) screen = hw.DrawReply(screen)
if hw.focused { if hw.focused {

View File

@ -53,6 +53,19 @@ func NewImageMessage(matrix ifc.MatrixContainer, event *mautrix.Event, displayna
} }
} }
func (msg *ImageMessage) Clone() UIMessage {
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,
data: data,
matrix: msg.matrix,
}
}
func (msg *ImageMessage) RegisterMatrix(matrix ifc.MatrixContainer) { func (msg *ImageMessage) RegisterMatrix(matrix ifc.MatrixContainer) {
msg.matrix = matrix msg.matrix = matrix

View File

@ -43,6 +43,8 @@ type UIMessage interface {
Height() int Height() int
PlainText() string PlainText() string
Clone() UIMessage
RealSender() string RealSender() string
RegisterMatrix(matrix ifc.MatrixContainer) RegisterMatrix(matrix ifc.MatrixContainer)
} }

View File

@ -31,7 +31,18 @@ import (
"maunium.net/go/gomuks/ui/widget" "maunium.net/go/gomuks/ui/widget"
) )
func ParseEvent(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.Event) 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 {
return replyToMsg
}
}
}
return nil
}
func ParseEvent(matrix ifc.MatrixContainer, mainView ifc.MainView, room *rooms.Room, evt *mautrix.Event) UIMessage {
msg := directParseEvent(matrix, room, evt) msg := directParseEvent(matrix, room, evt)
if msg == nil { if msg == nil {
return nil return nil
@ -41,10 +52,14 @@ func ParseEvent(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.Event
if len(evt.Content.RelatesTo.InReplyTo.RoomID) > 0 { if len(evt.Content.RelatesTo.InReplyTo.RoomID) > 0 {
replyToRoom = matrix.GetRoom(evt.Content.RelatesTo.InReplyTo.RoomID) replyToRoom = matrix.GetRoom(evt.Content.RelatesTo.InReplyTo.RoomID)
} }
replyToEvt, _ := matrix.GetEvent(replyToRoom, evt.Content.GetReplyTo())
if replyToEvt != nil { if replyToMsg := getCachedEvent(mainView, replyToRoom.ID, evt.Content.GetReplyTo()); replyToMsg != nil {
replyToMsg := directParseEvent(matrix, replyToRoom, replyToEvt) debug.Print("Cloning cached UIMessage", replyToMsg)
if 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) msg.SetReplyTo(replyToMsg)
} else { } else {
// TODO add unrenderable reply header // TODO add unrenderable reply header
@ -68,6 +83,7 @@ func directParseEvent(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix
case mautrix.StateMember: case mautrix.StateMember:
return ParseMembershipEvent(room, evt) return ParseMembershipEvent(room, evt)
} }
return nil return nil
} }

View File

@ -43,15 +43,22 @@ func NewTextMessage(event *mautrix.Event, displayname string, text string) UIMes
func NewServiceMessage(text string) UIMessage { func NewServiceMessage(text string) UIMessage {
return &TextMessage{ return &TextMessage{
BaseMessage: BaseMessage{ BaseMessage: BaseMessage{
MsgSenderID: "*", MsgSenderID: "*",
MsgSender: "*", MsgSender: "*",
MsgTimestamp: time.Now(), MsgTimestamp: time.Now(),
MsgIsService: true, MsgIsService: true,
}, },
MsgText: text, MsgText: text,
} }
} }
func (msg *TextMessage) Clone() UIMessage {
return &TextMessage{
BaseMessage: msg.BaseMessage.clone(),
MsgText: msg.MsgText,
}
}
func (msg *TextMessage) getCache() tstring.TString { func (msg *TextMessage) getCache() tstring.TString {
if msg.cache == nil { if msg.cache == nil {
switch msg.MsgType { switch msg.MsgType {

View File

@ -21,6 +21,7 @@ import (
"unicode" "unicode"
"github.com/mattn/go-runewidth" "github.com/mattn/go-runewidth"
"maunium.net/go/mauview" "maunium.net/go/mauview"
"maunium.net/go/tcell" "maunium.net/go/tcell"
@ -29,11 +30,11 @@ import (
type TString []Cell type TString []Cell
func NewBlankTString() TString { func NewBlankTString() TString {
return make([]Cell, 0) return make(TString, 0)
} }
func NewTString(str string) TString { func NewTString(str string) TString {
newStr := make([]Cell, len(str)) newStr := make(TString, len(str))
for i, char := range str { for i, char := range str {
newStr[i] = NewCell(char) newStr[i] = NewCell(char)
} }
@ -41,7 +42,7 @@ func NewTString(str string) TString {
} }
func NewColorTString(str string, color tcell.Color) TString { func NewColorTString(str string, color tcell.Color) TString {
newStr := make([]Cell, len(str)) newStr := make(TString, len(str))
for i, char := range str { for i, char := range str {
newStr[i] = NewColorCell(char, color) newStr[i] = NewColorCell(char, color)
} }
@ -49,7 +50,7 @@ func NewColorTString(str string, color tcell.Color) TString {
} }
func NewStyleTString(str string, style tcell.Style) TString { func NewStyleTString(str string, style tcell.Style) TString {
newStr := make([]Cell, len(str)) newStr := make(TString, len(str))
for i, char := range str { for i, char := range str {
newStr[i] = NewStyleCell(char, style) newStr[i] = NewStyleCell(char, style)
} }
@ -74,6 +75,12 @@ func Join(strings []TString, separator string) TString {
return out return out
} }
func (str TString) Clone() TString {
newStr := make(TString, len(str))
copy(newStr, str)
return newStr
}
func (str TString) AppendTString(dataList ...TString) TString { func (str TString) AppendTString(dataList ...TString) TString {
newStr := str newStr := str
for _, data := range dataList { for _, data := range dataList {

View File

@ -445,5 +445,13 @@ func (view *RoomView) AddMessage(message ifc.Message) {
} }
func (view *RoomView) ParseEvent(evt *mautrix.Event) ifc.Message { func (view *RoomView) ParseEvent(evt *mautrix.Event) ifc.Message {
return messages.ParseEvent(view.parent.matrix, view.Room, evt) return messages.ParseEvent(view.parent.matrix, view.parent, view.Room, evt)
}
func (view *RoomView) GetEvent(eventID string) ifc.Message {
message, ok := view.content.messageIDs[eventID]
if !ok {
return nil
}
return message
} }