diff --git a/go/src/miller/auxents/repl/prompt.go b/go/src/miller/auxents/repl/prompt.go index 193755a46..c5a636b11 100644 --- a/go/src/miller/auxents/repl/prompt.go +++ b/go/src/miller/auxents/repl/prompt.go @@ -18,7 +18,6 @@ const ENV_SECONDARY_PROMPT = "MLR_REPL_PS2" const DEFAULT_PRIMARY_PROMPT = "[mlr] " const DEFAULT_SECONDARY_PROMPT = "" - func getInputIsTerminal() bool { return term.IsTerminal(int(os.Stdin.Fd())) } diff --git a/go/src/miller/auxents/repl/session.go b/go/src/miller/auxents/repl/session.go index ab5a07d69..c20f295c9 100644 --- a/go/src/miller/auxents/repl/session.go +++ b/go/src/miller/auxents/repl/session.go @@ -22,7 +22,9 @@ import ( "fmt" "io" "os" + "os/signal" "strings" + "syscall" "miller/cliutil" "miller/dsl/cst" @@ -63,6 +65,12 @@ func NewRepl( // types.MlrvalFromVoid(). runtimeState.FilterExpression = types.MlrvalFromVoid() + // For control-C handling + sysToSignalHandlerChannel := make(chan os.Signal, 1) // Our signal handler reads system notification here + appSignalNotificationChannel := make(chan bool, 1) // Our signal handler writes this for our app to poll + signal.Notify(sysToSignalHandlerChannel, os.Interrupt, syscall.SIGTERM) + go controlCHandler(sysToSignalHandlerChannel, appSignalNotificationChannel) + return &Repl{ exeName: exeName, replName: replName, @@ -80,10 +88,23 @@ func NewRepl( recordReader: recordReader, recordWriter: recordWriter, - runtimeState: runtimeState, + runtimeState: runtimeState, + sysToSignalHandlerChannel: sysToSignalHandlerChannel, + appSignalNotificationChannel: appSignalNotificationChannel, }, nil } +// When the user types control-C, immediately print something to the screen, +// then also write to a channel while long-running things like :skip and +// :process can check it. +func controlCHandler(sysToSignalHandlerChannel chan os.Signal, appSignalNotificationChannel chan bool) { + for { + <-sysToSignalHandlerChannel // Block until control-C notification is sent by system + fmt.Println("^C") // Acknowledge for user reassurance + appSignalNotificationChannel <- true // Let our app poll this + } +} + // ---------------------------------------------------------------- func (this *Repl) handleSession(istream *os.File) { this.printStartupBanner() @@ -103,6 +124,14 @@ func (this *Repl) handleSession(istream *os.File) { os.Exit(1) } + // Acknowledge any control-C's, even if typed at a ready prompt + select { + case _ = <-this.appSignalNotificationChannel: + continue + default: + break + } + // This trims the trailing newline, as well as leading/trailing whitespace: trimmedLine := strings.TrimSpace(line) diff --git a/go/src/miller/auxents/repl/types.go b/go/src/miller/auxents/repl/types.go index ac1e39f29..b5b0afd7c 100644 --- a/go/src/miller/auxents/repl/types.go +++ b/go/src/miller/auxents/repl/types.go @@ -5,6 +5,8 @@ package repl import ( + "os" + "miller/cliutil" "miller/dsl/cst" "miller/input" @@ -47,4 +49,8 @@ type Repl struct { recordWriter output.IRecordWriter runtimeState *runtime.State + + // For control-C handling + sysToSignalHandlerChannel chan os.Signal // Our signal handler reads system notification here + appSignalNotificationChannel chan bool // Our signal handler writes this for our app to poll } diff --git a/go/src/miller/auxents/repl/verbs.go b/go/src/miller/auxents/repl/verbs.go index aeae4e30c..9098e16ab 100644 --- a/go/src/miller/auxents/repl/verbs.go +++ b/go/src/miller/auxents/repl/verbs.go @@ -350,6 +350,8 @@ func handleSkipOrProcessN(this *Repl, n int, processingNotSkipping bool) { break case err = <-this.errorChannel: break + case _ = <-this.appSignalNotificationChannel: // user typed control-C + break } if err != nil { @@ -398,11 +400,18 @@ func handleSkipOrProcessUntil(this *Repl, dslString string, processingNotSkippin var recordAndContext *types.RecordAndContext = nil for { + doubleBreak := false select { case recordAndContext = <-this.inputChannel: break case err = <-this.errorChannel: break + case _ = <-this.appSignalNotificationChannel: // user typed control-C + doubleBreak = true + break + } + if doubleBreak { + break } if err != nil { @@ -715,7 +724,7 @@ func handleHelpSingle(this *Repl, arg string) { func showREPLIntro(this *Repl) { fmt.Printf( -`The Miller REPL is an interactive counterpart to record-processing using the + `The Miller REPL is an interactive counterpart to record-processing using the put/filter DSL. Using put and filter, you can do the following: diff --git a/go/todo.txt b/go/todo.txt index 0141ba88f..a401eb6aa 100644 --- a/go/todo.txt +++ b/go/todo.txt @@ -22,6 +22,7 @@ o make a README.md - rlwrap note + ! for ^C -- need a :skip xxx / :process xxx -- also online help ! output-specifier: write or append. - applies to all record-writer stuff incl strings - update ':help intro' @@ -35,7 +36,6 @@ o tilde-expand for load/open ... - if '~' is in the string, run it though sh -c echo ... - ? :continue / ^C -- ?!? - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -