forked from tslocum/cview
This commit is contained in:
parent
96473a04c6
commit
911fb9543e
5 changed files with 365 additions and 93 deletions
|
@ -64,6 +64,9 @@ 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.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)
|
||||
|
|
237
ansii.go
Normal file
237
ansii.go
Normal 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()
|
||||
}
|
8
doc.go
8
doc.go
|
@ -85,8 +85,8 @@ tag is as follows:
|
|||
|
||||
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. (If the flags field is indicated
|
||||
by a colon but left empty, it will reset any flags.)
|
||||
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):
|
||||
|
@ -104,7 +104,9 @@ Examples:
|
|||
[:red]Red background, text color unchanged
|
||||
[yellow::u]Yellow text underlined
|
||||
[::bl]Bold, blinking text
|
||||
[::]Colors unchanged, flags reset
|
||||
[::-]Colors unchanged, flags reset
|
||||
[-]Reset foreground color
|
||||
[-:-:-]Reset everything
|
||||
[:]No effect
|
||||
[]Not a valid color tag, will print square brackets as they are
|
||||
|
||||
|
|
59
textview.go
59
textview.go
|
@ -18,13 +18,14 @@ var TabSize = 4
|
|||
// textViewIndex contains information about each line displayed in the text
|
||||
// view.
|
||||
type textViewIndex struct {
|
||||
Line int // The index into the "buffer" variable.
|
||||
Pos int // The index into the "buffer" string (byte position).
|
||||
NextPos int // The (byte) index of the next character in this buffer line.
|
||||
Width int // The screen width of this line.
|
||||
Style tcell.Style // The starting style.
|
||||
OverwriteAttr bool // The starting flag indicating if style attributes should be overwritten.
|
||||
Region string // The starting region ID.
|
||||
Line int // The index into the "buffer" variable.
|
||||
Pos int // The index into the "buffer" string (byte position).
|
||||
NextPos int // The (byte) index of the next character in this buffer line.
|
||||
Width int // The screen width of this line.
|
||||
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.
|
||||
}
|
||||
|
||||
// TextView is a box which displays text. It implements the io.Writer interface
|
||||
|
@ -500,8 +501,7 @@ func (t *TextView) reindexBuffer(width int) {
|
|||
|
||||
// Initial states.
|
||||
regionID := ""
|
||||
var highlighted, overwriteAttr bool
|
||||
style := tcell.StyleDefault.Foreground(t.textColor)
|
||||
var highlighted bool
|
||||
|
||||
// Go through each line in the buffer.
|
||||
for bufferIndex, str := range t.buffer {
|
||||
|
@ -558,14 +558,18 @@ func (t *TextView) reindexBuffer(width int) {
|
|||
}
|
||||
|
||||
// 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 {
|
||||
line := &textViewIndex{
|
||||
Line: bufferIndex,
|
||||
Pos: originalPos,
|
||||
Style: style,
|
||||
OverwriteAttr: overwriteAttr,
|
||||
Region: regionID,
|
||||
Line: bufferIndex,
|
||||
Pos: originalPos,
|
||||
ForegroundColor: foregroundColor,
|
||||
BackgroundColor: backgroundColor,
|
||||
Attributes: attributes,
|
||||
Region: regionID,
|
||||
}
|
||||
|
||||
// Shift original position with tags.
|
||||
|
@ -574,7 +578,7 @@ func (t *TextView) reindexBuffer(width int) {
|
|||
if colorPos < len(colorTagIndices) && colorTagIndices[colorPos][0] <= originalPos+lineLength {
|
||||
// Process color tags.
|
||||
originalPos += colorTagIndices[colorPos][1] - colorTagIndices[colorPos][0]
|
||||
style, overwriteAttr = styleFromTag(style, overwriteAttr, colorTags[colorPos])
|
||||
foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colorTags[colorPos])
|
||||
colorPos++
|
||||
} else if regionPos < len(regionIndices) && regionIndices[regionPos][0] <= originalPos+lineLength {
|
||||
// Process region tags.
|
||||
|
@ -712,6 +716,7 @@ func (t *TextView) Draw(screen tcell.Screen) {
|
|||
}
|
||||
|
||||
// Draw the buffer.
|
||||
defaultStyle := tcell.StyleDefault.Foreground(t.textColor)
|
||||
for line := t.lineOffset; line < len(t.index); line++ {
|
||||
// Are we done?
|
||||
if line-t.lineOffset >= height {
|
||||
|
@ -721,8 +726,9 @@ func (t *TextView) Draw(screen tcell.Screen) {
|
|||
// Get the text for this line.
|
||||
index := t.index[line]
|
||||
text := t.buffer[index.Line][index.Pos:index.NextPos]
|
||||
style := index.Style
|
||||
overwriteAttr := index.OverwriteAttr
|
||||
foregroundColor := index.ForegroundColor
|
||||
backgroundColor := index.BackgroundColor
|
||||
attributes := index.Attributes
|
||||
regionID := index.Region
|
||||
|
||||
// Get color tags.
|
||||
|
@ -768,7 +774,7 @@ func (t *TextView) Draw(screen tcell.Screen) {
|
|||
// Get the color.
|
||||
if currentTag < len(colorTags) && pos >= colorTagIndices[currentTag][0] && pos < colorTagIndices[currentTag][1] {
|
||||
if pos == colorTagIndices[currentTag][1]-1 {
|
||||
style, overwriteAttr = styleFromTag(style, overwriteAttr, colorTags[currentTag])
|
||||
foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colorTags[currentTag])
|
||||
currentTag++
|
||||
}
|
||||
continue
|
||||
|
@ -809,8 +815,12 @@ func (t *TextView) Draw(screen tcell.Screen) {
|
|||
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?
|
||||
finalStyle := style
|
||||
var highlighted bool
|
||||
if len(regionID) > 0 {
|
||||
if _, ok := t.highlights[regionID]; ok {
|
||||
|
@ -818,7 +828,7 @@ func (t *TextView) Draw(screen tcell.Screen) {
|
|||
}
|
||||
}
|
||||
if highlighted {
|
||||
fg, bg, _ := finalStyle.Decompose()
|
||||
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}
|
||||
|
@ -829,15 +839,12 @@ func (t *TextView) Draw(screen tcell.Screen) {
|
|||
bg = tcell.ColorBlack
|
||||
}
|
||||
}
|
||||
finalStyle = style.Background(fg).Foreground(bg)
|
||||
} else {
|
||||
_, _, existingStyle, _ := screen.GetContent(x+posX, y+line-t.lineOffset)
|
||||
finalStyle = overlayStyle(existingStyle, style, overwriteAttr)
|
||||
style = style.Background(fg).Foreground(bg)
|
||||
}
|
||||
|
||||
// Draw the character.
|
||||
for offset := 0; offset < chWidth; offset++ {
|
||||
screen.SetContent(x+posX+offset, y+line-t.lineOffset, ch, nil, finalStyle)
|
||||
screen.SetContent(x+posX+offset, y+line-t.lineOffset, ch, nil, style)
|
||||
}
|
||||
|
||||
// Advance.
|
||||
|
|
151
util.go
151
util.go
|
@ -1,6 +1,7 @@
|
|||
package tview
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
@ -104,7 +105,7 @@ var joints = map[string]rune{
|
|||
|
||||
// Common regular expressions.
|
||||
var (
|
||||
colorPattern = regexp.MustCompile(`\[([a-zA-Z]+|#[0-9a-zA-Z]{6})?(:([a-zA-Z]+|#[0-9a-zA-Z]{6})?(:([lbdru]+))?)?\]`)
|
||||
colorPattern = regexp.MustCompile(`\[([a-zA-Z]+|#[0-9a-zA-Z]{6}|\-)?(:([a-zA-Z]+|#[0-9a-zA-Z]{6}|\-)?(:([lbdru]+|\-)?)?)?\]`)
|
||||
regionPattern = regexp.MustCompile(`\["([a-zA-Z0-9_,;: \-\.]*)"\]`)
|
||||
escapePattern = regexp.MustCompile(`\[([a-zA-Z0-9_,;: \-\."#]+)\[(\[*)\]`)
|
||||
nonEscapePattern = regexp.MustCompile(`(\[[a-zA-Z0-9_,;: \-\."#]+\[*)\]`)
|
||||
|
@ -158,35 +159,72 @@ func init() {
|
|||
}
|
||||
}
|
||||
|
||||
// styleFromTag takes the given style and modifies it based on the substrings
|
||||
// extracted by the regular expression for color tags. The new style is returned
|
||||
// as well as the flag indicating if any style attributes were explicitly
|
||||
// specified (whose original value is also returned).
|
||||
func styleFromTag(style tcell.Style, overwriteAttr bool, tagSubstrings []string) (tcell.Style, bool) {
|
||||
// Colors.
|
||||
// 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 == "" {
|
||||
style = style.Foreground(tcell.ColorDefault)
|
||||
} else {
|
||||
style = style.Foreground(tcell.GetColor(color))
|
||||
}
|
||||
}
|
||||
if tagSubstrings[colorBackgroundPos-1] != "" {
|
||||
color := tagSubstrings[colorBackgroundPos]
|
||||
if color == "" {
|
||||
style = style.Background(tcell.ColorDefault)
|
||||
} else {
|
||||
style = style.Background(tcell.GetColor(color))
|
||||
if color == "-" {
|
||||
fgColor = "-"
|
||||
} else if color != "" {
|
||||
fgColor = color
|
||||
}
|
||||
}
|
||||
|
||||
// Flags.
|
||||
specified := tagSubstrings[colorFlagPos-1] != ""
|
||||
if specified {
|
||||
overwriteAttr = true
|
||||
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 tagSubstrings[colorFlagPos] {
|
||||
for _, flag := range attributes {
|
||||
switch flag {
|
||||
case 'l':
|
||||
style = style.Blink(true)
|
||||
|
@ -201,26 +239,7 @@ func styleFromTag(style tcell.Style, overwriteAttr bool, tagSubstrings []string)
|
|||
}
|
||||
}
|
||||
}
|
||||
return style, overwriteAttr
|
||||
}
|
||||
|
||||
// overlayStyle mixes a bottom and a top style and returns the result. Top
|
||||
// colors (other than tcell.ColorDefault) overwrite bottom colors. Top
|
||||
// style attributes overwrite bottom style attributes only if overwriteAttr is
|
||||
// true.
|
||||
func overlayStyle(bottom, top tcell.Style, overwriteAttr bool) tcell.Style {
|
||||
style := bottom
|
||||
fg, bg, attr := top.Decompose()
|
||||
if bg != tcell.ColorDefault {
|
||||
style = style.Background(bg)
|
||||
}
|
||||
if fg != tcell.ColorDefault {
|
||||
style = style.Foreground(fg)
|
||||
}
|
||||
if overwriteAttr {
|
||||
style = style.Normal()
|
||||
style |= tcell.Style(attr)
|
||||
}
|
||||
return style
|
||||
}
|
||||
|
||||
|
@ -272,13 +291,12 @@ func decomposeString(text string) (colorIndices [][]int, colors [][]string, esca
|
|||
// Returns the number of actual runes printed (not 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), false)
|
||||
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. The overwriteAttr indicates whether or not a style's
|
||||
// additional attributes (see tcell.AttrMask) should be overwritten.
|
||||
func printWithStyle(screen tcell.Screen, text string, x, y, maxWidth, align int, style tcell.Style, overwriteAttr bool) (int, int) {
|
||||
// foreground color.
|
||||
func printWithStyle(screen tcell.Screen, text string, x, y, maxWidth, align int, style tcell.Style) (int, int) {
|
||||
if maxWidth < 0 {
|
||||
return 0, 0
|
||||
}
|
||||
|
@ -289,17 +307,20 @@ func printWithStyle(screen tcell.Screen, text string, x, y, maxWidth, align int,
|
|||
// We deal with runes, not with bytes.
|
||||
runes := []rune(strippedText)
|
||||
|
||||
// This helper function takes positions for a substring of "runes" and a start
|
||||
// style and returns the substring with the original tags and the new start
|
||||
// style.
|
||||
substring := func(from, to int, style tcell.Style, overwriteAttr bool) (string, tcell.Style, bool) {
|
||||
var colorPos, escapePos, runePos, startPos int
|
||||
// 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 {
|
||||
var (
|
||||
colorPos, escapePos, runePos, startPos int
|
||||
foregroundColor, backgroundColor, attributes string
|
||||
)
|
||||
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 {
|
||||
style, overwriteAttr = styleFromTag(style, overwriteAttr, colors[colorPos])
|
||||
foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colors[colorPos])
|
||||
}
|
||||
colorPos++
|
||||
}
|
||||
|
@ -319,13 +340,13 @@ func printWithStyle(screen tcell.Screen, text string, x, y, maxWidth, align int,
|
|||
if runePos == from {
|
||||
startPos = pos
|
||||
} else if runePos >= to {
|
||||
return text[startPos:pos], style, overwriteAttr
|
||||
return fmt.Sprintf(`[%s:%s:%s]%s`, foregroundColor, backgroundColor, attributes, text[startPos:pos])
|
||||
}
|
||||
|
||||
runePos++
|
||||
}
|
||||
|
||||
return text[startPos:], style, overwriteAttr
|
||||
return fmt.Sprintf(`[%s:%s:%s]%s`, foregroundColor, backgroundColor, attributes, text[startPos:])
|
||||
}
|
||||
|
||||
// We want to reduce everything to AlignLeft.
|
||||
|
@ -340,17 +361,16 @@ func printWithStyle(screen tcell.Screen, text string, x, y, maxWidth, align int,
|
|||
width += w
|
||||
start = index
|
||||
}
|
||||
text, style, overwriteAttr = substring(start, len(runes), style, overwriteAttr)
|
||||
return printWithStyle(screen, text, x+maxWidth-width, y, width, AlignLeft, style, overwriteAttr)
|
||||
return printWithStyle(screen, substring(start, len(runes)), x+maxWidth-width, y, width, AlignLeft, style)
|
||||
} else if align == AlignCenter {
|
||||
width := runewidth.StringWidth(strippedText)
|
||||
if width == maxWidth {
|
||||
// Use the exact space.
|
||||
return printWithStyle(screen, text, x, y, maxWidth, AlignLeft, style, overwriteAttr)
|
||||
return printWithStyle(screen, text, x, y, maxWidth, AlignLeft, style)
|
||||
} else if width < maxWidth {
|
||||
// We have more space than we need.
|
||||
half := (maxWidth - width) / 2
|
||||
return printWithStyle(screen, text, x+half, y, maxWidth-half, AlignLeft, style, overwriteAttr)
|
||||
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
|
||||
|
@ -366,20 +386,22 @@ func printWithStyle(screen tcell.Screen, text string, x, y, maxWidth, align int,
|
|||
rightIndex--
|
||||
}
|
||||
}
|
||||
text, style, overwriteAttr = substring(leftIndex, rightIndex, style, overwriteAttr)
|
||||
return printWithStyle(screen, text, x, y, maxWidth, AlignLeft, style, overwriteAttr)
|
||||
return printWithStyle(screen, substring(leftIndex, rightIndex), x, y, maxWidth, AlignLeft, style)
|
||||
}
|
||||
}
|
||||
|
||||
// Draw text.
|
||||
drawn := 0
|
||||
drawnWidth := 0
|
||||
var colorPos, escapePos int
|
||||
var (
|
||||
colorPos, escapePos int
|
||||
foregroundColor, backgroundColor, attributes string
|
||||
)
|
||||
for pos, ch := range text {
|
||||
// Handle color tags.
|
||||
if colorPos < len(colorIndices) && pos >= colorIndices[colorPos][0] && pos < colorIndices[colorPos][1] {
|
||||
if pos == colorIndices[colorPos][1]-1 {
|
||||
style, overwriteAttr = styleFromTag(style, overwriteAttr, colors[colorPos])
|
||||
foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colors[colorPos])
|
||||
colorPos++
|
||||
}
|
||||
continue
|
||||
|
@ -403,7 +425,8 @@ func printWithStyle(screen tcell.Screen, text string, x, y, maxWidth, align int,
|
|||
|
||||
// Print the rune.
|
||||
_, _, finalStyle, _ := screen.GetContent(finalX, y)
|
||||
finalStyle = overlayStyle(finalStyle, style, overwriteAttr)
|
||||
_, background, _ := finalStyle.Decompose()
|
||||
finalStyle = overlayStyle(background, style, foregroundColor, backgroundColor, attributes)
|
||||
for offset := 0; offset < chWidth; offset++ {
|
||||
// To avoid undesired effects, we place the same character in all cells.
|
||||
screen.SetContent(finalX+offset, y, ch, nil, finalStyle)
|
||||
|
|
Loading…
Reference in a new issue