mirror of
https://github.com/rivo/tview.git
synced 2025-04-24 13:48:56 +08:00
Added color tag functionality to all strings. Resolves #25
This commit is contained in:
parent
13cf1c1ee4
commit
258f212e5e
@ -62,6 +62,8 @@ Add your issue here on GitHub. Feel free to get in touch if you have any questio
|
||||
|
||||
## Releases
|
||||
|
||||
- v0.8 (2018-01-17)
|
||||
- Color tags can now be used almost everywhere.
|
||||
- v0.7 (2018-01-16)
|
||||
- Forms can now also have a horizontal layout.
|
||||
- v0.6 (2018-01-14)
|
||||
|
22
box.go
22
box.go
@ -2,7 +2,6 @@ package tview
|
||||
|
||||
import (
|
||||
"github.com/gdamore/tcell"
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
// Box implements Primitive with a background and optional elements such as a
|
||||
@ -222,23 +221,12 @@ func (b *Box) Draw(screen tcell.Screen) {
|
||||
|
||||
// Draw title.
|
||||
if b.title != "" && b.width >= 4 {
|
||||
width := b.width - 2
|
||||
title := b.title
|
||||
titleWidth := runewidth.StringWidth(title)
|
||||
if width < titleWidth && width > 0 {
|
||||
// Grow title until we hit the end.
|
||||
abbrWidth := runewidth.RuneWidth(GraphicsEllipsis)
|
||||
abbrPos := 0
|
||||
for pos, ch := range title {
|
||||
if abbrWidth >= width {
|
||||
title = title[:abbrPos] + string(GraphicsEllipsis)
|
||||
break
|
||||
}
|
||||
abbrWidth += runewidth.RuneWidth(ch)
|
||||
abbrPos = pos
|
||||
}
|
||||
_, 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 {
|
||||
_, _, 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, title, b.x+1, b.y, width, b.titleAlign, b.titleColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package tview
|
||||
|
||||
import (
|
||||
"github.com/gdamore/tcell"
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
// Button is labeled box that triggers an action when selected.
|
||||
@ -34,7 +33,7 @@ type Button struct {
|
||||
// NewButton returns a new input field.
|
||||
func NewButton(label string) *Button {
|
||||
box := NewBox().SetBackgroundColor(Styles.ContrastBackgroundColor)
|
||||
box.SetRect(0, 0, runewidth.StringWidth(label)+4, 1)
|
||||
box.SetRect(0, 0, StringWidth(label)+4, 1)
|
||||
return &Button{
|
||||
Box: box,
|
||||
label: label,
|
||||
|
33
demos/presentation/colors.go
Normal file
33
demos/presentation/colors.go
Normal file
@ -0,0 +1,33 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
const colorsText = `You can use color tags almost everywhere to partially change the color of a string. Simply put a color name or hex string in square brackets to change the following characters' color. H[green]er[white]e i[yellow]s a[darkcyan]n ex[red]amp[white]le. The tags look like this: [red[] [#00ff00[]`
|
||||
|
||||
// Colors demonstrates how to use colors.
|
||||
func Colors(nextSlide func()) (title string, content tview.Primitive) {
|
||||
table := tview.NewTable().
|
||||
SetBorders(true).
|
||||
SetBordersColor(tcell.ColorBlue).
|
||||
SetDoneFunc(func(key tcell.Key) {
|
||||
nextSlide()
|
||||
})
|
||||
var row, column int
|
||||
for _, word := range strings.Split(colorsText, " ") {
|
||||
table.SetCellSimple(row, column, word)
|
||||
column++
|
||||
if column > 6 {
|
||||
column = 0
|
||||
row++
|
||||
}
|
||||
}
|
||||
table.SetBorderPadding(1, 1, 2, 2).
|
||||
SetBorder(true).
|
||||
SetTitle("A [red]c[yellow]o[green]l[darkcyan]o[blue]r[darkmagenta]f[red]u[yellow]l[white] title")
|
||||
return "Colors", Center(73, 19, table)
|
||||
}
|
@ -41,6 +41,7 @@ func main() {
|
||||
TextView2,
|
||||
Table,
|
||||
Flex,
|
||||
Colors,
|
||||
End,
|
||||
}
|
||||
|
||||
|
30
doc.go
30
doc.go
@ -59,6 +59,32 @@ You will find more demos in the "demos" subdirectory. It also contains a
|
||||
presentation (written using tview) which gives an overview of the different
|
||||
widgets and how they can be used.
|
||||
|
||||
Colors
|
||||
|
||||
Throughout this package, colors are specified using the tcell.Color type.
|
||||
Functions such as tcell.GetColor(), tcell.NewHexColor(), and tcell.NewRGBColor()
|
||||
can be used to create colors from W3C color names or RGB values.
|
||||
|
||||
Almost all strings which are displayed can contain color tags. Color tags are
|
||||
W3C color names or six hexadecimal digits following a hash tag, wrapped in
|
||||
square brackets. Examples:
|
||||
|
||||
This is a [red]warning[white]!
|
||||
The sky is [#8080ff]blue[#ffffff].
|
||||
|
||||
A color tag changes the color of the characters following that color tag. This
|
||||
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.
|
||||
See the TextView documentation for more information.
|
||||
|
||||
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
|
||||
bracket before the closing square bracket. Examples:
|
||||
|
||||
[red[] will be output as [red]
|
||||
["123"[] will be output as ["123"]
|
||||
[#6aff00[[] will be output as [#6aff00[]
|
||||
|
||||
Styles
|
||||
|
||||
When primitives are instantiated, they are initialized with colors taken from
|
||||
@ -77,8 +103,8 @@ therefore available for all widgets, too.
|
||||
All widgets also implement the Primitive interface. There is also the Focusable
|
||||
interface which is used to override functions in subclassing types.
|
||||
|
||||
The tview package is based on github.com/gdamore/tcell. It uses types and
|
||||
constants from that package (e.g. colors and keyboard values).
|
||||
The tview package is based on https://github.com/gdamore/tcell. It uses types
|
||||
and constants from that package (e.g. colors and keyboard values).
|
||||
|
||||
This package does not process mouse input (yet).
|
||||
*/
|
||||
|
@ -2,7 +2,6 @@ package tview
|
||||
|
||||
import (
|
||||
"github.com/gdamore/tcell"
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
// dropDownOption is one option that can be selected in a drop-down primitive.
|
||||
@ -145,7 +144,7 @@ func (d *DropDown) GetFieldLength() int {
|
||||
}
|
||||
fieldLength := 0
|
||||
for _, option := range d.options {
|
||||
length := runewidth.StringWidth(option.Text)
|
||||
length := StringWidth(option.Text)
|
||||
if length > fieldLength {
|
||||
fieldLength = length
|
||||
}
|
||||
@ -215,7 +214,7 @@ func (d *DropDown) Draw(screen tcell.Screen) {
|
||||
// What's the longest option text?
|
||||
maxLength := 0
|
||||
for _, option := range d.options {
|
||||
length := runewidth.StringWidth(option.Text)
|
||||
length := StringWidth(option.Text)
|
||||
if length > maxLength {
|
||||
maxLength = length
|
||||
}
|
||||
|
7
form.go
7
form.go
@ -4,7 +4,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
// DefaultFormFieldLength is the default field length of form elements whose
|
||||
@ -262,7 +261,7 @@ func (f *Form) Draw(screen tcell.Screen) {
|
||||
var labelLength int
|
||||
for _, item := range f.items {
|
||||
label := strings.TrimSpace(item.GetLabel())
|
||||
labelWidth := runewidth.StringWidth(label)
|
||||
labelWidth := StringWidth(label)
|
||||
if labelWidth > labelLength {
|
||||
labelLength = labelWidth
|
||||
}
|
||||
@ -278,7 +277,7 @@ func (f *Form) Draw(screen tcell.Screen) {
|
||||
|
||||
// Calculate the space needed.
|
||||
label := strings.TrimSpace(item.GetLabel())
|
||||
labelWidth := runewidth.StringWidth(label)
|
||||
labelWidth := StringWidth(label)
|
||||
var itemWidth int
|
||||
if f.horizontal {
|
||||
fieldLength := item.GetFieldLength()
|
||||
@ -331,7 +330,7 @@ func (f *Form) Draw(screen tcell.Screen) {
|
||||
buttonWidths := make([]int, len(f.buttons))
|
||||
buttonsWidth := 0
|
||||
for index, button := range f.buttons {
|
||||
width := runewidth.StringWidth(button.GetLabel()) + 4
|
||||
width := StringWidth(button.GetLabel()) + 4
|
||||
buttonWidths[index] = width
|
||||
buttonsWidth += width + 1
|
||||
}
|
||||
|
@ -188,15 +188,15 @@ func (i *InputField) Draw(screen tcell.Screen) {
|
||||
x += drawnWidth
|
||||
|
||||
// Draw input area.
|
||||
fieldLength := i.fieldLength
|
||||
if fieldLength == 0 {
|
||||
fieldLength = math.MaxInt32
|
||||
fieldWidth := i.fieldLength
|
||||
if fieldWidth == 0 {
|
||||
fieldWidth = math.MaxInt32
|
||||
}
|
||||
if rightLimit-x < fieldLength {
|
||||
fieldLength = rightLimit - x
|
||||
if rightLimit-x < fieldWidth {
|
||||
fieldWidth = rightLimit - x
|
||||
}
|
||||
fieldStyle := tcell.StyleDefault.Background(i.fieldBackgroundColor)
|
||||
for index := 0; index < fieldLength; index++ {
|
||||
for index := 0; index < fieldWidth; index++ {
|
||||
screen.SetContent(x+index, y, ' ', nil, fieldStyle)
|
||||
}
|
||||
|
||||
@ -205,11 +205,35 @@ func (i *InputField) Draw(screen tcell.Screen) {
|
||||
if i.maskCharacter > 0 {
|
||||
text = strings.Repeat(string(i.maskCharacter), utf8.RuneCountInString(i.text))
|
||||
}
|
||||
fieldLength-- // We need one cell for the cursor.
|
||||
if fieldLength < runewidth.StringWidth(i.text) {
|
||||
Print(screen, text, x, y, fieldLength, AlignRight, i.fieldTextColor)
|
||||
fieldWidth-- // We need one cell for the cursor.
|
||||
if fieldWidth < runewidth.StringWidth(i.text) {
|
||||
runes := []rune(i.text)
|
||||
for pos := len(runes) - 1; pos >= 0; pos-- {
|
||||
ch := runes[pos]
|
||||
w := runewidth.RuneWidth(ch)
|
||||
if fieldWidth-w < 0 {
|
||||
break
|
||||
}
|
||||
_, _, style, _ := screen.GetContent(x+fieldWidth-w, y)
|
||||
style = style.Foreground(i.fieldTextColor)
|
||||
for w > 0 {
|
||||
fieldWidth--
|
||||
screen.SetContent(x+fieldWidth, y, ch, nil, style)
|
||||
w--
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Print(screen, text, x, y, fieldLength, AlignLeft, i.fieldTextColor)
|
||||
pos := 0
|
||||
for _, ch := range text {
|
||||
w := runewidth.RuneWidth(ch)
|
||||
_, _, style, _ := screen.GetContent(x+pos, y)
|
||||
style = style.Foreground(i.fieldTextColor)
|
||||
for w > 0 {
|
||||
screen.SetContent(x+pos, y, ch, nil, style)
|
||||
pos++
|
||||
w--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set cursor.
|
||||
@ -232,7 +256,7 @@ func (i *InputField) setCursor(screen tcell.Screen) {
|
||||
if i.fieldLength > 0 && fieldLength > i.fieldLength-1 {
|
||||
fieldLength = i.fieldLength - 1
|
||||
}
|
||||
x += runewidth.StringWidth(i.label) + fieldLength
|
||||
x += StringWidth(i.label) + fieldLength
|
||||
if x >= rightLimit {
|
||||
x = rightLimit - 1
|
||||
}
|
||||
|
19
list.go
19
list.go
@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
// listItem represents one item in a List.
|
||||
@ -206,16 +205,22 @@ func (l *List) Draw(screen tcell.Screen) {
|
||||
}
|
||||
|
||||
// Main text.
|
||||
color := l.mainTextColor
|
||||
Print(screen, item.MainText, x, y, width, AlignLeft, l.mainTextColor)
|
||||
|
||||
// Background color of selected text.
|
||||
if index == l.currentItem {
|
||||
textLength := runewidth.StringWidth(item.MainText)
|
||||
style := tcell.StyleDefault.Background(l.selectedBackgroundColor)
|
||||
textLength := StringWidth(item.MainText)
|
||||
for bx := 0; bx < textLength && bx < width; bx++ {
|
||||
screen.SetContent(x+bx, y, ' ', nil, style)
|
||||
m, c, style, _ := screen.GetContent(x+bx, y)
|
||||
fg, _, _ := style.Decompose()
|
||||
if fg == l.mainTextColor {
|
||||
fg = l.selectedTextColor
|
||||
}
|
||||
style = style.Background(l.selectedBackgroundColor).Foreground(fg)
|
||||
screen.SetContent(x+bx, y, m, c, style)
|
||||
}
|
||||
color = l.selectedTextColor
|
||||
}
|
||||
Print(screen, item.MainText, x, y, width, AlignLeft, color)
|
||||
|
||||
y++
|
||||
|
||||
if y >= bottomLimit {
|
||||
|
3
modal.go
3
modal.go
@ -2,7 +2,6 @@ package tview
|
||||
|
||||
import (
|
||||
"github.com/gdamore/tcell"
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
// Modal is a centered message window used to inform the user or prompt them
|
||||
@ -102,7 +101,7 @@ func (m *Modal) Draw(screen tcell.Screen) {
|
||||
// Calculate the width of this modal.
|
||||
buttonsWidth := 0
|
||||
for _, button := range m.form.buttons {
|
||||
buttonsWidth += runewidth.StringWidth(button.label) + 4 + 2
|
||||
buttonsWidth += StringWidth(button.label) + 4 + 2
|
||||
}
|
||||
buttonsWidth -= 2
|
||||
screenWidth, screenHeight := screen.Size()
|
||||
|
79
table.go
79
table.go
@ -2,7 +2,6 @@ package tview
|
||||
|
||||
import (
|
||||
"github.com/gdamore/tcell"
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
// TableCell represents one cell inside a Table. You can instantiate this type
|
||||
@ -144,6 +143,8 @@ func (c *TableCell) GetLastPosition() (x, y, width int) {
|
||||
// rows and columns). When there is a selection, the user moves the selection.
|
||||
// The class will attempt to keep the selection from moving out of the screen.
|
||||
//
|
||||
// Use SetInputCapture() to override or modify keyboard input.
|
||||
//
|
||||
// See https://github.com/rivo/tview/wiki/Table for an example.
|
||||
type Table struct {
|
||||
*Box
|
||||
@ -550,7 +551,7 @@ ColumnLoop:
|
||||
maxWidth := -1
|
||||
for _, row := range rows {
|
||||
if cell := getCell(row, column); cell != nil {
|
||||
cellWidth := runewidth.StringWidth(cell.Text)
|
||||
cellWidth := StringWidth(cell.Text)
|
||||
if cell.MaxWidth > 0 && cell.MaxWidth < cellWidth {
|
||||
cellWidth = cell.MaxWidth
|
||||
}
|
||||
@ -628,59 +629,61 @@ ColumnLoop:
|
||||
continue
|
||||
}
|
||||
|
||||
// Determine cell colors.
|
||||
bgColor := t.backgroundColor
|
||||
textColor := cell.Color
|
||||
if cell.BackgroundColor != tcell.ColorDefault {
|
||||
bgColor = cell.BackgroundColor
|
||||
}
|
||||
if cellSelected && !cell.NotSelectable {
|
||||
textColor, bgColor = bgColor, textColor
|
||||
}
|
||||
|
||||
// Draw cell background.
|
||||
bgStyle := tcell.StyleDefault.Background(bgColor)
|
||||
// Draw text.
|
||||
finalWidth := columnWidth
|
||||
if columnX+1+columnWidth >= width {
|
||||
finalWidth = width - columnX - 1
|
||||
}
|
||||
for pos := 0; pos < finalWidth; pos++ {
|
||||
screen.SetContent(x+columnX+1+pos, y+rowY, ' ', nil, bgStyle)
|
||||
}
|
||||
cell.x, cell.y, cell.width = x+columnX+1, y+rowY, finalWidth
|
||||
_, printed := Print(screen, cell.Text, x+columnX+1, y+rowY, finalWidth, cell.Align, cell.Color)
|
||||
if StringWidth(cell.Text)-printed > 0 && printed > 0 {
|
||||
_, _, style, _ := screen.GetContent(x+columnX+1+finalWidth-1, y+rowY)
|
||||
fg, _, _ := style.Decompose()
|
||||
Print(screen, string(GraphicsEllipsis), x+columnX+1+finalWidth-1, y+rowY, 1, AlignLeft, fg)
|
||||
}
|
||||
|
||||
// Draw cell background.
|
||||
if cellSelected && !cell.NotSelectable || cell.BackgroundColor != tcell.ColorDefault {
|
||||
for pos := 0; pos < finalWidth; pos++ {
|
||||
m, c, style, _ := screen.GetContent(x+columnX+1+pos, y+rowY)
|
||||
if cellSelected && !cell.NotSelectable {
|
||||
// Create style for a selected cell.
|
||||
fg, _, _ := style.Decompose()
|
||||
if fg == cell.Color {
|
||||
fg = cell.BackgroundColor
|
||||
if fg == tcell.ColorDefault {
|
||||
fg = t.backgroundColor
|
||||
}
|
||||
}
|
||||
style = style.Background(cell.Color).Foreground(fg)
|
||||
} else {
|
||||
// Create style for a cell with a colored background.
|
||||
style = style.Background(cell.BackgroundColor)
|
||||
}
|
||||
screen.SetContent(x+columnX+1+pos, y+rowY, m, c, style)
|
||||
}
|
||||
}
|
||||
|
||||
// We may want continuous background colors in rows so change
|
||||
// border/separator background colors, too.
|
||||
if cell.BackgroundColor != tcell.ColorDefault && column > 0 {
|
||||
cellBackground := cell.BackgroundColor
|
||||
if cellSelected && !cell.NotSelectable {
|
||||
cellBackground = cell.Color
|
||||
}
|
||||
if cellBackground != tcell.ColorDefault && column > 0 {
|
||||
leftCell := getCell(row, column-1)
|
||||
if leftCell != nil {
|
||||
if cell.BackgroundColor == leftCell.BackgroundColor {
|
||||
_, _, style, _ := screen.GetContent(x+columnX+1, y+rowY)
|
||||
m, c, _, _ := screen.GetContent(x+columnX, y+rowY)
|
||||
_, bgColor, _ := style.Decompose()
|
||||
if t.columnsSelectable && column == t.selectedColumn {
|
||||
bgColor = cell.BackgroundColor
|
||||
}
|
||||
ch, _, style, _ := screen.GetContent(x+columnX, y+rowY)
|
||||
screen.SetContent(x+columnX, y+rowY, ch, nil, style.Background(bgColor))
|
||||
screen.SetContent(x+columnX, y+rowY, m, c, style.Background(bgColor).Foreground(t.bordersColor))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw text.
|
||||
text := cell.Text
|
||||
textWidth := runewidth.StringWidth(text)
|
||||
if finalWidth < textWidth && finalWidth > 0 {
|
||||
// Grow title until we hit the end.
|
||||
abbrWidth := runewidth.RuneWidth(GraphicsEllipsis)
|
||||
abbrPos := 0
|
||||
for pos, ch := range text {
|
||||
if abbrWidth >= finalWidth {
|
||||
text = text[:abbrPos] + string(GraphicsEllipsis)
|
||||
break
|
||||
}
|
||||
abbrWidth += runewidth.RuneWidth(ch)
|
||||
abbrPos = pos
|
||||
}
|
||||
}
|
||||
Print(screen, text, x+columnX+1, y+rowY, finalWidth, cell.Align, textColor)
|
||||
}
|
||||
|
||||
// Draw bottom border.
|
||||
|
86
textview.go
86
textview.go
@ -10,15 +10,6 @@ import (
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
// Regular expressions commonly used throughout the TextView class.
|
||||
var (
|
||||
colorPattern = regexp.MustCompile(`\[([a-zA-Z]+|#[0-9a-zA-Z]{6})\]`)
|
||||
regionPattern = regexp.MustCompile(`\["([a-zA-Z0-9_,;: \-\.]*)"\]`)
|
||||
escapePattern = regexp.MustCompile(`\[("[a-zA-Z0-9_,;: \-\.]*"|[a-zA-Z]+|#[0-9a-zA-Z]{6})\[(\[*)\]`)
|
||||
boundaryPattern = regexp.MustCompile("([[:punct:]]\\s*|\\s+)")
|
||||
spacePattern = regexp.MustCompile(`\s+`)
|
||||
)
|
||||
|
||||
// TabSize is the number of spaces with which a tab character will be replaced.
|
||||
var TabSize = 4
|
||||
|
||||
@ -55,21 +46,14 @@ type textViewIndex struct {
|
||||
// If the text is not scrollable, any text above the top visible line is
|
||||
// discarded.
|
||||
//
|
||||
// Navigation can be intercepted by installing a callback function via
|
||||
// SetCaptureFunc() which receives all keyboard events and decides which ones
|
||||
// to forward to the default handler.
|
||||
// Use SetInputCapture() to override or modify keyboard input.
|
||||
//
|
||||
// Colors
|
||||
//
|
||||
// If dynamic colors are enabled via SetDynamicColors(), text color can be
|
||||
// changed dynamically by embedding color strings in square brackets. For
|
||||
// example,
|
||||
//
|
||||
// This is a [red]warning[white]!
|
||||
//
|
||||
// will print the word "warning" in red. You can provide W3C color names or
|
||||
// hex strings starting with "#", followed by 6 hexadecimal digits. See
|
||||
// tcell.GetColor() for more information.
|
||||
// changed dynamically by embedding color strings in square brackets. This works
|
||||
// the same way as anywhere else. Please see the package documentation for more
|
||||
// information.
|
||||
//
|
||||
// Regions and Highlights
|
||||
//
|
||||
@ -92,17 +76,6 @@ type textViewIndex struct {
|
||||
// The ScrollToHighlight() function can be used to jump to the currently
|
||||
// highlighted region once when the text view is drawn the next time.
|
||||
//
|
||||
// Escape Tags
|
||||
//
|
||||
// In the rare case that you have color tags or regions enabled but still want
|
||||
// to output a tag as text instead of causing its functionality, you can close
|
||||
// the tag with an opening and closing bracket "[]" instead of only a closing
|
||||
// bracket "]". Examples:
|
||||
//
|
||||
// [red[] will be output as [red]
|
||||
// ["123"[] will be output as ["123"]
|
||||
// [#6aff00[[] will be output as [#6aff00[]
|
||||
//
|
||||
// See https://github.com/rivo/tview/wiki/TextView for an example.
|
||||
type TextView struct {
|
||||
sync.Mutex
|
||||
@ -174,10 +147,6 @@ type TextView struct {
|
||||
// highlight(s) into the visible screen.
|
||||
scrollToHighlights bool
|
||||
|
||||
// An optional function which will receive all key events sent to this text
|
||||
// view. Returning true also invokes the default key handling.
|
||||
capture func(*tcell.EventKey) bool
|
||||
|
||||
// An optional function which is called when the content of the text view has
|
||||
// changed.
|
||||
changed func()
|
||||
@ -273,15 +242,6 @@ func (t *TextView) SetRegions(regions bool) *TextView {
|
||||
return t
|
||||
}
|
||||
|
||||
// SetCaptureFunc sets a handler which is called whenever a key is pressed.
|
||||
// This allows you to override the default key handling of the text view.
|
||||
// Returning true will allow the default key handling to go forward after the
|
||||
// handler returns. Returning false will disable any default key handling.
|
||||
func (t *TextView) SetCaptureFunc(handler func(event *tcell.EventKey) bool) *TextView {
|
||||
t.capture = handler
|
||||
return t
|
||||
}
|
||||
|
||||
// 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.
|
||||
@ -600,20 +560,15 @@ func (t *TextView) reindexBuffer(width int) {
|
||||
}
|
||||
|
||||
// Shift original position with tags.
|
||||
lineWidth := 0
|
||||
for index, ch := range splitLine {
|
||||
// Get the width of the current rune.
|
||||
lineWidth += runewidth.RuneWidth(ch)
|
||||
|
||||
// Process color tags.
|
||||
for colorPos < len(colorTagIndices) && colorTagIndices[colorPos][0] <= originalPos+index {
|
||||
lineLength := len(splitLine)
|
||||
for {
|
||||
if colorPos < len(colorTagIndices) && colorTagIndices[colorPos][0] <= originalPos+lineLength {
|
||||
// Process color tags.
|
||||
originalPos += colorTagIndices[colorPos][1] - colorTagIndices[colorPos][0]
|
||||
color = tcell.GetColor(colorTags[colorPos][1])
|
||||
colorPos++
|
||||
}
|
||||
|
||||
// Process region tags.
|
||||
for regionPos < len(regionIndices) && regionIndices[regionPos][0] <= originalPos+index {
|
||||
} else if regionPos < len(regionIndices) && regionIndices[regionPos][0] <= originalPos+lineLength {
|
||||
// Process region tags.
|
||||
originalPos += regionIndices[regionPos][1] - regionIndices[regionPos][0]
|
||||
regionID = regions[regionPos][1]
|
||||
_, highlighted = t.highlights[regionID]
|
||||
@ -629,22 +584,22 @@ func (t *TextView) reindexBuffer(width int) {
|
||||
}
|
||||
|
||||
regionPos++
|
||||
}
|
||||
|
||||
// Process escape tags.
|
||||
for escapePos < len(escapeIndices) && escapeIndices[escapePos][0] <= originalPos+index {
|
||||
} else if escapePos < len(escapeIndices) && escapeIndices[escapePos][0] <= originalPos+lineLength {
|
||||
// Process escape tags.
|
||||
originalPos++
|
||||
escapePos++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Advance to next line.
|
||||
startPos += len(splitLine)
|
||||
originalPos += len(splitLine)
|
||||
startPos += lineLength
|
||||
originalPos += lineLength
|
||||
|
||||
// Append this line.
|
||||
line.NextPos = originalPos
|
||||
line.Width = lineWidth
|
||||
line.Width = runewidth.StringWidth(splitLine)
|
||||
t.index = append(t.index, line)
|
||||
}
|
||||
|
||||
@ -877,13 +832,6 @@ func (t *TextView) Draw(screen tcell.Screen) {
|
||||
// InputHandler returns the handler for this primitive.
|
||||
func (t *TextView) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
|
||||
return t.wrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
|
||||
// Do we pass this event on?
|
||||
if t.capture != nil {
|
||||
if !t.capture(event) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
key := event.Key()
|
||||
|
||||
if key == tcell.KeyEscape || key == tcell.KeyEnter || key == tcell.KeyTab || key == tcell.KeyBacktab {
|
||||
|
181
util.go
181
util.go
@ -2,6 +2,7 @@ package tview
|
||||
|
||||
import (
|
||||
"math"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@ -38,6 +39,16 @@ const (
|
||||
GraphicsEllipsis = '\u2026'
|
||||
)
|
||||
|
||||
// Common regular expressions.
|
||||
var (
|
||||
colorPattern = regexp.MustCompile(`\[([a-zA-Z]+|#[0-9a-zA-Z]{6})\]`)
|
||||
regionPattern = regexp.MustCompile(`\["([a-zA-Z0-9_,;: \-\.]*)"\]`)
|
||||
escapePattern = regexp.MustCompile(`\[("[a-zA-Z0-9_,;: \-\.]*"|[a-zA-Z]+|#[0-9a-zA-Z]{6})\[(\[*)\]`)
|
||||
boundaryPattern = regexp.MustCompile("([[:punct:]]\\s*|\\s+)")
|
||||
spacePattern = regexp.MustCompile(`\s+`)
|
||||
)
|
||||
|
||||
// Predefined InputField acceptance functions.
|
||||
var (
|
||||
// InputFieldInteger accepts integers.
|
||||
InputFieldInteger func(text string, ch rune) bool
|
||||
@ -63,7 +74,7 @@ func init() {
|
||||
return err == nil
|
||||
}
|
||||
InputFieldFloat = func(text string, ch rune) bool {
|
||||
if text == "-" || text == "." {
|
||||
if text == "-" || text == "." || text == "-." {
|
||||
return true
|
||||
}
|
||||
_, err := strconv.ParseFloat(text, 64)
|
||||
@ -80,18 +91,80 @@ func init() {
|
||||
// not exceeding that box. "align" is one of AlignLeft, AlignCenter, or
|
||||
// AlignRight. The screen's background color will not be changed.
|
||||
//
|
||||
// Returns the number of actual runes printed and the actual width used for the
|
||||
// printed runes.
|
||||
// You can change the text color 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.
|
||||
func Print(screen tcell.Screen, text string, x, y, maxWidth, align int, color tcell.Color) (int, int) {
|
||||
// We deal with runes, not with bytes.
|
||||
runes := []rune(text)
|
||||
if maxWidth < 0 {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
// AlignCenter is a special case.
|
||||
if align == AlignCenter {
|
||||
width := runewidth.StringWidth(text)
|
||||
// Get positions of color and escape tags. Remove them from original string.
|
||||
colorIndices := colorPattern.FindAllStringIndex(text, -1)
|
||||
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.
|
||||
runes := []rune(strippedText)
|
||||
|
||||
// This helper function takes positions for a substring of "runes" and a start
|
||||
// color and returns the substring with the original tags and the new start
|
||||
// color.
|
||||
substring := func(from, to int, color tcell.Color) (string, tcell.Color) {
|
||||
var colorPos, escapePos, runePos, startPos int
|
||||
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 {
|
||||
color = tcell.GetColor(colors[colorPos][1])
|
||||
}
|
||||
colorPos++
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Handle escape tags.
|
||||
if escapePos < len(escapeIndices) && pos >= escapeIndices[escapePos][0] && pos < escapeIndices[escapePos][1] {
|
||||
if pos == escapeIndices[escapePos][1]-1 {
|
||||
escapePos++
|
||||
} else if pos == escapeIndices[escapePos][1]-2 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Check boundaries.
|
||||
if runePos == from {
|
||||
startPos = pos
|
||||
} else if runePos >= to {
|
||||
return text[startPos:pos], color
|
||||
}
|
||||
|
||||
runePos++
|
||||
}
|
||||
|
||||
return text[startPos:len(text)], color
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
text, color = substring(start, len(runes), color)
|
||||
return Print(screen, text, x+maxWidth-width, y, width, AlignLeft, color)
|
||||
} else if align == AlignCenter {
|
||||
width := runewidth.StringWidth(strippedText)
|
||||
if width == maxWidth {
|
||||
// Use the exact space.
|
||||
return Print(screen, text, x, y, maxWidth, AlignLeft, color)
|
||||
@ -101,43 +174,62 @@ func Print(screen tcell.Screen, text string, x, y, maxWidth, align int, color tc
|
||||
return Print(screen, text, x+half, y, maxWidth-half, AlignLeft, color)
|
||||
} else {
|
||||
// Chop off runes until we have a perfect fit.
|
||||
var start, choppedLeft, choppedRight int
|
||||
ru := runes
|
||||
for len(ru) > 0 && width-choppedLeft-choppedRight > maxWidth {
|
||||
leftWidth := runewidth.RuneWidth(ru[0])
|
||||
rightWidth := runewidth.RuneWidth(ru[len(ru)-1])
|
||||
var choppedLeft, choppedRight, leftIndex, rightIndex int
|
||||
rightIndex = len(runes) - 1
|
||||
for rightIndex > leftIndex && width-choppedLeft-choppedRight > maxWidth {
|
||||
leftWidth := runewidth.RuneWidth(runes[leftIndex])
|
||||
rightWidth := runewidth.RuneWidth(runes[rightIndex])
|
||||
if choppedLeft < choppedRight {
|
||||
start++
|
||||
choppedLeft += leftWidth
|
||||
ru = ru[1:]
|
||||
leftIndex++
|
||||
} else {
|
||||
choppedRight += rightWidth
|
||||
ru = ru[:len(ru)-1]
|
||||
rightIndex--
|
||||
}
|
||||
}
|
||||
return Print(screen, string(ru), x, y, maxWidth, AlignLeft, color)
|
||||
text, color = substring(leftIndex, rightIndex, color)
|
||||
return Print(screen, text, x, y, maxWidth, AlignLeft, color)
|
||||
}
|
||||
}
|
||||
|
||||
// Draw text.
|
||||
drawn := 0
|
||||
drawnWidth := 0
|
||||
for pos, ch := range runes {
|
||||
var colorPos, escapePos int
|
||||
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 {
|
||||
color = tcell.GetColor(colors[colorPos][1])
|
||||
colorPos++
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Handle escape tags.
|
||||
if escapePos < len(escapeIndices) && pos >= escapeIndices[escapePos][0] && pos < escapeIndices[escapePos][1] {
|
||||
if pos == escapeIndices[escapePos][1]-1 {
|
||||
escapePos++
|
||||
} else if pos == escapeIndices[escapePos][1]-2 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we have enough space for this rune.
|
||||
chWidth := runewidth.RuneWidth(ch)
|
||||
if drawnWidth+chWidth > maxWidth {
|
||||
break
|
||||
}
|
||||
finalX := x + drawnWidth
|
||||
if align == AlignRight {
|
||||
ch = runes[len(runes)-1-pos]
|
||||
finalX = x + maxWidth - chWidth - drawnWidth
|
||||
}
|
||||
|
||||
// Print the rune.
|
||||
_, _, style, _ := screen.GetContent(finalX, y)
|
||||
style = style.Foreground(color)
|
||||
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, style)
|
||||
}
|
||||
|
||||
drawn++
|
||||
drawnWidth += chWidth
|
||||
}
|
||||
@ -150,9 +242,17 @@ func PrintSimple(screen tcell.Screen, text string, x, y int) {
|
||||
Print(screen, text, x, y, math.MaxInt32, AlignLeft, Styles.PrimaryTextColor)
|
||||
}
|
||||
|
||||
// StringWidth returns the width of the given string needed to print it on
|
||||
// screen. The text may contain color tags which are not counted.
|
||||
func StringWidth(text string) int {
|
||||
return runewidth.StringWidth(escapePattern.ReplaceAllString(colorPattern.ReplaceAllString(text, ""), "[$1$2]"))
|
||||
}
|
||||
|
||||
// WordWrap splits a text such that each resulting line does not exceed the
|
||||
// given screen width. Possible split points are after commas, dots, dashes,
|
||||
// and any whitespace. Whitespace at split points will be dropped.
|
||||
// given screen width. Possible split points are after any punctuation or
|
||||
// whitespace. Whitespace after split points will be dropped.
|
||||
//
|
||||
// This function considers color tags to have no width.
|
||||
//
|
||||
// Text is always split at newline characters ('\n').
|
||||
func WordWrap(text string, width int) (lines []string) {
|
||||
@ -163,8 +263,29 @@ func WordWrap(text string, width int) (lines []string) {
|
||||
countAfterCandidate := 0
|
||||
var evaluatingCandidate bool
|
||||
text = strings.TrimSpace(text)
|
||||
colorIndices := colorPattern.FindAllStringIndex(text, -1)
|
||||
escapeIndices := escapePattern.FindAllStringIndex(text, -1)
|
||||
|
||||
var colorPos, escapePos int
|
||||
for pos, ch := range text {
|
||||
// Skip color tags.
|
||||
if colorPos < len(colorIndices) && pos >= colorIndices[colorPos][0] && pos < colorIndices[colorPos][1] {
|
||||
if pos == colorIndices[colorPos][1]-1 {
|
||||
colorPos++
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Handle escape tags.
|
||||
if escapePos < len(escapeIndices) && pos >= escapeIndices[escapePos][0] && pos < escapeIndices[escapePos][1] {
|
||||
if pos == escapeIndices[escapePos][1]-1 {
|
||||
escapePos++
|
||||
} else if pos == escapeIndices[escapePos][1]-2 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// What's the width of this rune?
|
||||
chWidth := runewidth.RuneWidth(ch)
|
||||
|
||||
if !evaluatingCandidate && x >= width {
|
||||
@ -182,21 +303,21 @@ func WordWrap(text string, width int) (lines []string) {
|
||||
evaluatingCandidate = false
|
||||
}
|
||||
|
||||
switch ch {
|
||||
switch {
|
||||
// We have a candidate.
|
||||
case ',', '.', '-':
|
||||
case ch >= '!' && ch <= '/', ch >= ':' && ch <= '@', ch >= '[' && ch <= '`', ch >= '{' && ch <= '~':
|
||||
if x > 0 {
|
||||
candidate = pos + 1
|
||||
evaluatingCandidate = true
|
||||
}
|
||||
// If we've had a candidate, skip whitespace. If not, we have a candidate.
|
||||
case ' ', '\t':
|
||||
// If we've had a candidate, skip whitespace. If not, we have a candidate.
|
||||
case ch == ' ', ch == '\t':
|
||||
if x > 0 && !evaluatingCandidate {
|
||||
candidate = pos
|
||||
evaluatingCandidate = true
|
||||
}
|
||||
// Split in any case.
|
||||
case '\n':
|
||||
// Split in any case.
|
||||
case ch == '\n':
|
||||
lines = append(lines, text[start:pos])
|
||||
start = pos + 1
|
||||
evaluatingCandidate = false
|
||||
|
Loading…
x
Reference in New Issue
Block a user