Mercurial > hg > rlgwebd
comparison termemu.js @ 211:d60063a674e1
Terminal emulation: implement the CSI b sequence.
CSI b repeats the previous character, if it was printable and not an
escape sequence.
| author | John "Elwin" Edwards |
|---|---|
| date | Thu, 05 Sep 2019 15:19:27 -0400 |
| parents | 1d3cfe10974a |
| children |
comparison
equal
deleted
inserted
replaced
| 210:b04313038a0b | 211:d60063a674e1 |
|---|---|
| 69 bgColor: "black", // Default background color | 69 bgColor: "black", // Default background color |
| 70 scrT: 0, // top and bottom of scrolling region | 70 scrT: 0, // top and bottom of scrolling region |
| 71 scrB: 0, // init() will set this properly | 71 scrB: 0, // init() will set this properly |
| 72 c: null, // Contains cursor position and text attributes | 72 c: null, // Contains cursor position and text attributes |
| 73 offedge: false, // Going off the edge doesn't mean adding a new line | 73 offedge: false, // Going off the edge doesn't mean adding a new line |
| 74 lastcode: 0, // Last printed character | |
| 74 clearAttrs: function () { | 75 clearAttrs: function () { |
| 75 /* Make sure to reset ALL attribute properties and NOTHING else. */ | 76 /* Make sure to reset ALL attribute properties and NOTHING else. */ |
| 76 this.c.bold = false; | 77 this.c.bold = false; |
| 77 this.c.inverse = false; | 78 this.c.inverse = false; |
| 78 this.c.uline = false; | 79 this.c.uline = false; |
| 458 } | 459 } |
| 459 for (var i = 0; i < this.h; i++) { | 460 for (var i = 0; i < this.h; i++) { |
| 460 this.screen.replaceChild(this.makeRow(), this.screen.childNodes[i]); | 461 this.screen.replaceChild(this.makeRow(), this.screen.childNodes[i]); |
| 461 } | 462 } |
| 462 this.flipCursor(); // make it appear in the new row | 463 this.flipCursor(); // make it appear in the new row |
| 464 this.lastcode = 0; | |
| 463 return; | 465 return; |
| 464 }, | 466 }, |
| 465 write: function (codes) { | 467 write: function (codes) { |
| 466 //dchunk(codes); | 468 //dchunk(codes); |
| 467 for (var i = 0; i < codes.length; i++) { | 469 for (var i = 0; i < codes.length; i++) { |
| 528 } | 530 } |
| 529 else { | 531 else { |
| 530 /* Nothing else is implemented yet. */ | 532 /* Nothing else is implemented yet. */ |
| 531 debug(1, "Unrecognized sequence ESC " + codes[i].toString(16)); | 533 debug(1, "Unrecognized sequence ESC " + codes[i].toString(16)); |
| 532 this.comseq = []; | 534 this.comseq = []; |
| 535 } | |
| 536 if (this.comseq.length == 0) { | |
| 537 // A complete sequence was processed, clear lastcode. | |
| 538 this.lastcode = 0; | |
| 533 } | 539 } |
| 534 } | 540 } |
| 535 else if (this.comseq.length == 2 && this.comseq[0] == 27) { | 541 else if (this.comseq.length == 2 && this.comseq[0] == 27) { |
| 536 /* An ESC C N sequence. Not implemented. Doesn't check validity | 542 /* An ESC C N sequence. Not implemented. Doesn't check validity |
| 537 * of N. */ | 543 * of N. */ |
| 553 else | 559 else |
| 554 debug(1, "Unknown sequence ESC " + | 560 debug(1, "Unknown sequence ESC " + |
| 555 String.fromCharCode(this.comseq[1]) + " 0x" + | 561 String.fromCharCode(this.comseq[1]) + " 0x" + |
| 556 codes[i].toString(16)); | 562 codes[i].toString(16)); |
| 557 this.comseq = []; | 563 this.comseq = []; |
| 564 this.lastcode = 0; | |
| 558 } | 565 } |
| 559 else if (this.comseq[0] == 157) { | 566 else if (this.comseq[0] == 157) { |
| 560 /* Commands beginning with OSC */ | 567 /* Commands beginning with OSC */ |
| 561 /* Check for string terminator */ | 568 /* Check for string terminator */ |
| 562 if (codes[i] == 7 || codes[i] == 156 || (codes[i] == 92 && | 569 if (codes[i] == 7 || codes[i] == 156 || (codes[i] == 92 && |
| 564 if (codes[i] == 92 && this.comseq[this.comseq.length - 1] == 27) | 571 if (codes[i] == 92 && this.comseq[this.comseq.length - 1] == 27) |
| 565 this.comseq.pop(); | 572 this.comseq.pop(); |
| 566 debug(0, "Got " + (this.comseq.length - 1) + "-byte OSC sequence"); | 573 debug(0, "Got " + (this.comseq.length - 1) + "-byte OSC sequence"); |
| 567 this.oscProcess(); | 574 this.oscProcess(); |
| 568 this.comseq = []; | 575 this.comseq = []; |
| 576 this.lastcode = 0; | |
| 569 } | 577 } |
| 570 else | 578 else |
| 571 this.comseq.push(codes[i]); | 579 this.comseq.push(codes[i]); |
| 572 } | 580 } |
| 573 else if (this.comseq[0] == 155) { | 581 else if (this.comseq[0] == 155) { |
| 580 if (csiPre.indexOf(codes[i]) >= 0) { | 588 if (csiPre.indexOf(codes[i]) >= 0) { |
| 581 if (this.comseq.length > 1) { | 589 if (this.comseq.length > 1) { |
| 582 /* Chars in csiPre can only occur right after the CSI */ | 590 /* Chars in csiPre can only occur right after the CSI */ |
| 583 debug(1, "Invalid CSI sequence: misplaced prefix"); | 591 debug(1, "Invalid CSI sequence: misplaced prefix"); |
| 584 this.comseq = []; | 592 this.comseq = []; |
| 593 this.lastcode = 0; | |
| 585 } | 594 } |
| 586 else | 595 else |
| 587 this.comseq.push(codes[i]); | 596 this.comseq.push(codes[i]); |
| 588 } | 597 } |
| 589 else if (csiPost.indexOf(this.comseq[this.comseq.length - 1]) >= 0 && | 598 else if (csiPost.indexOf(this.comseq[this.comseq.length - 1]) >= 0 && |
| 590 !csiFinal(codes[i])) { | 599 !csiFinal(codes[i])) { |
| 591 /* Chars is csiPost must come right before the final char */ | 600 /* Chars in csiPost must come right before the final char */ |
| 592 debug(1, "Invalid CSI sequence: misplaced postfix"); | 601 debug(1, "Invalid CSI sequence: misplaced postfix"); |
| 593 this.comseq = []; | 602 this.comseq = []; |
| 603 this.lastcode = 0; | |
| 594 } | 604 } |
| 595 else if ((codes[i] >= 48 && codes[i] <= 57) || codes[i] == 59 || | 605 else if ((codes[i] >= 48 && codes[i] <= 57) || codes[i] == 59 || |
| 596 csiPost.indexOf(codes[i]) >= 0) { | 606 csiPost.indexOf(codes[i]) >= 0) { |
| 597 /* Numbers and ; can go anywhere */ | 607 /* Numbers and ; can go anywhere */ |
| 598 this.comseq.push(codes[i]); | 608 this.comseq.push(codes[i]); |
| 603 this.comseq = []; | 613 this.comseq = []; |
| 604 } | 614 } |
| 605 else { | 615 else { |
| 606 debug(1, "Invalid CSI sequence: unknown code " + codes[i].toString(16)); | 616 debug(1, "Invalid CSI sequence: unknown code " + codes[i].toString(16)); |
| 607 this.comseq = []; | 617 this.comseq = []; |
| 618 this.lastcode = 0; | |
| 608 } | 619 } |
| 609 } | 620 } |
| 610 else { | 621 else { |
| 611 debug(1, "Unknown sequence with " + this.comseq[0].toString(16)); | 622 debug(1, "Unknown sequence with " + this.comseq[0].toString(16)); |
| 612 this.comseq = []; | 623 this.comseq = []; |
| 613 } | 624 this.lastcode = 0; |
| 614 continue; | 625 } |
| 615 } | 626 } |
| 616 /* Treat it as a single character. */ | 627 else if ((codes[i] >= 32 && codes[i] < 127) || codes[i] >= 160) { |
| 617 if (codes[i] == 5) { | |
| 618 sendback("06"); | |
| 619 } | |
| 620 else if (codes[i] == 7) { | |
| 621 /* bell */ | |
| 622 bell(true); | |
| 623 } | |
| 624 else if (codes[i] == 8) { | |
| 625 /* backspace */ | |
| 626 if (this.offedge) | |
| 627 this.offedge = false; | |
| 628 else if (this.c.x > 0) | |
| 629 this.cmove(null, this.c.x - 1); | |
| 630 } | |
| 631 else if (codes[i] == 9) { | |
| 632 /* tab */ | |
| 633 var xnew; | |
| 634 if (this.c.x < this.w - 1) { | |
| 635 xnew = 8 * (Math.floor(this.c.x / 8) + 1); | |
| 636 if (xnew >= this.w) | |
| 637 xnew = this.w - 1; | |
| 638 this.cmove(null, xnew); | |
| 639 } | |
| 640 else { | |
| 641 this.offedge = true; | |
| 642 } | |
| 643 } | |
| 644 else if (codes[i] >= 10 && codes[i] <= 12) { | |
| 645 /* newline, vertical tab, form feed */ | |
| 646 if (this.offedge) | |
| 647 this.newline(true); | |
| 648 else | |
| 649 this.newline(false); | |
| 650 } | |
| 651 else if (codes[i] == 13) { | |
| 652 /* carriage return \r */ | |
| 653 this.cmove(null, 0); | |
| 654 } | |
| 655 else if (codes[i] == 14) { | |
| 656 /* shift out */ | |
| 657 // Currently assuming that G1 is DEC Special & Line Drawing | |
| 658 this.c.cset = "0"; | |
| 659 debug(0, "Using DEC graphics charset."); | |
| 660 } | |
| 661 else if (codes[i] == 15) { | |
| 662 /* shift in */ | |
| 663 // Currently assuming that G0 is ASCII | |
| 664 this.c.cset = "B"; | |
| 665 debug(0, "Using ASCII charset."); | |
| 666 } | |
| 667 else if (codes[i] == 27) { | |
| 668 /* escape */ | |
| 669 this.comseq.push(codes[i]); | |
| 670 } | |
| 671 else if (codes[i] < 32 || (codes[i] >= 127 && codes[i] < 160)) { | |
| 672 /* Some kind of control character. */ | |
| 673 debug(1, "Unprintable character 0x" + codes[i].toString(16)); | |
| 674 } | |
| 675 else { | |
| 676 /* If it's ASCII, it's printable; take a risk on anything higher */ | 628 /* If it's ASCII, it's printable; take a risk on anything higher */ |
| 677 if ((this.c.cset == "0") && (codes[i] in decChars)) { | 629 if ((this.c.cset == "0") && (codes[i] in decChars)) { |
| 678 // DEC special character set | 630 // DEC special character set |
| 679 this.placechar(String.fromCharCode(decChars[codes[i]])); | 631 this.lastcode = decChars[codes[i]]; |
| 680 } | 632 } |
| 681 else { | 633 else { |
| 682 this.placechar(String.fromCharCode(codes[i])); | 634 this.lastcode = codes[i]; |
| 683 } | 635 } |
| 636 this.placechar(String.fromCharCode(this.lastcode)); | |
| 637 } | |
| 638 else { | |
| 639 /* Treat it as a single control character. */ | |
| 640 this.singleCtl(codes[i]); | |
| 684 } | 641 } |
| 685 } | 642 } |
| 686 return; | 643 return; |
| 644 }, | |
| 645 singleCtl: function (ctlcode) { | |
| 646 if (ctlcode == 5) { | |
| 647 sendback("06"); | |
| 648 } | |
| 649 else if (ctlcode == 7) { | |
| 650 /* bell */ | |
| 651 bell(true); | |
| 652 } | |
| 653 else if (ctlcode == 8) { | |
| 654 /* backspace */ | |
| 655 if (this.offedge) | |
| 656 this.offedge = false; | |
| 657 else if (this.c.x > 0) | |
| 658 this.cmove(null, this.c.x - 1); | |
| 659 } | |
| 660 else if (ctlcode == 9) { | |
| 661 /* tab */ | |
| 662 var xnew; | |
| 663 if (this.c.x < this.w - 1) { | |
| 664 xnew = 8 * (Math.floor(this.c.x / 8) + 1); | |
| 665 if (xnew >= this.w) | |
| 666 xnew = this.w - 1; | |
| 667 this.cmove(null, xnew); | |
| 668 } | |
| 669 else { | |
| 670 this.offedge = true; | |
| 671 } | |
| 672 } | |
| 673 else if (ctlcode >= 10 && ctlcode <= 12) { | |
| 674 /* newline, vertical tab, form feed */ | |
| 675 if (this.offedge) | |
| 676 this.newline(true); | |
| 677 else | |
| 678 this.newline(false); | |
| 679 } | |
| 680 else if (ctlcode == 13) { | |
| 681 /* carriage return \r */ | |
| 682 this.cmove(null, 0); | |
| 683 } | |
| 684 else if (ctlcode == 14) { | |
| 685 /* shift out */ | |
| 686 // Currently assuming that G1 is DEC Special & Line Drawing | |
| 687 this.c.cset = "0"; | |
| 688 debug(0, "Using DEC graphics charset."); | |
| 689 } | |
| 690 else if (ctlcode == 15) { | |
| 691 /* shift in */ | |
| 692 // Currently assuming that G0 is ASCII | |
| 693 this.c.cset = "B"; | |
| 694 debug(0, "Using ASCII charset."); | |
| 695 } | |
| 696 else if (ctlcode == 27) { | |
| 697 /* escape */ | |
| 698 this.comseq.push(27); | |
| 699 } | |
| 700 else { | |
| 701 debug(1, "Unprintable character 0x" + ctlcode.toString(16)); | |
| 702 } | |
| 703 if (ctlcode != 27) { | |
| 704 // Sequences should preserve lastcode until they are completed | |
| 705 this.lastcode = 0; | |
| 706 } | |
| 687 }, | 707 }, |
| 688 csiProcess: function () { | 708 csiProcess: function () { |
| 689 /* Processes the CSI sequence in this.comseq */ | 709 /* Processes the CSI sequence in this.comseq */ |
| 690 var c = this.comseq[this.comseq.length - 1]; | 710 var c = this.comseq[this.comseq.length - 1]; |
| 691 if (this.comseq[0] != 155 || !csiFinal(c)) | 711 if (this.comseq[0] != 155 || !csiFinal(c)) |
| 692 return; | 712 return; |
| 713 var printed = false; | |
| 693 var comstr = ""; | 714 var comstr = ""; |
| 694 for (var i = 1; i < this.comseq.length; i++) | 715 for (var i = 1; i < this.comseq.length; i++) |
| 695 comstr += String.fromCharCode(this.comseq[i]); | 716 comstr += String.fromCharCode(this.comseq[i]); |
| 696 debug(0, "CSI sequence: " + comstr); | 717 debug(0, "CSI sequence: " + comstr); |
| 697 var reCSI = /^([>?!])?([0-9;]*)([ "$'])?([A-Za-z@`{|])$/; | 718 var reCSI = /^([>?!])?([0-9;]*)([ "$'])?([A-Za-z@`{|])$/; |
| 698 var matchCSI = comstr.match(reCSI); | 719 var matchCSI = comstr.match(reCSI); |
| 699 if (!matchCSI) { | 720 if (!matchCSI) { |
| 700 debug(1, "Unrecognized CSI sequence: " + comstr); | 721 debug(1, "Unrecognized CSI sequence: " + comstr); |
| 722 this.lastcode = 0; | |
| 701 return; | 723 return; |
| 702 } | 724 } |
| 703 var prefix = null; | 725 var prefix = null; |
| 704 if (matchCSI[1]) | 726 if (matchCSI[1]) |
| 705 prefix = matchCSI[1]; | 727 prefix = matchCSI[1]; |
| 723 /* The final character determines the action. */ | 745 /* The final character determines the action. */ |
| 724 if (c == 64) { | 746 if (c == 64) { |
| 725 /* @ - insert spaces at cursor */ | 747 /* @ - insert spaces at cursor */ |
| 726 if (prefix || postfix) { | 748 if (prefix || postfix) { |
| 727 debug(1, "Invalid CSI @ sequence: " + comstr); | 749 debug(1, "Invalid CSI @ sequence: " + comstr); |
| 750 this.lastcode = 0; | |
| 728 return; | 751 return; |
| 729 } | 752 } |
| 730 /* The cursor stays still, but characters move out from under it. */ | 753 /* The cursor stays still, but characters move out from under it. */ |
| 731 this.flipCursor(); | 754 this.flipCursor(); |
| 732 var rowdiv = this.screen.childNodes[this.c.y]; | 755 var rowdiv = this.screen.childNodes[this.c.y]; |
| 743 else if (c >= 65 && c <= 71) { | 766 else if (c >= 65 && c <= 71) { |
| 744 /* A - up, B - down, C - forward, D - backward */ | 767 /* A - up, B - down, C - forward, D - backward */ |
| 745 /* E - next line, F - previous line, G - to column */ | 768 /* E - next line, F - previous line, G - to column */ |
| 746 if (prefix || postfix) { | 769 if (prefix || postfix) { |
| 747 debug(1, "Invalid CSI sequence: " + comstr); | 770 debug(1, "Invalid CSI sequence: " + comstr); |
| 771 this.lastcode = 0; | |
| 748 return; | 772 return; |
| 749 } | 773 } |
| 750 /* These may be out of range, but cmove will take care of that. */ | 774 /* These may be out of range, but cmove will take care of that. */ |
| 751 if (c == 65) | 775 if (c == 65) |
| 752 this.cmove(this.c.y - count, null); | 776 this.cmove(this.c.y - count, null); |
| 767 /* H - move */ | 791 /* H - move */ |
| 768 var x = 0; | 792 var x = 0; |
| 769 var y = 0; | 793 var y = 0; |
| 770 if (prefix || postfix) { | 794 if (prefix || postfix) { |
| 771 debug(1, "Invalid CSI H sequence: " + comstr); | 795 debug(1, "Invalid CSI H sequence: " + comstr); |
| 796 this.lastcode = 0; | |
| 772 return; | 797 return; |
| 773 } | 798 } |
| 774 if (params[0]) | 799 if (params[0]) |
| 775 y = params[0] - 1; | 800 y = params[0] - 1; |
| 776 if (params[1]) | 801 if (params[1]) |
| 785 else if (c == 73) { | 810 else if (c == 73) { |
| 786 /* I - move forward by tabs */ | 811 /* I - move forward by tabs */ |
| 787 var x = this.c.x; | 812 var x = this.c.x; |
| 788 if (prefix || postfix) { | 813 if (prefix || postfix) { |
| 789 debug(1, "Invalid CSI I sequence: " + comstr); | 814 debug(1, "Invalid CSI I sequence: " + comstr); |
| 815 this.lastcode = 0; | |
| 790 return; | 816 return; |
| 791 } | 817 } |
| 792 while (count > 0) { | 818 while (count > 0) { |
| 793 x = 8 * (Math.floor(x / 8) + 1); | 819 x = 8 * (Math.floor(x / 8) + 1); |
| 794 if (x >= this.w) { | 820 if (x >= this.w) { |
| 806 var cols; | 832 var cols; |
| 807 if (prefix == '?') | 833 if (prefix == '?') |
| 808 debug(1, "Warning: CSI ?J not implemented"); | 834 debug(1, "Warning: CSI ?J not implemented"); |
| 809 else if (prefix || postfix) { | 835 else if (prefix || postfix) { |
| 810 debug(1, "Invalid CSI J sequence: " + comstr); | 836 debug(1, "Invalid CSI J sequence: " + comstr); |
| 837 this.lastcode = 0; | |
| 811 return; | 838 return; |
| 812 } | 839 } |
| 813 if (!params[0]) { | 840 if (!params[0]) { |
| 814 /* Either 0 or not given */ | 841 /* Either 0 or not given */ |
| 815 start = this.c.y + 1; | 842 start = this.c.y + 1; |
| 826 end = this.h - 1; | 853 end = this.h - 1; |
| 827 cols = 0; | 854 cols = 0; |
| 828 } | 855 } |
| 829 else { |
