@@ -340,62 +340,61 @@ func (c *Console) Evaluate(statement string) {
340340// the configured user prompter.
341341func (c * Console ) Interactive () {
342342 var (
343- prompt = c .prompt // Current prompt line (used for multi-line inputs)
344- indents = 0 // Current number of input indents (used for multi-line inputs)
345- input = "" // Current user input
346- scheduler = make (chan string ) // Channel to send the next prompt on and receive the input
343+ prompt = c .prompt // the current prompt line (used for multi-line inputs)
344+ indents = 0 // the current number of input indents (used for multi-line inputs)
345+ input = "" // the current user input
346+ inputLine = make (chan string , 1 ) // receives user input
347+ inputErr = make (chan error , 1 ) // receives liner errors
348+ requestLine = make (chan string ) // requests a line of input
349+ interrupt = make (chan os.Signal , 1 )
347350 )
348- // Start a goroutine to listen for prompt requests and send back inputs
349- go func () {
350- for {
351- // Read the next user input
352- line , err := c .prompter .PromptInput (<- scheduler )
353- if err != nil {
354- // In case of an error, either clear the prompt or fail
355- if err == liner .ErrPromptAborted { // ctrl-C
356- prompt , indents , input = c .prompt , 0 , ""
357- scheduler <- ""
358- continue
359- }
360- close (scheduler )
361- return
362- }
363- // User input retrieved, send for interpretation and loop
364- scheduler <- line
365- }
366- }()
367- // Monitor Ctrl-C too in case the input is empty and we need to bail
368- abort := make (chan os.Signal , 1 )
369- signal .Notify (abort , syscall .SIGINT , syscall .SIGTERM )
370351
371- // Start sending prompts to the user and reading back inputs
352+ // Monitor Ctrl-C. While liner does turn on the relevant terminal mode bits to avoid
353+ // the signal, a signal can still be received for unsupported terminals. Unfortunately
354+ // there is no way to cancel the line reader when this happens. The readLines
355+ // goroutine will be leaked in this case.
356+ signal .Notify (interrupt , syscall .SIGINT , syscall .SIGTERM )
357+ defer signal .Stop (interrupt )
358+
359+ // The line reader runs in a separate goroutine.
360+ go c .readLines (inputLine , inputErr , requestLine )
361+ defer close (requestLine )
362+
372363 for {
373- // Send the next prompt, triggering an input read and process the result
374- scheduler <- prompt
364+ // Send the next prompt, triggering an input read.
365+ requestLine <- prompt
366+
375367 select {
376- case <- abort :
377- // User forcefully quite the console
368+ case <- interrupt :
378369 fmt .Fprintln (c .printer , "caught interrupt, exiting" )
379370 return
380371
381- case line , ok := <- scheduler :
382- // User input was returned by the prompter, handle special cases
383- if ! ok || (indents <= 0 && exit .MatchString (line )) {
372+ case err := <- inputErr :
373+ if err == liner .ErrPromptAborted && indents > 0 {
374+ // When prompting for multi-line input, the first Ctrl-C resets
375+ // the multi-line state.
376+ prompt , indents , input = c .prompt , 0 , ""
377+ continue
378+ }
379+ return
380+
381+ case line := <- inputLine :
382+ // User input was returned by the prompter, handle special cases.
383+ if indents <= 0 && exit .MatchString (line ) {
384384 return
385385 }
386386 if onlyWhitespace .MatchString (line ) {
387387 continue
388388 }
389- // Append the line to the input and check for multi-line interpretation
389+ // Append the line to the input and check for multi-line interpretation.
390390 input += line + "\n "
391-
392391 indents = countIndents (input )
393392 if indents <= 0 {
394393 prompt = c .prompt
395394 } else {
396395 prompt = strings .Repeat ("." , indents * 3 ) + " "
397396 }
398- // If all the needed lines are present, save the command and run
397+ // If all the needed lines are present, save the command and run it.
399398 if indents <= 0 {
400399 if len (input ) > 0 && input [0 ] != ' ' && ! passwordRegexp .MatchString (input ) {
401400 if command := strings .TrimSpace (input ); len (c .history ) == 0 || command != c .history [len (c .history )- 1 ] {
@@ -412,6 +411,18 @@ func (c *Console) Interactive() {
412411 }
413412}
414413
414+ // readLines runs in its own goroutine, prompting for input.
415+ func (c * Console ) readLines (input chan <- string , errc chan <- error , prompt <- chan string ) {
416+ for p := range prompt {
417+ line , err := c .prompter .PromptInput (p )
418+ if err != nil {
419+ errc <- err
420+ } else {
421+ input <- line
422+ }
423+ }
424+ }
425+
415426// countIndents returns the number of identations for the given input.
416427// In case of invalid input such as var a = } the result can be negative.
417428func countIndents (input string ) int {
0 commit comments