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