Fix things
This commit is contained in:
parent
cfb2cc057c
commit
ba387764ca
94
Gopkg.lock
generated
94
Gopkg.lock
generated
@ -2,79 +2,104 @@
|
|||||||
|
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec"
|
||||||
name = "github.com/davecgh/go-spew"
|
name = "github.com/davecgh/go-spew"
|
||||||
packages = ["spew"]
|
packages = ["spew"]
|
||||||
|
pruneopts = "UT"
|
||||||
revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73"
|
revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73"
|
||||||
version = "v1.1.1"
|
version = "v1.1.1"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:2694597a20d303502ad1b7f1cc688977c7dc34da732ccf4d27e98b552cceed5b"
|
||||||
name = "github.com/disintegration/imaging"
|
name = "github.com/disintegration/imaging"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
pruneopts = "UT"
|
||||||
revision = "0bd5694c78c9c3d9a3cd06a706a8f3c59296a9ac"
|
revision = "0bd5694c78c9c3d9a3cd06a706a8f3c59296a9ac"
|
||||||
version = "v1.5.0"
|
version = "v1.5.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
digest = "1:40d0056c1b1f503c366ba441df92a82b5a2654d6f3747b1689a611eb5c9ce0a2"
|
||||||
name = "github.com/gdamore/encoding"
|
name = "github.com/gdamore/encoding"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
pruneopts = "UT"
|
||||||
revision = "b23993cbb6353f0e6aa98d0ee318a34728f628b9"
|
revision = "b23993cbb6353f0e6aa98d0ee318a34728f628b9"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:f4bd9b6cfcbaafa94259861747781022d8f06ff3c88aafe49a815ac02609bc96"
|
||||||
name = "github.com/kyokomi/emoji"
|
name = "github.com/kyokomi/emoji"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
pruneopts = "UT"
|
||||||
revision = "2e9a9507333f3ee28f3fab88c2c3aba34455d734"
|
revision = "2e9a9507333f3ee28f3fab88c2c3aba34455d734"
|
||||||
version = "v1.5.1"
|
version = "v1.5.1"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:c65a16ac77d0b1aefc7009cabb6ac5ad05def02025f5be85f450c03f52cc6f86"
|
||||||
name = "github.com/lucasb-eyer/go-colorful"
|
name = "github.com/lucasb-eyer/go-colorful"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
pruneopts = "UT"
|
||||||
revision = "345fbb3dbcdb252d9985ee899a84963c0fa24c82"
|
revision = "345fbb3dbcdb252d9985ee899a84963c0fa24c82"
|
||||||
version = "v1.0"
|
version = "v1.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:cdb899c199f907ac9fb50495ec71212c95cb5b0e0a8ee0800da0238036091033"
|
||||||
name = "github.com/mattn/go-runewidth"
|
name = "github.com/mattn/go-runewidth"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
pruneopts = "UT"
|
||||||
revision = "ce7b0b5c7b45a81508558cd1dba6bb1e4ddb51bb"
|
revision = "ce7b0b5c7b45a81508558cd1dba6bb1e4ddb51bb"
|
||||||
version = "v0.0.3"
|
version = "v0.0.3"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
digest = "1:0e1e5f960c58fdc677212fcc70e55042a0084d367623e51afbdb568963832f5d"
|
||||||
name = "github.com/nu7hatch/gouuid"
|
name = "github.com/nu7hatch/gouuid"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
pruneopts = "UT"
|
||||||
revision = "179d4d0c4d8d407a32af483c2354df1d2c91e6c3"
|
revision = "179d4d0c4d8d407a32af483c2354df1d2c91e6c3"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe"
|
||||||
name = "github.com/pmezard/go-difflib"
|
name = "github.com/pmezard/go-difflib"
|
||||||
packages = ["difflib"]
|
packages = ["difflib"]
|
||||||
|
pruneopts = "UT"
|
||||||
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
|
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
|
||||||
version = "v1.0.0"
|
version = "v1.0.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
digest = "1:3fd3d634f6815f19ac4b2c5e16d28ec9aa4584d0bba25d1ee6c424d813cca22a"
|
||||||
name = "github.com/renstrom/fuzzysearch"
|
name = "github.com/renstrom/fuzzysearch"
|
||||||
packages = ["fuzzy"]
|
packages = ["fuzzy"]
|
||||||
|
pruneopts = "UT"
|
||||||
revision = "b18e754edff4833912ef4dce9eaca885bd3f0de1"
|
revision = "b18e754edff4833912ef4dce9eaca885bd3f0de1"
|
||||||
version = "v1.0.1"
|
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
digest = "1:def689e73e9252f6f7fe66834a76751a41b767e03daab299e607e7226c58a855"
|
||||||
name = "github.com/shurcooL/sanitized_anchor_name"
|
name = "github.com/shurcooL/sanitized_anchor_name"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
pruneopts = "UT"
|
||||||
revision = "86672fcb3f950f35f2e675df2240550f2a50762f"
|
revision = "86672fcb3f950f35f2e675df2240550f2a50762f"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:18752d0b95816a1b777505a97f71c7467a8445b8ffb55631a7bf779f6ba4fa83"
|
||||||
name = "github.com/stretchr/testify"
|
name = "github.com/stretchr/testify"
|
||||||
packages = ["assert"]
|
packages = ["assert"]
|
||||||
|
pruneopts = "UT"
|
||||||
revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686"
|
revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686"
|
||||||
version = "v1.2.2"
|
version = "v1.2.2"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
digest = "1:22738a5261588086d75acb3c792f7c931d30fd32e351117e9e046331135f5297"
|
||||||
name = "github.com/zyedidia/clipboard"
|
name = "github.com/zyedidia/clipboard"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
pruneopts = "UT"
|
||||||
revision = "bd31d747117d04b4e25b61f73e1ea4faeea3c56a"
|
revision = "bd31d747117d04b4e25b61f73e1ea4faeea3c56a"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
digest = "1:441333ba1f7f3074035297647244e3d1ce3665e4e7c5054b3a8b138463f8ff58"
|
||||||
name = "golang.org/x/image"
|
name = "golang.org/x/image"
|
||||||
packages = [
|
packages = [
|
||||||
"bmp",
|
"bmp",
|
||||||
@ -83,79 +108,108 @@
|
|||||||
"tiff/lzw",
|
"tiff/lzw",
|
||||||
"vp8",
|
"vp8",
|
||||||
"vp8l",
|
"vp8l",
|
||||||
"webp"
|
"webp",
|
||||||
]
|
]
|
||||||
revision = "c73c2afc3b812cdd6385de5a50616511c4a3d458"
|
pruneopts = "UT"
|
||||||
|
revision = "249dc8530c0efc2766606a940c1c50b434b2f1cd"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
digest = "1:1a1ecfa7b54ca3f7a0115ab5c578d7d6a5d8b605839c549e80260468c42f8be7"
|
||||||
name = "golang.org/x/net"
|
name = "golang.org/x/net"
|
||||||
packages = [
|
packages = [
|
||||||
"html",
|
"html",
|
||||||
"html/atom"
|
"html/atom",
|
||||||
]
|
]
|
||||||
revision = "8a410e7b638dca158bf9e766925842f6651ff828"
|
pruneopts = "UT"
|
||||||
|
revision = "88d92db4c548972d942ac2a3531a8a9a34c82ca6"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:37672ad5821719e2df8509c2edd4ba5ae192463237c73c3a2d24ef8b2bc9e36f"
|
||||||
name = "golang.org/x/text"
|
name = "golang.org/x/text"
|
||||||
packages = [
|
packages = [
|
||||||
"encoding",
|
"encoding",
|
||||||
"encoding/internal/identifier",
|
"encoding/internal/identifier",
|
||||||
"internal/gen",
|
"internal/gen",
|
||||||
"transform",
|
"transform",
|
||||||
"unicode/cldr"
|
"unicode/cldr",
|
||||||
]
|
]
|
||||||
|
pruneopts = "UT"
|
||||||
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
|
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
|
||||||
version = "v0.3.0"
|
version = "v0.3.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:2ee0f15eb0fb04f918db7c2dcf39745f40d69f798ef171610a730e8a56aaa4fd"
|
||||||
name = "gopkg.in/russross/blackfriday.v2"
|
name = "gopkg.in/russross/blackfriday.v2"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "cadec560ec52d93835bf2f15bd794700d3a2473b"
|
pruneopts = "UT"
|
||||||
version = "v2.0.0"
|
revision = "d3b5b032dc8e8927d31a5071b56e14c89f045135"
|
||||||
|
version = "v2.0.1"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "v1"
|
branch = "v1"
|
||||||
|
digest = "1:b71ccd8615521db4ea2f9381f55780a1312654f5d8431d2e58eca51c74110de2"
|
||||||
name = "gopkg.in/toast.v1"
|
name = "gopkg.in/toast.v1"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
pruneopts = "UT"
|
||||||
revision = "0a84660828b24d25b35525c9a1f1f51267f8da91"
|
revision = "0a84660828b24d25b35525c9a1f1f51267f8da91"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:342378ac4dcb378a5448dd723f0784ae519383532f5e70ade24132c4c8693202"
|
||||||
name = "gopkg.in/yaml.v2"
|
name = "gopkg.in/yaml.v2"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
pruneopts = "UT"
|
||||||
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
|
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
|
||||||
version = "v2.2.1"
|
version = "v2.2.1"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "maunium.net/go/gomatrix"
|
digest = "1:d23e0bbfa4e45fe847e61fdfd442b24adf9bd3d0b050a846f9ef0f3af4f43c86"
|
||||||
|
name = "maunium.net/go/mautrix"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "d651abc3ecb4bbd85a6b17b710632e73ddbbc6aa"
|
pruneopts = "UT"
|
||||||
|
revision = "e8080dcf484d1db9021d2019fee132ffc9e37e3c"
|
||||||
[[projects]]
|
|
||||||
name = "maunium.net/go/maulogger"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "64f0aa33b6c51313e15575257db71dec44fe7988"
|
|
||||||
version = "v1.0"
|
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
digest = "1:28d6d6123ce29c9b0b5d393af0085f1454a9d93d70b4c9893efe6cff025702b9"
|
||||||
name = "maunium.net/go/tcell"
|
name = "maunium.net/go/tcell"
|
||||||
packages = [
|
packages = [
|
||||||
".",
|
".",
|
||||||
"terminfo"
|
"terminfo",
|
||||||
]
|
]
|
||||||
revision = "f32e44a866ab0475655e11ede91337d7b562956d"
|
pruneopts = "UT"
|
||||||
|
revision = "a62189e4543d731b94ae0d3b659d1853975ea657"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
digest = "1:b4c2e264221b6afa98aec4e0a097f3775cb014eebac8a6f49d33c53b237b77af"
|
||||||
name = "maunium.net/go/tview"
|
name = "maunium.net/go/tview"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "8b261597bbdb95dcaef03854aaa0cc192f56b1ff"
|
pruneopts = "UT"
|
||||||
|
revision = "dae31f32cda75339f5ec2a64411ef3ec1862ef5e"
|
||||||
|
|
||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
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-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
@ -63,7 +63,7 @@
|
|||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "maunium.net/go/gomatrix"
|
name = "maunium.net/go/mautrix"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
![Chat Preview](chat-preview.png)
|
![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.
|
Basic usage is possible, but expect bugs and missing features.
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
"maunium.net/go/gomatrix"
|
"maunium.net/go/mautrix"
|
||||||
"maunium.net/go/gomuks/debug"
|
"maunium.net/go/gomuks/debug"
|
||||||
"maunium.net/go/gomuks/matrix/pushrules"
|
"maunium.net/go/gomuks/matrix/pushrules"
|
||||||
"maunium.net/go/gomuks/matrix/rooms"
|
"maunium.net/go/gomuks/matrix/rooms"
|
||||||
@ -289,12 +289,12 @@ func (config *Config) PutRoom(room *rooms.Room) {
|
|||||||
room.Save(config.getRoomCachePath(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 := config.GetRoom(room.ID)
|
||||||
gmxRoom.Room = room
|
gmxRoom.Room = room
|
||||||
gmxRoom.Save(config.getRoomCachePath(gmxRoom))
|
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
|
return config.GetRoom(roomID).Room
|
||||||
}
|
}
|
||||||
|
@ -17,12 +17,12 @@
|
|||||||
package ifc
|
package ifc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"maunium.net/go/gomatrix"
|
"maunium.net/go/mautrix"
|
||||||
"maunium.net/go/gomuks/matrix/rooms"
|
"maunium.net/go/gomuks/matrix/rooms"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MatrixContainer interface {
|
type MatrixContainer interface {
|
||||||
Client() *gomatrix.Client
|
Client() *mautrix.Client
|
||||||
InitClient() error
|
InitClient() error
|
||||||
Initialized() bool
|
Initialized() bool
|
||||||
|
|
||||||
@ -33,14 +33,14 @@ type MatrixContainer interface {
|
|||||||
Logout()
|
Logout()
|
||||||
|
|
||||||
SendPreferencesToMatrix()
|
SendPreferencesToMatrix()
|
||||||
SendMessage(roomID string, msgtype gomatrix.MessageType, message string) (string, error)
|
SendMessage(roomID string, msgtype mautrix.MessageType, message string) (string, error)
|
||||||
SendMarkdownMessage(roomID string, msgtype gomatrix.MessageType, message string) (string, error)
|
SendMarkdownMessage(roomID string, msgtype mautrix.MessageType, message string) (string, error)
|
||||||
SendTyping(roomID string, typing bool)
|
SendTyping(roomID string, typing bool)
|
||||||
MarkRead(roomID, eventID string)
|
MarkRead(roomID, eventID string)
|
||||||
JoinRoom(roomID, server string) (*rooms.Room, error)
|
JoinRoom(roomID, server string) (*rooms.Room, error)
|
||||||
LeaveRoom(roomID string) 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
|
GetRoom(roomID string) *rooms.Room
|
||||||
|
|
||||||
Download(mxcURL string) ([]byte, string, string, error)
|
Download(mxcURL string) ([]byte, string, string, error)
|
||||||
|
@ -19,7 +19,7 @@ package ifc
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"maunium.net/go/gomatrix"
|
"maunium.net/go/mautrix"
|
||||||
"maunium.net/go/gomuks/matrix/pushrules"
|
"maunium.net/go/gomuks/matrix/pushrules"
|
||||||
"maunium.net/go/gomuks/matrix/rooms"
|
"maunium.net/go/gomuks/matrix/rooms"
|
||||||
"maunium.net/go/tcell"
|
"maunium.net/go/tcell"
|
||||||
@ -50,7 +50,7 @@ type MainView interface {
|
|||||||
UpdateTags(room *rooms.Room)
|
UpdateTags(room *rooms.Room)
|
||||||
|
|
||||||
SetTyping(roomID string, users []string)
|
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)
|
NotifyMessage(room *rooms.Room, message Message, should pushrules.PushActionArrayShould)
|
||||||
InitialSyncDone()
|
InitialSyncDone()
|
||||||
@ -73,8 +73,8 @@ type RoomView interface {
|
|||||||
SetTyping(users []string)
|
SetTyping(users []string)
|
||||||
UpdateUserList()
|
UpdateUserList()
|
||||||
|
|
||||||
NewMessage(id, sender string, msgtype gomatrix.MessageType, text string, timestamp time.Time) Message
|
NewMessage(id, sender string, msgtype mautrix.MessageType, text string, timestamp time.Time) Message
|
||||||
NewTempMessage(msgtype gomatrix.MessageType, text string) Message
|
NewTempMessage(msgtype mautrix.MessageType, text string) Message
|
||||||
AddMessage(message Message, direction MessageDirection)
|
AddMessage(message Message, direction MessageDirection)
|
||||||
AddServiceMessage(message string)
|
AddServiceMessage(message string)
|
||||||
}
|
}
|
||||||
@ -111,8 +111,8 @@ type Message interface {
|
|||||||
SetID(id string)
|
SetID(id string)
|
||||||
ID() string
|
ID() string
|
||||||
|
|
||||||
SetType(msgtype gomatrix.MessageType)
|
SetType(msgtype mautrix.MessageType)
|
||||||
Type() gomatrix.MessageType
|
Type() mautrix.MessageType
|
||||||
|
|
||||||
NotificationContent() string
|
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
|
package matrix
|
||||||
|
@ -34,20 +34,20 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"gopkg.in/russross/blackfriday.v2"
|
"gopkg.in/russross/blackfriday.v2"
|
||||||
"maunium.net/go/gomatrix"
|
|
||||||
"maunium.net/go/gomuks/config"
|
"maunium.net/go/gomuks/config"
|
||||||
"maunium.net/go/gomuks/debug"
|
"maunium.net/go/gomuks/debug"
|
||||||
"maunium.net/go/gomuks/interface"
|
"maunium.net/go/gomuks/interface"
|
||||||
"maunium.net/go/gomuks/lib/bfhtml"
|
"maunium.net/go/gomuks/lib/bfhtml"
|
||||||
"maunium.net/go/gomuks/matrix/pushrules"
|
"maunium.net/go/gomuks/matrix/pushrules"
|
||||||
"maunium.net/go/gomuks/matrix/rooms"
|
"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.
|
// It is used for all Matrix calls from the UI and Matrix event handlers.
|
||||||
type Container struct {
|
type Container struct {
|
||||||
client *gomatrix.Client
|
client *mautrix.Client
|
||||||
syncer *GomuksSyncer
|
syncer *GomuksSyncer
|
||||||
gmx ifc.Gomuks
|
gmx ifc.Gomuks
|
||||||
ui ifc.GomuksUI
|
ui ifc.GomuksUI
|
||||||
@ -69,12 +69,18 @@ func NewContainer(gmx ifc.Gomuks) *Container {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client returns the underlying gomatrix Client.
|
// Client returns the underlying mautrix Client.
|
||||||
func (c *Container) Client() *gomatrix.Client {
|
func (c *Container) Client() *mautrix.Client {
|
||||||
return c.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 {
|
func (c *Container) InitClient() error {
|
||||||
if len(c.config.HS) == 0 {
|
if len(c.config.HS) == 0 {
|
||||||
return fmt.Errorf("no homeserver in config")
|
return fmt.Errorf("no homeserver in config")
|
||||||
@ -92,10 +98,11 @@ func (c *Container) InitClient() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var err 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
c.client.Logger = mxLogger{}
|
||||||
|
|
||||||
allowInsecure := len(os.Getenv("GOMUKS_ALLOW_INSECURE_CONNECTIONS")) > 0
|
allowInsecure := len(os.Getenv("GOMUKS_ALLOW_INSECURE_CONNECTIONS")) > 0
|
||||||
if allowInsecure {
|
if allowInsecure {
|
||||||
@ -112,14 +119,14 @@ func (c *Container) InitClient() error {
|
|||||||
return nil
|
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 {
|
func (c *Container) Initialized() bool {
|
||||||
return c.client != nil
|
return c.client != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Login sends a password login request with the given username and password.
|
// Login sends a password login request with the given username and password.
|
||||||
func (c *Container) Login(user, password string) error {
|
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",
|
Type: "m.login.password",
|
||||||
User: user,
|
User: user,
|
||||||
Password: password,
|
Password: password,
|
||||||
@ -175,7 +182,7 @@ func (c *Container) PushRules() *pushrules.PushRuleset {
|
|||||||
return c.config.PushRules
|
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.
|
// OnLogin initializes the syncer and updates the room list.
|
||||||
func (c *Container) OnLogin() {
|
func (c *Container) OnLogin() {
|
||||||
@ -185,15 +192,16 @@ func (c *Container) OnLogin() {
|
|||||||
|
|
||||||
debug.Print("Initializing syncer")
|
debug.Print("Initializing syncer")
|
||||||
c.syncer = NewGomuksSyncer(c.config)
|
c.syncer = NewGomuksSyncer(c.config)
|
||||||
c.syncer.OnEventType(gomatrix.EventMessage, c.HandleMessage)
|
c.syncer.OnEventType(mautrix.EventMessage, c.HandleMessage)
|
||||||
c.syncer.OnEventType(gomatrix.StateMember, c.HandleMembership)
|
c.syncer.OnEventType(mautrix.StateMember, c.HandleMembership)
|
||||||
c.syncer.OnEventType(gomatrix.EphemeralEventReceipt, c.HandleReadReceipt)
|
c.syncer.OnEventType(mautrix.EphemeralEventReceipt, c.HandleReadReceipt)
|
||||||
c.syncer.OnEventType(gomatrix.EphemeralEventTyping, c.HandleTyping)
|
c.syncer.OnEventType(mautrix.EphemeralEventTyping, c.HandleTyping)
|
||||||
c.syncer.OnEventType(gomatrix.AccountDataDirectChats, c.HandleDirectChatInfo)
|
c.syncer.OnEventType(mautrix.AccountDataDirectChats, c.HandleDirectChatInfo)
|
||||||
c.syncer.OnEventType(gomatrix.AccountDataPushRules, c.HandlePushRules)
|
c.syncer.OnEventType(mautrix.AccountDataPushRules, c.HandlePushRules)
|
||||||
c.syncer.OnEventType(gomatrix.AccountDataRoomTags, c.HandleTag)
|
c.syncer.OnEventType(mautrix.AccountDataRoomTags, c.HandleTag)
|
||||||
c.syncer.OnEventType(AccountDataGomuksPreferences, c.HandlePreferences)
|
c.syncer.OnEventType(AccountDataGomuksPreferences, c.HandlePreferences)
|
||||||
c.syncer.InitDoneCallback = func() {
|
c.syncer.InitDoneCallback = func() {
|
||||||
|
debug.Print("Initial sync done")
|
||||||
c.config.AuthCache.InitialSyncDone = true
|
c.config.AuthCache.InitialSyncDone = true
|
||||||
c.config.SaveAuthCache()
|
c.config.SaveAuthCache()
|
||||||
c.ui.MainView().InitialSyncDone()
|
c.ui.MainView().InitialSyncDone()
|
||||||
@ -227,7 +235,7 @@ func (c *Container) Start() {
|
|||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
if err := c.client.Sync(); err != nil {
|
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")
|
debug.Print("Sync() errored with ", err, " -> logging out")
|
||||||
c.Logout()
|
c.Logout()
|
||||||
} else {
|
} 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
|
orig := c.config.Preferences
|
||||||
rt, _ := json.Marshal(&evt.Content)
|
rt, _ := json.Marshal(&evt.Content)
|
||||||
json.Unmarshal(rt, &c.config.Preferences)
|
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.
|
// 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 {
|
if source&EventSourceLeave != 0 {
|
||||||
return
|
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.
|
// 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
|
isLeave := source&EventSourceLeave != 0
|
||||||
isTimeline := source&EventSourceTimeline != 0
|
isTimeline := source&EventSourceTimeline != 0
|
||||||
isNonTimelineLeave := isLeave && !isTimeline
|
isNonTimelineLeave := isLeave && !isTimeline
|
||||||
@ -302,9 +310,9 @@ func (c *Container) HandleMembership(source EventSource, evt *gomatrix.Event) {
|
|||||||
c.HandleMessage(source, evt)
|
c.HandleMessage(source, evt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) processOwnMembershipChange(evt *gomatrix.Event) {
|
func (c *Container) processOwnMembershipChange(evt *mautrix.Event) {
|
||||||
membership := evt.Content.Membership
|
membership := evt.Content.Membership
|
||||||
prevMembership := gomatrix.MembershipLeave
|
prevMembership := mautrix.MembershipLeave
|
||||||
if evt.Unsigned.PrevContent != nil {
|
if evt.Unsigned.PrevContent != nil {
|
||||||
prevMembership = evt.Unsigned.PrevContent.Membership
|
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
|
var largestTimestamp int64
|
||||||
for eventID, rawContent := range evt.Content.Raw {
|
for eventID, rawContent := range evt.Content.Raw {
|
||||||
content, ok := rawContent.(map[string]interface{})
|
content, ok := rawContent.(map[string]interface{})
|
||||||
@ -353,7 +361,7 @@ func (c *Container) parseReadReceipt(evt *gomatrix.Event) (largestTimestampEvent
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) HandleReadReceipt(source EventSource, evt *gomatrix.Event) {
|
func (c *Container) HandleReadReceipt(source EventSource, evt *mautrix.Event) {
|
||||||
if source&EventSourceLeave != 0 {
|
if source&EventSourceLeave != 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -368,7 +376,7 @@ func (c *Container) HandleReadReceipt(source EventSource, evt *gomatrix.Event) {
|
|||||||
c.ui.Render()
|
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)
|
directChats := make(map[*rooms.Room]bool)
|
||||||
for _, rawRoomIDList := range evt.Content.Raw {
|
for _, rawRoomIDList := range evt.Content.Raw {
|
||||||
roomIDList, ok := rawRoomIDList.([]interface{})
|
roomIDList, ok := rawRoomIDList.([]interface{})
|
||||||
@ -391,7 +399,7 @@ func (c *Container) parseDirectChatInfo(evt *gomatrix.Event) map[*rooms.Room]boo
|
|||||||
return directChats
|
return directChats
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) HandleDirectChatInfo(source EventSource, evt *gomatrix.Event) {
|
func (c *Container) HandleDirectChatInfo(source EventSource, evt *mautrix.Event) {
|
||||||
directChats := c.parseDirectChatInfo(evt)
|
directChats := c.parseDirectChatInfo(evt)
|
||||||
for _, room := range c.config.Rooms {
|
for _, room := range c.config.Rooms {
|
||||||
shouldBeDirect := directChats[room]
|
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.
|
// 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")
|
debug.Print("Received updated push rules")
|
||||||
var err error
|
var err error
|
||||||
c.config.PushRules, err = pushrules.EventToPushRules(evt)
|
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.
|
// 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)
|
room := c.config.GetRoom(evt.RoomID)
|
||||||
|
|
||||||
newTags := make([]rooms.RoomTag, len(evt.Content.RoomTags))
|
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 {
|
for tag, info := range evt.Content.RoomTags {
|
||||||
order := "0.5"
|
order := "0.5"
|
||||||
if len(info.Order) > 0 {
|
if len(info.Order) > 0 {
|
||||||
order = info.Order
|
order = info.Order.String()
|
||||||
}
|
}
|
||||||
newTags[index] = rooms.RoomTag{
|
newTags[index] = rooms.RoomTag{
|
||||||
Tag: tag,
|
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.
|
// 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)
|
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.
|
// 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()
|
defer debug.Recover()
|
||||||
c.SendTyping(roomID, false)
|
c.SendTyping(roomID, false)
|
||||||
resp, err := c.client.SendMessageEvent(roomID, gomatrix.EventMessage,
|
resp, err := c.client.SendMessageEvent(roomID, mautrix.EventMessage,
|
||||||
gomatrix.Content{MsgType: msgtype, Body: text})
|
mautrix.Content{MsgType: msgtype, Body: text})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
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.
|
// If the given text contains markdown formatting symbols, it will be rendered into HTML before sending.
|
||||||
// Otherwise, it will be sent as plain text.
|
// 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()
|
defer debug.Recover()
|
||||||
|
|
||||||
html := c.renderMarkdown(text)
|
html := c.renderMarkdown(text)
|
||||||
@ -504,11 +512,11 @@ func (c *Container) SendMarkdownMessage(roomID string, msgtype gomatrix.MessageT
|
|||||||
text = roomRegex.ReplaceAllString(text, "$1")
|
text = roomRegex.ReplaceAllString(text, "$1")
|
||||||
|
|
||||||
c.SendTyping(roomID, false)
|
c.SendTyping(roomID, false)
|
||||||
resp, err := c.client.SendMessageEvent(roomID, gomatrix.EventMessage,
|
resp, err := c.client.SendMessageEvent(roomID, mautrix.EventMessage,
|
||||||
gomatrix.Content{
|
mautrix.Content{
|
||||||
MsgType: msgtype,
|
MsgType: msgtype,
|
||||||
Body: text,
|
Body: text,
|
||||||
Format: gomatrix.FormatHTML,
|
Format: mautrix.FormatHTML,
|
||||||
FormattedBody: html,
|
FormattedBody: html,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -560,7 +568,7 @@ func (c *Container) LeaveRoom(roomID string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetHistory fetches room history.
|
// 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)
|
resp, err := c.client.Messages(roomID, prevBatch, "", 'b', limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
|
@ -21,7 +21,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"maunium.net/go/gomatrix"
|
"maunium.net/go/mautrix"
|
||||||
"maunium.net/go/gomuks/config"
|
"maunium.net/go/gomuks/config"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@ -80,7 +80,7 @@ func TestContainer_SendMarkdownMessage_WithMarkdown(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestContainer_SendTyping(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) {
|
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" {
|
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)
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
call := gomatrix.ReqTyping{}
|
call := mautrix.ReqTyping{}
|
||||||
err = json.Unmarshal(rawBody, &call)
|
err = json.Unmarshal(rawBody, &call)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -187,8 +187,8 @@ func TestContainer_GetHistory(t *testing.T) {
|
|||||||
assert.Equal(t, "456", prevBatch)
|
assert.Equal(t, "456", prevBatch)
|
||||||
}
|
}
|
||||||
|
|
||||||
func mockClient(fn func(*http.Request) (*http.Response, error)) *gomatrix.Client {
|
func mockClient(fn func(*http.Request) (*http.Response, error)) *mautrix.Client {
|
||||||
client, _ := gomatrix.NewClient("https://example.com", "@user:example.com", "foobar")
|
client, _ := mautrix.NewClient("https://example.com", "@user:example.com", "foobar")
|
||||||
client.Client = &http.Client{Transport: MockRoundTripper{RT: fn}}
|
client.Client = &http.Client{Transport: MockRoundTripper{RT: fn}}
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
@ -21,14 +21,14 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"maunium.net/go/gomatrix"
|
"maunium.net/go/mautrix"
|
||||||
"maunium.net/go/gomuks/lib/glob"
|
"maunium.net/go/gomuks/lib/glob"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Room is an interface with the functions that are needed for processing room-specific push conditions
|
// Room is an interface with the functions that are needed for processing room-specific push conditions
|
||||||
type Room interface {
|
type Room interface {
|
||||||
GetMember(mxid string) *gomatrix.Member
|
GetMember(mxid string) *mautrix.Member
|
||||||
GetMembers() map[string]*gomatrix.Member
|
GetMembers() map[string]*mautrix.Member
|
||||||
GetSessionOwner() string
|
GetSessionOwner() string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ type PushCondition struct {
|
|||||||
var MemberCountFilterRegex = regexp.MustCompile("^(==|[<>]=?)?([0-9]+)$")
|
var MemberCountFilterRegex = regexp.MustCompile("^(==|[<>]=?)?([0-9]+)$")
|
||||||
|
|
||||||
// Match checks if this condition is fulfilled for the given event in the given room.
|
// 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 {
|
switch cond.Kind {
|
||||||
case KindEventMatch:
|
case KindEventMatch:
|
||||||
return cond.matchValue(room, event)
|
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, '.')
|
index := strings.IndexRune(cond.Key, '.')
|
||||||
key := cond.Key
|
key := cond.Key
|
||||||
subkey := ""
|
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()
|
ownerID := room.GetSessionOwner()
|
||||||
if ownerID == event.Sender {
|
if ownerID == event.Sender {
|
||||||
return false
|
return false
|
||||||
@ -115,7 +115,7 @@ func (cond *PushCondition) matchDisplayName(room Room, event *gomatrix.Event) bo
|
|||||||
return strings.Contains(event.Content.Body, member.Displayname)
|
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)
|
group := MemberCountFilterRegex.FindStringSubmatch(cond.MemberCountCondition)
|
||||||
if len(group) != 3 {
|
if len(group) != 3 {
|
||||||
return false
|
return false
|
||||||
|
@ -21,7 +21,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"maunium.net/go/gomatrix"
|
"maunium.net/go/mautrix"
|
||||||
"maunium.net/go/gomuks/matrix/pushrules"
|
"maunium.net/go/gomuks/matrix/pushrules"
|
||||||
"maunium.net/go/gomuks/matrix/rooms"
|
"maunium.net/go/gomuks/matrix/rooms"
|
||||||
)
|
)
|
||||||
@ -30,7 +30,7 @@ var (
|
|||||||
blankTestRoom *rooms.Room
|
blankTestRoom *rooms.Room
|
||||||
displaynameTestRoom pushrules.Room
|
displaynameTestRoom pushrules.Room
|
||||||
|
|
||||||
countConditionTestEvent *gomatrix.Event
|
countConditionTestEvent *mautrix.Event
|
||||||
|
|
||||||
displaynamePushCondition *pushrules.PushCondition
|
displaynamePushCondition *pushrules.PushCondition
|
||||||
)
|
)
|
||||||
@ -38,7 +38,7 @@ var (
|
|||||||
func init() {
|
func init() {
|
||||||
blankTestRoom = rooms.NewRoom("!fakeroom:maunium.net", "@tulir:maunium.net")
|
blankTestRoom = rooms.NewRoom("!fakeroom:maunium.net", "@tulir:maunium.net")
|
||||||
|
|
||||||
countConditionTestEvent = &gomatrix.Event{
|
countConditionTestEvent = &mautrix.Event{
|
||||||
Sender: "@tulir:maunium.net",
|
Sender: "@tulir:maunium.net",
|
||||||
Type: "m.room.message",
|
Type: "m.room.message",
|
||||||
Timestamp: 1523791120,
|
Timestamp: 1523791120,
|
||||||
@ -56,8 +56,8 @@ func init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newFakeEvent(evtType string, content map[string]interface{}) *gomatrix.Event {
|
func newFakeEvent(evtType string, content map[string]interface{}) *mautrix.Event {
|
||||||
return &gomatrix.Event{
|
return &mautrix.Event{
|
||||||
Sender: "@tulir:maunium.net",
|
Sender: "@tulir:maunium.net",
|
||||||
Type: evtType,
|
Type: evtType,
|
||||||
Timestamp: 1523791120,
|
Timestamp: 1523791120,
|
||||||
|
@ -4,16 +4,16 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"maunium.net/go/gomatrix"
|
"maunium.net/go/mautrix"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetPushRules returns the push notification rules for the global scope.
|
// 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")
|
return GetScopedPushRules(client, "global")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetScopedPushRules returns the push notification rules for the given scope.
|
// 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))
|
u, _ := url.Parse(client.BuildURL("pushrules", scope))
|
||||||
// client.BuildURL returns the URL without a trailing slash, but the pushrules endpoint requires the slash.
|
// client.BuildURL returns the URL without a trailing slash, but the pushrules endpoint requires the slash.
|
||||||
u.Path += "/"
|
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.
|
// 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{}
|
content := &contentWithRuleset{}
|
||||||
err := json.Unmarshal(event.Content.VeryRaw, content)
|
err := json.Unmarshal(event.Content.VeryRaw, content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -21,7 +21,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"maunium.net/go/gomatrix"
|
"maunium.net/go/mautrix"
|
||||||
"maunium.net/go/gomuks/matrix/pushrules"
|
"maunium.net/go/gomuks/matrix/pushrules"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -33,7 +33,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEventToPushRules(t *testing.T) {
|
func TestEventToPushRules(t *testing.T) {
|
||||||
event := &gomatrix.Event{
|
event := &mautrix.Event{
|
||||||
Type: "m.push_rules",
|
Type: "m.push_rules",
|
||||||
Timestamp: 1523380910,
|
Timestamp: 1523380910,
|
||||||
Content: mapExamplePushRules,
|
Content: mapExamplePushRules,
|
||||||
|
@ -18,7 +18,7 @@ package pushrules
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"maunium.net/go/gomatrix"
|
"maunium.net/go/mautrix"
|
||||||
"maunium.net/go/gomuks/lib/glob"
|
"maunium.net/go/gomuks/lib/glob"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type PushRuleCollection interface {
|
type PushRuleCollection interface {
|
||||||
GetActions(room Room, event *gomatrix.Event) PushActionArray
|
GetActions(room Room, event *mautrix.Event) PushActionArray
|
||||||
}
|
}
|
||||||
|
|
||||||
type PushRuleArray []*PushRule
|
type PushRuleArray []*PushRule
|
||||||
@ -40,7 +40,7 @@ func (rules PushRuleArray) SetType(typ PushRuleType) PushRuleArray {
|
|||||||
return rules
|
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 {
|
for _, rule := range rules {
|
||||||
if !rule.Match(room, event) {
|
if !rule.Match(room, event) {
|
||||||
continue
|
continue
|
||||||
@ -67,7 +67,7 @@ func (rules PushRuleArray) SetTypeAndMap(typ PushRuleType) PushRuleMap {
|
|||||||
return data
|
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 rule *PushRule
|
||||||
var found bool
|
var found bool
|
||||||
switch ruleMap.Type {
|
switch ruleMap.Type {
|
||||||
@ -122,7 +122,7 @@ type PushRule struct {
|
|||||||
Pattern string `json:"pattern,omitempty"`
|
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 {
|
if !rule.Enabled {
|
||||||
return false
|
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 {
|
for _, cond := range rule.Conditions {
|
||||||
if !cond.Match(room, event) {
|
if !cond.Match(room, event) {
|
||||||
return false
|
return false
|
||||||
@ -149,7 +149,7 @@ func (rule *PushRule) matchConditions(room Room, event *gomatrix.Event) bool {
|
|||||||
return true
|
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)
|
pattern, err := glob.Compile(rule.Pattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
|
@ -19,7 +19,7 @@ package pushrules
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"maunium.net/go/gomatrix"
|
"maunium.net/go/mautrix"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PushRuleset struct {
|
type PushRuleset struct {
|
||||||
@ -80,7 +80,7 @@ var DefaultPushActions = make(PushActionArray, 0)
|
|||||||
// GetActions matches the given event against all of the push rule
|
// GetActions matches the given event against all of the push rule
|
||||||
// collections in this push ruleset in the order of priority as
|
// collections in this push ruleset in the order of priority as
|
||||||
// specified in spec section 11.12.1.4.
|
// 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
|
// Add push rule collections to array in priority order
|
||||||
arrays := []PushRuleCollection{rs.Override, rs.Content, rs.Room, rs.Sender, rs.Underride}
|
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.
|
// Loop until one of the push rule collections matches the room/event combo.
|
||||||
|
@ -23,7 +23,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"maunium.net/go/gomatrix"
|
"maunium.net/go/mautrix"
|
||||||
"maunium.net/go/gomuks/debug"
|
"maunium.net/go/gomuks/debug"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
@ -58,7 +58,7 @@ type UnreadMessage struct {
|
|||||||
|
|
||||||
// Room represents a single Matrix room.
|
// Room represents a single Matrix room.
|
||||||
type Room struct {
|
type Room struct {
|
||||||
*gomatrix.Room
|
*mautrix.Room
|
||||||
|
|
||||||
// Whether or not the user has left the room.
|
// Whether or not the user has left the room.
|
||||||
HasLeft bool
|
HasLeft bool
|
||||||
@ -82,10 +82,10 @@ type Room struct {
|
|||||||
LastReceivedMessage time.Time
|
LastReceivedMessage time.Time
|
||||||
|
|
||||||
// MXID -> Member cache calculated from membership events.
|
// 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 first non-SessionUserID member in the room. Calculated at
|
||||||
// the same time as memberCache.
|
// the same time as memberCache.
|
||||||
firstMemberCache *gomatrix.Member
|
firstMemberCache *mautrix.Member
|
||||||
// The name of the room. Calculated from the state event name,
|
// The name of the room. Calculated from the state event name,
|
||||||
// canonical_alias or alias or the member cache.
|
// canonical_alias or alias or the member cache.
|
||||||
nameCache string
|
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
|
// UpdateState updates the room's current state with the given Event. This will clobber events based
|
||||||
// on the type/state_key combination.
|
// 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]
|
_, exists := room.State[event.Type]
|
||||||
if !exists {
|
if !exists {
|
||||||
room.State[event.Type] = make(map[string]*gomatrix.Event)
|
room.State[event.Type] = make(map[string]*mautrix.Event)
|
||||||
}
|
}
|
||||||
switch event.Type {
|
switch event.Type {
|
||||||
case gomatrix.StateRoomName:
|
case mautrix.StateRoomName:
|
||||||
room.nameCache = ""
|
room.nameCache = ""
|
||||||
case gomatrix.StateCanonicalAlias:
|
case mautrix.StateCanonicalAlias:
|
||||||
if room.nameCacheSource >= CanonicalAliasRoomName {
|
if room.nameCacheSource >= CanonicalAliasRoomName {
|
||||||
room.nameCache = ""
|
room.nameCache = ""
|
||||||
}
|
}
|
||||||
room.canonicalAliasCache = ""
|
room.canonicalAliasCache = ""
|
||||||
case gomatrix.StateAliases:
|
case mautrix.StateAliases:
|
||||||
if room.nameCacheSource >= AliasRoomName {
|
if room.nameCacheSource >= AliasRoomName {
|
||||||
room.nameCache = ""
|
room.nameCache = ""
|
||||||
}
|
}
|
||||||
room.aliasesCache = nil
|
room.aliasesCache = nil
|
||||||
case gomatrix.StateMember:
|
case mautrix.StateMember:
|
||||||
room.memberCache = nil
|
room.memberCache = nil
|
||||||
room.firstMemberCache = nil
|
room.firstMemberCache = nil
|
||||||
if room.nameCacheSource >= MemberRoomName {
|
if room.nameCacheSource >= MemberRoomName {
|
||||||
room.nameCache = ""
|
room.nameCache = ""
|
||||||
}
|
}
|
||||||
case gomatrix.StateTopic:
|
case mautrix.StateTopic:
|
||||||
room.topicCache = ""
|
room.topicCache = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,7 +248,7 @@ func (room *Room) UpdateState(event *gomatrix.Event) {
|
|||||||
if event.StateKey != nil {
|
if event.StateKey != nil {
|
||||||
stateKey = *event.StateKey
|
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)
|
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.
|
// 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]
|
stateEventMap, _ := room.State[eventType]
|
||||||
event, _ := stateEventMap[stateKey]
|
event, _ := stateEventMap[stateKey]
|
||||||
return event
|
return event
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStateEvents returns the state events for the given type.
|
// 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]
|
stateEventMap, _ := room.State[eventType]
|
||||||
return stateEventMap
|
return stateEventMap
|
||||||
}
|
}
|
||||||
@ -275,7 +275,7 @@ func (room *Room) GetStateEvents(eventType gomatrix.EventType) map[string]*gomat
|
|||||||
// GetTopic returns the topic of the room.
|
// GetTopic returns the topic of the room.
|
||||||
func (room *Room) GetTopic() string {
|
func (room *Room) GetTopic() string {
|
||||||
if len(room.topicCache) == 0 {
|
if len(room.topicCache) == 0 {
|
||||||
topicEvt := room.GetStateEvent(gomatrix.StateTopic, "")
|
topicEvt := room.GetStateEvent(mautrix.StateTopic, "")
|
||||||
if topicEvt != nil {
|
if topicEvt != nil {
|
||||||
room.topicCache = topicEvt.Content.Topic
|
room.topicCache = topicEvt.Content.Topic
|
||||||
}
|
}
|
||||||
@ -285,7 +285,7 @@ func (room *Room) GetTopic() string {
|
|||||||
|
|
||||||
func (room *Room) GetCanonicalAlias() string {
|
func (room *Room) GetCanonicalAlias() string {
|
||||||
if len(room.canonicalAliasCache) == 0 {
|
if len(room.canonicalAliasCache) == 0 {
|
||||||
canonicalAliasEvt := room.GetStateEvent(gomatrix.StateCanonicalAlias, "")
|
canonicalAliasEvt := room.GetStateEvent(mautrix.StateCanonicalAlias, "")
|
||||||
if canonicalAliasEvt != nil {
|
if canonicalAliasEvt != nil {
|
||||||
room.canonicalAliasCache = canonicalAliasEvt.Content.Alias
|
room.canonicalAliasCache = canonicalAliasEvt.Content.Alias
|
||||||
} else {
|
} else {
|
||||||
@ -301,7 +301,7 @@ func (room *Room) GetCanonicalAlias() string {
|
|||||||
// GetAliases returns the list of aliases that point to this room.
|
// GetAliases returns the list of aliases that point to this room.
|
||||||
func (room *Room) GetAliases() []string {
|
func (room *Room) GetAliases() []string {
|
||||||
if room.aliasesCache == nil {
|
if room.aliasesCache == nil {
|
||||||
aliasEvents := room.GetStateEvents(gomatrix.StateAliases)
|
aliasEvents := room.GetStateEvents(mautrix.StateAliases)
|
||||||
room.aliasesCache = []string{}
|
room.aliasesCache = []string{}
|
||||||
for _, event := range aliasEvents {
|
for _, event := range aliasEvents {
|
||||||
room.aliasesCache = append(room.aliasesCache, event.Content.Aliases...)
|
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.
|
// updateNameFromNameEvent updates the room display name to be the name set in the name event.
|
||||||
func (room *Room) updateNameFromNameEvent() {
|
func (room *Room) updateNameFromNameEvent() {
|
||||||
nameEvt := room.GetStateEvent(gomatrix.StateRoomName, "")
|
nameEvt := room.GetStateEvent(mautrix.StateRoomName, "")
|
||||||
if nameEvt != nil {
|
if nameEvt != nil {
|
||||||
room.nameCache = nameEvt.Content.Name
|
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.
|
// createMemberCache caches all member events into a easily processable MXID -> *Member map.
|
||||||
func (room *Room) createMemberCache() map[string]*gomatrix.Member {
|
func (room *Room) createMemberCache() map[string]*mautrix.Member {
|
||||||
cache := make(map[string]*gomatrix.Member)
|
cache := make(map[string]*mautrix.Member)
|
||||||
events := room.GetStateEvents(gomatrix.StateMember)
|
events := room.GetStateEvents(mautrix.StateMember)
|
||||||
room.firstMemberCache = nil
|
room.firstMemberCache = nil
|
||||||
if events != nil {
|
if events != nil {
|
||||||
for userID, event := range events {
|
for userID, event := range events {
|
||||||
@ -407,7 +407,7 @@ func (room *Room) createMemberCache() map[string]*gomatrix.Member {
|
|||||||
//
|
//
|
||||||
// The members are returned from the cache.
|
// The members are returned from the cache.
|
||||||
// If the cache is empty, it is updated first.
|
// 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 {
|
if len(room.memberCache) == 0 || room.firstMemberCache == nil {
|
||||||
room.createMemberCache()
|
room.createMemberCache()
|
||||||
}
|
}
|
||||||
@ -416,7 +416,7 @@ func (room *Room) GetMembers() map[string]*gomatrix.Member {
|
|||||||
|
|
||||||
// GetMember returns the member with the given MXID.
|
// GetMember returns the member with the given MXID.
|
||||||
// If the member doesn't exist, nil is returned.
|
// 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 {
|
if len(room.memberCache) == 0 {
|
||||||
room.createMemberCache()
|
room.createMemberCache()
|
||||||
}
|
}
|
||||||
@ -432,7 +432,7 @@ func (room *Room) GetSessionOwner() string {
|
|||||||
// NewRoom creates a new Room with the given ID
|
// NewRoom creates a new Room with the given ID
|
||||||
func NewRoom(roomID, owner string) *Room {
|
func NewRoom(roomID, owner string) *Room {
|
||||||
return &Room{
|
return &Room{
|
||||||
Room: gomatrix.NewRoom(roomID),
|
Room: mautrix.NewRoom(roomID),
|
||||||
fetchHistoryLock: &sync.Mutex{},
|
fetchHistoryLock: &sync.Mutex{},
|
||||||
SessionUserID: owner,
|
SessionUserID: owner,
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"maunium.net/go/gomatrix"
|
"maunium.net/go/mautrix"
|
||||||
"maunium.net/go/gomuks/matrix/rooms"
|
"maunium.net/go/gomuks/matrix/rooms"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ func TestNewRoom_DefaultValues(t *testing.T) {
|
|||||||
|
|
||||||
func TestRoom_GetCanonicalAlias(t *testing.T) {
|
func TestRoom_GetCanonicalAlias(t *testing.T) {
|
||||||
room := rooms.NewRoom("!test:maunium.net", "@tulir:maunium.net")
|
room := rooms.NewRoom("!test:maunium.net", "@tulir:maunium.net")
|
||||||
room.UpdateState(&gomatrix.Event{
|
room.UpdateState(&mautrix.Event{
|
||||||
Type: "m.room.canonical_alias",
|
Type: "m.room.canonical_alias",
|
||||||
Content: map[string]interface{}{
|
Content: map[string]interface{}{
|
||||||
"alias": "#foo:maunium.net",
|
"alias": "#foo:maunium.net",
|
||||||
@ -50,7 +50,7 @@ func TestRoom_GetCanonicalAlias(t *testing.T) {
|
|||||||
|
|
||||||
func TestRoom_GetTopic(t *testing.T) {
|
func TestRoom_GetTopic(t *testing.T) {
|
||||||
room := rooms.NewRoom("!test:maunium.net", "@tulir:maunium.net")
|
room := rooms.NewRoom("!test:maunium.net", "@tulir:maunium.net")
|
||||||
room.UpdateState(&gomatrix.Event{
|
room.UpdateState(&mautrix.Event{
|
||||||
Type: "m.room.topic",
|
Type: "m.room.topic",
|
||||||
Content: map[string]interface{}{
|
Content: map[string]interface{}{
|
||||||
"topic": "test topic",
|
"topic": "test topic",
|
||||||
@ -87,7 +87,7 @@ func TestRoom_GetAliases(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func addName(room *rooms.Room) {
|
func addName(room *rooms.Room) {
|
||||||
room.UpdateState(&gomatrix.Event{
|
room.UpdateState(&mautrix.Event{
|
||||||
Type: "m.room.name",
|
Type: "m.room.name",
|
||||||
Content: map[string]interface{}{
|
Content: map[string]interface{}{
|
||||||
"name": "Test room",
|
"name": "Test room",
|
||||||
@ -96,7 +96,7 @@ func addName(room *rooms.Room) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func addCanonicalAlias(room *rooms.Room) {
|
func addCanonicalAlias(room *rooms.Room) {
|
||||||
room.UpdateState(&gomatrix.Event{
|
room.UpdateState(&mautrix.Event{
|
||||||
Type: "m.room.canonical_alias",
|
Type: "m.room.canonical_alias",
|
||||||
Content: map[string]interface{}{
|
Content: map[string]interface{}{
|
||||||
"alias": "#foo:maunium.net",
|
"alias": "#foo:maunium.net",
|
||||||
@ -106,7 +106,7 @@ func addCanonicalAlias(room *rooms.Room) {
|
|||||||
|
|
||||||
func addAliases(room *rooms.Room) {
|
func addAliases(room *rooms.Room) {
|
||||||
server1 := "maunium.net"
|
server1 := "maunium.net"
|
||||||
room.UpdateState(&gomatrix.Event{
|
room.UpdateState(&mautrix.Event{
|
||||||
Type: "m.room.aliases",
|
Type: "m.room.aliases",
|
||||||
StateKey: &server1,
|
StateKey: &server1,
|
||||||
Content: map[string]interface{}{
|
Content: map[string]interface{}{
|
||||||
@ -115,7 +115,7 @@ func addAliases(room *rooms.Room) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
server2 := "matrix.org"
|
server2 := "matrix.org"
|
||||||
room.UpdateState(&gomatrix.Event{
|
room.UpdateState(&mautrix.Event{
|
||||||
Type: "m.room.aliases",
|
Type: "m.room.aliases",
|
||||||
StateKey: &server2,
|
StateKey: &server2,
|
||||||
Content: map[string]interface{}{
|
Content: map[string]interface{}{
|
||||||
@ -126,7 +126,7 @@ func addAliases(room *rooms.Room) {
|
|||||||
|
|
||||||
func addMembers(room *rooms.Room, count int) {
|
func addMembers(room *rooms.Room, count int) {
|
||||||
user1 := "@tulir:maunium.net"
|
user1 := "@tulir:maunium.net"
|
||||||
room.UpdateState(&gomatrix.Event{
|
room.UpdateState(&mautrix.Event{
|
||||||
Type: "m.room.member",
|
Type: "m.room.member",
|
||||||
StateKey: &user1,
|
StateKey: &user1,
|
||||||
Content: map[string]interface{}{
|
Content: map[string]interface{}{
|
||||||
@ -146,7 +146,7 @@ func addMembers(room *rooms.Room, count int) {
|
|||||||
if i%5 == 0 {
|
if i%5 == 0 {
|
||||||
content["membership"] = "invite"
|
content["membership"] = "invite"
|
||||||
}
|
}
|
||||||
room.UpdateState(&gomatrix.Event{
|
room.UpdateState(&mautrix.Event{
|
||||||
Type: "m.room.member",
|
Type: "m.room.member",
|
||||||
StateKey: &userN,
|
StateKey: &userN,
|
||||||
Content: content,
|
Content: content,
|
||||||
|
@ -14,15 +14,16 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// 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
|
package matrix
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"maunium.net/go/gomuks/debug"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"maunium.net/go/gomatrix"
|
"maunium.net/go/mautrix"
|
||||||
"maunium.net/go/gomuks/matrix/rooms"
|
"maunium.net/go/gomuks/matrix/rooms"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -44,14 +45,14 @@ const (
|
|||||||
EventSourceEphemeral
|
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
|
// 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
|
// 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.
|
// pattern to notify callers about incoming events. See GomuksSyncer.OnEventType for more information.
|
||||||
type GomuksSyncer struct {
|
type GomuksSyncer struct {
|
||||||
Session SyncerSession
|
Session SyncerSession
|
||||||
listeners map[gomatrix.EventType][]EventHandler // event type to listeners array
|
listeners map[mautrix.EventType][]EventHandler // event type to listeners array
|
||||||
FirstSyncDone bool
|
FirstSyncDone bool
|
||||||
InitDoneCallback func()
|
InitDoneCallback func()
|
||||||
}
|
}
|
||||||
@ -60,13 +61,14 @@ type GomuksSyncer struct {
|
|||||||
func NewGomuksSyncer(session SyncerSession) *GomuksSyncer {
|
func NewGomuksSyncer(session SyncerSession) *GomuksSyncer {
|
||||||
return &GomuksSyncer{
|
return &GomuksSyncer{
|
||||||
Session: session,
|
Session: session,
|
||||||
listeners: make(map[gomatrix.EventType][]EventHandler),
|
listeners: make(map[mautrix.EventType][]EventHandler),
|
||||||
FirstSyncDone: false,
|
FirstSyncDone: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProcessResponse processes a Matrix sync response.
|
// 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.Presence.Events, EventSourcePresence, false)
|
||||||
s.processSyncEvents(nil, res.AccountData.Events, EventSourceAccountData, false)
|
s.processSyncEvents(nil, res.AccountData.Events, EventSourceAccountData, false)
|
||||||
|
|
||||||
@ -106,7 +108,7 @@ func (s *GomuksSyncer) ProcessResponse(res *gomatrix.RespSync, since string) (er
|
|||||||
return
|
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 {
|
for _, event := range events {
|
||||||
if !checkStateKey || event.StateKey != nil {
|
if !checkStateKey || event.StateKey != nil {
|
||||||
s.processSyncEvent(room, event, source)
|
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 {
|
if room != nil {
|
||||||
event.RoomID = room.ID
|
event.RoomID = room.ID
|
||||||
}
|
}
|
||||||
if event.Type.Class == gomatrix.StateEventType {
|
if event.Type.Class == mautrix.StateEventType {
|
||||||
room.UpdateState(event)
|
room.UpdateState(event)
|
||||||
}
|
}
|
||||||
s.notifyListeners(source, 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.
|
// OnEventType allows callers to be notified when there are new events for the given event type.
|
||||||
// There are no duplicate checks.
|
// 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]
|
_, exists := s.listeners[eventType]
|
||||||
if !exists {
|
if !exists {
|
||||||
s.listeners[eventType] = []EventHandler{}
|
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)
|
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]
|
listeners, exists := s.listeners[event.Type]
|
||||||
if !exists {
|
if !exists {
|
||||||
return
|
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.
|
// 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
|
return 10 * time.Second, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFilterJSON returns a filter with a timeline limit of 50.
|
// GetFilterJSON returns a filter with a timeline limit of 50.
|
||||||
func (s *GomuksSyncer) GetFilterJSON(userID string) json.RawMessage {
|
func (s *GomuksSyncer) GetFilterJSON(userID string) json.RawMessage {
|
||||||
filter := &gomatrix.Filter{
|
filter := &mautrix.Filter{
|
||||||
Room: gomatrix.RoomFilter{
|
Room: mautrix.RoomFilter{
|
||||||
IncludeLeave: false,
|
IncludeLeave: false,
|
||||||
State: gomatrix.FilterPart{
|
State: mautrix.FilterPart{
|
||||||
Types: []string{
|
Types: []string{
|
||||||
"m.room.member",
|
"m.room.member",
|
||||||
"m.room.name",
|
"m.room.name",
|
||||||
@ -163,21 +166,21 @@ func (s *GomuksSyncer) GetFilterJSON(userID string) json.RawMessage {
|
|||||||
"m.room.aliases",
|
"m.room.aliases",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Timeline: gomatrix.FilterPart{
|
Timeline: mautrix.FilterPart{
|
||||||
Types: []string{"m.room.message", "m.room.member"},
|
Types: []string{"m.room.message", "m.room.member"},
|
||||||
Limit: 50,
|
Limit: 50,
|
||||||
},
|
},
|
||||||
Ephemeral: gomatrix.FilterPart{
|
Ephemeral: mautrix.FilterPart{
|
||||||
Types: []string{"m.typing", "m.receipt"},
|
Types: []string{"m.typing", "m.receipt"},
|
||||||
},
|
},
|
||||||
AccountData: gomatrix.FilterPart{
|
AccountData: mautrix.FilterPart{
|
||||||
Types: []string{"m.tag"},
|
Types: []string{"m.tag"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
AccountData: gomatrix.FilterPart{
|
AccountData: mautrix.FilterPart{
|
||||||
Types: []string{"m.push_rules", "m.direct", "net.maunium.gomuks.preferences"},
|
Types: []string{"m.push_rules", "m.direct", "net.maunium.gomuks.preferences"},
|
||||||
},
|
},
|
||||||
Presence: gomatrix.FilterPart{
|
Presence: mautrix.FilterPart{
|
||||||
Types: []string{},
|
Types: []string{},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"maunium.net/go/gomatrix"
|
"maunium.net/go/mautrix"
|
||||||
"maunium.net/go/gomuks/matrix"
|
"maunium.net/go/gomuks/matrix"
|
||||||
"maunium.net/go/gomuks/matrix/rooms"
|
"maunium.net/go/gomuks/matrix/rooms"
|
||||||
)
|
)
|
||||||
@ -42,13 +42,13 @@ func TestGomuksSyncer_ProcessResponse(t *testing.T) {
|
|||||||
userID: "@tulir:maunium.net",
|
userID: "@tulir:maunium.net",
|
||||||
rooms: map[string]*rooms.Room{
|
rooms: map[string]*rooms.Room{
|
||||||
"!foo:maunium.net": {
|
"!foo:maunium.net": {
|
||||||
Room: gomatrix.NewRoom("!foo:maunium.net"),
|
Room: mautrix.NewRoom("!foo:maunium.net"),
|
||||||
},
|
},
|
||||||
"!bar:maunium.net": {
|
"!bar:maunium.net": {
|
||||||
Room: gomatrix.NewRoom("!bar:maunium.net"),
|
Room: mautrix.NewRoom("!bar:maunium.net"),
|
||||||
},
|
},
|
||||||
"!test: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.OnEventType("m.room.message", ml.receive)
|
||||||
syncer.GetFilterJSON("@tulir:maunium.net")
|
syncer.GetFilterJSON("@tulir:maunium.net")
|
||||||
|
|
||||||
joinEvt := &gomatrix.Event{
|
joinEvt := &mautrix.Event{
|
||||||
ID: "!join:maunium.net",
|
ID: "!join:maunium.net",
|
||||||
Type: "m.room.member",
|
Type: "m.room.member",
|
||||||
Sender: "@tulir:maunium.net",
|
Sender: "@tulir:maunium.net",
|
||||||
@ -67,7 +67,7 @@ func TestGomuksSyncer_ProcessResponse(t *testing.T) {
|
|||||||
"membership": "join",
|
"membership": "join",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
messageEvt := &gomatrix.Event{
|
messageEvt := &mautrix.Event{
|
||||||
ID: "!msg:maunium.net",
|
ID: "!msg:maunium.net",
|
||||||
Type: "m.room.message",
|
Type: "m.room.message",
|
||||||
Content: map[string]interface{}{
|
Content: map[string]interface{}{
|
||||||
@ -75,11 +75,11 @@ func TestGomuksSyncer_ProcessResponse(t *testing.T) {
|
|||||||
"msgtype": "m.text",
|
"msgtype": "m.text",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
unhandledEvt := &gomatrix.Event{
|
unhandledEvt := &mautrix.Event{
|
||||||
ID: "!unhandled:maunium.net",
|
ID: "!unhandled:maunium.net",
|
||||||
Type: "m.room.unhandled_event",
|
Type: "m.room.unhandled_event",
|
||||||
}
|
}
|
||||||
inviteEvt := &gomatrix.Event{
|
inviteEvt := &mautrix.Event{
|
||||||
ID: "!invite:matrix.org",
|
ID: "!invite:matrix.org",
|
||||||
Type: "m.room.member",
|
Type: "m.room.member",
|
||||||
Sender: "@you:matrix.org",
|
Sender: "@you:matrix.org",
|
||||||
@ -88,7 +88,7 @@ func TestGomuksSyncer_ProcessResponse(t *testing.T) {
|
|||||||
"membership": "invite",
|
"membership": "invite",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
leaveEvt := &gomatrix.Event{
|
leaveEvt := &mautrix.Event{
|
||||||
ID: "!leave:matrix.org",
|
ID: "!leave:matrix.org",
|
||||||
Type: "m.room.member",
|
Type: "m.room.member",
|
||||||
Sender: "@you:matrix.org",
|
Sender: "@you:matrix.org",
|
||||||
@ -100,27 +100,27 @@ func TestGomuksSyncer_ProcessResponse(t *testing.T) {
|
|||||||
|
|
||||||
resp := newRespSync()
|
resp := newRespSync()
|
||||||
resp.Rooms.Join["!foo:maunium.net"] = join{
|
resp.Rooms.Join["!foo:maunium.net"] = join{
|
||||||
State: events{Events: []*gomatrix.Event{joinEvt}},
|
State: events{Events: []*mautrix.Event{joinEvt}},
|
||||||
Timeline: timeline{Events: []*gomatrix.Event{messageEvt, unhandledEvt}},
|
Timeline: timeline{Events: []*mautrix.Event{messageEvt, unhandledEvt}},
|
||||||
}
|
}
|
||||||
resp.Rooms.Invite["!bar:maunium.net"] = struct {
|
resp.Rooms.Invite["!bar:maunium.net"] = struct {
|
||||||
State struct {
|
State struct {
|
||||||
Events []*gomatrix.Event `json:"events"`
|
Events []*mautrix.Event `json:"events"`
|
||||||
} `json:"invite_state"`
|
} `json:"invite_state"`
|
||||||
}{
|
}{
|
||||||
State: events{Events: []*gomatrix.Event{inviteEvt}},
|
State: events{Events: []*mautrix.Event{inviteEvt}},
|
||||||
}
|
}
|
||||||
resp.Rooms.Leave["!test:maunium.net"] = struct {
|
resp.Rooms.Leave["!test:maunium.net"] = struct {
|
||||||
State struct {
|
State struct {
|
||||||
Events []*gomatrix.Event `json:"events"`
|
Events []*mautrix.Event `json:"events"`
|
||||||
} `json:"state"`
|
} `json:"state"`
|
||||||
Timeline struct {
|
Timeline struct {
|
||||||
Events []*gomatrix.Event `json:"events"`
|
Events []*mautrix.Event `json:"events"`
|
||||||
Limited bool `json:"limited"`
|
Limited bool `json:"limited"`
|
||||||
PrevBatch string `json:"prev_batch"`
|
PrevBatch string `json:"prev_batch"`
|
||||||
} `json:"timeline"`
|
} `json:"timeline"`
|
||||||
}{
|
}{
|
||||||
State: events{Events: []*gomatrix.Event{leaveEvt}},
|
State: events{Events: []*mautrix.Event{leaveEvt}},
|
||||||
}
|
}
|
||||||
|
|
||||||
syncer.ProcessResponse(resp, "since")
|
syncer.ProcessResponse(resp, "since")
|
||||||
@ -145,28 +145,28 @@ func (mss *mockSyncerSession) GetUserID() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type events struct {
|
type events struct {
|
||||||
Events []*gomatrix.Event `json:"events"`
|
Events []*mautrix.Event `json:"events"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type timeline struct {
|
type timeline struct {
|
||||||
Events []*gomatrix.Event `json:"events"`
|
Events []*mautrix.Event `json:"events"`
|
||||||
Limited bool `json:"limited"`
|
Limited bool `json:"limited"`
|
||||||
PrevBatch string `json:"prev_batch"`
|
PrevBatch string `json:"prev_batch"`
|
||||||
}
|
}
|
||||||
type join struct {
|
type join struct {
|
||||||
State struct {
|
State struct {
|
||||||
Events []*gomatrix.Event `json:"events"`
|
Events []*mautrix.Event `json:"events"`
|
||||||
} `json:"state"`
|
} `json:"state"`
|
||||||
Timeline struct {
|
Timeline struct {
|
||||||
Events []*gomatrix.Event `json:"events"`
|
Events []*mautrix.Event `json:"events"`
|
||||||
Limited bool `json:"limited"`
|
Limited bool `json:"limited"`
|
||||||
PrevBatch string `json:"prev_batch"`
|
PrevBatch string `json:"prev_batch"`
|
||||||
} `json:"timeline"`
|
} `json:"timeline"`
|
||||||
Ephemeral struct {
|
Ephemeral struct {
|
||||||
Events []*gomatrix.Event `json:"events"`
|
Events []*mautrix.Event `json:"events"`
|
||||||
} `json:"ephemeral"`
|
} `json:"ephemeral"`
|
||||||
AccountData struct {
|
AccountData struct {
|
||||||
Events []*gomatrix.Event `json:"events"`
|
Events []*mautrix.Event `json:"events"`
|
||||||
} `json:"account_data"`
|
} `json:"account_data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,42 +175,42 @@ func ptr(text string) *string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type mockListener struct {
|
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)
|
ml.received = append(ml.received, evt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRespSync() *gomatrix.RespSync {
|
func newRespSync() *mautrix.RespSync {
|
||||||
resp := &gomatrix.RespSync{NextBatch: "123"}
|
resp := &mautrix.RespSync{NextBatch: "123"}
|
||||||
resp.Rooms.Join = make(map[string]struct {
|
resp.Rooms.Join = make(map[string]struct {
|
||||||
State struct {
|
State struct {
|
||||||
Events []*gomatrix.Event `json:"events"`
|
Events []*mautrix.Event `json:"events"`
|
||||||
} `json:"state"`
|
} `json:"state"`
|
||||||
Timeline struct {
|
Timeline struct {
|
||||||
Events []*gomatrix.Event `json:"events"`
|
Events []*mautrix.Event `json:"events"`
|
||||||
Limited bool `json:"limited"`
|
Limited bool `json:"limited"`
|
||||||
PrevBatch string `json:"prev_batch"`
|
PrevBatch string `json:"prev_batch"`
|
||||||
} `json:"timeline"`
|
} `json:"timeline"`
|
||||||
Ephemeral struct {
|
Ephemeral struct {
|
||||||
Events []*gomatrix.Event `json:"events"`
|
Events []*mautrix.Event `json:"events"`
|
||||||
} `json:"ephemeral"`
|
} `json:"ephemeral"`
|
||||||
AccountData struct {
|
AccountData struct {
|
||||||
Events []*gomatrix.Event `json:"events"`
|
Events []*mautrix.Event `json:"events"`
|
||||||
} `json:"account_data"`
|
} `json:"account_data"`
|
||||||
})
|
})
|
||||||
resp.Rooms.Invite = make(map[string]struct {
|
resp.Rooms.Invite = make(map[string]struct {
|
||||||
State struct {
|
State struct {
|
||||||
Events []*gomatrix.Event `json:"events"`
|
Events []*mautrix.Event `json:"events"`
|
||||||
} `json:"invite_state"`
|
} `json:"invite_state"`
|
||||||
})
|
})
|
||||||
resp.Rooms.Leave = make(map[string]struct {
|
resp.Rooms.Leave = make(map[string]struct {
|
||||||
State struct {
|
State struct {
|
||||||
Events []*gomatrix.Event `json:"events"`
|
Events []*mautrix.Event `json:"events"`
|
||||||
} `json:"state"`
|
} `json:"state"`
|
||||||
Timeline struct {
|
Timeline struct {
|
||||||
Events []*gomatrix.Event `json:"events"`
|
Events []*mautrix.Event `json:"events"`
|
||||||
Limited bool `json:"limited"`
|
Limited bool `json:"limited"`
|
||||||
PrevBatch string `json:"prev_batch"`
|
PrevBatch string `json:"prev_batch"`
|
||||||
} `json:"timeline"`
|
} `json:"timeline"`
|
||||||
|
@ -19,7 +19,7 @@ package ui
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"maunium.net/go/gomatrix"
|
"maunium.net/go/mautrix"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
@ -133,7 +133,7 @@ func cmdSendEvent(cmd *Command) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
roomID := cmd.Args[0]
|
roomID := cmd.Args[0]
|
||||||
eventType := gomatrix.NewEventType(cmd.Args[1])
|
eventType := mautrix.NewEventType(cmd.Args[1])
|
||||||
rawContent := strings.Join(cmd.Args[2:], "")
|
rawContent := strings.Join(cmd.Args[2:], "")
|
||||||
debug.Print(roomID, eventType, rawContent)
|
debug.Print(roomID, eventType, rawContent)
|
||||||
|
|
||||||
@ -162,7 +162,7 @@ func cmdSetState(cmd *Command) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
roomID := cmd.Args[0]
|
roomID := cmd.Args[0]
|
||||||
eventType := gomatrix.NewEventType(cmd.Args[1])
|
eventType := mautrix.NewEventType(cmd.Args[1])
|
||||||
stateKey := cmd.Args[2]
|
stateKey := cmd.Args[2]
|
||||||
if stateKey == "-" {
|
if stateKey == "-" {
|
||||||
stateKey = ""
|
stateKey = ""
|
||||||
|
@ -18,7 +18,7 @@ package messages
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"maunium.net/go/gomatrix"
|
"maunium.net/go/mautrix"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"maunium.net/go/gomuks/config"
|
"maunium.net/go/gomuks/config"
|
||||||
@ -34,7 +34,7 @@ func init() {
|
|||||||
|
|
||||||
type BaseMessage struct {
|
type BaseMessage struct {
|
||||||
MsgID string
|
MsgID string
|
||||||
MsgType gomatrix.MessageType
|
MsgType mautrix.MessageType
|
||||||
MsgSenderID string
|
MsgSenderID string
|
||||||
MsgSender string
|
MsgSender string
|
||||||
MsgSenderColor tcell.Color
|
MsgSenderColor tcell.Color
|
||||||
@ -48,7 +48,7 @@ type BaseMessage struct {
|
|||||||
prevPrefs config.UserPreferences
|
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{
|
return BaseMessage{
|
||||||
MsgSenderID: sender,
|
MsgSenderID: sender,
|
||||||
MsgSender: displayname,
|
MsgSender: displayname,
|
||||||
@ -195,11 +195,11 @@ func (msg *BaseMessage) SetID(id string) {
|
|||||||
msg.MsgID = id
|
msg.MsgID = id
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msg *BaseMessage) Type() gomatrix.MessageType {
|
func (msg *BaseMessage) Type() mautrix.MessageType {
|
||||||
return msg.MsgType
|
return msg.MsgType
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msg *BaseMessage) SetType(msgtype gomatrix.MessageType) {
|
func (msg *BaseMessage) SetType(msgtype mautrix.MessageType) {
|
||||||
msg.MsgType = msgtype
|
msg.MsgType = msgtype
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ package messages
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"maunium.net/go/gomatrix"
|
"maunium.net/go/mautrix"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"maunium.net/go/gomuks/config"
|
"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.
|
// 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{
|
return &ExpandedTextMessage{
|
||||||
BaseMessage: newBaseMessage(id, sender, displayname, msgtype, timestamp),
|
BaseMessage: newBaseMessage(id, sender, displayname, msgtype, timestamp),
|
||||||
MsgText: text,
|
MsgText: text,
|
||||||
|
@ -20,7 +20,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"fmt"
|
"fmt"
|
||||||
"maunium.net/go/gomatrix"
|
"maunium.net/go/mautrix"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"image/color"
|
"image/color"
|
||||||
@ -48,7 +48,7 @@ type ImageMessage struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewImageMessage creates a new ImageMessage object with the provided values and the default state.
|
// 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{
|
return &ImageMessage{
|
||||||
newBaseMessage(id, sender, displayname, msgtype, timestamp),
|
newBaseMessage(id, sender, displayname, msgtype, timestamp),
|
||||||
body,
|
body,
|
||||||
|
@ -24,10 +24,10 @@ import (
|
|||||||
|
|
||||||
"github.com/lucasb-eyer/go-colorful"
|
"github.com/lucasb-eyer/go-colorful"
|
||||||
"golang.org/x/net/html"
|
"golang.org/x/net/html"
|
||||||
"maunium.net/go/gomatrix"
|
|
||||||
"maunium.net/go/gomuks/matrix/rooms"
|
"maunium.net/go/gomuks/matrix/rooms"
|
||||||
"maunium.net/go/gomuks/ui/messages/tstring"
|
"maunium.net/go/gomuks/ui/messages/tstring"
|
||||||
"maunium.net/go/gomuks/ui/widget"
|
"maunium.net/go/gomuks/ui/widget"
|
||||||
|
"maunium.net/go/mautrix"
|
||||||
"maunium.net/go/tcell"
|
"maunium.net/go/tcell"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
@ -69,6 +69,9 @@ func (parser *htmlParser) getAttribute(node *html.Node, attribute string) string
|
|||||||
}
|
}
|
||||||
|
|
||||||
func digits(num int) int {
|
func digits(num int) int {
|
||||||
|
if num <= 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
return int(math.Floor(math.Log10(float64(num))) + 1)
|
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.
|
// 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 := evt.Content.FormattedBody
|
||||||
htmlData = strings.Replace(htmlData, "\t", " ", -1)
|
htmlData = strings.Replace(htmlData, "\t", " ", -1)
|
||||||
|
|
||||||
parser := htmlParser{room}
|
parser := htmlParser{room}
|
||||||
str := parser.Parse(htmlData)
|
str := parser.Parse(htmlData)
|
||||||
|
|
||||||
if evt.Content.MsgType == gomatrix.MsgEmote {
|
if evt.Content.MsgType == mautrix.MsgEmote {
|
||||||
str = tstring.Join([]tstring.TString{
|
str = tstring.Join([]tstring.TString{
|
||||||
tstring.NewTString("* "),
|
tstring.NewTString("* "),
|
||||||
tstring.NewColorTString(senderDisplayname, widget.GetHashColor(evt.Sender)),
|
tstring.NewColorTString(senderDisplayname, widget.GetHashColor(evt.Sender)),
|
||||||
|
@ -18,24 +18,28 @@ package parser
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"maunium.net/go/gomatrix"
|
|
||||||
"maunium.net/go/gomuks/debug"
|
"maunium.net/go/gomuks/debug"
|
||||||
"maunium.net/go/gomuks/interface"
|
"maunium.net/go/gomuks/interface"
|
||||||
"maunium.net/go/gomuks/matrix/rooms"
|
"maunium.net/go/gomuks/matrix/rooms"
|
||||||
"maunium.net/go/gomuks/ui/messages"
|
"maunium.net/go/gomuks/ui/messages"
|
||||||
"maunium.net/go/gomuks/ui/messages/tstring"
|
"maunium.net/go/gomuks/ui/messages/tstring"
|
||||||
"maunium.net/go/gomuks/ui/widget"
|
"maunium.net/go/gomuks/ui/widget"
|
||||||
|
"maunium.net/go/mautrix"
|
||||||
"maunium.net/go/tcell"
|
"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 {
|
switch evt.Type {
|
||||||
case gomatrix.EventMessage:
|
case mautrix.EventSticker:
|
||||||
|
evt.Content.MsgType = mautrix.MsgImage
|
||||||
|
fallthrough
|
||||||
|
case mautrix.EventMessage:
|
||||||
return ParseMessage(matrix, room, evt)
|
return ParseMessage(matrix, room, evt)
|
||||||
case gomatrix.StateMember:
|
case mautrix.StateMember:
|
||||||
return ParseMembershipEvent(room, evt)
|
return ParseMembershipEvent(room, evt)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -49,16 +53,27 @@ func unixToTime(unix int64) time.Time {
|
|||||||
return timestamp
|
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
|
displayname := evt.Sender
|
||||||
member := room.GetMember(evt.Sender)
|
member := room.GetMember(evt.Sender)
|
||||||
if member != nil {
|
if member != nil {
|
||||||
displayname = member.Displayname
|
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)
|
ts := unixToTime(evt.Timestamp)
|
||||||
switch evt.Content.MsgType {
|
switch evt.Content.MsgType {
|
||||||
case "m.text", "m.notice", "m.emote":
|
case "m.text", "m.notice", "m.emote":
|
||||||
if evt.Content.Format == gomatrix.FormatHTML {
|
if evt.Content.Format == mautrix.FormatHTML {
|
||||||
text := ParseHTMLMessage(room, evt, displayname)
|
text := ParseHTMLMessage(room, evt, displayname)
|
||||||
return messages.NewExpandedTextMessage(evt.ID, evt.Sender, displayname, evt.Content.MsgType, text, ts)
|
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
|
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 {
|
switch membership {
|
||||||
case "invite":
|
case "invite":
|
||||||
sender = "---"
|
sender = "---"
|
||||||
@ -88,7 +103,7 @@ func getMembershipChangeMessage(evt *gomatrix.Event, membership, prevMembership
|
|||||||
case "leave":
|
case "leave":
|
||||||
sender = "<--"
|
sender = "<--"
|
||||||
if evt.Sender != *evt.StateKey {
|
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 = tstring.NewColorTString(fmt.Sprintf("%s unbanned %s", senderDisplayname, displayname), tcell.ColorGreen)
|
||||||
text.Colorize(len(senderDisplayname)+len(" unbanned "), len(displayname), widget.GetHashColor(*evt.StateKey))
|
text.Colorize(len(senderDisplayname)+len(" unbanned "), len(displayname), widget.GetHashColor(*evt.StateKey))
|
||||||
} else {
|
} else {
|
||||||
@ -111,7 +126,7 @@ func getMembershipChangeMessage(evt *gomatrix.Event, membership, prevMembership
|
|||||||
return
|
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)
|
member := room.GetMember(evt.Sender)
|
||||||
senderDisplayname := evt.Sender
|
senderDisplayname := evt.Sender
|
||||||
if member != nil {
|
if member != nil {
|
||||||
@ -124,7 +139,7 @@ func getMembershipEventContent(room *rooms.Room, evt *gomatrix.Event) (sender st
|
|||||||
displayname = *evt.StateKey
|
displayname = *evt.StateKey
|
||||||
}
|
}
|
||||||
|
|
||||||
prevMembership := gomatrix.MembershipLeave
|
prevMembership := mautrix.MembershipLeave
|
||||||
prevDisplayname := *evt.StateKey
|
prevDisplayname := *evt.StateKey
|
||||||
if evt.Unsigned.PrevContent != nil {
|
if evt.Unsigned.PrevContent != nil {
|
||||||
prevMembership = evt.Unsigned.PrevContent.Membership
|
prevMembership = evt.Unsigned.PrevContent.Membership
|
||||||
@ -146,7 +161,7 @@ func getMembershipEventContent(room *rooms.Room, evt *gomatrix.Event) (sender st
|
|||||||
return
|
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)
|
displayname, text := getMembershipEventContent(room, evt)
|
||||||
if len(text) == 0 {
|
if len(text) == 0 {
|
||||||
return nil
|
return nil
|
||||||
|
@ -19,7 +19,7 @@ package messages
|
|||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"fmt"
|
"fmt"
|
||||||
"maunium.net/go/gomatrix"
|
"maunium.net/go/mautrix"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"maunium.net/go/gomuks/config"
|
"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.
|
// 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{
|
return &TextMessage{
|
||||||
BaseMessage: newBaseMessage(id, sender, displayname, msgtype, timestamp),
|
BaseMessage: newBaseMessage(id, sender, displayname, msgtype, timestamp),
|
||||||
MsgText: text,
|
MsgText: text,
|
||||||
@ -58,7 +58,7 @@ func (msg *TextMessage) getCache() tstring.TString {
|
|||||||
return msg.cache
|
return msg.cache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msg *TextMessage) SetType(msgtype gomatrix.MessageType) {
|
func (msg *TextMessage) SetType(msgtype mautrix.MessageType) {
|
||||||
msg.BaseMessage.SetType(msgtype)
|
msg.BaseMessage.SetType(msgtype)
|
||||||
msg.cache = nil
|
msg.cache = nil
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ package ui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"maunium.net/go/gomatrix"
|
"maunium.net/go/mautrix"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"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)
|
member := view.Room.GetMember(sender)
|
||||||
displayname := sender
|
displayname := sender
|
||||||
if member != nil {
|
if member != nil {
|
||||||
@ -357,11 +357,11 @@ func (view *RoomView) newUIMessage(id, sender string, msgtype gomatrix.MessageTy
|
|||||||
return msg
|
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)
|
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()
|
now := time.Now()
|
||||||
id := strconv.FormatInt(now.UnixNano(), 10)
|
id := strconv.FormatInt(now.UnixNano(), 10)
|
||||||
sender := ""
|
sender := ""
|
||||||
|
@ -17,11 +17,11 @@
|
|||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"maunium.net/go/gomatrix"
|
|
||||||
"maunium.net/go/gomuks/config"
|
"maunium.net/go/gomuks/config"
|
||||||
"maunium.net/go/gomuks/debug"
|
"maunium.net/go/gomuks/debug"
|
||||||
"maunium.net/go/gomuks/interface"
|
"maunium.net/go/gomuks/interface"
|
||||||
"maunium.net/go/gomuks/ui/widget"
|
"maunium.net/go/gomuks/ui/widget"
|
||||||
|
"maunium.net/go/mautrix"
|
||||||
"maunium.net/go/tview"
|
"maunium.net/go/tview"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -89,8 +89,8 @@ func (view *LoginView) Login() {
|
|||||||
debug.Print("Init error:", err)
|
debug.Print("Init error:", err)
|
||||||
err = view.matrix.Login(mxid, password)
|
err = view.matrix.Login(mxid, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if httpErr, ok := err.(gomatrix.HTTPError); ok {
|
if httpErr, ok := err.(mautrix.HTTPError); ok {
|
||||||
if respErr, ok := httpErr.WrappedError.(gomatrix.RespError); ok {
|
if respErr, ok := httpErr.WrappedError.(mautrix.RespError); ok {
|
||||||
view.Error(respErr.Err)
|
view.Error(respErr.Err)
|
||||||
} else {
|
} else {
|
||||||
view.Error(httpErr.Message)
|
view.Error(httpErr.Message)
|
||||||
|
@ -26,7 +26,7 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"maunium.net/go/gomatrix"
|
"maunium.net/go/mautrix"
|
||||||
"maunium.net/go/gomuks/config"
|
"maunium.net/go/gomuks/config"
|
||||||
"maunium.net/go/gomuks/debug"
|
"maunium.net/go/gomuks/debug"
|
||||||
"maunium.net/go/gomuks/interface"
|
"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)
|
eventID, err := view.matrix.SendMarkdownMessage(roomView.Room.ID, tempMessage.Type(), text)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tempMessage.SetState(ifc.MessageStateFailed)
|
tempMessage.SetState(ifc.MessageStateFailed)
|
||||||
if httpErr, ok := err.(gomatrix.HTTPError); ok {
|
if httpErr, ok := err.(mautrix.HTTPError); ok {
|
||||||
if respErr, ok := httpErr.WrappedError.(gomatrix.RespError); ok {
|
if respErr, ok := httpErr.WrappedError.(mautrix.RespError); ok {
|
||||||
// Show shorter version if available
|
// Show shorter version if available
|
||||||
err = respErr
|
err = respErr
|
||||||
}
|
}
|
||||||
@ -510,6 +510,6 @@ func (view *MainView) LoadHistory(room string) {
|
|||||||
view.parent.Render()
|
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)
|
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()
|
x, y, width, height := border.GetRect()
|
||||||
if width == 1 {
|
if width == 1 {
|
||||||
for borderY := y; borderY < y+height; borderY++ {
|
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 {
|
} else if height == 1 {
|
||||||
for borderX := x; borderX < x+width; borderX++ {
|
for borderX := x; borderX < x+width; borderX++ {
|
||||||
screen.SetContent(borderX, y, tview.GraphicsHoriBar, nil, background)
|
screen.SetContent(borderX, y, tview.Borders.Horizontal, nil, background)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
26
vendor/golang.org/x/image/bmp/reader.go
generated
vendored
26
vendor/golang.org/x/image/bmp/reader.go
generated
vendored
@ -139,18 +139,24 @@ func decodeConfig(r io.Reader) (config image.Config, bitsPerPixel int, topDown b
|
|||||||
const (
|
const (
|
||||||
fileHeaderLen = 14
|
fileHeaderLen = 14
|
||||||
infoHeaderLen = 40
|
infoHeaderLen = 40
|
||||||
|
v4InfoHeaderLen = 108
|
||||||
|
v5InfoHeaderLen = 124
|
||||||
)
|
)
|
||||||
var b [1024]byte
|
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
|
return image.Config{}, 0, false, err
|
||||||
}
|
}
|
||||||
if string(b[:2]) != "BM" {
|
if string(b[:2]) != "BM" {
|
||||||
return image.Config{}, 0, false, errors.New("bmp: invalid format")
|
return image.Config{}, 0, false, errors.New("bmp: invalid format")
|
||||||
}
|
}
|
||||||
offset := readUint32(b[10:14])
|
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
|
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])))
|
width := int(int32(readUint32(b[18:22])))
|
||||||
height := int(int32(readUint32(b[22:26])))
|
height := int(int32(readUint32(b[22:26])))
|
||||||
if height < 0 {
|
if height < 0 {
|
||||||
@ -159,14 +165,22 @@ func decodeConfig(r io.Reader) (config image.Config, bitsPerPixel int, topDown b
|
|||||||
if width < 0 || height < 0 {
|
if width < 0 || height < 0 {
|
||||||
return image.Config{}, 0, false, ErrUnsupported
|
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])
|
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 {
|
if planes != 1 || compression != 0 {
|
||||||
return image.Config{}, 0, false, ErrUnsupported
|
return image.Config{}, 0, false, ErrUnsupported
|
||||||
}
|
}
|
||||||
switch bpp {
|
switch bpp {
|
||||||
case 8:
|
case 8:
|
||||||
if offset != fileHeaderLen+infoHeaderLen+256*4 {
|
if offset != fileHeaderLen+infoLen+256*4 {
|
||||||
return image.Config{}, 0, false, ErrUnsupported
|
return image.Config{}, 0, false, ErrUnsupported
|
||||||
}
|
}
|
||||||
_, err = io.ReadFull(r, b[:256*4])
|
_, 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
|
return image.Config{ColorModel: pcm, Width: width, Height: height}, 8, topDown, nil
|
||||||
case 24:
|
case 24:
|
||||||
if offset != fileHeaderLen+infoHeaderLen {
|
if offset != fileHeaderLen+infoLen {
|
||||||
return image.Config{}, 0, false, ErrUnsupported
|
return image.Config{}, 0, false, ErrUnsupported
|
||||||
}
|
}
|
||||||
return image.Config{ColorModel: color.RGBAModel, Width: width, Height: height}, 24, topDown, nil
|
return image.Config{ColorModel: color.RGBAModel, Width: width, Height: height}, 24, topDown, nil
|
||||||
case 32:
|
case 32:
|
||||||
if offset != fileHeaderLen+infoHeaderLen {
|
if offset != fileHeaderLen+infoLen {
|
||||||
return image.Config{}, 0, false, ErrUnsupported
|
return image.Config{}, 0, false, ErrUnsupported
|
||||||
}
|
}
|
||||||
return image.Config{ColorModel: color.RGBAModel, Width: width, Height: height}, 32, topDown, nil
|
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
|
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
|
// stows away the data in the decoder. It returns the tag number of the
|
||||||
// entry and an error, if any.
|
// entry and an error, if any.
|
||||||
func (d *decoder) parseIFD(p []byte) (int, error) {
|
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
|
pred [4]uint8
|
||||||
// nzMask is a mask of 8 bits: 4 for the bottom or right 4x4 luma regions,
|
// 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
|
// 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
|
nzMask uint8
|
||||||
// nzY16 is a 0/1 value that is 1 if the macroblock used Y16 prediction and
|
// nzY16 is a 0/1 value that is 1 if the macroblock used Y16 prediction and
|
||||||
// had non-zero coefficients.
|
// had non-zero coefficients.
|
||||||
@ -274,7 +274,7 @@ func (d *Decoder) parseOtherPartitions() error {
|
|||||||
var partLens [maxNOP]int
|
var partLens [maxNOP]int
|
||||||
d.nOP = 1 << d.fp.readUint(uniformProb, 2)
|
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
|
// (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.
|
// lengths are stored as 24-bit uints, i.e. up to 16 MiB per partition.
|
||||||
n := 3 * (d.nOP - 1)
|
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
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// +build go1.6
|
|
||||||
|
|
||||||
package webp
|
package webp
|
||||||
|
|
||||||
import (
|
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 {
|
switch element.Namespace {
|
||||||
case "", "html":
|
case "", "html":
|
||||||
return isSpecialElementMap[element.Data]
|
return isSpecialElementMap[element.Data]
|
||||||
|
case "math":
|
||||||
|
switch element.Data {
|
||||||
|
case "mi", "mo", "mn", "ms", "mtext", "annotation-xml":
|
||||||
|
return true
|
||||||
|
}
|
||||||
case "svg":
|
case "svg":
|
||||||
return element.Data == "foreignObject"
|
switch element.Data {
|
||||||
|
case "foreignObject", "desc", "title":
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false
|
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:
|
case a.Table:
|
||||||
p.im = inTableIM
|
p.im = inTableIM
|
||||||
case a.Template:
|
case a.Template:
|
||||||
|
// TODO: remove this divergence from the HTML5 spec.
|
||||||
|
if n.Namespace != "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
p.im = p.templateStack.top()
|
p.im = p.templateStack.top()
|
||||||
case a.Head:
|
case a.Head:
|
||||||
// TODO: remove this divergence from the HTML5 spec.
|
// TODO: remove this divergence from the HTML5 spec.
|
||||||
@ -984,6 +988,14 @@ func inBodyIM(p *parser) bool {
|
|||||||
p.acknowledgeSelfClosingTag()
|
p.acknowledgeSelfClosingTag()
|
||||||
p.popUntil(buttonScope, a.P)
|
p.popUntil(buttonScope, a.P)
|
||||||
p.parseImpliedToken(StartTagToken, a.Form, a.Form.String())
|
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 != "" {
|
if action != "" {
|
||||||
p.form.Attr = []Attribute{{Key: "action", Val: action}}
|
p.form.Attr = []Attribute{{Key: "action", Val: action}}
|
||||||
}
|
}
|
||||||
@ -1252,12 +1264,6 @@ func (p *parser) inBodyEndTagFormatting(tagAtom a.Atom) {
|
|||||||
switch commonAncestor.DataAtom {
|
switch commonAncestor.DataAtom {
|
||||||
case a.Table, a.Tbody, a.Tfoot, a.Thead, a.Tr:
|
case a.Table, a.Tbody, a.Tfoot, a.Thead, a.Tr:
|
||||||
p.fosterParent(lastNode)
|
p.fosterParent(lastNode)
|
||||||
case a.Template:
|
|
||||||
// TODO: remove namespace checking
|
|
||||||
if commonAncestor.Namespace == "html" {
|
|
||||||
commonAncestor = commonAncestor.LastChild
|
|
||||||
}
|
|
||||||
fallthrough
|
|
||||||
default:
|
default:
|
||||||
commonAncestor.AppendChild(lastNode)
|
commonAncestor.AppendChild(lastNode)
|
||||||
}
|
}
|
||||||
@ -2209,6 +2215,15 @@ func (p *parser) parse() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse returns the parse tree for the HTML from the given Reader.
|
// 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.
|
// The input is assumed to be UTF-8 encoded.
|
||||||
func Parse(r io.Reader) (*Node, error) {
|
func Parse(r io.Reader) (*Node, error) {
|
||||||
p := &parser{
|
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
|
// 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
|
// found. If the fragment is the InnerHTML for an existing element, pass that
|
||||||
// element in context.
|
// element in context.
|
||||||
|
//
|
||||||
|
// It has the same intricacies as Parse.
|
||||||
func ParseFragment(r io.Reader, context *Node) ([]*Node, error) {
|
func ParseFragment(r io.Reader, context *Node) ([]*Node, error) {
|
||||||
contextTag := ""
|
contextTag := ""
|
||||||
if context != nil {
|
if context != nil {
|
||||||
|
25
vendor/gopkg.in/russross/blackfriday.v2/.travis.yml
generated
vendored
25
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
|
sudo: false
|
||||||
# open source projects. This file configures it to run unit tests for
|
|
||||||
# blackfriday.
|
|
||||||
|
|
||||||
language: go
|
language: go
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- 1.5
|
- "1.10.x"
|
||||||
- 1.6
|
- "1.11.x"
|
||||||
- 1.7
|
- tip
|
||||||
|
matrix:
|
||||||
|
fast_finish: true
|
||||||
|
allow_failures:
|
||||||
|
- go: tip
|
||||||
install:
|
install:
|
||||||
- go get -d -t -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).
|
||||||
- go build -v ./...
|
|
||||||
|
|
||||||
script:
|
script:
|
||||||
|
- go get -t -v ./...
|
||||||
|
- diff -u <(echo -n) <(gofmt -d -s .)
|
||||||
|
- go tool vet .
|
||||||
- go test -v ./...
|
- go test -v ./...
|
||||||
- go test -run=^$ -bench=BenchmarkReference -benchmem
|
|
||||||
|
16
vendor/gopkg.in/russross/blackfriday.v2/README.md
generated
vendored
16
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
|
Currently maintained and recommended version of Blackfriday is `v2`. It's being
|
||||||
developed on its own branch: https://github.com/russross/blackfriday/v2. You
|
developed on its own branch: https://github.com/russross/blackfriday/tree/v2 and the
|
||||||
should install and import it via [gopkg.in][6] at
|
documentation is available at
|
||||||
`gopkg.in/russross/blackfriday.v2`.
|
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:
|
Version 2 offers a number of improvements over v1:
|
||||||
|
|
||||||
@ -255,9 +261,11 @@ are a few of note:
|
|||||||
* [markdownfmt](https://github.com/shurcooL/markdownfmt): like gofmt,
|
* [markdownfmt](https://github.com/shurcooL/markdownfmt): like gofmt,
|
||||||
but for markdown.
|
but for markdown.
|
||||||
|
|
||||||
* [LaTeX output](https://bitbucket.org/ambrevar/blackfriday-latex):
|
* [LaTeX output](https://github.com/Ambrevar/Blackfriday-LaTeX):
|
||||||
renders output as 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
|
Todo
|
||||||
----
|
----
|
||||||
|
105
vendor/gopkg.in/russross/blackfriday.v2/block.go
generated
vendored
105
vendor/gopkg.in/russross/blackfriday.v2/block.go
generated
vendored
@ -17,6 +17,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"html"
|
"html"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/shurcooL/sanitized_anchor_name"
|
"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,
|
// 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.
|
// 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.
|
// If info 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) {
|
func isFenceLine(data []byte, info *string, oldmarker string) (end int, marker string) {
|
||||||
i, size := 0, 0
|
i, size := 0, 0
|
||||||
|
|
||||||
// skip up to three spaces
|
// 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
|
// 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.
|
// into one, always get the info string, and discard it if the caller doesn't care.
|
||||||
if syntax != nil {
|
if info != nil {
|
||||||
syn := 0
|
infoLength := 0
|
||||||
i = skipChar(data, i, ' ')
|
i = skipChar(data, i, ' ')
|
||||||
|
|
||||||
if i >= len(data) {
|
if i >= len(data) {
|
||||||
@ -617,14 +618,14 @@ func isFenceLine(data []byte, syntax *string, oldmarker string) (end int, marker
|
|||||||
return 0, ""
|
return 0, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
syntaxStart := i
|
infoStart := i
|
||||||
|
|
||||||
if data[i] == '{' {
|
if data[i] == '{' {
|
||||||
i++
|
i++
|
||||||
syntaxStart++
|
infoStart++
|
||||||
|
|
||||||
for i < len(data) && data[i] != '}' && data[i] != '\n' {
|
for i < len(data) && data[i] != '}' && data[i] != '\n' {
|
||||||
syn++
|
infoLength++
|
||||||
i++
|
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
|
// strip all whitespace at the beginning and the end
|
||||||
// of the {} block
|
// of the {} block
|
||||||
for syn > 0 && isspace(data[syntaxStart]) {
|
for infoLength > 0 && isspace(data[infoStart]) {
|
||||||
syntaxStart++
|
infoStart++
|
||||||
syn--
|
infoLength--
|
||||||
}
|
}
|
||||||
|
|
||||||
for syn > 0 && isspace(data[syntaxStart+syn-1]) {
|
for infoLength > 0 && isspace(data[infoStart+infoLength-1]) {
|
||||||
syn--
|
infoLength--
|
||||||
}
|
}
|
||||||
|
|
||||||
i++
|
i++
|
||||||
} else {
|
|
||||||
for i < len(data) && !isspace(data[i]) {
|
|
||||||
syn++
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*syntax = string(data[syntaxStart : syntaxStart+syn])
|
|
||||||
}
|
|
||||||
|
|
||||||
i = skipChar(data, i, ' ')
|
i = skipChar(data, i, ' ')
|
||||||
if i >= len(data) || data[i] != '\n' {
|
} else {
|
||||||
|
for i < len(data) && !isverticalspace(data[i]) {
|
||||||
|
infoLength++
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*info = strings.TrimSpace(string(data[infoStart : infoStart+infoLength]))
|
||||||
|
}
|
||||||
|
|
||||||
if i == len(data) {
|
if i == len(data) {
|
||||||
return i, marker
|
return i, marker
|
||||||
}
|
}
|
||||||
|
if i > len(data) || data[i] != '\n' {
|
||||||
return 0, ""
|
return 0, ""
|
||||||
}
|
}
|
||||||
return i + 1, marker // Take newline into account.
|
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.
|
// 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.
|
// If doRender is true, a final newline is mandatory to recognize the fenced code block.
|
||||||
func (p *Markdown) fencedCodeBlock(data []byte, doRender bool) int {
|
func (p *Markdown) fencedCodeBlock(data []byte, doRender bool) int {
|
||||||
var syntax string
|
var info string
|
||||||
beg, marker := isFenceLine(data, &syntax, "")
|
beg, marker := isFenceLine(data, &info, "")
|
||||||
if beg == 0 || beg >= len(data) {
|
if beg == 0 || beg >= len(data) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
var work bytes.Buffer
|
var work bytes.Buffer
|
||||||
work.Write([]byte(syntax))
|
work.Write([]byte(info))
|
||||||
work.WriteByte('\n')
|
work.WriteByte('\n')
|
||||||
|
|
||||||
for {
|
for {
|
||||||
@ -1148,6 +1148,18 @@ func (p *Markdown) list(data []byte, flags ListType) int {
|
|||||||
return i
|
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
|
// Returns true if block ends with a blank line, descending if needed
|
||||||
// into lists and sublists.
|
// into lists and sublists.
|
||||||
func endsWithBlankLine(block *Node) bool {
|
func endsWithBlankLine(block *Node) bool {
|
||||||
@ -1246,6 +1258,7 @@ func (p *Markdown) listItem(data []byte, flags *ListType) int {
|
|||||||
// process the following lines
|
// process the following lines
|
||||||
containsBlankLine := false
|
containsBlankLine := false
|
||||||
sublist := 0
|
sublist := 0
|
||||||
|
codeBlockMarker := ""
|
||||||
|
|
||||||
gatherlines:
|
gatherlines:
|
||||||
for line < len(data) {
|
for line < len(data) {
|
||||||
@ -1279,6 +1292,27 @@ gatherlines:
|
|||||||
|
|
||||||
chunk := data[line+indentIndex : i]
|
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
|
// evaluate how this line fits in
|
||||||
switch {
|
switch {
|
||||||
// is this a nested list item?
|
// is this a nested list item?
|
||||||
@ -1286,16 +1320,23 @@ gatherlines:
|
|||||||
p.oliPrefix(chunk) > 0 ||
|
p.oliPrefix(chunk) > 0 ||
|
||||||
p.dliPrefix(chunk) > 0:
|
p.dliPrefix(chunk) > 0:
|
||||||
|
|
||||||
if containsBlankLine {
|
// 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
|
*flags |= ListItemContainsBlock
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
break gatherlines
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if containsBlankLine {
|
||||||
|
*flags |= ListItemContainsBlock
|
||||||
|
}
|
||||||
|
|
||||||
// is this the first item in the nested list?
|
// is this the first item in the nested list?
|
||||||
if sublist == 0 {
|
if sublist == 0 {
|
||||||
sublist = raw.Len()
|
sublist = raw.Len()
|
||||||
|
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
|
Safelink // Only link to trusted protocols
|
||||||
NofollowLinks // Only link with rel="nofollow"
|
NofollowLinks // Only link with rel="nofollow"
|
||||||
NoreferrerLinks // Only link with rel="noreferrer"
|
NoreferrerLinks // Only link with rel="noreferrer"
|
||||||
|
NoopenerLinks // Only link with rel="noopener"
|
||||||
HrefTargetBlank // Add a blank target
|
HrefTargetBlank // Add a blank target
|
||||||
CompletePage // Generate a complete HTML page
|
CompletePage // Generate a complete HTML page
|
||||||
UseXHTML // Generate XHTML output instead of HTML
|
UseXHTML // Generate XHTML output instead of HTML
|
||||||
@ -87,6 +88,10 @@ type HTMLRendererParameters struct {
|
|||||||
HeadingIDPrefix string
|
HeadingIDPrefix string
|
||||||
// If set, add this text to the back of each Heading ID, to ensure uniqueness.
|
// If set, add this text to the back of each Heading ID, to ensure uniqueness.
|
||||||
HeadingIDSuffix string
|
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)
|
Title string // Document title (used if CompletePage is set)
|
||||||
CSS string // Optional CSS file URL (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 {
|
if flags&NoreferrerLinks != 0 {
|
||||||
val = append(val, "noreferrer")
|
val = append(val, "noreferrer")
|
||||||
}
|
}
|
||||||
|
if flags&NoopenerLinks != 0 {
|
||||||
|
val = append(val, "noopener")
|
||||||
|
}
|
||||||
if flags&HrefTargetBlank != 0 {
|
if flags&HrefTargetBlank != 0 {
|
||||||
attrs = append(attrs, "target=\"_blank\"")
|
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 {
|
func footnoteRef(prefix string, node *Node) []byte {
|
||||||
urlFrag := prefix + string(slugify(node.Destination))
|
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))
|
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) {
|
func headingTagsFromLevel(level int) ([]byte, []byte) {
|
||||||
switch level {
|
if level <= 1 {
|
||||||
case 1:
|
|
||||||
return h1Tag, h1CloseTag
|
return h1Tag, h1CloseTag
|
||||||
|
}
|
||||||
|
switch level {
|
||||||
case 2:
|
case 2:
|
||||||
return h2Tag, h2CloseTag
|
return h2Tag, h2CloseTag
|
||||||
case 3:
|
case 3:
|
||||||
@ -471,9 +480,8 @@ func headingTagsFromLevel(level int) ([]byte, []byte) {
|
|||||||
return h4Tag, h4CloseTag
|
return h4Tag, h4CloseTag
|
||||||
case 5:
|
case 5:
|
||||||
return h5Tag, h5CloseTag
|
return h5Tag, h5CloseTag
|
||||||
default:
|
|
||||||
return h6Tag, h6CloseTag
|
|
||||||
}
|
}
|
||||||
|
return h6Tag, h6CloseTag
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *HTMLRenderer) outHRTag(w io.Writer) {
|
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.out(w, node.Literal)
|
||||||
r.cr(w)
|
r.cr(w)
|
||||||
case Heading:
|
case Heading:
|
||||||
openTag, closeTag := headingTagsFromLevel(node.Level)
|
headingLevel := r.HTMLRendererParameters.HeadingLevelOffset + node.Level
|
||||||
|
openTag, closeTag := headingTagsFromLevel(headingLevel)
|
||||||
if entering {
|
if entering {
|
||||||
if node.IsTitleblock {
|
if node.IsTitleblock {
|
||||||
attrs = append(attrs, `class="title"`)
|
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+&@#\/%?=~_|!:,.;\(\)]+`
|
urlRe = `((https?|ftp):\/\/|\/)[-A-Za-z0-9+&@#\/%?=~_|!:,.;\(\)]+`
|
||||||
anchorRe = regexp.MustCompile(`^(<a\shref="` + urlRe + `"(\stitle="[^"<>]+")?\s?>` + urlRe + `<\/a>)`)
|
anchorRe = regexp.MustCompile(`^(<a\shref="` + urlRe + `"(\stitle="[^"<>]+")?\s?>` + urlRe + `<\/a>)`)
|
||||||
|
|
||||||
// TODO: improve this regexp to catch all possible entities:
|
// https://www.w3.org/TR/html5/syntax.html#character-references
|
||||||
htmlEntityRe = regexp.MustCompile(`&[a-z]{2,5};`)
|
// 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
|
// 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.
|
// 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.
|
// Any of these can be included in markdown text without special escaping.
|
||||||
var blockTags = map[string]struct{}{
|
var blockTags = map[string]struct{}{
|
||||||
"blockquote": struct{}{},
|
"blockquote": {},
|
||||||
"del": struct{}{},
|
"del": {},
|
||||||
"div": struct{}{},
|
"div": {},
|
||||||
"dl": struct{}{},
|
"dl": {},
|
||||||
"fieldset": struct{}{},
|
"fieldset": {},
|
||||||
"form": struct{}{},
|
"form": {},
|
||||||
"h1": struct{}{},
|
"h1": {},
|
||||||
"h2": struct{}{},
|
"h2": {},
|
||||||
"h3": struct{}{},
|
"h3": {},
|
||||||
"h4": struct{}{},
|
"h4": {},
|
||||||
"h5": struct{}{},
|
"h5": {},
|
||||||
"h6": struct{}{},
|
"h6": {},
|
||||||
"iframe": struct{}{},
|
"iframe": {},
|
||||||
"ins": struct{}{},
|
"ins": {},
|
||||||
"math": struct{}{},
|
"math": {},
|
||||||
"noscript": struct{}{},
|
"noscript": {},
|
||||||
"ol": struct{}{},
|
"ol": {},
|
||||||
"pre": struct{}{},
|
"pre": {},
|
||||||
"p": struct{}{},
|
"p": {},
|
||||||
"script": struct{}{},
|
"script": {},
|
||||||
"style": struct{}{},
|
"style": {},
|
||||||
"table": struct{}{},
|
"table": {},
|
||||||
"ul": struct{}{},
|
"ul": {},
|
||||||
|
|
||||||
// HTML5
|
// HTML5
|
||||||
"address": struct{}{},
|
"address": {},
|
||||||
"article": struct{}{},
|
"article": {},
|
||||||
"aside": struct{}{},
|
"aside": {},
|
||||||
"canvas": struct{}{},
|
"canvas": {},
|
||||||
"figcaption": struct{}{},
|
"figcaption": {},
|
||||||
"figure": struct{}{},
|
"figure": {},
|
||||||
"footer": struct{}{},
|
"footer": {},
|
||||||
"header": struct{}{},
|
"header": {},
|
||||||
"hgroup": struct{}{},
|
"hgroup": {},
|
||||||
"main": struct{}{},
|
"main": {},
|
||||||
"nav": struct{}{},
|
"nav": {},
|
||||||
"output": struct{}{},
|
"output": {},
|
||||||
"progress": struct{}{},
|
"progress": {},
|
||||||
"section": struct{}{},
|
"section": {},
|
||||||
"video": struct{}{},
|
"video": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Renderer is the rendering interface. This is mostly of interest if you are
|
// 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.
|
// [^note]: This is the explanation.
|
||||||
//
|
//
|
||||||
// Footnotes should be placed at the end of the document in an ordered list.
|
// 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
|
// reference holds all information necessary for a reference-style links or
|
||||||
// footnotes.
|
// footnotes.
|
||||||
@ -813,7 +813,17 @@ func ispunct(c byte) bool {
|
|||||||
|
|
||||||
// Test if a character is a whitespace character.
|
// Test if a character is a whitespace character.
|
||||||
func isspace(c byte) bool {
|
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.
|
// 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
|
// Specification can be found at http://matrix.org/docs/spec/client_server/r0.4.0.html
|
||||||
package gomatrix
|
package mautrix
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -10,7 +10,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"maunium.net/go/maulogger"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
@ -20,6 +19,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Logger interface {
|
||||||
|
Debugfln(message string, args ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
// Client represents a Matrix client.
|
// Client represents a Matrix client.
|
||||||
type Client struct {
|
type Client struct {
|
||||||
HomeserverURL *url.URL // The base homeserver URL
|
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.
|
Client *http.Client // The underlying HTTP client which will be used to make HTTP requests.
|
||||||
Syncer Syncer // The thing which can process /sync responses
|
Syncer Syncer // The thing which can process /sync responses
|
||||||
Store Storer // The thing which can store rooms/tokens/ids
|
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,
|
// 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.
|
// no user_id parameter will be sent.
|
||||||
@ -132,7 +135,6 @@ func (cli *Client) Sync() error {
|
|||||||
filterID = resFilter.FilterID
|
filterID = resFilter.FilterID
|
||||||
cli.Store.SaveFilterID(cli.UserID, filterID)
|
cli.Store.SaveFilterID(cli.UserID, filterID)
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
resSync, err := cli.SyncRequest(30000, nextBatch, filterID, false, "")
|
resSync, err := cli.SyncRequest(30000, nextBatch, filterID, false, "")
|
||||||
if err != nil {
|
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.
|
// 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",
|
// Username: "alice",
|
||||||
// Password: "wonderland",
|
// 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
|
// 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",
|
// Preset: "public_chat",
|
||||||
// })
|
// })
|
||||||
// fmt.Println("Room:", resp.RoomID)
|
// fmt.Println("Room:", resp.RoomID)
|
43
vendor/maunium.net/go/gomatrix/events.go → vendor/maunium.net/go/mautrix/events.go
generated
vendored
43
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 (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@ -101,7 +102,7 @@ var (
|
|||||||
// Ephemeral events
|
// Ephemeral events
|
||||||
var (
|
var (
|
||||||
EphemeralEventReceipt = EventType{"m.receipt", EphemeralEventType}
|
EphemeralEventReceipt = EventType{"m.receipt", EphemeralEventType}
|
||||||
EphemeralEventTyping = EventType{"m.receipt", EphemeralEventType}
|
EphemeralEventTyping = EventType{"m.typing", EphemeralEventType}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Account data events
|
// Account data events
|
||||||
@ -165,6 +166,17 @@ type Unsigned struct {
|
|||||||
PrevSender string `json:"prev_sender,omitempty"`
|
PrevSender string `json:"prev_sender,omitempty"`
|
||||||
ReplacesState string `json:"replaces_state,omitempty"`
|
ReplacesState string `json:"replaces_state,omitempty"`
|
||||||
Age int64 `json:"age,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 {
|
type Content struct {
|
||||||
@ -183,6 +195,7 @@ type Content struct {
|
|||||||
Membership Membership `json:"membership,omitempty"`
|
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
|
PowerLevels
|
||||||
Member
|
Member
|
||||||
@ -197,14 +210,30 @@ type Content struct {
|
|||||||
|
|
||||||
type serializableContent Content
|
type serializableContent Content
|
||||||
|
|
||||||
|
var DisableFancyEventParsing = false
|
||||||
|
|
||||||
func (content *Content) UnmarshalJSON(data []byte) error {
|
func (content *Content) UnmarshalJSON(data []byte) error {
|
||||||
content.VeryRaw = data
|
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 err
|
||||||
}
|
}
|
||||||
return json.Unmarshal(data, (*serializableContent)(content))
|
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) {
|
func (content *Content) UnmarshalPowerLevels() (pl PowerLevels, err error) {
|
||||||
err = json.Unmarshal(content.VeryRaw, &pl)
|
err = json.Unmarshal(content.VeryRaw, &pl)
|
||||||
return
|
return
|
||||||
@ -228,7 +257,7 @@ func (content *Content) GetInfo() *FileInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Tags map[string]struct {
|
type Tags map[string]struct {
|
||||||
Order string `json:"order"`
|
Order json.Number `json:"order"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RoomName struct {
|
type RoomName struct {
|
||||||
@ -411,3 +440,9 @@ type InReplyTo struct {
|
|||||||
// Not required, just for future-proofing
|
// Not required, just for future-proofing
|
||||||
RoomID string `json:"room_id,omitempty"`
|
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
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package gomatrix
|
package mautrix
|
||||||
|
|
||||||
import "errors"
|
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 (
|
import (
|
||||||
"fmt"
|
"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
|
// 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 {
|
type ReqRegister struct {
|
@ -1,4 +1,4 @@
|
|||||||
package gomatrix
|
package mautrix
|
||||||
|
|
||||||
// RespError is the standard JSON error response from Homeservers. It also implements the Golang "error" interface.
|
// 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
|
// See http://matrix.org/docs/spec/client_server/r0.2.0.html#api-standards
|
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.
|
// Room represents a single Matrix room.
|
||||||
type Room struct {
|
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.
|
// 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 (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
2
vendor/maunium.net/go/gomatrix/userids.go → vendor/maunium.net/go/mautrix/userids.go
generated
vendored
2
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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
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
|
i := 0
|
||||||
for i < len(c.currComb) {
|
for i < len(c.currComb) {
|
||||||
r := c.currComb[i]
|
r := c.currComb[i]
|
||||||
|
if r == '\u200d' {
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
}
|
||||||
if runewidth.RuneWidth(r) != 0 {
|
if runewidth.RuneWidth(r) != 0 {
|
||||||
// not a combining character, yank it
|
// not a combining character, yank it
|
||||||
c.currComb = append(c.currComb[:i-1], c.currComb[i+1:]...)
|
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
|
// Fill fills the entire cell buffer array with the specified character
|
||||||
// and style. Normally choose ' ' to clear the screen. This API doesn't
|
// 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) {
|
func (cb *CellBuffer) Fill(r rune, style Style) {
|
||||||
for i := range cb.cells {
|
for i := range cb.cells {
|
||||||
c := &cb.cells[i]
|
c := &cb.cells[i]
|
||||||
c.currMain = r
|
c.currMain = r
|
||||||
c.currComb = nil
|
c.currComb = nil
|
||||||
c.currStyle = style
|
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
|
in syscall.Handle
|
||||||
out syscall.Handle
|
out syscall.Handle
|
||||||
cancelflag syscall.Handle
|
cancelflag syscall.Handle
|
||||||
title syscall.Handle
|
|
||||||
scandone chan struct{}
|
scandone chan struct{}
|
||||||
evch chan Event
|
evch chan Event
|
||||||
quit chan struct{}
|
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",
|
||||||
|
})
|
||||||
|
}
|
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
|
MIT License
|
||||||
|
|
||||||
Copyright (c) [year] [fullname]
|
Copyright (c) 2018 Oliver Kuederle
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
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__)
|
- __Input forms__ (include __input/password fields__, __drop-down selections__, __checkboxes__, and __buttons__)
|
||||||
- Navigable multi-color __text views__
|
- Navigable multi-color __text views__
|
||||||
- Sophisticated navigable __table views__
|
- Sophisticated navigable __table views__
|
||||||
|
- Flexible __tree views__
|
||||||
- Selectable __lists__
|
- Selectable __lists__
|
||||||
- __Grid__, __Flexbox__ and __page layouts__
|
- __Grid__, __Flexbox__ and __page layouts__
|
||||||
- Modal __message windows__
|
- 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.)
|
(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)
|
- v0.14 (2018-04-13)
|
||||||
- Added an `Escape()` function which keep strings like color or region tags from being recognized as such.
|
- 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)
|
- v0.13 (2018-04-01)
|
||||||
- Added background colors and text attributes to color tags.
|
- Added background colors and text attributes to color tags.
|
||||||
- v0.12 (2018-03-13)
|
- 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"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The states of the ANSII escape code parser.
|
// The states of the ANSI escape code parser.
|
||||||
const (
|
const (
|
||||||
ansiiText = iota
|
ansiText = iota
|
||||||
ansiiEscape
|
ansiEscape
|
||||||
ansiiSubstring
|
ansiSubstring
|
||||||
ansiiControlSequence
|
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.
|
// tags.
|
||||||
type ansii struct {
|
type ansi struct {
|
||||||
io.Writer
|
io.Writer
|
||||||
|
|
||||||
// Reusable buffers.
|
// Reusable buffers.
|
||||||
buffer *bytes.Buffer // The entire output text of one Write().
|
buffer *bytes.Buffer // The entire output text of one Write().
|
||||||
csiParameter, csiIntermediate *bytes.Buffer // Partial CSI strings.
|
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
|
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
|
// 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
|
// and are simply removed. The translated text is written to the provided
|
||||||
// writer.
|
// writer.
|
||||||
func ANSIIWriter(writer io.Writer) io.Writer {
|
func ANSIWriter(writer io.Writer) io.Writer {
|
||||||
return &ansii{
|
return &ansi{
|
||||||
Writer: writer,
|
Writer: writer,
|
||||||
buffer: new(bytes.Buffer),
|
buffer: new(bytes.Buffer),
|
||||||
csiParameter: new(bytes.Buffer),
|
csiParameter: new(bytes.Buffer),
|
||||||
csiIntermediate: 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.
|
// 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() {
|
defer func() {
|
||||||
a.buffer.Reset()
|
a.buffer.Reset()
|
||||||
}()
|
}()
|
||||||
@ -54,23 +54,23 @@ func (a *ansii) Write(text []byte) (int, error) {
|
|||||||
switch a.state {
|
switch a.state {
|
||||||
|
|
||||||
// We just entered an escape sequence.
|
// We just entered an escape sequence.
|
||||||
case ansiiEscape:
|
case ansiEscape:
|
||||||
switch r {
|
switch r {
|
||||||
case '[': // Control Sequence Introducer.
|
case '[': // Control Sequence Introducer.
|
||||||
a.csiParameter.Reset()
|
a.csiParameter.Reset()
|
||||||
a.csiIntermediate.Reset()
|
a.csiIntermediate.Reset()
|
||||||
a.state = ansiiControlSequence
|
a.state = ansiControlSequence
|
||||||
case 'c': // Reset.
|
case 'c': // Reset.
|
||||||
fmt.Fprint(a.buffer, "[-:-:-]")
|
fmt.Fprint(a.buffer, "[-:-:-]")
|
||||||
a.state = ansiiText
|
a.state = ansiText
|
||||||
case 'P', ']', 'X', '^', '_': // Substrings and commands.
|
case 'P', ']', 'X', '^', '_': // Substrings and commands.
|
||||||
a.state = ansiiSubstring
|
a.state = ansiSubstring
|
||||||
default: // Ignore.
|
default: // Ignore.
|
||||||
a.state = ansiiText
|
a.state = ansiText
|
||||||
}
|
}
|
||||||
|
|
||||||
// CSI Sequences.
|
// CSI Sequences.
|
||||||
case ansiiControlSequence:
|
case ansiControlSequence:
|
||||||
switch {
|
switch {
|
||||||
case r >= 0x30 && r <= 0x3f: // Parameter bytes.
|
case r >= 0x30 && r <= 0x3f: // Parameter bytes.
|
||||||
if _, err := a.csiParameter.WriteRune(r); err != nil {
|
if _, err := a.csiParameter.WriteRune(r); err != nil {
|
||||||
@ -166,16 +166,16 @@ func (a *ansii) Write(text []byte) (int, error) {
|
|||||||
red := (colorNumber - 16) / 36
|
red := (colorNumber - 16) / 36
|
||||||
green := ((colorNumber - 16) / 6) % 6
|
green := ((colorNumber - 16) / 6) % 6
|
||||||
blue := (colorNumber - 16) % 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 {
|
} else if colorNumber <= 255 {
|
||||||
grey := 255 * (colorNumber - 232) / 23
|
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.
|
} else if fields[index+1] == "2" && len(fields) > index+4 { // 24-bit colors.
|
||||||
red, _ := strconv.Atoi(fields[index+2])
|
red, _ := strconv.Atoi(fields[index+2])
|
||||||
green, _ := strconv.Atoi(fields[index+3])
|
green, _ := strconv.Atoi(fields[index+3])
|
||||||
blue, _ := strconv.Atoi(fields[index+4])
|
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 {
|
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)
|
fmt.Fprintf(a.buffer, "[%s:%s%s]", foreground, background, attributes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
a.state = ansiiText
|
a.state = ansiText
|
||||||
default: // Undefined byte.
|
default: // Undefined byte.
|
||||||
a.state = ansiiText // Abort CSI.
|
a.state = ansiText // Abort CSI.
|
||||||
}
|
}
|
||||||
|
|
||||||
// We just entered a substring/command sequence.
|
// We just entered a substring/command sequence.
|
||||||
case ansiiSubstring:
|
case ansiSubstring:
|
||||||
if r == 27 { // Most likely the end of the substring.
|
if r == 27 { // Most likely the end of the substring.
|
||||||
a.state = ansiiEscape
|
a.state = ansiEscape
|
||||||
} // Ignore all other characters.
|
} // Ignore all other characters.
|
||||||
|
|
||||||
// "ansiiText" and all others.
|
// "ansiText" and all others.
|
||||||
default:
|
default:
|
||||||
if r == 27 {
|
if r == 27 {
|
||||||
// This is the start of an escape sequence.
|
// This is the start of an escape sequence.
|
||||||
a.state = ansiiEscape
|
a.state = ansiEscape
|
||||||
} else {
|
} else {
|
||||||
// Just a regular rune. Send to buffer.
|
// Just a regular rune. Send to buffer.
|
||||||
if _, err := a.buffer.WriteRune(r); err != nil {
|
if _, err := a.buffer.WriteRune(r); err != nil {
|
||||||
@ -227,11 +227,11 @@ func (a *ansii) Write(text []byte) (int, error) {
|
|||||||
return len(text), nil
|
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.
|
// with tview's color tags and returns the resulting string.
|
||||||
func TranslateANSII(text string) string {
|
func TranslateANSI(text string) string {
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
writer := ANSIIWriter(&buffer)
|
writer := ANSIWriter(&buffer)
|
||||||
writer.Write([]byte(text))
|
writer.Write([]byte(text))
|
||||||
return buffer.String()
|
return buffer.String()
|
||||||
}
|
}
|
264
vendor/maunium.net/go/tview/application.go
generated
vendored
264
vendor/maunium.net/go/tview/application.go
generated
vendored
@ -1,24 +1,36 @@
|
|||||||
package tview
|
package tview
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"maunium.net/go/tcell"
|
"maunium.net/go/tcell"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// The size of the event/update/redraw channels.
|
||||||
|
const queueSize = 100
|
||||||
|
|
||||||
// Application represents the top node of an application.
|
// Application represents the top node of an application.
|
||||||
//
|
//
|
||||||
// It is not strictly required to use this class as none of the other classes
|
// 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
|
// depend on it. However, it provides useful tools to set up an application and
|
||||||
// plays nicely with all widgets.
|
// 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 {
|
type Application struct {
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
|
|
||||||
// The application's screen.
|
// The application's screen.
|
||||||
screen tcell.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.
|
// The primitive which currently has the keyboard focus.
|
||||||
focus Primitive
|
focus Primitive
|
||||||
|
|
||||||
@ -48,13 +60,23 @@ type Application struct {
|
|||||||
// was drawn.
|
// was drawn.
|
||||||
afterDraw func(screen tcell.Screen)
|
afterDraw func(screen tcell.Screen)
|
||||||
|
|
||||||
// If this value is true, the application has entered suspended mode.
|
// Used to send screen events from separate goroutine to main event loop
|
||||||
suspended bool
|
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.
|
// NewApplication creates and returns a new application.
|
||||||
func NewApplication() *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
|
// SetInputCapture sets a function which captures all key events before they are
|
||||||
@ -97,79 +119,150 @@ func (a *Application) GetScreen() tcell.Screen {
|
|||||||
return a.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
|
// Run starts the application and thus the event loop. This function returns
|
||||||
// when Stop() was called.
|
// when Stop() was called.
|
||||||
func (a *Application) Run() error {
|
func (a *Application) Run() error {
|
||||||
var err error
|
var err error
|
||||||
a.Lock()
|
a.Lock()
|
||||||
|
|
||||||
// Make a screen.
|
// Make a screen if there is none yet.
|
||||||
|
if a.screen == nil {
|
||||||
a.screen, err = tcell.NewScreen()
|
a.screen, err = tcell.NewScreen()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.Unlock()
|
a.Unlock()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if err = a.screen.Init(); err != nil {
|
if err = a.screen.Init(); err != nil {
|
||||||
a.Unlock()
|
a.Unlock()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
a.screen.EnableMouse()
|
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.
|
// Draw the screen for the first time.
|
||||||
a.Unlock()
|
a.Unlock()
|
||||||
a.Draw()
|
a.draw()
|
||||||
|
|
||||||
|
// Separate loop to wait for screen events.
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
a.suspendToken <- struct{}{} // We need this to get started.
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for range a.suspendToken {
|
||||||
|
for {
|
||||||
|
a.RLock()
|
||||||
|
screen := a.screen
|
||||||
|
a.RUnlock()
|
||||||
|
if screen == nil {
|
||||||
|
// We have no screen. We might need to stop.
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
// Start event loop.
|
||||||
|
EventLoop:
|
||||||
for {
|
for {
|
||||||
a.Lock()
|
select {
|
||||||
screen := a.screen
|
case event := <-a.events:
|
||||||
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 {
|
if event == nil {
|
||||||
a.Lock()
|
break EventLoop
|
||||||
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) {
|
switch event := event.(type) {
|
||||||
case *tcell.EventKey:
|
case *tcell.EventKey:
|
||||||
a.RLock()
|
a.RLock()
|
||||||
p := a.focus
|
p := a.focus
|
||||||
|
inputCapture := a.inputCapture
|
||||||
a.RUnlock()
|
a.RUnlock()
|
||||||
|
|
||||||
// Intercept keys.
|
// Intercept keys.
|
||||||
if a.inputCapture != nil {
|
if inputCapture != nil {
|
||||||
event = a.inputCapture(event)
|
event = inputCapture(event)
|
||||||
if event == nil {
|
if event == nil {
|
||||||
break // Don't forward event.
|
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.
|
// Pass other key events to the currently focused primitive.
|
||||||
if p != nil {
|
if p != nil {
|
||||||
if handler := p.InputHandler(); handler != nil {
|
if handler := p.InputHandler(); handler != nil {
|
||||||
handler(event, func(p Primitive) {
|
handler(event, func(p Primitive) {
|
||||||
a.SetFocus(p)
|
a.SetFocus(p)
|
||||||
})
|
})
|
||||||
a.Draw()
|
a.draw()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case *tcell.EventMouse:
|
case *tcell.EventMouse:
|
||||||
a.RLock()
|
a.RLock()
|
||||||
p := a.focus
|
p := a.focus
|
||||||
@ -211,26 +304,37 @@ func (a *Application) Run() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case *tcell.EventResize:
|
case *tcell.EventResize:
|
||||||
a.Lock()
|
a.RLock()
|
||||||
screen := a.screen
|
screen := a.screen
|
||||||
a.Unlock()
|
a.RUnlock()
|
||||||
screen.Clear()
|
screen.Clear()
|
||||||
a.Draw()
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop stops the application, causing Run() to return.
|
// Stop stops the application, causing Run() to return.
|
||||||
func (a *Application) Stop() {
|
func (a *Application) Stop() {
|
||||||
a.RLock()
|
a.Lock()
|
||||||
defer a.RUnlock()
|
defer a.Unlock()
|
||||||
if a.screen == nil {
|
screen := a.screen
|
||||||
|
if screen == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
a.screen.Fini()
|
|
||||||
a.screen = nil
|
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
|
// 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 {
|
func (a *Application) Suspend(f func()) bool {
|
||||||
a.Lock()
|
a.Lock()
|
||||||
|
|
||||||
if a.suspended || a.screen == nil {
|
screen := a.screen
|
||||||
// Application is already suspended.
|
if screen == nil {
|
||||||
|
// Screen has not yet been initialized.
|
||||||
a.Unlock()
|
a.Unlock()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enter suspended mode.
|
// Enter suspended mode. Make a new screen here already so our event loop can
|
||||||
a.suspended = true
|
// continue.
|
||||||
|
a.screen = nil
|
||||||
|
a.running = false
|
||||||
|
screen.Fini()
|
||||||
a.Unlock()
|
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.
|
// Wait for "f" to return.
|
||||||
f()
|
f()
|
||||||
|
|
||||||
// Make a new screen and redraw.
|
// Initialize our new screen and draw the contents.
|
||||||
a.Lock()
|
a.Lock()
|
||||||
var err error
|
var err error
|
||||||
a.screen, err = tcell.NewScreen()
|
a.screen, err = tcell.NewScreen()
|
||||||
@ -278,23 +377,36 @@ func (a *Application) Suspend(f func()) bool {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
a.screen.EnableMouse()
|
a.screen.EnableMouse()
|
||||||
|
a.running = true
|
||||||
a.Unlock()
|
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.
|
// Continue application loop.
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw refreshes the screen. It calls the Draw() function of the application's
|
// Draw refreshes the screen (during the next update cycle). It calls the Draw()
|
||||||
// root primitive and then syncs the screen buffer.
|
// function of the application's root primitive and then syncs the screen
|
||||||
|
// buffer.
|
||||||
func (a *Application) Draw() *Application {
|
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
|
screen := a.screen
|
||||||
root := a.root
|
root := a.root
|
||||||
fullscreen := a.rootFullscreen
|
fullscreen := a.rootFullscreen
|
||||||
before := a.beforeDraw
|
before := a.beforeDraw
|
||||||
after := a.afterDraw
|
after := a.afterDraw
|
||||||
a.RUnlock()
|
|
||||||
|
|
||||||
// Maybe we're not ready yet or not anymore.
|
// Maybe we're not ready yet or not anymore.
|
||||||
if screen == nil || root == nil {
|
if screen == nil || root == nil {
|
||||||
@ -427,3 +539,35 @@ func (a *Application) GetFocus() Primitive {
|
|||||||
defer a.RUnlock()
|
defer a.RUnlock()
|
||||||
return a.focus
|
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,
|
||||||
|
}
|
60
vendor/maunium.net/go/tview/box.go
generated
vendored
60
vendor/maunium.net/go/tview/box.go
generated
vendored
@ -32,6 +32,9 @@ type Box struct {
|
|||||||
// The color of the border.
|
// The color of the border.
|
||||||
borderColor tcell.Color
|
borderColor tcell.Color
|
||||||
|
|
||||||
|
// The style attributes of the border.
|
||||||
|
borderAttributes tcell.AttrMask
|
||||||
|
|
||||||
// The title. Only visible if there is a border, too.
|
// The title. Only visible if there is a border, too.
|
||||||
title string
|
title string
|
||||||
|
|
||||||
@ -48,10 +51,6 @@ type Box struct {
|
|||||||
// Whether or not this box has focus.
|
// Whether or not this box has focus.
|
||||||
hasFocus bool
|
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
|
// 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
|
// event to be forwarded to the primitive's default input handler (nil if
|
||||||
// nothing should be forwarded).
|
// nothing should be forwarded).
|
||||||
@ -78,7 +77,6 @@ func NewBox() *Box {
|
|||||||
borderColor: Styles.BorderColor,
|
borderColor: Styles.BorderColor,
|
||||||
titleColor: Styles.TitleColor,
|
titleColor: Styles.TitleColor,
|
||||||
titleAlign: AlignCenter,
|
titleAlign: AlignCenter,
|
||||||
clampToScreen: true,
|
|
||||||
}
|
}
|
||||||
b.focus = b
|
b.focus = b
|
||||||
return b
|
return b
|
||||||
@ -121,6 +119,7 @@ func (b *Box) SetRect(x, y, width, height int) {
|
|||||||
b.y = y
|
b.y = y
|
||||||
b.width = width
|
b.width = width
|
||||||
b.height = height
|
b.height = height
|
||||||
|
b.innerX = -1 // Mark inner rect as uninitialized.
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDrawFunc sets a callback function which is invoked after the box primitive
|
// 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
|
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.
|
// SetTitle sets the box's title.
|
||||||
func (b *Box) SetTitle(title string) *Box {
|
func (b *Box) SetTitle(title string) *Box {
|
||||||
b.title = title
|
b.title = title
|
||||||
@ -319,30 +327,30 @@ func (b *Box) Draw(screen tcell.Screen) {
|
|||||||
|
|
||||||
// Draw border.
|
// Draw border.
|
||||||
if b.border && b.width >= 2 && b.height >= 2 {
|
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
|
var vertical, horizontal, topLeft, topRight, bottomLeft, bottomRight rune
|
||||||
if b.focus.HasFocus() {
|
if b.focus.HasFocus() {
|
||||||
vertical = GraphicsDbVertBar
|
horizontal = Borders.HorizontalFocus
|
||||||
horizontal = GraphicsDbHorBar
|
vertical = Borders.VerticalFocus
|
||||||
topLeft = GraphicsDbTopLeftCorner
|
topLeft = Borders.TopLeftFocus
|
||||||
topRight = GraphicsDbTopRightCorner
|
topRight = Borders.TopRightFocus
|
||||||
bottomLeft = GraphicsDbBottomLeftCorner
|
bottomLeft = Borders.BottomLeftFocus
|
||||||
bottomRight = GraphicsDbBottomRightCorner
|
bottomRight = Borders.BottomRightFocus
|
||||||
} else {
|
} else {
|
||||||
vertical = GraphicsHoriBar
|
horizontal = Borders.Horizontal
|
||||||
horizontal = GraphicsVertBar
|
vertical = Borders.Vertical
|
||||||
topLeft = GraphicsTopLeftCorner
|
topLeft = Borders.TopLeft
|
||||||
topRight = GraphicsTopRightCorner
|
topRight = Borders.TopRight
|
||||||
bottomLeft = GraphicsBottomLeftCorner
|
bottomLeft = Borders.BottomLeft
|
||||||
bottomRight = GraphicsBottomRightCorner
|
bottomRight = Borders.BottomRight
|
||||||
}
|
}
|
||||||
for x := b.x + 1; x < b.x+b.width-1; x++ {
|
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, horizontal, nil, border)
|
||||||
screen.SetContent(x, b.y+b.height-1, vertical, 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++ {
|
for y := b.y + 1; y < b.y+b.height-1; y++ {
|
||||||
screen.SetContent(b.x, y, horizontal, nil, border)
|
screen.SetContent(b.x, y, vertical, nil, border)
|
||||||
screen.SetContent(b.x+b.width-1, y, horizontal, 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.y, topLeft, nil, border)
|
||||||
screen.SetContent(b.x+b.width-1, b.y, topRight, 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.
|
// Draw title.
|
||||||
if b.title != "" && b.width >= 4 {
|
if b.title != "" && b.width >= 4 {
|
||||||
_, printed := Print(screen, b.title, b.x+1, b.y, b.width-2, b.titleAlign, b.titleColor)
|
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 {
|
if len(b.title)-printed > 0 && printed > 0 {
|
||||||
_, _, style, _ := screen.GetContent(b.x+b.width-2, b.y)
|
_, _, style, _ := screen.GetContent(b.x+b.width-2, b.y)
|
||||||
fg, _, _ := style.Decompose()
|
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,7 +378,6 @@ func (b *Box) Draw(screen tcell.Screen) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clamp inner rect to screen.
|
// Clamp inner rect to screen.
|
||||||
if b.clampToScreen {
|
|
||||||
width, height := screen.Size()
|
width, height := screen.Size()
|
||||||
if b.innerX < 0 {
|
if b.innerX < 0 {
|
||||||
b.innerWidth += b.innerX
|
b.innerWidth += b.innerX
|
||||||
@ -387,7 +394,6 @@ func (b *Box) Draw(screen tcell.Screen) {
|
|||||||
b.innerY = 0
|
b.innerY = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Focus is called when this primitive receives focus.
|
// Focus is called when this primitive receives focus.
|
||||||
func (b *Box) Focus(delegate func(p Primitive)) {
|
func (b *Box) Focus(delegate func(p Primitive)) {
|
||||||
|
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
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDoneFunc sets a handler which is called when the user is done entering
|
// SetDoneFunc sets a handler which is called when the user is done using the
|
||||||
// text. The callback function is provided with the key that was pressed, which
|
// checkbox. The callback function is provided with the key that was pressed,
|
||||||
// is one of the following:
|
// which is one of the following:
|
||||||
//
|
//
|
||||||
// - KeyEscape: Abort text input.
|
// - KeyEscape: Abort text input.
|
||||||
// - KeyTab: Move to the next field.
|
// - 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:
|
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.
|
be highlighted.
|
||||||
- Table: Scrollable display of tabular data. Table cells, rows, or columns may
|
- Table: A scrollable display of tabular data. Table cells, rows, or columns
|
||||||
also be highlighted.
|
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.
|
- List: A navigable text list with optional keyboard shortcuts.
|
||||||
- InputField: One-line input fields to enter text.
|
- InputField: One-line input fields to enter text.
|
||||||
- DropDown: Drop-down selection fields.
|
- DropDown: Drop-down selection fields.
|
||||||
@ -83,7 +85,7 @@ tag is as follows:
|
|||||||
|
|
||||||
[<foreground>:<background>:<flags>]
|
[<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
|
(Empty square brackets "[]", however, are not considered color tags.) Colors
|
||||||
that are not specified will be left unchanged. A field with just a dash ("-")
|
that are not specified will be left unchanged. A field with just a dash ("-")
|
||||||
means "reset to default".
|
means "reset to default".
|
||||||
@ -135,6 +137,27 @@ Unicode Support
|
|||||||
|
|
||||||
This package supports unicode characters including wide characters.
|
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
|
Type Hierarchy
|
||||||
|
|
||||||
All widgets listed above contain the Box type. All of Box's functions are
|
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.
|
// Hand control over to the list.
|
||||||
d.open = true
|
d.open = true
|
||||||
|
optionBefore := d.currentOption
|
||||||
d.list.SetSelectedFunc(func(index int, mainText, secondaryText string, shortcut rune) {
|
d.list.SetSelectedFunc(func(index int, mainText, secondaryText string, shortcut rune) {
|
||||||
// An option was selected. Close the list again.
|
// An option was selected. Close the list again.
|
||||||
d.open = false
|
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])
|
d.prefix = string(r[:len(r)-1])
|
||||||
}
|
}
|
||||||
evalPrefix()
|
evalPrefix()
|
||||||
|
} else if event.Key() == tcell.KeyEscape {
|
||||||
|
d.open = false
|
||||||
|
d.currentOption = optionBefore
|
||||||
|
setFocus(d)
|
||||||
} else {
|
} else {
|
||||||
d.prefix = ""
|
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
|
*Box
|
||||||
|
|
||||||
// The items to be positioned.
|
// The items to be positioned.
|
||||||
items []flexItem
|
items []*flexItem
|
||||||
|
|
||||||
// FlexRow or FlexColumn.
|
// FlexRow or FlexColumn.
|
||||||
direction int
|
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
|
// You can provide a nil value for the primitive. This will still consume screen
|
||||||
// space but nothing will be drawn.
|
// space but nothing will be drawn.
|
||||||
func (f *Flex) AddItem(item Primitive, fixedSize, proportion int, focus bool) *Flex {
|
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
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,6 +94,19 @@ func (f *Flex) RemoveItem(p Primitive) *Flex {
|
|||||||
return f
|
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.
|
// Draw draws this primitive onto the screen.
|
||||||
func (f *Flex) Draw(screen tcell.Screen) {
|
func (f *Flex) Draw(screen tcell.Screen) {
|
||||||
f.Box.Draw(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.
|
// required.
|
||||||
GetFieldWidth() int
|
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
|
// 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
|
// 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).
|
// 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
|
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
|
// Clear removes all input elements from the form, including the buttons if
|
||||||
// specified.
|
// specified.
|
||||||
func (f *Form) Clear(includeButtons bool) *Form {
|
func (f *Form) Clear(includeButtons bool) *Form {
|
||||||
@ -251,6 +282,14 @@ func (f *Form) GetFormItem(index int) FormItem {
|
|||||||
return f.items[index]
|
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
|
// 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
|
// no such element is found, nil is returned. Buttons are not searched and will
|
||||||
// therefore not be returned.
|
// therefore not be returned.
|
||||||
@ -263,6 +302,18 @@ func (f *Form) GetFormItemByLabel(label string) FormItem {
|
|||||||
return nil
|
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
|
// SetCancelFunc sets a handler which is called when the user hits the Escape
|
||||||
// key.
|
// key.
|
||||||
func (f *Form) SetCancelFunc(callback func()) *Form {
|
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
|
by := item.y - 1
|
||||||
if by >= 0 && by < height {
|
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
|
by = item.y + item.h
|
||||||
if by >= 0 && by < height {
|
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.
|
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
|
bx := item.x - 1
|
||||||
if bx >= 0 && bx < width {
|
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
|
bx = item.x + item.w
|
||||||
if bx >= 0 && bx < width {
|
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.
|
bx, by := item.x-1, item.y-1 // Top-left corner.
|
||||||
if bx >= 0 && bx < width && by >= 0 && by < height {
|
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.
|
bx, by = item.x+item.w, item.y-1 // Top-right corner.
|
||||||
if bx >= 0 && bx < width && by >= 0 && by < height {
|
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.
|
bx, by = item.x-1, item.y+item.h // Bottom-left corner.
|
||||||
if bx >= 0 && bx < width && by >= 0 && by < height {
|
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.
|
bx, by = item.x+item.w, item.y+item.h // Bottom-right corner.
|
||||||
if bx >= 0 && bx < width && by >= 0 && by < height {
|
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
|
// 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
|
// The following keys can be used for navigation and editing:
|
||||||
// input).
|
//
|
||||||
|
// - 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.
|
// See https://github.com/rivo/tview/wiki/InputField for an example.
|
||||||
type InputField struct {
|
type InputField struct {
|
||||||
@ -53,6 +66,12 @@ type InputField struct {
|
|||||||
// disables masking.
|
// disables masking.
|
||||||
maskCharacter rune
|
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.
|
// An optional function which may reject the last character that was entered.
|
||||||
accept func(text string, ch rune) bool
|
accept func(text string, ch rune) bool
|
||||||
|
|
||||||
@ -83,6 +102,7 @@ func NewInputField() *InputField {
|
|||||||
// SetText sets the current text of the input field.
|
// SetText sets the current text of the input field.
|
||||||
func (i *InputField) SetText(text string) *InputField {
|
func (i *InputField) SetText(text string) *InputField {
|
||||||
i.text = text
|
i.text = text
|
||||||
|
i.cursorPos = len(text)
|
||||||
if i.changed != nil {
|
if i.changed != nil {
|
||||||
i.changed(text)
|
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
|
// SetAcceptanceFunc sets a handler which may reject the last character that was
|
||||||
// entered (by returning false).
|
// 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).
|
// be used for common input (e.g. numbers, maximum text length).
|
||||||
func (i *InputField) SetAcceptanceFunc(handler func(textToCheck string, lastChar rune) bool) *InputField {
|
func (i *InputField) SetAcceptanceFunc(handler func(textToCheck string, lastChar rune) bool) *InputField {
|
||||||
i.accept = handler
|
i.accept = handler
|
||||||
@ -244,56 +264,69 @@ func (i *InputField) Draw(screen tcell.Screen) {
|
|||||||
screen.SetContent(x+index, y, ' ', nil, fieldStyle)
|
screen.SetContent(x+index, y, ' ', nil, fieldStyle)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw placeholder text.
|
// Text.
|
||||||
|
var cursorScreenPos int
|
||||||
text := i.text
|
text := i.text
|
||||||
if text == "" && i.placeholder != "" {
|
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 {
|
} else {
|
||||||
// Draw entered text.
|
// Draw entered text.
|
||||||
if i.maskCharacter > 0 {
|
if i.maskCharacter > 0 {
|
||||||
text = strings.Repeat(string(i.maskCharacter), utf8.RuneCountInString(i.text))
|
text = strings.Repeat(string(i.maskCharacter), utf8.RuneCountInString(i.text))
|
||||||
} else {
|
|
||||||
text = Escape(text)
|
|
||||||
}
|
}
|
||||||
fieldWidth-- // We need one cell for the cursor.
|
stringWidth := runewidth.StringWidth(text)
|
||||||
if fieldWidth < runewidth.StringWidth(text) {
|
if fieldWidth >= stringWidth {
|
||||||
Print(screen, text, x, y, fieldWidth, AlignRight, i.fieldTextColor)
|
// 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 {
|
} 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.
|
// Set cursor.
|
||||||
if i.focus.HasFocus() {
|
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.
|
// InputHandler returns the handler for this primitive.
|
||||||
func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
|
func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
|
||||||
return i.WrapInputHandler(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.
|
// Process key event.
|
||||||
switch key := event.Key(); key {
|
switch key := event.Key(); key {
|
||||||
case tcell.KeyRune: // Regular character.
|
case tcell.KeyRune: // Regular character.
|
||||||
newText := i.text + string(event.Rune())
|
if event.Modifiers()&tcell.ModAlt > 0 {
|
||||||
if i.accept != nil {
|
// We accept some Alt- key combinations.
|
||||||
if !i.accept(newText, event.Rune()) {
|
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
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i.text = newText
|
|
||||||
case tcell.KeyCtrlU: // Delete all.
|
case tcell.KeyCtrlU: // Delete all.
|
||||||
i.text = ""
|
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.
|
case tcell.KeyCtrlW: // Delete last word.
|
||||||
lastWord := regexp.MustCompile(`\s*\S+\s*$`)
|
lastWord := regexp.MustCompile(`\S+\s*$`)
|
||||||
i.text = lastWord.ReplaceAllString(i.text, "")
|
newText := lastWord.ReplaceAllString(i.text[:i.cursorPos], "") + i.text[i.cursorPos:]
|
||||||
case tcell.KeyBackspace, tcell.KeyBackspace2: // Delete last character.
|
i.cursorPos -= len(i.text) - len(newText)
|
||||||
if len(i.text) == 0 {
|
i.text = newText
|
||||||
break
|
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)
|
case tcell.KeyDelete: // Delete character after the cursor.
|
||||||
i.text = string(runes[:len(runes)-1])
|
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.
|
case tcell.KeyEnter, tcell.KeyTab, tcell.KeyBacktab, tcell.KeyEscape: // We're done.
|
||||||
if i.done != nil {
|
if i.done != nil {
|
||||||
i.done(key)
|
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
|
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.
|
// SetMainTextColor sets the color of the items' main text.
|
||||||
func (l *List) SetMainTextColor(color tcell.Color) *List {
|
func (l *List) SetMainTextColor(color tcell.Color) *List {
|
||||||
l.mainTextColor = color
|
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
|
// This function is also called when the first item is added or when
|
||||||
// SetCurrentItem() is called.
|
// 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
|
l.changed = handler
|
||||||
return l
|
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).
|
SetButtonBackgroundColor(Styles.PrimitiveBackgroundColor).
|
||||||
SetButtonTextColor(Styles.PrimaryTextColor)
|
SetButtonTextColor(Styles.PrimaryTextColor)
|
||||||
m.form.SetBackgroundColor(Styles.ContrastBackgroundColor).SetBorderPadding(0, 0, 0, 0)
|
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 = NewFrame(m.form).SetBorders(0, 0, 1, 0, 0, 0)
|
||||||
m.frame.SetBorder(true).
|
m.frame.SetBorder(true).
|
||||||
SetBackgroundColor(Styles.ContrastBackgroundColor).
|
SetBackgroundColor(Styles.ContrastBackgroundColor).
|
||||||
@ -81,6 +86,16 @@ func (m *Modal) AddButtons(labels []string) *Modal {
|
|||||||
m.done(i, l)
|
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)
|
}(index, label)
|
||||||
}
|
}
|
||||||
return m
|
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.
|
// The number of visible rows the last time the table was drawn.
|
||||||
visibleRows int
|
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
|
// An optional function which gets called when the user presses Enter on a
|
||||||
// selected cell. If entire rows selected, the column value is undefined.
|
// selected cell. If entire rows selected, the column value is undefined.
|
||||||
// Likewise for entire columns.
|
// Likewise for entire columns.
|
||||||
@ -276,9 +280,21 @@ func (t *Table) SetBordersColor(color tcell.Color) *Table {
|
|||||||
return t
|
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
|
// SetSeparator sets the character used to fill the space between two
|
||||||
// neighboring cells. This is a space character ' ' per default but you may
|
// 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
|
// separation should be more visible. If cell borders are activated, this is
|
||||||
// ignored.
|
// 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
|
// 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.
|
// the Text and Color fields should be set.
|
||||||
//
|
//
|
||||||
// Note that setting cells in previously unknown rows and columns will
|
// 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
|
// 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.
|
// was not previously set.
|
||||||
func (t *Table) GetCell(row, column int) *TableCell {
|
func (t *Table) GetCell(row, column int) *TableCell {
|
||||||
if row >= len(t.cells) || column >= len(t.cells[row]) {
|
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]
|
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.
|
// GetRowCount returns the number of rows in the table.
|
||||||
func (t *Table) GetRowCount() int {
|
func (t *Table) GetRowCount() int {
|
||||||
return len(t.cells)
|
return len(t.cells)
|
||||||
@ -644,7 +685,6 @@ ColumnLoop:
|
|||||||
}
|
}
|
||||||
expWidth := toDistribute * expansion / expansionTotal
|
expWidth := toDistribute * expansion / expansionTotal
|
||||||
widths[index] += expWidth
|
widths[index] += expWidth
|
||||||
tableWidth += expWidth
|
|
||||||
toDistribute -= expWidth
|
toDistribute -= expWidth
|
||||||
expansionTotal -= expansion
|
expansionTotal -= expansion
|
||||||
}
|
}
|
||||||
@ -668,24 +708,24 @@ ColumnLoop:
|
|||||||
// Draw borders.
|
// Draw borders.
|
||||||
rowY *= 2
|
rowY *= 2
|
||||||
for pos := 0; pos < columnWidth && columnX+1+pos < width; pos++ {
|
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 columnIndex == 0 {
|
||||||
if rowY == 0 {
|
if rowY == 0 {
|
||||||
ch = GraphicsTopLeftCorner
|
ch = Borders.TopLeft
|
||||||
} else {
|
} else {
|
||||||
ch = GraphicsLeftT
|
ch = Borders.LeftT
|
||||||
}
|
}
|
||||||
} else if rowY == 0 {
|
} else if rowY == 0 {
|
||||||
ch = GraphicsTopT
|
ch = Borders.TopT
|
||||||
}
|
}
|
||||||
drawBorder(columnX, rowY, ch)
|
drawBorder(columnX, rowY, ch)
|
||||||
rowY++
|
rowY++
|
||||||
if rowY >= height {
|
if rowY >= height {
|
||||||
break // No space for the text anymore.
|
break // No space for the text anymore.
|
||||||
}
|
}
|
||||||
drawBorder(columnX, rowY, GraphicsVertBar)
|
drawBorder(columnX, rowY, Borders.Vertical)
|
||||||
} else if columnIndex > 0 {
|
} else if columnIndex > 0 {
|
||||||
// Draw separator.
|
// Draw separator.
|
||||||
drawBorder(columnX, rowY, t.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))
|
_, 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 {
|
if StringWidth(cell.Text)-printed > 0 && printed > 0 {
|
||||||
_, _, style, _ := screen.GetContent(x+columnX+1+finalWidth-1, y+rowY)
|
_, _, 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.
|
// Draw bottom border.
|
||||||
if rowY := 2 * len(rows); t.borders && rowY < height {
|
if rowY := 2 * len(rows); t.borders && rowY < height {
|
||||||
for pos := 0; pos < columnWidth && columnX+1+pos < width; pos++ {
|
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 {
|
if columnIndex == 0 {
|
||||||
ch = GraphicsBottomLeftCorner
|
ch = Borders.BottomLeft
|
||||||
}
|
}
|
||||||
drawBorder(columnX, rowY, ch)
|
drawBorder(columnX, rowY, ch)
|
||||||
}
|
}
|
||||||
@ -730,26 +770,31 @@ ColumnLoop:
|
|||||||
for rowY := range rows {
|
for rowY := range rows {
|
||||||
rowY *= 2
|
rowY *= 2
|
||||||
if rowY+1 < height {
|
if rowY+1 < height {
|
||||||
drawBorder(columnX, rowY+1, GraphicsVertBar)
|
drawBorder(columnX, rowY+1, Borders.Vertical)
|
||||||
}
|
}
|
||||||
ch := GraphicsRightT
|
ch := Borders.RightT
|
||||||
if rowY == 0 {
|
if rowY == 0 {
|
||||||
ch = GraphicsTopRightCorner
|
ch = Borders.TopRight
|
||||||
}
|
}
|
||||||
drawBorder(columnX, rowY, ch)
|
drawBorder(columnX, rowY, ch)
|
||||||
}
|
}
|
||||||
if rowY := 2 * len(rows); rowY < height {
|
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.
|
// 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 by := 0; by < h && fromY+by < y+height; by++ {
|
||||||
for bx := 0; bx < w && fromX+bx < x+width; bx++ {
|
for bx := 0; bx < w && fromX+bx < x+width; bx++ {
|
||||||
m, c, style, _ := screen.GetContent(fromX+bx, fromY+by)
|
m, c, style, _ := screen.GetContent(fromX+bx, fromY+by)
|
||||||
if selected {
|
fg, bg, a := style.Decompose()
|
||||||
fg, _, _ := style.Decompose()
|
if invert {
|
||||||
if fg == textColor || fg == t.bordersColor {
|
if fg == textColor || fg == t.bordersColor {
|
||||||
fg = backgroundColor
|
fg = backgroundColor
|
||||||
}
|
}
|
||||||
@ -758,10 +803,16 @@ ColumnLoop:
|
|||||||
}
|
}
|
||||||
style = style.Background(textColor).Foreground(fg)
|
style = style.Background(textColor).Foreground(fg)
|
||||||
} else {
|
} else {
|
||||||
if backgroundColor == tcell.ColorDefault {
|
if backgroundColor != tcell.ColorDefault {
|
||||||
continue
|
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)
|
screen.SetContent(fromX+bx, fromY+by, m, c, style)
|
||||||
}
|
}
|
||||||
@ -770,11 +821,12 @@ ColumnLoop:
|
|||||||
|
|
||||||
// Color the cell backgrounds. To avoid undesirable artefacts, we combine
|
// Color the cell backgrounds. To avoid undesirable artefacts, we combine
|
||||||
// the drawing of a cell by background color, selected cells last.
|
// 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
|
x, y, w, h int
|
||||||
text tcell.Color
|
text tcell.Color
|
||||||
selected bool
|
selected bool
|
||||||
})
|
}
|
||||||
|
cellsByBackgroundColor := make(map[tcell.Color][]*cellInfo)
|
||||||
var backgroundColors []tcell.Color
|
var backgroundColors []tcell.Color
|
||||||
for rowY, row := range rows {
|
for rowY, row := range rows {
|
||||||
columnX := 0
|
columnX := 0
|
||||||
@ -794,11 +846,7 @@ ColumnLoop:
|
|||||||
columnSelected := t.columnsSelectable && !t.rowsSelectable && column == t.selectedColumn
|
columnSelected := t.columnsSelectable && !t.rowsSelectable && column == t.selectedColumn
|
||||||
cellSelected := !cell.NotSelectable && (columnSelected || rowSelected || t.rowsSelectable && t.columnsSelectable && column == t.selectedColumn && row == t.selectedRow)
|
cellSelected := !cell.NotSelectable && (columnSelected || rowSelected || t.rowsSelectable && t.columnsSelectable && column == t.selectedColumn && row == t.selectedRow)
|
||||||
entries, ok := cellsByBackgroundColor[cell.BackgroundColor]
|
entries, ok := cellsByBackgroundColor[cell.BackgroundColor]
|
||||||
cellsByBackgroundColor[cell.BackgroundColor] = append(entries, &struct {
|
cellsByBackgroundColor[cell.BackgroundColor] = append(entries, &cellInfo{
|
||||||
x, y, w, h int
|
|
||||||
text tcell.Color
|
|
||||||
selected bool
|
|
||||||
}{
|
|
||||||
x: bx,
|
x: bx,
|
||||||
y: by,
|
y: by,
|
||||||
w: bw,
|
w: bw,
|
||||||
@ -822,13 +870,18 @@ ColumnLoop:
|
|||||||
_, _, lj := c.Hcl()
|
_, _, lj := c.Hcl()
|
||||||
return li < lj
|
return li < lj
|
||||||
})
|
})
|
||||||
|
selFg, selBg, selAttr := t.selectedStyle.Decompose()
|
||||||
for _, bgColor := range backgroundColors {
|
for _, bgColor := range backgroundColors {
|
||||||
entries := cellsByBackgroundColor[bgColor]
|
entries := cellsByBackgroundColor[bgColor]
|
||||||
for _, cell := range entries {
|
for _, cell := range entries {
|
||||||
if cell.selected {
|
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 {
|
} else {
|
||||||
colorBackground(cell.x, cell.y, cell.w, cell.h, bgColor, cell.text, false)
|
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, 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
|
// 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
|
// 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
|
// but if a handler is installed via SetChangedFunc(), you can cause it to be
|
||||||
// redrawn.
|
// redrawn. (See SetChangedFunc() for more details.)
|
||||||
//
|
//
|
||||||
// Navigation
|
// Navigation
|
||||||
//
|
//
|
||||||
@ -103,6 +103,10 @@ type TextView struct {
|
|||||||
// during re-indexing. Set to -1 if there is no current highlight.
|
// during re-indexing. Set to -1 if there is no current highlight.
|
||||||
fromHighlight, toHighlight int
|
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.
|
// A set of region IDs that are currently highlighted.
|
||||||
highlights map[string]struct{}
|
highlights map[string]struct{}
|
||||||
|
|
||||||
@ -170,6 +174,7 @@ func NewTextView() *TextView {
|
|||||||
align: AlignLeft,
|
align: AlignLeft,
|
||||||
wrap: true,
|
wrap: true,
|
||||||
textColor: Styles.PrimaryTextColor,
|
textColor: Styles.PrimaryTextColor,
|
||||||
|
regions: false,
|
||||||
dynamicColors: 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
|
// 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
|
// text view has changed. This is useful when text is written to this io.Writer
|
||||||
// redraw the screen.
|
// 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 {
|
func (t *TextView) SetChangedFunc(handler func()) *TextView {
|
||||||
t.changed = handler
|
t.changed = handler
|
||||||
return t
|
return t
|
||||||
@ -270,6 +287,16 @@ func (t *TextView) SetDoneFunc(handler func(key tcell.Key)) *TextView {
|
|||||||
return t
|
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
|
// ScrollToBeginning scrolls to the top left corner of the text if the text view
|
||||||
// is scrollable.
|
// is scrollable.
|
||||||
func (t *TextView) ScrollToBeginning() *TextView {
|
func (t *TextView) ScrollToBeginning() *TextView {
|
||||||
@ -294,6 +321,12 @@ func (t *TextView) ScrollToEnd() *TextView {
|
|||||||
return t
|
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.
|
// Clear removes all text from the buffer.
|
||||||
func (t *TextView) Clear() *TextView {
|
func (t *TextView) Clear() *TextView {
|
||||||
t.buffer = nil
|
t.buffer = nil
|
||||||
@ -420,13 +453,33 @@ func (t *TextView) GetRegionText(regionID string) string {
|
|||||||
return escapePattern.ReplaceAllString(buffer.String(), `[$1$2]`)
|
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
|
// 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
|
// replaced with TabSize space characters. A "\n" or "\r\n" will be interpreted
|
||||||
// as a new line.
|
// as a new line.
|
||||||
func (t *TextView) Write(p []byte) (n int, err error) {
|
func (t *TextView) Write(p []byte) (n int, err error) {
|
||||||
// Notify at the end.
|
// Notify at the end.
|
||||||
if t.changed != nil {
|
t.Lock()
|
||||||
defer t.changed()
|
changed := t.changed
|
||||||
|
t.Unlock()
|
||||||
|
if changed != nil {
|
||||||
|
defer changed() // Deadlocks may occur if we lock here.
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Lock()
|
t.Lock()
|
||||||
@ -492,7 +545,7 @@ func (t *TextView) reindexBuffer(width int) {
|
|||||||
return // Nothing has changed. We can still use the current index.
|
return // Nothing has changed. We can still use the current index.
|
||||||
}
|
}
|
||||||
t.index = nil
|
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 there's no space, there's no index.
|
||||||
if width < 1 {
|
if width < 1 {
|
||||||
@ -511,8 +564,9 @@ func (t *TextView) reindexBuffer(width int) {
|
|||||||
colorTags [][]string
|
colorTags [][]string
|
||||||
escapeIndices [][]int
|
escapeIndices [][]int
|
||||||
)
|
)
|
||||||
|
strippedStr := str
|
||||||
if t.dynamicColors {
|
if t.dynamicColors {
|
||||||
colorTagIndices, colorTags, escapeIndices, str, _ = decomposeString(str)
|
colorTagIndices, colorTags, escapeIndices, strippedStr, _ = decomposeString(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find all regions in this line. Then remove them.
|
// Find all regions in this line. Then remove them.
|
||||||
@ -523,14 +577,12 @@ func (t *TextView) reindexBuffer(width int) {
|
|||||||
if t.regions {
|
if t.regions {
|
||||||
regionIndices = regionPattern.FindAllStringIndex(str, -1)
|
regionIndices = regionPattern.FindAllStringIndex(str, -1)
|
||||||
regions = regionPattern.FindAllStringSubmatch(str, -1)
|
regions = regionPattern.FindAllStringSubmatch(str, -1)
|
||||||
str = regionPattern.ReplaceAllString(str, "")
|
strippedStr = regionPattern.ReplaceAllString(strippedStr, "")
|
||||||
if !t.dynamicColors {
|
|
||||||
// We haven't detected escape tags yet. Do it now.
|
|
||||||
escapeIndices = escapePattern.FindAllStringIndex(str, -1)
|
|
||||||
str = escapePattern.ReplaceAllString(str, "[$1$2]")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We don't need the original string anymore for now.
|
||||||
|
str = strippedStr
|
||||||
|
|
||||||
// Split the line if required.
|
// Split the line if required.
|
||||||
var splitLines []string
|
var splitLines []string
|
||||||
if t.wrap && len(str) > 0 {
|
if t.wrap && len(str) > 0 {
|
||||||
@ -574,15 +626,53 @@ func (t *TextView) reindexBuffer(width int) {
|
|||||||
|
|
||||||
// Shift original position with tags.
|
// Shift original position with tags.
|
||||||
lineLength := len(splitLine)
|
lineLength := len(splitLine)
|
||||||
|
remainingLength := lineLength
|
||||||
|
tagEnd := originalPos
|
||||||
|
totalTagLength := 0
|
||||||
for {
|
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.
|
// Process color tags.
|
||||||
originalPos += colorTagIndices[colorPos][1] - colorTagIndices[colorPos][0]
|
|
||||||
foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colorTags[colorPos])
|
foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colorTags[colorPos])
|
||||||
colorPos++
|
colorPos++
|
||||||
} else if regionPos < len(regionIndices) && regionIndices[regionPos][0] <= originalPos+lineLength {
|
case 1:
|
||||||
// Process region tags.
|
// Process region tags.
|
||||||
originalPos += regionIndices[regionPos][1] - regionIndices[regionPos][0]
|
|
||||||
regionID = regions[regionPos][1]
|
regionID = regions[regionPos][1]
|
||||||
_, highlighted = t.highlights[regionID]
|
_, highlighted = t.highlights[regionID]
|
||||||
|
|
||||||
@ -591,23 +681,21 @@ func (t *TextView) reindexBuffer(width int) {
|
|||||||
line := len(t.index)
|
line := len(t.index)
|
||||||
if t.fromHighlight < 0 {
|
if t.fromHighlight < 0 {
|
||||||
t.fromHighlight, t.toHighlight = line, line
|
t.fromHighlight, t.toHighlight = line, line
|
||||||
|
t.posHighlight = runewidth.StringWidth(splitLine[:strippedTagStart])
|
||||||
} else if line > t.toHighlight {
|
} else if line > t.toHighlight {
|
||||||
t.toHighlight = line
|
t.toHighlight = line
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
regionPos++
|
regionPos++
|
||||||
} else if escapePos < len(escapeIndices) && escapeIndices[escapePos][0] <= originalPos+lineLength {
|
case 2:
|
||||||
// Process escape tags.
|
// Process escape tags.
|
||||||
originalPos++
|
|
||||||
escapePos++
|
escapePos++
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Advance to next line.
|
// Advance to next line.
|
||||||
originalPos += lineLength
|
originalPos += lineLength + totalTagLength
|
||||||
|
|
||||||
// Append this line.
|
// Append this line.
|
||||||
line.NextPos = originalPos
|
line.NextPos = originalPos
|
||||||
@ -649,7 +737,7 @@ func (t *TextView) Draw(screen tcell.Screen) {
|
|||||||
t.pageSize = height
|
t.pageSize = height
|
||||||
|
|
||||||
// If the width has changed, we need to reindex.
|
// If the width has changed, we need to reindex.
|
||||||
if width != t.lastWidth {
|
if width != t.lastWidth && t.wrap {
|
||||||
t.index = nil
|
t.index = nil
|
||||||
}
|
}
|
||||||
t.lastWidth = width
|
t.lastWidth = width
|
||||||
@ -672,6 +760,16 @@ func (t *TextView) Draw(screen tcell.Screen) {
|
|||||||
// No, let's move to the start of the highlights.
|
// No, let's move to the start of the highlights.
|
||||||
t.lineOffset = t.fromHighlight
|
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
|
t.scrollToHighlights = false
|
||||||
|
|
||||||
@ -737,8 +835,9 @@ func (t *TextView) Draw(screen tcell.Screen) {
|
|||||||
colorTags [][]string
|
colorTags [][]string
|
||||||
escapeIndices [][]int
|
escapeIndices [][]int
|
||||||
)
|
)
|
||||||
|
strippedText := text
|
||||||
if t.dynamicColors {
|
if t.dynamicColors {
|
||||||
colorTagIndices, colorTags, escapeIndices, _, _ = decomposeString(text)
|
colorTagIndices, colorTags, escapeIndices, strippedText, _ = decomposeString(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get regions.
|
// Get regions.
|
||||||
@ -749,8 +848,10 @@ func (t *TextView) Draw(screen tcell.Screen) {
|
|||||||
if t.regions {
|
if t.regions {
|
||||||
regionIndices = regionPattern.FindAllStringIndex(text, -1)
|
regionIndices = regionPattern.FindAllStringIndex(text, -1)
|
||||||
regions = regionPattern.FindAllStringSubmatch(text, -1)
|
regions = regionPattern.FindAllStringSubmatch(text, -1)
|
||||||
|
strippedText = regionPattern.ReplaceAllString(strippedText, "")
|
||||||
if !t.dynamicColors {
|
if !t.dynamicColors {
|
||||||
escapeIndices = escapePattern.FindAllStringIndex(text, -1)
|
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.
|
// Print the line.
|
||||||
var currentTag, currentRegion, currentEscapeTag, skipped, runeSeqWidth int
|
var colorPos, regionPos, escapePos, tagOffset, skipped int
|
||||||
runeSequence := make([]rune, 0, 10)
|
iterateString(strippedText, func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
|
||||||
flush := func() {
|
// Process tags.
|
||||||
if len(runeSequence) == 0 {
|
for {
|
||||||
return
|
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.
|
// 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)
|
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.
|
// Skip to the right.
|
||||||
if !t.wrap && skipped < skip {
|
if !t.wrap && skipped < skip {
|
||||||
skipped += chWidth
|
skipped += screenWidth
|
||||||
continue
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop at the right border.
|
// Stop at the right border.
|
||||||
if posX+runeSeqWidth+chWidth > width {
|
if posX+screenWidth > width {
|
||||||
break
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flush the rune sequence.
|
// Draw the character.
|
||||||
flush()
|
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.
|
// Advance.
|
||||||
runeSequence = append(runeSequence, ch)
|
posX += screenWidth
|
||||||
runeSeqWidth += chWidth
|
return false
|
||||||
}
|
})
|
||||||
if posX+runeSeqWidth <= width {
|
|
||||||
flush()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this view is not scrollable, we'll purge the buffer of lines that have
|
// 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()
|
||||||
|
})
|
||||||
|
}
|
644
vendor/maunium.net/go/tview/util.go
generated
vendored
644
vendor/maunium.net/go/tview/util.go
generated
vendored
@ -1,11 +1,9 @@
|
|||||||
package tview
|
package tview
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"math"
|
"math"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"maunium.net/go/tcell"
|
"maunium.net/go/tcell"
|
||||||
@ -19,97 +17,13 @@ const (
|
|||||||
AlignRight
|
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.
|
// Common regular expressions.
|
||||||
var (
|
var (
|
||||||
colorPattern = regexp.MustCompile(`\[([a-zA-Z]+|#[0-9a-zA-Z]{6}|\-)?(:([a-zA-Z]+|#[0-9a-zA-Z]{6}|\-)?(:([lbdru]+|\-)?)?)?\]`)
|
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_,;: \-\.]*)"\]`)
|
regionPattern = regexp.MustCompile(`\["([a-zA-Z0-9_,;: \-\.]*)"\]`)
|
||||||
escapePattern = regexp.MustCompile(`\[([a-zA-Z0-9_,;: \-\."#]+)\[(\[*)\]`)
|
escapePattern = regexp.MustCompile(`\[([a-zA-Z0-9_,;: \-\."#]+)\[(\[*)\]`)
|
||||||
nonEscapePattern = 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+`)
|
spacePattern = regexp.MustCompile(`\s+`)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -204,13 +118,12 @@ func overlayStyle(background tcell.Color, defaultStyle tcell.Style, fgColor, bgC
|
|||||||
defFg, defBg, defAttr := defaultStyle.Decompose()
|
defFg, defBg, defAttr := defaultStyle.Decompose()
|
||||||
style := defaultStyle.Background(background)
|
style := defaultStyle.Background(background)
|
||||||
|
|
||||||
if fgColor == "-" {
|
|
||||||
style = style.Foreground(defFg)
|
style = style.Foreground(defFg)
|
||||||
} else if fgColor != "" {
|
if fgColor != "" {
|
||||||
style = style.Foreground(tcell.GetColor(fgColor))
|
style = style.Foreground(tcell.GetColor(fgColor))
|
||||||
}
|
}
|
||||||
|
|
||||||
if bgColor == "-" {
|
if bgColor == "-" || bgColor == "" && defBg != tcell.ColorDefault {
|
||||||
style = style.Background(defBg)
|
style = style.Background(defBg)
|
||||||
} else if bgColor != "" {
|
} else if bgColor != "" {
|
||||||
style = style.Background(tcell.GetColor(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.
|
// You can change the colors and text styles mid-text by inserting a color tag.
|
||||||
// See the package description for details.
|
// See the package description for details.
|
||||||
//
|
//
|
||||||
// Returns the number of actual runes printed (not including color tags) and the
|
// Returns the number of actual bytes of the text printed (including color tags)
|
||||||
// actual width used for the printed runes.
|
// 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) {
|
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))
|
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.
|
// Decompose the text.
|
||||||
colorIndices, colors, escapeIndices, strippedText, _ := decomposeString(text)
|
colorIndices, colors, escapeIndices, strippedText, strippedWidth := decomposeString(text)
|
||||||
|
|
||||||
// We deal with runes, not with bytes.
|
// We want to reduce all alignments to AlignLeft.
|
||||||
runes := []rune(strippedText)
|
if align == AlignRight {
|
||||||
|
if strippedWidth <= maxWidth {
|
||||||
// This helper function takes positions for a substring of "runes" and returns
|
// There's enough space for the entire text.
|
||||||
// a new string corresponding to this substring, making sure printing that
|
return printWithStyle(screen, text, x+maxWidth-strippedWidth, y, maxWidth, AlignLeft, style)
|
||||||
// substring will observe color tags.
|
}
|
||||||
substring := func(from, to int) string {
|
// Trim characters off the beginning.
|
||||||
var (
|
var (
|
||||||
colorPos, escapePos, runePos, startPos int
|
bytes, width, colorPos, escapePos, tagOffset int
|
||||||
foregroundColor, backgroundColor, attributes string
|
foregroundColor, backgroundColor, attributes string
|
||||||
)
|
)
|
||||||
if from >= len(runes) {
|
_, originalBackground, _ := style.Decompose()
|
||||||
return ""
|
iterateString(strippedText, func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
|
||||||
}
|
// Update color/escape tag offset and style.
|
||||||
for pos := range text {
|
if colorPos < len(colorIndices) && textPos+tagOffset >= colorIndices[colorPos][0] && textPos+tagOffset < colorIndices[colorPos][1] {
|
||||||
// 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])
|
foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colors[colorPos])
|
||||||
}
|
style = overlayStyle(originalBackground, style, foregroundColor, backgroundColor, attributes)
|
||||||
|
tagOffset += colorIndices[colorPos][1] - colorIndices[colorPos][0]
|
||||||
colorPos++
|
colorPos++
|
||||||
}
|
}
|
||||||
continue
|
if escapePos < len(escapeIndices) && textPos+tagOffset >= escapeIndices[escapePos][0] && textPos+tagOffset < escapeIndices[escapePos][1] {
|
||||||
}
|
tagOffset++
|
||||||
|
|
||||||
// Handle escape tags.
|
|
||||||
if escapePos < len(escapeIndices) && pos >= escapeIndices[escapePos][0] && pos < escapeIndices[escapePos][1] {
|
|
||||||
if pos == escapeIndices[escapePos][1]-1 {
|
|
||||||
escapePos++
|
escapePos++
|
||||||
} else if pos == escapeIndices[escapePos][1]-2 {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
if strippedWidth-screenPos < maxWidth {
|
||||||
|
// We chopped off enough.
|
||||||
|
if escapePos > 0 && textPos+tagOffset-1 >= escapeIndices[escapePos-1][0] && textPos+tagOffset-1 < escapeIndices[escapePos-1][1] {
|
||||||
|
// Unescape open escape sequences.
|
||||||
|
escapeCharPos := escapeIndices[escapePos-1][1] - 2
|
||||||
|
text = text[:escapeCharPos] + text[escapeCharPos+1:]
|
||||||
}
|
}
|
||||||
|
// Print and return.
|
||||||
// Check boundaries.
|
bytes, width = printWithStyle(screen, text[textPos+tagOffset:], x, y, maxWidth, AlignLeft, style)
|
||||||
if runePos == from {
|
return true
|
||||||
startPos = pos
|
|
||||||
} else if runePos >= to {
|
|
||||||
return fmt.Sprintf(`[%s:%s:%s]%s`, foregroundColor, backgroundColor, attributes, text[startPos:pos])
|
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
runePos++
|
})
|
||||||
}
|
return bytes, width
|
||||||
|
|
||||||
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)
|
|
||||||
} else if align == AlignCenter {
|
} else if align == AlignCenter {
|
||||||
width := runewidth.StringWidth(strippedText)
|
if strippedWidth == maxWidth {
|
||||||
if width == maxWidth {
|
|
||||||
// Use the exact space.
|
// Use the exact space.
|
||||||
return printWithStyle(screen, text, x, y, maxWidth, AlignLeft, style)
|
return printWithStyle(screen, text, x, y, maxWidth, AlignLeft, style)
|
||||||
} else if width < maxWidth {
|
} else if strippedWidth < maxWidth {
|
||||||
// We have more space than we need.
|
// 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)
|
return printWithStyle(screen, text, x+half, y, maxWidth-half, AlignLeft, style)
|
||||||
} else {
|
} else {
|
||||||
// Chop off runes until we have a perfect fit.
|
// Chop off runes until we have a perfect fit.
|
||||||
var choppedLeft, choppedRight, leftIndex, rightIndex int
|
var choppedLeft, choppedRight, leftIndex, rightIndex int
|
||||||
rightIndex = len(runes) - 1
|
rightIndex = len(strippedText)
|
||||||
for rightIndex > leftIndex && width-choppedLeft-choppedRight > maxWidth {
|
for rightIndex-1 > leftIndex && strippedWidth-choppedLeft-choppedRight > maxWidth {
|
||||||
if choppedLeft < choppedRight {
|
if choppedLeft < choppedRight {
|
||||||
leftWidth := runewidth.RuneWidth(runes[leftIndex])
|
// Iterate on the left by one character.
|
||||||
choppedLeft += leftWidth
|
iterateString(strippedText[leftIndex:], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
|
||||||
leftIndex++
|
choppedLeft += screenWidth
|
||||||
for leftIndex < len(runes) && leftIndex < rightIndex && runewidth.RuneWidth(runes[leftIndex]) == 0 {
|
leftIndex += textWidth
|
||||||
leftIndex++
|
return true
|
||||||
}
|
})
|
||||||
} else {
|
} else {
|
||||||
rightWidth := runewidth.RuneWidth(runes[rightIndex])
|
// Iterate on the right by one character.
|
||||||
choppedRight += rightWidth
|
iterateStringReverse(strippedText[leftIndex:rightIndex], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
|
||||||
rightIndex--
|
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.
|
// Draw text.
|
||||||
drawn := 0
|
|
||||||
drawnWidth := 0
|
|
||||||
var (
|
var (
|
||||||
colorPos, escapePos int
|
drawn, drawnWidth, colorPos, escapePos, tagOffset int
|
||||||
foregroundColor, backgroundColor, attributes string
|
foregroundColor, backgroundColor, attributes string
|
||||||
)
|
)
|
||||||
runeSequence := make([]rune, 0, 10)
|
iterateString(strippedText, func(main rune, comb []rune, textPos, length, screenPos, screenWidth int) bool {
|
||||||
runeSeqWidth := 0
|
// Only continue if there is still space.
|
||||||
flush := func() {
|
if drawnWidth+screenWidth > maxWidth {
|
||||||
if len(runeSequence) == 0 {
|
return true
|
||||||
return // Nothing to flush.
|
}
|
||||||
|
|
||||||
|
// 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.
|
// 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)
|
_, _, finalStyle, _ := screen.GetContent(finalX, y)
|
||||||
_, background, _ := finalStyle.Decompose()
|
_, background, _ := finalStyle.Decompose()
|
||||||
finalStyle = overlayStyle(background, style, foregroundColor, backgroundColor, attributes)
|
finalStyle = overlayStyle(background, style, foregroundColor, backgroundColor, attributes)
|
||||||
var comb []rune
|
for offset := screenWidth - 1; offset >= 0; offset-- {
|
||||||
if len(runeSequence) > 1 {
|
// To avoid undesired effects, we populate all cells.
|
||||||
// Allocate space for the combining characters only when necessary.
|
if offset == 0 {
|
||||||
comb = make([]rune, len(runeSequence)-1)
|
screen.SetContent(finalX+offset, y, main, comb, finalStyle)
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we have enough space for this rune.
|
|
||||||
chWidth := runewidth.RuneWidth(ch)
|
|
||||||
if drawnWidth+chWidth > maxWidth {
|
|
||||||
break // No. We're done then.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put this rune in the queue.
|
|
||||||
if chWidth == 0 {
|
|
||||||
// If this is not a modifier, we treat it as a space character.
|
|
||||||
if len(runeSequence) == 0 {
|
|
||||||
ch = ' '
|
|
||||||
chWidth = 1
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// We have a character. Flush all previous runes.
|
screen.SetContent(finalX+offset, y, ' ', nil, finalStyle)
|
||||||
flush()
|
|
||||||
}
|
}
|
||||||
runeSequence = append(runeSequence, ch)
|
|
||||||
runeSeqWidth += chWidth
|
|
||||||
|
|
||||||
}
|
|
||||||
if drawnWidth+runeSeqWidth <= maxWidth {
|
|
||||||
flush()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return drawn, drawnWidth
|
// Advance.
|
||||||
|
drawn += length
|
||||||
|
drawnWidth += screenWidth
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
return drawn + tagOffset + len(escapeIndices), drawnWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrintSimple prints white text to the screen at the given position.
|
// 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)
|
colorTagIndices, _, escapeIndices, strippedText, _ := decomposeString(text)
|
||||||
|
|
||||||
// Find candidate breakpoints.
|
// 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
|
// Process stripped text one character at a time.
|
||||||
// positions are in stripped index space.
|
var (
|
||||||
addLine := func(from, to int) {
|
colorPos, escapePos, breakpointPos, tagOffset int
|
||||||
// Shift indices back to original index space.
|
lastBreakpoint, lastContinuation, currentLineStart int
|
||||||
var colorTagIndex, escapeIndex int
|
lineWidth, continuationWidth int
|
||||||
for colorTagIndex < len(colorTagIndices) && to >= colorTagIndices[colorTagIndex][0] ||
|
newlineBreakpoint bool
|
||||||
escapeIndex < len(escapeIndices) && to >= escapeIndices[escapeIndex][0] {
|
)
|
||||||
past := 0
|
unescape := func(substr string, startIndex int) string {
|
||||||
if colorTagIndex < len(colorTagIndices) {
|
// A helper function to unescape escaped tags.
|
||||||
tagWidth := colorTagIndices[colorTagIndex][1] - colorTagIndices[colorTagIndex][0]
|
for index := escapePos; index >= 0; index-- {
|
||||||
if colorTagIndices[colorTagIndex][0] < from {
|
if index < len(escapeIndices) && startIndex > escapeIndices[index][0] && startIndex < escapeIndices[index][1]-1 {
|
||||||
from += tagWidth
|
pos := escapeIndices[index][1] - 2 - startIndex
|
||||||
to += tagWidth
|
return substr[:pos] + substr[pos+1:]
|
||||||
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.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lines = append(lines, text[from:to])
|
return substr
|
||||||
|
}
|
||||||
|
iterateString(strippedText, func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
|
||||||
|
// Handle colour tags.
|
||||||
|
if colorPos < len(colorTagIndices) && textPos+tagOffset >= colorTagIndices[colorPos][0] && textPos+tagOffset < colorTagIndices[colorPos][1] {
|
||||||
|
tagOffset += colorTagIndices[colorPos][1] - colorTagIndices[colorPos][0]
|
||||||
|
colorPos++
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine final breakpoints.
|
// Handle escape tags.
|
||||||
var start, lastEnd, newStart, breakPoint int
|
if escapePos < len(escapeIndices) && textPos+tagOffset == escapeIndices[escapePos][1]-2 {
|
||||||
for {
|
tagOffset++
|
||||||
// What's our candidate string?
|
escapePos++
|
||||||
var candidate string
|
|
||||||
if breakPoint < len(breakPoints) {
|
|
||||||
candidate = text[start:breakPoints[breakPoint][1]]
|
|
||||||
} else {
|
|
||||||
candidate = text[start:]
|
|
||||||
}
|
}
|
||||||
candidate = strings.TrimRightFunc(candidate, unicode.IsSpace)
|
|
||||||
|
|
||||||
if runewidth.StringWidth(candidate) >= width {
|
// Check if a break is warranted.
|
||||||
// We're past the available width.
|
afterContinuation := lastContinuation > 0 && textPos+tagOffset >= lastContinuation
|
||||||
if lastEnd > start {
|
noBreakpoint := lastContinuation == 0
|
||||||
// Use the previous candidate.
|
beyondWidth := lineWidth > 0 && lineWidth > width
|
||||||
addLine(start, lastEnd)
|
if beyondWidth && noBreakpoint {
|
||||||
start = newStart
|
// We need a hard break without a breakpoint.
|
||||||
} else {
|
lines = append(lines, unescape(text[currentLineStart:textPos+tagOffset], currentLineStart))
|
||||||
// We have no previous candidate. Make a hard break.
|
currentLineStart = textPos + tagOffset
|
||||||
var lineWidth int
|
lineWidth = continuationWidth
|
||||||
for index, ch := range text {
|
} else if afterContinuation && (beyondWidth || newlineBreakpoint) {
|
||||||
if index < start {
|
// Break at last breakpoint or at newline.
|
||||||
continue
|
lines = append(lines, unescape(text[currentLineStart:lastBreakpoint], currentLineStart))
|
||||||
|
currentLineStart = lastContinuation
|
||||||
|
lineWidth = continuationWidth
|
||||||
|
lastBreakpoint, lastContinuation, newlineBreakpoint = 0, 0, false
|
||||||
}
|
}
|
||||||
chWidth := runewidth.RuneWidth(ch)
|
|
||||||
if lineWidth > 0 && lineWidth+chWidth >= width {
|
// Is this a breakpoint?
|
||||||
addLine(start, index)
|
if breakpointPos < len(breakpoints) && textPos == breakpoints[breakpointPos][0] {
|
||||||
start = index
|
// Yes, it is. Set up breakpoint infos depending on its type.
|
||||||
break
|
lastBreakpoint = breakpoints[breakpointPos][0] + tagOffset
|
||||||
|
lastContinuation = breakpoints[breakpointPos][1] + tagOffset
|
||||||
|
newlineBreakpoint = main == '\n'
|
||||||
|
if breakpoints[breakpointPos][6] < 0 && !newlineBreakpoint {
|
||||||
|
lastBreakpoint++ // Don't skip punctuation.
|
||||||
}
|
}
|
||||||
lineWidth += chWidth
|
breakpointPos++
|
||||||
}
|
|
||||||
}
|
|
||||||
} 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++
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
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
|
// 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
|
// 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:
|
// 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 {
|
func Escape(text string) string {
|
||||||
return nonEscapePattern.ReplaceAllString(text, "$1[]")
|
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