From 2571a0654f87e4663ac8eb4cbddaa6dd4075d15a Mon Sep 17 00:00:00 2001
From: javacs3
Date: Sun, 7 Jul 2019 22:59:15 +0800
Subject: [PATCH 01/47] add SerializeAddon
---
addons/xterm-addon-serialize/.gitignore | 2 +
addons/xterm-addon-serialize/.npmignore | 5 +
addons/xterm-addon-serialize/package.json | 20 +++
.../src/SerializeAddon.api.ts | 134 ++++++++++++++++++
.../src/SerializeAddon.ts | 42 ++++++
.../xterm-addon-serialize/src/tsconfig.json | 19 +++
.../typings/xterm-addon-serialize.d.ts | 30 ++++
.../xterm-addon-serialize/webpack.config.js | 31 ++++
demo/client.ts | 3 +
package.json | 1 +
tsconfig.all.json | 3 +-
11 files changed, 289 insertions(+), 1 deletion(-)
create mode 100644 addons/xterm-addon-serialize/.gitignore
create mode 100644 addons/xterm-addon-serialize/.npmignore
create mode 100644 addons/xterm-addon-serialize/package.json
create mode 100644 addons/xterm-addon-serialize/src/SerializeAddon.api.ts
create mode 100644 addons/xterm-addon-serialize/src/SerializeAddon.ts
create mode 100644 addons/xterm-addon-serialize/src/tsconfig.json
create mode 100644 addons/xterm-addon-serialize/typings/xterm-addon-serialize.d.ts
create mode 100644 addons/xterm-addon-serialize/webpack.config.js
diff --git a/addons/xterm-addon-serialize/.gitignore b/addons/xterm-addon-serialize/.gitignore
new file mode 100644
index 00000000..3063f07d
--- /dev/null
+++ b/addons/xterm-addon-serialize/.gitignore
@@ -0,0 +1,2 @@
+lib
+node_modules
diff --git a/addons/xterm-addon-serialize/.npmignore b/addons/xterm-addon-serialize/.npmignore
new file mode 100644
index 00000000..1c794445
--- /dev/null
+++ b/addons/xterm-addon-serialize/.npmignore
@@ -0,0 +1,5 @@
+**/*.api.js
+**/*.api.ts
+tsconfig.json
+.yarnrc
+webpack.config.js
diff --git a/addons/xterm-addon-serialize/package.json b/addons/xterm-addon-serialize/package.json
new file mode 100644
index 00000000..c8a66c4b
--- /dev/null
+++ b/addons/xterm-addon-serialize/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "xterm-addon-serialize",
+ "version": "0.1.0",
+ "author": {
+ "name": "The xterm.js authors",
+ "url": "https://xtermjs.org/"
+ },
+ "main": "lib/xterm-addon-serialize.js",
+ "types": "typings/xterm-addon-serialize.d.ts",
+ "license": "MIT",
+ "scripts": {
+ "build": "../../node_modules/.bin/tsc -p src",
+ "prepackage": "npm run build",
+ "package": "../../node_modules/.bin/webpack",
+ "prepublishOnly": "npm run package"
+ },
+ "peerDependencies": {
+ "xterm": "^3.14.0"
+ }
+}
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.api.ts b/addons/xterm-addon-serialize/src/SerializeAddon.api.ts
new file mode 100644
index 00000000..88e4503e
--- /dev/null
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.api.ts
@@ -0,0 +1,134 @@
+/**
+ * Copyright (c) 2019 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import * as puppeteer from 'puppeteer';
+import * as util from 'util';
+import { assert } from 'chai';
+import { ITerminalOptions } from 'xterm';
+
+const APP = 'http://127.0.0.1:3000/test';
+
+let browser: puppeteer.Browser;
+let page: puppeteer.Page;
+const width = 800;
+const height = 600;
+
+describe('SerializeAddon', () => {
+ before(async function (): Promise {
+ this.timeout(20000);
+ browser = await puppeteer.launch({
+ headless: process.argv.indexOf('--headless') !== -1,
+ slowMo: 80,
+ args: [`--window-size=${width},${height}`]
+ });
+ page = (await browser.pages())[0];
+ await page.setViewport({ width, height });
+ });
+
+ after(async () => {
+ await browser.close();
+ });
+
+ beforeEach(async function (): Promise {
+ this.timeout(20000);
+ await page.goto(APP);
+ });
+
+ it('empty content', async function (): Promise {
+ this.timeout(20000);
+ const rows = 10;
+ const cols = 10;
+ const blankline = ' '.repeat(cols);
+ const lines = newArray(blankline, rows);
+
+ await openTerminal({ rows: rows, cols: cols, rendererType: 'dom' });
+ await page.evaluate(`
+ window.serializeAddon = new SerializeAddon();
+ window.term.loadAddon(window.serializeAddon);
+ `)
+
+ assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
+ });
+
+ it('digits content', async function (): Promise {
+ this.timeout(20000);
+ const rows = 10;
+ const cols = 10;
+ const digitsLine = digitsString(cols);
+ const lines = newArray(digitsLine, rows);
+
+ await openTerminal({ rows: rows, cols: cols, rendererType: 'dom' });
+ await page.evaluate(`
+ window.serializeAddon = new SerializeAddon();
+ window.term.loadAddon(window.serializeAddon);
+ window.term.write(${util.inspect(lines.join('\r\n'))});
+ `)
+
+ assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
+ });
+
+ it('serialize n rows of content', async function (): Promise {
+ this.timeout(20000);
+ const rows = 10;
+ const halfRows = rows >> 1;
+ const cols = 10;
+ const lines = newArray((index: number) => digitsString(cols, index), rows);
+
+ await openTerminal({ rows: rows, cols: cols, rendererType: 'dom' });
+ await page.evaluate(`
+ window.serializeAddon = new SerializeAddon();
+ window.term.loadAddon(window.serializeAddon);
+ window.term.write(${util.inspect(lines.join('\r\n'))});
+ `)
+
+ assert.equal(await page.evaluate(`serializeAddon.serialize(${halfRows});`), lines.slice(0, halfRows).join('\r\n'));
+ });
+
+ it('serialize 0 rows of content', async function (): Promise {
+ this.timeout(20000);
+ const rows = 10;
+ const cols = 10;
+ const lines = newArray((index: number) => digitsString(cols, index), rows);
+
+ await openTerminal({ rows: rows, cols: cols, rendererType: 'dom' });
+ await page.evaluate(`
+ window.serializeAddon = new SerializeAddon();
+ window.term.loadAddon(window.serializeAddon);
+ window.term.write(${util.inspect(lines.join('\r\n'))});
+ `)
+
+ assert.equal(await page.evaluate(`serializeAddon.serialize(0);`), '');
+ });
+});
+
+async function openTerminal(options: ITerminalOptions = {}): Promise {
+ await page.evaluate(`window.term = new Terminal(${JSON.stringify(options)})`);
+ await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);
+ if (options.rendererType === 'dom') {
+ await page.waitForSelector('.xterm-rows');
+ } else {
+ await page.waitForSelector('.xterm-text-layer');
+ }
+}
+
+function newArray(initial: T | ((index: number) => T), count: number): T[] {
+ const array: T[] = new Array(count);
+ for (let i = 0; i < array.length; i++) {
+ if (typeof initial === 'function') {
+ array[i] = (<(index: number) => T>initial)(i);
+ } else {
+ array[i] = initial;
+ }
+ }
+ return array;
+}
+
+function digitsString(length: number, from: number = 0) {
+ let s = '';
+ for (let i = 0; i < length; i++) {
+ s += (from++) % 10;
+ }
+ return s;
+}
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts
new file mode 100644
index 00000000..8dd5b194
--- /dev/null
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2019 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { Terminal, ITerminalAddon } from 'xterm';
+
+export class SerializeAddon implements ITerminalAddon {
+ private _terminal: Terminal | undefined;
+
+ constructor() { }
+
+ public activate(terminal: Terminal): void {
+ this._terminal = terminal;
+ }
+
+ public serialize(rows?: number): string {
+ if (!this._terminal) {
+ return '';
+ }
+ const buffer = this._terminal.buffer;
+ const length = Math.max(0, Math.min((rows === undefined ? buffer.length : rows), buffer.length));
+ let data = '';
+
+ for (let i = 0; i < length; i++) {
+ const line = buffer.getLine(i);
+ const last = i === length - 1;
+ if (line) {
+ data += line.translateToString();
+ }
+ if (!last) {
+ data += '\r\n';
+ }
+ }
+
+ return data;
+ }
+
+ public dispose(): void {
+ if (this._terminal !== undefined) { }
+ }
+}
diff --git a/addons/xterm-addon-serialize/src/tsconfig.json b/addons/xterm-addon-serialize/src/tsconfig.json
new file mode 100644
index 00000000..5539aa56
--- /dev/null
+++ b/addons/xterm-addon-serialize/src/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "target": "es5",
+ "lib": [
+ "dom",
+ "es2015"
+ ],
+ "rootDir": ".",
+ "outDir": "../out",
+ "sourceMap": true,
+ "removeComments": true,
+ "strict": true
+ },
+ "include": [
+ "./**/*",
+ "../../../typings/xterm.d.ts"
+ ]
+}
diff --git a/addons/xterm-addon-serialize/typings/xterm-addon-serialize.d.ts b/addons/xterm-addon-serialize/typings/xterm-addon-serialize.d.ts
new file mode 100644
index 00000000..e36e724a
--- /dev/null
+++ b/addons/xterm-addon-serialize/typings/xterm-addon-serialize.d.ts
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+
+import { Terminal, ITerminalAddon } from 'xterm';
+
+declare module 'xterm-addon-serialize' {
+ /**
+ * An xterm.js addon that enables web links.
+ */
+ export class SerializeAddon implements ITerminalAddon {
+
+ constructor();
+
+ /**
+ * Activates the addon
+ * @param terminal The terminal the addon is being loaded in.
+ */
+ public activate(terminal: Terminal): void;
+
+ public serialize(rows?: number): string;
+
+ /**
+ * Disposes the addon.
+ */
+ public dispose(): void;
+ }
+}
diff --git a/addons/xterm-addon-serialize/webpack.config.js b/addons/xterm-addon-serialize/webpack.config.js
new file mode 100644
index 00000000..4cabbad9
--- /dev/null
+++ b/addons/xterm-addon-serialize/webpack.config.js
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2019 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+const path = require('path');
+
+const addonName = 'SerializeAddon';
+const mainFile = 'xterm-addon-serialize.js';
+
+module.exports = {
+ entry: `./out/${addonName}.js`,
+ devtool: 'source-map',
+ module: {
+ rules: [
+ {
+ test: /\.js$/,
+ use: ["source-map-loader"],
+ enforce: "pre",
+ exclude: /node_modules/
+ }
+ ]
+ },
+ output: {
+ filename: mainFile,
+ path: path.resolve('./lib'),
+ library: addonName,
+ libraryTarget: 'umd'
+ },
+ mode: 'production'
+};
diff --git a/demo/client.ts b/demo/client.ts
index e2259841..eb8b1053 100644
--- a/demo/client.ts
+++ b/demo/client.ts
@@ -14,6 +14,7 @@ import { FitAddon } from '../addons/xterm-addon-fit/out/FitAddon';
import { SearchAddon, ISearchOptions } from '../addons/xterm-addon-search/out/SearchAddon';
import { WebLinksAddon } from '../addons/xterm-addon-web-links/out/WebLinksAddon';
import { WebglAddon } from '../addons/xterm-addon-webgl/out/WebglAddon';
+import { SerializeAddon } from '../addons/xterm-addon-serialize/out/SerializeAddon';
// Use webpacked version (yarn package)
// import { Terminal } from '../lib/xterm';
@@ -35,6 +36,7 @@ export interface IWindowWithTerminal extends Window {
SearchAddon?: typeof SearchAddon;
WebLinksAddon?: typeof WebLinksAddon;
WebglAddon?: typeof WebglAddon;
+ SerializeAddon?: typeof SerializeAddon;
}
declare let window: IWindowWithTerminal;
@@ -88,6 +90,7 @@ if (document.location.pathname === '/test') {
window.SearchAddon = SearchAddon;
window.WebLinksAddon = WebLinksAddon;
window.WebglAddon = WebglAddon;
+ window.SerializeAddon = SerializeAddon;
} else {
createTerminal();
document.getElementById('dispose').addEventListener('click', disposeRecreateButtonHandler);
diff --git a/package.json b/package.json
index f26c010f..1ee4986d 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,7 @@
"test": "npm run test-unit",
"posttest": "npm run lint",
"test-api": "mocha \"**/*.api.js\"",
+ "test-addons": "mocha \"**/*.test.js\"",
"test-unit": "node ./bin/test.js",
"build": "tsc -b ./tsconfig.all.json",
"prepare": "npm run build",
diff --git a/tsconfig.all.json b/tsconfig.all.json
index 7ddf33d5..5a7ae05b 100644
--- a/tsconfig.all.json
+++ b/tsconfig.all.json
@@ -9,6 +9,7 @@
{ "path": "./addons/xterm-addon-fit/src" },
{ "path": "./addons/xterm-addon-search/src" },
{ "path": "./addons/xterm-addon-web-links/src" },
- { "path": "./addons/xterm-addon-webgl/src" }
+ { "path": "./addons/xterm-addon-webgl/src" },
+ { "path": "./addons/xterm-addon-serialize/src" }
]
}
From 3ba6ffe5f499aad1c57c78e89530aa6e1fc068f4 Mon Sep 17 00:00:00 2001
From: javacs3
Date: Wed, 10 Jul 2019 21:57:50 +0800
Subject: [PATCH 02/47] fix lint error
---
addons/xterm-addon-serialize/src/SerializeAddon.api.ts | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.api.ts b/addons/xterm-addon-serialize/src/SerializeAddon.api.ts
index 88e4503e..464316c3 100644
--- a/addons/xterm-addon-serialize/src/SerializeAddon.api.ts
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.api.ts
@@ -47,7 +47,7 @@ describe('SerializeAddon', () => {
await page.evaluate(`
window.serializeAddon = new SerializeAddon();
window.term.loadAddon(window.serializeAddon);
- `)
+ `);
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
});
@@ -64,7 +64,7 @@ describe('SerializeAddon', () => {
window.serializeAddon = new SerializeAddon();
window.term.loadAddon(window.serializeAddon);
window.term.write(${util.inspect(lines.join('\r\n'))});
- `)
+ `);
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
});
@@ -81,7 +81,7 @@ describe('SerializeAddon', () => {
window.serializeAddon = new SerializeAddon();
window.term.loadAddon(window.serializeAddon);
window.term.write(${util.inspect(lines.join('\r\n'))});
- `)
+ `);
assert.equal(await page.evaluate(`serializeAddon.serialize(${halfRows});`), lines.slice(0, halfRows).join('\r\n'));
});
@@ -97,7 +97,7 @@ describe('SerializeAddon', () => {
window.serializeAddon = new SerializeAddon();
window.term.loadAddon(window.serializeAddon);
window.term.write(${util.inspect(lines.join('\r\n'))});
- `)
+ `);
assert.equal(await page.evaluate(`serializeAddon.serialize(0);`), '');
});
@@ -125,7 +125,7 @@ function newArray(initial: T | ((index: number) => T), count: number): T[] {
return array;
}
-function digitsString(length: number, from: number = 0) {
+function digitsString(length: number, from: number = 0): string {
let s = '';
for (let i = 0; i < length; i++) {
s += (from++) % 10;
From 57a82556547d5c352a12f39112360c7bfa5f6f66 Mon Sep 17 00:00:00 2001
From: javacs3
Date: Fri, 12 Jul 2019 23:03:49 +0800
Subject: [PATCH 03/47] add test files from addons folder
---
bin/test.js | 3 ++-
package.json | 1 -
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/bin/test.js b/bin/test.js
index 89f0d61e..2d816097 100644
--- a/bin/test.js
+++ b/bin/test.js
@@ -12,7 +12,8 @@ env.NODE_PATH = path.resolve(__dirname, '../out');
let testFiles = [
'./out/*test.js',
- './out/**/*test.js'
+ './out/**/*test.js',
+ './addons/**/out/*test.js',
];
// ability to inject particular test files via
diff --git a/package.json b/package.json
index 1ee4986d..f26c010f 100644
--- a/package.json
+++ b/package.json
@@ -15,7 +15,6 @@
"test": "npm run test-unit",
"posttest": "npm run lint",
"test-api": "mocha \"**/*.api.js\"",
- "test-addons": "mocha \"**/*.test.js\"",
"test-unit": "node ./bin/test.js",
"build": "tsc -b ./tsconfig.all.json",
"prepare": "npm run build",
From 31f812cf94c3514f2749b8183c3e2e50ae8e0f41 Mon Sep 17 00:00:00 2001
From: javacs3
Date: Fri, 12 Jul 2019 23:21:48 +0800
Subject: [PATCH 04/47] add missing api docs and refactor serialize
implementation by using list join instead and some code clean up
---
.../src/SerializeAddon.ts | 19 ++++++-------------
.../typings/xterm-addon-serialize.d.ts | 8 ++++++++
2 files changed, 14 insertions(+), 13 deletions(-)
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts
index 8dd5b194..720da58b 100644
--- a/addons/xterm-addon-serialize/src/SerializeAddon.ts
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts
@@ -15,28 +15,21 @@ export class SerializeAddon implements ITerminalAddon {
}
public serialize(rows?: number): string {
+ // TODO: Add frontground/background color support later
if (!this._terminal) {
- return '';
+ throw new Error('No terminal found!');
}
const buffer = this._terminal.buffer;
const length = Math.max(0, Math.min((rows === undefined ? buffer.length : rows), buffer.length));
- let data = '';
+ const lines: string[] = new Array(length);
for (let i = 0; i < length; i++) {
const line = buffer.getLine(i);
- const last = i === length - 1;
- if (line) {
- data += line.translateToString();
- }
- if (!last) {
- data += '\r\n';
- }
+ lines[i] = line ? line.translateToString() : '';
}
- return data;
+ return lines.join('\r\n');
}
- public dispose(): void {
- if (this._terminal !== undefined) { }
- }
+ public dispose(): void { }
}
diff --git a/addons/xterm-addon-serialize/typings/xterm-addon-serialize.d.ts b/addons/xterm-addon-serialize/typings/xterm-addon-serialize.d.ts
index e36e724a..78b615d7 100644
--- a/addons/xterm-addon-serialize/typings/xterm-addon-serialize.d.ts
+++ b/addons/xterm-addon-serialize/typings/xterm-addon-serialize.d.ts
@@ -20,6 +20,14 @@ declare module 'xterm-addon-serialize' {
*/
public activate(terminal: Terminal): void;
+ /**
+ * Serializes terminal rows into a string that can be written back to the terminal
+ * to restore the state. The cursor will also be positioned to the correct cell.
+ * When restoring a terminal it is best to do before `Terminal.open` is called
+ * to avoid wasting CPU cycles rendering incomplete frames.
+ * @param rows The number of rows to serialize, starting from the top of the
+ * terminal. This defaults to the number of rows in the viewport.
+ */
public serialize(rows?: number): string;
/**
From fe3520c68587a6e538bdd75ed8f280d628b9d4fc Mon Sep 17 00:00:00 2001
From: javacs3
Date: Sat, 13 Jul 2019 17:25:18 +0800
Subject: [PATCH 05/47] change exception message
---
addons/xterm-addon-serialize/src/SerializeAddon.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts
index 720da58b..10d920cd 100644
--- a/addons/xterm-addon-serialize/src/SerializeAddon.ts
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts
@@ -17,7 +17,7 @@ export class SerializeAddon implements ITerminalAddon {
public serialize(rows?: number): string {
// TODO: Add frontground/background color support later
if (!this._terminal) {
- throw new Error('No terminal found!');
+ throw new Error('Cannot use addon until it has been loaded');
}
const buffer = this._terminal.buffer;
const length = Math.max(0, Math.min((rows === undefined ? buffer.length : rows), buffer.length));
From bca90245c8ff900353770a38b0155cfcb2dcfdaa Mon Sep 17 00:00:00 2001
From: javacs3
Date: Sun, 14 Jul 2019 23:22:20 +0800
Subject: [PATCH 06/47] serialize from the bottom of the terminal
---
.../src/SerializeAddon.api.ts | 2 +-
.../src/SerializeAddon.ts | 19 ++++++++++++++-----
.../typings/xterm-addon-serialize.d.ts | 2 +-
3 files changed, 16 insertions(+), 7 deletions(-)
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.api.ts b/addons/xterm-addon-serialize/src/SerializeAddon.api.ts
index 464316c3..5543696b 100644
--- a/addons/xterm-addon-serialize/src/SerializeAddon.api.ts
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.api.ts
@@ -83,7 +83,7 @@ describe('SerializeAddon', () => {
window.term.write(${util.inspect(lines.join('\r\n'))});
`);
- assert.equal(await page.evaluate(`serializeAddon.serialize(${halfRows});`), lines.slice(0, halfRows).join('\r\n'));
+ assert.equal(await page.evaluate(`serializeAddon.serialize(${halfRows});`), lines.slice(halfRows, 2 * halfRows).join('\r\n'));
});
it('serialize 0 rows of content', async function (): Promise {
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts
index 10d920cd..5d060949 100644
--- a/addons/xterm-addon-serialize/src/SerializeAddon.ts
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts
@@ -5,6 +5,10 @@
import { Terminal, ITerminalAddon } from 'xterm';
+function crop(value: number, from: number, to: number) {
+ return Math.max(from, Math.min(value, to))
+}
+
export class SerializeAddon implements ITerminalAddon {
private _terminal: Terminal | undefined;
@@ -19,13 +23,18 @@ export class SerializeAddon implements ITerminalAddon {
if (!this._terminal) {
throw new Error('Cannot use addon until it has been loaded');
}
- const buffer = this._terminal.buffer;
- const length = Math.max(0, Math.min((rows === undefined ? buffer.length : rows), buffer.length));
- const lines: string[] = new Array(length);
+ const terminalRows = this._terminal.rows;
+ if (rows === undefined) {
+ rows = terminalRows;
+ }
+ rows = crop(rows, 0, terminalRows);
- for (let i = 0; i < length; i++) {
+ const buffer = this._terminal.buffer;
+ const lines: string[] = new Array(rows);
+
+ for (let i = terminalRows - rows; i < terminalRows; i++) {
const line = buffer.getLine(i);
- lines[i] = line ? line.translateToString() : '';
+ lines[i - terminalRows + rows] = line ? line.translateToString() : '';
}
return lines.join('\r\n');
diff --git a/addons/xterm-addon-serialize/typings/xterm-addon-serialize.d.ts b/addons/xterm-addon-serialize/typings/xterm-addon-serialize.d.ts
index 78b615d7..9e7b500a 100644
--- a/addons/xterm-addon-serialize/typings/xterm-addon-serialize.d.ts
+++ b/addons/xterm-addon-serialize/typings/xterm-addon-serialize.d.ts
@@ -25,7 +25,7 @@ declare module 'xterm-addon-serialize' {
* to restore the state. The cursor will also be positioned to the correct cell.
* When restoring a terminal it is best to do before `Terminal.open` is called
* to avoid wasting CPU cycles rendering incomplete frames.
- * @param rows The number of rows to serialize, starting from the top of the
+ * @param rows The number of rows to serialize, starting from the bottom of the
* terminal. This defaults to the number of rows in the viewport.
*/
public serialize(rows?: number): string;
From f859bffa749b1dd6ccdd401e89d15964e3bc63ee Mon Sep 17 00:00:00 2001
From: javacs3
Date: Mon, 15 Jul 2019 09:42:56 +0800
Subject: [PATCH 07/47] fix lint error
---
addons/xterm-addon-serialize/src/SerializeAddon.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts
index 5d060949..9328e674 100644
--- a/addons/xterm-addon-serialize/src/SerializeAddon.ts
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts
@@ -5,8 +5,8 @@
import { Terminal, ITerminalAddon } from 'xterm';
-function crop(value: number, from: number, to: number) {
- return Math.max(from, Math.min(value, to))
+function crop(value: number, from: number, to: number): number {
+ return Math.max(from, Math.min(value, to));
}
export class SerializeAddon implements ITerminalAddon {
From a069df7a369512a1e2ea5c850dacee9ebe8396ec Mon Sep 17 00:00:00 2001
From: javacs3
Date: Sun, 4 Aug 2019 23:02:05 +0800
Subject: [PATCH 08/47] refactor SerializeAddons initial value validation
---
.../src/SerializeAddon.ts | 21 +++++++++++--------
.../xterm-addon-serialize/src/tsconfig.json | 7 +++++++
2 files changed, 19 insertions(+), 9 deletions(-)
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts
index 9328e674..4a051c33 100644
--- a/addons/xterm-addon-serialize/src/SerializeAddon.ts
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts
@@ -4,9 +4,14 @@
*/
import { Terminal, ITerminalAddon } from 'xterm';
+// import { IBufferLine } from 'common/Types';
-function crop(value: number, from: number, to: number): number {
- return Math.max(from, Math.min(value, to));
+function crop(value: number | undefined, low: number, high: number, initial: number): number {
+ if (value === undefined) {
+ return initial;
+ } else {
+ return Math.max(low, Math.min(value, high));
+ }
}
export class SerializeAddon implements ITerminalAddon {
@@ -23,18 +28,16 @@ export class SerializeAddon implements ITerminalAddon {
if (!this._terminal) {
throw new Error('Cannot use addon until it has been loaded');
}
- const terminalRows = this._terminal.rows;
- if (rows === undefined) {
- rows = terminalRows;
- }
- rows = crop(rows, 0, terminalRows);
+
+ const maxRows = this._terminal.rows;
+ rows = crop(rows, 0, maxRows, maxRows);
const buffer = this._terminal.buffer;
const lines: string[] = new Array(rows);
- for (let i = terminalRows - rows; i < terminalRows; i++) {
+ for (let i = maxRows - rows; i < maxRows; i++) {
const line = buffer.getLine(i);
- lines[i - terminalRows + rows] = line ? line.translateToString() : '';
+ lines[i - maxRows + rows] = line ? line.translateToString() : '';
}
return lines.join('\r\n');
diff --git a/addons/xterm-addon-serialize/src/tsconfig.json b/addons/xterm-addon-serialize/src/tsconfig.json
index 5539aa56..57f3d6ed 100644
--- a/addons/xterm-addon-serialize/src/tsconfig.json
+++ b/addons/xterm-addon-serialize/src/tsconfig.json
@@ -10,10 +10,17 @@
"outDir": "../out",
"sourceMap": true,
"removeComments": true,
+ "baseUrl": ".",
+ "paths": {
+ "common/*": [ "../../../src/common/*" ]
+ },
"strict": true
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
+ ],
+ "references": [
+ { "path": "../../../src/common" }
]
}
From f088a9953f1efdfc7f35b5d58d36d11cf466120e Mon Sep 17 00:00:00 2001
From: javacs3
Date: Mon, 5 Aug 2019 21:08:51 +0800
Subject: [PATCH 09/47] refactor SerializeAddon extract SerializeHandler
---
.../src/SerializeAddon.ts | 33 ++++++++++++-------
1 file changed, 22 insertions(+), 11 deletions(-)
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts
index 4a051c33..44438c98 100644
--- a/addons/xterm-addon-serialize/src/SerializeAddon.ts
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts
@@ -5,12 +5,29 @@
import { Terminal, ITerminalAddon } from 'xterm';
// import { IBufferLine } from 'common/Types';
+import { IBuffer } from 'common/buffer/Types';
function crop(value: number | undefined, low: number, high: number, initial: number): number {
if (value === undefined) {
return initial;
- } else {
- return Math.max(low, Math.min(value, high));
+ }
+ return Math.max(low, Math.min(value, high));
+}
+
+class SerializeHandler {
+ constructor(private _buffer: IBuffer) { }
+
+ serialize(start: number, end: number): string {
+ const rows = end - start;
+ const lines: string[] = new Array(rows);
+
+ for (let i = start; i < end; i++) {
+ const line = this._buffer.lines.get(i);
+
+ lines[i - start] = line ? line.translateToString() : '';
+ }
+
+ return lines.join('\r\n');
}
}
@@ -30,17 +47,11 @@ export class SerializeAddon implements ITerminalAddon {
}
const maxRows = this._terminal.rows;
+ const handler = new SerializeHandler((this._terminal)._core.buffer);
+
rows = crop(rows, 0, maxRows, maxRows);
- const buffer = this._terminal.buffer;
- const lines: string[] = new Array(rows);
-
- for (let i = maxRows - rows; i < maxRows; i++) {
- const line = buffer.getLine(i);
- lines[i - maxRows + rows] = line ? line.translateToString() : '';
- }
-
- return lines.join('\r\n');
+ return handler.serialize(maxRows - rows, maxRows);
}
public dispose(): void { }
From d81b4999dc8381a87d79188758670d1589981f81 Mon Sep 17 00:00:00 2001
From: javacs3
Date: Wed, 7 Aug 2019 22:56:18 +0800
Subject: [PATCH 10/47] add frontground/background color support for
SerializeAddon
---
.../src/SerializeAddon.api.ts | 307 +++++++++++++++++-
.../src/SerializeAddon.ts | 190 ++++++++++-
src/common/buffer/Constants.ts | 14 +-
3 files changed, 492 insertions(+), 19 deletions(-)
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.api.ts b/addons/xterm-addon-serialize/src/SerializeAddon.api.ts
index 5543696b..eec5b6fc 100644
--- a/addons/xterm-addon-serialize/src/SerializeAddon.api.ts
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.api.ts
@@ -21,6 +21,7 @@ describe('SerializeAddon', () => {
browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1,
slowMo: 80,
+ devtools: true,
args: [`--window-size=${width},${height}`]
});
page = (await browser.pages())[0];
@@ -40,8 +41,7 @@ describe('SerializeAddon', () => {
this.timeout(20000);
const rows = 10;
const cols = 10;
- const blankline = ' '.repeat(cols);
- const lines = newArray(blankline, rows);
+ const lines = newArray('', rows);
await openTerminal({ rows: rows, cols: cols, rendererType: 'dom' });
await page.evaluate(`
@@ -69,7 +69,7 @@ describe('SerializeAddon', () => {
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
});
- it('serialize n rows of content', async function (): Promise {
+ it('serialize half rows of content', async function (): Promise {
this.timeout(20000);
const rows = 10;
const halfRows = rows >> 1;
@@ -101,6 +101,254 @@ describe('SerializeAddon', () => {
assert.equal(await page.evaluate(`serializeAddon.serialize(0);`), '');
});
+
+ it('serialize all rows of content with color16', async function (): Promise {
+ this.timeout(20000);
+ const rows = 16;
+ const cols = 10;
+ const color16 = [
+ 30, 31, 32, 33, 34, 35, 36, 37, // Set foreground color
+ 40, 41, 42, 43, 44, 45, 46, 47 // Set background color
+ ];
+ const lines = newArray(
+ (index: number) => digitsString(cols, index, `\x1b[${color16[index % color16.length]}m`),
+ rows
+ );
+
+ await openTerminal({ rows: rows, cols: cols, rendererType: 'dom' });
+ await page.evaluate(`
+ window.serializeAddon = new SerializeAddon();
+ window.term.loadAddon(window.serializeAddon);
+ window.term.write(${util.inspect(lines.join('\r\n'))});
+ `);
+
+ assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
+ });
+
+ it('serialize all rows of content with fg/bg flags', async function (): Promise {
+ this.timeout(20000);
+ const cols = 10;
+ const line = '+'.repeat(cols);
+ const lines: string[] = [
+ mkSGR(FG_P16_GREEN) + line, // Workaround: If we clear all flags a the end, serialize will use \x1b[0m to clear instead of the sepcific disable sequence
+ mkSGR(INVERSE) + line,
+ mkSGR(BOLD) + line,
+ mkSGR(UNDERLINED) + line,
+ mkSGR(BLINK) + line,
+ mkSGR(INVISIBLE) + line,
+ mkSGR(NO_INVERSE) + line,
+ mkSGR(NO_BOLD) + line,
+ mkSGR(NO_UNDERLINED) + line,
+ mkSGR(NO_BLINK) + line,
+ mkSGR(NO_INVISIBLE) + line
+ ];
+ const rows = lines.length;
+
+ await openTerminal({ rows: rows, cols: cols, rendererType: 'dom' });
+ await page.evaluate(`
+ window.serializeAddon = new SerializeAddon();
+ window.term.loadAddon(window.serializeAddon);
+ window.term.write(${util.inspect(lines.join('\r\n'))});
+ `);
+
+ assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
+ });
+
+ it('serialize all rows of content with color256', async function (): Promise {
+ this.timeout(20000);
+ const rows = 32;
+ const cols = 10;
+ const lines = newArray(
+ (index: number) => digitsString(cols, index, `\x1b[38;5;${index}m`),
+ rows
+ );
+
+ await openTerminal({ rows: rows, cols: cols, rendererType: 'dom' });
+ await page.evaluate(`
+ window.serializeAddon = new SerializeAddon();
+ window.term.loadAddon(window.serializeAddon);
+ window.term.write(${util.inspect(lines.join('\r\n'))});
+ `);
+
+ assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
+ });
+
+ it('serialize all rows of content with color16 and style separately', async function (): Promise {
+ this.timeout(20000);
+ const cols = 10;
+ const line = '+'.repeat(cols);
+ const lines: string[] = [
+ mkSGR(FG_P16_RED) + line, // fg Red,
+ mkSGR(UNDERLINED) + line, // fg Red, Underlined
+ mkSGR(FG_P16_GREEN) + line, // fg Green, Underlined
+ mkSGR(INVERSE) + line, // fg Green, Underlined, Inverse
+ mkSGR(NO_INVERSE) + line, // fg Green, Underlined
+ mkSGR(INVERSE) + line, // fg Green, Underlined, Inverse
+ mkSGR(BG_P16_YELLOW) + line, // fg Green, bg Yellow, Underlined, Inverse
+ mkSGR(FG_RESET) + line, // bg Yellow, Underlined, Inverse
+ mkSGR(BG_RESET) + line, // Underlined, Inverse
+ mkSGR(NORMAL) + line // Back to normal
+ ];
+ const rows = lines.length;
+
+ await openTerminal({ rows: rows, cols: cols, rendererType: 'dom' });
+ await page.evaluate(`
+ window.serializeAddon = new SerializeAddon();
+ window.term.loadAddon(window.serializeAddon);
+ window.term.write(${util.inspect(lines.join('\r\n'))});
+ `);
+
+ assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
+ });
+
+ it('serialize all rows of content with color16 and style together', async function (): Promise {
+ this.timeout(20000);
+ const cols = 10;
+ const line = '+'.repeat(cols);
+ const lines: string[] = [
+ mkSGR(FG_P16_RED) + line, // fg Red
+ mkSGR(FG_P16_GREEN, BG_P16_YELLOW) + line, // fg Green, bg Yellow
+ mkSGR(UNDERLINED, ITALIC) + line, // fg Green, bg Yellow, Underlined, Italic
+ mkSGR(NO_UNDERLINED, NO_ITALIC) + line, // fg Green, bg Yellow
+ mkSGR(FG_RESET, ITALIC) + line, // bg Yellow, Italic
+ mkSGR(BG_RESET) + line, // Italic
+ mkSGR(NORMAL) + line, // Back to normal
+ mkSGR(FG_P16_RED) + line, // fg Red
+ mkSGR(FG_P16_GREEN, BG_P16_YELLOW) + line, // fg Green, bg Yellow
+ mkSGR(UNDERLINED, ITALIC) + line, // fg Green, bg Yellow, Underlined, Italic
+ mkSGR(NO_UNDERLINED, NO_ITALIC) + line, // fg Green, bg Yellow
+ mkSGR(FG_RESET, ITALIC) + line, // bg Yellow, Italic
+ mkSGR(BG_RESET) + line // Italic
+ ];
+ const rows = lines.length;
+
+ await openTerminal({ rows: rows, cols: cols, rendererType: 'dom' });
+ await page.evaluate(`
+ window.serializeAddon = new SerializeAddon();
+ window.term.loadAddon(window.serializeAddon);
+ window.term.write(${util.inspect(lines.join('\r\n'))});
+ `);
+
+ assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
+ });
+
+ it('serialize all rows of content with color256 and style separately', async function (): Promise {
+ this.timeout(20000);
+ const cols = 10;
+ const line = '+'.repeat(cols);
+ const lines: string[] = [
+ mkSGR(FG_P256_RED) + line, // fg Red 256,
+ mkSGR(UNDERLINED) + line, // fg Red 256, Underlined
+ mkSGR(FG_P256_GREEN) + line, // fg Green 256, Underlined
+ mkSGR(INVERSE) + line, // fg Green 256, Underlined, Inverse
+ mkSGR(NO_INVERSE) + line, // fg Green 256, Underlined
+ mkSGR(INVERSE) + line, // fg Green 256, Underlined, Inverse
+ mkSGR(BG_P256_YELLOW) + line, // fg Green 256, bg Yellow 256, Underlined, Inverse
+ mkSGR(FG_RESET) + line, // bg Yellow 256, Underlined, Inverse
+ mkSGR(BG_RESET) + line, // Underlined, Inverse
+ mkSGR(NORMAL) + line // Back to normal
+ ];
+ const rows = lines.length;
+
+ await openTerminal({ rows: rows, cols: cols, rendererType: 'dom' });
+ await page.evaluate(`
+ window.serializeAddon = new SerializeAddon();
+ window.term.loadAddon(window.serializeAddon);
+ window.term.write(${util.inspect(lines.join('\r\n'))});
+ `);
+
+ assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
+ });
+
+ it('serialize all rows of content with color256 and style together', async function (): Promise {
+ this.timeout(20000);
+ const cols = 10;
+ const line = '+'.repeat(cols);
+ const lines: string[] = [
+ mkSGR(FG_P256_RED) + line, // fg Red 256
+ mkSGR(FG_P256_GREEN, BG_P256_YELLOW) + line, // fg Green 256, bg Yellow 256
+ mkSGR(UNDERLINED, ITALIC) + line, // fg Green 256, bg Yellow 256, Underlined, Italic
+ mkSGR(NO_UNDERLINED, NO_ITALIC) + line, // fg Green 256, bg Yellow 256
+ mkSGR(FG_RESET, ITALIC) + line, // bg Yellow 256, Italic
+ mkSGR(BG_RESET) + line, // Italic
+ mkSGR(NORMAL) + line, // Back to normal
+ mkSGR(FG_P256_RED) + line, // fg Red 256
+ mkSGR(FG_P256_GREEN, BG_P256_YELLOW) + line, // fg Green 256, bg Yellow 256
+ mkSGR(UNDERLINED, ITALIC) + line, // fg Green 256, bg Yellow 256, Underlined, Italic
+ mkSGR(NO_UNDERLINED, NO_ITALIC) + line, // fg Green 256, bg Yellow 256
+ mkSGR(FG_RESET, ITALIC) + line, // bg Yellow 256, Italic
+ mkSGR(BG_RESET) + line // Italic
+ ];
+ const rows = lines.length;
+
+ await openTerminal({ rows: rows, cols: cols, rendererType: 'dom' });
+ await page.evaluate(`
+ window.serializeAddon = new SerializeAddon();
+ window.term.loadAddon(window.serializeAddon);
+ window.term.write(${util.inspect(lines.join('\r\n'))});
+ `);
+
+ assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
+ });
+
+ it('serialize all rows of content with colorRGB and style separately', async function (): Promise {
+ this.timeout(20000);
+ const cols = 10;
+ const line = '+'.repeat(cols);
+ const lines: string[] = [
+ mkSGR(FG_RGB_RED) + line, // fg Red RGB,
+ mkSGR(UNDERLINED) + line, // fg Red RGB, Underlined
+ mkSGR(FG_RGB_GREEN) + line, // fg Green RGB, Underlined
+ mkSGR(INVERSE) + line, // fg Green RGB, Underlined, Inverse
+ mkSGR(NO_INVERSE) + line, // fg Green RGB, Underlined
+ mkSGR(INVERSE) + line, // fg Green RGB, Underlined, Inverse
+ mkSGR(BG_RGB_YELLOW) + line, // fg Green RGB, bg Yellow RGB, Underlined, Inverse
+ mkSGR(FG_RESET) + line, // bg Yellow RGB, Underlined, Inverse
+ mkSGR(BG_RESET) + line, // Underlined, Inverse
+ mkSGR(NORMAL) + line // Back to normal
+ ];
+ const rows = lines.length;
+
+ await openTerminal({ rows: rows, cols: cols, rendererType: 'dom' });
+ await page.evaluate(`
+ window.serializeAddon = new SerializeAddon();
+ window.term.loadAddon(window.serializeAddon);
+ window.term.write(${util.inspect(lines.join('\r\n'))});
+ `);
+
+ assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
+ });
+
+ it('serialize all rows of content with colorRGB and style together', async function (): Promise {
+ this.timeout(20000);
+ const cols = 10;
+ const line = '+'.repeat(cols);
+ const lines: string[] = [
+ mkSGR(FG_RGB_RED) + line, // fg Red RGB
+ mkSGR(FG_RGB_GREEN, BG_RGB_YELLOW) + line, // fg Green RGB, bg Yellow RGB
+ mkSGR(UNDERLINED, ITALIC) + line, // fg Green RGB, bg Yellow RGB, Underlined, Italic
+ mkSGR(NO_UNDERLINED, NO_ITALIC) + line, // fg Green RGB, bg Yellow RGB
+ mkSGR(FG_RESET, ITALIC) + line, // bg Yellow RGB, Italic
+ mkSGR(BG_RESET) + line, // Italic
+ mkSGR(NORMAL) + line, // Back to normal
+ mkSGR(FG_RGB_RED) + line, // fg Red RGB
+ mkSGR(FG_RGB_GREEN, BG_RGB_YELLOW) + line, // fg Green RGB, bg Yellow RGB
+ mkSGR(UNDERLINED, ITALIC) + line, // fg Green RGB, bg Yellow RGB, Underlined, Italic
+ mkSGR(NO_UNDERLINED, NO_ITALIC) + line, // fg Green RGB, bg Yellow RGB
+ mkSGR(FG_RESET, ITALIC) + line, // bg Yellow RGB, Italic
+ mkSGR(BG_RESET) + line // Italic
+ ];
+ const rows = lines.length;
+
+ await openTerminal({ rows: rows, cols: cols, rendererType: 'dom' });
+ await page.evaluate(`
+ window.serializeAddon = new SerializeAddon();
+ window.term.loadAddon(window.serializeAddon);
+ window.term.write(${util.inspect(lines.join('\r\n'))});
+ `);
+
+ assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
+ });
});
async function openTerminal(options: ITerminalOptions = {}): Promise {
@@ -125,10 +373,57 @@ function newArray(initial: T | ((index: number) => T), count: number): T[] {
return array;
}
-function digitsString(length: number, from: number = 0): string {
- let s = '';
+function digitsString(length: number, from: number = 0, sgr: string = ''): string {
+ let s = sgr;
for (let i = 0; i < length; i++) {
- s += (from++) % 10;
+ s += `${(from++) % 10}`;
}
return s;
}
+
+function mkSGR(...seq: string[]): string {
+ return `\x1b[${seq.join(';')}m`;
+}
+
+const NORMAL = '0';
+
+const FG_P16_RED = '31';
+const FG_P16_GREEN = '32';
+const FG_P16_YELLOW = '33';
+const FG_P256_RED = '38;5;1';
+const FG_P256_GREEN = '38;5;2';
+const FG_P256_YELLOW = '38;5;3';
+const FG_RGB_RED = '38;2;255;0;0';
+const FG_RGB_GREEN = '38;2;0;255;0';
+const FG_RGB_YELLOW = '38;2;255;255;0';
+const FG_RESET = '39';
+
+
+const BG_P16_RED = '41';
+const BG_P16_GREEN = '42';
+const BG_P16_YELLOW = '43';
+const BG_P256_RED = '48;5;1';
+const BG_P256_GREEN = '48;5;2';
+const BG_P256_YELLOW = '48;5;3';
+const BG_RGB_RED = '48;2;255;0;0';
+const BG_RGB_GREEN = '48;2;0;255;0';
+const BG_RGB_YELLOW = '48;2;255;255;0';
+const BG_RESET = '49';
+
+const INVERSE = '7';
+const BOLD = '1';
+const UNDERLINED = '4';
+const BLINK = '5';
+const INVISIBLE = '8';
+
+const NO_INVERSE = '27';
+const NO_BOLD = '22';
+const NO_UNDERLINED = '24';
+const NO_BLINK = '25';
+const NO_INVISIBLE = '28';
+
+const ITALIC = '3';
+const DIM = '2';
+
+const NO_ITALIC = '23';
+const NO_DIM = '22';
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts
index 44438c98..88ea852f 100644
--- a/addons/xterm-addon-serialize/src/SerializeAddon.ts
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts
@@ -4,8 +4,10 @@
*/
import { Terminal, ITerminalAddon } from 'xterm';
-// import { IBufferLine } from 'common/Types';
+import { ICellData } from 'common/Types';
+import { CellData } from 'common/buffer/CellData';
import { IBuffer } from 'common/buffer/Types';
+import { Attributes, FgFlags, BgFlags } from 'common/buffer/Constants';
function crop(value: number | undefined, low: number, high: number, initial: number): number {
if (value === undefined) {
@@ -14,20 +16,184 @@ function crop(value: number | undefined, low: number, high: number, initial: num
return Math.max(low, Math.min(value, high));
}
-class SerializeHandler {
+class BaseSerializeHandler {
constructor(private _buffer: IBuffer) { }
- serialize(start: number, end: number): string {
- const rows = end - start;
- const lines: string[] = new Array(rows);
+ serialize(startRow: number, endRow: number): string {
+ let oldCell = new CellData();
- for (let i = start; i < end; i++) {
- const line = this._buffer.lines.get(i);
+ this._serializeStart(endRow - startRow);
- lines[i - start] = line ? line.translateToString() : '';
+ for (let row = startRow; row < endRow; row++) {
+ const line = this._buffer.lines.get(row);
+
+ this._lineStart(row);
+
+ if (line) {
+ for (let col = 0; col < line.length; col++) {
+ const cell = new CellData();
+
+ line.loadCell(col, cell);
+
+ if (oldCell.fg !== cell.fg) {
+ this._fgChanged(cell, oldCell, row, col);
+ }
+ if (oldCell.bg !== cell.bg) {
+ this._bgChanged(cell, oldCell, row, col);
+ }
+
+ this._cellChanged(cell, oldCell, row, col);
+
+ oldCell = cell;
+ }
+ }
+
+ this._lineEnd(row);
}
- return lines.join('\r\n');
+ this._serializeEnd();
+
+ return this._serializeFinished();
+ }
+
+ protected _cellChanged(cell: ICellData, oldCell: ICellData, row: number, col: number): void { }
+
+ protected _fgChanged(cell: ICellData, oldCell: ICellData, row: number, col: number): void { }
+
+ protected _bgChanged(cell: ICellData, oldCell: ICellData, row: number, col: number): void { }
+
+ protected _lineStart(row: number): void { }
+
+ protected _lineEnd(row: number): void { }
+
+ protected _serializeStart(rows: number): void { }
+
+ protected _serializeEnd(): void { }
+
+ protected _serializeFinished(): string { return ''; }
+}
+
+const FG_FM_MASK = FgFlags.FM_MASK;
+const BG_FM_MASK = BgFlags.FM_MASK;
+const COLOR_MASK = Attributes.CM_MASK | Attributes.RGB_MASK;
+
+class StringSerializeHandler extends BaseSerializeHandler {
+ private _rowIndex: number = 0;
+ private _allRows: string[] = new Array();
+ private _currentRow: string = '';
+ private _sgrSeq: string[] = [];
+
+ constructor(buffer: IBuffer) {
+ super(buffer);
+ }
+
+ protected _serializeStart(rows: number): void {
+ this._allRows = new Array(rows);
+ }
+
+ protected _lineEnd(row: number): void {
+ this._allRows[this._rowIndex++] = this._currentRow;
+ this._currentRow = '';
+ }
+
+ protected _fgChanged(cell: ICellData, oldCell: ICellData, row: number, col: number): void {
+ const fgFlagsChanged = (cell.fg ^ oldCell.fg) & FG_FM_MASK;
+ const fgColorChanged = (cell.fg ^ oldCell.fg) & COLOR_MASK;
+ const sgrSeq = this._sgrSeq;
+
+ if ((cell.fg === 0) && (cell.bg === 0)) {
+ return;
+ }
+
+ if (fgFlagsChanged) {
+ if (fgFlagsChanged & FgFlags.INVERSE) {
+ sgrSeq.push(cell.isInverse() ? '7' : '27');
+ }
+ if (fgFlagsChanged & FgFlags.BOLD) {
+ sgrSeq.push(cell.isBold() ? '1' : '22');
+ }
+ if (fgFlagsChanged & FgFlags.UNDERLINE) {
+ sgrSeq.push(cell.isUnderline() ? '4' : '24');
+ }
+ if (fgFlagsChanged & FgFlags.BLINK) {
+ sgrSeq.push(cell.isBlink() ? '5' : '25');
+ }
+ if (fgFlagsChanged & FgFlags.INVISIBLE) {
+ sgrSeq.push(cell.isInvisible() ? '8' : '28');
+ }
+ }
+
+ if (fgColorChanged) {
+ const fgColor = cell.getFgColor();
+
+ if (cell.isFgDefault()) {
+ sgrSeq.push('39');
+ } else if (cell.isFgPalette()) {
+ switch (cell.getFgColorMode()) {
+ case Attributes.CM_P16: sgrSeq.push(`${30 + fgColor}`); break;
+ case Attributes.CM_P256: sgrSeq.push(`38;5;${fgColor}`); break;
+ }
+ } else if (cell.isFgRGB()) {
+ const [r, g, b] = CellData.toColorRGB(fgColor);
+ sgrSeq.push(`38;2;${r};${g};${b}`);
+ }
+ }
+ }
+
+ protected _bgChanged(cell: ICellData, oldCell: ICellData, row: number, col: number): void {
+ const bgFlagsChanged = (cell.bg ^ oldCell.bg) & BG_FM_MASK;
+ const bgColorChanged = (cell.bg ^ oldCell.bg) & COLOR_MASK;
+ const sgrSeq = this._sgrSeq;
+
+ if ((cell.bg === 0) && (cell.fg === 0)) {
+ return;
+ }
+
+ if (bgFlagsChanged) {
+ if (bgFlagsChanged & BgFlags.ITALIC) {
+ sgrSeq.push(cell.isItalic() ? '3' : '23');
+ }
+ if (bgFlagsChanged & BgFlags.DIM) {
+ sgrSeq.push(cell.isDim() ? '2' : '22');
+ }
+ }
+
+ if (bgColorChanged) {
+ const bgColor = cell.getBgColor();
+
+ if (cell.isBgDefault()) {
+ sgrSeq.push('49');
+ } else if (cell.isBgPalette()) {
+ switch (cell.getBgColorMode()) {
+ case Attributes.CM_P16: sgrSeq.push(`${40 + bgColor}`); break;
+ case Attributes.CM_P256: sgrSeq.push(`48;5;${bgColor}`); break;
+ }
+ } else if (cell.isFgRGB()) {
+ const [r, g, b] = CellData.toColorRGB(bgColor);
+ sgrSeq.push(`48;2;${r};${g};${b}`);
+ }
+ }
+ }
+
+ protected _cellChanged(cell: ICellData, oldCell: ICellData, row: number, col: number): void {
+ const fgChanged = cell.fg !== oldCell.fg;
+ const bgChanged = cell.bg !== oldCell.bg;
+ const isfgBgNormal = (cell.fg === 0) && (cell.bg === 0);
+
+ if ((fgChanged || bgChanged) && isfgBgNormal) {
+ this._currentRow += '\x1b[0m';
+ }
+
+ if (this._sgrSeq.length) {
+ this._currentRow += `\x1b[${this._sgrSeq.join(';')}m`;
+ this._sgrSeq = [];
+ }
+
+ this._currentRow += cell.getChars();
+ }
+
+ protected _serializeFinished(): string {
+ return this._allRows.join('\r\n');
}
}
@@ -41,13 +207,15 @@ export class SerializeAddon implements ITerminalAddon {
}
public serialize(rows?: number): string {
- // TODO: Add frontground/background color support later
+ // TODO: Add re-position cursor support
+ // TODO: Add word wrap mode support
+ // TODO: Add combinedData support
if (!this._terminal) {
throw new Error('Cannot use addon until it has been loaded');
}
const maxRows = this._terminal.rows;
- const handler = new SerializeHandler((this._terminal)._core.buffer);
+ const handler = new StringSerializeHandler((this._terminal)._core.buffer);
rows = crop(rows, 0, maxRows, maxRows);
diff --git a/src/common/buffer/Constants.ts b/src/common/buffer/Constants.ts
index 276a5c54..81ac773a 100644
--- a/src/common/buffer/Constants.ts
+++ b/src/common/buffer/Constants.ts
@@ -116,7 +116,12 @@ export const enum FgFlags {
BOLD = 0x8000000,
UNDERLINE = 0x10000000,
BLINK = 0x20000000,
- INVISIBLE = 0x40000000
+ INVISIBLE = 0x40000000,
+
+ /**
+ * bit 27..31 (32th bit unused)
+ */
+ FM_MASK = 0x7C000000
}
export const enum BgFlags {
@@ -124,5 +129,10 @@ export const enum BgFlags {
* bit 27..32 (upper 4 unused)
*/
ITALIC = 0x4000000,
- DIM = 0x8000000
+ DIM = 0x8000000,
+
+ /**
+ * bit 27..32 (upper 4 unused)
+ */
+ FM_MASK = 0xFC000000
}
From 92709110efe443cc2445db0e987ec7271b2aa54a Mon Sep 17 00:00:00 2001
From: javacs3
Date: Thu, 8 Aug 2019 23:04:46 +0800
Subject: [PATCH 11/47] add SerializeAddon in demo page
---
demo/client.ts | 17 +++++++++++++++++
demo/index.html | 6 ++++++
demo/style.css | 6 ++++++
3 files changed, 29 insertions(+)
diff --git a/demo/client.ts b/demo/client.ts
index 1ca35a6a..91c5d33d 100644
--- a/demo/client.ts
+++ b/demo/client.ts
@@ -43,6 +43,7 @@ declare let window: IWindowWithTerminal;
let term;
let fitAddon: FitAddon;
let searchAddon: SearchAddon;
+let serializeAddon: SerializeAddon;
let protocol;
let socketURL;
let socket;
@@ -96,6 +97,7 @@ if (document.location.pathname === '/test') {
createTerminal();
document.getElementById('dispose').addEventListener('click', disposeRecreateButtonHandler);
document.getElementById('webgl').addEventListener('click', () => term.loadAddon(new WebglAddon()));
+ document.getElementById('serialize').addEventListener('click', serializeButtonHandler);
}
function createTerminal(): void {
@@ -116,6 +118,8 @@ function createTerminal(): void {
typedTerm.loadAddon(searchAddon);
fitAddon = new FitAddon();
typedTerm.loadAddon(fitAddon);
+ serializeAddon = new SerializeAddon();
+ typedTerm.loadAddon(serializeAddon);
window.term = term; // Expose `term` to window for debugging purposes
term.onResize((size: { cols: number, rows: number }) => {
@@ -323,3 +327,16 @@ function updateTerminalSize(): void {
terminalContainer.style.height = height;
fitAddon.fit();
}
+
+function serializeButtonHandler(): void {
+ const output = serializeAddon.serialize();
+ const outputString = JSON.stringify(output);
+ console.log('serialize output', outputString);
+
+ document.getElementById('serialize-output').innerText = outputString;
+
+ if ((document.getElementById('write-to-terminal') as HTMLInputElement).checked) {
+ term.reset();
+ term.write(output);
+ }
+}
diff --git a/demo/index.html b/demo/index.html
index ef8f3891..4a4335ee 100644
--- a/demo/index.html
+++ b/demo/index.html
@@ -20,6 +20,12 @@
+
+
SerializeAddon
+
+
+
+
Options
diff --git a/demo/style.css b/demo/style.css
index b061dfcb..12728289 100644
--- a/demo/style.css
+++ b/demo/style.css
@@ -30,3 +30,9 @@ p {
padding-left: 20px;
vertical-align: top;
}
+
+code {
+ padding: 2px 4px;
+ color: #c7254e;
+ background-color: #f9f2f4;
+}
From 693f30477efb279d77ed65f25c0d4653c8f4c767 Mon Sep 17 00:00:00 2001
From: javacs3
Date: Fri, 9 Aug 2019 16:34:54 +0800
Subject: [PATCH 12/47] add trim last empty lines support for SerializeAddon
---
.../src/SerializeAddon.api.ts | 31 +++++++++++++++++--
.../src/SerializeAddon.ts | 10 +++++-
2 files changed, 38 insertions(+), 3 deletions(-)
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.api.ts b/addons/xterm-addon-serialize/src/SerializeAddon.api.ts
index eec5b6fc..79343fbe 100644
--- a/addons/xterm-addon-serialize/src/SerializeAddon.api.ts
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.api.ts
@@ -41,7 +41,6 @@ describe('SerializeAddon', () => {
this.timeout(20000);
const rows = 10;
const cols = 10;
- const lines = newArray('', rows);
await openTerminal({ rows: rows, cols: cols, rendererType: 'dom' });
await page.evaluate(`
@@ -49,7 +48,35 @@ describe('SerializeAddon', () => {
window.term.loadAddon(window.serializeAddon);
`);
- assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
+ assert.equal(await page.evaluate(`serializeAddon.serialize();`), '');
+ });
+
+ it('trim last empty lines', async function (): Promise {
+ this.timeout(20000);
+ const cols = 10;
+ const lines = [
+ '',
+ '',
+ digitsString(cols),
+ digitsString(cols),
+ '',
+ '',
+ digitsString(cols),
+ digitsString(cols),
+ '',
+ '',
+ ''
+ ];
+ const rows = lines.length;
+
+ await openTerminal({ rows: rows, cols: cols, rendererType: 'dom' });
+ await page.evaluate(`
+ window.serializeAddon = new SerializeAddon();
+ window.term.loadAddon(window.serializeAddon);
+ window.term.write(${util.inspect(lines.join('\r\n'))});
+ `);
+
+ assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.slice(0, 8).join('\r\n'));
});
it('digits content', async function (): Promise {
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts
index 88ea852f..564bbddc 100644
--- a/addons/xterm-addon-serialize/src/SerializeAddon.ts
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts
@@ -193,7 +193,15 @@ class StringSerializeHandler extends BaseSerializeHandler {
}
protected _serializeFinished(): string {
- return this._allRows.join('\r\n');
+ let rowEnd = this._allRows.length;
+
+ for (; rowEnd > 0; rowEnd--) {
+ if (this._allRows[rowEnd - 1]) {
+ break;
+ }
+ }
+
+ return this._allRows.slice(0, rowEnd).join('\r\n');
}
}
From f12120b0552ce6f1551f68b91d81f81d577c7b9e Mon Sep 17 00:00:00 2001
From: javacs3
Date: Fri, 9 Aug 2019 19:59:48 +0800
Subject: [PATCH 13/47] fix demo page tag won't display multi space
chars
---
demo/index.html | 4 ++--
demo/style.css | 10 ++++++++--
2 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/demo/index.html b/demo/index.html
index 4a4335ee..0fee6f4a 100644
--- a/demo/index.html
+++ b/demo/index.html
@@ -23,8 +23,8 @@
diff --git a/demo/style.css b/demo/style.css
index 12728289..1ab5dc3c 100644
--- a/demo/style.css
+++ b/demo/style.css
@@ -31,8 +31,14 @@ p {
vertical-align: top;
}
-code {
- padding: 2px 4px;
+pre {
+ display: block;
+ padding: 9.5px;
+ font-size: 13px;
color: #c7254e;
background-color: #f9f2f4;
+ word-break: break-all;
+ word-wrap: break-word;
+ white-space: pre-wrap;
+ word-wrap: break-word;
}
From 6710c13cc225cc343e96ccc51b4a2de41d8b7db4 Mon Sep 17 00:00:00 2001
From: javacs3
Date: Fri, 9 Aug 2019 21:58:27 +0800
Subject: [PATCH 14/47] fix cell.getFgColor/getBgColor return color256 code
even it's color16 mode leading to serialize return wrong sequence
---
.../src/SerializeAddon.api.ts | 6 +++--
.../src/SerializeAddon.ts | 22 +++++++++++++++++--
2 files changed, 24 insertions(+), 4 deletions(-)
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.api.ts b/addons/xterm-addon-serialize/src/SerializeAddon.api.ts
index 79343fbe..6fbf9af2 100644
--- a/addons/xterm-addon-serialize/src/SerializeAddon.api.ts
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.api.ts
@@ -131,12 +131,14 @@ describe('SerializeAddon', () => {
it('serialize all rows of content with color16', async function (): Promise {
this.timeout(20000);
- const rows = 16;
const cols = 10;
const color16 = [
30, 31, 32, 33, 34, 35, 36, 37, // Set foreground color
- 40, 41, 42, 43, 44, 45, 46, 47 // Set background color
+ 90, 91, 92, 93, 94, 95, 96, 97,
+ 40, 41, 42, 43, 44, 45, 46, 47, // Set background color
+ 100, 101, 103, 104, 105, 106, 107
];
+ const rows = color16.length;
const lines = newArray(
(index: number) => digitsString(cols, index, `\x1b[${color16[index % color16.length]}m`),
rows
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts
index 564bbddc..6143d94f 100644
--- a/addons/xterm-addon-serialize/src/SerializeAddon.ts
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts
@@ -77,6 +77,24 @@ const FG_FM_MASK = FgFlags.FM_MASK;
const BG_FM_MASK = BgFlags.FM_MASK;
const COLOR_MASK = Attributes.CM_MASK | Attributes.RGB_MASK;
+function fgColor256to16(c: number): number {
+ if (0 <= c && c <= 7) {
+ return 30 + c;
+ } else if (8 <= c && c <= 15) {
+ return 82 + c;
+ }
+ return -1;
+}
+
+function bgColor256to16(c: number): number {
+ if (0 <= c && c <= 7) {
+ return 40 + c;
+ } else if (8 <= c && c <= 15) {
+ return 92 + c;
+ }
+ return -1;
+}
+
class StringSerializeHandler extends BaseSerializeHandler {
private _rowIndex: number = 0;
private _allRows: string[] = new Array();
@@ -130,7 +148,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
sgrSeq.push('39');
} else if (cell.isFgPalette()) {
switch (cell.getFgColorMode()) {
- case Attributes.CM_P16: sgrSeq.push(`${30 + fgColor}`); break;
+ case Attributes.CM_P16: sgrSeq.push(fgColor256to16(fgColor).toString()); break;
case Attributes.CM_P256: sgrSeq.push(`38;5;${fgColor}`); break;
}
} else if (cell.isFgRGB()) {
@@ -165,7 +183,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
sgrSeq.push('49');
} else if (cell.isBgPalette()) {
switch (cell.getBgColorMode()) {
- case Attributes.CM_P16: sgrSeq.push(`${40 + bgColor}`); break;
+ case Attributes.CM_P16: sgrSeq.push(bgColor256to16(bgColor).toString()); break;
case Attributes.CM_P256: sgrSeq.push(`48;5;${bgColor}`); break;
}
} else if (cell.isFgRGB()) {
From 3cba961dad5c94c9328c076668a9e1d6ea99831c Mon Sep 17 00:00:00 2001
From: javacs3
Date: Sun, 11 Aug 2019 00:13:53 +0800
Subject: [PATCH 15/47] refactor SerializeAddon: use public api as much as
possible
---
.../src/SerializeAddon.ts | 102 +++++++++++-------
src/public/Terminal.ts | 35 +++++-
typings/xterm.d.ts | 27 +++++
3 files changed, 123 insertions(+), 41 deletions(-)
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts
index 6143d94f..ae72ce4b 100644
--- a/addons/xterm-addon-serialize/src/SerializeAddon.ts
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts
@@ -3,10 +3,7 @@
* @license MIT
*/
-import { Terminal, ITerminalAddon } from 'xterm';
-import { ICellData } from 'common/Types';
-import { CellData } from 'common/buffer/CellData';
-import { IBuffer } from 'common/buffer/Types';
+import { Terminal, ITerminalAddon, IBuffer, IBufferCell } from 'xterm';
import { Attributes, FgFlags, BgFlags } from 'common/buffer/Constants';
function crop(value: number | undefined, low: number, high: number, initial: number): number {
@@ -16,24 +13,50 @@ function crop(value: number | undefined, low: number, high: number, initial: num
return Math.max(low, Math.min(value, high));
}
+class NullBufferCell implements IBufferCell {
+ fg: number = 0; bg: number = 0;
+ char: string = '';
+ width: number = 0;
+ isInverse: number = 0;
+ isBold: number = 0;
+ isUnderline: number = 0;
+ isBlink: number = 0;
+ isInvisible: number = 0;
+ isItalic: number = 0;
+ isDim: number = 0;
+ fgColorMode: number = 0;
+ bgColorMode: number = 0;
+ isFgRGB: boolean = false;
+ isBgRGB: boolean = false;
+ isFgPalette: boolean = false;
+ isBgPalette: boolean = false;
+ isFgDefault: boolean = false;
+ isBgDefault: boolean = false;
+ fgColor: number = 0;
+ bgColor: number = 0;
+}
+
class BaseSerializeHandler {
constructor(private _buffer: IBuffer) { }
serialize(startRow: number, endRow: number): string {
- let oldCell = new CellData();
+ let oldCell: IBufferCell = new NullBufferCell();
this._serializeStart(endRow - startRow);
for (let row = startRow; row < endRow; row++) {
- const line = this._buffer.lines.get(row);
+ const line = this._buffer.getLine(row);
this._lineStart(row);
if (line) {
for (let col = 0; col < line.length; col++) {
- const cell = new CellData();
+ const cell = line.getCell(col);
- line.loadCell(col, cell);
+ if (!cell) {
+ console.warn(`Can't get cell at row=${row}, col=${col}`);
+ continue;
+ }
if (oldCell.fg !== cell.fg) {
this._fgChanged(cell, oldCell, row, col);
@@ -41,7 +64,6 @@ class BaseSerializeHandler {
if (oldCell.bg !== cell.bg) {
this._bgChanged(cell, oldCell, row, col);
}
-
this._cellChanged(cell, oldCell, row, col);
oldCell = cell;
@@ -56,11 +78,11 @@ class BaseSerializeHandler {
return this._serializeFinished();
}
- protected _cellChanged(cell: ICellData, oldCell: ICellData, row: number, col: number): void { }
+ protected _cellChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
- protected _fgChanged(cell: ICellData, oldCell: ICellData, row: number, col: number): void { }
+ protected _fgChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
- protected _bgChanged(cell: ICellData, oldCell: ICellData, row: number, col: number): void { }
+ protected _bgChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
protected _lineStart(row: number): void { }
@@ -95,6 +117,14 @@ function bgColor256to16(c: number): number {
return -1;
}
+function hex2rgb(value: number): [number, number, number] {
+ return [
+ value >>> Attributes.RED_SHIFT & 255,
+ value >>> Attributes.GREEN_SHIFT & 255,
+ value & 255
+ ];
+}
+
class StringSerializeHandler extends BaseSerializeHandler {
private _rowIndex: number = 0;
private _allRows: string[] = new Array();
@@ -114,7 +144,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
this._currentRow = '';
}
- protected _fgChanged(cell: ICellData, oldCell: ICellData, row: number, col: number): void {
+ protected _fgChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
const fgFlagsChanged = (cell.fg ^ oldCell.fg) & FG_FM_MASK;
const fgColorChanged = (cell.fg ^ oldCell.fg) & COLOR_MASK;
const sgrSeq = this._sgrSeq;
@@ -125,40 +155,40 @@ class StringSerializeHandler extends BaseSerializeHandler {
if (fgFlagsChanged) {
if (fgFlagsChanged & FgFlags.INVERSE) {
- sgrSeq.push(cell.isInverse() ? '7' : '27');
+ sgrSeq.push(cell.isInverse ? '7' : '27');
}
if (fgFlagsChanged & FgFlags.BOLD) {
- sgrSeq.push(cell.isBold() ? '1' : '22');
+ sgrSeq.push(cell.isBold ? '1' : '22');
}
if (fgFlagsChanged & FgFlags.UNDERLINE) {
- sgrSeq.push(cell.isUnderline() ? '4' : '24');
+ sgrSeq.push(cell.isUnderline ? '4' : '24');
}
if (fgFlagsChanged & FgFlags.BLINK) {
- sgrSeq.push(cell.isBlink() ? '5' : '25');
+ sgrSeq.push(cell.isBlink ? '5' : '25');
}
if (fgFlagsChanged & FgFlags.INVISIBLE) {
- sgrSeq.push(cell.isInvisible() ? '8' : '28');
+ sgrSeq.push(cell.isInvisible ? '8' : '28');
}
}
if (fgColorChanged) {
- const fgColor = cell.getFgColor();
+ const fgColor = cell.fgColor;
- if (cell.isFgDefault()) {
+ if (cell.isFgDefault) {
sgrSeq.push('39');
- } else if (cell.isFgPalette()) {
- switch (cell.getFgColorMode()) {
+ } else if (cell.isFgPalette) {
+ switch (cell.fgColorMode) {
case Attributes.CM_P16: sgrSeq.push(fgColor256to16(fgColor).toString()); break;
case Attributes.CM_P256: sgrSeq.push(`38;5;${fgColor}`); break;
}
- } else if (cell.isFgRGB()) {
- const [r, g, b] = CellData.toColorRGB(fgColor);
+ } else if (cell.isFgRGB) {
+ const [r, g, b] = hex2rgb(fgColor);
sgrSeq.push(`38;2;${r};${g};${b}`);
}
}
}
- protected _bgChanged(cell: ICellData, oldCell: ICellData, row: number, col: number): void {
+ protected _bgChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
const bgFlagsChanged = (cell.bg ^ oldCell.bg) & BG_FM_MASK;
const bgColorChanged = (cell.bg ^ oldCell.bg) & COLOR_MASK;
const sgrSeq = this._sgrSeq;
@@ -169,31 +199,31 @@ class StringSerializeHandler extends BaseSerializeHandler {
if (bgFlagsChanged) {
if (bgFlagsChanged & BgFlags.ITALIC) {
- sgrSeq.push(cell.isItalic() ? '3' : '23');
+ sgrSeq.push(cell.isItalic ? '3' : '23');
}
if (bgFlagsChanged & BgFlags.DIM) {
- sgrSeq.push(cell.isDim() ? '2' : '22');
+ sgrSeq.push(cell.isDim ? '2' : '22');
}
}
if (bgColorChanged) {
- const bgColor = cell.getBgColor();
+ const bgColor = cell.bgColor;
- if (cell.isBgDefault()) {
+ if (cell.isBgDefault) {
sgrSeq.push('49');
- } else if (cell.isBgPalette()) {
- switch (cell.getBgColorMode()) {
+ } else if (cell.isBgPalette) {
+ switch (cell.bgColorMode) {
case Attributes.CM_P16: sgrSeq.push(bgColor256to16(bgColor).toString()); break;
case Attributes.CM_P256: sgrSeq.push(`48;5;${bgColor}`); break;
}
- } else if (cell.isFgRGB()) {
- const [r, g, b] = CellData.toColorRGB(bgColor);
+ } else if (cell.isFgRGB) {
+ const [r, g, b] = hex2rgb(bgColor);
sgrSeq.push(`48;2;${r};${g};${b}`);
}
}
}
- protected _cellChanged(cell: ICellData, oldCell: ICellData, row: number, col: number): void {
+ protected _cellChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
const fgChanged = cell.fg !== oldCell.fg;
const bgChanged = cell.bg !== oldCell.bg;
const isfgBgNormal = (cell.fg === 0) && (cell.bg === 0);
@@ -207,7 +237,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
this._sgrSeq = [];
}
- this._currentRow += cell.getChars();
+ this._currentRow += cell.char;
}
protected _serializeFinished(): string {
@@ -241,7 +271,7 @@ export class SerializeAddon implements ITerminalAddon {
}
const maxRows = this._terminal.rows;
- const handler = new StringSerializeHandler((this._terminal)._core.buffer);
+ const handler = new StringSerializeHandler(this._terminal.buffer);
rows = crop(rows, 0, maxRows, maxRows);
diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts
index 5d270ca7..20ecf7e9 100644
--- a/src/public/Terminal.ts
+++ b/src/public/Terminal.ts
@@ -5,8 +5,9 @@
import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi } from 'xterm';
import { ITerminal } from '../Types';
-import { IBufferLine } from 'common/Types';
+import { IBufferLine, ICellData } from 'common/Types';
import { IBuffer } from 'common/buffer/Types';
+import { CellData } from 'common/buffer/CellData';
import { Terminal as TerminalCore } from '../Terminal';
import * as Strings from '../browser/LocalizableStrings';
import { IEvent } from 'common/EventEmitter';
@@ -204,11 +205,15 @@ class BufferLineApiView implements IBufferLineApi {
constructor(private _line: IBufferLine) {}
public get isWrapped(): boolean { return this._line.isWrapped; }
+ public get length(): number { return this._line.length; }
public getCell(x: number): IBufferCellApi | undefined {
if (x < 0 || x >= this._line.length) {
return undefined;
}
- return new BufferCellApiView(this._line, x);
+
+ const cell = new CellData();
+ this._line.loadCell(x, cell);
+ return new BufferCellApiView(cell);
}
public translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string {
return this._line.translateToString(trimRight, startColumn, endColumn);
@@ -216,7 +221,27 @@ class BufferLineApiView implements IBufferLineApi {
}
class BufferCellApiView implements IBufferCellApi {
- constructor(private _line: IBufferLine, private _x: number) {}
- public get char(): string { return this._line.getString(this._x); }
- public get width(): number { return this._line.getWidth(this._x); }
+ constructor(private _cell: ICellData) {}
+
+ public get fg(): number { return this._cell.fg; }
+ public get bg(): number { return this._cell.bg; }
+ public get char(): string { return this._cell.getChars(); }
+ public get width(): number { return this._cell.getWidth(); }
+ public get isInverse(): number { return this._cell.isInverse(); }
+ public get isBold(): number { return this._cell.isBold(); }
+ public get isUnderline(): number { return this._cell.isUnderline(); }
+ public get isBlink(): number { return this._cell.isBlink(); }
+ public get isInvisible(): number { return this._cell.isInvisible(); }
+ public get isItalic(): number { return this._cell.isItalic(); }
+ public get isDim(): number { return this._cell.isDim(); }
+ public get fgColorMode(): number { return this._cell.getFgColorMode(); }
+ public get bgColorMode(): number { return this._cell.getBgColorMode(); }
+ public get isFgRGB(): boolean { return this._cell.isFgRGB(); }
+ public get isBgRGB(): boolean { return this._cell.isBgRGB(); }
+ public get isFgPalette(): boolean { return this._cell.isFgPalette(); }
+ public get isBgPalette(): boolean { return this._cell.isBgPalette(); }
+ public get isFgDefault(): boolean { return this._cell.isFgDefault(); }
+ public get isBgDefault(): boolean { return this._cell.isBgDefault(); }
+ public get fgColor(): number { return this._cell.getFgColor(); }
+ public get bgColor(): number { return this._cell.getBgColor(); }
}
diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts
index d9e28b26..1ec96674 100644
--- a/typings/xterm.d.ts
+++ b/typings/xterm.d.ts
@@ -910,6 +910,7 @@ declare module 'xterm' {
* Whether the line is wrapped from the previous line.
*/
readonly isWrapped: boolean;
+ readonly length: number;
/**
* Gets a cell from the line, or undefined if the line index does not exist.
@@ -937,6 +938,9 @@ declare module 'xterm' {
* Represents a single cell in the terminal's buffer.
*/
interface IBufferCell {
+ readonly fg: number;
+ readonly bg: number;
+
/**
* The character within the cell.
*/
@@ -950,5 +954,28 @@ declare module 'xterm' {
* - This is `0` for cells immediately following cells with a width of `2`.
*/
readonly width: number;
+
+ // flags
+ readonly isInverse: number;
+ readonly isBold: number;
+ readonly isUnderline: number;
+ readonly isBlink: number;
+ readonly isInvisible: number;
+ readonly isItalic: number;
+ readonly isDim: number;
+
+ // color modes
+ readonly fgColorMode: number;
+ readonly bgColorMode: number;
+ readonly isFgRGB: boolean;
+ readonly isBgRGB: boolean;
+ readonly isFgPalette: boolean;
+ readonly isBgPalette: boolean;
+ readonly isFgDefault: boolean;
+ readonly isBgDefault: boolean;
+
+ // colors
+ readonly fgColor: number;
+ readonly bgColor: number;
}
}
From 73f9d45fdc26fd52b712fd181cc3efaf890e03de Mon Sep 17 00:00:00 2001
From: javacs3
Date: Thu, 15 Aug 2019 00:02:01 +0800
Subject: [PATCH 16/47] refactor use a nicer IBufferCell design
---
.../src/SerializeAddon.ts | 211 ++++++++----------
src/public/Terminal.ts | 59 +++--
typings/xterm.d.ts | 74 ++++--
3 files changed, 188 insertions(+), 156 deletions(-)
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts
index ae72ce4b..0332477f 100644
--- a/addons/xterm-addon-serialize/src/SerializeAddon.ts
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts
@@ -3,8 +3,30 @@
* @license MIT
*/
-import { Terminal, ITerminalAddon, IBuffer, IBufferCell } from 'xterm';
-import { Attributes, FgFlags, BgFlags } from 'common/buffer/Constants';
+import { Terminal, ITerminalAddon, IBuffer, IBufferCell, Color } from 'xterm';
+
+// TODO: Workaround here, will remove this later
+// If I use `import { CellStyle } from 'xterm'` instead, demo page will raise bellow error
+//
+// ERROR in ./addons/xterm-addon-serialize/out/SerializeAddon.js
+// Module not found: Error: Can't resolve 'xterm' in '/Users/javacs3/Lab/Playground/xterm.js/addons/xterm-addon-serialize/out'
+// @ ./addons/xterm-addon-serialize/out/SerializeAddon.js 16:14-30
+// @ ./demo/client.ts
+//
+// Looks like typescript generate this line `var xterm_1 = require("xterm");` that leads to the error;
+//
+enum CellStyle {
+ default = 0,
+ // foreground style
+ inverse = 0x4000000 >>> 24,
+ bold = 0x8000000 >>> 24,
+ underline = 0x10000000 >>> 24,
+ blink = 0x20000000 >>> 24,
+ invisible = 0x40000000 >>> 24,
+ // background style
+ italic = 0x4000000 >>> 16,
+ dim = 0x8000000 >>> 16
+}
function crop(value: number | undefined, low: number, high: number, initial: number): number {
if (value === undefined) {
@@ -14,29 +36,14 @@ function crop(value: number | undefined, low: number, high: number, initial: num
}
class NullBufferCell implements IBufferCell {
- fg: number = 0; bg: number = 0;
char: string = '';
width: number = 0;
- isInverse: number = 0;
- isBold: number = 0;
- isUnderline: number = 0;
- isBlink: number = 0;
- isInvisible: number = 0;
- isItalic: number = 0;
- isDim: number = 0;
- fgColorMode: number = 0;
- bgColorMode: number = 0;
- isFgRGB: boolean = false;
- isBgRGB: boolean = false;
- isFgPalette: boolean = false;
- isBgPalette: boolean = false;
- isFgDefault: boolean = false;
- isBgDefault: boolean = false;
- fgColor: number = 0;
- bgColor: number = 0;
+ foregroundColor: Color = { type: 'default', hash: 0 };
+ backgroundColor: Color = { type: 'default', hash: 0 }
+ style: CellStyle = CellStyle.default;
}
-class BaseSerializeHandler {
+abstract class BaseSerializeHandler {
constructor(private _buffer: IBuffer) { }
serialize(startRow: number, endRow: number): string {
@@ -57,14 +64,15 @@ class BaseSerializeHandler {
console.warn(`Can't get cell at row=${row}, col=${col}`);
continue;
}
+ if ((cell.foregroundColor.hash !== oldCell.foregroundColor.hash)
+ || (cell.backgroundColor.hash !== oldCell.backgroundColor.hash)) {
+ this._cellColorChanged(cell, oldCell, row, col);
+ }
+ if (cell.style !== oldCell.style) {
+ this._cellStyleChanged(cell, oldCell, row, col);
+ }
- if (oldCell.fg !== cell.fg) {
- this._fgChanged(cell, oldCell, row, col);
- }
- if (oldCell.bg !== cell.bg) {
- this._bgChanged(cell, oldCell, row, col);
- }
- this._cellChanged(cell, oldCell, row, col);
+ this._nextCell(cell, oldCell, row, col);
oldCell = cell;
}
@@ -78,11 +86,11 @@ class BaseSerializeHandler {
return this._serializeFinished();
}
- protected _cellChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
+ protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
- protected _fgChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
+ protected _cellStyleChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
- protected _bgChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
+ protected _cellColorChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
protected _lineStart(row: number): void { }
@@ -95,10 +103,6 @@ class BaseSerializeHandler {
protected _serializeFinished(): string { return ''; }
}
-const FG_FM_MASK = FgFlags.FM_MASK;
-const BG_FM_MASK = BgFlags.FM_MASK;
-const COLOR_MASK = Attributes.CM_MASK | Attributes.RGB_MASK;
-
function fgColor256to16(c: number): number {
if (0 <= c && c <= 7) {
return 30 + c;
@@ -117,12 +121,8 @@ function bgColor256to16(c: number): number {
return -1;
}
-function hex2rgb(value: number): [number, number, number] {
- return [
- value >>> Attributes.RED_SHIFT & 255,
- value >>> Attributes.GREEN_SHIFT & 255,
- value & 255
- ];
+function isDefaultColorStyle(cell: IBufferCell) {
+ return (cell.foregroundColor.hash === 0) && (cell.backgroundColor.hash === 0) && (cell.style === CellStyle.default);
}
class StringSerializeHandler extends BaseSerializeHandler {
@@ -144,91 +144,76 @@ class StringSerializeHandler extends BaseSerializeHandler {
this._currentRow = '';
}
- protected _fgChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
- const fgFlagsChanged = (cell.fg ^ oldCell.fg) & FG_FM_MASK;
- const fgColorChanged = (cell.fg ^ oldCell.fg) & COLOR_MASK;
+ protected _cellStyleChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
+ const styleChangedMask = cell.style ^ oldCell.style;
+ const style = cell.style;
const sgrSeq = this._sgrSeq;
- if ((cell.fg === 0) && (cell.bg === 0)) {
+ // skip if it's default color style, we will use \x1b[0m to clear every color style later
+ if (isDefaultColorStyle(cell)) {
return;
}
- if (fgFlagsChanged) {
- if (fgFlagsChanged & FgFlags.INVERSE) {
- sgrSeq.push(cell.isInverse ? '7' : '27');
- }
- if (fgFlagsChanged & FgFlags.BOLD) {
- sgrSeq.push(cell.isBold ? '1' : '22');
- }
- if (fgFlagsChanged & FgFlags.UNDERLINE) {
- sgrSeq.push(cell.isUnderline ? '4' : '24');
- }
- if (fgFlagsChanged & FgFlags.BLINK) {
- sgrSeq.push(cell.isBlink ? '5' : '25');
- }
- if (fgFlagsChanged & FgFlags.INVISIBLE) {
- sgrSeq.push(cell.isInvisible ? '8' : '28');
+ if (styleChangedMask & CellStyle.inverse) {
+ sgrSeq.push((style & CellStyle.inverse) ? '7' : '27');
+ }
+ if (styleChangedMask & CellStyle.bold) {
+ sgrSeq.push((style & CellStyle.bold) ? '1' : '22');
+ }
+ if (styleChangedMask & CellStyle.underline) {
+ sgrSeq.push((style & CellStyle.underline) ? '4' : '24');
+ }
+ if (styleChangedMask & CellStyle.blink) {
+ sgrSeq.push((style & CellStyle.blink) ? '5' : '25');
+ }
+ if (styleChangedMask & CellStyle.invisible) {
+ sgrSeq.push((style & CellStyle.invisible) ? '8' : '28');
+ }
+ if (styleChangedMask & CellStyle.italic) {
+ sgrSeq.push((style & CellStyle.italic) ? '3' : '23');
+ }
+ if (styleChangedMask & CellStyle.dim) {
+ sgrSeq.push((style & CellStyle.dim) ? '2' : '22');
+ }
+ }
+
+ protected _cellColorChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
+ const foregroundColorChanged = cell.foregroundColor.hash !== oldCell.foregroundColor.hash;
+ const backgroundColorChanged = cell.backgroundColor.hash !== oldCell.backgroundColor.hash;
+ const sgrSeq = this._sgrSeq;
+
+ // skip if it's default color style, we will use \x1b[0m to clear every color style later
+ if (isDefaultColorStyle(cell)) {
+ return;
+ }
+
+ if (foregroundColorChanged) {
+ const foregroundColor = cell.foregroundColor;
+ switch (foregroundColor.type) {
+ case 'default': sgrSeq.push('39'); break;
+ case 'palette16': sgrSeq.push(fgColor256to16(foregroundColor.id).toString()); break;
+ case 'palette256': sgrSeq.push(`38;5;${foregroundColor.id}`); break;
+ case 'rgb': const { red, green, blue } = foregroundColor; sgrSeq.push(`38;2;${red};${green};${blue}`); break;
}
}
- if (fgColorChanged) {
- const fgColor = cell.fgColor;
-
- if (cell.isFgDefault) {
- sgrSeq.push('39');
- } else if (cell.isFgPalette) {
- switch (cell.fgColorMode) {
- case Attributes.CM_P16: sgrSeq.push(fgColor256to16(fgColor).toString()); break;
- case Attributes.CM_P256: sgrSeq.push(`38;5;${fgColor}`); break;
- }
- } else if (cell.isFgRGB) {
- const [r, g, b] = hex2rgb(fgColor);
- sgrSeq.push(`38;2;${r};${g};${b}`);
+ if (backgroundColorChanged) {
+ const backgroundColor = cell.backgroundColor;
+ switch (backgroundColor.type) {
+ case 'default': sgrSeq.push('49'); break;
+ case 'palette16': sgrSeq.push(bgColor256to16(backgroundColor.id).toString()); break;
+ case 'palette256': sgrSeq.push(`48;5;${backgroundColor.id}`); break;
+ case 'rgb': const { red, green, blue } = backgroundColor; sgrSeq.push(`48;2;${red};${green};${blue}`); break;
}
}
}
- protected _bgChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
- const bgFlagsChanged = (cell.bg ^ oldCell.bg) & BG_FM_MASK;
- const bgColorChanged = (cell.bg ^ oldCell.bg) & COLOR_MASK;
- const sgrSeq = this._sgrSeq;
+ protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
+ const foregroundColorChanged = cell.foregroundColor.hash !== oldCell.foregroundColor.hash;
+ const backgroundColorChanged = cell.backgroundColor.hash !== oldCell.backgroundColor.hash;
+ const styleChanged = cell.style !== oldCell.style;
- if ((cell.bg === 0) && (cell.fg === 0)) {
- return;
- }
-
- if (bgFlagsChanged) {
- if (bgFlagsChanged & BgFlags.ITALIC) {
- sgrSeq.push(cell.isItalic ? '3' : '23');
- }
- if (bgFlagsChanged & BgFlags.DIM) {
- sgrSeq.push(cell.isDim ? '2' : '22');
- }
- }
-
- if (bgColorChanged) {
- const bgColor = cell.bgColor;
-
- if (cell.isBgDefault) {
- sgrSeq.push('49');
- } else if (cell.isBgPalette) {
- switch (cell.bgColorMode) {
- case Attributes.CM_P16: sgrSeq.push(bgColor256to16(bgColor).toString()); break;
- case Attributes.CM_P256: sgrSeq.push(`48;5;${bgColor}`); break;
- }
- } else if (cell.isFgRGB) {
- const [r, g, b] = hex2rgb(bgColor);
- sgrSeq.push(`48;2;${r};${g};${b}`);
- }
- }
- }
-
- protected _cellChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
- const fgChanged = cell.fg !== oldCell.fg;
- const bgChanged = cell.bg !== oldCell.bg;
- const isfgBgNormal = (cell.fg === 0) && (cell.bg === 0);
-
- if ((fgChanged || bgChanged) && isfgBgNormal) {
+ if ((foregroundColorChanged || backgroundColorChanged || styleChanged) && isDefaultColorStyle(cell)) {
this._currentRow += '\x1b[0m';
}
diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts
index 20ecf7e9..377f7acd 100644
--- a/src/public/Terminal.ts
+++ b/src/public/Terminal.ts
@@ -3,10 +3,11 @@
* @license MIT
*/
-import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi } from 'xterm';
+import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi, Color, CellStyle} from 'xterm';
import { ITerminal } from '../Types';
import { IBufferLine, ICellData } from 'common/Types';
import { IBuffer } from 'common/buffer/Types';
+import { Attributes, FgFlags, BgFlags } from 'common/buffer/Constants';
import { CellData } from 'common/buffer/CellData';
import { Terminal as TerminalCore } from '../Terminal';
import * as Strings from '../browser/LocalizableStrings';
@@ -220,28 +221,46 @@ class BufferLineApiView implements IBufferLineApi {
}
}
+const COLOR_MASK = Attributes.CM_MASK | Attributes.RGB_MASK;
+
class BufferCellApiView implements IBufferCellApi {
constructor(private _cell: ICellData) {}
- public get fg(): number { return this._cell.fg; }
- public get bg(): number { return this._cell.bg; }
public get char(): string { return this._cell.getChars(); }
public get width(): number { return this._cell.getWidth(); }
- public get isInverse(): number { return this._cell.isInverse(); }
- public get isBold(): number { return this._cell.isBold(); }
- public get isUnderline(): number { return this._cell.isUnderline(); }
- public get isBlink(): number { return this._cell.isBlink(); }
- public get isInvisible(): number { return this._cell.isInvisible(); }
- public get isItalic(): number { return this._cell.isItalic(); }
- public get isDim(): number { return this._cell.isDim(); }
- public get fgColorMode(): number { return this._cell.getFgColorMode(); }
- public get bgColorMode(): number { return this._cell.getBgColorMode(); }
- public get isFgRGB(): boolean { return this._cell.isFgRGB(); }
- public get isBgRGB(): boolean { return this._cell.isBgRGB(); }
- public get isFgPalette(): boolean { return this._cell.isFgPalette(); }
- public get isBgPalette(): boolean { return this._cell.isBgPalette(); }
- public get isFgDefault(): boolean { return this._cell.isFgDefault(); }
- public get isBgDefault(): boolean { return this._cell.isBgDefault(); }
- public get fgColor(): number { return this._cell.getFgColor(); }
- public get bgColor(): number { return this._cell.getBgColor(); }
+ public get foregroundColor(): Color {
+ const cell = this._cell;
+ const hash = cell.fg & COLOR_MASK;
+ if (cell.isFgDefault()) {
+ return { type: 'default', hash: 0 };
+ } else if (cell.isFgPalette()) {
+ switch (cell.getFgColorMode()) {
+ case Attributes.CM_P16: return { type: 'palette16', hash, id: cell.getFgColor() };
+ case Attributes.CM_P256: return { type: 'palette256', hash, id: cell.getFgColor() };
+ }
+ } else if (cell.isFgRGB()) {
+ const [red, green, blue] = CellData.toColorRGB(cell.fg);
+ return { type: 'rgb', hash, red, green, blue };
+ }
+ throw new Error('Invalid foregroundColor');
+ }
+ public get backgroundColor(): Color {
+ const cell = this._cell;
+ const hash = cell.bg & COLOR_MASK;
+ if (cell.isBgDefault()) {
+ return { type: 'default', hash: 0 };
+ } else if (cell.isBgPalette()) {
+ switch (cell.getBgColorMode()) {
+ case Attributes.CM_P16: return { type: 'palette16', hash, id: cell.getBgColor() };
+ case Attributes.CM_P256: return { type: 'palette256', hash, id: cell.getBgColor() };
+ }
+ } else if (cell.isBgRGB()) {
+ const [red, green, blue] = CellData.toColorRGB(cell.bg);
+ return { type: 'rgb', hash, red, green, blue };
+ }
+ throw new Error('Invalid backgroundColor');
+ }
+ public get style(): CellStyle {
+ return ((this._cell.bg & BgFlags.FM_MASK) >>> 16) | ((this._cell.fg & FgFlags.FM_MASK) >>> 24);
+ }
}
diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts
index 1ec96674..4503e1c1 100644
--- a/typings/xterm.d.ts
+++ b/typings/xterm.d.ts
@@ -938,9 +938,6 @@ declare module 'xterm' {
* Represents a single cell in the terminal's buffer.
*/
interface IBufferCell {
- readonly fg: number;
- readonly bg: number;
-
/**
* The character within the cell.
*/
@@ -955,27 +952,58 @@ declare module 'xterm' {
*/
readonly width: number;
- // flags
- readonly isInverse: number;
- readonly isBold: number;
- readonly isUnderline: number;
- readonly isBlink: number;
- readonly isInvisible: number;
- readonly isItalic: number;
- readonly isDim: number;
+ readonly foregroundColor: Color;
+ readonly backgroundColor: Color;
+ readonly style: CellStyle;
+ }
- // color modes
- readonly fgColorMode: number;
- readonly bgColorMode: number;
- readonly isFgRGB: boolean;
- readonly isBgRGB: boolean;
- readonly isFgPalette: boolean;
- readonly isBgPalette: boolean;
- readonly isFgDefault: boolean;
- readonly isBgDefault: boolean;
+ export type Color = IDefaultColor | IPalette16Color | IPalette256Color | IRgbColor;
+ export enum CellStyle {
+ default = 0,
+ // foreground style
+ inverse = 0x4000000 >>> 24,
+ bold = 0x8000000 >>> 24,
+ underline = 0x10000000 >>> 24,
+ blink = 0x20000000 >>> 24,
+ invisible = 0x40000000 >>> 24,
+ // background style
+ italic = 0x4000000 >>> 16,
+ dim = 0x8000000 >>> 16
+ }
- // colors
- readonly fgColor: number;
- readonly bgColor: number;
+ interface ICellColor {
+ type: string;
+ hash: number;
+ }
+
+ interface IDefaultColor extends ICellColor {
+ type: 'default';
+ hash: 0;
+ }
+
+ interface IRgbColor extends ICellColor {
+ type: 'rgb';
+ hash: number;
+
+ // 0-255
+ red: number;
+ green: number;
+ blue: number;
+ }
+
+ interface IPalette16Color extends ICellColor {
+ type: 'palette16';
+ hash: number;
+
+ // 0-15
+ id: number;
+ }
+
+ interface IPalette256Color extends ICellColor {
+ type: 'palette256';
+ hash: number;
+
+ // 0-255
+ id: number;
}
}
From 7a7f3466f3b9b5e905db64b421dfbdf1d23d2d00 Mon Sep 17 00:00:00 2001
From: javacs3
Date: Sat, 24 Aug 2019 00:03:50 +0800
Subject: [PATCH 17/47] refactor ICellColor to CellColor
---
.../src/SerializeAddon.ts | 32 +++++-----
src/public/Terminal.ts | 62 +++++++++++++------
typings/xterm.d.ts | 45 ++++----------
3 files changed, 71 insertions(+), 68 deletions(-)
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts
index 0332477f..b5d6e08d 100644
--- a/addons/xterm-addon-serialize/src/SerializeAddon.ts
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts
@@ -3,7 +3,7 @@
* @license MIT
*/
-import { Terminal, ITerminalAddon, IBuffer, IBufferCell, Color } from 'xterm';
+import { Terminal, ITerminalAddon, IBuffer, IBufferCell, CellColor } from 'xterm';
// TODO: Workaround here, will remove this later
// If I use `import { CellStyle } from 'xterm'` instead, demo page will raise bellow error
@@ -38,8 +38,8 @@ function crop(value: number | undefined, low: number, high: number, initial: num
class NullBufferCell implements IBufferCell {
char: string = '';
width: number = 0;
- foregroundColor: Color = { type: 'default', hash: 0 };
- backgroundColor: Color = { type: 'default', hash: 0 }
+ foregroundColor: CellColor = CellColor.getDefault();
+ backgroundColor: CellColor = CellColor.getDefault();
style: CellStyle = CellStyle.default;
}
@@ -64,8 +64,8 @@ abstract class BaseSerializeHandler {
console.warn(`Can't get cell at row=${row}, col=${col}`);
continue;
}
- if ((cell.foregroundColor.hash !== oldCell.foregroundColor.hash)
- || (cell.backgroundColor.hash !== oldCell.backgroundColor.hash)) {
+ if (!cell.foregroundColor.equals(oldCell.foregroundColor)
+ || !cell.backgroundColor.equals(oldCell.backgroundColor)) {
this._cellColorChanged(cell, oldCell, row, col);
}
if (cell.style !== oldCell.style) {
@@ -122,7 +122,7 @@ function bgColor256to16(c: number): number {
}
function isDefaultColorStyle(cell: IBufferCell) {
- return (cell.foregroundColor.hash === 0) && (cell.backgroundColor.hash === 0) && (cell.style === CellStyle.default);
+ return cell.foregroundColor.isDefault() && cell.backgroundColor.isDefault() && (cell.style === CellStyle.default);
}
class StringSerializeHandler extends BaseSerializeHandler {
@@ -178,8 +178,8 @@ class StringSerializeHandler extends BaseSerializeHandler {
}
protected _cellColorChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
- const foregroundColorChanged = cell.foregroundColor.hash !== oldCell.foregroundColor.hash;
- const backgroundColorChanged = cell.backgroundColor.hash !== oldCell.backgroundColor.hash;
+ const foregroundColorChanged = !cell.foregroundColor.equals(oldCell.foregroundColor);
+ const backgroundColorChanged = !cell.backgroundColor.equals(oldCell.backgroundColor);
const sgrSeq = this._sgrSeq;
// skip if it's default color style, we will use \x1b[0m to clear every color style later
@@ -191,9 +191,9 @@ class StringSerializeHandler extends BaseSerializeHandler {
const foregroundColor = cell.foregroundColor;
switch (foregroundColor.type) {
case 'default': sgrSeq.push('39'); break;
- case 'palette16': sgrSeq.push(fgColor256to16(foregroundColor.id).toString()); break;
- case 'palette256': sgrSeq.push(`38;5;${foregroundColor.id}`); break;
- case 'rgb': const { red, green, blue } = foregroundColor; sgrSeq.push(`38;2;${red};${green};${blue}`); break;
+ case 'palette16': sgrSeq.push(fgColor256to16(foregroundColor.paletteId()).toString()); break;
+ case 'palette256': sgrSeq.push(`38;5;${foregroundColor.paletteId()}`); break;
+ case 'rgb': const [red, green, blue] = foregroundColor.rgbColor(); sgrSeq.push(`38;2;${red};${green};${blue}`); break;
}
}
@@ -201,16 +201,16 @@ class StringSerializeHandler extends BaseSerializeHandler {
const backgroundColor = cell.backgroundColor;
switch (backgroundColor.type) {
case 'default': sgrSeq.push('49'); break;
- case 'palette16': sgrSeq.push(bgColor256to16(backgroundColor.id).toString()); break;
- case 'palette256': sgrSeq.push(`48;5;${backgroundColor.id}`); break;
- case 'rgb': const { red, green, blue } = backgroundColor; sgrSeq.push(`48;2;${red};${green};${blue}`); break;
+ case 'palette16': sgrSeq.push(bgColor256to16(backgroundColor.paletteId()).toString()); break;
+ case 'palette256': sgrSeq.push(`48;5;${backgroundColor.paletteId()}`); break;
+ case 'rgb': const [red, green, blue] = backgroundColor.rgbColor(); sgrSeq.push(`48;2;${red};${green};${blue}`); break;
}
}
}
protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
- const foregroundColorChanged = cell.foregroundColor.hash !== oldCell.foregroundColor.hash;
- const backgroundColorChanged = cell.backgroundColor.hash !== oldCell.backgroundColor.hash;
+ const foregroundColorChanged = !cell.foregroundColor.equals(oldCell.foregroundColor);
+ const backgroundColorChanged = !cell.backgroundColor.equals(oldCell.backgroundColor);
const styleChanged = cell.style !== oldCell.style;
if ((foregroundColorChanged || backgroundColorChanged || styleChanged) && isDefaultColorStyle(cell)) {
diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts
index 377f7acd..61b06b51 100644
--- a/src/public/Terminal.ts
+++ b/src/public/Terminal.ts
@@ -3,7 +3,7 @@
* @license MIT
*/
-import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi, Color, CellStyle} from 'xterm';
+import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi, CellColor as ICellColorApi, CellStyle } from 'xterm';
import { ITerminal } from '../Types';
import { IBufferLine, ICellData } from 'common/Types';
import { IBuffer } from 'common/buffer/Types';
@@ -186,7 +186,7 @@ export class Terminal implements ITerminalApi {
}
class BufferApiView implements IBufferApi {
- constructor(private _buffer: IBuffer) {}
+ constructor(private _buffer: IBuffer) { }
public get cursorY(): number { return this._buffer.y; }
public get cursorX(): number { return this._buffer.x; }
@@ -203,7 +203,7 @@ class BufferApiView implements IBufferApi {
}
class BufferLineApiView implements IBufferLineApi {
- constructor(private _line: IBufferLine) {}
+ constructor(private _line: IBufferLine) { }
public get isWrapped(): boolean { return this._line.isWrapped; }
public get length(): number { return this._line.length; }
@@ -223,40 +223,66 @@ class BufferLineApiView implements IBufferLineApi {
const COLOR_MASK = Attributes.CM_MASK | Attributes.RGB_MASK;
+class CellColorApi implements ICellColorApi {
+ readonly type: 'default' | 'rgb' | 'palette16' | 'palette256';
+ readonly value: number = 0;
+
+ constructor(type: 'default' | 'rgb' | 'palette16' | 'palette256', value: number) {
+ this.type = type;
+ this.value = value;
+ }
+ public isDefault(): boolean { return this.value === 0; }
+ public equals(c: ICellColorApi): boolean { return this.value === c.value; }
+ public paletteId(): number {
+ switch (this.type) {
+ case 'default':
+ case 'palette16':
+ case 'palette256': return this.value & Attributes.PCOLOR_MASK;
+ }
+ return -1;
+ }
+ public rgbColor(): [number, number, number] {
+ if (this.type === 'rgb') {
+ return CellData.toColorRGB(this.value);
+ }
+ return [-1, -1, -1];
+ }
+
+ public static getDefault(): ICellColorApi { return new CellColorApi('default', 0); }
+}
+
class BufferCellApiView implements IBufferCellApi {
- constructor(private _cell: ICellData) {}
+ constructor(private _cell: ICellData) { }
public get char(): string { return this._cell.getChars(); }
public get width(): number { return this._cell.getWidth(); }
- public get foregroundColor(): Color {
+ public get foregroundColor(): ICellColorApi {
const cell = this._cell;
- const hash = cell.fg & COLOR_MASK;
+ const value = cell.fg & COLOR_MASK;
if (cell.isFgDefault()) {
- return { type: 'default', hash: 0 };
+ return new CellColorApi('default', 0);
} else if (cell.isFgPalette()) {
switch (cell.getFgColorMode()) {
- case Attributes.CM_P16: return { type: 'palette16', hash, id: cell.getFgColor() };
- case Attributes.CM_P256: return { type: 'palette256', hash, id: cell.getFgColor() };
+ case Attributes.CM_P16: return new CellColorApi('palette16', value);
+ case Attributes.CM_P256: return new CellColorApi('palette256', value);
}
} else if (cell.isFgRGB()) {
- const [red, green, blue] = CellData.toColorRGB(cell.fg);
- return { type: 'rgb', hash, red, green, blue };
+ return new CellColorApi('rgb', value);
}
throw new Error('Invalid foregroundColor');
}
- public get backgroundColor(): Color {
+ public get backgroundColor(): ICellColorApi {
const cell = this._cell;
- const hash = cell.bg & COLOR_MASK;
+ const value = cell.bg & COLOR_MASK;
if (cell.isBgDefault()) {
- return { type: 'default', hash: 0 };
+ return new CellColorApi('default', 0);
} else if (cell.isBgPalette()) {
switch (cell.getBgColorMode()) {
- case Attributes.CM_P16: return { type: 'palette16', hash, id: cell.getBgColor() };
- case Attributes.CM_P256: return { type: 'palette256', hash, id: cell.getBgColor() };
+ case Attributes.CM_P16: return new CellColorApi('palette16', value);
+ case Attributes.CM_P256: return new CellColorApi('palette256', value);
}
} else if (cell.isBgRGB()) {
- const [red, green, blue] = CellData.toColorRGB(cell.bg);
- return { type: 'rgb', hash, red, green, blue };
+ return new CellColorApi('rgb', value);
}
throw new Error('Invalid backgroundColor');
}
diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts
index 4503e1c1..afdfa3e4 100644
--- a/typings/xterm.d.ts
+++ b/typings/xterm.d.ts
@@ -952,12 +952,11 @@ declare module 'xterm' {
*/
readonly width: number;
- readonly foregroundColor: Color;
- readonly backgroundColor: Color;
+ readonly foregroundColor: CellColor;
+ readonly backgroundColor: CellColor;
readonly style: CellStyle;
}
- export type Color = IDefaultColor | IPalette16Color | IPalette256Color | IRgbColor;
export enum CellStyle {
default = 0,
// foreground style
@@ -971,39 +970,17 @@ declare module 'xterm' {
dim = 0x8000000 >>> 16
}
- interface ICellColor {
- type: string;
- hash: number;
- }
+ export class CellColor {
+ readonly type: 'default' | 'rgb' | 'palette16' | 'palette256';
+ readonly value: number;
- interface IDefaultColor extends ICellColor {
- type: 'default';
- hash: 0;
- }
+ constructor(type: 'default' | 'rgb' | 'palette16' | 'palette256', value: number);
- interface IRgbColor extends ICellColor {
- type: 'rgb';
- hash: number;
+ isDefault(): boolean;
+ equals(c: CellColor): boolean;
+ paletteId(): number;
+ rgbColor(): [number, number, number];
- // 0-255
- red: number;
- green: number;
- blue: number;
- }
-
- interface IPalette16Color extends ICellColor {
- type: 'palette16';
- hash: number;
-
- // 0-15
- id: number;
- }
-
- interface IPalette256Color extends ICellColor {
- type: 'palette256';
- hash: number;
-
- // 0-255
- id: number;
+ static getDefault(): CellColor;
}
}
From 22a2496360883447629878ab4ec69869255269af Mon Sep 17 00:00:00 2001
From: javacs3
Date: Sat, 24 Aug 2019 12:26:48 +0800
Subject: [PATCH 18/47] fix 'Can't resolve 'xterm' in
xterm.js/addons/xterm-addon-serialize/out'
---
demo/start.js | 3 ++-
demo/tsconfig.json | 3 ++-
src/public/Terminal.ts | 20 ++++++++++----------
3 files changed, 14 insertions(+), 12 deletions(-)
diff --git a/demo/start.js b/demo/start.js
index a054e939..64f41805 100644
--- a/demo/start.js
+++ b/demo/start.js
@@ -51,7 +51,8 @@ const clientConfig = {
extensions: [ '.tsx', '.ts', '.js' ],
alias: {
common: path.resolve('./out/common'),
- browser: path.resolve('./out/browser')
+ browser: path.resolve('./out/browser'),
+ xterm$: path.resolve('./out/public/Terminal.js')
}
},
output: {
diff --git a/demo/tsconfig.json b/demo/tsconfig.json
index 2bf76f67..28ea5262 100644
--- a/demo/tsconfig.json
+++ b/demo/tsconfig.json
@@ -9,7 +9,8 @@
"xterm-addon-attach": ["../addons/xterm-addon-attach"],
"xterm-addon-fit": ["../addons/xterm-addon-fit"],
"xterm-addon-search": ["../addons/xterm-addon-search"],
- "xterm-addon-web-links": ["../addons/xterm-addon-web-links"]
+ "xterm-addon-web-links": ["../addons/xterm-addon-web-links"],
+ "xterm-addon-serialize": ["../addons/xterm-addon-serialize"]
}
},
"include": [
diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts
index 61b06b51..e1fb8d6d 100644
--- a/src/public/Terminal.ts
+++ b/src/public/Terminal.ts
@@ -223,7 +223,7 @@ class BufferLineApiView implements IBufferLineApi {
const COLOR_MASK = Attributes.CM_MASK | Attributes.RGB_MASK;
-class CellColorApi implements ICellColorApi {
+export class CellColor implements ICellColorApi {
readonly type: 'default' | 'rgb' | 'palette16' | 'palette256';
readonly value: number = 0;
@@ -248,7 +248,7 @@ class CellColorApi implements ICellColorApi {
return [-1, -1, -1];
}
- public static getDefault(): ICellColorApi { return new CellColorApi('default', 0); }
+ public static getDefault(): ICellColorApi { return new CellColor('default', 0); }
}
class BufferCellApiView implements IBufferCellApi {
@@ -260,14 +260,14 @@ class BufferCellApiView implements IBufferCellApi {
const cell = this._cell;
const value = cell.fg & COLOR_MASK;
if (cell.isFgDefault()) {
- return new CellColorApi('default', 0);
+ return new CellColor('default', 0);
} else if (cell.isFgPalette()) {
switch (cell.getFgColorMode()) {
- case Attributes.CM_P16: return new CellColorApi('palette16', value);
- case Attributes.CM_P256: return new CellColorApi('palette256', value);
+ case Attributes.CM_P16: return new CellColor('palette16', value);
+ case Attributes.CM_P256: return new CellColor('palette256', value);
}
} else if (cell.isFgRGB()) {
- return new CellColorApi('rgb', value);
+ return new CellColor('rgb', value);
}
throw new Error('Invalid foregroundColor');
}
@@ -275,14 +275,14 @@ class BufferCellApiView implements IBufferCellApi {
const cell = this._cell;
const value = cell.bg & COLOR_MASK;
if (cell.isBgDefault()) {
- return new CellColorApi('default', 0);
+ return new CellColor('default', 0);
} else if (cell.isBgPalette()) {
switch (cell.getBgColorMode()) {
- case Attributes.CM_P16: return new CellColorApi('palette16', value);
- case Attributes.CM_P256: return new CellColorApi('palette256', value);
+ case Attributes.CM_P16: return new CellColor('palette16', value);
+ case Attributes.CM_P256: return new CellColor('palette256', value);
}
} else if (cell.isBgRGB()) {
- return new CellColorApi('rgb', value);
+ return new CellColor('rgb', value);
}
throw new Error('Invalid backgroundColor');
}
From f1a17e3cf5c181da82d96b0c34ba0548f09086d2 Mon Sep 17 00:00:00 2001
From: javacs3
Date: Sun, 25 Aug 2019 22:25:45 +0800
Subject: [PATCH 19/47] refactor CellColor constructor
---
src/public/Terminal.ts | 40 +++++++++++-----------------------------
typings/xterm.d.ts | 2 +-
2 files changed, 12 insertions(+), 30 deletions(-)
diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts
index e1fb8d6d..28758642 100644
--- a/src/public/Terminal.ts
+++ b/src/public/Terminal.ts
@@ -227,9 +227,15 @@ export class CellColor implements ICellColorApi {
readonly type: 'default' | 'rgb' | 'palette16' | 'palette256';
readonly value: number = 0;
- constructor(type: 'default' | 'rgb' | 'palette16' | 'palette256', value: number) {
- this.type = type;
+ constructor(value: number) {
this.value = value;
+ switch (value & Attributes.CM_MASK) {
+ case Attributes.CM_P16: this.type = 'palette16'; break;
+ case Attributes.CM_P256: this.type = 'palette256'; break;
+ case Attributes.CM_RGB: this.type = 'rgb'; break;
+ case Attributes.CM_DEFAULT: this.type = 'default'; break;
+ default: throw new Error('Invalid CellColor value');
+ }
}
public isDefault(): boolean { return this.value === 0; }
public equals(c: ICellColorApi): boolean { return this.value === c.value; }
@@ -248,7 +254,7 @@ export class CellColor implements ICellColorApi {
return [-1, -1, -1];
}
- public static getDefault(): ICellColorApi { return new CellColor('default', 0); }
+ public static getDefault(): ICellColorApi { return new CellColor(0); }
}
class BufferCellApiView implements IBufferCellApi {
@@ -257,34 +263,10 @@ class BufferCellApiView implements IBufferCellApi {
public get char(): string { return this._cell.getChars(); }
public get width(): number { return this._cell.getWidth(); }
public get foregroundColor(): ICellColorApi {
- const cell = this._cell;
- const value = cell.fg & COLOR_MASK;
- if (cell.isFgDefault()) {
- return new CellColor('default', 0);
- } else if (cell.isFgPalette()) {
- switch (cell.getFgColorMode()) {
- case Attributes.CM_P16: return new CellColor('palette16', value);
- case Attributes.CM_P256: return new CellColor('palette256', value);
- }
- } else if (cell.isFgRGB()) {
- return new CellColor('rgb', value);
- }
- throw new Error('Invalid foregroundColor');
+ return new CellColor(this._cell.fg & COLOR_MASK);
}
public get backgroundColor(): ICellColorApi {
- const cell = this._cell;
- const value = cell.bg & COLOR_MASK;
- if (cell.isBgDefault()) {
- return new CellColor('default', 0);
- } else if (cell.isBgPalette()) {
- switch (cell.getBgColorMode()) {
- case Attributes.CM_P16: return new CellColor('palette16', value);
- case Attributes.CM_P256: return new CellColor('palette256', value);
- }
- } else if (cell.isBgRGB()) {
- return new CellColor('rgb', value);
- }
- throw new Error('Invalid backgroundColor');
+ return new CellColor(this._cell.bg & COLOR_MASK);
}
public get style(): CellStyle {
return ((this._cell.bg & BgFlags.FM_MASK) >>> 16) | ((this._cell.fg & FgFlags.FM_MASK) >>> 24);
diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts
index afdfa3e4..ae11a8f1 100644
--- a/typings/xterm.d.ts
+++ b/typings/xterm.d.ts
@@ -974,7 +974,7 @@ declare module 'xterm' {
readonly type: 'default' | 'rgb' | 'palette16' | 'palette256';
readonly value: number;
- constructor(type: 'default' | 'rgb' | 'palette16' | 'palette256', value: number);
+ constructor(value: number);
isDefault(): boolean;
equals(c: CellColor): boolean;
From ae70ef0fa2855aa5fa0ba0f6c293460f8c3822f7 Mon Sep 17 00:00:00 2001
From: javacs3
Date: Sun, 25 Aug 2019 22:34:20 +0800
Subject: [PATCH 20/47] resolve SerializeAddon importing workaround
---
.../src/SerializeAddon.ts | 25 +------------------
src/public/Terminal.ts | 15 ++++++++++-
typings/xterm.d.ts | 16 ++++++------
3 files changed, 23 insertions(+), 33 deletions(-)
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts
index b5d6e08d..b8b57b6b 100644
--- a/addons/xterm-addon-serialize/src/SerializeAddon.ts
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts
@@ -3,30 +3,7 @@
* @license MIT
*/
-import { Terminal, ITerminalAddon, IBuffer, IBufferCell, CellColor } from 'xterm';
-
-// TODO: Workaround here, will remove this later
-// If I use `import { CellStyle } from 'xterm'` instead, demo page will raise bellow error
-//
-// ERROR in ./addons/xterm-addon-serialize/out/SerializeAddon.js
-// Module not found: Error: Can't resolve 'xterm' in '/Users/javacs3/Lab/Playground/xterm.js/addons/xterm-addon-serialize/out'
-// @ ./addons/xterm-addon-serialize/out/SerializeAddon.js 16:14-30
-// @ ./demo/client.ts
-//
-// Looks like typescript generate this line `var xterm_1 = require("xterm");` that leads to the error;
-//
-enum CellStyle {
- default = 0,
- // foreground style
- inverse = 0x4000000 >>> 24,
- bold = 0x8000000 >>> 24,
- underline = 0x10000000 >>> 24,
- blink = 0x20000000 >>> 24,
- invisible = 0x40000000 >>> 24,
- // background style
- italic = 0x4000000 >>> 16,
- dim = 0x8000000 >>> 16
-}
+import { Terminal, ITerminalAddon, IBuffer, IBufferCell, CellColor, CellStyle } from 'xterm';
function crop(value: number | undefined, low: number, high: number, initial: number): number {
if (value === undefined) {
diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts
index 28758642..baae367c 100644
--- a/src/public/Terminal.ts
+++ b/src/public/Terminal.ts
@@ -3,7 +3,7 @@
* @license MIT
*/
-import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi, CellColor as ICellColorApi, CellStyle } from 'xterm';
+import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi, CellColor as ICellColorApi } from 'xterm';
import { ITerminal } from '../Types';
import { IBufferLine, ICellData } from 'common/Types';
import { IBuffer } from 'common/buffer/Types';
@@ -221,6 +221,19 @@ class BufferLineApiView implements IBufferLineApi {
}
}
+export enum CellStyle {
+ default = 0,
+ // foreground style
+ inverse = 0x4000000 >>> 24,
+ bold = 0x8000000 >>> 24,
+ underline = 0x10000000 >>> 24,
+ blink = 0x20000000 >>> 24,
+ invisible = 0x40000000 >>> 24,
+ // background style
+ italic = 0x4000000 >>> 16,
+ dim = 0x8000000 >>> 16
+}
+
const COLOR_MASK = Attributes.CM_MASK | Attributes.RGB_MASK;
export class CellColor implements ICellColorApi {
diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts
index ae11a8f1..6fcc1e53 100644
--- a/typings/xterm.d.ts
+++ b/typings/xterm.d.ts
@@ -958,16 +958,16 @@ declare module 'xterm' {
}
export enum CellStyle {
- default = 0,
+ default,
// foreground style
- inverse = 0x4000000 >>> 24,
- bold = 0x8000000 >>> 24,
- underline = 0x10000000 >>> 24,
- blink = 0x20000000 >>> 24,
- invisible = 0x40000000 >>> 24,
+ inverse,
+ bold,
+ underline,
+ blink,
+ invisible,
// background style
- italic = 0x4000000 >>> 16,
- dim = 0x8000000 >>> 16
+ italic,
+ dim
}
export class CellColor {
From cd5f3e9740291e5ca848400b4a3b1558fa937170 Mon Sep 17 00:00:00 2001
From: javacs3
Date: Thu, 29 Aug 2019 23:17:24 +0800
Subject: [PATCH 21/47] update cell color api design
---
.../src/SerializeAddon.ts | 140 ++++++------------
src/public/Terminal.ts | 130 ++++++++--------
typings/xterm.d.ts | 116 +++++++++++----
3 files changed, 201 insertions(+), 185 deletions(-)
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts
index b8b57b6b..0fa5c3dd 100644
--- a/addons/xterm-addon-serialize/src/SerializeAddon.ts
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts
@@ -3,7 +3,7 @@
* @license MIT
*/
-import { Terminal, ITerminalAddon, IBuffer, IBufferCell, CellColor, CellStyle } from 'xterm';
+import { Terminal, ITerminalAddon, IBuffer, IBufferCell } from 'xterm';
function crop(value: number | undefined, low: number, high: number, initial: number): number {
if (value === undefined) {
@@ -12,19 +12,14 @@ function crop(value: number | undefined, low: number, high: number, initial: num
return Math.max(low, Math.min(value, high));
}
-class NullBufferCell implements IBufferCell {
- char: string = '';
- width: number = 0;
- foregroundColor: CellColor = CellColor.getDefault();
- backgroundColor: CellColor = CellColor.getDefault();
- style: CellStyle = CellStyle.default;
-}
-
abstract class BaseSerializeHandler {
constructor(private _buffer: IBuffer) { }
serialize(startRow: number, endRow: number): string {
- let oldCell: IBufferCell = new NullBufferCell();
+ // we need two of them to flip between old and new cell
+ const cell1 = this._buffer.getNullCell();
+ const cell2 = this._buffer.getNullCell();
+ let oldCell = cell1;
this._serializeStart(endRow - startRow);
@@ -35,23 +30,22 @@ abstract class BaseSerializeHandler {
if (line) {
for (let col = 0; col < line.length; col++) {
- const cell = line.getCell(col);
+ const newCell = line.getCell(col, oldCell === cell1 ? cell2 : cell1);
- if (!cell) {
+ if (!newCell) {
console.warn(`Can't get cell at row=${row}, col=${col}`);
continue;
}
- if (!cell.foregroundColor.equals(oldCell.foregroundColor)
- || !cell.backgroundColor.equals(oldCell.backgroundColor)) {
- this._cellColorChanged(cell, oldCell, row, col);
+ if (!newCell.equalFg(oldCell) || !newCell.equalBg(oldCell)) {
+ this._cellFgBgChanged(newCell, oldCell, row, col);
}
- if (cell.style !== oldCell.style) {
- this._cellStyleChanged(cell, oldCell, row, col);
+ if (!newCell.equalFlags(oldCell)) {
+ this._cellFlagsChanged(newCell, oldCell, row, col);
}
- this._nextCell(cell, oldCell, row, col);
+ this._nextCell(newCell, oldCell, row, col);
- oldCell = cell;
+ oldCell = newCell;
}
}
@@ -65,9 +59,9 @@ abstract class BaseSerializeHandler {
protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
- protected _cellStyleChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
+ protected _cellFlagsChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
- protected _cellColorChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
+ protected _cellFgBgChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
protected _lineStart(row: number): void { }
@@ -80,33 +74,11 @@ abstract class BaseSerializeHandler {
protected _serializeFinished(): string { return ''; }
}
-function fgColor256to16(c: number): number {
- if (0 <= c && c <= 7) {
- return 30 + c;
- } else if (8 <= c && c <= 15) {
- return 82 + c;
- }
- return -1;
-}
-
-function bgColor256to16(c: number): number {
- if (0 <= c && c <= 7) {
- return 40 + c;
- } else if (8 <= c && c <= 15) {
- return 92 + c;
- }
- return -1;
-}
-
-function isDefaultColorStyle(cell: IBufferCell) {
- return cell.foregroundColor.isDefault() && cell.backgroundColor.isDefault() && (cell.style === CellStyle.default);
-}
-
class StringSerializeHandler extends BaseSerializeHandler {
private _rowIndex: number = 0;
private _allRows: string[] = new Array();
private _currentRow: string = '';
- private _sgrSeq: string[] = [];
+ private _sgrSeq: number[] = [];
constructor(buffer: IBuffer) {
super(buffer);
@@ -121,76 +93,54 @@ class StringSerializeHandler extends BaseSerializeHandler {
this._currentRow = '';
}
- protected _cellStyleChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
- const styleChangedMask = cell.style ^ oldCell.style;
- const style = cell.style;
+ protected _cellFlagsChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
const sgrSeq = this._sgrSeq;
// skip if it's default color style, we will use \x1b[0m to clear every color style later
- if (isDefaultColorStyle(cell)) {
- return;
- }
+ if (cell.isDefaultAttibutes() || cell.equalFlags(oldCell)) { return; }
- if (styleChangedMask & CellStyle.inverse) {
- sgrSeq.push((style & CellStyle.inverse) ? '7' : '27');
- }
- if (styleChangedMask & CellStyle.bold) {
- sgrSeq.push((style & CellStyle.bold) ? '1' : '22');
- }
- if (styleChangedMask & CellStyle.underline) {
- sgrSeq.push((style & CellStyle.underline) ? '4' : '24');
- }
- if (styleChangedMask & CellStyle.blink) {
- sgrSeq.push((style & CellStyle.blink) ? '5' : '25');
- }
- if (styleChangedMask & CellStyle.invisible) {
- sgrSeq.push((style & CellStyle.invisible) ? '8' : '28');
- }
- if (styleChangedMask & CellStyle.italic) {
- sgrSeq.push((style & CellStyle.italic) ? '3' : '23');
- }
- if (styleChangedMask & CellStyle.dim) {
- sgrSeq.push((style & CellStyle.dim) ? '2' : '22');
- }
+ if (cell.flags.inverse !== oldCell.flags.inverse) { sgrSeq.push(cell.flags.inverse ? 7 : 27); }
+ if (cell.flags.bold !== oldCell.flags.bold) { sgrSeq.push(cell.flags.bold ? 1 : 22); }
+ if (cell.flags.underline !== oldCell.flags.underline) { sgrSeq.push(cell.flags.underline ? 4 : 24); }
+ if (cell.flags.blink !== oldCell.flags.blink) { sgrSeq.push(cell.flags.blink ? 5 : 25); }
+ if (cell.flags.invisible !== oldCell.flags.invisible) { sgrSeq.push(cell.flags.invisible ? 8 : 28); }
+ if (cell.flags.italic !== oldCell.flags.italic) { sgrSeq.push(cell.flags.italic ? 3 : 23); }
+ if (cell.flags.dim !== oldCell.flags.dim) { sgrSeq.push(cell.flags.dim ? 2 : 22); }
}
- protected _cellColorChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
- const foregroundColorChanged = !cell.foregroundColor.equals(oldCell.foregroundColor);
- const backgroundColorChanged = !cell.backgroundColor.equals(oldCell.backgroundColor);
+ protected _cellFgBgChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
const sgrSeq = this._sgrSeq;
// skip if it's default color style, we will use \x1b[0m to clear every color style later
- if (isDefaultColorStyle(cell)) {
- return;
- }
+ if (cell.isDefaultAttibutes()) { return; }
- if (foregroundColorChanged) {
- const foregroundColor = cell.foregroundColor;
- switch (foregroundColor.type) {
- case 'default': sgrSeq.push('39'); break;
- case 'palette16': sgrSeq.push(fgColor256to16(foregroundColor.paletteId()).toString()); break;
- case 'palette256': sgrSeq.push(`38;5;${foregroundColor.paletteId()}`); break;
- case 'rgb': const [red, green, blue] = foregroundColor.rgbColor(); sgrSeq.push(`38;2;${red};${green};${blue}`); break;
+ if (!cell.equalFg(oldCell)) {
+ const color = cell.fg.color;
+ switch (cell.fg.colorMode) {
+ case 'RGB': sgrSeq.push(38, 2, (color >>> 16) & 0xFF, (color >>> 8) & 0xFF, color & 0xFF); break;
+ case 'P256': sgrSeq.push(38, 5, color); break;
+ case 'P16': sgrSeq.push(color & 8 ? 90 + (color & 7) : 30 + (color & 7)); break;
+ default: sgrSeq.push(39); break;
}
}
- if (backgroundColorChanged) {
- const backgroundColor = cell.backgroundColor;
- switch (backgroundColor.type) {
- case 'default': sgrSeq.push('49'); break;
- case 'palette16': sgrSeq.push(bgColor256to16(backgroundColor.paletteId()).toString()); break;
- case 'palette256': sgrSeq.push(`48;5;${backgroundColor.paletteId()}`); break;
- case 'rgb': const [red, green, blue] = backgroundColor.rgbColor(); sgrSeq.push(`48;2;${red};${green};${blue}`); break;
+ if (!cell.equalBg(oldCell)) {
+ const color = cell.bg.color;
+ switch (cell.bg.colorMode) {
+ case 'RGB': sgrSeq.push(48, 2, (color >>> 16) & 0xFF, (color >>> 8) & 0xFF, color & 0xFF); break;
+ case 'P256': sgrSeq.push(48, 5, color); break;
+ case 'P16': sgrSeq.push(color & 8 ? 100 + (color & 7) : 40 + (color & 7)); break;
+ default: sgrSeq.push(49); break;
}
}
}
protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
- const foregroundColorChanged = !cell.foregroundColor.equals(oldCell.foregroundColor);
- const backgroundColorChanged = !cell.backgroundColor.equals(oldCell.backgroundColor);
- const styleChanged = cell.style !== oldCell.style;
+ const fgChanged = !cell.equalFg(oldCell);
+ const bgChanged = !cell.equalBg(oldCell);
+ const flagsChanged = !cell.equalFlags(oldCell);
- if ((foregroundColorChanged || backgroundColorChanged || styleChanged) && isDefaultColorStyle(cell)) {
+ if (cell.isDefaultAttibutes() && (fgChanged || bgChanged || flagsChanged)) {
this._currentRow += '\x1b[0m';
}
diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts
index baae367c..e1aef88e 100644
--- a/src/public/Terminal.ts
+++ b/src/public/Terminal.ts
@@ -3,9 +3,9 @@
* @license MIT
*/
-import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi, CellColor as ICellColorApi } from 'xterm';
+import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi, IBufferCellColor as IBufferCellColorApi, IBufferCellFlags as IBufferCellFlagsApi } from 'xterm';
import { ITerminal } from '../Types';
-import { IBufferLine, ICellData } from 'common/Types';
+import { IBufferLine } from 'common/Types';
import { IBuffer } from 'common/buffer/Types';
import { Attributes, FgFlags, BgFlags } from 'common/buffer/Constants';
import { CellData } from 'common/buffer/CellData';
@@ -14,6 +14,7 @@ import * as Strings from '../browser/LocalizableStrings';
import { IEvent } from 'common/EventEmitter';
import { AddonManager } from './AddonManager';
import { IParams } from 'common/parser/Types';
+import { AttributeData } from 'common/buffer/AttributeData';
export class Terminal implements ITerminalApi {
private _core: ITerminal;
@@ -200,6 +201,7 @@ class BufferApiView implements IBufferApi {
}
return new BufferLineApiView(line);
}
+ public getNullCell(): IBufferCellApi { return new BufferCellApiView(new CellData()); }
}
class BufferLineApiView implements IBufferLineApi {
@@ -207,81 +209,81 @@ class BufferLineApiView implements IBufferLineApi {
public get isWrapped(): boolean { return this._line.isWrapped; }
public get length(): number { return this._line.length; }
- public getCell(x: number): IBufferCellApi | undefined {
+ public getCell(x: number, cell?: BufferCellApiView): IBufferCellApi | undefined {
if (x < 0 || x >= this._line.length) {
return undefined;
}
- const cell = new CellData();
- this._line.loadCell(x, cell);
- return new BufferCellApiView(cell);
+ if (cell) {
+ this._line.loadCell(x, cell.cell);
+ return cell;
+ }
+ return new BufferCellApiView(this._line.loadCell(x, new CellData()));
}
public translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string {
return this._line.translateToString(trimRight, startColumn, endColumn);
}
}
-export enum CellStyle {
- default = 0,
- // foreground style
- inverse = 0x4000000 >>> 24,
- bold = 0x8000000 >>> 24,
- underline = 0x10000000 >>> 24,
- blink = 0x20000000 >>> 24,
- invisible = 0x40000000 >>> 24,
- // background style
- italic = 0x4000000 >>> 16,
- dim = 0x8000000 >>> 16
-}
-
-const COLOR_MASK = Attributes.CM_MASK | Attributes.RGB_MASK;
-
-export class CellColor implements ICellColorApi {
- readonly type: 'default' | 'rgb' | 'palette16' | 'palette256';
- readonly value: number = 0;
-
- constructor(value: number) {
- this.value = value;
- switch (value & Attributes.CM_MASK) {
- case Attributes.CM_P16: this.type = 'palette16'; break;
- case Attributes.CM_P256: this.type = 'palette256'; break;
- case Attributes.CM_RGB: this.type = 'rgb'; break;
- case Attributes.CM_DEFAULT: this.type = 'default'; break;
- default: throw new Error('Invalid CellColor value');
- }
- }
- public isDefault(): boolean { return this.value === 0; }
- public equals(c: ICellColorApi): boolean { return this.value === c.value; }
- public paletteId(): number {
- switch (this.type) {
- case 'default':
- case 'palette16':
- case 'palette256': return this.value & Attributes.PCOLOR_MASK;
- }
- return -1;
- }
- public rgbColor(): [number, number, number] {
- if (this.type === 'rgb') {
- return CellData.toColorRGB(this.value);
- }
- return [-1, -1, -1];
- }
-
- public static getDefault(): ICellColorApi { return new CellColor(0); }
-}
+const fgFlagMask = FgFlags.BOLD | FgFlags.BLINK | FgFlags.INVERSE | FgFlags.INVISIBLE | FgFlags.UNDERLINE;
+const bgFlagMask = BgFlags.DIM | BgFlags.ITALIC;
+const colorMask = Attributes.CM_MASK | Attributes.RGB_MASK;
class BufferCellApiView implements IBufferCellApi {
- constructor(private _cell: ICellData) { }
-
- public get char(): string { return this._cell.getChars(); }
- public get width(): number { return this._cell.getWidth(); }
- public get foregroundColor(): ICellColorApi {
- return new CellColor(this._cell.fg & COLOR_MASK);
+ public flags: IBufferCellFlagsApi;
+ public fg: IBufferCellColorApi;
+ public bg: IBufferCellColorApi;
+ constructor(public cell: CellData) {
+ this.flags = {
+ get bold(): boolean { return !!(cell.fg & FgFlags.BOLD); },
+ get underline(): boolean { return !!(cell.fg & FgFlags.UNDERLINE); },
+ get blink(): boolean { return !!(cell.fg & FgFlags.BLINK); },
+ get inverse(): boolean { return !!(cell.fg & FgFlags.INVERSE); },
+ get invisible(): boolean { return !!(cell.fg & FgFlags.INVISIBLE); },
+ get italic(): boolean { return !!(cell.bg & BgFlags.ITALIC); },
+ get dim(): boolean { return !!(cell.bg & BgFlags.DIM); }
+ };
+ this.fg = {
+ get colorMode(): 'RGB' | 'P256' | 'P16' | 'DEFAULT' {
+ switch (cell.getFgColorMode()) {
+ case Attributes.CM_RGB: return 'RGB';
+ case Attributes.CM_P256: return 'P256';
+ case Attributes.CM_P16: return 'P16';
+ default: return 'DEFAULT';
+ }
+ },
+ get color(): number { return cell.getFgColor(); },
+ get rgb(): [number, number, number] { return AttributeData.toColorRGB(cell.getFgColor()); }
+ };
+ this.bg = {
+ get colorMode(): 'RGB' | 'P256' | 'P16' | 'DEFAULT' {
+ switch (cell.getFgColorMode()) {
+ case Attributes.CM_RGB: return 'RGB';
+ case Attributes.CM_P256: return 'P256';
+ case Attributes.CM_P16: return 'P16';
+ default: return 'DEFAULT';
+ }
+ },
+ get color(): number { return cell.getBgColor(); },
+ get rgb(): [number, number, number] { return AttributeData.toColorRGB(cell.getBgColor()); }
+ };
}
- public get backgroundColor(): ICellColorApi {
- return new CellColor(this._cell.bg & COLOR_MASK);
+ public get char(): string { return this.cell.getChars(); }
+ public get width(): number { return this.cell.getWidth(); }
+ public isDefaultAttibutes(): boolean {
+ return this.cell.fg === 0 && this.cell.bg === 0;
}
- public get style(): CellStyle {
- return ((this._cell.bg & BgFlags.FM_MASK) >>> 16) | ((this._cell.fg & FgFlags.FM_MASK) >>> 24);
+ public equalAttibutes(other: BufferCellApiView): boolean {
+ return this.cell.fg === other.cell.fg && this.cell.bg === other.cell.bg;
+ }
+ public equalFlags(other: BufferCellApiView): boolean {
+ return (this.cell.fg & fgFlagMask) === (other.cell.fg & fgFlagMask)
+ && (this.cell.bg & bgFlagMask) === (other.cell.bg & bgFlagMask);
+ }
+ public equalFg(other: BufferCellApiView): boolean {
+ return (this.cell.fg & colorMask) === (other.cell.fg & colorMask);
+ }
+ public equalBg(other: BufferCellApiView): boolean {
+ return (this.cell.bg & colorMask) === (other.cell.bg & colorMask);
}
}
diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts
index 6fcc1e53..b0415350 100644
--- a/typings/xterm.d.ts
+++ b/typings/xterm.d.ts
@@ -900,6 +900,13 @@ declare module 'xterm' {
* @param y The line index to get.
*/
getLine(y: number): IBufferLine | undefined;
+
+ /**
+ * Creates an empty cell object suitable as a cell reference in
+ * `line.getCell(x, cell)`. Use this to avoid costly recreation of
+ * cell objects when dealing with tons of cells.
+ */
+ getNullCell(): IBufferCell;
}
/**
@@ -920,8 +927,9 @@ declare module 'xterm' {
* behavior.
*
* @param x The character index to get.
+ * @param cell Optional cell object to load data into.
*/
- getCell(x: number): IBufferCell | undefined;
+ getCell(x: number, cell?: IBufferCell): IBufferCell | undefined;
/**
* Gets the line as a string. Note that this is gets only the string for the
@@ -934,6 +942,49 @@ declare module 'xterm' {
translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string;
}
+ /**
+ * Represents foreground and background color settings of a cell.
+ */
+ interface IBufferCellColor {
+ /**
+ * Color mode of the color setting.
+ * RGB Color is an RGB color, use `.rgb` to grab the different channels.
+ * P256 Color is an indexed value of the 256 color palette.
+ * P16 Color is an indexed value of the 8 color palette (+8 for AIX bright colors).
+ * DEFAULT No color set, thus default color should be used.
+ */
+ colorMode: 'RGB' | 'P256' | 'P16' | 'DEFAULT';
+
+ /**
+ * Color value set in the current color mode.
+ * Note that the color value can only be interpreted in conjunction
+ * with the color mode:
+ * RGB color contains 8 bit channels in RGB32 bitorder, e.g. red << 16 | green << 8 | blue
+ * P256 color contains indexed value 0..255
+ * P16 color contains indexed value 0..15
+ * DEFAULT color always contains -1
+ */
+ color: number;
+
+ /**
+ * Helper to get RGB channels from color mode RGB. Reports channels as [red, green, blue].
+ */
+ rgb: [number, number, number];
+ }
+
+ /**
+ * Represents style flags of a cell.
+ */
+ interface IBufferCellFlags {
+ readonly bold: boolean;
+ readonly underline: boolean;
+ readonly blink: boolean;
+ readonly inverse: boolean;
+ readonly invisible: boolean;
+ readonly italic: boolean;
+ readonly dim: boolean;
+ }
+
/**
* Represents a single cell in the terminal's buffer.
*/
@@ -952,35 +1003,48 @@ declare module 'xterm' {
*/
readonly width: number;
- readonly foregroundColor: CellColor;
- readonly backgroundColor: CellColor;
- readonly style: CellStyle;
- }
+ /**
+ * Text attribute flags like bold, underline etc.
+ */
+ readonly flags: IBufferCellFlags;
- export enum CellStyle {
- default,
- // foreground style
- inverse,
- bold,
- underline,
- blink,
- invisible,
- // background style
- italic,
- dim
- }
+ /**
+ * Foreground color.
+ */
+ readonly fg: IBufferCellColor;
- export class CellColor {
- readonly type: 'default' | 'rgb' | 'palette16' | 'palette256';
- readonly value: number;
+ /**
+ * Background color.
+ */
+ readonly bg: IBufferCellColor;
- constructor(value: number);
+ /**
+ * Whether cells have default attributes (flags and colors).
+ */
+ isDefaultAttibutes(): boolean;
- isDefault(): boolean;
- equals(c: CellColor): boolean;
- paletteId(): number;
- rgbColor(): [number, number, number];
+ /**
+ * Whether cells have the same text attributes (flags and colors).
+ * @param other Other cell.
+ */
+ equalAttibutes(other: IBufferCell): boolean;
- static getDefault(): CellColor;
+ /**
+ * Whether cells have the same text attribute flags.
+ * @param other Other cell.
+ */
+ equalFlags(other: IBufferCell): boolean;
+
+ /**
+ * Whether cells have the same foreground color.
+ * @param other Other cell.
+ */
+ equalFg(other: IBufferCell): boolean;
+
+ /**
+ * Whether cells have the same background color.
+ * @param other Other cell.
+ */
+ equalBg(other: IBufferCell): boolean;
}
}
From e2df7dc96c6c9ef9afd9d9f6abfd2ac0ac026779 Mon Sep 17 00:00:00 2001
From: javacs3
Date: Fri, 16 Aug 2019 21:22:37 +0800
Subject: [PATCH 22/47] add benchmark for serialize addon
---
.../src/SerializeAddon.ts | 2 +-
package.json | 9 +-
test/benchmark/SerializeAddon.benchmark.ts | 84 +++++++++++++++++++
test/benchmark/tsconfig.json | 6 +-
yarn.lock | 29 +++++++
5 files changed, 123 insertions(+), 7 deletions(-)
create mode 100644 test/benchmark/SerializeAddon.benchmark.ts
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts
index 0fa5c3dd..c68abab7 100644
--- a/addons/xterm-addon-serialize/src/SerializeAddon.ts
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts
@@ -182,7 +182,7 @@ export class SerializeAddon implements ITerminalAddon {
throw new Error('Cannot use addon until it has been loaded');
}
- const maxRows = this._terminal.rows;
+ const maxRows = this._terminal.buffer.length;
const handler = new StringSerializeHandler(this._terminal.buffer);
rows = crop(rows, 0, maxRows, maxRows);
diff --git a/package.json b/package.json
index 68467a89..57af066d 100644
--- a/package.json
+++ b/package.json
@@ -14,15 +14,15 @@
"lint": "tslint 'src/**/*.ts' 'addons/**/*.ts'",
"test": "npm run test-unit",
"posttest": "npm run lint",
- "test-api": "mocha \"**/*.api.js\"",
+ "test-api": "mocha \"**/SerializeAddon.api.js\"",
"test-unit": "node ./bin/test.js",
"build": "tsc -b ./tsconfig.all.json",
"prepare": "npm run build",
"prepublishOnly": "npm run package",
"watch": "tsc -b -w ./tsconfig.all.json --preserveWatchOutput",
- "benchmark": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json",
- "benchmark-baseline": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json --baseline out-test/benchmark/test/benchmark/*benchmark.js",
- "benchmark-eval": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json --eval out-test/benchmark/test/benchmark/*benchmark.js",
+ "benchmark": "NODE_PATH=./out:./out-test/benchmark xterm-benchmark -r 5 -c test/benchmark/benchmark.json",
+ "benchmark-baseline": "NODE_PATH=./out:./out-test/benchmark xterm-benchmark -r 5 -c test/benchmark/benchmark.json --baseline out-test/benchmark/test/benchmark/SerializeAddon.benchmark.js",
+ "benchmark-eval": "NODE_PATH=./out:./out-test/benchmark xterm-benchmark -r 5 -c test/benchmark/benchmark.json --eval out-test/benchmark/test/benchmark/SerializeAddon.benchmark.js",
"clean": "rm -rf lib out addons/*/lib addons/*/out"
},
"devDependencies": {
@@ -49,6 +49,7 @@
"tslint-consistent-codestyle": "^1.13.0",
"typescript": "3.5",
"utf8": "^3.0.0",
+ "v8-profiler-node8": "^6.1.1",
"webpack": "^4.35.3",
"webpack-cli": "^3.1.0",
"ws": "^7.0.0",
diff --git a/test/benchmark/SerializeAddon.benchmark.ts b/test/benchmark/SerializeAddon.benchmark.ts
new file mode 100644
index 00000000..9423f400
--- /dev/null
+++ b/test/benchmark/SerializeAddon.benchmark.ts
@@ -0,0 +1,84 @@
+/**
+ * Copyright (c) 2019 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { perfContext, before, after, ThroughputRuntimeCase } from 'xterm-benchmark';
+
+import * as fs from 'fs'
+import { spawn } from 'node-pty';
+import * as profiler from 'v8-profiler-node8';
+import { Utf8ToUtf32, stringFromCodePoint } from 'common/input/TextDecoder';
+import { Terminal } from 'public/Terminal';
+import { SerializeAddon } from 'addons/xterm-addon-serialize/src/SerializeAddon';
+
+class TestTerminal extends Terminal {
+ writeSync(data: string): void {
+ (this)._core.writeBuffer.push(data);
+ (this)._core._innerWrite();
+ }
+ writeSyncUtf8(data: Uint8Array): void {
+ (this)._core.writeBufferUtf8.push(data);
+ (this)._core._innerWriteUtf8();
+ }
+}
+
+perfContext('Terminal: sh -c "dd if=/dev/random count=40 bs=1k | hexdump | lolcat -f"', () => {
+ let content = '';
+ let contentUtf8: Uint8Array;
+
+ before(async () => {
+ const p = spawn('sh', ['-c', 'dd if=/dev/random count=40 bs=1k | hexdump | lolcat -f'], {
+ name: 'xterm-256color',
+ cols: 80,
+ rows: 25,
+ cwd: process.env.HOME,
+ env: process.env,
+ encoding: (null as unknown as string) // needs to be fixed in node-pty
+ });
+ const chunks: Buffer[] = [];
+ let length = 0;
+ p.on('data', data => {
+ chunks.push(data as unknown as Buffer);
+ length += data.length;
+ });
+ await new Promise(resolve => p.on('exit', () => resolve()));
+ contentUtf8 = Buffer.concat(chunks, length);
+ // translate to content string
+ const buffer = new Uint32Array(contentUtf8.length);
+ const decoder = new Utf8ToUtf32();
+ const codepoints = decoder.decode(contentUtf8, buffer);
+ for (let i = 0; i < codepoints; ++i) {
+ content += stringFromCodePoint(buffer[i]);
+ // peek into content to force flat repr in v8
+ if (!(i % 10000000)) {
+ content[i];
+ }
+ }
+ });
+
+ perfContext('serialize', () => {
+ let terminal: TestTerminal;
+ let serializeAddon = new SerializeAddon();
+ before(() => {
+ terminal = new TestTerminal({ cols: 80, rows: 25, scrollback: 5000 });
+ serializeAddon.activate(terminal);
+ terminal.writeSync(content);
+ profiler.startProfiling();
+ });
+ after(() => {
+ const p1 = profiler.stopProfiling();
+ p1.export((err, res) => {
+ if (err) {
+ console.error(err);
+ return;
+ }
+ console.log(p1.getHeader());
+ fs.writeFileSync('serialize-profile.cpuprofile', res);
+ })
+ })
+ new ThroughputRuntimeCase('', () => {
+ return { payloadSize: serializeAddon.serialize().length };
+ }, { fork: false }).showAverageThroughput();
+ });
+});
diff --git a/test/benchmark/tsconfig.json b/test/benchmark/tsconfig.json
index 8b93dcb1..51abeba5 100644
--- a/test/benchmark/tsconfig.json
+++ b/test/benchmark/tsconfig.json
@@ -16,7 +16,9 @@
"paths": {
"common/*": [ "../../src/common/*" ],
"browser/*": [ "../../src/browser/*" ],
- "Terminal": [ "../../src/Terminal" ]
+ "addons/xterm-addon-serialize/src/*": ["../../addons/xterm-addon-serialize/src/*"],
+ "public/*": ["../../src/public/*"],
+ "Terminal": ["../../src/Terminal"]
},
},
"include": [
@@ -31,4 +33,4 @@
{ "path": "../../src/common" },
{ "path": "../../src/browser" },
]
-}
\ No newline at end of file
+}
diff --git a/yarn.lock b/yarn.lock
index a67ede71..30f2ea79 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3143,6 +3143,11 @@ nan@2.10.0, nan@^2.9.2:
resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f"
integrity sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==
+nan@^2.14.0:
+ version "2.14.0"
+ resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
+ integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==
+
nanomatch@^1.2.9:
version "1.2.13"
resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
@@ -3237,6 +3242,22 @@ node-pre-gyp@^0.10.0:
semver "^5.3.0"
tar "^4"
+node-pre-gyp@^0.13.0:
+ version "0.13.0"
+ resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.13.0.tgz#df9ab7b68dd6498137717838e4f92a33fc9daa42"
+ integrity sha512-Md1D3xnEne8b/HGVQkZZwV27WUi1ZRuZBij24TNaZwUPU3ZAFtvT6xxJGaUVillfmMKnn5oD1HoGsp2Ftik7SQ==
+ dependencies:
+ detect-libc "^1.0.2"
+ mkdirp "^0.5.1"
+ needle "^2.2.1"
+ nopt "^4.0.1"
+ npm-packlist "^1.1.6"
+ npmlog "^4.0.2"
+ rc "^1.2.7"
+ rimraf "^2.6.1"
+ semver "^5.3.0"
+ tar "^4"
+
node-pty@0.7.6:
version "0.7.6"
resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.7.6.tgz#bff6148c9c5836ca7e73c7aaaec067dcbdac2f7b"
@@ -4842,6 +4863,14 @@ v8-compile-cache@^2.0.0:
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.2.tgz#a428b28bb26790734c4fc8bc9fa106fccebf6a6c"
integrity sha512-1wFuMUIM16MDJRCrpbpuEPTUGmM5QMUg0cr3KFwra2XgOgFcPGDQHDh3CszSCD2Zewc/dh/pamNEW8CbfDebUw==
+v8-profiler-node8@^6.1.1:
+ version "6.1.1"
+ resolved "https://registry.yarnpkg.com/v8-profiler-node8/-/v8-profiler-node8-6.1.1.tgz#2046dd46d80744784f9e94bce1411717435c1f76"
+ integrity sha512-mKS7TXRRYi70hvbv5c1tk9AbuqNrtbLc+jFLlsZ2TpaC1l5lWryBlDLZKJ1JP6hjSbMEjW1ucjWLSaKsaPnGXg==
+ dependencies:
+ nan "^2.14.0"
+ node-pre-gyp "^0.13.0"
+
vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
From 3543155953ff08bcb7d09c3a5f1d038b8b281f16 Mon Sep 17 00:00:00 2001
From: javacs3
Date: Sun, 8 Sep 2019 23:09:28 +0800
Subject: [PATCH 23/47] fix typo
---
src/public/Terminal.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts
index e1aef88e..9f3aca87 100644
--- a/src/public/Terminal.ts
+++ b/src/public/Terminal.ts
@@ -257,7 +257,7 @@ class BufferCellApiView implements IBufferCellApi {
};
this.bg = {
get colorMode(): 'RGB' | 'P256' | 'P16' | 'DEFAULT' {
- switch (cell.getFgColorMode()) {
+ switch (cell.getBgColorMode()) {
case Attributes.CM_RGB: return 'RGB';
case Attributes.CM_P256: return 'P256';
case Attributes.CM_P16: return 'P16';
From 7ec99c2469fa96876e4c24c1e358bc4f2af06e0c Mon Sep 17 00:00:00 2001
From: javacs3
Date: Wed, 20 Nov 2019 22:56:44 +0800
Subject: [PATCH 24/47] update IBufferCell API
---
.../src/SerializeAddon.ts | 89 +++++++-------
package.json | 2 +-
src/public/Terminal.ts | 95 +++++++--------
typings/xterm.d.ts | 110 +++++-------------
4 files changed, 114 insertions(+), 182 deletions(-)
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts
index c68abab7..66796ecd 100644
--- a/addons/xterm-addon-serialize/src/SerializeAddon.ts
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts
@@ -12,6 +12,7 @@ function crop(value: number | undefined, low: number, high: number, initial: num
return Math.max(low, Math.min(value, high));
}
+// TODO: Refine this template class later
abstract class BaseSerializeHandler {
constructor(private _buffer: IBuffer) { }
@@ -21,12 +22,12 @@ abstract class BaseSerializeHandler {
const cell2 = this._buffer.getNullCell();
let oldCell = cell1;
- this._serializeStart(endRow - startRow);
+ this.serializeStart(endRow - startRow);
for (let row = startRow; row < endRow; row++) {
const line = this._buffer.getLine(row);
- this._lineStart(row);
+ this.lineStart(row);
if (line) {
for (let col = 0; col < line.length; col++) {
@@ -37,41 +38,41 @@ abstract class BaseSerializeHandler {
continue;
}
if (!newCell.equalFg(oldCell) || !newCell.equalBg(oldCell)) {
- this._cellFgBgChanged(newCell, oldCell, row, col);
+ this.cellFgBgChanged(newCell, oldCell, row, col);
}
if (!newCell.equalFlags(oldCell)) {
- this._cellFlagsChanged(newCell, oldCell, row, col);
+ this.cellFlagsChanged(newCell, oldCell, row, col);
}
- this._nextCell(newCell, oldCell, row, col);
+ this.nextCell(newCell, oldCell, row, col);
oldCell = newCell;
}
}
- this._lineEnd(row);
+ this.lineEnd(row);
}
- this._serializeEnd();
+ this.serializeEnd();
- return this._serializeFinished();
+ return this.serializeFinished();
}
- protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
+ protected nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
- protected _cellFlagsChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
+ protected cellFlagsChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
- protected _cellFgBgChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
+ protected cellFgBgChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
- protected _lineStart(row: number): void { }
+ protected lineStart(row: number): void { }
- protected _lineEnd(row: number): void { }
+ protected lineEnd(row: number): void { }
- protected _serializeStart(rows: number): void { }
+ protected serializeStart(rows: number): void { }
- protected _serializeEnd(): void { }
+ protected serializeEnd(): void { }
- protected _serializeFinished(): string { return ''; }
+ protected serializeFinished(): string { return ''; }
}
class StringSerializeHandler extends BaseSerializeHandler {
@@ -84,63 +85,59 @@ class StringSerializeHandler extends BaseSerializeHandler {
super(buffer);
}
- protected _serializeStart(rows: number): void {
+ protected serializeStart(rows: number): void {
this._allRows = new Array(rows);
}
- protected _lineEnd(row: number): void {
+ protected lineEnd(row: number): void {
this._allRows[this._rowIndex++] = this._currentRow;
this._currentRow = '';
}
- protected _cellFlagsChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
+ protected cellFlagsChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
const sgrSeq = this._sgrSeq;
// skip if it's default color style, we will use \x1b[0m to clear every color style later
- if (cell.isDefaultAttibutes() || cell.equalFlags(oldCell)) { return; }
+ if (cell.isAttributeDefault() || cell.equalFlags(oldCell)) { return; }
- if (cell.flags.inverse !== oldCell.flags.inverse) { sgrSeq.push(cell.flags.inverse ? 7 : 27); }
- if (cell.flags.bold !== oldCell.flags.bold) { sgrSeq.push(cell.flags.bold ? 1 : 22); }
- if (cell.flags.underline !== oldCell.flags.underline) { sgrSeq.push(cell.flags.underline ? 4 : 24); }
- if (cell.flags.blink !== oldCell.flags.blink) { sgrSeq.push(cell.flags.blink ? 5 : 25); }
- if (cell.flags.invisible !== oldCell.flags.invisible) { sgrSeq.push(cell.flags.invisible ? 8 : 28); }
- if (cell.flags.italic !== oldCell.flags.italic) { sgrSeq.push(cell.flags.italic ? 3 : 23); }
- if (cell.flags.dim !== oldCell.flags.dim) { sgrSeq.push(cell.flags.dim ? 2 : 22); }
+ if (cell.isInverse() !== oldCell.isInverse()) { sgrSeq.push(cell.isInverse() ? 7 : 27); }
+ if (cell.isBold() !== oldCell.isBold()) { sgrSeq.push(cell.isBold() ? 1 : 22); }
+ if (cell.isUnderline() !== oldCell.isUnderline()) { sgrSeq.push(cell.isUnderline() ? 4 : 24); }
+ if (cell.isBlink() !== oldCell.isBlink()) { sgrSeq.push(cell.isBlink() ? 5 : 25); }
+ if (cell.isInvisible() !== oldCell.isInvisible()) { sgrSeq.push(cell.isInvisible() ? 8 : 28); }
+ if (cell.isItalic() !== oldCell.isItalic()) { sgrSeq.push(cell.isItalic() ? 3 : 23); }
+ if (cell.isDim() !== oldCell.isDim()) { sgrSeq.push(cell.isDim() ? 2 : 22); }
}
- protected _cellFgBgChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
+ protected cellFgBgChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
const sgrSeq = this._sgrSeq;
// skip if it's default color style, we will use \x1b[0m to clear every color style later
- if (cell.isDefaultAttibutes()) { return; }
+ if (cell.isAttributeDefault()) { return; }
if (!cell.equalFg(oldCell)) {
- const color = cell.fg.color;
- switch (cell.fg.colorMode) {
- case 'RGB': sgrSeq.push(38, 2, (color >>> 16) & 0xFF, (color >>> 8) & 0xFF, color & 0xFF); break;
- case 'P256': sgrSeq.push(38, 5, color); break;
- case 'P16': sgrSeq.push(color & 8 ? 90 + (color & 7) : 30 + (color & 7)); break;
- default: sgrSeq.push(39); break;
- }
+ const color = cell.getFgColor();
+ if (cell.isFgRGB()) { sgrSeq.push(38, 2, (color >>> 16) & 0xFF, (color >>> 8) & 0xFF, color & 0xFF); }
+ else if (cell.isFgPalette256()) { sgrSeq.push(38, 5, color); }
+ else if (cell.isFgPalette16()) { sgrSeq.push(color & 8 ? 90 + (color & 7) : 30 + (color & 7)); }
+ else { sgrSeq.push(39); }
}
if (!cell.equalBg(oldCell)) {
- const color = cell.bg.color;
- switch (cell.bg.colorMode) {
- case 'RGB': sgrSeq.push(48, 2, (color >>> 16) & 0xFF, (color >>> 8) & 0xFF, color & 0xFF); break;
- case 'P256': sgrSeq.push(48, 5, color); break;
- case 'P16': sgrSeq.push(color & 8 ? 100 + (color & 7) : 40 + (color & 7)); break;
- default: sgrSeq.push(49); break;
- }
+ const color = cell.getBgColor();
+ if (cell.isBgRGB()) { sgrSeq.push(48, 2, (color >>> 16) & 0xFF, (color >>> 8) & 0xFF, color & 0xFF); }
+ else if (cell.isBgPalette256()) { sgrSeq.push(48, 5, color); }
+ else if (cell.isBgPalette16()) { sgrSeq.push(color & 8 ? 100 + (color & 7) : 40 + (color & 7)); }
+ else { sgrSeq.push(49); }
}
}
- protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
+ protected nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
const fgChanged = !cell.equalFg(oldCell);
const bgChanged = !cell.equalBg(oldCell);
const flagsChanged = !cell.equalFlags(oldCell);
- if (cell.isDefaultAttibutes() && (fgChanged || bgChanged || flagsChanged)) {
+ if (cell.isAttributeDefault() && (fgChanged || bgChanged || flagsChanged)) {
this._currentRow += '\x1b[0m';
}
@@ -152,7 +149,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
this._currentRow += cell.char;
}
- protected _serializeFinished(): string {
+ protected serializeFinished(): string {
let rowEnd = this._allRows.length;
for (; rowEnd > 0; rowEnd--) {
diff --git a/package.json b/package.json
index 57af066d..19ccf0d9 100644
--- a/package.json
+++ b/package.json
@@ -14,7 +14,7 @@
"lint": "tslint 'src/**/*.ts' 'addons/**/*.ts'",
"test": "npm run test-unit",
"posttest": "npm run lint",
- "test-api": "mocha \"**/SerializeAddon.api.js\"",
+ "test-api": "mocha \"**/*.api.js\"",
"test-unit": "node ./bin/test.js",
"build": "tsc -b ./tsconfig.all.json",
"prepare": "npm run build",
diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts
index 9f3aca87..540ef638 100644
--- a/src/public/Terminal.ts
+++ b/src/public/Terminal.ts
@@ -3,7 +3,7 @@
* @license MIT
*/
-import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi, IBufferCellColor as IBufferCellColorApi, IBufferCellFlags as IBufferCellFlagsApi } from 'xterm';
+import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi } from 'xterm';
import { ITerminal } from '../Types';
import { IBufferLine } from 'common/Types';
import { IBuffer } from 'common/buffer/Types';
@@ -14,7 +14,6 @@ import * as Strings from '../browser/LocalizableStrings';
import { IEvent } from 'common/EventEmitter';
import { AddonManager } from './AddonManager';
import { IParams } from 'common/parser/Types';
-import { AttributeData } from 'common/buffer/AttributeData';
export class Terminal implements ITerminalApi {
private _core: ITerminal;
@@ -225,65 +224,55 @@ class BufferLineApiView implements IBufferLineApi {
}
}
-const fgFlagMask = FgFlags.BOLD | FgFlags.BLINK | FgFlags.INVERSE | FgFlags.INVISIBLE | FgFlags.UNDERLINE;
-const bgFlagMask = BgFlags.DIM | BgFlags.ITALIC;
-const colorMask = Attributes.CM_MASK | Attributes.RGB_MASK;
+const FG_FLAG_MASK = FgFlags.BOLD | FgFlags.BLINK | FgFlags.INVERSE | FgFlags.INVISIBLE | FgFlags.UNDERLINE;
+const BG_FLAG_MASK = BgFlags.DIM | BgFlags.ITALIC;
+const COLOR_MASK = Attributes.CM_MASK | Attributes.RGB_MASK;
class BufferCellApiView implements IBufferCellApi {
- public flags: IBufferCellFlagsApi;
- public fg: IBufferCellColorApi;
- public bg: IBufferCellColorApi;
- constructor(public cell: CellData) {
- this.flags = {
- get bold(): boolean { return !!(cell.fg & FgFlags.BOLD); },
- get underline(): boolean { return !!(cell.fg & FgFlags.UNDERLINE); },
- get blink(): boolean { return !!(cell.fg & FgFlags.BLINK); },
- get inverse(): boolean { return !!(cell.fg & FgFlags.INVERSE); },
- get invisible(): boolean { return !!(cell.fg & FgFlags.INVISIBLE); },
- get italic(): boolean { return !!(cell.bg & BgFlags.ITALIC); },
- get dim(): boolean { return !!(cell.bg & BgFlags.DIM); }
- };
- this.fg = {
- get colorMode(): 'RGB' | 'P256' | 'P16' | 'DEFAULT' {
- switch (cell.getFgColorMode()) {
- case Attributes.CM_RGB: return 'RGB';
- case Attributes.CM_P256: return 'P256';
- case Attributes.CM_P16: return 'P16';
- default: return 'DEFAULT';
- }
- },
- get color(): number { return cell.getFgColor(); },
- get rgb(): [number, number, number] { return AttributeData.toColorRGB(cell.getFgColor()); }
- };
- this.bg = {
- get colorMode(): 'RGB' | 'P256' | 'P16' | 'DEFAULT' {
- switch (cell.getBgColorMode()) {
- case Attributes.CM_RGB: return 'RGB';
- case Attributes.CM_P256: return 'P256';
- case Attributes.CM_P16: return 'P16';
- default: return 'DEFAULT';
- }
- },
- get color(): number { return cell.getBgColor(); },
- get rgb(): [number, number, number] { return AttributeData.toColorRGB(cell.getBgColor()); }
- };
- }
+ constructor(public cell: CellData) {}
+
public get char(): string { return this.cell.getChars(); }
public get width(): number { return this.cell.getWidth(); }
- public isDefaultAttibutes(): boolean {
- return this.cell.fg === 0 && this.cell.bg === 0;
- }
- public equalAttibutes(other: BufferCellApiView): boolean {
- return this.cell.fg === other.cell.fg && this.cell.bg === other.cell.bg;
- }
+
+ public getWidth(): number { return this.cell.getWidth(); }
+ public getChars(): string { return this.cell.getChars(); }
+ public getCode(): number { return this.cell.getCode(); }
+
+ public isInverse(): number { return this.cell.isInverse(); }
+ public isBold(): number { return this.cell.isBold(); }
+ public isUnderline(): number { return this.cell.isUnderline(); }
+ public isBlink(): number { return this.cell.isBlink(); }
+ public isInvisible(): number { return this.cell.isInvisible(); }
+ public isItalic(): number { return this.cell.isItalic(); }
+ public isDim(): number { return this.cell.isDim(); }
+
+ public getFgColorMode(): number { return this.cell.getFgColorMode(); }
+ public getBgColorMode(): number { return this.cell.getBgColorMode(); }
+ public isFgRGB(): boolean { return this.cell.isFgRGB(); }
+ public isBgRGB(): boolean { return this.cell.isBgRGB(); }
+ public isFgPalette(): boolean { return this.cell.isFgPalette(); }
+ public isBgPalette(): boolean { return this.cell.isBgPalette(); }
+ public isFgPalette16(): boolean { return this.cell.getFgColorMode() == Attributes.CM_P16; }
+ public isBgPalette16(): boolean { return this.cell.getBgColorMode() == Attributes.CM_P16; }
+ public isFgPalette256(): boolean { return this.cell.getFgColorMode() == Attributes.CM_P256; }
+ public isBgPalette256(): boolean { return this.cell.getBgColorMode() == Attributes.CM_P256; }
+
+ public isAttributeDefault(): boolean { return this.cell.fg === 0 && this.cell.bg === 0; }
+ public isFgDefault(): boolean { return this.cell.isFgDefault(); }
+ public isBgDefault(): boolean { return this.cell.isBgDefault(); }
+
+ public getFgColor(): number { return this.cell.getFgColor(); }
+ public getBgColor(): number { return this.cell.getBgColor(); }
+
+
public equalFlags(other: BufferCellApiView): boolean {
- return (this.cell.fg & fgFlagMask) === (other.cell.fg & fgFlagMask)
- && (this.cell.bg & bgFlagMask) === (other.cell.bg & bgFlagMask);
+ return (this.cell.fg & FG_FLAG_MASK) === (other.cell.fg & FG_FLAG_MASK)
+ && (this.cell.bg & BG_FLAG_MASK) === (other.cell.bg & BG_FLAG_MASK);
}
public equalFg(other: BufferCellApiView): boolean {
- return (this.cell.fg & colorMask) === (other.cell.fg & colorMask);
+ return (this.cell.fg & COLOR_MASK) === (other.cell.fg & COLOR_MASK);
}
public equalBg(other: BufferCellApiView): boolean {
- return (this.cell.bg & colorMask) === (other.cell.bg & colorMask);
+ return (this.cell.bg & COLOR_MASK) === (other.cell.bg & COLOR_MASK);
}
}
diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts
index b0415350..5043b867 100644
--- a/typings/xterm.d.ts
+++ b/typings/xterm.d.ts
@@ -942,49 +942,6 @@ declare module 'xterm' {
translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string;
}
- /**
- * Represents foreground and background color settings of a cell.
- */
- interface IBufferCellColor {
- /**
- * Color mode of the color setting.
- * RGB Color is an RGB color, use `.rgb` to grab the different channels.
- * P256 Color is an indexed value of the 256 color palette.
- * P16 Color is an indexed value of the 8 color palette (+8 for AIX bright colors).
- * DEFAULT No color set, thus default color should be used.
- */
- colorMode: 'RGB' | 'P256' | 'P16' | 'DEFAULT';
-
- /**
- * Color value set in the current color mode.
- * Note that the color value can only be interpreted in conjunction
- * with the color mode:
- * RGB color contains 8 bit channels in RGB32 bitorder, e.g. red << 16 | green << 8 | blue
- * P256 color contains indexed value 0..255
- * P16 color contains indexed value 0..15
- * DEFAULT color always contains -1
- */
- color: number;
-
- /**
- * Helper to get RGB channels from color mode RGB. Reports channels as [red, green, blue].
- */
- rgb: [number, number, number];
- }
-
- /**
- * Represents style flags of a cell.
- */
- interface IBufferCellFlags {
- readonly bold: boolean;
- readonly underline: boolean;
- readonly blink: boolean;
- readonly inverse: boolean;
- readonly invisible: boolean;
- readonly italic: boolean;
- readonly dim: boolean;
- }
-
/**
* Represents a single cell in the terminal's buffer.
*/
@@ -1003,48 +960,37 @@ declare module 'xterm' {
*/
readonly width: number;
- /**
- * Text attribute flags like bold, underline etc.
- */
- readonly flags: IBufferCellFlags;
+ getWidth(): number;
+ getChars(): string;
+ getCode(): number;
- /**
- * Foreground color.
- */
- readonly fg: IBufferCellColor;
+ getFgColorMode(): number;
+ getBgColorMode(): number;
+ getFgColor(): number;
+ getBgColor(): number;
- /**
- * Background color.
- */
- readonly bg: IBufferCellColor;
+ isInverse(): number;
+ isBold(): number;
+ isUnderline(): number;
+ isBlink(): number;
+ isInvisible(): number;
+ isItalic(): number;
+ isDim(): number;
- /**
- * Whether cells have default attributes (flags and colors).
- */
- isDefaultAttibutes(): boolean;
+ isFgRGB(): boolean;
+ isBgRGB(): boolean;
+ isFgPalette(): boolean;
+ isBgPalette(): boolean;
+ isFgPalette16(): boolean;
+ isBgPalette16(): boolean;
+ isFgPalette256(): boolean;
+ isBgPalette256(): boolean;
+ isAttributeDefault(): boolean;
+ isFgDefault(): boolean;
+ isBgDefault(): boolean;
- /**
- * Whether cells have the same text attributes (flags and colors).
- * @param other Other cell.
- */
- equalAttibutes(other: IBufferCell): boolean;
-
- /**
- * Whether cells have the same text attribute flags.
- * @param other Other cell.
- */
- equalFlags(other: IBufferCell): boolean;
-
- /**
- * Whether cells have the same foreground color.
- * @param other Other cell.
- */
- equalFg(other: IBufferCell): boolean;
-
- /**
- * Whether cells have the same background color.
- * @param other Other cell.
- */
- equalBg(other: IBufferCell): boolean;
+ equalFg(cell: IBufferCell): boolean;
+ equalBg(cell: IBufferCell): boolean;
+ equalFlags(cell: IBufferCell): boolean;
}
}
From 1093b7c3d3d4a27183fe2a6e115c9f6e5a14f00b Mon Sep 17 00:00:00 2001
From: javacs3
Date: Thu, 21 Nov 2019 20:53:53 +0800
Subject: [PATCH 25/47] fix lint error
---
.../src/SerializeAddon.ts | 44 +++++++++----------
src/public/Terminal.ts | 8 ++--
2 files changed, 26 insertions(+), 26 deletions(-)
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts
index 66796ecd..b0071865 100644
--- a/addons/xterm-addon-serialize/src/SerializeAddon.ts
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts
@@ -22,12 +22,12 @@ abstract class BaseSerializeHandler {
const cell2 = this._buffer.getNullCell();
let oldCell = cell1;
- this.serializeStart(endRow - startRow);
+ this._serializeStart(endRow - startRow);
for (let row = startRow; row < endRow; row++) {
const line = this._buffer.getLine(row);
- this.lineStart(row);
+ this._lineStart(row);
if (line) {
for (let col = 0; col < line.length; col++) {
@@ -38,41 +38,41 @@ abstract class BaseSerializeHandler {
continue;
}
if (!newCell.equalFg(oldCell) || !newCell.equalBg(oldCell)) {
- this.cellFgBgChanged(newCell, oldCell, row, col);
+ this._cellFgBgChanged(newCell, oldCell, row, col);
}
if (!newCell.equalFlags(oldCell)) {
- this.cellFlagsChanged(newCell, oldCell, row, col);
+ this._cellFlagsChanged(newCell, oldCell, row, col);
}
- this.nextCell(newCell, oldCell, row, col);
+ this._nextCell(newCell, oldCell, row, col);
oldCell = newCell;
}
}
- this.lineEnd(row);
+ this._lineEnd(row);
}
- this.serializeEnd();
+ this._serializeEnd();
- return this.serializeFinished();
+ return this._serializeFinished();
}
- protected nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
+ protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
- protected cellFlagsChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
+ protected _cellFlagsChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
- protected cellFgBgChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
+ protected _cellFgBgChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
- protected lineStart(row: number): void { }
+ protected _lineStart(row: number): void { }
- protected lineEnd(row: number): void { }
+ protected _lineEnd(row: number): void { }
- protected serializeStart(rows: number): void { }
+ protected _serializeStart(rows: number): void { }
- protected serializeEnd(): void { }
+ protected _serializeEnd(): void { }
- protected serializeFinished(): string { return ''; }
+ protected _serializeFinished(): string { return ''; }
}
class StringSerializeHandler extends BaseSerializeHandler {
@@ -85,16 +85,16 @@ class StringSerializeHandler extends BaseSerializeHandler {
super(buffer);
}
- protected serializeStart(rows: number): void {
+ protected _serializeStart(rows: number): void {
this._allRows = new Array(rows);
}
- protected lineEnd(row: number): void {
+ protected _lineEnd(row: number): void {
this._allRows[this._rowIndex++] = this._currentRow;
this._currentRow = '';
}
- protected cellFlagsChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
+ protected _cellFlagsChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
const sgrSeq = this._sgrSeq;
// skip if it's default color style, we will use \x1b[0m to clear every color style later
@@ -109,7 +109,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
if (cell.isDim() !== oldCell.isDim()) { sgrSeq.push(cell.isDim() ? 2 : 22); }
}
- protected cellFgBgChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
+ protected _cellFgBgChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
const sgrSeq = this._sgrSeq;
// skip if it's default color style, we will use \x1b[0m to clear every color style later
@@ -132,7 +132,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
}
}
- protected nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
+ protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
const fgChanged = !cell.equalFg(oldCell);
const bgChanged = !cell.equalBg(oldCell);
const flagsChanged = !cell.equalFlags(oldCell);
@@ -149,7 +149,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
this._currentRow += cell.char;
}
- protected serializeFinished(): string {
+ protected _serializeFinished(): string {
let rowEnd = this._allRows.length;
for (; rowEnd > 0; rowEnd--) {
diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts
index 540ef638..0cfd0560 100644
--- a/src/public/Terminal.ts
+++ b/src/public/Terminal.ts
@@ -252,10 +252,10 @@ class BufferCellApiView implements IBufferCellApi {
public isBgRGB(): boolean { return this.cell.isBgRGB(); }
public isFgPalette(): boolean { return this.cell.isFgPalette(); }
public isBgPalette(): boolean { return this.cell.isBgPalette(); }
- public isFgPalette16(): boolean { return this.cell.getFgColorMode() == Attributes.CM_P16; }
- public isBgPalette16(): boolean { return this.cell.getBgColorMode() == Attributes.CM_P16; }
- public isFgPalette256(): boolean { return this.cell.getFgColorMode() == Attributes.CM_P256; }
- public isBgPalette256(): boolean { return this.cell.getBgColorMode() == Attributes.CM_P256; }
+ public isFgPalette16(): boolean { return this.cell.getFgColorMode() === Attributes.CM_P16; }
+ public isBgPalette16(): boolean { return this.cell.getBgColorMode() === Attributes.CM_P16; }
+ public isFgPalette256(): boolean { return this.cell.getFgColorMode() === Attributes.CM_P256; }
+ public isBgPalette256(): boolean { return this.cell.getBgColorMode() === Attributes.CM_P256; }
public isAttributeDefault(): boolean { return this.cell.fg === 0 && this.cell.bg === 0; }
public isFgDefault(): boolean { return this.cell.isFgDefault(); }
From 709214a4145fb919bd90f343059568124e16e52f Mon Sep 17 00:00:00 2001
From: Daniel Imms
Date: Sun, 24 Nov 2019 10:06:41 -0800
Subject: [PATCH 26/47] Add webpacked version to client
---
demo/client.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/demo/client.ts b/demo/client.ts
index e045dc43..d607cb07 100644
--- a/demo/client.ts
+++ b/demo/client.ts
@@ -21,6 +21,7 @@ import { WebglAddon } from '../addons/xterm-addon-webgl/out/WebglAddon';
// import { AttachAddon } from 'xterm-addon-attach';
// import { FitAddon } from 'xterm-addon-fit';
// import { SearchAddon, ISearchOptions } from 'xterm-addon-search';
+// import { SerializeAddon } from 'xterm-addon-serialize';
// import { WebLinksAddon } from 'xterm-addon-web-links';
// import { WebglAddon } from 'xterm-addon-webgl';
From 45af02590eec350bd73e6edf643000ac4387ed51 Mon Sep 17 00:00:00 2001
From: Daniel Imms
Date: Sun, 24 Nov 2019 10:15:31 -0800
Subject: [PATCH 27/47] Make serialize addon api tests fast
---
.../src/SerializeAddon.api.ts | 185 ++++--------------
1 file changed, 41 insertions(+), 144 deletions(-)
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.api.ts b/addons/xterm-addon-serialize/src/SerializeAddon.api.ts
index 6fbf9af2..a316f34e 100644
--- a/addons/xterm-addon-serialize/src/SerializeAddon.api.ts
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.api.ts
@@ -4,7 +4,6 @@
*/
import * as puppeteer from 'puppeteer';
-import * as util from 'util';
import { assert } from 'chai';
import { ITerminalOptions } from 'xterm';
@@ -17,42 +16,30 @@ const height = 600;
describe('SerializeAddon', () => {
before(async function (): Promise {
- this.timeout(20000);
browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1,
- slowMo: 80,
- devtools: true,
args: [`--window-size=${width},${height}`]
});
page = (await browser.pages())[0];
await page.setViewport({ width, height });
- });
-
- after(async () => {
- await browser.close();
- });
-
- beforeEach(async function (): Promise {
- this.timeout(20000);
await page.goto(APP);
- });
-
- it('empty content', async function (): Promise {
- this.timeout(20000);
- const rows = 10;
- const cols = 10;
-
- await openTerminal({ rows: rows, cols: cols, rendererType: 'dom' });
+ await openTerminal({ rows: 10, cols: 10, rendererType: 'dom' });
await page.evaluate(`
window.serializeAddon = new SerializeAddon();
window.term.loadAddon(window.serializeAddon);
`);
+ });
+ after(async () => await browser.close());
+ beforeEach(async () => await page.evaluate(`window.term.reset()`));
+
+ it('empty content', async function (): Promise {
+ const rows = 10;
+ const cols = 10;
assert.equal(await page.evaluate(`serializeAddon.serialize();`), '');
});
it('trim last empty lines', async function (): Promise {
- this.timeout(20000);
const cols = 10;
const lines = [
'',
@@ -67,70 +54,37 @@ describe('SerializeAddon', () => {
'',
''
];
- const rows = lines.length;
-
- await openTerminal({ rows: rows, cols: cols, rendererType: 'dom' });
- await page.evaluate(`
- window.serializeAddon = new SerializeAddon();
- window.term.loadAddon(window.serializeAddon);
- window.term.write(${util.inspect(lines.join('\r\n'))});
- `);
-
+ await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.slice(0, 8).join('\r\n'));
});
it('digits content', async function (): Promise {
- this.timeout(20000);
const rows = 10;
const cols = 10;
const digitsLine = digitsString(cols);
const lines = newArray(digitsLine, rows);
-
- await openTerminal({ rows: rows, cols: cols, rendererType: 'dom' });
- await page.evaluate(`
- window.serializeAddon = new SerializeAddon();
- window.term.loadAddon(window.serializeAddon);
- window.term.write(${util.inspect(lines.join('\r\n'))});
- `);
-
+ await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
});
it('serialize half rows of content', async function (): Promise {
- this.timeout(20000);
const rows = 10;
const halfRows = rows >> 1;
const cols = 10;
const lines = newArray((index: number) => digitsString(cols, index), rows);
-
- await openTerminal({ rows: rows, cols: cols, rendererType: 'dom' });
- await page.evaluate(`
- window.serializeAddon = new SerializeAddon();
- window.term.loadAddon(window.serializeAddon);
- window.term.write(${util.inspect(lines.join('\r\n'))});
- `);
-
+ await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize(${halfRows});`), lines.slice(halfRows, 2 * halfRows).join('\r\n'));
});
it('serialize 0 rows of content', async function (): Promise {
- this.timeout(20000);
const rows = 10;
const cols = 10;
const lines = newArray((index: number) => digitsString(cols, index), rows);
-
- await openTerminal({ rows: rows, cols: cols, rendererType: 'dom' });
- await page.evaluate(`
- window.serializeAddon = new SerializeAddon();
- window.term.loadAddon(window.serializeAddon);
- window.term.write(${util.inspect(lines.join('\r\n'))});
- `);
-
+ await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize(0);`), '');
});
it('serialize all rows of content with color16', async function (): Promise {
- this.timeout(20000);
const cols = 10;
const color16 = [
30, 31, 32, 33, 34, 35, 36, 37, // Set foreground color
@@ -143,19 +97,11 @@ describe('SerializeAddon', () => {
(index: number) => digitsString(cols, index, `\x1b[${color16[index % color16.length]}m`),
rows
);
-
- await openTerminal({ rows: rows, cols: cols, rendererType: 'dom' });
- await page.evaluate(`
- window.serializeAddon = new SerializeAddon();
- window.term.loadAddon(window.serializeAddon);
- window.term.write(${util.inspect(lines.join('\r\n'))});
- `);
-
+ await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
});
it('serialize all rows of content with fg/bg flags', async function (): Promise {
- this.timeout(20000);
const cols = 10;
const line = '+'.repeat(cols);
const lines: string[] = [
@@ -172,38 +118,22 @@ describe('SerializeAddon', () => {
mkSGR(NO_INVISIBLE) + line
];
const rows = lines.length;
-
- await openTerminal({ rows: rows, cols: cols, rendererType: 'dom' });
- await page.evaluate(`
- window.serializeAddon = new SerializeAddon();
- window.term.loadAddon(window.serializeAddon);
- window.term.write(${util.inspect(lines.join('\r\n'))});
- `);
-
+ await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
});
it('serialize all rows of content with color256', async function (): Promise {
- this.timeout(20000);
const rows = 32;
const cols = 10;
const lines = newArray(
(index: number) => digitsString(cols, index, `\x1b[38;5;${index}m`),
rows
);
-
- await openTerminal({ rows: rows, cols: cols, rendererType: 'dom' });
- await page.evaluate(`
- window.serializeAddon = new SerializeAddon();
- window.term.loadAddon(window.serializeAddon);
- window.term.write(${util.inspect(lines.join('\r\n'))});
- `);
-
+ await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
});
it('serialize all rows of content with color16 and style separately', async function (): Promise {
- this.timeout(20000);
const cols = 10;
const line = '+'.repeat(cols);
const lines: string[] = [
@@ -218,20 +148,11 @@ describe('SerializeAddon', () => {
mkSGR(BG_RESET) + line, // Underlined, Inverse
mkSGR(NORMAL) + line // Back to normal
];
- const rows = lines.length;
-
- await openTerminal({ rows: rows, cols: cols, rendererType: 'dom' });
- await page.evaluate(`
- window.serializeAddon = new SerializeAddon();
- window.term.loadAddon(window.serializeAddon);
- window.term.write(${util.inspect(lines.join('\r\n'))});
- `);
-
+ await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
});
it('serialize all rows of content with color16 and style together', async function (): Promise {
- this.timeout(20000);
const cols = 10;
const line = '+'.repeat(cols);
const lines: string[] = [
@@ -249,20 +170,11 @@ describe('SerializeAddon', () => {
mkSGR(FG_RESET, ITALIC) + line, // bg Yellow, Italic
mkSGR(BG_RESET) + line // Italic
];
- const rows = lines.length;
-
- await openTerminal({ rows: rows, cols: cols, rendererType: 'dom' });
- await page.evaluate(`
- window.serializeAddon = new SerializeAddon();
- window.term.loadAddon(window.serializeAddon);
- window.term.write(${util.inspect(lines.join('\r\n'))});
- `);
-
+ await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
});
it('serialize all rows of content with color256 and style separately', async function (): Promise {
- this.timeout(20000);
const cols = 10;
const line = '+'.repeat(cols);
const lines: string[] = [
@@ -277,20 +189,11 @@ describe('SerializeAddon', () => {
mkSGR(BG_RESET) + line, // Underlined, Inverse
mkSGR(NORMAL) + line // Back to normal
];
- const rows = lines.length;
-
- await openTerminal({ rows: rows, cols: cols, rendererType: 'dom' });
- await page.evaluate(`
- window.serializeAddon = new SerializeAddon();
- window.term.loadAddon(window.serializeAddon);
- window.term.write(${util.inspect(lines.join('\r\n'))});
- `);
-
+ await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
});
it('serialize all rows of content with color256 and style together', async function (): Promise {
- this.timeout(20000);
const cols = 10;
const line = '+'.repeat(cols);
const lines: string[] = [
@@ -308,20 +211,11 @@ describe('SerializeAddon', () => {
mkSGR(FG_RESET, ITALIC) + line, // bg Yellow 256, Italic
mkSGR(BG_RESET) + line // Italic
];
- const rows = lines.length;
-
- await openTerminal({ rows: rows, cols: cols, rendererType: 'dom' });
- await page.evaluate(`
- window.serializeAddon = new SerializeAddon();
- window.term.loadAddon(window.serializeAddon);
- window.term.write(${util.inspect(lines.join('\r\n'))});
- `);
-
+ await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
});
it('serialize all rows of content with colorRGB and style separately', async function (): Promise {
- this.timeout(20000);
const cols = 10;
const line = '+'.repeat(cols);
const lines: string[] = [
@@ -336,20 +230,11 @@ describe('SerializeAddon', () => {
mkSGR(BG_RESET) + line, // Underlined, Inverse
mkSGR(NORMAL) + line // Back to normal
];
- const rows = lines.length;
-
- await openTerminal({ rows: rows, cols: cols, rendererType: 'dom' });
- await page.evaluate(`
- window.serializeAddon = new SerializeAddon();
- window.term.loadAddon(window.serializeAddon);
- window.term.write(${util.inspect(lines.join('\r\n'))});
- `);
-
+ await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
});
it('serialize all rows of content with colorRGB and style together', async function (): Promise {
- this.timeout(20000);
const cols = 10;
const line = '+'.repeat(cols);
const lines: string[] = [
@@ -367,15 +252,7 @@ describe('SerializeAddon', () => {
mkSGR(FG_RESET, ITALIC) + line, // bg Yellow RGB, Italic
mkSGR(BG_RESET) + line // Italic
];
- const rows = lines.length;
-
- await openTerminal({ rows: rows, cols: cols, rendererType: 'dom' });
- await page.evaluate(`
- window.serializeAddon = new SerializeAddon();
- window.term.loadAddon(window.serializeAddon);
- window.term.write(${util.inspect(lines.join('\r\n'))});
- `);
-
+ await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
});
});
@@ -456,3 +333,23 @@ const DIM = '2';
const NO_ITALIC = '23';
const NO_DIM = '22';
+
+async function writeSync(page: puppeteer.Page, data: string): Promise {
+ await page.evaluate(`
+ window.ready = false;
+ window.term.write('${data}', () => window.ready = true);
+ `);
+ await pollFor(page, 'window.ready', true);
+}
+
+async function pollFor(page: puppeteer.Page, fn: string, val: any, preFn?: () => Promise): Promise {
+ if (preFn) {
+ await preFn();
+ }
+ const result = await page.evaluate(fn);
+ if (result !== val) {
+ return new Promise(r => {
+ setTimeout(() => r(pollFor(page, fn, val, preFn)), 10);
+ });
+ }
+}
From 3969eb93826d89359d1b538fb6654b7808d3a64d Mon Sep 17 00:00:00 2001
From: Daniel Imms
Date: Sun, 24 Nov 2019 10:20:19 -0800
Subject: [PATCH 28/47] Add failing tab test
---
addons/xterm-addon-serialize/src/SerializeAddon.api.ts | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.api.ts b/addons/xterm-addon-serialize/src/SerializeAddon.api.ts
index a316f34e..fdef10a2 100644
--- a/addons/xterm-addon-serialize/src/SerializeAddon.api.ts
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.api.ts
@@ -255,6 +255,15 @@ describe('SerializeAddon', () => {
await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
});
+
+ it('serialize tabs correctly', async () => {
+ const lines = [
+ 'a\tb',
+ 'foo\tbar\tbaz'
+ ];
+ await writeSync(page, lines.join('\\r\\n'));
+ assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
+ });
});
async function openTerminal(options: ITerminalOptions = {}): Promise {
From 903d75e6d3b4500bcfa2839199047d47a1aae8be Mon Sep 17 00:00:00 2001
From: javacs3
Date: Thu, 28 Nov 2019 22:17:53 +0800
Subject: [PATCH 29/47] Refactor
1. add 8 secs timeout for SerializeAddon API test
2. move demo page's addons control panel into a single div
3. remove unnecessary config in demo page
4. remove v8-profiler-node8 dependency
5. remove duplicate css option
---
.../src/SerializeAddon.api.ts | 1 +
demo/client.ts | 2 --
demo/index.html | 31 +++++++++----------
demo/start.js | 3 +-
demo/style.css | 1 -
package.json | 1 -
test/benchmark/SerializeAddon.benchmark.ts | 25 ++-------------
7 files changed, 20 insertions(+), 44 deletions(-)
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.api.ts b/addons/xterm-addon-serialize/src/SerializeAddon.api.ts
index fdef10a2..a165411a 100644
--- a/addons/xterm-addon-serialize/src/SerializeAddon.api.ts
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.api.ts
@@ -16,6 +16,7 @@ const height = 600;
describe('SerializeAddon', () => {
before(async function (): Promise {
+ this.timeout(8 * 1000);
browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1,
args: [`--window-size=${width},${height}`]
diff --git a/demo/client.ts b/demo/client.ts
index d607cb07..a4f79e77 100644
--- a/demo/client.ts
+++ b/demo/client.ts
@@ -391,10 +391,8 @@ function updateTerminalSize(): void {
function serializeButtonHandler(): void {
const output = addons.serialize.instance.serialize();
const outputString = JSON.stringify(output);
- console.log('serialize output', outputString);
document.getElementById('serialize-output').innerText = outputString;
-
if ((document.getElementById('write-to-terminal') as HTMLInputElement).checked) {
term.reset();
term.write(output);
diff --git a/demo/index.html b/demo/index.html
index c55a818d..4d5149f3 100644
--- a/demo/index.html
+++ b/demo/index.html
@@ -11,22 +11,6 @@
xterm.js: A terminal for the web
-
Style
diff --git a/demo/start.js b/demo/start.js
index 8ffedb42..a14627c1 100644
--- a/demo/start.js
+++ b/demo/start.js
@@ -48,8 +48,7 @@ const clientConfig = {
extensions: [ '.tsx', '.ts', '.js' ],
alias: {
common: path.resolve('./out/common'),
- browser: path.resolve('./out/browser'),
- xterm$: path.resolve('./out/public/Terminal.js')
+ browser: path.resolve('./out/browser')
}
},
output: {
diff --git a/demo/style.css b/demo/style.css
index 1ab5dc3c..9c5fd0bd 100644
--- a/demo/style.css
+++ b/demo/style.css
@@ -40,5 +40,4 @@ pre {
word-break: break-all;
word-wrap: break-word;
white-space: pre-wrap;
- word-wrap: break-word;
}
diff --git a/package.json b/package.json
index fe98b468..b740e045 100644
--- a/package.json
+++ b/package.json
@@ -55,7 +55,6 @@
"tslint-consistent-codestyle": "^1.13.0",
"typescript": "3.7",
"utf8": "^3.0.0",
- "v8-profiler-node8": "^6.1.1",
"webpack": "^4.35.3",
"webpack-cli": "^3.1.0",
"ws": "^7.0.0",
diff --git a/test/benchmark/SerializeAddon.benchmark.ts b/test/benchmark/SerializeAddon.benchmark.ts
index 9423f400..3b8c4724 100644
--- a/test/benchmark/SerializeAddon.benchmark.ts
+++ b/test/benchmark/SerializeAddon.benchmark.ts
@@ -3,23 +3,16 @@
* @license MIT
*/
-import { perfContext, before, after, ThroughputRuntimeCase } from 'xterm-benchmark';
+import { perfContext, before, ThroughputRuntimeCase } from 'xterm-benchmark';
-import * as fs from 'fs'
import { spawn } from 'node-pty';
-import * as profiler from 'v8-profiler-node8';
import { Utf8ToUtf32, stringFromCodePoint } from 'common/input/TextDecoder';
import { Terminal } from 'public/Terminal';
import { SerializeAddon } from 'addons/xterm-addon-serialize/src/SerializeAddon';
class TestTerminal extends Terminal {
writeSync(data: string): void {
- (
this)._core.writeBuffer.push(data);
- (this)._core._innerWrite();
- }
- writeSyncUtf8(data: Uint8Array): void {
- (this)._core.writeBufferUtf8.push(data);
- (this)._core._innerWriteUtf8();
+ (this)._core.writeSync(data);
}
}
@@ -59,24 +52,12 @@ perfContext('Terminal: sh -c "dd if=/dev/random count=40 bs=1k | hexdump | lolca
perfContext('serialize', () => {
let terminal: TestTerminal;
- let serializeAddon = new SerializeAddon();
+ const serializeAddon = new SerializeAddon();
before(() => {
terminal = new TestTerminal({ cols: 80, rows: 25, scrollback: 5000 });
serializeAddon.activate(terminal);
terminal.writeSync(content);
- profiler.startProfiling();
});
- after(() => {
- const p1 = profiler.stopProfiling();
- p1.export((err, res) => {
- if (err) {
- console.error(err);
- return;
- }
- console.log(p1.getHeader());
- fs.writeFileSync('serialize-profile.cpuprofile', res);
- })
- })
new ThroughputRuntimeCase('', () => {
return { payloadSize: serializeAddon.serialize().length };
}, { fork: false }).showAverageThroughput();
From ca12cf58020259da76e78427258e508721d1647f Mon Sep 17 00:00:00 2001
From: Daniel Imms
Date: Sat, 7 Dec 2019 10:24:22 -0800
Subject: [PATCH 30/47] Support serializing tabs
---
.../xterm-addon-serialize/src/SerializeAddon.api.ts | 10 ++++++++--
addons/xterm-addon-serialize/src/SerializeAddon.ts | 11 +++++++++++
2 files changed, 19 insertions(+), 2 deletions(-)
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.api.ts b/addons/xterm-addon-serialize/src/SerializeAddon.api.ts
index a165411a..27843714 100644
--- a/addons/xterm-addon-serialize/src/SerializeAddon.api.ts
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.api.ts
@@ -260,10 +260,16 @@ describe('SerializeAddon', () => {
it('serialize tabs correctly', async () => {
const lines = [
'a\tb',
- 'foo\tbar\tbaz'
+ 'aa\tc',
+ 'aaa\td'
+ ];
+ const expected = [
+ 'a\x1b[7Cb',
+ 'aa\x1b[6Cc',
+ 'aaa\x1b[5Cd'
];
await writeSync(page, lines.join('\\r\\n'));
- assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
+ assert.equal(await page.evaluate(`serializeAddon.serialize();`), expected.join('\r\n'));
});
});
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts
index b0071865..1364b7d2 100644
--- a/addons/xterm-addon-serialize/src/SerializeAddon.ts
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts
@@ -79,6 +79,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
private _rowIndex: number = 0;
private _allRows: string[] = new Array();
private _currentRow: string = '';
+ private _nullCellCount: number = 0;
private _sgrSeq: number[] = [];
constructor(buffer: IBuffer) {
@@ -92,6 +93,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
protected _lineEnd(row: number): void {
this._allRows[this._rowIndex++] = this._currentRow;
this._currentRow = '';
+ this._nullCellCount = 0;
}
protected _cellFlagsChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
@@ -133,6 +135,15 @@ class StringSerializeHandler extends BaseSerializeHandler {
}
protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
+ // Count number of null cells encountered after the last non-null cell and move the cursor
+ // if a non-null cell is found (eg. \t or cursor move)
+ if (cell.char === '') {
+ this._nullCellCount++;
+ } else if (this._nullCellCount > 0) {
+ this._currentRow += `\x1b[${this._nullCellCount}C`;
+ this._nullCellCount = 0;
+ }
+
const fgChanged = !cell.equalFg(oldCell);
const bgChanged = !cell.equalBg(oldCell);
const flagsChanged = !cell.equalFlags(oldCell);
From 9e1f607bd93d2d2904c9bbad382ccba5100a00a6 Mon Sep 17 00:00:00 2001
From: javacs3
Date: Mon, 16 Dec 2019 21:49:12 +0800
Subject: [PATCH 31/47] fix: serialize-addon, remove unused packages in
yarn.lock
---
yarn.lock | 26 +-------------------------
1 file changed, 1 insertion(+), 25 deletions(-)
diff --git a/yarn.lock b/yarn.lock
index 2388aed9..171b66e4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3431,7 +3431,7 @@ nan@2.10.0:
resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f"
integrity sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==
-nan@^2.12.1, nan@^2.14.0:
+nan@^2.12.1:
version "2.14.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==
@@ -3530,22 +3530,6 @@ node-pre-gyp@^0.12.0:
semver "^5.3.0"
tar "^4"
-node-pre-gyp@^0.13.0:
- version "0.13.0"
- resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.13.0.tgz#df9ab7b68dd6498137717838e4f92a33fc9daa42"
- integrity sha512-Md1D3xnEne8b/HGVQkZZwV27WUi1ZRuZBij24TNaZwUPU3ZAFtvT6xxJGaUVillfmMKnn5oD1HoGsp2Ftik7SQ==
- dependencies:
- detect-libc "^1.0.2"
- mkdirp "^0.5.1"
- needle "^2.2.1"
- nopt "^4.0.1"
- npm-packlist "^1.1.6"
- npmlog "^4.0.2"
- rc "^1.2.7"
- rimraf "^2.6.1"
- semver "^5.3.0"
- tar "^4"
-
node-pty@0.7.6:
version "0.7.6"
resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.7.6.tgz#bff6148c9c5836ca7e73c7aaaec067dcbdac2f7b"
@@ -5363,14 +5347,6 @@ v8-compile-cache@^2.0.0:
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.2.tgz#a428b28bb26790734c4fc8bc9fa106fccebf6a6c"
integrity sha512-1wFuMUIM16MDJRCrpbpuEPTUGmM5QMUg0cr3KFwra2XgOgFcPGDQHDh3CszSCD2Zewc/dh/pamNEW8CbfDebUw==
-v8-profiler-node8@^6.1.1:
- version "6.1.1"
- resolved "https://registry.yarnpkg.com/v8-profiler-node8/-/v8-profiler-node8-6.1.1.tgz#2046dd46d80744784f9e94bce1411717435c1f76"
- integrity sha512-mKS7TXRRYi70hvbv5c1tk9AbuqNrtbLc+jFLlsZ2TpaC1l5lWryBlDLZKJ1JP6hjSbMEjW1ucjWLSaKsaPnGXg==
- dependencies:
- nan "^2.14.0"
- node-pre-gyp "^0.13.0"
-
validate-npm-package-license@^3.0.1:
version "3.0.4"
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
From a43cc23d6717a06796ef7a62f3272ba1c59720da Mon Sep 17 00:00:00 2001
From: javacs3
Date: Tue, 17 Dec 2019 21:21:12 +0800
Subject: [PATCH 32/47] refactor: serialize-addon, remove _cellFgBgChanged(),
_cellFlagsChanged(), _lineStart() functions
---
.../src/SerializeAddon.ts | 111 +++++++-----------
1 file changed, 42 insertions(+), 69 deletions(-)
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts
index 1364b7d2..91ea1d26 100644
--- a/addons/xterm-addon-serialize/src/SerializeAddon.ts
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts
@@ -27,8 +27,6 @@ abstract class BaseSerializeHandler {
for (let row = startRow; row < endRow; row++) {
const line = this._buffer.getLine(row);
- this._lineStart(row);
-
if (line) {
for (let col = 0; col < line.length; col++) {
const newCell = line.getCell(col, oldCell === cell1 ? cell2 : cell1);
@@ -37,12 +35,6 @@ abstract class BaseSerializeHandler {
console.warn(`Can't get cell at row=${row}, col=${col}`);
continue;
}
- if (!newCell.equalFg(oldCell) || !newCell.equalBg(oldCell)) {
- this._cellFgBgChanged(newCell, oldCell, row, col);
- }
- if (!newCell.equalFlags(oldCell)) {
- this._cellFlagsChanged(newCell, oldCell, row, col);
- }
this._nextCell(newCell, oldCell, row, col);
@@ -50,7 +42,7 @@ abstract class BaseSerializeHandler {
}
}
- this._lineEnd(row);
+ this._nextRow(row);
}
this._serializeEnd();
@@ -60,13 +52,7 @@ abstract class BaseSerializeHandler {
protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
- protected _cellFlagsChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
-
- protected _cellFgBgChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
-
- protected _lineStart(row: number): void { }
-
- protected _lineEnd(row: number): void { }
+ protected _nextRow(row: number): void { }
protected _serializeStart(rows: number): void { }
@@ -80,7 +66,6 @@ class StringSerializeHandler extends BaseSerializeHandler {
private _allRows: string[] = new Array();
private _currentRow: string = '';
private _nullCellCount: number = 0;
- private _sgrSeq: number[] = [];
constructor(buffer: IBuffer) {
super(buffer);
@@ -90,51 +75,52 @@ class StringSerializeHandler extends BaseSerializeHandler {
this._allRows = new Array(rows);
}
- protected _lineEnd(row: number): void {
+ protected _nextRow(row: number): void {
this._allRows[this._rowIndex++] = this._currentRow;
this._currentRow = '';
this._nullCellCount = 0;
}
- protected _cellFlagsChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
- const sgrSeq = this._sgrSeq;
-
- // skip if it's default color style, we will use \x1b[0m to clear every color style later
- if (cell.isAttributeDefault() || cell.equalFlags(oldCell)) { return; }
-
- if (cell.isInverse() !== oldCell.isInverse()) { sgrSeq.push(cell.isInverse() ? 7 : 27); }
- if (cell.isBold() !== oldCell.isBold()) { sgrSeq.push(cell.isBold() ? 1 : 22); }
- if (cell.isUnderline() !== oldCell.isUnderline()) { sgrSeq.push(cell.isUnderline() ? 4 : 24); }
- if (cell.isBlink() !== oldCell.isBlink()) { sgrSeq.push(cell.isBlink() ? 5 : 25); }
- if (cell.isInvisible() !== oldCell.isInvisible()) { sgrSeq.push(cell.isInvisible() ? 8 : 28); }
- if (cell.isItalic() !== oldCell.isItalic()) { sgrSeq.push(cell.isItalic() ? 3 : 23); }
- if (cell.isDim() !== oldCell.isDim()) { sgrSeq.push(cell.isDim() ? 2 : 22); }
- }
-
- protected _cellFgBgChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
- const sgrSeq = this._sgrSeq;
-
- // skip if it's default color style, we will use \x1b[0m to clear every color style later
- if (cell.isAttributeDefault()) { return; }
-
- if (!cell.equalFg(oldCell)) {
- const color = cell.getFgColor();
- if (cell.isFgRGB()) { sgrSeq.push(38, 2, (color >>> 16) & 0xFF, (color >>> 8) & 0xFF, color & 0xFF); }
- else if (cell.isFgPalette256()) { sgrSeq.push(38, 5, color); }
- else if (cell.isFgPalette16()) { sgrSeq.push(color & 8 ? 90 + (color & 7) : 30 + (color & 7)); }
- else { sgrSeq.push(39); }
- }
-
- if (!cell.equalBg(oldCell)) {
- const color = cell.getBgColor();
- if (cell.isBgRGB()) { sgrSeq.push(48, 2, (color >>> 16) & 0xFF, (color >>> 8) & 0xFF, color & 0xFF); }
- else if (cell.isBgPalette256()) { sgrSeq.push(48, 5, color); }
- else if (cell.isBgPalette16()) { sgrSeq.push(color & 8 ? 100 + (color & 7) : 40 + (color & 7)); }
- else { sgrSeq.push(49); }
- }
- }
-
protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
+ const sgrSeq: number[] = [];
+ const fgChanged = !cell.equalFg(oldCell);
+ const bgChanged = !cell.equalBg(oldCell);
+ const flagsChanged = !cell.equalFlags(oldCell);
+
+ if (fgChanged || bgChanged || flagsChanged) {
+ if (cell.isAttributeDefault()) {
+ this._currentRow += '\x1b[0m';
+ } else {
+ if (fgChanged) {
+ const color = cell.getFgColor();
+ if (cell.isFgRGB()) { sgrSeq.push(38, 2, (color >>> 16) & 0xFF, (color >>> 8) & 0xFF, color & 0xFF); }
+ else if (cell.isFgPalette256()) { sgrSeq.push(38, 5, color); }
+ else if (cell.isFgPalette16()) { sgrSeq.push(color & 8 ? 90 + (color & 7) : 30 + (color & 7)); }
+ else { sgrSeq.push(39); }
+ }
+ if (bgChanged) {
+ const color = cell.getBgColor();
+ if (cell.isBgRGB()) { sgrSeq.push(48, 2, (color >>> 16) & 0xFF, (color >>> 8) & 0xFF, color & 0xFF); }
+ else if (cell.isBgPalette256()) { sgrSeq.push(48, 5, color); }
+ else if (cell.isBgPalette16()) { sgrSeq.push(color & 8 ? 100 + (color & 7) : 40 + (color & 7)); }
+ else { sgrSeq.push(49); }
+ }
+ if (flagsChanged) {
+ if (cell.isInverse() !== oldCell.isInverse()) { sgrSeq.push(cell.isInverse() ? 7 : 27); }
+ if (cell.isBold() !== oldCell.isBold()) { sgrSeq.push(cell.isBold() ? 1 : 22); }
+ if (cell.isUnderline() !== oldCell.isUnderline()) { sgrSeq.push(cell.isUnderline() ? 4 : 24); }
+ if (cell.isBlink() !== oldCell.isBlink()) { sgrSeq.push(cell.isBlink() ? 5 : 25); }
+ if (cell.isInvisible() !== oldCell.isInvisible()) { sgrSeq.push(cell.isInvisible() ? 8 : 28); }
+ if (cell.isItalic() !== oldCell.isItalic()) { sgrSeq.push(cell.isItalic() ? 3 : 23); }
+ if (cell.isDim() !== oldCell.isDim()) { sgrSeq.push(cell.isDim() ? 2 : 22); }
+ }
+ }
+ }
+
+ if (sgrSeq.length) {
+ this._currentRow += `\x1b[${sgrSeq.join(';')}m`;
+ }
+
// Count number of null cells encountered after the last non-null cell and move the cursor
// if a non-null cell is found (eg. \t or cursor move)
if (cell.char === '') {
@@ -144,19 +130,6 @@ class StringSerializeHandler extends BaseSerializeHandler {
this._nullCellCount = 0;
}
- const fgChanged = !cell.equalFg(oldCell);
- const bgChanged = !cell.equalBg(oldCell);
- const flagsChanged = !cell.equalFlags(oldCell);
-
- if (cell.isAttributeDefault() && (fgChanged || bgChanged || flagsChanged)) {
- this._currentRow += '\x1b[0m';
- }
-
- if (this._sgrSeq.length) {
- this._currentRow += `\x1b[${this._sgrSeq.join(';')}m`;
- this._sgrSeq = [];
- }
-
this._currentRow += cell.char;
}
From b80b5a94f99e1cdd2cbe020b14184a8ffb21fcef Mon Sep 17 00:00:00 2001
From: javacs3
Date: Tue, 17 Dec 2019 22:42:09 +0800
Subject: [PATCH 33/47] refactor: serialize-addon, rename _nextRow() to
_rowEnd()
---
addons/xterm-addon-serialize/src/SerializeAddon.ts | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts
index 91ea1d26..468b9655 100644
--- a/addons/xterm-addon-serialize/src/SerializeAddon.ts
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts
@@ -42,7 +42,7 @@ abstract class BaseSerializeHandler {
}
}
- this._nextRow(row);
+ this._rowEnd(row);
}
this._serializeEnd();
@@ -52,7 +52,7 @@ abstract class BaseSerializeHandler {
protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
- protected _nextRow(row: number): void { }
+ protected _rowEnd(row: number): void { }
protected _serializeStart(rows: number): void { }
@@ -75,7 +75,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
this._allRows = new Array(rows);
}
- protected _nextRow(row: number): void {
+ protected _rowEnd(row: number): void {
this._allRows[this._rowIndex++] = this._currentRow;
this._currentRow = '';
this._nullCellCount = 0;
From 84f5f20edbe5d3cc45035dc6cb4dc03a03abd111 Mon Sep 17 00:00:00 2001
From: javacs3
Date: Wed, 18 Dec 2019 16:09:57 +0800
Subject: [PATCH 34/47] refactor: serialize-addon, remove equalFg(), equalBg(),
equalFlags() api from xterm.d.ts
---
.../src/SerializeAddon.ts | 26 ++++++++++++++++---
src/public/Terminal.ts | 18 +------------
typings/xterm.d.ts | 6 +----
3 files changed, 25 insertions(+), 25 deletions(-)
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts
index 468b9655..160f6459 100644
--- a/addons/xterm-addon-serialize/src/SerializeAddon.ts
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts
@@ -61,6 +61,26 @@ abstract class BaseSerializeHandler {
protected _serializeFinished(): string { return ''; }
}
+function equalFg(cell1: IBufferCell, cell2: IBufferCell): boolean {
+ return cell1.getFgColorMode() === cell2.getFgColorMode()
+ && cell1.getFgColor() === cell2.getFgColor();
+}
+
+function equalBg(cell1: IBufferCell, cell2: IBufferCell): boolean {
+ return cell1.getBgColorMode() === cell2.getBgColorMode()
+ && cell1.getBgColor() === cell2.getBgColor();
+}
+
+function equalFlags(cell1: IBufferCell, cell2: IBufferCell): boolean {
+ return cell1.isInverse() === cell2.isInverse()
+ && cell1.isBold() === cell2.isBold()
+ && cell1.isUnderline() === cell2.isUnderline()
+ && cell1.isBlink() === cell2.isBlink()
+ && cell1.isInvisible() === cell2.isInvisible()
+ && cell1.isItalic() === cell2.isItalic()
+ && cell1.isDim() === cell2.isDim();
+}
+
class StringSerializeHandler extends BaseSerializeHandler {
private _rowIndex: number = 0;
private _allRows: string[] = new Array();
@@ -83,9 +103,9 @@ class StringSerializeHandler extends BaseSerializeHandler {
protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
const sgrSeq: number[] = [];
- const fgChanged = !cell.equalFg(oldCell);
- const bgChanged = !cell.equalBg(oldCell);
- const flagsChanged = !cell.equalFlags(oldCell);
+ const fgChanged = !equalFg(cell, oldCell);
+ const bgChanged = !equalBg(cell, oldCell);
+ const flagsChanged = !equalFlags(cell, oldCell);
if (fgChanged || bgChanged || flagsChanged) {
if (cell.isAttributeDefault()) {
diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts
index 22776d0a..5854cef6 100644
--- a/src/public/Terminal.ts
+++ b/src/public/Terminal.ts
@@ -7,7 +7,7 @@ import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILink
import { ITerminal } from '../Types';
import { IBufferLine } from 'common/Types';
import { IBuffer } from 'common/buffer/Types';
-import { Attributes, FgFlags, BgFlags } from 'common/buffer/Constants';
+import { Attributes } from 'common/buffer/Constants';
import { CellData } from 'common/buffer/CellData';
import { Terminal as TerminalCore } from '../Terminal';
import * as Strings from '../browser/LocalizableStrings';
@@ -227,10 +227,6 @@ class BufferLineApiView implements IBufferLineApi {
}
}
-const FG_FLAG_MASK = FgFlags.BOLD | FgFlags.BLINK | FgFlags.INVERSE | FgFlags.INVISIBLE | FgFlags.UNDERLINE;
-const BG_FLAG_MASK = BgFlags.DIM | BgFlags.ITALIC;
-const COLOR_MASK = Attributes.CM_MASK | Attributes.RGB_MASK;
-
class BufferCellApiView implements IBufferCellApi {
constructor(public cell: CellData) {}
@@ -266,18 +262,6 @@ class BufferCellApiView implements IBufferCellApi {
public getFgColor(): number { return this.cell.getFgColor(); }
public getBgColor(): number { return this.cell.getBgColor(); }
-
-
- public equalFlags(other: BufferCellApiView): boolean {
- return (this.cell.fg & FG_FLAG_MASK) === (other.cell.fg & FG_FLAG_MASK)
- && (this.cell.bg & BG_FLAG_MASK) === (other.cell.bg & BG_FLAG_MASK);
- }
- public equalFg(other: BufferCellApiView): boolean {
- return (this.cell.fg & COLOR_MASK) === (other.cell.fg & COLOR_MASK);
- }
- public equalBg(other: BufferCellApiView): boolean {
- return (this.cell.bg & COLOR_MASK) === (other.cell.bg & COLOR_MASK);
- }
}
class ParserApi implements IParser {
diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts
index 6bf2aebf..243266d0 100644
--- a/typings/xterm.d.ts
+++ b/typings/xterm.d.ts
@@ -439,7 +439,7 @@ declare module 'xterm' {
* Currently this is only used for a certain type of mouse reports that
* happen to be not UTF-8 compatible.
* The event value is a JS string, pass it to the underlying pty as
- * binary data, e.g. `pty.write(Buffer.from(data, 'binary'))`.
+ * binary data, e.g. `pty.write(Buffer.from(data, 'binary'))`.
* @returns an `IDisposable` to stop listening.
*/
onBinary: IEvent;
@@ -1049,10 +1049,6 @@ declare module 'xterm' {
isAttributeDefault(): boolean;
isFgDefault(): boolean;
isBgDefault(): boolean;
-
- equalFg(cell: IBufferCell): boolean;
- equalBg(cell: IBufferCell): boolean;
- equalFlags(cell: IBufferCell): boolean;
}
/**
From 7a8c3a42cd51c4961bed3ae8e8e0b11e602952ce Mon Sep 17 00:00:00 2001
From: javacs3
Date: Wed, 18 Dec 2019 16:38:02 +0800
Subject: [PATCH 35/47] refactor: serialize-addon, rename some APIs in
SerializeAddon
---
.../src/SerializeAddon.ts | 44 +++++++------------
1 file changed, 16 insertions(+), 28 deletions(-)
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts
index 160f6459..b9c5ed4f 100644
--- a/addons/xterm-addon-serialize/src/SerializeAddon.ts
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts
@@ -1,14 +1,13 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
+ *
+ * (EXPERIMENTAL) This Addon is still under development
*/
import { Terminal, ITerminalAddon, IBuffer, IBufferCell } from 'xterm';
-function crop(value: number | undefined, low: number, high: number, initial: number): number {
- if (value === undefined) {
- return initial;
- }
+function constrain(value: number, low: number, high: number): number {
return Math.max(low, Math.min(value, high));
}
@@ -22,43 +21,34 @@ abstract class BaseSerializeHandler {
const cell2 = this._buffer.getNullCell();
let oldCell = cell1;
- this._serializeStart(endRow - startRow);
+ this._beforeSerialize(endRow - startRow);
for (let row = startRow; row < endRow; row++) {
const line = this._buffer.getLine(row);
-
if (line) {
for (let col = 0; col < line.length; col++) {
- const newCell = line.getCell(col, oldCell === cell1 ? cell2 : cell1);
-
- if (!newCell) {
+ const c = line.getCell(col, oldCell === cell1 ? cell2 : cell1);
+ if (!c) {
console.warn(`Can't get cell at row=${row}, col=${col}`);
continue;
}
-
- this._nextCell(newCell, oldCell, row, col);
-
- oldCell = newCell;
+ this._nextCell(c, oldCell, row, col);
+ oldCell = c;
}
}
-
this._rowEnd(row);
}
- this._serializeEnd();
+ this._afterSerialize();
- return this._serializeFinished();
+ return this._serializeString();
}
protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
-
protected _rowEnd(row: number): void { }
-
- protected _serializeStart(rows: number): void { }
-
- protected _serializeEnd(): void { }
-
- protected _serializeFinished(): string { return ''; }
+ protected _beforeSerialize(rows: number): void { }
+ protected _afterSerialize(): void { }
+ protected _serializeString(): string { return ''; }
}
function equalFg(cell1: IBufferCell, cell2: IBufferCell): boolean {
@@ -91,7 +81,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
super(buffer);
}
- protected _serializeStart(rows: number): void {
+ protected _beforeSerialize(rows: number): void {
this._allRows = new Array(rows);
}
@@ -153,15 +143,13 @@ class StringSerializeHandler extends BaseSerializeHandler {
this._currentRow += cell.char;
}
- protected _serializeFinished(): string {
+ protected _serializeString(): string {
let rowEnd = this._allRows.length;
-
for (; rowEnd > 0; rowEnd--) {
if (this._allRows[rowEnd - 1]) {
break;
}
}
-
return this._allRows.slice(0, rowEnd).join('\r\n');
}
}
@@ -186,7 +174,7 @@ export class SerializeAddon implements ITerminalAddon {
const maxRows = this._terminal.buffer.length;
const handler = new StringSerializeHandler(this._terminal.buffer);
- rows = crop(rows, 0, maxRows, maxRows);
+ rows = (rows === undefined) ? maxRows : constrain(rows, 0, maxRows);
return handler.serialize(maxRows - rows, maxRows);
}
From 34bf2f9a0f6049fb0eef27e54f22648042609293 Mon Sep 17 00:00:00 2001
From: javacs3
Date: Thu, 19 Dec 2019 22:17:32 +0800
Subject: [PATCH 36/47] refactor: serialize-addon, remove timeout in api test
---
addons/xterm-addon-serialize/src/SerializeAddon.api.ts | 1 -
1 file changed, 1 deletion(-)
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.api.ts b/addons/xterm-addon-serialize/src/SerializeAddon.api.ts
index 27843714..c2a3c46e 100644
--- a/addons/xterm-addon-serialize/src/SerializeAddon.api.ts
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.api.ts
@@ -16,7 +16,6 @@ const height = 600;
describe('SerializeAddon', () => {
before(async function (): Promise {
- this.timeout(8 * 1000);
browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1,
args: [`--window-size=${width},${height}`]
From a4efefbf5eb45d48551ee5dab600a40a70e0e274 Mon Sep 17 00:00:00 2001
From: javacs3
Date: Tue, 31 Dec 2019 22:06:19 +0800
Subject: [PATCH 37/47] refactor: serialize-addon, move
SerializeAddon.benchmark.ts into addons/xterm-addon-serialize and add
README.md
---
addons/xterm-addon-serialize/.gitignore | 1 +
addons/xterm-addon-serialize/README.md | 42 +++++++++++++++++++
.../benchmark/SerializeAddon.benchmark.ts | 2 +-
.../benchmark/benchmark.json | 19 +++++++++
.../benchmark/tsconfig.json | 25 +++++++++++
addons/xterm-addon-serialize/package.json | 5 ++-
package.json | 6 +--
tsconfig.all.json | 3 +-
8 files changed, 97 insertions(+), 6 deletions(-)
create mode 100644 addons/xterm-addon-serialize/README.md
rename {test => addons/xterm-addon-serialize}/benchmark/SerializeAddon.benchmark.ts (96%)
create mode 100644 addons/xterm-addon-serialize/benchmark/benchmark.json
create mode 100644 addons/xterm-addon-serialize/benchmark/tsconfig.json
diff --git a/addons/xterm-addon-serialize/.gitignore b/addons/xterm-addon-serialize/.gitignore
index 3063f07d..03c051b3 100644
--- a/addons/xterm-addon-serialize/.gitignore
+++ b/addons/xterm-addon-serialize/.gitignore
@@ -1,2 +1,3 @@
lib
node_modules
+out-benchmark
diff --git a/addons/xterm-addon-serialize/README.md b/addons/xterm-addon-serialize/README.md
new file mode 100644
index 00000000..12e0ac87
--- /dev/null
+++ b/addons/xterm-addon-serialize/README.md
@@ -0,0 +1,42 @@
+## xterm-addon-serialize
+
+An addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables xterm.js to serialize a terminal framebuffer into string or html. This addon requires xterm.js v4+.
+
+⚠️ This is an experimental addon that is still under construction ⚠️
+
+### Install
+
+```bash
+npm install --save xterm-addon-serialize
+```
+
+### Usage
+
+```ts
+import { Terminal } from "xterm";
+import { SerializeAddon } from "xterm-addon-serialize";
+
+const terminal = new Terminal();
+const serializeAddon = new SerializeAddon();
+terminal.loadAddon(serializeAddon);
+
+terminal.write("something...", () => {
+ console.log(serializeAddon.serialize());
+});
+```
+
+See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/xterm-addon-serialize/typings/xterm-addon-serialize.d.ts) for more advanced usage.
+
+### Benchmark
+
+⚠️ Ensure you have `lolcat`, `hexdump` programs installed in your computer
+
+```shell
+$ git clone https://github.com/xtermjs/xterm.js.git
+$ cd xterm.js
+$ yarn
+$ cd addons/xterm-addon-serialize
+$ yarn benchmark && yarn benchmark-baseline
+$ # change some code in `xterm-addon-serialize`
+$ yarn benchmark-eval
+```
diff --git a/test/benchmark/SerializeAddon.benchmark.ts b/addons/xterm-addon-serialize/benchmark/SerializeAddon.benchmark.ts
similarity index 96%
rename from test/benchmark/SerializeAddon.benchmark.ts
rename to addons/xterm-addon-serialize/benchmark/SerializeAddon.benchmark.ts
index 3b8c4724..025e7194 100644
--- a/test/benchmark/SerializeAddon.benchmark.ts
+++ b/addons/xterm-addon-serialize/benchmark/SerializeAddon.benchmark.ts
@@ -8,7 +8,7 @@ import { perfContext, before, ThroughputRuntimeCase } from 'xterm-benchmark';
import { spawn } from 'node-pty';
import { Utf8ToUtf32, stringFromCodePoint } from 'common/input/TextDecoder';
import { Terminal } from 'public/Terminal';
-import { SerializeAddon } from 'addons/xterm-addon-serialize/src/SerializeAddon';
+import { SerializeAddon } from 'SerializeAddon';
class TestTerminal extends Terminal {
writeSync(data: string): void {
diff --git a/addons/xterm-addon-serialize/benchmark/benchmark.json b/addons/xterm-addon-serialize/benchmark/benchmark.json
new file mode 100644
index 00000000..f8b99b55
--- /dev/null
+++ b/addons/xterm-addon-serialize/benchmark/benchmark.json
@@ -0,0 +1,19 @@
+{
+ "APP_PATH": ".benchmark",
+ "evalConfig": {
+ "tolerance": {
+ "*": [0.75, 1.5],
+ "*.dev": [0.01, 1.5],
+ "*.cv": [0.01, 1.5],
+ "EscapeSequenceParser.benchmark.js.*.averageThroughput.mean": [0.9, 5]
+ },
+ "skip": [
+ "*.median",
+ "*.runs",
+ "*.dev",
+ "*.cv",
+ "EscapeSequenceParser.benchmark.js.*.averageRuntime",
+ "Terminal.benchmark.js.*.averageRuntime"
+ ]
+ }
+}
diff --git a/addons/xterm-addon-serialize/benchmark/tsconfig.json b/addons/xterm-addon-serialize/benchmark/tsconfig.json
new file mode 100644
index 00000000..4e62ed59
--- /dev/null
+++ b/addons/xterm-addon-serialize/benchmark/tsconfig.json
@@ -0,0 +1,25 @@
+{
+ "compilerOptions": {
+ "lib": ["dom", "es6"],
+ "outDir": "../out-benchmark",
+ "types": ["../../../node_modules/@types/node"],
+ "moduleResolution": "node",
+ "strict": false,
+ "target": "es2015",
+ "module": "commonjs",
+ "baseUrl": ".",
+ "paths": {
+ "common/*": ["../../../src/common/*"],
+ "browser/*": ["../../../src/browser/*"],
+ "public/*": ["../../../src/public/*"],
+ "Terminal": ["../../../src/Terminal"],
+ "SerializeAddon": ["../src/SerializeAddon"]
+ }
+ },
+ "include": ["../**/*", "../../../typings/xterm.d.ts", "../../../out/**/*"],
+ "exclude": ["../../../**/*test.ts", "../../**/*api.ts"],
+ "references": [
+ { "path": "../../../src/common" },
+ { "path": "../../../src/browser" }
+ ]
+}
diff --git a/addons/xterm-addon-serialize/package.json b/addons/xterm-addon-serialize/package.json
index c8a66c4b..787462f9 100644
--- a/addons/xterm-addon-serialize/package.json
+++ b/addons/xterm-addon-serialize/package.json
@@ -12,7 +12,10 @@
"build": "../../node_modules/.bin/tsc -p src",
"prepackage": "npm run build",
"package": "../../node_modules/.bin/webpack",
- "prepublishOnly": "npm run package"
+ "prepublishOnly": "npm run package",
+ "benchmark": "NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json",
+ "benchmark-baseline": "NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json --baseline out-benchmark/addons/xterm-addon-serialize/benchmark/*benchmark.js",
+ "benchmark-eval": "NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json --eval out-benchmark/addons/xterm-addon-serialize/benchmark/*benchmark.js"
},
"peerDependencies": {
"xterm": "^3.14.0"
diff --git a/package.json b/package.json
index 1acf11f5..e04a1ff8 100644
--- a/package.json
+++ b/package.json
@@ -23,9 +23,9 @@
"presetup": "node ./bin/install-addons.js",
"prepublishOnly": "npm run package",
"watch": "tsc -b -w ./tsconfig.all.json --preserveWatchOutput",
- "benchmark": "NODE_PATH=./out:./out-test/benchmark xterm-benchmark -r 5 -c test/benchmark/benchmark.json",
- "benchmark-baseline": "NODE_PATH=./out:./out-test/benchmark xterm-benchmark -r 5 -c test/benchmark/benchmark.json --baseline out-test/benchmark/test/benchmark/SerializeAddon.benchmark.js",
- "benchmark-eval": "NODE_PATH=./out:./out-test/benchmark xterm-benchmark -r 5 -c test/benchmark/benchmark.json --eval out-test/benchmark/test/benchmark/SerializeAddon.benchmark.js",
+ "benchmark": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json",
+ "benchmark-baseline": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json --baseline out-test/benchmark/test/benchmark/*benchmark.js",
+ "benchmark-eval": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json --eval out-test/benchmark/test/benchmark/*benchmark.js",
"clean": "rm -rf lib out addons/*/lib addons/*/out"
},
"devDependencies": {
diff --git a/tsconfig.all.json b/tsconfig.all.json
index 5a7ae05b..60cb9164 100644
--- a/tsconfig.all.json
+++ b/tsconfig.all.json
@@ -10,6 +10,7 @@
{ "path": "./addons/xterm-addon-search/src" },
{ "path": "./addons/xterm-addon-web-links/src" },
{ "path": "./addons/xterm-addon-webgl/src" },
- { "path": "./addons/xterm-addon-serialize/src" }
+ { "path": "./addons/xterm-addon-serialize/src" },
+ { "path": "./addons/xterm-addon-serialize/benchmark" }
]
}
From 56f4c3d6aefc29370ec0c71593744eb48046dfda Mon Sep 17 00:00:00 2001
From: javacs3
Date: Thu, 9 Jan 2020 21:15:30 +0800
Subject: [PATCH 38/47] remove BufferCellApiView
---
addons/xterm-addon-search/src/SearchAddon.ts | 4 +-
.../src/SerializeAddon.api.ts | 16 +++----
.../src/SerializeAddon.ts | 16 ++++---
addons/xterm-addon-webgl/src/GlyphRenderer.ts | 2 +-
src/common/Types.d.ts | 1 +
src/common/buffer/AttributeData.ts | 1 +
src/common/buffer/Constants.ts | 14 +-----
src/public/Terminal.ts | 48 ++-----------------
typings/xterm.d.ts | 18 -------
9 files changed, 30 insertions(+), 90 deletions(-)
diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts
index db2968e6..183687ac 100644
--- a/addons/xterm-addon-search/src/SearchAddon.ts
+++ b/addons/xterm-addon-search/src/SearchAddon.ts
@@ -320,13 +320,13 @@ export class SearchAddon implements ITerminalAddon {
break;
}
// Adjust the searchIndex to normalize emoji into single chars
- const char = cell.char;
+ const char = cell.getChars();
if (char.length > 1) {
resultIndex -= char.length - 1;
}
// Adjust the searchIndex for empty characters following wide unicode
// chars (eg. CJK)
- const charWidth = cell.width;
+ const charWidth = cell.getWidth();
if (charWidth === 0) {
resultIndex++;
}
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.api.ts b/addons/xterm-addon-serialize/src/SerializeAddon.api.ts
index c2a3c46e..a34aa0bd 100644
--- a/addons/xterm-addon-serialize/src/SerializeAddon.api.ts
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.api.ts
@@ -126,7 +126,7 @@ describe('SerializeAddon', () => {
const rows = 32;
const cols = 10;
const lines = newArray(
- (index: number) => digitsString(cols, index, `\x1b[38;5;${index}m`),
+ (index: number) => digitsString(cols, index, `\x1b[38;5;${16 + index}m`),
rows
);
await writeSync(page, lines.join('\\r\\n'));
@@ -256,7 +256,7 @@ describe('SerializeAddon', () => {
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
});
- it('serialize tabs correctly', async () => {
+ it('serialize tabs correctly', async () => {
const lines = [
'a\tb',
'aa\tc',
@@ -311,9 +311,9 @@ const NORMAL = '0';
const FG_P16_RED = '31';
const FG_P16_GREEN = '32';
const FG_P16_YELLOW = '33';
-const FG_P256_RED = '38;5;1';
-const FG_P256_GREEN = '38;5;2';
-const FG_P256_YELLOW = '38;5;3';
+const FG_P256_RED = '38;5;196';
+const FG_P256_GREEN = '38;5;46';
+const FG_P256_YELLOW = '38;5;226';
const FG_RGB_RED = '38;2;255;0;0';
const FG_RGB_GREEN = '38;2;0;255;0';
const FG_RGB_YELLOW = '38;2;255;255;0';
@@ -323,9 +323,9 @@ const FG_RESET = '39';
const BG_P16_RED = '41';
const BG_P16_GREEN = '42';
const BG_P16_YELLOW = '43';
-const BG_P256_RED = '48;5;1';
-const BG_P256_GREEN = '48;5;2';
-const BG_P256_YELLOW = '48;5;3';
+const BG_P256_RED = '48;5;196';
+const BG_P256_GREEN = '48;5;46';
+const BG_P256_YELLOW = '48;5;226';
const BG_RGB_RED = '48;2;255;0;0';
const BG_RGB_GREEN = '48;2;0;255;0';
const BG_RGB_YELLOW = '48;2;255;255;0';
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts
index b9c5ed4f..1d011726 100644
--- a/addons/xterm-addon-serialize/src/SerializeAddon.ts
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts
@@ -104,15 +104,19 @@ class StringSerializeHandler extends BaseSerializeHandler {
if (fgChanged) {
const color = cell.getFgColor();
if (cell.isFgRGB()) { sgrSeq.push(38, 2, (color >>> 16) & 0xFF, (color >>> 8) & 0xFF, color & 0xFF); }
- else if (cell.isFgPalette256()) { sgrSeq.push(38, 5, color); }
- else if (cell.isFgPalette16()) { sgrSeq.push(color & 8 ? 90 + (color & 7) : 30 + (color & 7)); }
+ else if (cell.isFgPalette()) {
+ if (color >= 16) { sgrSeq.push(38, 5, color); }
+ else { sgrSeq.push(color & 8 ? 90 + (color & 7) : 30 + (color & 7)); }
+ }
else { sgrSeq.push(39); }
}
if (bgChanged) {
const color = cell.getBgColor();
if (cell.isBgRGB()) { sgrSeq.push(48, 2, (color >>> 16) & 0xFF, (color >>> 8) & 0xFF, color & 0xFF); }
- else if (cell.isBgPalette256()) { sgrSeq.push(48, 5, color); }
- else if (cell.isBgPalette16()) { sgrSeq.push(color & 8 ? 100 + (color & 7) : 40 + (color & 7)); }
+ else if (cell.isBgPalette()) {
+ if (color >= 16) { sgrSeq.push(48, 5, color); }
+ else { sgrSeq.push(color & 8 ? 100 + (color & 7) : 40 + (color & 7)); }
+ }
else { sgrSeq.push(49); }
}
if (flagsChanged) {
@@ -133,14 +137,14 @@ class StringSerializeHandler extends BaseSerializeHandler {
// Count number of null cells encountered after the last non-null cell and move the cursor
// if a non-null cell is found (eg. \t or cursor move)
- if (cell.char === '') {
+ if (cell.getChars() === '') {
this._nullCellCount++;
} else if (this._nullCellCount > 0) {
this._currentRow += `\x1b[${this._nullCellCount}C`;
this._nullCellCount = 0;
}
- this._currentRow += cell.char;
+ this._currentRow += cell.getChars();
}
protected _serializeString(): string {
diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts
index 7b35949d..bc46a085 100644
--- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts
+++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts
@@ -258,7 +258,7 @@ export class GlyphRenderer {
if (!line) {
line = terminal.buffer.getLine(row);
}
- const chars = line!.getCell(x)!.char;
+ const chars = line!.getCell(x)!.getChars();
this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, model.cells[offset + RENDER_MODEL_FG_OFFSET], chars);
} else {
this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, model.cells[offset + RENDER_MODEL_FG_OFFSET]);
diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts
index 2dcc704b..33cc67d7 100644
--- a/src/common/Types.d.ts
+++ b/src/common/Types.d.ts
@@ -93,6 +93,7 @@ export interface IAttributeData {
isBgPalette(): boolean;
isFgDefault(): boolean;
isBgDefault(): boolean;
+ isAttributeDefault(): boolean;
// colors
getFgColor(): number;
diff --git a/src/common/buffer/AttributeData.ts b/src/common/buffer/AttributeData.ts
index 0e7e2705..0b2679ee 100644
--- a/src/common/buffer/AttributeData.ts
+++ b/src/common/buffer/AttributeData.ts
@@ -47,6 +47,7 @@ export class AttributeData implements IAttributeData {
public isBgPalette(): boolean { return (this.bg & Attributes.CM_MASK) === Attributes.CM_P16 || (this.bg & Attributes.CM_MASK) === Attributes.CM_P256; }
public isFgDefault(): boolean { return (this.fg & Attributes.CM_MASK) === 0; }
public isBgDefault(): boolean { return (this.bg & Attributes.CM_MASK) === 0; }
+ public isAttributeDefault(): boolean { return this.fg === 0 && this.bg === 0; }
// colors
public getFgColor(): number {
diff --git a/src/common/buffer/Constants.ts b/src/common/buffer/Constants.ts
index 81ac773a..276a5c54 100644
--- a/src/common/buffer/Constants.ts
+++ b/src/common/buffer/Constants.ts
@@ -116,12 +116,7 @@ export const enum FgFlags {
BOLD = 0x8000000,
UNDERLINE = 0x10000000,
BLINK = 0x20000000,
- INVISIBLE = 0x40000000,
-
- /**
- * bit 27..31 (32th bit unused)
- */
- FM_MASK = 0x7C000000
+ INVISIBLE = 0x40000000
}
export const enum BgFlags {
@@ -129,10 +124,5 @@ export const enum BgFlags {
* bit 27..32 (upper 4 unused)
*/
ITALIC = 0x4000000,
- DIM = 0x8000000,
-
- /**
- * bit 27..32 (upper 4 unused)
- */
- FM_MASK = 0xFC000000
+ DIM = 0x8000000
}
diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts
index 5854cef6..fd61aaf2 100644
--- a/src/public/Terminal.ts
+++ b/src/public/Terminal.ts
@@ -5,9 +5,8 @@
import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi, IParser, IFunctionIdentifier } from 'xterm';
import { ITerminal } from '../Types';
-import { IBufferLine } from 'common/Types';
+import { IBufferLine, ICellData } from 'common/Types';
import { IBuffer } from 'common/buffer/Types';
-import { Attributes } from 'common/buffer/Constants';
import { CellData } from 'common/buffer/CellData';
import { Terminal as TerminalCore } from '../Terminal';
import * as Strings from '../browser/LocalizableStrings';
@@ -203,7 +202,7 @@ class BufferApiView implements IBufferApi {
}
return new BufferLineApiView(line);
}
- public getNullCell(): IBufferCellApi { return new BufferCellApiView(new CellData()); }
+ public getNullCell(): IBufferCellApi { return new CellData(); }
}
class BufferLineApiView implements IBufferLineApi {
@@ -211,59 +210,22 @@ class BufferLineApiView implements IBufferLineApi {
public get isWrapped(): boolean { return this._line.isWrapped; }
public get length(): number { return this._line.length; }
- public getCell(x: number, cell?: BufferCellApiView): IBufferCellApi | undefined {
+ public getCell(x: number, cell?: IBufferCellApi): IBufferCellApi | undefined {
if (x < 0 || x >= this._line.length) {
return undefined;
}
if (cell) {
- this._line.loadCell(x, cell.cell);
+ this._line.loadCell(x, cell);
return cell;
}
- return new BufferCellApiView(this._line.loadCell(x, new CellData()));
+ return this._line.loadCell(x, new CellData());
}
public translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string {
return this._line.translateToString(trimRight, startColumn, endColumn);
}
}
-class BufferCellApiView implements IBufferCellApi {
- constructor(public cell: CellData) {}
-
- public get char(): string { return this.cell.getChars(); }
- public get width(): number { return this.cell.getWidth(); }
-
- public getWidth(): number { return this.cell.getWidth(); }
- public getChars(): string { return this.cell.getChars(); }
- public getCode(): number { return this.cell.getCode(); }
-
- public isInverse(): number { return this.cell.isInverse(); }
- public isBold(): number { return this.cell.isBold(); }
- public isUnderline(): number { return this.cell.isUnderline(); }
- public isBlink(): number { return this.cell.isBlink(); }
- public isInvisible(): number { return this.cell.isInvisible(); }
- public isItalic(): number { return this.cell.isItalic(); }
- public isDim(): number { return this.cell.isDim(); }
-
- public getFgColorMode(): number { return this.cell.getFgColorMode(); }
- public getBgColorMode(): number { return this.cell.getBgColorMode(); }
- public isFgRGB(): boolean { return this.cell.isFgRGB(); }
- public isBgRGB(): boolean { return this.cell.isBgRGB(); }
- public isFgPalette(): boolean { return this.cell.isFgPalette(); }
- public isBgPalette(): boolean { return this.cell.isBgPalette(); }
- public isFgPalette16(): boolean { return this.cell.getFgColorMode() === Attributes.CM_P16; }
- public isBgPalette16(): boolean { return this.cell.getBgColorMode() === Attributes.CM_P16; }
- public isFgPalette256(): boolean { return this.cell.getFgColorMode() === Attributes.CM_P256; }
- public isBgPalette256(): boolean { return this.cell.getBgColorMode() === Attributes.CM_P256; }
-
- public isAttributeDefault(): boolean { return this.cell.fg === 0 && this.cell.bg === 0; }
- public isFgDefault(): boolean { return this.cell.isFgDefault(); }
- public isBgDefault(): boolean { return this.cell.isBgDefault(); }
-
- public getFgColor(): number { return this.cell.getFgColor(); }
- public getBgColor(): number { return this.cell.getBgColor(); }
-}
-
class ParserApi implements IParser {
constructor(private _core: ITerminal) {}
diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts
index 243266d0..c687b783 100644
--- a/typings/xterm.d.ts
+++ b/typings/xterm.d.ts
@@ -1007,20 +1007,6 @@ declare module 'xterm' {
* Represents a single cell in the terminal's buffer.
*/
interface IBufferCell {
- /**
- * The character within the cell.
- */
- readonly char: string;
-
- /**
- * The width of the character. Some examples:
- *
- * - This is `1` for most cells.
- * - This is `2` for wide character like CJK glyphs.
- * - This is `0` for cells immediately following cells with a width of `2`.
- */
- readonly width: number;
-
getWidth(): number;
getChars(): string;
getCode(): number;
@@ -1042,10 +1028,6 @@ declare module 'xterm' {
isBgRGB(): boolean;
isFgPalette(): boolean;
isBgPalette(): boolean;
- isFgPalette16(): boolean;
- isBgPalette16(): boolean;
- isFgPalette256(): boolean;
- isBgPalette256(): boolean;
isAttributeDefault(): boolean;
isFgDefault(): boolean;
isBgDefault(): boolean;
From c512e48c5261441698a3da011d85154e813a7818 Mon Sep 17 00:00:00 2001
From: javacs3
Date: Tue, 4 Feb 2020 14:46:03 +0800
Subject: [PATCH 39/47] fix: api test fail due to IBufferCell api changes
---
test/api/CharWidth.api.ts | 4 ++--
test/api/Terminal.api.ts | 16 ++++++++--------
2 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/test/api/CharWidth.api.ts b/test/api/CharWidth.api.ts
index 39f0bfe7..a2352f40 100644
--- a/test/api/CharWidth.api.ts
+++ b/test/api/CharWidth.api.ts
@@ -98,8 +98,8 @@ async function sumWidths(start: number, end: number, sentinel: string): Promise<
if (!cell) {
break;
}
- window.result += cell.width;
- if (cell.char === '${sentinel}') {
+ window.result += cell.getWidth();
+ if (cell.getChars() === '${sentinel}') {
return;
}
}
diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts
index 40deecf2..cdfab61f 100644
--- a/test/api/Terminal.api.ts
+++ b/test/api/Terminal.api.ts
@@ -498,15 +498,15 @@ describe('API Integration Tests', function(): void {
await openTerminal({ cols: 5 });
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(-1)`), undefined);
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(5)`), undefined);
- assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(0).char`), '');
- assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(0).width`), 1);
+ assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(0).getChars()`), '');
+ assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(0).getWidth()`), 1);
await writeSync(page, 'a文');
- assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(0).char`), 'a');
- assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(0).width`), 1);
- assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(1).char`), '文');
- assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(1).width`), 2);
- assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(2).char`), '');
- assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(2).width`), 0);
+ assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(0).getChars()`), 'a');
+ assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(0).getWidth()`), 1);
+ assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(1).getChars()`), '文');
+ assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(1).getWidth()`), 2);
+ assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(2).getChars()`), '');
+ assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(2).getWidth()`), 0);
});
});
});
From 1b4a3475e86193bd4b4172099161c4443fb30708 Mon Sep 17 00:00:00 2001
From: Daniel Imms
Date: Tue, 4 Feb 2020 07:07:34 -0800
Subject: [PATCH 40/47] Fix demo after bad merge
---
demo/client.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/demo/client.ts b/demo/client.ts
index b7951423..95dfcd1e 100644
--- a/demo/client.ts
+++ b/demo/client.ts
@@ -152,6 +152,7 @@ function createTerminal(): void {
addons.serialize.instance = new SerializeAddon();
addons.fit.instance = new FitAddon();
addons.unicode11.instance = new Unicode11Addon();
+ addons['web-links'].instance = new WebLinksAddon();
typedTerm.loadAddon(addons.fit.instance);
typedTerm.loadAddon(addons.search.instance);
typedTerm.loadAddon(addons.serialize.instance);
From b50b6ccb8b6208694495456bd0bc3f9d870ec071 Mon Sep 17 00:00:00 2001
From: Daniel Imms
Date: Tue, 4 Feb 2020 07:41:04 -0800
Subject: [PATCH 41/47] Fix fg used by selection in webgl again
See #2650
---
addons/xterm-addon-webgl/src/GlyphRenderer.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts
index 5c5d5970..6c67febb 100644
--- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts
+++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts
@@ -284,7 +284,7 @@ export class GlyphRenderer {
line = terminal.buffer.getLine(row);
}
const chars = line!.getCell(x)!.getChars();
- this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, model.cells[offset + RENDER_MODEL_FG_OFFSET], chars);
+ this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, fg, chars);
} else {
this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, fg);
}
From 51a564eb9c22e4d765f98f153c9352e81ce6bd75 Mon Sep 17 00:00:00 2001
From: Daniel Imms
Date: Tue, 4 Feb 2020 07:45:12 -0800
Subject: [PATCH 42/47] Remove links to serialize from test/benchmark
---
test/benchmark/tsconfig.json | 2 --
1 file changed, 2 deletions(-)
diff --git a/test/benchmark/tsconfig.json b/test/benchmark/tsconfig.json
index 51abeba5..cac99d90 100644
--- a/test/benchmark/tsconfig.json
+++ b/test/benchmark/tsconfig.json
@@ -16,8 +16,6 @@
"paths": {
"common/*": [ "../../src/common/*" ],
"browser/*": [ "../../src/browser/*" ],
- "addons/xterm-addon-serialize/src/*": ["../../addons/xterm-addon-serialize/src/*"],
- "public/*": ["../../src/public/*"],
"Terminal": ["../../src/Terminal"]
},
},
From 89370a1f74a7962ec3d65ce464b6e94e933ed86c Mon Sep 17 00:00:00 2001
From: Daniel Imms
Date: Tue, 4 Feb 2020 07:54:11 -0800
Subject: [PATCH 43/47] Explain why reusing IBufferCell is useful
---
typings/xterm.d.ts | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts
index 2af7e87e..58f818ee 100644
--- a/typings/xterm.d.ts
+++ b/typings/xterm.d.ts
@@ -982,7 +982,9 @@ declare module 'xterm' {
* behavior.
*
* @param x The character index to get.
- * @param cell Optional cell object to load data into.
+ * @param cell Optional cell object to load data into for performance
+ * reasons. This is mainly useful when every cell in the buffer is being
+ * looped over to avoid creating new objects for every cell.
*/
getCell(x: number, cell?: IBufferCell): IBufferCell | undefined;
From e5246f3993825559f8a8e987c815e56c7d3d3451 Mon Sep 17 00:00:00 2001
From: Daniel Imms
Date: Tue, 4 Feb 2020 07:55:26 -0800
Subject: [PATCH 44/47] jsdoc IBufferLine.length
---
typings/xterm.d.ts | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts
index 58f818ee..067ffc31 100644
--- a/typings/xterm.d.ts
+++ b/typings/xterm.d.ts
@@ -972,6 +972,11 @@ declare module 'xterm' {
* Whether the line is wrapped from the previous line.
*/
readonly isWrapped: boolean;
+
+ /**
+ * The length of the line, all call to getCell beyond the length will result
+ * in `undefined`.
+ */
readonly length: number;
/**
From fac025367b161ce072b29d62ac9883b6317d4cad Mon Sep 17 00:00:00 2001
From: Daniel Imms
Date: Tue, 4 Feb 2020 08:15:21 -0800
Subject: [PATCH 45/47] Document IBufferCell members
---
typings/xterm.d.ts | 82 +++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 77 insertions(+), 5 deletions(-)
diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts
index 067ffc31..5f97cf10 100644
--- a/typings/xterm.d.ts
+++ b/typings/xterm.d.ts
@@ -1008,30 +1008,102 @@ declare module 'xterm' {
* Represents a single cell in the terminal's buffer.
*/
interface IBufferCell {
+ /**
+ * The width of the character. Some examples:
+ *
+ * - `1` for most cells.
+ * - `2` for wide character like CJK glyphs.
+ * - `0` for cells immediately following cells with a width of `2`.
+ */
getWidth(): number;
+
+ /**
+ * The character(s) within the cell. Examples of what this can contain:
+ *
+ * - A normal width character
+ * - A wide character (eg. CJK)
+ * - An emoji
+ */
getChars(): string;
+
+ /**
+ * Gets the UTF32 codepoint of single characters, if content is a combined
+ * string it returns the codepoint of the last character in the string.
+ */
getCode(): number;
+ /**
+ * Gets the number representation of the foreground color mode, this can be
+ * used to perform quick comparisons of 2 cells to see if they're the same.
+ * Use `isFgRGB`, `isFgPalette` and `isFgDefault` to check what color mode
+ * a cell is.
+ */
getFgColorMode(): number;
+
+ /**
+ * Gets the number representation of the background color mode, this can be
+ * used to perform quick comparisons of 2 cells to see if they're the same.
+ * Use `isBgRGB`, `isBgPalette` and `isBgDefault` to check what color mode
+ * a cell is.
+ */
getBgColorMode(): number;
+
+ /**
+ * Gets a cell's foreground color number, this differs depending on what the
+ * color mode of the cell is:
+ *
+ * - Default: This should be 0, representing the default foreground color
+ * (CSI 39 m).
+ * - Palette: This is a number from 0 to 255 of ANSI colors (CSI 3(0-7) m,
+ * CSI 9(0-7) m, CSI 38 ; 5 ; 0-255 m).
+ * - RGB: A hex value representing a 'true color': 0xRRGGBB.
+ * (CSI 3 8 ; 2 ; Pi ; Pr ; Pg ; Pb)
+ */
getFgColor(): number;
+
+ /**
+ * Gets a cell's background color number, this differs depending on what the
+ * color mode of the cell is:
+ *
+ * - Default: This should be 0, representing the default background color
+ * (CSI 49 m).
+ * - Palette: This is a number from 0 to 255 of ANSI colors
+ * (CSI 4(0-7) m, CSI 10(0-7) m, CSI 48 ; 5 ; 0-255 m).
+ * - RGB: A hex value representing a 'true color': 0xRRGGBB
+ * (CSI 4 8 ; 2 ; Pi ; Pr ; Pg ; Pb)
+ */
getBgColor(): number;
- isInverse(): number;
+ /** Whether the cell has the bold attribute (CSI 1 m). */
isBold(): number;
- isUnderline(): number;
- isBlink(): number;
- isInvisible(): number;
+ /** Whether the cell has the inverse attribute (CSI 3 m). */
isItalic(): number;
+ /** Whether the cell has the inverse attribute (CSI 2 m). */
isDim(): number;
+ /** Whether the cell has the underline attribute (CSI 4 m). */
+ isUnderline(): number;
+ /** Whether the cell has the inverse attribute (CSI 5 m). */
+ isBlink(): number;
+ /** Whether the cell has the inverse attribute (CSI 7 m). */
+ isInverse(): number;
+ /** Whether the cell has the inverse attribute (CSI 8 m). */
+ isInvisible(): number;
+ /** Whether the cell is using the RGB foreground color mode. */
isFgRGB(): boolean;
+ /** Whether the cell is using the RGB background color mode. */
isBgRGB(): boolean;
+ /** Whether the cell is using the palette foreground color mode. */
isFgPalette(): boolean;
+ /** Whether the cell is using the palette background color mode. */
isBgPalette(): boolean;
- isAttributeDefault(): boolean;
+ /** Whether the cell is using the default foreground color mode. */
isFgDefault(): boolean;
+ /** Whether the cell is using the default background color mode. */
isBgDefault(): boolean;
+
+ /** Whether the cell has the default attribute (no color or style). */
+ isAttributeDefault(): boolean;
}
/**
From 0b6a5b763095f516dd88453844a5743fffe0cfbc Mon Sep 17 00:00:00 2001
From: Daniel Imms
Date: Tue, 4 Feb 2020 08:31:06 -0800
Subject: [PATCH 46/47] Remove getNullCell
Replace with fetching cell (0,0) and using that as the initial null cell. We
can reconsider adding this back if it's needed
---
.../src/SerializeAddon.ts | 20 ++++++++++++++++---
src/public/Terminal.ts | 1 -
typings/xterm.d.ts | 7 -------
3 files changed, 17 insertions(+), 11 deletions(-)
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts
index 1d011726..94a3b7ce 100644
--- a/addons/xterm-addon-serialize/src/SerializeAddon.ts
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts
@@ -16,9 +16,7 @@ abstract class BaseSerializeHandler {
constructor(private _buffer: IBuffer) { }
serialize(startRow: number, endRow: number): string {
- // we need two of them to flip between old and new cell
- const cell1 = this._buffer.getNullCell();
- const cell2 = this._buffer.getNullCell();
+ const { cell1, cell2 } = this._getWorkCells();
let oldCell = cell1;
this._beforeSerialize(endRow - startRow);
@@ -44,6 +42,22 @@ abstract class BaseSerializeHandler {
return this._serializeString();
}
+ private _getWorkCells(): { cell1: IBufferCell, cell2: IBufferCell } {
+ const line = this._buffer.getLine(0);
+ if (!line) {
+ throw new Error('Could not fetch first line for serialization');
+ }
+ const cell1 = line.getCell(0);
+ if (!cell1) {
+ throw new Error('Could not fetch first cell for serialization');
+ }
+ const cell2 = line.getCell(0);
+ if (!cell2) {
+ throw new Error('Could not fetch first cell for serialization');
+ }
+ return { cell1, cell2 };
+ }
+
protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
protected _rowEnd(row: number): void { }
protected _beforeSerialize(rows: number): void { }
diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts
index 065dfb64..2b180a4a 100644
--- a/src/public/Terminal.ts
+++ b/src/public/Terminal.ts
@@ -204,7 +204,6 @@ class BufferApiView implements IBufferApi {
}
return new BufferLineApiView(line);
}
- public getNullCell(): IBufferCellApi { return new CellData(); }
}
class BufferLineApiView implements IBufferLineApi {
diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts
index 5f97cf10..2fcce231 100644
--- a/typings/xterm.d.ts
+++ b/typings/xterm.d.ts
@@ -955,13 +955,6 @@ declare module 'xterm' {
* @param y The line index to get.
*/
getLine(y: number): IBufferLine | undefined;
-
- /**
- * Creates an empty cell object suitable as a cell reference in
- * `line.getCell(x, cell)`. Use this to avoid costly recreation of
- * cell objects when dealing with tons of cells.
- */
- getNullCell(): IBufferCell;
}
/**
From 9c8deb14705435377aa243ae6db041e608aa540e Mon Sep 17 00:00:00 2001
From: Daniel Imms
Date: Tue, 4 Feb 2020 08:36:57 -0800
Subject: [PATCH 47/47] Revert "Remove getNullCell"
This reverts commit 0b6a5b763095f516dd88453844a5743fffe0cfbc.
---
.../src/SerializeAddon.ts | 20 +++----------------
src/public/Terminal.ts | 1 +
typings/xterm.d.ts | 7 +++++++
3 files changed, 11 insertions(+), 17 deletions(-)
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts
index 94a3b7ce..1d011726 100644
--- a/addons/xterm-addon-serialize/src/SerializeAddon.ts
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts
@@ -16,7 +16,9 @@ abstract class BaseSerializeHandler {
constructor(private _buffer: IBuffer) { }
serialize(startRow: number, endRow: number): string {
- const { cell1, cell2 } = this._getWorkCells();
+ // we need two of them to flip between old and new cell
+ const cell1 = this._buffer.getNullCell();
+ const cell2 = this._buffer.getNullCell();
let oldCell = cell1;
this._beforeSerialize(endRow - startRow);
@@ -42,22 +44,6 @@ abstract class BaseSerializeHandler {
return this._serializeString();
}
- private _getWorkCells(): { cell1: IBufferCell, cell2: IBufferCell } {
- const line = this._buffer.getLine(0);
- if (!line) {
- throw new Error('Could not fetch first line for serialization');
- }
- const cell1 = line.getCell(0);
- if (!cell1) {
- throw new Error('Could not fetch first cell for serialization');
- }
- const cell2 = line.getCell(0);
- if (!cell2) {
- throw new Error('Could not fetch first cell for serialization');
- }
- return { cell1, cell2 };
- }
-
protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
protected _rowEnd(row: number): void { }
protected _beforeSerialize(rows: number): void { }
diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts
index 2b180a4a..065dfb64 100644
--- a/src/public/Terminal.ts
+++ b/src/public/Terminal.ts
@@ -204,6 +204,7 @@ class BufferApiView implements IBufferApi {
}
return new BufferLineApiView(line);
}
+ public getNullCell(): IBufferCellApi { return new CellData(); }
}
class BufferLineApiView implements IBufferLineApi {
diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts
index 2685d55c..7b430722 100644
--- a/typings/xterm.d.ts
+++ b/typings/xterm.d.ts
@@ -1117,6 +1117,13 @@ declare module 'xterm' {
* @param y The line index to get.
*/
getLine(y: number): IBufferLine | undefined;
+
+ /**
+ * Creates an empty cell object suitable as a cell reference in
+ * `line.getCell(x, cell)`. Use this to avoid costly recreation of
+ * cell objects when dealing with tons of cells.
+ */
+ getNullCell(): IBufferCell;
}
/**