1
0
mirror of https://github.com/taigrr/wtf synced 2026-04-02 09:28:47 -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

@@ -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
}