aboutsummaryrefslogtreecommitdiff
path: root/ui/messages/parser
diff options
context:
space:
mode:
authorTulir Asokan <tulir@maunium.net>2019-04-07 20:13:23 +0300
committerTulir Asokan <tulir@maunium.net>2019-04-07 20:13:23 +0300
commitcf93671ecdebc96cfd45fefaeb9fc95f62393a33 (patch)
tree4be979095f5a622f8308092a82a033be665d0781 /ui/messages/parser
parent083ae8bd44ad34859781ea88cae9f57d9404c7e5 (diff)
Add syntax highlighting. Fixes #28
Diffstat (limited to 'ui/messages/parser')
-rw-r--r--ui/messages/parser/htmlparser.go95
1 files changed, 92 insertions, 3 deletions
diff --git a/ui/messages/parser/htmlparser.go b/ui/messages/parser/htmlparser.go
index 9a9c2d1..688deb3 100644
--- a/ui/messages/parser/htmlparser.go
+++ b/ui/messages/parser/htmlparser.go
@@ -23,6 +23,9 @@ import (
"strconv"
"strings"
+ "github.com/alecthomas/chroma"
+ "github.com/alecthomas/chroma/lexers"
+ "github.com/alecthomas/chroma/styles"
"github.com/lucasb-eyer/go-colorful"
"golang.org/x/net/html"
@@ -216,18 +219,102 @@ func (parser *htmlParser) linkToEntity(node *html.Node, stripLinebreak bool) *me
}
}
}
- // TODO add click action for links
+ // TODO add click action and underline on hover for links
return entity
}
-func (parser *htmlParser) codeblockToEntity(node *html.Node) *messages.HTMLEntity {
+func (parser *htmlParser) imageToEntity(node *html.Node) *messages.HTMLEntity {
+ alt := parser.getAttribute(node, "alt")
+ if len(alt) == 0 {
+ alt = parser.getAttribute(node, "title")
+ if len(alt) == 0 {
+ alt = "[inline image]"
+ }
+ }
+ entity := &messages.HTMLEntity{
+ Tag: "img",
+ Text: alt,
+ }
+ // TODO add click action and underline on hover for inline images
+ return entity
+}
+
+func colourToColor(colour chroma.Colour) tcell.Color {
+ if !colour.IsSet() {
+ return tcell.ColorDefault
+ }
+ return tcell.NewRGBColor(int32(colour.Red()), int32(colour.Green()), int32(colour.Blue()))
+}
+
+func styleEntryToStyle(se chroma.StyleEntry) tcell.Style {
+ return tcell.StyleDefault.
+ Bold(se.Bold == chroma.Yes).
+ Italic(se.Italic == chroma.Yes).
+ Underline(se.Underline == chroma.Yes).
+ Foreground(colourToColor(se.Colour)).
+ Background(colourToColor(se.Background))
+}
+
+func (parser *htmlParser) syntaxHighlight(text, language string) *messages.HTMLEntity {
+ lexer := lexers.Get(language)
+ if lexer == nil {
+ return nil
+ }
+ iter, err := lexer.Tokenise(nil, text)
+ if err != nil {
+ return nil
+ }
+ style := styles.SolarizedDark
+ tokens := iter.Tokens()
+ children := make([]*messages.HTMLEntity, len(tokens))
+ for i, token := range tokens {
+ if token.Value == "\n" {
+ children[i] = &messages.HTMLEntity{Block: true, Tag: "br"}
+ } else {
+ children[i] = &messages.HTMLEntity{
+ Tag: token.Type.String(),
+ Text: token.Value,
+ Style: styleEntryToStyle(style.Get(token.Type)),
+
+ DefaultHeight: 1,
+ }
+ }
+ }
return &messages.HTMLEntity{
Tag: "pre",
- Children: parser.nodeToEntities(node.FirstChild, false),
Block: true,
+ Children: children,
}
}
+func (parser *htmlParser) codeblockToEntity(node *html.Node) *messages.HTMLEntity {
+ entity := &messages.HTMLEntity{
+ Tag: "pre",
+ Block: true,
+ }
+ // TODO allow disabling syntax highlighting
+ if node.FirstChild.Type == html.ElementNode && node.FirstChild.Data == "code" {
+ text := (&messages.HTMLEntity{
+ Children: parser.nodeToEntities(node.FirstChild.FirstChild, false),
+ }).PlainText()
+ attr := parser.getAttribute(node.FirstChild, "class")
+ var lang string
+ for _, class := range strings.Split(attr, " ") {
+ if strings.HasPrefix(class, "language-") {
+ lang = class[len("language-"):]
+ break
+ }
+ }
+ if len(lang) != 0 {
+ if parsed := parser.syntaxHighlight(text, lang); parsed != nil {
+ return parsed
+ }
+ }
+ }
+ entity.Children = parser.nodeToEntities(node.FirstChild, false)
+ return entity
+}
+
func (parser *htmlParser) tagNodeToEntity(node *html.Node, stripLinebreak bool) *messages.HTMLEntity {
switch node.Data {
case "blockquote":
@@ -242,6 +329,8 @@ func (parser *htmlParser) tagNodeToEntity(node *html.Node, stripLinebreak bool)
return parser.basicFormatToEntity(node, stripLinebreak)
case "a":
return parser.linkToEntity(node, stripLinebreak)
+ case "img":
+ return parser.imageToEntity(node)
case "pre":
return parser.codeblockToEntity(node)
default: