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