github.com/prattmic/llgo-embedded@v0.0.0-20150820070356-41cfecea0e1e/third_party/liner/input.go (about) 1 // +build linux darwin openbsd freebsd netbsd 2 3 package liner 4 5 import ( 6 "bufio" 7 "errors" 8 "os" 9 "os/signal" 10 "strconv" 11 "strings" 12 "syscall" 13 "time" 14 ) 15 16 type nexter struct { 17 r rune 18 err error 19 } 20 21 // State represents an open terminal 22 type State struct { 23 commonState 24 origMode termios 25 defaultMode termios 26 next <-chan nexter 27 winch chan os.Signal 28 pending []rune 29 useCHA bool 30 } 31 32 // NewLiner initializes a new *State, and sets the terminal into raw mode. To 33 // restore the terminal to its previous state, call State.Close(). 34 // 35 // Note if you are still using Go 1.0: NewLiner handles SIGWINCH, so it will 36 // leak a channel every time you call it. Therefore, it is recommened that you 37 // upgrade to a newer release of Go, or ensure that NewLiner is only called 38 // once. 39 func NewLiner() *State { 40 var s State 41 s.r = bufio.NewReader(os.Stdin) 42 43 s.terminalSupported = TerminalSupported() 44 if m, err := TerminalMode(); err == nil { 45 s.origMode = *m.(*termios) 46 } else { 47 s.inputRedirected = true 48 } 49 if _, err := getMode(syscall.Stdout); err != 0 { 50 s.outputRedirected = true 51 } 52 if s.inputRedirected && s.outputRedirected { 53 s.terminalSupported = false 54 } 55 if s.terminalSupported && !s.inputRedirected && !s.outputRedirected { 56 mode := s.origMode 57 mode.Iflag &^= icrnl | inpck | istrip | ixon 58 mode.Cflag |= cs8 59 mode.Lflag &^= syscall.ECHO | icanon | iexten 60 mode.ApplyMode() 61 62 winch := make(chan os.Signal, 1) 63 signal.Notify(winch, syscall.SIGWINCH) 64 s.winch = winch 65 66 s.checkOutput() 67 } 68 69 if !s.outputRedirected { 70 s.getColumns() 71 s.outputRedirected = s.columns <= 0 72 } 73 74 return &s 75 } 76 77 var errTimedOut = errors.New("timeout") 78 79 func (s *State) startPrompt() { 80 if s.terminalSupported { 81 if m, err := TerminalMode(); err == nil { 82 s.defaultMode = *m.(*termios) 83 mode := s.defaultMode 84 mode.Lflag &^= isig 85 mode.ApplyMode() 86 } 87 } 88 s.restartPrompt() 89 } 90 91 func (s *State) restartPrompt() { 92 next := make(chan nexter) 93 go func() { 94 for { 95 var n nexter 96 n.r, _, n.err = s.r.ReadRune() 97 next <- n 98 // Shut down nexter loop when an end condition has been reached 99 if n.err != nil || n.r == '\n' || n.r == '\r' || n.r == ctrlC || n.r == ctrlD { 100 close(next) 101 return 102 } 103 } 104 }() 105 s.next = next 106 } 107 108 func (s *State) stopPrompt() { 109 if s.terminalSupported { 110 s.defaultMode.ApplyMode() 111 } 112 } 113 114 func (s *State) nextPending(timeout <-chan time.Time) (rune, error) { 115 select { 116 case thing, ok := <-s.next: 117 if !ok { 118 return 0, errors.New("liner: internal error") 119 } 120 if thing.err != nil { 121 return 0, thing.err 122 } 123 s.pending = append(s.pending, thing.r) 124 return thing.r, nil 125 case <-timeout: 126 rv := s.pending[0] 127 s.pending = s.pending[1:] 128 return rv, errTimedOut 129 } 130 // not reached 131 return 0, nil 132 } 133 134 func (s *State) readNext() (interface{}, error) { 135 if len(s.pending) > 0 { 136 rv := s.pending[0] 137 s.pending = s.pending[1:] 138 return rv, nil 139 } 140 var r rune 141 select { 142 case thing, ok := <-s.next: 143 if !ok { 144 return 0, errors.New("liner: internal error") 145 } 146 if thing.err != nil { 147 return nil, thing.err 148 } 149 r = thing.r 150 case <-s.winch: 151 s.getColumns() 152 return winch, nil 153 } 154 if r != esc { 155 return r, nil 156 } 157 s.pending = append(s.pending, r) 158 159 // Wait at most 50 ms for the rest of the escape sequence 160 // If nothing else arrives, it was an actual press of the esc key 161 timeout := time.After(50 * time.Millisecond) 162 flag, err := s.nextPending(timeout) 163 if err != nil { 164 if err == errTimedOut { 165 return flag, nil 166 } 167 return unknown, err 168 } 169 170 switch flag { 171 case '[': 172 code, err := s.nextPending(timeout) 173 if err != nil { 174 if err == errTimedOut { 175 return code, nil 176 } 177 return unknown, err 178 } 179 switch code { 180 case 'A': 181 s.pending = s.pending[:0] // escape code complete 182 return up, nil 183 case 'B': 184 s.pending = s.pending[:0] // escape code complete 185 return down, nil 186 case 'C': 187 s.pending = s.pending[:0] // escape code complete 188 return right, nil 189 case 'D': 190 s.pending = s.pending[:0] // escape code complete 191 return left, nil 192 case 'F': 193 s.pending = s.pending[:0] // escape code complete 194 return end, nil 195 case 'H': 196 s.pending = s.pending[:0] // escape code complete 197 return home, nil 198 case 'Z': 199 s.pending = s.pending[:0] // escape code complete 200 return shiftTab, nil 201 case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': 202 num := []rune{code} 203 for { 204 code, err := s.nextPending(timeout) 205 if err != nil { 206 if err == errTimedOut { 207 return code, nil 208 } 209 return nil, err 210 } 211 switch code { 212 case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': 213 num = append(num, code) 214 case ';': 215 // Modifier code to follow 216 // This only supports Ctrl-left and Ctrl-right for now 217 x, _ := strconv.ParseInt(string(num), 10, 32) 218 if x != 1 { 219 // Can't be left or right 220 rv := s.pending[0] 221 s.pending = s.pending[1:] 222 return rv, nil 223 } 224 num = num[:0] 225 for { 226 code, err = s.nextPending(timeout) 227 if err != nil { 228 if err == errTimedOut { 229 rv := s.pending[0] 230 s.pending = s.pending[1:] 231 return rv, nil 232 } 233 return nil, err 234 } 235 switch code { 236 case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': 237 num = append(num, code) 238 case 'C', 'D': 239 // right, left 240 mod, _ := strconv.ParseInt(string(num), 10, 32) 241 if mod != 5 { 242 // Not bare Ctrl 243 rv := s.pending[0] 244 s.pending = s.pending[1:] 245 return rv, nil 246 } 247 s.pending = s.pending[:0] // escape code complete 248 if code == 'C' { 249 return wordRight, nil 250 } 251 return wordLeft, nil 252 default: 253 // Not left or right 254 rv := s.pending[0] 255 s.pending = s.pending[1:] 256 return rv, nil 257 } 258 } 259 case '~': 260 s.pending = s.pending[:0] // escape code complete 261 x, _ := strconv.ParseInt(string(num), 10, 32) 262 switch x { 263 case 2: 264 return insert, nil 265 case 3: 266 return del, nil 267 case 5: 268 return pageUp, nil 269 case 6: 270 return pageDown, nil 271 case 7: 272 return home, nil 273 case 8: 274 return end, nil 275 case 15: 276 return f5, nil 277 case 17: 278 return f6, nil 279 case 18: 280 return f7, nil 281 case 19: 282 return f8, nil 283 case 20: 284 return f9, nil 285 case 21: 286 return f10, nil 287 case 23: 288 return f11, nil 289 case 24: 290 return f12, nil 291 default: 292 return unknown, nil 293 } 294 default: 295 // unrecognized escape code 296 rv := s.pending[0] 297 s.pending = s.pending[1:] 298 return rv, nil 299 } 300 } 301 } 302 303 case 'O': 304 code, err := s.nextPending(timeout) 305 if err != nil { 306 if err == errTimedOut { 307 return code, nil 308 } 309 return nil, err 310 } 311 s.pending = s.pending[:0] // escape code complete 312 switch code { 313 case 'c': 314 return wordRight, nil 315 case 'd': 316 return wordLeft, nil 317 case 'H': 318 return home, nil 319 case 'F': 320 return end, nil 321 case 'P': 322 return f1, nil 323 case 'Q': 324 return f2, nil 325 case 'R': 326 return f3, nil 327 case 'S': 328 return f4, nil 329 default: 330 return unknown, nil 331 } 332 case 'y': 333 s.pending = s.pending[:0] // escape code complete 334 return altY, nil 335 default: 336 rv := s.pending[0] 337 s.pending = s.pending[1:] 338 return rv, nil 339 } 340 341 // not reached 342 return r, nil 343 } 344 345 // Close returns the terminal to its previous mode 346 func (s *State) Close() error { 347 stopSignal(s.winch) 348 if !s.inputRedirected { 349 s.origMode.ApplyMode() 350 } 351 return nil 352 } 353 354 // TerminalSupported returns true if the current terminal supports 355 // line editing features, and false if liner will use the 'dumb' 356 // fallback for input. 357 // Note that TerminalSupported does not check all factors that may 358 // cause liner to not fully support the terminal (such as stdin redirection) 359 func TerminalSupported() bool { 360 bad := map[string]bool{"": true, "dumb": true, "cons25": true} 361 return !bad[strings.ToLower(os.Getenv("TERM"))] 362 }