github.com/arieschain/arieschain@v0.0.0-20191023063405-37c074544356/console/prompter.go (about) 1 package console 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/peterh/liner" 8 ) 9 10 // Stdin holds the stdin line reader (also using stdout for printing prompts). 11 // Only this reader may be used for input because it keeps an internal buffer. 12 var Stdin = newTerminalPrompter() 13 14 // UserPrompter defines the methods needed by the console to promt the user for 15 // various types of inputs. 16 type UserPrompter interface { 17 // PromptInput displays the given prompt to the user and requests some textual 18 // data to be entered, returning the input of the user. 19 PromptInput(prompt string) (string, error) 20 21 // PromptPassword displays the given prompt to the user and requests some textual 22 // data to be entered, but one which must not be echoed out into the terminal. 23 // The method returns the input provided by the user. 24 PromptPassword(prompt string) (string, error) 25 26 // PromptConfirm displays the given prompt to the user and requests a boolean 27 // choice to be made, returning that choice. 28 PromptConfirm(prompt string) (bool, error) 29 30 // SetHistory sets the the input scrollback history that the prompter will allow 31 // the user to scroll back to. 32 SetHistory(history []string) 33 34 // AppendHistory appends an entry to the scrollback history. It should be called 35 // if and only if the prompt to append was a valid command. 36 AppendHistory(command string) 37 38 // ClearHistory clears the entire history 39 ClearHistory() 40 41 // SetWordCompleter sets the completion function that the prompter will call to 42 // fetch completion candidates when the user presses tab. 43 SetWordCompleter(completer WordCompleter) 44 } 45 46 // WordCompleter takes the currently edited line with the cursor position and 47 // returns the completion candidates for the partial word to be completed. If 48 // the line is "Hello, wo!!!" and the cursor is before the first '!', ("Hello, 49 // wo!!!", 9) is passed to the completer which may returns ("Hello, ", {"world", 50 // "Word"}, "!!!") to have "Hello, world!!!". 51 type WordCompleter func(line string, pos int) (string, []string, string) 52 53 // terminalPrompter is a UserPrompter backed by the liner package. It supports 54 // prompting the user for various input, among others for non-echoing password 55 // input. 56 type terminalPrompter struct { 57 *liner.State 58 warned bool 59 supported bool 60 normalMode liner.ModeApplier 61 rawMode liner.ModeApplier 62 } 63 64 // newTerminalPrompter creates a liner based user input prompter working off the 65 // standard input and output streams. 66 func newTerminalPrompter() *terminalPrompter { 67 p := new(terminalPrompter) 68 // Get the original mode before calling NewLiner. 69 // This is usually regular "cooked" mode where characters echo. 70 normalMode, _ := liner.TerminalMode() 71 // Turn on liner. It switches to raw mode. 72 p.State = liner.NewLiner() 73 rawMode, err := liner.TerminalMode() 74 if err != nil || !liner.TerminalSupported() { 75 p.supported = false 76 } else { 77 p.supported = true 78 p.normalMode = normalMode 79 p.rawMode = rawMode 80 // Switch back to normal mode while we're not prompting. 81 normalMode.ApplyMode() 82 } 83 p.SetCtrlCAborts(true) 84 p.SetTabCompletionStyle(liner.TabPrints) 85 p.SetMultiLineMode(true) 86 return p 87 } 88 89 // PromptInput displays the given prompt to the user and requests some textual 90 // data to be entered, returning the input of the user. 91 func (p *terminalPrompter) PromptInput(prompt string) (string, error) { 92 if p.supported { 93 p.rawMode.ApplyMode() 94 defer p.normalMode.ApplyMode() 95 } else { 96 // liner tries to be smart about printing the prompt 97 // and doesn't print anything if input is redirected. 98 // Un-smart it by printing the prompt always. 99 fmt.Print(prompt) 100 prompt = "" 101 defer fmt.Println() 102 } 103 return p.State.Prompt(prompt) 104 } 105 106 // PromptPassword displays the given prompt to the user and requests some textual 107 // data to be entered, but one which must not be echoed out into the terminal. 108 // The method returns the input provided by the user. 109 func (p *terminalPrompter) PromptPassword(prompt string) (passwd string, err error) { 110 if p.supported { 111 p.rawMode.ApplyMode() 112 defer p.normalMode.ApplyMode() 113 return p.State.PasswordPrompt(prompt) 114 } 115 if !p.warned { 116 fmt.Println("!! Unsupported terminal, password will be echoed.") 117 p.warned = true 118 } 119 // Just as in Prompt, handle printing the prompt here instead of relying on liner. 120 fmt.Print(prompt) 121 passwd, err = p.State.Prompt("") 122 fmt.Println() 123 return passwd, err 124 } 125 126 // PromptConfirm displays the given prompt to the user and requests a boolean 127 // choice to be made, returning that choice. 128 func (p *terminalPrompter) PromptConfirm(prompt string) (bool, error) { 129 input, err := p.Prompt(prompt + " [y/N] ") 130 if len(input) > 0 && strings.ToUpper(input[:1]) == "Y" { 131 return true, nil 132 } 133 return false, err 134 } 135 136 // SetHistory sets the the input scrollback history that the prompter will allow 137 // the user to scroll back to. 138 func (p *terminalPrompter) SetHistory(history []string) { 139 p.State.ReadHistory(strings.NewReader(strings.Join(history, "\n"))) 140 } 141 142 // AppendHistory appends an entry to the scrollback history. 143 func (p *terminalPrompter) AppendHistory(command string) { 144 p.State.AppendHistory(command) 145 } 146 147 // ClearHistory clears the entire history 148 func (p *terminalPrompter) ClearHistory() { 149 p.State.ClearHistory() 150 } 151 152 // SetWordCompleter sets the completion function that the prompter will call to 153 // fetch completion candidates when the user presses tab. 154 func (p *terminalPrompter) SetWordCompleter(completer WordCompleter) { 155 p.State.SetWordCompleter(liner.WordCompleter(completer)) 156 }