go-prompt icon indicating copy to clipboard operation
go-prompt copied to clipboard

binding a key to os.Exit results in strange terminal output

Open jvsteiner opened this issue 7 years ago • 5 comments

Issue

I bound ControlC to a function that calls os.Exit - because that's what I want it to do

Expected Behavior

program to exit, drop back into bash, continue work

Current Behavior and Steps to Reproduce

my app exits nicely, but the bash session acts "strange" afterwards. There is no newline after hitting Enter (the prompt just repeats without one) and history does not appear with up/down arrow keys (though the commands that are there still work - just that the text is not written into the shell)

Note - these are not problems while using my application that imports go-prompt, they are problems that occur in my regular OS shell after I have quit my application via a ctrl-c keybinding that happens to call os.Exit.

If, instead, I call panic everything works fine afterwards - I just have to ignore the stacktrace.

Context

image

minimal reproducible example (use ctrl-c to observe behavior):

package main

import (
	"fmt"
	"os"

	prompt "github.com/c-bata/go-prompt"
)

func main() {
	p := prompt.New(
		dummyExecutor,
		completer,
		prompt.OptionPrefix(">>> "),
		prompt.OptionAddKeyBind(quit),
	)
	text := p.Input()
	fmt.Println("You've got text: ", text)
}

func dummyExecutor(in string) { return }

func completer(d prompt.Document) []prompt.Suggest {
	s := []prompt.Suggest{
		{Text: "dummy", Description: "I'm a dummy"},
	}
	return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
}

var quit = prompt.KeyBind{
	Key: prompt.ControlC,
	Fn: func(b *prompt.Buffer) {
		os.Exit(0) // log.Fatal doesn't work, but panic somehow avoids this issue...
	},
}
  • Operating System: OSX Sierra, go v1.10
  • tag of go-prompt or commit revision: de51277535db236123a9f8f97d03290f62c5f2a6

jvsteiner avatar Mar 11 '18 19:03 jvsteiner

I suppose the problem here is that this library puts the terminal in raw mode so it can directly control the movement of the text cursor, get access to raw keypresses, etc. If you call os.Exit then you immediately end the program without giving the library a chance to put the terminal back in a normal mode again.

The renderer and input implementations have functionality in their Teardown methods to reset these states back to what the shell is expecting, but I can't see any way in the current API to run these since the relevant objects are hidden in private fields of the Prompt instance.

There is no way for this library to intercept an os.Exit call, so to address your problem here some other API would need to be added either to ask the library to tear itself down in preparation for you to call os.Exit or, ideally, to request that p.Run() would return and give func main() a chance to do any cleanup steps of its own it needs to do before exiting. (Not sure how that would behave with p.Input, though.)

apparentlymart avatar Mar 17 '18 03:03 apparentlymart

There's a workaround to gracefully handle exit:

type Exit int

func exit(_ *prompt.Buffer) {
	panic(Exit(0))
}

func handleExit() {
	switch v := recover().(type) {
	case nil:
		return
	case Exit:
		os.Exit(int(v))
	default:
		fmt.Println(v)
		fmt.Println(string(debug.Stack()))
	}
}
func main() {
    defer handleExit()
    ...
    prompt.KeyBind{
        Key: prompt.ControlC,
        Fn: exit,
    }
}

But honestly, I don't quite understand what the rationale behind hiding most of the prompt methods is. Why would you intentionally make the library less usable than it can be...

pltr avatar Mar 25 '18 20:03 pltr

I am now using the workaround, and can also confirm that it works even if the prompt loop is run in a sub goroutine, as opposed to in the main loop, which was the case for me.

jvsteiner avatar Jan 26 '19 11:01 jvsteiner

@pltr thx, it's works for me.

snail007 avatar May 08 '19 05:05 snail007

MR: https://github.com/c-bata/go-prompt/pull/239 resolves this problem. The code sample above no longer works.

josephspurrier avatar Jan 17 '22 00:01 josephspurrier