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 { |