diff options
Diffstat (limited to 'vendor')
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. + [](https://travis-ci.org/atotto/clipboard) [](https://drone.io/github.com/atotto/clipboard/latest) [](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 < + // at one end and ∳ at the other. There + // are also sometimes numbers at the end, although this isn't inherent + // in the specification; there are never numbers anywhere else in + // current character references, though; see ¾ and ▒, etc. + // https://www.w3.org/TR/html5/syntax.html#named-character-references + // + // entity := "&" (named group | number ref) ";" + // named group := [a-zA-Z]{2,31}[0-9]{0,2} + // number ref := "#" (dec ref | hex ref) + // dec ref := [0-9]{1,7} + // hex ref := ("x" | "X") [0-9a-fA-F]{1,6} + htmlEntityRe = regexp.MustCompile(`&([a-zA-Z]{2,31}[0-9]{0,2}|#([0-9]{1,7}|[xX][0-9a-fA-F]{1,6}));`) ) // Functions to parse text within a block 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 -[](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 +[](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 Binary files differdeleted file mode 100644 index 24333c4..0000000 --- a/vendor/maunium.net/go/tcell/tcell.png +++ /dev/null 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 +} |