changeset 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 b04313038a0b
children e6af951def94
files termemu.js
diffstat 1 files changed, 126 insertions(+), 70 deletions(-) [+]
line wrap: on
line diff
--- 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 () {