1 // Copyright 2022 Garrett D'Amore 2 // 3 // Distributed under the Boost Software License, Version 1.0. 4 // (See accompanying file LICENSE or https://www.boost.org/LICENSE_1_0.txt) 5 6 module dcell.parser; 7 8 import core.time; 9 import std.ascii; 10 import std.string; 11 import std.utf; 12 13 import dcell.key; 14 import dcell.mouse; 15 import dcell.event; 16 import dcell.termcap; 17 18 package: 19 20 struct KeyCode 21 { 22 Key key; 23 Modifiers mod; 24 } 25 26 struct ParseKeys 27 { 28 29 immutable bool[Key] exist; 30 immutable KeyCode[string] keys; 31 string pasteStart; 32 string pasteEnd; 33 34 this(const Termcap* caps) 35 { 36 37 bool[Key] ex; 38 KeyCode[string] kc; 39 40 void addKey(Key key, string val, Modifiers mod = Modifiers.none, Key replace = cast(Key) 0) 41 { 42 if (val == "") 43 return; 44 if ((val !in kc) || kc[val].key == replace) 45 { 46 ex[key] = true; 47 kc[val] = KeyCode(key, mod); 48 } 49 } 50 51 void addXTermKey(Key key, string val) 52 { 53 if (val.length > 2 && val[0] == '\x1b' && val[1] == '[' && val[$ - 1] == '~') 54 { 55 // These suffixes are calculated assuming Xterm style modifier suffixes. 56 // Please see https://invisible-island.net/xterm/ctlseqs/ctlseqs.pdf for 57 // more information (specifically "PC-Style Function Keys"). 58 val = val[0 .. $ - 1]; // drop trailing ~ 59 addKey(key, val ~ ";2~", Modifiers.shift, cast(Key)(key + 12)); 60 addKey(key, val ~ ";3~", Modifiers.alt, cast(Key)(key + 48)); 61 addKey(key, val ~ ";4~", Modifiers.alt | Modifiers.shift, cast(Key)(key + 60)); 62 addKey(key, val ~ ";5~", Modifiers.ctrl, cast(Key)(key + 24)); 63 addKey(key, val ~ ";6~", Modifiers.ctrl | Modifiers.shift, cast(Key)(key + 36)); 64 addKey(key, val ~ ";7~", Modifiers.alt | Modifiers.ctrl); 65 addKey(key, val ~ ";8~", Modifiers.shift | Modifiers.alt | Modifiers.ctrl); 66 addKey(key, val ~ ";9~", Modifiers.meta); 67 addKey(key, val ~ ";10~", Modifiers.meta | Modifiers.shift); 68 addKey(key, val ~ ";11~", Modifiers.meta | Modifiers.alt); 69 addKey(key, val ~ ";12~", Modifiers.meta | Modifiers.shift | Modifiers.alt); 70 addKey(key, val ~ ";13~", Modifiers.meta | Modifiers.ctrl); 71 addKey(key, val ~ ";14~", Modifiers.meta | Modifiers.ctrl | Modifiers.shift); 72 addKey(key, val ~ ";15~", Modifiers.meta | Modifiers.ctrl | Modifiers.alt); 73 addKey(key, val ~ ";16~", 74 Modifiers.meta | Modifiers.ctrl | Modifiers.shift | Modifiers.alt); 75 } 76 else if (val.length == 3 && val[0] == '\x1b' && val[1] == 'O') 77 { 78 val = val[2 .. $]; 79 addKey(key, "\x1b[1;2" ~ val, Modifiers.shift, cast(Key)(key + 12)); 80 addKey(key, "\x1b[1;3" ~ val, Modifiers.alt, cast(Key)(key + 48)); 81 addKey(key, "\x1b[1;5" ~ val, Modifiers.ctrl, cast(Key)(key + 24)); 82 addKey(key, "\x1b[1;6" ~ val, Modifiers.ctrl | Modifiers.shift, cast(Key)(key + 36)); 83 addKey(key, "\x1b[1;4" ~ val, Modifiers.alt | Modifiers.shift, cast(Key)(key + 60)); 84 addKey(key, "\x1b[1;7" ~ val, Modifiers.alt | Modifiers.ctrl); 85 addKey(key, "\x1b[1;8" ~ val, Modifiers.shift | Modifiers.alt | Modifiers.ctrl); 86 addKey(key, "\x1b[1;9" ~ val, Modifiers.meta,); 87 addKey(key, "\x1b[1;10" ~ val, Modifiers.meta | Modifiers.shift); 88 addKey(key, "\x1b[1;11" ~ val, Modifiers.meta | Modifiers.alt); 89 addKey(key, "\x1b[1;12" ~ val, Modifiers.meta | Modifiers.alt | Modifiers.shift); 90 addKey(key, "\x1b[1;13" ~ val, Modifiers.meta | Modifiers.ctrl); 91 addKey(key, "\x1b[1;14" ~ val, Modifiers.meta | Modifiers.ctrl | Modifiers.shift); 92 addKey(key, "\x1b[1;15" ~ val, Modifiers.meta | Modifiers.ctrl | Modifiers.alt); 93 addKey(key, "\x1b[1;16" ~ val, 94 Modifiers.meta | Modifiers.ctrl | Modifiers.alt | Modifiers.shift); 95 } 96 } 97 98 void addXTermKeys(const Termcap* caps) 99 { 100 if (caps.keyShfRight != "\x1b[1;2C") // does this look "xtermish"? 101 return; 102 addXTermKey(Key.right, caps.keyRight); 103 addXTermKey(Key.left, caps.keyLeft); 104 addXTermKey(Key.up, caps.keyUp); 105 addXTermKey(Key.down, caps.keyDown); 106 addXTermKey(Key.insert, caps.keyInsert); 107 addXTermKey(Key.del, caps.keyDelete); 108 addXTermKey(Key.pgUp, caps.keyPgUp); 109 addXTermKey(Key.pgDn, caps.keyPgDn); 110 addXTermKey(Key.home, caps.keyHome); 111 addXTermKey(Key.end, caps.keyEnd); 112 addXTermKey(Key.f1, caps.keyF1); 113 addXTermKey(Key.f2, caps.keyF2); 114 addXTermKey(Key.f3, caps.keyF3); 115 addXTermKey(Key.f4, caps.keyF4); 116 addXTermKey(Key.f5, caps.keyF5); 117 addXTermKey(Key.f6, caps.keyF6); 118 addXTermKey(Key.f7, caps.keyF7); 119 addXTermKey(Key.f8, caps.keyF8); 120 addXTermKey(Key.f9, caps.keyF9); 121 addXTermKey(Key.f10, caps.keyF10); 122 addXTermKey(Key.f11, caps.keyF11); 123 addXTermKey(Key.f12, caps.keyF12); 124 } 125 126 void addCtrlKeys() 127 { 128 // we look briefly at all the keyCodes we 129 // have, to find their starting character. 130 // the vast majority of these will be escape. 131 bool[char] initials; 132 foreach (esc, _; kc) 133 { 134 if (esc != "") 135 initials[esc[0]] = true; 136 } 137 // Add key mappings for control keys. 138 for (char i = 0; i < ' '; i++) 139 { 140 141 // If this is starting character (typically esc) of other sequences, 142 // then do not set up the fast path mapping for it. 143 // We need to let the read do the whole timeout thing. 144 if (i in initials) 145 continue; 146 147 Key k = cast(Key) i; 148 ex[k] = true; 149 switch (k) 150 { 151 case Key.backspace, Key.tab, Key.esc, Key.enter: 152 // these are directly typeable 153 kc["" ~ i] = KeyCode(k, Modifiers.none); 154 break; 155 default: 156 // these are generally represented as a control sequence 157 kc["" ~ i] = KeyCode(k, Modifiers.ctrl); 158 break; 159 } 160 } 161 162 } 163 164 addKey(Key.backspace, caps.keyBackspace); 165 addKey(Key.f1, caps.keyF1); 166 addKey(Key.f2, caps.keyF2); 167 addKey(Key.f3, caps.keyF3); 168 addKey(Key.f4, caps.keyF4); 169 addKey(Key.f5, caps.keyF5); 170 addKey(Key.f6, caps.keyF6); 171 addKey(Key.f7, caps.keyF7); 172 addKey(Key.f8, caps.keyF8); 173 addKey(Key.f9, caps.keyF9); 174 addKey(Key.f10, caps.keyF10); 175 addKey(Key.f11, caps.keyF11); 176 addKey(Key.f12, caps.keyF12); 177 addKey(Key.f13, caps.keyF13); 178 addKey(Key.f14, caps.keyF14); 179 addKey(Key.f15, caps.keyF15); 180 addKey(Key.f16, caps.keyF16); 181 addKey(Key.f17, caps.keyF17); 182 addKey(Key.f18, caps.keyF18); 183 addKey(Key.f19, caps.keyF19); 184 addKey(Key.f20, caps.keyF20); 185 addKey(Key.f21, caps.keyF21); 186 addKey(Key.f22, caps.keyF22); 187 addKey(Key.f23, caps.keyF23); 188 addKey(Key.f24, caps.keyF24); 189 addKey(Key.f25, caps.keyF25); 190 addKey(Key.f26, caps.keyF26); 191 addKey(Key.f27, caps.keyF27); 192 addKey(Key.f28, caps.keyF28); 193 addKey(Key.f29, caps.keyF29); 194 addKey(Key.f30, caps.keyF30); 195 addKey(Key.f31, caps.keyF31); 196 addKey(Key.f32, caps.keyF32); 197 addKey(Key.f33, caps.keyF33); 198 addKey(Key.f34, caps.keyF34); 199 addKey(Key.f35, caps.keyF35); 200 addKey(Key.f36, caps.keyF36); 201 addKey(Key.f37, caps.keyF37); 202 addKey(Key.f38, caps.keyF38); 203 addKey(Key.f39, caps.keyF39); 204 addKey(Key.f40, caps.keyF40); 205 addKey(Key.f41, caps.keyF41); 206 addKey(Key.f42, caps.keyF42); 207 addKey(Key.f43, caps.keyF43); 208 addKey(Key.f44, caps.keyF44); 209 addKey(Key.f45, caps.keyF45); 210 addKey(Key.f46, caps.keyF46); 211 addKey(Key.f47, caps.keyF47); 212 addKey(Key.f48, caps.keyF48); 213 addKey(Key.f49, caps.keyF49); 214 addKey(Key.f50, caps.keyF50); 215 addKey(Key.f51, caps.keyF51); 216 addKey(Key.f52, caps.keyF52); 217 addKey(Key.f53, caps.keyF53); 218 addKey(Key.f54, caps.keyF54); 219 addKey(Key.f55, caps.keyF55); 220 addKey(Key.f56, caps.keyF56); 221 addKey(Key.f57, caps.keyF57); 222 addKey(Key.f58, caps.keyF58); 223 addKey(Key.f59, caps.keyF59); 224 addKey(Key.f60, caps.keyF60); 225 addKey(Key.f61, caps.keyF61); 226 addKey(Key.f62, caps.keyF62); 227 addKey(Key.f63, caps.keyF63); 228 addKey(Key.f64, caps.keyF64); 229 addKey(Key.insert, caps.keyInsert); 230 addKey(Key.del, caps.keyDelete); 231 addKey(Key.home, caps.keyHome); 232 addKey(Key.end, caps.keyEnd); 233 addKey(Key.up, caps.keyUp); 234 addKey(Key.down, caps.keyDown); 235 addKey(Key.left, caps.keyLeft); 236 addKey(Key.right, caps.keyRight); 237 addKey(Key.pgUp, caps.keyPgUp); 238 addKey(Key.pgDn, caps.keyPgDn); 239 addKey(Key.help, caps.keyHelp); 240 addKey(Key.print, caps.keyPrint); 241 addKey(Key.cancel, caps.keyCancel); 242 addKey(Key.exit, caps.keyExit); 243 addKey(Key.backtab, caps.keyBacktab); 244 245 addKey(Key.right, caps.keyShfRight, Modifiers.shift); 246 addKey(Key.left, caps.keyShfLeft, Modifiers.shift); 247 addKey(Key.up, caps.keyShfUp, Modifiers.shift); 248 addKey(Key.down, caps.keyShfDown, Modifiers.shift); 249 addKey(Key.home, caps.keyShfHome, Modifiers.shift); 250 addKey(Key.end, caps.keyShfEnd, Modifiers.shift); 251 addKey(Key.pgUp, caps.keyShfPgUp, Modifiers.shift); 252 addKey(Key.pgDn, caps.keyShfPgDn, Modifiers.shift); 253 254 addKey(Key.right, caps.keyCtrlRight, Modifiers.ctrl); 255 addKey(Key.left, caps.keyCtrlLeft, Modifiers.ctrl); 256 addKey(Key.up, caps.keyCtrlUp, Modifiers.ctrl); 257 addKey(Key.down, caps.keyCtrlDown, Modifiers.ctrl); 258 addKey(Key.home, caps.keyCtrlHome, Modifiers.ctrl); 259 addKey(Key.end, caps.keyCtrlEnd, Modifiers.ctrl); 260 261 // Sadly, xterm handling of keycodes is somewhat erratic. In 262 // particular, different codes are sent depending on application 263 // mode is in use or not, and the entries for many of these are 264 // simply absent from terminfo on many systems. So we insert 265 // a number of escape sequences if they are not already used, in 266 // order to have the widest correct usage. Note that prepareKey 267 // will not inject codes if the escape sequence is already known. 268 // We also only do this for terminals that have the application 269 // mode present. 270 271 if (caps.enterKeypad != "") 272 { 273 addKey(Key.up, "\x1b[A"); 274 addKey(Key.down, "\x1b[B"); 275 addKey(Key.right, "\x1b[C"); 276 addKey(Key.left, "\x1b[D"); 277 addKey(Key.end, "\x1b[F"); 278 addKey(Key.home, "\x1b[H"); 279 addKey(Key.del2, "\x1b[3~"); 280 addKey(Key.home, "\x1b[1~"); 281 addKey(Key.end, "\x1b[4~"); 282 addKey(Key.pgUp, "\x1b[5~"); 283 addKey(Key.pgDn, "\x1b[6~"); 284 285 // Application mode 286 addKey(Key.up, "\x1bOA"); 287 addKey(Key.down, "\x1bOB"); 288 addKey(Key.right, "\x1bOC"); 289 addKey(Key.left, "\x1bOD"); 290 addKey(Key.home, "\x1bOH"); 291 } 292 293 addXTermKeys(caps); 294 295 pasteStart = caps.pasteStart; 296 pasteEnd = caps.pasteEnd; 297 298 addCtrlKeys(); // do this one last 299 300 // if DELETE didn't make it at 0x7F, add it - seems to be missing 301 // from some of the sequences. 302 addKey(Key.del2, "\x7F"); 303 304 exist = cast(immutable(bool[Key])) ex; 305 keys = cast(immutable(KeyCode[string])) kc; 306 307 } 308 309 bool hasKey(Key k) const pure 310 { 311 if (k == Key.rune) 312 { 313 return true; 314 } 315 return (k in exist ? true : false); 316 } 317 } 318 319 class Parser 320 { 321 322 this(const ParseKeys pk) 323 { 324 keyExist = pk.exist; 325 keyCodes = pk.keys; 326 pasteStart = pk.pasteStart; 327 pasteEnd = pk.pasteEnd; 328 } 329 330 Event[] events() pure 331 { 332 auto res = evs; 333 evs = null; 334 return cast(Event[]) res; 335 } 336 337 bool parse(string b) 338 { 339 auto now = MonoTime.currTime(); 340 if (b.length != 0) 341 { 342 // if we are adding to it, restart the timer 343 keyStart = now; 344 } 345 buf ~= b; 346 347 while (buf.length != 0) 348 { 349 partial = false; 350 351 // we have to parse the paste bit first 352 if (parsePaste() || parseRune() || parseFnKey() || parseSgrMouse() || parseLegacyMouse()) 353 { 354 keyStart = now; 355 continue; 356 } 357 auto expire = ((now - keyStart) > seqTime); 358 359 if (!partial || expire) 360 { 361 if (buf[0] == '\x1b') 362 { 363 if (buf.length == 1) 364 { 365 evs ~= newKeyEvent(Key.esc); 366 escaped = false; 367 } 368 else 369 { 370 escaped = true; 371 } 372 buf = buf[1 .. $]; 373 keyStart = now; 374 continue; 375 } 376 // no matches or timeout waiting for data, yank off the first byte 377 evs ~= newKeyEvent(Key.rune, buf[0], escaped ? Modifiers.alt : Modifiers.none); 378 escaped = false; 379 keyStart = now; 380 continue; 381 } 382 // we must have partial data, so wait and come back in a bit 383 return false; 384 } 385 386 return true; 387 } 388 389 bool empty() const pure 390 { 391 return buf.length == 0; 392 } 393 394 private: 395 396 bool escaped; 397 ubyte[] buf; 398 Event[] evs; 399 const KeyCode[string] keyCodes; 400 const bool[Key] keyExist; 401 bool partial; // record partially parsed sequences 402 MonoTime keyStart; // when the timer started 403 Duration seqTime = msecs(50); // time to fully decode a partial sequence 404 bool buttonDown; // true if buttons were down 405 bool wasButton; // true if we saw a button press for recent mouse event 406 bool pasting; 407 MonoTime pasteTime; 408 dstring pasteBuf; 409 string pasteStart; 410 string pasteEnd; 411 412 Event newKeyEvent(Key k, dchar dch = 0, Modifiers mod = Modifiers.none) 413 { 414 Event ev = { 415 type: EventType.key, when: MonoTime.currTime(), key: { 416 key: k, ch: dch, mod: mod 417 } 418 }; 419 return ev; 420 } 421 422 // NB: it is possible for x and y to be outside the current coordinates 423 // (happens for click drag for example). Consumer of the event should clip 424 // the coordinates as needed. 425 Event newMouseEvent(int x, int y, int btn) 426 { 427 Event ev = { 428 type: EventType.mouse, when: MonoTime.currTime, mouse: { 429 pos: Coord(x, y) 430 } 431 }; 432 433 // Mouse wheel has bit 6 set, no release events. It should be noted 434 // that wheel events are sometimes misdelivered as mouse button events 435 // during a click-drag, so we debounce these, considering them to be 436 // button press events unless we see an intervening release event. 437 438 switch (btn & 0x43) 439 { 440 case 0: 441 ev.mouse.btn = Buttons.button1; 442 wasButton = true; 443 break; 444 case 1: 445 ev.mouse.btn = Buttons.button3; 446 wasButton = true; 447 break; 448 case 2: 449 ev.mouse.btn = Buttons.button2; 450 wasButton = true; 451 break; 452 case 3: 453 ev.mouse.btn = Buttons.none; 454 wasButton = false; 455 break; 456 case 0x40: 457 ev.mouse.btn = wasButton ? Buttons.button1 : Buttons.wheelUp; 458 break; 459 case 0x41: 460 ev.mouse.btn = wasButton ? Buttons.button2 : Buttons.wheelDown; 461 break; 462 default: 463 break; 464 } 465 if (btn & 0x4) 466 ev.mouse.mod |= Modifiers.shift; 467 if (btn & 0x8) 468 ev.mouse.mod |= Modifiers.alt; 469 if (btn & 0x10) 470 ev.mouse.mod |= Modifiers.ctrl; 471 return ev; 472 } 473 474 Event newPasteEvent(dstring buffer) 475 { 476 Event ev = { 477 type: EventType.paste, when: MonoTime.currTime(), paste: { 478 content: buffer 479 } 480 }; 481 return ev; 482 483 } 484 485 bool parseSequence(string seq) 486 { 487 if (startsWith(buf, seq)) 488 { 489 buf = buf[seq.length .. $]; // yank the sequence 490 return true; 491 } 492 if (startsWith(seq, buf)) 493 { 494 partial = true; 495 } 496 return false; 497 } 498 499 bool parsePaste() 500 { 501 bool matches; 502 503 if (pasteStart == "") 504 { 505 return false; 506 } 507 508 auto now = MonoTime.currTime(); 509 if (!pasting) 510 { 511 if (parseSequence(pasteStart)) 512 { 513 pasting = true; 514 pasteBuf = ""; 515 pasteTime = now; 516 return true; 517 } 518 return false; 519 } 520 assert(pasting); 521 522 // we end the sequence if we see it, but also if we timed out looking for it 523 if (parseSequence(pasteEnd) || 524 ((now - pasteTime) > seqTime * 4)) 525 { 526 pasting = false; 527 auto ev = newPasteEvent(pasteBuf); 528 pasteBuf = ""; 529 evs ~= ev; 530 return true; 531 } 532 if (parseRune()) 533 { 534 pasteTime = now; 535 } 536 // we pretend to have eaten the entire thing, even if there is stuff left 537 return true; 538 } 539 540 bool parseRune() 541 { 542 if (buf.length == 0) 543 { 544 return false; 545 } 546 dchar dc; 547 Modifiers mod = Modifiers.none; 548 if ((buf[0] >= ' ' && buf[0] < 0x7F) || 549 (pasting && isWhite(buf[0]))) 550 { 551 dc = buf[0]; 552 buf = buf[1 .. $]; 553 // printable ascii, easy to deal with 554 if (escaped) 555 { 556 escaped = false; 557 mod = Modifiers.alt; 558 } 559 if (pasting) 560 pasteBuf ~= dc; 561 else 562 evs ~= newKeyEvent(Key.rune, dc, mod); 563 return true; 564 } 565 if ((buf[0] < 0x80) || (buf[0] == 0x7F)) // control character, not a rune 566 return false; 567 // unicode bits... 568 size_t index = 0; 569 auto temp = cast(string) buf; 570 try 571 { 572 dc = temp.decode(index); 573 } 574 catch (UTFException e) 575 { 576 return false; 577 } 578 if (pasting) 579 pasteBuf ~= dc; 580 else 581 evs ~= newKeyEvent(Key.rune, dc, mod); 582 buf = buf[index .. $]; 583 return true; 584 } 585 586 bool parseFnKey() 587 { 588 Modifiers mod = Modifiers.none; 589 if (buf.length == 0) 590 { 591 return false; 592 } 593 foreach (seq, kc; keyCodes) 594 { 595 // ideally we'd use a trie to isolate 596 // duplicates for this. for now, we just handle esc. 597 if (seq.length == 1 && seq[0] == '\x1b') 598 { 599 continue; 600 } 601 if (parseSequence(seq)) 602 { 603 mod = kc.mod; 604 if (escaped) 605 { 606 escaped = false; 607 mod = Modifiers.alt; 608 } 609 evs ~= newKeyEvent(kc.key, seq.length == 1 ? seq[0] : 0, mod); 610 return true; 611 } 612 } 613 return false; 614 } 615 616 // look for an SGR mouse record, using a state machine 617 bool parseSgrMouse() 618 { 619 int x, y, btn, mov, state, val; 620 bool dig, neg; 621 622 for (int i = 0; i < buf.length; i++) 623 { 624 switch (buf[i]) 625 { 626 case '\x1b': 627 if (state != 0) 628 return false; 629 state = 1; 630 break; 631 case '\x9b': // 8-bit mode CSI 632 if (state != 0) 633 return false; 634 state = 2; 635 break; 636 case '[': 637 if (state != 1) 638 return false; 639 state = 2; 640 break; 641 case '<': 642 if (state != 2) 643 return false; 644 val = 0; 645 dig = false; 646 neg = false; 647 state = 3; 648 break; 649 case '-': 650 if (state < 3 || state > 5 || dig || neg) 651 return false; 652 neg = true; // no state change 653 break; 654 case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': 655 if (state < 3 || state > 5) 656 return false; 657 val *= 10; 658 val += int(buf[i] - '0'); 659 dig = true; // no state change 660 break; 661 case ';': 662 if (state != 3 && state != 4) 663 return false; 664 val = neg ? -val : val; 665 if (state == 3) 666 btn = neg ? -val : val; 667 else 668 x = (neg ? -val : val) - 1; 669 state++; 670 neg = false; 671 dig = false; 672 val = 0; 673 break; 674 case 'm', 'M': 675 if (state != 5) 676 return false; 677 y = (neg ? -val : val) - 1; 678 mov = (btn & 0x20) != 0; 679 btn ^= 0x20; 680 if (buf[i] == 'm') 681 { // release 682 btn |= 0x3; 683 btn ^= 0x40; 684 buttonDown = false; 685 } 686 else if (mov) 687 { 688 // Some broken terminals appear to send 689 // mouse button one motion events, instead of 690 // encoding 35 (no buttons) into these events. 691 // We resolve these by looking for a non-motion 692 // event first. 693 if (!buttonDown) 694 { 695 btn |= 0x3; 696 btn ^= 0x40; 697 } 698 } 699 else 700 { 701 buttonDown = true; 702 } 703 buf = buf[i + 1 .. $]; 704 import std.stdio; 705 706 evs ~= newMouseEvent(x, y, btn); 707 return true; 708 default: 709 // definitely not ours 710 return false; 711 } 712 } 713 // might be ours, inconclusive 714 if (state > 0) 715 partial = true; 716 return false; 717 } 718 719 bool parseLegacyMouse() 720 { 721 int x, y, btn; 722 723 enum State 724 { 725 start, 726 bracket, 727 end 728 } 729 730 State state; 731 732 for (int i = 0; i < buf.length; i++) 733 { 734 final switch (state) 735 { 736 case State.start: 737 switch (buf[i]) 738 { 739 case '\x1b': 740 state = State.bracket; 741 break; 742 case '\x9b': 743 state = State.end; 744 break; 745 default: 746 return false; 747 } 748 break; 749 case State.bracket: 750 if (buf[i] != '[') 751 return false; 752 state++; 753 break; 754 case State.end: 755 if (buf[i] != 'M') 756 return false; 757 if (buf.length < i + 4) 758 break; 759 buf = buf[i + 1 .. $]; 760 btn = int(buf[0]); 761 x = int(buf[1]) - 32 - 1; 762 y = int(buf[2]) - 32 - 1; 763 buf = buf[3 .. $]; 764 evs ~= newMouseEvent(x, y, btn); 765 return true; 766 } 767 } 768 if (state > 0) 769 partial = true; 770 return false; 771 } 772 } 773 774 unittest 775 { 776 import core.thread; 777 import dcell.database; 778 779 // taken from xterm, but pared down 780 static immutable Termcap term = { 781 name: "test-term", 782 enterKeypad: "\x1b[?1h\x1b=", 783 exitKeypad: "\x1b[?1l\x1b>", 784 cursorBack1: "\x08", 785 cursorUp1: "\x1b[A", 786 keyBackspace: "\x08", 787 keyF1: "\x1bOP", 788 keyF2: "\x1bOQ", 789 keyF3: "\x1bOR", 790 keyInsert: "\x1b[2~", 791 keyDelete: "\x1b[3~", 792 keyHome: "\x1bOH", 793 keyEnd: "\x1bOF", 794 keyPgUp: "\x1b[5~", 795 keyPgDn: "\x1b[6~", 796 keyUp: "\x1bOA", 797 keyDown: "\x1bOB", 798 keyLeft: "\x1bOD", 799 keyRight: "\x1bOC", 800 keyBacktab: "\x1b[Z", 801 keyShfRight: "\x1b[1;2C", 802 mouse: "\x1b[M", 803 }; 804 Database.put(&term); 805 auto tc = Database.get("test-term"); 806 assert(tc !is null); 807 Parser p = new Parser(ParseKeys(tc)); 808 assert(p.empty()); 809 assert(p.parse("")); // no data, is fine 810 assert(p.parse("\x1bOC")); 811 auto ev = p.events(); 812 813 assert(ev.length == 1); 814 assert(ev[0].type == EventType.key); 815 assert(ev[0].key.key == Key.right); 816 817 // this tests that the timed pase parsing works - 818 // escape sequences are kept partially until we 819 // have a match or we have waited long enough. 820 assert(p.parse(['\x1b', 'O']) == false); 821 ev = p.events(); 822 assert(ev.length == 0); 823 Thread.sleep(msecs(100)); 824 assert(p.parse([]) == true); 825 ev = p.events(); 826 assert(ev.length == 1); 827 assert(ev[0].type == EventType.key); 828 assert(ev[0].key.key == Key.rune); 829 assert(ev[0].key.mod == Modifiers.alt); 830 831 // lone escape 832 assert(p.parse(['\x1b']) == false); 833 ev = p.events(); 834 assert(ev.length == 0); 835 Thread.sleep(msecs(100)); 836 assert(p.parse([]) == true); 837 ev = p.events(); 838 assert(ev.length == 1); 839 assert(ev[0].type == EventType.key); 840 assert(ev[0].key.key == Key.esc); 841 assert(ev[0].key.mod == Modifiers.none); 842 843 // try injecting paste events 844 assert(tc.enablePaste != ""); 845 assert(p.parse(['\x1b', '[', '2', '0', '0', '~'])); 846 assert(p.parse(['A'])); 847 assert(p.parse(['\x1b', '[', '2', '0', '1', '~'])); 848 849 ev = p.events(); 850 assert(ev.length == 1); 851 assert(ev[0].type == EventType.paste); 852 assert(ev[0].paste.content == "A"); 853 854 // mouse events 855 assert(p.parse(['\x1b', '[', '<', '3', ';', '2', ';', '3', 'M'])); 856 ev = p.events(); 857 assert(ev.length == 1); 858 assert(ev[0].type == EventType.mouse); 859 assert(ev[0].mouse.pos.x == 1); 860 assert(ev[0].mouse.pos.y == 2); 861 862 // unicode 863 string b = [0xe2, 0x82, 0xac]; 864 assert(p.parse(b)); 865 ev = p.events(); 866 assert(ev.length == 1); 867 assert(ev[0].type == EventType.key); 868 assert(ev[0].key.key == Key.rune); 869 assert(ev[0].key.ch == '€'); 870 }