[Pkg-javascript-commits] [node-xterm] 01/04: New upstream version 2.7.0+ds1

Ghislain Vaillant ghisvail-guest at moszumanska.debian.org
Tue Jun 6 18:30:36 UTC 2017


This is an automated email from the git hooks/post-receive script.

ghisvail-guest pushed a commit to branch master
in repository node-xterm.

commit c6bedee9147cfef12abf8778da568ac9663de220
Author: Ghislain Antony Vaillant <ghisvail at gmail.com>
Date:   Tue Jun 6 19:14:02 2017 +0100

    New upstream version 2.7.0+ds1
---
 AUTHORS                                |   5 +
 README.md                              |   4 +
 bower.json                             |   2 +-
 demo/index.html                        |  10 ++
 demo/main.js                           |   4 +
 gulpfile.js                            |   1 +
 package.json                           |   2 +-
 src/EventEmitter.ts                    |   5 +-
 src/Parser.ts                          |  14 +--
 src/Renderer.ts                        | 185 +++++++++++++++++++--------------
 src/handlers/Clipboard.test.ts         |  11 ++
 src/handlers/Clipboard.ts              |  14 +++
 src/utils/DomElementObjectPool.test.ts |  47 +++++++++
 src/utils/DomElementObjectPool.ts      |  75 +++++++++++++
 src/utils/Mouse.ts                     |  64 ++++++++++++
 src/xterm.css                          |   3 +-
 src/xterm.js                           |  87 +++++++---------
 17 files changed, 391 insertions(+), 142 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index 3d7145b..1a5d898 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -24,14 +24,18 @@ Dan Kaplun <dbkaplun at twitch.tv>
 Darin Morrison <freebroccolo at users.noreply.github.com>
 Edgar Andrés Margffoy Tuay <andfoy at gmail.com>
 Elliot Saba <staticfloat at gmail.com>
+Gary Ritchie <gary at rstudio.com>
 hiro-su <h.sugipon at gmail.com>
 Ian Lewis <ianlewis at google.com>
 imoses <ido at twiggle.com>
 InDieTasten <indietasten at gmail.com>
 Jean Bruenn <himself at jeanbruenn.info>
+Jeremy Danyow <jedanyow at microsoft.com>
 Jörg Breitbart <jerch at rockborn.de>
+Justin Mecham <justin at mecham.me>
 Lucian Buzzo <lucian.buzzo at gmail.com>
 Maël Nison <nison.mael at gmail.com>
+Martin Chloride <i at martincl2.me>
 Martin Wang <jiahaow at ca.ibm.com>
 Michael Irwin <mikesir87 at gmail.com>
 Mikko Karvonen <mikko.karvonen at arm.com>
@@ -40,6 +44,7 @@ Paris Kasidiaris <pariskasidiaris at gmail.com>
 Paris Kasidiaris <paris at sourcelair.com>
 runarberg <runar at greenqloud.com>
 Saswat Das <saswatds at users.noreply.github.com>
+Saul Costa <saul at codevolve.com>
 Shuanglei Tao <tsl0922 at gmail.com>
 Steven Silvester <steven.silvester at ieee.org>
 Thanasis Daglis <thanasis at sourcelair.com>
diff --git a/README.md b/README.md
index d0de156..05c9651 100644
--- a/README.md
+++ b/README.md
@@ -32,6 +32,10 @@ Xterm.js is used in several world-class applications to provide great terminal e
 - [**WebSSH2**](https://github.com/billchurch/WebSSH2): A web based SSH2 client using `xterm.js`, socket.io, and ssh2.
 - [**Spyder Terminal**](https://github.com/spyder-ide/spyder-terminal): A full fledged system terminal embedded on Spyder IDE. 
 - [**Cloud Commander**](https://cloudcmd.io "Cloud Commander"): Orthodox web file manager with console and editor.
+- [**Codevolve**](https://www.codevolve.com "Codevolve"): Online platform for interactive coding and web development courses. Live container-backed terminal uses `xterm.js`.
+- [**RStudio**](https://www.rstudio.com/products/RStudio "RStudio"): RStudio is an integrated development environment (IDE) for R.
+- [**Terminal for Atom**](https://github.com/jsmecham/atom-terminal-tab): A simple terminal for the Atom text editor.
+- [**Eclipse Orion**](https://orionhub.org): A modern, open source software development environment that runs in the cloud. Code, deploy and run in the cloud.
 
 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.
 
diff --git a/bower.json b/bower.json
index eb5a0bc..5d3daf1 100644
--- a/bower.json
+++ b/bower.json
@@ -1,6 +1,6 @@
 {
   "name": "xterm.js",
-  "version": "2.5.0",
+  "version": "2.7.0",
   "ignore": ["demo", "test", ".gitignore"],
   "main": [
     "dist/xterm.js",
diff --git a/demo/index.html b/demo/index.html
index 0d8efac..d4574aa 100644
--- a/demo/index.html
+++ b/demo/index.html
@@ -20,6 +20,16 @@
             <label><input type="checkbox" id="option-cursor-blink"> cursorBlink</label>
           </p>
           <p>
+            <label>
+              cursorStyle
+              <select id="option-cursor-style">
+                <option value="block">block</option>
+                <option value="underline">underline</option>
+                <option value="bar">bar</option>
+              </select>
+            </label>
+          </p>
+          <p>
             <label>scrollback <input type="number" id="option-scrollback" value="1000" /></label>
           </p>
           <p>
diff --git a/demo/main.js b/demo/main.js
index b262ce5..c8d03ae 100644
--- a/demo/main.js
+++ b/demo/main.js
@@ -9,6 +9,7 @@ var term,
 var terminalContainer = document.getElementById('terminal-container'),
     optionElements = {
       cursorBlink: document.querySelector('#option-cursor-blink'),
+      cursorStyle: document.querySelector('#option-cursor-style'),
       scrollback: document.querySelector('#option-scrollback'),
       tabstopwidth: document.querySelector('#option-tabstopwidth')
     },
@@ -32,6 +33,9 @@ rowsElement.addEventListener('change', setTerminalSize);
 optionElements.cursorBlink.addEventListener('change', function () {
   term.setOption('cursorBlink', optionElements.cursorBlink.checked);
 });
+optionElements.cursorStyle.addEventListener('change', function () {
+  term.setOption('cursorStyle', optionElements.cursorStyle.value);
+});
 optionElements.scrollback.addEventListener('change', function () {
   term.setOption('scrollback', parseInt(optionElements.scrollback.value, 10));
 });
diff --git a/gulpfile.js b/gulpfile.js
index feaa3dc..1f92e53 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -83,6 +83,7 @@ gulp.task('instrument-test', function () {
 gulp.task('mocha', ['instrument-test'], function () {
   return gulp.src([`${outDir}/*test.js`, `${outDir}/**/*test.js`], {read: false})
       .pipe(mocha())
+      .once('error', () => process.exit(1))
       .pipe(istanbul.writeReports());
 });
 
diff --git a/package.json b/package.json
index 03b725b..6dda754 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "xterm",
   "description": "Full xterm terminal, in your browser",
-  "version": "2.5.0",
+  "version": "2.7.0",
   "ignore": [
     "demo",
     "test",
diff --git a/src/EventEmitter.ts b/src/EventEmitter.ts
index 1db8676..a4fd454 100644
--- a/src/EventEmitter.ts
+++ b/src/EventEmitter.ts
@@ -53,14 +53,11 @@ export class EventEmitter {
     return this.on(type, on);
   }
 
-  public emit(type): void {
+  public emit(type: string, ...args: any[]): void {
     if (!this._events[type]) {
       return;
     }
-
-    let args = Array.prototype.slice.call(arguments, 1);
     let obj = this._events[type];
-
     for (let i = 0; i < obj.length; i++) {
       obj[i].apply(this, args);
     }
diff --git a/src/Parser.ts b/src/Parser.ts
index 01821a5..00d574e 100644
--- a/src/Parser.ts
+++ b/src/Parser.ts
@@ -181,7 +181,7 @@ export class Parser {
    *
    * @param data The data to parse.
    */
-  public parse(data: string) {
+  public parse(data: string): ParserState {
     let l = data.length, j, cs, ch, code, low;
 
     this._position = 0;
@@ -471,6 +471,8 @@ export class Parser {
         case ParserState.DCS:
           if (ch === C0.ESC || ch === C0.BEL) {
             if (ch === C0.ESC) this._position++;
+            let pt;
+            let valid: boolean;
 
             switch (this._terminal.prefix) {
               // User-Defined Keys (DECUDK).
@@ -480,8 +482,8 @@ export class Parser {
               // Request Status String (DECRQSS).
               // test: echo -e '\eP$q"p\e\\'
               case '$q':
-                let pt = this._terminal.currentParam
-                , valid = false;
+                pt = this._terminal.currentParam;
+                valid = false;
 
                 switch (pt) {
                   // DECSCA
@@ -526,9 +528,8 @@ export class Parser {
               // This can cause a small glitch in vim.
               // test: echo -ne '\eP+q6b64\e\\'
               case '+q':
-                // TODO: Don't declare pt twice
-                /*let*/ pt = this._terminal.currentParam
-                , valid = false;
+                pt = this._terminal.currentParam;
+                valid = false;
 
                 this._terminal.send(C0.ESC + 'P' + +valid + '+r' + pt + C0.ESC + '\\');
                 break;
@@ -563,6 +564,7 @@ export class Parser {
           break;
       }
     }
+    return this._state;
   }
 
   /**
diff --git a/src/Renderer.ts b/src/Renderer.ts
index 4131abc..af95d51 100644
--- a/src/Renderer.ts
+++ b/src/Renderer.ts
@@ -3,6 +3,7 @@
  */
 
 import { ITerminal } from './Interfaces';
+import { DomElementObjectPool } from './utils/DomElementObjectPool';
 
 /**
  * The maximum number of refresh frames to skip when the write buffer is non-
@@ -30,12 +31,15 @@ export class Renderer {
   private _refreshFramesSkipped = 0;
   private _refreshAnimationFrame = null;
 
+  private _spanElementObjectPool = new DomElementObjectPool('span');
+
   constructor(private _terminal: ITerminal) {
     // Figure out whether boldness affects
     // the character width of monospace fonts.
     if (brokenBold === null) {
       brokenBold = checkBoldBroken((<any>this._terminal).element);
     }
+    this._spanElementObjectPool = new DomElementObjectPool('span');
 
     // TODO: Pull more DOM interactions into Renderer.constructor, element for
     // example should be owned by Renderer (and also exposed by Terminal due to
@@ -117,9 +121,8 @@ export class Renderer {
    * @param {number} end The row to end at (between fromRow and terminal's height terminal - 1)
    */
   private _refresh(start: number, end: number): void {
-    let 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
+    let parent;
     if (end - start >= this._terminal.rows / 2) {
       parent = this._terminal.element.parentNode;
       if (parent) {
@@ -127,8 +130,8 @@ export class Renderer {
       }
     }
 
-    width = this._terminal.cols;
-    y = start;
+    let width = this._terminal.cols;
+    let y = start;
 
     if (end >= this._terminal.rows) {
       this._terminal.log('`end` is too large. Most likely a bad CSR.');
@@ -136,80 +139,104 @@ export class Renderer {
     }
 
     for (; y <= end; y++) {
-      row = y + this._terminal.ydisp;
+      let row = y + this._terminal.ydisp;
 
-      line = this._terminal.lines.get(row);
-      if (!line || !this._terminal.children[y]) {
-        // Continue if the line is not available, this means a resize is currently in progress
-        continue;
-      }
-      out = '';
+      let line = this._terminal.lines.get(row);
 
-      if (this._terminal.y === y - (this._terminal.ybase - this._terminal.ydisp)
-          && this._terminal.cursorState
-          && !this._terminal.cursorHidden) {
+      let x;
+      if (this._terminal.y === y - (this._terminal.ybase - this._terminal.ydisp) &&
+          this._terminal.cursorState &&
+          !this._terminal.cursorHidden) {
         x = this._terminal.x;
       } else {
         x = -1;
       }
 
-      attr = this._terminal.defAttr;
-      i = 0;
+      let attr = this._terminal.defAttr;
+
+      const documentFragment = document.createDocumentFragment();
+      let innerHTML = '';
+      let currentElement;
 
-      for (; i < width; i++) {
-        if (!line[i]) {
-          // Continue if the character is not available, this means a resize is currently in progress
+      // Return the row's spans to the pool
+      while (this._terminal.children[y].children.length) {
+        const child = this._terminal.children[y].children[0];
+        this._terminal.children[y].removeChild(child);
+        this._spanElementObjectPool.release(<HTMLElement>child);
+      }
+
+      for (let i = 0; i < width; i++) {
+        // TODO: Could data be a more specific type?
+        let data: any = line[i][0];
+        const ch = line[i][1];
+        const ch_width: any = line[i][2];
+        if (!ch_width) {
           continue;
         }
-        data = line[i][0];
-        ch = line[i][1];
-        ch_width = line[i][2];
-        if (!ch_width)
-          continue;
 
-        if (i === x) data = -1;
+        if (i === x) {
+          data = -1;
+        }
 
         if (data !== attr) {
           if (attr !== this._terminal.defAttr) {
-            out += '</span>';
+            if (innerHTML) {
+              currentElement.innerHTML = innerHTML;
+              innerHTML = '';
+            }
+            documentFragment.appendChild(currentElement);
+            currentElement = null;
           }
           if (data !== this._terminal.defAttr) {
+            if (innerHTML && !currentElement) {
+              currentElement = this._spanElementObjectPool.acquire();
+            }
+            if (currentElement) {
+              if (innerHTML) {
+                currentElement.innerHTML = innerHTML;
+                innerHTML = '';
+              }
+              documentFragment.appendChild(currentElement);
+            }
+            currentElement = this._spanElementObjectPool.acquire();
             if (data === -1) {
-              out += '<span class="reverse-video terminal-cursor">';
+              currentElement.classList.add('reverse-video', 'terminal-cursor');
             } else {
-              let classNames = [];
-
-              bg = data & 0x1ff;
-              fg = (data >> 9) & 0x1ff;
-              flags = data >> 18;
+              let bg = data & 0x1ff;
+              let fg = (data >> 9) & 0x1ff;
+              let flags = data >> 18;
 
               if (flags & FLAGS.BOLD) {
                 if (!brokenBold) {
-                  classNames.push('xterm-bold');
+                  currentElement.classList.add('xterm-bold');
                 }
                 // See: XTerm*boldColors
-                if (fg < 8) fg += 8;
+                if (fg < 8) {
+                  fg += 8;
+                }
               }
 
               if (flags & FLAGS.UNDERLINE) {
-                classNames.push('xterm-underline');
+                currentElement.classList.add('xterm-underline');
               }
 
               if (flags & FLAGS.BLINK) {
-                classNames.push('xterm-blink');
+                currentElement.classList.add('xterm-blink');
               }
 
               // If inverse flag is on, then swap the foreground and background variables.
               if (flags & FLAGS.INVERSE) {
-                /* One-line variable swap in JavaScript: http://stackoverflow.com/a/16201730 */
-                bg = [fg, fg = bg][0];
-                // Should inverse just be before the
-                // above boldColors effect instead?
-                if ((flags & 1) && fg < 8) fg += 8;
+                let temp = bg;
+                bg = fg;
+                fg = temp;
+                // Should inverse just be before the above boldColors effect instead?
+                if ((flags & 1) && fg < 8) {
+                  fg += 8;
+                }
               }
 
               if (flags & FLAGS.INVISIBLE) {
-                classNames.push('xterm-hidden');
+                currentElement.classList.add('xterm-hidden');
               }
 
               /**
@@ -229,55 +256,60 @@ export class Renderer {
               }
 
               if (bg < 256) {
-                classNames.push('xterm-bg-color-' + bg);
+                currentElement.classList.add(`xterm-bg-color-${bg}`);
               }
 
               if (fg < 256) {
-                classNames.push('xterm-color-' + fg);
-              }
-
-              out += '<span';
-              if (classNames.length) {
-                out += ' class="' + classNames.join(' ') + '"';
+                currentElement.classList.add(`xterm-color-${fg}`);
               }
-              out += '>';
             }
           }
         }
 
         if (ch_width === 2) {
-          out += '<span class="xterm-wide-char">';
-        }
-        switch (ch) {
-          case '&':
-            out += '&';
-            break;
-          case '<':
-            out += '<';
-            break;
-          case '>':
-            out += '>';
-            break;
-          default:
-            if (ch <= ' ') {
-              out += ' ';
-            } else {
-              out += ch;
-            }
-            break;
-        }
-        if (ch_width === 2) {
-          out += '</span>';
+          // Wrap wide characters so they're sized correctly. It's more difficult to release these
+          // from the object pool so just create new ones via innerHTML.
+          innerHTML += `<span class="xterm-wide-char">${ch}</span>`;
+        } else if (ch.charCodeAt(0) > 255) {
+          // Wrap any non-wide unicode character as some fonts size them badly
+          innerHTML += `<span class="xterm-normal-char">${ch}</span>`;
+        } else {
+          switch (ch) {
+            case '&':
+              innerHTML += '&';
+              break;
+            case '<':
+              innerHTML += '<';
+              break;
+            case '>':
+              innerHTML += '>';
+              break;
+            default:
+              if (ch <= ' ') {
+                innerHTML += ' ';
+              } else {
+                innerHTML += ch;
+              }
+              break;
+          }
         }
 
         attr = data;
       }
 
-      if (attr !== this._terminal.defAttr) {
-        out += '</span>';
+      if (innerHTML && !currentElement) {
+        currentElement = this._spanElementObjectPool.acquire();
+      }
+      if (currentElement) {
+        if (innerHTML) {
+          currentElement.innerHTML = innerHTML;
+          innerHTML = '';
+        }
+        documentFragment.appendChild(currentElement);
+        currentElement = null;
       }
 
-      this._terminal.children[y].innerHTML = out;
+      this._terminal.children[y].appendChild(documentFragment);
     }
 
     if (parent) {
@@ -289,8 +321,7 @@ export class Renderer {
 }
 
 
-// if bold is broken, we can't
-// use it in the terminal.
+// If bold is broken, we can't use it in the terminal.
 function checkBoldBroken(terminal) {
   const document = terminal.ownerDocument;
   const el = document.createElement('span');
diff --git a/src/handlers/Clipboard.test.ts b/src/handlers/Clipboard.test.ts
index 0937469..471389c 100644
--- a/src/handlers/Clipboard.test.ts
+++ b/src/handlers/Clipboard.test.ts
@@ -16,3 +16,14 @@ describe('evaluateCopiedTextProcessing', function () {
     assert.equal(processedText.indexOf(nonBreakingSpace), -1);
   });
 });
+
+describe('evaluatePastedTextProcessing', function () {
+  it('should replace carriage return + line feed with line feed on windows', function () {
+    const pastedText = 'foo\r\nbar\r\n',
+          processedText = Clipboard.prepareTextForTerminal(pastedText, false),
+          windowsProcessedText = Clipboard.prepareTextForTerminal(pastedText, true);
+
+    assert.equal(processedText, 'foo\r\nbar\r\n');
+    assert.equal(windowsProcessedText, 'foo\nbar\n');
+  });
+});
diff --git a/src/handlers/Clipboard.ts b/src/handlers/Clipboard.ts
index 073310b..0f3b9d0 100644
--- a/src/handlers/Clipboard.ts
+++ b/src/handlers/Clipboard.ts
@@ -39,6 +39,17 @@ export function prepareTextForClipboard(text: string): string {
 }
 
 /**
+ * Prepares text to be pasted into the terminal by normalizing the line endings
+ * @param text The pasted text that needs processing before inserting into the terminal
+ */
+export function prepareTextForTerminal(text: string, isMSWindows: boolean): string {
+  if (isMSWindows) {
+    return text.replace(/\r?\n/g, '\n');
+  }
+  return text;
+}
+
+/**
  * Binds copy functionality to the given terminal.
  * @param {ClipboardEvent} ev The original copy event to be handled
  */
@@ -68,8 +79,11 @@ export function pasteHandler(ev: ClipboardEvent, term: ITerminal) {
   let text: string;
 
   let dispatchPaste = function(text) {
+    text = prepareTextForTerminal(text, term.browser.isMSWindows);
     term.handler(text);
     term.textarea.value = '';
+    term.emit('paste', text);
+
     return term.cancel(ev);
   };
 
diff --git a/src/utils/DomElementObjectPool.test.ts b/src/utils/DomElementObjectPool.test.ts
new file mode 100644
index 0000000..7298f19
--- /dev/null
+++ b/src/utils/DomElementObjectPool.test.ts
@@ -0,0 +1,47 @@
+import { assert } from 'chai';
+import { DomElementObjectPool } from './DomElementObjectPool';
+
+class MockDocument {
+  private _attr: {[key: string]: string} = {};
+  constructor() {}
+  public getAttribute(key: string): string { return this._attr[key]; };
+  public setAttribute(key: string, value: string): void { this._attr[key] = value; }
+}
+
+describe('DomElementObjectPool', () => {
+  let pool: DomElementObjectPool;
+
+  beforeEach(() => {
+    pool = new DomElementObjectPool('span');
+    (<any>global).document = {
+      createElement: () => new MockDocument()
+    };
+  });
+
+  it('should acquire distinct elements', () => {
+    const element1 = pool.acquire();
+    const element2 = pool.acquire();
+    assert.notEqual(element1, element2);
+  });
+
+  it('should acquire released elements', () => {
+    const element = pool.acquire();
+    pool.release(element);
+    assert.equal(pool.acquire(), element);
+  });
+
+  it('should handle a series of acquisitions and releases', () => {
+    const element1 = pool.acquire();
+    const element2 = pool.acquire();
+    pool.release(element1);
+    assert.equal(pool.acquire(), element1);
+    pool.release(element1);
+    pool.release(element2);
+    assert.equal(pool.acquire(), element2);
+    assert.equal(pool.acquire(), element1);
+  });
+
+  it('should throw when releasing an element that was not acquired', () => {
+    assert.throws(() => pool.release(document.createElement('span')));
+  });
+});
diff --git a/src/utils/DomElementObjectPool.ts b/src/utils/DomElementObjectPool.ts
new file mode 100644
index 0000000..7707ad9
--- /dev/null
+++ b/src/utils/DomElementObjectPool.ts
@@ -0,0 +1,75 @@
+/**
+ * @module xterm/utils/DomElementObjectPool
+ * @license MIT
+ */
+
+/**
+ * An object pool that manages acquisition and releasing of DOM elements for
+ * when reuse is desirable.
+ */
+export class DomElementObjectPool {
+  private static readonly OBJECT_ID_ATTRIBUTE = 'data-obj-id';
+
+  private static _objectCount = 0;
+
+  private _type: string;
+  private _pool: HTMLElement[];
+  private _inUse: {[key: string]: HTMLElement};
+
+  /**
+   * @param type The DOM element type (div, span, etc.).
+   */
+  constructor(private type: string) {
+    this._type = type;
+    this._pool = [];
+    this._inUse = {};
+  }
+
+  /**
+   * Acquire an element from the pool, creating it if the pool is empty.
+   */
+  public acquire(): HTMLElement {
+    let element: HTMLElement;
+    if (this._pool.length === 0) {
+      element = this._createNew();
+    } else {
+      element = this._pool.pop();
+    }
+    this._inUse[element.getAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE)] = element;
+    return element;
+  }
+
+  /**
+   * Release an element back into the pool. It's up to the caller of this
+   * function to ensure that all external references to the element have been
+   * removed.
+   * @param element The element being released.
+   */
+  public release(element: HTMLElement): void {
+    if (!this._inUse[element.getAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE)]) {
+      throw new Error('Could not release an element not yet acquired');
+    }
+    delete this._inUse[element.getAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE)];
+    this._cleanElement(element);
+    this._pool.push(element);
+  }
+
+  /**
+   * Creates a new element for the pool.
+   */
+  private _createNew(): HTMLElement {
+    const element = document.createElement(this._type);
+    const id = DomElementObjectPool._objectCount++;
+    element.setAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE, id.toString(10));
+    return element;
+  }
+
+  /**
+   * Resets an element back to a "clean state".
+   * @param element The element to be cleaned.
+   */
+  private _cleanElement(element: HTMLElement): void {
+    element.className = '';
+    element.innerHTML = '';
+  }
+}
diff --git a/src/utils/Mouse.ts b/src/utils/Mouse.ts
new file mode 100644
index 0000000..a9efdf9
--- /dev/null
+++ b/src/utils/Mouse.ts
@@ -0,0 +1,64 @@
+/**
+ * @license MIT
+ */
+
+import { CharMeasure } from './CharMeasure';
+
+/**
+ * Gets coordinates within the terminal for a particular mouse event. The result
+ * is returned as an array in the form [x, y] instead of an object as it's a
+ * little faster and this function is used in some low level code.
+ * @param event The mouse event.
+ * @param rowContainer The terminal's row container.
+ * @param charMeasure The char measure object used to determine character sizes.
+ */
+export function getCoords(event: MouseEvent, rowContainer: HTMLElement, charMeasure: CharMeasure): [number, number] {
+  // Ignore browsers that don't support MouseEvent.pageX
+  if (event.pageX == null) {
+    return null;
+  }
+
+  let x = event.pageX;
+  let y = event.pageY;
+  let el = rowContainer;
+
+  // Converts the coordinates from being relative to the document to being
+  // relative to the terminal.
+  while (el && el !== self.document.documentElement) {
+    x -= el.offsetLeft;
+    y -= el.offsetTop;
+    el = 'offsetParent' in el ? <HTMLElement>el.offsetParent : <HTMLElement>el.parentElement;
+  }
+
+  // Convert to cols/rows
+  x = Math.ceil(x / charMeasure.width);
+  y = Math.ceil(y / charMeasure.height);
+
+  return [x, y];
+}
+
+/**
+ * Gets coordinates within the terminal for a particular mouse event, wrapping
+ * them to the bounds of the terminal and adding 32 to both the x and y values
+ * as expected by xterm.
+ * @param event The mouse event.
+ * @param rowContainer The terminal's row container.
+ * @param charMeasure The char measure object used to determine character sizes.
+ * @param colCount The number of columns in the terminal.
+ * @param rowCount The number of rows in the terminal.
+ */
+export function getRawByteCoords(event: MouseEvent, rowContainer: HTMLElement, charMeasure: CharMeasure, colCount: number, rowCount: number): { x: number, y: number } {
+  const coords = getCoords(event, rowContainer, charMeasure);
+  let x = coords[0];
+  let y = coords[1];
+
+  // Ensure coordinates are within the terminal viewport.
+  x = Math.min(Math.max(x, 0), colCount);
+  y = Math.min(Math.max(y, 0), rowCount);
+
+  // xterm sends raw bytes and starts at 32 (SP) for each.
+  x += 32;
+  y += 32;
+
+  return { x, y };
+}
diff --git a/src/xterm.css b/src/xterm.css
index a67485e..efdc016 100644
--- a/src/xterm.css
+++ b/src/xterm.css
@@ -153,7 +153,8 @@
     overflow-y: scroll;
 }
 
-.terminal .xterm-wide-char {
+.terminal .xterm-wide-char,
+.terminal .xterm-normal-char {
     display: inline-block;
 }
 
diff --git a/src/xterm.js b/src/xterm.js
index 09c0332..bf359a9 100644
--- a/src/xterm.js
+++ b/src/xterm.js
@@ -24,6 +24,7 @@ import { CharMeasure } from './utils/CharMeasure';
 import * as Browser from './utils/Browser';
 import * as Keyboard from './utils/Keyboard';
 import { CHARSETS } from './Charsets';
+import { getRawByteCoords } from './utils/Mouse';
 
 /**
  * Terminal Emulation References:
@@ -607,8 +608,9 @@ Terminal.prototype.insertRow = function (row) {
  * Opens the terminal within an element.
  *
  * @param {HTMLElement} parent The element to create the terminal within.
+ * @param {boolean} focus Focus the terminal, after it gets instantiated in the DOM
  */
-Terminal.prototype.open = function(parent) {
+Terminal.prototype.open = function(parent, focus) {
   var self=this, i=0, div;
 
   this.parent = parent || this.parent;
@@ -629,7 +631,7 @@ Terminal.prototype.open = function(parent) {
   this.element.classList.add('xterm-theme-' + this.theme);
   this.setCursorBlinking(this.options.cursorBlink);
 
-  this.element.style.height
+  this.element.style.height;
   this.element.setAttribute('tabindex', 0);
 
   this.viewportElement = document.createElement('div');
@@ -696,8 +698,23 @@ Terminal.prototype.open = function(parent) {
   // need to be taken on the document.
   this.initGlobal();
 
-  // Ensure there is a Terminal.focus.
-  this.focus();
+  /**
+   * Automatic focus functionality.
+   * TODO: Default to `false` starting with xterm.js 3.0.
+   */
+  if (typeof focus == 'undefined') {
+    let message = 'You did not pass the `focus` argument in `Terminal.prototype.open()`.\n';
+
+    message += 'The `focus` argument now defaults to `true` but starting with xterm.js 3.0 ';
+    message += 'it will default to `false`.';
+
+    console.warn(message);
+    focus = true;
+  }
+
+  if (focus) {
+    this.focus();
+  }
 
   on(this.element, 'click', function() {
     var selection = document.getSelection(),
@@ -744,7 +761,9 @@ Terminal.loadAddon = function(addon, callback) {
  * character width has been changed.
  */
 Terminal.prototype.updateCharSizeCSS = function() {
-  this.charSizeStyleElement.textContent = '.xterm-wide-char{width:' + (this.charMeasure.width * 2) + 'px;}';
+  this.charSizeStyleElement.textContent =
+      `.xterm-wide-char{width:${this.charMeasure.width * 2}px;}` +
+      `.xterm-normal-char{width:${this.charMeasure.width}px;}`
 }
 
 /**
@@ -771,7 +790,7 @@ Terminal.prototype.bindMouse = function() {
     button = getButton(ev);
 
     // get mouse coordinates
-    pos = getCoords(ev);
+    pos = getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.cols, self.rows);
     if (!pos) return;
 
     sendEvent(button, pos);
@@ -799,7 +818,7 @@ Terminal.prototype.bindMouse = function() {
     var button = pressed
     , pos;
 
-    pos = getCoords(ev);
+    pos = getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.cols, self.rows);
     if (!pos) return;
 
     // buttons marked as motions
@@ -974,50 +993,6 @@ Terminal.prototype.bindMouse = function() {
     return button;
   }
 
-  // mouse coordinates measured in cols/rows
-  function getCoords(ev) {
-    var x, y, w, h, el;
-
-    // ignore browsers without pageX for now
-    if (ev.pageX == null) return;
-
-    x = ev.pageX;
-    y = ev.pageY;
-    el = self.element;
-
-    // should probably check offsetParent
-    // but this is more portable
-    while (el && el !== self.document.documentElement) {
-      x -= el.offsetLeft;
-      y -= el.offsetTop;
-      el = 'offsetParent' in el
-        ? el.offsetParent
-      : el.parentNode;
-    }
-
-    // convert to cols/rows
-    x = Math.ceil(x / self.charMeasure.width);
-    y = Math.ceil(y / self.charMeasure.height);
-
-    // be sure to avoid sending
-    // bad positions to the program
-    if (x < 0) x = 0;
-    if (x > self.cols) x = self.cols;
-    if (y < 0) y = 0;
-    if (y > self.rows) y = self.rows;
-
-    // xterm sends raw bytes and
-    // starts at 32 (SP) for each.
-    x += 32;
-    y += 32;
-
-    return {
-      x: x,
-      y: y,
-      type: 'wheel'
-    };
-  }
-
   on(el, 'mousedown', function(ev) {
     if (!self.mouseEvents) return;
 
@@ -1277,7 +1252,13 @@ Terminal.prototype.innerWrite = function() {
     this.refreshStart = this.y;
     this.refreshEnd = this.y;
 
-    this.parser.parse(data);
+    // HACK: Set the parser state based on it's state at the time of return.
+    // This works around the bug #662 which saw the parser state reset in the
+    // middle of parsing escape sequence in two chunks. For some reason the
+    // state of the parser resets to 0 after exiting parser.parse. This change
+    // just sets the state back based on the correct return statement.
+    var state = this.parser.parse(data);
+    this.parser.setState(state);
 
     this.updateRange(this.y);
     this.refresh(this.refreshStart, this.refreshEnd);
@@ -2193,8 +2174,10 @@ Terminal.prototype.reset = function() {
   this.options.rows = this.rows;
   this.options.cols = this.cols;
   var customKeydownHandler = this.customKeydownHandler;
+  var cursorBlinkInterval = this.cursorBlinkInterval;
   Terminal.call(this, this.options);
   this.customKeydownHandler = customKeydownHandler;
+  this.cursorBlinkInterval = cursorBlinkInterval;
   this.refresh(0, this.rows - 1);
   this.viewport.syncScrollArea();
 };

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-xterm.git



More information about the Pkg-javascript-commits mailing list