diff options
Diffstat (limited to 'ui/messages/htmlmessage.go')
-rw-r--r-- | ui/messages/htmlmessage.go | 187 |
1 files changed, 187 insertions, 0 deletions
diff --git a/ui/messages/htmlmessage.go b/ui/messages/htmlmessage.go new file mode 100644 index 0000000..de4b30c --- /dev/null +++ b/ui/messages/htmlmessage.go @@ -0,0 +1,187 @@ +// 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 messages + +import ( + "time" + + "github.com/mattn/go-runewidth" + + "maunium.net/go/gomuks/config" + "maunium.net/go/gomuks/ui/widget" + "maunium.net/go/mautrix" + "maunium.net/go/mauview" + "maunium.net/go/tcell" +) + +type HTMLMessage struct { + BaseMessage + + Root *HTMLEntity +} + +func NewHTMLMessage(id, sender, displayname string, msgtype mautrix.MessageType, root *HTMLEntity, timestamp time.Time) UIMessage { + return &HTMLMessage{ + BaseMessage: newBaseMessage(id, sender, displayname, msgtype, timestamp), + Root: root, + } +} +func (hw *HTMLMessage) Draw(screen mauview.Screen) { + hw.Root.Draw(screen) +} + +func (hw *HTMLMessage) OnKeyEvent(event mauview.KeyEvent) bool { + return false +} + +func (hw *HTMLMessage) OnMouseEvent(event mauview.MouseEvent) bool { + return false +} + +func (hw *HTMLMessage) OnPasteEvent(event mauview.PasteEvent) bool { + return false +} + +func (hw *HTMLMessage) CalculateBuffer(preferences config.UserPreferences, width int) { + // TODO account for bare messages in initial startX + startX := 0 + hw.Root.calculateBuffer(width, startX, preferences.BareMessageView) +} + +func (hw *HTMLMessage) Height() int { + return hw.Root.height +} + +func (hw *HTMLMessage) PlainText() string { + return "Plaintext unavailable" +} + +func (hw *HTMLMessage) NotificationContent() string { + return "Notification content unavailable" +} + +type HTMLEntity struct { + // Permanent variables + Tag string + Text string + Style tcell.Style + Children []*HTMLEntity + Block bool + Indent int + + // Non-permanent variables (calculated buffer data) + buffer []string + prevWidth int + startX int + height int +} + +func (he *HTMLEntity) AdjustStyle(fn func(tcell.Style) tcell.Style) *HTMLEntity { + for _, child := range he.Children { + child.AdjustStyle(fn) + } + he.Style = fn(he.Style) + return he +} + +func (he *HTMLEntity) Draw(screen mauview.Screen) { + width, _ := screen.Size() + if len(he.buffer) > 0 { + x := he.startX + for y, line := range he.buffer { + widget.WriteLine(screen, mauview.AlignLeft, line, x, y, width, he.Style) + x = 0 + } + } + if len(he.Children) > 0 { + proxyScreen := &mauview.ProxyScreen{Parent: screen, OffsetX: he.Indent, Width: width - he.Indent} + for _, entity := range he.Children { + if entity.Block { + proxyScreen.OffsetY++ + } + proxyScreen.Height = entity.height + entity.Draw(proxyScreen) + proxyScreen.OffsetY += entity.height - 1 + } + } +} + +func (he *HTMLEntity) calculateBuffer(width, startX int, bare bool) int { + if len(he.Children) > 0 { + childStartX := 0 + for _, entity := range he.Children { + childStartX = entity.calculateBuffer(width-he.Indent, childStartX, bare) + he.height += entity.height - 1 + } + } + if len(he.Text) > 0 && width != he.prevWidth { + he.prevWidth = width + he.buffer = make([]string, 0, 1) + text := he.Text + if !he.Block { + he.startX = startX + } else { + startX = 0 + } + for { + extract := runewidth.Truncate(text, width-startX, "") + extract = trim(extract, text, bare) + he.buffer = append(he.buffer, extract) + text = text[len(extract):] + startX = 0 + if len(text) == 0 { + he.height += len(he.buffer) + // This entity is over, return the startX for the next entity + if he.Block { + // ...except if it's a block entity + return 0 + } + return runewidth.StringWidth(extract) + } + } + } + return 0 +} + +// Regular expressions used to split lines when calculating the buffer. +/*var ( + boundaryPattern = regexp.MustCompile(`([[:punct:]]\s*|\s+)`) + bareBoundaryPattern = regexp.MustCompile(`(\s+)`) + spacePattern = regexp.MustCompile(`\s+`) +)*/ + +func trim(extract, full string, bare bool) string { + if len(extract) == len(full) { + return extract + } + if spaces := spacePattern.FindStringIndex(full[len(extract):]); spaces != nil && spaces[0] == 0 { + extract = full[:len(extract)+spaces[1]] + } + regex := boundaryPattern + if bare { + regex = bareBoundaryPattern + } + matches := regex.FindAllStringIndex(extract, -1) + if len(matches) > 0 { + if match := matches[len(matches)-1]; len(match) >= 2 { + if until := match[1]; until < len(extract) { + extract = extract[:until] + } + } + } + return extract +} |