[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