Fix things
This commit is contained in:
parent
cfb2cc057c
commit
ba387764ca
94
Gopkg.lock
generated
94
Gopkg.lock
generated
@ -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"
|
||||
digest = "1:d23e0bbfa4e45fe847e61fdfd442b24adf9bd3d0b050a846f9ef0f3af4f43c86"
|
||||
name = "maunium.net/go/mautrix"
|
||||
packages = ["."]
|
||||
revision = "d651abc3ecb4bbd85a6b17b710632e73ddbbc6aa"
|
||||
|
||||
[[projects]]
|
||||
name = "maunium.net/go/maulogger"
|
||||
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
|
||||
|
@ -63,7 +63,7 @@
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "maunium.net/go/gomatrix"
|
||||
name = "maunium.net/go/mautrix"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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{},
|
||||
},
|
||||
}
|
||||
|
@ -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"`
|
||||
|
@ -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 = ""
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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)),
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 := ""
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
30
vendor/golang.org/x/image/bmp/reader.go
generated
vendored
30
vendor/golang.org/x/image/bmp/reader.go
generated
vendored
@ -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
|
||||
|
2
vendor/golang.org/x/image/tiff/reader.go
generated
vendored
2
vendor/golang.org/x/image/tiff/reader.go
generated
vendored
@ -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) {
|
||||
|
4
vendor/golang.org/x/image/vp8/decode.go
generated
vendored
4
vendor/golang.org/x/image/vp8/decode.go
generated
vendored
@ -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)
|
||||
|
2
vendor/golang.org/x/image/webp/decode.go
generated
vendored
2
vendor/golang.org/x/image/webp/decode.go
generated
vendored
@ -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 (
|
||||
|
9
vendor/golang.org/x/image/webp/doc.go
generated
vendored
Normal file
9
vendor/golang.org/x/image/webp/doc.go
generated
vendored
Normal file
@ -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"
|
30
vendor/golang.org/x/image/webp/webp.go
generated
vendored
30
vendor/golang.org/x/image/webp/webp.go
generated
vendored
@ -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).
|
10
vendor/golang.org/x/net/html/const.go
generated
vendored
10
vendor/golang.org/x/net/html/const.go
generated
vendored
@ -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
|
||||
}
|
||||
|
29
vendor/golang.org/x/net/html/parse.go
generated
vendored
29
vendor/golang.org/x/net/html/parse.go
generated
vendored
@ -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 {
|
||||
|
27
vendor/gopkg.in/russross/blackfriday.v2/.travis.yml
generated
vendored
27
vendor/gopkg.in/russross/blackfriday.v2/.travis.yml
generated
vendored
@ -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 ./...
|
||||
|
20
vendor/gopkg.in/russross/blackfriday.v2/README.md
generated
vendored
20
vendor/gopkg.in/russross/blackfriday.v2/README.md
generated
vendored
@ -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
|
||||
----
|
||||
|
103
vendor/gopkg.in/russross/blackfriday.v2/block.go
generated
vendored
103
vendor/gopkg.in/russross/blackfriday.v2/block.go
generated
vendored
@ -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,14 +1320,21 @@ 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 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
|
||||
}
|
||||
|
||||
// to be a nested list, it must be indented more
|
||||
// if not, it is the next item in the same list
|
||||
if indent <= itemIndent {
|
||||
break gatherlines
|
||||
if containsBlankLine {
|
||||
*flags |= ListItemContainsBlock
|
||||
}
|
||||
|
||||
// is this the first item in the nested list?
|
||||
|
1
vendor/gopkg.in/russross/blackfriday.v2/go.mod
generated
vendored
Normal file
1
vendor/gopkg.in/russross/blackfriday.v2/go.mod
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
module github.com/russross/blackfriday/v2
|
21
vendor/gopkg.in/russross/blackfriday.v2/html.go
generated
vendored
21
vendor/gopkg.in/russross/blackfriday.v2/html.go
generated
vendored
@ -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"`)
|
||||
|
18
vendor/gopkg.in/russross/blackfriday.v2/inline.go
generated
vendored
18
vendor/gopkg.in/russross/blackfriday.v2/inline.go
generated
vendored
@ -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 <
|
||||
// at one end and ∳ 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 ¾ and ▒, 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
|
||||
|
94
vendor/gopkg.in/russross/blackfriday.v2/markdown.go
generated
vendored
94
vendor/gopkg.in/russross/blackfriday.v2/markdown.go
generated
vendored
@ -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.
|
||||
|
24
vendor/maunium.net/go/gomatrix/.gitignore
generated
vendored
24
vendor/maunium.net/go/gomatrix/.gitignore
generated
vendored
@ -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
|
9
vendor/maunium.net/go/gomatrix/.travis.yml
generated
vendored
9
vendor/maunium.net/go/gomatrix/.travis.yml
generated
vendored
@ -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
|
6
vendor/maunium.net/go/gomatrix/README.md
generated
vendored
6
vendor/maunium.net/go/gomatrix/README.md
generated
vendored
@ -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.**
|
21
vendor/maunium.net/go/maulogger/LICENSE
generated
vendored
21
vendor/maunium.net/go/maulogger/LICENSE
generated
vendored
@ -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.
|
6
vendor/maunium.net/go/maulogger/README.md
generated
vendored
6
vendor/maunium.net/go/maulogger/README.md
generated
vendored
@ -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`
|
219
vendor/maunium.net/go/maulogger/logger.go
generated
vendored
219
vendor/maunium.net/go/maulogger/logger.go
generated
vendored
@ -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()
|
||||
}
|
||||
}
|
2
vendor/maunium.net/go/mautrix/.gitignore
generated
vendored
Normal file
2
vendor/maunium.net/go/mautrix/.gitignore
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
.idea/
|
||||
.vscode/
|
0
vendor/maunium.net/go/gomatrix/LICENSE → vendor/maunium.net/go/mautrix/LICENSE
generated
vendored
0
vendor/maunium.net/go/gomatrix/LICENSE → vendor/maunium.net/go/mautrix/LICENSE
generated
vendored
4
vendor/maunium.net/go/mautrix/README.md
generated
vendored
Normal file
4
vendor/maunium.net/go/mautrix/README.md
generated
vendored
Normal file
@ -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.
|
18
vendor/maunium.net/go/gomatrix/client.go → vendor/maunium.net/go/mautrix/client.go
generated
vendored
18
vendor/maunium.net/go/gomatrix/client.go → vendor/maunium.net/go/mautrix/client.go
generated
vendored
@ -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)
|
45
vendor/maunium.net/go/gomatrix/events.go → vendor/maunium.net/go/mautrix/events.go
generated
vendored
45
vendor/maunium.net/go/gomatrix/events.go → vendor/maunium.net/go/mautrix/events.go
generated
vendored
@ -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"`
|
||||
}
|
2
vendor/maunium.net/go/gomatrix/filter.go → vendor/maunium.net/go/mautrix/filter.go
generated
vendored
2
vendor/maunium.net/go/gomatrix/filter.go → vendor/maunium.net/go/mautrix/filter.go
generated
vendored
@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package gomatrix
|
||||
package mautrix
|
||||
|
||||
import "errors"
|
||||
|
3
vendor/maunium.net/go/gomatrix/reply.go → vendor/maunium.net/go/mautrix/reply.go
generated
vendored
3
vendor/maunium.net/go/gomatrix/reply.go → vendor/maunium.net/go/mautrix/reply.go
generated
vendored
@ -1,4 +1,5 @@
|
||||
package gomatrix
|
||||
// Copyright 2018 Tulir Asokan
|
||||
package mautrix
|
||||
|
||||
import (
|
||||
"fmt"
|
@ -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"`
|
||||
}
|
||||
}
|
18
vendor/maunium.net/go/gomatrix/responses.go → vendor/maunium.net/go/mautrix/responses.go
generated
vendored
18
vendor/maunium.net/go/gomatrix/responses.go → vendor/maunium.net/go/mautrix/responses.go
generated
vendored
@ -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 {
|
2
vendor/maunium.net/go/gomatrix/room.go → vendor/maunium.net/go/mautrix/room.go
generated
vendored
2
vendor/maunium.net/go/gomatrix/room.go → vendor/maunium.net/go/mautrix/room.go
generated
vendored
@ -1,4 +1,4 @@
|
||||
package gomatrix
|
||||
package mautrix
|
||||
|
||||
// Room represents a single Matrix room.
|
||||
type Room struct {
|
2
vendor/maunium.net/go/gomatrix/store.go → vendor/maunium.net/go/mautrix/store.go
generated
vendored
2
vendor/maunium.net/go/gomatrix/store.go → vendor/maunium.net/go/mautrix/store.go
generated
vendored
@ -1,4 +1,4 @@
|
||||
package gomatrix
|
||||
package mautrix
|
||||
|
||||
// Storer is an interface which must be satisfied to store client data.
|
||||
//
|
2
vendor/maunium.net/go/gomatrix/sync.go → vendor/maunium.net/go/mautrix/sync.go
generated
vendored
2
vendor/maunium.net/go/gomatrix/sync.go → vendor/maunium.net/go/mautrix/sync.go
generated
vendored
@ -1,4 +1,4 @@
|
||||
package gomatrix
|
||||
package mautrix
|
||||
|
||||
import (
|
||||
"encoding/json"
|
4
vendor/maunium.net/go/gomatrix/userids.go → vendor/maunium.net/go/mautrix/userids.go
generated
vendored
4
vendor/maunium.net/go/gomatrix/userids.go → vendor/maunium.net/go/mautrix/userids.go
generated
vendored
@ -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
|
||||
}
|
270
vendor/maunium.net/go/tcell/README.adoc
generated
vendored
Normal file
270
vendor/maunium.net/go/tcell/README.adoc
generated
vendored
Normal file
@ -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.
|
||||
|===
|
7
vendor/maunium.net/go/tcell/cell.go
generated
vendored
7
vendor/maunium.net/go/tcell/cell.go
generated
vendored
@ -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
|
||||
}
|
||||
}
|
||||
|
1
vendor/maunium.net/go/tcell/console_win.go
generated
vendored
1
vendor/maunium.net/go/tcell/console_win.go
generated
vendored
@ -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{}
|
||||
|
BIN
vendor/maunium.net/go/tcell/tcell.png
generated
vendored
BIN
vendor/maunium.net/go/tcell/tcell.png
generated
vendored
Binary file not shown.
Before Width: | Height: | Size: 5.2 KiB |
93
vendor/maunium.net/go/tcell/tcell.svg
generated
vendored
93
vendor/maunium.net/go/tcell/tcell.svg
generated
vendored
@ -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>
|
Before Width: | Height: | Size: 3.7 KiB |
152
vendor/maunium.net/go/tcell/terminfo/term_termite.go
generated
vendored
Normal file
152
vendor/maunium.net/go/tcell/terminfo/term_termite.go
generated
vendored
Normal file
@ -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",
|
||||
})
|
||||
}
|
6
vendor/maunium.net/go/tcell/tscreen.go
generated
vendored
6
vendor/maunium.net/go/tcell/tscreen.go
generated
vendored
@ -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()
|
||||
}
|
||||
|
||||
|
2
vendor/maunium.net/go/tview/LICENSE.txt
generated
vendored
2
vendor/maunium.net/go/tview/LICENSE.txt
generated
vendored
@ -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
|
||||
|
11
vendor/maunium.net/go/tview/README.md
generated
vendored
11
vendor/maunium.net/go/tview/README.md
generated
vendored
@ -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)
|
||||
|
64
vendor/maunium.net/go/tview/ansii.go → vendor/maunium.net/go/tview/ansi.go
generated
vendored
64
vendor/maunium.net/go/tview/ansii.go → vendor/maunium.net/go/tview/ansi.go
generated
vendored
@ -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()
|
||||
}
|
394
vendor/maunium.net/go/tview/application.go
generated
vendored
394
vendor/maunium.net/go/tview/application.go
generated
vendored
@ -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()
|
||||
|
||||
// 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.
|
||||
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.
|
||||
}
|
||||
a.Unlock()
|
||||
|
||||
// The screen was finalized. Exit the loop.
|
||||
break
|
||||
}
|
||||
|
||||
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.
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Start event loop.
|
||||
EventLoop:
|
||||
for {
|
||||
select {
|
||||
case event := <-a.events:
|
||||
if event == nil {
|
||||
break EventLoop
|
||||
}
|
||||
|
||||
if p != nil {
|
||||
if handler := p.PasteHandler(); handler != nil {
|
||||
handler(event)
|
||||
a.Draw()
|
||||
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.
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
45
vendor/maunium.net/go/tview/borders.go
generated
vendored
Normal file
45
vendor/maunium.net/go/tview/borders.go
generated
vendored
Normal file
@ -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,
|
||||
}
|
88
vendor/maunium.net/go/tview/box.go
generated
vendored
88
vendor/maunium.net/go/tview/box.go
generated
vendored
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
6
vendor/maunium.net/go/tview/checkbox.go
generated
vendored
6
vendor/maunium.net/go/tview/checkbox.go
generated
vendored
@ -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.
|
||||
|
31
vendor/maunium.net/go/tview/doc.go
generated
vendored
31
vendor/maunium.net/go/tview/doc.go
generated
vendored
@ -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
|
||||
|
5
vendor/maunium.net/go/tview/dropdown.go
generated
vendored
5
vendor/maunium.net/go/tview/dropdown.go
generated
vendored
@ -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 = ""
|
||||
}
|
||||
|
17
vendor/maunium.net/go/tview/flex.go
generated
vendored
17
vendor/maunium.net/go/tview/flex.go
generated
vendored
@ -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)
|
||||
|
53
vendor/maunium.net/go/tview/form.go
generated
vendored
53
vendor/maunium.net/go/tview/form.go
generated
vendored
@ -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 {
|
||||
|
16
vendor/maunium.net/go/tview/grid.go
generated
vendored
16
vendor/maunium.net/go/tview/grid.go
generated
vendored
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
205
vendor/maunium.net/go/tview/inputfield.go
generated
vendored
205
vendor/maunium.net/go/tview/inputfield.go
generated
vendored
@ -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,56 +264,69 @@ 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)
|
||||
screen.ShowCursor(x+cursorScreenPos, y)
|
||||
}
|
||||
}
|
||||
|
||||
// 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, y)
|
||||
}
|
||||
|
||||
// InputHandler returns the handler for this primitive.
|
||||
func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
|
||||
return i.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p 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
|
||||
}
|
||||
runes := []rune(i.text)
|
||||
i.text = string(runes[:len(runes)-1])
|
||||
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()
|
||||
}
|
||||
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)
|
||||
|
15
vendor/maunium.net/go/tview/list.go
generated
vendored
15
vendor/maunium.net/go/tview/list.go
generated
vendored
@ -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
|
||||
}
|
||||
|
15
vendor/maunium.net/go/tview/modal.go
generated
vendored
15
vendor/maunium.net/go/tview/modal.go
generated
vendored
@ -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
|
||||
|
296
vendor/maunium.net/go/tview/semigraphics.go
generated
vendored
Normal file
296
vendor/maunium.net/go/tview/semigraphics.go
generated
vendored
Normal file
@ -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)
|
||||
}
|
119
vendor/maunium.net/go/tview/table.go
generated
vendored
119
vendor/maunium.net/go/tview/table.go
generated
vendored
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
266
vendor/maunium.net/go/tview/textview.go
generated
vendored
266
vendor/maunium.net/go/tview/textview.go
generated
vendored
@ -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
|
||||
|
684
vendor/maunium.net/go/tview/treeview.go
generated
vendored
Normal file
684
vendor/maunium.net/go/tview/treeview.go
generated
vendored
Normal file
@ -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()
|
||||
})
|
||||
}
|
664
vendor/maunium.net/go/tview/util.go
generated
vendored
664
vendor/maunium.net/go/tview/util.go
generated
vendored
@ -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,115 +215,136 @@ 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++
|
||||
_, 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++
|
||||
}
|
||||
if escapePos < len(escapeIndices) && textPos+tagOffset >= escapeIndices[escapePos][0] && textPos+tagOffset < escapeIndices[escapePos][1] {
|
||||
tagOffset++
|
||||
escapePos++
|
||||
}
|
||||
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:]
|
||||
}
|
||||
continue
|
||||
// Print and return.
|
||||
bytes, width = printWithStyle(screen, text[textPos+tagOffset:], x, y, maxWidth, AlignLeft, style)
|
||||
return true
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// Check boundaries.
|
||||
if runePos == from {
|
||||
startPos = pos
|
||||
} else if runePos >= to {
|
||||
return fmt.Sprintf(`[%s:%s:%s]%s`, foregroundColor, backgroundColor, attributes, text[startPos:pos])
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
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.
|
||||
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
|
||||
}
|
||||
|
||||
// Handle color tags.
|
||||
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 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++
|
||||
}
|
||||
}
|
||||
|
||||
// Print the rune sequence.
|
||||
@ -418,70 +352,23 @@ func printWithStyle(screen tcell.Screen, text string, x, y, maxWidth, align int,
|
||||
_, _, 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)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Handle escape tags.
|
||||
if escapePos < len(escapeIndices) && pos >= escapeIndices[escapePos][0] && pos < escapeIndices[escapePos][1] {
|
||||
flush()
|
||||
if pos == escapeIndices[escapePos][1]-1 {
|
||||
escapePos++
|
||||
} else if pos == escapeIndices[escapePos][1]-2 {
|
||||
continue
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we have enough space for this rune.
|
||||
chWidth := runewidth.RuneWidth(ch)
|
||||
if drawnWidth+chWidth > maxWidth {
|
||||
break // No. We're done then.
|
||||
}
|
||||
// Advance.
|
||||
drawn += length
|
||||
drawnWidth += screenWidth
|
||||
|
||||
// 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
|
||||
}
|
||||
} else {
|
||||
// We have a character. Flush all previous runes.
|
||||
flush()
|
||||
}
|
||||
runeSequence = append(runeSequence, ch)
|
||||
runeSeqWidth += chWidth
|
||||
return false
|
||||
})
|
||||
|
||||
}
|
||||
if drawnWidth+runeSeqWidth <= maxWidth {
|
||||
flush()
|
||||
}
|
||||
|
||||
return drawn, drawnWidth
|
||||
return drawn + tagOffset + len(escapeIndices), drawnWidth
|
||||
}
|
||||
|
||||
// PrintSimple prints white text to the screen at the given position.
|
||||
@ -507,133 +394,88 @@ func WordWrap(text string, width int) (lines []string) {
|
||||
colorTagIndices, _, escapeIndices, strippedText, _ := decomposeString(text)
|
||||
|
||||
// Find candidate breakpoints.
|
||||
breakPoints := boundaryPattern.FindAllStringIndex(strippedText, -1)
|
||||
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.
|
||||
|
||||
// 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.
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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:]
|
||||
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++
|
||||
}
|
||||
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++
|
||||
}
|
||||
// Handle escape tags.
|
||||
if escapePos < len(escapeIndices) && textPos+tagOffset == escapeIndices[escapePos][1]-2 {
|
||||
tagOffset++
|
||||
escapePos++
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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++
|
||||
}
|
||||
|
||||
// Once we hit the continuation point, we start buffering widths.
|
||||
if textPos+tagOffset < lastContinuation {
|
||||
continuationWidth = 0
|
||||
}
|
||||
|
||||
lineWidth += screenWidth
|
||||
continuationWidth += screenWidth
|
||||
return false
|
||||
})
|
||||
|
||||
// Flush the rest.
|
||||
if currentLineStart < len(text) {
|
||||
lines = append(lines, unescape(text[currentLineStart:], currentLineStart))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
result = joints[string(previous)+string(ch)]
|
||||
}
|
||||
if result == 0 {
|
||||
result = ch
|
||||
}
|
||||
|
||||
// We only print something if we have something.
|
||||
screen.SetContent(x, y, result, nil, style)
|
||||
}
|
||||
|
||||
// Escape escapes the given text such that color and/or region tags are not
|
||||
// recognized and substituted by the print functions of this package. For
|
||||
// example, to include a tag-like string in a box title or in a TextView:
|
||||
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user