diff --git a/cmd/gitql/shell.go b/cmd/gitql/shell.go index 8a1fbf8e8..91b131de0 100644 --- a/cmd/gitql/shell.go +++ b/cmd/gitql/shell.go @@ -1,11 +1,15 @@ package main import ( - "bufio" - "bytes" "fmt" - "os" "strings" + + "github.com/chzyer/readline" +) + +const ( + initPrompt = "!> " + multilinePrompt = "!>>> " ) type CmdShell struct { @@ -21,69 +25,56 @@ func (c *CmdShell) Execute(args []string) error { return err } - fmt.Print(` + rl, err := readline.NewEx(&readline.Config{ + Prompt: initPrompt, + HistoryFile: "/tmp/gitql-history", + DisableAutoSaveHistory: true, + }) + if err != nil { + return err + } + + rl.Terminal.Print(fmt.Sprint(` gitQL SHELL ----------- You must end your queries with ';' -`) - - s := bufio.NewScanner(os.Stdin) - - s.Split(scanQueries) +`)) + var cmds []string for { - fmt.Print("!> ") - - if !s.Scan() { + line, err := rl.Readline() + if err != nil { break } + line = strings.TrimSpace(line) + if len(line) == 0 { + continue + } + cmds = append(cmds, line) + if !strings.HasSuffix(line, ";") { + rl.SetPrompt(multilinePrompt) + continue + } - query := s.Text() - - query = strings.Replace(query, "\n", " ", -1) - query = strings.TrimSpace(query) + query := strings.Join(cmds, " ") + cmds = cmds[:0] + rl.SetPrompt(initPrompt) + rl.SaveHistory(query) - fmt.Printf("\n--> Executing query: %s\n\n", query) + rl.Terminal.Print(fmt.Sprintf("\n--> Executing query: %s\n\n", query)) schema, rowIter, err := c.executeQuery(query) if err != nil { - c.printError(err) + rl.Terminal.Print(fmt.Sprintf("ERROR: %v\n\n", err)) continue } if err := c.printQuery(schema, rowIter, "pretty"); err != nil { - c.printError(err) + rl.Terminal.Print(fmt.Sprintf("ERROR: %v\n\n", err)) + continue } } - return s.Err() -} - -func (c *CmdShell) printError(err error) { - fmt.Printf("ERROR: %v\n\n", err) -} - -func scanQueries(data []byte, atEOF bool) (int, []byte, error) { - if atEOF && len(data) == 0 { - return 0, nil, nil - } - if i := bytes.IndexByte(data, ';'); i >= 0 { - // We have a full newline-terminated line. - return i + 1, dropCR(data[0:i]), nil - } - // If we're at EOF, we have a final, non-terminated line. Return it. - if atEOF { - return len(data), dropCR(data), nil - } - // Request more data. - return 0, nil, nil -} - -// dropCR drops a terminal \r from the data. -func dropCR(data []byte) []byte { - if len(data) > 0 && data[len(data)-1] == '\r' { - return data[0 : len(data)-1] - } - return data + return rl.Close() }