]> git.proxmox.com Git - mirror_xterm.js.git/commitdiff
Merge remote-tracking branch 'upstream/master' into 441_windows_support
authorDaniel Imms <daimms@microsoft.com>
Sun, 15 Jan 2017 10:12:17 +0000 (02:12 -0800)
committerDaniel Imms <daimms@microsoft.com>
Sun, 15 Jan 2017 10:12:17 +0000 (02:12 -0800)
22 files changed:
Dockerfile
README.md
bin/build [deleted file]
bin/prepare-release
demo/index.html
demo/main.js
gulpfile.js [new file with mode: 0644]
package.json
src/EscapeSequences.ts [new file with mode: 0644]
src/EventEmitter.ts
src/Viewport.test.ts
src/Viewport.ts
src/test/escape-sequences-test.js
src/test/test.js
src/utils/Browser.js [deleted file]
src/utils/Browser.ts [new file with mode: 0644]
src/utils/CharMeasure.ts [new file with mode: 0644]
src/utils/Generic.js [deleted file]
src/utils/Generic.ts [new file with mode: 0644]
src/xterm.css
src/xterm.js
tsconfig.json

index 36e821bd7fb18bfb41052273a0a5389c0403c26b..1f0db1f9d93637a36d212b037e674d341dfe3069 100644 (file)
@@ -1,11 +1,6 @@
 FROM node:6.9
 MAINTAINER Paris Kasidiaris <paris@sourcelair.com>
 
-# Install cpio, used for building
-RUN apt-get update \
-    && apt-get install -y --no-install-recommends cpio \
-    && rm -rf /var/lib/apt/lists/*
-
 # Set the working directory
 WORKDIR /usr/src/app
 
index b5d30041cda8a85c37a09c44382eb1dc748a96ba..f68da0f1d3505fa6296e516eac664186ebe16c5f 100644 (file)
--- a/README.md
+++ b/README.md
@@ -26,6 +26,8 @@ Xterm.js is used in several world-class applications to provide great terminal e
 - [**Microsoft Visual Studio Code**](http://code.visualstudio.com/): Modern, versatile and powerful open source code editor that provides an integrated terminal based on xterm.js
 - [**ttyd**](https://github.com/tsl0922/ttyd): A command-line tool for sharing terminal over the web, with fully-featured terminal emulation based on xterm.js
 - [**Katacoda**](https://www.katacoda.com/): Katacoda is an Interactive Learning Platform for software developers, covering the latest Cloud Native technologies.
+- [**Eclipse Che**](http://www.eclipse.org/che): Developer workspace server, cloud IDE, and Eclipse next-generation IDE.
+- [**Codenvy**](http://www.codenvy.com): Cloud workspaces for development teams.
 
 
 Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it in our list.
@@ -117,6 +119,14 @@ Visit https://lair.io/sourcelair/xterm and follow the instructions. All developm
 
 [Download Visual Studio Code](http://code.visualstudio.com/Download), clone xterm.js and you are all set.
 
+#### [Eclipse Che](http://www.eclipse.org/che)
+
+You can start Eclipse Che with `docker run eclipse/che start`.
+
+#### [Codenvy](http://www.codenvy.io)
+
+You can create a trial account or install an enterprise version with `docker run codenvy/cli start`.
+
 ## License Agreement
 
 If you contribute code to this project, you are implicitly allowing your code to be distributed under the MIT license. You are also implicitly verifying that all code is your original work.
diff --git a/bin/build b/bin/build
deleted file mode 100755 (executable)
index a01a1b9..0000000
--- a/bin/build
+++ /dev/null
@@ -1,36 +0,0 @@
-#! /usr/bin/env bash
-
-set -e
-
-# $BUILD_DIR should default to "build"
-BUILD_DIR=${BUILD_DIR:=build}
-
-# Create the build directory
-mkdir -p $BUILD_DIR
-
-
-# Clean lib/* to prevent confusion if files were deleted in src/
-rm -rf lib/*
-
-# Build all TypeScript files (including tests) to lib/
-tsc
-
-# Concat all xterm.js files into a single file and output as a UMD to $BUILD_DIR/xterm.js
-browserify ./lib/xterm.js --standalone Terminal --debug --outfile ./$BUILD_DIR/xterm.js
-cat ./$BUILD_DIR/xterm.js | exorcist ./$BUILD_DIR/xterm.js.map -b ./$BUILD_DIR > ./$BUILD_DIR/xterm.temp.js
-rm ./$BUILD_DIR/xterm.js
-mv ./$BUILD_DIR/xterm.temp.js ./$BUILD_DIR/xterm.js
-
-# Resolve the chain of sourcemaps so that ./$BUILD_DIR/xterm.js.map points at ./src
-sorcery -i $BUILD_DIR/xterm.js
-
-# Copy all CSS files from src/ to $BUILD_DIR/ and lib/
-cd src
-find . -name '*.css' | cpio -pdm ../$BUILD_DIR
-find . -name '*.css' | cpio -pdm ../lib
-cd ..
-
-# Copy addons from lib/ to $BUILD_DIR/
-cd lib/addons
-find . -name '*.js' | cpio -pdm ../../$BUILD_DIR/addons
-cd ../..
index 6bd39d3dadf35d0cd50f9fe7a7ff37f512025e77..464e65b4d60c8db3a891fc3854267d5d66eb8a43 100755 (executable)
@@ -21,7 +21,7 @@ CURRENT_BOWER_JSON_VERSION=$(cat bower.json \
 
 # Build xterm.js into `dist`
 export BUILD_DIR=dist
-./bin/build
+npm run build
 
 # Update AUTHORS file
 sh bin/generate-authors
index 285fe0026b5aa5b14cf60792efba8f7b621e5780..764c8442ef1ec6a92a1f71c812f80ff9a5042752 100644 (file)
         <div id="terminal-container"></div>
         <div>
           <h2>Options</h2>
-          <label><input type="checkbox" id="option-cursor-blink"> cursorBlink</label>
+          <p>
+            <label><input type="checkbox" id="option-cursor-blink"> cursorBlink</label>
+          </p>
+          <p>
+            <label>Scrollback <input type="number" id="option-scrollback" value="1000" /></label>
+          </p>
           <div>
                <h3>Size</h3>
             <div>
index 86fbd31483bdddaadcf0a59fda6cc926a578b1da..52da2cef299f8000d1eee36d2544a693e0089ac1 100644 (file)
@@ -8,7 +8,8 @@ var term,
 
 var terminalContainer = document.getElementById('terminal-container'),
     optionElements = {
-      cursorBlink: document.querySelector('#option-cursor-blink')
+      cursorBlink: document.querySelector('#option-cursor-blink'),
+      scrollback: document.querySelector('#option-scrollback')
     },
     colsElement = document.getElementById('cols'),
     rowsElement = document.getElementById('rows');
@@ -28,6 +29,9 @@ colsElement.addEventListener('change', setTerminalSize);
 rowsElement.addEventListener('change', setTerminalSize);
 
 optionElements.cursorBlink.addEventListener('change', createTerminal);
+optionElements.scrollback.addEventListener('change', function () {
+  terminal.setOption('scrollback', parseInt(optionElements.scrollback.value, 10));
+});
 
 createTerminal();
 
@@ -37,7 +41,8 @@ function createTerminal() {
     terminalContainer.removeChild(terminalContainer.children[0]);
   }
   term = new Terminal({
-    cursorBlink: optionElements.cursorBlink.checked
+    cursorBlink: optionElements.cursorBlink.checked,
+    scrollback: parseInt(optionElements.scrollback.value, 10)
   });
   term.on('resize', function (size) {
     if (!pid) {
@@ -78,7 +83,6 @@ function createTerminal() {
   });
 }
 
-
 function runRealTerminal() {
   term.attach(socket);
   term._initialized = true;
diff --git a/gulpfile.js b/gulpfile.js
new file mode 100644 (file)
index 0000000..144b883
--- /dev/null
@@ -0,0 +1,87 @@
+const browserify = require('browserify');
+const buffer = require('vinyl-buffer');
+const fs = require('fs-extra');
+const gulp = require('gulp');
+const merge = require('merge-stream');
+const sorcery = require('sorcery');
+const source = require('vinyl-source-stream');
+const sourcemaps = require('gulp-sourcemaps');
+const ts = require('gulp-typescript');
+const tsify = require('tsify');
+
+
+let buildDir = process.env.BUILD_DIR || 'build';
+
+
+/**
+ * Compile TypeScript sources to JavaScript files and create a source map file for each TypeScript
+ * file compiled.
+ */
+gulp.task('tsc', function () {
+  // Remove the lib/ directory to prevent confusion if files were deleted in src/
+  fs.emptyDirSync('lib');
+
+  // Build all TypeScript files (including tests) to lib/, based on the configuration defined in
+  // `tsconfig.json`.
+  let tsProject = ts.createProject('tsconfig.json');
+  let tsResult = tsProject.src().pipe(sourcemaps.init()).pipe(tsProject());
+  let tsc = tsResult.js.pipe(sourcemaps.write('.', {includeContent: false, sourceRoot: ''})).pipe(gulp.dest('lib'));
+
+  // Copy all addons from src/ to lib/
+  let copyAddons = gulp.src('src/addons/**/*').pipe(gulp.dest('lib/addons'));
+
+  // Copy stylesheets from src/ to lib/
+  let copyStylesheets = gulp.src('src/**/*.css').pipe(gulp.dest('lib'));
+
+  return merge(tsc, copyAddons, copyStylesheets);
+});
+
+/**
+ * Bundle JavaScript files produced by the `tsc` task, into a single file named `xterm.js` with
+ * Browserify.
+ */
+gulp.task('browserify', ['tsc'], function() {
+  // Ensure that the build directory exists
+  fs.ensureDirSync(buildDir);
+
+  let browserifyOptions = {
+    basedir: buildDir,
+    debug: true,
+    entries: ['../lib/xterm.js'],
+    standalone: 'Terminal',
+    cache: {},
+    packageCache: {}
+  };
+  let bundleStream = browserify(browserifyOptions)
+        .plugin(tsify)
+        .bundle()
+        .pipe(source('xterm.js'))
+        .pipe(buffer())
+        .pipe(sourcemaps.init({loadMaps: true, sourceRoot: '..'}))
+        .pipe(sourcemaps.write('./'))
+        .pipe(gulp.dest(buildDir));
+
+  // Copy all add-ons from lib/ to buildDir
+  let copyAddons = gulp.src('lib/addons/**/*').pipe(gulp.dest(`${buildDir}/addons`));
+
+  // Copy stylesheets from src/ to lib/
+  let copyStylesheets = gulp.src('lib/**/*.css').pipe(gulp.dest(buildDir));
+
+  return merge(bundleStream, copyAddons, copyStylesheets);
+});
+
+
+/**
+ * Use `sorcery` to resolve the source map chain and point back to the TypeScript files.
+ * (Without this task the source maps produced for the JavaScript bundle points into the
+ * compiled JavaScript files in lib/).
+ */
+gulp.task('sorcery', ['browserify'], function () {
+  var chain = sorcery.loadSync(`${buildDir}/xterm.js`);
+  var map = chain.apply();
+  chain.writeSync();
+});
+
+gulp.task('build', ['sorcery']);
+
+gulp.task('default', ['build']);
index 74ba3e00927324af7b6ce78c6be25926ee0a3261..67eb824dc90f2770af7274e505db4352a0bcb8d7 100644 (file)
     "browserify": "^13.1.0",
     "chai": "3.5.0",
     "docdash": "0.4.0",
-    "exorcist": "^0.4.0",
     "express": "4.13.4",
     "express-ws": "2.0.0-rc.1",
+    "fs-extra": "^1.0.0",
     "glob": "^7.0.5",
+    "gulp": "^3.9.1",
+    "gulp-cli": "^1.2.2",
+    "gulp-sourcemaps": "1.9.1",
+    "gulp-typescript": "^3.1.3",
     "jsdoc": "3.4.3",
+    "merge-stream": "^1.0.1",
     "mocha": "2.5.3",
     "node-pty": "^0.4.1",
     "nodemon": "1.10.2",
     "sleep": "^3.0.1",
     "sorcery": "^0.10.0",
+    "tsify": "^3.0.0",
     "tslint": "^4.0.2",
-    "typescript": "^2.0.3"
+    "typescript": "^2.0.3",
+    "vinyl-buffer": "^1.0.0",
+    "vinyl-source-stream": "^1.1.0"
   },
   "scripts": {
     "prestart": "npm run build",
@@ -58,7 +66,7 @@
     "lint": "tslint src/**/*.ts",
     "test": "mocha --recursive ./lib",
     "build:docs": "jsdoc -c jsdoc.json",
-    "build": "./bin/build",
+    "build": "gulp build",
     "prepublish": "npm run build"
   }
 }
diff --git a/src/EscapeSequences.ts b/src/EscapeSequences.ts
new file mode 100644 (file)
index 0000000..34dfde9
--- /dev/null
@@ -0,0 +1,74 @@
+/**
+ * C0 control codes
+ * See = https://en.wikipedia.org/wiki/C0_and_C1_control_codes
+ */
+export namespace C0 {
+  /** Null (Caret = ^@, C = \0) */
+  export const NUL = '\x00';
+  /** Start of Heading (Caret = ^A) */
+  export const SOH = '\x01';
+  /** Start of Text (Caret = ^B) */
+  export const STX = '\x02';
+  /** End of Text (Caret = ^C) */
+  export const ETX = '\x03';
+  /** End of Transmission (Caret = ^D) */
+  export const EOT = '\x04';
+  /** Enquiry (Caret = ^E) */
+  export const ENQ = '\x05';
+  /** Acknowledge (Caret = ^F) */
+  export const ACK = '\x06';
+  /** Bell (Caret = ^G, C = \a) */
+  export const BEL = '\x07';
+  /** Backspace (Caret = ^H, C = \b) */
+  export const BS  = '\x08';
+  /** Character Tabulation, Horizontal Tabulation (Caret = ^I, C = \t) */
+  export const HT  = '\x09';
+  /** Line Feed (Caret = ^J, C = \n) */
+  export const LF  = '\x0a';
+  /** Line Tabulation, Vertical Tabulation (Caret = ^K, C = \v) */
+  export const VT  = '\x0b';
+  /** Form Feed (Caret = ^L, C = \f) */
+  export const FF  = '\x0c';
+  /** Carriage Return (Caret = ^M, C = \r) */
+  export const CR  = '\x0d';
+  /** Shift Out (Caret = ^N) */
+  export const SO  = '\x0e';
+  /** Shift In (Caret = ^O) */
+  export const SI  = '\x0f';
+  /** Data Link Escape (Caret = ^P) */
+  export const DLE = '\x10';
+  /** Device Control One (XON) (Caret = ^Q) */
+  export const DC1 = '\x11';
+  /** Device Control Two (Caret = ^R) */
+  export const DC2 = '\x12';
+  /** Device Control Three (XOFF) (Caret = ^S) */
+  export const DC3 = '\x13';
+  /** Device Control Four (Caret = ^T) */
+  export const DC4 = '\x14';
+  /** Negative Acknowledge (Caret = ^U) */
+  export const NAK = '\x15';
+  /** Synchronous Idle (Caret = ^V) */
+  export const SYN = '\x16';
+  /** End of Transmission Block (Caret = ^W) */
+  export const ETB = '\x17';
+  /** Cancel (Caret = ^X) */
+  export const CAN = '\x18';
+  /** End of Medium (Caret = ^Y) */
+  export const EM  = '\x19';
+  /** Substitute (Caret = ^Z) */
+  export const SUB = '\x1a';
+  /** Escape (Caret = ^[, C = \e) */
+  export const ESC = '\x1b';
+  /** File Separator (Caret = ^\) */
+  export const FS  = '\x1c';
+  /** Group Separator (Caret = ^]) */
+  export const GS  = '\x1d';
+  /** Record Separator (Caret = ^^) */
+  export const RS  = '\x1e';
+  /** Unit Separator (Caret = ^_) */
+  export const US  = '\x1f';
+  /** Space */
+  export const SP  = '\x20';
+  /** Delete (Caret = ^?) */
+  export const DEL = '\x7f';
+};
index d05e3863a16f2cdb54c71a786784772f4386839d..1db8676328edd4443e8d4a3629bf95953ee23dec 100644 (file)
@@ -11,7 +11,9 @@ export class EventEmitter {
   private _events: {[type: string]: ListenerType[]};
 
   constructor() {
-    this._events = {};
+    // Restore the previous events if available, this will happen if the
+    // constructor is called multiple times on the same object (terminal reset).
+    this._events = this._events || {};
   }
 
   public on(type, listener): void {
index 5b106b434538ff84732a5237e2346b30a9940b44..4fa77ec09af14def6a182b1d537097c52e85b267 100644 (file)
@@ -2,11 +2,11 @@ import { assert } from 'chai';
 import { Viewport } from './Viewport';
 
 describe('Viewport', () => {
-  var terminal;
-  var viewportElement;
-  var charMeasureElement;
-  var viewport;
-  var scrollAreaElement;
+  let terminal;
+  let viewportElement;
+  let charMeasure;
+  let viewport;
+  let scrollAreaElement;
 
   const CHARACTER_HEIGHT = 10;
 
@@ -34,21 +34,17 @@ describe('Viewport', () => {
         height: 0
       }
     };
-    charMeasureElement = {
-      getBoundingClientRect: () => {
-        return { width: null, height: CHARACTER_HEIGHT };
-      }
+    charMeasure = {
+      height: CHARACTER_HEIGHT
     };
-    viewport = new Viewport(terminal, viewportElement, scrollAreaElement, charMeasureElement);
+    viewport = new Viewport(terminal, viewportElement, scrollAreaElement, charMeasure);
   });
 
   describe('refresh', () => {
     it('should set the line-height of the terminal', () => {
       assert.equal(viewportElement.style.lineHeight, CHARACTER_HEIGHT + 'px');
       assert.equal(terminal.rowContainer.style.lineHeight, CHARACTER_HEIGHT + 'px');
-      charMeasureElement.getBoundingClientRect = () => {
-        return { width: null, height: 1 };
-      };
+      charMeasure.height = 1;
       viewport.refresh();
       assert.equal(viewportElement.style.lineHeight, '1px');
       assert.equal(terminal.rowContainer.style.lineHeight, '1px');
@@ -59,9 +55,7 @@ describe('Viewport', () => {
       terminal.rows = 1;
       viewport.refresh();
       assert.equal(viewportElement.style.height, 1 * CHARACTER_HEIGHT + 'px');
-      charMeasureElement.getBoundingClientRect = () => {
-        return { width: null, height: 20 };
-      };
+      charMeasure.height = 20;
       viewport.refresh();
       assert.equal(viewportElement.style.height, 20 + 'px');
     });
index 3aa1319fb1c74ac7ccd7f4b6b0d3a8790fd33d48..aaafbc5f9726d6a415777508303870632f610a7e 100644 (file)
@@ -3,6 +3,7 @@
  */
 
 import { ITerminal } from './Interfaces';
+import { CharMeasure } from './utils/CharMeasure';
 
 /**
  * Represents the viewport of a terminal, the visible area within the larger buffer of output.
@@ -24,7 +25,7 @@ export class Viewport {
     private terminal: ITerminal,
     private viewportElement: HTMLElement,
     private scrollArea: HTMLElement,
-    private charMeasureElement: HTMLElement
+    private charMeasure: CharMeasure
   ) {
     this.currentRowHeight = 0;
     this.lastRecordedBufferLength = 0;
@@ -43,21 +44,20 @@ export class Viewport {
    * @param charSize A character size measurement bounding rect object, if it doesn't exist it will
    *   be created.
    */
-  private refresh(charSize?: ClientRect): void {
-    var size = charSize || this.charMeasureElement.getBoundingClientRect();
-    if (size.height > 0) {
-      var rowHeightChanged = size.height !== this.currentRowHeight;
+  private refresh(): void {
+    if (this.charMeasure.height > 0) {
+      const rowHeightChanged = this.charMeasure.height !== this.currentRowHeight;
       if (rowHeightChanged) {
-        this.currentRowHeight = size.height;
-        this.viewportElement.style.lineHeight = size.height + 'px';
-        this.terminal.rowContainer.style.lineHeight = size.height + 'px';
+        this.currentRowHeight = this.charMeasure.height;
+        this.viewportElement.style.lineHeight = this.charMeasure.height + 'px';
+        this.terminal.rowContainer.style.lineHeight = this.charMeasure.height + 'px';
       }
-      var viewportHeightChanged = this.lastRecordedViewportHeight !== this.terminal.rows;
+      const viewportHeightChanged = this.lastRecordedViewportHeight !== this.terminal.rows;
       if (rowHeightChanged || viewportHeightChanged) {
         this.lastRecordedViewportHeight = this.terminal.rows;
-        this.viewportElement.style.height = size.height * this.terminal.rows + 'px';
+        this.viewportElement.style.height = this.charMeasure.height * this.terminal.rows + 'px';
       }
-      this.scrollArea.style.height = (size.height * this.lastRecordedBufferLength) + 'px';
+      this.scrollArea.style.height = (this.charMeasure.height * this.lastRecordedBufferLength) + 'px';
     }
   }
 
@@ -74,14 +74,13 @@ export class Viewport {
       this.refresh();
     } else {
       // If size has changed, refresh viewport
-      var size = this.charMeasureElement.getBoundingClientRect();
-      if (size.height !== this.currentRowHeight) {
-        this.refresh(size);
+      if (this.charMeasure.height !== this.currentRowHeight) {
+        this.refresh();
       }
     }
 
     // Sync scrollTop
-    var scrollTop = this.terminal.ydisp * this.currentRowHeight;
+    const scrollTop = this.terminal.ydisp * this.currentRowHeight;
     if (this.viewportElement.scrollTop !== scrollTop) {
       this.viewportElement.scrollTop = scrollTop;
     }
@@ -93,8 +92,8 @@ export class Viewport {
    * @param ev The scroll event.
    */
   private onScroll(ev: Event) {
-    var newRow = Math.round(this.viewportElement.scrollTop / this.currentRowHeight);
-    var diff = newRow - this.terminal.ydisp;
+    const newRow = Math.round(this.viewportElement.scrollTop / this.currentRowHeight);
+    const diff = newRow - this.terminal.ydisp;
     this.terminal.scrollDisp(diff, true);
   }
 
@@ -110,7 +109,7 @@ export class Viewport {
       return;
     }
     // Fallback to WheelEvent.DOM_DELTA_PIXEL
-    var multiplier = 1;
+    let multiplier = 1;
     if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) {
       multiplier = this.currentRowHeight;
     } else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
index 7dcefd95b496d2e90b9e222c6d163101f5a1183a..6c64b562ef22b91826d84eb613b753a11baafd0e 100644 (file)
@@ -92,7 +92,11 @@ describe('xterm output comparison', function() {
         var from_pty = pty_write_read(in_file);
         // uncomment this to get log from terminal
         //console.log = function(){};
-        xterm.write(from_pty);
+
+        // Perform a synchronous .write(data)
+        xterm.writeBuffer.push(from_pty);
+        xterm.innerWrite();
+
         var from_emulator = terminalToString(xterm);
         console.log = CONSOLE_LOG;
         var expected = fs.readFileSync(filename.split('.')[0] + '.text', 'utf8');
index a44d2066163f620e7edb64cca80d1b0fb4d32301..7c716b2abc524beac68db4aa0d35d32d801a8cef 100644 (file)
@@ -14,6 +14,15 @@ describe('xterm.js', function() {
     xterm.compositionHelper = {
       keydown: function(){ return true; }
     };
+    // Force synchronous refreshes
+    xterm.queueRefresh = function(start, end) {
+      xterm.refresh(start, end);
+    };
+    // Force synchronous writes
+    xterm.write = function(data) {
+      xterm.writeBuffer.push(data);
+      xterm.innerWrite();
+    };
   });
 
   describe('getOption', function() {
diff --git a/src/utils/Browser.js b/src/utils/Browser.js
deleted file mode 100644 (file)
index cd13e02..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-/**
- * Attributes and methods to help with identifying the current browser and platform.
- * @module xterm/utils/Browser
- * @license MIT
- */
-
-import { contains } from './Generic.js';
-
-let isNode = (typeof navigator == 'undefined') ? true : false;
-let userAgent = (isNode) ? 'node' : navigator.userAgent;
-let platform = (isNode) ? 'node' : navigator.platform;
-
-export let isFirefox = !!~userAgent.indexOf('Firefox');
-export let isMSIE = !!~userAgent.indexOf('MSIE') || !!~userAgent.indexOf('Trident');
-
-// Find the users platform. We use this to interpret the meta key
-// and ISO third level shifts.
-// http://stackoverflow.com/q/19877924/577598
-export let isMac = contains(['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'], platform);
-export let isIpad = platform === 'iPad';
-export let isIphone = platform === 'iPhone';
-export let isMSWindows = contains(['Windows', 'Win16', 'Win32', 'WinCE'], platform);
diff --git a/src/utils/Browser.ts b/src/utils/Browser.ts
new file mode 100644 (file)
index 0000000..04da698
--- /dev/null
@@ -0,0 +1,22 @@
+/**
+ * Attributes and methods to help with identifying the current browser and platform.
+ * @module xterm/utils/Browser
+ * @license MIT
+ */
+
+import { contains } from './Generic';
+
+const isNode = (typeof navigator === 'undefined') ? true : false;
+const userAgent = (isNode) ? 'node' : navigator.userAgent;
+const platform = (isNode) ? 'node' : navigator.platform;
+
+export const isFirefox = !!~userAgent.indexOf('Firefox');
+export const isMSIE = !!~userAgent.indexOf('MSIE') || !!~userAgent.indexOf('Trident');
+
+// Find the users platform. We use this to interpret the meta key
+// and ISO third level shifts.
+// http://stackoverflow.com/q/19877924/577598
+export const isMac = contains(['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'], platform);
+export const isIpad = platform === 'iPad';
+export const isIphone = platform === 'iPhone';
+export const isMSWindows = contains(['Windows', 'Win16', 'Win32', 'WinCE'], platform);
diff --git a/src/utils/CharMeasure.ts b/src/utils/CharMeasure.ts
new file mode 100644 (file)
index 0000000..6ca5046
--- /dev/null
@@ -0,0 +1,57 @@
+/**
+ * @module xterm/utils/CharMeasure
+ * @license MIT
+ */
+
+import { EventEmitter } from '../EventEmitter.js';
+
+/**
+ * Utility class that measures the size of a character.
+ */
+export class CharMeasure extends EventEmitter {
+  private _parentElement: HTMLElement;
+  private _measureElement: HTMLElement;
+  private _width: number;
+  private _height: number;
+
+  constructor(parentElement: HTMLElement) {
+    super();
+    this._parentElement = parentElement;
+  }
+
+  public get width(): number {
+    return this._width;
+  }
+
+  public get height(): number {
+    return this._height;
+  }
+
+  public measure(): void {
+    if (!this._measureElement) {
+      this._measureElement = document.createElement('span');
+      this._measureElement.style.position = 'absolute';
+      this._measureElement.style.top = '0';
+      this._measureElement.style.left = '-9999em';
+      this._measureElement.textContent = 'W';
+      this._parentElement.appendChild(this._measureElement);
+      // Perform _doMeasure async if the element was just attached as sometimes
+      // getBoundingClientRect does not return accurate values without this.
+      setTimeout(() => this._doMeasure(), 0);
+    } else {
+      this._doMeasure();
+    }
+  }
+
+  private _doMeasure(): void {
+    const oldWidth = this._width;
+    const oldHeight = this._height;
+    const geometry = this._measureElement.getBoundingClientRect();
+
+    if (this._width !== geometry.width || this._height !== geometry.height) {
+      this._width = geometry.width;
+      this._height = geometry.height;
+      this.emit('charsizechanged');
+    }
+  }
+}
diff --git a/src/utils/Generic.js b/src/utils/Generic.js
deleted file mode 100644 (file)
index 42f876f..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-/**
- * Generic utilities module with methods that can be helpful at different parts of the code base.
- * @module xterm/utils/Generic
- * @license MIT
- */
-
-/**
- * Return if the given array contains the given element
- * @param {Array} array The array to search for the given element.
- * @param {Object} el The element to look for into the array
- */
-export let contains = function(arr, el) {
-  return arr.indexOf(el) >= 0;
-};
diff --git a/src/utils/Generic.ts b/src/utils/Generic.ts
new file mode 100644 (file)
index 0000000..ce09c1b
--- /dev/null
@@ -0,0 +1,14 @@
+/**
+ * Generic utilities module with methods that can be helpful at different parts of the code base.
+ * @module xterm/utils/Generic
+ * @license MIT
+ */
+
+/**
+ * Return if the given array contains the given element
+ * @param {Array} array The array to search for the given element.
+ * @param {Object} el The element to look for into the array
+ */
+export function contains(arr: any[], el: any) {
+  return arr.indexOf(el) >= 0;
+};
index 4877f86e3a8280bdbd89b9889fbc32c1a548aed2..3f4f4bbe0a467fe67ab81d10a7ac064b67aba733 100644 (file)
     overflow-y: scroll;
 }
 
+.terminal .xterm-wide-char {
+    display: inline-block;
+}
+
 .terminal .xterm-rows {
     position: absolute;
     left: 0;
index 1d2206529efba38c3f7f4e767759a2ee8f3cfa29..353b96185885edb2988af4c131d36ffe49164f86 100644 (file)
  * @license MIT
  */
 
-import { CompositionHelper } from './CompositionHelper.js';
-import { EventEmitter } from './EventEmitter.js';
-import { Viewport } from './Viewport.js';
-import { rightClickHandler, pasteHandler, copyHandler } from './handlers/Clipboard.js';
-import { CircularList } from './utils/CircularList.js';
+import { CompositionHelper } from './CompositionHelper';
+import { EventEmitter } from './EventEmitter';
+import { Viewport } from './Viewport';
+import { rightClickHandler, pasteHandler, copyHandler } from './handlers/Clipboard';
+import { CircularList } from './utils/CircularList';
+import { C0 } from './EscapeSequences';
+import { CharMeasure } from './utils/CharMeasure';
 import * as Browser from './utils/Browser';
 import * as Keyboard from './utils/Keyboard';
 
@@ -37,6 +39,26 @@ var document = (typeof window != 'undefined') ? window.document : null;
  */
 var normal = 0, escaped = 1, csi = 2, osc = 3, charset = 4, dcs = 5, ignore = 6;
 
+/**
+ * The amount of write requests to queue before sending an XOFF signal to the
+ * pty process. This number must be small in order for ^C and similar sequences
+ * to be responsive.
+ */
+var WRITE_BUFFER_PAUSE_THRESHOLD = 5;
+
+/**
+ * The number of writes to perform in a single batch before allowing the
+ * renderer to catch up with a 0ms setTimeout.
+ */
+var WRITE_BATCH_SIZE = 300;
+
+/**
+ * The maximum number of refresh frames to skip when the write buffer is non-
+ * empty. Note that these frames may be intermingled with frames that are
+ * skipped via requestAnimationFrame's mechanism.
+ */
+var MAX_REFRESH_FRAME_SKIP = 5;
+
 /**
  * Terminal
  */
@@ -137,14 +159,8 @@ function Terminal(options) {
    */
   this.y = 0;
 
-  /**
-   * Used to debounce the refresh function
-   */
-  this.isRefreshing = false;
-
-  /**
-   * Whether there is a full terminal refresh queued
-   */
+  /** A queue of the rows to be refreshed */
+  this.refreshRowsQueue = [];
 
   this.cursorState = 0;
   this.cursorHidden = false;
@@ -202,6 +218,22 @@ function Terminal(options) {
   this.prefix = '';
   this.postfix = '';
 
+  // user input states
+  this.writeBuffer = [];
+  this.writeInProgress = false;
+  this.refreshFramesSkipped = 0;
+
+  /**
+   * Whether _xterm.js_ sent XOFF in order to catch up with the pty process.
+   * This is a distinct state from writeStopped so that if the user requested
+   * XOFF via ^S that it will not automatically resume when the writeBuffer goes
+   * below threshold.
+   */
+  this.xoffSentToCatchUp = false;
+
+  /** Whether writing has been stopped as a result of XOFF */
+  this.writeStopped = false;
+
   // leftover surrogate high from previous write invocation
   this.surrogate_high = '';
 
@@ -326,7 +358,8 @@ Terminal.defaults = {
   scrollback: 1000,
   screenKeys: false,
   debug: false,
-  cancelEvents: false
+  cancelEvents: false,
+  disableStdin: false
   // programFeatures: false,
   // focusKeys: false,
 };
@@ -372,6 +405,24 @@ Terminal.prototype.setOption = function(key, value) {
   if (!(key in Terminal.defaults)) {
     throw new Error('No option with key "' + key + '"');
   }
+  switch (key) {
+    case 'scrollback':
+      if (this.options[key] !== value) {
+        if (this.lines.length > value) {
+          const amountToTrim = this.lines.length - value;
+          const needsRefresh = (this.ydisp - amountToTrim < 0);
+          this.lines.trimStart(amountToTrim);
+          this.ybase = Math.max(this.ybase - amountToTrim, 0);
+          this.ydisp = Math.max(this.ydisp - amountToTrim, 0);
+          if (needsRefresh) {
+            this.refresh(0, this.rows - 1);
+          }
+        }
+        this.lines.maxLength = value;
+        this.viewport.syncScrollArea();
+      }
+      break;
+  }
   this[key] = value;
   this.options[key] = value;
 };
@@ -384,7 +435,7 @@ Terminal.prototype.setOption = function(key, value) {
 Terminal.bindFocus = function (term) {
   on(term.textarea, 'focus', function (ev) {
     if (term.sendFocus) {
-      term.send('\x1b[I');
+      term.send(C0.ESC + '[I');
     }
     term.element.classList.add('focus');
     term.showCursor();
@@ -407,9 +458,9 @@ Terminal.prototype.blur = function() {
  */
 Terminal.bindBlur = function (term) {
   on(term.textarea, 'blur', function (ev) {
-    term.refresh(term.y, term.y);
+    term.queueRefresh(term.y, term.y);
     if (term.sendFocus) {
-      term.send('\x1b[O');
+      term.send(C0.ESC + '[O');
     }
     term.element.classList.remove('focus');
     Terminal.focus = null;
@@ -573,20 +624,25 @@ Terminal.prototype.open = function(parent) {
   this.compositionHelper = new CompositionHelper(this.textarea, this.compositionView, this);
   this.helperContainer.appendChild(this.compositionView);
 
-  this.charMeasureElement = document.createElement('div');
-  this.charMeasureElement.classList.add('xterm-char-measure-element');
-  this.charMeasureElement.innerHTML = 'W';
-  this.helperContainer.appendChild(this.charMeasureElement);
+  this.charSizeStyleElement = document.createElement('style');
+  this.helperContainer.appendChild(this.charSizeStyleElement);
 
   for (; i < this.rows; i++) {
     this.insertRow();
   }
   this.parent.appendChild(this.element);
 
-  this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasureElement);
+  this.charMeasure = new CharMeasure(this.helperContainer);
+  this.charMeasure.on('charsizechanged', function () {
+    self.updateCharSizeCSS();
+  });
+  this.charMeasure.measure();
+
+  this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure);
 
-  // Draw the screen.
-  this.refresh(0, this.rows - 1);
+  // Setup loop that draws to screen
+  this.queueRefresh(0, this.rows - 1);
+  this.refreshLoop();
 
   // Initialize global actions that
   // need to be taken on the document.
@@ -641,6 +697,13 @@ Terminal.loadAddon = function(addon, callback) {
   }
 };
 
+/**
+ * Updates the helper CSS class with any changes necessary after the terminal's
+ * character width has been changed.
+ */
+Terminal.prototype.updateCharSizeCSS = function() {
+  this.charSizeStyleElement.textContent = '.xterm-wide-char{width:' + (this.charMeasure.width * 2) + 'px;}';
+}
 
 /**
  * XTerm mouse events
@@ -742,7 +805,7 @@ Terminal.prototype.bindMouse = function() {
       button &= 3;
       pos.x -= 32;
       pos.y -= 32;
-      var data = '\x1b[24';
+      var data = C0.ESC + '[24';
       if (button === 0) data += '1';
       else if (button === 1) data += '3';
       else if (button === 2) data += '5';
@@ -762,7 +825,7 @@ Terminal.prototype.bindMouse = function() {
       else if (button === 1) button = 4;
       else if (button === 2) button = 6;
       else if (button === 3) button = 3;
-      self.send('\x1b['
+      self.send(C0.ESC + '['
                 + button
                 + ';'
                 + (button === 3 ? 4 : 0)
@@ -781,14 +844,14 @@ Terminal.prototype.bindMouse = function() {
       pos.y -= 32;
       pos.x++;
       pos.y++;
-      self.send('\x1b[' + button + ';' + pos.x + ';' + pos.y + 'M');
+      self.send(C0.ESC + '[' + button + ';' + pos.x + ';' + pos.y + 'M');
       return;
     }
 
     if (self.sgrMouse) {
       pos.x -= 32;
       pos.y -= 32;
-      self.send('\x1b[<'
+      self.send(C0.ESC + '[<'
                 + (((button & 3) === 3 ? button & ~3 : button) - 32)
                 + ';'
                 + pos.x
@@ -804,7 +867,7 @@ Terminal.prototype.bindMouse = function() {
     encode(data, pos.x);
     encode(data, pos.y);
 
-    self.send('\x1b[M' + String.fromCharCode.apply(String, data));
+    self.send(C0.ESC + '[M' + String.fromCharCode.apply(String, data));
   }
 
   function getButton(ev) {
@@ -997,6 +1060,57 @@ Terminal.flags = {
   INVISIBLE: 16
 }
 
+/**
+ * Queues a refresh between two rows (inclusive), to be done on next animation
+ * frame.
+ * @param {number} start The start row.
+ * @param {number} end The end row.
+ */
+Terminal.prototype.queueRefresh = function(start, end) {
+  this.refreshRowsQueue.push({ start: start, end: end });
+}
+
+/**
+ * Performs the refresh loop callback, calling refresh only if a refresh is
+ * necessary before queueing up the next one.
+ */
+Terminal.prototype.refreshLoop = function() {
+  // Don't refresh if there were no row changes
+  if (this.refreshRowsQueue.length > 0) {
+    // Skip MAX_REFRESH_FRAME_SKIP frames if the writeBuffer is non-empty as it
+    // will need to be immediately refreshed anyway. This saves a lot of
+    // rendering time as the viewport DOM does not need to be refreshed, no
+    // scroll events, no layouts, etc.
+    var skipFrame = this.writeBuffer.length > 0 && this.refreshFramesSkipped++ <= MAX_REFRESH_FRAME_SKIP;
+
+    if (!skipFrame) {
+      this.refreshFramesSkipped = 0;
+      var start;
+      var end;
+      if (this.refreshRowsQueue.length > 4) {
+        // Just do a full refresh when 5+ refreshes are queued
+        start = 0;
+        end = this.rows - 1;
+      } else {
+        // Get start and end rows that need refreshing
+        start = this.refreshRowsQueue[0].start;
+        end = this.refreshRowsQueue[0].end;
+        for (var i = 1; i < this.refreshRowsQueue.length; i++) {
+          if (this.refreshRowsQueue[i].start < start) {
+            start = this.refreshRowsQueue[i].start;
+          }
+          if (this.refreshRowsQueue[i].end > end) {
+            end = this.refreshRowsQueue[i].end;
+          }
+        }
+      }
+      this.refreshRowsQueue = [];
+      this.refresh(start, end);
+    }
+  }
+  window.requestAnimationFrame(this.refreshLoop.bind(this));
+}
+
 /**
  * Refreshes (re-renders) terminal content within two rows (inclusive)
  *
@@ -1017,47 +1131,10 @@ Terminal.flags = {
  *
  * @param {number} start The row to start from (between 0 and terminal's height terminal - 1)
  * @param {number} end The row to end at (between fromRow and terminal's height terminal - 1)
- * @param {boolean} queue Whether the refresh should ran right now or be queued
  */
-Terminal.prototype.refresh = function(start, end, queue) {
+Terminal.prototype.refresh = function(start, end) {
   var self = this;
 
-  // queue defaults to true
-  queue = (typeof queue == 'undefined') ? true : queue;
-
-  /**
-   * The refresh queue allows refresh to execute only approximately 30 times a second. For
-   * commands that pass a significant amount of output to the write function, this prevents the
-   * terminal from maxing out the CPU and making the UI unresponsive. While commands can still
-   * run beyond what they do on the terminal, it is far better with a debounce in place as
-   * every single terminal manipulation does not need to be constructed in the DOM.
-   *
-   * A side-effect of this is that it makes ^C to interrupt a process seem more responsive.
-   */
-  if (queue) {
-    // If refresh should be queued, order the refresh and return.
-    if (this._refreshIsQueued) {
-      // If a refresh has already been queued, just order a full refresh next
-      this._fullRefreshNext = true;
-    } else {
-      setTimeout(function () {
-        self.refresh(start, end, false);
-      }, 34)
-      this._refreshIsQueued = true;
-    }
-    return;
-  }
-
-  // If refresh should be run right now (not be queued), release the lock
-  this._refreshIsQueued = false;
-
-  // If multiple refreshes were requested, make a full refresh.
-  if (this._fullRefreshNext) {
-    start = 0;
-    end = this.rows - 1;
-    this._fullRefreshNext = false // reset lock
-  }
-
   var x, y, i, line, out, ch, ch_width, width, data, attr, bg, fg, flags, row, parent, focused = document.activeElement;
 
   // If this is a big refresh, remove the terminal rows from the DOM for faster calculations
@@ -1080,6 +1157,10 @@ Terminal.prototype.refresh = function(start, end, queue) {
     row = y + this.ydisp;
 
     line = this.lines.get(row);
+    if (!line || !this.children[y]) {
+      // Continue if the line is not available, this means a resize is currently in progress
+      continue;
+    }
     out = '';
 
     if (this.y === y - (this.ybase - this.ydisp)
@@ -1094,6 +1175,10 @@ Terminal.prototype.refresh = function(start, end, queue) {
     i = 0;
 
     for (; i < width; i++) {
+      if (!line[i]) {
+        // Continue if the character is not available, this means a resize is currently in progress
+        continue;
+      }
       data = line[i][0];
       ch = line[i][1];
       ch_width = line[i][2];
@@ -1182,6 +1267,9 @@ Terminal.prototype.refresh = function(start, end, queue) {
         }
       }
 
+      if (ch_width === 2) {
+        out += '<span class="xterm-wide-char">';
+      }
       switch (ch) {
         case '&':
           out += '&amp;';
@@ -1200,6 +1288,9 @@ Terminal.prototype.refresh = function(start, end, queue) {
           }
           break;
       }
+      if (ch_width === 2) {
+        out += '</span>';
+      }
 
       attr = data;
     }
@@ -1224,7 +1315,7 @@ Terminal.prototype.refresh = function(start, end, queue) {
 Terminal.prototype.showCursor = function() {
   if (!this.cursorState) {
     this.cursorState = 1;
-    this.refresh(this.y, this.y);
+    this.queueRefresh(this.y, this.y);
   }
 };
 
@@ -1234,6 +1325,15 @@ Terminal.prototype.showCursor = function() {
 Terminal.prototype.scroll = function() {
   var row;
 
+  // Make room for the new row in lines
+  if (this.lines.length === this.lines.maxLength) {
+    this.lines.trimStart(1);
+    this.ybase--;
+    if (this.ydisp !== 0) {
+      this.ydisp--;
+    }
+  }
+
   this.ybase++;
 
   // TODO: Why is this done twice?
@@ -1248,13 +1348,6 @@ Terminal.prototype.scroll = function() {
   row -= this.rows - 1 - this.scrollBottom;
 
   if (row === this.lines.length) {
-    // Compensate ybase and ydisp if lines has hit the maximum buffer size
-    if (this.lines.length === this.lines.maxLength) {
-      this.ybase--;
-      if (this.ydisp !== 0) {
-        this.ydisp--;
-      }
-    }
     // Optimization: pushing is faster than splicing when they amount to the same behavior
     this.lines.push(this.blankLine());
   } else {
@@ -1311,7 +1404,7 @@ Terminal.prototype.scrollDisp = function(disp, suppressScrollEvent) {
     this.emit('scroll', this.ydisp);
   }
 
-  this.refresh(0, this.rows - 1);
+  this.queueRefresh(0, this.rows - 1);
 };
 
 /**
@@ -1341,745 +1434,780 @@ Terminal.prototype.scrollToBottom = function() {
  * @param {string} text The text to write to the terminal.
  */
 Terminal.prototype.write = function(data) {
-  var l = data.length, i = 0, j, cs, ch, code, low, ch_width, row;
-
-  this.refreshStart = this.y;
-  this.refreshEnd = this.y;
+  this.writeBuffer.push(data);
+
+  // Send XOFF to pause the pty process if the write buffer becomes too large so
+  // xterm.js can catch up before more data is sent. This is necessary in order
+  // to keep signals such as ^C responsive.
+  if (!this.xoffSentToCatchUp && this.writeBuffer.length >= WRITE_BUFFER_PAUSE_THRESHOLD) {
+    // XOFF - stop pty pipe
+    // XON will be triggered by emulator before processing data chunk
+    this.send(C0.DC3);
+    this.xoffSentToCatchUp = true;
+  }
 
-  // apply leftover surrogate high from last write
-  if (this.surrogate_high) {
-    data = this.surrogate_high + data;
-    this.surrogate_high = '';
+  if (!this.writeInProgress && this.writeBuffer.length > 0) {
+    // Kick off a write which will write all data in sequence recursively
+    this.writeInProgress = true;
+    // Kick off an async innerWrite so more writes can come in while processing data
+    var self = this;
+    setTimeout(function () {
+      self.innerWrite();
+    });
   }
+}
 
-  for (; i < l; i++) {
-    ch = data[i];
-
-    // FIXME: higher chars than 0xa0 are not allowed in escape sequences
-    //        --> maybe move to default
-    code = data.charCodeAt(i);
-    if (0xD800 <= code && code <= 0xDBFF) {
-      // we got a surrogate high
-      // get surrogate low (next 2 bytes)
-      low = data.charCodeAt(i+1);
-      if (isNaN(low)) {
-        // end of data stream, save surrogate high
-        this.surrogate_high = ch;
-        continue;
-      }
-      code = ((code - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000;
-      ch += data.charAt(i+1);
+Terminal.prototype.innerWrite = function() {
+  var writeBatch = this.writeBuffer.splice(0, WRITE_BATCH_SIZE);
+  while (writeBatch.length > 0) {
+    var data = writeBatch.shift();
+    var l = data.length, i = 0, j, cs, ch, code, low, ch_width, row;
+
+    // If XOFF was sent in order to catch up with the pty process, resume it if
+    // the writeBuffer is empty to allow more data to come in.
+    if (this.xoffSentToCatchUp && writeBatch.length === 0 && this.writeBuffer.length === 0) {
+      this.send(C0.DC1);
+      this.xoffSentToCatchUp = false;
     }
-    // surrogate low - already handled above
-    if (0xDC00 <= code && code <= 0xDFFF)
-      continue;
-    switch (this.state) {
-      case normal:
-        switch (ch) {
-          case '\x07':
-            this.bell();
-            break;
 
-          // '\n', '\v', '\f'
-          case '\n':
-          case '\x0b':
-          case '\x0c':
-            if (this.convertEol) {
-              this.x = 0;
-            }
-            this.y++;
-            if (this.y > this.scrollBottom) {
-              this.y--;
-              this.scroll();
-            }
-            break;
+    this.refreshStart = this.y;
+    this.refreshEnd = this.y;
 
-          // '\r'
-          case '\r':
-            this.x = 0;
-            break;
+    // apply leftover surrogate high from last write
+    if (this.surrogate_high) {
+      data = this.surrogate_high + data;
+      this.surrogate_high = '';
+    }
 
-          // '\b'
-          case '\x08':
-            if (this.x > 0) {
-              this.x--;
-            }
-            break;
+    for (; i < l; i++) {
+      ch = data[i];
+
+      // FIXME: higher chars than 0xa0 are not allowed in escape sequences
+      //        --> maybe move to default
+      code = data.charCodeAt(i);
+      if (0xD800 <= code && code <= 0xDBFF) {
+        // we got a surrogate high
+        // get surrogate low (next 2 bytes)
+        low = data.charCodeAt(i+1);
+        if (isNaN(low)) {
+          // end of data stream, save surrogate high
+          this.surrogate_high = ch;
+          continue;
+        }
+        code = ((code - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000;
+        ch += data.charAt(i+1);
+      }
 
-          // '\t'
-          case '\t':
-            this.x = this.nextStop();
-            break;
+      // surrogate low - already handled above
+      if (0xDC00 <= code && code <= 0xDFFF)
+        continue;
+      switch (this.state) {
+        case normal:
+          switch (ch) {
+            case C0.BEL:
+              this.bell();
+              break;
 
-          // shift out
-          case '\x0e':
-            this.setgLevel(1);
-            break;
+            // '\n', '\v', '\f'
+            case C0.LF:
+            case C0.VT:
+            case C0.FF:
+              if (this.convertEol) {
+                this.x = 0;
+              }
+              this.y++;
+              if (this.y > this.scrollBottom) {
+                this.y--;
+                this.scroll();
+              }
+              break;
 
-          // shift in
-          case '\x0f':
-            this.setgLevel(0);
-            break;
+            // '\r'
+            case '\r':
+              this.x = 0;
+              break;
 
-          // '\e'
-          case '\x1b':
-            this.state = escaped;
-            break;
+            // '\b'
+            case C0.BS:
+              if (this.x > 0) {
+                this.x--;
+              }
+              break;
 
-          default:
-            // ' '
-            // calculate print space
-            // expensive call, therefore we save width in line buffer
-            ch_width = wcwidth(code);
+            // '\t'
+            case C0.HT:
+              this.x = this.nextStop();
+              break;
 
-            if (ch >= ' ') {
-              if (this.charset && this.charset[ch]) {
-                ch = this.charset[ch];
-              }
+            // shift out
+            case C0.SO:
+              this.setgLevel(1);
+              break;
 
-              row = this.y + this.ybase;
+            // shift in
+            case C0.SI:
+              this.setgLevel(0);
+              break;
+
+            // '\e'
+            case C0.ESC:
+              this.state = escaped;
+              break;
+
+            default:
+              // ' '
+              // calculate print space
+              // expensive call, therefore we save width in line buffer
+              ch_width = wcwidth(code);
+
+              if (ch >= ' ') {
+                if (this.charset && this.charset[ch]) {
+                  ch = this.charset[ch];
+                }
 
-              // insert combining char in last cell
-              // FIXME: needs handling after cursor jumps
-              if (!ch_width && this.x) {
-                // dont overflow left
-                if (this.lines.get(row)[this.x-1]) {
-                  if (!this.lines.get(row)[this.x-1][2]) {
+                row = this.y + this.ybase;
 
-                    // found empty cell after fullwidth, need to go 2 cells back
-                    if (this.lines.get(row)[this.x-2])
-                      this.lines.get(row)[this.x-2][1] += ch;
+                // insert combining char in last cell
+                // FIXME: needs handling after cursor jumps
+                if (!ch_width && this.x) {
+                  // dont overflow left
+                  if (this.lines.get(row)[this.x-1]) {
+                    if (!this.lines.get(row)[this.x-1][2]) {
 
-                  } else {
-                    this.lines.get(row)[this.x-1][1] += ch;
+                      // found empty cell after fullwidth, need to go 2 cells back
+                      if (this.lines.get(row)[this.x-2])
+                        this.lines.get(row)[this.x-2][1] += ch;
+
+                    } else {
+                      this.lines.get(row)[this.x-1][1] += ch;
+                    }
+                    this.updateRange(this.y);
                   }
-                  this.updateRange(this.y);
+                  break;
                 }
-                break;
-              }
 
-              // goto next line if ch would overflow
-              // TODO: needs a global min terminal width of 2
-              if (this.x+ch_width-1 >= this.cols) {
-                // autowrap - DECAWM
-                if (this.wraparoundMode) {
-                  this.x = 0;
-                  this.y++;
-                  if (this.y > this.scrollBottom) {
-                    this.y--;
-                    this.scroll();
+                // goto next line if ch would overflow
+                // TODO: needs a global min terminal width of 2
+                if (this.x+ch_width-1 >= this.cols) {
+                  // autowrap - DECAWM
+                  if (this.wraparoundMode) {
+                    this.x = 0;
+                    this.y++;
+                    if (this.y > this.scrollBottom) {
+                      this.y--;
+                      this.scroll();
+                    }
+                  } else {
+                    this.x = this.cols-1;
+                    if(ch_width===2)  // FIXME: check for xterm behavior
+                      continue;
                   }
-                } else {
-                  this.x = this.cols-1;
-                  if(ch_width===2)  // FIXME: check for xterm behavior
-                    continue;
                 }
-              }
-              row = this.y + this.ybase;
-
-              // insert mode: move characters to right
-              if (this.insertMode) {
-                // do this twice for a fullwidth char
-                for (var moves=0; moves<ch_width; ++moves) {
-                  // remove last cell, if it's width is 0
-                  // we have to adjust the second last cell as well
-                  var removed = this.lines.get(this.y + this.ybase).pop();
-                  if (removed[2]===0
-                      && this.lines.get(row)[this.cols-2]
-                  && this.lines.get(row)[this.cols-2][2]===2)
-                    this.lines.get(row)[this.cols-2] = [this.curAttr, ' ', 1];
-
-                  // insert empty cell at cursor
-                  this.lines.get(row).splice(this.x, 0, [this.curAttr, ' ', 1]);
+                row = this.y + this.ybase;
+
+                // insert mode: move characters to right
+                if (this.insertMode) {
+                  // do this twice for a fullwidth char
+                  for (var moves=0; moves<ch_width; ++moves) {
+                    // remove last cell, if it's width is 0
+                    // we have to adjust the second last cell as well
+                    var removed = this.lines.get(this.y + this.ybase).pop();
+                    if (removed[2]===0
+                        && this.lines.get(row)[this.cols-2]
+                    && this.lines.get(row)[this.cols-2][2]===2)
+                      this.lines.get(row)[this.cols-2] = [this.curAttr, ' ', 1];
+
+                    // insert empty cell at cursor
+                    this.lines.get(row).splice(this.x, 0, [this.curAttr, ' ', 1]);
+                  }
                 }
-              }
 
-              this.lines.get(row)[this.x] = [this.curAttr, ch, ch_width];
-              this.x++;
-              this.updateRange(this.y);
-
-              // fullwidth char - set next cell width to zero and advance cursor
-              if (ch_width===2) {
-                this.lines.get(row)[this.x] = [this.curAttr, '', 0];
+                this.lines.get(row)[this.x] = [this.curAttr, ch, ch_width];
                 this.x++;
-              }
-            }
-            break;
-        }
-        break;
-      case escaped:
-        switch (ch) {
-          // ESC [ Control Sequence Introducer ( CSI is 0x9b).
-          case '[':
-            this.params = [];
-            this.currentParam = 0;
-            this.state = csi;
-            break;
-
-          // ESC ] Operating System Command ( OSC is 0x9d).
-          case ']':
-            this.params = [];
-            this.currentParam = 0;
-            this.state = osc;
-            break;
+                this.updateRange(this.y);
 
-          // ESC P Device Control String ( DCS is 0x90).
-          case 'P':
-            this.params = [];
-            this.currentParam = 0;
-            this.state = dcs;
-            break;
-
-          // ESC _ Application Program Command ( APC is 0x9f).
-          case '_':
-            this.state = ignore;
-            break;
+                // fullwidth char - set next cell width to zero and advance cursor
+                if (ch_width===2) {
+                  this.lines.get(row)[this.x] = [this.curAttr, '', 0];
+                  this.x++;
+                }
+              }
+              break;
+          }
+          break;
+        case escaped:
+          switch (ch) {
+            // ESC [ Control Sequence Introducer ( CSI is 0x9b).
+            case '[':
+              this.params = [];
+              this.currentParam = 0;
+              this.state = csi;
+              break;
 
-          // ESC ^ Privacy Message ( PM is 0x9e).
-          case '^':
-            this.state = ignore;
-            break;
+            // ESC ] Operating System Command ( OSC is 0x9d).
+            case ']':
+              this.params = [];
+              this.currentParam = 0;
+              this.state = osc;
+              break;
 
-          // ESC c Full Reset (RIS).
-          case 'c':
-            this.reset();
-            break;
+            // ESC P Device Control String ( DCS is 0x90).
+            case 'P':
+              this.params = [];
+              this.currentParam = 0;
+              this.state = dcs;
+              break;
 
-          // ESC E Next Line ( NEL is 0x85).
-          // ESC D Index ( IND is 0x84).
-          case 'E':
-            this.x = 0;
-            ;
-          case 'D':
-            this.index();
-            break;
+            // ESC _ Application Program Command ( APC is 0x9f).
+            case '_':
+              this.state = ignore;
+              break;
 
-          // ESC M Reverse Index ( RI is 0x8d).
-          case 'M':
-            this.reverseIndex();
-            break;
+            // ESC ^ Privacy Message ( PM is 0x9e).
+            case '^':
+              this.state = ignore;
+              break;
 
-          // ESC % Select default/utf-8 character set.
-          // @ = default, G = utf-8
-          case '%':
-            //this.charset = null;
-            this.setgLevel(0);
-            this.setgCharset(0, Terminal.charsets.US);
-            this.state = normal;
-            i++;
-            break;
+            // ESC c Full Reset (RIS).
+            case 'c':
+              this.reset();
+              break;
 
-          // ESC (,),*,+,-,. Designate G0-G2 Character Set.
-          case '(': // <-- this seems to get all the attention
-          case ')':
-          case '*':
-          case '+':
-          case '-':
-          case '.':
-            switch (ch) {
-              case '(':
-                this.gcharset = 0;
-                break;
-              case ')':
-                this.gcharset = 1;
-                break;
-              case '*':
-                this.gcharset = 2;
-                break;
-              case '+':
-                this.gcharset = 3;
-                break;
-              case '-':
-                this.gcharset = 1;
-                break;
-              case '.':
-                this.gcharset = 2;
-                break;
-            }
-            this.state = charset;
-            break;
+            // ESC E Next Line ( NEL is 0x85).
+            // ESC D Index ( IND is 0x84).
+            case 'E':
+              this.x = 0;
+              ;
+            case 'D':
+              this.index();
+              break;
 
-          // Designate G3 Character Set (VT300).
-          // A = ISO Latin-1 Supplemental.
-          // Not implemented.
-          case '/':
-            this.gcharset = 3;
-            this.state = charset;
-            i--;
-            break;
+            // ESC M Reverse Index ( RI is 0x8d).
+            case 'M':
+              this.reverseIndex();
+              break;
 
-          // ESC N
-          // Single Shift Select of G2 Character Set
-          // ( SS2 is 0x8e). This affects next character only.
-          case 'N':
-            break;
-          // ESC O
-          // Single Shift Select of G3 Character Set
-          // ( SS3 is 0x8f). This affects next character only.
-          case 'O':
-            break;
-          // ESC n
-          // Invoke the G2 Character Set as GL (LS2).
-          case 'n':
-            this.setgLevel(2);
-            break;
-          // ESC o
-          // Invoke the G3 Character Set as GL (LS3).
-          case 'o':
-            this.setgLevel(3);
-            break;
-          // ESC |
-          // Invoke the G3 Character Set as GR (LS3R).
-          case '|':
-            this.setgLevel(3);
-            break;
-          // ESC }
-          // Invoke the G2 Character Set as GR (LS2R).
-          case '}':
-            this.setgLevel(2);
-            break;
-          // ESC ~
-          // Invoke the G1 Character Set as GR (LS1R).
-          case '~':
-            this.setgLevel(1);
-            break;
+            // ESC % Select default/utf-8 character set.
+            // @ = default, G = utf-8
+            case '%':
+              //this.charset = null;
+              this.setgLevel(0);
+              this.setgCharset(0, Terminal.charsets.US);
+              this.state = normal;
+              i++;
+              break;
 
-          // ESC 7 Save Cursor (DECSC).
-          case '7':
-            this.saveCursor();
-            this.state = normal;
-            break;
+            // ESC (,),*,+,-,. Designate G0-G2 Character Set.
+            case '(': // <-- this seems to get all the attention
+            case ')':
+            case '*':
+            case '+':
+            case '-':
+            case '.':
+              switch (ch) {
+                case '(':
+                  this.gcharset = 0;
+                  break;
+                case ')':
+                  this.gcharset = 1;
+                  break;
+                case '*':
+                  this.gcharset = 2;
+                  break;
+                case '+':
+                  this.gcharset = 3;
+                  break;
+                case '-':
+                  this.gcharset = 1;
+                  break;
+                case '.':
+                  this.gcharset = 2;
+                  break;
+              }
+              this.state = charset;
+              break;
 
-          // ESC 8 Restore Cursor (DECRC).
-          case '8':
-            this.restoreCursor();
-            this.state = normal;
-            break;
+            // Designate G3 Character Set (VT300).
+            // A = ISO Latin-1 Supplemental.
+            // Not implemented.
+            case '/':
+              this.gcharset = 3;
+              this.state = charset;
+              i--;
+              break;
 
-          // ESC # 3 DEC line height/width
-          case '#':
-            this.state = normal;
-            i++;
-            break;
+            // ESC N
+            // Single Shift Select of G2 Character Set
+            // ( SS2 is 0x8e). This affects next character only.
+            case 'N':
+              break;
+            // ESC O
+            // Single Shift Select of G3 Character Set
+            // ( SS3 is 0x8f). This affects next character only.
+            case 'O':
+              break;
+            // ESC n
+            // Invoke the G2 Character Set as GL (LS2).
+            case 'n':
+              this.setgLevel(2);
+              break;
+            // ESC o
+            // Invoke the G3 Character Set as GL (LS3).
+            case 'o':
+              this.setgLevel(3);
+              break;
+            // ESC |
+            // Invoke the G3 Character Set as GR (LS3R).
+            case '|':
+              this.setgLevel(3);
+              break;
+            // ESC }
+            // Invoke the G2 Character Set as GR (LS2R).
+            case '}':
+              this.setgLevel(2);
+              break;
+            // ESC ~
+            // Invoke the G1 Character Set as GR (LS1R).
+            case '~':
+              this.setgLevel(1);
+              break;
 
-          // ESC H Tab Set (HTS is 0x88).
-          case 'H':
-            this.tabSet();
-            break;
+            // ESC 7 Save Cursor (DECSC).
+            case '7':
+              this.saveCursor();
+              this.state = normal;
+              break;
 
-          // ESC = Application Keypad (DECKPAM).
-          case '=':
-            this.log('Serial port requested application keypad.');
-            this.applicationKeypad = true;
-            this.viewport.syncScrollArea();
-            this.state = normal;
-            break;
+            // ESC 8 Restore Cursor (DECRC).
+            case '8':
+              this.restoreCursor();
+              this.state = normal;
+              break;
 
-          // ESC > Normal Keypad (DECKPNM).
-          case '>':
-            this.log('Switching back to normal keypad.');
-            this.applicationKeypad = false;
-            this.viewport.syncScrollArea();
-            this.state = normal;
-            break;
+            // ESC # 3 DEC line height/width
+            case '#':
+              this.state = normal;
+              i++;
+              break;
 
-          default:
-            this.state = normal;
-            this.error('Unknown ESC control: %s.', ch);
-            break;
-        }
-        break;
+            // ESC H Tab Set (HTS is 0x88).
+            case 'H':
+              this.tabSet();
+              break;
 
-      case charset:
-        switch (ch) {
-          case '0': // DEC Special Character and Line Drawing Set.
-            cs = Terminal.charsets.SCLD;
-            break;
-          case 'A': // UK
-            cs = Terminal.charsets.UK;
-            break;
-          case 'B': // United States (USASCII).
-            cs = Terminal.charsets.US;
-            break;
-          case '4': // Dutch
-            cs = Terminal.charsets.Dutch;
-            break;
-          case 'C': // Finnish
-          case '5':
-            cs = Terminal.charsets.Finnish;
-            break;
-          case 'R': // French
-            cs = Terminal.charsets.French;
-            break;
-          case 'Q': // FrenchCanadian
-            cs = Terminal.charsets.FrenchCanadian;
-            break;
-          case 'K': // German
-            cs = Terminal.charsets.German;
-            break;
-          case 'Y': // Italian
-            cs = Terminal.charsets.Italian;
-            break;
-          case 'E': // NorwegianDanish
-          case '6':
-            cs = Terminal.charsets.NorwegianDanish;
-            break;
-          case 'Z': // Spanish
-            cs = Terminal.charsets.Spanish;
-            break;
-          case 'H': // Swedish
-          case '7':
-            cs = Terminal.charsets.Swedish;
-            break;
-          case '=': // Swiss
-            cs = Terminal.charsets.Swiss;
-            break;
-          case '/': // ISOLatin (actually /A)
-            cs = Terminal.charsets.ISOLatin;
-            i++;
-            break;
-          default: // Default
-            cs = Terminal.charsets.US;
-            break;
-        }
-        this.setgCharset(this.gcharset, cs);
-        this.gcharset = null;
-        this.state = normal;
-        break;
+            // ESC = Application Keypad (DECKPAM).
+            case '=':
+              this.log('Serial port requested application keypad.');
+              this.applicationKeypad = true;
+              this.viewport.syncScrollArea();
+              this.state = normal;
+              break;
 
-      case osc:
-        // OSC Ps ; Pt ST
-        // OSC Ps ; Pt BEL
-        //   Set Text Parameters.
-        if (ch === '\x1b' || ch === '\x07') {
-          if (ch === '\x1b') i++;
+            // ESC > Normal Keypad (DECKPNM).
+            case '>':
+              this.log('Switching back to normal keypad.');
+              this.applicationKeypad = false;
+              this.viewport.syncScrollArea();
+              this.state = normal;
+              break;
 
-          this.params.push(this.currentParam);
+            default:
+              this.state = normal;
+              this.error('Unknown ESC control: %s.', ch);
+              break;
+          }
+          break;
 
-          switch (this.params[0]) {
-            case 0:
-            case 1:
-            case 2:
-              if (this.params[1]) {
-                this.title = this.params[1];
-                this.handleTitle(this.title);
-              }
+        case charset:
+          switch (ch) {
+            case '0': // DEC Special Character and Line Drawing Set.
+              cs = Terminal.charsets.SCLD;
+              break;
+            case 'A': // UK
+              cs = Terminal.charsets.UK;
+              break;
+            case 'B': // United States (USASCII).
+              cs = Terminal.charsets.US;
+              break;
+            case '4': // Dutch
+              cs = Terminal.charsets.Dutch;
+              break;
+            case 'C': // Finnish
+            case '5':
+              cs = Terminal.charsets.Finnish;
+              break;
+            case 'R': // French
+              cs = Terminal.charsets.French;
+              break;
+            case 'Q': // FrenchCanadian
+              cs = Terminal.charsets.FrenchCanadian;
               break;
-            case 3:
-              // set X property
+            case 'K': // German
+              cs = Terminal.charsets.German;
               break;
-            case 4:
-            case 5:
-              // change dynamic colors
+            case 'Y': // Italian
+              cs = Terminal.charsets.Italian;
               break;
-            case 10:
-            case 11:
-            case 12:
-            case 13:
-            case 14:
-            case 15:
-            case 16:
-            case 17:
-            case 18:
-            case 19:
-              // change dynamic ui colors
+            case 'E': // NorwegianDanish
+            case '6':
+              cs = Terminal.charsets.NorwegianDanish;
               break;
-            case 46:
-              // change log file
+            case 'Z': // Spanish
+              cs = Terminal.charsets.Spanish;
               break;
-            case 50:
-              // dynamic font
+            case 'H': // Swedish
+            case '7':
+              cs = Terminal.charsets.Swedish;
               break;
-            case 51:
-              // emacs shell
+            case '=': // Swiss
+              cs = Terminal.charsets.Swiss;
               break;
-            case 52:
-              // manipulate selection data
+            case '/': // ISOLatin (actually /A)
+              cs = Terminal.charsets.ISOLatin;
+              i++;
               break;
-            case 104:
-            case 105:
-            case 110:
-            case 111:
-            case 112:
-            case 113:
-            case 114:
-            case 115:
-            case 116:
-            case 117:
-            case 118:
-              // reset colors
+            default: // Default
+              cs = Terminal.charsets.US;
               break;
           }
-
-          this.params = [];
-          this.currentParam = 0;
+          this.setgCharset(this.gcharset, cs);
+          this.gcharset = null;
           this.state = normal;
-        } else {
-          if (!this.params.length) {
-            if (ch >= '0' && ch <= '9') {
-              this.currentParam =
-                this.currentParam * 10 + ch.charCodeAt(0) - 48;
-            } else if (ch === ';') {
-              this.params.push(this.currentParam);
-              this.currentParam = '';
+          break;
+
+        case osc:
+          // OSC Ps ; Pt ST
+          // OSC Ps ; Pt BEL
+          //   Set Text Parameters.
+          if (ch === C0.ESC || ch === C0.BEL) {
+            if (ch === C0.ESC) i++;
+
+            this.params.push(this.currentParam);
+
+            switch (this.params[0]) {
+              case 0:
+              case 1:
+              case 2:
+                if (this.params[1]) {
+                  this.title = this.params[1];
+                  this.handleTitle(this.title);
+                }
+                break;
+              case 3:
+                // set X property
+                break;
+              case 4:
+              case 5:
+                // change dynamic colors
+                break;
+              case 10:
+              case 11:
+              case 12:
+              case 13:
+              case 14:
+              case 15:
+              case 16:
+              case 17:
+              case 18:
+              case 19:
+                // change dynamic ui colors
+                break;
+              case 46:
+                // change log file
+                break;
+              case 50:
+                // dynamic font
+                break;
+              case 51:
+                // emacs shell
+                break;
+              case 52:
+                // manipulate selection data
+                break;
+              case 104:
+              case 105:
+              case 110:
+              case 111:
+              case 112:
+              case 113:
+              case 114:
+              case 115:
+              case 116:
+              case 117:
+              case 118:
+                // reset colors
+                break;
             }
+
+            this.params = [];
+            this.currentParam = 0;
+            this.state = normal;
           } else {
-            this.currentParam += ch;
+            if (!this.params.length) {
+              if (ch >= '0' && ch <= '9') {
+                this.currentParam =
+                  this.currentParam * 10 + ch.charCodeAt(0) - 48;
+              } else if (ch === ';') {
+                this.params.push(this.currentParam);
+                this.currentParam = '';
+              }
+            } else {
+              this.currentParam += ch;
+            }
           }
-        }
-        break;
-
-      case csi:
-        // '?', '>', '!'
-        if (ch === '?' || ch === '>' || ch === '!') {
-          this.prefix = ch;
           break;
-        }
 
-        // 0 - 9
-        if (ch >= '0' && ch <= '9') {
-          this.currentParam = this.currentParam * 10 + ch.charCodeAt(0) - 48;
-          break;
-        }
+        case csi:
+          // '?', '>', '!'
+          if (ch === '?' || ch === '>' || ch === '!') {
+            this.prefix = ch;
+            break;
+          }
 
-        // '$', '"', ' ', '\''
-        if (ch === '$' || ch === '"' || ch === ' ' || ch === '\'') {
-          this.postfix = ch;
-          break;
-        }
+          // 0 - 9
+          if (ch >= '0' && ch <= '9') {
+            this.currentParam = this.currentParam * 10 + ch.charCodeAt(0) - 48;
+            break;
+          }
 
-        this.params.push(this.currentParam);
-        this.currentParam = 0;
+          // '$', '"', ' ', '\''
+          if (ch === '$' || ch === '"' || ch === ' ' || ch === '\'') {
+            this.postfix = ch;
+            break;
+          }
 
-        // ';'
-        if (ch === ';') break;
+          this.params.push(this.currentParam);
+          this.currentParam = 0;
 
-        this.state = normal;
+          // ';'
+          if (ch === ';') break;
 
-        switch (ch) {
-          // CSI Ps A
-          // Cursor Up Ps Times (default = 1) (CUU).
-          case 'A':
-            this.cursorUp(this.params);
-            break;
+          this.state = normal;
 
-          // CSI Ps B
-          // Cursor Down Ps Times (default = 1) (CUD).
-          case 'B':
-            this.cursorDown(this.params);
-            break;
+          switch (ch) {
+            // CSI Ps A
+            // Cursor Up Ps Times (default = 1) (CUU).
+            case 'A':
+              this.cursorUp(this.params);
+              break;
 
-          // CSI Ps C
-          // Cursor Forward Ps Times (default = 1) (CUF).
-          case 'C':
-            this.cursorForward(this.params);
-            break;
+            // CSI Ps B
+            // Cursor Down Ps Times (default = 1) (CUD).
+            case 'B':
+              this.cursorDown(this.params);
+              break;
 
-          // CSI Ps D
-          // Cursor Backward Ps Times (default = 1) (CUB).
-          case 'D':
-            this.cursorBackward(this.params);
-            break;
+            // CSI Ps C
+            // Cursor Forward Ps Times (default = 1) (CUF).
+            case 'C':
+              this.cursorForward(this.params);
+              break;
 
-          // CSI Ps ; Ps H
-          // Cursor Position [row;column] (default = [1,1]) (CUP).
-          case 'H':
-            this.cursorPos(this.params);
-            break;
+            // CSI Ps D
+            // Cursor Backward Ps Times (default = 1) (CUB).
+            case 'D':
+              this.cursorBackward(this.params);
+              break;
 
-          // CSI Ps J  Erase in Display (ED).
-          case 'J':
-            this.eraseInDisplay(this.params);
-            break;
+            // CSI Ps ; Ps H
+            // Cursor Position [row;column] (default = [1,1]) (CUP).
+            case 'H':
+              this.cursorPos(this.params);
+              break;
 
-          // CSI Ps K  Erase in Line (EL).
-          case 'K':
-            this.eraseInLine(this.params);
-            break;
+            // CSI Ps J  Erase in Display (ED).
+            case 'J':
+              this.eraseInDisplay(this.params);
+              break;
 
-          // CSI Pm m  Character Attributes (SGR).
-          case 'm':
-            if (!this.prefix) {
-              this.charAttributes(this.params);
-            }
-            break;
+            // CSI Ps K  Erase in Line (EL).
+            case 'K':
+              this.eraseInLine(this.params);
+              break;
 
-          // CSI Ps n  Device Status Report (DSR).
-          case 'n':
-            if (!this.prefix) {
-              this.deviceStatus(this.params);
-            }
-            break;
+            // CSI Pm m  Character Attributes (SGR).
+            case 'm':
+              if (!this.prefix) {
+                this.charAttributes(this.params);
+              }
+              break;
 
-            /**
-             * Additions
-             */
+            // CSI Ps n  Device Status Report (DSR).
+            case 'n':
+              if (!this.prefix) {
+                this.deviceStatus(this.params);
+              }
+              break;
 
-          // CSI Ps @
-          // Insert Ps (Blank) Character(s) (default = 1) (ICH).
-          case '@':
-            this.insertChars(this.params);
-            break;
+              /**
+               * Additions
+               */
 
-          // CSI Ps E
-          // Cursor Next Line Ps Times (default = 1) (CNL).
-          case 'E':
-            this.cursorNextLine(this.params);
-            break;
+            // CSI Ps @
+            // Insert Ps (Blank) Character(s) (default = 1) (ICH).
+            case '@':
+              this.insertChars(this.params);
+              break;
 
-          // CSI Ps F
-          // Cursor Preceding Line Ps Times (default = 1) (CNL).
-          case 'F':
-            this.cursorPrecedingLine(this.params);
-            break;
+            // CSI Ps E
+            // Cursor Next Line Ps Times (default = 1) (CNL).
+            case 'E':
+              this.cursorNextLine(this.params);
+              break;
 
-          // CSI Ps G
-          // Cursor Character Absolute  [column] (default = [row,1]) (CHA).
-          case 'G':
-            this.cursorCharAbsolute(this.params);
-            break;
+            // CSI Ps F
+            // Cursor Preceding Line Ps Times (default = 1) (CNL).
+            case 'F':
+              this.cursorPrecedingLine(this.params);
+              break;
 
-          // CSI Ps L
-          // Insert Ps Line(s) (default = 1) (IL).
-          case 'L':
-            this.insertLines(this.params);
-            break;
+            // CSI Ps G
+            // Cursor Character Absolute  [column] (default = [row,1]) (CHA).
+            case 'G':
+              this.cursorCharAbsolute(this.params);
+              break;
 
-          // CSI Ps M
-          // Delete Ps Line(s) (default = 1) (DL).
-          case 'M':
-            this.deleteLines(this.params);
-            break;
+            // CSI Ps L
+            // Insert Ps Line(s) (default = 1) (IL).
+            case 'L':
+              this.insertLines(this.params);
+              break;
 
-          // CSI Ps P
-          // Delete Ps Character(s) (default = 1) (DCH).
-          case 'P':
-            this.deleteChars(this.params);
-            break;
+            // CSI Ps M
+            // Delete Ps Line(s) (default = 1) (DL).
+            case 'M':
+              this.deleteLines(this.params);
+              break;
 
-          // CSI Ps X
-          // Erase Ps Character(s) (default = 1) (ECH).
-          case 'X':
-            this.eraseChars(this.params);
-            break;
+            // CSI Ps P
+            // Delete Ps Character(s) (default = 1) (DCH).
+            case 'P':
+              this.deleteChars(this.params);
+              break;
 
-          // CSI Pm `  Character Position Absolute
-          //   [column] (default = [row,1]) (HPA).
-          case '`':
-            this.charPosAbsolute(this.params);
-            break;
+            // CSI Ps X
+            // Erase Ps Character(s) (default = 1) (ECH).
+            case 'X':
+              this.eraseChars(this.params);
+              break;
 
-          // 141 61 a * HPR -
-          // Horizontal Position Relative
-          case 'a':
-            this.HPositionRelative(this.params);
-            break;
+            // CSI Pm `  Character Position Absolute
+            //   [column] (default = [row,1]) (HPA).
+            case '`':
+              this.charPosAbsolute(this.params);
+              break;
 
-          // CSI P s c
-          // Send Device Attributes (Primary DA).
-          // CSI > P s c
-          // Send Device Attributes (Secondary DA)
-          case 'c':
-            this.sendDeviceAttributes(this.params);
-            break;
+            // 141 61 a * HPR -
+            // Horizontal Position Relative
+            case 'a':
+              this.HPositionRelative(this.params);
+              break;
 
-          // CSI Pm d
-          // Line Position Absolute  [row] (default = [1,column]) (VPA).
-          case 'd':
-            this.linePosAbsolute(this.params);
-            break;
+            // CSI P s c
+            // Send Device Attributes (Primary DA).
+            // CSI > P s c
+            // Send Device Attributes (Secondary DA)
+            case 'c':
+              this.sendDeviceAttributes(this.params);
+              break;
 
-          // 145 65 e * VPR - Vertical Position Relative
-          case 'e':
-            this.VPositionRelative(this.params);
-            break;
+            // CSI Pm d
+            // Line Position Absolute  [row] (default = [1,column]) (VPA).
+            case 'd':
+              this.linePosAbsolute(this.params);
+              break;
 
-          // CSI Ps ; Ps f
-          //   Horizontal and Vertical Position [row;column] (default =
-          //   [1,1]) (HVP).
-          case 'f':
-            this.HVPosition(this.params);
-            break;
+            // 145 65 e * VPR - Vertical Position Relative
+            case 'e':
+              this.VPositionRelative(this.params);
+              break;
 
-          // CSI Pm h  Set Mode (SM).
-          // CSI ? Pm h - mouse escape codes, cursor escape codes
-          case 'h':
-            this.setMode(this.params);
-            break;
+            // CSI Ps ; Ps f
+            //   Horizontal and Vertical Position [row;column] (default =
+            //   [1,1]) (HVP).
+            case 'f':
+              this.HVPosition(this.params);
+              break;
 
-          // CSI Pm l  Reset Mode (RM).
-          // CSI ? Pm l
-          case 'l':
-            this.resetMode(this.params);
-            break;
+            // CSI Pm h  Set Mode (SM).
+            // CSI ? Pm h - mouse escape codes, cursor escape codes
+            case 'h':
+              this.setMode(this.params);
+              break;
 
-          // CSI Ps ; Ps r
-          //   Set Scrolling Region [top;bottom] (default = full size of win-
-          //   dow) (DECSTBM).
-          // CSI ? Pm r
-          case 'r':
-            this.setScrollRegion(this.params);
-            break;
+            // CSI Pm l  Reset Mode (RM).
+            // CSI ? Pm l
+            case 'l':
+              this.resetMode(this.params);
+              break;
 
-          // CSI s
-          //   Save cursor (ANSI.SYS).
-          case 's':
-            this.saveCursor(this.params);
-            break;
+            // CSI Ps ; Ps r
+            //   Set Scrolling Region [top;bottom] (default = full size of win-
+            //   dow) (DECSTBM).
+            // CSI ? Pm r
+            case 'r':
+              this.setScrollRegion(this.params);
+              break;
 
-          // CSI u
-          //   Restore cursor (ANSI.SYS).
-          case 'u':
-            this.restoreCursor(this.params);
-            break;
+            // CSI s
+            //   Save cursor (ANSI.SYS).
+            case 's':
+              this.saveCursor(this.params);
+              break;
 
-            /**
-             * Lesser Used
-             */
+            // CSI u
+            //   Restore cursor (ANSI.SYS).
+            case 'u':
+              this.restoreCursor(this.params);
+              break;
 
-          // CSI Ps I
-          // Cursor Forward Tabulation Ps tab stops (default = 1) (CHT).
-          case 'I':
-            this.cursorForwardTab(this.params);
-            break;
+              /**
+               * Lesser Used
+               */
 
-          // CSI Ps S  Scroll up Ps lines (default = 1) (SU).
-          case 'S':
-            this.scrollUp(this.params);
-            break;
+            // CSI Ps I
+            // Cursor Forward Tabulation Ps tab stops (default = 1) (CHT).
+            case 'I':
+              this.cursorForwardTab(this.params);
+              break;
 
-          // CSI Ps T  Scroll down Ps lines (default = 1) (SD).
-          // CSI Ps ; Ps ; Ps ; Ps ; Ps T
-          // CSI > Ps; Ps T
-          case 'T':
-            // if (this.prefix === '>') {
-            //   this.resetTitleModes(this.params);
-            //   break;
-            // }
-            // if (this.params.length > 2) {
-            //   this.initMouseTracking(this.params);
-            //   break;
-            // }
-            if (this.params.length < 2 && !this.prefix) {
-              this.scrollDown(this.params);
-            }
-            break;
+            // CSI Ps S  Scroll up Ps lines (default = 1) (SU).
+            case 'S':
+              this.scrollUp(this.params);
+              break;
 
-          // CSI Ps Z
-          // Cursor Backward Tabulation Ps tab stops (default = 1) (CBT).
-          case 'Z':
-            this.cursorBackwardTab(this.params);
-            break;
+            // CSI Ps T  Scroll down Ps lines (default = 1) (SD).
+            // CSI Ps ; Ps ; Ps ; Ps ; Ps T
+            // CSI > Ps; Ps T
+            case 'T':
+              // if (this.prefix === '>') {
+              //   this.resetTitleModes(this.params);
+              //   break;
+              // }
+              // if (this.params.length > 2) {
+              //   this.initMouseTracking(this.params);
+              //   break;
+              // }
+              if (this.params.length < 2 && !this.prefix) {
+                this.scrollDown(this.params);
+              }
+              break;
 
-          // CSI Ps b  Repeat the preceding graphic character Ps times (REP).
-          case 'b':
-            this.repeatPrecedingCharacter(this.params);
-            break;
+            // CSI Ps Z
+            // Cursor Backward Tabulation Ps tab stops (default = 1) (CBT).
+            case 'Z':
+              this.cursorBackwardTab(this.params);
+              break;
 
-          // CSI Ps g  Tab Clear (TBC).
-          case 'g':
-            this.tabClear(this.params);
-            break;
+            // CSI Ps b  Repeat the preceding graphic character Ps times (REP).
+            case 'b':
+              this.repeatPrecedingCharacter(this.params);
+              break;
+
+            // CSI Ps g  Tab Clear (TBC).
+            case 'g':
+              this.tabClear(this.params);
+              break;
 
             // CSI Pm i  Media Copy (MC).
             // CSI ? Pm i
@@ -2114,272 +2242,282 @@ Terminal.prototype.write = function(data) {
             // CSI ? Ps$ p
             //   Request DEC private mode (DECRQM).
             // CSI Ps ; Ps " p
-          case 'p':
-            switch (this.prefix) {
-                // case '>':
-                //   this.setPointerMode(this.params);
-                //   break;
-              case '!':
-                this.softReset(this.params);
-                break;
-                // case '?':
-                //   if (this.postfix === '$') {
-                //     this.requestPrivateMode(this.params);
-                //   }
-                //   break;
-                // default:
-                //   if (this.postfix === '"') {
-                //     this.setConformanceLevel(this.params);
-                //   } else if (this.postfix === '$') {
-                //     this.requestAnsiMode(this.params);
-                //   }
-                //   break;
-            }
-            break;
-
-            // CSI Ps q  Load LEDs (DECLL).
-            // CSI Ps SP q
-            // CSI Ps " q
-            // case 'q':
-            //   if (this.postfix === ' ') {
-            //     this.setCursorStyle(this.params);
-            //     break;
-            //   }
-            //   if (this.postfix === '"') {
-            //     this.setCharProtectionAttr(this.params);
-            //     break;
-            //   }
-            //   this.loadLEDs(this.params);
-            //   break;
-
-            // CSI Ps ; Ps r
-            //   Set Scrolling Region [top;bottom] (default = full size of win-
-            //   dow) (DECSTBM).
-            // CSI ? Pm r
-            // CSI Pt; Pl; Pb; Pr; Ps$ r
-            // case 'r': // duplicate
-            //   if (this.prefix === '?') {
-            //     this.restorePrivateValues(this.params);
-            //   } else if (this.postfix === '$') {
-            //     this.setAttrInRectangle(this.params);
-            //   } else {
-            //     this.setScrollRegion(this.params);
-            //   }
-            //   break;
-
-            // CSI s     Save cursor (ANSI.SYS).
-            // CSI ? Pm s
-            // case 's': // duplicate
-            //   if (this.prefix === '?') {
-            //     this.savePrivateValues(this.params);
-            //   } else {
-            //     this.saveCursor(this.params);
-            //   }
-            //   break;
-
-            // CSI Ps ; Ps ; Ps t
-            // CSI Pt; Pl; Pb; Pr; Ps$ t
-            // CSI > Ps; Ps t
-            // CSI Ps SP t
-            // case 't':
-            //   if (this.postfix === '$') {
-            //     this.reverseAttrInRectangle(this.params);
-            //   } else if (this.postfix === ' ') {
-            //     this.setWarningBellVolume(this.params);
-            //   } else {
-            //     if (this.prefix === '>') {
-            //       this.setTitleModeFeature(this.params);
-            //     } else {
-            //       this.manipulateWindow(this.params);
-            //     }
-            //   }
-            //   break;
-
-            // CSI u     Restore cursor (ANSI.SYS).
-            // CSI Ps SP u
-            // case 'u': // duplicate
-            //   if (this.postfix === ' ') {
-            //     this.setMarginBellVolume(this.params);
-            //   } else {
-            //     this.restoreCursor(this.params);
-            //   }
-            //   break;
-
-            // CSI Pt; Pl; Pb; Pr; Pp; Pt; Pl; Pp$ v
-            // case 'v':
-            //   if (this.postfix === '$') {
-            //     this.copyRectagle(this.params);
-            //   }
-            //   break;
-
-            // CSI Pt ; Pl ; Pb ; Pr ' w
-            // case 'w':
-            //   if (this.postfix === '\'') {
-            //     this.enableFilterRectangle(this.params);
-            //   }
-            //   break;
-
-            // CSI Ps x  Request Terminal Parameters (DECREQTPARM).
-            // CSI Ps x  Select Attribute Change Extent (DECSACE).
-            // CSI Pc; Pt; Pl; Pb; Pr$ x
-            // case 'x':
-            //   if (this.postfix === '$') {
-            //     this.fillRectangle(this.params);
-            //   } else {
-            //     this.requestParameters(this.params);
-            //     //this.__(this.params);
-            //   }
-            //   break;
-
-            // CSI Ps ; Pu ' z
-            // CSI Pt; Pl; Pb; Pr$ z
-            // case 'z':
-            //   if (this.postfix === '\'') {
-            //     this.enableLocatorReporting(this.params);
-            //   } else if (this.postfix === '$') {
-            //     this.eraseRectangle(this.params);
-            //   }
-            //   break;
-
-            // CSI Pm ' {
-            // CSI Pt; Pl; Pb; Pr$ {
-            // case '{':
-            //   if (this.postfix === '\'') {
-            //     this.setLocatorEvents(this.params);
-            //   } else if (this.postfix === '$') {
-            //     this.selectiveEraseRectangle(this.params);
-            //   }
-            //   break;
-
-            // CSI Ps ' |
-            // case '|':
-            //   if (this.postfix === '\'') {
-            //     this.requestLocatorPosition(this.params);
-            //   }
-            //   break;
-
-            // CSI P m SP }
-            // Insert P s Column(s) (default = 1) (DECIC), VT420 and up.
-            // case '}':
-            //   if (this.postfix === ' ') {
-            //     this.insertColumns(this.params);
-            //   }
-            //   break;
-
-            // CSI P m SP ~
-            // Delete P s Column(s) (default = 1) (DECDC), VT420 and up
-            // case '~':
-            //   if (this.postfix === ' ') {
-            //     this.deleteColumns(this.params);
-            //   }
-            //   break;
-
-          default:
-            this.error('Unknown CSI code: %s.', ch);
-            break;
-        }
-
-        this.prefix = '';
-        this.postfix = '';
-        break;
-
-      case dcs:
-        if (ch === '\x1b' || ch === '\x07') {
-          if (ch === '\x1b') i++;
-
-          switch (this.prefix) {
-            // User-Defined Keys (DECUDK).
-            case '':
+            case 'p':
+              switch (this.prefix) {
+                  // case '>':
+                  //   this.setPointerMode(this.params);
+                  //   break;
+                case '!':
+                  this.softReset(this.params);
+                  break;
+                  // case '?':
+                  //   if (this.postfix === '$') {
+                  //     this.requestPrivateMode(this.params);
+                  //   }
+                  //   break;
+                  // default:
+                  //   if (this.postfix === '"') {
+                  //     this.setConformanceLevel(this.params);
+                  //   } else if (this.postfix === '$') {
+                  //     this.requestAnsiMode(this.params);
+                  //   }
+                  //   break;
+              }
               break;
 
-            // Request Status String (DECRQSS).
-            // test: echo -e '\eP$q"p\e\\'
-            case '$q':
-              var pt = this.currentParam
-              , valid = false;
+              // CSI Ps q  Load LEDs (DECLL).
+              // CSI Ps SP q
+              // CSI Ps " q
+              // case 'q':
+              //   if (this.postfix === ' ') {
+              //     this.setCursorStyle(this.params);
+              //     break;
+              //   }
+              //   if (this.postfix === '"') {
+              //     this.setCharProtectionAttr(this.params);
+              //     break;
+              //   }
+              //   this.loadLEDs(this.params);
+              //   break;
+
+              // CSI Ps ; Ps r
+              //   Set Scrolling Region [top;bottom] (default = full size of win-
+              //   dow) (DECSTBM).
+              // CSI ? Pm r
+              // CSI Pt; Pl; Pb; Pr; Ps$ r
+              // case 'r': // duplicate
+              //   if (this.prefix === '?') {
+              //     this.restorePrivateValues(this.params);
+              //   } else if (this.postfix === '$') {
+              //     this.setAttrInRectangle(this.params);
+              //   } else {
+              //     this.setScrollRegion(this.params);
+              //   }
+              //   break;
+
+              // CSI s     Save cursor (ANSI.SYS).
+              // CSI ? Pm s
+              // case 's': // duplicate
+              //   if (this.prefix === '?') {
+              //     this.savePrivateValues(this.params);
+              //   } else {
+              //     this.saveCursor(this.params);
+              //   }
+              //   break;
+
+              // CSI Ps ; Ps ; Ps t
+              // CSI Pt; Pl; Pb; Pr; Ps$ t
+              // CSI > Ps; Ps t
+              // CSI Ps SP t
+              // case 't':
+              //   if (this.postfix === '$') {
+              //     this.reverseAttrInRectangle(this.params);
+              //   } else if (this.postfix === ' ') {
+              //     this.setWarningBellVolume(this.params);
+              //   } else {
+              //     if (this.prefix === '>') {
+              //       this.setTitleModeFeature(this.params);
+              //     } else {
+              //       this.manipulateWindow(this.params);
+              //     }
+              //   }
+              //   break;
+
+              // CSI u     Restore cursor (ANSI.SYS).
+              // CSI Ps SP u
+              // case 'u': // duplicate
+              //   if (this.postfix === ' ') {
+              //     this.setMarginBellVolume(this.params);
+              //   } else {
+              //     this.restoreCursor(this.params);
+              //   }
+              //   break;
+
+              // CSI Pt; Pl; Pb; Pr; Pp; Pt; Pl; Pp$ v
+              // case 'v':
+              //   if (this.postfix === '$') {
+              //     this.copyRectagle(this.params);
+              //   }
+              //   break;
+
+              // CSI Pt ; Pl ; Pb ; Pr ' w
+              // case 'w':
+              //   if (this.postfix === '\'') {
+              //     this.enableFilterRectangle(this.params);
+              //   }
+              //   break;
+
+              // CSI Ps x  Request Terminal Parameters (DECREQTPARM).
+              // CSI Ps x  Select Attribute Change Extent (DECSACE).
+              // CSI Pc; Pt; Pl; Pb; Pr$ x
+              // case 'x':
+              //   if (this.postfix === '$') {
+              //     this.fillRectangle(this.params);
+              //   } else {
+              //     this.requestParameters(this.params);
+              //     //this.__(this.params);
+              //   }
+              //   break;
+
+              // CSI Ps ; Pu ' z
+              // CSI Pt; Pl; Pb; Pr$ z
+              // case 'z':
+              //   if (this.postfix === '\'') {
+              //     this.enableLocatorReporting(this.params);
+              //   } else if (this.postfix === '$') {
+              //     this.eraseRectangle(this.params);
+              //   }
+              //   break;
+
+              // CSI Pm ' {
+              // CSI Pt; Pl; Pb; Pr$ {
+              // case '{':
+              //   if (this.postfix === '\'') {
+              //     this.setLocatorEvents(this.params);
+              //   } else if (this.postfix === '$') {
+              //     this.selectiveEraseRectangle(this.params);
+              //   }
+              //   break;
+
+              // CSI Ps ' |
+              // case '|':
+              //   if (this.postfix === '\'') {
+              //     this.requestLocatorPosition(this.params);
+              //   }
+              //   break;
+
+              // CSI P m SP }
+              // Insert P s Column(s) (default = 1) (DECIC), VT420 and up.
+              // case '}':
+              //   if (this.postfix === ' ') {
+              //     this.insertColumns(this.params);
+              //   }
+              //   break;
+
+              // CSI P m SP ~
+              // Delete P s Column(s) (default = 1) (DECDC), VT420 and up
+              // case '~':
+              //   if (this.postfix === ' ') {
+              //     this.deleteColumns(this.params);
+              //   }
+              //   break;
 
-              switch (pt) {
-                // DECSCA
-                case '"q':
-                  pt = '0"q';
-                  break;
+            default:
+              this.error('Unknown CSI code: %s.', ch);
+              break;
+          }
 
-                // DECSCL
-                case '"p':
-                  pt = '61"p';
-                  break;
+          this.prefix = '';
+          this.postfix = '';
+          break;
 
-                // DECSTBM
-                case 'r':
-                  pt = ''
-                    + (this.scrollTop + 1)
-                    + ';'
-                    + (this.scrollBottom + 1)
-                    + 'r';
-                  break;
+        case dcs:
+          if (ch === C0.ESC || ch === C0.BEL) {
+            if (ch === C0.ESC) i++;
 
-                // SGR
-                case 'm':
-                  pt = '0m';
-                  break;
+            switch (this.prefix) {
+              // User-Defined Keys (DECUDK).
+              case '':
+                break;
 
-                default:
-                  this.error('Unknown DCS Pt: %s.', pt);
-                  pt = '';
-                  break;
-              }
+              // Request Status String (DECRQSS).
+              // test: echo -e '\eP$q"p\e\\'
+              case '$q':
+                var pt = this.currentParam
+                , valid = false;
+
+                switch (pt) {
+                  // DECSCA
+                  case '"q':
+                    pt = '0"q';
+                    break;
+
+                  // DECSCL
+                  case '"p':
+                    pt = '61"p';
+                    break;
+
+                  // DECSTBM
+                  case 'r':
+                    pt = ''
+                      + (this.scrollTop + 1)
+                      + ';'
+                      + (this.scrollBottom + 1)
+                      + 'r';
+                    break;
+
+                  // SGR
+                  case 'm':
+                    pt = '0m';
+                    break;
+
+                  default:
+                    this.error('Unknown DCS Pt: %s.', pt);
+                    pt = '';
+                    break;
+                }
 
-              this.send('\x1bP' + +valid + '$r' + pt + '\x1b\\');
-              break;
+                this.send(C0.ESC + 'P' + +valid + '$r' + pt + C0.ESC + '\\');
+                break;
 
-            // Set Termcap/Terminfo Data (xterm, experimental).
-            case '+p':
-              break;
+              // Set Termcap/Terminfo Data (xterm, experimental).
+              case '+p':
+                break;
 
-            // Request Termcap/Terminfo String (xterm, experimental)
-            // Regular xterm does not even respond to this sequence.
-            // This can cause a small glitch in vim.
-            // test: echo -ne '\eP+q6b64\e\\'
-            case '+q':
-              var pt = this.currentParam
-              , valid = false;
+              // Request Termcap/Terminfo String (xterm, experimental)
+              // Regular xterm does not even respond to this sequence.
+              // This can cause a small glitch in vim.
+              // test: echo -ne '\eP+q6b64\e\\'
+              case '+q':
+                var pt = this.currentParam
+                , valid = false;
 
-              this.send('\x1bP' + +valid + '+r' + pt + '\x1b\\');
-              break;
+                this.send(C0.ESC + 'P' + +valid + '+r' + pt + C0.ESC + '\\');
+                break;
 
-            default:
-              this.error('Unknown DCS prefix: %s.', this.prefix);
-              break;
-          }
+              default:
+                this.error('Unknown DCS prefix: %s.', this.prefix);
+                break;
+            }
 
-          this.currentParam = 0;
-          this.prefix = '';
-          this.state = normal;
-        } else if (!this.currentParam) {
-          if (!this.prefix && ch !== '$' && ch !== '+') {
-            this.currentParam = ch;
-          } else if (this.prefix.length === 2) {
-            this.currentParam = ch;
+            this.currentParam = 0;
+            this.prefix = '';
+            this.state = normal;
+          } else if (!this.currentParam) {
+            if (!this.prefix && ch !== '$' && ch !== '+') {
+              this.currentParam = ch;
+            } else if (this.prefix.length === 2) {
+              this.currentParam = ch;
+            } else {
+              this.prefix += ch;
+            }
           } else {
-            this.prefix += ch;
+            this.currentParam += ch;
           }
-        } else {
-          this.currentParam += ch;
-        }
-        break;
+          break;
 
-      case ignore:
-        // For PM and APC.
-        if (ch === '\x1b' || ch === '\x07') {
-          if (ch === '\x1b') i++;
-          this.state = normal;
-        }
-        break;
+        case ignore:
+          // For PM and APC.
+          if (ch === C0.ESC || ch === C0.BEL) {
+            if (ch === C0.ESC) i++;
+            this.state = normal;
+          }
+          break;
+      }
     }
-  }
 
-  this.updateRange(this.y);
-  this.refresh(this.refreshStart, this.refreshEnd);
+    this.updateRange(this.y);
+    this.queueRefresh(this.refreshStart, this.refreshEnd);
+  }
+  if (this.writeBuffer.length > 0) {
+    // Allow renderer to catch up before processing the next batch
+    var self = this;
+    setTimeout(function () {
+      self.innerWrite();
+    }, 0);
+  } else {
+    this.writeInProgress = false;
+  }
 };
 
 /**
@@ -2423,6 +2561,12 @@ Terminal.prototype.keyDown = function(ev) {
   var self = this;
   var result = this.evaluateKeyEscapeSequence(ev);
 
+  if (result.key === C0.DC3) { // XOFF
+    this.writeStopped = true;
+  } else if (result.key === C0.DC1) { // XON
+    this.writeStopped = false;
+  }
+
   if (result.scrollDisp) {
     this.scrollDisp(result.scrollDisp);
     return this.cancel(ev, true);
@@ -2471,90 +2615,90 @@ Terminal.prototype.evaluateKeyEscapeSequence = function(ev) {
     case 8:
       // backspace
       if (ev.shiftKey) {
-        result.key = '\x08'; // ^H
+        result.key = C0.BS; // ^H
         break;
       }
-      result.key = '\x7f'; // ^?
+      result.key = C0.DEL; // ^?
       break;
     case 9:
       // tab
       if (ev.shiftKey) {
-        result.key = '\x1b[Z';
+        result.key = C0.ESC + '[Z';
         break;
       }
-      result.key = '\t';
+      result.key = C0.HT;
       result.cancel = true;
       break;
     case 13:
       // return/enter
-      result.key = '\r';
+      result.key = C0.CR;
       result.cancel = true;
       break;
     case 27:
       // escape
-      result.key = '\x1b';
+      result.key = C0.ESC;
       result.cancel = true;
       break;
     case 37:
       // left-arrow
       if (modifiers) {
-        result.key = '\x1b[1;' + (modifiers + 1) + 'D';
+        result.key = C0.ESC + '[1;' + (modifiers + 1) + 'D';
         // HACK: Make Alt + left-arrow behave like Ctrl + left-arrow: move one word backwards
         // http://unix.stackexchange.com/a/108106
         // macOS uses different escape sequences than linux
-        if (result.key == '\x1b[1;3D') {
-          result.key = (this.browser.isMac) ? '\x1bb' : '\x1b[1;5D';
+        if (result.key == C0.ESC + '[1;3D') {
+          result.key = (this.browser.isMac) ? C0.ESC + 'b' : C0.ESC + '[1;5D';
         }
       } else if (this.applicationCursor) {
-        result.key = '\x1bOD';
+        result.key = C0.ESC + 'OD';
       } else {
-        result.key = '\x1b[D';
+        result.key = C0.ESC + '[D';
       }
       break;
     case 39:
       // right-arrow
       if (modifiers) {
-        result.key = '\x1b[1;' + (modifiers + 1) + 'C';
+        result.key = C0.ESC + '[1;' + (modifiers + 1) + 'C';
         // HACK: Make Alt + right-arrow behave like Ctrl + right-arrow: move one word forward
         // http://unix.stackexchange.com/a/108106
         // macOS uses different escape sequences than linux
-        if (result.key == '\x1b[1;3C') {
-          result.key = (this.browser.isMac) ? '\x1bf' : '\x1b[1;5C';
+        if (result.key == C0.ESC + '[1;3C') {
+          result.key = (this.browser.isMac) ? C0.ESC + 'f' : C0.ESC + '[1;5C';
         }
       } else if (this.applicationCursor) {
-        result.key = '\x1bOC';
+        result.key = C0.ESC + 'OC';
       } else {
-        result.key = '\x1b[C';
+        result.key = C0.ESC + '[C';
       }
       break;
     case 38:
       // up-arrow
       if (modifiers) {
-        result.key = '\x1b[1;' + (modifiers + 1) + 'A';
+        result.key = C0.ESC + '[1;' + (modifiers + 1) + 'A';
         // HACK: Make Alt + up-arrow behave like Ctrl + up-arrow
         // http://unix.stackexchange.com/a/108106
-        if (result.key == '\x1b[1;3A') {
-          result.key = '\x1b[1;5A';
+        if (result.key == C0.ESC + '[1;3A') {
+          result.key = C0.ESC + '[1;5A';
         }
       } else if (this.applicationCursor) {
-        result.key = '\x1bOA';
+        result.key = C0.ESC + 'OA';
       } else {
-        result.key = '\x1b[A';
+        result.key = C0.ESC + '[A';
       }
       break;
     case 40:
       // down-arrow
       if (modifiers) {
-        result.key = '\x1b[1;' + (modifiers + 1) + 'B';
+        result.key = C0.ESC + '[1;' + (modifiers + 1) + 'B';
         // HACK: Make Alt + down-arrow behave like Ctrl + down-arrow
         // http://unix.stackexchange.com/a/108106
-        if (result.key == '\x1b[1;3B') {
-          result.key = '\x1b[1;5B';
+        if (result.key == C0.ESC + '[1;3B') {
+          result.key = C0.ESC + '[1;5B';
         }
       } else if (this.applicationCursor) {
-        result.key = '\x1bOB';
+        result.key = C0.ESC + 'OB';
       } else {
-        result.key = '\x1b[B';
+        result.key = C0.ESC + '[B';
       }
       break;
     case 45:
@@ -2562,41 +2706,41 @@ Terminal.prototype.evaluateKeyEscapeSequence = function(ev) {
       if (!ev.shiftKey && !ev.ctrlKey) {
         // <Ctrl> or <Shift> + <Insert> are used to
         // copy-paste on some systems.
-        result.key = '\x1b[2~';
+        result.key = C0.ESC + '[2~';
       }
       break;
     case 46:
       // delete
       if (modifiers) {
-        result.key = '\x1b[3;' + (modifiers + 1) + '~';
+        result.key = C0.ESC + '[3;' + (modifiers + 1) + '~';
       } else {
-        result.key = '\x1b[3~';
+        result.key = C0.ESC + '[3~';
       }
       break;
     case 36:
       // home
       if (modifiers)
-        result.key = '\x1b[1;' + (modifiers + 1) + 'H';
+        result.key = C0.ESC + '[1;' + (modifiers + 1) + 'H';
       else if (this.applicationCursor)
-        result.key = '\x1bOH';
+        result.key = C0.ESC + 'OH';
       else
-        result.key = '\x1b[H';
+        result.key = C0.ESC + '[H';
       break;
     case 35:
       // end
       if (modifiers)
-        result.key = '\x1b[1;' + (modifiers + 1) + 'F';
+        result.key = C0.ESC + '[1;' + (modifiers + 1) + 'F';
       else if (this.applicationCursor)
-        result.key = '\x1bOF';
+        result.key = C0.ESC + 'OF';
       else
-        result.key = '\x1b[F';
+        result.key = C0.ESC + '[F';
       break;
     case 33:
       // page up
       if (ev.shiftKey) {
         result.scrollDisp = -(this.rows - 1);
       } else {
-        result.key = '\x1b[5~';
+        result.key = C0.ESC + '[5~';
       }
       break;
     case 34:
@@ -2604,92 +2748,92 @@ Terminal.prototype.evaluateKeyEscapeSequence = function(ev) {
       if (ev.shiftKey) {
         result.scrollDisp = this.rows - 1;
       } else {
-        result.key = '\x1b[6~';
+        result.key = C0.ESC + '[6~';
       }
       break;
     case 112:
       // F1-F12
       if (modifiers) {
-        result.key = '\x1b[1;' + (modifiers + 1) + 'P';
+        result.key = C0.ESC + '[1;' + (modifiers + 1) + 'P';
       } else {
-        result.key = '\x1bOP';
+        result.key = C0.ESC + 'OP';
       }
       break;
     case 113:
       if (modifiers) {
-        result.key = '\x1b[1;' + (modifiers + 1) + 'Q';
+        result.key = C0.ESC + '[1;' + (modifiers + 1) + 'Q';
       } else {
-        result.key = '\x1bOQ';
+        result.key = C0.ESC + 'OQ';
       }
       break;
     case 114:
       if (modifiers) {
-        result.key = '\x1b[1;' + (modifiers + 1) + 'R';
+        result.key = C0.ESC + '[1;' + (modifiers + 1) + 'R';
       } else {
-        result.key = '\x1bOR';
+        result.key = C0.ESC + 'OR';
       }
       break;
     case 115:
       if (modifiers) {
-        result.key = '\x1b[1;' + (modifiers + 1) + 'S';
+        result.key = C0.ESC + '[1;' + (modifiers + 1) + 'S';
       } else {
-        result.key = '\x1bOS';
+        result.key = C0.ESC + 'OS';
       }
       break;
     case 116:
       if (modifiers) {
-        result.key = '\x1b[15;' + (modifiers + 1) + '~';
+        result.key = C0.ESC + '[15;' + (modifiers + 1) + '~';
       } else {
-        result.key = '\x1b[15~';
+        result.key = C0.ESC + '[15~';
       }
       break;
     case 117:
       if (modifiers) {
-        result.key = '\x1b[17;' + (modifiers + 1) + '~';
+        result.key = C0.ESC + '[17;' + (modifiers + 1) + '~';
       } else {
-        result.key = '\x1b[17~';
+        result.key = C0.ESC + '[17~';
       }
       break;
     case 118:
       if (modifiers) {
-        result.key = '\x1b[18;' + (modifiers + 1) + '~';
+        result.key = C0.ESC + '[18;' + (modifiers + 1) + '~';
       } else {
-        result.key = '\x1b[18~';
+        result.key = C0.ESC + '[18~';
       }
       break;
     case 119:
       if (modifiers) {
-        result.key = '\x1b[19;' + (modifiers + 1) + '~';
+        result.key = C0.ESC + '[19;' + (modifiers + 1) + '~';
       } else {
-        result.key = '\x1b[19~';
+        result.key = C0.ESC + '[19~';
       }
       break;
     case 120:
       if (modifiers) {
-        result.key = '\x1b[20;' + (modifiers + 1) + '~';
+        result.key = C0.ESC + '[20;' + (modifiers + 1) + '~';
       } else {
-        result.key = '\x1b[20~';
+        result.key = C0.ESC + '[20~';
       }
       break;
     case 121:
       if (modifiers) {
-        result.key = '\x1b[21;' + (modifiers + 1) + '~';
+        result.key = C0.ESC + '[21;' + (modifiers + 1) + '~';
       } else {
-        result.key = '\x1b[21~';
+        result.key = C0.ESC + '[21~';
       }
       break;
     case 122:
       if (modifiers) {
-        result.key = '\x1b[23;' + (modifiers + 1) + '~';
+        result.key = C0.ESC + '[23;' + (modifiers + 1) + '~';
       } else {
-        result.key = '\x1b[23~';
+        result.key = C0.ESC + '[23~';
       }
       break;
     case 123:
       if (modifiers) {
-        result.key = '\x1b[24;' + (modifiers + 1) + '~';
+        result.key = C0.ESC + '[24;' + (modifiers + 1) + '~';
       } else {
-        result.key = '\x1b[24~';
+        result.key = C0.ESC + '[24~';
       }
       break;
     default:
@@ -2719,15 +2863,16 @@ Terminal.prototype.evaluateKeyEscapeSequence = function(ev) {
       } else if (!this.browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) {
         // On Mac this is a third level shift. Use <Esc> instead.
         if (ev.keyCode >= 65 && ev.keyCode <= 90) {
-          result.key = '\x1b' + String.fromCharCode(ev.keyCode + 32);
+          result.key = C0.ESC + String.fromCharCode(ev.keyCode + 32);
         } else if (ev.keyCode === 192) {
-          result.key = '\x1b`';
+          result.key = C0.ESC + '`';
         } else if (ev.keyCode >= 48 && ev.keyCode <= 57) {
-          result.key = '\x1b' + (ev.keyCode - 48);
+          result.key = C0.ESC + (ev.keyCode - 48);
         }
       }
       break;
   }
+
   return result;
 };
 
@@ -2945,7 +3090,9 @@ Terminal.prototype.resize = function(x, y) {
   this.scrollTop = 0;
   this.scrollBottom = y - 1;
 
-  this.refresh(0, this.rows - 1);
+  this.charMeasure.measure();
+
+  this.queueRefresh(0, this.rows - 1);
 
   this.normal = null;
 
@@ -3074,7 +3221,7 @@ Terminal.prototype.clear = function() {
   for (var i = 1; i < this.rows; i++) {
     this.lines.push(this.blankLine());
   }
-  this.refresh(0, this.rows - 1);
+  this.queueRefresh(0, this.rows - 1);
   this.emit('scroll', this.ydisp);
 };
 
@@ -3134,6 +3281,11 @@ Terminal.prototype.is = function(term) {
  * @param {string} data The data to populate in the event.
  */
 Terminal.prototype.handler = function(data) {
+  // Prevents all events to pty process if stdin is disabled
+  if (this.options.disableStdin) {
+    return;
+  }
+
   // Input is being sent to the terminal, the terminal should focus the prompt.
   if (this.ybase !== this.ydisp) {
     this.scrollToBottom();
@@ -3205,7 +3357,7 @@ Terminal.prototype.reset = function() {
   var customKeydownHandler = this.customKeydownHandler;
   Terminal.call(this, this.options);
   this.customKeydownHandler = customKeydownHandler;
-  this.refresh(0, this.rows - 1);
+  this.queueRefresh(0, this.rows - 1);
   this.viewport.syncScrollArea();
 };
 
@@ -3583,11 +3735,11 @@ Terminal.prototype.deviceStatus = function(params) {
     switch (params[0]) {
       case 5:
         // status report
-        this.send('\x1b[0n');
+        this.send(C0.ESC + '[0n');
         break;
       case 6:
         // cursor position
-        this.send('\x1b['
+        this.send(C0.ESC + '['
                   + (this.y + 1)
                   + ';'
                   + (this.x + 1)
@@ -3600,7 +3752,7 @@ Terminal.prototype.deviceStatus = function(params) {
     switch (params[0]) {
       case 6:
         // cursor position
-        this.send('\x1b[?'
+        this.send(C0.ESC + '[?'
                   + (this.y + 1)
                   + ';'
                   + (this.x + 1)
@@ -3608,19 +3760,19 @@ Terminal.prototype.deviceStatus = function(params) {
         break;
       case 15:
         // no printer
-        // this.send('\x1b[?11n');
+        // this.send(C0.ESC + '[?11n');
         break;
       case 25:
         // dont support user defined keys
-        // this.send('\x1b[?21n');
+        // this.send(C0.ESC + '[?21n');
         break;
       case 26:
         // north american keyboard
-        // this.send('\x1b[?27;1;0;0n');
+        // this.send(C0.ESC + '[?27;1;0;0n');
         break;
       case 53:
         // no dec locator/mouse
-        // this.send('\x1b[?50n');
+        // this.send(C0.ESC + '[?50n');
         break;
     }
   }
@@ -3871,24 +4023,24 @@ Terminal.prototype.sendDeviceAttributes = function(params) {
     if (this.is('xterm')
         || this.is('rxvt-unicode')
         || this.is('screen')) {
-      this.send('\x1b[?1;2c');
+      this.send(C0.ESC + '[?1;2c');
     } else if (this.is('linux')) {
-      this.send('\x1b[?6c');
+      this.send(C0.ESC + '[?6c');
     }
   } else if (this.prefix === '>') {
     // xterm and urxvt
     // seem to spit this
     // out around ~370 times (?).
     if (this.is('xterm')) {
-      this.send('\x1b[>0;276;0c');
+      this.send(C0.ESC + '[>0;276;0c');
     } else if (this.is('rxvt-unicode')) {
-      this.send('\x1b[>85;95;0c');
+      this.send(C0.ESC + '[>85;95;0c');
     } else if (this.is('linux')) {
       // not supported by linux console.
       // linux console echoes parameters.
       this.send(params[0] + 'c');
     } else if (this.is('screen')) {
-      this.send('\x1b[>83;40003;0c');
+      this.send(C0.ESC + '[>83;40003;0c');
     }
   }
 };
@@ -4324,7 +4476,7 @@ Terminal.prototype.resetMode = function(params) {
           //   this.x = this.savedX;
           //   this.y = this.savedY;
           // }
-          this.refresh(0, this.rows - 1);
+          this.queueRefresh(0, this.rows - 1);
           this.viewport.syncScrollArea();
           this.showCursor();
         }
index f4a5a1b2a202eeb01629541b09d0b7700dede4af..f5a7d66c1ab7ffa1d87fc57f5113300570d832f4 100644 (file)
@@ -7,13 +7,17 @@
     "outDir": "lib",
     "sourceMap": true
   },
+  "include": [
+    "src/**/*"
+  ],
   "exclude": [
-    "addons",
+    "src/addons/**/*",
     "build",
     "demo",
     "dist",
     "out",
     "test",
-    "node_modules"
+    "node_modules",
+    "docs"
   ]
 }