1
0
mirror of https://github.com/taigrr/wtf synced 2026-04-01 22:58:42 -07:00

Update dependencies to latest versions

This commit is contained in:
Chris Cummer
2019-01-11 16:44:42 -08:00
parent ea27f40164
commit 48cb7ba773
358 changed files with 29553 additions and 8982 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.19 (2018-10-28)
- Added `QueueUpdate()` and `QueueEvent()` to `Application` to help with modifications to primitives from goroutines.
- v0.18 (2018-10-18)
- `InputField` elements can now be navigated freely.
- v0.17 (2018-06-20)

View File

@@ -1,27 +1,34 @@
package tview
import (
"fmt"
"os"
"sync"
"github.com/gdamore/tcell"
)
// The size of the event/update/redraw channels.
const queueSize = 100
// Application represents the top node of an application.
//
// It is not strictly required to use this class as none of the other classes
// depend on it. However, it provides useful tools to set up an application and
// plays nicely with all widgets.
//
// The following command displays a primitive p on the screen until Ctrl-C is
// pressed:
//
// if err := tview.NewApplication().SetRoot(p, true).Run(); err != nil {
// panic(err)
// }
type Application struct {
sync.RWMutex
// The application's screen.
// The application's screen. Apart from Run(), this variable should never be
// set directly. Always use the screenReplacement channel after calling
// Fini(), to set a new screen (or nil to stop the application).
screen tcell.Screen
// Indicates whether the application's screen is currently active.
running bool
// The primitive which currently has the keyboard focus.
focus Primitive
@@ -44,13 +51,26 @@ type Application struct {
// was drawn.
afterDraw func(screen tcell.Screen)
// Halts the event loop during suspended mode.
suspendMutex sync.Mutex
// Used to send screen events from separate goroutine to main event loop
events chan tcell.Event
// Functions queued from goroutines, used to serialize updates to primitives.
updates chan func()
// An object that the screen variable will be set to after Fini() was called.
// Use this channel to set a new screen object for the application
// (screen.Init() and draw() will be called implicitly). A value of nil will
// stop the application.
screenReplacement chan tcell.Screen
}
// NewApplication creates and returns a new application.
func NewApplication() *Application {
return &Application{}
return &Application{
events: make(chan tcell.Event, queueSize),
updates: make(chan func(), queueSize),
screenReplacement: make(chan tcell.Screen, 1),
}
}
// SetInputCapture sets a function which captures all key events before they are
@@ -75,29 +95,29 @@ func (a *Application) GetInputCapture() func(event *tcell.EventKey) *tcell.Event
// 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.
// tcell.Screen when using this function.
//
// 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.
// This function is typically called before the first call to Run(). Init() need
// not be called on the screen.
func (a *Application) SetScreen(screen tcell.Screen) *Application {
if screen == nil {
return a // Invalid input. Do nothing.
}
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)
}
if a.screen == nil {
// Run() has not been called yet.
a.screen = screen
a.Unlock()
return a
}
// Run() is already in progress. Exchange screen.
oldScreen := a.screen
a.Unlock()
oldScreen.Fini()
a.screenReplacement <- screen
return a
}
@@ -114,12 +134,11 @@ func (a *Application) Run() error {
a.Unlock()
return err
}
if err = a.screen.Init(); 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() {
@@ -127,72 +146,118 @@ func (a *Application) Run() error {
if a.screen != nil {
a.screen.Fini()
}
a.running = false
panic(p)
}
}()
// Draw the screen for the first time.
a.Unlock()
a.Draw()
a.draw()
// Start event loop.
for {
// Do not poll events during suspend mode
a.suspendMutex.Lock()
a.RLock()
screen := a.screen
a.RUnlock()
if screen == nil {
a.suspendMutex.Unlock()
break
}
// Wait for next event.
event := a.screen.PollEvent()
a.suspendMutex.Unlock()
if event == nil {
// The screen was finalized. Exit the loop.
break
}
switch event := event.(type) {
case *tcell.EventKey:
a.RLock()
p := a.focus
a.RUnlock()
// Intercept keys.
if a.inputCapture != nil {
event = a.inputCapture(event)
if event == nil {
break // Don't forward event.
}
}
// Ctrl-C closes the application.
if event.Key() == tcell.KeyCtrlC {
a.Stop()
}
// Pass other key events to the currently focused primitive.
if p != nil {
if handler := p.InputHandler(); handler != nil {
handler(event, func(p Primitive) {
a.SetFocus(p)
})
a.Draw()
}
}
case *tcell.EventResize:
// Separate loop to wait for screen events.
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
for {
a.RLock()
screen := a.screen
a.RUnlock()
screen.Clear()
a.Draw()
if screen == nil {
// We have no screen. Let's stop.
a.QueueEvent(nil)
break
}
// Wait for next event and queue it.
event := screen.PollEvent()
if event != nil {
// Regular event. Queue.
a.QueueEvent(event)
continue
}
// A screen was finalized (event is nil). Wait for a new scren.
screen = <-a.screenReplacement
if screen == nil {
// No new screen. We're done.
a.QueueEvent(nil)
return
}
// We have a new screen. Keep going.
a.Lock()
a.screen = screen
a.Unlock()
// Initialize and draw this screen.
if err := screen.Init(); err != nil {
panic(err)
}
a.draw()
}
}()
// Start event loop.
EventLoop:
for {
select {
case event := <-a.events:
if event == nil {
break EventLoop
}
switch event := event.(type) {
case *tcell.EventKey:
a.RLock()
p := a.focus
inputCapture := a.inputCapture
a.RUnlock()
// Intercept keys.
if inputCapture != nil {
event = inputCapture(event)
if event == nil {
a.draw()
continue // Don't forward event.
}
}
// Ctrl-C closes the application.
if event.Key() == tcell.KeyCtrlC {
a.Stop()
}
// Pass other key events to the currently focused primitive.
if p != nil {
if handler := p.InputHandler(); handler != nil {
handler(event, func(p Primitive) {
a.SetFocus(p)
})
a.draw()
}
}
case *tcell.EventResize:
a.RLock()
screen := a.screen
a.RUnlock()
if screen == nil {
continue
}
screen.Clear()
a.draw()
}
// If we have updates, now is the time to execute them.
case updater := <-a.updates:
updater()
}
}
// Wait for the event loop to finish.
wg.Wait()
a.screen = nil
return nil
}
@@ -200,12 +265,13 @@ func (a *Application) Run() error {
func (a *Application) Stop() {
a.Lock()
defer a.Unlock()
if a.screen == nil {
screen := a.screen
if screen == nil {
return
}
a.screen.Fini()
a.screen = nil
a.running = false
screen.Fini()
a.screenReplacement <- nil
}
// Suspend temporarily suspends the application by exiting terminal UI mode and
@@ -217,53 +283,54 @@ func (a *Application) Stop() {
// terminal UI mode was not exited, and "f" was not called.
func (a *Application) Suspend(f func()) bool {
a.RLock()
if a.screen == nil {
// Screen has not yet been initialized.
a.RUnlock()
return false
screen := a.screen
a.RUnlock()
if screen == nil {
return false // Screen has not yet been initialized.
}
// Enter suspended mode.
a.suspendMutex.Lock()
defer a.suspendMutex.Unlock()
a.RUnlock()
a.Stop()
// Deal with panics during suspended mode. Exit the program.
defer func() {
if p := recover(); p != nil {
fmt.Println(p)
os.Exit(1)
}
}()
screen.Fini()
// Wait for "f" to return.
f()
// Make a new screen and redraw.
a.Lock()
// Make a new screen.
var err error
a.screen, err = tcell.NewScreen()
screen, err = tcell.NewScreen()
if err != nil {
a.Unlock()
panic(err)
}
if err = a.screen.Init(); err != nil {
a.Unlock()
panic(err)
}
a.running = true
a.Unlock()
a.Draw()
a.screenReplacement <- screen
// One key event will get lost, see https://github.com/gdamore/tcell/issues/194
// Continue application loop.
return true
}
// Draw refreshes the screen. It calls the Draw() function of the application's
// root primitive and then syncs the screen buffer.
// Draw refreshes the screen (during the next update cycle). It calls the Draw()
// function of the application's root primitive and then syncs the screen
// buffer.
func (a *Application) Draw() *Application {
a.QueueUpdate(func() {
a.draw()
})
return a
}
// ForceDraw refreshes the screen immediately. Use this function with caution as
// it may lead to race conditions with updates to primitives in other
// goroutines. It is always preferrable to use Draw() instead. Never call this
// function from a goroutine.
//
// It is safe to call this function during queued updates and direct event
// handling.
func (a *Application) ForceDraw() *Application {
return a.draw()
}
// draw actually does what Draw() promises to do.
func (a *Application) draw() *Application {
a.Lock()
defer a.Unlock()
@@ -404,3 +471,35 @@ func (a *Application) GetFocus() Primitive {
defer a.RUnlock()
return a.focus
}
// QueueUpdate is used to synchronize access to primitives from non-main
// goroutines. The provided function will be executed as part of the event loop
// and thus will not cause race conditions with other such update functions or
// the Draw() function.
//
// Note that Draw() is not implicitly called after the execution of f as that
// may not be desirable. You can call Draw() from f if the screen should be
// refreshed after each update. Alternatively, use QueueUpdateDraw() to follow
// up with an immediate refresh of the screen.
func (a *Application) QueueUpdate(f func()) *Application {
a.updates <- f
return a
}
// QueueUpdateDraw works like QueueUpdate() except it refreshes the screen
// immediately after executing f.
func (a *Application) QueueUpdateDraw(f func()) *Application {
a.QueueUpdate(func() {
f()
a.draw()
})
return a
}
// QueueEvent sends an event to the Application event loop.
//
// It is not recommended for event to be nil.
func (a *Application) QueueEvent(event tcell.Event) *Application {
a.events <- event
return a
}

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

@@ -106,7 +106,11 @@ func (b *Box) GetInnerRect() (int, int, int, int) {
height - b.paddingTop - b.paddingBottom
}
// SetRect sets a new position of the primitive.
// SetRect sets a new position of the primitive. Note that this has no effect
// if this primitive is part of a layout (e.g. Flex, Grid) or if it was added
// like this:
//
// application.SetRoot(b, true)
func (b *Box) SetRect(x, y, width, height int) {
b.x = x
b.y = y
@@ -162,6 +166,13 @@ func (b *Box) InputHandler() func(event *tcell.EventKey, setFocus func(p Primiti
// be called.
//
// Providing a nil handler will remove a previously existing handler.
//
// Note that this function will not have an effect on primitives composed of
// other primitives, such as Form, Flex, or Grid. Key events are only captured
// by the primitives that have focus (e.g. InputField) and only one primitive
// can have focus at a time. Composing primitives such as Form pass the focus on
// to their contained primitives and thus never receive any key events
// themselves. Therefore, they cannot intercept key events.
func (b *Box) SetInputCapture(capture func(event *tcell.EventKey) *tcell.EventKey) *Box {
b.inputCapture = capture
return b

27
vendor/github.com/rivo/tview/doc.go generated vendored
View File

@@ -21,6 +21,7 @@ The package implements the following widgets:
- Form: Forms composed of input fields, drop down selections, checkboxes, and
buttons.
- Modal: A centered window with a text message and one or more buttons.
- Grid: A grid based layout manager.
- Flex: A Flexbox based layout manager.
- Pages: A page based layout manager.
@@ -137,6 +138,32 @@ Unicode Support
This package supports unicode characters including wide characters.
Concurrency
Many functions in this package are not thread-safe. For many applications, this
may not be an issue: If your code makes changes in response to key events, it
will execute in the main goroutine and thus will not cause any race conditions.
If you access your primitives from other goroutines, however, you will need to
synchronize execution. The easiest way to do this is to call
Application.QueueUpdate() or Application.QueueUpdateDraw() (see the function
documentation for details):
go func() {
app.QueueUpdateDraw(func() {
table.SetCellSimple(0, 0, "Foo bar")
})
}()
One exception to this is the io.Writer interface implemented by TextView. You
can safely write to a TextView from any goroutine. See the TextView
documentation for details.
You can also call Application.Draw() from any goroutine without having to wrap
it in QueueUpdate(). And, as mentioned above, key event callbacks are executed
in the main goroutine and thus should not use QueueUpdate() as that may lead to
deadlocks.
Type Hierarchy
All widgets listed above contain the Box type. All of Box's functions are

View File

@@ -507,8 +507,10 @@ func (f *Form) Draw(screen tcell.Screen) {
// Focus is called by the application when the primitive receives focus.
func (f *Form) Focus(delegate func(p Primitive)) {
if len(f.items)+len(f.buttons) == 0 {
f.hasFocus = true
return
}
f.hasFocus = false
// Hand on the focus to one of our child elements.
if f.focusedElement < 0 || f.focusedElement >= len(f.items)+len(f.buttons) {
@@ -550,6 +552,9 @@ func (f *Form) Focus(delegate func(p Primitive)) {
// HasFocus returns whether or not this primitive has focus.
func (f *Form) HasFocus() bool {
if f.hasFocus {
return true
}
for _, item := range f.items {
if item.GetFocusable().HasFocus() {
return true

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

@@ -307,6 +307,7 @@ func (g *Grid) InputHandler() func(event *tcell.EventKey, setFocus func(p Primit
func (g *Grid) Draw(screen tcell.Screen) {
g.Box.Draw(screen)
x, y, width, height := g.GetInnerRect()
screenWidth, screenHeight := screen.Size()
// Make a list of items which apply.
items := make(map[Primitive]*gridItem)
@@ -555,10 +556,10 @@ func (g *Grid) Draw(screen tcell.Screen) {
}
item.x -= offsetX
item.y -= offsetY
if item.x+item.w > width {
if item.x+item.w > x+width {
item.w = width - item.x
}
if item.y+item.h > height {
if item.y+item.h > y+height {
item.h = height - item.y
}
if item.x < 0 {
@@ -573,7 +574,7 @@ func (g *Grid) Draw(screen tcell.Screen) {
item.visible = false
continue
}
primitive.SetRect(x+item.x, y+item.y, item.w, item.h)
primitive.SetRect(item.x, item.y, item.w, item.h)
// Draw primitive.
if item == focus {
@@ -585,46 +586,46 @@ func (g *Grid) Draw(screen tcell.Screen) {
// Draw border around primitive.
if g.borders {
for bx := item.x; bx < item.x+item.w; bx++ { // Top/bottom lines.
if bx < 0 || bx >= width {
if bx < 0 || bx >= screenWidth {
continue
}
by := item.y - 1
if by >= 0 && by < height {
PrintJoinedSemigraphics(screen, x+bx, y+by, Borders.Horizontal, g.bordersColor)
if by >= 0 && by < screenHeight {
PrintJoinedSemigraphics(screen, bx, by, Borders.Horizontal, g.bordersColor)
}
by = item.y + item.h
if by >= 0 && by < height {
PrintJoinedSemigraphics(screen, x+bx, y+by, Borders.Horizontal, g.bordersColor)
if by >= 0 && by < screenHeight {
PrintJoinedSemigraphics(screen, bx, by, Borders.Horizontal, g.bordersColor)
}
}
for by := item.y; by < item.y+item.h; by++ { // Left/right lines.
if by < 0 || by >= height {
if by < 0 || by >= screenHeight {
continue
}
bx := item.x - 1
if bx >= 0 && bx < width {
PrintJoinedSemigraphics(screen, x+bx, y+by, Borders.Vertical, g.bordersColor)
if bx >= 0 && bx < screenWidth {
PrintJoinedSemigraphics(screen, bx, by, Borders.Vertical, g.bordersColor)
}
bx = item.x + item.w
if bx >= 0 && bx < width {
PrintJoinedSemigraphics(screen, x+bx, y+by, Borders.Vertical, g.bordersColor)
if bx >= 0 && bx < screenWidth {
PrintJoinedSemigraphics(screen, bx, by, Borders.Vertical, g.bordersColor)
}
}
bx, by := item.x-1, item.y-1 // Top-left corner.
if bx >= 0 && bx < width && by >= 0 && by < height {
PrintJoinedSemigraphics(screen, x+bx, y+by, Borders.TopLeft, g.bordersColor)
if bx >= 0 && bx < screenWidth && by >= 0 && by < screenHeight {
PrintJoinedSemigraphics(screen, bx, by, Borders.TopLeft, g.bordersColor)
}
bx, by = item.x+item.w, item.y-1 // Top-right corner.
if bx >= 0 && bx < width && by >= 0 && by < height {
PrintJoinedSemigraphics(screen, x+bx, y+by, Borders.TopRight, g.bordersColor)
if bx >= 0 && bx < screenWidth && by >= 0 && by < screenHeight {
PrintJoinedSemigraphics(screen, bx, by, Borders.TopRight, g.bordersColor)
}
bx, by = item.x-1, item.y+item.h // Bottom-left corner.
if bx >= 0 && bx < width && by >= 0 && by < height {
PrintJoinedSemigraphics(screen, x+bx, y+by, Borders.BottomLeft, g.bordersColor)
if bx >= 0 && bx < screenWidth && by >= 0 && by < screenHeight {
PrintJoinedSemigraphics(screen, bx, by, Borders.BottomLeft, g.bordersColor)
}
bx, by = item.x+item.w, item.y+item.h // Bottom-right corner.
if bx >= 0 && bx < width && by >= 0 && by < height {
PrintJoinedSemigraphics(screen, x+bx, y+by, Borders.BottomRight, g.bordersColor)
if bx >= 0 && bx < screenWidth && by >= 0 && by < screenHeight {
PrintJoinedSemigraphics(screen, bx, by, Borders.BottomRight, g.bordersColor)
}
}
}

View File

@@ -102,6 +102,7 @@ func NewInputField() *InputField {
// SetText sets the current text of the input field.
func (i *InputField) SetText(text string) *InputField {
i.text = text
i.cursorPos = len(text)
if i.changed != nil {
i.changed(text)
}
@@ -359,21 +360,22 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
i.cursorPos = len(i.text) - len(regexp.MustCompile(`^\s*\S+\s*`).ReplaceAllString(i.text[i.cursorPos:], ""))
}
// Add character function. Returns whether or not the rune character is
// accepted.
add := func(r rune) bool {
newText := i.text[:i.cursorPos] + string(r) + i.text[i.cursorPos:]
if i.accept != nil && !i.accept(newText, r) {
return false
}
i.text = newText
i.cursorPos += len(string(r))
return true
}
// Process key event.
switch key := event.Key(); key {
case tcell.KeyRune: // Regular character.
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 {
if event.Modifiers()&tcell.ModAlt > 0 {
// We accept some Alt- key combinations.
switch event.Rune() {
case 'a': // Home.
@@ -385,6 +387,11 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
case 'f': // Move word right.
moveWordRight()
}
} else {
// Other keys are simply accepted as regular characters.
if !add(event.Rune()) {
break
}
}
case tcell.KeyCtrlU: // Delete all.
i.text = ""

13
vendor/github.com/rivo/tview/list.go generated vendored
View File

@@ -44,6 +44,9 @@ type List struct {
// The background color for selected items.
selectedBackgroundColor tcell.Color
// If true, the selection is only shown when the list has focus.
selectedFocusOnly bool
// An optional function which is called when the user has navigated to a list
// item.
changed func(index int, mainText, secondaryText string, shortcut rune)
@@ -128,6 +131,14 @@ func (l *List) SetSelectedBackgroundColor(color tcell.Color) *List {
return l
}
// SetSelectedFocusOnly sets a flag which determines when the currently selected
// list item is highlighted. If set to true, selected items are only highlighted
// when the list has focus. If set to false, they are always highlighted.
func (l *List) SetSelectedFocusOnly(focusOnly bool) *List {
l.selectedFocusOnly = focusOnly
return l
}
// ShowSecondaryText determines whether or not to show secondary item texts.
func (l *List) ShowSecondaryText(show bool) *List {
l.showSecondaryText = show
@@ -263,7 +274,7 @@ func (l *List) Draw(screen tcell.Screen) {
Print(screen, item.MainText, x, y, width, AlignLeft, l.mainTextColor)
// Background color of selected text.
if index == l.currentItem {
if index == l.currentItem && (!l.selectedFocusOnly || l.HasFocus()) {
textWidth := StringWidth(item.MainText)
for bx := 0; bx < textWidth && bx < width; bx++ {
m, c, style, _ := screen.GetContent(x+bx, y)

View File

@@ -389,7 +389,7 @@ func (t *Table) SetDoneFunc(handler func(key tcell.Key)) *Table {
}
// SetCell sets the content of a cell the specified position. It is ok to
// directly instantiate a TableCell object. If the cell has contain, at least
// directly instantiate a TableCell object. If the cell has content, at least
// the Text and Color fields should be set.
//
// Note that setting cells in previously unknown rows and columns will
@@ -422,7 +422,7 @@ func (t *Table) SetCellSimple(row, column int, text string) *Table {
}
// GetCell returns the contents of the cell at the specified position. A valid
// TableCell object is always returns but it will be uninitialized if the cell
// TableCell object is always returned but it will be uninitialized if the cell
// was not previously set.
func (t *Table) GetCell(row, column int) *TableCell {
if row >= len(t.cells) || column >= len(t.cells[row]) {
@@ -431,6 +431,31 @@ func (t *Table) GetCell(row, column int) *TableCell {
return t.cells[row][column]
}
// RemoveRow removes the row at the given position from the table. If there is
// no such row, this has no effect.
func (t *Table) RemoveRow(row int) *Table {
if row < 0 || row >= len(t.cells) {
return t
}
t.cells = append(t.cells[:row], t.cells[row+1:]...)
return t
}
// RemoveColumn removes the column at the given position from the table. If
// there is no such column, this has no effect.
func (t *Table) RemoveColumn(column int) *Table {
for row := range t.cells {
if column < 0 || column >= len(t.cells[row]) {
continue
}
t.cells[row] = append(t.cells[row][:column], t.cells[row][column+1:]...)
}
return t
}
// GetRowCount returns the number of rows in the table.
func (t *Table) GetRowCount() int {
return len(t.cells)

View File

@@ -31,7 +31,7 @@ type textViewIndex struct {
// TextView is a box which displays text. It implements the io.Writer interface
// so you can stream text to it. This does not trigger a redraw automatically
// but if a handler is installed via SetChangedFunc(), you can cause it to be
// redrawn.
// redrawn. (See SetChangedFunc() for more details.)
//
// Navigation
//
@@ -260,8 +260,20 @@ func (t *TextView) SetRegions(regions bool) *TextView {
}
// SetChangedFunc sets a handler function which is called when the text of the
// text view has changed. This is typically used to cause the application to
// redraw the screen.
// text view has changed. This is useful when text is written to this io.Writer
// in a separate goroutine. This does not automatically cause the screen to be
// refreshed so you may want to use the "changed" handler to redraw the screen.
//
// Note that to avoid race conditions or deadlocks, there are a few rules you
// should follow:
//
// - You can call Application.Draw() from this handler.
// - You can call TextView.HasFocus() from this handler.
// - During the execution of this handler, access to any other variables from
// this primitive or any other primitive should be queued using
// Application.QueueUpdate().
//
// See package description for details on dealing with concurrency.
func (t *TextView) SetChangedFunc(handler func()) *TextView {
t.changed = handler
return t
@@ -441,13 +453,33 @@ func (t *TextView) GetRegionText(regionID string) string {
return escapePattern.ReplaceAllString(buffer.String(), `[$1$2]`)
}
// Focus is called when this primitive receives focus.
func (t *TextView) Focus(delegate func(p Primitive)) {
// Implemented here with locking because this is used by layout primitives.
t.Lock()
defer t.Unlock()
t.hasFocus = true
}
// HasFocus returns whether or not this primitive has focus.
func (t *TextView) HasFocus() bool {
// Implemented here with locking because this may be used in the "changed"
// callback.
t.Lock()
defer t.Unlock()
return t.hasFocus
}
// Write lets us implement the io.Writer interface. Tab characters will be
// replaced with TabSize space characters. A "\n" or "\r\n" will be interpreted
// as a new line.
func (t *TextView) Write(p []byte) (n int, err error) {
// Notify at the end.
if t.changed != nil {
defer t.changed()
t.Lock()
changed := t.changed
t.Unlock()
if changed != nil {
defer changed() // Deadlocks may occur if we lock here.
}
t.Lock()
@@ -840,18 +872,21 @@ func (t *TextView) Draw(screen tcell.Screen) {
// Print the line.
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++
// Process tags.
for {
if colorPos < len(colorTags) && textPos+tagOffset >= colorTagIndices[colorPos][0] && textPos+tagOffset < colorTagIndices[colorPos][1] {
// Get the color.
foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colorTags[colorPos])
tagOffset += colorTagIndices[colorPos][1] - colorTagIndices[colorPos][0]
colorPos++
} else if regionPos < len(regionIndices) && textPos+tagOffset >= regionIndices[regionPos][0] && textPos+tagOffset < regionIndices[regionPos][1] {
// Get the region.
regionID = regions[regionPos][1]
tagOffset += regionIndices[regionPos][1] - regionIndices[regionPos][0]
regionPos++
} else {
break
}
}
// Skip the second-to-last character of an escape tag.
@@ -894,7 +929,7 @@ func (t *TextView) Draw(screen tcell.Screen) {
}
// Stop at the right border.
if posX+screenWidth >= width {
if posX+screenWidth > width {
return true
}

View File

@@ -569,7 +569,7 @@ func (t *TreeView) Draw(screen tcell.Screen) {
// Draw the tree.
posY := y
lineStyle := tcell.StyleDefault.Foreground(t.graphicsColor)
lineStyle := tcell.StyleDefault.Background(t.backgroundColor).Foreground(t.graphicsColor)
for index, node := range t.nodes {
// Skip invisible parts.
if posY >= y+height+1 {

View File

@@ -51,6 +51,9 @@ var (
// Package initialization.
func init() {
// We'll use zero width joiners.
runewidth.ZeroWidthJoiner = true
// Initialize the predefined input field handlers.
InputFieldInteger = func(text string, ch rune) bool {
if text == "-" {
@@ -513,7 +516,7 @@ func iterateString(text string, callback func(main rune, comb []rune, textPos, t
}
for index, r := range text {
if unicode.In(r, unicode.Lm, unicode.M) || r == '\u200d' {
if unicode.In(r, unicode.M) || r == '\u200d' {
lastZeroWidthJoiner = r == '\u200d'
} else {
// We have a rune that's not a modifier. It could be the beginning of a