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