Skip to content

Commit

Permalink
fix: make sure the interactive printers can cleanup after Ctrl+C
Browse files Browse the repository at this point in the history
After canceling the interactive printers with Ctrl+C, `os.Exit` was
directly called in the keyboard listener. Because of this the
`defer` statements of the surrounding function were not called
(for example `cursor.Show`) and the keyboard listener
was still running.
This resulted in a broken user "terminal" (no Cursor, no Ctrl+C)
  • Loading branch information
jochil committed Jul 25, 2022
1 parent 8acc75a commit f359cb3
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 6 deletions.
10 changes: 8 additions & 2 deletions interactive_multiselect_printer.go
Expand Up @@ -2,13 +2,13 @@ package pterm

import (
"fmt"
"os"
"sort"

"atomicgo.dev/cursor"
"atomicgo.dev/keyboard"
"atomicgo.dev/keyboard/keys"
"github.com/lithammer/fuzzysearch/fuzzy"
"github.com/pterm/pterm/internal"
)

var (
Expand Down Expand Up @@ -72,6 +72,11 @@ func (p InteractiveMultiselectPrinter) WithMaxHeight(maxHeight int) *Interactive

// Show shows the interactive multiselect menu and returns the selected entry.
func (p *InteractiveMultiselectPrinter) Show(text ...string) ([]string, error) {
// should be the first defer statement to make sure it is executed last
// and all the needed cleanup can be done before
cancelationSignal := internal.CancelationSignal{}
defer cancelationSignal.ExitOnCancelation()

if len(text) == 0 || Sprint(text[0]) == "" {
text = []string{p.DefaultText}
}
Expand Down Expand Up @@ -219,7 +224,8 @@ func (p *InteractiveMultiselectPrinter) Show(text ...string) ([]string, error) {

area.Update(p.renderSelectMenu())
case keys.CtrlC:
os.Exit(1)
cancelationSignal.Cancel()
return true, nil
case keys.Enter:
// Select option if not already selected
p.selectOption(p.fuzzySearchMatches[p.selectedOption])
Expand Down
11 changes: 9 additions & 2 deletions interactive_select_printer.go
Expand Up @@ -2,13 +2,13 @@ package pterm

import (
"fmt"
"os"
"sort"

"atomicgo.dev/cursor"
"atomicgo.dev/keyboard"
"atomicgo.dev/keyboard/keys"
"github.com/lithammer/fuzzysearch/fuzzy"
"github.com/pterm/pterm/internal"
)

var (
Expand Down Expand Up @@ -72,6 +72,11 @@ func (p InteractiveSelectPrinter) WithMaxHeight(maxHeight int) *InteractiveSelec

// Show shows the interactive select menu and returns the selected entry.
func (p *InteractiveSelectPrinter) Show(text ...string) (string, error) {
// should be the first defer statement to make sure it is executed last
// and all the needed cleanup can be done before
cancelationSignal := internal.CancelationSignal{}
defer cancelationSignal.ExitOnCancelation()

if len(text) == 0 || Sprint(text[0]) == "" {
text = []string{p.DefaultText}
}
Expand Down Expand Up @@ -124,6 +129,7 @@ func (p *InteractiveSelectPrinter) Show(text ...string) (string, error) {

cursor.Hide()
defer cursor.Show()

err = keyboard.Listen(func(keyInfo keys.Key) (stop bool, err error) {
key := keyInfo.Code

Expand Down Expand Up @@ -216,7 +222,8 @@ func (p *InteractiveSelectPrinter) Show(text ...string) (string, error) {

area.Update(p.renderSelectMenu())
case keys.CtrlC:
os.Exit(1)
cancelationSignal.Cancel()
return true, nil
case keys.Enter:
if len(p.fuzzySearchMatches) == 0 {
return false, nil
Expand Down
9 changes: 7 additions & 2 deletions interactive_textinput_printer.go
@@ -1,7 +1,6 @@
package pterm

import (
"os"
"strings"

"atomicgo.dev/cursor"
Expand Down Expand Up @@ -51,6 +50,11 @@ func (p *InteractiveTextInputPrinter) WithMultiLine(multiLine ...bool) *Interact

// Show shows the interactive select menu and returns the selected entry.
func (p *InteractiveTextInputPrinter) Show(text ...string) (string, error) {
// should be the first defer statement to make sure it is executed last
// and all the needed cleanup can be done before
cancelationSignal := internal.CancelationSignal{}
defer cancelationSignal.ExitOnCancelation()

var areaText string

if len(text) == 0 || Sprint(text[0]) == "" {
Expand Down Expand Up @@ -130,7 +134,8 @@ func (p *InteractiveTextInputPrinter) Show(text ...string) (string, error) {
p.cursorXPos = 0
}
case keys.CtrlC:
os.Exit(0)
cancelationSignal.Cancel()
return true, nil
case keys.Down:
if p.cursorYPos+1 < len(p.input) {
p.cursorXPos = (internal.GetStringMaxWidth(p.input[p.cursorYPos]) + p.cursorXPos) - internal.GetStringMaxWidth(p.input[p.cursorYPos+1])
Expand Down
22 changes: 22 additions & 0 deletions internal/cancelation_signal.go
@@ -0,0 +1,22 @@
package internal

import (
"os"
)

// CancelationSignal for keeping track of a cancelation
type CancelationSignal struct {
canceled bool
}

// Cancel marks the signal as canceled
func (cs *CancelationSignal) Cancel() {
cs.canceled = true
}

// ExitOnCancelation exists with code=1 if the signal was marked as canceled
func (cs *CancelationSignal) ExitOnCancelation() {
if cs.canceled {
os.Exit(1)
}
}

0 comments on commit f359cb3

Please sign in to comment.