Add basic HTML rendering (ref #16)
This commit is contained in:
		@@ -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 = ""
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										136
									
								
								ui/messages/htmlparser.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								ui/messages/htmlparser.go
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
}
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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 {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user