github.com/prattmic/llgo-embedded@v0.0.0-20150820070356-41cfecea0e1e/third_party/liner/common.go (about) 1 /* 2 Package liner implements a simple command line editor, inspired by linenoise 3 (https://github.com/antirez/linenoise/). This package supports WIN32 in 4 addition to the xterm codes supported by everything else. 5 */ 6 package liner 7 8 import ( 9 "bufio" 10 "bytes" 11 "container/ring" 12 "errors" 13 "fmt" 14 "io" 15 "strings" 16 "sync" 17 "unicode/utf8" 18 ) 19 20 type commonState struct { 21 terminalSupported bool 22 outputRedirected bool 23 inputRedirected bool 24 history []string 25 historyMutex sync.RWMutex 26 completer WordCompleter 27 columns int 28 killRing *ring.Ring 29 ctrlCAborts bool 30 r *bufio.Reader 31 tabStyle TabStyle 32 } 33 34 // TabStyle is used to select how tab completions are displayed. 35 type TabStyle int 36 37 // Two tab styles are currently available: 38 // 39 // TabCircular cycles through each completion item and displays it directly on 40 // the prompt 41 // 42 // TabPrints prints the list of completion items to the screen after a second 43 // tab key is pressed. This behaves similar to GNU readline and BASH (which 44 // uses readline) 45 const ( 46 TabCircular TabStyle = iota 47 TabPrints 48 ) 49 50 // ErrPromptAborted is returned from Prompt or PasswordPrompt when the user presses Ctrl-C 51 // if SetCtrlCAborts(true) has been called on the State 52 var ErrPromptAborted = errors.New("prompt aborted") 53 54 // ErrNotTerminalOutput is returned from Prompt or PasswordPrompt if the 55 // platform is normally supported, but stdout has been redirected 56 var ErrNotTerminalOutput = errors.New("standard output is not a terminal") 57 58 // Max elements to save on the killring 59 const KillRingMax = 60 60 61 // HistoryLimit is the maximum number of entries saved in the scrollback history. 62 const HistoryLimit = 1000 63 64 // ReadHistory reads scrollback history from r. Returns the number of lines 65 // read, and any read error (except io.EOF). 66 func (s *State) ReadHistory(r io.Reader) (num int, err error) { 67 s.historyMutex.Lock() 68 defer s.historyMutex.Unlock() 69 70 in := bufio.NewReader(r) 71 num = 0 72 for { 73 line, part, err := in.ReadLine() 74 if err == io.EOF { 75 break 76 } 77 if err != nil { 78 return num, err 79 } 80 if part { 81 return num, fmt.Errorf("line %d is too long", num+1) 82 } 83 if !utf8.Valid(line) { 84 return num, fmt.Errorf("invalid string at line %d", num+1) 85 } 86 num++ 87 s.history = append(s.history, string(line)) 88 if len(s.history) > HistoryLimit { 89 s.history = s.history[1:] 90 } 91 } 92 return num, nil 93 } 94 95 // WriteHistory writes scrollback history to w. Returns the number of lines 96 // successfully written, and any write error. 97 // 98 // Unlike the rest of liner's API, WriteHistory is safe to call 99 // from another goroutine while Prompt is in progress. 100 // This exception is to facilitate the saving of the history buffer 101 // during an unexpected exit (for example, due to Ctrl-C being invoked) 102 func (s *State) WriteHistory(w io.Writer) (num int, err error) { 103 s.historyMutex.RLock() 104 defer s.historyMutex.RUnlock() 105 106 for _, item := range s.history { 107 _, err := fmt.Fprintln(w, item) 108 if err != nil { 109 return num, err 110 } 111 num++ 112 } 113 return num, nil 114 } 115 116 // AppendHistory appends an entry to the scrollback history. AppendHistory 117 // should be called iff Prompt returns a valid command. 118 func (s *State) AppendHistory(item string) { 119 s.historyMutex.Lock() 120 defer s.historyMutex.Unlock() 121 122 if len(s.history) > 0 { 123 if item == s.history[len(s.history)-1] { 124 return 125 } 126 } 127 s.history = append(s.history, item) 128 if len(s.history) > HistoryLimit { 129 s.history = s.history[1:] 130 } 131 } 132 133 // Returns the history lines starting with prefix 134 func (s *State) getHistoryByPrefix(prefix string) (ph []string) { 135 for _, h := range s.history { 136 if strings.HasPrefix(h, prefix) { 137 ph = append(ph, h) 138 } 139 } 140 return 141 } 142 143 // Returns the history lines matching the inteligent search 144 func (s *State) getHistoryByPattern(pattern string) (ph []string, pos []int) { 145 if pattern == "" { 146 return 147 } 148 for _, h := range s.history { 149 if i := strings.Index(h, pattern); i >= 0 { 150 ph = append(ph, h) 151 pos = append(pos, i) 152 } 153 } 154 return 155 } 156 157 // Completer takes the currently edited line content at the left of the cursor 158 // and returns a list of completion candidates. 159 // If the line is "Hello, wo!!!" and the cursor is before the first '!', "Hello, wo" is passed 160 // to the completer which may return {"Hello, world", "Hello, Word"} to have "Hello, world!!!". 161 type Completer func(line string) []string 162 163 // WordCompleter takes the currently edited line with the cursor position and 164 // returns the completion candidates for the partial word to be completed. 165 // If the line is "Hello, wo!!!" and the cursor is before the first '!', ("Hello, wo!!!", 9) is passed 166 // to the completer which may returns ("Hello, ", {"world", "Word"}, "!!!") to have "Hello, world!!!". 167 type WordCompleter func(line string, pos int) (head string, completions []string, tail string) 168 169 // SetCompleter sets the completion function that Liner will call to 170 // fetch completion candidates when the user presses tab. 171 func (s *State) SetCompleter(f Completer) { 172 if f == nil { 173 s.completer = nil 174 return 175 } 176 s.completer = func(line string, pos int) (string, []string, string) { 177 return "", f(line[:pos]), line[pos:] 178 } 179 } 180 181 // SetWordCompleter sets the completion function that Liner will call to 182 // fetch completion candidates when the user presses tab. 183 func (s *State) SetWordCompleter(f WordCompleter) { 184 s.completer = f 185 } 186 187 // SetTabCompletionStyle sets the behvavior when the Tab key is pressed 188 // for auto-completion. TabCircular is the default behavior and cycles 189 // through the list of candidates at the prompt. TabPrints will print 190 // the available completion candidates to the screen similar to BASH 191 // and GNU Readline 192 func (s *State) SetTabCompletionStyle(tabStyle TabStyle) { 193 s.tabStyle = tabStyle 194 } 195 196 // ModeApplier is the interface that wraps a representation of the terminal 197 // mode. ApplyMode sets the terminal to this mode. 198 type ModeApplier interface { 199 ApplyMode() error 200 } 201 202 // SetCtrlCAborts sets whether Prompt on a supported terminal will return an 203 // ErrPromptAborted when Ctrl-C is pressed. The default is false (will not 204 // return when Ctrl-C is pressed). Unsupported terminals typically raise SIGINT 205 // (and Prompt does not return) regardless of the value passed to SetCtrlCAborts. 206 func (s *State) SetCtrlCAborts(aborts bool) { 207 s.ctrlCAborts = aborts 208 } 209 210 func (s *State) promptUnsupported(p string) (string, error) { 211 if !s.inputRedirected || !s.terminalSupported { 212 fmt.Print(p) 213 } 214 linebuf, _, err := s.r.ReadLine() 215 if err != nil { 216 return "", err 217 } 218 return string(bytes.TrimSpace(linebuf)), nil 219 }