Update dependencies

This commit is contained in:
Tulir Asokan 2018-04-30 10:55:37 +03:00
parent 576bab9e2e
commit e48ff5bea4
17 changed files with 737 additions and 112 deletions

2
Gopkg.lock generated
View File

@ -139,7 +139,7 @@
branch = "master" branch = "master"
name = "maunium.net/go/tview" name = "maunium.net/go/tview"
packages = ["."] packages = ["."]
revision = "6146b7fe2331e23a78e217016705bc6801bfc55a" revision = "7eabba90a261a481d36ace89daa79c56582238d7"
[solve-meta] [solve-meta]
analyzer-name = "dep" analyzer-name = "dep"

View File

@ -64,6 +64,11 @@ Add your issue here on GitHub. Feel free to get in touch if you have any questio
(There are no corresponding tags in the project. I only keep such a history in this README.) (There are no corresponding tags in the project. I only keep such a history in this README.)
- v0.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.
- v0.13 (2018-04-01)
- Added background colors and text attributes to color tags.
- v0.12 (2018-03-13) - v0.12 (2018-03-13)
- Added "suspended mode" to `Application`. - Added "suspended mode" to `Application`.
- v0.11 (2018-03-02) - v0.11 (2018-03-02)

237
vendor/maunium.net/go/tview/ansii.go generated vendored Normal file
View File

@ -0,0 +1,237 @@
package tview
import (
"bytes"
"fmt"
"io"
"strconv"
"strings"
)
// The states of the ANSII escape code parser.
const (
ansiiText = iota
ansiiEscape
ansiiSubstring
ansiiControlSequence
)
// ansii is a io.Writer which translates ANSII escape codes into tview color
// tags.
type ansii 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.
state int
}
// ANSIIWriter returns an io.Writer which translates any ANSII 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{
Writer: writer,
buffer: new(bytes.Buffer),
csiParameter: new(bytes.Buffer),
csiIntermediate: new(bytes.Buffer),
state: ansiiText,
}
}
// Write parses the given text as a string of runes, translates ANSII escape
// codes to color tags and writes them to the output writer.
func (a *ansii) Write(text []byte) (int, error) {
defer func() {
a.buffer.Reset()
}()
for _, r := range string(text) {
switch a.state {
// We just entered an escape sequence.
case ansiiEscape:
switch r {
case '[': // Control Sequence Introducer.
a.csiParameter.Reset()
a.csiIntermediate.Reset()
a.state = ansiiControlSequence
case 'c': // Reset.
fmt.Fprint(a.buffer, "[-:-:-]")
a.state = ansiiText
case 'P', ']', 'X', '^', '_': // Substrings and commands.
a.state = ansiiSubstring
default: // Ignore.
a.state = ansiiText
}
// CSI Sequences.
case ansiiControlSequence:
switch {
case r >= 0x30 && r <= 0x3f: // Parameter bytes.
if _, err := a.csiParameter.WriteRune(r); err != nil {
return 0, err
}
case r >= 0x20 && r <= 0x2f: // Intermediate bytes.
if _, err := a.csiIntermediate.WriteRune(r); err != nil {
return 0, err
}
case r >= 0x40 && r <= 0x7e: // Final byte.
switch r {
case 'E': // Next line.
count, _ := strconv.Atoi(a.csiParameter.String())
if count == 0 {
count = 1
}
fmt.Fprint(a.buffer, strings.Repeat("\n", count))
case 'm': // Select Graphic Rendition.
var (
background, foreground, attributes string
clearAttributes bool
)
fields := strings.Split(a.csiParameter.String(), ";")
if len(fields) == 0 || len(fields) == 1 && fields[0] == "0" {
// Reset.
if _, err := a.buffer.WriteString("[-:-:-]"); err != nil {
return 0, err
}
break
}
lookupColor := func(colorNumber int, bright bool) string {
if colorNumber < 0 || colorNumber > 7 {
return "black"
}
if bright {
colorNumber += 8
}
return [...]string{
"black",
"red",
"green",
"yellow",
"blue",
"darkmagenta",
"darkcyan",
"white",
"#7f7f7f",
"#ff0000",
"#00ff00",
"#ffff00",
"#5c5cff",
"#ff00ff",
"#00ffff",
"#ffffff",
}[colorNumber]
}
for index, field := range fields {
switch field {
case "1", "01":
attributes += "b"
case "2", "02":
attributes += "d"
case "4", "04":
attributes += "u"
case "5", "05":
attributes += "l"
case "7", "07":
attributes += "7"
case "22", "24", "25", "27":
clearAttributes = true
case "30", "31", "32", "33", "34", "35", "36", "37":
colorNumber, _ := strconv.Atoi(field)
foreground = lookupColor(colorNumber-30, false)
case "40", "41", "42", "43", "44", "45", "46", "47":
colorNumber, _ := strconv.Atoi(field)
background = lookupColor(colorNumber-40, false)
case "90", "91", "92", "93", "94", "95", "96", "97":
colorNumber, _ := strconv.Atoi(field)
foreground = lookupColor(colorNumber-90, true)
case "100", "101", "102", "103", "104", "105", "106", "107":
colorNumber, _ := strconv.Atoi(field)
background = lookupColor(colorNumber-100, true)
case "38", "48":
var color string
if len(fields) > index+1 {
if fields[index+1] == "5" && len(fields) > index+2 { // 8-bit colors.
colorNumber, _ := strconv.Atoi(fields[index+2])
if colorNumber <= 7 {
color = lookupColor(colorNumber, false)
} else if colorNumber <= 15 {
color = lookupColor(colorNumber, true)
} else if colorNumber <= 231 {
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)
} else if colorNumber <= 255 {
grey := 255 * (colorNumber - 232) / 23
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)
}
}
if len(color) > 0 {
if field == "38" {
foreground = color
} else {
background = color
}
}
}
}
if len(attributes) > 0 || clearAttributes {
attributes = ":" + attributes
}
if len(foreground) > 0 || len(background) > 0 || len(attributes) > 0 {
fmt.Fprintf(a.buffer, "[%s:%s%s]", foreground, background, attributes)
}
}
a.state = ansiiText
default: // Undefined byte.
a.state = ansiiText // Abort CSI.
}
// We just entered a substring/command sequence.
case ansiiSubstring:
if r == 27 { // Most likely the end of the substring.
a.state = ansiiEscape
} // Ignore all other characters.
// "ansiiText" and all others.
default:
if r == 27 {
// This is the start of an escape sequence.
a.state = ansiiEscape
} else {
// Just a regular rune. Send to buffer.
if _, err := a.buffer.WriteRune(r); err != nil {
return 0, err
}
}
}
}
// Write buffer to target writer.
n, err := a.buffer.WriteTo(a.Writer)
if err != nil {
return int(n), err
}
return len(text), nil
}
// TranslateANSII replaces ANSII escape sequences found in the provided string
// with tview's color tags and returns the resulting string.
func TranslateANSII(text string) string {
var buffer bytes.Buffer
writer := ANSIIWriter(&buffer)
writer.Write([]byte(text))
return buffer.String()
}

View File

@ -38,6 +38,8 @@ type Application struct {
// be forwarded). // be forwarded).
mouseCapture func(event *tcell.EventMouse) *tcell.EventMouse mouseCapture func(event *tcell.EventMouse) *tcell.EventMouse
pasteCapture func(event *tcell.EventPaste) *tcell.EventPaste
// An optional callback function which is invoked just before the root // An optional callback function which is invoked just before the root
// primitive is drawn. // primitive is drawn.
beforeDraw func(screen tcell.Screen) bool beforeDraw func(screen tcell.Screen) bool
@ -190,6 +192,24 @@ func (a *Application) Run() error {
//a.Draw() //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: case *tcell.EventResize:
a.Lock() a.Lock()
screen := a.screen screen := a.screen

32
vendor/maunium.net/go/tview/box.go generated vendored
View File

@ -62,6 +62,8 @@ type Box struct {
// nothing should be forwarded). // nothing should be forwarded).
mouseCapture func(event *tcell.EventMouse) *tcell.EventMouse mouseCapture func(event *tcell.EventMouse) *tcell.EventMouse
pasteCapture func(event *tcell.EventPaste) *tcell.EventPaste
// An optional function which is called before the box is drawn. // An optional function which is called before the box is drawn.
draw func(screen tcell.Screen, x, y, width, height int) (int, int, int, int) draw func(screen tcell.Screen, x, y, width, height int) (int, int, int, int)
} }
@ -218,6 +220,36 @@ func (b *Box) GetMouseCapture() func(event *tcell.EventMouse) *tcell.EventMouse
return b.mouseCapture return b.mouseCapture
} }
func (b *Box) WrapPasteHandler(pasteHandler func(*tcell.EventPaste)) func(*tcell.EventPaste) {
return func(event *tcell.EventPaste) {
if b.pasteCapture != nil {
event = b.pasteCapture(event)
}
if event != nil && pasteHandler != nil {
pasteHandler(event)
}
}
}
func (b *Box) PasteHandler() func(event *tcell.EventPaste) {
return b.WrapPasteHandler(func(event *tcell.EventPaste) {
// Default paste handler just calls input handler with each character.
inputHandler := b.InputHandler()
for _, char := range event.Text() {
inputHandler(tcell.NewEventKey(tcell.KeyRune, char, tcell.ModNone), nil)
}
})
}
func (b *Box) SetPasteCapture(capture func(event *tcell.EventPaste) *tcell.EventPaste) *Box {
b.pasteCapture = capture
return b
}
func (b *Box) GetPasteCapture() func(event *tcell.EventPaste) *tcell.EventPaste {
return b.pasteCapture
}
// SetBackgroundColor sets the box's background color. // SetBackgroundColor sets the box's background color.
func (b *Box) SetBackgroundColor(color tcell.Color) *Box { func (b *Box) SetBackgroundColor(color tcell.Color) *Box {
b.backgroundColor = color b.backgroundColor = color

View File

@ -17,6 +17,10 @@ type Checkbox struct {
// The text to be displayed before the input area. // The text to be displayed before the input area.
label string label string
// The screen width of the label area. A value of 0 means use the width of
// the label text.
labelWidth int
// The label color. // The label color.
labelColor tcell.Color labelColor tcell.Color
@ -34,6 +38,10 @@ type Checkbox struct {
// are done entering text. The key which was pressed is provided (tab, // are done entering text. The key which was pressed is provided (tab,
// shift-tab, or escape). // shift-tab, or escape).
done func(tcell.Key) done func(tcell.Key)
// A callback function set by the Form class and called when the user leaves
// this form item.
finished func(tcell.Key)
} }
// NewCheckbox returns a new input field. // NewCheckbox returns a new input field.
@ -68,6 +76,13 @@ func (c *Checkbox) GetLabel() string {
return c.label return c.label
} }
// SetLabelWidth sets the screen width of the label. A value of 0 will cause the
// primitive to use the width of the label string.
func (c *Checkbox) SetLabelWidth(width int) *Checkbox {
c.labelWidth = width
return c
}
// SetLabelColor sets the color of the label. // SetLabelColor sets the color of the label.
func (c *Checkbox) SetLabelColor(color tcell.Color) *Checkbox { func (c *Checkbox) SetLabelColor(color tcell.Color) *Checkbox {
c.labelColor = color c.labelColor = color
@ -87,8 +102,8 @@ func (c *Checkbox) SetFieldTextColor(color tcell.Color) *Checkbox {
} }
// SetFormAttributes sets attributes shared by all form items. // SetFormAttributes sets attributes shared by all form items.
func (c *Checkbox) SetFormAttributes(label string, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem { func (c *Checkbox) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem {
c.label = label c.labelWidth = labelWidth
c.labelColor = labelColor c.labelColor = labelColor
c.backgroundColor = bgColor c.backgroundColor = bgColor
c.fieldTextColor = fieldTextColor c.fieldTextColor = fieldTextColor
@ -121,9 +136,10 @@ func (c *Checkbox) SetDoneFunc(handler func(key tcell.Key)) *Checkbox {
return c return c
} }
// SetFinishedFunc calls SetDoneFunc(). // SetFinishedFunc sets a callback invoked when the user leaves this form item.
func (c *Checkbox) SetFinishedFunc(handler func(key tcell.Key)) FormItem { func (c *Checkbox) SetFinishedFunc(handler func(key tcell.Key)) FormItem {
return c.SetDoneFunc(handler) c.finished = handler
return c
} }
// Draw draws this primitive onto the screen. // Draw draws this primitive onto the screen.
@ -138,8 +154,17 @@ func (c *Checkbox) Draw(screen tcell.Screen) {
} }
// Draw label. // Draw label.
if c.labelWidth > 0 {
labelWidth := c.labelWidth
if labelWidth > rightLimit-x {
labelWidth = rightLimit - x
}
Print(screen, c.label, x, y, labelWidth, AlignLeft, c.labelColor)
x += labelWidth
} else {
_, drawnWidth := Print(screen, c.label, x, y, rightLimit-x, AlignLeft, c.labelColor) _, drawnWidth := Print(screen, c.label, x, y, rightLimit-x, AlignLeft, c.labelColor)
x += drawnWidth x += drawnWidth
}
// Draw checkbox. // Draw checkbox.
fieldStyle := tcell.StyleDefault.Background(c.fieldBackgroundColor).Foreground(c.fieldTextColor) fieldStyle := tcell.StyleDefault.Background(c.fieldBackgroundColor).Foreground(c.fieldTextColor)
@ -170,6 +195,9 @@ func (c *Checkbox) InputHandler() func(event *tcell.EventKey, setFocus func(p Pr
if c.done != nil { if c.done != nil {
c.done(key) c.done(key)
} }
if c.finished != nil {
c.finished(key)
}
} }
}) })
} }

42
vendor/maunium.net/go/tview/doc.go generated vendored
View File

@ -77,13 +77,53 @@ applies to almost everything from box titles, list text, form item labels, to
table cells. In a TextView, this functionality has to be switched on explicitly. table cells. In a TextView, this functionality has to be switched on explicitly.
See the TextView documentation for more information. See the TextView documentation for more information.
Color tags may contain not just the foreground (text) color but also the
background color and additional flags. In fact, the full definition of a color
tag is as follows:
[<foreground>:<background>:<flags>]
Each of the three fields can be left blank and trailing fields can be ommitted.
(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".
You can specify the following flags (some flags may not be supported by your
terminal):
l: blink
b: bold
d: dim
r: reverse (switch foreground and background color)
u: underline
Examples:
[yellow]Yellow text
[yellow:red]Yellow text on red background
[:red]Red background, text color unchanged
[yellow::u]Yellow text underlined
[::bl]Bold, blinking text
[::-]Colors unchanged, flags reset
[-]Reset foreground color
[-:-:-]Reset everything
[:]No effect
[]Not a valid color tag, will print square brackets as they are
In the rare event that you want to display a string such as "[red]" or In the rare event that you want to display a string such as "[red]" or
"[#00ff1a]" without applying its effect, you need to put an opening square "[#00ff1a]" without applying its effect, you need to put an opening square
bracket before the closing square bracket. Examples: bracket before the closing square bracket. Note that the text inside the
brackets will be matched less strictly than region or colors tags. I.e. any
character that may be used in color or region tags will be recognized. Examples:
[red[] will be output as [red] [red[] will be output as [red]
["123"[] will be output as ["123"] ["123"[] will be output as ["123"]
[#6aff00[[] will be output as [#6aff00[] [#6aff00[[] will be output as [#6aff00[]
[a#"[[[] will be output as [a#"[[]
[] will be output as [] (see color tags above)
[[] will be output as [[] (not an escaped tag)
You can use the Escape() function to insert brackets automatically where needed.
Styles Styles

View File

@ -51,6 +51,10 @@ type DropDown struct {
// The color for prefixes. // The color for prefixes.
prefixTextColor tcell.Color prefixTextColor tcell.Color
// The screen width of the label area. A value of 0 means use the width of
// the label text.
labelWidth int
// The screen width of the input area. A value of 0 means extend as much as // The screen width of the input area. A value of 0 means extend as much as
// possible. // possible.
fieldWidth int fieldWidth int
@ -59,6 +63,10 @@ type DropDown struct {
// are done selecting options. The key which was pressed is provided (tab, // are done selecting options. The key which was pressed is provided (tab,
// shift-tab, or escape). // shift-tab, or escape).
done func(tcell.Key) done func(tcell.Key)
// A callback function set by the Form class and called when the user leaves
// this form item.
finished func(tcell.Key)
} }
// NewDropDown returns a new drop-down. // NewDropDown returns a new drop-down.
@ -113,6 +121,13 @@ func (d *DropDown) GetLabel() string {
return d.label return d.label
} }
// SetLabelWidth sets the screen width of the label. A value of 0 will cause the
// primitive to use the width of the label string.
func (d *DropDown) SetLabelWidth(width int) *DropDown {
d.labelWidth = width
return d
}
// SetLabelColor sets the color of the label. // SetLabelColor sets the color of the label.
func (d *DropDown) SetLabelColor(color tcell.Color) *DropDown { func (d *DropDown) SetLabelColor(color tcell.Color) *DropDown {
d.labelColor = color d.labelColor = color
@ -140,8 +155,8 @@ func (d *DropDown) SetPrefixTextColor(color tcell.Color) *DropDown {
} }
// SetFormAttributes sets attributes shared by all form items. // SetFormAttributes sets attributes shared by all form items.
func (d *DropDown) SetFormAttributes(label string, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem { func (d *DropDown) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem {
d.label = label d.labelWidth = labelWidth
d.labelColor = labelColor d.labelColor = labelColor
d.backgroundColor = bgColor d.backgroundColor = bgColor
d.fieldTextColor = fieldTextColor d.fieldTextColor = fieldTextColor
@ -210,9 +225,10 @@ func (d *DropDown) SetDoneFunc(handler func(key tcell.Key)) *DropDown {
return d return d
} }
// SetFinishedFunc calls SetDoneFunc(). // SetFinishedFunc sets a callback invoked when the user leaves this form item.
func (d *DropDown) SetFinishedFunc(handler func(key tcell.Key)) FormItem { func (d *DropDown) SetFinishedFunc(handler func(key tcell.Key)) FormItem {
return d.SetDoneFunc(handler) d.finished = handler
return d
} }
// Draw draws this primitive onto the screen. // Draw draws this primitive onto the screen.
@ -227,8 +243,17 @@ func (d *DropDown) Draw(screen tcell.Screen) {
} }
// Draw label. // Draw label.
if d.labelWidth > 0 {
labelWidth := d.labelWidth
if labelWidth > rightLimit-x {
labelWidth = rightLimit - x
}
Print(screen, d.label, x, y, labelWidth, AlignLeft, d.labelColor)
x += labelWidth
} else {
_, drawnWidth := Print(screen, d.label, x, y, rightLimit-x, AlignLeft, d.labelColor) _, drawnWidth := Print(screen, d.label, x, y, rightLimit-x, AlignLeft, d.labelColor)
x += drawnWidth x += drawnWidth
}
// What's the longest option text? // What's the longest option text?
maxWidth := 0 maxWidth := 0
@ -359,6 +384,9 @@ func (d *DropDown) InputHandler() func(event *tcell.EventKey, setFocus func(p Pr
if d.done != nil { if d.done != nil {
d.done(key) d.done(key)
} }
if d.finished != nil {
d.finished(key)
}
} }
}) })
} }

View File

@ -69,8 +69,8 @@ func (f *Flex) SetFullScreen(fullScreen bool) *Flex {
// that its size is flexible and may be changed. The "proportion" argument // that its size is flexible and may be changed. The "proportion" argument
// defines the relative size of the item compared to other flexible-size items. // defines the relative size of the item compared to other flexible-size items.
// For example, items with a proportion of 2 will be twice as large as items // For example, items with a proportion of 2 will be twice as large as items
// with a proportion of 1. Must be at least 1 if fixedSize > 0 (ignored // with a proportion of 1. The proportion must be at least 1 if fixedSize == 0
// otherwise) // (ignored otherwise).
// //
// If "focus" is set to true, the item will receive focus when the Flex // If "focus" is set to true, the item will receive focus when the Flex
// primitive receives focus. If multiple items have the "focus" flag set to // primitive receives focus. If multiple items have the "focus" flag set to

36
vendor/maunium.net/go/tview/form.go generated vendored
View File

@ -1,8 +1,6 @@
package tview package tview
import ( import (
"strings"
"maunium.net/go/tcell" "maunium.net/go/tcell"
) )
@ -20,7 +18,7 @@ type FormItem interface {
GetLabel() string GetLabel() string
// SetFormAttributes sets a number of item attributes at once. // SetFormAttributes sets a number of item attributes at once.
SetFormAttributes(label string, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem
// GetFieldWidth returns the width of the form item's field (the area which // GetFieldWidth returns the width of the form item's field (the area which
// is manipulated by the user) in number of screen cells. A value of 0 // is manipulated by the user) in number of screen cells. A value of 0
@ -233,7 +231,14 @@ func (f *Form) Clear(includeButtons bool) *Form {
// AddFormItem adds a new item to the form. This can be used to add your own // AddFormItem adds a new item to the form. This can be used to add your own
// objects to the form. Note, however, that the Form class will override some // objects to the form. Note, however, that the Form class will override some
// of its attributes to make it work in the form context. // of its attributes to make it work in the form context. Specifically, these
// are:
//
// - The label width
// - The label color
// - The background color
// - The field text color
// - The field background color
func (f *Form) AddFormItem(item FormItem) *Form { func (f *Form) AddFormItem(item FormItem) *Form {
f.items = append(f.items, item) f.items = append(f.items, item)
return f return f
@ -246,6 +251,18 @@ func (f *Form) GetFormItem(index int) FormItem {
return f.items[index] return f.items[index]
} }
// 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.
func (f *Form) GetFormItemByLabel(label string) FormItem {
for _, item := range f.items {
if item.GetLabel() == label {
return item
}
}
return nil
}
// SetCancelFunc sets a handler which is called when the user hits the Escape // SetCancelFunc sets a handler which is called when the user hits the Escape
// key. // key.
func (f *Form) SetCancelFunc(callback func()) *Form { func (f *Form) SetCancelFunc(callback func()) *Form {
@ -267,8 +284,7 @@ func (f *Form) Draw(screen tcell.Screen) {
// Find the longest label. // Find the longest label.
var maxLabelWidth int var maxLabelWidth int
for _, item := range f.items { for _, item := range f.items {
label := strings.TrimSpace(item.GetLabel()) labelWidth := StringWidth(item.GetLabel())
labelWidth := StringWidth(label)
if labelWidth > maxLabelWidth { if labelWidth > maxLabelWidth {
maxLabelWidth = labelWidth maxLabelWidth = labelWidth
} }
@ -280,20 +296,18 @@ func (f *Form) Draw(screen tcell.Screen) {
var focusedPosition struct{ x, y, width, height int } var focusedPosition struct{ x, y, width, height int }
for index, item := range f.items { for index, item := range f.items {
// Calculate the space needed. // Calculate the space needed.
label := strings.TrimSpace(item.GetLabel()) labelWidth := StringWidth(item.GetLabel())
labelWidth := StringWidth(label)
var itemWidth int var itemWidth int
if f.horizontal { if f.horizontal {
fieldWidth := item.GetFieldWidth() fieldWidth := item.GetFieldWidth()
if fieldWidth == 0 { if fieldWidth == 0 {
fieldWidth = DefaultFormFieldWidth fieldWidth = DefaultFormFieldWidth
} }
label += " "
labelWidth++ labelWidth++
itemWidth = labelWidth + fieldWidth itemWidth = labelWidth + fieldWidth
} else { } else {
// We want all fields to align vertically. // We want all fields to align vertically.
label += strings.Repeat(" ", maxLabelWidth-labelWidth) labelWidth = maxLabelWidth
itemWidth = width itemWidth = width
} }
@ -308,7 +322,7 @@ func (f *Form) Draw(screen tcell.Screen) {
itemWidth = rightLimit - x itemWidth = rightLimit - x
} }
item.SetFormAttributes( item.SetFormAttributes(
label, labelWidth,
f.labelColor, f.labelColor,
f.backgroundColor, f.backgroundColor,
f.fieldTextColor, f.fieldTextColor,

View File

@ -258,7 +258,7 @@ func (g *Grid) HasFocus() bool {
return true return true
} }
} }
return false return g.hasFocus
} }
// InputHandler returns the handler for this primitive. // InputHandler returns the handler for this primitive.

View File

@ -41,6 +41,10 @@ type InputField struct {
// The text color of the placeholder. // The text color of the placeholder.
placeholderTextColor tcell.Color placeholderTextColor tcell.Color
// The screen width of the label area. A value of 0 means use the width of
// the label text.
labelWidth int
// The screen width of the input area. A value of 0 means extend as much as // The screen width of the input area. A value of 0 means extend as much as
// possible. // possible.
fieldWidth int fieldWidth int
@ -59,6 +63,10 @@ type InputField struct {
// are done entering text. The key which was pressed is provided (tab, // are done entering text. The key which was pressed is provided (tab,
// shift-tab, enter, or escape). // shift-tab, enter, or escape).
done func(tcell.Key) done func(tcell.Key)
// A callback function set by the Form class and called when the user leaves
// this form item.
finished func(tcell.Key)
} }
// NewInputField returns a new input field. // NewInputField returns a new input field.
@ -97,6 +105,13 @@ func (i *InputField) GetLabel() string {
return i.label return i.label
} }
// SetLabelWidth sets the screen width of the label. A value of 0 will cause the
// primitive to use the width of the label string.
func (i *InputField) SetLabelWidth(width int) *InputField {
i.labelWidth = width
return i
}
// SetPlaceholder sets the text to be displayed when the input text is empty. // SetPlaceholder sets the text to be displayed when the input text is empty.
func (i *InputField) SetPlaceholder(text string) *InputField { func (i *InputField) SetPlaceholder(text string) *InputField {
i.placeholder = text i.placeholder = text
@ -121,15 +136,15 @@ func (i *InputField) SetFieldTextColor(color tcell.Color) *InputField {
return i return i
} }
// SetPlaceholderExtColor sets the text color of placeholder text. // SetPlaceholderTextColor sets the text color of placeholder text.
func (i *InputField) SetPlaceholderExtColor(color tcell.Color) *InputField { func (i *InputField) SetPlaceholderTextColor(color tcell.Color) *InputField {
i.placeholderTextColor = color i.placeholderTextColor = color
return i return i
} }
// SetFormAttributes sets attributes shared by all form items. // SetFormAttributes sets attributes shared by all form items.
func (i *InputField) SetFormAttributes(label string, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem { func (i *InputField) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem {
i.label = label i.labelWidth = labelWidth
i.labelColor = labelColor i.labelColor = labelColor
i.backgroundColor = bgColor i.backgroundColor = bgColor
i.fieldTextColor = fieldTextColor i.fieldTextColor = fieldTextColor
@ -186,9 +201,10 @@ func (i *InputField) SetDoneFunc(handler func(key tcell.Key)) *InputField {
return i return i
} }
// SetFinishedFunc calls SetDoneFunc(). // SetFinishedFunc sets a callback invoked when the user leaves this form item.
func (i *InputField) SetFinishedFunc(handler func(key tcell.Key)) FormItem { func (i *InputField) SetFinishedFunc(handler func(key tcell.Key)) FormItem {
return i.SetDoneFunc(handler) i.finished = handler
return i
} }
// Draw draws this primitive onto the screen. // Draw draws this primitive onto the screen.
@ -203,8 +219,17 @@ func (i *InputField) Draw(screen tcell.Screen) {
} }
// Draw label. // Draw label.
if i.labelWidth > 0 {
labelWidth := i.labelWidth
if labelWidth > rightLimit-x {
labelWidth = rightLimit - x
}
Print(screen, i.label, x, y, labelWidth, AlignLeft, i.labelColor)
x += labelWidth
} else {
_, drawnWidth := Print(screen, i.label, x, y, rightLimit-x, AlignLeft, i.labelColor) _, drawnWidth := Print(screen, i.label, x, y, rightLimit-x, AlignLeft, i.labelColor)
x += drawnWidth x += drawnWidth
}
// Draw input area. // Draw input area.
fieldWidth := i.fieldWidth fieldWidth := i.fieldWidth
@ -280,7 +305,11 @@ func (i *InputField) setCursor(screen tcell.Screen) {
if i.fieldWidth > 0 && fieldWidth > i.fieldWidth-1 { if i.fieldWidth > 0 && fieldWidth > i.fieldWidth-1 {
fieldWidth = i.fieldWidth - 1 fieldWidth = i.fieldWidth - 1
} }
if i.labelWidth > 0 {
x += i.labelWidth + fieldWidth
} else {
x += StringWidth(i.label) + fieldWidth x += StringWidth(i.label) + fieldWidth
}
if x >= rightLimit { if x >= rightLimit {
x = rightLimit - 1 x = rightLimit - 1
} }
@ -323,6 +352,9 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
if i.done != nil { if i.done != nil {
i.done(key) i.done(key)
} }
if i.finished != nil {
i.finished(key)
}
} }
}) })
} }

20
vendor/maunium.net/go/tview/list.go generated vendored
View File

@ -173,6 +173,26 @@ func (l *List) AddItem(mainText, secondaryText string, shortcut rune, selected f
return l return l
} }
// GetItemCount returns the number of items in the list.
func (l *List) GetItemCount() int {
return len(l.items)
}
// GetItemText returns an item's texts (main and secondary). Panics if the index
// is out of range.
func (l *List) GetItemText(index int) (main, secondary string) {
return l.items[index].MainText, l.items[index].SecondaryText
}
// SetItemText sets an item's main and secondary text. Panics if the index is
// out of range.
func (l *List) SetItemText(index int, main, secondary string) *List {
item := l.items[index]
item.MainText = main
item.SecondaryText = secondary
return l
}
// Clear removes all items from the list. // Clear removes all items from the list.
func (l *List) Clear() *List { func (l *List) Clear() *List {
l.items = nil l.items = nil

View File

@ -36,6 +36,8 @@ type Primitive interface {
MouseHandler() func(event *tcell.EventMouse, setFocus func(p Primitive)) MouseHandler() func(event *tcell.EventMouse, setFocus func(p Primitive))
PasteHandler() func(event *tcell.EventPaste)
// Focus is called by the application when the primitive receives focus. // Focus is called by the application when the primitive receives focus.
// Implementers may call delegate() to pass the focus on to another primitive. // Implementers may call delegate() to pass the focus on to another primitive.
Focus(delegate func(p Primitive)) Focus(delegate func(p Primitive))

View File

@ -590,7 +590,7 @@ ColumnLoop:
expansion := 0 expansion := 0
for _, row := range rows { for _, row := range rows {
if cell := getCell(row, column); cell != nil { if cell := getCell(row, column); cell != nil {
cellWidth := StringWidth(cell.Text) _, _, _, _, cellWidth := decomposeString(cell.Text)
if cell.MaxWidth > 0 && cell.MaxWidth < cellWidth { if cell.MaxWidth > 0 && cell.MaxWidth < cellWidth {
cellWidth = cell.MaxWidth cellWidth = cell.MaxWidth
} }

View File

@ -8,6 +8,7 @@ import (
"unicode/utf8" "unicode/utf8"
"maunium.net/go/tcell" "maunium.net/go/tcell"
"github.com/lucasb-eyer/go-colorful"
runewidth "github.com/mattn/go-runewidth" runewidth "github.com/mattn/go-runewidth"
) )
@ -21,7 +22,9 @@ type textViewIndex struct {
Pos int // The index into the "buffer" string (byte position). Pos int // The index into the "buffer" string (byte position).
NextPos int // The (byte) index of the next character in this buffer line. NextPos int // The (byte) index of the next character in this buffer line.
Width int // The screen width of this line. Width int // The screen width of this line.
Color tcell.Color // The starting color. ForegroundColor string // The starting foreground color ("" = don't change, "-" = reset).
BackgroundColor string // The starting background color ("" = don't change, "-" = reset).
Attributes string // The starting attributes ("" = don't change, "-" = reset).
Region string // The starting region ID. Region string // The starting region ID.
} }
@ -499,7 +502,6 @@ func (t *TextView) reindexBuffer(width int) {
// Initial states. // Initial states.
regionID := "" regionID := ""
var highlighted bool var highlighted bool
color := t.textColor
// Go through each line in the buffer. // Go through each line in the buffer.
for bufferIndex, str := range t.buffer { for bufferIndex, str := range t.buffer {
@ -507,11 +509,10 @@ func (t *TextView) reindexBuffer(width int) {
var ( var (
colorTagIndices [][]int colorTagIndices [][]int
colorTags [][]string colorTags [][]string
escapeIndices [][]int
) )
if t.dynamicColors { if t.dynamicColors {
colorTagIndices = colorPattern.FindAllStringIndex(str, -1) colorTagIndices, colorTags, escapeIndices, str, _ = decomposeString(str)
colorTags = colorPattern.FindAllStringSubmatch(str, -1)
str = colorPattern.ReplaceAllString(str, "")
} }
// Find all regions in this line. Then remove them. // Find all regions in this line. Then remove them.
@ -523,14 +524,12 @@ func (t *TextView) reindexBuffer(width int) {
regionIndices = regionPattern.FindAllStringIndex(str, -1) regionIndices = regionPattern.FindAllStringIndex(str, -1)
regions = regionPattern.FindAllStringSubmatch(str, -1) regions = regionPattern.FindAllStringSubmatch(str, -1)
str = regionPattern.ReplaceAllString(str, "") str = regionPattern.ReplaceAllString(str, "")
} if !t.dynamicColors {
// We haven't detected escape tags yet. Do it now.
// Find all replace tags in this line. Then replace them.
var escapeIndices [][]int
if t.dynamicColors || t.regions {
escapeIndices = escapePattern.FindAllStringIndex(str, -1) escapeIndices = escapePattern.FindAllStringIndex(str, -1)
str = escapePattern.ReplaceAllString(str, "[$1$2]") str = escapePattern.ReplaceAllString(str, "[$1$2]")
} }
}
// Split the line if required. // Split the line if required.
var splitLines []string var splitLines []string
@ -559,12 +558,17 @@ func (t *TextView) reindexBuffer(width int) {
} }
// Create index from split lines. // Create index from split lines.
var originalPos, colorPos, regionPos, escapePos int var (
originalPos, colorPos, regionPos, escapePos int
foregroundColor, backgroundColor, attributes string
)
for _, splitLine := range splitLines { for _, splitLine := range splitLines {
line := &textViewIndex{ line := &textViewIndex{
Line: bufferIndex, Line: bufferIndex,
Pos: originalPos, Pos: originalPos,
Color: color, ForegroundColor: foregroundColor,
BackgroundColor: backgroundColor,
Attributes: attributes,
Region: regionID, Region: regionID,
} }
@ -574,7 +578,7 @@ func (t *TextView) reindexBuffer(width int) {
if colorPos < len(colorTagIndices) && colorTagIndices[colorPos][0] <= originalPos+lineLength { if colorPos < len(colorTagIndices) && colorTagIndices[colorPos][0] <= originalPos+lineLength {
// Process color tags. // Process color tags.
originalPos += colorTagIndices[colorPos][1] - colorTagIndices[colorPos][0] originalPos += colorTagIndices[colorPos][1] - colorTagIndices[colorPos][0]
color = tcell.GetColor(colorTags[colorPos][1]) foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colorTags[colorPos])
colorPos++ colorPos++
} else if regionPos < len(regionIndices) && regionIndices[regionPos][0] <= originalPos+lineLength { } else if regionPos < len(regionIndices) && regionIndices[regionPos][0] <= originalPos+lineLength {
// Process region tags. // Process region tags.
@ -712,6 +716,7 @@ func (t *TextView) Draw(screen tcell.Screen) {
} }
// Draw the buffer. // Draw the buffer.
defaultStyle := tcell.StyleDefault.Foreground(t.textColor)
for line := t.lineOffset; line < len(t.index); line++ { for line := t.lineOffset; line < len(t.index); line++ {
// Are we done? // Are we done?
if line-t.lineOffset >= height { if line-t.lineOffset >= height {
@ -721,17 +726,19 @@ func (t *TextView) Draw(screen tcell.Screen) {
// Get the text for this line. // Get the text for this line.
index := t.index[line] index := t.index[line]
text := t.buffer[index.Line][index.Pos:index.NextPos] text := t.buffer[index.Line][index.Pos:index.NextPos]
color := index.Color foregroundColor := index.ForegroundColor
backgroundColor := index.BackgroundColor
attributes := index.Attributes
regionID := index.Region regionID := index.Region
// Get color tags. // Get color tags.
var ( var (
colorTagIndices [][]int colorTagIndices [][]int
colorTags [][]string colorTags [][]string
escapeIndices [][]int
) )
if t.dynamicColors { if t.dynamicColors {
colorTagIndices = colorPattern.FindAllStringIndex(text, -1) colorTagIndices, colorTags, escapeIndices, _, _ = decomposeString(text)
colorTags = colorPattern.FindAllStringSubmatch(text, -1)
} }
// Get regions. // Get regions.
@ -742,13 +749,10 @@ func (t *TextView) Draw(screen tcell.Screen) {
if t.regions { if t.regions {
regionIndices = regionPattern.FindAllStringIndex(text, -1) regionIndices = regionPattern.FindAllStringIndex(text, -1)
regions = regionPattern.FindAllStringSubmatch(text, -1) regions = regionPattern.FindAllStringSubmatch(text, -1)
} if !t.dynamicColors {
// Get escape tags.
var escapeIndices [][]int
if t.dynamicColors || t.regions {
escapeIndices = escapePattern.FindAllStringIndex(text, -1) escapeIndices = escapePattern.FindAllStringIndex(text, -1)
} }
}
// Calculate the position of the line. // Calculate the position of the line.
var skip, posX int var skip, posX int
@ -770,7 +774,7 @@ func (t *TextView) Draw(screen tcell.Screen) {
// Get the color. // Get the color.
if currentTag < len(colorTags) && pos >= colorTagIndices[currentTag][0] && pos < colorTagIndices[currentTag][1] { if currentTag < len(colorTags) && pos >= colorTagIndices[currentTag][0] && pos < colorTagIndices[currentTag][1] {
if pos == colorTagIndices[currentTag][1]-1 { if pos == colorTagIndices[currentTag][1]-1 {
color = tcell.GetColor(colorTags[currentTag][1]) foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colorTags[currentTag])
currentTag++ currentTag++
} }
continue continue
@ -811,13 +815,32 @@ func (t *TextView) Draw(screen tcell.Screen) {
break break
} }
// Mix the existing style with the new style.
_, _, existingStyle, _ := screen.GetContent(x+posX, y+line-t.lineOffset)
_, background, _ := existingStyle.Decompose()
style := overlayStyle(background, defaultStyle, foregroundColor, backgroundColor, attributes)
// Do we highlight this character? // Do we highlight this character?
style := tcell.StyleDefault.Background(t.backgroundColor).Foreground(color) var highlighted bool
if len(regionID) > 0 { if len(regionID) > 0 {
if _, ok := t.highlights[regionID]; ok { if _, ok := t.highlights[regionID]; ok {
style = tcell.StyleDefault.Background(color).Foreground(t.backgroundColor) highlighted = true
} }
} }
if highlighted {
fg, bg, _ := style.Decompose()
if bg == tcell.ColorDefault {
r, g, b := fg.RGB()
c := colorful.Color{R: float64(r) / 255, G: float64(g) / 255, B: float64(b) / 255}
_, _, li := c.Hcl()
if li < .5 {
bg = tcell.ColorWhite
} else {
bg = tcell.ColorBlack
}
}
style = style.Background(fg).Foreground(bg)
}
// Draw the character. // Draw the character.
for offset := 0; offset < chWidth; offset++ { for offset := 0; offset < chWidth; offset++ {

216
vendor/maunium.net/go/tview/util.go generated vendored
View File

@ -1,6 +1,7 @@
package tview package tview
import ( import (
"fmt"
"math" "math"
"regexp" "regexp"
"strconv" "strconv"
@ -104,13 +105,21 @@ var joints = map[string]rune{
// Common regular expressions. // Common regular expressions.
var ( var (
colorPattern = regexp.MustCompile(`\[([a-zA-Z]+|#[0-9a-zA-Z]{6})\]`) colorPattern = regexp.MustCompile(`\[([a-zA-Z]+|#[0-9a-zA-Z]{6}|\-)?(:([a-zA-Z]+|#[0-9a-zA-Z]{6}|\-)?(:([lbdru]+|\-)?)?)?\]`)
regionPattern = regexp.MustCompile(`\["([a-zA-Z0-9_,;: \-\.]*)"\]`) regionPattern = regexp.MustCompile(`\["([a-zA-Z0-9_,;: \-\.]*)"\]`)
escapePattern = regexp.MustCompile(`\[("[a-zA-Z0-9_,;: \-\.]*"|[a-zA-Z]+|#[0-9a-zA-Z]{6})\[(\[*)\]`) escapePattern = regexp.MustCompile(`\[([a-zA-Z0-9_,;: \-\."#]+)\[(\[*)\]`)
nonEscapePattern = regexp.MustCompile(`(\[[a-zA-Z0-9_,;: \-\."#]+\[*)\]`)
boundaryPattern = regexp.MustCompile("([[:punct:]]\\s*|\\s+)") boundaryPattern = regexp.MustCompile("([[:punct:]]\\s*|\\s+)")
spacePattern = regexp.MustCompile(`\s+`) spacePattern = regexp.MustCompile(`\s+`)
) )
// Positions of substrings in regular expressions.
const (
colorForegroundPos = 1
colorBackgroundPos = 3
colorFlagPos = 5
)
// Predefined InputField acceptance functions. // Predefined InputField acceptance functions.
var ( var (
// InputFieldInteger accepts integers. // InputFieldInteger accepts integers.
@ -150,40 +159,168 @@ func init() {
} }
} }
// styleFromTag takes the given style, defined by a foreground color (fgColor),
// a background color (bgColor), and style attributes, and modifies it based on
// the substrings (tagSubstrings) extracted by the regular expression for color
// tags. The new colors and attributes are returned where empty strings mean
// "don't modify" and a dash ("-") means "reset to default".
func styleFromTag(fgColor, bgColor, attributes string, tagSubstrings []string) (newFgColor, newBgColor, newAttributes string) {
if tagSubstrings[colorForegroundPos] != "" {
color := tagSubstrings[colorForegroundPos]
if color == "-" {
fgColor = "-"
} else if color != "" {
fgColor = color
}
}
if tagSubstrings[colorBackgroundPos-1] != "" {
color := tagSubstrings[colorBackgroundPos]
if color == "-" {
bgColor = "-"
} else if color != "" {
bgColor = color
}
}
if tagSubstrings[colorFlagPos-1] != "" {
flags := tagSubstrings[colorFlagPos]
if flags == "-" {
attributes = "-"
} else if flags != "" {
attributes = flags
}
}
return fgColor, bgColor, attributes
}
// overlayStyle mixes a background color with a foreground color (fgColor),
// a (possibly new) background color (bgColor), and style attributes, and
// returns the resulting style. For a definition of the colors and attributes,
// see styleFromTag(). Reset instructions cause the corresponding part of the
// default style to be used.
func overlayStyle(background tcell.Color, defaultStyle tcell.Style, fgColor, bgColor, attributes string) tcell.Style {
defFg, defBg, defAttr := defaultStyle.Decompose()
style := defaultStyle.Background(background)
if fgColor == "-" {
style = style.Foreground(defFg)
} else if fgColor != "" {
style = style.Foreground(tcell.GetColor(fgColor))
}
if bgColor == "-" {
style = style.Background(defBg)
} else if bgColor != "" {
style = style.Background(tcell.GetColor(bgColor))
}
if attributes == "-" {
style = style.Bold(defAttr&tcell.AttrBold > 0)
style = style.Blink(defAttr&tcell.AttrBlink > 0)
style = style.Reverse(defAttr&tcell.AttrReverse > 0)
style = style.Underline(defAttr&tcell.AttrUnderline > 0)
style = style.Dim(defAttr&tcell.AttrDim > 0)
} else if attributes != "" {
style = style.Normal()
for _, flag := range attributes {
switch flag {
case 'l':
style = style.Blink(true)
case 'b':
style = style.Bold(true)
case 'd':
style = style.Dim(true)
case 'r':
style = style.Reverse(true)
case 'u':
style = style.Underline(true)
}
}
}
return style
}
// decomposeString returns information about a string which may contain color
// tags. It returns the indices of the color tags (as returned by
// re.FindAllStringIndex()), the color tags themselves (as returned by
// re.FindAllStringSubmatch()), the indices of an escaped tags, the string
// stripped by any color tags and escaped, and the screen width of the stripped
// string.
func decomposeString(text string) (colorIndices [][]int, colors [][]string, escapeIndices [][]int, stripped string, width int) {
// Get positions of color and escape tags.
colorIndices = colorPattern.FindAllStringIndex(text, -1)
colors = colorPattern.FindAllStringSubmatch(text, -1)
escapeIndices = escapePattern.FindAllStringIndex(text, -1)
// Because the color pattern detects empty tags, we need to filter them out.
for i := len(colorIndices) - 1; i >= 0; i-- {
if colorIndices[i][1]-colorIndices[i][0] == 2 {
colorIndices = append(colorIndices[:i], colorIndices[i+1:]...)
colors = append(colors[:i], colors[i+1:]...)
}
}
// Remove the color tags from the original string.
var from int
buf := make([]byte, 0, len(text))
for _, indices := range colorIndices {
buf = append(buf, []byte(text[from:indices[0]])...)
from = indices[1]
}
buf = append(buf, text[from:]...)
// Escape string.
stripped = string(escapePattern.ReplaceAll(buf, []byte("[$1$2]")))
// Get the width of the stripped string.
width = runewidth.StringWidth(stripped)
return
}
// Print prints text onto the screen into the given box at (x,y,maxWidth,1), // Print prints text onto the screen into the given box at (x,y,maxWidth,1),
// not exceeding that box. "align" is one of AlignLeft, AlignCenter, or // not exceeding that box. "align" is one of AlignLeft, AlignCenter, or
// AlignRight. The screen's background color will not be changed. // AlignRight. The screen's background color will not be changed.
// //
// You can change the text color mid-text by inserting a color tag. See the // You can change the colors and text styles mid-text by inserting a color tag.
// package description for details. // See the package description for details.
// //
// Returns the number of actual runes printed (not including color tags) and the // Returns the number of actual runes printed (not including color tags) and the
// actual width used for the printed runes. // actual width used for the printed runes.
func Print(screen tcell.Screen, text string, x, y, maxWidth, align int, color tcell.Color) (int, int) { func Print(screen tcell.Screen, text string, x, y, maxWidth, align int, color tcell.Color) (int, int) {
return printWithStyle(screen, text, x, y, maxWidth, align, tcell.StyleDefault.Foreground(color))
}
// printWithStyle works like Print() but it takes a style instead of just a
// foreground color.
func printWithStyle(screen tcell.Screen, text string, x, y, maxWidth, align int, style tcell.Style) (int, int) {
if maxWidth < 0 { if maxWidth < 0 {
return 0, 0 return 0, 0
} }
// Get positions of color and escape tags. Remove them from original string. // Decompose the text.
colorIndices := colorPattern.FindAllStringIndex(text, -1) colorIndices, colors, escapeIndices, strippedText, _ := decomposeString(text)
colors := colorPattern.FindAllStringSubmatch(text, -1)
escapeIndices := escapePattern.FindAllStringIndex(text, -1)
strippedText := escapePattern.ReplaceAllString(colorPattern.ReplaceAllString(text, ""), "[$1$2]")
// We deal with runes, not with bytes. // We deal with runes, not with bytes.
runes := []rune(strippedText) runes := []rune(strippedText)
// This helper function takes positions for a substring of "runes" and a start // This helper function takes positions for a substring of "runes" and returns
// color and returns the substring with the original tags and the new start // a new string corresponding to this substring, making sure printing that
// color. // substring will observe color tags.
substring := func(from, to int, color tcell.Color) (string, tcell.Color) { substring := func(from, to int) string {
var colorPos, escapePos, runePos, startPos int var (
colorPos, escapePos, runePos, startPos int
foregroundColor, backgroundColor, attributes string
)
for pos := range text { for pos := range text {
// Handle color tags. // Handle color tags.
if colorPos < len(colorIndices) && pos >= colorIndices[colorPos][0] && pos < colorIndices[colorPos][1] { if colorPos < len(colorIndices) && pos >= colorIndices[colorPos][0] && pos < colorIndices[colorPos][1] {
if pos == colorIndices[colorPos][1]-1 { if pos == colorIndices[colorPos][1]-1 {
if runePos <= from { if runePos <= from {
color = tcell.GetColor(colors[colorPos][1]) foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colors[colorPos])
} }
colorPos++ colorPos++
} }
@ -203,13 +340,13 @@ func Print(screen tcell.Screen, text string, x, y, maxWidth, align int, color tc
if runePos == from { if runePos == from {
startPos = pos startPos = pos
} else if runePos >= to { } else if runePos >= to {
return text[startPos:pos], color return fmt.Sprintf(`[%s:%s:%s]%s`, foregroundColor, backgroundColor, attributes, text[startPos:pos])
} }
runePos++ runePos++
} }
return text[startPos:], color return fmt.Sprintf(`[%s:%s:%s]%s`, foregroundColor, backgroundColor, attributes, text[startPos:])
} }
// We want to reduce everything to AlignLeft. // We want to reduce everything to AlignLeft.
@ -224,17 +361,16 @@ func Print(screen tcell.Screen, text string, x, y, maxWidth, align int, color tc
width += w width += w
start = index start = index
} }
text, color = substring(start, len(runes), color) return printWithStyle(screen, substring(start, len(runes)), x+maxWidth-width, y, width, AlignLeft, style)
return Print(screen, text, x+maxWidth-width, y, width, AlignLeft, color)
} else if align == AlignCenter { } else if align == AlignCenter {
width := runewidth.StringWidth(strippedText) width := runewidth.StringWidth(strippedText)
if width == maxWidth { if width == maxWidth {
// Use the exact space. // Use the exact space.
return Print(screen, text, x, y, maxWidth, AlignLeft, color) return printWithStyle(screen, text, x, y, maxWidth, AlignLeft, style)
} else if width < maxWidth { } else if width < maxWidth {
// We have more space than we need. // We have more space than we need.
half := (maxWidth - width) / 2 half := (maxWidth - width) / 2
return Print(screen, text, x+half, y, maxWidth-half, AlignLeft, color) return printWithStyle(screen, text, x+half, y, maxWidth-half, AlignLeft, style)
} else { } else {
// Chop off runes until we have a perfect fit. // Chop off runes until we have a perfect fit.
var choppedLeft, choppedRight, leftIndex, rightIndex int var choppedLeft, choppedRight, leftIndex, rightIndex int
@ -250,20 +386,22 @@ func Print(screen tcell.Screen, text string, x, y, maxWidth, align int, color tc
rightIndex-- rightIndex--
} }
} }
text, color = substring(leftIndex, rightIndex, color) return printWithStyle(screen, substring(leftIndex, rightIndex), x, y, maxWidth, AlignLeft, style)
return Print(screen, text, x, y, maxWidth, AlignLeft, color)
} }
} }
// Draw text. // Draw text.
drawn := 0 drawn := 0
drawnWidth := 0 drawnWidth := 0
var colorPos, escapePos int var (
colorPos, escapePos int
foregroundColor, backgroundColor, attributes string
)
for pos, ch := range text { for pos, ch := range text {
// Handle color tags. // Handle color tags.
if colorPos < len(colorIndices) && pos >= colorIndices[colorPos][0] && pos < colorIndices[colorPos][1] { if colorPos < len(colorIndices) && pos >= colorIndices[colorPos][0] && pos < colorIndices[colorPos][1] {
if pos == colorIndices[colorPos][1]-1 { if pos == colorIndices[colorPos][1]-1 {
color = tcell.GetColor(colors[colorPos][1]) foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colors[colorPos])
colorPos++ colorPos++
} }
continue continue
@ -286,11 +424,12 @@ func Print(screen tcell.Screen, text string, x, y, maxWidth, align int, color tc
finalX := x + drawnWidth finalX := x + drawnWidth
// Print the rune. // Print the rune.
_, _, style, _ := screen.GetContent(finalX, y) _, _, finalStyle, _ := screen.GetContent(finalX, y)
style = style.Foreground(color) _, background, _ := finalStyle.Decompose()
finalStyle = overlayStyle(background, style, foregroundColor, backgroundColor, attributes)
for offset := 0; offset < chWidth; offset++ { for offset := 0; offset < chWidth; offset++ {
// To avoid undesired effects, we place the same character in all cells. // To avoid undesired effects, we place the same character in all cells.
screen.SetContent(finalX+offset, y, ch, nil, style) screen.SetContent(finalX+offset, y, ch, nil, finalStyle)
} }
drawn++ drawn++
@ -308,7 +447,8 @@ func PrintSimple(screen tcell.Screen, text string, x, y int) {
// StringWidth returns the width of the given string needed to print it on // StringWidth returns the width of the given string needed to print it on
// screen. The text may contain color tags which are not counted. // screen. The text may contain color tags which are not counted.
func StringWidth(text string) int { func StringWidth(text string) int {
return runewidth.StringWidth(escapePattern.ReplaceAllString(colorPattern.ReplaceAllString(text, ""), "[$1$2]")) _, _, _, _, width := decomposeString(text)
return width
} }
// WordWrap splits a text such that each resulting line does not exceed the // WordWrap splits a text such that each resulting line does not exceed the
@ -319,13 +459,7 @@ func StringWidth(text string) int {
// //
// Text is always split at newline characters ('\n'). // Text is always split at newline characters ('\n').
func WordWrap(text string, width int) (lines []string) { func WordWrap(text string, width int) (lines []string) {
// Strip color tags. colorTagIndices, _, escapeIndices, strippedText, _ := decomposeString(text)
strippedText := escapePattern.ReplaceAllString(colorPattern.ReplaceAllString(text, ""), "[$1$2]")
// Keep track of color tags and escape patterns so we can restore the original
// indices.
colorTagIndices := colorPattern.FindAllStringIndex(text, -1)
escapeIndices := escapePattern.FindAllStringIndex(text, -1)
// Find candidate breakpoints. // Find candidate breakpoints.
breakPoints := boundaryPattern.FindAllStringIndex(strippedText, -1) breakPoints := boundaryPattern.FindAllStringIndex(strippedText, -1)
@ -454,3 +588,13 @@ func PrintJoinedBorder(screen tcell.Screen, x, y int, ch rune, color tcell.Color
// We only print something if we have something. // We only print something if we have something.
screen.SetContent(x, y, result, nil, style) 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:
//
// box.SetTitle(tview.Escape("[squarebrackets]"))
// fmt.Fprint(textView, tview.Escape(`["quoted"]`))
func Escape(text string) string {
return nonEscapePattern.ReplaceAllString(text, "$1[]")
}