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