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 {