@ -340,62 +340,61 @@ func (c *Console) Evaluate(statement string) {
// the configured user prompter.
// the configured user prompter.
func ( c * Console ) Interactive ( ) {
func ( c * Console ) Interactive ( ) {
var (
var (
prompt = c . prompt // Current prompt line (used for multi-line inputs)
prompt = c . prompt // the current prompt line (used for multi-line inputs)
indents = 0 // Current number of input indents (used for multi-line inputs)
indents = 0 // the current number of input indents (used for multi-line inputs)
input = "" // Current user input
input = "" // the current user input
scheduler = make ( chan string ) // Channel to send the next prompt on and receive the input
inputLine = make ( chan string , 1 ) // receives user input
inputErr = make ( chan error , 1 ) // receives liner errors
requestLine = make ( chan string ) // requests a line of input
interrupt = make ( chan os . Signal , 1 )
)
)
// Start a goroutine to listen for prompt requests and send back inputs
go func ( ) {
for {
// Read the next user input
line , err := c . prompter . PromptInput ( <- scheduler )
if err != nil {
// In case of an error, either clear the prompt or fail
if err == liner . ErrPromptAborted { // ctrl-C
prompt , indents , input = c . prompt , 0 , ""
scheduler <- ""
continue
}
close ( scheduler )
return
}
// User input retrieved, send for interpretation and loop
scheduler <- line
}
} ( )
// Monitor Ctrl-C too in case the input is empty and we need to bail
abort := make ( chan os . Signal , 1 )
signal . Notify ( abort , syscall . SIGINT , syscall . SIGTERM )
// Start sending prompts to the user and reading back inputs
// Monitor Ctrl-C. While liner does turn on the relevant terminal mode bits to avoid
// the signal, a signal can still be received for unsupported terminals. Unfortunately
// there is no way to cancel the line reader when this happens. The readLines
// goroutine will be leaked in this case.
signal . Notify ( interrupt , syscall . SIGINT , syscall . SIGTERM )
defer signal . Stop ( interrupt )
// The line reader runs in a separate goroutine.
go c . readLines ( inputLine , inputErr , requestLine )
defer close ( requestLine )
for {
for {
// Send the next prompt, triggering an input read and process the result
// Send the next prompt, triggering an input read.
scheduler <- prompt
requestLine <- prompt
select {
select {
case <- abort :
case <- interrupt :
// User forcefully quite the console
fmt . Fprintln ( c . printer , "caught interrupt, exiting" )
fmt . Fprintln ( c . printer , "caught interrupt, exiting" )
return
return
case line , ok := <- scheduler :
case err := <- inputErr :
// User input was returned by the prompter, handle special cases
if err == liner . ErrPromptAborted && indents > 0 {
if ! ok || ( indents <= 0 && exit . MatchString ( line ) ) {
// When prompting for multi-line input, the first Ctrl-C resets
// the multi-line state.
prompt , indents , input = c . prompt , 0 , ""
continue
}
return
case line := <- inputLine :
// User input was returned by the prompter, handle special cases.
if indents <= 0 && exit . MatchString ( line ) {
return
return
}
}
if onlyWhitespace . MatchString ( line ) {
if onlyWhitespace . MatchString ( line ) {
continue
continue
}
}
// Append the line to the input and check for multi-line interpretation
// Append the line to the input and check for multi-line interpretation.
input += line + "\n"
input += line + "\n"
indents = countIndents ( input )
indents = countIndents ( input )
if indents <= 0 {
if indents <= 0 {
prompt = c . prompt
prompt = c . prompt
} else {
} else {
prompt = strings . Repeat ( "." , indents * 3 ) + " "
prompt = strings . Repeat ( "." , indents * 3 ) + " "
}
}
// If all the needed lines are present, save the command and run
// If all the needed lines are present, save the command and run it.
if indents <= 0 {
if indents <= 0 {
if len ( input ) > 0 && input [ 0 ] != ' ' && ! passwordRegexp . MatchString ( input ) {
if len ( input ) > 0 && input [ 0 ] != ' ' && ! passwordRegexp . MatchString ( input ) {
if command := strings . TrimSpace ( input ) ; len ( c . history ) == 0 || command != c . history [ len ( c . history ) - 1 ] {
if command := strings . TrimSpace ( input ) ; len ( c . history ) == 0 || command != c . history [ len ( c . history ) - 1 ] {
@ -412,6 +411,18 @@ func (c *Console) Interactive() {
}
}
}
}
// readLines runs in its own goroutine, prompting for input.
func ( c * Console ) readLines ( input chan <- string , errc chan <- error , prompt <- chan string ) {
for p := range prompt {
line , err := c . prompter . PromptInput ( p )
if err != nil {
errc <- err
} else {
input <- line
}
}
}
// countIndents returns the number of identations for the given input.
// countIndents returns the number of identations for the given input.
// In case of invalid input such as var a = } the result can be negative.
// In case of invalid input such as var a = } the result can be negative.
func countIndents ( input string ) int {
func countIndents ( input string ) int {