github.com/xyproto/u-root@v6.0.1-0.20200302025726-5528e0c77a3c+incompatible/cmds/core/elvish/edit/tty/reader_unix.go (about) 1 // +build !windows,!plan9 2 3 package tty 4 5 import ( 6 "fmt" 7 "os" 8 "time" 9 10 "github.com/u-root/u-root/cmds/core/elvish/edit/ui" 11 ) 12 13 // DefaultSeqTimeout is the amount of time within which runes that make up an 14 // escape sequence are supposed to follow each other. Modern terminal emulators 15 // send escape sequences very fast, so 10ms is more than sufficient. SSH 16 // connections on a slow link might be problematic though. 17 const DefaultSeqTimeout = 10 * time.Millisecond 18 19 // reader reads terminal escape sequences and decodes them into events. 20 type reader struct { 21 ar *runeReader 22 seqTimeout time.Duration 23 raw bool 24 25 eventChan chan Event 26 stopChan chan struct{} 27 stopAckChan chan struct{} 28 } 29 30 func newReader(f *os.File) *reader { 31 rd := &reader{ 32 newRuneReader(f), 33 DefaultSeqTimeout, 34 false, 35 make(chan Event), 36 nil, 37 nil, 38 } 39 return rd 40 } 41 42 // SetRaw turns the raw option on or off. If the reader is in the middle of 43 // reading one event, it takes effect after this event is fully read. 44 func (rd *reader) SetRaw(raw bool) { 45 rd.raw = raw 46 } 47 48 // EventChan returns the channel onto which the Reader writes what it has read. 49 func (rd *reader) EventChan() <-chan Event { 50 return rd.eventChan 51 } 52 53 // Start starts the Reader. 54 func (rd *reader) Start() { 55 rd.stopChan = make(chan struct{}) 56 rd.stopAckChan = make(chan struct{}) 57 rd.ar.Start() 58 go rd.run() 59 } 60 61 func (rd *reader) run() { 62 // NOTE: Stop may be called at any time. All channel reads and sends should 63 // be wrapped in a select and have a "case <-rd.stopChan" clause. 64 for { 65 select { 66 case r := <-rd.ar.Chan(): 67 if rd.raw { 68 rd.send(RawRune(r)) 69 } else { 70 event, seqError, ioError := rd.readOne(r) 71 if event != nil { 72 rd.send(event) 73 } 74 if seqError != nil { 75 rd.send(NonfatalErrorEvent{seqError}) 76 } 77 if ioError != nil { 78 rd.send(FatalErrorEvent{ioError}) 79 <-rd.stopChan 80 } 81 } 82 case err := <-rd.ar.ErrorChan(): 83 rd.send(FatalErrorEvent{err}) 84 <-rd.stopChan 85 case <-rd.stopChan: 86 } 87 88 select { 89 case <-rd.stopChan: 90 close(rd.stopAckChan) 91 return 92 default: 93 } 94 } 95 } 96 97 // send tries to send an event, unless stop was requested. If stop was requested 98 // before, it does nothing; hence it is safe to use after stop. 99 func (rd *reader) send(event Event) { 100 select { 101 case rd.eventChan <- event: 102 case <-rd.stopChan: 103 } 104 } 105 106 // Stop stops the Reader. 107 func (rd *reader) Stop() { 108 rd.ar.Stop() 109 close(rd.stopChan) 110 <-rd.stopAckChan 111 } 112 113 // Close releases files associated with the Reader. It does not close the file 114 // used to create it. 115 func (rd *reader) Close() { 116 rd.ar.Close() 117 } 118 119 // Used by readRune in readOne to signal end of current sequence. 120 const runeEndOfSeq rune = -1 121 122 // readOne attempts to read one key or CPR, led by a rune already read. 123 func (rd *reader) readOne(r rune) (event Event, seqError, ioError error) { 124 currentSeq := string(r) 125 126 badSeq := func(msg string) { 127 seqError = fmt.Errorf("%s: %q", msg, currentSeq) 128 } 129 130 // readRune attempts to read a rune within a timeout of EscSequenceTimeout. 131 // It may return runeEndOfSeq when the read timed out, an error was 132 // encountered (in which case it sets ioError) or when stopped. In all three 133 // cases, the reader should terminate the current sequence. If the current 134 // sequence is valid, it should set event. If not, it should set seqError by 135 // calling badSeq. 136 readRune := 137 func() rune { 138 select { 139 case r := <-rd.ar.Chan(): 140 currentSeq += string(r) 141 return r 142 case ioError = <-rd.ar.ErrorChan(): 143 return runeEndOfSeq 144 case <-time.After(rd.seqTimeout): 145 return runeEndOfSeq 146 case <-rd.stopChan: 147 return runeEndOfSeq 148 } 149 } 150 151 switch r { 152 case 0x1b: // ^[ Escape 153 r2 := readRune() 154 // According to https://unix.stackexchange.com/a/73697, rxvt and derivatives 155 // prepend another ESC to a CSI-style or G3-style sequence to signal Alt. 156 // If that happens, remember this now; it will be later picked up when parsing 157 // those two kinds of sequences. 158 // 159 // issue #181 160 hasTwoLeadingESC := false 161 if r2 == 0x1b { 162 hasTwoLeadingESC = true 163 r2 = readRune() 164 } 165 if r2 == runeEndOfSeq { 166 // XXX Error is swallowed 167 // Nothing follows. Taken as a lone Escape. 168 event = KeyEvent{'[', ui.Ctrl} 169 break 170 } 171 switch r2 { 172 case '[': 173 // A '[' follows. CSI style function key sequence. 174 r = readRune() 175 if r == runeEndOfSeq { 176 event = KeyEvent{'[', ui.Alt} 177 return 178 } 179 180 nums := make([]int, 0, 2) 181 var starter rune 182 183 // Read an optional starter. 184 switch r { 185 case '<': 186 starter = r 187 r = readRune() 188 case 'M': 189 // Mouse event. 190 cb := readRune() 191 if cb == runeEndOfSeq { 192 badSeq("Incomplete mouse event") 193 return 194 } 195 cx := readRune() 196 if cx == runeEndOfSeq { 197 badSeq("Incomplete mouse event") 198 return 199 } 200 cy := readRune() 201 if cy == runeEndOfSeq { 202 badSeq("Incomplete mouse event") 203 return 204 } 205 down := true 206 button := int(cb & 3) 207 if button == 3 { 208 down = false 209 button = -1 210 } 211 mod := mouseModify(int(cb)) 212 event = MouseEvent{ 213 Pos{int(cy) - 32, int(cx) - 32}, down, button, mod} 214 return 215 } 216 CSISeq: 217 for { 218 switch { 219 case r == ';': 220 nums = append(nums, 0) 221 case '0' <= r && r <= '9': 222 if len(nums) == 0 { 223 nums = append(nums, 0) 224 } 225 cur := len(nums) - 1 226 nums[cur] = nums[cur]*10 + int(r-'0') 227 case r == runeEndOfSeq: 228 // Incomplete CSI. 229 badSeq("Incomplete CSI") 230 return 231 default: // Treat as a terminator. 232 break CSISeq 233 } 234 235 r = readRune() 236 } 237 if starter == 0 && r == 'R' { 238 // Cursor position report. 239 if len(nums) != 2 { 240 badSeq("bad CPR") 241 return 242 } 243 event = CursorPosition{nums[0], nums[1]} 244 } else if starter == '<' && (r == 'm' || r == 'M') { 245 // SGR-style mouse event. 246 if len(nums) != 3 { 247 badSeq("bad SGR mouse event") 248 return 249 } 250 down := r == 'M' 251 button := nums[0] & 3 252 mod := mouseModify(nums[0]) 253 event = MouseEvent{Pos{nums[2], nums[1]}, down, button, mod} 254 } else if r == '~' && len(nums) == 1 && (nums[0] == 200 || nums[0] == 201) { 255 b := nums[0] == 200 256 event = PasteSetting(b) 257 } else { 258 k := parseCSI(nums, r, currentSeq) 259 if k == (ui.Key{}) { 260 badSeq("bad CSI") 261 } else { 262 if hasTwoLeadingESC { 263 k.Mod |= ui.Alt 264 } 265 event = KeyEvent(k) 266 } 267 } 268 case 'O': 269 // An 'O' follows. G3 style function key sequence: read one rune. 270 r = readRune() 271 if r == runeEndOfSeq { 272 // Nothing follows after 'O'. Taken as Alt-o. 273 event = KeyEvent{'o', ui.Alt} 274 return 275 } 276 k, ok := g3Seq[r] 277 if ok { 278 if hasTwoLeadingESC { 279 k.Mod |= ui.Alt 280 } 281 event = KeyEvent(k) 282 } else { 283 badSeq("bad G3") 284 } 285 default: 286 // Something other than '[' or 'O' follows. Taken as an 287 // Alt-modified key, possibly also modified by Ctrl. 288 k := ctrlModify(r2) 289 k.Mod |= ui.Alt 290 event = KeyEvent(k) 291 } 292 default: 293 event = KeyEvent(ctrlModify(r)) 294 } 295 return 296 } 297 298 // ctrlModify determines whether a rune corresponds to a Ctrl-modified key and 299 // returns the ui.Key the rune represents. 300 func ctrlModify(r rune) ui.Key { 301 switch r { 302 case 0x0: 303 return ui.Key{'`', ui.Ctrl} // ^@ 304 case 0x1e: 305 return ui.Key{'6', ui.Ctrl} // ^^ 306 case 0x1f: 307 return ui.Key{'/', ui.Ctrl} // ^_ 308 case ui.Tab, ui.Enter, ui.Backspace: // ^I ^J ^? 309 return ui.Key{r, 0} 310 default: 311 // Regular ui.Ctrl sequences. 312 if 0x1 <= r && r <= 0x1d { 313 return ui.Key{r + 0x40, ui.Ctrl} 314 } 315 } 316 return ui.Key{r, 0} 317 } 318 319 // Tables for key sequences. Comments document which terminal emulators are 320 // known to generate which sequences. The terminal emulators tested are 321 // categorized into xterm (including actual xterm, libvte-based terminals, 322 // Konsole and Terminal.app unless otherwise noted), urxvt, tmux. 323 324 // G3-style key sequences: \eO followed by exactly one character. For instance, 325 // \eOP is F1. These are pretty limited in that they cannot be extended to 326 // support modifier keys, other than a leading \e for Alt (e.g. \e\eOP is 327 // Alt-F1). Terminals that send G3-style key sequences typically switch to 328 // sending a CSI-style key sequence when a non-Alt modifier key is pressed. 329 var g3Seq = map[rune]ui.Key{ 330 // xterm, tmux -- only in Vim, depends on termios setting? 331 // NOTE(xiaq): According to urxvt's manpage, \eO[ABCD] sequences are used for 332 // Ctrl-Shift-modified arrow keys; however, this doesn't seem to be true for 333 // urxvt 9.22 packaged by Debian; those keys simply send the same sequence 334 // as Ctrl-modified keys (\eO[abcd]). 335 'A': {ui.Up, 0}, 'B': {ui.Down, 0}, 'C': {ui.Right, 0}, 'D': {ui.Left, 0}, 336 'H': {ui.Home, 0}, 'F': {ui.End, 0}, 'M': {ui.Insert, 0}, 337 // urxvt 338 'a': {ui.Up, ui.Ctrl}, 'b': {ui.Down, ui.Ctrl}, 339 'c': {ui.Right, ui.Ctrl}, 'd': {ui.Left, ui.Ctrl}, 340 // xterm, urxvt, tmux 341 'P': {ui.F1, 0}, 'Q': {ui.F2, 0}, 'R': {ui.F3, 0}, 'S': {ui.F4, 0}, 342 } 343 344 // Tables for CSI-style key sequences. A CSI sequence is \e[ followed by zero or 345 // more numerical arguments (separated by semicolons), ending in a non-numeric, 346 // non-semicolon rune. They are used for many purposes, and CSI-style key 347 // sequences are a subset of them. 348 // 349 // There are several variants of CSI-style key sequences; see comments above the 350 // respective tables. In all variants, modifier keys are encoded in numerical 351 // arguments; see xtermModify. Note that although the set of possible sequences 352 // make it possible to express a very complete set of key combinations, they are 353 // not always sent by terminals. For instance, many (if not most) terminals will 354 // send the same sequence for Up when Shift-Up is pressed, even if Shift-Up is 355 // expressible using the escape sequences described below. 356 357 // CSI-style key sequences identified by the last rune. For instance, \e[A is 358 // Up. When modified, two numerical arguments are added, the first always beging 359 // 1 and the second identifying the modifier. For instance, \e1;5A is Ctrl-Up. 360 var csiSeqByLast = map[rune]ui.Key{ 361 // xterm, urxvt, tmux 362 'A': {ui.Up, 0}, 'B': {ui.Down, 0}, 'C': {ui.Right, 0}, 'D': {ui.Left, 0}, 363 // urxvt 364 'a': {ui.Up, ui.Shift}, 'b': {ui.Down, ui.Shift}, 365 'c': {ui.Right, ui.Shift}, 'd': {ui.Left, ui.Shift}, 366 // xterm (Terminal.app only sends those in alternate screen) 367 'H': {ui.Home, 0}, 'F': {ui.End, 0}, 368 // xterm, urxvt, tmux 369 'Z': {ui.Tab, ui.Shift}, 370 } 371 372 // CSI-style key sequences ending with '~' and identified by one numerical 373 // argument. For instance, \e[3~ is Delete. When modified, an additional 374 // argument identifies the modifier; for instance, \e[3;5~ is Ctrl-Delete. 375 // 376 // An alternative encoding of the modifier key, only known to be used by urxvt 377 // (or for that matter, likely also rxvt) is to change the last rune: '$' for 378 // Shift, '^' for Ctrl, and '@' for Ctrl+Shift. The numeric argument is kept 379 // unchanged. For instance, \e[3^ is Ctrl-Delete. 380 var csiSeqTilde = map[int]rune{ 381 // tmux (NOTE: urxvt uses the pair for Find/Select) 382 1: ui.Home, 4: ui.End, 383 // xterm (Terminal.app sends ^M for Fn+Enter), urxvt, tmux 384 2: ui.Insert, 385 // xterm, urxvt, tmux 386 3: ui.Delete, 387 // xterm (Terminal.app only sends those in alternate screen), urxvt, tmux 388 // NOTE: called Prior/Next in urxvt manpage 389 5: ui.PageUp, 6: ui.PageDown, 390 // urxvt 391 7: ui.Home, 8: ui.End, 392 // urxvt 393 11: ui.F1, 12: ui.F2, 13: ui.F3, 14: ui.F4, 394 // xterm, urxvt, tmux 395 // NOTE: 16 and 22 are unused 396 15: ui.F5, 17: ui.F6, 18: ui.F7, 19: ui.F8, 397 20: ui.F9, 21: ui.F10, 23: ui.F11, 24: ui.F12, 398 } 399 400 // CSI-style key sequences ending with '~', with the first argument always 27, 401 // the second argument identifying the modifier, and the third argument 402 // identifying the key. For instance, \e[27;5;9~ is Ctrl-Tab. 403 // 404 // NOTE(xiaq): The list is taken blindly from xterm-keys.c in the tmux source 405 // tree. I do not have a keyboard-terminal combination that generate such 406 // sequences, but assumably they are generated by some terminals for numpad 407 // inputs. 408 var csiSeqTilde27 = map[int]rune{ 409 9: '\t', 13: '\r', 410 33: '!', 35: '#', 39: '\'', 40: '(', 41: ')', 43: '+', 44: ',', 45: '-', 411 46: '.', 412 48: '0', 49: '1', 50: '2', 51: '3', 52: '4', 53: '5', 54: '6', 55: '7', 413 56: '8', 57: '9', 414 58: ':', 59: ';', 60: '<', 61: '=', 62: '>', 63: ';', 415 } 416 417 // parseCSI parses a CSI-style key sequence. See comments above for all the 3 418 // variants this function handles. 419 func parseCSI(nums []int, last rune, seq string) ui.Key { 420 if k, ok := csiSeqByLast[last]; ok { 421 if len(nums) == 0 { 422 // Unmodified: \e[A (Up) 423 return k 424 } else if len(nums) == 2 && nums[0] == 1 { 425 // Modified: \e[1;5A (Ctrl-Up) 426 return xtermModify(k, nums[1], seq) 427 } else { 428 return ui.Key{} 429 } 430 } 431 432 switch last { 433 case '~': 434 if len(nums) == 1 || len(nums) == 2 { 435 if r, ok := csiSeqTilde[nums[0]]; ok { 436 k := ui.Key{r, 0} 437 if len(nums) == 1 { 438 // Unmodified: \e[5~ (e.g. PageUp) 439 return k 440 } 441 // Modified: \e[5;5~ (e.g. Ctrl-PageUp) 442 return xtermModify(k, nums[1], seq) 443 } 444 } else if len(nums) == 3 && nums[0] == 27 { 445 if r, ok := csiSeqTilde27[nums[2]]; ok { 446 k := ui.Key{r, 0} 447 return xtermModify(k, nums[1], seq) 448 } 449 } 450 case '$', '^', '@': 451 // Modified by urxvt; see comment above csiSeqTilde. 452 if len(nums) == 1 { 453 if r, ok := csiSeqTilde[nums[0]]; ok { 454 var mod ui.Mod 455 switch last { 456 case '$': 457 mod = ui.Shift 458 case '^': 459 mod = ui.Ctrl 460 case '@': 461 mod = ui.Shift | ui.Ctrl 462 } 463 return ui.Key{r, mod} 464 } 465 } 466 } 467 468 return ui.Key{} 469 } 470 471 func xtermModify(k ui.Key, mod int, seq string) ui.Key { 472 switch mod { 473 case 0: 474 // do nothing 475 case 2: 476 k.Mod |= ui.Shift 477 case 3: 478 k.Mod |= ui.Alt 479 case 4: 480 k.Mod |= ui.Shift | ui.Alt 481 case 5: 482 k.Mod |= ui.Ctrl 483 case 6: 484 k.Mod |= ui.Shift | ui.Ctrl 485 case 7: 486 k.Mod |= ui.Alt | ui.Ctrl 487 case 8: 488 k.Mod |= ui.Shift | ui.Alt | ui.Ctrl 489 default: 490 return ui.Key{} 491 } 492 return k 493 } 494 495 func mouseModify(n int) ui.Mod { 496 var mod ui.Mod 497 if n&4 != 0 { 498 mod |= ui.Shift 499 } 500 if n&8 != 0 { 501 mod |= ui.Alt 502 } 503 if n&16 != 0 { 504 mod |= ui.Ctrl 505 } 506 return mod 507 }