// Package readline provides a modern, pure Go `readline` shell implementation, // with full `.inputrc` and legacy readline command/option support, and extended // with various commands,options and tools commonly found in more modern shells. // // Example usage: // // // Create a new shell with a custom prompt. // rl := readline.NewShell() // rl.Prompt.Primary(func() string { return "> "} ) // // // Display the prompt, read user input. // for { // line, err := rl.Readline() // if err != nil { // break // } // fmt.Println(line) // }
package readline import ( ) // ErrInterrupt is returned when the interrupt sequence // is pressed on the keyboard. The sequence is usually Ctrl-C. var ErrInterrupt = errors.New(os.Interrupt.String()) // Readline displays the readline prompt and reads user input. // It can return from the call because of different things: // // - When the user accepts the line (generally with Enter). // - If a particular keystroke mapping returns an error. // (Ctrl-C returns ErrInterrupt, Ctrl-D returns io.EOF). // // In all cases, the current input line is returned along with any error, // and it is up to the caller to decide what to do with the line result. // When the error is not nil, the returned line is not written to history. func ( *Shell) () (string, error) { := int(os.Stdin.Fd()) , := term.MakeRaw() if != nil { return "", } defer term.Restore(, ) // Prompts and cursor styles .Display.PrintPrimaryPrompt() defer .Display.RefreshTransient() defer fmt.Print(keymap.CursorStyle("default")) .init() // Terminal resize events := display.WatchResize(.Display) defer close() for { // Whether or not the command is resolved, let the macro // engine record the keys if currently recording a macro. // This is done before flushing all used keys, on purpose. macro.RecordKeys(.Macros) // Get the rid of the keys that were consumed during the // previous command run. This may include keys that have // been consumed but did not match any command. core.FlushUsed(.Keys) // Since we always update helpers after being asked to read // for user input again, we do it before actually reading it. .Display.Refresh() // Block and wait for available user input keys. // These might be read on stdin, or already available because // the macro engine has fed some keys in bulk when running one. core.WaitAvailableKeys(.Keys, .Config) // 1 - Local keymap (Completion/Isearch/Vim operator pending). , , := keymap.MatchLocal(.Keymap) if { continue } , , := .run(false, , ) if { return , } else if != nil { continue } // Past the local keymap, our actions have a direct effect // on the line or on the cursor position, so we must first // "reset" or accept any completion state we're in, if any, // such as a virtually inserted candidate. completion.UpdateInserted(.completer) // 2 - Main keymap (Vim command/insertion, Emacs). , , = keymap.MatchMain(.Keymap) if { continue } , , = .run(true, , ) if { return , } // Reaching this point means the last key/sequence has not // been dispatched down to a command: therefore this key is // undefined for the current local/main keymaps. .handleUndefined(, ) } } // init gathers all steps to perform at the beginning of readline loop. func ( *Shell) () { // Reset core editor components. core.FlushUsed(.Keys) .line.Set() .cursor.Set(0) .cursor.ResetMark() .selection.Reset() .Buffers.Reset() .History.Reset() .Iterations.Reset() // Some accept-* commands must fetch a specific // line outright, or keep the accepted one. history.Init(.History) .History.Save() // Reset/initialize user interface components. .Hint.Reset() .completer.ResetForce() display.Init(.Display, .SyntaxHighlighter) } // run wraps the execution of a target command/sequence with various pre/post actions // and setup steps (buffers setup, cursor checks, iterations, key flushing, etc...) func ( *Shell) ( bool, inputrc.Bind, func()) (bool, string, error) { // An empty bind match in the local keymap means nothing // should be done, the main keymap must work it out. if ! && .Action == "" { return false, "", nil } // If the resolved bind is a macro itself, reinject its // bound sequence back to the key stack. if .Macro { := inputrc.Unescape(.Action) .Keys.Feed(false, []rune()...) } // The completion system might have control of the // input line and be using it with a virtual insertion, // so it knows which line and cursor we should work on. .line, .cursor, .selection = .completer.GetBuffer() // The command might be nil, because the provided key sequence // did not match any. We regardless execute everything related // to the command, like any pending ones, and cursor checks. .execute() // Either print/clear iterations/active registers hints. .updatePosRunHints() // If the command just run was using the incremental search // buffer (acting on it), update the list of matches. .completer.UpdateIsearch() // Work is done: ask the completion system to // return the correct input line and cursor. .line, .cursor, .selection = .completer.GetBuffer() // History: save the last action to the line history, // and return with the call to the history system that // checks if the line has been accepted (entered), in // which case this will automatically write the history // sources and set up errors/returned line values. .History.SaveWithCommand() return .History.LineAccepted() } // Run the dispatched command, any pending operator // commands (Vim mode) and some post-run checks. func ( *Shell) ( func()) { if != nil { () } // Only run pending-operator commands when the command we // just executed has not had any influence on iterations. if !.Iterations.IsPending() { .Keymap.RunPending() } // Update/check cursor positions after run. switch .Keymap.Main() { case keymap.ViCommand, keymap.ViMove, keymap.Vi: .cursor.CheckCommand() default: .cursor.CheckAppend() } } // Some commands show their current status as a hint (iterations/macro). func ( *Shell) () { := core.ResetPostRunIterations(.Iterations) , := .Buffers.IsSelected() if == "" && ! && !.Macros.Recording() { .Hint.ResetPersist() return } if != "" { .Hint.Persist() } else if { .Hint.Persist(color.Dim + fmt.Sprintf("(register: %s)", )) } } // handleUndefined is in charge of all actions to take when the // last key/sequence was not dispatched down to a readline command. func ( *Shell) ( inputrc.Bind, func()) { if .Action != "" || != nil { return } // Undefined keys incremental-search mode cancels it. if .Keymap.Local() == keymap.Isearch { .Hint.Reset() .completer.Reset() } }