aboutsummaryrefslogtreecommitdiff
path: root/vendor
diff options
context:
space:
mode:
Diffstat (limited to 'vendor')
-rw-r--r--vendor/github.com/davecgh/go-spew/LICENSE2
-rw-r--r--vendor/github.com/davecgh/go-spew/spew/bypass.go187
-rw-r--r--vendor/github.com/davecgh/go-spew/spew/bypasssafe.go2
-rw-r--r--vendor/github.com/davecgh/go-spew/spew/common.go2
-rw-r--r--vendor/github.com/davecgh/go-spew/spew/dump.go10
-rw-r--r--vendor/github.com/davecgh/go-spew/spew/format.go4
-rw-r--r--vendor/github.com/disintegration/imaging/effects.go36
-rw-r--r--vendor/github.com/disintegration/imaging/helpers.go272
-rw-r--r--vendor/github.com/disintegration/imaging/io.go463
-rw-r--r--vendor/github.com/disintegration/imaging/tools.go34
-rw-r--r--vendor/github.com/lucasb-eyer/go-colorful/README.md33
-rw-r--r--vendor/github.com/lucasb-eyer/go-colorful/colors.go7
-rw-r--r--vendor/github.com/mattn/go-runewidth/runewidth.go14
-rw-r--r--vendor/github.com/renstrom/fuzzysearch/LICENSE2
-rw-r--r--vendor/github.com/renstrom/fuzzysearch/fuzzy/fuzzy.go5
-rw-r--r--vendor/github.com/stretchr/testify/assert/assertion_format.go139
-rw-r--r--vendor/github.com/stretchr/testify/assert/assertion_format.go.tmpl1
-rw-r--r--vendor/github.com/stretchr/testify/assert/assertion_forward.go278
-rw-r--r--vendor/github.com/stretchr/testify/assert/assertion_forward.go.tmpl1
-rw-r--r--vendor/github.com/stretchr/testify/assert/assertions.go214
-rw-r--r--vendor/github.com/stretchr/testify/assert/http_assertions.go22
-rw-r--r--vendor/github.com/zyedidia/clipboard/LICENSE4
-rw-r--r--vendor/github.com/zyedidia/clipboard/README.md4
-rw-r--r--vendor/golang.org/x/image/bmp/reader.go30
-rw-r--r--vendor/golang.org/x/image/bmp/writer.go122
-rw-r--r--vendor/golang.org/x/image/tiff/reader.go2
-rw-r--r--vendor/golang.org/x/image/vp8/decode.go4
-rw-r--r--vendor/golang.org/x/image/webp/decode.go2
-rw-r--r--vendor/golang.org/x/image/webp/doc.go9
-rw-r--r--vendor/golang.org/x/image/webp/webp.go30
-rw-r--r--vendor/golang.org/x/net/html/const.go10
-rw-r--r--vendor/golang.org/x/net/html/parse.go132
-rw-r--r--vendor/gopkg.in/russross/blackfriday.v2/.travis.yml27
-rw-r--r--vendor/gopkg.in/russross/blackfriday.v2/README.md20
-rw-r--r--vendor/gopkg.in/russross/blackfriday.v2/block.go101
-rw-r--r--vendor/gopkg.in/russross/blackfriday.v2/go.mod1
-rw-r--r--vendor/gopkg.in/russross/blackfriday.v2/html.go21
-rw-r--r--vendor/gopkg.in/russross/blackfriday.v2/inline.go18
-rw-r--r--vendor/gopkg.in/russross/blackfriday.v2/markdown.go94
-rw-r--r--vendor/gopkg.in/toast.v1/toast.go5
-rw-r--r--vendor/maunium.net/go/gomatrix/.gitignore24
-rw-r--r--vendor/maunium.net/go/gomatrix/.travis.yml9
-rw-r--r--vendor/maunium.net/go/gomatrix/README.md6
-rw-r--r--vendor/maunium.net/go/gomatrix/events.go110
-rw-r--r--vendor/maunium.net/go/mautrix/.gitignore2
-rw-r--r--vendor/maunium.net/go/mautrix/LICENSE (renamed from vendor/maunium.net/go/gomatrix/LICENSE)0
-rw-r--r--vendor/maunium.net/go/mautrix/README.md4
-rw-r--r--vendor/maunium.net/go/mautrix/client.go (renamed from vendor/maunium.net/go/gomatrix/client.go)159
-rw-r--r--vendor/maunium.net/go/mautrix/events.go448
-rw-r--r--vendor/maunium.net/go/mautrix/filter.go (renamed from vendor/maunium.net/go/gomatrix/filter.go)2
-rw-r--r--vendor/maunium.net/go/mautrix/reply.go97
-rw-r--r--vendor/maunium.net/go/mautrix/requests.go (renamed from vendor/maunium.net/go/gomatrix/requests.go)10
-rw-r--r--vendor/maunium.net/go/mautrix/responses.go (renamed from vendor/maunium.net/go/gomatrix/responses.go)20
-rw-r--r--vendor/maunium.net/go/mautrix/room.go (renamed from vendor/maunium.net/go/gomatrix/room.go)22
-rw-r--r--vendor/maunium.net/go/mautrix/store.go (renamed from vendor/maunium.net/go/gomatrix/store.go)2
-rw-r--r--vendor/maunium.net/go/mautrix/sync.go (renamed from vendor/maunium.net/go/gomatrix/sync.go)17
-rw-r--r--vendor/maunium.net/go/mautrix/userids.go (renamed from vendor/maunium.net/go/gomatrix/userids.go)4
-rw-r--r--vendor/maunium.net/go/tcell/README.adoc270
-rw-r--r--vendor/maunium.net/go/tcell/cell.go7
-rw-r--r--vendor/maunium.net/go/tcell/console_win.go1
-rw-r--r--vendor/maunium.net/go/tcell/tcell.pngbin5336 -> 0 bytes
-rw-r--r--vendor/maunium.net/go/tcell/tcell.svg93
-rw-r--r--vendor/maunium.net/go/tcell/terminfo/term_termite.go152
-rw-r--r--vendor/maunium.net/go/tcell/tscreen.go6
-rw-r--r--vendor/maunium.net/go/tview/LICENSE.txt2
-rw-r--r--vendor/maunium.net/go/tview/README.md11
-rw-r--r--vendor/maunium.net/go/tview/ansi.go (renamed from vendor/maunium.net/go/tview/ansii.go)64
-rw-r--r--vendor/maunium.net/go/tview/application.go376
-rw-r--r--vendor/maunium.net/go/tview/borders.go45
-rw-r--r--vendor/maunium.net/go/tview/box.go88
-rw-r--r--vendor/maunium.net/go/tview/checkbox.go6
-rw-r--r--vendor/maunium.net/go/tview/doc.go31
-rw-r--r--vendor/maunium.net/go/tview/dropdown.go5
-rw-r--r--vendor/maunium.net/go/tview/flex.go17
-rw-r--r--vendor/maunium.net/go/tview/form.go53
-rw-r--r--vendor/maunium.net/go/tview/grid.go16
-rw-r--r--vendor/maunium.net/go/tview/inputfield.go205
-rw-r--r--vendor/maunium.net/go/tview/list.go15
-rw-r--r--vendor/maunium.net/go/tview/modal.go15
-rw-r--r--vendor/maunium.net/go/tview/semigraphics.go296
-rw-r--r--vendor/maunium.net/go/tview/table.go119
-rw-r--r--vendor/maunium.net/go/tview/textview.go266
-rw-r--r--vendor/maunium.net/go/tview/treeview.go684
-rw-r--r--vendor/maunium.net/go/tview/util.go658
84 files changed, 5055 insertions, 1746 deletions
diff --git a/vendor/github.com/davecgh/go-spew/LICENSE b/vendor/github.com/davecgh/go-spew/LICENSE
index c836416..bc52e96 100644
--- a/vendor/github.com/davecgh/go-spew/LICENSE
+++ b/vendor/github.com/davecgh/go-spew/LICENSE
@@ -2,7 +2,7 @@ ISC License
Copyright (c) 2012-2016 Dave Collins <dave@davec.name>
-Permission to use, copy, modify, and distribute this software for any
+Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
diff --git a/vendor/github.com/davecgh/go-spew/spew/bypass.go b/vendor/github.com/davecgh/go-spew/spew/bypass.go
index 8a4a658..7929947 100644
--- a/vendor/github.com/davecgh/go-spew/spew/bypass.go
+++ b/vendor/github.com/davecgh/go-spew/spew/bypass.go
@@ -16,7 +16,9 @@
// when the code is not running on Google App Engine, compiled by GopherJS, and
// "-tags safe" is not added to the go build command line. The "disableunsafe"
// tag is deprecated and thus should not be used.
-// +build !js,!appengine,!safe,!disableunsafe
+// Go versions prior to 1.4 are disabled because they use a different layout
+// for interfaces which make the implementation of unsafeReflectValue more complex.
+// +build !js,!appengine,!safe,!disableunsafe,go1.4
package spew
@@ -34,80 +36,49 @@ const (
ptrSize = unsafe.Sizeof((*byte)(nil))
)
+type flag uintptr
+
var (
- // offsetPtr, offsetScalar, and offsetFlag are the offsets for the
- // internal reflect.Value fields. These values are valid before golang
- // commit ecccf07e7f9d which changed the format. The are also valid
- // after commit 82f48826c6c7 which changed the format again to mirror
- // the original format. Code in the init function updates these offsets
- // as necessary.
- offsetPtr = uintptr(ptrSize)
- offsetScalar = uintptr(0)
- offsetFlag = uintptr(ptrSize * 2)
-
- // flagKindWidth and flagKindShift indicate various bits that the
- // reflect package uses internally to track kind information.
- //
- // flagRO indicates whether or not the value field of a reflect.Value is
- // read-only.
- //
- // flagIndir indicates whether the value field of a reflect.Value is
- // the actual data or a pointer to the data.
- //
- // These values are valid before golang commit 90a7c3c86944 which
- // changed their positions. Code in the init function updates these
- // flags as necessary.
- flagKindWidth = uintptr(5)
- flagKindShift = uintptr(flagKindWidth - 1)
- flagRO = uintptr(1 << 0)
- flagIndir = uintptr(1 << 1)
+ // flagRO indicates whether the value field of a reflect.Value
+ // is read-only.
+ flagRO flag
+
+ // flagAddr indicates whether the address of the reflect.Value's
+ // value may be taken.
+ flagAddr flag
)
-func init() {
- // Older versions of reflect.Value stored small integers directly in the
- // ptr field (which is named val in the older versions). Versions
- // between commits ecccf07e7f9d and 82f48826c6c7 added a new field named
- // scalar for this purpose which unfortunately came before the flag
- // field, so the offset of the flag field is different for those
- // versions.
- //
- // This code constructs a new reflect.Value from a known small integer
- // and checks if the size of the reflect.Value struct indicates it has
- // the scalar field. When it does, the offsets are updated accordingly.
- vv := reflect.ValueOf(0xf00)
- if unsafe.Sizeof(vv) == (ptrSize * 4) {
- offsetScalar = ptrSize * 2
- offsetFlag = ptrSize * 3
- }
+// flagKindMask holds the bits that make up the kind
+// part of the flags field. In all the supported versions,
+// it is in the lower 5 bits.
+const flagKindMask = flag(0x1f)
- // Commit 90a7c3c86944 changed the flag positions such that the low
- // order bits are the kind. This code extracts the kind from the flags
- // field and ensures it's the correct type. When it's not, the flag
- // order has been changed to the newer format, so the flags are updated
- // accordingly.
- upf := unsafe.Pointer(uintptr(unsafe.Pointer(&vv)) + offsetFlag)
- upfv := *(*uintptr)(upf)
- flagKindMask := uintptr((1<<flagKindWidth - 1) << flagKindShift)
- if (upfv&flagKindMask)>>flagKindShift != uintptr(reflect.Int) {
- flagKindShift = 0
- flagRO = 1 << 5
- flagIndir = 1 << 6
-
- // Commit adf9b30e5594 modified the flags to separate the
- // flagRO flag into two bits which specifies whether or not the
- // field is embedded. This causes flagIndir to move over a bit
- // and means that flagRO is the combination of either of the
- // original flagRO bit and the new bit.
- //
- // This code detects the change by extracting what used to be
- // the indirect bit to ensure it's set. When it's not, the flag
- // order has been changed to the newer format, so the flags are
- // updated accordingly.
- if upfv&flagIndir == 0 {
- flagRO = 3 << 5
- flagIndir = 1 << 7
- }
+// Different versions of Go have used different
+// bit layouts for the flags type. This table
+// records the known combinations.
+var okFlags = []struct {
+ ro, addr flag
+}{{
+ // From Go 1.4 to 1.5
+ ro: 1 << 5,
+ addr: 1 << 7,
+}, {
+ // Up to Go tip.
+ ro: 1<<5 | 1<<6,
+ addr: 1 << 8,
+}}
+
+var flagValOffset = func() uintptr {
+ field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
+ if !ok {
+ panic("reflect.Value has no flag field")
}
+ return field.Offset
+}()
+
+// flagField returns a pointer to the flag field of a reflect.Value.
+func flagField(v *reflect.Value) *flag {
+ return (*flag)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + flagValOffset))
}
// unsafeReflectValue converts the passed reflect.Value into a one that bypasses
@@ -119,34 +90,56 @@ func init() {
// This allows us to check for implementations of the Stringer and error
// interfaces to be used for pretty printing ordinarily unaddressable and
// inaccessible values such as unexported struct fields.
-func unsafeReflectValue(v reflect.Value) (rv reflect.Value) {
- indirects := 1
- vt := v.Type()
- upv := unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetPtr)
- rvf := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetFlag))
- if rvf&flagIndir != 0 {
- vt = reflect.PtrTo(v.Type())
- indirects++
- } else if offsetScalar != 0 {
- // The value is in the scalar field when it's not one of the
- // reference types.
- switch vt.Kind() {
- case reflect.Uintptr:
- case reflect.Chan:
- case reflect.Func:
- case reflect.Map:
- case reflect.Ptr:
- case reflect.UnsafePointer:
- default:
- upv = unsafe.Pointer(uintptr(unsafe.Pointer(&v)) +
- offsetScalar)
- }
+func unsafeReflectValue(v reflect.Value) reflect.Value {
+ if !v.IsValid() || (v.CanInterface() && v.CanAddr()) {
+ return v
}
+ flagFieldPtr := flagField(&v)
+ *flagFieldPtr &^= flagRO
+ *flagFieldPtr |= flagAddr
+ return v
+}
- pv := reflect.NewAt(vt, upv)
- rv = pv
- for i := 0; i < indirects; i++ {
- rv = rv.Elem()
+// Sanity checks against future reflect package changes
+// to the type or semantics of the Value.flag field.
+func init() {
+ field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
+ if !ok {
+ panic("reflect.Value has no flag field")
+ }
+ if field.Type.Kind() != reflect.TypeOf(flag(0)).Kind() {
+ panic("reflect.Value flag field has changed kind")
+ }
+ type t0 int
+ var t struct {
+ A t0
+ // t0 will have flagEmbedRO set.
+ t0
+ // a will have flagStickyRO set
+ a t0
+ }
+ vA := reflect.ValueOf(t).FieldByName("A")
+ va := reflect.ValueOf(t).FieldByName("a")
+ vt0 := reflect.ValueOf(t).FieldByName("t0")
+
+ // Infer flagRO from the difference between the flags
+ // for the (otherwise identical) fields in t.
+ flagPublic := *flagField(&vA)
+ flagWithRO := *flagField(&va) | *flagField(&vt0)
+ flagRO = flagPublic ^ flagWithRO
+
+ // Infer flagAddr from the difference between a value
+ // taken from a pointer and not.
+ vPtrA := reflect.ValueOf(&t).Elem().FieldByName("A")
+ flagNoPtr := *flagField(&vA)
+ flagPtr := *flagField(&vPtrA)
+ flagAddr = flagNoPtr ^ flagPtr
+
+ // Check that the inferred flags tally with one of the known versions.
+ for _, f := range okFlags {
+ if flagRO == f.ro && flagAddr == f.addr {
+ return
+ }
}
- return rv
+ panic("reflect.Value read-only flag has changed semantics")
}
diff --git a/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go b/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go
index 1fe3cf3..205c28d 100644
--- a/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go
+++ b/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go
@@ -16,7 +16,7 @@
// when the code is running on Google App Engine, compiled by GopherJS, or
// "-tags safe" is added to the go build command line. The "disableunsafe"
// tag is deprecated and thus should not be used.
-// +build js appengine safe disableunsafe
+// +build js appengine safe disableunsafe !go1.4
package spew
diff --git a/vendor/github.com/davecgh/go-spew/spew/common.go b/vendor/github.com/davecgh/go-spew/spew/common.go
index 7c519ff..1be8ce9 100644
--- a/vendor/github.com/davecgh/go-spew/spew/common.go
+++ b/vendor/github.com/davecgh/go-spew/spew/common.go
@@ -180,7 +180,7 @@ func printComplex(w io.Writer, c complex128, floatPrecision int) {
w.Write(closeParenBytes)
}
-// printHexPtr outputs a uintptr formatted as hexidecimal with a leading '0x'
+// printHexPtr outputs a uintptr formatted as hexadecimal with a leading '0x'
// prefix to Writer w.
func printHexPtr(w io.Writer, p uintptr) {
// Null pointer.
diff --git a/vendor/github.com/davecgh/go-spew/spew/dump.go b/vendor/github.com/davecgh/go-spew/spew/dump.go
index df1d582..f78d89f 100644
--- a/vendor/github.com/davecgh/go-spew/spew/dump.go
+++ b/vendor/github.com/davecgh/go-spew/spew/dump.go
@@ -35,16 +35,16 @@ var (
// cCharRE is a regular expression that matches a cgo char.
// It is used to detect character arrays to hexdump them.
- cCharRE = regexp.MustCompile("^.*\\._Ctype_char$")
+ cCharRE = regexp.MustCompile(`^.*\._Ctype_char$`)
// cUnsignedCharRE is a regular expression that matches a cgo unsigned
// char. It is used to detect unsigned character arrays to hexdump
// them.
- cUnsignedCharRE = regexp.MustCompile("^.*\\._Ctype_unsignedchar$")
+ cUnsignedCharRE = regexp.MustCompile(`^.*\._Ctype_unsignedchar$`)
// cUint8tCharRE is a regular expression that matches a cgo uint8_t.
// It is used to detect uint8_t arrays to hexdump them.
- cUint8tCharRE = regexp.MustCompile("^.*\\._Ctype_uint8_t$")
+ cUint8tCharRE = regexp.MustCompile(`^.*\._Ctype_uint8_t$`)
)
// dumpState contains information about the state of a dump operation.
@@ -143,10 +143,10 @@ func (d *dumpState) dumpPtr(v reflect.Value) {
// Display dereferenced value.
d.w.Write(openParenBytes)
switch {
- case nilFound == true:
+ case nilFound:
d.w.Write(nilAngleBytes)
- case cycleFound == true:
+ case cycleFound:
d.w.Write(circularBytes)
default:
diff --git a/vendor/github.com/davecgh/go-spew/spew/format.go b/vendor/github.com/davecgh/go-spew/spew/format.go
index c49875b..b04edb7 100644
--- a/vendor/github.com/davecgh/go-spew/spew/format.go
+++ b/vendor/github.com/davecgh/go-spew/spew/format.go
@@ -182,10 +182,10 @@ func (f *formatState) formatPtr(v reflect.Value) {
// Display dereferenced value.
switch {
- case nilFound == true:
+ case nilFound:
f.fs.Write(nilAngleBytes)
- case cycleFound == true:
+ case cycleFound:
f.fs.Write(circularShortBytes)
default:
diff --git a/vendor/github.com/disintegration/imaging/effects.go b/vendor/github.com/disintegration/imaging/effects.go
index b16781f..149cfeb 100644
--- a/vendor/github.com/disintegration/imaging/effects.go
+++ b/vendor/github.com/disintegration/imaging/effects.go
@@ -38,9 +38,13 @@ func blurHorizontal(img image.Image, kernel []float64) *image.NRGBA {
parallel(0, src.h, func(ys <-chan int) {
scanLine := make([]uint8, src.w*4)
+ scanLineF := make([]float64, len(scanLine))
for y := range ys {
src.scan(0, y, src.w, y+1, scanLine)
- for x := 0; x < src.w; x++ {
+ for i, v := range scanLine {
+ scanLineF[i] = float64(v)
+ }
+ for x, idx := 0, 0; x < src.w; x, idx = x+1, idx+4 {
min := x - radius
if min < 0 {
min = 0
@@ -55,10 +59,10 @@ func blurHorizontal(img image.Image, kernel []float64) *image.NRGBA {
i := ix * 4
weight := kernel[absint(x-ix)]
wsum += weight
- wa := float64(scanLine[i+3]) * weight
- r += float64(scanLine[i+0]) * wa
- g += float64(scanLine[i+1]) * wa
- b += float64(scanLine[i+2]) * wa
+ wa := scanLineF[i+3] * weight
+ r += scanLineF[i+0] * wa
+ g += scanLineF[i+1] * wa
+ b += scanLineF[i+2] * wa
a += wa
}
if a != 0 {
@@ -67,12 +71,12 @@ func blurHorizontal(img image.Image, kernel []float64) *image.NRGBA {
b /= a
}
- j := y*dst.Stride + x*4
- dst.Pix[j+0] = clamp(r)
- dst.Pix[j+1] = clamp(g)
- dst.Pix[j+2] = clamp(b)
- dst.Pix[j+3] = clamp(a / wsum)
+ scanLine[idx+0] = clamp(r)
+ scanLine[idx+1] = clamp(g)
+ scanLine[idx+2] = clamp(b)
+ scanLine[idx+3] = clamp(a / wsum)
}
+ copy(dst.Pix[y*dst.Stride:], scanLine)
}
})
@@ -86,8 +90,12 @@ func blurVertical(img image.Image, kernel []float64) *image.NRGBA {
parallel(0, src.w, func(xs <-chan int) {
scanLine := make([]uint8, src.h*4)
+ scanLineF := make([]float64, len(scanLine))
for x := range xs {
src.scan(x, 0, x+1, src.h, scanLine)
+ for i, v := range scanLine {
+ scanLineF[i] = float64(v)
+ }
for y := 0; y < src.h; y++ {
min := y - radius
if min < 0 {
@@ -103,10 +111,10 @@ func blurVertical(img image.Image, kernel []float64) *image.NRGBA {
i := iy * 4
weight := kernel[absint(y-iy)]
wsum += weight
- wa := float64(scanLine[i+3]) * weight
- r += float64(scanLine[i+0]) * wa
- g += float64(scanLine[i+1]) * wa
- b += float64(scanLine[i+2]) * wa
+ wa := scanLineF[i+3] * weight
+ r += scanLineF[i+0] * wa
+ g += scanLineF[i+1] * wa
+ b += scanLineF[i+2] * wa
a += wa
}
if a != 0 {
diff --git a/vendor/github.com/disintegration/imaging/helpers.go b/vendor/github.com/disintegration/imaging/helpers.go
deleted file mode 100644
index dcb4d7e..0000000
--- a/vendor/github.com/disintegration/imaging/helpers.go
+++ /dev/null
@@ -1,272 +0,0 @@
-package imaging
-
-import (
- "bytes"
- "errors"
- "image"
- "image/color"
- "image/draw"
- "image/gif"
- "image/jpeg"
- "image/png"
- "io"
- "os"
- "path/filepath"
- "strings"
-
- "golang.org/x/image/bmp"
- "golang.org/x/image/tiff"
-)
-
-// Format is an image file format.
-type Format int
-
-// Image file formats.
-const (
- JPEG Format = iota
- PNG
- GIF
- TIFF
- BMP
-)
-
-func (f Format) String() string {
- switch f {
- case JPEG:
- return "JPEG"
- case PNG:
- return "PNG"
- case GIF:
- return "GIF"
- case TIFF:
- return "TIFF"
- case BMP:
- return "BMP"
- default:
- return "Unsupported"
- }
-}
-
-var formatFromExt = map[string]Format{
- ".jpg": JPEG,
- ".jpeg": JPEG,
- ".png": PNG,
- ".tif": TIFF,
- ".tiff": TIFF,
- ".bmp": BMP,
- ".gif": GIF,
-}
-
-// FormatFromFilename parses image format from filename extension:
-// "jpg" (or "jpeg"), "png", "gif", "tif" (or "tiff") and "bmp" are supported.
-func FormatFromFilename(filename string) (Format, error) {
- ext := strings.ToLower(filepath.Ext(filename))
- if f, ok := formatFromExt[ext]; ok {
- return f, nil
- }
- return -1, ErrUnsupportedFormat
-}
-
-var (
- // ErrUnsupportedFormat means the given image format (or file extension) is unsupported.
- ErrUnsupportedFormat = errors.New("imaging: unsupported image format")
-)
-
-type fileSystem interface {
- Create(string) (io.WriteCloser, error)
- Open(string) (io.ReadCloser, error)
-}
-
-type localFS struct{}
-
-func (localFS) Create(name string) (io.WriteCloser, error) { return os.Create(name) }
-func (localFS) Open(name string) (io.ReadCloser, error) { return os.Open(name) }
-
-var fs fileSystem = localFS{}
-
-// Decode reads an image from r.
-func Decode(r io.Reader) (image.Image, error) {
- img, _, err := image.Decode(r)
- return img, err
-}
-
-// Open loads an image from file
-func Open(filename string) (image.Image, error) {
- file, err := fs.Open(filename)
- if err != nil {
- return nil, err
- }
- defer file.Close()
- return Decode(file)
-}
-
-type encodeConfig struct {
- jpegQuality int
- gifNumColors int
- gifQuantizer draw.Quantizer
- gifDrawer draw.Drawer
- pngCompressionLevel png.CompressionLevel
-}
-
-var defaultEncodeConfig = encodeConfig{
- jpegQuality: 95,
- gifNumColors: 256,
- gifQuantizer: nil,
- gifDrawer: nil,
- pngCompressionLevel: png.DefaultCompression,
-}
-
-// EncodeOption sets an optional parameter for the Encode and Save functions.
-type EncodeOption func(*encodeConfig)
-
-// JPEGQuality returns an EncodeOption that sets the output JPEG quality.
-// Quality ranges from 1 to 100 inclusive, higher is better. Default is 95.
-func JPEGQuality(quality int) EncodeOption {
- return func(c *encodeConfig) {
- c.jpegQuality = quality
- }
-}
-
-// GIFNumColors returns an EncodeOption that sets the maximum number of colors
-// used in the GIF-encoded image. It ranges from 1 to 256. Default is 256.
-func GIFNumColors(numColors int) EncodeOption {
- return func(c *encodeConfig) {
- c.gifNumColors = numColors
- }
-}
-
-// GIFQuantizer returns an EncodeOption that sets the quantizer that is used to produce
-// a palette of the GIF-encoded image.
-func GIFQuantizer(quantizer draw.Quantizer) EncodeOption {
- return func(c *encodeConfig) {
- c.gifQuantizer = quantizer
- }
-}
-
-// GIFDrawer returns an EncodeOption that sets the drawer that is used to convert
-// the source image to the desired palette of the GIF-encoded image.
-func GIFDrawer(drawer draw.Drawer) EncodeOption {
- return func(c *encodeConfig) {
- c.gifDrawer = drawer
- }
-}
-
-// PNGCompressionLevel returns an EncodeOption that sets the compression level
-// of the PNG-encoded image. Default is png.DefaultCompression.
-func PNGCompressionLevel(level png.CompressionLevel) EncodeOption {
- return func(c *encodeConfig) {
- c.pngCompressionLevel = level
- }
-}
-
-// Encode writes the image img to w in the specified format (JPEG, PNG, GIF, TIFF or BMP).
-func Encode(w io.Writer, img image.Image, format Format, opts ...EncodeOption) error {
- cfg := defaultEncodeConfig
- for _, option := range opts {
- option(&cfg)
- }
-
- var err error
- switch format {
- case JPEG:
- var rgba *image.RGBA
- if nrgba, ok := img.(*image.NRGBA); ok {
- if nrgba.Opaque() {
- rgba = &image.RGBA{
- Pix: nrgba.Pix,
- Stride: nrgba.Stride,
- Rect: nrgba.Rect,
- }
- }
- }
- if rgba != nil {
- err = jpeg.Encode(w, rgba, &jpeg.Options{Quality: cfg.jpegQuality})
- } else {
- err = jpeg.Encode(w, img, &jpeg.Options{Quality: cfg.jpegQuality})
- }
-
- case PNG:
- enc := png.Encoder{CompressionLevel: cfg.pngCompressionLevel}
- err = enc.Encode(w, img)
-
- case GIF:
- err = gif.Encode(w, img, &gif.Options{
- NumColors: cfg.gifNumColors,
- Quantizer: cfg.gifQuantizer,
- Drawer: cfg.gifDrawer,
- })
-
- case TIFF:
- err = tiff.Encode(w, img, &tiff.Options{Compression: tiff.Deflate, Predictor: true})
-
- case BMP:
- err = bmp.Encode(w, img)
-
- default:
- err = ErrUnsupportedFormat
- }
- return err
-}
-
-// Save saves the image to file with the specified filename.
-// The format is determined from the filename extension: "jpg" (or "jpeg"), "png", "gif", "tif" (or "tiff") and "bmp" are supported.
-//
-// Examples:
-//
-// // Save the image as PNG.
-// err := imaging.Save(img, "out.png")
-//
-// // Save the image as JPEG with optional quality parameter set to 80.
-// err := imaging.Save(img, "out.jpg", imaging.JPEGQuality(80))
-//
-func Save(img image.Image, filename string, opts ...EncodeOption) (err error) {
- f, err := FormatFromFilename(filename)
- if err != nil {
- return err
- }
- file, err := fs.Create(filename)
- if err != nil {
- return err
- }
-
- defer func() {
- cerr := file.Close()
- if err == nil {
- err = cerr
- }
- }()
-
- return Encode(file, img, f, opts...)
-}
-
-// New creates a new image with the specified width and height, and fills it with the specified color.
-func New(width, height int, fillColor color.Color) *image.NRGBA {
- if width <= 0 || height <= 0 {
- return &image.NRGBA{}
- }
-
- c := color.NRGBAModel.Convert(fillColor).(color.NRGBA)
- if (c == color.NRGBA{0, 0, 0, 0}) {
- return image.NewNRGBA(image.Rect(0, 0, width, height))
- }
-
- return &image.NRGBA{
- Pix: bytes.Repeat([]byte{c.R, c.G, c.B, c.A}, width*height),
- Stride: 4 * width,
- Rect: image.Rect(0, 0, width, height),
- }
-}
-
-// Clone returns a copy of the given image.
-func Clone(img image.Image) *image.NRGBA {
- src := newScanner(img)
- dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
- size := src.w * 4
- parallel(0, src.h, func(ys <-chan int) {
- for y := range ys {
- i := y * dst.Stride
- src.scan(0, y, src.w, y+1, dst.Pix[i:i+size])
- }
- })
- return dst
-}
diff --git a/vendor/github.com/disintegration/imaging/io.go b/vendor/github.com/disintegration/imaging/io.go
new file mode 100644
index 0000000..557bf2f
--- /dev/null
+++ b/vendor/github.com/disintegration/imaging/io.go
@@ -0,0 +1,463 @@
+package imaging
+
+import (
+ "encoding/binary"
+ "errors"
+ "image"
+ "image/draw"
+ "image/gif"
+ "image/jpeg"
+ "image/png"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "golang.org/x/image/bmp"
+ "golang.org/x/image/tiff"
+)
+
+// Format is an image file format.
+type Format int
+
+// Image file formats.
+const (
+ JPEG Format = iota
+ PNG
+ GIF
+ TIFF
+ BMP
+)
+
+func (f Format) String() string {
+ switch f {
+ case JPEG:
+ return "JPEG"
+ case PNG:
+ return "PNG"
+ case GIF:
+ return "GIF"
+ case TIFF:
+ return "TIFF"
+ case BMP:
+ return "BMP"
+ default:
+ return "Unsupported"
+ }
+}
+
+var formatFromExt = map[string]Format{
+ "jpg": JPEG,
+ "jpeg": JPEG,
+ "png": PNG,
+ "tif": TIFF,
+ "tiff": TIFF,
+ "bmp": BMP,
+ "gif": GIF,
+}
+
+// FormatFromExtension parses image format from extension:
+// "jpg" (or "jpeg"), "png", "gif", "tif" (or "tiff") and "bmp" are supported.
+func FormatFromExtension(ext string) (Format, error) {
+ if f, ok := formatFromExt[strings.ToLower(strings.TrimPrefix(ext, "."))]; ok {
+ return f, nil
+ }
+ return -1, ErrUnsupportedFormat
+}
+
+// FormatFromFilename parses image format from filename extension:
+// "jpg" (or "jpeg"), "png", "gif", "tif" (or "tiff") and "bmp" are supported.
+func FormatFromFilename(filename string) (Format, error) {
+ ext := filepath.Ext(filename)
+ return FormatFromExtension(ext)
+}
+
+var (
+ // ErrUnsupportedFormat means the given image format (or file extension) is unsupported.
+ ErrUnsupportedFormat = errors.New("imaging: unsupported image format")
+)
+
+type fileSystem interface {
+ Create(string) (io.WriteCloser, error)
+ Open(string) (io.ReadCloser, error)
+}
+
+type localFS struct{}
+
+func (localFS) Create(name string) (io.WriteCloser, error) { return os.Create(name) }
+func (localFS) Open(name string) (io.ReadCloser, error) { return os.Open(name) }
+
+var fs fileSystem = localFS{}
+
+type decodeConfig struct {
+ autoOrientation bool
+}
+
+var defaultDecodeConfig = decodeConfig{
+ autoOrientation: false,
+}
+
+// DecodeOption sets an optional parameter for the Decode and Open functions.
+type DecodeOption func(*decodeConfig)
+
+// AutoOrientation returns a DecodeOption that sets the auto-orientation mode.
+// If auto-orientation is enabled, the image will be transformed after decoding
+// according to the EXIF orientation tag (if present). By default it's disabled.
+func AutoOrientation(enabled bool) DecodeOption {
+ return func(c *decodeConfig) {
+ c.autoOrientation = enabled
+ }
+}
+
+// Decode reads an image from r.
+func Decode(r io.Reader, opts ...DecodeOption) (image.Image, error) {
+ cfg := defaultDecodeConfig
+ for _, option := range opts {
+ option(&cfg)
+ }
+
+ if !cfg.autoOrientation {
+ img, _, err := image.Decode(r)
+ return img, err
+ }
+
+ var orient orientation
+ pr, pw := io.Pipe()
+ r = io.TeeReader(r, pw)
+ done := make(chan struct{})
+ go func() {
+ defer close(done)
+ orient = readOrientation(pr)
+ io.Copy(ioutil.Discard, pr)
+ }()
+
+ img, _, err := image.Decode(r)
+ pw.Close()
+ <-done
+ if err != nil {
+ return nil, err
+ }
+
+ return fixOrientation(img, orient), nil
+}
+
+// Open loads an image from file.
+//
+// Examples:
+//
+// // Load an image from file.
+// img, err := imaging.Open("test.jpg")
+//
+// // Load an image and transform it depending on the EXIF orientation tag (if present).
+// img, err := imaging.Open("test.jpg", imaging.AutoOrientation(true))
+//
+func Open(filename string, opts ...DecodeOption) (image.Image, error) {
+ file, err := fs.Open(filename)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+ return Decode(file, opts...)
+}
+
+type encodeConfig struct {
+ jpegQuality int
+ gifNumColors int
+ gifQuantizer draw.Quantizer
+ gifDrawer draw.Drawer
+ pngCompressionLevel png.CompressionLevel
+}
+
+var defaultEncodeConfig = encodeConfig{
+ jpegQuality: 95,
+ gifNumColors: 256,
+ gifQuantizer: nil,
+ gifDrawer: nil,
+ pngCompressionLevel: png.DefaultCompression,
+}
+
+// EncodeOption sets an optional parameter for the Encode and Save functions.
+type EncodeOption func(*encodeConfig)
+
+// JPEGQuality returns an EncodeOption that sets the output JPEG quality.
+// Quality ranges from 1 to 100 inclusive, higher is better. Default is 95.
+func JPEGQuality(quality int) EncodeOption {
+ return func(c *encodeConfig) {
+ c.jpegQuality = quality
+ }
+}
+
+// GIFNumColors returns an EncodeOption that sets the maximum number of colors
+// used in the GIF-encoded image. It ranges from 1 to 256. Default is 256.
+func GIFNumColors(numColors int) EncodeOption {
+ return func(c *encodeConfig) {
+ c.gifNumColors = numColors
+ }
+}
+
+// GIFQuantizer returns an EncodeOption that sets the quantizer that is used to produce
+// a palette of the GIF-encoded image.
+func GIFQuantizer(quantizer draw.Quantizer) EncodeOption {
+ return func(c *encodeConfig) {
+ c.gifQuantizer = quantizer
+ }
+}
+
+// GIFDrawer returns an EncodeOption that sets the drawer that is used to convert
+// the source image to the desired palette of the GIF-encoded image.
+func GIFDrawer(drawer draw.Drawer) EncodeOption {
+ return func(c *encodeConfig) {
+ c.gifDrawer = drawer
+ }
+}
+
+// PNGCompressionLevel returns an EncodeOption that sets the compression level
+// of the PNG-encoded image. Default is png.DefaultCompression.
+func PNGCompressionLevel(level png.CompressionLevel) EncodeOption {
+ return func(c *encodeConfig) {
+ c.pngCompressionLevel = level
+ }
+}
+
+// Encode writes the image img to w in the specified format (JPEG, PNG, GIF, TIFF or BMP).
+func Encode(w io.Writer, img image.Image, format Format, opts ...EncodeOption) error {
+ cfg := defaultEncodeConfig
+ for _, option := range opts {
+ option(&cfg)
+ }
+
+ var err error
+ switch format {
+ case JPEG:
+ var rgba *image.RGBA
+ if nrgba, ok := img.(*image.NRGBA); ok {
+ if nrgba.Opaque() {
+ rgba = &image.RGBA{
+ Pix: nrgba.Pix,
+ Stride: nrgba.Stride,
+ Rect: nrgba.Rect,
+ }
+ }
+ }
+ if rgba != nil {
+ err = jpeg.Encode(w, rgba, &jpeg.Options{Quality: cfg.jpegQuality})
+ } else {
+ err = jpeg.Encode(w, img, &jpeg.Options{Quality: cfg.jpegQuality})
+ }
+
+ case PNG:
+ enc := png.Encoder{CompressionLevel: cfg.pngCompressionLevel}
+ err = enc.Encode(w, img)
+
+ case GIF:
+ err = gif.Encode(w, img, &gif.Options{
+ NumColors: cfg.gifNumColors,
+ Quantizer: cfg.gifQuantizer,
+ Drawer: cfg.gifDrawer,
+ })
+
+ case TIFF:
+ err = tiff.Encode(w, img, &tiff.Options{Compression: tiff.Deflate, Predictor: true})
+
+ case BMP:
+ err = bmp.Encode(w, img)
+
+ default:
+ err = ErrUnsupportedFormat
+ }
+ return err
+}
+
+// Save saves the image to file with the specified filename.
+// The format is determined from the filename extension:
+// "jpg" (or "jpeg"), "png", "gif", "tif" (or "tiff") and "bmp" are supported.
+//
+// Examples:
+//
+// // Save the image as PNG.
+// err := imaging.Save(img, "out.png")
+//
+// // Save the image as JPEG with optional quality parameter set to 80.
+// err := imaging.Save(img, "out.jpg", imaging.JPEGQuality(80))
+//
+func Save(img image.Image, filename string, opts ...EncodeOption) (err error) {
+ f, err := FormatFromFilename(filename)
+ if err != nil {
+ return err
+ }
+ file, err := fs.Create(filename)
+ if err != nil {
+ return err
+ }
+
+ defer func() {
+ cerr := file.Close()
+ if err == nil {
+ err = cerr
+ }
+ }()
+
+ return Encode(file, img, f, opts...)
+}
+
+// orientation is an EXIF flag that specifies the transformation
+// that should be applied to image to display it correctly.
+type orientation int
+
+const (
+ orientationUnspecified = 0
+ orientationNormal = 1
+ orientationFlipH = 2
+ orientationRotate180 = 3
+ orientationFlipV = 4
+ orientationTranspose = 5
+ orientationRotate270 = 6
+ orientationTransverse = 7
+ orientationRotate90 = 8
+)
+
+// readOrientation tries to read the orientation EXIF flag from image data in r.
+// If the EXIF data block is not found or the orientation flag is not found
+// or any other error occures while reading the data, it returns the
+// orientationUnspecified (0) value.
+func readOrientation(r io.Reader) orientation {
+ const (
+ markerSOI = 0xffd8
+ markerAPP1 = 0xffe1
+ exifHeader = 0x45786966
+ byteOrderBE = 0x4d4d
+ byteOrderLE = 0x4949
+ orientationTag = 0x0112
+ )
+
+ // Check if JPEG SOI marker is present.
+ var soi uint16
+ if err := binary.Read(r, binary.BigEndian, &soi); err != nil {
+ return orientationUnspecified
+ }
+ if soi != markerSOI {
+ return orientationUnspecified // Missing JPEG SOI marker.
+ }
+
+ // Find JPEG APP1 marker.
+ for {
+ var marker, size uint16
+ if err := binary.Read(r, binary.BigEndian, &marker); err != nil {
+ return orientationUnspecified
+ }
+ if err := binary.Read(r, binary.BigEndian, &size); err != nil {
+ return orientationUnspecified
+ }
+ if marker>>8 != 0xff {
+ return orientationUnspecified // Invalid JPEG marker.
+ }
+ if marker == markerAPP1 {
+ break
+ }
+ if size < 2 {
+ return orientationUnspecified // Invalid block size.
+ }
+ if _, err := io.CopyN(ioutil.Discard, r, int64(size-2)); err != nil {
+ return orientationUnspecified
+ }
+ }
+
+ // Check if EXIF header is present.
+ var header uint32
+ if err := binary.Read(r, binary.BigEndian, &header); err != nil {
+ return orientationUnspecified
+ }
+ if header != exifHeader {
+ return orientationUnspecified
+ }
+ if _, err := io.CopyN(ioutil.Discard, r, 2); err != nil {
+ return orientationUnspecified
+ }
+
+ // Read byte order information.
+ var (
+ byteOrderTag uint16
+ byteOrder binary.ByteOrder
+ )
+ if err := binary.Read(r, binary.BigEndian, &byteOrderTag); err != nil {
+ return orientationUnspecified
+ }
+ switch byteOrderTag {
+ case byteOrderBE:
+ byteOrder = binary.BigEndian
+ case byteOrderLE:
+ byteOrder = binary.LittleEndian
+ default:
+ return orientationUnspecified // Invalid byte order flag.
+ }
+ if _, err := io.CopyN(ioutil.Discard, r, 2); err != nil {
+ return orientationUnspecified
+ }
+
+ // Skip the EXIF offset.
+ var offset uint32
+ if err := binary.Read(r, byteOrder, &offset); err != nil {
+ return orientationUnspecified
+ }
+ if offset < 8 {
+ return orientationUnspecified // Invalid offset value.
+ }
+ if _, err := io.CopyN(ioutil.Discard, r, int64(offset-8)); err != nil {
+ return orientationUnspecified
+ }
+
+ // Read the number of tags.
+ var numTags uint16
+ if err := binary.Read(r, byteOrder, &numTags); err != nil {
+ return orientationUnspecified
+ }
+
+ // Find the orientation tag.
+ for i := 0; i < int(numTags); i++ {
+ var tag uint16
+ if err := binary.Read(r, byteOrder, &tag); err != nil {
+ return orientationUnspecified
+ }
+ if tag != orientationTag {
+ if _, err := io.CopyN(ioutil.Discard, r, 10); err != nil {
+ return orientationUnspecified
+ }
+ continue
+ }
+ if _, err := io.CopyN(ioutil.Discard, r, 6); err != nil {
+ return orientationUnspecified
+ }
+ var val uint16
+ if err := binary.Read(r, byteOrder, &val); err != nil {
+ return orientationUnspecified
+ }
+ if val < 1 || val > 8 {
+ return orientationUnspecified // Invalid tag value.
+ }
+ return orientation(val)
+ }
+ return orientationUnspecified // Missing orientation tag.
+}
+
+// fixOrientation applies a transform to img corresponding to the given orientation flag.
+func fixOrientation(img image.Image, o orientation) image.Image {
+ switch o {
+ case orientationNormal:
+ case orientationFlipH:
+ img = FlipH(img)
+ case orientationFlipV:
+ img = FlipV(img)
+ case orientationRotate90:
+ img = Rotate90(img)
+ case orientationRotate180:
+ img = Rotate180(img)
+ case orientationRotate270:
+ img = Rotate270(img)
+ case orientationTranspose:
+ img = Transpose(img)
+ case orientationTransverse:
+ img = Transverse(img)
+ }
+ return img
+}
diff --git a/vendor/github.com/disintegration/imaging/tools.go b/vendor/github.com/disintegration/imaging/tools.go
index fae1fa1..7887946 100644
--- a/vendor/github.com/disintegration/imaging/tools.go
+++ b/vendor/github.com/disintegration/imaging/tools.go
@@ -1,10 +1,44 @@
package imaging
import (
+ "bytes"
"image"
+ "image/color"
"math"
)
+// New creates a new image with the specified width and height, and fills it with the specified color.
+func New(width, height int, fillColor color.Color) *image.NRGBA {
+ if width <= 0 || height <= 0 {
+ return &image.NRGBA{}
+ }
+
+ c := color.NRGBAModel.Convert(fillColor).(color.NRGBA)
+ if (c == color.NRGBA{0, 0, 0, 0}) {
+ return image.NewNRGBA(image.Rect(0, 0, width, height))
+ }
+
+ return &image.NRGBA{
+ Pix: bytes.Repeat([]byte{c.R, c.G, c.B, c.A}, width*height),
+ Stride: 4 * width,
+ Rect: image.Rect(0, 0, width, height),
+ }
+}
+
+// Clone returns a copy of the given image.
+func Clone(img image.Image) *image.NRGBA {
+ src := newScanner(img)
+ dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
+ size := src.w * 4
+ parallel(0, src.h, func(ys <-chan int) {
+ for y := range ys {
+ i := y * dst.Stride
+ src.scan(0, y, src.w, y+1, dst.Pix[i:i+size])
+ }
+ })
+ return dst
+}
+
// Anchor is the anchor point for image alignment.
type Anchor int
diff --git a/vendor/github.com/lucasb-eyer/go-colorful/README.md b/vendor/github.com/lucasb-eyer/go-colorful/README.md
index c170435..8c89c38 100644
--- a/vendor/github.com/lucasb-eyer/go-colorful/README.md
+++ b/vendor/github.com/lucasb-eyer/go-colorful/README.md
@@ -124,17 +124,16 @@ Because a `colorful.Color` implements Go's `color.Color` interface (found in the
`image/color` package), it can be used anywhere that expects a `color.Color`.
Furthermore, you can convert anything that implements the `color.Color` interface
-into a `colorful.Color` using the `MakeColorful` function:
+into a `colorful.Color` using the `MakeColor` function:
```go
-c := colorful.MakeColor(color.Gray16{12345})
+c, ok := colorful.MakeColor(color.Gray16{12345})
```
-**Caveat:** Be aware that for this latter conversion (using `MakeColor`) hits a corner-case
-when alpha is exactly zero. Because `color.Color` uses pre-multiplied alpha colors,
-this means the RGB values are lost and it's impossible to recover them. The current
-implementation `panic()`s in that case, see [issue 21](https://github.com/lucasb-eyer/go-colorful/issues/21)
-for discussion and suggesting alternatives.
+**Caveat:** Be aware that this latter conversion (using `MakeColor`) hits a
+corner-case when alpha is exactly zero. Because `color.Color` uses pre-multiplied
+alpha colors, this means the RGB values are lost (set to 0) and it's impossible
+to recover them. In such a case `MakeColor` will return `false` as its second value.
### Comparing colors
In the RGB color space, the Euclidian distance between colors *doesn't* correspond
@@ -456,16 +455,26 @@ but that is outside the scope of this library.
Thanks to [@ZirconiumX](https://github.com/ZirconiumX) for starting this investigation,
see [issue #18](https://github.com/lucasb-eyer/go-colorful/issues/18) for details.
-### Q: `MakeColor` panics when alpha is zero!
-A: Because in that case, the conversion is undefined. See
-[issue 21](https://github.com/lucasb-eyer/go-colorful/issues/21)
-as well as the short caveat discussion in the ["The `color.Color` interface"](README.md#the-colorcolor-interface) section above.
+### Q: Why would `MakeColor` ever fail!?
+A: `MakeColor` fails when the alpha channel is zero. In that case, the
+conversion is undefined. See [issue 21](https://github.com/lucasb-eyer/go-colorful/issues/21)
+as well as the short caveat note in the ["The `color.Color` interface"](README.md#the-colorcolor-interface)
+section above.
Who?
====
This library has been developed by Lucas Beyer with contributions from
-Bastien Dejean (@baskerville) and Phil Kulak (@pkulak).
+Bastien Dejean (@baskerville), Phil Kulak (@pkulak) and Christian Muehlhaeuser (@muesli).
+
+Release Notes
+=============
+
+### Version 1.0
+- API Breaking change in `MakeColor`: instead of `panic`ing when alpha is zero, it now returns a secondary, boolean return value indicating success. See [the color.Color interface](https://github.com/lucasb-eyer/go-colorful#the-colorcolor-interface) section and [this FAQ entry](https://github.com/lucasb-eyer/go-colorful#q-why-would-makecolor-ever-fail) for details.
+
+### Version 0.9
+- Initial version number after having ignored versioning for a long time :)
License: MIT
============
diff --git a/vendor/github.com/lucasb-eyer/go-colorful/colors.go b/vendor/github.com/lucasb-eyer/go-colorful/colors.go
index 7469cf7..febf94c 100644
--- a/vendor/github.com/lucasb-eyer/go-colorful/colors.go
+++ b/vendor/github.com/lucasb-eyer/go-colorful/colors.go
@@ -22,8 +22,11 @@ func (col Color) RGBA() (r, g, b, a uint32) {
}
// Constructs a colorful.Color from something implementing color.Color
-func MakeColor(col color.Color) Color {
+func MakeColor(col color.Color) (Color, bool) {
r, g, b, a := col.RGBA()
+ if a == 0 {
+ return Color{0, 0, 0}, false
+ }
// Since color.Color is alpha pre-multiplied, we need to divide the
// RGB values by alpha again in order to get back the original RGB.
@@ -34,7 +37,7 @@ func MakeColor(col color.Color) Color {
b *= 0xffff
b /= a
- return Color{float64(r) / 65535.0, float64(g) / 65535.0, float64(b) / 65535.0}
+ return Color{float64(r) / 65535.0, float64(g) / 65535.0, float64(b) / 65535.0}, true
}
// Might come in handy sometimes to reduce boilerplate code.
diff --git a/vendor/github.com/mattn/go-runewidth/runewidth.go b/vendor/github.com/mattn/go-runewidth/runewidth.go
index 2164497..82568a1 100644
--- a/vendor/github.com/mattn/go-runewidth/runewidth.go
+++ b/vendor/github.com/mattn/go-runewidth/runewidth.go
@@ -1,13 +1,24 @@
package runewidth
+import "os"
+
var (
// EastAsianWidth will be set true if the current locale is CJK
- EastAsianWidth = IsEastAsian()
+ EastAsianWidth bool
// DefaultCondition is a condition in current locale
DefaultCondition = &Condition{EastAsianWidth}
)
+func init() {
+ env := os.Getenv("RUNEWIDTH_EASTASIAN")
+ if env == "" {
+ EastAsianWidth = IsEastAsian()
+ } else {
+ EastAsianWidth = env == "1"
+ }
+}
+
type interval struct {
first rune
last rune
@@ -55,6 +66,7 @@ var private = table{
var nonprint = table{
{0x0000, 0x001F}, {0x007F, 0x009F}, {0x00AD, 0x00AD},
{0x070F, 0x070F}, {0x180B, 0x180E}, {0x200B, 0x200F},
+ {0x2028, 0x2029},
{0x202A, 0x202E}, {0x206A, 0x206F}, {0xD800, 0xDFFF},
{0xFEFF, 0xFEFF}, {0xFFF9, 0xFFFB}, {0xFFFE, 0xFFFF},
}
diff --git a/vendor/github.com/renstrom/fuzzysearch/LICENSE b/vendor/github.com/renstrom/fuzzysearch/LICENSE
index 9cc7533..dee3d1d 100644
--- a/vendor/github.com/renstrom/fuzzysearch/LICENSE
+++ b/vendor/github.com/renstrom/fuzzysearch/LICENSE
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2015 Peter Renström
+Copyright (c) 2018 Peter Lithammer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/vendor/github.com/renstrom/fuzzysearch/fuzzy/fuzzy.go b/vendor/github.com/renstrom/fuzzysearch/fuzzy/fuzzy.go
index 76b1c6e..33d4898 100644
--- a/vendor/github.com/renstrom/fuzzysearch/fuzzy/fuzzy.go
+++ b/vendor/github.com/renstrom/fuzzysearch/fuzzy/fuzzy.go
@@ -112,10 +112,7 @@ Outer:
}
// Count up remaining char
- for len(target) > 0 {
- target = target[utf8.RuneLen(rune(target[0])):]
- runeDiff++
- }
+ runeDiff += utf8.RuneCountInString(target)
return runeDiff
}
diff --git a/vendor/github.com/stretchr/testify/assert/assertion_format.go b/vendor/github.com/stretchr/testify/assert/assertion_format.go
index ae06a54..aa1c2b9 100644
--- a/vendor/github.com/stretchr/testify/assert/assertion_format.go
+++ b/vendor/github.com/stretchr/testify/assert/assertion_format.go
@@ -13,6 +13,9 @@ import (
// Conditionf uses a Comparison to assert a complex condition.
func Conditionf(t TestingT, comp Comparison, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return Condition(t, comp, append([]interface{}{msg}, args...)...)
}
@@ -23,11 +26,17 @@ func Conditionf(t TestingT, comp Comparison, msg string, args ...interface{}) bo
// assert.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted")
// assert.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted")
func Containsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return Contains(t, s, contains, append([]interface{}{msg}, args...)...)
}
// DirExistsf checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists.
func DirExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return DirExists(t, path, append([]interface{}{msg}, args...)...)
}
@@ -37,6 +46,9 @@ func DirExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
//
// assert.ElementsMatchf(t, [1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted")
func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return ElementsMatch(t, listA, listB, append([]interface{}{msg}, args...)...)
}
@@ -45,6 +57,9 @@ func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string
//
// assert.Emptyf(t, obj, "error message %s", "formatted")
func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return Empty(t, object, append([]interface{}{msg}, args...)...)
}
@@ -56,6 +71,9 @@ func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) boo
// referenced values (as opposed to the memory addresses). Function equality
// cannot be determined and will always fail.
func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return Equal(t, expected, actual, append([]interface{}{msg}, args...)...)
}
@@ -65,6 +83,9 @@ func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, ar
// actualObj, err := SomeFunction()
// assert.EqualErrorf(t, err, expectedErrorString, "error message %s", "formatted")
func EqualErrorf(t TestingT, theError error, errString string, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return EqualError(t, theError, errString, append([]interface{}{msg}, args...)...)
}
@@ -73,6 +94,9 @@ func EqualErrorf(t TestingT, theError error, errString string, msg string, args
//
// assert.EqualValuesf(t, uint32(123, "error message %s", "formatted"), int32(123))
func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return EqualValues(t, expected, actual, append([]interface{}{msg}, args...)...)
}
@@ -83,6 +107,9 @@ func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg stri
// assert.Equal(t, expectedErrorf, err)
// }
func Errorf(t TestingT, err error, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return Error(t, err, append([]interface{}{msg}, args...)...)
}
@@ -90,16 +117,25 @@ func Errorf(t TestingT, err error, msg string, args ...interface{}) bool {
//
// assert.Exactlyf(t, int32(123, "error message %s", "formatted"), int64(123))
func Exactlyf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return Exactly(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// Failf reports a failure through
func Failf(t TestingT, failureMessage string, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return Fail(t, failureMessage, append([]interface{}{msg}, args...)...)
}
// FailNowf fails test
func FailNowf(t TestingT, failureMessage string, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return FailNow(t, failureMessage, append([]interface{}{msg}, args...)...)
}
@@ -107,31 +143,43 @@ func FailNowf(t TestingT, failureMessage string, msg string, args ...interface{}
//
// assert.Falsef(t, myBool, "error message %s", "formatted")
func Falsef(t TestingT, value bool, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return False(t, value, append([]interface{}{msg}, args...)...)
}
// FileExistsf checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file.
func FileExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return FileExists(t, path, append([]interface{}{msg}, args...)...)
}
// HTTPBodyContainsf asserts that a specified handler returns a
// body that contains a string.
//
-// assert.HTTPBodyContainsf(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
+// assert.HTTPBodyContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return HTTPBodyContains(t, handler, method, url, values, str, append([]interface{}{msg}, args...)...)
}
// HTTPBodyNotContainsf asserts that a specified handler returns a
// body that does not contain a string.
//
-// assert.HTTPBodyNotContainsf(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
+// assert.HTTPBodyNotContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return HTTPBodyNotContains(t, handler, method, url, values, str, append([]interface{}{msg}, args...)...)
}
@@ -141,6 +189,9 @@ func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, u
//
// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return HTTPError(t, handler, method, url, values, append([]interface{}{msg}, args...)...)
}
@@ -150,6 +201,9 @@ func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string,
//
// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return HTTPRedirect(t, handler, method, url, values, append([]interface{}{msg}, args...)...)
}
@@ -159,6 +213,9 @@ func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url stri
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return HTTPSuccess(t, handler, method, url, values, append([]interface{}{msg}, args...)...)
}
@@ -166,6 +223,9 @@ func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url strin
//
// assert.Implementsf(t, (*MyInterface, "error message %s", "formatted")(nil), new(MyObject))
func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return Implements(t, interfaceObject, object, append([]interface{}{msg}, args...)...)
}
@@ -173,31 +233,49 @@ func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, ms
//
// assert.InDeltaf(t, math.Pi, (22 / 7.0, "error message %s", "formatted"), 0.01)
func InDeltaf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return InDelta(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
}
// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys.
func InDeltaMapValuesf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return InDeltaMapValues(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
}
// InDeltaSlicef is the same as InDelta, except it compares two slices.
func InDeltaSlicef(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return InDeltaSlice(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
}
// InEpsilonf asserts that expected and actual have a relative error less than epsilon
func InEpsilonf(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return InEpsilon(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...)
}
// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices.
func InEpsilonSlicef(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return InEpsilonSlice(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...)
}
// IsTypef asserts that the specified objects are of the same type.
func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return IsType(t, expectedType, object, append([]interface{}{msg}, args...)...)
}
@@ -205,6 +283,9 @@ func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg strin
//
// assert.JSONEqf(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted")
func JSONEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return JSONEq(t, expected, actual, append([]interface{}{msg}, args...)...)
}
@@ -213,6 +294,9 @@ func JSONEqf(t TestingT, expected string, actual string, msg string, args ...int
//
// assert.Lenf(t, mySlice, 3, "error message %s", "formatted")
func Lenf(t TestingT, object interface{}, length int, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return Len(t, object, length, append([]interface{}{msg}, args...)...)
}
@@ -220,6 +304,9 @@ func Lenf(t TestingT, object interface{}, length int, msg string, args ...interf
//
// assert.Nilf(t, err, "error message %s", "formatted")
func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return Nil(t, object, append([]interface{}{msg}, args...)...)
}
@@ -230,6 +317,9 @@ func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) bool
// assert.Equal(t, expectedObj, actualObj)
// }
func NoErrorf(t TestingT, err error, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return NoError(t, err, append([]interface{}{msg}, args...)...)
}
@@ -240,6 +330,9 @@ func NoErrorf(t TestingT, err error, msg string, args ...interface{}) bool {
// assert.NotContainsf(t, ["Hello", "World"], "Earth", "error message %s", "formatted")
// assert.NotContainsf(t, {"Hello": "World"}, "Earth", "error message %s", "formatted")
func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return NotContains(t, s, contains, append([]interface{}{msg}, args...)...)
}
@@ -250,6 +343,9 @@ func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, a
// assert.Equal(t, "two", obj[1])
// }
func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return NotEmpty(t, object, append([]interface{}{msg}, args...)...)
}
@@ -260,6 +356,9 @@ func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{})
// Pointer variable equality is determined based on the equality of the
// referenced values (as opposed to the memory addresses).
func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return NotEqual(t, expected, actual, append([]interface{}{msg}, args...)...)
}
@@ -267,6 +366,9 @@ func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string,
//
// assert.NotNilf(t, err, "error message %s", "formatted")
func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return NotNil(t, object, append([]interface{}{msg}, args...)...)
}
@@ -274,6 +376,9 @@ func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) bo
//
// assert.NotPanicsf(t, func(){ RemainCalm() }, "error message %s", "formatted")
func NotPanicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return NotPanics(t, f, append([]interface{}{msg}, args...)...)
}
@@ -282,6 +387,9 @@ func NotPanicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bo
// assert.NotRegexpf(t, regexp.MustCompile("starts", "error message %s", "formatted"), "it's starting")
// assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted")
func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return NotRegexp(t, rx, str, append([]interface{}{msg}, args...)...)
}
@@ -290,11 +398,17 @@ func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ..
//
// assert.NotSubsetf(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted")
func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return NotSubset(t, list, subset, append([]interface{}{msg}, args...)...)
}
// NotZerof asserts that i is not the zero value for its type.
func NotZerof(t TestingT, i interface{}, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return NotZero(t, i, append([]interface{}{msg}, args...)...)
}
@@ -302,6 +416,9 @@ func NotZerof(t TestingT, i interface{}, msg string, args ...interface{}) bool {
//
// assert.Panicsf(t, func(){ GoCrazy() }, "error message %s", "formatted")
func Panicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return Panics(t, f, append([]interface{}{msg}, args...)...)
}
@@ -310,6 +427,9 @@ func Panicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool
//
// assert.PanicsWithValuef(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted")
func PanicsWithValuef(t TestingT, expected interface{}, f PanicTestFunc, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return PanicsWithValue(t, expected, f, append([]interface{}{msg}, args...)...)
}
@@ -318,6 +438,9 @@ func PanicsWithValuef(t TestingT, expected interface{}, f PanicTestFunc, msg str
// assert.Regexpf(t, regexp.MustCompile("start", "error message %s", "formatted"), "it's starting")
// assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted")
func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return Regexp(t, rx, str, append([]interface{}{msg}, args...)...)
}
@@ -326,6 +449,9 @@ func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...in
//
// assert.Subsetf(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted")
func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return Subset(t, list, subset, append([]interface{}{msg}, args...)...)
}
@@ -333,6 +459,9 @@ func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args
//
// assert.Truef(t, myBool, "error message %s", "formatted")
func Truef(t TestingT, value bool, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return True(t, value, append([]interface{}{msg}, args...)...)
}
@@ -340,10 +469,16 @@ func Truef(t TestingT, value bool, msg string, args ...interface{}) bool {
//
// assert.WithinDurationf(t, time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted")
func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return WithinDuration(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
}
// Zerof asserts that i is the zero value for its type.
func Zerof(t TestingT, i interface{}, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return Zero(t, i, append([]interface{}{msg}, args...)...)
}
diff --git a/vendor/github.com/stretchr/testify/assert/assertion_format.go.tmpl b/vendor/github.com/stretchr/testify/assert/assertion_format.go.tmpl
index c5cc66f..d2bb0b8 100644
--- a/vendor/github.com/stretchr/testify/assert/assertion_format.go.tmpl
+++ b/vendor/github.com/stretchr/testify/assert/assertion_format.go.tmpl
@@ -1,4 +1,5 @@
{{.CommentFormat}}
func {{.DocInfo.Name}}f(t TestingT, {{.ParamsFormat}}) bool {
+ if h, ok := t.(tHelper); ok { h.Helper() }
return {{.DocInfo.Name}}(t, {{.ForwardedParamsFormat}})
}
diff --git a/vendor/github.com/stretchr/testify/assert/assertion_forward.go b/vendor/github.com/stretchr/testify/assert/assertion_forward.go
index ffa5428..de39f79 100644
--- a/vendor/github.com/stretchr/testify/assert/assertion_forward.go
+++ b/vendor/github.com/stretchr/testify/assert/assertion_forward.go
@@ -13,11 +13,17 @@ import (
// Condition uses a Comparison to assert a complex condition.
func (a *Assertions) Condition(comp Comparison, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return Condition(a.t, comp, msgAndArgs...)
}
// Conditionf uses a Comparison to assert a complex condition.
func (a *Assertions) Conditionf(comp Comparison, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return Conditionf(a.t, comp, msg, args...)
}
@@ -28,6 +34,9 @@ func (a *Assertions) Conditionf(comp Comparison, msg string, args ...interface{}
// a.Contains(["Hello", "World"], "World")
// a.Contains({"Hello": "World"}, "Hello")
func (a *Assertions) Contains(s interface{}, contains interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return Contains(a.t, s, contains, msgAndArgs...)
}
@@ -38,16 +47,25 @@ func (a *Assertions) Contains(s interface{}, contains interface{}, msgAndArgs ..
// a.Containsf(["Hello", "World"], "World", "error message %s", "formatted")
// a.Containsf({"Hello": "World"}, "Hello", "error message %s", "formatted")
func (a *Assertions) Containsf(s interface{}, contains interface{}, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return Containsf(a.t, s, contains, msg, args...)
}
// DirExists checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists.
func (a *Assertions) DirExists(path string, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return DirExists(a.t, path, msgAndArgs...)
}
// DirExistsf checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists.
func (a *Assertions) DirExistsf(path string, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return DirExistsf(a.t, path, msg, args...)
}
@@ -57,6 +75,9 @@ func (a *Assertions) DirExistsf(path string, msg string, args ...interface{}) bo
//
// a.ElementsMatch([1, 3, 2, 3], [1, 3, 3, 2])
func (a *Assertions) ElementsMatch(listA interface{}, listB interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return ElementsMatch(a.t, listA, listB, msgAndArgs...)
}
@@ -66,6 +87,9 @@ func (a *Assertions) ElementsMatch(listA interface{}, listB interface{}, msgAndA
//
// a.ElementsMatchf([1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted")
func (a *Assertions) ElementsMatchf(listA interface{}, listB interface{}, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return ElementsMatchf(a.t, listA, listB, msg, args...)
}
@@ -74,6 +98,9 @@ func (a *Assertions) ElementsMatchf(listA interface{}, listB interface{}, msg st
//
// a.Empty(obj)
func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return Empty(a.t, object, msgAndArgs...)
}
@@ -82,6 +109,9 @@ func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) bool {
//
// a.Emptyf(obj, "error message %s", "formatted")
func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return Emptyf(a.t, object, msg, args...)
}
@@ -93,6 +123,9 @@ func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{})
// referenced values (as opposed to the memory addresses). Function equality
// cannot be determined and will always fail.
func (a *Assertions) Equal(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return Equal(a.t, expected, actual, msgAndArgs...)
}
@@ -102,6 +135,9 @@ func (a *Assertions) Equal(expected interface{}, actual interface{}, msgAndArgs
// actualObj, err := SomeFunction()
// a.EqualError(err, expectedErrorString)
func (a *Assertions) EqualError(theError error, errString string, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return EqualError(a.t, theError, errString, msgAndArgs...)
}
@@ -111,6 +147,9 @@ func (a *Assertions) EqualError(theError error, errString string, msgAndArgs ...
// actualObj, err := SomeFunction()
// a.EqualErrorf(err, expectedErrorString, "error message %s", "formatted")
func (a *Assertions) EqualErrorf(theError error, errString string, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return EqualErrorf(a.t, theError, errString, msg, args...)
}
@@ -119,6 +158,9 @@ func (a *Assertions) EqualErrorf(theError error, errString string, msg string, a
//
// a.EqualValues(uint32(123), int32(123))
func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return EqualValues(a.t, expected, actual, msgAndArgs...)
}
@@ -127,6 +169,9 @@ func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAn
//
// a.EqualValuesf(uint32(123, "error message %s", "formatted"), int32(123))
func (a *Assertions) EqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return EqualValuesf(a.t, expected, actual, msg, args...)
}
@@ -138,6 +183,9 @@ func (a *Assertions) EqualValuesf(expected interface{}, actual interface{}, msg
// referenced values (as opposed to the memory addresses). Function equality
// cannot be determined and will always fail.
func (a *Assertions) Equalf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return Equalf(a.t, expected, actual, msg, args...)
}
@@ -148,6 +196,9 @@ func (a *Assertions) Equalf(expected interface{}, actual interface{}, msg string
// assert.Equal(t, expectedError, err)
// }
func (a *Assertions) Error(err error, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return Error(a.t, err, msgAndArgs...)
}
@@ -158,6 +209,9 @@ func (a *Assertions) Error(err error, msgAndArgs ...interface{}) bool {
// assert.Equal(t, expectedErrorf, err)
// }
func (a *Assertions) Errorf(err error, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return Errorf(a.t, err, msg, args...)
}
@@ -165,6 +219,9 @@ func (a *Assertions) Errorf(err error, msg string, args ...interface{}) bool {
//
// a.Exactly(int32(123), int64(123))
func (a *Assertions) Exactly(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return Exactly(a.t, expected, actual, msgAndArgs...)
}
@@ -172,26 +229,41 @@ func (a *Assertions) Exactly(expected interface{}, actual interface{}, msgAndArg
//
// a.Exactlyf(int32(123, "error message %s", "formatted"), int64(123))
func (a *Assertions) Exactlyf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return Exactlyf(a.t, expected, actual, msg, args...)
}
// Fail reports a failure through
func (a *Assertions) Fail(failureMessage string, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return Fail(a.t, failureMessage, msgAndArgs...)
}
// FailNow fails test
func (a *Assertions) FailNow(failureMessage string, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return FailNow(a.t, failureMessage, msgAndArgs...)
}
// FailNowf fails test
func (a *Assertions) FailNowf(failureMessage string, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return FailNowf(a.t, failureMessage, msg, args...)
}
// Failf reports a failure through
func (a *Assertions) Failf(failureMessage string, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return Failf(a.t, failureMessage, msg, args...)
}
@@ -199,6 +271,9 @@ func (a *Assertions) Failf(failureMessage string, msg string, args ...interface{
//
// a.False(myBool)
func (a *Assertions) False(value bool, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return False(a.t, value, msgAndArgs...)
}
@@ -206,56 +281,77 @@ func (a *Assertions) False(value bool, msgAndArgs ...interface{}) bool {
//
// a.Falsef(myBool, "error message %s", "formatted")
func (a *Assertions) Falsef(value bool, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return Falsef(a.t, value, msg, args...)
}
// FileExists checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file.
func (a *Assertions) FileExists(path string, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return FileExists(a.t, path, msgAndArgs...)
}
// FileExistsf checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file.
func (a *Assertions) FileExistsf(path string, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return FileExistsf(a.t, path, msg, args...)
}
// HTTPBodyContains asserts that a specified handler returns a
// body that contains a string.
//
-// a.HTTPBodyContains(myHandler, "www.google.com", nil, "I'm Feeling Lucky")
+// a.HTTPBodyContains(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return HTTPBodyContains(a.t, handler, method, url, values, str, msgAndArgs...)
}
// HTTPBodyContainsf asserts that a specified handler returns a
// body that contains a string.
//
-// a.HTTPBodyContainsf(myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
+// a.HTTPBodyContainsf(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) HTTPBodyContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return HTTPBodyContainsf(a.t, handler, method, url, values, str, msg, args...)
}
// HTTPBodyNotContains asserts that a specified handler returns a
// body that does not contain a string.
//
-// a.HTTPBodyNotContains(myHandler, "www.google.com", nil, "I'm Feeling Lucky")
+// a.HTTPBodyNotContains(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return HTTPBodyNotContains(a.t, handler, method, url, values, str, msgAndArgs...)
}
// HTTPBodyNotContainsf asserts that a specified handler returns a
// body that does not contain a string.
//
-// a.HTTPBodyNotContainsf(myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
+// a.HTTPBodyNotContainsf(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) HTTPBodyNotContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return HTTPBodyNotContainsf(a.t, handler, method, url, values, str, msg, args...)
}
@@ -265,6 +361,9 @@ func (a *Assertions) HTTPBodyNotContainsf(handler http.HandlerFunc, method strin
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return HTTPError(a.t, handler, method, url, values, msgAndArgs...)
}
@@ -274,6 +373,9 @@ func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url stri
//
// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
func (a *Assertions) HTTPErrorf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return HTTPErrorf(a.t, handler, method, url, values, msg, args...)
}
@@ -283,6 +385,9 @@ func (a *Assertions) HTTPErrorf(handler http.HandlerFunc, method string, url str
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return HTTPRedirect(a.t, handler, method, url, values, msgAndArgs...)
}
@@ -292,6 +397,9 @@ func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url s
//
// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
func (a *Assertions) HTTPRedirectf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return HTTPRedirectf(a.t, handler, method, url, values, msg, args...)
}
@@ -301,6 +409,9 @@ func (a *Assertions) HTTPRedirectf(handler http.HandlerFunc, method string, url
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return HTTPSuccess(a.t, handler, method, url, values, msgAndArgs...)
}
@@ -310,6 +421,9 @@ func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url st
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) HTTPSuccessf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return HTTPSuccessf(a.t, handler, method, url, values, msg, args...)
}
@@ -317,6 +431,9 @@ func (a *Assertions) HTTPSuccessf(handler http.HandlerFunc, method string, url s
//
// a.Implements((*MyInterface)(nil), new(MyObject))
func (a *Assertions) Implements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return Implements(a.t, interfaceObject, object, msgAndArgs...)
}
@@ -324,6 +441,9 @@ func (a *Assertions) Implements(interfaceObject interface{}, object interface{},
//
// a.Implementsf((*MyInterface, "error message %s", "formatted")(nil), new(MyObject))
func (a *Assertions) Implementsf(interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return Implementsf(a.t, interfaceObject, object, msg, args...)
}
@@ -331,26 +451,41 @@ func (a *Assertions) Implementsf(interfaceObject interface{}, object interface{}
//
// a.InDelta(math.Pi, (22 / 7.0), 0.01)
func (a *Assertions) InDelta(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return InDelta(a.t, expected, actual, delta, msgAndArgs...)
}
// InDeltaMapValues is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys.
func (a *Assertions) InDeltaMapValues(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return InDeltaMapValues(a.t, expected, actual, delta, msgAndArgs...)
}
// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys.
func (a *Assertions) InDeltaMapValuesf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return InDeltaMapValuesf(a.t, expected, actual, delta, msg, args...)
}
// InDeltaSlice is the same as InDelta, except it compares two slices.
func (a *Assertions) InDeltaSlice(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return InDeltaSlice(a.t, expected, actual, delta, msgAndArgs...)
}
// InDeltaSlicef is the same as InDelta, except it compares two slices.
func (a *Assertions) InDeltaSlicef(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return InDeltaSlicef(a.t, expected, actual, delta, msg, args...)
}
@@ -358,36 +493,57 @@ func (a *Assertions) InDeltaSlicef(expected interface{}, actual interface{}, del
//
// a.InDeltaf(math.Pi, (22 / 7.0, "error message %s", "formatted"), 0.01)
func (a *Assertions) InDeltaf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return InDeltaf(a.t, expected, actual, delta, msg, args...)
}
// InEpsilon asserts that expected and actual have a relative error less than epsilon
func (a *Assertions) InEpsilon(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return InEpsilon(a.t, expected, actual, epsilon, msgAndArgs...)
}
// InEpsilonSlice is the same as InEpsilon, except it compares each value from two slices.
func (a *Assertions) InEpsilonSlice(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return InEpsilonSlice(a.t, expected, actual, epsilon, msgAndArgs...)
}
// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices.
func (a *Assertions) InEpsilonSlicef(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return InEpsilonSlicef(a.t, expected, actual, epsilon, msg, args...)
}
// InEpsilonf asserts that expected and actual have a relative error less than epsilon
func (a *Assertions) InEpsilonf(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return InEpsilonf(a.t, expected, actual, epsilon, msg, args...)
}
// IsType asserts that the specified objects are of the same type.
func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return IsType(a.t, expectedType, object, msgAndArgs...)
}
// IsTypef asserts that the specified objects are of the same type.
func (a *Assertions) IsTypef(expectedType interface{}, object interface{}, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return IsTypef(a.t, expectedType, object, msg, args...)
}
@@ -395,6 +551,9 @@ func (a *Assertions) IsTypef(expectedType interface{}, object interface{}, msg s
//
// a.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`)
func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return JSONEq(a.t, expected, actual, msgAndArgs...)
}
@@ -402,6 +561,9 @@ func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interf
//
// a.JSONEqf(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted")
func (a *Assertions) JSONEqf(expected string, actual string, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return JSONEqf(a.t, expected, actual, msg, args...)
}
@@ -410,6 +572,9 @@ func (a *Assertions) JSONEqf(expected string, actual string, msg string, args ..
//
// a.Len(mySlice, 3)
func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return Len(a.t, object, length, msgAndArgs...)
}
@@ -418,6 +583,9 @@ func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface
//
// a.Lenf(mySlice, 3, "error message %s", "formatted")
func (a *Assertions) Lenf(object interface{}, length int, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return Lenf(a.t, object, length, msg, args...)
}
@@ -425,6 +593,9 @@ func (a *Assertions) Lenf(object interface{}, length int, msg string, args ...in
//
// a.Nil(err)
func (a *Assertions) Nil(object interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return Nil(a.t, object, msgAndArgs...)
}
@@ -432,6 +603,9 @@ func (a *Assertions) Nil(object interface{}, msgAndArgs ...interface{}) bool {
//
// a.Nilf(err, "error message %s", "formatted")
func (a *Assertions) Nilf(object interface{}, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return Nilf(a.t, object, msg, args...)
}
@@ -442,6 +616,9 @@ func (a *Assertions) Nilf(object interface{}, msg string, args ...interface{}) b
// assert.Equal(t, expectedObj, actualObj)
// }
func (a *Assertions) NoError(err error, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return NoError(a.t, err, msgAndArgs...)
}
@@ -452,6 +629,9 @@ func (a *Assertions) NoError(err error, msgAndArgs ...interface{}) bool {
// assert.Equal(t, expectedObj, actualObj)
// }
func (a *Assertions) NoErrorf(err error, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return NoErrorf(a.t, err, msg, args...)
}
@@ -462,6 +642,9 @@ func (a *Assertions) NoErrorf(err error, msg string, args ...interface{}) bool {
// a.NotContains(["Hello", "World"], "Earth")
// a.NotContains({"Hello": "World"}, "Earth")
func (a *Assertions) NotContains(s interface{}, contains interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return NotContains(a.t, s, contains, msgAndArgs...)
}
@@ -472,6 +655,9 @@ func (a *Assertions) NotContains(s interface{}, contains interface{}, msgAndArgs
// a.NotContainsf(["Hello", "World"], "Earth", "error message %s", "formatted")
// a.NotContainsf({"Hello": "World"}, "Earth", "error message %s", "formatted")
func (a *Assertions) NotContainsf(s interface{}, contains interface{}, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return NotContainsf(a.t, s, contains, msg, args...)
}
@@ -482,6 +668,9 @@ func (a *Assertions) NotContainsf(s interface{}, contains interface{}, msg strin
// assert.Equal(t, "two", obj[1])
// }
func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return NotEmpty(a.t, object, msgAndArgs...)
}
@@ -492,6 +681,9 @@ func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) boo
// assert.Equal(t, "two", obj[1])
// }
func (a *Assertions) NotEmptyf(object interface{}, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return NotEmptyf(a.t, object, msg, args...)
}
@@ -502,6 +694,9 @@ func (a *Assertions) NotEmptyf(object interface{}, msg string, args ...interface
// Pointer variable equality is determined based on the equality of the
// referenced values (as opposed to the memory addresses).
func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return NotEqual(a.t, expected, actual, msgAndArgs...)
}
@@ -512,6 +707,9 @@ func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndAr
// Pointer variable equality is determined based on the equality of the
// referenced values (as opposed to the memory addresses).
func (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return NotEqualf(a.t, expected, actual, msg, args...)
}
@@ -519,6 +717,9 @@ func (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg str
//
// a.NotNil(err)
func (a *Assertions) NotNil(object interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return NotNil(a.t, object, msgAndArgs...)
}
@@ -526,6 +727,9 @@ func (a *Assertions) NotNil(object interface{}, msgAndArgs ...interface{}) bool
//
// a.NotNilf(err, "error message %s", "formatted")
func (a *Assertions) NotNilf(object interface{}, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return NotNilf(a.t, object, msg, args...)
}
@@ -533,6 +737,9 @@ func (a *Assertions) NotNilf(object interface{}, msg string, args ...interface{}
//
// a.NotPanics(func(){ RemainCalm() })
func (a *Assertions) NotPanics(f PanicTestFunc, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return NotPanics(a.t, f, msgAndArgs...)
}
@@ -540,6 +747,9 @@ func (a *Assertions) NotPanics(f PanicTestFunc, msgAndArgs ...interface{}) bool
//
// a.NotPanicsf(func(){ RemainCalm() }, "error message %s", "formatted")
func (a *Assertions) NotPanicsf(f PanicTestFunc, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return NotPanicsf(a.t, f, msg, args...)
}
@@ -548,6 +758,9 @@ func (a *Assertions) NotPanicsf(f PanicTestFunc, msg string, args ...interface{}
// a.NotRegexp(regexp.MustCompile("starts"), "it's starting")
// a.NotRegexp("^start", "it's not starting")
func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return NotRegexp(a.t, rx, str, msgAndArgs...)
}
@@ -556,6 +769,9 @@ func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...in
// a.NotRegexpf(regexp.MustCompile("starts", "error message %s", "formatted"), "it's starting")
// a.NotRegexpf("^start", "it's not starting", "error message %s", "formatted")
func (a *Assertions) NotRegexpf(rx interface{}, str interface{}, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return NotRegexpf(a.t, rx, str, msg, args...)
}
@@ -564,6 +780,9 @@ func (a *Assertions) NotRegexpf(rx interface{}, str interface{}, msg string, arg
//
// a.NotSubset([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]")
func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return NotSubset(a.t, list, subset, msgAndArgs...)
}
@@ -572,16 +791,25 @@ func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs
//
// a.NotSubsetf([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted")
func (a *Assertions) NotSubsetf(list interface{}, subset interface{}, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return NotSubsetf(a.t, list, subset, msg, args...)
}
// NotZero asserts that i is not the zero value for its type.
func (a *Assertions) NotZero(i interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return NotZero(a.t, i, msgAndArgs...)
}
// NotZerof asserts that i is not the zero value for its type.
func (a *Assertions) NotZerof(i interface{}, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return NotZerof(a.t, i, msg, args...)
}
@@ -589,6 +817,9 @@ func (a *Assertions) NotZerof(i interface{}, msg string, args ...interface{}) bo
//
// a.Panics(func(){ GoCrazy() })
func (a *Assertions) Panics(f PanicTestFunc, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return Panics(a.t, f, msgAndArgs...)
}
@@ -597,6 +828,9 @@ func (a *Assertions) Panics(f PanicTestFunc, msgAndArgs ...interface{}) bool {
//
// a.PanicsWithValue("crazy error", func(){ GoCrazy() })
func (a *Assertions) PanicsWithValue(expected interface{}, f PanicTestFunc, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return PanicsWithValue(a.t, expected, f, msgAndArgs...)
}
@@ -605,6 +839,9 @@ func (a *Assertions) PanicsWithValue(expected interface{}, f PanicTestFunc, msgA
//
// a.PanicsWithValuef("crazy error", func(){ GoCrazy() }, "error message %s", "formatted")
func (a *Assertions) PanicsWithValuef(expected interface{}, f PanicTestFunc, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return PanicsWithValuef(a.t, expected, f, msg, args...)
}
@@ -612,6 +849,9 @@ func (a *Assertions) PanicsWithValuef(expected interface{}, f PanicTestFunc, msg
//
// a.Panicsf(func(){ GoCrazy() }, "error message %s", "formatted")
func (a *Assertions) Panicsf(f PanicTestFunc, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return Panicsf(a.t, f, msg, args...)
}
@@ -620,6 +860,9 @@ func (a *Assertions) Panicsf(f PanicTestFunc, msg string, args ...interface{}) b
// a.Regexp(regexp.MustCompile("start"), "it's starting")
// a.Regexp("start...$", "it's not starting")
func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return Regexp(a.t, rx, str, msgAndArgs...)
}
@@ -628,6 +871,9 @@ func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...inter
// a.Regexpf(regexp.MustCompile("start", "error message %s", "formatted"), "it's starting")
// a.Regexpf("start...$", "it's not starting", "error message %s", "formatted")
func (a *Assertions) Regexpf(rx interface{}, str interface{}, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return Regexpf(a.t, rx, str, msg, args...)
}
@@ -636,6 +882,9 @@ func (a *Assertions) Regexpf(rx interface{}, str interface{}, msg string, args .
//
// a.Subset([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]")
func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return Subset(a.t, list, subset, msgAndArgs...)
}
@@ -644,6 +893,9 @@ func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ...
//
// a.Subsetf([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted")
func (a *Assertions) Subsetf(list interface{}, subset interface{}, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return Subsetf(a.t, list, subset, msg, args...)
}
@@ -651,6 +903,9 @@ func (a *Assertions) Subsetf(list interface{}, subset interface{}, msg string, a
//
// a.True(myBool)
func (a *Assertions) True(value bool, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return True(a.t, value, msgAndArgs...)
}
@@ -658,6 +913,9 @@ func (a *Assertions) True(value bool, msgAndArgs ...interface{}) bool {
//
// a.Truef(myBool, "error message %s", "formatted")
func (a *Assertions) Truef(value bool, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return Truef(a.t, value, msg, args...)
}
@@ -665,6 +923,9 @@ func (a *Assertions) Truef(value bool, msg string, args ...interface{}) bool {
//
// a.WithinDuration(time.Now(), time.Now(), 10*time.Second)
func (a *Assertions) WithinDuration(expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return WithinDuration(a.t, expected, actual, delta, msgAndArgs...)
}
@@ -672,15 +933,24 @@ func (a *Assertions) WithinDuration(expected time.Time, actual time.Time, delta
//
// a.WithinDurationf(time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted")
func (a *Assertions) WithinDurationf(expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return WithinDurationf(a.t, expected, actual, delta, msg, args...)
}
// Zero asserts that i is the zero value for its type.
func (a *Assertions) Zero(i interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return Zero(a.t, i, msgAndArgs...)
}
// Zerof asserts that i is the zero value for its type.
func (a *Assertions) Zerof(i interface{}, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
return Zerof(a.t, i, msg, args...)
}
diff --git a/vendor/github.com/stretchr/testify/assert/assertion_forward.go.tmpl b/vendor/github.com/stretchr/testify/assert/assertion_forward.go.tmpl
index 99f9acf..188bb9e 100644
--- a/vendor/github.com/stretchr/testify/assert/assertion_forward.go.tmpl
+++ b/vendor/github.com/stretchr/testify/assert/assertion_forward.go.tmpl
@@ -1,4 +1,5 @@
{{.CommentWithoutT "a"}}
func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) bool {
+ if h, ok := a.t.(tHelper); ok { h.Helper() }
return {{.DocInfo.Name}}(a.t, {{.ForwardedParams}})
}
diff --git a/vendor/github.com/stretchr/testify/assert/assertions.go b/vendor/github.com/stretchr/testify/assert/assertions.go
index 47bda77..5bdec56 100644
--- a/vendor/github.com/stretchr/testify/assert/assertions.go
+++ b/vendor/github.com/stretchr/testify/assert/assertions.go
@@ -27,6 +27,22 @@ type TestingT interface {
Errorf(format string, args ...interface{})
}
+// ComparisonAssertionFunc is a common function prototype when comparing two values. Can be useful
+// for table driven tests.
+type ComparisonAssertionFunc func(TestingT, interface{}, interface{}, ...interface{}) bool
+
+// ValueAssertionFunc is a common function prototype when validating a single value. Can be useful
+// for table driven tests.
+type ValueAssertionFunc func(TestingT, interface{}, ...interface{}) bool
+
+// BoolAssertionFunc is a common function prototype when validating a bool value. Can be useful
+// for table driven tests.
+type BoolAssertionFunc func(TestingT, bool, ...interface{}) bool
+
+// ValuesAssertionFunc is a common function prototype when validating an error value. Can be useful
+// for table driven tests.
+type ErrorAssertionFunc func(TestingT, error, ...interface{}) bool
+
// Comparison a custom function that returns true on success and false on failure
type Comparison func() (success bool)
@@ -38,21 +54,23 @@ type Comparison func() (success bool)
//
// This function does no assertion of any kind.
func ObjectsAreEqual(expected, actual interface{}) bool {
-
if expected == nil || actual == nil {
return expected == actual
}
- if exp, ok := expected.([]byte); ok {
- act, ok := actual.([]byte)
- if !ok {
- return false
- } else if exp == nil || act == nil {
- return exp == nil && act == nil
- }
- return bytes.Equal(exp, act)
+
+ exp, ok := expected.([]byte)
+ if !ok {
+ return reflect.DeepEqual(expected, actual)
}
- return reflect.DeepEqual(expected, actual)
+ act, ok := actual.([]byte)
+ if !ok {
+ return false
+ }
+ if exp == nil || act == nil {
+ return exp == nil && act == nil
+ }
+ return bytes.Equal(exp, act)
}
// ObjectsAreEqualValues gets whether two objects are equal, or if their
@@ -156,21 +174,6 @@ func isTest(name, prefix string) bool {
return !unicode.IsLower(rune)
}
-// getWhitespaceString returns a string that is long enough to overwrite the default
-// output from the go testing framework.
-func getWhitespaceString() string {
-
- _, file, line, ok := runtime.Caller(1)
- if !ok {
- return ""
- }
- parts := strings.Split(file, "/")
- file = parts[len(parts)-1]
-
- return strings.Repeat(" ", len(fmt.Sprintf("%s:%d: ", file, line)))
-
-}
-
func messageFromMsgAndArgs(msgAndArgs ...interface{}) string {
if len(msgAndArgs) == 0 || msgAndArgs == nil {
return ""
@@ -195,7 +198,7 @@ func indentMessageLines(message string, longestLabelLen int) string {
// no need to align first line because it starts at the correct location (after the label)
if i != 0 {
// append alignLen+1 spaces to align with "{{longestLabel}}:" before adding tab
- outBuf.WriteString("\n\r\t" + strings.Repeat(" ", longestLabelLen+1) + "\t")
+ outBuf.WriteString("\n\t" + strings.Repeat(" ", longestLabelLen+1) + "\t")
}
outBuf.WriteString(scanner.Text())
}
@@ -209,6 +212,9 @@ type failNower interface {
// FailNow fails test
func FailNow(t TestingT, failureMessage string, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
Fail(t, failureMessage, msgAndArgs...)
// We cannot extend TestingT with FailNow() and
@@ -227,8 +233,11 @@ func FailNow(t TestingT, failureMessage string, msgAndArgs ...interface{}) bool
// Fail reports a failure through
func Fail(t TestingT, failureMessage string, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
content := []labeledContent{
- {"Error Trace", strings.Join(CallerInfo(), "\n\r\t\t\t")},
+ {"Error Trace", strings.Join(CallerInfo(), "\n\t\t\t")},
{"Error", failureMessage},
}
@@ -244,7 +253,7 @@ func Fail(t TestingT, failureMessage string, msgAndArgs ...interface{}) bool {
content = append(content, labeledContent{"Messages", message})
}
- t.Errorf("%s", "\r"+getWhitespaceString()+labeledOutput(content...))
+ t.Errorf("\n%s", ""+labeledOutput(content...))
return false
}
@@ -256,7 +265,7 @@ type labeledContent struct {
// labeledOutput returns a string consisting of the provided labeledContent. Each labeled output is appended in the following manner:
//
-// \r\t{{label}}:{{align_spaces}}\t{{content}}\n
+// \t{{label}}:{{align_spaces}}\t{{content}}\n
//
// The initial carriage return is required to undo/erase any padding added by testing.T.Errorf. The "\t{{label}}:" is for the label.
// If a label is shorter than the longest label provided, padding spaces are added to make all the labels match in length. Once this
@@ -272,7 +281,7 @@ func labeledOutput(content ...labeledContent) string {
}
var output string
for _, v := range content {
- output += "\r\t" + v.label + ":" + strings.Repeat(" ", longestLabel-len(v.label)) + "\t" + indentMessageLines(v.content, longestLabel) + "\n"
+ output += "\t" + v.label + ":" + strings.Repeat(" ", longestLabel-len(v.label)) + "\t" + indentMessageLines(v.content, longestLabel) + "\n"
}
return output
}
@@ -281,6 +290,9 @@ func labeledOutput(content ...labeledContent) string {
//
// assert.Implements(t, (*MyInterface)(nil), new(MyObject))
func Implements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
interfaceType := reflect.TypeOf(interfaceObject).Elem()
if object == nil {
@@ -295,6 +307,9 @@ func Implements(t TestingT, interfaceObject interface{}, object interface{}, msg
// IsType asserts that the specified objects are of the same type.
func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
if !ObjectsAreEqual(reflect.TypeOf(object), reflect.TypeOf(expectedType)) {
return Fail(t, fmt.Sprintf("Object expected to be of type %v, but was %v", reflect.TypeOf(expectedType), reflect.TypeOf(object)), msgAndArgs...)
@@ -311,6 +326,9 @@ func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs
// referenced values (as opposed to the memory addresses). Function equality
// cannot be determined and will always fail.
func Equal(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
if err := validateEqualArgs(expected, actual); err != nil {
return Fail(t, fmt.Sprintf("Invalid operation: %#v == %#v (%s)",
expected, actual, err), msgAndArgs...)
@@ -349,6 +367,9 @@ func formatUnequalValues(expected, actual interface{}) (e string, a string) {
//
// assert.EqualValues(t, uint32(123), int32(123))
func EqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
if !ObjectsAreEqualValues(expected, actual) {
diff := diff(expected, actual)
@@ -366,12 +387,15 @@ func EqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interfa
//
// assert.Exactly(t, int32(123), int64(123))
func Exactly(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
aType := reflect.TypeOf(expected)
bType := reflect.TypeOf(actual)
if aType != bType {
- return Fail(t, fmt.Sprintf("Types expected to match exactly\n\r\t%v != %v", aType, bType), msgAndArgs...)
+ return Fail(t, fmt.Sprintf("Types expected to match exactly\n\t%v != %v", aType, bType), msgAndArgs...)
}
return Equal(t, expected, actual, msgAndArgs...)
@@ -382,6 +406,9 @@ func Exactly(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}
//
// assert.NotNil(t, err)
func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
if !isNil(object) {
return true
}
@@ -407,6 +434,9 @@ func isNil(object interface{}) bool {
//
// assert.Nil(t, err)
func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
if isNil(object) {
return true
}
@@ -446,6 +476,9 @@ func isEmpty(object interface{}) bool {
//
// assert.Empty(t, obj)
func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
pass := isEmpty(object)
if !pass {
@@ -463,6 +496,9 @@ func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
// assert.Equal(t, "two", obj[1])
// }
func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
pass := !isEmpty(object)
if !pass {
@@ -490,6 +526,9 @@ func getLen(x interface{}) (ok bool, length int) {
//
// assert.Len(t, mySlice, 3)
func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
ok, l := getLen(object)
if !ok {
return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", object), msgAndArgs...)
@@ -505,6 +544,14 @@ func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{})
//
// assert.True(t, myBool)
func True(t TestingT, value bool, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
+ if h, ok := t.(interface {
+ Helper()
+ }); ok {
+ h.Helper()
+ }
if value != true {
return Fail(t, "Should be true", msgAndArgs...)
@@ -518,6 +565,9 @@ func True(t TestingT, value bool, msgAndArgs ...interface{}) bool {
//
// assert.False(t, myBool)
func False(t TestingT, value bool, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
if value != false {
return Fail(t, "Should be false", msgAndArgs...)
@@ -534,6 +584,9 @@ func False(t TestingT, value bool, msgAndArgs ...interface{}) bool {
// Pointer variable equality is determined based on the equality of the
// referenced values (as opposed to the memory addresses).
func NotEqual(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
if err := validateEqualArgs(expected, actual); err != nil {
return Fail(t, fmt.Sprintf("Invalid operation: %#v != %#v (%s)",
expected, actual, err), msgAndArgs...)
@@ -592,6 +645,9 @@ func includeElement(list interface{}, element interface{}) (ok, found bool) {
// assert.Contains(t, ["Hello", "World"], "World")
// assert.Contains(t, {"Hello": "World"}, "Hello")
func Contains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
ok, found := includeElement(s, contains)
if !ok {
@@ -612,6 +668,9 @@ func Contains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bo
// assert.NotContains(t, ["Hello", "World"], "Earth")
// assert.NotContains(t, {"Hello": "World"}, "Earth")
func NotContains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
ok, found := includeElement(s, contains)
if !ok {
@@ -630,6 +689,9 @@ func NotContains(t TestingT, s, contains interface{}, msgAndArgs ...interface{})
//
// assert.Subset(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]")
func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok bool) {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
if subset == nil {
return true // we consider nil to be equal to the nil set
}
@@ -671,6 +733,9 @@ func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok
//
// assert.NotSubset(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]")
func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok bool) {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
if subset == nil {
return Fail(t, fmt.Sprintf("nil is the empty set which is a subset of every set"), msgAndArgs...)
}
@@ -713,6 +778,9 @@ func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{})
//
// assert.ElementsMatch(t, [1, 3, 2, 3], [1, 3, 3, 2])
func ElementsMatch(t TestingT, listA, listB interface{}, msgAndArgs ...interface{}) (ok bool) {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
if isEmpty(listA) && isEmpty(listB) {
return true
}
@@ -763,6 +831,9 @@ func ElementsMatch(t TestingT, listA, listB interface{}, msgAndArgs ...interface
// Condition uses a Comparison to assert a complex condition.
func Condition(t TestingT, comp Comparison, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
result := comp()
if !result {
Fail(t, "Condition failed!", msgAndArgs...)
@@ -800,9 +871,12 @@ func didPanic(f PanicTestFunc) (bool, interface{}) {
//
// assert.Panics(t, func(){ GoCrazy() })
func Panics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
if funcDidPanic, panicValue := didPanic(f); !funcDidPanic {
- return Fail(t, fmt.Sprintf("func %#v should panic\n\r\tPanic value:\t%v", f, panicValue), msgAndArgs...)
+ return Fail(t, fmt.Sprintf("func %#v should panic\n\tPanic value:\t%#v", f, panicValue), msgAndArgs...)
}
return true
@@ -813,13 +887,16 @@ func Panics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool {
//
// assert.PanicsWithValue(t, "crazy error", func(){ GoCrazy() })
func PanicsWithValue(t TestingT, expected interface{}, f PanicTestFunc, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
funcDidPanic, panicValue := didPanic(f)
if !funcDidPanic {
- return Fail(t, fmt.Sprintf("func %#v should panic\n\r\tPanic value:\t%v", f, panicValue), msgAndArgs...)
+ return Fail(t, fmt.Sprintf("func %#v should panic\n\tPanic value:\t%#v", f, panicValue), msgAndArgs...)
}
if panicValue != expected {
- return Fail(t, fmt.Sprintf("func %#v should panic with value:\t%v\n\r\tPanic value:\t%v", f, expected, panicValue), msgAndArgs...)
+ return Fail(t, fmt.Sprintf("func %#v should panic with value:\t%#v\n\tPanic value:\t%#v", f, expected, panicValue), msgAndArgs...)
}
return true
@@ -829,9 +906,12 @@ func PanicsWithValue(t TestingT, expected interface{}, f PanicTestFunc, msgAndAr
//
// assert.NotPanics(t, func(){ RemainCalm() })
func NotPanics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
if funcDidPanic, panicValue := didPanic(f); funcDidPanic {
- return Fail(t, fmt.Sprintf("func %#v should not panic\n\r\tPanic value:\t%v", f, panicValue), msgAndArgs...)
+ return Fail(t, fmt.Sprintf("func %#v should not panic\n\tPanic value:\t%v", f, panicValue), msgAndArgs...)
}
return true
@@ -841,6 +921,9 @@ func NotPanics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool {
//
// assert.WithinDuration(t, time.Now(), time.Now(), 10*time.Second)
func WithinDuration(t TestingT, expected, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
dt := expected.Sub(actual)
if dt < -delta || dt > delta {
@@ -890,6 +973,9 @@ func toFloat(x interface{}) (float64, bool) {
//
// assert.InDelta(t, math.Pi, (22 / 7.0), 0.01)
func InDelta(t TestingT, expected, actual interface{}, delta float64, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
af, aok := toFloat(expected)
bf, bok := toFloat(actual)
@@ -916,6 +1002,9 @@ func InDelta(t TestingT, expected, actual interface{}, delta float64, msgAndArgs
// InDeltaSlice is the same as InDelta, except it compares two slices.
func InDeltaSlice(t TestingT, expected, actual interface{}, delta float64, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
if expected == nil || actual == nil ||
reflect.TypeOf(actual).Kind() != reflect.Slice ||
reflect.TypeOf(expected).Kind() != reflect.Slice {
@@ -937,6 +1026,9 @@ func InDeltaSlice(t TestingT, expected, actual interface{}, delta float64, msgAn
// InDeltaMapValues is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys.
func InDeltaMapValues(t TestingT, expected, actual interface{}, delta float64, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
if expected == nil || actual == nil ||
reflect.TypeOf(actual).Kind() != reflect.Map ||
reflect.TypeOf(expected).Kind() != reflect.Map {
@@ -994,6 +1086,9 @@ func calcRelativeError(expected, actual interface{}) (float64, error) {
// InEpsilon asserts that expected and actual have a relative error less than epsilon
func InEpsilon(t TestingT, expected, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
actualEpsilon, err := calcRelativeError(expected, actual)
if err != nil {
return Fail(t, err.Error(), msgAndArgs...)
@@ -1008,6 +1103,9 @@ func InEpsilon(t TestingT, expected, actual interface{}, epsilon float64, msgAnd
// InEpsilonSlice is the same as InEpsilon, except it compares each value from two slices.
func InEpsilonSlice(t TestingT, expected, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
if expected == nil || actual == nil ||
reflect.TypeOf(actual).Kind() != reflect.Slice ||
reflect.TypeOf(expected).Kind() != reflect.Slice {
@@ -1038,6 +1136,9 @@ func InEpsilonSlice(t TestingT, expected, actual interface{}, epsilon float64, m
// assert.Equal(t, expectedObj, actualObj)
// }
func NoError(t TestingT, err error, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
if err != nil {
return Fail(t, fmt.Sprintf("Received unexpected error:\n%+v", err), msgAndArgs...)
}
@@ -1052,6 +1153,9 @@ func NoError(t TestingT, err error, msgAndArgs ...interface{}) bool {
// assert.Equal(t, expectedError, err)
// }
func Error(t TestingT, err error, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
if err == nil {
return Fail(t, "An error is expected but got nil.", msgAndArgs...)
@@ -1066,6 +1170,9 @@ func Error(t TestingT, err error, msgAndArgs ...interface{}) bool {
// actualObj, err := SomeFunction()
// assert.EqualError(t, err, expectedErrorString)
func EqualError(t TestingT, theError error, errString string, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
if !Error(t, theError, msgAndArgs...) {
return false
}
@@ -1099,6 +1206,9 @@ func matchRegexp(rx interface{}, str interface{}) bool {
// assert.Regexp(t, regexp.MustCompile("start"), "it's starting")
// assert.Regexp(t, "start...$", "it's not starting")
func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
match := matchRegexp(rx, str)
@@ -1114,6 +1224,9 @@ func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface
// assert.NotRegexp(t, regexp.MustCompile("starts"), "it's starting")
// assert.NotRegexp(t, "^start", "it's not starting")
func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
match := matchRegexp(rx, str)
if match {
@@ -1126,6 +1239,9 @@ func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interf
// Zero asserts that i is the zero value for its type.
func Zero(t TestingT, i interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
if i != nil && !reflect.DeepEqual(i, reflect.Zero(reflect.TypeOf(i)).Interface()) {
return Fail(t, fmt.Sprintf("Should be zero, but was %v", i), msgAndArgs...)
}
@@ -1134,6 +1250,9 @@ func Zero(t TestingT, i interface{}, msgAndArgs ...interface{}) bool {
// NotZero asserts that i is not the zero value for its type.
func NotZero(t TestingT, i interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
if i == nil || reflect.DeepEqual(i, reflect.Zero(reflect.TypeOf(i)).Interface()) {
return Fail(t, fmt.Sprintf("Should not be zero, but was %v", i), msgAndArgs...)
}
@@ -1142,6 +1261,9 @@ func NotZero(t TestingT, i interface{}, msgAndArgs ...interface{}) bool {
// FileExists checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file.
func FileExists(t TestingT, path string, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
info, err := os.Lstat(path)
if err != nil {
if os.IsNotExist(err) {
@@ -1157,6 +1279,9 @@ func FileExists(t TestingT, path string, msgAndArgs ...interface{}) bool {
// DirExists checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists.
func DirExists(t TestingT, path string, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
info, err := os.Lstat(path)
if err != nil {
if os.IsNotExist(err) {
@@ -1174,6 +1299,9 @@ func DirExists(t TestingT, path string, msgAndArgs ...interface{}) bool {
//
// assert.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`)
func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
var expectedJSONAsInterface, actualJSONAsInterface interface{}
if err := json.Unmarshal([]byte(expected), &expectedJSONAsInterface); err != nil {
@@ -1212,12 +1340,18 @@ func diff(expected interface{}, actual interface{}) string {
return ""
}
- if ek != reflect.Struct && ek != reflect.Map && ek != reflect.Slice && ek != reflect.Array {
+ if ek != reflect.Struct && ek != reflect.Map && ek != reflect.Slice && ek != reflect.Array && ek != reflect.String {
return ""
}
- e := spewConfig.Sdump(expected)
- a := spewConfig.Sdump(actual)
+ var e, a string
+ if ek != reflect.String {
+ e = spewConfig.Sdump(expected)
+ a = spewConfig.Sdump(actual)
+ } else {
+ e = expected.(string)
+ a = actual.(string)
+ }
diff, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
A: difflib.SplitLines(e),
@@ -1254,3 +1388,7 @@ var spewConfig = spew.ConfigState{
DisableCapacities: true,
SortKeys: true,
}
+
+type tHelper interface {
+ Helper()
+}
diff --git a/vendor/github.com/stretchr/testify/assert/http_assertions.go b/vendor/github.com/stretchr/testify/assert/http_assertions.go
index 3101e78..df46fa7 100644
--- a/vendor/github.com/stretchr/testify/assert/http_assertions.go
+++ b/vendor/github.com/stretchr/testify/assert/http_assertions.go
@@ -12,10 +12,11 @@ import (
// an error if building a new request fails.
func httpCode(handler http.HandlerFunc, method, url string, values url.Values) (int, error) {
w := httptest.NewRecorder()
- req, err := http.NewRequest(method, url+"?"+values.Encode(), nil)
+ req, err := http.NewRequest(method, url, nil)
if err != nil {
return -1, err
}
+ req.URL.RawQuery = values.Encode()
handler(w, req)
return w.Code, nil
}
@@ -26,6 +27,9 @@ func httpCode(handler http.HandlerFunc, method, url string, values url.Values) (
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
code, err := httpCode(handler, method, url, values)
if err != nil {
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
@@ -46,6 +50,9 @@ func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, value
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
code, err := httpCode(handler, method, url, values)
if err != nil {
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
@@ -66,6 +73,9 @@ func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, valu
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
code, err := httpCode(handler, method, url, values)
if err != nil {
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
@@ -95,10 +105,13 @@ func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values) s
// HTTPBodyContains asserts that a specified handler returns a
// body that contains a string.
//
-// assert.HTTPBodyContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky")
+// assert.HTTPBodyContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
body := HTTPBody(handler, method, url, values)
contains := strings.Contains(body, fmt.Sprint(str))
@@ -112,10 +125,13 @@ func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string,
// HTTPBodyNotContains asserts that a specified handler returns a
// body that does not contain a string.
//
-// assert.HTTPBodyNotContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky")
+// assert.HTTPBodyNotContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
body := HTTPBody(handler, method, url, values)
contains := strings.Contains(body, fmt.Sprint(str))
diff --git a/vendor/github.com/zyedidia/clipboard/LICENSE b/vendor/github.com/zyedidia/clipboard/LICENSE
index dee3257..bdec9a5 100644
--- a/vendor/github.com/zyedidia/clipboard/LICENSE
+++ b/vendor/github.com/zyedidia/clipboard/LICENSE
@@ -1,3 +1,7 @@
+Copyright (c) 2018 Zachary Yedidia. Modifications to atotto/clipboard.
+
+Original license:
+
Copyright (c) 2013 Ato Araki. All rights reserved.
Redistribution and use in source and binary forms, with or without
diff --git a/vendor/github.com/zyedidia/clipboard/README.md b/vendor/github.com/zyedidia/clipboard/README.md
index 6e2d15f..8691eb1 100644
--- a/vendor/github.com/zyedidia/clipboard/README.md
+++ b/vendor/github.com/zyedidia/clipboard/README.md
@@ -1,3 +1,7 @@
+# This is a fork of atotto/clipboard
+
+This fork is used for `zyedidia/micro` and has some modifications, namely: support for the primary clipboard on linux and support for an internal clipboard if the system clipboard is not available.
+
[![Build Status](https://travis-ci.org/atotto/clipboard.svg?branch=master)](https://travis-ci.org/atotto/clipboard) [![Build Status](https://drone.io/github.com/atotto/clipboard/status.png)](https://drone.io/github.com/atotto/clipboard/latest)
[![GoDoc](https://godoc.org/github.com/atotto/clipboard?status.svg)](http://godoc.org/github.com/atotto/clipboard)
diff --git a/vendor/golang.org/x/image/bmp/reader.go b/vendor/golang.org/x/image/bmp/reader.go
index a0f2715..c10a022 100644
--- a/vendor/golang.org/x/image/bmp/reader.go
+++ b/vendor/golang.org/x/image/bmp/reader.go
@@ -137,20 +137,26 @@ func decodeConfig(r io.Reader) (config image.Config, bitsPerPixel int, topDown b
// We only support those BMP images that are a BITMAPFILEHEADER
// immediately followed by a BITMAPINFOHEADER.
const (
- fileHeaderLen = 14
- infoHeaderLen = 40
+ fileHeaderLen = 14
+ infoHeaderLen = 40
+ v4InfoHeaderLen = 108
+ v5InfoHeaderLen = 124
)
var b [1024]byte
- if _, err := io.ReadFull(r, b[:fileHeaderLen+infoHeaderLen]); err != nil {
+ if _, err := io.ReadFull(r, b[:fileHeaderLen+4]); err != nil {
return image.Config{}, 0, false, err
}
if string(b[:2]) != "BM" {
return image.Config{}, 0, false, errors.New("bmp: invalid format")
}
offset := readUint32(b[10:14])
- if readUint32(b[14:18]) != infoHeaderLen {
+ infoLen := readUint32(b[14:18])
+ if infoLen != infoHeaderLen && infoLen != v4InfoHeaderLen && infoLen != v5InfoHeaderLen {
return image.Config{}, 0, false, ErrUnsupported
}
+ if _, err := io.ReadFull(r, b[fileHeaderLen+4:fileHeaderLen+infoLen]); err != nil {
+ return image.Config{}, 0, false, err
+ }
width := int(int32(readUint32(b[18:22])))
height := int(int32(readUint32(b[22:26])))
if height < 0 {
@@ -159,14 +165,22 @@ func decodeConfig(r io.Reader) (config image.Config, bitsPerPixel int, topDown b
if width < 0 || height < 0 {
return image.Config{}, 0, false, ErrUnsupported
}
- // We only support 1 plane, 8 or 24 bits per pixel and no compression.
+ // We only support 1 plane and 8, 24 or 32 bits per pixel and no
+ // compression.
planes, bpp, compression := readUint16(b[26:28]), readUint16(b[28:30]), readUint32(b[30:34])
+ // if compression is set to BITFIELDS, but the bitmask is set to the default bitmask
+ // that would be used if compression was set to 0, we can continue as if compression was 0
+ if compression == 3 && infoLen > infoHeaderLen &&
+ readUint32(b[54:58]) == 0xff0000 && readUint32(b[58:62]) == 0xff00 &&
+ readUint32(b[62:66]) == 0xff && readUint32(b[66:70]) == 0xff000000 {
+ compression = 0
+ }
if planes != 1 || compression != 0 {
return image.Config{}, 0, false, ErrUnsupported
}
switch bpp {
case 8:
- if offset != fileHeaderLen+infoHeaderLen+256*4 {
+ if offset != fileHeaderLen+infoLen+256*4 {
return image.Config{}, 0, false, ErrUnsupported
}
_, err = io.ReadFull(r, b[:256*4])
@@ -181,12 +195,12 @@ func decodeConfig(r io.Reader) (config image.Config, bitsPerPixel int, topDown b
}
return image.Config{ColorModel: pcm, Width: width, Height: height}, 8, topDown, nil
case 24:
- if offset != fileHeaderLen+infoHeaderLen {
+ if offset != fileHeaderLen+infoLen {
return image.Config{}, 0, false, ErrUnsupported
}
return image.Config{ColorModel: color.RGBAModel, Width: width, Height: height}, 24, topDown, nil
case 32:
- if offset != fileHeaderLen+infoHeaderLen {
+ if offset != fileHeaderLen+infoLen {
return image.Config{}, 0, false, ErrUnsupported
}
return image.Config{ColorModel: color.RGBAModel, Width: width, Height: height}, 32, topDown, nil
diff --git a/vendor/golang.org/x/image/bmp/writer.go b/vendor/golang.org/x/image/bmp/writer.go
index 6947968..f07b39d 100644
--- a/vendor/golang.org/x/image/bmp/writer.go
+++ b/vendor/golang.org/x/image/bmp/writer.go
@@ -49,20 +49,91 @@ func encodePaletted(w io.Writer, pix []uint8, dx, dy, stride, step int) error {
return nil
}
-func encodeRGBA(w io.Writer, pix []uint8, dx, dy, stride, step int) error {
+func encodeRGBA(w io.Writer, pix []uint8, dx, dy, stride, step int, opaque bool) error {
buf := make([]byte, step)
- for y := dy - 1; y >= 0; y-- {
- min := y*stride + 0
- max := y*stride + dx*4
- off := 0
- for i := min; i < max; i += 4 {
- buf[off+2] = pix[i+0]
- buf[off+1] = pix[i+1]
- buf[off+0] = pix[i+2]
- off += 3
+ if opaque {
+ for y := dy - 1; y >= 0; y-- {
+ min := y*stride + 0
+ max := y*stride + dx*4
+ off := 0
+ for i := min; i < max; i += 4 {
+ buf[off+2] = pix[i+0]
+ buf[off+1] = pix[i+1]
+ buf[off+0] = pix[i+2]
+ off += 3
+ }
+ if _, err := w.Write(buf); err != nil {
+ return err
+ }
}
- if _, err := w.Write(buf); err != nil {
- return err
+ } else {
+ for y := dy - 1; y >= 0; y-- {
+ min := y*stride + 0
+ max := y*stride + dx*4
+ off := 0
+ for i := min; i < max; i += 4 {
+ a := uint32(pix[i+3])
+ if a == 0 {
+ buf[off+2] = 0
+ buf[off+1] = 0
+ buf[off+0] = 0
+ buf[off+3] = 0
+ off += 4
+ continue
+ } else if a == 0xff {
+ buf[off+2] = pix[i+0]
+ buf[off+1] = pix[i+1]
+ buf[off+0] = pix[i+2]
+ buf[off+3] = 0xff
+ off += 4
+ continue
+ }
+ buf[off+2] = uint8(((uint32(pix[i+0]) * 0xffff) / a) >> 8)
+ buf[off+1] = uint8(((uint32(pix[i+1]) * 0xffff) / a) >> 8)
+ buf[off+0] = uint8(((uint32(pix[i+2]) * 0xffff) / a) >> 8)
+ buf[off+3] = uint8(a)
+ off += 4
+ }
+ if _, err := w.Write(buf); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+func encodeNRGBA(w io.Writer, pix []uint8, dx, dy, stride, step int, opaque bool) error {
+ buf := make([]byte, step)
+ if opaque {
+ for y := dy - 1; y >= 0; y-- {
+ min := y*stride + 0
+ max := y*stride + dx*4
+ off := 0
+ for i := min; i < max; i += 4 {
+ buf[off+2] = pix[i+0]
+ buf[off+1] = pix[i+1]
+ buf[off+0] = pix[i+2]
+ off += 3
+ }
+ if _, err := w.Write(buf); err != nil {
+ return err
+ }
+ }
+ } else {
+ for y := dy - 1; y >= 0; y-- {
+ min := y*stride + 0
+ max := y*stride + dx*4
+ off := 0
+ for i := min; i < max; i += 4 {
+ buf[off+2] = pix[i+0]
+ buf[off+1] = pix[i+1]
+ buf[off+0] = pix[i+2]
+ buf[off+3] = pix[i+3]
+ off += 4
+ }
+ if _, err := w.Write(buf); err != nil {
+ return err
+ }
}
}
return nil
@@ -105,6 +176,7 @@ func Encode(w io.Writer, m image.Image) error {
var step int
var palette []byte
+ var opaque bool
switch m := m.(type) {
case *image.Gray:
step = (d.X + 3) &^ 3
@@ -134,6 +206,28 @@ func Encode(w io.Writer, m image.Image) error {
h.fileSize += uint32(len(palette)) + h.imageSize
h.pixOffset += uint32(len(palette))
h.bpp = 8
+ case *image.RGBA:
+ opaque = m.Opaque()
+ if opaque {
+ step = (3*d.X + 3) &^ 3
+ h.bpp = 24
+ } else {
+ step = 4 * d.X
+ h.bpp = 32
+ }
+ h.imageSize = uint32(d.Y * step)
+ h.fileSize += h.imageSize
+ case *image.NRGBA:
+ opaque = m.Opaque()
+ if opaque {
+ step = (3*d.X + 3) &^ 3
+ h.bpp = 24
+ } else {
+ step = 4 * d.X
+ h.bpp = 32
+ }
+ h.imageSize = uint32(d.Y * step)
+ h.fileSize += h.imageSize
default:
step = (3*d.X + 3) &^ 3
h.imageSize = uint32(d.Y * step)
@@ -160,7 +254,9 @@ func Encode(w io.Writer, m image.Image) error {
case *image.Paletted:
return encodePaletted(w, m.Pix, d.X, d.Y, m.Stride, step)
case *image.RGBA:
- return encodeRGBA(w, m.Pix, d.X, d.Y, m.Stride, step)
+ return encodeRGBA(w, m.Pix, d.X, d.Y, m.Stride, step, opaque)
+ case *image.NRGBA:
+ return encodeNRGBA(w, m.Pix, d.X, d.Y, m.Stride, step, opaque)
}
return encode(w, m, step)
}
diff --git a/vendor/golang.org/x/image/tiff/reader.go b/vendor/golang.org/x/image/tiff/reader.go
index 8a941c1..ce2ef71 100644
--- a/vendor/golang.org/x/image/tiff/reader.go
+++ b/vendor/golang.org/x/image/tiff/reader.go
@@ -110,7 +110,7 @@ func (d *decoder) ifdUint(p []byte) (u []uint, err error) {
return u, nil
}
-// parseIFD decides whether the the IFD entry in p is "interesting" and
+// parseIFD decides whether the IFD entry in p is "interesting" and
// stows away the data in the decoder. It returns the tag number of the
// entry and an error, if any.
func (d *decoder) parseIFD(p []byte) (int, error) {
diff --git a/vendor/golang.org/x/image/vp8/decode.go b/vendor/golang.org/x/image/vp8/decode.go
index 1bb5028..2aa9fee 100644
--- a/vendor/golang.org/x/image/vp8/decode.go
+++ b/vendor/golang.org/x/image/vp8/decode.go
@@ -82,7 +82,7 @@ type mb struct {
pred [4]uint8
// nzMask is a mask of 8 bits: 4 for the bottom or right 4x4 luma regions,
// and 2 + 2 for the bottom or right 4x4 chroma regions. A 1 bit indicates
- // that that region has non-zero coefficients.
+ // that region has non-zero coefficients.
nzMask uint8
// nzY16 is a 0/1 value that is 1 if the macroblock used Y16 prediction and
// had non-zero coefficients.
@@ -274,7 +274,7 @@ func (d *Decoder) parseOtherPartitions() error {
var partLens [maxNOP]int
d.nOP = 1 << d.fp.readUint(uniformProb, 2)
- // The final partition length is implied by the the remaining chunk data
+ // The final partition length is implied by the remaining chunk data
// (d.r.n) and the other d.nOP-1 partition lengths. Those d.nOP-1 partition
// lengths are stored as 24-bit uints, i.e. up to 16 MiB per partition.
n := 3 * (d.nOP - 1)
diff --git a/vendor/golang.org/x/image/webp/decode.go b/vendor/golang.org/x/image/webp/decode.go
index 111f358..f77a4eb 100644
--- a/vendor/golang.org/x/image/webp/decode.go
+++ b/vendor/golang.org/x/image/webp/decode.go
@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// +build go1.6
-
package webp
import (
diff --git a/vendor/golang.org/x/image/webp/doc.go b/vendor/golang.org/x/image/webp/doc.go
new file mode 100644
index 0000000..e321c85
--- /dev/null
+++ b/vendor/golang.org/x/image/webp/doc.go
@@ -0,0 +1,9 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package webp implements a decoder for WEBP images.
+//
+// WEBP is defined at:
+// https://developers.google.com/speed/webp/docs/riff_container
+package webp // import "golang.org/x/image/webp"
diff --git a/vendor/golang.org/x/image/webp/webp.go b/vendor/golang.org/x/image/webp/webp.go
deleted file mode 100644
index 850cdc8..0000000
--- a/vendor/golang.org/x/image/webp/webp.go
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2016 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package webp implements a decoder for WEBP images.
-//
-// WEBP is defined at:
-// https://developers.google.com/speed/webp/docs/riff_container
-//
-// It requires Go 1.6 or later.
-package webp // import "golang.org/x/image/webp"
-
-// This blank Go file, other than the package clause, exists so that this
-// package can be built for Go 1.5 and earlier. (The other files in this
-// package are all marked "+build go1.6" for the NYCbCrA types introduced in Go
-// 1.6). There is no functionality in a blank package, but some image
-// manipulation programs might still underscore import this package for the
-// side effect of registering the WEBP format with the standard library's
-// image.RegisterFormat and image.Decode functions. For example, that program
-// might contain:
-//
-// // Underscore imports to register some formats for image.Decode.
-// import _ "image/gif"
-// import _ "image/jpeg"
-// import _ "image/png"
-// import _ "golang.org/x/image/webp"
-//
-// Such a program will still compile for Go 1.5 (due to this placeholder Go
-// file). It will simply not be able to recognize and decode WEBP (but still
-// handle GIF, JPEG and PNG).
diff --git a/vendor/golang.org/x/net/html/const.go b/vendor/golang.org/x/net/html/const.go
index 5eb7c5a..a3a918f 100644
--- a/vendor/golang.org/x/net/html/const.go
+++ b/vendor/golang.org/x/net/html/const.go
@@ -97,8 +97,16 @@ func isSpecialElement(element *Node) bool {
switch element.Namespace {
case "", "html":
return isSpecialElementMap[element.Data]
+ case "math":
+ switch element.Data {
+ case "mi", "mo", "mn", "ms", "mtext", "annotation-xml":
+ return true
+ }
case "svg":
- return element.Data == "foreignObject"
+ switch element.Data {
+ case "foreignObject", "desc", "title":
+ return true
+ }
}
return false
}
diff --git a/vendor/golang.org/x/net/html/parse.go b/vendor/golang.org/x/net/html/parse.go
index d23e05e..64a5793 100644
--- a/vendor/golang.org/x/net/html/parse.go
+++ b/vendor/golang.org/x/net/html/parse.go
@@ -209,27 +209,6 @@ loop:
p.oe = p.oe[:i+1]
}
-// generateAllImpliedEndTags pops nodes off the stack of open elements as long as
-// the top node has a tag name of caption, colgroup, dd, div, dt, li, optgroup, option, p, rb,
-// rp, rt, rtc, span, tbody, td, tfoot, th, thead or tr.
-func (p *parser) generateAllImpliedEndTags() {
- var i int
- for i = len(p.oe) - 1; i >= 0; i-- {
- n := p.oe[i]
- if n.Type == ElementNode {
- switch n.DataAtom {
- // TODO: remove this divergence from the HTML5 spec
- case a.Caption, a.Colgroup, a.Dd, a.Div, a.Dt, a.Li, a.Optgroup, a.Option, a.P, a.Rb,
- a.Rp, a.Rt, a.Rtc, a.Span, a.Tbody, a.Td, a.Tfoot, a.Th, a.Thead, a.Tr:
- continue
- }
- }
- break
- }
-
- p.oe = p.oe[:i+1]
-}
-
// addChild adds a child node n to the top element, and pushes n onto the stack
// of open elements if it is an element node.
func (p *parser) addChild(n *Node) {
@@ -276,7 +255,7 @@ func (p *parser) fosterParent(n *Node) {
}
}
- if template != nil && (table == nil || j < i) {
+ if template != nil && (table == nil || j > i) {
template.AppendChild(n)
return
}
@@ -491,6 +470,10 @@ func (p *parser) resetInsertionMode() {
case a.Table:
p.im = inTableIM
case a.Template:
+ // TODO: remove this divergence from the HTML5 spec.
+ if n.Namespace != "" {
+ continue
+ }
p.im = p.templateStack.top()
case a.Head:
// TODO: remove this divergence from the HTML5 spec.
@@ -679,11 +662,16 @@ func inHeadIM(p *parser) bool {
if !p.oe.contains(a.Template) {
return true
}
- p.generateAllImpliedEndTags()
- if n := p.oe.top(); n.DataAtom != a.Template {
- return true
+ // TODO: remove this divergence from the HTML5 spec.
+ //
+ // See https://bugs.chromium.org/p/chromium/issues/detail?id=829668
+ p.generateImpliedEndTags()
+ for i := len(p.oe) - 1; i >= 0; i-- {
+ if n := p.oe[i]; n.Namespace == "" && n.DataAtom == a.Template {
+ p.oe = p.oe[:i]
+ break
+ }
}
- p.popUntil(defaultScope, a.Template)
p.clearActiveFormattingElements()
p.templateStack.pop()
p.resetInsertionMode()
@@ -860,9 +848,13 @@ func inBodyIM(p *parser) bool {
// The newline, if any, will be dealt with by the TextToken case.
p.framesetOK = false
case a.Form:
- if p.oe.contains(a.Template) || p.form == nil {
- p.popUntil(buttonScope, a.P)
- p.addElement()
+ if p.form != nil && !p.oe.contains(a.Template) {
+ // Ignore the token
+ return true
+ }
+ p.popUntil(buttonScope, a.P)
+ p.addElement()
+ if !p.oe.contains(a.Template) {
p.form = p.top()
}
case a.Li:
@@ -996,6 +988,14 @@ func inBodyIM(p *parser) bool {
p.acknowledgeSelfClosingTag()
p.popUntil(buttonScope, a.P)
p.parseImpliedToken(StartTagToken, a.Form, a.Form.String())
+ if p.form == nil {
+ // NOTE: The 'isindex' element has been removed,
+ // and the 'template' element has not been designed to be
+ // collaborative with the index element.
+ //
+ // Ignore the token.
+ return true
+ }
if action != "" {
p.form.Attr = []Attribute{{Key: "action", Val: action}}
}
@@ -1070,13 +1070,7 @@ func inBodyIM(p *parser) bool {
p.acknowledgeSelfClosingTag()
}
return true
- case a.Frame:
- // TODO: remove this divergence from the HTML5 spec.
- if p.oe.contains(a.Template) {
- p.addElement()
- return true
- }
- case a.Caption, a.Col, a.Colgroup, a.Head, a.Tbody, a.Td, a.Tfoot, a.Th, a.Thead, a.Tr:
+ case a.Caption, a.Col, a.Colgroup, a.Frame, a.Head, a.Tbody, a.Td, a.Tfoot, a.Th, a.Thead, a.Tr:
// Ignore the token.
default:
p.reconstructActiveFormattingElements()
@@ -1098,12 +1092,13 @@ func inBodyIM(p *parser) bool {
p.popUntil(defaultScope, p.tok.DataAtom)
case a.Form:
if p.oe.contains(a.Template) {
- if !p.oe.contains(a.Form) {
+ i := p.indexOfElementInScope(defaultScope, a.Form)
+ if i == -1 {
// Ignore the token.
return true
}
p.generateImpliedEndTags()
- if p.tok.DataAtom == a.Form {
+ if p.oe[i].DataAtom != a.Form {
// Ignore the token.
return true
}
@@ -1269,12 +1264,6 @@ func (p *parser) inBodyEndTagFormatting(tagAtom a.Atom) {
switch commonAncestor.DataAtom {
case a.Table, a.Tbody, a.Tfoot, a.Thead, a.Tr:
p.fosterParent(lastNode)
- case a.Template:
- // TODO: remove namespace checking
- if commonAncestor.Namespace == "html" {
- commonAncestor = commonAncestor.LastChild
- }
- fallthrough
default:
commonAncestor.AppendChild(lastNode)
}
@@ -1346,9 +1335,6 @@ func textIM(p *parser) bool {
// Section 12.2.6.4.9.
func inTableIM(p *parser) bool {
switch p.tok.Type {
- case ErrorToken:
- // Stop parsing.
- return true
case TextToken:
p.tok.Data = strings.Replace(p.tok.Data, "\x00", "", -1)
switch p.oe.top().DataAtom {
@@ -1443,6 +1429,8 @@ func inTableIM(p *parser) bool {
case DoctypeToken:
// Ignore the token.
return true
+ case ErrorToken:
+ return inBodyIM(p)
}
p.fosterParenting = true
@@ -1545,6 +1533,8 @@ func inColumnGroupIM(p *parser) bool {
case a.Template:
return inHeadIM(p)
}
+ case ErrorToken:
+ return inBodyIM(p)
}
if p.oe.top().DataAtom != a.Colgroup {
return true
@@ -1709,9 +1699,6 @@ func inCellIM(p *parser) bool {
// Section 12.2.6.4.16.
func inSelectIM(p *parser) bool {
switch p.tok.Type {
- case ErrorToken:
- // Stop parsing.
- return true
case TextToken:
p.addText(strings.Replace(p.tok.Data, "\x00", "", -1))
case StartTagToken:
@@ -1775,6 +1762,8 @@ func inSelectIM(p *parser) bool {
case DoctypeToken:
// Ignore the token.
return true
+ case ErrorToken:
+ return inBodyIM(p)
}
return true
@@ -1841,15 +1830,26 @@ func inTemplateIM(p *parser) bool {
// Ignore the token.
return true
}
+ case ErrorToken:
+ if !p.oe.contains(a.Template) {
+ // Ignore the token.
+ return true
+ }
+ // TODO: remove this divergence from the HTML5 spec.
+ //
+ // See https://bugs.chromium.org/p/chromium/issues/detail?id=829668
+ p.generateImpliedEndTags()
+ for i := len(p.oe) - 1; i >= 0; i-- {
+ if n := p.oe[i]; n.Namespace == "" && n.DataAtom == a.Template {
+ p.oe = p.oe[:i]
+ break
+ }
+ }
+ p.clearActiveFormattingElements()
+ p.templateStack.pop()
+ p.resetInsertionMode()
+ return false
}
- if !p.oe.contains(a.Template) {
- // Ignore the token.
- return true
- }
- p.popUntil(defaultScope, a.Template)
- p.clearActiveFormattingElements()
- p.templateStack.pop()
- p.resetInsertionMode()
return false
}
@@ -1923,11 +1923,6 @@ func inFramesetIM(p *parser) bool {
p.acknowledgeSelfClosingTag()
case a.Noframes:
return inHeadIM(p)
- case a.Template:
- // TODO: remove this divergence from the HTML5 spec.
- //
- // See https://bugs.chromium.org/p/chromium/issues/detail?id=829668
- return inTemplateIM(p)
}
case EndTagToken:
switch p.tok.DataAtom {
@@ -2220,6 +2215,15 @@ func (p *parser) parse() error {
}
// Parse returns the parse tree for the HTML from the given Reader.
+//
+// It implements the HTML5 parsing algorithm
+// (https://html.spec.whatwg.org/multipage/syntax.html#tree-construction),
+// which is very complicated. The resultant tree can contain implicitly created
+// nodes that have no explicit <tag> listed in r's data, and nodes' parents can
+// differ from the nesting implied by a naive processing of start and end
+// <tag>s. Conversely, explicit <tag>s in r's data can be silently dropped,
+// with no corresponding node in the resulting tree.
+//
// The input is assumed to be UTF-8 encoded.
func Parse(r io.Reader) (*Node, error) {
p := &parser{
@@ -2241,6 +2245,8 @@ func Parse(r io.Reader) (*Node, error) {
// ParseFragment parses a fragment of HTML and returns the nodes that were
// found. If the fragment is the InnerHTML for an existing element, pass that
// element in context.
+//
+// It has the same intricacies as Parse.
func ParseFragment(r io.Reader, context *Node) ([]*Node, error) {
contextTag := ""
if context != nil {
diff --git a/vendor/gopkg.in/russross/blackfriday.v2/.travis.yml b/vendor/gopkg.in/russross/blackfriday.v2/.travis.yml
index a4eb257..b0b525a 100644
--- a/vendor/gopkg.in/russross/blackfriday.v2/.travis.yml
+++ b/vendor/gopkg.in/russross/blackfriday.v2/.travis.yml
@@ -1,18 +1,17 @@
-# Travis CI (http://travis-ci.org/) is a continuous integration service for
-# open source projects. This file configures it to run unit tests for
-# blackfriday.
-
+sudo: false
language: go
-
go:
- - 1.5
- - 1.6
- - 1.7
-
+ - "1.10.x"
+ - "1.11.x"
+ - tip
+matrix:
+ fast_finish: true
+ allow_failures:
+ - go: tip
install:
- - go get -d -t -v ./...
- - go build -v ./...
-
+ - # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step).
script:
- - go test -v ./...
- - go test -run=^$ -bench=BenchmarkReference -benchmem
+ - go get -t -v ./...
+ - diff -u <(echo -n) <(gofmt -d -s .)
+ - go tool vet .
+ - go test -v ./...
diff --git a/vendor/gopkg.in/russross/blackfriday.v2/README.md b/vendor/gopkg.in/russross/blackfriday.v2/README.md
index 2e0db35..d5a8649 100644
--- a/vendor/gopkg.in/russross/blackfriday.v2/README.md
+++ b/vendor/gopkg.in/russross/blackfriday.v2/README.md
@@ -34,9 +34,15 @@ Versions
--------
Currently maintained and recommended version of Blackfriday is `v2`. It's being
-developed on its own branch: https://github.com/russross/blackfriday/v2. You
-should install and import it via [gopkg.in][6] at
-`gopkg.in/russross/blackfriday.v2`.
+developed on its own branch: https://github.com/russross/blackfriday/tree/v2 and the
+documentation is available at
+https://godoc.org/gopkg.in/russross/blackfriday.v2.
+
+It is `go get`-able via via [gopkg.in][6] at `gopkg.in/russross/blackfriday.v2`,
+but we highly recommend using package management tool like [dep][7] or
+[Glide][8] and make use of semantic versioning. With package management you
+should import `github.com/russross/blackfriday` and specify that you're using
+version 2.0.0.
Version 2 offers a number of improvements over v1:
@@ -198,7 +204,7 @@ implements the following extensions:
Cat
: Fluffy animal everyone likes
-
+
Internet
: Vector of transmission for pictures of cats
@@ -209,7 +215,7 @@ implements the following extensions:
end of the document. A footnote looks like this:
This is a footnote.[^1]
-
+
[^1]: the footnote text.
* **Autolinking**. Blackfriday can find URLs that have not been
@@ -255,9 +261,11 @@ are a few of note:
* [markdownfmt](https://github.com/shurcooL/markdownfmt): like gofmt,
but for markdown.
-* [LaTeX output](https://bitbucket.org/ambrevar/blackfriday-latex):
+* [LaTeX output](https://github.com/Ambrevar/Blackfriday-LaTeX):
renders output as LaTeX.
+* [Blackfriday-Confluence](https://github.com/kentaro-m/blackfriday-confluence): provides a [Confluence Wiki Markup](https://confluence.atlassian.com/doc/confluence-wiki-markup-251003035.html) renderer.
+
Todo
----
diff --git a/vendor/gopkg.in/russross/blackfriday.v2/block.go b/vendor/gopkg.in/russross/blackfriday.v2/block.go
index d7da33f..b860747 100644
--- a/vendor/gopkg.in/russross/blackfriday.v2/block.go
+++ b/vendor/gopkg.in/russross/blackfriday.v2/block.go
@@ -17,6 +17,7 @@ import (
"bytes"
"html"
"regexp"
+ "strings"
"github.com/shurcooL/sanitized_anchor_name"
)
@@ -568,8 +569,8 @@ func (*Markdown) isHRule(data []byte) bool {
// isFenceLine checks if there's a fence line (e.g., ``` or ``` go) at the beginning of data,
// and returns the end index if so, or 0 otherwise. It also returns the marker found.
-// If syntax is not nil, it gets set to the syntax specified in the fence line.
-func isFenceLine(data []byte, syntax *string, oldmarker string) (end int, marker string) {
+// If info is not nil, it gets set to the syntax specified in the fence line.
+func isFenceLine(data []byte, info *string, oldmarker string) (end int, marker string) {
i, size := 0, 0
// skip up to three spaces
@@ -605,9 +606,9 @@ func isFenceLine(data []byte, syntax *string, oldmarker string) (end int, marker
}
// TODO(shurcooL): It's probably a good idea to simplify the 2 code paths here
- // into one, always get the syntax, and discard it if the caller doesn't care.
- if syntax != nil {
- syn := 0
+ // into one, always get the info string, and discard it if the caller doesn't care.
+ if info != nil {
+ infoLength := 0
i = skipChar(data, i, ' ')
if i >= len(data) {
@@ -617,14 +618,14 @@ func isFenceLine(data []byte, syntax *string, oldmarker string) (end int, marker
return 0, ""
}
- syntaxStart := i
+ infoStart := i
if data[i] == '{' {
i++
- syntaxStart++
+ infoStart++
for i < len(data) && data[i] != '}' && data[i] != '\n' {
- syn++
+ infoLength++
i++
}
@@ -634,31 +635,30 @@ func isFenceLine(data []byte, syntax *string, oldmarker string) (end int, marker
// strip all whitespace at the beginning and the end
// of the {} block
- for syn > 0 && isspace(data[syntaxStart]) {
- syntaxStart++
- syn--
+ for infoLength > 0 && isspace(data[infoStart]) {
+ infoStart++
+ infoLength--
}
- for syn > 0 && isspace(data[syntaxStart+syn-1]) {
- syn--
+ for infoLength > 0 && isspace(data[infoStart+infoLength-1]) {
+ infoLength--
}
-
i++
+ i = skipChar(data, i, ' ')
} else {
- for i < len(data) && !isspace(data[i]) {
- syn++
+ for i < len(data) && !isverticalspace(data[i]) {
+ infoLength++
i++
}
}
- *syntax = string(data[syntaxStart : syntaxStart+syn])
+ *info = strings.TrimSpace(string(data[infoStart : infoStart+infoLength]))
}
- i = skipChar(data, i, ' ')
- if i >= len(data) || data[i] != '\n' {
- if i == len(data) {
- return i, marker
- }
+ if i == len(data) {
+ return i, marker
+ }
+ if i > len(data) || data[i] != '\n' {
return 0, ""
}
return i + 1, marker // Take newline into account.
@@ -668,14 +668,14 @@ func isFenceLine(data []byte, syntax *string, oldmarker string) (end int, marker
// or 0 otherwise. It writes to out if doRender is true, otherwise it has no side effects.
// If doRender is true, a final newline is mandatory to recognize the fenced code block.
func (p *Markdown) fencedCodeBlock(data []byte, doRender bool) int {
- var syntax string
- beg, marker := isFenceLine(data, &syntax, "")
+ var info string
+ beg, marker := isFenceLine(data, &info, "")
if beg == 0 || beg >= len(data) {
return 0
}
var work bytes.Buffer
- work.Write([]byte(syntax))
+ work.Write([]byte(info))
work.WriteByte('\n')
for {
@@ -1148,6 +1148,18 @@ func (p *Markdown) list(data []byte, flags ListType) int {
return i
}
+// Returns true if the list item is not the same type as its parent list
+func (p *Markdown) listTypeChanged(data []byte, flags *ListType) bool {
+ if p.dliPrefix(data) > 0 && *flags&ListTypeDefinition == 0 {
+ return true
+ } else if p.oliPrefix(data) > 0 && *flags&ListTypeOrdered == 0 {
+ return true
+ } else if p.uliPrefix(data) > 0 && (*flags&ListTypeOrdered != 0 || *flags&ListTypeDefinition != 0) {
+ return true
+ }
+ return false
+}
+
// Returns true if block ends with a blank line, descending if needed
// into lists and sublists.
func endsWithBlankLine(block *Node) bool {
@@ -1246,6 +1258,7 @@ func (p *Markdown) listItem(data []byte, flags *ListType) int {
// process the following lines
containsBlankLine := false
sublist := 0
+ codeBlockMarker := ""
gatherlines:
for line < len(data) {
@@ -1279,6 +1292,27 @@ gatherlines:
chunk := data[line+indentIndex : i]
+ if p.extensions&FencedCode != 0 {
+ // determine if in or out of codeblock
+ // if in codeblock, ignore normal list processing
+ _, marker := isFenceLine(chunk, nil, codeBlockMarker)
+ if marker != "" {
+ if codeBlockMarker == "" {
+ // start of codeblock
+ codeBlockMarker = marker
+ } else {
+ // end of codeblock.
+ codeBlockMarker = ""
+ }
+ }
+ // we are in a codeblock, write line, and continue
+ if codeBlockMarker != "" || marker != "" {
+ raw.Write(data[line+indentIndex : i])
+ line = i
+ continue gatherlines
+ }
+ }
+
// evaluate how this line fits in
switch {
// is this a nested list item?
@@ -1286,16 +1320,23 @@ gatherlines:
p.oliPrefix(chunk) > 0 ||
p.dliPrefix(chunk) > 0:
- if containsBlankLine {
- *flags |= ListItemContainsBlock
- }
-
// to be a nested list, it must be indented more
- // if not, it is the next item in the same list
+ // if not, it is either a different kind of list
+ // or the next item in the same list
if indent <= itemIndent {
+ if p.listTypeChanged(chunk, flags) {
+ *flags |= ListItemEndOfList
+ } else if containsBlankLine {
+ *flags |= ListItemContainsBlock
+ }
+
break gatherlines
}
+ if containsBlankLine {
+ *flags |= ListItemContainsBlock
+ }
+
// is this the first item in the nested list?
if sublist == 0 {
sublist = raw.Len()
diff --git a/vendor/gopkg.in/russross/blackfriday.v2/go.mod b/vendor/gopkg.in/russross/blackfriday.v2/go.mod
new file mode 100644
index 0000000..620b74e
--- /dev/null
+++ b/vendor/gopkg.in/russross/blackfriday.v2/go.mod
@@ -0,0 +1 @@
+module github.com/russross/blackfriday/v2
diff --git a/vendor/gopkg.in/russross/blackfriday.v2/html.go b/vendor/gopkg.in/russross/blackfriday.v2/html.go
index 25fb185..284c871 100644
--- a/vendor/gopkg.in/russross/blackfriday.v2/html.go
+++ b/vendor/gopkg.in/russross/blackfriday.v2/html.go
@@ -35,6 +35,7 @@ const (
Safelink // Only link to trusted protocols
NofollowLinks // Only link with rel="nofollow"
NoreferrerLinks // Only link with rel="noreferrer"
+ NoopenerLinks // Only link with rel="noopener"
HrefTargetBlank // Add a blank target
CompletePage // Generate a complete HTML page
UseXHTML // Generate XHTML output instead of HTML
@@ -87,6 +88,10 @@ type HTMLRendererParameters struct {
HeadingIDPrefix string
// If set, add this text to the back of each Heading ID, to ensure uniqueness.
HeadingIDSuffix string
+ // Increase heading levels: if the offset is 1, <h1> becomes <h2> etc.
+ // Negative offset is also valid.
+ // Resulting levels are clipped between 1 and 6.
+ HeadingLevelOffset int
Title string // Document title (used if CompletePage is set)
CSS string // Optional CSS file URL (used if CompletePage is set)
@@ -282,6 +287,9 @@ func appendLinkAttrs(attrs []string, flags HTMLFlags, link []byte) []string {
if flags&NoreferrerLinks != 0 {
val = append(val, "noreferrer")
}
+ if flags&NoopenerLinks != 0 {
+ val = append(val, "noopener")
+ }
if flags&HrefTargetBlank != 0 {
attrs = append(attrs, "target=\"_blank\"")
}
@@ -331,7 +339,7 @@ func (r *HTMLRenderer) tag(w io.Writer, name []byte, attrs []string) {
func footnoteRef(prefix string, node *Node) []byte {
urlFrag := prefix + string(slugify(node.Destination))
- anchor := fmt.Sprintf(`<a rel="footnote" href="#fn:%s">%d</a>`, urlFrag, node.NoteID)
+ anchor := fmt.Sprintf(`<a href="#fn:%s">%d</a>`, urlFrag, node.NoteID)
return []byte(fmt.Sprintf(`<sup class="footnote-ref" id="fnref:%s">%s</sup>`, urlFrag, anchor))
}
@@ -460,9 +468,10 @@ var (
)
func headingTagsFromLevel(level int) ([]byte, []byte) {
- switch level {
- case 1:
+ if level <= 1 {
return h1Tag, h1CloseTag
+ }
+ switch level {
case 2:
return h2Tag, h2CloseTag
case 3:
@@ -471,9 +480,8 @@ func headingTagsFromLevel(level int) ([]byte, []byte) {
return h4Tag, h4CloseTag
case 5:
return h5Tag, h5CloseTag
- default:
- return h6Tag, h6CloseTag
}
+ return h6Tag, h6CloseTag
}
func (r *HTMLRenderer) outHRTag(w io.Writer) {
@@ -651,7 +659,8 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt
r.out(w, node.Literal)
r.cr(w)
case Heading:
- openTag, closeTag := headingTagsFromLevel(node.Level)
+ headingLevel := r.HTMLRendererParameters.HeadingLevelOffset + node.Level
+ openTag, closeTag := headingTagsFromLevel(headingLevel)
if entering {
if node.IsTitleblock {
attrs = append(attrs, `class="title"`)
diff --git a/vendor/gopkg.in/russross/blackfriday.v2/inline.go b/vendor/gopkg.in/russross/blackfriday.v2/inline.go
index 3d63310..4ed2907 100644
--- a/vendor/gopkg.in/russross/blackfriday.v2/inline.go
+++ b/vendor/gopkg.in/russross/blackfriday.v2/inline.go
@@ -23,8 +23,22 @@ var (
urlRe = `((https?|ftp):\/\/|\/)[-A-Za-z0-9+&@#\/%?=~_|!:,.;\(\)]+`
anchorRe = regexp.MustCompile(`^(<a\shref="` + urlRe + `"(\stitle="[^"<>]+")?\s?>` + urlRe + `<\/a>)`)
- // TODO: improve this regexp to catch all possible entities:
- htmlEntityRe = regexp.MustCompile(`&[a-z]{2,5};`)
+ // https://www.w3.org/TR/html5/syntax.html#character-references
+ // highest unicode code point in 17 planes (2^20): 1,114,112d =
+ // 7 dec digits or 6 hex digits
+ // named entity references can be 2-31 characters with stuff like &lt;
+ // at one end and &CounterClockwiseContourIntegral; at the other. There
+ // are also sometimes numbers at the end, although this isn't inherent
+ // in the specification; there are never numbers anywhere else in
+ // current character references, though; see &frac34; and &blk12;, etc.
+ // https://www.w3.org/TR/html5/syntax.html#named-character-references
+ //
+ // entity := "&" (named group | number ref) ";"
+ // named group := [a-zA-Z]{2,31}[0-9]{0,2}
+ // number ref := "#" (dec ref | hex ref)
+ // dec ref := [0-9]{1,7}
+ // hex ref := ("x" | "X") [0-9a-fA-F]{1,6}
+ htmlEntityRe = regexp.MustCompile(`&([a-zA-Z]{2,31}[0-9]{0,2}|#([0-9]{1,7}|[xX][0-9a-fA-F]{1,6}));`)
)
// Functions to parse text within a block
diff --git a/vendor/gopkg.in/russross/blackfriday.v2/markdown.go b/vendor/gopkg.in/russross/blackfriday.v2/markdown.go
index ff61cb0..58d2e45 100644
--- a/vendor/gopkg.in/russross/blackfriday.v2/markdown.go
+++ b/vendor/gopkg.in/russross/blackfriday.v2/markdown.go
@@ -93,46 +93,46 @@ const (
// blockTags is a set of tags that are recognized as HTML block tags.
// Any of these can be included in markdown text without special escaping.
var blockTags = map[string]struct{}{
- "blockquote": struct{}{},
- "del": struct{}{},
- "div": struct{}{},
- "dl": struct{}{},
- "fieldset": struct{}{},
- "form": struct{}{},
- "h1": struct{}{},
- "h2": struct{}{},
- "h3": struct{}{},
- "h4": struct{}{},
- "h5": struct{}{},
- "h6": struct{}{},
- "iframe": struct{}{},
- "ins": struct{}{},
- "math": struct{}{},
- "noscript": struct{}{},
- "ol": struct{}{},
- "pre": struct{}{},
- "p": struct{}{},
- "script": struct{}{},
- "style": struct{}{},
- "table": struct{}{},
- "ul": struct{}{},
+ "blockquote": {},
+ "del": {},
+ "div": {},
+ "dl": {},
+ "fieldset": {},
+ "form": {},
+ "h1": {},
+ "h2": {},
+ "h3": {},
+ "h4": {},
+ "h5": {},
+ "h6": {},
+ "iframe": {},
+ "ins": {},
+ "math": {},
+ "noscript": {},
+ "ol": {},
+ "pre": {},
+ "p": {},
+ "script": {},
+ "style": {},
+ "table": {},
+ "ul": {},
// HTML5
- "address": struct{}{},
- "article": struct{}{},
- "aside": struct{}{},
- "canvas": struct{}{},
- "figcaption": struct{}{},
- "figure": struct{}{},
- "footer": struct{}{},
- "header": struct{}{},
- "hgroup": struct{}{},
- "main": struct{}{},
- "nav": struct{}{},
- "output": struct{}{},
- "progress": struct{}{},
- "section": struct{}{},
- "video": struct{}{},
+ "address": {},
+ "article": {},
+ "aside": {},
+ "canvas": {},
+ "figcaption": {},
+ "figure": {},
+ "footer": {},
+ "header": {},
+ "hgroup": {},
+ "main": {},
+ "nav": {},
+ "output": {},
+ "progress": {},
+ "section": {},
+ "video": {},
}
// Renderer is the rendering interface. This is mostly of interest if you are
@@ -480,11 +480,11 @@ func (p *Markdown) parseRefsToAST() {
// [^note]: This is the explanation.
//
// Footnotes should be placed at the end of the document in an ordered list.
-// Inline footnotes such as:
+// Finally, there are inline footnotes such as:
//
-// Inline footnotes^[Not supported.] also exist.
+// Inline footnotes^[Also supported.] provide a quick inline explanation,
+// but are rendered at the bottom of the document.
//
-// are not yet supported.
// reference holds all information necessary for a reference-style links or
// footnotes.
@@ -813,7 +813,17 @@ func ispunct(c byte) bool {
// Test if a character is a whitespace character.
func isspace(c byte) bool {
- return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v'
+ return ishorizontalspace(c) || isverticalspace(c)
+}
+
+// Test if a character is a horizontal whitespace character.
+func ishorizontalspace(c byte) bool {
+ return c == ' ' || c == '\t'
+}
+
+// Test if a character is a vertical character.
+func isverticalspace(c byte) bool {
+ return c == '\n' || c == '\r' || c == '\f' || c == '\v'
}
// Test if a character is letter.
diff --git a/vendor/gopkg.in/toast.v1/toast.go b/vendor/gopkg.in/toast.v1/toast.go
index 1782e5e..7ea32c9 100644
--- a/vendor/gopkg.in/toast.v1/toast.go
+++ b/vendor/gopkg.in/toast.v1/toast.go
@@ -11,6 +11,7 @@ import (
"text/template"
"github.com/nu7hatch/gouuid"
+ "syscall"
)
var toastTemplate *template.Template
@@ -347,7 +348,9 @@ func invokeTemporaryScript(content string) error {
if err != nil {
return err
}
- if err = exec.Command("PowerShell", "-ExecutionPolicy", "Bypass", "-File", file).Run(); err != nil {
+ cmd := exec.Command("PowerShell", "-ExecutionPolicy", "Bypass", "-File", file)
+ cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
+ if err = cmd.Run(); err != nil {
return err
}
return nil
diff --git a/vendor/maunium.net/go/gomatrix/.gitignore b/vendor/maunium.net/go/gomatrix/.gitignore
deleted file mode 100644
index daf913b..0000000
--- a/vendor/maunium.net/go/gomatrix/.gitignore
+++ /dev/null
@@ -1,24 +0,0 @@
-# Compiled Object files, Static and Dynamic libs (Shared Objects)
-*.o
-*.a
-*.so
-
-# Folders
-_obj
-_test
-
-# Architecture specific extensions/prefixes
-*.[568vq]
-[568vq].out
-
-*.cgo1.go
-*.cgo2.c
-_cgo_defun.c
-_cgo_gotypes.go
-_cgo_export.*
-
-_testmain.go
-
-*.exe
-*.test
-*.prof
diff --git a/vendor/maunium.net/go/gomatrix/.travis.yml b/vendor/maunium.net/go/gomatrix/.travis.yml
deleted file mode 100644
index fadc326..0000000
--- a/vendor/maunium.net/go/gomatrix/.travis.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-language: go
-go:
- - 1.8
-install:
- - go get github.com/golang/lint/golint
- - go get github.com/fzipp/gocyclo
- - go get github.com/client9/misspell/...
- - go get github.com/gordonklaus/ineffassign
-script: ./hooks/pre-commit
diff --git a/vendor/maunium.net/go/gomatrix/README.md b/vendor/maunium.net/go/gomatrix/README.md
deleted file mode 100644
index ea9109a..0000000
--- a/vendor/maunium.net/go/gomatrix/README.md
+++ /dev/null
@@ -1,6 +0,0 @@
-# gomatrix
-[![GoDoc](https://godoc.org/github.com/matrix-org/gomatrix?status.svg)](https://godoc.org/github.com/matrix-org/gomatrix)
-
-A Golang Matrix client.
-
-**THIS IS UNDER ACTIVE DEVELOPMENT: BREAKING CHANGES ARE FREQUENT.**
diff --git a/vendor/maunium.net/go/gomatrix/events.go b/vendor/maunium.net/go/gomatrix/events.go
deleted file mode 100644
index 7233c7c..0000000
--- a/vendor/maunium.net/go/gomatrix/events.go
+++ /dev/null
@@ -1,110 +0,0 @@
-package gomatrix
-
-import (
- "html"
- "regexp"
-)
-
-// Event represents a single Matrix event.
-type Event struct {
- StateKey *string `json:"state_key,omitempty"` // The state key for the event. Only present on State Events.
- Sender string `json:"sender"` // The user ID of the sender of the event
- Type string `json:"type"` // The event type
- Timestamp int64 `json:"origin_server_ts"` // The unix timestamp when this message was sent by the origin server
- ID string `json:"event_id"` // The unique ID of this event
- RoomID string `json:"room_id"` // The room the event was sent to. May be nil (e.g. for presence)
- Content map[string]interface{} `json:"content"` // The JSON content of the event.
- Redacts string `json:"redacts,omitempty"` // The event ID that was redacted if a m.room.redaction event
- Unsigned Unsigned `json:"unsigned,omitempty"` // Unsigned content set by own homeserver.
-}
-
-type Unsigned struct {
- PrevContent map[string]interface{} `json:"prev_content,omitempty"`
- PrevSender string `json:"prev_sender,omitempty"`
- ReplacesState string `json:"replaces_state,omitempty"`
- Age int64 `json:"age"`
-}
-
-// Body returns the value of the "body" key in the event content if it is
-// present and is a string.
-func (event *Event) Body() (body string, ok bool) {
- value, exists := event.Content["body"]
- if !exists {
- return
- }
- body, ok = value.(string)
- return
-}
-
-// MessageType returns the value of the "msgtype" key in the event content if
-// it is present and is a string.
-func (event *Event) MessageType() (msgtype string, ok bool) {
- value, exists := event.Content["msgtype"]
- if !exists {
- return
- }
- msgtype, ok = value.(string)
- return
-}
-
-// TextMessage is the contents of a Matrix formated message event.
-type TextMessage struct {
- MsgType string `json:"msgtype"`
- Body string `json:"body"`
-}
-
-// ImageInfo contains info about an image - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-image
-type ImageInfo struct {
- Height uint `json:"h,omitempty"`
- Width uint `json:"w,omitempty"`
- Mimetype string `json:"mimetype,omitempty"`
- Size uint `json:"size,omitempty"`
-}
-
-// VideoInfo contains info about a video - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-video
-type VideoInfo struct {
- Mimetype string `json:"mimetype,omitempty"`
- ThumbnailInfo ImageInfo `json:"thumbnail_info"`
- ThumbnailURL string `json:"thumbnail_url,omitempty"`
- Height uint `json:"h,omitempty"`
- Width uint `json:"w,omitempty"`
- Duration uint `json:"duration,omitempty"`
- Size uint `json:"size,omitempty"`
-}
-
-// VideoMessage is an m.video - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-video
-type VideoMessage struct {
- MsgType string `json:"msgtype"`
- Body string `json:"body"`
- URL string `json:"url"`
- Info VideoInfo `json:"info"`
-}
-
-// ImageMessage is an m.image event
-type ImageMessage struct {
- MsgType string `json:"msgtype"`
- Body string `json:"body"`
- URL string `json:"url"`
- Info ImageInfo `json:"info"`
-}
-
-// An HTMLMessage is the contents of a Matrix HTML formated message event.
-type HTMLMessage struct {
- Body string `json:"body"`
- MsgType string `json:"msgtype"`
- Format string `json:"format"`
- FormattedBody string `json:"formatted_body"`
-}
-
-var htmlRegex = regexp.MustCompile("<[^<]+?>")
-
-// GetHTMLMessage returns an HTMLMessage with the body set to a stripped version of the provided HTML, in addition
-// to the provided HTML.
-func GetHTMLMessage(msgtype, htmlText string) HTMLMessage {
- return HTMLMessage{
- Body: html.UnescapeString(htmlRegex.ReplaceAllLiteralString(htmlText, "")),
- MsgType: msgtype,
- Format: "org.matrix.custom.html",
- FormattedBody: htmlText,
- }
-}
diff --git a/vendor/maunium.net/go/mautrix/.gitignore b/vendor/maunium.net/go/mautrix/.gitignore
new file mode 100644
index 0000000..66f8fb5
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/.gitignore
@@ -0,0 +1,2 @@
+.idea/
+.vscode/
diff --git a/vendor/maunium.net/go/gomatrix/LICENSE b/vendor/maunium.net/go/mautrix/LICENSE
index 8dada3e..8dada3e 100644
--- a/vendor/maunium.net/go/gomatrix/LICENSE
+++ b/vendor/maunium.net/go/mautrix/LICENSE
diff --git a/vendor/maunium.net/go/mautrix/README.md b/vendor/maunium.net/go/mautrix/README.md
new file mode 100644
index 0000000..ca135a6
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/README.md
@@ -0,0 +1,4 @@
+# mautrix-go
+[![GoDoc](https://godoc.org/maunium.net/go/mautrix?status.svg)](https://godoc.org/maunium.net/go/mautrix)
+
+A Golang Matrix framework.
diff --git a/vendor/maunium.net/go/gomatrix/client.go b/vendor/maunium.net/go/mautrix/client.go
index 7725ac3..d908b62 100644
--- a/vendor/maunium.net/go/gomatrix/client.go
+++ b/vendor/maunium.net/go/mautrix/client.go
@@ -1,11 +1,12 @@
-// Package gomatrix implements the Matrix Client-Server API.
+// Package mautrix implements the Matrix Client-Server API.
//
-// Specification can be found at http://matrix.org/docs/spec/client_server/r0.2.0.html
-package gomatrix
+// Specification can be found at http://matrix.org/docs/spec/client_server/r0.4.0.html
+package mautrix
import (
"bytes"
"encoding/json"
+ "errors"
"fmt"
"io"
"io/ioutil"
@@ -13,10 +14,15 @@ import (
"net/url"
"path"
"strconv"
+ "strings"
"sync"
"time"
)
+type Logger interface {
+ Debugfln(message string, args ...interface{})
+}
+
// Client represents a Matrix client.
type Client struct {
HomeserverURL *url.URL // The base homeserver URL
@@ -26,6 +32,7 @@ type Client struct {
Client *http.Client // The underlying HTTP client which will be used to make HTTP requests.
Syncer Syncer // The thing which can process /sync responses
Store Storer // The thing which can store rooms/tokens/ids
+ Logger Logger
// The ?user_id= query parameter for application services. This must be set *prior* to calling a method. If this is empty,
// no user_id parameter will be sent.
@@ -39,6 +46,7 @@ type Client struct {
// HTTPError An HTTP Error response, which may wrap an underlying native Go Error.
type HTTPError struct {
WrappedError error
+ RespError *RespError
Message string
Code int
}
@@ -127,7 +135,6 @@ func (cli *Client) Sync() error {
filterID = resFilter.FilterID
cli.Store.SaveFilterID(cli.UserID, filterID)
}
-
for {
resSync, err := cli.SyncRequest(30000, nextBatch, filterID, false, "")
if err != nil {
@@ -177,6 +184,14 @@ func (cli *Client) StopSync() {
cli.incrementSyncingID()
}
+func (cli *Client) LogRequest(req *http.Request, body string) {
+ if cli.Logger == nil {
+ return
+ }
+
+ cli.Logger.Debugfln("%s %s %s", req.Method, req.URL.Path, body)
+}
+
// MakeRequest makes a JSON HTTP request to the given URL.
// If "resBody" is not nil, the response body will be json.Unmarshalled into it.
//
@@ -186,12 +201,14 @@ func (cli *Client) StopSync() {
func (cli *Client) MakeRequest(method string, httpURL string, reqBody interface{}, resBody interface{}) ([]byte, error) {
var req *http.Request
var err error
+ logBody := "{}"
if reqBody != nil {
var jsonStr []byte
jsonStr, err = json.Marshal(reqBody)
if err != nil {
return nil, err
}
+ logBody = string(jsonStr)
req, err = http.NewRequest(method, httpURL, bytes.NewBuffer(jsonStr))
} else {
req, err = http.NewRequest(method, httpURL, nil)
@@ -201,6 +218,7 @@ func (cli *Client) MakeRequest(method string, httpURL string, reqBody interface{
return nil, err
}
req.Header.Set("Content-Type", "application/json")
+ cli.LogRequest(req, logBody)
res, err := cli.Client.Do(req)
if res != nil {
defer res.Body.Close()
@@ -211,9 +229,11 @@ func (cli *Client) MakeRequest(method string, httpURL string, reqBody interface{
contents, err := ioutil.ReadAll(res.Body)
if res.StatusCode/100 != 2 { // not 2xx
var wrap error
- var respErr RespError
- if _ = json.Unmarshal(contents, &respErr); respErr.ErrCode != "" {
+ respErr := &RespError{}
+ if _ = json.Unmarshal(contents, respErr); respErr.ErrCode != "" {
wrap = respErr
+ } else {
+ respErr = nil
}
// If we failed to decode as RespError, don't just drop the HTTP body, include it in the
@@ -227,6 +247,7 @@ func (cli *Client) MakeRequest(method string, httpURL string, reqBody interface{
Code: res.StatusCode,
Message: msg,
WrappedError: wrap,
+ RespError: respErr,
}
}
if err != nil {
@@ -318,7 +339,7 @@ func (cli *Client) RegisterGuest(req *ReqRegister) (*RespRegister, *RespUserInte
//
// This does not set credentials on the client instance. See SetCredentials() instead.
//
-// res, err := cli.RegisterDummy(&gomatrix.ReqRegister{
+// res, err := cli.RegisterDummy(&mautrix.ReqRegister{
// Username: "alice",
// Password: "wonderland",
// })
@@ -342,7 +363,7 @@ func (cli *Client) RegisterDummy(req *ReqRegister) (*RespRegister, error) {
}
}
if res == nil {
- return nil, fmt.Errorf("registration failed: does this server support m.login.dummy?")
+ return nil, fmt.Errorf("registration failed: does this server support m.login.dummy? ")
}
return res, nil
}
@@ -442,17 +463,38 @@ func (cli *Client) SetAvatarURL(url string) (err error) {
// SendMessageEvent sends a message event into a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid
// contentJSON should be a pointer to something that can be encoded as JSON using json.Marshal.
-func (cli *Client) SendMessageEvent(roomID string, eventType string, contentJSON interface{}) (resp *RespSendEvent, err error) {
+func (cli *Client) SendMessageEvent(roomID string, eventType EventType, contentJSON interface{}) (resp *RespSendEvent, err error) {
+ txnID := txnID()
+ urlPath := cli.BuildURL("rooms", roomID, "send", eventType.String(), txnID)
+ _, err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp)
+ return
+}
+
+// SendMessageEvent sends a message event into a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid
+// contentJSON should be a pointer to something that can be encoded as JSON using json.Marshal.
+func (cli *Client) SendMassagedMessageEvent(roomID string, eventType EventType, contentJSON interface{}, ts int64) (resp *RespSendEvent, err error) {
txnID := txnID()
- urlPath := cli.BuildURL("rooms", roomID, "send", eventType, txnID)
+ urlPath := cli.BuildURLWithQuery([]string{"rooms", roomID, "send", eventType.String(), txnID}, map[string]string{
+ "ts": strconv.FormatInt(ts, 10),
+ })
+ _, err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp)
+ return
+}
+
+// SendStateEvent sends a state event into a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-state-eventtype-statekey
+// contentJSON should be a pointer to something that can be encoded as JSON using json.Marshal.
+func (cli *Client) SendStateEvent(roomID string, eventType EventType, stateKey string, contentJSON interface{}) (resp *RespSendEvent, err error) {
+ urlPath := cli.BuildURL("rooms", roomID, "state", eventType.String(), stateKey)
_, err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp)
return
}
// SendStateEvent sends a state event into a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-state-eventtype-statekey
// contentJSON should be a pointer to something that can be encoded as JSON using json.Marshal.
-func (cli *Client) SendStateEvent(roomID, eventType, stateKey string, contentJSON interface{}) (resp *RespSendEvent, err error) {
- urlPath := cli.BuildURL("rooms", roomID, "state", eventType, stateKey)
+func (cli *Client) SendMassagedStateEvent(roomID string, eventType EventType, stateKey string, contentJSON interface{}, ts int64) (resp *RespSendEvent, err error) {
+ urlPath := cli.BuildURLWithQuery([]string{"rooms", roomID, "state", eventType.String(), stateKey}, map[string]string{
+ "ts": strconv.FormatInt(ts, 10),
+ })
_, err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp)
return
}
@@ -460,37 +502,39 @@ func (cli *Client) SendStateEvent(roomID, eventType, stateKey string, contentJSO
// SendText sends an m.room.message event into the given room with a msgtype of m.text
// See http://matrix.org/docs/spec/client_server/r0.2.0.html#m-text
func (cli *Client) SendText(roomID, text string) (*RespSendEvent, error) {
- return cli.SendMessageEvent(roomID, "m.room.message",
- TextMessage{"m.text", text})
+ return cli.SendMessageEvent(roomID, EventMessage, Content{
+ MsgType: MsgText,
+ Body: text,
+ })
}
// SendImage sends an m.room.message event into the given room with a msgtype of m.image
// See https://matrix.org/docs/spec/client_server/r0.2.0.html#m-image
func (cli *Client) SendImage(roomID, body, url string) (*RespSendEvent, error) {
- return cli.SendMessageEvent(roomID, "m.room.message",
- ImageMessage{
- MsgType: "m.image",
- Body: body,
- URL: url,
- })
+ return cli.SendMessageEvent(roomID, EventMessage, Content{
+ MsgType: MsgImage,
+ Body: body,
+ URL: url,
+ })
}
// SendVideo sends an m.room.message event into the given room with a msgtype of m.video
// See https://matrix.org/docs/spec/client_server/r0.2.0.html#m-video
func (cli *Client) SendVideo(roomID, body, url string) (*RespSendEvent, error) {
- return cli.SendMessageEvent(roomID, "m.room.message",
- VideoMessage{
- MsgType: "m.video",
- Body: body,
- URL: url,
- })
+ return cli.SendMessageEvent(roomID, EventMessage, Content{
+ MsgType: MsgVideo,
+ Body: body,
+ URL: url,
+ })
}
// SendNotice sends an m.room.message event into the given room with a msgtype of m.notice
// See http://matrix.org/docs/spec/client_server/r0.2.0.html#m-notice
func (cli *Client) SendNotice(roomID, text string) (*RespSendEvent, error) {
- return cli.SendMessageEvent(roomID, "m.room.message",
- TextMessage{"m.notice", text})
+ return cli.SendMessageEvent(roomID, EventMessage, Content{
+ MsgType: MsgNotice,
+ Body: text,
+ })
}
// RedactEvent redacts the given event. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-redact-eventid-txnid
@@ -502,7 +546,7 @@ func (cli *Client) RedactEvent(roomID, eventID string, req *ReqRedact) (resp *Re
}
// CreateRoom creates a new Matrix room. See https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom
-// resp, err := cli.CreateRoom(&gomatrix.ReqCreateRoom{
+// resp, err := cli.CreateRoom(&mautrix.ReqCreateRoom{
// Preset: "public_chat",
// })
// fmt.Println("Room:", resp.RoomID)
@@ -569,11 +613,18 @@ func (cli *Client) UserTyping(roomID string, typing bool, timeout int64) (resp *
return
}
+func (cli *Client) SetPresence(status string) (err error) {
+ req := ReqPresence{Presence: status}
+ u := cli.BuildURL("presence", cli.UserID, "status")
+ _, err = cli.MakeRequest("PUT", u, req, nil)
+ return
+}
+
// StateEvent gets a single state event in a room. It will attempt to JSON unmarshal into the given "outContent" struct with
// the HTTP response body, or return an error.
// See http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-state-eventtype-statekey
-func (cli *Client) StateEvent(roomID, eventType, stateKey string, outContent interface{}) (err error) {
- u := cli.BuildURL("rooms", roomID, "state", eventType, stateKey)
+func (cli *Client) StateEvent(roomID string, eventType EventType, stateKey string, outContent interface{}) (err error) {
+ u := cli.BuildURL("rooms", roomID, "state", eventType.String(), stateKey)
_, err = cli.MakeRequest("GET", u, nil, outContent)
return
}
@@ -587,18 +638,48 @@ func (cli *Client) UploadLink(link string) (*RespMediaUpload, error) {
if err != nil {
return nil, err
}
- return cli.UploadToContentRepo(res.Body, res.Header.Get("Content-Type"), res.ContentLength)
+ return cli.Upload(res.Body, res.Header.Get("Content-Type"), res.ContentLength)
+}
+
+func (cli *Client) Download(mxcURL string) (io.ReadCloser, error) {
+ if !strings.HasPrefix(mxcURL, "mxc://") {
+ return nil, errors.New("invalid Matrix content URL")
+ }
+ parts := strings.Split(mxcURL[len("mxc://"):], "/")
+ if len(parts) != 2 {
+ return nil, errors.New("invalid Matrix content URL")
+ }
+ u := cli.BuildBaseURL("_matrix/media/r0/download", parts[0], parts[1])
+ resp, err := cli.Client.Get(u)
+ if err != nil {
+ return nil, err
+ }
+ return resp.Body, nil
+}
+
+func (cli *Client) DownloadBytes(mxcURL string) ([]byte, error) {
+ resp, err := cli.Download(mxcURL)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Close()
+ return ioutil.ReadAll(resp)
+}
+
+func (cli *Client) UploadBytes(data []byte, contentType string) (*RespMediaUpload, error) {
+ return cli.Upload(bytes.NewReader(data), contentType, int64(len(data)))
}
// UploadToContentRepo uploads the given bytes to the content repository and returns an MXC URI.
// See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-media-r0-upload
-func (cli *Client) UploadToContentRepo(content io.Reader, contentType string, contentLength int64) (*RespMediaUpload, error) {
+func (cli *Client) Upload(content io.Reader, contentType string, contentLength int64) (*RespMediaUpload, error) {
req, err := http.NewRequest("POST", cli.BuildBaseURL("_matrix/media/r0/upload"), content)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", contentType)
req.ContentLength = contentLength
+ cli.LogRequest(req, fmt.Sprintf("%d bytes", contentLength))
res, err := cli.Client.Do(req)
if res != nil {
defer res.Body.Close()
@@ -666,6 +747,18 @@ func (cli *Client) Messages(roomID, from, to string, dir rune, limit int) (resp
return
}
+func (cli *Client) GetEvent(roomID, eventID string) (resp *Event, err error) {
+ urlPath := cli.BuildURL("rooms", roomID, "event", eventID)
+ _, err = cli.MakeRequest("GET", urlPath, nil, &resp)
+ return
+}
+
+func (cli *Client) MarkRead(roomID, eventID string) (err error) {
+ urlPath := cli.BuildURL("rooms", roomID, "receipt", "m.read", eventID)
+ _, err = cli.MakeRequest("POST", urlPath, struct{}{}, nil)
+ return
+}
+
// TurnServer returns turn server details and credentials for the client to use when initiating calls.
// See http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-voip-turnserver
func (cli *Client) TurnServer() (resp *RespTurnServer, err error) {
diff --git a/vendor/maunium.net/go/mautrix/events.go b/vendor/maunium.net/go/mautrix/events.go
new file mode 100644
index 0000000..c974bf9
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/events.go
@@ -0,0 +1,448 @@
+// Copyright 2018 Tulir Asokan
+package mautrix
+
+import (
+ "encoding/json"
+ "strings"
+ "sync"
+)
+
+type EventTypeClass int
+
+const (
+ // Normal message events
+ MessageEventType EventTypeClass = iota
+ // State events
+ StateEventType
+ // Ephemeral events
+ EphemeralEventType
+ // Account data events
+ AccountDataEventType
+ // Unknown events
+ UnknownEventType
+)
+
+type EventType struct {
+ Type string
+ Class EventTypeClass
+}
+
+func NewEventType(name string) EventType {
+ evtType := EventType{Type: name}
+ evtType.Class = evtType.GuessClass()
+ return evtType
+}
+
+func (et *EventType) IsState() bool {
+ return et.Class == StateEventType
+}
+
+func (et *EventType) IsEphemeral() bool {
+ return et.Class == EphemeralEventType
+}
+
+func (et *EventType) IsCustom() bool {
+ return !strings.HasPrefix(et.Type, "m.")
+}
+
+func (et *EventType) GuessClass() EventTypeClass {
+ switch et.Type {
+ case StateAliases.Type, StateCanonicalAlias.Type, StateCreate.Type, StateJoinRules.Type, StateMember.Type,
+ StatePowerLevels.Type, StateRoomName.Type, StateRoomAvatar.Type, StateTopic.Type, StatePinnedEvents.Type:
+ return StateEventType
+ case EphemeralEventReceipt.Type, EphemeralEventTyping.Type:
+ return EphemeralEventType
+ case AccountDataDirectChats.Type, AccountDataPushRules.Type, AccountDataRoomTags.Type:
+ return AccountDataEventType
+ case EventRedaction.Type, EventMessage.Type, EventSticker.Type:
+ return MessageEventType
+ default:
+ return UnknownEventType
+ }
+}
+
+func (et *EventType) UnmarshalJSON(data []byte) error {
+ err := json.Unmarshal(data, &et.Type)
+ if err != nil {
+ return err
+ }
+ et.Class = et.GuessClass()
+ return nil
+}
+
+func (et *EventType) MarshalJSON() ([]byte, error) {
+ return json.Marshal(&et.Type)
+}
+
+func (et *EventType) String() string {
+ return et.Type
+}
+
+// State events
+var (
+ StateAliases = EventType{"m.room.aliases", StateEventType}
+ StateCanonicalAlias = EventType{"m.room.canonical_alias", StateEventType}
+ StateCreate = EventType{"m.room.create", StateEventType}
+ StateJoinRules = EventType{"m.room.join_rules", StateEventType}
+ StateMember = EventType{"m.room.member", StateEventType}
+ StatePowerLevels = EventType{"m.room.power_levels", StateEventType}
+ StateRoomName = EventType{"m.room.name", StateEventType}
+ StateTopic = EventType{"m.room.topic", StateEventType}
+ StateRoomAvatar = EventType{"m.room.avatar", StateEventType}
+ StatePinnedEvents = EventType{"m.room.pinned_events", StateEventType}
+)
+
+// Message events
+var (
+ EventRedaction = EventType{"m.room.redaction", MessageEventType}
+ EventMessage = EventType{"m.room.message", MessageEventType}
+ EventSticker = EventType{"m.sticker", MessageEventType}
+)
+
+// Ephemeral events
+var (
+ EphemeralEventReceipt = EventType{"m.receipt", EphemeralEventType}
+ EphemeralEventTyping = EventType{"m.typing", EphemeralEventType}
+)
+
+// Account data events
+var (
+ AccountDataDirectChats = EventType{"m.direct", AccountDataEventType}
+ AccountDataPushRules = EventType{"m.push_rules", AccountDataEventType}
+ AccountDataRoomTags = EventType{"m.tag", AccountDataEventType}
+)
+
+type MessageType string
+
+// Msgtypes
+const (
+ MsgText MessageType = "m.text"
+ MsgEmote = "m.emote"
+ MsgNotice = "m.notice"
+ MsgImage = "m.image"
+ MsgLocation = "m.location"
+ MsgVideo = "m.video"
+ MsgAudio = "m.audio"
+ MsgFile = "m.file"
+)
+
+type Format string
+
+// Message formats
+const (
+ FormatHTML Format = "org.matrix.custom.html"
+)
+
+// Event represents a single Matrix event.
+type Event struct {
+ StateKey *string `json:"state_key,omitempty"` // The state key for the event. Only present on State Events.
+ Sender string `json:"sender"` // The user ID of the sender of the event
+ Type EventType `json:"type"` // The event type
+ Timestamp int64 `json:"origin_server_ts"` // The unix timestamp when this message was sent by the origin server
+ ID string `json:"event_id"` // The unique ID of this event
+ RoomID string `json:"room_id"` // The room the event was sent to. May be nil (e.g. for presence)
+ Content Content `json:"content"` // The JSON content of the event.
+ Redacts string `json:"redacts,omitempty"` // The event ID that was redacted if a m.room.redaction event
+ Unsigned Unsigned `json:"unsigned,omitempty"` // Unsigned content set by own homeserver.
+
+ InviteRoomState []StrippedState `json:"invite_room_state"`
+}
+
+func (evt *Event) GetStateKey() string {
+ if evt.StateKey != nil {
+ return *evt.StateKey
+ }
+ return ""
+}
+
+type StrippedState struct {
+ Content Content `json:"content"`
+ Type EventType `json:"type"`
+ StateKey string `json:"state_key"`
+}
+
+type Unsigned struct {
+ PrevContent *Content `json:"prev_content,omitempty"`
+ PrevSender string `json:"prev_sender,omitempty"`
+ ReplacesState string `json:"replaces_state,omitempty"`
+ Age int64 `json:"age,omitempty"`
+
+ PassiveCommand map[string]*MatchedPassiveCommand `json:"m.passive_command,omitempty"`
+}
+
+type MatchedPassiveCommand struct {
+ // Matched string `json:"matched"`
+ // Value string `json:"value"`
+ Captured [][]string `json:"captured"`
+
+ BackCompatCommand string `json:"command"`
+ BackCompatArguments map[string]string `json:"arguments"`
+}
+
+type Content struct {
+ VeryRaw json.RawMessage `json:"-"`
+ Raw map[string]interface{} `json:"-"`
+
+ MsgType MessageType `json:"msgtype,omitempty"`
+ Body string `json:"body,omitempty"`
+ Format Format `json:"format,omitempty"`
+ FormattedBody string `json:"formatted_body,omitempty"`
+
+ Info *FileInfo `json:"info,omitempty"`
+ URL string `json:"url,omitempty"`
+
+ // Membership key for easy access in m.room.member events
+ Membership Membership `json:"membership,omitempty"`
+
+ RelatesTo *RelatesTo `json:"m.relates_to,omitempty"`
+ Command *MatchedCommand `json:"m.command,omitempty"`
+
+ PowerLevels
+ Member
+ Aliases []string `json:"aliases,omitempty"`
+ CanonicalAlias
+ RoomName
+ RoomTopic
+
+ RoomTags Tags `json:"tags,omitempty"`
+ TypingUserIDs []string `json:"user_ids,omitempty"`
+}
+
+type serializableContent Content
+
+var DisableFancyEventParsing = false
+
+func (content *Content) UnmarshalJSON(data []byte) error {
+ content.VeryRaw = data
+ if err := json.Unmarshal(data, &content.Raw); err != nil || DisableFancyEventParsing {
+ return err
+ }
+ return json.Unmarshal(data, (*serializableContent)(content))
+}
+
+func (content *Content) GetCommand() *MatchedCommand {
+ if content.Command == nil {
+ content.Command = &MatchedCommand{}
+ }
+ return content.Command
+}
+
+func (content *Content) GetRelatesTo() *RelatesTo {
+ if content.RelatesTo == nil {
+ content.RelatesTo = &RelatesTo{}
+ }
+ return content.RelatesTo
+}
+
+func (content *Content) UnmarshalPowerLevels() (pl PowerLevels, err error) {
+ err = json.Unmarshal(content.VeryRaw, &pl)
+ return
+}
+
+func (content *Content) UnmarshalMember() (m Member, err error) {
+ err = json.Unmarshal(content.VeryRaw, &m)
+ return
+}
+
+func (content *Content) UnmarshalCanonicalAlias() (ca CanonicalAlias, err error) {
+ err = json.Unmarshal(content.VeryRaw, &ca)
+ return
+}
+
+func (content *Content) GetInfo() *FileInfo {
+ if content.Info == nil {
+ content.Info = &FileInfo{}
+ }
+ return content.Info
+}
+
+type Tags map[string]struct {
+ Order json.Number `json:"order"`
+}
+
+type RoomName struct {
+ Name string `json:"name,omitempty"`
+}
+
+type RoomTopic struct {
+ Topic string `json:"topic,omitempty"`
+}
+
+// Membership is an enum specifying the membership state of a room member.
+type Membership string
+
+// The allowed membership states as specified in spec section 10.5.5.
+const (
+ MembershipJoin Membership = "join"
+ MembershipLeave Membership = "leave"
+ MembershipInvite Membership = "invite"
+ MembershipBan Membership = "ban"
+ MembershipKnock Membership = "knock"
+)
+
+type Member struct {
+ Membership Membership `json:"membership,omitempty"`
+ AvatarURL string `json:"avatar_url,omitempty"`
+ Displayname string `json:"displayname,omitempty"`
+ ThirdPartyInvite *ThirdPartyInvite `json:"third_party_invite,omitempty"`
+ Reason string `json:"reason,omitempty"`
+}
+
+type ThirdPartyInvite struct {
+ DisplayName string `json:"display_name"`
+ Signed struct {
+ Token string `json:"token"`
+ Signatures json.RawMessage `json:"signatures"`
+ MXID string `json:"mxid"`
+ }
+}
+
+type CanonicalAlias struct {
+ Alias string `json:"alias,omitempty"`
+}
+
+type PowerLevels struct {
+ usersLock sync.RWMutex `json:"-"`
+ Users map[string]int `json:"users,omitempty"`
+ UsersDefault int `json:"users_default,omitempty"`
+
+ eventsLock sync.RWMutex `json:"-"`
+ Events map[string]int `json:"events,omitempty"`
+ EventsDefault int `json:"events_default,omitempty"`
+
+ StateDefaultPtr *int `json:"state_default,omitempty"`
+
+ InvitePtr *int `json:"invite,omitempty"`
+ KickPtr *int `json:"kick,omitempty"`
+ BanPtr *int `json:"ban,omitempty"`
+ RedactPtr *int `json:"redact,omitempty"`
+}
+
+func (pl *PowerLevels) Invite() int {
+ if pl.InvitePtr != nil {
+ return *pl.InvitePtr
+ }
+ return 50
+}
+
+func (pl *PowerLevels) Kick() int {
+ if pl.KickPtr != nil {
+ return *pl.KickPtr
+ }
+ return 50
+}
+
+func (pl *PowerLevels) Ban() int {
+ if pl.BanPtr != nil {
+ return *pl.BanPtr
+ }
+ return 50
+}
+
+func (pl *PowerLevels) Redact() int {
+ if pl.RedactPtr != nil {
+ return *pl.RedactPtr
+ }
+ return 50
+}
+
+func (pl *PowerLevels) StateDefault() int {
+ if pl.StateDefaultPtr != nil {
+ return *pl.StateDefaultPtr
+ }
+ return 50
+}
+
+func (pl *PowerLevels) GetUserLevel(userID string) int {
+ pl.usersLock.RLock()
+ defer pl.usersLock.RUnlock()
+ level, ok := pl.Users[userID]
+ if !ok {
+ return pl.UsersDefault
+ }
+ return level
+}
+
+func (pl *PowerLevels) SetUserLevel(userID string, level int) {
+ pl.usersLock.Lock()
+ defer pl.usersLock.Unlock()
+ if level == pl.UsersDefault {
+ delete(pl.Users, userID)
+ } else {
+ pl.Users[userID] = level
+ }
+}
+
+func (pl *PowerLevels) EnsureUserLevel(userID string, level int) bool {
+ existingLevel := pl.GetUserLevel(userID)
+ if existingLevel != level {
+ pl.SetUserLevel(userID, level)
+ return true
+ }
+ return false
+}
+
+func (pl *PowerLevels) GetEventLevel(eventType EventType) int {
+ pl.eventsLock.RLock()
+ defer pl.eventsLock.RUnlock()
+ level, ok := pl.Events[eventType.String()]
+ if !ok {
+ if eventType.IsState() {
+ return pl.StateDefault()
+ }
+ return pl.EventsDefault
+ }
+ return level
+}
+
+func (pl *PowerLevels) SetEventLevel(eventType EventType, level int) {
+ pl.eventsLock.Lock()
+ defer pl.eventsLock.Unlock()
+ if (eventType.IsState() && level == pl.StateDefault()) || (!eventType.IsState() && level == pl.EventsDefault) {
+ delete(pl.Events, eventType.String())
+ } else {
+ pl.Events[eventType.String()] = level
+ }
+}
+
+func (pl *PowerLevels) EnsureEventLevel(eventType EventType, level int) bool {
+ existingLevel := pl.GetEventLevel(eventType)
+ if existingLevel != level {
+ pl.SetEventLevel(eventType, level)
+ return true
+ }
+ return false
+}
+
+type FileInfo struct {
+ MimeType string `json:"mimetype,omitempty"`
+ ThumbnailInfo *FileInfo `json:"thumbnail_info,omitempty"`
+ ThumbnailURL string `json:"thumbnail_url,omitempty"`
+ Height int `json:"h,omitempty"`
+ Width int `json:"w,omitempty"`
+ Duration uint `json:"duration,omitempty"`
+ Size int `json:"size,omitempty"`
+}
+
+func (fileInfo *FileInfo) GetThumbnailInfo() *FileInfo {
+ if fileInfo.ThumbnailInfo == nil {
+ fileInfo.ThumbnailInfo = &FileInfo{}
+ }
+ return fileInfo.ThumbnailInfo
+}
+
+type RelatesTo struct {
+ InReplyTo InReplyTo `json:"m.in_reply_to,omitempty"`
+}
+
+type InReplyTo struct {
+ EventID string `json:"event_id,omitempty"`
+ // Not required, just for future-proofing
+ RoomID string `json:"room_id,omitempty"`
+}
+
+type MatchedCommand struct {
+ Target string `json:"target"`
+ Matched string `json:"matched"`
+ Arguments map[string]string `json:"arguments"`
+}
diff --git a/vendor/maunium.net/go/gomatrix/filter.go b/vendor/maunium.net/go/mautrix/filter.go
index 2a0c37f..41cab2d 100644
--- a/vendor/maunium.net/go/gomatrix/filter.go
+++ b/vendor/maunium.net/go/mautrix/filter.go
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package gomatrix
+package mautrix
import "errors"
diff --git a/vendor/maunium.net/go/mautrix/reply.go b/vendor/maunium.net/go/mautrix/reply.go
new file mode 100644
index 0000000..5e3af92
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/reply.go
@@ -0,0 +1,97 @@
+// Copyright 2018 Tulir Asokan
+package mautrix
+
+import (
+ "fmt"
+ "regexp"
+ "strings"
+
+ "golang.org/x/net/html"
+)
+
+var HTMLReplyFallbackRegex = regexp.MustCompile(`^<mx-reply>[\s\S]+?</mx-reply>`)
+
+func TrimReplyFallbackHTML(html string) string {
+ return HTMLReplyFallbackRegex.ReplaceAllString(html, "")
+}
+
+func TrimReplyFallbackText(text string) string {
+ if !strings.HasPrefix(text, "> ") || !strings.Contains(text, "\n") {
+ return text
+ }
+
+ lines := strings.Split(text, "\n")
+ for len(lines) > 0 && strings.HasPrefix(lines[0], "> ") {
+ lines = lines[1:]
+ }
+ return strings.TrimSpace(strings.Join(lines, "\n"))
+}
+
+func (content *Content) RemoveReplyFallback() {
+ if len(content.GetReplyTo()) > 0 {
+ if content.Format == FormatHTML {
+ content.FormattedBody = TrimReplyFallbackHTML(content.FormattedBody)
+ }
+ content.Body = TrimReplyFallbackText(content.Body)
+ }
+}
+
+func (content *Content) GetReplyTo() string {
+ if content.RelatesTo != nil {
+ return content.RelatesTo.InReplyTo.EventID
+ }
+ return ""
+}
+
+const ReplyFormat = `<mx-reply><blockquote>
+<a href="https://matrix.to/#/%s/%s">In reply to</a>
+<a href="https://matrix.to/#/%s">%s</a>
+%s
+</blockquote></mx-reply>
+`
+
+func (evt *Event) GenerateReplyFallbackHTML() string {
+ body := evt.Content.FormattedBody
+ if len(body) == 0 {
+ body = html.EscapeString(evt.Content.Body)
+ }
+
+ senderDisplayName := evt.Sender
+
+ return fmt.Sprintf(ReplyFormat, evt.RoomID, evt.ID, evt.Sender, senderDisplayName, body)
+}
+
+func (evt *Event) GenerateReplyFallbackText() string {
+ body := evt.Content.Body
+ lines := strings.Split(strings.TrimSpace(body), "\n")
+ firstLine, lines := lines[0], lines[1:]
+
+ senderDisplayName := evt.Sender
+
+ var fallbackText strings.Builder
+ fmt.Fprintf(&fallbackText, "> <%s> %s", senderDisplayName, firstLine)
+ for _, line := range lines {
+ fmt.Fprintf(&fallbackText, "\n> %s", line)
+ }
+ fallbackText.WriteString("\n\n")
+ return fallbackText.String()
+}
+
+func (content *Content) SetReply(inReplyTo *Event) {
+ if content.RelatesTo == nil {
+ content.RelatesTo = &RelatesTo{}
+ }
+ content.RelatesTo.InReplyTo = InReplyTo{
+ EventID: inReplyTo.ID,
+ RoomID: inReplyTo.RoomID,
+ }
+
+ if content.MsgType == MsgText || content.MsgType == MsgNotice {
+ if len(content.FormattedBody) == 0 || content.Format != FormatHTML {
+ content.FormattedBody = html.EscapeString(content.Body)
+ content.Format = FormatHTML
+ }
+ content.FormattedBody = inReplyTo.GenerateReplyFallbackHTML() + content.FormattedBody
+ content.Body = inReplyTo.GenerateReplyFallbackText() + content.Body
+ }
+}
diff --git a/vendor/maunium.net/go/gomatrix/requests.go b/vendor/maunium.net/go/mautrix/requests.go
index af99a22..b90e6fb 100644
--- a/vendor/maunium.net/go/gomatrix/requests.go
+++ b/vendor/maunium.net/go/mautrix/requests.go
@@ -1,4 +1,4 @@
-package gomatrix
+package mautrix
// ReqRegister is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register
type ReqRegister struct {
@@ -31,7 +31,7 @@ type ReqCreateRoom struct {
Invite []string `json:"invite,omitempty"`
Invite3PID []ReqInvite3PID `json:"invite_3pid,omitempty"`
CreationContent map[string]interface{} `json:"creation_content,omitempty"`
- InitialState []Event `json:"initial_state,omitempty"`
+ InitialState []*Event `json:"initial_state,omitempty"`
Preset string `json:"preset,omitempty"`
IsDirect bool `json:"is_direct,omitempty"`
}
@@ -74,5 +74,9 @@ type ReqUnbanUser struct {
// ReqTyping is the JSON request for https://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-typing-userid
type ReqTyping struct {
Typing bool `json:"typing"`
- Timeout int64 `json:"timeout"`
+ Timeout int64 `json:"timeout,omitempty"`
+}
+
+type ReqPresence struct {
+ Presence string `json:"presence"`
}
diff --git a/vendor/maunium.net/go/gomatrix/responses.go b/vendor/maunium.net/go/mautrix/responses.go
index 6d43bd3..2adf90a 100644
--- a/vendor/maunium.net/go/gomatrix/responses.go
+++ b/vendor/maunium.net/go/mautrix/responses.go
@@ -1,4 +1,4 @@
-package gomatrix
+package mautrix
// RespError is the standard JSON error response from Homeservers. It also implements the Golang "error" interface.
// See http://matrix.org/docs/spec/client_server/r0.2.0.html#api-standards
@@ -63,9 +63,9 @@ type RespJoinedMembers struct {
// RespMessages is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-messages
type RespMessages struct {
- Start string `json:"start"`
- Chunk []Event `json:"chunk"`
- End string `json:"end"`
+ Start string `json:"start"`
+ Chunk []*Event `json:"chunk"`
+ End string `json:"end"`
}
// RespSendEvent is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid
@@ -146,8 +146,8 @@ type RespSync struct {
} `json:"state"`
Timeline struct {
Events []*Event `json:"events"`
- Limited bool `json:"limited"`
- PrevBatch string `json:"prev_batch"`
+ Limited bool `json:"limited"`
+ PrevBatch string `json:"prev_batch"`
} `json:"timeline"`
} `json:"leave"`
Join map[string]struct {
@@ -156,14 +156,14 @@ type RespSync struct {
} `json:"state"`
Timeline struct {
Events []*Event `json:"events"`
- Limited bool `json:"limited"`
- PrevBatch string `json:"prev_batch"`
+ Limited bool `json:"limited"`
+ PrevBatch string `json:"prev_batch"`
} `json:"timeline"`
Ephemeral struct {
- Events []*Event `json:"events"`
+ Events []*Event `json:"events"`
} `json:"ephemeral"`
AccountData struct {
- Events []*Event `json:"events"`
+ Events []*Event `json:"events"`
} `json:"account_data"`
} `json:"join"`
Invite map[string]struct {
diff --git a/vendor/maunium.net/go/gomatrix/room.go b/vendor/maunium.net/go/mautrix/room.go
index c9b2351..086e259 100644
--- a/vendor/maunium.net/go/gomatrix/room.go
+++ b/vendor/maunium.net/go/mautrix/room.go
@@ -1,9 +1,9 @@
-package gomatrix
+package mautrix
// Room represents a single Matrix room.
type Room struct {
ID string
- State map[string]map[string]*Event
+ State map[EventType]map[string]*Event
}
// UpdateState updates the room's current state with the given Event. This will clobber events based
@@ -17,7 +17,7 @@ func (room Room) UpdateState(event *Event) {
}
// GetStateEvent returns the state event for the given type/state_key combo, or nil.
-func (room Room) GetStateEvent(eventType string, stateKey string) *Event {
+func (room Room) GetStateEvent(eventType EventType, stateKey string) *Event {
stateEventMap, _ := room.State[eventType]
event, _ := stateEventMap[stateKey]
return event
@@ -25,17 +25,11 @@ func (room Room) GetStateEvent(eventType string, stateKey string) *Event {
// GetMembershipState returns the membership state of the given user ID in this room. If there is
// no entry for this member, 'leave' is returned for consistency with left users.
-func (room Room) GetMembershipState(userID string) string {
- state := "leave"
- event := room.GetStateEvent("m.room.member", userID)
+func (room Room) GetMembershipState(userID string) Membership {
+ state := MembershipLeave
+ event := room.GetStateEvent(StateMember, userID)
if event != nil {
- membershipState, found := event.Content["membership"]
- if found {
- mState, isString := membershipState.(string)
- if isString {
- state = mState
- }
- }
+ state = event.Content.Membership
}
return state
}
@@ -45,6 +39,6 @@ func NewRoom(roomID string) *Room {
// Init the State map and return a pointer to the Room
return &Room{
ID: roomID,
- State: make(map[string]map[string]*Event),
+ State: make(map[EventType]map[string]*Event),
}
}
diff --git a/vendor/maunium.net/go/gomatrix/store.go b/vendor/maunium.net/go/mautrix/store.go
index 6dc687e..774398e 100644
--- a/vendor/maunium.net/go/gomatrix/store.go
+++ b/vendor/maunium.net/go/mautrix/store.go
@@ -1,4 +1,4 @@
-package gomatrix
+package mautrix
// Storer is an interface which must be satisfied to store client data.
//
diff --git a/vendor/maunium.net/go/gomatrix/sync.go b/vendor/maunium.net/go/mautrix/sync.go
index e1233a4..9589edc 100644
--- a/vendor/maunium.net/go/gomatrix/sync.go
+++ b/vendor/maunium.net/go/mautrix/sync.go
@@ -1,4 +1,4 @@
-package gomatrix
+package mautrix
import (
"encoding/json"
@@ -25,7 +25,7 @@ type Syncer interface {
type DefaultSyncer struct {
UserID string
Store Storer
- listeners map[string][]OnEventListener // event type to listeners array
+ listeners map[EventType][]OnEventListener // event type to listeners array
}
// OnEventListener can be used with DefaultSyncer.OnEventType to be informed of incoming events.
@@ -36,7 +36,7 @@ func NewDefaultSyncer(userID string, store Storer) *DefaultSyncer {
return &DefaultSyncer{
UserID: userID,
Store: store,
- listeners: make(map[string][]OnEventListener),
+ listeners: make(map[EventType][]OnEventListener),
}
}
@@ -88,7 +88,7 @@ func (s *DefaultSyncer) ProcessResponse(res *RespSync, since string) (err error)
// OnEventType allows callers to be notified when there are new events for the given event type.
// There are no duplicate checks.
-func (s *DefaultSyncer) OnEventType(eventType string, callback OnEventListener) {
+func (s *DefaultSyncer) OnEventType(eventType EventType, callback OnEventListener) {
_, exists := s.listeners[eventType]
if !exists {
s.listeners[eventType] = []OnEventListener{}
@@ -112,13 +112,8 @@ func (s *DefaultSyncer) shouldProcessResponse(resp *RespSync, since string) bool
for roomID, roomData := range resp.Rooms.Join {
for i := len(roomData.Timeline.Events) - 1; i >= 0; i-- {
e := roomData.Timeline.Events[i]
- if e.Type == "m.room.member" && e.StateKey != nil && *e.StateKey == s.UserID {
- m := e.Content["membership"]
- mship, ok := m.(string)
- if !ok {
- continue
- }
- if mship == "join" {
+ if e.Type == StateMember && e.GetStateKey() == s.UserID {
+ if e.Content.Membership == "join" {
_, ok := resp.Rooms.Join[roomID]
if !ok {
continue
diff --git a/vendor/maunium.net/go/gomatrix/userids.go b/vendor/maunium.net/go/mautrix/userids.go
index 23e7807..ce6e02d 100644
--- a/vendor/maunium.net/go/gomatrix/userids.go
+++ b/vendor/maunium.net/go/mautrix/userids.go
@@ -1,4 +1,4 @@
-package gomatrix
+package mautrix
import (
"bytes"
@@ -125,6 +125,6 @@ func ExtractUserLocalpart(userID string) (string, error) {
}
return strings.TrimPrefix(
strings.SplitN(userID, ":", 2)[0], // @foo:bar:8448 => [ "@foo", "bar:8448" ]
- "@", // remove "@" prefix
+ "@", // remove "@" prefix
), nil
}
diff --git a/vendor/maunium.net/go/tcell/README.adoc b/vendor/maunium.net/go/tcell/README.adoc
new file mode 100644
index 0000000..3b60057
--- /dev/null
+++ b/vendor/maunium.net/go/tcell/README.adoc
@@ -0,0 +1,270 @@
+= tcell
+
+
+image:https://img.shields.io/travis/gdamore/tcell.svg?label=linux[Linux Status,link="https://travis-ci.org/gdamore/tcell"]
+image:https://img.shields.io/appveyor/ci/gdamore/tcell.svg?label=windows[Windows Status,link="https://ci.appveyor.com/project/gdamore/tcell"]
+image:https://img.shields.io/badge/license-APACHE2-blue.svg[Apache License,link="https://github.com/gdamore/tcell/blob/master/LICENSE"]
+image:https://img.shields.io/badge/gitter-join-brightgreen.svg[Gitter,link="https://gitter.im/gdamore/tcell"]
+image:https://img.shields.io/badge/godoc-reference-blue.svg[GoDoc,link="https://godoc.org/github.com/gdamore/tcell"]
+image:http://goreportcard.com/badge/gdamore/tcell[Go Report Card,link="http://goreportcard.com/report/gdamore/tcell"]
+image:https://codecov.io/gh/gdamore/tcell/branch/master/graph/badge.svg[codecov,link="https://codecov.io/gh/gdamore/tcell"]
+image:https://tidelift.com/badges/github/gdamore/tcell?style=flat[Dependencies]
+
+[cols="2",grid="none"]
+|===
+|_Tcell_ is a _Go_ package that provides a cell based view for text terminals, like _xterm_.
+It was inspired by _termbox_, but includes many additional improvements.
+a|[.right]
+image::logos/tcell.png[float="right"]
+|===
+
+## Examples
+
+* https://github.com/gdamore/proxima5[proxima5] - space shooter (https://youtu.be/jNxKTCmY_bQ[video])
+* https://github.com/gdamore/govisor[govisor] - service management UI (http://2.bp.blogspot.com/--OsvnfzSNow/Vf7aqMw3zXI/AAAAAAAAARo/uOMtOvw4Sbg/s1600/Screen%2BShot%2B2015-09-20%2Bat%2B9.08.41%2BAM.png[screenshot])
+* mouse demo - included mouse test (http://2.bp.blogspot.com/-fWvW5opT0es/VhIdItdKqJI/AAAAAAAAATE/7Ojc0L1SpB0/s1600/Screen%2BShot%2B2015-10-04%2Bat%2B11.47.13%2BPM.png[screenshot])
+* https://github.com/gdamore/gomatrix[gomatrix] - converted from Termbox
+* https://github.com/zyedidia/micro/[micro] - lightweight text editor with syntax-highlighting and themes
+* https://github.com/viktomas/godu[godu] - simple golang utility helping to discover large files/folders.
+* https://github.com/rivo/tview[tview] - rich interactive widgets for terminal UIs
+* https://github.com/marcusolsson/tui-go[tui-go] - UI library for terminal apps
+* https://github.com/rgm3/gomandelbrot[gomandelbrot] - Mandelbrot!
+* https://github.com/senorprogrammer/wtf[WTF]- Personal information dashboard for your terminal
+* https://github.com/browsh-org/browsh[browsh] - A fully-modern text-based browser, rendering to TTY and browsers (https://www.youtube.com/watch?v=HZq86XfBoRo[video])
+* https://github.com/sachaos/go-life[go-life] - Conway's Game of Life.
+
+## Pure Go Terminfo Database
+
+_Tcell_ includes a full parser and expander for terminfo capability strings,
+so that it can avoid hard coding escape strings for formatting. It also favors
+portability, and includes support for all POSIX systems.
+
+The database is also flexible & extensible, and can modified by either running
+a program to build the entire database, or an entry for just a single terminal.
+
+## More Portable
+
+_Tcell_ is portable to a wide variety of systems.
+_Tcell_ is believed
+to work with all of the systems officially supported by golang with
+the exception of nacl (which lacks any kind of a terminal interface).
+(Plan9 is not supported by _Tcell_, but it is experimental status only
+in golang.) For all of these systems *except Solaris/illumos*, _Tcell_
+is pure Go, with no need for CGO.
+
+## No Async IO
+
+_Tcell_ is able to operate without requiring `SIGIO` signals (unlike _termbox_),
+or asynchronous I/O, and can instead use standard Go file
+objects and Go routines.
+This means it should be safe, especially for
+use with programs that use exec, or otherwise need to manipulate the
+tty streams.
+This model is also much closer to idiomatic Go, leading
+to fewer surprises.
+
+## Rich Unicode & non-Unicode support
+
+_Tcell_ includes enhanced support for Unicode, including wide characters and
+combining characters, provided your terminal can support them.
+Note that
+Windows terminals generally don't support the full Unicode repertoire.
+
+It will also convert to and from Unicode locales, so that the program
+can work with UTF-8 internally, and get reasonable output in other locales.
+_Tcell_ tries hard to convert to native characters on both input and output, and
+on output _Tcell_ even makes use of the alternate character set to facilitate
+drawing certain characters.
+
+## More Function Keys
+
+_Tcell_ also has richer support for a larger number of special keys that some terminals can send.
+
+## Better Color Handling
+
+_Tcell_ will respect your terminal's color space as specified within your terminfo
+entries, so that for example attempts to emit color sequences on VT100 terminals
+won't result in unintended consequences.
+
+In Windows mode, _Tcell_ supports 16 colors, bold, dim, and reverse,
+instead of just termbox's 8 colors with reverse. (Note that there is some
+conflation with bold/dim and colors.)
+
+_Tcell_ maps 16 colors down to 8, for terminals that need it.
+(The upper 8 colors are just brighter versions of the lower 8.)
+
+## Better Mouse Support
+
+_Tcell_ supports enhanced mouse tracking mode, so your application can receive
+regular mouse motion events, and wheel events, if your terminal supports it.
+
+## _Termbox_ Compatibility
+
+A compatibility layer for _termbox_ is provided in the `compat` directory.
+To use it, try importing `github.com/gdamore/tcell/termbox`
+instead. Most _termbox-go_ programs will probably work without further
+modification.
+
+## Working With Unicode
+
+Internally Tcell uses UTF-8, just like Go.
+However, Tcell understands how to
+convert to and from other character sets, using the capabilities of
+the `golang.org/x/text/encoding packages`.
+Your application must supply
+them, as the full set of the most common ones bloats the program by about 2MB.
+If you're lazy, and want them all anyway, see the `encoding` sub-directory.
+
+## Wide & Combining Characters
+
+The `SetContent()` API takes a primary rune, and an optional list of combining runes.
+If any of the runes is a wide (East Asian) rune occupying two cells,
+then the library will skip output from the following cell, but care must be
+taken in the application to avoid explicitly attempting to set content in the
+next cell, otherwise the results are undefined. (Normally wide character
+is displayed, and the other character is not; do not depend on that behavior.)
+
+Experience has shown that the vanilla Windows 8 console application does not
+support any of these characters properly, but at least some options like
+_ConEmu_ do support Wide characters.
+
+## Colors
+
+_Tcell_ assumes the ANSI/XTerm color model, including the 256 color map that
+XTerm uses when it supports 256 colors. The terminfo guidance will be
+honored, with respect to the number of colors supported. Also, only
+terminals which expose ANSI style `setaf` and `setab` will support color;
+if you have a color terminal that only has `setf` and `setb`, please let me
+know; it wouldn't be hard to add that if there is need.
+
+## 24-bit Color
+
+_Tcell_ _supports true color_! (That is, if your terminal can support it,
+_Tcell_ can accurately display 24-bit color.)
+
+To use 24-bit color, you need to use a terminal that supports it. Modern
+xterm and similar teminal emulators can support this. As terminfo lacks any
+way to describe this capability, we fabricate the capability for
+terminals with names ending in `*-truecolor`. The stock distribution ships
+with a database that defines `xterm-truecolor`.
+To try it out, set your
+`TERM` variable to `xterm-truecolor`.
+
+When using TrueColor, programs will display the colors that the programmer
+intended, overriding any "`themes`" you may have set in your terminal
+emulator. (For some cases, accurate color fidelity is more important
+than respecting themes. For other cases, such as typical text apps that
+only use a few colors, its more desirable to respect the themes that
+the user has established.)
+
+If you find this undesirable, you can either use a `TERM` variable
+that lacks the `TRUECOLOR` setting, or set `TCELL_TRUECOLOR=disable` in your
+environment.
+
+## Performance
+
+Reasonable attempts have been made to minimize sending data to terminals,
+avoiding repeated sequences or drawing the same cell on refresh updates.
+
+## Terminfo
+
+(Not relevent for Windows users.)
+
+The Terminfo implementation operates with two forms of database. The first
+is the built-in go database, which contains a number of real database entries
+that are compiled into the program directly. This should minimize calling
+out to database file searches.
+
+The second is in the form of JSON files, that contain the same information,
+which can be located either by the `$TCELLDB` environment file, `$HOME/.tcelldb`,
+or is located in the Go source directory as `database.json`.
+
+These files (both the Go and the JSON files) can be generated using the
+mkinfo.go program. If you need to regnerate the entire set for some reason,
+run the mkdatabase.sh file. The generation uses the infocmp(1) program on
+the system to collect the necessary information.
+
+The `mkinfo.go` program can also be used to generate specific database entries
+for named terminals, in case your favorite terminal is missing. (If you
+find that this is the case, please let me know and I'll try to add it!)
+
+_Tcell_ requires that the terminal support the `cup` mode of cursor addressing.
+Terminals without absolute cursor addressability are not supported.
+This is unlikely to be a problem; such terminals have not been mass produced
+since the early 1970s.
+
+## Mouse Support
+
+Mouse support is detected via the `kmous` terminfo variable, however,
+enablement/disablement and decoding mouse events is done using hard coded
+sequences based on the XTerm X11 model. As of this writing all popular
+terminals with mouse tracking support this model. (Full terminfo support
+is not possible as terminfo sequences are not defined.)
+
+On Windows, the mouse works normally.
+
+Mouse wheel buttons on various terminals are known to work, but the support
+in terminal emulators, as well as support for various buttons and
+live mouse tracking, varies widely. Modern _xterm_, macOS _Terminal_, and _iTerm_ all work well.
+
+## Testablity
+
+There is a `SimulationScreen`, that can be used to simulate a real screen
+for automated testing. The supplied tests do this. The simulation contains
+event delivery, screen resizing support, and capabilities to inject events
+and examine "`physical`" screen contents.
+
+## Platforms
+
+### POSIX (Linux, FreeBSD, macOS, Solaris, etc.)
+
+For mainstream systems with a suitably well defined system call interface
+to tty settings, everything works using pure Go.
+
+For the remainder (right now means only Solaris/illumos) we use POSIX function
+calls to manage termios, which implies that CGO is required on those platforms.
+
+### Windows
+
+Windows console mode applications are supported. Unfortunately _mintty_
+and other _cygwin_ style applications are not supported.
+
+Modern console applications like ConEmu, as well as the Windows 10
+console itself, support all the good features (resize, mouse tracking, etc.)
+
+I haven't figured out how to cleanly resolve the dichotomy between cygwin
+style termios and the Windows Console API; it seems that perhaps nobody else
+has either. If anyone has suggestions, let me know! Really, if you're
+using a Windows application, you should use the native Windows console or a
+fully compatible console implementation.
+
+### Plan9 and Native Client (Nacl)
+
+The nacl and plan9 platforms won't work, but compilation stubs are supplied
+for folks that want to include parts of this in software targetting those
+platforms. The Simulation screen works, but as Tcell doesn't know how to
+allocate a real screen object on those platforms, `NewScreen()` will fail.
+
+If anyone has wisdom about how to improve support for either of these,
+please let me know. PRs are especially welcome.
+
+### Commercial Support
+
+_Tcell_ is absolutely free, but if you want to obtain commercial, professional support, there are options.
+
+[cols="2",align="center",frame="none", grid="none"]
+|===
+^.^|
+image:logos/tidelift.png[100,100]
+a|
+https://tidelift.com/[Tidelift] subscriptions include support for _Tcell_, as well as many other open source packages.
+
+^.^|
+image:logos/staysail.png[100,100]
+a|
+mailto:info@staysail.tech[Staysail Systems, Inc.] offers direct support, and custom development around _Tcell_ on an hourly basis.
+
+^.^|
+image:logos/patreon.png[100,100]
+a|I also welcome donations at https://www.patron.com/gedamore/[Patreon], if you just want to make a contribution.
+|===
diff --git a/vendor/maunium.net/go/tcell/cell.go b/vendor/maunium.net/go/tcell/cell.go
index 496f10f..957b62f 100644
--- a/vendor/maunium.net/go/tcell/cell.go
+++ b/vendor/maunium.net/go/tcell/cell.go
@@ -52,6 +52,10 @@ func (cb *CellBuffer) SetContent(x int, y int,
i := 0
for i < len(c.currComb) {
r := c.currComb[i]
+ if r == '\u200d' {
+ i += 2
+ continue
+ }
if runewidth.RuneWidth(r) != 0 {
// not a combining character, yank it
c.currComb = append(c.currComb[:i-1], c.currComb[i+1:]...)
@@ -175,12 +179,13 @@ func (cb *CellBuffer) Resize(w, h int) {
// Fill fills the entire cell buffer array with the specified character
// and style. Normally choose ' ' to clear the screen. This API doesn't
-// support combining characters.
+// support combining characters, or characters with a width larger than one.
func (cb *CellBuffer) Fill(r rune, style Style) {
for i := range cb.cells {
c := &cb.cells[i]
c.currMain = r
c.currComb = nil
c.currStyle = style
+ c.width = 1
}
}
diff --git a/vendor/maunium.net/go/tcell/console_win.go b/vendor/maunium.net/go/tcell/console_win.go
index 5957a17..a7507c2 100644
--- a/vendor/maunium.net/go/tcell/console_win.go
+++ b/vendor/maunium.net/go/tcell/console_win.go
@@ -28,7 +28,6 @@ type cScreen struct {
in syscall.Handle
out syscall.Handle
cancelflag syscall.Handle
- title syscall.Handle
scandone chan struct{}
evch chan Event
quit chan struct{}
diff --git a/vendor/maunium.net/go/tcell/tcell.png b/vendor/maunium.net/go/tcell/tcell.png
deleted file mode 100644
index 24333c4..0000000
--- a/vendor/maunium.net/go/tcell/tcell.png
+++ /dev/null
Binary files differ
diff --git a/vendor/maunium.net/go/tcell/tcell.svg b/vendor/maunium.net/go/tcell/tcell.svg
deleted file mode 100644
index d8695d5..0000000
--- a/vendor/maunium.net/go/tcell/tcell.svg
+++ /dev/null
@@ -1,93 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:xlink="http://www.w3.org/1999/xlink"
- id="svg8"
- version="1.1"
- viewBox="0 0 210 297"
- height="297mm"
- width="210mm">
- <defs
- id="defs2">
- <linearGradient
- id="linearGradient2680">
- <stop
- id="stop2676"
- offset="0"
- style="stop-color:#ababab;stop-opacity:1;" />
- <stop
- id="stop2678"
- offset="1"
- style="stop-color:#ababab;stop-opacity:0;" />
- </linearGradient>
- <marker
- style="overflow:visible"
- id="Arrow1Lstart"
- refX="0.0"
- refY="0.0"
- orient="auto">
- <path
- transform="scale(0.8) translate(12.5,0)"
- style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt"
- d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
- id="path848" />
- </marker>
- <radialGradient
- gradientUnits="userSpaceOnUse"
- gradientTransform="matrix(1,0,0,0.28804762,0,94.764912)"
- r="19.622099"
- fy="133.10568"
- fx="111.58373"
- cy="133.10568"
- cx="111.58373"
- id="radialGradient2684"
- xlink:href="#linearGradient2680" />
- </defs>
- <metadata
- id="metadata5">
- <rdf:RDF>
- <cc:Work
- rdf:about="">
- <dc:format>image/svg+xml</dc:format>
- <dc:type
- rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- <dc:title></dc:title>
- </cc:Work>
- </rdf:RDF>
- </metadata>
- <g
- id="layer1">
- <rect
- y="99.44445"
- x="31.750006"
- height="86.430557"
- width="129.64584"
- id="rect1130"
- style="opacity:1;fill:#5d6c53;fill-opacity:1;stroke:#244f24;stroke-width:10;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
- <text
- transform="scale(0.99941234,1.000588)"
- id="text1128"
- y="160.47581"
- x="44.689861"
- style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:46.0962944px;line-height:1.25;font-family:'Glass TTY VT220';-inkscape-font-specification:'Glass TTY VT220, Medium';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#5ef86d;fill-opacity:1;stroke:#59ff32;stroke-width:0.80994618;stroke-opacity:0.94520545;"
- xml:space="preserve"><tspan
- style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:46.0962944px;font-family:'Glass TTY VT220';-inkscape-font-specification:'Glass TTY VT220, Medium';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#5ef86d;fill-opacity:1;stroke:#59ff32;stroke-width:0.80994618;stroke-opacity:0.94520545;"
- y="160.47581"
- x="44.689861"
- id="tspan1126">tcell</tspan></text>
- <flowRoot
- style="fill:black;fill-opacity:1;stroke:none;font-family:sans-serif;font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;letter-spacing:0px;word-spacing:0px"
- id="flowRoot1132"
- xml:space="preserve"><flowRegion
- id="flowRegion1134"><rect
- y="432.51968"
- x="290"
- height="160"
- width="330"
- id="rect1136" /></flowRegion><flowPara
- id="flowPara1138"></flowPara></flowRoot> </g>
-</svg>
diff --git a/vendor/maunium.net/go/tcell/terminfo/term_termite.go b/vendor/maunium.net/go/tcell/terminfo/term_termite.go
new file mode 100644
index 0000000..8e7f683
--- /dev/null
+++ b/vendor/maunium.net/go/tcell/terminfo/term_termite.go
@@ -0,0 +1,152 @@
+// Generated automatically. DO NOT HAND-EDIT.
+
+package terminfo
+
+func init() {
+ // VTE-based terminal
+ AddTerminfo(&Terminfo{
+ Name: "xterm-termite",
+ Columns: 80,
+ Lines: 24,
+ Colors: 256,
+ Bell: "\a",
+ Clear: "\x1b[H\x1b[2J",
+ EnterCA: "\x1b[?1049h",
+ ExitCA: "\x1b[?1049l",
+ ShowCursor: "\x1b[?12l\x1b[?25h",
+ HideCursor: "\x1b[?25l",
+ AttrOff: "\x1b(B\x1b[m",
+ Underline: "\x1b[4m",
+ Bold: "\x1b[1m",
+ Dim: "\x1b[2m",
+ Reverse: "\x1b[7m",
+ EnterKeypad: "\x1b[?1h\x1b=",
+ ExitKeypad: "\x1b[?1l\x1b>",
+ SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m",
+ SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m",
+ SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m",
+ AltChars: "++,,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
+ EnterAcs: "\x1b(0",
+ ExitAcs: "\x1b(B",
+ Mouse: "\x1b[M",
+ MouseMode: "%?%p1%{1}%=%t%'h'%Pa%e%'l'%Pa%;\x1b[?1000%ga%c\x1b[?1002%ga%c\x1b[?1003%ga%c\x1b[?1006%ga%c",
+ SetCursor: "\x1b[%i%p1%d;%p2%dH",
+ CursorBack1: "\b",
+ CursorUp1: "\x1b[A",
+ KeyUp: "\x1bOA",
+ KeyDown: "\x1bOB",
+ KeyRight: "\x1bOC",
+ KeyLeft: "\x1bOD",
+ KeyInsert: "\x1b[2~",
+ KeyDelete: "\x1b[3~",
+ KeyBackspace: "\xff",
+ KeyHome: "\x1bOH",
+ KeyEnd: "\x1bOF",
+ KeyPgUp: "\x1b[5~",
+ KeyPgDn: "\x1b[6~",
+ KeyF1: "\x1bOP",
+ KeyF2: "\x1bOQ",
+ KeyF3: "\x1bOR",
+ KeyF4: "\x1bOS",
+ KeyF5: "\x1b[15~",
+ KeyF6: "\x1b[17~",
+ KeyF7: "\x1b[18~",
+ KeyF8: "\x1b[19~",
+ KeyF9: "\x1b[20~",
+ KeyF10: "\x1b[21~",
+ KeyF11: "\x1b[23~",
+ KeyF12: "\x1b[24~",
+ KeyF13: "\x1b[1;2P",
+ KeyF14: "\x1b[1;2Q",
+ KeyF15: "\x1b[1;2R",
+ KeyF16: "\x1b[1;2S",
+ KeyF17: "\x1b[15;2~",
+ KeyF18: "\x1b[17;2~",
+ KeyF19: "\x1b[18;2~",
+ KeyF20: "\x1b[19;2~",
+ KeyF21: "\x1b[20;2~",
+ KeyF22: "\x1b[21;2~",
+ KeyF23: "\x1b[23;2~",
+ KeyF24: "\x1b[24;2~",
+ KeyF25: "\x1b[1;5P",
+ KeyF26: "\x1b[1;5Q",
+ KeyF27: "\x1b[1;5R",
+ KeyF28: "\x1b[1;5S",
+ KeyF29: "\x1b[15;5~",
+ KeyF30: "\x1b[17;5~",
+ KeyF31: "\x1b[18;5~",
+ KeyF32: "\x1b[19;5~",
+ KeyF33: "\x1b[20;5~",
+ KeyF34: "\x1b[21;5~",
+ KeyF35: "\x1b[23;5~",
+ KeyF36: "\x1b[24;5~",
+ KeyF37: "\x1b[1;6P",
+ KeyF38: "\x1b[1;6Q",
+ KeyF39: "\x1b[1;6R",
+ KeyF40: "\x1b[1;6S",
+ KeyF41: "\x1b[15;6~",
+ KeyF42: "\x1b[17;6~",
+ KeyF43: "\x1b[18;6~",
+ KeyF44: "\x1b[19;6~",
+ KeyF45: "\x1b[20;6~",
+ KeyF46: "\x1b[21;6~",
+ KeyF47: "\x1b[23;6~",
+ KeyF48: "\x1b[24;6~",
+ KeyF49: "\x1b[1;3P",
+ KeyF50: "\x1b[1;3Q",
+ KeyF51: "\x1b[1;3R",
+ KeyF52: "\x1b[1;3S",
+ KeyF53: "\x1b[15;3~",
+ KeyF54: "\x1b[17;3~",
+ KeyF55: "\x1b[18;3~",
+ KeyF56: "\x1b[19;3~",
+ KeyF57: "\x1b[20;3~",
+ KeyF58: "\x1b[21;3~",
+ KeyF59: "\x1b[23;3~",
+ KeyF60: "\x1b[24;3~",
+ KeyF61: "\x1b[1;4P",
+ KeyF62: "\x1b[1;4Q",
+ KeyF63: "\x1b[1;4R",
+ KeyBacktab: "\x1b[Z",
+ KeyShfLeft: "\x1b[1;2D",
+ KeyShfRight: "\x1b[1;2C",
+ KeyShfUp: "\x1b[1;2A",
+ KeyShfDown: "\x1b[1;2B",
+ KeyCtrlLeft: "\x1b[1;5D",
+ KeyCtrlRight: "\x1b[1;5C",
+ KeyCtrlUp: "\x1b[1;5A",
+ KeyCtrlDown: "\x1b[1;5B",
+ KeyMetaLeft: "\x1b[1;9D",
+ KeyMetaRight: "\x1b[1;9C",
+ KeyMetaUp: "\x1b[1;9A",
+ KeyMetaDown: "\x1b[1;9B",
+ KeyAltLeft: "\x1b[1;3D",
+ KeyAltRight: "\x1b[1;3C",
+ KeyAltUp: "\x1b[1;3A",
+ KeyAltDown: "\x1b[1;3B",
+ KeyAltShfLeft: "\x1b[1;4D",
+ KeyAltShfRight: "\x1b[1;4C",
+ KeyAltShfUp: "\x1b[1;4A",
+ KeyAltShfDown: "\x1b[1;4B",
+ KeyMetaShfLeft: "\x1b[1;10D",
+ KeyMetaShfRight: "\x1b[1;10C",
+ KeyMetaShfUp: "\x1b[1;10A",
+ KeyMetaShfDown: "\x1b[1;10B",
+ KeyCtrlShfLeft: "\x1b[1;6D",
+ KeyCtrlShfRight: "\x1b[1;6C",
+ KeyCtrlShfUp: "\x1b[1;6A",
+ KeyCtrlShfDown: "\x1b[1;6B",
+ KeyShfHome: "\x1b[1;2H",
+ KeyShfEnd: "\x1b[1;2F",
+ KeyCtrlHome: "\x1b[1;5H",
+ KeyCtrlEnd: "\x1b[1;5F",
+ KeyAltHome: "\x1b[1;9H",
+ KeyAltEnd: "\x1b[1;9F",
+ KeyCtrlShfHome: "\x1b[1;6H",
+ KeyCtrlShfEnd: "\x1b[1;6F",
+ KeyMetaShfHome: "\x1b[1;10H",
+ KeyMetaShfEnd: "\x1b[1;10F",
+ KeyAltShfHome: "\x1b[1;4H",
+ KeyAltShfEnd: "\x1b[1;4F",
+ })
+}
diff --git a/vendor/maunium.net/go/tcell/tscreen.go b/vendor/maunium.net/go/tcell/tscreen.go
index d8e62b2..dd49814 100644
--- a/vendor/maunium.net/go/tcell/tscreen.go
+++ b/vendor/maunium.net/go/tcell/tscreen.go
@@ -444,8 +444,8 @@ func (t *tScreen) ResetTitle() {
func (t *tScreen) Fini() {
t.Lock()
defer t.Unlock()
-
- ti := t.ti
+
+ ti := t.ti
t.cells.Resize(0, 0)
t.TPuts(ti.ShowCursor)
t.TPuts(ti.AttrOff)
@@ -467,7 +467,7 @@ func (t *tScreen) Fini() {
default:
close(t.quit)
}
-
+
t.termioFini()
}
diff --git a/vendor/maunium.net/go/tview/LICENSE.txt b/vendor/maunium.net/go/tview/LICENSE.txt
index 8aa2645..9d69430 100644
--- a/vendor/maunium.net/go/tview/LICENSE.txt
+++ b/vendor/maunium.net/go/tview/LICENSE.txt
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) [year] [fullname]
+Copyright (c) 2018 Oliver Kuederle
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/vendor/maunium.net/go/tview/README.md b/vendor/maunium.net/go/tview/README.md
index 3e5734e..7ce06a2 100644
--- a/vendor/maunium.net/go/tview/README.md
+++ b/vendor/maunium.net/go/tview/README.md
@@ -12,6 +12,7 @@ Among these components are:
- __Input forms__ (include __input/password fields__, __drop-down selections__, __checkboxes__, and __buttons__)
- Navigable multi-color __text views__
- Sophisticated navigable __table views__
+- Flexible __tree views__
- Selectable __lists__
- __Grid__, __Flexbox__ and __page layouts__
- Modal __message windows__
@@ -64,9 +65,17 @@ Add your issue here on GitHub. Feel free to get in touch if you have any questio
(There are no corresponding tags in the project. I only keep such a history in this README.)
+- v0.19 (2018-10-28)
+ - Added `QueueUpdate()` and `QueueEvent()` to `Application` to help with modifications to primitives from goroutines.
+- v0.18 (2018-10-18)
+ - `InputField` elements can now be navigated freely.
+- v0.17 (2018-06-20)
+ - Added `TreeView`.
+- v0.15 (2018-05-02)
+ - `Flex` and `Grid` don't clear their background per default, thus allowing for custom modals. See the [Wiki](https://github.com/rivo/tview/wiki/Modal) for an example.
- v0.14 (2018-04-13)
- Added an `Escape()` function which keep strings like color or region tags from being recognized as such.
- - Added `ANSIIWriter()` and `TranslateANSII()` which convert ANSII escape sequences to `tview` color tags.
+ - Added `ANSIWriter()` and `TranslateANSI()` which convert ANSI escape sequences to `tview` color tags.
- v0.13 (2018-04-01)
- Added background colors and text attributes to color tags.
- v0.12 (2018-03-13)
diff --git a/vendor/maunium.net/go/tview/ansii.go b/vendor/maunium.net/go/tview/ansi.go
index 0ce3d4a..4d14c28 100644
--- a/vendor/maunium.net/go/tview/ansii.go
+++ b/vendor/maunium.net/go/tview/ansi.go
@@ -8,44 +8,44 @@ import (
"strings"
)
-// The states of the ANSII escape code parser.
+// The states of the ANSI escape code parser.
const (
- ansiiText = iota
- ansiiEscape
- ansiiSubstring
- ansiiControlSequence
+ ansiText = iota
+ ansiEscape
+ ansiSubstring
+ ansiControlSequence
)
-// ansii is a io.Writer which translates ANSII escape codes into tview color
+// ansi is a io.Writer which translates ANSI escape codes into tview color
// tags.
-type ansii struct {
+type ansi struct {
io.Writer
// Reusable buffers.
buffer *bytes.Buffer // The entire output text of one Write().
csiParameter, csiIntermediate *bytes.Buffer // Partial CSI strings.
- // The current state of the parser. One of the ansii constants.
+ // The current state of the parser. One of the ansi constants.
state int
}
-// ANSIIWriter returns an io.Writer which translates any ANSII escape codes
+// ANSIWriter returns an io.Writer which translates any ANSI escape codes
// written to it into tview color tags. Other escape codes don't have an effect
// and are simply removed. The translated text is written to the provided
// writer.
-func ANSIIWriter(writer io.Writer) io.Writer {
- return &ansii{
+func ANSIWriter(writer io.Writer) io.Writer {
+ return &ansi{
Writer: writer,
buffer: new(bytes.Buffer),
csiParameter: new(bytes.Buffer),
csiIntermediate: new(bytes.Buffer),
- state: ansiiText,
+ state: ansiText,
}
}
-// Write parses the given text as a string of runes, translates ANSII escape
+// Write parses the given text as a string of runes, translates ANSI escape
// codes to color tags and writes them to the output writer.
-func (a *ansii) Write(text []byte) (int, error) {
+func (a *ansi) Write(text []byte) (int, error) {
defer func() {
a.buffer.Reset()
}()
@@ -54,23 +54,23 @@ func (a *ansii) Write(text []byte) (int, error) {
switch a.state {
// We just entered an escape sequence.
- case ansiiEscape:
+ case ansiEscape:
switch r {
case '[': // Control Sequence Introducer.
a.csiParameter.Reset()
a.csiIntermediate.Reset()
- a.state = ansiiControlSequence
+ a.state = ansiControlSequence
case 'c': // Reset.
fmt.Fprint(a.buffer, "[-:-:-]")
- a.state = ansiiText
+ a.state = ansiText
case 'P', ']', 'X', '^', '_': // Substrings and commands.
- a.state = ansiiSubstring
+ a.state = ansiSubstring
default: // Ignore.
- a.state = ansiiText
+ a.state = ansiText
}
// CSI Sequences.
- case ansiiControlSequence:
+ case ansiControlSequence:
switch {
case r >= 0x30 && r <= 0x3f: // Parameter bytes.
if _, err := a.csiParameter.WriteRune(r); err != nil {
@@ -166,16 +166,16 @@ func (a *ansii) Write(text []byte) (int, error) {
red := (colorNumber - 16) / 36
green := ((colorNumber - 16) / 6) % 6
blue := (colorNumber - 16) % 6
- color = fmt.Sprintf("%02x%02x%02x", 255*red/5, 255*green/5, 255*blue/5)
+ color = fmt.Sprintf("#%02x%02x%02x", 255*red/5, 255*green/5, 255*blue/5)
} else if colorNumber <= 255 {
grey := 255 * (colorNumber - 232) / 23
- color = fmt.Sprintf("%02x%02x%02x", grey, grey, grey)
+ color = fmt.Sprintf("#%02x%02x%02x", grey, grey, grey)
}
} else if fields[index+1] == "2" && len(fields) > index+4 { // 24-bit colors.
red, _ := strconv.Atoi(fields[index+2])
green, _ := strconv.Atoi(fields[index+3])
blue, _ := strconv.Atoi(fields[index+4])
- color = fmt.Sprintf("%02x%02x%02x", red, green, blue)
+ color = fmt.Sprintf("#%02x%02x%02x", red, green, blue)
}
}
if len(color) > 0 {
@@ -194,22 +194,22 @@ func (a *ansii) Write(text []byte) (int, error) {
fmt.Fprintf(a.buffer, "[%s:%s%s]", foreground, background, attributes)
}
}
- a.state = ansiiText
+ a.state = ansiText
default: // Undefined byte.
- a.state = ansiiText // Abort CSI.
+ a.state = ansiText // Abort CSI.
}
// We just entered a substring/command sequence.
- case ansiiSubstring:
+ case ansiSubstring:
if r == 27 { // Most likely the end of the substring.
- a.state = ansiiEscape
+ a.state = ansiEscape
} // Ignore all other characters.
- // "ansiiText" and all others.
+ // "ansiText" and all others.
default:
if r == 27 {
// This is the start of an escape sequence.
- a.state = ansiiEscape
+ a.state = ansiEscape
} else {
// Just a regular rune. Send to buffer.
if _, err := a.buffer.WriteRune(r); err != nil {
@@ -227,11 +227,11 @@ func (a *ansii) Write(text []byte) (int, error) {
return len(text), nil
}
-// TranslateANSII replaces ANSII escape sequences found in the provided string
+// TranslateANSI replaces ANSI escape sequences found in the provided string
// with tview's color tags and returns the resulting string.
-func TranslateANSII(text string) string {
+func TranslateANSI(text string) string {
var buffer bytes.Buffer
- writer := ANSIIWriter(&buffer)
+ writer := ANSIWriter(&buffer)
writer.Write([]byte(text))
return buffer.String()
}
diff --git a/vendor/maunium.net/go/tview/application.go b/vendor/maunium.net/go/tview/application.go
index 7e6da3a..ae4d7f8 100644
--- a/vendor/maunium.net/go/tview/application.go
+++ b/vendor/maunium.net/go/tview/application.go
@@ -1,24 +1,36 @@
package tview
import (
- "fmt"
- "os"
"sync"
"maunium.net/go/tcell"
)
+// The size of the event/update/redraw channels.
+const queueSize = 100
+
// Application represents the top node of an application.
//
// It is not strictly required to use this class as none of the other classes
// depend on it. However, it provides useful tools to set up an application and
// plays nicely with all widgets.
+//
+// The following command displays a primitive p on the screen until Ctrl-C is
+// pressed:
+//
+// if err := tview.NewApplication().SetRoot(p, true).Run(); err != nil {
+// panic(err)
+// }
type Application struct {
sync.RWMutex
// The application's screen.
screen tcell.Screen
+ // Indicates whether the application's screen is currently active. This is
+ // false during suspended mode.
+ running bool
+
// The primitive which currently has the keyboard focus.
focus Primitive
@@ -48,13 +60,23 @@ type Application struct {
// was drawn.
afterDraw func(screen tcell.Screen)
- // If this value is true, the application has entered suspended mode.
- suspended bool
+ // Used to send screen events from separate goroutine to main event loop
+ events chan tcell.Event
+
+ // Functions queued from goroutines, used to serialize updates to primitives.
+ updates chan func()
+
+ // A channel which signals the end of the suspended mode.
+ suspendToken chan struct{}
}
// NewApplication creates and returns a new application.
func NewApplication() *Application {
- return &Application{}
+ return &Application{
+ events: make(chan tcell.Event, queueSize),
+ updates: make(chan func(), queueSize),
+ suspendToken: make(chan struct{}, 1),
+ }
}
// SetInputCapture sets a function which captures all key events before they are
@@ -97,140 +119,222 @@ func (a *Application) GetScreen() tcell.Screen {
return a.screen
}
+// SetScreen allows you to provide your own tcell.Screen object. For most
+// applications, this is not needed and you should be familiar with
+// tcell.Screen when using this function. Run() will call Init() and Fini() on
+// the provided screen object.
+//
+// This function is typically called before calling Run(). Calling it while an
+// application is running will switch the application to the new screen. Fini()
+// will be called on the old screen and Init() on the new screen (errors
+// returned by Init() will lead to a panic).
+//
+// Note that calling Suspend() will invoke Fini() on your screen object and it
+// will not be restored when suspended mode ends. Instead, a new default screen
+// object will be created.
+func (a *Application) SetScreen(screen tcell.Screen) *Application {
+ a.Lock()
+ defer a.Unlock()
+ if a.running {
+ a.screen.Fini()
+ }
+ a.screen = screen
+ if a.running {
+ if err := a.screen.Init(); err != nil {
+ panic(err)
+ }
+ }
+ return a
+}
+
// Run starts the application and thus the event loop. This function returns
// when Stop() was called.
func (a *Application) Run() error {
var err error
a.Lock()
- // Make a screen.
- a.screen, err = tcell.NewScreen()
- if err != nil {
- a.Unlock()
- return err
+ // Make a screen if there is none yet.
+ if a.screen == nil {
+ a.screen, err = tcell.NewScreen()
+ if err != nil {
+ a.Unlock()
+ return err
+ }
}
if err = a.screen.Init(); err != nil {
a.Unlock()
return err
}
a.screen.EnableMouse()
+ a.running = true
+
+ // We catch panics to clean up because they mess up the terminal.
+ defer func() {
+ if p := recover(); p != nil {
+ if a.screen != nil {
+ a.screen.Fini()
+ }
+ a.running = false
+ panic(p)
+ }
+ }()
// Draw the screen for the first time.
a.Unlock()
- a.Draw()
+ a.draw()
+
+ // Separate loop to wait for screen events.
+ var wg sync.WaitGroup
+ wg.Add(1)
+ a.suspendToken <- struct{}{} // We need this to get started.
+ go func() {
+ defer wg.Done()
+ for range a.suspendToken {
+ for {
+ a.RLock()
+ screen := a.screen
+ a.RUnlock()
+ if screen == nil {
+ // We have no screen. We might need to stop.
+ break
+ }
- // Start event loop.
- for {
- a.Lock()
- screen := a.screen
- if a.suspended {
- a.suspended = false // Clear previous suspended flag.
- }
- a.Unlock()
- if screen == nil {
- break
+ // Wait for next event and queue it.
+ event := screen.PollEvent()
+ if event != nil {
+ // Regular event. Queue.
+ a.QueueEvent(event)
+ continue
+ }
+
+ // A screen was finalized (event is nil).
+ a.RLock()
+ running := a.running
+ a.RUnlock()
+ if running {
+ // The application was stopped. End the event loop.
+ a.QueueEvent(nil)
+ return
+ }
+
+ // We're in suspended mode (running is false). Pause and wait for new
+ // token.
+ break
+ }
}
+ }()
- // Wait for next event.
- event := a.screen.PollEvent()
- if event == nil {
- a.Lock()
- if a.suspended {
- // This screen was renewed due to suspended mode.
- a.suspended = false
- a.Unlock()
- continue // Resume.
+ // Start event loop.
+EventLoop:
+ for {
+ select {
+ case event := <-a.events:
+ if event == nil {
+ break EventLoop
}
- a.Unlock()
- // The screen was finalized. Exit the loop.
- break
- }
+ switch event := event.(type) {
+ case *tcell.EventKey:
+ a.RLock()
+ p := a.focus
+ inputCapture := a.inputCapture
+ a.RUnlock()
+
+ // Intercept keys.
+ if inputCapture != nil {
+ event = inputCapture(event)
+ if event == nil {
+ continue // Don't forward event.
+ }
+ }
- switch event := event.(type) {
- case *tcell.EventKey:
- a.RLock()
- p := a.focus
- a.RUnlock()
-
- // Intercept keys.
- if a.inputCapture != nil {
- event = a.inputCapture(event)
- if event == nil {
- break // Don't forward event.
+ // Ctrl-C closes the application.
+ if event.Key() == tcell.KeyCtrlC {
+ a.Stop()
}
- }
- // Pass other key events to the currently focused primitive.
- if p != nil {
- if handler := p.InputHandler(); handler != nil {
- handler(event, func(p Primitive) {
- a.SetFocus(p)
- })
- a.Draw()
+ // Pass other key events to the currently focused primitive.
+ if p != nil {
+ if handler := p.InputHandler(); handler != nil {
+ handler(event, func(p Primitive) {
+ a.SetFocus(p)
+ })
+ a.draw()
+ }
}
- }
- case *tcell.EventMouse:
- a.RLock()
- p := a.focus
- a.RUnlock()
-
- // Intercept keys.
- if a.mouseCapture != nil {
- event = a.mouseCapture(event)
- if event == nil {
- break // Don't forward event.
+
+ case *tcell.EventMouse:
+ a.RLock()
+ p := a.focus
+ a.RUnlock()
+
+ // Intercept keys.
+ if a.mouseCapture != nil {
+ event = a.mouseCapture(event)
+ if event == nil {
+ break // Don't forward event.
+ }
}
- }
- // Pass other key events to the currently focused primitive.
- if p != nil {
- if handler := p.MouseHandler(); handler != nil {
- handler(event, func(p Primitive) {
- a.SetFocus(p)
- })
- //a.Draw()
+ // Pass other key events to the currently focused primitive.
+ if p != nil {
+ if handler := p.MouseHandler(); handler != nil {
+ handler(event, func(p Primitive) {
+ a.SetFocus(p)
+ })
+ //a.Draw()
+ }
}
- }
- case *tcell.EventPaste:
- a.RLock()
- p := a.focus
- a.RUnlock()
-
- if a.pasteCapture != nil {
- event = a.pasteCapture(event)
- if event == nil {
- break
+ case *tcell.EventPaste:
+ a.RLock()
+ p := a.focus
+ a.RUnlock()
+
+ if a.pasteCapture != nil {
+ event = a.pasteCapture(event)
+ if event == nil {
+ break
+ }
}
- }
- if p != nil {
- if handler := p.PasteHandler(); handler != nil {
- handler(event)
- a.Draw()
+ if p != nil {
+ if handler := p.PasteHandler(); handler != nil {
+ handler(event)
+ a.Draw()
+ }
}
+ case *tcell.EventResize:
+ a.RLock()
+ screen := a.screen
+ a.RUnlock()
+ screen.Clear()
+ a.draw()
}
- case *tcell.EventResize:
- a.Lock()
- screen := a.screen
- a.Unlock()
- screen.Clear()
- a.Draw()
+
+ // If we have updates, now is the time to execute them.
+ case updater := <-a.updates:
+ updater()
}
}
+ a.running = false
+ close(a.suspendToken)
+ wg.Wait()
+
return nil
}
// Stop stops the application, causing Run() to return.
func (a *Application) Stop() {
- a.RLock()
- defer a.RUnlock()
- if a.screen == nil {
+ a.Lock()
+ defer a.Unlock()
+ screen := a.screen
+ if screen == nil {
return
}
- a.screen.Fini()
a.screen = nil
+ screen.Fini()
+ // a.running is still true, the main loop will clean up.
}
// Suspend temporarily suspends the application by exiting terminal UI mode and
@@ -243,29 +347,24 @@ func (a *Application) Stop() {
func (a *Application) Suspend(f func()) bool {
a.Lock()
- if a.suspended || a.screen == nil {
- // Application is already suspended.
+ screen := a.screen
+ if screen == nil {
+ // Screen has not yet been initialized.
a.Unlock()
return false
}
- // Enter suspended mode.
- a.suspended = true
+ // Enter suspended mode. Make a new screen here already so our event loop can
+ // continue.
+ a.screen = nil
+ a.running = false
+ screen.Fini()
a.Unlock()
- a.Stop()
-
- // Deal with panics during suspended mode. Exit the program.
- defer func() {
- if p := recover(); p != nil {
- fmt.Println(p)
- os.Exit(1)
- }
- }()
// Wait for "f" to return.
f()
- // Make a new screen and redraw.
+ // Initialize our new screen and draw the contents.
a.Lock()
var err error
a.screen, err = tcell.NewScreen()
@@ -278,23 +377,36 @@ func (a *Application) Suspend(f func()) bool {
panic(err)
}
a.screen.EnableMouse()
+ a.running = true
a.Unlock()
- a.Draw()
+ a.draw()
+ a.suspendToken <- struct{}{}
+ // One key event will get lost, see https://github.com/gdamore/tcell/issues/194
// Continue application loop.
return true
}
-// Draw refreshes the screen. It calls the Draw() function of the application's
-// root primitive and then syncs the screen buffer.
+// Draw refreshes the screen (during the next update cycle). It calls the Draw()
+// function of the application's root primitive and then syncs the screen
+// buffer.
func (a *Application) Draw() *Application {
- a.RLock()
+ a.QueueUpdate(func() {
+ a.draw()
+ })
+ return a
+}
+
+// draw actually does what Draw() promises to do.
+func (a *Application) draw() *Application {
+ a.Lock()
+ defer a.Unlock()
+
screen := a.screen
root := a.root
fullscreen := a.rootFullscreen
before := a.beforeDraw
after := a.afterDraw
- a.RUnlock()
// Maybe we're not ready yet or not anymore.
if screen == nil || root == nil {
@@ -427,3 +539,35 @@ func (a *Application) GetFocus() Primitive {
defer a.RUnlock()
return a.focus
}
+
+// QueueUpdate is used to synchronize access to primitives from non-main
+// goroutines. The provided function will be executed as part of the event loop
+// and thus will not cause race conditions with other such update functions or
+// the Draw() function.
+//
+// Note that Draw() is not implicitly called after the execution of f as that
+// may not be desirable. You can call Draw() from f if the screen should be
+// refreshed after each update. Alternatively, use QueueUpdateDraw() to follow
+// up with an immediate refresh of the screen.
+func (a *Application) QueueUpdate(f func()) *Application {
+ a.updates <- f
+ return a
+}
+
+// QueueUpdateDraw works like QueueUpdate() except it refreshes the screen
+// immediately after executing f.
+func (a *Application) QueueUpdateDraw(f func()) *Application {
+ a.QueueUpdate(func() {
+ f()
+ a.draw()
+ })
+ return a
+}
+
+// QueueEvent sends an event to the Application event loop.
+//
+// It is not recommended for event to be nil.
+func (a *Application) QueueEvent(event tcell.Event) *Application {
+ a.events <- event
+ return a
+}
diff --git a/vendor/maunium.net/go/tview/borders.go b/vendor/maunium.net/go/tview/borders.go
new file mode 100644
index 0000000..946c878
--- /dev/null
+++ b/vendor/maunium.net/go/tview/borders.go
@@ -0,0 +1,45 @@
+package tview
+
+// Borders defines various borders used when primitives are drawn.
+// These may be changed to accommodate a different look and feel.
+var Borders = struct {
+ Horizontal rune
+ Vertical rune
+ TopLeft rune
+ TopRight rune
+ BottomLeft rune
+ BottomRight rune
+
+ LeftT rune
+ RightT rune
+ TopT rune
+ BottomT rune
+ Cross rune
+
+ HorizontalFocus rune
+ VerticalFocus rune
+ TopLeftFocus rune
+ TopRightFocus rune
+ BottomLeftFocus rune
+ BottomRightFocus rune
+}{
+ Horizontal: BoxDrawingsLightHorizontal,
+ Vertical: BoxDrawingsLightVertical,
+ TopLeft: BoxDrawingsLightDownAndRight,
+ TopRight: BoxDrawingsLightDownAndLeft,
+ BottomLeft: BoxDrawingsLightUpAndRight,
+ BottomRight: BoxDrawingsLightUpAndLeft,
+
+ LeftT: BoxDrawingsLightVerticalAndRight,
+ RightT: BoxDrawingsLightVerticalAndLeft,
+ TopT: BoxDrawingsLightDownAndHorizontal,
+ BottomT: BoxDrawingsLightUpAndHorizontal,
+ Cross: BoxDrawingsLightVerticalAndHorizontal,
+
+ HorizontalFocus: BoxDrawingsDoubleHorizontal,
+ VerticalFocus: BoxDrawingsDoubleVertical,
+ TopLeftFocus: BoxDrawingsDoubleDownAndRight,
+ TopRightFocus: BoxDrawingsDoubleDownAndLeft,
+ BottomLeftFocus: BoxDrawingsDoubleUpAndRight,
+ BottomRightFocus: BoxDrawingsDoubleUpAndLeft,
+}
diff --git a/vendor/maunium.net/go/tview/box.go b/vendor/maunium.net/go/tview/box.go
index ff3cc1e..85bf1b2 100644
--- a/vendor/maunium.net/go/tview/box.go
+++ b/vendor/maunium.net/go/tview/box.go
@@ -32,6 +32,9 @@ type Box struct {
// The color of the border.
borderColor tcell.Color
+ // The style attributes of the border.
+ borderAttributes tcell.AttrMask
+
// The title. Only visible if there is a border, too.
title string
@@ -48,10 +51,6 @@ type Box struct {
// Whether or not this box has focus.
hasFocus bool
- // If set to true, the inner rect of this box will be within the screen at the
- // last time the box was drawn.
- clampToScreen bool
-
// An optional capture function which receives a key event and returns the
// event to be forwarded to the primitive's default input handler (nil if
// nothing should be forwarded).
@@ -78,7 +77,6 @@ func NewBox() *Box {
borderColor: Styles.BorderColor,
titleColor: Styles.TitleColor,
titleAlign: AlignCenter,
- clampToScreen: true,
}
b.focus = b
return b
@@ -121,6 +119,7 @@ func (b *Box) SetRect(x, y, width, height int) {
b.y = y
b.width = width
b.height = height
+ b.innerX = -1 // Mark inner rect as uninitialized.
}
// SetDrawFunc sets a callback function which is invoked after the box primitive
@@ -273,6 +272,15 @@ func (b *Box) SetBorderColor(color tcell.Color) *Box {
return b
}
+// SetBorderAttributes sets the border's style attributes. You can combine
+// different attributes using bitmask operations:
+//
+// box.SetBorderAttributes(tcell.AttrUnderline | tcell.AttrBold)
+func (b *Box) SetBorderAttributes(attr tcell.AttrMask) *Box {
+ b.borderAttributes = attr
+ return b
+}
+
// SetTitle sets the box's title.
func (b *Box) SetTitle(title string) *Box {
b.title = title
@@ -319,30 +327,30 @@ func (b *Box) Draw(screen tcell.Screen) {
// Draw border.
if b.border && b.width >= 2 && b.height >= 2 {
- border := background.Foreground(b.borderColor)
+ border := background.Foreground(b.borderColor) | tcell.Style(b.borderAttributes)
var vertical, horizontal, topLeft, topRight, bottomLeft, bottomRight rune
if b.focus.HasFocus() {
- vertical = GraphicsDbVertBar
- horizontal = GraphicsDbHorBar
- topLeft = GraphicsDbTopLeftCorner
- topRight = GraphicsDbTopRightCorner
- bottomLeft = GraphicsDbBottomLeftCorner
- bottomRight = GraphicsDbBottomRightCorner
+ horizontal = Borders.HorizontalFocus
+ vertical = Borders.VerticalFocus
+ topLeft = Borders.TopLeftFocus
+ topRight = Borders.TopRightFocus
+ bottomLeft = Borders.BottomLeftFocus
+ bottomRight = Borders.BottomRightFocus
} else {
- vertical = GraphicsHoriBar
- horizontal = GraphicsVertBar
- topLeft = GraphicsTopLeftCorner
- topRight = GraphicsTopRightCorner
- bottomLeft = GraphicsBottomLeftCorner
- bottomRight = GraphicsBottomRightCorner
+ horizontal = Borders.Horizontal
+ vertical = Borders.Vertical
+ topLeft = Borders.TopLeft
+ topRight = Borders.TopRight
+ bottomLeft = Borders.BottomLeft
+ bottomRight = Borders.BottomRight
}
for x := b.x + 1; x < b.x+b.width-1; x++ {
- screen.SetContent(x, b.y, vertical, nil, border)
- screen.SetContent(x, b.y+b.height-1, vertical, nil, border)
+ screen.SetContent(x, b.y, horizontal, nil, border)
+ screen.SetContent(x, b.y+b.height-1, horizontal, nil, border)
}
for y := b.y + 1; y < b.y+b.height-1; y++ {
- screen.SetContent(b.x, y, horizontal, nil, border)
- screen.SetContent(b.x+b.width-1, y, horizontal, nil, border)
+ screen.SetContent(b.x, y, vertical, nil, border)
+ screen.SetContent(b.x+b.width-1, y, vertical, nil, border)
}
screen.SetContent(b.x, b.y, topLeft, nil, border)
screen.SetContent(b.x+b.width-1, b.y, topRight, nil, border)
@@ -351,11 +359,11 @@ func (b *Box) Draw(screen tcell.Screen) {
// Draw title.
if b.title != "" && b.width >= 4 {
- _, printed := Print(screen, b.title, b.x+1, b.y, b.width-2, b.titleAlign, b.titleColor)
- if StringWidth(b.title)-printed > 0 && printed > 0 {
+ printed, _ := Print(screen, b.title, b.x+1, b.y, b.width-2, b.titleAlign, b.titleColor)
+ if len(b.title)-printed > 0 && printed > 0 {
_, _, style, _ := screen.GetContent(b.x+b.width-2, b.y)
fg, _, _ := style.Decompose()
- Print(screen, string(GraphicsEllipsis), b.x+b.width-2, b.y, 1, AlignLeft, fg)
+ Print(screen, string(SemigraphicsHorizontalEllipsis), b.x+b.width-2, b.y, 1, AlignLeft, fg)
}
}
}
@@ -370,22 +378,20 @@ func (b *Box) Draw(screen tcell.Screen) {
}
// Clamp inner rect to screen.
- if b.clampToScreen {
- width, height := screen.Size()
- if b.innerX < 0 {
- b.innerWidth += b.innerX
- b.innerX = 0
- }
- if b.innerX+b.innerWidth >= width {
- b.innerWidth = width - b.innerX
- }
- if b.innerY+b.innerHeight >= height {
- b.innerHeight = height - b.innerY
- }
- if b.innerY < 0 {
- b.innerHeight += b.innerY
- b.innerY = 0
- }
+ width, height := screen.Size()
+ if b.innerX < 0 {
+ b.innerWidth += b.innerX
+ b.innerX = 0
+ }
+ if b.innerX+b.innerWidth >= width {
+ b.innerWidth = width - b.innerX
+ }
+ if b.innerY+b.innerHeight >= height {
+ b.innerHeight = height - b.innerY
+ }
+ if b.innerY < 0 {
+ b.innerHeight += b.innerY
+ b.innerY = 0
}
}
diff --git a/vendor/maunium.net/go/tview/checkbox.go b/vendor/maunium.net/go/tview/checkbox.go
index ae58720..48d4592 100644
--- a/vendor/maunium.net/go/tview/checkbox.go
+++ b/vendor/maunium.net/go/tview/checkbox.go
@@ -124,9 +124,9 @@ func (c *Checkbox) SetChangedFunc(handler func(checked bool)) *Checkbox {
return c
}
-// SetDoneFunc sets a handler which is called when the user is done entering
-// text. The callback function is provided with the key that was pressed, which
-// is one of the following:
+// SetDoneFunc sets a handler which is called when the user is done using the
+// checkbox. The callback function is provided with the key that was pressed,
+// which is one of the following:
//
// - KeyEscape: Abort text input.
// - KeyTab: Move to the next field.
diff --git a/vendor/maunium.net/go/tview/doc.go b/vendor/maunium.net/go/tview/doc.go
index ccaaaf1..ddc410f 100644
--- a/vendor/maunium.net/go/tview/doc.go
+++ b/vendor/maunium.net/go/tview/doc.go
@@ -7,10 +7,12 @@ Widgets
The package implements the following widgets:
- - TextView: Scrollable windows that display multi-colored text. Text may also
+ - TextView: A scrollable window that display multi-colored text. Text may also
be highlighted.
- - Table: Scrollable display of tabular data. Table cells, rows, or columns may
- also be highlighted.
+ - Table: A scrollable display of tabular data. Table cells, rows, or columns
+ may also be highlighted.
+ - TreeView: A scrollable display for hierarchical data. Tree nodes can be
+ highlighted, collapsed, expanded, and more.
- List: A navigable text list with optional keyboard shortcuts.
- InputField: One-line input fields to enter text.
- DropDown: Drop-down selection fields.
@@ -83,7 +85,7 @@ tag is as follows:
[<foreground>:<background>:<flags>]
-Each of the three fields can be left blank and trailing fields can be ommitted.
+Each of the three fields can be left blank and trailing fields can be omitted.
(Empty square brackets "[]", however, are not considered color tags.) Colors
that are not specified will be left unchanged. A field with just a dash ("-")
means "reset to default".
@@ -135,6 +137,27 @@ Unicode Support
This package supports unicode characters including wide characters.
+Concurrency
+
+Many functions in this package are not thread-safe. For many applications, this
+may not be an issue: If your code makes changes in response to key events, it
+will execute in the main goroutine and thus will not cause any race conditions.
+
+If you access your primitives from other goroutines, however, you will need to
+synchronize execution. The easiest way to do this is to call
+Application.QueueUpdate() or Application.QueueUpdateDraw() (see the function
+documentation for details):
+
+ go func() {
+ app.QueueUpdateDraw(func() {
+ table.SetCellSimple(0, 0, "Foo bar")
+ })
+ }()
+
+One exception to this is the io.Writer interface implemented by TextView. You
+can safely write to a TextView from any goroutine. See the TextView
+documentation for details.
+
Type Hierarchy
All widgets listed above contain the Box type. All of Box's functions are
diff --git a/vendor/maunium.net/go/tview/dropdown.go b/vendor/maunium.net/go/tview/dropdown.go
index 4e4d888..02c93bd 100644
--- a/vendor/maunium.net/go/tview/dropdown.go
+++ b/vendor/maunium.net/go/tview/dropdown.go
@@ -354,6 +354,7 @@ func (d *DropDown) InputHandler() func(event *tcell.EventKey, setFocus func(p Pr
// Hand control over to the list.
d.open = true
+ optionBefore := d.currentOption
d.list.SetSelectedFunc(func(index int, mainText, secondaryText string, shortcut rune) {
// An option was selected. Close the list again.
d.open = false
@@ -374,6 +375,10 @@ func (d *DropDown) InputHandler() func(event *tcell.EventKey, setFocus func(p Pr
d.prefix = string(r[:len(r)-1])
}
evalPrefix()
+ } else if event.Key() == tcell.KeyEscape {
+ d.open = false
+ d.currentOption = optionBefore
+ setFocus(d)
} else {
d.prefix = ""
}
diff --git a/vendor/maunium.net/go/tview/flex.go b/vendor/maunium.net/go/tview/flex.go
index ad42d3a..bc1cfe1 100644
--- a/vendor/maunium.net/go/tview/flex.go
+++ b/vendor/maunium.net/go/tview/flex.go
@@ -28,7 +28,7 @@ type Flex struct {
*Box
// The items to be positioned.
- items []flexItem
+ items []*flexItem
// FlexRow or FlexColumn.
direction int
@@ -79,7 +79,7 @@ func (f *Flex) SetFullScreen(fullScreen bool) *Flex {
// You can provide a nil value for the primitive. This will still consume screen
// space but nothing will be drawn.
func (f *Flex) AddItem(item Primitive, fixedSize, proportion int, focus bool) *Flex {
- f.items = append(f.items, flexItem{Item: item, FixedSize: fixedSize, Proportion: proportion, Focus: focus})
+ f.items = append(f.items, &flexItem{Item: item, FixedSize: fixedSize, Proportion: proportion, Focus: focus})
return f
}
@@ -94,6 +94,19 @@ func (f *Flex) RemoveItem(p Primitive) *Flex {
return f
}
+// ResizeItem sets a new size for the item(s) with the given primitive. If there
+// are multiple Flex items with the same primitive, they will all receive the
+// same size. For details regarding the size parameters, see AddItem().
+func (f *Flex) ResizeItem(p Primitive, fixedSize, proportion int) *Flex {
+ for _, item := range f.items {
+ if item.Item == p {
+ item.FixedSize = fixedSize
+ item.Proportion = proportion
+ }
+ }
+ return f
+}
+
// Draw draws this primitive onto the screen.
func (f *Flex) Draw(screen tcell.Screen) {
f.Box.Draw(screen)
diff --git a/vendor/maunium.net/go/tview/form.go b/vendor/maunium.net/go/tview/form.go
index fe0e980..e960a52 100644
--- a/vendor/maunium.net/go/tview/form.go
+++ b/vendor/maunium.net/go/tview/form.go
@@ -26,7 +26,7 @@ type FormItem interface {
// required.
GetFieldWidth() int
- // SetEnteredFunc sets the handler function for when the user finished
+ // SetFinishedFunc sets the handler function for when the user finished
// entering data into the item. The handler may receive events for the
// Enter key (we're done), the Escape key (cancel input), the Tab key (move to
// next field), and the Backtab key (move to previous field).
@@ -218,6 +218,37 @@ func (f *Form) AddButton(label string, selected func()) *Form {
return f
}
+// GetButton returns the button at the specified 0-based index. Note that
+// buttons have been specially prepared for this form and modifying some of
+// their attributes may have unintended side effects.
+func (f *Form) GetButton(index int) *Button {
+ return f.buttons[index]
+}
+
+// RemoveButton removes the button at the specified position, starting with 0
+// for the button that was added first.
+func (f *Form) RemoveButton(index int) *Form {
+ f.buttons = append(f.buttons[:index], f.buttons[index+1:]...)
+ return f
+}
+
+// GetButtonCount returns the number of buttons in this form.
+func (f *Form) GetButtonCount() int {
+ return len(f.buttons)
+}
+
+// GetButtonIndex returns the index of the button with the given label, starting
+// with 0 for the button that was added first. If no such label was found, -1
+// is returned.
+func (f *Form) GetButtonIndex(label string) int {
+ for index, button := range f.buttons {
+ if button.GetLabel() == label {
+ return index
+ }
+ }
+ return -1
+}
+
// Clear removes all input elements from the form, including the buttons if
// specified.
func (f *Form) Clear(includeButtons bool) *Form {
@@ -251,6 +282,14 @@ func (f *Form) GetFormItem(index int) FormItem {
return f.items[index]
}
+// RemoveFormItem removes the form element at the given position, starting with
+// index 0. Elements are referenced in the order they were added. Buttons are
+// not included.
+func (f *Form) RemoveFormItem(index int) *Form {
+ f.items = append(f.items[:index], f.items[index+1:]...)
+ return f
+}
+
// GetFormItemByLabel returns the first form element with the given label. If
// no such element is found, nil is returned. Buttons are not searched and will
// therefore not be returned.
@@ -263,6 +302,18 @@ func (f *Form) GetFormItemByLabel(label string) FormItem {
return nil
}
+// GetFormItemIndex returns the index of the first form element with the given
+// label. If no such element is found, -1 is returned. Buttons are not searched
+// and will therefore not be returned.
+func (f *Form) GetFormItemIndex(label string) int {
+ for index, item := range f.items {
+ if item.GetLabel() == label {
+ return index
+ }
+ }
+ return -1
+}
+
// SetCancelFunc sets a handler which is called when the user hits the Escape
// key.
func (f *Form) SetCancelFunc(callback func()) *Form {
diff --git a/vendor/maunium.net/go/tview/grid.go b/vendor/maunium.net/go/tview/grid.go
index 77797ba..d8f6c97 100644
--- a/vendor/maunium.net/go/tview/grid.go
+++ b/vendor/maunium.net/go/tview/grid.go
@@ -583,11 +583,11 @@ func (g *Grid) Draw(screen tcell.Screen) {
}
by := item.y - 1
if by >= 0 && by < height {
- PrintJoinedBorder(screen, x+bx, y+by, GraphicsHoriBar, g.bordersColor)
+ PrintJoinedSemigraphics(screen, x+bx, y+by, Borders.Horizontal, g.bordersColor)
}
by = item.y + item.h
if by >= 0 && by < height {
- PrintJoinedBorder(screen, x+bx, y+by, GraphicsHoriBar, g.bordersColor)
+ PrintJoinedSemigraphics(screen, x+bx, y+by, Borders.Horizontal, g.bordersColor)
}
}
for by := item.y; by < item.y+item.h; by++ { // Left/right lines.
@@ -596,28 +596,28 @@ func (g *Grid) Draw(screen tcell.Screen) {
}
bx := item.x - 1
if bx >= 0 && bx < width {
- PrintJoinedBorder(screen, x+bx, y+by, GraphicsVertBar, g.bordersColor)
+ PrintJoinedSemigraphics(screen, x+bx, y+by, Borders.Vertical, g.bordersColor)
}
bx = item.x + item.w
if bx >= 0 && bx < width {
- PrintJoinedBorder(screen, x+bx, y+by, GraphicsVertBar, g.bordersColor)
+ PrintJoinedSemigraphics(screen, x+bx, y+by, Borders.Vertical, g.bordersColor)
}
}
bx, by := item.x-1, item.y-1 // Top-left corner.
if bx >= 0 && bx < width && by >= 0 && by < height {
- PrintJoinedBorder(screen, x+bx, y+by, GraphicsTopLeftCorner, g.bordersColor)
+ PrintJoinedSemigraphics(screen, x+bx, y+by, Borders.TopLeft, g.bordersColor)
}
bx, by = item.x+item.w, item.y-1 // Top-right corner.
if bx >= 0 && bx < width && by >= 0 && by < height {
- PrintJoinedBorder(screen, x+bx, y+by, GraphicsTopRightCorner, g.bordersColor)
+ PrintJoinedSemigraphics(screen, x+bx, y+by, Borders.TopRight, g.bordersColor)
}
bx, by = item.x-1, item.y+item.h // Bottom-left corner.
if bx >= 0 && bx < width && by >= 0 && by < height {
- PrintJoinedBorder(screen, x+bx, y+by, GraphicsBottomLeftCorner, g.bordersColor)
+ PrintJoinedSemigraphics(screen, x+bx, y+by, Borders.BottomLeft, g.bordersColor)
}
bx, by = item.x+item.w, item.y+item.h // Bottom-right corner.
if bx >= 0 && bx < width && by >= 0 && by < height {
- PrintJoinedBorder(screen, x+bx, y+by, GraphicsBottomRightCorner, g.bordersColor)
+ PrintJoinedSemigraphics(screen, x+bx, y+by, Borders.BottomRight, g.bordersColor)
}
}
}
diff --git a/vendor/maunium.net/go/tview/inputfield.go b/vendor/maunium.net/go/tview/inputfield.go
index 73224f2..ccc66e3 100644
--- a/vendor/maunium.net/go/tview/inputfield.go
+++ b/vendor/maunium.net/go/tview/inputfield.go
@@ -11,10 +11,23 @@ import (
)
// InputField is a one-line box (three lines if there is a title) where the
-// user can enter text.
+// user can enter text. Use SetAcceptanceFunc() to accept or reject input,
+// SetChangedFunc() to listen for changes, and SetMaskCharacter() to hide input
+// from onlookers (e.g. for password input).
//
-// Use SetMaskCharacter() to hide input from onlookers (e.g. for password
-// input).
+// The following keys can be used for navigation and editing:
+//
+// - Left arrow: Move left by one character.
+// - Right arrow: Move right by one character.
+// - Home, Ctrl-A, Alt-a: Move to the beginning of the line.
+// - End, Ctrl-E, Alt-e: Move to the end of the line.
+// - Alt-left, Alt-b: Move left by one word.
+// - Alt-right, Alt-f: Move right by one word.
+// - Backspace: Delete the character before the cursor.
+// - Delete: Delete the character after the cursor.
+// - Ctrl-K: Delete from the cursor to the end of the line.
+// - Ctrl-W: Delete the last word before the cursor.
+// - Ctrl-U: Delete the entire line.
//
// See https://github.com/rivo/tview/wiki/InputField for an example.
type InputField struct {
@@ -53,6 +66,12 @@ type InputField struct {
// disables masking.
maskCharacter rune
+ // The cursor position as a byte index into the text string.
+ cursorPos int
+
+ // The number of bytes of the text string skipped ahead while drawing.
+ offset int
+
// An optional function which may reject the last character that was entered.
accept func(text string, ch rune) bool
@@ -83,6 +102,7 @@ func NewInputField() *InputField {
// SetText sets the current text of the input field.
func (i *InputField) SetText(text string) *InputField {
i.text = text
+ i.cursorPos = len(text)
if i.changed != nil {
i.changed(text)
}
@@ -174,7 +194,7 @@ func (i *InputField) SetMaskCharacter(mask rune) *InputField {
// SetAcceptanceFunc sets a handler which may reject the last character that was
// entered (by returning false).
//
-// This package defines a number of variables Prefixed with InputField which may
+// This package defines a number of variables prefixed with InputField which may
// be used for common input (e.g. numbers, maximum text length).
func (i *InputField) SetAcceptanceFunc(handler func(textToCheck string, lastChar rune) bool) *InputField {
i.accept = handler
@@ -244,54 +264,67 @@ func (i *InputField) Draw(screen tcell.Screen) {
screen.SetContent(x+index, y, ' ', nil, fieldStyle)
}
- // Draw placeholder text.
+ // Text.
+ var cursorScreenPos int
text := i.text
if text == "" && i.placeholder != "" {
- Print(screen, i.placeholder, x, y, fieldWidth, AlignLeft, i.placeholderTextColor)
+ // Draw placeholder text.
+ Print(screen, Escape(i.placeholder), x, y, fieldWidth, AlignLeft, i.placeholderTextColor)
+ i.offset = 0
} else {
// Draw entered text.
if i.maskCharacter > 0 {
text = strings.Repeat(string(i.maskCharacter), utf8.RuneCountInString(i.text))
- } else {
- text = Escape(text)
}
- fieldWidth-- // We need one cell for the cursor.
- if fieldWidth < runewidth.StringWidth(text) {
- Print(screen, text, x, y, fieldWidth, AlignRight, i.fieldTextColor)
+ stringWidth := runewidth.StringWidth(text)
+ if fieldWidth >= stringWidth {
+ // We have enough space for the full text.
+ Print(screen, Escape(text), x, y, fieldWidth, AlignLeft, i.fieldTextColor)
+ i.offset = 0
+ iterateString(text, func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
+ if textPos >= i.cursorPos {
+ return true
+ }
+ cursorScreenPos += screenWidth
+ return false
+ })
} else {
- Print(screen, text, x, y, fieldWidth, AlignLeft, i.fieldTextColor)
+ // The text doesn't fit. Where is the cursor?
+ if i.cursorPos < 0 {
+ i.cursorPos = 0
+ } else if i.cursorPos > len(text) {
+ i.cursorPos = len(text)
+ }
+ // Shift the text so the cursor is inside the field.
+ var shiftLeft int
+ if i.offset > i.cursorPos {
+ i.offset = i.cursorPos
+ } else if subWidth := runewidth.StringWidth(text[i.offset:i.cursorPos]); subWidth > fieldWidth-1 {
+ shiftLeft = subWidth - fieldWidth + 1
+ }
+ currentOffset := i.offset
+ iterateString(text, func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
+ if textPos >= currentOffset {
+ if shiftLeft > 0 {
+ i.offset = textPos + textWidth
+ shiftLeft -= screenWidth
+ } else {
+ if textPos+textWidth > i.cursorPos {
+ return true
+ }
+ cursorScreenPos += screenWidth
+ }
+ }
+ return false
+ })
+ Print(screen, Escape(text[i.offset:]), x, y, fieldWidth, AlignLeft, i.fieldTextColor)
}
}
// Set cursor.
if i.focus.HasFocus() {
- i.setCursor(screen)
- }
-}
-
-// setCursor sets the cursor position.
-func (i *InputField) setCursor(screen tcell.Screen) {
- x := i.x
- y := i.y
- rightLimit := x + i.width
- if i.border {
- x++
- y++
- rightLimit -= 2
- }
- fieldWidth := runewidth.StringWidth(i.text)
- if i.fieldWidth > 0 && fieldWidth > i.fieldWidth-1 {
- fieldWidth = i.fieldWidth - 1
- }
- if i.labelWidth > 0 {
- x += i.labelWidth + fieldWidth
- } else {
- x += StringWidth(i.label) + fieldWidth
- }
- if x >= rightLimit {
- x = rightLimit - 1
+ screen.ShowCursor(x+cursorScreenPos, y)
}
- screen.ShowCursor(x, y)
}
// InputHandler returns the handler for this primitive.
@@ -305,27 +338,101 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
}
}()
+ // Movement functions.
+ home := func() { i.cursorPos = 0 }
+ end := func() { i.cursorPos = len(i.text) }
+ moveLeft := func() {
+ iterateStringReverse(i.text[:i.cursorPos], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
+ i.cursorPos -= textWidth
+ return true
+ })
+ }
+ moveRight := func() {
+ iterateString(i.text[i.cursorPos:], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
+ i.cursorPos += textWidth
+ return true
+ })
+ }
+ moveWordLeft := func() {
+ i.cursorPos = len(regexp.MustCompile(`\S+\s*$`).ReplaceAllString(i.text[:i.cursorPos], ""))
+ }
+ moveWordRight := func() {
+ i.cursorPos = len(i.text) - len(regexp.MustCompile(`^\s*\S+\s*`).ReplaceAllString(i.text[i.cursorPos:], ""))
+ }
+
+ // Add character function. Returns whether or not the rune character is
+ // accepted.
+ add := func(r rune) bool {
+ newText := i.text[:i.cursorPos] + string(r) + i.text[i.cursorPos:]
+ if i.accept != nil {
+ return i.accept(newText, r)
+ }
+ i.text = newText
+ i.cursorPos += len(string(r))
+ return true
+ }
+
// Process key event.
switch key := event.Key(); key {
case tcell.KeyRune: // Regular character.
- newText := i.text + string(event.Rune())
- if i.accept != nil {
- if !i.accept(newText, event.Rune()) {
+ if event.Modifiers()&tcell.ModAlt > 0 {
+ // We accept some Alt- key combinations.
+ switch event.Rune() {
+ case 'a': // Home.
+ home()
+ case 'e': // End.
+ end()
+ case 'b': // Move word left.
+ moveWordLeft()
+ case 'f': // Move word right.
+ moveWordRight()
+ }
+ } else {
+ // Other keys are simply accepted as regular characters.
+ if !add(event.Rune()) {
break
}
}
- i.text = newText
case tcell.KeyCtrlU: // Delete all.
i.text = ""
+ i.cursorPos = 0
+ case tcell.KeyCtrlK: // Delete until the end of the line.
+ i.text = i.text[:i.cursorPos]
case tcell.KeyCtrlW: // Delete last word.
- lastWord := regexp.MustCompile(`\s*\S+\s*$`)
- i.text = lastWord.ReplaceAllString(i.text, "")
- case tcell.KeyBackspace, tcell.KeyBackspace2: // Delete last character.
- if len(i.text) == 0 {
- break
+ lastWord := regexp.MustCompile(`\S+\s*$`)
+ newText := lastWord.ReplaceAllString(i.text[:i.cursorPos], "") + i.text[i.cursorPos:]
+ i.cursorPos -= len(i.text) - len(newText)
+ i.text = newText
+ case tcell.KeyBackspace, tcell.KeyBackspace2: // Delete character before the cursor.
+ iterateStringReverse(i.text[:i.cursorPos], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
+ i.text = i.text[:textPos] + i.text[textPos+textWidth:]
+ i.cursorPos -= textWidth
+ return true
+ })
+ if i.offset >= i.cursorPos {
+ i.offset = 0
+ }
+ case tcell.KeyDelete: // Delete character after the cursor.
+ iterateString(i.text[i.cursorPos:], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
+ i.text = i.text[:i.cursorPos] + i.text[i.cursorPos+textWidth:]
+ return true
+ })
+ case tcell.KeyLeft:
+ if event.Modifiers()&tcell.ModAlt > 0 {
+ moveWordLeft()
+ } else {
+ moveLeft()
+ }
+ case tcell.KeyRight:
+ if event.Modifiers()&tcell.ModAlt > 0 {
+ moveWordRight()
+ } else {
+ moveRight()
}
- runes := []rune(i.text)
- i.text = string(runes[:len(runes)-1])
+ case tcell.KeyHome, tcell.KeyCtrlA:
+ home()
+ case tcell.KeyEnd, tcell.KeyCtrlE:
+ end()
case tcell.KeyEnter, tcell.KeyTab, tcell.KeyBacktab, tcell.KeyEscape: // We're done.
if i.done != nil {
i.done(key)
diff --git a/vendor/maunium.net/go/tview/list.go b/vendor/maunium.net/go/tview/list.go
index bc5be85..e8dc5dd 100644
--- a/vendor/maunium.net/go/tview/list.go
+++ b/vendor/maunium.net/go/tview/list.go
@@ -85,6 +85,19 @@ func (l *List) GetCurrentItem() int {
return l.currentItem
}
+// RemoveItem removes the item with the given index (starting at 0) from the
+// list. Does nothing if the index is out of range.
+func (l *List) RemoveItem(index int) *List {
+ if index < 0 || index >= len(l.items) {
+ return l
+ }
+ l.items = append(l.items[:index], l.items[index+1:]...)
+ if l.currentItem >= len(l.items) {
+ l.currentItem = len(l.items) - 1
+ }
+ return l
+}
+
// SetMainTextColor sets the color of the items' main text.
func (l *List) SetMainTextColor(color tcell.Color) *List {
l.mainTextColor = color
@@ -127,7 +140,7 @@ func (l *List) ShowSecondaryText(show bool) *List {
//
// This function is also called when the first item is added or when
// SetCurrentItem() is called.
-func (l *List) SetChangedFunc(handler func(int, string, string, rune)) *List {
+func (l *List) SetChangedFunc(handler func(index int, mainText string, secondaryText string, shortcut rune)) *List {
l.changed = handler
return l
}
diff --git a/vendor/maunium.net/go/tview/modal.go b/vendor/maunium.net/go/tview/modal.go
index f53a265..f5e92f1 100644
--- a/vendor/maunium.net/go/tview/modal.go
+++ b/vendor/maunium.net/go/tview/modal.go
@@ -40,6 +40,11 @@ func NewModal() *Modal {
SetButtonBackgroundColor(Styles.PrimitiveBackgroundColor).
SetButtonTextColor(Styles.PrimaryTextColor)
m.form.SetBackgroundColor(Styles.ContrastBackgroundColor).SetBorderPadding(0, 0, 0, 0)
+ m.form.SetCancelFunc(func() {
+ if m.done != nil {
+ m.done(-1, "")
+ }
+ })
m.frame = NewFrame(m.form).SetBorders(0, 0, 1, 0, 0, 0)
m.frame.SetBorder(true).
SetBackgroundColor(Styles.ContrastBackgroundColor).
@@ -81,6 +86,16 @@ func (m *Modal) AddButtons(labels []string) *Modal {
m.done(i, l)
}
})
+ button := m.form.GetButton(m.form.GetButtonCount() - 1)
+ button.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
+ switch event.Key() {
+ case tcell.KeyDown, tcell.KeyRight:
+ return tcell.NewEventKey(tcell.KeyTab, 0, tcell.ModNone)
+ case tcell.KeyUp, tcell.KeyLeft:
+ return tcell.NewEventKey(tcell.KeyBacktab, 0, tcell.ModNone)
+ }
+ return event
+ })
}(index, label)
}
return m
diff --git a/vendor/maunium.net/go/tview/semigraphics.go b/vendor/maunium.net/go/tview/semigraphics.go
new file mode 100644
index 0000000..2455c87
--- /dev/null
+++ b/vendor/maunium.net/go/tview/semigraphics.go
@@ -0,0 +1,296 @@
+package tview
+
+import "maunium.net/go/tcell"
+
+// Semigraphics provides an easy way to access unicode characters for drawing.
+//
+// Named like the unicode characters, 'Semigraphics'-prefix used if unicode block
+// isn't prefixed itself.
+const (
+ // Block: General Punctation U+2000-U+206F (http://unicode.org/charts/PDF/U2000.pdf)
+ SemigraphicsHorizontalEllipsis rune = '\u2026' // …
+
+ // Block: Box Drawing U+2500-U+257F (http://unicode.org/charts/PDF/U2500.pdf)
+ BoxDrawingsLightHorizontal rune = '\u2500' // ─
+ BoxDrawingsHeavyHorizontal rune = '\u2501' // ━
+ BoxDrawingsLightVertical rune = '\u2502' // │
+ BoxDrawingsHeavyVertical rune = '\u2503' // ┃
+ BoxDrawingsLightTripleDashHorizontal rune = '\u2504' // ┄
+ BoxDrawingsHeavyTripleDashHorizontal rune = '\u2505' // ┅
+ BoxDrawingsLightTripleDashVertical rune = '\u2506' // ┆
+ BoxDrawingsHeavyTripleDashVertical rune = '\u2507' // ┇
+ BoxDrawingsLightQuadrupleDashHorizontal rune = '\u2508' // ┈
+ BoxDrawingsHeavyQuadrupleDashHorizontal rune = '\u2509' // ┉
+ BoxDrawingsLightQuadrupleDashVertical rune = '\u250a' // ┊
+ BoxDrawingsHeavyQuadrupleDashVertical rune = '\u250b' // ┋
+ BoxDrawingsLightDownAndRight rune = '\u250c' // ┌
+ BoxDrawingsDownLighAndRightHeavy rune = '\u250d' // ┍
+ BoxDrawingsDownHeavyAndRightLight rune = '\u250e' // ┎
+ BoxDrawingsHeavyDownAndRight rune = '\u250f' // ┏
+ BoxDrawingsLightDownAndLeft rune = '\u2510' // ┐
+ BoxDrawingsDownLighAndLeftHeavy rune = '\u2511' // ┑
+ BoxDrawingsDownHeavyAndLeftLight rune = '\u2512' // ┒
+ BoxDrawingsHeavyDownAndLeft rune = '\u2513' // ┓
+ BoxDrawingsLightUpAndRight rune = '\u2514' // └
+ BoxDrawingsUpLightAndRightHeavy rune = '\u2515' // ┕
+ BoxDrawingsUpHeavyAndRightLight rune = '\u2516' // ┖
+ BoxDrawingsHeavyUpAndRight rune = '\u2517' // ┗
+ BoxDrawingsLightUpAndLeft rune = '\u2518' // ┘
+ BoxDrawingsUpLightAndLeftHeavy rune = '\u2519' // ┙
+ BoxDrawingsUpHeavyAndLeftLight rune = '\u251a' // ┚
+ BoxDrawingsHeavyUpAndLeft rune = '\u251b' // ┛
+ BoxDrawingsLightVerticalAndRight rune = '\u251c' // ├
+ BoxDrawingsVerticalLightAndRightHeavy rune = '\u251d' // ┝
+ BoxDrawingsUpHeavyAndRightDownLight rune = '\u251e' // ┞
+ BoxDrawingsDownHeacyAndRightUpLight rune = '\u251f' // ┟
+ BoxDrawingsVerticalHeavyAndRightLight rune = '\u2520' // ┠
+ BoxDrawingsDownLightAnbdRightUpHeavy rune = '\u2521' // ┡
+ BoxDrawingsUpLightAndRightDownHeavy rune = '\u2522' // ┢
+ BoxDrawingsHeavyVerticalAndRight rune = '\u2523' // ┣
+ BoxDrawingsLightVerticalAndLeft rune = '\u2524' // ┤
+ BoxDrawingsVerticalLightAndLeftHeavy rune = '\u2525' // ┥
+ BoxDrawingsUpHeavyAndLeftDownLight rune = '\u2526' // ┦
+ BoxDrawingsDownHeavyAndLeftUpLight rune = '\u2527' // ┧
+ BoxDrawingsVerticalheavyAndLeftLight rune = '\u2528' // ┨
+ BoxDrawingsDownLightAndLeftUpHeavy rune = '\u2529' // ┨
+ BoxDrawingsUpLightAndLeftDownHeavy rune = '\u252a' // ┪
+ BoxDrawingsHeavyVerticalAndLeft rune = '\u252b' // ┫
+ BoxDrawingsLightDownAndHorizontal rune = '\u252c' // ┬
+ BoxDrawingsLeftHeavyAndRightDownLight rune = '\u252d' // ┭
+ BoxDrawingsRightHeavyAndLeftDownLight rune = '\u252e' // ┮
+ BoxDrawingsDownLightAndHorizontalHeavy rune = '\u252f' // ┯
+ BoxDrawingsDownHeavyAndHorizontalLight rune = '\u2530' // ┰
+ BoxDrawingsRightLightAndLeftDownHeavy rune = '\u2531' // ┱
+ BoxDrawingsLeftLightAndRightDownHeavy rune = '\u2532' // ┲
+ BoxDrawingsHeavyDownAndHorizontal rune = '\u2533' // ┳
+ BoxDrawingsLightUpAndHorizontal rune = '\u2534' // ┴
+ BoxDrawingsLeftHeavyAndRightUpLight rune = '\u2535' // ┵
+ BoxDrawingsRightHeavyAndLeftUpLight rune = '\u2536' // ┶
+ BoxDrawingsUpLightAndHorizontalHeavy rune = '\u2537' // ┷
+ BoxDrawingsUpHeavyAndHorizontalLight rune = '\u2538' // ┸
+ BoxDrawingsRightLightAndLeftUpHeavy rune = '\u2539' // ┹
+ BoxDrawingsLeftLightAndRightUpHeavy rune = '\u253a' // ┺
+ BoxDrawingsHeavyUpAndHorizontal rune = '\u253b' // ┻
+ BoxDrawingsLightVerticalAndHorizontal rune = '\u253c' // ┼
+ BoxDrawingsLeftHeavyAndRightVerticalLight rune = '\u253d' // ┽
+ BoxDrawingsRightHeavyAndLeftVerticalLight rune = '\u253e' // ┾
+ BoxDrawingsVerticalLightAndHorizontalHeavy rune = '\u253f' // ┿
+ BoxDrawingsUpHeavyAndDownHorizontalLight rune = '\u2540' // ╀
+ BoxDrawingsDownHeavyAndUpHorizontalLight rune = '\u2541' // ╁
+ BoxDrawingsVerticalHeavyAndHorizontalLight rune = '\u2542' // ╂
+ BoxDrawingsLeftUpHeavyAndRightDownLight rune = '\u2543' // ╃
+ BoxDrawingsRightUpHeavyAndLeftDownLight rune = '\u2544' // ╄
+ BoxDrawingsLeftDownHeavyAndRightUpLight rune = '\u2545' // ╅
+ BoxDrawingsRightDownHeavyAndLeftUpLight rune = '\u2546' // ╆
+ BoxDrawingsDownLightAndUpHorizontalHeavy rune = '\u2547' // ╇
+ BoxDrawingsUpLightAndDownHorizontalHeavy rune = '\u2548' // ╈
+ BoxDrawingsRightLightAndLeftVerticalHeavy rune = '\u2549' // ╉
+ BoxDrawingsLeftLightAndRightVerticalHeavy rune = '\u254a' // ╊
+ BoxDrawingsHeavyVerticalAndHorizontal rune = '\u254b' // ╋
+ BoxDrawingsLightDoubleDashHorizontal rune = '\u254c' // ╌
+ BoxDrawingsHeavyDoubleDashHorizontal rune = '\u254d' // ╍
+ BoxDrawingsLightDoubleDashVertical rune = '\u254e' // ╎
+ BoxDrawingsHeavyDoubleDashVertical rune = '\u254f' // ╏
+ BoxDrawingsDoubleHorizontal rune = '\u2550' // ═
+ BoxDrawingsDoubleVertical rune = '\u2551' // ║
+ BoxDrawingsDownSingleAndRightDouble rune = '\u2552' // ╒
+ BoxDrawingsDownDoubleAndRightSingle rune = '\u2553' // ╓
+ BoxDrawingsDoubleDownAndRight rune = '\u2554' // ╔
+ BoxDrawingsDownSingleAndLeftDouble rune = '\u2555' // ╕
+ BoxDrawingsDownDoubleAndLeftSingle rune = '\u2556' // ╖
+ BoxDrawingsDoubleDownAndLeft rune = '\u2557' // ╗
+ BoxDrawingsUpSingleAndRightDouble rune = '\u2558' // ╘
+ BoxDrawingsUpDoubleAndRightSingle rune = '\u2559' // ╙
+ BoxDrawingsDoubleUpAndRight rune = '\u255a' // ╚
+ BoxDrawingsUpSingleAndLeftDouble rune = '\u255b' // ╛
+ BoxDrawingsUpDobuleAndLeftSingle rune = '\u255c' // ╜
+ BoxDrawingsDoubleUpAndLeft rune = '\u255d' // ╝
+ BoxDrawingsVerticalSingleAndRightDouble rune = '\u255e' // ╞
+ BoxDrawingsVerticalDoubleAndRightSingle rune = '\u255f' // ╟
+ BoxDrawingsDoubleVerticalAndRight rune = '\u2560' // ╠
+ BoxDrawingsVerticalSingleAndLeftDouble rune = '\u2561' // ╡
+ BoxDrawingsVerticalDoubleAndLeftSingle rune = '\u2562' // ╢
+ BoxDrawingsDoubleVerticalAndLeft rune = '\u2563' // ╣
+ BoxDrawingsDownSingleAndHorizontalDouble rune = '\u2564' // ╤
+ BoxDrawingsDownDoubleAndHorizontalSingle rune = '\u2565' // ╥
+ BoxDrawingsDoubleDownAndHorizontal rune = '\u2566' // ╦
+ BoxDrawingsUpSingleAndHorizontalDouble rune = '\u2567' // ╧
+ BoxDrawingsUpDoubleAndHorizontalSingle rune = '\u2568' // ╨
+ BoxDrawingsDoubleUpAndHorizontal rune = '\u2569' // ╩
+ BoxDrawingsVerticalSingleAndHorizontalDouble rune = '\u256a' // ╪
+ BoxDrawingsVerticalDoubleAndHorizontalSingle rune = '\u256b' // ╫
+ BoxDrawingsDoubleVerticalAndHorizontal rune = '\u256c' // ╬
+ BoxDrawingsLightArcDownAndRight rune = '\u256d' // ╭
+ BoxDrawingsLightArcDownAndLeft rune = '\u256e' // ╮
+ BoxDrawingsLightArcUpAndLeft rune = '\u256f' // ╯
+ BoxDrawingsLightArcUpAndRight rune = '\u2570' // ╰
+ BoxDrawingsLightDiagonalUpperRightToLowerLeft rune = '\u2571' // ╱
+ BoxDrawingsLightDiagonalUpperLeftToLowerRight rune = '\u2572' // ╲
+ BoxDrawingsLightDiagonalCross rune = '\u2573' // ╳
+ BoxDrawingsLightLeft rune = '\u2574' // ╴
+ BoxDrawingsLightUp rune = '\u2575' // ╵
+ BoxDrawingsLightRight rune = '\u2576' // ╶
+ BoxDrawingsLightDown rune = '\u2577' // ╷
+ BoxDrawingsHeavyLeft rune = '\u2578' // ╸
+ BoxDrawingsHeavyUp rune = '\u2579' // ╹
+ BoxDrawingsHeavyRight rune = '\u257a' // ╺
+ BoxDrawingsHeavyDown rune = '\u257b' // ╻
+ BoxDrawingsLightLeftAndHeavyRight rune = '\u257c' // ╼
+ BoxDrawingsLightUpAndHeavyDown rune = '\u257d' // ╽
+ BoxDrawingsHeavyLeftAndLightRight rune = '\u257e' // ╾
+ BoxDrawingsHeavyUpAndLightDown rune = '\u257f' // ╿
+)
+
+// SemigraphicJoints is a map for joining semigraphic (or otherwise) runes.
+// So far only light lines are supported but if you want to change the border
+// styling you need to provide the joints, too.
+// The matching will be sorted ascending by rune value, so you don't need to
+// provide all rune combinations,
+// e.g. (─) + (│) = (┼) will also match (│) + (─) = (┼)
+var SemigraphicJoints = map[string]rune{
+ // (─) + (│) = (┼)
+ string([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightVertical}): BoxDrawingsLightVerticalAndHorizontal,
+ // (─) + (┌) = (┬)
+ string([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightDownAndRight}): BoxDrawingsLightDownAndHorizontal,
+ // (─) + (┐) = (┬)
+ string([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightDownAndLeft}): BoxDrawingsLightDownAndHorizontal,
+ // (─) + (└) = (┴)
+ string([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightUpAndRight}): BoxDrawingsLightUpAndHorizontal,
+ // (─) + (┘) = (┴)
+ string([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightUpAndLeft}): BoxDrawingsLightUpAndHorizontal,
+ // (─) + (├) = (┼)
+ string([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightVerticalAndRight}): BoxDrawingsLightVerticalAndHorizontal,
+ // (─) + (┤) = (┼)
+ string([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightVerticalAndLeft}): BoxDrawingsLightVerticalAndHorizontal,
+ // (─) + (┬) = (┬)
+ string([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightDownAndHorizontal}): BoxDrawingsLightDownAndHorizontal,
+ // (─) + (┴) = (┴)
+ string([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightUpAndHorizontal}): BoxDrawingsLightUpAndHorizontal,
+ // (─) + (┼) = (┼)
+ string([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+
+ // (│) + (┌) = (├)
+ string([]rune{BoxDrawingsLightVertical, BoxDrawingsLightDownAndRight}): BoxDrawingsLightVerticalAndRight,
+ // (│) + (┐) = (┤)
+ string([]rune{BoxDrawingsLightVertical, BoxDrawingsLightDownAndLeft}): BoxDrawingsLightVerticalAndLeft,
+ // (│) + (└) = (├)
+ string([]rune{BoxDrawingsLightVertical, BoxDrawingsLightUpAndRight}): BoxDrawingsLightVerticalAndRight,
+ // (│) + (┘) = (┤)
+ string([]rune{BoxDrawingsLightVertical, BoxDrawingsLightUpAndLeft}): BoxDrawingsLightVerticalAndLeft,
+ // (│) + (├) = (├)
+ string([]rune{BoxDrawingsLightVertical, BoxDrawingsLightVerticalAndRight}): BoxDrawingsLightVerticalAndRight,
+ // (│) + (┤) = (┤)
+ string([]rune{BoxDrawingsLightVertical, BoxDrawingsLightVerticalAndLeft}): BoxDrawingsLightVerticalAndLeft,
+ // (│) + (┬) = (┼)
+ string([]rune{BoxDrawingsLightVertical, BoxDrawingsLightDownAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+ // (│) + (┴) = (┼)
+ string([]rune{BoxDrawingsLightVertical, BoxDrawingsLightUpAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+ // (│) + (┼) = (┼)
+ string([]rune{BoxDrawingsLightVertical, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+
+ // (┌) + (┐) = (┬)
+ string([]rune{BoxDrawingsLightDownAndRight, BoxDrawingsLightDownAndLeft}): BoxDrawingsLightDownAndHorizontal,
+ // (┌) + (└) = (├)
+ string([]rune{BoxDrawingsLightDownAndRight, BoxDrawingsLightUpAndRight}): BoxDrawingsLightVerticalAndRight,
+ // (┌) + (┘) = (┼)
+ string([]rune{BoxDrawingsLightDownAndRight, BoxDrawingsLightUpAndLeft}): BoxDrawingsLightVerticalAndHorizontal,
+ // (┌) + (├) = (├)
+ string([]rune{BoxDrawingsLightDownAndRight, BoxDrawingsLightVerticalAndRight}): BoxDrawingsLightVerticalAndRight,
+ // (┌) + (┤) = (┼)
+ string([]rune{BoxDrawingsLightDownAndRight, BoxDrawingsLightVerticalAndLeft}): BoxDrawingsLightVerticalAndHorizontal,
+ // (┌) + (┬) = (┬)
+ string([]rune{BoxDrawingsLightDownAndRight, BoxDrawingsLightDownAndHorizontal}): BoxDrawingsLightDownAndHorizontal,
+ // (┌) + (┴) = (┼)
+ string([]rune{BoxDrawingsLightDownAndRight, BoxDrawingsLightUpAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+ // (┌) + (┴) = (┼)
+ string([]rune{BoxDrawingsLightDownAndRight, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+
+ // (┐) + (└) = (┼)
+ string([]rune{BoxDrawingsLightDownAndLeft, BoxDrawingsLightUpAndRight}): BoxDrawingsLightVerticalAndHorizontal,
+ // (┐) + (┘) = (┤)
+ string([]rune{BoxDrawingsLightDownAndLeft, BoxDrawingsLightUpAndLeft}): BoxDrawingsLightVerticalAndLeft,
+ // (┐) + (├) = (┼)
+ string([]rune{BoxDrawingsLightDownAndLeft, BoxDrawingsLightVerticalAndRight}): BoxDrawingsLightVerticalAndHorizontal,
+ // (┐) + (┤) = (┤)
+ string([]rune{BoxDrawingsLightDownAndLeft, BoxDrawingsLightVerticalAndLeft}): BoxDrawingsLightVerticalAndLeft,
+ // (┐) + (┬) = (┬)
+ string([]rune{BoxDrawingsLightDownAndLeft, BoxDrawingsLightDownAndHorizontal}): BoxDrawingsLightDownAndHorizontal,
+ // (┐) + (┴) = (┼)
+ string([]rune{BoxDrawingsLightDownAndLeft, BoxDrawingsLightUpAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+ // (┐) + (┼) = (┼)
+ string([]rune{BoxDrawingsLightDownAndLeft, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+
+ // (└) + (┘) = (┴)
+ string([]rune{BoxDrawingsLightUpAndRight, BoxDrawingsLightUpAndLeft}): BoxDrawingsLightUpAndHorizontal,
+ // (└) + (├) = (├)
+ string([]rune{BoxDrawingsLightUpAndRight, BoxDrawingsLightVerticalAndRight}): BoxDrawingsLightVerticalAndRight,
+ // (└) + (┤) = (┼)
+ string([]rune{BoxDrawingsLightUpAndRight, BoxDrawingsLightVerticalAndLeft}): BoxDrawingsLightVerticalAndHorizontal,
+ // (└) + (┬) = (┼)
+ string([]rune{BoxDrawingsLightUpAndRight, BoxDrawingsLightDownAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+ // (└) + (┴) = (┴)
+ string([]rune{BoxDrawingsLightUpAndRight, BoxDrawingsLightUpAndHorizontal}): BoxDrawingsLightUpAndHorizontal,
+ // (└) + (┼) = (┼)
+ string([]rune{BoxDrawingsLightUpAndRight, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+
+ // (┘) + (├) = (┼)
+ string([]rune{BoxDrawingsLightUpAndLeft, BoxDrawingsLightVerticalAndRight}): BoxDrawingsLightVerticalAndHorizontal,
+ // (┘) + (┤) = (┤)
+ string([]rune{BoxDrawingsLightUpAndLeft, BoxDrawingsLightVerticalAndLeft}): BoxDrawingsLightVerticalAndLeft,
+ // (┘) + (┬) = (┼)
+ string([]rune{BoxDrawingsLightUpAndLeft, BoxDrawingsLightDownAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+ // (┘) + (┴) = (┴)
+ string([]rune{BoxDrawingsLightUpAndLeft, BoxDrawingsLightUpAndHorizontal}): BoxDrawingsLightUpAndHorizontal,
+ // (┘) + (┼) = (┼)
+ string([]rune{BoxDrawingsLightUpAndLeft, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+
+ // (├) + (┤) = (┼)
+ string([]rune{BoxDrawingsLightVerticalAndRight, BoxDrawingsLightVerticalAndLeft}): BoxDrawingsLightVerticalAndHorizontal,
+ // (├) + (┬) = (┼)
+ string([]rune{BoxDrawingsLightVerticalAndRight, BoxDrawingsLightDownAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+ // (├) + (┴) = (┼)
+ string([]rune{BoxDrawingsLightVerticalAndRight, BoxDrawingsLightUpAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+ // (├) + (┼) = (┼)
+ string([]rune{BoxDrawingsLightVerticalAndRight, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+
+ // (┤) + (┬) = (┼)
+ string([]rune{BoxDrawingsLightVerticalAndLeft, BoxDrawingsLightDownAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+ // (┤) + (┴) = (┼)
+ string([]rune{BoxDrawingsLightVerticalAndLeft, BoxDrawingsLightUpAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+ // (┤) + (┼) = (┼)
+ string([]rune{BoxDrawingsLightVerticalAndLeft, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+
+ // (┬) + (┴) = (┼)
+ string([]rune{BoxDrawingsLightDownAndHorizontal, BoxDrawingsLightUpAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+ // (┬) + (┼) = (┼)
+ string([]rune{BoxDrawingsLightDownAndHorizontal, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+
+ // (┴) + (┼) = (┼)
+ string([]rune{BoxDrawingsLightUpAndHorizontal, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+}
+
+// PrintJoinedSemigraphics prints a semigraphics rune into the screen at the given
+// position with the given color, joining it with any existing semigraphics
+// rune. Background colors are preserved. At this point, only regular single
+// line borders are supported.
+func PrintJoinedSemigraphics(screen tcell.Screen, x, y int, ch rune, color tcell.Color) {
+ previous, _, style, _ := screen.GetContent(x, y)
+ style = style.Foreground(color)
+
+ // What's the resulting rune?
+ var result rune
+ if ch == previous {
+ result = ch
+ } else {
+ if ch < previous {
+ previous, ch = ch, previous
+ }
+ result = SemigraphicJoints[string([]rune{previous, ch})]
+ }
+ if result == 0 {
+ result = ch
+ }
+
+ // We only print something if we have something.
+ screen.SetContent(x, y, result, nil, style)
+}
diff --git a/vendor/maunium.net/go/tview/table.go b/vendor/maunium.net/go/tview/table.go
index 2491ec7..6c636e7 100644
--- a/vendor/maunium.net/go/tview/table.go
+++ b/vendor/maunium.net/go/tview/table.go
@@ -231,6 +231,10 @@ type Table struct {
// The number of visible rows the last time the table was drawn.
visibleRows int
+ // The style of the selected rows. If this value is 0, selected rows are
+ // simply inverted.
+ selectedStyle tcell.Style
+
// An optional function which gets called when the user presses Enter on a
// selected cell. If entire rows selected, the column value is undefined.
// Likewise for entire columns.
@@ -276,9 +280,21 @@ func (t *Table) SetBordersColor(color tcell.Color) *Table {
return t
}
+// SetSelectedStyle sets a specific style for selected cells. If no such style
+// is set, per default, selected cells are inverted (i.e. their foreground and
+// background colors are swapped).
+//
+// To reset a previous setting to its default, make the following call:
+//
+// table.SetSelectedStyle(tcell.ColorDefault, tcell.ColorDefault, 0)
+func (t *Table) SetSelectedStyle(foregroundColor, backgroundColor tcell.Color, attributes tcell.AttrMask) *Table {
+ t.selectedStyle = tcell.StyleDefault.Foreground(foregroundColor).Background(backgroundColor) | tcell.Style(attributes)
+ return t
+}
+
// SetSeparator sets the character used to fill the space between two
// neighboring cells. This is a space character ' ' per default but you may
-// want to set it to GraphicsVertBar (or any other rune) if the column
+// want to set it to Borders.Vertical (or any other rune) if the column
// separation should be more visible. If cell borders are activated, this is
// ignored.
//
@@ -373,7 +389,7 @@ func (t *Table) SetDoneFunc(handler func(key tcell.Key)) *Table {
}
// SetCell sets the content of a cell the specified position. It is ok to
-// directly instantiate a TableCell object. If the cell has contain, at least
+// directly instantiate a TableCell object. If the cell has content, at least
// the Text and Color fields should be set.
//
// Note that setting cells in previously unknown rows and columns will
@@ -406,7 +422,7 @@ func (t *Table) SetCellSimple(row, column int, text string) *Table {
}
// GetCell returns the contents of the cell at the specified position. A valid
-// TableCell object is always returns but it will be uninitialized if the cell
+// TableCell object is always returned but it will be uninitialized if the cell
// was not previously set.
func (t *Table) GetCell(row, column int) *TableCell {
if row >= len(t.cells) || column >= len(t.cells[row]) {
@@ -415,6 +431,31 @@ func (t *Table) GetCell(row, column int) *TableCell {
return t.cells[row][column]
}
+// RemoveRow removes the row at the given position from the table. If there is
+// no such row, this has no effect.
+func (t *Table) RemoveRow(row int) *Table {
+ if row < 0 || row >= len(t.cells) {
+ return t
+ }
+
+ t.cells = append(t.cells[:row], t.cells[row+1:]...)
+
+ return t
+}
+
+// RemoveColumn removes the column at the given position from the table. If
+// there is no such column, this has no effect.
+func (t *Table) RemoveColumn(column int) *Table {
+ for row := range t.cells {
+ if column < 0 || column >= len(t.cells[row]) {
+ continue
+ }
+ t.cells[row] = append(t.cells[row][:column], t.cells[row][column+1:]...)
+ }
+
+ return t
+}
+
// GetRowCount returns the number of rows in the table.
func (t *Table) GetRowCount() int {
return len(t.cells)
@@ -644,7 +685,6 @@ ColumnLoop:
}
expWidth := toDistribute * expansion / expansionTotal
widths[index] += expWidth
- tableWidth += expWidth
toDistribute -= expWidth
expansionTotal -= expansion
}
@@ -668,24 +708,24 @@ ColumnLoop:
// Draw borders.
rowY *= 2
for pos := 0; pos < columnWidth && columnX+1+pos < width; pos++ {
- drawBorder(columnX+pos+1, rowY, GraphicsHoriBar)
+ drawBorder(columnX+pos+1, rowY, Borders.Horizontal)
}
- ch := GraphicsCross
+ ch := Borders.Cross
if columnIndex == 0 {
if rowY == 0 {
- ch = GraphicsTopLeftCorner
+ ch = Borders.TopLeft
} else {
- ch = GraphicsLeftT
+ ch = Borders.LeftT
}
} else if rowY == 0 {
- ch = GraphicsTopT
+ ch = Borders.TopT
}
drawBorder(columnX, rowY, ch)
rowY++
if rowY >= height {
break // No space for the text anymore.
}
- drawBorder(columnX, rowY, GraphicsVertBar)
+ drawBorder(columnX, rowY, Borders.Vertical)
} else if columnIndex > 0 {
// Draw separator.
drawBorder(columnX, rowY, t.separator)
@@ -706,18 +746,18 @@ ColumnLoop:
_, printed := printWithStyle(screen, cell.Text, x+columnX+1, y+rowY, finalWidth, cell.Align, tcell.StyleDefault.Foreground(cell.Color)|tcell.Style(cell.Attributes))
if StringWidth(cell.Text)-printed > 0 && printed > 0 {
_, _, style, _ := screen.GetContent(x+columnX+1+finalWidth-1, y+rowY)
- printWithStyle(screen, string(GraphicsEllipsis), x+columnX+1+finalWidth-1, y+rowY, 1, AlignLeft, style)
+ printWithStyle(screen, string(SemigraphicsHorizontalEllipsis), x+columnX+1+finalWidth-1, y+rowY, 1, AlignLeft, style)
}
}
// Draw bottom border.
if rowY := 2 * len(rows); t.borders && rowY < height {
for pos := 0; pos < columnWidth && columnX+1+pos < width; pos++ {
- drawBorder(columnX+pos+1, rowY, GraphicsHoriBar)
+ drawBorder(columnX+pos+1, rowY, Borders.Horizontal)
}
- ch := GraphicsBottomT
+ ch := Borders.BottomT
if columnIndex == 0 {
- ch = GraphicsBottomLeftCorner
+ ch = Borders.BottomLeft
}
drawBorder(columnX, rowY, ch)
}
@@ -730,26 +770,31 @@ ColumnLoop:
for rowY := range rows {
rowY *= 2
if rowY+1 < height {
- drawBorder(columnX, rowY+1, GraphicsVertBar)
+ drawBorder(columnX, rowY+1, Borders.Vertical)
}
- ch := GraphicsRightT
+ ch := Borders.RightT
if rowY == 0 {
- ch = GraphicsTopRightCorner
+ ch = Borders.TopRight
}
drawBorder(columnX, rowY, ch)
}
if rowY := 2 * len(rows); rowY < height {
- drawBorder(columnX, rowY, GraphicsBottomRightCorner)
+ drawBorder(columnX, rowY, Borders.BottomRight)
}
}
// Helper function which colors the background of a box.
- colorBackground := func(fromX, fromY, w, h int, backgroundColor, textColor tcell.Color, selected bool) {
+ // backgroundColor == tcell.ColorDefault => Don't color the background.
+ // textColor == tcell.ColorDefault => Don't change the text color.
+ // attr == 0 => Don't change attributes.
+ // invert == true => Ignore attr, set text to backgroundColor or t.backgroundColor;
+ // set background to textColor.
+ colorBackground := func(fromX, fromY, w, h int, backgroundColor, textColor tcell.Color, attr tcell.AttrMask, invert bool) {
for by := 0; by < h && fromY+by < y+height; by++ {
for bx := 0; bx < w && fromX+bx < x+width; bx++ {
m, c, style, _ := screen.GetContent(fromX+bx, fromY+by)
- if selected {
- fg, _, _ := style.Decompose()
+ fg, bg, a := style.Decompose()
+ if invert {
if fg == textColor || fg == t.bordersColor {
fg = backgroundColor
}
@@ -758,10 +803,16 @@ ColumnLoop:
}
style = style.Background(textColor).Foreground(fg)
} else {
- if backgroundColor == tcell.ColorDefault {
- continue
+ if backgroundColor != tcell.ColorDefault {
+ bg = backgroundColor
}
- style = style.Background(backgroundColor)
+ if textColor != tcell.ColorDefault {
+ fg = textColor
+ }
+ if attr != 0 {
+ a = attr
+ }
+ style = style.Background(bg).Foreground(fg) | tcell.Style(a)
}
screen.SetContent(fromX+bx, fromY+by, m, c, style)
}
@@ -770,11 +821,12 @@ ColumnLoop:
// Color the cell backgrounds. To avoid undesirable artefacts, we combine
// the drawing of a cell by background color, selected cells last.
- cellsByBackgroundColor := make(map[tcell.Color][]*struct {
+ type cellInfo struct {
x, y, w, h int
text tcell.Color
selected bool
- })
+ }
+ cellsByBackgroundColor := make(map[tcell.Color][]*cellInfo)
var backgroundColors []tcell.Color
for rowY, row := range rows {
columnX := 0
@@ -794,11 +846,7 @@ ColumnLoop:
columnSelected := t.columnsSelectable && !t.rowsSelectable && column == t.selectedColumn
cellSelected := !cell.NotSelectable && (columnSelected || rowSelected || t.rowsSelectable && t.columnsSelectable && column == t.selectedColumn && row == t.selectedRow)
entries, ok := cellsByBackgroundColor[cell.BackgroundColor]
- cellsByBackgroundColor[cell.BackgroundColor] = append(entries, &struct {
- x, y, w, h int
- text tcell.Color
- selected bool
- }{
+ cellsByBackgroundColor[cell.BackgroundColor] = append(entries, &cellInfo{
x: bx,
y: by,
w: bw,
@@ -822,13 +870,18 @@ ColumnLoop:
_, _, lj := c.Hcl()
return li < lj
})
+ selFg, selBg, selAttr := t.selectedStyle.Decompose()
for _, bgColor := range backgroundColors {
entries := cellsByBackgroundColor[bgColor]
for _, cell := range entries {
if cell.selected {
- defer colorBackground(cell.x, cell.y, cell.w, cell.h, bgColor, cell.text, true)
+ if t.selectedStyle != 0 {
+ defer colorBackground(cell.x, cell.y, cell.w, cell.h, selBg, selFg, selAttr, false)
+ } else {
+ defer colorBackground(cell.x, cell.y, cell.w, cell.h, bgColor, cell.text, 0, true)
+ }
} else {
- colorBackground(cell.x, cell.y, cell.w, cell.h, bgColor, cell.text, false)
+ colorBackground(cell.x, cell.y, cell.w, cell.h, bgColor, tcell.ColorDefault, 0, false)
}
}
}
diff --git a/vendor/maunium.net/go/tview/textview.go b/vendor/maunium.net/go/tview/textview.go
index 44aeb1e..63d9796 100644
--- a/vendor/maunium.net/go/tview/textview.go
+++ b/vendor/maunium.net/go/tview/textview.go
@@ -31,7 +31,7 @@ type textViewIndex struct {
// TextView is a box which displays text. It implements the io.Writer interface
// so you can stream text to it. This does not trigger a redraw automatically
// but if a handler is installed via SetChangedFunc(), you can cause it to be
-// redrawn.
+// redrawn. (See SetChangedFunc() for more details.)
//
// Navigation
//
@@ -103,6 +103,10 @@ type TextView struct {
// during re-indexing. Set to -1 if there is no current highlight.
fromHighlight, toHighlight int
+ // The screen space column of the highlight in its first line. Set to -1 if
+ // there is no current highlight.
+ posHighlight int
+
// A set of region IDs that are currently highlighted.
highlights map[string]struct{}
@@ -170,6 +174,7 @@ func NewTextView() *TextView {
align: AlignLeft,
wrap: true,
textColor: Styles.PrimaryTextColor,
+ regions: false,
dynamicColors: false,
}
}
@@ -255,8 +260,20 @@ func (t *TextView) SetRegions(regions bool) *TextView {
}
// SetChangedFunc sets a handler function which is called when the text of the
-// text view has changed. This is typically used to cause the application to
-// redraw the screen.
+// text view has changed. This is useful when text is written to this io.Writer
+// in a separate goroutine. This does not automatically cause the screen to be
+// refreshed so you may want to use the "changed" handler to redraw the screen.
+//
+// Note that to avoid race conditions or deadlocks, there are a few rules you
+// should follow:
+//
+// - You can call Application.Draw() from this handler.
+// - You can call TextView.HasFocus() from this handler.
+// - During the execution of this handler, access to any other variables from
+// this primitive or any other primitive should be queued using
+// Application.QueueUpdate().
+//
+// See package description for details on dealing with concurrency.
func (t *TextView) SetChangedFunc(handler func()) *TextView {
t.changed = handler
return t
@@ -270,6 +287,16 @@ func (t *TextView) SetDoneFunc(handler func(key tcell.Key)) *TextView {
return t
}
+// ScrollTo scrolls to the specified row and column (both starting with 0).
+func (t *TextView) ScrollTo(row, column int) *TextView {
+ if !t.scrollable {
+ return t
+ }
+ t.lineOffset = row
+ t.columnOffset = column
+ return t
+}
+
// ScrollToBeginning scrolls to the top left corner of the text if the text view
// is scrollable.
func (t *TextView) ScrollToBeginning() *TextView {
@@ -294,6 +321,12 @@ func (t *TextView) ScrollToEnd() *TextView {
return t
}
+// GetScrollOffset returns the number of rows and columns that are skipped at
+// the top left corner when the text view has been scrolled.
+func (t *TextView) GetScrollOffset() (row, column int) {
+ return t.lineOffset, t.columnOffset
+}
+
// Clear removes all text from the buffer.
func (t *TextView) Clear() *TextView {
t.buffer = nil
@@ -420,13 +453,33 @@ func (t *TextView) GetRegionText(regionID string) string {
return escapePattern.ReplaceAllString(buffer.String(), `[$1$2]`)
}
+// Focus is called when this primitive receives focus.
+func (t *TextView) Focus(delegate func(p Primitive)) {
+ // Implemented here with locking because this is used by layout primitives.
+ t.Lock()
+ defer t.Unlock()
+ t.hasFocus = true
+}
+
+// HasFocus returns whether or not this primitive has focus.
+func (t *TextView) HasFocus() bool {
+ // Implemented here with locking because this may be used in the "changed"
+ // callback.
+ t.Lock()
+ defer t.Unlock()
+ return t.hasFocus
+}
+
// Write lets us implement the io.Writer interface. Tab characters will be
// replaced with TabSize space characters. A "\n" or "\r\n" will be interpreted
// as a new line.
func (t *TextView) Write(p []byte) (n int, err error) {
// Notify at the end.
- if t.changed != nil {
- defer t.changed()
+ t.Lock()
+ changed := t.changed
+ t.Unlock()
+ if changed != nil {
+ defer changed() // Deadlocks may occur if we lock here.
}
t.Lock()
@@ -492,7 +545,7 @@ func (t *TextView) reindexBuffer(width int) {
return // Nothing has changed. We can still use the current index.
}
t.index = nil
- t.fromHighlight, t.toHighlight = -1, -1
+ t.fromHighlight, t.toHighlight, t.posHighlight = -1, -1, -1
// If there's no space, there's no index.
if width < 1 {
@@ -511,8 +564,9 @@ func (t *TextView) reindexBuffer(width int) {
colorTags [][]string
escapeIndices [][]int
)
+ strippedStr := str
if t.dynamicColors {
- colorTagIndices, colorTags, escapeIndices, str, _ = decomposeString(str)
+ colorTagIndices, colorTags, escapeIndices, strippedStr, _ = decomposeString(str)
}
// Find all regions in this line. Then remove them.
@@ -523,14 +577,12 @@ func (t *TextView) reindexBuffer(width int) {
if t.regions {
regionIndices = regionPattern.FindAllStringIndex(str, -1)
regions = regionPattern.FindAllStringSubmatch(str, -1)
- str = regionPattern.ReplaceAllString(str, "")
- if !t.dynamicColors {
- // We haven't detected escape tags yet. Do it now.
- escapeIndices = escapePattern.FindAllStringIndex(str, -1)
- str = escapePattern.ReplaceAllString(str, "[$1$2]")
- }
+ strippedStr = regionPattern.ReplaceAllString(strippedStr, "")
}
+ // We don't need the original string anymore for now.
+ str = strippedStr
+
// Split the line if required.
var splitLines []string
if t.wrap && len(str) > 0 {
@@ -574,15 +626,53 @@ func (t *TextView) reindexBuffer(width int) {
// Shift original position with tags.
lineLength := len(splitLine)
+ remainingLength := lineLength
+ tagEnd := originalPos
+ totalTagLength := 0
for {
- if colorPos < len(colorTagIndices) && colorTagIndices[colorPos][0] <= originalPos+lineLength {
+ // Which tag comes next?
+ nextTag := make([][3]int, 0, 3)
+ if colorPos < len(colorTagIndices) {
+ nextTag = append(nextTag, [3]int{colorTagIndices[colorPos][0], colorTagIndices[colorPos][1], 0}) // 0 = color tag.
+ }
+ if regionPos < len(regionIndices) {
+ nextTag = append(nextTag, [3]int{regionIndices[regionPos][0], regionIndices[regionPos][1], 1}) // 1 = region tag.
+ }
+ if escapePos < len(escapeIndices) {
+ nextTag = append(nextTag, [3]int{escapeIndices[escapePos][0], escapeIndices[escapePos][1], 2}) // 2 = escape tag.
+ }
+ minPos := -1
+ tagIndex := -1
+ for index, pair := range nextTag {
+ if minPos < 0 || pair[0] < minPos {
+ minPos = pair[0]
+ tagIndex = index
+ }
+ }
+
+ // Is the next tag in range?
+ if tagIndex < 0 || minPos >= tagEnd+remainingLength {
+ break // No. We're done with this line.
+ }
+
+ // Advance.
+ strippedTagStart := nextTag[tagIndex][0] - originalPos - totalTagLength
+ tagEnd = nextTag[tagIndex][1]
+ tagLength := tagEnd - nextTag[tagIndex][0]
+ if nextTag[tagIndex][2] == 2 {
+ tagLength = 1
+ }
+ totalTagLength += tagLength
+ remainingLength = lineLength - (tagEnd - originalPos - totalTagLength)
+
+ // Process the tag.
+ switch nextTag[tagIndex][2] {
+ case 0:
// Process color tags.
- originalPos += colorTagIndices[colorPos][1] - colorTagIndices[colorPos][0]
foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colorTags[colorPos])
colorPos++
- } else if regionPos < len(regionIndices) && regionIndices[regionPos][0] <= originalPos+lineLength {
+ case 1:
// Process region tags.
- originalPos += regionIndices[regionPos][1] - regionIndices[regionPos][0]
regionID = regions[regionPos][1]
_, highlighted = t.highlights[regionID]
@@ -591,23 +681,21 @@ func (t *TextView) reindexBuffer(width int) {
line := len(t.index)
if t.fromHighlight < 0 {
t.fromHighlight, t.toHighlight = line, line
+ t.posHighlight = runewidth.StringWidth(splitLine[:strippedTagStart])
} else if line > t.toHighlight {
t.toHighlight = line
}
}
regionPos++
- } else if escapePos < len(escapeIndices) && escapeIndices[escapePos][0] <= originalPos+lineLength {
+ case 2:
// Process escape tags.
- originalPos++
escapePos++
- } else {
- break
}
}
// Advance to next line.
- originalPos += lineLength
+ originalPos += lineLength + totalTagLength
// Append this line.
line.NextPos = originalPos
@@ -649,7 +737,7 @@ func (t *TextView) Draw(screen tcell.Screen) {
t.pageSize = height
// If the width has changed, we need to reindex.
- if width != t.lastWidth {
+ if width != t.lastWidth && t.wrap {
t.index = nil
}
t.lastWidth = width
@@ -672,6 +760,16 @@ func (t *TextView) Draw(screen tcell.Screen) {
// No, let's move to the start of the highlights.
t.lineOffset = t.fromHighlight
}
+
+ // If the highlight is too far to the right, move it to the middle.
+ if t.posHighlight-t.columnOffset > 3*width/4 {
+ t.columnOffset = t.posHighlight - width/2
+ }
+
+ // If the highlight is off-screen on the left, move it on-screen.
+ if t.posHighlight-t.columnOffset < 0 {
+ t.columnOffset = t.posHighlight - width/4
+ }
}
t.scrollToHighlights = false
@@ -737,8 +835,9 @@ func (t *TextView) Draw(screen tcell.Screen) {
colorTags [][]string
escapeIndices [][]int
)
+ strippedText := text
if t.dynamicColors {
- colorTagIndices, colorTags, escapeIndices, _, _ = decomposeString(text)
+ colorTagIndices, colorTags, escapeIndices, strippedText, _ = decomposeString(text)
}
// Get regions.
@@ -749,8 +848,10 @@ func (t *TextView) Draw(screen tcell.Screen) {
if t.regions {
regionIndices = regionPattern.FindAllStringIndex(text, -1)
regions = regionPattern.FindAllStringSubmatch(text, -1)
+ strippedText = regionPattern.ReplaceAllString(strippedText, "")
if !t.dynamicColors {
escapeIndices = escapePattern.FindAllStringIndex(text, -1)
+ strippedText = string(escapePattern.ReplaceAllString(strippedText, "[$1$2]"))
}
}
@@ -769,11 +870,29 @@ func (t *TextView) Draw(screen tcell.Screen) {
}
// Print the line.
- var currentTag, currentRegion, currentEscapeTag, skipped, runeSeqWidth int
- runeSequence := make([]rune, 0, 10)
- flush := func() {
- if len(runeSequence) == 0 {
- return
+ var colorPos, regionPos, escapePos, tagOffset, skipped int
+ iterateString(strippedText, func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
+ // Process tags.
+ for {
+ if colorPos < len(colorTags) && textPos+tagOffset >= colorTagIndices[colorPos][0] && textPos+tagOffset < colorTagIndices[colorPos][1] {
+ // Get the color.
+ foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colorTags[colorPos])
+ tagOffset += colorTagIndices[colorPos][1] - colorTagIndices[colorPos][0]
+ colorPos++
+ } else if regionPos < len(regionIndices) && textPos+tagOffset >= regionIndices[regionPos][0] && textPos+tagOffset < regionIndices[regionPos][1] {
+ // Get the region.
+ regionID = regions[regionPos][1]
+ tagOffset += regionIndices[regionPos][1] - regionIndices[regionPos][0]
+ regionPos++
+ } else {
+ break
+ }
+ }
+
+ // Skip the second-to-last character of an escape tag.
+ if escapePos < len(escapeIndices) && textPos+tagOffset == escapeIndices[escapePos][1]-2 {
+ tagOffset++
+ escapePos++
}
// Mix the existing style with the new style.
@@ -803,87 +922,30 @@ func (t *TextView) Draw(screen tcell.Screen) {
style = style.Background(fg).Foreground(bg)
}
- // Draw the character.
- var comb []rune
- if len(runeSequence) > 1 {
- // Allocate space for the combining characters only when necessary.
- comb = make([]rune, len(runeSequence)-1)
- copy(comb, runeSequence[1:])
- }
- for offset := 0; offset < runeSeqWidth; offset++ {
- screen.SetContent(x+posX+offset, y+line-t.lineOffset, runeSequence[0], comb, style)
- }
-
- // Advance.
- posX += runeSeqWidth
- runeSequence = runeSequence[:0]
- runeSeqWidth = 0
- }
- for pos, ch := range text {
- // Get the color.
- if currentTag < len(colorTags) && pos >= colorTagIndices[currentTag][0] && pos < colorTagIndices[currentTag][1] {
- flush()
- if pos == colorTagIndices[currentTag][1]-1 {
- foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colorTags[currentTag])
- currentTag++
- }
- continue
- }
-
- // Get the region.
- if currentRegion < len(regionIndices) && pos >= regionIndices[currentRegion][0] && pos < regionIndices[currentRegion][1] {
- flush()
- if pos == regionIndices[currentRegion][1]-1 {
- regionID = regions[currentRegion][1]
- currentRegion++
- }
- continue
- }
-
- // Skip the second-to-last character of an escape tag.
- if currentEscapeTag < len(escapeIndices) && pos >= escapeIndices[currentEscapeTag][0] && pos < escapeIndices[currentEscapeTag][1] {
- flush()
- if pos == escapeIndices[currentEscapeTag][1]-1 {
- currentEscapeTag++
- } else if pos == escapeIndices[currentEscapeTag][1]-2 {
- continue
- }
- }
-
- // Determine the width of this rune.
- chWidth := runewidth.RuneWidth(ch)
- if chWidth == 0 {
- // If this is not a modifier, we treat it as a space character.
- if len(runeSequence) == 0 {
- ch = ' '
- chWidth = 1
- } else {
- runeSequence = append(runeSequence, ch)
- continue
- }
- }
-
// Skip to the right.
if !t.wrap && skipped < skip {
- skipped += chWidth
- continue
+ skipped += screenWidth
+ return false
}
// Stop at the right border.
- if posX+runeSeqWidth+chWidth > width {
- break
+ if posX+screenWidth > width {
+ return true
}
- // Flush the rune sequence.
- flush()
+ // Draw the character.
+ for offset := screenWidth - 1; offset >= 0; offset-- {
+ if offset == 0 {
+ screen.SetContent(x+posX+offset, y+line-t.lineOffset, main, comb, style)
+ } else {
+ screen.SetContent(x+posX+offset, y+line-t.lineOffset, ' ', nil, style)
+ }
+ }
- // Queue this rune.
- runeSequence = append(runeSequence, ch)
- runeSeqWidth += chWidth
- }
- if posX+runeSeqWidth <= width {
- flush()
- }
+ // Advance.
+ posX += screenWidth
+ return false
+ })
}
// If this view is not scrollable, we'll purge the buffer of lines that have
diff --git a/vendor/maunium.net/go/tview/treeview.go b/vendor/maunium.net/go/tview/treeview.go
new file mode 100644
index 0000000..1b0af21
--- /dev/null
+++ b/vendor/maunium.net/go/tview/treeview.go
@@ -0,0 +1,684 @@
+package tview
+
+import (
+ "maunium.net/go/tcell"
+)
+
+// Tree navigation events.
+const (
+ treeNone int = iota
+ treeHome
+ treeEnd
+ treeUp
+ treeDown
+ treePageUp
+ treePageDown
+)
+
+// TreeNode represents one node in a tree view.
+type TreeNode struct {
+ // The reference object.
+ reference interface{}
+
+ // This node's child nodes.
+ children []*TreeNode
+
+ // The item's text.
+ text string
+
+ // The text color.
+ color tcell.Color
+
+ // Whether or not this node can be selected.
+ selectable bool
+
+ // Whether or not this node's children should be displayed.
+ expanded bool
+
+ // The additional horizontal indent of this node's text.
+ indent int
+
+ // An optional function which is called when the user selects this node.
+ selected func()
+
+ // Temporary member variables.
+ parent *TreeNode // The parent node (nil for the root).
+ level int // The hierarchy level (0 for the root, 1 for its children, and so on).
+ graphicsX int // The x-coordinate of the left-most graphics rune.
+ textX int // The x-coordinate of the first rune of the text.
+}
+
+// NewTreeNode returns a new tree node.
+func NewTreeNode(text string) *TreeNode {
+ return &TreeNode{
+ text: text,
+ color: Styles.PrimaryTextColor,
+ indent: 2,
+ expanded: true,
+ selectable: true,
+ }
+}
+
+// Walk traverses this node's subtree in depth-first, pre-order (NLR) order and
+// calls the provided callback function on each traversed node (which includes
+// this node) with the traversed node and its parent node (nil for this node).
+// The callback returns whether traversal should continue with the traversed
+// node's child nodes (true) or not recurse any deeper (false).
+func (n *TreeNode) Walk(callback func(node, parent *TreeNode) bool) *TreeNode {
+ n.parent = nil
+ nodes := []*TreeNode{n}
+ for len(nodes) > 0 {
+ // Pop the top node and process it.
+ node := nodes[len(nodes)-1]
+ nodes = nodes[:len(nodes)-1]
+ if !callback(node, node.parent) {
+ // Don't add any children.
+ continue
+ }
+
+ // Add children in reverse order.
+ for index := len(node.children) - 1; index >= 0; index-- {
+ node.children[index].parent = node
+ nodes = append(nodes, node.children[index])
+ }
+ }
+
+ return n
+}
+
+// SetReference allows you to store a reference of any type in this node. This
+// will allow you to establish a mapping between the TreeView hierarchy and your
+// internal tree structure.
+func (n *TreeNode) SetReference(reference interface{}) *TreeNode {
+ n.reference = reference
+ return n
+}
+
+// GetReference returns this node's reference object.
+func (n *TreeNode) GetReference() interface{} {
+ return n.reference
+}
+
+// SetChildren sets this node's child nodes.
+func (n *TreeNode) SetChildren(childNodes []*TreeNode) *TreeNode {
+ n.children = childNodes
+ return n
+}
+
+// GetChildren returns this node's children.
+func (n *TreeNode) GetChildren() []*TreeNode {
+ return n.children
+}
+
+// ClearChildren removes all child nodes from this node.
+func (n *TreeNode) ClearChildren() *TreeNode {
+ n.children = nil
+ return n
+}
+
+// AddChild adds a new child node to this node.
+func (n *TreeNode) AddChild(node *TreeNode) *TreeNode {
+ n.children = append(n.children, node)
+ return n
+}
+
+// SetSelectable sets a flag indicating whether this node can be selected by
+// the user.
+func (n *TreeNode) SetSelectable(selectable bool) *TreeNode {
+ n.selectable = selectable
+ return n
+}
+
+// SetSelectedFunc sets a function which is called when the user selects this
+// node by hitting Enter when it is selected.
+func (n *TreeNode) SetSelectedFunc(handler func()) *TreeNode {
+ n.selected = handler
+ return n
+}
+
+// SetExpanded sets whether or not this node's child nodes should be displayed.
+func (n *TreeNode) SetExpanded(expanded bool) *TreeNode {
+ n.expanded = expanded
+ return n
+}
+
+// Expand makes the child nodes of this node appear.
+func (n *TreeNode) Expand() *TreeNode {
+ n.expanded = true
+ return n
+}
+
+// Collapse makes the child nodes of this node disappear.
+func (n *TreeNode) Collapse() *TreeNode {
+ n.expanded = false
+ return n
+}
+
+// ExpandAll expands this node and all descendent nodes.
+func (n *TreeNode) ExpandAll() *TreeNode {
+ n.Walk(func(node, parent *TreeNode) bool {
+ node.expanded = true
+ return true
+ })
+ return n
+}
+
+// CollapseAll collapses this node and all descendent nodes.
+func (n *TreeNode) CollapseAll() *TreeNode {
+ n.Walk(func(node, parent *TreeNode) bool {
+ n.expanded = false
+ return true
+ })
+ return n
+}
+
+// IsExpanded returns whether the child nodes of this node are visible.
+func (n *TreeNode) IsExpanded() bool {
+ return n.expanded
+}
+
+// SetText sets the node's text which is displayed.
+func (n *TreeNode) SetText(text string) *TreeNode {
+ n.text = text
+ return n
+}
+
+// SetColor sets the node's text color.
+func (n *TreeNode) SetColor(color tcell.Color) *TreeNode {
+ n.color = color
+ return n
+}
+
+// SetIndent sets an additional indentation for this node's text. A value of 0
+// keeps the text as far left as possible with a minimum of line graphics. Any
+// value greater than that moves the text to the right.
+func (n *TreeNode) SetIndent(indent int) *TreeNode {
+ n.indent = indent
+ return n
+}
+
+// TreeView displays tree structures. A tree consists of nodes (TreeNode
+// objects) where each node has zero or more child nodes and exactly one parent
+// node (except for the root node which has no parent node).
+//
+// The SetRoot() function is used to specify the root of the tree. Other nodes
+// are added locally to the root node or any of its descendents. See the
+// TreeNode documentation for details on node attributes. (You can use
+// SetReference() to store a reference to nodes of your own tree structure.)
+//
+// Nodes can be selected by calling SetCurrentNode(). The user can navigate the
+// selection or the tree by using the following keys:
+//
+// - j, down arrow, right arrow: Move (the selection) down by one node.
+// - k, up arrow, left arrow: Move (the selection) up by one node.
+// - g, home: Move (the selection) to the top.
+// - G, end: Move (the selection) to the bottom.
+// - Ctrl-F, page down: Move (the selection) down by one page.
+// - Ctrl-B, page up: Move (the selection) up by one page.
+//
+// Selected nodes can trigger the "selected" callback when the user hits Enter.
+//
+// The root node corresponds to level 0, its children correspond to level 1,
+// their children to level 2, and so on. Per default, the first level that is
+// displayed is 0, i.e. the root node. You can call SetTopLevel() to hide
+// levels.
+//
+// If graphics are turned on (see SetGraphics()), lines indicate the tree's
+// hierarchy. Alternative (or additionally), you can set different prefixes
+// using SetPrefixes() for different levels, for example to display hierarchical
+// bullet point lists.
+//
+// See https://github.com/rivo/tview/wiki/TreeView for an example.
+type TreeView struct {
+ *Box
+
+ // The root node.
+ root *TreeNode
+
+ // The currently selected node or nil if no node is selected.
+ currentNode *TreeNode
+
+ // The movement to be performed during the call to Draw(), one of the
+ // constants defined above.
+ movement int
+
+ // The top hierarchical level shown. (0 corresponds to the root level.)
+ topLevel int
+
+ // Strings drawn before the nodes, based on their level.
+ prefixes []string
+
+ // Vertical scroll offset.
+ offsetY int
+
+ // If set to true, all node texts will be aligned horizontally.
+ align bool
+
+ // If set to true, the tree structure is drawn using lines.
+ graphics bool
+
+ // The color of the lines.
+ graphicsColor tcell.Color
+
+ // An optional function which is called when the user has navigated to a new
+ // tree node.
+ changed func(node *TreeNode)
+
+ // An optional function which is called when a tree item was selected.
+ selected func(node *TreeNode)
+
+ // The visible nodes, top-down, as set by process().
+ nodes []*TreeNode
+}
+
+// NewTreeView returns a new tree view.
+func NewTreeView() *TreeView {
+ return &TreeView{
+ Box: NewBox(),
+ graphics: true,
+ graphicsColor: Styles.GraphicsColor,
+ }
+}
+
+// SetRoot sets the root node of the tree.
+func (t *TreeView) SetRoot(root *TreeNode) *TreeView {
+ t.root = root
+ return t
+}
+
+// GetRoot returns the root node of the tree. If no such node was previously
+// set, nil is returned.
+func (t *TreeView) GetRoot() *TreeNode {
+ return t.root
+}
+
+// SetCurrentNode sets the currently selected node. Provide nil to clear all
+// selections. Selected nodes must be visible and selectable, or else the
+// selection will be changed to the top-most selectable and visible node.
+//
+// This function does NOT trigger the "changed" callback.
+func (t *TreeView) SetCurrentNode(node *TreeNode) *TreeView {
+ t.currentNode = node
+ return t
+}
+
+// GetCurrentNode returns the currently selected node or nil of no node is
+// currently selected.
+func (t *TreeView) GetCurrentNode() *TreeNode {
+ return t.currentNode
+}
+
+// SetTopLevel sets the first tree level that is visible with 0 referring to the
+// root, 1 to the root's child nodes, and so on. Nodes above the top level are
+// not displayed.
+func (t *TreeView) SetTopLevel(topLevel int) *TreeView {
+ t.topLevel = topLevel
+ return t
+}
+
+// SetPrefixes defines the strings drawn before the nodes' texts. This is a
+// slice of strings where each element corresponds to a node's hierarchy level,
+// i.e. 0 for the root, 1 for the root's children, and so on (levels will
+// cycle).
+//
+// For example, to display a hierarchical list with bullet points:
+//
+// treeView.SetGraphics(false).
+// SetPrefixes([]string{"* ", "- ", "x "})
+func (t *TreeView) SetPrefixes(prefixes []string) *TreeView {
+ t.prefixes = prefixes
+ return t
+}
+
+// SetAlign controls the horizontal alignment of the node texts. If set to true,
+// all texts except that of top-level nodes will be placed in the same column.
+// If set to false, they will indent with the hierarchy.
+func (t *TreeView) SetAlign(align bool) *TreeView {
+ t.align = align
+ return t
+}
+
+// SetGraphics sets a flag which determines whether or not line graphics are
+// drawn to illustrate the tree's hierarchy.
+func (t *TreeView) SetGraphics(showGraphics bool) *TreeView {
+ t.graphics = showGraphics
+ return t
+}
+
+// SetGraphicsColor sets the colors of the lines used to draw the tree structure.
+func (t *TreeView) SetGraphicsColor(color tcell.Color) *TreeView {
+ t.graphicsColor = color
+ return t
+}
+
+// SetChangedFunc sets the function which is called when the user navigates to
+// a new tree node.
+func (t *TreeView) SetChangedFunc(handler func(node *TreeNode)) *TreeView {
+ t.changed = handler
+ return t
+}
+
+// SetSelectedFunc sets the function which is called when the user selects a
+// node by pressing Enter on the current selection.
+func (t *TreeView) SetSelectedFunc(handler func(node *TreeNode)) *TreeView {
+ t.selected = handler
+ return t
+}
+
+// process builds the visible tree, populates the "nodes" slice, and processes
+// pending selection actions.
+func (t *TreeView) process() {
+ _, _, _, height := t.GetInnerRect()
+
+ // Determine visible nodes and their placement.
+ var graphicsOffset, maxTextX int
+ t.nodes = nil
+ selectedIndex := -1
+ topLevelGraphicsX := -1
+ if t.graphics {
+ graphicsOffset = 1
+ }
+ t.root.Walk(func(node, parent *TreeNode) bool {
+ // Set node attributes.
+ node.parent = parent
+ if parent == nil {
+ node.level = 0
+ node.graphicsX = 0
+ node.textX = 0
+ } else {
+ node.level = parent.level + 1
+ node.graphicsX = parent.textX
+ node.textX = node.graphicsX + graphicsOffset + node.indent
+ }
+ if !t.graphics && t.align {
+ // Without graphics, we align nodes on the first column.
+ node.textX = 0
+ }
+ if node.level == t.topLevel {
+ // No graphics for top level nodes.
+ node.graphicsX = 0
+ node.textX = 0
+ }
+ if node.textX > maxTextX {
+ maxTextX = node.textX
+ }
+ if node == t.currentNode && node.selectable {
+ selectedIndex = len(t.nodes)
+ }
+
+ // Maybe we want to skip this level.
+ if t.topLevel == node.level && (topLevelGraphicsX < 0 || node.graphicsX < topLevelGraphicsX) {
+ topLevelGraphicsX = node.graphicsX
+ }
+
+ // Add and recurse (if desired).
+ if node.level >= t.topLevel {
+ t.nodes = append(t.nodes, node)
+ }
+ return node.expanded
+ })
+
+ // Post-process positions.
+ for _, node := range t.nodes {
+ // If text must align, we correct the positions.
+ if t.align && node.level > t.topLevel {
+ node.textX = maxTextX
+ }
+
+ // If we skipped levels, shift to the left.
+ if topLevelGraphicsX > 0 {
+ node.graphicsX -= topLevelGraphicsX
+ node.textX -= topLevelGraphicsX
+ }
+ }
+
+ // Process selection. (Also trigger events if necessary.)
+ if selectedIndex >= 0 {
+ // Move the selection.
+ newSelectedIndex := selectedIndex
+ MovementSwitch:
+ switch t.movement {
+ case treeUp:
+ for newSelectedIndex > 0 {
+ newSelectedIndex--
+ if t.nodes[newSelectedIndex].selectable {
+ break MovementSwitch
+ }
+ }
+ newSelectedIndex = selectedIndex
+ case treeDown:
+ for newSelectedIndex < len(t.nodes)-1 {
+ newSelectedIndex++
+ if t.nodes[newSelectedIndex].selectable {
+ break MovementSwitch
+ }
+ }
+ newSelectedIndex = selectedIndex
+ case treeHome:
+ for newSelectedIndex = 0; newSelectedIndex < len(t.nodes); newSelectedIndex++ {
+ if t.nodes[newSelectedIndex].selectable {
+ break MovementSwitch
+ }
+ }
+ newSelectedIndex = selectedIndex
+ case treeEnd:
+ for newSelectedIndex = len(t.nodes) - 1; newSelectedIndex >= 0; newSelectedIndex-- {
+ if t.nodes[newSelectedIndex].selectable {
+ break MovementSwitch
+ }
+ }
+ newSelectedIndex = selectedIndex
+ case treePageUp:
+ if newSelectedIndex+height < len(t.nodes) {
+ newSelectedIndex += height
+ } else {
+ newSelectedIndex = len(t.nodes) - 1
+ }
+ for ; newSelectedIndex < len(t.nodes); newSelectedIndex++ {
+ if t.nodes[newSelectedIndex].selectable {
+ break MovementSwitch
+ }
+ }
+ newSelectedIndex = selectedIndex
+ case treePageDown:
+ if newSelectedIndex >= height {
+ newSelectedIndex -= height
+ } else {
+ newSelectedIndex = 0
+ }
+ for ; newSelectedIndex >= 0; newSelectedIndex-- {
+ if t.nodes[newSelectedIndex].selectable {
+ break MovementSwitch
+ }
+ }
+ newSelectedIndex = selectedIndex
+ }
+ t.currentNode = t.nodes[newSelectedIndex]
+ if newSelectedIndex != selectedIndex {
+ t.movement = treeNone
+ if t.changed != nil {
+ t.changed(t.currentNode)
+ }
+ }
+ selectedIndex = newSelectedIndex
+
+ // Move selection into viewport.
+ if selectedIndex-t.offsetY >= height {
+ t.offsetY = selectedIndex - height + 1
+ }
+ if selectedIndex < t.offsetY {
+ t.offsetY = selectedIndex
+ }
+ } else {
+ // If selection is not visible or selectable, select the first candidate.
+ if t.currentNode != nil {
+ for index, node := range t.nodes {
+ if node.selectable {
+ selectedIndex = index
+ t.currentNode = node
+ break
+ }
+ }
+ }
+ if selectedIndex < 0 {
+ t.currentNode = nil
+ }
+ }
+}
+
+// Draw draws this primitive onto the screen.
+func (t *TreeView) Draw(screen tcell.Screen) {
+ t.Box.Draw(screen)
+ if t.root == nil {
+ return
+ }
+
+ // Build the tree if necessary.
+ if t.nodes == nil {
+ t.process()
+ }
+ defer func() {
+ t.nodes = nil // Rebuild during next call to Draw()
+ }()
+
+ // Scroll the tree.
+ x, y, width, height := t.GetInnerRect()
+ switch t.movement {
+ case treeUp:
+ t.offsetY--
+ case treeDown:
+ t.offsetY++
+ case treeHome:
+ t.offsetY = 0
+ case treeEnd:
+ t.offsetY = len(t.nodes)
+ case treePageUp:
+ t.offsetY -= height
+ case treePageDown:
+ t.offsetY += height
+ }
+ t.movement = treeNone
+
+ // Fix invalid offsets.
+ if t.offsetY >= len(t.nodes)-height {
+ t.offsetY = len(t.nodes) - height
+ }
+ if t.offsetY < 0 {
+ t.offsetY = 0
+ }
+
+ // Draw the tree.
+ posY := y
+ lineStyle := tcell.StyleDefault.Background(t.backgroundColor).Foreground(t.graphicsColor)
+ for index, node := range t.nodes {
+ // Skip invisible parts.
+ if posY >= y+height+1 {
+ break
+ }
+ if index < t.offsetY {
+ continue
+ }
+
+ // Draw the graphics.
+ if t.graphics {
+ // Draw ancestor branches.
+ ancestor := node.parent
+ for ancestor != nil && ancestor.parent != nil && ancestor.parent.level >= t.topLevel {
+ if ancestor.graphicsX >= width {
+ continue
+ }
+
+ // Draw a branch if this ancestor is not a last child.
+ if ancestor.parent.children[len(ancestor.parent.children)-1] != ancestor {
+ if posY-1 >= y && ancestor.textX > ancestor.graphicsX {
+ PrintJoinedSemigraphics(screen, x+ancestor.graphicsX, posY-1, Borders.Vertical, t.graphicsColor)
+ }
+ if posY < y+height {
+ screen.SetContent(x+ancestor.graphicsX, posY, Borders.Vertical, nil, lineStyle)
+ }
+ }
+ ancestor = ancestor.parent
+ }
+
+ if node.textX > node.graphicsX && node.graphicsX < width {
+ // Connect to the node above.
+ if posY-1 >= y && t.nodes[index-1].graphicsX <= node.graphicsX && t.nodes[index-1].textX > node.graphicsX {
+ PrintJoinedSemigraphics(screen, x+node.graphicsX, posY-1, Borders.TopLeft, t.graphicsColor)
+ }
+
+ // Join this node.
+ if posY < y+height {
+ screen.SetContent(x+node.graphicsX, posY, Borders.BottomLeft, nil, lineStyle)
+ for pos := node.graphicsX + 1; pos < node.textX && pos < width; pos++ {
+ screen.SetContent(x+pos, posY, Borders.Horizontal, nil, lineStyle)
+ }
+ }
+ }
+ }
+
+ // Draw the prefix and the text.
+ if node.textX < width && posY < y+height {
+ // Prefix.
+ var prefixWidth int
+ if len(t.prefixes) > 0 {
+ _, prefixWidth = Print(screen, t.prefixes[(node.level-t.topLevel)%len(t.prefixes)], x+node.textX, posY, width-node.textX, AlignLeft, node.color)
+ }
+
+ // Text.
+ if node.textX+prefixWidth < width {
+ style := tcell.StyleDefault.Foreground(node.color)
+ if node == t.currentNode {
+ style = tcell.StyleDefault.Background(node.color).Foreground(t.backgroundColor)
+ }
+ printWithStyle(screen, node.text, x+node.textX+prefixWidth, posY, width-node.textX-prefixWidth, AlignLeft, style)
+ }
+ }
+
+ // Advance.
+ posY++
+ }
+}
+
+// InputHandler returns the handler for this primitive.
+func (t *TreeView) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
+ return t.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
+ // Because the tree is flattened into a list only at drawing time, we also
+ // postpone the (selection) movement to drawing time.
+ switch key := event.Key(); key {
+ case tcell.KeyTab, tcell.KeyDown, tcell.KeyRight:
+ t.movement = treeDown
+ case tcell.KeyBacktab, tcell.KeyUp, tcell.KeyLeft:
+ t.movement = treeUp
+ case tcell.KeyHome:
+ t.movement = treeHome
+ case tcell.KeyEnd:
+ t.movement = treeEnd
+ case tcell.KeyPgDn, tcell.KeyCtrlF:
+ t.movement = treePageDown
+ case tcell.KeyPgUp, tcell.KeyCtrlB:
+ t.movement = treePageUp
+ case tcell.KeyRune:
+ switch event.Rune() {
+ case 'g':
+ t.movement = treeHome
+ case 'G':
+ t.movement = treeEnd
+ case 'j':
+ t.movement = treeDown
+ case 'k':
+ t.movement = treeUp
+ }
+ case tcell.KeyEnter:
+ if t.currentNode != nil {
+ if t.selected != nil {
+ t.selected(t.currentNode)
+ }
+ if t.currentNode.selected != nil {
+ t.currentNode.selected()
+ }
+ }
+ }
+
+ t.process()
+ })
+}
diff --git a/vendor/maunium.net/go/tview/util.go b/vendor/maunium.net/go/tview/util.go
index 41e52dd..e408b18 100644
--- a/vendor/maunium.net/go/tview/util.go
+++ b/vendor/maunium.net/go/tview/util.go
@@ -1,11 +1,9 @@
package tview
import (
- "fmt"
"math"
"regexp"
"strconv"
- "strings"
"unicode"
"maunium.net/go/tcell"
@@ -19,97 +17,13 @@ const (
AlignRight
)
-// Semigraphical runes.
-const (
- GraphicsHoriBar = '\u2500'
- GraphicsVertBar = '\u2502'
- GraphicsTopLeftCorner = '\u250c'
- GraphicsTopRightCorner = '\u2510'
- GraphicsBottomLeftCorner = '\u2514'
- GraphicsBottomRightCorner = '\u2518'
- GraphicsLeftT = '\u251c'
- GraphicsRightT = '\u2524'
- GraphicsTopT = '\u252c'
- GraphicsBottomT = '\u2534'
- GraphicsCross = '\u253c'
- GraphicsDbVertBar = '\u2550'
- GraphicsDbHorBar = '\u2551'
- GraphicsDbTopLeftCorner = '\u2554'
- GraphicsDbTopRightCorner = '\u2557'
- GraphicsDbBottomRightCorner = '\u255d'
- GraphicsDbBottomLeftCorner = '\u255a'
- GraphicsEllipsis = '\u2026'
-)
-
-// joints maps combinations of two graphical runes to the rune that results
-// when joining the two in the same screen cell. The keys of this map are
-// two-rune strings where the value of the first rune is lower than the value
-// of the second rune. Identical runes are not contained.
-var joints = map[string]rune{
- "\u2500\u2502": GraphicsCross,
- "\u2500\u250c": GraphicsTopT,
- "\u2500\u2510": GraphicsTopT,
- "\u2500\u2514": GraphicsBottomT,
- "\u2500\u2518": GraphicsBottomT,
- "\u2500\u251c": GraphicsCross,
- "\u2500\u2524": GraphicsCross,
- "\u2500\u252c": GraphicsTopT,
- "\u2500\u2534": GraphicsBottomT,
- "\u2500\u253c": GraphicsCross,
- "\u2502\u250c": GraphicsLeftT,
- "\u2502\u2510": GraphicsRightT,
- "\u2502\u2514": GraphicsLeftT,
- "\u2502\u2518": GraphicsRightT,
- "\u2502\u251c": GraphicsLeftT,
- "\u2502\u2524": GraphicsRightT,
- "\u2502\u252c": GraphicsCross,
- "\u2502\u2534": GraphicsCross,
- "\u2502\u253c": GraphicsCross,
- "\u250c\u2510": GraphicsTopT,
- "\u250c\u2514": GraphicsLeftT,
- "\u250c\u2518": GraphicsCross,
- "\u250c\u251c": GraphicsLeftT,
- "\u250c\u2524": GraphicsCross,
- "\u250c\u252c": GraphicsTopT,
- "\u250c\u2534": GraphicsCross,
- "\u250c\u253c": GraphicsCross,
- "\u2510\u2514": GraphicsCross,
- "\u2510\u2518": GraphicsRightT,
- "\u2510\u251c": GraphicsCross,
- "\u2510\u2524": GraphicsRightT,
- "\u2510\u252c": GraphicsTopT,
- "\u2510\u2534": GraphicsCross,
- "\u2510\u253c": GraphicsCross,
- "\u2514\u2518": GraphicsBottomT,
- "\u2514\u251c": GraphicsLeftT,
- "\u2514\u2524": GraphicsCross,
- "\u2514\u252c": GraphicsCross,
- "\u2514\u2534": GraphicsBottomT,
- "\u2514\u253c": GraphicsCross,
- "\u2518\u251c": GraphicsCross,
- "\u2518\u2524": GraphicsRightT,
- "\u2518\u252c": GraphicsCross,
- "\u2518\u2534": GraphicsBottomT,
- "\u2518\u253c": GraphicsCross,
- "\u251c\u2524": GraphicsCross,
- "\u251c\u252c": GraphicsCross,
- "\u251c\u2534": GraphicsCross,
- "\u251c\u253c": GraphicsCross,
- "\u2524\u252c": GraphicsCross,
- "\u2524\u2534": GraphicsCross,
- "\u2524\u253c": GraphicsCross,
- "\u252c\u2534": GraphicsCross,
- "\u252c\u253c": GraphicsCross,
- "\u2534\u253c": GraphicsCross,
-}
-
// Common regular expressions.
var (
colorPattern = regexp.MustCompile(`\[([a-zA-Z]+|#[0-9a-zA-Z]{6}|\-)?(:([a-zA-Z]+|#[0-9a-zA-Z]{6}|\-)?(:([lbdru]+|\-)?)?)?\]`)
regionPattern = regexp.MustCompile(`\["([a-zA-Z0-9_,;: \-\.]*)"\]`)
escapePattern = regexp.MustCompile(`\[([a-zA-Z0-9_,;: \-\."#]+)\[(\[*)\]`)
nonEscapePattern = regexp.MustCompile(`(\[[a-zA-Z0-9_,;: \-\."#]+\[*)\]`)
- boundaryPattern = regexp.MustCompile("([[:punct:]]\\s*|\\s+)")
+ boundaryPattern = regexp.MustCompile(`(([[:punct:]]|\n)[ \t\f\r]*|(\s+))`)
spacePattern = regexp.MustCompile(`\s+`)
)
@@ -204,13 +118,12 @@ func overlayStyle(background tcell.Color, defaultStyle tcell.Style, fgColor, bgC
defFg, defBg, defAttr := defaultStyle.Decompose()
style := defaultStyle.Background(background)
- if fgColor == "-" {
- style = style.Foreground(defFg)
- } else if fgColor != "" {
+ style = style.Foreground(defFg)
+ if fgColor != "" {
style = style.Foreground(tcell.GetColor(fgColor))
}
- if bgColor == "-" {
+ if bgColor == "-" || bgColor == "" && defBg != tcell.ColorDefault {
style = style.Background(defBg)
} else if bgColor != "" {
style = style.Background(tcell.GetColor(bgColor))
@@ -288,8 +201,8 @@ func decomposeString(text string) (colorIndices [][]int, colors [][]string, esca
// You can change the colors and text styles mid-text by inserting a color tag.
// See the package description for details.
//
-// Returns the number of actual runes printed (not including color tags) and the
-// actual width used for the printed runes.
+// Returns the number of actual bytes of the text printed (including color tags)
+// and the actual width used for the printed runes.
func Print(screen tcell.Screen, text string, x, y, maxWidth, align int, color tcell.Color) (int, int) {
return printWithStyle(screen, text, x, y, maxWidth, align, tcell.StyleDefault.Foreground(color))
}
@@ -302,186 +215,160 @@ func printWithStyle(screen tcell.Screen, text string, x, y, maxWidth, align int,
}
// Decompose the text.
- colorIndices, colors, escapeIndices, strippedText, _ := decomposeString(text)
+ colorIndices, colors, escapeIndices, strippedText, strippedWidth := decomposeString(text)
- // We deal with runes, not with bytes.
- runes := []rune(strippedText)
-
- // This helper function takes positions for a substring of "runes" and returns
- // a new string corresponding to this substring, making sure printing that
- // substring will observe color tags.
- substring := func(from, to int) string {
+ // We want to reduce all alignments to AlignLeft.
+ if align == AlignRight {
+ if strippedWidth <= maxWidth {
+ // There's enough space for the entire text.
+ return printWithStyle(screen, text, x+maxWidth-strippedWidth, y, maxWidth, AlignLeft, style)
+ }
+ // Trim characters off the beginning.
var (
- colorPos, escapePos, runePos, startPos int
+ bytes, width, colorPos, escapePos, tagOffset int
foregroundColor, backgroundColor, attributes string
)
- if from >= len(runes) {
- return ""
- }
- for pos := range text {
- // Handle color tags.
- if colorPos < len(colorIndices) && pos >= colorIndices[colorPos][0] && pos < colorIndices[colorPos][1] {
- if pos == colorIndices[colorPos][1]-1 {
- if runePos <= from {
- foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colors[colorPos])
- }
- colorPos++
- }
- continue
- }
-
- // Handle escape tags.
- if escapePos < len(escapeIndices) && pos >= escapeIndices[escapePos][0] && pos < escapeIndices[escapePos][1] {
- if pos == escapeIndices[escapePos][1]-1 {
- escapePos++
- } else if pos == escapeIndices[escapePos][1]-2 {
- continue
- }
+ _, originalBackground, _ := style.Decompose()
+ iterateString(strippedText, func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
+ // Update color/escape tag offset and style.
+ if colorPos < len(colorIndices) && textPos+tagOffset >= colorIndices[colorPos][0] && textPos+tagOffset < colorIndices[colorPos][1] {
+ foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colors[colorPos])
+ style = overlayStyle(originalBackground, style, foregroundColor, backgroundColor, attributes)
+ tagOffset += colorIndices[colorPos][1] - colorIndices[colorPos][0]
+ colorPos++
}
-
- // Check boundaries.
- if runePos == from {
- startPos = pos
- } else if runePos >= to {
- return fmt.Sprintf(`[%s:%s:%s]%s`, foregroundColor, backgroundColor, attributes, text[startPos:pos])
+ if escapePos < len(escapeIndices) && textPos+tagOffset >= escapeIndices[escapePos][0] && textPos+tagOffset < escapeIndices[escapePos][1] {
+ tagOffset++
+ escapePos++
}
-
- runePos++
- }
-
- return fmt.Sprintf(`[%s:%s:%s]%s`, foregroundColor, backgroundColor, attributes, text[startPos:])
- }
-
- // We want to reduce everything to AlignLeft.
- if align == AlignRight {
- width := 0
- start := len(runes)
- for index := start - 1; index >= 0; index-- {
- w := runewidth.RuneWidth(runes[index])
- if width+w > maxWidth {
- break
+ if strippedWidth-screenPos < maxWidth {
+ // We chopped off enough.
+ if escapePos > 0 && textPos+tagOffset-1 >= escapeIndices[escapePos-1][0] && textPos+tagOffset-1 < escapeIndices[escapePos-1][1] {
+ // Unescape open escape sequences.
+ escapeCharPos := escapeIndices[escapePos-1][1] - 2
+ text = text[:escapeCharPos] + text[escapeCharPos+1:]
+ }
+ // Print and return.
+ bytes, width = printWithStyle(screen, text[textPos+tagOffset:], x, y, maxWidth, AlignLeft, style)
+ return true
}
- width += w
- start = index
- }
- for start < len(runes) && runewidth.RuneWidth(runes[start]) == 0 {
- start++
- }
- return printWithStyle(screen, substring(start, len(runes)), x+maxWidth-width, y, width, AlignLeft, style)
+ return false
+ })
+ return bytes, width
} else if align == AlignCenter {
- width := runewidth.StringWidth(strippedText)
- if width == maxWidth {
+ if strippedWidth == maxWidth {
// Use the exact space.
return printWithStyle(screen, text, x, y, maxWidth, AlignLeft, style)
- } else if width < maxWidth {
+ } else if strippedWidth < maxWidth {
// We have more space than we need.
- half := (maxWidth - width) / 2
+ half := (maxWidth - strippedWidth) / 2
return printWithStyle(screen, text, x+half, y, maxWidth-half, AlignLeft, style)
} else {
// Chop off runes until we have a perfect fit.
var choppedLeft, choppedRight, leftIndex, rightIndex int
- rightIndex = len(runes) - 1
- for rightIndex > leftIndex && width-choppedLeft-choppedRight > maxWidth {
+ rightIndex = len(strippedText)
+ for rightIndex-1 > leftIndex && strippedWidth-choppedLeft-choppedRight > maxWidth {
if choppedLeft < choppedRight {
- leftWidth := runewidth.RuneWidth(runes[leftIndex])
- choppedLeft += leftWidth
- leftIndex++
- for leftIndex < len(runes) && leftIndex < rightIndex && runewidth.RuneWidth(runes[leftIndex]) == 0 {
- leftIndex++
- }
+ // Iterate on the left by one character.
+ iterateString(strippedText[leftIndex:], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
+ choppedLeft += screenWidth
+ leftIndex += textWidth
+ return true
+ })
} else {
- rightWidth := runewidth.RuneWidth(runes[rightIndex])
- choppedRight += rightWidth
- rightIndex--
+ // Iterate on the right by one character.
+ iterateStringReverse(strippedText[leftIndex:rightIndex], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
+ choppedRight += screenWidth
+ rightIndex -= textWidth
+ return true
+ })
}
}
- return printWithStyle(screen, substring(leftIndex, rightIndex), x, y, maxWidth, AlignLeft, style)
+
+ // Add tag offsets and determine start style.
+ var (
+ colorPos, escapePos, tagOffset int
+ foregroundColor, backgroundColor, attributes string
+ )
+ _, originalBackground, _ := style.Decompose()
+ for index := range strippedText {
+ // We only need the offset of the left index.
+ if index > leftIndex {
+ // We're done.
+ if escapePos > 0 && leftIndex+tagOffset-1 >= escapeIndices[escapePos-1][0] && leftIndex+tagOffset-1 < escapeIndices[escapePos-1][1] {
+ // Unescape open escape sequences.
+ escapeCharPos := escapeIndices[escapePos-1][1] - 2
+ text = text[:escapeCharPos] + text[escapeCharPos+1:]
+ }
+ break
+ }
+
+ // Update color/escape tag offset.
+ if colorPos < len(colorIndices) && index+tagOffset >= colorIndices[colorPos][0] && index+tagOffset < colorIndices[colorPos][1] {
+ if index <= leftIndex {
+ foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colors[colorPos])
+ style = overlayStyle(originalBackground, style, foregroundColor, backgroundColor, attributes)
+ }
+ tagOffset += colorIndices[colorPos][1] - colorIndices[colorPos][0]
+ colorPos++
+ }
+ if escapePos < len(escapeIndices) && index+tagOffset >= escapeIndices[escapePos][0] && index+tagOffset < escapeIndices[escapePos][1] {
+ tagOffset++
+ escapePos++
+ }
+ }
+ return printWithStyle(screen, text[leftIndex+tagOffset:], x, y, maxWidth, AlignLeft, style)
}
}
// Draw text.
- drawn := 0
- drawnWidth := 0
var (
- colorPos, escapePos int
- foregroundColor, backgroundColor, attributes string
+ drawn, drawnWidth, colorPos, escapePos, tagOffset int
+ foregroundColor, backgroundColor, attributes string
)
- runeSequence := make([]rune, 0, 10)
- runeSeqWidth := 0
- flush := func() {
- if len(runeSequence) == 0 {
- return // Nothing to flush.
- }
-
- // Print the rune sequence.
- finalX := x + drawnWidth
- _, _, finalStyle, _ := screen.GetContent(finalX, y)
- _, background, _ := finalStyle.Decompose()
- finalStyle = overlayStyle(background, style, foregroundColor, backgroundColor, attributes)
- var comb []rune
- if len(runeSequence) > 1 {
- // Allocate space for the combining characters only when necessary.
- comb = make([]rune, len(runeSequence)-1)
- copy(comb, runeSequence[1:])
- }
- for offset := 0; offset < runeSeqWidth; offset++ {
- // To avoid undesired effects, we place the same character in all cells.
- screen.SetContent(finalX+offset, y, runeSequence[0], comb, finalStyle)
+ iterateString(strippedText, func(main rune, comb []rune, textPos, length, screenPos, screenWidth int) bool {
+ // Only continue if there is still space.
+ if drawnWidth+screenWidth > maxWidth {
+ return true
}
- // Advance and reset.
- drawn += len(runeSequence)
- drawnWidth += runeSeqWidth
- runeSequence = runeSequence[:0]
- runeSeqWidth = 0
- }
- for pos, ch := range text {
// Handle color tags.
- if colorPos < len(colorIndices) && pos >= colorIndices[colorPos][0] && pos < colorIndices[colorPos][1] {
- flush()
- if pos == colorIndices[colorPos][1]-1 {
- foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colors[colorPos])
- colorPos++
- }
- continue
+ if colorPos < len(colorIndices) && textPos+tagOffset >= colorIndices[colorPos][0] && textPos+tagOffset < colorIndices[colorPos][1] {
+ foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colors[colorPos])
+ tagOffset += colorIndices[colorPos][1] - colorIndices[colorPos][0]
+ colorPos++
}
- // Handle escape tags.
- if escapePos < len(escapeIndices) && pos >= escapeIndices[escapePos][0] && pos < escapeIndices[escapePos][1] {
- flush()
- if pos == escapeIndices[escapePos][1]-1 {
+ // Handle scape tags.
+ if escapePos < len(escapeIndices) && textPos+tagOffset >= escapeIndices[escapePos][0] && textPos+tagOffset < escapeIndices[escapePos][1] {
+ if textPos+tagOffset == escapeIndices[escapePos][1]-2 {
+ tagOffset++
escapePos++
- } else if pos == escapeIndices[escapePos][1]-2 {
- continue
}
}
- // Check if we have enough space for this rune.
- chWidth := runewidth.RuneWidth(ch)
- if drawnWidth+chWidth > maxWidth {
- break // No. We're done then.
- }
-
- // Put this rune in the queue.
- if chWidth == 0 {
- // If this is not a modifier, we treat it as a space character.
- if len(runeSequence) == 0 {
- ch = ' '
- chWidth = 1
+ // Print the rune sequence.
+ finalX := x + drawnWidth
+ _, _, finalStyle, _ := screen.GetContent(finalX, y)
+ _, background, _ := finalStyle.Decompose()
+ finalStyle = overlayStyle(background, style, foregroundColor, backgroundColor, attributes)
+ for offset := screenWidth - 1; offset >= 0; offset-- {
+ // To avoid undesired effects, we populate all cells.
+ if offset == 0 {
+ screen.SetContent(finalX+offset, y, main, comb, finalStyle)
+ } else {
+ screen.SetContent(finalX+offset, y, ' ', nil, finalStyle)
}
- } else {
- // We have a character. Flush all previous runes.
- flush()
}
- runeSequence = append(runeSequence, ch)
- runeSeqWidth += chWidth
- }
- if drawnWidth+runeSeqWidth <= maxWidth {
- flush()
- }
+ // Advance.
+ drawn += length
+ drawnWidth += screenWidth
+
+ return false
+ })
- return drawn, drawnWidth
+ return drawn + tagOffset + len(escapeIndices), drawnWidth
}
// PrintSimple prints white text to the screen at the given position.
@@ -507,131 +394,86 @@ func WordWrap(text string, width int) (lines []string) {
colorTagIndices, _, escapeIndices, strippedText, _ := decomposeString(text)
// Find candidate breakpoints.
- breakPoints := boundaryPattern.FindAllStringIndex(strippedText, -1)
-
- // This helper function adds a new line to the result slice. The provided
- // positions are in stripped index space.
- addLine := func(from, to int) {
- // Shift indices back to original index space.
- var colorTagIndex, escapeIndex int
- for colorTagIndex < len(colorTagIndices) && to >= colorTagIndices[colorTagIndex][0] ||
- escapeIndex < len(escapeIndices) && to >= escapeIndices[escapeIndex][0] {
- past := 0
- if colorTagIndex < len(colorTagIndices) {
- tagWidth := colorTagIndices[colorTagIndex][1] - colorTagIndices[colorTagIndex][0]
- if colorTagIndices[colorTagIndex][0] < from {
- from += tagWidth
- to += tagWidth
- colorTagIndex++
- } else if colorTagIndices[colorTagIndex][0] < to {
- to += tagWidth
- colorTagIndex++
- } else {
- past++
- }
- } else {
- past++
- }
- if escapeIndex < len(escapeIndices) {
- tagWidth := escapeIndices[escapeIndex][1] - escapeIndices[escapeIndex][0]
- if escapeIndices[escapeIndex][0] < from {
- from += tagWidth
- to += tagWidth
- escapeIndex++
- } else if escapeIndices[escapeIndex][0] < to {
- to += tagWidth
- escapeIndex++
- } else {
- past++
- }
- } else {
- past++
- }
- if past == 2 {
- break // All other indices are beyond the requested string.
+ breakpoints := boundaryPattern.FindAllStringSubmatchIndex(strippedText, -1)
+ // Results in one entry for each candidate. Each entry is an array a of
+ // indices into strippedText where a[6] < 0 for newline/punctuation matches
+ // and a[4] < 0 for whitespace matches.
+
+ // Process stripped text one character at a time.
+ var (
+ colorPos, escapePos, breakpointPos, tagOffset int
+ lastBreakpoint, lastContinuation, currentLineStart int
+ lineWidth, continuationWidth int
+ newlineBreakpoint bool
+ )
+ unescape := func(substr string, startIndex int) string {
+ // A helper function to unescape escaped tags.
+ for index := escapePos; index >= 0; index-- {
+ if index < len(escapeIndices) && startIndex > escapeIndices[index][0] && startIndex < escapeIndices[index][1]-1 {
+ pos := escapeIndices[index][1] - 2 - startIndex
+ return substr[:pos] + substr[pos+1:]
}
}
- lines = append(lines, text[from:to])
+ return substr
}
+ iterateString(strippedText, func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
+ // Handle colour tags.
+ if colorPos < len(colorTagIndices) && textPos+tagOffset >= colorTagIndices[colorPos][0] && textPos+tagOffset < colorTagIndices[colorPos][1] {
+ tagOffset += colorTagIndices[colorPos][1] - colorTagIndices[colorPos][0]
+ colorPos++
+ }
- // Determine final breakpoints.
- var start, lastEnd, newStart, breakPoint int
- for {
- // What's our candidate string?
- var candidate string
- if breakPoint < len(breakPoints) {
- candidate = text[start:breakPoints[breakPoint][1]]
- } else {
- candidate = text[start:]
+ // Handle escape tags.
+ if escapePos < len(escapeIndices) && textPos+tagOffset == escapeIndices[escapePos][1]-2 {
+ tagOffset++
+ escapePos++
}
- candidate = strings.TrimRightFunc(candidate, unicode.IsSpace)
-
- if runewidth.StringWidth(candidate) >= width {
- // We're past the available width.
- if lastEnd > start {
- // Use the previous candidate.
- addLine(start, lastEnd)
- start = newStart
- } else {
- // We have no previous candidate. Make a hard break.
- var lineWidth int
- for index, ch := range text {
- if index < start {
- continue
- }
- chWidth := runewidth.RuneWidth(ch)
- if lineWidth > 0 && lineWidth+chWidth >= width {
- addLine(start, index)
- start = index
- break
- }
- lineWidth += chWidth
- }
- }
- } else {
- // We haven't hit the right border yet.
- if breakPoint >= len(breakPoints) {
- // It's the last line. We're done.
- if len(candidate) > 0 {
- addLine(start, len(strippedText))
- }
- break
- } else {
- // We have a new candidate.
- lastEnd = start + len(candidate)
- newStart = breakPoints[breakPoint][1]
- breakPoint++
- }
+
+ // Check if a break is warranted.
+ afterContinuation := lastContinuation > 0 && textPos+tagOffset >= lastContinuation
+ noBreakpoint := lastContinuation == 0
+ beyondWidth := lineWidth > 0 && lineWidth > width
+ if beyondWidth && noBreakpoint {
+ // We need a hard break without a breakpoint.
+ lines = append(lines, unescape(text[currentLineStart:textPos+tagOffset], currentLineStart))
+ currentLineStart = textPos + tagOffset
+ lineWidth = continuationWidth
+ } else if afterContinuation && (beyondWidth || newlineBreakpoint) {
+ // Break at last breakpoint or at newline.
+ lines = append(lines, unescape(text[currentLineStart:lastBreakpoint], currentLineStart))
+ currentLineStart = lastContinuation
+ lineWidth = continuationWidth
+ lastBreakpoint, lastContinuation, newlineBreakpoint = 0, 0, false
}
- }
- return
-}
+ // Is this a breakpoint?
+ if breakpointPos < len(breakpoints) && textPos == breakpoints[breakpointPos][0] {
+ // Yes, it is. Set up breakpoint infos depending on its type.
+ lastBreakpoint = breakpoints[breakpointPos][0] + tagOffset
+ lastContinuation = breakpoints[breakpointPos][1] + tagOffset
+ newlineBreakpoint = main == '\n'
+ if breakpoints[breakpointPos][6] < 0 && !newlineBreakpoint {
+ lastBreakpoint++ // Don't skip punctuation.
+ }
+ breakpointPos++
+ }
-// PrintJoinedBorder prints a border graphics rune into the screen at the given
-// position with the given color, joining it with any existing border graphics
-// rune. Background colors are preserved. At this point, only regular single
-// line borders are supported.
-func PrintJoinedBorder(screen tcell.Screen, x, y int, ch rune, color tcell.Color) {
- previous, _, style, _ := screen.GetContent(x, y)
- style = style.Foreground(color)
-
- // What's the resulting rune?
- var result rune
- if ch == previous {
- result = ch
- } else {
- if ch < previous {
- previous, ch = ch, previous
+ // Once we hit the continuation point, we start buffering widths.
+ if textPos+tagOffset < lastContinuation {
+ continuationWidth = 0
}
- result = joints[string(previous)+string(ch)]
- }
- if result == 0 {
- result = ch
+
+ lineWidth += screenWidth
+ continuationWidth += screenWidth
+ return false
+ })
+
+ // Flush the rest.
+ if currentLineStart < len(text) {
+ lines = append(lines, unescape(text[currentLineStart:], currentLineStart))
}
- // We only print something if we have something.
- screen.SetContent(x, y, result, nil, style)
+ return
}
// Escape escapes the given text such that color and/or region tags are not
@@ -643,3 +485,121 @@ func PrintJoinedBorder(screen tcell.Screen, x, y int, ch rune, color tcell.Color
func Escape(text string) string {
return nonEscapePattern.ReplaceAllString(text, "$1[]")
}
+
+// iterateString iterates through the given string one printed character at a
+// time. For each such character, the callback function is called with the
+// Unicode code points of the character (the first rune and any combining runes
+// which may be nil if there aren't any), the starting position (in bytes)
+// within the original string, its length in bytes, the screen position of the
+// character, and the screen width of it. The iteration stops if the callback
+// returns true. This function returns true if the iteration was stopped before
+// the last character.
+func iterateString(text string, callback func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool) bool {
+ var (
+ runes []rune
+ lastZeroWidthJoiner bool
+ startIndex int
+ startPos int
+ pos int
+ )
+
+ // Helper function which invokes the callback.
+ flush := func(index int) bool {
+ var comb []rune
+ if len(runes) > 1 {
+ comb = runes[1:]
+ }
+ return callback(runes[0], comb, startIndex, index-startIndex, startPos, pos-startPos)
+ }
+
+ for index, r := range text {
+ if unicode.In(r, unicode.Lm, unicode.M) || r == '\u200d' {
+ lastZeroWidthJoiner = r == '\u200d'
+ } else {
+ // We have a rune that's not a modifier. It could be the beginning of a
+ // new character.
+ if !lastZeroWidthJoiner {
+ if len(runes) > 0 {
+ // It is. Invoke callback.
+ if flush(index) {
+ return true // We're done.
+ }
+ // Reset rune store.
+ runes = runes[:0]
+ startIndex = index
+ startPos = pos
+ }
+ pos += runewidth.RuneWidth(r)
+ } else {
+ lastZeroWidthJoiner = false
+ }
+ }
+ runes = append(runes, r)
+ }
+
+ // Flush any remaining runes.
+ if len(runes) > 0 {
+ flush(len(text))
+ }
+
+ return false
+}
+
+// iterateStringReverse iterates through the given string in reverse, starting
+// from the end of the string, one printed character at a time. For each such
+// character, the callback function is called with the Unicode code points of
+// the character (the first rune and any combining runes which may be nil if
+// there aren't any), the starting position (in bytes) within the original
+// string, its length in bytes, the screen position of the character, and the
+// screen width of it. The iteration stops if the callback returns true. This
+// function returns true if the iteration was stopped before the last character.
+func iterateStringReverse(text string, callback func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool) bool {
+ type runePos struct {
+ r rune
+ pos int // The byte position of the rune in the original string.
+ width int // The screen width of the rune.
+ mod bool // Modifier or zero-width-joiner.
+ }
+
+ // We use the following:
+ // len(text) >= number of runes in text.
+
+ // Put all runes into a runePos slice in reverse.
+ runesReverse := make([]runePos, len(text))
+ index := len(text) - 1
+ for pos, ch := range text {
+ runesReverse[index].r = ch
+ runesReverse[index].pos = pos
+ runesReverse[index].width = runewidth.RuneWidth(ch)
+ runesReverse[index].mod = unicode.In(ch, unicode.Lm, unicode.M) || ch == '\u200d'
+ index--
+ }
+ runesReverse = runesReverse[index+1:]
+
+ // Parse reverse runes.
+ var screenWidth int
+ buffer := make([]rune, len(text)) // We fill this up from the back so it's forward again.
+ bufferPos := len(text)
+ stringWidth := runewidth.StringWidth(text)
+ for index, r := range runesReverse {
+ // Put this rune into the buffer.
+ bufferPos--
+ buffer[bufferPos] = r.r
+
+ // Do we need to flush the buffer?
+ if r.pos == 0 || !r.mod && runesReverse[index+1].r != '\u200d' {
+ // Yes, invoke callback.
+ var comb []rune
+ if len(text)-bufferPos > 1 {
+ comb = buffer[bufferPos+1:]
+ }
+ if callback(r.r, comb, r.pos, len(text)-r.pos, stringWidth-screenWidth, r.width) {
+ return true
+ }
+ screenWidth += r.width
+ bufferPos = len(text)
+ }
+ }
+
+ return false
+}