go-prompt
go-prompt copied to clipboard
binding a key to os.Exit results in strange terminal output
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

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
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.)
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...
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.
@pltr thx, it's works for me.
MR: https://github.com/c-bata/go-prompt/pull/239 resolves this problem. The code sample above no longer works.