github.com/ladydascalie/elvish@v0.0.0-20170703214355-2964dd3ece7f/edit/tty/reader.go (about) 1 package tty 2 3 import ( 4 "fmt" 5 "os" 6 "time" 7 8 "github.com/elves/elvish/edit/ui" 9 ) 10 11 var ( 12 // EscSequenceTimeout is the amount of time within which runes that make up 13 // an escape sequence are supposed to follow each other. Modern terminal 14 // emulators send escape sequences very fast, so 10ms is more than 15 // sufficient. SSH connections on a slow link might be problematic though. 16 EscSequenceTimeout = 10 * time.Millisecond 17 ) 18 19 // Special rune values used in the return value of (*Reader).ReadRune. 20 const ( 21 // No rune received before specified time. 22 runeTimeout rune = -1 - iota 23 // Error occurred in AsyncReader. The error is left at the readError field. 24 runeReadError 25 ) 26 27 // Reader converts a stream of events on separate channels. 28 type Reader struct { 29 ar *AsyncReader 30 raw bool 31 32 unitChan chan ReadUnit 33 /* 34 rawRuneChan chan rune 35 keyChan chan ui.Key 36 cprChan chan Pos 37 mouseChan chan MouseEvent 38 pasteChan chan bool 39 */ 40 errChan chan error 41 quit chan struct{} 42 } 43 44 type MouseEvent struct { 45 Pos 46 Down bool 47 // Number of the Button, 0-based. -1 for unknown. 48 Button int 49 Mod ui.Mod 50 } 51 52 // NewReader creates a new Reader on the given terminal file. 53 func NewReader(f *os.File) *Reader { 54 rd := &Reader{ 55 NewAsyncReader(f), 56 false, 57 make(chan ReadUnit), 58 /* 59 make(chan rune), 60 make(chan ui.Key), 61 make(chan Pos), 62 make(chan MouseEvent), 63 make(chan bool), 64 */ 65 make(chan error), 66 nil, 67 } 68 return rd 69 } 70 71 // SetRaw turns the raw option on or off. If the reader is in the middle of 72 // reading one event, it takes effect after this event is fully read. 73 func (rd *Reader) SetRaw(raw bool) { 74 rd.raw = raw 75 } 76 77 // UnitChan returns the channel onto which the Reader writes what it has read. 78 func (rd *Reader) UnitChan() <-chan ReadUnit { 79 return rd.unitChan 80 } 81 82 // ErrorChan returns the channel onto which the Reader writes errors it came 83 // across during the reading process. 84 func (rd *Reader) ErrorChan() <-chan error { 85 return rd.errChan 86 } 87 88 // Run runs the Reader. It blocks until Quit is called and should be called in 89 // a separate goroutine. 90 func (rd *Reader) Run() { 91 runes := rd.ar.Chan() 92 quit := make(chan struct{}) 93 rd.quit = quit 94 go rd.ar.Run() 95 96 for { 97 select { 98 case r := <-runes: 99 if rd.raw { 100 rd.unitChan <- RawRune(r) 101 } else { 102 rd.readOne(r) 103 } 104 case <-quit: 105 return 106 } 107 } 108 } 109 110 // Quit terminates the loop of Run. 111 func (rd *Reader) Quit() { 112 rd.ar.Quit() 113 close(rd.quit) 114 } 115 116 // Close releases files associated with the Reader. It does not close the file 117 // used to create it. 118 func (rd *Reader) Close() { 119 rd.ar.Close() 120 } 121 122 // readOne attempts to read one key or CPR, led by a rune already read. 123 func (rd *Reader) readOne(r rune) { 124 var unit ReadUnit 125 var err error 126 currentSeq := string(r) 127 128 badSeq := func(msg string) { 129 err = fmt.Errorf("%s: %q", msg, currentSeq) 130 } 131 132 // readRune attempts to read a rune within EscSequenceTimeout. It writes to 133 // the err and currentSeq variable in the outer scope. 134 readRune := 135 func() rune { 136 select { 137 case r := <-rd.ar.Chan(): 138 currentSeq += string(r) 139 return r 140 case err = <-rd.ar.ErrorChan(): 141 return runeReadError 142 case <-time.After(EscSequenceTimeout): 143 return runeTimeout 144 } 145 } 146 147 defer func() { 148 if unit != nil { 149 select { 150 case rd.unitChan <- unit: 151 case <-rd.quit: 152 } 153 } 154 if err != nil { 155 select { 156 case rd.errChan <- err: 157 case <-rd.quit: 158 } 159 } 160 }() 161 162 switch r { 163 case 0x1b: // ^[ Escape 164 r2 := readRune() 165 if r2 == runeTimeout || r2 == runeReadError { 166 // Nothing follows. Taken as a lone Escape. 167 unit = Key{'[', ui.Ctrl} 168 break 169 } 170 switch r2 { 171 case '[': 172 // A '[' follows. CSI style function key sequence. 173 r = readRune() 174 if r == runeTimeout || r == runeReadError { 175 unit = Key{'[', ui.Alt} 176 return 177 } 178 179 nums := make([]int, 0, 2) 180 var starter rune 181 182 // Read an optional starter. 183 switch r { 184 case '<': 185 starter = r 186 r = readRune() 187 case 'M': 188 // Mouse event. 189 cb := readRune() 190 if cb == runeTimeout || cb == runeReadError { 191 badSeq("Incomplete mouse event") 192 return 193 } 194 cx := readRune() 195 if cx == runeTimeout || cx == runeReadError { 196 badSeq("Incomplete mouse event") 197 return 198 } 199 cy := readRune() 200 if cy == runeTimeout || cy == runeReadError { 201 badSeq("Incomplete mouse event") 202 return 203 } 204 down := true 205 button := int(cb & 3) 206 if button == 3 { 207 down = false 208 button = -1 209 } 210 mod := mouseModify(int(cb)) 211 unit = MouseEvent{ 212 Pos{int(cy) - 32, int(cx) - 32}, down, button, mod} 213 return 214 } 215 CSISeq: 216 for { 217 switch { 218 case r == ';': 219 nums = append(nums, 0) 220 case '0' <= r && r <= '9': 221 if len(nums) == 0 { 222 nums = append(nums, 0) 223 } 224 cur := len(nums) - 1 225 nums[cur] = nums[cur]*10 + int(r-'0') 226 case r == runeTimeout: 227 // Incomplete CSI. 228 badSeq("Incomplete CSI") 229 return 230 case r == runeReadError: 231 // TODO Also complain about incomplte CSI. 232 return 233 default: // Treat as a terminator. 234 break CSISeq 235 } 236 237 r = readRune() 238 } 239 if starter == 0 && r == 'R' { 240 // Cursor position report. 241 if len(nums) != 2 { 242 badSeq("bad CPR") 243 return 244 } 245 unit = CursorPosition{nums[0], nums[1]} 246 } else if starter == '<' && (r == 'm' || r == 'M') { 247 // SGR-style mouse event. 248 if len(nums) != 3 { 249 badSeq("bad SGR mouse event") 250 return 251 } 252 down := r == 'M' 253 button := nums[0] & 3 254 mod := mouseModify(nums[0]) 255 unit = MouseEvent{Pos{nums[2], nums[1]}, down, button, mod} 256 } else if r == '~' && len(nums) == 1 && (nums[0] == 200 || nums[0] == 201) { 257 b := nums[0] == 200 258 unit = PasteSetting(b) 259 } else { 260 k := parseCSI(nums, r, currentSeq) 261 if k == (ui.Key{}) { 262 badSeq("bad CSI") 263 } else { 264 unit = Key(k) 265 } 266 } 267 case 'O': 268 // An 'O' follows. G3 style function key sequence: read one rune. 269 r = readRune() 270 if r == runeTimeout || r == runeReadError { 271 // Nothing follows after 'O'. Taken as ui.Alt-o. 272 unit = Key{'o', ui.Alt} 273 return 274 } 275 r, ok := g3Seq[r] 276 if ok { 277 unit = Key{r, 0} 278 } else { 279 badSeq("bad G3") 280 } 281 default: 282 // Something other than '[' or 'O' follows. Taken as an 283 // ui.Alt-modified key, possibly also modified by ui.Ctrl. 284 k := ctrlModify(r2) 285 k.Mod |= ui.Alt 286 unit = Key(k) 287 } 288 default: 289 k := ctrlModify(r) 290 unit = Key(k) 291 } 292 } 293 294 // ctrlModify determines whether a rune corresponds to a ui.Ctrl-modified key and 295 // returns the ui.Key the rune represents. 296 func ctrlModify(r rune) ui.Key { 297 switch r { 298 case 0x0: 299 return ui.Key{'`', ui.Ctrl} // ^@ 300 case 0x1e: 301 return ui.Key{'6', ui.Ctrl} // ^^ 302 case 0x1f: 303 return ui.Key{'/', ui.Ctrl} // ^_ 304 case ui.Tab, ui.Enter, ui.Backspace: // ^I ^J ^? 305 return ui.Key{r, 0} 306 default: 307 // Regular ui.Ctrl sequences. 308 if 0x1 <= r && r <= 0x1d { 309 return ui.Key{r + 0x40, ui.Ctrl} 310 } 311 } 312 return ui.Key{r, 0} 313 } 314 315 // G3-style key sequences: \eO followed by exactly one character. For instance, 316 // \eOP is ui.F1. 317 var g3Seq = map[rune]rune{ 318 'A': ui.Up, 'B': ui.Down, 'C': ui.Right, 'D': ui.Left, 319 320 // ui.F1-ui.F4: xterm, libvte and tmux 321 'P': ui.F1, 'Q': ui.F2, 322 'R': ui.F3, 'S': ui.F4, 323 324 // ui.Home and ui.End: libvte 325 'H': ui.Home, 'F': ui.End, 326 } 327 328 // Tables for CSI-style key sequences, which are \e[ followed by a list of 329 // semicolon-delimited numeric arguments, before being concluded by a 330 // non-numeric, non-semicolon rune. 331 332 // CSI-style key sequences that can be identified based on the ending rune. For 333 // instance, \e[A is ui.Up. 334 var keyByLast = map[rune]ui.Key{ 335 'A': {ui.Up, 0}, 'B': {ui.Down, 0}, 336 'C': {ui.Right, 0}, 'D': {ui.Left, 0}, 337 'H': {ui.Home, 0}, 'F': {ui.End, 0}, 338 'Z': {ui.Tab, ui.Shift}, 339 } 340 341 // CSI-style key sequences ending with '~' and can be identified based on the 342 // only number argument. For instance, \e[~ is ui.Home. When they are 343 // modified, they take two arguments, first being 1 and second identifying the 344 // modifier (see xtermModify). For instance, \e[1;4~ is Shift-Alt-Home. 345 var keyByNum0 = map[int]rune{ 346 1: ui.Home, 2: ui.Insert, 3: ui.Delete, 4: ui.End, 347 5: ui.PageUp, 6: ui.PageDown, 348 11: ui.F1, 12: ui.F2, 13: ui.F3, 14: ui.F4, 349 15: ui.F5, 17: ui.F6, 18: ui.F7, 19: ui.F8, 350 20: ui.F9, 21: ui.F10, 23: ui.F11, 24: ui.F12, 351 } 352 353 // CSI-style key sequences ending with '~', with 27 as the first numeric 354 // argument. For instance, \e[27;9~ is ui.Tab. 355 // 356 // The list is taken blindly from tmux source xterm-keys.c. I don't have a 357 // keyboard-terminal combination that generate such sequences, but assumably 358 // some PC keyboard with a numpad can. 359 var keyByNum2 = map[int]rune{ 360 9: '\t', 13: '\r', 361 33: '!', 35: '#', 39: '\'', 40: '(', 41: ')', 43: '+', 44: ',', 45: '-', 362 46: '.', 363 48: '0', 49: '1', 50: '2', 51: '3', 52: '4', 53: '5', 54: '6', 55: '7', 364 56: '8', 57: '9', 365 58: ':', 59: ';', 60: '<', 61: '=', 62: '>', 63: ';', 366 } 367 368 // parseCSI parses a CSI-style key sequence. 369 func parseCSI(nums []int, last rune, seq string) ui.Key { 370 if k, ok := keyByLast[last]; ok { 371 if len(nums) == 0 { 372 // Unmodified: \e[A (ui.Up) 373 return k 374 } else if len(nums) == 2 && nums[0] == 1 { 375 // Modified: \e[1;5A (ui.Ctrl-ui.Up) 376 return xtermModify(k, nums[1], seq) 377 } else { 378 return ui.Key{} 379 } 380 } 381 382 if last == '~' { 383 if len(nums) == 1 || len(nums) == 2 { 384 if r, ok := keyByNum0[nums[0]]; ok { 385 k := ui.Key{r, 0} 386 if len(nums) == 1 { 387 // Unmodified: \e[5~ (ui.PageUp) 388 return k 389 } 390 // Modified: \e[5;5~ (ui.Ctrl-ui.PageUp) 391 return xtermModify(k, nums[1], seq) 392 } 393 } else if len(nums) == 3 && nums[0] == 27 { 394 if r, ok := keyByNum2[nums[2]]; ok { 395 k := ui.Key{r, 0} 396 return xtermModify(k, nums[1], seq) 397 } 398 } 399 } 400 401 return ui.Key{} 402 } 403 404 func xtermModify(k ui.Key, mod int, seq string) ui.Key { 405 switch mod { 406 case 0: 407 // do nothing 408 case 2: 409 k.Mod |= ui.Shift 410 case 3: 411 k.Mod |= ui.Alt 412 case 4: 413 k.Mod |= ui.Shift | ui.Alt 414 case 5: 415 k.Mod |= ui.Ctrl 416 case 6: 417 k.Mod |= ui.Shift | ui.Ctrl 418 case 7: 419 k.Mod |= ui.Alt | ui.Ctrl 420 case 8: 421 k.Mod |= ui.Shift | ui.Alt | ui.Ctrl 422 default: 423 return ui.Key{} 424 } 425 return k 426 } 427 428 func mouseModify(n int) ui.Mod { 429 var mod ui.Mod 430 if n&4 != 0 { 431 mod |= ui.Shift 432 } 433 if n&8 != 0 { 434 mod |= ui.Alt 435 } 436 if n&16 != 0 { 437 mod |= ui.Ctrl 438 } 439 return mod 440 }