@@ -340,62 +340,61 @@ func (c *Console) Evaluate(statement string) {
340
340
// the configured user prompter.
341
341
func (c * Console ) Interactive () {
342
342
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 )
347
350
)
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 )
370
351
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
+
372
363
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
+
375
367
select {
376
- case <- abort :
377
- // User forcefully quite the console
368
+ case <- interrupt :
378
369
fmt .Fprintln (c .printer , "caught interrupt, exiting" )
379
370
return
380
371
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 ) {
384
384
return
385
385
}
386
386
if onlyWhitespace .MatchString (line ) {
387
387
continue
388
388
}
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.
390
390
input += line + "\n "
391
-
392
391
indents = countIndents (input )
393
392
if indents <= 0 {
394
393
prompt = c .prompt
395
394
} else {
396
395
prompt = strings .Repeat ("." , indents * 3 ) + " "
397
396
}
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.
399
398
if indents <= 0 {
400
399
if len (input ) > 0 && input [0 ] != ' ' && ! passwordRegexp .MatchString (input ) {
401
400
if command := strings .TrimSpace (input ); len (c .history ) == 0 || command != c .history [len (c .history )- 1 ] {
@@ -412,6 +411,18 @@ func (c *Console) Interactive() {
412
411
}
413
412
}
414
413
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
+
415
426
// countIndents returns the number of identations for the given input.
416
427
// In case of invalid input such as var a = } the result can be negative.
417
428
func countIndents (input string ) int {
0 commit comments