diff --git a/CHANGELOG b/CHANGELOG index 3d64e85..c3eb7ba 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,5 @@ v1.5.7 (WIP) +- Add Application.HandlePanic - Add Modal.SetButtonsAlign and Modal.SetTextAlign - Fix TextView.GetRegionText error when text contains color tags - Fix TextView region tags when placed at the end of a line diff --git a/application.go b/application.go index 63fd5fc..dacba78 100644 --- a/application.go +++ b/application.go @@ -125,6 +125,23 @@ func NewApplication() *Application { } } +// HandlePanic (when deferred at the start of a goroutine) handles panics +// gracefully. The terminal is returned to its original state before the panic +// message is printed. +// +// Panics may only be handled by the panicking goroutine. Because of this, +// HandlePanic must be deferred at the start of each goroutine (including main). +func (a *Application) HandlePanic() { + p := recover() + if p == nil { + return + } + + a.finalizeScreen() + + panic(p) +} + // SetInputCapture sets a function which captures all key events before they are // forwarded to the key event handler of the primitive which currently has // focus. This function can then choose to forward that key event (or a @@ -290,15 +307,7 @@ func (a *Application) Run() error { return err } - // We catch panics to clean up because they mess up the terminal. - defer func() { - if p := recover(); p != nil { - if a.screen != nil { - a.screen.Fini() - } - panic(p) - } - }() + defer a.HandlePanic() // Draw the screen for the first time. a.Unlock() @@ -308,6 +317,8 @@ func (a *Application) Run() error { var wg sync.WaitGroup wg.Add(1) go func() { + defer a.HandlePanic() + defer wg.Done() for { a.RLock() @@ -431,6 +442,8 @@ func (a *Application) Run() error { semaphore := &sync.Mutex{} go func() { + defer a.HandlePanic() + for update := range a.updates { semaphore.Lock() update() @@ -570,13 +583,18 @@ func (a *Application) Stop() { a.Lock() defer a.Unlock() + a.finalizeScreen() + a.screenReplacement <- nil +} + +func (a *Application) finalizeScreen() { screen := a.screen if screen == nil { return } + a.screen = nil screen.Fini() - a.screenReplacement <- nil } // Suspend temporarily suspends the application by exiting terminal UI mode and diff --git a/demos/box/main.go b/demos/box/main.go index 7d9228b..a7a38a8 100644 --- a/demos/box/main.go +++ b/demos/box/main.go @@ -8,6 +8,7 @@ import ( func main() { app := cview.NewApplication() + defer app.HandlePanic() box := cview.NewBox() box.SetBorder(true) diff --git a/demos/button/main.go b/demos/button/main.go index 696b265..187fc92 100644 --- a/demos/button/main.go +++ b/demos/button/main.go @@ -5,6 +5,8 @@ import "code.rocketnine.space/tslocum/cview" func main() { app := cview.NewApplication() + defer app.HandlePanic() + app.EnableMouse(true) button := cview.NewButton("Hit Enter to close") diff --git a/demos/checkbox/main.go b/demos/checkbox/main.go index e26c216..b8bf52b 100644 --- a/demos/checkbox/main.go +++ b/demos/checkbox/main.go @@ -7,6 +7,8 @@ import ( func main() { app := cview.NewApplication() + defer app.HandlePanic() + app.EnableMouse(true) checkbox := cview.NewCheckBox() diff --git a/demos/dropdown/main.go b/demos/dropdown/main.go index 514afc6..602d823 100644 --- a/demos/dropdown/main.go +++ b/demos/dropdown/main.go @@ -5,6 +5,8 @@ import "code.rocketnine.space/tslocum/cview" func main() { app := cview.NewApplication() + defer app.HandlePanic() + app.EnableMouse(true) dropdown := cview.NewDropDown() diff --git a/demos/flex/main.go b/demos/flex/main.go index b78b2e0..7206f90 100644 --- a/demos/flex/main.go +++ b/demos/flex/main.go @@ -14,6 +14,8 @@ func demoBox(title string) *cview.Box { func main() { app := cview.NewApplication() + defer app.HandlePanic() + app.EnableMouse(true) subFlex := cview.NewFlex() diff --git a/demos/focusmanager/main.go b/demos/focusmanager/main.go index 0b2c3e1..4e1f6b9 100644 --- a/demos/focusmanager/main.go +++ b/demos/focusmanager/main.go @@ -18,6 +18,8 @@ func wrap(f func()) func(ev *tcell.EventKey) *tcell.EventKey { func main() { app := cview.NewApplication() + defer app.HandlePanic() + app.EnableMouse(true) input1 := cview.NewInputField() diff --git a/demos/form/main.go b/demos/form/main.go index 11073a5..6470d7a 100644 --- a/demos/form/main.go +++ b/demos/form/main.go @@ -7,6 +7,8 @@ import ( func main() { app := cview.NewApplication() + defer app.HandlePanic() + app.EnableMouse(true) form := cview.NewForm() diff --git a/demos/frame/main.go b/demos/frame/main.go index e3a1142..396ca49 100644 --- a/demos/frame/main.go +++ b/demos/frame/main.go @@ -8,6 +8,8 @@ import ( func main() { app := cview.NewApplication() + defer app.HandlePanic() + app.EnableMouse(true) box := cview.NewBox() diff --git a/demos/grid/main.go b/demos/grid/main.go index f0e8ae9..602683e 100644 --- a/demos/grid/main.go +++ b/demos/grid/main.go @@ -7,6 +7,8 @@ import ( func main() { app := cview.NewApplication() + defer app.HandlePanic() + app.EnableMouse(true) newPrimitive := func(text string) cview.Primitive { diff --git a/demos/inputfield/autocomplete/main.go b/demos/inputfield/autocomplete/main.go index 7485b71..770dc96 100644 --- a/demos/inputfield/autocomplete/main.go +++ b/demos/inputfield/autocomplete/main.go @@ -12,6 +12,7 @@ const wordList = "ability,able,about,above,accept,according,account,across,act,a func main() { app := cview.NewApplication() + defer app.HandlePanic() words := strings.Split(wordList, ",") diff --git a/demos/inputfield/autocompleteasync/main.go b/demos/inputfield/autocompleteasync/main.go index d9ed093..abfd939 100644 --- a/demos/inputfield/autocompleteasync/main.go +++ b/demos/inputfield/autocompleteasync/main.go @@ -17,6 +17,8 @@ type company struct { func main() { app := cview.NewApplication() + defer app.HandlePanic() + inputField := cview.NewInputField() inputField.SetLabel("Enter a company name: ") inputField.SetFieldWidth(30) diff --git a/demos/inputfield/simple/main.go b/demos/inputfield/simple/main.go index 31bde30..4ed41ca 100644 --- a/demos/inputfield/simple/main.go +++ b/demos/inputfield/simple/main.go @@ -8,6 +8,8 @@ import ( func main() { app := cview.NewApplication() + defer app.HandlePanic() + app.EnableMouse(true) inputField := cview.NewInputField() diff --git a/demos/list/main.go b/demos/list/main.go index 056de23..87e0e15 100644 --- a/demos/list/main.go +++ b/demos/list/main.go @@ -9,6 +9,8 @@ import ( func main() { app := cview.NewApplication() + defer app.HandlePanic() + app.EnableMouse(true) list := cview.NewList() diff --git a/demos/modal/main.go b/demos/modal/main.go index 93df908..d4b1595 100644 --- a/demos/modal/main.go +++ b/demos/modal/main.go @@ -7,6 +7,8 @@ import ( func main() { app := cview.NewApplication() + defer app.HandlePanic() + app.EnableMouse(true) modal := cview.NewModal() diff --git a/demos/panels/main.go b/demos/panels/main.go index 8fc09bd..11c7389 100644 --- a/demos/panels/main.go +++ b/demos/panels/main.go @@ -11,6 +11,8 @@ const panelCount = 5 func main() { app := cview.NewApplication() + defer app.HandlePanic() + app.EnableMouse(true) panels := cview.NewPanels() diff --git a/demos/presentation/main.go b/demos/presentation/main.go index aa80a3e..ad22ef9 100644 --- a/demos/presentation/main.go +++ b/demos/presentation/main.go @@ -43,6 +43,8 @@ var app = cview.NewApplication() // Starting point for the presentation. func main() { + defer app.HandlePanic() + var debugPort int flag.IntVar(&debugPort, "debug", 0, "port to serve debug info") flag.Parse() diff --git a/demos/primitive/main.go b/demos/primitive/main.go index a99ef81..2927c8f 100644 --- a/demos/primitive/main.go +++ b/demos/primitive/main.go @@ -61,6 +61,7 @@ func (r *RadioButtons) InputHandler() func(event *tcell.EventKey, setFocus func( func main() { app := cview.NewApplication() + defer app.HandlePanic() radioButtons := NewRadioButtons([]string{"Lions", "Elephants", "Giraffes"}) radioButtons.SetBorder(true) diff --git a/demos/progressbar/main.go b/demos/progressbar/main.go index e5d117c..29965dc 100644 --- a/demos/progressbar/main.go +++ b/demos/progressbar/main.go @@ -9,6 +9,7 @@ import ( func main() { app := cview.NewApplication() + defer app.HandlePanic() grid := cview.NewGrid() grid.SetColumns(-1, 6, 4, 30, -1) diff --git a/demos/tabbedpanels/main.go b/demos/tabbedpanels/main.go index 4eb4edd..a04b7e6 100644 --- a/demos/tabbedpanels/main.go +++ b/demos/tabbedpanels/main.go @@ -11,6 +11,8 @@ const panelCount = 5 func main() { app := cview.NewApplication() + defer app.HandlePanic() + app.EnableMouse(true) panels := cview.NewTabbedPanels() diff --git a/demos/table/main.go b/demos/table/main.go index d4ddcbe..3446d98 100644 --- a/demos/table/main.go +++ b/demos/table/main.go @@ -12,6 +12,8 @@ const loremIpsumText = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, func main() { app := cview.NewApplication() + defer app.HandlePanic() + app.EnableMouse(true) table := cview.NewTable() diff --git a/demos/textview/main.go b/demos/textview/main.go index 79e0c84..2c499fa 100644 --- a/demos/textview/main.go +++ b/demos/textview/main.go @@ -21,6 +21,8 @@ Capitalize on low hanging fruit to identify a ballpark value added activity to b func main() { app := cview.NewApplication() + defer app.HandlePanic() + app.EnableMouse(true) textView := cview.NewTextView() diff --git a/demos/treeview/main.go b/demos/treeview/main.go index 27acbc5..34b7bfc 100644 --- a/demos/treeview/main.go +++ b/demos/treeview/main.go @@ -12,6 +12,8 @@ import ( // Show a navigable tree view of the current directory. func main() { app := cview.NewApplication() + defer app.HandlePanic() + app.EnableMouse(true) rootDir := "." diff --git a/demos/unicode/main.go b/demos/unicode/main.go index be3fe1f..1be6376 100644 --- a/demos/unicode/main.go +++ b/demos/unicode/main.go @@ -9,6 +9,8 @@ import ( func main() { app := cview.NewApplication() + defer app.HandlePanic() + panels := cview.NewPanels() form := cview.NewForm() diff --git a/doc_test.go b/doc_test.go index 1ac2a29..408ad76 100644 --- a/doc_test.go +++ b/doc_test.go @@ -10,6 +10,8 @@ import ( func ExampleNewApplication() { // Initialize application. app := NewApplication() + // Handle panics gracefully. + defer app.HandlePanic() // Create shared TextView. sharedTextView := NewTextView() @@ -71,6 +73,8 @@ func ExampleNewApplication() { func ExampleApplication_EnableMouse() { // Initialize application. app := NewApplication() + // Handle panics gracefully. + defer app.HandlePanic() // Enable mouse support. app.EnableMouse(true)