mirror of
https://github.com/taigrr/wtf
synced 2026-04-01 20:38:43 -07:00
Update dependencies
This commit is contained in:
2
vendor/github.com/rivo/tview/README.md
generated
vendored
2
vendor/github.com/rivo/tview/README.md
generated
vendored
@@ -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.18 (2018-10-18)
|
||||
- `InputField` elements can now be navigated freely.
|
||||
- v0.17 (2018-06-20)
|
||||
- Added `TreeView`.
|
||||
- v0.15 (2018-05-02)
|
||||
|
||||
47
vendor/github.com/rivo/tview/application.go
generated
vendored
47
vendor/github.com/rivo/tview/application.go
generated
vendored
@@ -19,6 +19,9 @@ type Application struct {
|
||||
// The application's screen.
|
||||
screen tcell.Screen
|
||||
|
||||
// Indicates whether the application's screen is currently active.
|
||||
running bool
|
||||
|
||||
// The primitive which currently has the keyboard focus.
|
||||
focus Primitive
|
||||
|
||||
@@ -70,22 +73,53 @@ func (a *Application) GetInputCapture() func(event *tcell.EventKey) *tcell.Event
|
||||
return a.inputCapture
|
||||
}
|
||||
|
||||
// SetScreen allows you to provide your own tcell.Screen object. For most
|
||||
// applications, this is not needed and you should be familiar with
|
||||
// tcell.Screen when using this function. Run() will call Init() and Fini() on
|
||||
// the provided screen object.
|
||||
//
|
||||
// This function is typically called before calling Run(). Calling it while an
|
||||
// application is running will switch the application to the new screen. Fini()
|
||||
// will be called on the old screen and Init() on the new screen (errors
|
||||
// returned by Init() will lead to a panic).
|
||||
//
|
||||
// Note that calling Suspend() will invoke Fini() on your screen object and it
|
||||
// will not be restored when suspended mode ends. Instead, a new default screen
|
||||
// object will be created.
|
||||
func (a *Application) SetScreen(screen tcell.Screen) *Application {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
if a.running {
|
||||
a.screen.Fini()
|
||||
}
|
||||
a.screen = screen
|
||||
if a.running {
|
||||
if err := a.screen.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Run starts the application and thus the event loop. This function returns
|
||||
// when Stop() was called.
|
||||
func (a *Application) Run() error {
|
||||
var err error
|
||||
a.Lock()
|
||||
|
||||
// Make a screen.
|
||||
a.screen, err = tcell.NewScreen()
|
||||
if err != nil {
|
||||
a.Unlock()
|
||||
return err
|
||||
// Make a screen if there is none yet.
|
||||
if a.screen == nil {
|
||||
a.screen, err = tcell.NewScreen()
|
||||
if err != nil {
|
||||
a.Unlock()
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err = a.screen.Init(); err != nil {
|
||||
a.Unlock()
|
||||
return err
|
||||
}
|
||||
a.running = true
|
||||
|
||||
// We catch panics to clean up because they mess up the terminal.
|
||||
defer func() {
|
||||
@@ -93,6 +127,7 @@ func (a *Application) Run() error {
|
||||
if a.screen != nil {
|
||||
a.screen.Fini()
|
||||
}
|
||||
a.running = false
|
||||
panic(p)
|
||||
}
|
||||
}()
|
||||
@@ -170,6 +205,7 @@ func (a *Application) Stop() {
|
||||
}
|
||||
a.screen.Fini()
|
||||
a.screen = nil
|
||||
a.running = false
|
||||
}
|
||||
|
||||
// Suspend temporarily suspends the application by exiting terminal UI mode and
|
||||
@@ -217,6 +253,7 @@ func (a *Application) Suspend(f func()) bool {
|
||||
a.Unlock()
|
||||
panic(err)
|
||||
}
|
||||
a.running = true
|
||||
a.Unlock()
|
||||
a.Draw()
|
||||
|
||||
|
||||
40
vendor/github.com/rivo/tview/box.go
generated
vendored
40
vendor/github.com/rivo/tview/box.go
generated
vendored
@@ -51,10 +51,6 @@ type Box struct {
|
||||
// Whether or not this box has focus.
|
||||
hasFocus bool
|
||||
|
||||
// If set to true, the inner rect of this box will be within the screen at the
|
||||
// last time the box was drawn.
|
||||
clampToScreen bool
|
||||
|
||||
// An optional capture function which receives a key event and returns the
|
||||
// event to be forwarded to the primitive's default input handler (nil if
|
||||
// nothing should be forwarded).
|
||||
@@ -74,7 +70,6 @@ func NewBox() *Box {
|
||||
borderColor: Styles.BorderColor,
|
||||
titleColor: Styles.TitleColor,
|
||||
titleAlign: AlignCenter,
|
||||
clampToScreen: true,
|
||||
}
|
||||
b.focus = b
|
||||
return b
|
||||
@@ -117,6 +112,7 @@ func (b *Box) SetRect(x, y, width, height int) {
|
||||
b.y = y
|
||||
b.width = width
|
||||
b.height = height
|
||||
b.innerX = -1 // Mark inner rect as uninitialized.
|
||||
}
|
||||
|
||||
// SetDrawFunc sets a callback function which is invoked after the box primitive
|
||||
@@ -277,8 +273,8 @@ func (b *Box) Draw(screen tcell.Screen) {
|
||||
|
||||
// Draw title.
|
||||
if b.title != "" && b.width >= 4 {
|
||||
_, 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 {
|
||||
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)
|
||||
@@ -296,22 +292,20 @@ func (b *Box) Draw(screen tcell.Screen) {
|
||||
}
|
||||
|
||||
// Clamp inner rect to screen.
|
||||
if b.clampToScreen {
|
||||
width, height := screen.Size()
|
||||
if b.innerX < 0 {
|
||||
b.innerWidth += b.innerX
|
||||
b.innerX = 0
|
||||
}
|
||||
if b.innerX+b.innerWidth >= width {
|
||||
b.innerWidth = width - b.innerX
|
||||
}
|
||||
if b.innerY+b.innerHeight >= height {
|
||||
b.innerHeight = height - b.innerY
|
||||
}
|
||||
if b.innerY < 0 {
|
||||
b.innerHeight += b.innerY
|
||||
b.innerY = 0
|
||||
}
|
||||
width, height := screen.Size()
|
||||
if b.innerX < 0 {
|
||||
b.innerWidth += b.innerX
|
||||
b.innerX = 0
|
||||
}
|
||||
if b.innerX+b.innerWidth >= width {
|
||||
b.innerWidth = width - b.innerX
|
||||
}
|
||||
if b.innerY+b.innerHeight >= height {
|
||||
b.innerHeight = height - b.innerY
|
||||
}
|
||||
if b.innerY < 0 {
|
||||
b.innerHeight += b.innerY
|
||||
b.innerY = 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
6
vendor/github.com/rivo/tview/checkbox.go
generated
vendored
6
vendor/github.com/rivo/tview/checkbox.go
generated
vendored
@@ -124,9 +124,9 @@ func (c *Checkbox) SetChangedFunc(handler func(checked bool)) *Checkbox {
|
||||
return c
|
||||
}
|
||||
|
||||
// SetDoneFunc sets a handler which is called when the user is done entering
|
||||
// text. The callback function is provided with the key that was pressed, which
|
||||
// is one of the following:
|
||||
// SetDoneFunc sets a handler which is called when the user is done using the
|
||||
// checkbox. The callback function is provided with the key that was pressed,
|
||||
// which is one of the following:
|
||||
//
|
||||
// - KeyEscape: Abort text input.
|
||||
// - KeyTab: Move to the next field.
|
||||
|
||||
12
vendor/github.com/rivo/tview/form.go
generated
vendored
12
vendor/github.com/rivo/tview/form.go
generated
vendored
@@ -218,6 +218,13 @@ func (f *Form) AddButton(label string, selected func()) *Form {
|
||||
return f
|
||||
}
|
||||
|
||||
// GetButton returns the button at the specified 0-based index. Note that
|
||||
// buttons have been specially prepared for this form and modifying some of
|
||||
// their attributes may have unintended side effects.
|
||||
func (f *Form) GetButton(index int) *Button {
|
||||
return f.buttons[index]
|
||||
}
|
||||
|
||||
// RemoveButton removes the button at the specified position, starting with 0
|
||||
// for the button that was added first.
|
||||
func (f *Form) RemoveButton(index int) *Form {
|
||||
@@ -225,6 +232,11 @@ func (f *Form) RemoveButton(index int) *Form {
|
||||
return f
|
||||
}
|
||||
|
||||
// GetButtonCount returns the number of buttons in this form.
|
||||
func (f *Form) GetButtonCount() int {
|
||||
return len(f.buttons)
|
||||
}
|
||||
|
||||
// GetButtonIndex returns the index of the button with the given label, starting
|
||||
// with 0 for the button that was added first. If no such label was found, -1
|
||||
// is returned.
|
||||
|
||||
200
vendor/github.com/rivo/tview/inputfield.go
generated
vendored
200
vendor/github.com/rivo/tview/inputfield.go
generated
vendored
@@ -11,10 +11,23 @@ import (
|
||||
)
|
||||
|
||||
// InputField is a one-line box (three lines if there is a title) where the
|
||||
// user can enter text.
|
||||
// user can enter text. Use SetAcceptanceFunc() to accept or reject input,
|
||||
// SetChangedFunc() to listen for changes, and SetMaskCharacter() to hide input
|
||||
// from onlookers (e.g. for password input).
|
||||
//
|
||||
// Use SetMaskCharacter() to hide input from onlookers (e.g. for password
|
||||
// input).
|
||||
// The following keys can be used for navigation and editing:
|
||||
//
|
||||
// - Left arrow: Move left by one character.
|
||||
// - Right arrow: Move right by one character.
|
||||
// - Home, Ctrl-A, Alt-a: Move to the beginning of the line.
|
||||
// - End, Ctrl-E, Alt-e: Move to the end of the line.
|
||||
// - Alt-left, Alt-b: Move left by one word.
|
||||
// - Alt-right, Alt-f: Move right by one word.
|
||||
// - Backspace: Delete the character before the cursor.
|
||||
// - Delete: Delete the character after the cursor.
|
||||
// - Ctrl-K: Delete from the cursor to the end of the line.
|
||||
// - Ctrl-W: Delete the last word before the cursor.
|
||||
// - Ctrl-U: Delete the entire line.
|
||||
//
|
||||
// See https://github.com/rivo/tview/wiki/InputField for an example.
|
||||
type InputField struct {
|
||||
@@ -53,6 +66,12 @@ type InputField struct {
|
||||
// disables masking.
|
||||
maskCharacter rune
|
||||
|
||||
// The cursor position as a byte index into the text string.
|
||||
cursorPos int
|
||||
|
||||
// The number of bytes of the text string skipped ahead while drawing.
|
||||
offset int
|
||||
|
||||
// An optional function which may reject the last character that was entered.
|
||||
accept func(text string, ch rune) bool
|
||||
|
||||
@@ -174,7 +193,7 @@ func (i *InputField) SetMaskCharacter(mask rune) *InputField {
|
||||
// SetAcceptanceFunc sets a handler which may reject the last character that was
|
||||
// entered (by returning false).
|
||||
//
|
||||
// This package defines a number of variables Prefixed with InputField which may
|
||||
// This package defines a number of variables prefixed with InputField which may
|
||||
// be used for common input (e.g. numbers, maximum text length).
|
||||
func (i *InputField) SetAcceptanceFunc(handler func(textToCheck string, lastChar rune) bool) *InputField {
|
||||
i.accept = handler
|
||||
@@ -244,56 +263,69 @@ func (i *InputField) Draw(screen tcell.Screen) {
|
||||
screen.SetContent(x+index, y, ' ', nil, fieldStyle)
|
||||
}
|
||||
|
||||
// Draw placeholder text.
|
||||
// Text.
|
||||
var cursorScreenPos int
|
||||
text := i.text
|
||||
if text == "" && i.placeholder != "" {
|
||||
Print(screen, i.placeholder, x, y, fieldWidth, AlignLeft, i.placeholderTextColor)
|
||||
// Draw placeholder text.
|
||||
Print(screen, Escape(i.placeholder), x, y, fieldWidth, AlignLeft, i.placeholderTextColor)
|
||||
i.offset = 0
|
||||
} else {
|
||||
// Draw entered text.
|
||||
if i.maskCharacter > 0 {
|
||||
text = strings.Repeat(string(i.maskCharacter), utf8.RuneCountInString(i.text))
|
||||
} else {
|
||||
text = Escape(text)
|
||||
}
|
||||
fieldWidth-- // We need one cell for the cursor.
|
||||
if fieldWidth < runewidth.StringWidth(text) {
|
||||
Print(screen, text, x, y, fieldWidth, AlignRight, i.fieldTextColor)
|
||||
stringWidth := runewidth.StringWidth(text)
|
||||
if fieldWidth >= stringWidth {
|
||||
// We have enough space for the full text.
|
||||
Print(screen, Escape(text), x, y, fieldWidth, AlignLeft, i.fieldTextColor)
|
||||
i.offset = 0
|
||||
iterateString(text, func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
|
||||
if textPos >= i.cursorPos {
|
||||
return true
|
||||
}
|
||||
cursorScreenPos += screenWidth
|
||||
return false
|
||||
})
|
||||
} else {
|
||||
Print(screen, text, x, y, fieldWidth, AlignLeft, i.fieldTextColor)
|
||||
// The text doesn't fit. Where is the cursor?
|
||||
if i.cursorPos < 0 {
|
||||
i.cursorPos = 0
|
||||
} else if i.cursorPos > len(text) {
|
||||
i.cursorPos = len(text)
|
||||
}
|
||||
// Shift the text so the cursor is inside the field.
|
||||
var shiftLeft int
|
||||
if i.offset > i.cursorPos {
|
||||
i.offset = i.cursorPos
|
||||
} else if subWidth := runewidth.StringWidth(text[i.offset:i.cursorPos]); subWidth > fieldWidth-1 {
|
||||
shiftLeft = subWidth - fieldWidth + 1
|
||||
}
|
||||
currentOffset := i.offset
|
||||
iterateString(text, func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
|
||||
if textPos >= currentOffset {
|
||||
if shiftLeft > 0 {
|
||||
i.offset = textPos + textWidth
|
||||
shiftLeft -= screenWidth
|
||||
} else {
|
||||
if textPos+textWidth > i.cursorPos {
|
||||
return true
|
||||
}
|
||||
cursorScreenPos += screenWidth
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
Print(screen, Escape(text[i.offset:]), x, y, fieldWidth, AlignLeft, i.fieldTextColor)
|
||||
}
|
||||
}
|
||||
|
||||
// Set cursor.
|
||||
if i.focus.HasFocus() {
|
||||
i.setCursor(screen)
|
||||
screen.ShowCursor(x+cursorScreenPos, y)
|
||||
}
|
||||
}
|
||||
|
||||
// setCursor sets the cursor position.
|
||||
func (i *InputField) setCursor(screen tcell.Screen) {
|
||||
x := i.x
|
||||
y := i.y
|
||||
rightLimit := x + i.width
|
||||
if i.border {
|
||||
x++
|
||||
y++
|
||||
rightLimit -= 2
|
||||
}
|
||||
fieldWidth := runewidth.StringWidth(i.text)
|
||||
if i.fieldWidth > 0 && fieldWidth > i.fieldWidth-1 {
|
||||
fieldWidth = i.fieldWidth - 1
|
||||
}
|
||||
if i.labelWidth > 0 {
|
||||
x += i.labelWidth + fieldWidth
|
||||
} else {
|
||||
x += StringWidth(i.label) + fieldWidth
|
||||
}
|
||||
if x >= rightLimit {
|
||||
x = rightLimit - 1
|
||||
}
|
||||
screen.ShowCursor(x, y)
|
||||
}
|
||||
|
||||
// InputHandler returns the handler for this primitive.
|
||||
func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
|
||||
return i.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
|
||||
@@ -305,27 +337,95 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
|
||||
}
|
||||
}()
|
||||
|
||||
// Movement functions.
|
||||
home := func() { i.cursorPos = 0 }
|
||||
end := func() { i.cursorPos = len(i.text) }
|
||||
moveLeft := func() {
|
||||
iterateStringReverse(i.text[:i.cursorPos], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
|
||||
i.cursorPos -= textWidth
|
||||
return true
|
||||
})
|
||||
}
|
||||
moveRight := func() {
|
||||
iterateString(i.text[i.cursorPos:], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
|
||||
i.cursorPos += textWidth
|
||||
return true
|
||||
})
|
||||
}
|
||||
moveWordLeft := func() {
|
||||
i.cursorPos = len(regexp.MustCompile(`\S+\s*$`).ReplaceAllString(i.text[:i.cursorPos], ""))
|
||||
}
|
||||
moveWordRight := func() {
|
||||
i.cursorPos = len(i.text) - len(regexp.MustCompile(`^\s*\S+\s*`).ReplaceAllString(i.text[i.cursorPos:], ""))
|
||||
}
|
||||
|
||||
// Process key event.
|
||||
switch key := event.Key(); key {
|
||||
case tcell.KeyRune: // Regular character.
|
||||
newText := i.text + string(event.Rune())
|
||||
if i.accept != nil {
|
||||
if !i.accept(newText, event.Rune()) {
|
||||
break
|
||||
modifiers := event.Modifiers()
|
||||
if modifiers == tcell.ModNone {
|
||||
ch := string(event.Rune())
|
||||
newText := i.text[:i.cursorPos] + ch + i.text[i.cursorPos:]
|
||||
if i.accept != nil {
|
||||
if !i.accept(newText, event.Rune()) {
|
||||
break
|
||||
}
|
||||
}
|
||||
i.text = newText
|
||||
i.cursorPos += len(ch)
|
||||
} else if modifiers&tcell.ModAlt > 0 {
|
||||
// We accept some Alt- key combinations.
|
||||
switch event.Rune() {
|
||||
case 'a': // Home.
|
||||
home()
|
||||
case 'e': // End.
|
||||
end()
|
||||
case 'b': // Move word left.
|
||||
moveWordLeft()
|
||||
case 'f': // Move word right.
|
||||
moveWordRight()
|
||||
}
|
||||
}
|
||||
i.text = newText
|
||||
case tcell.KeyCtrlU: // Delete all.
|
||||
i.text = ""
|
||||
i.cursorPos = 0
|
||||
case tcell.KeyCtrlK: // Delete until the end of the line.
|
||||
i.text = i.text[:i.cursorPos]
|
||||
case tcell.KeyCtrlW: // Delete last word.
|
||||
lastWord := regexp.MustCompile(`\s*\S+\s*$`)
|
||||
i.text = lastWord.ReplaceAllString(i.text, "")
|
||||
case tcell.KeyBackspace, tcell.KeyBackspace2: // Delete last character.
|
||||
if len(i.text) == 0 {
|
||||
break
|
||||
lastWord := regexp.MustCompile(`\S+\s*$`)
|
||||
newText := lastWord.ReplaceAllString(i.text[:i.cursorPos], "") + i.text[i.cursorPos:]
|
||||
i.cursorPos -= len(i.text) - len(newText)
|
||||
i.text = newText
|
||||
case tcell.KeyBackspace, tcell.KeyBackspace2: // Delete character before the cursor.
|
||||
iterateStringReverse(i.text[:i.cursorPos], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
|
||||
i.text = i.text[:textPos] + i.text[textPos+textWidth:]
|
||||
i.cursorPos -= textWidth
|
||||
return true
|
||||
})
|
||||
if i.offset >= i.cursorPos {
|
||||
i.offset = 0
|
||||
}
|
||||
runes := []rune(i.text)
|
||||
i.text = string(runes[:len(runes)-1])
|
||||
case tcell.KeyDelete: // Delete character after the cursor.
|
||||
iterateString(i.text[i.cursorPos:], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
|
||||
i.text = i.text[:i.cursorPos] + i.text[i.cursorPos+textWidth:]
|
||||
return true
|
||||
})
|
||||
case tcell.KeyLeft:
|
||||
if event.Modifiers()&tcell.ModAlt > 0 {
|
||||
moveWordLeft()
|
||||
} else {
|
||||
moveLeft()
|
||||
}
|
||||
case tcell.KeyRight:
|
||||
if event.Modifiers()&tcell.ModAlt > 0 {
|
||||
moveWordRight()
|
||||
} else {
|
||||
moveRight()
|
||||
}
|
||||
case tcell.KeyHome, tcell.KeyCtrlA:
|
||||
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)
|
||||
|
||||
15
vendor/github.com/rivo/tview/list.go
generated
vendored
15
vendor/github.com/rivo/tview/list.go
generated
vendored
@@ -85,6 +85,19 @@ func (l *List) GetCurrentItem() int {
|
||||
return l.currentItem
|
||||
}
|
||||
|
||||
// RemoveItem removes the item with the given index (starting at 0) from the
|
||||
// list. Does nothing if the index is out of range.
|
||||
func (l *List) RemoveItem(index int) *List {
|
||||
if index < 0 || index >= len(l.items) {
|
||||
return l
|
||||
}
|
||||
l.items = append(l.items[:index], l.items[index+1:]...)
|
||||
if l.currentItem >= len(l.items) {
|
||||
l.currentItem = len(l.items) - 1
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// SetMainTextColor sets the color of the items' main text.
|
||||
func (l *List) SetMainTextColor(color tcell.Color) *List {
|
||||
l.mainTextColor = color
|
||||
@@ -127,7 +140,7 @@ func (l *List) ShowSecondaryText(show bool) *List {
|
||||
//
|
||||
// This function is also called when the first item is added or when
|
||||
// SetCurrentItem() is called.
|
||||
func (l *List) SetChangedFunc(handler func(int, string, string, rune)) *List {
|
||||
func (l *List) SetChangedFunc(handler func(index int, mainText string, secondaryText string, shortcut rune)) *List {
|
||||
l.changed = handler
|
||||
return l
|
||||
}
|
||||
|
||||
10
vendor/github.com/rivo/tview/modal.go
generated
vendored
10
vendor/github.com/rivo/tview/modal.go
generated
vendored
@@ -86,6 +86,16 @@ func (m *Modal) AddButtons(labels []string) *Modal {
|
||||
m.done(i, l)
|
||||
}
|
||||
})
|
||||
button := m.form.GetButton(m.form.GetButtonCount() - 1)
|
||||
button.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
switch event.Key() {
|
||||
case tcell.KeyDown, tcell.KeyRight:
|
||||
return tcell.NewEventKey(tcell.KeyTab, 0, tcell.ModNone)
|
||||
case tcell.KeyUp, tcell.KeyLeft:
|
||||
return tcell.NewEventKey(tcell.KeyBacktab, 0, tcell.ModNone)
|
||||
}
|
||||
return event
|
||||
})
|
||||
}(index, label)
|
||||
}
|
||||
return m
|
||||
|
||||
59
vendor/github.com/rivo/tview/table.go
generated
vendored
59
vendor/github.com/rivo/tview/table.go
generated
vendored
@@ -231,6 +231,10 @@ type Table struct {
|
||||
// The number of visible rows the last time the table was drawn.
|
||||
visibleRows int
|
||||
|
||||
// The style of the selected rows. If this value is 0, selected rows are
|
||||
// simply inverted.
|
||||
selectedStyle tcell.Style
|
||||
|
||||
// An optional function which gets called when the user presses Enter on a
|
||||
// selected cell. If entire rows selected, the column value is undefined.
|
||||
// Likewise for entire columns.
|
||||
@@ -276,6 +280,18 @@ func (t *Table) SetBordersColor(color tcell.Color) *Table {
|
||||
return t
|
||||
}
|
||||
|
||||
// SetSelectedStyle sets a specific style for selected cells. If no such style
|
||||
// is set, per default, selected cells are inverted (i.e. their foreground and
|
||||
// background colors are swapped).
|
||||
//
|
||||
// To reset a previous setting to its default, make the following call:
|
||||
//
|
||||
// table.SetSelectedStyle(tcell.ColorDefault, tcell.ColorDefault, 0)
|
||||
func (t *Table) SetSelectedStyle(foregroundColor, backgroundColor tcell.Color, attributes tcell.AttrMask) *Table {
|
||||
t.selectedStyle = tcell.StyleDefault.Foreground(foregroundColor).Background(backgroundColor) | tcell.Style(attributes)
|
||||
return t
|
||||
}
|
||||
|
||||
// SetSeparator sets the character used to fill the space between two
|
||||
// neighboring cells. This is a space character ' ' per default but you may
|
||||
// want to set it to Borders.Vertical (or any other rune) if the column
|
||||
@@ -743,12 +759,17 @@ ColumnLoop:
|
||||
}
|
||||
|
||||
// Helper function which colors the background of a box.
|
||||
colorBackground := func(fromX, fromY, w, h int, backgroundColor, textColor tcell.Color, selected bool) {
|
||||
// backgroundColor == tcell.ColorDefault => Don't color the background.
|
||||
// textColor == tcell.ColorDefault => Don't change the text color.
|
||||
// attr == 0 => Don't change attributes.
|
||||
// invert == true => Ignore attr, set text to backgroundColor or t.backgroundColor;
|
||||
// set background to textColor.
|
||||
colorBackground := func(fromX, fromY, w, h int, backgroundColor, textColor tcell.Color, attr tcell.AttrMask, invert bool) {
|
||||
for by := 0; by < h && fromY+by < y+height; by++ {
|
||||
for bx := 0; bx < w && fromX+bx < x+width; bx++ {
|
||||
m, c, style, _ := screen.GetContent(fromX+bx, fromY+by)
|
||||
if selected {
|
||||
fg, _, _ := style.Decompose()
|
||||
fg, bg, a := style.Decompose()
|
||||
if invert {
|
||||
if fg == textColor || fg == t.bordersColor {
|
||||
fg = backgroundColor
|
||||
}
|
||||
@@ -757,10 +778,16 @@ ColumnLoop:
|
||||
}
|
||||
style = style.Background(textColor).Foreground(fg)
|
||||
} else {
|
||||
if backgroundColor == tcell.ColorDefault {
|
||||
continue
|
||||
if backgroundColor != tcell.ColorDefault {
|
||||
bg = backgroundColor
|
||||
}
|
||||
style = style.Background(backgroundColor)
|
||||
if textColor != tcell.ColorDefault {
|
||||
fg = textColor
|
||||
}
|
||||
if attr != 0 {
|
||||
a = attr
|
||||
}
|
||||
style = style.Background(bg).Foreground(fg) | tcell.Style(a)
|
||||
}
|
||||
screen.SetContent(fromX+bx, fromY+by, m, c, style)
|
||||
}
|
||||
@@ -769,11 +796,12 @@ ColumnLoop:
|
||||
|
||||
// Color the cell backgrounds. To avoid undesirable artefacts, we combine
|
||||
// the drawing of a cell by background color, selected cells last.
|
||||
cellsByBackgroundColor := make(map[tcell.Color][]*struct {
|
||||
type cellInfo struct {
|
||||
x, y, w, h int
|
||||
text tcell.Color
|
||||
selected bool
|
||||
})
|
||||
}
|
||||
cellsByBackgroundColor := make(map[tcell.Color][]*cellInfo)
|
||||
var backgroundColors []tcell.Color
|
||||
for rowY, row := range rows {
|
||||
columnX := 0
|
||||
@@ -793,11 +821,7 @@ ColumnLoop:
|
||||
columnSelected := t.columnsSelectable && !t.rowsSelectable && column == t.selectedColumn
|
||||
cellSelected := !cell.NotSelectable && (columnSelected || rowSelected || t.rowsSelectable && t.columnsSelectable && column == t.selectedColumn && row == t.selectedRow)
|
||||
entries, ok := cellsByBackgroundColor[cell.BackgroundColor]
|
||||
cellsByBackgroundColor[cell.BackgroundColor] = append(entries, &struct {
|
||||
x, y, w, h int
|
||||
text tcell.Color
|
||||
selected bool
|
||||
}{
|
||||
cellsByBackgroundColor[cell.BackgroundColor] = append(entries, &cellInfo{
|
||||
x: bx,
|
||||
y: by,
|
||||
w: bw,
|
||||
@@ -821,13 +845,18 @@ ColumnLoop:
|
||||
_, _, lj := c.Hcl()
|
||||
return li < lj
|
||||
})
|
||||
selFg, selBg, selAttr := t.selectedStyle.Decompose()
|
||||
for _, bgColor := range backgroundColors {
|
||||
entries := cellsByBackgroundColor[bgColor]
|
||||
for _, cell := range entries {
|
||||
if cell.selected {
|
||||
defer colorBackground(cell.x, cell.y, cell.w, cell.h, bgColor, cell.text, true)
|
||||
if t.selectedStyle != 0 {
|
||||
defer colorBackground(cell.x, cell.y, cell.w, cell.h, selBg, selFg, selAttr, false)
|
||||
} else {
|
||||
defer colorBackground(cell.x, cell.y, cell.w, cell.h, bgColor, cell.text, 0, true)
|
||||
}
|
||||
} else {
|
||||
colorBackground(cell.x, cell.y, cell.w, cell.h, bgColor, cell.text, false)
|
||||
colorBackground(cell.x, cell.y, cell.w, cell.h, bgColor, tcell.ColorDefault, 0, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
126
vendor/github.com/rivo/tview/textview.go
generated
vendored
126
vendor/github.com/rivo/tview/textview.go
generated
vendored
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sync"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
@@ -549,12 +548,6 @@ func (t *TextView) reindexBuffer(width int) {
|
||||
strippedStr = regionPattern.ReplaceAllString(strippedStr, "")
|
||||
}
|
||||
|
||||
// Find all escape tags in this line. Escape them.
|
||||
if t.dynamicColors || t.regions {
|
||||
escapeIndices = escapePattern.FindAllStringIndex(str, -1)
|
||||
strippedStr = escapePattern.ReplaceAllString(strippedStr, "[$1$2]")
|
||||
}
|
||||
|
||||
// We don't need the original string anymore for now.
|
||||
str = strippedStr
|
||||
|
||||
@@ -810,8 +803,9 @@ func (t *TextView) Draw(screen tcell.Screen) {
|
||||
colorTags [][]string
|
||||
escapeIndices [][]int
|
||||
)
|
||||
strippedText := text
|
||||
if t.dynamicColors {
|
||||
colorTagIndices, colorTags, escapeIndices, _, _ = decomposeString(text)
|
||||
colorTagIndices, colorTags, escapeIndices, strippedText, _ = decomposeString(text)
|
||||
}
|
||||
|
||||
// Get regions.
|
||||
@@ -822,8 +816,10 @@ func (t *TextView) Draw(screen tcell.Screen) {
|
||||
if t.regions {
|
||||
regionIndices = regionPattern.FindAllStringIndex(text, -1)
|
||||
regions = regionPattern.FindAllStringSubmatch(text, -1)
|
||||
strippedText = regionPattern.ReplaceAllString(strippedText, "")
|
||||
if !t.dynamicColors {
|
||||
escapeIndices = escapePattern.FindAllStringIndex(text, -1)
|
||||
strippedText = string(escapePattern.ReplaceAllString(strippedText, "[$1$2]"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -842,11 +838,26 @@ func (t *TextView) Draw(screen tcell.Screen) {
|
||||
}
|
||||
|
||||
// Print the line.
|
||||
var currentTag, currentRegion, currentEscapeTag, skipped, runeSeqWidth int
|
||||
runeSequence := make([]rune, 0, 10)
|
||||
flush := func() {
|
||||
if len(runeSequence) == 0 {
|
||||
return
|
||||
var colorPos, regionPos, escapePos, tagOffset, skipped int
|
||||
iterateString(strippedText, func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
|
||||
// Get the color.
|
||||
if colorPos < len(colorTags) && textPos+tagOffset >= colorTagIndices[colorPos][0] && textPos+tagOffset < colorTagIndices[colorPos][1] {
|
||||
foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colorTags[colorPos])
|
||||
tagOffset += colorTagIndices[colorPos][1] - colorTagIndices[colorPos][0]
|
||||
colorPos++
|
||||
}
|
||||
|
||||
// Get the region.
|
||||
if regionPos < len(regionIndices) && textPos+tagOffset >= regionIndices[regionPos][0] && textPos+tagOffset < regionIndices[regionPos][1] {
|
||||
regionID = regions[regionPos][1]
|
||||
tagOffset += regionIndices[regionPos][1] - regionIndices[regionPos][0]
|
||||
regionPos++
|
||||
}
|
||||
|
||||
// Skip the second-to-last character of an escape tag.
|
||||
if escapePos < len(escapeIndices) && textPos+tagOffset == escapeIndices[escapePos][1]-2 {
|
||||
tagOffset++
|
||||
escapePos++
|
||||
}
|
||||
|
||||
// Mix the existing style with the new style.
|
||||
@@ -876,87 +887,30 @@ func (t *TextView) Draw(screen tcell.Screen) {
|
||||
style = style.Background(fg).Foreground(bg)
|
||||
}
|
||||
|
||||
// Draw the character.
|
||||
var comb []rune
|
||||
if len(runeSequence) > 1 && !unicode.IsControl(runeSequence[1]) {
|
||||
// Allocate space for the combining characters only when necessary.
|
||||
comb = make([]rune, len(runeSequence)-1)
|
||||
copy(comb, runeSequence[1:])
|
||||
}
|
||||
for offset := 0; offset < runeSeqWidth; offset++ {
|
||||
screen.SetContent(x+posX+offset, y+line-t.lineOffset, runeSequence[0], comb, style)
|
||||
}
|
||||
|
||||
// Advance.
|
||||
posX += runeSeqWidth
|
||||
runeSequence = runeSequence[:0]
|
||||
runeSeqWidth = 0
|
||||
}
|
||||
for pos, ch := range text {
|
||||
// Get the color.
|
||||
if currentTag < len(colorTags) && pos >= colorTagIndices[currentTag][0] && pos < colorTagIndices[currentTag][1] {
|
||||
flush()
|
||||
if pos == colorTagIndices[currentTag][1]-1 {
|
||||
foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colorTags[currentTag])
|
||||
currentTag++
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Get the region.
|
||||
if currentRegion < len(regionIndices) && pos >= regionIndices[currentRegion][0] && pos < regionIndices[currentRegion][1] {
|
||||
flush()
|
||||
if pos == regionIndices[currentRegion][1]-1 {
|
||||
regionID = regions[currentRegion][1]
|
||||
currentRegion++
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip the second-to-last character of an escape tag.
|
||||
if currentEscapeTag < len(escapeIndices) && pos >= escapeIndices[currentEscapeTag][0] && pos < escapeIndices[currentEscapeTag][1] {
|
||||
flush()
|
||||
if pos == escapeIndices[currentEscapeTag][1]-1 {
|
||||
currentEscapeTag++
|
||||
} else if pos == escapeIndices[currentEscapeTag][1]-2 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the width of this rune.
|
||||
chWidth := runewidth.RuneWidth(ch)
|
||||
if chWidth == 0 {
|
||||
// If this is not a modifier, we treat it as a space character.
|
||||
if len(runeSequence) == 0 {
|
||||
ch = ' '
|
||||
chWidth = 1
|
||||
} else {
|
||||
runeSequence = append(runeSequence, ch)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Skip to the right.
|
||||
if !t.wrap && skipped < skip {
|
||||
skipped += chWidth
|
||||
continue
|
||||
skipped += screenWidth
|
||||
return false
|
||||
}
|
||||
|
||||
// Stop at the right border.
|
||||
if posX+runeSeqWidth+chWidth > width {
|
||||
break
|
||||
if posX+screenWidth >= width {
|
||||
return true
|
||||
}
|
||||
|
||||
// Flush the rune sequence.
|
||||
flush()
|
||||
// Draw the character.
|
||||
for offset := screenWidth - 1; offset >= 0; offset-- {
|
||||
if offset == 0 {
|
||||
screen.SetContent(x+posX+offset, y+line-t.lineOffset, main, comb, style)
|
||||
} else {
|
||||
screen.SetContent(x+posX+offset, y+line-t.lineOffset, ' ', nil, style)
|
||||
}
|
||||
}
|
||||
|
||||
// Queue this rune.
|
||||
runeSequence = append(runeSequence, ch)
|
||||
runeSeqWidth += chWidth
|
||||
}
|
||||
if posX+runeSeqWidth <= width {
|
||||
flush()
|
||||
}
|
||||
// Advance.
|
||||
posX += screenWidth
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
// If this view is not scrollable, we'll purge the buffer of lines that have
|
||||
|
||||
546
vendor/github.com/rivo/tview/util.go
generated
vendored
546
vendor/github.com/rivo/tview/util.go
generated
vendored
@@ -1,11 +1,9 @@
|
||||
package tview
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
@@ -25,7 +23,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:]]\\s*|\\s+)")
|
||||
boundaryPattern = regexp.MustCompile(`(([[:punct:]]|\n)[ \t\f\r]*|(\s+))`)
|
||||
spacePattern = regexp.MustCompile(`\s+`)
|
||||
)
|
||||
|
||||
@@ -203,8 +201,8 @@ func decomposeString(text string) (colorIndices [][]int, colors [][]string, esca
|
||||
// You can change the colors and text styles 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.
|
||||
// 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) {
|
||||
return printWithStyle(screen, text, x, y, maxWidth, align, tcell.StyleDefault.Foreground(color))
|
||||
}
|
||||
@@ -217,115 +215,136 @@ func printWithStyle(screen tcell.Screen, text string, x, y, maxWidth, align int,
|
||||
}
|
||||
|
||||
// Decompose the text.
|
||||
colorIndices, colors, escapeIndices, strippedText, _ := decomposeString(text)
|
||||
colorIndices, colors, escapeIndices, strippedText, strippedWidth := decomposeString(text)
|
||||
|
||||
// We deal with runes, not with bytes.
|
||||
runes := []rune(strippedText)
|
||||
|
||||
// This helper function takes positions for a substring of "runes" and returns
|
||||
// a new string corresponding to this substring, making sure printing that
|
||||
// substring will observe color tags.
|
||||
substring := func(from, to int) string {
|
||||
// We want to reduce all alignments to AlignLeft.
|
||||
if align == AlignRight {
|
||||
if strippedWidth <= maxWidth {
|
||||
// There's enough space for the entire text.
|
||||
return printWithStyle(screen, text, x+maxWidth-strippedWidth, y, maxWidth, AlignLeft, style)
|
||||
}
|
||||
// Trim characters off the beginning.
|
||||
var (
|
||||
colorPos, escapePos, runePos, startPos int
|
||||
bytes, width, colorPos, escapePos, tagOffset int
|
||||
foregroundColor, backgroundColor, attributes string
|
||||
)
|
||||
if from >= len(runes) {
|
||||
return ""
|
||||
}
|
||||
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 {
|
||||
foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colors[colorPos])
|
||||
}
|
||||
colorPos++
|
||||
_, originalBackground, _ := style.Decompose()
|
||||
iterateString(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])
|
||||
style = overlayStyle(originalBackground, style, foregroundColor, backgroundColor, attributes)
|
||||
tagOffset += colorIndices[colorPos][1] - colorIndices[colorPos][0]
|
||||
colorPos++
|
||||
}
|
||||
if escapePos < len(escapeIndices) && textPos+tagOffset >= escapeIndices[escapePos][0] && textPos+tagOffset < escapeIndices[escapePos][1] {
|
||||
tagOffset++
|
||||
escapePos++
|
||||
}
|
||||
if strippedWidth-screenPos < maxWidth {
|
||||
// We chopped off enough.
|
||||
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:]
|
||||
}
|
||||
continue
|
||||
// Print and return.
|
||||
bytes, width = printWithStyle(screen, text[textPos+tagOffset:], x, y, maxWidth, AlignLeft, style)
|
||||
return true
|
||||
}
|
||||
|
||||
// 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 fmt.Sprintf(`[%s:%s:%s]%s`, foregroundColor, backgroundColor, attributes, text[startPos:pos])
|
||||
}
|
||||
|
||||
runePos++
|
||||
}
|
||||
|
||||
return fmt.Sprintf(`[%s:%s:%s]%s`, foregroundColor, backgroundColor, attributes, text[startPos:])
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
for start < len(runes) && runewidth.RuneWidth(runes[start]) == 0 {
|
||||
start++
|
||||
}
|
||||
return printWithStyle(screen, substring(start, len(runes)), x+maxWidth-width, y, width, AlignLeft, style)
|
||||
return false
|
||||
})
|
||||
return bytes, width
|
||||
} else if align == AlignCenter {
|
||||
width := runewidth.StringWidth(strippedText)
|
||||
if width == maxWidth {
|
||||
if strippedWidth == maxWidth {
|
||||
// Use the exact space.
|
||||
return printWithStyle(screen, text, x, y, maxWidth, AlignLeft, style)
|
||||
} else if width < maxWidth {
|
||||
} else if strippedWidth < maxWidth {
|
||||
// We have more space than we need.
|
||||
half := (maxWidth - width) / 2
|
||||
half := (maxWidth - strippedWidth) / 2
|
||||
return printWithStyle(screen, text, x+half, y, maxWidth-half, AlignLeft, style)
|
||||
} else {
|
||||
// Chop off runes until we have a perfect fit.
|
||||
var choppedLeft, choppedRight, leftIndex, rightIndex int
|
||||
rightIndex = len(runes) - 1
|
||||
for rightIndex > leftIndex && width-choppedLeft-choppedRight > maxWidth {
|
||||
rightIndex = len(strippedText)
|
||||
for rightIndex-1 > leftIndex && strippedWidth-choppedLeft-choppedRight > maxWidth {
|
||||
if choppedLeft < choppedRight {
|
||||
leftWidth := runewidth.RuneWidth(runes[leftIndex])
|
||||
choppedLeft += leftWidth
|
||||
leftIndex++
|
||||
for leftIndex < len(runes) && leftIndex < rightIndex && runewidth.RuneWidth(runes[leftIndex]) == 0 {
|
||||
leftIndex++
|
||||
}
|
||||
// Iterate on the left by one character.
|
||||
iterateString(strippedText[leftIndex:], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
|
||||
choppedLeft += screenWidth
|
||||
leftIndex += textWidth
|
||||
return true
|
||||
})
|
||||
} else {
|
||||
rightWidth := runewidth.RuneWidth(runes[rightIndex])
|
||||
choppedRight += rightWidth
|
||||
rightIndex--
|
||||
// Iterate on the right by one character.
|
||||
iterateStringReverse(strippedText[leftIndex:rightIndex], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
|
||||
choppedRight += screenWidth
|
||||
rightIndex -= textWidth
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
return printWithStyle(screen, substring(leftIndex, rightIndex), x, y, maxWidth, AlignLeft, style)
|
||||
|
||||
// Add tag offsets and determine start style.
|
||||
var (
|
||||
colorPos, escapePos, tagOffset int
|
||||
foregroundColor, backgroundColor, attributes string
|
||||
)
|
||||
_, originalBackground, _ := style.Decompose()
|
||||
for index := range strippedText {
|
||||
// We only need the offset of the left index.
|
||||
if index > leftIndex {
|
||||
// We're done.
|
||||
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:]
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// Update color/escape tag offset.
|
||||
if colorPos < len(colorIndices) && index+tagOffset >= colorIndices[colorPos][0] && index+tagOffset < colorIndices[colorPos][1] {
|
||||
if index <= leftIndex {
|
||||
foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colors[colorPos])
|
||||
style = overlayStyle(originalBackground, style, foregroundColor, backgroundColor, attributes)
|
||||
}
|
||||
tagOffset += colorIndices[colorPos][1] - colorIndices[colorPos][0]
|
||||
colorPos++
|
||||
}
|
||||
if escapePos < len(escapeIndices) && index+tagOffset >= escapeIndices[escapePos][0] && index+tagOffset < escapeIndices[escapePos][1] {
|
||||
tagOffset++
|
||||
escapePos++
|
||||
}
|
||||
}
|
||||
return printWithStyle(screen, text[leftIndex+tagOffset:], x, y, maxWidth, AlignLeft, style)
|
||||
}
|
||||
}
|
||||
|
||||
// Draw text.
|
||||
drawn := 0
|
||||
drawnWidth := 0
|
||||
var (
|
||||
colorPos, escapePos int
|
||||
foregroundColor, backgroundColor, attributes string
|
||||
drawn, drawnWidth, colorPos, escapePos, tagOffset int
|
||||
foregroundColor, backgroundColor, attributes string
|
||||
)
|
||||
runeSequence := make([]rune, 0, 10)
|
||||
runeSeqWidth := 0
|
||||
flush := func() {
|
||||
if len(runeSequence) == 0 {
|
||||
return // Nothing to flush.
|
||||
iterateString(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
|
||||
}
|
||||
|
||||
// Handle color tags.
|
||||
if colorPos < len(colorIndices) && textPos+tagOffset >= colorIndices[colorPos][0] && textPos+tagOffset < colorIndices[colorPos][1] {
|
||||
foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colors[colorPos])
|
||||
tagOffset += colorIndices[colorPos][1] - colorIndices[colorPos][0]
|
||||
colorPos++
|
||||
}
|
||||
|
||||
// Handle scape tags.
|
||||
if escapePos < len(escapeIndices) && textPos+tagOffset >= escapeIndices[escapePos][0] && textPos+tagOffset < escapeIndices[escapePos][1] {
|
||||
if textPos+tagOffset == escapeIndices[escapePos][1]-2 {
|
||||
tagOffset++
|
||||
escapePos++
|
||||
}
|
||||
}
|
||||
|
||||
// Print the rune sequence.
|
||||
@@ -333,69 +352,23 @@ func printWithStyle(screen tcell.Screen, text string, x, y, maxWidth, align int,
|
||||
_, _, finalStyle, _ := screen.GetContent(finalX, y)
|
||||
_, background, _ := finalStyle.Decompose()
|
||||
finalStyle = overlayStyle(background, style, foregroundColor, backgroundColor, attributes)
|
||||
var comb []rune
|
||||
if len(runeSequence) > 1 && !unicode.IsControl(runeSequence[1]) {
|
||||
// Allocate space for the combining characters only when necessary.
|
||||
comb = make([]rune, len(runeSequence)-1)
|
||||
copy(comb, runeSequence[1:])
|
||||
}
|
||||
for offset := 0; offset < runeSeqWidth; offset++ {
|
||||
// To avoid undesired effects, we place the same character in all cells.
|
||||
screen.SetContent(finalX+offset, y, runeSequence[0], comb, finalStyle)
|
||||
}
|
||||
|
||||
// Advance and reset.
|
||||
drawn += len(runeSequence)
|
||||
drawnWidth += runeSeqWidth
|
||||
runeSequence = runeSequence[:0]
|
||||
runeSeqWidth = 0
|
||||
}
|
||||
for pos, ch := range text {
|
||||
// Handle color tags.
|
||||
if colorPos < len(colorIndices) && pos >= colorIndices[colorPos][0] && pos < colorIndices[colorPos][1] {
|
||||
flush()
|
||||
if pos == colorIndices[colorPos][1]-1 {
|
||||
foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colors[colorPos])
|
||||
colorPos++
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Handle escape tags.
|
||||
if escapePos < len(escapeIndices) && pos >= escapeIndices[escapePos][0] && pos < escapeIndices[escapePos][1] {
|
||||
flush()
|
||||
if pos == escapeIndices[escapePos][1]-1 {
|
||||
escapePos++
|
||||
} else if pos == escapeIndices[escapePos][1]-2 {
|
||||
continue
|
||||
for offset := screenWidth - 1; offset >= 0; offset-- {
|
||||
// To avoid undesired effects, we populate all cells.
|
||||
if offset == 0 {
|
||||
screen.SetContent(finalX+offset, y, main, comb, finalStyle)
|
||||
} else {
|
||||
screen.SetContent(finalX+offset, y, ' ', nil, finalStyle)
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we have enough space for this rune.
|
||||
chWidth := runewidth.RuneWidth(ch)
|
||||
if drawnWidth+chWidth > maxWidth {
|
||||
break // No. We're done then.
|
||||
}
|
||||
// Advance.
|
||||
drawn += length
|
||||
drawnWidth += screenWidth
|
||||
|
||||
// Put this rune in the queue.
|
||||
if chWidth == 0 {
|
||||
// If this is not a modifier, we treat it as a space character.
|
||||
if len(runeSequence) == 0 {
|
||||
ch = ' '
|
||||
chWidth = 1
|
||||
}
|
||||
} else {
|
||||
// We have a character. Flush all previous runes.
|
||||
flush()
|
||||
}
|
||||
runeSequence = append(runeSequence, ch)
|
||||
runeSeqWidth += chWidth
|
||||
}
|
||||
if drawnWidth+runeSeqWidth <= maxWidth {
|
||||
flush()
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
return drawn, drawnWidth
|
||||
return drawn + tagOffset + len(escapeIndices), drawnWidth
|
||||
}
|
||||
|
||||
// PrintSimple prints white text to the screen at the given position.
|
||||
@@ -421,102 +394,83 @@ func WordWrap(text string, width int) (lines []string) {
|
||||
colorTagIndices, _, escapeIndices, strippedText, _ := decomposeString(text)
|
||||
|
||||
// Find candidate breakpoints.
|
||||
breakPoints := boundaryPattern.FindAllStringIndex(strippedText, -1)
|
||||
breakpoints := boundaryPattern.FindAllStringSubmatchIndex(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.
|
||||
|
||||
// This helper function adds a new line to the result slice. The provided
|
||||
// positions are in stripped index space.
|
||||
addLine := func(from, to int) {
|
||||
// Shift indices back to original index space.
|
||||
var colorTagIndex, escapeIndex int
|
||||
for colorTagIndex < len(colorTagIndices) && to >= colorTagIndices[colorTagIndex][0] ||
|
||||
escapeIndex < len(escapeIndices) && to >= escapeIndices[escapeIndex][0] {
|
||||
past := 0
|
||||
if colorTagIndex < len(colorTagIndices) {
|
||||
tagWidth := colorTagIndices[colorTagIndex][1] - colorTagIndices[colorTagIndex][0]
|
||||
if colorTagIndices[colorTagIndex][0] < from {
|
||||
from += tagWidth
|
||||
to += tagWidth
|
||||
colorTagIndex++
|
||||
} else if colorTagIndices[colorTagIndex][0] < to {
|
||||
to += tagWidth
|
||||
colorTagIndex++
|
||||
} else {
|
||||
past++
|
||||
}
|
||||
} else {
|
||||
past++
|
||||
}
|
||||
if escapeIndex < len(escapeIndices) {
|
||||
tagWidth := escapeIndices[escapeIndex][1] - escapeIndices[escapeIndex][0]
|
||||
if escapeIndices[escapeIndex][0] < from {
|
||||
from += tagWidth
|
||||
to += tagWidth
|
||||
escapeIndex++
|
||||
} else if escapeIndices[escapeIndex][0] < to {
|
||||
to += tagWidth
|
||||
escapeIndex++
|
||||
} else {
|
||||
past++
|
||||
}
|
||||
} else {
|
||||
past++
|
||||
}
|
||||
if past == 2 {
|
||||
break // All other indices are beyond the requested string.
|
||||
// Process stripped text one character at a time.
|
||||
var (
|
||||
colorPos, escapePos, breakpointPos, tagOffset int
|
||||
lastBreakpoint, lastContinuation, currentLineStart int
|
||||
lineWidth, continuationWidth int
|
||||
newlineBreakpoint bool
|
||||
)
|
||||
unescape := func(substr string, startIndex int) string {
|
||||
// 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:]
|
||||
}
|
||||
}
|
||||
lines = append(lines, text[from:to])
|
||||
return substr
|
||||
}
|
||||
|
||||
// Determine final breakpoints.
|
||||
var start, lastEnd, newStart, breakPoint int
|
||||
for {
|
||||
// What's our candidate string?
|
||||
var candidate string
|
||||
if breakPoint < len(breakPoints) {
|
||||
candidate = text[start:breakPoints[breakPoint][1]]
|
||||
} else {
|
||||
candidate = text[start:]
|
||||
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++
|
||||
}
|
||||
candidate = strings.TrimRightFunc(candidate, unicode.IsSpace)
|
||||
|
||||
if runewidth.StringWidth(candidate) >= width {
|
||||
// We're past the available width.
|
||||
if lastEnd > start {
|
||||
// Use the previous candidate.
|
||||
addLine(start, lastEnd)
|
||||
start = newStart
|
||||
} else {
|
||||
// We have no previous candidate. Make a hard break.
|
||||
var lineWidth int
|
||||
for index, ch := range text {
|
||||
if index < start {
|
||||
continue
|
||||
}
|
||||
chWidth := runewidth.RuneWidth(ch)
|
||||
if lineWidth > 0 && lineWidth+chWidth >= width {
|
||||
addLine(start, index)
|
||||
start = index
|
||||
break
|
||||
}
|
||||
lineWidth += chWidth
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// We haven't hit the right border yet.
|
||||
if breakPoint >= len(breakPoints) {
|
||||
// It's the last line. We're done.
|
||||
if len(candidate) > 0 {
|
||||
addLine(start, len(strippedText))
|
||||
}
|
||||
break
|
||||
} else {
|
||||
// We have a new candidate.
|
||||
lastEnd = start + len(candidate)
|
||||
newStart = breakPoints[breakPoint][1]
|
||||
breakPoint++
|
||||
}
|
||||
// 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
|
||||
}
|
||||
|
||||
// Is this a breakpoint?
|
||||
if breakpointPos < len(breakpoints) && textPos == 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 {
|
||||
lastBreakpoint++ // Don't skip punctuation.
|
||||
}
|
||||
breakpointPos++
|
||||
}
|
||||
|
||||
// Once we hit the continuation point, we start buffering widths.
|
||||
if textPos+tagOffset < lastContinuation {
|
||||
continuationWidth = 0
|
||||
}
|
||||
|
||||
lineWidth += screenWidth
|
||||
continuationWidth += screenWidth
|
||||
return false
|
||||
})
|
||||
|
||||
// Flush the rest.
|
||||
if currentLineStart < len(text) {
|
||||
lines = append(lines, unescape(text[currentLineStart:], currentLineStart))
|
||||
}
|
||||
|
||||
return
|
||||
@@ -531,3 +485,121 @@ func WordWrap(text string, width int) (lines []string) {
|
||||
func Escape(text string) string {
|
||||
return nonEscapePattern.ReplaceAllString(text, "$1[]")
|
||||
}
|
||||
|
||||
// iterateString iterates through the given string one printed character at a
|
||||
// time. For each such character, the callback function is called with the
|
||||
// Unicode code points of the character (the first rune and any combining runes
|
||||
// which may be nil if there aren't any), the starting position (in bytes)
|
||||
// within the original string, its length in bytes, the screen position of the
|
||||
// character, and the screen width of it. The iteration stops if the callback
|
||||
// returns true. This function returns true if the iteration was stopped before
|
||||
// the last character.
|
||||
func iterateString(text string, callback func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool) bool {
|
||||
var (
|
||||
runes []rune
|
||||
lastZeroWidthJoiner bool
|
||||
startIndex int
|
||||
startPos int
|
||||
pos int
|
||||
)
|
||||
|
||||
// Helper function which invokes the callback.
|
||||
flush := func(index int) bool {
|
||||
var comb []rune
|
||||
if len(runes) > 1 {
|
||||
comb = runes[1:]
|
||||
}
|
||||
return callback(runes[0], comb, startIndex, index-startIndex, startPos, pos-startPos)
|
||||
}
|
||||
|
||||
for index, r := range text {
|
||||
if unicode.In(r, unicode.Lm, unicode.M) || r == '\u200d' {
|
||||
lastZeroWidthJoiner = r == '\u200d'
|
||||
} else {
|
||||
// We have a rune that's not a modifier. It could be the beginning of a
|
||||
// new character.
|
||||
if !lastZeroWidthJoiner {
|
||||
if len(runes) > 0 {
|
||||
// It is. Invoke callback.
|
||||
if flush(index) {
|
||||
return true // We're done.
|
||||
}
|
||||
// Reset rune store.
|
||||
runes = runes[:0]
|
||||
startIndex = index
|
||||
startPos = pos
|
||||
}
|
||||
pos += runewidth.RuneWidth(r)
|
||||
} else {
|
||||
lastZeroWidthJoiner = false
|
||||
}
|
||||
}
|
||||
runes = append(runes, r)
|
||||
}
|
||||
|
||||
// Flush any remaining runes.
|
||||
if len(runes) > 0 {
|
||||
flush(len(text))
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// iterateStringReverse iterates through the given string in reverse, starting
|
||||
// from the end of the string, one printed character at a time. For each such
|
||||
// character, the callback function is called with the Unicode code points of
|
||||
// the character (the first rune and any combining runes which may be nil if
|
||||
// there aren't any), the starting position (in bytes) within the original
|
||||
// string, its length in bytes, the screen position of the character, and the
|
||||
// screen width of it. The iteration stops if the callback returns true. This
|
||||
// function returns true if the iteration was stopped before the last character.
|
||||
func iterateStringReverse(text string, callback func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool) bool {
|
||||
type runePos struct {
|
||||
r rune
|
||||
pos int // The byte position of the rune in the original string.
|
||||
width int // The screen width of the rune.
|
||||
mod bool // Modifier or zero-width-joiner.
|
||||
}
|
||||
|
||||
// We use the following:
|
||||
// len(text) >= number of runes in text.
|
||||
|
||||
// Put all runes into a runePos slice in reverse.
|
||||
runesReverse := make([]runePos, len(text))
|
||||
index := len(text) - 1
|
||||
for pos, ch := range text {
|
||||
runesReverse[index].r = ch
|
||||
runesReverse[index].pos = pos
|
||||
runesReverse[index].width = runewidth.RuneWidth(ch)
|
||||
runesReverse[index].mod = unicode.In(ch, unicode.Lm, unicode.M) || ch == '\u200d'
|
||||
index--
|
||||
}
|
||||
runesReverse = runesReverse[index+1:]
|
||||
|
||||
// Parse reverse runes.
|
||||
var screenWidth int
|
||||
buffer := make([]rune, len(text)) // We fill this up from the back so it's forward again.
|
||||
bufferPos := len(text)
|
||||
stringWidth := runewidth.StringWidth(text)
|
||||
for index, r := range runesReverse {
|
||||
// Put this rune into the buffer.
|
||||
bufferPos--
|
||||
buffer[bufferPos] = r.r
|
||||
|
||||
// Do we need to flush the buffer?
|
||||
if r.pos == 0 || !r.mod && runesReverse[index+1].r != '\u200d' {
|
||||
// Yes, invoke callback.
|
||||
var comb []rune
|
||||
if len(text)-bufferPos > 1 {
|
||||
comb = buffer[bufferPos+1:]
|
||||
}
|
||||
if callback(r.r, comb, r.pos, len(text)-r.pos, stringWidth-screenWidth, r.width) {
|
||||
return true
|
||||
}
|
||||
screenWidth += r.width
|
||||
bufferPos = len(text)
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user