forked from tslocum/cview
Print text using []byte instead of string, calculate string width using runewidth instead of uniseg
This commit is contained in:
parent
50a085333b
commit
6b34a95e75
17 changed files with 173 additions and 160 deletions
|
@ -1,6 +1,7 @@
|
|||
v1.5.1 (WIP)
|
||||
- Store TextView buffer as [][]byte instead of []string
|
||||
- Add TextView.SetBytes and TextView.GetBytes
|
||||
- Allow modification of scroll bar render text
|
||||
|
||||
v1.5.0 (2020-10-03)
|
||||
- Add scroll bar to TextView
|
||||
|
|
|
@ -9,9 +9,6 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
// StandardDoubleClick is the standard double click interval.
|
||||
StandardDoubleClick = 500 * time.Millisecond
|
||||
|
||||
// The size of the event/update/redraw channels.
|
||||
queueSize = 100
|
||||
|
||||
|
@ -19,30 +16,6 @@ const (
|
|||
resizeEventThrottle = 200 * time.Millisecond
|
||||
)
|
||||
|
||||
// MouseAction indicates one of the actions the mouse is logically doing.
|
||||
type MouseAction int16
|
||||
|
||||
// Available mouse actions.
|
||||
const (
|
||||
MouseMove MouseAction = iota
|
||||
MouseLeftDown
|
||||
MouseLeftUp
|
||||
MouseLeftClick
|
||||
MouseLeftDoubleClick
|
||||
MouseMiddleDown
|
||||
MouseMiddleUp
|
||||
MouseMiddleClick
|
||||
MouseMiddleDoubleClick
|
||||
MouseRightDown
|
||||
MouseRightUp
|
||||
MouseRightClick
|
||||
MouseRightDoubleClick
|
||||
MouseScrollUp
|
||||
MouseScrollDown
|
||||
MouseScrollLeft
|
||||
MouseScrollRight
|
||||
)
|
||||
|
||||
// Application represents the top node of an application.
|
||||
//
|
||||
// It is not strictly required to use this class as none of the other classes
|
||||
|
|
10
box.go
10
box.go
|
@ -39,7 +39,7 @@ type Box struct {
|
|||
borderAttributes tcell.AttrMask
|
||||
|
||||
// The title. Only visible if there is a border, too.
|
||||
title string
|
||||
title []byte
|
||||
|
||||
// The color of the title.
|
||||
titleColor tcell.Color
|
||||
|
@ -376,7 +376,7 @@ func (b *Box) SetTitle(title string) *Box {
|
|||
b.l.Lock()
|
||||
defer b.l.Unlock()
|
||||
|
||||
b.title = title
|
||||
b.title = []byte(title)
|
||||
return b
|
||||
}
|
||||
|
||||
|
@ -385,7 +385,7 @@ func (b *Box) GetTitle() string {
|
|||
b.l.RLock()
|
||||
defer b.l.RUnlock()
|
||||
|
||||
return b.title
|
||||
return string(b.title)
|
||||
}
|
||||
|
||||
// SetTitleColor sets the box's title color.
|
||||
|
@ -474,12 +474,12 @@ func (b *Box) Draw(screen tcell.Screen) {
|
|||
screen.SetContent(b.x+b.width-1, b.y+b.height-1, bottomRight, nil, border)
|
||||
|
||||
// Draw title.
|
||||
if b.title != "" && b.width >= 4 {
|
||||
if len(b.title) > 0 && b.width >= 4 {
|
||||
printed, _ := Print(screen, b.title, b.x+1, b.y, b.width-2, b.titleAlign, b.titleColor)
|
||||
if len(b.title)-printed > 0 && printed > 0 {
|
||||
_, _, style, _ := screen.GetContent(b.x+b.width-2, b.y)
|
||||
fg, _, _ := style.Decompose()
|
||||
Print(screen, string(SemigraphicsHorizontalEllipsis), b.x+b.width-2, b.y, 1, AlignLeft, fg)
|
||||
Print(screen, []byte(string(SemigraphicsHorizontalEllipsis)), b.x+b.width-2, b.y, 1, AlignLeft, fg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ type Button struct {
|
|||
*Box
|
||||
|
||||
// The text to be displayed before the input area.
|
||||
label string
|
||||
label []byte
|
||||
|
||||
// The label color.
|
||||
labelColor tcell.Color
|
||||
|
@ -38,7 +38,7 @@ func NewButton(label string) *Button {
|
|||
box.SetRect(0, 0, TaggedStringWidth(label)+4, 1)
|
||||
return &Button{
|
||||
Box: box,
|
||||
label: label,
|
||||
label: []byte(label),
|
||||
labelColor: Styles.PrimaryTextColor,
|
||||
labelColorFocused: Styles.InverseTextColor,
|
||||
backgroundColorFocused: Styles.PrimaryTextColor,
|
||||
|
@ -50,7 +50,7 @@ func (b *Button) SetLabel(label string) *Button {
|
|||
b.Lock()
|
||||
defer b.Unlock()
|
||||
|
||||
b.label = label
|
||||
b.label = []byte(label)
|
||||
return b
|
||||
}
|
||||
|
||||
|
@ -59,7 +59,7 @@ func (b *Button) GetLabel() string {
|
|||
b.RLock()
|
||||
defer b.RUnlock()
|
||||
|
||||
return b.label
|
||||
return string(b.label)
|
||||
}
|
||||
|
||||
// SetLabelColor sets the color of the button text.
|
||||
|
|
16
checkbox.go
16
checkbox.go
|
@ -15,10 +15,10 @@ type CheckBox struct {
|
|||
checked bool
|
||||
|
||||
// The text to be displayed before the checkbox.
|
||||
label string
|
||||
label []byte
|
||||
|
||||
// The text to be displayed after the checkbox.
|
||||
message string
|
||||
message []byte
|
||||
|
||||
// The screen width of the label area. A value of 0 means use the width of
|
||||
// the label text.
|
||||
|
@ -106,7 +106,7 @@ func (c *CheckBox) SetLabel(label string) *CheckBox {
|
|||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
c.label = label
|
||||
c.label = []byte(label)
|
||||
return c
|
||||
}
|
||||
|
||||
|
@ -115,7 +115,7 @@ func (c *CheckBox) GetLabel() string {
|
|||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
return c.label
|
||||
return string(c.label)
|
||||
}
|
||||
|
||||
// SetMessage sets the text to be displayed after the checkbox
|
||||
|
@ -123,7 +123,7 @@ func (c *CheckBox) SetMessage(message string) *CheckBox {
|
|||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
c.message = message
|
||||
c.message = []byte(message)
|
||||
return c
|
||||
}
|
||||
|
||||
|
@ -132,7 +132,7 @@ func (c *CheckBox) GetMessage() string {
|
|||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
return c.message
|
||||
return string(c.message)
|
||||
}
|
||||
|
||||
// SetLabelWidth sets the screen width of the label. A value of 0 will cause the
|
||||
|
@ -209,7 +209,7 @@ func (c *CheckBox) GetFieldWidth() int {
|
|||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
if c.message == "" {
|
||||
if len(c.message) == 0 {
|
||||
return 1
|
||||
}
|
||||
|
||||
|
@ -321,7 +321,7 @@ func (c *CheckBox) Draw(screen tcell.Screen) {
|
|||
screen.SetContent(x+1, y, checkedRune, nil, fieldStyle)
|
||||
screen.SetContent(x+2, y, ' ', nil, fieldStyle)
|
||||
|
||||
if c.message != "" {
|
||||
if len(c.message) > 0 {
|
||||
Print(screen, c.message, x+4, y, len(c.message), AlignLeft, labelColor)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ func (r *RadioButtons) Draw(screen tcell.Screen) {
|
|||
radioButton = "\u25c9" // Checked.
|
||||
}
|
||||
line := fmt.Sprintf(`%s[white] %s`, radioButton, option)
|
||||
cview.Print(screen, line, x, y+index, width, cview.AlignLeft, tcell.ColorYellow.TrueColor())
|
||||
cview.Print(screen, []byte(line), x, y+index, width, cview.AlignLeft, tcell.ColorYellow.TrueColor())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
15
dropdown.go
15
dropdown.go
|
@ -5,6 +5,7 @@ import (
|
|||
"sync"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
// DropDownOption is one option that can be selected in a drop-down primitive.
|
||||
|
@ -569,10 +570,10 @@ func (d *DropDown) Draw(screen tcell.Screen) {
|
|||
if labelWidth > rightLimit-x {
|
||||
labelWidth = rightLimit - x
|
||||
}
|
||||
Print(screen, d.label, x, y, labelWidth, AlignLeft, labelColor)
|
||||
Print(screen, []byte(d.label), x, y, labelWidth, AlignLeft, labelColor)
|
||||
x += labelWidth
|
||||
} else {
|
||||
_, drawnWidth := Print(screen, d.label, x, y, rightLimit-x, AlignLeft, labelColor)
|
||||
_, drawnWidth := Print(screen, []byte(d.label), x, y, rightLimit-x, AlignLeft, labelColor)
|
||||
x += drawnWidth
|
||||
}
|
||||
|
||||
|
@ -614,12 +615,12 @@ func (d *DropDown) Draw(screen tcell.Screen) {
|
|||
if d.open && len(d.prefix) > 0 {
|
||||
// Show the prefix.
|
||||
currentOptionPrefixWidth := TaggedStringWidth(d.currentOptionPrefix)
|
||||
prefixWidth := stringWidth(d.prefix)
|
||||
prefixWidth := runewidth.StringWidth(d.prefix)
|
||||
listItemText := d.options[d.list.GetCurrentItemIndex()].text
|
||||
Print(screen, d.currentOptionPrefix, x, y, fieldWidth, AlignLeft, fieldTextColor)
|
||||
Print(screen, d.prefix, x+currentOptionPrefixWidth, y, fieldWidth-currentOptionPrefixWidth, AlignLeft, d.prefixTextColor)
|
||||
Print(screen, []byte(d.currentOptionPrefix), x, y, fieldWidth, AlignLeft, fieldTextColor)
|
||||
Print(screen, []byte(d.prefix), x+currentOptionPrefixWidth, y, fieldWidth-currentOptionPrefixWidth, AlignLeft, d.prefixTextColor)
|
||||
if len(d.prefix) < len(listItemText) {
|
||||
Print(screen, listItemText[len(d.prefix):]+d.currentOptionSuffix, x+prefixWidth+currentOptionPrefixWidth, y, fieldWidth-prefixWidth-currentOptionPrefixWidth, AlignLeft, fieldTextColor)
|
||||
Print(screen, []byte(listItemText[len(d.prefix):]+d.currentOptionSuffix), x+prefixWidth+currentOptionPrefixWidth, y, fieldWidth-prefixWidth-currentOptionPrefixWidth, AlignLeft, fieldTextColor)
|
||||
}
|
||||
} else {
|
||||
color := fieldTextColor
|
||||
|
@ -633,7 +634,7 @@ func (d *DropDown) Draw(screen tcell.Screen) {
|
|||
}
|
||||
|
||||
// Just show the current selection.
|
||||
Print(screen, text, x, y, fieldWidth, AlignLeft, color)
|
||||
Print(screen, []byte(text), x, y, fieldWidth, AlignLeft, color)
|
||||
}
|
||||
|
||||
// Draw drop-down symbol
|
||||
|
|
2
frame.go
2
frame.go
|
@ -137,7 +137,7 @@ func (f *Frame) Draw(screen tcell.Screen) {
|
|||
}
|
||||
|
||||
// Draw text.
|
||||
Print(screen, text.Text, x, y, width, text.Align, text.Color)
|
||||
Print(screen, []byte(text.Text), x, y, width, text.Align, text.Color)
|
||||
}
|
||||
|
||||
// Set the size of the contained primitive.
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"unicode/utf8"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
// InputField is a one-line box (three lines if there is a title) where the
|
||||
|
@ -613,10 +614,10 @@ func (i *InputField) Draw(screen tcell.Screen) {
|
|||
if labelWidth > rightLimit-x {
|
||||
labelWidth = rightLimit - x
|
||||
}
|
||||
Print(screen, i.label, x, y, labelWidth, AlignLeft, labelColor)
|
||||
Print(screen, []byte(i.label), x, y, labelWidth, AlignLeft, labelColor)
|
||||
x += labelWidth
|
||||
} else {
|
||||
_, drawnWidth := Print(screen, i.label, x, y, rightLimit-x, AlignLeft, labelColor)
|
||||
_, drawnWidth := Print(screen, []byte(i.label), x, y, rightLimit-x, AlignLeft, labelColor)
|
||||
x += drawnWidth
|
||||
}
|
||||
|
||||
|
@ -643,7 +644,7 @@ func (i *InputField) Draw(screen tcell.Screen) {
|
|||
if i.GetFocusable().HasFocus() && i.placeholderTextColorFocused != ColorUnset {
|
||||
placeholderTextColor = i.placeholderTextColorFocused
|
||||
}
|
||||
Print(screen, Escape(i.placeholder), x, y, fieldWidth, AlignLeft, placeholderTextColor)
|
||||
Print(screen, []byte(Escape(i.placeholder)), x, y, fieldWidth, AlignLeft, placeholderTextColor)
|
||||
i.offset = 0
|
||||
} else {
|
||||
// Draw entered text.
|
||||
|
@ -651,10 +652,10 @@ func (i *InputField) Draw(screen tcell.Screen) {
|
|||
text = strings.Repeat(string(i.maskCharacter), utf8.RuneCountInString(i.text))
|
||||
}
|
||||
drawnText := ""
|
||||
if fieldWidth >= stringWidth(text) {
|
||||
if fieldWidth >= runewidth.StringWidth(text) {
|
||||
// We have enough space for the full text.
|
||||
drawnText = Escape(text)
|
||||
Print(screen, drawnText, x, y, fieldWidth, AlignLeft, fieldTextColor)
|
||||
Print(screen, []byte(drawnText), x, y, fieldWidth, AlignLeft, fieldTextColor)
|
||||
i.offset = 0
|
||||
iterateString(text, func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
|
||||
if textPos >= i.cursorPos {
|
||||
|
@ -674,7 +675,7 @@ func (i *InputField) Draw(screen tcell.Screen) {
|
|||
var shiftLeft int
|
||||
if i.offset > i.cursorPos {
|
||||
i.offset = i.cursorPos
|
||||
} else if subWidth := stringWidth(text[i.offset:i.cursorPos]); subWidth > fieldWidth-1 {
|
||||
} else if subWidth := runewidth.StringWidth(text[i.offset:i.cursorPos]); subWidth > fieldWidth-1 {
|
||||
shiftLeft = subWidth - fieldWidth + 1
|
||||
}
|
||||
currentOffset := i.offset
|
||||
|
@ -693,17 +694,17 @@ func (i *InputField) Draw(screen tcell.Screen) {
|
|||
return false
|
||||
})
|
||||
drawnText = Escape(text[i.offset:])
|
||||
Print(screen, drawnText, x, y, fieldWidth, AlignLeft, fieldTextColor)
|
||||
Print(screen, []byte(drawnText), x, y, fieldWidth, AlignLeft, fieldTextColor)
|
||||
}
|
||||
// Draw suggestion
|
||||
if i.maskCharacter == 0 && i.autocompleteListSuggestion != "" {
|
||||
Print(screen, i.autocompleteListSuggestion, x+stringWidth(drawnText), y, fieldWidth-stringWidth(drawnText), AlignLeft, i.autocompleteSuggestionTextColor)
|
||||
Print(screen, []byte(i.autocompleteListSuggestion), x+runewidth.StringWidth(drawnText), y, fieldWidth-runewidth.StringWidth(drawnText), AlignLeft, i.autocompleteSuggestionTextColor)
|
||||
}
|
||||
}
|
||||
|
||||
// Draw field note
|
||||
if i.fieldNote != "" {
|
||||
Print(screen, i.fieldNote, x, y+1, fieldWidth, AlignLeft, i.fieldNoteTextColor)
|
||||
Print(screen, []byte(i.fieldNote), x, y+1, fieldWidth, AlignLeft, i.fieldNoteTextColor)
|
||||
}
|
||||
|
||||
// Draw autocomplete list.
|
||||
|
|
17
list.go
17
list.go
|
@ -1,6 +1,7 @@
|
|||
package cview
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -871,9 +872,9 @@ func (l *List) Draw(screen tcell.Screen) {
|
|||
}
|
||||
|
||||
if item.mainText == "" && item.secondaryText == "" && item.shortcut == 0 { // Divider
|
||||
Print(screen, string(tcell.RuneLTee), (x-5)-l.paddingLeft, y, 1, AlignLeft, l.mainTextColor)
|
||||
Print(screen, strings.Repeat(string(tcell.RuneHLine), width+4+l.paddingLeft+l.paddingRight), (x-4)-l.paddingLeft, y, width+4+l.paddingLeft+l.paddingRight, AlignLeft, l.mainTextColor)
|
||||
Print(screen, string(tcell.RuneRTee), (x-5)+width+5+l.paddingRight, y, 1, AlignLeft, l.mainTextColor)
|
||||
Print(screen, []byte(string(tcell.RuneLTee)), (x-5)-l.paddingLeft, y, 1, AlignLeft, l.mainTextColor)
|
||||
Print(screen, bytes.Repeat([]byte(string(tcell.RuneHLine)), width+4+l.paddingLeft+l.paddingRight), (x-4)-l.paddingLeft, y, width+4+l.paddingLeft+l.paddingRight, AlignLeft, l.mainTextColor)
|
||||
Print(screen, []byte(string(tcell.RuneRTee)), (x-5)+width+5+l.paddingRight, y, 1, AlignLeft, l.mainTextColor)
|
||||
|
||||
RenderScrollBar(screen, l.scrollBarVisibility, scrollBarX, y, scrollBarHeight, len(l.items), scrollBarCursor, index-l.offset, l.hasFocus, l.scrollBarColor)
|
||||
y++
|
||||
|
@ -881,11 +882,11 @@ func (l *List) Draw(screen tcell.Screen) {
|
|||
} else if !item.enabled { // Disabled item
|
||||
// Shortcuts.
|
||||
if showShortcuts && item.shortcut != 0 {
|
||||
Print(screen, fmt.Sprintf("(%s)", string(item.shortcut)), x-5, y, 4, AlignRight, tcell.ColorDarkSlateGray.TrueColor())
|
||||
Print(screen, []byte(fmt.Sprintf("(%c)", item.shortcut)), x-5, y, 4, AlignRight, tcell.ColorDarkSlateGray.TrueColor())
|
||||
}
|
||||
|
||||
// Main text.
|
||||
Print(screen, item.mainText, x, y, width, AlignLeft, tcell.ColorGray.TrueColor())
|
||||
Print(screen, []byte(item.mainText), x, y, width, AlignLeft, tcell.ColorGray.TrueColor())
|
||||
|
||||
RenderScrollBar(screen, l.scrollBarVisibility, scrollBarX, y, scrollBarHeight, len(l.items), scrollBarCursor, index-l.offset, l.hasFocus, l.scrollBarColor)
|
||||
y++
|
||||
|
@ -894,11 +895,11 @@ func (l *List) Draw(screen tcell.Screen) {
|
|||
|
||||
// Shortcuts.
|
||||
if showShortcuts && item.shortcut != 0 {
|
||||
Print(screen, fmt.Sprintf("(%s)", string(item.shortcut)), x-5, y, 4, AlignRight, l.shortcutColor)
|
||||
Print(screen, []byte(fmt.Sprintf("(%c)", item.shortcut)), x-5, y, 4, AlignRight, l.shortcutColor)
|
||||
}
|
||||
|
||||
// Main text.
|
||||
Print(screen, item.mainText, x, y, width, AlignLeft, l.mainTextColor)
|
||||
Print(screen, []byte(item.mainText), x, y, width, AlignLeft, l.mainTextColor)
|
||||
|
||||
// Background color of selected text.
|
||||
if index == l.currentItem && (!l.selectedFocusOnly || hasFocus) {
|
||||
|
@ -930,7 +931,7 @@ func (l *List) Draw(screen tcell.Screen) {
|
|||
|
||||
// Secondary text.
|
||||
if l.showSecondaryText {
|
||||
Print(screen, item.secondaryText, x, y, width, AlignLeft, l.secondaryTextColor)
|
||||
Print(screen, []byte(item.secondaryText), x, y, width, AlignLeft, l.secondaryTextColor)
|
||||
|
||||
RenderScrollBar(screen, l.scrollBarVisibility, scrollBarX, y, scrollBarHeight, len(l.items), scrollBarCursor, index-l.offset, l.hasFocus, l.scrollBarColor)
|
||||
|
||||
|
|
6
modal.go
6
modal.go
|
@ -201,7 +201,7 @@ func (m *Modal) Draw(screen tcell.Screen) {
|
|||
// Calculate the width of this Modal.
|
||||
buttonsWidth := 0
|
||||
for _, button := range m.form.buttons {
|
||||
buttonsWidth += TaggedStringWidth(button.label) + 4 + 2
|
||||
buttonsWidth += TaggedTextWidth(button.label) + 4 + 2
|
||||
}
|
||||
buttonsWidth -= 2
|
||||
screenWidth, screenHeight := screen.Size()
|
||||
|
@ -213,9 +213,9 @@ func (m *Modal) Draw(screen tcell.Screen) {
|
|||
|
||||
// Reset the text and find out how wide it is.
|
||||
m.frame.Clear()
|
||||
lines := WordWrap(m.text, width)
|
||||
lines := WordWrap([]byte(m.text), width)
|
||||
for _, line := range lines {
|
||||
m.frame.AddText(line, true, AlignCenter, m.textColor)
|
||||
m.frame.AddText(string(line), true, AlignCenter, m.textColor)
|
||||
}
|
||||
|
||||
// Set the Modal's position and size.
|
||||
|
|
30
mouse.go
Normal file
30
mouse.go
Normal file
|
@ -0,0 +1,30 @@
|
|||
package cview
|
||||
|
||||
import "time"
|
||||
|
||||
// MouseAction indicates one of the actions the mouse is logically doing.
|
||||
type MouseAction int16
|
||||
|
||||
// Available mouse actions.
|
||||
const (
|
||||
MouseMove MouseAction = iota
|
||||
MouseLeftDown
|
||||
MouseLeftUp
|
||||
MouseLeftClick
|
||||
MouseLeftDoubleClick
|
||||
MouseMiddleDown
|
||||
MouseMiddleUp
|
||||
MouseMiddleClick
|
||||
MouseMiddleDoubleClick
|
||||
MouseRightDown
|
||||
MouseRightUp
|
||||
MouseRightClick
|
||||
MouseRightDoubleClick
|
||||
MouseScrollUp
|
||||
MouseScrollDown
|
||||
MouseScrollLeft
|
||||
MouseScrollRight
|
||||
)
|
||||
|
||||
// StandardDoubleClick is a commonly used double click interval.
|
||||
const StandardDoubleClick = 500 * time.Millisecond
|
6
table.go
6
table.go
|
@ -995,7 +995,7 @@ ColumnLoop:
|
|||
}
|
||||
for _, row := range evaluationRows {
|
||||
if cell := getCell(row, column); cell != nil {
|
||||
_, _, _, _, _, _, cellWidth := decomposeString(cell.Text, true, false)
|
||||
_, _, _, _, _, _, cellWidth := decomposeText([]byte(cell.Text), true, false)
|
||||
if cell.MaxWidth > 0 && cell.MaxWidth < cellWidth {
|
||||
cellWidth = cell.MaxWidth
|
||||
}
|
||||
|
@ -1089,10 +1089,10 @@ ColumnLoop:
|
|||
finalWidth = width - columnX - 1
|
||||
}
|
||||
cell.x, cell.y, cell.width = x+columnX+1, y+rowY, finalWidth
|
||||
_, printed := printWithStyle(screen, cell.Text, x+columnX+1, y+rowY, finalWidth, cell.Align, SetAttributes(tcell.StyleDefault.Foreground(cell.Color), cell.Attributes))
|
||||
_, printed := printWithStyle(screen, []byte(cell.Text), x+columnX+1, y+rowY, finalWidth, cell.Align, SetAttributes(tcell.StyleDefault.Foreground(cell.Color), cell.Attributes))
|
||||
if TaggedStringWidth(cell.Text)-printed > 0 && printed > 0 {
|
||||
_, _, style, _ := screen.GetContent(x+columnX+finalWidth, y+rowY)
|
||||
printWithStyle(screen, string(SemigraphicsHorizontalEllipsis), x+columnX+finalWidth, y+rowY, 1, AlignLeft, style)
|
||||
printWithStyle(screen, []byte(string(SemigraphicsHorizontalEllipsis)), x+columnX+finalWidth, y+rowY, 1, AlignLeft, style)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
35
textview.go
35
textview.go
|
@ -32,13 +32,13 @@ type textViewIndex struct {
|
|||
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 []byte // The starting region ID.
|
||||
}
|
||||
|
||||
// textViewRegion contains information about a region.
|
||||
type textViewRegion struct {
|
||||
// The region ID.
|
||||
ID string
|
||||
ID []byte
|
||||
|
||||
// The starting and end screen position of the region as determined the last
|
||||
// time Draw() was called. A negative value indicates out-of-rect positions.
|
||||
|
@ -831,7 +831,7 @@ func (t *TextView) reindexBuffer(width int) {
|
|||
}
|
||||
|
||||
// Initial states.
|
||||
regionID := ""
|
||||
var regionID []byte
|
||||
var (
|
||||
highlighted bool
|
||||
foregroundColor, backgroundColor, attributes string
|
||||
|
@ -839,12 +839,11 @@ func (t *TextView) reindexBuffer(width int) {
|
|||
|
||||
// Go through each line in the buffer.
|
||||
for bufferIndex, buf := range t.buffer {
|
||||
str := string(buf)
|
||||
colorTagIndices, colorTags, regionIndices, regions, escapeIndices, strippedStr, _ := decomposeString(str, t.dynamicColors, t.regions)
|
||||
colorTagIndices, colorTags, regionIndices, regions, escapeIndices, strippedStr, _ := decomposeText(buf, t.dynamicColors, t.regions)
|
||||
|
||||
// Split the line if required.
|
||||
var splitLines []string
|
||||
str = strippedStr
|
||||
str := string(strippedStr) // TODO
|
||||
if t.wrap && len(str) > 0 {
|
||||
for len(str) > 0 {
|
||||
extract := runewidth.Truncate(str, width, "")
|
||||
|
@ -938,14 +937,14 @@ func (t *TextView) reindexBuffer(width int) {
|
|||
case 1:
|
||||
// Process region tags.
|
||||
regionID = regions[regionPos][1]
|
||||
_, highlighted = t.highlights[regionID]
|
||||
_, highlighted = t.highlights[string(regionID)]
|
||||
|
||||
// Update highlight range.
|
||||
if highlighted {
|
||||
line := len(t.index)
|
||||
if t.fromHighlight < 0 {
|
||||
t.fromHighlight, t.toHighlight = line, line
|
||||
t.posHighlight = stringWidth(splitLine[:strippedTagStart])
|
||||
t.posHighlight = runewidth.StringWidth(splitLine[:strippedTagStart])
|
||||
} else if line > t.toHighlight {
|
||||
t.toHighlight = line
|
||||
}
|
||||
|
@ -963,7 +962,7 @@ func (t *TextView) reindexBuffer(width int) {
|
|||
|
||||
// Append this line.
|
||||
line.NextPos = originalPos
|
||||
line.Width = stringWidth(splitLine)
|
||||
line.Width = runewidth.StringWidth(splitLine)
|
||||
t.index = append(t.index, line)
|
||||
}
|
||||
|
||||
|
@ -975,7 +974,7 @@ func (t *TextView) reindexBuffer(width int) {
|
|||
if len(trimmed) != len(str) {
|
||||
oldNextPos := line.NextPos
|
||||
line.NextPos -= len(str) - len(trimmed)
|
||||
line.Width -= stringWidth(string(t.buffer[line.Line][line.NextPos:oldNextPos]))
|
||||
line.Width -= runewidth.StringWidth(string(t.buffer[line.Line][line.NextPos:oldNextPos]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1106,7 +1105,7 @@ func (t *TextView) Draw(screen tcell.Screen) {
|
|||
backgroundColor := index.BackgroundColor
|
||||
attributes := index.Attributes
|
||||
regionID := index.Region
|
||||
if t.regions && regionID != "" && (len(t.regionInfos) == 0 || t.regionInfos[len(t.regionInfos)-1].ID != regionID) {
|
||||
if t.regions && len(regionID) > 0 && (len(t.regionInfos) == 0 || !bytes.Equal(t.regionInfos[len(t.regionInfos)-1].ID, regionID)) {
|
||||
t.regionInfos = append(t.regionInfos, &textViewRegion{
|
||||
ID: regionID,
|
||||
FromX: x,
|
||||
|
@ -1117,7 +1116,7 @@ func (t *TextView) Draw(screen tcell.Screen) {
|
|||
}
|
||||
|
||||
// Process tags.
|
||||
colorTagIndices, colorTags, regionIndices, regions, escapeIndices, strippedText, _ := decomposeString(string(text), t.dynamicColors, t.regions)
|
||||
colorTagIndices, colorTags, regionIndices, regions, escapeIndices, strippedText, _ := decomposeText(text, t.dynamicColors, t.regions)
|
||||
|
||||
// Calculate the position of the line.
|
||||
var skip, posX int
|
||||
|
@ -1136,7 +1135,7 @@ func (t *TextView) Draw(screen tcell.Screen) {
|
|||
// Print the line.
|
||||
if y+line-t.lineOffset >= 0 {
|
||||
var colorPos, regionPos, escapePos, tagOffset, skipped int
|
||||
iterateString(strippedText, func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
|
||||
iterateString(string(strippedText), func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
|
||||
// Process tags.
|
||||
for {
|
||||
if colorPos < len(colorTags) && textPos+tagOffset >= colorTagIndices[colorPos][0] && textPos+tagOffset < colorTagIndices[colorPos][1] {
|
||||
|
@ -1146,13 +1145,13 @@ func (t *TextView) Draw(screen tcell.Screen) {
|
|||
colorPos++
|
||||
} else if regionPos < len(regionIndices) && textPos+tagOffset >= regionIndices[regionPos][0] && textPos+tagOffset < regionIndices[regionPos][1] {
|
||||
// Get the region.
|
||||
if regionID != "" && len(t.regionInfos) > 0 && t.regionInfos[len(t.regionInfos)-1].ID == regionID {
|
||||
if len(regionID) > 0 && len(t.regionInfos) > 0 && !bytes.Equal(t.regionInfos[len(t.regionInfos)-1].ID, regionID) {
|
||||
// End last region.
|
||||
t.regionInfos[len(t.regionInfos)-1].ToX = x + posX
|
||||
t.regionInfos[len(t.regionInfos)-1].ToY = y + line - t.lineOffset
|
||||
}
|
||||
regionID = regions[regionPos][1]
|
||||
if regionID != "" {
|
||||
if len(regionID) > 0 {
|
||||
// Start new region.
|
||||
t.regionInfos = append(t.regionInfos, &textViewRegion{
|
||||
ID: regionID,
|
||||
|
@ -1182,8 +1181,8 @@ func (t *TextView) Draw(screen tcell.Screen) {
|
|||
|
||||
// Do we highlight this character?
|
||||
var highlighted bool
|
||||
if regionID != "" {
|
||||
if _, ok := t.highlights[regionID]; ok {
|
||||
if len(regionID) > 0 {
|
||||
if _, ok := t.highlights[string(regionID)]; ok {
|
||||
highlighted = true
|
||||
}
|
||||
}
|
||||
|
@ -1313,7 +1312,7 @@ func (t *TextView) MouseHandler() func(action MouseAction, event *tcell.EventMou
|
|||
region.ToY >= 0 && y > region.ToY {
|
||||
continue
|
||||
}
|
||||
t.Highlight(region.ID)
|
||||
t.Highlight(string(region.ID))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
|
@ -140,6 +140,9 @@ func BenchmarkTextViewIndex(b *testing.B) {
|
|||
b.Errorf("failed to write: expected to write %d bytes, wrote %d", randomDataSize, n)
|
||||
}
|
||||
|
||||
tv.index = nil
|
||||
tv.reindexBuffer(80)
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
|
@ -210,13 +213,16 @@ func BenchmarkTextViewGetText(b *testing.B) {
|
|||
b.Errorf("failed to write: expected to write %d bytes, wrote %d", randomDataSize, n)
|
||||
}
|
||||
|
||||
v = tv.GetBytes(true)
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
v = tv.GetBytes(true)
|
||||
_ = v
|
||||
}
|
||||
|
||||
_ = v
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -286,6 +292,8 @@ func BenchmarkTextViewDraw(b *testing.B) {
|
|||
b.Errorf("failed to write: expected to write %d bytes, wrote %d", randomDataSize, n)
|
||||
}
|
||||
|
||||
tv.Draw(app.screen)
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
|
|
|
@ -331,7 +331,7 @@ type TreeView struct {
|
|||
topLevel int
|
||||
|
||||
// Strings drawn before the nodes, based on their level.
|
||||
prefixes []string
|
||||
prefixes [][]byte
|
||||
|
||||
// Vertical scroll offset.
|
||||
offsetY int
|
||||
|
@ -452,7 +452,10 @@ func (t *TreeView) SetPrefixes(prefixes []string) *TreeView {
|
|||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.prefixes = prefixes
|
||||
t.prefixes = make([][]byte, len(prefixes))
|
||||
for i := range prefixes {
|
||||
t.prefixes[i] = []byte(prefixes[i])
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
|
@ -880,7 +883,7 @@ func (t *TreeView) Draw(screen tcell.Screen) {
|
|||
}
|
||||
style = tcell.StyleDefault.Background(backgroundColor).Foreground(foregroundColor)
|
||||
}
|
||||
printWithStyle(screen, node.text, x+node.textX+prefixWidth, posY, width-node.textX-prefixWidth, AlignLeft, style)
|
||||
printWithStyle(screen, []byte(node.text), x+node.textX+prefixWidth, posY, width-node.textX-prefixWidth, AlignLeft, style)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
120
util.go
120
util.go
|
@ -101,9 +101,9 @@ func init() {
|
|||
// 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]
|
||||
func styleFromTag(fgColor, bgColor, attributes string, tagSubstrings [][]byte) (newFgColor, newBgColor, newAttributes string) {
|
||||
if len(tagSubstrings[colorForegroundPos]) > 0 {
|
||||
color := string(tagSubstrings[colorForegroundPos])
|
||||
if color == "-" {
|
||||
fgColor = "-"
|
||||
} else if color != "" {
|
||||
|
@ -111,8 +111,8 @@ func styleFromTag(fgColor, bgColor, attributes string, tagSubstrings []string) (
|
|||
}
|
||||
}
|
||||
|
||||
if tagSubstrings[colorBackgroundPos-1] != "" {
|
||||
color := tagSubstrings[colorBackgroundPos]
|
||||
if len(tagSubstrings[colorBackgroundPos-1]) > 0 {
|
||||
color := string(tagSubstrings[colorBackgroundPos])
|
||||
if color == "-" {
|
||||
bgColor = "-"
|
||||
} else if color != "" {
|
||||
|
@ -120,8 +120,8 @@ func styleFromTag(fgColor, bgColor, attributes string, tagSubstrings []string) (
|
|||
}
|
||||
}
|
||||
|
||||
if tagSubstrings[colorFlagPos-1] != "" {
|
||||
flags := tagSubstrings[colorFlagPos]
|
||||
if len(tagSubstrings[colorFlagPos-1]) > 0 {
|
||||
flags := string(tagSubstrings[colorFlagPos])
|
||||
if flags == "-" {
|
||||
attributes = "-"
|
||||
} else if flags != "" {
|
||||
|
@ -200,7 +200,7 @@ func SetAttributes(style tcell.Style, attrs tcell.AttrMask) tcell.Style {
|
|||
Underline(attrs&tcell.AttrUnderline != 0)
|
||||
}
|
||||
|
||||
// decomposeString returns information about a string which may contain color
|
||||
// decomposeText returns information about a string which may contain color
|
||||
// tags or region tags, depending on which ones are requested to be found. It
|
||||
// returns the indices of the color tags (as returned by
|
||||
// re.FindAllStringIndex()), the color tags themselves (as returned by
|
||||
|
@ -208,22 +208,22 @@ func SetAttributes(style tcell.Style, attrs tcell.AttrMask) tcell.Style {
|
|||
// themselves, the indices of an escaped tags (only if at least color tags or
|
||||
// region tags are requested), the string stripped by any tags and escaped, and
|
||||
// the screen width of the stripped string.
|
||||
func decomposeString(text string, findColors, findRegions bool) (colorIndices [][]int, colors [][]string, regionIndices [][]int, regions [][]string, escapeIndices [][]int, stripped string, width int) {
|
||||
func decomposeText(text []byte, findColors, findRegions bool) (colorIndices [][]int, colors [][][]byte, regionIndices [][]int, regions [][][]byte, escapeIndices [][]int, stripped []byte, width int) {
|
||||
// Shortcut for the trivial case.
|
||||
if !findColors && !findRegions {
|
||||
return nil, nil, nil, nil, nil, text, stringWidth(text)
|
||||
return nil, nil, nil, nil, nil, text, runewidth.StringWidth(string(text))
|
||||
}
|
||||
|
||||
// Get positions of any tags.
|
||||
if findColors {
|
||||
colorIndices = colorPattern.FindAllStringIndex(text, -1)
|
||||
colors = colorPattern.FindAllStringSubmatch(text, -1)
|
||||
colorIndices = colorPattern.FindAllIndex(text, -1)
|
||||
colors = colorPattern.FindAllSubmatch(text, -1)
|
||||
}
|
||||
if findRegions {
|
||||
regionIndices = regionPattern.FindAllStringIndex(text, -1)
|
||||
regions = regionPattern.FindAllStringSubmatch(text, -1)
|
||||
regionIndices = regionPattern.FindAllIndex(text, -1)
|
||||
regions = regionPattern.FindAllSubmatch(text, -1)
|
||||
}
|
||||
escapeIndices = escapePattern.FindAllStringIndex(text, -1)
|
||||
escapeIndices = escapePattern.FindAllIndex(text, -1)
|
||||
|
||||
// Because the color pattern detects empty tags, we need to filter them out.
|
||||
for i := len(colorIndices) - 1; i >= 0; i-- {
|
||||
|
@ -259,10 +259,10 @@ func decomposeString(text string, findColors, findRegions bool) (colorIndices []
|
|||
buf = append(buf, text[from:]...)
|
||||
|
||||
// Escape string.
|
||||
stripped = string(escapePattern.ReplaceAll(buf, []byte("[$1$2]")))
|
||||
stripped = escapePattern.ReplaceAll(buf, []byte("[$1$2]"))
|
||||
|
||||
// Get the width of the stripped string.
|
||||
width = stringWidth(stripped)
|
||||
width = runewidth.StringWidth(string(stripped))
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -276,19 +276,19 @@ func decomposeString(text string, findColors, findRegions bool) (colorIndices []
|
|||
//
|
||||
// Returns the number of actual bytes of the text printed (including color tags)
|
||||
// and the actual width used for the printed runes.
|
||||
func Print(screen tcell.Screen, text string, x, y, maxWidth, align int, color tcell.Color) (int, int) {
|
||||
func Print(screen tcell.Screen, text []byte, 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) {
|
||||
func printWithStyle(screen tcell.Screen, text []byte, x, y, maxWidth, align int, style tcell.Style) (int, int) {
|
||||
if maxWidth <= 0 || len(text) == 0 {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
// Decompose the text.
|
||||
colorIndices, colors, _, _, escapeIndices, strippedText, strippedWidth := decomposeString(text, true, false)
|
||||
colorIndices, colors, _, _, escapeIndices, strippedText, strippedWidth := decomposeText(text, true, false)
|
||||
|
||||
// We want to reduce all alignments to AlignLeft.
|
||||
if align == AlignRight {
|
||||
|
@ -302,7 +302,7 @@ func printWithStyle(screen tcell.Screen, text string, x, y, maxWidth, align int,
|
|||
foregroundColor, backgroundColor, attributes string
|
||||
)
|
||||
_, originalBackground, _ := style.Decompose()
|
||||
iterateString(strippedText, func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
|
||||
iterateString(string(strippedText), func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
|
||||
// Update color/escape tag offset and style.
|
||||
if colorPos < len(colorIndices) && textPos+tagOffset >= colorIndices[colorPos][0] && textPos+tagOffset < colorIndices[colorPos][1] {
|
||||
foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colors[colorPos])
|
||||
|
@ -319,7 +319,7 @@ func printWithStyle(screen tcell.Screen, text string, x, y, maxWidth, align int,
|
|||
if escapePos > 0 && textPos+tagOffset-1 >= escapeIndices[escapePos-1][0] && textPos+tagOffset-1 < escapeIndices[escapePos-1][1] {
|
||||
// Unescape open escape sequences.
|
||||
escapeCharPos := escapeIndices[escapePos-1][1] - 2
|
||||
text = text[:escapeCharPos] + text[escapeCharPos+1:]
|
||||
text = append(text[:escapeCharPos], text[escapeCharPos+1:]...)
|
||||
}
|
||||
// Print and return.
|
||||
bytes, width = printWithStyle(screen, text[textPos+tagOffset:], x, y, maxWidth, AlignLeft, style)
|
||||
|
@ -343,14 +343,14 @@ func printWithStyle(screen tcell.Screen, text string, x, y, maxWidth, align int,
|
|||
for rightIndex-1 > leftIndex && strippedWidth-choppedLeft-choppedRight > maxWidth {
|
||||
if choppedLeft < choppedRight {
|
||||
// Iterate on the left by one character.
|
||||
iterateString(strippedText[leftIndex:], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
|
||||
iterateString(string(strippedText[leftIndex:]), func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
|
||||
choppedLeft += screenWidth
|
||||
leftIndex += textWidth
|
||||
return true
|
||||
})
|
||||
} else {
|
||||
// Iterate on the right by one character.
|
||||
iterateStringReverse(strippedText[leftIndex:rightIndex], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
|
||||
iterateStringReverse(string(strippedText[leftIndex:rightIndex]), func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
|
||||
choppedRight += screenWidth
|
||||
rightIndex -= textWidth
|
||||
return true
|
||||
|
@ -371,7 +371,7 @@ func printWithStyle(screen tcell.Screen, text string, x, y, maxWidth, align int,
|
|||
if escapePos > 0 && leftIndex+tagOffset-1 >= escapeIndices[escapePos-1][0] && leftIndex+tagOffset-1 < escapeIndices[escapePos-1][1] {
|
||||
// Unescape open escape sequences.
|
||||
escapeCharPos := escapeIndices[escapePos-1][1] - 2
|
||||
text = text[:escapeCharPos] + text[escapeCharPos+1:]
|
||||
text = append(text[:escapeCharPos], text[escapeCharPos+1:]...)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
@ -399,7 +399,7 @@ func printWithStyle(screen tcell.Screen, text string, x, y, maxWidth, align int,
|
|||
drawn, drawnWidth, colorPos, escapePos, tagOffset int
|
||||
foregroundColor, backgroundColor, attributes string
|
||||
)
|
||||
iterateString(strippedText, func(main rune, comb []rune, textPos, length, screenPos, screenWidth int) bool {
|
||||
iterateString(string(strippedText), func(main rune, comb []rune, textPos, length, screenPos, screenWidth int) bool {
|
||||
// Only continue if there is still space.
|
||||
if drawnWidth+screenWidth > maxWidth {
|
||||
return true
|
||||
|
@ -445,33 +445,21 @@ func printWithStyle(screen tcell.Screen, text string, x, y, maxWidth, align int,
|
|||
}
|
||||
|
||||
// PrintSimple prints white text to the screen at the given position.
|
||||
func PrintSimple(screen tcell.Screen, text string, x, y int) {
|
||||
func PrintSimple(screen tcell.Screen, text []byte, x, y int) {
|
||||
Print(screen, text, x, y, math.MaxInt32, AlignLeft, Styles.PrimaryTextColor)
|
||||
}
|
||||
|
||||
// TaggedTextWidth returns the width of the given string needed to print it on
|
||||
// screen. The text may contain color tags which are not counted.
|
||||
func TaggedTextWidth(text []byte) int {
|
||||
_, _, _, _, _, _, width := decomposeText(text, true, false)
|
||||
return width
|
||||
}
|
||||
|
||||
// TaggedStringWidth returns the width of the given string needed to print it on
|
||||
// screen. The text may contain color tags which are not counted.
|
||||
func TaggedStringWidth(text string) int {
|
||||
_, _, _, _, _, _, width := decomposeString(text, true, false)
|
||||
return width
|
||||
}
|
||||
|
||||
// stringWidth returns the number of horizontal cells needed to print the given
|
||||
// text. It splits the text into its grapheme clusters, calculates each
|
||||
// cluster's width, and adds them up to a total.
|
||||
func stringWidth(text string) (width int) {
|
||||
g := uniseg.NewGraphemes(text)
|
||||
for g.Next() {
|
||||
var chWidth int
|
||||
for _, r := range g.Runes() {
|
||||
chWidth = runewidth.RuneWidth(r)
|
||||
if chWidth > 0 {
|
||||
break // Our best guess at this point is to use the width of the first non-zero-width rune.
|
||||
}
|
||||
}
|
||||
width += chWidth
|
||||
}
|
||||
return
|
||||
return TaggedTextWidth([]byte(text))
|
||||
}
|
||||
|
||||
// WordWrap splits a text such that each resulting line does not exceed the
|
||||
|
@ -481,11 +469,11 @@ func stringWidth(text string) (width int) {
|
|||
// 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) {
|
||||
colorTagIndices, _, _, _, escapeIndices, strippedText, _ := decomposeString(text, true, false)
|
||||
func WordWrap(text []byte, width int) (lines [][]byte) {
|
||||
colorTagIndices, _, _, _, escapeIndices, strippedText, _ := decomposeText(text, true, false)
|
||||
|
||||
// Find candidate breakpoints.
|
||||
breakpoints := boundaryPattern.FindAllStringSubmatchIndex(strippedText, -1)
|
||||
breakpoints := boundaryPattern.FindAllSubmatchIndex(strippedText, -1)
|
||||
// Results in one entry for each candidate. Each entry is an array a of
|
||||
// indices into strippedText where a[6] < 0 for newline/punctuation matches
|
||||
// and a[4] < 0 for whitespace matches.
|
||||
|
@ -497,17 +485,17 @@ func WordWrap(text string, width int) (lines []string) {
|
|||
lineWidth, overflow int
|
||||
forceBreak bool
|
||||
)
|
||||
unescape := func(substr string, startIndex int) string {
|
||||
unescape := func(substr []byte, startIndex int) []byte {
|
||||
// A helper function to unescape escaped tags.
|
||||
for index := escapePos; index >= 0; index-- {
|
||||
if index < len(escapeIndices) && startIndex > escapeIndices[index][0] && startIndex < escapeIndices[index][1]-1 {
|
||||
pos := escapeIndices[index][1] - 2 - startIndex
|
||||
return substr[:pos] + substr[pos+1:]
|
||||
return append(substr[:pos], substr[pos+1:]...)
|
||||
}
|
||||
}
|
||||
return substr
|
||||
}
|
||||
iterateString(strippedText, func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
|
||||
iterateString(string(strippedText), func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
|
||||
// Handle tags.
|
||||
for {
|
||||
if colorPos < len(colorTagIndices) && textPos+tagOffset >= colorTagIndices[colorPos][0] && textPos+tagOffset < colorTagIndices[colorPos][1] {
|
||||
|
@ -603,7 +591,7 @@ func iterateString(text string, callback func(main rune, comb []rune, textPos, t
|
|||
for gr.Next() {
|
||||
r := gr.Runes()
|
||||
from, to := gr.Positions()
|
||||
width := stringWidth(gr.Str())
|
||||
width := runewidth.StringWidth(gr.Str())
|
||||
var comb []rune
|
||||
if len(r) > 1 {
|
||||
comb = r[1:]
|
||||
|
@ -679,7 +667,15 @@ const (
|
|||
ScrollBarAlways
|
||||
)
|
||||
|
||||
// RenderScrollBar renders a scroll bar character at the specified position.
|
||||
// Scroll bar render text (must be one cell wide)
|
||||
var (
|
||||
ScrollBarArea = []byte("[-:-:-]░")
|
||||
ScrollBarAreaFocused = []byte("[-:-:-]▒")
|
||||
ScrollBarHandle = []byte("[-:-:-]▓")
|
||||
ScrollBarHandleFocused = []byte("[::r] [-:-:-]")
|
||||
)
|
||||
|
||||
// RenderScrollBar renders a scroll bar at the specified position.
|
||||
func RenderScrollBar(screen tcell.Screen, visibility ScrollBarVisibility, x int, y int, height int, items int, cursor int, printed int, focused bool, color tcell.Color) {
|
||||
if visibility == ScrollBarNever || (visibility == ScrollBarAuto && items <= height) {
|
||||
return
|
||||
|
@ -698,20 +694,20 @@ func RenderScrollBar(screen tcell.Screen, visibility ScrollBarVisibility, x int,
|
|||
// Calculate handle position.
|
||||
handlePosition := int(float64(height-1) * (float64(cursor) / float64(items-1)))
|
||||
|
||||
// Print character.
|
||||
var scrollBar string
|
||||
// Print scroll bar.
|
||||
var text []byte
|
||||
if printed == handlePosition {
|
||||
if focused {
|
||||
scrollBar = "[::r] [-:-:-]"
|
||||
text = ScrollBarHandleFocused
|
||||
} else {
|
||||
scrollBar = "[-:-:-]▓"
|
||||
text = ScrollBarHandle
|
||||
}
|
||||
} else {
|
||||
if focused {
|
||||
scrollBar = "[-:-:-]▒"
|
||||
text = ScrollBarAreaFocused
|
||||
} else {
|
||||
scrollBar = "[-:-:-]░"
|
||||
text = ScrollBarArea
|
||||
}
|
||||
}
|
||||
Print(screen, scrollBar, x, y, 1, AlignLeft, color)
|
||||
Print(screen, text, x, y, 1, AlignLeft, color)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue