aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTulir Asokan <tulir@maunium.net>2018-04-14 00:34:25 +0300
committerTulir Asokan <tulir@maunium.net>2018-04-14 00:34:25 +0300
commite7bf5bd59fc0a43172b6ab5b338e1d60bd4b3bbb (patch)
tree67aacbe31ba2d95fd7a648254f5173d7f46b4dc1
parent69c163cfe9d353060896403a3e844848d4fd54eb (diff)
Add basic HTML rendering (ref #16)
-rw-r--r--matrix/rooms/room.go6
-rw-r--r--ui/messages/base.go4
-rw-r--r--ui/messages/htmlparser.go136
-rw-r--r--ui/messages/parser.go10
-rw-r--r--ui/messages/tstring/string.go33
-rw-r--r--ui/view-main.go4
6 files changed, 179 insertions, 14 deletions
diff --git a/matrix/rooms/room.go b/matrix/rooms/room.go
index 7dd2af4..2e12a28 100644
--- a/matrix/rooms/room.go
+++ b/matrix/rooms/room.go
@@ -94,11 +94,7 @@ func (room *Room) UpdateState(event *gomatrix.Event) {
room.memberCache = nil
room.firstMemberCache = ""
fallthrough
- case "m.room.name":
- fallthrough
- case "m.room.canonical_alias":
- fallthrough
- case "m.room.alias":
+ case "m.room.name", "m.room.canonical_alias", "m.room.alias":
room.nameCache = ""
case "m.room.topic":
room.topicCache = ""
diff --git a/ui/messages/base.go b/ui/messages/base.go
index deb153f..aed7903 100644
--- a/ui/messages/base.go
+++ b/ui/messages/base.go
@@ -141,9 +141,7 @@ func (msg *BaseMessage) TextColor() tcell.Color {
switch {
case stateColor != tcell.ColorDefault:
return stateColor
- case msg.MsgIsService:
- fallthrough
- case msg.MsgType == "m.notice":
+ case msg.MsgIsService, msg.MsgType == "m.notice":
return tcell.ColorGray
case msg.MsgIsHighlight:
return tcell.ColorYellow
diff --git a/ui/messages/htmlparser.go b/ui/messages/htmlparser.go
new file mode 100644
index 0000000..0475e7a
--- /dev/null
+++ b/ui/messages/htmlparser.go
@@ -0,0 +1,136 @@
+// 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 (
+ "strings"
+
+ "golang.org/x/net/html"
+ "maunium.net/go/gomatrix"
+ "maunium.net/go/gomuks/debug"
+ "maunium.net/go/gomuks/ui/messages/tstring"
+ "maunium.net/go/tcell"
+)
+
+// TagArray is a reversed queue for remembering what HTML tags are open.
+type TagArray []string
+
+// Pushb converts the given byte array into a string and calls Push().
+func (ta *TagArray) Pushb(tag []byte) {
+ ta.Push(string(tag))
+}
+
+// Popb converts the given byte array into a string and calls Pop().
+func (ta *TagArray) Popb(tag []byte) {
+ ta.Pop(string(tag))
+}
+
+// Hasb converts the given byte array into a string and calls Has().
+func (ta *TagArray) Hasb(tag []byte) {
+ ta.Has(string(tag))
+}
+
+// HasAfterb converts the given byte array into a string and calls HasAfter().
+func (ta *TagArray) HasAfterb(tag []byte, after int) {
+ ta.HasAfter(string(tag), after)
+}
+
+// Push adds the given tag to the array.
+func (ta *TagArray) Push(tag string) {
+ *ta = append(*ta, "")
+ copy((*ta)[1:], *ta)
+ (*ta)[0] = tag
+}
+
+// Pop removes the given tag from the array.
+func (ta *TagArray) Pop(tag string) {
+ if (*ta)[0] == tag {
+ // This is the default case and is lighter than append(), so we handle it separately.
+ *ta = (*ta)[1:]
+ } else if index := ta.Has(tag); index != -1 {
+ *ta = append((*ta)[:index], (*ta)[index+1:]...)
+ }
+}
+
+// Has returns the first index where the given tag is, or -1 if it's not in the list.
+func (ta *TagArray) Has(tag string) int {
+ return ta.HasAfter(tag, -1)
+}
+
+// HasAfter returns the first index after the given index where the given tag is,
+// or -1 if the given tag is not on the list after the given index.
+func (ta *TagArray) HasAfter(tag string, after int) int {
+ for i := after + 1; i < len(*ta); i++ {
+ if (*ta)[i] == tag {
+ return i
+ }
+ }
+ return -1
+}
+
+// ParseHTMLMessage parses a HTML-formatted Matrix event into a UIMessage.
+func ParseHTMLMessage(evt *gomatrix.Event) tstring.TString {
+ //textData, _ := evt.Content["body"].(string)
+ htmlData, _ := evt.Content["formatted_body"].(string)
+
+ z := html.NewTokenizer(strings.NewReader(htmlData))
+ text := tstring.NewTString("")
+
+ openTags := &TagArray{}
+
+Loop:
+ for {
+ tt := z.Next()
+ switch tt {
+ case html.ErrorToken:
+ break Loop
+ case html.TextToken:
+ style := tcell.StyleDefault
+ for _, tag := range *openTags {
+ switch tag {
+ case "b", "strong":
+ style = style.Bold(true)
+ case "i", "em":
+ style = style.Italic(true)
+ case "s", "del":
+ style = style.Strikethrough(true)
+ case "u", "ins":
+ style = style.Underline(true)
+ }
+ }
+ text = text.AppendStyle(string(z.Text()), style)
+ case html.SelfClosingTagToken, html.StartTagToken:
+ tagb, _ := z.TagName()
+ tag := string(tagb)
+ switch tag {
+ case "br":
+ debug.Print("BR found")
+ debug.Print(text.String())
+ text = text.Append("\n")
+ default:
+ if tt == html.StartTagToken {
+ openTags.Push(tag)
+ }
+ }
+ case html.EndTagToken:
+ tagb, _ := z.TagName()
+ openTags.Popb(tagb)
+ }
+ }
+
+ return text
+}
diff --git a/ui/messages/parser.go b/ui/messages/parser.go
index 263bced..d8069c6 100644
--- a/ui/messages/parser.go
+++ b/ui/messages/parser.go
@@ -56,8 +56,14 @@ func ParseMessage(gmx ifc.Gomuks, evt *gomatrix.Event) UIMessage {
ts := unixToTime(evt.Timestamp)
switch msgtype {
case "m.text", "m.notice", "m.emote":
- text, _ := evt.Content["body"].(string)
- return NewTextMessage(evt.ID, evt.Sender, msgtype, text, ts)
+ format, hasFormat := evt.Content["format"].(string)
+ if hasFormat && format == "org.matrix.custom.html" {
+ text := ParseHTMLMessage(evt)
+ return NewExpandedTextMessage(evt.ID, evt.Sender, msgtype, text, ts)
+ } else {
+ text, _ := evt.Content["body"].(string)
+ return NewTextMessage(evt.ID, evt.Sender, msgtype, text, ts)
+ }
case "m.image":
url, _ := evt.Content["url"].(string)
data, hs, id, err := gmx.Matrix().Download(url)
diff --git a/ui/messages/tstring/string.go b/ui/messages/tstring/string.go
index ad0b2c8..d1ad446 100644
--- a/ui/messages/tstring/string.go
+++ b/ui/messages/tstring/string.go
@@ -19,8 +19,8 @@ package tstring
import (
"strings"
- "maunium.net/go/tcell"
"github.com/mattn/go-runewidth"
+ "maunium.net/go/tcell"
)
type TString []Cell
@@ -49,6 +49,37 @@ func NewStyleTString(str string, style tcell.Style) TString {
return newStr
}
+func (str TString) AppendTString(data TString) TString {
+ return append(str, data...)
+}
+
+func (str TString) Append(data string) TString {
+ newStr := make(TString, len(str)+len(data))
+ copy(newStr, str)
+ for i, char := range data {
+ newStr[i+len(str)] = NewCell(char)
+ }
+ return newStr
+}
+
+func (str TString) AppendColor(data string, color tcell.Color) TString {
+ newStr := make(TString, len(str)+len(data))
+ copy(newStr, str)
+ for i, char := range data {
+ newStr[i+len(str)] = NewColorCell(char, color)
+ }
+ return newStr
+}
+
+func (str TString) AppendStyle(data string, style tcell.Style) TString {
+ newStr := make(TString, len(str)+len(data))
+ copy(newStr, str)
+ for i, char := range data {
+ newStr[i+len(str)] = NewStyleCell(char, style)
+ }
+ return newStr
+}
+
func (str TString) Colorize(from, length int, color tcell.Color) {
for i := from; i < from+length; i++ {
str[i].Style = str[i].Style.Foreground(color)
diff --git a/ui/view-main.go b/ui/view-main.go
index e5850d3..ccb3cc1 100644
--- a/ui/view-main.go
+++ b/ui/view-main.go
@@ -164,9 +164,7 @@ func (view *MainView) HandleCommand(roomView *RoomView, command string, args []s
view.gmx.Stop()
case "/panic":
panic("This is a test panic.")
- case "/part":
- fallthrough
- case "/leave":
+ case "/part", "/leave":
debug.Print("Leave room result:", view.matrix.LeaveRoom(roomView.Room.ID))
case "/join":
if len(args) == 0 {