Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add foreground/background color support for SerializeAddon #2369

Merged
merged 34 commits into from
Feb 3, 2020
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
a069df7
refactor SerializeAddons initial value validation
JavaCS3 Aug 4, 2019
f088a99
refactor SerializeAddon extract SerializeHandler
JavaCS3 Aug 5, 2019
d81b499
add frontground/background color support for SerializeAddon
JavaCS3 Aug 7, 2019
9270911
add SerializeAddon in demo page
JavaCS3 Aug 8, 2019
693f304
add trim last empty lines support for SerializeAddon
JavaCS3 Aug 9, 2019
f12120b
fix demo page <code> tag won't display multi space chars
JavaCS3 Aug 9, 2019
6710c13
fix cell.getFgColor/getBgColor return color256 code even it's color16
JavaCS3 Aug 9, 2019
3cba961
refactor SerializeAddon: use public api as much as possible
JavaCS3 Aug 10, 2019
73f9d45
refactor use a nicer IBufferCell design
JavaCS3 Aug 14, 2019
7a7f346
refactor ICellColor to CellColor
JavaCS3 Aug 23, 2019
22a2496
fix 'Can't resolve 'xterm' in xterm.js/addons/xterm-addon-serialize/out'
JavaCS3 Aug 24, 2019
f1a17e3
refactor CellColor constructor
JavaCS3 Aug 25, 2019
ae70ef0
resolve SerializeAddon importing workaround
JavaCS3 Aug 25, 2019
cd5f3e9
update cell color api design
JavaCS3 Aug 29, 2019
e2df7dc
add benchmark for serialize addon
JavaCS3 Aug 16, 2019
3543155
fix typo
JavaCS3 Sep 8, 2019
7ec99c2
update IBufferCell API
JavaCS3 Nov 20, 2019
1093b7c
fix lint error
JavaCS3 Nov 21, 2019
6dda0c2
Merge branch 'feat/serialize-addon' into pr/JavaCS3/2369
Tyriar Nov 24, 2019
709214a
Add webpacked version to client
Tyriar Nov 24, 2019
45af025
Make serialize addon api tests fast
Tyriar Nov 24, 2019
3969eb9
Add failing tab test
Tyriar Nov 24, 2019
903d75e
Refactor
JavaCS3 Nov 28, 2019
ca12cf5
Support serializing tabs
Tyriar Dec 7, 2019
fb9b19b
Merge remote-tracking branch 'ups/feat/serialize-addon' into pr/JavaC…
Tyriar Dec 7, 2019
9e1f607
fix: serialize-addon, remove unused packages in yarn.lock
JavaCS3 Dec 16, 2019
a43cc23
refactor: serialize-addon, remove _cellFgBgChanged(), _cellFlagsChang…
JavaCS3 Dec 17, 2019
b80b5a9
refactor: serialize-addon, rename _nextRow() to _rowEnd()
JavaCS3 Dec 17, 2019
84f5f20
refactor: serialize-addon, remove equalFg(), equalBg(), equalFlags() …
JavaCS3 Dec 18, 2019
7a8c3a4
refactor: serialize-addon, rename some APIs in SerializeAddon
JavaCS3 Dec 18, 2019
34bf2f9
refactor: serialize-addon, remove timeout in api test
JavaCS3 Dec 19, 2019
a4efefb
refactor: serialize-addon, move SerializeAddon.benchmark.ts into addo…
JavaCS3 Dec 31, 2019
56f4c3d
remove BufferCellApiView
JavaCS3 Jan 9, 2020
cf33f7c
Merge branch 'feat/serialize-addon' into feat/serialize-addon
Tyriar Feb 3, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
338 changes: 287 additions & 51 deletions addons/xterm-addon-serialize/src/SerializeAddon.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
*/

import * as puppeteer from 'puppeteer';
import * as util from 'util';
import { assert } from 'chai';
import { ITerminalOptions } from 'xterm';

Expand All @@ -17,89 +16,259 @@ const height = 600;

describe('SerializeAddon', () => {
before(async function (): Promise<any> {
this.timeout(20000);
Tyriar marked this conversation as resolved.
Show resolved Hide resolved
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<any> {
this.timeout(20000);
await page.goto(APP);
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<any> {
this.timeout(20000);
const rows = 10;
const cols = 10;
const blankline = ' '.repeat(cols);
const lines = newArray<string>(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();`), '');
});

assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
it('trim last empty lines', async function (): Promise<any> {
const cols = 10;
const lines = [
'',
'',
digitsString(cols),
digitsString(cols),
'',
'',
digitsString(cols),
digitsString(cols),
'',
'',
''
];
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<any> {
this.timeout(20000);
const rows = 10;
const cols = 10;
const digitsLine = digitsString(cols);
const lines = newArray<string>(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 n rows of content', async function (): Promise<any> {
this.timeout(20000);
it('serialize half rows of content', async function (): Promise<any> {
const rows = 10;
const halfRows = rows >> 1;
const cols = 10;
const lines = newArray<string>((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<any> {
this.timeout(20000);
const rows = 10;
const cols = 10;
const lines = newArray<string>((index: number) => digitsString(cols, index), rows);
await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize(0);`), '');
});

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'))});
`);
it('serialize all rows of content with color16', async function (): Promise<any> {
const cols = 10;
const color16 = [
30, 31, 32, 33, 34, 35, 36, 37, // Set foreground 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<string>(
(index: number) => digitsString(cols, index, `\x1b[${color16[index % color16.length]}m`),
rows
);
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(0);`), '');
it('serialize all rows of content with fg/bg flags', async function (): Promise<any> {
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 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<any> {
const rows = 32;
const cols = 10;
const lines = newArray<string>(
(index: number) => digitsString(cols, index, `\x1b[38;5;${index}m`),
rows
);
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<any> {
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
];
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<any> {
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
];
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<any> {
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
];
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<any> {
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
];
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<any> {
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
];
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<any> {
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
];
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',
'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();`), expected.join('\r\n'));
});
});

Expand All @@ -125,10 +294,77 @@ function newArray<T>(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';

async function writeSync(page: puppeteer.Page, data: string): Promise<void> {
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<void>): Promise<void> {
if (preFn) {
await preFn();
}
const result = await page.evaluate(fn);
if (result !== val) {
return new Promise<void>(r => {
setTimeout(() => r(pollFor(page, fn, val, preFn)), 10);
});
}
}
Loading