aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Gopkg.lock94
-rw-r--r--Gopkg.toml2
-rw-r--r--README.md2
-rw-r--r--config/config.go6
-rw-r--r--interface/matrix.go10
-rw-r--r--interface/ui.go12
-rw-r--r--matrix/doc.go2
-rw-r--r--matrix/matrix.go86
-rw-r--r--matrix/matrix_test.go10
-rw-r--r--matrix/pushrules/condition.go14
-rw-r--r--matrix/pushrules/condition_test.go10
-rw-r--r--matrix/pushrules/pushrules.go8
-rw-r--r--matrix/pushrules/pushrules_test.go4
-rw-r--r--matrix/pushrules/rule.go14
-rw-r--r--matrix/pushrules/ruleset.go4
-rw-r--r--matrix/rooms/room.go48
-rw-r--r--matrix/rooms/room_test.go18
-rw-r--r--matrix/sync.go43
-rw-r--r--matrix/sync_test.go66
-rw-r--r--ui/commands.go6
-rw-r--r--ui/messages/base.go10
-rw-r--r--ui/messages/expandedtextmessage.go4
-rw-r--r--ui/messages/imagemessage.go4
-rw-r--r--ui/messages/parser/htmlparser.go9
-rw-r--r--ui/messages/parser/parser.go37
-rw-r--r--ui/messages/textmessage.go6
-rw-r--r--ui/room-view.go8
-rw-r--r--ui/view-login.go6
-rw-r--r--ui/view-main.go8
-rw-r--r--ui/widget/border.go4
-rw-r--r--vendor/golang.org/x/image/bmp/reader.go30
-rw-r--r--vendor/golang.org/x/image/tiff/reader.go2
-rw-r--r--vendor/golang.org/x/image/vp8/decode.go4
-rw-r--r--vendor/golang.org/x/image/webp/decode.go2
-rw-r--r--vendor/golang.org/x/image/webp/doc.go9
-rw-r--r--vendor/golang.org/x/image/webp/webp.go30
-rw-r--r--vendor/golang.org/x/net/html/const.go10
-rw-r--r--vendor/golang.org/x/net/html/parse.go29
-rw-r--r--vendor/gopkg.in/russross/blackfriday.v2/.travis.yml27
-rw-r--r--vendor/gopkg.in/russross/blackfriday.v2/README.md20
-rw-r--r--vendor/gopkg.in/russross/blackfriday.v2/block.go101
-rw-r--r--vendor/gopkg.in/russross/blackfriday.v2/go.mod1
-rw-r--r--vendor/gopkg.in/russross/blackfriday.v2/html.go21
-rw-r--r--vendor/gopkg.in/russross/blackfriday.v2/inline.go18
-rw-r--r--vendor/gopkg.in/russross/blackfriday.v2/markdown.go94
-rw-r--r--vendor/maunium.net/go/gomatrix/.gitignore24
-rw-r--r--vendor/maunium.net/go/gomatrix/.travis.yml9
-rw-r--r--vendor/maunium.net/go/gomatrix/README.md6
-rw-r--r--vendor/maunium.net/go/maulogger/LICENSE21
-rw-r--r--vendor/maunium.net/go/maulogger/README.md6
-rw-r--r--vendor/maunium.net/go/maulogger/logger.go219
-rw-r--r--vendor/maunium.net/go/mautrix/.gitignore2
-rw-r--r--vendor/maunium.net/go/mautrix/LICENSE (renamed from vendor/maunium.net/go/gomatrix/LICENSE)0
-rw-r--r--vendor/maunium.net/go/mautrix/README.md4
-rw-r--r--vendor/maunium.net/go/mautrix/client.go (renamed from vendor/maunium.net/go/gomatrix/client.go)18
-rw-r--r--vendor/maunium.net/go/mautrix/events.go (renamed from vendor/maunium.net/go/gomatrix/events.go)45
-rw-r--r--vendor/maunium.net/go/mautrix/filter.go (renamed from vendor/maunium.net/go/gomatrix/filter.go)2
-rw-r--r--vendor/maunium.net/go/mautrix/reply.go (renamed from vendor/maunium.net/go/gomatrix/reply.go)3
-rw-r--r--vendor/maunium.net/go/mautrix/requests.go (renamed from vendor/maunium.net/go/gomatrix/requests.go)6
-rw-r--r--vendor/maunium.net/go/mautrix/responses.go (renamed from vendor/maunium.net/go/gomatrix/responses.go)18
-rw-r--r--vendor/maunium.net/go/mautrix/room.go (renamed from vendor/maunium.net/go/gomatrix/room.go)2
-rw-r--r--vendor/maunium.net/go/mautrix/store.go (renamed from vendor/maunium.net/go/gomatrix/store.go)2
-rw-r--r--vendor/maunium.net/go/mautrix/sync.go (renamed from vendor/maunium.net/go/gomatrix/sync.go)2
-rw-r--r--vendor/maunium.net/go/mautrix/userids.go (renamed from vendor/maunium.net/go/gomatrix/userids.go)4
-rw-r--r--vendor/maunium.net/go/tcell/README.adoc270
-rw-r--r--vendor/maunium.net/go/tcell/cell.go7
-rw-r--r--vendor/maunium.net/go/tcell/console_win.go1
-rw-r--r--vendor/maunium.net/go/tcell/tcell.pngbin5336 -> 0 bytes
-rw-r--r--vendor/maunium.net/go/tcell/tcell.svg93
-rw-r--r--vendor/maunium.net/go/tcell/terminfo/term_termite.go152
-rw-r--r--vendor/maunium.net/go/tcell/tscreen.go6
-rw-r--r--vendor/maunium.net/go/tview/LICENSE.txt2
-rw-r--r--vendor/maunium.net/go/tview/README.md11
-rw-r--r--vendor/maunium.net/go/tview/ansi.go (renamed from vendor/maunium.net/go/tview/ansii.go)64
-rw-r--r--vendor/maunium.net/go/tview/application.go376
-rw-r--r--vendor/maunium.net/go/tview/borders.go45
-rw-r--r--vendor/maunium.net/go/tview/box.go88
-rw-r--r--vendor/maunium.net/go/tview/checkbox.go6
-rw-r--r--vendor/maunium.net/go/tview/doc.go31
-rw-r--r--vendor/maunium.net/go/tview/dropdown.go5
-rw-r--r--vendor/maunium.net/go/tview/flex.go17
-rw-r--r--vendor/maunium.net/go/tview/form.go53
-rw-r--r--vendor/maunium.net/go/tview/grid.go16
-rw-r--r--vendor/maunium.net/go/tview/inputfield.go205
-rw-r--r--vendor/maunium.net/go/tview/list.go15
-rw-r--r--vendor/maunium.net/go/tview/modal.go15
-rw-r--r--vendor/maunium.net/go/tview/semigraphics.go296
-rw-r--r--vendor/maunium.net/go/tview/table.go119
-rw-r--r--vendor/maunium.net/go/tview/textview.go266
-rw-r--r--vendor/maunium.net/go/tview/treeview.go684
-rw-r--r--vendor/maunium.net/go/tview/util.go658
91 files changed, 3303 insertions, 1544 deletions
diff --git a/Gopkg.lock b/Gopkg.lock
index 25c518f..5e46ec5 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -2,79 +2,104 @@
[[projects]]
+ digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec"
name = "github.com/davecgh/go-spew"
packages = ["spew"]
+ pruneopts = "UT"
revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73"
version = "v1.1.1"
[[projects]]
+ digest = "1:2694597a20d303502ad1b7f1cc688977c7dc34da732ccf4d27e98b552cceed5b"
name = "github.com/disintegration/imaging"
packages = ["."]
+ pruneopts = "UT"
revision = "0bd5694c78c9c3d9a3cd06a706a8f3c59296a9ac"
version = "v1.5.0"
[[projects]]
branch = "master"
+ digest = "1:40d0056c1b1f503c366ba441df92a82b5a2654d6f3747b1689a611eb5c9ce0a2"
name = "github.com/gdamore/encoding"
packages = ["."]
+ pruneopts = "UT"
revision = "b23993cbb6353f0e6aa98d0ee318a34728f628b9"
[[projects]]
+ digest = "1:f4bd9b6cfcbaafa94259861747781022d8f06ff3c88aafe49a815ac02609bc96"
name = "github.com/kyokomi/emoji"
packages = ["."]
+ pruneopts = "UT"
revision = "2e9a9507333f3ee28f3fab88c2c3aba34455d734"
version = "v1.5.1"
[[projects]]
+ digest = "1:c65a16ac77d0b1aefc7009cabb6ac5ad05def02025f5be85f450c03f52cc6f86"
name = "github.com/lucasb-eyer/go-colorful"
packages = ["."]
+ pruneopts = "UT"
revision = "345fbb3dbcdb252d9985ee899a84963c0fa24c82"
version = "v1.0"
[[projects]]
+ digest = "1:cdb899c199f907ac9fb50495ec71212c95cb5b0e0a8ee0800da0238036091033"
name = "github.com/mattn/go-runewidth"
packages = ["."]
+ pruneopts = "UT"
revision = "ce7b0b5c7b45a81508558cd1dba6bb1e4ddb51bb"
version = "v0.0.3"
[[projects]]
branch = "master"
+ digest = "1:0e1e5f960c58fdc677212fcc70e55042a0084d367623e51afbdb568963832f5d"
name = "github.com/nu7hatch/gouuid"
packages = ["."]
+ pruneopts = "UT"
revision = "179d4d0c4d8d407a32af483c2354df1d2c91e6c3"
[[projects]]
+ digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe"
name = "github.com/pmezard/go-difflib"
packages = ["difflib"]
+ pruneopts = "UT"
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
version = "v1.0.0"
[[projects]]
+ branch = "master"
+ digest = "1:3fd3d634f6815f19ac4b2c5e16d28ec9aa4584d0bba25d1ee6c424d813cca22a"
name = "github.com/renstrom/fuzzysearch"
packages = ["fuzzy"]
+ pruneopts = "UT"
revision = "b18e754edff4833912ef4dce9eaca885bd3f0de1"
- version = "v1.0.1"
[[projects]]
branch = "master"
+ digest = "1:def689e73e9252f6f7fe66834a76751a41b767e03daab299e607e7226c58a855"
name = "github.com/shurcooL/sanitized_anchor_name"
packages = ["."]
+ pruneopts = "UT"
revision = "86672fcb3f950f35f2e675df2240550f2a50762f"
[[projects]]
+ digest = "1:18752d0b95816a1b777505a97f71c7467a8445b8ffb55631a7bf779f6ba4fa83"
name = "github.com/stretchr/testify"
packages = ["assert"]
+ pruneopts = "UT"
revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686"
version = "v1.2.2"
[[projects]]
branch = "master"
+ digest = "1:22738a5261588086d75acb3c792f7c931d30fd32e351117e9e046331135f5297"
name = "github.com/zyedidia/clipboard"
packages = ["."]
+ pruneopts = "UT"
revision = "bd31d747117d04b4e25b61f73e1ea4faeea3c56a"
[[projects]]
branch = "master"
+ digest = "1:441333ba1f7f3074035297647244e3d1ce3665e4e7c5054b3a8b138463f8ff58"
name = "golang.org/x/image"
packages = [
"bmp",
@@ -83,79 +108,108 @@
"tiff/lzw",
"vp8",
"vp8l",
- "webp"
+ "webp",
]
- revision = "c73c2afc3b812cdd6385de5a50616511c4a3d458"
+ pruneopts = "UT"
+ revision = "249dc8530c0efc2766606a940c1c50b434b2f1cd"
[[projects]]
branch = "master"
+ digest = "1:1a1ecfa7b54ca3f7a0115ab5c578d7d6a5d8b605839c549e80260468c42f8be7"
name = "golang.org/x/net"
packages = [
"html",
- "html/atom"
+ "html/atom",
]
- revision = "8a410e7b638dca158bf9e766925842f6651ff828"
+ pruneopts = "UT"
+ revision = "88d92db4c548972d942ac2a3531a8a9a34c82ca6"
[[projects]]
+ digest = "1:37672ad5821719e2df8509c2edd4ba5ae192463237c73c3a2d24ef8b2bc9e36f"
name = "golang.org/x/text"
packages = [
"encoding",
"encoding/internal/identifier",
"internal/gen",
"transform",
- "unicode/cldr"
+ "unicode/cldr",
]
+ pruneopts = "UT"
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
version = "v0.3.0"
[[projects]]
+ digest = "1:2ee0f15eb0fb04f918db7c2dcf39745f40d69f798ef171610a730e8a56aaa4fd"
name = "gopkg.in/russross/blackfriday.v2"
packages = ["."]
- revision = "cadec560ec52d93835bf2f15bd794700d3a2473b"
- version = "v2.0.0"
+ pruneopts = "UT"
+ revision = "d3b5b032dc8e8927d31a5071b56e14c89f045135"
+ version = "v2.0.1"
[[projects]]
branch = "v1"
+ digest = "1:b71ccd8615521db4ea2f9381f55780a1312654f5d8431d2e58eca51c74110de2"
name = "gopkg.in/toast.v1"
packages = ["."]
+ pruneopts = "UT"
revision = "0a84660828b24d25b35525c9a1f1f51267f8da91"
[[projects]]
+ digest = "1:342378ac4dcb378a5448dd723f0784ae519383532f5e70ade24132c4c8693202"
name = "gopkg.in/yaml.v2"
packages = ["."]
+ pruneopts = "UT"
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
version = "v2.2.1"
[[projects]]
branch = "master"
- name = "maunium.net/go/gomatrix"
- packages = ["."]
- revision = "d651abc3ecb4bbd85a6b17b710632e73ddbbc6aa"
-
-[[projects]]
- name = "maunium.net/go/maulogger"
+ digest = "1:d23e0bbfa4e45fe847e61fdfd442b24adf9bd3d0b050a846f9ef0f3af4f43c86"
+ name = "maunium.net/go/mautrix"
packages = ["."]
- revision = "64f0aa33b6c51313e15575257db71dec44fe7988"
- version = "v1.0"
+ pruneopts = "UT"
+ revision = "e8080dcf484d1db9021d2019fee132ffc9e37e3c"
[[projects]]
branch = "master"
+ digest = "1:28d6d6123ce29c9b0b5d393af0085f1454a9d93d70b4c9893efe6cff025702b9"
name = "maunium.net/go/tcell"
packages = [
".",
- "terminfo"
+ "terminfo",
]
- revision = "f32e44a866ab0475655e11ede91337d7b562956d"
+ pruneopts = "UT"
+ revision = "a62189e4543d731b94ae0d3b659d1853975ea657"
[[projects]]
branch = "master"
+ digest = "1:b4c2e264221b6afa98aec4e0a097f3775cb014eebac8a6f49d33c53b237b77af"
name = "maunium.net/go/tview"
packages = ["."]
- revision = "8b261597bbdb95dcaef03854aaa0cc192f56b1ff"
+ pruneopts = "UT"
+ revision = "dae31f32cda75339f5ec2a64411ef3ec1862ef5e"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
- inputs-digest = "6c0a263ebffa1c073f4b67a895832017afeab906b0d88b31da38ae22bc75e1aa"
+ input-imports = [
+ "github.com/disintegration/imaging",
+ "github.com/kyokomi/emoji",
+ "github.com/lucasb-eyer/go-colorful",
+ "github.com/mattn/go-runewidth",
+ "github.com/renstrom/fuzzysearch/fuzzy",
+ "github.com/stretchr/testify/assert",
+ "github.com/zyedidia/clipboard",
+ "golang.org/x/image/bmp",
+ "golang.org/x/image/tiff",
+ "golang.org/x/image/webp",
+ "golang.org/x/net/html",
+ "gopkg.in/russross/blackfriday.v2",
+ "gopkg.in/toast.v1",
+ "gopkg.in/yaml.v2",
+ "maunium.net/go/mautrix",
+ "maunium.net/go/tcell",
+ "maunium.net/go/tview",
+ ]
solver-name = "gps-cdcl"
solver-version = 1
diff --git a/Gopkg.toml b/Gopkg.toml
index 5927463..2fa3148 100644
--- a/Gopkg.toml
+++ b/Gopkg.toml
@@ -63,7 +63,7 @@
[[constraint]]
branch = "master"
- name = "maunium.net/go/gomatrix"
+ name = "maunium.net/go/mautrix"
[[constraint]]
branch = "master"
diff --git a/README.md b/README.md
index 79509d3..e39c33b 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
![Chat Preview](chat-preview.png)
-A terminal Matrix client written in Go using [gomatrix](https://github.com/matrix-org/gomatrix) and [tview](https://github.com/rivo/tview).
+A terminal Matrix client written in Go using [mautrix](https://github.com/matrix-org/mautrix) and [tview](https://github.com/rivo/tview).
Basic usage is possible, but expect bugs and missing features.
diff --git a/config/config.go b/config/config.go
index e4fb743..8f45d76 100644
--- a/config/config.go
+++ b/config/config.go
@@ -25,7 +25,7 @@ import (
"strings"
"gopkg.in/yaml.v2"
- "maunium.net/go/gomatrix"
+ "maunium.net/go/mautrix"
"maunium.net/go/gomuks/debug"
"maunium.net/go/gomuks/matrix/pushrules"
"maunium.net/go/gomuks/matrix/rooms"
@@ -289,12 +289,12 @@ func (config *Config) PutRoom(room *rooms.Room) {
room.Save(config.getRoomCachePath(room))
}
-func (config *Config) SaveRoom(room *gomatrix.Room) {
+func (config *Config) SaveRoom(room *mautrix.Room) {
gmxRoom := config.GetRoom(room.ID)
gmxRoom.Room = room
gmxRoom.Save(config.getRoomCachePath(gmxRoom))
}
-func (config *Config) LoadRoom(roomID string) *gomatrix.Room {
+func (config *Config) LoadRoom(roomID string) *mautrix.Room {
return config.GetRoom(roomID).Room
}
diff --git a/interface/matrix.go b/interface/matrix.go
index 0538df9..01f224e 100644
--- a/interface/matrix.go
+++ b/interface/matrix.go
@@ -17,12 +17,12 @@
package ifc
import (
- "maunium.net/go/gomatrix"
+ "maunium.net/go/mautrix"
"maunium.net/go/gomuks/matrix/rooms"
)
type MatrixContainer interface {
- Client() *gomatrix.Client
+ Client() *mautrix.Client
InitClient() error
Initialized() bool
@@ -33,14 +33,14 @@ type MatrixContainer interface {
Logout()
SendPreferencesToMatrix()
- SendMessage(roomID string, msgtype gomatrix.MessageType, message string) (string, error)
- SendMarkdownMessage(roomID string, msgtype gomatrix.MessageType, message string) (string, error)
+ SendMessage(roomID string, msgtype mautrix.MessageType, message string) (string, error)
+ SendMarkdownMessage(roomID string, msgtype mautrix.MessageType, message string) (string, error)
SendTyping(roomID string, typing bool)
MarkRead(roomID, eventID string)
JoinRoom(roomID, server string) (*rooms.Room, error)
LeaveRoom(roomID string) error
- GetHistory(roomID, prevBatch string, limit int) ([]*gomatrix.Event, string, error)
+ GetHistory(roomID, prevBatch string, limit int) ([]*mautrix.Event, string, error)
GetRoom(roomID string) *rooms.Room
Download(mxcURL string) ([]byte, string, string, error)
diff --git a/interface/ui.go b/interface/ui.go
index 929dc65..39473f5 100644
--- a/interface/ui.go
+++ b/interface/ui.go
@@ -19,7 +19,7 @@ package ifc
import (
"time"
- "maunium.net/go/gomatrix"
+ "maunium.net/go/mautrix"
"maunium.net/go/gomuks/matrix/pushrules"
"maunium.net/go/gomuks/matrix/rooms"
"maunium.net/go/tcell"
@@ -50,7 +50,7 @@ type MainView interface {
UpdateTags(room *rooms.Room)
SetTyping(roomID string, users []string)
- ParseEvent(roomView RoomView, evt *gomatrix.Event) Message
+ ParseEvent(roomView RoomView, evt *mautrix.Event) Message
NotifyMessage(room *rooms.Room, message Message, should pushrules.PushActionArrayShould)
InitialSyncDone()
@@ -73,8 +73,8 @@ type RoomView interface {
SetTyping(users []string)
UpdateUserList()
- NewMessage(id, sender string, msgtype gomatrix.MessageType, text string, timestamp time.Time) Message
- NewTempMessage(msgtype gomatrix.MessageType, text string) Message
+ NewMessage(id, sender string, msgtype mautrix.MessageType, text string, timestamp time.Time) Message
+ NewTempMessage(msgtype mautrix.MessageType, text string) Message
AddMessage(message Message, direction MessageDirection)
AddServiceMessage(message string)
}
@@ -111,8 +111,8 @@ type Message interface {
SetID(id string)
ID() string
- SetType(msgtype gomatrix.MessageType)
- Type() gomatrix.MessageType
+ SetType(msgtype mautrix.MessageType)
+ Type() mautrix.MessageType
NotificationContent() string
diff --git a/matrix/doc.go b/matrix/doc.go
index 2cd54e2..f789895 100644
--- a/matrix/doc.go
+++ b/matrix/doc.go
@@ -1,2 +1,2 @@
-// Package matrix contains wrappers for gomatrix for use by the UI of gomuks.
+// Package matrix contains wrappers for mautrix for use by the UI of gomuks.
package matrix
diff --git a/matrix/matrix.go b/matrix/matrix.go
index 060becd..cf9e463 100644
--- a/matrix/matrix.go
+++ b/matrix/matrix.go
@@ -34,20 +34,20 @@ import (
"encoding/json"
"gopkg.in/russross/blackfriday.v2"
- "maunium.net/go/gomatrix"
"maunium.net/go/gomuks/config"
"maunium.net/go/gomuks/debug"
"maunium.net/go/gomuks/interface"
"maunium.net/go/gomuks/lib/bfhtml"
"maunium.net/go/gomuks/matrix/pushrules"
"maunium.net/go/gomuks/matrix/rooms"
+ "maunium.net/go/mautrix"
)
-// Container is a wrapper for a gomatrix Client and some other stuff.
+// Container is a wrapper for a mautrix Client and some other stuff.
//
// It is used for all Matrix calls from the UI and Matrix event handlers.
type Container struct {
- client *gomatrix.Client
+ client *mautrix.Client
syncer *GomuksSyncer
gmx ifc.Gomuks
ui ifc.GomuksUI
@@ -69,12 +69,18 @@ func NewContainer(gmx ifc.Gomuks) *Container {
return c
}
-// Client returns the underlying gomatrix Client.
-func (c *Container) Client() *gomatrix.Client {
+// Client returns the underlying mautrix Client.
+func (c *Container) Client() *mautrix.Client {
return c.client
}
-// InitClient initializes the gomatrix client and connects to the homeserver specified in the config.
+type mxLogger struct{}
+
+func (log mxLogger) Debugfln(message string, args ...interface{}) {
+ debug.Printf("[Matrix] "+message, args...)
+}
+
+// InitClient initializes the mautrix client and connects to the homeserver specified in the config.
func (c *Container) InitClient() error {
if len(c.config.HS) == 0 {
return fmt.Errorf("no homeserver in config")
@@ -92,10 +98,11 @@ func (c *Container) InitClient() error {
}
var err error
- c.client, err = gomatrix.NewClient(c.config.HS, mxid, accessToken)
+ c.client, err = mautrix.NewClient(c.config.HS, mxid, accessToken)
if err != nil {
return err
}
+ c.client.Logger = mxLogger{}
allowInsecure := len(os.Getenv("GOMUKS_ALLOW_INSECURE_CONNECTIONS")) > 0
if allowInsecure {
@@ -112,14 +119,14 @@ func (c *Container) InitClient() error {
return nil
}
-// Initialized returns whether or not the gomatrix client is initialized (see InitClient())
+// Initialized returns whether or not the mautrix client is initialized (see InitClient())
func (c *Container) Initialized() bool {
return c.client != nil
}
// Login sends a password login request with the given username and password.
func (c *Container) Login(user, password string) error {
- resp, err := c.client.Login(&gomatrix.ReqLogin{
+ resp, err := c.client.Login(&mautrix.ReqLogin{
Type: "m.login.password",
User: user,
Password: password,
@@ -175,7 +182,7 @@ func (c *Container) PushRules() *pushrules.PushRuleset {
return c.config.PushRules
}
-var AccountDataGomuksPreferences = gomatrix.NewEventType("net.maunium.gomuks.preferences")
+var AccountDataGomuksPreferences = mautrix.NewEventType("net.maunium.gomuks.preferences")
// OnLogin initializes the syncer and updates the room list.
func (c *Container) OnLogin() {
@@ -185,15 +192,16 @@ func (c *Container) OnLogin() {
debug.Print("Initializing syncer")
c.syncer = NewGomuksSyncer(c.config)
- c.syncer.OnEventType(gomatrix.EventMessage, c.HandleMessage)
- c.syncer.OnEventType(gomatrix.StateMember, c.HandleMembership)
- c.syncer.OnEventType(gomatrix.EphemeralEventReceipt, c.HandleReadReceipt)
- c.syncer.OnEventType(gomatrix.EphemeralEventTyping, c.HandleTyping)
- c.syncer.OnEventType(gomatrix.AccountDataDirectChats, c.HandleDirectChatInfo)
- c.syncer.OnEventType(gomatrix.AccountDataPushRules, c.HandlePushRules)
- c.syncer.OnEventType(gomatrix.AccountDataRoomTags, c.HandleTag)
+ c.syncer.OnEventType(mautrix.EventMessage, c.HandleMessage)
+ c.syncer.OnEventType(mautrix.StateMember, c.HandleMembership)
+ c.syncer.OnEventType(mautrix.EphemeralEventReceipt, c.HandleReadReceipt)
+ c.syncer.OnEventType(mautrix.EphemeralEventTyping, c.HandleTyping)
+ c.syncer.OnEventType(mautrix.AccountDataDirectChats, c.HandleDirectChatInfo)
+ c.syncer.OnEventType(mautrix.AccountDataPushRules, c.HandlePushRules)
+ c.syncer.OnEventType(mautrix.AccountDataRoomTags, c.HandleTag)
c.syncer.OnEventType(AccountDataGomuksPreferences, c.HandlePreferences)
c.syncer.InitDoneCallback = func() {
+ debug.Print("Initial sync done")
c.config.AuthCache.InitialSyncDone = true
c.config.SaveAuthCache()
c.ui.MainView().InitialSyncDone()
@@ -227,7 +235,7 @@ func (c *Container) Start() {
return
default:
if err := c.client.Sync(); err != nil {
- if httpErr, ok := err.(gomatrix.HTTPError); ok && httpErr.Code == http.StatusUnauthorized {
+ if httpErr, ok := err.(mautrix.HTTPError); ok && httpErr.Code == http.StatusUnauthorized {
debug.Print("Sync() errored with ", err, " -> logging out")
c.Logout()
} else {
@@ -240,7 +248,7 @@ func (c *Container) Start() {
}
}
-func (c *Container) HandlePreferences(source EventSource, evt *gomatrix.Event) {
+func (c *Container) HandlePreferences(source EventSource, evt *mautrix.Event) {
orig := c.config.Preferences
rt, _ := json.Marshal(&evt.Content)
json.Unmarshal(rt, &c.config.Preferences)
@@ -259,7 +267,7 @@ func (c *Container) SendPreferencesToMatrix() {
}
// HandleMessage is the event handler for the m.room.message timeline event.
-func (c *Container) HandleMessage(source EventSource, evt *gomatrix.Event) {
+func (c *Container) HandleMessage(source EventSource, evt *mautrix.Event) {
if source&EventSourceLeave != 0 {
return
}
@@ -286,7 +294,7 @@ func (c *Container) HandleMessage(source EventSource, evt *gomatrix.Event) {
}
// HandleMembership is the event handler for the m.room.member state event.
-func (c *Container) HandleMembership(source EventSource, evt *gomatrix.Event) {
+func (c *Container) HandleMembership(source EventSource, evt *mautrix.Event) {
isLeave := source&EventSourceLeave != 0
isTimeline := source&EventSourceTimeline != 0
isNonTimelineLeave := isLeave && !isTimeline
@@ -302,9 +310,9 @@ func (c *Container) HandleMembership(source EventSource, evt *gomatrix.Event) {
c.HandleMessage(source, evt)
}
-func (c *Container) processOwnMembershipChange(evt *gomatrix.Event) {
+func (c *Container) processOwnMembershipChange(evt *mautrix.Event) {
membership := evt.Content.Membership
- prevMembership := gomatrix.MembershipLeave
+ prevMembership := mautrix.MembershipLeave
if evt.Unsigned.PrevContent != nil {
prevMembership = evt.Unsigned.PrevContent.Membership
}
@@ -326,7 +334,7 @@ func (c *Container) processOwnMembershipChange(evt *gomatrix.Event) {
}
}
-func (c *Container) parseReadReceipt(evt *gomatrix.Event) (largestTimestampEvent string) {
+func (c *Container) parseReadReceipt(evt *mautrix.Event) (largestTimestampEvent string) {
var largestTimestamp int64
for eventID, rawContent := range evt.Content.Raw {
content, ok := rawContent.(map[string]interface{})
@@ -353,7 +361,7 @@ func (c *Container) parseReadReceipt(evt *gomatrix.Event) (largestTimestampEvent
return
}
-func (c *Container) HandleReadReceipt(source EventSource, evt *gomatrix.Event) {
+func (c *Container) HandleReadReceipt(source EventSource, evt *mautrix.Event) {
if source&EventSourceLeave != 0 {
return
}
@@ -368,7 +376,7 @@ func (c *Container) HandleReadReceipt(source EventSource, evt *gomatrix.Event) {
c.ui.Render()
}
-func (c *Container) parseDirectChatInfo(evt *gomatrix.Event) map[*rooms.Room]bool {
+func (c *Container) parseDirectChatInfo(evt *mautrix.Event) map[*rooms.Room]bool {
directChats := make(map[*rooms.Room]bool)
for _, rawRoomIDList := range evt.Content.Raw {
roomIDList, ok := rawRoomIDList.([]interface{})
@@ -391,7 +399,7 @@ func (c *Container) parseDirectChatInfo(evt *gomatrix.Event) map[*rooms.Room]boo
return directChats
}
-func (c *Container) HandleDirectChatInfo(source EventSource, evt *gomatrix.Event) {
+func (c *Container) HandleDirectChatInfo(source EventSource, evt *mautrix.Event) {
directChats := c.parseDirectChatInfo(evt)
for _, room := range c.config.Rooms {
shouldBeDirect := directChats[room]
@@ -403,7 +411,7 @@ func (c *Container) HandleDirectChatInfo(source EventSource, evt *gomatrix.Event
}
// HandlePushRules is the event handler for the m.push_rules account data event.
-func (c *Container) HandlePushRules(source EventSource, evt *gomatrix.Event) {
+func (c *Container) HandlePushRules(source EventSource, evt *mautrix.Event) {
debug.Print("Received updated push rules")
var err error
c.config.PushRules, err = pushrules.EventToPushRules(evt)
@@ -415,7 +423,7 @@ func (c *Container) HandlePushRules(source EventSource, evt *gomatrix.Event) {
}
// HandleTag is the event handler for the m.tag account data event.
-func (c *Container) HandleTag(source EventSource, evt *gomatrix.Event) {
+func (c *Container) HandleTag(source EventSource, evt *mautrix.Event) {
room := c.config.GetRoom(evt.RoomID)
newTags := make([]rooms.RoomTag, len(evt.Content.RoomTags))
@@ -423,7 +431,7 @@ func (c *Container) HandleTag(source EventSource, evt *gomatrix.Event) {
for tag, info := range evt.Content.RoomTags {
order := "0.5"
if len(info.Order) > 0 {
- order = info.Order
+ order = info.Order.String()
}
newTags[index] = rooms.RoomTag{
Tag: tag,
@@ -438,7 +446,7 @@ func (c *Container) HandleTag(source EventSource, evt *gomatrix.Event) {
}
// HandleTyping is the event handler for the m.typing event.
-func (c *Container) HandleTyping(source EventSource, evt *gomatrix.Event) {
+func (c *Container) HandleTyping(source EventSource, evt *mautrix.Event) {
c.ui.MainView().SetTyping(evt.RoomID, evt.Content.TypingUserIDs)
}
@@ -448,11 +456,11 @@ func (c *Container) MarkRead(roomID, eventID string) {
}
// SendMessage sends a message with the given text to the given room.
-func (c *Container) SendMessage(roomID string, msgtype gomatrix.MessageType, text string) (string, error) {
+func (c *Container) SendMessage(roomID string, msgtype mautrix.MessageType, text string) (string, error) {
defer debug.Recover()
c.SendTyping(roomID, false)
- resp, err := c.client.SendMessageEvent(roomID, gomatrix.EventMessage,
- gomatrix.Content{MsgType: msgtype, Body: text})
+ resp, err := c.client.SendMessageEvent(roomID, mautrix.EventMessage,
+ mautrix.Content{MsgType: msgtype, Body: text})
if err != nil {
return "", err
}
@@ -491,7 +499,7 @@ var roomRegex = regexp.MustCompile("\\[.+?]\\(https://matrix.to/#/(#.+?:[^/]+?)\
//
// If the given text contains markdown formatting symbols, it will be rendered into HTML before sending.
// Otherwise, it will be sent as plain text.
-func (c *Container) SendMarkdownMessage(roomID string, msgtype gomatrix.MessageType, text string) (string, error) {
+func (c *Container) SendMarkdownMessage(roomID string, msgtype mautrix.MessageType, text string) (string, error) {
defer debug.Recover()
html := c.renderMarkdown(text)
@@ -504,11 +512,11 @@ func (c *Container) SendMarkdownMessage(roomID string, msgtype gomatrix.MessageT
text = roomRegex.ReplaceAllString(text, "$1")
c.SendTyping(roomID, false)
- resp, err := c.client.SendMessageEvent(roomID, gomatrix.EventMessage,
- gomatrix.Content{
+ resp, err := c.client.SendMessageEvent(roomID, mautrix.EventMessage,
+ mautrix.Content{
MsgType: msgtype,
Body: text,
- Format: gomatrix.FormatHTML,
+ Format: mautrix.FormatHTML,
FormattedBody: html,
})
if err != nil {
@@ -560,7 +568,7 @@ func (c *Container) LeaveRoom(roomID string) error {
}
// GetHistory fetches room history.
-func (c *Container) GetHistory(roomID, prevBatch string, limit int) ([]*gomatrix.Event, string, error) {
+func (c *Container) GetHistory(roomID, prevBatch string, limit int) ([]*mautrix.Event, string, error) {
resp, err := c.client.Messages(roomID, prevBatch, "", 'b', limit)
if err != nil {
return nil, "", err
diff --git a/matrix/matrix_test.go b/matrix/matrix_test.go
index b6511ec..0d8bc3c 100644
--- a/matrix/matrix_test.go
+++ b/matrix/matrix_test.go
@@ -21,7 +21,7 @@ import (
"fmt"
"github.com/stretchr/testify/assert"
"io/ioutil"
- "maunium.net/go/gomatrix"
+ "maunium.net/go/mautrix"
"maunium.net/go/gomuks/config"
"net/http"
"os"
@@ -80,7 +80,7 @@ func TestContainer_SendMarkdownMessage_WithMarkdown(t *testing.T) {
}
func TestContainer_SendTyping(t *testing.T) {
- var calls []gomatrix.ReqTyping
+ var calls []mautrix.ReqTyping
c := Container{client: mockClient(func(req *http.Request) (*http.Response, error) {
if req.Method != http.MethodPut || req.URL.Path != "/_matrix/client/r0/rooms/!foo:example.com/typing/@user:example.com" {
return nil, fmt.Errorf("unexpected query: %s %s", req.Method, req.URL.Path)
@@ -91,7 +91,7 @@ func TestContainer_SendTyping(t *testing.T) {
return nil, err
}
- call := gomatrix.ReqTyping{}
+ call := mautrix.ReqTyping{}
err = json.Unmarshal(rawBody, &call)
if err != nil {
return nil, err
@@ -187,8 +187,8 @@ func TestContainer_GetHistory(t *testing.T) {
assert.Equal(t, "456", prevBatch)
}
-func mockClient(fn func(*http.Request) (*http.Response, error)) *gomatrix.Client {
- client, _ := gomatrix.NewClient("https://example.com", "@user:example.com", "foobar")
+func mockClient(fn func(*http.Request) (*http.Response, error)) *mautrix.Client {
+ client, _ := mautrix.NewClient("https://example.com", "@user:example.com", "foobar")
client.Client = &http.Client{Transport: MockRoundTripper{RT: fn}}
return client
}
diff --git a/matrix/pushrules/condition.go b/matrix/pushrules/condition.go
index 22d59aa..bacab56 100644
--- a/matrix/pushrules/condition.go
+++ b/matrix/pushrules/condition.go
@@ -21,14 +21,14 @@ import (
"strconv"
"strings"
- "maunium.net/go/gomatrix"
+ "maunium.net/go/mautrix"
"maunium.net/go/gomuks/lib/glob"
)
// Room is an interface with the functions that are needed for processing room-specific push conditions
type Room interface {
- GetMember(mxid string) *gomatrix.Member
- GetMembers() map[string]*gomatrix.Member
+ GetMember(mxid string) *mautrix.Member
+ GetMembers() map[string]*mautrix.Member
GetSessionOwner() string
}
@@ -59,7 +59,7 @@ type PushCondition struct {
var MemberCountFilterRegex = regexp.MustCompile("^(==|[<>]=?)?([0-9]+)$")
// Match checks if this condition is fulfilled for the given event in the given room.
-func (cond *PushCondition) Match(room Room, event *gomatrix.Event) bool {
+func (cond *PushCondition) Match(room Room, event *mautrix.Event) bool {
switch cond.Kind {
case KindEventMatch:
return cond.matchValue(room, event)
@@ -72,7 +72,7 @@ func (cond *PushCondition) Match(room Room, event *gomatrix.Event) bool {
}
}
-func (cond *PushCondition) matchValue(room Room, event *gomatrix.Event) bool {
+func (cond *PushCondition) matchValue(room Room, event *mautrix.Event) bool {
index := strings.IndexRune(cond.Key, '.')
key := cond.Key
subkey := ""
@@ -106,7 +106,7 @@ func (cond *PushCondition) matchValue(room Room, event *gomatrix.Event) bool {
}
}
-func (cond *PushCondition) matchDisplayName(room Room, event *gomatrix.Event) bool {
+func (cond *PushCondition) matchDisplayName(room Room, event *mautrix.Event) bool {
ownerID := room.GetSessionOwner()
if ownerID == event.Sender {
return false
@@ -115,7 +115,7 @@ func (cond *PushCondition) matchDisplayName(room Room, event *gomatrix.Event) bo
return strings.Contains(event.Content.Body, member.Displayname)
}
-func (cond *PushCondition) matchMemberCount(room Room, event *gomatrix.Event) bool {
+func (cond *PushCondition) matchMemberCount(room Room, event *mautrix.Event) bool {
group := MemberCountFilterRegex.FindStringSubmatch(cond.MemberCountCondition)
if len(group) != 3 {
return false
diff --git a/matrix/pushrules/condition_test.go b/matrix/pushrules/condition_test.go
index 7fd06ee..750f2c7 100644
--- a/matrix/pushrules/condition_test.go
+++ b/matrix/pushrules/condition_test.go
@@ -21,7 +21,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
- "maunium.net/go/gomatrix"
+ "maunium.net/go/mautrix"
"maunium.net/go/gomuks/matrix/pushrules"
"maunium.net/go/gomuks/matrix/rooms"
)
@@ -30,7 +30,7 @@ var (
blankTestRoom *rooms.Room
displaynameTestRoom pushrules.Room
- countConditionTestEvent *gomatrix.Event
+ countConditionTestEvent *mautrix.Event
displaynamePushCondition *pushrules.PushCondition
)
@@ -38,7 +38,7 @@ var (
func init() {
blankTestRoom = rooms.NewRoom("!fakeroom:maunium.net", "@tulir:maunium.net")
- countConditionTestEvent = &gomatrix.Event{
+ countConditionTestEvent = &mautrix.Event{
Sender: "@tulir:maunium.net",
Type: "m.room.message",
Timestamp: 1523791120,
@@ -56,8 +56,8 @@ func init() {
}
}
-func newFakeEvent(evtType string, content map[string]interface{}) *gomatrix.Event {
- return &gomatrix.Event{
+func newFakeEvent(evtType string, content map[string]interface{}) *mautrix.Event {
+ return &mautrix.Event{
Sender: "@tulir:maunium.net",
Type: evtType,
Timestamp: 1523791120,
diff --git a/matrix/pushrules/pushrules.go b/matrix/pushrules/pushrules.go
index b383c66..643f2f2 100644
--- a/matrix/pushrules/pushrules.go
+++ b/matrix/pushrules/pushrules.go
@@ -4,16 +4,16 @@ import (
"encoding/json"
"net/url"
- "maunium.net/go/gomatrix"
+ "maunium.net/go/mautrix"
)
// GetPushRules returns the push notification rules for the global scope.
-func GetPushRules(client *gomatrix.Client) (*PushRuleset, error) {
+func GetPushRules(client *mautrix.Client) (*PushRuleset, error) {
return GetScopedPushRules(client, "global")
}
// GetScopedPushRules returns the push notification rules for the given scope.
-func GetScopedPushRules(client *gomatrix.Client, scope string) (resp *PushRuleset, err error) {
+func GetScopedPushRules(client *mautrix.Client, scope string) (resp *PushRuleset, err error) {
u, _ := url.Parse(client.BuildURL("pushrules", scope))
// client.BuildURL returns the URL without a trailing slash, but the pushrules endpoint requires the slash.
u.Path += "/"
@@ -26,7 +26,7 @@ type contentWithRuleset struct {
}
// EventToPushRules converts a m.push_rules event to a PushRuleset by passing the data through JSON.
-func EventToPushRules(event *gomatrix.Event) (*PushRuleset, error) {
+func EventToPushRules(event *mautrix.Event) (*PushRuleset, error) {
content := &contentWithRuleset{}
err := json.Unmarshal(event.Content.VeryRaw, content)
if err != nil {
diff --git a/matrix/pushrules/pushrules_test.go b/matrix/pushrules/pushrules_test.go
index 09698ac..73fa787 100644
--- a/matrix/pushrules/pushrules_test.go
+++ b/matrix/pushrules/pushrules_test.go
@@ -21,7 +21,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
- "maunium.net/go/gomatrix"
+ "maunium.net/go/mautrix"
"maunium.net/go/gomuks/matrix/pushrules"
)
@@ -33,7 +33,7 @@ func init() {
}
func TestEventToPushRules(t *testing.T) {
- event := &gomatrix.Event{
+ event := &mautrix.Event{
Type: "m.push_rules",
Timestamp: 1523380910,
Content: mapExamplePushRules,
diff --git a/matrix/pushrules/rule.go b/matrix/pushrules/rule.go
index 71f71e5..62318ac 100644
--- a/matrix/pushrules/rule.go
+++ b/matrix/pushrules/rule.go
@@ -18,7 +18,7 @@ package pushrules
import (
"encoding/gob"
- "maunium.net/go/gomatrix"
+ "maunium.net/go/mautrix"
"maunium.net/go/gomuks/lib/glob"
)
@@ -28,7 +28,7 @@ func init() {
}
type PushRuleCollection interface {
- GetActions(room Room, event *gomatrix.Event) PushActionArray
+ GetActions(room Room, event *mautrix.Event) PushActionArray
}
type PushRuleArray []*PushRule
@@ -40,7 +40,7 @@ func (rules PushRuleArray) SetType(typ PushRuleType) PushRuleArray {
return rules
}
-func (rules PushRuleArray) GetActions(room Room, event *gomatrix.Event) PushActionArray {
+func (rules PushRuleArray) GetActions(room Room, event *mautrix.Event) PushActionArray {
for _, rule := range rules {
if !rule.Match(room, event) {
continue
@@ -67,7 +67,7 @@ func (rules PushRuleArray) SetTypeAndMap(typ PushRuleType) PushRuleMap {
return data
}
-func (ruleMap PushRuleMap) GetActions(room Room, event *gomatrix.Event) PushActionArray {
+func (ruleMap PushRuleMap) GetActions(room Room, event *mautrix.Event) PushActionArray {
var rule *PushRule
var found bool
switch ruleMap.Type {
@@ -122,7 +122,7 @@ type PushRule struct {
Pattern string `json:"pattern,omitempty"`
}
-func (rule *PushRule) Match(room Room, event *gomatrix.Event) bool {
+func (rule *PushRule) Match(room Room, event *mautrix.Event) bool {
if !rule.Enabled {
return false
}
@@ -140,7 +140,7 @@ func (rule *PushRule) Match(room Room, event *gomatrix.Event) bool {
}
}
-func (rule *PushRule) matchConditions(room Room, event *gomatrix.Event) bool {
+func (rule *PushRule) matchConditions(room Room, event *mautrix.Event) bool {
for _, cond := range rule.Conditions {
if !cond.Match(room, event) {
return false
@@ -149,7 +149,7 @@ func (rule *PushRule) matchConditions(room Room, event *gomatrix.Event) bool {
return true
}
-func (rule *PushRule) matchPattern(room Room, event *gomatrix.Event) bool {
+func (rule *PushRule) matchPattern(room Room, event *mautrix.Event) bool {
pattern, err := glob.Compile(rule.Pattern)
if err != nil {
return false
diff --git a/matrix/pushrules/ruleset.go b/matrix/pushrules/ruleset.go
index 940025f..366702e 100644
--- a/matrix/pushrules/ruleset.go
+++ b/matrix/pushrules/ruleset.go
@@ -19,7 +19,7 @@ package pushrules
import (
"encoding/json"
- "maunium.net/go/gomatrix"
+ "maunium.net/go/mautrix"
)
type PushRuleset struct {
@@ -80,7 +80,7 @@ var DefaultPushActions = make(PushActionArray, 0)
// GetActions matches the given event against all of the push rule
// collections in this push ruleset in the order of priority as
// specified in spec section 11.12.1.4.
-func (rs *PushRuleset) GetActions(room Room, event *gomatrix.Event) (match PushActionArray) {
+func (rs *PushRuleset) GetActions(room Room, event *mautrix.Event) (match PushActionArray) {
// Add push rule collections to array in priority order
arrays := []PushRuleCollection{rs.Override, rs.Content, rs.Room, rs.Sender, rs.Underride}
// Loop until one of the push rule collections matches the room/event combo.
diff --git a/matrix/rooms/room.go b/matrix/rooms/room.go
index bd233f8..72251c4 100644
--- a/matrix/rooms/room.go
+++ b/matrix/rooms/room.go
@@ -23,7 +23,7 @@ import (
"time"
"encoding/gob"
- "maunium.net/go/gomatrix"
+ "maunium.net/go/mautrix"
"maunium.net/go/gomuks/debug"
"os"
)
@@ -58,7 +58,7 @@ type UnreadMessage struct {
// Room represents a single Matrix room.
type Room struct {
- *gomatrix.Room
+ *mautrix.Room
// Whether or not the user has left the room.
HasLeft bool
@@ -82,10 +82,10 @@ type Room struct {
LastReceivedMessage time.Time
// MXID -> Member cache calculated from membership events.
- memberCache map[string]*gomatrix.Member
+ memberCache map[string]*mautrix.Member
// The first non-SessionUserID member in the room. Calculated at
// the same time as memberCache.
- firstMemberCache *gomatrix.Member
+ firstMemberCache *mautrix.Member
// The name of the room. Calculated from the state event name,
// canonical_alias or alias or the member cache.
nameCache string
@@ -216,31 +216,31 @@ func (room *Room) Tags() []RoomTag {
// UpdateState updates the room's current state with the given Event. This will clobber events based
// on the type/state_key combination.
-func (room *Room) UpdateState(event *gomatrix.Event) {
+func (room *Room) UpdateState(event *mautrix.Event) {
_, exists := room.State[event.Type]
if !exists {
- room.State[event.Type] = make(map[string]*gomatrix.Event)
+ room.State[event.Type] = make(map[string]*mautrix.Event)
}
switch event.Type {
- case gomatrix.StateRoomName:
+ case mautrix.StateRoomName:
room.nameCache = ""
- case gomatrix.StateCanonicalAlias:
+ case mautrix.StateCanonicalAlias:
if room.nameCacheSource >= CanonicalAliasRoomName {
room.nameCache = ""
}
room.canonicalAliasCache = ""
- case gomatrix.StateAliases:
+ case mautrix.StateAliases:
if room.nameCacheSource >= AliasRoomName {
room.nameCache = ""
}
room.aliasesCache = nil
- case gomatrix.StateMember:
+ case mautrix.StateMember:
room.memberCache = nil
room.firstMemberCache = nil
if room.nameCacheSource >= MemberRoomName {
room.nameCache = ""
}
- case gomatrix.StateTopic:
+ case mautrix.StateTopic:
room.topicCache = ""
}
@@ -248,7 +248,7 @@ func (room *Room) UpdateState(event *gomatrix.Event) {
if event.StateKey != nil {
stateKey = *event.StateKey
}
- if event.Type != gomatrix.StateMember {
+ if event.Type != mautrix.StateMember {
debug.Printf("Updating state %s#%s for %s", event.Type, stateKey, room.ID)
}
@@ -260,14 +260,14 @@ func (room *Room) UpdateState(event *gomatrix.Event) {
}
// GetStateEvent returns the state event for the given type/state_key combo, or nil.
-func (room *Room) GetStateEvent(eventType gomatrix.EventType, stateKey string) *gomatrix.Event {
+func (room *Room) GetStateEvent(eventType mautrix.EventType, stateKey string) *mautrix.Event {
stateEventMap, _ := room.State[eventType]
event, _ := stateEventMap[stateKey]
return event
}
// GetStateEvents returns the state events for the given type.
-func (room *Room) GetStateEvents(eventType gomatrix.EventType) map[string]*gomatrix.Event {
+func (room *Room) GetStateEvents(eventType mautrix.EventType) map[string]*mautrix.Event {
stateEventMap, _ := room.State[eventType]
return stateEventMap
}
@@ -275,7 +275,7 @@ func (room *Room) GetStateEvents(eventType gomatrix.EventType) map[string]*gomat
// GetTopic returns the topic of the room.
func (room *Room) GetTopic() string {
if len(room.topicCache) == 0 {
- topicEvt := room.GetStateEvent(gomatrix.StateTopic, "")
+ topicEvt := room.GetStateEvent(mautrix.StateTopic, "")
if topicEvt != nil {
room.topicCache = topicEvt.Content.Topic
}
@@ -285,7 +285,7 @@ func (room *Room) GetTopic() string {
func (room *Room) GetCanonicalAlias() string {
if len(room.canonicalAliasCache) == 0 {
- canonicalAliasEvt := room.GetStateEvent(gomatrix.StateCanonicalAlias, "")
+ canonicalAliasEvt := room.GetStateEvent(mautrix.StateCanonicalAlias, "")
if canonicalAliasEvt != nil {
room.canonicalAliasCache = canonicalAliasEvt.Content.Alias
} else {
@@ -301,7 +301,7 @@ func (room *Room) GetCanonicalAlias() string {
// GetAliases returns the list of aliases that point to this room.
func (room *Room) GetAliases() []string {
if room.aliasesCache == nil {
- aliasEvents := room.GetStateEvents(gomatrix.StateAliases)
+ aliasEvents := room.GetStateEvents(mautrix.StateAliases)
room.aliasesCache = []string{}
for _, event := range aliasEvents {
room.aliasesCache = append(room.aliasesCache, event.Content.Aliases...)
@@ -312,7 +312,7 @@ func (room *Room) GetAliases() []string {
// updateNameFromNameEvent updates the room display name to be the name set in the name event.
func (room *Room) updateNameFromNameEvent() {
- nameEvt := room.GetStateEvent(gomatrix.StateRoomName, "")
+ nameEvt := room.GetStateEvent(mautrix.StateRoomName, "")
if nameEvt != nil {
room.nameCache = nameEvt.Content.Name
}
@@ -384,9 +384,9 @@ func (room *Room) GetTitle() string {
}
// createMemberCache caches all member events into a easily processable MXID -> *Member map.
-func (room *Room) createMemberCache() map[string]*gomatrix.Member {
- cache := make(map[string]*gomatrix.Member)
- events := room.GetStateEvents(gomatrix.StateMember)
+func (room *Room) createMemberCache() map[string]*mautrix.Member {
+ cache := make(map[string]*mautrix.Member)
+ events := room.GetStateEvents(mautrix.StateMember)
room.firstMemberCache = nil
if events != nil {
for userID, event := range events {
@@ -407,7 +407,7 @@ func (room *Room) createMemberCache() map[string]*gomatrix.Member {
//
// The members are returned from the cache.
// If the cache is empty, it is updated first.
-func (room *Room) GetMembers() map[string]*gomatrix.Member {
+func (room *Room) GetMembers() map[string]*mautrix.Member {
if len(room.memberCache) == 0 || room.firstMemberCache == nil {
room.createMemberCache()
}
@@ -416,7 +416,7 @@ func (room *Room) GetMembers() map[string]*gomatrix.Member {
// GetMember returns the member with the given MXID.
// If the member doesn't exist, nil is returned.
-func (room *Room) GetMember(userID string) *gomatrix.Member {
+func (room *Room) GetMember(userID string) *mautrix.Member {
if len(room.memberCache) == 0 {
room.createMemberCache()
}
@@ -432,7 +432,7 @@ func (room *Room) GetSessionOwner() string {
// NewRoom creates a new Room with the given ID
func NewRoom(roomID, owner string) *Room {
return &Room{
- Room: gomatrix.NewRoom(roomID),
+ Room: mautrix.NewRoom(roomID),
fetchHistoryLock: &sync.Mutex{},
SessionUserID: owner,
}
diff --git a/matrix/rooms/room_test.go b/matrix/rooms/room_test.go
index 1fabdcc..db7f586 100644
--- a/matrix/rooms/room_test.go
+++ b/matrix/rooms/room_test.go
@@ -21,7 +21,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
- "maunium.net/go/gomatrix"
+ "maunium.net/go/mautrix"
"maunium.net/go/gomuks/matrix/rooms"
)
@@ -39,7 +39,7 @@ func TestNewRoom_DefaultValues(t *testing.T) {
func TestRoom_GetCanonicalAlias(t *testing.T) {
room := rooms.NewRoom("!test:maunium.net", "@tulir:maunium.net")
- room.UpdateState(&gomatrix.Event{
+ room.UpdateState(&mautrix.Event{
Type: "m.room.canonical_alias",
Content: map[string]interface{}{
"alias": "#foo:maunium.net",
@@ -50,7 +50,7 @@ func TestRoom_GetCanonicalAlias(t *testing.T) {
func TestRoom_GetTopic(t *testing.T) {
room := rooms.NewRoom("!test:maunium.net", "@tulir:maunium.net")
- room.UpdateState(&gomatrix.Event{
+ room.UpdateState(&mautrix.Event{
Type: "m.room.topic",
Content: map[string]interface{}{
"topic": "test topic",
@@ -87,7 +87,7 @@ func TestRoom_GetAliases(t *testing.T) {
}
func addName(room *rooms.Room) {
- room.UpdateState(&gomatrix.Event{
+ room.UpdateState(&mautrix.Event{
Type: "m.room.name",
Content: map[string]interface{}{
"name": "Test room",
@@ -96,7 +96,7 @@ func addName(room *rooms.Room) {
}
func addCanonicalAlias(room *rooms.Room) {
- room.UpdateState(&gomatrix.Event{
+ room.UpdateState(&mautrix.Event{
Type: "m.room.canonical_alias",
Content: map[string]interface{}{
"alias": "#foo:maunium.net",
@@ -106,7 +106,7 @@ func addCanonicalAlias(room *rooms.Room) {
func addAliases(room *rooms.Room) {
server1 := "maunium.net"
- room.UpdateState(&gomatrix.Event{
+ room.UpdateState(&mautrix.Event{
Type: "m.room.aliases",
StateKey: &server1,
Content: map[string]interface{}{
@@ -115,7 +115,7 @@ func addAliases(room *rooms.Room) {
})
server2 := "matrix.org"
- room.UpdateState(&gomatrix.Event{
+ room.UpdateState(&mautrix.Event{
Type: "m.room.aliases",
StateKey: &server2,
Content: map[string]interface{}{
@@ -126,7 +126,7 @@ func addAliases(room *rooms.Room) {
func addMembers(room *rooms.Room, count int) {
user1 := "@tulir:maunium.net"
- room.UpdateState(&gomatrix.Event{
+ room.UpdateState(&mautrix.Event{
Type: "m.room.member",
StateKey: &user1,
Content: map[string]interface{}{
@@ -146,7 +146,7 @@ func addMembers(room *rooms.Room, count int) {
if i%5 == 0 {
content["membership"] = "invite"
}
- room.UpdateState(&gomatrix.Event{
+ room.UpdateState(&mautrix.Event{
Type: "m.room.member",
StateKey: &userN,
Content: content,
diff --git a/matrix/sync.go b/matrix/sync.go
index 6d5def7..06c7f47 100644
--- a/matrix/sync.go
+++ b/matrix/sync.go
@@ -14,15 +14,16 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
-// Based on https://github.com/matrix-org/gomatrix/blob/master/sync.go
+// Based on https://github.com/matrix-org/mautrix/blob/master/sync.go
package matrix
import (
"encoding/json"
+ "maunium.net/go/gomuks/debug"
"time"
- "maunium.net/go/gomatrix"
+ "maunium.net/go/mautrix"
"maunium.net/go/gomuks/matrix/rooms"
)
@@ -44,14 +45,14 @@ const (
EventSourceEphemeral
)
-type EventHandler func(source EventSource, event *gomatrix.Event)
+type EventHandler func(source EventSource, event *mautrix.Event)
// GomuksSyncer is the default syncing implementation. You can either write your own syncer, or selectively
// replace parts of this default syncer (e.g. the ProcessResponse method). The default syncer uses the observer
// pattern to notify callers about incoming events. See GomuksSyncer.OnEventType for more information.
type GomuksSyncer struct {
Session SyncerSession
- listeners map[gomatrix.EventType][]EventHandler // event type to listeners array
+ listeners map[mautrix.EventType][]EventHandler // event type to listeners array
FirstSyncDone bool
InitDoneCallback func()
}
@@ -60,13 +61,14 @@ type GomuksSyncer struct {
func NewGomuksSyncer(session SyncerSession) *GomuksSyncer {
return &GomuksSyncer{
Session: session,
- listeners: make(map[gomatrix.EventType][]EventHandler),
+ listeners: make(map[mautrix.EventType][]EventHandler),
FirstSyncDone: false,
}
}
// ProcessResponse processes a Matrix sync response.
-func (s *GomuksSyncer) ProcessResponse(res *gomatrix.RespSync, since string) (err error) {
+func (s *GomuksSyncer) ProcessResponse(res *mautrix.RespSync, since string) (err error) {
+ debug.Print("Received sync response")
s.processSyncEvents(nil, res.Presence.Events, EventSourcePresence, false)
s.processSyncEvents(nil, res.AccountData.Events, EventSourceAccountData, false)
@@ -106,7 +108,7 @@ func (s *GomuksSyncer) ProcessResponse(res *gomatrix.RespSync, since string) (er
return
}
-func (s *GomuksSyncer) processSyncEvents(room *rooms.Room, events []*gomatrix.Event, source EventSource, checkStateKey bool) {
+func (s *GomuksSyncer) processSyncEvents(room *rooms.Room, events []*mautrix.Event, source EventSource, checkStateKey bool) {
for _, event := range events {
if !checkStateKey || event.StateKey != nil {
s.processSyncEvent(room, event, source)
@@ -114,11 +116,11 @@ func (s *GomuksSyncer) processSyncEvents(room *rooms.Room, events []*gomatrix.Ev
}
}
-func (s *GomuksSyncer) processSyncEvent(room *rooms.Room, event *gomatrix.Event, source EventSource) {
+func (s *GomuksSyncer) processSyncEvent(room *rooms.Room, event *mautrix.Event, source EventSource) {
if room != nil {
event.RoomID = room.ID
}
- if event.Type.Class == gomatrix.StateEventType {
+ if event.Type.Class == mautrix.StateEventType {
room.UpdateState(event)
}
s.notifyListeners(source, event)
@@ -126,7 +128,7 @@ func (s *GomuksSyncer) processSyncEvent(room *rooms.Room, event *gomatrix.Event,
// OnEventType allows callers to be notified when there are new events for the given event type.
// There are no duplicate checks.
-func (s *GomuksSyncer) OnEventType(eventType gomatrix.EventType, callback EventHandler) {
+func (s *GomuksSyncer) OnEventType(eventType mautrix.EventType, callback EventHandler) {
_, exists := s.listeners[eventType]
if !exists {
s.listeners[eventType] = []EventHandler{}
@@ -134,7 +136,7 @@ func (s *GomuksSyncer) OnEventType(eventType gomatrix.EventType, callback EventH
s.listeners[eventType] = append(s.listeners[eventType], callback)
}
-func (s *GomuksSyncer) notifyListeners(source EventSource, event *gomatrix.Event) {
+func (s *GomuksSyncer) notifyListeners(source EventSource, event *mautrix.Event) {
listeners, exists := s.listeners[event.Type]
if !exists {
return
@@ -145,16 +147,17 @@ func (s *GomuksSyncer) notifyListeners(source EventSource, event *gomatrix.Event
}
// OnFailedSync always returns a 10 second wait period between failed /syncs, never a fatal error.
-func (s *GomuksSyncer) OnFailedSync(res *gomatrix.RespSync, err error) (time.Duration, error) {
+func (s *GomuksSyncer) OnFailedSync(res *mautrix.RespSync, err error) (time.Duration, error) {
+ debug.Printf("Sync failed: %v", err)
return 10 * time.Second, nil
}
// GetFilterJSON returns a filter with a timeline limit of 50.
func (s *GomuksSyncer) GetFilterJSON(userID string) json.RawMessage {
- filter := &gomatrix.Filter{
- Room: gomatrix.RoomFilter{
+ filter := &mautrix.Filter{
+ Room: mautrix.RoomFilter{
IncludeLeave: false,
- State: gomatrix.FilterPart{
+ State: mautrix.FilterPart{
Types: []string{
"m.room.member",
"m.room.name",
@@ -163,21 +166,21 @@ func (s *GomuksSyncer) GetFilterJSON(userID string) json.RawMessage {
"m.room.aliases",
},
},
- Timeline: gomatrix.FilterPart{
+ Timeline: mautrix.FilterPart{
Types: []string{"m.room.message", "m.room.member"},
Limit: 50,
},
- Ephemeral: gomatrix.FilterPart{
+ Ephemeral: mautrix.FilterPart{
Types: []string{"m.typing", "m.receipt"},
},
- AccountData: gomatrix.FilterPart{
+ AccountData: mautrix.FilterPart{
Types: []string{"m.tag"},
},
},
- AccountData: gomatrix.FilterPart{
+ AccountData: mautrix.FilterPart{
Types: []string{"m.push_rules", "m.direct", "net.maunium.gomuks.preferences"},
},
- Presence: gomatrix.FilterPart{
+ Presence: mautrix.FilterPart{
Types: []string{},
},
}
diff --git a/matrix/sync_test.go b/matrix/sync_test.go
index 49bbcb0..79cb11b 100644
--- a/matrix/sync_test.go
+++ b/matrix/sync_test.go
@@ -20,7 +20,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
- "maunium.net/go/gomatrix"
+ "maunium.net/go/mautrix"
"maunium.net/go/gomuks/matrix"
"maunium.net/go/gomuks/matrix/rooms"
)
@@ -42,13 +42,13 @@ func TestGomuksSyncer_ProcessResponse(t *testing.T) {
userID: "@tulir:maunium.net",
rooms: map[string]*rooms.Room{
"!foo:maunium.net": {
- Room: gomatrix.NewRoom("!foo:maunium.net"),
+ Room: mautrix.NewRoom("!foo:maunium.net"),
},
"!bar:maunium.net": {
- Room: gomatrix.NewRoom("!bar:maunium.net"),
+ Room: mautrix.NewRoom("!bar:maunium.net"),
},
"!test:maunium.net": {
- Room: gomatrix.NewRoom("!test:maunium.net"),
+ Room: mautrix.NewRoom("!test:maunium.net"),
},
},
}
@@ -58,7 +58,7 @@ func TestGomuksSyncer_ProcessResponse(t *testing.T) {
syncer.OnEventType("m.room.message", ml.receive)
syncer.GetFilterJSON("@tulir:maunium.net")
- joinEvt := &gomatrix.Event{
+ joinEvt := &mautrix.Event{
ID: "!join:maunium.net",
Type: "m.room.member",
Sender: "@tulir:maunium.net",
@@ -67,7 +67,7 @@ func TestGomuksSyncer_ProcessResponse(t *testing.T) {
"membership": "join",
},
}
- messageEvt := &gomatrix.Event{
+ messageEvt := &mautrix.Event{
ID: "!msg:maunium.net",
Type: "m.room.message",
Content: map[string]interface{}{
@@ -75,11 +75,11 @@ func TestGomuksSyncer_ProcessResponse(t *testing.T) {
"msgtype": "m.text",
},
}
- unhandledEvt := &gomatrix.Event{
+ unhandledEvt := &mautrix.Event{
ID: "!unhandled:maunium.net",
Type: "m.room.unhandled_event",
}
- inviteEvt := &gomatrix.Event{
+ inviteEvt := &mautrix.Event{
ID: "!invite:matrix.org",
Type: "m.room.member",
Sender: "@you:matrix.org",
@@ -88,7 +88,7 @@ func TestGomuksSyncer_ProcessResponse(t *testing.T) {
"membership": "invite",
},
}
- leaveEvt := &gomatrix.Event{
+ leaveEvt := &mautrix.Event{
ID: "!leave:matrix.org",
Type: "m.room.member",
Sender: "@you:matrix.org",
@@ -100,27 +100,27 @@ func TestGomuksSyncer_ProcessResponse(t *testing.T) {
resp := newRespSync()
resp.Rooms.Join["!foo:maunium.net"] = join{
- State: events{Events: []*gomatrix.Event{joinEvt}},
- Timeline: timeline{Events: []*gomatrix.Event{messageEvt, unhandledEvt}},
+ State: events{Events: []*mautrix.Event{joinEvt}},
+ Timeline: timeline{Events: []*mautrix.Event{messageEvt, unhandledEvt}},
}
resp.Rooms.Invite["!bar:maunium.net"] = struct {
State struct {
- Events []*gomatrix.Event `json:"events"`
+ Events []*mautrix.Event `json:"events"`
} `json:"invite_state"`
}{
- State: events{Events: []*gomatrix.Event{inviteEvt}},
+ State: events{Events: []*mautrix.Event{inviteEvt}},
}
resp.Rooms.Leave["!test:maunium.net"] = struct {
State struct {
- Events []*gomatrix.Event `json:"events"`
+ Events []*mautrix.Event `json:"events"`
} `json:"state"`
Timeline struct {
- Events []*gomatrix.Event `json:"events"`
+ Events []*mautrix.Event `json:"events"`
Limited bool `json:"limited"`
PrevBatch string `json:"prev_batch"`
} `json:"timeline"`
}{
- State: events{Events: []*gomatrix.Event{leaveEvt}},
+ State: events{Events: []*mautrix.Event{leaveEvt}},
}
syncer.ProcessResponse(resp, "since")
@@ -145,28 +145,28 @@ func (mss *mockSyncerSession) GetUserID() string {
}
type events struct {
- Events []*gomatrix.Event `json:"events"`
+ Events []*mautrix.Event `json:"events"`
}
type timeline struct {
- Events []*gomatrix.Event `json:"events"`
+ Events []*mautrix.Event `json:"events"`
Limited bool `json:"limited"`
PrevBatch string `json:"prev_batch"`
}
type join struct {
State struct {
- Events []*gomatrix.Event `json:"events"`
+ Events []*mautrix.Event `json:"events"`
} `json:"state"`
Timeline struct {
- Events []*gomatrix.Event `json:"events"`
+ Events []*mautrix.Event `json:"events"`
Limited bool `json:"limited"`
PrevBatch string `json:"prev_batch"`
} `json:"timeline"`
Ephemeral struct {
- Events []*gomatrix.Event `json:"events"`
+ Events []*mautrix.Event `json:"events"`
} `json:"ephemeral"`
AccountData struct {
- Events []*gomatrix.Event `json:"events"`
+ Events []*mautrix.Event `json:"events"`
} `json:"account_data"`
}
@@ -175,42 +175,42 @@ func ptr(text string) *string {
}
type mockListener struct {
- received []*gomatrix.Event
+ received []*mautrix.Event
}
-func (ml *mockListener) receive(source matrix.EventSource, evt *gomatrix.Event) {
+func (ml *mockListener) receive(source matrix.EventSource, evt *mautrix.Event) {
ml.received = append(ml.received, evt)
}
-func newRespSync() *gomatrix.RespSync {
- resp := &gomatrix.RespSync{NextBatch: "123"}
+func newRespSync() *mautrix.RespSync {
+ resp := &mautrix.RespSync{NextBatch: "123"}
resp.Rooms.Join = make(map[string]struct {
State struct {
- Events []*gomatrix.Event `json:"events"`
+ Events []*mautrix.Event `json:"events"`
} `json:"state"`
Timeline struct {
- Events []*gomatrix.Event `json:"events"`
+ Events []*mautrix.Event `json:"events"`
Limited bool `json:"limited"`
PrevBatch string `json:"prev_batch"`
} `json:"timeline"`
Ephemeral struct {
- Events []*gomatrix.Event `json:"events"`
+ Events []*mautrix.Event `json:"events"`
} `json:"ephemeral"`
AccountData struct {
- Events []*gomatrix.Event `json:"events"`
+ Events []*mautrix.Event `json:"events"`
} `json:"account_data"`
})
resp.Rooms.Invite = make(map[string]struct {
State struct {
- Events []*gomatrix.Event `json:"events"`
+ Events []*mautrix.Event `json:"events"`
} `json:"invite_state"`
})
resp.Rooms.Leave = make(map[string]struct {
State struct {
- Events []*gomatrix.Event `json:"events"`
+ Events []*mautrix.Event `json:"events"`
} `json:"state"`
Timeline struct {
- Events []*gomatrix.Event `json:"events"`
+ Events []*mautrix.Event `json:"events"`
Limited bool `json:"limited"`
PrevBatch string `json:"prev_batch"`
} `json:"timeline"`
diff --git a/ui/commands.go b/ui/commands.go
index b80fa07..0feda29 100644
--- a/ui/commands.go
+++ b/ui/commands.go
@@ -19,7 +19,7 @@ package ui
import (
"encoding/json"
"fmt"
- "maunium.net/go/gomatrix"
+ "maunium.net/go/mautrix"
"strings"
"unicode"
@@ -133,7 +133,7 @@ func cmdSendEvent(cmd *Command) {
return
}
roomID := cmd.Args[0]
- eventType := gomatrix.NewEventType(cmd.Args[1])
+ eventType := mautrix.NewEventType(cmd.Args[1])
rawContent := strings.Join(cmd.Args[2:], "")
debug.Print(roomID, eventType, rawContent)
@@ -162,7 +162,7 @@ func cmdSetState(cmd *Command) {
}
roomID := cmd.Args[0]
- eventType := gomatrix.NewEventType(cmd.Args[1])
+ eventType := mautrix.NewEventType(cmd.Args[1])
stateKey := cmd.Args[2]
if stateKey == "-" {
stateKey = ""
diff --git a/ui/messages/base.go b/ui/messages/base.go
index c9da389..441aca1 100644
--- a/ui/messages/base.go
+++ b/ui/messages/base.go
@@ -18,7 +18,7 @@ package messages
import (
"encoding/gob"
- "maunium.net/go/gomatrix"
+ "maunium.net/go/mautrix"
"time"
"maunium.net/go/gomuks/config"
@@ -34,7 +34,7 @@ func init() {
type BaseMessage struct {
MsgID string
- MsgType gomatrix.MessageType
+ MsgType mautrix.MessageType
MsgSenderID string
MsgSender string
MsgSenderColor tcell.Color
@@ -48,7 +48,7 @@ type BaseMessage struct {
prevPrefs config.UserPreferences
}
-func newBaseMessage(id, sender, displayname string, msgtype gomatrix.MessageType, timestamp time.Time) BaseMessage {
+func newBaseMessage(id, sender, displayname string, msgtype mautrix.MessageType, timestamp time.Time) BaseMessage {
return BaseMessage{
MsgSenderID: sender,
MsgSender: displayname,
@@ -195,11 +195,11 @@ func (msg *BaseMessage) SetID(id string) {
msg.MsgID = id
}
-func (msg *BaseMessage) Type() gomatrix.MessageType {
+func (msg *BaseMessage) Type() mautrix.MessageType {
return msg.MsgType
}
-func (msg *BaseMessage) SetType(msgtype gomatrix.MessageType) {
+func (msg *BaseMessage) SetType(msgtype mautrix.MessageType) {
msg.MsgType = msgtype
}
diff --git a/ui/messages/expandedtextmessage.go b/ui/messages/expandedtextmessage.go
index 2e77a24..fbb373d 100644
--- a/ui/messages/expandedtextmessage.go
+++ b/ui/messages/expandedtextmessage.go
@@ -18,7 +18,7 @@ package messages
import (
"encoding/gob"
- "maunium.net/go/gomatrix"
+ "maunium.net/go/mautrix"
"time"
"maunium.net/go/gomuks/config"
@@ -35,7 +35,7 @@ type ExpandedTextMessage struct {
}
// NewExpandedTextMessage creates a new ExpandedTextMessage object with the provided values and the default state.
-func NewExpandedTextMessage(id, sender, displayname string, msgtype gomatrix.MessageType, text tstring.TString, timestamp time.Time) UIMessage {
+func NewExpandedTextMessage(id, sender, displayname string, msgtype mautrix.MessageType, text tstring.TString, timestamp time.Time) UIMessage {
return &ExpandedTextMessage{
BaseMessage: newBaseMessage(id, sender, displayname, msgtype, timestamp),
MsgText: text,
diff --git a/ui/messages/imagemessage.go b/ui/messages/imagemessage.go
index a17c842..8ccff67 100644
--- a/ui/messages/imagemessage.go
+++ b/ui/messages/imagemessage.go
@@ -20,7 +20,7 @@ import (
"bytes"
"encoding/gob"
"fmt"
- "maunium.net/go/gomatrix"
+ "maunium.net/go/mautrix"
"time"
"image/color"
@@ -48,7 +48,7 @@ type ImageMessage struct {
}
// NewImageMessage creates a new ImageMessage object with the provided values and the default state.
-func NewImageMessage(matrix ifc.MatrixContainer, id, sender, displayname string, msgtype gomatrix.MessageType, body, homeserver, fileID string, data []byte, timestamp time.Time) UIMessage {
+func NewImageMessage(matrix ifc.MatrixContainer, id, sender, displayname string, msgtype mautrix.MessageType, body, homeserver, fileID string, data []byte, timestamp time.Time) UIMessage {
return &ImageMessage{
newBaseMessage(id, sender, displayname, msgtype, timestamp),
body,
diff --git a/ui/messages/parser/htmlparser.go b/ui/messages/parser/htmlparser.go
index bcde14d..b2deeba 100644
--- a/ui/messages/parser/htmlparser.go
+++ b/ui/messages/parser/htmlparser.go
@@ -24,10 +24,10 @@ import (
"github.com/lucasb-eyer/go-colorful"
"golang.org/x/net/html"
- "maunium.net/go/gomatrix"
"maunium.net/go/gomuks/matrix/rooms"
"maunium.net/go/gomuks/ui/messages/tstring"
"maunium.net/go/gomuks/ui/widget"
+ "maunium.net/go/mautrix"
"maunium.net/go/tcell"
"strconv"
)
@@ -69,6 +69,9 @@ func (parser *htmlParser) getAttribute(node *html.Node, attribute string) string
}
func digits(num int) int {
+ if num <= 0 {
+ return 0
+ }
return int(math.Floor(math.Log10(float64(num))) + 1)
}
@@ -270,14 +273,14 @@ func (parser *htmlParser) Parse(htmlData string) tstring.TString {
}
// ParseHTMLMessage parses a HTML-formatted Matrix event into a UIMessage.
-func ParseHTMLMessage(room *rooms.Room, evt *gomatrix.Event, senderDisplayname string) tstring.TString {
+func ParseHTMLMessage(room *rooms.Room, evt *mautrix.Event, senderDisplayname string) tstring.TString {
htmlData := evt.Content.FormattedBody
htmlData = strings.Replace(htmlData, "\t", " ", -1)
parser := htmlParser{room}
str := parser.Parse(htmlData)
- if evt.Content.MsgType == gomatrix.MsgEmote {
+ if evt.Content.MsgType == mautrix.MsgEmote {
str = tstring.Join([]tstring.TString{
tstring.NewTString("* "),
tstring.NewColorTString(senderDisplayname, widget.GetHashColor(evt.Sender)),
diff --git a/ui/messages/parser/parser.go b/ui/messages/parser/parser.go
index 1d7ced3..ef6578f 100644
--- a/ui/messages/parser/parser.go
+++ b/ui/messages/parser/parser.go
@@ -18,24 +18,28 @@ package parser
import (
"fmt"
+ "html"
"strings"
"time"
- "maunium.net/go/gomatrix"
"maunium.net/go/gomuks/debug"
"maunium.net/go/gomuks/interface"
"maunium.net/go/gomuks/matrix/rooms"
"maunium.net/go/gomuks/ui/messages"
"maunium.net/go/gomuks/ui/messages/tstring"
"maunium.net/go/gomuks/ui/widget"
+ "maunium.net/go/mautrix"
"maunium.net/go/tcell"
)
-func ParseEvent(matrix ifc.MatrixContainer, room *rooms.Room, evt *gomatrix.Event) messages.UIMessage {
+func ParseEvent(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.Event) messages.UIMessage {
switch evt.Type {
- case gomatrix.EventMessage:
+ case mautrix.EventSticker:
+ evt.Content.MsgType = mautrix.MsgImage
+ fallthrough
+ case mautrix.EventMessage:
return ParseMessage(matrix, room, evt)
- case gomatrix.StateMember:
+ case mautrix.StateMember:
return ParseMembershipEvent(room, evt)
}
return nil
@@ -49,16 +53,27 @@ func unixToTime(unix int64) time.Time {
return timestamp
}
-func ParseMessage(matrix ifc.MatrixContainer, room *rooms.Room, evt *gomatrix.Event) messages.UIMessage {
+func ParseMessage(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.Event) messages.UIMessage {
displayname := evt.Sender
member := room.GetMember(evt.Sender)
if member != nil {
displayname = member.Displayname
}
+ if len(evt.Content.GetReplyTo()) > 0 {
+ evt.Content.RemoveReplyFallback()
+ replyToEvt, _ := matrix.Client().GetEvent(room.ID, evt.Content.GetReplyTo())
+ replyToEvt.Content.RemoveReplyFallback()
+ if len(replyToEvt.Content.FormattedBody) == 0 {
+ replyToEvt.Content.FormattedBody = html.EscapeString(replyToEvt.Content.Body)
+ }
+ evt.Content.FormattedBody = fmt.Sprintf(
+ "In reply to <a href='https://matrix.to/#/%[1]s'>%[1]s</a><blockquote>%[2]s</blockquote><br/>%[3]s",
+ replyToEvt.Sender, replyToEvt.Content.FormattedBody, evt.Content.FormattedBody)
+ }
ts := unixToTime(evt.Timestamp)
switch evt.Content.MsgType {
case "m.text", "m.notice", "m.emote":
- if evt.Content.Format == gomatrix.FormatHTML {
+ if evt.Content.Format == mautrix.FormatHTML {
text := ParseHTMLMessage(room, evt, displayname)
return messages.NewExpandedTextMessage(evt.ID, evt.Sender, displayname, evt.Content.MsgType, text, ts)
}
@@ -74,7 +89,7 @@ func ParseMessage(matrix ifc.MatrixContainer, room *rooms.Room, evt *gomatrix.Ev
return nil
}
-func getMembershipChangeMessage(evt *gomatrix.Event, membership, prevMembership gomatrix.Membership, senderDisplayname, displayname, prevDisplayname string) (sender string, text tstring.TString) {
+func getMembershipChangeMessage(evt *mautrix.Event, membership, prevMembership mautrix.Membership, senderDisplayname, displayname, prevDisplayname string) (sender string, text tstring.TString) {
switch membership {
case "invite":
sender = "---"
@@ -88,7 +103,7 @@ func getMembershipChangeMessage(evt *gomatrix.Event, membership, prevMembership
case "leave":
sender = "<--"
if evt.Sender != *evt.StateKey {
- if prevMembership == gomatrix.MembershipBan {
+ if prevMembership == mautrix.MembershipBan {
text = tstring.NewColorTString(fmt.Sprintf("%s unbanned %s", senderDisplayname, displayname), tcell.ColorGreen)
text.Colorize(len(senderDisplayname)+len(" unbanned "), len(displayname), widget.GetHashColor(*evt.StateKey))
} else {
@@ -111,7 +126,7 @@ func getMembershipChangeMessage(evt *gomatrix.Event, membership, prevMembership
return
}
-func getMembershipEventContent(room *rooms.Room, evt *gomatrix.Event) (sender string, text tstring.TString) {
+func getMembershipEventContent(room *rooms.Room, evt *mautrix.Event) (sender string, text tstring.TString) {
member := room.GetMember(evt.Sender)
senderDisplayname := evt.Sender
if member != nil {
@@ -124,7 +139,7 @@ func getMembershipEventContent(room *rooms.Room, evt *gomatrix.Event) (sender st
displayname = *evt.StateKey
}
- prevMembership := gomatrix.MembershipLeave
+ prevMembership := mautrix.MembershipLeave
prevDisplayname := *evt.StateKey
if evt.Unsigned.PrevContent != nil {
prevMembership = evt.Unsigned.PrevContent.Membership
@@ -146,7 +161,7 @@ func getMembershipEventContent(room *rooms.Room, evt *gomatrix.Event) (sender st
return
}
-func ParseMembershipEvent(room *rooms.Room, evt *gomatrix.Event) messages.UIMessage {
+func ParseMembershipEvent(room *rooms.Room, evt *mautrix.Event) messages.UIMessage {
displayname, text := getMembershipEventContent(room, evt)
if len(text) == 0 {
return nil
diff --git a/ui/messages/textmessage.go b/ui/messages/textmessage.go
index d5ce324..8622c32 100644
--- a/ui/messages/textmessage.go
+++ b/ui/messages/textmessage.go
@@ -19,7 +19,7 @@ package messages
import (
"encoding/gob"
"fmt"
- "maunium.net/go/gomatrix"
+ "maunium.net/go/mautrix"
"time"
"maunium.net/go/gomuks/config"
@@ -38,7 +38,7 @@ type TextMessage struct {
}
// NewTextMessage creates a new UITextMessage object with the provided values and the default state.
-func NewTextMessage(id, sender, displayname string, msgtype gomatrix.MessageType, text string, timestamp time.Time) UIMessage {
+func NewTextMessage(id, sender, displayname string, msgtype mautrix.MessageType, text string, timestamp time.Time) UIMessage {
return &TextMessage{
BaseMessage: newBaseMessage(id, sender, displayname, msgtype, timestamp),
MsgText: text,
@@ -58,7 +58,7 @@ func (msg *TextMessage) getCache() tstring.TString {
return msg.cache
}
-func (msg *TextMessage) SetType(msgtype gomatrix.MessageType) {
+func (msg *TextMessage) SetType(msgtype mautrix.MessageType) {
msg.BaseMessage.SetType(msgtype)
msg.cache = nil
}
diff --git a/ui/room-view.go b/ui/room-view.go
index 4b2cd48..6eda559 100644
--- a/ui/room-view.go
+++ b/ui/room-view.go
@@ -18,7 +18,7 @@ package ui
import (
"fmt"
- "maunium.net/go/gomatrix"
+ "maunium.net/go/mautrix"
"path/filepath"
"sort"
"strconv"
@@ -347,7 +347,7 @@ func (view *RoomView) UpdateUserList() {
}
}
-func (view *RoomView) newUIMessage(id, sender string, msgtype gomatrix.MessageType, text string, timestamp time.Time) messages.UIMessage {
+func (view *RoomView) newUIMessage(id, sender string, msgtype mautrix.MessageType, text string, timestamp time.Time) messages.UIMessage {
member := view.Room.GetMember(sender)
displayname := sender
if member != nil {
@@ -357,11 +357,11 @@ func (view *RoomView) newUIMessage(id, sender string, msgtype gomatrix.MessageTy
return msg
}
-func (view *RoomView) NewMessage(id, sender string, msgtype gomatrix.MessageType, text string, timestamp time.Time) ifc.Message {
+func (view *RoomView) NewMessage(id, sender string, msgtype mautrix.MessageType, text string, timestamp time.Time) ifc.Message {
return view.newUIMessage(id, sender, msgtype, text, timestamp)
}
-func (view *RoomView) NewTempMessage(msgtype gomatrix.MessageType, text string) ifc.Message {
+func (view *RoomView) NewTempMessage(msgtype mautrix.MessageType, text string) ifc.Message {
now := time.Now()
id := strconv.FormatInt(now.UnixNano(), 10)
sender := ""
diff --git a/ui/view-login.go b/ui/view-login.go
index a900870..3d42506 100644
--- a/ui/view-login.go
+++ b/ui/view-login.go
@@ -17,11 +17,11 @@
package ui
import (
- "maunium.net/go/gomatrix"
"maunium.net/go/gomuks/config"
"maunium.net/go/gomuks/debug"
"maunium.net/go/gomuks/interface"
"maunium.net/go/gomuks/ui/widget"
+ "maunium.net/go/mautrix"
"maunium.net/go/tview"
)
@@ -89,8 +89,8 @@ func (view *LoginView) Login() {
debug.Print("Init error:", err)
err = view.matrix.Login(mxid, password)
if err != nil {
- if httpErr, ok := err.(gomatrix.HTTPError); ok {
- if respErr, ok := httpErr.WrappedError.(gomatrix.RespError); ok {
+ if httpErr, ok := err.(mautrix.HTTPError); ok {
+ if respErr, ok := httpErr.WrappedError.(mautrix.RespError); ok {
view.Error(respErr.Err)
} else {
view.Error(httpErr.Message)
diff --git a/ui/view-main.go b/ui/view-main.go
index c5abb19..5faa804 100644
--- a/ui/view-main.go
+++ b/ui/view-main.go
@@ -26,7 +26,7 @@ import (
"bufio"
"os"
- "maunium.net/go/gomatrix"
+ "maunium.net/go/mautrix"
"maunium.net/go/gomuks/config"
"maunium.net/go/gomuks/debug"
"maunium.net/go/gomuks/interface"
@@ -152,8 +152,8 @@ func (view *MainView) sendTempMessage(roomView *RoomView, tempMessage ifc.Messag
eventID, err := view.matrix.SendMarkdownMessage(roomView.Room.ID, tempMessage.Type(), text)
if err != nil {
tempMessage.SetState(ifc.MessageStateFailed)
- if httpErr, ok := err.(gomatrix.HTTPError); ok {
- if respErr, ok := httpErr.WrappedError.(gomatrix.RespError); ok {
+ if httpErr, ok := err.(mautrix.HTTPError); ok {
+ if respErr, ok := httpErr.WrappedError.(mautrix.RespError); ok {
// Show shorter version if available
err = respErr
}
@@ -510,6 +510,6 @@ func (view *MainView) LoadHistory(room string) {
view.parent.Render()
}
-func (view *MainView) ParseEvent(roomView ifc.RoomView, evt *gomatrix.Event) ifc.Message {
+func (view *MainView) ParseEvent(roomView ifc.RoomView, evt *mautrix.Event) ifc.Message {
return parser.ParseEvent(view.matrix, roomView.MxRoom(), evt)
}
diff --git a/ui/widget/border.go b/ui/widget/border.go
index b3eb65d..834eedb 100644
--- a/ui/widget/border.go
+++ b/ui/widget/border.go
@@ -40,11 +40,11 @@ func (border *Border) Draw(screen tcell.Screen) {
x, y, width, height := border.GetRect()
if width == 1 {
for borderY := y; borderY < y+height; borderY++ {
- screen.SetContent(x, borderY, tview.GraphicsVertBar, nil, background)
+ screen.SetContent(x, borderY, tview.Borders.Vertical, nil, background)
}
} else if height == 1 {
for borderX := x; borderX < x+width; borderX++ {
- screen.SetContent(borderX, y, tview.GraphicsHoriBar, nil, background)
+ screen.SetContent(borderX, y, tview.Borders.Horizontal, nil, background)
}
}
}
diff --git a/vendor/golang.org/x/image/bmp/reader.go b/vendor/golang.org/x/image/bmp/reader.go
index a0f2715..c10a022 100644
--- a/vendor/golang.org/x/image/bmp/reader.go
+++ b/vendor/golang.org/x/image/bmp/reader.go
@@ -137,20 +137,26 @@ func decodeConfig(r io.Reader) (config image.Config, bitsPerPixel int, topDown b
// We only support those BMP images that are a BITMAPFILEHEADER
// immediately followed by a BITMAPINFOHEADER.
const (
- fileHeaderLen = 14
- infoHeaderLen = 40
+ fileHeaderLen = 14
+ infoHeaderLen = 40
+ v4InfoHeaderLen = 108
+ v5InfoHeaderLen = 124
)
var b [1024]byte
- if _, err := io.ReadFull(r, b[:fileHeaderLen+infoHeaderLen]); err != nil {
+ if _, err := io.ReadFull(r, b[:fileHeaderLen+4]); err != nil {
return image.Config{}, 0, false, err
}
if string(b[:2]) != "BM" {
return image.Config{}, 0, false, errors.New("bmp: invalid format")
}
offset := readUint32(b[10:14])
- if readUint32(b[14:18]) != infoHeaderLen {
+ infoLen := readUint32(b[14:18])
+ if infoLen != infoHeaderLen && infoLen != v4InfoHeaderLen && infoLen != v5InfoHeaderLen {
return image.Config{}, 0, false, ErrUnsupported
}
+ if _, err := io.ReadFull(r, b[fileHeaderLen+4:fileHeaderLen+infoLen]); err != nil {
+ return image.Config{}, 0, false, err
+ }
width := int(int32(readUint32(b[18:22])))
height := int(int32(readUint32(b[22:26])))
if height < 0 {
@@ -159,14 +165,22 @@ func decodeConfig(r io.Reader) (config image.Config, bitsPerPixel int, topDown b
if width < 0 || height < 0 {
return image.Config{}, 0, false, ErrUnsupported
}
- // We only support 1 plane, 8 or 24 bits per pixel and no compression.
+ // We only support 1 plane and 8, 24 or 32 bits per pixel and no
+ // compression.
planes, bpp, compression := readUint16(b[26:28]), readUint16(b[28:30]), readUint32(b[30:34])
+ // if compression is set to BITFIELDS, but the bitmask is set to the default bitmask
+ // that would be used if compression was set to 0, we can continue as if compression was 0
+ if compression == 3 && infoLen > infoHeaderLen &&
+ readUint32(b[54:58]) == 0xff0000 && readUint32(b[58:62]) == 0xff00 &&
+ readUint32(b[62:66]) == 0xff && readUint32(b[66:70]) == 0xff000000 {
+ compression = 0
+ }
if planes != 1 || compression != 0 {
return image.Config{}, 0, false, ErrUnsupported
}
switch bpp {
case 8:
- if offset != fileHeaderLen+infoHeaderLen+256*4 {
+ if offset != fileHeaderLen+infoLen+256*4 {
return image.Config{}, 0, false, ErrUnsupported
}
_, err = io.ReadFull(r, b[:256*4])
@@ -181,12 +195,12 @@ func decodeConfig(r io.Reader) (config image.Config, bitsPerPixel int, topDown b
}
return image.Config{ColorModel: pcm, Width: width, Height: height}, 8, topDown, nil
case 24:
- if offset != fileHeaderLen+infoHeaderLen {
+ if offset != fileHeaderLen+infoLen {
return image.Config{}, 0, false, ErrUnsupported
}
return image.Config{ColorModel: color.RGBAModel, Width: width, Height: height}, 24, topDown, nil
case 32:
- if offset != fileHeaderLen+infoHeaderLen {
+ if offset != fileHeaderLen+infoLen {
return image.Config{}, 0, false, ErrUnsupported
}
return image.Config{ColorModel: color.RGBAModel, Width: width, Height: height}, 32, topDown, nil
diff --git a/vendor/golang.org/x/image/tiff/reader.go b/vendor/golang.org/x/image/tiff/reader.go
index 8a941c1..ce2ef71 100644
--- a/vendor/golang.org/x/image/tiff/reader.go
+++ b/vendor/golang.org/x/image/tiff/reader.go
@@ -110,7 +110,7 @@ func (d *decoder) ifdUint(p []byte) (u []uint, err error) {
return u, nil
}
-// parseIFD decides whether the the IFD entry in p is "interesting" and
+// parseIFD decides whether the IFD entry in p is "interesting" and
// stows away the data in the decoder. It returns the tag number of the
// entry and an error, if any.
func (d *decoder) parseIFD(p []byte) (int, error) {
diff --git a/vendor/golang.org/x/image/vp8/decode.go b/vendor/golang.org/x/image/vp8/decode.go
index 1bb5028..2aa9fee 100644
--- a/vendor/golang.org/x/image/vp8/decode.go
+++ b/vendor/golang.org/x/image/vp8/decode.go
@@ -82,7 +82,7 @@ type mb struct {
pred [4]uint8
// nzMask is a mask of 8 bits: 4 for the bottom or right 4x4 luma regions,
// and 2 + 2 for the bottom or right 4x4 chroma regions. A 1 bit indicates
- // that that region has non-zero coefficients.
+ // that region has non-zero coefficients.
nzMask uint8
// nzY16 is a 0/1 value that is 1 if the macroblock used Y16 prediction and
// had non-zero coefficients.
@@ -274,7 +274,7 @@ func (d *Decoder) parseOtherPartitions() error {
var partLens [maxNOP]int
d.nOP = 1 << d.fp.readUint(uniformProb, 2)
- // The final partition length is implied by the the remaining chunk data
+ // The final partition length is implied by the remaining chunk data
// (d.r.n) and the other d.nOP-1 partition lengths. Those d.nOP-1 partition
// lengths are stored as 24-bit uints, i.e. up to 16 MiB per partition.
n := 3 * (d.nOP - 1)
diff --git a/vendor/golang.org/x/image/webp/decode.go b/vendor/golang.org/x/image/webp/decode.go
index 111f358..f77a4eb 100644
--- a/vendor/golang.org/x/image/webp/decode.go
+++ b/vendor/golang.org/x/image/webp/decode.go
@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// +build go1.6
-
package webp
import (
diff --git a/vendor/golang.org/x/image/webp/doc.go b/vendor/golang.org/x/image/webp/doc.go
new file mode 100644
index 0000000..e321c85
--- /dev/null
+++ b/vendor/golang.org/x/image/webp/doc.go
@@ -0,0 +1,9 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package webp implements a decoder for WEBP images.
+//
+// WEBP is defined at:
+// https://developers.google.com/speed/webp/docs/riff_container
+package webp // import "golang.org/x/image/webp"
diff --git a/vendor/golang.org/x/image/webp/webp.go b/vendor/golang.org/x/image/webp/webp.go
deleted file mode 100644
index 850cdc8..0000000
--- a/vendor/golang.org/x/image/webp/webp.go
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2016 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package webp implements a decoder for WEBP images.
-//
-// WEBP is defined at:
-// https://developers.google.com/speed/webp/docs/riff_container
-//
-// It requires Go 1.6 or later.
-package webp // import "golang.org/x/image/webp"
-
-// This blank Go file, other than the package clause, exists so that this
-// package can be built for Go 1.5 and earlier. (The other files in this
-// package are all marked "+build go1.6" for the NYCbCrA types introduced in Go
-// 1.6). There is no functionality in a blank package, but some image
-// manipulation programs might still underscore import this package for the
-// side effect of registering the WEBP format with the standard library's
-// image.RegisterFormat and image.Decode functions. For example, that program
-// might contain:
-//
-// // Underscore imports to register some formats for image.Decode.
-// import _ "image/gif"
-// import _ "image/jpeg"
-// import _ "image/png"
-// import _ "golang.org/x/image/webp"
-//
-// Such a program will still compile for Go 1.5 (due to this placeholder Go
-// file). It will simply not be able to recognize and decode WEBP (but still
-// handle GIF, JPEG and PNG).
diff --git a/vendor/golang.org/x/net/html/const.go b/vendor/golang.org/x/net/html/const.go
index 5eb7c5a..a3a918f 100644
--- a/vendor/golang.org/x/net/html/const.go
+++ b/vendor/golang.org/x/net/html/const.go
@@ -97,8 +97,16 @@ func isSpecialElement(element *Node) bool {
switch element.Namespace {
case "", "html":
return isSpecialElementMap[element.Data]
+ case "math":
+ switch element.Data {
+ case "mi", "mo", "mn", "ms", "mtext", "annotation-xml":
+ return true
+ }
case "svg":
- return element.Data == "foreignObject"
+ switch element.Data {
+ case "foreignObject", "desc", "title":
+ return true
+ }
}
return false
}
diff --git a/vendor/golang.org/x/net/html/parse.go b/vendor/golang.org/x/net/html/parse.go
index 4b1fa42..64a5793 100644
--- a/vendor/golang.org/x/net/html/parse.go
+++ b/vendor/golang.org/x/net/html/parse.go
@@ -470,6 +470,10 @@ func (p *parser) resetInsertionMode() {
case a.Table:
p.im = inTableIM
case a.Template:
+ // TODO: remove this divergence from the HTML5 spec.
+ if n.Namespace != "" {
+ continue
+ }
p.im = p.templateStack.top()
case a.Head:
// TODO: remove this divergence from the HTML5 spec.
@@ -984,6 +988,14 @@ func inBodyIM(p *parser) bool {
p.acknowledgeSelfClosingTag()
p.popUntil(buttonScope, a.P)
p.parseImpliedToken(StartTagToken, a.Form, a.Form.String())
+ if p.form == nil {
+ // NOTE: The 'isindex' element has been removed,
+ // and the 'template' element has not been designed to be
+ // collaborative with the index element.
+ //
+ // Ignore the token.
+ return true
+ }
if action != "" {
p.form.Attr = []Attribute{{Key: "action", Val: action}}
}
@@ -1252,12 +1264,6 @@ func (p *parser) inBodyEndTagFormatting(tagAtom a.Atom) {
switch commonAncestor.DataAtom {
case a.Table, a.Tbody, a.Tfoot, a.Thead, a.Tr:
p.fosterParent(lastNode)
- case a.Template:
- // TODO: remove namespace checking
- if commonAncestor.Namespace == "html" {
- commonAncestor = commonAncestor.LastChild
- }
- fallthrough
default:
commonAncestor.AppendChild(lastNode)
}
@@ -2209,6 +2215,15 @@ func (p *parser) parse() error {
}
// Parse returns the parse tree for the HTML from the given Reader.
+//
+// It implements the HTML5 parsing algorithm
+// (https://html.spec.whatwg.org/multipage/syntax.html#tree-construction),
+// which is very complicated. The resultant tree can contain implicitly created
+// nodes that have no explicit <tag> listed in r's data, and nodes' parents can
+// differ from the nesting implied by a naive processing of start and end
+// <tag>s. Conversely, explicit <tag>s in r's data can be silently dropped,
+// with no corresponding node in the resulting tree.
+//
// The input is assumed to be UTF-8 encoded.
func Parse(r io.Reader) (*Node, error) {
p := &parser{
@@ -2230,6 +2245,8 @@ func Parse(r io.Reader) (*Node, error) {
// ParseFragment parses a fragment of HTML and returns the nodes that were
// found. If the fragment is the InnerHTML for an existing element, pass that
// element in context.
+//
+// It has the same intricacies as Parse.
func ParseFragment(r io.Reader, context *Node) ([]*Node, error) {
contextTag := ""
if context != nil {
diff --git a/vendor/gopkg.in/russross/blackfriday.v2/.travis.yml b/vendor/gopkg.in/russross/blackfriday.v2/.travis.yml
index a4eb257..b0b525a 100644
--- a/vendor/gopkg.in/russross/blackfriday.v2/.travis.yml
+++ b/vendor/gopkg.in/russross/blackfriday.v2/.travis.yml
@@ -1,18 +1,17 @@
-# Travis CI (http://travis-ci.org/) is a continuous integration service for
-# open source projects. This file configures it to run unit tests for
-# blackfriday.
-
+sudo: false
language: go
-
go:
- - 1.5
- - 1.6
- - 1.7
-
+ - "1.10.x"
+ - "1.11.x"
+ - tip
+matrix:
+ fast_finish: true
+ allow_failures:
+ - go: tip
install:
- - go get -d -t -v ./...
- - go build -v ./...
-
+ - # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step).
script:
- - go test -v ./...
- - go test -run=^$ -bench=BenchmarkReference -benchmem
+ - go get -t -v ./...
+ - diff -u <(echo -n) <(gofmt -d -s .)
+ - go tool vet .
+ - go test -v ./...
diff --git a/vendor/gopkg.in/russross/blackfriday.v2/README.md b/vendor/gopkg.in/russross/blackfriday.v2/README.md
index 2e0db35..d5a8649 100644
--- a/vendor/gopkg.in/russross/blackfriday.v2/README.md
+++ b/vendor/gopkg.in/russross/blackfriday.v2/README.md
@@ -34,9 +34,15 @@ Versions
--------
Currently maintained and recommended version of Blackfriday is `v2`. It's being
-developed on its own branch: https://github.com/russross/blackfriday/v2. You
-should install and import it via [gopkg.in][6] at
-`gopkg.in/russross/blackfriday.v2`.
+developed on its own branch: https://github.com/russross/blackfriday/tree/v2 and the
+documentation is available at
+https://godoc.org/gopkg.in/russross/blackfriday.v2.
+
+It is `go get`-able via via [gopkg.in][6] at `gopkg.in/russross/blackfriday.v2`,
+but we highly recommend using package management tool like [dep][7] or
+[Glide][8] and make use of semantic versioning. With package management you
+should import `github.com/russross/blackfriday` and specify that you're using
+version 2.0.0.
Version 2 offers a number of improvements over v1:
@@ -198,7 +204,7 @@ implements the following extensions:
Cat
: Fluffy animal everyone likes
-
+
Internet
: Vector of transmission for pictures of cats
@@ -209,7 +215,7 @@ implements the following extensions:
end of the document. A footnote looks like this:
This is a footnote.[^1]
-
+
[^1]: the footnote text.
* **Autolinking**. Blackfriday can find URLs that have not been
@@ -255,9 +261,11 @@ are a few of note:
* [markdownfmt](https://github.com/shurcooL/markdownfmt): like gofmt,
but for markdown.
-* [LaTeX output](https://bitbucket.org/ambrevar/blackfriday-latex):
+* [LaTeX output](https://github.com/Ambrevar/Blackfriday-LaTeX):
renders output as LaTeX.
+* [Blackfriday-Confluence](https://github.com/kentaro-m/blackfriday-confluence): provides a [Confluence Wiki Markup](https://confluence.atlassian.com/doc/confluence-wiki-markup-251003035.html) renderer.
+
Todo
----
diff --git a/vendor/gopkg.in/russross/blackfriday.v2/block.go b/vendor/gopkg.in/russross/blackfriday.v2/block.go
index d7da33f..b860747 100644
--- a/vendor/gopkg.in/russross/blackfriday.v2/block.go
+++ b/vendor/gopkg.in/russross/blackfriday.v2/block.go
@@ -17,6 +17,7 @@ import (
"bytes"
"html"
"regexp"
+ "strings"
"github.com/shurcooL/sanitized_anchor_name"
)
@@ -568,8 +569,8 @@ func (*Markdown) isHRule(data []byte) bool {
// isFenceLine checks if there's a fence line (e.g., ``` or ``` go) at the beginning of data,
// and returns the end index if so, or 0 otherwise. It also returns the marker found.
-// If syntax is not nil, it gets set to the syntax specified in the fence line.
-func isFenceLine(data []byte, syntax *string, oldmarker string) (end int, marker string) {
+// If info is not nil, it gets set to the syntax specified in the fence line.
+func isFenceLine(data []byte, info *string, oldmarker string) (end int, marker string) {
i, size := 0, 0
// skip up to three spaces
@@ -605,9 +606,9 @@ func isFenceLine(data []byte, syntax *string, oldmarker string) (end int, marker
}
// TODO(shurcooL): It's probably a good idea to simplify the 2 code paths here
- // into one, always get the syntax, and discard it if the caller doesn't care.
- if syntax != nil {
- syn := 0
+ // into one, always get the info string, and discard it if the caller doesn't care.
+ if info != nil {
+ infoLength := 0
i = skipChar(data, i, ' ')
if i >= len(data) {
@@ -617,14 +618,14 @@ func isFenceLine(data []byte, syntax *string, oldmarker string) (end int, marker
return 0, ""
}
- syntaxStart := i
+ infoStart := i
if data[i] == '{' {
i++
- syntaxStart++
+ infoStart++
for i < len(data) && data[i] != '}' && data[i] != '\n' {
- syn++
+ infoLength++
i++
}
@@ -634,31 +635,30 @@ func isFenceLine(data []byte, syntax *string, oldmarker string) (end int, marker
// strip all whitespace at the beginning and the end
// of the {} block
- for syn > 0 && isspace(data[syntaxStart]) {
- syntaxStart++
- syn--
+ for infoLength > 0 && isspace(data[infoStart]) {
+ infoStart++
+ infoLength--
}
- for syn > 0 && isspace(data[syntaxStart+syn-1]) {
- syn--
+ for infoLength > 0 && isspace(data[infoStart+infoLength-1]) {
+ infoLength--
}
-
i++
+ i = skipChar(data, i, ' ')
} else {
- for i < len(data) && !isspace(data[i]) {
- syn++
+ for i < len(data) && !isverticalspace(data[i]) {
+ infoLength++
i++
}
}
- *syntax = string(data[syntaxStart : syntaxStart+syn])
+ *info = strings.TrimSpace(string(data[infoStart : infoStart+infoLength]))
}
- i = skipChar(data, i, ' ')
- if i >= len(data) || data[i] != '\n' {
- if i == len(data) {
- return i, marker
- }
+ if i == len(data) {
+ return i, marker
+ }
+ if i > len(data) || data[i] != '\n' {
return 0, ""
}
return i + 1, marker // Take newline into account.
@@ -668,14 +668,14 @@ func isFenceLine(data []byte, syntax *string, oldmarker string) (end int, marker
// or 0 otherwise. It writes to out if doRender is true, otherwise it has no side effects.
// If doRender is true, a final newline is mandatory to recognize the fenced code block.
func (p *Markdown) fencedCodeBlock(data []byte, doRender bool) int {
- var syntax string
- beg, marker := isFenceLine(data, &syntax, "")
+ var info string
+ beg, marker := isFenceLine(data, &info, "")
if beg == 0 || beg >= len(data) {
return 0
}
var work bytes.Buffer
- work.Write([]byte(syntax))
+ work.Write([]byte(info))
work.WriteByte('\n')
for {
@@ -1148,6 +1148,18 @@ func (p *Markdown) list(data []byte, flags ListType) int {
return i
}
+// Returns true if the list item is not the same type as its parent list
+func (p *Markdown) listTypeChanged(data []byte, flags *ListType) bool {
+ if p.dliPrefix(data) > 0 && *flags&ListTypeDefinition == 0 {
+ return true
+ } else if p.oliPrefix(data) > 0 && *flags&ListTypeOrdered == 0 {
+ return true
+ } else if p.uliPrefix(data) > 0 && (*flags&ListTypeOrdered != 0 || *flags&ListTypeDefinition != 0) {
+ return true
+ }
+ return false
+}
+
// Returns true if block ends with a blank line, descending if needed
// into lists and sublists.
func endsWithBlankLine(block *Node) bool {
@@ -1246,6 +1258,7 @@ func (p *Markdown) listItem(data []byte, flags *ListType) int {
// process the following lines
containsBlankLine := false
sublist := 0
+ codeBlockMarker := ""
gatherlines:
for line < len(data) {
@@ -1279,6 +1292,27 @@ gatherlines:
chunk := data[line+indentIndex : i]
+ if p.extensions&FencedCode != 0 {
+ // determine if in or out of codeblock
+ // if in codeblock, ignore normal list processing
+ _, marker := isFenceLine(chunk, nil, codeBlockMarker)
+ if marker != "" {
+ if codeBlockMarker == "" {
+ // start of codeblock
+ codeBlockMarker = marker
+ } else {
+ // end of codeblock.
+ codeBlockMarker = ""
+ }
+ }
+ // we are in a codeblock, write line, and continue
+ if codeBlockMarker != "" || marker != "" {
+ raw.Write(data[line+indentIndex : i])
+ line = i
+ continue gatherlines
+ }
+ }
+
// evaluate how this line fits in
switch {
// is this a nested list item?
@@ -1286,16 +1320,23 @@ gatherlines:
p.oliPrefix(chunk) > 0 ||
p.dliPrefix(chunk) > 0:
- if containsBlankLine {
- *flags |= ListItemContainsBlock
- }
-
// to be a nested list, it must be indented more
- // if not, it is the next item in the same list
+ // if not, it is either a different kind of list
+ // or the next item in the same list
if indent <= itemIndent {
+ if p.listTypeChanged(chunk, flags) {
+ *flags |= ListItemEndOfList
+ } else if containsBlankLine {
+ *flags |= ListItemContainsBlock
+ }
+
break gatherlines
}
+ if containsBlankLine {
+ *flags |= ListItemContainsBlock
+ }
+
// is this the first item in the nested list?
if sublist == 0 {
sublist = raw.Len()
diff --git a/vendor/gopkg.in/russross/blackfriday.v2/go.mod b/vendor/gopkg.in/russross/blackfriday.v2/go.mod
new file mode 100644
index 0000000..620b74e
--- /dev/null
+++ b/vendor/gopkg.in/russross/blackfriday.v2/go.mod
@@ -0,0 +1 @@
+module github.com/russross/blackfriday/v2
diff --git a/vendor/gopkg.in/russross/blackfriday.v2/html.go b/vendor/gopkg.in/russross/blackfriday.v2/html.go
index 25fb185..284c871 100644
--- a/vendor/gopkg.in/russross/blackfriday.v2/html.go
+++ b/vendor/gopkg.in/russross/blackfriday.v2/html.go
@@ -35,6 +35,7 @@ const (
Safelink // Only link to trusted protocols
NofollowLinks // Only link with rel="nofollow"
NoreferrerLinks // Only link with rel="noreferrer"
+ NoopenerLinks // Only link with rel="noopener"
HrefTargetBlank // Add a blank target
CompletePage // Generate a complete HTML page
UseXHTML // Generate XHTML output instead of HTML
@@ -87,6 +88,10 @@ type HTMLRendererParameters struct {
HeadingIDPrefix string
// If set, add this text to the back of each Heading ID, to ensure uniqueness.
HeadingIDSuffix string
+ // Increase heading levels: if the offset is 1, <h1> becomes <h2> etc.
+ // Negative offset is also valid.
+ // Resulting levels are clipped between 1 and 6.
+ HeadingLevelOffset int
Title string // Document title (used if CompletePage is set)
CSS string // Optional CSS file URL (used if CompletePage is set)
@@ -282,6 +287,9 @@ func appendLinkAttrs(attrs []string, flags HTMLFlags, link []byte) []string {
if flags&NoreferrerLinks != 0 {
val = append(val, "noreferrer")
}
+ if flags&NoopenerLinks != 0 {
+ val = append(val, "noopener")
+ }
if flags&HrefTargetBlank != 0 {
attrs = append(attrs, "target=\"_blank\"")
}
@@ -331,7 +339,7 @@ func (r *HTMLRenderer) tag(w io.Writer, name []byte, attrs []string) {
func footnoteRef(prefix string, node *Node) []byte {
urlFrag := prefix + string(slugify(node.Destination))
- anchor := fmt.Sprintf(`<a rel="footnote" href="#fn:%s">%d</a>`, urlFrag, node.NoteID)
+ anchor := fmt.Sprintf(`<a href="#fn:%s">%d</a>`, urlFrag, node.NoteID)
return []byte(fmt.Sprintf(`<sup class="footnote-ref" id="fnref:%s">%s</sup>`, urlFrag, anchor))
}
@@ -460,9 +468,10 @@ var (
)
func headingTagsFromLevel(level int) ([]byte, []byte) {
- switch level {
- case 1:
+ if level <= 1 {
return h1Tag, h1CloseTag
+ }
+ switch level {
case 2:
return h2Tag, h2CloseTag
case 3:
@@ -471,9 +480,8 @@ func headingTagsFromLevel(level int) ([]byte, []byte) {
return h4Tag, h4CloseTag
case 5:
return h5Tag, h5CloseTag
- default:
- return h6Tag, h6CloseTag
}
+ return h6Tag, h6CloseTag
}
func (r *HTMLRenderer) outHRTag(w io.Writer) {
@@ -651,7 +659,8 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt
r.out(w, node.Literal)
r.cr(w)
case Heading:
- openTag, closeTag := headingTagsFromLevel(node.Level)
+ headingLevel := r.HTMLRendererParameters.HeadingLevelOffset + node.Level
+ openTag, closeTag := headingTagsFromLevel(headingLevel)
if entering {
if node.IsTitleblock {
attrs = append(attrs, `class="title"`)
diff --git a/vendor/gopkg.in/russross/blackfriday.v2/inline.go b/vendor/gopkg.in/russross/blackfriday.v2/inline.go
index 3d63310..4ed2907 100644
--- a/vendor/gopkg.in/russross/blackfriday.v2/inline.go
+++ b/vendor/gopkg.in/russross/blackfriday.v2/inline.go
@@ -23,8 +23,22 @@ var (
urlRe = `((https?|ftp):\/\/|\/)[-A-Za-z0-9+&@#\/%?=~_|!:,.;\(\)]+`
anchorRe = regexp.MustCompile(`^(<a\shref="` + urlRe + `"(\stitle="[^"<>]+")?\s?>` + urlRe + `<\/a>)`)
- // TODO: improve this regexp to catch all possible entities:
- htmlEntityRe = regexp.MustCompile(`&[a-z]{2,5};`)
+ // https://www.w3.org/TR/html5/syntax.html#character-references
+ // highest unicode code point in 17 planes (2^20): 1,114,112d =
+ // 7 dec digits or 6 hex digits
+ // named entity references can be 2-31 characters with stuff like &lt;
+ // at one end and &CounterClockwiseContourIntegral; at the other. There
+ // are also sometimes numbers at the end, although this isn't inherent
+ // in the specification; there are never numbers anywhere else in
+ // current character references, though; see &frac34; and &blk12;, etc.
+ // https://www.w3.org/TR/html5/syntax.html#named-character-references
+ //
+ // entity := "&" (named group | number ref) ";"
+ // named group := [a-zA-Z]{2,31}[0-9]{0,2}
+ // number ref := "#" (dec ref | hex ref)
+ // dec ref := [0-9]{1,7}
+ // hex ref := ("x" | "X") [0-9a-fA-F]{1,6}
+ htmlEntityRe = regexp.MustCompile(`&([a-zA-Z]{2,31}[0-9]{0,2}|#([0-9]{1,7}|[xX][0-9a-fA-F]{1,6}));`)
)
// Functions to parse text within a block
diff --git a/vendor/gopkg.in/russross/blackfriday.v2/markdown.go b/vendor/gopkg.in/russross/blackfriday.v2/markdown.go
index ff61cb0..58d2e45 100644
--- a/vendor/gopkg.in/russross/blackfriday.v2/markdown.go
+++ b/vendor/gopkg.in/russross/blackfriday.v2/markdown.go
@@ -93,46 +93,46 @@ const (
// blockTags is a set of tags that are recognized as HTML block tags.
// Any of these can be included in markdown text without special escaping.
var blockTags = map[string]struct{}{
- "blockquote": struct{}{},
- "del": struct{}{},
- "div": struct{}{},
- "dl": struct{}{},
- "fieldset": struct{}{},
- "form": struct{}{},
- "h1": struct{}{},
- "h2": struct{}{},
- "h3": struct{}{},
- "h4": struct{}{},
- "h5": struct{}{},
- "h6": struct{}{},
- "iframe": struct{}{},
- "ins": struct{}{},
- "math": struct{}{},
- "noscript": struct{}{},
- "ol": struct{}{},
- "pre": struct{}{},
- "p": struct{}{},
- "script": struct{}{},
- "style": struct{}{},
- "table": struct{}{},
- "ul": struct{}{},
+ "blockquote": {},
+ "del": {},
+ "div": {},
+ "dl": {},
+ "fieldset": {},
+ "form": {},
+ "h1": {},
+ "h2": {},
+ "h3": {},
+ "h4": {},
+ "h5": {},
+ "h6": {},
+ "iframe": {},
+ "ins": {},
+ "math": {},
+ "noscript": {},
+ "ol": {},
+ "pre": {},
+ "p": {},
+ "script": {},
+ "style": {},
+ "table": {},
+ "ul": {},
// HTML5
- "address": struct{}{},
- "article": struct{}{},
- "aside": struct{}{},
- "canvas": struct{}{},
- "figcaption": struct{}{},
- "figure": struct{}{},
- "footer": struct{}{},
- "header": struct{}{},
- "hgroup": struct{}{},
- "main": struct{}{},
- "nav": struct{}{},
- "output": struct{}{},
- "progress": struct{}{},
- "section": struct{}{},
- "video": struct{}{},
+ "address": {},
+ "article": {},
+ "aside": {},
+ "canvas": {},
+ "figcaption": {},
+ "figure": {},
+ "footer": {},
+ "header": {},
+ "hgroup": {},
+ "main": {},
+ "nav": {},
+ "output": {},
+ "progress": {},
+ "section": {},
+ "video": {},
}
// Renderer is the rendering interface. This is mostly of interest if you are
@@ -480,11 +480,11 @@ func (p *Markdown) parseRefsToAST() {
// [^note]: This is the explanation.
//
// Footnotes should be placed at the end of the document in an ordered list.
-// Inline footnotes such as:
+// Finally, there are inline footnotes such as:
//
-// Inline footnotes^[Not supported.] also exist.
+// Inline footnotes^[Also supported.] provide a quick inline explanation,
+// but are rendered at the bottom of the document.
//
-// are not yet supported.
// reference holds all information necessary for a reference-style links or
// footnotes.
@@ -813,7 +813,17 @@ func ispunct(c byte) bool {
// Test if a character is a whitespace character.
func isspace(c byte) bool {
- return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v'
+ return ishorizontalspace(c) || isverticalspace(c)
+}
+
+// Test if a character is a horizontal whitespace character.
+func ishorizontalspace(c byte) bool {
+ return c == ' ' || c == '\t'
+}
+
+// Test if a character is a vertical character.
+func isverticalspace(c byte) bool {
+ return c == '\n' || c == '\r' || c == '\f' || c == '\v'
}
// Test if a character is letter.
diff --git a/vendor/maunium.net/go/gomatrix/.gitignore b/vendor/maunium.net/go/gomatrix/.gitignore
deleted file mode 100644
index daf913b..0000000
--- a/vendor/maunium.net/go/gomatrix/.gitignore
+++ /dev/null
@@ -1,24 +0,0 @@
-# Compiled Object files, Static and Dynamic libs (Shared Objects)
-*.o
-*.a
-*.so
-
-# Folders
-_obj
-_test
-
-# Architecture specific extensions/prefixes
-*.[568vq]
-[568vq].out
-
-*.cgo1.go
-*.cgo2.c
-_cgo_defun.c
-_cgo_gotypes.go
-_cgo_export.*
-
-_testmain.go
-
-*.exe
-*.test
-*.prof
diff --git a/vendor/maunium.net/go/gomatrix/.travis.yml b/vendor/maunium.net/go/gomatrix/.travis.yml
deleted file mode 100644
index fadc326..0000000
--- a/vendor/maunium.net/go/gomatrix/.travis.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-language: go
-go:
- - 1.8
-install:
- - go get github.com/golang/lint/golint
- - go get github.com/fzipp/gocyclo
- - go get github.com/client9/misspell/...
- - go get github.com/gordonklaus/ineffassign
-script: ./hooks/pre-commit
diff --git a/vendor/maunium.net/go/gomatrix/README.md b/vendor/maunium.net/go/gomatrix/README.md
deleted file mode 100644
index ea9109a..0000000
--- a/vendor/maunium.net/go/gomatrix/README.md
+++ /dev/null
@@ -1,6 +0,0 @@
-# gomatrix
-[![GoDoc](https://godoc.org/github.com/matrix-org/gomatrix?status.svg)](https://godoc.org/github.com/matrix-org/gomatrix)
-
-A Golang Matrix client.
-
-**THIS IS UNDER ACTIVE DEVELOPMENT: BREAKING CHANGES ARE FREQUENT.**
diff --git a/vendor/maunium.net/go/maulogger/LICENSE b/vendor/maunium.net/go/maulogger/LICENSE
deleted file mode 100644
index c9739fb..0000000
--- a/vendor/maunium.net/go/maulogger/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-The MIT License (MIT)
-
-Copyright (c) 2016 Tulir Asokan
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-of the Software, and to permit persons to whom the Software is furnished to do
-so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/vendor/maunium.net/go/maulogger/README.md b/vendor/maunium.net/go/maulogger/README.md
deleted file mode 100644
index 68fe253..0000000
--- a/vendor/maunium.net/go/maulogger/README.md
+++ /dev/null
@@ -1,6 +0,0 @@
-# maulogger
-A logger in Go.
-
-Docs: [godoc.org/maunium.net/go/maulogger](https://godoc.org/maunium.net/go/maulogger)
-
-Go get: `go get maunium.net/go/maulogger`
diff --git a/vendor/maunium.net/go/maulogger/logger.go b/vendor/maunium.net/go/maulogger/logger.go
deleted file mode 100644
index e887237..0000000
--- a/vendor/maunium.net/go/maulogger/logger.go
+++ /dev/null
@@ -1,219 +0,0 @@
-package maulog
-
-import (
- "bufio"
- "fmt"
- "os"
- "time"
-)
-
-// Level is the severity level of a log entry.
-type Level struct {
- Name string
- Severity, Color int
-}
-
-// LogWriter writes to the log with an optional prefix
-type LogWriter struct {
- Level Level
- Prefix string
-}
-
-func (lw LogWriter) Write(p []byte) (n int, err error) {
- log(lw.Level, fmt.Sprint(lw.Prefix, string(p)))
- return len(p), nil
-}
-
-// GetColor gets the ANSI escape color code for the log level.
-func (lvl Level) GetColor() []byte {
- if lvl.Color < 0 {
- return []byte("")
- }
- return []byte(fmt.Sprintf("\x1b[%dm", lvl.Color))
-}
-
-// GetReset gets the ANSI escape reset code.
-func (lvl Level) GetReset() []byte {
- if lvl.Color < 0 {
- return []byte("")
- }
- return []byte("\x1b[0m")
-}
-
-var (
- // Debug is the level for debug messages.
- Debug = Level{Name: "DEBUG", Color: 36, Severity: 0}
- // Info is the level for basic log messages.
- Info = Level{Name: "INFO", Color: -1, Severity: 10}
- // Warn is the level saying that something went wrong, but the program will continue operating mostly normally.
- Warn = Level{Name: "WARN", Color: 33, Severity: 50}
- // Error is the level saying that something went wrong and the program may not operate as expected, but will still continue.
- Error = Level{Name: "ERROR", Color: 31, Severity: 100}
- // Fatal is the level saying that something went wrong and the program will not operate normally.
- Fatal = Level{Name: "FATAL", Color: 35, Severity: 9001}
-)
-
-// PrintLevel tells the first severity level at which messages should be printed to stdout
-var PrintLevel = 10
-
-// PrintDebug means PrintLevel = 0, kept for backwards compatibility
-var PrintDebug = false
-
-// FileTimeformat is the time format used in log file names.
-var FileTimeformat = "2006-01-02"
-
-// FileformatArgs is an undocumented integer.
-var FileformatArgs = 3
-
-// Fileformat is the format used for log file names.
-var Fileformat = func(now string, i int) string { return fmt.Sprintf("%[1]s-%02[2]d.log", now, i) }
-
-// Timeformat is the time format used in logging.
-var Timeformat = "15:04:05 02.01.2006"
-
-var writer *bufio.Writer
-var lines int
-
-// InitWithWriter initializes MauLogger with the given writer.
-func InitWithWriter(w *bufio.Writer) {
- writer = w
-}
-
-// Init initializes MauLogger.
-func Init() {
- // Find the next file name.
- now := time.Now().Format(FileTimeformat)
- i := 1
- for ; ; i++ {
- if _, err := os.Stat(Fileformat(now, i)); os.IsNotExist(err) {
- break
- }
- if i == 99 {
- i = 1
- break
- }
- }
- // Open the file
- file, err := os.OpenFile(Fileformat(now, i), os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0700)
- if err != nil {
- panic(err)
- }
- if file == nil {
- panic(os.ErrInvalid)
- }
- // Create a writer
- writer = bufio.NewWriter(file)
-}
-
-// Debugf formats and logs a debug message.
-func Debugf(message string, args ...interface{}) {
- logln(Debug, fmt.Sprintf(message, args...))
-}
-
-// Printf formats and logs a string in the Info log level.
-func Printf(message string, args ...interface{}) {
- Infof(message, args...)
-}
-
-// Infof formats and logs a string in the Info log level.
-func Infof(message string, args ...interface{}) {
- logln(Info, fmt.Sprintf(message, args...))
-}
-
-// Warnf formats and logs a string in the Warn log level.
-func Warnf(message string, args ...interface{}) {
- logln(Warn, fmt.Sprintf(message, args...))
-}
-
-// Errorf formats and logs a string in the Error log level.
-func Errorf(message string, args ...interface{}) {
- logln(Error, fmt.Sprintf(message, args...))
-}
-
-// Fatalf formats and logs a string in the Fatal log level.
-func Fatalf(message string, args ...interface{}) {
- logln(Fatal, fmt.Sprintf(message, args...))
-}
-
-// Logf formats and logs a message in the given log level.
-func Logf(level Level, message string, args ...interface{}) {
- logln(level, fmt.Sprintf(message, args...))
-}
-
-// Debugln logs a debug message.
-func Debugln(args ...interface{}) {
- log(Debug, fmt.Sprintln(args...))
-}
-
-// Println logs a string in the Info log level.
-func Println(args ...interface{}) {
- Infoln(args...)
-}
-
-// Infoln logs a string in the Info log level.
-func Infoln(args ...interface{}) {
- log(Info, fmt.Sprintln(args...))
-}
-
-// Warnln logs a string in the Warn log level.
-func Warnln(args ...interface{}) {
- log(Warn, fmt.Sprintln(args...))
-}
-
-// Errorln logs a string in the Error log level.
-func Errorln(args ...interface{}) {
- log(Error, fmt.Sprintln(args...))
-}
-
-// Fatalln logs a string in the Fatal log level.
-func Fatalln(args ...interface{}) {
- log(Fatal, fmt.Sprintln(args...))
-}
-
-// Logln logs a message in the given log level.
-func Logln(level Level, args ...interface{}) {
- log(level, fmt.Sprintln(args...))
-}
-
-func logln(level Level, message string) {
- log(level, fmt.Sprintln(message))
-}
-
-func log(level Level, message string) {
- // Prefix the message with the timestamp and log level.
- msg := []byte(fmt.Sprintf("[%[1]s] [%[2]s] %[3]s", time.Now().Format(Timeformat), level.Name, message))
-
- if writer != nil {
- // Write it to the log file.
- _, err := writer.Write(msg)
- if err != nil {
- panic(err)
- }
- lines++
- // Flush the file if needed
- if lines == 5 {
- lines = 0
- writer.Flush()
- }
- }
-
- // Print to stdout using correct color
- if level.Severity >= PrintLevel || PrintDebug {
- if level.Severity >= Error.Severity {
- os.Stderr.Write(level.GetColor())
- os.Stderr.Write(msg)
- os.Stderr.Write(level.GetReset())
- } else {
- os.Stdout.Write(level.GetColor())
- os.Stdout.Write(msg)
- os.Stdout.Write(level.GetReset())
- }
- }
-}
-
-// Shutdown cleans up the logger.
-func Shutdown() {
- if writer != nil {
- writer.Flush()
- }
-}
diff --git a/vendor/maunium.net/go/mautrix/.gitignore b/vendor/maunium.net/go/mautrix/.gitignore
new file mode 100644
index 0000000..66f8fb5
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/.gitignore
@@ -0,0 +1,2 @@
+.idea/
+.vscode/
diff --git a/vendor/maunium.net/go/gomatrix/LICENSE b/vendor/maunium.net/go/mautrix/LICENSE
index 8dada3e..8dada3e 100644
--- a/vendor/maunium.net/go/gomatrix/LICENSE
+++ b/vendor/maunium.net/go/mautrix/LICENSE
diff --git a/vendor/maunium.net/go/mautrix/README.md b/vendor/maunium.net/go/mautrix/README.md
new file mode 100644
index 0000000..ca135a6
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/README.md
@@ -0,0 +1,4 @@
+# mautrix-go
+[![GoDoc](https://godoc.org/maunium.net/go/mautrix?status.svg)](https://godoc.org/maunium.net/go/mautrix)
+
+A Golang Matrix framework.
diff --git a/vendor/maunium.net/go/gomatrix/client.go b/vendor/maunium.net/go/mautrix/client.go
index 0806138..d908b62 100644
--- a/vendor/maunium.net/go/gomatrix/client.go
+++ b/vendor/maunium.net/go/mautrix/client.go
@@ -1,7 +1,7 @@
-// Package gomatrix implements the Matrix Client-Server API.
+// Package mautrix implements the Matrix Client-Server API.
//
-// Specification can be found at http://matrix.org/docs/spec/client_server/r0.2.0.html
-package gomatrix
+// Specification can be found at http://matrix.org/docs/spec/client_server/r0.4.0.html
+package mautrix
import (
"bytes"
@@ -10,7 +10,6 @@ import (
"fmt"
"io"
"io/ioutil"
- "maunium.net/go/maulogger"
"net/http"
"net/url"
"path"
@@ -20,6 +19,10 @@ import (
"time"
)
+type Logger interface {
+ Debugfln(message string, args ...interface{})
+}
+
// Client represents a Matrix client.
type Client struct {
HomeserverURL *url.URL // The base homeserver URL
@@ -29,7 +32,7 @@ type Client struct {
Client *http.Client // The underlying HTTP client which will be used to make HTTP requests.
Syncer Syncer // The thing which can process /sync responses
Store Storer // The thing which can store rooms/tokens/ids
- Logger maulogger.Logger
+ Logger Logger
// The ?user_id= query parameter for application services. This must be set *prior* to calling a method. If this is empty,
// no user_id parameter will be sent.
@@ -132,7 +135,6 @@ func (cli *Client) Sync() error {
filterID = resFilter.FilterID
cli.Store.SaveFilterID(cli.UserID, filterID)
}
-
for {
resSync, err := cli.SyncRequest(30000, nextBatch, filterID, false, "")
if err != nil {
@@ -337,7 +339,7 @@ func (cli *Client) RegisterGuest(req *ReqRegister) (*RespRegister, *RespUserInte
//
// This does not set credentials on the client instance. See SetCredentials() instead.
//
-// res, err := cli.RegisterDummy(&gomatrix.ReqRegister{
+// res, err := cli.RegisterDummy(&mautrix.ReqRegister{
// Username: "alice",
// Password: "wonderland",
// })
@@ -544,7 +546,7 @@ func (cli *Client) RedactEvent(roomID, eventID string, req *ReqRedact) (resp *Re
}
// CreateRoom creates a new Matrix room. See https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom
-// resp, err := cli.CreateRoom(&gomatrix.ReqCreateRoom{
+// resp, err := cli.CreateRoom(&mautrix.ReqCreateRoom{
// Preset: "public_chat",
// })
// fmt.Println("Room:", resp.RoomID)
diff --git a/vendor/maunium.net/go/gomatrix/events.go b/vendor/maunium.net/go/mautrix/events.go
index 30166cd..c974bf9 100644
--- a/vendor/maunium.net/go/gomatrix/events.go
+++ b/vendor/maunium.net/go/mautrix/events.go
@@ -1,4 +1,5 @@
-package gomatrix
+// Copyright 2018 Tulir Asokan
+package mautrix
import (
"encoding/json"
@@ -101,7 +102,7 @@ var (
// Ephemeral events
var (
EphemeralEventReceipt = EventType{"m.receipt", EphemeralEventType}
- EphemeralEventTyping = EventType{"m.receipt", EphemeralEventType}
+ EphemeralEventTyping = EventType{"m.typing", EphemeralEventType}
)
// Account data events
@@ -165,6 +166,17 @@ type Unsigned struct {
PrevSender string `json:"prev_sender,omitempty"`
ReplacesState string `json:"replaces_state,omitempty"`
Age int64 `json:"age,omitempty"`
+
+ PassiveCommand map[string]*MatchedPassiveCommand `json:"m.passive_command,omitempty"`
+}
+
+type MatchedPassiveCommand struct {
+ // Matched string `json:"matched"`
+ // Value string `json:"value"`
+ Captured [][]string `json:"captured"`
+
+ BackCompatCommand string `json:"command"`
+ BackCompatArguments map[string]string `json:"arguments"`
}
type Content struct {
@@ -182,7 +194,8 @@ type Content struct {
// Membership key for easy access in m.room.member events
Membership Membership `json:"membership,omitempty"`
- RelatesTo *RelatesTo `json:"m.relates_to,omitempty"`
+ RelatesTo *RelatesTo `json:"m.relates_to,omitempty"`
+ Command *MatchedCommand `json:"m.command,omitempty"`
PowerLevels
Member
@@ -197,14 +210,30 @@ type Content struct {
type serializableContent Content
+var DisableFancyEventParsing = false
+
func (content *Content) UnmarshalJSON(data []byte) error {
content.VeryRaw = data
- if err := json.Unmarshal(data, &content.Raw); err != nil {
+ if err := json.Unmarshal(data, &content.Raw); err != nil || DisableFancyEventParsing {
return err
}
return json.Unmarshal(data, (*serializableContent)(content))
}
+func (content *Content) GetCommand() *MatchedCommand {
+ if content.Command == nil {
+ content.Command = &MatchedCommand{}
+ }
+ return content.Command
+}
+
+func (content *Content) GetRelatesTo() *RelatesTo {
+ if content.RelatesTo == nil {
+ content.RelatesTo = &RelatesTo{}
+ }
+ return content.RelatesTo
+}
+
func (content *Content) UnmarshalPowerLevels() (pl PowerLevels, err error) {
err = json.Unmarshal(content.VeryRaw, &pl)
return
@@ -228,7 +257,7 @@ func (content *Content) GetInfo() *FileInfo {
}
type Tags map[string]struct {
- Order string `json:"order"`
+ Order json.Number `json:"order"`
}
type RoomName struct {
@@ -411,3 +440,9 @@ type InReplyTo struct {
// Not required, just for future-proofing
RoomID string `json:"room_id,omitempty"`
}
+
+type MatchedCommand struct {
+ Target string `json:"target"`
+ Matched string `json:"matched"`
+ Arguments map[string]string `json:"arguments"`
+}
diff --git a/vendor/maunium.net/go/gomatrix/filter.go b/vendor/maunium.net/go/mautrix/filter.go
index 2a0c37f..41cab2d 100644
--- a/vendor/maunium.net/go/gomatrix/filter.go
+++ b/vendor/maunium.net/go/mautrix/filter.go
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package gomatrix
+package mautrix
import "errors"
diff --git a/vendor/maunium.net/go/gomatrix/reply.go b/vendor/maunium.net/go/mautrix/reply.go
index 6985421..5e3af92 100644
--- a/vendor/maunium.net/go/gomatrix/reply.go
+++ b/vendor/maunium.net/go/mautrix/reply.go
@@ -1,4 +1,5 @@
-package gomatrix
+// Copyright 2018 Tulir Asokan
+package mautrix
import (
"fmt"
diff --git a/vendor/maunium.net/go/gomatrix/requests.go b/vendor/maunium.net/go/mautrix/requests.go
index d8e10a6..b90e6fb 100644
--- a/vendor/maunium.net/go/gomatrix/requests.go
+++ b/vendor/maunium.net/go/mautrix/requests.go
@@ -1,4 +1,4 @@
-package gomatrix
+package mautrix
// ReqRegister is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register
type ReqRegister struct {
@@ -31,7 +31,7 @@ type ReqCreateRoom struct {
Invite []string `json:"invite,omitempty"`
Invite3PID []ReqInvite3PID `json:"invite_3pid,omitempty"`
CreationContent map[string]interface{} `json:"creation_content,omitempty"`
- InitialState []*Event `json:"initial_state,omitempty"`
+ InitialState []*Event `json:"initial_state,omitempty"`
Preset string `json:"preset,omitempty"`
IsDirect bool `json:"is_direct,omitempty"`
}
@@ -79,4 +79,4 @@ type ReqTyping struct {
type ReqPresence struct {
Presence string `json:"presence"`
-} \ No newline at end of file
+}
diff --git a/vendor/maunium.net/go/gomatrix/responses.go b/vendor/maunium.net/go/mautrix/responses.go
index 9524d62..2adf90a 100644
--- a/vendor/maunium.net/go/gomatrix/responses.go
+++ b/vendor/maunium.net/go/mautrix/responses.go
@@ -1,4 +1,4 @@
-package gomatrix
+package mautrix
// RespError is the standard JSON error response from Homeservers. It also implements the Golang "error" interface.
// See http://matrix.org/docs/spec/client_server/r0.2.0.html#api-standards
@@ -63,9 +63,9 @@ type RespJoinedMembers struct {
// RespMessages is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-messages
type RespMessages struct {
- Start string `json:"start"`
+ Start string `json:"start"`
Chunk []*Event `json:"chunk"`
- End string `json:"end"`
+ End string `json:"end"`
}
// RespSendEvent is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid
@@ -146,8 +146,8 @@ type RespSync struct {
} `json:"state"`
Timeline struct {
Events []*Event `json:"events"`
- Limited bool `json:"limited"`
- PrevBatch string `json:"prev_batch"`
+ Limited bool `json:"limited"`
+ PrevBatch string `json:"prev_batch"`
} `json:"timeline"`
} `json:"leave"`
Join map[string]struct {
@@ -156,14 +156,14 @@ type RespSync struct {
} `json:"state"`
Timeline struct {
Events []*Event `json:"events"`
- Limited bool `json:"limited"`
- PrevBatch string `json:"prev_batch"`
+ Limited bool `json:"limited"`
+ PrevBatch string `json:"prev_batch"`
} `json:"timeline"`
Ephemeral struct {
- Events []*Event `json:"events"`
+ Events []*Event `json:"events"`
} `json:"ephemeral"`
AccountData struct {
- Events []*Event `json:"events"`
+ Events []*Event `json:"events"`
} `json:"account_data"`
} `json:"join"`
Invite map[string]struct {
diff --git a/vendor/maunium.net/go/gomatrix/room.go b/vendor/maunium.net/go/mautrix/room.go
index 80a91d8..086e259 100644
--- a/vendor/maunium.net/go/gomatrix/room.go
+++ b/vendor/maunium.net/go/mautrix/room.go
@@ -1,4 +1,4 @@
-package gomatrix
+package mautrix
// Room represents a single Matrix room.
type Room struct {
diff --git a/vendor/maunium.net/go/gomatrix/store.go b/vendor/maunium.net/go/mautrix/store.go
index 6dc687e..774398e 100644
--- a/vendor/maunium.net/go/gomatrix/store.go
+++ b/vendor/maunium.net/go/mautrix/store.go
@@ -1,4 +1,4 @@
-package gomatrix
+package mautrix
// Storer is an interface which must be satisfied to store client data.
//
diff --git a/vendor/maunium.net/go/gomatrix/sync.go b/vendor/maunium.net/go/mautrix/sync.go
index 09170d7..9589edc 100644
--- a/vendor/maunium.net/go/gomatrix/sync.go
+++ b/vendor/maunium.net/go/mautrix/sync.go
@@ -1,4 +1,4 @@
-package gomatrix
+package mautrix
import (
"encoding/json"
diff --git a/vendor/maunium.net/go/gomatrix/userids.go b/vendor/maunium.net/go/mautrix/userids.go
index 23e7807..ce6e02d 100644
--- a/vendor/maunium.net/go/gomatrix/userids.go
+++ b/vendor/maunium.net/go/mautrix/userids.go
@@ -1,4 +1,4 @@
-package gomatrix
+package mautrix
import (
"bytes"
@@ -125,6 +125,6 @@ func ExtractUserLocalpart(userID string) (string, error) {
}
return strings.TrimPrefix(
strings.SplitN(userID, ":", 2)[0], // @foo:bar:8448 => [ "@foo", "bar:8448" ]
- "@", // remove "@" prefix
+ "@", // remove "@" prefix
), nil
}
diff --git a/vendor/maunium.net/go/tcell/README.adoc b/vendor/maunium.net/go/tcell/README.adoc
new file mode 100644
index 0000000..3b60057
--- /dev/null
+++ b/vendor/maunium.net/go/tcell/README.adoc
@@ -0,0 +1,270 @@
+= tcell
+
+
+image:https://img.shields.io/travis/gdamore/tcell.svg?label=linux[Linux Status,link="https://travis-ci.org/gdamore/tcell"]
+image:https://img.shields.io/appveyor/ci/gdamore/tcell.svg?label=windows[Windows Status,link="https://ci.appveyor.com/project/gdamore/tcell"]
+image:https://img.shields.io/badge/license-APACHE2-blue.svg[Apache License,link="https://github.com/gdamore/tcell/blob/master/LICENSE"]
+image:https://img.shields.io/badge/gitter-join-brightgreen.svg[Gitter,link="https://gitter.im/gdamore/tcell"]
+image:https://img.shields.io/badge/godoc-reference-blue.svg[GoDoc,link="https://godoc.org/github.com/gdamore/tcell"]
+image:http://goreportcard.com/badge/gdamore/tcell[Go Report Card,link="http://goreportcard.com/report/gdamore/tcell"]
+image:https://codecov.io/gh/gdamore/tcell/branch/master/graph/badge.svg[codecov,link="https://codecov.io/gh/gdamore/tcell"]
+image:https://tidelift.com/badges/github/gdamore/tcell?style=flat[Dependencies]
+
+[cols="2",grid="none"]
+|===
+|_Tcell_ is a _Go_ package that provides a cell based view for text terminals, like _xterm_.
+It was inspired by _termbox_, but includes many additional improvements.
+a|[.right]
+image::logos/tcell.png[float="right"]
+|===
+
+## Examples
+
+* https://github.com/gdamore/proxima5[proxima5] - space shooter (https://youtu.be/jNxKTCmY_bQ[video])
+* https://github.com/gdamore/govisor[govisor] - service management UI (http://2.bp.blogspot.com/--OsvnfzSNow/Vf7aqMw3zXI/AAAAAAAAARo/uOMtOvw4Sbg/s1600/Screen%2BShot%2B2015-09-20%2Bat%2B9.08.41%2BAM.png[screenshot])
+* mouse demo - included mouse test (http://2.bp.blogspot.com/-fWvW5opT0es/VhIdItdKqJI/AAAAAAAAATE/7Ojc0L1SpB0/s1600/Screen%2BShot%2B2015-10-04%2Bat%2B11.47.13%2BPM.png[screenshot])
+* https://github.com/gdamore/gomatrix[gomatrix] - converted from Termbox
+* https://github.com/zyedidia/micro/[micro] - lightweight text editor with syntax-highlighting and themes
+* https://github.com/viktomas/godu[godu] - simple golang utility helping to discover large files/folders.
+* https://github.com/rivo/tview[tview] - rich interactive widgets for terminal UIs
+* https://github.com/marcusolsson/tui-go[tui-go] - UI library for terminal apps
+* https://github.com/rgm3/gomandelbrot[gomandelbrot] - Mandelbrot!
+* https://github.com/senorprogrammer/wtf[WTF]- Personal information dashboard for your terminal
+* https://github.com/browsh-org/browsh[browsh] - A fully-modern text-based browser, rendering to TTY and browsers (https://www.youtube.com/watch?v=HZq86XfBoRo[video])
+* https://github.com/sachaos/go-life[go-life] - Conway's Game of Life.
+
+## Pure Go Terminfo Database
+
+_Tcell_ includes a full parser and expander for terminfo capability strings,
+so that it can avoid hard coding escape strings for formatting. It also favors
+portability, and includes support for all POSIX systems.
+
+The database is also flexible & extensible, and can modified by either running
+a program to build the entire database, or an entry for just a single terminal.
+
+## More Portable
+
+_Tcell_ is portable to a wide variety of systems.
+_Tcell_ is believed
+to work with all of the systems officially supported by golang with
+the exception of nacl (which lacks any kind of a terminal interface).
+(Plan9 is not supported by _Tcell_, but it is experimental status only
+in golang.) For all of these systems *except Solaris/illumos*, _Tcell_
+is pure Go, with no need for CGO.
+
+## No Async IO
+
+_Tcell_ is able to operate without requiring `SIGIO` signals (unlike _termbox_),
+or asynchronous I/O, and can instead use standard Go file
+objects and Go routines.
+This means it should be safe, especially for
+use with programs that use exec, or otherwise need to manipulate the
+tty streams.
+This model is also much closer to idiomatic Go, leading
+to fewer surprises.
+
+## Rich Unicode & non-Unicode support
+
+_Tcell_ includes enhanced support for Unicode, including wide characters and
+combining characters, provided your terminal can support them.
+Note that
+Windows terminals generally don't support the full Unicode repertoire.
+
+It will also convert to and from Unicode locales, so that the program
+can work with UTF-8 internally, and get reasonable output in other locales.
+_Tcell_ tries hard to convert to native characters on both input and output, and
+on output _Tcell_ even makes use of the alternate character set to facilitate
+drawing certain characters.
+
+## More Function Keys
+
+_Tcell_ also has richer support for a larger number of special keys that some terminals can send.
+
+## Better Color Handling
+
+_Tcell_ will respect your terminal's color space as specified within your terminfo
+entries, so that for example attempts to emit color sequences on VT100 terminals
+won't result in unintended consequences.
+
+In Windows mode, _Tcell_ supports 16 colors, bold, dim, and reverse,
+instead of just termbox's 8 colors with reverse. (Note that there is some
+conflation with bold/dim and colors.)
+
+_Tcell_ maps 16 colors down to 8, for terminals that need it.
+(The upper 8 colors are just brighter versions of the lower 8.)
+
+## Better Mouse Support
+
+_Tcell_ supports enhanced mouse tracking mode, so your application can receive
+regular mouse motion events, and wheel events, if your terminal supports it.
+
+## _Termbox_ Compatibility
+
+A compatibility layer for _termbox_ is provided in the `compat` directory.
+To use it, try importing `github.com/gdamore/tcell/termbox`
+instead. Most _termbox-go_ programs will probably work without further
+modification.
+
+## Working With Unicode
+
+Internally Tcell uses UTF-8, just like Go.
+However, Tcell understands how to
+convert to and from other character sets, using the capabilities of
+the `golang.org/x/text/encoding packages`.
+Your application must supply
+them, as the full set of the most common ones bloats the program by about 2MB.
+If you're lazy, and want them all anyway, see the `encoding` sub-directory.
+
+## Wide & Combining Characters
+
+The `SetContent()` API takes a primary rune, and an optional list of combining runes.
+If any of the runes is a wide (East Asian) rune occupying two cells,
+then the library will skip output from the following cell, but care must be
+taken in the application to avoid explicitly attempting to set content in the
+next cell, otherwise the results are undefined. (Normally wide character
+is displayed, and the other character is not; do not depend on that behavior.)
+
+Experience has shown that the vanilla Windows 8 console application does not
+support any of these characters properly, but at least some options like
+_ConEmu_ do support Wide characters.
+
+## Colors
+
+_Tcell_ assumes the ANSI/XTerm color model, including the 256 color map that
+XTerm uses when it supports 256 colors. The terminfo guidance will be
+honored, with respect to the number of colors supported. Also, only
+terminals which expose ANSI style `setaf` and `setab` will support color;
+if you have a color terminal that only has `setf` and `setb`, please let me
+know; it wouldn't be hard to add that if there is need.
+
+## 24-bit Color
+
+_Tcell_ _supports true color_! (That is, if your terminal can support it,
+_Tcell_ can accurately display 24-bit color.)
+
+To use 24-bit color, you need to use a terminal that supports it. Modern
+xterm and similar teminal emulators can support this. As terminfo lacks any
+way to describe this capability, we fabricate the capability for
+terminals with names ending in `*-truecolor`. The stock distribution ships
+with a database that defines `xterm-truecolor`.
+To try it out, set your
+`TERM` variable to `xterm-truecolor`.
+
+When using TrueColor, programs will display the colors that the programmer
+intended, overriding any "`themes`" you may have set in your terminal
+emulator. (For some cases, accurate color fidelity is more important
+than respecting themes. For other cases, such as typical text apps that
+only use a few colors, its more desirable to respect the themes that
+the user has established.)
+
+If you find this undesirable, you can either use a `TERM` variable
+that lacks the `TRUECOLOR` setting, or set `TCELL_TRUECOLOR=disable` in your
+environment.
+
+## Performance
+
+Reasonable attempts have been made to minimize sending data to terminals,
+avoiding repeated sequences or drawing the same cell on refresh updates.
+
+## Terminfo
+
+(Not relevent for Windows users.)
+
+The Terminfo implementation operates with two forms of database. The first
+is the built-in go database, which contains a number of real database entries
+that are compiled into the program directly. This should minimize calling
+out to database file searches.
+
+The second is in the form of JSON files, that contain the same information,
+which can be located either by the `$TCELLDB` environment file, `$HOME/.tcelldb`,
+or is located in the Go source directory as `database.json`.
+
+These files (both the Go and the JSON files) can be generated using the
+mkinfo.go program. If you need to regnerate the entire set for some reason,
+run the mkdatabase.sh file. The generation uses the infocmp(1) program on
+the system to collect the necessary information.
+
+The `mkinfo.go` program can also be used to generate specific database entries
+for named terminals, in case your favorite terminal is missing. (If you
+find that this is the case, please let me know and I'll try to add it!)
+
+_Tcell_ requires that the terminal support the `cup` mode of cursor addressing.
+Terminals without absolute cursor addressability are not supported.
+This is unlikely to be a problem; such terminals have not been mass produced
+since the early 1970s.
+
+## Mouse Support
+
+Mouse support is detected via the `kmous` terminfo variable, however,
+enablement/disablement and decoding mouse events is done using hard coded
+sequences based on the XTerm X11 model. As of this writing all popular
+terminals with mouse tracking support this model. (Full terminfo support
+is not possible as terminfo sequences are not defined.)
+
+On Windows, the mouse works normally.
+
+Mouse wheel buttons on various terminals are known to work, but the support
+in terminal emulators, as well as support for various buttons and
+live mouse tracking, varies widely. Modern _xterm_, macOS _Terminal_, and _iTerm_ all work well.
+
+## Testablity
+
+There is a `SimulationScreen`, that can be used to simulate a real screen
+for automated testing. The supplied tests do this. The simulation contains
+event delivery, screen resizing support, and capabilities to inject events
+and examine "`physical`" screen contents.
+
+## Platforms
+
+### POSIX (Linux, FreeBSD, macOS, Solaris, etc.)
+
+For mainstream systems with a suitably well defined system call interface
+to tty settings, everything works using pure Go.
+
+For the remainder (right now means only Solaris/illumos) we use POSIX function
+calls to manage termios, which implies that CGO is required on those platforms.
+
+### Windows
+
+Windows console mode applications are supported. Unfortunately _mintty_
+and other _cygwin_ style applications are not supported.
+
+Modern console applications like ConEmu, as well as the Windows 10
+console itself, support all the good features (resize, mouse tracking, etc.)
+
+I haven't figured out how to cleanly resolve the dichotomy between cygwin
+style termios and the Windows Console API; it seems that perhaps nobody else
+has either. If anyone has suggestions, let me know! Really, if you're
+using a Windows application, you should use the native Windows console or a
+fully compatible console implementation.
+
+### Plan9 and Native Client (Nacl)
+
+The nacl and plan9 platforms won't work, but compilation stubs are supplied
+for folks that want to include parts of this in software targetting those
+platforms. The Simulation screen works, but as Tcell doesn't know how to
+allocate a real screen object on those platforms, `NewScreen()` will fail.
+
+If anyone has wisdom about how to improve support for either of these,
+please let me know. PRs are especially welcome.
+
+### Commercial Support
+
+_Tcell_ is absolutely free, but if you want to obtain commercial, professional support, there are options.
+
+[cols="2",align="center",frame="none", grid="none"]
+|===
+^.^|
+image:logos/tidelift.png[100,100]
+a|
+https://tidelift.com/[Tidelift] subscriptions include support for _Tcell_, as well as many other open source packages.
+
+^.^|
+image:logos/staysail.png[100,100]
+a|
+mailto:info@staysail.tech[Staysail Systems, Inc.] offers direct support, and custom development around _Tcell_ on an hourly basis.
+
+^.^|
+image:logos/patreon.png[100,100]
+a|I also welcome donations at https://www.patron.com/gedamore/[Patreon], if you just want to make a contribution.
+|===
diff --git a/vendor/maunium.net/go/tcell/cell.go b/vendor/maunium.net/go/tcell/cell.go
index 496f10f..957b62f 100644
--- a/vendor/maunium.net/go/tcell/cell.go
+++ b/vendor/maunium.net/go/tcell/cell.go
@@ -52,6 +52,10 @@ func (cb *CellBuffer) SetContent(x int, y int,
i := 0
for i < len(c.currComb) {
r := c.currComb[i]
+ if r == '\u200d' {
+ i += 2
+ continue
+ }
if runewidth.RuneWidth(r) != 0 {
// not a combining character, yank it
c.currComb = append(c.currComb[:i-1], c.currComb[i+1:]...)
@@ -175,12 +179,13 @@ func (cb *CellBuffer) Resize(w, h int) {
// Fill fills the entire cell buffer array with the specified character
// and style. Normally choose ' ' to clear the screen. This API doesn't
-// support combining characters.
+// support combining characters, or characters with a width larger than one.
func (cb *CellBuffer) Fill(r rune, style Style) {
for i := range cb.cells {
c := &cb.cells[i]
c.currMain = r
c.currComb = nil
c.currStyle = style
+ c.width = 1
}
}
diff --git a/vendor/maunium.net/go/tcell/console_win.go b/vendor/maunium.net/go/tcell/console_win.go
index 5957a17..a7507c2 100644
--- a/vendor/maunium.net/go/tcell/console_win.go
+++ b/vendor/maunium.net/go/tcell/console_win.go
@@ -28,7 +28,6 @@ type cScreen struct {
in syscall.Handle
out syscall.Handle
cancelflag syscall.Handle
- title syscall.Handle
scandone chan struct{}
evch chan Event
quit chan struct{}
diff --git a/vendor/maunium.net/go/tcell/tcell.png b/vendor/maunium.net/go/tcell/tcell.png
deleted file mode 100644
index 24333c4..0000000
--- a/vendor/maunium.net/go/tcell/tcell.png
+++ /dev/null
Binary files differ
diff --git a/vendor/maunium.net/go/tcell/tcell.svg b/vendor/maunium.net/go/tcell/tcell.svg
deleted file mode 100644
index d8695d5..0000000
--- a/vendor/maunium.net/go/tcell/tcell.svg
+++ /dev/null
@@ -1,93 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:xlink="http://www.w3.org/1999/xlink"
- id="svg8"
- version="1.1"
- viewBox="0 0 210 297"
- height="297mm"
- width="210mm">
- <defs
- id="defs2">
- <linearGradient
- id="linearGradient2680">
- <stop
- id="stop2676"
- offset="0"
- style="stop-color:#ababab;stop-opacity:1;" />
- <stop
- id="stop2678"
- offset="1"
- style="stop-color:#ababab;stop-opacity:0;" />
- </linearGradient>
- <marker
- style="overflow:visible"
- id="Arrow1Lstart"
- refX="0.0"
- refY="0.0"
- orient="auto">
- <path
- transform="scale(0.8) translate(12.5,0)"
- style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt"
- d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
- id="path848" />
- </marker>
- <radialGradient
- gradientUnits="userSpaceOnUse"
- gradientTransform="matrix(1,0,0,0.28804762,0,94.764912)"
- r="19.622099"
- fy="133.10568"
- fx="111.58373"
- cy="133.10568"
- cx="111.58373"
- id="radialGradient2684"
- xlink:href="#linearGradient2680" />
- </defs>
- <metadata
- id="metadata5">
- <rdf:RDF>
- <cc:Work
- rdf:about="">
- <dc:format>image/svg+xml</dc:format>
- <dc:type
- rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- <dc:title></dc:title>
- </cc:Work>
- </rdf:RDF>
- </metadata>
- <g
- id="layer1">
- <rect
- y="99.44445"
- x="31.750006"
- height="86.430557"
- width="129.64584"
- id="rect1130"
- style="opacity:1;fill:#5d6c53;fill-opacity:1;stroke:#244f24;stroke-width:10;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
- <text
- transform="scale(0.99941234,1.000588)"
- id="text1128"
- y="160.47581"
- x="44.689861"
- style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:46.0962944px;line-height:1.25;font-family:'Glass TTY VT220';-inkscape-font-specification:'Glass TTY VT220, Medium';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#5ef86d;fill-opacity:1;stroke:#59ff32;stroke-width:0.80994618;stroke-opacity:0.94520545;"
- xml:space="preserve"><tspan
- style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:46.0962944px;font-family:'Glass TTY VT220';-inkscape-font-specification:'Glass TTY VT220, Medium';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#5ef86d;fill-opacity:1;stroke:#59ff32;stroke-width:0.80994618;stroke-opacity:0.94520545;"
- y="160.47581"
- x="44.689861"
- id="tspan1126">tcell</tspan></text>
- <flowRoot
- style="fill:black;fill-opacity:1;stroke:none;font-family:sans-serif;font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;letter-spacing:0px;word-spacing:0px"
- id="flowRoot1132"
- xml:space="preserve"><flowRegion
- id="flowRegion1134"><rect
- y="432.51968"
- x="290"
- height="160"
- width="330"
- id="rect1136" /></flowRegion><flowPara
- id="flowPara1138"></flowPara></flowRoot> </g>
-</svg>
diff --git a/vendor/maunium.net/go/tcell/terminfo/term_termite.go b/vendor/maunium.net/go/tcell/terminfo/term_termite.go
new file mode 100644
index 0000000..8e7f683
--- /dev/null
+++ b/vendor/maunium.net/go/tcell/terminfo/term_termite.go
@@ -0,0 +1,152 @@
+// Generated automatically. DO NOT HAND-EDIT.
+
+package terminfo
+
+func init() {
+ // VTE-based terminal
+ AddTerminfo(&Terminfo{
+ Name: "xterm-termite",
+ Columns: 80,
+ Lines: 24,
+ Colors: 256,
+ Bell: "\a",
+ Clear: "\x1b[H\x1b[2J",
+ EnterCA: "\x1b[?1049h",
+ ExitCA: "\x1b[?1049l",
+ ShowCursor: "\x1b[?12l\x1b[?25h",
+ HideCursor: "\x1b[?25l",
+ AttrOff: "\x1b(B\x1b[m",
+ Underline: "\x1b[4m",
+ Bold: "\x1b[1m",
+ Dim: "\x1b[2m",
+ Reverse: "\x1b[7m",
+ EnterKeypad: "\x1b[?1h\x1b=",
+ ExitKeypad: "\x1b[?1l\x1b>",
+ SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m",
+ SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m",
+ SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m",
+ AltChars: "++,,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
+ EnterAcs: "\x1b(0",
+ ExitAcs: "\x1b(B",
+ Mouse: "\x1b[M",
+ MouseMode: "%?%p1%{1}%=%t%'h'%Pa%e%'l'%Pa%;\x1b[?1000%ga%c\x1b[?1002%ga%c\x1b[?1003%ga%c\x1b[?1006%ga%c",
+ SetCursor: "\x1b[%i%p1%d;%p2%dH",
+ CursorBack1: "\b",
+ CursorUp1: "\x1b[A",
+ KeyUp: "\x1bOA",
+ KeyDown: "\x1bOB",
+ KeyRight: "\x1bOC",
+ KeyLeft: "\x1bOD",
+ KeyInsert: "\x1b[2~",
+ KeyDelete: "\x1b[3~",
+ KeyBackspace: "\xff",
+ KeyHome: "\x1bOH",
+ KeyEnd: "\x1bOF",
+ KeyPgUp: "\x1b[5~",
+ KeyPgDn: "\x1b[6~",
+ KeyF1: "\x1bOP",
+ KeyF2: "\x1bOQ",
+ KeyF3: "\x1bOR",
+ KeyF4: "\x1bOS",
+ KeyF5: "\x1b[15~",
+ KeyF6: "\x1b[17~",
+ KeyF7: "\x1b[18~",
+ KeyF8: "\x1b[19~",
+ KeyF9: "\x1b[20~",
+ KeyF10: "\x1b[21~",
+ KeyF11: "\x1b[23~",
+ KeyF12: "\x1b[24~",
+ KeyF13: "\x1b[1;2P",
+ KeyF14: "\x1b[1;2Q",
+ KeyF15: "\x1b[1;2R",
+ KeyF16: "\x1b[1;2S",
+ KeyF17: "\x1b[15;2~",
+ KeyF18: "\x1b[17;2~",
+ KeyF19: "\x1b[18;2~",
+ KeyF20: "\x1b[19;2~",
+ KeyF21: "\x1b[20;2~",
+ KeyF22: "\x1b[21;2~",
+ KeyF23: "\x1b[23;2~",
+ KeyF24: "\x1b[24;2~",
+ KeyF25: "\x1b[1;5P",
+ KeyF26: "\x1b[1;5Q",
+ KeyF27: "\x1b[1;5R",
+ KeyF28: "\x1b[1;5S",
+ KeyF29: "\x1b[15;5~",
+ KeyF30: "\x1b[17;5~",
+ KeyF31: "\x1b[18;5~",
+ KeyF32: "\x1b[19;5~",
+ KeyF33: "\x1b[20;5~",
+ KeyF34: "\x1b[21;5~",
+ KeyF35: "\x1b[23;5~",
+ KeyF36: "\x1b[24;5~",
+ KeyF37: "\x1b[1;6P",
+ KeyF38: "\x1b[1;6Q",
+ KeyF39: "\x1b[1;6R",
+ KeyF40: "\x1b[1;6S",
+ KeyF41: "\x1b[15;6~",
+ KeyF42: "\x1b[17;6~",
+ KeyF43: "\x1b[18;6~",
+ KeyF44: "\x1b[19;6~",
+ KeyF45: "\x1b[20;6~",
+ KeyF46: "\x1b[21;6~",
+ KeyF47: "\x1b[23;6~",
+ KeyF48: "\x1b[24;6~",
+ KeyF49: "\x1b[1;3P",
+ KeyF50: "\x1b[1;3Q",
+ KeyF51: "\x1b[1;3R",
+ KeyF52: "\x1b[1;3S",
+ KeyF53: "\x1b[15;3~",
+ KeyF54: "\x1b[17;3~",
+ KeyF55: "\x1b[18;3~",
+ KeyF56: "\x1b[19;3~",
+ KeyF57: "\x1b[20;3~",
+ KeyF58: "\x1b[21;3~",
+ KeyF59: "\x1b[23;3~",
+ KeyF60: "\x1b[24;3~",
+ KeyF61: "\x1b[1;4P",
+ KeyF62: "\x1b[1;4Q",
+ KeyF63: "\x1b[1;4R",
+ KeyBacktab: "\x1b[Z",
+ KeyShfLeft: "\x1b[1;2D",
+ KeyShfRight: "\x1b[1;2C",
+ KeyShfUp: "\x1b[1;2A",
+ KeyShfDown: "\x1b[1;2B",
+ KeyCtrlLeft: "\x1b[1;5D",
+ KeyCtrlRight: "\x1b[1;5C",
+ KeyCtrlUp: "\x1b[1;5A",
+ KeyCtrlDown: "\x1b[1;5B",
+ KeyMetaLeft: "\x1b[1;9D",
+ KeyMetaRight: "\x1b[1;9C",
+ KeyMetaUp: "\x1b[1;9A",
+ KeyMetaDown: "\x1b[1;9B",
+ KeyAltLeft: "\x1b[1;3D",
+ KeyAltRight: "\x1b[1;3C",
+ KeyAltUp: "\x1b[1;3A",
+ KeyAltDown: "\x1b[1;3B",
+ KeyAltShfLeft: "\x1b[1;4D",
+ KeyAltShfRight: "\x1b[1;4C",
+ KeyAltShfUp: "\x1b[1;4A",
+ KeyAltShfDown: "\x1b[1;4B",
+ KeyMetaShfLeft: "\x1b[1;10D",
+ KeyMetaShfRight: "\x1b[1;10C",
+ KeyMetaShfUp: "\x1b[1;10A",
+ KeyMetaShfDown: "\x1b[1;10B",
+ KeyCtrlShfLeft: "\x1b[1;6D",
+ KeyCtrlShfRight: "\x1b[1;6C",
+ KeyCtrlShfUp: "\x1b[1;6A",
+ KeyCtrlShfDown: "\x1b[1;6B",
+ KeyShfHome: "\x1b[1;2H",
+ KeyShfEnd: "\x1b[1;2F",
+ KeyCtrlHome: "\x1b[1;5H",
+ KeyCtrlEnd: "\x1b[1;5F",
+ KeyAltHome: "\x1b[1;9H",
+ KeyAltEnd: "\x1b[1;9F",
+ KeyCtrlShfHome: "\x1b[1;6H",
+ KeyCtrlShfEnd: "\x1b[1;6F",
+ KeyMetaShfHome: "\x1b[1;10H",
+ KeyMetaShfEnd: "\x1b[1;10F",
+ KeyAltShfHome: "\x1b[1;4H",
+ KeyAltShfEnd: "\x1b[1;4F",
+ })
+}
diff --git a/vendor/maunium.net/go/tcell/tscreen.go b/vendor/maunium.net/go/tcell/tscreen.go
index d8e62b2..dd49814 100644
--- a/vendor/maunium.net/go/tcell/tscreen.go
+++ b/vendor/maunium.net/go/tcell/tscreen.go
@@ -444,8 +444,8 @@ func (t *tScreen) ResetTitle() {
func (t *tScreen) Fini() {
t.Lock()
defer t.Unlock()
-
- ti := t.ti
+
+ ti := t.ti
t.cells.Resize(0, 0)
t.TPuts(ti.ShowCursor)
t.TPuts(ti.AttrOff)
@@ -467,7 +467,7 @@ func (t *tScreen) Fini() {
default:
close(t.quit)
}
-
+
t.termioFini()
}
diff --git a/vendor/maunium.net/go/tview/LICENSE.txt b/vendor/maunium.net/go/tview/LICENSE.txt
index 8aa2645..9d69430 100644
--- a/vendor/maunium.net/go/tview/LICENSE.txt
+++ b/vendor/maunium.net/go/tview/LICENSE.txt
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) [year] [fullname]
+Copyright (c) 2018 Oliver Kuederle
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/vendor/maunium.net/go/tview/README.md b/vendor/maunium.net/go/tview/README.md
index 3e5734e..7ce06a2 100644
--- a/vendor/maunium.net/go/tview/README.md
+++ b/vendor/maunium.net/go/tview/README.md
@@ -12,6 +12,7 @@ Among these components are:
- __Input forms__ (include __input/password fields__, __drop-down selections__, __checkboxes__, and __buttons__)
- Navigable multi-color __text views__
- Sophisticated navigable __table views__
+- Flexible __tree views__
- Selectable __lists__
- __Grid__, __Flexbox__ and __page layouts__
- Modal __message windows__
@@ -64,9 +65,17 @@ Add your issue here on GitHub. Feel free to get in touch if you have any questio
(There are no corresponding tags in the project. I only keep such a history in this README.)
+- v0.19 (2018-10-28)
+ - Added `QueueUpdate()` and `QueueEvent()` to `Application` to help with modifications to primitives from goroutines.
+- v0.18 (2018-10-18)
+ - `InputField` elements can now be navigated freely.
+- v0.17 (2018-06-20)
+ - Added `TreeView`.
+- v0.15 (2018-05-02)
+ - `Flex` and `Grid` don't clear their background per default, thus allowing for custom modals. See the [Wiki](https://github.com/rivo/tview/wiki/Modal) for an example.
- v0.14 (2018-04-13)
- Added an `Escape()` function which keep strings like color or region tags from being recognized as such.
- - Added `ANSIIWriter()` and `TranslateANSII()` which convert ANSII escape sequences to `tview` color tags.
+ - Added `ANSIWriter()` and `TranslateANSI()` which convert ANSI escape sequences to `tview` color tags.
- v0.13 (2018-04-01)
- Added background colors and text attributes to color tags.
- v0.12 (2018-03-13)
diff --git a/vendor/maunium.net/go/tview/ansii.go b/vendor/maunium.net/go/tview/ansi.go
index 0ce3d4a..4d14c28 100644
--- a/vendor/maunium.net/go/tview/ansii.go
+++ b/vendor/maunium.net/go/tview/ansi.go
@@ -8,44 +8,44 @@ import (
"strings"
)
-// The states of the ANSII escape code parser.
+// The states of the ANSI escape code parser.
const (
- ansiiText = iota
- ansiiEscape
- ansiiSubstring
- ansiiControlSequence
+ ansiText = iota
+ ansiEscape
+ ansiSubstring
+ ansiControlSequence
)
-// ansii is a io.Writer which translates ANSII escape codes into tview color
+// ansi is a io.Writer which translates ANSI escape codes into tview color
// tags.
-type ansii struct {
+type ansi struct {
io.Writer
// Reusable buffers.
buffer *bytes.Buffer // The entire output text of one Write().
csiParameter, csiIntermediate *bytes.Buffer // Partial CSI strings.
- // The current state of the parser. One of the ansii constants.
+ // The current state of the parser. One of the ansi constants.
state int
}
-// ANSIIWriter returns an io.Writer which translates any ANSII escape codes
+// ANSIWriter returns an io.Writer which translates any ANSI escape codes
// written to it into tview color tags. Other escape codes don't have an effect
// and are simply removed. The translated text is written to the provided
// writer.
-func ANSIIWriter(writer io.Writer) io.Writer {
- return &ansii{
+func ANSIWriter(writer io.Writer) io.Writer {
+ return &ansi{
Writer: writer,
buffer: new(bytes.Buffer),
csiParameter: new(bytes.Buffer),
csiIntermediate: new(bytes.Buffer),
- state: ansiiText,
+ state: ansiText,
}
}
-// Write parses the given text as a string of runes, translates ANSII escape
+// Write parses the given text as a string of runes, translates ANSI escape
// codes to color tags and writes them to the output writer.
-func (a *ansii) Write(text []byte) (int, error) {
+func (a *ansi) Write(text []byte) (int, error) {
defer func() {
a.buffer.Reset()
}()
@@ -54,23 +54,23 @@ func (a *ansii) Write(text []byte) (int, error) {
switch a.state {
// We just entered an escape sequence.
- case ansiiEscape:
+ case ansiEscape:
switch r {
case '[': // Control Sequence Introducer.
a.csiParameter.Reset()
a.csiIntermediate.Reset()
- a.state = ansiiControlSequence
+ a.state = ansiControlSequence
case 'c': // Reset.
fmt.Fprint(a.buffer, "[-:-:-]")
- a.state = ansiiText
+ a.state = ansiText
case 'P', ']', 'X', '^', '_': // Substrings and commands.
- a.state = ansiiSubstring
+ a.state = ansiSubstring
default: // Ignore.
- a.state = ansiiText
+ a.state = ansiText
}
// CSI Sequences.
- case ansiiControlSequence:
+ case ansiControlSequence:
switch {
case r >= 0x30 && r <= 0x3f: // Parameter bytes.
if _, err := a.csiParameter.WriteRune(r); err != nil {
@@ -166,16 +166,16 @@ func (a *ansii) Write(text []byte) (int, error) {
red := (colorNumber - 16) / 36
green := ((colorNumber - 16) / 6) % 6
blue := (colorNumber - 16) % 6
- color = fmt.Sprintf("%02x%02x%02x", 255*red/5, 255*green/5, 255*blue/5)
+ color = fmt.Sprintf("#%02x%02x%02x", 255*red/5, 255*green/5, 255*blue/5)
} else if colorNumber <= 255 {
grey := 255 * (colorNumber - 232) / 23
- color = fmt.Sprintf("%02x%02x%02x", grey, grey, grey)
+ color = fmt.Sprintf("#%02x%02x%02x", grey, grey, grey)
}
} else if fields[index+1] == "2" && len(fields) > index+4 { // 24-bit colors.
red, _ := strconv.Atoi(fields[index+2])
green, _ := strconv.Atoi(fields[index+3])
blue, _ := strconv.Atoi(fields[index+4])
- color = fmt.Sprintf("%02x%02x%02x", red, green, blue)
+ color = fmt.Sprintf("#%02x%02x%02x", red, green, blue)
}
}
if len(color) > 0 {
@@ -194,22 +194,22 @@ func (a *ansii) Write(text []byte) (int, error) {
fmt.Fprintf(a.buffer, "[%s:%s%s]", foreground, background, attributes)
}
}
- a.state = ansiiText
+ a.state = ansiText
default: // Undefined byte.
- a.state = ansiiText // Abort CSI.
+ a.state = ansiText // Abort CSI.
}
// We just entered a substring/command sequence.
- case ansiiSubstring:
+ case ansiSubstring:
if r == 27 { // Most likely the end of the substring.
- a.state = ansiiEscape
+ a.state = ansiEscape
} // Ignore all other characters.
- // "ansiiText" and all others.
+ // "ansiText" and all others.
default:
if r == 27 {
// This is the start of an escape sequence.
- a.state = ansiiEscape
+ a.state = ansiEscape
} else {
// Just a regular rune. Send to buffer.
if _, err := a.buffer.WriteRune(r); err != nil {
@@ -227,11 +227,11 @@ func (a *ansii) Write(text []byte) (int, error) {
return len(text), nil
}
-// TranslateANSII replaces ANSII escape sequences found in the provided string
+// TranslateANSI replaces ANSI escape sequences found in the provided string
// with tview's color tags and returns the resulting string.
-func TranslateANSII(text string) string {
+func TranslateANSI(text string) string {
var buffer bytes.Buffer
- writer := ANSIIWriter(&buffer)
+ writer := ANSIWriter(&buffer)
writer.Write([]byte(text))
return buffer.String()
}
diff --git a/vendor/maunium.net/go/tview/application.go b/vendor/maunium.net/go/tview/application.go
index 7e6da3a..ae4d7f8 100644
--- a/vendor/maunium.net/go/tview/application.go
+++ b/vendor/maunium.net/go/tview/application.go
@@ -1,24 +1,36 @@
package tview
import (
- "fmt"
- "os"
"sync"
"maunium.net/go/tcell"
)
+// The size of the event/update/redraw channels.
+const queueSize = 100
+
// Application represents the top node of an application.
//
// It is not strictly required to use this class as none of the other classes
// depend on it. However, it provides useful tools to set up an application and
// plays nicely with all widgets.
+//
+// The following command displays a primitive p on the screen until Ctrl-C is
+// pressed:
+//
+// if err := tview.NewApplication().SetRoot(p, true).Run(); err != nil {
+// panic(err)
+// }
type Application struct {
sync.RWMutex
// The application's screen.
screen tcell.Screen
+ // Indicates whether the application's screen is currently active. This is
+ // false during suspended mode.
+ running bool
+
// The primitive which currently has the keyboard focus.
focus Primitive
@@ -48,13 +60,23 @@ type Application struct {
// was drawn.
afterDraw func(screen tcell.Screen)
- // If this value is true, the application has entered suspended mode.
- suspended bool
+ // Used to send screen events from separate goroutine to main event loop
+ events chan tcell.Event
+
+ // Functions queued from goroutines, used to serialize updates to primitives.
+ updates chan func()
+
+ // A channel which signals the end of the suspended mode.
+ suspendToken chan struct{}
}
// NewApplication creates and returns a new application.
func NewApplication() *Application {
- return &Application{}
+ return &Application{
+ events: make(chan tcell.Event, queueSize),
+ updates: make(chan func(), queueSize),
+ suspendToken: make(chan struct{}, 1),
+ }
}
// SetInputCapture sets a function which captures all key events before they are
@@ -97,140 +119,222 @@ func (a *Application) GetScreen() tcell.Screen {
return a.screen
}
+// SetScreen allows you to provide your own tcell.Screen object. For most
+// applications, this is not needed and you should be familiar with
+// tcell.Screen when using this function. Run() will call Init() and Fini() on
+// the provided screen object.
+//
+// This function is typically called before calling Run(). Calling it while an
+// application is running will switch the application to the new screen. Fini()
+// will be called on the old screen and Init() on the new screen (errors
+// returned by Init() will lead to a panic).
+//
+// Note that calling Suspend() will invoke Fini() on your screen object and it
+// will not be restored when suspended mode ends. Instead, a new default screen
+// object will be created.
+func (a *Application) SetScreen(screen tcell.Screen) *Application {
+ a.Lock()
+ defer a.Unlock()
+ if a.running {
+ a.screen.Fini()
+ }
+ a.screen = screen
+ if a.running {
+ if err := a.screen.Init(); err != nil {
+ panic(err)
+ }
+ }
+ return a
+}
+
// Run starts the application and thus the event loop. This function returns
// when Stop() was called.
func (a *Application) Run() error {
var err error
a.Lock()
- // Make a screen.
- a.screen, err = tcell.NewScreen()
- if err != nil {
- a.Unlock()
- return err
+ // Make a screen if there is none yet.
+ if a.screen == nil {
+ a.screen, err = tcell.NewScreen()
+ if err != nil {
+ a.Unlock()
+ return err
+ }
}
if err = a.screen.Init(); err != nil {
a.Unlock()
return err
}
a.screen.EnableMouse()
+ a.running = true
+
+ // We catch panics to clean up because they mess up the terminal.
+ defer func() {
+ if p := recover(); p != nil {
+ if a.screen != nil {
+ a.screen.Fini()
+ }
+ a.running = false
+ panic(p)
+ }
+ }()
// Draw the screen for the first time.
a.Unlock()
- a.Draw()
+ a.draw()
+
+ // Separate loop to wait for screen events.
+ var wg sync.WaitGroup
+ wg.Add(1)
+ a.suspendToken <- struct{}{} // We need this to get started.
+ go func() {
+ defer wg.Done()
+ for range a.suspendToken {
+ for {
+ a.RLock()
+ screen := a.screen
+ a.RUnlock()
+ if screen == nil {
+ // We have no screen. We might need to stop.
+ break
+ }
- // Start event loop.
- for {
- a.Lock()
- screen := a.screen
- if a.suspended {
- a.suspended = false // Clear previous suspended flag.
- }
- a.Unlock()
- if screen == nil {
- break
+ // Wait for next event and queue it.
+ event := screen.PollEvent()
+ if event != nil {
+ // Regular event. Queue.
+ a.QueueEvent(event)
+ continue
+ }
+
+ // A screen was finalized (event is nil).
+ a.RLock()
+ running := a.running
+ a.RUnlock()
+ if running {
+ // The application was stopped. End the event loop.
+ a.QueueEvent(nil)
+ return
+ }
+
+ // We're in suspended mode (running is false). Pause and wait for new
+ // token.
+ break
+ }
}
+ }()
- // Wait for next event.
- event := a.screen.PollEvent()
- if event == nil {
- a.Lock()
- if a.suspended {
- // This screen was renewed due to suspended mode.
- a.suspended = false
- a.Unlock()
- continue // Resume.
+ // Start event loop.
+EventLoop:
+ for {
+ select {
+ case event := <-a.events:
+ if event == nil {
+ break EventLoop
}
- a.Unlock()
- // The screen was finalized. Exit the loop.
- break
- }
+ switch event := event.(type) {
+ case *tcell.EventKey:
+ a.RLock()
+ p := a.focus
+ inputCapture := a.inputCapture
+ a.RUnlock()
+
+ // Intercept keys.
+ if inputCapture != nil {
+ event = inputCapture(event)
+ if event == nil {
+ continue // Don't forward event.
+ }
+ }
- switch event := event.(type) {
- case *tcell.EventKey:
- a.RLock()
- p := a.focus
- a.RUnlock()
-
- // Intercept keys.
- if a.inputCapture != nil {
- event = a.inputCapture(event)
- if event == nil {
- break // Don't forward event.
+ // Ctrl-C closes the application.
+ if event.Key() == tcell.KeyCtrlC {
+ a.Stop()
}
- }
- // Pass other key events to the currently focused primitive.
- if p != nil {
- if handler := p.InputHandler(); handler != nil {
- handler(event, func(p Primitive) {
- a.SetFocus(p)
- })
- a.Draw()
+ // Pass other key events to the currently focused primitive.
+ if p != nil {
+ if handler := p.InputHandler(); handler != nil {
+ handler(event, func(p Primitive) {
+ a.SetFocus(p)
+ })
+ a.draw()
+ }
}
- }
- case *tcell.EventMouse:
- a.RLock()
- p := a.focus
- a.RUnlock()
-
- // Intercept keys.
- if a.mouseCapture != nil {
- event = a.mouseCapture(event)
- if event == nil {
- break // Don't forward event.
+
+ case *tcell.EventMouse:
+ a.RLock()
+ p := a.focus
+ a.RUnlock()
+
+ // Intercept keys.
+ if a.mouseCapture != nil {
+ event = a.mouseCapture(event)
+ if event == nil {
+ break // Don't forward event.
+ }
}
- }
- // Pass other key events to the currently focused primitive.
- if p != nil {
- if handler := p.MouseHandler(); handler != nil {
- handler(event, func(p Primitive) {
- a.SetFocus(p)
- })
- //a.Draw()
+ // Pass other key events to the currently focused primitive.
+ if p != nil {
+ if handler := p.MouseHandler(); handler != nil {
+ handler(event, func(p Primitive) {
+ a.SetFocus(p)
+ })
+ //a.Draw()
+ }
}
- }
- case *tcell.EventPaste:
- a.RLock()
- p := a.focus
- a.RUnlock()
-
- if a.pasteCapture != nil {
- event = a.pasteCapture(event)
- if event == nil {
- break
+ case *tcell.EventPaste:
+ a.RLock()
+ p := a.focus
+ a.RUnlock()
+
+ if a.pasteCapture != nil {
+ event = a.pasteCapture(event)
+ if event == nil {
+ break
+ }
}
- }
- if p != nil {
- if handler := p.PasteHandler(); handler != nil {
- handler(event)
- a.Draw()
+ if p != nil {
+ if handler := p.PasteHandler(); handler != nil {
+ handler(event)
+ a.Draw()
+ }
}
+ case *tcell.EventResize:
+ a.RLock()
+ screen := a.screen
+ a.RUnlock()
+ screen.Clear()
+ a.draw()
}
- case *tcell.EventResize:
- a.Lock()
- screen := a.screen
- a.Unlock()
- screen.Clear()
- a.Draw()
+
+ // If we have updates, now is the time to execute them.
+ case updater := <-a.updates:
+ updater()
}
}
+ a.running = false
+ close(a.suspendToken)
+ wg.Wait()
+
return nil
}
// Stop stops the application, causing Run() to return.
func (a *Application) Stop() {
- a.RLock()
- defer a.RUnlock()
- if a.screen == nil {
+ a.Lock()
+ defer a.Unlock()
+ screen := a.screen
+ if screen == nil {
return
}
- a.screen.Fini()
a.screen = nil
+ screen.Fini()
+ // a.running is still true, the main loop will clean up.
}
// Suspend temporarily suspends the application by exiting terminal UI mode and
@@ -243,29 +347,24 @@ func (a *Application) Stop() {
func (a *Application) Suspend(f func()) bool {
a.Lock()
- if a.suspended || a.screen == nil {
- // Application is already suspended.
+ screen := a.screen
+ if screen == nil {
+ // Screen has not yet been initialized.
a.Unlock()
return false
}
- // Enter suspended mode.
- a.suspended = true
+ // Enter suspended mode. Make a new screen here already so our event loop can
+ // continue.
+ a.screen = nil
+ a.running = false
+ screen.Fini()
a.Unlock()
- a.Stop()
-
- // Deal with panics during suspended mode. Exit the program.
- defer func() {
- if p := recover(); p != nil {
- fmt.Println(p)
- os.Exit(1)
- }
- }()
// Wait for "f" to return.
f()
- // Make a new screen and redraw.
+ // Initialize our new screen and draw the contents.
a.Lock()
var err error
a.screen, err = tcell.NewScreen()
@@ -278,23 +377,36 @@ func (a *Application) Suspend(f func()) bool {
panic(err)
}
a.screen.EnableMouse()
+ a.running = true
a.Unlock()
- a.Draw()
+ a.draw()
+ a.suspendToken <- struct{}{}
+ // One key event will get lost, see https://github.com/gdamore/tcell/issues/194
// Continue application loop.
return true
}
-// Draw refreshes the screen. It calls the Draw() function of the application's
-// root primitive and then syncs the screen buffer.
+// Draw refreshes the screen (during the next update cycle). It calls the Draw()
+// function of the application's root primitive and then syncs the screen
+// buffer.
func (a *Application) Draw() *Application {
- a.RLock()
+ a.QueueUpdate(func() {
+ a.draw()
+ })
+ return a
+}
+
+// draw actually does what Draw() promises to do.
+func (a *Application) draw() *Application {
+ a.Lock()
+ defer a.Unlock()
+
screen := a.screen
root := a.root
fullscreen := a.rootFullscreen
before := a.beforeDraw
after := a.afterDraw
- a.RUnlock()
// Maybe we're not ready yet or not anymore.
if screen == nil || root == nil {
@@ -427,3 +539,35 @@ func (a *Application) GetFocus() Primitive {
defer a.RUnlock()
return a.focus
}
+
+// QueueUpdate is used to synchronize access to primitives from non-main
+// goroutines. The provided function will be executed as part of the event loop
+// and thus will not cause race conditions with other such update functions or
+// the Draw() function.
+//
+// Note that Draw() is not implicitly called after the execution of f as that
+// may not be desirable. You can call Draw() from f if the screen should be
+// refreshed after each update. Alternatively, use QueueUpdateDraw() to follow
+// up with an immediate refresh of the screen.
+func (a *Application) QueueUpdate(f func()) *Application {
+ a.updates <- f
+ return a
+}
+
+// QueueUpdateDraw works like QueueUpdate() except it refreshes the screen
+// immediately after executing f.
+func (a *Application) QueueUpdateDraw(f func()) *Application {
+ a.QueueUpdate(func() {
+ f()
+ a.draw()
+ })
+ return a
+}
+
+// QueueEvent sends an event to the Application event loop.
+//
+// It is not recommended for event to be nil.
+func (a *Application) QueueEvent(event tcell.Event) *Application {
+ a.events <- event
+ return a
+}
diff --git a/vendor/maunium.net/go/tview/borders.go b/vendor/maunium.net/go/tview/borders.go
new file mode 100644
index 0000000..946c878
--- /dev/null
+++ b/vendor/maunium.net/go/tview/borders.go
@@ -0,0 +1,45 @@
+package tview
+
+// Borders defines various borders used when primitives are drawn.
+// These may be changed to accommodate a different look and feel.
+var Borders = struct {
+ Horizontal rune
+ Vertical rune
+ TopLeft rune
+ TopRight rune
+ BottomLeft rune
+ BottomRight rune
+
+ LeftT rune
+ RightT rune
+ TopT rune
+ BottomT rune
+ Cross rune
+
+ HorizontalFocus rune
+ VerticalFocus rune
+ TopLeftFocus rune
+ TopRightFocus rune
+ BottomLeftFocus rune
+ BottomRightFocus rune
+}{
+ Horizontal: BoxDrawingsLightHorizontal,
+ Vertical: BoxDrawingsLightVertical,
+ TopLeft: BoxDrawingsLightDownAndRight,
+ TopRight: BoxDrawingsLightDownAndLeft,
+ BottomLeft: BoxDrawingsLightUpAndRight,
+ BottomRight: BoxDrawingsLightUpAndLeft,
+
+ LeftT: BoxDrawingsLightVerticalAndRight,
+ RightT: BoxDrawingsLightVerticalAndLeft,
+ TopT: BoxDrawingsLightDownAndHorizontal,
+ BottomT: BoxDrawingsLightUpAndHorizontal,
+ Cross: BoxDrawingsLightVerticalAndHorizontal,
+
+ HorizontalFocus: BoxDrawingsDoubleHorizontal,
+ VerticalFocus: BoxDrawingsDoubleVertical,
+ TopLeftFocus: BoxDrawingsDoubleDownAndRight,
+ TopRightFocus: BoxDrawingsDoubleDownAndLeft,
+ BottomLeftFocus: BoxDrawingsDoubleUpAndRight,
+ BottomRightFocus: BoxDrawingsDoubleUpAndLeft,
+}
diff --git a/vendor/maunium.net/go/tview/box.go b/vendor/maunium.net/go/tview/box.go
index ff3cc1e..85bf1b2 100644
--- a/vendor/maunium.net/go/tview/box.go
+++ b/vendor/maunium.net/go/tview/box.go
@@ -32,6 +32,9 @@ type Box struct {
// The color of the border.
borderColor tcell.Color
+ // The style attributes of the border.
+ borderAttributes tcell.AttrMask
+
// The title. Only visible if there is a border, too.
title string
@@ -48,10 +51,6 @@ type Box struct {
// Whether or not this box has focus.
hasFocus bool
- // If set to true, the inner rect of this box will be within the screen at the
- // last time the box was drawn.
- clampToScreen bool
-
// An optional capture function which receives a key event and returns the
// event to be forwarded to the primitive's default input handler (nil if
// nothing should be forwarded).
@@ -78,7 +77,6 @@ func NewBox() *Box {
borderColor: Styles.BorderColor,
titleColor: Styles.TitleColor,
titleAlign: AlignCenter,
- clampToScreen: true,
}
b.focus = b
return b
@@ -121,6 +119,7 @@ func (b *Box) SetRect(x, y, width, height int) {
b.y = y
b.width = width
b.height = height
+ b.innerX = -1 // Mark inner rect as uninitialized.
}
// SetDrawFunc sets a callback function which is invoked after the box primitive
@@ -273,6 +272,15 @@ func (b *Box) SetBorderColor(color tcell.Color) *Box {
return b
}
+// SetBorderAttributes sets the border's style attributes. You can combine
+// different attributes using bitmask operations:
+//
+// box.SetBorderAttributes(tcell.AttrUnderline | tcell.AttrBold)
+func (b *Box) SetBorderAttributes(attr tcell.AttrMask) *Box {
+ b.borderAttributes = attr
+ return b
+}
+
// SetTitle sets the box's title.
func (b *Box) SetTitle(title string) *Box {
b.title = title
@@ -319,30 +327,30 @@ func (b *Box) Draw(screen tcell.Screen) {
// Draw border.
if b.border && b.width >= 2 && b.height >= 2 {
- border := background.Foreground(b.borderColor)
+ border := background.Foreground(b.borderColor) | tcell.Style(b.borderAttributes)
var vertical, horizontal, topLeft, topRight, bottomLeft, bottomRight rune
if b.focus.HasFocus() {
- vertical = GraphicsDbVertBar
- horizontal = GraphicsDbHorBar
- topLeft = GraphicsDbTopLeftCorner
- topRight = GraphicsDbTopRightCorner
- bottomLeft = GraphicsDbBottomLeftCorner
- bottomRight = GraphicsDbBottomRightCorner
+ horizontal = Borders.HorizontalFocus
+ vertical = Borders.VerticalFocus
+ topLeft = Borders.TopLeftFocus
+ topRight = Borders.TopRightFocus
+ bottomLeft = Borders.BottomLeftFocus
+ bottomRight = Borders.BottomRightFocus
} else {
- vertical = GraphicsHoriBar
- horizontal = GraphicsVertBar
- topLeft = GraphicsTopLeftCorner
- topRight = GraphicsTopRightCorner
- bottomLeft = GraphicsBottomLeftCorner
- bottomRight = GraphicsBottomRightCorner
+ horizontal = Borders.Horizontal
+ vertical = Borders.Vertical
+ topLeft = Borders.TopLeft
+ topRight = Borders.TopRight
+ bottomLeft = Borders.BottomLeft
+ bottomRight = Borders.BottomRight
}
for x := b.x + 1; x < b.x+b.width-1; x++ {
- screen.SetContent(x, b.y, vertical, nil, border)
- screen.SetContent(x, b.y+b.height-1, vertical, nil, border)
+ screen.SetContent(x, b.y, horizontal, nil, border)
+ screen.SetContent(x, b.y+b.height-1, horizontal, nil, border)
}
for y := b.y + 1; y < b.y+b.height-1; y++ {
- screen.SetContent(b.x, y, horizontal, nil, border)
- screen.SetContent(b.x+b.width-1, y, horizontal, nil, border)
+ screen.SetContent(b.x, y, vertical, nil, border)
+ screen.SetContent(b.x+b.width-1, y, vertical, nil, border)
}
screen.SetContent(b.x, b.y, topLeft, nil, border)
screen.SetContent(b.x+b.width-1, b.y, topRight, nil, border)
@@ -351,11 +359,11 @@ func (b *Box) Draw(screen tcell.Screen) {
// Draw title.
if b.title != "" && b.width >= 4 {
- _, printed := Print(screen, b.title, b.x+1, b.y, b.width-2, b.titleAlign, b.titleColor)
- if StringWidth(b.title)-printed > 0 && printed > 0 {
+ printed, _ := Print(screen, b.title, b.x+1, b.y, b.width-2, b.titleAlign, b.titleColor)
+ if len(b.title)-printed > 0 && printed > 0 {
_, _, style, _ := screen.GetContent(b.x+b.width-2, b.y)
fg, _, _ := style.Decompose()
- Print(screen, string(GraphicsEllipsis), b.x+b.width-2, b.y, 1, AlignLeft, fg)
+ Print(screen, string(SemigraphicsHorizontalEllipsis), b.x+b.width-2, b.y, 1, AlignLeft, fg)
}
}
}
@@ -370,22 +378,20 @@ func (b *Box) Draw(screen tcell.Screen) {
}
// Clamp inner rect to screen.
- if b.clampToScreen {
- width, height := screen.Size()
- if b.innerX < 0 {
- b.innerWidth += b.innerX
- b.innerX = 0
- }
- if b.innerX+b.innerWidth >= width {
- b.innerWidth = width - b.innerX
- }
- if b.innerY+b.innerHeight >= height {
- b.innerHeight = height - b.innerY
- }
- if b.innerY < 0 {
- b.innerHeight += b.innerY
- b.innerY = 0
- }
+ width, height := screen.Size()
+ if b.innerX < 0 {
+ b.innerWidth += b.innerX
+ b.innerX = 0
+ }
+ if b.innerX+b.innerWidth >= width {
+ b.innerWidth = width - b.innerX
+ }
+ if b.innerY+b.innerHeight >= height {
+ b.innerHeight = height - b.innerY
+ }
+ if b.innerY < 0 {
+ b.innerHeight += b.innerY
+ b.innerY = 0
}
}
diff --git a/vendor/maunium.net/go/tview/checkbox.go b/vendor/maunium.net/go/tview/checkbox.go
index ae58720..48d4592 100644
--- a/vendor/maunium.net/go/tview/checkbox.go
+++ b/vendor/maunium.net/go/tview/checkbox.go
@@ -124,9 +124,9 @@ func (c *Checkbox) SetChangedFunc(handler func(checked bool)) *Checkbox {
return c
}
-// SetDoneFunc sets a handler which is called when the user is done entering
-// text. The callback function is provided with the key that was pressed, which
-// is one of the following:
+// SetDoneFunc sets a handler which is called when the user is done using the
+// checkbox. The callback function is provided with the key that was pressed,
+// which is one of the following:
//
// - KeyEscape: Abort text input.
// - KeyTab: Move to the next field.
diff --git a/vendor/maunium.net/go/tview/doc.go b/vendor/maunium.net/go/tview/doc.go
index ccaaaf1..ddc410f 100644
--- a/vendor/maunium.net/go/tview/doc.go
+++ b/vendor/maunium.net/go/tview/doc.go
@@ -7,10 +7,12 @@ Widgets
The package implements the following widgets:
- - TextView: Scrollable windows that display multi-colored text. Text may also
+ - TextView: A scrollable window that display multi-colored text. Text may also
be highlighted.
- - Table: Scrollable display of tabular data. Table cells, rows, or columns may
- also be highlighted.
+ - Table: A scrollable display of tabular data. Table cells, rows, or columns
+ may also be highlighted.
+ - TreeView: A scrollable display for hierarchical data. Tree nodes can be
+ highlighted, collapsed, expanded, and more.
- List: A navigable text list with optional keyboard shortcuts.
- InputField: One-line input fields to enter text.
- DropDown: Drop-down selection fields.
@@ -83,7 +85,7 @@ tag is as follows:
[<foreground>:<background>:<flags>]
-Each of the three fields can be left blank and trailing fields can be ommitted.
+Each of the three fields can be left blank and trailing fields can be omitted.
(Empty square brackets "[]", however, are not considered color tags.) Colors
that are not specified will be left unchanged. A field with just a dash ("-")
means "reset to default".
@@ -135,6 +137,27 @@ Unicode Support
This package supports unicode characters including wide characters.
+Concurrency
+
+Many functions in this package are not thread-safe. For many applications, this
+may not be an issue: If your code makes changes in response to key events, it
+will execute in the main goroutine and thus will not cause any race conditions.
+
+If you access your primitives from other goroutines, however, you will need to
+synchronize execution. The easiest way to do this is to call
+Application.QueueUpdate() or Application.QueueUpdateDraw() (see the function
+documentation for details):
+
+ go func() {
+ app.QueueUpdateDraw(func() {
+ table.SetCellSimple(0, 0, "Foo bar")
+ })
+ }()
+
+One exception to this is the io.Writer interface implemented by TextView. You
+can safely write to a TextView from any goroutine. See the TextView
+documentation for details.
+
Type Hierarchy
All widgets listed above contain the Box type. All of Box's functions are
diff --git a/vendor/maunium.net/go/tview/dropdown.go b/vendor/maunium.net/go/tview/dropdown.go
index 4e4d888..02c93bd 100644
--- a/vendor/maunium.net/go/tview/dropdown.go
+++ b/vendor/maunium.net/go/tview/dropdown.go
@@ -354,6 +354,7 @@ func (d *DropDown) InputHandler() func(event *tcell.EventKey, setFocus func(p Pr
// Hand control over to the list.
d.open = true
+ optionBefore := d.currentOption
d.list.SetSelectedFunc(func(index int, mainText, secondaryText string, shortcut rune) {
// An option was selected. Close the list again.
d.open = false
@@ -374,6 +375,10 @@ func (d *DropDown) InputHandler() func(event *tcell.EventKey, setFocus func(p Pr
d.prefix = string(r[:len(r)-1])
}
evalPrefix()
+ } else if event.Key() == tcell.KeyEscape {
+ d.open = false
+ d.currentOption = optionBefore
+ setFocus(d)
} else {
d.prefix = ""
}
diff --git a/vendor/maunium.net/go/tview/flex.go b/vendor/maunium.net/go/tview/flex.go
index ad42d3a..bc1cfe1 100644
--- a/vendor/maunium.net/go/tview/flex.go
+++ b/vendor/maunium.net/go/tview/flex.go
@@ -28,7 +28,7 @@ type Flex struct {
*Box
// The items to be positioned.
- items []flexItem
+ items []*flexItem
// FlexRow or FlexColumn.
direction int
@@ -79,7 +79,7 @@ func (f *Flex) SetFullScreen(fullScreen bool) *Flex {
// You can provide a nil value for the primitive. This will still consume screen
// space but nothing will be drawn.
func (f *Flex) AddItem(item Primitive, fixedSize, proportion int, focus bool) *Flex {
- f.items = append(f.items, flexItem{Item: item, FixedSize: fixedSize, Proportion: proportion, Focus: focus})
+ f.items = append(f.items, &flexItem{Item: item, FixedSize: fixedSize, Proportion: proportion, Focus: focus})
return f
}
@@ -94,6 +94,19 @@ func (f *Flex) RemoveItem(p Primitive) *Flex {
return f
}
+// ResizeItem sets a new size for the item(s) with the given primitive. If there
+// are multiple Flex items with the same primitive, they will all receive the
+// same size. For details regarding the size parameters, see AddItem().
+func (f *Flex) ResizeItem(p Primitive, fixedSize, proportion int) *Flex {
+ for _, item := range f.items {
+ if item.Item == p {
+ item.FixedSize = fixedSize
+ item.Proportion = proportion
+ }
+ }
+ return f
+}
+
// Draw draws this primitive onto the screen.
func (f *Flex) Draw(screen tcell.Screen) {
f.Box.Draw(screen)
diff --git a/vendor/maunium.net/go/tview/form.go b/vendor/maunium.net/go/tview/form.go
index fe0e980..e960a52 100644
--- a/vendor/maunium.net/go/tview/form.go
+++ b/vendor/maunium.net/go/tview/form.go
@@ -26,7 +26,7 @@ type FormItem interface {
// required.
GetFieldWidth() int
- // SetEnteredFunc sets the handler function for when the user finished
+ // SetFinishedFunc sets the handler function for when the user finished
// entering data into the item. The handler may receive events for the
// Enter key (we're done), the Escape key (cancel input), the Tab key (move to
// next field), and the Backtab key (move to previous field).
@@ -218,6 +218,37 @@ func (f *Form) AddButton(label string, selected func()) *Form {
return f
}
+// GetButton returns the button at the specified 0-based index. Note that
+// buttons have been specially prepared for this form and modifying some of
+// their attributes may have unintended side effects.
+func (f *Form) GetButton(index int) *Button {
+ return f.buttons[index]
+}
+
+// RemoveButton removes the button at the specified position, starting with 0
+// for the button that was added first.
+func (f *Form) RemoveButton(index int) *Form {
+ f.buttons = append(f.buttons[:index], f.buttons[index+1:]...)
+ return f
+}
+
+// GetButtonCount returns the number of buttons in this form.
+func (f *Form) GetButtonCount() int {
+ return len(f.buttons)
+}
+
+// GetButtonIndex returns the index of the button with the given label, starting
+// with 0 for the button that was added first. If no such label was found, -1
+// is returned.
+func (f *Form) GetButtonIndex(label string) int {
+ for index, button := range f.buttons {
+ if button.GetLabel() == label {
+ return index
+ }
+ }
+ return -1
+}
+
// Clear removes all input elements from the form, including the buttons if
// specified.
func (f *Form) Clear(includeButtons bool) *Form {
@@ -251,6 +282,14 @@ func (f *Form) GetFormItem(index int) FormItem {
return f.items[index]
}
+// RemoveFormItem removes the form element at the given position, starting with
+// index 0. Elements are referenced in the order they were added. Buttons are
+// not included.
+func (f *Form) RemoveFormItem(index int) *Form {
+ f.items = append(f.items[:index], f.items[index+1:]...)
+ return f
+}
+
// GetFormItemByLabel returns the first form element with the given label. If
// no such element is found, nil is returned. Buttons are not searched and will
// therefore not be returned.
@@ -263,6 +302,18 @@ func (f *Form) GetFormItemByLabel(label string) FormItem {
return nil
}
+// GetFormItemIndex returns the index of the first form element with the given
+// label. If no such element is found, -1 is returned. Buttons are not searched
+// and will therefore not be returned.
+func (f *Form) GetFormItemIndex(label string) int {
+ for index, item := range f.items {
+ if item.GetLabel() == label {
+ return index
+ }
+ }
+ return -1
+}
+
// SetCancelFunc sets a handler which is called when the user hits the Escape
// key.
func (f *Form) SetCancelFunc(callback func()) *Form {
diff --git a/vendor/maunium.net/go/tview/grid.go b/vendor/maunium.net/go/tview/grid.go
index 77797ba..d8f6c97 100644
--- a/vendor/maunium.net/go/tview/grid.go
+++ b/vendor/maunium.net/go/tview/grid.go
@@ -583,11 +583,11 @@ func (g *Grid) Draw(screen tcell.Screen) {
}
by := item.y - 1
if by >= 0 && by < height {
- PrintJoinedBorder(screen, x+bx, y+by, GraphicsHoriBar, g.bordersColor)
+ PrintJoinedSemigraphics(screen, x+bx, y+by, Borders.Horizontal, g.bordersColor)
}
by = item.y + item.h
if by >= 0 && by < height {
- PrintJoinedBorder(screen, x+bx, y+by, GraphicsHoriBar, g.bordersColor)
+ PrintJoinedSemigraphics(screen, x+bx, y+by, Borders.Horizontal, g.bordersColor)
}
}
for by := item.y; by < item.y+item.h; by++ { // Left/right lines.
@@ -596,28 +596,28 @@ func (g *Grid) Draw(screen tcell.Screen) {
}
bx := item.x - 1
if bx >= 0 && bx < width {
- PrintJoinedBorder(screen, x+bx, y+by, GraphicsVertBar, g.bordersColor)
+ PrintJoinedSemigraphics(screen, x+bx, y+by, Borders.Vertical, g.bordersColor)
}
bx = item.x + item.w
if bx >= 0 && bx < width {
- PrintJoinedBorder(screen, x+bx, y+by, GraphicsVertBar, g.bordersColor)
+ PrintJoinedSemigraphics(screen, x+bx, y+by, Borders.Vertical, g.bordersColor)
}
}
bx, by := item.x-1, item.y-1 // Top-left corner.
if bx >= 0 && bx < width && by >= 0 && by < height {
- PrintJoinedBorder(screen, x+bx, y+by, GraphicsTopLeftCorner, g.bordersColor)
+ PrintJoinedSemigraphics(screen, x+bx, y+by, Borders.TopLeft, g.bordersColor)
}
bx, by = item.x+item.w, item.y-1 // Top-right corner.
if bx >= 0 && bx < width && by >= 0 && by < height {
- PrintJoinedBorder(screen, x+bx, y+by, GraphicsTopRightCorner, g.bordersColor)
+ PrintJoinedSemigraphics(screen, x+bx, y+by, Borders.TopRight, g.bordersColor)
}
bx, by = item.x-1, item.y+item.h // Bottom-left corner.
if bx >= 0 && bx < width && by >= 0 && by < height {
- PrintJoinedBorder(screen, x+bx, y+by, GraphicsBottomLeftCorner, g.bordersColor)
+ PrintJoinedSemigraphics(screen, x+bx, y+by, Borders.BottomLeft, g.bordersColor)
}
bx, by = item.x+item.w, item.y+item.h // Bottom-right corner.
if bx >= 0 && bx < width && by >= 0 && by < height {
- PrintJoinedBorder(screen, x+bx, y+by, GraphicsBottomRightCorner, g.bordersColor)
+ PrintJoinedSemigraphics(screen, x+bx, y+by, Borders.BottomRight, g.bordersColor)
}
}
}
diff --git a/vendor/maunium.net/go/tview/inputfield.go b/vendor/maunium.net/go/tview/inputfield.go
index 73224f2..ccc66e3 100644
--- a/vendor/maunium.net/go/tview/inputfield.go
+++ b/vendor/maunium.net/go/tview/inputfield.go
@@ -11,10 +11,23 @@ import (
)
// InputField is a one-line box (three lines if there is a title) where the
-// user can enter text.
+// user can enter text. Use SetAcceptanceFunc() to accept or reject input,
+// SetChangedFunc() to listen for changes, and SetMaskCharacter() to hide input
+// from onlookers (e.g. for password input).
//
-// Use SetMaskCharacter() to hide input from onlookers (e.g. for password
-// input).
+// The following keys can be used for navigation and editing:
+//
+// - Left arrow: Move left by one character.
+// - Right arrow: Move right by one character.
+// - Home, Ctrl-A, Alt-a: Move to the beginning of the line.
+// - End, Ctrl-E, Alt-e: Move to the end of the line.
+// - Alt-left, Alt-b: Move left by one word.
+// - Alt-right, Alt-f: Move right by one word.
+// - Backspace: Delete the character before the cursor.
+// - Delete: Delete the character after the cursor.
+// - Ctrl-K: Delete from the cursor to the end of the line.
+// - Ctrl-W: Delete the last word before the cursor.
+// - Ctrl-U: Delete the entire line.
//
// See https://github.com/rivo/tview/wiki/InputField for an example.
type InputField struct {
@@ -53,6 +66,12 @@ type InputField struct {
// disables masking.
maskCharacter rune
+ // The cursor position as a byte index into the text string.
+ cursorPos int
+
+ // The number of bytes of the text string skipped ahead while drawing.
+ offset int
+
// An optional function which may reject the last character that was entered.
accept func(text string, ch rune) bool
@@ -83,6 +102,7 @@ func NewInputField() *InputField {
// SetText sets the current text of the input field.
func (i *InputField) SetText(text string) *InputField {
i.text = text
+ i.cursorPos = len(text)
if i.changed != nil {
i.changed(text)
}
@@ -174,7 +194,7 @@ func (i *InputField) SetMaskCharacter(mask rune) *InputField {
// SetAcceptanceFunc sets a handler which may reject the last character that was
// entered (by returning false).
//
-// This package defines a number of variables Prefixed with InputField which may
+// This package defines a number of variables prefixed with InputField which may
// be used for common input (e.g. numbers, maximum text length).
func (i *InputField) SetAcceptanceFunc(handler func(textToCheck string, lastChar rune) bool) *InputField {
i.accept = handler
@@ -244,54 +264,67 @@ func (i *InputField) Draw(screen tcell.Screen) {
screen.SetContent(x+index, y, ' ', nil, fieldStyle)
}
- // Draw placeholder text.
+ // Text.
+ var cursorScreenPos int
text := i.text
if text == "" && i.placeholder != "" {
- Print(screen, i.placeholder, x, y, fieldWidth, AlignLeft, i.placeholderTextColor)
+ // Draw placeholder text.
+ Print(screen, Escape(i.placeholder), x, y, fieldWidth, AlignLeft, i.placeholderTextColor)
+ i.offset = 0
} else {
// Draw entered text.
if i.maskCharacter > 0 {
text = strings.Repeat(string(i.maskCharacter), utf8.RuneCountInString(i.text))
- } else {
- text = Escape(text)
}
- fieldWidth-- // We need one cell for the cursor.
- if fieldWidth < runewidth.StringWidth(text) {
- Print(screen, text, x, y, fieldWidth, AlignRight, i.fieldTextColor)
+ stringWidth := runewidth.StringWidth(text)
+ if fieldWidth >= stringWidth {
+ // We have enough space for the full text.
+ Print(screen, Escape(text), x, y, fieldWidth, AlignLeft, i.fieldTextColor)
+ i.offset = 0
+ iterateString(text, func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
+ if textPos >= i.cursorPos {
+ return true
+ }
+ cursorScreenPos += screenWidth
+ return false
+ })
} else {
- Print(screen, text, x, y, fieldWidth, AlignLeft, i.fieldTextColor)
+ // The text doesn't fit. Where is the cursor?
+ if i.cursorPos < 0 {
+ i.cursorPos = 0
+ } else if i.cursorPos > len(text) {
+ i.cursorPos = len(text)
+ }
+ // Shift the text so the cursor is inside the field.
+ var shiftLeft int
+ if i.offset > i.cursorPos {
+ i.offset = i.cursorPos
+ } else if subWidth := runewidth.StringWidth(text[i.offset:i.cursorPos]); subWidth > fieldWidth-1 {
+ shiftLeft = subWidth - fieldWidth + 1
+ }
+ currentOffset := i.offset
+ iterateString(text, func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
+ if textPos >= currentOffset {
+ if shiftLeft > 0 {
+ i.offset = textPos + textWidth
+ shiftLeft -= screenWidth
+ } else {
+ if textPos+textWidth > i.cursorPos {
+ return true
+ }
+ cursorScreenPos += screenWidth
+ }
+ }
+ return false
+ })
+ Print(screen, Escape(text[i.offset:]), x, y, fieldWidth, AlignLeft, i.fieldTextColor)
}
}
// Set cursor.
if i.focus.HasFocus() {
- i.setCursor(screen)
- }
-}
-
-// setCursor sets the cursor position.
-func (i *InputField) setCursor(screen tcell.Screen) {
- x := i.x
- y := i.y
- rightLimit := x + i.width
- if i.border {
- x++
- y++
- rightLimit -= 2
- }
- fieldWidth := runewidth.StringWidth(i.text)
- if i.fieldWidth > 0 && fieldWidth > i.fieldWidth-1 {
- fieldWidth = i.fieldWidth - 1
- }
- if i.labelWidth > 0 {
- x += i.labelWidth + fieldWidth
- } else {
- x += StringWidth(i.label) + fieldWidth
- }
- if x >= rightLimit {
- x = rightLimit - 1
+ screen.ShowCursor(x+cursorScreenPos, y)
}
- screen.ShowCursor(x, y)
}
// InputHandler returns the handler for this primitive.
@@ -305,27 +338,101 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
}
}()
+ // Movement functions.
+ home := func() { i.cursorPos = 0 }
+ end := func() { i.cursorPos = len(i.text) }
+ moveLeft := func() {
+ iterateStringReverse(i.text[:i.cursorPos], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
+ i.cursorPos -= textWidth
+ return true
+ })
+ }
+ moveRight := func() {
+ iterateString(i.text[i.cursorPos:], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
+ i.cursorPos += textWidth
+ return true
+ })
+ }
+ moveWordLeft := func() {
+ i.cursorPos = len(regexp.MustCompile(`\S+\s*$`).ReplaceAllString(i.text[:i.cursorPos], ""))
+ }
+ moveWordRight := func() {
+ i.cursorPos = len(i.text) - len(regexp.MustCompile(`^\s*\S+\s*`).ReplaceAllString(i.text[i.cursorPos:], ""))
+ }
+
+ // Add character function. Returns whether or not the rune character is
+ // accepted.
+ add := func(r rune) bool {
+ newText := i.text[:i.cursorPos] + string(r) + i.text[i.cursorPos:]
+ if i.accept != nil {
+ return i.accept(newText, r)
+ }
+ i.text = newText
+ i.cursorPos += len(string(r))
+ return true
+ }
+
// Process key event.
switch key := event.Key(); key {
case tcell.KeyRune: // Regular character.
- newText := i.text + string(event.Rune())
- if i.accept != nil {
- if !i.accept(newText, event.Rune()) {
+ if event.Modifiers()&tcell.ModAlt > 0 {
+ // We accept some Alt- key combinations.
+ switch event.Rune() {
+ case 'a': // Home.
+ home()
+ case 'e': // End.
+ end()
+ case 'b': // Move word left.
+ moveWordLeft()
+ case 'f': // Move word right.
+ moveWordRight()
+ }
+ } else {
+ // Other keys are simply accepted as regular characters.
+ if !add(event.Rune()) {
break
}
}
- i.text = newText
case tcell.KeyCtrlU: // Delete all.
i.text = ""
+ i.cursorPos = 0
+ case tcell.KeyCtrlK: // Delete until the end of the line.
+ i.text = i.text[:i.cursorPos]
case tcell.KeyCtrlW: // Delete last word.
- lastWord := regexp.MustCompile(`\s*\S+\s*$`)
- i.text = lastWord.ReplaceAllString(i.text, "")
- case tcell.KeyBackspace, tcell.KeyBackspace2: // Delete last character.
- if len(i.text) == 0 {
- break
+ lastWord := regexp.MustCompile(`\S+\s*$`)
+ newText := lastWord.ReplaceAllString(i.text[:i.cursorPos], "") + i.text[i.cursorPos:]
+ i.cursorPos -= len(i.text) - len(newText)
+ i.text = newText
+ case tcell.KeyBackspace, tcell.KeyBackspace2: // Delete character before the cursor.
+ iterateStringReverse(i.text[:i.cursorPos], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
+ i.text = i.text[:textPos] + i.text[textPos+textWidth:]
+ i.cursorPos -= textWidth
+ return true
+ })
+ if i.offset >= i.cursorPos {
+ i.offset = 0
+ }
+ case tcell.KeyDelete: // Delete character after the cursor.
+ iterateString(i.text[i.cursorPos:], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
+ i.text = i.text[:i.cursorPos] + i.text[i.cursorPos+textWidth:]
+ return true
+ })
+ case tcell.KeyLeft:
+ if event.Modifiers()&tcell.ModAlt > 0 {
+ moveWordLeft()
+ } else {
+ moveLeft()
+ }
+ case tcell.KeyRight:
+ if event.Modifiers()&tcell.ModAlt > 0 {
+ moveWordRight()
+ } else {
+ moveRight()
}
- runes := []rune(i.text)
- i.text = string(runes[:len(runes)-1])
+ case tcell.KeyHome, tcell.KeyCtrlA:
+ home()
+ case tcell.KeyEnd, tcell.KeyCtrlE:
+ end()
case tcell.KeyEnter, tcell.KeyTab, tcell.KeyBacktab, tcell.KeyEscape: // We're done.
if i.done != nil {
i.done(key)
diff --git a/vendor/maunium.net/go/tview/list.go b/vendor/maunium.net/go/tview/list.go
index bc5be85..e8dc5dd 100644
--- a/vendor/maunium.net/go/tview/list.go
+++ b/vendor/maunium.net/go/tview/list.go
@@ -85,6 +85,19 @@ func (l *List) GetCurrentItem() int {
return l.currentItem
}
+// RemoveItem removes the item with the given index (starting at 0) from the
+// list. Does nothing if the index is out of range.
+func (l *List) RemoveItem(index int) *List {
+ if index < 0 || index >= len(l.items) {
+ return l
+ }
+ l.items = append(l.items[:index], l.items[index+1:]...)
+ if l.currentItem >= len(l.items) {
+ l.currentItem = len(l.items) - 1
+ }
+ return l
+}
+
// SetMainTextColor sets the color of the items' main text.
func (l *List) SetMainTextColor(color tcell.Color) *List {
l.mainTextColor = color
@@ -127,7 +140,7 @@ func (l *List) ShowSecondaryText(show bool) *List {
//
// This function is also called when the first item is added or when
// SetCurrentItem() is called.
-func (l *List) SetChangedFunc(handler func(int, string, string, rune)) *List {
+func (l *List) SetChangedFunc(handler func(index int, mainText string, secondaryText string, shortcut rune)) *List {
l.changed = handler
return l
}
diff --git a/vendor/maunium.net/go/tview/modal.go b/vendor/maunium.net/go/tview/modal.go
index f53a265..f5e92f1 100644
--- a/vendor/maunium.net/go/tview/modal.go
+++ b/vendor/maunium.net/go/tview/modal.go
@@ -40,6 +40,11 @@ func NewModal() *Modal {
SetButtonBackgroundColor(Styles.PrimitiveBackgroundColor).
SetButtonTextColor(Styles.PrimaryTextColor)
m.form.SetBackgroundColor(Styles.ContrastBackgroundColor).SetBorderPadding(0, 0, 0, 0)
+ m.form.SetCancelFunc(func() {
+ if m.done != nil {
+ m.done(-1, "")
+ }
+ })
m.frame = NewFrame(m.form).SetBorders(0, 0, 1, 0, 0, 0)
m.frame.SetBorder(true).
SetBackgroundColor(Styles.ContrastBackgroundColor).
@@ -81,6 +86,16 @@ func (m *Modal) AddButtons(labels []string) *Modal {
m.done(i, l)
}
})
+ button := m.form.GetButton(m.form.GetButtonCount() - 1)
+ button.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
+ switch event.Key() {
+ case tcell.KeyDown, tcell.KeyRight:
+ return tcell.NewEventKey(tcell.KeyTab, 0, tcell.ModNone)
+ case tcell.KeyUp, tcell.KeyLeft:
+ return tcell.NewEventKey(tcell.KeyBacktab, 0, tcell.ModNone)
+ }
+ return event
+ })
}(index, label)
}
return m
diff --git a/vendor/maunium.net/go/tview/semigraphics.go b/vendor/maunium.net/go/tview/semigraphics.go
new file mode 100644
index 0000000..2455c87
--- /dev/null
+++ b/vendor/maunium.net/go/tview/semigraphics.go
@@ -0,0 +1,296 @@
+package tview
+
+import "maunium.net/go/tcell"
+
+// Semigraphics provides an easy way to access unicode characters for drawing.
+//
+// Named like the unicode characters, 'Semigraphics'-prefix used if unicode block
+// isn't prefixed itself.
+const (
+ // Block: General Punctation U+2000-U+206F (http://unicode.org/charts/PDF/U2000.pdf)
+ SemigraphicsHorizontalEllipsis rune = '\u2026' // …
+
+ // Block: Box Drawing U+2500-U+257F (http://unicode.org/charts/PDF/U2500.pdf)
+ BoxDrawingsLightHorizontal rune = '\u2500' // ─
+ BoxDrawingsHeavyHorizontal rune = '\u2501' // ━
+ BoxDrawingsLightVertical rune = '\u2502' // │
+ BoxDrawingsHeavyVertical rune = '\u2503' // ┃
+ BoxDrawingsLightTripleDashHorizontal rune = '\u2504' // ┄
+ BoxDrawingsHeavyTripleDashHorizontal rune = '\u2505' // ┅
+ BoxDrawingsLightTripleDashVertical rune = '\u2506' // ┆
+ BoxDrawingsHeavyTripleDashVertical rune = '\u2507' // ┇
+ BoxDrawingsLightQuadrupleDashHorizontal rune = '\u2508' // ┈
+ BoxDrawingsHeavyQuadrupleDashHorizontal rune = '\u2509' // ┉
+ BoxDrawingsLightQuadrupleDashVertical rune = '\u250a' // ┊
+ BoxDrawingsHeavyQuadrupleDashVertical rune = '\u250b' // ┋
+ BoxDrawingsLightDownAndRight rune = '\u250c' // ┌
+ BoxDrawingsDownLighAndRightHeavy rune = '\u250d' // ┍
+ BoxDrawingsDownHeavyAndRightLight rune = '\u250e' // ┎
+ BoxDrawingsHeavyDownAndRight rune = '\u250f' // ┏
+ BoxDrawingsLightDownAndLeft rune = '\u2510' // ┐
+ BoxDrawingsDownLighAndLeftHeavy rune = '\u2511' // ┑
+ BoxDrawingsDownHeavyAndLeftLight rune = '\u2512' // ┒
+ BoxDrawingsHeavyDownAndLeft rune = '\u2513' // ┓
+ BoxDrawingsLightUpAndRight rune = '\u2514' // └
+ BoxDrawingsUpLightAndRightHeavy rune = '\u2515' // ┕
+ BoxDrawingsUpHeavyAndRightLight rune = '\u2516' // ┖
+ BoxDrawingsHeavyUpAndRight rune = '\u2517' // ┗
+ BoxDrawingsLightUpAndLeft rune = '\u2518' // ┘
+ BoxDrawingsUpLightAndLeftHeavy rune = '\u2519' // ┙
+ BoxDrawingsUpHeavyAndLeftLight rune = '\u251a' // ┚
+ BoxDrawingsHeavyUpAndLeft rune = '\u251b' // ┛
+ BoxDrawingsLightVerticalAndRight rune = '\u251c' // ├
+ BoxDrawingsVerticalLightAndRightHeavy rune = '\u251d' // ┝
+ BoxDrawingsUpHeavyAndRightDownLight rune = '\u251e' // ┞
+ BoxDrawingsDownHeacyAndRightUpLight rune = '\u251f' // ┟
+ BoxDrawingsVerticalHeavyAndRightLight rune = '\u2520' // ┠
+ BoxDrawingsDownLightAnbdRightUpHeavy rune = '\u2521' // ┡
+ BoxDrawingsUpLightAndRightDownHeavy rune = '\u2522' // ┢
+ BoxDrawingsHeavyVerticalAndRight rune = '\u2523' // ┣
+ BoxDrawingsLightVerticalAndLeft rune = '\u2524' // ┤
+ BoxDrawingsVerticalLightAndLeftHeavy rune = '\u2525' // ┥
+ BoxDrawingsUpHeavyAndLeftDownLight rune = '\u2526' // ┦
+ BoxDrawingsDownHeavyAndLeftUpLight rune = '\u2527' // ┧
+ BoxDrawingsVerticalheavyAndLeftLight rune = '\u2528' // ┨
+ BoxDrawingsDownLightAndLeftUpHeavy rune = '\u2529' // ┨
+ BoxDrawingsUpLightAndLeftDownHeavy rune = '\u252a' // ┪
+ BoxDrawingsHeavyVerticalAndLeft rune = '\u252b' // ┫
+ BoxDrawingsLightDownAndHorizontal rune = '\u252c' // ┬
+ BoxDrawingsLeftHeavyAndRightDownLight rune = '\u252d' // ┭
+ BoxDrawingsRightHeavyAndLeftDownLight rune = '\u252e' // ┮
+ BoxDrawingsDownLightAndHorizontalHeavy rune = '\u252f' // ┯
+ BoxDrawingsDownHeavyAndHorizontalLight rune = '\u2530' // ┰
+ BoxDrawingsRightLightAndLeftDownHeavy rune = '\u2531' // ┱
+ BoxDrawingsLeftLightAndRightDownHeavy rune = '\u2532' // ┲
+ BoxDrawingsHeavyDownAndHorizontal rune = '\u2533' // ┳
+ BoxDrawingsLightUpAndHorizontal rune = '\u2534' // ┴
+ BoxDrawingsLeftHeavyAndRightUpLight rune = '\u2535' // ┵
+ BoxDrawingsRightHeavyAndLeftUpLight rune = '\u2536' // ┶
+ BoxDrawingsUpLightAndHorizontalHeavy rune = '\u2537' // ┷
+ BoxDrawingsUpHeavyAndHorizontalLight rune = '\u2538' // ┸
+ BoxDrawingsRightLightAndLeftUpHeavy rune = '\u2539' // ┹
+ BoxDrawingsLeftLightAndRightUpHeavy rune = '\u253a' // ┺
+ BoxDrawingsHeavyUpAndHorizontal rune = '\u253b' // ┻
+ BoxDrawingsLightVerticalAndHorizontal rune = '\u253c' // ┼
+ BoxDrawingsLeftHeavyAndRightVerticalLight rune = '\u253d' // ┽
+ BoxDrawingsRightHeavyAndLeftVerticalLight rune = '\u253e' // ┾
+ BoxDrawingsVerticalLightAndHorizontalHeavy rune = '\u253f' // ┿
+ BoxDrawingsUpHeavyAndDownHorizontalLight rune = '\u2540' // ╀
+ BoxDrawingsDownHeavyAndUpHorizontalLight rune = '\u2541' // ╁
+ BoxDrawingsVerticalHeavyAndHorizontalLight rune = '\u2542' // ╂
+ BoxDrawingsLeftUpHeavyAndRightDownLight rune = '\u2543' // ╃
+ BoxDrawingsRightUpHeavyAndLeftDownLight rune = '\u2544' // ╄
+ BoxDrawingsLeftDownHeavyAndRightUpLight rune = '\u2545' // ╅
+ BoxDrawingsRightDownHeavyAndLeftUpLight rune = '\u2546' // ╆
+ BoxDrawingsDownLightAndUpHorizontalHeavy rune = '\u2547' // ╇
+ BoxDrawingsUpLightAndDownHorizontalHeavy rune = '\u2548' // ╈
+ BoxDrawingsRightLightAndLeftVerticalHeavy rune = '\u2549' // ╉
+ BoxDrawingsLeftLightAndRightVerticalHeavy rune = '\u254a' // ╊
+ BoxDrawingsHeavyVerticalAndHorizontal rune = '\u254b' // ╋
+ BoxDrawingsLightDoubleDashHorizontal rune = '\u254c' // ╌
+ BoxDrawingsHeavyDoubleDashHorizontal rune = '\u254d' // ╍
+ BoxDrawingsLightDoubleDashVertical rune = '\u254e' // ╎
+ BoxDrawingsHeavyDoubleDashVertical rune = '\u254f' // ╏
+ BoxDrawingsDoubleHorizontal rune = '\u2550' // ═
+ BoxDrawingsDoubleVertical rune = '\u2551' // ║
+ BoxDrawingsDownSingleAndRightDouble rune = '\u2552' // ╒
+ BoxDrawingsDownDoubleAndRightSingle rune = '\u2553' // ╓
+ BoxDrawingsDoubleDownAndRight rune = '\u2554' // ╔
+ BoxDrawingsDownSingleAndLeftDouble rune = '\u2555' // ╕
+ BoxDrawingsDownDoubleAndLeftSingle rune = '\u2556' // ╖
+ BoxDrawingsDoubleDownAndLeft rune = '\u2557' // ╗
+ BoxDrawingsUpSingleAndRightDouble rune = '\u2558' // ╘
+ BoxDrawingsUpDoubleAndRightSingle rune = '\u2559' // ╙
+ BoxDrawingsDoubleUpAndRight rune = '\u255a' // ╚
+ BoxDrawingsUpSingleAndLeftDouble rune = '\u255b' // ╛
+ BoxDrawingsUpDobuleAndLeftSingle rune = '\u255c' // ╜
+ BoxDrawingsDoubleUpAndLeft rune = '\u255d' // ╝
+ BoxDrawingsVerticalSingleAndRightDouble rune = '\u255e' // ╞
+ BoxDrawingsVerticalDoubleAndRightSingle rune = '\u255f' // ╟
+ BoxDrawingsDoubleVerticalAndRight rune = '\u2560' // ╠
+ BoxDrawingsVerticalSingleAndLeftDouble rune = '\u2561' // ╡
+ BoxDrawingsVerticalDoubleAndLeftSingle rune = '\u2562' // ╢
+ BoxDrawingsDoubleVerticalAndLeft rune = '\u2563' // ╣
+ BoxDrawingsDownSingleAndHorizontalDouble rune = '\u2564' // ╤
+ BoxDrawingsDownDoubleAndHorizontalSingle rune = '\u2565' // ╥
+ BoxDrawingsDoubleDownAndHorizontal rune = '\u2566' // ╦
+ BoxDrawingsUpSingleAndHorizontalDouble rune = '\u2567' // ╧
+ BoxDrawingsUpDoubleAndHorizontalSingle rune = '\u2568' // ╨
+ BoxDrawingsDoubleUpAndHorizontal rune = '\u2569' // ╩
+ BoxDrawingsVerticalSingleAndHorizontalDouble rune = '\u256a' // ╪
+ BoxDrawingsVerticalDoubleAndHorizontalSingle rune = '\u256b' // ╫
+ BoxDrawingsDoubleVerticalAndHorizontal rune = '\u256c' // ╬
+ BoxDrawingsLightArcDownAndRight rune = '\u256d' // ╭
+ BoxDrawingsLightArcDownAndLeft rune = '\u256e' // ╮
+ BoxDrawingsLightArcUpAndLeft rune = '\u256f' // ╯
+ BoxDrawingsLightArcUpAndRight rune = '\u2570' // ╰
+ BoxDrawingsLightDiagonalUpperRightToLowerLeft rune = '\u2571' // ╱
+ BoxDrawingsLightDiagonalUpperLeftToLowerRight rune = '\u2572' // ╲
+ BoxDrawingsLightDiagonalCross rune = '\u2573' // ╳
+ BoxDrawingsLightLeft rune = '\u2574' // ╴
+ BoxDrawingsLightUp rune = '\u2575' // ╵
+ BoxDrawingsLightRight rune = '\u2576' // ╶
+ BoxDrawingsLightDown rune = '\u2577' // ╷
+ BoxDrawingsHeavyLeft rune = '\u2578' // ╸
+ BoxDrawingsHeavyUp rune = '\u2579' // ╹
+ BoxDrawingsHeavyRight rune = '\u257a' // ╺
+ BoxDrawingsHeavyDown rune = '\u257b' // ╻
+ BoxDrawingsLightLeftAndHeavyRight rune = '\u257c' // ╼
+ BoxDrawingsLightUpAndHeavyDown rune = '\u257d' // ╽
+ BoxDrawingsHeavyLeftAndLightRight rune = '\u257e' // ╾
+ BoxDrawingsHeavyUpAndLightDown rune = '\u257f' // ╿
+)
+
+// SemigraphicJoints is a map for joining semigraphic (or otherwise) runes.
+// So far only light lines are supported but if you want to change the border
+// styling you need to provide the joints, too.
+// The matching will be sorted ascending by rune value, so you don't need to
+// provide all rune combinations,
+// e.g. (─) + (│) = (┼) will also match (│) + (─) = (┼)
+var SemigraphicJoints = map[string]rune{
+ // (─) + (│) = (┼)
+ string([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightVertical}): BoxDrawingsLightVerticalAndHorizontal,
+ // (─) + (┌) = (┬)
+ string([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightDownAndRight}): BoxDrawingsLightDownAndHorizontal,
+ // (─) + (┐) = (┬)
+ string([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightDownAndLeft}): BoxDrawingsLightDownAndHorizontal,
+ // (─) + (└) = (┴)
+ string([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightUpAndRight}): BoxDrawingsLightUpAndHorizontal,
+ // (─) + (┘) = (┴)
+ string([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightUpAndLeft}): BoxDrawingsLightUpAndHorizontal,
+ // (─) + (├) = (┼)
+ string([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightVerticalAndRight}): BoxDrawingsLightVerticalAndHorizontal,
+ // (─) + (┤) = (┼)
+ string([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightVerticalAndLeft}): BoxDrawingsLightVerticalAndHorizontal,
+ // (─) + (┬) = (┬)
+ string([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightDownAndHorizontal}): BoxDrawingsLightDownAndHorizontal,
+ // (─) + (┴) = (┴)
+ string([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightUpAndHorizontal}): BoxDrawingsLightUpAndHorizontal,
+ // (─) + (┼) = (┼)
+ string([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+
+ // (│) + (┌) = (├)
+ string([]rune{BoxDrawingsLightVertical, BoxDrawingsLightDownAndRight}): BoxDrawingsLightVerticalAndRight,
+ // (│) + (┐) = (┤)
+ string([]rune{BoxDrawingsLightVertical, BoxDrawingsLightDownAndLeft}): BoxDrawingsLightVerticalAndLeft,
+ // (│) + (└) = (├)
+ string([]rune{BoxDrawingsLightVertical, BoxDrawingsLightUpAndRight}): BoxDrawingsLightVerticalAndRight,
+ // (│) + (┘) = (┤)
+ string([]rune{BoxDrawingsLightVertical, BoxDrawingsLightUpAndLeft}): BoxDrawingsLightVerticalAndLeft,
+ // (│) + (├) = (├)
+ string([]rune{BoxDrawingsLightVertical, BoxDrawingsLightVerticalAndRight}): BoxDrawingsLightVerticalAndRight,
+ // (│) + (┤) = (┤)
+ string([]rune{BoxDrawingsLightVertical, BoxDrawingsLightVerticalAndLeft}): BoxDrawingsLightVerticalAndLeft,
+ // (│) + (┬) = (┼)
+ string([]rune{BoxDrawingsLightVertical, BoxDrawingsLightDownAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+ // (│) + (┴) = (┼)
+ string([]rune{BoxDrawingsLightVertical, BoxDrawingsLightUpAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+ // (│) + (┼) = (┼)
+ string([]rune{BoxDrawingsLightVertical, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+
+ // (┌) + (┐) = (┬)
+ string([]rune{BoxDrawingsLightDownAndRight, BoxDrawingsLightDownAndLeft}): BoxDrawingsLightDownAndHorizontal,
+ // (┌) + (└) = (├)
+ string([]rune{BoxDrawingsLightDownAndRight, BoxDrawingsLightUpAndRight}): BoxDrawingsLightVerticalAndRight,
+ // (┌) + (┘) = (┼)
+ string([]rune{BoxDrawingsLightDownAndRight, BoxDrawingsLightUpAndLeft}): BoxDrawingsLightVerticalAndHorizontal,
+ // (┌) + (├) = (├)
+ string([]rune{BoxDrawingsLightDownAndRight, BoxDrawingsLightVerticalAndRight}): BoxDrawingsLightVerticalAndRight,
+ // (┌) + (┤) = (┼)
+ string([]rune{BoxDrawingsLightDownAndRight, BoxDrawingsLightVerticalAndLeft}): BoxDrawingsLightVerticalAndHorizontal,
+ // (┌) + (┬) = (┬)
+ string([]rune{BoxDrawingsLightDownAndRight, BoxDrawingsLightDownAndHorizontal}): BoxDrawingsLightDownAndHorizontal,
+ // (┌) + (┴) = (┼)
+ string([]rune{BoxDrawingsLightDownAndRight, BoxDrawingsLightUpAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+ // (┌) + (┴) = (┼)
+ string([]rune{BoxDrawingsLightDownAndRight, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+
+ // (┐) + (└) = (┼)
+ string([]rune{BoxDrawingsLightDownAndLeft, BoxDrawingsLightUpAndRight}): BoxDrawingsLightVerticalAndHorizontal,
+ // (┐) + (┘) = (┤)
+ string([]rune{BoxDrawingsLightDownAndLeft, BoxDrawingsLightUpAndLeft}): BoxDrawingsLightVerticalAndLeft,
+ // (┐) + (├) = (┼)
+ string([]rune{BoxDrawingsLightDownAndLeft, BoxDrawingsLightVerticalAndRight}): BoxDrawingsLightVerticalAndHorizontal,
+ // (┐) + (┤) = (┤)
+ string([]rune{BoxDrawingsLightDownAndLeft, BoxDrawingsLightVerticalAndLeft}): BoxDrawingsLightVerticalAndLeft,
+ // (┐) + (┬) = (┬)
+ string([]rune{BoxDrawingsLightDownAndLeft, BoxDrawingsLightDownAndHorizontal}): BoxDrawingsLightDownAndHorizontal,
+ // (┐) + (┴) = (┼)
+ string([]rune{BoxDrawingsLightDownAndLeft, BoxDrawingsLightUpAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+ // (┐) + (┼) = (┼)
+ string([]rune{BoxDrawingsLightDownAndLeft, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+
+ // (└) + (┘) = (┴)
+ string([]rune{BoxDrawingsLightUpAndRight, BoxDrawingsLightUpAndLeft}): BoxDrawingsLightUpAndHorizontal,
+ // (└) + (├) = (├)
+ string([]rune{BoxDrawingsLightUpAndRight, BoxDrawingsLightVerticalAndRight}): BoxDrawingsLightVerticalAndRight,
+ // (└) + (┤) = (┼)
+ string([]rune{BoxDrawingsLightUpAndRight, BoxDrawingsLightVerticalAndLeft}): BoxDrawingsLightVerticalAndHorizontal,
+ // (└) + (┬) = (┼)
+ string([]rune{BoxDrawingsLightUpAndRight, BoxDrawingsLightDownAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+ // (└) + (┴) = (┴)
+ string([]rune{BoxDrawingsLightUpAndRight, BoxDrawingsLightUpAndHorizontal}): BoxDrawingsLightUpAndHorizontal,
+ // (└) + (┼) = (┼)
+ string([]rune{BoxDrawingsLightUpAndRight, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+
+ // (┘) + (├) = (┼)
+ string([]rune{BoxDrawingsLightUpAndLeft, BoxDrawingsLightVerticalAndRight}): BoxDrawingsLightVerticalAndHorizontal,
+ // (┘) + (┤) = (┤)
+ string([]rune{BoxDrawingsLightUpAndLeft, BoxDrawingsLightVerticalAndLeft}): BoxDrawingsLightVerticalAndLeft,
+ // (┘) + (┬) = (┼)
+ string([]rune{BoxDrawingsLightUpAndLeft, BoxDrawingsLightDownAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+ // (┘) + (┴) = (┴)
+ string([]rune{BoxDrawingsLightUpAndLeft, BoxDrawingsLightUpAndHorizontal}): BoxDrawingsLightUpAndHorizontal,
+ // (┘) + (┼) = (┼)
+ string([]rune{BoxDrawingsLightUpAndLeft, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+
+ // (├) + (┤) = (┼)
+ string([]rune{BoxDrawingsLightVerticalAndRight, BoxDrawingsLightVerticalAndLeft}): BoxDrawingsLightVerticalAndHorizontal,
+ // (├) + (┬) = (┼)
+ string([]rune{BoxDrawingsLightVerticalAndRight, BoxDrawingsLightDownAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+ // (├) + (┴) = (┼)
+ string([]rune{BoxDrawingsLightVerticalAndRight, BoxDrawingsLightUpAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+ // (├) + (┼) = (┼)
+ string([]rune{BoxDrawingsLightVerticalAndRight, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+
+ // (┤) + (┬) = (┼)
+ string([]rune{BoxDrawingsLightVerticalAndLeft, BoxDrawingsLightDownAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+ // (┤) + (┴) = (┼)
+ string([]rune{BoxDrawingsLightVerticalAndLeft, BoxDrawingsLightUpAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+ // (┤) + (┼) = (┼)
+ string([]rune{BoxDrawingsLightVerticalAndLeft, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+
+ // (┬) + (┴) = (┼)
+ string([]rune{BoxDrawingsLightDownAndHorizontal, BoxDrawingsLightUpAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+ // (┬) + (┼) = (┼)
+ string([]rune{BoxDrawingsLightDownAndHorizontal, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+
+ // (┴) + (┼) = (┼)
+ string([]rune{BoxDrawingsLightUpAndHorizontal, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+}
+
+// PrintJoinedSemigraphics prints a semigraphics rune into the screen at the given
+// position with the given color, joining it with any existing semigraphics
+// rune. Background colors are preserved. At this point, only regular single
+// line borders are supported.
+func PrintJoinedSemigraphics(screen tcell.Screen, x, y int, ch rune, color tcell.Color) {
+ previous, _, style, _ := screen.GetContent(x, y)
+ style = style.Foreground(color)
+
+ // What's the resulting rune?
+ var result rune
+ if ch == previous {
+ result = ch
+ } else {
+ if ch < previous {
+ previous, ch = ch, previous
+ }
+ result = SemigraphicJoints[string([]rune{previous, ch})]
+ }
+ if result == 0 {
+ result = ch
+ }
+
+ // We only print something if we have something.
+ screen.SetContent(x, y, result, nil, style)
+}
diff --git a/vendor/maunium.net/go/tview/table.go b/vendor/maunium.net/go/tview/table.go
index 2491ec7..6c636e7 100644
--- a/vendor/maunium.net/go/tview/table.go
+++ b/vendor/maunium.net/go/tview/table.go
@@ -231,6 +231,10 @@ type Table struct {
// The number of visible rows the last time the table was drawn.
visibleRows int
+ // The style of the selected rows. If this value is 0, selected rows are
+ // simply inverted.
+ selectedStyle tcell.Style
+
// An optional function which gets called when the user presses Enter on a
// selected cell. If entire rows selected, the column value is undefined.
// Likewise for entire columns.
@@ -276,9 +280,21 @@ func (t *Table) SetBordersColor(color tcell.Color) *Table {
return t
}
+// SetSelectedStyle sets a specific style for selected cells. If no such style
+// is set, per default, selected cells are inverted (i.e. their foreground and
+// background colors are swapped).
+//
+// To reset a previous setting to its default, make the following call:
+//
+// table.SetSelectedStyle(tcell.ColorDefault, tcell.ColorDefault, 0)
+func (t *Table) SetSelectedStyle(foregroundColor, backgroundColor tcell.Color, attributes tcell.AttrMask) *Table {
+ t.selectedStyle = tcell.StyleDefault.Foreground(foregroundColor).Background(backgroundColor) | tcell.Style(attributes)
+ return t
+}
+
// SetSeparator sets the character used to fill the space between two
// neighboring cells. This is a space character ' ' per default but you may
-// want to set it to GraphicsVertBar (or any other rune) if the column
+// want to set it to Borders.Vertical (or any other rune) if the column
// separation should be more visible. If cell borders are activated, this is
// ignored.
//
@@ -373,7 +389,7 @@ func (t *Table) SetDoneFunc(handler func(key tcell.Key)) *Table {
}
// SetCell sets the content of a cell the specified position. It is ok to
-// directly instantiate a TableCell object. If the cell has contain, at least
+// directly instantiate a TableCell object. If the cell has content, at least
// the Text and Color fields should be set.
//
// Note that setting cells in previously unknown rows and columns will
@@ -406,7 +422,7 @@ func (t *Table) SetCellSimple(row, column int, text string) *Table {
}
// GetCell returns the contents of the cell at the specified position. A valid
-// TableCell object is always returns but it will be uninitialized if the cell
+// TableCell object is always returned but it will be uninitialized if the cell
// was not previously set.
func (t *Table) GetCell(row, column int) *TableCell {
if row >= len(t.cells) || column >= len(t.cells[row]) {
@@ -415,6 +431,31 @@ func (t *Table) GetCell(row, column int) *TableCell {
return t.cells[row][column]
}
+// RemoveRow removes the row at the given position from the table. If there is
+// no such row, this has no effect.
+func (t *Table) RemoveRow(row int) *Table {
+ if row < 0 || row >= len(t.cells) {
+ return t
+ }
+
+ t.cells = append(t.cells[:row], t.cells[row+1:]...)
+
+ return t
+}
+
+// RemoveColumn removes the column at the given position from the table. If
+// there is no such column, this has no effect.
+func (t *Table) RemoveColumn(column int) *Table {
+ for row := range t.cells {
+ if column < 0 || column >= len(t.cells[row]) {
+ continue
+ }
+ t.cells[row] = append(t.cells[row][:column], t.cells[row][column+1:]...)
+ }
+
+ return t
+}
+
// GetRowCount returns the number of rows in the table.
func (t *Table) GetRowCount() int {
return len(t.cells)
@@ -644,7 +685,6 @@ ColumnLoop:
}
expWidth := toDistribute * expansion / expansionTotal
widths[index] += expWidth
- tableWidth += expWidth
toDistribute -= expWidth
expansionTotal -= expansion
}
@@ -668,24 +708,24 @@ ColumnLoop:
// Draw borders.
rowY *= 2
for pos := 0; pos < columnWidth && columnX+1+pos < width; pos++ {
- drawBorder(columnX+pos+1, rowY, GraphicsHoriBar)
+ drawBorder(columnX+pos+1, rowY, Borders.Horizontal)
}
- ch := GraphicsCross
+ ch := Borders.Cross
if columnIndex == 0 {
if rowY == 0 {
- ch = GraphicsTopLeftCorner
+ ch = Borders.TopLeft
} else {
- ch = GraphicsLeftT
+ ch = Borders.LeftT
}
} else if rowY == 0 {
- ch = GraphicsTopT
+ ch = Borders.TopT
}
drawBorder(columnX, rowY, ch)
rowY++
if rowY >= height {
break // No space for the text anymore.
}
- drawBorder(columnX, rowY, GraphicsVertBar)
+ drawBorder(columnX, rowY, Borders.Vertical)
} else if columnIndex > 0 {
// Draw separator.
drawBorder(columnX, rowY, t.separator)
@@ -706,18 +746,18 @@ ColumnLoop:
_, printed := printWithStyle(screen, cell.Text, x+columnX+1, y+rowY, finalWidth, cell.Align, tcell.StyleDefault.Foreground(cell.Color)|tcell.Style(cell.Attributes))
if StringWidth(cell.Text)-printed > 0 && printed > 0 {
_, _, style, _ := screen.GetContent(x+columnX+1+finalWidth-1, y+rowY)
- printWithStyle(screen, string(GraphicsEllipsis), x+columnX+1+finalWidth-1, y+rowY, 1, AlignLeft, style)
+ printWithStyle(screen, string(SemigraphicsHorizontalEllipsis), x+columnX+1+finalWidth-1, y+rowY, 1, AlignLeft, style)
}
}
// Draw bottom border.
if rowY := 2 * len(rows); t.borders && rowY < height {
for pos := 0; pos < columnWidth && columnX+1+pos < width; pos++ {
- drawBorder(columnX+pos+1, rowY, GraphicsHoriBar)
+ drawBorder(columnX+pos+1, rowY, Borders.Horizontal)
}
- ch := GraphicsBottomT
+ ch := Borders.BottomT
if columnIndex == 0 {
- ch = GraphicsBottomLeftCorner
+ ch = Borders.BottomLeft
}
drawBorder(columnX, rowY, ch)
}
@@ -730,26 +770,31 @@ ColumnLoop:
for rowY := range rows {
rowY *= 2
if rowY+1 < height {
- drawBorder(columnX, rowY+1, GraphicsVertBar)
+ drawBorder(columnX, rowY+1, Borders.Vertical)
}
- ch := GraphicsRightT
+ ch := Borders.RightT
if rowY == 0 {
- ch = GraphicsTopRightCorner
+ ch = Borders.TopRight
}
drawBorder(columnX, rowY, ch)
}
if rowY := 2 * len(rows); rowY < height {
- drawBorder(columnX, rowY, GraphicsBottomRightCorner)
+ drawBorder(columnX, rowY, Borders.BottomRight)
}
}
// Helper function which colors the background of a box.
- colorBackground := func(fromX, fromY, w, h int, backgroundColor, textColor tcell.Color, selected bool) {
+ // backgroundColor == tcell.ColorDefault => Don't color the background.
+ // textColor == tcell.ColorDefault => Don't change the text color.
+ // attr == 0 => Don't change attributes.
+ // invert == true => Ignore attr, set text to backgroundColor or t.backgroundColor;
+ // set background to textColor.
+ colorBackground := func(fromX, fromY, w, h int, backgroundColor, textColor tcell.Color, attr tcell.AttrMask, invert bool) {
for by := 0; by < h && fromY+by < y+height; by++ {
for bx := 0; bx < w && fromX+bx < x+width; bx++ {
m, c, style, _ := screen.GetContent(fromX+bx, fromY+by)
- if selected {
- fg, _, _ := style.Decompose()
+ fg, bg, a := style.Decompose()
+ if invert {
if fg == textColor || fg == t.bordersColor {
fg = backgroundColor
}
@@ -758,10 +803,16 @@ ColumnLoop:
}
style = style.Background(textColor).Foreground(fg)
} else {
- if backgroundColor == tcell.ColorDefault {
- continue
+ if backgroundColor != tcell.ColorDefault {
+ bg = backgroundColor
}
- style = style.Background(backgroundColor)
+ if textColor != tcell.ColorDefault {
+ fg = textColor
+ }
+ if attr != 0 {
+ a = attr
+ }
+ style = style.Background(bg).Foreground(fg) | tcell.Style(a)
}
screen.SetContent(fromX+bx, fromY+by, m, c, style)
}
@@ -770,11 +821,12 @@ ColumnLoop:
// Color the cell backgrounds. To avoid undesirable artefacts, we combine
// the drawing of a cell by background color, selected cells last.
- cellsByBackgroundColor := make(map[tcell.Color][]*struct {
+ type cellInfo struct {
x, y, w, h int
text tcell.Color
selected bool
- })
+ }
+ cellsByBackgroundColor := make(map[tcell.Color][]*cellInfo)
var backgroundColors []tcell.Color
for rowY, row := range rows {
columnX := 0
@@ -794,11 +846,7 @@ ColumnLoop:
columnSelected := t.columnsSelectable && !t.rowsSelectable && column == t.selectedColumn
cellSelected := !cell.NotSelectable && (columnSelected || rowSelected || t.rowsSelectable && t.columnsSelectable && column == t.selectedColumn && row == t.selectedRow)
entries, ok := cellsByBackgroundColor[cell.BackgroundColor]
- cellsByBackgroundColor[cell.BackgroundColor] = append(entries, &struct {
- x, y, w, h int
- text tcell.Color
- selected bool
- }{
+ cellsByBackgroundColor[cell.BackgroundColor] = append(entries, &cellInfo{
x: bx,
y: by,
w: bw,
@@ -822,13 +870,18 @@ ColumnLoop:
_, _, lj := c.Hcl()
return li < lj
})
+ selFg, selBg, selAttr := t.selectedStyle.Decompose()
for _, bgColor := range backgroundColors {
entries := cellsByBackgroundColor[bgColor]
for _, cell := range entries {
if cell.selected {
- defer colorBackground(cell.x, cell.y, cell.w, cell.h, bgColor, cell.text, true)
+ if t.selectedStyle != 0 {
+ defer colorBackground(cell.x, cell.y, cell.w, cell.h, selBg, selFg, selAttr, false)
+ } else {
+ defer colorBackground(cell.x, cell.y, cell.w, cell.h, bgColor, cell.text, 0, true)
+ }
} else {
- colorBackground(cell.x, cell.y, cell.w, cell.h, bgColor, cell.text, false)
+ colorBackground(cell.x, cell.y, cell.w, cell.h, bgColor, tcell.ColorDefault, 0, false)
}
}
}
diff --git a/vendor/maunium.net/go/tview/textview.go b/vendor/maunium.net/go/tview/textview.go
index 44aeb1e..63d9796 100644
--- a/vendor/maunium.net/go/tview/textview.go
+++ b/vendor/maunium.net/go/tview/textview.go
@@ -31,7 +31,7 @@ type textViewIndex struct {
// TextView is a box which displays text. It implements the io.Writer interface
// so you can stream text to it. This does not trigger a redraw automatically
// but if a handler is installed via SetChangedFunc(), you can cause it to be
-// redrawn.
+// redrawn. (See SetChangedFunc() for more details.)
//
// Navigation
//
@@ -103,6 +103,10 @@ type TextView struct {
// during re-indexing. Set to -1 if there is no current highlight.
fromHighlight, toHighlight int
+ // The screen space column of the highlight in its first line. Set to -1 if
+ // there is no current highlight.
+ posHighlight int
+
// A set of region IDs that are currently highlighted.
highlights map[string]struct{}
@@ -170,6 +174,7 @@ func NewTextView() *TextView {
align: AlignLeft,
wrap: true,
textColor: Styles.PrimaryTextColor,
+ regions: false,
dynamicColors: false,
}
}
@@ -255,8 +260,20 @@ func (t *TextView) SetRegions(regions bool) *TextView {
}
// SetChangedFunc sets a handler function which is called when the text of the
-// text view has changed. This is typically used to cause the application to
-// redraw the screen.
+// text view has changed. This is useful when text is written to this io.Writer
+// in a separate goroutine. This does not automatically cause the screen to be
+// refreshed so you may want to use the "changed" handler to redraw the screen.
+//
+// Note that to avoid race conditions or deadlocks, there are a few rules you
+// should follow:
+//
+// - You can call Application.Draw() from this handler.
+// - You can call TextView.HasFocus() from this handler.
+// - During the execution of this handler, access to any other variables from
+// this primitive or any other primitive should be queued using
+// Application.QueueUpdate().
+//
+// See package description for details on dealing with concurrency.
func (t *TextView) SetChangedFunc(handler func()) *TextView {
t.changed = handler
return t
@@ -270,6 +287,16 @@ func (t *TextView) SetDoneFunc(handler func(key tcell.Key)) *TextView {
return t
}
+// ScrollTo scrolls to the specified row and column (both starting with 0).
+func (t *TextView) ScrollTo(row, column int) *TextView {
+ if !t.scrollable {
+ return t
+ }
+ t.lineOffset = row
+ t.columnOffset = column
+ return t
+}
+
// ScrollToBeginning scrolls to the top left corner of the text if the text view
// is scrollable.
func (t *TextView) ScrollToBeginning() *TextView {
@@ -294,6 +321,12 @@ func (t *TextView) ScrollToEnd() *TextView {
return t
}
+// GetScrollOffset returns the number of rows and columns that are skipped at
+// the top left corner when the text view has been scrolled.
+func (t *TextView) GetScrollOffset() (row, column int) {
+ return t.lineOffset, t.columnOffset
+}
+
// Clear removes all text from the buffer.
func (t *TextView) Clear() *TextView {
t.buffer = nil
@@ -420,13 +453,33 @@ func (t *TextView) GetRegionText(regionID string) string {
return escapePattern.ReplaceAllString(buffer.String(), `[$1$2]`)
}
+// Focus is called when this primitive receives focus.
+func (t *TextView) Focus(delegate func(p Primitive)) {
+ // Implemented here with locking because this is used by layout primitives.
+ t.Lock()
+ defer t.Unlock()
+ t.hasFocus = true
+}
+
+// HasFocus returns whether or not this primitive has focus.
+func (t *TextView) HasFocus() bool {
+ // Implemented here with locking because this may be used in the "changed"
+ // callback.
+ t.Lock()
+ defer t.Unlock()
+ return t.hasFocus
+}
+
// Write lets us implement the io.Writer interface. Tab characters will be
// replaced with TabSize space characters. A "\n" or "\r\n" will be interpreted
// as a new line.
func (t *TextView) Write(p []byte) (n int, err error) {
// Notify at the end.
- if t.changed != nil {
- defer t.changed()
+ t.Lock()
+ changed := t.changed
+ t.Unlock()
+ if changed != nil {
+ defer changed() // Deadlocks may occur if we lock here.
}
t.Lock()
@@ -492,7 +545,7 @@ func (t *TextView) reindexBuffer(width int) {
return // Nothing has changed. We can still use the current index.
}
t.index = nil
- t.fromHighlight, t.toHighlight = -1, -1
+ t.fromHighlight, t.toHighlight, t.posHighlight = -1, -1, -1
// If there's no space, there's no index.
if width < 1 {
@@ -511,8 +564,9 @@ func (t *TextView) reindexBuffer(width int) {
colorTags [][]string
escapeIndices [][]int
)
+ strippedStr := str
if t.dynamicColors {
- colorTagIndices, colorTags, escapeIndices, str, _ = decomposeString(str)
+ colorTagIndices, colorTags, escapeIndices, strippedStr, _ = decomposeString(str)
}
// Find all regions in this line. Then remove them.
@@ -523,14 +577,12 @@ func (t *TextView) reindexBuffer(width int) {
if t.regions {
regionIndices = regionPattern.FindAllStringIndex(str, -1)
regions = regionPattern.FindAllStringSubmatch(str, -1)
- str = regionPattern.ReplaceAllString(str, "")
- if !t.dynamicColors {
- // We haven't detected escape tags yet. Do it now.
- escapeIndices = escapePattern.FindAllStringIndex(str, -1)
- str = escapePattern.ReplaceAllString(str, "[$1$2]")
- }
+ strippedStr = regionPattern.ReplaceAllString(strippedStr, "")
}
+ // We don't need the original string anymore for now.
+ str = strippedStr
+
// Split the line if required.
var splitLines []string
if t.wrap && len(str) > 0 {
@@ -574,15 +626,53 @@ func (t *TextView) reindexBuffer(width int) {
// Shift original position with tags.
lineLength := len(splitLine)
+ remainingLength := lineLength
+ tagEnd := originalPos
+ totalTagLength := 0
for {
- if colorPos < len(colorTagIndices) && colorTagIndices[colorPos][0] <= originalPos+lineLength {
+ // Which tag comes next?
+ nextTag := make([][3]int, 0, 3)
+ if colorPos < len(colorTagIndices) {
+ nextTag = append(nextTag, [3]int{colorTagIndices[colorPos][0], colorTagIndices[colorPos][1], 0}) // 0 = color tag.
+ }
+ if regionPos < len(regionIndices) {
+ nextTag = append(nextTag, [3]int{regionIndices[regionPos][0], regionIndices[regionPos][1], 1}) // 1 = region tag.
+ }
+ if escapePos < len(escapeIndices) {
+ nextTag = append(nextTag, [3]int{escapeIndices[escapePos][0], escapeIndices[escapePos][1], 2}) // 2 = escape tag.
+ }
+ minPos := -1
+ tagIndex := -1
+ for index, pair := range nextTag {
+ if minPos < 0 || pair[0] < minPos {
+ minPos = pair[0]
+ tagIndex = index
+ }
+ }
+
+ // Is the next tag in range?
+ if tagIndex < 0 || minPos >= tagEnd+remainingLength {
+ break // No. We're done with this line.
+ }
+
+ // Advance.
+ strippedTagStart := nextTag[tagIndex][0] - originalPos - totalTagLength
+ tagEnd = nextTag[tagIndex][1]
+ tagLength := tagEnd - nextTag[tagIndex][0]
+ if nextTag[tagIndex][2] == 2 {
+ tagLength = 1
+ }
+ totalTagLength += tagLength
+ remainingLength = lineLength - (tagEnd - originalPos - totalTagLength)
+
+ // Process the tag.
+ switch nextTag[tagIndex][2] {
+ case 0:
// Process color tags.
- originalPos += colorTagIndices[colorPos][1] - colorTagIndices[colorPos][0]
foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colorTags[colorPos])
colorPos++
- } else if regionPos < len(regionIndices) && regionIndices[regionPos][0] <= originalPos+lineLength {
+ case 1:
// Process region tags.
- originalPos += regionIndices[regionPos][1] - regionIndices[regionPos][0]
regionID = regions[regionPos][1]
_, highlighted = t.highlights[regionID]
@@ -591,23 +681,21 @@ func (t *TextView) reindexBuffer(width int) {
line := len(t.index)
if t.fromHighlight < 0 {
t.fromHighlight, t.toHighlight = line, line
+ t.posHighlight = runewidth.StringWidth(splitLine[:strippedTagStart])
} else if line > t.toHighlight {
t.toHighlight = line
}
}
regionPos++
- } else if escapePos < len(escapeIndices) && escapeIndices[escapePos][0] <= originalPos+lineLength {
+ case 2:
// Process escape tags.
- originalPos++
escapePos++
- } else {
- break
}
}
// Advance to next line.
- originalPos += lineLength
+ originalPos += lineLength + totalTagLength
// Append this line.
line.NextPos = originalPos
@@ -649,7 +737,7 @@ func (t *TextView) Draw(screen tcell.Screen) {
t.pageSize = height
// If the width has changed, we need to reindex.
- if width != t.lastWidth {
+ if width != t.lastWidth && t.wrap {
t.index = nil
}
t.lastWidth = width
@@ -672,6 +760,16 @@ func (t *TextView) Draw(screen tcell.Screen) {
// No, let's move to the start of the highlights.
t.lineOffset = t.fromHighlight
}
+
+ // If the highlight is too far to the right, move it to the middle.
+ if t.posHighlight-t.columnOffset > 3*width/4 {
+ t.columnOffset = t.posHighlight - width/2
+ }
+
+ // If the highlight is off-screen on the left, move it on-screen.
+ if t.posHighlight-t.columnOffset < 0 {
+ t.columnOffset = t.posHighlight - width/4
+ }
}
t.scrollToHighlights = false
@@ -737,8 +835,9 @@ func (t *TextView) Draw(screen tcell.Screen) {
colorTags [][]string
escapeIndices [][]int
)
+ strippedText := text
if t.dynamicColors {
- colorTagIndices, colorTags, escapeIndices, _, _ = decomposeString(text)
+ colorTagIndices, colorTags, escapeIndices, strippedText, _ = decomposeString(text)
}
// Get regions.
@@ -749,8 +848,10 @@ func (t *TextView) Draw(screen tcell.Screen) {
if t.regions {
regionIndices = regionPattern.FindAllStringIndex(text, -1)
regions = regionPattern.FindAllStringSubmatch(text, -1)
+ strippedText = regionPattern.ReplaceAllString(strippedText, "")
if !t.dynamicColors {
escapeIndices = escapePattern.FindAllStringIndex(text, -1)
+ strippedText = string(escapePattern.ReplaceAllString(strippedText, "[$1$2]"))
}
}
@@ -769,11 +870,29 @@ func (t *TextView) Draw(screen tcell.Screen) {
}
// Print the line.
- var currentTag, currentRegion, currentEscapeTag, skipped, runeSeqWidth int
- runeSequence := make([]rune, 0, 10)
- flush := func() {
- if len(runeSequence) == 0 {
- return
+ var colorPos, regionPos, escapePos, tagOffset, skipped int
+ iterateString(strippedText, func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
+ // Process tags.
+ for {
+ if colorPos < len(colorTags) && textPos+tagOffset >= colorTagIndices[colorPos][0] && textPos+tagOffset < colorTagIndices[colorPos][1] {
+ // Get the color.
+ foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colorTags[colorPos])
+ tagOffset += colorTagIndices[colorPos][1] - colorTagIndices[colorPos][0]
+ colorPos++
+ } else if regionPos < len(regionIndices) && textPos+tagOffset >= regionIndices[regionPos][0] && textPos+tagOffset < regionIndices[regionPos][1] {
+ // Get the region.
+ regionID = regions[regionPos][1]
+ tagOffset += regionIndices[regionPos][1] - regionIndices[regionPos][0]
+ regionPos++
+ } else {
+ break
+ }
+ }
+
+ // Skip the second-to-last character of an escape tag.
+ if escapePos < len(escapeIndices) && textPos+tagOffset == escapeIndices[escapePos][1]-2 {
+ tagOffset++
+ escapePos++
}
// Mix the existing style with the new style.
@@ -803,87 +922,30 @@ func (t *TextView) Draw(screen tcell.Screen) {
style = style.Background(fg).Foreground(bg)
}
- // Draw the character.
- var comb []rune
- if len(runeSequence) > 1 {
- // Allocate space for the combining characters only when necessary.
- comb = make([]rune, len(runeSequence)-1)
- copy(comb, runeSequence[1:])
- }
- for offset := 0; offset < runeSeqWidth; offset++ {
- screen.SetContent(x+posX+offset, y+line-t.lineOffset, runeSequence[0], comb, style)
- }
-
- // Advance.
- posX += runeSeqWidth
- runeSequence = runeSequence[:0]
- runeSeqWidth = 0
- }
- for pos, ch := range text {
- // Get the color.
- if currentTag < len(colorTags) && pos >= colorTagIndices[currentTag][0] && pos < colorTagIndices[currentTag][1] {
- flush()
- if pos == colorTagIndices[currentTag][1]-1 {
- foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colorTags[currentTag])
- currentTag++
- }
- continue
- }
-
- // Get the region.
- if currentRegion < len(regionIndices) && pos >= regionIndices[currentRegion][0] && pos < regionIndices[currentRegion][1] {
- flush()
- if pos == regionIndices[currentRegion][1]-1 {
- regionID = regions[currentRegion][1]
- currentRegion++
- }
- continue
- }
-
- // Skip the second-to-last character of an escape tag.
- if currentEscapeTag < len(escapeIndices) && pos >= escapeIndices[currentEscapeTag][0] && pos < escapeIndices[currentEscapeTag][1] {
- flush()
- if pos == escapeIndices[currentEscapeTag][1]-1 {
- currentEscapeTag++
- } else if pos == escapeIndices[currentEscapeTag][1]-2 {
- continue
- }
- }
-
- // Determine the width of this rune.
- chWidth := runewidth.RuneWidth(ch)
- if chWidth == 0 {
- // If this is not a modifier, we treat it as a space character.
- if len(runeSequence) == 0 {
- ch = ' '
- chWidth = 1
- } else {
- runeSequence = append(runeSequence, ch)
- continue
- }
- }
-
// Skip to the right.
if !t.wrap && skipped < skip {
- skipped += chWidth
- continue
+ skipped += screenWidth
+ return false
}
// Stop at the right border.
- if posX+runeSeqWidth+chWidth > width {
- break
+ if posX+screenWidth > width {
+ return true
}
- // Flush the rune sequence.
- flush()
+ // Draw the character.
+ for offset := screenWidth - 1; offset >= 0; offset-- {
+ if offset == 0 {
+ screen.SetContent(x+posX+offset, y+line-t.lineOffset, main, comb, style)
+ } else {
+ screen.SetContent(x+posX+offset, y+line-t.lineOffset, ' ', nil, style)
+ }
+ }
- // Queue this rune.
- runeSequence = append(runeSequence, ch)
- runeSeqWidth += chWidth
- }
- if posX+runeSeqWidth <= width {
- flush()
- }
+ // Advance.
+ posX += screenWidth
+ return false
+ })
}
// If this view is not scrollable, we'll purge the buffer of lines that have
diff --git a/vendor/maunium.net/go/tview/treeview.go b/vendor/maunium.net/go/tview/treeview.go
new file mode 100644
index 0000000..1b0af21
--- /dev/null
+++ b/vendor/maunium.net/go/tview/treeview.go
@@ -0,0 +1,684 @@
+package tview
+
+import (
+ "maunium.net/go/tcell"
+)
+
+// Tree navigation events.
+const (
+ treeNone int = iota
+ treeHome
+ treeEnd
+ treeUp
+ treeDown
+ treePageUp
+ treePageDown
+)
+
+// TreeNode represents one node in a tree view.
+type TreeNode struct {
+ // The reference object.
+ reference interface{}
+
+ // This node's child nodes.
+ children []*TreeNode
+
+ // The item's text.
+ text string
+
+ // The text color.
+ color tcell.Color
+
+ // Whether or not this node can be selected.
+ selectable bool
+
+ // Whether or not this node's children should be displayed.
+ expanded bool
+
+ // The additional horizontal indent of this node's text.
+ indent int
+
+ // An optional function which is called when the user selects this node.
+ selected func()
+
+ // Temporary member variables.
+ parent *TreeNode // The parent node (nil for the root).
+ level int // The hierarchy level (0 for the root, 1 for its children, and so on).
+ graphicsX int // The x-coordinate of the left-most graphics rune.
+ textX int // The x-coordinate of the first rune of the text.
+}
+
+// NewTreeNode returns a new tree node.
+func NewTreeNode(text string) *TreeNode {
+ return &TreeNode{
+ text: text,
+ color: Styles.PrimaryTextColor,
+ indent: 2,
+ expanded: true,
+ selectable: true,
+ }
+}
+
+// Walk traverses this node's subtree in depth-first, pre-order (NLR) order and
+// calls the provided callback function on each traversed node (which includes
+// this node) with the traversed node and its parent node (nil for this node).
+// The callback returns whether traversal should continue with the traversed
+// node's child nodes (true) or not recurse any deeper (false).
+func (n *TreeNode) Walk(callback func(node, parent *TreeNode) bool) *TreeNode {
+ n.parent = nil
+ nodes := []*TreeNode{n}
+ for len(nodes) > 0 {
+ // Pop the top node and process it.
+ node := nodes[len(nodes)-1]
+ nodes = nodes[:len(nodes)-1]
+ if !callback(node, node.parent) {
+ // Don't add any children.
+ continue
+ }
+
+ // Add children in reverse order.
+ for index := len(node.children) - 1; index >= 0; index-- {
+ node.children[index].parent = node
+ nodes = append(nodes, node.children[index])
+ }
+ }
+
+ return n
+}
+
+// SetReference allows you to store a reference of any type in this node. This
+// will allow you to establish a mapping between the TreeView hierarchy and your
+// internal tree structure.
+func (n *TreeNode) SetReference(reference interface{}) *TreeNode {
+ n.reference = reference
+ return n
+}
+
+// GetReference returns this node's reference object.
+func (n *TreeNode) GetReference() interface{} {
+ return n.reference
+}
+
+// SetChildren sets this node's child nodes.
+func (n *TreeNode) SetChildren(childNodes []*TreeNode) *TreeNode {
+ n.children = childNodes
+ return n
+}
+
+// GetChildren returns this node's children.
+func (n *TreeNode) GetChildren() []*TreeNode {
+ return n.children
+}
+
+// ClearChildren removes all child nodes from this node.
+func (n *TreeNode) ClearChildren() *TreeNode {
+ n.children = nil
+ return n
+}
+
+// AddChild adds a new child node to this node.
+func (n *TreeNode) AddChild(node *TreeNode) *TreeNode {
+ n.children = append(n.children, node)
+ return n
+}
+
+// SetSelectable sets a flag indicating whether this node can be selected by
+// the user.
+func (n *TreeNode) SetSelectable(selectable bool) *TreeNode {
+ n.selectable = selectable
+ return n
+}
+
+// SetSelectedFunc sets a function which is called when the user selects this
+// node by hitting Enter when it is selected.
+func (n *TreeNode) SetSelectedFunc(handler func()) *TreeNode {
+ n.selected = handler
+ return n
+}
+
+// SetExpanded sets whether or not this node's child nodes should be displayed.
+func (n *TreeNode) SetExpanded(expanded bool) *TreeNode {
+ n.expanded = expanded
+ return n
+}
+
+// Expand makes the child nodes of this node appear.
+func (n *TreeNode) Expand() *TreeNode {
+ n.expanded = true
+ return n
+}
+
+// Collapse makes the child nodes of this node disappear.
+func (n *TreeNode) Collapse() *TreeNode {
+ n.expanded = false
+ return n
+}
+
+// ExpandAll expands this node and all descendent nodes.
+func (n *TreeNode) ExpandAll() *TreeNode {
+ n.Walk(func(node, parent *TreeNode) bool {
+ node.expanded = true
+ return true
+ })
+ return n
+}
+
+// CollapseAll collapses this node and all descendent nodes.
+func (n *TreeNode) CollapseAll() *TreeNode {
+ n.Walk(func(node, parent *TreeNode) bool {
+ n.expanded = false
+ return true
+ })
+ return n
+}
+
+// IsExpanded returns whether the child nodes of this node are visible.
+func (n *TreeNode) IsExpanded() bool {
+ return n.expanded
+}
+
+// SetText sets the node's text which is displayed.
+func (n *TreeNode) SetText(text string) *TreeNode {
+ n.text = text
+ return n
+}
+
+// SetColor sets the node's text color.
+func (n *TreeNode) SetColor(color tcell.Color) *TreeNode {
+ n.color = color
+ return n
+}
+
+// SetIndent sets an additional indentation for this node's text. A value of 0
+// keeps the text as far left as possible with a minimum of line graphics. Any
+// value greater than that moves the text to the right.
+func (n *TreeNode) SetIndent(indent int) *TreeNode {
+ n.indent = indent
+ return n
+}
+
+// TreeView displays tree structures. A tree consists of nodes (TreeNode
+// objects) where each node has zero or more child nodes and exactly one parent
+// node (except for the root node which has no parent node).
+//
+// The SetRoot() function is used to specify the root of the tree. Other nodes
+// are added locally to the root node or any of its descendents. See the
+// TreeNode documentation for details on node attributes. (You can use
+// SetReference() to store a reference to nodes of your own tree structure.)
+//
+// Nodes can be selected by calling SetCurrentNode(). The user can navigate the
+// selection or the tree by using the following keys:
+//
+// - j, down arrow, right arrow: Move (the selection) down by one node.
+// - k, up arrow, left arrow: Move (the selection) up by one node.
+// - g, home: Move (the selection) to the top.
+// - G, end: Move (the selection) to the bottom.
+// - Ctrl-F, page down: Move (the selection) down by one page.
+// - Ctrl-B, page up: Move (the selection) up by one page.
+//
+// Selected nodes can trigger the "selected" callback when the user hits Enter.
+//
+// The root node corresponds to level 0, its children correspond to level 1,
+// their children to level 2, and so on. Per default, the first level that is
+// displayed is 0, i.e. the root node. You can call SetTopLevel() to hide
+// levels.
+//
+// If graphics are turned on (see SetGraphics()), lines indicate the tree's
+// hierarchy. Alternative (or additionally), you can set different prefixes
+// using SetPrefixes() for different levels, for example to display hierarchical
+// bullet point lists.
+//
+// See https://github.com/rivo/tview/wiki/TreeView for an example.
+type TreeView struct {
+ *Box
+
+ // The root node.
+ root *TreeNode
+
+ // The currently selected node or nil if no node is selected.
+ currentNode *TreeNode
+
+ // The movement to be performed during the call to Draw(), one of the
+ // constants defined above.
+ movement int
+
+ // The top hierarchical level shown. (0 corresponds to the root level.)
+ topLevel int
+
+ // Strings drawn before the nodes, based on their level.
+ prefixes []string
+
+ // Vertical scroll offset.
+ offsetY int
+
+ // If set to true, all node texts will be aligned horizontally.
+ align bool
+
+ // If set to true, the tree structure is drawn using lines.
+ graphics bool
+
+ // The color of the lines.
+ graphicsColor tcell.Color
+
+ // An optional function which is called when the user has navigated to a new
+ // tree node.
+ changed func(node *TreeNode)
+
+ // An optional function which is called when a tree item was selected.
+ selected func(node *TreeNode)
+
+ // The visible nodes, top-down, as set by process().
+ nodes []*TreeNode
+}
+
+// NewTreeView returns a new tree view.
+func NewTreeView() *TreeView {
+ return &TreeView{
+ Box: NewBox(),
+ graphics: true,
+ graphicsColor: Styles.GraphicsColor,
+ }
+}
+
+// SetRoot sets the root node of the tree.
+func (t *TreeView) SetRoot(root *TreeNode) *TreeView {
+ t.root = root
+ return t
+}
+
+// GetRoot returns the root node of the tree. If no such node was previously
+// set, nil is returned.
+func (t *TreeView) GetRoot() *TreeNode {
+ return t.root
+}
+
+// SetCurrentNode sets the currently selected node. Provide nil to clear all
+// selections. Selected nodes must be visible and selectable, or else the
+// selection will be changed to the top-most selectable and visible node.
+//
+// This function does NOT trigger the "changed" callback.
+func (t *TreeView) SetCurrentNode(node *TreeNode) *TreeView {
+ t.currentNode = node
+ return t
+}
+
+// GetCurrentNode returns the currently selected node or nil of no node is
+// currently selected.
+func (t *TreeView) GetCurrentNode() *TreeNode {
+ return t.currentNode
+}
+
+// SetTopLevel sets the first tree level that is visible with 0 referring to the
+// root, 1 to the root's child nodes, and so on. Nodes above the top level are
+// not displayed.
+func (t *TreeView) SetTopLevel(topLevel int) *TreeView {
+ t.topLevel = topLevel
+ return t
+}
+
+// SetPrefixes defines the strings drawn before the nodes' texts. This is a
+// slice of strings where each element corresponds to a node's hierarchy level,
+// i.e. 0 for the root, 1 for the root's children, and so on (levels will
+// cycle).
+//
+// For example, to display a hierarchical list with bullet points:
+//
+// treeView.SetGraphics(false).
+// SetPrefixes([]string{"* ", "- ", "x "})
+func (t *TreeView) SetPrefixes(prefixes []string) *TreeView {
+ t.prefixes = prefixes
+ return t
+}
+
+// SetAlign controls the horizontal alignment of the node texts. If set to true,
+// all texts except that of top-level nodes will be placed in the same column.
+// If set to false, they will indent with the hierarchy.
+func (t *TreeView) SetAlign(align bool) *TreeView {
+ t.align = align
+ return t
+}
+
+// SetGraphics sets a flag which determines whether or not line graphics are
+// drawn to illustrate the tree's hierarchy.
+func (t *TreeView) SetGraphics(showGraphics bool) *TreeView {
+ t.graphics = showGraphics
+ return t
+}
+
+// SetGraphicsColor sets the colors of the lines used to draw the tree structure.
+func (t *TreeView) SetGraphicsColor(color tcell.Color) *TreeView {
+ t.graphicsColor = color
+ return t
+}
+
+// SetChangedFunc sets the function which is called when the user navigates to
+// a new tree node.
+func (t *TreeView) SetChangedFunc(handler func(node *TreeNode)) *TreeView {
+ t.changed = handler
+ return t
+}
+
+// SetSelectedFunc sets the function which is called when the user selects a
+// node by pressing Enter on the current selection.
+func (t *TreeView) SetSelectedFunc(handler func(node *TreeNode)) *TreeView {
+ t.selected = handler
+ return t
+}
+
+// process builds the visible tree, populates the "nodes" slice, and processes
+// pending selection actions.
+func (t *TreeView) process() {
+ _, _, _, height := t.GetInnerRect()
+
+ // Determine visible nodes and their placement.
+ var graphicsOffset, maxTextX int
+ t.nodes = nil
+ selectedIndex := -1
+ topLevelGraphicsX := -1
+ if t.graphics {
+ graphicsOffset = 1
+ }
+ t.root.Walk(func(node, parent *TreeNode) bool {
+ // Set node attributes.
+ node.parent = parent
+ if parent == nil {
+ node.level = 0
+ node.graphicsX = 0
+ node.textX = 0
+ } else {
+ node.level = parent.level + 1
+ node.graphicsX = parent.textX
+ node.textX = node.graphicsX + graphicsOffset + node.indent
+ }
+ if !t.graphics && t.align {
+ // Without graphics, we align nodes on the first column.
+ node.textX = 0
+ }
+ if node.level == t.topLevel {
+ // No graphics for top level nodes.
+ node.graphicsX = 0
+ node.textX = 0
+ }
+ if node.textX > maxTextX {
+ maxTextX = node.textX
+ }
+ if node == t.currentNode && node.selectable {
+ selectedIndex = len(t.nodes)
+ }
+
+ // Maybe we want to skip this level.
+ if t.topLevel == node.level && (topLevelGraphicsX < 0 || node.graphicsX < topLevelGraphicsX) {
+ topLevelGraphicsX = node.graphicsX
+ }
+
+ // Add and recurse (if desired).
+ if node.level >= t.topLevel {
+ t.nodes = append(t.nodes, node)
+ }
+ return node.expanded
+ })
+
+ // Post-process positions.
+ for _, node := range t.nodes {
+ // If text must align, we correct the positions.
+ if t.align && node.level > t.topLevel {
+ node.textX = maxTextX
+ }
+
+ // If we skipped levels, shift to the left.
+ if topLevelGraphicsX > 0 {
+ node.graphicsX -= topLevelGraphicsX
+ node.textX -= topLevelGraphicsX
+ }
+ }
+
+ // Process selection. (Also trigger events if necessary.)
+ if selectedIndex >= 0 {
+ // Move the selection.
+ newSelectedIndex := selectedIndex
+ MovementSwitch:
+ switch t.movement {
+ case treeUp:
+ for newSelectedIndex > 0 {
+ newSelectedIndex--
+ if t.nodes[newSelectedIndex].selectable {
+ break MovementSwitch
+ }
+ }
+ newSelectedIndex = selectedIndex
+ case treeDown:
+ for newSelectedIndex < len(t.nodes)-1 {
+ newSelectedIndex++
+ if t.nodes[newSelectedIndex].selectable {
+ break MovementSwitch
+ }
+ }
+ newSelectedIndex = selectedIndex
+ case treeHome:
+ for newSelectedIndex = 0; newSelectedIndex < len(t.nodes); newSelectedIndex++ {
+ if t.nodes[newSelectedIndex].selectable {
+ break MovementSwitch
+ }
+ }
+ newSelectedIndex = selectedIndex
+ case treeEnd:
+ for newSelectedIndex = len(t.nodes) - 1; newSelectedIndex >= 0; newSelectedIndex-- {
+ if t.nodes[newSelectedIndex].selectable {
+ break MovementSwitch
+ }
+ }
+ newSelectedIndex = selectedIndex
+ case treePageUp:
+ if newSelectedIndex+height < len(t.nodes) {
+ newSelectedIndex += height
+ } else {
+ newSelectedIndex = len(t.nodes) - 1
+ }
+ for ; newSelectedIndex < len(t.nodes); newSelectedIndex++ {
+ if t.nodes[newSelectedIndex].selectable {
+ break MovementSwitch
+ }
+ }
+ newSelectedIndex = selectedIndex
+ case treePageDown:
+ if newSelectedIndex >= height {
+ newSelectedIndex -= height
+ } else {
+ newSelectedIndex = 0
+ }
+ for ; newSelectedIndex >= 0; newSelectedIndex-- {
+ if t.nodes[newSelectedIndex].selectable {
+ break MovementSwitch
+ }
+ }
+ newSelectedIndex = selectedIndex
+ }
+ t.currentNode = t.nodes[newSelectedIndex]
+ if newSelectedIndex != selectedIndex {
+ t.movement = treeNone
+ if t.changed != nil {
+ t.changed(t.currentNode)
+ }
+ }
+ selectedIndex = newSelectedIndex
+
+ // Move selection into viewport.
+ if selectedIndex-t.offsetY >= height {
+ t.offsetY = selectedIndex - height + 1
+ }
+ if selectedIndex < t.offsetY {
+ t.offsetY = selectedIndex
+ }
+ } else {
+ // If selection is not visible or selectable, select the first candidate.
+ if t.currentNode != nil {
+ for index, node := range t.nodes {
+ if node.selectable {
+ selectedIndex = index
+ t.currentNode = node
+ break
+ }
+ }
+ }
+ if selectedIndex < 0 {
+ t.currentNode = nil
+ }
+ }
+}
+
+// Draw draws this primitive onto the screen.
+func (t *TreeView) Draw(screen tcell.Screen) {
+ t.Box.Draw(screen)
+ if t.root == nil {
+ return
+ }
+
+ // Build the tree if necessary.
+ if t.nodes == nil {
+ t.process()
+ }
+ defer func() {
+ t.nodes = nil // Rebuild during next call to Draw()
+ }()
+
+ // Scroll the tree.
+ x, y, width, height := t.GetInnerRect()
+ switch t.movement {
+ case treeUp:
+ t.offsetY--
+ case treeDown:
+ t.offsetY++
+ case treeHome:
+ t.offsetY = 0
+ case treeEnd:
+ t.offsetY = len(t.nodes)
+ case treePageUp:
+ t.offsetY -= height
+ case treePageDown:
+ t.offsetY += height
+ }
+ t.movement = treeNone
+
+ // Fix invalid offsets.
+ if t.offsetY >= len(t.nodes)-height {
+ t.offsetY = len(t.nodes) - height
+ }
+ if t.offsetY < 0 {
+ t.offsetY = 0
+ }
+
+ // Draw the tree.
+ posY := y
+ lineStyle := tcell.StyleDefault.Background(t.backgroundColor).Foreground(t.graphicsColor)
+ for index, node := range t.nodes {
+ // Skip invisible parts.
+ if posY >= y+height+1 {
+ break
+ }
+ if index < t.offsetY {
+ continue
+ }
+
+ // Draw the graphics.
+ if t.graphics {
+ // Draw ancestor branches.
+ ancestor := node.parent
+ for ancestor != nil && ancestor.parent != nil && ancestor.parent.level >= t.topLevel {
+ if ancestor.graphicsX >= width {
+ continue
+ }
+
+ // Draw a branch if this ancestor is not a last child.
+ if ancestor.parent.children[len(ancestor.parent.children)-1] != ancestor {
+ if posY-1 >= y && ancestor.textX > ancestor.graphicsX {
+ PrintJoinedSemigraphics(screen, x+ancestor.graphicsX, posY-1, Borders.Vertical, t.graphicsColor)
+ }
+ if posY < y+height {
+ screen.SetContent(x+ancestor.graphicsX, posY, Borders.Vertical, nil, lineStyle)
+ }
+ }
+ ancestor = ancestor.parent
+ }
+
+ if node.textX > node.graphicsX && node.graphicsX < width {
+ // Connect to the node above.
+ if posY-1 >= y && t.nodes[index-1].graphicsX <= node.graphicsX && t.nodes[index-1].textX > node.graphicsX {
+ PrintJoinedSemigraphics(screen, x+node.graphicsX, posY-1, Borders.TopLeft, t.graphicsColor)
+ }
+
+ // Join this node.
+ if posY < y+height {
+ screen.SetContent(x+node.graphicsX, posY, Borders.BottomLeft, nil, lineStyle)
+ for pos := node.graphicsX + 1; pos < node.textX && pos < width; pos++ {
+ screen.SetContent(x+pos, posY, Borders.Horizontal, nil, lineStyle)
+ }
+ }
+ }
+ }
+
+ // Draw the prefix and the text.
+ if node.textX < width && posY < y+height {
+ // Prefix.
+ var prefixWidth int
+ if len(t.prefixes) > 0 {
+ _, prefixWidth = Print(screen, t.prefixes[(node.level-t.topLevel)%len(t.prefixes)], x+node.textX, posY, width-node.textX, AlignLeft, node.color)
+ }
+
+ // Text.
+ if node.textX+prefixWidth < width {
+ style := tcell.StyleDefault.Foreground(node.color)
+ if node == t.currentNode {
+ style = tcell.StyleDefault.Background(node.color).Foreground(t.backgroundColor)
+ }
+ printWithStyle(screen, node.text, x+node.textX+prefixWidth, posY, width-node.textX-prefixWidth, AlignLeft, style)
+ }
+ }
+
+ // Advance.
+ posY++
+ }
+}
+
+// InputHandler returns the handler for this primitive.
+func (t *TreeView) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
+ return t.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
+ // Because the tree is flattened into a list only at drawing time, we also
+ // postpone the (selection) movement to drawing time.
+ switch key := event.Key(); key {
+ case tcell.KeyTab, tcell.KeyDown, tcell.KeyRight:
+ t.movement = treeDown
+ case tcell.KeyBacktab, tcell.KeyUp, tcell.KeyLeft:
+ t.movement = treeUp
+ case tcell.KeyHome:
+ t.movement = treeHome
+ case tcell.KeyEnd:
+ t.movement = treeEnd
+ case tcell.KeyPgDn, tcell.KeyCtrlF:
+ t.movement = treePageDown
+ case tcell.KeyPgUp, tcell.KeyCtrlB:
+ t.movement = treePageUp
+ case tcell.KeyRune:
+ switch event.Rune() {
+ case 'g':
+ t.movement = treeHome
+ case 'G':
+ t.movement = treeEnd
+ case 'j':
+ t.movement = treeDown
+ case 'k':
+ t.movement = treeUp
+ }
+ case tcell.KeyEnter:
+ if t.currentNode != nil {
+ if t.selected != nil {
+ t.selected(t.currentNode)
+ }
+ if t.currentNode.selected != nil {
+ t.currentNode.selected()
+ }
+ }
+ }
+
+ t.process()
+ })
+}
diff --git a/vendor/maunium.net/go/tview/util.go b/vendor/maunium.net/go/tview/util.go
index 41e52dd..e408b18 100644
--- a/vendor/maunium.net/go/tview/util.go
+++ b/vendor/maunium.net/go/tview/util.go
@@ -1,11 +1,9 @@
package tview
import (
- "fmt"
"math"
"regexp"
"strconv"
- "strings"
"unicode"
"maunium.net/go/tcell"
@@ -19,97 +17,13 @@ const (
AlignRight
)
-// Semigraphical runes.
-const (
- GraphicsHoriBar = '\u2500'
- GraphicsVertBar = '\u2502'
- GraphicsTopLeftCorner = '\u250c'
- GraphicsTopRightCorner = '\u2510'
- GraphicsBottomLeftCorner = '\u2514'
- GraphicsBottomRightCorner = '\u2518'
- GraphicsLeftT = '\u251c'
- GraphicsRightT = '\u2524'
- GraphicsTopT = '\u252c'
- GraphicsBottomT = '\u2534'
- GraphicsCross = '\u253c'
- GraphicsDbVertBar = '\u2550'
- GraphicsDbHorBar = '\u2551'
- GraphicsDbTopLeftCorner = '\u2554'
- GraphicsDbTopRightCorner = '\u2557'
- GraphicsDbBottomRightCorner = '\u255d'
- GraphicsDbBottomLeftCorner = '\u255a'
- GraphicsEllipsis = '\u2026'
-)
-
-// joints maps combinations of two graphical runes to the rune that results
-// when joining the two in the same screen cell. The keys of this map are
-// two-rune strings where the value of the first rune is lower than the value
-// of the second rune. Identical runes are not contained.
-var joints = map[string]rune{
- "\u2500\u2502": GraphicsCross,
- "\u2500\u250c": GraphicsTopT,
- "\u2500\u2510": GraphicsTopT,
- "\u2500\u2514": GraphicsBottomT,
- "\u2500\u2518": GraphicsBottomT,
- "\u2500\u251c": GraphicsCross,
- "\u2500\u2524": GraphicsCross,
- "\u2500\u252c": GraphicsTopT,
- "\u2500\u2534": GraphicsBottomT,
- "\u2500\u253c": GraphicsCross,
- "\u2502\u250c": GraphicsLeftT,
- "\u2502\u2510": GraphicsRightT,
- "\u2502\u2514": GraphicsLeftT,
- "\u2502\u2518": GraphicsRightT,
- "\u2502\u251c": GraphicsLeftT,
- "\u2502\u2524": GraphicsRightT,
- "\u2502\u252c": GraphicsCross,
- "\u2502\u2534": GraphicsCross,
- "\u2502\u253c": GraphicsCross,
- "\u250c\u2510": GraphicsTopT,
- "\u250c\u2514": GraphicsLeftT,
- "\u250c\u2518": GraphicsCross,
- "\u250c\u251c": GraphicsLeftT,
- "\u250c\u2524": GraphicsCross,
- "\u250c\u252c": GraphicsTopT,
- "\u250c\u2534": GraphicsCross,
- "\u250c\u253c": GraphicsCross,
- "\u2510\u2514": GraphicsCross,
- "\u2510\u2518": GraphicsRightT,
- "\u2510\u251c": GraphicsCross,
- "\u2510\u2524": GraphicsRightT,
- "\u2510\u252c": GraphicsTopT,
- "\u2510\u2534": GraphicsCross,
- "\u2510\u253c": GraphicsCross,
- "\u2514\u2518": GraphicsBottomT,
- "\u2514\u251c": GraphicsLeftT,
- "\u2514\u2524": GraphicsCross,
- "\u2514\u252c": GraphicsCross,
- "\u2514\u2534": GraphicsBottomT,
- "\u2514\u253c": GraphicsCross,
- "\u2518\u251c": GraphicsCross,
- "\u2518\u2524": GraphicsRightT,
- "\u2518\u252c": GraphicsCross,
- "\u2518\u2534": GraphicsBottomT,
- "\u2518\u253c": GraphicsCross,
- "\u251c\u2524": GraphicsCross,
- "\u251c\u252c": GraphicsCross,
- "\u251c\u2534": GraphicsCross,
- "\u251c\u253c": GraphicsCross,
- "\u2524\u252c": GraphicsCross,
- "\u2524\u2534": GraphicsCross,
- "\u2524\u253c": GraphicsCross,
- "\u252c\u2534": GraphicsCross,
- "\u252c\u253c": GraphicsCross,
- "\u2534\u253c": GraphicsCross,
-}
-
// Common regular expressions.
var (
colorPattern = regexp.MustCompile(`\[([a-zA-Z]+|#[0-9a-zA-Z]{6}|\-)?(:([a-zA-Z]+|#[0-9a-zA-Z]{6}|\-)?(:([lbdru]+|\-)?)?)?\]`)
regionPattern = regexp.MustCompile(`\["([a-zA-Z0-9_,;: \-\.]*)"\]`)
escapePattern = regexp.MustCompile(`\[([a-zA-Z0-9_,;: \-\."#]+)\[(\[*)\]`)
nonEscapePattern = regexp.MustCompile(`(\[[a-zA-Z0-9_,;: \-\."#]+\[*)\]`)
- boundaryPattern = regexp.MustCompile("([[:punct:]]\\s*|\\s+)")
+ boundaryPattern = regexp.MustCompile(`(([[:punct:]]|\n)[ \t\f\r]*|(\s+))`)
spacePattern = regexp.MustCompile(`\s+`)
)
@@ -204,13 +118,12 @@ func overlayStyle(background tcell.Color, defaultStyle tcell.Style, fgColor, bgC
defFg, defBg, defAttr := defaultStyle.Decompose()
style := defaultStyle.Background(background)
- if fgColor == "-" {
- style = style.Foreground(defFg)
- } else if fgColor != "" {
+ style = style.Foreground(defFg)
+ if fgColor != "" {
style = style.Foreground(tcell.GetColor(fgColor))
}
- if bgColor == "-" {
+ if bgColor == "-" || bgColor == "" && defBg != tcell.ColorDefault {
style = style.Background(defBg)
} else if bgColor != "" {
style = style.Background(tcell.GetColor(bgColor))
@@ -288,8 +201,8 @@ func decomposeString(text string) (colorIndices [][]int, colors [][]string, esca
// You can change the colors and text styles mid-text by inserting a color tag.
// See the package description for details.
//
-// Returns the number of actual runes printed (not including color tags) and the
-// actual width used for the printed runes.
+// Returns the number of actual bytes of the text printed (including color tags)
+// and the actual width used for the printed runes.
func Print(screen tcell.Screen, text string, x, y, maxWidth, align int, color tcell.Color) (int, int) {
return printWithStyle(screen, text, x, y, maxWidth, align, tcell.StyleDefault.Foreground(color))
}
@@ -302,186 +215,160 @@ func printWithStyle(screen tcell.Screen, text string, x, y, maxWidth, align int,
}
// Decompose the text.
- colorIndices, colors, escapeIndices, strippedText, _ := decomposeString(text)
+ colorIndices, colors, escapeIndices, strippedText, strippedWidth := decomposeString(text)
- // We deal with runes, not with bytes.
- runes := []rune(strippedText)
-
- // This helper function takes positions for a substring of "runes" and returns
- // a new string corresponding to this substring, making sure printing that
- // substring will observe color tags.
- substring := func(from, to int) string {
+ // We want to reduce all alignments to AlignLeft.
+ if align == AlignRight {
+ if strippedWidth <= maxWidth {
+ // There's enough space for the entire text.
+ return printWithStyle(screen, text, x+maxWidth-strippedWidth, y, maxWidth, AlignLeft, style)
+ }
+ // Trim characters off the beginning.
var (
- colorPos, escapePos, runePos, startPos int
+ bytes, width, colorPos, escapePos, tagOffset int
foregroundColor, backgroundColor, attributes string
)
- if from >= len(runes) {
- return ""
- }
- for pos := range text {
- // Handle color tags.
- if colorPos < len(colorIndices) && pos >= colorIndices[colorPos][0] && pos < colorIndices[colorPos][1] {
- if pos == colorIndices[colorPos][1]-1 {
- if runePos <= from {
- foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colors[colorPos])
- }
- colorPos++
- }
- continue
- }
-
- // Handle escape tags.
- if escapePos < len(escapeIndices) && pos >= escapeIndices[escapePos][0] && pos < escapeIndices[escapePos][1] {
- if pos == escapeIndices[escapePos][1]-1 {
- escapePos++
- } else if pos == escapeIndices[escapePos][1]-2 {
- continue
- }
+ _, originalBackground, _ := style.Decompose()
+ iterateString(strippedText, func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
+ // Update color/escape tag offset and style.
+ if colorPos < len(colorIndices) && textPos+tagOffset >= colorIndices[colorPos][0] && textPos+tagOffset < colorIndices[colorPos][1] {
+ foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colors[colorPos])
+ style = overlayStyle(originalBackground, style, foregroundColor, backgroundColor, attributes)
+ tagOffset += colorIndices[colorPos][1] - colorIndices[colorPos][0]
+ colorPos++
}
-
- // Check boundaries.
- if runePos == from {
- startPos = pos
- } else if runePos >= to {
- return fmt.Sprintf(`[%s:%s:%s]%s`, foregroundColor, backgroundColor, attributes, text[startPos:pos])
+ if escapePos < len(escapeIndices) && textPos+tagOffset >= escapeIndices[escapePos][0] && textPos+tagOffset < escapeIndices[escapePos][1] {
+ tagOffset++
+ escapePos++
}
-
- runePos++
- }
-
- return fmt.Sprintf(`[%s:%s:%s]%s`, foregroundColor, backgroundColor, attributes, text[startPos:])
- }
-
- // We want to reduce everything to AlignLeft.
- if align == AlignRight {
- width := 0
- start := len(runes)
- for index := start - 1; index >= 0; index-- {
- w := runewidth.RuneWidth(runes[index])
- if width+w > maxWidth {
- break
+ if strippedWidth-screenPos < maxWidth {
+ // We chopped off enough.
+ if escapePos > 0 && textPos+tagOffset-1 >= escapeIndices[escapePos-1][0] && textPos+tagOffset-1 < escapeIndices[escapePos-1][1] {
+ // Unescape open escape sequences.
+ escapeCharPos := escapeIndices[escapePos-1][1] - 2
+ text = text[:escapeCharPos] + text[escapeCharPos+1:]
+ }
+ // Print and return.
+ bytes, width = printWithStyle(screen, text[textPos+tagOffset:], x, y, maxWidth, AlignLeft, style)
+ return true
}
- width += w
- start = index
- }
- for start < len(runes) && runewidth.RuneWidth(runes[start]) == 0 {
- start++
- }
- return printWithStyle(screen, substring(start, len(runes)), x+maxWidth-width, y, width, AlignLeft, style)
+ return false
+ })
+ return bytes, width
} else if align == AlignCenter {
- width := runewidth.StringWidth(strippedText)
- if width == maxWidth {
+ if strippedWidth == maxWidth {
// Use the exact space.
return printWithStyle(screen, text, x, y, maxWidth, AlignLeft, style)
- } else if width < maxWidth {
+ } else if strippedWidth < maxWidth {
// We have more space than we need.
- half := (maxWidth - width) / 2
+ half := (maxWidth - strippedWidth) / 2
return printWithStyle(screen, text, x+half, y, maxWidth-half, AlignLeft, style)
} else {
// Chop off runes until we have a perfect fit.
var choppedLeft, choppedRight, leftIndex, rightIndex int
- rightIndex = len(runes) - 1
- for rightIndex > leftIndex && width-choppedLeft-choppedRight > maxWidth {
+ rightIndex = len(strippedText)
+ for rightIndex-1 > leftIndex && strippedWidth-choppedLeft-choppedRight > maxWidth {
if choppedLeft < choppedRight {
- leftWidth := runewidth.RuneWidth(runes[leftIndex])
- choppedLeft += leftWidth
- leftIndex++
- for leftIndex < len(runes) && leftIndex < rightIndex && runewidth.RuneWidth(runes[leftIndex]) == 0 {
- leftIndex++
- }
+ // Iterate on the left by one character.
+ iterateString(strippedText[leftIndex:], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
+ choppedLeft += screenWidth
+ leftIndex += textWidth
+ return true
+ })
} else {
- rightWidth := runewidth.RuneWidth(runes[rightIndex])
- choppedRight += rightWidth
- rightIndex--
+ // Iterate on the right by one character.
+ iterateStringReverse(strippedText[leftIndex:rightIndex], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
+ choppedRight += screenWidth
+ rightIndex -= textWidth
+ return true
+ })
}
}
- return printWithStyle(screen, substring(leftIndex, rightIndex), x, y, maxWidth, AlignLeft, style)
+
+ // Add tag offsets and determine start style.
+ var (
+ colorPos, escapePos, tagOffset int
+ foregroundColor, backgroundColor, attributes string
+ )
+ _, originalBackground, _ := style.Decompose()
+ for index := range strippedText {
+ // We only need the offset of the left index.
+ if index > leftIndex {
+ // We're done.
+ if escapePos > 0 && leftIndex+tagOffset-1 >= escapeIndices[escapePos-1][0] && leftIndex+tagOffset-1 < escapeIndices[escapePos-1][1] {
+ // Unescape open escape sequences.
+ escapeCharPos := escapeIndices[escapePos-1][1] - 2
+ text = text[:escapeCharPos] + text[escapeCharPos+1:]
+ }
+ break
+ }
+
+ // Update color/escape tag offset.
+ if colorPos < len(colorIndices) && index+tagOffset >= colorIndices[colorPos][0] && index+tagOffset < colorIndices[colorPos][1] {
+ if index <= leftIndex {
+ foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colors[colorPos])
+ style = overlayStyle(originalBackground, style, foregroundColor, backgroundColor, attributes)
+ }
+ tagOffset += colorIndices[colorPos][1] - colorIndices[colorPos][0]
+ colorPos++
+ }
+ if escapePos < len(escapeIndices) && index+tagOffset >= escapeIndices[escapePos][0] && index+tagOffset < escapeIndices[escapePos][1] {
+ tagOffset++
+ escapePos++
+ }
+ }
+ return printWithStyle(screen, text[leftIndex+tagOffset:], x, y, maxWidth, AlignLeft, style)
}
}
// Draw text.
- drawn := 0
- drawnWidth := 0
var (
- colorPos, escapePos int
- foregroundColor, backgroundColor, attributes string
+ drawn, drawnWidth, colorPos, escapePos, tagOffset int
+ foregroundColor, backgroundColor, attributes string
)
- runeSequence := make([]rune, 0, 10)
- runeSeqWidth := 0
- flush := func() {
- if len(runeSequence) == 0 {
- return // Nothing to flush.
- }
-
- // Print the rune sequence.
- finalX := x + drawnWidth
- _, _, finalStyle, _ := screen.GetContent(finalX, y)
- _, background, _ := finalStyle.Decompose()
- finalStyle = overlayStyle(background, style, foregroundColor, backgroundColor, attributes)
- var comb []rune
- if len(runeSequence) > 1 {
- // Allocate space for the combining characters only when necessary.
- comb = make([]rune, len(runeSequence)-1)
- copy(comb, runeSequence[1:])
- }
- for offset := 0; offset < runeSeqWidth; offset++ {
- // To avoid undesired effects, we place the same character in all cells.
- screen.SetContent(finalX+offset, y, runeSequence[0], comb, finalStyle)
+ iterateString(strippedText, func(main rune, comb []rune, textPos, length, screenPos, screenWidth int) bool {
+ // Only continue if there is still space.
+ if drawnWidth+screenWidth > maxWidth {
+ return true
}
- // Advance and reset.
- drawn += len(runeSequence)
- drawnWidth += runeSeqWidth
- runeSequence = runeSequence[:0]
- runeSeqWidth = 0
- }
- for pos, ch := range text {
// Handle color tags.
- if colorPos < len(colorIndices) && pos >= colorIndices[colorPos][0] && pos < colorIndices[colorPos][1] {
- flush()
- if pos == colorIndices[colorPos][1]-1 {
- foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colors[colorPos])
- colorPos++
- }
- continue
+ if colorPos < len(colorIndices) && textPos+tagOffset >= colorIndices[colorPos][0] && textPos+tagOffset < colorIndices[colorPos][1] {
+ foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colors[colorPos])
+ tagOffset += colorIndices[colorPos][1] - colorIndices[colorPos][0]
+ colorPos++
}
- // Handle escape tags.
- if escapePos < len(escapeIndices) && pos >= escapeIndices[escapePos][0] && pos < escapeIndices[escapePos][1] {
- flush()
- if pos == escapeIndices[escapePos][1]-1 {
+ // Handle scape tags.
+ if escapePos < len(escapeIndices) && textPos+tagOffset >= escapeIndices[escapePos][0] && textPos+tagOffset < escapeIndices[escapePos][1] {
+ if textPos+tagOffset == escapeIndices[escapePos][1]-2 {
+ tagOffset++
escapePos++
- } else if pos == escapeIndices[escapePos][1]-2 {
- continue
}
}
- // Check if we have enough space for this rune.
- chWidth := runewidth.RuneWidth(ch)
- if drawnWidth+chWidth > maxWidth {
- break // No. We're done then.
- }
-
- // Put this rune in the queue.
- if chWidth == 0 {
- // If this is not a modifier, we treat it as a space character.
- if len(runeSequence) == 0 {
- ch = ' '
- chWidth = 1
+ // Print the rune sequence.
+ finalX := x + drawnWidth
+ _, _, finalStyle, _ := screen.GetContent(finalX, y)
+ _, background, _ := finalStyle.Decompose()
+ finalStyle = overlayStyle(background, style, foregroundColor, backgroundColor, attributes)
+ for offset := screenWidth - 1; offset >= 0; offset-- {
+ // To avoid undesired effects, we populate all cells.
+ if offset == 0 {
+ screen.SetContent(finalX+offset, y, main, comb, finalStyle)
+ } else {
+ screen.SetContent(finalX+offset, y, ' ', nil, finalStyle)
}
- } else {
- // We have a character. Flush all previous runes.
- flush()
}
- runeSequence = append(runeSequence, ch)
- runeSeqWidth += chWidth
- }
- if drawnWidth+runeSeqWidth <= maxWidth {
- flush()
- }
+ // Advance.
+ drawn += length
+ drawnWidth += screenWidth
+
+ return false
+ })
- return drawn, drawnWidth
+ return drawn + tagOffset + len(escapeIndices), drawnWidth
}
// PrintSimple prints white text to the screen at the given position.
@@ -507,131 +394,86 @@ func WordWrap(text string, width int) (lines []string) {
colorTagIndices, _, escapeIndices, strippedText, _ := decomposeString(text)
// Find candidate breakpoints.
- breakPoints := boundaryPattern.FindAllStringIndex(strippedText, -1)
-
- // This helper function adds a new line to the result slice. The provided
- // positions are in stripped index space.
- addLine := func(from, to int) {
- // Shift indices back to original index space.
- var colorTagIndex, escapeIndex int
- for colorTagIndex < len(colorTagIndices) && to >= colorTagIndices[colorTagIndex][0] ||
- escapeIndex < len(escapeIndices) && to >= escapeIndices[escapeIndex][0] {
- past := 0
- if colorTagIndex < len(colorTagIndices) {
- tagWidth := colorTagIndices[colorTagIndex][1] - colorTagIndices[colorTagIndex][0]
- if colorTagIndices[colorTagIndex][0] < from {
- from += tagWidth
- to += tagWidth
- colorTagIndex++
- } else if colorTagIndices[colorTagIndex][0] < to {
- to += tagWidth
- colorTagIndex++
- } else {
- past++
- }
- } else {
- past++
- }
- if escapeIndex < len(escapeIndices) {
- tagWidth := escapeIndices[escapeIndex][1] - escapeIndices[escapeIndex][0]
- if escapeIndices[escapeIndex][0] < from {
- from += tagWidth
- to += tagWidth
- escapeIndex++
- } else if escapeIndices[escapeIndex][0] < to {
- to += tagWidth
- escapeIndex++
- } else {
- past++
- }
- } else {
- past++
- }
- if past == 2 {
- break // All other indices are beyond the requested string.
+ breakpoints := boundaryPattern.FindAllStringSubmatchIndex(strippedText, -1)
+ // Results in one entry for each candidate. Each entry is an array a of
+ // indices into strippedText where a[6] < 0 for newline/punctuation matches
+ // and a[4] < 0 for whitespace matches.
+
+ // Process stripped text one character at a time.
+ var (
+ colorPos, escapePos, breakpointPos, tagOffset int
+ lastBreakpoint, lastContinuation, currentLineStart int
+ lineWidth, continuationWidth int
+ newlineBreakpoint bool
+ )
+ unescape := func(substr string, startIndex int) string {
+ // A helper function to unescape escaped tags.
+ for index := escapePos; index >= 0; index-- {
+ if index < len(escapeIndices) && startIndex > escapeIndices[index][0] && startIndex < escapeIndices[index][1]-1 {
+ pos := escapeIndices[index][1] - 2 - startIndex
+ return substr[:pos] + substr[pos+1:]
}
}
- lines = append(lines, text[from:to])
+ return substr
}
+ iterateString(strippedText, func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
+ // Handle colour tags.
+ if colorPos < len(colorTagIndices) && textPos+tagOffset >= colorTagIndices[colorPos][0] && textPos+tagOffset < colorTagIndices[colorPos][1] {
+ tagOffset += colorTagIndices[colorPos][1] - colorTagIndices[colorPos][0]
+ colorPos++
+ }
- // Determine final breakpoints.
- var start, lastEnd, newStart, breakPoint int
- for {
- // What's our candidate string?
- var candidate string
- if breakPoint < len(breakPoints) {
- candidate = text[start:breakPoints[breakPoint][1]]
- } else {
- candidate = text[start:]
+ // Handle escape tags.
+ if escapePos < len(escapeIndices) && textPos+tagOffset == escapeIndices[escapePos][1]-2 {
+ tagOffset++
+ escapePos++
}
- candidate = strings.TrimRightFunc(candidate, unicode.IsSpace)
-
- if runewidth.StringWidth(candidate) >= width {
- // We're past the available width.
- if lastEnd > start {
- // Use the previous candidate.
- addLine(start, lastEnd)
- start = newStart
- } else {
- // We have no previous candidate. Make a hard break.
- var lineWidth int
- for index, ch := range text {
- if index < start {
- continue
- }
- chWidth := runewidth.RuneWidth(ch)
- if lineWidth > 0 && lineWidth+chWidth >= width {
- addLine(start, index)
- start = index
- break
- }
- lineWidth += chWidth
- }
- }
- } else {
- // We haven't hit the right border yet.
- if breakPoint >= len(breakPoints) {
- // It's the last line. We're done.
- if len(candidate) > 0 {
- addLine(start, len(strippedText))
- }
- break
- } else {
- // We have a new candidate.
- lastEnd = start + len(candidate)
- newStart = breakPoints[breakPoint][1]
- breakPoint++
- }
+
+ // Check if a break is warranted.
+ afterContinuation := lastContinuation > 0 && textPos+tagOffset >= lastContinuation
+ noBreakpoint := lastContinuation == 0
+ beyondWidth := lineWidth > 0 && lineWidth > width
+ if beyondWidth && noBreakpoint {
+ // We need a hard break without a breakpoint.
+ lines = append(lines, unescape(text[currentLineStart:textPos+tagOffset], currentLineStart))
+ currentLineStart = textPos + tagOffset
+ lineWidth = continuationWidth
+ } else if afterContinuation && (beyondWidth || newlineBreakpoint) {
+ // Break at last breakpoint or at newline.
+ lines = append(lines, unescape(text[currentLineStart:lastBreakpoint], currentLineStart))
+ currentLineStart = lastContinuation
+ lineWidth = continuationWidth
+ lastBreakpoint, lastContinuation, newlineBreakpoint = 0, 0, false
}
- }
- return
-}
+ // Is this a breakpoint?
+ if breakpointPos < len(breakpoints) && textPos == breakpoints[breakpointPos][0] {
+ // Yes, it is. Set up breakpoint infos depending on its type.
+ lastBreakpoint = breakpoints[breakpointPos][0] + tagOffset
+ lastContinuation = breakpoints[breakpointPos][1] + tagOffset
+ newlineBreakpoint = main == '\n'
+ if breakpoints[breakpointPos][6] < 0 && !newlineBreakpoint {
+ lastBreakpoint++ // Don't skip punctuation.
+ }
+ breakpointPos++
+ }
-// PrintJoinedBorder prints a border graphics rune into the screen at the given
-// position with the given color, joining it with any existing border graphics
-// rune. Background colors are preserved. At this point, only regular single
-// line borders are supported.
-func PrintJoinedBorder(screen tcell.Screen, x, y int, ch rune, color tcell.Color) {
- previous, _, style, _ := screen.GetContent(x, y)
- style = style.Foreground(color)
-
- // What's the resulting rune?
- var result rune
- if ch == previous {
- result = ch
- } else {
- if ch < previous {
- previous, ch = ch, previous
+ // Once we hit the continuation point, we start buffering widths.
+ if textPos+tagOffset < lastContinuation {
+ continuationWidth = 0
}
- result = joints[string(previous)+string(ch)]
- }
- if result == 0 {
- result = ch
+
+ lineWidth += screenWidth
+ continuationWidth += screenWidth
+ return false
+ })
+
+ // Flush the rest.
+ if currentLineStart < len(text) {
+ lines = append(lines, unescape(text[currentLineStart:], currentLineStart))
}
- // We only print something if we have something.
- screen.SetContent(x, y, result, nil, style)
+ return
}
// Escape escapes the given text such that color and/or region tags are not
@@ -643,3 +485,121 @@ func PrintJoinedBorder(screen tcell.Screen, x, y int, ch rune, color tcell.Color
func Escape(text string) string {
return nonEscapePattern.ReplaceAllString(text, "$1[]")
}
+
+// iterateString iterates through the given string one printed character at a
+// time. For each such character, the callback function is called with the
+// Unicode code points of the character (the first rune and any combining runes
+// which may be nil if there aren't any), the starting position (in bytes)
+// within the original string, its length in bytes, the screen position of the
+// character, and the screen width of it. The iteration stops if the callback
+// returns true. This function returns true if the iteration was stopped before
+// the last character.
+func iterateString(text string, callback func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool) bool {
+ var (
+ runes []rune
+ lastZeroWidthJoiner bool
+ startIndex int
+ startPos int
+ pos int
+ )
+
+ // Helper function which invokes the callback.
+ flush := func(index int) bool {
+ var comb []rune
+ if len(runes) > 1 {
+ comb = runes[1:]
+ }
+ return callback(runes[0], comb, startIndex, index-startIndex, startPos, pos-startPos)
+ }
+
+ for index, r := range text {
+ if unicode.In(r, unicode.Lm, unicode.M) || r == '\u200d' {
+ lastZeroWidthJoiner = r == '\u200d'
+ } else {
+ // We have a rune that's not a modifier. It could be the beginning of a
+ // new character.
+ if !lastZeroWidthJoiner {
+ if len(runes) > 0 {
+ // It is. Invoke callback.
+ if flush(index) {
+ return true // We're done.
+ }
+ // Reset rune store.
+ runes = runes[:0]
+ startIndex = index
+ startPos = pos
+ }
+ pos += runewidth.RuneWidth(r)
+ } else {
+ lastZeroWidthJoiner = false
+ }
+ }
+ runes = append(runes, r)
+ }
+
+ // Flush any remaining runes.
+ if len(runes) > 0 {
+ flush(len(text))
+ }
+
+ return false
+}
+
+// iterateStringReverse iterates through the given string in reverse, starting
+// from the end of the string, one printed character at a time. For each such
+// character, the callback function is called with the Unicode code points of
+// the character (the first rune and any combining runes which may be nil if
+// there aren't any), the starting position (in bytes) within the original
+// string, its length in bytes, the screen position of the character, and the
+// screen width of it. The iteration stops if the callback returns true. This
+// function returns true if the iteration was stopped before the last character.
+func iterateStringReverse(text string, callback func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool) bool {
+ type runePos struct {
+ r rune
+ pos int // The byte position of the rune in the original string.
+ width int // The screen width of the rune.
+ mod bool // Modifier or zero-width-joiner.
+ }
+
+ // We use the following:
+ // len(text) >= number of runes in text.
+
+ // Put all runes into a runePos slice in reverse.
+ runesReverse := make([]runePos, len(text))
+ index := len(text) - 1
+ for pos, ch := range text {
+ runesReverse[index].r = ch
+ runesReverse[index].pos = pos
+ runesReverse[index].width = runewidth.RuneWidth(ch)
+ runesReverse[index].mod = unicode.In(ch, unicode.Lm, unicode.M) || ch == '\u200d'
+ index--
+ }
+ runesReverse = runesReverse[index+1:]
+
+ // Parse reverse runes.
+ var screenWidth int
+ buffer := make([]rune, len(text)) // We fill this up from the back so it's forward again.
+ bufferPos := len(text)
+ stringWidth := runewidth.StringWidth(text)
+ for index, r := range runesReverse {
+ // Put this rune into the buffer.
+ bufferPos--
+ buffer[bufferPos] = r.r
+
+ // Do we need to flush the buffer?
+ if r.pos == 0 || !r.mod && runesReverse[index+1].r != '\u200d' {
+ // Yes, invoke callback.
+ var comb []rune
+ if len(text)-bufferPos > 1 {
+ comb = buffer[bufferPos+1:]
+ }
+ if callback(r.r, comb, r.pos, len(text)-r.pos, stringWidth-screenWidth, r.width) {
+ return true
+ }
+ screenWidth += r.width
+ bufferPos = len(text)
+ }
+ }
+
+ return false
+}