# HG changeset patch # User John "Elwin" Edwards # Date 1567711167 14400 # Node ID d60063a674e13f973e2921416a461170d02867b7 # Parent b04313038a0ba912a0c5f15e6f9b6b6613a54841 Terminal emulation: implement the CSI b sequence. CSI b repeats the previous character, if it was printable and not an escape sequence. diff -r b04313038a0b -r d60063a674e1 termemu.js --- a/termemu.js Sun Aug 25 21:27:31 2019 -0400 +++ b/termemu.js Thu Sep 05 15:19:27 2019 -0400 @@ -71,6 +71,7 @@ scrB: 0, // init() will set this properly c: null, // Contains cursor position and text attributes offedge: false, // Going off the edge doesn't mean adding a new line + lastcode: 0, // Last printed character clearAttrs: function () { /* Make sure to reset ALL attribute properties and NOTHING else. */ this.c.bold = false; @@ -460,6 +461,7 @@ this.screen.replaceChild(this.makeRow(), this.screen.childNodes[i]); } this.flipCursor(); // make it appear in the new row + this.lastcode = 0; return; }, write: function (codes) { @@ -531,6 +533,10 @@ debug(1, "Unrecognized sequence ESC " + codes[i].toString(16)); this.comseq = []; } + if (this.comseq.length == 0) { + // A complete sequence was processed, clear lastcode. + this.lastcode = 0; + } } else if (this.comseq.length == 2 && this.comseq[0] == 27) { /* An ESC C N sequence. Not implemented. Doesn't check validity @@ -555,6 +561,7 @@ String.fromCharCode(this.comseq[1]) + " 0x" + codes[i].toString(16)); this.comseq = []; + this.lastcode = 0; } else if (this.comseq[0] == 157) { /* Commands beginning with OSC */ @@ -566,6 +573,7 @@ debug(0, "Got " + (this.comseq.length - 1) + "-byte OSC sequence"); this.oscProcess(); this.comseq = []; + this.lastcode = 0; } else this.comseq.push(codes[i]); @@ -582,15 +590,17 @@ /* Chars in csiPre can only occur right after the CSI */ debug(1, "Invalid CSI sequence: misplaced prefix"); this.comseq = []; + this.lastcode = 0; } else this.comseq.push(codes[i]); } else if (csiPost.indexOf(this.comseq[this.comseq.length - 1]) >= 0 && !csiFinal(codes[i])) { - /* Chars is csiPost must come right before the final char */ + /* Chars in csiPost must come right before the final char */ debug(1, "Invalid CSI sequence: misplaced postfix"); this.comseq = []; + this.lastcode = 0; } else if ((codes[i] >= 48 && codes[i] <= 57) || codes[i] == 59 || csiPost.indexOf(codes[i]) >= 0) { @@ -605,91 +615,102 @@ else { debug(1, "Invalid CSI sequence: unknown code " + codes[i].toString(16)); this.comseq = []; + this.lastcode = 0; } } else { debug(1, "Unknown sequence with " + this.comseq[0].toString(16)); this.comseq = []; - } - continue; - } - /* Treat it as a single character. */ - if (codes[i] == 5) { - sendback("06"); - } - else if (codes[i] == 7) { - /* bell */ - bell(true); - } - else if (codes[i] == 8) { - /* backspace */ - if (this.offedge) - this.offedge = false; - else if (this.c.x > 0) - this.cmove(null, this.c.x - 1); - } - else if (codes[i] == 9) { - /* tab */ - var xnew; - if (this.c.x < this.w - 1) { - xnew = 8 * (Math.floor(this.c.x / 8) + 1); - if (xnew >= this.w) - xnew = this.w - 1; - this.cmove(null, xnew); - } - else { - this.offedge = true; + this.lastcode = 0; } } - else if (codes[i] >= 10 && codes[i] <= 12) { - /* newline, vertical tab, form feed */ - if (this.offedge) - this.newline(true); - else - this.newline(false); - } - else if (codes[i] == 13) { - /* carriage return \r */ - this.cmove(null, 0); - } - else if (codes[i] == 14) { - /* shift out */ - // Currently assuming that G1 is DEC Special & Line Drawing - this.c.cset = "0"; - debug(0, "Using DEC graphics charset."); - } - else if (codes[i] == 15) { - /* shift in */ - // Currently assuming that G0 is ASCII - this.c.cset = "B"; - debug(0, "Using ASCII charset."); - } - else if (codes[i] == 27) { - /* escape */ - this.comseq.push(codes[i]); - } - else if (codes[i] < 32 || (codes[i] >= 127 && codes[i] < 160)) { - /* Some kind of control character. */ - debug(1, "Unprintable character 0x" + codes[i].toString(16)); - } - else { + else if ((codes[i] >= 32 && codes[i] < 127) || codes[i] >= 160) { /* If it's ASCII, it's printable; take a risk on anything higher */ if ((this.c.cset == "0") && (codes[i] in decChars)) { // DEC special character set - this.placechar(String.fromCharCode(decChars[codes[i]])); + this.lastcode = decChars[codes[i]]; } else { - this.placechar(String.fromCharCode(codes[i])); + this.lastcode = codes[i]; } + this.placechar(String.fromCharCode(this.lastcode)); + } + else { + /* Treat it as a single control character. */ + this.singleCtl(codes[i]); } } return; }, + singleCtl: function (ctlcode) { + if (ctlcode == 5) { + sendback("06"); + } + else if (ctlcode == 7) { + /* bell */ + bell(true); + } + else if (ctlcode == 8) { + /* backspace */ + if (this.offedge) + this.offedge = false; + else if (this.c.x > 0) + this.cmove(null, this.c.x - 1); + } + else if (ctlcode == 9) { + /* tab */ + var xnew; + if (this.c.x < this.w - 1) { + xnew = 8 * (Math.floor(this.c.x / 8) + 1); + if (xnew >= this.w) + xnew = this.w - 1; + this.cmove(null, xnew); + } + else { + this.offedge = true; + } + } + else if (ctlcode >= 10 && ctlcode <= 12) { + /* newline, vertical tab, form feed */ + if (this.offedge) + this.newline(true); + else + this.newline(false); + } + else if (ctlcode == 13) { + /* carriage return \r */ + this.cmove(null, 0); + } + else if (ctlcode == 14) { + /* shift out */ + // Currently assuming that G1 is DEC Special & Line Drawing + this.c.cset = "0"; + debug(0, "Using DEC graphics charset."); + } + else if (ctlcode == 15) { + /* shift in */ + // Currently assuming that G0 is ASCII + this.c.cset = "B"; + debug(0, "Using ASCII charset."); + } + else if (ctlcode == 27) { + /* escape */ + this.comseq.push(27); + } + else { + debug(1, "Unprintable character 0x" + ctlcode.toString(16)); + } + if (ctlcode != 27) { + // Sequences should preserve lastcode until they are completed + this.lastcode = 0; + } + }, csiProcess: function () { /* Processes the CSI sequence in this.comseq */ var c = this.comseq[this.comseq.length - 1]; if (this.comseq[0] != 155 || !csiFinal(c)) return; + var printed = false; var comstr = ""; for (var i = 1; i < this.comseq.length; i++) comstr += String.fromCharCode(this.comseq[i]); @@ -698,6 +719,7 @@ var matchCSI = comstr.match(reCSI); if (!matchCSI) { debug(1, "Unrecognized CSI sequence: " + comstr); + this.lastcode = 0; return; } var prefix = null; @@ -725,6 +747,7 @@ /* @ - insert spaces at cursor */ if (prefix || postfix) { debug(1, "Invalid CSI @ sequence: " + comstr); + this.lastcode = 0; return; } /* The cursor stays still, but characters move out from under it. */ @@ -745,6 +768,7 @@ /* E - next line, F - previous line, G - to column */ if (prefix || postfix) { debug(1, "Invalid CSI sequence: " + comstr); + this.lastcode = 0; return; } /* These may be out of range, but cmove will take care of that. */ @@ -769,6 +793,7 @@ var y = 0; if (prefix || postfix) { debug(1, "Invalid CSI H sequence: " + comstr); + this.lastcode = 0; return; } if (params[0]) @@ -787,6 +812,7 @@ var x = this.c.x; if (prefix || postfix) { debug(1, "Invalid CSI I sequence: " + comstr); + this.lastcode = 0; return; } while (count > 0) { @@ -808,6 +834,7 @@ debug(1, "Warning: CSI ?J not implemented"); else if (prefix || postfix) { debug(1, "Invalid CSI J sequence: " + comstr); + this.lastcode = 0; return; } if (!params[0]) { @@ -828,6 +855,7 @@ } else { debug(1, "Unimplemented parameter in CSI J sequence: " + comstr); + this.lastcode = 0; return; } for (var nrow = start; nrow <= end; nrow++) { @@ -855,6 +883,7 @@ debug(1, "Warning: CSI ?K not implemented"); else if (prefix || postfix) { debug(1, "Invalid CSI K sequence: " + comstr); + this.lastcode = 0; return; } /* 0 (default): right, 1: left, 2: all. Include cursor position. */ @@ -885,11 +914,14 @@ * M - delete current lines */ if (prefix || postfix) { debug(1, "Invalid CSI sequence: " + comstr); + this.lastcode = 0; return; } /* CSI LM have no effect outside of the scrolling region */ - if (this.c.y < this.scrT || this.c.y > this.scrB) + if (this.c.y < this.scrT || this.c.y > this.scrB) { + this.lastcode = 0; return; + } this.flipCursor(); while (count > 0) { var blankrow = this.makeRow(); @@ -915,6 +947,7 @@ /* P - delete at active position, causing cells on the right to shift. */ if (prefix || postfix) { debug(1, "Invalid CSI P sequence: " + comstr); + this.lastcode = 0; return; } var cursrow = this.screen.childNodes[this.c.y]; @@ -930,6 +963,7 @@ /* S - scroll up, T - scroll down */ if (prefix || postfix) { debug(1, "Invalid CSI sequence: " + comstr); + this.lastcode = 0; return; } if (c == 83) @@ -941,6 +975,7 @@ /* X - erase characters */ if (prefix || postfix) { debug(1, "Invalid CSI sequence: " + comstr); + this.lastcode = 0; return; } var row = this.screen.childNodes[this.c.y]; @@ -954,6 +989,7 @@ var x = this.c.x; if (prefix || postfix) { debug(1, "Invalid CSI Z sequence: " + comstr); + this.lastcode = 0; return; } while (count > 0) { @@ -970,14 +1006,26 @@ /* ` - go to col */ if (prefix || postfix) { debug(1, "Invalid CSI ` sequence: " + comstr); + this.lastcode = 0; return; } this.cmove(null, count - 1); } + else if (c == 98) { + /* b - repeat previous character */ + if (this.lastcode !== 0) { + while (count > 0) { + this.placechar(String.fromCharCode(this.lastcode)); + count--; + } + printed = true; + } + } else if (c == 99) { /* c - query terminal attributes */ if (prefix !== null) { debug(1, "Unimplemented CSI sequence: " + comstr); + this.lastcode = 0; return; } /* "CSI ? 1 ; 2 c" - VT100 */ @@ -987,6 +1035,7 @@ /* d - go to row */ if (prefix || postfix) { debug(1, "Invalid CSI d sequence: " + comstr); + this.lastcode = 0; return; } this.cmove(count - 1, null); @@ -997,6 +1046,7 @@ var y = 0; if (prefix || postfix) { debug(1, "Invalid CSI f sequence: " + comstr); + this.lastcode = 0; return; } if (params[0]) @@ -1009,6 +1059,7 @@ /* h - set modes */ if (prefix != '?') { debug(1, "Unimplemented CSI sequence: " + comstr); + this.lastcode = 0; return; } for (var i = 0; i < params.length; i++) { @@ -1047,6 +1098,7 @@ } else { debug(1, "Unimplemented CSI sequence: " + comstr); + this.lastcode = 0; return; } } @@ -1080,6 +1132,7 @@ /* m - character attributes */ if (prefix !== null) { debug(1, "Unimplemented CSI sequence: " + comstr); + this.lastcode = 0; return; } if (params.length == 0) @@ -1133,15 +1186,18 @@ t = params[0] - 1; if (params[1] && params[1] <= this.h) b = params[1] - 1; - if (b <= t) - return; - this.scrT = t; - this.scrB = b; - this.cmove(0, 0); + if (b > t) { + this.scrT = t; + this.scrB = b; + this.cmove(0, 0); + } } else { debug(1, "Unimplemented CSI sequence: " + comstr); } + if (!printed) { + this.lastcode = 0; + } return; }, oscProcess: function () {