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