1
0
mirror of https://github.com/taigrr/wtf synced 2026-04-01 20:38:43 -07:00

WTF-539 Add missing vendored dependencies back

This commit is contained in:
Chris Cummer
2019-08-28 07:17:07 -07:00
parent 67658e172c
commit 8df08c4b1f
1668 changed files with 911587 additions and 139 deletions

View File

@@ -65,6 +65,8 @@ 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.20 (2019-07-08)
- Added autocomplete functionality to `InputField`.
- v0.19 (2018-10-28)
- Added `QueueUpdate()` and `QueueEvent()` to `Application` to help with modifications to primitives from goroutines.
- v0.18 (2018-10-18)

View File

@@ -127,6 +127,7 @@ func (a *ansi) Write(text []byte) (int, error) {
"#ffffff",
}[colorNumber]
}
FieldLoop:
for index, field := range fields {
switch field {
case "1", "01":
@@ -185,6 +186,7 @@ func (a *ansi) Write(text []byte) (int, error) {
background = color
}
}
break FieldLoop
}
}
if len(attributes) > 0 || clearAttributes {

View File

@@ -81,7 +81,7 @@ func NewApplication() *Application {
//
// Note that this also affects the default event handling of the application
// itself: Such a handler can intercept the Ctrl-C event which closes the
// applicatoon.
// application.
func (a *Application) SetInputCapture(capture func(event *tcell.EventKey) *tcell.EventKey) *Application {
a.inputCapture = capture
return a

24
vendor/github.com/rivo/tview/box.go generated vendored
View File

@@ -88,7 +88,8 @@ func (b *Box) GetRect() (int, int, int, int) {
}
// GetInnerRect returns the position of the inner rectangle (x, y, width,
// height), without the border and without any padding.
// height), without the border and without any padding. Width and height values
// will clamp to 0 and thus never be negative.
func (b *Box) GetInnerRect() (int, int, int, int) {
if b.innerX >= 0 {
return b.innerX, b.innerY, b.innerWidth, b.innerHeight
@@ -100,10 +101,17 @@ func (b *Box) GetInnerRect() (int, int, int, int) {
width -= 2
height -= 2
}
return x + b.paddingLeft,
y + b.paddingTop,
width - b.paddingLeft - b.paddingRight,
height - b.paddingTop - b.paddingBottom
x, y, width, height = x+b.paddingLeft,
y+b.paddingTop,
width-b.paddingLeft-b.paddingRight,
height-b.paddingTop-b.paddingBottom
if width < 0 {
width = 0
}
if height < 0 {
height = 0
}
return x, y, width, height
}
// SetRect sets a new position of the primitive. Note that this has no effect
@@ -318,6 +326,12 @@ func (b *Box) Draw(screen tcell.Screen) {
b.innerHeight += b.innerY
b.innerY = 0
}
if b.innerWidth < 0 {
b.innerWidth = 0
}
if b.innerHeight < 0 {
b.innerHeight = 0
}
}
// Focus is called when this primitive receives focus.

View File

@@ -22,10 +22,19 @@ type DropDown struct {
// The options from which the user can choose.
options []*dropDownOption
// Strings to be placed before and after each drop-down option.
optionPrefix, optionSuffix string
// The index of the currently selected option. Negative if no option is
// currently selected.
currentOption int
// Strings to be placed beefore and after the current option.
currentOptionPrefix, currentOptionSuffix string
// The text to be displayed when no option has yet been selected.
noSelection string
// Set to true if the options are visible and selectable.
open bool
@@ -74,10 +83,12 @@ type DropDown struct {
// NewDropDown returns a new drop-down.
func NewDropDown() *DropDown {
list := NewList().ShowSecondaryText(false)
list.SetMainTextColor(Styles.PrimitiveBackgroundColor).
list := NewList()
list.ShowSecondaryText(false).
SetMainTextColor(Styles.PrimitiveBackgroundColor).
SetSelectedTextColor(Styles.PrimitiveBackgroundColor).
SetSelectedBackgroundColor(Styles.PrimaryTextColor).
SetHighlightFullLine(true).
SetBackgroundColor(Styles.MoreContrastBackgroundColor)
d := &DropDown{
@@ -128,6 +139,23 @@ func (d *DropDown) GetCurrentOption() (int, string) {
return d.currentOption, text
}
// SetTextOptions sets the text to be placed before and after each drop-down
// option (prefix/suffix), the text placed before and after the currently
// selected option (currentPrefix/currentSuffix) as well as the text to be
// displayed when no option is currently selected. Per default, all of these
// strings are empty.
func (d *DropDown) SetTextOptions(prefix, suffix, currentPrefix, currentSuffix, noSelection string) *DropDown {
d.currentOptionPrefix = currentPrefix
d.currentOptionSuffix = currentSuffix
d.noSelection = noSelection
d.optionPrefix = prefix
d.optionSuffix = suffix
for index := 0; index < d.list.GetItemCount(); index++ {
d.list.SetItemText(index, prefix+d.options[index].Text+suffix, "")
}
return d
}
// SetLabel sets the text to be displayed before the input area.
func (d *DropDown) SetLabel(label string) *DropDown {
d.label = label
@@ -208,7 +236,7 @@ func (d *DropDown) GetFieldWidth() int {
// callback is called when this option was selected. It may be nil.
func (d *DropDown) AddOption(text string, selected func()) *DropDown {
d.options = append(d.options, &dropDownOption{Text: text, Selected: selected})
d.list.AddItem(text, "", 0, nil)
d.list.AddItem(d.optionPrefix+text+d.optionSuffix, "", 0, nil)
return d
}
@@ -282,8 +310,9 @@ func (d *DropDown) Draw(screen tcell.Screen) {
// What's the longest option text?
maxWidth := 0
optionWrapWidth := TaggedStringWidth(d.optionPrefix + d.optionSuffix)
for _, option := range d.options {
strWidth := TaggedStringWidth(option.Text)
strWidth := TaggedStringWidth(option.Text) + optionWrapWidth
if strWidth > maxWidth {
maxWidth = strWidth
}
@@ -293,6 +322,17 @@ func (d *DropDown) Draw(screen tcell.Screen) {
fieldWidth := d.fieldWidth
if fieldWidth == 0 {
fieldWidth = maxWidth
if d.currentOption < 0 {
noSelectionWidth := TaggedStringWidth(d.noSelection)
if noSelectionWidth > fieldWidth {
fieldWidth = noSelectionWidth
}
} else if d.currentOption < len(d.options) {
currentOptionWidth := TaggedStringWidth(d.currentOptionPrefix + d.options[d.currentOption].Text + d.currentOptionSuffix)
if currentOptionWidth > fieldWidth {
fieldWidth = currentOptionWidth
}
}
}
if rightLimit-x < fieldWidth {
fieldWidth = rightLimit - x
@@ -308,21 +348,25 @@ func (d *DropDown) Draw(screen tcell.Screen) {
// Draw selected text.
if d.open && len(d.prefix) > 0 {
// Show the prefix.
Print(screen, d.prefix, x, y, fieldWidth, AlignLeft, d.prefixTextColor)
currentOptionPrefixWidth := TaggedStringWidth(d.currentOptionPrefix)
prefixWidth := stringWidth(d.prefix)
listItemText := d.options[d.list.GetCurrentItem()].Text
if prefixWidth < fieldWidth && len(d.prefix) < len(listItemText) {
Print(screen, listItemText[len(d.prefix):], x+prefixWidth, y, fieldWidth-prefixWidth, AlignLeft, d.fieldTextColor)
Print(screen, d.currentOptionPrefix, x, y, fieldWidth, AlignLeft, d.fieldTextColor)
Print(screen, 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, d.fieldTextColor)
}
} else {
color := d.fieldTextColor
text := d.noSelection
if d.currentOption >= 0 && d.currentOption < len(d.options) {
color := d.fieldTextColor
// Just show the current selection.
if d.GetFocusable().HasFocus() && !d.open {
color = d.fieldBackgroundColor
}
Print(screen, d.options[d.currentOption].Text, x, y, fieldWidth, AlignLeft, color)
text = d.currentOptionPrefix + d.options[d.currentOption].Text + d.currentOptionSuffix
}
// Just show the current selection.
if d.GetFocusable().HasFocus() && !d.open {
color = d.fieldBackgroundColor
}
Print(screen, text, x, y, fieldWidth, AlignLeft, color)
}
// Draw options list.

View File

@@ -209,8 +209,8 @@ func (f *Form) AddPasswordField(label, value string, fieldWidth int, mask rune,
func (f *Form) AddDropDown(label string, options []string, initialOption int, selected func(option string, optionIndex int)) *Form {
f.items = append(f.items, NewDropDown().
SetLabel(label).
SetCurrentOption(initialOption).
SetOptions(options, selected))
SetOptions(options, selected).
SetCurrentOption(initialOption))
return f
}

142
vendor/github.com/rivo/tview/grid.go generated vendored
View File

@@ -68,7 +68,7 @@ type Grid struct {
// grid.SetBackgroundColor(tview.Styles.PrimitiveBackgroundColor)
func NewGrid() *Grid {
g := &Grid{
Box: NewBox().SetBackgroundColor(tcell.ColorDefault),
Box: NewBox(),
bordersColor: Styles.GraphicsColor,
}
g.focus = g
@@ -230,7 +230,7 @@ func (g *Grid) Clear() *Grid {
// drawing the first grid cell in the top-left corner. As the grid will never
// completely move off the screen, these values may be adjusted the next time
// the grid is drawn. The actual position of the grid may also be adjusted such
// that contained primitives that have focus are visible.
// that contained primitives that have focus remain visible.
func (g *Grid) SetOffset(rows, columns int) *Grid {
g.rowOffset, g.columnOffset = rows, columns
return g
@@ -392,8 +392,6 @@ func (g *Grid) Draw(screen tcell.Screen) {
}
// Distribute proportional rows/columns.
gridWidth := 0
gridHeight := 0
for index := 0; index < rows; index++ {
row := 0
if index < len(g.rows) {
@@ -403,7 +401,6 @@ func (g *Grid) Draw(screen tcell.Screen) {
if row < g.minHeight {
row = g.minHeight
}
gridHeight += row
continue // Not proportional. We already know the width.
} else if row == 0 {
row = 1
@@ -417,7 +414,6 @@ func (g *Grid) Draw(screen tcell.Screen) {
rowAbs = g.minHeight
}
rowHeight[index] = rowAbs
gridHeight += rowAbs
}
for index := 0; index < columns; index++ {
column := 0
@@ -428,7 +424,6 @@ func (g *Grid) Draw(screen tcell.Screen) {
if column < g.minWidth {
column = g.minWidth
}
gridWidth += column
continue // Not proportional. We already know the height.
} else if column == 0 {
column = 1
@@ -442,18 +437,10 @@ func (g *Grid) Draw(screen tcell.Screen) {
columnAbs = g.minWidth
}
columnWidth[index] = columnAbs
gridWidth += columnAbs
}
if g.borders {
gridHeight += rows + 1
gridWidth += columns + 1
} else {
gridHeight += (rows - 1) * g.gapRows
gridWidth += (columns - 1) * g.gapColumns
}
// Calculate row/column positions.
columnX, rowY := x, y
var columnX, rowY int
if g.borders {
columnX++
rowY++
@@ -502,50 +489,87 @@ func (g *Grid) Draw(screen tcell.Screen) {
}
// Calculate screen offsets.
var offsetX, offsetY, add int
if g.rowOffset < 0 {
g.rowOffset = 0
var offsetX, offsetY int
add := 1
if !g.borders {
add = g.gapRows
}
if g.columnOffset < 0 {
g.columnOffset = 0
for index, height := range rowHeight {
if index >= g.rowOffset {
break
}
offsetY += height + add
}
if !g.borders {
add = g.gapColumns
}
for index, width := range columnWidth {
if index >= g.columnOffset {
break
}
offsetX += width + add
}
// Line up the last row/column with the end of the available area.
var border int
if g.borders {
add = 1
border = 1
}
for row := 0; row < rows-1; row++ {
remainingHeight := gridHeight - offsetY
if focus != nil && focus.y-add <= offsetY || // Don't let the focused item move out of screen.
row >= g.rowOffset && (focus == nil || focus != nil && focus.y-offsetY < height) || // We've reached the requested offset.
remainingHeight <= height { // We have enough space to show the rest.
if row > 0 {
if focus != nil && focus.y+focus.h+add-offsetY > height {
offsetY += focus.y + focus.h + add - offsetY - height
}
if remainingHeight < height {
offsetY = gridHeight - height
}
}
g.rowOffset = row
break
}
offsetY = rowPos[row+1] - add
last := len(rowPos) - 1
if rowPos[last]+rowHeight[last]+border-offsetY < height {
offsetY = rowPos[last] - height + rowHeight[last] + border
}
for column := 0; column < columns-1; column++ {
remainingWidth := gridWidth - offsetX
if focus != nil && focus.x-add <= offsetX || // Don't let the focused item move out of screen.
column >= g.columnOffset && (focus == nil || focus != nil && focus.x-offsetX < width) || // We've reached the requested offset.
remainingWidth <= width { // We have enough space to show the rest.
if column > 0 {
if focus != nil && focus.x+focus.w+add-offsetX > width {
offsetX += focus.x + focus.w + add - offsetX - width
} else if remainingWidth < width {
offsetX = gridWidth - width
}
}
g.columnOffset = column
break
last = len(columnPos) - 1
if columnPos[last]+columnWidth[last]+border-offsetX < width {
offsetX = columnPos[last] - width + columnWidth[last] + border
}
// The focused item must be within the visible area.
if focus != nil {
if focus.y+focus.h-offsetY >= height {
offsetY = focus.y - height + focus.h
}
offsetX = columnPos[column+1] - add
if focus.y-offsetY < 0 {
offsetY = focus.y
}
if focus.x+focus.w-offsetX >= width {
offsetX = focus.x - width + focus.w
}
if focus.x-offsetX < 0 {
offsetX = focus.x
}
}
// Adjust row/column offsets based on this value.
var from, to int
for index, pos := range rowPos {
if pos-offsetY < 0 {
from = index + 1
}
if pos-offsetY < height {
to = index
}
}
if g.rowOffset < from {
g.rowOffset = from
}
if g.rowOffset > to {
g.rowOffset = to
}
from, to = 0, 0
for index, pos := range columnPos {
if pos-offsetX < 0 {
from = index + 1
}
if pos-offsetX < width {
to = index
}
}
if g.columnOffset < from {
g.columnOffset = from
}
if g.columnOffset > to {
g.columnOffset = to
}
// Draw primitives and borders.
@@ -556,10 +580,14 @@ func (g *Grid) Draw(screen tcell.Screen) {
}
item.x -= offsetX
item.y -= offsetY
if item.x+item.w > x+width {
if item.x >= width || item.x+item.w <= 0 || item.y >= height || item.y+item.h <= 0 {
item.visible = false
continue
}
if item.x+item.w > width {
item.w = width - item.x
}
if item.y+item.h > y+height {
if item.y+item.h > height {
item.h = height - item.y
}
if item.x < 0 {
@@ -574,6 +602,8 @@ func (g *Grid) Draw(screen tcell.Screen) {
item.visible = false
continue
}
item.x += x
item.y += y
primitive.SetRect(item.x, item.y, item.w, item.h)
// Draw primitive.

View File

@@ -4,6 +4,7 @@ import (
"math"
"regexp"
"strings"
"sync"
"unicode/utf8"
"github.com/gdamore/tcell"
@@ -71,6 +72,16 @@ type InputField struct {
// The number of bytes of the text string skipped ahead while drawing.
offset int
// An optional autocomplete function which receives the current text of the
// input field and returns a slice of strings to be displayed in a drop-down
// selection.
autocomplete func(text string) []string
// The List object which shows the selectable autocomplete entries. If not
// nil, the list's main texts represent the current autocomplete entries.
autocompleteList *List
autocompleteListMutex sync.Mutex
// An optional function which may reject the last character that was entered.
accept func(text string, ch rune) bool
@@ -190,6 +201,70 @@ func (i *InputField) SetMaskCharacter(mask rune) *InputField {
return i
}
// SetAutocompleteFunc sets an autocomplete callback function which may return
// strings to be selected from a drop-down based on the current text of the
// input field. The drop-down appears only if len(entries) > 0. The callback is
// invoked in this function and whenever the current text changes or when
// Autocomplete() is called. Entries are cleared when the user selects an entry
// or presses Escape.
func (i *InputField) SetAutocompleteFunc(callback func(currentText string) (entries []string)) *InputField {
i.autocomplete = callback
i.Autocomplete()
return i
}
// Autocomplete invokes the autocomplete callback (if there is one). If the
// length of the returned autocomplete entries slice is greater than 0, the
// input field will present the user with a corresponding drop-down list the
// next time the input field is drawn.
//
// It is safe to call this function from any goroutine. Note that the input
// field is not redrawn automatically unless called from the main goroutine
// (e.g. in response to events).
func (i *InputField) Autocomplete() *InputField {
i.autocompleteListMutex.Lock()
defer i.autocompleteListMutex.Unlock()
if i.autocomplete == nil {
return i
}
// Do we have any autocomplete entries?
entries := i.autocomplete(i.text)
if len(entries) == 0 {
// No entries, no list.
i.autocompleteList = nil
return i
}
// Make a list if we have none.
if i.autocompleteList == nil {
i.autocompleteList = NewList()
i.autocompleteList.ShowSecondaryText(false).
SetMainTextColor(Styles.PrimitiveBackgroundColor).
SetSelectedTextColor(Styles.PrimitiveBackgroundColor).
SetSelectedBackgroundColor(Styles.PrimaryTextColor).
SetHighlightFullLine(true).
SetBackgroundColor(Styles.MoreContrastBackgroundColor)
}
// Fill it with the entries.
currentEntry := -1
i.autocompleteList.Clear()
for index, entry := range entries {
i.autocompleteList.AddItem(entry, "", 0, nil)
if currentEntry < 0 && entry == i.text {
currentEntry = index
}
}
// Set the selection if we have one.
if currentEntry >= 0 {
i.autocompleteList.SetCurrentItem(currentEntry)
}
return i
}
// SetAcceptanceFunc sets a handler which may reject the last character that was
// entered (by returning false).
//
@@ -319,6 +394,38 @@ func (i *InputField) Draw(screen tcell.Screen) {
}
}
// Draw autocomplete list.
i.autocompleteListMutex.Lock()
defer i.autocompleteListMutex.Unlock()
if i.autocompleteList != nil {
// How much space do we need?
lheight := i.autocompleteList.GetItemCount()
lwidth := 0
for index := 0; index < lheight; index++ {
entry, _ := i.autocompleteList.GetItemText(index)
width := TaggedStringWidth(entry)
if width > lwidth {
lwidth = width
}
}
// We prefer to drop down but if there is no space, maybe drop up?
lx := x
ly := y + 1
_, sheight := screen.Size()
if ly+lheight >= sheight && ly-2 > lheight-ly {
ly = y - lheight
if ly < 0 {
ly = 0
}
}
if ly+lheight >= sheight {
lheight = sheight - ly
}
i.autocompleteList.SetRect(lx, ly, lwidth, lheight)
i.autocompleteList.Draw(screen)
}
// Set cursor.
if i.focus.HasFocus() {
screen.ShowCursor(x+cursorScreenPos, y)
@@ -331,8 +438,11 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
// Trigger changed events.
currentText := i.text
defer func() {
if i.text != currentText && i.changed != nil {
i.changed(i.text)
if i.text != currentText {
i.Autocomplete()
if i.changed != nil {
i.changed(i.text)
}
}
}()
@@ -370,7 +480,19 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
return true
}
// Finish up.
finish := func(key tcell.Key) {
if i.done != nil {
i.done(key)
}
if i.finished != nil {
i.finished(key)
}
}
// Process key event.
i.autocompleteListMutex.Lock()
defer i.autocompleteListMutex.Unlock()
switch key := event.Key(); key {
case tcell.KeyRune: // Regular character.
if event.Modifiers()&tcell.ModAlt > 0 {
@@ -435,12 +557,36 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
home()
case tcell.KeyEnd, tcell.KeyCtrlE:
end()
case tcell.KeyEnter, tcell.KeyTab, tcell.KeyBacktab, tcell.KeyEscape: // We're done.
if i.done != nil {
i.done(key)
case tcell.KeyEnter, tcell.KeyEscape: // We might be done.
if i.autocompleteList != nil {
i.autocompleteList = nil
} else {
finish(key)
}
if i.finished != nil {
i.finished(key)
case tcell.KeyDown, tcell.KeyTab: // Autocomplete selection.
if i.autocompleteList != nil {
count := i.autocompleteList.GetItemCount()
newEntry := i.autocompleteList.GetCurrentItem() + 1
if newEntry >= count {
newEntry = 0
}
i.autocompleteList.SetCurrentItem(newEntry)
currentText, _ = i.autocompleteList.GetItemText(newEntry) // Don't trigger changed function twice.
i.SetText(currentText)
} else {
finish(key)
}
case tcell.KeyUp, tcell.KeyBacktab: // Autocomplete selection.
if i.autocompleteList != nil {
newEntry := i.autocompleteList.GetCurrentItem() - 1
if newEntry < 0 {
newEntry = i.autocompleteList.GetItemCount() - 1
}
i.autocompleteList.SetCurrentItem(newEntry)
currentText, _ = i.autocompleteList.GetItemText(newEntry) // Don't trigger changed function twice.
i.SetText(currentText)
} else {
finish(key)
}
}
})

View File

@@ -95,13 +95,14 @@ func (l *List) SetCurrentItem(index int) *List {
if index < 0 {
index = 0
}
l.currentItem = index
if index != l.currentItem && l.changed != nil {
item := l.items[l.currentItem]
l.changed(l.currentItem, item.MainText, item.SecondaryText, item.Shortcut)
item := l.items[index]
l.changed(index, item.MainText, item.SecondaryText, item.Shortcut)
}
l.currentItem = index
return l
}

View File

@@ -355,9 +355,14 @@ func (t *Table) GetSelection() (row, column int) {
// Select sets the selected cell. Depending on the selection settings
// specified via SetSelectable(), this may be an entire row or column, or even
// ignored completely.
// ignored completely. The "selection changed" event is fired if such a callback
// is available (even if the selection ends up being the same as before, even if
// cells are not selectable).
func (t *Table) Select(row, column int) *Table {
t.selectedRow, t.selectedColumn = row, column
if t.selectionChanged != nil {
t.selectionChanged(row, column)
}
return t
}
@@ -387,10 +392,10 @@ func (t *Table) SetSelectedFunc(handler func(row, column int)) *Table {
return t
}
// SetSelectionChangedFunc sets a handler which is called whenever the user
// navigates to a new selection. The handler receives the position of the new
// selection. If entire rows are selected, the column index is undefined.
// Likewise for entire columns.
// SetSelectionChangedFunc sets a handler which is called whenever the current
// selection changes. The handler receives the position of the new selection.
// If entire rows are selected, the column index is undefined. Likewise for
// entire columns.
func (t *Table) SetSelectionChangedFunc(handler func(row, column int)) *Table {
t.selectionChanged = handler
return t

View File

@@ -939,8 +939,13 @@ func (t *TextView) Draw(screen tcell.Screen) {
// If this view is not scrollable, we'll purge the buffer of lines that have
// scrolled out of view.
if !t.scrollable && t.lineOffset > 0 {
t.buffer = t.buffer[t.index[t.lineOffset].Line:]
if t.lineOffset <= len(t.index) {
t.buffer = nil
} else {
t.buffer = t.buffer[t.index[t.lineOffset].Line:]
}
t.index = nil
t.lineOffset = 0
}
}

86
vendor/github.com/rivo/tview/util.go generated vendored
View File

@@ -24,7 +24,7 @@ var (
regionPattern = regexp.MustCompile(`\["([a-zA-Z0-9_,;: \-\.]*)"\]`)
escapePattern = regexp.MustCompile(`\[([a-zA-Z0-9_,;: \-\."#]+)\[(\[*)\]`)
nonEscapePattern = regexp.MustCompile(`(\[[a-zA-Z0-9_,;: \-\."#]+\[*)\]`)
boundaryPattern = regexp.MustCompile(`(([[:punct:]]|\n)[ \t\f\r]*|(\s+))`)
boundaryPattern = regexp.MustCompile(`(([,\.\-:;!\?&#+]|\n)[ \t\f\r]*|([ \t\f\r]+))`)
spacePattern = regexp.MustCompile(`\s+`)
)
@@ -454,8 +454,8 @@ func WordWrap(text string, width int) (lines []string) {
var (
colorPos, escapePos, breakpointPos, tagOffset int
lastBreakpoint, lastContinuation, currentLineStart int
lineWidth, continuationWidth int
newlineBreakpoint bool
lineWidth, overflow int
forceBreak bool
)
unescape := func(substr string, startIndex int) string {
// A helper function to unescape escaped tags.
@@ -468,54 +468,65 @@ func WordWrap(text string, width int) (lines []string) {
return substr
}
iterateString(strippedText, func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
// Handle colour tags.
if colorPos < len(colorTagIndices) && textPos+tagOffset >= colorTagIndices[colorPos][0] && textPos+tagOffset < colorTagIndices[colorPos][1] {
tagOffset += colorTagIndices[colorPos][1] - colorTagIndices[colorPos][0]
colorPos++
}
// Handle escape tags.
if escapePos < len(escapeIndices) && textPos+tagOffset == escapeIndices[escapePos][1]-2 {
tagOffset++
escapePos++
}
// Check if a break is warranted.
afterContinuation := lastContinuation > 0 && textPos+tagOffset >= lastContinuation
noBreakpoint := lastContinuation == 0
beyondWidth := lineWidth > 0 && lineWidth > width
if beyondWidth && noBreakpoint {
// We need a hard break without a breakpoint.
lines = append(lines, unescape(text[currentLineStart:textPos+tagOffset], currentLineStart))
currentLineStart = textPos + tagOffset
lineWidth = continuationWidth
} else if afterContinuation && (beyondWidth || newlineBreakpoint) {
// Break at last breakpoint or at newline.
lines = append(lines, unescape(text[currentLineStart:lastBreakpoint], currentLineStart))
currentLineStart = lastContinuation
lineWidth = continuationWidth
lastBreakpoint, lastContinuation, newlineBreakpoint = 0, 0, false
// Handle tags.
for {
if colorPos < len(colorTagIndices) && textPos+tagOffset >= colorTagIndices[colorPos][0] && textPos+tagOffset < colorTagIndices[colorPos][1] {
// Colour tags.
tagOffset += colorTagIndices[colorPos][1] - colorTagIndices[colorPos][0]
colorPos++
} else if escapePos < len(escapeIndices) && textPos+tagOffset == escapeIndices[escapePos][1]-2 {
// Escape tags.
tagOffset++
escapePos++
} else {
break
}
}
// Is this a breakpoint?
if breakpointPos < len(breakpoints) && textPos == breakpoints[breakpointPos][0] {
if breakpointPos < len(breakpoints) && textPos+tagOffset == breakpoints[breakpointPos][0] {
// Yes, it is. Set up breakpoint infos depending on its type.
lastBreakpoint = breakpoints[breakpointPos][0] + tagOffset
lastContinuation = breakpoints[breakpointPos][1] + tagOffset
newlineBreakpoint = main == '\n'
if breakpoints[breakpointPos][6] < 0 && !newlineBreakpoint {
overflow = 0
forceBreak = main == '\n'
if breakpoints[breakpointPos][6] < 0 && !forceBreak {
lastBreakpoint++ // Don't skip punctuation.
}
breakpointPos++
}
// Once we hit the continuation point, we start buffering widths.
if textPos+tagOffset < lastContinuation {
continuationWidth = 0
// Check if a break is warranted.
if forceBreak || lineWidth > 0 && lineWidth+screenWidth > width {
breakpoint := lastBreakpoint
continuation := lastContinuation
if forceBreak {
breakpoint = textPos + tagOffset
continuation = textPos + tagOffset + 1
lastBreakpoint = 0
overflow = 0
} else if lastBreakpoint <= currentLineStart {
breakpoint = textPos + tagOffset
continuation = textPos + tagOffset
overflow = 0
}
lines = append(lines, unescape(text[currentLineStart:breakpoint], currentLineStart))
currentLineStart, lineWidth, forceBreak = continuation, overflow, false
}
// Remember the characters since the last breakpoint.
if lastBreakpoint > 0 && lastContinuation <= textPos+tagOffset {
overflow += screenWidth
}
// Advance.
lineWidth += screenWidth
continuationWidth += screenWidth
// But if we're still inside a breakpoint, skip next character (whitespace).
if textPos+tagOffset < currentLineStart {
lineWidth -= screenWidth
}
return false
})
@@ -558,7 +569,6 @@ func iterateString(text string, callback func(main rune, comb []rune, textPos, t
comb = r[1:]
}
// panic(fmt.Sprintf(`from=%d to=%d screenPos=%d width=%d`, from, to, screenPos, width))
if callback(r[0], comb, from, to-from, screenPos, width) {
return true
}