From 2571a0654f87e4663ac8eb4cbddaa6dd4075d15a Mon Sep 17 00:00:00 2001 From: javacs3 Date: Sun, 7 Jul 2019 22:59:15 +0800 Subject: [PATCH 001/268] 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 000000000..3063f07d5 --- /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 000000000..1c7944456 --- /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 000000000..c8a66c4ba --- /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 000000000..88e4503e2 --- /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 000000000..8dd5b1940 --- /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 000000000..5539aa564 --- /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 000000000..e36e724a2 --- /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 000000000..4cabbad91 --- /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 e2259841f..eb8b10535 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 f26c010f0..1ee4986d4 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 7ddf33d51..5a7ae05bb 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 002/268] 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 88e4503e2..464316c31 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 003/268] 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 89f0d61e5..2d8160977 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 1ee4986d4..f26c010f0 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 004/268] 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 8dd5b1940..720da58be 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 e36e724a2..78b615d74 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 005/268] 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 720da58be..10d920cdc 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 006/268] 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 464316c31..5543696ba 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 10d920cdc..5d0609493 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 78b615d74..9e7b500aa 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 007/268] 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 5d0609493..9328e6748 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 2de22f93568ba143898313625923dd2177b9eab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 18 Aug 2019 17:36:06 +0200 Subject: [PATCH 008/268] extract @vt entries from docs --- bin/extract_vtfeatures.js | 181 ++++++++++++++++++++++++++++++++++++++ package.json | 4 +- src/InputHandler.ts | 14 +++ yarn.lock | 5 ++ 4 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 bin/extract_vtfeatures.js diff --git a/bin/extract_vtfeatures.js b/bin/extract_vtfeatures.js new file mode 100644 index 000000000..60f3200e5 --- /dev/null +++ b/bin/extract_vtfeatures.js @@ -0,0 +1,181 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + * + * Script to extract vt features documented in docstrings. + */ +const fs = require('fs'); +const Mustache = require('mustache'); + +/** + * regexp to fetch all comments + * Fetches all multiline comments and single lines containing '// @vt:'. + */ +const REX_COMMENTS = /^\s*?[/][*][*]([\s\S]*?)[*][/]|^\s*?\/\/ ([@]vt[:].*?)$/mug; + +// expected - "@vt: type name "sequence" "short description" +/** + * regexp to parse the @vt line + * expected data - "@vt: <"name"> "" "" + */ +const REX_VT_LINE = /^[@]vt\:\s*(\w+)\s*(\w+)\s*(\w+)\s*"(.*?)"\s*"(.*?)"\s*"(.*?)".*$/; + +// known vt command types +const TYPES = [ + 'C0', + 'C1', + 'ESC', + 'CSI', + 'DCS', + 'OSC', + 'APC', + 'PM', + 'SOS' +]; + +const MARKDOWN_TMPL = ` +# Supported VT features by xterm.js +Version: {{version}} +### C0 + +| Mnemonic | Name | Sequence | Short Description | Status | +| -------- | ---- | -------- | ----------------- | ------ | +{{#C0}} +| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{shortDescription}} | {{status}} | +{{/C0}} + + +### C1 + +| Mnemonic | Name | Sequence | Short Description | Status | +| -------- | ---- | -------- | ----------------- | ------ | +{{#C1}} +| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{shortDescription}} | {{status}} | +{{/C1}} + + +### CSI + +| Mnemonic | Name | Sequence | Short Description | Status | +| -------- | ---- | -------- | ----------------- | ------ | +{{#CSI}} +| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{shortDescription}} | {{status}} | +{{/CSI}} + + +### DCS + +| Mnemonic | Name | Sequence | Short Description | Status | +| -------- | ---- | -------- | ----------------- | ------ | +{{#DCS}} +| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{shortDescription}} | {{status}} | +{{/DCS}} + + +### ESC + +| Mnemonic | Name | Sequence | Short Description | Status | +| -------- | ---- | -------- | ----------------- | ------ | +{{#ESC}} +| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{shortDescription}} | {{status}} | +{{/ESC}} + + +### OSC + +| Mnemonic | Name | Sequence | Short Description | Status | +| -------- | ---- | -------- | ----------------- | ------ | +{{#OSC}} +| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{shortDescription}} | {{status}} | +{{/OSC}} +` + +function parseMultiLine(filename, s) { + if (!~s.indexOf('@vt:')) { + return; + } + const lines = s.split('\n').map(el => el.trim().replace(/[*]/, '').replace(/\s/, '')); + let grabLine = false; + const longDescription = []; + let feature; + for (const line of lines) { + if (grabLine) { + if (!line) { + break; + } + longDescription.push(line); + } + if (~line.indexOf('@vt:')) { + feature = parseSingleLine(filename, line); + grabLine = true; + } + } + if (feature) { + feature.longDescription = longDescription; + return feature; + } +} + +function parseSingleLine(filename, s) { + const line = s.trim(); + const match = line.match(REX_VT_LINE); + if (match !== null) { + if (!~TYPES.indexOf(match[2])) { + throw new Error(`unkown vt-command type "${match[2]}" specified in "${filename}"`); + } + return { + status: match[1], + type: match[2], + mnemonic: match[3], + name: match[4], + sequence: match[5], + shortDescription: match[6], + longDescription: [], + source: filename + }; + } +} + +function postProcessData(features) { + const featureTable = {}; + for (const feature of features) { + if (featureTable[feature.type] === undefined) { + featureTable[feature.type] = []; + } + featureTable[feature.type].push(feature); + } + for (const entry in featureTable) { + featureTable[entry].sort((a, b) => a.sequence.slice(-1) > b.sequence.slice(-1)); + } + // console.error(featureTable); + featureTable.version = require('../package.json').version; + console.log(Mustache.render(MARKDOWN_TMPL, featureTable)); +} + +function main(filenames) { + // console.error(filenames); + let leftToProcess = filenames.length; + const features = []; + for (const filename of filenames) { + fs.readFile(filename, 'utf-8', (err, data) => { + let match; + while ((match = REX_COMMENTS.exec(data)) !== null) { + if (match.index === REX_COMMENTS.lastIndex) { + REX_COMMENTS.lastIndex++; + } + const feature = match[1] + ? parseMultiLine(filename, match[1]) + : parseSingleLine(filename, match[2]); + if (feature) { + features.push(feature); + } + } + leftToProcess--; + if (!leftToProcess) { + postProcessData(features); + } + }); + } +} + +main(process.argv.slice(2)) diff --git a/package.json b/package.json index 68467a89c..5c5bd7890 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,8 @@ "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" + "clean": "rm -rf lib out addons/*/lib addons/*/out", + "vtfeatures": "node bin/extract_vt.js src/**/*.ts src/*.ts" }, "devDependencies": { "@types/chai": "^3.4.34", @@ -41,6 +42,7 @@ "glob": "^7.0.5", "jsdom": "^11.11.0", "mocha": "^6.1.4", + "mustache": "^3.0.1", "node-pty": "0.7.6", "puppeteer": "^1.15.0", "source-map-loader": "^0.2.4", diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 900677728..9b5505855 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -177,13 +177,18 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI handler */ this._parser.setCsiHandler({final: '@'}, params => this.insertChars(params)); + // @vt: supported CSI CUU "Cursor Up" "CSI Ps A" "Move the cursor position `Ps` times to the top (default=1)." this._parser.setCsiHandler({final: 'A'}, params => this.cursorUp(params)); + // @vt: supported CSI CUD "Cursor Down" "CSI Ps B" "Move the cursor position `Ps` times to the bottom (default=1)." this._parser.setCsiHandler({final: 'B'}, params => this.cursorDown(params)); + // @vt: supported CSI CUF "Cursor Forward" "CSI Ps C" "Move the cursor position `Ps` times to the right (default=1)." this._parser.setCsiHandler({final: 'C'}, params => this.cursorForward(params)); + // @vt: supported CSI CUB "Cursor Backward" "CSI Ps D" "Move the cursor position `Ps` times to the left (default=1)." this._parser.setCsiHandler({final: 'D'}, params => this.cursorBackward(params)); this._parser.setCsiHandler({final: 'E'}, params => this.cursorNextLine(params)); this._parser.setCsiHandler({final: 'F'}, params => this.cursorPrecedingLine(params)); this._parser.setCsiHandler({final: 'G'}, params => this.cursorCharAbsolute(params)); + // @vt: supported CSI CUP "Cursor Position" "CSI Ps ; Ps H" "Set the cursor to position [`Ps`, `Ps`]." this._parser.setCsiHandler({final: 'H'}, params => this.cursorPosition(params)); this._parser.setCsiHandler({final: 'I'}, params => this.cursorForwardTab(params)); this._parser.setCsiHandler({final: 'J'}, params => this.eraseInDisplay(params)); @@ -210,6 +215,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._parser.setCsiHandler({prefix: '?', final: 'h'}, params => this.setModePrivate(params)); this._parser.setCsiHandler({final: 'l'}, params => this.resetMode(params)); this._parser.setCsiHandler({prefix: '?', final: 'l'}, params => this.resetModePrivate(params)); + // @vt: partly CSI SGR "Select Graphic Rendition" "CSI Pm m" "Set various text attributes." this._parser.setCsiHandler({final: 'm'}, params => this.charAttributes(params)); this._parser.setCsiHandler({final: 'n'}, params => this.deviceStatus(params)); this._parser.setCsiHandler({prefix: '?', final: 'n'}, params => this.deviceStatusPrivate(params)); @@ -222,11 +228,15 @@ export class InputHandler extends Disposable implements IInputHandler { /** * execute handler */ + // @vt: supported C0 BEL "Bell" "\x07" "Rings the bell." this._parser.setExecuteHandler(C0.BEL, () => this.bell()); + // @vt: supported C0 LF "Line Feed" "\n" "Moves the cursor one row down, scrolling if needed." this._parser.setExecuteHandler(C0.LF, () => this.lineFeed()); this._parser.setExecuteHandler(C0.VT, () => this.lineFeed()); this._parser.setExecuteHandler(C0.FF, () => this.lineFeed()); + // @vt: supported C0 CR "Carriage Return" "\r" "Moves the cursor to the beginning of the row." this._parser.setExecuteHandler(C0.CR, () => this.carriageReturn()); + // @vt: supported C0 BS "Backspace" "\x08" "Moves the cursor one position to the left." this._parser.setExecuteHandler(C0.BS, () => this.backspace()); this._parser.setExecuteHandler(C0.HT, () => this.tab()); this._parser.setExecuteHandler(C0.SO, () => this.shiftOut()); @@ -241,9 +251,12 @@ export class InputHandler extends Disposable implements IInputHandler { * OSC handler */ // 0 - icon name + title + // @vt: partly OSC OSC0 "" "OSC 0 ; Pt BEL" "Set window title and icon name." this._parser.setOscHandler(0, new OscHandler((data: string) => this.setTitle(data))); // 1 - icon name + // @vt: unsupported OSC OSC1 "" "OSC 1 ; Pt BEL" "Set icon name." // 2 - title + // @vt: supported OSC OSC2 "" "OSC 2 ; Pt BEL" "Set window title." this._parser.setOscHandler(2, new OscHandler((data: string) => this.setTitle(data))); // 3 - set property X in the form "prop=value" // 4 - Change Color Number @@ -306,6 +319,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._parser.setEscHandler({intermediates: '.', final: flag}, () => this.selectCharset('.' + flag)); this._parser.setEscHandler({intermediates: '/', final: flag}, () => this.selectCharset('/' + flag)); // TODO: supported? } + // @vt: supported ESC DECALN "Screen Alignment Pattern" "ESC # 8" "Fill viewport with a test pattern (E)." this._parser.setEscHandler({intermediates: '#', final: '8'}, () => this.screenAlignmentPattern()); /** diff --git a/yarn.lock b/yarn.lock index a67ede71d..42ecc4cb3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3133,6 +3133,11 @@ ms@2.1.1, ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== +mustache@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/mustache/-/mustache-3.0.1.tgz#873855f23aa8a95b150fb96d9836edbc5a1d248a" + integrity sha512-jFI/4UVRsRYdUbuDTKT7KzfOp7FiD5WzYmmwNwXyUVypC0xjoTL78Fqc0jHUPIvvGD+6DQSPHIt1NE7D1ArsqA== + mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" From 9cd80a2c6b925e80e54fd05de0a17f577f274042 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 18 Aug 2019 17:52:26 +0200 Subject: [PATCH 009/268] cleanup --- bin/extract_vtfeatures.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bin/extract_vtfeatures.js b/bin/extract_vtfeatures.js index 60f3200e5..fdc7e4bf8 100644 --- a/bin/extract_vtfeatures.js +++ b/bin/extract_vtfeatures.js @@ -13,10 +13,9 @@ const Mustache = require('mustache'); */ const REX_COMMENTS = /^\s*?[/][*][*]([\s\S]*?)[*][/]|^\s*?\/\/ ([@]vt[:].*?)$/mug; -// expected - "@vt: type name "sequence" "short description" /** * regexp to parse the @vt line - * expected data - "@vt: <"name"> "" "" + * expected data - "@vt: "" "" "" */ const REX_VT_LINE = /^[@]vt\:\s*(\w+)\s*(\w+)\s*(\w+)\s*"(.*?)"\s*"(.*?)"\s*"(.*?)".*$/; From f34edc311663e1083487a4158b3d6acb26ea1f91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 18 Aug 2019 17:57:22 +0200 Subject: [PATCH 010/268] rename script --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5c5bd7890..25dd9c881 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "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", - "vtfeatures": "node bin/extract_vt.js src/**/*.ts src/*.ts" + "vtfeatures": "node bin/extract_vtfeatures.js src/**/*.ts src/*.ts" }, "devDependencies": { "@types/chai": "^3.4.34", From 66709e6f22e5d1a42062a92214cca777f86ccfb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 18 Aug 2019 21:50:17 +0200 Subject: [PATCH 011/268] fix OSC list header; general notes --- bin/extract_vtfeatures.js | 75 ++++++++++++++++++++++++++++++++++----- src/InputHandler.ts | 6 ++-- 2 files changed, 70 insertions(+), 11 deletions(-) diff --git a/bin/extract_vtfeatures.js b/bin/extract_vtfeatures.js index fdc7e4bf8..ebebbf063 100644 --- a/bin/extract_vtfeatures.js +++ b/bin/extract_vtfeatures.js @@ -35,6 +35,55 @@ const TYPES = [ const MARKDOWN_TMPL = ` # Supported VT features by xterm.js Version: {{version}} + +### Table of Contents + +- [General notes](#general-notes) +{{#C0.length}} +- [C0](#c0) +{{/C0.length}} +{{#C1.length}} +- [C1](#c1) +{{/C1.length}} +{{#CSI.length}} +- [CSI](#csi) +{{/CSI.length}} +{{#DCS.length}} +- [DCS](#dcs) +{{/DCS.length}} +{{#ESC.length}} +- [ESC](#esc) +{{/ESC.length}} +{{#OSC.length}} +- [OSC](#osc) +{{/OSC.length}} + +### General notes + +This document lists xterm.js' support of typical VT commands. The commands are grouped by their type: + +- C0: single byte command (7bit control characters, byte range \\x00 .. \\x1f) +- C1: single byte command (8bit control characters, byte range \\x80 .. \\x9f) +- ESC: sequence starting with \`ESC\` (\`\\x1b\`) +- CSI - Control Sequence Introducer: sequence starting with \`ESC [\` (7bit) or CSI (\`\\x9b\` 8bit) +- DCS - Device Control String: sequence starting with \`ESC P\` (7bit) or DCS (\`\\x90\` 8bit) +- OSC - Operating System Command: sequence starting with \`ESC ]\` (7bit) or OSC (\`\\x9d\` 8bit) + +Application Program Command (APC), Privacy Message (PM) and Start of String (SOS) are not supported, +any sequence of these types will be ignored. + +Note that the list only contains commands implemented in xterm.js' core codebase. Missing commands are either +not supported or unstable/experimental. Furthermore addons can provide additional commands. + +To denote the sequences the lists use the same abbreviations as xterm does: +- \`Ps\`: A single (usually optional) numeric parameter, composed of one or more decimal digits. +- \`Pm\`: A multiple numeric parameter composed of any number of single numeric parameters, separated by ; character(s), + e.g. \` Ps ; Ps ; ... \`. +- \`Pt\`: A text parameter composed of printable characters. Note that for most commands with \`Pt\` only + ASCII printables are specified to work. Additionally xterm.js will let any character >C1 pass as printable. + + +{{#C0.length}} ### C0 | Mnemonic | Name | Sequence | Short Description | Status | @@ -42,8 +91,9 @@ Version: {{version}} {{#C0}} | {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{shortDescription}} | {{status}} | {{/C0}} +{{/C0.length}} - +{{#C1.length}} ### C1 | Mnemonic | Name | Sequence | Short Description | Status | @@ -51,8 +101,9 @@ Version: {{version}} {{#C1}} | {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{shortDescription}} | {{status}} | {{/C1}} +{{/C1.length}} - +{{#CSI.length}} ### CSI | Mnemonic | Name | Sequence | Short Description | Status | @@ -60,8 +111,9 @@ Version: {{version}} {{#CSI}} | {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{shortDescription}} | {{status}} | {{/CSI}} +{{/CSI.length}} - +{{#DCS.length}} ### DCS | Mnemonic | Name | Sequence | Short Description | Status | @@ -69,8 +121,9 @@ Version: {{version}} {{#DCS}} | {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{shortDescription}} | {{status}} | {{/DCS}} +{{/DCS.length}} - +{{#ESC.length}} ### ESC | Mnemonic | Name | Sequence | Short Description | Status | @@ -78,15 +131,21 @@ Version: {{version}} {{#ESC}} | {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{shortDescription}} | {{status}} | {{/ESC}} +{{/ESC.length}} - +{{#OSC.length}} ### OSC -| Mnemonic | Name | Sequence | Short Description | Status | -| -------- | ---- | -------- | ----------------- | ------ | +| Identifier | Sequence | Short Description | Status | +| ---------- | -------- | ----------------- | ------ | {{#OSC}} -| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{shortDescription}} | {{status}} | +| {{mnemonic}} | \`{{sequence}}\` | {{shortDescription}} | {{status}} | {{/OSC}} +{{/OSC.length}} + +### TODO +- specific notes on several commands (long description) +- references ` function parseMultiLine(filename, s) { diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 9b5505855..90107e24f 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -251,12 +251,12 @@ export class InputHandler extends Disposable implements IInputHandler { * OSC handler */ // 0 - icon name + title - // @vt: partly OSC OSC0 "" "OSC 0 ; Pt BEL" "Set window title and icon name." + // @vt: partly OSC 0 "" "OSC 0 ; Pt BEL" "Set window title and icon name." this._parser.setOscHandler(0, new OscHandler((data: string) => this.setTitle(data))); // 1 - icon name - // @vt: unsupported OSC OSC1 "" "OSC 1 ; Pt BEL" "Set icon name." + // @vt: unsupported OSC 1 "" "OSC 1 ; Pt BEL" "Set icon name." // 2 - title - // @vt: supported OSC OSC2 "" "OSC 2 ; Pt BEL" "Set window title." + // @vt: supported OSC 2 "" "OSC 2 ; Pt BEL" "Set window title." this._parser.setOscHandler(2, new OscHandler((data: string) => this.setTitle(data))); // 3 - set property X in the form "prop=value" // 4 - Change Color Number From 8b2d85c82359c7240cc77186cd91be50fca316f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 19 Aug 2019 22:54:55 +0200 Subject: [PATCH 012/268] document more commands; long description hook --- bin/extract_vtfeatures.js | 104 ++++++++++++++++++++++++-- src/InputHandler.ts | 152 ++++++++++++++++++++++++++++++++++---- 2 files changed, 236 insertions(+), 20 deletions(-) diff --git a/bin/extract_vtfeatures.js b/bin/extract_vtfeatures.js index ebebbf063..2122e19aa 100644 --- a/bin/extract_vtfeatures.js +++ b/bin/extract_vtfeatures.js @@ -89,65 +89,150 @@ To denote the sequences the lists use the same abbreviations as xterm does: | Mnemonic | Name | Sequence | Short Description | Status | | -------- | ---- | -------- | ----------------- | ------ | {{#C0}} -| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{shortDescription}} | {{status}} | +| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}})_{{/longDescription.length}} | {{status}} | {{/C0}} + +{{#C0.hasLongDescriptions}} +{{#C0}} +{{#longDescription.length}} +#### {{name}} +{{#longDescription}} +{{{.}}} +{{/longDescription}} +{{/longDescription.length}} +{{/C0}} +{{/C0.hasLongDescriptions}} + {{/C0.length}} + {{#C1.length}} ### C1 | Mnemonic | Name | Sequence | Short Description | Status | | -------- | ---- | -------- | ----------------- | ------ | {{#C1}} -| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{shortDescription}} | {{status}} | +| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}})_{{/longDescription.length}} | {{status}} | {{/C1}} + +{{#C1.hasLongDescriptions}} +{{#C1}} +{{#longDescription.length}} +#### {{name}} +{{#longDescription}} +{{{.}}} +{{/longDescription}} +{{/longDescription.length}} +{{/C1}} +{{/C1.hasLongDescriptions}} + {{/C1.length}} + {{#CSI.length}} ### CSI | Mnemonic | Name | Sequence | Short Description | Status | | -------- | ---- | -------- | ----------------- | ------ | {{#CSI}} -| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{shortDescription}} | {{status}} | +| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}})_{{/longDescription.length}} | {{status}} | {{/CSI}} + +{{#CSI.hasLongDescriptions}} +{{#CSI}} +{{#longDescription.length}} +#### {{name}} +{{#longDescription}} +{{{.}}} +{{/longDescription}} +{{/longDescription.length}} +{{/CSI}} +{{/CSI.hasLongDescriptions}} + {{/CSI.length}} + {{#DCS.length}} ### DCS | Mnemonic | Name | Sequence | Short Description | Status | | -------- | ---- | -------- | ----------------- | ------ | {{#DCS}} -| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{shortDescription}} | {{status}} | +| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}})_{{/longDescription.length}} | {{status}} | {{/DCS}} + +{{#DCS.hasLongDescriptions}} +{{#DCS}} +{{#longDescription.length}} +#### {{name}} +{{#longDescription}} +{{{.}}} +{{/longDescription}} +{{/longDescription.length}} +{{/DCS}} +{{/DCS.hasLongDescriptions}} + {{/DCS.length}} + {{#ESC.length}} ### ESC | Mnemonic | Name | Sequence | Short Description | Status | | -------- | ---- | -------- | ----------------- | ------ | {{#ESC}} -| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{shortDescription}} | {{status}} | +| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}})_{{/longDescription.length}} | {{status}} | {{/ESC}} + +{{#ESC.hasLongDescriptions}} +{{#ESC}} +{{#longDescription.length}} +#### {{name}} +{{#longDescription}} +{{{.}}} +{{/longDescription}} +{{/longDescription.length}} +{{/ESC}} +{{/ESC.hasLongDescriptions}} + {{/ESC.length}} + {{#OSC.length}} ### OSC | Identifier | Sequence | Short Description | Status | | ---------- | -------- | ----------------- | ------ | {{#OSC}} -| {{mnemonic}} | \`{{sequence}}\` | {{shortDescription}} | {{status}} | +| {{mnemonic}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}})_{{/longDescription.length}} | {{status}} | {{/OSC}} + +{{#OSC.hasLongDescriptions}} +{{#OSC}} +{{#longDescription.length}} +#### {{name}} +{{#longDescription}} +{{{.}}} +{{/longDescription}} +{{/longDescription.length}} +{{/OSC}} +{{/OSC.hasLongDescriptions}} + {{/OSC.length}} + ### TODO -- specific notes on several commands (long description) +- improve table sorting: + - sort C0/C1 in byte order + - sort OSC in numerical order + - sort CSI/ESC/DCS in final byte order - references ` +function createAnchorSlug(s) { + return s.toLowerCase().split(' ').join('-'); +} + function parseMultiLine(filename, s) { if (!~s.indexOf('@vt:')) { return; @@ -170,6 +255,7 @@ function parseMultiLine(filename, s) { } if (feature) { feature.longDescription = longDescription; + feature.longTarget = createAnchorSlug(feature.name); return feature; } } @@ -189,6 +275,7 @@ function parseSingleLine(filename, s) { sequence: match[5], shortDescription: match[6], longDescription: [], + longTarget: '', source: filename }; } @@ -201,6 +288,9 @@ function postProcessData(features) { featureTable[feature.type] = []; } featureTable[feature.type].push(feature); + if (feature.longDescription) { + featureTable[feature.type].hasLongDescriptions = true; + } } for (const entry in featureTable) { featureTable[entry].sort((a, b) => a.sequence.slice(-1) > b.sequence.slice(-1)); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 90107e24f..c9a8e893f 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -29,6 +29,14 @@ import { DcsHandler } from 'common/parser/DcsParser'; */ const GLEVEL: {[key: string]: number} = {'(': 0, ')': 1, '*': 2, '+': 3, '-': 1, '.': 2}; +/** + * Document common VT features here that are currently unsupported + */ +// @vt: unsupported DCS SIXEL "SIXEL Graphics" "DCS Ps ; Ps ; Ps ; q Pt ST" "Draw SIXEL image starting at cursor position." +// @vt: unsupported CSI SL "Scroll Left" "CSI Ps SP @" "Scroll viewport `Ps` times to the left." +// @vt: unsupported CSI SR "Scroll Right" "CSI Ps SP A" "Scroll viewport `Ps` times to the right." +// @vt: unsupported CSI DECIC "Insert Columns" "CSI Ps ' }" "Insert `Ps` columns at cursor position." +// @vt: unsupported CSI DECDC "Delete Columns" "CSI Ps ' ~" "Delete `Ps` columns at cursor position." /** * DCS subparser implementations @@ -39,6 +47,12 @@ const GLEVEL: {[key: string]: number} = {'(': 0, ')': 1, '*': 2, '+': 3, '-': 1, * DECRQSS (https://vt100.net/docs/vt510-rm/DECRQSS.html) * Request Status String (DECRQSS), VT420 and up. * Response: DECRPSS (https://vt100.net/docs/vt510-rm/DECRPSS.html) + * + * @vt: partly DCS DECRQSS "Request Selection or Setting" "DCS $ q Pt ST" "Request several the terminal settings." + * Supported: + * - Graphic Rendition (SGR): `DCS $ q m ST` (always reporting 0m) + * - Top and Bottom Margins (DECSTBM): `DCS $ q m ST` + * - Cursor Style (DECSCUSR): `DCS $ q SP q ST` */ class DECRQSS implements IDcsHandler { private _data: Uint32Array = new Uint32Array(0); @@ -177,86 +191,192 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI handler */ this._parser.setCsiHandler({final: '@'}, params => this.insertChars(params)); - // @vt: supported CSI CUU "Cursor Up" "CSI Ps A" "Move the cursor position `Ps` times to the top (default=1)." + // @vt: supported CSI CUU "Cursor Up" "CSI Ps A" "Move cursor `Ps` times up (default=1)." this._parser.setCsiHandler({final: 'A'}, params => this.cursorUp(params)); - // @vt: supported CSI CUD "Cursor Down" "CSI Ps B" "Move the cursor position `Ps` times to the bottom (default=1)." + // @vt: supported CSI CUD "Cursor Down" "CSI Ps B" "Move cursor `Ps` times down (default=1)." this._parser.setCsiHandler({final: 'B'}, params => this.cursorDown(params)); - // @vt: supported CSI CUF "Cursor Forward" "CSI Ps C" "Move the cursor position `Ps` times to the right (default=1)." + // @vt: supported CSI CUF "Cursor Forward" "CSI Ps C" "Move cursor `Ps` times forward (default=1)." this._parser.setCsiHandler({final: 'C'}, params => this.cursorForward(params)); - // @vt: supported CSI CUB "Cursor Backward" "CSI Ps D" "Move the cursor position `Ps` times to the left (default=1)." + // @vt: supported CSI CUB "Cursor Backward" "CSI Ps D" "Move cursor `Ps` times backward (default=1)." this._parser.setCsiHandler({final: 'D'}, params => this.cursorBackward(params)); + // @vt: supported CSI CNL "Cursor Next Line" "CSI Ps E" "Move cursor `Ps` times down (default=1) and to the first column." this._parser.setCsiHandler({final: 'E'}, params => this.cursorNextLine(params)); + // @vt: supported CSI CPL "Cursor Backward" "CSI Ps F" "Move cursor `Ps` times up (default=1) and to the first column." this._parser.setCsiHandler({final: 'F'}, params => this.cursorPrecedingLine(params)); + // @vt: supported CSI CHA "Cursor Horizontal Absolute" "CSI Ps G" "Move cursor to `Ps`-th column of the active row (default=1)." this._parser.setCsiHandler({final: 'G'}, params => this.cursorCharAbsolute(params)); - // @vt: supported CSI CUP "Cursor Position" "CSI Ps ; Ps H" "Set the cursor to position [`Ps`, `Ps`]." + // @vt: supported CSI CUP "Cursor Position" "CSI Ps ; Ps H" "Set cursor to position [`Ps`, `Ps`]." this._parser.setCsiHandler({final: 'H'}, params => this.cursorPosition(params)); + // @vt: supported CSI CHT "Cursor Horizontal Tabulation" "CSI Ps I" "Move cursor `Ps` times tabs forward (default=1)." this._parser.setCsiHandler({final: 'I'}, params => this.cursorForwardTab(params)); + /** + * @vt: supported CSI ED "Erase In Display" "CSI Ps J" "Erase various parts of the viewport." + * TODO: document different modes... + */ this._parser.setCsiHandler({final: 'J'}, params => this.eraseInDisplay(params)); + // @vt: partly CSI DECSED "Selective Erase In Display" "CSI ? Ps J" "Currently the same as ED." this._parser.setCsiHandler({prefix: '?', final: 'J'}, params => this.eraseInDisplay(params)); + /** + * @vt: supported CSI EL "Erase In Line" "CSI Ps K" "Erase various parts of the active row." + * TODO: document different modes... + */ this._parser.setCsiHandler({final: 'K'}, params => this.eraseInLine(params)); + // @vt: partly CSI DECSEL "Selective Erase In Line" "CSI ? Ps K" "Currently the same as EL." this._parser.setCsiHandler({prefix: '?', final: 'K'}, params => this.eraseInLine(params)); + // @vt: supported CSI IL "Insert Line" "CSI Ps L" "Insert `Ps` blank lines at active row (default=1)." this._parser.setCsiHandler({final: 'L'}, params => this.insertLines(params)); + // @vt: supported CSI DL "Delete Line" "CSI Ps M" "Delete `Ps` lines at active row (default=1)." this._parser.setCsiHandler({final: 'M'}, params => this.deleteLines(params)); + // @vt: supported CSI DCH "Delete Character" "CSI Ps P" "Delete `Ps` characters in the active row (default=1)." this._parser.setCsiHandler({final: 'P'}, params => this.deleteChars(params)); + // @vt: supported CSI SU "Scroll Up" "CSI Ps S" "Scroll `Ps` lines up (default=1)." this._parser.setCsiHandler({final: 'S'}, params => this.scrollUp(params)); + // @vt: supported CSI SD "Scroll Down" "CSI Ps T" "Scroll `Ps` lines down (default=1)." this._parser.setCsiHandler({final: 'T'}, params => this.scrollDown(params)); + // @vt: supported CSI ECH "Erase Character" "CSI Ps X" "Erase `Ps` characters from current cursor position to hte right (default=1)." this._parser.setCsiHandler({final: 'X'}, params => this.eraseChars(params)); + //@vt: supported CSI CBT "Cursor Backward Tabulation" "CSI Ps Z" "Move cursor `Ps` tabs backward (default=1)." this._parser.setCsiHandler({final: 'Z'}, params => this.cursorBackwardTab(params)); + // @vt: supported CSI HPA "Horizontal Position Absolute" "CSI Ps `" "Same as CHA." this._parser.setCsiHandler({final: '`'}, params => this.charPosAbsolute(params)); + // @vt: supported CSI HPR "Horizontal Position Relative" "CSI Ps a" "Same as CUF." this._parser.setCsiHandler({final: 'a'}, params => this.hPositionRelative(params)); + /** + * @vt: supported CSI REP "Repeat Preceding Character" "CSI Ps b" "Repeat preceding character `Ps` times (default=1)." + * Has no effect if the sequence does not follow a printed character (NOOP for any other sequence in between). + * TODO: document character limitations due to xterm compliance + */ this._parser.setCsiHandler({final: 'b'}, params => this.repeatPrecedingCharacter(params)); + /** + * @vt: supported CSI DA1 "Primary Device Attributes" "CSI c" "Send primary device attributes." + * TODO: Describe response... + */ this._parser.setCsiHandler({final: 'c'}, params => this.sendDeviceAttributesPrimary(params)); + /** + * @vt: supported CSI DA2 "Secondary Device Attributes" "CSI > c" "Send primary device attributes." + * TODO: Describe response... + */ this._parser.setCsiHandler({prefix: '>', final: 'c'}, params => this.sendDeviceAttributesSecondary(params)); + // @vt: supported CSI VPA "Vertical Position Absolute" "CSI Ps d" "Move cursor to `Ps`-th row (default=1)." this._parser.setCsiHandler({final: 'd'}, params => this.linePosAbsolute(params)); + // @vt: supported CSI VPR "Vertical Position Relative" "CSI Ps e" "Move cursor `Ps` times down (default=1)." this._parser.setCsiHandler({final: 'e'}, params => this.vPositionRelative(params)); + // @vt: supported CSI HVP "Horizontal and Vertical Position" "CSI Ps ; Ps f" "Same as CUP." this._parser.setCsiHandler({final: 'f'}, params => this.hVPosition(params)); + // @vt: supported CSI TBC "Tab Clear" "CSI Ps g" "Clear tab stops at current position (0) or all (3) (default=0)." this._parser.setCsiHandler({final: 'g'}, params => this.tabClear(params)); + /** + * @vt: partly CSI SM "Set Mode" "CSI Pm h" "Set various terminal attributes." + * TODO: Describe all supported attributes. + */ this._parser.setCsiHandler({final: 'h'}, params => this.setMode(params)); + /** + * @vt: partly CSI DECSET "DEC Private Set Mode" "CSI ? Pm h" "Set various terminal attributes." + * TODO: Describe all supported attributes. + */ this._parser.setCsiHandler({prefix: '?', final: 'h'}, params => this.setModePrivate(params)); + /** + * @vt: partly CSI RM "Reset Mode" "CSI Pm l" "Set various terminal attributes." + * TODO: Describe all supported attributes. + */ this._parser.setCsiHandler({final: 'l'}, params => this.resetMode(params)); + /** + * @vt: partly CSI DECRST "DEC Private Reset Mode" "CSI ? Pm l" "Reset various terminal attributes." + * TODO: Describe all supported attributes. + */ this._parser.setCsiHandler({prefix: '?', final: 'l'}, params => this.resetModePrivate(params)); - // @vt: partly CSI SGR "Select Graphic Rendition" "CSI Pm m" "Set various text attributes." + /** + * @vt: partly CSI SGR "Select Graphic Rendition" "CSI Pm m" "Reset various text attributes." + * Detailed description goes here... + */ this._parser.setCsiHandler({final: 'm'}, params => this.charAttributes(params)); + // @vt: supported CSI DSR "Device Status Report" "CSI Ps n" "Request cursor position (CPR) with `Ps` = 6." this._parser.setCsiHandler({final: 'n'}, params => this.deviceStatus(params)); + // @vt: partly CSI DECDSR "DEC Device Status Report" "CSI ? Ps n" "Only CPR is supported (same as DSR)." this._parser.setCsiHandler({prefix: '?', final: 'n'}, params => this.deviceStatusPrivate(params)); + /** + * @vt: supported CSI DECSTR "Soft Terminal Reset" "CSI ! p" "Reset several terminal attributes to initial state." + * There are two terminal reset sequences - RIS and DECSTR. While RIS performs almost a full terminal bootstrap, + * DECSTR only resets certain attributes. For most needs DECSTR should be sufficient. + * Attributes reset to default values: + * - TODO: list attributes here ... + */ this._parser.setCsiHandler({intermediates: '!', final: 'p'}, params => this.softReset(params)); + /** + * @vt: supported CSI DECSCUSR "Set Cursor Style" "CSI Ps SP q" "Set cursor style." + * Supported cursor styles (note that most renderers dont implement the blink feature, + * thus will show the steady variant instead): + * - empty, 0 or 1: steady block + * - 2: blink block + * - 3: steady underline + * - 4: blink underline + * - 5: steady bar + * - 6: blink bar + */ this._parser.setCsiHandler({intermediates: ' ', final: 'q'}, params => this.setCursorStyle(params)); + /** + * @vt: supported CSI DECSTBM "Set Top and Bottom Margin" "CSI Ps ; Ps r" "Set top and bottom margins of the viewport [top;bottom] (default = viewport size)." + * TODO: document specialties like dependent cursor commands and scrolling... + */ this._parser.setCsiHandler({final: 'r'}, params => this.setScrollRegion(params)); + // @vt: partly CSI SCOSC "Save Cursor" "CSI s" "Save cursor position, charmap and text attributes." this._parser.setCsiHandler({final: 's'}, params => this.saveCursor(params)); + // @vt: partly CSI SCORC "Restore Cursor" "CSI u" "Restore cursor position, charmap and text attributes." this._parser.setCsiHandler({final: 'u'}, params => this.restoreCursor(params)); /** * execute handler */ - // @vt: supported C0 BEL "Bell" "\x07" "Rings the bell." + /** + * @vt: supported C0 BEL "Bell" "\a" "Ring the bell." + * The behavior of the bell is further customizable with `ITerminalOptions.bellStyle` + * and `ITerminalOptions.bellSound`. + */ this._parser.setExecuteHandler(C0.BEL, () => this.bell()); - // @vt: supported C0 LF "Line Feed" "\n" "Moves the cursor one row down, scrolling if needed." + // @vt: supported C0 LF "Line Feed" "\n" "Move the cursor one row down, scrolling if needed." this._parser.setExecuteHandler(C0.LF, () => this.lineFeed()); + // @vt: supported C0 VT "Vertical Tabulation" "\v" "Treated as LF." this._parser.setExecuteHandler(C0.VT, () => this.lineFeed()); + // @vt: supported C0 FF "Form Feed" "\f" "Treated as LF." this._parser.setExecuteHandler(C0.FF, () => this.lineFeed()); - // @vt: supported C0 CR "Carriage Return" "\r" "Moves the cursor to the beginning of the row." + // @vt: supported C0 CR "Carriage Return" "\r" "Move the cursor to the beginning of the row." this._parser.setExecuteHandler(C0.CR, () => this.carriageReturn()); - // @vt: supported C0 BS "Backspace" "\x08" "Moves the cursor one position to the left." + // @vt: supported C0 BS "Backspace" "\b" "Move the cursor one position to the left." this._parser.setExecuteHandler(C0.BS, () => this.backspace()); + // @vt: supported C0 HT "Horizontal Tabulation" "\t" "Move the cursor to the next character tab stop." this._parser.setExecuteHandler(C0.HT, () => this.tab()); + /** + * @vt: partly C0 SO "Shift Out" "\x0e" "Switch to an alternative character set." + * TODO: document supported native character sets and support limitations ... + */ this._parser.setExecuteHandler(C0.SO, () => this.shiftOut()); + // @vt: supported C0 SI "Shift In" "\x0f" "Return to regular character set after Shift Out." this._parser.setExecuteHandler(C0.SI, () => this.shiftIn()); // FIXME: What do to with missing? Old code just added those to print. + // @vt: supported C1 IND "Index" "\x84" "Move the cursor one line down scrolling if needed." this._parser.setExecuteHandler(C1.IND, () => this.index()); + // @vt: supported C1 NEL "Next Line" "\x85" "Move the cursor to the beginning of the next row." this._parser.setExecuteHandler(C1.NEL, () => this.nextLine()); + // @vt: supported C1 HTS "Horizontal Tabulation Set" "\x88" "Places a tab stop at the current cursor position." this._parser.setExecuteHandler(C1.HTS, () => this.tabSet()); /** * OSC handler */ // 0 - icon name + title - // @vt: partly OSC 0 "" "OSC 0 ; Pt BEL" "Set window title and icon name." + /** + * @vt: partly OSC 0 "Set Windows Title and Icon Name" "OSC 0 ; Pt BEL" "Set window title and icon name." + * Icon name is not supported. For Window Title see below. + */ this._parser.setOscHandler(0, new OscHandler((data: string) => this.setTitle(data))); // 1 - icon name - // @vt: unsupported OSC 1 "" "OSC 1 ; Pt BEL" "Set icon name." + // @vt: unsupported OSC 1 "Set Icon Name" "OSC 1 ; Pt BEL" "Set icon name." // 2 - title - // @vt: supported OSC 2 "" "OSC 2 ; Pt BEL" "Set window title." + /** + * @vt: supported OSC 2 "Set Windows Title" "OSC 2 ; Pt BEL" "Set window title." + * xterm.js does not manipulate the title directly, instead exposes changes via the event `Terminal.onTitleChange`. + */ this._parser.setOscHandler(2, new OscHandler((data: string) => this.setTitle(data))); // 3 - set property X in the form "prop=value" // 4 - Change Color Number @@ -294,11 +414,17 @@ export class InputHandler extends Disposable implements IInputHandler { /** * ESC handlers */ + // @vt: supported ESC SC "Save Cursor" "ESC 7" "Save cursor position, charmap and text attributes." this._parser.setEscHandler({final: '7'}, () => this.saveCursor()); + // @vt: supported ESC RC "Restore Cursor" "ESC 8" "Restore cursor position, charmap and text attributes." this._parser.setEscHandler({final: '8'}, () => this.restoreCursor()); + // @vt: supported ESC IND "Index" "ESC D" "Move the cursor one line down scrolling if needed." this._parser.setEscHandler({final: 'D'}, () => this.index()); + // @vt: supported ESC NEL "Next Line" "ESC E" "Move the cursor to the beginning of the next row." this._parser.setEscHandler({final: 'E'}, () => this.nextLine()); + // @vt: supported ESC HTS "Horizontal Tabulation Set" "ESC H" "Places a tab stop at the current cursor position." this._parser.setEscHandler({final: 'H'}, () => this.tabSet()); + // @vt: supported ESC IR "Reverse Index" "ESC M" "Move the cursor one line up scrolling if needed." this._parser.setEscHandler({final: 'M'}, () => this.reverseIndex()); this._parser.setEscHandler({final: '='}, () => this.keypadApplicationMode()); this._parser.setEscHandler({final: '>'}, () => this.keypadNumericMode()); From aacc9c86b9a7c4d98ade70352fd669ac23852bc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 19 Aug 2019 22:59:14 +0200 Subject: [PATCH 013/268] linter --- src/InputHandler.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index c9a8e893f..6c584b4e5 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -47,7 +47,7 @@ const GLEVEL: {[key: string]: number} = {'(': 0, ')': 1, '*': 2, '+': 3, '-': 1, * DECRQSS (https://vt100.net/docs/vt510-rm/DECRQSS.html) * Request Status String (DECRQSS), VT420 and up. * Response: DECRPSS (https://vt100.net/docs/vt510-rm/DECRPSS.html) - * + * * @vt: partly DCS DECRQSS "Request Selection or Setting" "DCS $ q Pt ST" "Request several the terminal settings." * Supported: * - Graphic Rendition (SGR): `DCS $ q m ST` (always reporting 0m) @@ -235,7 +235,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._parser.setCsiHandler({final: 'T'}, params => this.scrollDown(params)); // @vt: supported CSI ECH "Erase Character" "CSI Ps X" "Erase `Ps` characters from current cursor position to hte right (default=1)." this._parser.setCsiHandler({final: 'X'}, params => this.eraseChars(params)); - //@vt: supported CSI CBT "Cursor Backward Tabulation" "CSI Ps Z" "Move cursor `Ps` tabs backward (default=1)." + // @vt: supported CSI CBT "Cursor Backward Tabulation" "CSI Ps Z" "Move cursor `Ps` tabs backward (default=1)." this._parser.setCsiHandler({final: 'Z'}, params => this.cursorBackwardTab(params)); // @vt: supported CSI HPA "Horizontal Position Absolute" "CSI Ps `" "Same as CHA." this._parser.setCsiHandler({final: '`'}, params => this.charPosAbsolute(params)); From b3750bed2d209713a45e656c0c1cb4db3e60fb8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 24 Aug 2019 11:47:46 +0200 Subject: [PATCH 014/268] early hack --- src/InputHandler.ts | 97 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 900677728..b9f5aee59 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -217,6 +217,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._parser.setCsiHandler({intermediates: ' ', final: 'q'}, params => this.setCursorStyle(params)); this._parser.setCsiHandler({final: 'r'}, params => this.setScrollRegion(params)); this._parser.setCsiHandler({final: 's'}, params => this.saveCursor(params)); + this._parser.setCsiHandler({final: 't'}, params => this.manipulateWindowOptions(params)); this._parser.setCsiHandler({final: 'u'}, params => this.restoreCursor(params)); /** @@ -1951,6 +1952,102 @@ export class InputHandler extends Disposable implements IInputHandler { } } + /** + * CSI Ps ; Ps ; Ps t - Various window manipulations and reports (xterm) + * Ps = 1 -> De-iconify window. not supported + * Ps = 2 -> Iconify window. not supported + * Ps = 3 ; x ; y -> Move window to [x, y]. not supported + * Ps = 4 ; height ; width TBD + * Resize the xterm window to given height and width in pixels. + * Omitted parameters reuse the current height or width. + * Zero parameters use the display's height or width. + * Ps = 5 not supported + * Raise the xterm window to the front of the stacking order. + * Ps = 6 not supported + * Lower the xterm window to the bottom of the stacking order. + * Ps = 7 TBD + * Refresh the xterm window. + * Ps = 8 ; height ; width TBD + * Resize the text area to given height and width in characters. + * Omitted parameters reuse the current height or width. + * Zero parameters use the display's height or width. + * Ps = 9 ; 0 -> Restore maximized window. TBD + * Ps = 9 ; 1 TBD + * Maximize window (i.e., resize to screen size). + * Ps = 9 ; 2 -> Maximize window vertically. TBD + * Ps = 9 ; 3 -> Maximize window horizontally. TBD + * Ps = 10 ; 0 -> Undo full-screen mode. TBD + * Ps = 10 ; 1 -> Change to full-screen. TBD + * Ps = 10 ; 2 -> Toggle full-screen. TBD + * Ps = 11 not supported (always report non-iconified?) + * Report xterm window state. + * If the xterm window is non-iconified, it returns CSI 1 t . + * If the xterm window is iconified, it returns CSI 2 t . + * Ps = 13 -> Report xterm window position. TBD + * Note: X Toolkit positions can be negative, but the reported + * values are unsigned, in the range 0-65535. Negative values + * correspond to 32768-65535. + * Result is CSI 3 ; x ; y t + * Ps = 13 ; 2 TBD + * Report xterm text-area position. Result is CSI 3 ; x ; y t + * Ps = 1 4 TBD + * Report xterm text area size in pixels. Result is CSI 4 ; height ; width t + * Ps = 14 ; 2 TBD + * Report xterm window size in pixels. + * Normally xterm's window is larger than its text area, since it + * includes the frame (or decoration) applied by the window manager, + * as well as the area used by a scroll-bar. Result is CSI 4 ; height ; width t + * Ps = 15 TBD + * Report size of the screen in pixels. Result is CSI 5 ; height ; width t + * Ps = 16 TBD + * Report xterm character cell size in pixels. Result is CSI 6 ; height ; width t + * Ps = 18 TBD + * Report the size of the text area in characters. Result is CSI 8 ; height ; width t + * Ps = 19 TBD + * Report the size of the screen in characters. Result is CSI 9 ; height ; width t + * Ps = 20 TBD + * Report xterm window's icon label. Result is OSC L label ST + * Ps = 21 TBD + * Report xterm window's title. Result is OSC l label ST + * Ps = 22 ; 0 -> Save xterm icon and window title on stack. TBD + * Ps = 22 ; 1 -> Save xterm icon title on stack. TBD + * Ps = 22 ; 2 -> Save xterm window title on stack. TBD + * Ps = 23 ; 0 -> Restore xterm icon and window title from stack. TBD + * Ps = 23 ; 1 -> Restore xterm icon title from stack. TBD + * Ps = 23 ; 2 -> Restore xterm window title from stack. TBD + * Ps >= 24 TBD + * Resize to Ps lines (DECSLPP), VT340 and VT420. xterm adapts this by resizing its window. + */ + public manipulateWindowOptions(params?: IParams): void { + console.log(params); + switch (params.params[0]) { + case 8: + if (params.length === 3) { + // TODO: 0/1 support + const cols = Math.max(2, params.params[2]); + const rows = Math.max(2, params.params[1]); + // make it work with demo thus shamelessly taken from demo's client.ts + const width = (cols * (this._terminal as any)._renderService.dimensions.actualCellWidth + this._terminal.viewport.scrollBarWidth).toString() + 'px'; + const height = (rows * (this._terminal as any)._renderService.dimensions.actualCellHeight).toString() + 'px'; + (this._terminal as any)._parent.style.width = width; + (this._terminal as any)._parent.style.height = height; + const term = Function('return this.term;')(); + // move fit into browser codebase? + for (const addon of (term as any)._addonManager._addons) { + if (`${addon.instance.constructor}`.indexOf('FitAddon') !== -1) { + addon.instance.fit(); + } + } + } + break; + case 18: + const height = this._bufferService.rows; + const width = this._bufferService.cols; + this._coreService.triggerDataEvent(`${C0.ESC}[8;${height};${width}t`); + break; + } + } + /** * CSI s From da3ae79c37932271e322f3c7c4b125b07cb06ec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 29 Oct 2019 13:57:36 +0100 Subject: [PATCH 015/268] implement 14, 16, 18, 20, 21, 22, 23; restrict DECCOLM to 24 --- demo/client.ts | 5 +- src/InputHandler.test.ts | 147 +++++++++++++++++ src/InputHandler.ts | 227 +++++++++++++++----------- src/Terminal.ts | 6 +- src/TestUtils.test.ts | 1 - src/Types.d.ts | 1 - src/common/services/OptionsService.ts | 48 +++++- src/common/services/Services.ts | 2 + 8 files changed, 331 insertions(+), 106 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 7942cffe5..2041c0487 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -227,7 +227,8 @@ function initOptions(term: TerminalType): void { fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], logLevel: ['debug', 'info', 'warn', 'error', 'off'], rendererType: ['dom', 'canvas'], - wordSeparator: null + wordSeparator: null, + allowedWindowOps: '' }; const options = Object.keys((term)._core.options); const booleanOptions = []; @@ -258,7 +259,7 @@ function initOptions(term: TerminalType): void { }); html += '
'; Object.keys(stringOptions).forEach(o => { - if (stringOptions[o]) { + if (stringOptions[o] && typeof stringOptions[o] !== 'string') { html += `
`; } else { html += `
`; diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index d8defa1cc..21d6d7427 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -1266,4 +1266,151 @@ describe('InputHandler', () => { [131072, 131072], [131072, 131072], [131072, 300000 - 131072 - 131072] ]); }); + describe('windowOps', () => { + it('all should be disabled by default and not report', () => { + const term = new TestTerminal({cols: 10, rows: 10}); + assert.deepEqual(term.options.allowedWindowOps, []); + const stack: string[] = []; + term.onData(data => stack.push(data)); + term.writeSync('\x1b[14t'); + term.writeSync('\x1b[16t'); + term.writeSync('\x1b[18t'); + term.writeSync('\x1b[20t'); + term.writeSync('\x1b[21t'); + assert.deepEqual(stack, []); + }); + it('14 - GetWinSizePixels', () => { + const term = new TestTerminal({cols: 10, rows: 10, allowedWindowOps: [14]}); + assert.deepEqual(term.options.allowedWindowOps, [14]); + const stack: string[] = []; + term.onData(data => stack.push(data)); + term.writeSync('\x1b[14t'); + // does not report in test terminal due to missing renderer + assert.deepEqual(stack, []); + }); + it('16 - GetCellSizePixels', () => { + const term = new TestTerminal({cols: 10, rows: 10, allowedWindowOps: [16]}); + assert.deepEqual(term.options.allowedWindowOps, [16]); + const stack: string[] = []; + term.onData(data => stack.push(data)); + term.writeSync('\x1b[16t'); + // does not report in test terminal due to missing renderer + assert.deepEqual(stack, []); + }); + it('18 - GetWinSizeChars', () => { + const term = new TestTerminal({cols: 10, rows: 10, allowedWindowOps: [18]}); + assert.deepEqual(term.options.allowedWindowOps, [18]); + const stack: string[] = []; + term.onData(data => stack.push(data)); + term.writeSync('\x1b[18t'); + assert.deepEqual(stack, ['\x1b[8;10;10t']); + term.resize(50, 20); + term.writeSync('\x1b[18t'); + assert.deepEqual(stack, ['\x1b[8;10;10t', '\x1b[8;20;50t']); + }); + it('20 - GetIconTitle', () => { + const term = new TestTerminal({cols: 10, rows: 10, allowedWindowOps: [20]}); + assert.deepEqual(term.options.allowedWindowOps, [20]); + const stack: string[] = []; + term.onData(data => stack.push(data)); + term.writeSync('\x1b]1;hello world!\x07'); + term.writeSync('\x1b[20t'); + assert.deepEqual(stack, ['\x1b]Lhello world!\x1b\\']); + term.writeSync('\x1b]1;some other\x07'); + term.writeSync('\x1b[20t'); + assert.deepEqual(stack, ['\x1b]Lhello world!\x1b\\', '\x1b]Lsome other\x1b\\']); + }); + it('21 - GetWinTitle', () => { + const term = new TestTerminal({cols: 10, rows: 10, allowedWindowOps: [21]}); + assert.deepEqual(term.options.allowedWindowOps, [21]); + const stack: string[] = []; + term.onData(data => stack.push(data)); + term.writeSync('\x1b]2;hello world!\x07'); + term.writeSync('\x1b[21t'); + assert.deepEqual(stack, ['\x1b]lhello world!\x1b\\']); + term.writeSync('\x1b]2;some other\x07'); + term.writeSync('\x1b[21t'); + assert.deepEqual(stack, ['\x1b]lhello world!\x1b\\', '\x1b]lsome other\x1b\\']); + }); + it('22/23 - PushTitle/PopTitle', () => { + const term = new TestTerminal({cols: 10, rows: 10, allowedWindowOps: [22, 23]}); + assert.deepEqual(term.options.allowedWindowOps, [22, 23]); + const stack: string[] = []; + term.onTitleChange(data => stack.push(data)); + term.writeSync('\x1b]0;1\x07'); + term.writeSync('\x1b[22t'); + term.writeSync('\x1b]0;2\x07'); + term.writeSync('\x1b[22t'); + term.writeSync('\x1b]0;3\x07'); + term.writeSync('\x1b[22t'); + assert.deepEqual((term as any)._inputHandler._windowTitleStack, ['1', '2', '3']); + assert.deepEqual((term as any)._inputHandler._iconNameStack, ['1', '2', '3']); + assert.deepEqual(stack, ['1', '2', '3']); + term.writeSync('\x1b[23t'); + term.writeSync('\x1b[23t'); + term.writeSync('\x1b[23t'); + term.writeSync('\x1b[23t'); // one more to test "overflow" + assert.deepEqual((term as any)._inputHandler._windowTitleStack, []); + assert.deepEqual((term as any)._inputHandler._iconNameStack, []); + assert.deepEqual(stack, ['1', '2', '3', '3', '2', '1']); + }); + it('22/23 - PushTitle/PopTitle with ;1', () => { + const term = new TestTerminal({cols: 10, rows: 10, allowedWindowOps: [22, 23]}); + assert.deepEqual(term.options.allowedWindowOps, [22, 23]); + const stack: string[] = []; + term.onTitleChange(data => stack.push(data)); + term.writeSync('\x1b]0;1\x07'); + term.writeSync('\x1b[22;1t'); + term.writeSync('\x1b]0;2\x07'); + term.writeSync('\x1b[22;1t'); + term.writeSync('\x1b]0;3\x07'); + term.writeSync('\x1b[22;1t'); + assert.deepEqual((term as any)._inputHandler._windowTitleStack, []); + assert.deepEqual((term as any)._inputHandler._iconNameStack, ['1', '2', '3']); + assert.deepEqual(stack, ['1', '2', '3']); + term.writeSync('\x1b[23;1t'); + term.writeSync('\x1b[23;1t'); + term.writeSync('\x1b[23;1t'); + term.writeSync('\x1b[23;1t'); // one more to test "overflow" + assert.deepEqual((term as any)._inputHandler._windowTitleStack, []); + assert.deepEqual((term as any)._inputHandler._iconNameStack, []); + assert.deepEqual(stack, ['1', '2', '3']); + }); + it('22/23 - PushTitle/PopTitle with ;2', () => { + const term = new TestTerminal({cols: 10, rows: 10, allowedWindowOps: [22, 23]}); + assert.deepEqual(term.options.allowedWindowOps, [22, 23]); + const stack: string[] = []; + term.onTitleChange(data => stack.push(data)); + term.writeSync('\x1b]0;1\x07'); + term.writeSync('\x1b[22;2t'); + term.writeSync('\x1b]0;2\x07'); + term.writeSync('\x1b[22;2t'); + term.writeSync('\x1b]0;3\x07'); + term.writeSync('\x1b[22;2t'); + assert.deepEqual((term as any)._inputHandler._windowTitleStack, ['1', '2', '3']); + assert.deepEqual((term as any)._inputHandler._iconNameStack, []); + assert.deepEqual(stack, ['1', '2', '3']); + term.writeSync('\x1b[23;2t'); + term.writeSync('\x1b[23;2t'); + term.writeSync('\x1b[23;2t'); + term.writeSync('\x1b[23;2t'); // one more to test "overflow" + assert.deepEqual((term as any)._inputHandler._windowTitleStack, []); + assert.deepEqual((term as any)._inputHandler._iconNameStack, []); + assert.deepEqual(stack, ['1', '2', '3', '3', '2', '1']); + }); + it('DECCOLM - should only work with "SetWinLines" (24) enabled', () => { + // disabled + const term = new TestTerminal({cols: 10, rows: 10}); + term.writeSync('\x1b[?3l'); + assert.equal((term as any)._bufferService.cols, 10); + term.writeSync('\x1b[?3h'); + assert.equal((term as any)._bufferService.cols, 10); + // enabled + const term2 = new TestTerminal({cols: 10, rows: 10, allowedWindowOps: [24]}); + term2.writeSync('\x1b[?3l'); + assert.equal((term2 as any)._bufferService.cols, 80); + term2.writeSync('\x1b[?3h'); + assert.equal((term2 as any)._bufferService.cols, 132); + }); + }); }); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index de28c40cb..574d29b8d 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -33,6 +33,11 @@ const GLEVEL: {[key: string]: number} = {'(': 0, ')': 1, '*': 2, '+': 3, '-': 1, */ const MAX_PARSEBUFFER_LENGTH = 131072; +/** + * Limit length of title and icon name stacks. + */ +const STACK_LIMIT = 10; + /** * DCS subparser implementations @@ -127,6 +132,10 @@ export class InputHandler extends Disposable implements IInputHandler { private _stringDecoder: StringToUtf32 = new StringToUtf32(); private _utf8Decoder: Utf8ToUtf32 = new Utf8ToUtf32(); private _workCell: CellData = new CellData(); + private _windowTitle = ''; + private _iconName = ''; + private _windowTitleStack: string[] = []; + private _iconNameStack: string[] = []; private _onCursorMove = new EventEmitter(); public get onCursorMove(): IEvent { return this._onCursorMove.event; } @@ -222,7 +231,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._parser.setCsiHandler({intermediates: ' ', final: 'q'}, params => this.setCursorStyle(params)); this._parser.setCsiHandler({final: 'r'}, params => this.setScrollRegion(params)); this._parser.setCsiHandler({final: 's'}, params => this.saveCursor(params)); - this._parser.setCsiHandler({final: 't'}, params => this.manipulateWindowOptions(params)); + this._parser.setCsiHandler({final: 't'}, params => this.windowOptions(params)); this._parser.setCsiHandler({final: 'u'}, params => this.restoreCursor(params)); this._parser.setCsiHandler({intermediates: '\'', final: '}'}, params => this.insertColumns(params)); this._parser.setCsiHandler({intermediates: '\'', final: '~'}, params => this.deleteColumns(params)); @@ -249,8 +258,9 @@ export class InputHandler extends Disposable implements IInputHandler { * OSC handler */ // 0 - icon name + title - this._parser.setOscHandler(0, new OscHandler((data: string) => this.setTitle(data))); + this._parser.setOscHandler(0, new OscHandler((data: string) => { this.setTitle(data); this.setIconName(data); })); // 1 - icon name + this._parser.setOscHandler(1, new OscHandler((data: string) => this.setIconName(data))); // 2 - title this._parser.setOscHandler(2, new OscHandler((data: string) => this.setTitle(data))); // 3 - set property X in the form "prop=value" @@ -502,6 +512,15 @@ export class InputHandler extends Disposable implements IInputHandler { * Forward addCsiHandler from parser. */ public addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable { + if (id.final === 't' && !id.prefix && !id.intermediates) { + // security: always check whether window option is allowed + return this._parser.addCsiHandler(id, params => { + if (!~this._optionsService.options.allowedWindowOps.indexOf(params.params[0])) { + return true; + } + return callback(params); + }); + } return this._parser.addCsiHandler(id, callback); } @@ -1391,11 +1410,16 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.setgCharset(3, DEFAULT_CHARSET); // set VT100 mode here break; - case 3: // 132 col mode - // TODO: move DECCOLM into compat addon - this._terminal.savedCols = this._bufferService.cols; - this._terminal.resize(132, this._bufferService.rows); - this._terminal.reset(); + case 3: + /** + * DECCOLM - 132 column mode. + * This is only active if 'SetWinLines' (24) is listed + * in `options.allowedWindowOps`. + */ + if (~this._optionsService.options.allowedWindowOps.indexOf(24)) { + this._terminal.resize(132, this._bufferService.rows); + this._terminal.reset(); + } break; case 6: this._terminal.originMode = true; @@ -1574,11 +1598,15 @@ export class InputHandler extends Disposable implements IInputHandler { // TODO: move DECCOLM into compat addon // Note: This impl currently does not enforce col 80, instead reverts // to previous terminal width before entering DECCOLM 132 - if (this._bufferService.cols === 132 && this._terminal.savedCols) { - this._terminal.resize(this._terminal.savedCols, this._bufferService.rows); + /** + * DECCOLM - 80 column mode. + * This is only active if 'SetWinLines' (24) is listed + * in `options.allowedWindowOps`. + */ + if (~this._optionsService.options.allowedWindowOps.indexOf(24)) { + this._terminal.resize(80, this._bufferService.rows); + this._terminal.reset(); } - delete this._terminal.savedCols; - this._terminal.reset(); break; case 6: this._terminal.originMode = false; @@ -2020,96 +2048,91 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps ; Ps ; Ps t - Various window manipulations and reports (xterm) - * Ps = 1 -> De-iconify window. not supported - * Ps = 2 -> Iconify window. not supported - * Ps = 3 ; x ; y -> Move window to [x, y]. not supported - * Ps = 4 ; height ; width TBD - * Resize the xterm window to given height and width in pixels. - * Omitted parameters reuse the current height or width. - * Zero parameters use the display's height or width. - * Ps = 5 not supported - * Raise the xterm window to the front of the stacking order. - * Ps = 6 not supported - * Lower the xterm window to the bottom of the stacking order. - * Ps = 7 TBD - * Refresh the xterm window. - * Ps = 8 ; height ; width TBD - * Resize the text area to given height and width in characters. - * Omitted parameters reuse the current height or width. - * Zero parameters use the display's height or width. - * Ps = 9 ; 0 -> Restore maximized window. TBD - * Ps = 9 ; 1 TBD - * Maximize window (i.e., resize to screen size). - * Ps = 9 ; 2 -> Maximize window vertically. TBD - * Ps = 9 ; 3 -> Maximize window horizontally. TBD - * Ps = 10 ; 0 -> Undo full-screen mode. TBD - * Ps = 10 ; 1 -> Change to full-screen. TBD - * Ps = 10 ; 2 -> Toggle full-screen. TBD - * Ps = 11 not supported (always report non-iconified?) - * Report xterm window state. - * If the xterm window is non-iconified, it returns CSI 1 t . - * If the xterm window is iconified, it returns CSI 2 t . - * Ps = 13 -> Report xterm window position. TBD - * Note: X Toolkit positions can be negative, but the reported - * values are unsigned, in the range 0-65535. Negative values - * correspond to 32768-65535. - * Result is CSI 3 ; x ; y t - * Ps = 13 ; 2 TBD - * Report xterm text-area position. Result is CSI 3 ; x ; y t - * Ps = 1 4 TBD - * Report xterm text area size in pixels. Result is CSI 4 ; height ; width t - * Ps = 14 ; 2 TBD - * Report xterm window size in pixels. - * Normally xterm's window is larger than its text area, since it - * includes the frame (or decoration) applied by the window manager, - * as well as the area used by a scroll-bar. Result is CSI 4 ; height ; width t - * Ps = 15 TBD - * Report size of the screen in pixels. Result is CSI 5 ; height ; width t - * Ps = 16 TBD - * Report xterm character cell size in pixels. Result is CSI 6 ; height ; width t - * Ps = 18 TBD - * Report the size of the text area in characters. Result is CSI 8 ; height ; width t - * Ps = 19 TBD - * Report the size of the screen in characters. Result is CSI 9 ; height ; width t - * Ps = 20 TBD - * Report xterm window's icon label. Result is OSC L label ST - * Ps = 21 TBD - * Report xterm window's title. Result is OSC l label ST - * Ps = 22 ; 0 -> Save xterm icon and window title on stack. TBD - * Ps = 22 ; 1 -> Save xterm icon title on stack. TBD - * Ps = 22 ; 2 -> Save xterm window title on stack. TBD - * Ps = 23 ; 0 -> Restore xterm icon and window title from stack. TBD - * Ps = 23 ; 1 -> Restore xterm icon title from stack. TBD - * Ps = 23 ; 2 -> Restore xterm window title from stack. TBD - * Ps >= 24 TBD - * Resize to Ps lines (DECSLPP), VT340 and VT420. xterm adapts this by resizing its window. + * + * Note: Only those listed below are supported. All others are left to integrators and + * need special treatment based on the embedding environment. + * + * Ps = 1 4 supported + * Report xterm text area size in pixels. + * Result is CSI 4 ; height ; width t + * Ps = 14 ; 2 not implemented + * Ps = 16 supported + * Report xterm character cell size in pixels. + * Result is CSI 6 ; height ; width t + * Ps = 18 supported + * Report the size of the text area in characters. + * Result is CSI 8 ; height ; width t + * Ps = 20 supported + * Report xterm window's icon label. + * Result is OSC L label ST + * Ps = 21 supported + * Report xterm window's title. + * Result is OSC l label ST + * Ps = 22 ; 0 -> Save xterm icon and window title on stack. supported + * Ps = 22 ; 1 -> Save xterm icon title on stack. supported + * Ps = 22 ; 2 -> Save xterm window title on stack. supported + * Ps = 23 ; 0 -> Restore xterm icon and window title from stack. supported + * Ps = 23 ; 1 -> Restore xterm icon title from stack. supported + * Ps = 23 ; 2 -> Restore xterm window title from stack. supported + * Ps >= 24 not implemented */ - public manipulateWindowOptions(params?: IParams): void { - console.log(params); + public windowOptions(params?: IParams): void { + if (!~this._optionsService.options.allowedWindowOps.indexOf(params.params[0])) { + return; + } + const second = (params.length > 1) ? params.params[1] : 0; + const rs = (this._terminal as any)._renderService; // FIXME: import renderService to get rid of any type here switch (params.params[0]) { - case 8: - if (params.length === 3) { - // TODO: 0/1 support - const cols = Math.max(2, params.params[2]); - const rows = Math.max(2, params.params[1]); - // make it work with demo thus shamelessly taken from demo's client.ts - const width = (cols * (this._terminal as any)._renderService.dimensions.actualCellWidth + this._terminal.viewport.scrollBarWidth).toString() + 'px'; - const height = (rows * (this._terminal as any)._renderService.dimensions.actualCellHeight).toString() + 'px'; - (this._terminal as any)._parent.style.width = width; - (this._terminal as any)._parent.style.height = height; - const term = Function('return this.term;')(); - // move fit into browser codebase? - for (const addon of (term as any)._addonManager._addons) { - if (`${addon.instance.constructor}`.indexOf('FitAddon') !== -1) { - addon.instance.fit(); - } + case 14: // GetWinSizePixels, returns CSI 4 ; height ; width t + if (rs && second !== 2) { + const w = rs.dimensions.canvasWidth.toFixed(0); + const h = rs.dimensions.canvasHeight.toFixed(0); + this._coreService.triggerDataEvent(`${C0.ESC}[4;${h};${w}t`); + } + break; + case 16: // GetCellSizePixels, returns CSI 6 ; height ; width t + if (rs) { + const w = rs.dimensions.actualCellWidth.toFixed(0); + const h = rs.dimensions.actualCellHeight.toFixed(0); + this._coreService.triggerDataEvent(`${C0.ESC}[4;${h};${w}t`); + } + break; + case 18: // GetWinSizeChars, returns CSI 8 ; height ; width t + if (this._bufferService) { + this._coreService.triggerDataEvent(`${C0.ESC}[8;${this._bufferService.rows};${this._bufferService.cols}t`); + } + break; + case 20: // GetIconTitle, returns OSC L label ST + this._coreService.triggerDataEvent(`${C0.ESC}]L${this._iconName}${C0.ESC}\\`); + break; + case 21: // GetWinTitle, returns OSC l label ST + this._coreService.triggerDataEvent(`${C0.ESC}]l${this._windowTitle}${C0.ESC}\\`); + break; + case 22: // PushTitle + if (!second || second === 2) { + this._windowTitleStack.push(this._windowTitle); + if (this._windowTitleStack.length > STACK_LIMIT) { + this._windowTitleStack.shift(); + } + } + if (!second || second === 1) { + this._iconNameStack.push(this._iconName); + if (this._iconNameStack.length > STACK_LIMIT) { + this._iconNameStack.shift(); } } break; - case 18: - const height = this._bufferService.rows; - const width = this._bufferService.cols; - this._coreService.triggerDataEvent(`${C0.ESC}[8;${height};${width}t`); + case 23: // PopTitle + if (!second || second === 2) { + if (this._windowTitleStack.length) { + this.setTitle(this._windowTitleStack.pop()); + } + } + if (!second || second === 1) { + if (this._iconNameStack.length) { + this.setIconName(this._iconNameStack.pop()); + } + } break; } } @@ -2148,14 +2171,22 @@ export class InputHandler extends Disposable implements IInputHandler { /** - * OSC 0; ST (set icon name + window title) * OSC 2; ST (set window title) - * Proxy to set window title. Icon name is not supported. + * Proxy to set window title. */ public setTitle(data: string): void { + this._windowTitle = data; this._terminal.handleTitle(data); } + /** + * OSC 1; ST + * Note: Icon name is not exposed. + */ + public setIconName(data: string): void { + this._iconName = data; + } + /** * ESC E * C1.NEL diff --git a/src/Terminal.ts b/src/Terminal.ts index 3e7e55ca9..ffa34da80 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -131,8 +131,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp public sendFocus: boolean; // misc - public savedCols: number; - public curAttrData: IAttributeData; private _eraseAttrData: IAttributeData; @@ -1463,7 +1461,9 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // Sync the scroll area to make sure scroll events don't fire and scroll the viewport to an // invalid location - this.viewport.syncScrollArea(true); + if (this.viewport) { + this.viewport.syncScrollArea(true); + } this.refresh(0, this.rows - 1); this._onResize.fire({ cols: x, rows: y }); diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index a70f751c5..640a6c760 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -211,7 +211,6 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal { wraparoundMode: boolean; bracketedPasteMode: boolean; curAttrData = new AttributeData(); - savedCols: number; x10Mouse: boolean; vt200Mouse: boolean; normalMouse: boolean; diff --git a/src/Types.d.ts b/src/Types.d.ts index be1019e58..faccb4025 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -35,7 +35,6 @@ export interface IInputHandlingTerminal { wraparoundMode: boolean; bracketedPasteMode: boolean; curAttrData: IAttributeData; - savedCols: number; mouseEvents: CoreMouseEventType; sendFocus: boolean; cursorHidden: boolean; diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index d9ea60d5a..3c3d4bea5 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -14,6 +14,34 @@ import { clone } from 'common/Clone'; // made, apart from the conversion to base64. export const DEFAULT_BELL_SOUND = 'data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4LjMyLjEwNAAAAAAAAAAAAAAA//tQxAADB8AhSmxhIIEVCSiJrDCQBTcu3UrAIwUdkRgQbFAZC1CQEwTJ9mjRvBA4UOLD8nKVOWfh+UlK3z/177OXrfOdKl7pyn3Xf//WreyTRUoAWgBgkOAGbZHBgG1OF6zM82DWbZaUmMBptgQhGjsyYqc9ae9XFz280948NMBWInljyzsNRFLPWdnZGWrddDsjK1unuSrVN9jJsK8KuQtQCtMBjCEtImISdNKJOopIpBFpNSMbIHCSRpRR5iakjTiyzLhchUUBwCgyKiweBv/7UsQbg8isVNoMPMjAAAA0gAAABEVFGmgqK////9bP/6XCykxBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq'; +// setting names controlled by allowedWindowOps +// not supported: GetChecksum, GetSelection, SetChecksum, SetSelection, SetXprop +export const WINDOW_OPTIONS: {[key: string]: number} = { + 'RestoreWin': 1, + 'MinimizeWin': 2, + 'SetWinPosition': 3, + 'SetWinSizePixels': 4, + 'RaiseWin': 5, + 'LowerWin': 6, + 'RefreshWin': 7, + 'SetWinSizeChars': 8, + 'MaximizeWin': 9, + 'FullscreenWin': 10, + 'GetWinState': 11, + 'GetWinPosition': 13, + 'GetWinSizePixels': 14, + 'GetScreenSizePixels': 15, // note: name not in xterm + 'GetCellSizePixels': 16, // note: name not in xterm + 'GetWinSizeChars': 18, + 'GetScreenSizeChars': 19, + 'GetIconTitle': 20, + 'GetWinTitle': 21, + 'PushTitle': 22, + 'PopTitle': 23, + 'SetWinLines': 24 // any param >= 24, also handles DECCOLM +}; + + // TODO: Freeze? export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({ cols: 80, @@ -50,7 +78,8 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({ screenKeys: false, cancelEvents: false, useFlowControl: false, - wordSeparator: ' ()[]{}\',:;"' + wordSeparator: ' ()[]{}\',:;"', + allowedWindowOps: [] }); /** @@ -128,6 +157,20 @@ export class OptionsService implements IOptionsService { throw new Error(`${key} cannot be less than or equal to 0, value: ${value}`); } break; + case 'allowedWindowOps': + const values = (value as string).split(','); + const cleaned: number[] = []; + for (let i = 0; i < values.length; ++i) { + const option = parseInt(values[i].trim()) || WINDOW_OPTIONS[values[i].trim()]; + if (!option || option < 0 || option > 24) { + throw new Error(`unknown window option "${values[i]}"`); + } + if (!~cleaned.indexOf(option)) { + cleaned.push(option); + } + } + value = cleaned; + break; } return value; } @@ -136,6 +179,9 @@ export class OptionsService implements IOptionsService { if (!(key in DEFAULT_OPTIONS)) { throw new Error(`No option with key "${key}"`); } + if (key === 'allowedWindowOps') { + return this.options[key].join(); + } return this.options[key]; } } diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 0872d3db7..a0a25bb81 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -201,6 +201,7 @@ export interface IPartialTerminalOptions { theme?: ITheme; windowsMode?: boolean; wordSeparator?: string; + allowedWindowOps?: number[]; } export interface ITerminalOptions { @@ -240,6 +241,7 @@ export interface ITerminalOptions { screenKeys: boolean; termName: string; useFlowControl: boolean; + allowedWindowOps: number[]; } export interface ITheme { From 2cad586ccc1d1fb6f4a875427692a967142b0463 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 29 Oct 2019 15:50:54 +0100 Subject: [PATCH 016/268] add integration tests for pixel reports --- src/InputHandler.ts | 2 +- test/api/InputHandler.api.ts | 59 ++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 574d29b8d..259a2b1c1 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -2094,7 +2094,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (rs) { const w = rs.dimensions.actualCellWidth.toFixed(0); const h = rs.dimensions.actualCellHeight.toFixed(0); - this._coreService.triggerDataEvent(`${C0.ESC}[4;${h};${w}t`); + this._coreService.triggerDataEvent(`${C0.ESC}[6;${h};${w}t`); } break; case 18: // GetWinSizeChars, returns CSI 8 ; height ; width t diff --git a/test/api/InputHandler.api.ts b/test/api/InputHandler.api.ts index fe0686103..991c5cb5d 100644 --- a/test/api/InputHandler.api.ts +++ b/test/api/InputHandler.api.ts @@ -346,6 +346,51 @@ describe('InputHandler Integration Tests', function(): void { `); assert.deepEqual(await getLinesAsArray(3), ['#', ' #', 'abcd####']); }); + + describe.only('Window Options - CSI Ps ; Ps ; Ps t', () => { + it('should be disabled by default', async function() { + assert.equal(await page.evaluate(`(() => window.term.getOption('allowedWindowOps'))()`), ''); + await page.evaluate(`(() => { + window._stack = []; + const _h = window.term.onData(data => window._stack.push(data)); + window.term.write('\x1b[14t'); + window.term.write('\x1b[16t'); + window.term.write('\x1b[18t'); + window.term.write('\x1b[20t'); + window.term.write('\x1b[21t'); + return new Promise((r) => window.term.write('', () => { _h.dispose(); r(); })); + })()`); + assert.deepEqual(await page.evaluate(`(() => _stack)()`), []); + }); + it('14 - GetWinSizePixels', async function() { + assert.equal(await page.evaluate(`(() => { + window.term.setOption('allowedWindowOps', 'GetWinSizePixels'); + return window.term.getOption('allowedWindowOps'); + })()`), '14'); + await page.evaluate(`(() => { + window._stack = []; + const _h = window.term.onData(data => window._stack.push(data)); + window.term.write('\x1b[14t'); + return new Promise((r) => window.term.write('', () => { _h.dispose(); r(); })); + })()`); + const d = await getDimensions(); + assert.deepEqual(await page.evaluate(`(() => _stack)()`), [`\x1b[4;${d.height};${d.width}t`]); + }); + it('16 - GetCellSizePixels', async function() { + assert.equal(await page.evaluate(`(() => { + window.term.setOption('allowedWindowOps', 'GetCellSizePixels'); + return window.term.getOption('allowedWindowOps'); + })()`), '16'); + await page.evaluate(`(() => { + window._stack = []; + const _h = window.term.onData(data => window._stack.push(data)); + window.term.write('\x1b[16t'); + return new Promise((r) => window.term.write('', () => { _h.dispose(); r(); })); + })()`); + const d = await getDimensions(); + assert.deepEqual(await page.evaluate(`(() => _stack)()`), [`\x1b[6;${d.cellHeight};${d.cellWidth}t`]); + }); + }); }); describe('ESC', () => { @@ -404,3 +449,17 @@ async function getCursor(): Promise<{col: number, row: number}> { })(); `); } + +async function getDimensions(): Promise { + const dim = await page.evaluate(` + (function() { + return term._core._renderService.dimensions; + })(); + `); + return { + cellWidth: dim.actualCellWidth.toFixed(0), + cellHeight: dim.actualCellHeight.toFixed(0), + width: dim.canvasWidth.toFixed(0), + height: dim.canvasHeight.toFixed(0) + }; +} From 98cc17df24a9c4e06704abe25373b315c14c23d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 29 Oct 2019 15:57:11 +0100 Subject: [PATCH 017/268] remove leftover .only --- test/api/InputHandler.api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/api/InputHandler.api.ts b/test/api/InputHandler.api.ts index 991c5cb5d..a3725f9c8 100644 --- a/test/api/InputHandler.api.ts +++ b/test/api/InputHandler.api.ts @@ -347,7 +347,7 @@ describe('InputHandler Integration Tests', function(): void { assert.deepEqual(await getLinesAsArray(3), ['#', ' #', 'abcd####']); }); - describe.only('Window Options - CSI Ps ; Ps ; Ps t', () => { + describe('Window Options - CSI Ps ; Ps ; Ps t', () => { it('should be disabled by default', async function() { assert.equal(await page.evaluate(`(() => window.term.getOption('allowedWindowOps'))()`), ''); await page.evaluate(`(() => { From bcd4c766d61bbaf4d275d7faa1994d72923acdf5 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 09:09:59 -0800 Subject: [PATCH 018/268] Implement webgl renderer dispose Fixes #2254 --- addons/xterm-addon-webgl/src/WebglAddon.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index 0074f9b28..be867d754 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -9,20 +9,28 @@ import { IRenderService } from 'browser/services/Services'; import { IColorSet } from 'browser/Types'; export class WebglAddon implements ITerminalAddon { + private _terminal?: Terminal; + constructor( private _preserveDrawingBuffer?: boolean ) {} public activate(terminal: Terminal): void { if (!terminal.element) { - throw new Error('Cannot activate WebglRendererAddon before Terminal.open'); + throw new Error('Cannot activate WebglAddon before Terminal.open'); } + this._terminal = terminal; const renderService: IRenderService = (terminal)._core._renderService; const colors: IColorSet = (terminal)._core._colorManager.colors; renderService.setRenderer(new WebglRenderer(terminal, colors, this._preserveDrawingBuffer)); } public dispose(): void { - throw new Error('WebglRendererAddon.dispose Not yet implemented'); + if (!this._terminal) { + throw new Error('Cannot dispose WebglAddon because it is activated'); + } + const renderService: IRenderService = (this._terminal)._core._renderService; + renderService.setRenderer((this._terminal)._core._createRenderer()); + renderService.onResize(this._terminal.cols, this._terminal.rows); } } From 2aec4e6d29c1b1fd8ed4c2004ceac74b2a1a8634 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 09:27:30 -0800 Subject: [PATCH 019/268] Add a test for removing canvases --- .../src/WebglRenderer.api.ts | 54 ++++++++++++------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 9303782d7..4debb5f96 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -18,29 +18,25 @@ const height = 600; describe('WebGL Renderer Integration Tests', function(): void { this.timeout(20000); - before(async function(): Promise { - browser = await puppeteer.launch({ - headless: process.argv.indexOf('--headless') !== -1, - slowMo: 80, - args: [`--window-size=${width},${height}`, `--no-sandbox`] - }); - page = (await browser.pages())[0]; - await page.setViewport({ width, height }); - await page.goto(APP); - await openTerminal(); - await page.evaluate(`window.term.loadAddon(new WebglAddon(true));`); - }); - after(() => { browser.close(); }); - beforeEach(async () => { - await page.evaluate(`window.term.reset()`); + it('dispose removes renderer canvases', async () => { + await setupBrowser(); + assert.equal(await page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 3); + await page.evaluate(`addon.dispose()`); + assert.equal(await page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 0); }); - describe('WebGL Renderer', () => { - it('foreground colors normal', async function(): Promise { + describe('colors', () => { + before(async () => setupBrowser()); + + beforeEach(async () => { + await page.evaluate(`window.term.reset()`); + }); + + it('foreground colors normal', async () => { const theme: ITheme = { black: '#010203', red: '#040506', @@ -63,7 +59,7 @@ describe('WebGL Renderer Integration Tests', function(): void { assert.deepEqual(await getCellColor(8, 1), [22, 23, 24, 255]); }); - it('foreground colors bright', async function(): Promise { + it('foreground colors bright', async () => { const theme: ITheme = { brightBlack: '#010203', brightRed: '#040506', @@ -86,7 +82,7 @@ describe('WebGL Renderer Integration Tests', function(): void { assert.deepEqual(await getCellColor(8, 1), [22, 23, 24, 255]); }); - it('background colors normal', async function(): Promise { + it('background colors normal', async () => { const theme: ITheme = { black: '#010203', red: '#040506', @@ -109,7 +105,7 @@ describe('WebGL Renderer Integration Tests', function(): void { assert.deepEqual(await getCellColor(8, 1), [22, 23, 24, 255]); }); - it('background colors bright', async function(): Promise { + it('background colors bright', async () => { const theme: ITheme = { brightBlack: '#010203', brightRed: '#040506', @@ -161,3 +157,21 @@ async function getCellColor(col: number, row: number): Promise { `); return await page.evaluate(`Array.from(window.result)`); } + +async function setupBrowser(): Promise { + browser = await puppeteer.launch({ + headless: process.argv.indexOf('--headless') !== -1, + slowMo: 80, + args: [`--window-size=${width},${height}`, `--no-sandbox`] + }); + page = (await browser.pages())[0]; + await page.setViewport({ width, height }); + await page.goto(APP); + await openTerminal({ + rendererType: 'dom' + }); + await page.evaluate(` + window.addon = new WebglAddon(true); + window.term.loadAddon(window.addon); + `); +} From d09ac41f75dbedaf104aabae5f16a6849c4a2c1a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 09:29:04 -0800 Subject: [PATCH 020/268] After tests wait for browser close --- addons/xterm-addon-webgl/src/WebglRenderer.api.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 4debb5f96..be8957438 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -18,9 +18,7 @@ const height = 600; describe('WebGL Renderer Integration Tests', function(): void { this.timeout(20000); - after(() => { - browser.close(); - }); + after(async () => browser.close()); it('dispose removes renderer canvases', async () => { await setupBrowser(); @@ -31,10 +29,7 @@ describe('WebGL Renderer Integration Tests', function(): void { describe('colors', () => { before(async () => setupBrowser()); - - beforeEach(async () => { - await page.evaluate(`window.term.reset()`); - }); + beforeEach(async () => page.evaluate(`window.term.reset()`)); it('foreground colors normal', async () => { const theme: ITheme = { From 8adbed84b07a8f278e082f4cc9888beaf92e0f06 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 09:34:40 -0800 Subject: [PATCH 021/268] webgl: Refresh rows on options change Fixes #2549 --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 1f28d5893..a32b03e0d 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -189,7 +189,6 @@ export class WebglRenderer extends Disposable implements IRenderer { this._rectangleRenderer.updateSelection(this._model.selection, columnSelectMode); this._glyphRenderer.updateSelection(this._model, columnSelectMode); - // TODO: #2102 Should this move to RenderCoordinator? this._onRequestRefreshRows.fire({ start: 0, end: this._terminal.rows - 1 }); } @@ -201,6 +200,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._renderLayers.forEach(l => l.onOptionsChanged(this._terminal)); this._updateDimensions(); this._refreshCharAtlas(); + this._onRequestRefreshRows.fire({ start: 0, end: this._terminal.rows - 1 }); } /** From 22f4342194b0576bedc22f370c568992d906ea0e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 10:07:24 -0800 Subject: [PATCH 022/268] Move options row refresh into render service --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 1 - src/browser/renderer/dom/DomRenderer.ts | 1 - src/browser/services/RenderService.ts | 1 + 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index a32b03e0d..ab2cb3546 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -200,7 +200,6 @@ export class WebglRenderer extends Disposable implements IRenderer { this._renderLayers.forEach(l => l.onOptionsChanged(this._terminal)); this._updateDimensions(); this._refreshCharAtlas(); - this._onRequestRefreshRows.fire({ start: 0, end: this._terminal.rows - 1 }); } /** diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 3d544a902..e8fc85f66 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -340,7 +340,6 @@ export class DomRenderer extends Disposable implements IRenderer { // Force a refresh this._updateDimensions(); this._injectCss(); - this._onRequestRefreshRows.fire({ start: 0, end: this._bufferService.rows - 1 }); } public clear(): void { diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index b28c99713..2949c2c50 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -95,6 +95,7 @@ export class RenderService extends Disposable implements IRenderService { public changeOptions(): void { this._renderer.onOptionsChanged(); + this.refreshRows(0, this._rowCount - 1); this._fireOnCanvasResize(); } From 60bda1bbd3fb0df867686426a29c6ae3c57f7d00 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 11:04:55 -0800 Subject: [PATCH 023/268] Fix tests hanging --- addons/xterm-addon-webgl/src/WebglRenderer.api.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index be8957438..73e83bb20 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -18,17 +18,17 @@ const height = 600; describe('WebGL Renderer Integration Tests', function(): void { this.timeout(20000); - after(async () => browser.close()); - it('dispose removes renderer canvases', async () => { await setupBrowser(); assert.equal(await page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 3); await page.evaluate(`addon.dispose()`); assert.equal(await page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 0); + await browser.close(); }); describe('colors', () => { before(async () => setupBrowser()); + after(async () => browser.close()); beforeEach(async () => page.evaluate(`window.term.reset()`)); it('foreground colors normal', async () => { From b8acdd6e673e0cbec386d5f969e45238a668621f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 11:15:15 -0800 Subject: [PATCH 024/268] Remove slowmo from puppeteer --- addons/xterm-addon-attach/src/AttachAddon.api.ts | 2 +- addons/xterm-addon-fit/src/FitAddon.api.ts | 2 +- addons/xterm-addon-search/src/SearchAddon.api.ts | 2 +- addons/xterm-addon-web-links/src/WebLinksAddon.api.ts | 2 +- addons/xterm-addon-webgl/src/WebglRenderer.api.ts | 2 +- test/api/CharWidth.api.ts | 2 +- test/api/InputHandler.api.ts | 2 +- test/api/MouseTracking.api.ts | 2 +- test/api/Parser.api.ts | 2 +- test/api/Terminal.api.ts | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/addons/xterm-addon-attach/src/AttachAddon.api.ts b/addons/xterm-addon-attach/src/AttachAddon.api.ts index 94d11139b..f027e25e4 100644 --- a/addons/xterm-addon-attach/src/AttachAddon.api.ts +++ b/addons/xterm-addon-attach/src/AttachAddon.api.ts @@ -20,7 +20,7 @@ describe('AttachAddon', () => { this.timeout(20000); browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 80, + slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; diff --git a/addons/xterm-addon-fit/src/FitAddon.api.ts b/addons/xterm-addon-fit/src/FitAddon.api.ts index 4f9558684..3115f2edd 100644 --- a/addons/xterm-addon-fit/src/FitAddon.api.ts +++ b/addons/xterm-addon-fit/src/FitAddon.api.ts @@ -19,7 +19,7 @@ describe('FitAddon', () => { this.timeout(20000); browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 80, + slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; diff --git a/addons/xterm-addon-search/src/SearchAddon.api.ts b/addons/xterm-addon-search/src/SearchAddon.api.ts index ff195f5e9..96c62a7d4 100644 --- a/addons/xterm-addon-search/src/SearchAddon.api.ts +++ b/addons/xterm-addon-search/src/SearchAddon.api.ts @@ -22,7 +22,7 @@ describe('Search Tests', function (): void { before(async function (): Promise { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 80, + slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; diff --git a/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts b/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts index 3fb1a5365..2a3fd199d 100644 --- a/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts +++ b/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts @@ -19,7 +19,7 @@ describe('WebLinksAddon', () => { this.timeout(10000); browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 80, + slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 9303782d7..a7079df10 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -21,7 +21,7 @@ describe('WebGL Renderer Integration Tests', function(): void { before(async function(): Promise { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 80, + slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; diff --git a/test/api/CharWidth.api.ts b/test/api/CharWidth.api.ts index 0561bf075..a20ecc8b9 100644 --- a/test/api/CharWidth.api.ts +++ b/test/api/CharWidth.api.ts @@ -20,7 +20,7 @@ describe('CharWidth Integration Tests', function(): void { before(async function(): Promise { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 80, + slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; diff --git a/test/api/InputHandler.api.ts b/test/api/InputHandler.api.ts index fe0686103..2070dfe61 100644 --- a/test/api/InputHandler.api.ts +++ b/test/api/InputHandler.api.ts @@ -20,7 +20,7 @@ describe('InputHandler Integration Tests', function(): void { before(async function(): Promise { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 80, + slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; diff --git a/test/api/MouseTracking.api.ts b/test/api/MouseTracking.api.ts index 37d46f74e..a993c6a1b 100644 --- a/test/api/MouseTracking.api.ts +++ b/test/api/MouseTracking.api.ts @@ -204,7 +204,7 @@ describe('Mouse Tracking Tests', function(): void { before(async function(): Promise { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 80, + slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; diff --git a/test/api/Parser.api.ts b/test/api/Parser.api.ts index e46242ab1..4dcda70c7 100644 --- a/test/api/Parser.api.ts +++ b/test/api/Parser.api.ts @@ -20,7 +20,7 @@ describe('Parser Integration Tests', function(): void { before(async function(): Promise { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 80, + slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index 08963d9ca..07dc372c7 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -20,7 +20,7 @@ describe('API Integration Tests', function(): void { before(async function(): Promise { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 80, + slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; From 840e56cf1e0e53cab83b011f4455f1e62030e134 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 11:30:45 -0800 Subject: [PATCH 025/268] Fix Attach tests --- addons/xterm-addon-attach/src/AttachAddon.api.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/addons/xterm-addon-attach/src/AttachAddon.api.ts b/addons/xterm-addon-attach/src/AttachAddon.api.ts index f027e25e4..627647cd6 100644 --- a/addons/xterm-addon-attach/src/AttachAddon.api.ts +++ b/addons/xterm-addon-attach/src/AttachAddon.api.ts @@ -20,7 +20,6 @@ describe('AttachAddon', () => { this.timeout(20000); browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; @@ -43,6 +42,7 @@ describe('AttachAddon', () => { const server = new WebSocket.Server({ port }); server.on('connection', socket => socket.send('foo')); await page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}')))`); + await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foo'); assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo'); server.close(); }); @@ -55,6 +55,7 @@ describe('AttachAddon', () => { const data = new Uint8Array([102, 111, 111]); server.on('connection', socket => socket.send(data)); await page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}')))`); + await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foo'); assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo'); server.close(); }); @@ -69,3 +70,12 @@ async function openTerminal(options: ITerminalOptions = {}): Promise { await page.waitForSelector('.xterm-text-layer'); } } + +async function pollFor(page: puppeteer.Page, fn: string, val: any): Promise { + const result = await page.evaluate(fn); + if (result !== val) { + return new Promise(r => { + setTimeout(() => r(pollFor(page, fn, val)), 10); + }); + } +} From d3bf013ceb1f31d81842b5abc9e49181f33f753a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 13:57:42 -0800 Subject: [PATCH 026/268] Fix timing issues in web links addon tests --- .../xterm-addon-attach/src/AttachAddon.api.ts | 2 - .../src/WebLinksAddon.api.ts | 38 +++++++++++++------ 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/addons/xterm-addon-attach/src/AttachAddon.api.ts b/addons/xterm-addon-attach/src/AttachAddon.api.ts index 627647cd6..264b32ee1 100644 --- a/addons/xterm-addon-attach/src/AttachAddon.api.ts +++ b/addons/xterm-addon-attach/src/AttachAddon.api.ts @@ -43,7 +43,6 @@ describe('AttachAddon', () => { server.on('connection', socket => socket.send('foo')); await page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}')))`); await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foo'); - assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo'); server.close(); }); @@ -56,7 +55,6 @@ describe('AttachAddon', () => { server.on('connection', socket => socket.send(data)); await page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}')))`); await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foo'); - assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo'); server.close(); }); }); diff --git a/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts b/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts index 2a3fd199d..49ec4feef 100644 --- a/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts +++ b/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts @@ -19,7 +19,6 @@ describe('WebLinksAddon', () => { this.timeout(10000); browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; @@ -54,22 +53,25 @@ describe('WebLinksAddon', () => { async function testHostName(hostname: string): Promise { await openTerminal({ rendererType: 'dom' }); await page.evaluate(`window.term.loadAddon(new window.WebLinksAddon())`); + await new Promise(r => setTimeout(r, 100)); await page.evaluate(` + window.ready = false; window.term.writeln(' http://${hostname} '); window.term.writeln(' http://${hostname}/a~b#c~d?e~f '); window.term.writeln(' http://${hostname}/colon:test '); window.term.writeln(' http://${hostname}/colon:test: '); window.term.writeln('"http://${hostname}/"'); window.term.writeln('\\'http://${hostname}/\\''); - window.term.writeln('http://${hostname}/subpath/+/id'); + window.term.writeln('http://${hostname}/subpath/+/id', () => window.ready = true); `); - assert.equal(await getLinkAtCell(3, 1), `http://${hostname}`); - assert.equal(await getLinkAtCell(3, 2), `http://${hostname}/a~b#c~d?e~f`); - assert.equal(await getLinkAtCell(3, 3), `http://${hostname}/colon:test`); - assert.equal(await getLinkAtCell(3, 4), `http://${hostname}/colon:test`); - assert.equal(await getLinkAtCell(2, 5), `http://${hostname}/`); - assert.equal(await getLinkAtCell(2, 6), `http://${hostname}/`); - assert.equal(await getLinkAtCell(1, 7), `http://${hostname}/subpath/+/id`); + await pollFor(page, 'window.ready', true); + await pollForLinkAtCell(3, 1, `http://${hostname}`); + await pollForLinkAtCell(3, 2, `http://${hostname}/a~b#c~d?e~f`); + await pollForLinkAtCell(3, 3, `http://${hostname}/colon:test`); + await pollForLinkAtCell(3, 4, `http://${hostname}/colon:test`); + await pollForLinkAtCell(2, 5, `http://${hostname}/`); + await pollForLinkAtCell(2, 6, `http://${hostname}/`); + await pollForLinkAtCell(1, 7, `http://${hostname}/subpath/+/id`); } async function openTerminal(options: ITerminalOptions = {}): Promise { @@ -82,8 +84,20 @@ async function openTerminal(options: ITerminalOptions = {}): Promise { } } -async function getLinkAtCell(col: number, row: number): Promise { +async function pollForLinkAtCell(col: number, row: number, value: string): Promise { const rowSelector = `.xterm-rows > :nth-child(${row})`; - await page.hover(`${rowSelector} > :nth-child(${col})`); - return await page.evaluate(`Array.prototype.reduce.call(document.querySelectorAll('${rowSelector} > span[style]'), (a, b) => a + b.textContent, '');`); + await pollFor(page, `document.querySelectorAll('${rowSelector} > span[style]').length > 0`, true, async () => page.hover(`${rowSelector} > :nth-child(${col})`)); + assert.equal(await page.evaluate(`Array.prototype.reduce.call(document.querySelectorAll('${rowSelector} > span[style]'), (a, b) => a + b.textContent, '');`), value); +} + +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 b0c0f8701e8037485e3f777545d5b17e3bbee3c9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 14:08:37 -0800 Subject: [PATCH 027/268] Fix timing issues in webglrenderer --- .../src/WebglRenderer.api.ts | 87 +++++++++++-------- 1 file changed, 51 insertions(+), 36 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index a7079df10..18d802763 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -15,13 +15,10 @@ let page: puppeteer.Page; const width = 800; const height = 600; -describe('WebGL Renderer Integration Tests', function(): void { - this.timeout(20000); - +describe.only('WebGL Renderer Integration Tests', function(): void { before(async function(): Promise { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; @@ -53,14 +50,14 @@ describe('WebGL Renderer Integration Tests', function(): void { }; await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); await writeSync(`\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█`); - assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]); - assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]); - assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]); - assert.deepEqual(await getCellColor(4, 1), [10, 11, 12, 255]); - assert.deepEqual(await getCellColor(5, 1), [13, 14, 15, 255]); - assert.deepEqual(await getCellColor(6, 1), [16, 17, 18, 255]); - assert.deepEqual(await getCellColor(7, 1), [19, 20, 21, 255]); - assert.deepEqual(await getCellColor(8, 1), [22, 23, 24, 255]); + await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]); + await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]); + await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]); + await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]); + await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]); + await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]); + await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]); + await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); }); it('foreground colors bright', async function(): Promise { @@ -76,14 +73,14 @@ describe('WebGL Renderer Integration Tests', function(): void { }; await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); await writeSync(`\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█`); - assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]); - assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]); - assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]); - assert.deepEqual(await getCellColor(4, 1), [10, 11, 12, 255]); - assert.deepEqual(await getCellColor(5, 1), [13, 14, 15, 255]); - assert.deepEqual(await getCellColor(6, 1), [16, 17, 18, 255]); - assert.deepEqual(await getCellColor(7, 1), [19, 20, 21, 255]); - assert.deepEqual(await getCellColor(8, 1), [22, 23, 24, 255]); + await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]); + await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]); + await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]); + await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]); + await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]); + await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]); + await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]); + await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); }); it('background colors normal', async function(): Promise { @@ -99,14 +96,14 @@ describe('WebGL Renderer Integration Tests', function(): void { }; await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); await writeSync(`\\x1b[40m \\x1b[41m \\x1b[42m \\x1b[43m \\x1b[44m \\x1b[45m \\x1b[46m \\x1b[47m `); - assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]); - assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]); - assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]); - assert.deepEqual(await getCellColor(4, 1), [10, 11, 12, 255]); - assert.deepEqual(await getCellColor(5, 1), [13, 14, 15, 255]); - assert.deepEqual(await getCellColor(6, 1), [16, 17, 18, 255]); - assert.deepEqual(await getCellColor(7, 1), [19, 20, 21, 255]); - assert.deepEqual(await getCellColor(8, 1), [22, 23, 24, 255]); + await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]); + await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]); + await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]); + await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]); + await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]); + await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]); + await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]); + await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); }); it('background colors bright', async function(): Promise { @@ -122,14 +119,14 @@ describe('WebGL Renderer Integration Tests', function(): void { }; await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); await writeSync(`\\x1b[100m \\x1b[101m \\x1b[102m \\x1b[103m \\x1b[104m \\x1b[105m \\x1b[106m \\x1b[107m `); - assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]); - assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]); - assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]); - assert.deepEqual(await getCellColor(4, 1), [10, 11, 12, 255]); - assert.deepEqual(await getCellColor(5, 1), [13, 14, 15, 255]); - assert.deepEqual(await getCellColor(6, 1), [16, 17, 18, 255]); - assert.deepEqual(await getCellColor(7, 1), [19, 20, 21, 255]); - assert.deepEqual(await getCellColor(8, 1), [22, 23, 24, 255]); + await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]); + await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]); + await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]); + await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]); + await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]); + await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]); + await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]); + await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); }); }); }); @@ -161,3 +158,21 @@ async function getCellColor(col: number, row: number): Promise { `); return await page.evaluate(`Array.from(window.result)`); } + +async function pollFor(page: puppeteer.Page, evalOrFn: string | (() => Promise), val: T, preFn?: () => Promise): Promise { + if (preFn) { + await preFn(); + } + const result = typeof evalOrFn === 'string' ? await page.evaluate(evalOrFn) : await evalOrFn(); + let equal = false; + if (typeof result === 'object') { + equal = Object.keys(result).every(e => result[e] === (val as any)[e]); + } else { + equal = result === val; + } + if (!equal) { + return new Promise(r => { + setTimeout(() => r(pollFor(page, evalOrFn, val, preFn)), 10); + }); + } +} From 74f04ae92e4251e029faab3ed05fe5f5e4c201da Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 14:12:46 -0800 Subject: [PATCH 028/268] Fix timing issues in char width test --- .../src/WebglRenderer.api.ts | 3 +-- test/api/CharWidth.api.ts | 17 ++++++------- test/api/TestUtils.ts | 24 +++++++++++++++++++ 3 files changed, 32 insertions(+), 12 deletions(-) create mode 100644 test/api/TestUtils.ts diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 18d802763..9dfac8433 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -4,7 +4,6 @@ */ import * as puppeteer from 'puppeteer'; -import { assert } from 'chai'; import { ITerminalOptions } from '../../../src/Types'; import { ITheme } from 'xterm'; @@ -15,7 +14,7 @@ let page: puppeteer.Page; const width = 800; const height = 600; -describe.only('WebGL Renderer Integration Tests', function(): void { +describe('WebGL Renderer Integration Tests', function(): void { before(async function(): Promise { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, diff --git a/test/api/CharWidth.api.ts b/test/api/CharWidth.api.ts index a20ecc8b9..39f0bfe72 100644 --- a/test/api/CharWidth.api.ts +++ b/test/api/CharWidth.api.ts @@ -4,8 +4,8 @@ */ import * as puppeteer from 'puppeteer'; -import { assert } from 'chai'; import { ITerminalOptions } from 'xterm'; +import { pollFor } from './TestUtils'; const APP = 'http://127.0.0.1:3000/test'; @@ -15,12 +15,9 @@ const width = 800; const height = 600; describe('CharWidth Integration Tests', function(): void { - this.timeout(20000); - before(async function(): Promise { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; @@ -41,37 +38,37 @@ describe('CharWidth Integration Tests', function(): void { it('ASCII chars', async function(): Promise { const input = 'This is just ASCII text.#'; await page.evaluate(`window.term.write('${input}')`); - assert.equal(25, await sumWidths(0, 1, '#')); + await pollFor(page, () => sumWidths(0, 1, '#'), 25); }); it('combining chars', async function(): Promise { const input = 'e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301#'; await page.evaluate(`window.term.write('${input}')`); - assert.equal(10, await sumWidths(0, 1, '#')); + await pollFor(page, () => sumWidths(0, 1, '#'), 10); }); it('surrogate chars', async function(): Promise { const input = '𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞#'; await page.evaluate(`window.term.write('${input}')`); - assert.equal(28, await sumWidths(0, 1, '#')); + await pollFor(page, () => sumWidths(0, 1, '#'), 28); }); it('surrogate combining chars', async function(): Promise { const input = '𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301#'; await page.evaluate(`window.term.write('${input}')`); - assert.equal(12, await sumWidths(0, 1, '#')); + await pollFor(page, () => sumWidths(0, 1, '#'), 12); }); it('fullwidth chars', async function(): Promise { const input = '1234567890#'; await page.evaluate(`window.term.write('${input}')`); - assert.equal(21, await sumWidths(0, 1, '#')); + await pollFor(page, () => sumWidths(0, 1, '#'), 21); }); it('fullwidth chars offset 1', async function(): Promise { const input = 'a1234567890#'; await page.evaluate(`window.term.write('${input}')`); - assert.equal(22, await sumWidths(0, 1, '#')); + await pollFor(page, () => sumWidths(0, 1, '#'), 22); }); // TODO: multiline tests once #1685 is resolved diff --git a/test/api/TestUtils.ts b/test/api/TestUtils.ts new file mode 100644 index 000000000..ff1c661e6 --- /dev/null +++ b/test/api/TestUtils.ts @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import * as puppeteer from 'puppeteer'; + +export async function pollFor(page: puppeteer.Page, evalOrFn: string | (() => Promise), val: T, preFn?: () => Promise): Promise { + if (preFn) { + await preFn(); + } + const result = typeof evalOrFn === 'string' ? await page.evaluate(evalOrFn) : await evalOrFn(); + let equal = false; + if (typeof result === 'object') { + equal = Object.keys(result).every(e => result[e] === (val as any)[e]); + } else { + equal = result === val; + } + if (!equal) { + return new Promise(r => { + setTimeout(() => r(pollFor(page, evalOrFn, val, preFn)), 10); + }); + } +} From 2aaa997d395d97bd7ac9a6633ced85eae6fa19c0 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 14:15:16 -0800 Subject: [PATCH 029/268] Fix timing issues in input handler --- test/api/InputHandler.api.ts | 86 ++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/test/api/InputHandler.api.ts b/test/api/InputHandler.api.ts index 2070dfe61..4bc2c07d1 100644 --- a/test/api/InputHandler.api.ts +++ b/test/api/InputHandler.api.ts @@ -6,6 +6,7 @@ import * as puppeteer from 'puppeteer'; import { assert } from 'chai'; import { ITerminalOptions } from 'xterm'; +import { pollFor } from './TestUtils'; const APP = 'http://127.0.0.1:3000/test'; @@ -15,8 +16,6 @@ const width = 800; const height = 600; describe('InputHandler Integration Tests', function(): void { - this.timeout(20000); - before(async function(): Promise { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, @@ -45,7 +44,7 @@ describe('InputHandler Integration Tests', function(): void { // Explicit window.term.write('bar\\x1b[3D\\x1b[4@') `); - assert.deepEqual(await getLinesAsArray(2), [' foo', ' bar']); + await pollFor(page, () => getLinesAsArray(2), [' foo', ' bar']); }); it('CUU: Cursor Up Ps Times (default = 1) - CSI Ps A', async function(): Promise { @@ -55,7 +54,7 @@ describe('InputHandler Integration Tests', function(): void { // Explicit window.term.write('\x1b[2Ab') `); - assert.deepEqual(await getLinesAsArray(4), ['', ' b', '', 'a']); + await pollFor(page, () => getLinesAsArray(4), ['', ' b', '', 'a']); }); it('CUD: Cursor Down Ps Times (default = 1) - CSI Ps B', async function(): Promise { @@ -65,7 +64,7 @@ describe('InputHandler Integration Tests', function(): void { // Explicit window.term.write('\x1b[2Bb') `); - assert.deepEqual(await getLinesAsArray(4), ['', 'a', '', ' b']); + await pollFor(page, () => getLinesAsArray(4), ['', 'a', '', ' b']); }); it('CUF: Cursor Forward Ps Times (default = 1) - CSI Ps C', async function(): Promise { @@ -75,7 +74,7 @@ describe('InputHandler Integration Tests', function(): void { // Explicit window.term.write('\x1b[2Cb') `); - assert.deepEqual(await getLinesAsArray(1), [' a b']); + await pollFor(page, () => getLinesAsArray(1), [' a b']); }); it('CUB: Cursor Backward Ps Times (default = 1) - CSI Ps D', async function(): Promise { @@ -85,7 +84,7 @@ describe('InputHandler Integration Tests', function(): void { // Explicit window.term.write('\x1b[2Db') `); - assert.deepEqual(await getLinesAsArray(1), ['fba']); + await pollFor(page, () => getLinesAsArray(1), ['fba']); }); it('CNL: Cursor Next Line Ps Times (default = 1) - CSI Ps E', async function(): Promise { @@ -95,7 +94,7 @@ describe('InputHandler Integration Tests', function(): void { // Explicit window.term.write('\x1b[2Eb') `); - assert.deepEqual(await getLinesAsArray(4), ['', 'a', '', 'b']); + await pollFor(page, () => getLinesAsArray(4), ['', 'a', '', 'b']); }); it('CPL: Cursor Preceding Line Ps Times (default = 1) - CSI Ps F', async function(): Promise { @@ -105,7 +104,7 @@ describe('InputHandler Integration Tests', function(): void { // Explicit window.term.write('\x1b[2Fb') `); - assert.deepEqual(await getLinesAsArray(5), ['', 'b', '', 'a', '']); + await pollFor(page, () => getLinesAsArray(5), ['', 'b', '', 'a', '']); }); it('CHA: Cursor Character Absolute [column] (default = [row,1]) - CSI Ps G', async function(): Promise { @@ -115,7 +114,7 @@ describe('InputHandler Integration Tests', function(): void { // Explicit window.term.write('\x1b[10Gb') `); - assert.deepEqual(await getLinesAsArray(1), ['aoo b']); + await pollFor(page, () => getLinesAsArray(1), ['aoo b']); }); it('CUP: Cursor Position [row;column] (default = [1,1]) - CSI Ps ; Ps H', async function(): Promise { @@ -125,7 +124,7 @@ describe('InputHandler Integration Tests', function(): void { // Explicit window.term.write('\x1b[3;3Hb') `); - assert.deepEqual(await getLinesAsArray(3), ['aoo', '', ' b']); + await pollFor(page, () => getLinesAsArray(3), ['aoo', '', ' b']); }); it('CHT: Cursor Forward Tabulation Ps tab stops (default = 1) - CSI Ps I', async function(): Promise { @@ -135,7 +134,7 @@ describe('InputHandler Integration Tests', function(): void { // Explicit window.term.write('\\n\\r\x1b[2Ib') `); - assert.deepEqual(await getLinesAsArray(2), [' a', ' b']); + await pollFor(page, () => getLinesAsArray(2), [' a', ' b']); }); it('ED: Erase in Display, VT100 - CSI Ps J', async function(): Promise { @@ -145,26 +144,26 @@ describe('InputHandler Integration Tests', function(): void { window.term.resize(5, 5); window.term.write('${fixture}\x1b[J') `); - assert.deepEqual(await getLinesAsArray(3), ['abc', 'd', '']); + await pollFor(page, () => getLinesAsArray(3), ['abc', 'd', '']); await page.evaluate(` // 0: Erase Below window.term.reset() window.term.write('${fixture}\x1b[0J') `); - assert.deepEqual(await getLinesAsArray(3), ['abc', 'd', '']); + await pollFor(page, () => getLinesAsArray(3), ['abc', 'd', '']); await page.evaluate(` // 1: Erase Above window.term.reset() window.term.write('${fixture}\x1b[1J') `); - assert.deepEqual(await getLinesAsArray(3), ['', ' f', 'ghi']); + await pollFor(page, () => getLinesAsArray(3), ['', ' f', 'ghi']); await page.evaluate(` // 2: Erase Saved Lines (scrollback) window.term.reset() window.term.write('1\\n2\\n3\\n4\\n5${fixture}\x1b[3J') `); - assert.equal(await page.evaluate(`window.term.buffer.length`), 5); - assert.deepEqual(await getLinesAsArray(5), [' 4', ' 5', 'abc', 'def', 'ghi']); + await pollFor(page, () => page.evaluate(`window.term.buffer.length`), 5); + await pollFor(page, () => getLinesAsArray(5), [' 4', ' 5', 'abc', 'def', 'ghi']); }); it('DECSED: Erase in Display, VT220 - CSI ? Ps J', async function(): Promise { @@ -174,26 +173,26 @@ describe('InputHandler Integration Tests', function(): void { window.term.resize(5, 5); window.term.write('${fixture}\x1b[?J') `); - assert.deepEqual(await getLinesAsArray(3), ['abc', 'd', '']); + await pollFor(page, () => getLinesAsArray(3), ['abc', 'd', '']); await page.evaluate(` // 0: Erase Below window.term.reset() window.term.write('${fixture}\x1b[?0J') `); - assert.deepEqual(await getLinesAsArray(3), ['abc', 'd', '']); + await pollFor(page, () => getLinesAsArray(3), ['abc', 'd', '']); await page.evaluate(` // 1: Erase Above window.term.reset() window.term.write('${fixture}\x1b[?1J') `); - assert.deepEqual(await getLinesAsArray(3), ['', ' f', 'ghi']); + await pollFor(page, () => getLinesAsArray(3), ['', ' f', 'ghi']); await page.evaluate(` // 2: Erase Saved Lines (scrollback) window.term.reset() window.term.write('1\\n2\\n3\\n4\\n5${fixture}\x1b[?3J') `); - assert.equal(await page.evaluate(`window.term.buffer.length`), 5); - assert.deepEqual(await getLinesAsArray(5), [' 4', ' 5', 'abc', 'def', 'ghi']); + await pollFor(page, () => page.evaluate(`window.term.buffer.length`), 5); + await pollFor(page, () => getLinesAsArray(5), [' 4', ' 5', 'abc', 'def', 'ghi']); }); it('IL: Insert Ps Line(s) (default = 1) - CSI Ps L', async function(): Promise { @@ -203,7 +202,7 @@ describe('InputHandler Integration Tests', function(): void { // Explicit window.term.write('\x1b[2Lb') `); - assert.deepEqual(await getLinesAsArray(4), ['b', '', 'a', 'foo']); + await pollFor(page, () => getLinesAsArray(4), ['b', '', 'a', 'foo']); }); it('DL: Delete Ps Line(s) (default = 1) - CSI Ps M', async function(): Promise { @@ -213,7 +212,7 @@ describe('InputHandler Integration Tests', function(): void { // Explicit window.term.write('\x1b[1Ed\\ne\\nf\x1b[2F\x1b[2M') `); - assert.deepEqual(await getLinesAsArray(5), [' b', ' f', '', '', '']); + await pollFor(page, () => getLinesAsArray(5), [' b', ' f', '', '', '']); }); it('DCH: Delete Ps Character(s) (default = 1) - CSI Ps P', async function(): Promise { @@ -223,7 +222,7 @@ describe('InputHandler Integration Tests', function(): void { // Explicit window.term.write('\\n\\rdef\x1b[2;1H\x1b[2P') `); - assert.deepEqual(await getLinesAsArray(2), ['bc', 'f']); + await pollFor(page, () => getLinesAsArray(2), ['bc', 'f']); }); describe('DSR: Device Status Report', () => { @@ -232,31 +231,31 @@ describe('InputHandler Integration Tests', function(): void { window.term.onData(e => window.result = e); window.term.write('\\x1b[5n'); `); - assert.equal(await page.evaluate(`window.result`), '\x1b[0n'); + await pollFor(page, () => page.evaluate(`window.result`), '\x1b[0n'); }); it('Report Cursor Position (CPR) - CSI 6 n', async function(): Promise { await page.evaluate(`window.term.write('\\n\\nfoo')`); - assert.deepEqual(await page.evaluate(` + await pollFor(page, () => page.evaluate(` [window.term.buffer.cursorY, window.term.buffer.cursorX] `), [2, 3]); await page.evaluate(` window.term.onData(e => window.result = e); window.term.write('\\x1b[6n'); `); - assert.equal(await page.evaluate(`window.result`), '\x1b[3;4R'); + await pollFor(page, () => page.evaluate(`window.result`), '\x1b[3;4R'); }); it('Report Cursor Position (DECXCPR) - CSI ? 6 n', async function(): Promise { await page.evaluate(`window.term.write('\\n\\nfoo')`); - assert.deepEqual(await page.evaluate(` + await pollFor(page, () => page.evaluate(` [window.term.buffer.cursorY, window.term.buffer.cursorX] `), [2, 3]); await page.evaluate(` window.term.onData(e => window.result = e); window.term.write('\\x1b[?6n'); `); - assert.equal(await page.evaluate(`window.result`), '\x1b[?3;4R'); + await pollFor(page, () => page.evaluate(`window.result`), '\x1b[?3;4R'); }); }); @@ -277,22 +276,23 @@ describe('InputHandler Integration Tests', function(): void { await page.mouse.up(); // Clear selection await page.mouse.click((coords.left + coords.right) / 2, (coords.top + coords.bottom) / 2); - assert.equal(await page.evaluate(`window.term.getSelection().length`), 0); + await pollFor(page, () => page.evaluate(`window.term.getSelection().length`), 0); // Enable mouse events await page.evaluate(`window.term.write('\x1b[?1003h')`); // Click and drag and ensure there is no selection await page.mouse.click((coords.left + coords.right) / 2, (coords.top + coords.bottom) / 2); await page.mouse.down(); await page.mouse.move((coords.left + coords.right) / 2, (coords.top + coords.bottom) / 4); - assert.equal(await page.evaluate(`window.term.getSelection().length`), 0, 'mouse events are on so there should be no selection'); + // mouse events are on so there should be no selection + await pollFor(page, () => page.evaluate(`window.term.getSelection().length`), 0); await page.mouse.up(); }); it('Pm = 2004, Set bracketed paste mode', async function(): Promise { - assert.equal(await simulatePaste('foo'), 'foo'); + await pollFor(page, () => simulatePaste('foo'), 'foo'); await page.evaluate(`window.term.write('\x1b[?2004h')`); - assert.equal(await simulatePaste('bar'), '\x1b[200~bar\x1b[201~'); + await pollFor(page, () => simulatePaste('bar'), '\x1b[200~bar\x1b[201~'); await page.evaluate(`window.term.write('\x1b[?2004l')`); - assert.equal(await simulatePaste('baz'), 'baz'); + await pollFor(page, () => simulatePaste('baz'), 'baz'); }); }); }); @@ -309,32 +309,32 @@ describe('InputHandler Integration Tests', function(): void { window.term.writeln(''); window.term.write('#\x1b[5b'); `); - assert.deepEqual(await getLinesAsArray(4), ['##', '##', '##', '######']); - assert.deepEqual(await getCursor(), {col: 6, row: 3}); + await pollFor(page, () => getLinesAsArray(4), ['##', '##', '##', '######']); + await pollFor(page, () => getCursor(), {col: 6, row: 3}); // should not repeat on fullwidth chars await page.evaluate(` window.term.reset(); window.term.write('¥\x1b[10b'); `); - assert.deepEqual(await getLinesAsArray(1), ['¥']); + await pollFor(page, () => getLinesAsArray(1), ['¥']); // should repeat only base char of combining await page.evaluate(` window.term.reset(); window.term.write('e\u0301\x1b[5b'); `); - assert.deepEqual(await getLinesAsArray(1), ['e\u0301eeeee']); + await pollFor(page, () => getLinesAsArray(1), ['e\u0301eeeee']); // should wrap correctly await page.evaluate(` window.term.reset(); window.term.write('#\x1b[15b'); `); - assert.deepEqual(await getLinesAsArray(2), ['##########', '######']); + await pollFor(page, () => getLinesAsArray(2), ['##########', '######']); await page.evaluate(` window.term.reset(); window.term.write('\x1b[?7l'); // disable wrap around window.term.write('#\x1b[15b'); `); - assert.deepEqual(await getLinesAsArray(2), ['##########', '']); + await pollFor(page, () => getLinesAsArray(2), ['##########', '']); // any successful sequence should reset REP await page.evaluate(` window.term.reset(); @@ -344,7 +344,7 @@ describe('InputHandler Integration Tests', function(): void { window.term.writeln(''); window.term.write('abcdefg\x1b[3D\x1b[10b#\x1b[3b'); `); - assert.deepEqual(await getLinesAsArray(3), ['#', ' #', 'abcd####']); + await pollFor(page, () => getLinesAsArray(3), ['#', ' #', 'abcd####']); }); }); @@ -360,7 +360,7 @@ describe('InputHandler Integration Tests', function(): void { window.term.resize(10, 4); window.term.write('\\x1b[?47l\\x1b8'); `); - assert.deepEqual(await getCursor(), {col: 1, row: 3}); + await pollFor(page, () => getCursor(), {col: 1, row: 3}); }); }); }); From 17677568f9ab3402355ba94870ab1defc3bbede6 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 14:16:57 -0800 Subject: [PATCH 030/268] Fix timing issues in mouse tracking tests --- test/api/MouseTracking.api.ts | 249 +++++++++++++++++----------------- 1 file changed, 122 insertions(+), 127 deletions(-) diff --git a/test/api/MouseTracking.api.ts b/test/api/MouseTracking.api.ts index a993c6a1b..910763cde 100644 --- a/test/api/MouseTracking.api.ts +++ b/test/api/MouseTracking.api.ts @@ -4,8 +4,8 @@ */ import * as puppeteer from 'puppeteer'; -import { assert } from 'chai'; import { ITerminalOptions } from 'xterm'; +import { pollFor } from './TestUtils'; const APP = 'http://127.0.0.1:3000/test'; @@ -199,21 +199,16 @@ function parseReport(encoding: string, msg: number[]): {state: any; row: number; * Mouse tracking tests. */ describe('Mouse Tracking Tests', function(): void { - this.timeout(60000); - before(async function(): Promise { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; await page.setViewport({ width, height }); }); - after(() => { - browser.close(); - }); + after(async () => browser.close()); beforeEach(async () => { await page.goto(APP); @@ -246,18 +241,18 @@ describe('Mouse Tracking Tests', function(): void { // test at 0,0 await mouseDown('left'); - assert.deepEqual(await getReports(encoding), [{col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); // mouseup should not report await mouseUp('left'); - assert.deepEqual(await getReports(encoding), []); + await pollFor(page, () => getReports(encoding), []); // mousemove should not report await mouseMove(50, 10); - assert.deepEqual(await getReports(encoding), []); + await pollFor(page, () => getReports(encoding), []); await mouseDown('left'); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [{col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); // test at max rows/cols // bug: we are capped at col 95 currently @@ -265,7 +260,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseMove(cols - 1, rows - 1); await mouseDown('left'); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [{col: 95, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 95, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); // button press/move/release tests // left button @@ -274,7 +269,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseDown('left'); await mouseMove(44, 24); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); // middle button // bug: default action not cancelled (adds data to getReports from clipboard under X11) // await mouseMove(43, 24); @@ -282,7 +277,7 @@ describe('Mouse Tracking Tests', function(): void { // await mouseDown('middle'); // await mouseMove(44, 24); // await mouseUp('middle'); - // assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); + // await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); // right button // bug: default action not cancelled (popup shown) await mouseMove(43, 24); @@ -290,15 +285,15 @@ describe('Mouse Tracking Tests', function(): void { await mouseDown('right'); await mouseMove(44, 24); await mouseUp('right'); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}]); // wheel await mouseMove(43, 24); await getReports(encoding); // clear reports await wheelUp(); - assert.deepEqual(await getReports(encoding), []); + await pollFor(page, () => getReports(encoding), []); await wheelDown(); - assert.deepEqual(await getReports(encoding), []); + await pollFor(page, () => getReports(encoding), []); // modifiers // CTRL @@ -310,7 +305,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('left'); await wheelDown(); await page.keyboard.up('Control'); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); // ALT @@ -322,7 +317,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('left'); await wheelDown(); await page.keyboard.up('Alt'); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); // SHIFT // note: caught by selection manager @@ -337,9 +332,9 @@ describe('Mouse Tracking Tests', function(): void { await wheelDown(); await page.keyboard.up('Shift'); if (noShift) { - assert.deepEqual(await getReports(encoding), []); + await pollFor(page, () => getReports(encoding), []); } else { - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); } @@ -358,7 +353,7 @@ describe('Mouse Tracking Tests', function(): void { await page.keyboard.up('Control'); await page.keyboard.up('Alt'); // await page.keyboard.up('Shift'); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); }); it('SGR encoding', async () => { const encoding = 'SGR'; @@ -368,24 +363,24 @@ describe('Mouse Tracking Tests', function(): void { // test at 0,0 await mouseDown('left'); - assert.deepEqual(await getReports(encoding), [{col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); // mouseup should not report await mouseUp('left'); - assert.deepEqual(await getReports(encoding), []); + await pollFor(page, () => getReports(encoding), []); // mousemove should not report await mouseMove(50, 10); - assert.deepEqual(await getReports(encoding), []); + await pollFor(page, () => getReports(encoding), []); await mouseDown('left'); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [{col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); // test at max rows/cols await mouseMove(cols - 1, rows - 1); await mouseDown('left'); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [{col: cols, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: cols, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); // button press/move/release tests // left button @@ -394,7 +389,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseDown('left'); await mouseMove(44, 24); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); // middle button // bug: default action not cancelled (adds data to getReports from clipboard under X11) // await mouseMove(43, 24); @@ -402,7 +397,7 @@ describe('Mouse Tracking Tests', function(): void { // await mouseDown('middle'); // await mouseMove(44, 24); // await mouseUp('middle'); - // assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); + // await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); // right button // bug: default action not cancelled (popup shown) await mouseMove(43, 24); @@ -410,15 +405,15 @@ describe('Mouse Tracking Tests', function(): void { await mouseDown('right'); await mouseMove(44, 24); await mouseUp('right'); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}]); // wheel await mouseMove(43, 24); await getReports(encoding); // clear reports await wheelUp(); - assert.deepEqual(await getReports(encoding), []); + await pollFor(page, () => getReports(encoding), []); await wheelDown(); - assert.deepEqual(await getReports(encoding), []); + await pollFor(page, () => getReports(encoding), []); // modifiers // CTRL @@ -430,7 +425,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('left'); await wheelDown(); await page.keyboard.up('Control'); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); // ALT @@ -442,7 +437,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('left'); await wheelDown(); await page.keyboard.up('Alt'); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); // SHIFT // note: caught by selection manager @@ -455,9 +450,9 @@ describe('Mouse Tracking Tests', function(): void { await wheelDown(); await page.keyboard.up('Shift'); if (noShift) { - assert.deepEqual(await getReports(encoding), []); + await pollFor(page, () => getReports(encoding), []); } else { - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); } @@ -476,7 +471,7 @@ describe('Mouse Tracking Tests', function(): void { await page.keyboard.up('Control'); await page.keyboard.up('Alt'); // await page.keyboard.up('Shift'); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); }); }); describe('DECSET 1000 (VT200 mouse)', () => { @@ -495,22 +490,22 @@ describe('Mouse Tracking Tests', function(): void { // test at 0,0 await mouseDown('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); // mouseup should report, encoding cannot report released button await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 1, row: 1, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} ]); // mousemove should not report await mouseMove(50, 10); - assert.deepEqual(await getReports(encoding), []); + await pollFor(page, () => getReports(encoding), []); await mouseDown('left'); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: 51, row: 11, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} ]); @@ -521,7 +516,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseMove(cols - 1, rows - 1); await mouseDown('left'); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 95, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: 95, row: rows, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} ]); @@ -533,7 +528,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseDown('left'); await mouseMove(44, 24); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} ]); @@ -544,7 +539,7 @@ describe('Mouse Tracking Tests', function(): void { // await mouseDown('middle'); // await mouseMove(44, 24); // await mouseUp('middle'); - // assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); + // await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); // right button // bug: default action not cancelled (popup shown) await mouseMove(43, 24); @@ -552,7 +547,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseDown('right'); await mouseMove(44, 24); await mouseUp('right'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} ]); @@ -561,9 +556,9 @@ describe('Mouse Tracking Tests', function(): void { await mouseMove(43, 24); await getReports(encoding); // clear reports await wheelUp(); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); await wheelDown(); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); // modifiers // CTRL @@ -575,7 +570,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('left'); await wheelDown(); await page.keyboard.up('Control'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: false}}} @@ -590,7 +585,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('left'); await wheelDown(); await page.keyboard.up('Alt'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: true}}} @@ -608,11 +603,11 @@ describe('Mouse Tracking Tests', function(): void { await wheelDown(); await page.keyboard.up('Shift'); if (noShift) { - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} ]); } else { - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: true, meta: false}}}, {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: true, meta: false}}}, {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} @@ -633,7 +628,7 @@ describe('Mouse Tracking Tests', function(): void { await page.keyboard.up('Control'); await page.keyboard.up('Alt'); // await page.keyboard.up('Shift'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: true}}} @@ -647,22 +642,22 @@ describe('Mouse Tracking Tests', function(): void { // test at 0,0 await mouseDown('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); // mouseup should report await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 1, row: 1, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); // mousemove should not report await mouseMove(50, 10); - assert.deepEqual(await getReports(encoding), []); + await pollFor(page, () => getReports(encoding), []); await mouseDown('left'); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: 51, row: 11, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); @@ -671,7 +666,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseMove(cols - 1, rows - 1); await mouseDown('left'); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: cols, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: cols, row: rows, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); @@ -683,7 +678,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseDown('left'); await mouseMove(44, 24); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); @@ -694,7 +689,7 @@ describe('Mouse Tracking Tests', function(): void { // await mouseDown('middle'); // await mouseMove(44, 24); // await mouseUp('middle'); - // assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); + // await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); // right button // bug: default action not cancelled (popup shown) await mouseMove(43, 24); @@ -702,7 +697,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseDown('right'); await mouseMove(44, 24); await mouseUp('right'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'release', button: 'right', modifier: {control: false, shift: false, meta: false}}} ]); @@ -711,9 +706,9 @@ describe('Mouse Tracking Tests', function(): void { await mouseMove(43, 24); await getReports(encoding); // clear reports await wheelUp(); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); await wheelDown(); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); // modifiers // CTRL @@ -725,7 +720,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('left'); await wheelDown(); await page.keyboard.up('Control'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: true, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: false}}} @@ -740,7 +735,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('left'); await wheelDown(); await page.keyboard.up('Alt'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: true}}} @@ -757,11 +752,11 @@ describe('Mouse Tracking Tests', function(): void { await wheelDown(); await page.keyboard.up('Shift'); if (noShift) { - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} ]); } else { - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: true, meta: false}}}, {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: true, meta: false}}}, {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} @@ -782,7 +777,7 @@ describe('Mouse Tracking Tests', function(): void { await page.keyboard.up('Control'); await page.keyboard.up('Alt'); // await page.keyboard.up('Shift'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: true, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: true}}} @@ -805,22 +800,22 @@ describe('Mouse Tracking Tests', function(): void { // test at 0,0 await mouseDown('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); // mouseup should report, encoding cannot report released button await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 1, row: 1, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} ]); // mousemove should not report await mouseMove(50, 10); - assert.deepEqual(await getReports(encoding), []); + await pollFor(page, () => getReports(encoding), []); await mouseDown('left'); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: 51, row: 11, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} ]); @@ -831,7 +826,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseMove(cols - 1, rows - 1); await mouseDown('left'); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 95, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: 95, row: rows, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} ]); @@ -843,7 +838,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseDown('left'); await mouseMove(44, 24); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} @@ -855,7 +850,7 @@ describe('Mouse Tracking Tests', function(): void { // await mouseDown('middle'); // await mouseMove(44, 24); // await mouseUp('middle'); - // assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); + // await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); // right button // bug: default action not cancelled (popup shown) await mouseMove(43, 24); @@ -863,7 +858,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseDown('right'); await mouseMove(44, 24); await mouseUp('right'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'move', button: 'right', modifier: {control: false, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} @@ -873,9 +868,9 @@ describe('Mouse Tracking Tests', function(): void { await mouseMove(43, 24); await getReports(encoding); // clear reports await wheelUp(); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); await wheelDown(); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); // modifiers // CTRL @@ -887,7 +882,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('left'); await wheelDown(); await page.keyboard.up('Control'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: false}}}, @@ -903,7 +898,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('left'); await wheelDown(); await page.keyboard.up('Alt'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: true}}}, @@ -921,11 +916,11 @@ describe('Mouse Tracking Tests', function(): void { await wheelDown(); await page.keyboard.up('Shift'); if (noShift) { - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} ]); } else { - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: true, meta: false}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: true, meta: false}}}, {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: true, meta: false}}}, @@ -947,7 +942,7 @@ describe('Mouse Tracking Tests', function(): void { await page.keyboard.up('Control'); await page.keyboard.up('Alt'); // await page.keyboard.up('Shift'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: true}}}, @@ -963,22 +958,22 @@ describe('Mouse Tracking Tests', function(): void { // test at 0,0 // bug: release is fired immediately await mouseDown('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); // mouseup should report, encoding cannot report released button await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 1, row: 1, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); // mousemove should not report await mouseMove(50, 10); - assert.deepEqual(await getReports(encoding), []); + await pollFor(page, () => getReports(encoding), []); await mouseDown('left'); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: 51, row: 11, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); @@ -987,7 +982,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseMove(cols - 1, rows - 1); await mouseDown('left'); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: cols, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: cols, row: rows, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); @@ -999,7 +994,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseDown('left'); await mouseMove(44, 24); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} @@ -1011,7 +1006,7 @@ describe('Mouse Tracking Tests', function(): void { // await mouseDown('middle'); // await mouseMove(44, 24); // await mouseUp('middle'); - // assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); + // await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); // right button // bug: default action not cancelled (popup shown) await mouseMove(43, 24); @@ -1019,7 +1014,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseDown('right'); await mouseMove(44, 24); await mouseUp('right'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'move', button: 'right', modifier: {control: false, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'release', button: 'right', modifier: {control: false, shift: false, meta: false}}} @@ -1029,9 +1024,9 @@ describe('Mouse Tracking Tests', function(): void { await mouseMove(43, 24); await getReports(encoding); // clear reports await wheelUp(); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); await wheelDown(); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); // modifiers // CTRL @@ -1043,7 +1038,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('left'); await wheelDown(); await page.keyboard.up('Control'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: true, shift: false, meta: false}}}, @@ -1059,7 +1054,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('left'); await wheelDown(); await page.keyboard.up('Alt'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: true}}}, @@ -1077,11 +1072,11 @@ describe('Mouse Tracking Tests', function(): void { await wheelDown(); await page.keyboard.up('Shift'); if (noShift) { - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} ]); } else { - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: true, meta: false}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: true, meta: false}}}, {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: true, meta: false}}}, @@ -1103,7 +1098,7 @@ describe('Mouse Tracking Tests', function(): void { await page.keyboard.up('Control'); await page.keyboard.up('Alt'); // await page.keyboard.up('Shift'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: true, shift: false, meta: true}}}, @@ -1124,24 +1119,24 @@ describe('Mouse Tracking Tests', function(): void { // test at 0,0 await mouseDown('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); // mouseup should report, encoding cannot report released button await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 1, row: 1, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} ]); // mousemove should report await mouseMove(50, 10); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 51, row: 11, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: false}}} ]); await mouseDown('left'); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: 51, row: 11, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} ]); @@ -1152,7 +1147,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseMove(cols - 1, rows - 1); await mouseDown('left'); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 95, row: rows, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: false}}}, {col: 95, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: 95, row: rows, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} @@ -1164,7 +1159,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseDown('left'); await mouseMove(44, 24); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: false}}}, {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: false}}}, @@ -1177,14 +1172,14 @@ describe('Mouse Tracking Tests', function(): void { // await mouseDown('middle'); // await mouseMove(44, 24); // await mouseUp('middle'); - // assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); + // await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); // right button // bug: default action not cancelled (popup shown) await mouseMove(43, 24); await mouseDown('right'); await mouseMove(44, 24); await mouseUp('right'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: false}}}, {col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'move', button: 'right', modifier: {control: false, shift: false, meta: false}}}, @@ -1195,9 +1190,9 @@ describe('Mouse Tracking Tests', function(): void { await mouseMove(43, 24); await getReports(encoding); // clear reports await wheelUp(); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); await wheelDown(); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); // modifiers // CTRL @@ -1208,7 +1203,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('left'); await wheelDown(); await page.keyboard.up('Control'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: true, shift: false, meta: false}}}, {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: false}}}, @@ -1224,7 +1219,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('left'); await wheelDown(); await page.keyboard.up('Alt'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: true}}}, {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: true}}}, @@ -1242,12 +1237,12 @@ describe('Mouse Tracking Tests', function(): void { await wheelDown(); await page.keyboard.up('Shift'); if (noShift) { - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: true, meta: false}}}, {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} ]); } else { - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: true, meta: false}}}, {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: true, meta: false}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: true, meta: false}}}, @@ -1269,7 +1264,7 @@ describe('Mouse Tracking Tests', function(): void { await page.keyboard.up('Control'); await page.keyboard.up('Alt'); // await page.keyboard.up('Shift'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: true, shift: false, meta: true}}}, {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: true}}}, @@ -1285,24 +1280,24 @@ describe('Mouse Tracking Tests', function(): void { // test at 0,0 await mouseDown('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); // mouseup should report, encoding cannot report released button await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 1, row: 1, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); // mousemove should report await mouseMove(50, 10); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 51, row: 11, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: false}}} ]); await mouseDown('left'); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: 51, row: 11, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); @@ -1313,7 +1308,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseMove(cols - 1, rows - 1); await mouseDown('left'); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: cols, row: rows, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: false}}}, {col: cols, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: cols, row: rows, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} @@ -1325,7 +1320,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseDown('left'); await mouseMove(44, 24); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: false}}}, {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: false}}}, @@ -1338,14 +1333,14 @@ describe('Mouse Tracking Tests', function(): void { // await mouseDown('middle'); // await mouseMove(44, 24); // await mouseUp('middle'); - // assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); + // await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); // right button // bug: default action not cancelled (popup shown) await mouseMove(43, 24); await mouseDown('right'); await mouseMove(44, 24); await mouseUp('right'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: false}}}, {col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'move', button: 'right', modifier: {control: false, shift: false, meta: false}}}, @@ -1356,9 +1351,9 @@ describe('Mouse Tracking Tests', function(): void { await mouseMove(43, 24); await getReports(encoding); // clear reports await wheelUp(); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); await wheelDown(); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); // modifiers // CTRL @@ -1369,7 +1364,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('left'); await wheelDown(); await page.keyboard.up('Control'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: true, shift: false, meta: false}}}, {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: false}}}, @@ -1385,7 +1380,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('left'); await wheelDown(); await page.keyboard.up('Alt'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: true}}}, {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: true}}}, @@ -1403,12 +1398,12 @@ describe('Mouse Tracking Tests', function(): void { await wheelDown(); await page.keyboard.up('Shift'); if (noShift) { - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: true, meta: false}}}, {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} ]); } else { - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: true, meta: false}}}, {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: true, meta: false}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: true, meta: false}}}, @@ -1430,7 +1425,7 @@ describe('Mouse Tracking Tests', function(): void { await page.keyboard.up('Control'); await page.keyboard.up('Alt'); // await page.keyboard.up('Shift'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: true, shift: false, meta: true}}}, {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: true}}}, From 34e703e4725b06179ec0d69d8258513f5e674ad3 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 17:29:32 -0800 Subject: [PATCH 031/268] Fix most of Terminal.api timing issues --- package.json | 2 + test/api/MouseTracking.api.ts | 4 +- test/api/Parser.api.ts | 7 +- test/api/Terminal.api.ts | 215 +++++++++++++++++----------------- test/api/TestUtils.ts | 17 +-- yarn.lock | 36 +++++- 6 files changed, 156 insertions(+), 125 deletions(-) diff --git a/package.json b/package.json index 7ed717419..c37594afc 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ }, "devDependencies": { "@types/chai": "^3.4.34", + "@types/deep-equal": "^1.0.1", "@types/glob": "^5.0.35", "@types/jsdom": "11.0.1", "@types/mocha": "^2.2.33", @@ -39,6 +40,7 @@ "@types/webpack": "^4.4.11", "@types/ws": "^6.0.1", "chai": "3.5.0", + "deep-equal": "^1.1.0", "express": "^4.17.1", "express-ws": "^4.0.0", "glob": "^7.0.5", diff --git a/test/api/MouseTracking.api.ts b/test/api/MouseTracking.api.ts index 910763cde..d10a5b951 100644 --- a/test/api/MouseTracking.api.ts +++ b/test/api/MouseTracking.api.ts @@ -198,8 +198,8 @@ function parseReport(encoding: string, msg: number[]): {state: any; row: number; /** * Mouse tracking tests. */ -describe('Mouse Tracking Tests', function(): void { - before(async function(): Promise { +describe('Mouse Tracking Tests', () => { + before(async () => { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, args: [`--window-size=${width},${height}`, `--no-sandbox`] diff --git a/test/api/Parser.api.ts b/test/api/Parser.api.ts index 4dcda70c7..9b0fb66bf 100644 --- a/test/api/Parser.api.ts +++ b/test/api/Parser.api.ts @@ -15,12 +15,9 @@ const width = 800; const height = 600; describe('Parser Integration Tests', function(): void { - this.timeout(20000); - before(async function(): Promise { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; @@ -29,9 +26,7 @@ describe('Parser Integration Tests', function(): void { await openTerminal(); }); - after(() => { - browser.close(); - }); + after(async () => browser.close()); describe('addCsiHandler', () => { it('should call custom CSI handler with js array params', async () => { diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index 07dc372c7..6d7a4d202 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -6,6 +6,7 @@ import * as puppeteer from 'puppeteer'; import { assert } from 'chai'; import { ITerminalOptions } from 'xterm'; +import { pollFor, writeSync } from './TestUtils'; const APP = 'http://127.0.0.1:3000/test'; @@ -14,55 +15,47 @@ let page: puppeteer.Page; const width = 800; const height = 600; -describe('API Integration Tests', function(): void { - this.timeout(20000); - - before(async function(): Promise { +describe.only('API Integration Tests', function(): void { + before(async () => { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; await page.setViewport({ width, height }); }); - after(() => { - browser.close(); - }); + after(async () => browser.close()); + beforeEach(async () => page.goto(APP)); - beforeEach(async () => { - await page.goto(APP); - }); - - it('Default options', async function(): Promise { + it('Default options', async () => { await openTerminal(); assert.equal(await page.evaluate(`window.term.cols`), 80); assert.equal(await page.evaluate(`window.term.rows`), 24); }); - it('write', async function(): Promise { + it('write', async () => { await openTerminal(); await page.evaluate(` window.term.write('foo'); window.term.write('bar'); window.term.write('文'); `); - assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foobar文'); + await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foobar文'); }); - it('write with callback', async function(): Promise { + it('write with callback', async () => { await openTerminal(); await page.evaluate(` window.term.write('foo', () => { window.__x = 'a'; }); window.term.write('bar', () => { window.__x += 'b'; }); window.term.write('文', () => { window.__x += 'c'; }); `); - assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foobar文'); - assert.equal(await page.evaluate(`window.__x`), 'abc'); + await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foobar文'); + await pollFor(page, `window.__x`, 'abc'); }); - it('write - bytes (UTF8)', async function(): Promise { + it('write - bytes (UTF8)', async () => { await openTerminal(); await page.evaluate(` // foo @@ -72,10 +65,10 @@ describe('API Integration Tests', function(): void { // 文 window.term.write(new Uint8Array([230, 150, 135])); `); - assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foobar文'); + await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foobar文'); }); - it('write - bytes (UTF8) with callback', async function(): Promise { + it('write - bytes (UTF8) with callback', async () => { await openTerminal(); await page.evaluate(` // foo @@ -85,48 +78,48 @@ describe('API Integration Tests', function(): void { // 文 window.term.write(new Uint8Array([230, 150, 135]), () => { window.__x += 'C'; }); `); - assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foobar文'); - assert.equal(await page.evaluate(`window.__x`), 'ABC'); + await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foobar文'); + await pollFor(page, `window.__x`, 'ABC'); }); - it('writeln', async function(): Promise { + it('writeln', async () => { await openTerminal(); await page.evaluate(` window.term.writeln('foo'); window.term.writeln('bar'); window.term.writeln('文'); `); - assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo'); - assert.equal(await page.evaluate(`window.term.buffer.getLine(1).translateToString(true)`), 'bar'); - assert.equal(await page.evaluate(`window.term.buffer.getLine(2).translateToString(true)`), '文'); + await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foo'); + await pollFor(page, `window.term.buffer.getLine(1).translateToString(true)`, 'bar'); + await pollFor(page, `window.term.buffer.getLine(2).translateToString(true)`, '文'); }); - it('writeln with callback', async function(): Promise { + it('writeln with callback', async () => { await openTerminal(); await page.evaluate(` window.term.writeln('foo', () => { window.__x = '1'; }); window.term.writeln('bar', () => { window.__x += '2'; }); window.term.writeln('文', () => { window.__x += '3'; }); `); - assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo'); - assert.equal(await page.evaluate(`window.term.buffer.getLine(1).translateToString(true)`), 'bar'); - assert.equal(await page.evaluate(`window.term.buffer.getLine(2).translateToString(true)`), '文'); - assert.equal(await page.evaluate(`window.__x`), '123'); + await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foo'); + await pollFor(page, `window.term.buffer.getLine(1).translateToString(true)`, 'bar'); + await pollFor(page, `window.term.buffer.getLine(2).translateToString(true)`, '文'); + await pollFor(page, `window.__x`, '123'); }); - it('writeln - bytes (UTF8)', async function(): Promise { + it('writeln - bytes (UTF8)', async () => { await openTerminal(); await page.evaluate(` window.term.writeln(new Uint8Array([102, 111, 111])); window.term.writeln(new Uint8Array([98, 97, 114])); window.term.writeln(new Uint8Array([230, 150, 135])); `); - assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo'); - assert.equal(await page.evaluate(`window.term.buffer.getLine(1).translateToString(true)`), 'bar'); - assert.equal(await page.evaluate(`window.term.buffer.getLine(2).translateToString(true)`), '文'); + await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foo'); + await pollFor(page, `window.term.buffer.getLine(1).translateToString(true)`, 'bar'); + await pollFor(page, `window.term.buffer.getLine(2).translateToString(true)`, '文'); }); - it('paste', async function(): Promise { + it('paste', async () => { await openTerminal(); await page.evaluate(` window.calls = []; @@ -137,26 +130,28 @@ describe('API Integration Tests', function(): void { window.term.paste('foo'); }); `); - assert.deepEqual(await page.evaluate(`window.calls`), ['foo', '\rfoo\rbar\r', '\x1b[200~foo\x1b[201~']); + await pollFor(page, `window.calls`, ['foo', '\rfoo\rbar\r', '\x1b[200~foo\x1b[201~']); }); - it('clear', async function(): Promise { + it('clear', async () => { await openTerminal({ rows: 5 }); await page.evaluate(` window.term.write('test0'); + window.parsed = 0; for (let i = 1; i < 10; i++) { - window.term.write('\\n\\rtest' + i); + window.term.write('\\n\\rtest' + i, () => window.parsed++); } `); + await pollFor(page, `window.parsed`, 9); await page.evaluate(`window.term.clear()`); - assert.equal(await page.evaluate(`window.term.buffer.length`), '5'); - assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'test9'); + await pollFor(page, `window.term.buffer.length`, 5); + await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'test9'); for (let i = 1; i < 5; i++) { - assert.equal(await page.evaluate(`window.term.buffer.getLine(${i}).translateToString(true)`), ''); + await pollFor(page, `window.term.buffer.getLine(${i}).translateToString(true)`, ''); } }); - it('getOption, setOption', async function(): Promise { + it('getOption, setOption', async () => { await openTerminal(); assert.equal(await page.evaluate(`window.term.getOption('rendererType')`), 'canvas'); await page.evaluate(`window.term.setOption('rendererType', 'dom')`); @@ -164,9 +159,10 @@ describe('API Integration Tests', function(): void { }); describe('renderer', () => { - it('foreground', async function(): Promise { + it('foreground', async () => { await openTerminal({ rendererType: 'dom' }); - await page.evaluate(`window.term.write('\\x1b[30m0\\x1b[31m1\\x1b[32m2\\x1b[33m3\\x1b[34m4\\x1b[35m5\\x1b[36m6\\x1b[37m7')`); + await writeSync(page, '\\x1b[30m0\\x1b[31m1\\x1b[32m2\\x1b[33m3\\x1b[34m4\\x1b[35m5\\x1b[36m6\\x1b[37m7'); + await pollFor(page, `!!document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(1)')`, true); assert.deepEqual(await page.evaluate(` [ document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(1)').className, @@ -188,9 +184,10 @@ describe('API Integration Tests', function(): void { ]); }); - it('background', async function(): Promise { + it('background', async () => { await openTerminal({ rendererType: 'dom' }); - await page.evaluate(`window.term.write('\\x1b[40m0\\x1b[41m1\\x1b[42m2\\x1b[43m3\\x1b[44m4\\x1b[45m5\\x1b[46m6\\x1b[47m7')`); + await writeSync(page, '\\x1b[40m0\\x1b[41m1\\x1b[42m2\\x1b[43m3\\x1b[44m4\\x1b[45m5\\x1b[46m6\\x1b[47m7'); + await pollFor(page, `!!document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(1)')`, true); assert.deepEqual(await page.evaluate(` [ document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(1)').className, @@ -213,7 +210,7 @@ describe('API Integration Tests', function(): void { }); }); - it('selection', async function(): Promise { + it('selection', async () => { await openTerminal({ rows: 5, cols: 5 }); await page.evaluate(`window.term.write('\\n\\nfoo\\n\\n\\rbar\\n\\n\\rbaz')`); assert.equal(await page.evaluate(`window.term.hasSelection()`), false); @@ -233,7 +230,7 @@ describe('API Integration Tests', function(): void { assert.deepEqual(await page.evaluate(`window.term.getSelectionPosition()`), { startColumn: 1, startRow: 2, endColumn: 3, endRow: 2 }); }); - it('focus, blur', async function(): Promise { + it('focus, blur', async () => { await openTerminal(); assert.equal(await page.evaluate(`document.activeElement.className`), ''); await page.evaluate(`window.term.focus()`); @@ -243,7 +240,7 @@ describe('API Integration Tests', function(): void { }); describe('loadAddon', () => { - it('constructor', async function(): Promise { + it('constructor', async () => { await openTerminal({ cols: 5 }); await page.evaluate(` window.cols = 0; @@ -255,7 +252,7 @@ describe('API Integration Tests', function(): void { assert.equal(await page.evaluate(`window.cols`), 5); }); - it('dispose (addon)', async function(): Promise { + it('dispose (addon)', async () => { await openTerminal(); await page.evaluate(` window.disposeCalled = false @@ -270,7 +267,7 @@ describe('API Integration Tests', function(): void { assert.equal(await page.evaluate(`window.disposeCalled`), true); }); - it('dispose (terminal)', async function(): Promise { + it('dispose (terminal)', async () => { await openTerminal(); await page.evaluate(` window.disposeCalled = false @@ -286,19 +283,19 @@ describe('API Integration Tests', function(): void { }); describe('Events', () => { - it('onCursorMove', async function(): Promise { + it('onCursorMove', async () => { await openTerminal(); await page.evaluate(` window.callCount = 0; window.term.onCursorMove(e => window.callCount++); window.term.write('foo'); `); - assert.equal(await page.evaluate(`window.callCount`), 1); + await pollFor(page, `window.callCount`, 1); await page.evaluate(`window.term.write('bar')`); - assert.equal(await page.evaluate(`window.callCount`), 2); + await pollFor(page, `window.callCount`, 2); }); - it('onData', async function(): Promise { + it('onData', async () => { await openTerminal(); await page.evaluate(` window.calls = []; @@ -308,7 +305,7 @@ describe('API Integration Tests', function(): void { assert.deepEqual(await page.evaluate(`window.calls`), ['f', 'o', 'o']); }); - it('onKey', async function(): Promise { + it('onKey', async () => { await openTerminal(); await page.evaluate(` window.calls = []; @@ -318,19 +315,19 @@ describe('API Integration Tests', function(): void { assert.deepEqual(await page.evaluate(`window.calls`), ['f', 'o', 'o']); }); - it('onLineFeed', async function(): Promise { + it('onLineFeed', async () => { await openTerminal(); await page.evaluate(` window.callCount = 0; window.term.onLineFeed(() => callCount++); window.term.writeln('foo'); `); - assert.equal(await page.evaluate(`window.callCount`), 1); + await pollFor(page, `window.callCount`, 1); await page.evaluate(`window.term.writeln('bar')`); - assert.equal(await page.evaluate(`window.callCount`), 2); + await pollFor(page, `window.callCount`, 2); }); - it('onScroll', async function(): Promise { + it('onScroll', async () => { await openTerminal({ rows: 5 }); await page.evaluate(` window.calls = []; @@ -339,94 +336,94 @@ describe('API Integration Tests', function(): void { window.term.writeln('foo'); } `); - assert.deepEqual(await page.evaluate(`window.calls`), []); + await pollFor(page, `window.calls`, []); await page.evaluate(`window.term.writeln('bar')`); - assert.deepEqual(await page.evaluate(`window.calls`), [1]); + await pollFor(page, `window.calls`, [1]); await page.evaluate(`window.term.writeln('baz')`); - assert.deepEqual(await page.evaluate(`window.calls`), [1, 2]); + await pollFor(page, `window.calls`, [1, 2]); }); - it('onSelectionChange', async function(): Promise { + it('onSelectionChange', async () => { await openTerminal(); await page.evaluate(` window.callCount = 0; window.term.onSelectionChange(() => window.callCount++); `); - assert.equal(await page.evaluate(`window.callCount`), 0); + await pollFor(page, `window.callCount`, 0); await page.evaluate(`window.term.selectAll()`); - assert.equal(await page.evaluate(`window.callCount`), 1); + await pollFor(page, `window.callCount`, 1); await page.evaluate(`window.term.clearSelection()`); - assert.equal(await page.evaluate(`window.callCount`), 2); + await pollFor(page, `window.callCount`, 2); }); - it('onRender', async function(): Promise { + it('onRender', async () => { await openTerminal(); await page.evaluate(` window.calls = []; window.term.onRender(e => window.calls.push([e.start, e.end])); `); - assert.deepEqual(await page.evaluate(`window.calls`), []); + await pollFor(page, `window.calls`, []); await page.evaluate(`window.term.write('foo')`); - assert.deepEqual(await page.evaluate(`window.calls`), [[0, 0]]); + await pollFor(page, `window.calls`, [[0, 0]]); await page.evaluate(`window.term.write('bar\\n\\nbaz')`); - assert.deepEqual(await page.evaluate(`window.calls`), [[0, 0], [0, 2]]); + await pollFor(page, `window.calls`, [[0, 0], [0, 2]]); }); - it('onResize', async function(): Promise { + it('onResize', async () => { await openTerminal(); await page.evaluate(` window.calls = []; window.term.onResize(e => window.calls.push([e.cols, e.rows])); `); - assert.deepEqual(await page.evaluate(`window.calls`), []); + await pollFor(page, `window.calls`, []); await page.evaluate(`window.term.resize(10, 5)`); - assert.deepEqual(await page.evaluate(`window.calls`), [[10, 5]]); + await pollFor(page, `window.calls`, [[10, 5]]); await page.evaluate(`window.term.resize(20, 15)`); - assert.deepEqual(await page.evaluate(`window.calls`), [[10, 5], [20, 15]]); + await pollFor(page, `window.calls`, [[10, 5], [20, 15]]); }); - it('onTitleChange', async function(): Promise { + it('onTitleChange', async () => { await openTerminal(); await page.evaluate(` window.calls = []; window.term.onTitleChange(e => window.calls.push(e)); `); - assert.deepEqual(await page.evaluate(`window.calls`), []); + await pollFor(page, `window.calls`, []); await page.evaluate(`window.term.write('\\x1b]2;foo\\x9c')`); - assert.deepEqual(await page.evaluate(`window.calls`), ['foo']); + await pollFor(page, `window.calls`, ['foo']); }); }); describe('buffer', () => { - it('cursorX, cursorY', async function(): Promise { + it('cursorX, cursorY', async () => { await openTerminal({ rows: 5, cols: 5 }); assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 0); assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 0); - await page.evaluate(`window.term.write('foo')`); + await writeSync(page, 'foo'); assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 3); assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 0); - await page.evaluate(`window.term.write('\\n')`); + await writeSync(page, '\\n'); assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 3); assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 1); - await page.evaluate(`window.term.write('\\r')`); + await writeSync(page, '\\r'); assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 0); assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 1); - await page.evaluate(`window.term.write('abcde')`); + await writeSync(page, 'abcde'); assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 5); assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 1); - await page.evaluate(`window.term.write('\\n\\r\\n\\n\\n\\n\\n')`); + await writeSync(page, '\\n\\r\\n\\n\\n\\n\\n'); assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 0); assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 4); }); - it('viewportY', async function(): Promise { + it('viewportY', async () => { await openTerminal({ rows: 5 }); assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 0); - await page.evaluate(`window.term.write('\\n\\n\\n\\n')`); + await writeSync(page, '\\n\\n\\n\\n'); assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 0); - await page.evaluate(`window.term.write('\\n')`); + await writeSync(page, '\\n'); assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 1); - await page.evaluate(`window.term.write('\\n\\n\\n\\n')`); + await writeSync(page, '\\n\\n\\n\\n'); assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 5); await page.evaluate(`window.term.scrollLines(-1)`); assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 4); @@ -434,14 +431,14 @@ describe('API Integration Tests', function(): void { assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 0); }); - it('baseY', async function(): Promise { + it('baseY', async () => { await openTerminal({ rows: 5 }); assert.equal(await page.evaluate(`window.term.buffer.baseY`), 0); - await page.evaluate(`window.term.write('\\n\\n\\n\\n')`); + await writeSync(page, '\\n\\n\\n\\n'); assert.equal(await page.evaluate(`window.term.buffer.baseY`), 0); - await page.evaluate(`window.term.write('\\n')`); + await writeSync(page, '\\n'); assert.equal(await page.evaluate(`window.term.buffer.baseY`), 1); - await page.evaluate(`window.term.write('\\n\\n\\n\\n')`); + await writeSync(page, '\\n\\n\\n\\n'); assert.equal(await page.evaluate(`window.term.buffer.baseY`), 5); await page.evaluate(`window.term.scrollLines(-1)`); assert.equal(await page.evaluate(`window.term.buffer.baseY`), 5); @@ -449,44 +446,44 @@ describe('API Integration Tests', function(): void { assert.equal(await page.evaluate(`window.term.buffer.baseY`), 5); }); - it('length', async function(): Promise { + it('length', async () => { await openTerminal({ rows: 5 }); assert.equal(await page.evaluate(`window.term.buffer.length`), 5); - await page.evaluate(`window.term.write('\\n\\n\\n\\n')`); + await writeSync(page, '\\n\\n\\n\\n'); assert.equal(await page.evaluate(`window.term.buffer.length`), 5); - await page.evaluate(`window.term.write('\\n')`); + await writeSync(page, '\\n'); assert.equal(await page.evaluate(`window.term.buffer.length`), 6); - await page.evaluate(`window.term.write('\\n\\n\\n\\n')`); + await writeSync(page, '\\n\\n\\n\\n'); assert.equal(await page.evaluate(`window.term.buffer.length`), 10); }); describe('getLine', () => { - it('invalid index', async function(): Promise { + it('invalid index', async () => { await openTerminal({ rows: 5 }); assert.equal(await page.evaluate(`window.term.buffer.getLine(-1)`), undefined); assert.equal(await page.evaluate(`window.term.buffer.getLine(5)`), undefined); }); - it('isWrapped', async function(): Promise { + it('isWrapped', async () => { await openTerminal({ cols: 5 }); assert.equal(await page.evaluate(`window.term.buffer.getLine(0).isWrapped`), false); assert.equal(await page.evaluate(`window.term.buffer.getLine(1).isWrapped`), false); - await page.evaluate(`window.term.write('abcde')`); + await writeSync(page, 'abcde'); assert.equal(await page.evaluate(`window.term.buffer.getLine(0).isWrapped`), false); assert.equal(await page.evaluate(`window.term.buffer.getLine(1).isWrapped`), false); - await page.evaluate(`window.term.write('f')`); + await writeSync(page, 'f'); assert.equal(await page.evaluate(`window.term.buffer.getLine(0).isWrapped`), false); assert.equal(await page.evaluate(`window.term.buffer.getLine(1).isWrapped`), true); }); - it('translateToString', async function(): Promise { + it('translateToString', async () => { await openTerminal({ cols: 5 }); assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString()`), ' '); assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), ''); - await page.evaluate(`window.term.write('foo')`); + await writeSync(page, 'foo'); assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString()`), 'foo '); assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo'); - await page.evaluate(`window.term.write('bar')`); + await writeSync(page, 'bar'); assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString()`), 'fooba'); assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'fooba'); assert.equal(await page.evaluate(`window.term.buffer.getLine(1).translateToString(true)`), 'r'); @@ -494,13 +491,13 @@ describe('API Integration Tests', function(): void { assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(false, 1, 3)`), 'oo'); }); - it('getCell', async function(): Promise { + it('getCell', async () => { 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); - await page.evaluate(`window.term.write('a文')`); + 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`), '文'); @@ -511,7 +508,7 @@ describe('API Integration Tests', function(): void { }); }); - it('dispose', async function(): Promise { + it('dispose', async () => { await page.evaluate(` window.term = new Terminal(); window.term.dispose(); @@ -519,7 +516,7 @@ describe('API Integration Tests', function(): void { assert.equal(await page.evaluate(`window.term._core._isDisposed`), true); }); - it('dispose (opened)', async function(): Promise { + it('dispose (opened)', async () => { await openTerminal(); await page.evaluate(`window.term.dispose()`); assert.equal(await page.evaluate(`window.term._core._isDisposed`), true); diff --git a/test/api/TestUtils.ts b/test/api/TestUtils.ts index ff1c661e6..f0dd71d4b 100644 --- a/test/api/TestUtils.ts +++ b/test/api/TestUtils.ts @@ -4,21 +4,24 @@ */ import * as puppeteer from 'puppeteer'; +import deepEqual = require('deep-equal'); export async function pollFor(page: puppeteer.Page, evalOrFn: string | (() => Promise), val: T, preFn?: () => Promise): Promise { if (preFn) { await preFn(); } const result = typeof evalOrFn === 'string' ? await page.evaluate(evalOrFn) : await evalOrFn(); - let equal = false; - if (typeof result === 'object') { - equal = Object.keys(result).every(e => result[e] === (val as any)[e]); - } else { - equal = result === val; - } - if (!equal) { + if (!deepEqual(result, val)) { return new Promise(r => { setTimeout(() => r(pollFor(page, evalOrFn, val, preFn)), 10); }); } } + +export 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); +} diff --git a/yarn.lock b/yarn.lock index cb02cf91c..171b66e4e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -130,6 +130,11 @@ resolved "https://registry.yarnpkg.com/@types/cli-table/-/cli-table-0.3.0.tgz#f1857156bf5fd115c6a2db260ba0be1f8fc5671c" integrity sha512-QnZUISJJXyhyD6L1e5QwXDV/A5i2W1/gl6D6YMc8u0ncPepbv/B4w3S+izVvtAg60m6h+JP09+Y/0zF2mojlFQ== +"@types/deep-equal@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/deep-equal/-/deep-equal-1.0.1.tgz#71cfabb247c22bcc16d536111f50c0ed12476b03" + integrity sha512-mMUu4nWHLBlHtxXY17Fg6+ucS/MnndyOWyOe7MmwkoMYxvfQU2ajtRaEvqSUv+aVkMqH/C0NCI8UoVfRNQ10yg== + "@types/events@*": version "1.2.0" resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86" @@ -1454,6 +1459,18 @@ deep-eql@^0.1.3: dependencies: type-detect "0.1.1" +deep-equal@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.0.tgz#3103cdf8ab6d32cf4a8df7865458f2b8d33f3745" + integrity sha512-ZbfWJq/wN1Z273o7mUSjILYqehAktR2NVoSrOukDkU9kg2v/Uv89yU4Cvz8seJeAmtN5oqiefKq8FPuXOboqLw== + dependencies: + is-arguments "^1.0.4" + is-date-object "^1.0.1" + is-regex "^1.0.4" + object-is "^1.0.1" + object-keys "^1.1.1" + regexp.prototype.flags "^1.2.0" + deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" @@ -2567,6 +2584,11 @@ is-accessor-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" +is-arguments@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3" + integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA== + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -3634,7 +3656,12 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" -object-keys@^1.0.11, object-keys@^1.0.12: +object-is@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.1.tgz#0aa60ec9989a0b3ed795cf4d06f62cf1ad6539b6" + integrity sha1-CqYOyZiaCz7Xlc9NBvYs8a1lObY= + +object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== @@ -4238,6 +4265,13 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" +regexp.prototype.flags@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.2.0.tgz#6b30724e306a27833eeb171b66ac8890ba37e41c" + integrity sha512-ztaw4M1VqgMwl9HlPpOuiYgItcHlunW0He2fE6eNfT6E/CF2FtYi9ofOYe4mKntstYk0Fyh/rDRBdS3AnxjlrA== + dependencies: + define-properties "^1.1.2" + release-zalgo@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/release-zalgo/-/release-zalgo-1.0.0.tgz#09700b7e5074329739330e535c5a90fb67851730" From 7ff6e4f4933bc604325aab5feb2b248e9a735082 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 18:28:24 -0800 Subject: [PATCH 032/268] Fix flaky terminal api tests --- test/api/Terminal.api.ts | 10 ++++++---- test/api/TestUtils.ts | 7 ++++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index 6d7a4d202..0b05736d7 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -6,7 +6,7 @@ import * as puppeteer from 'puppeteer'; import { assert } from 'chai'; import { ITerminalOptions } from 'xterm'; -import { pollFor, writeSync } from './TestUtils'; +import { pollFor, timeout, writeSync } from './TestUtils'; const APP = 'http://127.0.0.1:3000/test'; @@ -15,7 +15,7 @@ let page: puppeteer.Page; const width = 800; const height = 600; -describe.only('API Integration Tests', function(): void { +describe('API Integration Tests', function(): void { before(async () => { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, @@ -162,7 +162,7 @@ describe.only('API Integration Tests', function(): void { it('foreground', async () => { await openTerminal({ rendererType: 'dom' }); await writeSync(page, '\\x1b[30m0\\x1b[31m1\\x1b[32m2\\x1b[33m3\\x1b[34m4\\x1b[35m5\\x1b[36m6\\x1b[37m7'); - await pollFor(page, `!!document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(1)')`, true); + await pollFor(page, `document.querySelectorAll('.xterm-rows > :nth-child(1) > *').length`, 9); assert.deepEqual(await page.evaluate(` [ document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(1)').className, @@ -187,7 +187,7 @@ describe.only('API Integration Tests', function(): void { it('background', async () => { await openTerminal({ rendererType: 'dom' }); await writeSync(page, '\\x1b[40m0\\x1b[41m1\\x1b[42m2\\x1b[43m3\\x1b[44m4\\x1b[45m5\\x1b[46m6\\x1b[47m7'); - await pollFor(page, `!!document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(1)')`, true); + await pollFor(page, `document.querySelectorAll('.xterm-rows > :nth-child(1) > *').length`, 9); assert.deepEqual(await page.evaluate(` [ document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(1)').className, @@ -358,6 +358,7 @@ describe.only('API Integration Tests', function(): void { it('onRender', async () => { await openTerminal(); + await timeout(20); // Ensure all init events are fired await page.evaluate(` window.calls = []; window.term.onRender(e => window.calls.push([e.start, e.end])); @@ -371,6 +372,7 @@ describe.only('API Integration Tests', function(): void { it('onResize', async () => { await openTerminal(); + await timeout(20); // Ensure all init events are fired await page.evaluate(` window.calls = []; window.term.onResize(e => window.calls.push([e.cols, e.rows])); diff --git a/test/api/TestUtils.ts b/test/api/TestUtils.ts index f0dd71d4b..a31c2b467 100644 --- a/test/api/TestUtils.ts +++ b/test/api/TestUtils.ts @@ -12,8 +12,9 @@ export async function pollFor(page: puppeteer.Page, evalOrFn: string | (() => } const result = typeof evalOrFn === 'string' ? await page.evaluate(evalOrFn) : await evalOrFn(); if (!deepEqual(result, val)) { + console.log('result', result, 'val', val); return new Promise(r => { - setTimeout(() => r(pollFor(page, evalOrFn, val, preFn)), 10); + setTimeout(() => r(pollFor(page, evalOrFn, val, preFn)), 1); }); } } @@ -25,3 +26,7 @@ export async function writeSync(page: puppeteer.Page, data: string): Promise { + return new Promise(r => setTimeout(r, ms)); +} From b0640b7705b2b80df84c867ba60913e5729ca5cd Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 18:28:50 -0800 Subject: [PATCH 033/268] Remove slowMo param everywhere --- addons/xterm-addon-fit/src/FitAddon.api.ts | 1 - addons/xterm-addon-search/src/SearchAddon.api.ts | 1 - test/api/InputHandler.api.ts | 1 - 3 files changed, 3 deletions(-) diff --git a/addons/xterm-addon-fit/src/FitAddon.api.ts b/addons/xterm-addon-fit/src/FitAddon.api.ts index 3115f2edd..aa73a9b61 100644 --- a/addons/xterm-addon-fit/src/FitAddon.api.ts +++ b/addons/xterm-addon-fit/src/FitAddon.api.ts @@ -19,7 +19,6 @@ describe('FitAddon', () => { this.timeout(20000); browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; diff --git a/addons/xterm-addon-search/src/SearchAddon.api.ts b/addons/xterm-addon-search/src/SearchAddon.api.ts index 96c62a7d4..72a69b5fb 100644 --- a/addons/xterm-addon-search/src/SearchAddon.api.ts +++ b/addons/xterm-addon-search/src/SearchAddon.api.ts @@ -22,7 +22,6 @@ describe('Search Tests', function (): void { before(async function (): Promise { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; diff --git a/test/api/InputHandler.api.ts b/test/api/InputHandler.api.ts index 4bc2c07d1..968a9e0bb 100644 --- a/test/api/InputHandler.api.ts +++ b/test/api/InputHandler.api.ts @@ -19,7 +19,6 @@ describe('InputHandler Integration Tests', function(): void { before(async function(): Promise { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; From 971e69f50592046d12ad64ae8cd11575c9d725b3 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 18:43:25 -0800 Subject: [PATCH 034/268] Reduce flakiness --- .../src/WebLinksAddon.api.ts | 28 +++++++++++-------- test/api/MouseTracking.api.ts | 20 ++++++------- test/api/Parser.api.ts | 17 ++++------- test/api/TestUtils.ts | 1 - 4 files changed, 31 insertions(+), 35 deletions(-) diff --git a/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts b/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts index 49ec4feef..0cb222c84 100644 --- a/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts +++ b/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts @@ -53,18 +53,14 @@ describe('WebLinksAddon', () => { async function testHostName(hostname: string): Promise { await openTerminal({ rendererType: 'dom' }); await page.evaluate(`window.term.loadAddon(new window.WebLinksAddon())`); - await new Promise(r => setTimeout(r, 100)); - await page.evaluate(` - window.ready = false; - window.term.writeln(' http://${hostname} '); - window.term.writeln(' http://${hostname}/a~b#c~d?e~f '); - window.term.writeln(' http://${hostname}/colon:test '); - window.term.writeln(' http://${hostname}/colon:test: '); - window.term.writeln('"http://${hostname}/"'); - window.term.writeln('\\'http://${hostname}/\\''); - window.term.writeln('http://${hostname}/subpath/+/id', () => window.ready = true); - `); - await pollFor(page, 'window.ready', true); + const data = ` http://${hostname} \\r\\n` + + ` http://${hostname}/a~b#c~d?e~f \\r\\n` + + ` http://${hostname}/colon:test \\r\\n` + + ` http://${hostname}/colon:test: \\r\\n` + + `"http://${hostname}/"\\r\\n` + + `\\'http://${hostname}/\\'\\r\\n` + + `http://${hostname}/subpath/+/id`; + await writeSync(page, data); await pollForLinkAtCell(3, 1, `http://${hostname}`); await pollForLinkAtCell(3, 2, `http://${hostname}/a~b#c~d?e~f`); await pollForLinkAtCell(3, 3, `http://${hostname}/colon:test`); @@ -101,3 +97,11 @@ async function pollFor(page: puppeteer.Page, fn: string, val: any, preFn?: () => }); } } + +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); +} diff --git a/test/api/MouseTracking.api.ts b/test/api/MouseTracking.api.ts index d10a5b951..f8124350c 100644 --- a/test/api/MouseTracking.api.ts +++ b/test/api/MouseTracking.api.ts @@ -5,7 +5,7 @@ import * as puppeteer from 'puppeteer'; import { ITerminalOptions } from 'xterm'; -import { pollFor } from './TestUtils'; +import { pollFor, writeSync } from './TestUtils'; const APP = 'http://127.0.0.1:3000/test'; @@ -199,7 +199,7 @@ function parseReport(encoding: string, msg: number[]): {state: any; row: number; * Mouse tracking tests. */ describe('Mouse Tracking Tests', () => { - before(async () => { + before(async function(): Promise { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, args: [`--window-size=${width},${height}`, `--no-sandbox`] @@ -237,7 +237,7 @@ describe('Mouse Tracking Tests', () => { const encoding = 'DEFAULT'; await resetMouseModes(); await mouseMove(0, 0); - await page.evaluate(`window.term.write('\x1b[?9h');`); + await writeSync(page, '\x1b[?9h'); // test at 0,0 await mouseDown('left'); @@ -359,7 +359,7 @@ describe('Mouse Tracking Tests', () => { const encoding = 'SGR'; await resetMouseModes(); await mouseMove(0, 0); - await page.evaluate(`window.term.write('\x1b[?9h\x1b[?1006h');`); + await writeSync(page, '\x1b[?9h\x1b[?1006h'); // test at 0,0 await mouseDown('left'); @@ -486,7 +486,7 @@ describe('Mouse Tracking Tests', () => { const encoding = 'DEFAULT'; await resetMouseModes(); await mouseMove(0, 0); - await page.evaluate(`window.term.write('\x1b[?1000h');`); + await writeSync(page, '\x1b[?1000h'); // test at 0,0 await mouseDown('left'); @@ -638,7 +638,7 @@ describe('Mouse Tracking Tests', () => { const encoding = 'SGR'; await resetMouseModes(); await mouseMove(0, 0); - await page.evaluate(`window.term.write('\x1b[?1000h\x1b[?1006h');`); + await writeSync(page, '\x1b[?1000h\x1b[?1006h'); // test at 0,0 await mouseDown('left'); @@ -796,7 +796,7 @@ describe('Mouse Tracking Tests', () => { const encoding = 'DEFAULT'; await resetMouseModes(); await mouseMove(0, 0); - await page.evaluate(`window.term.write('\x1b[?1002h');`); + await writeSync(page, '\x1b[?1002h'); // test at 0,0 await mouseDown('left'); @@ -953,7 +953,7 @@ describe('Mouse Tracking Tests', () => { const encoding = 'SGR'; await resetMouseModes(); await mouseMove(0, 0); - await page.evaluate(`window.term.write('\x1b[?1002h\x1b[?1006h');`); + await writeSync(page, '\x1b[?1002h\x1b[?1006h'); // test at 0,0 // bug: release is fired immediately @@ -1115,7 +1115,7 @@ describe('Mouse Tracking Tests', () => { const encoding = 'DEFAULT'; await resetMouseModes(); await mouseMove(0, 0); - await page.evaluate(`window.term.write('\x1b[?1003h');`); + await writeSync(page, '\x1b[?1003h'); // test at 0,0 await mouseDown('left'); @@ -1276,7 +1276,7 @@ describe('Mouse Tracking Tests', () => { const encoding = 'SGR'; await resetMouseModes(); await mouseMove(0, 0); - await page.evaluate(`window.term.write('\x1b[?1003h\x1b[?1006h');`); + await writeSync(page, '\x1b[?1003h\x1b[?1006h'); // test at 0,0 await mouseDown('left'); diff --git a/test/api/Parser.api.ts b/test/api/Parser.api.ts index 9b0fb66bf..72f567994 100644 --- a/test/api/Parser.api.ts +++ b/test/api/Parser.api.ts @@ -6,6 +6,7 @@ import * as puppeteer from 'puppeteer'; import { assert } from 'chai'; import { ITerminalOptions } from 'xterm'; +import { writeSync } from './TestUtils'; const APP = 'http://127.0.0.1:3000/test'; @@ -38,9 +39,7 @@ describe('Parser Integration Tests', function(): void { return false; }, ''); `); - await page.evaluate(` - window.term.write('\x1b[38;5;123mparams\x1b[38:2::50:100:150msubparams'); - `); + await writeSync(page, '\x1b[38;5;123mparams\x1b[38:2::50:100:150msubparams'); assert.deepEqual(await page.evaluate(`(() => _customCsiHandlerParams)();`), [[38, 5, 123], [38, [2, -1, 50, 100, 150]]]); }); }); @@ -62,9 +61,7 @@ describe('Parser Integration Tests', function(): void { return false; }); `); - await page.evaluate(` - window.term.write('\x1bP1;2+psome data\x1b\\\\'); - `); + await writeSync(page, '\x1bP1;2+psome data\x1b\\\\'); assert.deepEqual(await page.evaluate(`(() => _customDcsHandlerCallStack)();`), [['C', [1, 2], 'some data'], ['B', [1, 2], 'some data']]); }); }); @@ -86,9 +83,7 @@ describe('Parser Integration Tests', function(): void { return false; }); `); - await page.evaluate(` - window.term.write('\x1b(B'); - `); + await writeSync(page, '\x1b(B'); assert.deepEqual(await page.evaluate(`(() => _customEscHandlerCallStack)();`), ['C', 'B']); }); }); @@ -110,9 +105,7 @@ describe('Parser Integration Tests', function(): void { return false; }); `); - await page.evaluate(` - window.term.write('\x1b]1234;some data\x07'); - `); + await writeSync(page, '\x1b]1234;some data\x07'); assert.deepEqual(await page.evaluate(`(() => _customOscHandlerCallStack)();`), [['C', 'some data'], ['B', 'some data']]); }); }); diff --git a/test/api/TestUtils.ts b/test/api/TestUtils.ts index a31c2b467..d36748122 100644 --- a/test/api/TestUtils.ts +++ b/test/api/TestUtils.ts @@ -12,7 +12,6 @@ export async function pollFor(page: puppeteer.Page, evalOrFn: string | (() => } const result = typeof evalOrFn === 'string' ? await page.evaluate(evalOrFn) : await evalOrFn(); if (!deepEqual(result, val)) { - console.log('result', result, 'val', val); return new Promise(r => { setTimeout(() => r(pollFor(page, evalOrFn, val, preFn)), 1); }); From 669622108cbe19d1ef1126d478aff291d261336b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 18:48:09 -0800 Subject: [PATCH 035/268] Wait for all elements to be rendered in web links --- addons/xterm-addon-web-links/src/WebLinksAddon.api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts b/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts index 0cb222c84..7bbaf29e0 100644 --- a/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts +++ b/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts @@ -82,7 +82,7 @@ async function openTerminal(options: ITerminalOptions = {}): Promise { async function pollForLinkAtCell(col: number, row: number, value: string): Promise { const rowSelector = `.xterm-rows > :nth-child(${row})`; - await pollFor(page, `document.querySelectorAll('${rowSelector} > span[style]').length > 0`, true, async () => page.hover(`${rowSelector} > :nth-child(${col})`)); + await pollFor(page, `document.querySelectorAll('${rowSelector} > span[style]').length >= ${value.length}`, true, async () => page.hover(`${rowSelector} > :nth-child(${col})`)); assert.equal(await page.evaluate(`Array.prototype.reduce.call(document.querySelectorAll('${rowSelector} > span[style]'), (a, b) => a + b.textContent, '');`), value); } From 057d0a16f2ef5c41abf4854f2b23158f0a831c25 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 18:54:24 -0800 Subject: [PATCH 036/268] Ensure link hover exists before hovering --- addons/xterm-addon-web-links/src/WebLinksAddon.api.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts b/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts index 7bbaf29e0..ab61fdbfc 100644 --- a/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts +++ b/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts @@ -82,6 +82,8 @@ async function openTerminal(options: ITerminalOptions = {}): Promise { async function pollForLinkAtCell(col: number, row: number, value: string): Promise { const rowSelector = `.xterm-rows > :nth-child(${row})`; + // Ensure the hover element exists before trying to hover it + await pollFor(page, `!!document.querySelector('${rowSelector} > :nth-child(${col})')`, true); await pollFor(page, `document.querySelectorAll('${rowSelector} > span[style]').length >= ${value.length}`, true, async () => page.hover(`${rowSelector} > :nth-child(${col})`)); assert.equal(await page.evaluate(`Array.prototype.reduce.call(document.querySelectorAll('${rowSelector} > span[style]'), (a, b) => a + b.textContent, '');`), value); } From 40b3766abc26c4a3233d9511bed80cdf872b76b1 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 19:05:20 -0800 Subject: [PATCH 037/268] Fix import that got lost in merge --- addons/xterm-addon-webgl/src/WebglRenderer.api.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index c3c543402..51dae37ad 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -6,6 +6,7 @@ import * as puppeteer from 'puppeteer'; import { ITerminalOptions } from '../../../src/Types'; import { ITheme } from 'xterm'; +import { assert } from 'chai'; const APP = 'http://127.0.0.1:3000/test'; From 8a6e0fca07fa28ef51df1d1af296c73ab53f402b Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 9 Nov 2019 21:15:07 -0800 Subject: [PATCH 038/268] Move parts over to CellData --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index ab2cb3546..785f6e69f 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -23,6 +23,7 @@ import { IColorSet } from 'browser/Types'; import { FLAGS } from './Constants'; import { getCompatAttr } from './CharDataCompat'; import { EventEmitter } from 'common/EventEmitter'; +import { CellData } from 'common/buffer/CellData'; export const INDICIES_PER_CELL = 4; @@ -32,6 +33,7 @@ export class WebglRenderer extends Disposable implements IRenderer { private _devicePixelRatio: number; private _model: RenderModel = new RenderModel(); + private _workCell: CellData = new CellData(); private _canvas: HTMLCanvasElement; private _gl: IWebGL2RenderingContext; @@ -258,12 +260,19 @@ export class WebglRenderer extends Disposable implements IRenderer { const line = terminal.buffer.lines.get(row)!; this._model.lineLengths[y] = 0; for (let x = 0; x < terminal.cols; x++) { - const charData = line.get(x); - const chars = charData[CHAR_DATA_CHAR_INDEX]; - let code = charData[CHAR_DATA_CODE_INDEX]; + line.loadCell(x, this._workCell); + + // const charData = line.get(x); + // const chars = charData[CHAR_DATA_CHAR_INDEX]; + const chars = this._workCell.getChars(); + // let code = charData[CHAR_DATA_CODE_INDEX]; + let code = this._workCell.getCode(); const attr = getCompatAttr(line, x); // charData[CHAR_DATA_ATTR_INDEX]; const i = ((y * terminal.cols) + x) * INDICIES_PER_CELL; + // console.log('workcell', this._workCell); + // console.log('attr: ' + attr); + if (code !== NULL_CELL_CODE) { this._model.lineLengths[y] = x + 1; } From e72ee3c58dc87ccfaffd103eff24439e1554ce96 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 9 Nov 2019 22:28:59 -0800 Subject: [PATCH 039/268] Move webgl drawing over from compat Still need to tackle glyph key --- .../src/RectangleRenderer.ts | 25 ++++++++++++------- addons/xterm-addon-webgl/src/WebglRenderer.ts | 22 +++++++--------- .../src/atlas/CharAtlasUtils.ts | 4 +-- .../src/atlas/WebglCharAtlas.ts | 14 +++++------ 4 files changed, 34 insertions(+), 31 deletions(-) diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index 2c464d2ae..70b5ee3c1 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -8,7 +8,7 @@ import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelect import { fill } from 'common/TypedArrayUtils'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { is256Color } from './atlas/CharAtlasUtils'; -import { DEFAULT_COLOR } from 'common/buffer/Constants'; +import { DEFAULT_COLOR, Attributes, FgFlags } from 'common/buffer/Constants'; import { Terminal } from 'xterm'; import { IColorSet, IColor } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; @@ -248,37 +248,44 @@ export class RectangleRenderer { for (let y = 0; y < terminal.rows; y++) { let currentStartX = -1; let currentBg = DEFAULT_COLOR; + let currentFg = DEFAULT_COLOR; for (let x = 0; x < terminal.cols; x++) { const modelIndex = ((y * terminal.cols) + x) * 4; const bg = model.cells[modelIndex + 2]; + const fg = model.cells[modelIndex + 1]; if (bg !== currentBg) { // A rectangle needs to be drawn if going from non-default to another color + // TODO: DEFAULT_COLOR probably isn't right anymore? if (currentBg !== DEFAULT_COLOR) { const offset = rectangleCount++ * INDICES_PER_RECTANGLE; - this._updateRectangle(vertices, offset, currentBg, currentStartX, x, y); + this._updateRectangle(vertices, offset, currentFg, currentBg, currentStartX, x, y); } currentStartX = x; currentBg = bg; + currentFg = fg; } } // Finish rectangle if it's still going if (currentBg !== DEFAULT_COLOR) { const offset = rectangleCount++ * INDICES_PER_RECTANGLE; - this._updateRectangle(vertices, offset, currentBg, currentStartX, terminal.cols, y); + this._updateRectangle(vertices, offset, currentFg, currentBg, currentStartX, terminal.cols, y); } } vertices.count = rectangleCount; } - private _updateRectangle(vertices: IVertices, offset: number, bg: number, startX: number, endX: number, y: number): void { + private _updateRectangle(vertices: IVertices, offset: number, fg: number, bg: number, startX: number, endX: number, y: number): void { let color: IColor | null = null; - if (bg === INVERTED_DEFAULT_COLOR) { + if (fg & FgFlags.INVERSE) { + // Inverted color color = this._colors.foreground; - } else if (is256Color(bg)) { - color = this._colors.ansi[bg]; + } else if ((bg & Attributes.CM_MASK) === Attributes.CM_P16 || (bg & Attributes.CM_MASK) === Attributes.CM_P256) { + // 256 palette + color = this._colors.ansi[bg & Attributes.PCOLOR_MASK]; } else { - // TODO: Add support for true color - color = this._colors.foreground; + // TODO: Support true color + // Default color + color = this._colors.background; } if (vertices.attributes.length < offset + 4) { vertices.attributes = expandFloat32Array(vertices.attributes, this._terminal.rows * this._terminal.cols * INDICES_PER_RECTANGLE); diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 785f6e69f..ef20f6143 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -14,7 +14,7 @@ import { IWebGL2RenderingContext } from './Types'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel'; import { Disposable } from 'common/Lifecycle'; -import { DEFAULT_COLOR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, NULL_CELL_CODE } from 'common/buffer/Constants'; +import { DEFAULT_COLOR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, NULL_CELL_CODE, FgFlags } from 'common/buffer/Constants'; import { Terminal, IEvent } from 'xterm'; import { getLuminance } from './ColorUtils'; import { IRenderLayer } from './renderLayer/Types'; @@ -262,17 +262,11 @@ export class WebglRenderer extends Disposable implements IRenderer { for (let x = 0; x < terminal.cols; x++) { line.loadCell(x, this._workCell); - // const charData = line.get(x); - // const chars = charData[CHAR_DATA_CHAR_INDEX]; const chars = this._workCell.getChars(); - // let code = charData[CHAR_DATA_CODE_INDEX]; let code = this._workCell.getCode(); const attr = getCompatAttr(line, x); // charData[CHAR_DATA_ATTR_INDEX]; const i = ((y * terminal.cols) + x) * INDICIES_PER_CELL; - // console.log('workcell', this._workCell); - // console.log('attr: ' + attr); - if (code !== NULL_CELL_CODE) { this._model.lineLengths[y] = x + 1; } @@ -283,12 +277,11 @@ export class WebglRenderer extends Disposable implements IRenderer { } // Resolve bg and fg and cache in the model - const flags = attr >> 18; - let bg = attr & 0x1ff; - let fg = (attr >> 9) & 0x1ff; + let bg = this._workCell.bg; + let fg = this._workCell.fg; // If inverse flag is on, the foreground should become the background. - if (flags & FLAGS.INVERSE) { + if (this._workCell.isInverse()) { const temp = bg; bg = fg; fg = temp; @@ -299,8 +292,11 @@ export class WebglRenderer extends Disposable implements IRenderer { bg = INVERTED_DEFAULT_COLOR; } } - const drawInBrightColor = terminal.options.drawBoldTextInBrightColors && !!(flags & FLAGS.BOLD) && fg < 8 && fg !== INVERTED_DEFAULT_COLOR; - fg += drawInBrightColor ? 8 : 0; + + // Apply drawBoldTextInBrightColors + if (terminal.options.drawBoldTextInBrightColors && this._workCell.isBold() && fg & FgFlags.BOLD && this._workCell.getFgColor() < 8) { + fg += 8; + } // Flag combined chars with a bit mask so they're easily identifiable if (chars.length > 1) { diff --git a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts index 857fa6287..821b53a48 100644 --- a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts +++ b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts @@ -4,7 +4,7 @@ */ import { ICharAtlasConfig } from './Types'; -import { DEFAULT_COLOR } from 'common/buffer/Constants'; +import { DEFAULT_COLOR, Attributes } from 'common/buffer/Constants'; import { Terminal, FontWeight } from 'xterm'; import { IColorSet, IColor } from 'browser/Types'; @@ -57,5 +57,5 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean } export function is256Color(colorCode: number): boolean { - return colorCode < DEFAULT_COLOR; + return (colorCode & Attributes.CM_MASK) === Attributes.CM_P16 || (colorCode & Attributes.CM_MASK) === Attributes.CM_P256; } diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 8d8e995bf..14cd017d6 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -6,7 +6,7 @@ import { ICharAtlasConfig } from './Types'; import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types'; -import { DEFAULT_COLOR, DEFAULT_ATTR } from 'common/buffer/Constants'; +import { DEFAULT_COLOR, DEFAULT_ATTR, FgFlags, Attributes } from 'common/buffer/Constants'; import { is256Color } from './CharAtlasUtils'; import { throwIfFalsy } from '../WebglUtils'; import { IColor } from 'browser/Types'; @@ -161,26 +161,26 @@ export class WebglCharAtlas implements IDisposable { return this._config.colors.ansi[idx]; } - private _getBackgroundColor(bg: number): IColor { + private _getBackgroundColor(bg: number, fg: number): IColor { if (this._config.allowTransparency) { // The background color might have some transparency, so we need to render it as fully // transparent in the atlas. Otherwise we'd end up drawing the transparent background twice // around the anti-aliased edges of the glyph, and it would look too dark. return TRANSPARENT_COLOR; - } else if (bg === INVERTED_DEFAULT_COLOR) { + } else if (fg & FgFlags.INVERSE) { return this._config.colors.foreground; } else if (is256Color(bg)) { - return this._getColorFromAnsiIndex(bg); + return this._getColorFromAnsiIndex(bg & Attributes.PCOLOR_MASK); } // TODO: Support true color return this._config.colors.background; } private _getForegroundColor(fg: number): IColor { - if (fg === INVERTED_DEFAULT_COLOR) { + if (fg & FgFlags.INVERSE) { return this._config.colors.background; } else if (is256Color(fg)) { - return this._getColorFromAnsiIndex(fg); + return this._getColorFromAnsiIndex(fg & Attributes.PCOLOR_MASK); } // TODO: Support true color return this._config.colors.foreground; @@ -202,7 +202,7 @@ export class WebglCharAtlas implements IDisposable { this._tmpCtx.save(); // draw the background - const backgroundColor = this._getBackgroundColor(bg); + const backgroundColor = this._getBackgroundColor(bg, fg); // Use a 'copy' composite operation to clear any existing glyph out of _tmpCtxWithAlpha, regardless of // transparency in backgroundColor this._tmpCtx.globalCompositeOperation = 'copy'; From 8f0f8bf827435d9268ef59d88b4a54fd3b99fdeb Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 9 Nov 2019 22:31:10 -0800 Subject: [PATCH 040/268] Remove cache reliance on flags --- .../src/atlas/WebglCharAtlas.ts | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 14cd017d6..f3858c692 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -6,7 +6,7 @@ import { ICharAtlasConfig } from './Types'; import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types'; -import { DEFAULT_COLOR, DEFAULT_ATTR, FgFlags, Attributes } from 'common/buffer/Constants'; +import { DEFAULT_COLOR, DEFAULT_ATTR, FgFlags, Attributes, BgFlags } from 'common/buffer/Constants'; import { is256Color } from './CharAtlasUtils'; import { throwIfFalsy } from '../WebglUtils'; import { IColor } from 'browser/Types'; @@ -103,7 +103,7 @@ export class WebglCharAtlas implements IDisposable { protected _doWarmUp(): void { // Pre-fill with ASCII 33-126 for (let i = 33; i < 126; i++) { - const rasterizedGlyph = this._drawToCache(i, DEFAULT_ATTR, DEFAULT_COLOR, DEFAULT_COLOR); + const rasterizedGlyph = this._drawToCache(i, DEFAULT_COLOR, DEFAULT_COLOR); this._cacheMap[i] = { [DEFAULT_ATTR]: rasterizedGlyph }; @@ -131,7 +131,7 @@ export class WebglCharAtlas implements IDisposable { } let rasterizedGlyph = rasterizedGlyphSet[attr]; if (!rasterizedGlyph) { - rasterizedGlyph = this._drawToCache(chars, attr, bg, fg); + rasterizedGlyph = this._drawToCache(chars, bg, fg); rasterizedGlyphSet[attr] = rasterizedGlyph; } return rasterizedGlyph; @@ -148,7 +148,7 @@ export class WebglCharAtlas implements IDisposable { } let rasterizedGlyph = rasterizedGlyphSet[attr]; if (!rasterizedGlyph) { - rasterizedGlyph = this._drawToCache(code, attr, bg, fg); + rasterizedGlyph = this._drawToCache(code, bg, fg); rasterizedGlyphSet[attr] = rasterizedGlyph; } return rasterizedGlyph; @@ -186,18 +186,16 @@ export class WebglCharAtlas implements IDisposable { return this._config.colors.foreground; } - private _drawToCache(code: number, attr: number, bg: number, fg: number): IRasterizedGlyph; - private _drawToCache(chars: string, attr: number, bg: number, fg: number): IRasterizedGlyph; - private _drawToCache(codeOrChars: number | string, attr: number, bg: number, fg: number): IRasterizedGlyph { + private _drawToCache(code: number, bg: number, fg: number): IRasterizedGlyph; + private _drawToCache(chars: string, bg: number, fg: number): IRasterizedGlyph; + private _drawToCache(codeOrChars: number | string, bg: number, fg: number): IRasterizedGlyph { const chars = typeof codeOrChars === 'number' ? String.fromCharCode(codeOrChars) : codeOrChars; this.hasCanvasChanged = true; - const flags = attr >> 18; - - const bold = !!(flags & FLAGS.BOLD); - const dim = !!(flags & FLAGS.DIM); - const italic = !!(flags & FLAGS.ITALIC); + const bold = !!(fg & FgFlags.BOLD); + const dim = !!(bg & BgFlags.DIM); + const italic = !!(bg & BgFlags.ITALIC); this._tmpCtx.save(); From e15476704ca9fecbf93e607ce7253eedd040840e Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 9 Nov 2019 22:43:25 -0800 Subject: [PATCH 041/268] Adopt new [bg][fg] map in char cache --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 14 ++++---- addons/xterm-addon-webgl/src/Types.d.ts | 2 +- addons/xterm-addon-webgl/src/WebglRenderer.ts | 3 +- .../src/atlas/WebglCharAtlas.ts | 32 ++++++++++++++----- 4 files changed, 34 insertions(+), 17 deletions(-) diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index bd4488dc1..dd83131e1 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -169,11 +169,11 @@ export class GlyphRenderer { return this._atlas ? this._atlas.beginFrame() : true; } - public updateCell(x: number, y: number, code: number, attr: number, bg: number, fg: number, chars: string): void { - this._updateCell(this._vertices.attributes, x, y, code, attr, bg, fg, chars); + public updateCell(x: number, y: number, code: number, bg: number, fg: number, chars: string): void { + this._updateCell(this._vertices.attributes, x, y, code, bg, fg, chars); } - private _updateCell(array: Float32Array, x: number, y: number, code: number | undefined, attr: number, bg: number, fg: number, chars?: string): void { + private _updateCell(array: Float32Array, x: number, y: number, code: number | undefined, bg: number, fg: number, chars?: string): void { const terminal = this._terminal; const i = (y * terminal.cols + x) * INDICES_PER_CELL; @@ -189,9 +189,9 @@ export class GlyphRenderer { throw new Error('atlas must be set before updating cell'); } if (chars && chars.length > 1) { - rasterizedGlyph = this._atlas.getRasterizedGlyphCombinedChar(chars, attr, bg, fg); + rasterizedGlyph = this._atlas.getRasterizedGlyphCombinedChar(chars, bg, fg); } else { - rasterizedGlyph = this._atlas.getRasterizedGlyph(code, attr, bg, fg); + rasterizedGlyph = this._atlas.getRasterizedGlyph(code, bg, fg); } // Fill empty if no glyph was found @@ -269,9 +269,9 @@ export class GlyphRenderer { line = terminal.buffer.getLine(row); } const chars = line!.getCell(x)!.char; - this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], attr, bg, fg, 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], attr, bg, fg); + this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, fg); } } } diff --git a/addons/xterm-addon-webgl/src/Types.d.ts b/addons/xterm-addon-webgl/src/Types.d.ts index 6ebc8d64d..f01092eec 100644 --- a/addons/xterm-addon-webgl/src/Types.d.ts +++ b/addons/xterm-addon-webgl/src/Types.d.ts @@ -4,7 +4,7 @@ */ export interface IRasterizedGlyphSet { - [flags: number]: IRasterizedGlyph; + [bg: number]: { [fg: number]: IRasterizedGlyph } | undefined; } /** diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index ef20f6143..afdb7fa40 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -304,11 +304,12 @@ export class WebglRenderer extends Disposable implements IRenderer { } this._model.cells[i ] = code; + // TODO: Remove attr from model this._model.cells[i + 1] = attr; this._model.cells[i + 2] = bg; this._model.cells[i + 3] = fg; - this._glyphRenderer.updateCell(x, y, code, attr, bg, fg, chars); + this._glyphRenderer.updateCell(x, y, code, bg, fg, chars); } } this._rectangleRenderer.updateBackgrounds(this._model); diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index f3858c692..9d8eb286e 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -4,7 +4,7 @@ */ import { ICharAtlasConfig } from './Types'; -import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; +import { DIM_OPACITY } from 'browser/renderer/atlas/Constants'; import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types'; import { DEFAULT_COLOR, DEFAULT_ATTR, FgFlags, Attributes, BgFlags } from 'common/buffer/Constants'; import { is256Color } from './CharAtlasUtils'; @@ -105,7 +105,9 @@ export class WebglCharAtlas implements IDisposable { for (let i = 33; i < 126; i++) { const rasterizedGlyph = this._drawToCache(i, DEFAULT_COLOR, DEFAULT_COLOR); this._cacheMap[i] = { - [DEFAULT_ATTR]: rasterizedGlyph + [DEFAULT_COLOR]: { + [DEFAULT_COLOR]: rasterizedGlyph + } }; } } @@ -123,16 +125,23 @@ export class WebglCharAtlas implements IDisposable { return false; } - public getRasterizedGlyphCombinedChar(chars: string, attr: number, bg: number, fg: number): IRasterizedGlyph { + public getRasterizedGlyphCombinedChar(chars: string, bg: number, fg: number): IRasterizedGlyph { let rasterizedGlyphSet = this._cacheMapCombined[chars]; if (!rasterizedGlyphSet) { rasterizedGlyphSet = {}; this._cacheMapCombined[chars] = rasterizedGlyphSet; } - let rasterizedGlyph = rasterizedGlyphSet[attr]; + let rasterizedGlyph: IRasterizedGlyph | undefined; + const rasterizedGlyphSetBg = rasterizedGlyphSet[bg]; + if (rasterizedGlyphSetBg) { + rasterizedGlyph = rasterizedGlyphSetBg[fg]; + } if (!rasterizedGlyph) { rasterizedGlyph = this._drawToCache(chars, bg, fg); - rasterizedGlyphSet[attr] = rasterizedGlyph; + if (!rasterizedGlyphSet[bg]) { + rasterizedGlyphSet[bg] = {}; + } + rasterizedGlyphSet[bg]![fg] = rasterizedGlyph; } return rasterizedGlyph; } @@ -140,16 +149,23 @@ export class WebglCharAtlas implements IDisposable { /** * Gets the glyphs texture coords, drawing the texture if it's not already */ - public getRasterizedGlyph(code: number, attr: number, bg: number, fg: number): IRasterizedGlyph { + public getRasterizedGlyph(code: number, bg: number, fg: number): IRasterizedGlyph { let rasterizedGlyphSet = this._cacheMap[code]; if (!rasterizedGlyphSet) { rasterizedGlyphSet = {}; this._cacheMap[code] = rasterizedGlyphSet; } - let rasterizedGlyph = rasterizedGlyphSet[attr]; + let rasterizedGlyph: IRasterizedGlyph | undefined; + const rasterizedGlyphSetBg = rasterizedGlyphSet[bg]; + if (rasterizedGlyphSetBg) { + rasterizedGlyph = rasterizedGlyphSetBg[fg]; + } if (!rasterizedGlyph) { rasterizedGlyph = this._drawToCache(code, bg, fg); - rasterizedGlyphSet[attr] = rasterizedGlyph; + if (!rasterizedGlyphSet[bg]) { + rasterizedGlyphSet[bg] = {}; + } + rasterizedGlyphSet[bg]![fg] = rasterizedGlyph; } return rasterizedGlyph; } From 9dd2ccb1ad87e8af70d970eb481316097cf8b9dc Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 9 Nov 2019 22:48:28 -0800 Subject: [PATCH 042/268] Fix selection color to set correct mode --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 6 +++--- addons/xterm-addon-webgl/src/WebglRenderer.ts | 13 +++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index dd83131e1..00b216295 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -10,7 +10,7 @@ import { INDICIES_PER_CELL } from './WebglRenderer'; import { COMBINED_CHAR_BIT_MASK } from './RenderModel'; import { fill } from 'common/TypedArrayUtils'; import { slice } from './TypedArray'; -import { NULL_CELL_CODE, WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; +import { NULL_CELL_CODE, WHITESPACE_CELL_CODE, Attributes } from 'common/buffer/Constants'; import { getLuminance } from './ColorUtils'; import { Terminal, IBufferLine } from 'xterm'; import { IColorSet } from 'browser/Types'; @@ -223,8 +223,8 @@ export class GlyphRenderer { // TODO: Make fg and bg configurable, currently since the buffer doesn't // support truecolor the char atlas cannot store it. const lumi = getLuminance(this._colors.background); - const fg = lumi > 0.5 ? 7 : 0; - const bg = lumi > 0.5 ? 0 : 7; + const fg = (lumi > 0.5 ? 7 : 0) | Attributes.CM_P16; + const bg = (lumi > 0.5 ? 0 : 7) | Attributes.CM_P16; if (columnSelectMode) { const startCol = model.selection.startCol; diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index afdb7fa40..a9a2f8607 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -271,15 +271,15 @@ export class WebglRenderer extends Disposable implements IRenderer { this._model.lineLengths[y] = x + 1; } - // Nothing has changed, no updates needed - if (this._model.cells[i] === code && this._model.cells[i + 1] === attr) { - continue; - } - - // Resolve bg and fg and cache in the model + // Resolve bg and fg let bg = this._workCell.bg; let fg = this._workCell.fg; + // Nothing has changed, no updates needed + if (this._model.cells[i] === code && this._model.cells[i + 2] === bg && this._model.cells[i + 3] === fg) { + continue; + } + // If inverse flag is on, the foreground should become the background. if (this._workCell.isInverse()) { const temp = bg; @@ -303,6 +303,7 @@ export class WebglRenderer extends Disposable implements IRenderer { code = code | COMBINED_CHAR_BIT_MASK; } + // Cache the results in the model this._model.cells[i ] = code; // TODO: Remove attr from model this._model.cells[i + 1] = attr; From a2bd0e8eaac09bfcf6eea556685f821a048f5e7d Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 9 Nov 2019 22:50:22 -0800 Subject: [PATCH 043/268] Remove attr compat from webgl --- .../xterm-addon-webgl/src/CharDataCompat.ts | 28 ------------------- addons/xterm-addon-webgl/src/WebglRenderer.ts | 7 ++--- 2 files changed, 2 insertions(+), 33 deletions(-) delete mode 100644 addons/xterm-addon-webgl/src/CharDataCompat.ts diff --git a/addons/xterm-addon-webgl/src/CharDataCompat.ts b/addons/xterm-addon-webgl/src/CharDataCompat.ts deleted file mode 100644 index 13613c884..000000000 --- a/addons/xterm-addon-webgl/src/CharDataCompat.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2019 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { CellData } from 'common/buffer/CellData'; -import { FLAGS } from './Constants'; -import { IBufferLine } from 'common/Types'; - -export function getCompatAttr(bufferLine: IBufferLine, index: number): number { - // TODO: Need to move WebGL over to the new system and remove this block - const cell = new CellData(); - bufferLine.loadCell(index, cell); - const oldBg = cell.getBgColor() === -1 ? 256 : cell.getBgColor(); - const oldFg = cell.getFgColor() === -1 ? 256 : cell.getFgColor(); - const oldAttr = - (cell.isBold() ? FLAGS.BOLD : 0) | - (cell.isUnderline() ? FLAGS.UNDERLINE : 0) | - (cell.isBlink() ? FLAGS.BLINK : 0) | - (cell.isInverse() ? FLAGS.INVERSE : 0) | - (cell.isDim() ? FLAGS.DIM : 0) | - (cell.isItalic() ? FLAGS.ITALIC : 0); - const attrCompat = - oldBg | - (oldFg << 9) | - (oldAttr << 18); - return attrCompat; -} diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index a9a2f8607..9acbc8df8 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -14,14 +14,12 @@ import { IWebGL2RenderingContext } from './Types'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel'; import { Disposable } from 'common/Lifecycle'; -import { DEFAULT_COLOR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, NULL_CELL_CODE, FgFlags } from 'common/buffer/Constants'; +import { DEFAULT_COLOR, NULL_CELL_CODE, FgFlags } from 'common/buffer/Constants'; import { Terminal, IEvent } from 'xterm'; import { getLuminance } from './ColorUtils'; import { IRenderLayer } from './renderLayer/Types'; import { IRenderDimensions, IRenderer, IRequestRefreshRowsEvent } from 'browser/renderer/Types'; import { IColorSet } from 'browser/Types'; -import { FLAGS } from './Constants'; -import { getCompatAttr } from './CharDataCompat'; import { EventEmitter } from 'common/EventEmitter'; import { CellData } from 'common/buffer/CellData'; @@ -264,7 +262,6 @@ export class WebglRenderer extends Disposable implements IRenderer { const chars = this._workCell.getChars(); let code = this._workCell.getCode(); - const attr = getCompatAttr(line, x); // charData[CHAR_DATA_ATTR_INDEX]; const i = ((y * terminal.cols) + x) * INDICIES_PER_CELL; if (code !== NULL_CELL_CODE) { @@ -306,7 +303,7 @@ export class WebglRenderer extends Disposable implements IRenderer { // Cache the results in the model this._model.cells[i ] = code; // TODO: Remove attr from model - this._model.cells[i + 1] = attr; + this._model.cells[i + 1] = 0; this._model.cells[i + 2] = bg; this._model.cells[i + 3] = fg; From 2b5d229aabfa63e4d6e4d28b9fd66c754ee686c1 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 9 Nov 2019 22:56:42 -0800 Subject: [PATCH 044/268] Reduce size of model to 3 by removing attr --- addons/xterm-addon-webgl/src/Constants.ts | 12 ++---------- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 3 --- addons/xterm-addon-webgl/src/RectangleRenderer.ts | 7 +++---- addons/xterm-addon-webgl/src/RenderModel.ts | 2 +- addons/xterm-addon-webgl/src/WebglRenderer.ts | 13 +++++++------ .../xterm-addon-webgl/src/atlas/WebglCharAtlas.ts | 3 +-- 6 files changed, 14 insertions(+), 26 deletions(-) diff --git a/addons/xterm-addon-webgl/src/Constants.ts b/addons/xterm-addon-webgl/src/Constants.ts index ab18b97ab..bf39ec989 100644 --- a/addons/xterm-addon-webgl/src/Constants.ts +++ b/addons/xterm-addon-webgl/src/Constants.ts @@ -3,13 +3,5 @@ * @license MIT */ -// TODO: Should be removed after chardata workaround is fixed -export const enum FLAGS { - BOLD = 1, - UNDERLINE = 2, - BLINK = 4, - INVERSE = 8, - INVISIBLE = 16, - DIM = 32, - ITALIC = 64 -} +export const MODEL_BG_OFFSET = 2; +export const MODEL_FG_OFFSET = 3; diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index 00b216295..a28d5c10a 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -260,9 +260,6 @@ export class GlyphRenderer { let line: IBufferLine | undefined; for (let x = startCol; x < endCol; x++) { const offset = (y * this._terminal.cols + x) * INDICIES_PER_CELL; - // Because the cache uses attr as a lookup key it needs to contain the selection colors as well - let attr = model.cells[offset + 1]; - attr = attr & ~0x3ffff | bg << 9 | fg; const code = model.cells[offset]; if (code & COMBINED_CHAR_BIT_MASK) { if (!line) { diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index 70b5ee3c1..3a5f64170 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -6,12 +6,11 @@ import { createProgram, expandFloat32Array, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils'; import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelectionRenderModel } from './Types'; import { fill } from 'common/TypedArrayUtils'; -import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; -import { is256Color } from './atlas/CharAtlasUtils'; import { DEFAULT_COLOR, Attributes, FgFlags } from 'common/buffer/Constants'; import { Terminal } from 'xterm'; import { IColorSet, IColor } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; +import { MODEL_FG_OFFSET, MODEL_BG_OFFSET } from './Constants'; const enum VertexAttribLocations { POSITION = 0, @@ -251,8 +250,8 @@ export class RectangleRenderer { let currentFg = DEFAULT_COLOR; for (let x = 0; x < terminal.cols; x++) { const modelIndex = ((y * terminal.cols) + x) * 4; - const bg = model.cells[modelIndex + 2]; - const fg = model.cells[modelIndex + 1]; + const bg = model.cells[modelIndex + MODEL_BG_OFFSET]; + const fg = model.cells[modelIndex + MODEL_FG_OFFSET]; if (bg !== currentBg) { // A rectangle needs to be drawn if going from non-default to another color // TODO: DEFAULT_COLOR probably isn't right anymore? diff --git a/addons/xterm-addon-webgl/src/RenderModel.ts b/addons/xterm-addon-webgl/src/RenderModel.ts index a9ee24e9a..f55111d4d 100644 --- a/addons/xterm-addon-webgl/src/RenderModel.ts +++ b/addons/xterm-addon-webgl/src/RenderModel.ts @@ -6,7 +6,7 @@ import { IRenderModel, ISelectionRenderModel } from './Types'; import { fill } from 'common/TypedArrayUtils'; -export const RENDER_MODEL_INDICIES_PER_CELL = 4; +export const RENDER_MODEL_INDICIES_PER_CELL = 3; export const COMBINED_CHAR_BIT_MASK = 0x80000000; diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 9acbc8df8..8bbd1b64d 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -22,6 +22,7 @@ import { IRenderDimensions, IRenderer, IRequestRefreshRowsEvent } from 'browser/ import { IColorSet } from 'browser/Types'; import { EventEmitter } from 'common/EventEmitter'; import { CellData } from 'common/buffer/CellData'; +import { MODEL_BG_OFFSET, MODEL_FG_OFFSET } from './Constants'; export const INDICIES_PER_CELL = 4; @@ -273,7 +274,9 @@ export class WebglRenderer extends Disposable implements IRenderer { let fg = this._workCell.fg; // Nothing has changed, no updates needed - if (this._model.cells[i] === code && this._model.cells[i + 2] === bg && this._model.cells[i + 3] === fg) { + if (this._model.cells[i] === code && + this._model.cells[i + MODEL_BG_OFFSET] === bg && + this._model.cells[i + MODEL_FG_OFFSET] === fg) { continue; } @@ -301,11 +304,9 @@ export class WebglRenderer extends Disposable implements IRenderer { } // Cache the results in the model - this._model.cells[i ] = code; - // TODO: Remove attr from model - this._model.cells[i + 1] = 0; - this._model.cells[i + 2] = bg; - this._model.cells[i + 3] = fg; + this._model.cells[i] = code; + this._model.cells[i + MODEL_BG_OFFSET] = bg; + this._model.cells[i + MODEL_FG_OFFSET] = fg; this._glyphRenderer.updateCell(x, y, code, bg, fg, chars); } diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 9d8eb286e..b9c4a827d 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -6,11 +6,10 @@ import { ICharAtlasConfig } from './Types'; import { DIM_OPACITY } from 'browser/renderer/atlas/Constants'; import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types'; -import { DEFAULT_COLOR, DEFAULT_ATTR, FgFlags, Attributes, BgFlags } from 'common/buffer/Constants'; +import { DEFAULT_COLOR, FgFlags, Attributes, BgFlags } from 'common/buffer/Constants'; import { is256Color } from './CharAtlasUtils'; import { throwIfFalsy } from '../WebglUtils'; import { IColor } from 'browser/Types'; -import { FLAGS } from '../Constants'; import { IDisposable } from 'xterm'; // In practice we're probably never going to exhaust a texture this large. For debugging purposes, From 39f0e17edf4ff0a64d51eea31f7e4c3a01db09a6 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 9 Nov 2019 23:05:39 -0800 Subject: [PATCH 045/268] Consolidate constants, fix rectangle rendering --- addons/xterm-addon-webgl/src/Constants.ts | 7 ------- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 5 ++--- addons/xterm-addon-webgl/src/RectangleRenderer.ts | 8 ++++---- addons/xterm-addon-webgl/src/RenderModel.ts | 2 ++ addons/xterm-addon-webgl/src/WebglRenderer.ts | 15 ++++++--------- 5 files changed, 14 insertions(+), 23 deletions(-) delete mode 100644 addons/xterm-addon-webgl/src/Constants.ts diff --git a/addons/xterm-addon-webgl/src/Constants.ts b/addons/xterm-addon-webgl/src/Constants.ts deleted file mode 100644 index bf39ec989..000000000 --- a/addons/xterm-addon-webgl/src/Constants.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Copyright (c) 2019 The xterm.js authors. All rights reserved. - * @license MIT - */ - -export const MODEL_BG_OFFSET = 2; -export const MODEL_FG_OFFSET = 3; diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index a28d5c10a..7ec80907e 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -6,8 +6,7 @@ import { createProgram, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils'; import { WebglCharAtlas } from './atlas/WebglCharAtlas'; import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; -import { INDICIES_PER_CELL } from './WebglRenderer'; -import { COMBINED_CHAR_BIT_MASK } from './RenderModel'; +import { COMBINED_CHAR_BIT_MASK, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; import { fill } from 'common/TypedArrayUtils'; import { slice } from './TypedArray'; import { NULL_CELL_CODE, WHITESPACE_CELL_CODE, Attributes } from 'common/buffer/Constants'; @@ -259,7 +258,7 @@ export class GlyphRenderer { const row = y + terminal.buffer.viewportY; let line: IBufferLine | undefined; for (let x = startCol; x < endCol; x++) { - const offset = (y * this._terminal.cols + x) * INDICIES_PER_CELL; + const offset = (y * this._terminal.cols + x) * RENDER_MODEL_INDICIES_PER_CELL; const code = model.cells[offset]; if (code & COMBINED_CHAR_BIT_MASK) { if (!line) { diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index 3a5f64170..0a0880343 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -10,7 +10,7 @@ import { DEFAULT_COLOR, Attributes, FgFlags } from 'common/buffer/Constants'; import { Terminal } from 'xterm'; import { IColorSet, IColor } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; -import { MODEL_FG_OFFSET, MODEL_BG_OFFSET } from './Constants'; +import { RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; const enum VertexAttribLocations { POSITION = 0, @@ -249,9 +249,9 @@ export class RectangleRenderer { let currentBg = DEFAULT_COLOR; let currentFg = DEFAULT_COLOR; for (let x = 0; x < terminal.cols; x++) { - const modelIndex = ((y * terminal.cols) + x) * 4; - const bg = model.cells[modelIndex + MODEL_BG_OFFSET]; - const fg = model.cells[modelIndex + MODEL_FG_OFFSET]; + const modelIndex = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL; + const bg = model.cells[modelIndex + RENDER_MODEL_BG_OFFSET]; + const fg = model.cells[modelIndex + RENDER_MODEL_FG_OFFSET]; if (bg !== currentBg) { // A rectangle needs to be drawn if going from non-default to another color // TODO: DEFAULT_COLOR probably isn't right anymore? diff --git a/addons/xterm-addon-webgl/src/RenderModel.ts b/addons/xterm-addon-webgl/src/RenderModel.ts index f55111d4d..2b48047f8 100644 --- a/addons/xterm-addon-webgl/src/RenderModel.ts +++ b/addons/xterm-addon-webgl/src/RenderModel.ts @@ -7,6 +7,8 @@ import { IRenderModel, ISelectionRenderModel } from './Types'; import { fill } from 'common/TypedArrayUtils'; export const RENDER_MODEL_INDICIES_PER_CELL = 3; +export const RENDER_MODEL_BG_OFFSET = 1; +export const RENDER_MODEL_FG_OFFSET = 2; export const COMBINED_CHAR_BIT_MASK = 0x80000000; diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 8bbd1b64d..4d6428f56 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -12,7 +12,7 @@ import { WebglCharAtlas } from './atlas/WebglCharAtlas'; import { RectangleRenderer } from './RectangleRenderer'; import { IWebGL2RenderingContext } from './Types'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; -import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel'; +import { RenderModel, COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; import { Disposable } from 'common/Lifecycle'; import { DEFAULT_COLOR, NULL_CELL_CODE, FgFlags } from 'common/buffer/Constants'; import { Terminal, IEvent } from 'xterm'; @@ -22,9 +22,6 @@ import { IRenderDimensions, IRenderer, IRequestRefreshRowsEvent } from 'browser/ import { IColorSet } from 'browser/Types'; import { EventEmitter } from 'common/EventEmitter'; import { CellData } from 'common/buffer/CellData'; -import { MODEL_BG_OFFSET, MODEL_FG_OFFSET } from './Constants'; - -export const INDICIES_PER_CELL = 4; export class WebglRenderer extends Disposable implements IRenderer { private _renderLayers: IRenderLayer[]; @@ -263,7 +260,7 @@ export class WebglRenderer extends Disposable implements IRenderer { const chars = this._workCell.getChars(); let code = this._workCell.getCode(); - const i = ((y * terminal.cols) + x) * INDICIES_PER_CELL; + const i = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL; if (code !== NULL_CELL_CODE) { this._model.lineLengths[y] = x + 1; @@ -275,8 +272,8 @@ export class WebglRenderer extends Disposable implements IRenderer { // Nothing has changed, no updates needed if (this._model.cells[i] === code && - this._model.cells[i + MODEL_BG_OFFSET] === bg && - this._model.cells[i + MODEL_FG_OFFSET] === fg) { + this._model.cells[i + RENDER_MODEL_BG_OFFSET] === bg && + this._model.cells[i + RENDER_MODEL_FG_OFFSET] === fg) { continue; } @@ -305,8 +302,8 @@ export class WebglRenderer extends Disposable implements IRenderer { // Cache the results in the model this._model.cells[i] = code; - this._model.cells[i + MODEL_BG_OFFSET] = bg; - this._model.cells[i + MODEL_FG_OFFSET] = fg; + this._model.cells[i + RENDER_MODEL_BG_OFFSET] = bg; + this._model.cells[i + RENDER_MODEL_FG_OFFSET] = fg; this._glyphRenderer.updateCell(x, y, code, bg, fg, chars); } From 3773b22439b6630aac5c838a225e93cae91b7694 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 9 Nov 2019 23:41:00 -0800 Subject: [PATCH 046/268] Support true color... mostly --- .../src/RectangleRenderer.ts | 22 +++++++++------ .../src/atlas/WebglCharAtlas.ts | 27 ++++++++++++++++--- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index 0a0880343..4e7118462 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -11,6 +11,7 @@ import { Terminal } from 'xterm'; import { IColorSet, IColor } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; import { RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; +import { AttributeData } from 'common/buffer/AttributeData'; const enum VertexAttribLocations { POSITION = 0, @@ -274,26 +275,31 @@ export class RectangleRenderer { } private _updateRectangle(vertices: IVertices, offset: number, fg: number, bg: number, startX: number, endX: number, y: number): void { - let color: IColor | null = null; + let rgba: number | undefined; + const colorMode = bg & Attributes.CM_MASK; if (fg & FgFlags.INVERSE) { // Inverted color - color = this._colors.foreground; + rgba = this._colors.foreground.rgba; } else if ((bg & Attributes.CM_MASK) === Attributes.CM_P16 || (bg & Attributes.CM_MASK) === Attributes.CM_P256) { // 256 palette - color = this._colors.ansi[bg & Attributes.PCOLOR_MASK]; + rgba = this._colors.ansi[bg & Attributes.PCOLOR_MASK].rgba; + } else if (colorMode === Attributes.CM_RGB) { + // True color + // TODO: Use a switch instead? + rgba = (bg & Attributes.RGB_MASK) << 8; } else { - // TODO: Support true color // Default color - color = this._colors.background; + rgba = this._colors.background.rgba; } if (vertices.attributes.length < offset + 4) { vertices.attributes = expandFloat32Array(vertices.attributes, this._terminal.rows * this._terminal.cols * INDICES_PER_RECTANGLE); } const x1 = startX * this._dimensions.scaledCellWidth; const y1 = y * this._dimensions.scaledCellHeight; - const r = ((color.rgba >> 24) & 0xFF) / 255; - const g = ((color.rgba >> 16) & 0xFF) / 255; - const b = ((color.rgba >> 8 ) & 0xFF) / 255; + const r = ((rgba >> 24) & 0xFF) / 255; + const g = ((rgba >> 16) & 0xFF) / 255; + const b = ((rgba >> 8 ) & 0xFF) / 255; + console.log(r, g, b); this._addRectangle(vertices.attributes, offset, x1, y1, (endX - startX) * this._dimensions.scaledCellWidth, this._dimensions.scaledCellHeight, r, g, b, 1); } diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index b9c4a827d..00b249885 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -11,6 +11,7 @@ import { is256Color } from './CharAtlasUtils'; import { throwIfFalsy } from '../WebglUtils'; import { IColor } from 'browser/Types'; import { IDisposable } from 'xterm'; +import { AttributeData } from 'common/buffer/AttributeData'; // In practice we're probably never going to exhaust a texture this large. For debugging purposes, // however, it can be useful to set this to a really tiny value, to verify that LRU eviction works. @@ -186,18 +187,33 @@ export class WebglCharAtlas implements IDisposable { return this._config.colors.foreground; } else if (is256Color(bg)) { return this._getColorFromAnsiIndex(bg & Attributes.PCOLOR_MASK); + } else if ((bg & Attributes.CM_MASK) === Attributes.CM_RGB) { + // TODO: Use a switch for the color mode? + // True color + const rgb = bg & Attributes.RGB_MASK; + const arr = AttributeData.toColorRGB(rgb); + // TODO: This object creation is slow + return { + rgba: rgb << 255, + css: `#${toPaddedHex(arr[0])}${toPaddedHex(arr[1])}${toPaddedHex(arr[2])}` + }; } - // TODO: Support true color return this._config.colors.background; } - private _getForegroundColor(fg: number): IColor { + private _getForegroundColor(fg: number): { css: string } { + // TODO: Just return the string value if (fg & FgFlags.INVERSE) { return this._config.colors.background; } else if (is256Color(fg)) { return this._getColorFromAnsiIndex(fg & Attributes.PCOLOR_MASK); + } else if ((fg & Attributes.CM_MASK) === Attributes.CM_RGB) { + // TODO: Use a switch for the color mode? + // TODO: True color + const rgb = fg & Attributes.RGB_MASK; + const arr = AttributeData.toColorRGB(rgb); + return { css: `#${toPaddedHex(arr[0])}${toPaddedHex(arr[1])}${toPaddedHex(arr[2])}` }; } - // TODO: Support true color return this._config.colors.foreground; } @@ -411,3 +427,8 @@ function clearColor(imageData: ImageData, color: IColor): boolean { } return isEmpty; } + +function toPaddedHex(c: number): string { + const s = c.toString(16); + return s.length < 2 ? '0' + s : s; +} From a2da8f9fbb3dd5a66bcf63f81222c15848168f42 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 10 Nov 2019 00:23:08 -0800 Subject: [PATCH 047/268] Fix true color glyphs not having background cleared --- addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 00b249885..0dc3b9742 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -194,7 +194,7 @@ export class WebglCharAtlas implements IDisposable { const arr = AttributeData.toColorRGB(rgb); // TODO: This object creation is slow return { - rgba: rgb << 255, + rgba: rgb << 8, css: `#${toPaddedHex(arr[0])}${toPaddedHex(arr[1])}${toPaddedHex(arr[2])}` }; } From b13e9066bdc028045457fed73851d408dd0b06aa Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 10 Nov 2019 00:29:50 -0800 Subject: [PATCH 048/268] Use a switch for color mode conditions --- .../src/RectangleRenderer.ts | 23 ++++---- .../src/atlas/WebglCharAtlas.ts | 54 +++++++++++-------- 2 files changed, 45 insertions(+), 32 deletions(-) diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index 4e7118462..ed7e8f0ce 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -280,17 +280,21 @@ export class RectangleRenderer { if (fg & FgFlags.INVERSE) { // Inverted color rgba = this._colors.foreground.rgba; - } else if ((bg & Attributes.CM_MASK) === Attributes.CM_P16 || (bg & Attributes.CM_MASK) === Attributes.CM_P256) { - // 256 palette - rgba = this._colors.ansi[bg & Attributes.PCOLOR_MASK].rgba; - } else if (colorMode === Attributes.CM_RGB) { - // True color - // TODO: Use a switch instead? - rgba = (bg & Attributes.RGB_MASK) << 8; } else { - // Default color - rgba = this._colors.background.rgba; + switch (colorMode) { + case Attributes.CM_P16: + case Attributes.CM_P256: + rgba = this._colors.ansi[bg & Attributes.PCOLOR_MASK].rgba; + break; + case Attributes.CM_RGB: + rgba = (bg & Attributes.RGB_MASK) << 8; + break; + case Attributes.CM_DEFAULT: + default: + rgba = this._colors.background.rgba; + } } + if (vertices.attributes.length < offset + 4) { vertices.attributes = expandFloat32Array(vertices.attributes, this._terminal.rows * this._terminal.cols * INDICES_PER_RECTANGLE); } @@ -299,7 +303,6 @@ export class RectangleRenderer { const r = ((rgba >> 24) & 0xFF) / 255; const g = ((rgba >> 16) & 0xFF) / 255; const b = ((rgba >> 8 ) & 0xFF) / 255; - console.log(r, g, b); this._addRectangle(vertices.attributes, offset, x1, y1, (endX - startX) * this._dimensions.scaledCellWidth, this._dimensions.scaledCellHeight, r, g, b, 1); } diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 0dc3b9742..9a923863d 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -185,36 +185,46 @@ export class WebglCharAtlas implements IDisposable { return TRANSPARENT_COLOR; } else if (fg & FgFlags.INVERSE) { return this._config.colors.foreground; - } else if (is256Color(bg)) { - return this._getColorFromAnsiIndex(bg & Attributes.PCOLOR_MASK); - } else if ((bg & Attributes.CM_MASK) === Attributes.CM_RGB) { - // TODO: Use a switch for the color mode? - // True color - const rgb = bg & Attributes.RGB_MASK; - const arr = AttributeData.toColorRGB(rgb); - // TODO: This object creation is slow - return { - rgba: rgb << 8, - css: `#${toPaddedHex(arr[0])}${toPaddedHex(arr[1])}${toPaddedHex(arr[2])}` - }; } - return this._config.colors.background; + + const colorMode = bg & Attributes.CM_MASK; + switch (colorMode) { + case Attributes.CM_P16: + case Attributes.CM_P256: + return this._getColorFromAnsiIndex(bg & Attributes.PCOLOR_MASK); + case Attributes.CM_RGB: + const rgb = bg & Attributes.RGB_MASK; + const arr = AttributeData.toColorRGB(rgb); + // TODO: This object creation is slow + return { + rgba: rgb << 8, + css: `#${toPaddedHex(arr[0])}${toPaddedHex(arr[1])}${toPaddedHex(arr[2])}` + }; + case Attributes.CM_DEFAULT: + default: + return this._config.colors.background; + } } private _getForegroundColor(fg: number): { css: string } { // TODO: Just return the string value if (fg & FgFlags.INVERSE) { return this._config.colors.background; - } else if (is256Color(fg)) { - return this._getColorFromAnsiIndex(fg & Attributes.PCOLOR_MASK); - } else if ((fg & Attributes.CM_MASK) === Attributes.CM_RGB) { - // TODO: Use a switch for the color mode? - // TODO: True color - const rgb = fg & Attributes.RGB_MASK; - const arr = AttributeData.toColorRGB(rgb); - return { css: `#${toPaddedHex(arr[0])}${toPaddedHex(arr[1])}${toPaddedHex(arr[2])}` }; } - return this._config.colors.foreground; + + const colorMode = fg & Attributes.CM_MASK; + switch (colorMode) { + case Attributes.CM_P16: + case Attributes.CM_P256: + return this._getColorFromAnsiIndex(fg & Attributes.PCOLOR_MASK); + case Attributes.CM_RGB: + const rgb = fg & Attributes.RGB_MASK; + const arr = AttributeData.toColorRGB(rgb); + return { css: `#${toPaddedHex(arr[0])}${toPaddedHex(arr[1])}${toPaddedHex(arr[2])}` }; + case Attributes.CM_DEFAULT: + default: + return this._config.colors.foreground; + } } private _drawToCache(code: number, bg: number, fg: number): IRasterizedGlyph; From 149cc76803c79d525a69bea4862d872eae67e91c Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 10 Nov 2019 00:30:58 -0800 Subject: [PATCH 049/268] Avoid extra object creation for foreground fill --- .../xterm-addon-webgl/src/atlas/WebglCharAtlas.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 9a923863d..69acfdbf6 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -206,24 +206,23 @@ export class WebglCharAtlas implements IDisposable { } } - private _getForegroundColor(fg: number): { css: string } { - // TODO: Just return the string value + private _getForegroundCss(fg: number): string { if (fg & FgFlags.INVERSE) { - return this._config.colors.background; + return this._config.colors.background.css; } const colorMode = fg & Attributes.CM_MASK; switch (colorMode) { case Attributes.CM_P16: case Attributes.CM_P256: - return this._getColorFromAnsiIndex(fg & Attributes.PCOLOR_MASK); + return this._getColorFromAnsiIndex(fg & Attributes.PCOLOR_MASK).css; case Attributes.CM_RGB: const rgb = fg & Attributes.RGB_MASK; const arr = AttributeData.toColorRGB(rgb); - return { css: `#${toPaddedHex(arr[0])}${toPaddedHex(arr[1])}${toPaddedHex(arr[2])}` }; + return `#${toPaddedHex(arr[0])}${toPaddedHex(arr[1])}${toPaddedHex(arr[2])}`; case Attributes.CM_DEFAULT: default: - return this._config.colors.foreground; + return this._config.colors.foreground.css; } } @@ -256,7 +255,7 @@ export class WebglCharAtlas implements IDisposable { `${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`; this._tmpCtx.textBaseline = 'top'; - this._tmpCtx.fillStyle = this._getForegroundColor(fg).css; + this._tmpCtx.fillStyle = this._getForegroundCss(fg); // Apply alpha to dim the character if (dim) { From b56e29a35bfc7ff26c7fd7e702df744fecaa4a6e Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 10 Nov 2019 00:39:46 -0800 Subject: [PATCH 050/268] Import clean up --- addons/xterm-addon-webgl/src/RectangleRenderer.ts | 1 - addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts | 2 +- addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index ed7e8f0ce..7746acb18 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -11,7 +11,6 @@ import { Terminal } from 'xterm'; import { IColorSet, IColor } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; import { RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; -import { AttributeData } from 'common/buffer/AttributeData'; const enum VertexAttribLocations { POSITION = 0, diff --git a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts index 821b53a48..1fd4c4f6b 100644 --- a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts +++ b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts @@ -4,7 +4,7 @@ */ import { ICharAtlasConfig } from './Types'; -import { DEFAULT_COLOR, Attributes } from 'common/buffer/Constants'; +import { Attributes } from 'common/buffer/Constants'; import { Terminal, FontWeight } from 'xterm'; import { IColorSet, IColor } from 'browser/Types'; diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 69acfdbf6..c364c3c17 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -7,7 +7,6 @@ import { ICharAtlasConfig } from './Types'; import { DIM_OPACITY } from 'browser/renderer/atlas/Constants'; import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types'; import { DEFAULT_COLOR, FgFlags, Attributes, BgFlags } from 'common/buffer/Constants'; -import { is256Color } from './CharAtlasUtils'; import { throwIfFalsy } from '../WebglUtils'; import { IColor } from 'browser/Types'; import { IDisposable } from 'xterm'; From 2cde01139781642f4b4d03ddedcc480f35137fa5 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 10 Nov 2019 00:49:10 -0800 Subject: [PATCH 051/268] Add retries to onRender test I've seen this flaky locally and in CI --- test/api/Terminal.api.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index 0b05736d7..40deecf20 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -356,7 +356,8 @@ describe('API Integration Tests', function(): void { await pollFor(page, `window.callCount`, 2); }); - it('onRender', async () => { + it('onRender', async function (): Promise { + this.retries(3); await openTerminal(); await timeout(20); // Ensure all init events are fired await page.evaluate(` From 811307562ba9b4eca27f930f51096d3bbeb7a2fc Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 10 Nov 2019 00:56:43 -0800 Subject: [PATCH 052/268] Use actual default color for rectangle renderer --- addons/xterm-addon-webgl/src/RectangleRenderer.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index 7746acb18..d5e245c06 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -6,7 +6,7 @@ import { createProgram, expandFloat32Array, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils'; import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelectionRenderModel } from './Types'; import { fill } from 'common/TypedArrayUtils'; -import { DEFAULT_COLOR, Attributes, FgFlags } from 'common/buffer/Constants'; +import { Attributes, FgFlags } from 'common/buffer/Constants'; import { Terminal } from 'xterm'; import { IColorSet, IColor } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; @@ -246,16 +246,15 @@ export class RectangleRenderer { for (let y = 0; y < terminal.rows; y++) { let currentStartX = -1; - let currentBg = DEFAULT_COLOR; - let currentFg = DEFAULT_COLOR; + let currentBg = 0; + let currentFg = 0; for (let x = 0; x < terminal.cols; x++) { const modelIndex = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL; const bg = model.cells[modelIndex + RENDER_MODEL_BG_OFFSET]; const fg = model.cells[modelIndex + RENDER_MODEL_FG_OFFSET]; if (bg !== currentBg) { // A rectangle needs to be drawn if going from non-default to another color - // TODO: DEFAULT_COLOR probably isn't right anymore? - if (currentBg !== DEFAULT_COLOR) { + if (currentBg !== 0) { const offset = rectangleCount++ * INDICES_PER_RECTANGLE; this._updateRectangle(vertices, offset, currentFg, currentBg, currentStartX, x, y); } @@ -265,7 +264,7 @@ export class RectangleRenderer { } } // Finish rectangle if it's still going - if (currentBg !== DEFAULT_COLOR) { + if (currentBg !== 0) { const offset = rectangleCount++ * INDICES_PER_RECTANGLE; this._updateRectangle(vertices, offset, currentFg, currentBg, currentStartX, terminal.cols, y); } From 01628362f566524e55aa15f58f3ac1fd5178336c Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 10 Nov 2019 01:19:27 -0800 Subject: [PATCH 053/268] Support selection color --- addons/xterm-addon-webgl/src/ColorUtils.ts | 14 ----------- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 23 ++++++++----------- addons/xterm-addon-webgl/src/WebglRenderer.ts | 14 ----------- 3 files changed, 9 insertions(+), 42 deletions(-) delete mode 100644 addons/xterm-addon-webgl/src/ColorUtils.ts diff --git a/addons/xterm-addon-webgl/src/ColorUtils.ts b/addons/xterm-addon-webgl/src/ColorUtils.ts deleted file mode 100644 index 80372e650..000000000 --- a/addons/xterm-addon-webgl/src/ColorUtils.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * @license MIT - * Copyright (c) 2018 The xterm.js authors. All rights reserved. - */ - -import { IColor } from 'browser/Types'; - -export function getLuminance(color: IColor): number { - // Coefficients taken from: https://www.w3.org/TR/AERT/#color-contrast - const r = color.rgba >> 24 & 0xff; - const g = color.rgba >> 16 & 0xff; - const b = color.rgba >> 8 & 0xff; - return (0.299 * r + 0.587 * g + 0.114 * b) / 255; -} diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index 7ec80907e..3ee9cdb51 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -6,11 +6,10 @@ import { createProgram, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils'; import { WebglCharAtlas } from './atlas/WebglCharAtlas'; import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; -import { COMBINED_CHAR_BIT_MASK, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; +import { COMBINED_CHAR_BIT_MASK, RENDER_MODEL_INDICIES_PER_CELL, RENDER_MODEL_FG_OFFSET } from './RenderModel'; import { fill } from 'common/TypedArrayUtils'; import { slice } from './TypedArray'; import { NULL_CELL_CODE, WHITESPACE_CELL_CODE, Attributes } from 'common/buffer/Constants'; -import { getLuminance } from './ColorUtils'; import { Terminal, IBufferLine } from 'xterm'; import { IColorSet } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; @@ -219,41 +218,37 @@ export class GlyphRenderer { this._vertices.selectionAttributes = slice(this._vertices.attributes, 0); - // TODO: Make fg and bg configurable, currently since the buffer doesn't - // support truecolor the char atlas cannot store it. - const lumi = getLuminance(this._colors.background); - const fg = (lumi > 0.5 ? 7 : 0) | Attributes.CM_P16; - const bg = (lumi > 0.5 ? 0 : 7) | Attributes.CM_P16; + const bg = (this._colors.selection.rgba >>> 8) | Attributes.CM_RGB; if (columnSelectMode) { const startCol = model.selection.startCol; const width = model.selection.endCol - startCol; const height = model.selection.viewportCappedEndRow - model.selection.viewportCappedStartRow + 1; for (let y = model.selection.viewportCappedStartRow; y < model.selection.viewportCappedStartRow + height; y++) { - this._updateSelectionRange(startCol, startCol + width, y, model, bg, fg); + this._updateSelectionRange(startCol, startCol + width, y, model, bg); } } else { // Draw first row const startCol = model.selection.viewportStartRow === model.selection.viewportCappedStartRow ? model.selection.startCol : 0; const startRowEndCol = model.selection.viewportCappedStartRow === model.selection.viewportCappedEndRow ? model.selection.endCol : terminal.cols; - this._updateSelectionRange(startCol, startRowEndCol, model.selection.viewportCappedStartRow, model, bg, fg); + this._updateSelectionRange(startCol, startRowEndCol, model.selection.viewportCappedStartRow, model, bg); // Draw middle rows const middleRowsCount = Math.max(model.selection.viewportCappedEndRow - model.selection.viewportCappedStartRow - 1, 0); for (let y = model.selection.viewportCappedStartRow + 1; y <= model.selection.viewportCappedStartRow + middleRowsCount; y++) { - this._updateSelectionRange(0, startRowEndCol, y, model, bg, fg); + this._updateSelectionRange(0, startRowEndCol, y, model, bg); } // Draw final row if (model.selection.viewportCappedStartRow !== model.selection.viewportCappedEndRow) { // Only draw viewportEndRow if it's not the same as viewportStartRow const endCol = model.selection.viewportEndRow === model.selection.viewportCappedEndRow ? model.selection.endCol : terminal.cols; - this._updateSelectionRange(0, endCol, model.selection.viewportCappedEndRow, model, bg, fg); + this._updateSelectionRange(0, endCol, model.selection.viewportCappedEndRow, model, bg); } } } - private _updateSelectionRange(startCol: number, endCol: number, y: number, model: IRenderModel, bg: number, fg: number): void { + private _updateSelectionRange(startCol: number, endCol: number, y: number, model: IRenderModel, bg: number): void { const terminal = this._terminal; const row = y + terminal.buffer.viewportY; let line: IBufferLine | undefined; @@ -265,9 +260,9 @@ export class GlyphRenderer { line = terminal.buffer.getLine(row); } const chars = line!.getCell(x)!.char; - this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, fg, chars); + 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, fg); + this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, model.cells[offset + RENDER_MODEL_FG_OFFSET]); } } } diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 4d6428f56..5eae61db6 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -16,7 +16,6 @@ import { RenderModel, COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MOD import { Disposable } from 'common/Lifecycle'; import { DEFAULT_COLOR, NULL_CELL_CODE, FgFlags } from 'common/buffer/Constants'; import { Terminal, IEvent } from 'xterm'; -import { getLuminance } from './ColorUtils'; import { IRenderLayer } from './renderLayer/Types'; import { IRenderDimensions, IRenderer, IRequestRefreshRowsEvent } from 'browser/renderer/Types'; import { IColorSet } from 'browser/Types'; @@ -52,8 +51,6 @@ export class WebglRenderer extends Disposable implements IRenderer { this._core = (this._terminal)._core; - this._applyBgLuminanceBasedSelection(); - this._renderLayers = [ new LinkRenderLayer(this._core.screenElement, 2, this._colors, this._core), new CursorRenderLayer(this._core.screenElement, 3, this._colors, this._onRequestRefreshRows) @@ -101,20 +98,9 @@ export class WebglRenderer extends Disposable implements IRenderer { super.dispose(); } - private _applyBgLuminanceBasedSelection(): void { - // HACK: This is needed until webgl renderer adds support for selection colors - if (getLuminance(this._colors.background) > 0.5) { - this._colors.selection = { css: '#000', rgba: 255 }; - } else { - this._colors.selection = { css: '#fff', rgba: 4294967295 }; - } - } - public setColors(colors: IColorSet): void { this._colors = colors; - this._applyBgLuminanceBasedSelection(); - // Clear layers and force a full render this._renderLayers.forEach(l => { l.setColors(this._terminal, this._colors); From 8bf837795ffff9f70b17e0714f13e173bd77cb7f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 10 Nov 2019 10:29:50 -0800 Subject: [PATCH 054/268] Add tests for webgl colors 16-255 --- .../src/WebglRenderer.api.ts | 81 +++++++++++++++---- 1 file changed, 67 insertions(+), 14 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 51dae37ad..3f7dee357 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -7,6 +7,7 @@ import * as puppeteer from 'puppeteer'; import { ITerminalOptions } from '../../../src/Types'; import { ITheme } from 'xterm'; import { assert } from 'chai'; +import deepEqual = require('deep-equal'); const APP = 'http://127.0.0.1:3000/test'; @@ -15,7 +16,7 @@ let page: puppeteer.Page; const width = 800; const height = 600; -describe('WebGL Renderer Integration Tests', function(): void { +describe.only('WebGL Renderer Integration Tests', function(): void { it('dispose removes renderer canvases', async () => { await setupBrowser(); assert.equal(await page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 3); @@ -29,7 +30,7 @@ describe('WebGL Renderer Integration Tests', function(): void { after(async () => browser.close()); beforeEach(async () => page.evaluate(`window.term.reset()`)); - it('foreground colors normal', async () => { + it('foreground 0-15', async () => { const theme: ITheme = { black: '#010203', red: '#040506', @@ -52,7 +53,7 @@ describe('WebGL Renderer Integration Tests', function(): void { await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); }); - it('foreground colors bright', async () => { + it('foreground 0-15 bright', async () => { const theme: ITheme = { brightBlack: '#010203', brightRed: '#040506', @@ -75,7 +76,7 @@ describe('WebGL Renderer Integration Tests', function(): void { await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); }); - it('background colors normal', async () => { + it('background 0-15 normal', async () => { const theme: ITheme = { black: '#010203', red: '#040506', @@ -98,7 +99,7 @@ describe('WebGL Renderer Integration Tests', function(): void { await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); }); - it('background colors bright', async () => { + it('background 0-15 bright', async () => { const theme: ITheme = { brightBlack: '#010203', brightRed: '#040506', @@ -120,6 +121,46 @@ describe('WebGL Renderer Integration Tests', function(): void { await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]); await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); }); + + it('background 16-255', async function(): Promise { + let data = ''; + for (let x = 0; x < 240 / 16; x++) { + for (let y = 0; y < 16; y++) { + data += `\\x1b[48;5;${16 + x * 16 + y}m \x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + const cssColor = COLORS_16_TO_255[y * 16 + x]; + const r = parseInt(cssColor.substr(1, 2), 16); + const g = parseInt(cssColor.substr(3, 2), 16); + const b = parseInt(cssColor.substr(5, 2), 16); + await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); + } + } + }); + + it('foreground 16-255', async function(): Promise { + let data = ''; + for (let x = 0; x < 240 / 16; x++) { + for (let y = 0; y < 16; y++) { + data += `\\x1b[38;5;${16 + x * 16 + y}m█\x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + const cssColor = COLORS_16_TO_255[y * 16 + x]; + const r = parseInt(cssColor.substr(1, 2), 16); + const g = parseInt(cssColor.substr(3, 2), 16); + const b = parseInt(cssColor.substr(5, 2), 16); + await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); + } + } + }); }); }); @@ -168,20 +209,32 @@ async function setupBrowser(): Promise { `); } -async function pollFor(page: puppeteer.Page, evalOrFn: string | (() => Promise), val: T, preFn?: () => Promise): Promise { +export async function pollFor(page: puppeteer.Page, evalOrFn: string | (() => Promise), val: T, preFn?: () => Promise): Promise { if (preFn) { await preFn(); } const result = typeof evalOrFn === 'string' ? await page.evaluate(evalOrFn) : await evalOrFn(); - let equal = false; - if (typeof result === 'object') { - equal = Object.keys(result).every(e => result[e] === (val as any)[e]); - } else { - equal = result === val; - } - if (!equal) { + if (!deepEqual(result, val)) { return new Promise(r => { - setTimeout(() => r(pollFor(page, evalOrFn, val, preFn)), 10); + setTimeout(() => r(pollFor(page, evalOrFn, val, preFn)), 1); }); } } + +const COLORS_16_TO_255 = [ + '#000000', '#00005f', '#000087', '#0000af', '#0000d7', '#0000ff', '#005f00', '#005f5f', '#005f87', '#005faf', '#005fd7', '#005fff', '#008700', '#00875f', '#008787', '#0087af', + '#0087d7', '#0087ff', '#00af00', '#00af5f', '#00af87', '#00afaf', '#00afd7', '#00afff', '#00d700', '#00d75f', '#00d787', '#00d7af', '#00d7d7', '#00d7ff', '#00ff00', '#00ff5f', + '#00ff87', '#00ffaf', '#00ffd7', '#00ffff', '#5f0000', '#5f005f', '#5f0087', '#5f00af', '#5f00d7', '#5f00ff', '#5f5f00', '#5f5f5f', '#5f5f87', '#5f5faf', '#5f5fd7', '#5f5fff', + '#5f8700', '#5f875f', '#5f8787', '#5f87af', '#5f87d7', '#5f87ff', '#5faf00', '#5faf5f', '#5faf87', '#5fafaf', '#5fafd7', '#5fafff', '#5fd700', '#5fd75f', '#5fd787', '#5fd7af', + '#5fd7d7', '#5fd7ff', '#5fff00', '#5fff5f', '#5fff87', '#5fffaf', '#5fffd7', '#5fffff', '#870000', '#87005f', '#870087', '#8700af', '#8700d7', '#8700ff', '#875f00', '#875f5f', + '#875f87', '#875faf', '#875fd7', '#875fff', '#878700', '#87875f', '#878787', '#8787af', '#8787d7', '#8787ff', '#87af00', '#87af5f', '#87af87', '#87afaf', '#87afd7', '#87afff', + '#87d700', '#87d75f', '#87d787', '#87d7af', '#87d7d7', '#87d7ff', '#87ff00', '#87ff5f', '#87ff87', '#87ffaf', '#87ffd7', '#87ffff', '#af0000', '#af005f', '#af0087', '#af00af', + '#af00d7', '#af00ff', '#af5f00', '#af5f5f', '#af5f87', '#af5faf', '#af5fd7', '#af5fff', '#af8700', '#af875f', '#af8787', '#af87af', '#af87d7', '#af87ff', '#afaf00', '#afaf5f', + '#afaf87', '#afafaf', '#afafd7', '#afafff', '#afd700', '#afd75f', '#afd787', '#afd7af', '#afd7d7', '#afd7ff', '#afff00', '#afff5f', '#afff87', '#afffaf', '#afffd7', '#afffff', + '#d70000', '#d7005f', '#d70087', '#d700af', '#d700d7', '#d700ff', '#d75f00', '#d75f5f', '#d75f87', '#d75faf', '#d75fd7', '#d75fff', '#d78700', '#d7875f', '#d78787', '#d787af', + '#d787d7', '#d787ff', '#d7af00', '#d7af5f', '#d7af87', '#d7afaf', '#d7afd7', '#d7afff', '#d7d700', '#d7d75f', '#d7d787', '#d7d7af', '#d7d7d7', '#d7d7ff', '#d7ff00', '#d7ff5f', + '#d7ff87', '#d7ffaf', '#d7ffd7', '#d7ffff', '#ff0000', '#ff005f', '#ff0087', '#ff00af', '#ff00d7', '#ff00ff', '#ff5f00', '#ff5f5f', '#ff5f87', '#ff5faf', '#ff5fd7', '#ff5fff', + '#ff8700', '#ff875f', '#ff8787', '#ff87af', '#ff87d7', '#ff87ff', '#ffaf00', '#ffaf5f', '#ffaf87', '#ffafaf', '#ffafd7', '#ffafff', '#ffd700', '#ffd75f', '#ffd787', '#ffd7af', + '#ffd7d7', '#ffd7ff', '#ffff00', '#ffff5f', '#ffff87', '#ffffaf', '#ffffd7', '#ffffff', '#080808', '#121212', '#1c1c1c', '#262626', '#303030', '#3a3a3a', '#444444', '#4e4e4e', + '#585858', '#626262', '#6c6c6c', '#767676', '#808080', '#8a8a8a', '#949494', '#9e9e9e', '#a8a8a8', '#b2b2b2', '#bcbcbc', '#c6c6c6', '#d0d0d0', '#dadada', '#e4e4e4', '#eeeeee' +]; From 753ad30dbc0b48b96483d31fbdbf38d18c1f5d3d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 10 Nov 2019 10:38:19 -0800 Subject: [PATCH 055/268] Add true color fg tests for webgl --- .../src/WebglRenderer.api.ts | 88 +++++++++++++++++-- 1 file changed, 80 insertions(+), 8 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 3f7dee357..71add30a6 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -122,11 +122,11 @@ describe.only('WebGL Renderer Integration Tests', function(): void { await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); }); - it('background 16-255', async function(): Promise { + it('background 16-255', async () => { let data = ''; - for (let x = 0; x < 240 / 16; x++) { - for (let y = 0; y < 16; y++) { - data += `\\x1b[48;5;${16 + x * 16 + y}m \x1b[0m`; + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + data += `\\x1b[48;5;${16 + y * 16 + x}m \x1b[0m`; } data += '\\r\\n'; } @@ -142,11 +142,11 @@ describe.only('WebGL Renderer Integration Tests', function(): void { } }); - it('foreground 16-255', async function(): Promise { + it('foreground 16-255', async () => { let data = ''; - for (let x = 0; x < 240 / 16; x++) { - for (let y = 0; y < 16; y++) { - data += `\\x1b[38;5;${16 + x * 16 + y}m█\x1b[0m`; + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + data += `\\x1b[38;5;${16 + y * 16 + x}m█\x1b[0m`; } data += '\\r\\n'; } @@ -161,6 +161,78 @@ describe.only('WebGL Renderer Integration Tests', function(): void { } } }); + + it('foreground true color red', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\\x1b[38;2;${i};0;0m█\x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]); + } + } + }); + + it('foreground true color green', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\\x1b[38;2;0;${i};0m█\x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]); + } + } + }); + + it('foreground true color blue', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\\x1b[38;2;0;0;${i}m█\x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]); + } + } + }); + + it('foreground true color grey', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\\x1b[38;2;${i};${i};${i}m█\x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]); + } + } + }); }); }); From 350ca8d5fb08000857e6a792f098560d257fac6b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 10 Nov 2019 10:39:56 -0800 Subject: [PATCH 056/268] Organize tests --- .../src/WebglRenderer.api.ts | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 71add30a6..bb2f59695 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -53,6 +53,29 @@ describe.only('WebGL Renderer Integration Tests', function(): void { await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); }); + it('background 0-15', async () => { + const theme: ITheme = { + black: '#010203', + red: '#040506', + green: '#070809', + yellow: '#0a0b0c', + blue: '#0d0e0f', + magenta: '#101112', + cyan: '#131415', + white: '#161718' + }; + await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); + await writeSync(`\\x1b[40m \\x1b[41m \\x1b[42m \\x1b[43m \\x1b[44m \\x1b[45m \\x1b[46m \\x1b[47m `); + await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]); + await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]); + await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]); + await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]); + await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]); + await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]); + await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]); + await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); + }); + it('foreground 0-15 bright', async () => { const theme: ITheme = { brightBlack: '#010203', @@ -76,29 +99,6 @@ describe.only('WebGL Renderer Integration Tests', function(): void { await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); }); - it('background 0-15 normal', async () => { - const theme: ITheme = { - black: '#010203', - red: '#040506', - green: '#070809', - yellow: '#0a0b0c', - blue: '#0d0e0f', - magenta: '#101112', - cyan: '#131415', - white: '#161718' - }; - await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); - await writeSync(`\\x1b[40m \\x1b[41m \\x1b[42m \\x1b[43m \\x1b[44m \\x1b[45m \\x1b[46m \\x1b[47m `); - await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]); - await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]); - await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]); - await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]); - await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]); - await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]); - await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]); - await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); - }); - it('background 0-15 bright', async () => { const theme: ITheme = { brightBlack: '#010203', @@ -122,11 +122,11 @@ describe.only('WebGL Renderer Integration Tests', function(): void { await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); }); - it('background 16-255', async () => { + it('foreground 16-255', async () => { let data = ''; for (let y = 0; y < 240 / 16; y++) { for (let x = 0; x < 16; x++) { - data += `\\x1b[48;5;${16 + y * 16 + x}m \x1b[0m`; + data += `\\x1b[38;5;${16 + y * 16 + x}m█\x1b[0m`; } data += '\\r\\n'; } @@ -142,11 +142,11 @@ describe.only('WebGL Renderer Integration Tests', function(): void { } }); - it('foreground 16-255', async () => { + it('background 16-255', async () => { let data = ''; for (let y = 0; y < 240 / 16; y++) { for (let x = 0; x < 16; x++) { - data += `\\x1b[38;5;${16 + y * 16 + x}m█\x1b[0m`; + data += `\\x1b[48;5;${16 + y * 16 + x}m \x1b[0m`; } data += '\\r\\n'; } From 2b2bd91e2ef5a8208e569add26b8e91679ea7908 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 10 Nov 2019 10:41:15 -0800 Subject: [PATCH 057/268] Add background true color tests --- .../src/WebglRenderer.api.ts | 74 ++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index bb2f59695..304973fef 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -16,7 +16,7 @@ let page: puppeteer.Page; const width = 800; const height = 600; -describe.only('WebGL Renderer Integration Tests', function(): void { +describe('WebGL Renderer Integration Tests', function(): void { it('dispose removes renderer canvases', async () => { await setupBrowser(); assert.equal(await page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 3); @@ -180,6 +180,24 @@ describe.only('WebGL Renderer Integration Tests', function(): void { } }); + it('background true color red', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\\x1b[48;2;${i};0;0m \x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]); + } + } + }); + it('foreground true color green', async () => { let data = ''; for (let y = 0; y < 16; y++) { @@ -198,6 +216,24 @@ describe.only('WebGL Renderer Integration Tests', function(): void { } }); + it('background true color green', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\\x1b[48;2;0;${i};0m \x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]); + } + } + }); + it('foreground true color blue', async () => { let data = ''; for (let y = 0; y < 16; y++) { @@ -216,6 +252,24 @@ describe.only('WebGL Renderer Integration Tests', function(): void { } }); + it('background true color blue', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\\x1b[48;2;0;0;${i}m \x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]); + } + } + }); + it('foreground true color grey', async () => { let data = ''; for (let y = 0; y < 16; y++) { @@ -233,6 +287,24 @@ describe.only('WebGL Renderer Integration Tests', function(): void { } } }); + + it('background true color grey', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\\x1b[48;2;${i};${i};${i}m \x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]); + } + } + }); }); }); From 53b0149764a94471c6a3dcb32510505cf837f928 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 10 Nov 2019 11:11:26 -0800 Subject: [PATCH 058/268] Add selectionOpaque and factor out color into new file --- .../src/atlas/CharAtlasUtils.ts | 1 + src/browser/Color.test.ts | 31 ++++++++++ src/browser/Color.ts | 39 +++++++++++++ src/browser/ColorManager.ts | 56 ++++++++----------- src/browser/Types.d.ts | 2 + 5 files changed, 96 insertions(+), 33 deletions(-) create mode 100644 src/browser/Color.test.ts create mode 100644 src/browser/Color.ts diff --git a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts index 1fd4c4f6b..03867b410 100644 --- a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts +++ b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts @@ -21,6 +21,7 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number cursor: NULL_COLOR, cursorAccent: NULL_COLOR, selection: NULL_COLOR, + selectionOpaque: NULL_COLOR, // For the static char atlas, we only use the first 16 colors, but we need all 256 for the // dynamic character atlas. ansi: colors.ansi.slice() diff --git a/src/browser/Color.test.ts b/src/browser/Color.test.ts new file mode 100644 index 000000000..fc75d4b90 --- /dev/null +++ b/src/browser/Color.test.ts @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert } from 'chai'; +import { blend } from 'browser/Color'; + +describe('Color', () => { + describe('blend', () => { + it('should blend colors based on the alpha channel', () => { + assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF00', rgba: 0xFFFFFF00 }), { css: '#000000', rgba: 0x000000FF }); + assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF10', rgba: 0xFFFFFF10 }), { css: '#101010', rgba: 0x101010FF }); + assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF20', rgba: 0xFFFFFF20 }), { css: '#202020', rgba: 0x202020FF }); + assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF30', rgba: 0xFFFFFF30 }), { css: '#303030', rgba: 0x303030FF }); + assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF40', rgba: 0xFFFFFF40 }), { css: '#404040', rgba: 0x404040FF }); + assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF50', rgba: 0xFFFFFF50 }), { css: '#505050', rgba: 0x505050FF }); + assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF60', rgba: 0xFFFFFF60 }), { css: '#606060', rgba: 0x606060FF }); + assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF70', rgba: 0xFFFFFF70 }), { css: '#707070', rgba: 0x707070FF }); + assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF80', rgba: 0xFFFFFF80 }), { css: '#808080', rgba: 0x808080FF }); + assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF90', rgba: 0xFFFFFF90 }), { css: '#909090', rgba: 0x909090FF }); + assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFA0', rgba: 0xFFFFFFA0 }), { css: '#a0a0a0', rgba: 0xA0A0A0FF }); + assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFB0', rgba: 0xFFFFFFB0 }), { css: '#b0b0b0', rgba: 0xB0B0B0FF }); + assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFC0', rgba: 0xFFFFFFC0 }), { css: '#c0c0c0', rgba: 0xC0C0C0FF }); + assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFD0', rgba: 0xFFFFFFD0 }), { css: '#d0d0d0', rgba: 0xD0D0D0FF }); + assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFE0', rgba: 0xFFFFFFE0 }), { css: '#e0e0e0', rgba: 0xE0E0E0FF }); + assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFF0', rgba: 0xFFFFFFF0 }), { css: '#f0f0f0', rgba: 0xF0F0F0FF }); + assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFFF', rgba: 0xFFFFFFFF }), { css: '#FFFFFFFF', rgba: 0xFFFFFFFF }); + }); + }); +}); diff --git a/src/browser/Color.ts b/src/browser/Color.ts new file mode 100644 index 000000000..31087116b --- /dev/null +++ b/src/browser/Color.ts @@ -0,0 +1,39 @@ +import { IColor } from './Types'; + +export function blend(bg: IColor, fg: IColor): IColor { + const a = (fg.rgba & 0xFF) / 255; + if (a === 1) { + return { + css: fg.css, + rgba: fg.rgba + }; + } + const fgR = (fg.rgba >> 24) & 0xFF; + const fgG = (fg.rgba >> 16) & 0xFF; + const fgB = (fg.rgba >> 8) & 0xFF; + const bgR = (bg.rgba >> 24) & 0xFF; + const bgG = (bg.rgba >> 16) & 0xFF; + const bgB = (bg.rgba >> 8) & 0xFF; + const r = bgR + Math.round((fgR - bgR) * a); + const g = bgG + Math.round((fgG - bgG) * a); + const b = bgB + Math.round((fgB - bgB) * a); + const css = toCss(r, g, b); + const rgba = ((r << 24) | (g << 16) | (b << 8) | 0xFF) >>> 0; + return { css, rgba }; +} + +export function fromCss(css: string): IColor { + return { + css, + rgba: parseInt(css.slice(1), 16) << 8 | 0xFF + }; +} + +export function toPaddedHex(c: number): string { + const s = c.toString(16); + return s.length < 2 ? '0' + s : s; +} + +export function toCss(r: number, g: number, b: number): string { + return `#${toPaddedHex(r)}${toPaddedHex(g)}${toPaddedHex(b)}`; +} diff --git a/src/browser/ColorManager.ts b/src/browser/ColorManager.ts index 70d21a7a4..a1bfc105e 100644 --- a/src/browser/ColorManager.ts +++ b/src/browser/ColorManager.ts @@ -5,11 +5,12 @@ import { IColorManager, IColor, IColorSet } from 'browser/Types'; import { ITheme } from 'common/services/Services'; +import { fromCss, toPaddedHex, toCss, blend } from 'browser/Color'; -const DEFAULT_FOREGROUND = fromHex('#ffffff'); -const DEFAULT_BACKGROUND = fromHex('#000000'); -const DEFAULT_CURSOR = fromHex('#ffffff'); -const DEFAULT_CURSOR_ACCENT = fromHex('#000000'); +const DEFAULT_FOREGROUND = fromCss('#ffffff'); +const DEFAULT_BACKGROUND = fromCss('#000000'); +const DEFAULT_CURSOR = fromCss('#ffffff'); +const DEFAULT_CURSOR_ACCENT = fromCss('#000000'); const DEFAULT_SELECTION = { css: 'rgba(255, 255, 255, 0.3)', rgba: 0xFFFFFF77 @@ -20,23 +21,23 @@ const DEFAULT_SELECTION = { export const DEFAULT_ANSI_COLORS = (() => { const colors = [ // dark: - fromHex('#2e3436'), - fromHex('#cc0000'), - fromHex('#4e9a06'), - fromHex('#c4a000'), - fromHex('#3465a4'), - fromHex('#75507b'), - fromHex('#06989a'), - fromHex('#d3d7cf'), + fromCss('#2e3436'), + fromCss('#cc0000'), + fromCss('#4e9a06'), + fromCss('#c4a000'), + fromCss('#3465a4'), + fromCss('#75507b'), + fromCss('#06989a'), + fromCss('#d3d7cf'), // bright: - fromHex('#555753'), - fromHex('#ef2929'), - fromHex('#8ae234'), - fromHex('#fce94f'), - fromHex('#729fcf'), - fromHex('#ad7fa8'), - fromHex('#34e2e2'), - fromHex('#eeeeec') + fromCss('#555753'), + fromCss('#ef2929'), + fromCss('#8ae234'), + fromCss('#fce94f'), + fromCss('#729fcf'), + fromCss('#ad7fa8'), + fromCss('#34e2e2'), + fromCss('#eeeeec') ]; // Fill in the remaining 240 ANSI colors. @@ -47,7 +48,7 @@ export const DEFAULT_ANSI_COLORS = (() => { const g = v[(i / 6) % 6 | 0]; const b = v[i % 6]; colors.push({ - css: `#${toPaddedHex(r)}${toPaddedHex(g)}${toPaddedHex(b)}`, + css: toCss(r, g, b), // Use >>> 0 to force a conversion to an unsigned int rgba: ((r << 24) | (g << 16) | (b << 8) | 0xFF) >>> 0 }); @@ -66,18 +67,6 @@ export const DEFAULT_ANSI_COLORS = (() => { return colors; })(); -function fromHex(css: string): IColor { - return { - css, - rgba: parseInt(css.slice(1), 16) << 8 | 0xFF - }; -} - -function toPaddedHex(c: number): string { - const s = c.toString(16); - return s.length < 2 ? '0' + s : s; -} - /** * Manages the source of truth for a terminal's colors. */ @@ -103,6 +92,7 @@ export class ColorManager implements IColorManager { cursor: DEFAULT_CURSOR, cursorAccent: DEFAULT_CURSOR_ACCENT, selection: DEFAULT_SELECTION, + selectionOpaque: blend(DEFAULT_BACKGROUND, DEFAULT_SELECTION), ansi: DEFAULT_ANSI_COLORS.slice() }; } diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index aa7a4c86e..ca852b6aa 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -21,6 +21,8 @@ export interface IColorSet { cursor: IColor; cursorAccent: IColor; selection: IColor; + /** The selection blended on top of background. */ + selectionOpaque: IColor; ansi: IColor[]; } From 51c77568a8ad2dc1db4bb0fdd1f52183a2dab7b3 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 10 Nov 2019 11:17:36 -0800 Subject: [PATCH 059/268] Add tests for Color functions --- src/browser/Color.test.ts | 68 ++++++++++++++++++++++++++++++++++++++- src/browser/Color.ts | 2 +- 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/src/browser/Color.test.ts b/src/browser/Color.test.ts index fc75d4b90..5542611f1 100644 --- a/src/browser/Color.test.ts +++ b/src/browser/Color.test.ts @@ -4,7 +4,7 @@ */ import { assert } from 'chai'; -import { blend } from 'browser/Color'; +import { blend, fromCss, toPaddedHex, toCss } from 'browser/Color'; describe('Color', () => { describe('blend', () => { @@ -28,4 +28,70 @@ describe('Color', () => { assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFFF', rgba: 0xFFFFFFFF }), { css: '#FFFFFFFF', rgba: 0xFFFFFFFF }); }); }); + + describe('fromCss', () => { + it('should covert a CSS string to an IColor', () => { + assert.deepEqual(fromCss('#000000'), { css: '#000000', rgba: 0x000000FF }); + assert.deepEqual(fromCss('#101010'), { css: '#101010', rgba: 0x101010FF }); + assert.deepEqual(fromCss('#202020'), { css: '#202020', rgba: 0x202020FF }); + assert.deepEqual(fromCss('#303030'), { css: '#303030', rgba: 0x303030FF }); + assert.deepEqual(fromCss('#404040'), { css: '#404040', rgba: 0x404040FF }); + assert.deepEqual(fromCss('#505050'), { css: '#505050', rgba: 0x505050FF }); + assert.deepEqual(fromCss('#606060'), { css: '#606060', rgba: 0x606060FF }); + assert.deepEqual(fromCss('#707070'), { css: '#707070', rgba: 0x707070FF }); + assert.deepEqual(fromCss('#808080'), { css: '#808080', rgba: 0x808080FF }); + assert.deepEqual(fromCss('#909090'), { css: '#909090', rgba: 0x909090FF }); + assert.deepEqual(fromCss('#a0a0a0'), { css: '#a0a0a0', rgba: 0xa0a0a0FF }); + assert.deepEqual(fromCss('#b0b0b0'), { css: '#b0b0b0', rgba: 0xb0b0b0FF }); + assert.deepEqual(fromCss('#c0c0c0'), { css: '#c0c0c0', rgba: 0xc0c0c0FF }); + assert.deepEqual(fromCss('#d0d0d0'), { css: '#d0d0d0', rgba: 0xd0d0d0FF }); + assert.deepEqual(fromCss('#e0e0e0'), { css: '#e0e0e0', rgba: 0xe0e0e0FF }); + assert.deepEqual(fromCss('#f0f0f0'), { css: '#f0f0f0', rgba: 0xf0f0f0FF }); + assert.deepEqual(fromCss('#ffffff'), { css: '#ffffff', rgba: 0xffffffFF }); + }); + }); + + describe('toPaddedHex', () => { + it('should convert numbers to 2-digit hex values', () => { + assert.equal(toPaddedHex(0x00), '00'); + assert.equal(toPaddedHex(0x10), '10'); + assert.equal(toPaddedHex(0x20), '20'); + assert.equal(toPaddedHex(0x30), '30'); + assert.equal(toPaddedHex(0x40), '40'); + assert.equal(toPaddedHex(0x50), '50'); + assert.equal(toPaddedHex(0x60), '60'); + assert.equal(toPaddedHex(0x70), '70'); + assert.equal(toPaddedHex(0x80), '80'); + assert.equal(toPaddedHex(0x90), '90'); + assert.equal(toPaddedHex(0xa0), 'a0'); + assert.equal(toPaddedHex(0xb0), 'b0'); + assert.equal(toPaddedHex(0xc0), 'c0'); + assert.equal(toPaddedHex(0xd0), 'd0'); + assert.equal(toPaddedHex(0xe0), 'e0'); + assert.equal(toPaddedHex(0xf0), 'f0'); + assert.equal(toPaddedHex(0xff), 'ff'); + }); + }); + + describe('toCss', () => { + it('should convert an rgb array to css hex string', () => { + assert.equal(toCss(0x00, 0x00, 0x00), '#000000'); + assert.equal(toCss(0x10, 0x10, 0x10), '#101010'); + assert.equal(toCss(0x20, 0x20, 0x20), '#202020'); + assert.equal(toCss(0x30, 0x30, 0x30), '#303030'); + assert.equal(toCss(0x40, 0x40, 0x40), '#404040'); + assert.equal(toCss(0x50, 0x50, 0x50), '#505050'); + assert.equal(toCss(0x60, 0x60, 0x60), '#606060'); + assert.equal(toCss(0x70, 0x70, 0x70), '#707070'); + assert.equal(toCss(0x80, 0x80, 0x80), '#808080'); + assert.equal(toCss(0x90, 0x90, 0x90), '#909090'); + assert.equal(toCss(0xa0, 0xa0, 0xa0), '#a0a0a0'); + assert.equal(toCss(0xb0, 0xb0, 0xb0), '#b0b0b0'); + assert.equal(toCss(0xc0, 0xc0, 0xc0), '#c0c0c0'); + assert.equal(toCss(0xd0, 0xd0, 0xd0), '#d0d0d0'); + assert.equal(toCss(0xe0, 0xe0, 0xe0), '#e0e0e0'); + assert.equal(toCss(0xf0, 0xf0, 0xf0), '#f0f0f0'); + assert.equal(toCss(0xff, 0xff, 0xff), '#ffffff'); + }); + }); }); diff --git a/src/browser/Color.ts b/src/browser/Color.ts index 31087116b..9771f656a 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -25,7 +25,7 @@ export function blend(bg: IColor, fg: IColor): IColor { export function fromCss(css: string): IColor { return { css, - rgba: parseInt(css.slice(1), 16) << 8 | 0xFF + rgba: (parseInt(css.slice(1), 16) << 8 | 0xFF) >>> 0 }; } From 2da77dd5f8dfe729bcfdd8499d5070b559566052 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 10 Nov 2019 11:23:34 -0800 Subject: [PATCH 060/268] Use opaque selection in webgl, fix selection default rgba --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 2 +- addons/xterm-addon-webgl/src/RectangleRenderer.ts | 2 +- src/browser/Color.ts | 5 +++++ src/browser/ColorManager.ts | 3 ++- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index 3ee9cdb51..3d4d43d1d 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -218,7 +218,7 @@ export class GlyphRenderer { this._vertices.selectionAttributes = slice(this._vertices.attributes, 0); - const bg = (this._colors.selection.rgba >>> 8) | Attributes.CM_RGB; + const bg = (this._colors.selectionOpaque.rgba >>> 8) | Attributes.CM_RGB; if (columnSelectMode) { const startCol = model.selection.startCol; diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index d5e245c06..8dcc5c083 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -155,7 +155,7 @@ export class RectangleRenderer { private _updateCachedColors(): void { this._bgFloat = this._colorToFloat32Array(this._colors.background); - this._selectionFloat = this._colorToFloat32Array(this._colors.selection); + this._selectionFloat = this._colorToFloat32Array(this._colors.selectionOpaque); } private _updateViewportRectangle(): void { diff --git a/src/browser/Color.ts b/src/browser/Color.ts index 9771f656a..5e86d5e98 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -1,3 +1,8 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + import { IColor } from './Types'; export function blend(bg: IColor, fg: IColor): IColor { diff --git a/src/browser/ColorManager.ts b/src/browser/ColorManager.ts index a1bfc105e..170fead63 100644 --- a/src/browser/ColorManager.ts +++ b/src/browser/ColorManager.ts @@ -13,7 +13,7 @@ const DEFAULT_CURSOR = fromCss('#ffffff'); const DEFAULT_CURSOR_ACCENT = fromCss('#000000'); const DEFAULT_SELECTION = { css: 'rgba(255, 255, 255, 0.3)', - rgba: 0xFFFFFF77 + rgba: 0xFFFFFF4D }; // An IIFE to generate DEFAULT_ANSI_COLORS. Do not mutate DEFAULT_ANSI_COLORS, instead make a copy @@ -108,6 +108,7 @@ export class ColorManager implements IColorManager { this.colors.cursor = this._parseColor(theme.cursor, DEFAULT_CURSOR, true); this.colors.cursorAccent = this._parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT, true); this.colors.selection = this._parseColor(theme.selection, DEFAULT_SELECTION, true); + this.colors.selectionOpaque = blend(this.colors.background, this.colors.selection); this.colors.ansi[0] = this._parseColor(theme.black, DEFAULT_ANSI_COLORS[0]); this.colors.ansi[1] = this._parseColor(theme.red, DEFAULT_ANSI_COLORS[1]); this.colors.ansi[2] = this._parseColor(theme.green, DEFAULT_ANSI_COLORS[2]); From 734bd8c96a43f7ed38e1bdf377b014533d4a7928 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 10 Nov 2019 11:30:48 -0800 Subject: [PATCH 061/268] Factor out toRgba function and test --- src/browser/Color.test.ts | 43 ++++++++++++++++++++++++++++++++++++- src/browser/Color.ts | 7 +++++- src/browser/ColorManager.ts | 12 +++++------ 3 files changed, 53 insertions(+), 9 deletions(-) diff --git a/src/browser/Color.test.ts b/src/browser/Color.test.ts index 5542611f1..44cd52f32 100644 --- a/src/browser/Color.test.ts +++ b/src/browser/Color.test.ts @@ -4,7 +4,7 @@ */ import { assert } from 'chai'; -import { blend, fromCss, toPaddedHex, toCss } from 'browser/Color'; +import { blend, fromCss, toPaddedHex, toCss, toRgba } from 'browser/Color'; describe('Color', () => { describe('blend', () => { @@ -94,4 +94,45 @@ describe('Color', () => { assert.equal(toCss(0xff, 0xff, 0xff), '#ffffff'); }); }); + + describe('toRgba', () => { + it('should convert an rgb array to an rgba number', () => { + assert.equal(toRgba(0x00, 0x00, 0x00), 0x000000FF); + assert.equal(toRgba(0x10, 0x10, 0x10), 0x101010FF); + assert.equal(toRgba(0x20, 0x20, 0x20), 0x202020FF); + assert.equal(toRgba(0x30, 0x30, 0x30), 0x303030FF); + assert.equal(toRgba(0x40, 0x40, 0x40), 0x404040FF); + assert.equal(toRgba(0x50, 0x50, 0x50), 0x505050FF); + assert.equal(toRgba(0x60, 0x60, 0x60), 0x606060FF); + assert.equal(toRgba(0x70, 0x70, 0x70), 0x707070FF); + assert.equal(toRgba(0x80, 0x80, 0x80), 0x808080FF); + assert.equal(toRgba(0x90, 0x90, 0x90), 0x909090FF); + assert.equal(toRgba(0xa0, 0xa0, 0xa0), 0xa0a0a0FF); + assert.equal(toRgba(0xb0, 0xb0, 0xb0), 0xb0b0b0FF); + assert.equal(toRgba(0xc0, 0xc0, 0xc0), 0xc0c0c0FF); + assert.equal(toRgba(0xd0, 0xd0, 0xd0), 0xd0d0d0FF); + assert.equal(toRgba(0xe0, 0xe0, 0xe0), 0xe0e0e0FF); + assert.equal(toRgba(0xf0, 0xf0, 0xf0), 0xf0f0f0FF); + assert.equal(toRgba(0xff, 0xff, 0xff), 0xffffffFF); + }); + it('should convert an rgba array to an rgba number', () => { + assert.equal(toRgba(0x00, 0x00, 0x00, 0x00), 0x00000000); + assert.equal(toRgba(0x10, 0x10, 0x10, 0x10), 0x10101010); + assert.equal(toRgba(0x20, 0x20, 0x20, 0x20), 0x20202020); + assert.equal(toRgba(0x30, 0x30, 0x30, 0x30), 0x30303030); + assert.equal(toRgba(0x40, 0x40, 0x40, 0x40), 0x40404040); + assert.equal(toRgba(0x50, 0x50, 0x50, 0x50), 0x50505050); + assert.equal(toRgba(0x60, 0x60, 0x60, 0x60), 0x60606060); + assert.equal(toRgba(0x70, 0x70, 0x70, 0x70), 0x70707070); + assert.equal(toRgba(0x80, 0x80, 0x80, 0x80), 0x80808080); + assert.equal(toRgba(0x90, 0x90, 0x90, 0x90), 0x90909090); + assert.equal(toRgba(0xa0, 0xa0, 0xa0, 0xa0), 0xa0a0a0a0); + assert.equal(toRgba(0xb0, 0xb0, 0xb0, 0xb0), 0xb0b0b0b0); + assert.equal(toRgba(0xc0, 0xc0, 0xc0, 0xc0), 0xc0c0c0c0); + assert.equal(toRgba(0xd0, 0xd0, 0xd0, 0xd0), 0xd0d0d0d0); + assert.equal(toRgba(0xe0, 0xe0, 0xe0, 0xe0), 0xe0e0e0e0); + assert.equal(toRgba(0xf0, 0xf0, 0xf0, 0xf0), 0xf0f0f0f0); + assert.equal(toRgba(0xff, 0xff, 0xff, 0xff), 0xffffffff); + }); + }); }); diff --git a/src/browser/Color.ts b/src/browser/Color.ts index 5e86d5e98..51c1a4468 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -23,7 +23,7 @@ export function blend(bg: IColor, fg: IColor): IColor { const g = bgG + Math.round((fgG - bgG) * a); const b = bgB + Math.round((fgB - bgB) * a); const css = toCss(r, g, b); - const rgba = ((r << 24) | (g << 16) | (b << 8) | 0xFF) >>> 0; + const rgba = toRgba(r, g, b); return { css, rgba }; } @@ -42,3 +42,8 @@ export function toPaddedHex(c: number): string { export function toCss(r: number, g: number, b: number): string { return `#${toPaddedHex(r)}${toPaddedHex(g)}${toPaddedHex(b)}`; } + +export function toRgba(r: number, g: number, b: number, a: number = 0xFF): number { + // >>> 0 forces an unsigned int + return (r << 24 | g << 16 | b << 8 | a) >>> 0; +} diff --git a/src/browser/ColorManager.ts b/src/browser/ColorManager.ts index 170fead63..c6354f711 100644 --- a/src/browser/ColorManager.ts +++ b/src/browser/ColorManager.ts @@ -5,7 +5,7 @@ import { IColorManager, IColor, IColorSet } from 'browser/Types'; import { ITheme } from 'common/services/Services'; -import { fromCss, toPaddedHex, toCss, blend } from 'browser/Color'; +import { fromCss, toCss, blend, toRgba } from 'browser/Color'; const DEFAULT_FOREGROUND = fromCss('#ffffff'); const DEFAULT_BACKGROUND = fromCss('#000000'); @@ -49,18 +49,16 @@ export const DEFAULT_ANSI_COLORS = (() => { const b = v[i % 6]; colors.push({ css: toCss(r, g, b), - // Use >>> 0 to force a conversion to an unsigned int - rgba: ((r << 24) | (g << 16) | (b << 8) | 0xFF) >>> 0 + rgba: toRgba(r, g, b) }); } // Generate greys (232-255) for (let i = 0; i < 24; i++) { const c = 8 + i * 10; - const ch = toPaddedHex(c); colors.push({ - css: `#${ch}${ch}${ch}`, - rgba: ((c << 24) | (c << 16) | (c << 8) | 0xFF) >>> 0 + css: toCss(c, c, c), + rgba: toRgba(c, c, c) }); } @@ -175,7 +173,7 @@ export class ColorManager implements IColorManager { return { css, - rgba: (data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]) >>> 0 + rgba: toRgba(data[0], data[1], data[2], data[3]) }; } } From e89d79b879a4aa8550a7957e78aeafd584effa56 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 10 Nov 2019 11:33:41 -0800 Subject: [PATCH 062/268] Remove unused code --- .../src/renderLayer/BaseRenderLayer.ts | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index 0e2d645e5..29487ad06 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -4,7 +4,6 @@ */ import { IRenderLayer } from './Types'; -import { IGlyphIdentifier } from '../atlas/Types'; import { acquireCharAtlas } from '../atlas/CharAtlasCache'; import { Terminal } from 'xterm'; import { IColorSet } from 'browser/Types'; @@ -25,19 +24,6 @@ export abstract class BaseRenderLayer implements IRenderLayer { protected _charAtlas: WebglCharAtlas | undefined; - /** - * An object that's reused when drawing glyphs in order to reduce GC. - */ - private _currentGlyphIdentifier: IGlyphIdentifier = { - chars: '', - code: 0, - bg: 0, - fg: 0, - bold: false, - dim: false, - italic: false - }; - constructor( private _container: HTMLElement, id: string, From 450a6262181e1bfc42fb1197f7760d90b1ca3d3e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 10 Nov 2019 11:48:32 -0800 Subject: [PATCH 063/268] Correct import in Color.ts --- src/browser/Color.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/Color.ts b/src/browser/Color.ts index 51c1a4468..a8ce2d16a 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IColor } from './Types'; +import { IColor } from 'browser/Types'; export function blend(bg: IColor, fg: IColor): IColor { const a = (fg.rgba & 0xFF) / 255; From 6b7e29ea32d5ca4f8421d113835aaefd8b64adb9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 10 Nov 2019 12:06:20 -0800 Subject: [PATCH 064/268] Whitespace to force a webgl release --- addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index c364c3c17..16b7ba07b 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -440,3 +440,4 @@ function toPaddedHex(c: number): string { const s = c.toString(16); return s.length < 2 ? '0' + s : s; } + From 5bc80fdf67d3852fa94dab3ec97e76ebfdf247cb Mon Sep 17 00:00:00 2001 From: ivanwonder Date: Mon, 11 Nov 2019 11:01:32 +0800 Subject: [PATCH 065/268] force alpha to 1 when using background color as inverted foreground color. --- src/browser/Color.test.ts | 24 ++++++++++++++++++- src/browser/Color.ts | 4 ++++ .../renderer/atlas/DynamicCharAtlas.ts | 9 ++++++- src/browser/renderer/dom/DomRenderer.ts | 4 +++- 4 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/browser/Color.test.ts b/src/browser/Color.test.ts index 44cd52f32..005e73768 100644 --- a/src/browser/Color.test.ts +++ b/src/browser/Color.test.ts @@ -4,7 +4,7 @@ */ import { assert } from 'chai'; -import { blend, fromCss, toPaddedHex, toCss, toRgba } from 'browser/Color'; +import { blend, fromCss, toPaddedHex, toCss, toRgba, fromRgba } from 'browser/Color'; describe('Color', () => { describe('blend', () => { @@ -135,4 +135,26 @@ describe('Color', () => { assert.equal(toRgba(0xff, 0xff, 0xff, 0xff), 0xffffffff); }); }); + + describe('fromRgba', () => { + it('should convert an rgba number to an rgba array', () => { + assert.deepEqual(fromRgba(0x00000000), [0x00, 0x00, 0x00, 0x00]); + assert.deepEqual(fromRgba(0x10101010), [0x10, 0x10, 0x10, 0x10]); + assert.deepEqual(fromRgba(0x20202020), [0x20, 0x20, 0x20, 0x20]); + assert.deepEqual(fromRgba(0x30303030), [0x30, 0x30, 0x30, 0x30]); + assert.deepEqual(fromRgba(0x40404040), [0x40, 0x40, 0x40, 0x40]); + assert.deepEqual(fromRgba(0x50505050), [0x50, 0x50, 0x50, 0x50]); + assert.deepEqual(fromRgba(0x60606060), [0x60, 0x60, 0x60, 0x60]); + assert.deepEqual(fromRgba(0x70707070), [0x70, 0x70, 0x70, 0x70]); + assert.deepEqual(fromRgba(0x80808080), [0x80, 0x80, 0x80, 0x80]); + assert.deepEqual(fromRgba(0x90909090), [0x90, 0x90, 0x90, 0x90]); + assert.deepEqual(fromRgba(0xa0a0a0a0), [0xa0, 0xa0, 0xa0, 0xa0]); + assert.deepEqual(fromRgba(0xb0b0b0b0), [0xb0, 0xb0, 0xb0, 0xb0]); + assert.deepEqual(fromRgba(0xc0c0c0c0), [0xc0, 0xc0, 0xc0, 0xc0]); + assert.deepEqual(fromRgba(0xd0d0d0d0), [0xd0, 0xd0, 0xd0, 0xd0]); + assert.deepEqual(fromRgba(0xe0e0e0e0), [0xe0, 0xe0, 0xe0, 0xe0]); + assert.deepEqual(fromRgba(0xf0f0f0f0), [0xf0, 0xf0, 0xf0, 0xf0]); + assert.deepEqual(fromRgba(0xffffffff), [0xff, 0xff, 0xff, 0xff]); + }); + }); }); diff --git a/src/browser/Color.ts b/src/browser/Color.ts index a8ce2d16a..a9db22155 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -47,3 +47,7 @@ export function toRgba(r: number, g: number, b: number, a: number = 0xFF): numbe // >>> 0 forces an unsigned int return (r << 24 | g << 16 | b << 8 | a) >>> 0; } + +export function fromRgba(value: number): [number, number, number, number] { + return [(value >> 24) & 0xFF, (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF]; +} diff --git a/src/browser/renderer/atlas/DynamicCharAtlas.ts b/src/browser/renderer/atlas/DynamicCharAtlas.ts index 40103bc72..4ddaf2bde 100644 --- a/src/browser/renderer/atlas/DynamicCharAtlas.ts +++ b/src/browser/renderer/atlas/DynamicCharAtlas.ts @@ -11,6 +11,7 @@ import { LRUMap } from 'browser/renderer/atlas/LRUMap'; import { isFirefox, isSafari } from 'common/Platform'; import { IColor } from 'browser/Types'; import { throwIfFalsy } from 'browser/renderer/RendererUtils'; +import { fromRgba, toCss } from 'browser/Color'; // In practice we're probably never going to exhaust a texture this large. For debugging purposes, // however, it can be useful to set this to a really tiny value, to verify that LRU eviction works. @@ -253,7 +254,13 @@ export class DynamicCharAtlas extends BaseCharAtlas { `${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`; this._tmpCtx.textBaseline = 'middle'; - this._tmpCtx.fillStyle = this._getForegroundColor(glyph).css; + const fgColor = this._getForegroundColor(glyph); + this._tmpCtx.fillStyle = fgColor.css; + + if (glyph.fg === INVERTED_DEFAULT_COLOR) { + const rgba = fromRgba(fgColor.rgba); + this._tmpCtx.fillStyle = toCss(rgba[0], rgba[1], rgba[2]); + } // Apply alpha to dim the character if (glyph.dim) { diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index e8fc85f66..e11a413d7 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -11,6 +11,7 @@ import { IColorSet, ILinkifierEvent, ILinkifier } from 'browser/Types'; import { ICharSizeService } from 'browser/services/Services'; import { IOptionsService, IBufferService } from 'common/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { fromRgba, toCss } from 'browser/Color'; const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-'; const ROW_CONTAINER_CLASS = 'xterm-rows'; @@ -229,8 +230,9 @@ export class DomRenderer extends Disposable implements IRenderer { `${this._terminalSelector} .${FG_CLASS_PREFIX}${i} { color: ${c.css}; }` + `${this._terminalSelector} .${BG_CLASS_PREFIX}${i} { background-color: ${c.css}; }`; }); + const rgba = fromRgba(this._colors.background.rgba); styles += - `${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { color: ${this._colors.background.css}; }` + + `${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { color: ${toCss(rgba[0], rgba[1], rgba[2])}; }` + `${this._terminalSelector} .${BG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { background-color: ${this._colors.foreground.css}; }`; this._themeStyleElement.innerHTML = styles; From 94afd1fa38d31c07838d0960075b3994f610dcd7 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 10 Nov 2019 21:38:43 -0800 Subject: [PATCH 066/268] Generate coverage report for pipelines --- azure-pipelines.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 4daa09618..0f61dfe1d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -25,7 +25,9 @@ jobs: displayName: Cache node modules - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' - - script: yarn test-unit-coverage --forbid-only + - script: | + yarn test-unit-coverage --forbid-only + ./node_modules/.bin/nyc report --reporter=cobertura displayName: 'Unit tests' - script: yarn lint displayName: 'Lint' From 25f9487239aad2a50368f79d19692c0007234670 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 11 Nov 2019 10:24:54 -0800 Subject: [PATCH 067/268] Prefer non-relative imports Fixes some auto imports in VS Code, see microsoft/TypeScript#32144 --- .vscode/settings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 3c8a2dc2e..20e4915ca 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "typescript.preferences.quoteStyle": "single" + "typescript.preferences.quoteStyle": "single", + "typescript.preferences.importModuleSpecifier": "non-relative" } From dfc1664d6e29d2ff7ed7a1bab6a8560e673b837c Mon Sep 17 00:00:00 2001 From: Andrew Sverdrup Date: Mon, 11 Nov 2019 13:23:59 -0800 Subject: [PATCH 068/268] Update Codevolve to Next Tech We changed our name several months back. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5cf2848f5..d2944003b 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ Xterm.js is used in several world-class applications to provide great terminal e - [**WebSSH2**](https://github.com/billchurch/WebSSH2): A web based SSH2 client using xterm.js, socket.io, and ssh2. - [**Spyder Terminal**](https://github.com/spyder-ide/spyder-terminal): A full fledged system terminal embedded on Spyder IDE. - [**Cloud Commander**](https://cloudcmd.io "Cloud Commander"): Orthodox web file manager with console and editor. -- [**Codevolve**](https://www.codevolve.com "Codevolve"): Online platform for interactive coding and web development courses. Live container-backed terminal uses xterm.js. +- [**Next Tech**](https://next.tech "Next Tech"): Online platform for interactive coding and web development courses. Live container-backed terminal uses xterm.js. - [**RStudio**](https://www.rstudio.com/products/RStudio "RStudio"): RStudio is an integrated development environment (IDE) for R. - [**Terminal for Atom**](https://github.com/jsmecham/atom-terminal-tab): A simple terminal for the Atom text editor. - [**Eclipse Orion**](https://orionhub.org): A modern, open source software development environment that runs in the cloud. Code, deploy and run in the cloud. From fd868044fd4c6eb819a3dbdf429bd61b2aa59b59 Mon Sep 17 00:00:00 2001 From: ivanwonder Date: Tue, 12 Nov 2019 10:06:51 +0800 Subject: [PATCH 069/268] add a helper to make color opaque --- src/browser/Color.test.ts | 24 ++++++++++++++++++- src/browser/Color.ts | 9 +++++++ .../renderer/atlas/DynamicCharAtlas.ts | 12 +++------- src/browser/renderer/dom/DomRenderer.ts | 5 ++-- 4 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/browser/Color.test.ts b/src/browser/Color.test.ts index 005e73768..7dea98e2e 100644 --- a/src/browser/Color.test.ts +++ b/src/browser/Color.test.ts @@ -4,7 +4,7 @@ */ import { assert } from 'chai'; -import { blend, fromCss, toPaddedHex, toCss, toRgba, fromRgba } from 'browser/Color'; +import { blend, fromCss, toPaddedHex, toCss, toRgba, fromRgba, opaque } from 'browser/Color'; describe('Color', () => { describe('blend', () => { @@ -157,4 +157,26 @@ describe('Color', () => { assert.deepEqual(fromRgba(0xffffffff), [0xff, 0xff, 0xff, 0xff]); }); }); + + describe('opaque', () => { + it('should make the color opaque', () => { + assert.deepEqual(opaque({ css: '#00000000', rgba: 0x00000000 }), { css: '#000000', rgba: 0x000000FF }); + assert.deepEqual(opaque({ css: '#10101010', rgba: 0x10101010 }), { css: '#101010', rgba: 0x101010FF }); + assert.deepEqual(opaque({ css: '#20202020', rgba: 0x20202020 }), { css: '#202020', rgba: 0x202020FF }); + assert.deepEqual(opaque({ css: '#30303030', rgba: 0x30303030 }), { css: '#303030', rgba: 0x303030FF }); + assert.deepEqual(opaque({ css: '#40404040', rgba: 0x40404040 }), { css: '#404040', rgba: 0x404040FF }); + assert.deepEqual(opaque({ css: '#50505050', rgba: 0x50505050 }), { css: '#505050', rgba: 0x505050FF }); + assert.deepEqual(opaque({ css: '#60606060', rgba: 0x60606060 }), { css: '#606060', rgba: 0x606060FF }); + assert.deepEqual(opaque({ css: '#70707070', rgba: 0x70707070 }), { css: '#707070', rgba: 0x707070FF }); + assert.deepEqual(opaque({ css: '#80808080', rgba: 0x80808080 }), { css: '#808080', rgba: 0x808080FF }); + assert.deepEqual(opaque({ css: '#90909090', rgba: 0x90909090 }), { css: '#909090', rgba: 0x909090FF }); + assert.deepEqual(opaque({ css: '#a0a0a0a0', rgba: 0xa0a0a0a0 }), { css: '#a0a0a0', rgba: 0xa0a0a0FF }); + assert.deepEqual(opaque({ css: '#b0b0b0b0', rgba: 0xb0b0b0b0 }), { css: '#b0b0b0', rgba: 0xb0b0b0FF }); + assert.deepEqual(opaque({ css: '#c0c0c0c0', rgba: 0xc0c0c0c0 }), { css: '#c0c0c0', rgba: 0xc0c0c0FF }); + assert.deepEqual(opaque({ css: '#d0d0d0d0', rgba: 0xd0d0d0d0 }), { css: '#d0d0d0', rgba: 0xd0d0d0FF }); + assert.deepEqual(opaque({ css: '#e0e0e0e0', rgba: 0xe0e0e0e0 }), { css: '#e0e0e0', rgba: 0xe0e0e0FF }); + assert.deepEqual(opaque({ css: '#f0f0f0f0', rgba: 0xf0f0f0f0 }), { css: '#f0f0f0', rgba: 0xf0f0f0FF }); + assert.deepEqual(opaque({ css: '#ffffffff', rgba: 0xffffffff }), { css: '#ffffff', rgba: 0xffffffFF }); + }); + }); }); diff --git a/src/browser/Color.ts b/src/browser/Color.ts index a9db22155..bc4f7d139 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -51,3 +51,12 @@ export function toRgba(r: number, g: number, b: number, a: number = 0xFF): numbe export function fromRgba(value: number): [number, number, number, number] { return [(value >> 24) & 0xFF, (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF]; } + +export function opaque(color: IColor): IColor { + const rgba = (color.rgba | 0xFF) >>> 0; + const [r, g, b] = fromRgba(rgba); + return { + css: toCss(r, g, b), + rgba + }; +} diff --git a/src/browser/renderer/atlas/DynamicCharAtlas.ts b/src/browser/renderer/atlas/DynamicCharAtlas.ts index 4ddaf2bde..84a501602 100644 --- a/src/browser/renderer/atlas/DynamicCharAtlas.ts +++ b/src/browser/renderer/atlas/DynamicCharAtlas.ts @@ -11,7 +11,7 @@ import { LRUMap } from 'browser/renderer/atlas/LRUMap'; import { isFirefox, isSafari } from 'common/Platform'; import { IColor } from 'browser/Types'; import { throwIfFalsy } from 'browser/renderer/RendererUtils'; -import { fromRgba, toCss } from 'browser/Color'; +import { opaque } from 'browser/Color'; // In practice we're probably never going to exhaust a texture this large. For debugging purposes, // however, it can be useful to set this to a really tiny value, to verify that LRU eviction works. @@ -223,7 +223,7 @@ export class DynamicCharAtlas extends BaseCharAtlas { private _getForegroundColor(glyph: IGlyphIdentifier): IColor { if (glyph.fg === INVERTED_DEFAULT_COLOR) { - return this._config.colors.background; + return opaque(this._config.colors.background); } else if (glyph.fg < 256) { // 256 color support return this._getColorFromAnsiIndex(glyph.fg); @@ -254,13 +254,7 @@ export class DynamicCharAtlas extends BaseCharAtlas { `${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`; this._tmpCtx.textBaseline = 'middle'; - const fgColor = this._getForegroundColor(glyph); - this._tmpCtx.fillStyle = fgColor.css; - - if (glyph.fg === INVERTED_DEFAULT_COLOR) { - const rgba = fromRgba(fgColor.rgba); - this._tmpCtx.fillStyle = toCss(rgba[0], rgba[1], rgba[2]); - } + this._tmpCtx.fillStyle = this._getForegroundColor(glyph).css; // Apply alpha to dim the character if (glyph.dim) { diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index e11a413d7..d776f608f 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -11,7 +11,7 @@ import { IColorSet, ILinkifierEvent, ILinkifier } from 'browser/Types'; import { ICharSizeService } from 'browser/services/Services'; import { IOptionsService, IBufferService } from 'common/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; -import { fromRgba, toCss } from 'browser/Color'; +import { opaque } from 'browser/Color'; const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-'; const ROW_CONTAINER_CLASS = 'xterm-rows'; @@ -230,9 +230,8 @@ export class DomRenderer extends Disposable implements IRenderer { `${this._terminalSelector} .${FG_CLASS_PREFIX}${i} { color: ${c.css}; }` + `${this._terminalSelector} .${BG_CLASS_PREFIX}${i} { background-color: ${c.css}; }`; }); - const rgba = fromRgba(this._colors.background.rgba); styles += - `${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { color: ${toCss(rgba[0], rgba[1], rgba[2])}; }` + + `${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { color: ${opaque(this._colors.background).css}; }` + `${this._terminalSelector} .${BG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { background-color: ${this._colors.foreground.css}; }`; this._themeStyleElement.innerHTML = styles; From e46ff9ec852f1ee4f8c076950e445b9c4442c868 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 11 Nov 2019 19:03:12 -0800 Subject: [PATCH 070/268] Add minimumContrastRatio to DOM renderer Part of #322 --- src/browser/Color.test.ts | 24 ++++- src/browser/Color.ts | 99 +++++++++++++++++ src/browser/renderer/dom/DomRenderer.ts | 2 +- .../dom/DomRendererRowFactory.test.ts | 2 +- .../renderer/dom/DomRendererRowFactory.ts | 100 +++++++++++++----- src/common/services/OptionsService.ts | 3 + src/common/services/Services.ts | 1 + typings/xterm.d.ts | 8 ++ 8 files changed, 209 insertions(+), 30 deletions(-) diff --git a/src/browser/Color.test.ts b/src/browser/Color.test.ts index 44cd52f32..a8e4fa03d 100644 --- a/src/browser/Color.test.ts +++ b/src/browser/Color.test.ts @@ -4,7 +4,7 @@ */ import { assert } from 'chai'; -import { blend, fromCss, toPaddedHex, toCss, toRgba } from 'browser/Color'; +import { blend, fromCss, toPaddedHex, toCss, toRgba, rgbRelativeLuminance, contrastRatio } from 'browser/Color'; describe('Color', () => { describe('blend', () => { @@ -135,4 +135,26 @@ describe('Color', () => { assert.equal(toRgba(0xff, 0xff, 0xff, 0xff), 0xffffffff); }); }); + describe('rgbRelativeLuminance', () => { + it('should calculate the relative luminance of the color', () => { + assert.equal(rgbRelativeLuminance(0x000000), 0); + + // TODO: Fill in tests + + assert.equal(rgbRelativeLuminance(0xFFFFFF), 1); + }); + }); + describe('contrastRatio', () => { + it('should calculate the relative luminance of the color', () => { + assert.equal(contrastRatio(0, 0), 1); + + // TODO: Fill in tests + + assert.equal(contrastRatio(0, 1), 21); + }); + it('should work regardless of the parameter order', () => { + assert.equal(contrastRatio(0, 1), 21); + assert.equal(contrastRatio(1, 0), 21); + }); + }); }); diff --git a/src/browser/Color.ts b/src/browser/Color.ts index a8ce2d16a..4063e4c19 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -47,3 +47,102 @@ export function toRgba(r: number, g: number, b: number, a: number = 0xFF): numbe // >>> 0 forces an unsigned int return (r << 24 | g << 16 | b << 8 | a) >>> 0; } + +/** + * Gets the relative luminance of an RGB color, this is useful in determining the contrast ratio + * between two colors. + * @param rgb The color to use. + * @see https://www.w3.org/TR/WCAG20/#relativeluminancedef + */ +export function rgbRelativeLuminance(rgb: number): number { + return rgbRelativeLuminance2( + (rgb >> 16) & 0xFF, + (rgb >> 8 ) & 0xFF, + (rgb ) & 0xFF); +} + +export function rgbRelativeLuminance2(r: number, g: number, b: number): number { + const rs = r / 255; + const gs = g / 255; + const bs = b / 255; + const rr = rs <= 0.03928 ? rs / 12.92 : Math.pow((rs + 0.055) / 1.055, 2.4); + const rg = gs <= 0.03928 ? gs / 12.92 : Math.pow((gs + 0.055) / 1.055, 2.4); + const rb = bs <= 0.03928 ? bs / 12.92 : Math.pow((bs + 0.055) / 1.055, 2.4); + return rr * 0.2126 + rg * 0.7152 + rb * 0.0722; +} + +/** + * Gets the contrast ratio between two relative luminance values. + * @param l1 The first relative luminance. + * @param l2 The first relative luminance. + * + * // TODO: Is this link right? + * @see https://www.w3.org/TR/WCAG20/#contrastratio + */ +export function contrastRatio(l1: number, l2: number): number { + if (l1 < l2) { + return (l2 + 0.05) / (l1 + 0.05); + } + return (l1 + 0.05) / (l2 + 0.05); +} + +// TODO: Cache [bg][fg]: result, should probably be owned by ColorManager? + +export function ensureContrastRatio(bg: IColor, fg: IColor, ratio: number): IColor | undefined { + const bgL = rgbRelativeLuminance(bg.rgba >> 8); + const fgL = rgbRelativeLuminance(fg.rgba >> 8); + const cr = contrastRatio(bgL, fgL); + if (cr < ratio) { + if (fgL < bgL) { + return reduceLuminance(bg, fg, ratio); + } + return increaseLuminance(bg, fg, ratio); + } + return undefined; +} + +export function reduceLuminance(bg: IColor, fg: IColor, ratio: number): IColor { + // This is a naive but fast approach to reducing luminance as converting to + // HSL and back is expensive + const bgR = (bg.rgba >> 24) & 0xFF; + const bgG = (bg.rgba >> 16) & 0xFF; + const bgB = (bg.rgba >> 8) & 0xFF; + let fgR = (fg.rgba >> 24) & 0xFF; + let fgG = (fg.rgba >> 16) & 0xFF; + let fgB = (fg.rgba >> 8) & 0xFF; + let cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); + while (cr < ratio && (fgR > 0 || fgG > 0 || fgB > 0)) { + // Increase by 10% (ceil) until the ratio is hit + fgR -= Math.max(0, Math.ceil(fgR * 0.1)); + fgG -= Math.max(0, Math.ceil(fgG * 0.1)); + fgB -= Math.max(0, Math.ceil(fgB * 0.1)); + cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); + } + return { + css: toCss(fgR, fgG, fgB), + rgba: toRgba(fgR, fgG, fgB) + }; +} + +export function increaseLuminance(bg: IColor, fg: IColor, ratio: number): IColor { + // This is a naive but fast approach to increasing luminance as converting to + // HSL and back is expensive + const bgR = (bg.rgba >> 24) & 0xFF; + const bgG = (bg.rgba >> 16) & 0xFF; + const bgB = (bg.rgba >> 8) & 0xFF; + let fgR = (fg.rgba >> 24) & 0xFF; + let fgG = (fg.rgba >> 16) & 0xFF; + let fgB = (fg.rgba >> 8) & 0xFF; + let cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); + while (cr < ratio && (fgR < 0xFF || fgG < 0xFF || fgB < 0xFF)) { + // Increase by 10% until the ratio is hit + fgR = Math.min(0xFF, fgR + Math.floor((255 - fgR) * 0.1)); + fgG = Math.min(0xFF, fgG + Math.floor((255 - fgG) * 0.1)); + fgB = Math.min(0xFF, fgB + Math.floor((255 - fgB) * 0.1)); + cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); + } + return { + css: toCss(fgR, fgG, fgB), + rgba: toRgba(fgR, fgG, fgB) + }; +} diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index e8fc85f66..23c5a3a19 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -79,7 +79,7 @@ export class DomRenderer extends Disposable implements IRenderer { this._updateDimensions(); this._injectCss(); - this._rowFactory = new DomRendererRowFactory(document, this._optionsService); + this._rowFactory = new DomRendererRowFactory(document, this._optionsService, this._colors); this._element.classList.add(TERMINAL_CLASS_PREFIX + this._terminalClass); this._screenElement.appendChild(this._rowContainer); diff --git a/src/browser/renderer/dom/DomRendererRowFactory.test.ts b/src/browser/renderer/dom/DomRendererRowFactory.test.ts index b76d644d6..bb9d97b64 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.test.ts @@ -19,7 +19,7 @@ describe('DomRendererRowFactory', () => { beforeEach(() => { dom = new jsdom.JSDOM(''); - rowFactory = new DomRendererRowFactory(dom.window.document, new MockOptionsService({ drawBoldTextInBrightColors: true })); + rowFactory = new DomRendererRowFactory(dom.window.document, new MockOptionsService({ drawBoldTextInBrightColors: true }), {} as any); lineData = createEmptyLineData(2); }); diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index 3e3b5df0d..2c206f33d 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -6,9 +6,11 @@ import { IBufferLine } from 'common/Types'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { AttributeData } from 'common/buffer/AttributeData'; -import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR } from 'common/buffer/Constants'; +import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Attributes } from 'common/buffer/Constants'; import { CellData } from 'common/buffer/CellData'; import { IOptionsService } from 'common/services/Services'; +import { rgbRelativeLuminance, fromCss, contrastRatio, ensureContrastRatio } from 'browser/Color'; +import { IColorSet, IColor } from 'browser/Types'; export const BOLD_CLASS = 'xterm-bold'; export const DIM_CLASS = 'xterm-dim'; @@ -24,11 +26,16 @@ export class DomRendererRowFactory { private _workCell: CellData = new CellData(); constructor( - private _document: Document, - private _optionsService: IOptionsService + private readonly _document: Document, + private readonly _optionsService: IOptionsService, + private _colors: IColorSet ) { } + public setColors(colors: IColorSet): void { + this._colors = colors; + } + public createRow(lineData: IBufferLine, isCursorRow: boolean, cursorStyle: string | undefined, cursorX: number, cursorBlink: boolean, cellWidth: number, cols: number): DocumentFragment { const fragment = this._document.createDocumentFragment(); @@ -97,36 +104,75 @@ export class DomRendererRowFactory { charElement.textContent = this._workCell.getChars() || WHITESPACE_CELL_CHAR; - const swapColor = this._workCell.isInverse(); - - // fg - if (this._workCell.isFgRGB()) { - let style = charElement.getAttribute('style') || ''; - style += `${swapColor ? 'background-' : ''}color:rgb(${(AttributeData.toColorRGB(this._workCell.getFgColor())).join(',')});`; - charElement.setAttribute('style', style); - } else if (this._workCell.isFgPalette()) { - let fg = this._workCell.getFgColor(); - if (this._workCell.isBold() && fg < 8 && !swapColor && this._optionsService.options.drawBoldTextInBrightColors) { - fg += 8; - } - charElement.classList.add(`xterm-${swapColor ? 'b' : 'f'}g-${fg}`); - } else if (swapColor) { - charElement.classList.add(`xterm-bg-${INVERTED_DEFAULT_COLOR}`); + let fg = this._workCell.getFgColor(); + let fgColorMode = this._workCell.getFgColorMode(); + let bg = this._workCell.getBgColor(); + let bgColorMode = this._workCell.getBgColorMode(); + const isInverse = !!this._workCell.isInverse(); + if (isInverse) { + const temp = fg; + fg = bg; + bg = temp; + const temp2 = fgColorMode; + fgColorMode = bgColorMode; + bgColorMode = temp2; } - // bg - if (this._workCell.isBgRGB()) { - let style = charElement.getAttribute('style') || ''; - style += `${swapColor ? '' : 'background-'}color:rgb(${(AttributeData.toColorRGB(this._workCell.getBgColor())).join(',')});`; - charElement.setAttribute('style', style); - } else if (this._workCell.isBgPalette()) { - charElement.classList.add(`xterm-${swapColor ? 'f' : 'b'}g-${this._workCell.getBgColor()}`); - } else if (swapColor) { - charElement.classList.add(`xterm-fg-${INVERTED_DEFAULT_COLOR}`); + // Foreground + switch (fgColorMode) { + case Attributes.CM_P16: + case Attributes.CM_P256: + if (this._workCell.isBold() && fg < 8 && this._optionsService.options.drawBoldTextInBrightColors) { + fg += 8; + } + if (!this._applyMinimumContrast(charElement, this._colors.background, this._colors.ansi[fg])) { + charElement.classList.add(`xterm-fg-${fg}`); + } + break; + case Attributes.CM_RGB: + charElement.setAttribute('style', `${charElement.getAttribute('style') || ''}color:rgb(${(AttributeData.toColorRGB(fg)).join(',')});`); + break; + case Attributes.CM_DEFAULT: + default: + if (!this._applyMinimumContrast(charElement, this._colors.background, this._colors.foreground)) { + if (isInverse) { + charElement.classList.add(`xterm-fg-${INVERTED_DEFAULT_COLOR}`); + } + } + } + + // Background + switch (bgColorMode) { + case Attributes.CM_P16: + case Attributes.CM_P256: + charElement.classList.add(`xterm-bg-${bg}`); + break; + case Attributes.CM_RGB: + charElement.setAttribute('style', `${charElement.getAttribute('style') || ''}background-color:rgb(${(AttributeData.toColorRGB(bg)).join(',')});`); + break; + case Attributes.CM_DEFAULT: + default: + if (isInverse) { + charElement.classList.add(`xterm-bg-${INVERTED_DEFAULT_COLOR}`); + } } fragment.appendChild(charElement); } return fragment; } + + private _applyMinimumContrast(element: HTMLElement, bg: IColor, fg: IColor): boolean { + if (this._optionsService.options.minimumContrastRatio === 1) { + return false; + } + + const adustedColor = ensureContrastRatio(bg, fg, this._optionsService.options.minimumContrastRatio); + if (adustedColor) { + element.setAttribute('style', `${element.getAttribute('style') || ''}color:${adustedColor.css}`); + return true; + } + + return false; + } } diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index d9ea60d5a..9174398ec 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -37,6 +37,7 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({ screenReaderMode: false, macOptionIsMeta: false, macOptionClickForcesSelection: false, + minimumContrastRatio: 6, disableStdin: false, allowTransparency: false, tabStopWidth: 8, @@ -116,6 +117,8 @@ export class OptionsService implements IOptionsService { throw new Error(`${key} cannot be less than 1, value: ${value}`); } break; + case 'minimumContrastRatio': + value = Math.max(1, Math.min(21, Math.round(value * 10) / 10)); case 'scrollback': value = Math.min(value, 4294967295); if (value < 0) { diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index e4ff90d0f..1a39cced7 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -229,6 +229,7 @@ export interface ITerminalOptions { logLevel: LogLevel; macOptionIsMeta: boolean; macOptionClickForcesSelection: boolean; + minimumContrastRatio: number; rendererType: RendererType; rightClickSelectsWord: boolean; rows: number; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 9728a72cb..b49ed04bf 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -148,6 +148,14 @@ declare module 'xterm' { */ macOptionClickForcesSelection?: boolean; + /** + * The minimum contrast ratio for text in the terminal, setting this will + * change the foreground color dynamically depending on whether the contrast + * ratio is met. This can be to set 0.1 increments between 1 (default, do + * nothing) and 21 (foreground will be black or white). + */ + minimumContrastRatio?: number; + /** * The type of renderer to use, this allows using the fallback DOM renderer * when canvas is too slow for the environment. The following features do From 9cd66e72bbfac8f06c752fe8c88bda307d49fa9a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 11 Nov 2019 19:14:35 -0800 Subject: [PATCH 071/268] Add tests for relative luminance --- src/browser/Color.test.ts | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/browser/Color.test.ts b/src/browser/Color.test.ts index a8e4fa03d..975315c6e 100644 --- a/src/browser/Color.test.ts +++ b/src/browser/Color.test.ts @@ -138,18 +138,28 @@ describe('Color', () => { describe('rgbRelativeLuminance', () => { it('should calculate the relative luminance of the color', () => { assert.equal(rgbRelativeLuminance(0x000000), 0); - - // TODO: Fill in tests - + assert.equal(rgbRelativeLuminance(0x101010).toFixed(4), '0.0052'); + assert.equal(rgbRelativeLuminance(0x202020).toFixed(4), '0.0144'); + assert.equal(rgbRelativeLuminance(0x303030).toFixed(4), '0.0296'); + assert.equal(rgbRelativeLuminance(0x404040).toFixed(4), '0.0513'); + assert.equal(rgbRelativeLuminance(0x505050).toFixed(4), '0.0802'); + assert.equal(rgbRelativeLuminance(0x606060).toFixed(4), '0.1170'); + assert.equal(rgbRelativeLuminance(0x707070).toFixed(4), '0.1620'); + assert.equal(rgbRelativeLuminance(0x808080).toFixed(4), '0.2159'); + assert.equal(rgbRelativeLuminance(0x909090).toFixed(4), '0.2789'); + assert.equal(rgbRelativeLuminance(0xA0A0A0).toFixed(4), '0.3515'); + assert.equal(rgbRelativeLuminance(0xB0B0B0).toFixed(4), '0.4342'); + assert.equal(rgbRelativeLuminance(0xC0C0C0).toFixed(4), '0.5271'); + assert.equal(rgbRelativeLuminance(0xD0D0D0).toFixed(4), '0.6308'); + assert.equal(rgbRelativeLuminance(0xE0E0E0).toFixed(4), '0.7454'); + assert.equal(rgbRelativeLuminance(0xF0F0F0).toFixed(4), '0.8714'); assert.equal(rgbRelativeLuminance(0xFFFFFF), 1); }); }); describe('contrastRatio', () => { it('should calculate the relative luminance of the color', () => { assert.equal(contrastRatio(0, 0), 1); - - // TODO: Fill in tests - + assert.equal(contrastRatio(0, 0.5), 11); assert.equal(contrastRatio(0, 1), 21); }); it('should work regardless of the parameter order', () => { From a6dcb21f1ca8c0cd70437d92a3f2da12fca3ca46 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 11 Nov 2019 19:30:36 -0800 Subject: [PATCH 072/268] Fix tests --- src/browser/Color.ts | 4 +-- .../dom/DomRendererRowFactory.test.ts | 34 ++++++++++++++++--- .../renderer/dom/DomRendererRowFactory.ts | 2 +- src/common/services/OptionsService.ts | 2 +- 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/browser/Color.ts b/src/browser/Color.ts index 4063e4c19..c74e87455 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -75,9 +75,7 @@ export function rgbRelativeLuminance2(r: number, g: number, b: number): number { * Gets the contrast ratio between two relative luminance values. * @param l1 The first relative luminance. * @param l2 The first relative luminance. - * - * // TODO: Is this link right? - * @see https://www.w3.org/TR/WCAG20/#contrastratio + * @see https://www.w3.org/TR/WCAG20/#contrast-ratiodef */ export function contrastRatio(l1: number, l2: number): number { if (l1 < l2) { diff --git a/src/browser/renderer/dom/DomRendererRowFactory.test.ts b/src/browser/renderer/dom/DomRendererRowFactory.test.ts index bb9d97b64..180db0fc4 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.test.ts @@ -11,6 +11,7 @@ import { BufferLine, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { IBufferLine } from 'common/Types'; import { CellData } from 'common/buffer/CellData'; import { MockOptionsService } from 'common/TestUtils.test'; +import { fromCss } from 'browser/Color'; describe('DomRendererRowFactory', () => { let dom: jsdom.JSDOM; @@ -19,7 +20,30 @@ describe('DomRendererRowFactory', () => { beforeEach(() => { dom = new jsdom.JSDOM(''); - rowFactory = new DomRendererRowFactory(dom.window.document, new MockOptionsService({ drawBoldTextInBrightColors: true }), {} as any); + rowFactory = new DomRendererRowFactory(dom.window.document, new MockOptionsService({ drawBoldTextInBrightColors: true }), { + background: fromCss('#010101'), + foreground: fromCss('#020202'), + ansi: [ + // dark: + fromCss('#2e3436'), + fromCss('#cc0000'), + fromCss('#4e9a06'), + fromCss('#c4a000'), + fromCss('#3465a4'), + fromCss('#75507b'), + fromCss('#06989a'), + fromCss('#d3d7cf'), + // bright: + fromCss('#555753'), + fromCss('#ef2929'), + fromCss('#8ae234'), + fromCss('#fce94f'), + fromCss('#729fcf'), + fromCss('#ad7fa8'), + fromCss('#34e2e2'), + fromCss('#eeeeec') + ] + } as any); lineData = createEmptyLineData(2); }); @@ -142,7 +166,7 @@ describe('DomRendererRowFactory', () => { lineData.setCell(0, cell); const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), - 'a' + 'a' ); }); @@ -153,7 +177,7 @@ describe('DomRendererRowFactory', () => { lineData.setCell(0, cell); const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), - 'a' + 'a' ); }); @@ -163,7 +187,7 @@ describe('DomRendererRowFactory', () => { lineData.setCell(0, cell); const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), - 'a' + 'a' ); }); @@ -199,7 +223,7 @@ describe('DomRendererRowFactory', () => { lineData.setCell(0, cell); const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), - 'a' + 'a' ); }); }); diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index 2c206f33d..b5caccaf7 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -9,7 +9,7 @@ import { AttributeData } from 'common/buffer/AttributeData'; import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Attributes } from 'common/buffer/Constants'; import { CellData } from 'common/buffer/CellData'; import { IOptionsService } from 'common/services/Services'; -import { rgbRelativeLuminance, fromCss, contrastRatio, ensureContrastRatio } from 'browser/Color'; +import { ensureContrastRatio } from 'browser/Color'; import { IColorSet, IColor } from 'browser/Types'; export const BOLD_CLASS = 'xterm-bold'; diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 9174398ec..b0b576942 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -37,7 +37,7 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({ screenReaderMode: false, macOptionIsMeta: false, macOptionClickForcesSelection: false, - minimumContrastRatio: 6, + minimumContrastRatio: 1, disableStdin: false, allowTransparency: false, tabStopWidth: 8, From b1bcb899e0aa478ee49023ede4c346bbc6f20c66 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 11 Nov 2019 21:29:02 -0800 Subject: [PATCH 073/268] Fix inverse in webgl, add tests --- .../src/RectangleRenderer.ts | 26 +- .../src/WebglRenderer.api.ts | 233 +++++++++++++++++- addons/xterm-addon-webgl/src/WebglRenderer.ts | 32 +-- .../src/atlas/WebglCharAtlas.ts | 74 ++++-- 4 files changed, 309 insertions(+), 56 deletions(-) diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index 8dcc5c083..356e17472 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -248,23 +248,26 @@ export class RectangleRenderer { let currentStartX = -1; let currentBg = 0; let currentFg = 0; + let currentInverse = false; for (let x = 0; x < terminal.cols; x++) { const modelIndex = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL; const bg = model.cells[modelIndex + RENDER_MODEL_BG_OFFSET]; const fg = model.cells[modelIndex + RENDER_MODEL_FG_OFFSET]; - if (bg !== currentBg) { + const inverse = !!(fg & FgFlags.INVERSE); + if (bg !== currentBg || ((inverse || currentInverse) && fg !== currentFg)) { // A rectangle needs to be drawn if going from non-default to another color - if (currentBg !== 0) { + if (currentBg !== 0 || (currentInverse && currentFg !== 0)) { const offset = rectangleCount++ * INDICES_PER_RECTANGLE; this._updateRectangle(vertices, offset, currentFg, currentBg, currentStartX, x, y); } currentStartX = x; currentBg = bg; currentFg = fg; + currentInverse = inverse; } } // Finish rectangle if it's still going - if (currentBg !== 0) { + if (currentBg !== 0 || (currentInverse && currentFg !== 0)) { const offset = rectangleCount++ * INDICES_PER_RECTANGLE; this._updateRectangle(vertices, offset, currentFg, currentBg, currentStartX, terminal.cols, y); } @@ -274,12 +277,21 @@ export class RectangleRenderer { private _updateRectangle(vertices: IVertices, offset: number, fg: number, bg: number, startX: number, endX: number, y: number): void { let rgba: number | undefined; - const colorMode = bg & Attributes.CM_MASK; if (fg & FgFlags.INVERSE) { - // Inverted color - rgba = this._colors.foreground.rgba; + switch (fg & Attributes.CM_MASK) { + case Attributes.CM_P16: + case Attributes.CM_P256: + rgba = this._colors.ansi[fg & Attributes.PCOLOR_MASK].rgba; + break; + case Attributes.CM_RGB: + rgba = (fg & Attributes.RGB_MASK) << 8; + break; + case Attributes.CM_DEFAULT: + default: + rgba = this._colors.foreground.rgba; + } } else { - switch (colorMode) { + switch (bg & Attributes.CM_MASK) { case Attributes.CM_P16: case Attributes.CM_P256: rgba = this._colors.ansi[bg & Attributes.PCOLOR_MASK].rgba; diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 304973fef..556061e76 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -16,7 +16,7 @@ let page: puppeteer.Page; const width = 800; const height = 600; -describe('WebGL Renderer Integration Tests', function(): void { +describe.only('WebGL Renderer Integration Tests', function(): void { it('dispose removes renderer canvases', async () => { await setupBrowser(); assert.equal(await page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 3); @@ -76,6 +76,52 @@ describe('WebGL Renderer Integration Tests', function(): void { await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); }); + it('foreground 0-15 inverse', async () => { + const theme: ITheme = { + black: '#010203', + red: '#040506', + green: '#070809', + yellow: '#0a0b0c', + blue: '#0d0e0f', + magenta: '#101112', + cyan: '#131415', + white: '#161718' + }; + await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); + await writeSync(`\\x1b[7;30m \\x1b[7;31m \\x1b[7;32m \\x1b[7;33m \\x1b[7;34m \\x1b[7;35m \\x1b[7;36m \\x1b[7;37m `); + await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]); + await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]); + await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]); + await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]); + await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]); + await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]); + await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]); + await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); + }); + + it('background 0-15 inverse', async () => { + const theme: ITheme = { + black: '#010203', + red: '#040506', + green: '#070809', + yellow: '#0a0b0c', + blue: '#0d0e0f', + magenta: '#101112', + cyan: '#131415', + white: '#161718' + }; + await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); + await writeSync(`\\x1b[7;40m█\\x1b[7;41m█\\x1b[7;42m█\\x1b[7;43m█\\x1b[7;44m█\\x1b[7;45m█\\x1b[7;46m█\\x1b[7;47m█`); + await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]); + await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]); + await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]); + await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]); + await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]); + await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]); + await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]); + await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); + }); + it('foreground 0-15 bright', async () => { const theme: ITheme = { brightBlack: '#010203', @@ -162,6 +208,46 @@ describe('WebGL Renderer Integration Tests', function(): void { } }); + it('foreground 16-255 inverse', async () => { + let data = ''; + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + data += `\\x1b[7;38;5;${16 + y * 16 + x}m \x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + const cssColor = COLORS_16_TO_255[y * 16 + x]; + const r = parseInt(cssColor.substr(1, 2), 16); + const g = parseInt(cssColor.substr(3, 2), 16); + const b = parseInt(cssColor.substr(5, 2), 16); + await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); + } + } + }); + + it('background 16-255 inverse', async () => { + let data = ''; + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + data += `\\x1b[7;48;5;${16 + y * 16 + x}m█\x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + const cssColor = COLORS_16_TO_255[y * 16 + x]; + const r = parseInt(cssColor.substr(1, 2), 16); + const g = parseInt(cssColor.substr(3, 2), 16); + const b = parseInt(cssColor.substr(5, 2), 16); + await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); + } + } + }); + it('foreground true color red', async () => { let data = ''; for (let y = 0; y < 16; y++) { @@ -305,6 +391,151 @@ describe('WebGL Renderer Integration Tests', function(): void { } } }); + + it('foreground true color red inverse', async function(): Promise { + this.timeout(60000); + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[7;38;2;${i};0;0m \x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]); + } + } + }); + + it('background true color red inverse', async function(): Promise { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\\x1b[7;48;2;${i};0;0m█\\x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]); + } + } + }); + + it('foreground true color green inverse', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\\x1b[7;38;2;0;${i};0m \x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]); + } + } + }); + + it('background true color green inverse', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\\x1b[7;48;2;0;${i};0m█\x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]); + } + } + }); + + it('foreground true color blue inverse', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\\x1b[7;38;2;0;0;${i}m \x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]); + } + } + }); + + it('background true color blue inverse', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\\x1b[7;48;2;0;0;${i}m█\x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]); + } + } + }); + + it('foreground true color grey inverse', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\\x1b[7;38;2;${i};${i};${i}m \x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]); + } + } + }); + + it('background true color grey inverse', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\\x1b[7;48;2;${i};${i};${i}m█\x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]); + } + } + }); }); }); diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 5eae61db6..3a71dcd8e 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -252,35 +252,13 @@ export class WebglRenderer extends Disposable implements IRenderer { this._model.lineLengths[y] = x + 1; } - // Resolve bg and fg - let bg = this._workCell.bg; - let fg = this._workCell.fg; - // Nothing has changed, no updates needed if (this._model.cells[i] === code && - this._model.cells[i + RENDER_MODEL_BG_OFFSET] === bg && - this._model.cells[i + RENDER_MODEL_FG_OFFSET] === fg) { + this._model.cells[i + RENDER_MODEL_BG_OFFSET] === this._workCell.bg && + this._model.cells[i + RENDER_MODEL_FG_OFFSET] === this._workCell.fg) { continue; } - // If inverse flag is on, the foreground should become the background. - if (this._workCell.isInverse()) { - const temp = bg; - bg = fg; - fg = temp; - if (fg === DEFAULT_COLOR) { - fg = INVERTED_DEFAULT_COLOR; - } - if (bg === DEFAULT_COLOR) { - bg = INVERTED_DEFAULT_COLOR; - } - } - - // Apply drawBoldTextInBrightColors - if (terminal.options.drawBoldTextInBrightColors && this._workCell.isBold() && fg & FgFlags.BOLD && this._workCell.getFgColor() < 8) { - fg += 8; - } - // Flag combined chars with a bit mask so they're easily identifiable if (chars.length > 1) { code = code | COMBINED_CHAR_BIT_MASK; @@ -288,10 +266,10 @@ export class WebglRenderer extends Disposable implements IRenderer { // Cache the results in the model this._model.cells[i] = code; - this._model.cells[i + RENDER_MODEL_BG_OFFSET] = bg; - this._model.cells[i + RENDER_MODEL_FG_OFFSET] = fg; + this._model.cells[i + RENDER_MODEL_BG_OFFSET] = this._workCell.bg; + this._model.cells[i + RENDER_MODEL_FG_OFFSET] = this._workCell.fg; - this._glyphRenderer.updateCell(x, y, code, bg, fg, chars); + this._glyphRenderer.updateCell(x, y, code, this._workCell.bg, this._workCell.fg, chars); } } this._rectangleRenderer.updateBackgrounds(this._model); diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 16b7ba07b..b4d3f822f 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -176,51 +176,47 @@ export class WebglCharAtlas implements IDisposable { return this._config.colors.ansi[idx]; } - private _getBackgroundColor(bg: number, fg: number): IColor { + private _getBackgroundColor(bgColorMode: number, bgColor: number, inverse: boolean): IColor { if (this._config.allowTransparency) { // The background color might have some transparency, so we need to render it as fully // transparent in the atlas. Otherwise we'd end up drawing the transparent background twice // around the anti-aliased edges of the glyph, and it would look too dark. return TRANSPARENT_COLOR; - } else if (fg & FgFlags.INVERSE) { - return this._config.colors.foreground; } - const colorMode = bg & Attributes.CM_MASK; - switch (colorMode) { + switch (bgColorMode) { case Attributes.CM_P16: case Attributes.CM_P256: - return this._getColorFromAnsiIndex(bg & Attributes.PCOLOR_MASK); + return this._getColorFromAnsiIndex(bgColor); case Attributes.CM_RGB: - const rgb = bg & Attributes.RGB_MASK; - const arr = AttributeData.toColorRGB(rgb); + const arr = AttributeData.toColorRGB(bgColor); // TODO: This object creation is slow return { - rgba: rgb << 8, + rgba: bgColor << 8, css: `#${toPaddedHex(arr[0])}${toPaddedHex(arr[1])}${toPaddedHex(arr[2])}` }; case Attributes.CM_DEFAULT: default: + if (inverse) { + return this._config.colors.foreground; + } return this._config.colors.background; } } - private _getForegroundCss(fg: number): string { - if (fg & FgFlags.INVERSE) { - return this._config.colors.background.css; - } - - const colorMode = fg & Attributes.CM_MASK; - switch (colorMode) { + private _getForegroundCss(fgColorMode: number, fgColor: number, inverse: boolean): string { + switch (fgColorMode) { case Attributes.CM_P16: case Attributes.CM_P256: - return this._getColorFromAnsiIndex(fg & Attributes.PCOLOR_MASK).css; + return this._getColorFromAnsiIndex(fgColor).css; case Attributes.CM_RGB: - const rgb = fg & Attributes.RGB_MASK; - const arr = AttributeData.toColorRGB(rgb); + const arr = AttributeData.toColorRGB(fgColor); return `#${toPaddedHex(arr[0])}${toPaddedHex(arr[1])}${toPaddedHex(arr[2])}`; case Attributes.CM_DEFAULT: default: + if (inverse) { + return this._config.colors.background.css; + } return this._config.colors.foreground.css; } } @@ -233,13 +229,33 @@ export class WebglCharAtlas implements IDisposable { this.hasCanvasChanged = true; const bold = !!(fg & FgFlags.BOLD); + const inverse = !!(fg & FgFlags.INVERSE); const dim = !!(bg & BgFlags.DIM); const italic = !!(bg & BgFlags.ITALIC); this._tmpCtx.save(); + let fgColor = getFgColor(fg); + let fgColorMode = fg & Attributes.CM_MASK; + let bgColor = getBgColor(bg); + let bgColorMode = bg & Attributes.CM_MASK; + if (inverse) { + const temp = fgColor; + fgColor = bgColor; + bgColor = temp; + const temp2 = fgColorMode; + fgColorMode = bgColorMode; + bgColorMode = temp2; + } + + // TODO: Pass drawBoldTextInBrightColors through + // Apply drawBoldTextInBrightColors + // if (terminal.options.drawBoldTextInBrightColors && this._workCell.isBold() && fg & FgFlags.BOLD && this._workCell.getFgColor() < 8) { + // fg += 8; + // } + // draw the background - const backgroundColor = this._getBackgroundColor(bg, fg); + const backgroundColor = this._getBackgroundColor(bgColorMode, bgColor, inverse); // Use a 'copy' composite operation to clear any existing glyph out of _tmpCtxWithAlpha, regardless of // transparency in backgroundColor this._tmpCtx.globalCompositeOperation = 'copy'; @@ -254,7 +270,7 @@ export class WebglCharAtlas implements IDisposable { `${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`; this._tmpCtx.textBaseline = 'top'; - this._tmpCtx.fillStyle = this._getForegroundCss(fg); + this._tmpCtx.fillStyle = this._getForegroundCss(fgColorMode, fgColor, inverse); // Apply alpha to dim the character if (dim) { @@ -441,3 +457,19 @@ function toPaddedHex(c: number): string { return s.length < 2 ? '0' + s : s; } +function getFgColor(fg: number): number { + switch (fg & Attributes.CM_MASK) { + case Attributes.CM_P16: + case Attributes.CM_P256: return fg & Attributes.PCOLOR_MASK; + case Attributes.CM_RGB: return fg & Attributes.RGB_MASK; + default: return -1; // CM_DEFAULT defaults to -1 + } +} +function getBgColor(bg: number): number { + switch (bg & Attributes.CM_MASK) { + case Attributes.CM_P16: + case Attributes.CM_P256: return bg & Attributes.PCOLOR_MASK; + case Attributes.CM_RGB: return bg & Attributes.RGB_MASK; + default: return -1; // CM_DEFAULT defaults to -1 + } +} From db775afd70086be49fe2df9aa429523900ac0d42 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 11 Nov 2019 21:35:49 -0800 Subject: [PATCH 074/268] Support drawBoldTextInBrightColors in webgl again This broke as part of this PR --- .../src/WebglRenderer.api.ts | 27 ++++++++++++++++++- .../src/atlas/CharAtlasUtils.ts | 1 + addons/xterm-addon-webgl/src/atlas/Types.d.ts | 1 + .../src/atlas/WebglCharAtlas.ts | 18 ++++++------- 4 files changed, 37 insertions(+), 10 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 556061e76..18449e8ae 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -53,6 +53,32 @@ describe.only('WebGL Renderer Integration Tests', function(): void { await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); }); + it('foreground 0-7 drawBoldTextInBrightColors', async () => { + const theme: ITheme = { + brightBlack: '#010203', + brightRed: '#040506', + brightGreen: '#070809', + brightYellow: '#0a0b0c', + brightBlue: '#0d0e0f', + brightMagenta: '#101112', + brightCyan: '#131415', + brightWhite: '#161718' + }; + await page.evaluate(` + window.term.setOption('theme', ${JSON.stringify(theme)}); + window.term.setOption('drawBoldTextInBrightColors', true); + `); + await writeSync(`\\x1b[1;30m█\\x1b[1;31m█\\x1b[1;32m█\\x1b[1;33m█\\x1b[1;34m█\\x1b[1;35m█\\x1b[1;36m█\\x1b[1;37m█`); + await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]); + await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]); + await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]); + await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]); + await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]); + await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]); + await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]); + await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); + }); + it('background 0-15', async () => { const theme: ITheme = { black: '#010203', @@ -393,7 +419,6 @@ describe.only('WebGL Renderer Integration Tests', function(): void { }); it('foreground true color red inverse', async function(): Promise { - this.timeout(60000); let data = ''; for (let y = 0; y < 16; y++) { for (let x = 0; x < 16; x++) { diff --git a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts index 03867b410..ec7d9a7e8 100644 --- a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts +++ b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts @@ -35,6 +35,7 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number fontWeight: terminal.getOption('fontWeight') as FontWeight, fontWeightBold: terminal.getOption('fontWeightBold') as FontWeight, allowTransparency: terminal.getOption('allowTransparency'), + drawBoldTextInBrightColors: terminal.getOption('drawBoldTextInBrightColors'), colors: clonedColors }; } diff --git a/addons/xterm-addon-webgl/src/atlas/Types.d.ts b/addons/xterm-addon-webgl/src/atlas/Types.d.ts index 1de843e08..5b96adb06 100644 --- a/addons/xterm-addon-webgl/src/atlas/Types.d.ts +++ b/addons/xterm-addon-webgl/src/atlas/Types.d.ts @@ -25,5 +25,6 @@ export interface ICharAtlasConfig { scaledCharWidth: number; scaledCharHeight: number; allowTransparency: boolean; + drawBoldTextInBrightColors: boolean; colors: IColorSet; } diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index b4d3f822f..6325ca920 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -68,7 +68,10 @@ export class WebglCharAtlas implements IDisposable { private _workBoundingBox: IBoundingBox = { top: 0, left: 0, bottom: 0, right: 0 }; - constructor(document: Document, private _config: ICharAtlasConfig) { + constructor( + document: Document, + private _config: ICharAtlasConfig + ) { this.cacheCanvas = document.createElement('canvas'); this.cacheCanvas.width = TEXTURE_WIDTH; this.cacheCanvas.height = TEXTURE_HEIGHT; @@ -204,10 +207,13 @@ export class WebglCharAtlas implements IDisposable { } } - private _getForegroundCss(fgColorMode: number, fgColor: number, inverse: boolean): string { + private _getForegroundCss(fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): string { switch (fgColorMode) { case Attributes.CM_P16: case Attributes.CM_P256: + if (this._config.drawBoldTextInBrightColors && bold && fgColor < 8) { + fgColor += 8; + } return this._getColorFromAnsiIndex(fgColor).css; case Attributes.CM_RGB: const arr = AttributeData.toColorRGB(fgColor); @@ -248,12 +254,6 @@ export class WebglCharAtlas implements IDisposable { bgColorMode = temp2; } - // TODO: Pass drawBoldTextInBrightColors through - // Apply drawBoldTextInBrightColors - // if (terminal.options.drawBoldTextInBrightColors && this._workCell.isBold() && fg & FgFlags.BOLD && this._workCell.getFgColor() < 8) { - // fg += 8; - // } - // draw the background const backgroundColor = this._getBackgroundColor(bgColorMode, bgColor, inverse); // Use a 'copy' composite operation to clear any existing glyph out of _tmpCtxWithAlpha, regardless of @@ -270,7 +270,7 @@ export class WebglCharAtlas implements IDisposable { `${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`; this._tmpCtx.textBaseline = 'top'; - this._tmpCtx.fillStyle = this._getForegroundCss(fgColorMode, fgColor, inverse); + this._tmpCtx.fillStyle = this._getForegroundCss(fgColorMode, fgColor, inverse, bold); // Apply alpha to dim the character if (dim) { From 2283ea45a6de0dddacd7a112cdca2b5bd5d694e7 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 11 Nov 2019 22:18:39 -0800 Subject: [PATCH 075/268] Refresh rows after ratio changes --- src/Terminal.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Terminal.ts b/src/Terminal.ts index 3c589ffa9..b08270fd9 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -329,6 +329,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp case 'lineHeight': case 'fontWeight': case 'fontWeightBold': + case 'minimumContrastRatio': // When the font changes the size of the cells may change which requires a renderer clear if (this._renderService) { this._renderService.clear(); From 531409c2c7c3dea4b05394e0ec1952bfe443fa2a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 11 Nov 2019 22:47:26 -0800 Subject: [PATCH 076/268] Support minimum contrast ratio in webgl --- .../src/WebglRenderer.api.ts | 72 ++++++- .../src/atlas/CharAtlasUtils.ts | 3 + addons/xterm-addon-webgl/src/atlas/Types.d.ts | 1 + .../src/atlas/WebglCharAtlas.ts | 179 +++++++++++++++++- src/browser/Color.ts | 67 ++++--- 5 files changed, 291 insertions(+), 31 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 18449e8ae..25719faad 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -16,7 +16,7 @@ let page: puppeteer.Page; const width = 800; const height = 600; -describe.only('WebGL Renderer Integration Tests', function(): void { +describe('WebGL Renderer Integration Tests', function(): void { it('dispose removes renderer canvases', async () => { await setupBrowser(); assert.equal(await page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 3); @@ -562,6 +562,76 @@ describe.only('WebGL Renderer Integration Tests', function(): void { } }); }); + + describe('minimumContrastRatio', async () => { + before(async () => setupBrowser()); + after(async () => browser.close()); + beforeEach(async () => page.evaluate(`window.term.reset()`)); + + it('should adjust 0-15 colors on black background', async () => { + const theme: ITheme = { + black: '#2e3436', + red: '#cc0000', + green: '#4e9a06', + yellow: '#c4a000', + blue: '#3465a4', + magenta: '#75507b', + cyan: '#06989a', + white: '#d3d7cf', + brightBlack: '#555753', + brightRed: '#ef2929', + brightGreen: '#8ae234', + brightYellow: '#fce94f', + brightBlue: '#729fcf', + brightMagenta: '#ad7fa8', + brightCyan: '#34e2e2', + brightWhite: '#eeeeec' + }; + await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); + await writeSync( + `\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█\\r\\n` + + `\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█` + ); + // Validate before minimumContrastRatio is applied + await pollFor(page, () => getCellColor(1, 1), [0x2e, 0x34, 0x36, 255]); + await pollFor(page, () => getCellColor(2, 1), [0xcc, 0x00, 0x00, 255]); + await pollFor(page, () => getCellColor(3, 1), [0x4e, 0x9a, 0x06, 255]); + await pollFor(page, () => getCellColor(4, 1), [0xc4, 0xa0, 0x00, 255]); + await pollFor(page, () => getCellColor(5, 1), [0x34, 0x65, 0xa4, 255]); + await pollFor(page, () => getCellColor(6, 1), [0x75, 0x50, 0x7b, 255]); + await pollFor(page, () => getCellColor(7, 1), [0x06, 0x98, 0x9a, 255]); + await pollFor(page, () => getCellColor(8, 1), [0xd3, 0xd7, 0xcf, 255]); + await pollFor(page, () => getCellColor(1, 2), [0x55, 0x57, 0x53, 255]); + await pollFor(page, () => getCellColor(2, 2), [0xef, 0x29, 0x29, 255]); + await pollFor(page, () => getCellColor(3, 2), [0x8a, 0xe2, 0x34, 255]); + await pollFor(page, () => getCellColor(4, 2), [0xfc, 0xe9, 0x4f, 255]); + await pollFor(page, () => getCellColor(5, 2), [0x72, 0x9f, 0xcf, 255]); + await pollFor(page, () => getCellColor(6, 2), [0xad, 0x7f, 0xa8, 255]); + await pollFor(page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]); + await pollFor(page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]); + // Setting and check for minimum contrast values, note that these are note + // exact to the contrast ratio, if the increase luminance algorithm + // changes then these will probably fail + await page.evaluate(`window.term.setOption('minimumContrastRatio', 10);`); + await pollFor(page, () => getCellColor(1, 1), [179, 182, 182, 255]); + await pollFor(page, () => getCellColor(2, 1), [234, 163, 163, 255]); + await pollFor(page, () => getCellColor(3, 1), [196, 221, 174, 255]); + await pollFor(page, () => getCellColor(4, 1), [231, 219, 163, 255]); + await pollFor(page, () => getCellColor(5, 1), [133, 162, 200, 255]); + await pollFor(page, () => getCellColor(6, 1), [180, 159, 182, 255]); + await pollFor(page, () => getCellColor(7, 1), [106, 192, 194, 255]); + await pollFor(page, () => getCellColor(8, 1), [211, 215, 207, 255]); + await pollFor(page, () => getCellColor(1, 2), [180, 181, 179, 255]); + await pollFor(page, () => getCellColor(2, 2), [246, 160, 160, 255]); + await pollFor(page, () => getCellColor(3, 2), [138, 226, 52, 255]); + await pollFor(page, () => getCellColor(4, 2), [252, 233, 79, 255]); + await pollFor(page, () => getCellColor(5, 2), [114, 159, 207, 255]); + await pollFor(page, () => getCellColor(6, 2), [188, 150, 183, 255]); + // Unchanged + await pollFor(page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]); + await pollFor(page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]); + }); + }); }); async function openTerminal(options: ITerminalOptions = {}): Promise { diff --git a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts index ec7d9a7e8..e8e190955 100644 --- a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts +++ b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts @@ -36,6 +36,7 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number fontWeightBold: terminal.getOption('fontWeightBold') as FontWeight, allowTransparency: terminal.getOption('allowTransparency'), drawBoldTextInBrightColors: terminal.getOption('drawBoldTextInBrightColors'), + minimumContrastRatio: terminal.getOption('minimumContrastRatio'), colors: clonedColors }; } @@ -54,6 +55,8 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean a.allowTransparency === b.allowTransparency && a.scaledCharWidth === b.scaledCharWidth && a.scaledCharHeight === b.scaledCharHeight && + a.drawBoldTextInBrightColors === b.drawBoldTextInBrightColors && + a.minimumContrastRatio === b.minimumContrastRatio && a.colors.foreground === b.colors.foreground && a.colors.background === b.colors.background; } diff --git a/addons/xterm-addon-webgl/src/atlas/Types.d.ts b/addons/xterm-addon-webgl/src/atlas/Types.d.ts index 5b96adb06..cd73393ce 100644 --- a/addons/xterm-addon-webgl/src/atlas/Types.d.ts +++ b/addons/xterm-addon-webgl/src/atlas/Types.d.ts @@ -26,5 +26,6 @@ export interface ICharAtlasConfig { scaledCharHeight: number; allowTransparency: boolean; drawBoldTextInBrightColors: boolean; + minimumContrastRatio: number; colors: IColorSet; } diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 6325ca920..f66910676 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -207,7 +207,12 @@ export class WebglCharAtlas implements IDisposable { } } - private _getForegroundCss(fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): string { + private _getForegroundCss(bgColorMode: number, bgColor: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): string { + const minimumContrastCss = this._getMinimumContrastCss(bgColorMode, bgColor, fgColorMode, fgColor, inverse); + if (minimumContrastCss) { + return minimumContrastCss; + } + switch (fgColorMode) { case Attributes.CM_P16: case Attributes.CM_P256: @@ -217,7 +222,7 @@ export class WebglCharAtlas implements IDisposable { return this._getColorFromAnsiIndex(fgColor).css; case Attributes.CM_RGB: const arr = AttributeData.toColorRGB(fgColor); - return `#${toPaddedHex(arr[0])}${toPaddedHex(arr[1])}${toPaddedHex(arr[2])}`; + return toCss(arr[0], arr[1], arr[2]); case Attributes.CM_DEFAULT: default: if (inverse) { @@ -227,6 +232,57 @@ export class WebglCharAtlas implements IDisposable { } } + private _resolveBackgroundRgba(bgColorMode: number, bgColor: number, fgColorMode: number, fgColor: number, inverse: boolean): number { + switch (bgColorMode) { + case Attributes.CM_P16: + case Attributes.CM_P256: + return this._getColorFromAnsiIndex(bgColor).rgba; + case Attributes.CM_RGB: + return bgColor << 8; + case Attributes.CM_DEFAULT: + default: + if (inverse) { + this._config.colors.foreground.rgba; + } + return this._config.colors.background.rgba; + } + } + + private _resolveForegroundRgba(bgColorMode: number, bgColor: number, fgColorMode: number, fgColor: number, inverse: boolean): number { + switch (fgColorMode) { + case Attributes.CM_P16: + case Attributes.CM_P256: + return this._getColorFromAnsiIndex(fgColor).rgba; + case Attributes.CM_RGB: + return fgColor << 8; + case Attributes.CM_DEFAULT: + default: + if (inverse) { + this._config.colors.background.rgba; + } + return this._config.colors.foreground.rgba; + } + } + + private _getMinimumContrastCss(bgColorMode: number, bgColor: number, fgColorMode: number, fgColor: number, inverse: boolean): string | undefined { + const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, fgColorMode, fgColor, inverse); + const fgRgba = this._resolveForegroundRgba(bgColorMode, bgColor, fgColorMode, fgColor, inverse); + console.log('ratio', this._config.minimumContrastRatio); + if (this._config.minimumContrastRatio === 1) { + return undefined; + } + const result = ensureContrastRatioRgba(bgRgba, fgRgba, this._config.minimumContrastRatio); + console.log('get min', result, bgRgba, fgRgba); + if (!result) { + return undefined; + } + return toCss( + (result >> 24) & 0xFF, + (result >> 16) & 0xFF, + (result >> 8) & 0xFF + ); + } + private _drawToCache(code: number, bg: number, fg: number): IRasterizedGlyph; private _drawToCache(chars: string, bg: number, fg: number): IRasterizedGlyph; private _drawToCache(codeOrChars: number | string, bg: number, fg: number): IRasterizedGlyph { @@ -270,7 +326,7 @@ export class WebglCharAtlas implements IDisposable { `${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`; this._tmpCtx.textBaseline = 'top'; - this._tmpCtx.fillStyle = this._getForegroundCss(fgColorMode, fgColor, inverse, bold); + this._tmpCtx.fillStyle = this._getForegroundCss(bgColorMode, bgColor, fgColorMode, fgColor, inverse, bold); // Apply alpha to dim the character if (dim) { @@ -473,3 +529,120 @@ function getBgColor(bg: number): number { default: return -1; // CM_DEFAULT defaults to -1 } } + +export function toCss(r: number, g: number, b: number): string { + return `#${toPaddedHex(r)}${toPaddedHex(g)}${toPaddedHex(b)}`; +} + +export function toRgba(r: number, g: number, b: number, a: number = 0xFF): number { + // >>> 0 forces an unsigned int + return (r << 24 | g << 16 | b << 8 | a) >>> 0; +} + +/** + * Gets the relative luminance of an RGB color, this is useful in determining the contrast ratio + * between two colors. + * @param rgb The color to use. + * @see https://www.w3.org/TR/WCAG20/#relativeluminancedef + */ +export function rgbRelativeLuminance(rgb: number): number { + return rgbRelativeLuminance2( + (rgb >> 16) & 0xFF, + (rgb >> 8 ) & 0xFF, + (rgb ) & 0xFF); +} + +export function rgbRelativeLuminance2(r: number, g: number, b: number): number { + const rs = r / 255; + const gs = g / 255; + const bs = b / 255; + const rr = rs <= 0.03928 ? rs / 12.92 : Math.pow((rs + 0.055) / 1.055, 2.4); + const rg = gs <= 0.03928 ? gs / 12.92 : Math.pow((gs + 0.055) / 1.055, 2.4); + const rb = bs <= 0.03928 ? bs / 12.92 : Math.pow((bs + 0.055) / 1.055, 2.4); + return rr * 0.2126 + rg * 0.7152 + rb * 0.0722; +} + +/** + * Gets the contrast ratio between two relative luminance values. + * @param l1 The first relative luminance. + * @param l2 The first relative luminance. + * @see https://www.w3.org/TR/WCAG20/#contrast-ratiodef + */ +export function contrastRatio(l1: number, l2: number): number { + if (l1 < l2) { + return (l2 + 0.05) / (l1 + 0.05); + } + return (l1 + 0.05) / (l2 + 0.05); +} + +function rgbaToColor(r: number, g: number, b: number): IColor { + return { + css: toCss(r, g, b), + rgba: toRgba(r, g, b) + }; +} + +export function ensureContrastRatioRgba(bgRgba: number, fgRgba: number, ratio: number): number | undefined { + const bgL = rgbRelativeLuminance(bgRgba >> 8); + const fgL = rgbRelativeLuminance(fgRgba >> 8); + const cr = contrastRatio(bgL, fgL); + if (cr < ratio) { + if (fgL < bgL) { + return reduceLuminance(bgRgba, fgRgba, ratio); + } + return increaseLuminance(bgRgba, fgRgba, ratio); + } + return undefined; +} + +export function ensureContrastRatio(bg: IColor, fg: IColor, ratio: number): IColor | undefined { + const result = ensureContrastRatioRgba(bg.rgba, fg.rgba, ratio); + if (!result) { + return undefined; + } + return rgbaToColor( + (result >> 24 & 0xFF), + (result >> 16 & 0xFF), + (result >> 8 & 0xFF) + ); +} + +export function reduceLuminance(bgRgba: number, fgRgba: number, ratio: number): number { + // This is a naive but fast approach to reducing luminance as converting to + // HSL and back is expensive + const bgR = (bgRgba >> 24) & 0xFF; + const bgG = (bgRgba >> 16) & 0xFF; + const bgB = (bgRgba >> 8) & 0xFF; + let fgR = (fgRgba >> 24) & 0xFF; + let fgG = (fgRgba >> 16) & 0xFF; + let fgB = (fgRgba >> 8) & 0xFF; + let cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); + while (cr < ratio && (fgR > 0 || fgG > 0 || fgB > 0)) { + // Increase by 10% (ceil) until the ratio is hit + fgR -= Math.max(0, Math.ceil(fgR * 0.1)); + fgG -= Math.max(0, Math.ceil(fgG * 0.1)); + fgB -= Math.max(0, Math.ceil(fgB * 0.1)); + cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); + } + return fgR << 24 | fgG << 16 | fgB << 8 | 0xFF; +} + +export function increaseLuminance(bgRgba: number, fgRgba: number, ratio: number): number { + // This is a naive but fast approach to increasing luminance as converting to + // HSL and back is expensive + const bgR = (bgRgba >> 24) & 0xFF; + const bgG = (bgRgba >> 16) & 0xFF; + const bgB = (bgRgba >> 8) & 0xFF; + let fgR = (fgRgba >> 24) & 0xFF; + let fgG = (fgRgba >> 16) & 0xFF; + let fgB = (fgRgba >> 8) & 0xFF; + let cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); + while (cr < ratio && (fgR < 0xFF || fgG < 0xFF || fgB < 0xFF)) { + // Increase by 10% until the ratio is hit + fgR = Math.min(0xFF, fgR + Math.floor((255 - fgR) * 0.1)); + fgG = Math.min(0xFF, fgG + Math.floor((255 - fgG) * 0.1)); + fgB = Math.min(0xFF, fgB + Math.floor((255 - fgB) * 0.1)); + cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); + } + return fgR << 24 | fgG << 16 | fgB << 8 | 0xFF; +} diff --git a/src/browser/Color.ts b/src/browser/Color.ts index c74e87455..c63ccee33 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -86,28 +86,47 @@ export function contrastRatio(l1: number, l2: number): number { // TODO: Cache [bg][fg]: result, should probably be owned by ColorManager? -export function ensureContrastRatio(bg: IColor, fg: IColor, ratio: number): IColor | undefined { - const bgL = rgbRelativeLuminance(bg.rgba >> 8); - const fgL = rgbRelativeLuminance(fg.rgba >> 8); +function rgbaToColor(r: number, g: number, b: number): IColor { + return { + css: toCss(r, g, b), + rgba: toRgba(r, g, b) + }; +} + +export function ensureContrastRatioRgba(bgRgba: number, fgRgba: number, ratio: number): number | undefined { + const bgL = rgbRelativeLuminance(bgRgba >> 8); + const fgL = rgbRelativeLuminance(fgRgba >> 8); const cr = contrastRatio(bgL, fgL); if (cr < ratio) { if (fgL < bgL) { - return reduceLuminance(bg, fg, ratio); + return reduceLuminance(bgRgba, fgRgba, ratio); } - return increaseLuminance(bg, fg, ratio); + return increaseLuminance(bgRgba, fgRgba, ratio); } return undefined; } -export function reduceLuminance(bg: IColor, fg: IColor, ratio: number): IColor { +export function ensureContrastRatio(bg: IColor, fg: IColor, ratio: number): IColor | undefined { + const result = ensureContrastRatioRgba(bg.rgba, fg.rgba, ratio); + if (!result) { + return undefined; + } + return rgbaToColor( + (result >> 24 & 0xFF), + (result >> 16 & 0xFF), + (result >> 8 & 0xFF) + ); +} + +export function reduceLuminance(bgRgba: number, fgRgba: number, ratio: number): number { // This is a naive but fast approach to reducing luminance as converting to // HSL and back is expensive - const bgR = (bg.rgba >> 24) & 0xFF; - const bgG = (bg.rgba >> 16) & 0xFF; - const bgB = (bg.rgba >> 8) & 0xFF; - let fgR = (fg.rgba >> 24) & 0xFF; - let fgG = (fg.rgba >> 16) & 0xFF; - let fgB = (fg.rgba >> 8) & 0xFF; + const bgR = (bgRgba >> 24) & 0xFF; + const bgG = (bgRgba >> 16) & 0xFF; + const bgB = (bgRgba >> 8) & 0xFF; + let fgR = (fgRgba >> 24) & 0xFF; + let fgG = (fgRgba >> 16) & 0xFF; + let fgB = (fgRgba >> 8) & 0xFF; let cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); while (cr < ratio && (fgR > 0 || fgG > 0 || fgB > 0)) { // Increase by 10% (ceil) until the ratio is hit @@ -116,21 +135,18 @@ export function reduceLuminance(bg: IColor, fg: IColor, ratio: number): IColor { fgB -= Math.max(0, Math.ceil(fgB * 0.1)); cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); } - return { - css: toCss(fgR, fgG, fgB), - rgba: toRgba(fgR, fgG, fgB) - }; + return fgR << 24 | fgG << 16 | fgB << 8 | 0xFF; } -export function increaseLuminance(bg: IColor, fg: IColor, ratio: number): IColor { +export function increaseLuminance(bgRgba: number, fgRgba: number, ratio: number): number { // This is a naive but fast approach to increasing luminance as converting to // HSL and back is expensive - const bgR = (bg.rgba >> 24) & 0xFF; - const bgG = (bg.rgba >> 16) & 0xFF; - const bgB = (bg.rgba >> 8) & 0xFF; - let fgR = (fg.rgba >> 24) & 0xFF; - let fgG = (fg.rgba >> 16) & 0xFF; - let fgB = (fg.rgba >> 8) & 0xFF; + const bgR = (bgRgba >> 24) & 0xFF; + const bgG = (bgRgba >> 16) & 0xFF; + const bgB = (bgRgba >> 8) & 0xFF; + let fgR = (fgRgba >> 24) & 0xFF; + let fgG = (fgRgba >> 16) & 0xFF; + let fgB = (fgRgba >> 8) & 0xFF; let cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); while (cr < ratio && (fgR < 0xFF || fgG < 0xFF || fgB < 0xFF)) { // Increase by 10% until the ratio is hit @@ -139,8 +155,5 @@ export function increaseLuminance(bg: IColor, fg: IColor, ratio: number): IColor fgB = Math.min(0xFF, fgB + Math.floor((255 - fgB) * 0.1)); cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); } - return { - css: toCss(fgR, fgG, fgB), - rgba: toRgba(fgR, fgG, fgB) - }; + return fgR << 24 | fgG << 16 | fgB << 8 | 0xFF; } From 9b139a744b4d4862dda4e66b924210ed7ec4d0c8 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 11 Nov 2019 22:55:08 -0800 Subject: [PATCH 077/268] Add test for min contrast on white bg --- .../src/WebglRenderer.api.ts | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 25719faad..998d55364 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -631,6 +631,70 @@ describe('WebGL Renderer Integration Tests', function(): void { await pollFor(page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]); await pollFor(page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]); }); + + it.only('should adjust 0-15 colors on white background', async () => { + const theme: ITheme = { + background: '#ffffff', + black: '#2e3436', + red: '#cc0000', + green: '#4e9a06', + yellow: '#c4a000', + blue: '#3465a4', + magenta: '#75507b', + cyan: '#06989a', + white: '#d3d7cf', + brightBlack: '#555753', + brightRed: '#ef2929', + brightGreen: '#8ae234', + brightYellow: '#fce94f', + brightBlue: '#729fcf', + brightMagenta: '#ad7fa8', + brightCyan: '#34e2e2', + brightWhite: '#eeeeec' + }; + await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); + await writeSync( + `\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█\\r\\n` + + `\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█` + ); + // Validate before minimumContrastRatio is applied + await pollFor(page, () => getCellColor(1, 1), [0x2e, 0x34, 0x36, 255]); + await pollFor(page, () => getCellColor(2, 1), [0xcc, 0x00, 0x00, 255]); + await pollFor(page, () => getCellColor(3, 1), [0x4e, 0x9a, 0x06, 255]); + await pollFor(page, () => getCellColor(4, 1), [0xc4, 0xa0, 0x00, 255]); + await pollFor(page, () => getCellColor(5, 1), [0x34, 0x65, 0xa4, 255]); + await pollFor(page, () => getCellColor(6, 1), [0x75, 0x50, 0x7b, 255]); + await pollFor(page, () => getCellColor(7, 1), [0x06, 0x98, 0x9a, 255]); + await pollFor(page, () => getCellColor(8, 1), [0xd3, 0xd7, 0xcf, 255]); + await pollFor(page, () => getCellColor(1, 2), [0x55, 0x57, 0x53, 255]); + await pollFor(page, () => getCellColor(2, 2), [0xef, 0x29, 0x29, 255]); + await pollFor(page, () => getCellColor(3, 2), [0x8a, 0xe2, 0x34, 255]); + await pollFor(page, () => getCellColor(4, 2), [0xfc, 0xe9, 0x4f, 255]); + await pollFor(page, () => getCellColor(5, 2), [0x72, 0x9f, 0xcf, 255]); + await pollFor(page, () => getCellColor(6, 2), [0xad, 0x7f, 0xa8, 255]); + await pollFor(page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]); + await pollFor(page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]); + // Setting and check for minimum contrast values, note that these are note + // exact to the contrast ratio, if the increase luminance algorithm + // changes then these will probably fail + await page.evaluate(`window.term.setOption('minimumContrastRatio', 10);`); + await pollFor(page, () => getCellColor(1, 1), [46, 52, 54, 255]); + await pollFor(page, () => getCellColor(2, 1), [132, 0, 0, 255]); + await pollFor(page, () => getCellColor(3, 1), [78, 154, 6, 255]); + await pollFor(page, () => getCellColor(4, 1), [114, 93, 0, 255]); + await pollFor(page, () => getCellColor(5, 1), [19, 40, 68, 255]); + await pollFor(page, () => getCellColor(6, 1), [60, 40, 64, 255]); + await pollFor(page, () => getCellColor(7, 1), [0, 71, 72, 255]); + await pollFor(page, () => getCellColor(8, 1), [64, 64, 63, 255]); + await pollFor(page, () => getCellColor(1, 2), [61, 63, 59, 255]); + await pollFor(page, () => getCellColor(2, 2), [125, 19, 19, 255]); + await pollFor(page, () => getCellColor(3, 2), [89, 146, 32, 255]); + await pollFor(page, () => getCellColor(4, 2), [105, 98, 32, 255]); + await pollFor(page, () => getCellColor(5, 2), [36, 52, 70, 255]); + await pollFor(page, () => getCellColor(6, 2), [64, 45, 63, 255]); + await pollFor(page, () => getCellColor(7, 2), [13, 67, 67, 255]); + await pollFor(page, () => getCellColor(8, 2), [64, 64, 64, 255]); + }); }); }); From 2fc1791dde2bc26e23ada463c45319410016b831 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 11 Nov 2019 23:11:36 -0800 Subject: [PATCH 078/268] Fix floor/ceil in luminance methods --- .../src/atlas/WebglCharAtlas.ts | 27 ++++++++++--------- src/browser/Color.ts | 14 +++++----- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index f66910676..60068de8a 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -208,7 +208,7 @@ export class WebglCharAtlas implements IDisposable { } private _getForegroundCss(bgColorMode: number, bgColor: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): string { - const minimumContrastCss = this._getMinimumContrastCss(bgColorMode, bgColor, fgColorMode, fgColor, inverse); + const minimumContrastCss = this._getMinimumContrastCss(bgColorMode, bgColor, fgColorMode, fgColor, inverse, bold); if (minimumContrastCss) { return minimumContrastCss; } @@ -248,10 +248,13 @@ export class WebglCharAtlas implements IDisposable { } } - private _resolveForegroundRgba(bgColorMode: number, bgColor: number, fgColorMode: number, fgColor: number, inverse: boolean): number { + private _resolveForegroundRgba(bgColorMode: number, bgColor: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): number { switch (fgColorMode) { case Attributes.CM_P16: case Attributes.CM_P256: + if (this._config.drawBoldTextInBrightColors && bold && fgColor < 8) { + fgColor += 8; + } return this._getColorFromAnsiIndex(fgColor).rgba; case Attributes.CM_RGB: return fgColor << 8; @@ -264,15 +267,13 @@ export class WebglCharAtlas implements IDisposable { } } - private _getMinimumContrastCss(bgColorMode: number, bgColor: number, fgColorMode: number, fgColor: number, inverse: boolean): string | undefined { + private _getMinimumContrastCss(bgColorMode: number, bgColor: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): string | undefined { const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, fgColorMode, fgColor, inverse); - const fgRgba = this._resolveForegroundRgba(bgColorMode, bgColor, fgColorMode, fgColor, inverse); - console.log('ratio', this._config.minimumContrastRatio); + const fgRgba = this._resolveForegroundRgba(bgColorMode, bgColor, fgColorMode, fgColor, inverse, bold); if (this._config.minimumContrastRatio === 1) { return undefined; } const result = ensureContrastRatioRgba(bgRgba, fgRgba, this._config.minimumContrastRatio); - console.log('get min', result, bgRgba, fgRgba); if (!result) { return undefined; } @@ -618,10 +619,10 @@ export function reduceLuminance(bgRgba: number, fgRgba: number, ratio: number): let fgB = (fgRgba >> 8) & 0xFF; let cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); while (cr < ratio && (fgR > 0 || fgG > 0 || fgB > 0)) { - // Increase by 10% (ceil) until the ratio is hit - fgR -= Math.max(0, Math.ceil(fgR * 0.1)); - fgG -= Math.max(0, Math.ceil(fgG * 0.1)); - fgB -= Math.max(0, Math.ceil(fgB * 0.1)); + // Reduce by 10% until the ratio is hit + fgR -= Math.max(0, Math.floor(fgR * 0.1)); + fgG -= Math.max(0, Math.floor(fgG * 0.1)); + fgB -= Math.max(0, Math.floor(fgB * 0.1)); cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); } return fgR << 24 | fgG << 16 | fgB << 8 | 0xFF; @@ -639,9 +640,9 @@ export function increaseLuminance(bgRgba: number, fgRgba: number, ratio: number) let cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); while (cr < ratio && (fgR < 0xFF || fgG < 0xFF || fgB < 0xFF)) { // Increase by 10% until the ratio is hit - fgR = Math.min(0xFF, fgR + Math.floor((255 - fgR) * 0.1)); - fgG = Math.min(0xFF, fgG + Math.floor((255 - fgG) * 0.1)); - fgB = Math.min(0xFF, fgB + Math.floor((255 - fgB) * 0.1)); + fgR = Math.min(0xFF, fgR + Math.ceil((255 - fgR) * 0.1)); + fgG = Math.min(0xFF, fgG + Math.ceil((255 - fgG) * 0.1)); + fgB = Math.min(0xFF, fgB + Math.ceil((255 - fgB) * 0.1)); cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); } return fgR << 24 | fgG << 16 | fgB << 8 | 0xFF; diff --git a/src/browser/Color.ts b/src/browser/Color.ts index c63ccee33..258ea45a3 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -129,10 +129,10 @@ export function reduceLuminance(bgRgba: number, fgRgba: number, ratio: number): let fgB = (fgRgba >> 8) & 0xFF; let cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); while (cr < ratio && (fgR > 0 || fgG > 0 || fgB > 0)) { - // Increase by 10% (ceil) until the ratio is hit - fgR -= Math.max(0, Math.ceil(fgR * 0.1)); - fgG -= Math.max(0, Math.ceil(fgG * 0.1)); - fgB -= Math.max(0, Math.ceil(fgB * 0.1)); + // Reduce by 10% until the ratio is hit + fgR -= Math.max(0, Math.floor(fgR * 0.1)); + fgG -= Math.max(0, Math.floor(fgG * 0.1)); + fgB -= Math.max(0, Math.floor(fgB * 0.1)); cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); } return fgR << 24 | fgG << 16 | fgB << 8 | 0xFF; @@ -150,9 +150,9 @@ export function increaseLuminance(bgRgba: number, fgRgba: number, ratio: number) let cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); while (cr < ratio && (fgR < 0xFF || fgG < 0xFF || fgB < 0xFF)) { // Increase by 10% until the ratio is hit - fgR = Math.min(0xFF, fgR + Math.floor((255 - fgR) * 0.1)); - fgG = Math.min(0xFF, fgG + Math.floor((255 - fgG) * 0.1)); - fgB = Math.min(0xFF, fgB + Math.floor((255 - fgB) * 0.1)); + fgR = Math.min(0xFF, fgR + Math.ceil((255 - fgR) * 0.1)); + fgG = Math.min(0xFF, fgG + Math.ceil((255 - fgG) * 0.1)); + fgB = Math.min(0xFF, fgB + Math.ceil((255 - fgB) * 0.1)); cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); } return fgR << 24 | fgG << 16 | fgB << 8 | 0xFF; From 6626f9628d22ba9e41efc033ac34eff7e7c77dee Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 11 Nov 2019 23:13:21 -0800 Subject: [PATCH 079/268] Use ceil in both --- addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts | 6 +++--- src/browser/Color.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 60068de8a..1642ce088 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -620,9 +620,9 @@ export function reduceLuminance(bgRgba: number, fgRgba: number, ratio: number): let cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); while (cr < ratio && (fgR > 0 || fgG > 0 || fgB > 0)) { // Reduce by 10% until the ratio is hit - fgR -= Math.max(0, Math.floor(fgR * 0.1)); - fgG -= Math.max(0, Math.floor(fgG * 0.1)); - fgB -= Math.max(0, Math.floor(fgB * 0.1)); + fgR -= Math.max(0, Math.ceil(fgR * 0.1)); + fgG -= Math.max(0, Math.ceil(fgG * 0.1)); + fgB -= Math.max(0, Math.ceil(fgB * 0.1)); cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); } return fgR << 24 | fgG << 16 | fgB << 8 | 0xFF; diff --git a/src/browser/Color.ts b/src/browser/Color.ts index 258ea45a3..3a2b50324 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -130,9 +130,9 @@ export function reduceLuminance(bgRgba: number, fgRgba: number, ratio: number): let cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); while (cr < ratio && (fgR > 0 || fgG > 0 || fgB > 0)) { // Reduce by 10% until the ratio is hit - fgR -= Math.max(0, Math.floor(fgR * 0.1)); - fgG -= Math.max(0, Math.floor(fgG * 0.1)); - fgB -= Math.max(0, Math.floor(fgB * 0.1)); + fgR -= Math.max(0, Math.ceil(fgR * 0.1)); + fgG -= Math.max(0, Math.ceil(fgG * 0.1)); + fgB -= Math.max(0, Math.ceil(fgB * 0.1)); cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); } return fgR << 24 | fgG << 16 | fgB << 8 | 0xFF; From 5ee9bcf2b58e2d21291471c31c83cd76adfa9187 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 11 Nov 2019 23:23:33 -0800 Subject: [PATCH 080/268] Remove .only --- addons/xterm-addon-webgl/src/WebglRenderer.api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 998d55364..88aa3b455 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -632,7 +632,7 @@ describe('WebGL Renderer Integration Tests', function(): void { await pollFor(page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]); }); - it.only('should adjust 0-15 colors on white background', async () => { + it('should adjust 0-15 colors on white background', async () => { const theme: ITheme = { background: '#ffffff', black: '#2e3436', From ca5cee7e8625ec7ed0dfd0bdf642513d32372f6d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 12 Nov 2019 08:53:06 -0800 Subject: [PATCH 081/268] Fix contrast tests --- .../src/WebglRenderer.api.ts | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 88aa3b455..72f65c976 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -8,6 +8,7 @@ import { ITerminalOptions } from '../../../src/Types'; import { ITheme } from 'xterm'; import { assert } from 'chai'; import deepEqual = require('deep-equal'); +import { contrastRatio, reduceLuminance, toRgba } from 'atlas/WebglCharAtlas'; const APP = 'http://127.0.0.1:3000/test'; @@ -613,20 +614,20 @@ describe('WebGL Renderer Integration Tests', function(): void { // exact to the contrast ratio, if the increase luminance algorithm // changes then these will probably fail await page.evaluate(`window.term.setOption('minimumContrastRatio', 10);`); - await pollFor(page, () => getCellColor(1, 1), [179, 182, 182, 255]); - await pollFor(page, () => getCellColor(2, 1), [234, 163, 163, 255]); - await pollFor(page, () => getCellColor(3, 1), [196, 221, 174, 255]); - await pollFor(page, () => getCellColor(4, 1), [231, 219, 163, 255]); - await pollFor(page, () => getCellColor(5, 1), [133, 162, 200, 255]); - await pollFor(page, () => getCellColor(6, 1), [180, 159, 182, 255]); - await pollFor(page, () => getCellColor(7, 1), [106, 192, 194, 255]); + await pollFor(page, () => getCellColor(1, 1), [176, 180, 180, 255]); + await pollFor(page, () => getCellColor(2, 1), [238, 158, 158, 255]); + await pollFor(page, () => getCellColor(3, 1), [197, 223, 171, 255]); + await pollFor(page, () => getCellColor(4, 1), [235, 221, 158, 255]); + await pollFor(page, () => getCellColor(5, 1), [124, 156, 198, 255]); + await pollFor(page, () => getCellColor(6, 1), [183, 165, 187, 255]); + await pollFor(page, () => getCellColor(7, 1), [110, 197, 198, 255]); await pollFor(page, () => getCellColor(8, 1), [211, 215, 207, 255]); - await pollFor(page, () => getCellColor(1, 2), [180, 181, 179, 255]); - await pollFor(page, () => getCellColor(2, 2), [246, 160, 160, 255]); + await pollFor(page, () => getCellColor(1, 2), [183, 185, 183, 255]); + await pollFor(page, () => getCellColor(2, 2), [249, 156, 156, 255]); await pollFor(page, () => getCellColor(3, 2), [138, 226, 52, 255]); await pollFor(page, () => getCellColor(4, 2), [252, 233, 79, 255]); await pollFor(page, () => getCellColor(5, 2), [114, 159, 207, 255]); - await pollFor(page, () => getCellColor(6, 2), [188, 150, 183, 255]); + await pollFor(page, () => getCellColor(6, 2), [190, 152, 185, 255]); // Unchanged await pollFor(page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]); await pollFor(page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]); @@ -748,6 +749,7 @@ export async function pollFor(page: puppeteer.Page, evalOrFn: string | (() => await preFn(); } const result = typeof evalOrFn === 'string' ? await page.evaluate(evalOrFn) : await evalOrFn(); + console.log(result); if (!deepEqual(result, val)) { return new Promise(r => { setTimeout(() => r(pollFor(page, evalOrFn, val, preFn)), 1); From c2474626f0678402e9e572ada2a5921af19a06ce Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 12 Nov 2019 09:07:08 -0800 Subject: [PATCH 082/268] Clear setting as part of run --- addons/xterm-addon-webgl/src/WebglRenderer.api.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 72f65c976..26e966a01 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -8,7 +8,6 @@ import { ITerminalOptions } from '../../../src/Types'; import { ITheme } from 'xterm'; import { assert } from 'chai'; import deepEqual = require('deep-equal'); -import { contrastRatio, reduceLuminance, toRgba } from 'atlas/WebglCharAtlas'; const APP = 'http://127.0.0.1:3000/test'; @@ -588,7 +587,10 @@ describe('WebGL Renderer Integration Tests', function(): void { brightCyan: '#34e2e2', brightWhite: '#eeeeec' }; - await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); + await page.evaluate(` + window.term.setOption('theme', ${JSON.stringify(theme)}); + window.term.setOption('minimumContrastRatio', 1); + `); await writeSync( `\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█\\r\\n` + `\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█` @@ -653,7 +655,10 @@ describe('WebGL Renderer Integration Tests', function(): void { brightCyan: '#34e2e2', brightWhite: '#eeeeec' }; - await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); + await page.evaluate(` + window.term.setOption('theme', ${JSON.stringify(theme)}); + window.term.setOption('minimumContrastRatio', 1); + `); await writeSync( `\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█\\r\\n` + `\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█` From faaa07be68849e0a85dc17181900f904d15a1ba9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 12 Nov 2019 09:33:50 -0800 Subject: [PATCH 083/268] Add contrast caching for DOM renderer --- .../src/atlas/CharAtlasUtils.ts | 3 +- src/Terminal.ts | 1 + src/browser/ColorContrastCache.ts | 38 +++++++++++++++++++ src/browser/ColorManager.ts | 16 +++++++- src/browser/Types.d.ts | 10 +++++ .../renderer/dom/DomRendererRowFactory.ts | 14 +++++-- 6 files changed, 76 insertions(+), 6 deletions(-) create mode 100644 src/browser/ColorContrastCache.ts diff --git a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts index e8e190955..ad25bf87c 100644 --- a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts +++ b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts @@ -24,7 +24,8 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number selectionOpaque: NULL_COLOR, // For the static char atlas, we only use the first 16 colors, but we need all 256 for the // dynamic character atlas. - ansi: colors.ansi.slice() + ansi: colors.ansi.slice(), + contrastCache: {} as any }; return { devicePixelRatio: window.devicePixelRatio, diff --git a/src/Terminal.ts b/src/Terminal.ts index b08270fd9..927ff47b6 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -546,6 +546,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._theme = this.options.theme || this._theme; this.options.theme = undefined; this._colorManager = new ColorManager(document, this.options.allowTransparency); + this.optionsService.onOptionChange(e => this._colorManager.onOptionsChange(e)); this._colorManager.setTheme(this._theme); const renderer = this._createRenderer(); diff --git a/src/browser/ColorContrastCache.ts b/src/browser/ColorContrastCache.ts new file mode 100644 index 000000000..3198ad832 --- /dev/null +++ b/src/browser/ColorContrastCache.ts @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IColor, IColorContrastCache } from 'browser/Types'; + +export class ColorContrastCache implements IColorContrastCache { + private _color: { [bg: number]: { [fg: number]: IColor | null | undefined } | undefined } = {}; + private _rgba: { [bg: number]: { [fg: number]: number | null | undefined } | undefined } = {}; + + public clear(): void { + this._color = {}; + this._rgba = {}; + } + + public setRgba(bg: number, fg: number, value: number | null): void { + if (!this._rgba[bg]) { + this._rgba[bg] = {}; + } + this._rgba[bg]![fg] = value; + } + + public getRgba(bg: number, fg: number): number | null | undefined { + return this._rgba[bg] ? this._rgba[bg]![fg] : undefined; + } + + public setColor(bg: number, fg: number, value: IColor | null): void { + if (!this._color[bg]) { + this._color[bg] = {}; + } + this._color[bg]![fg] = value; + } + + public getColor(bg: number, fg: number): IColor | null | undefined { + return this._color[bg] ? this._color[bg]![fg] : undefined; + } +} diff --git a/src/browser/ColorManager.ts b/src/browser/ColorManager.ts index c6354f711..92994cfc4 100644 --- a/src/browser/ColorManager.ts +++ b/src/browser/ColorManager.ts @@ -3,9 +3,10 @@ * @license MIT */ -import { IColorManager, IColor, IColorSet } from 'browser/Types'; +import { IColorManager, IColor, IColorSet, IColorContrastCache } from 'browser/Types'; import { ITheme } from 'common/services/Services'; import { fromCss, toCss, blend, toRgba } from 'browser/Color'; +import { ColorContrastCache } from 'browser/ColorContrastCache'; const DEFAULT_FOREGROUND = fromCss('#ffffff'); const DEFAULT_BACKGROUND = fromCss('#000000'); @@ -72,6 +73,7 @@ export class ColorManager implements IColorManager { public colors: IColorSet; private _ctx: CanvasRenderingContext2D; private _litmusColor: CanvasGradient; + private _contrastCache: IColorContrastCache; constructor(document: Document, public allowTransparency: boolean) { const canvas = document.createElement('canvas'); @@ -84,6 +86,7 @@ export class ColorManager implements IColorManager { this._ctx = ctx; this._ctx.globalCompositeOperation = 'copy'; this._litmusColor = this._ctx.createLinearGradient(0, 0, 1, 1); + this._contrastCache = new ColorContrastCache(); this.colors = { foreground: DEFAULT_FOREGROUND, background: DEFAULT_BACKGROUND, @@ -91,10 +94,17 @@ export class ColorManager implements IColorManager { cursorAccent: DEFAULT_CURSOR_ACCENT, selection: DEFAULT_SELECTION, selectionOpaque: blend(DEFAULT_BACKGROUND, DEFAULT_SELECTION), - ansi: DEFAULT_ANSI_COLORS.slice() + ansi: DEFAULT_ANSI_COLORS.slice(), + contrastCache: this._contrastCache }; } + public onOptionsChange(key: string): void { + if (key === 'minimumContrastRatio') { + this._contrastCache.clear(); + } + } + /** * Sets the terminal's theme. * @param theme The theme to use. If a partial theme is provided then default @@ -123,6 +133,8 @@ export class ColorManager implements IColorManager { this.colors.ansi[13] = this._parseColor(theme.brightMagenta, DEFAULT_ANSI_COLORS[13]); this.colors.ansi[14] = this._parseColor(theme.brightCyan, DEFAULT_ANSI_COLORS[14]); this.colors.ansi[15] = this._parseColor(theme.brightWhite, DEFAULT_ANSI_COLORS[15]); + // Clear our the cache + this._contrastCache.clear(); } private _parseColor( diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index ca852b6aa..06c3fe387 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -8,6 +8,7 @@ import { IDisposable } from 'common/Types'; export interface IColorManager { colors: IColorSet; + onOptionsChange(key: string): void; } export interface IColor { @@ -24,6 +25,15 @@ export interface IColorSet { /** The selection blended on top of background. */ selectionOpaque: IColor; ansi: IColor[]; + contrastCache: IColorContrastCache +} + +export interface IColorContrastCache { + clear(): void; + setRgba(bg: number, fg: number, value: number | null): void; + getRgba(bg: number, fg: number): number | null | undefined; + setColor(bg: number, fg: number, value: IColor | null): void; + getColor(bg: number, fg: number): IColor | null | undefined; } export interface IPartialColorSet { diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index b5caccaf7..bb96fb723 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -167,9 +167,17 @@ export class DomRendererRowFactory { return false; } - const adustedColor = ensureContrastRatio(bg, fg, this._optionsService.options.minimumContrastRatio); - if (adustedColor) { - element.setAttribute('style', `${element.getAttribute('style') || ''}color:${adustedColor.css}`); + // Try get from cache first + let adjustedColor = this._colors.contrastCache.getColor(this._workCell.bg, this._workCell.fg); + + // Calculate and store in cache + if (adjustedColor === undefined) { + adjustedColor = ensureContrastRatio(bg, fg, this._optionsService.options.minimumContrastRatio); + this._colors.contrastCache.setColor(this._workCell.bg, this._workCell.fg, adjustedColor ?? null); + } + + if (adjustedColor) { + element.setAttribute('style', `${element.getAttribute('style') || ''}color:${adjustedColor.css}`); return true; } From 8a0122f482a2c9a63dcb535be8d9f29e596e1e5f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 12 Nov 2019 09:42:55 -0800 Subject: [PATCH 084/268] Add contrast caching to webgl --- .../src/atlas/CharAtlasUtils.ts | 2 +- .../src/atlas/WebglCharAtlas.ts | 27 ++++++++++++++----- src/browser/ColorContrastCache.ts | 6 ++--- src/browser/Types.d.ts | 4 +-- 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts index ad25bf87c..8e26b1d6a 100644 --- a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts +++ b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts @@ -25,7 +25,7 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number // For the static char atlas, we only use the first 16 colors, but we need all 256 for the // dynamic character atlas. ansi: colors.ansi.slice(), - contrastCache: {} as any + contrastCache: colors.contrastCache }; return { devicePixelRatio: window.devicePixelRatio, diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 1642ce088..b40c27cc9 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -207,8 +207,8 @@ export class WebglCharAtlas implements IDisposable { } } - private _getForegroundCss(bgColorMode: number, bgColor: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): string { - const minimumContrastCss = this._getMinimumContrastCss(bgColorMode, bgColor, fgColorMode, fgColor, inverse, bold); + private _getForegroundCss(bg: number, bgColorMode: number, bgColor: number, fg: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): string { + const minimumContrastCss = this._getMinimumContrastCss(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, bold); if (minimumContrastCss) { return minimumContrastCss; } @@ -267,21 +267,34 @@ export class WebglCharAtlas implements IDisposable { } } - private _getMinimumContrastCss(bgColorMode: number, bgColor: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): string | undefined { - const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, fgColorMode, fgColor, inverse); - const fgRgba = this._resolveForegroundRgba(bgColorMode, bgColor, fgColorMode, fgColor, inverse, bold); + private _getMinimumContrastCss(bg: number, bgColorMode: number, bgColor: number, fg: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): string | undefined { if (this._config.minimumContrastRatio === 1) { return undefined; } + + // Try get from cache first + const adjustedColor = this._config.colors.contrastCache.getCss(bg, fg); + if (adjustedColor !== undefined) { + return adjustedColor || undefined; + } + + const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, fgColorMode, fgColor, inverse); + const fgRgba = this._resolveForegroundRgba(bgColorMode, bgColor, fgColorMode, fgColor, inverse, bold); const result = ensureContrastRatioRgba(bgRgba, fgRgba, this._config.minimumContrastRatio); + if (!result) { + this._config.colors.contrastCache.setCss(bg, fg, null); return undefined; } - return toCss( + + const css = toCss( (result >> 24) & 0xFF, (result >> 16) & 0xFF, (result >> 8) & 0xFF ); + this._config.colors.contrastCache.setCss(bg, fg, css); + + return css; } private _drawToCache(code: number, bg: number, fg: number): IRasterizedGlyph; @@ -327,7 +340,7 @@ export class WebglCharAtlas implements IDisposable { `${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`; this._tmpCtx.textBaseline = 'top'; - this._tmpCtx.fillStyle = this._getForegroundCss(bgColorMode, bgColor, fgColorMode, fgColor, inverse, bold); + this._tmpCtx.fillStyle = this._getForegroundCss(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, bold); // Apply alpha to dim the character if (dim) { diff --git a/src/browser/ColorContrastCache.ts b/src/browser/ColorContrastCache.ts index 3198ad832..b96b66cc4 100644 --- a/src/browser/ColorContrastCache.ts +++ b/src/browser/ColorContrastCache.ts @@ -7,21 +7,21 @@ import { IColor, IColorContrastCache } from 'browser/Types'; export class ColorContrastCache implements IColorContrastCache { private _color: { [bg: number]: { [fg: number]: IColor | null | undefined } | undefined } = {}; - private _rgba: { [bg: number]: { [fg: number]: number | null | undefined } | undefined } = {}; + private _rgba: { [bg: number]: { [fg: number]: string | null | undefined } | undefined } = {}; public clear(): void { this._color = {}; this._rgba = {}; } - public setRgba(bg: number, fg: number, value: number | null): void { + public setCss(bg: number, fg: number, value: string | null): void { if (!this._rgba[bg]) { this._rgba[bg] = {}; } this._rgba[bg]![fg] = value; } - public getRgba(bg: number, fg: number): number | null | undefined { + public getCss(bg: number, fg: number): string | null | undefined { return this._rgba[bg] ? this._rgba[bg]![fg] : undefined; } diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index 06c3fe387..89c72fa3c 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -30,8 +30,8 @@ export interface IColorSet { export interface IColorContrastCache { clear(): void; - setRgba(bg: number, fg: number, value: number | null): void; - getRgba(bg: number, fg: number): number | null | undefined; + setCss(bg: number, fg: number, value: string | null): void; + getCss(bg: number, fg: number): string | null | undefined; setColor(bg: number, fg: number, value: IColor | null): void; getColor(bg: number, fg: number): IColor | null | undefined; } From 3b0042fd46df81b44f4fe16a8068034502b9e934 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 12 Nov 2019 09:46:07 -0800 Subject: [PATCH 085/268] Remove log --- addons/xterm-addon-webgl/src/WebglRenderer.api.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 26e966a01..e26e054a9 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -754,7 +754,6 @@ export async function pollFor(page: puppeteer.Page, evalOrFn: string | (() => await preFn(); } const result = typeof evalOrFn === 'string' ? await page.evaluate(evalOrFn) : await evalOrFn(); - console.log(result); if (!deepEqual(result, val)) { return new Promise(r => { setTimeout(() => r(pollFor(page, evalOrFn, val, preFn)), 1); From 647cff53dbd9c4f964b87b763b5ea97a4158751d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 12 Nov 2019 09:53:42 -0800 Subject: [PATCH 086/268] Lint --- src/browser/Types.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index 89c72fa3c..31d00af85 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -25,7 +25,7 @@ export interface IColorSet { /** The selection blended on top of background. */ selectionOpaque: IColor; ansi: IColor[]; - contrastCache: IColorContrastCache + contrastCache: IColorContrastCache; } export interface IColorContrastCache { From b3f439af092c1adaf4a1695be44f7a5b8e5753dd Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 12 Nov 2019 09:55:09 -0800 Subject: [PATCH 087/268] Remove unused imports --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 3a71dcd8e..46fd27301 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -11,10 +11,9 @@ import { acquireCharAtlas } from './atlas/CharAtlasCache'; import { WebglCharAtlas } from './atlas/WebglCharAtlas'; import { RectangleRenderer } from './RectangleRenderer'; import { IWebGL2RenderingContext } from './Types'; -import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { RenderModel, COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; import { Disposable } from 'common/Lifecycle'; -import { DEFAULT_COLOR, NULL_CELL_CODE, FgFlags } from 'common/buffer/Constants'; +import { NULL_CELL_CODE } from 'common/buffer/Constants'; import { Terminal, IEvent } from 'xterm'; import { IRenderLayer } from './renderLayer/Types'; import { IRenderDimensions, IRenderer, IRequestRefreshRowsEvent } from 'browser/renderer/Types'; From 032185f8b06a6289375e4e899a99638b29d97e9b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 12 Nov 2019 10:00:40 -0800 Subject: [PATCH 088/268] Link webgl directly to browser/Color --- .../src/atlas/WebglCharAtlas.ts | 118 +----------------- 1 file changed, 1 insertion(+), 117 deletions(-) diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index b40c27cc9..e1b0474f4 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -11,6 +11,7 @@ import { throwIfFalsy } from '../WebglUtils'; import { IColor } from 'browser/Types'; import { IDisposable } from 'xterm'; import { AttributeData } from 'common/buffer/AttributeData'; +import { toCss, ensureContrastRatioRgba } from 'browser/Color'; // In practice we're probably never going to exhaust a texture this large. For debugging purposes, // however, it can be useful to set this to a really tiny value, to verify that LRU eviction works. @@ -543,120 +544,3 @@ function getBgColor(bg: number): number { default: return -1; // CM_DEFAULT defaults to -1 } } - -export function toCss(r: number, g: number, b: number): string { - return `#${toPaddedHex(r)}${toPaddedHex(g)}${toPaddedHex(b)}`; -} - -export function toRgba(r: number, g: number, b: number, a: number = 0xFF): number { - // >>> 0 forces an unsigned int - return (r << 24 | g << 16 | b << 8 | a) >>> 0; -} - -/** - * Gets the relative luminance of an RGB color, this is useful in determining the contrast ratio - * between two colors. - * @param rgb The color to use. - * @see https://www.w3.org/TR/WCAG20/#relativeluminancedef - */ -export function rgbRelativeLuminance(rgb: number): number { - return rgbRelativeLuminance2( - (rgb >> 16) & 0xFF, - (rgb >> 8 ) & 0xFF, - (rgb ) & 0xFF); -} - -export function rgbRelativeLuminance2(r: number, g: number, b: number): number { - const rs = r / 255; - const gs = g / 255; - const bs = b / 255; - const rr = rs <= 0.03928 ? rs / 12.92 : Math.pow((rs + 0.055) / 1.055, 2.4); - const rg = gs <= 0.03928 ? gs / 12.92 : Math.pow((gs + 0.055) / 1.055, 2.4); - const rb = bs <= 0.03928 ? bs / 12.92 : Math.pow((bs + 0.055) / 1.055, 2.4); - return rr * 0.2126 + rg * 0.7152 + rb * 0.0722; -} - -/** - * Gets the contrast ratio between two relative luminance values. - * @param l1 The first relative luminance. - * @param l2 The first relative luminance. - * @see https://www.w3.org/TR/WCAG20/#contrast-ratiodef - */ -export function contrastRatio(l1: number, l2: number): number { - if (l1 < l2) { - return (l2 + 0.05) / (l1 + 0.05); - } - return (l1 + 0.05) / (l2 + 0.05); -} - -function rgbaToColor(r: number, g: number, b: number): IColor { - return { - css: toCss(r, g, b), - rgba: toRgba(r, g, b) - }; -} - -export function ensureContrastRatioRgba(bgRgba: number, fgRgba: number, ratio: number): number | undefined { - const bgL = rgbRelativeLuminance(bgRgba >> 8); - const fgL = rgbRelativeLuminance(fgRgba >> 8); - const cr = contrastRatio(bgL, fgL); - if (cr < ratio) { - if (fgL < bgL) { - return reduceLuminance(bgRgba, fgRgba, ratio); - } - return increaseLuminance(bgRgba, fgRgba, ratio); - } - return undefined; -} - -export function ensureContrastRatio(bg: IColor, fg: IColor, ratio: number): IColor | undefined { - const result = ensureContrastRatioRgba(bg.rgba, fg.rgba, ratio); - if (!result) { - return undefined; - } - return rgbaToColor( - (result >> 24 & 0xFF), - (result >> 16 & 0xFF), - (result >> 8 & 0xFF) - ); -} - -export function reduceLuminance(bgRgba: number, fgRgba: number, ratio: number): number { - // This is a naive but fast approach to reducing luminance as converting to - // HSL and back is expensive - const bgR = (bgRgba >> 24) & 0xFF; - const bgG = (bgRgba >> 16) & 0xFF; - const bgB = (bgRgba >> 8) & 0xFF; - let fgR = (fgRgba >> 24) & 0xFF; - let fgG = (fgRgba >> 16) & 0xFF; - let fgB = (fgRgba >> 8) & 0xFF; - let cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); - while (cr < ratio && (fgR > 0 || fgG > 0 || fgB > 0)) { - // Reduce by 10% until the ratio is hit - fgR -= Math.max(0, Math.ceil(fgR * 0.1)); - fgG -= Math.max(0, Math.ceil(fgG * 0.1)); - fgB -= Math.max(0, Math.ceil(fgB * 0.1)); - cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); - } - return fgR << 24 | fgG << 16 | fgB << 8 | 0xFF; -} - -export function increaseLuminance(bgRgba: number, fgRgba: number, ratio: number): number { - // This is a naive but fast approach to increasing luminance as converting to - // HSL and back is expensive - const bgR = (bgRgba >> 24) & 0xFF; - const bgG = (bgRgba >> 16) & 0xFF; - const bgB = (bgRgba >> 8) & 0xFF; - let fgR = (fgRgba >> 24) & 0xFF; - let fgG = (fgRgba >> 16) & 0xFF; - let fgB = (fgRgba >> 8) & 0xFF; - let cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); - while (cr < ratio && (fgR < 0xFF || fgG < 0xFF || fgB < 0xFF)) { - // Increase by 10% until the ratio is hit - fgR = Math.min(0xFF, fgR + Math.ceil((255 - fgR) * 0.1)); - fgG = Math.min(0xFF, fgG + Math.ceil((255 - fgG) * 0.1)); - fgB = Math.min(0xFF, fgB + Math.ceil((255 - fgB) * 0.1)); - cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); - } - return fgR << 24 | fgG << 16 | fgB << 8 | 0xFF; -} From b0c3606207f043f234de51860d5a94fbee5b9481 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 12 Nov 2019 10:03:38 -0800 Subject: [PATCH 089/268] Add example values to .d.ts --- typings/xterm.d.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index b49ed04bf..03f89e0cf 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -151,8 +151,12 @@ declare module 'xterm' { /** * The minimum contrast ratio for text in the terminal, setting this will * change the foreground color dynamically depending on whether the contrast - * ratio is met. This can be to set 0.1 increments between 1 (default, do - * nothing) and 21 (foreground will be black or white). + * ratio is met. Example values: + * + * - 1: The default, do nothing. + * - 4.5: Minimum for WCAG AA compliance. + * - 7: Minimum for WCAG AAA compliance. + * - 21: White on black or black on white. */ minimumContrastRatio?: number; From 56d12fe84651a9a59b28b444c0debfb23a7c3010 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 12 Nov 2019 10:09:32 -0800 Subject: [PATCH 090/268] Fix color manager unit test --- src/browser/ColorManager.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/ColorManager.test.ts b/src/browser/ColorManager.test.ts index a213c6166..39a8f8c2f 100644 --- a/src/browser/ColorManager.test.ts +++ b/src/browser/ColorManager.test.ts @@ -34,7 +34,7 @@ describe('ColorManager', () => { describe('constructor', () => { it('should fill all colors with values', () => { for (const key of Object.keys(cm.colors)) { - if (key !== 'ansi') { + if (key !== 'ansi' && key !== 'contrastCache') { // A #rrggbb or rgba(...) assert.ok((cm.colors)[key].css.length >= 7); } From d1f7d2ef6acf9577ca4e2ca9556eaba9a55a8829 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 12 Nov 2019 10:10:55 -0800 Subject: [PATCH 091/268] Tweak readme to call out min contrast ratio --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d2944003b..70ff7891d 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Xterm.js is a front-end component written in TypeScript that lets applications b - **Performant**: Xterm.js is *really* fast, it even includes a GPU-accelerated renderer. - **Rich unicode support**: Supports CJK, emojis and IMEs. - **Self-contained**: Requires zero dependencies to work. -- **Accessible**: Screen reader support can be turned on using the `screenReaderMode` option. +- **Accessible**: Screen reader and minimum contrast ratio support can be turned on - **And much more**: Links, theming, addons, well documented API, etc. ## What xterm.js is not From db090c70de99e8623e38d01e44f5132a67a58dd4 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 12 Nov 2019 10:21:23 -0800 Subject: [PATCH 092/268] Add unit tests for ColorContrastCache --- src/browser/Color.ts | 2 -- src/browser/ColorContrastCache.test.ts | 43 ++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 src/browser/ColorContrastCache.test.ts diff --git a/src/browser/Color.ts b/src/browser/Color.ts index 3a2b50324..09c6a4f1c 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -84,8 +84,6 @@ export function contrastRatio(l1: number, l2: number): number { return (l1 + 0.05) / (l2 + 0.05); } -// TODO: Cache [bg][fg]: result, should probably be owned by ColorManager? - function rgbaToColor(r: number, g: number, b: number): IColor { return { css: toCss(r, g, b), diff --git a/src/browser/ColorContrastCache.test.ts b/src/browser/ColorContrastCache.test.ts new file mode 100644 index 000000000..17e5df383 --- /dev/null +++ b/src/browser/ColorContrastCache.test.ts @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert } from 'chai'; +import { ColorContrastCache } from 'browser/ColorContrastCache'; + +describe('ColorContrastCache', () => { + let cache: ColorContrastCache; + + beforeEach(() => { + cache = new ColorContrastCache(); + }); + + it('should save and get color values', () => { + assert.strictEqual(cache.getColor(0x01, 0x00), undefined); + cache.setColor(0x01, 0x01, null); + assert.strictEqual(cache.getColor(0x01, 0x01), null); + cache.setColor(0x01, 0x02, { css: '#030303', rgba: 0x030303ff}); + assert.deepEqual(cache.getColor(0x01, 0x02), { css: '#030303', rgba: 0x030303ff}); + }); + + it('should save and get css values', () => { + assert.strictEqual(cache.getCss(0x01, 0x00), undefined); + cache.setCss(0x01, 0x01, null); + assert.strictEqual(cache.getCss(0x01, 0x01), null); + cache.setCss(0x01, 0x02, '#030303'); + assert.deepEqual(cache.getCss(0x01, 0x02), '#030303'); + }); + + it('should clear all values on clear', () => { + cache.setColor(0x01, 0x01, null); + cache.setColor(0x01, 0x02, { css: '#030303', rgba: 0x030303ff}); + cache.setCss(0x01, 0x01, null); + cache.setCss(0x01, 0x02, '#030303'); + cache.clear(); + assert.strictEqual(cache.getColor(0x01, 0x01), undefined); + assert.strictEqual(cache.getColor(0x01, 0x02), undefined); + assert.strictEqual(cache.getCss(0x01, 0x01), undefined); + assert.strictEqual(cache.getCss(0x01, 0x02), undefined); + }); +}); From 20b0a95bb0ef8decb76720f6187d1e8c8609a523 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 12 Nov 2019 10:40:35 -0800 Subject: [PATCH 093/268] Add tests for ensureContrastRatioRgba --- src/browser/Color.test.ts | 54 ++++++++++++++++++++++++++++++++++++++- src/browser/Color.ts | 4 +-- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/src/browser/Color.test.ts b/src/browser/Color.test.ts index 975315c6e..673fffc89 100644 --- a/src/browser/Color.test.ts +++ b/src/browser/Color.test.ts @@ -4,7 +4,7 @@ */ import { assert } from 'chai'; -import { blend, fromCss, toPaddedHex, toCss, toRgba, rgbRelativeLuminance, contrastRatio } from 'browser/Color'; +import { blend, fromCss, toPaddedHex, toCss, toRgba, rgbRelativeLuminance, contrastRatio, ensureContrastRatioRgba } from 'browser/Color'; describe('Color', () => { describe('blend', () => { @@ -167,4 +167,56 @@ describe('Color', () => { assert.equal(contrastRatio(1, 0), 21); }); }); + describe('ensureContrastRatioRgba', () => { + it('should return undefined if the color already meets the contrast ratio (black bg)', () => { + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 1), undefined); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 2), undefined); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 3), undefined); + }); + it('should return a color that meets the contrast ratio (black bg)', () => { + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 4), 0x707070ff); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 5), 0x7f7f7fff); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 6), 0x8c8c8cff); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 7), 0x989898ff); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 8), 0xa3a3a3ff); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 9), 0xadadadff); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 10), 0xb6b6b6ff); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 11), 0xbebebeff); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 12), 0xc5c5c5ff); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 13), 0xd1d1d1ff); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 14), 0xd6d6d6ff); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 15), 0xdbdbdbff); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 16), 0xe3e3e3ff); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 17), 0xe9e9e9ff); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 18), 0xeeeeeeff); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 19), 0xf4f4f4ff); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 20), 0xfafafaff); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 21), 0xffffffff); + }); + it('should return undefined if the color already meets the contrast ratio (white bg)', () => { + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 1), undefined); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 2), undefined); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 3), undefined); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 4), undefined); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 5), undefined); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 6), undefined); + }); + it('should return a color that meets the contrast ratio (white bg)', () => { + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 7), 0x565656ff); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 8), 0x4d4d4dff); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 9), 0x454545ff); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 10), 0x3e3e3eff); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 11), 0x373737ff); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 12), 0x313131ff); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 13), 0x313131ff); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 14), 0x272727ff); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 15), 0x232323ff); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 16), 0x1f1f1fff); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 17), 0x1b1b1bff); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 18), 0x151515ff); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 19), 0x101010ff); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 20), 0x080808ff); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 21), 0x000000ff); + }); + }); }); diff --git a/src/browser/Color.ts b/src/browser/Color.ts index 09c6a4f1c..aaf5ea2ee 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -133,7 +133,7 @@ export function reduceLuminance(bgRgba: number, fgRgba: number, ratio: number): fgB -= Math.max(0, Math.ceil(fgB * 0.1)); cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); } - return fgR << 24 | fgG << 16 | fgB << 8 | 0xFF; + return (fgR << 24 | fgG << 16 | fgB << 8 | 0xFF) >>> 0; } export function increaseLuminance(bgRgba: number, fgRgba: number, ratio: number): number { @@ -153,5 +153,5 @@ export function increaseLuminance(bgRgba: number, fgRgba: number, ratio: number) fgB = Math.min(0xFF, fgB + Math.ceil((255 - fgB) * 0.1)); cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); } - return fgR << 24 | fgG << 16 | fgB << 8 | 0xFF; + return (fgR << 24 | fgG << 16 | fgB << 8 | 0xFF) >>> 0; } From 2c959dfb2e3df7b6ecefa20d69af019702b774f2 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 13 Nov 2019 06:52:17 -0800 Subject: [PATCH 094/268] Keep a working AttributeData in WebglCharAtlas --- .../src/atlas/WebglCharAtlas.ts | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index e1b0474f4..9910b0097 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -68,6 +68,7 @@ export class WebglCharAtlas implements IDisposable { public hasCanvasChanged = false; private _workBoundingBox: IBoundingBox = { top: 0, left: 0, bottom: 0, right: 0 }; + private _workAttributeData: AttributeData = new AttributeData(); constructor( document: Document, @@ -305,17 +306,19 @@ export class WebglCharAtlas implements IDisposable { this.hasCanvasChanged = true; - const bold = !!(fg & FgFlags.BOLD); - const inverse = !!(fg & FgFlags.INVERSE); - const dim = !!(bg & BgFlags.DIM); - const italic = !!(bg & BgFlags.ITALIC); - this._tmpCtx.save(); - let fgColor = getFgColor(fg); - let fgColorMode = fg & Attributes.CM_MASK; - let bgColor = getBgColor(bg); - let bgColorMode = bg & Attributes.CM_MASK; + this._workAttributeData.fg = fg; + this._workAttributeData.bg = bg; + + const bold = !!this._workAttributeData.isBold(); + const inverse = !!this._workAttributeData.isInverse(); + const dim = !!this._workAttributeData.isDim(); + const italic = !!this._workAttributeData.isItalic(); + let fgColor = this._workAttributeData.getFgColor(); + let fgColorMode = this._workAttributeData.getFgColorMode(); + let bgColor = this._workAttributeData.getBgColor(); + let bgColorMode = this._workAttributeData.getBgColorMode(); if (inverse) { const temp = fgColor; fgColor = bgColor; From 579d74ee33b54c1db62d568ef71c7f0028f93ccd Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 13 Nov 2019 06:53:29 -0800 Subject: [PATCH 095/268] jsdoc rgbRelativeLuminance2 --- src/browser/Color.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/browser/Color.ts b/src/browser/Color.ts index aaf5ea2ee..d4cbfe0e1 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -61,6 +61,14 @@ export function rgbRelativeLuminance(rgb: number): number { (rgb ) & 0xFF); } +/** + * Gets the relative luminance of an RGB color, this is useful in determining the contrast ratio + * between two colors. + * @param r The red channel (0x00 to 0xFF). + * @param g The green channel (0x00 to 0xFF). + * @param b The blue channel (0x00 to 0xFF). + * @see https://www.w3.org/TR/WCAG20/#relativeluminancedef + */ export function rgbRelativeLuminance2(r: number, g: number, b: number): number { const rs = r / 255; const gs = g / 255; From 7652cd8af3a20d5466634fd417126db9f81e049c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 13 Nov 2019 06:59:46 -0800 Subject: [PATCH 096/268] Use hash notation over rgb() for dom colors --- .../renderer/dom/DomRendererRowFactory.test.ts | 4 ++-- src/browser/renderer/dom/DomRendererRowFactory.ts | 11 +++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/browser/renderer/dom/DomRendererRowFactory.test.ts b/src/browser/renderer/dom/DomRendererRowFactory.test.ts index 180db0fc4..c71945cfd 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.test.ts @@ -212,7 +212,7 @@ describe('DomRendererRowFactory', () => { lineData.setCell(0, cell); const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), - 'a' + 'a' ); }); @@ -223,7 +223,7 @@ describe('DomRendererRowFactory', () => { lineData.setCell(0, cell); const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), - 'a' + 'a' ); }); }); diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index bb96fb723..5e53cf604 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -130,7 +130,7 @@ export class DomRendererRowFactory { } break; case Attributes.CM_RGB: - charElement.setAttribute('style', `${charElement.getAttribute('style') || ''}color:rgb(${(AttributeData.toColorRGB(fg)).join(',')});`); + charElement.setAttribute('style', `${charElement.getAttribute('style') || ''}color:#${padStart(fg.toString(16), '0', 6)};`); break; case Attributes.CM_DEFAULT: default: @@ -148,7 +148,7 @@ export class DomRendererRowFactory { charElement.classList.add(`xterm-bg-${bg}`); break; case Attributes.CM_RGB: - charElement.setAttribute('style', `${charElement.getAttribute('style') || ''}background-color:rgb(${(AttributeData.toColorRGB(bg)).join(',')});`); + charElement.setAttribute('style', `${charElement.getAttribute('style') || ''}background-color:#${padStart(bg.toString(16), '0', 6)};`); break; case Attributes.CM_DEFAULT: default: @@ -184,3 +184,10 @@ export class DomRendererRowFactory { return false; } } + +function padStart(text: string, padChar: string, length: number): string { + while (text.length < length) { + text = padChar + text; + } + return text; +} From d09e2fbcec60e0c4fe0edfbed5b71163f57ee46e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 13 Nov 2019 23:09:52 +0100 Subject: [PATCH 097/268] stub for window options --- src/common/parser/WindowOptions.ts | 137 +++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 src/common/parser/WindowOptions.ts diff --git a/src/common/parser/WindowOptions.ts b/src/common/parser/WindowOptions.ts new file mode 100644 index 000000000..a75faa630 --- /dev/null +++ b/src/common/parser/WindowOptions.ts @@ -0,0 +1,137 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +interface IWindowOptions { + [key: string]: boolean | undefined; + restoreWin?: boolean; + minimizeWin?: boolean; + setWinPosition?: boolean; + setWinSizePixels?: boolean; + raiseWin?: boolean; + lowerWin?: boolean; + refreshWin?: boolean; + setWinSizeChars?: boolean; + maximizeWin?: boolean; + fullscreenWin?: boolean; + getWinState?: boolean; + getWinPosition?: boolean; + getWinSizePixels?: boolean; + getScreenSizePixels?: boolean; + getCellSizePixels?: boolean; + getWinSizeChars?: boolean; + getScreenSizeChars?: boolean; + getIconTitle?: boolean; + getWinTitle?: boolean; + pushTitle?: boolean; + popTitle?: boolean; + setWinLines?: boolean; +} + +const enum WindowOptions { + restoreWin = 1 << 1, + minimizeWin = 1 << 2, + setWinPosition = 1 << 3, + setWinSizePixels = 1 << 4, + raiseWin = 1 << 5, + lowerWin = 1 << 6, + refreshWin = 1 << 7, + setWinSizeChars = 1 << 8, + maximizeWin = 1 << 9, + fullscreenWin = 1 << 10, + getWinState = 1 << 11, + getWinPosition = 1 << 13, + getWinSizePixels = 1 << 14, + getScreenSizePixels = 1 << 15, // note: name not in xterm + getCellSizePixels = 1 << 16, // note: name not in xterm + getWinSizeChars = 1 << 18, + getScreenSizeChars = 1 << 19, + getIconTitle = 1 << 20, + getWinTitle = 1 << 21, + pushTitle = 1 << 22, + popTitle = 1 << 23, + setWinLines = 1 << 24 // any param >= 24, also handles DECCOLM +} + +let layer: {[key in keyof typeof WindowOptions]?: boolean} + +function hasOption(n: number, opts: WindowOptions): boolean { + return !!(opts & (1 << Math.max(n, 24))); +} + +function getOptions(opts: WindowOptions): IWindowOptions { + /* + const result: IWindowOptions = {}; + for (const key in WindowOptions) { + if (parseInt(key)) continue; + result[key] = !!(opts & WindowOptions[key as any] as unknown as number); + } + return result; + */ + return { + restoreWin: !!(opts & WindowOptions.restoreWin), + minimizeWin: !!(opts & WindowOptions.minimizeWin), + setWinPosition: !!(opts & WindowOptions.setWinPosition), + setWinSizePixels: !!(opts & WindowOptions.setWinSizePixels), + raiseWin: !!(opts & WindowOptions.raiseWin), + lowerWin: !!(opts & WindowOptions.lowerWin), + refreshWin: !!(opts & WindowOptions.refreshWin), + setWinSizeChars: !!(opts & WindowOptions.setWinSizeChars), + maximizeWin: !!(opts & WindowOptions.maximizeWin), + fullscreenWin: !!(opts & WindowOptions.fullscreenWin), + getWinState: !!(opts & WindowOptions.getWinState), + getWinPosition: !!(opts & WindowOptions.getWinPosition), + getWinSizePixels: !!(opts & WindowOptions.getWinSizePixels), + getScreenSizePixels: !!(opts & WindowOptions.getScreenSizePixels), + getCellSizePixels: !!(opts & WindowOptions.getCellSizePixels), + getWinSizeChars: !!(opts & WindowOptions.getWinSizeChars), + getScreenSizeChars: !!(opts & WindowOptions.getScreenSizeChars), + getIconTitle: !!(opts & WindowOptions.getIconTitle), + getWinTitle: !!(opts & WindowOptions.getWinTitle), + pushTitle: !!(opts & WindowOptions.pushTitle), + popTitle: !!(opts & WindowOptions.popTitle), + setWinLines: !!(opts & WindowOptions.setWinLines) + }; +} + +function setOptions(v: IWindowOptions & {[key: string]: boolean}, opts: WindowOptions): WindowOptions { + for (const optionName in v) { + const value = v[optionName]; + //const option = WindowOptions[optionName as any] as unknown as number; + //opts = value ? opts | option : opts & ~option; + switch (optionName) { + case 'restoreWin': opts = value ? opts | WindowOptions.restoreWin : opts & ~WindowOptions.restoreWin; break; + case 'minimizeWin': opts = value ? opts | WindowOptions.minimizeWin : opts & ~WindowOptions.minimizeWin; break; + case 'setWinPosition': opts = value ? opts | WindowOptions.setWinPosition : opts & ~WindowOptions.setWinPosition; break; + case 'setWinSizePixels': opts = value ? opts | WindowOptions.setWinSizePixels : opts & ~WindowOptions.setWinSizePixels; break; + case 'raiseWin': opts = value ? opts | WindowOptions.raiseWin : opts & ~WindowOptions.raiseWin; break; + case 'lowerWin': opts = value ? opts | WindowOptions.lowerWin : opts & ~WindowOptions.lowerWin; break; + case 'refreshWin': opts = value ? opts | WindowOptions.refreshWin : opts & ~WindowOptions.refreshWin; break; + case 'setWinSizeChars': opts = value ? opts | WindowOptions.setWinSizeChars : opts & ~WindowOptions.setWinSizeChars; break; + case 'maximizeWin': opts = value ? opts | WindowOptions.maximizeWin : opts & ~WindowOptions.maximizeWin; break; + case 'fullscreenWin': opts = value ? opts | WindowOptions.fullscreenWin : opts & ~WindowOptions.fullscreenWin; break; + case 'getWinState': opts = value ? opts | WindowOptions.getWinState : opts & ~WindowOptions.getWinState; break; + case 'getWinPosition': opts = value ? opts | WindowOptions.getWinPosition : opts & ~WindowOptions.getWinPosition; break; + case 'getWinSizePixels': opts = value ? opts | WindowOptions.getWinSizePixels : opts & ~WindowOptions.getWinSizePixels; break; + case 'getScreenSizePixels': opts = value ? opts | WindowOptions.getScreenSizePixels : opts & ~WindowOptions.getScreenSizePixels; break; + case 'getCellSizePixels': opts = value ? opts | WindowOptions.getCellSizePixels : opts & ~WindowOptions.getCellSizePixels; break; + case 'getWinSizeChars': opts = value ? opts | WindowOptions.getWinSizeChars : opts & ~WindowOptions.getWinSizeChars; break; + case 'getScreenSizeChars': opts = value ? opts | WindowOptions.getScreenSizeChars : opts & ~WindowOptions.getScreenSizeChars; break; + case 'getIconTitle': opts = value ? opts | WindowOptions.getIconTitle : opts & ~WindowOptions.getIconTitle; break; + case 'getWinTitle': opts = value ? opts | WindowOptions.getWinTitle : opts & ~WindowOptions.getWinTitle; break; + case 'pushTitle': opts = value ? opts | WindowOptions.pushTitle : opts & ~WindowOptions.pushTitle; break; + case 'popTitle': opts = value ? opts | WindowOptions.popTitle : opts & ~WindowOptions.popTitle; break; + case 'setWinLines': opts = value ? opts | WindowOptions.setWinLines : opts & ~WindowOptions.setWinLines; break; + default: throw new Error(`unknown WindowOption "${optionName}"`); + } + } + return opts; +} + +declare const console: any; +let opts: WindowOptions = 0; + +opts = setOptions({refreshWin: true, pushTitle: true, popTitle: true}, opts); +console.log(opts); +console.log(getOptions(opts)); From b780abaf3b934dcae1c3cadd5391538b0fdbb42a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 13 Nov 2019 23:46:01 +0100 Subject: [PATCH 098/268] add onBinary on CoreService, use for DEFAULT mouse reports --- src/common/TestUtils.test.ts | 2 ++ src/common/services/CoreMouseService.test.ts | 11 +++++----- src/common/services/CoreMouseService.ts | 21 ++++++++++++-------- src/common/services/CoreService.ts | 10 ++++++++++ src/common/services/Services.ts | 9 ++++++++- 5 files changed, 39 insertions(+), 14 deletions(-) diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 7d9c3aa60..7e47d4b0a 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -50,8 +50,10 @@ export class MockCoreService implements ICoreService { decPrivateModes: IDecPrivateModes = {} as any; onData: IEvent = new EventEmitter().event; onUserInput: IEvent = new EventEmitter().event; + onBinary: IEvent = new EventEmitter().event; reset(): void {} triggerDataEvent(data: string, wasUserInput?: boolean): void {} + triggerBinaryEvent(data: string): void {} } export class MockDirtyRowService implements IDirtyRowService { diff --git a/src/common/services/CoreMouseService.test.ts b/src/common/services/CoreMouseService.test.ts index f7d3bf838..d2a470e53 100644 --- a/src/common/services/CoreMouseService.test.ts +++ b/src/common/services/CoreMouseService.test.ts @@ -6,7 +6,7 @@ import { CoreMouseService } from 'common/services/CoreMouseService'; import { MockCoreService, MockBufferService } from 'common/TestUtils.test'; import { assert } from 'chai'; import { ICoreMouseEvent, CoreMouseEventType, CoreMouseButton, CoreMouseAction } from 'common/Types'; - +declare const console: any; // needed mock services const bufferService = new MockBufferService(300, 100); const coreService = new MockCoreService(); @@ -79,6 +79,7 @@ describe('CoreMouseService', () => { cms = new CoreMouseService(bufferService, coreService); reports = []; coreService.triggerDataEvent = (data: string, userInput?: boolean) => reports.push(data); + coreService.triggerBinaryEvent = (data: string) => reports.push(data); }); it('NONE', () => { assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), false); @@ -143,11 +144,11 @@ describe('CoreMouseService', () => { cms.activeProtocol = 'ANY'; for (let i = 0; i < bufferService.cols; ++i) { assert.equal(cms.triggerMouseEvent({ col: i, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true); - // capped at 95 - if (i < 95) { - assert.deepEqual(toBytes(reports.pop()), [0x1b, 0x5b, 0x4d, 0x20, i + 33, 0x21]); + if (i > 222) { + // supress mouse reports if we are out of addressible range (max. 222) + assert.deepEqual(toBytes(reports.pop()), []); } else { - assert.deepEqual(toBytes(reports.pop()), [0x1b, 0x5b, 0x4d, 0x20, 0x7f, 0x21]); + assert.deepEqual(toBytes(reports.pop()), [0x1b, 0x5b, 0x4d, 0x20, i + 33, 0x21]); } } }); diff --git a/src/common/services/CoreMouseService.ts b/src/common/services/CoreMouseService.ts index 500655b90..57aa2581f 100644 --- a/src/common/services/CoreMouseService.ts +++ b/src/common/services/CoreMouseService.ts @@ -121,15 +121,13 @@ const DEFAULT_ENCODINGS: {[key: string]: CoreMouseEncoding} = { /** * DEFAULT - CSI M Pb Px Py * Single byte encoding for coords and event code. - * Can encode values up to 223. The Encoding of higher - * values is not UTF-8 compatible (and currently limited - * to 95 in xterm.js). + * Can encode values up to 223 (1-based). */ DEFAULT: (e: ICoreMouseEvent) => { - let params = [eventCode(e, false) + 32, e.col + 32, e.row + 32]; - // FIXME: we are currently limited to ASCII range - params = params.map(v => (v > 127) ? 127 : v); - // FIXED: params = params.map(v => (v > 255) ? 0 : value); + const params = [eventCode(e, false) + 32, e.col + 32, e.row + 32]; + if (params[0] > 255 || params[1] > 255 || params[2] > 255) { + return ''; + } return `\x1b[M${S(params[0])}${S(params[1])}${S(params[2])}`; }, /** @@ -266,7 +264,14 @@ export class CoreMouseService implements ICoreMouseService { // encode report and send const report = this._encodings[this._activeEncoding](e); - this._coreService.triggerDataEvent(report, true); + if (this._activeProtocol === 'DEFAULT') { + // always send DEFAULT as binary data + if (report) { + this._coreService.triggerBinaryEvent(report); + } + } else { + this._coreService.triggerDataEvent(report, true); + } this._lastEvent = e; diff --git a/src/common/services/CoreService.ts b/src/common/services/CoreService.ts index 11c3f3053..35b61e847 100644 --- a/src/common/services/CoreService.ts +++ b/src/common/services/CoreService.ts @@ -23,6 +23,8 @@ export class CoreService implements ICoreService { public get onData(): IEvent { return this._onData.event; } private _onUserInput = new EventEmitter(); public get onUserInput(): IEvent { return this._onUserInput.event; } + private _onBinary = new EventEmitter(); + public get onBinary(): IEvent { return this._onBinary.event; } constructor( // TODO: Move this into a service @@ -59,4 +61,12 @@ export class CoreService implements ICoreService { this._logService.debug(`sending data "${data}"`, () => data.split('').map(e => e.charCodeAt(0))); this._onData.fire(data); } + + public triggerBinaryEvent(data: string): void { + if (this._optionsService.options.disableStdin) { + return; + } + this._logService.debug(`sending binary "${data}"`, () => data.split('').map(e => e.charCodeAt(0))); + this._onBinary.fire(data); + } } diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index e4ff90d0f..45d39a6af 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -68,6 +68,7 @@ export interface ICoreService { readonly onData: IEvent; readonly onUserInput: IEvent; + readonly onBinary: IEvent; reset(): void; @@ -78,8 +79,14 @@ export interface ICoreService { * resulting from parsing incoming data). When true this will also: * - Scroll to the bottom of the buffer.s * - Fire the `onUserInput` event (so selection can be cleared). - */ + */ triggerDataEvent(data: string, wasUserInput?: boolean): void; + + /** + * Triggers the onBinary event in the public API. + * @param data The data that is being emitted. + */ + triggerBinaryEvent(data: string): void; } export const IDirtyRowService = createDecorator('DirtyRowService'); From 00d626b6ccd449698de12a5132fd8c95058b7827 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 13 Nov 2019 23:57:51 +0100 Subject: [PATCH 099/268] add onBinary to interfaces --- src/Terminal.ts | 2 ++ src/TestUtils.test.ts | 1 + src/Types.d.ts | 1 + src/common/services/CoreMouseService.test.ts | 2 +- src/public/Terminal.ts | 1 + typings/xterm.d.ts | 11 +++++++++++ 6 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 3c589ffa9..d19f34e3c 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -169,6 +169,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp public get onCursorMove(): IEvent { return this._onCursorMove.event; } private _onData = new EventEmitter(); public get onData(): IEvent { return this._onData.event; } + private _onBinary = new EventEmitter(); + public get onBinary(): IEvent { return this._onBinary.event; } private _onKey = new EventEmitter<{ key: string, domEvent: KeyboardEvent }>(); public get onKey(): IEvent<{ key: string, domEvent: KeyboardEvent }> { return this._onKey.event; } private _onLineFeed = new EventEmitter(); diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index e7c483811..84c0d0b75 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -32,6 +32,7 @@ export class MockTerminal implements ITerminal { onLineFeed: IEvent; onSelectionChange: IEvent; onData: IEvent; + onBinary: IEvent; onTitleChange: IEvent; onScroll: IEvent; onKey: IEvent<{ key: string; domEvent: KeyboardEvent; }>; diff --git a/src/Types.d.ts b/src/Types.d.ts index 2a922ae1c..f4d3a556d 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -180,6 +180,7 @@ export interface IPublicTerminal extends IDisposable { markers: IMarker[]; onCursorMove: IEvent; onData: IEvent; + onBinary: IEvent; onKey: IEvent<{ key: string, domEvent: KeyboardEvent }>; onLineFeed: IEvent; onScroll: IEvent; diff --git a/src/common/services/CoreMouseService.test.ts b/src/common/services/CoreMouseService.test.ts index d2a470e53..2bf011b02 100644 --- a/src/common/services/CoreMouseService.test.ts +++ b/src/common/services/CoreMouseService.test.ts @@ -6,7 +6,7 @@ import { CoreMouseService } from 'common/services/CoreMouseService'; import { MockCoreService, MockBufferService } from 'common/TestUtils.test'; import { assert } from 'chai'; import { ICoreMouseEvent, CoreMouseEventType, CoreMouseButton, CoreMouseAction } from 'common/Types'; -declare const console: any; + // needed mock services const bufferService = new MockBufferService(300, 100); const coreService = new MockCoreService(); diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index c167bed89..397898c7e 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -27,6 +27,7 @@ export class Terminal implements ITerminalApi { public get onLineFeed(): IEvent { return this._core.onLineFeed; } public get onSelectionChange(): IEvent { return this._core.onSelectionChange; } public get onData(): IEvent { return this._core.onData; } + public get onBinary(): IEvent { return this._core.onBinary; } public get onTitleChange(): IEvent { return this._core.onTitleChange; } public get onScroll(): IEvent { return this._core.onScroll; } public get onKey(): IEvent<{ key: string, domEvent: KeyboardEvent }> { return this._core.onKey; } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 9728a72cb..a086d030b 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -421,6 +421,17 @@ declare module 'xterm' { */ constructor(options?: ITerminalOptions); + /** + * Adds an event listener for when a binary event fires. This is used to + * enable non UTF-8 conformant binary messages to be sent to the backend. + * 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'))`. + * @returns an `IDisposable` to stop listening. + */ + onBinary: IEvent; + /** * Adds an event listener for the cursor moves. * @returns an `IDisposable` to stop listening. From f1f103850ce2bcf43d43a269e48d74fa8980f542 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 14 Nov 2019 00:48:16 +0100 Subject: [PATCH 100/268] fix tests; fix attach addon --- addons/xterm-addon-attach/src/AttachAddon.ts | 12 ++++++ src/Terminal.ts | 1 + src/common/services/CoreMouseService.ts | 2 +- test/api/MouseTracking.api.ts | 43 +++++++++++--------- 4 files changed, 37 insertions(+), 21 deletions(-) diff --git a/addons/xterm-addon-attach/src/AttachAddon.ts b/addons/xterm-addon-attach/src/AttachAddon.ts index 117b2b584..279d1b2ee 100644 --- a/addons/xterm-addon-attach/src/AttachAddon.ts +++ b/addons/xterm-addon-attach/src/AttachAddon.ts @@ -33,6 +33,7 @@ export class AttachAddon implements ITerminalAddon { if (this._bidirectional) { this._disposables.push(terminal.onData(data => this._sendData(data))); + this._disposables.push(terminal.onBinary(data => this._sendBinary(data))); } this._disposables.push(addSocketListener(this._socket, 'close', () => this.dispose())); @@ -51,6 +52,17 @@ export class AttachAddon implements ITerminalAddon { } this._socket.send(data); } + + private _sendBinary(data: string): void { + if (this._socket.readyState !== 1) { + return; + } + const buffer = new Uint8Array(data.length); + for (let i = 0; i < data.length; ++i) { + buffer[i] = data.charCodeAt(i) & 255; + } + this._socket.send(buffer); + } } function addSocketListener(socket: WebSocket, type: K, handler: (this: WebSocket, ev: WebSocketEventMap[K]) => any): IDisposable { diff --git a/src/Terminal.ts b/src/Terminal.ts index d19f34e3c..6a039a0ed 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -223,6 +223,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._coreService = this._instantiationService.createInstance(CoreService, () => this.scrollToBottom()); this._instantiationService.setService(ICoreService, this._coreService); this._coreService.onData(e => this._onData.fire(e)); + this._coreService.onBinary(e => this._onBinary.fire(e)); this._coreMouseService = this._instantiationService.createInstance(CoreMouseService); this._instantiationService.setService(ICoreMouseService, this._coreMouseService); this._dirtyRowService = this._instantiationService.createInstance(DirtyRowService); diff --git a/src/common/services/CoreMouseService.ts b/src/common/services/CoreMouseService.ts index 57aa2581f..5c9d39442 100644 --- a/src/common/services/CoreMouseService.ts +++ b/src/common/services/CoreMouseService.ts @@ -264,7 +264,7 @@ export class CoreMouseService implements ICoreMouseService { // encode report and send const report = this._encodings[this._activeEncoding](e); - if (this._activeProtocol === 'DEFAULT') { + if (this._activeEncoding === 'DEFAULT') { // always send DEFAULT as binary data if (report) { this._coreService.triggerBinaryEvent(report); diff --git a/test/api/MouseTracking.api.ts b/test/api/MouseTracking.api.ts index f8124350c..7cae660f5 100644 --- a/test/api/MouseTracking.api.ts +++ b/test/api/MouseTracking.api.ts @@ -220,6 +220,7 @@ describe('Mouse Tracking Tests', () => { await page.evaluate(` window.calls = []; window.term.onData(e => calls.push( Array.from(e).map(el => el.charCodeAt(0)) )); + window.term.onBinary(e => calls.push( Array.from(e).map(el => el.charCodeAt(0)) )); window.term.setOption('fontSize', ${fontSize}); window.term.resize(${cols}, ${rows}); `); @@ -255,12 +256,17 @@ describe('Mouse Tracking Tests', () => { await pollFor(page, () => getReports(encoding), [{col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); // test at max rows/cols - // bug: we are capped at col 95 currently - // fix: allow values up to 223, any bigger should drop to 0 - await mouseMove(cols - 1, rows - 1); + // capped at 223 (1-based) + await mouseMove(223 - 1, rows - 1); await mouseDown('left'); await mouseUp('left'); - await pollFor(page, () => getReports(encoding), [{col: 95, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 223, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + + // higher than 223 should not report at all + await mouseMove(257, rows - 1); + await mouseDown('left'); + await mouseUp('left'); + await pollFor(page, () => getReports(encoding), []); // button press/move/release tests // left button @@ -511,14 +517,13 @@ describe('Mouse Tracking Tests', () => { ]); // test at max rows/cols - // bug: we are capped at col 95 currently - // fix: allow values up to 223, any bigger should drop to 0 - await mouseMove(cols - 1, rows - 1); + // capped at 223 (1-based) + await mouseMove(223 - 1, rows - 1); await mouseDown('left'); await mouseUp('left'); await pollFor(page, () => getReports(encoding), [ - {col: 95, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 95, row: rows, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + {col: 223, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 223, row: rows, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} ]); // button press/move/release tests @@ -821,14 +826,13 @@ describe('Mouse Tracking Tests', () => { ]); // test at max rows/cols - // bug: we are capped at col 95 currently - // fix: allow values up to 223, any bigger should drop to 0 - await mouseMove(cols - 1, rows - 1); + // capped at 223 (1-based) + await mouseMove(223 - 1, rows - 1); await mouseDown('left'); await mouseUp('left'); await pollFor(page, () => getReports(encoding), [ - {col: 95, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 95, row: rows, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + {col: 223, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 223, row: rows, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} ]); // button press/move/release tests @@ -1142,15 +1146,14 @@ describe('Mouse Tracking Tests', () => { ]); // test at max rows/cols - // bug: we are capped at col 95 currently - // fix: allow values up to 223, any bigger should drop to 0 - await mouseMove(cols - 1, rows - 1); + // capped at 223 (1-based) + await mouseMove(223 - 1, rows - 1); await mouseDown('left'); await mouseUp('left'); await pollFor(page, () => getReports(encoding), [ - {col: 95, row: rows, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: false}}}, - {col: 95, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 95, row: rows, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + {col: 223, row: rows, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: false}}}, + {col: 223, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 223, row: rows, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} ]); // button press/move/release tests From 8fc2d22b2870c33f899d1a73eea17c5a77027474 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 14 Nov 2019 10:20:51 +0100 Subject: [PATCH 101/268] add IWindowOptions to xterm.d.ts --- typings/xterm.d.ts | 127 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 9728a72cb..3433bd1e6 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -362,6 +362,133 @@ declare module 'xterm' { tooMuchOutput: string; } + /** + * Enable various window manipulation and report features (CSI Ps ; Ps ; Ps t). + * + * Note that most settings have no default implementation, as they heavily + * rely on the embedding environment. + * + * To implement a features, create a custom CSI hook like this: + * ```Typescript + * term.parser.addCsiHandler({final: 't'}, params => { + * const ps = params[0]; + * switch (ps) { + * case XY: + * ... // your implementation + * return true; // signal Ps=XY was handled + * } + * return false; // any Ps that was not handled + * }); + * ``` + * + * All features are disabled by default for security reasons. + * All custom implementations are guarded by the boolean values automatically. + */ + export interface IWindowOptions { + /** Ps=1 De-iconify window. */ + restoreWin?: boolean; + /** Ps=2 Iconify window. */ + minimizeWin?: boolean; + /** + * Ps=3 ; x ; y + * Move window to [x, y]. + */ + setWinPosition?: boolean; + /** + * Ps = 4 ; height ; width + * Resize the window to given `height` and `width` in pixels. + * Omitted parameters should reuse the current height or width. + * Zero parameters should use the display's height or width. + */ + setWinSizePixels?: boolean; + /** Ps=5 Raise the window to the front of the stacking order. */ + raiseWin?: boolean; + /** Ps=6 Lower the xterm window to the bottom of the stacking order. */ + lowerWin?: boolean; + /** Ps=7 Refresh the window. */ + refreshWin?: boolean; + /** + * Ps = 8 ; height ; width + * Resize the text area to given height and width in characters. + * Omitted parameters should reuse the current height or width. + * Zero parameters use the display's height or width. + */ + setWinSizeChars?: boolean; + /** + * Ps=9 ; 0 Restore maximized window. + * Ps=9 ; 1 Maximize window (i.e., resize to screen size). + * Ps=9 ; 2 Maximize window vertically. + * Ps=9 ; 3 Maximize window horizontally. + */ + maximizeWin?: boolean; + /** + * Ps=10 ; 0 Undo full-screen mode. + * Ps=10 ; 1 Change to full-screen. + * Ps=10 ; 2 Toggle full-screen. + */ + fullscreenWin?: boolean; + /** Ps=11 Report xterm window state. + * If the xterm window is non-iconified, it returns "CSI 1 t". + * If the xterm window is iconified, it returns "CSI 2 t". + */ + getWinState?: boolean; + /** + * Ps=13 Report xterm window position. Result is "CSI 3 ; x ; y t". + * Ps=13 ; 2 Report xterm text-area position. Result is "CSI 3 ; x ; y t". + */ + getWinPosition?: boolean; + /** + * Ps=14 Report xterm text area size in pixels. Result is "CSI 4 ; height ; width t". + * Ps=14 ; 2 Report xterm window size in pixels. Result is "CSI 4 ; height ; width t". + * Ps=14 has a default implementation. + */ + getWinSizePixels?: boolean; + /** Ps=15 Report size of the screen in pixels. Result is "CSI 5 ; height ; width t". */ + getScreenSizePixels?: boolean; + /** + * Ps=16 Report xterm character cell size in pixels. Result is "CSI 6 ; height ; width t". + * Implemented by default. + */ + getCellSizePixels?: boolean; + /** + * Ps=18 Report the size of the text area in characters. Result is "CSI 8 ; height ; width t". + * Implemented by default. + */ + getWinSizeChars?: boolean; + /** Ps=19 Report the size of the screen in characters. Result is "CSI 9 ; height ; width t". */ + getScreenSizeChars?: boolean; + /** + * Ps=20 Report xterm window's icon label. Result is "OSC L label ST". + * Implemented by default. + */ + getIconTitle?: boolean; + /** + * Ps=21 Report xterm window's title. Result is "OSC l label ST". + * Implemented by default. + */ + getWinTitle?: boolean; + /** + * Ps=22 ; 0 Save xterm icon and window title on stack. + * Ps=22 ; 1 Save xterm icon title on stack. + * Ps=22 ; 2 Save xterm window title on stack. + * All variants have a default implementation. + */ + pushTitle?: boolean; + /** + * Ps=23 ; 0 Restore xterm icon and window title from stack. + * Ps=23 ; 1 Restore xterm icon title from stack. + * Ps=23 ; 2 Restore xterm window title from stack. + * All variants have a default implementation. + */ + popTitle?: boolean; + /** + * Ps>=24 Resize to Ps lines (DECSLPP). + * DECSLPP is not implemented. This settings is also used to + * enable / disable DECCOLM (earlier variant of DECSLPP). + */ + setWinLines?: boolean; + } + /** * The class that represents an xterm.js terminal. */ From eeb9270882102ed46969db49ccc40b2116ba6aaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 14 Nov 2019 11:23:21 +0100 Subject: [PATCH 102/268] change WindowOptions type --- demo/client.ts | 6 +-- src/InputHandler.test.ts | 39 +++++++++-------- src/InputHandler.ts | 24 +++++----- src/common/Types.d.ts | 29 ++++++++++++ src/common/{parser => }/WindowOptions.ts | 56 +++--------------------- src/common/services/OptionsService.ts | 50 +++------------------ src/common/services/Services.ts | 6 ++- typings/xterm.d.ts | 2 +- 8 files changed, 82 insertions(+), 130 deletions(-) rename src/common/{parser => }/WindowOptions.ts (76%) diff --git a/demo/client.ts b/demo/client.ts index 4eb0c2acf..42e4fe7d1 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -244,7 +244,8 @@ function initOptions(term: TerminalType): void { 'termName', 'useFlowControl', // Complex option - 'theme' + 'theme', + 'windowOptions' ]; const stringOptions = { bellSound: null, @@ -256,8 +257,7 @@ function initOptions(term: TerminalType): void { fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], logLevel: ['debug', 'info', 'warn', 'error', 'off'], rendererType: ['dom', 'canvas'], - wordSeparator: null, - allowedWindowOps: '' + wordSeparator: null }; const options = Object.keys((term)._core.options); const booleanOptions = []; diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 21d6d7427..afbef1682 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -17,6 +17,7 @@ import { MockCoreService, MockBufferService, MockDirtyRowService, MockOptionsSer import { IBufferService } from 'common/services/Services'; import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; import { clone } from 'common/Clone'; +import { WindowOptions } from 'common/WindowOptions'; function getCursor(term: TestTerminal): number[] { return [ @@ -1266,10 +1267,10 @@ describe('InputHandler', () => { [131072, 131072], [131072, 131072], [131072, 300000 - 131072 - 131072] ]); }); - describe('windowOps', () => { + describe('windowOptions', () => { it('all should be disabled by default and not report', () => { const term = new TestTerminal({cols: 10, rows: 10}); - assert.deepEqual(term.options.allowedWindowOps, []); + assert.deepEqual(term.options.windowOptions, 0); const stack: string[] = []; term.onData(data => stack.push(data)); term.writeSync('\x1b[14t'); @@ -1280,8 +1281,8 @@ describe('InputHandler', () => { assert.deepEqual(stack, []); }); it('14 - GetWinSizePixels', () => { - const term = new TestTerminal({cols: 10, rows: 10, allowedWindowOps: [14]}); - assert.deepEqual(term.options.allowedWindowOps, [14]); + const term = new TestTerminal({cols: 10, rows: 10, windowOptions: WindowOptions.getWinSizePixels}); + assert.deepEqual(term.options.windowOptions, 1 << 14); const stack: string[] = []; term.onData(data => stack.push(data)); term.writeSync('\x1b[14t'); @@ -1289,8 +1290,8 @@ describe('InputHandler', () => { assert.deepEqual(stack, []); }); it('16 - GetCellSizePixels', () => { - const term = new TestTerminal({cols: 10, rows: 10, allowedWindowOps: [16]}); - assert.deepEqual(term.options.allowedWindowOps, [16]); + const term = new TestTerminal({cols: 10, rows: 10, windowOptions: WindowOptions.getCellSizePixels}); + assert.deepEqual(term.options.windowOptions, 1 << 16); const stack: string[] = []; term.onData(data => stack.push(data)); term.writeSync('\x1b[16t'); @@ -1298,8 +1299,8 @@ describe('InputHandler', () => { assert.deepEqual(stack, []); }); it('18 - GetWinSizeChars', () => { - const term = new TestTerminal({cols: 10, rows: 10, allowedWindowOps: [18]}); - assert.deepEqual(term.options.allowedWindowOps, [18]); + const term = new TestTerminal({cols: 10, rows: 10, windowOptions: WindowOptions.getWinSizeChars}); + assert.deepEqual(term.options.windowOptions, 1 << 18); const stack: string[] = []; term.onData(data => stack.push(data)); term.writeSync('\x1b[18t'); @@ -1309,8 +1310,8 @@ describe('InputHandler', () => { assert.deepEqual(stack, ['\x1b[8;10;10t', '\x1b[8;20;50t']); }); it('20 - GetIconTitle', () => { - const term = new TestTerminal({cols: 10, rows: 10, allowedWindowOps: [20]}); - assert.deepEqual(term.options.allowedWindowOps, [20]); + const term = new TestTerminal({cols: 10, rows: 10, windowOptions: WindowOptions.getIconTitle}); + assert.deepEqual(term.options.windowOptions, 1 << 20); const stack: string[] = []; term.onData(data => stack.push(data)); term.writeSync('\x1b]1;hello world!\x07'); @@ -1321,8 +1322,8 @@ describe('InputHandler', () => { assert.deepEqual(stack, ['\x1b]Lhello world!\x1b\\', '\x1b]Lsome other\x1b\\']); }); it('21 - GetWinTitle', () => { - const term = new TestTerminal({cols: 10, rows: 10, allowedWindowOps: [21]}); - assert.deepEqual(term.options.allowedWindowOps, [21]); + const term = new TestTerminal({cols: 10, rows: 10, windowOptions: WindowOptions.getWinTitle}); + assert.deepEqual(term.options.windowOptions, 1 << 21); const stack: string[] = []; term.onData(data => stack.push(data)); term.writeSync('\x1b]2;hello world!\x07'); @@ -1333,8 +1334,8 @@ describe('InputHandler', () => { assert.deepEqual(stack, ['\x1b]lhello world!\x1b\\', '\x1b]lsome other\x1b\\']); }); it('22/23 - PushTitle/PopTitle', () => { - const term = new TestTerminal({cols: 10, rows: 10, allowedWindowOps: [22, 23]}); - assert.deepEqual(term.options.allowedWindowOps, [22, 23]); + const term = new TestTerminal({cols: 10, rows: 10, windowOptions: WindowOptions.pushTitle | WindowOptions.popTitle}); + assert.deepEqual(term.options.windowOptions, (1 << 22) | (1 << 23)); const stack: string[] = []; term.onTitleChange(data => stack.push(data)); term.writeSync('\x1b]0;1\x07'); @@ -1355,8 +1356,8 @@ describe('InputHandler', () => { assert.deepEqual(stack, ['1', '2', '3', '3', '2', '1']); }); it('22/23 - PushTitle/PopTitle with ;1', () => { - const term = new TestTerminal({cols: 10, rows: 10, allowedWindowOps: [22, 23]}); - assert.deepEqual(term.options.allowedWindowOps, [22, 23]); + const term = new TestTerminal({cols: 10, rows: 10, windowOptions: WindowOptions.pushTitle | WindowOptions.popTitle}); + assert.deepEqual(term.options.windowOptions, (1 << 22) | (1 << 23)); const stack: string[] = []; term.onTitleChange(data => stack.push(data)); term.writeSync('\x1b]0;1\x07'); @@ -1377,8 +1378,8 @@ describe('InputHandler', () => { assert.deepEqual(stack, ['1', '2', '3']); }); it('22/23 - PushTitle/PopTitle with ;2', () => { - const term = new TestTerminal({cols: 10, rows: 10, allowedWindowOps: [22, 23]}); - assert.deepEqual(term.options.allowedWindowOps, [22, 23]); + const term = new TestTerminal({cols: 10, rows: 10, windowOptions: WindowOptions.pushTitle | WindowOptions.popTitle}); + assert.deepEqual(term.options.windowOptions, (1 << 22) | (1 << 23)); const stack: string[] = []; term.onTitleChange(data => stack.push(data)); term.writeSync('\x1b]0;1\x07'); @@ -1406,7 +1407,7 @@ describe('InputHandler', () => { term.writeSync('\x1b[?3h'); assert.equal((term as any)._bufferService.cols, 10); // enabled - const term2 = new TestTerminal({cols: 10, rows: 10, allowedWindowOps: [24]}); + const term2 = new TestTerminal({cols: 10, rows: 10, windowOptions: WindowOptions.setWinLines}); term2.writeSync('\x1b[?3l'); assert.equal((term2 as any)._bufferService.cols, 80); term2.writeSync('\x1b[?3h'); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 814138990..21bab83b2 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -22,6 +22,7 @@ import { IAttributeData, IDisposable } from 'common/Types'; import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService, ICoreMouseService } from 'common/services/Services'; import { OscHandler } from 'common/parser/OscParser'; import { DcsHandler } from 'common/parser/DcsParser'; +import { hasWindowOption, WindowOptions } from 'common/WindowOptions'; /** * Map collect to glevel. Used in `selectCharset`. @@ -520,7 +521,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (id.final === 't' && !id.prefix && !id.intermediates) { // security: always check whether window option is allowed return this._parser.addCsiHandler(id, params => { - if (!~this._optionsService.options.allowedWindowOps.indexOf(params.params[0])) { + if (!hasWindowOption(params.params[0], this._optionsService.options.windowOptions)) { return true; } return callback(params); @@ -1419,9 +1420,9 @@ export class InputHandler extends Disposable implements IInputHandler { /** * DECCOLM - 132 column mode. * This is only active if 'SetWinLines' (24) is listed - * in `options.allowedWindowOps`. + * in `options.windowsOptions`. */ - if (~this._optionsService.options.allowedWindowOps.indexOf(24)) { + if (this._optionsService.options.windowOptions & WindowOptions.setWinLines) { this._terminal.resize(132, this._bufferService.rows); this._terminal.reset(); } @@ -1606,9 +1607,9 @@ export class InputHandler extends Disposable implements IInputHandler { /** * DECCOLM - 80 column mode. * This is only active if 'SetWinLines' (24) is listed - * in `options.allowedWindowOps`. + * in `options.windowsOptions`. */ - if (~this._optionsService.options.allowedWindowOps.indexOf(24)) { + if (this._optionsService.options.windowOptions & WindowOptions.setWinLines) { this._terminal.resize(80, this._bufferService.rows); this._terminal.reset(); } @@ -2081,8 +2082,8 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 23 ; 2 -> Restore xterm window title from stack. supported * Ps >= 24 not implemented */ - public windowOptions(params?: IParams): void { - if (!~this._optionsService.options.allowedWindowOps.indexOf(params.params[0])) { + public windowOptions(params: IParams): void { + if (!hasWindowOption(params.params[0], this._optionsService.options.windowOptions)) { return; } const second = (params.length > 1) ? params.params[1] : 0; @@ -2090,15 +2091,16 @@ export class InputHandler extends Disposable implements IInputHandler { switch (params.params[0]) { case 14: // GetWinSizePixels, returns CSI 4 ; height ; width t if (rs && second !== 2) { - const w = rs.dimensions.canvasWidth.toFixed(0); - const h = rs.dimensions.canvasHeight.toFixed(0); + console.log(rs.dimensions); + const w = rs.dimensions.scaledCanvasWidth.toFixed(0); + const h = rs.dimensions.scaledCanvasHeight.toFixed(0); this._coreService.triggerDataEvent(`${C0.ESC}[4;${h};${w}t`); } break; case 16: // GetCellSizePixels, returns CSI 6 ; height ; width t if (rs) { - const w = rs.dimensions.actualCellWidth.toFixed(0); - const h = rs.dimensions.actualCellHeight.toFixed(0); + const w = rs.dimensions.scaledCellWidth.toFixed(0); + const h = rs.dimensions.scaledCellHeight.toFixed(0); this._coreService.triggerDataEvent(`${C0.ESC}[6;${h};${w}t`); } break; diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index 2f7420389..9b38aa3c9 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -251,3 +251,32 @@ export interface ICoreMouseProtocol { * with the active encoding and sent out. */ export type CoreMouseEncoding = (event: ICoreMouseEvent) => string; + +/** + * WindowOption settings. + */ +export interface IWindowOptions { + [key: string]: boolean | undefined; + restoreWin?: boolean; + minimizeWin?: boolean; + setWinPosition?: boolean; + setWinSizePixels?: boolean; + raiseWin?: boolean; + lowerWin?: boolean; + refreshWin?: boolean; + setWinSizeChars?: boolean; + maximizeWin?: boolean; + fullscreenWin?: boolean; + getWinState?: boolean; + getWinPosition?: boolean; + getWinSizePixels?: boolean; + getScreenSizePixels?: boolean; + getCellSizePixels?: boolean; + getWinSizeChars?: boolean; + getScreenSizeChars?: boolean; + getIconTitle?: boolean; + getWinTitle?: boolean; + pushTitle?: boolean; + popTitle?: boolean; + setWinLines?: boolean; +} diff --git a/src/common/parser/WindowOptions.ts b/src/common/WindowOptions.ts similarity index 76% rename from src/common/parser/WindowOptions.ts rename to src/common/WindowOptions.ts index a75faa630..582a52152 100644 --- a/src/common/parser/WindowOptions.ts +++ b/src/common/WindowOptions.ts @@ -2,34 +2,9 @@ * Copyright (c) 2019 The xterm.js authors. All rights reserved. * @license MIT */ +import { IWindowOptions } from 'common/Types'; -interface IWindowOptions { - [key: string]: boolean | undefined; - restoreWin?: boolean; - minimizeWin?: boolean; - setWinPosition?: boolean; - setWinSizePixels?: boolean; - raiseWin?: boolean; - lowerWin?: boolean; - refreshWin?: boolean; - setWinSizeChars?: boolean; - maximizeWin?: boolean; - fullscreenWin?: boolean; - getWinState?: boolean; - getWinPosition?: boolean; - getWinSizePixels?: boolean; - getScreenSizePixels?: boolean; - getCellSizePixels?: boolean; - getWinSizeChars?: boolean; - getScreenSizeChars?: boolean; - getIconTitle?: boolean; - getWinTitle?: boolean; - pushTitle?: boolean; - popTitle?: boolean; - setWinLines?: boolean; -} - -const enum WindowOptions { +export const enum WindowOptions { restoreWin = 1 << 1, minimizeWin = 1 << 2, setWinPosition = 1 << 3, @@ -54,21 +29,11 @@ const enum WindowOptions { setWinLines = 1 << 24 // any param >= 24, also handles DECCOLM } -let layer: {[key in keyof typeof WindowOptions]?: boolean} - -function hasOption(n: number, opts: WindowOptions): boolean { - return !!(opts & (1 << Math.max(n, 24))); +export function hasWindowOption(n: number, opts: WindowOptions): boolean { + return !!(opts & (1 << Math.min(n, 24))); } -function getOptions(opts: WindowOptions): IWindowOptions { - /* - const result: IWindowOptions = {}; - for (const key in WindowOptions) { - if (parseInt(key)) continue; - result[key] = !!(opts & WindowOptions[key as any] as unknown as number); - } - return result; - */ +export function getWindowOptions(opts: WindowOptions): IWindowOptions { return { restoreWin: !!(opts & WindowOptions.restoreWin), minimizeWin: !!(opts & WindowOptions.minimizeWin), @@ -95,11 +60,9 @@ function getOptions(opts: WindowOptions): IWindowOptions { }; } -function setOptions(v: IWindowOptions & {[key: string]: boolean}, opts: WindowOptions): WindowOptions { +export function setWindowOptions(v: IWindowOptions & {[key: string]: boolean}, opts: WindowOptions): WindowOptions { for (const optionName in v) { const value = v[optionName]; - //const option = WindowOptions[optionName as any] as unknown as number; - //opts = value ? opts | option : opts & ~option; switch (optionName) { case 'restoreWin': opts = value ? opts | WindowOptions.restoreWin : opts & ~WindowOptions.restoreWin; break; case 'minimizeWin': opts = value ? opts | WindowOptions.minimizeWin : opts & ~WindowOptions.minimizeWin; break; @@ -128,10 +91,3 @@ function setOptions(v: IWindowOptions & {[key: string]: boolean}, opts: WindowOp } return opts; } - -declare const console: any; -let opts: WindowOptions = 0; - -opts = setOptions({refreshWin: true, pushTitle: true, popTitle: true}, opts); -console.log(opts); -console.log(getOptions(opts)); diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 3c3d4bea5..099c4501e 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -7,6 +7,7 @@ import { IOptionsService, ITerminalOptions, IPartialTerminalOptions } from 'comm import { EventEmitter, IEvent } from 'common/EventEmitter'; import { isMac } from 'common/Platform'; import { clone } from 'common/Clone'; +import { setWindowOptions, getWindowOptions } from 'common/WindowOptions'; // Source: https://freesound.org/people/altemark/sounds/45759/ // This sound is released under the Creative Commons Attribution 3.0 Unported @@ -14,33 +15,6 @@ import { clone } from 'common/Clone'; // made, apart from the conversion to base64. export const DEFAULT_BELL_SOUND = 'data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4LjMyLjEwNAAAAAAAAAAAAAAA//tQxAADB8AhSmxhIIEVCSiJrDCQBTcu3UrAIwUdkRgQbFAZC1CQEwTJ9mjRvBA4UOLD8nKVOWfh+UlK3z/177OXrfOdKl7pyn3Xf//WreyTRUoAWgBgkOAGbZHBgG1OF6zM82DWbZaUmMBptgQhGjsyYqc9ae9XFz280948NMBWInljyzsNRFLPWdnZGWrddDsjK1unuSrVN9jJsK8KuQtQCtMBjCEtImISdNKJOopIpBFpNSMbIHCSRpRR5iakjTiyzLhchUUBwCgyKiweBv/7UsQbg8isVNoMPMjAAAA0gAAABEVFGmgqK////9bP/6XCykxBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq'; -// setting names controlled by allowedWindowOps -// not supported: GetChecksum, GetSelection, SetChecksum, SetSelection, SetXprop -export const WINDOW_OPTIONS: {[key: string]: number} = { - 'RestoreWin': 1, - 'MinimizeWin': 2, - 'SetWinPosition': 3, - 'SetWinSizePixels': 4, - 'RaiseWin': 5, - 'LowerWin': 6, - 'RefreshWin': 7, - 'SetWinSizeChars': 8, - 'MaximizeWin': 9, - 'FullscreenWin': 10, - 'GetWinState': 11, - 'GetWinPosition': 13, - 'GetWinSizePixels': 14, - 'GetScreenSizePixels': 15, // note: name not in xterm - 'GetCellSizePixels': 16, // note: name not in xterm - 'GetWinSizeChars': 18, - 'GetScreenSizeChars': 19, - 'GetIconTitle': 20, - 'GetWinTitle': 21, - 'PushTitle': 22, - 'PopTitle': 23, - 'SetWinLines': 24 // any param >= 24, also handles DECCOLM -}; - // TODO: Freeze? export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({ @@ -79,7 +53,7 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({ cancelEvents: false, useFlowControl: false, wordSeparator: ' ()[]{}\',:;"', - allowedWindowOps: [] + windowOptions: 0 }); /** @@ -157,20 +131,8 @@ export class OptionsService implements IOptionsService { throw new Error(`${key} cannot be less than or equal to 0, value: ${value}`); } break; - case 'allowedWindowOps': - const values = (value as string).split(','); - const cleaned: number[] = []; - for (let i = 0; i < values.length; ++i) { - const option = parseInt(values[i].trim()) || WINDOW_OPTIONS[values[i].trim()]; - if (!option || option < 0 || option > 24) { - throw new Error(`unknown window option "${values[i]}"`); - } - if (!~cleaned.indexOf(option)) { - cleaned.push(option); - } - } - value = cleaned; - break; + case 'windowOptions': + value = setWindowOptions(value, this.options.windowOptions); } return value; } @@ -179,8 +141,8 @@ export class OptionsService implements IOptionsService { if (!(key in DEFAULT_OPTIONS)) { throw new Error(`No option with key "${key}"`); } - if (key === 'allowedWindowOps') { - return this.options[key].join(); + if (key === 'windowOptions') { + return getWindowOptions(this.options.windowOptions); } return this.options[key]; } diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index f6444868a..30650957d 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -7,6 +7,7 @@ import { IEvent } from 'common/EventEmitter'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType } from 'common/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; +import { WindowOptions } from 'common/WindowOptions'; export const IBufferService = createDecorator('BufferService'); export interface IBufferService { @@ -207,7 +208,7 @@ export interface IPartialTerminalOptions { theme?: ITheme; windowsMode?: boolean; wordSeparator?: string; - allowedWindowOps?: number[]; + windowOptions?: WindowOptions; } export interface ITerminalOptions { @@ -247,7 +248,7 @@ export interface ITerminalOptions { screenKeys: boolean; termName: string; useFlowControl: boolean; - allowedWindowOps: number[]; + windowOptions: WindowOptions; } export interface ITheme { @@ -273,3 +274,4 @@ export interface ITheme { brightCyan?: string; brightWhite?: string; } + diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 3433bd1e6..0530bee71 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -440,7 +440,7 @@ declare module 'xterm' { /** * Ps=14 Report xterm text area size in pixels. Result is "CSI 4 ; height ; width t". * Ps=14 ; 2 Report xterm window size in pixels. Result is "CSI 4 ; height ; width t". - * Ps=14 has a default implementation. + * Ps=14 has a default implementation (also handles Ps=14 ; 2 if not overwritten by custom implementation). */ getWinSizePixels?: boolean; /** Ps=15 Report size of the screen in pixels. Result is "CSI 5 ; height ; width t". */ From 26052a29635302f6fbe31a913b5fff1184150568 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 14 Nov 2019 11:38:18 +0100 Subject: [PATCH 103/268] fix api test --- test/api/InputHandler.api.ts | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/test/api/InputHandler.api.ts b/test/api/InputHandler.api.ts index ef13c755c..eb928b67f 100644 --- a/test/api/InputHandler.api.ts +++ b/test/api/InputHandler.api.ts @@ -348,7 +348,6 @@ describe('InputHandler Integration Tests', function(): void { describe('Window Options - CSI Ps ; Ps ; Ps t', () => { it('should be disabled by default', async function() { - assert.equal(await page.evaluate(`(() => window.term.getOption('allowedWindowOps'))()`), ''); await page.evaluate(`(() => { window._stack = []; const _h = window.term.onData(data => window._stack.push(data)); @@ -359,13 +358,10 @@ describe('InputHandler Integration Tests', function(): void { window.term.write('\x1b[21t'); return new Promise((r) => window.term.write('', () => { _h.dispose(); r(); })); })()`); - assert.deepEqual(await page.evaluate(`(() => _stack)()`), []); + await pollFor(page, async () => await page.evaluate(`(() => _stack)()`), []); }); it('14 - GetWinSizePixels', async function() { - assert.equal(await page.evaluate(`(() => { - window.term.setOption('allowedWindowOps', 'GetWinSizePixels'); - return window.term.getOption('allowedWindowOps'); - })()`), '14'); + await page.evaluate(`window.term.setOption('windowOptions', {getWinSizePixels: true});`); await page.evaluate(`(() => { window._stack = []; const _h = window.term.onData(data => window._stack.push(data)); @@ -373,13 +369,10 @@ describe('InputHandler Integration Tests', function(): void { return new Promise((r) => window.term.write('', () => { _h.dispose(); r(); })); })()`); const d = await getDimensions(); - assert.deepEqual(await page.evaluate(`(() => _stack)()`), [`\x1b[4;${d.height};${d.width}t`]); + await pollFor(page, async () => await page.evaluate(`(() => _stack)()`), [`\x1b[4;${d.height};${d.width}t`]); }); it('16 - GetCellSizePixels', async function() { - assert.equal(await page.evaluate(`(() => { - window.term.setOption('allowedWindowOps', 'GetCellSizePixels'); - return window.term.getOption('allowedWindowOps'); - })()`), '16'); + await page.evaluate(`window.term.setOption('windowOptions', {getCellSizePixels: true});`); await page.evaluate(`(() => { window._stack = []; const _h = window.term.onData(data => window._stack.push(data)); @@ -387,7 +380,7 @@ describe('InputHandler Integration Tests', function(): void { return new Promise((r) => window.term.write('', () => { _h.dispose(); r(); })); })()`); const d = await getDimensions(); - assert.deepEqual(await page.evaluate(`(() => _stack)()`), [`\x1b[6;${d.cellHeight};${d.cellWidth}t`]); + await pollFor(page, async () => await page.evaluate(`(() => _stack)()`), [`\x1b[6;${d.cellHeight};${d.cellWidth}t`]); }); }); }); From 45d1139ab0d65da874d9d99b37026e859270a5ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 14 Nov 2019 12:20:59 +0100 Subject: [PATCH 104/268] windowOptions on ctor options --- src/InputHandler.test.ts | 28 +++++++++------------------ src/InputHandler.ts | 8 ++++---- src/common/TestUtils.test.ts | 1 + src/common/Types.d.ts | 1 - src/common/services/OptionsService.ts | 12 ++++++++---- src/common/services/Services.ts | 7 ++++--- typings/xterm.d.ts | 6 ++++++ 7 files changed, 32 insertions(+), 31 deletions(-) diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index afbef1682..873998a6a 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -17,7 +17,6 @@ import { MockCoreService, MockBufferService, MockDirtyRowService, MockOptionsSer import { IBufferService } from 'common/services/Services'; import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; import { clone } from 'common/Clone'; -import { WindowOptions } from 'common/WindowOptions'; function getCursor(term: TestTerminal): number[] { return [ @@ -1270,7 +1269,6 @@ describe('InputHandler', () => { describe('windowOptions', () => { it('all should be disabled by default and not report', () => { const term = new TestTerminal({cols: 10, rows: 10}); - assert.deepEqual(term.options.windowOptions, 0); const stack: string[] = []; term.onData(data => stack.push(data)); term.writeSync('\x1b[14t'); @@ -1281,8 +1279,7 @@ describe('InputHandler', () => { assert.deepEqual(stack, []); }); it('14 - GetWinSizePixels', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: WindowOptions.getWinSizePixels}); - assert.deepEqual(term.options.windowOptions, 1 << 14); + const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {getWinSizePixels: true}}); const stack: string[] = []; term.onData(data => stack.push(data)); term.writeSync('\x1b[14t'); @@ -1290,8 +1287,7 @@ describe('InputHandler', () => { assert.deepEqual(stack, []); }); it('16 - GetCellSizePixels', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: WindowOptions.getCellSizePixels}); - assert.deepEqual(term.options.windowOptions, 1 << 16); + const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {getCellSizePixels: true}}); const stack: string[] = []; term.onData(data => stack.push(data)); term.writeSync('\x1b[16t'); @@ -1299,8 +1295,7 @@ describe('InputHandler', () => { assert.deepEqual(stack, []); }); it('18 - GetWinSizeChars', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: WindowOptions.getWinSizeChars}); - assert.deepEqual(term.options.windowOptions, 1 << 18); + const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {getWinSizeChars: true}}); const stack: string[] = []; term.onData(data => stack.push(data)); term.writeSync('\x1b[18t'); @@ -1310,8 +1305,7 @@ describe('InputHandler', () => { assert.deepEqual(stack, ['\x1b[8;10;10t', '\x1b[8;20;50t']); }); it('20 - GetIconTitle', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: WindowOptions.getIconTitle}); - assert.deepEqual(term.options.windowOptions, 1 << 20); + const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {getIconTitle: true}}); const stack: string[] = []; term.onData(data => stack.push(data)); term.writeSync('\x1b]1;hello world!\x07'); @@ -1322,8 +1316,7 @@ describe('InputHandler', () => { assert.deepEqual(stack, ['\x1b]Lhello world!\x1b\\', '\x1b]Lsome other\x1b\\']); }); it('21 - GetWinTitle', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: WindowOptions.getWinTitle}); - assert.deepEqual(term.options.windowOptions, 1 << 21); + const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {getWinTitle: true}}); const stack: string[] = []; term.onData(data => stack.push(data)); term.writeSync('\x1b]2;hello world!\x07'); @@ -1334,8 +1327,7 @@ describe('InputHandler', () => { assert.deepEqual(stack, ['\x1b]lhello world!\x1b\\', '\x1b]lsome other\x1b\\']); }); it('22/23 - PushTitle/PopTitle', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: WindowOptions.pushTitle | WindowOptions.popTitle}); - assert.deepEqual(term.options.windowOptions, (1 << 22) | (1 << 23)); + const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {pushTitle: true, popTitle: true}}); const stack: string[] = []; term.onTitleChange(data => stack.push(data)); term.writeSync('\x1b]0;1\x07'); @@ -1356,8 +1348,7 @@ describe('InputHandler', () => { assert.deepEqual(stack, ['1', '2', '3', '3', '2', '1']); }); it('22/23 - PushTitle/PopTitle with ;1', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: WindowOptions.pushTitle | WindowOptions.popTitle}); - assert.deepEqual(term.options.windowOptions, (1 << 22) | (1 << 23)); + const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {pushTitle: true, popTitle: true}}); const stack: string[] = []; term.onTitleChange(data => stack.push(data)); term.writeSync('\x1b]0;1\x07'); @@ -1378,8 +1369,7 @@ describe('InputHandler', () => { assert.deepEqual(stack, ['1', '2', '3']); }); it('22/23 - PushTitle/PopTitle with ;2', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: WindowOptions.pushTitle | WindowOptions.popTitle}); - assert.deepEqual(term.options.windowOptions, (1 << 22) | (1 << 23)); + const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {pushTitle: true, popTitle: true}}); const stack: string[] = []; term.onTitleChange(data => stack.push(data)); term.writeSync('\x1b]0;1\x07'); @@ -1407,7 +1397,7 @@ describe('InputHandler', () => { term.writeSync('\x1b[?3h'); assert.equal((term as any)._bufferService.cols, 10); // enabled - const term2 = new TestTerminal({cols: 10, rows: 10, windowOptions: WindowOptions.setWinLines}); + const term2 = new TestTerminal({cols: 10, rows: 10, windowOptions: {setWinLines: true}}); term2.writeSync('\x1b[?3l'); assert.equal((term2 as any)._bufferService.cols, 80); term2.writeSync('\x1b[?3h'); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 21bab83b2..a2bb9be93 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -521,7 +521,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (id.final === 't' && !id.prefix && !id.intermediates) { // security: always check whether window option is allowed return this._parser.addCsiHandler(id, params => { - if (!hasWindowOption(params.params[0], this._optionsService.options.windowOptions)) { + if (!hasWindowOption(params.params[0], this._optionsService.windowOptions)) { return true; } return callback(params); @@ -1422,7 +1422,7 @@ export class InputHandler extends Disposable implements IInputHandler { * This is only active if 'SetWinLines' (24) is listed * in `options.windowsOptions`. */ - if (this._optionsService.options.windowOptions & WindowOptions.setWinLines) { + if (this._optionsService.windowOptions & WindowOptions.setWinLines) { this._terminal.resize(132, this._bufferService.rows); this._terminal.reset(); } @@ -1609,7 +1609,7 @@ export class InputHandler extends Disposable implements IInputHandler { * This is only active if 'SetWinLines' (24) is listed * in `options.windowsOptions`. */ - if (this._optionsService.options.windowOptions & WindowOptions.setWinLines) { + if (this._optionsService.windowOptions & WindowOptions.setWinLines) { this._terminal.resize(80, this._bufferService.rows); this._terminal.reset(); } @@ -2083,7 +2083,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps >= 24 not implemented */ public windowOptions(params: IParams): void { - if (!hasWindowOption(params.params[0], this._optionsService.options.windowOptions)) { + if (!hasWindowOption(params.params[0], this._optionsService.windowOptions)) { return; } const second = (params.length > 1) ? params.params[1] : 0; diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 7d9c3aa60..42152fac5 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -75,6 +75,7 @@ export class MockLogService implements ILogService { export class MockOptionsService implements IOptionsService { serviceBrand: any; options: ITerminalOptions = clone(DEFAULT_OPTIONS); + windowOptions = 0; onOptionChange: IEvent = new EventEmitter().event; constructor(testOptions?: IPartialTerminalOptions) { if (testOptions) { diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index 9b38aa3c9..2587820ae 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -256,7 +256,6 @@ export type CoreMouseEncoding = (event: ICoreMouseEvent) => string; * WindowOption settings. */ export interface IWindowOptions { - [key: string]: boolean | undefined; restoreWin?: boolean; minimizeWin?: boolean; setWinPosition?: boolean; diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 099c4501e..9ded45e99 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -7,7 +7,7 @@ import { IOptionsService, ITerminalOptions, IPartialTerminalOptions } from 'comm import { EventEmitter, IEvent } from 'common/EventEmitter'; import { isMac } from 'common/Platform'; import { clone } from 'common/Clone'; -import { setWindowOptions, getWindowOptions } from 'common/WindowOptions'; +import { setWindowOptions, getWindowOptions, WindowOptions } from 'common/WindowOptions'; // Source: https://freesound.org/people/altemark/sounds/45759/ // This sound is released under the Creative Commons Attribution 3.0 Unported @@ -53,7 +53,7 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({ cancelEvents: false, useFlowControl: false, wordSeparator: ' ()[]{}\',:;"', - windowOptions: 0 + windowOptions: {} }); /** @@ -65,6 +65,7 @@ export class OptionsService implements IOptionsService { serviceBrand: any; public options: ITerminalOptions; + public windowOptions: WindowOptions = 0; private _onOptionChange = new EventEmitter(); public get onOptionChange(): IEvent { return this._onOptionChange.event; } @@ -77,6 +78,9 @@ export class OptionsService implements IOptionsService { this.options[k] = newValue; } }); + if (options.windowOptions) { + this.windowOptions = setWindowOptions(options.windowOptions as any, 0); + } } public setOption(key: string, value: any): void { @@ -132,7 +136,7 @@ export class OptionsService implements IOptionsService { } break; case 'windowOptions': - value = setWindowOptions(value, this.options.windowOptions); + this.windowOptions = setWindowOptions(value, this.windowOptions); } return value; } @@ -142,7 +146,7 @@ export class OptionsService implements IOptionsService { throw new Error(`No option with key "${key}"`); } if (key === 'windowOptions') { - return getWindowOptions(this.options.windowOptions); + return getWindowOptions(this.windowOptions); } return this.options[key]; } diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 30650957d..7a045fc7e 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -5,7 +5,7 @@ import { IEvent } from 'common/EventEmitter'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; -import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType } from 'common/Types'; +import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, IWindowOptions } from 'common/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; import { WindowOptions } from 'common/WindowOptions'; @@ -167,6 +167,7 @@ export interface IOptionsService { serviceBrand: any; readonly options: ITerminalOptions; + readonly windowOptions: WindowOptions; readonly onOptionChange: IEvent; @@ -208,7 +209,7 @@ export interface IPartialTerminalOptions { theme?: ITheme; windowsMode?: boolean; wordSeparator?: string; - windowOptions?: WindowOptions; + windowOptions?: IWindowOptions; } export interface ITerminalOptions { @@ -248,7 +249,7 @@ export interface ITerminalOptions { screenKeys: boolean; termName: string; useFlowControl: boolean; - windowOptions: WindowOptions; + windowOptions: IWindowOptions; } export interface ITheme { diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 0530bee71..e984bd44b 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -215,6 +215,12 @@ declare module 'xterm' { * double click to select work logic. */ wordSeparator?: string; + + /** + * Enable various window manipulation and report features. + * All features are disabled by default for security reasons. + */ + windowOptions?: IWindowOptions; } /** From 8991ed82709acd4c19e762d14b49f08a83ddfe06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 14 Nov 2019 12:59:48 +0100 Subject: [PATCH 105/268] simplify options --- src/InputHandler.ts | 44 +++++++++++-- src/common/WindowOptions.ts | 93 --------------------------- src/common/services/OptionsService.ts | 10 --- src/common/services/Services.ts | 2 - 4 files changed, 38 insertions(+), 111 deletions(-) delete mode 100644 src/common/WindowOptions.ts diff --git a/src/InputHandler.ts b/src/InputHandler.ts index a2bb9be93..1cfa95906 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -18,11 +18,10 @@ import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams, IFunctionId import { NULL_CELL_CODE, NULL_CELL_WIDTH, Attributes, FgFlags, BgFlags, Content } from 'common/buffer/Constants'; import { CellData } from 'common/buffer/CellData'; import { AttributeData } from 'common/buffer/AttributeData'; -import { IAttributeData, IDisposable } from 'common/Types'; +import { IAttributeData, IDisposable, IWindowOptions } from 'common/Types'; import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService, ICoreMouseService } from 'common/services/Services'; import { OscHandler } from 'common/parser/OscParser'; import { DcsHandler } from 'common/parser/DcsParser'; -import { hasWindowOption, WindowOptions } from 'common/WindowOptions'; /** * Map collect to glevel. Used in `selectCharset`. @@ -39,6 +38,39 @@ const MAX_PARSEBUFFER_LENGTH = 131072; */ const STACK_LIMIT = 10; +// map params to window option +function paramToWindowOption(n: number, opts: IWindowOptions): boolean { + if (n > 24) { + return opts.setWinLines || false; + } + switch (n) { + case 1: return opts.restoreWin || false; + case 2: return opts.minimizeWin || false; + case 3: return opts.setWinPosition || false; + case 4: return opts.setWinSizePixels || false; + case 5: return opts.raiseWin || false; + case 6: return opts.lowerWin || false; + case 7: return opts.refreshWin || false; + case 8: return opts.setWinSizeChars || false; + case 9: return opts.maximizeWin || false; + case 10: return opts.fullscreenWin || false; + case 11: return opts.getWinState || false; + case 13: return opts.getWinPosition || false; + case 14: return opts.getWinSizePixels || false; + case 15: return opts.getScreenSizePixels || false; + case 16: return opts.getCellSizePixels || false; + case 18: return opts.getWinSizeChars || false; + case 19: return opts.getScreenSizeChars || false; + case 20: return opts.getIconTitle || false; + case 21: return opts.getWinTitle || false; + case 22: return opts.pushTitle || false; + case 23: return opts.popTitle || false; + case 24: return opts.setWinLines || false; + } + return false; +} + + /** * DCS subparser implementations @@ -521,7 +553,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (id.final === 't' && !id.prefix && !id.intermediates) { // security: always check whether window option is allowed return this._parser.addCsiHandler(id, params => { - if (!hasWindowOption(params.params[0], this._optionsService.windowOptions)) { + if (!paramToWindowOption(params.params[0], this._optionsService.options.windowOptions)) { return true; } return callback(params); @@ -1422,7 +1454,7 @@ export class InputHandler extends Disposable implements IInputHandler { * This is only active if 'SetWinLines' (24) is listed * in `options.windowsOptions`. */ - if (this._optionsService.windowOptions & WindowOptions.setWinLines) { + if (this._optionsService.options.windowOptions.setWinLines) { this._terminal.resize(132, this._bufferService.rows); this._terminal.reset(); } @@ -1609,7 +1641,7 @@ export class InputHandler extends Disposable implements IInputHandler { * This is only active if 'SetWinLines' (24) is listed * in `options.windowsOptions`. */ - if (this._optionsService.windowOptions & WindowOptions.setWinLines) { + if (this._optionsService.options.windowOptions.setWinLines) { this._terminal.resize(80, this._bufferService.rows); this._terminal.reset(); } @@ -2083,7 +2115,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps >= 24 not implemented */ public windowOptions(params: IParams): void { - if (!hasWindowOption(params.params[0], this._optionsService.windowOptions)) { + if (!paramToWindowOption(params.params[0], this._optionsService.options.windowOptions)) { return; } const second = (params.length > 1) ? params.params[1] : 0; diff --git a/src/common/WindowOptions.ts b/src/common/WindowOptions.ts deleted file mode 100644 index 582a52152..000000000 --- a/src/common/WindowOptions.ts +++ /dev/null @@ -1,93 +0,0 @@ -/** - * Copyright (c) 2019 The xterm.js authors. All rights reserved. - * @license MIT - */ -import { IWindowOptions } from 'common/Types'; - -export const enum WindowOptions { - restoreWin = 1 << 1, - minimizeWin = 1 << 2, - setWinPosition = 1 << 3, - setWinSizePixels = 1 << 4, - raiseWin = 1 << 5, - lowerWin = 1 << 6, - refreshWin = 1 << 7, - setWinSizeChars = 1 << 8, - maximizeWin = 1 << 9, - fullscreenWin = 1 << 10, - getWinState = 1 << 11, - getWinPosition = 1 << 13, - getWinSizePixels = 1 << 14, - getScreenSizePixels = 1 << 15, // note: name not in xterm - getCellSizePixels = 1 << 16, // note: name not in xterm - getWinSizeChars = 1 << 18, - getScreenSizeChars = 1 << 19, - getIconTitle = 1 << 20, - getWinTitle = 1 << 21, - pushTitle = 1 << 22, - popTitle = 1 << 23, - setWinLines = 1 << 24 // any param >= 24, also handles DECCOLM -} - -export function hasWindowOption(n: number, opts: WindowOptions): boolean { - return !!(opts & (1 << Math.min(n, 24))); -} - -export function getWindowOptions(opts: WindowOptions): IWindowOptions { - return { - restoreWin: !!(opts & WindowOptions.restoreWin), - minimizeWin: !!(opts & WindowOptions.minimizeWin), - setWinPosition: !!(opts & WindowOptions.setWinPosition), - setWinSizePixels: !!(opts & WindowOptions.setWinSizePixels), - raiseWin: !!(opts & WindowOptions.raiseWin), - lowerWin: !!(opts & WindowOptions.lowerWin), - refreshWin: !!(opts & WindowOptions.refreshWin), - setWinSizeChars: !!(opts & WindowOptions.setWinSizeChars), - maximizeWin: !!(opts & WindowOptions.maximizeWin), - fullscreenWin: !!(opts & WindowOptions.fullscreenWin), - getWinState: !!(opts & WindowOptions.getWinState), - getWinPosition: !!(opts & WindowOptions.getWinPosition), - getWinSizePixels: !!(opts & WindowOptions.getWinSizePixels), - getScreenSizePixels: !!(opts & WindowOptions.getScreenSizePixels), - getCellSizePixels: !!(opts & WindowOptions.getCellSizePixels), - getWinSizeChars: !!(opts & WindowOptions.getWinSizeChars), - getScreenSizeChars: !!(opts & WindowOptions.getScreenSizeChars), - getIconTitle: !!(opts & WindowOptions.getIconTitle), - getWinTitle: !!(opts & WindowOptions.getWinTitle), - pushTitle: !!(opts & WindowOptions.pushTitle), - popTitle: !!(opts & WindowOptions.popTitle), - setWinLines: !!(opts & WindowOptions.setWinLines) - }; -} - -export function setWindowOptions(v: IWindowOptions & {[key: string]: boolean}, opts: WindowOptions): WindowOptions { - for (const optionName in v) { - const value = v[optionName]; - switch (optionName) { - case 'restoreWin': opts = value ? opts | WindowOptions.restoreWin : opts & ~WindowOptions.restoreWin; break; - case 'minimizeWin': opts = value ? opts | WindowOptions.minimizeWin : opts & ~WindowOptions.minimizeWin; break; - case 'setWinPosition': opts = value ? opts | WindowOptions.setWinPosition : opts & ~WindowOptions.setWinPosition; break; - case 'setWinSizePixels': opts = value ? opts | WindowOptions.setWinSizePixels : opts & ~WindowOptions.setWinSizePixels; break; - case 'raiseWin': opts = value ? opts | WindowOptions.raiseWin : opts & ~WindowOptions.raiseWin; break; - case 'lowerWin': opts = value ? opts | WindowOptions.lowerWin : opts & ~WindowOptions.lowerWin; break; - case 'refreshWin': opts = value ? opts | WindowOptions.refreshWin : opts & ~WindowOptions.refreshWin; break; - case 'setWinSizeChars': opts = value ? opts | WindowOptions.setWinSizeChars : opts & ~WindowOptions.setWinSizeChars; break; - case 'maximizeWin': opts = value ? opts | WindowOptions.maximizeWin : opts & ~WindowOptions.maximizeWin; break; - case 'fullscreenWin': opts = value ? opts | WindowOptions.fullscreenWin : opts & ~WindowOptions.fullscreenWin; break; - case 'getWinState': opts = value ? opts | WindowOptions.getWinState : opts & ~WindowOptions.getWinState; break; - case 'getWinPosition': opts = value ? opts | WindowOptions.getWinPosition : opts & ~WindowOptions.getWinPosition; break; - case 'getWinSizePixels': opts = value ? opts | WindowOptions.getWinSizePixels : opts & ~WindowOptions.getWinSizePixels; break; - case 'getScreenSizePixels': opts = value ? opts | WindowOptions.getScreenSizePixels : opts & ~WindowOptions.getScreenSizePixels; break; - case 'getCellSizePixels': opts = value ? opts | WindowOptions.getCellSizePixels : opts & ~WindowOptions.getCellSizePixels; break; - case 'getWinSizeChars': opts = value ? opts | WindowOptions.getWinSizeChars : opts & ~WindowOptions.getWinSizeChars; break; - case 'getScreenSizeChars': opts = value ? opts | WindowOptions.getScreenSizeChars : opts & ~WindowOptions.getScreenSizeChars; break; - case 'getIconTitle': opts = value ? opts | WindowOptions.getIconTitle : opts & ~WindowOptions.getIconTitle; break; - case 'getWinTitle': opts = value ? opts | WindowOptions.getWinTitle : opts & ~WindowOptions.getWinTitle; break; - case 'pushTitle': opts = value ? opts | WindowOptions.pushTitle : opts & ~WindowOptions.pushTitle; break; - case 'popTitle': opts = value ? opts | WindowOptions.popTitle : opts & ~WindowOptions.popTitle; break; - case 'setWinLines': opts = value ? opts | WindowOptions.setWinLines : opts & ~WindowOptions.setWinLines; break; - default: throw new Error(`unknown WindowOption "${optionName}"`); - } - } - return opts; -} diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 9ded45e99..c05401d02 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -7,7 +7,6 @@ import { IOptionsService, ITerminalOptions, IPartialTerminalOptions } from 'comm import { EventEmitter, IEvent } from 'common/EventEmitter'; import { isMac } from 'common/Platform'; import { clone } from 'common/Clone'; -import { setWindowOptions, getWindowOptions, WindowOptions } from 'common/WindowOptions'; // Source: https://freesound.org/people/altemark/sounds/45759/ // This sound is released under the Creative Commons Attribution 3.0 Unported @@ -65,7 +64,6 @@ export class OptionsService implements IOptionsService { serviceBrand: any; public options: ITerminalOptions; - public windowOptions: WindowOptions = 0; private _onOptionChange = new EventEmitter(); public get onOptionChange(): IEvent { return this._onOptionChange.event; } @@ -78,9 +76,6 @@ export class OptionsService implements IOptionsService { this.options[k] = newValue; } }); - if (options.windowOptions) { - this.windowOptions = setWindowOptions(options.windowOptions as any, 0); - } } public setOption(key: string, value: any): void { @@ -135,8 +130,6 @@ export class OptionsService implements IOptionsService { throw new Error(`${key} cannot be less than or equal to 0, value: ${value}`); } break; - case 'windowOptions': - this.windowOptions = setWindowOptions(value, this.windowOptions); } return value; } @@ -145,9 +138,6 @@ export class OptionsService implements IOptionsService { if (!(key in DEFAULT_OPTIONS)) { throw new Error(`No option with key "${key}"`); } - if (key === 'windowOptions') { - return getWindowOptions(this.windowOptions); - } return this.options[key]; } } diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 7a045fc7e..253e3110f 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -7,7 +7,6 @@ import { IEvent } from 'common/EventEmitter'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, IWindowOptions } from 'common/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; -import { WindowOptions } from 'common/WindowOptions'; export const IBufferService = createDecorator('BufferService'); export interface IBufferService { @@ -167,7 +166,6 @@ export interface IOptionsService { serviceBrand: any; readonly options: ITerminalOptions; - readonly windowOptions: WindowOptions; readonly onOptionChange: IEvent; From 7ad2a68052869bf3fd2d18e302a59bd4481e2791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 14 Nov 2019 13:05:24 +0100 Subject: [PATCH 106/268] cleanup --- demo/client.ts | 2 +- src/common/TestUtils.test.ts | 1 - src/common/services/OptionsService.ts | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 42e4fe7d1..7118ec2ef 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -288,7 +288,7 @@ function initOptions(term: TerminalType): void { }); html += '
'; Object.keys(stringOptions).forEach(o => { - if (stringOptions[o] && typeof stringOptions[o] !== 'string') { + if (stringOptions[o]) { html += `
`; } else { html += `
`; diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 42152fac5..7d9c3aa60 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -75,7 +75,6 @@ export class MockLogService implements ILogService { export class MockOptionsService implements IOptionsService { serviceBrand: any; options: ITerminalOptions = clone(DEFAULT_OPTIONS); - windowOptions = 0; onOptionChange: IEvent = new EventEmitter().event; constructor(testOptions?: IPartialTerminalOptions) { if (testOptions) { diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index c05401d02..70a885fcd 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -14,7 +14,6 @@ import { clone } from 'common/Clone'; // made, apart from the conversion to base64. export const DEFAULT_BELL_SOUND = 'data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4LjMyLjEwNAAAAAAAAAAAAAAA//tQxAADB8AhSmxhIIEVCSiJrDCQBTcu3UrAIwUdkRgQbFAZC1CQEwTJ9mjRvBA4UOLD8nKVOWfh+UlK3z/177OXrfOdKl7pyn3Xf//WreyTRUoAWgBgkOAGbZHBgG1OF6zM82DWbZaUmMBptgQhGjsyYqc9ae9XFz280948NMBWInljyzsNRFLPWdnZGWrddDsjK1unuSrVN9jJsK8KuQtQCtMBjCEtImISdNKJOopIpBFpNSMbIHCSRpRR5iakjTiyzLhchUUBwCgyKiweBv/7UsQbg8isVNoMPMjAAAA0gAAABEVFGmgqK////9bP/6XCykxBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq'; - // TODO: Freeze? export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({ cols: 80, From 57fe84508cb43291d508e546ed5130174ee5a141 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 14 Nov 2019 22:29:46 +0100 Subject: [PATCH 107/268] apply empty report rule to all encodings; comments added --- src/common/Types.d.ts | 3 +++ src/common/services/CoreMouseService.ts | 12 ++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index 2f7420389..2dcc704bd 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -249,5 +249,8 @@ export interface ICoreMouseProtocol { * The tracking encoding can be registered and activated at the CoreMouseService. * If a ICoreMouseEvent passes all procotol restrictions it will be encoded * with the active encoding and sent out. + * Note: Returning an empty string will supress sending a mouse report, + * which can be used to skip creating falsey reports in limited encodings + * (DEFAULT only supports up to 223 1-based as coord value). */ export type CoreMouseEncoding = (event: ICoreMouseEvent) => string; diff --git a/src/common/services/CoreMouseService.ts b/src/common/services/CoreMouseService.ts index 5c9d39442..0bd25dd03 100644 --- a/src/common/services/CoreMouseService.ts +++ b/src/common/services/CoreMouseService.ts @@ -125,6 +125,10 @@ const DEFAULT_ENCODINGS: {[key: string]: CoreMouseEncoding} = { */ DEFAULT: (e: ICoreMouseEvent) => { const params = [eventCode(e, false) + 32, e.col + 32, e.row + 32]; + // supress mouse report if we exceed addressible range + // Note this is handled differently by emulators + // - xterm: sends 0;0 coords instead + // - vte, konsole: no report if (params[0] > 255 || params[1] > 255 || params[2] > 255) { return ''; } @@ -264,13 +268,13 @@ export class CoreMouseService implements ICoreMouseService { // encode report and send const report = this._encodings[this._activeEncoding](e); - if (this._activeEncoding === 'DEFAULT') { + if (report) { // always send DEFAULT as binary data - if (report) { + if (this._activeEncoding === 'DEFAULT') { this._coreService.triggerBinaryEvent(report); + } else { + this._coreService.triggerDataEvent(report, true); } - } else { - this._coreService.triggerDataEvent(report, true); } this._lastEvent = e; From 9c9aa68c23b228bce802ef6e5bdde7998276ea3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 15 Nov 2019 01:07:14 +0100 Subject: [PATCH 108/268] wcwidth rules unicode 10 --- src/InputHandler.ts | 3 +- src/common/CharWidth.ts | 249 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 251 insertions(+), 1 deletion(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 3994d532c..28d17b3e6 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -7,7 +7,7 @@ import { IInputHandler, IInputHandlingTerminal } from './Types'; import { C0, C1 } from 'common/data/EscapeSequences'; import { CHARSETS, DEFAULT_CHARSET } from 'common/data/Charsets'; -import { wcwidth } from 'common/CharWidth'; +import { wcwidth, wcwidth10 } from 'common/CharWidth'; import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser'; import { Disposable } from 'common/Lifecycle'; import { concat } from 'common/TypedArrayUtils'; @@ -394,6 +394,7 @@ export class InputHandler extends Disposable implements IInputHandler { // calculate print space // expensive call, therefore we save width in line buffer chWidth = wcwidth(code); + chWidth = wcwidth10(code); // get charset replacement character // charset is only defined for ASCII, therefore we only diff --git a/src/common/CharWidth.ts b/src/common/CharWidth.ts index 24f53450f..ae826c89e 100644 --- a/src/common/CharWidth.ts +++ b/src/common/CharWidth.ts @@ -169,3 +169,252 @@ export function getStringCellWidth(s: string): number { } return result; } + + + +const COMBINING_BMP = [ + [0x0300, 0x036F], [0x0483, 0x0489], [0x0591, 0x05BD], + [0x05BF, 0x05BF], [0x05C1, 0x05C2], [0x05C4, 0x05C5], + [0x05C7, 0x05C7], [0x0600, 0x0605], [0x0610, 0x061A], + [0x061C, 0x061C], [0x064B, 0x065F], [0x0670, 0x0670], + [0x06D6, 0x06DD], [0x06DF, 0x06E4], [0x06E7, 0x06E8], + [0x06EA, 0x06ED], [0x070F, 0x070F], [0x0711, 0x0711], + [0x0730, 0x074A], [0x07A6, 0x07B0], [0x07EB, 0x07F3], + [0x07FD, 0x07FD], [0x0816, 0x0819], [0x081B, 0x0823], + [0x0825, 0x0827], [0x0829, 0x082D], [0x0859, 0x085B], + [0x08D3, 0x0902], [0x093A, 0x093A], [0x093C, 0x093C], + [0x0941, 0x0948], [0x094D, 0x094D], [0x0951, 0x0957], + [0x0962, 0x0963], [0x0981, 0x0981], [0x09BC, 0x09BC], + [0x09C1, 0x09C4], [0x09CD, 0x09CD], [0x09E2, 0x09E3], + [0x09FE, 0x09FE], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C], + [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D], + [0x0A51, 0x0A51], [0x0A70, 0x0A71], [0x0A75, 0x0A75], + [0x0A81, 0x0A82], [0x0ABC, 0x0ABC], [0x0AC1, 0x0AC5], + [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD], [0x0AE2, 0x0AE3], + [0x0AFA, 0x0AFF], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C], + [0x0B3F, 0x0B3F], [0x0B41, 0x0B44], [0x0B4D, 0x0B4D], + [0x0B56, 0x0B56], [0x0B62, 0x0B63], [0x0B82, 0x0B82], + [0x0BC0, 0x0BC0], [0x0BCD, 0x0BCD], [0x0C00, 0x0C00], + [0x0C04, 0x0C04], [0x0C3E, 0x0C40], [0x0C46, 0x0C48], + [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0C62, 0x0C63], + [0x0C81, 0x0C81], [0x0CBC, 0x0CBC], [0x0CBF, 0x0CBF], + [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD], [0x0CE2, 0x0CE3], + [0x0D00, 0x0D01], [0x0D3B, 0x0D3C], [0x0D41, 0x0D44], + [0x0D4D, 0x0D4D], [0x0D62, 0x0D63], [0x0DCA, 0x0DCA], + [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6], [0x0E31, 0x0E31], + [0x0E34, 0x0E3A], [0x0E47, 0x0E4E], [0x0EB1, 0x0EB1], + [0x0EB4, 0x0EBC], [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], + [0x0F35, 0x0F35], [0x0F37, 0x0F37], [0x0F39, 0x0F39], + [0x0F71, 0x0F7E], [0x0F80, 0x0F84], [0x0F86, 0x0F87], + [0x0F8D, 0x0F97], [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], + [0x102D, 0x1030], [0x1032, 0x1037], [0x1039, 0x103A], + [0x103D, 0x103E], [0x1058, 0x1059], [0x105E, 0x1060], + [0x1071, 0x1074], [0x1082, 0x1082], [0x1085, 0x1086], + [0x108D, 0x108D], [0x109D, 0x109D], [0x1160, 0x11FF], + [0x135D, 0x135F], [0x1712, 0x1714], [0x1732, 0x1734], + [0x1752, 0x1753], [0x1772, 0x1773], [0x17B4, 0x17B5], + [0x17B7, 0x17BD], [0x17C6, 0x17C6], [0x17C9, 0x17D3], + [0x17DD, 0x17DD], [0x180B, 0x180E], [0x1885, 0x1886], + [0x18A9, 0x18A9], [0x1920, 0x1922], [0x1927, 0x1928], + [0x1932, 0x1932], [0x1939, 0x193B], [0x1A17, 0x1A18], + [0x1A1B, 0x1A1B], [0x1A56, 0x1A56], [0x1A58, 0x1A5E], + [0x1A60, 0x1A60], [0x1A62, 0x1A62], [0x1A65, 0x1A6C], + [0x1A73, 0x1A7C], [0x1A7F, 0x1A7F], [0x1AB0, 0x1ABE], + [0x1B00, 0x1B03], [0x1B34, 0x1B34], [0x1B36, 0x1B3A], + [0x1B3C, 0x1B3C], [0x1B42, 0x1B42], [0x1B6B, 0x1B73], + [0x1B80, 0x1B81], [0x1BA2, 0x1BA5], [0x1BA8, 0x1BA9], + [0x1BAB, 0x1BAD], [0x1BE6, 0x1BE6], [0x1BE8, 0x1BE9], + [0x1BED, 0x1BED], [0x1BEF, 0x1BF1], [0x1C2C, 0x1C33], + [0x1C36, 0x1C37], [0x1CD0, 0x1CD2], [0x1CD4, 0x1CE0], + [0x1CE2, 0x1CE8], [0x1CED, 0x1CED], [0x1CF4, 0x1CF4], + [0x1CF8, 0x1CF9], [0x1DC0, 0x1DF9], [0x1DFB, 0x1DFF], + [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2064], + [0x2066, 0x206F], [0x20D0, 0x20F0], [0x2CEF, 0x2CF1], + [0x2D7F, 0x2D7F], [0x2DE0, 0x2DFF], [0x302A, 0x302D], + [0x3099, 0x309A], [0xA66F, 0xA672], [0xA674, 0xA67D], + [0xA69E, 0xA69F], [0xA6F0, 0xA6F1], [0xA802, 0xA802], + [0xA806, 0xA806], [0xA80B, 0xA80B], [0xA825, 0xA826], + [0xA8C4, 0xA8C5], [0xA8E0, 0xA8F1], [0xA8FF, 0xA8FF], + [0xA926, 0xA92D], [0xA947, 0xA951], [0xA980, 0xA982], + [0xA9B3, 0xA9B3], [0xA9B6, 0xA9B9], [0xA9BC, 0xA9BD], + [0xA9E5, 0xA9E5], [0xAA29, 0xAA2E], [0xAA31, 0xAA32], + [0xAA35, 0xAA36], [0xAA43, 0xAA43], [0xAA4C, 0xAA4C], + [0xAA7C, 0xAA7C], [0xAAB0, 0xAAB0], [0xAAB2, 0xAAB4], + [0xAAB7, 0xAAB8], [0xAABE, 0xAABF], [0xAAC1, 0xAAC1], + [0xAAEC, 0xAAED], [0xAAF6, 0xAAF6], [0xABE5, 0xABE5], + [0xABE8, 0xABE8], [0xABED, 0xABED], [0xFB1E, 0xFB1E], + [0xFE00, 0xFE0F], [0xFE20, 0xFE2F], [0xFEFF, 0xFEFF], + [0xFFF9, 0xFFFB] +]; + +const COMBINING_HIGH = [ + [0x101FD, 0x101FD], [0x102E0, 0x102E0], + [0x10376, 0x1037A], [0x10A01, 0x10A03], [0x10A05, 0x10A06], + [0x10A0C, 0x10A0F], [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], + [0x10AE5, 0x10AE6], [0x10D24, 0x10D27], [0x10F46, 0x10F50], + [0x11001, 0x11001], [0x11038, 0x11046], [0x1107F, 0x11081], + [0x110B3, 0x110B6], [0x110B9, 0x110BA], [0x110BD, 0x110BD], + [0x110CD, 0x110CD], [0x11100, 0x11102], [0x11127, 0x1112B], + [0x1112D, 0x11134], [0x11173, 0x11173], [0x11180, 0x11181], + [0x111B6, 0x111BE], [0x111C9, 0x111CC], [0x1122F, 0x11231], + [0x11234, 0x11234], [0x11236, 0x11237], [0x1123E, 0x1123E], + [0x112DF, 0x112DF], [0x112E3, 0x112EA], [0x11300, 0x11301], + [0x1133B, 0x1133C], [0x11340, 0x11340], [0x11366, 0x1136C], + [0x11370, 0x11374], [0x11438, 0x1143F], [0x11442, 0x11444], + [0x11446, 0x11446], [0x1145E, 0x1145E], [0x114B3, 0x114B8], + [0x114BA, 0x114BA], [0x114BF, 0x114C0], [0x114C2, 0x114C3], + [0x115B2, 0x115B5], [0x115BC, 0x115BD], [0x115BF, 0x115C0], + [0x115DC, 0x115DD], [0x11633, 0x1163A], [0x1163D, 0x1163D], + [0x1163F, 0x11640], [0x116AB, 0x116AB], [0x116AD, 0x116AD], + [0x116B0, 0x116B5], [0x116B7, 0x116B7], [0x1171D, 0x1171F], + [0x11722, 0x11725], [0x11727, 0x1172B], [0x1182F, 0x11837], + [0x11839, 0x1183A], [0x119D4, 0x119D7], [0x119DA, 0x119DB], + [0x119E0, 0x119E0], [0x11A01, 0x11A0A], [0x11A33, 0x11A38], + [0x11A3B, 0x11A3E], [0x11A47, 0x11A47], [0x11A51, 0x11A56], + [0x11A59, 0x11A5B], [0x11A8A, 0x11A96], [0x11A98, 0x11A99], + [0x11C30, 0x11C36], [0x11C38, 0x11C3D], [0x11C3F, 0x11C3F], + [0x11C92, 0x11CA7], [0x11CAA, 0x11CB0], [0x11CB2, 0x11CB3], + [0x11CB5, 0x11CB6], [0x11D31, 0x11D36], [0x11D3A, 0x11D3A], + [0x11D3C, 0x11D3D], [0x11D3F, 0x11D45], [0x11D47, 0x11D47], + [0x11D90, 0x11D91], [0x11D95, 0x11D95], [0x11D97, 0x11D97], + [0x11EF3, 0x11EF4], [0x13430, 0x13438], [0x16AF0, 0x16AF4], + [0x16B30, 0x16B36], [0x16F4F, 0x16F4F], [0x16F8F, 0x16F92], + [0x1BC9D, 0x1BC9E], [0x1BCA0, 0x1BCA3], [0x1D167, 0x1D169], + [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD], + [0x1D242, 0x1D244], [0x1DA00, 0x1DA36], [0x1DA3B, 0x1DA6C], + [0x1DA75, 0x1DA75], [0x1DA84, 0x1DA84], [0x1DA9B, 0x1DA9F], + [0x1DAA1, 0x1DAAF], [0x1E000, 0x1E006], [0x1E008, 0x1E018], + [0x1E01B, 0x1E021], [0x1E023, 0x1E024], [0x1E026, 0x1E02A], + [0x1E130, 0x1E136], [0x1E2EC, 0x1E2EF], [0x1E8D0, 0x1E8D6], + [0x1E944, 0x1E94A], [0xE0001, 0xE0001], [0xE0020, 0xE007F], + [0xE0100, 0xE01EF] +]; + +const WIDE_BMP = [ + [0x1100, 0x115F], [0x231A, 0x231B], [0x2329, 0x232A], + [0x23E9, 0x23EC], [0x23F0, 0x23F0], [0x23F3, 0x23F3], + [0x25FD, 0x25FE], [0x2614, 0x2615], [0x2648, 0x2653], + [0x267F, 0x267F], [0x2693, 0x2693], [0x26A1, 0x26A1], + [0x26AA, 0x26AB], [0x26BD, 0x26BE], [0x26C4, 0x26C5], + [0x26CE, 0x26CE], [0x26D4, 0x26D4], [0x26EA, 0x26EA], + [0x26F2, 0x26F3], [0x26F5, 0x26F5], [0x26FA, 0x26FA], + [0x26FD, 0x26FD], [0x2705, 0x2705], [0x270A, 0x270B], + [0x2728, 0x2728], [0x274C, 0x274C], [0x274E, 0x274E], + [0x2753, 0x2755], [0x2757, 0x2757], [0x2795, 0x2797], + [0x27B0, 0x27B0], [0x27BF, 0x27BF], [0x2B1B, 0x2B1C], + [0x2B50, 0x2B50], [0x2B55, 0x2B55], [0x2E80, 0x2E99], + [0x2E9B, 0x2EF3], [0x2F00, 0x2FD5], [0x2FF0, 0x2FFB], + [0x3000, 0x3029], [0x302E, 0x303E], [0x3041, 0x3096], + [0x309B, 0x30FF], [0x3105, 0x312F], [0x3131, 0x318E], + [0x3190, 0x31BA], [0x31C0, 0x31E3], [0x31F0, 0x321E], + [0x3220, 0x3247], [0x3250, 0x4DBF], [0x4E00, 0xA48C], + [0xA490, 0xA4C6], [0xA960, 0xA97C], [0xAC00, 0xD7A3], + [0xF900, 0xFAFF], [0xFE10, 0xFE19], [0xFE30, 0xFE52], + [0xFE54, 0xFE66], [0xFE68, 0xFE6B], [0xFF01, 0xFF60], + [0xFFE0, 0xFFE6] +]; + +const WIDE_HIGH = [ + [0x16FE0, 0x16FE3], [0x17000, 0x187F7], + [0x18800, 0x18AF2], [0x1B000, 0x1B11E], [0x1B150, 0x1B152], + [0x1B164, 0x1B167], [0x1B170, 0x1B2FB], [0x1F004, 0x1F004], + [0x1F0CF, 0x1F0CF], [0x1F18E, 0x1F18E], [0x1F191, 0x1F19A], + [0x1F200, 0x1F202], [0x1F210, 0x1F23B], [0x1F240, 0x1F248], + [0x1F250, 0x1F251], [0x1F260, 0x1F265], [0x1F300, 0x1F320], + [0x1F32D, 0x1F335], [0x1F337, 0x1F37C], [0x1F37E, 0x1F393], + [0x1F3A0, 0x1F3CA], [0x1F3CF, 0x1F3D3], [0x1F3E0, 0x1F3F0], + [0x1F3F4, 0x1F3F4], [0x1F3F8, 0x1F43E], [0x1F440, 0x1F440], + [0x1F442, 0x1F4FC], [0x1F4FF, 0x1F53D], [0x1F54B, 0x1F54E], + [0x1F550, 0x1F567], [0x1F57A, 0x1F57A], [0x1F595, 0x1F596], + [0x1F5A4, 0x1F5A4], [0x1F5FB, 0x1F64F], [0x1F680, 0x1F6C5], + [0x1F6CC, 0x1F6CC], [0x1F6D0, 0x1F6D2], [0x1F6D5, 0x1F6D5], + [0x1F6EB, 0x1F6EC], [0x1F6F4, 0x1F6FA], [0x1F7E0, 0x1F7EB], + [0x1F90D, 0x1F971], [0x1F973, 0x1F976], [0x1F97A, 0x1F9A2], + [0x1F9A5, 0x1F9AA], [0x1F9AE, 0x1F9CA], [0x1F9CD, 0x1F9FF], + [0x1FA70, 0x1FA73], [0x1FA78, 0x1FA7A], [0x1FA80, 0x1FA82], + [0x1FA90, 0x1FA95], [0x20000, 0x2FFFD], [0x30000, 0x3FFFD] +]; + +function bisearch(ucs: number, data: number[][]): boolean { + let min = 0; + let max = data.length - 1; + let mid; + if (ucs < data[0][0] || ucs > data[max][1]) { + return false; + } + while (max >= min) { + mid = (min + max) >> 1; + if (ucs > data[mid][1]) { + min = mid + 1; + } else if (ucs < data[mid][0]) { + max = mid - 1; + } else { + return true; + } + } + return false; +} + +function wcwidthHigh(ucs: number): 0 | 1 | 2 { + if (bisearch(ucs, COMBINING_HIGH)) { + return 0; + } + if (bisearch(ucs, WIDE_HIGH)) { + return 2; + } + return 1; +} + + +const TABLE = new Uint8Array(65536); +fill(TABLE, 1); +fill(TABLE, 0, 1, 32); +fill(TABLE, 0, 0x7f, 0xa0); +for (let r = 0; r < COMBINING_BMP.length; ++r) { + fill(TABLE, 0, COMBINING_BMP[r][0], COMBINING_BMP[r][1] + 1); +} +for (let r = 0; r < WIDE_BMP.length; ++r) { + fill(TABLE, 2, WIDE_BMP[r][0], WIDE_BMP[r][1] + 1); +} + +export function wcwidth10(num: number): number { + if (num < 32) { + return 0; + } + if (num < 127) { + return 1; + } + if (num < 65536) { + return TABLE[num]; + } + return wcwidthHigh(num); +}; + +export function getStringCellWidth10(s: string): number { + let result = 0; + const length = s.length; + for (let i = 0; i < length; ++i) { + let code = s.charCodeAt(i); + // surrogate pair first + if (0xD800 <= code && code <= 0xDBFF) { + if (++i >= length) { + // this should not happen with strings retrieved from + // Buffer.translateToString as it converts from UTF-32 + // and therefore always should contain the second part + // for any other string we still have to handle it somehow: + // simply treat the lonely surrogate first as a single char (UCS-2 behavior) + return result + wcwidth10(code); + } + const second = s.charCodeAt(i); + // convert surrogate pair to high codepoint only for valid second part (UTF-16) + // otherwise treat them independently (UCS-2 behavior) + if (0xDC00 <= second && second <= 0xDFFF) { + code = (code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; + } else { + result += wcwidth10(second); + } + } + result += wcwidth10(code); + } + return result; +} From 24dc87af2c6165216d4fb4b70c84f474f72155ed Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 14 Nov 2019 19:46:24 -0800 Subject: [PATCH 109/268] Only enter updateBackgounds condition if cell changed We want to check when the background changed or when the foreground changed (and either the old or new value was inverse) --- addons/xterm-addon-webgl/src/RectangleRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index 356e17472..b52a506e2 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -254,7 +254,7 @@ export class RectangleRenderer { const bg = model.cells[modelIndex + RENDER_MODEL_BG_OFFSET]; const fg = model.cells[modelIndex + RENDER_MODEL_FG_OFFSET]; const inverse = !!(fg & FgFlags.INVERSE); - if (bg !== currentBg || ((inverse || currentInverse) && fg !== currentFg)) { + if (bg !== currentBg || (fg !== currentFg && (currentInverse || inverse))) { // A rectangle needs to be drawn if going from non-default to another color if (currentBg !== 0 || (currentInverse && currentFg !== 0)) { const offset = rectangleCount++ * INDICES_PER_RECTANGLE; From be9700878b7604dfec6133c73c20926fc72af8ae Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 14 Nov 2019 20:02:40 -0800 Subject: [PATCH 110/268] Have inverse bold respect drawBoldTextInBrightColors --- src/browser/renderer/BaseRenderLayer.ts | 8 ++++++-- src/browser/renderer/TextRenderLayer.ts | 6 +++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/browser/renderer/BaseRenderLayer.ts b/src/browser/renderer/BaseRenderLayer.ts index a33bef28e..2c748c50e 100644 --- a/src/browser/renderer/BaseRenderLayer.ts +++ b/src/browser/renderer/BaseRenderLayer.ts @@ -282,7 +282,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { fg = (cell.isFgDefault()) ? DEFAULT_COLOR : cell.getFgColor(); } - const drawInBrightColor = this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8 && fg !== INVERTED_DEFAULT_COLOR; + const drawInBrightColor = this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8; fg += drawInBrightColor ? 8 : 0; this._currentGlyphIdentifier.chars = cell.getChars() || WHITESPACE_CELL_CHAR; @@ -325,7 +325,11 @@ export abstract class BaseRenderLayer implements IRenderLayer { } else if (cell.isBgRGB()) { this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`; } else { - this._ctx.fillStyle = this._colors.ansi[cell.getBgColor()].css; + let bg = cell.getBgColor(); + if (this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && bg < 8) { + bg += 8; + } + this._ctx.fillStyle = this._colors.ansi[bg].css; } } else { if (cell.isFgDefault()) { diff --git a/src/browser/renderer/TextRenderLayer.ts b/src/browser/renderer/TextRenderLayer.ts index 330400ca2..fee261f60 100644 --- a/src/browser/renderer/TextRenderLayer.ts +++ b/src/browser/renderer/TextRenderLayer.ts @@ -227,7 +227,11 @@ export class TextRenderLayer extends BaseRenderLayer { } else if (cell.isBgRGB()) { this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`; } else { - this._ctx.fillStyle = this._colors.ansi[cell.getBgColor()].css; + let bg = cell.getBgColor(); + if (this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && bg < 8) { + bg += 8; + } + this._ctx.fillStyle = this._colors.ansi[bg].css; } } else { if (cell.isFgDefault()) { From 023fab9095c63ff3e549dbc171225da232b7d2ea Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 14 Nov 2019 20:34:01 -0800 Subject: [PATCH 111/268] Remove unused import --- src/browser/renderer/dom/DomRendererRowFactory.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index 5e53cf604..cffc56242 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -5,7 +5,6 @@ import { IBufferLine } from 'common/Types'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; -import { AttributeData } from 'common/buffer/AttributeData'; import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Attributes } from 'common/buffer/Constants'; import { CellData } from 'common/buffer/CellData'; import { IOptionsService } from 'common/services/Services'; From c95337a742c6334df50c82121416b97e63dbae55 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 14 Nov 2019 21:57:15 -0800 Subject: [PATCH 112/268] Remove unneeded params from webgl funcs --- addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 9910b0097..686dcb100 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -234,7 +234,7 @@ export class WebglCharAtlas implements IDisposable { } } - private _resolveBackgroundRgba(bgColorMode: number, bgColor: number, fgColorMode: number, fgColor: number, inverse: boolean): number { + private _resolveBackgroundRgba(bgColorMode: number, bgColor: number, inverse: boolean): number { switch (bgColorMode) { case Attributes.CM_P16: case Attributes.CM_P256: @@ -250,7 +250,7 @@ export class WebglCharAtlas implements IDisposable { } } - private _resolveForegroundRgba(bgColorMode: number, bgColor: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): number { + private _resolveForegroundRgba(fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): number { switch (fgColorMode) { case Attributes.CM_P16: case Attributes.CM_P256: @@ -280,8 +280,8 @@ export class WebglCharAtlas implements IDisposable { return adjustedColor || undefined; } - const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, fgColorMode, fgColor, inverse); - const fgRgba = this._resolveForegroundRgba(bgColorMode, bgColor, fgColorMode, fgColor, inverse, bold); + const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, inverse); + const fgRgba = this._resolveForegroundRgba(fgColorMode, fgColor, inverse, bold); const result = ensureContrastRatioRgba(bgRgba, fgRgba, this._config.minimumContrastRatio); if (!result) { From e5f4e2ff8f2ac034764744fb4d411c0ac1b62952 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 14 Nov 2019 22:07:50 -0800 Subject: [PATCH 113/268] Support min contrast ratio in canvas renderer too: --- src/browser/renderer/BaseRenderLayer.ts | 103 ++++++++++++++++++++++-- 1 file changed, 96 insertions(+), 7 deletions(-) diff --git a/src/browser/renderer/BaseRenderLayer.ts b/src/browser/renderer/BaseRenderLayer.ts index 2c748c50e..599ab114a 100644 --- a/src/browser/renderer/BaseRenderLayer.ts +++ b/src/browser/renderer/BaseRenderLayer.ts @@ -5,16 +5,17 @@ import { IRenderDimensions, IRenderLayer } from 'browser/renderer/Types'; import { ICellData } from 'common/Types'; -import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; +import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE, Attributes } from 'common/buffer/Constants'; import { IGlyphIdentifier } from 'browser/renderer/atlas/Types'; import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { BaseCharAtlas } from 'browser/renderer/atlas/BaseCharAtlas'; import { acquireCharAtlas } from 'browser/renderer/atlas/CharAtlasCache'; import { AttributeData } from 'common/buffer/AttributeData'; -import { IColorSet } from 'browser/Types'; +import { IColorSet, IColor } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; import { IBufferService, IOptionsService } from 'common/services/Services'; import { throwIfFalsy } from 'browser/renderer/RendererUtils'; +import { toCss, ensureContrastRatioRgba } from 'browser/Color'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; @@ -262,13 +263,14 @@ export abstract class BaseRenderLayer implements IRenderLayer { * @param bold Whether the text is bold. */ protected _drawChars(cell: ICellData, x: number, y: number): void { + const contrastColor = this._getContrastColor(cell); // skip cache right away if we draw in RGB // Note: to avoid bad runtime JoinedCellData will be skipped // in the cache handler itself (atlasDidDraw == false) and // fall through to uncached later down below - if (cell.isFgRGB() || cell.isBgRGB()) { - this._drawUncachedChars(cell, x, y); + if (contrastColor || cell.isFgRGB() || cell.isBgRGB()) { + this._drawUncachedChars(cell, x, y, contrastColor); return; } @@ -314,13 +316,15 @@ export abstract class BaseRenderLayer implements IRenderLayer { * @param x The column to draw at. * @param y The row to draw at. */ - private _drawUncachedChars(cell: ICellData, x: number, y: number): void { + private _drawUncachedChars(cell: ICellData, x: number, y: number, fgOverride?: IColor): void { this._ctx.save(); this._ctx.font = this._getFont(!!cell.isBold(), !!cell.isItalic()); this._ctx.textBaseline = 'middle'; if (cell.isInverse()) { - if (cell.isBgDefault()) { + if (fgOverride) { + this._ctx.fillStyle = fgOverride.css; + } else if (cell.isBgDefault()) { this._ctx.fillStyle = this._colors.background.css; } else if (cell.isBgRGB()) { this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`; @@ -332,7 +336,9 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._ctx.fillStyle = this._colors.ansi[bg].css; } } else { - if (cell.isFgDefault()) { + if (fgOverride) { + this._ctx.fillStyle = fgOverride.css; + } else if (cell.isFgDefault()) { this._ctx.fillStyle = this._colors.foreground.css; } else if (cell.isFgRGB()) { this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`; @@ -383,5 +389,88 @@ export abstract class BaseRenderLayer implements IRenderLayer { return `${fontStyle} ${fontWeight} ${this._optionsService.options.fontSize * window.devicePixelRatio}px ${this._optionsService.options.fontFamily}`; } + + private _getContrastColor(cell: CellData): IColor | undefined { + if (this._optionsService.options.minimumContrastRatio === 1) { + return undefined; + } + + // Try get from cache first + const adjustedColor = this._colors.contrastCache.getColor(cell.bg, cell.fg); + if (adjustedColor !== undefined) { + return adjustedColor || undefined; + } + + let fgColor = cell.getFgColor(); + let fgColorMode = cell.getFgColorMode(); + let bgColor = cell.getBgColor(); + let bgColorMode = cell.getBgColorMode(); + const isInverse = !!cell.isInverse(); + const isBold = !!cell.isInverse(); + if (isInverse) { + const temp = fgColor; + fgColor = bgColor; + bgColor = temp; + const temp2 = fgColorMode; + fgColorMode = bgColorMode; + bgColorMode = temp2; + } + + const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, isInverse); + const fgRgba = this._resolveForegroundRgba(fgColorMode, fgColor, isInverse, isBold); + const result = ensureContrastRatioRgba(bgRgba, fgRgba, this._optionsService.options.minimumContrastRatio); + + if (!result) { + this._colors.contrastCache.setColor(cell.bg, cell.fg, null); + return undefined; + } + + const color: IColor = { + css: toCss( + (result >> 24) & 0xFF, + (result >> 16) & 0xFF, + (result >> 8) & 0xFF + ), + rgba: result + }; + this._colors.contrastCache.setColor(cell.bg, cell.fg, color); + + return color; + } + + private _resolveBackgroundRgba(bgColorMode: number, bgColor: number, inverse: boolean): number { + switch (bgColorMode) { + case Attributes.CM_P16: + case Attributes.CM_P256: + return this._colors.ansi[bgColor].rgba; + case Attributes.CM_RGB: + return bgColor << 8; + case Attributes.CM_DEFAULT: + default: + if (inverse) { + return this._colors.foreground.rgba; + } + return this._colors.background.rgba; + } + } + + private _resolveForegroundRgba(fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): number { + switch (fgColorMode) { + case Attributes.CM_P16: + case Attributes.CM_P256: + if (this._optionsService.options.drawBoldTextInBrightColors && bold && fgColor < 8) { + fgColor += 8; + } + return this._colors.ansi[fgColor].rgba; + case Attributes.CM_RGB: + return fgColor << 8; + case Attributes.CM_DEFAULT: + default: + if (inverse) { + return this._colors.background.rgba; + } + return this._colors.foreground.rgba; + } + } } From 5aa31576722f02213411be860b678d12281b58d0 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 14 Nov 2019 22:12:36 -0800 Subject: [PATCH 114/268] Return inverse colors correctly This probably wasn't causing a bug since the opposite values were being returned, it was wrong and made the code confusing though. --- addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 686dcb100..cda202253 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -244,7 +244,7 @@ export class WebglCharAtlas implements IDisposable { case Attributes.CM_DEFAULT: default: if (inverse) { - this._config.colors.foreground.rgba; + return this._config.colors.foreground.rgba; } return this._config.colors.background.rgba; } @@ -263,7 +263,7 @@ export class WebglCharAtlas implements IDisposable { case Attributes.CM_DEFAULT: default: if (inverse) { - this._config.colors.background.rgba; + return this._config.colors.background.rgba; } return this._config.colors.foreground.rgba; } From 943928c4d1a6d4f46fb33fd7969fff71aeed7786 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 14 Nov 2019 22:26:02 -0800 Subject: [PATCH 115/268] Move WindowsMode to common/ Part of #1507 --- src/Terminal.ts | 14 +++++++++----- src/{ => common}/WindowsMode.ts | 15 +++++++-------- 2 files changed, 16 insertions(+), 13 deletions(-) rename src/{ => common}/WindowsMode.ts (67%) diff --git a/src/Terminal.ts b/src/Terminal.ts index 3c589ffa9..fc354966f 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -43,7 +43,7 @@ import { IKeyboardEvent, KeyboardResultType, ICharset, IBufferLine, IAttributeDa import { evaluateKeyboardEvent } from 'common/input/Keyboard'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; -import { applyWindowsMode } from './WindowsMode'; +import { handleWindowsModeLineFeed } from 'common/WindowsMode'; import { ColorManager } from 'browser/ColorManager'; import { RenderService } from 'browser/services/RenderService'; import { IOptionsService, IBufferService, ICoreMouseService, ICoreService, ILogService, IDirtyRowService, IInstantiationService } from 'common/services/Services'; @@ -281,7 +281,13 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this.linkifier = this.linkifier || new Linkifier(this._bufferService, this._logService); if (this.options.windowsMode) { - this._windowsMode = applyWindowsMode(this); + this._enableWindowsMode(); + } + } + + private _enableWindowsMode(): void { + if (!this._windowsMode) { + this._windowsMode = this.onLineFeed(handleWindowsModeLineFeed.bind(null, this._bufferService)); } } @@ -362,9 +368,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp break; case 'windowsMode': if (this.optionsService.options.windowsMode) { - if (!this._windowsMode) { - this._windowsMode = applyWindowsMode(this); - } + this._enableWindowsMode(); } else { this._windowsMode?.dispose(); this._windowsMode = undefined; diff --git a/src/WindowsMode.ts b/src/common/WindowsMode.ts similarity index 67% rename from src/WindowsMode.ts rename to src/common/WindowsMode.ts index 0838cae20..ff0e75913 100644 --- a/src/WindowsMode.ts +++ b/src/common/WindowsMode.ts @@ -3,11 +3,10 @@ * @license MIT */ -import { IDisposable } from 'xterm'; -import { ITerminal } from './Types'; import { CHAR_DATA_CODE_INDEX, NULL_CELL_CODE, WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; +import { IBufferService } from 'common/services/Services'; -export function applyWindowsMode(terminal: ITerminal): IDisposable { +export function handleWindowsModeLineFeed(bufferService: IBufferService): void { // Winpty does not support wraparound mode which means that lines will never // be marked as wrapped. This causes issues for things like copying a line // retaining the wrapped new line characters or if consumers are listening @@ -18,11 +17,11 @@ export function applyWindowsMode(terminal: ITerminal): IDisposable { // space. This is certainly not without its problems, but generally on // Windows when text reaches the end of the terminal it's likely going to be // wrapped. - return terminal.onLineFeed(() => { - const line = terminal.buffer.lines.get(terminal.buffer.ybase + terminal.buffer.y - 1); - const lastChar = line.get(terminal.cols - 1); + const line = bufferService.buffer.lines.get(bufferService.buffer.ybase + bufferService.buffer.y - 1); + const lastChar = line?.get(bufferService.cols - 1); - const nextLine = terminal.buffer.lines.get(terminal.buffer.ybase + terminal.buffer.y); + const nextLine = bufferService.buffer.lines.get(bufferService.buffer.ybase + bufferService.buffer.y); + if (nextLine && lastChar) { nextLine.isWrapped = (lastChar[CHAR_DATA_CODE_INDEX] !== NULL_CELL_CODE && lastChar[CHAR_DATA_CODE_INDEX] !== WHITESPACE_CELL_CODE); - }); + } } From c7ad4aa460710aa2062b2bcf06b015525a9aa3fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 15 Nov 2019 13:33:28 +0100 Subject: [PATCH 116/268] terminal option unicodeVersion --- demo/client.ts | 3 ++- src/InputHandler.ts | 12 +++++++++--- src/Terminal.test.ts | 4 ++-- src/browser/Linkifier.ts | 4 ++-- src/common/CharWidth.test.ts | 4 ++-- src/common/CharWidth.ts | 20 ++++++++++---------- src/common/services/OptionsService.ts | 3 ++- src/common/services/Services.ts | 2 ++ 8 files changed, 31 insertions(+), 21 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 5b76700ea..51c16d50d 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -256,7 +256,8 @@ function initOptions(term: TerminalType): void { fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], logLevel: ['debug', 'info', 'warn', 'error', 'off'], rendererType: ['dom', 'canvas'], - wordSeparator: null + wordSeparator: null, + unicodeVersion: ['6', '10'] }; const options = Object.keys((term)._core.options); const booleanOptions = []; diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 28d17b3e6..d2ad6cd31 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -7,7 +7,7 @@ import { IInputHandler, IInputHandlingTerminal } from './Types'; import { C0, C1 } from 'common/data/EscapeSequences'; import { CHARSETS, DEFAULT_CHARSET } from 'common/data/Charsets'; -import { wcwidth, wcwidth10 } from 'common/CharWidth'; +import { wcwidthV6, wcwidthV10 } from 'common/CharWidth'; import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser'; import { Disposable } from 'common/Lifecycle'; import { concat } from 'common/TypedArrayUtils'; @@ -127,6 +127,7 @@ export class InputHandler extends Disposable implements IInputHandler { private _stringDecoder: StringToUtf32 = new StringToUtf32(); private _utf8Decoder: Utf8ToUtf32 = new Utf8ToUtf32(); private _workCell: CellData = new CellData(); + private _wcwidth: (codepoint: number) => number; private _onCursorMove = new EventEmitter(); public get onCursorMove(): IEvent { return this._onCursorMove.event; } @@ -146,6 +147,12 @@ export class InputHandler extends Disposable implements IInputHandler { private readonly _parser: IEscapeSequenceParser = new EscapeSequenceParser()) { super(); + this._wcwidth = this._optionsService.options.unicodeVersion === '10' ? wcwidthV10 : wcwidthV6; + this._optionsService.onOptionChange(option => { + if (option === 'unicodeVersion') { + this._wcwidth = this._optionsService.options.unicodeVersion === '10' ? wcwidthV10 : wcwidthV6; + } + }); this.register(this._parser); @@ -393,8 +400,7 @@ export class InputHandler extends Disposable implements IInputHandler { // calculate print space // expensive call, therefore we save width in line buffer - chWidth = wcwidth(code); - chWidth = wcwidth10(code); + chWidth = this._wcwidth(code); // get charset replacement character // charset is only defined for ASCII, therefore we only diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index b55a516b2..2335f0f63 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -7,7 +7,7 @@ import { assert, expect } from 'chai'; import { MockViewport, MockCompositionHelper, MockRenderer, TestTerminal } from './TestUtils.test'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { CellData } from 'common/buffer/CellData'; -import { wcwidth } from 'common/CharWidth'; +import { wcwidthV6 } from 'common/CharWidth'; import { IBufferService } from 'common/services/Services'; import { Linkifier } from 'browser/Linkifier'; import { MockLogService } from 'common/TestUtils.test'; @@ -762,7 +762,7 @@ describe('Terminal', () => { for (let i = 0xDC00; i <= 0xDCFF; ++i) { term.buffer.x = term.cols - 1; term.wraparoundMode = false; - const width = wcwidth((0xD800 - 0xD800) * 0x400 + i - 0xDC00 + 0x10000); + const width = wcwidthV6((0xD800 - 0xD800) * 0x400 + i - 0xDC00 + 0x10000); if (width !== 1) { continue; } diff --git a/src/browser/Linkifier.ts b/src/browser/Linkifier.ts index 58aca70eb..f51ea1ca2 100644 --- a/src/browser/Linkifier.ts +++ b/src/browser/Linkifier.ts @@ -5,7 +5,7 @@ import { ILinkifierEvent, ILinkMatcher, LinkMatcherHandler, ILinkMatcherOptions, ILinkifier, IMouseZoneManager, IMouseZone, IRegisteredLinkMatcher } from 'browser/Types'; import { IBufferStringIteratorResult } from 'common/buffer/Types'; -import { getStringCellWidth } from 'common/CharWidth'; +import { getStringCellWidthV6 } from 'common/CharWidth'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { ILogService, IBufferService } from 'common/services/Services'; @@ -278,7 +278,7 @@ export class Linkifier implements ILinkifier { return; } - const width = getStringCellWidth(uri); + const width = getStringCellWidthV6(uri); // FIXME: apply options.unicodeVersion const x1 = x % this._bufferService.cols; const y1 = y + Math.floor(x / this._bufferService.cols); let x2 = (x1 + width) % this._bufferService.cols; diff --git a/src/common/CharWidth.test.ts b/src/common/CharWidth.test.ts index 6b2493eef..8a857424e 100644 --- a/src/common/CharWidth.test.ts +++ b/src/common/CharWidth.test.ts @@ -4,7 +4,7 @@ */ import { assert } from 'chai'; -import { wcwidth } from 'common/CharWidth'; +import { wcwidthV6 } from 'common/CharWidth'; it('wcwidth should match all values from the old implementation', function(): void { // old implementation @@ -177,6 +177,6 @@ it('wcwidth should match all values from the old implementation', function(): vo // test full BMP range old vs new implmenetation for (let i = 0; i < 65536; ++i) { - assert.equal(wcwidth(i), wcwidthOld(i), `mismatch for i: ${i}`); + assert.equal(wcwidthV6(i), wcwidthOld(i), `mismatch for i: ${i}`); } }); diff --git a/src/common/CharWidth.ts b/src/common/CharWidth.ts index ae826c89e..b03f74ad4 100644 --- a/src/common/CharWidth.ts +++ b/src/common/CharWidth.ts @@ -5,7 +5,7 @@ import { fill } from 'common/TypedArrayUtils'; -export const wcwidth = (function(opts: {nul: number, control: number}): (ucs: number) => number { +export const wcwidthV6 = (function(opts: {nul: number, control: number}): (ucs: number) => number { // extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c // combining characters const COMBINING_BMP = [ @@ -141,7 +141,7 @@ export const wcwidth = (function(opts: {nul: number, control: number}): (ucs: nu /** * Get the terminal cell width for a string. */ -export function getStringCellWidth(s: string): number { +export function getStringCellWidthV6(s: string): number { let result = 0; const length = s.length; for (let i = 0; i < length; ++i) { @@ -154,7 +154,7 @@ export function getStringCellWidth(s: string): number { // and therefore always should contain the second part // for any other string we still have to handle it somehow: // simply treat the lonely surrogate first as a single char (UCS-2 behavior) - return result + wcwidth(code); + return result + wcwidthV6(code); } const second = s.charCodeAt(i); // convert surrogate pair to high codepoint only for valid second part (UTF-16) @@ -162,10 +162,10 @@ export function getStringCellWidth(s: string): number { if (0xDC00 <= second && second <= 0xDFFF) { code = (code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; } else { - result += wcwidth(second); + result += wcwidthV6(second); } } - result += wcwidth(code); + result += wcwidthV6(code); } return result; } @@ -377,7 +377,7 @@ for (let r = 0; r < WIDE_BMP.length; ++r) { fill(TABLE, 2, WIDE_BMP[r][0], WIDE_BMP[r][1] + 1); } -export function wcwidth10(num: number): number { +export function wcwidthV10(num: number): number { if (num < 32) { return 0; } @@ -390,7 +390,7 @@ export function wcwidth10(num: number): number { return wcwidthHigh(num); }; -export function getStringCellWidth10(s: string): number { +export function getStringCellWidthV10(s: string): number { let result = 0; const length = s.length; for (let i = 0; i < length; ++i) { @@ -403,7 +403,7 @@ export function getStringCellWidth10(s: string): number { // and therefore always should contain the second part // for any other string we still have to handle it somehow: // simply treat the lonely surrogate first as a single char (UCS-2 behavior) - return result + wcwidth10(code); + return result + wcwidthV10(code); } const second = s.charCodeAt(i); // convert surrogate pair to high codepoint only for valid second part (UTF-16) @@ -411,10 +411,10 @@ export function getStringCellWidth10(s: string): number { if (0xDC00 <= second && second <= 0xDFFF) { code = (code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; } else { - result += wcwidth10(second); + result += wcwidthV10(second); } } - result += wcwidth10(code); + result += wcwidthV10(code); } return result; } diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index d9ea60d5a..ccd38d47f 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -50,7 +50,8 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({ screenKeys: false, cancelEvents: false, useFlowControl: false, - wordSeparator: ' ()[]{}\',:;"' + wordSeparator: ' ()[]{}\',:;"', + unicodeVersion: '6' }); /** diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 45d39a6af..380bc6c40 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -212,6 +212,7 @@ export interface IPartialTerminalOptions { scrollSensitivity?: number; tabStopWidth?: number; theme?: ITheme; + unicodeVersion?: '6' | '10'; windowsMode?: boolean; wordSeparator?: string; } @@ -244,6 +245,7 @@ export interface ITerminalOptions { scrollSensitivity: number; tabStopWidth: number; theme: ITheme; + unicodeVersion: '6' | '10'; windowsMode: boolean; wordSeparator: string; From 25bbde2be26cf6dc29bc11411b641efa9a1ebe31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 15 Nov 2019 14:47:51 +0100 Subject: [PATCH 117/268] cleanup CharWidth.ts --- src/common/CharWidth.ts | 364 ++++++++++++++++------------------------ 1 file changed, 146 insertions(+), 218 deletions(-) diff --git a/src/common/CharWidth.ts b/src/common/CharWidth.ts index b03f74ad4..852b99afc 100644 --- a/src/common/CharWidth.ts +++ b/src/common/CharWidth.ts @@ -5,143 +5,28 @@ import { fill } from 'common/TypedArrayUtils'; -export const wcwidthV6 = (function(opts: {nul: number, control: number}): (ucs: number) => number { - // extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c - // combining characters - const COMBINING_BMP = [ - [0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489], - [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2], - [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603], - [0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670], - [0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED], - [0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A], - [0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902], - [0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D], - [0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981], - [0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD], - [0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C], - [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D], - [0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC], - [0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD], - [0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C], - [0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D], - [0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0], - [0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48], - [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC], - [0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD], - [0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D], - [0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6], - [0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E], - [0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC], - [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35], - [0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E], - [0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97], - [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030], - [0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039], - [0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F], - [0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753], - [0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD], - [0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD], - [0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922], - [0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B], - [0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34], - [0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42], - [0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF], - [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063], - [0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F], - [0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B], - [0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F], - [0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB] - ]; - const COMBINING_HIGH = [ - [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F], - [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169], - [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD], - [0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F], - [0xE0100, 0xE01EF] - ]; - // binary search - function bisearch(ucs: number, data: number[][]): boolean { - let min = 0; - let max = data.length - 1; - let mid; - if (ucs < data[0][0] || ucs > data[max][1]) { - return false; - } - while (max >= min) { - mid = (min + max) >> 1; - if (ucs > data[mid][1]) { - min = mid + 1; - } else if (ucs < data[mid][0]) { - max = mid - 1; - } else { - return true; - } - } +// helper: binary search +function bisearch(ucs: number, data: number[][]): boolean { + let min = 0; + let max = data.length - 1; + let mid; + if (ucs < data[0][0] || ucs > data[max][1]) { return false; } - function wcwidthHigh(ucs: number): 0 | 1 | 2 { - if (bisearch(ucs, COMBINING_HIGH)) { - return 0; + while (max >= min) { + mid = (min + max) >> 1; + if (ucs > data[mid][1]) { + min = mid + 1; + } else if (ucs < data[mid][0]) { + max = mid - 1; + } else { + return true; } - if ((ucs >= 0x20000 && ucs <= 0x2fffd) || (ucs >= 0x30000 && ucs <= 0x3fffd)) { - return 2; - } - return 1; } - const control = opts.control | 0; + return false; +} - // create lookup table for BMP plane - const table = new Uint8Array(65536); - fill(table, 1); - table[0] = opts.nul; - // control chars - fill(table, opts.control, 1, 32); - fill(table, opts.control, 0x7f, 0xa0); - - // apply wide char rules first - // wide chars - fill(table, 2, 0x1100, 0x1160); - table[0x2329] = 2; - table[0x232a] = 2; - fill(table, 2, 0x2e80, 0xa4d0); - table[0x303f] = 1; // wrongly in last line - - fill(table, 2, 0xac00, 0xd7a4); - fill(table, 2, 0xf900, 0xfb00); - fill(table, 2, 0xfe10, 0xfe1a); - fill(table, 2, 0xfe30, 0xfe70); - fill(table, 2, 0xff00, 0xff61); - fill(table, 2, 0xffe0, 0xffe7); - - // apply combining last to ensure we overwrite - // wrongly wide set chars: - // the original algo evals combining first and falls - // through to wide check so we simply do here the opposite - // combining 0 - for (let r = 0; r < COMBINING_BMP.length; ++r) { - fill(table, 0, COMBINING_BMP[r][0], COMBINING_BMP[r][1] + 1); - } - - return function (num: number): number { - if (num < 32) { - return control | 0; - } - if (num < 127) { - return 1; - } - if (num < 65536) { - return table[num]; - } - // do a full search for high codepoints - return wcwidthHigh(num); - }; -})({nul: 0, control: 0}); // configurable options - -/** - * Get the terminal cell width for a string. - */ -export function getStringCellWidthV6(s: string): number { +function getStringCellWidthPrivate(wcwidthImpl: (cp: number) => number, s: string): number { let result = 0; const length = s.length; for (let i = 0; i < length; ++i) { @@ -154,7 +39,7 @@ export function getStringCellWidthV6(s: string): number { // and therefore always should contain the second part // for any other string we still have to handle it somehow: // simply treat the lonely surrogate first as a single char (UCS-2 behavior) - return result + wcwidthV6(code); + return result + wcwidthImpl(code); } const second = s.charCodeAt(i); // convert surrogate pair to high codepoint only for valid second part (UTF-16) @@ -162,17 +47,119 @@ export function getStringCellWidthV6(s: string): number { if (0xDC00 <= second && second <= 0xDFFF) { code = (code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; } else { - result += wcwidthV6(second); + result += wcwidthImpl(second); } } - result += wcwidthV6(code); + result += wcwidthImpl(code); } return result; } +/** + * Unicode version 6 + */ +const BMP_COMBINING_V6 = [ + [0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489], + [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2], + [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603], + [0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670], + [0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED], + [0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A], + [0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902], + [0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D], + [0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981], + [0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD], + [0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C], + [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D], + [0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC], + [0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD], + [0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C], + [0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D], + [0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0], + [0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48], + [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC], + [0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD], + [0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D], + [0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6], + [0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E], + [0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC], + [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35], + [0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E], + [0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97], + [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030], + [0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039], + [0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F], + [0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753], + [0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD], + [0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD], + [0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922], + [0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B], + [0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34], + [0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42], + [0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF], + [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063], + [0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F], + [0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B], + [0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F], + [0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB] +]; +const HIGH_COMBINING_V6 = [ + [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F], + [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169], + [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD], + [0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F], + [0xE0100, 0xE01EF] +]; +// create lookup table for BMP plane +const BMP_TABLE_V6 = new Uint8Array(65536); +fill(BMP_TABLE_V6, 1); +BMP_TABLE_V6[0] = 0; +// control chars +fill(BMP_TABLE_V6, 0, 1, 32); +fill(BMP_TABLE_V6, 0, 0x7f, 0xa0); -const COMBINING_BMP = [ +// apply wide char rules first +// wide chars +fill(BMP_TABLE_V6, 2, 0x1100, 0x1160); +BMP_TABLE_V6[0x2329] = 2; +BMP_TABLE_V6[0x232a] = 2; +fill(BMP_TABLE_V6, 2, 0x2e80, 0xa4d0); +BMP_TABLE_V6[0x303f] = 1; // wrongly in last line + +fill(BMP_TABLE_V6, 2, 0xac00, 0xd7a4); +fill(BMP_TABLE_V6, 2, 0xf900, 0xfb00); +fill(BMP_TABLE_V6, 2, 0xfe10, 0xfe1a); +fill(BMP_TABLE_V6, 2, 0xfe30, 0xfe70); +fill(BMP_TABLE_V6, 2, 0xff00, 0xff61); +fill(BMP_TABLE_V6, 2, 0xffe0, 0xffe7); + +// apply combining last to ensure we overwrite +// wrongly wide set chars: +// the original algo evals combining first and falls +// through to wide check so we simply do here the opposite +// combining 0 +for (let r = 0; r < BMP_COMBINING_V6.length; ++r) { + fill(BMP_TABLE_V6, 0, BMP_COMBINING_V6[r][0], BMP_COMBINING_V6[r][1] + 1); +} + +export function wcwidthV6(num: number): number { + if (num < 32) return 0; + if (num < 127) return 1; + if (num < 65536) return BMP_TABLE_V6[num]; + if (bisearch(num, HIGH_COMBINING_V6)) return 0; + if ((num >= 0x20000 && num <= 0x2fffd) || (num >= 0x30000 && num <= 0x3fffd)) return 2; + return 1; +} + +export function getStringCellWidthV6(s: string): number { + return getStringCellWidthPrivate(wcwidthV6, s); +} + +/** + * Unicode version 10 + */ +const BMP_COMBINING_V10 = [ [0x0300, 0x036F], [0x0483, 0x0489], [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2], [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0605], [0x0610, 0x061A], @@ -244,10 +231,10 @@ const COMBINING_BMP = [ [0xAAEC, 0xAAED], [0xAAF6, 0xAAF6], [0xABE5, 0xABE5], [0xABE8, 0xABE8], [0xABED, 0xABED], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F], [0xFE20, 0xFE2F], [0xFEFF, 0xFEFF], - [0xFFF9, 0xFFFB] + [0xFFF9, 0xFFFB] ]; - -const COMBINING_HIGH = [ + +const HIGH_COMBINING_V10 = [ [0x101FD, 0x101FD], [0x102E0, 0x102E0], [0x10376, 0x1037A], [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F], [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], @@ -290,7 +277,7 @@ const COMBINING_HIGH = [ [0xE0100, 0xE01EF] ]; -const WIDE_BMP = [ +const BMP_WIDE_V10 = [ [0x1100, 0x115F], [0x231A, 0x231B], [0x2329, 0x232A], [0x23E9, 0x23EC], [0x23F0, 0x23F0], [0x23F3, 0x23F3], [0x25FD, 0x25FE], [0x2614, 0x2615], [0x2648, 0x2653], @@ -313,8 +300,8 @@ const WIDE_BMP = [ [0xFE54, 0xFE66], [0xFE68, 0xFE6B], [0xFF01, 0xFF60], [0xFFE0, 0xFFE6] ]; - -const WIDE_HIGH = [ + +const HIGH_WIDE_V10 = [ [0x16FE0, 0x16FE3], [0x17000, 0x187F7], [0x18800, 0x18AF2], [0x1B000, 0x1B11E], [0x1B150, 0x1B152], [0x1B164, 0x1B167], [0x1B170, 0x1B2FB], [0x1F004, 0x1F004], @@ -335,86 +322,27 @@ const WIDE_HIGH = [ [0x1FA90, 0x1FA95], [0x20000, 0x2FFFD], [0x30000, 0x3FFFD] ]; -function bisearch(ucs: number, data: number[][]): boolean { - let min = 0; - let max = data.length - 1; - let mid; - if (ucs < data[0][0] || ucs > data[max][1]) { - return false; - } - while (max >= min) { - mid = (min + max) >> 1; - if (ucs > data[mid][1]) { - min = mid + 1; - } else if (ucs < data[mid][0]) { - max = mid - 1; - } else { - return true; - } - } - return false; +const BMP_TABLE_V10 = new Uint8Array(65536); +fill(BMP_TABLE_V10, 1); +BMP_TABLE_V10[0] = 0; +fill(BMP_TABLE_V10, 0, 1, 32); +fill(BMP_TABLE_V10, 0, 0x7f, 0xa0); +for (let r = 0; r < BMP_COMBINING_V10.length; ++r) { + fill(BMP_TABLE_V10, 0, BMP_COMBINING_V10[r][0], BMP_COMBINING_V10[r][1] + 1); } - -function wcwidthHigh(ucs: number): 0 | 1 | 2 { - if (bisearch(ucs, COMBINING_HIGH)) { - return 0; - } - if (bisearch(ucs, WIDE_HIGH)) { - return 2; - } - return 1; -} - - -const TABLE = new Uint8Array(65536); -fill(TABLE, 1); -fill(TABLE, 0, 1, 32); -fill(TABLE, 0, 0x7f, 0xa0); -for (let r = 0; r < COMBINING_BMP.length; ++r) { - fill(TABLE, 0, COMBINING_BMP[r][0], COMBINING_BMP[r][1] + 1); -} -for (let r = 0; r < WIDE_BMP.length; ++r) { - fill(TABLE, 2, WIDE_BMP[r][0], WIDE_BMP[r][1] + 1); +for (let r = 0; r < BMP_WIDE_V10.length; ++r) { + fill(BMP_TABLE_V10, 2, BMP_WIDE_V10[r][0], BMP_WIDE_V10[r][1] + 1); } export function wcwidthV10(num: number): number { - if (num < 32) { - return 0; - } - if (num < 127) { - return 1; - } - if (num < 65536) { - return TABLE[num]; - } - return wcwidthHigh(num); -}; + if (num < 32) return 0; + if (num < 127) return 1; + if (num < 65536) return BMP_TABLE_V10[num]; + if (bisearch(num, HIGH_COMBINING_V10)) return 0; + if (bisearch(num, HIGH_WIDE_V10)) return 2; + return 1; +} export function getStringCellWidthV10(s: string): number { - let result = 0; - const length = s.length; - for (let i = 0; i < length; ++i) { - let code = s.charCodeAt(i); - // surrogate pair first - if (0xD800 <= code && code <= 0xDBFF) { - if (++i >= length) { - // this should not happen with strings retrieved from - // Buffer.translateToString as it converts from UTF-32 - // and therefore always should contain the second part - // for any other string we still have to handle it somehow: - // simply treat the lonely surrogate first as a single char (UCS-2 behavior) - return result + wcwidthV10(code); - } - const second = s.charCodeAt(i); - // convert surrogate pair to high codepoint only for valid second part (UTF-16) - // otherwise treat them independently (UCS-2 behavior) - if (0xDC00 <= second && second <= 0xDFFF) { - code = (code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; - } else { - result += wcwidthV10(second); - } - } - result += wcwidthV10(code); - } - return result; + return getStringCellWidthPrivate(wcwidthV10, s); } From aca6d24e964419a6b292a0fc36ddeaba6c04af4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 15 Nov 2019 18:08:17 +0100 Subject: [PATCH 118/268] change to v11, expose unicodeVersion, fix linkifier --- demo/client.ts | 2 +- src/InputHandler.ts | 6 +++--- src/Terminal.test.ts | 4 ++-- src/Terminal.ts | 2 +- src/browser/Linkifier.test.ts | 4 ++-- src/browser/Linkifier.ts | 10 ++++----- src/common/CharWidth.test.ts | 9 +++++++- src/common/CharWidth.ts | 38 ++++++++++++++++----------------- src/common/services/Services.ts | 4 ++-- typings/xterm.d.ts | 5 +++++ 10 files changed, 48 insertions(+), 36 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 51c16d50d..347a9d65c 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -257,7 +257,7 @@ function initOptions(term: TerminalType): void { logLevel: ['debug', 'info', 'warn', 'error', 'off'], rendererType: ['dom', 'canvas'], wordSeparator: null, - unicodeVersion: ['6', '10'] + unicodeVersion: ['6', '11'] }; const options = Object.keys((term)._core.options); const booleanOptions = []; diff --git a/src/InputHandler.ts b/src/InputHandler.ts index d2ad6cd31..e439a7d97 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -7,7 +7,7 @@ import { IInputHandler, IInputHandlingTerminal } from './Types'; import { C0, C1 } from 'common/data/EscapeSequences'; import { CHARSETS, DEFAULT_CHARSET } from 'common/data/Charsets'; -import { wcwidthV6, wcwidthV10 } from 'common/CharWidth'; +import { wcwidthV6, wcwidthV11 } from 'common/CharWidth'; import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser'; import { Disposable } from 'common/Lifecycle'; import { concat } from 'common/TypedArrayUtils'; @@ -147,10 +147,10 @@ export class InputHandler extends Disposable implements IInputHandler { private readonly _parser: IEscapeSequenceParser = new EscapeSequenceParser()) { super(); - this._wcwidth = this._optionsService.options.unicodeVersion === '10' ? wcwidthV10 : wcwidthV6; + this._wcwidth = this._optionsService.options.unicodeVersion === '11' ? wcwidthV11 : wcwidthV6; this._optionsService.onOptionChange(option => { if (option === 'unicodeVersion') { - this._wcwidth = this._optionsService.options.unicodeVersion === '10' ? wcwidthV10 : wcwidthV6; + this._wcwidth = this._optionsService.options.unicodeVersion === '11' ? wcwidthV11 : wcwidthV6; } }); diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index 2335f0f63..5f7f0721b 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -10,7 +10,7 @@ import { CellData } from 'common/buffer/CellData'; import { wcwidthV6 } from 'common/CharWidth'; import { IBufferService } from 'common/services/Services'; import { Linkifier } from 'browser/Linkifier'; -import { MockLogService } from 'common/TestUtils.test'; +import { MockLogService, MockOptionsService } from 'common/TestUtils.test'; import { IRegisteredLinkMatcher, IMouseZoneManager, IMouseZone } from 'browser/Types'; const INIT_COLS = 80; @@ -1371,7 +1371,7 @@ describe('Terminal', () => { class TestLinkifier extends Linkifier { constructor(bufferService: IBufferService) { - super(bufferService, new MockLogService()); + super(bufferService, new MockLogService(), new MockOptionsService()); Linkifier._timeBeforeLatency = 0; } diff --git a/src/Terminal.ts b/src/Terminal.ts index 6a039a0ed..b4c0e4e6a 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -281,7 +281,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._inputHandler.onLineFeed(() => this._onLineFeed.fire()); this.register(this._inputHandler); - this.linkifier = this.linkifier || new Linkifier(this._bufferService, this._logService); + this.linkifier = this.linkifier || new Linkifier(this._bufferService, this._logService, this.optionsService); if (this.options.windowsMode) { this._windowsMode = applyWindowsMode(this); diff --git a/src/browser/Linkifier.test.ts b/src/browser/Linkifier.test.ts index 128f786d9..a4b4d2f55 100644 --- a/src/browser/Linkifier.test.ts +++ b/src/browser/Linkifier.test.ts @@ -9,12 +9,12 @@ import { IBufferLine } from 'common/Types'; import { Linkifier } from 'browser/Linkifier'; import { BufferLine } from 'common/buffer/BufferLine'; import { CellData } from 'common/buffer/CellData'; -import { MockLogService, MockBufferService } from 'common/TestUtils.test'; +import { MockLogService, MockBufferService, MockOptionsService } from 'common/TestUtils.test'; import { IBufferService } from 'common/services/Services'; class TestLinkifier extends Linkifier { constructor(bufferService: IBufferService) { - super(bufferService, new MockLogService()); + super(bufferService, new MockLogService(), new MockOptionsService()); Linkifier._timeBeforeLatency = 0; } diff --git a/src/browser/Linkifier.ts b/src/browser/Linkifier.ts index f51ea1ca2..072f79721 100644 --- a/src/browser/Linkifier.ts +++ b/src/browser/Linkifier.ts @@ -5,9 +5,9 @@ import { ILinkifierEvent, ILinkMatcher, LinkMatcherHandler, ILinkMatcherOptions, ILinkifier, IMouseZoneManager, IMouseZone, IRegisteredLinkMatcher } from 'browser/Types'; import { IBufferStringIteratorResult } from 'common/buffer/Types'; -import { getStringCellWidthV6 } from 'common/CharWidth'; +import { getStringCellWidthV6, getStringCellWidthV11 } from 'common/CharWidth'; import { EventEmitter, IEvent } from 'common/EventEmitter'; -import { ILogService, IBufferService } from 'common/services/Services'; +import { ILogService, IBufferService, IOptionsService } from 'common/services/Services'; /** * Limit of the unwrapping line expansion (overscan) at the top and bottom @@ -45,7 +45,8 @@ export class Linkifier implements ILinkifier { constructor( protected readonly _bufferService: IBufferService, - private readonly _logService: ILogService + private readonly _logService: ILogService, + private readonly _optionsService: IOptionsService ) { this._rowsToLinkify = { start: undefined, @@ -277,8 +278,7 @@ export class Linkifier implements ILinkifier { if (!this._mouseZoneManager || !this._element) { return; } - - const width = getStringCellWidthV6(uri); // FIXME: apply options.unicodeVersion + const width = this._optionsService.options.unicodeVersion === '11' ? getStringCellWidthV11(uri) : getStringCellWidthV6(uri); const x1 = x % this._bufferService.cols; const y1 = y + Math.floor(x / this._bufferService.cols); let x2 = (x1 + width) % this._bufferService.cols; diff --git a/src/common/CharWidth.test.ts b/src/common/CharWidth.test.ts index 8a857424e..ff6eb3a61 100644 --- a/src/common/CharWidth.test.ts +++ b/src/common/CharWidth.test.ts @@ -4,7 +4,7 @@ */ import { assert } from 'chai'; -import { wcwidthV6 } from 'common/CharWidth'; +import { wcwidthV6, getStringCellWidthV6, getStringCellWidthV11 } from 'common/CharWidth'; it('wcwidth should match all values from the old implementation', function(): void { // old implementation @@ -180,3 +180,10 @@ it('wcwidth should match all values from the old implementation', function(): vo assert.equal(wcwidthV6(i), wcwidthOld(i), `mismatch for i: ${i}`); } }); + +it('wcwidth V6 vs. V11', () => { + const widthV6 = getStringCellWidthV6('🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣'); + assert.equal(widthV6, 10); + const widthV10 = getStringCellWidthV11('🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣'); + assert.equal(widthV10, 20); +}); diff --git a/src/common/CharWidth.ts b/src/common/CharWidth.ts index 852b99afc..ca7cd2d10 100644 --- a/src/common/CharWidth.ts +++ b/src/common/CharWidth.ts @@ -159,7 +159,7 @@ export function getStringCellWidthV6(s: string): number { /** * Unicode version 10 */ -const BMP_COMBINING_V10 = [ +const BMP_COMBINING_V11 = [ [0x0300, 0x036F], [0x0483, 0x0489], [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2], [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0605], [0x0610, 0x061A], @@ -234,7 +234,7 @@ const BMP_COMBINING_V10 = [ [0xFFF9, 0xFFFB] ]; -const HIGH_COMBINING_V10 = [ +const HIGH_COMBINING_V11 = [ [0x101FD, 0x101FD], [0x102E0, 0x102E0], [0x10376, 0x1037A], [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F], [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], @@ -277,7 +277,7 @@ const HIGH_COMBINING_V10 = [ [0xE0100, 0xE01EF] ]; -const BMP_WIDE_V10 = [ +const BMP_WIDE_V11 = [ [0x1100, 0x115F], [0x231A, 0x231B], [0x2329, 0x232A], [0x23E9, 0x23EC], [0x23F0, 0x23F0], [0x23F3, 0x23F3], [0x25FD, 0x25FE], [0x2614, 0x2615], [0x2648, 0x2653], @@ -301,7 +301,7 @@ const BMP_WIDE_V10 = [ [0xFFE0, 0xFFE6] ]; -const HIGH_WIDE_V10 = [ +const HIGH_WIDE_V11 = [ [0x16FE0, 0x16FE3], [0x17000, 0x187F7], [0x18800, 0x18AF2], [0x1B000, 0x1B11E], [0x1B150, 0x1B152], [0x1B164, 0x1B167], [0x1B170, 0x1B2FB], [0x1F004, 0x1F004], @@ -322,27 +322,27 @@ const HIGH_WIDE_V10 = [ [0x1FA90, 0x1FA95], [0x20000, 0x2FFFD], [0x30000, 0x3FFFD] ]; -const BMP_TABLE_V10 = new Uint8Array(65536); -fill(BMP_TABLE_V10, 1); -BMP_TABLE_V10[0] = 0; -fill(BMP_TABLE_V10, 0, 1, 32); -fill(BMP_TABLE_V10, 0, 0x7f, 0xa0); -for (let r = 0; r < BMP_COMBINING_V10.length; ++r) { - fill(BMP_TABLE_V10, 0, BMP_COMBINING_V10[r][0], BMP_COMBINING_V10[r][1] + 1); +const BMP_TABLE_V11 = new Uint8Array(65536); +fill(BMP_TABLE_V11, 1); +BMP_TABLE_V11[0] = 0; +fill(BMP_TABLE_V11, 0, 1, 32); +fill(BMP_TABLE_V11, 0, 0x7f, 0xa0); +for (let r = 0; r < BMP_COMBINING_V11.length; ++r) { + fill(BMP_TABLE_V11, 0, BMP_COMBINING_V11[r][0], BMP_COMBINING_V11[r][1] + 1); } -for (let r = 0; r < BMP_WIDE_V10.length; ++r) { - fill(BMP_TABLE_V10, 2, BMP_WIDE_V10[r][0], BMP_WIDE_V10[r][1] + 1); +for (let r = 0; r < BMP_WIDE_V11.length; ++r) { + fill(BMP_TABLE_V11, 2, BMP_WIDE_V11[r][0], BMP_WIDE_V11[r][1] + 1); } -export function wcwidthV10(num: number): number { +export function wcwidthV11(num: number): number { if (num < 32) return 0; if (num < 127) return 1; - if (num < 65536) return BMP_TABLE_V10[num]; - if (bisearch(num, HIGH_COMBINING_V10)) return 0; - if (bisearch(num, HIGH_WIDE_V10)) return 2; + if (num < 65536) return BMP_TABLE_V11[num]; + if (bisearch(num, HIGH_COMBINING_V11)) return 0; + if (bisearch(num, HIGH_WIDE_V11)) return 2; return 1; } -export function getStringCellWidthV10(s: string): number { - return getStringCellWidthPrivate(wcwidthV10, s); +export function getStringCellWidthV11(s: string): number { + return getStringCellWidthPrivate(wcwidthV11, s); } diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 380bc6c40..b86c635e6 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -212,7 +212,7 @@ export interface IPartialTerminalOptions { scrollSensitivity?: number; tabStopWidth?: number; theme?: ITheme; - unicodeVersion?: '6' | '10'; + unicodeVersion?: '6' | '11'; windowsMode?: boolean; wordSeparator?: string; } @@ -245,7 +245,7 @@ export interface ITerminalOptions { scrollSensitivity: number; tabStopWidth: number; theme: ITheme; - unicodeVersion: '6' | '10'; + unicodeVersion: '6' | '11'; windowsMode: boolean; wordSeparator: string; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index a086d030b..4cd235447 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -198,6 +198,11 @@ declare module 'xterm' { */ theme?: ITheme; + /** + * Unicode version to be used for wcwidth. Default is version 6. + */ + unicodeVersion?: '6' | '11'; + /** * Whether "Windows mode" is enabled. Because Windows backends winpty and * conpty operate by doing line wrapping on their side, xterm.js does not From a069df7a369512a1e2ea5c850dacee9ebe8396ec Mon Sep 17 00:00:00 2001 From: javacs3 Date: Sun, 4 Aug 2019 23:02:05 +0800 Subject: [PATCH 119/268] 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 9328e6748..4a051c338 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 5539aa564..57f3d6edf 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 120/268] 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 4a051c338..44438c983 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 121/268] 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 5543696ba..eec5b6fcf 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 44438c983..88ea852f1 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 276a5c543..81ac773ad 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 122/268] 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 1ca35a6a1..91c5d33d2 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 ef8f3891c..4a4335eed 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 b061dfcbb..127282890 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 123/268] 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 eec5b6fcf..79343fbe3 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 88ea852f1..564bbddc1 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 124/268] 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 4a4335eed..0fee6f4a9 100644 --- a/demo/index.html +++ b/demo/index.html @@ -23,8 +23,8 @@

SerializeAddon

- - + +
diff --git a/demo/style.css b/demo/style.css index 127282890..1ab5dc3cb 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 125/268] 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 79343fbe3..6fbf9af2b 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 564bbddc1..6143d94f8 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 126/268] 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 6143d94f8..ae72ce4be 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 5d270ca71..20ecf7e90 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 d9e28b265..1ec966741 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 127/268] 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 ae72ce4be..0332477f8 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 20ecf7e90..377f7acd6 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 1ec966741..4503e1c17 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 128/268] 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 0332477f8..b5d6e08d4 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 377f7acd6..61b06b51e 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 4503e1c17..afdfa3e4b 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 129/268] 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 a054e939c..64f41805f 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 2bf76f670..28ea52623 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 61b06b51e..e1fb8d6d6 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 130/268] 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 e1fb8d6d6..28758642a 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 afdfa3e4b..ae11a8f1e 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 131/268] 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 b5d6e08d4..b8b57b6b5 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 28758642a..baae367cb 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 ae11a8f1e..6fcc1e536 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 132/268] 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 b8b57b6b5..0fa5c3dd7 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 baae367cb..e1aef88e1 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 6fcc1e536..b0415350c 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 133/268] 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 0fa5c3dd7..c68abab7e 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 68467a89c..57af066d8 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 000000000..9423f4000 --- /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 8b93dcb13..51abeba59 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 a67ede71d..30f2ea792 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 134/268] 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 e1aef88e1..9f3aca877 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 135/268] 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 c68abab7e..66796ecdb 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 57af066d8..19ccf0d9a 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 9f3aca877..540ef6389 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 b0415350c..5043b8670 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 136/268] 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 66796ecdb..b00718658 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 540ef6389..0cfd05602 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 93487d4fb373602686ab0e874af1824287eafbae Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 22 Nov 2019 09:34:06 -0800 Subject: [PATCH 137/268] Direct question issues to Stack Overflow --- .github/ISSUE_TEMPLATE/config.yml | 5 +++++ .github/ISSUE_TEMPLATE/question.md | 8 -------- 2 files changed, 5 insertions(+), 8 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/config.yml delete mode 100644 .github/ISSUE_TEMPLATE/question.md diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..dccd0c5df --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Question + url: https://stackoverflow.com/questions/tagged/xtermjs + about: Please ask and answer questions here. diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md deleted file mode 100644 index 1fe5c763a..000000000 --- a/.github/ISSUE_TEMPLATE/question.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: Question -about: The issue tracker is not for questions. Please ask questions on https://stackoverflow.com/questions/tagged/xtermjs ---- - -🛑 The issue tracker is not for questions 🛑 - -If you have a question, please ask it on https://stackoverflow.com/questions/tagged/xtermjs. From 0bd0f3a364c8f403d7a49c2d434d614e6f1a7911 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 22 Nov 2019 16:00:25 -0800 Subject: [PATCH 138/268] Prevent atlas missing exception when opened before attach See microsoft/vscode#85048 --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 46fd27301..6f98d2c19 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -37,6 +37,7 @@ export class WebglRenderer extends Disposable implements IRenderer { public dimensions: IRenderDimensions; private _core: ITerminal; + private _isAttached: boolean; private _onRequestRefreshRows = new EventEmitter(); public get onRequestRefreshRows(): IEvent { return this._onRequestRefreshRows.event; } @@ -89,6 +90,8 @@ export class WebglRenderer extends Disposable implements IRenderer { // Update dimensions and acquire char atlas this.onCharSizeChanged(); + + this._isAttached = document.body.contains(this._core.screenElement); } public dispose(): void { @@ -99,7 +102,6 @@ export class WebglRenderer extends Disposable implements IRenderer { public setColors(colors: IColorSet): void { this._colors = colors; - // Clear layers and force a full render this._renderLayers.forEach(l => { l.setColors(this._terminal, this._colors); @@ -192,6 +194,8 @@ export class WebglRenderer extends Disposable implements IRenderer { */ private _refreshCharAtlas(): void { if (this.dimensions.scaledCharWidth <= 0 && this.dimensions.scaledCharHeight <= 0) { + // Mark as not attached so char atlas gets refreshed on next render + this._isAttached = false; return; } @@ -217,6 +221,16 @@ export class WebglRenderer extends Disposable implements IRenderer { } public renderRows(start: number, end: number): void { + if (!this._isAttached) { + if (document.body.contains(this._core.screenElement) && (this._core)._charSizeService.width && (this._core)._charSizeService.height) { + this._updateDimensions(); + this._refreshCharAtlas(); + this._isAttached = true; + } else { + return; + } + } + // Update render layers this._renderLayers.forEach(l => l.onGridChanged(this._terminal, start, end)); From 2f1f2f87855b2d78af6f6e021f0d97127fdb7740 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 22 Nov 2019 19:37:53 -0800 Subject: [PATCH 139/268] Add a . before beta and beta version Fixes #2576 --- bin/publish.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/publish.js b/bin/publish.js index 9d58ed159..4a7536cb7 100644 --- a/bin/publish.js +++ b/bin/publish.js @@ -105,7 +105,7 @@ function getNextBetaVersion(packageJson) { return aVersion > bVersion ? -1 : 1; })[0]; const latestTagVersion = parseInt(latestPublishedVersion.substr(latestPublishedVersion.search(/[0-9]+$/)), 10); - return `${nextStableVersion}-${tag}${latestTagVersion + 1}`; + return `${nextStableVersion}-${tag}.${latestTagVersion + 1}`; } function getPublishedVersions(packageJson, version, tag) { From 53fb1ba54df402d24f0e48ba36e918ca46d05653 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 24 Nov 2019 09:22:07 -0800 Subject: [PATCH 140/268] Don't throw when rendering before atlas is set This fixes a problem when loading the webgl addon before the terminal is attached. See microsoft/vscode#85048 --- 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 3d4d43d1d..e72346dcf 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -184,7 +184,7 @@ export class GlyphRenderer { let rasterizedGlyph: IRasterizedGlyph; if (!this._atlas) { - throw new Error('atlas must be set before updating cell'); + return; } if (chars && chars.length > 1) { rasterizedGlyph = this._atlas.getRasterizedGlyphCombinedChar(chars, bg, fg); From c60b822f4b3dd8422253c41795e726c57eb5d13d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 24 Nov 2019 09:34:29 -0800 Subject: [PATCH 141/268] Fix publish script for dot separated beta --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 1 - bin/publish.js | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index e72346dcf..7b35949d9 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -101,7 +101,6 @@ export class GlyphRenderer { private _dimensions: IRenderDimensions ) { const gl = this._gl; - const program = throwIfFalsy(createProgram(gl, vertexShaderSource, fragmentShaderSource)); this._program = program; diff --git a/bin/publish.js b/bin/publish.js index 4a7536cb7..9050c344e 100644 --- a/bin/publish.js +++ b/bin/publish.js @@ -97,7 +97,7 @@ function getNextBetaVersion(packageJson) { const nextStableVersion = `${stableVersion[0]}.${parseInt(stableVersion[1]) + 1}.0`; const publishedVersions = getPublishedVersions(packageJson, nextStableVersion, tag); if (publishedVersions.length === 0) { - return `${nextStableVersion}-${tag}1`; + return `${nextStableVersion}-${tag}.1`; } const latestPublishedVersion = publishedVersions.sort((a, b) => { const aVersion = parseInt(a.substr(a.search(/[0-9]+$/))); @@ -112,7 +112,7 @@ function getPublishedVersions(packageJson, version, tag) { const versionsProcess = cp.spawnSync('npm', ['view', packageJson.name, 'versions', '--json']); const versionsJson = JSON.parse(versionsProcess.stdout); if (tag) { - return versionsJson.filter(v => !v.search(new RegExp(`${version}-${tag}[0-9]+`))); + return versionsJson.filter(v => !v.search(new RegExp(`${version}-${tag}.[0-9]+`))); } return versionsJson; } From 2da5a7a26b44dc66f4ac9b772cd6b9bb4a4271b8 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 24 Nov 2019 09:42:33 -0800 Subject: [PATCH 142/268] Skip puppeteer download during release job --- azure-pipelines.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 0f61dfe1d..f6f892c7e 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -127,6 +127,8 @@ jobs: condition: and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(variables['FORCE_RELEASE'], 'true'))) pool: vmImage: 'ubuntu-16.04' + variables: + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1 steps: - task: NodeTool@0 inputs: @@ -140,6 +142,7 @@ jobs: inputs: key: yarn2 | $(Agent.OS) | yarn.lock path: node_modules + displayName: Cache node modules - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' - script: NPM_AUTH_TOKEN="$(NPM_AUTH_TOKEN)" node ./bin/publish.js From 709214a4145fb919bd90f343059568124e16e52f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 24 Nov 2019 10:06:41 -0800 Subject: [PATCH 143/268] 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 e045dc43b..d607cb07e 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 144/268] 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 6fbf9af2b..a316f34e7 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 145/268] 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 a316f34e7..fdef10a23 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 217bc242355dd261f9fb9bf2e4a3e4b726329809 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 24 Nov 2019 11:15:28 -0800 Subject: [PATCH 146/268] Mark before and after cursor rows dirty when changed The DOM renderer renders the cursor as part of the content, not on a separate layer, so it needs to be marked dirty to draw. Fixes #2581 --- src/InputHandler.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 3994d532c..7a0a6ac32 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -546,6 +546,7 @@ export class InputHandler extends Disposable implements IInputHandler { // make buffer local for faster access const buffer = this._bufferService.buffer; + this._dirtyRowService.markDirty(buffer.y); if (this._optionsService.options.convertEol) { buffer.x = 0; } @@ -560,6 +561,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (buffer.x >= this._bufferService.cols) { buffer.x--; } + this._dirtyRowService.markDirty(buffer.y); this._onLineFeed.fire(); } @@ -624,12 +626,14 @@ export class InputHandler extends Disposable implements IInputHandler { this._bufferService.buffer.y = this._terminal.originMode ? Math.min(this._bufferService.buffer.scrollBottom, Math.max(this._bufferService.buffer.scrollTop, this._bufferService.buffer.y)) : Math.min(this._bufferService.rows - 1, Math.max(0, this._bufferService.buffer.y)); + this._dirtyRowService.markDirty(this._bufferService.buffer.y); } /** * Set absolute cursor position. */ private _setCursor(x: number, y: number): void { + this._dirtyRowService.markDirty(this._bufferService.buffer.y); if (this._terminal.originMode) { this._bufferService.buffer.x = x; this._bufferService.buffer.y = this._bufferService.buffer.scrollTop + y; @@ -638,6 +642,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._bufferService.buffer.y = y; } this._restrictCursor(); + this._dirtyRowService.markDirty(this._bufferService.buffer.y); } /** From 25f2e66222fd1557ddc0131d62fc64e0565a9f1f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 25 Nov 2019 07:36:06 -0800 Subject: [PATCH 147/268] Include ` in word separators See microsoft/vscode#84483 --- src/common/services/OptionsService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index b0b576942..9eaad94f6 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -51,7 +51,7 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({ screenKeys: false, cancelEvents: false, useFlowControl: false, - wordSeparator: ' ()[]{}\',:;"' + wordSeparator: ' ()[]{}\',:;"`' }); /** From afe6cb6b865a0f805e3a71996cd176325c3d5f89 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 25 Nov 2019 09:12:39 -0800 Subject: [PATCH 148/268] Remove unused imports/functions --- .../src/atlas/WebglCharAtlas.ts | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index cda202253..097c420d5 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -6,7 +6,7 @@ import { ICharAtlasConfig } from './Types'; import { DIM_OPACITY } from 'browser/renderer/atlas/Constants'; import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types'; -import { DEFAULT_COLOR, FgFlags, Attributes, BgFlags } from 'common/buffer/Constants'; +import { DEFAULT_COLOR, Attributes } from 'common/buffer/Constants'; import { throwIfFalsy } from '../WebglUtils'; import { IColor } from 'browser/Types'; import { IDisposable } from 'xterm'; @@ -530,20 +530,3 @@ function toPaddedHex(c: number): string { const s = c.toString(16); return s.length < 2 ? '0' + s : s; } - -function getFgColor(fg: number): number { - switch (fg & Attributes.CM_MASK) { - case Attributes.CM_P16: - case Attributes.CM_P256: return fg & Attributes.PCOLOR_MASK; - case Attributes.CM_RGB: return fg & Attributes.RGB_MASK; - default: return -1; // CM_DEFAULT defaults to -1 - } -} -function getBgColor(bg: number): number { - switch (bg & Attributes.CM_MASK) { - case Attributes.CM_P16: - case Attributes.CM_P256: return bg & Attributes.PCOLOR_MASK; - case Attributes.CM_RGB: return bg & Attributes.RGB_MASK; - default: return -1; // CM_DEFAULT defaults to -1 - } -} From 1a5c3f936460f6e8b9705f2322d1352d29dca4d0 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 25 Nov 2019 11:57:52 -0800 Subject: [PATCH 149/268] Properly set non-black transparency backgrounds Before it was always set to 0 as the resolved color was pulled from an ImageData --- src/browser/Color.ts | 5 ++- src/browser/ColorManager.ts | 73 +++++++++++++++++++++++++------------ 2 files changed, 54 insertions(+), 24 deletions(-) diff --git a/src/browser/Color.ts b/src/browser/Color.ts index 2fc97f0a2..e20f62e6e 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -39,7 +39,10 @@ export function toPaddedHex(c: number): string { return s.length < 2 ? '0' + s : s; } -export function toCss(r: number, g: number, b: number): string { +export function toCss(r: number, g: number, b: number, a?: number): string { + if (a !== undefined) { + return `#${toPaddedHex(r)}${toPaddedHex(g)}${toPaddedHex(b)}${toPaddedHex(a)}`; + } return `#${toPaddedHex(r)}${toPaddedHex(g)}${toPaddedHex(b)}`; } diff --git a/src/browser/ColorManager.ts b/src/browser/ColorManager.ts index 92994cfc4..46a7710e7 100644 --- a/src/browser/ColorManager.ts +++ b/src/browser/ColorManager.ts @@ -5,7 +5,7 @@ import { IColorManager, IColor, IColorSet, IColorContrastCache } from 'browser/Types'; import { ITheme } from 'common/services/Services'; -import { fromCss, toCss, blend, toRgba } from 'browser/Color'; +import { fromCss, toCss, blend, toRgba, toPaddedHex } from 'browser/Color'; import { ColorContrastCache } from 'browser/ColorContrastCache'; const DEFAULT_FOREGROUND = fromCss('#ffffff'); @@ -159,28 +159,55 @@ export class ColorManager implements IColorManager { this._ctx.fillRect(0, 0, 1, 1); const data = this._ctx.getImageData(0, 0, 1, 1).data; - if (!allowTransparency && data[3] !== 0xFF) { - // Ideally we'd just ignore the alpha channel, but... - // - // Browsers may not give back exactly the same RGB values we put in, because most/all - // convert the color to a pre-multiplied representation. getImageData converts that back to - // a un-premultipled representation, but the precision loss may make the RGB channels unuable - // on their own. - // - // E.g. In Chrome #12345610 turns into #10305010, and in the extreme case, 0xFFFFFF00 turns - // into 0x00000000. - // - // "Note: Due to the lossy nature of converting to and from premultiplied alpha color values, - // pixels that have just been set using putImageData() might be returned to an equivalent - // getImageData() as different values." - // -- https://html.spec.whatwg.org/multipage/canvas.html#pixel-manipulation - // - // So let's just use the fallback color in this case instead. - console.warn( - `Color: ${css} is using transparency, but allowTransparency is false. ` + - `Using fallback ${fallback.css}.` - ); - return fallback; + // Check if the printed color was transparent + if (data[3] !== 0xFF) { + if (!allowTransparency) { + // Ideally we'd just ignore the alpha channel, but... + // + // Browsers may not give back exactly the same RGB values we put in, because most/all + // convert the color to a pre-multiplied representation. getImageData converts that back to + // a un-premultipled representation, but the precision loss may make the RGB channels unuable + // on their own. + // + // E.g. In Chrome #12345610 turns into #10305010, and in the extreme case, 0xFFFFFF00 turns + // into 0x00000000. + // + // "Note: Due to the lossy nature of converting to and from premultiplied alpha color values, + // pixels that have just been set using putImageData() might be returned to an equivalent + // getImageData() as different values." + // -- https://html.spec.whatwg.org/multipage/canvas.html#pixel-manipulation + // + // So let's just use the fallback color in this case instead. + console.warn( + `Color: ${css} is using transparency, but allowTransparency is false. ` + + `Using fallback ${fallback.css}.` + ); + return fallback; + } + let r: number; + let g: number; + let b: number; + let a: number; + let rgba: number; + if (css.length === 5) { + const num = parseInt(css.substr(1), 16); + r = ((num >> 12) & 0xF) * 16; + g = ((num >> 8) & 0xF) * 16; + b = ((num >> 4) & 0xF) * 16; + a = (num & 0xF) * 16; + rgba = (r << 24) | (g << 16) | (b << 8) | a; + } else { + rgba = parseInt(css.substr(1), 16); + r = (rgba >> 24) & 0xFF; + g = (rgba >> 16) & 0xFF; + b = (rgba >> 8) & 0xFF; + a = (rgba ) & 0xFF; + } + + return { + rgba, + css: toCss(r, g, b, a) + }; } return { From 4cd8b22a5935657b35c6d123b7cd6c597fe200a7 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 25 Nov 2019 11:58:26 -0800 Subject: [PATCH 150/268] Use toRgba helper --- src/browser/ColorManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/ColorManager.ts b/src/browser/ColorManager.ts index 46a7710e7..b4bcdde04 100644 --- a/src/browser/ColorManager.ts +++ b/src/browser/ColorManager.ts @@ -195,7 +195,7 @@ export class ColorManager implements IColorManager { g = ((num >> 8) & 0xF) * 16; b = ((num >> 4) & 0xF) * 16; a = (num & 0xF) * 16; - rgba = (r << 24) | (g << 16) | (b << 8) | a; + rgba = toRgba(r, g, b, a); } else { rgba = parseInt(css.substr(1), 16); r = (rgba >> 24) & 0xFF; From bceddfbc28f968d89ff84eb3ce922a01d29f8fd9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 25 Nov 2019 14:22:03 -0800 Subject: [PATCH 151/268] Cover inverse non-black uncached case in canvas renderer --- src/browser/renderer/BaseRenderLayer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/browser/renderer/BaseRenderLayer.ts b/src/browser/renderer/BaseRenderLayer.ts index 599ab114a..f55a130dd 100644 --- a/src/browser/renderer/BaseRenderLayer.ts +++ b/src/browser/renderer/BaseRenderLayer.ts @@ -15,7 +15,7 @@ import { IColorSet, IColor } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; import { IBufferService, IOptionsService } from 'common/services/Services'; import { throwIfFalsy } from 'browser/renderer/RendererUtils'; -import { toCss, ensureContrastRatioRgba } from 'browser/Color'; +import { toCss, ensureContrastRatioRgba, opaque } from 'browser/Color'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; @@ -325,7 +325,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { if (fgOverride) { this._ctx.fillStyle = fgOverride.css; } else if (cell.isBgDefault()) { - this._ctx.fillStyle = this._colors.background.css; + this._ctx.fillStyle = opaque(this._colors.background).css; } else if (cell.isBgRGB()) { this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`; } else { From ef05a6a05354901eef52177d03858a4e05111697 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 25 Nov 2019 14:33:12 -0800 Subject: [PATCH 152/268] Draw transparent background in WebGL renderer Part of #2252 --- addons/xterm-addon-webgl/src/RectangleRenderer.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index b52a506e2..337ceacef 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -22,13 +22,13 @@ const enum VertexAttribLocations { const vertexShaderSource = `#version 300 es layout (location = ${VertexAttribLocations.POSITION}) in vec2 a_position; layout (location = ${VertexAttribLocations.SIZE}) in vec2 a_size; -layout (location = ${VertexAttribLocations.COLOR}) in vec3 a_color; +layout (location = ${VertexAttribLocations.COLOR}) in vec4 a_color; layout (location = ${VertexAttribLocations.UNIT_QUAD}) in vec2 a_unitquad; uniform mat4 u_projection; uniform vec2 u_resolution; -out vec3 v_color; +out vec4 v_color; void main() { vec2 zeroToOne = (a_position + (a_unitquad * a_size)) / u_resolution; @@ -39,12 +39,12 @@ void main() { const fragmentShaderSource = `#version 300 es precision lowp float; -in vec3 v_color; +in vec4 v_color; out vec4 outColor; void main() { - outColor = vec4(v_color, 1); + outColor = v_color; }`; interface IVertices { @@ -155,6 +155,7 @@ export class RectangleRenderer { private _updateCachedColors(): void { this._bgFloat = this._colorToFloat32Array(this._colors.background); + console.log('bgFloat', this._colors.background, this._bgFloat); this._selectionFloat = this._colorToFloat32Array(this._colors.selectionOpaque); } From 1ff1809ab75be9a9aeee134e6dd5c040f68efda0 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 25 Nov 2019 15:04:07 -0800 Subject: [PATCH 153/268] Support opaque inverse background in webgl, add test --- .../src/RectangleRenderer.ts | 1 - .../src/WebglRenderer.api.ts | 22 +++++++++++++++---- .../src/atlas/WebglCharAtlas.ts | 7 +++++- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index 337ceacef..2246cddd9 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -155,7 +155,6 @@ export class RectangleRenderer { private _updateCachedColors(): void { this._bgFloat = this._colorToFloat32Array(this._colors.background); - console.log('bgFloat', this._colors.background, this._bgFloat); this._selectionFloat = this._colorToFloat32Array(this._colors.selectionOpaque); } diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index e26e054a9..bf877dd87 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -702,6 +702,22 @@ describe('WebGL Renderer Integration Tests', function(): void { await pollFor(page, () => getCellColor(8, 2), [64, 64, 64, 255]); }); }); + + describe('allowTransparency', async () => { + before(async () => setupBrowser({ rendererType: 'dom', allowTransparency: true})); + after(async () => browser.close()); + beforeEach(async () => page.evaluate(`window.term.reset()`)); + it('transparent background inverse', async () => { + const theme: ITheme = { + background: '#ff000080' + }; + await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); + const data = `\\x1b[7m█\x1b[0m`; + await writeSync(data); + // Inverse background should be opaque + await pollFor(page, () => getCellColor(1, 1), [255, 0, 0, 255]); + }); + }); }); async function openTerminal(options: ITerminalOptions = {}): Promise { @@ -732,7 +748,7 @@ async function getCellColor(col: number, row: number): Promise { return await page.evaluate(`Array.from(window.result)`); } -async function setupBrowser(): Promise { +async function setupBrowser(options: ITerminalOptions = { rendererType: 'dom' }): Promise { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, args: [`--window-size=${width},${height}`, `--no-sandbox`] @@ -740,9 +756,7 @@ async function setupBrowser(): Promise { page = (await browser.pages())[0]; await page.setViewport({ width, height }); await page.goto(APP); - await openTerminal({ - rendererType: 'dom' - }); + await openTerminal(options); await page.evaluate(` window.addon = new WebglAddon(true); window.term.loadAddon(window.addon); diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 097c420d5..c96c2a695 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -228,7 +228,12 @@ export class WebglCharAtlas implements IDisposable { case Attributes.CM_DEFAULT: default: if (inverse) { - return this._config.colors.background.css; + const bg = this._config.colors.background.css; + if (bg.length === 9) { + // Remove bg alpha channel if present + return bg.substr(0, 7); + } + return bg; } return this._config.colors.foreground.css; } From 435d480cf17b7addcd271c8f3a3c79ae6d6acbcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 26 Nov 2019 01:59:10 +0100 Subject: [PATCH 154/268] unicode service stub --- src/common/services/Services.ts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index b86c635e6..28c5c3709 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -280,3 +280,28 @@ export interface ITheme { brightCyan?: string; brightWhite?: string; } + +export const IUnicodeService = createDecorator('UnicodeService'); +export interface IUnicodeService { + /** Register an Unicode version provider. */ + registerVersion(provider: IUnicodeVersionProvider): void; + /** Registered Unicode versions. */ + readonly versions: string[]; + /** Currently active version. */ + readonly activeVersion: string; + /** Event triggered, when activate version changed. */ + readonly onChange: IEvent; + /** Event triggered, when a new version provider was registered. */ + readonly onRegister: IEvent; + + /** + * Unicode version dependent + */ + wcwidth(codepoint: number): number; + getStringCellWidth(s: string): number; +} + +export interface IUnicodeVersionProvider { + version: string; + wcwidth(ucs: number): number; +} From 903d75e6d3b4500bcfa2839199047d47a1aae8be Mon Sep 17 00:00:00 2001 From: javacs3 Date: Thu, 28 Nov 2019 22:17:53 +0800 Subject: [PATCH 155/268] 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 fdef10a23..a165411ac 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 d607cb07e..a4f79e776 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 c55a818d5..4d5149f35 100644 --- a/demo/index.html +++ b/demo/index.html @@ -11,22 +11,6 @@

xterm.js: A terminal for the web

-
-

Actions

-

- - - - - -

-
-

SerializeAddon

- - -
-
-

Options

These options can be set in the Terminal constructor or using the Terminal.setOption function.

@@ -36,6 +20,21 @@

Addons

Addons can be loaded and unloaded on a particular terminal to extend its functionality.

+

Addons Control

+

SearchAddon

+

+ + + + + +

+

SerializeAddon

+

+ + +

+

Style

diff --git a/demo/start.js b/demo/start.js index 8ffedb429..a14627c1d 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 1ab5dc3cb..9c5fd0bd6 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 fe98b468b..b740e0450 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 9423f4000..3b8c47241 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 c630b92772fcc9f4a0b544be12cb33b1a9e4a926 Mon Sep 17 00:00:00 2001 From: Nick Pezza Date: Sat, 30 Nov 2019 14:37:07 -0500 Subject: [PATCH 156/268] Allow the thickness of the bar cursor to be configured --- addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts | 4 ++-- .../xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts | 2 +- src/browser/renderer/BaseRenderLayer.ts | 4 ++-- src/browser/renderer/CursorRenderLayer.ts | 2 +- src/common/services/OptionsService.ts | 1 + src/common/services/Services.ts | 2 ++ typings/xterm.d.ts | 5 +++++ 7 files changed, 14 insertions(+), 6 deletions(-) diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index 29487ad06..2b181847f 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -153,11 +153,11 @@ export abstract class BaseRenderLayer implements IRenderLayer { * @param x The column to fill. * @param y The row to fill. */ - protected _fillLeftLineAtCell(x: number, y: number): void { + protected _fillLeftLineAtCell(x: number, y: number, width: number = 1): void { this._ctx.fillRect( x * this._scaledCellWidth, y * this._scaledCellHeight, - window.devicePixelRatio, + window.devicePixelRatio * width, this._scaledCellHeight); } diff --git a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts index 413f4fdd4..29c380fb3 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts @@ -204,7 +204,7 @@ export class CursorRenderLayer extends BaseRenderLayer { private _renderBarCursor(terminal: Terminal, x: number, y: number, cell: ICellData): void { this._ctx.save(); this._ctx.fillStyle = this._colors.cursor.css; - this._fillLeftLineAtCell(x, y); + this._fillLeftLineAtCell(x, y, terminal.getOption('cursorBarWidth')); this._ctx.restore(); } diff --git a/src/browser/renderer/BaseRenderLayer.ts b/src/browser/renderer/BaseRenderLayer.ts index f55a130dd..2dfe5bd3a 100644 --- a/src/browser/renderer/BaseRenderLayer.ts +++ b/src/browser/renderer/BaseRenderLayer.ts @@ -171,11 +171,11 @@ export abstract class BaseRenderLayer implements IRenderLayer { * @param x The column to fill. * @param y The row to fill. */ - protected _fillLeftLineAtCell(x: number, y: number): void { + protected _fillLeftLineAtCell(x: number, y: number, width: number = 1): void { this._ctx.fillRect( x * this._scaledCellWidth, y * this._scaledCellHeight, - window.devicePixelRatio, + window.devicePixelRatio * width, this._scaledCellHeight); } diff --git a/src/browser/renderer/CursorRenderLayer.ts b/src/browser/renderer/CursorRenderLayer.ts index 0ca1b97a8..5927d8dfa 100644 --- a/src/browser/renderer/CursorRenderLayer.ts +++ b/src/browser/renderer/CursorRenderLayer.ts @@ -209,7 +209,7 @@ export class CursorRenderLayer extends BaseRenderLayer { private _renderBarCursor(x: number, y: number, cell: ICellData): void { this._ctx.save(); this._ctx.fillStyle = this._colors.cursor.css; - this._fillLeftLineAtCell(x, y); + this._fillLeftLineAtCell(x, y, this._optionsService.options.cursorBarWidth); this._ctx.restore(); } diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 9eaad94f6..987dde673 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -20,6 +20,7 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({ rows: 24, cursorBlink: false, cursorStyle: 'block', + cursorBarWidth: 1, bellSound: DEFAULT_BELL_SOUND, bellStyle: 'none', drawBoldTextInBrightColors: true, diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index b90db2998..4f90c0e9c 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -191,6 +191,7 @@ export interface IPartialTerminalOptions { cols?: number; cursorBlink?: boolean; cursorStyle?: 'block' | 'underline' | 'bar'; + cursorBarWidth?: number; disableStdin?: boolean; drawBoldTextInBrightColors?: boolean; fastScrollModifier?: 'alt' | 'ctrl' | 'shift'; @@ -223,6 +224,7 @@ export interface ITerminalOptions { cols: number; cursorBlink: boolean; cursorStyle: 'block' | 'underline' | 'bar'; + cursorBarWidth: number; disableStdin: boolean; drawBoldTextInBrightColors: boolean; fastScrollModifier: 'alt' | 'ctrl' | 'shift' | undefined; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 401f7abc3..a0e91c362 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -72,6 +72,11 @@ declare module 'xterm' { */ cursorStyle?: 'block' | 'underline' | 'bar'; + /** + * The width of the bar cursor. + */ + cursorBarWidth?: number; + /** * Whether input should be disabled. */ From 543b3d6c9f45b2b2b5141e02c20cca0b76a1cfc8 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 4 Dec 2019 10:54:17 -0800 Subject: [PATCH 157/268] Fix minimumContrastRatio on dom/truecolor Fixes #2593 --- src/browser/Color.ts | 2 +- .../renderer/dom/DomRendererRowFactory.ts | 19 +++++++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/browser/Color.ts b/src/browser/Color.ts index e20f62e6e..e40ff9e1a 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -108,7 +108,7 @@ export function contrastRatio(l1: number, l2: number): number { return (l1 + 0.05) / (l2 + 0.05); } -function rgbaToColor(r: number, g: number, b: number): IColor { +export function rgbaToColor(r: number, g: number, b: number): IColor { return { css: toCss(r, g, b), rgba: toRgba(r, g, b) diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index cffc56242..bd922f578 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -8,7 +8,7 @@ import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Attributes } from 'common/buffer/Constants'; import { CellData } from 'common/buffer/CellData'; import { IOptionsService } from 'common/services/Services'; -import { ensureContrastRatio } from 'browser/Color'; +import { ensureContrastRatio, rgbaToColor } from 'browser/Color'; import { IColorSet, IColor } from 'browser/Types'; export const BOLD_CLASS = 'xterm-bold'; @@ -129,7 +129,14 @@ export class DomRendererRowFactory { } break; case Attributes.CM_RGB: - charElement.setAttribute('style', `${charElement.getAttribute('style') || ''}color:#${padStart(fg.toString(16), '0', 6)};`); + const color = rgbaToColor( + (fg >> 16) & 0xFF, + (fg >> 8) & 0xFF, + (fg ) & 0xFF + ); + if (!this._applyMinimumContrast(charElement, this._colors.background, color)) { + this._addStyle(charElement, `color:#${padStart(fg.toString(16), '0', 6)}`); + } break; case Attributes.CM_DEFAULT: default: @@ -147,7 +154,7 @@ export class DomRendererRowFactory { charElement.classList.add(`xterm-bg-${bg}`); break; case Attributes.CM_RGB: - charElement.setAttribute('style', `${charElement.getAttribute('style') || ''}background-color:#${padStart(bg.toString(16), '0', 6)};`); + this._addStyle(charElement, `background-color:#${padStart(bg.toString(16), '0', 6)}`); break; case Attributes.CM_DEFAULT: default: @@ -176,12 +183,16 @@ export class DomRendererRowFactory { } if (adjustedColor) { - element.setAttribute('style', `${element.getAttribute('style') || ''}color:${adjustedColor.css}`); + this._addStyle(element, `color:${adjustedColor.css}`); return true; } return false; } + + private _addStyle(element: HTMLElement, style: string): void { + element.setAttribute('style', `${element.getAttribute('style') || ''}${style};`); + } } function padStart(text: string, padChar: string, length: number): string { From 3395ccb94330002837957fa4880cea847a0696da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 5 Dec 2019 15:42:55 +0100 Subject: [PATCH 158/268] UnicodeService & provider --- demo/client.ts | 2 +- src/InputHandler.test.ts | 20 +- src/InputHandler.ts | 14 +- src/Terminal.test.ts | 8 +- src/Terminal.ts | 17 +- src/browser/Linkifier.ts | 4 +- src/common/TestUtils.test.ts | 18 +- src/common/input/UnicodeV11.ts | 216 +++++++++++++++++++++ src/common/input/UnicodeV6.test.ts | 185 ++++++++++++++++++ src/common/input/UnicodeV6.ts | 129 ++++++++++++ src/common/services/Services.ts | 4 +- src/common/services/UnicodeService.test.ts | 50 +++++ src/common/services/UnicodeService.ts | 23 ++- 13 files changed, 648 insertions(+), 42 deletions(-) create mode 100644 src/common/input/UnicodeV11.ts create mode 100644 src/common/input/UnicodeV6.test.ts create mode 100644 src/common/input/UnicodeV6.ts create mode 100644 src/common/services/UnicodeService.test.ts diff --git a/demo/client.ts b/demo/client.ts index 347a9d65c..9f1c6f592 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -257,7 +257,7 @@ function initOptions(term: TerminalType): void { logLevel: ['debug', 'info', 'warn', 'error', 'off'], rendererType: ['dom', 'canvas'], wordSeparator: null, - unicodeVersion: ['6', '11'] + unicodeVersion: ['6'] }; const options = Object.keys((term)._core.options); const booleanOptions = []; diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index d8defa1cc..1aaeb7390 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -13,7 +13,7 @@ import { CellData } from 'common/buffer/CellData'; import { Attributes } from 'common/buffer/Constants'; import { AttributeData } from 'common/buffer/AttributeData'; import { Params } from 'common/parser/Params'; -import { MockCoreService, MockBufferService, MockDirtyRowService, MockOptionsService, MockLogService, MockCoreMouseService } from 'common/TestUtils.test'; +import { MockCoreService, MockBufferService, MockDirtyRowService, MockOptionsService, MockLogService, MockCoreMouseService, MockUnicodeService } from 'common/TestUtils.test'; import { IBufferService } from 'common/services/Services'; import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; import { clone } from 'common/Clone'; @@ -41,7 +41,7 @@ describe('InputHandler', () => { bufferService.buffer.x = 1; bufferService.buffer.y = 2; bufferService.buffer.ybase = 0; - const inputHandler = new InputHandler(terminal, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); + const inputHandler = new InputHandler(terminal, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); // Save cursor position inputHandler.saveCursor(); assert.equal(bufferService.buffer.x, 1); @@ -60,7 +60,7 @@ describe('InputHandler', () => { describe('setCursorStyle', () => { it('should call Terminal.setOption with correct params', () => { const optionsService = new MockOptionsService(); - const inputHandler = new InputHandler(new MockInputHandlingTerminal(), new MockBufferService(80, 30), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), optionsService, new MockCoreMouseService()); + const inputHandler = new InputHandler(new MockInputHandlingTerminal(), new MockBufferService(80, 30), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), optionsService, new MockCoreMouseService(), new MockUnicodeService()); inputHandler.setCursorStyle(Params.fromArray([0])); assert.equal(optionsService.options['cursorStyle'], 'block'); @@ -101,7 +101,7 @@ describe('InputHandler', () => { it('should toggle Terminal.bracketedPasteMode', () => { const terminal = new MockInputHandlingTerminal(); terminal.bracketedPasteMode = false; - const inputHandler = new InputHandler(terminal, new MockBufferService(80, 30), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); + const inputHandler = new InputHandler(terminal, new MockBufferService(80, 30), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); // Set bracketed paste mode inputHandler.setModePrivate(Params.fromArray([2004])); assert.equal(terminal.bracketedPasteMode, true); @@ -120,7 +120,7 @@ describe('InputHandler', () => { it('insertChars', function(): void { const term = new Terminal(); const bufferService = new MockBufferService(80, 30); - const inputHandler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); + const inputHandler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); // insert some data in first and second line inputHandler.parse(Array(bufferService.cols - 9).join('a')); @@ -158,7 +158,7 @@ describe('InputHandler', () => { it('deleteChars', function(): void { const term = new Terminal(); const bufferService = new MockBufferService(80, 30); - const inputHandler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); + const inputHandler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); // insert some data in first and second line inputHandler.parse(Array(bufferService.cols - 9).join('a')); @@ -199,7 +199,7 @@ describe('InputHandler', () => { it('eraseInLine', function(): void { const term = new Terminal(); const bufferService = new MockBufferService(80, 30); - const inputHandler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); + const inputHandler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); // fill 6 lines to test 3 different states inputHandler.parse(Array(bufferService.cols + 1).join('a')); @@ -228,7 +228,7 @@ describe('InputHandler', () => { it('eraseInDisplay', function(): void { const term = new Terminal({cols: 80, rows: 7}); const bufferService = new MockBufferService(80, 7); - const inputHandler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); + const inputHandler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); // fill display with a's for (let i = 0; i < bufferService.rows; ++i) inputHandler.parse(Array(bufferService.cols + 1).join('a')); @@ -363,7 +363,7 @@ describe('InputHandler', () => { describe('print', () => { it('should not cause an infinite loop (regression test)', () => { const term = new Terminal(); - const inputHandler = new InputHandler(term, new MockBufferService(80, 30), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); + const inputHandler = new InputHandler(term, new MockBufferService(80, 30), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); const container = new Uint32Array(10); container[0] = 0x200B; inputHandler.print(container, 0, 1); @@ -378,7 +378,7 @@ describe('InputHandler', () => { beforeEach(() => { term = new Terminal(); bufferService = new MockBufferService(80, 30); - handler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); + handler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); }); it('should handle DECSET/DECRST 47 (alt screen buffer)', () => { handler.parse('\x1b[?47h\r\n\x1b[31mJUNK\x1b[?47lTEST'); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index e439a7d97..db3953136 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -7,7 +7,6 @@ import { IInputHandler, IInputHandlingTerminal } from './Types'; import { C0, C1 } from 'common/data/EscapeSequences'; import { CHARSETS, DEFAULT_CHARSET } from 'common/data/Charsets'; -import { wcwidthV6, wcwidthV11 } from 'common/CharWidth'; import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser'; import { Disposable } from 'common/Lifecycle'; import { concat } from 'common/TypedArrayUtils'; @@ -19,7 +18,7 @@ import { NULL_CELL_CODE, NULL_CELL_WIDTH, Attributes, FgFlags, BgFlags, Content import { CellData } from 'common/buffer/CellData'; import { AttributeData } from 'common/buffer/AttributeData'; import { IAttributeData, IDisposable } from 'common/Types'; -import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService, ICoreMouseService } from 'common/services/Services'; +import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService, ICoreMouseService, IUnicodeService } from 'common/services/Services'; import { OscHandler } from 'common/parser/OscParser'; import { DcsHandler } from 'common/parser/DcsParser'; @@ -127,7 +126,6 @@ export class InputHandler extends Disposable implements IInputHandler { private _stringDecoder: StringToUtf32 = new StringToUtf32(); private _utf8Decoder: Utf8ToUtf32 = new Utf8ToUtf32(); private _workCell: CellData = new CellData(); - private _wcwidth: (codepoint: number) => number; private _onCursorMove = new EventEmitter(); public get onCursorMove(): IEvent { return this._onCursorMove.event; } @@ -144,16 +142,10 @@ export class InputHandler extends Disposable implements IInputHandler { private readonly _logService: ILogService, private readonly _optionsService: IOptionsService, private readonly _coreMouseService: ICoreMouseService, + private readonly _unicodeService: IUnicodeService, private readonly _parser: IEscapeSequenceParser = new EscapeSequenceParser()) { super(); - this._wcwidth = this._optionsService.options.unicodeVersion === '11' ? wcwidthV11 : wcwidthV6; - this._optionsService.onOptionChange(option => { - if (option === 'unicodeVersion') { - this._wcwidth = this._optionsService.options.unicodeVersion === '11' ? wcwidthV11 : wcwidthV6; - } - }); - this.register(this._parser); /** @@ -400,7 +392,7 @@ export class InputHandler extends Disposable implements IInputHandler { // calculate print space // expensive call, therefore we save width in line buffer - chWidth = this._wcwidth(code); + chWidth = this._unicodeService.wcwidth(code); // get charset replacement character // charset is only defined for ASCII, therefore we only diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index 5f7f0721b..266769ed0 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -7,15 +7,17 @@ import { assert, expect } from 'chai'; import { MockViewport, MockCompositionHelper, MockRenderer, TestTerminal } from './TestUtils.test'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { CellData } from 'common/buffer/CellData'; -import { wcwidthV6 } from 'common/CharWidth'; import { IBufferService } from 'common/services/Services'; import { Linkifier } from 'browser/Linkifier'; -import { MockLogService, MockOptionsService } from 'common/TestUtils.test'; +import { MockLogService, MockOptionsService, MockUnicodeService } from 'common/TestUtils.test'; import { IRegisteredLinkMatcher, IMouseZoneManager, IMouseZone } from 'browser/Types'; const INIT_COLS = 80; const INIT_ROWS = 24; +// grab wcwidth from mock unicode service (hardcoded to V6) +const wcwidth = (new MockUnicodeService()).wcwidth; + describe('Terminal', () => { let term: TestTerminal; const termOptions = { @@ -762,7 +764,7 @@ describe('Terminal', () => { for (let i = 0xDC00; i <= 0xDCFF; ++i) { term.buffer.x = term.cols - 1; term.wraparoundMode = false; - const width = wcwidthV6((0xD800 - 0xD800) * 0x400 + i - 0xDC00 + 0x10000); + const width = wcwidth((0xD800 - 0xD800) * 0x400 + i - 0xDC00 + 0x10000); if (width !== 1) { continue; } diff --git a/src/Terminal.ts b/src/Terminal.ts index cbc01cd9a..b2586df93 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -46,7 +46,7 @@ import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { handleWindowsModeLineFeed } from 'common/WindowsMode'; import { ColorManager } from 'browser/ColorManager'; import { RenderService } from 'browser/services/RenderService'; -import { IOptionsService, IBufferService, ICoreMouseService, ICoreService, ILogService, IDirtyRowService, IInstantiationService } from 'common/services/Services'; +import { IOptionsService, IBufferService, ICoreMouseService, ICoreService, ILogService, IDirtyRowService, IInstantiationService, IUnicodeService } from 'common/services/Services'; import { OptionsService } from 'common/services/OptionsService'; import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService, ICoreBrowserService } from 'browser/services/Services'; import { CharSizeService } from 'browser/services/CharSizeService'; @@ -64,6 +64,7 @@ import { InstantiationService } from 'common/services/InstantiationService'; import { CoreMouseService } from 'common/services/CoreMouseService'; import { WriteBuffer } from 'common/input/WriteBuffer'; import { CoreBrowserService } from 'browser/services/CoreBrowserService'; +import { UnicodeService } from 'common/services/UnicodeService'; // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; @@ -101,6 +102,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp private _instantiationService: IInstantiationService; private _logService: ILogService; public optionsService: IOptionsService; + private _unicodeService: IUnicodeService; // browser services private _charSizeService: ICharSizeService; @@ -228,6 +230,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._instantiationService.setService(ICoreMouseService, this._coreMouseService); this._dirtyRowService = this._instantiationService.createInstance(DirtyRowService); this._instantiationService.setService(IDirtyRowService, this._dirtyRowService); + this._unicodeService = this._instantiationService.createInstance(UnicodeService); + this._instantiationService.setService(IUnicodeService, this._unicodeService); this._setupOptionsListeners(); this._setup(); @@ -276,7 +280,16 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._userScrolling = false; // Register input handler and refire/handle events - this._inputHandler = new InputHandler(this, this._bufferService, this._coreService, this._dirtyRowService, this._logService, this.optionsService, this._coreMouseService); + this._inputHandler = new InputHandler( + this, + this._bufferService, + this._coreService, + this._dirtyRowService, + this._logService, + this.optionsService, + this._coreMouseService, + this._unicodeService + ); this._inputHandler.onCursorMove(() => this._onCursorMove.fire()); this._inputHandler.onLineFeed(() => this._onLineFeed.fire()); this.register(this._inputHandler); diff --git a/src/browser/Linkifier.ts b/src/browser/Linkifier.ts index 072f79721..3dc048c7c 100644 --- a/src/browser/Linkifier.ts +++ b/src/browser/Linkifier.ts @@ -5,7 +5,7 @@ import { ILinkifierEvent, ILinkMatcher, LinkMatcherHandler, ILinkMatcherOptions, ILinkifier, IMouseZoneManager, IMouseZone, IRegisteredLinkMatcher } from 'browser/Types'; import { IBufferStringIteratorResult } from 'common/buffer/Types'; -import { getStringCellWidthV6, getStringCellWidthV11 } from 'common/CharWidth'; +import { getStringCellWidthV6 } from 'common/CharWidth'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { ILogService, IBufferService, IOptionsService } from 'common/services/Services'; @@ -278,7 +278,7 @@ export class Linkifier implements ILinkifier { if (!this._mouseZoneManager || !this._element) { return; } - const width = this._optionsService.options.unicodeVersion === '11' ? getStringCellWidthV11(uri) : getStringCellWidthV6(uri); + const width = getStringCellWidthV6(uri); // FIXME: get cell length from buffer to avoid mismatch after version change const x1 = x % this._bufferService.cols; const y1 = y + Math.floor(x / this._bufferService.cols); let x2 = (x1 + width) % this._bufferService.cols; diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 7e47d4b0a..1d996a82b 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -3,13 +3,14 @@ * @license MIT */ -import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, IPartialTerminalOptions, IDirtyRowService, ICoreMouseService } from 'common/services/Services'; +import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, IPartialTerminalOptions, IDirtyRowService, ICoreMouseService, IUnicodeService, IUnicodeVersionProvider } from 'common/services/Services'; import { IEvent, EventEmitter } from 'common/EventEmitter'; import { clone } from 'common/Clone'; import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; import { IBufferSet, IBuffer } from 'common/buffer/Types'; import { BufferSet } from 'common/buffer/BufferSet'; import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEventType } from 'common/Types'; +import { UnicodeV6 } from 'common/input/UnicodeV6'; export class MockBufferService implements IBufferService { serviceBrand: any; @@ -90,3 +91,18 @@ export class MockOptionsService implements IOptionsService { throw new Error('Method not implemented.'); } } + +// defaults to V6 always to keep tests passing +export class MockUnicodeService implements IUnicodeService { + private _provider = new UnicodeV6(); + register(provider: IUnicodeVersionProvider): void { + throw new Error('Method not implemented.'); + } + versions: string[] = []; + activeVersion: string = ''; + onChange: IEvent = new EventEmitter().event; + wcwidth = (codepoint: number) => this._provider.wcwidth(codepoint); + getStringCellWidth(s: string): number { + throw new Error('Method not implemented.'); + } +} diff --git a/src/common/input/UnicodeV11.ts b/src/common/input/UnicodeV11.ts new file mode 100644 index 000000000..6393e3f5e --- /dev/null +++ b/src/common/input/UnicodeV11.ts @@ -0,0 +1,216 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ +import { IUnicodeVersionProvider } from 'common/services/Services'; +import { fill } from 'common/TypedArrayUtils'; + +export class UnicodeV11 implements IUnicodeVersionProvider { + static bmpCombining = [ + [0x0300, 0x036F], [0x0483, 0x0489], [0x0591, 0x05BD], + [0x05BF, 0x05BF], [0x05C1, 0x05C2], [0x05C4, 0x05C5], + [0x05C7, 0x05C7], [0x0600, 0x0605], [0x0610, 0x061A], + [0x061C, 0x061C], [0x064B, 0x065F], [0x0670, 0x0670], + [0x06D6, 0x06DD], [0x06DF, 0x06E4], [0x06E7, 0x06E8], + [0x06EA, 0x06ED], [0x070F, 0x070F], [0x0711, 0x0711], + [0x0730, 0x074A], [0x07A6, 0x07B0], [0x07EB, 0x07F3], + [0x07FD, 0x07FD], [0x0816, 0x0819], [0x081B, 0x0823], + [0x0825, 0x0827], [0x0829, 0x082D], [0x0859, 0x085B], + [0x08D3, 0x0902], [0x093A, 0x093A], [0x093C, 0x093C], + [0x0941, 0x0948], [0x094D, 0x094D], [0x0951, 0x0957], + [0x0962, 0x0963], [0x0981, 0x0981], [0x09BC, 0x09BC], + [0x09C1, 0x09C4], [0x09CD, 0x09CD], [0x09E2, 0x09E3], + [0x09FE, 0x09FE], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C], + [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D], + [0x0A51, 0x0A51], [0x0A70, 0x0A71], [0x0A75, 0x0A75], + [0x0A81, 0x0A82], [0x0ABC, 0x0ABC], [0x0AC1, 0x0AC5], + [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD], [0x0AE2, 0x0AE3], + [0x0AFA, 0x0AFF], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C], + [0x0B3F, 0x0B3F], [0x0B41, 0x0B44], [0x0B4D, 0x0B4D], + [0x0B56, 0x0B56], [0x0B62, 0x0B63], [0x0B82, 0x0B82], + [0x0BC0, 0x0BC0], [0x0BCD, 0x0BCD], [0x0C00, 0x0C00], + [0x0C04, 0x0C04], [0x0C3E, 0x0C40], [0x0C46, 0x0C48], + [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0C62, 0x0C63], + [0x0C81, 0x0C81], [0x0CBC, 0x0CBC], [0x0CBF, 0x0CBF], + [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD], [0x0CE2, 0x0CE3], + [0x0D00, 0x0D01], [0x0D3B, 0x0D3C], [0x0D41, 0x0D44], + [0x0D4D, 0x0D4D], [0x0D62, 0x0D63], [0x0DCA, 0x0DCA], + [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6], [0x0E31, 0x0E31], + [0x0E34, 0x0E3A], [0x0E47, 0x0E4E], [0x0EB1, 0x0EB1], + [0x0EB4, 0x0EBC], [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], + [0x0F35, 0x0F35], [0x0F37, 0x0F37], [0x0F39, 0x0F39], + [0x0F71, 0x0F7E], [0x0F80, 0x0F84], [0x0F86, 0x0F87], + [0x0F8D, 0x0F97], [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], + [0x102D, 0x1030], [0x1032, 0x1037], [0x1039, 0x103A], + [0x103D, 0x103E], [0x1058, 0x1059], [0x105E, 0x1060], + [0x1071, 0x1074], [0x1082, 0x1082], [0x1085, 0x1086], + [0x108D, 0x108D], [0x109D, 0x109D], [0x1160, 0x11FF], + [0x135D, 0x135F], [0x1712, 0x1714], [0x1732, 0x1734], + [0x1752, 0x1753], [0x1772, 0x1773], [0x17B4, 0x17B5], + [0x17B7, 0x17BD], [0x17C6, 0x17C6], [0x17C9, 0x17D3], + [0x17DD, 0x17DD], [0x180B, 0x180E], [0x1885, 0x1886], + [0x18A9, 0x18A9], [0x1920, 0x1922], [0x1927, 0x1928], + [0x1932, 0x1932], [0x1939, 0x193B], [0x1A17, 0x1A18], + [0x1A1B, 0x1A1B], [0x1A56, 0x1A56], [0x1A58, 0x1A5E], + [0x1A60, 0x1A60], [0x1A62, 0x1A62], [0x1A65, 0x1A6C], + [0x1A73, 0x1A7C], [0x1A7F, 0x1A7F], [0x1AB0, 0x1ABE], + [0x1B00, 0x1B03], [0x1B34, 0x1B34], [0x1B36, 0x1B3A], + [0x1B3C, 0x1B3C], [0x1B42, 0x1B42], [0x1B6B, 0x1B73], + [0x1B80, 0x1B81], [0x1BA2, 0x1BA5], [0x1BA8, 0x1BA9], + [0x1BAB, 0x1BAD], [0x1BE6, 0x1BE6], [0x1BE8, 0x1BE9], + [0x1BED, 0x1BED], [0x1BEF, 0x1BF1], [0x1C2C, 0x1C33], + [0x1C36, 0x1C37], [0x1CD0, 0x1CD2], [0x1CD4, 0x1CE0], + [0x1CE2, 0x1CE8], [0x1CED, 0x1CED], [0x1CF4, 0x1CF4], + [0x1CF8, 0x1CF9], [0x1DC0, 0x1DF9], [0x1DFB, 0x1DFF], + [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2064], + [0x2066, 0x206F], [0x20D0, 0x20F0], [0x2CEF, 0x2CF1], + [0x2D7F, 0x2D7F], [0x2DE0, 0x2DFF], [0x302A, 0x302D], + [0x3099, 0x309A], [0xA66F, 0xA672], [0xA674, 0xA67D], + [0xA69E, 0xA69F], [0xA6F0, 0xA6F1], [0xA802, 0xA802], + [0xA806, 0xA806], [0xA80B, 0xA80B], [0xA825, 0xA826], + [0xA8C4, 0xA8C5], [0xA8E0, 0xA8F1], [0xA8FF, 0xA8FF], + [0xA926, 0xA92D], [0xA947, 0xA951], [0xA980, 0xA982], + [0xA9B3, 0xA9B3], [0xA9B6, 0xA9B9], [0xA9BC, 0xA9BD], + [0xA9E5, 0xA9E5], [0xAA29, 0xAA2E], [0xAA31, 0xAA32], + [0xAA35, 0xAA36], [0xAA43, 0xAA43], [0xAA4C, 0xAA4C], + [0xAA7C, 0xAA7C], [0xAAB0, 0xAAB0], [0xAAB2, 0xAAB4], + [0xAAB7, 0xAAB8], [0xAABE, 0xAABF], [0xAAC1, 0xAAC1], + [0xAAEC, 0xAAED], [0xAAF6, 0xAAF6], [0xABE5, 0xABE5], + [0xABE8, 0xABE8], [0xABED, 0xABED], [0xFB1E, 0xFB1E], + [0xFE00, 0xFE0F], [0xFE20, 0xFE2F], [0xFEFF, 0xFEFF], + [0xFFF9, 0xFFFB] + ]; + static highCombining = [ + [0x101FD, 0x101FD], [0x102E0, 0x102E0], + [0x10376, 0x1037A], [0x10A01, 0x10A03], [0x10A05, 0x10A06], + [0x10A0C, 0x10A0F], [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], + [0x10AE5, 0x10AE6], [0x10D24, 0x10D27], [0x10F46, 0x10F50], + [0x11001, 0x11001], [0x11038, 0x11046], [0x1107F, 0x11081], + [0x110B3, 0x110B6], [0x110B9, 0x110BA], [0x110BD, 0x110BD], + [0x110CD, 0x110CD], [0x11100, 0x11102], [0x11127, 0x1112B], + [0x1112D, 0x11134], [0x11173, 0x11173], [0x11180, 0x11181], + [0x111B6, 0x111BE], [0x111C9, 0x111CC], [0x1122F, 0x11231], + [0x11234, 0x11234], [0x11236, 0x11237], [0x1123E, 0x1123E], + [0x112DF, 0x112DF], [0x112E3, 0x112EA], [0x11300, 0x11301], + [0x1133B, 0x1133C], [0x11340, 0x11340], [0x11366, 0x1136C], + [0x11370, 0x11374], [0x11438, 0x1143F], [0x11442, 0x11444], + [0x11446, 0x11446], [0x1145E, 0x1145E], [0x114B3, 0x114B8], + [0x114BA, 0x114BA], [0x114BF, 0x114C0], [0x114C2, 0x114C3], + [0x115B2, 0x115B5], [0x115BC, 0x115BD], [0x115BF, 0x115C0], + [0x115DC, 0x115DD], [0x11633, 0x1163A], [0x1163D, 0x1163D], + [0x1163F, 0x11640], [0x116AB, 0x116AB], [0x116AD, 0x116AD], + [0x116B0, 0x116B5], [0x116B7, 0x116B7], [0x1171D, 0x1171F], + [0x11722, 0x11725], [0x11727, 0x1172B], [0x1182F, 0x11837], + [0x11839, 0x1183A], [0x119D4, 0x119D7], [0x119DA, 0x119DB], + [0x119E0, 0x119E0], [0x11A01, 0x11A0A], [0x11A33, 0x11A38], + [0x11A3B, 0x11A3E], [0x11A47, 0x11A47], [0x11A51, 0x11A56], + [0x11A59, 0x11A5B], [0x11A8A, 0x11A96], [0x11A98, 0x11A99], + [0x11C30, 0x11C36], [0x11C38, 0x11C3D], [0x11C3F, 0x11C3F], + [0x11C92, 0x11CA7], [0x11CAA, 0x11CB0], [0x11CB2, 0x11CB3], + [0x11CB5, 0x11CB6], [0x11D31, 0x11D36], [0x11D3A, 0x11D3A], + [0x11D3C, 0x11D3D], [0x11D3F, 0x11D45], [0x11D47, 0x11D47], + [0x11D90, 0x11D91], [0x11D95, 0x11D95], [0x11D97, 0x11D97], + [0x11EF3, 0x11EF4], [0x13430, 0x13438], [0x16AF0, 0x16AF4], + [0x16B30, 0x16B36], [0x16F4F, 0x16F4F], [0x16F8F, 0x16F92], + [0x1BC9D, 0x1BC9E], [0x1BCA0, 0x1BCA3], [0x1D167, 0x1D169], + [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD], + [0x1D242, 0x1D244], [0x1DA00, 0x1DA36], [0x1DA3B, 0x1DA6C], + [0x1DA75, 0x1DA75], [0x1DA84, 0x1DA84], [0x1DA9B, 0x1DA9F], + [0x1DAA1, 0x1DAAF], [0x1E000, 0x1E006], [0x1E008, 0x1E018], + [0x1E01B, 0x1E021], [0x1E023, 0x1E024], [0x1E026, 0x1E02A], + [0x1E130, 0x1E136], [0x1E2EC, 0x1E2EF], [0x1E8D0, 0x1E8D6], + [0x1E944, 0x1E94A], [0xE0001, 0xE0001], [0xE0020, 0xE007F], + [0xE0100, 0xE01EF] + ]; + static bmpWide = [ + [0x1100, 0x115F], [0x231A, 0x231B], [0x2329, 0x232A], + [0x23E9, 0x23EC], [0x23F0, 0x23F0], [0x23F3, 0x23F3], + [0x25FD, 0x25FE], [0x2614, 0x2615], [0x2648, 0x2653], + [0x267F, 0x267F], [0x2693, 0x2693], [0x26A1, 0x26A1], + [0x26AA, 0x26AB], [0x26BD, 0x26BE], [0x26C4, 0x26C5], + [0x26CE, 0x26CE], [0x26D4, 0x26D4], [0x26EA, 0x26EA], + [0x26F2, 0x26F3], [0x26F5, 0x26F5], [0x26FA, 0x26FA], + [0x26FD, 0x26FD], [0x2705, 0x2705], [0x270A, 0x270B], + [0x2728, 0x2728], [0x274C, 0x274C], [0x274E, 0x274E], + [0x2753, 0x2755], [0x2757, 0x2757], [0x2795, 0x2797], + [0x27B0, 0x27B0], [0x27BF, 0x27BF], [0x2B1B, 0x2B1C], + [0x2B50, 0x2B50], [0x2B55, 0x2B55], [0x2E80, 0x2E99], + [0x2E9B, 0x2EF3], [0x2F00, 0x2FD5], [0x2FF0, 0x2FFB], + [0x3000, 0x3029], [0x302E, 0x303E], [0x3041, 0x3096], + [0x309B, 0x30FF], [0x3105, 0x312F], [0x3131, 0x318E], + [0x3190, 0x31BA], [0x31C0, 0x31E3], [0x31F0, 0x321E], + [0x3220, 0x3247], [0x3250, 0x4DBF], [0x4E00, 0xA48C], + [0xA490, 0xA4C6], [0xA960, 0xA97C], [0xAC00, 0xD7A3], + [0xF900, 0xFAFF], [0xFE10, 0xFE19], [0xFE30, 0xFE52], + [0xFE54, 0xFE66], [0xFE68, 0xFE6B], [0xFF01, 0xFF60], + [0xFFE0, 0xFFE6] + ]; + static highWide = [ + [0x16FE0, 0x16FE3], [0x17000, 0x187F7], + [0x18800, 0x18AF2], [0x1B000, 0x1B11E], [0x1B150, 0x1B152], + [0x1B164, 0x1B167], [0x1B170, 0x1B2FB], [0x1F004, 0x1F004], + [0x1F0CF, 0x1F0CF], [0x1F18E, 0x1F18E], [0x1F191, 0x1F19A], + [0x1F200, 0x1F202], [0x1F210, 0x1F23B], [0x1F240, 0x1F248], + [0x1F250, 0x1F251], [0x1F260, 0x1F265], [0x1F300, 0x1F320], + [0x1F32D, 0x1F335], [0x1F337, 0x1F37C], [0x1F37E, 0x1F393], + [0x1F3A0, 0x1F3CA], [0x1F3CF, 0x1F3D3], [0x1F3E0, 0x1F3F0], + [0x1F3F4, 0x1F3F4], [0x1F3F8, 0x1F43E], [0x1F440, 0x1F440], + [0x1F442, 0x1F4FC], [0x1F4FF, 0x1F53D], [0x1F54B, 0x1F54E], + [0x1F550, 0x1F567], [0x1F57A, 0x1F57A], [0x1F595, 0x1F596], + [0x1F5A4, 0x1F5A4], [0x1F5FB, 0x1F64F], [0x1F680, 0x1F6C5], + [0x1F6CC, 0x1F6CC], [0x1F6D0, 0x1F6D2], [0x1F6D5, 0x1F6D5], + [0x1F6EB, 0x1F6EC], [0x1F6F4, 0x1F6FA], [0x1F7E0, 0x1F7EB], + [0x1F90D, 0x1F971], [0x1F973, 0x1F976], [0x1F97A, 0x1F9A2], + [0x1F9A5, 0x1F9AA], [0x1F9AE, 0x1F9CA], [0x1F9CD, 0x1F9FF], + [0x1FA70, 0x1FA73], [0x1FA78, 0x1FA7A], [0x1FA80, 0x1FA82], + [0x1FA90, 0x1FA95], [0x20000, 0x2FFFD], [0x30000, 0x3FFFD] + ]; + static bmpTable: Uint8Array; + + static bisearch(ucs: number, data: number[][]): boolean { + let min = 0; + let max = data.length - 1; + let mid; + if (ucs < data[0][0] || ucs > data[max][1]) { + return false; + } + while (max >= min) { + mid = (min + max) >> 1; + if (ucs > data[mid][1]) { + min = mid + 1; + } else if (ucs < data[mid][0]) { + max = mid - 1; + } else { + return true; + } + } + return false; + } + + public version = '11'; + + constructor() { + if (!UnicodeV11.bmpTable) { + UnicodeV11.bmpTable = new Uint8Array(65536); + fill(UnicodeV11.bmpTable, 1); + UnicodeV11.bmpTable[0] = 0; + fill(UnicodeV11.bmpTable, 0, 1, 32); + fill(UnicodeV11.bmpTable, 0, 0x7f, 0xa0); + for (let r = 0; r < UnicodeV11.bmpCombining.length; ++r) { + fill(UnicodeV11.bmpTable, 0, UnicodeV11.bmpCombining[r][0], UnicodeV11.bmpCombining[r][1] + 1); + } + for (let r = 0; r < UnicodeV11.bmpWide.length; ++r) { + fill(UnicodeV11.bmpTable, 2, UnicodeV11.bmpWide[r][0], UnicodeV11.bmpWide[r][1] + 1); + } + } + } + + public wcwidth(num: number): number { + if (num < 32) return 0; + if (num < 127) return 1; + if (num < 65536) return UnicodeV11.bmpTable[num]; + if (UnicodeV11.bisearch(num, UnicodeV11.highCombining)) return 0; + if (UnicodeV11.bisearch(num, UnicodeV11.highWide)) return 2; + return 1; + } +} diff --git a/src/common/input/UnicodeV6.test.ts b/src/common/input/UnicodeV6.test.ts new file mode 100644 index 000000000..4a44f15bd --- /dev/null +++ b/src/common/input/UnicodeV6.test.ts @@ -0,0 +1,185 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert } from 'chai'; +import { UnicodeV6 } from 'common/input/UnicodeV6'; + +it('wcwidth should match all values from the old implementation', function(): void { + // new implementation + const wcwidthNew = (new UnicodeV6()).wcwidth; + + // old implementation + const wcwidthOld = (function(opts: {nul: number, control: number}): (ucs: number) => number { + // extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c + // combining characters + const COMBINING_BMP = [ + [0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489], + [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2], + [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603], + [0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670], + [0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED], + [0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A], + [0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902], + [0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D], + [0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981], + [0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD], + [0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C], + [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D], + [0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC], + [0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD], + [0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C], + [0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D], + [0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0], + [0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48], + [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC], + [0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD], + [0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D], + [0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6], + [0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E], + [0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC], + [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35], + [0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E], + [0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97], + [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030], + [0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039], + [0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F], + [0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753], + [0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD], + [0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD], + [0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922], + [0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B], + [0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34], + [0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42], + [0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF], + [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063], + [0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F], + [0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B], + [0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F], + [0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB] + ]; + const COMBINING_HIGH = [ + [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F], + [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169], + [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD], + [0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F], + [0xE0100, 0xE01EF] + ]; + // binary search + function bisearch(ucs: number, data: number[][]): boolean { + let min = 0; + let max = data.length - 1; + let mid; + if (ucs < data[0][0] || ucs > data[max][1]) { + return false; + } + while (max >= min) { + mid = (min + max) >> 1; + if (ucs > data[mid][1]) { + min = mid + 1; + } else if (ucs < data[mid][0]) { + max = mid - 1; + } else { + return true; + } + } + return false; + } + function wcwidthBMP(ucs: number): number { + // test for 8-bit control characters + if (ucs === 0) { + return opts.nul; + } + if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) { + return opts.control; + } + // binary search in table of non-spacing characters + if (bisearch(ucs, COMBINING_BMP)) { + return 0; + } + // if we arrive here, ucs is not a combining or C0/C1 control character + if (isWideBMP(ucs)) { + return 2; + } + return 1; + } + function isWideBMP(ucs: number): boolean { + return ( + ucs >= 0x1100 && ( + ucs <= 0x115f || // Hangul Jamo init. consonants + ucs === 0x2329 || + ucs === 0x232a || + (ucs >= 0x2e80 && ucs <= 0xa4cf && ucs !== 0x303f) || // CJK..Yi + (ucs >= 0xac00 && ucs <= 0xd7a3) || // Hangul Syllables + (ucs >= 0xf900 && ucs <= 0xfaff) || // CJK Compat Ideographs + (ucs >= 0xfe10 && ucs <= 0xfe19) || // Vertical forms + (ucs >= 0xfe30 && ucs <= 0xfe6f) || // CJK Compat Forms + (ucs >= 0xff00 && ucs <= 0xff60) || // Fullwidth Forms + (ucs >= 0xffe0 && ucs <= 0xffe6))); + } + function wcwidthHigh(ucs: number): 0 | 1 | 2 { + if (bisearch(ucs, COMBINING_HIGH)) { + return 0; + } + if ((ucs >= 0x20000 && ucs <= 0x2fffd) || (ucs >= 0x30000 && ucs <= 0x3fffd)) { + return 2; + } + return 1; + } + const control = opts.control | 0; + let table: number[] | Uint32Array | undefined; + function initTable(): number[] | Uint32Array { + // lookup table for BMP + const CODEPOINTS = 65536; // BMP holds 65536 codepoints + const BITWIDTH = 2; // a codepoint can have a width of 0, 1 or 2 + const ITEMSIZE = 32; // using uint32_t + const CONTAINERSIZE = CODEPOINTS * BITWIDTH / ITEMSIZE; + const CODEPOINTS_PER_ITEM = ITEMSIZE / BITWIDTH; + table = (typeof Uint32Array === 'undefined') + ? new Array(CONTAINERSIZE) + : new Uint32Array(CONTAINERSIZE); + for (let i = 0; i < CONTAINERSIZE; ++i) { + let num = 0; + let pos = CODEPOINTS_PER_ITEM; + while (pos--) { + num = (num << 2) | wcwidthBMP(CODEPOINTS_PER_ITEM * i + pos); + } + table[i] = num; + } + return table; + } + // get width from lookup table + // position in container : num / CODEPOINTS_PER_ITEM + // ==> n = table[Math.floor(num / 16)] + // ==> n = table[num >> 4] + // 16 codepoints per number: FFEEDDCCBBAA99887766554433221100 + // position in number : (num % CODEPOINTS_PER_ITEM) * BITWIDTH + // ==> m = (n % 16) * 2 + // ==> m = (num & 15) << 1 + // right shift to position m + // ==> n = n >> m e.g. m=12 000000000000FFEEDDCCBBAA99887766 + // we are only interested in 2 LSBs, cut off higher bits + // ==> n = n & 3 e.g. 000000000000000000000000000000XX + return function (num: number): number { + num = num | 0; // get asm.js like optimization under V8 + if (num < 32) { + return control | 0; + } + if (num < 127) { + return 1; + } + const t = table || initTable(); + if (num < 65536) { + return t[num >> 4] >> ((num & 15) << 1) & 3; + } + // do a full search for high codepoints + return wcwidthHigh(num); + }; + })({nul: 0, control: 0}); // configurable options + + // test full BMP range old vs new implmenetation + for (let i = 0; i < 65536; ++i) { + assert.equal(wcwidthNew(i), wcwidthOld(i), `mismatch for i: ${i}`); + } +}); diff --git a/src/common/input/UnicodeV6.ts b/src/common/input/UnicodeV6.ts new file mode 100644 index 000000000..56ee71fd4 --- /dev/null +++ b/src/common/input/UnicodeV6.ts @@ -0,0 +1,129 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ +import { IUnicodeVersionProvider } from 'common/services/Services'; +import { fill } from 'common/TypedArrayUtils'; + +export class UnicodeV6 implements IUnicodeVersionProvider { + static bmpCombining = [ + [0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489], + [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2], + [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603], + [0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670], + [0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED], + [0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A], + [0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902], + [0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D], + [0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981], + [0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD], + [0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C], + [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D], + [0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC], + [0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD], + [0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C], + [0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D], + [0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0], + [0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48], + [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC], + [0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD], + [0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D], + [0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6], + [0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E], + [0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC], + [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35], + [0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E], + [0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97], + [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030], + [0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039], + [0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F], + [0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753], + [0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD], + [0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD], + [0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922], + [0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B], + [0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34], + [0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42], + [0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF], + [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063], + [0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F], + [0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B], + [0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F], + [0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB] + ]; + static highCombining = [ + [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F], + [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169], + [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD], + [0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F], + [0xE0100, 0xE01EF] + ]; + static bmpTable: Uint8Array; + + static bisearch(ucs: number, data: number[][]): boolean { + let min = 0; + let max = data.length - 1; + let mid; + if (ucs < data[0][0] || ucs > data[max][1]) { + return false; + } + while (max >= min) { + mid = (min + max) >> 1; + if (ucs > data[mid][1]) { + min = mid + 1; + } else if (ucs < data[mid][0]) { + max = mid - 1; + } else { + return true; + } + } + return false; + } + + public version = '6'; + + constructor() { + // init lookup table once + if (!UnicodeV6.bmpTable) { + UnicodeV6.bmpTable = new Uint8Array(65536); + fill(UnicodeV6.bmpTable, 1); + UnicodeV6.bmpTable[0] = 0; + // control chars + fill(UnicodeV6.bmpTable, 0, 1, 32); + fill(UnicodeV6.bmpTable, 0, 0x7f, 0xa0); + + // apply wide char rules first + // wide chars + fill(UnicodeV6.bmpTable, 2, 0x1100, 0x1160); + UnicodeV6.bmpTable[0x2329] = 2; + UnicodeV6.bmpTable[0x232a] = 2; + fill(UnicodeV6.bmpTable, 2, 0x2e80, 0xa4d0); + UnicodeV6.bmpTable[0x303f] = 1; // wrongly in last line + + fill(UnicodeV6.bmpTable, 2, 0xac00, 0xd7a4); + fill(UnicodeV6.bmpTable, 2, 0xf900, 0xfb00); + fill(UnicodeV6.bmpTable, 2, 0xfe10, 0xfe1a); + fill(UnicodeV6.bmpTable, 2, 0xfe30, 0xfe70); + fill(UnicodeV6.bmpTable, 2, 0xff00, 0xff61); + fill(UnicodeV6.bmpTable, 2, 0xffe0, 0xffe7); + + // apply combining last to ensure we overwrite + // wrongly wide set chars: + // the original algo evals combining first and falls + // through to wide check so we simply do here the opposite + // combining 0 + for (let r = 0; r < UnicodeV6.bmpCombining.length; ++r) { + fill(UnicodeV6.bmpTable, 0, UnicodeV6.bmpCombining[r][0], UnicodeV6.bmpCombining[r][1] + 1); + } + } + } + + public wcwidth(num: number): number { + if (num < 32) return 0; + if (num < 127) return 1; + if (num < 65536) return UnicodeV6.bmpTable[num]; + if (UnicodeV6.bisearch(num, UnicodeV6.highCombining)) return 0; + if ((num >= 0x20000 && num <= 0x2fffd) || (num >= 0x30000 && num <= 0x3fffd)) return 2; + return 1; + } +} diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 10df5924e..554e5e963 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -285,15 +285,13 @@ export interface ITheme { export const IUnicodeService = createDecorator('UnicodeService'); export interface IUnicodeService { /** Register an Unicode version provider. */ - registerVersion(provider: IUnicodeVersionProvider): void; + register(provider: IUnicodeVersionProvider): void; /** Registered Unicode versions. */ readonly versions: string[]; /** Currently active version. */ readonly activeVersion: string; /** Event triggered, when activate version changed. */ readonly onChange: IEvent; - /** Event triggered, when a new version provider was registered. */ - readonly onRegister: IEvent; /** * Unicode version dependent diff --git a/src/common/services/UnicodeService.test.ts b/src/common/services/UnicodeService.test.ts new file mode 100644 index 000000000..7df726052 --- /dev/null +++ b/src/common/services/UnicodeService.test.ts @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert } from 'chai'; +import { UnicodeService } from 'common/services/UnicodeService'; +import { IUnicodeVersionProvider } from 'common/services/Services'; + +class DummyProvider implements IUnicodeVersionProvider { + version = '123'; + wcwidth(n: number): number { + return 123; + } +} + +describe('unicode provider', () => { + let us: UnicodeService; + beforeEach(() => { + us = new UnicodeService(); + }); + it('default to V6', () => { + assert.equal(us.activeVersion, '6'); + assert.deepEqual(us.versions, ['6']); + assert.doesNotThrow(() => { us.activeVersion = '6'; }, `unknown Unicode version "6"`); + assert.equal(us.getStringCellWidth('hello'), 5); + }); + it('activate should throw for unknown version', () => { + assert.throws(() => { us.activeVersion = '55'; }, 'unknown Unicode version "55"'); + }); + it('should notify about version change', () => { + const notes: string[] = []; + us.onChange(version => notes.push(version)); + const dummyProvider = new DummyProvider(); + us.register(dummyProvider); + us.activeVersion = dummyProvider.version; + assert.deepEqual(notes, [dummyProvider.version]); + }); + it('correctly changes provider impl', () => { + assert.equal(us.getStringCellWidth('hello'), 5); + const dummyProvider = new DummyProvider(); + us.register(dummyProvider); + us.activeVersion = dummyProvider.version; + assert.equal(us.getStringCellWidth('hello'), 123 * 5); + }); + it('wcwidth V6 emoji test', () => { + const widthV6 = us.getStringCellWidth('🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣'); + assert.equal(widthV6, 10); + }); +}); diff --git a/src/common/services/UnicodeService.ts b/src/common/services/UnicodeService.ts index 35e256d99..14b9979f6 100644 --- a/src/common/services/UnicodeService.ts +++ b/src/common/services/UnicodeService.ts @@ -4,16 +4,23 @@ */ import { IUnicodeService, IUnicodeVersionProvider } from 'common/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { UnicodeV6 } from 'common/input/UnicodeV6'; export class UnicodeService implements IUnicodeService { private _providers: {[key: string]: IUnicodeVersionProvider} = Object.create(null); private _active: string = ''; - private _onRegister = new EventEmitter(); - public get onRegister(): IEvent { return this._onRegister.event; } + private _activeProvider: IUnicodeVersionProvider; private _onChange = new EventEmitter(); public get onChange(): IEvent { return this._onChange.event; } + constructor() { + const defaultProvider = new UnicodeV6(); + this.register(defaultProvider); + this._active = defaultProvider.version; + this._activeProvider = defaultProvider; + } + public get versions(): string[] { return Object.keys(this._providers); } @@ -27,22 +34,20 @@ export class UnicodeService implements IUnicodeService { throw new Error(`unknown Unicode version "${version}"`); } this._active = version; - this.wcwidth = (num: number) => this._providers[version].wcwidth(num); // perf an issue here? + this._activeProvider = this._providers[version]; this._onChange.fire(version); } - public registerVersion(provider: IUnicodeVersionProvider): void { + public register(provider: IUnicodeVersionProvider): void { this._providers[provider.version] = provider; - this._onRegister.fire(provider.version); - if (!this.activeVersion) { - this.activeVersion = provider.version; - } } /** * Unicode version dependent interface. */ - public wcwidth: (num: number) => number = () => { throw new Error('no Unicode version registered and activated'); } + public wcwidth(num: number): number { + return this._activeProvider.wcwidth(num); + } public getStringCellWidth(s: string): number { let result = 0; From 43303ba4c1c10ccb62fe0e0fdfa85807f7903325 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 5 Dec 2019 21:30:59 +0100 Subject: [PATCH 159/268] add interfaces and option handling --- src/Terminal.ts | 5 ++++ src/common/input/UnicodeV11.ts | 4 +-- src/common/input/UnicodeV6.ts | 4 +-- src/common/services/Services.ts | 4 +-- src/common/services/UnicodeService.test.ts | 6 ++--- src/public/Terminal.ts | 23 +++++++++++++++- typings/xterm.d.ts | 31 ++++++++++++++++++++++ 7 files changed, 67 insertions(+), 10 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index b2586df93..5a655da77 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -391,6 +391,11 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._windowsMode = undefined; } break; + case 'unicodeVersion': + if (this._unicodeService.activeVersion !== this.optionsService.options.unicodeVersion) { + this._unicodeService.activeVersion = this.optionsService.options.unicodeVersion; + } + break; } }); } diff --git a/src/common/input/UnicodeV11.ts b/src/common/input/UnicodeV11.ts index 6393e3f5e..b3ee56d35 100644 --- a/src/common/input/UnicodeV11.ts +++ b/src/common/input/UnicodeV11.ts @@ -205,10 +205,10 @@ export class UnicodeV11 implements IUnicodeVersionProvider { } } - public wcwidth(num: number): number { + public wcwidth(num: number): 0 | 1 | 2 { if (num < 32) return 0; if (num < 127) return 1; - if (num < 65536) return UnicodeV11.bmpTable[num]; + if (num < 65536) return UnicodeV11.bmpTable[num] as 0 | 1 | 2; if (UnicodeV11.bisearch(num, UnicodeV11.highCombining)) return 0; if (UnicodeV11.bisearch(num, UnicodeV11.highWide)) return 2; return 1; diff --git a/src/common/input/UnicodeV6.ts b/src/common/input/UnicodeV6.ts index 56ee71fd4..af5089b25 100644 --- a/src/common/input/UnicodeV6.ts +++ b/src/common/input/UnicodeV6.ts @@ -118,10 +118,10 @@ export class UnicodeV6 implements IUnicodeVersionProvider { } } - public wcwidth(num: number): number { + public wcwidth(num: number): 0 | 1 | 2 { if (num < 32) return 0; if (num < 127) return 1; - if (num < 65536) return UnicodeV6.bmpTable[num]; + if (num < 65536) return UnicodeV6.bmpTable[num] as 0 | 1 | 2; if (UnicodeV6.bisearch(num, UnicodeV6.highCombining)) return 0; if ((num >= 0x20000 && num <= 0x2fffd) || (num >= 0x30000 && num <= 0x3fffd)) return 2; return 1; diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 554e5e963..9ee348fb1 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -289,7 +289,7 @@ export interface IUnicodeService { /** Registered Unicode versions. */ readonly versions: string[]; /** Currently active version. */ - readonly activeVersion: string; + activeVersion: string; /** Event triggered, when activate version changed. */ readonly onChange: IEvent; @@ -302,5 +302,5 @@ export interface IUnicodeService { export interface IUnicodeVersionProvider { version: string; - wcwidth(ucs: number): number; + wcwidth(ucs: number): 0 | 1 | 2; } diff --git a/src/common/services/UnicodeService.test.ts b/src/common/services/UnicodeService.test.ts index 7df726052..cb03b09b0 100644 --- a/src/common/services/UnicodeService.test.ts +++ b/src/common/services/UnicodeService.test.ts @@ -9,8 +9,8 @@ import { IUnicodeVersionProvider } from 'common/services/Services'; class DummyProvider implements IUnicodeVersionProvider { version = '123'; - wcwidth(n: number): number { - return 123; + wcwidth(n: number): 0 | 1 | 2 { + return 2; } } @@ -41,7 +41,7 @@ describe('unicode provider', () => { const dummyProvider = new DummyProvider(); us.register(dummyProvider); us.activeVersion = dummyProvider.version; - assert.equal(us.getStringCellWidth('hello'), 123 * 5); + assert.equal(us.getStringCellWidth('hello'), 2 * 5); }); it('wcwidth V6 emoji test', () => { const widthV6 = us.getStringCellWidth('🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣'); diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 397898c7e..75c35e84b 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, IParser, IFunctionIdentifier } from 'xterm'; +import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi, IParser, IFunctionIdentifier, IUnicodeHandling, IUnicodeVersionProvider } from 'xterm'; import { ITerminal } from '../Types'; import { IBufferLine } from 'common/Types'; import { IBuffer } from 'common/buffer/Types'; @@ -41,6 +41,9 @@ export class Terminal implements ITerminalApi { } return this._parser; } + public get unicode(): IUnicodeHandling { + return new UnicodeApi(this._core); + } public get textarea(): HTMLTextAreaElement | undefined { return this._core.textarea; } public get rows(): number { return this._core.rows; } public get cols(): number { return this._core.cols; } @@ -240,3 +243,21 @@ class ParserApi implements IParser { return this._core.addOscHandler(ident, callback); } } + +class UnicodeApi implements IUnicodeHandling { + constructor(private _core: ITerminal) {} + + public register(provider: IUnicodeVersionProvider): void {} + + public get versions(): string[] { + return (this._core as any)._unicodeService.versions; + } + + public get activeVersion(): string { + return (this._core as any)._unicodeService.activeVersion; + } + + public set activeVersion(version: string) { + (this._core as any)._unicodeService.activeVersion = version; + } +} diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index a00bdcca7..a1ecbbfff 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -426,6 +426,12 @@ declare module 'xterm' { */ readonly parser: IParser; + /** + * (EXPERIMENTAL) Get the Unicode handling interface + * to register and switch Unicode version. + */ + readonly unicode: IUnicodeHandling; + /** * Natural language strings that can be localized. */ @@ -1130,4 +1136,29 @@ declare module 'xterm' { */ addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable; } + + /** + * (EXPERIMENTAL) Unicode version provider. + * Used to register custom Unicode versions with `Terminal.unicode.register`. + */ + export interface IUnicodeVersionProvider { + /** String indicating the Unicode version provided. */ + version: string; + /** Unicode version dependent wcwidth implementation. */ + wcwidth(ucs: number): 0 | 1 | 2; + } + + /** + * (EXPERIMENTAL) Unicode handling interface. + */ + export interface IUnicodeHandling { + /** + * Register a custom Unicode version provider. + */ + register(provider: IUnicodeVersionProvider): void; + /** + * Registered Unicode versions. + */ + versions: string[]; + } } From c9babb9477324e49d5ce7a0a3f087b8f29755741 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 5 Dec 2019 12:34:36 -0800 Subject: [PATCH 160/268] v4.3.0 --- addons/xterm-addon-attach/package.json | 2 +- addons/xterm-addon-search/package.json | 2 +- addons/xterm-addon-webgl/package.json | 2 +- package.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/xterm-addon-attach/package.json b/addons/xterm-addon-attach/package.json index 5718f356a..dd134308a 100644 --- a/addons/xterm-addon-attach/package.json +++ b/addons/xterm-addon-attach/package.json @@ -1,6 +1,6 @@ { "name": "xterm-addon-attach", - "version": "0.3.0", + "version": "0.4.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" diff --git a/addons/xterm-addon-search/package.json b/addons/xterm-addon-search/package.json index c426ee3b6..d66e8a687 100644 --- a/addons/xterm-addon-search/package.json +++ b/addons/xterm-addon-search/package.json @@ -1,6 +1,6 @@ { "name": "xterm-addon-search", - "version": "0.3.0", + "version": "0.4.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" diff --git a/addons/xterm-addon-webgl/package.json b/addons/xterm-addon-webgl/package.json index a6befe080..21e394d11 100644 --- a/addons/xterm-addon-webgl/package.json +++ b/addons/xterm-addon-webgl/package.json @@ -1,6 +1,6 @@ { "name": "xterm-addon-webgl", - "version": "0.3.0", + "version": "0.4.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" diff --git a/package.json b/package.json index c37594afc..e04a1ff83 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "xterm", "description": "Full xterm terminal, in your browser", - "version": "4.2.0", + "version": "4.3.0", "main": "lib/xterm.js", "style": "css/xterm.css", "types": "typings/xterm.d.ts", From 303abf8a9133d0a3754c61cdf60c839940cdd8db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 6 Dec 2019 16:28:16 +0100 Subject: [PATCH 161/268] unicode11 addon --- addons/xterm-addon-unicode11/.gitignore | 2 + addons/xterm-addon-unicode11/.npmignore | 5 + addons/xterm-addon-unicode11/LICENSE | 19 ++ addons/xterm-addon-unicode11/README.md | 20 ++ addons/xterm-addon-unicode11/package.json | 21 ++ .../src/Unicode11Addon.api.ts | 61 ++++ .../src/Unicode11Addon.ts | 267 ++++++++++++++++++ .../xterm-addon-unicode11/src/tsconfig.json | 19 ++ .../typings/xterm-addon-unicode11.d.ts | 14 + .../xterm-addon-unicode11/webpack.config.js | 31 ++ demo/client.ts | 15 +- src/public/Terminal.ts | 4 +- tsconfig.all.json | 3 +- 13 files changed, 476 insertions(+), 5 deletions(-) create mode 100644 addons/xterm-addon-unicode11/.gitignore create mode 100644 addons/xterm-addon-unicode11/.npmignore create mode 100644 addons/xterm-addon-unicode11/LICENSE create mode 100644 addons/xterm-addon-unicode11/README.md create mode 100644 addons/xterm-addon-unicode11/package.json create mode 100644 addons/xterm-addon-unicode11/src/Unicode11Addon.api.ts create mode 100644 addons/xterm-addon-unicode11/src/Unicode11Addon.ts create mode 100644 addons/xterm-addon-unicode11/src/tsconfig.json create mode 100644 addons/xterm-addon-unicode11/typings/xterm-addon-unicode11.d.ts create mode 100644 addons/xterm-addon-unicode11/webpack.config.js diff --git a/addons/xterm-addon-unicode11/.gitignore b/addons/xterm-addon-unicode11/.gitignore new file mode 100644 index 000000000..3063f07d5 --- /dev/null +++ b/addons/xterm-addon-unicode11/.gitignore @@ -0,0 +1,2 @@ +lib +node_modules diff --git a/addons/xterm-addon-unicode11/.npmignore b/addons/xterm-addon-unicode11/.npmignore new file mode 100644 index 000000000..1c7944456 --- /dev/null +++ b/addons/xterm-addon-unicode11/.npmignore @@ -0,0 +1,5 @@ +**/*.api.js +**/*.api.ts +tsconfig.json +.yarnrc +webpack.config.js diff --git a/addons/xterm-addon-unicode11/LICENSE b/addons/xterm-addon-unicode11/LICENSE new file mode 100644 index 000000000..8f1789258 --- /dev/null +++ b/addons/xterm-addon-unicode11/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2019, The xterm.js authors (https://github.com/xtermjs/xterm.js) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/addons/xterm-addon-unicode11/README.md b/addons/xterm-addon-unicode11/README.md new file mode 100644 index 000000000..eff46b395 --- /dev/null +++ b/addons/xterm-addon-unicode11/README.md @@ -0,0 +1,20 @@ +## xterm-addon-unicode11 + +An addon providing Unicode version 11 rules for xterm.js. + +### Install + +```bash +npm install --save xterm-addon-unicode11 +``` + +### Usage + +```ts +import { Terminal } from 'xterm'; +import { Unicode11Addon } from 'xterm-addon-unicode11'; + +const terminal = new Terminal(); +const unicode11Addon = new Unicode11Addon(); +terminal.loadAddon(unicode11Addon); +``` diff --git a/addons/xterm-addon-unicode11/package.json b/addons/xterm-addon-unicode11/package.json new file mode 100644 index 000000000..0aaa4a1de --- /dev/null +++ b/addons/xterm-addon-unicode11/package.json @@ -0,0 +1,21 @@ +{ + "name": "xterm-addon-unicode11", + "version": "0.1.0", + "author": { + "name": "The xterm.js authors", + "url": "https://xtermjs.org/" + }, + "main": "lib/xterm-addon-unicode11.js", + "types": "typings/xterm-addon-unicode11.d.ts", + "repository": "https://github.com/xtermjs/xterm.js", + "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": "^4.0.0" + } +} diff --git a/addons/xterm-addon-unicode11/src/Unicode11Addon.api.ts b/addons/xterm-addon-unicode11/src/Unicode11Addon.api.ts new file mode 100644 index 000000000..a731e7bba --- /dev/null +++ b/addons/xterm-addon-unicode11/src/Unicode11Addon.api.ts @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import * as puppeteer from 'puppeteer'; +import { ITerminalOptions } from 'xterm'; +import { assert } from 'chai'; + +const APP = 'http://127.0.0.1:3000/test'; + +let browser: puppeteer.Browser; +let page: puppeteer.Page; +const width = 800; +const height = 600; + +describe('Unicode11Addon', () => { + before(async function(): Promise { + this.timeout(20000); + browser = await puppeteer.launch({ + headless: process.argv.indexOf('--headless') !== -1, + args: [`--window-size=${width},${height}`, `--no-sandbox`] + }); + 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); + await openTerminal(); + }); + + it('wcwidth V11 emoji test', async () => { + await page.evaluate(` + window.unicode11 = new Unicode11Addon(); + window.term.loadAddon(window.unicode11); + `); + // should have loaded '11' + assert.deepEqual(await page.evaluate(`window.term.unicode.versions`), ['6', '11']); + // switch should not throw + await page.evaluate(`window.term.unicode.activeVersion = '11';`); + assert.deepEqual(await page.evaluate(`window.term.unicode.activeVersion`), '11'); + // v6: 10, V11: 20 + assert.deepEqual(await page.evaluate(`window.term._core._unicodeService.getStringCellWidth('🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣')`), 20); + }); +}); + +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'); + } +} diff --git a/addons/xterm-addon-unicode11/src/Unicode11Addon.ts b/addons/xterm-addon-unicode11/src/Unicode11Addon.ts new file mode 100644 index 000000000..926b5ad9f --- /dev/null +++ b/addons/xterm-addon-unicode11/src/Unicode11Addon.ts @@ -0,0 +1,267 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + * + * UnicodeVersionProvider for V11. + */ + +import { Terminal, ITerminalAddon, IUnicodeVersionProvider } from 'xterm'; + + +type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray + | Int8Array | Int16Array | Int32Array + | Float32Array | Float64Array; + + +/** + * polyfill for TypedArray.fill + * This is needed to support .fill in all safari versions and IE 11. + */ +function fill(array: T, value: number, start?: number, end?: number): T { + // all modern engines that support .fill + if (array.fill) { + return array.fill(value, start, end) as T; + } + return fillFallback(array, value, start, end); +} + +function fillFallback(array: T, value: number, start: number = 0, end: number = array.length): T { + // safari and IE 11 + // since IE 11 does not support Array.prototype.fill either + // we cannot use the suggested polyfill from MDN + // instead we simply fall back to looping + if (start >= array.length) { + return array; + } + start = (array.length + start) % array.length; + if (end >= array.length) { + end = array.length; + } else { + end = (array.length + end) % array.length; + } + for (let i = start; i < end; ++i) { + array[i] = value; + } + return array; +} + +class UnicodeV11 implements IUnicodeVersionProvider { + static bmpCombining = [ + [0x0300, 0x036F], [0x0483, 0x0489], [0x0591, 0x05BD], + [0x05BF, 0x05BF], [0x05C1, 0x05C2], [0x05C4, 0x05C5], + [0x05C7, 0x05C7], [0x0600, 0x0605], [0x0610, 0x061A], + [0x061C, 0x061C], [0x064B, 0x065F], [0x0670, 0x0670], + [0x06D6, 0x06DD], [0x06DF, 0x06E4], [0x06E7, 0x06E8], + [0x06EA, 0x06ED], [0x070F, 0x070F], [0x0711, 0x0711], + [0x0730, 0x074A], [0x07A6, 0x07B0], [0x07EB, 0x07F3], + [0x07FD, 0x07FD], [0x0816, 0x0819], [0x081B, 0x0823], + [0x0825, 0x0827], [0x0829, 0x082D], [0x0859, 0x085B], + [0x08D3, 0x0902], [0x093A, 0x093A], [0x093C, 0x093C], + [0x0941, 0x0948], [0x094D, 0x094D], [0x0951, 0x0957], + [0x0962, 0x0963], [0x0981, 0x0981], [0x09BC, 0x09BC], + [0x09C1, 0x09C4], [0x09CD, 0x09CD], [0x09E2, 0x09E3], + [0x09FE, 0x09FE], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C], + [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D], + [0x0A51, 0x0A51], [0x0A70, 0x0A71], [0x0A75, 0x0A75], + [0x0A81, 0x0A82], [0x0ABC, 0x0ABC], [0x0AC1, 0x0AC5], + [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD], [0x0AE2, 0x0AE3], + [0x0AFA, 0x0AFF], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C], + [0x0B3F, 0x0B3F], [0x0B41, 0x0B44], [0x0B4D, 0x0B4D], + [0x0B56, 0x0B56], [0x0B62, 0x0B63], [0x0B82, 0x0B82], + [0x0BC0, 0x0BC0], [0x0BCD, 0x0BCD], [0x0C00, 0x0C00], + [0x0C04, 0x0C04], [0x0C3E, 0x0C40], [0x0C46, 0x0C48], + [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0C62, 0x0C63], + [0x0C81, 0x0C81], [0x0CBC, 0x0CBC], [0x0CBF, 0x0CBF], + [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD], [0x0CE2, 0x0CE3], + [0x0D00, 0x0D01], [0x0D3B, 0x0D3C], [0x0D41, 0x0D44], + [0x0D4D, 0x0D4D], [0x0D62, 0x0D63], [0x0DCA, 0x0DCA], + [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6], [0x0E31, 0x0E31], + [0x0E34, 0x0E3A], [0x0E47, 0x0E4E], [0x0EB1, 0x0EB1], + [0x0EB4, 0x0EBC], [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], + [0x0F35, 0x0F35], [0x0F37, 0x0F37], [0x0F39, 0x0F39], + [0x0F71, 0x0F7E], [0x0F80, 0x0F84], [0x0F86, 0x0F87], + [0x0F8D, 0x0F97], [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], + [0x102D, 0x1030], [0x1032, 0x1037], [0x1039, 0x103A], + [0x103D, 0x103E], [0x1058, 0x1059], [0x105E, 0x1060], + [0x1071, 0x1074], [0x1082, 0x1082], [0x1085, 0x1086], + [0x108D, 0x108D], [0x109D, 0x109D], [0x1160, 0x11FF], + [0x135D, 0x135F], [0x1712, 0x1714], [0x1732, 0x1734], + [0x1752, 0x1753], [0x1772, 0x1773], [0x17B4, 0x17B5], + [0x17B7, 0x17BD], [0x17C6, 0x17C6], [0x17C9, 0x17D3], + [0x17DD, 0x17DD], [0x180B, 0x180E], [0x1885, 0x1886], + [0x18A9, 0x18A9], [0x1920, 0x1922], [0x1927, 0x1928], + [0x1932, 0x1932], [0x1939, 0x193B], [0x1A17, 0x1A18], + [0x1A1B, 0x1A1B], [0x1A56, 0x1A56], [0x1A58, 0x1A5E], + [0x1A60, 0x1A60], [0x1A62, 0x1A62], [0x1A65, 0x1A6C], + [0x1A73, 0x1A7C], [0x1A7F, 0x1A7F], [0x1AB0, 0x1ABE], + [0x1B00, 0x1B03], [0x1B34, 0x1B34], [0x1B36, 0x1B3A], + [0x1B3C, 0x1B3C], [0x1B42, 0x1B42], [0x1B6B, 0x1B73], + [0x1B80, 0x1B81], [0x1BA2, 0x1BA5], [0x1BA8, 0x1BA9], + [0x1BAB, 0x1BAD], [0x1BE6, 0x1BE6], [0x1BE8, 0x1BE9], + [0x1BED, 0x1BED], [0x1BEF, 0x1BF1], [0x1C2C, 0x1C33], + [0x1C36, 0x1C37], [0x1CD0, 0x1CD2], [0x1CD4, 0x1CE0], + [0x1CE2, 0x1CE8], [0x1CED, 0x1CED], [0x1CF4, 0x1CF4], + [0x1CF8, 0x1CF9], [0x1DC0, 0x1DF9], [0x1DFB, 0x1DFF], + [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2064], + [0x2066, 0x206F], [0x20D0, 0x20F0], [0x2CEF, 0x2CF1], + [0x2D7F, 0x2D7F], [0x2DE0, 0x2DFF], [0x302A, 0x302D], + [0x3099, 0x309A], [0xA66F, 0xA672], [0xA674, 0xA67D], + [0xA69E, 0xA69F], [0xA6F0, 0xA6F1], [0xA802, 0xA802], + [0xA806, 0xA806], [0xA80B, 0xA80B], [0xA825, 0xA826], + [0xA8C4, 0xA8C5], [0xA8E0, 0xA8F1], [0xA8FF, 0xA8FF], + [0xA926, 0xA92D], [0xA947, 0xA951], [0xA980, 0xA982], + [0xA9B3, 0xA9B3], [0xA9B6, 0xA9B9], [0xA9BC, 0xA9BD], + [0xA9E5, 0xA9E5], [0xAA29, 0xAA2E], [0xAA31, 0xAA32], + [0xAA35, 0xAA36], [0xAA43, 0xAA43], [0xAA4C, 0xAA4C], + [0xAA7C, 0xAA7C], [0xAAB0, 0xAAB0], [0xAAB2, 0xAAB4], + [0xAAB7, 0xAAB8], [0xAABE, 0xAABF], [0xAAC1, 0xAAC1], + [0xAAEC, 0xAAED], [0xAAF6, 0xAAF6], [0xABE5, 0xABE5], + [0xABE8, 0xABE8], [0xABED, 0xABED], [0xFB1E, 0xFB1E], + [0xFE00, 0xFE0F], [0xFE20, 0xFE2F], [0xFEFF, 0xFEFF], + [0xFFF9, 0xFFFB] + ]; + static highCombining = [ + [0x101FD, 0x101FD], [0x102E0, 0x102E0], + [0x10376, 0x1037A], [0x10A01, 0x10A03], [0x10A05, 0x10A06], + [0x10A0C, 0x10A0F], [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], + [0x10AE5, 0x10AE6], [0x10D24, 0x10D27], [0x10F46, 0x10F50], + [0x11001, 0x11001], [0x11038, 0x11046], [0x1107F, 0x11081], + [0x110B3, 0x110B6], [0x110B9, 0x110BA], [0x110BD, 0x110BD], + [0x110CD, 0x110CD], [0x11100, 0x11102], [0x11127, 0x1112B], + [0x1112D, 0x11134], [0x11173, 0x11173], [0x11180, 0x11181], + [0x111B6, 0x111BE], [0x111C9, 0x111CC], [0x1122F, 0x11231], + [0x11234, 0x11234], [0x11236, 0x11237], [0x1123E, 0x1123E], + [0x112DF, 0x112DF], [0x112E3, 0x112EA], [0x11300, 0x11301], + [0x1133B, 0x1133C], [0x11340, 0x11340], [0x11366, 0x1136C], + [0x11370, 0x11374], [0x11438, 0x1143F], [0x11442, 0x11444], + [0x11446, 0x11446], [0x1145E, 0x1145E], [0x114B3, 0x114B8], + [0x114BA, 0x114BA], [0x114BF, 0x114C0], [0x114C2, 0x114C3], + [0x115B2, 0x115B5], [0x115BC, 0x115BD], [0x115BF, 0x115C0], + [0x115DC, 0x115DD], [0x11633, 0x1163A], [0x1163D, 0x1163D], + [0x1163F, 0x11640], [0x116AB, 0x116AB], [0x116AD, 0x116AD], + [0x116B0, 0x116B5], [0x116B7, 0x116B7], [0x1171D, 0x1171F], + [0x11722, 0x11725], [0x11727, 0x1172B], [0x1182F, 0x11837], + [0x11839, 0x1183A], [0x119D4, 0x119D7], [0x119DA, 0x119DB], + [0x119E0, 0x119E0], [0x11A01, 0x11A0A], [0x11A33, 0x11A38], + [0x11A3B, 0x11A3E], [0x11A47, 0x11A47], [0x11A51, 0x11A56], + [0x11A59, 0x11A5B], [0x11A8A, 0x11A96], [0x11A98, 0x11A99], + [0x11C30, 0x11C36], [0x11C38, 0x11C3D], [0x11C3F, 0x11C3F], + [0x11C92, 0x11CA7], [0x11CAA, 0x11CB0], [0x11CB2, 0x11CB3], + [0x11CB5, 0x11CB6], [0x11D31, 0x11D36], [0x11D3A, 0x11D3A], + [0x11D3C, 0x11D3D], [0x11D3F, 0x11D45], [0x11D47, 0x11D47], + [0x11D90, 0x11D91], [0x11D95, 0x11D95], [0x11D97, 0x11D97], + [0x11EF3, 0x11EF4], [0x13430, 0x13438], [0x16AF0, 0x16AF4], + [0x16B30, 0x16B36], [0x16F4F, 0x16F4F], [0x16F8F, 0x16F92], + [0x1BC9D, 0x1BC9E], [0x1BCA0, 0x1BCA3], [0x1D167, 0x1D169], + [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD], + [0x1D242, 0x1D244], [0x1DA00, 0x1DA36], [0x1DA3B, 0x1DA6C], + [0x1DA75, 0x1DA75], [0x1DA84, 0x1DA84], [0x1DA9B, 0x1DA9F], + [0x1DAA1, 0x1DAAF], [0x1E000, 0x1E006], [0x1E008, 0x1E018], + [0x1E01B, 0x1E021], [0x1E023, 0x1E024], [0x1E026, 0x1E02A], + [0x1E130, 0x1E136], [0x1E2EC, 0x1E2EF], [0x1E8D0, 0x1E8D6], + [0x1E944, 0x1E94A], [0xE0001, 0xE0001], [0xE0020, 0xE007F], + [0xE0100, 0xE01EF] + ]; + static bmpWide = [ + [0x1100, 0x115F], [0x231A, 0x231B], [0x2329, 0x232A], + [0x23E9, 0x23EC], [0x23F0, 0x23F0], [0x23F3, 0x23F3], + [0x25FD, 0x25FE], [0x2614, 0x2615], [0x2648, 0x2653], + [0x267F, 0x267F], [0x2693, 0x2693], [0x26A1, 0x26A1], + [0x26AA, 0x26AB], [0x26BD, 0x26BE], [0x26C4, 0x26C5], + [0x26CE, 0x26CE], [0x26D4, 0x26D4], [0x26EA, 0x26EA], + [0x26F2, 0x26F3], [0x26F5, 0x26F5], [0x26FA, 0x26FA], + [0x26FD, 0x26FD], [0x2705, 0x2705], [0x270A, 0x270B], + [0x2728, 0x2728], [0x274C, 0x274C], [0x274E, 0x274E], + [0x2753, 0x2755], [0x2757, 0x2757], [0x2795, 0x2797], + [0x27B0, 0x27B0], [0x27BF, 0x27BF], [0x2B1B, 0x2B1C], + [0x2B50, 0x2B50], [0x2B55, 0x2B55], [0x2E80, 0x2E99], + [0x2E9B, 0x2EF3], [0x2F00, 0x2FD5], [0x2FF0, 0x2FFB], + [0x3000, 0x3029], [0x302E, 0x303E], [0x3041, 0x3096], + [0x309B, 0x30FF], [0x3105, 0x312F], [0x3131, 0x318E], + [0x3190, 0x31BA], [0x31C0, 0x31E3], [0x31F0, 0x321E], + [0x3220, 0x3247], [0x3250, 0x4DBF], [0x4E00, 0xA48C], + [0xA490, 0xA4C6], [0xA960, 0xA97C], [0xAC00, 0xD7A3], + [0xF900, 0xFAFF], [0xFE10, 0xFE19], [0xFE30, 0xFE52], + [0xFE54, 0xFE66], [0xFE68, 0xFE6B], [0xFF01, 0xFF60], + [0xFFE0, 0xFFE6] + ]; + static highWide = [ + [0x16FE0, 0x16FE3], [0x17000, 0x187F7], + [0x18800, 0x18AF2], [0x1B000, 0x1B11E], [0x1B150, 0x1B152], + [0x1B164, 0x1B167], [0x1B170, 0x1B2FB], [0x1F004, 0x1F004], + [0x1F0CF, 0x1F0CF], [0x1F18E, 0x1F18E], [0x1F191, 0x1F19A], + [0x1F200, 0x1F202], [0x1F210, 0x1F23B], [0x1F240, 0x1F248], + [0x1F250, 0x1F251], [0x1F260, 0x1F265], [0x1F300, 0x1F320], + [0x1F32D, 0x1F335], [0x1F337, 0x1F37C], [0x1F37E, 0x1F393], + [0x1F3A0, 0x1F3CA], [0x1F3CF, 0x1F3D3], [0x1F3E0, 0x1F3F0], + [0x1F3F4, 0x1F3F4], [0x1F3F8, 0x1F43E], [0x1F440, 0x1F440], + [0x1F442, 0x1F4FC], [0x1F4FF, 0x1F53D], [0x1F54B, 0x1F54E], + [0x1F550, 0x1F567], [0x1F57A, 0x1F57A], [0x1F595, 0x1F596], + [0x1F5A4, 0x1F5A4], [0x1F5FB, 0x1F64F], [0x1F680, 0x1F6C5], + [0x1F6CC, 0x1F6CC], [0x1F6D0, 0x1F6D2], [0x1F6D5, 0x1F6D5], + [0x1F6EB, 0x1F6EC], [0x1F6F4, 0x1F6FA], [0x1F7E0, 0x1F7EB], + [0x1F90D, 0x1F971], [0x1F973, 0x1F976], [0x1F97A, 0x1F9A2], + [0x1F9A5, 0x1F9AA], [0x1F9AE, 0x1F9CA], [0x1F9CD, 0x1F9FF], + [0x1FA70, 0x1FA73], [0x1FA78, 0x1FA7A], [0x1FA80, 0x1FA82], + [0x1FA90, 0x1FA95], [0x20000, 0x2FFFD], [0x30000, 0x3FFFD] + ]; + static bmpTable: Uint8Array; + + static bisearch(ucs: number, data: number[][]): boolean { + let min = 0; + let max = data.length - 1; + let mid; + if (ucs < data[0][0] || ucs > data[max][1]) { + return false; + } + while (max >= min) { + mid = (min + max) >> 1; + if (ucs > data[mid][1]) { + min = mid + 1; + } else if (ucs < data[mid][0]) { + max = mid - 1; + } else { + return true; + } + } + return false; + } + + public version = '11'; + + constructor() { + if (!UnicodeV11.bmpTable) { + UnicodeV11.bmpTable = new Uint8Array(65536); + fill(UnicodeV11.bmpTable, 1); + UnicodeV11.bmpTable[0] = 0; + fill(UnicodeV11.bmpTable, 0, 1, 32); + fill(UnicodeV11.bmpTable, 0, 0x7f, 0xa0); + for (let r = 0; r < UnicodeV11.bmpCombining.length; ++r) { + fill(UnicodeV11.bmpTable, 0, UnicodeV11.bmpCombining[r][0], UnicodeV11.bmpCombining[r][1] + 1); + } + for (let r = 0; r < UnicodeV11.bmpWide.length; ++r) { + fill(UnicodeV11.bmpTable, 2, UnicodeV11.bmpWide[r][0], UnicodeV11.bmpWide[r][1] + 1); + } + } + } + + public wcwidth(num: number): 0 | 1 | 2 { + if (num < 32) return 0; + if (num < 127) return 1; + if (num < 65536) return UnicodeV11.bmpTable[num] as 0 | 1 | 2; + if (UnicodeV11.bisearch(num, UnicodeV11.highCombining)) return 0; + if (UnicodeV11.bisearch(num, UnicodeV11.highWide)) return 2; + return 1; + } +} + + +export class Unicode11Addon implements ITerminalAddon { + public activate(terminal: Terminal): void { + console.log('got we loaded?'); + (window as any)._v11 = new UnicodeV11(); + terminal.unicode.register(new UnicodeV11()); + } + + public dispose(): void { } +} diff --git a/addons/xterm-addon-unicode11/src/tsconfig.json b/addons/xterm-addon-unicode11/src/tsconfig.json new file mode 100644 index 000000000..d875aa530 --- /dev/null +++ b/addons/xterm-addon-unicode11/src/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es2015", + "lib": [ + "dom", + "es2015" + ], + "rootDir": ".", + "outDir": "../out", + "sourceMap": true, + "removeComments": true, + "strict": true + }, + "include": [ + "./**/*", + "../../../typings/xterm.d.ts" + ] +} diff --git a/addons/xterm-addon-unicode11/typings/xterm-addon-unicode11.d.ts b/addons/xterm-addon-unicode11/typings/xterm-addon-unicode11.d.ts new file mode 100644 index 000000000..1d0dce1b1 --- /dev/null +++ b/addons/xterm-addon-unicode11/typings/xterm-addon-unicode11.d.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { Terminal, ITerminalAddon } from 'xterm'; + +declare module 'xterm-addon-unicode11' { + export class Unicode11Addon implements ITerminalAddon { + constructor(); + public activate(terminal: Terminal): void; + public dispose(): void; + } +} diff --git a/addons/xterm-addon-unicode11/webpack.config.js b/addons/xterm-addon-unicode11/webpack.config.js new file mode 100644 index 000000000..07e9de298 --- /dev/null +++ b/addons/xterm-addon-unicode11/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 = 'Unicode11Addon'; +const mainFile = 'xterm-addon-unicode11.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 9f1c6f592..47828d0f1 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 { Unicode11Addon } from '../addons/xterm-addon-unicode11/out/Unicode11Addon'; // Use webpacked version (yarn package) // import { Terminal } from '../lib/xterm'; @@ -22,6 +23,7 @@ import { WebglAddon } from '../addons/xterm-addon-webgl/out/WebglAddon'; // import { SearchAddon, ISearchOptions } from 'xterm-addon-search'; // import { WebLinksAddon } from 'xterm-addon-web-links'; // import { WebglAddon } from 'xterm-addon-webgl'; +// import { Unicode11Addon } from 'xterm-addon-unicode11'; // Pulling in the module's types relies on the above, it's looks a // little weird here as we're importing "this" module @@ -35,6 +37,7 @@ export interface IWindowWithTerminal extends Window { SearchAddon?: typeof SearchAddon; WebLinksAddon?: typeof WebLinksAddon; WebglAddon?: typeof WebglAddon; + Unicode11Addon?: typeof Unicode11Addon; } declare let window: IWindowWithTerminal; @@ -44,7 +47,7 @@ let socketURL; let socket; let pid; -type AddonType = 'attach' | 'fit' | 'search' | 'web-links' | 'webgl'; +type AddonType = 'attach' | 'fit' | 'search' | 'web-links' | 'webgl' | 'unicode11'; interface IDemoAddon { name: T; @@ -54,6 +57,7 @@ interface IDemoAddon { T extends 'fit' ? typeof FitAddon : T extends 'search' ? typeof SearchAddon : T extends 'web-links' ? typeof WebLinksAddon : + T extends 'unicode11' ? typeof Unicode11Addon : typeof WebglAddon; instance?: T extends 'attach' ? AttachAddon : @@ -61,6 +65,7 @@ interface IDemoAddon { T extends 'search' ? SearchAddon : T extends 'web-links' ? WebLinksAddon : T extends 'webgl' ? WebglAddon : + T extends 'unicode11' ? typeof Unicode11Addon : never; } @@ -69,7 +74,8 @@ const addons: { [T in AddonType]: IDemoAddon} = { fit: { name: 'fit', ctor: FitAddon, canChange: false }, search: { name: 'search', ctor: SearchAddon, canChange: true }, 'web-links': { name: 'web-links', ctor: WebLinksAddon, canChange: true }, - webgl: { name: 'webgl', ctor: WebglAddon, canChange: true } + webgl: { name: 'webgl', ctor: WebglAddon, canChange: true }, + unicode11: { name: 'unicode11', ctor: Unicode11Addon, canChange: false } }; const terminalContainer = document.getElementById('terminal-container'); @@ -115,6 +121,7 @@ if (document.location.pathname === '/test') { window.SearchAddon = SearchAddon; window.WebLinksAddon = WebLinksAddon; window.WebglAddon = WebglAddon; + window.Unicode11Addon = Unicode11Addon; } else { createTerminal(); document.getElementById('dispose').addEventListener('click', disposeRecreateButtonHandler); @@ -136,9 +143,11 @@ function createTerminal(): void { addons['web-links'].instance = new WebLinksAddon(); addons.search.instance = new SearchAddon(); addons.fit.instance = new FitAddon(); + addons.unicode11.instance = new Unicode11Addon(); typedTerm.loadAddon(addons['web-links'].instance); typedTerm.loadAddon(addons.search.instance); typedTerm.loadAddon(addons.fit.instance); + typedTerm.loadAddon(addons.unicode11.instance); window.term = term; // Expose `term` to window for debugging purposes term.onResize((size: { cols: number, rows: number }) => { @@ -257,7 +266,7 @@ function initOptions(term: TerminalType): void { logLevel: ['debug', 'info', 'warn', 'error', 'off'], rendererType: ['dom', 'canvas'], wordSeparator: null, - unicodeVersion: ['6'] + unicodeVersion: ['6', '11'] }; const options = Object.keys((term)._core.options); const booleanOptions = []; diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 75c35e84b..74ec2fcd7 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -247,7 +247,9 @@ class ParserApi implements IParser { class UnicodeApi implements IUnicodeHandling { constructor(private _core: ITerminal) {} - public register(provider: IUnicodeVersionProvider): void {} + public register(provider: IUnicodeVersionProvider): void { + (this._core as any)._unicodeService.register(provider); + } public get versions(): string[] { return (this._core as any)._unicodeService.versions; diff --git a/tsconfig.all.json b/tsconfig.all.json index 7ddf33d51..efd968f3a 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-unicode11/src" } ] } From 794573969c52c734f73c7ed93df3c05a6b1da839 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 6 Dec 2019 16:44:20 +0100 Subject: [PATCH 162/268] remove CharWidth.ts --- src/Terminal.test.ts | 2 +- src/Terminal.ts | 2 +- src/browser/Linkifier.test.ts | 4 +- src/browser/Linkifier.ts | 9 +- src/common/CharWidth.test.ts | 189 ------------------ src/common/CharWidth.ts | 348 ---------------------------------- 6 files changed, 9 insertions(+), 545 deletions(-) delete mode 100644 src/common/CharWidth.test.ts delete mode 100644 src/common/CharWidth.ts diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index 266769ed0..422394e4c 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -1373,7 +1373,7 @@ describe('Terminal', () => { class TestLinkifier extends Linkifier { constructor(bufferService: IBufferService) { - super(bufferService, new MockLogService(), new MockOptionsService()); + super(bufferService, new MockLogService(), new MockOptionsService(), new MockUnicodeService()); Linkifier._timeBeforeLatency = 0; } diff --git a/src/Terminal.ts b/src/Terminal.ts index 5a655da77..ca075fa8a 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -294,7 +294,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._inputHandler.onLineFeed(() => this._onLineFeed.fire()); this.register(this._inputHandler); - this.linkifier = this.linkifier || new Linkifier(this._bufferService, this._logService, this.optionsService); + this.linkifier = this.linkifier || new Linkifier(this._bufferService, this._logService, this.optionsService, this._unicodeService); if (this.options.windowsMode) { this._enableWindowsMode(); diff --git a/src/browser/Linkifier.test.ts b/src/browser/Linkifier.test.ts index a4b4d2f55..9f155d522 100644 --- a/src/browser/Linkifier.test.ts +++ b/src/browser/Linkifier.test.ts @@ -9,12 +9,12 @@ import { IBufferLine } from 'common/Types'; import { Linkifier } from 'browser/Linkifier'; import { BufferLine } from 'common/buffer/BufferLine'; import { CellData } from 'common/buffer/CellData'; -import { MockLogService, MockBufferService, MockOptionsService } from 'common/TestUtils.test'; +import { MockLogService, MockBufferService, MockOptionsService, MockUnicodeService } from 'common/TestUtils.test'; import { IBufferService } from 'common/services/Services'; class TestLinkifier extends Linkifier { constructor(bufferService: IBufferService) { - super(bufferService, new MockLogService(), new MockOptionsService()); + super(bufferService, new MockLogService(), new MockOptionsService(), new MockUnicodeService()); Linkifier._timeBeforeLatency = 0; } diff --git a/src/browser/Linkifier.ts b/src/browser/Linkifier.ts index 3dc048c7c..30c76e8a3 100644 --- a/src/browser/Linkifier.ts +++ b/src/browser/Linkifier.ts @@ -5,9 +5,8 @@ import { ILinkifierEvent, ILinkMatcher, LinkMatcherHandler, ILinkMatcherOptions, ILinkifier, IMouseZoneManager, IMouseZone, IRegisteredLinkMatcher } from 'browser/Types'; import { IBufferStringIteratorResult } from 'common/buffer/Types'; -import { getStringCellWidthV6 } from 'common/CharWidth'; import { EventEmitter, IEvent } from 'common/EventEmitter'; -import { ILogService, IBufferService, IOptionsService } from 'common/services/Services'; +import { ILogService, IBufferService, IOptionsService, IUnicodeService } from 'common/services/Services'; /** * Limit of the unwrapping line expansion (overscan) at the top and bottom @@ -46,7 +45,8 @@ export class Linkifier implements ILinkifier { constructor( protected readonly _bufferService: IBufferService, private readonly _logService: ILogService, - private readonly _optionsService: IOptionsService + private readonly _optionsService: IOptionsService, + private readonly _unicodeService: IUnicodeService ) { this._rowsToLinkify = { start: undefined, @@ -278,7 +278,8 @@ export class Linkifier implements ILinkifier { if (!this._mouseZoneManager || !this._element) { return; } - const width = getStringCellWidthV6(uri); // FIXME: get cell length from buffer to avoid mismatch after version change + // FIXME: get cell length from buffer to avoid mismatch after Unicode version change + const width = this._unicodeService.getStringCellWidth(uri); const x1 = x % this._bufferService.cols; const y1 = y + Math.floor(x / this._bufferService.cols); let x2 = (x1 + width) % this._bufferService.cols; diff --git a/src/common/CharWidth.test.ts b/src/common/CharWidth.test.ts deleted file mode 100644 index ff6eb3a61..000000000 --- a/src/common/CharWidth.test.ts +++ /dev/null @@ -1,189 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { assert } from 'chai'; -import { wcwidthV6, getStringCellWidthV6, getStringCellWidthV11 } from 'common/CharWidth'; - -it('wcwidth should match all values from the old implementation', function(): void { - // old implementation - const wcwidthOld = (function(opts: {nul: number, control: number}): (ucs: number) => number { - // extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c - // combining characters - const COMBINING_BMP = [ - [0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489], - [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2], - [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603], - [0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670], - [0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED], - [0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A], - [0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902], - [0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D], - [0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981], - [0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD], - [0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C], - [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D], - [0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC], - [0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD], - [0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C], - [0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D], - [0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0], - [0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48], - [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC], - [0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD], - [0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D], - [0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6], - [0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E], - [0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC], - [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35], - [0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E], - [0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97], - [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030], - [0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039], - [0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F], - [0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753], - [0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD], - [0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD], - [0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922], - [0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B], - [0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34], - [0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42], - [0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF], - [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063], - [0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F], - [0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B], - [0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F], - [0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB] - ]; - const COMBINING_HIGH = [ - [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F], - [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169], - [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD], - [0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F], - [0xE0100, 0xE01EF] - ]; - // binary search - function bisearch(ucs: number, data: number[][]): boolean { - let min = 0; - let max = data.length - 1; - let mid; - if (ucs < data[0][0] || ucs > data[max][1]) { - return false; - } - while (max >= min) { - mid = (min + max) >> 1; - if (ucs > data[mid][1]) { - min = mid + 1; - } else if (ucs < data[mid][0]) { - max = mid - 1; - } else { - return true; - } - } - return false; - } - function wcwidthBMP(ucs: number): number { - // test for 8-bit control characters - if (ucs === 0) { - return opts.nul; - } - if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) { - return opts.control; - } - // binary search in table of non-spacing characters - if (bisearch(ucs, COMBINING_BMP)) { - return 0; - } - // if we arrive here, ucs is not a combining or C0/C1 control character - if (isWideBMP(ucs)) { - return 2; - } - return 1; - } - function isWideBMP(ucs: number): boolean { - return ( - ucs >= 0x1100 && ( - ucs <= 0x115f || // Hangul Jamo init. consonants - ucs === 0x2329 || - ucs === 0x232a || - (ucs >= 0x2e80 && ucs <= 0xa4cf && ucs !== 0x303f) || // CJK..Yi - (ucs >= 0xac00 && ucs <= 0xd7a3) || // Hangul Syllables - (ucs >= 0xf900 && ucs <= 0xfaff) || // CJK Compat Ideographs - (ucs >= 0xfe10 && ucs <= 0xfe19) || // Vertical forms - (ucs >= 0xfe30 && ucs <= 0xfe6f) || // CJK Compat Forms - (ucs >= 0xff00 && ucs <= 0xff60) || // Fullwidth Forms - (ucs >= 0xffe0 && ucs <= 0xffe6))); - } - function wcwidthHigh(ucs: number): 0 | 1 | 2 { - if (bisearch(ucs, COMBINING_HIGH)) { - return 0; - } - if ((ucs >= 0x20000 && ucs <= 0x2fffd) || (ucs >= 0x30000 && ucs <= 0x3fffd)) { - return 2; - } - return 1; - } - const control = opts.control | 0; - let table: number[] | Uint32Array | undefined; - function initTable(): number[] | Uint32Array { - // lookup table for BMP - const CODEPOINTS = 65536; // BMP holds 65536 codepoints - const BITWIDTH = 2; // a codepoint can have a width of 0, 1 or 2 - const ITEMSIZE = 32; // using uint32_t - const CONTAINERSIZE = CODEPOINTS * BITWIDTH / ITEMSIZE; - const CODEPOINTS_PER_ITEM = ITEMSIZE / BITWIDTH; - table = (typeof Uint32Array === 'undefined') - ? new Array(CONTAINERSIZE) - : new Uint32Array(CONTAINERSIZE); - for (let i = 0; i < CONTAINERSIZE; ++i) { - let num = 0; - let pos = CODEPOINTS_PER_ITEM; - while (pos--) { - num = (num << 2) | wcwidthBMP(CODEPOINTS_PER_ITEM * i + pos); - } - table[i] = num; - } - return table; - } - // get width from lookup table - // position in container : num / CODEPOINTS_PER_ITEM - // ==> n = table[Math.floor(num / 16)] - // ==> n = table[num >> 4] - // 16 codepoints per number: FFEEDDCCBBAA99887766554433221100 - // position in number : (num % CODEPOINTS_PER_ITEM) * BITWIDTH - // ==> m = (n % 16) * 2 - // ==> m = (num & 15) << 1 - // right shift to position m - // ==> n = n >> m e.g. m=12 000000000000FFEEDDCCBBAA99887766 - // we are only interested in 2 LSBs, cut off higher bits - // ==> n = n & 3 e.g. 000000000000000000000000000000XX - return function (num: number): number { - num = num | 0; // get asm.js like optimization under V8 - if (num < 32) { - return control | 0; - } - if (num < 127) { - return 1; - } - const t = table || initTable(); - if (num < 65536) { - return t[num >> 4] >> ((num & 15) << 1) & 3; - } - // do a full search for high codepoints - return wcwidthHigh(num); - }; - })({nul: 0, control: 0}); // configurable options - - // test full BMP range old vs new implmenetation - for (let i = 0; i < 65536; ++i) { - assert.equal(wcwidthV6(i), wcwidthOld(i), `mismatch for i: ${i}`); - } -}); - -it('wcwidth V6 vs. V11', () => { - const widthV6 = getStringCellWidthV6('🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣'); - assert.equal(widthV6, 10); - const widthV10 = getStringCellWidthV11('🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣'); - assert.equal(widthV10, 20); -}); diff --git a/src/common/CharWidth.ts b/src/common/CharWidth.ts deleted file mode 100644 index ca7cd2d10..000000000 --- a/src/common/CharWidth.ts +++ /dev/null @@ -1,348 +0,0 @@ -/** - * Copyright (c) 2016 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { fill } from 'common/TypedArrayUtils'; - -// helper: binary search -function bisearch(ucs: number, data: number[][]): boolean { - let min = 0; - let max = data.length - 1; - let mid; - if (ucs < data[0][0] || ucs > data[max][1]) { - return false; - } - while (max >= min) { - mid = (min + max) >> 1; - if (ucs > data[mid][1]) { - min = mid + 1; - } else if (ucs < data[mid][0]) { - max = mid - 1; - } else { - return true; - } - } - return false; -} - -function getStringCellWidthPrivate(wcwidthImpl: (cp: number) => number, s: string): number { - let result = 0; - const length = s.length; - for (let i = 0; i < length; ++i) { - let code = s.charCodeAt(i); - // surrogate pair first - if (0xD800 <= code && code <= 0xDBFF) { - if (++i >= length) { - // this should not happen with strings retrieved from - // Buffer.translateToString as it converts from UTF-32 - // and therefore always should contain the second part - // for any other string we still have to handle it somehow: - // simply treat the lonely surrogate first as a single char (UCS-2 behavior) - return result + wcwidthImpl(code); - } - const second = s.charCodeAt(i); - // convert surrogate pair to high codepoint only for valid second part (UTF-16) - // otherwise treat them independently (UCS-2 behavior) - if (0xDC00 <= second && second <= 0xDFFF) { - code = (code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; - } else { - result += wcwidthImpl(second); - } - } - result += wcwidthImpl(code); - } - return result; -} - -/** - * Unicode version 6 - */ -const BMP_COMBINING_V6 = [ - [0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489], - [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2], - [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603], - [0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670], - [0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED], - [0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A], - [0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902], - [0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D], - [0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981], - [0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD], - [0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C], - [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D], - [0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC], - [0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD], - [0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C], - [0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D], - [0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0], - [0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48], - [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC], - [0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD], - [0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D], - [0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6], - [0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E], - [0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC], - [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35], - [0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E], - [0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97], - [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030], - [0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039], - [0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F], - [0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753], - [0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD], - [0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD], - [0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922], - [0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B], - [0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34], - [0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42], - [0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF], - [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063], - [0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F], - [0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B], - [0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F], - [0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB] -]; -const HIGH_COMBINING_V6 = [ - [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F], - [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169], - [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD], - [0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F], - [0xE0100, 0xE01EF] -]; - -// create lookup table for BMP plane -const BMP_TABLE_V6 = new Uint8Array(65536); -fill(BMP_TABLE_V6, 1); -BMP_TABLE_V6[0] = 0; -// control chars -fill(BMP_TABLE_V6, 0, 1, 32); -fill(BMP_TABLE_V6, 0, 0x7f, 0xa0); - -// apply wide char rules first -// wide chars -fill(BMP_TABLE_V6, 2, 0x1100, 0x1160); -BMP_TABLE_V6[0x2329] = 2; -BMP_TABLE_V6[0x232a] = 2; -fill(BMP_TABLE_V6, 2, 0x2e80, 0xa4d0); -BMP_TABLE_V6[0x303f] = 1; // wrongly in last line - -fill(BMP_TABLE_V6, 2, 0xac00, 0xd7a4); -fill(BMP_TABLE_V6, 2, 0xf900, 0xfb00); -fill(BMP_TABLE_V6, 2, 0xfe10, 0xfe1a); -fill(BMP_TABLE_V6, 2, 0xfe30, 0xfe70); -fill(BMP_TABLE_V6, 2, 0xff00, 0xff61); -fill(BMP_TABLE_V6, 2, 0xffe0, 0xffe7); - -// apply combining last to ensure we overwrite -// wrongly wide set chars: -// the original algo evals combining first and falls -// through to wide check so we simply do here the opposite -// combining 0 -for (let r = 0; r < BMP_COMBINING_V6.length; ++r) { - fill(BMP_TABLE_V6, 0, BMP_COMBINING_V6[r][0], BMP_COMBINING_V6[r][1] + 1); -} - -export function wcwidthV6(num: number): number { - if (num < 32) return 0; - if (num < 127) return 1; - if (num < 65536) return BMP_TABLE_V6[num]; - if (bisearch(num, HIGH_COMBINING_V6)) return 0; - if ((num >= 0x20000 && num <= 0x2fffd) || (num >= 0x30000 && num <= 0x3fffd)) return 2; - return 1; -} - -export function getStringCellWidthV6(s: string): number { - return getStringCellWidthPrivate(wcwidthV6, s); -} - -/** - * Unicode version 10 - */ -const BMP_COMBINING_V11 = [ - [0x0300, 0x036F], [0x0483, 0x0489], [0x0591, 0x05BD], - [0x05BF, 0x05BF], [0x05C1, 0x05C2], [0x05C4, 0x05C5], - [0x05C7, 0x05C7], [0x0600, 0x0605], [0x0610, 0x061A], - [0x061C, 0x061C], [0x064B, 0x065F], [0x0670, 0x0670], - [0x06D6, 0x06DD], [0x06DF, 0x06E4], [0x06E7, 0x06E8], - [0x06EA, 0x06ED], [0x070F, 0x070F], [0x0711, 0x0711], - [0x0730, 0x074A], [0x07A6, 0x07B0], [0x07EB, 0x07F3], - [0x07FD, 0x07FD], [0x0816, 0x0819], [0x081B, 0x0823], - [0x0825, 0x0827], [0x0829, 0x082D], [0x0859, 0x085B], - [0x08D3, 0x0902], [0x093A, 0x093A], [0x093C, 0x093C], - [0x0941, 0x0948], [0x094D, 0x094D], [0x0951, 0x0957], - [0x0962, 0x0963], [0x0981, 0x0981], [0x09BC, 0x09BC], - [0x09C1, 0x09C4], [0x09CD, 0x09CD], [0x09E2, 0x09E3], - [0x09FE, 0x09FE], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C], - [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D], - [0x0A51, 0x0A51], [0x0A70, 0x0A71], [0x0A75, 0x0A75], - [0x0A81, 0x0A82], [0x0ABC, 0x0ABC], [0x0AC1, 0x0AC5], - [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD], [0x0AE2, 0x0AE3], - [0x0AFA, 0x0AFF], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C], - [0x0B3F, 0x0B3F], [0x0B41, 0x0B44], [0x0B4D, 0x0B4D], - [0x0B56, 0x0B56], [0x0B62, 0x0B63], [0x0B82, 0x0B82], - [0x0BC0, 0x0BC0], [0x0BCD, 0x0BCD], [0x0C00, 0x0C00], - [0x0C04, 0x0C04], [0x0C3E, 0x0C40], [0x0C46, 0x0C48], - [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0C62, 0x0C63], - [0x0C81, 0x0C81], [0x0CBC, 0x0CBC], [0x0CBF, 0x0CBF], - [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD], [0x0CE2, 0x0CE3], - [0x0D00, 0x0D01], [0x0D3B, 0x0D3C], [0x0D41, 0x0D44], - [0x0D4D, 0x0D4D], [0x0D62, 0x0D63], [0x0DCA, 0x0DCA], - [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6], [0x0E31, 0x0E31], - [0x0E34, 0x0E3A], [0x0E47, 0x0E4E], [0x0EB1, 0x0EB1], - [0x0EB4, 0x0EBC], [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], - [0x0F35, 0x0F35], [0x0F37, 0x0F37], [0x0F39, 0x0F39], - [0x0F71, 0x0F7E], [0x0F80, 0x0F84], [0x0F86, 0x0F87], - [0x0F8D, 0x0F97], [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], - [0x102D, 0x1030], [0x1032, 0x1037], [0x1039, 0x103A], - [0x103D, 0x103E], [0x1058, 0x1059], [0x105E, 0x1060], - [0x1071, 0x1074], [0x1082, 0x1082], [0x1085, 0x1086], - [0x108D, 0x108D], [0x109D, 0x109D], [0x1160, 0x11FF], - [0x135D, 0x135F], [0x1712, 0x1714], [0x1732, 0x1734], - [0x1752, 0x1753], [0x1772, 0x1773], [0x17B4, 0x17B5], - [0x17B7, 0x17BD], [0x17C6, 0x17C6], [0x17C9, 0x17D3], - [0x17DD, 0x17DD], [0x180B, 0x180E], [0x1885, 0x1886], - [0x18A9, 0x18A9], [0x1920, 0x1922], [0x1927, 0x1928], - [0x1932, 0x1932], [0x1939, 0x193B], [0x1A17, 0x1A18], - [0x1A1B, 0x1A1B], [0x1A56, 0x1A56], [0x1A58, 0x1A5E], - [0x1A60, 0x1A60], [0x1A62, 0x1A62], [0x1A65, 0x1A6C], - [0x1A73, 0x1A7C], [0x1A7F, 0x1A7F], [0x1AB0, 0x1ABE], - [0x1B00, 0x1B03], [0x1B34, 0x1B34], [0x1B36, 0x1B3A], - [0x1B3C, 0x1B3C], [0x1B42, 0x1B42], [0x1B6B, 0x1B73], - [0x1B80, 0x1B81], [0x1BA2, 0x1BA5], [0x1BA8, 0x1BA9], - [0x1BAB, 0x1BAD], [0x1BE6, 0x1BE6], [0x1BE8, 0x1BE9], - [0x1BED, 0x1BED], [0x1BEF, 0x1BF1], [0x1C2C, 0x1C33], - [0x1C36, 0x1C37], [0x1CD0, 0x1CD2], [0x1CD4, 0x1CE0], - [0x1CE2, 0x1CE8], [0x1CED, 0x1CED], [0x1CF4, 0x1CF4], - [0x1CF8, 0x1CF9], [0x1DC0, 0x1DF9], [0x1DFB, 0x1DFF], - [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2064], - [0x2066, 0x206F], [0x20D0, 0x20F0], [0x2CEF, 0x2CF1], - [0x2D7F, 0x2D7F], [0x2DE0, 0x2DFF], [0x302A, 0x302D], - [0x3099, 0x309A], [0xA66F, 0xA672], [0xA674, 0xA67D], - [0xA69E, 0xA69F], [0xA6F0, 0xA6F1], [0xA802, 0xA802], - [0xA806, 0xA806], [0xA80B, 0xA80B], [0xA825, 0xA826], - [0xA8C4, 0xA8C5], [0xA8E0, 0xA8F1], [0xA8FF, 0xA8FF], - [0xA926, 0xA92D], [0xA947, 0xA951], [0xA980, 0xA982], - [0xA9B3, 0xA9B3], [0xA9B6, 0xA9B9], [0xA9BC, 0xA9BD], - [0xA9E5, 0xA9E5], [0xAA29, 0xAA2E], [0xAA31, 0xAA32], - [0xAA35, 0xAA36], [0xAA43, 0xAA43], [0xAA4C, 0xAA4C], - [0xAA7C, 0xAA7C], [0xAAB0, 0xAAB0], [0xAAB2, 0xAAB4], - [0xAAB7, 0xAAB8], [0xAABE, 0xAABF], [0xAAC1, 0xAAC1], - [0xAAEC, 0xAAED], [0xAAF6, 0xAAF6], [0xABE5, 0xABE5], - [0xABE8, 0xABE8], [0xABED, 0xABED], [0xFB1E, 0xFB1E], - [0xFE00, 0xFE0F], [0xFE20, 0xFE2F], [0xFEFF, 0xFEFF], - [0xFFF9, 0xFFFB] -]; - -const HIGH_COMBINING_V11 = [ - [0x101FD, 0x101FD], [0x102E0, 0x102E0], - [0x10376, 0x1037A], [0x10A01, 0x10A03], [0x10A05, 0x10A06], - [0x10A0C, 0x10A0F], [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], - [0x10AE5, 0x10AE6], [0x10D24, 0x10D27], [0x10F46, 0x10F50], - [0x11001, 0x11001], [0x11038, 0x11046], [0x1107F, 0x11081], - [0x110B3, 0x110B6], [0x110B9, 0x110BA], [0x110BD, 0x110BD], - [0x110CD, 0x110CD], [0x11100, 0x11102], [0x11127, 0x1112B], - [0x1112D, 0x11134], [0x11173, 0x11173], [0x11180, 0x11181], - [0x111B6, 0x111BE], [0x111C9, 0x111CC], [0x1122F, 0x11231], - [0x11234, 0x11234], [0x11236, 0x11237], [0x1123E, 0x1123E], - [0x112DF, 0x112DF], [0x112E3, 0x112EA], [0x11300, 0x11301], - [0x1133B, 0x1133C], [0x11340, 0x11340], [0x11366, 0x1136C], - [0x11370, 0x11374], [0x11438, 0x1143F], [0x11442, 0x11444], - [0x11446, 0x11446], [0x1145E, 0x1145E], [0x114B3, 0x114B8], - [0x114BA, 0x114BA], [0x114BF, 0x114C0], [0x114C2, 0x114C3], - [0x115B2, 0x115B5], [0x115BC, 0x115BD], [0x115BF, 0x115C0], - [0x115DC, 0x115DD], [0x11633, 0x1163A], [0x1163D, 0x1163D], - [0x1163F, 0x11640], [0x116AB, 0x116AB], [0x116AD, 0x116AD], - [0x116B0, 0x116B5], [0x116B7, 0x116B7], [0x1171D, 0x1171F], - [0x11722, 0x11725], [0x11727, 0x1172B], [0x1182F, 0x11837], - [0x11839, 0x1183A], [0x119D4, 0x119D7], [0x119DA, 0x119DB], - [0x119E0, 0x119E0], [0x11A01, 0x11A0A], [0x11A33, 0x11A38], - [0x11A3B, 0x11A3E], [0x11A47, 0x11A47], [0x11A51, 0x11A56], - [0x11A59, 0x11A5B], [0x11A8A, 0x11A96], [0x11A98, 0x11A99], - [0x11C30, 0x11C36], [0x11C38, 0x11C3D], [0x11C3F, 0x11C3F], - [0x11C92, 0x11CA7], [0x11CAA, 0x11CB0], [0x11CB2, 0x11CB3], - [0x11CB5, 0x11CB6], [0x11D31, 0x11D36], [0x11D3A, 0x11D3A], - [0x11D3C, 0x11D3D], [0x11D3F, 0x11D45], [0x11D47, 0x11D47], - [0x11D90, 0x11D91], [0x11D95, 0x11D95], [0x11D97, 0x11D97], - [0x11EF3, 0x11EF4], [0x13430, 0x13438], [0x16AF0, 0x16AF4], - [0x16B30, 0x16B36], [0x16F4F, 0x16F4F], [0x16F8F, 0x16F92], - [0x1BC9D, 0x1BC9E], [0x1BCA0, 0x1BCA3], [0x1D167, 0x1D169], - [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD], - [0x1D242, 0x1D244], [0x1DA00, 0x1DA36], [0x1DA3B, 0x1DA6C], - [0x1DA75, 0x1DA75], [0x1DA84, 0x1DA84], [0x1DA9B, 0x1DA9F], - [0x1DAA1, 0x1DAAF], [0x1E000, 0x1E006], [0x1E008, 0x1E018], - [0x1E01B, 0x1E021], [0x1E023, 0x1E024], [0x1E026, 0x1E02A], - [0x1E130, 0x1E136], [0x1E2EC, 0x1E2EF], [0x1E8D0, 0x1E8D6], - [0x1E944, 0x1E94A], [0xE0001, 0xE0001], [0xE0020, 0xE007F], - [0xE0100, 0xE01EF] -]; - -const BMP_WIDE_V11 = [ - [0x1100, 0x115F], [0x231A, 0x231B], [0x2329, 0x232A], - [0x23E9, 0x23EC], [0x23F0, 0x23F0], [0x23F3, 0x23F3], - [0x25FD, 0x25FE], [0x2614, 0x2615], [0x2648, 0x2653], - [0x267F, 0x267F], [0x2693, 0x2693], [0x26A1, 0x26A1], - [0x26AA, 0x26AB], [0x26BD, 0x26BE], [0x26C4, 0x26C5], - [0x26CE, 0x26CE], [0x26D4, 0x26D4], [0x26EA, 0x26EA], - [0x26F2, 0x26F3], [0x26F5, 0x26F5], [0x26FA, 0x26FA], - [0x26FD, 0x26FD], [0x2705, 0x2705], [0x270A, 0x270B], - [0x2728, 0x2728], [0x274C, 0x274C], [0x274E, 0x274E], - [0x2753, 0x2755], [0x2757, 0x2757], [0x2795, 0x2797], - [0x27B0, 0x27B0], [0x27BF, 0x27BF], [0x2B1B, 0x2B1C], - [0x2B50, 0x2B50], [0x2B55, 0x2B55], [0x2E80, 0x2E99], - [0x2E9B, 0x2EF3], [0x2F00, 0x2FD5], [0x2FF0, 0x2FFB], - [0x3000, 0x3029], [0x302E, 0x303E], [0x3041, 0x3096], - [0x309B, 0x30FF], [0x3105, 0x312F], [0x3131, 0x318E], - [0x3190, 0x31BA], [0x31C0, 0x31E3], [0x31F0, 0x321E], - [0x3220, 0x3247], [0x3250, 0x4DBF], [0x4E00, 0xA48C], - [0xA490, 0xA4C6], [0xA960, 0xA97C], [0xAC00, 0xD7A3], - [0xF900, 0xFAFF], [0xFE10, 0xFE19], [0xFE30, 0xFE52], - [0xFE54, 0xFE66], [0xFE68, 0xFE6B], [0xFF01, 0xFF60], - [0xFFE0, 0xFFE6] -]; - -const HIGH_WIDE_V11 = [ - [0x16FE0, 0x16FE3], [0x17000, 0x187F7], - [0x18800, 0x18AF2], [0x1B000, 0x1B11E], [0x1B150, 0x1B152], - [0x1B164, 0x1B167], [0x1B170, 0x1B2FB], [0x1F004, 0x1F004], - [0x1F0CF, 0x1F0CF], [0x1F18E, 0x1F18E], [0x1F191, 0x1F19A], - [0x1F200, 0x1F202], [0x1F210, 0x1F23B], [0x1F240, 0x1F248], - [0x1F250, 0x1F251], [0x1F260, 0x1F265], [0x1F300, 0x1F320], - [0x1F32D, 0x1F335], [0x1F337, 0x1F37C], [0x1F37E, 0x1F393], - [0x1F3A0, 0x1F3CA], [0x1F3CF, 0x1F3D3], [0x1F3E0, 0x1F3F0], - [0x1F3F4, 0x1F3F4], [0x1F3F8, 0x1F43E], [0x1F440, 0x1F440], - [0x1F442, 0x1F4FC], [0x1F4FF, 0x1F53D], [0x1F54B, 0x1F54E], - [0x1F550, 0x1F567], [0x1F57A, 0x1F57A], [0x1F595, 0x1F596], - [0x1F5A4, 0x1F5A4], [0x1F5FB, 0x1F64F], [0x1F680, 0x1F6C5], - [0x1F6CC, 0x1F6CC], [0x1F6D0, 0x1F6D2], [0x1F6D5, 0x1F6D5], - [0x1F6EB, 0x1F6EC], [0x1F6F4, 0x1F6FA], [0x1F7E0, 0x1F7EB], - [0x1F90D, 0x1F971], [0x1F973, 0x1F976], [0x1F97A, 0x1F9A2], - [0x1F9A5, 0x1F9AA], [0x1F9AE, 0x1F9CA], [0x1F9CD, 0x1F9FF], - [0x1FA70, 0x1FA73], [0x1FA78, 0x1FA7A], [0x1FA80, 0x1FA82], - [0x1FA90, 0x1FA95], [0x20000, 0x2FFFD], [0x30000, 0x3FFFD] -]; - -const BMP_TABLE_V11 = new Uint8Array(65536); -fill(BMP_TABLE_V11, 1); -BMP_TABLE_V11[0] = 0; -fill(BMP_TABLE_V11, 0, 1, 32); -fill(BMP_TABLE_V11, 0, 0x7f, 0xa0); -for (let r = 0; r < BMP_COMBINING_V11.length; ++r) { - fill(BMP_TABLE_V11, 0, BMP_COMBINING_V11[r][0], BMP_COMBINING_V11[r][1] + 1); -} -for (let r = 0; r < BMP_WIDE_V11.length; ++r) { - fill(BMP_TABLE_V11, 2, BMP_WIDE_V11[r][0], BMP_WIDE_V11[r][1] + 1); -} - -export function wcwidthV11(num: number): number { - if (num < 32) return 0; - if (num < 127) return 1; - if (num < 65536) return BMP_TABLE_V11[num]; - if (bisearch(num, HIGH_COMBINING_V11)) return 0; - if (bisearch(num, HIGH_WIDE_V11)) return 2; - return 1; -} - -export function getStringCellWidthV11(s: string): number { - return getStringCellWidthPrivate(wcwidthV11, s); -} From 0f83d645b602cad13ce27327f364c551612c99f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 6 Dec 2019 16:46:50 +0100 Subject: [PATCH 163/268] remove V11 from base repo --- src/common/input/UnicodeV11.ts | 216 --------------------------------- 1 file changed, 216 deletions(-) delete mode 100644 src/common/input/UnicodeV11.ts diff --git a/src/common/input/UnicodeV11.ts b/src/common/input/UnicodeV11.ts deleted file mode 100644 index b3ee56d35..000000000 --- a/src/common/input/UnicodeV11.ts +++ /dev/null @@ -1,216 +0,0 @@ -/** - * Copyright (c) 2019 The xterm.js authors. All rights reserved. - * @license MIT - */ -import { IUnicodeVersionProvider } from 'common/services/Services'; -import { fill } from 'common/TypedArrayUtils'; - -export class UnicodeV11 implements IUnicodeVersionProvider { - static bmpCombining = [ - [0x0300, 0x036F], [0x0483, 0x0489], [0x0591, 0x05BD], - [0x05BF, 0x05BF], [0x05C1, 0x05C2], [0x05C4, 0x05C5], - [0x05C7, 0x05C7], [0x0600, 0x0605], [0x0610, 0x061A], - [0x061C, 0x061C], [0x064B, 0x065F], [0x0670, 0x0670], - [0x06D6, 0x06DD], [0x06DF, 0x06E4], [0x06E7, 0x06E8], - [0x06EA, 0x06ED], [0x070F, 0x070F], [0x0711, 0x0711], - [0x0730, 0x074A], [0x07A6, 0x07B0], [0x07EB, 0x07F3], - [0x07FD, 0x07FD], [0x0816, 0x0819], [0x081B, 0x0823], - [0x0825, 0x0827], [0x0829, 0x082D], [0x0859, 0x085B], - [0x08D3, 0x0902], [0x093A, 0x093A], [0x093C, 0x093C], - [0x0941, 0x0948], [0x094D, 0x094D], [0x0951, 0x0957], - [0x0962, 0x0963], [0x0981, 0x0981], [0x09BC, 0x09BC], - [0x09C1, 0x09C4], [0x09CD, 0x09CD], [0x09E2, 0x09E3], - [0x09FE, 0x09FE], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C], - [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D], - [0x0A51, 0x0A51], [0x0A70, 0x0A71], [0x0A75, 0x0A75], - [0x0A81, 0x0A82], [0x0ABC, 0x0ABC], [0x0AC1, 0x0AC5], - [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD], [0x0AE2, 0x0AE3], - [0x0AFA, 0x0AFF], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C], - [0x0B3F, 0x0B3F], [0x0B41, 0x0B44], [0x0B4D, 0x0B4D], - [0x0B56, 0x0B56], [0x0B62, 0x0B63], [0x0B82, 0x0B82], - [0x0BC0, 0x0BC0], [0x0BCD, 0x0BCD], [0x0C00, 0x0C00], - [0x0C04, 0x0C04], [0x0C3E, 0x0C40], [0x0C46, 0x0C48], - [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0C62, 0x0C63], - [0x0C81, 0x0C81], [0x0CBC, 0x0CBC], [0x0CBF, 0x0CBF], - [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD], [0x0CE2, 0x0CE3], - [0x0D00, 0x0D01], [0x0D3B, 0x0D3C], [0x0D41, 0x0D44], - [0x0D4D, 0x0D4D], [0x0D62, 0x0D63], [0x0DCA, 0x0DCA], - [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6], [0x0E31, 0x0E31], - [0x0E34, 0x0E3A], [0x0E47, 0x0E4E], [0x0EB1, 0x0EB1], - [0x0EB4, 0x0EBC], [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], - [0x0F35, 0x0F35], [0x0F37, 0x0F37], [0x0F39, 0x0F39], - [0x0F71, 0x0F7E], [0x0F80, 0x0F84], [0x0F86, 0x0F87], - [0x0F8D, 0x0F97], [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], - [0x102D, 0x1030], [0x1032, 0x1037], [0x1039, 0x103A], - [0x103D, 0x103E], [0x1058, 0x1059], [0x105E, 0x1060], - [0x1071, 0x1074], [0x1082, 0x1082], [0x1085, 0x1086], - [0x108D, 0x108D], [0x109D, 0x109D], [0x1160, 0x11FF], - [0x135D, 0x135F], [0x1712, 0x1714], [0x1732, 0x1734], - [0x1752, 0x1753], [0x1772, 0x1773], [0x17B4, 0x17B5], - [0x17B7, 0x17BD], [0x17C6, 0x17C6], [0x17C9, 0x17D3], - [0x17DD, 0x17DD], [0x180B, 0x180E], [0x1885, 0x1886], - [0x18A9, 0x18A9], [0x1920, 0x1922], [0x1927, 0x1928], - [0x1932, 0x1932], [0x1939, 0x193B], [0x1A17, 0x1A18], - [0x1A1B, 0x1A1B], [0x1A56, 0x1A56], [0x1A58, 0x1A5E], - [0x1A60, 0x1A60], [0x1A62, 0x1A62], [0x1A65, 0x1A6C], - [0x1A73, 0x1A7C], [0x1A7F, 0x1A7F], [0x1AB0, 0x1ABE], - [0x1B00, 0x1B03], [0x1B34, 0x1B34], [0x1B36, 0x1B3A], - [0x1B3C, 0x1B3C], [0x1B42, 0x1B42], [0x1B6B, 0x1B73], - [0x1B80, 0x1B81], [0x1BA2, 0x1BA5], [0x1BA8, 0x1BA9], - [0x1BAB, 0x1BAD], [0x1BE6, 0x1BE6], [0x1BE8, 0x1BE9], - [0x1BED, 0x1BED], [0x1BEF, 0x1BF1], [0x1C2C, 0x1C33], - [0x1C36, 0x1C37], [0x1CD0, 0x1CD2], [0x1CD4, 0x1CE0], - [0x1CE2, 0x1CE8], [0x1CED, 0x1CED], [0x1CF4, 0x1CF4], - [0x1CF8, 0x1CF9], [0x1DC0, 0x1DF9], [0x1DFB, 0x1DFF], - [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2064], - [0x2066, 0x206F], [0x20D0, 0x20F0], [0x2CEF, 0x2CF1], - [0x2D7F, 0x2D7F], [0x2DE0, 0x2DFF], [0x302A, 0x302D], - [0x3099, 0x309A], [0xA66F, 0xA672], [0xA674, 0xA67D], - [0xA69E, 0xA69F], [0xA6F0, 0xA6F1], [0xA802, 0xA802], - [0xA806, 0xA806], [0xA80B, 0xA80B], [0xA825, 0xA826], - [0xA8C4, 0xA8C5], [0xA8E0, 0xA8F1], [0xA8FF, 0xA8FF], - [0xA926, 0xA92D], [0xA947, 0xA951], [0xA980, 0xA982], - [0xA9B3, 0xA9B3], [0xA9B6, 0xA9B9], [0xA9BC, 0xA9BD], - [0xA9E5, 0xA9E5], [0xAA29, 0xAA2E], [0xAA31, 0xAA32], - [0xAA35, 0xAA36], [0xAA43, 0xAA43], [0xAA4C, 0xAA4C], - [0xAA7C, 0xAA7C], [0xAAB0, 0xAAB0], [0xAAB2, 0xAAB4], - [0xAAB7, 0xAAB8], [0xAABE, 0xAABF], [0xAAC1, 0xAAC1], - [0xAAEC, 0xAAED], [0xAAF6, 0xAAF6], [0xABE5, 0xABE5], - [0xABE8, 0xABE8], [0xABED, 0xABED], [0xFB1E, 0xFB1E], - [0xFE00, 0xFE0F], [0xFE20, 0xFE2F], [0xFEFF, 0xFEFF], - [0xFFF9, 0xFFFB] - ]; - static highCombining = [ - [0x101FD, 0x101FD], [0x102E0, 0x102E0], - [0x10376, 0x1037A], [0x10A01, 0x10A03], [0x10A05, 0x10A06], - [0x10A0C, 0x10A0F], [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], - [0x10AE5, 0x10AE6], [0x10D24, 0x10D27], [0x10F46, 0x10F50], - [0x11001, 0x11001], [0x11038, 0x11046], [0x1107F, 0x11081], - [0x110B3, 0x110B6], [0x110B9, 0x110BA], [0x110BD, 0x110BD], - [0x110CD, 0x110CD], [0x11100, 0x11102], [0x11127, 0x1112B], - [0x1112D, 0x11134], [0x11173, 0x11173], [0x11180, 0x11181], - [0x111B6, 0x111BE], [0x111C9, 0x111CC], [0x1122F, 0x11231], - [0x11234, 0x11234], [0x11236, 0x11237], [0x1123E, 0x1123E], - [0x112DF, 0x112DF], [0x112E3, 0x112EA], [0x11300, 0x11301], - [0x1133B, 0x1133C], [0x11340, 0x11340], [0x11366, 0x1136C], - [0x11370, 0x11374], [0x11438, 0x1143F], [0x11442, 0x11444], - [0x11446, 0x11446], [0x1145E, 0x1145E], [0x114B3, 0x114B8], - [0x114BA, 0x114BA], [0x114BF, 0x114C0], [0x114C2, 0x114C3], - [0x115B2, 0x115B5], [0x115BC, 0x115BD], [0x115BF, 0x115C0], - [0x115DC, 0x115DD], [0x11633, 0x1163A], [0x1163D, 0x1163D], - [0x1163F, 0x11640], [0x116AB, 0x116AB], [0x116AD, 0x116AD], - [0x116B0, 0x116B5], [0x116B7, 0x116B7], [0x1171D, 0x1171F], - [0x11722, 0x11725], [0x11727, 0x1172B], [0x1182F, 0x11837], - [0x11839, 0x1183A], [0x119D4, 0x119D7], [0x119DA, 0x119DB], - [0x119E0, 0x119E0], [0x11A01, 0x11A0A], [0x11A33, 0x11A38], - [0x11A3B, 0x11A3E], [0x11A47, 0x11A47], [0x11A51, 0x11A56], - [0x11A59, 0x11A5B], [0x11A8A, 0x11A96], [0x11A98, 0x11A99], - [0x11C30, 0x11C36], [0x11C38, 0x11C3D], [0x11C3F, 0x11C3F], - [0x11C92, 0x11CA7], [0x11CAA, 0x11CB0], [0x11CB2, 0x11CB3], - [0x11CB5, 0x11CB6], [0x11D31, 0x11D36], [0x11D3A, 0x11D3A], - [0x11D3C, 0x11D3D], [0x11D3F, 0x11D45], [0x11D47, 0x11D47], - [0x11D90, 0x11D91], [0x11D95, 0x11D95], [0x11D97, 0x11D97], - [0x11EF3, 0x11EF4], [0x13430, 0x13438], [0x16AF0, 0x16AF4], - [0x16B30, 0x16B36], [0x16F4F, 0x16F4F], [0x16F8F, 0x16F92], - [0x1BC9D, 0x1BC9E], [0x1BCA0, 0x1BCA3], [0x1D167, 0x1D169], - [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD], - [0x1D242, 0x1D244], [0x1DA00, 0x1DA36], [0x1DA3B, 0x1DA6C], - [0x1DA75, 0x1DA75], [0x1DA84, 0x1DA84], [0x1DA9B, 0x1DA9F], - [0x1DAA1, 0x1DAAF], [0x1E000, 0x1E006], [0x1E008, 0x1E018], - [0x1E01B, 0x1E021], [0x1E023, 0x1E024], [0x1E026, 0x1E02A], - [0x1E130, 0x1E136], [0x1E2EC, 0x1E2EF], [0x1E8D0, 0x1E8D6], - [0x1E944, 0x1E94A], [0xE0001, 0xE0001], [0xE0020, 0xE007F], - [0xE0100, 0xE01EF] - ]; - static bmpWide = [ - [0x1100, 0x115F], [0x231A, 0x231B], [0x2329, 0x232A], - [0x23E9, 0x23EC], [0x23F0, 0x23F0], [0x23F3, 0x23F3], - [0x25FD, 0x25FE], [0x2614, 0x2615], [0x2648, 0x2653], - [0x267F, 0x267F], [0x2693, 0x2693], [0x26A1, 0x26A1], - [0x26AA, 0x26AB], [0x26BD, 0x26BE], [0x26C4, 0x26C5], - [0x26CE, 0x26CE], [0x26D4, 0x26D4], [0x26EA, 0x26EA], - [0x26F2, 0x26F3], [0x26F5, 0x26F5], [0x26FA, 0x26FA], - [0x26FD, 0x26FD], [0x2705, 0x2705], [0x270A, 0x270B], - [0x2728, 0x2728], [0x274C, 0x274C], [0x274E, 0x274E], - [0x2753, 0x2755], [0x2757, 0x2757], [0x2795, 0x2797], - [0x27B0, 0x27B0], [0x27BF, 0x27BF], [0x2B1B, 0x2B1C], - [0x2B50, 0x2B50], [0x2B55, 0x2B55], [0x2E80, 0x2E99], - [0x2E9B, 0x2EF3], [0x2F00, 0x2FD5], [0x2FF0, 0x2FFB], - [0x3000, 0x3029], [0x302E, 0x303E], [0x3041, 0x3096], - [0x309B, 0x30FF], [0x3105, 0x312F], [0x3131, 0x318E], - [0x3190, 0x31BA], [0x31C0, 0x31E3], [0x31F0, 0x321E], - [0x3220, 0x3247], [0x3250, 0x4DBF], [0x4E00, 0xA48C], - [0xA490, 0xA4C6], [0xA960, 0xA97C], [0xAC00, 0xD7A3], - [0xF900, 0xFAFF], [0xFE10, 0xFE19], [0xFE30, 0xFE52], - [0xFE54, 0xFE66], [0xFE68, 0xFE6B], [0xFF01, 0xFF60], - [0xFFE0, 0xFFE6] - ]; - static highWide = [ - [0x16FE0, 0x16FE3], [0x17000, 0x187F7], - [0x18800, 0x18AF2], [0x1B000, 0x1B11E], [0x1B150, 0x1B152], - [0x1B164, 0x1B167], [0x1B170, 0x1B2FB], [0x1F004, 0x1F004], - [0x1F0CF, 0x1F0CF], [0x1F18E, 0x1F18E], [0x1F191, 0x1F19A], - [0x1F200, 0x1F202], [0x1F210, 0x1F23B], [0x1F240, 0x1F248], - [0x1F250, 0x1F251], [0x1F260, 0x1F265], [0x1F300, 0x1F320], - [0x1F32D, 0x1F335], [0x1F337, 0x1F37C], [0x1F37E, 0x1F393], - [0x1F3A0, 0x1F3CA], [0x1F3CF, 0x1F3D3], [0x1F3E0, 0x1F3F0], - [0x1F3F4, 0x1F3F4], [0x1F3F8, 0x1F43E], [0x1F440, 0x1F440], - [0x1F442, 0x1F4FC], [0x1F4FF, 0x1F53D], [0x1F54B, 0x1F54E], - [0x1F550, 0x1F567], [0x1F57A, 0x1F57A], [0x1F595, 0x1F596], - [0x1F5A4, 0x1F5A4], [0x1F5FB, 0x1F64F], [0x1F680, 0x1F6C5], - [0x1F6CC, 0x1F6CC], [0x1F6D0, 0x1F6D2], [0x1F6D5, 0x1F6D5], - [0x1F6EB, 0x1F6EC], [0x1F6F4, 0x1F6FA], [0x1F7E0, 0x1F7EB], - [0x1F90D, 0x1F971], [0x1F973, 0x1F976], [0x1F97A, 0x1F9A2], - [0x1F9A5, 0x1F9AA], [0x1F9AE, 0x1F9CA], [0x1F9CD, 0x1F9FF], - [0x1FA70, 0x1FA73], [0x1FA78, 0x1FA7A], [0x1FA80, 0x1FA82], - [0x1FA90, 0x1FA95], [0x20000, 0x2FFFD], [0x30000, 0x3FFFD] - ]; - static bmpTable: Uint8Array; - - static bisearch(ucs: number, data: number[][]): boolean { - let min = 0; - let max = data.length - 1; - let mid; - if (ucs < data[0][0] || ucs > data[max][1]) { - return false; - } - while (max >= min) { - mid = (min + max) >> 1; - if (ucs > data[mid][1]) { - min = mid + 1; - } else if (ucs < data[mid][0]) { - max = mid - 1; - } else { - return true; - } - } - return false; - } - - public version = '11'; - - constructor() { - if (!UnicodeV11.bmpTable) { - UnicodeV11.bmpTable = new Uint8Array(65536); - fill(UnicodeV11.bmpTable, 1); - UnicodeV11.bmpTable[0] = 0; - fill(UnicodeV11.bmpTable, 0, 1, 32); - fill(UnicodeV11.bmpTable, 0, 0x7f, 0xa0); - for (let r = 0; r < UnicodeV11.bmpCombining.length; ++r) { - fill(UnicodeV11.bmpTable, 0, UnicodeV11.bmpCombining[r][0], UnicodeV11.bmpCombining[r][1] + 1); - } - for (let r = 0; r < UnicodeV11.bmpWide.length; ++r) { - fill(UnicodeV11.bmpTable, 2, UnicodeV11.bmpWide[r][0], UnicodeV11.bmpWide[r][1] + 1); - } - } - } - - public wcwidth(num: number): 0 | 1 | 2 { - if (num < 32) return 0; - if (num < 127) return 1; - if (num < 65536) return UnicodeV11.bmpTable[num] as 0 | 1 | 2; - if (UnicodeV11.bisearch(num, UnicodeV11.highCombining)) return 0; - if (UnicodeV11.bisearch(num, UnicodeV11.highWide)) return 2; - return 1; - } -} From 70dabf11db2b1945aaa0b6eb5dd5cabaa7f60116 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 6 Dec 2019 17:00:46 +0100 Subject: [PATCH 164/268] fix failing tests --- src/Terminal.test.ts | 8 ++++---- src/browser/Linkifier.test.ts | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index 422394e4c..c9cce9910 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -7,7 +7,7 @@ import { assert, expect } from 'chai'; import { MockViewport, MockCompositionHelper, MockRenderer, TestTerminal } from './TestUtils.test'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { CellData } from 'common/buffer/CellData'; -import { IBufferService } from 'common/services/Services'; +import { IBufferService, IUnicodeService } from 'common/services/Services'; import { Linkifier } from 'browser/Linkifier'; import { MockLogService, MockOptionsService, MockUnicodeService } from 'common/TestUtils.test'; import { IRegisteredLinkMatcher, IMouseZoneManager, IMouseZone } from 'browser/Types'; @@ -1047,7 +1047,7 @@ describe('Terminal', () => { // to get the special handling of fullwidth, surrogate and combining chars in the input handler beforeEach(() => { terminal = new TestTerminal({ cols: 10, rows: 5 }); - linkifier = new TestLinkifier((terminal as any)._bufferService); + linkifier = new TestLinkifier((terminal as any)._bufferService, (terminal as any)._unicodeService); mouseZoneManager = new TestMouseZoneManager(); linkifier.attachToDom({} as any, mouseZoneManager); }); @@ -1372,8 +1372,8 @@ describe('Terminal', () => { }); class TestLinkifier extends Linkifier { - constructor(bufferService: IBufferService) { - super(bufferService, new MockLogService(), new MockOptionsService(), new MockUnicodeService()); + constructor(bufferService: IBufferService, unicodeService: IUnicodeService) { + super(bufferService, new MockLogService(), new MockOptionsService(), unicodeService); Linkifier._timeBeforeLatency = 0; } diff --git a/src/browser/Linkifier.test.ts b/src/browser/Linkifier.test.ts index 9f155d522..f107d646d 100644 --- a/src/browser/Linkifier.test.ts +++ b/src/browser/Linkifier.test.ts @@ -9,12 +9,13 @@ import { IBufferLine } from 'common/Types'; import { Linkifier } from 'browser/Linkifier'; import { BufferLine } from 'common/buffer/BufferLine'; import { CellData } from 'common/buffer/CellData'; -import { MockLogService, MockBufferService, MockOptionsService, MockUnicodeService } from 'common/TestUtils.test'; +import { MockLogService, MockBufferService, MockOptionsService } from 'common/TestUtils.test'; import { IBufferService } from 'common/services/Services'; +import { UnicodeService } from 'common/services/UnicodeService'; class TestLinkifier extends Linkifier { constructor(bufferService: IBufferService) { - super(bufferService, new MockLogService(), new MockOptionsService(), new MockUnicodeService()); + super(bufferService, new MockLogService(), new MockOptionsService(), new UnicodeService()); Linkifier._timeBeforeLatency = 0; } From 92ac99e87887b01cd68e1d6e8469663a03f6730a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 6 Dec 2019 20:41:24 +0100 Subject: [PATCH 165/268] cleanup --- addons/xterm-addon-unicode11/src/Unicode11Addon.ts | 2 -- src/common/services/Services.ts | 4 ++-- typings/xterm.d.ts | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/addons/xterm-addon-unicode11/src/Unicode11Addon.ts b/addons/xterm-addon-unicode11/src/Unicode11Addon.ts index 926b5ad9f..37c5eb7d8 100644 --- a/addons/xterm-addon-unicode11/src/Unicode11Addon.ts +++ b/addons/xterm-addon-unicode11/src/Unicode11Addon.ts @@ -258,8 +258,6 @@ class UnicodeV11 implements IUnicodeVersionProvider { export class Unicode11Addon implements ITerminalAddon { public activate(terminal: Terminal): void { - console.log('got we loaded?'); - (window as any)._v11 = new UnicodeV11(); terminal.unicode.register(new UnicodeV11()); } diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 9ee348fb1..5e52a2069 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -212,7 +212,7 @@ export interface IPartialTerminalOptions { scrollSensitivity?: number; tabStopWidth?: number; theme?: ITheme; - unicodeVersion?: '6' | '11'; + unicodeVersion?: string; windowsMode?: boolean; wordSeparator?: string; } @@ -246,7 +246,7 @@ export interface ITerminalOptions { scrollSensitivity: number; tabStopWidth: number; theme: ITheme; - unicodeVersion: '6' | '11'; + unicodeVersion: string; windowsMode: boolean; wordSeparator: string; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index a1ecbbfff..472e01b88 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -213,7 +213,7 @@ declare module 'xterm' { /** * Unicode version to be used for wcwidth. Default is version 6. */ - unicodeVersion?: '6' | '11'; + unicodeVersion?: string; /** * Whether "Windows mode" is enabled. Because Windows backends winpty and From c5e144f327e7990853f2623f05562d877e133058 Mon Sep 17 00:00:00 2001 From: Steven Degutis Date: Fri, 6 Dec 2019 15:57:26 -0600 Subject: [PATCH 166/268] Avoid roundtrip to browser when double-disposing. --- src/browser/Lifecycle.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/browser/Lifecycle.ts b/src/browser/Lifecycle.ts index 1344ea9bc..a8172db50 100644 --- a/src/browser/Lifecycle.ts +++ b/src/browser/Lifecycle.ts @@ -17,12 +17,13 @@ export function addDisposableDomListener( useCapture?: boolean ): IDisposable { node.addEventListener(type, handler, useCapture); + let disposed = false; return { dispose: () => { - if (!handler) { - // Already disposed + if (!disposed) { return; } + disposed = true; node.removeEventListener(type, handler, useCapture); } }; From b5fdd3b8d85c287624c7d569f9a11d2115ec3cad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 7 Dec 2019 13:23:00 +0100 Subject: [PATCH 167/268] several fixes --- src/InputHandler.test.ts | 22 --------- src/InputHandler.ts | 14 ++---- src/Terminal.ts | 11 +---- src/common/Types.d.ts | 2 +- test/api/InputHandler.api.ts | 6 +-- typings/xterm.d.ts | 95 +++++++++++++++++++++++------------- 6 files changed, 69 insertions(+), 81 deletions(-) diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 873998a6a..f3238d022 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -1304,28 +1304,6 @@ describe('InputHandler', () => { term.writeSync('\x1b[18t'); assert.deepEqual(stack, ['\x1b[8;10;10t', '\x1b[8;20;50t']); }); - it('20 - GetIconTitle', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {getIconTitle: true}}); - const stack: string[] = []; - term.onData(data => stack.push(data)); - term.writeSync('\x1b]1;hello world!\x07'); - term.writeSync('\x1b[20t'); - assert.deepEqual(stack, ['\x1b]Lhello world!\x1b\\']); - term.writeSync('\x1b]1;some other\x07'); - term.writeSync('\x1b[20t'); - assert.deepEqual(stack, ['\x1b]Lhello world!\x1b\\', '\x1b]Lsome other\x1b\\']); - }); - it('21 - GetWinTitle', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {getWinTitle: true}}); - const stack: string[] = []; - term.onData(data => stack.push(data)); - term.writeSync('\x1b]2;hello world!\x07'); - term.writeSync('\x1b[21t'); - assert.deepEqual(stack, ['\x1b]lhello world!\x1b\\']); - term.writeSync('\x1b]2;some other\x07'); - term.writeSync('\x1b[21t'); - assert.deepEqual(stack, ['\x1b]lhello world!\x1b\\', '\x1b]lsome other\x1b\\']); - }); it('22/23 - PushTitle/PopTitle', () => { const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {pushTitle: true, popTitle: true}}); const stack: string[] = []; diff --git a/src/InputHandler.ts b/src/InputHandler.ts index be66f95d2..b98a62401 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -2146,20 +2146,14 @@ export class InputHandler extends Disposable implements IInputHandler { this._coreService.triggerDataEvent(`${C0.ESC}[8;${this._bufferService.rows};${this._bufferService.cols}t`); } break; - case 20: // GetIconTitle, returns OSC L label ST - this._coreService.triggerDataEvent(`${C0.ESC}]L${this._iconName}${C0.ESC}\\`); - break; - case 21: // GetWinTitle, returns OSC l label ST - this._coreService.triggerDataEvent(`${C0.ESC}]l${this._windowTitle}${C0.ESC}\\`); - break; case 22: // PushTitle - if (!second || second === 2) { + if (second === 0 || second === 2) { this._windowTitleStack.push(this._windowTitle); if (this._windowTitleStack.length > STACK_LIMIT) { this._windowTitleStack.shift(); } } - if (!second || second === 1) { + if (second === 0 || second === 1) { this._iconNameStack.push(this._iconName); if (this._iconNameStack.length > STACK_LIMIT) { this._iconNameStack.shift(); @@ -2167,12 +2161,12 @@ export class InputHandler extends Disposable implements IInputHandler { } break; case 23: // PopTitle - if (!second || second === 2) { + if (second === 0 || second === 2) { if (this._windowTitleStack.length) { this.setTitle(this._windowTitleStack.pop()); } } - if (!second || second === 1) { + if (second === 0 || second === 1) { if (this._iconNameStack.length) { this.setIconName(this._iconNameStack.pop()); } diff --git a/src/Terminal.ts b/src/Terminal.ts index d402eab63..255a81014 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -127,13 +127,9 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp public mouseEvents: CoreMouseEventType = CoreMouseEventType.NONE; public sendFocus: boolean; - // misc public curAttrData: IAttributeData; private _eraseAttrData: IAttributeData; - public params: (string | number)[]; - public currentParam: string | number; - // write buffer private _writeBuffer: WriteBuffer; @@ -268,9 +264,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this.curAttrData = DEFAULT_ATTR_DATA.clone(); this._eraseAttrData = DEFAULT_ATTR_DATA.clone(); - this.params = []; - this.currentParam = 0; - this._userScrolling = false; // Register input handler and refire/handle events @@ -1430,9 +1423,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // Sync the scroll area to make sure scroll events don't fire and scroll the viewport to an // invalid location - if (this.viewport) { - this.viewport.syncScrollArea(true); - } + this.viewport?.syncScrollArea(true); this.refresh(0, this.rows - 1); this._onResize.fire({ cols: x, rows: y }); diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index 851a8ce8f..a06a33cf7 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -256,7 +256,7 @@ export interface ICoreMouseProtocol { export type CoreMouseEncoding = (event: ICoreMouseEvent) => string; /** - * WindowOption settings. + * windowOptions */ export interface IWindowOptions { restoreWin?: boolean; diff --git a/test/api/InputHandler.api.ts b/test/api/InputHandler.api.ts index eb928b67f..ad30a51c1 100644 --- a/test/api/InputHandler.api.ts +++ b/test/api/InputHandler.api.ts @@ -443,11 +443,7 @@ async function getCursor(): Promise<{col: number, row: number}> { } async function getDimensions(): Promise { - const dim = await page.evaluate(` - (function() { - return term._core._renderService.dimensions; - })(); - `); + const dim = await page.evaluate(`term._core._renderService.dimensions`); return { cellWidth: dim.actualCellWidth.toFixed(0), cellHeight: dim.actualCellHeight.toFixed(0), diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 05cff40cb..21f412761 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -383,45 +383,63 @@ declare module 'xterm' { /** * Enable various window manipulation and report features (CSI Ps ; Ps ; Ps t). * - * Note that most settings have no default implementation, as they heavily - * rely on the embedding environment. + * Most settings have no default implementation, as they heavily rely on + * the embedding environment. * - * To implement a features, create a custom CSI hook like this: - * ```Typescript + * To implement a feature, create a custom CSI hook like this: + * ```ts * term.parser.addCsiHandler({final: 't'}, params => { * const ps = params[0]; * switch (ps) { * case XY: - * ... // your implementation + * ... // your implementation for option XY * return true; // signal Ps=XY was handled * } * return false; // any Ps that was not handled * }); * ``` * - * All features are disabled by default for security reasons. - * All custom implementations are guarded by the boolean values automatically. + * Note on security: + * Most features are meant to deal with some information of the host machine + * where the terminal runs on. This is seen as a security risk possibly leaking + * sensitive data of the host to the program in the terminal. Therefore all options + * (even those without a default implementation) are guarded by the boolean flag + * and disabled by default. */ export interface IWindowOptions { - /** Ps=1 De-iconify window. */ + /** + * Ps=1 De-iconify window. + * No default implementation. + */ restoreWin?: boolean; - /** Ps=2 Iconify window. */ + /** + * Ps=2 Iconify window. + * No default implementation. + */ minimizeWin?: boolean; /** * Ps=3 ; x ; y - * Move window to [x, y]. + * Move window to [x, y]. + * No default implementation. */ setWinPosition?: boolean; /** * Ps = 4 ; height ; width * Resize the window to given `height` and `width` in pixels. * Omitted parameters should reuse the current height or width. - * Zero parameters should use the display's height or width. + * Zero parameters should use the display's height or width. + * No default implementation. */ setWinSizePixels?: boolean; - /** Ps=5 Raise the window to the front of the stacking order. */ + /** + * Ps=5 Raise the window to the front of the stacking order. + * No default implementation. + */ raiseWin?: boolean; - /** Ps=6 Lower the xterm window to the bottom of the stacking order. */ + /** + * Ps=6 Lower the xterm window to the bottom of the stacking order. + * No default implementation. + */ lowerWin?: boolean; /** Ps=7 Refresh the window. */ refreshWin?: boolean; @@ -429,78 +447,89 @@ declare module 'xterm' { * Ps = 8 ; height ; width * Resize the text area to given height and width in characters. * Omitted parameters should reuse the current height or width. - * Zero parameters use the display's height or width. + * Zero parameters use the display's height or width. + * No default implementation. */ setWinSizeChars?: boolean; /** * Ps=9 ; 0 Restore maximized window. * Ps=9 ; 1 Maximize window (i.e., resize to screen size). * Ps=9 ; 2 Maximize window vertically. - * Ps=9 ; 3 Maximize window horizontally. + * Ps=9 ; 3 Maximize window horizontally. + * No default implementation. */ maximizeWin?: boolean; /** * Ps=10 ; 0 Undo full-screen mode. * Ps=10 ; 1 Change to full-screen. - * Ps=10 ; 2 Toggle full-screen. + * Ps=10 ; 2 Toggle full-screen. + * No default implementation. */ fullscreenWin?: boolean; /** Ps=11 Report xterm window state. * If the xterm window is non-iconified, it returns "CSI 1 t". - * If the xterm window is iconified, it returns "CSI 2 t". + * If the xterm window is iconified, it returns "CSI 2 t". + * No default implementation. */ getWinState?: boolean; /** * Ps=13 Report xterm window position. Result is "CSI 3 ; x ; y t". - * Ps=13 ; 2 Report xterm text-area position. Result is "CSI 3 ; x ; y t". + * Ps=13 ; 2 Report xterm text-area position. Result is "CSI 3 ; x ; y t". + * No default implementation. */ getWinPosition?: boolean; /** * Ps=14 Report xterm text area size in pixels. Result is "CSI 4 ; height ; width t". - * Ps=14 ; 2 Report xterm window size in pixels. Result is "CSI 4 ; height ; width t". - * Ps=14 has a default implementation (also handles Ps=14 ; 2 if not overwritten by custom implementation). + * Ps=14 ; 2 Report xterm window size in pixels. Result is "CSI 4 ; height ; width t". + * Has a default implementation. */ getWinSizePixels?: boolean; - /** Ps=15 Report size of the screen in pixels. Result is "CSI 5 ; height ; width t". */ + /** + * Ps=15 Report size of the screen in pixels. Result is "CSI 5 ; height ; width t". + * No default implementation. + */ getScreenSizePixels?: boolean; /** - * Ps=16 Report xterm character cell size in pixels. Result is "CSI 6 ; height ; width t". - * Implemented by default. + * Ps=16 Report xterm character cell size in pixels. Result is "CSI 6 ; height ; width t". + * Has a default implementation. */ getCellSizePixels?: boolean; /** - * Ps=18 Report the size of the text area in characters. Result is "CSI 8 ; height ; width t". - * Implemented by default. + * Ps=18 Report the size of the text area in characters. Result is "CSI 8 ; height ; width t". + * Has a default implementation. */ getWinSizeChars?: boolean; - /** Ps=19 Report the size of the screen in characters. Result is "CSI 9 ; height ; width t". */ + /** + * Ps=19 Report the size of the screen in characters. Result is "CSI 9 ; height ; width t". + * No default implementation. + */ getScreenSizeChars?: boolean; /** - * Ps=20 Report xterm window's icon label. Result is "OSC L label ST". - * Implemented by default. + * Ps=20 Report xterm window's icon label. Result is "OSC L label ST". + * No default implementation. */ getIconTitle?: boolean; /** - * Ps=21 Report xterm window's title. Result is "OSC l label ST". - * Implemented by default. + * Ps=21 Report xterm window's title. Result is "OSC l label ST". + * No default implementation. */ getWinTitle?: boolean; /** * Ps=22 ; 0 Save xterm icon and window title on stack. * Ps=22 ; 1 Save xterm icon title on stack. - * Ps=22 ; 2 Save xterm window title on stack. + * Ps=22 ; 2 Save xterm window title on stack. * All variants have a default implementation. */ pushTitle?: boolean; /** * Ps=23 ; 0 Restore xterm icon and window title from stack. * Ps=23 ; 1 Restore xterm icon title from stack. - * Ps=23 ; 2 Restore xterm window title from stack. + * Ps=23 ; 2 Restore xterm window title from stack. * All variants have a default implementation. */ popTitle?: boolean; /** - * Ps>=24 Resize to Ps lines (DECSLPP). + * Ps>=24 Resize to Ps lines (DECSLPP). * DECSLPP is not implemented. This settings is also used to * enable / disable DECCOLM (earlier variant of DECSLPP). */ From d7737f6c885d5ec5a2f1a8d836cbf0e6dd4a9ceb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 7 Dec 2019 13:39:06 +0100 Subject: [PATCH 168/268] cleanup --- src/InputHandler.ts | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index b98a62401..cfebd4859 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -44,28 +44,28 @@ function paramToWindowOption(n: number, opts: IWindowOptions): boolean { return opts.setWinLines || false; } switch (n) { - case 1: return opts.restoreWin || false; - case 2: return opts.minimizeWin || false; - case 3: return opts.setWinPosition || false; - case 4: return opts.setWinSizePixels || false; - case 5: return opts.raiseWin || false; - case 6: return opts.lowerWin || false; - case 7: return opts.refreshWin || false; - case 8: return opts.setWinSizeChars || false; - case 9: return opts.maximizeWin || false; - case 10: return opts.fullscreenWin || false; - case 11: return opts.getWinState || false; - case 13: return opts.getWinPosition || false; - case 14: return opts.getWinSizePixels || false; - case 15: return opts.getScreenSizePixels || false; - case 16: return opts.getCellSizePixels || false; - case 18: return opts.getWinSizeChars || false; - case 19: return opts.getScreenSizeChars || false; - case 20: return opts.getIconTitle || false; - case 21: return opts.getWinTitle || false; - case 22: return opts.pushTitle || false; - case 23: return opts.popTitle || false; - case 24: return opts.setWinLines || false; + case 1: return !!opts.restoreWin; + case 2: return !!opts.minimizeWin; + case 3: return !!opts.setWinPosition; + case 4: return !!opts.setWinSizePixels; + case 5: return !!opts.raiseWin; + case 6: return !!opts.lowerWin; + case 7: return !!opts.refreshWin; + case 8: return !!opts.setWinSizeChars; + case 9: return !!opts.maximizeWin; + case 10: return !!opts.fullscreenWin; + case 11: return !!opts.getWinState; + case 13: return !!opts.getWinPosition; + case 14: return !!opts.getWinSizePixels; + case 15: return !!opts.getScreenSizePixels; + case 16: return !!opts.getCellSizePixels; + case 18: return !!opts.getWinSizeChars; + case 19: return !!opts.getScreenSizeChars; + case 20: return !!opts.getIconTitle; + case 21: return !!opts.getWinTitle; + case 22: return !!opts.pushTitle; + case 23: return !!opts.popTitle; + case 24: return !!opts.setWinLines; } return false; } From ca12cf58020259da76e78427258e508721d1647f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 7 Dec 2019 10:24:22 -0800 Subject: [PATCH 169/268] 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 a165411ac..278437140 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 b00718658..1364b7d2e 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 58078459f874fd535a6fc59c711c66a6f8242f23 Mon Sep 17 00:00:00 2001 From: Nick Pezza Date: Sat, 7 Dec 2019 15:38:12 -0500 Subject: [PATCH 170/268] Update cursor width doc Co-Authored-By: Daniel Imms --- typings/xterm.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index a0e91c362..c492330ce 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -73,7 +73,7 @@ declare module 'xterm' { cursorStyle?: 'block' | 'underline' | 'bar'; /** - * The width of the bar cursor. + * The width of the cursor in CSS pixels when `cursorStyle` is set to 'bar'. */ cursorBarWidth?: number; From 48aebec01fee1c5cc10368aa3f4d2629506dc56d Mon Sep 17 00:00:00 2001 From: Nick Pezza Date: Sat, 7 Dec 2019 15:39:34 -0500 Subject: [PATCH 171/268] Remove default width value --- addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts | 2 +- src/browser/renderer/BaseRenderLayer.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index 2b181847f..b897ed44e 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -153,7 +153,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { * @param x The column to fill. * @param y The row to fill. */ - protected _fillLeftLineAtCell(x: number, y: number, width: number = 1): void { + protected _fillLeftLineAtCell(x: number, y: number, width: number): void { this._ctx.fillRect( x * this._scaledCellWidth, y * this._scaledCellHeight, diff --git a/src/browser/renderer/BaseRenderLayer.ts b/src/browser/renderer/BaseRenderLayer.ts index 2dfe5bd3a..4ea8bd52f 100644 --- a/src/browser/renderer/BaseRenderLayer.ts +++ b/src/browser/renderer/BaseRenderLayer.ts @@ -171,7 +171,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { * @param x The column to fill. * @param y The row to fill. */ - protected _fillLeftLineAtCell(x: number, y: number, width: number = 1): void { + protected _fillLeftLineAtCell(x: number, y: number, width: number): void { this._ctx.fillRect( x * this._scaledCellWidth, y * this._scaledCellHeight, From 97dc61e65f6acd496803929847d7809040c80f54 Mon Sep 17 00:00:00 2001 From: Nick Pezza Date: Sat, 7 Dec 2019 15:40:02 -0500 Subject: [PATCH 172/268] s/cursorBarWidth/cursorWidth --- addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts | 2 +- src/browser/renderer/CursorRenderLayer.ts | 2 +- src/common/services/OptionsService.ts | 2 +- src/common/services/Services.ts | 4 ++-- typings/xterm.d.ts | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts index 29c380fb3..1072a1719 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts @@ -204,7 +204,7 @@ export class CursorRenderLayer extends BaseRenderLayer { private _renderBarCursor(terminal: Terminal, x: number, y: number, cell: ICellData): void { this._ctx.save(); this._ctx.fillStyle = this._colors.cursor.css; - this._fillLeftLineAtCell(x, y, terminal.getOption('cursorBarWidth')); + this._fillLeftLineAtCell(x, y, terminal.getOption('cursorWidth')); this._ctx.restore(); } diff --git a/src/browser/renderer/CursorRenderLayer.ts b/src/browser/renderer/CursorRenderLayer.ts index 5927d8dfa..64ecf33fa 100644 --- a/src/browser/renderer/CursorRenderLayer.ts +++ b/src/browser/renderer/CursorRenderLayer.ts @@ -209,7 +209,7 @@ export class CursorRenderLayer extends BaseRenderLayer { private _renderBarCursor(x: number, y: number, cell: ICellData): void { this._ctx.save(); this._ctx.fillStyle = this._colors.cursor.css; - this._fillLeftLineAtCell(x, y, this._optionsService.options.cursorBarWidth); + this._fillLeftLineAtCell(x, y, this._optionsService.options.cursorWidth); this._ctx.restore(); } diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 987dde673..b92fa58fe 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -20,7 +20,7 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({ rows: 24, cursorBlink: false, cursorStyle: 'block', - cursorBarWidth: 1, + cursorWidth: 1, bellSound: DEFAULT_BELL_SOUND, bellStyle: 'none', drawBoldTextInBrightColors: true, diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 4f90c0e9c..2b284cd28 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -191,7 +191,7 @@ export interface IPartialTerminalOptions { cols?: number; cursorBlink?: boolean; cursorStyle?: 'block' | 'underline' | 'bar'; - cursorBarWidth?: number; + cursorWidth?: number; disableStdin?: boolean; drawBoldTextInBrightColors?: boolean; fastScrollModifier?: 'alt' | 'ctrl' | 'shift'; @@ -224,7 +224,7 @@ export interface ITerminalOptions { cols: number; cursorBlink: boolean; cursorStyle: 'block' | 'underline' | 'bar'; - cursorBarWidth: number; + cursorWidth: number; disableStdin: boolean; drawBoldTextInBrightColors: boolean; fastScrollModifier: 'alt' | 'ctrl' | 'shift' | undefined; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index c492330ce..22314fb64 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -75,7 +75,7 @@ declare module 'xterm' { /** * The width of the cursor in CSS pixels when `cursorStyle` is set to 'bar'. */ - cursorBarWidth?: number; + cursorWidth?: number; /** * Whether input should be disabled. From 70c8f8733a98c84dfdba8d955ab2ad66a5f2f1dc Mon Sep 17 00:00:00 2001 From: Nick Pezza Date: Sat, 7 Dec 2019 15:42:33 -0500 Subject: [PATCH 173/268] Sanitize cursorWidth option --- src/common/services/OptionsService.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index b92fa58fe..885f8ab5f 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -127,6 +127,7 @@ export class OptionsService implements IOptionsService { } break; case 'fastScrollSensitivity': + case 'cursorWidth': case 'scrollSensitivity': if (value <= 0) { throw new Error(`${key} cannot be less than or equal to 0, value: ${value}`); From fa4de08934c6ceb769dfe7e34031629ba01e3427 Mon Sep 17 00:00:00 2001 From: Nick Pezza Date: Sat, 7 Dec 2019 15:54:17 -0500 Subject: [PATCH 174/268] Apply changes to the dom renderer --- src/browser/renderer/dom/DomRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 018e63e5e..942a27925 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -206,7 +206,7 @@ export class DomRenderer extends Disposable implements IRenderer { ` color: ${this._colors.cursorAccent.css};` + `}` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_BAR_CLASS} {` + - ` box-shadow: 1px 0 0 ${this._colors.cursor.css} inset;` + + ` box-shadow: ${this._optionsService.options.cursorWidth}px 0 0 ${this._colors.cursor.css} inset;` + `}` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_UNDERLINE_CLASS} {` + ` box-shadow: 0 -1px 0 ${this._colors.cursor.css} inset;` + From 79ea49cafd73da44c684c54587dffcaf19894d30 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 7 Dec 2019 13:24:59 -0800 Subject: [PATCH 175/268] Floor cursorWidth to ensure it's an integer --- src/common/services/OptionsService.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 885f8ab5f..7cd111ac0 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -112,6 +112,9 @@ export class OptionsService implements IOptionsService { value = DEFAULT_OPTIONS[key]; } break; + case 'cursorWidth': + value = Math.floor(value); + // Fall through for bounds check case 'lineHeight': case 'tabStopWidth': if (value < 1) { @@ -120,6 +123,7 @@ export class OptionsService implements IOptionsService { break; case 'minimumContrastRatio': value = Math.max(1, Math.min(21, Math.round(value * 10) / 10)); + break; case 'scrollback': value = Math.min(value, 4294967295); if (value < 0) { @@ -127,7 +131,6 @@ export class OptionsService implements IOptionsService { } break; case 'fastScrollSensitivity': - case 'cursorWidth': case 'scrollSensitivity': if (value <= 0) { throw new Error(`${key} cannot be less than or equal to 0, value: ${value}`); From a628e427de0dbc01f6ce9289eae81a47dedb25cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 8 Dec 2019 18:46:40 +0100 Subject: [PATCH 176/268] update version of node-pty --- package.json | 2 +- yarn.lock | 17 ++++++----------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index e04a1ff83..6918e11fa 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "glob": "^7.0.5", "jsdom": "^11.11.0", "mocha": "^6.1.4", - "node-pty": "0.7.6", + "node-pty": "^0.9.0", "nyc": "13", "puppeteer": "^1.15.0", "source-map-loader": "^0.2.4", diff --git a/yarn.lock b/yarn.lock index 171b66e4e..e39324082 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3426,12 +3426,7 @@ mute-stream@0.0.7: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= -nan@2.10.0: - version "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.12.1, nan@^2.14.0: version "2.14.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== @@ -3530,12 +3525,12 @@ node-pre-gyp@^0.12.0: 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" - integrity sha512-ECzKUB7KkAFZ0cjyjMXp5WLJ+7YIZ1xnNmiiegOI6WdDaKABUNV5NbB1Dw9MXD4KrZipWII0wQ7RGZ6StU/7jA== +node-pty@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.9.0.tgz#8f9bcc0d1c5b970a3184ffd533d862c7eb6590a6" + integrity sha512-MBnCQl83FTYOu7B4xWw10AW77AAh7ThCE1VXEv+JeWj8mSpGo+0bwgsV+b23ljBFwEM9OmsOv3kM27iUPPm84g== dependencies: - nan "2.10.0" + nan "^2.14.0" nopt@^4.0.1: version "4.0.1" From ed7d8e7c3b9e56998da1eb810dc16a6f19cfe73f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 9 Dec 2019 19:34:45 -0800 Subject: [PATCH 177/268] Support hidden attr in DOM renderer --- src/browser/renderer/dom/DomRendererRowFactory.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index bd922f578..fea03b580 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -101,7 +101,11 @@ export class DomRendererRowFactory { charElement.classList.add(UNDERLINE_CLASS); } - charElement.textContent = this._workCell.getChars() || WHITESPACE_CELL_CHAR; + if (this._workCell.isInvisible()) { + charElement.textContent = WHITESPACE_CELL_CHAR; + } else { + charElement.textContent = this._workCell.getChars() || WHITESPACE_CELL_CHAR; + } let fg = this._workCell.getFgColor(); let fgColorMode = this._workCell.getFgColorMode(); From 93cdee3bab29db06f4a31e2ee72e9284026edcba Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 9 Dec 2019 19:39:59 -0800 Subject: [PATCH 178/268] Support hidden in WebGL renderer Fixes #2596 --- addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index c96c2a695..76acf2b2d 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -316,6 +316,11 @@ export class WebglCharAtlas implements IDisposable { this._workAttributeData.fg = fg; this._workAttributeData.bg = bg; + const invisible = !!this._workAttributeData.isInvisible(); + if (invisible) { + return NULL_RASTERIZED_GLYPH; + } + const bold = !!this._workAttributeData.isBold(); const inverse = !!this._workAttributeData.isInverse(); const dim = !!this._workAttributeData.isDim(); From 31da88ad89d99a02810218525b076dcb2c8eb610 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 9 Dec 2019 19:47:47 -0800 Subject: [PATCH 179/268] Add webgl invisible tests --- .../src/WebglRenderer.api.ts | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index bf877dd87..1cd198d96 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -148,6 +148,52 @@ describe('WebGL Renderer Integration Tests', function(): void { await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); }); + it('foreground 0-15 inivisible', async () => { + const theme: ITheme = { + black: '#010203', + red: '#040506', + green: '#070809', + yellow: '#0a0b0c', + blue: '#0d0e0f', + magenta: '#101112', + cyan: '#131415', + white: '#161718' + }; + await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); + await writeSync(`\\x1b[8;30m \\x1b[8;31m \\x1b[8;32m \\x1b[8;33m \\x1b[8;34m \\x1b[8;35m \\x1b[8;36m \\x1b[8;37m `); + await pollFor(page, () => getCellColor(1, 1), [0, 0, 0, 255]); + await pollFor(page, () => getCellColor(2, 1), [0, 0, 0, 255]); + await pollFor(page, () => getCellColor(3, 1), [0, 0, 0, 255]); + await pollFor(page, () => getCellColor(4, 1), [0, 0, 0, 255]); + await pollFor(page, () => getCellColor(5, 1), [0, 0, 0, 255]); + await pollFor(page, () => getCellColor(6, 1), [0, 0, 0, 255]); + await pollFor(page, () => getCellColor(7, 1), [0, 0, 0, 255]); + await pollFor(page, () => getCellColor(8, 1), [0, 0, 0, 255]); + }); + + it('background 0-15 inivisible', async () => { + const theme: ITheme = { + black: '#010203', + red: '#040506', + green: '#070809', + yellow: '#0a0b0c', + blue: '#0d0e0f', + magenta: '#101112', + cyan: '#131415', + white: '#161718' + }; + await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); + await writeSync(`\\x1b[8;40m█\\x1b[8;41m█\\x1b[8;42m█\\x1b[8;43m█\\x1b[8;44m█\\x1b[8;45m█\\x1b[8;46m█\\x1b[8;47m█`); + await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]); + await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]); + await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]); + await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]); + await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]); + await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]); + await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]); + await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); + }); + it('foreground 0-15 bright', async () => { const theme: ITheme = { brightBlack: '#010203', @@ -274,6 +320,46 @@ describe('WebGL Renderer Integration Tests', function(): void { } }); + it('foreground 16-255 invisible', async () => { + let data = ''; + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + data += `\\x1b[8;38;5;${16 + y * 16 + x}m \x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + const cssColor = COLORS_16_TO_255[y * 16 + x]; + const r = parseInt(cssColor.substr(1, 2), 16); + const g = parseInt(cssColor.substr(3, 2), 16); + const b = parseInt(cssColor.substr(5, 2), 16); + await pollFor(page, () => getCellColor(x + 1, y + 1), [0, 0, 0, 255]); + } + } + }); + + it('background 16-255 invisible', async () => { + let data = ''; + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + data += `\\x1b[8;48;5;${16 + y * 16 + x}m█\x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + const cssColor = COLORS_16_TO_255[y * 16 + x]; + const r = parseInt(cssColor.substr(1, 2), 16); + const g = parseInt(cssColor.substr(3, 2), 16); + const b = parseInt(cssColor.substr(5, 2), 16); + await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); + } + } + }); + it('foreground true color red', async () => { let data = ''; for (let y = 0; y < 16; y++) { @@ -561,6 +647,42 @@ describe('WebGL Renderer Integration Tests', function(): void { } } }); + + it('foreground true color grey invisible', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\\x1b[8;38;2;${i};${i};${i}m \x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(page, () => getCellColor(x + 1, y + 1), [0, 0, 0, 255]); + } + } + }); + + it('background true color grey invisible', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\\x1b[8;48;2;${i};${i};${i}m█\x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]); + } + } + }); }); describe('minimumContrastRatio', async () => { From 31669cbad654e7e9f1ae3c1c98b4f758b762873c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 11 Dec 2019 14:21:09 -0800 Subject: [PATCH 180/268] Expose texture atlas as API and use in demo Part of #2623 --- addons/xterm-addon-webgl/src/WebglAddon.ts | 9 ++++++++- addons/xterm-addon-webgl/src/WebglRenderer.ts | 4 ++++ addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts | 3 --- addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts | 2 ++ demo/client.ts | 8 ++++++++ 5 files changed, 22 insertions(+), 4 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index be867d754..3cf45f352 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -10,6 +10,7 @@ import { IColorSet } from 'browser/Types'; export class WebglAddon implements ITerminalAddon { private _terminal?: Terminal; + private _renderer?: WebglRenderer; constructor( private _preserveDrawingBuffer?: boolean @@ -22,7 +23,8 @@ export class WebglAddon implements ITerminalAddon { this._terminal = terminal; const renderService: IRenderService = (terminal)._core._renderService; const colors: IColorSet = (terminal)._core._colorManager.colors; - renderService.setRenderer(new WebglRenderer(terminal, colors, this._preserveDrawingBuffer)); + this._renderer = new WebglRenderer(terminal, colors, this._preserveDrawingBuffer); + renderService.setRenderer(this._renderer); } public dispose(): void { @@ -32,5 +34,10 @@ export class WebglAddon implements ITerminalAddon { const renderService: IRenderService = (this._terminal)._core._renderService; renderService.setRenderer((this._terminal)._core._createRenderer()); renderService.onResize(this._terminal.cols, this._terminal.rows); + this._renderer = undefined; + } + + public get textureAtlas(): HTMLCanvasElement | undefined { + return this._renderer?.textureAtlas; } } diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 6f98d2c19..ae85d9a01 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -100,6 +100,10 @@ export class WebglRenderer extends Disposable implements IRenderer { super.dispose(); } + public get textureAtlas(): HTMLCanvasElement | undefined { + return this._charAtlas?.cacheCanvas; + } + public setColors(colors: IColorSet): void { this._colors = colors; // Clear layers and force a full render diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index c96c2a695..5c3fb2391 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -86,9 +86,6 @@ export class WebglCharAtlas implements IDisposable { this._tmpCanvas.width = this._config.scaledCharWidth * 2 + TMP_CANVAS_GLYPH_PADDING * 2; this._tmpCanvas.height = this._config.scaledCharHeight + TMP_CANVAS_GLYPH_PADDING * 2; this._tmpCtx = throwIfFalsy(this._tmpCanvas.getContext('2d', {alpha: this._config.allowTransparency})); - - // This is useful for debugging - document.body.appendChild(this.cacheCanvas); } public dispose(): void { diff --git a/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts b/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts index 5199a2609..9586433a9 100644 --- a/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts +++ b/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts @@ -10,6 +10,8 @@ declare module 'xterm-addon-webgl' { * An xterm.js addon that provides search functionality. */ export class WebglAddon implements ITerminalAddon { + public textureAtlas?: HTMLCanvasElement; + constructor(preserveDrawingBuffer?: boolean); /** diff --git a/demo/client.ts b/demo/client.ts index 5b76700ea..6d146264b 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -343,7 +343,15 @@ function initAddons(term: TerminalType): void { if (checkbox.checked) { addon.instance = new addon.ctor(); term.loadAddon(addon.instance); + if (name === 'webgl') { + setTimeout(() => { + document.body.appendChild((addon.instance as WebglAddon).textureAtlas); + }, 0); + } } else { + if (name === 'webgl') { + document.body.removeChild((addon.instance as WebglAddon).textureAtlas); + } addon.instance!.dispose(); addon.instance = undefined; } From 89995fe7ca2b65d97b230a6b4ce80ba07b17447c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 11 Dec 2019 14:47:26 -0800 Subject: [PATCH 181/268] Webgl v0.4.1 --- addons/xterm-addon-webgl/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-webgl/package.json b/addons/xterm-addon-webgl/package.json index 21e394d11..550635250 100644 --- a/addons/xterm-addon-webgl/package.json +++ b/addons/xterm-addon-webgl/package.json @@ -1,6 +1,6 @@ { "name": "xterm-addon-webgl", - "version": "0.4.0", + "version": "0.4.1", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" From efb206950081b950d926d07e0d8d4c017c2cd29a Mon Sep 17 00:00:00 2001 From: ivanwonder Date: Fri, 13 Dec 2019 10:57:42 +0800 Subject: [PATCH 182/268] format color value to style '#rrggbbaa' --- src/browser/ColorManager.ts | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/src/browser/ColorManager.ts b/src/browser/ColorManager.ts index b4bcdde04..4ba49936a 100644 --- a/src/browser/ColorManager.ts +++ b/src/browser/ColorManager.ts @@ -184,34 +184,21 @@ export class ColorManager implements IColorManager { ); return fallback; } - let r: number; - let g: number; - let b: number; - let a: number; - let rgba: number; - if (css.length === 5) { - const num = parseInt(css.substr(1), 16); - r = ((num >> 12) & 0xF) * 16; - g = ((num >> 8) & 0xF) * 16; - b = ((num >> 4) & 0xF) * 16; - a = (num & 0xF) * 16; - rgba = toRgba(r, g, b, a); - } else { - rgba = parseInt(css.substr(1), 16); - r = (rgba >> 24) & 0xFF; - g = (rgba >> 16) & 0xFF; - b = (rgba >> 8) & 0xFF; - a = (rgba ) & 0xFF; - } - + // https://html.spec.whatwg.org/multipage/canvas.html#serialisation-of-a-color + // the color value has alpha less than 1.0, and the string is the color value in the CSS rgba() + const [r, g, b, a] = this._ctx.fillStyle.substring(5, this._ctx.fillStyle.length - 1).split(',').map(component => Number(component)); + const alpha = Math.round(a * 255); + const rgba: number = toRgba(r, g, b, alpha); return { rgba, - css: toCss(r, g, b, a) + css: toCss(r, g, b, alpha) }; } return { - css, + // https://html.spec.whatwg.org/multipage/canvas.html#serialisation-of-a-color + // if it has alpha equal to 1.0, then the string is a lowercase six-digit hex value, prefixed with a "#" character + css: this._ctx.fillStyle, rgba: toRgba(data[0], data[1], data[2], data[3]) }; } From ed334fd7905c92f05a50e7acfafa65b73ffdc907 Mon Sep 17 00:00:00 2001 From: Clark Meyer Date: Sat, 14 Dec 2019 21:59:43 -0800 Subject: [PATCH 183/268] Added Gus to list of xterm real-world users --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 70ff7891d..cd0d1d07e 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,7 @@ Xterm.js is used in several world-class applications to provide great terminal e - [**tty-share**](https://tty-share.com): Extremely simple terminal sharing over the Internet. - [**Ten Hands**](https://github.com/saisandeepvaddi/ten-hands): One place to run your command-line tasks. - [**WebAssembly.sh**](https://webassembly.sh): A WebAssembly WASI browser terminal +- [**Gus**](https://gus.jp): A shared coding pad where you can run Python with xterm.js [And much more...](https://github.com/xtermjs/xterm.js/network/dependents) From 94f5c378846d7d3cc80b21bf1fdc75184d65cbcd Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 18 Dec 2019 09:59:33 +1100 Subject: [PATCH 184/268] Support events with 2 args This will allow higher perf events by avoiding object creation --- src/common/EventEmitter.ts | 30 +++++++++++++++--------------- typings/xterm.d.ts | 6 +++--- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/common/EventEmitter.ts b/src/common/EventEmitter.ts index 5991e338d..301574e42 100644 --- a/src/common/EventEmitter.ts +++ b/src/common/EventEmitter.ts @@ -5,28 +5,28 @@ import { IDisposable } from 'common/Types'; -interface IListener { - (e: T): void; +interface IListener { + (arg1: T, arg2: U): void; } -export interface IEvent { - (listener: (e: T) => any): IDisposable; +export interface IEvent { + (listener: (arg1: T, arg2: U) => any): IDisposable; } -export interface IEventEmitter { - event: IEvent; - fire(data: T): void; +export interface IEventEmitter { + event: IEvent; + fire(arg1: T, arg2: U): void; dispose(): void; } -export class EventEmitter implements IEventEmitter { - private _listeners: IListener[] = []; - private _event?: IEvent; +export class EventEmitter implements IEventEmitter { + private _listeners: IListener[] = []; + private _event?: IEvent; private _disposed: boolean = false; - public get event(): IEvent { + public get event(): IEvent { if (!this._event) { - this._event = (listener: (e: T) => any) => { + this._event = (listener: (arg1: T, arg2: U) => any) => { this._listeners.push(listener); const disposable = { dispose: () => { @@ -46,13 +46,13 @@ export class EventEmitter implements IEventEmitter { return this._event; } - public fire(data: T): void { - const queue: IListener[] = []; + public fire(arg1: T, arg2: U): void { + const queue: IListener[] = []; for (let i = 0; i < this._listeners.length; i++) { queue.push(this._listeners[i]); } for (let i = 0; i < queue.length; i++) { - queue[i].call(undefined, data); + queue[i].call(undefined, arg1, arg2); } } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 22314fb64..ac33d087d 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -336,8 +336,8 @@ declare module 'xterm' { * An event that can be listened to. * @returns an `IDisposable` to stop listening. */ - export interface IEvent { - (listener: (e: T) => any): IDisposable; + export interface IEvent { + (listener: (arg1: T, arg2: U) => any): IDisposable; } /** @@ -444,7 +444,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; From a2297a5fa6ce04600d0ded4f5fd0ce1ca43fb902 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 18 Dec 2019 10:01:33 +1100 Subject: [PATCH 185/268] Remove Terminal.refresh usage in InputHandler --- src/InputHandler.ts | 8 +++++--- src/Terminal.ts | 1 + src/Types.d.ts | 1 - 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 7a0a6ac32..cfd2e1ec5 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -128,6 +128,8 @@ export class InputHandler extends Disposable implements IInputHandler { private _utf8Decoder: Utf8ToUtf32 = new Utf8ToUtf32(); private _workCell: CellData = new CellData(); + private _onRequestRefreshRows = new EventEmitter(); + public get onRequestRefreshRows(): IEvent { return this._onRequestRefreshRows.event; } private _onCursorMove = new EventEmitter(); public get onCursorMove(): IEvent { return this._onCursorMove.event; } private _onLineFeed = new EventEmitter(); @@ -372,7 +374,7 @@ export class InputHandler extends Disposable implements IInputHandler { } // Refresh any dirty rows accumulated as part of parsing - this._terminal.refresh(this._dirtyRowService.start, this._dirtyRowService.end); + this._onRequestRefreshRows.fire(this._dirtyRowService.start, this._dirtyRowService.end); } public print(data: Uint32Array, start: number, end: number): void { @@ -1465,7 +1467,7 @@ export class InputHandler extends Disposable implements IInputHandler { case 47: // alt screen buffer case 1047: // alt screen buffer this._bufferService.buffers.activateAltBuffer(this._terminal.eraseAttrData()); - this._terminal.refresh(0, this._bufferService.rows - 1); + this._onRequestRefreshRows.fire(0, this._bufferService.rows - 1); if (this._terminal.viewport) { this._terminal.viewport.syncScrollArea(); } @@ -1639,7 +1641,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (params.params[i] === 1049) { this.restoreCursor(); } - this._terminal.refresh(0, this._bufferService.rows - 1); + this._onRequestRefreshRows.fire(0, this._bufferService.rows - 1); if (this._terminal.viewport) { this._terminal.viewport.syncScrollArea(); } diff --git a/src/Terminal.ts b/src/Terminal.ts index 5c08a7092..bb67ac1f1 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -277,6 +277,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // Register input handler and refire/handle events this._inputHandler = new InputHandler(this, this._bufferService, this._coreService, this._dirtyRowService, this._logService, this.optionsService, this._coreMouseService); + this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end)); this._inputHandler.onCursorMove(() => this._onCursorMove.fire()); this._inputHandler.onLineFeed(() => this._onLineFeed.fire()); this.register(this._inputHandler); diff --git a/src/Types.d.ts b/src/Types.d.ts index f4d3a556d..063331dde 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -56,7 +56,6 @@ export interface IInputHandlingTerminal { resize(x: number, y: number): void; reset(): void; showCursor(): void; - refresh(start: number, end: number): void; handleTitle(title: string): void; } From 05e1cd84787dd90e68781de4a050d21cd3e5a7cf Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 18 Dec 2019 10:32:38 +1100 Subject: [PATCH 186/268] Move charset logic into core service --- src/InputHandler.ts | 32 +++++++++++++------------- src/Terminal.ts | 36 ++---------------------------- src/TestUtils.test.ts | 13 ----------- src/Types.d.ts | 6 ----- src/common/TestUtils.test.ts | 10 ++++++++- src/common/Types.d.ts | 6 +++++ src/common/services/CoreService.ts | 25 ++++++++++++++++++++- src/common/services/Services.ts | 19 +++++++++++++++- 8 files changed, 74 insertions(+), 73 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index cfd2e1ec5..1c9e65747 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -381,7 +381,7 @@ export class InputHandler extends Disposable implements IInputHandler { let code: number; let chWidth: number; const buffer = this._bufferService.buffer; - const charset = this._terminal.charset; + const charset = this._coreService.charsetModes.charset; const screenReaderMode = this._optionsService.options.screenReaderMode; const cols = this._bufferService.cols; const wraparoundMode = this._terminal.wraparoundMode; @@ -608,7 +608,7 @@ export class InputHandler extends Disposable implements IInputHandler { * G1 character set. */ public shiftOut(): void { - this._terminal.setgLevel(1); + this._coreService.setgLevel(1); } /** @@ -617,7 +617,7 @@ export class InputHandler extends Disposable implements IInputHandler { * character set (the default). */ public shiftIn(): void { - this._terminal.setgLevel(0); + this._coreService.setgLevel(0); } /** @@ -1396,10 +1396,10 @@ export class InputHandler extends Disposable implements IInputHandler { this._coreService.decPrivateModes.applicationCursorKeys = true; break; case 2: - this._terminal.setgCharset(0, DEFAULT_CHARSET); - this._terminal.setgCharset(1, DEFAULT_CHARSET); - this._terminal.setgCharset(2, DEFAULT_CHARSET); - this._terminal.setgCharset(3, DEFAULT_CHARSET); + this._coreService.setgCharset(0, DEFAULT_CHARSET); + this._coreService.setgCharset(1, DEFAULT_CHARSET); + this._coreService.setgCharset(2, DEFAULT_CHARSET); + this._coreService.setgCharset(3, DEFAULT_CHARSET); // set VT100 mode here break; case 3: // 132 col mode @@ -1974,9 +1974,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._bufferService.buffer.scrollBottom = this._bufferService.rows - 1; this._terminal.curAttrData = DEFAULT_ATTR_DATA.clone(); this._bufferService.buffer.x = this._bufferService.buffer.y = 0; // ? - this._terminal.charset = null; - this._terminal.glevel = 0; // ?? - this._terminal.charsets = [null]; // ?? + this._coreService.softReset(); } /** @@ -2040,7 +2038,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._bufferService.buffer.savedY = this._bufferService.buffer.ybase + this._bufferService.buffer.y; this._bufferService.buffer.savedCurAttrData.fg = this._terminal.curAttrData.fg; this._bufferService.buffer.savedCurAttrData.bg = this._terminal.curAttrData.bg; - this._bufferService.buffer.savedCharset = this._terminal.charset; + this._bufferService.buffer.savedCharset = this._coreService.charsetModes.charset; } @@ -2054,9 +2052,9 @@ export class InputHandler extends Disposable implements IInputHandler { this._bufferService.buffer.y = Math.max(this._bufferService.buffer.savedY - this._bufferService.buffer.ybase, 0); this._terminal.curAttrData.fg = this._bufferService.buffer.savedCurAttrData.fg; this._terminal.curAttrData.bg = this._bufferService.buffer.savedCurAttrData.bg; - this._terminal.charset = (this as any)._savedCharset; + this._coreService.charsetModes.charset = (this as any)._savedCharset; if (this._bufferService.buffer.savedCharset) { - this._terminal.charset = this._bufferService.buffer.savedCharset; + this._coreService.charsetModes.charset = this._bufferService.buffer.savedCharset; } this._restrictCursor(); } @@ -2115,8 +2113,8 @@ export class InputHandler extends Disposable implements IInputHandler { * therefore ESC % G does the same. */ public selectDefaultCharset(): void { - this._terminal.setgLevel(0); - this._terminal.setgCharset(0, DEFAULT_CHARSET); // US (default) + this._coreService.setgLevel(0); + this._coreService.setgCharset(0, DEFAULT_CHARSET); // US (default) } /** @@ -2143,7 +2141,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (collectAndFlag[0] === '/') { return; // TODO: Is this supported? } - this._terminal.setgCharset(GLEVEL[collectAndFlag[0]], CHARSETS[collectAndFlag[1]] || DEFAULT_CHARSET); + this._coreService.setgCharset(GLEVEL[collectAndFlag[0]], CHARSETS[collectAndFlag[1]] || DEFAULT_CHARSET); return; } @@ -2222,7 +2220,7 @@ export class InputHandler extends Disposable implements IInputHandler { * you use another locking shift. (partly supported) */ public setgLevel(level: number): void { - this._terminal.setgLevel(level); // TODO: save to move from terminal? + this._coreService.setgLevel(level); } /** diff --git a/src/Terminal.ts b/src/Terminal.ts index bb67ac1f1..eb199ea11 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -39,7 +39,7 @@ import { MouseZoneManager } from 'browser/MouseZoneManager'; import { AccessibilityManager } from './AccessibilityManager'; import { ITheme, IMarker, IDisposable, ISelectionPosition } from 'xterm'; import { DomRenderer } from 'browser/renderer/dom/DomRenderer'; -import { IKeyboardEvent, KeyboardResultType, ICharset, IBufferLine, IAttributeData, CoreMouseEventType, CoreMouseButton, CoreMouseAction } from 'common/Types'; +import { IKeyboardEvent, KeyboardResultType, IBufferLine, IAttributeData, CoreMouseEventType, CoreMouseButton, CoreMouseAction } from 'common/Types'; import { evaluateKeyboardEvent } from 'common/input/Keyboard'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; @@ -116,13 +116,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp public wraparoundMode: boolean; // defaults: xterm - true, vt100 - false public bracketedPasteMode: boolean; - // charset - // The current charset - public charset: ICharset; - public gcharset: number; - public glevel: number; - public charsets: ICharset[]; - // mouse properties public mouseEvents: CoreMouseEventType = CoreMouseEventType.NONE; public sendFocus: boolean; @@ -261,11 +254,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this.bracketedPasteMode = false; // charset - this.charset = null; - this.gcharset = null; - this.glevel = 0; - // TODO: Can this be just []? - this.charsets = [null]; + this._coreService.softReset(); this.curAttrData = DEFAULT_ATTR_DATA.clone(); this._eraseAttrData = DEFAULT_ATTR_DATA.clone(); @@ -1307,27 +1296,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp return thirdLevelKey && (!ev.keyCode || ev.keyCode > 47); } - /** - * Set the G level of the terminal - * @param g - */ - public setgLevel(g: number): void { - this.glevel = g; - this.charset = this.charsets[g]; - } - - /** - * Set the charset for the given G level of the terminal - * @param g - * @param charset - */ - public setgCharset(g: number, charset: ICharset): void { - this.charsets[g] = charset; - if (this.glevel === g) { - this.charset = charset; - } - } - protected _keyUp(ev: KeyboardEvent): void { if (this._customKeyEventHandler && this._customKeyEventHandler(ev) === false) { return; diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 84c0d0b75..8954ae838 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -201,10 +201,6 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal { options: ITerminalOptions = {}; cols: number; rows: number; - charset: { [key: string]: string; }; - gcharset: number; - glevel: number; - charsets: { [key: string]: string; }[]; applicationKeypad: boolean; applicationCursor: boolean; originMode: boolean; @@ -243,9 +239,6 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal { nextStop(x?: number): number { throw new Error('Method not implemented.'); } - setgLevel(g: number): void { - throw new Error('Method not implemented.'); - } eraseAttrData(): IAttributeData { throw new Error('Method not implemented.'); } @@ -264,9 +257,6 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal { is(term: string): boolean { throw new Error('Method not implemented.'); } - setgCharset(g: number, charset: { [key: string]: string; }): void { - throw new Error('Method not implemented.'); - } resize(x: number, y: number): void { throw new Error('Method not implemented.'); } @@ -279,9 +269,6 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal { showCursor(): void { throw new Error('Method not implemented.'); } - refresh(start: number, end: number): void { - throw new Error('Method not implemented.'); - } matchColor(r1: number, g1: number, b1: number): number { throw new Error('Method not implemented.'); } diff --git a/src/Types.d.ts b/src/Types.d.ts index 063331dde..3a3aef624 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -25,10 +25,6 @@ export interface IInputHandlingTerminal { options: ITerminalOptions; cols: number; rows: number; - charset: ICharset; - gcharset: number; - glevel: number; - charsets: ICharset[]; applicationKeypad: boolean; originMode: boolean; insertMode: boolean; @@ -49,10 +45,8 @@ export interface IInputHandlingTerminal { bell(): void; focus(): void; scroll(isWrapped?: boolean): void; - setgLevel(g: number): void; eraseAttrData(): IAttributeData; is(term: string): boolean; - setgCharset(g: number, charset: ICharset): void; resize(x: number, y: number): void; reset(): void; showCursor(): void; diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 7e47d4b0a..96027804c 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -9,7 +9,7 @@ import { clone } from 'common/Clone'; import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; import { IBufferSet, IBuffer } from 'common/buffer/Types'; import { BufferSet } from 'common/buffer/BufferSet'; -import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEventType } from 'common/Types'; +import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEventType, ICharsetModes, ICharset } from 'common/Types'; export class MockBufferService implements IBufferService { serviceBrand: any; @@ -47,11 +47,19 @@ export class MockCoreService implements ICoreService { isCursorHidden: boolean = false; isFocused: boolean = false; serviceBrand: any; + charsetModes: ICharsetModes = { + charset: undefined, + charsets: [], + glevel: 0 + }; decPrivateModes: IDecPrivateModes = {} as any; onData: IEvent = new EventEmitter().event; onUserInput: IEvent = new EventEmitter().event; onBinary: IEvent = new EventEmitter().event; reset(): void {} + softReset(): void {} + setgLevel(g: number): void {} + setgCharset(g: number, charset: ICharset): void {} triggerDataEvent(data: string, wasUserInput?: boolean): void {} triggerBinaryEvent(data: string): void {} } diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index 2dcc704bd..b8cb9b802 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -65,6 +65,12 @@ export interface ICharset { [key: string]: string; } +export interface ICharsetModes { + charset: ICharset | undefined; + glevel: number; + charsets: ICharset[]; +} + export type CharData = [number, string, number, number]; export type IColorRGB = [number, number, number]; diff --git a/src/common/services/CoreService.ts b/src/common/services/CoreService.ts index 35b61e847..af0d5f63e 100644 --- a/src/common/services/CoreService.ts +++ b/src/common/services/CoreService.ts @@ -5,7 +5,7 @@ import { ICoreService, ILogService, IOptionsService, IBufferService } from 'common/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; -import { IDecPrivateModes } from 'common/Types'; +import { IDecPrivateModes, ICharset, ICharsetModes } from 'common/Types'; import { clone } from 'common/Clone'; const DEFAULT_DEC_PRIVATE_MODES: IDecPrivateModes = Object.freeze({ @@ -18,6 +18,11 @@ export class CoreService implements ICoreService { public isCursorInitialized: boolean = false; public isCursorHidden: boolean = false; public decPrivateModes: IDecPrivateModes; + public charsetModes: ICharsetModes = { + charset: undefined, + charsets: [], + glevel: 0 + }; private _onData = new EventEmitter(); public get onData(): IEvent { return this._onData.event; } @@ -40,6 +45,12 @@ export class CoreService implements ICoreService { this.decPrivateModes = clone(DEFAULT_DEC_PRIVATE_MODES); } + public softReset(): void { + this.charsetModes.charset = undefined; + this.charsetModes.charsets = []; + this.charsetModes.glevel = 0; + } + public triggerDataEvent(data: string, wasUserInput: boolean = false): void { // Prevents all events to pty process if stdin is disabled if (this._optionsService.options.disableStdin) { @@ -69,4 +80,16 @@ export class CoreService implements ICoreService { this._logService.debug(`sending binary "${data}"`, () => data.split('').map(e => e.charCodeAt(0))); this._onBinary.fire(data); } + + public setgLevel(g: number): void { + this.charsetModes.glevel = g; + this.charsetModes.charset = this.charsetModes.charsets[g]; + } + + public setgCharset(g: number, charset: ICharset): void { + this.charsetModes.charsets[g] = charset; + if (this.charsetModes.glevel === g) { + this.charsetModes.charset = charset; + } + } } diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 2b284cd28..9e47be263 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -5,7 +5,7 @@ import { IEvent } from 'common/EventEmitter'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; -import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType } from 'common/Types'; +import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharsetModes, ICharset } from 'common/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; export const IBufferService = createDecorator('BufferService'); @@ -64,6 +64,9 @@ export interface ICoreService { */ isCursorInitialized: boolean; isCursorHidden: boolean; + + charsetModes: ICharsetModes; + readonly decPrivateModes: IDecPrivateModes; readonly onData: IEvent; @@ -71,6 +74,7 @@ export interface ICoreService { readonly onBinary: IEvent; reset(): void; + softReset(): void; /** * Triggers the onData event in the public API. @@ -87,6 +91,19 @@ export interface ICoreService { * @param data The data that is being emitted. */ triggerBinaryEvent(data: string): void; + + /** + * Set the G level of the terminal. + * @param g + */ + setgLevel(g: number): void; + + /** + * Set the charset for the given G level of the terminal. + * @param g + * @param charset + */ + setgCharset(g: number, charset: ICharset): void; } export const IDirtyRowService = createDecorator('DirtyRowService'); From 224fb5c6a2bfd9667e35db6e9c3d4a0aea79a0f1 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 18 Dec 2019 10:53:05 +1100 Subject: [PATCH 187/268] Move charset into own service --- src/InputHandler.test.ts | 20 ++++++++-------- src/InputHandler.ts | 33 ++++++++++++++------------- src/Terminal.ts | 10 +++++--- src/common/TestUtils.test.ts | 24 ++++++++++--------- src/common/Types.d.ts | 8 +------ src/common/services/CharsetService.ts | 33 +++++++++++++++++++++++++++ src/common/services/CoreService.ts | 25 +------------------- src/common/services/Services.ts | 16 +++++++++---- 8 files changed, 94 insertions(+), 75 deletions(-) create mode 100644 src/common/services/CharsetService.ts diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index d8defa1cc..fa9ef67c9 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -13,7 +13,7 @@ import { CellData } from 'common/buffer/CellData'; import { Attributes } from 'common/buffer/Constants'; import { AttributeData } from 'common/buffer/AttributeData'; import { Params } from 'common/parser/Params'; -import { MockCoreService, MockBufferService, MockDirtyRowService, MockOptionsService, MockLogService, MockCoreMouseService } from 'common/TestUtils.test'; +import { MockCoreService, MockBufferService, MockDirtyRowService, MockOptionsService, MockLogService, MockCoreMouseService, MockCharsetService } from 'common/TestUtils.test'; import { IBufferService } from 'common/services/Services'; import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; import { clone } from 'common/Clone'; @@ -41,7 +41,7 @@ describe('InputHandler', () => { bufferService.buffer.x = 1; bufferService.buffer.y = 2; bufferService.buffer.ybase = 0; - const inputHandler = new InputHandler(terminal, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); + const inputHandler = new InputHandler(terminal, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); // Save cursor position inputHandler.saveCursor(); assert.equal(bufferService.buffer.x, 1); @@ -60,7 +60,7 @@ describe('InputHandler', () => { describe('setCursorStyle', () => { it('should call Terminal.setOption with correct params', () => { const optionsService = new MockOptionsService(); - const inputHandler = new InputHandler(new MockInputHandlingTerminal(), new MockBufferService(80, 30), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), optionsService, new MockCoreMouseService()); + const inputHandler = new InputHandler(new MockInputHandlingTerminal(), new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), optionsService, new MockCoreMouseService()); inputHandler.setCursorStyle(Params.fromArray([0])); assert.equal(optionsService.options['cursorStyle'], 'block'); @@ -101,7 +101,7 @@ describe('InputHandler', () => { it('should toggle Terminal.bracketedPasteMode', () => { const terminal = new MockInputHandlingTerminal(); terminal.bracketedPasteMode = false; - const inputHandler = new InputHandler(terminal, new MockBufferService(80, 30), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); + const inputHandler = new InputHandler(terminal, new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); // Set bracketed paste mode inputHandler.setModePrivate(Params.fromArray([2004])); assert.equal(terminal.bracketedPasteMode, true); @@ -120,7 +120,7 @@ describe('InputHandler', () => { it('insertChars', function(): void { const term = new Terminal(); const bufferService = new MockBufferService(80, 30); - const inputHandler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); + const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); // insert some data in first and second line inputHandler.parse(Array(bufferService.cols - 9).join('a')); @@ -158,7 +158,7 @@ describe('InputHandler', () => { it('deleteChars', function(): void { const term = new Terminal(); const bufferService = new MockBufferService(80, 30); - const inputHandler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); + const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); // insert some data in first and second line inputHandler.parse(Array(bufferService.cols - 9).join('a')); @@ -199,7 +199,7 @@ describe('InputHandler', () => { it('eraseInLine', function(): void { const term = new Terminal(); const bufferService = new MockBufferService(80, 30); - const inputHandler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); + const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); // fill 6 lines to test 3 different states inputHandler.parse(Array(bufferService.cols + 1).join('a')); @@ -228,7 +228,7 @@ describe('InputHandler', () => { it('eraseInDisplay', function(): void { const term = new Terminal({cols: 80, rows: 7}); const bufferService = new MockBufferService(80, 7); - const inputHandler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); + const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); // fill display with a's for (let i = 0; i < bufferService.rows; ++i) inputHandler.parse(Array(bufferService.cols + 1).join('a')); @@ -363,7 +363,7 @@ describe('InputHandler', () => { describe('print', () => { it('should not cause an infinite loop (regression test)', () => { const term = new Terminal(); - const inputHandler = new InputHandler(term, new MockBufferService(80, 30), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); + const inputHandler = new InputHandler(term, new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); const container = new Uint32Array(10); container[0] = 0x200B; inputHandler.print(container, 0, 1); @@ -378,7 +378,7 @@ describe('InputHandler', () => { beforeEach(() => { term = new Terminal(); bufferService = new MockBufferService(80, 30); - handler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); + handler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); }); it('should handle DECSET/DECRST 47 (alt screen buffer)', () => { handler.parse('\x1b[?47h\r\n\x1b[31mJUNK\x1b[?47lTEST'); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 1c9e65747..79c769c7c 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -19,7 +19,7 @@ import { NULL_CELL_CODE, NULL_CELL_WIDTH, Attributes, FgFlags, BgFlags, Content import { CellData } from 'common/buffer/CellData'; import { AttributeData } from 'common/buffer/AttributeData'; import { IAttributeData, IDisposable } from 'common/Types'; -import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService, ICoreMouseService } from 'common/services/Services'; +import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService, ICoreMouseService, ICharsetService } from 'common/services/Services'; import { OscHandler } from 'common/parser/OscParser'; import { DcsHandler } from 'common/parser/DcsParser'; @@ -140,6 +140,7 @@ export class InputHandler extends Disposable implements IInputHandler { constructor( protected _terminal: IInputHandlingTerminal, private readonly _bufferService: IBufferService, + private readonly _charsetService: ICharsetService, private readonly _coreService: ICoreService, private readonly _dirtyRowService: IDirtyRowService, private readonly _logService: ILogService, @@ -381,7 +382,7 @@ export class InputHandler extends Disposable implements IInputHandler { let code: number; let chWidth: number; const buffer = this._bufferService.buffer; - const charset = this._coreService.charsetModes.charset; + const charset = this._charsetService.charset; const screenReaderMode = this._optionsService.options.screenReaderMode; const cols = this._bufferService.cols; const wraparoundMode = this._terminal.wraparoundMode; @@ -608,7 +609,7 @@ export class InputHandler extends Disposable implements IInputHandler { * G1 character set. */ public shiftOut(): void { - this._coreService.setgLevel(1); + this._charsetService.setgLevel(1); } /** @@ -617,7 +618,7 @@ export class InputHandler extends Disposable implements IInputHandler { * character set (the default). */ public shiftIn(): void { - this._coreService.setgLevel(0); + this._charsetService.setgLevel(0); } /** @@ -1396,10 +1397,10 @@ export class InputHandler extends Disposable implements IInputHandler { this._coreService.decPrivateModes.applicationCursorKeys = true; break; case 2: - this._coreService.setgCharset(0, DEFAULT_CHARSET); - this._coreService.setgCharset(1, DEFAULT_CHARSET); - this._coreService.setgCharset(2, DEFAULT_CHARSET); - this._coreService.setgCharset(3, DEFAULT_CHARSET); + this._charsetService.setgCharset(0, DEFAULT_CHARSET); + this._charsetService.setgCharset(1, DEFAULT_CHARSET); + this._charsetService.setgCharset(2, DEFAULT_CHARSET); + this._charsetService.setgCharset(3, DEFAULT_CHARSET); // set VT100 mode here break; case 3: // 132 col mode @@ -1974,7 +1975,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._bufferService.buffer.scrollBottom = this._bufferService.rows - 1; this._terminal.curAttrData = DEFAULT_ATTR_DATA.clone(); this._bufferService.buffer.x = this._bufferService.buffer.y = 0; // ? - this._coreService.softReset(); + this._charsetService.reset(); } /** @@ -2038,7 +2039,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._bufferService.buffer.savedY = this._bufferService.buffer.ybase + this._bufferService.buffer.y; this._bufferService.buffer.savedCurAttrData.fg = this._terminal.curAttrData.fg; this._bufferService.buffer.savedCurAttrData.bg = this._terminal.curAttrData.bg; - this._bufferService.buffer.savedCharset = this._coreService.charsetModes.charset; + this._bufferService.buffer.savedCharset = this._charsetService.charset; } @@ -2052,9 +2053,9 @@ export class InputHandler extends Disposable implements IInputHandler { this._bufferService.buffer.y = Math.max(this._bufferService.buffer.savedY - this._bufferService.buffer.ybase, 0); this._terminal.curAttrData.fg = this._bufferService.buffer.savedCurAttrData.fg; this._terminal.curAttrData.bg = this._bufferService.buffer.savedCurAttrData.bg; - this._coreService.charsetModes.charset = (this as any)._savedCharset; + this._charsetService.charset = (this as any)._savedCharset; if (this._bufferService.buffer.savedCharset) { - this._coreService.charsetModes.charset = this._bufferService.buffer.savedCharset; + this._charsetService.charset = this._bufferService.buffer.savedCharset; } this._restrictCursor(); } @@ -2113,8 +2114,8 @@ export class InputHandler extends Disposable implements IInputHandler { * therefore ESC % G does the same. */ public selectDefaultCharset(): void { - this._coreService.setgLevel(0); - this._coreService.setgCharset(0, DEFAULT_CHARSET); // US (default) + this._charsetService.setgLevel(0); + this._charsetService.setgCharset(0, DEFAULT_CHARSET); // US (default) } /** @@ -2141,7 +2142,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (collectAndFlag[0] === '/') { return; // TODO: Is this supported? } - this._coreService.setgCharset(GLEVEL[collectAndFlag[0]], CHARSETS[collectAndFlag[1]] || DEFAULT_CHARSET); + this._charsetService.setgCharset(GLEVEL[collectAndFlag[0]], CHARSETS[collectAndFlag[1]] || DEFAULT_CHARSET); return; } @@ -2220,7 +2221,7 @@ export class InputHandler extends Disposable implements IInputHandler { * you use another locking shift. (partly supported) */ public setgLevel(level: number): void { - this._coreService.setgLevel(level); + this._charsetService.setgLevel(level); } /** diff --git a/src/Terminal.ts b/src/Terminal.ts index eb199ea11..2de11f8d0 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -46,7 +46,7 @@ import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { handleWindowsModeLineFeed } from 'common/WindowsMode'; import { ColorManager } from 'browser/ColorManager'; import { RenderService } from 'browser/services/RenderService'; -import { IOptionsService, IBufferService, ICoreMouseService, ICoreService, ILogService, IDirtyRowService, IInstantiationService } from 'common/services/Services'; +import { IOptionsService, IBufferService, ICoreMouseService, ICoreService, ILogService, IDirtyRowService, IInstantiationService, ICharsetService } from 'common/services/Services'; import { OptionsService } from 'common/services/OptionsService'; import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService, ICoreBrowserService } from 'browser/services/Services'; import { CharSizeService } from 'browser/services/CharSizeService'; @@ -64,6 +64,7 @@ import { InstantiationService } from 'common/services/InstantiationService'; import { CoreMouseService } from 'common/services/CoreMouseService'; import { WriteBuffer } from 'common/input/WriteBuffer'; import { CoreBrowserService } from 'browser/services/CoreBrowserService'; +import { CharsetService } from 'common/services/CharsetService'; // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; @@ -96,6 +97,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // common services private _bufferService: IBufferService; private _coreService: ICoreService; + private _charsetService: ICharsetService; private _coreMouseService: ICoreMouseService; private _dirtyRowService: IDirtyRowService; private _instantiationService: IInstantiationService; @@ -221,6 +223,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._instantiationService.setService(ICoreMouseService, this._coreMouseService); this._dirtyRowService = this._instantiationService.createInstance(DirtyRowService); this._instantiationService.setService(IDirtyRowService, this._dirtyRowService); + this._charsetService = this._instantiationService.createInstance(CharsetService); + this._instantiationService.setService(ICharsetService, this._charsetService); this._setupOptionsListeners(); this._setup(); @@ -254,7 +258,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this.bracketedPasteMode = false; // charset - this._coreService.softReset(); + this._charsetService.reset(); this.curAttrData = DEFAULT_ATTR_DATA.clone(); this._eraseAttrData = DEFAULT_ATTR_DATA.clone(); @@ -265,7 +269,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._userScrolling = false; // Register input handler and refire/handle events - this._inputHandler = new InputHandler(this, this._bufferService, this._coreService, this._dirtyRowService, this._logService, this.optionsService, this._coreMouseService); + this._inputHandler = new InputHandler(this, this._bufferService, this._charsetService, this._coreService, this._dirtyRowService, this._logService, this.optionsService, this._coreMouseService); this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end)); this._inputHandler.onCursorMove(() => this._onCursorMove.fire()); this._inputHandler.onLineFeed(() => this._onLineFeed.fire()); diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 96027804c..11bb84bce 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -3,13 +3,13 @@ * @license MIT */ -import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, IPartialTerminalOptions, IDirtyRowService, ICoreMouseService } from 'common/services/Services'; +import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, IPartialTerminalOptions, IDirtyRowService, ICoreMouseService, ICharsetService } from 'common/services/Services'; import { IEvent, EventEmitter } from 'common/EventEmitter'; import { clone } from 'common/Clone'; import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; import { IBufferSet, IBuffer } from 'common/buffer/Types'; import { BufferSet } from 'common/buffer/BufferSet'; -import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEventType, ICharsetModes, ICharset } from 'common/Types'; +import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEventType, ICharset } from 'common/Types'; export class MockBufferService implements IBufferService { serviceBrand: any; @@ -42,24 +42,26 @@ export class MockCoreMouseService implements ICoreMouseService { } } +export class MockCharsetService implements ICharsetService { + serviceBrand: any; + charset: ICharset | undefined; + glevel: number = 0; + charsets: readonly ICharset[] = []; + reset(): void {} + setgLevel(g: number): void {} + setgCharset(g: number, charset: ICharset): void {} +} + export class MockCoreService implements ICoreService { + serviceBrand: any; isCursorInitialized: boolean = false; isCursorHidden: boolean = false; isFocused: boolean = false; - serviceBrand: any; - charsetModes: ICharsetModes = { - charset: undefined, - charsets: [], - glevel: 0 - }; decPrivateModes: IDecPrivateModes = {} as any; onData: IEvent = new EventEmitter().event; onUserInput: IEvent = new EventEmitter().event; onBinary: IEvent = new EventEmitter().event; reset(): void {} - softReset(): void {} - setgLevel(g: number): void {} - setgCharset(g: number, charset: ICharset): void {} triggerDataEvent(data: string, wasUserInput?: boolean): void {} triggerBinaryEvent(data: string): void {} } diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index b8cb9b802..35567dee7 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -62,13 +62,7 @@ export interface IKeyboardResult { } export interface ICharset { - [key: string]: string; -} - -export interface ICharsetModes { - charset: ICharset | undefined; - glevel: number; - charsets: ICharset[]; + [key: string]: string | undefined; } export type CharData = [number, string, number, number]; diff --git a/src/common/services/CharsetService.ts b/src/common/services/CharsetService.ts new file mode 100644 index 000000000..5d20628f3 --- /dev/null +++ b/src/common/services/CharsetService.ts @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { ICharsetService } from 'common/services/Services'; +import { ICharset } from 'common/Types'; + +export class CharsetService implements ICharsetService { + serviceBrand: any; + + public charset: ICharset | undefined; + public charsets: ICharset[] = []; + public glevel: number = 0; + + public reset(): void { + this.charset = undefined; + this.charsets = []; + this.glevel = 0; + } + + public setgLevel(g: number): void { + this.glevel = g; + this.charset = this.charsets[g]; + } + + public setgCharset(g: number, charset: ICharset): void { + this.charsets[g] = charset; + if (this.glevel === g) { + this.charset = charset; + } + } +} diff --git a/src/common/services/CoreService.ts b/src/common/services/CoreService.ts index af0d5f63e..58552f3f7 100644 --- a/src/common/services/CoreService.ts +++ b/src/common/services/CoreService.ts @@ -5,7 +5,7 @@ import { ICoreService, ILogService, IOptionsService, IBufferService } from 'common/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; -import { IDecPrivateModes, ICharset, ICharsetModes } from 'common/Types'; +import { IDecPrivateModes, ICharset } from 'common/Types'; import { clone } from 'common/Clone'; const DEFAULT_DEC_PRIVATE_MODES: IDecPrivateModes = Object.freeze({ @@ -18,11 +18,6 @@ export class CoreService implements ICoreService { public isCursorInitialized: boolean = false; public isCursorHidden: boolean = false; public decPrivateModes: IDecPrivateModes; - public charsetModes: ICharsetModes = { - charset: undefined, - charsets: [], - glevel: 0 - }; private _onData = new EventEmitter(); public get onData(): IEvent { return this._onData.event; } @@ -45,12 +40,6 @@ export class CoreService implements ICoreService { this.decPrivateModes = clone(DEFAULT_DEC_PRIVATE_MODES); } - public softReset(): void { - this.charsetModes.charset = undefined; - this.charsetModes.charsets = []; - this.charsetModes.glevel = 0; - } - public triggerDataEvent(data: string, wasUserInput: boolean = false): void { // Prevents all events to pty process if stdin is disabled if (this._optionsService.options.disableStdin) { @@ -80,16 +69,4 @@ export class CoreService implements ICoreService { this._logService.debug(`sending binary "${data}"`, () => data.split('').map(e => e.charCodeAt(0))); this._onBinary.fire(data); } - - public setgLevel(g: number): void { - this.charsetModes.glevel = g; - this.charsetModes.charset = this.charsetModes.charsets[g]; - } - - public setgCharset(g: number, charset: ICharset): void { - this.charsetModes.charsets[g] = charset; - if (this.charsetModes.glevel === g) { - this.charsetModes.charset = charset; - } - } } diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 9e47be263..32274f5e9 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -5,7 +5,7 @@ import { IEvent } from 'common/EventEmitter'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; -import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharsetModes, ICharset } from 'common/Types'; +import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset } from 'common/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; export const IBufferService = createDecorator('BufferService'); @@ -65,8 +65,6 @@ export interface ICoreService { isCursorInitialized: boolean; isCursorHidden: boolean; - charsetModes: ICharsetModes; - readonly decPrivateModes: IDecPrivateModes; readonly onData: IEvent; @@ -74,7 +72,6 @@ export interface ICoreService { readonly onBinary: IEvent; reset(): void; - softReset(): void; /** * Triggers the onData event in the public API. @@ -91,6 +88,17 @@ export interface ICoreService { * @param data The data that is being emitted. */ triggerBinaryEvent(data: string): void; +} + +export const ICharsetService = createDecorator('CharsetService'); +export interface ICharsetService { + serviceBrand: any; + + charset: ICharset | undefined; + readonly glevel: number; + readonly charsets: ReadonlyArray; + + reset(): void; /** * Set the G level of the terminal. From bff74b1d7d58ed0dac245bee17bd46d24c416390 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 18 Dec 2019 11:16:23 +1100 Subject: [PATCH 188/268] Move wraparound mode into core service modes --- src/InputHandler.ts | 9 ++++----- src/Terminal.test.ts | 12 ++---------- src/Terminal.ts | 3 +-- src/TestUtils.test.ts | 1 - src/Types.d.ts | 1 - src/common/TestUtils.test.ts | 5 ++++- src/common/Types.d.ts | 1 + src/common/services/CoreService.ts | 3 ++- 8 files changed, 14 insertions(+), 21 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 79c769c7c..750d03e9e 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -385,7 +385,7 @@ export class InputHandler extends Disposable implements IInputHandler { const charset = this._charsetService.charset; const screenReaderMode = this._optionsService.options.screenReaderMode; const cols = this._bufferService.cols; - const wraparoundMode = this._terminal.wraparoundMode; + const wraparoundMode = this._coreService.decPrivateModes.wraparound; const insertMode = this._terminal.insertMode; const curAttr = this._terminal.curAttrData; let bufferRow = buffer.lines.get(buffer.y + buffer.ybase); @@ -1414,7 +1414,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._setCursor(0, 0); break; case 7: - this._terminal.wraparoundMode = true; + this._coreService.decPrivateModes.wraparound = true; break; case 12: // this.cursorBlink = true; @@ -1597,7 +1597,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._setCursor(0, 0); break; case 7: - this._terminal.wraparoundMode = false; + this._coreService.decPrivateModes.wraparound = false; break; case 12: // this.cursorBlink = false; @@ -1965,16 +1965,15 @@ export class InputHandler extends Disposable implements IInputHandler { this._coreService.isCursorHidden = false; this._terminal.insertMode = false; this._terminal.originMode = false; - this._terminal.wraparoundMode = true; // defaults: xterm - true, vt100 - false this._terminal.applicationKeypad = false; // ? if (this._terminal.viewport) { this._terminal.viewport.syncScrollArea(); } - this._coreService.decPrivateModes.applicationCursorKeys = false; this._bufferService.buffer.scrollTop = 0; this._bufferService.buffer.scrollBottom = this._bufferService.rows - 1; this._terminal.curAttrData = DEFAULT_ATTR_DATA.clone(); this._bufferService.buffer.x = this._bufferService.buffer.y = 0; // ? + this._coreService.reset(); this._charsetService.reset(); } diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index b55a516b2..062bddf4b 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -747,7 +747,7 @@ describe('Terminal', () => { const cell = new CellData(); for (let i = 0xDC00; i <= 0xDCFF; ++i) { term.buffer.x = term.cols - 1; - term.wraparoundMode = true; + term.writeSync('a' + high + String.fromCharCode(i)); expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).getChars()).eql('a'); expect(term.buffer.lines.get(1).loadCell(0, cell).getChars()).eql(high + String.fromCharCode(i)); @@ -761,7 +761,7 @@ describe('Terminal', () => { const cell = new CellData(); for (let i = 0xDC00; i <= 0xDCFF; ++i) { term.buffer.x = term.cols - 1; - term.wraparoundMode = false; + term.writeSync('\x1b[?7l'); // Disable wraparound mode const width = wcwidth((0xD800 - 0xD800) * 0x400 + i - 0xDC00 + 0x10000); if (width !== 1) { continue; @@ -812,7 +812,6 @@ describe('Terminal', () => { expect(cell.getWidth()).eql(1); }); it('multiple combined é', () => { - term.wraparoundMode = true; term.writeSync(Array(100).join('e\u0301')); for (let i = 0; i < term.cols; ++i) { term.buffer.lines.get(0).loadCell(i, cell); @@ -826,7 +825,6 @@ describe('Terminal', () => { expect(cell.getWidth()).eql(1); }); it('multiple surrogate with combined', () => { - term.wraparoundMode = true; term.writeSync(Array(100).join('\uD800\uDC00\u0301')); for (let i = 0; i < term.cols; ++i) { term.buffer.lines.get(0).loadCell(i, cell); @@ -855,7 +853,6 @@ describe('Terminal', () => { expect(term.buffer.x).eql(3); }); it('line of ¥ even', () => { - term.wraparoundMode = true; term.writeSync(Array(50).join('¥')); for (let i = 0; i < term.cols; ++i) { term.buffer.lines.get(0).loadCell(i, cell); @@ -875,7 +872,6 @@ describe('Terminal', () => { expect(cell.getWidth()).eql(2); }); it('line of ¥ odd', () => { - term.wraparoundMode = true; term.buffer.x = 1; term.writeSync(Array(50).join('¥')); for (let i = 1; i < term.cols - 1; ++i) { @@ -900,7 +896,6 @@ describe('Terminal', () => { expect(cell.getWidth()).eql(2); }); it('line of ¥ with combining odd', () => { - term.wraparoundMode = true; term.buffer.x = 1; term.writeSync(Array(50).join('¥\u0301')); for (let i = 1; i < term.cols - 1; ++i) { @@ -925,7 +920,6 @@ describe('Terminal', () => { expect(cell.getWidth()).eql(2); }); it('line of ¥ with combining even', () => { - term.wraparoundMode = true; term.writeSync(Array(50).join('¥\u0301')); for (let i = 0; i < term.cols; ++i) { term.buffer.lines.get(0).loadCell(i, cell); @@ -945,7 +939,6 @@ describe('Terminal', () => { expect(cell.getWidth()).eql(2); }); it('line of surrogate fullwidth with combining odd', () => { - term.wraparoundMode = true; term.buffer.x = 1; term.writeSync(Array(50).join('\ud843\ude6d\u0301')); for (let i = 1; i < term.cols - 1; ++i) { @@ -970,7 +963,6 @@ describe('Terminal', () => { expect(cell.getWidth()).eql(2); }); it('line of surrogate fullwidth with combining even', () => { - term.wraparoundMode = true; term.writeSync(Array(50).join('\ud843\ude6d\u0301')); for (let i = 0; i < term.cols; ++i) { term.buffer.lines.get(0).loadCell(i, cell); diff --git a/src/Terminal.ts b/src/Terminal.ts index 2de11f8d0..0a76aa62a 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -115,7 +115,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp public applicationKeypad: boolean; public originMode: boolean; public insertMode: boolean; - public wraparoundMode: boolean; // defaults: xterm - true, vt100 - false public bracketedPasteMode: boolean; // mouse properties @@ -254,7 +253,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this.applicationKeypad = false; this.originMode = false; this.insertMode = false; - this.wraparoundMode = true; // defaults: xterm - true, vt100 - false + // this._coreService.decPrivateModes.wraparound = true; this.bracketedPasteMode = false; // charset diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 8954ae838..b3aa73582 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -205,7 +205,6 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal { applicationCursor: boolean; originMode: boolean; insertMode: boolean; - wraparoundMode: boolean; bracketedPasteMode: boolean; curAttrData = new AttributeData(); savedCols: number; diff --git a/src/Types.d.ts b/src/Types.d.ts index 3a3aef624..989821339 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -28,7 +28,6 @@ export interface IInputHandlingTerminal { applicationKeypad: boolean; originMode: boolean; insertMode: boolean; - wraparoundMode: boolean; bracketedPasteMode: boolean; curAttrData: IAttributeData; savedCols: number; diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 11bb84bce..65a68cbd9 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -57,7 +57,10 @@ export class MockCoreService implements ICoreService { isCursorInitialized: boolean = false; isCursorHidden: boolean = false; isFocused: boolean = false; - decPrivateModes: IDecPrivateModes = {} as any; + decPrivateModes: IDecPrivateModes = { + applicationCursorKeys: false, + wraparound: true + }; onData: IEvent = new EventEmitter().event; onUserInput: IEvent = new EventEmitter().event; onBinary: IEvent = new EventEmitter().event; diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index 35567dee7..674344250 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -152,6 +152,7 @@ export interface IMarker extends IDisposable { export interface IDecPrivateModes { applicationCursorKeys: boolean; + wraparound: boolean; // defaults: xterm - true, vt100 - false } export interface IRowRange { diff --git a/src/common/services/CoreService.ts b/src/common/services/CoreService.ts index 58552f3f7..f5f1c438c 100644 --- a/src/common/services/CoreService.ts +++ b/src/common/services/CoreService.ts @@ -9,7 +9,8 @@ import { IDecPrivateModes, ICharset } from 'common/Types'; import { clone } from 'common/Clone'; const DEFAULT_DEC_PRIVATE_MODES: IDecPrivateModes = Object.freeze({ - applicationCursorKeys: false + applicationCursorKeys: false, + wraparound: true // defaults: xterm - true, vt100 - false }); export class CoreService implements ICoreService { From dada7fe728798503707ebaca295cf025fd0415e6 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 18 Dec 2019 11:20:36 +1100 Subject: [PATCH 189/268] Move origin mode into core service --- src/InputHandler.ts | 9 ++++----- src/Types.d.ts | 1 - src/common/TestUtils.test.ts | 1 + src/common/Types.d.ts | 1 + src/common/services/CoreService.ts | 1 + 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 750d03e9e..681fc7a4b 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -626,7 +626,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ private _restrictCursor(): void { this._bufferService.buffer.x = Math.min(this._bufferService.cols - 1, Math.max(0, this._bufferService.buffer.x)); - this._bufferService.buffer.y = this._terminal.originMode + this._bufferService.buffer.y = this._coreService.decPrivateModes.origin ? Math.min(this._bufferService.buffer.scrollBottom, Math.max(this._bufferService.buffer.scrollTop, this._bufferService.buffer.y)) : Math.min(this._bufferService.rows - 1, Math.max(0, this._bufferService.buffer.y)); this._dirtyRowService.markDirty(this._bufferService.buffer.y); @@ -637,7 +637,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ private _setCursor(x: number, y: number): void { this._dirtyRowService.markDirty(this._bufferService.buffer.y); - if (this._terminal.originMode) { + if (this._coreService.decPrivateModes.origin) { this._bufferService.buffer.x = x; this._bufferService.buffer.y = this._bufferService.buffer.scrollTop + y; } else { @@ -1410,7 +1410,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.reset(); break; case 6: - this._terminal.originMode = true; + this._coreService.decPrivateModes.origin = true; this._setCursor(0, 0); break; case 7: @@ -1593,7 +1593,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.reset(); break; case 6: - this._terminal.originMode = false; + this._coreService.decPrivateModes.origin = false; this._setCursor(0, 0); break; case 7: @@ -1964,7 +1964,6 @@ export class InputHandler extends Disposable implements IInputHandler { public softReset(params: IParams): void { this._coreService.isCursorHidden = false; this._terminal.insertMode = false; - this._terminal.originMode = false; this._terminal.applicationKeypad = false; // ? if (this._terminal.viewport) { this._terminal.viewport.syncScrollArea(); diff --git a/src/Types.d.ts b/src/Types.d.ts index 989821339..095fdf352 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -26,7 +26,6 @@ export interface IInputHandlingTerminal { cols: number; rows: number; applicationKeypad: boolean; - originMode: boolean; insertMode: boolean; bracketedPasteMode: boolean; curAttrData: IAttributeData; diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 65a68cbd9..e556b371a 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -59,6 +59,7 @@ export class MockCoreService implements ICoreService { isFocused: boolean = false; decPrivateModes: IDecPrivateModes = { applicationCursorKeys: false, + origin: false, wraparound: true }; onData: IEvent = new EventEmitter().event; diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index 674344250..a3887e045 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -152,6 +152,7 @@ export interface IMarker extends IDisposable { export interface IDecPrivateModes { applicationCursorKeys: boolean; + origin: boolean; wraparound: boolean; // defaults: xterm - true, vt100 - false } diff --git a/src/common/services/CoreService.ts b/src/common/services/CoreService.ts index f5f1c438c..4a94b0bba 100644 --- a/src/common/services/CoreService.ts +++ b/src/common/services/CoreService.ts @@ -10,6 +10,7 @@ import { clone } from 'common/Clone'; const DEFAULT_DEC_PRIVATE_MODES: IDecPrivateModes = Object.freeze({ applicationCursorKeys: false, + origin: false, wraparound: true // defaults: xterm - true, vt100 - false }); From dc61fae12a7a6a7aeb4b8fd162c36869d4934dc3 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 18 Dec 2019 11:23:57 +1100 Subject: [PATCH 190/268] Move applicationKeypad into core service --- src/InputHandler.ts | 10 +++++----- src/Terminal.ts | 5 ----- src/TestUtils.test.ts | 3 --- src/Types.d.ts | 1 - src/common/TestUtils.test.ts | 1 + src/common/Types.d.ts | 1 + src/common/services/CoreService.ts | 1 + 7 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 681fc7a4b..a154379a3 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -1421,7 +1421,7 @@ export class InputHandler extends Disposable implements IInputHandler { break; case 66: this._logService.debug('Serial port requested application keypad.'); - this._terminal.applicationKeypad = true; + this._coreService.decPrivateModes.applicationKeypad = true; if (this._terminal.viewport) { this._terminal.viewport.syncScrollArea(); } @@ -1604,7 +1604,7 @@ export class InputHandler extends Disposable implements IInputHandler { break; case 66: this._logService.debug('Switching back to normal keypad.'); - this._terminal.applicationKeypad = false; + this._coreService.decPrivateModes.applicationKeypad = false; if (this._terminal.viewport) { this._terminal.viewport.syncScrollArea(); } @@ -1964,7 +1964,7 @@ export class InputHandler extends Disposable implements IInputHandler { public softReset(params: IParams): void { this._coreService.isCursorHidden = false; this._terminal.insertMode = false; - this._terminal.applicationKeypad = false; // ? + this._coreService.decPrivateModes.applicationKeypad = false; // ? if (this._terminal.viewport) { this._terminal.viewport.syncScrollArea(); } @@ -2086,7 +2086,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ public keypadApplicationMode(): void { this._logService.debug('Serial port requested application keypad.'); - this._terminal.applicationKeypad = true; + this._coreService.decPrivateModes.applicationKeypad = true; if (this._terminal.viewport) { this._terminal.viewport.syncScrollArea(); } @@ -2099,7 +2099,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ public keypadNumericMode(): void { this._logService.debug('Switching back to normal keypad.'); - this._terminal.applicationKeypad = false; + this._coreService.decPrivateModes.applicationKeypad = false; if (this._terminal.viewport) { this._terminal.viewport.syncScrollArea(); } diff --git a/src/Terminal.ts b/src/Terminal.ts index 0a76aa62a..6d0bbea23 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -112,8 +112,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp private _soundService: ISoundService; // modes - public applicationKeypad: boolean; - public originMode: boolean; public insertMode: boolean; public bracketedPasteMode: boolean; @@ -250,10 +248,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._customKeyEventHandler = null; // modes - this.applicationKeypad = false; - this.originMode = false; this.insertMode = false; - // this._coreService.decPrivateModes.wraparound = true; this.bracketedPasteMode = false; // charset diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index b3aa73582..cf23b8eb4 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -201,9 +201,6 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal { options: ITerminalOptions = {}; cols: number; rows: number; - applicationKeypad: boolean; - applicationCursor: boolean; - originMode: boolean; insertMode: boolean; bracketedPasteMode: boolean; curAttrData = new AttributeData(); diff --git a/src/Types.d.ts b/src/Types.d.ts index 095fdf352..7742b69c1 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -25,7 +25,6 @@ export interface IInputHandlingTerminal { options: ITerminalOptions; cols: number; rows: number; - applicationKeypad: boolean; insertMode: boolean; bracketedPasteMode: boolean; curAttrData: IAttributeData; diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index e556b371a..581477481 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -59,6 +59,7 @@ export class MockCoreService implements ICoreService { isFocused: boolean = false; decPrivateModes: IDecPrivateModes = { applicationCursorKeys: false, + applicationKeypad: false, origin: false, wraparound: true }; diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index a3887e045..9b8a18b5a 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -152,6 +152,7 @@ export interface IMarker extends IDisposable { export interface IDecPrivateModes { applicationCursorKeys: boolean; + applicationKeypad: boolean; origin: boolean; wraparound: boolean; // defaults: xterm - true, vt100 - false } diff --git a/src/common/services/CoreService.ts b/src/common/services/CoreService.ts index 4a94b0bba..df9161e9c 100644 --- a/src/common/services/CoreService.ts +++ b/src/common/services/CoreService.ts @@ -10,6 +10,7 @@ import { clone } from 'common/Clone'; const DEFAULT_DEC_PRIVATE_MODES: IDecPrivateModes = Object.freeze({ applicationCursorKeys: false, + applicationKeypad: false, origin: false, wraparound: true // defaults: xterm - true, vt100 - false }); From e3e59c35fc492026d2cf1149b815c40dd072077b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 18 Dec 2019 11:28:15 +1100 Subject: [PATCH 191/268] Use optional chaining in InputHandler --- src/InputHandler.ts | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index a154379a3..acf149ed8 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -1422,9 +1422,7 @@ export class InputHandler extends Disposable implements IInputHandler { case 66: this._logService.debug('Serial port requested application keypad.'); this._coreService.decPrivateModes.applicationKeypad = true; - if (this._terminal.viewport) { - this._terminal.viewport.syncScrollArea(); - } + this._terminal.viewport?.syncScrollArea(); break; case 9: // X10 Mouse // no release, no motion, no wheel, no modifiers. @@ -1469,9 +1467,7 @@ export class InputHandler extends Disposable implements IInputHandler { case 1047: // alt screen buffer this._bufferService.buffers.activateAltBuffer(this._terminal.eraseAttrData()); this._onRequestRefreshRows.fire(0, this._bufferService.rows - 1); - if (this._terminal.viewport) { - this._terminal.viewport.syncScrollArea(); - } + this._terminal.viewport?.syncScrollArea(); this._terminal.showCursor(); break; case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste) @@ -1605,9 +1601,7 @@ export class InputHandler extends Disposable implements IInputHandler { case 66: this._logService.debug('Switching back to normal keypad.'); this._coreService.decPrivateModes.applicationKeypad = false; - if (this._terminal.viewport) { - this._terminal.viewport.syncScrollArea(); - } + this._terminal.viewport?.syncScrollArea(); break; case 9: // X10 Mouse case 1000: // vt200 mouse @@ -1643,9 +1637,7 @@ export class InputHandler extends Disposable implements IInputHandler { this.restoreCursor(); } this._onRequestRefreshRows.fire(0, this._bufferService.rows - 1); - if (this._terminal.viewport) { - this._terminal.viewport.syncScrollArea(); - } + this._terminal.viewport?.syncScrollArea(); this._terminal.showCursor(); break; case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste) @@ -1965,9 +1957,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._coreService.isCursorHidden = false; this._terminal.insertMode = false; this._coreService.decPrivateModes.applicationKeypad = false; // ? - if (this._terminal.viewport) { - this._terminal.viewport.syncScrollArea(); - } + this._terminal.viewport?.syncScrollArea(); this._bufferService.buffer.scrollTop = 0; this._bufferService.buffer.scrollBottom = this._bufferService.rows - 1; this._terminal.curAttrData = DEFAULT_ATTR_DATA.clone(); @@ -2087,9 +2077,7 @@ export class InputHandler extends Disposable implements IInputHandler { public keypadApplicationMode(): void { this._logService.debug('Serial port requested application keypad.'); this._coreService.decPrivateModes.applicationKeypad = true; - if (this._terminal.viewport) { - this._terminal.viewport.syncScrollArea(); - } + this._terminal.viewport?.syncScrollArea(); } /** @@ -2100,9 +2088,7 @@ export class InputHandler extends Disposable implements IInputHandler { public keypadNumericMode(): void { this._logService.debug('Switching back to normal keypad.'); this._coreService.decPrivateModes.applicationKeypad = false; - if (this._terminal.viewport) { - this._terminal.viewport.syncScrollArea(); - } + this._terminal.viewport?.syncScrollArea(); } /** From 2bbda38488e4b246977d04850a48f77ae0b51128 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 18 Dec 2019 11:31:08 +1100 Subject: [PATCH 192/268] Remove redundant value reset Done in core service reset --- src/InputHandler.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index acf149ed8..21b194750 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -1956,7 +1956,6 @@ export class InputHandler extends Disposable implements IInputHandler { public softReset(params: IParams): void { this._coreService.isCursorHidden = false; this._terminal.insertMode = false; - this._coreService.decPrivateModes.applicationKeypad = false; // ? this._terminal.viewport?.syncScrollArea(); this._bufferService.buffer.scrollTop = 0; this._bufferService.buffer.scrollBottom = this._bufferService.rows - 1; From 7a1bef7daf2e9f74529a607aa968eda1595ce649 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 18 Dec 2019 11:47:45 +1100 Subject: [PATCH 193/268] More cur/erase attr data to input handler --- src/InputHandler.ts | 68 +++++++++++++++++++++++++------------------ src/Terminal.test.ts | 18 ++++++------ src/Terminal.ts | 18 +----------- src/TestUtils.test.ts | 1 + src/Types.d.ts | 4 +-- 5 files changed, 52 insertions(+), 57 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 21b194750..d01ebc5e4 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -128,6 +128,9 @@ export class InputHandler extends Disposable implements IInputHandler { private _utf8Decoder: Utf8ToUtf32 = new Utf8ToUtf32(); private _workCell: CellData = new CellData(); + private _curAttrData: IAttributeData = DEFAULT_ATTR_DATA.clone(); + private _eraseAttrDataInternal: IAttributeData = DEFAULT_ATTR_DATA.clone(); + private _onRequestRefreshRows = new EventEmitter(); public get onRequestRefreshRows(): IEvent { return this._onRequestRefreshRows.event; } private _onCursorMove = new EventEmitter(); @@ -387,7 +390,7 @@ export class InputHandler extends Disposable implements IInputHandler { const cols = this._bufferService.cols; const wraparoundMode = this._coreService.decPrivateModes.wraparound; const insertMode = this._terminal.insertMode; - const curAttr = this._terminal.curAttrData; + const curAttr = this._curAttrData; let bufferRow = buffer.lines.get(buffer.y + buffer.ybase); this._dirtyRowService.markDirty(buffer.y); @@ -442,7 +445,7 @@ export class InputHandler extends Disposable implements IInputHandler { buffer.y++; if (buffer.y === buffer.scrollBottom + 1) { buffer.y--; - this._terminal.scroll(true); + this._terminal.scroll(this._eraseAttrData(), true); } else { if (buffer.y >= this._bufferService.rows) { buffer.y = this._bufferService.rows - 1; @@ -556,7 +559,7 @@ export class InputHandler extends Disposable implements IInputHandler { buffer.y++; if (buffer.y === buffer.scrollBottom + 1) { buffer.y--; - this._terminal.scroll(); + this._terminal.scroll(this._eraseAttrData()); } else if (buffer.y >= this._bufferService.rows) { buffer.y = this._bufferService.rows - 1; } @@ -848,7 +851,7 @@ export class InputHandler extends Disposable implements IInputHandler { line.replaceCells( start, end, - this._bufferService.buffer.getNullCell(this._terminal.eraseAttrData()) + this._bufferService.buffer.getNullCell(this._eraseAttrData()) ); if (clearWrap) { line.isWrapped = false; @@ -862,7 +865,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ private _resetBufferLine(y: number): void { const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.ybase + y); - line.fill(this._bufferService.buffer.getNullCell(this._terminal.eraseAttrData())); + line.fill(this._bufferService.buffer.getNullCell(this._eraseAttrData())); line.isWrapped = false; } @@ -977,7 +980,7 @@ export class InputHandler extends Disposable implements IInputHandler { // test: echo -e '\e[44m\e[1L\e[0m' // blankLine(true) - xterm/linux behavior buffer.lines.splice(scrollBottomAbsolute - 1, 1); - buffer.lines.splice(row, 0, buffer.getBlankLine(this._terminal.eraseAttrData())); + buffer.lines.splice(row, 0, buffer.getBlankLine(this._eraseAttrData())); } this._dirtyRowService.markRangeDirty(buffer.y, buffer.scrollBottom); @@ -1008,7 +1011,7 @@ export class InputHandler extends Disposable implements IInputHandler { // test: echo -e '\e[44m\e[1M\e[0m' // blankLine(true) - xterm/linux behavior buffer.lines.splice(row, 1); - buffer.lines.splice(j, 0, buffer.getBlankLine(this._terminal.eraseAttrData())); + buffer.lines.splice(j, 0, buffer.getBlankLine(this._eraseAttrData())); } this._dirtyRowService.markRangeDirty(buffer.y, buffer.scrollBottom); @@ -1026,7 +1029,7 @@ export class InputHandler extends Disposable implements IInputHandler { line.insertCells( this._bufferService.buffer.x, params.params[0] || 1, - this._bufferService.buffer.getNullCell(this._terminal.eraseAttrData()) + this._bufferService.buffer.getNullCell(this._eraseAttrData()) ); this._dirtyRowService.markDirty(this._bufferService.buffer.y); } @@ -1043,7 +1046,7 @@ export class InputHandler extends Disposable implements IInputHandler { line.deleteCells( this._bufferService.buffer.x, params.params[0] || 1, - this._bufferService.buffer.getNullCell(this._terminal.eraseAttrData()) + this._bufferService.buffer.getNullCell(this._eraseAttrData()) ); this._dirtyRowService.markDirty(this._bufferService.buffer.y); } @@ -1060,7 +1063,7 @@ export class InputHandler extends Disposable implements IInputHandler { while (param--) { buffer.lines.splice(buffer.ybase + buffer.scrollTop, 1); - buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 0, buffer.getBlankLine(this._terminal.eraseAttrData())); + buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 0, buffer.getBlankLine(this._eraseAttrData())); } this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom); } @@ -1103,7 +1106,7 @@ export class InputHandler extends Disposable implements IInputHandler { const param = params.params[0] || 1; for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) { const line = buffer.lines.get(buffer.ybase + y); - line.deleteCells(0, param, buffer.getNullCell(this._terminal.eraseAttrData())); + line.deleteCells(0, param, buffer.getNullCell(this._eraseAttrData())); line.isWrapped = false; } this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom); @@ -1131,7 +1134,7 @@ export class InputHandler extends Disposable implements IInputHandler { const param = params.params[0] || 1; for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) { const line = buffer.lines.get(buffer.ybase + y); - line.insertCells(0, param, buffer.getNullCell(this._terminal.eraseAttrData())); + line.insertCells(0, param, buffer.getNullCell(this._eraseAttrData())); line.isWrapped = false; } this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom); @@ -1149,7 +1152,7 @@ export class InputHandler extends Disposable implements IInputHandler { const param = params.params[0] || 1; for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) { const line = this._bufferService.buffer.lines.get(buffer.ybase + y); - line.insertCells(buffer.x, param, buffer.getNullCell(this._terminal.eraseAttrData())); + line.insertCells(buffer.x, param, buffer.getNullCell(this._eraseAttrData())); line.isWrapped = false; } this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom); @@ -1167,7 +1170,7 @@ export class InputHandler extends Disposable implements IInputHandler { const param = params.params[0] || 1; for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) { const line = buffer.lines.get(buffer.ybase + y); - line.deleteCells(buffer.x, param, buffer.getNullCell(this._terminal.eraseAttrData())); + line.deleteCells(buffer.x, param, buffer.getNullCell(this._eraseAttrData())); line.isWrapped = false; } this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom); @@ -1184,7 +1187,7 @@ export class InputHandler extends Disposable implements IInputHandler { line.replaceCells( this._bufferService.buffer.x, this._bufferService.buffer.x + (params.params[0] || 1), - this._bufferService.buffer.getNullCell(this._terminal.eraseAttrData()) + this._bufferService.buffer.getNullCell(this._eraseAttrData()) ); this._dirtyRowService.markDirty(this._bufferService.buffer.y); } @@ -1465,7 +1468,7 @@ export class InputHandler extends Disposable implements IInputHandler { // FALL-THROUGH case 47: // alt screen buffer case 1047: // alt screen buffer - this._bufferService.buffers.activateAltBuffer(this._terminal.eraseAttrData()); + this._bufferService.buffers.activateAltBuffer(this._eraseAttrData()); this._onRequestRefreshRows.fire(0, this._bufferService.rows - 1); this._terminal.viewport?.syncScrollArea(); this._terminal.showCursor(); @@ -1787,14 +1790,14 @@ export class InputHandler extends Disposable implements IInputHandler { public charAttributes(params: IParams): void { // Optimize a single SGR0. if (params.length === 1 && params.params[0] === 0) { - this._terminal.curAttrData.fg = DEFAULT_ATTR_DATA.fg; - this._terminal.curAttrData.bg = DEFAULT_ATTR_DATA.bg; + this._curAttrData.fg = DEFAULT_ATTR_DATA.fg; + this._curAttrData.bg = DEFAULT_ATTR_DATA.bg; return; } const l = params.length; let p; - const attr = this._terminal.curAttrData; + const attr = this._curAttrData; for (let i = 0; i < l; i++) { p = params.params[i]; @@ -1959,7 +1962,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.viewport?.syncScrollArea(); this._bufferService.buffer.scrollTop = 0; this._bufferService.buffer.scrollBottom = this._bufferService.rows - 1; - this._terminal.curAttrData = DEFAULT_ATTR_DATA.clone(); + this._curAttrData = DEFAULT_ATTR_DATA.clone(); this._bufferService.buffer.x = this._bufferService.buffer.y = 0; // ? this._coreService.reset(); this._charsetService.reset(); @@ -2024,8 +2027,8 @@ export class InputHandler extends Disposable implements IInputHandler { public saveCursor(params?: IParams): void { this._bufferService.buffer.savedX = this._bufferService.buffer.x; this._bufferService.buffer.savedY = this._bufferService.buffer.ybase + this._bufferService.buffer.y; - this._bufferService.buffer.savedCurAttrData.fg = this._terminal.curAttrData.fg; - this._bufferService.buffer.savedCurAttrData.bg = this._terminal.curAttrData.bg; + this._bufferService.buffer.savedCurAttrData.fg = this._curAttrData.fg; + this._bufferService.buffer.savedCurAttrData.bg = this._curAttrData.bg; this._bufferService.buffer.savedCharset = this._charsetService.charset; } @@ -2038,8 +2041,8 @@ export class InputHandler extends Disposable implements IInputHandler { public restoreCursor(params?: IParams): void { this._bufferService.buffer.x = this._bufferService.buffer.savedX || 0; this._bufferService.buffer.y = Math.max(this._bufferService.buffer.savedY - this._bufferService.buffer.ybase, 0); - this._terminal.curAttrData.fg = this._bufferService.buffer.savedCurAttrData.fg; - this._terminal.curAttrData.bg = this._bufferService.buffer.savedCurAttrData.bg; + this._curAttrData.fg = this._bufferService.buffer.savedCurAttrData.fg; + this._curAttrData.bg = this._bufferService.buffer.savedCurAttrData.bg; this._charsetService.charset = (this as any)._savedCharset; if (this._bufferService.buffer.savedCharset) { this._charsetService.charset = this._bufferService.buffer.savedCharset; @@ -2141,7 +2144,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._bufferService.buffer.y++; if (buffer.y === buffer.scrollBottom + 1) { buffer.y--; - this._terminal.scroll(); + this._terminal.scroll(this._eraseAttrData()); } else if (buffer.y >= this._bufferService.rows) { buffer.y = this._bufferService.rows - 1; } @@ -2175,7 +2178,7 @@ export class InputHandler extends Disposable implements IInputHandler { // blankLine(true) is xterm/linux behavior const scrollRegionHeight = buffer.scrollBottom - buffer.scrollTop; buffer.lines.shiftElements(buffer.y + buffer.ybase, scrollRegionHeight, 1); - buffer.lines.set(buffer.y + buffer.ybase, buffer.getBlankLine(this._terminal.eraseAttrData())); + buffer.lines.set(buffer.y + buffer.ybase, buffer.getBlankLine(this._eraseAttrData())); this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom); } else { buffer.y--; @@ -2193,6 +2196,15 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.reset(); // TODO: save to move from terminal? } + /** + * back_color_erase feature for xterm. + */ + private _eraseAttrData(): IAttributeData { + this._eraseAttrDataInternal.bg &= ~(Attributes.CM_MASK | 0xFFFFFF); + this._eraseAttrDataInternal.bg |= this._curAttrData.bg & ~0xFC000000; + return this._eraseAttrDataInternal; + } + /** * ESC n * ESC o @@ -2219,8 +2231,8 @@ export class InputHandler extends Disposable implements IInputHandler { // prepare cell data const cell = new CellData(); cell.content = 1 << Content.WIDTH_SHIFT | 'E'.charCodeAt(0); - cell.fg = this._terminal.curAttrData.fg; - cell.bg = this._terminal.curAttrData.bg; + cell.fg = this._curAttrData.fg; + cell.bg = this._curAttrData.bg; const buffer = this._bufferService.buffer; diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index 062bddf4b..90522bf4f 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -112,7 +112,7 @@ describe('Terminal', () => { assert.equal(typeof e, 'number'); done(); }); - term.scroll(); + term.scroll(DEFAULT_ATTR_DATA.clone()); }); it('should fire the onTitleChange event', (done) => { term.onTitleChange(e => { @@ -397,7 +397,7 @@ describe('Terminal', () => { term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)])); term.buffer.lines.get(INIT_ROWS - 1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)])); term.buffer.y = INIT_ROWS - 1; // Move cursor to last line - term.scroll(); + term.scroll(DEFAULT_ATTR_DATA.clone()); assert.equal(term.buffer.lines.length, INIT_ROWS + 1); assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a'); assert.equal(term.buffer.lines.get(INIT_ROWS - 1).loadCell(0, new CellData()).getChars(), 'b'); @@ -410,7 +410,7 @@ describe('Terminal', () => { term.buffer.lines.get(2).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)])); term.buffer.y = INIT_ROWS - 1; // Move cursor to last line term.buffer.scrollTop = 1; - term.scroll(); + term.scroll(DEFAULT_ATTR_DATA.clone()); assert.equal(term.buffer.lines.length, INIT_ROWS); assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a'); assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'c'); @@ -424,7 +424,7 @@ describe('Terminal', () => { term.buffer.lines.get(4).setCell(0, CellData.fromCharData([0, 'e', 0, 'e'.charCodeAt(0)])); term.buffer.y = 3; term.buffer.scrollBottom = 3; - term.scroll(); + term.scroll(DEFAULT_ATTR_DATA.clone()); assert.equal(term.buffer.lines.length, INIT_ROWS + 1); assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a', '\'a\' should be pushed to the scrollback'); assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'b'); @@ -443,7 +443,7 @@ describe('Terminal', () => { term.buffer.y = INIT_ROWS - 1; // Move cursor to last line term.buffer.scrollTop = 1; term.buffer.scrollBottom = 3; - term.scroll(); + term.scroll(DEFAULT_ATTR_DATA.clone()); assert.equal(term.buffer.lines.length, INIT_ROWS); assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a'); assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'c', '\'b\' should be removed from the buffer'); @@ -465,7 +465,7 @@ describe('Terminal', () => { term.buffer.lines.get(INIT_ROWS - 1).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)])); term.buffer.y = INIT_ROWS - 1; // Move cursor to last line assert.equal(term.buffer.lines.length, INIT_ROWS); - term.scroll(); + term.scroll(DEFAULT_ATTR_DATA.clone()); assert.equal(term.buffer.lines.length, INIT_ROWS); // 'a' gets pushed out of buffer assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'b'); @@ -480,7 +480,7 @@ describe('Terminal', () => { term.buffer.lines.get(2).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)])); term.buffer.y = INIT_ROWS - 1; // Move cursor to last line term.buffer.scrollTop = 1; - term.scroll(); + term.scroll(DEFAULT_ATTR_DATA.clone()); assert.equal(term.buffer.lines.length, INIT_ROWS); assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a'); assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'c'); @@ -494,7 +494,7 @@ describe('Terminal', () => { term.buffer.lines.get(4).setCell(0, CellData.fromCharData([0, 'e', 0, 'e'.charCodeAt(0)])); term.buffer.y = 3; term.buffer.scrollBottom = 3; - term.scroll(); + term.scroll(DEFAULT_ATTR_DATA.clone()); assert.equal(term.buffer.lines.length, INIT_ROWS); assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'b'); assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'c'); @@ -512,7 +512,7 @@ describe('Terminal', () => { term.buffer.y = INIT_ROWS - 1; // Move cursor to last line term.buffer.scrollTop = 1; term.buffer.scrollBottom = 3; - term.scroll(); + term.scroll(DEFAULT_ATTR_DATA.clone()); assert.equal(term.buffer.lines.length, INIT_ROWS); assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a'); assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'c', '\'b\' should be removed from the buffer'); diff --git a/src/Terminal.ts b/src/Terminal.ts index 6d0bbea23..21d8ee601 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -122,9 +122,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // misc public savedCols: number; - public curAttrData: IAttributeData; - private _eraseAttrData: IAttributeData; - public params: (string | number)[]; public currentParam: string | number; @@ -254,9 +251,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // charset this._charsetService.reset(); - this.curAttrData = DEFAULT_ATTR_DATA.clone(); - this._eraseAttrData = DEFAULT_ATTR_DATA.clone(); - this.params = []; this.currentParam = 0; @@ -293,15 +287,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp return this._bufferService.buffers; } - /** - * back_color_erase feature for xterm. - */ - public eraseAttrData(): IAttributeData { - this._eraseAttrData.bg &= ~(Attributes.CM_MASK | 0xFFFFFF); - this._eraseAttrData.bg |= this.curAttrData.bg & ~0xFC000000; - return this._eraseAttrData; - } - /** * Focus the terminal. Delegates focus handling to the terminal's DOM element. */ @@ -946,10 +931,9 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp * Scroll the terminal down 1 row, creating a blank line. * @param isWrapped Whether the new line is wrapped from the previous line. */ - public scroll(isWrapped: boolean = false): void { + public scroll(eraseAttr: IAttributeData, isWrapped: boolean = false): void { let newLine: IBufferLine; newLine = this._blankLine; - const eraseAttr = this.eraseAttrData(); if (!newLine || newLine.length !== this.cols || newLine.getFg(0) !== eraseAttr.fg || newLine.getBg(0) !== eraseAttr.bg) { newLine = this.buffer.getBlankLine(eraseAttr, isWrapped); this._blankLine = newLine; diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index cf23b8eb4..9338fbc09 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -19,6 +19,7 @@ import { IParams, IFunctionIdentifier } from 'common/parser/Types'; import { ISelectionService } from 'browser/services/Services'; export class TestTerminal extends Terminal { + get curAttrData(): IAttributeData { return (this as any)._inputHandler._curAttrData; } keyDown(ev: any): boolean { return this._keyDown(ev); } keyPress(ev: any): boolean { return this._keyPress(ev); } } diff --git a/src/Types.d.ts b/src/Types.d.ts index 7742b69c1..e5021f880 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -27,7 +27,6 @@ export interface IInputHandlingTerminal { rows: number; insertMode: boolean; bracketedPasteMode: boolean; - curAttrData: IAttributeData; savedCols: number; mouseEvents: CoreMouseEventType; sendFocus: boolean; @@ -41,8 +40,7 @@ export interface IInputHandlingTerminal { bell(): void; focus(): void; - scroll(isWrapped?: boolean): void; - eraseAttrData(): IAttributeData; + scroll(eraseAttr: IAttributeData, isWrapped?: boolean): void; is(term: string): boolean; resize(x: number, y: number): void; reset(): void; From 33db0203ddadd8e37fc7aaa6fce02f261bb21de5 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 18 Dec 2019 11:55:25 +1100 Subject: [PATCH 194/268] Remove terminal.reset usage from input handler --- src/InputHandler.test.ts | 8 ++++---- src/InputHandler.ts | 8 +++++--- src/Terminal.ts | 2 +- src/TestUtils.test.ts | 4 +--- src/Types.d.ts | 1 - 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index fa9ef67c9..fa42312ca 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -36,26 +36,26 @@ function getLines(term: TestTerminal, limit: number = term.rows): string[] { describe('InputHandler', () => { describe('save and restore cursor', () => { const terminal = new MockInputHandlingTerminal(); - terminal.curAttrData.fg = 3; const bufferService = new MockBufferService(80, 30); bufferService.buffer.x = 1; bufferService.buffer.y = 2; bufferService.buffer.ybase = 0; const inputHandler = new InputHandler(terminal, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); + (inputHandler as any)._curAttrData.fg = 3; // Save cursor position inputHandler.saveCursor(); assert.equal(bufferService.buffer.x, 1); assert.equal(bufferService.buffer.y, 2); - assert.equal(terminal.curAttrData.fg, 3); + assert.equal((inputHandler as any)._curAttrData.fg, 3); // Change cursor position bufferService.buffer.x = 10; bufferService.buffer.y = 20; - terminal.curAttrData.fg = 30; + (inputHandler as any)._curAttrData.fg = 30; // Restore cursor position inputHandler.restoreCursor(); assert.equal(bufferService.buffer.x, 1); assert.equal(bufferService.buffer.y, 2); - assert.equal(terminal.curAttrData.fg, 3); + assert.equal((inputHandler as any)._curAttrData.fg, 3); }); describe('setCursorStyle', () => { it('should call Terminal.setOption with correct params', () => { diff --git a/src/InputHandler.ts b/src/InputHandler.ts index d01ebc5e4..ecc09a7d3 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -133,6 +133,8 @@ export class InputHandler extends Disposable implements IInputHandler { private _onRequestRefreshRows = new EventEmitter(); public get onRequestRefreshRows(): IEvent { return this._onRequestRefreshRows.event; } + private _onRequestReset = new EventEmitter(); + public get onRequestReset(): IEvent { return this._onRequestReset.event; } private _onCursorMove = new EventEmitter(); public get onCursorMove(): IEvent { return this._onCursorMove.event; } private _onLineFeed = new EventEmitter(); @@ -1410,7 +1412,7 @@ export class InputHandler extends Disposable implements IInputHandler { // TODO: move DECCOLM into compat addon this._terminal.savedCols = this._bufferService.cols; this._terminal.resize(132, this._bufferService.rows); - this._terminal.reset(); + this._onRequestReset.fire(); break; case 6: this._coreService.decPrivateModes.origin = true; @@ -1589,7 +1591,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.resize(this._terminal.savedCols, this._bufferService.rows); } delete this._terminal.savedCols; - this._terminal.reset(); + this._onRequestReset.fire(); break; case 6: this._coreService.decPrivateModes.origin = false; @@ -2193,7 +2195,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ public reset(): void { this._parser.reset(); - this._terminal.reset(); // TODO: save to move from terminal? + this._onRequestReset.fire(); } /** diff --git a/src/Terminal.ts b/src/Terminal.ts index 21d8ee601..91684cfd2 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -53,7 +53,6 @@ import { CharSizeService } from 'browser/services/CharSizeService'; import { BufferService, MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/BufferService'; import { Disposable } from 'common/Lifecycle'; import { IBufferSet, IBuffer } from 'common/buffer/Types'; -import { Attributes } from 'common/buffer/Constants'; import { MouseService } from 'browser/services/MouseService'; import { IParams, IFunctionIdentifier } from 'common/parser/Types'; import { CoreService } from 'common/services/CoreService'; @@ -259,6 +258,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // Register input handler and refire/handle events this._inputHandler = new InputHandler(this, this._bufferService, this._charsetService, this._coreService, this._dirtyRowService, this._logService, this.optionsService, this._coreMouseService); this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end)); + this._inputHandler.onRequestReset(() => this.reset()); this._inputHandler.onCursorMove(() => this._onCursorMove.fire()); this._inputHandler.onLineFeed(() => this._onLineFeed.fire()); this.register(this._inputHandler); diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 9338fbc09..d425a3587 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -204,7 +204,6 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal { rows: number; insertMode: boolean; bracketedPasteMode: boolean; - curAttrData = new AttributeData(); savedCols: number; x10Mouse: boolean; vt200Mouse: boolean; @@ -226,11 +225,10 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal { bell(): void { throw new Error('Method not implemented.'); } - updateRange(y: number): void { throw new Error('Method not implemented.'); } - scroll(isWrapped?: boolean): void { + scroll(eraseAttr: IAttributeData, isWrapped?: boolean): void { throw new Error('Method not implemented.'); } nextStop(x?: number): number { diff --git a/src/Types.d.ts b/src/Types.d.ts index e5021f880..bf3f32212 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -43,7 +43,6 @@ export interface IInputHandlingTerminal { scroll(eraseAttr: IAttributeData, isWrapped?: boolean): void; is(term: string): boolean; resize(x: number, y: number): void; - reset(): void; showCursor(): void; handleTitle(title: string): void; } From 5d351ec8c0705fbec62ffb8981d4c139b427efb5 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 18 Dec 2019 11:57:16 +1100 Subject: [PATCH 195/268] Isolate cast in TestInputHandler --- src/InputHandler.test.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index fa42312ca..ec3699cef 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -7,7 +7,7 @@ import { assert, expect } from 'chai'; import { InputHandler } from './InputHandler'; import { MockInputHandlingTerminal, TestTerminal } from './TestUtils.test'; import { Terminal } from './Terminal'; -import { IBufferLine } from 'common/Types'; +import { IBufferLine, IAttributeData } from 'common/Types'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { CellData } from 'common/buffer/CellData'; import { Attributes } from 'common/buffer/Constants'; @@ -33,6 +33,10 @@ function getLines(term: TestTerminal, limit: number = term.rows): string[] { return res; } +class TestInputHandler extends InputHandler { + get curAttrData(): IAttributeData { return (this as any)._curAttrData; } +} + describe('InputHandler', () => { describe('save and restore cursor', () => { const terminal = new MockInputHandlingTerminal(); @@ -40,22 +44,22 @@ describe('InputHandler', () => { bufferService.buffer.x = 1; bufferService.buffer.y = 2; bufferService.buffer.ybase = 0; - const inputHandler = new InputHandler(terminal, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); - (inputHandler as any)._curAttrData.fg = 3; + const inputHandler = new TestInputHandler(terminal, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); + inputHandler.curAttrData.fg = 3; // Save cursor position inputHandler.saveCursor(); assert.equal(bufferService.buffer.x, 1); assert.equal(bufferService.buffer.y, 2); - assert.equal((inputHandler as any)._curAttrData.fg, 3); + assert.equal(inputHandler.curAttrData.fg, 3); // Change cursor position bufferService.buffer.x = 10; bufferService.buffer.y = 20; - (inputHandler as any)._curAttrData.fg = 30; + inputHandler.curAttrData.fg = 30; // Restore cursor position inputHandler.restoreCursor(); assert.equal(bufferService.buffer.x, 1); assert.equal(bufferService.buffer.y, 2); - assert.equal((inputHandler as any)._curAttrData.fg, 3); + assert.equal(inputHandler.curAttrData.fg, 3); }); describe('setCursorStyle', () => { it('should call Terminal.setOption with correct params', () => { From 8e996ae571b9750eb04733ac46f4cfb8d3d26362 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 18 Dec 2019 12:00:38 +1100 Subject: [PATCH 196/268] Remove terminal.bell usage --- src/InputHandler.ts | 6 ++++-- src/Terminal.ts | 1 + src/Types.d.ts | 1 - 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index ecc09a7d3..c7eaeb26b 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -135,6 +135,8 @@ export class InputHandler extends Disposable implements IInputHandler { public get onRequestRefreshRows(): IEvent { return this._onRequestRefreshRows.event; } private _onRequestReset = new EventEmitter(); public get onRequestReset(): IEvent { return this._onRequestReset.event; } + private _onRequestBell = new EventEmitter(); + public get onRequestBell(): IEvent { return this._onRequestBell.event; } private _onCursorMove = new EventEmitter(); public get onCursorMove(): IEvent { return this._onCursorMove.event; } private _onLineFeed = new EventEmitter(); @@ -143,7 +145,7 @@ export class InputHandler extends Disposable implements IInputHandler { public get onScroll(): IEvent { return this._onScroll.event; } constructor( - protected _terminal: IInputHandlingTerminal, + private _terminal: IInputHandlingTerminal, private readonly _bufferService: IBufferService, private readonly _charsetService: ICharsetService, private readonly _coreService: ICoreService, @@ -543,7 +545,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Bell (Ctrl-G). */ public bell(): void { - this._terminal.bell(); + this._onRequestBell.fire(); } /** diff --git a/src/Terminal.ts b/src/Terminal.ts index 91684cfd2..ffecc6f2d 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -257,6 +257,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // Register input handler and refire/handle events this._inputHandler = new InputHandler(this, this._bufferService, this._charsetService, this._coreService, this._dirtyRowService, this._logService, this.optionsService, this._coreMouseService); + this._inputHandler.onRequestBell(() => this.bell()); this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end)); this._inputHandler.onRequestReset(() => this.reset()); this._inputHandler.onCursorMove(() => this._onCursorMove.fire()); diff --git a/src/Types.d.ts b/src/Types.d.ts index bf3f32212..e19a7d53c 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -38,7 +38,6 @@ export interface IInputHandlingTerminal { onA11yCharEmitter: IEventEmitter; onA11yTabEmitter: IEventEmitter; - bell(): void; focus(): void; scroll(eraseAttr: IAttributeData, isWrapped?: boolean): void; is(term: string): boolean; From 914861503bbca114f0d4e424a4c40d58acaf6a70 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 18 Dec 2019 12:06:41 +1100 Subject: [PATCH 197/268] Remove a bunch of unused interfaces --- src/TestUtils.test.ts | 70 +------------------------------------------ src/Types.d.ts | 6 ---- 2 files changed, 1 insertion(+), 75 deletions(-) diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index d425a3587..57a9fe785 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -6,7 +6,7 @@ import { IRenderer, IRenderDimensions, CharacterJoinerHandler, IRequestRefreshRowsEvent } from 'browser/renderer/Types'; import { IInputHandlingTerminal, ICompositionHelper, ITerminal, IBrowser, ITerminalOptions } from './Types'; import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types'; -import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset, CoreMouseEventType } from 'common/Types'; +import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset } from 'common/Types'; import { Buffer } from 'common/buffer/Buffer'; import * as Browser from 'common/Platform'; import { IDisposable, IMarker, IEvent, ISelectionPosition } from 'xterm'; @@ -198,93 +198,25 @@ export class MockTerminal implements ITerminal { export class MockInputHandlingTerminal implements IInputHandlingTerminal { onA11yCharEmitter: EventEmitter; onA11yTabEmitter: EventEmitter; - element: HTMLElement; - options: ITerminalOptions = {}; - cols: number; - rows: number; insertMode: boolean; bracketedPasteMode: boolean; savedCols: number; - x10Mouse: boolean; - vt200Mouse: boolean; - normalMouse: boolean; - mouseEvents: CoreMouseEventType; sendFocus: boolean; - utfMouse: boolean; - sgrMouse: boolean; - urxvtMouse: boolean; - cursorHidden: boolean; buffers: IBufferSet; buffer: IBuffer = new MockBuffer(); viewport: IViewport; - selectionService: ISelectionService; - focus(): void { - throw new Error('Method not implemented.'); - } - convertEol: boolean; - bell(): void { - throw new Error('Method not implemented.'); - } - updateRange(y: number): void { - throw new Error('Method not implemented.'); - } scroll(eraseAttr: IAttributeData, isWrapped?: boolean): void { throw new Error('Method not implemented.'); } - nextStop(x?: number): number { - throw new Error('Method not implemented.'); - } - eraseAttrData(): IAttributeData { - throw new Error('Method not implemented.'); - } - eraseRight(x: number, y: number): void { - throw new Error('Method not implemented.'); - } - eraseLine(y: number): void { - throw new Error('Method not implemented.'); - } - eraseLeft(x: number, y: number): void { - throw new Error('Method not implemented.'); - } - prevStop(x?: number): number { - throw new Error('Method not implemented.'); - } is(term: string): boolean { throw new Error('Method not implemented.'); } resize(x: number, y: number): void { throw new Error('Method not implemented.'); } - log(text: string, data?: any): void { - throw new Error('Method not implemented.'); - } - reset(): void { - throw new Error('Method not implemented.'); - } showCursor(): void { throw new Error('Method not implemented.'); } - matchColor(r1: number, g1: number, b1: number): number { - throw new Error('Method not implemented.'); - } - error(text: string, data?: any): void { - throw new Error('Method not implemented.'); - } - setOption(key: string, value: any): void { - (this.options)[key] = value; - } - on(type: string, listener: XtermListener): void { - throw new Error('Method not implemented.'); - } - off(type: string, listener: XtermListener): void { - throw new Error('Method not implemented.'); - } - emit(type: string, data?: any): void { - throw new Error('Method not implemented.'); - } - addDisposableListener(type: string, handler: XtermListener): IDisposable { - throw new Error('Method not implemented.'); - } handler(data: string): void { throw new Error('Method not implemented.'); } diff --git a/src/Types.d.ts b/src/Types.d.ts index e19a7d53c..761f556f2 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -21,14 +21,9 @@ export type LineData = CharData[]; * InputHandler cleanly from the ITerminal interface. */ export interface IInputHandlingTerminal { - element: HTMLElement; - options: ITerminalOptions; - cols: number; - rows: number; insertMode: boolean; bracketedPasteMode: boolean; savedCols: number; - mouseEvents: CoreMouseEventType; sendFocus: boolean; buffers: IBufferSet; @@ -38,7 +33,6 @@ export interface IInputHandlingTerminal { onA11yCharEmitter: IEventEmitter; onA11yTabEmitter: IEventEmitter; - focus(): void; scroll(eraseAttr: IAttributeData, isWrapped?: boolean): void; is(term: string): boolean; resize(x: number, y: number): void; From 9e1f607bd93d2d2904c9bbad382ccba5100a00a6 Mon Sep 17 00:00:00 2001 From: javacs3 Date: Mon, 16 Dec 2019 21:49:12 +0800 Subject: [PATCH 198/268] 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 2388aed90..171b66e4e 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 199/268] 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 1364b7d2e..91ea1d26c 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 200/268] 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 91ea1d26c..468b96554 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 201/268] 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 468b96554..160f64595 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 22776d0ad..5854cef6e 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 6bf2aebfd..243266d06 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 202/268] 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 160f64595..b9c5ed4f9 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 072779725407e7f105cad34bb5ad5892436a20ba Mon Sep 17 00:00:00 2001 From: Phillip Campbell <15082+phillc@users.noreply.github.com> Date: Wed, 18 Dec 2019 09:05:29 -0500 Subject: [PATCH 203/268] Add Linode to real world uses --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 70ff7891d..f4d562f5b 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,7 @@ Xterm.js is used in several world-class applications to provide great terminal e - [**tty-share**](https://tty-share.com): Extremely simple terminal sharing over the Internet. - [**Ten Hands**](https://github.com/saisandeepvaddi/ten-hands): One place to run your command-line tasks. - [**WebAssembly.sh**](https://webassembly.sh): A WebAssembly WASI browser terminal +- [**Linode**](https://linode.com): Linode uses xterm.js to provide users a web console for their Linode instances. [And much more...](https://github.com/xtermjs/xterm.js/network/dependents) From 34168bd22393a1df09bd1a9097059f963f17e76c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 19 Dec 2019 15:42:23 +1100 Subject: [PATCH 204/268] Fix InputHandler resetting --- src/InputHandler.ts | 9 +++++++-- src/Terminal.ts | 22 ++++++++++++---------- src/Types.d.ts | 2 +- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index c7eaeb26b..f512ac1cc 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -306,7 +306,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._parser.setEscHandler({final: 'M'}, () => this.reverseIndex()); this._parser.setEscHandler({final: '='}, () => this.keypadApplicationMode()); this._parser.setEscHandler({final: '>'}, () => this.keypadNumericMode()); - this._parser.setEscHandler({final: 'c'}, () => this.reset()); + this._parser.setEscHandler({final: 'c'}, () => this.fullReset()); this._parser.setEscHandler({final: 'n'}, () => this.setgLevel(2)); this._parser.setEscHandler({final: 'o'}, () => this.setgLevel(3)); this._parser.setEscHandler({final: '|'}, () => this.setgLevel(3)); @@ -2195,9 +2195,14 @@ export class InputHandler extends Disposable implements IInputHandler { * DEC mnemonic: RIS (https://vt100.net/docs/vt510-rm/RIS.html) * Reset to initial state. */ + public fullReset(): void { + this._onRequestReset.fire(); + } + public reset(): void { this._parser.reset(); - this._onRequestReset.fire(); + this._curAttrData = DEFAULT_ATTR_DATA.clone(); + this._eraseAttrDataInternal = DEFAULT_ATTR_DATA.clone(); } /** diff --git a/src/Terminal.ts b/src/Terminal.ts index ffecc6f2d..2364ffd12 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -255,14 +255,18 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._userScrolling = false; - // Register input handler and refire/handle events - this._inputHandler = new InputHandler(this, this._bufferService, this._charsetService, this._coreService, this._dirtyRowService, this._logService, this.optionsService, this._coreMouseService); - this._inputHandler.onRequestBell(() => this.bell()); - this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end)); - this._inputHandler.onRequestReset(() => this.reset()); - this._inputHandler.onCursorMove(() => this._onCursorMove.fire()); - this._inputHandler.onLineFeed(() => this._onLineFeed.fire()); - this.register(this._inputHandler); + if (this._inputHandler) { + this._inputHandler.reset(); + } else { + // Register input handler and refire/handle events + this._inputHandler = new InputHandler(this, this._bufferService, this._charsetService, this._coreService, this._dirtyRowService, this._logService, this.optionsService, this._coreMouseService); + this._inputHandler.onRequestBell(() => this.bell()); + this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end)); + this._inputHandler.onRequestReset(() => this.reset()); + this._inputHandler.onCursorMove(() => this._onCursorMove.fire()); + this._inputHandler.onLineFeed(() => this._onLineFeed.fire()); + this.register(this._inputHandler); + } this.linkifier = this.linkifier || new Linkifier(this._bufferService, this._logService); @@ -1464,7 +1468,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this.options.rows = this.rows; this.options.cols = this.cols; const customKeyEventHandler = this._customKeyEventHandler; - const inputHandler = this._inputHandler; const userScrolling = this._userScrolling; this._setup(); @@ -1475,7 +1478,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // reattach this._customKeyEventHandler = customKeyEventHandler; - this._inputHandler = inputHandler; this._userScrolling = userScrolling; // do a full screen refresh diff --git a/src/Types.d.ts b/src/Types.d.ts index 761f556f2..ebc5d338c 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -121,7 +121,7 @@ export interface IInputHandler { /** ESC D */ index(): void; /** ESC H */ tabSet(): void; /** ESC M */ reverseIndex(): void; - /** ESC c */ reset(): void; + /** ESC c */ fullReset(): void; /** ESC n ESC o ESC | From d045d39f8833ac16005d4b7dd95ea1795ee9f0c1 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 19 Dec 2019 15:58:15 +1100 Subject: [PATCH 205/268] Move charset service reset out of _setup --- src/Terminal.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 2364ffd12..b89efdb65 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -247,9 +247,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this.insertMode = false; this.bracketedPasteMode = false; - // charset - this._charsetService.reset(); - this.params = []; this.currentParam = 0; @@ -268,7 +265,9 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this.register(this._inputHandler); } - this.linkifier = this.linkifier || new Linkifier(this._bufferService, this._logService); + if (!this.linkifier) { + this.linkifier = new Linkifier(this._bufferService, this._logService); + } if (this.options.windowsMode) { this._enableWindowsMode(); @@ -1472,6 +1471,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._setup(); this._bufferService.reset(); + this._charsetService.reset(); this._coreService.reset(); this._coreMouseService.reset(); this._selectionService?.reset(); From e6c095754fcdbe5af307de29d3b2c1a39088798e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 19 Dec 2019 15:59:59 +1100 Subject: [PATCH 206/268] Remove old params props from Terminal --- src/Terminal.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index b89efdb65..77e7677ec 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -121,9 +121,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // misc public savedCols: number; - public params: (string | number)[]; - public currentParam: string | number; - // write buffer private _writeBuffer: WriteBuffer; @@ -247,9 +244,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this.insertMode = false; this.bracketedPasteMode = false; - this.params = []; - this.currentParam = 0; - this._userScrolling = false; if (this._inputHandler) { From 03fc6c3f972241c060398f80fe96a0fb3836ea2a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 19 Dec 2019 16:04:15 +1100 Subject: [PATCH 207/268] Don't keep a reference to parent anymore --- src/Terminal.ts | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 77e7677ec..74c2d00ea 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -74,10 +74,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp public element: HTMLElement; public screenElement: HTMLElement; - /** - * The HTMLElement that the terminal is created in, set by Terminal.open. - */ - private _parent: HTMLElement | null; private _document: Document; private _viewportScrollArea: HTMLElement; private _viewportElement: HTMLElement; @@ -236,8 +232,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp } private _setup(): void { - this._parent = document ? document.body : null; - this._customKeyEventHandler = null; // modes @@ -456,9 +450,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp * @param parent The element to create the terminal within. */ public open(parent: HTMLElement): void { - this._parent = parent || this._parent; - - if (!this._parent) { + if (!parent) { throw new Error('Terminal requires a parent element.'); } @@ -466,7 +458,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._logService.warn('Terminal.open was called on an element that was not attached to the DOM'); } - this._document = this._parent.ownerDocument; + this._document = parent.ownerDocument; // Create main element container this.element = this._document.createElement('div'); @@ -474,7 +466,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this.element.classList.add('terminal'); this.element.classList.add('xterm'); this.element.setAttribute('tabindex', '0'); - this._parent.appendChild(this.element); + parent.appendChild(this.element); // Performance: Use a document fragment to build the terminal // viewport and helper elements detached from the DOM From 34bf2f9a0f6049fb0eef27e54f22648042609293 Mon Sep 17 00:00:00 2001 From: javacs3 Date: Thu, 19 Dec 2019 22:17:32 +0800 Subject: [PATCH 208/268] 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 278437140..c2a3c46e1 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 15e3173177cb23fe4e7576339fd5130e52b6bf80 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 20 Dec 2019 06:14:26 +1100 Subject: [PATCH 209/268] Move back to reseting parser only on RIS Related #2637 --- src/InputHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index f512ac1cc..ae44c31ce 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -2196,11 +2196,11 @@ export class InputHandler extends Disposable implements IInputHandler { * Reset to initial state. */ public fullReset(): void { + this._parser.reset(); this._onRequestReset.fire(); } public reset(): void { - this._parser.reset(); this._curAttrData = DEFAULT_ATTR_DATA.clone(); this._eraseAttrDataInternal = DEFAULT_ATTR_DATA.clone(); } From 0631323bf57277d6b48ef6c2d2c20027c92498d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 21 Dec 2019 16:27:12 +0100 Subject: [PATCH 210/268] cleanup --- .../src/Unicode11Addon.ts | 253 +----------------- .../xterm-addon-unicode11/src/UnicodeV11.ts | 222 +++++++++++++++ .../xterm-addon-unicode11/src/tsconfig.json | 9 +- .../xterm-addon-unicode11/webpack.config.js | 7 + demo/client.ts | 2 +- src/common/input/UnicodeV6.ts | 190 ++++++------- tsconfig.all.json | 4 +- typings/xterm.d.ts | 2 +- 8 files changed, 340 insertions(+), 349 deletions(-) create mode 100644 addons/xterm-addon-unicode11/src/UnicodeV11.ts diff --git a/addons/xterm-addon-unicode11/src/Unicode11Addon.ts b/addons/xterm-addon-unicode11/src/Unicode11Addon.ts index 37c5eb7d8..cb8c9c563 100644 --- a/addons/xterm-addon-unicode11/src/Unicode11Addon.ts +++ b/addons/xterm-addon-unicode11/src/Unicode11Addon.ts @@ -5,261 +5,12 @@ * UnicodeVersionProvider for V11. */ -import { Terminal, ITerminalAddon, IUnicodeVersionProvider } from 'xterm'; - - -type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray - | Int8Array | Int16Array | Int32Array - | Float32Array | Float64Array; - - -/** - * polyfill for TypedArray.fill - * This is needed to support .fill in all safari versions and IE 11. - */ -function fill(array: T, value: number, start?: number, end?: number): T { - // all modern engines that support .fill - if (array.fill) { - return array.fill(value, start, end) as T; - } - return fillFallback(array, value, start, end); -} - -function fillFallback(array: T, value: number, start: number = 0, end: number = array.length): T { - // safari and IE 11 - // since IE 11 does not support Array.prototype.fill either - // we cannot use the suggested polyfill from MDN - // instead we simply fall back to looping - if (start >= array.length) { - return array; - } - start = (array.length + start) % array.length; - if (end >= array.length) { - end = array.length; - } else { - end = (array.length + end) % array.length; - } - for (let i = start; i < end; ++i) { - array[i] = value; - } - return array; -} - -class UnicodeV11 implements IUnicodeVersionProvider { - static bmpCombining = [ - [0x0300, 0x036F], [0x0483, 0x0489], [0x0591, 0x05BD], - [0x05BF, 0x05BF], [0x05C1, 0x05C2], [0x05C4, 0x05C5], - [0x05C7, 0x05C7], [0x0600, 0x0605], [0x0610, 0x061A], - [0x061C, 0x061C], [0x064B, 0x065F], [0x0670, 0x0670], - [0x06D6, 0x06DD], [0x06DF, 0x06E4], [0x06E7, 0x06E8], - [0x06EA, 0x06ED], [0x070F, 0x070F], [0x0711, 0x0711], - [0x0730, 0x074A], [0x07A6, 0x07B0], [0x07EB, 0x07F3], - [0x07FD, 0x07FD], [0x0816, 0x0819], [0x081B, 0x0823], - [0x0825, 0x0827], [0x0829, 0x082D], [0x0859, 0x085B], - [0x08D3, 0x0902], [0x093A, 0x093A], [0x093C, 0x093C], - [0x0941, 0x0948], [0x094D, 0x094D], [0x0951, 0x0957], - [0x0962, 0x0963], [0x0981, 0x0981], [0x09BC, 0x09BC], - [0x09C1, 0x09C4], [0x09CD, 0x09CD], [0x09E2, 0x09E3], - [0x09FE, 0x09FE], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C], - [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D], - [0x0A51, 0x0A51], [0x0A70, 0x0A71], [0x0A75, 0x0A75], - [0x0A81, 0x0A82], [0x0ABC, 0x0ABC], [0x0AC1, 0x0AC5], - [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD], [0x0AE2, 0x0AE3], - [0x0AFA, 0x0AFF], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C], - [0x0B3F, 0x0B3F], [0x0B41, 0x0B44], [0x0B4D, 0x0B4D], - [0x0B56, 0x0B56], [0x0B62, 0x0B63], [0x0B82, 0x0B82], - [0x0BC0, 0x0BC0], [0x0BCD, 0x0BCD], [0x0C00, 0x0C00], - [0x0C04, 0x0C04], [0x0C3E, 0x0C40], [0x0C46, 0x0C48], - [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0C62, 0x0C63], - [0x0C81, 0x0C81], [0x0CBC, 0x0CBC], [0x0CBF, 0x0CBF], - [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD], [0x0CE2, 0x0CE3], - [0x0D00, 0x0D01], [0x0D3B, 0x0D3C], [0x0D41, 0x0D44], - [0x0D4D, 0x0D4D], [0x0D62, 0x0D63], [0x0DCA, 0x0DCA], - [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6], [0x0E31, 0x0E31], - [0x0E34, 0x0E3A], [0x0E47, 0x0E4E], [0x0EB1, 0x0EB1], - [0x0EB4, 0x0EBC], [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], - [0x0F35, 0x0F35], [0x0F37, 0x0F37], [0x0F39, 0x0F39], - [0x0F71, 0x0F7E], [0x0F80, 0x0F84], [0x0F86, 0x0F87], - [0x0F8D, 0x0F97], [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], - [0x102D, 0x1030], [0x1032, 0x1037], [0x1039, 0x103A], - [0x103D, 0x103E], [0x1058, 0x1059], [0x105E, 0x1060], - [0x1071, 0x1074], [0x1082, 0x1082], [0x1085, 0x1086], - [0x108D, 0x108D], [0x109D, 0x109D], [0x1160, 0x11FF], - [0x135D, 0x135F], [0x1712, 0x1714], [0x1732, 0x1734], - [0x1752, 0x1753], [0x1772, 0x1773], [0x17B4, 0x17B5], - [0x17B7, 0x17BD], [0x17C6, 0x17C6], [0x17C9, 0x17D3], - [0x17DD, 0x17DD], [0x180B, 0x180E], [0x1885, 0x1886], - [0x18A9, 0x18A9], [0x1920, 0x1922], [0x1927, 0x1928], - [0x1932, 0x1932], [0x1939, 0x193B], [0x1A17, 0x1A18], - [0x1A1B, 0x1A1B], [0x1A56, 0x1A56], [0x1A58, 0x1A5E], - [0x1A60, 0x1A60], [0x1A62, 0x1A62], [0x1A65, 0x1A6C], - [0x1A73, 0x1A7C], [0x1A7F, 0x1A7F], [0x1AB0, 0x1ABE], - [0x1B00, 0x1B03], [0x1B34, 0x1B34], [0x1B36, 0x1B3A], - [0x1B3C, 0x1B3C], [0x1B42, 0x1B42], [0x1B6B, 0x1B73], - [0x1B80, 0x1B81], [0x1BA2, 0x1BA5], [0x1BA8, 0x1BA9], - [0x1BAB, 0x1BAD], [0x1BE6, 0x1BE6], [0x1BE8, 0x1BE9], - [0x1BED, 0x1BED], [0x1BEF, 0x1BF1], [0x1C2C, 0x1C33], - [0x1C36, 0x1C37], [0x1CD0, 0x1CD2], [0x1CD4, 0x1CE0], - [0x1CE2, 0x1CE8], [0x1CED, 0x1CED], [0x1CF4, 0x1CF4], - [0x1CF8, 0x1CF9], [0x1DC0, 0x1DF9], [0x1DFB, 0x1DFF], - [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2064], - [0x2066, 0x206F], [0x20D0, 0x20F0], [0x2CEF, 0x2CF1], - [0x2D7F, 0x2D7F], [0x2DE0, 0x2DFF], [0x302A, 0x302D], - [0x3099, 0x309A], [0xA66F, 0xA672], [0xA674, 0xA67D], - [0xA69E, 0xA69F], [0xA6F0, 0xA6F1], [0xA802, 0xA802], - [0xA806, 0xA806], [0xA80B, 0xA80B], [0xA825, 0xA826], - [0xA8C4, 0xA8C5], [0xA8E0, 0xA8F1], [0xA8FF, 0xA8FF], - [0xA926, 0xA92D], [0xA947, 0xA951], [0xA980, 0xA982], - [0xA9B3, 0xA9B3], [0xA9B6, 0xA9B9], [0xA9BC, 0xA9BD], - [0xA9E5, 0xA9E5], [0xAA29, 0xAA2E], [0xAA31, 0xAA32], - [0xAA35, 0xAA36], [0xAA43, 0xAA43], [0xAA4C, 0xAA4C], - [0xAA7C, 0xAA7C], [0xAAB0, 0xAAB0], [0xAAB2, 0xAAB4], - [0xAAB7, 0xAAB8], [0xAABE, 0xAABF], [0xAAC1, 0xAAC1], - [0xAAEC, 0xAAED], [0xAAF6, 0xAAF6], [0xABE5, 0xABE5], - [0xABE8, 0xABE8], [0xABED, 0xABED], [0xFB1E, 0xFB1E], - [0xFE00, 0xFE0F], [0xFE20, 0xFE2F], [0xFEFF, 0xFEFF], - [0xFFF9, 0xFFFB] - ]; - static highCombining = [ - [0x101FD, 0x101FD], [0x102E0, 0x102E0], - [0x10376, 0x1037A], [0x10A01, 0x10A03], [0x10A05, 0x10A06], - [0x10A0C, 0x10A0F], [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], - [0x10AE5, 0x10AE6], [0x10D24, 0x10D27], [0x10F46, 0x10F50], - [0x11001, 0x11001], [0x11038, 0x11046], [0x1107F, 0x11081], - [0x110B3, 0x110B6], [0x110B9, 0x110BA], [0x110BD, 0x110BD], - [0x110CD, 0x110CD], [0x11100, 0x11102], [0x11127, 0x1112B], - [0x1112D, 0x11134], [0x11173, 0x11173], [0x11180, 0x11181], - [0x111B6, 0x111BE], [0x111C9, 0x111CC], [0x1122F, 0x11231], - [0x11234, 0x11234], [0x11236, 0x11237], [0x1123E, 0x1123E], - [0x112DF, 0x112DF], [0x112E3, 0x112EA], [0x11300, 0x11301], - [0x1133B, 0x1133C], [0x11340, 0x11340], [0x11366, 0x1136C], - [0x11370, 0x11374], [0x11438, 0x1143F], [0x11442, 0x11444], - [0x11446, 0x11446], [0x1145E, 0x1145E], [0x114B3, 0x114B8], - [0x114BA, 0x114BA], [0x114BF, 0x114C0], [0x114C2, 0x114C3], - [0x115B2, 0x115B5], [0x115BC, 0x115BD], [0x115BF, 0x115C0], - [0x115DC, 0x115DD], [0x11633, 0x1163A], [0x1163D, 0x1163D], - [0x1163F, 0x11640], [0x116AB, 0x116AB], [0x116AD, 0x116AD], - [0x116B0, 0x116B5], [0x116B7, 0x116B7], [0x1171D, 0x1171F], - [0x11722, 0x11725], [0x11727, 0x1172B], [0x1182F, 0x11837], - [0x11839, 0x1183A], [0x119D4, 0x119D7], [0x119DA, 0x119DB], - [0x119E0, 0x119E0], [0x11A01, 0x11A0A], [0x11A33, 0x11A38], - [0x11A3B, 0x11A3E], [0x11A47, 0x11A47], [0x11A51, 0x11A56], - [0x11A59, 0x11A5B], [0x11A8A, 0x11A96], [0x11A98, 0x11A99], - [0x11C30, 0x11C36], [0x11C38, 0x11C3D], [0x11C3F, 0x11C3F], - [0x11C92, 0x11CA7], [0x11CAA, 0x11CB0], [0x11CB2, 0x11CB3], - [0x11CB5, 0x11CB6], [0x11D31, 0x11D36], [0x11D3A, 0x11D3A], - [0x11D3C, 0x11D3D], [0x11D3F, 0x11D45], [0x11D47, 0x11D47], - [0x11D90, 0x11D91], [0x11D95, 0x11D95], [0x11D97, 0x11D97], - [0x11EF3, 0x11EF4], [0x13430, 0x13438], [0x16AF0, 0x16AF4], - [0x16B30, 0x16B36], [0x16F4F, 0x16F4F], [0x16F8F, 0x16F92], - [0x1BC9D, 0x1BC9E], [0x1BCA0, 0x1BCA3], [0x1D167, 0x1D169], - [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD], - [0x1D242, 0x1D244], [0x1DA00, 0x1DA36], [0x1DA3B, 0x1DA6C], - [0x1DA75, 0x1DA75], [0x1DA84, 0x1DA84], [0x1DA9B, 0x1DA9F], - [0x1DAA1, 0x1DAAF], [0x1E000, 0x1E006], [0x1E008, 0x1E018], - [0x1E01B, 0x1E021], [0x1E023, 0x1E024], [0x1E026, 0x1E02A], - [0x1E130, 0x1E136], [0x1E2EC, 0x1E2EF], [0x1E8D0, 0x1E8D6], - [0x1E944, 0x1E94A], [0xE0001, 0xE0001], [0xE0020, 0xE007F], - [0xE0100, 0xE01EF] - ]; - static bmpWide = [ - [0x1100, 0x115F], [0x231A, 0x231B], [0x2329, 0x232A], - [0x23E9, 0x23EC], [0x23F0, 0x23F0], [0x23F3, 0x23F3], - [0x25FD, 0x25FE], [0x2614, 0x2615], [0x2648, 0x2653], - [0x267F, 0x267F], [0x2693, 0x2693], [0x26A1, 0x26A1], - [0x26AA, 0x26AB], [0x26BD, 0x26BE], [0x26C4, 0x26C5], - [0x26CE, 0x26CE], [0x26D4, 0x26D4], [0x26EA, 0x26EA], - [0x26F2, 0x26F3], [0x26F5, 0x26F5], [0x26FA, 0x26FA], - [0x26FD, 0x26FD], [0x2705, 0x2705], [0x270A, 0x270B], - [0x2728, 0x2728], [0x274C, 0x274C], [0x274E, 0x274E], - [0x2753, 0x2755], [0x2757, 0x2757], [0x2795, 0x2797], - [0x27B0, 0x27B0], [0x27BF, 0x27BF], [0x2B1B, 0x2B1C], - [0x2B50, 0x2B50], [0x2B55, 0x2B55], [0x2E80, 0x2E99], - [0x2E9B, 0x2EF3], [0x2F00, 0x2FD5], [0x2FF0, 0x2FFB], - [0x3000, 0x3029], [0x302E, 0x303E], [0x3041, 0x3096], - [0x309B, 0x30FF], [0x3105, 0x312F], [0x3131, 0x318E], - [0x3190, 0x31BA], [0x31C0, 0x31E3], [0x31F0, 0x321E], - [0x3220, 0x3247], [0x3250, 0x4DBF], [0x4E00, 0xA48C], - [0xA490, 0xA4C6], [0xA960, 0xA97C], [0xAC00, 0xD7A3], - [0xF900, 0xFAFF], [0xFE10, 0xFE19], [0xFE30, 0xFE52], - [0xFE54, 0xFE66], [0xFE68, 0xFE6B], [0xFF01, 0xFF60], - [0xFFE0, 0xFFE6] - ]; - static highWide = [ - [0x16FE0, 0x16FE3], [0x17000, 0x187F7], - [0x18800, 0x18AF2], [0x1B000, 0x1B11E], [0x1B150, 0x1B152], - [0x1B164, 0x1B167], [0x1B170, 0x1B2FB], [0x1F004, 0x1F004], - [0x1F0CF, 0x1F0CF], [0x1F18E, 0x1F18E], [0x1F191, 0x1F19A], - [0x1F200, 0x1F202], [0x1F210, 0x1F23B], [0x1F240, 0x1F248], - [0x1F250, 0x1F251], [0x1F260, 0x1F265], [0x1F300, 0x1F320], - [0x1F32D, 0x1F335], [0x1F337, 0x1F37C], [0x1F37E, 0x1F393], - [0x1F3A0, 0x1F3CA], [0x1F3CF, 0x1F3D3], [0x1F3E0, 0x1F3F0], - [0x1F3F4, 0x1F3F4], [0x1F3F8, 0x1F43E], [0x1F440, 0x1F440], - [0x1F442, 0x1F4FC], [0x1F4FF, 0x1F53D], [0x1F54B, 0x1F54E], - [0x1F550, 0x1F567], [0x1F57A, 0x1F57A], [0x1F595, 0x1F596], - [0x1F5A4, 0x1F5A4], [0x1F5FB, 0x1F64F], [0x1F680, 0x1F6C5], - [0x1F6CC, 0x1F6CC], [0x1F6D0, 0x1F6D2], [0x1F6D5, 0x1F6D5], - [0x1F6EB, 0x1F6EC], [0x1F6F4, 0x1F6FA], [0x1F7E0, 0x1F7EB], - [0x1F90D, 0x1F971], [0x1F973, 0x1F976], [0x1F97A, 0x1F9A2], - [0x1F9A5, 0x1F9AA], [0x1F9AE, 0x1F9CA], [0x1F9CD, 0x1F9FF], - [0x1FA70, 0x1FA73], [0x1FA78, 0x1FA7A], [0x1FA80, 0x1FA82], - [0x1FA90, 0x1FA95], [0x20000, 0x2FFFD], [0x30000, 0x3FFFD] - ]; - static bmpTable: Uint8Array; - - static bisearch(ucs: number, data: number[][]): boolean { - let min = 0; - let max = data.length - 1; - let mid; - if (ucs < data[0][0] || ucs > data[max][1]) { - return false; - } - while (max >= min) { - mid = (min + max) >> 1; - if (ucs > data[mid][1]) { - min = mid + 1; - } else if (ucs < data[mid][0]) { - max = mid - 1; - } else { - return true; - } - } - return false; - } - - public version = '11'; - - constructor() { - if (!UnicodeV11.bmpTable) { - UnicodeV11.bmpTable = new Uint8Array(65536); - fill(UnicodeV11.bmpTable, 1); - UnicodeV11.bmpTable[0] = 0; - fill(UnicodeV11.bmpTable, 0, 1, 32); - fill(UnicodeV11.bmpTable, 0, 0x7f, 0xa0); - for (let r = 0; r < UnicodeV11.bmpCombining.length; ++r) { - fill(UnicodeV11.bmpTable, 0, UnicodeV11.bmpCombining[r][0], UnicodeV11.bmpCombining[r][1] + 1); - } - for (let r = 0; r < UnicodeV11.bmpWide.length; ++r) { - fill(UnicodeV11.bmpTable, 2, UnicodeV11.bmpWide[r][0], UnicodeV11.bmpWide[r][1] + 1); - } - } - } - - public wcwidth(num: number): 0 | 1 | 2 { - if (num < 32) return 0; - if (num < 127) return 1; - if (num < 65536) return UnicodeV11.bmpTable[num] as 0 | 1 | 2; - if (UnicodeV11.bisearch(num, UnicodeV11.highCombining)) return 0; - if (UnicodeV11.bisearch(num, UnicodeV11.highWide)) return 2; - return 1; - } -} - +import { Terminal, ITerminalAddon } from 'xterm'; +import { UnicodeV11 } from './UnicodeV11'; export class Unicode11Addon implements ITerminalAddon { public activate(terminal: Terminal): void { terminal.unicode.register(new UnicodeV11()); } - public dispose(): void { } } diff --git a/addons/xterm-addon-unicode11/src/UnicodeV11.ts b/addons/xterm-addon-unicode11/src/UnicodeV11.ts new file mode 100644 index 000000000..bd778b403 --- /dev/null +++ b/addons/xterm-addon-unicode11/src/UnicodeV11.ts @@ -0,0 +1,222 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IUnicodeVersionProvider } from 'xterm'; +import { fill } from 'common/TypedArrayUtils'; + +type CharWidth = 0 | 1 | 2; + +const BMP_COMBINING = [ + [0x0300, 0x036F], [0x0483, 0x0489], [0x0591, 0x05BD], + [0x05BF, 0x05BF], [0x05C1, 0x05C2], [0x05C4, 0x05C5], + [0x05C7, 0x05C7], [0x0600, 0x0605], [0x0610, 0x061A], + [0x061C, 0x061C], [0x064B, 0x065F], [0x0670, 0x0670], + [0x06D6, 0x06DD], [0x06DF, 0x06E4], [0x06E7, 0x06E8], + [0x06EA, 0x06ED], [0x070F, 0x070F], [0x0711, 0x0711], + [0x0730, 0x074A], [0x07A6, 0x07B0], [0x07EB, 0x07F3], + [0x07FD, 0x07FD], [0x0816, 0x0819], [0x081B, 0x0823], + [0x0825, 0x0827], [0x0829, 0x082D], [0x0859, 0x085B], + [0x08D3, 0x0902], [0x093A, 0x093A], [0x093C, 0x093C], + [0x0941, 0x0948], [0x094D, 0x094D], [0x0951, 0x0957], + [0x0962, 0x0963], [0x0981, 0x0981], [0x09BC, 0x09BC], + [0x09C1, 0x09C4], [0x09CD, 0x09CD], [0x09E2, 0x09E3], + [0x09FE, 0x09FE], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C], + [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D], + [0x0A51, 0x0A51], [0x0A70, 0x0A71], [0x0A75, 0x0A75], + [0x0A81, 0x0A82], [0x0ABC, 0x0ABC], [0x0AC1, 0x0AC5], + [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD], [0x0AE2, 0x0AE3], + [0x0AFA, 0x0AFF], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C], + [0x0B3F, 0x0B3F], [0x0B41, 0x0B44], [0x0B4D, 0x0B4D], + [0x0B56, 0x0B56], [0x0B62, 0x0B63], [0x0B82, 0x0B82], + [0x0BC0, 0x0BC0], [0x0BCD, 0x0BCD], [0x0C00, 0x0C00], + [0x0C04, 0x0C04], [0x0C3E, 0x0C40], [0x0C46, 0x0C48], + [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0C62, 0x0C63], + [0x0C81, 0x0C81], [0x0CBC, 0x0CBC], [0x0CBF, 0x0CBF], + [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD], [0x0CE2, 0x0CE3], + [0x0D00, 0x0D01], [0x0D3B, 0x0D3C], [0x0D41, 0x0D44], + [0x0D4D, 0x0D4D], [0x0D62, 0x0D63], [0x0DCA, 0x0DCA], + [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6], [0x0E31, 0x0E31], + [0x0E34, 0x0E3A], [0x0E47, 0x0E4E], [0x0EB1, 0x0EB1], + [0x0EB4, 0x0EBC], [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], + [0x0F35, 0x0F35], [0x0F37, 0x0F37], [0x0F39, 0x0F39], + [0x0F71, 0x0F7E], [0x0F80, 0x0F84], [0x0F86, 0x0F87], + [0x0F8D, 0x0F97], [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], + [0x102D, 0x1030], [0x1032, 0x1037], [0x1039, 0x103A], + [0x103D, 0x103E], [0x1058, 0x1059], [0x105E, 0x1060], + [0x1071, 0x1074], [0x1082, 0x1082], [0x1085, 0x1086], + [0x108D, 0x108D], [0x109D, 0x109D], [0x1160, 0x11FF], + [0x135D, 0x135F], [0x1712, 0x1714], [0x1732, 0x1734], + [0x1752, 0x1753], [0x1772, 0x1773], [0x17B4, 0x17B5], + [0x17B7, 0x17BD], [0x17C6, 0x17C6], [0x17C9, 0x17D3], + [0x17DD, 0x17DD], [0x180B, 0x180E], [0x1885, 0x1886], + [0x18A9, 0x18A9], [0x1920, 0x1922], [0x1927, 0x1928], + [0x1932, 0x1932], [0x1939, 0x193B], [0x1A17, 0x1A18], + [0x1A1B, 0x1A1B], [0x1A56, 0x1A56], [0x1A58, 0x1A5E], + [0x1A60, 0x1A60], [0x1A62, 0x1A62], [0x1A65, 0x1A6C], + [0x1A73, 0x1A7C], [0x1A7F, 0x1A7F], [0x1AB0, 0x1ABE], + [0x1B00, 0x1B03], [0x1B34, 0x1B34], [0x1B36, 0x1B3A], + [0x1B3C, 0x1B3C], [0x1B42, 0x1B42], [0x1B6B, 0x1B73], + [0x1B80, 0x1B81], [0x1BA2, 0x1BA5], [0x1BA8, 0x1BA9], + [0x1BAB, 0x1BAD], [0x1BE6, 0x1BE6], [0x1BE8, 0x1BE9], + [0x1BED, 0x1BED], [0x1BEF, 0x1BF1], [0x1C2C, 0x1C33], + [0x1C36, 0x1C37], [0x1CD0, 0x1CD2], [0x1CD4, 0x1CE0], + [0x1CE2, 0x1CE8], [0x1CED, 0x1CED], [0x1CF4, 0x1CF4], + [0x1CF8, 0x1CF9], [0x1DC0, 0x1DF9], [0x1DFB, 0x1DFF], + [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2064], + [0x2066, 0x206F], [0x20D0, 0x20F0], [0x2CEF, 0x2CF1], + [0x2D7F, 0x2D7F], [0x2DE0, 0x2DFF], [0x302A, 0x302D], + [0x3099, 0x309A], [0xA66F, 0xA672], [0xA674, 0xA67D], + [0xA69E, 0xA69F], [0xA6F0, 0xA6F1], [0xA802, 0xA802], + [0xA806, 0xA806], [0xA80B, 0xA80B], [0xA825, 0xA826], + [0xA8C4, 0xA8C5], [0xA8E0, 0xA8F1], [0xA8FF, 0xA8FF], + [0xA926, 0xA92D], [0xA947, 0xA951], [0xA980, 0xA982], + [0xA9B3, 0xA9B3], [0xA9B6, 0xA9B9], [0xA9BC, 0xA9BD], + [0xA9E5, 0xA9E5], [0xAA29, 0xAA2E], [0xAA31, 0xAA32], + [0xAA35, 0xAA36], [0xAA43, 0xAA43], [0xAA4C, 0xAA4C], + [0xAA7C, 0xAA7C], [0xAAB0, 0xAAB0], [0xAAB2, 0xAAB4], + [0xAAB7, 0xAAB8], [0xAABE, 0xAABF], [0xAAC1, 0xAAC1], + [0xAAEC, 0xAAED], [0xAAF6, 0xAAF6], [0xABE5, 0xABE5], + [0xABE8, 0xABE8], [0xABED, 0xABED], [0xFB1E, 0xFB1E], + [0xFE00, 0xFE0F], [0xFE20, 0xFE2F], [0xFEFF, 0xFEFF], + [0xFFF9, 0xFFFB] +]; +const HIGH_COMBINING = [ + [0x101FD, 0x101FD], [0x102E0, 0x102E0], + [0x10376, 0x1037A], [0x10A01, 0x10A03], [0x10A05, 0x10A06], + [0x10A0C, 0x10A0F], [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], + [0x10AE5, 0x10AE6], [0x10D24, 0x10D27], [0x10F46, 0x10F50], + [0x11001, 0x11001], [0x11038, 0x11046], [0x1107F, 0x11081], + [0x110B3, 0x110B6], [0x110B9, 0x110BA], [0x110BD, 0x110BD], + [0x110CD, 0x110CD], [0x11100, 0x11102], [0x11127, 0x1112B], + [0x1112D, 0x11134], [0x11173, 0x11173], [0x11180, 0x11181], + [0x111B6, 0x111BE], [0x111C9, 0x111CC], [0x1122F, 0x11231], + [0x11234, 0x11234], [0x11236, 0x11237], [0x1123E, 0x1123E], + [0x112DF, 0x112DF], [0x112E3, 0x112EA], [0x11300, 0x11301], + [0x1133B, 0x1133C], [0x11340, 0x11340], [0x11366, 0x1136C], + [0x11370, 0x11374], [0x11438, 0x1143F], [0x11442, 0x11444], + [0x11446, 0x11446], [0x1145E, 0x1145E], [0x114B3, 0x114B8], + [0x114BA, 0x114BA], [0x114BF, 0x114C0], [0x114C2, 0x114C3], + [0x115B2, 0x115B5], [0x115BC, 0x115BD], [0x115BF, 0x115C0], + [0x115DC, 0x115DD], [0x11633, 0x1163A], [0x1163D, 0x1163D], + [0x1163F, 0x11640], [0x116AB, 0x116AB], [0x116AD, 0x116AD], + [0x116B0, 0x116B5], [0x116B7, 0x116B7], [0x1171D, 0x1171F], + [0x11722, 0x11725], [0x11727, 0x1172B], [0x1182F, 0x11837], + [0x11839, 0x1183A], [0x119D4, 0x119D7], [0x119DA, 0x119DB], + [0x119E0, 0x119E0], [0x11A01, 0x11A0A], [0x11A33, 0x11A38], + [0x11A3B, 0x11A3E], [0x11A47, 0x11A47], [0x11A51, 0x11A56], + [0x11A59, 0x11A5B], [0x11A8A, 0x11A96], [0x11A98, 0x11A99], + [0x11C30, 0x11C36], [0x11C38, 0x11C3D], [0x11C3F, 0x11C3F], + [0x11C92, 0x11CA7], [0x11CAA, 0x11CB0], [0x11CB2, 0x11CB3], + [0x11CB5, 0x11CB6], [0x11D31, 0x11D36], [0x11D3A, 0x11D3A], + [0x11D3C, 0x11D3D], [0x11D3F, 0x11D45], [0x11D47, 0x11D47], + [0x11D90, 0x11D91], [0x11D95, 0x11D95], [0x11D97, 0x11D97], + [0x11EF3, 0x11EF4], [0x13430, 0x13438], [0x16AF0, 0x16AF4], + [0x16B30, 0x16B36], [0x16F4F, 0x16F4F], [0x16F8F, 0x16F92], + [0x1BC9D, 0x1BC9E], [0x1BCA0, 0x1BCA3], [0x1D167, 0x1D169], + [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD], + [0x1D242, 0x1D244], [0x1DA00, 0x1DA36], [0x1DA3B, 0x1DA6C], + [0x1DA75, 0x1DA75], [0x1DA84, 0x1DA84], [0x1DA9B, 0x1DA9F], + [0x1DAA1, 0x1DAAF], [0x1E000, 0x1E006], [0x1E008, 0x1E018], + [0x1E01B, 0x1E021], [0x1E023, 0x1E024], [0x1E026, 0x1E02A], + [0x1E130, 0x1E136], [0x1E2EC, 0x1E2EF], [0x1E8D0, 0x1E8D6], + [0x1E944, 0x1E94A], [0xE0001, 0xE0001], [0xE0020, 0xE007F], + [0xE0100, 0xE01EF] +]; +const BMP_WIDE = [ + [0x1100, 0x115F], [0x231A, 0x231B], [0x2329, 0x232A], + [0x23E9, 0x23EC], [0x23F0, 0x23F0], [0x23F3, 0x23F3], + [0x25FD, 0x25FE], [0x2614, 0x2615], [0x2648, 0x2653], + [0x267F, 0x267F], [0x2693, 0x2693], [0x26A1, 0x26A1], + [0x26AA, 0x26AB], [0x26BD, 0x26BE], [0x26C4, 0x26C5], + [0x26CE, 0x26CE], [0x26D4, 0x26D4], [0x26EA, 0x26EA], + [0x26F2, 0x26F3], [0x26F5, 0x26F5], [0x26FA, 0x26FA], + [0x26FD, 0x26FD], [0x2705, 0x2705], [0x270A, 0x270B], + [0x2728, 0x2728], [0x274C, 0x274C], [0x274E, 0x274E], + [0x2753, 0x2755], [0x2757, 0x2757], [0x2795, 0x2797], + [0x27B0, 0x27B0], [0x27BF, 0x27BF], [0x2B1B, 0x2B1C], + [0x2B50, 0x2B50], [0x2B55, 0x2B55], [0x2E80, 0x2E99], + [0x2E9B, 0x2EF3], [0x2F00, 0x2FD5], [0x2FF0, 0x2FFB], + [0x3000, 0x3029], [0x302E, 0x303E], [0x3041, 0x3096], + [0x309B, 0x30FF], [0x3105, 0x312F], [0x3131, 0x318E], + [0x3190, 0x31BA], [0x31C0, 0x31E3], [0x31F0, 0x321E], + [0x3220, 0x3247], [0x3250, 0x4DBF], [0x4E00, 0xA48C], + [0xA490, 0xA4C6], [0xA960, 0xA97C], [0xAC00, 0xD7A3], + [0xF900, 0xFAFF], [0xFE10, 0xFE19], [0xFE30, 0xFE52], + [0xFE54, 0xFE66], [0xFE68, 0xFE6B], [0xFF01, 0xFF60], + [0xFFE0, 0xFFE6] +]; +const HIGH_WIDE = [ + [0x16FE0, 0x16FE3], [0x17000, 0x187F7], + [0x18800, 0x18AF2], [0x1B000, 0x1B11E], [0x1B150, 0x1B152], + [0x1B164, 0x1B167], [0x1B170, 0x1B2FB], [0x1F004, 0x1F004], + [0x1F0CF, 0x1F0CF], [0x1F18E, 0x1F18E], [0x1F191, 0x1F19A], + [0x1F200, 0x1F202], [0x1F210, 0x1F23B], [0x1F240, 0x1F248], + [0x1F250, 0x1F251], [0x1F260, 0x1F265], [0x1F300, 0x1F320], + [0x1F32D, 0x1F335], [0x1F337, 0x1F37C], [0x1F37E, 0x1F393], + [0x1F3A0, 0x1F3CA], [0x1F3CF, 0x1F3D3], [0x1F3E0, 0x1F3F0], + [0x1F3F4, 0x1F3F4], [0x1F3F8, 0x1F43E], [0x1F440, 0x1F440], + [0x1F442, 0x1F4FC], [0x1F4FF, 0x1F53D], [0x1F54B, 0x1F54E], + [0x1F550, 0x1F567], [0x1F57A, 0x1F57A], [0x1F595, 0x1F596], + [0x1F5A4, 0x1F5A4], [0x1F5FB, 0x1F64F], [0x1F680, 0x1F6C5], + [0x1F6CC, 0x1F6CC], [0x1F6D0, 0x1F6D2], [0x1F6D5, 0x1F6D5], + [0x1F6EB, 0x1F6EC], [0x1F6F4, 0x1F6FA], [0x1F7E0, 0x1F7EB], + [0x1F90D, 0x1F971], [0x1F973, 0x1F976], [0x1F97A, 0x1F9A2], + [0x1F9A5, 0x1F9AA], [0x1F9AE, 0x1F9CA], [0x1F9CD, 0x1F9FF], + [0x1FA70, 0x1FA73], [0x1FA78, 0x1FA7A], [0x1FA80, 0x1FA82], + [0x1FA90, 0x1FA95], [0x20000, 0x2FFFD], [0x30000, 0x3FFFD] +]; + +// BMP lookup table, lazy initialized during first addon loading +let TABLE: Uint8Array; + +function bisearch(ucs: number, data: number[][]): boolean { + let min = 0; + let max = data.length - 1; + let mid; + if (ucs < data[0][0] || ucs > data[max][1]) { + return false; + } + while (max >= min) { + mid = (min + max) >> 1; + if (ucs > data[mid][1]) { + min = mid + 1; + } else if (ucs < data[mid][0]) { + max = mid - 1; + } else { + return true; + } + } + return false; +} + + +export class UnicodeV11 implements IUnicodeVersionProvider { + public version = '11'; + + constructor() { + if (!TABLE) { + TABLE = new Uint8Array(65536); + fill(TABLE, 1); + TABLE[0] = 0; + fill(TABLE, 0, 1, 32); + fill(TABLE, 0, 0x7f, 0xa0); + for (let r = 0; r < BMP_COMBINING.length; ++r) { + fill(TABLE, 0, BMP_COMBINING[r][0], BMP_COMBINING[r][1] + 1); + } + for (let r = 0; r < BMP_WIDE.length; ++r) { + fill(TABLE, 2, BMP_WIDE[r][0], BMP_WIDE[r][1] + 1); + } + } + } + + public wcwidth(num: number): CharWidth { + if (num < 32) return 0; + if (num < 127) return 1; + if (num < 65536) return TABLE[num] as CharWidth; + if (bisearch(num, HIGH_COMBINING)) return 0; + if (bisearch(num, HIGH_WIDE)) return 2; + return 1; + } +} diff --git a/addons/xterm-addon-unicode11/src/tsconfig.json b/addons/xterm-addon-unicode11/src/tsconfig.json index d875aa530..1681e6680 100644 --- a/addons/xterm-addon-unicode11/src/tsconfig.json +++ b/addons/xterm-addon-unicode11/src/tsconfig.json @@ -10,10 +10,17 @@ "outDir": "../out", "sourceMap": true, "removeComments": true, - "strict": true + "strict": true, + "baseUrl": ".", + "paths": { + "common/*": [ "../../../src/common/*" ] + }, }, "include": [ "./**/*", "../../../typings/xterm.d.ts" + ], + "references": [ + { "path": "../../../src/common" } ] } diff --git a/addons/xterm-addon-unicode11/webpack.config.js b/addons/xterm-addon-unicode11/webpack.config.js index 07e9de298..66a83a2fb 100644 --- a/addons/xterm-addon-unicode11/webpack.config.js +++ b/addons/xterm-addon-unicode11/webpack.config.js @@ -21,6 +21,13 @@ module.exports = { } ] }, + resolve: { + modules: ['./node_modules'], + extensions: [ '.js' ], + alias: { + common: path.resolve('../../out/common') + } + }, output: { filename: mainFile, path: path.resolve('./lib'), diff --git a/demo/client.ts b/demo/client.ts index 9598ffe07..3a015710c 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -75,7 +75,7 @@ const addons: { [T in AddonType]: IDemoAddon} = { search: { name: 'search', ctor: SearchAddon, canChange: true }, 'web-links': { name: 'web-links', ctor: WebLinksAddon, canChange: true }, webgl: { name: 'webgl', ctor: WebglAddon, canChange: true }, - unicode11: { name: 'unicode11', ctor: Unicode11Addon, canChange: false } + unicode11: { name: 'unicode11', ctor: Unicode11Addon, canChange: true } }; const terminalContainer = document.getElementById('terminal-container'); diff --git a/src/common/input/UnicodeV6.ts b/src/common/input/UnicodeV6.ts index af5089b25..288e56920 100644 --- a/src/common/input/UnicodeV6.ts +++ b/src/common/input/UnicodeV6.ts @@ -5,124 +5,128 @@ import { IUnicodeVersionProvider } from 'common/services/Services'; import { fill } from 'common/TypedArrayUtils'; -export class UnicodeV6 implements IUnicodeVersionProvider { - static bmpCombining = [ - [0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489], - [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2], - [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603], - [0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670], - [0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED], - [0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A], - [0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902], - [0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D], - [0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981], - [0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD], - [0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C], - [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D], - [0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC], - [0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD], - [0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C], - [0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D], - [0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0], - [0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48], - [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC], - [0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD], - [0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D], - [0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6], - [0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E], - [0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC], - [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35], - [0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E], - [0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97], - [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030], - [0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039], - [0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F], - [0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753], - [0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD], - [0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD], - [0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922], - [0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B], - [0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34], - [0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42], - [0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF], - [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063], - [0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F], - [0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B], - [0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F], - [0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB] - ]; - static highCombining = [ - [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F], - [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169], - [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD], - [0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F], - [0xE0100, 0xE01EF] - ]; - static bmpTable: Uint8Array; +type CharWidth = 0 | 1 | 2; - static bisearch(ucs: number, data: number[][]): boolean { - let min = 0; - let max = data.length - 1; - let mid; - if (ucs < data[0][0] || ucs > data[max][1]) { - return false; - } - while (max >= min) { - mid = (min + max) >> 1; - if (ucs > data[mid][1]) { - min = mid + 1; - } else if (ucs < data[mid][0]) { - max = mid - 1; - } else { - return true; - } - } +const BMP_COMBINING = [ + [0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489], + [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2], + [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603], + [0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670], + [0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED], + [0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A], + [0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902], + [0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D], + [0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981], + [0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD], + [0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C], + [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D], + [0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC], + [0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD], + [0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C], + [0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D], + [0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0], + [0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48], + [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC], + [0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD], + [0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D], + [0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6], + [0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E], + [0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC], + [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35], + [0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E], + [0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97], + [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030], + [0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039], + [0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F], + [0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753], + [0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD], + [0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD], + [0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922], + [0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B], + [0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34], + [0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42], + [0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF], + [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063], + [0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F], + [0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B], + [0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F], + [0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB] +]; +const HIGH_COMBINING = [ + [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F], + [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169], + [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD], + [0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F], + [0xE0100, 0xE01EF] +]; + +// BMP lookup table, lazy initialized during first addon loading +let TABLE: Uint8Array; + +function bisearch(ucs: number, data: number[][]): boolean { + let min = 0; + let max = data.length - 1; + let mid; + if (ucs < data[0][0] || ucs > data[max][1]) { return false; } + while (max >= min) { + mid = (min + max) >> 1; + if (ucs > data[mid][1]) { + min = mid + 1; + } else if (ucs < data[mid][0]) { + max = mid - 1; + } else { + return true; + } + } + return false; +} +export class UnicodeV6 implements IUnicodeVersionProvider { public version = '6'; constructor() { // init lookup table once - if (!UnicodeV6.bmpTable) { - UnicodeV6.bmpTable = new Uint8Array(65536); - fill(UnicodeV6.bmpTable, 1); - UnicodeV6.bmpTable[0] = 0; + if (!TABLE) { + TABLE = new Uint8Array(65536); + fill(TABLE, 1); + TABLE[0] = 0; // control chars - fill(UnicodeV6.bmpTable, 0, 1, 32); - fill(UnicodeV6.bmpTable, 0, 0x7f, 0xa0); + fill(TABLE, 0, 1, 32); + fill(TABLE, 0, 0x7f, 0xa0); // apply wide char rules first // wide chars - fill(UnicodeV6.bmpTable, 2, 0x1100, 0x1160); - UnicodeV6.bmpTable[0x2329] = 2; - UnicodeV6.bmpTable[0x232a] = 2; - fill(UnicodeV6.bmpTable, 2, 0x2e80, 0xa4d0); - UnicodeV6.bmpTable[0x303f] = 1; // wrongly in last line + fill(TABLE, 2, 0x1100, 0x1160); + TABLE[0x2329] = 2; + TABLE[0x232a] = 2; + fill(TABLE, 2, 0x2e80, 0xa4d0); + TABLE[0x303f] = 1; // wrongly in last line - fill(UnicodeV6.bmpTable, 2, 0xac00, 0xd7a4); - fill(UnicodeV6.bmpTable, 2, 0xf900, 0xfb00); - fill(UnicodeV6.bmpTable, 2, 0xfe10, 0xfe1a); - fill(UnicodeV6.bmpTable, 2, 0xfe30, 0xfe70); - fill(UnicodeV6.bmpTable, 2, 0xff00, 0xff61); - fill(UnicodeV6.bmpTable, 2, 0xffe0, 0xffe7); + fill(TABLE, 2, 0xac00, 0xd7a4); + fill(TABLE, 2, 0xf900, 0xfb00); + fill(TABLE, 2, 0xfe10, 0xfe1a); + fill(TABLE, 2, 0xfe30, 0xfe70); + fill(TABLE, 2, 0xff00, 0xff61); + fill(TABLE, 2, 0xffe0, 0xffe7); // apply combining last to ensure we overwrite // wrongly wide set chars: // the original algo evals combining first and falls // through to wide check so we simply do here the opposite // combining 0 - for (let r = 0; r < UnicodeV6.bmpCombining.length; ++r) { - fill(UnicodeV6.bmpTable, 0, UnicodeV6.bmpCombining[r][0], UnicodeV6.bmpCombining[r][1] + 1); + for (let r = 0; r < BMP_COMBINING.length; ++r) { + fill(TABLE, 0, BMP_COMBINING[r][0], BMP_COMBINING[r][1] + 1); } } } - public wcwidth(num: number): 0 | 1 | 2 { + public wcwidth(num: number): CharWidth { if (num < 32) return 0; if (num < 127) return 1; - if (num < 65536) return UnicodeV6.bmpTable[num] as 0 | 1 | 2; - if (UnicodeV6.bisearch(num, UnicodeV6.highCombining)) return 0; + if (num < 65536) return TABLE[num] as CharWidth; + if (bisearch(num, HIGH_COMBINING)) return 0; if ((num >= 0x20000 && num <= 0x2fffd) || (num >= 0x30000 && num <= 0x3fffd)) return 2; return 1; } diff --git a/tsconfig.all.json b/tsconfig.all.json index efd968f3a..8a26c3021 100644 --- a/tsconfig.all.json +++ b/tsconfig.all.json @@ -8,8 +8,8 @@ { "path": "./addons/xterm-addon-attach/src" }, { "path": "./addons/xterm-addon-fit/src" }, { "path": "./addons/xterm-addon-search/src" }, + { "path": "./addons/xterm-addon-unicode11/src" }, { "path": "./addons/xterm-addon-web-links/src" }, - { "path": "./addons/xterm-addon-webgl/src" }, - { "path": "./addons/xterm-addon-unicode11/src" } + { "path": "./addons/xterm-addon-webgl/src" } ] } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 2efa39f73..b1fd2e060 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -1150,7 +1150,7 @@ declare module 'xterm' { /** String indicating the Unicode version provided. */ version: string; /** Unicode version dependent wcwidth implementation. */ - wcwidth(ucs: number): 0 | 1 | 2; + wcwidth(codepoint: number): 0 | 1 | 2; } /** From 0f8a034e8bfe980695fc47db8392d3b21dcbf719 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 21 Dec 2019 16:29:46 +0100 Subject: [PATCH 211/268] make linter happy --- .../xterm-addon-unicode11/src/UnicodeV11.ts | 20 +++++----- src/common/input/UnicodeV6.ts | 40 +++++++++---------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/addons/xterm-addon-unicode11/src/UnicodeV11.ts b/addons/xterm-addon-unicode11/src/UnicodeV11.ts index bd778b403..d1525d257 100644 --- a/addons/xterm-addon-unicode11/src/UnicodeV11.ts +++ b/addons/xterm-addon-unicode11/src/UnicodeV11.ts @@ -169,7 +169,7 @@ const HIGH_WIDE = [ ]; // BMP lookup table, lazy initialized during first addon loading -let TABLE: Uint8Array; +let table: Uint8Array; function bisearch(ucs: number, data: number[][]): boolean { let min = 0; @@ -196,17 +196,17 @@ export class UnicodeV11 implements IUnicodeVersionProvider { public version = '11'; constructor() { - if (!TABLE) { - TABLE = new Uint8Array(65536); - fill(TABLE, 1); - TABLE[0] = 0; - fill(TABLE, 0, 1, 32); - fill(TABLE, 0, 0x7f, 0xa0); + if (!table) { + table = new Uint8Array(65536); + fill(table, 1); + table[0] = 0; + fill(table, 0, 1, 32); + fill(table, 0, 0x7f, 0xa0); for (let r = 0; r < BMP_COMBINING.length; ++r) { - fill(TABLE, 0, BMP_COMBINING[r][0], BMP_COMBINING[r][1] + 1); + fill(table, 0, BMP_COMBINING[r][0], BMP_COMBINING[r][1] + 1); } for (let r = 0; r < BMP_WIDE.length; ++r) { - fill(TABLE, 2, BMP_WIDE[r][0], BMP_WIDE[r][1] + 1); + fill(table, 2, BMP_WIDE[r][0], BMP_WIDE[r][1] + 1); } } } @@ -214,7 +214,7 @@ export class UnicodeV11 implements IUnicodeVersionProvider { public wcwidth(num: number): CharWidth { if (num < 32) return 0; if (num < 127) return 1; - if (num < 65536) return TABLE[num] as CharWidth; + if (num < 65536) return table[num] as CharWidth; if (bisearch(num, HIGH_COMBINING)) return 0; if (bisearch(num, HIGH_WIDE)) return 2; return 1; diff --git a/src/common/input/UnicodeV6.ts b/src/common/input/UnicodeV6.ts index 288e56920..d6bb74588 100644 --- a/src/common/input/UnicodeV6.ts +++ b/src/common/input/UnicodeV6.ts @@ -61,7 +61,7 @@ const HIGH_COMBINING = [ ]; // BMP lookup table, lazy initialized during first addon loading -let TABLE: Uint8Array; +let table: Uint8Array; function bisearch(ucs: number, data: number[][]): boolean { let min = 0; @@ -88,28 +88,28 @@ export class UnicodeV6 implements IUnicodeVersionProvider { constructor() { // init lookup table once - if (!TABLE) { - TABLE = new Uint8Array(65536); - fill(TABLE, 1); - TABLE[0] = 0; + if (!table) { + table = new Uint8Array(65536); + fill(table, 1); + table[0] = 0; // control chars - fill(TABLE, 0, 1, 32); - fill(TABLE, 0, 0x7f, 0xa0); + fill(table, 0, 1, 32); + fill(table, 0, 0x7f, 0xa0); // apply wide char rules first // wide chars - fill(TABLE, 2, 0x1100, 0x1160); - TABLE[0x2329] = 2; - TABLE[0x232a] = 2; - fill(TABLE, 2, 0x2e80, 0xa4d0); - TABLE[0x303f] = 1; // wrongly in last line + fill(table, 2, 0x1100, 0x1160); + table[0x2329] = 2; + table[0x232a] = 2; + fill(table, 2, 0x2e80, 0xa4d0); + table[0x303f] = 1; // wrongly in last line - fill(TABLE, 2, 0xac00, 0xd7a4); - fill(TABLE, 2, 0xf900, 0xfb00); - fill(TABLE, 2, 0xfe10, 0xfe1a); - fill(TABLE, 2, 0xfe30, 0xfe70); - fill(TABLE, 2, 0xff00, 0xff61); - fill(TABLE, 2, 0xffe0, 0xffe7); + fill(table, 2, 0xac00, 0xd7a4); + fill(table, 2, 0xf900, 0xfb00); + fill(table, 2, 0xfe10, 0xfe1a); + fill(table, 2, 0xfe30, 0xfe70); + fill(table, 2, 0xff00, 0xff61); + fill(table, 2, 0xffe0, 0xffe7); // apply combining last to ensure we overwrite // wrongly wide set chars: @@ -117,7 +117,7 @@ export class UnicodeV6 implements IUnicodeVersionProvider { // through to wide check so we simply do here the opposite // combining 0 for (let r = 0; r < BMP_COMBINING.length; ++r) { - fill(TABLE, 0, BMP_COMBINING[r][0], BMP_COMBINING[r][1] + 1); + fill(table, 0, BMP_COMBINING[r][0], BMP_COMBINING[r][1] + 1); } } } @@ -125,7 +125,7 @@ export class UnicodeV6 implements IUnicodeVersionProvider { public wcwidth(num: number): CharWidth { if (num < 32) return 0; if (num < 127) return 1; - if (num < 65536) return TABLE[num] as CharWidth; + if (num < 65536) return table[num] as CharWidth; if (bisearch(num, HIGH_COMBINING)) return 0; if ((num >= 0x20000 && num <= 0x2fffd) || (num >= 0x30000 && num <= 0x3fffd)) return 2; return 1; From 2b4da1f88e0660a7542c9727bc9f32bf62c7b395 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 21 Dec 2019 21:40:01 +0100 Subject: [PATCH 212/268] add wide char handling to bufferline primitives --- src/common/buffer/BufferLine.test.ts | 90 ++++++++++++++++++++++++++++ src/common/buffer/BufferLine.ts | 38 ++++++++++-- 2 files changed, 124 insertions(+), 4 deletions(-) diff --git a/src/common/buffer/BufferLine.test.ts b/src/common/buffer/BufferLine.test.ts index ae80aa16f..686371f25 100644 --- a/src/common/buffer/BufferLine.test.ts +++ b/src/common/buffer/BufferLine.test.ts @@ -366,4 +366,94 @@ describe('BufferLine', function(): void { chai.assert.equal(cell.isCombined(), Content.IS_COMBINED_MASK); }); }); + describe('correct fullwidth handling', () => { + function populate(line: BufferLine): void { + const cell = CellData.fromCharData([1, '¥', 2, '¥'.charCodeAt(0)]); + for (let i = 0; i < line.length; i += 2) { + line.setCell(i, cell); + } + } + it('insert - wide char at pos', () => { + const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false); + populate(line); + line.insertCells(9, 1, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + chai.assert.equal(line.translateToString(), '¥¥¥¥ a'); + line.insertCells(8, 1, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + chai.assert.equal(line.translateToString(), '¥¥¥¥a '); + line.insertCells(1, 1, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + chai.assert.equal(line.translateToString(), ' a ¥¥¥a'); + }); + it('insert - wide char at end', () => { + const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false); + populate(line); + line.insertCells(0, 3, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + chai.assert.equal(line.translateToString(), 'aaa¥¥¥ '); + line.insertCells(4, 1, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + chai.assert.equal(line.translateToString(), 'aaa a ¥¥'); + line.insertCells(4, 1, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + chai.assert.equal(line.translateToString(), 'aaa aa ¥ '); + }); + it('delete', () => { + const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false); + populate(line); + line.deleteCells(0, 1, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + chai.assert.equal(line.translateToString(), ' ¥¥¥¥a'); + line.deleteCells(5, 2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + chai.assert.equal(line.translateToString(), ' ¥¥¥aaa'); + line.deleteCells(0, 2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + chai.assert.equal(line.translateToString(), ' ¥¥aaaaa'); + }); + it('replace - start at 0', () => { + let line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false); + populate(line); + line.replaceCells(0, 1, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + chai.assert.equal(line.translateToString(), 'a ¥¥¥¥'); + line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false); + populate(line); + line.replaceCells(0, 2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + chai.assert.equal(line.translateToString(), 'aa¥¥¥¥'); + line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false); + populate(line); + line.replaceCells(0, 3, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + chai.assert.equal(line.translateToString(), 'aaa ¥¥¥'); + line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false); + populate(line); + line.replaceCells(0, 8, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + chai.assert.equal(line.translateToString(), 'aaaaaaaa¥'); + line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false); + populate(line); + line.replaceCells(0, 9, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + chai.assert.equal(line.translateToString(), 'aaaaaaaaa '); + line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false); + populate(line); + line.replaceCells(0, 10, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + chai.assert.equal(line.translateToString(), 'aaaaaaaaaa'); + }); + it('replace - start at 1', () => { + let line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false); + populate(line); + line.replaceCells(1, 2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + chai.assert.equal(line.translateToString(), ' a¥¥¥¥'); + line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false); + populate(line); + line.replaceCells(1, 3, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + chai.assert.equal(line.translateToString(), ' aa ¥¥¥'); + line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false); + populate(line); + line.replaceCells(1, 4, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + chai.assert.equal(line.translateToString(), ' aaa¥¥¥'); + line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false); + populate(line); + line.replaceCells(1, 8, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + chai.assert.equal(line.translateToString(), ' aaaaaaa¥'); + line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false); + populate(line); + line.replaceCells(1, 9, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + chai.assert.equal(line.translateToString(), ' aaaaaaaa '); + line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false); + populate(line); + line.replaceCells(1, 10, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + chai.assert.equal(line.translateToString(), ' aaaaaaaaa'); + }); + }); }); diff --git a/src/common/buffer/BufferLine.ts b/src/common/buffer/BufferLine.ts index 1e95e0045..d54b59aaf 100644 --- a/src/common/buffer/BufferLine.ts +++ b/src/common/buffer/BufferLine.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { CharData, IBufferLine, ICellData } from 'common/Types'; +import { CharData, IBufferLine, ICellData, IAttributeData } from 'common/Types'; import { stringFromCodePoint } from 'common/input/TextDecoder'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Content } from 'common/buffer/Constants'; import { CellData } from 'common/buffer/CellData'; @@ -228,8 +228,14 @@ export class BufferLine implements IBufferLine { } } - public insertCells(pos: number, n: number, fillCellData: ICellData): void { + public insertCells(pos: number, n: number, fillCellData: ICellData, eraseAttr?: IAttributeData): void { pos %= this.length; + + // handle fullwidth at pos: reset cell one to the left if pos is second cell of a wide char + if (pos && this.getWidth(pos - 1) === 2) { + this.setCellFromCodePoint(pos - 1, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0); + } + if (n < this.length - pos) { const cell = new CellData(); for (let i = this.length - pos - n - 1; i >= 0; --i) { @@ -243,9 +249,14 @@ export class BufferLine implements IBufferLine { this.setCell(i, fillCellData); } } + + // handle fullwidth at line end: reset last cell if it is first cell of a wide char + if (this.getWidth(this.length - 1) === 2) { + this.setCellFromCodePoint(this.length - 1, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0); + } } - public deleteCells(pos: number, n: number, fillCellData: ICellData): void { + public deleteCells(pos: number, n: number, fillCellData: ICellData, eraseAttr?: IAttributeData): void { pos %= this.length; if (n < this.length - pos) { const cell = new CellData(); @@ -260,9 +271,28 @@ export class BufferLine implements IBufferLine { this.setCell(i, fillCellData); } } + + // handle fullwidth at pos: + // - reset pos-1 if wide char + // - reset pos if width==0 (previous second cell of a wide char) + if (pos && this.getWidth(pos - 1) === 2) { + this.setCellFromCodePoint(pos - 1, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0); + } + if (this.getWidth(pos) === 0 && !this.hasContent(pos)) { + this.setCellFromCodePoint(pos, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0); + } } - public replaceCells(start: number, end: number, fillCellData: ICellData): void { + public replaceCells(start: number, end: number, fillCellData: ICellData, eraseAttr?: IAttributeData): void { + // handle fullwidth at start: reset cell one to the left if start is second cell of a wide char + if (start && this.getWidth(start - 1) === 2) { + this.setCellFromCodePoint(start - 1, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0); + } + // handle fullwidth at last cell + 1: reset to empty cell if it is second part of a wide char + if (end < this.length && this.getWidth(end - 1) === 2) { + this.setCellFromCodePoint(end, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0); + } + while (start < end && start < this.length) { this.setCell(start++, fillCellData); } From 7ec658a3f806db268d478e4e5c23bab3f7c559bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 21 Dec 2019 21:57:39 +0100 Subject: [PATCH 213/268] apply erase attrs in handler methods --- src/InputHandler.ts | 22 +++++++++++++--------- src/common/Types.d.ts | 6 +++--- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index ae44c31ce..963ce145c 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -473,7 +473,7 @@ export class InputHandler extends Disposable implements IInputHandler { // insert mode: move characters to right if (insertMode) { // right shift cells according to the width - bufferRow.insertCells(buffer.x, chWidth, buffer.getNullCell(curAttr)); + bufferRow.insertCells(buffer.x, chWidth, buffer.getNullCell(curAttr), curAttr); // test last cell - since the last cell has only room for // a halfwidth char any fullwidth shifted there is lost // and will be set to empty cell @@ -855,7 +855,8 @@ export class InputHandler extends Disposable implements IInputHandler { line.replaceCells( start, end, - this._bufferService.buffer.getNullCell(this._eraseAttrData()) + this._bufferService.buffer.getNullCell(this._eraseAttrData()), + this._eraseAttrData() ); if (clearWrap) { line.isWrapped = false; @@ -1033,7 +1034,8 @@ export class InputHandler extends Disposable implements IInputHandler { line.insertCells( this._bufferService.buffer.x, params.params[0] || 1, - this._bufferService.buffer.getNullCell(this._eraseAttrData()) + this._bufferService.buffer.getNullCell(this._eraseAttrData()), + this._eraseAttrData() ); this._dirtyRowService.markDirty(this._bufferService.buffer.y); } @@ -1050,7 +1052,8 @@ export class InputHandler extends Disposable implements IInputHandler { line.deleteCells( this._bufferService.buffer.x, params.params[0] || 1, - this._bufferService.buffer.getNullCell(this._eraseAttrData()) + this._bufferService.buffer.getNullCell(this._eraseAttrData()), + this._eraseAttrData() ); this._dirtyRowService.markDirty(this._bufferService.buffer.y); } @@ -1110,7 +1113,7 @@ export class InputHandler extends Disposable implements IInputHandler { const param = params.params[0] || 1; for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) { const line = buffer.lines.get(buffer.ybase + y); - line.deleteCells(0, param, buffer.getNullCell(this._eraseAttrData())); + line.deleteCells(0, param, buffer.getNullCell(this._eraseAttrData()), this._eraseAttrData()); line.isWrapped = false; } this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom); @@ -1138,7 +1141,7 @@ export class InputHandler extends Disposable implements IInputHandler { const param = params.params[0] || 1; for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) { const line = buffer.lines.get(buffer.ybase + y); - line.insertCells(0, param, buffer.getNullCell(this._eraseAttrData())); + line.insertCells(0, param, buffer.getNullCell(this._eraseAttrData()), this._eraseAttrData()); line.isWrapped = false; } this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom); @@ -1156,7 +1159,7 @@ export class InputHandler extends Disposable implements IInputHandler { const param = params.params[0] || 1; for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) { const line = this._bufferService.buffer.lines.get(buffer.ybase + y); - line.insertCells(buffer.x, param, buffer.getNullCell(this._eraseAttrData())); + line.insertCells(buffer.x, param, buffer.getNullCell(this._eraseAttrData()), this._eraseAttrData()); line.isWrapped = false; } this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom); @@ -1174,7 +1177,7 @@ export class InputHandler extends Disposable implements IInputHandler { const param = params.params[0] || 1; for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) { const line = buffer.lines.get(buffer.ybase + y); - line.deleteCells(buffer.x, param, buffer.getNullCell(this._eraseAttrData())); + line.deleteCells(buffer.x, param, buffer.getNullCell(this._eraseAttrData()), this._eraseAttrData()); line.isWrapped = false; } this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom); @@ -1191,7 +1194,8 @@ export class InputHandler extends Disposable implements IInputHandler { line.replaceCells( this._bufferService.buffer.x, this._bufferService.buffer.x + (params.params[0] || 1), - this._bufferService.buffer.getNullCell(this._eraseAttrData()) + this._bufferService.buffer.getNullCell(this._eraseAttrData()), + this._eraseAttrData() ); this._dirtyRowService.markDirty(this._bufferService.buffer.y); } diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index 9b8a18b5a..fdb5eb739 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -123,9 +123,9 @@ export interface IBufferLine { setCell(index: number, cell: ICellData): void; setCellFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number): void; addCodepointToCell(index: number, codePoint: number): void; - insertCells(pos: number, n: number, ch: ICellData): void; - deleteCells(pos: number, n: number, fill: ICellData): void; - replaceCells(start: number, end: number, fill: ICellData): void; + insertCells(pos: number, n: number, ch: ICellData, eraseAttr?: IAttributeData): void; + deleteCells(pos: number, n: number, fill: ICellData, eraseAttr?: IAttributeData): void; + replaceCells(start: number, end: number, fill: ICellData, eraseAttr?: IAttributeData): void; resize(cols: number, fill: ICellData): void; fill(fillCellData: ICellData): void; copyFrom(line: IBufferLine): void; From 0edbbf9fe0c4a331ae8a9fc39d0f3dbb27856d76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 21 Dec 2019 23:07:12 +0100 Subject: [PATCH 214/268] fix print handler; tests --- src/InputHandler.test.ts | 59 ++++++++++++++++++++++++++++++++++++++++ src/InputHandler.ts | 12 ++++++++ 2 files changed, 71 insertions(+) diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index ec3699cef..b7eeb352c 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -1270,4 +1270,63 @@ describe('InputHandler', () => { [131072, 131072], [131072, 131072], [131072, 300000 - 131072 - 131072] ]); }); + describe('should correctly reset cells taken by wide chars', () => { + let term: TestTerminal; + beforeEach(() => { + term = new TestTerminal({cols: 10, rows: 5, scrollback: 1}); + term.writeSync('¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥'); + }); + it('print', () => { + term.writeSync('\x1b[H#'); + assert.deepEqual(getLines(term), ['# ¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); + term.writeSync('\x1b[1;6H######'); + assert.deepEqual(getLines(term), ['# ¥ #####', '# ¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); + term.writeSync('#'); + assert.deepEqual(getLines(term), ['# ¥ #####', '##¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); + term.writeSync('#'); + assert.deepEqual(getLines(term), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); + term.writeSync('\x1b[3;9H#'); + assert.deepEqual(getLines(term), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥#', '¥¥¥¥¥', '']); + term.writeSync('#'); + assert.deepEqual(getLines(term), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥##', '¥¥¥¥¥', '']); + term.writeSync('#'); + assert.deepEqual(getLines(term), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥##', '# ¥¥¥¥', '']); + term.writeSync('\x1b[4;10H#'); + assert.deepEqual(getLines(term), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥##', '# ¥¥¥ #', '']); + }); + it('EL', () => { + term.writeSync('\x1b[1;6H\x1b[K#'); + assert.deepEqual(getLines(term), ['¥¥ #', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); + term.writeSync('\x1b[2;5H\x1b[1K'); + assert.deepEqual(getLines(term), ['¥¥ #', ' ¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); + term.writeSync('\x1b[3;6H\x1b[1K'); + assert.deepEqual(getLines(term), ['¥¥ #', ' ¥¥', ' ¥¥', '¥¥¥¥¥', '']); + }); + it('ICH', () => { + term.writeSync('\x1b[1;6H\x1b[@'); + assert.deepEqual(getLines(term), ['¥¥ ¥', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); + term.writeSync('\x1b[2;4H\x1b[2@'); + assert.deepEqual(getLines(term), ['¥¥ ¥', '¥ ¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); + term.writeSync('\x1b[3;4H\x1b[3@'); + assert.deepEqual(getLines(term), ['¥¥ ¥', '¥ ¥¥', '¥ ¥', '¥¥¥¥¥', '']); + term.writeSync('\x1b[4;4H\x1b[4@'); + assert.deepEqual(getLines(term), ['¥¥ ¥', '¥ ¥¥', '¥ ¥', '¥ ¥', '']); + }); + it('DCH', () => { + term.writeSync('\x1b[1;6H\x1b[P'); + assert.deepEqual(getLines(term), ['¥¥ ¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); + term.writeSync('\x1b[2;6H\x1b[2P'); + assert.deepEqual(getLines(term), ['¥¥ ¥¥', '¥¥ ¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); + term.writeSync('\x1b[3;6H\x1b[3P'); + assert.deepEqual(getLines(term), ['¥¥ ¥¥', '¥¥ ¥', '¥¥ ¥', '¥¥¥¥¥', '']); + }); + it('ECH', () => { + term.writeSync('\x1b[1;6H\x1b[X'); + assert.deepEqual(getLines(term), ['¥¥ ¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); + term.writeSync('\x1b[2;6H\x1b[2X'); + assert.deepEqual(getLines(term), ['¥¥ ¥¥', '¥¥ ¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); + term.writeSync('\x1b[3;6H\x1b[3X'); + assert.deepEqual(getLines(term), ['¥¥ ¥¥', '¥¥ ¥', '¥¥ ¥', '¥¥¥¥¥', '']); + }); + }); }); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 963ce145c..0124f4994 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -398,6 +398,12 @@ export class InputHandler extends Disposable implements IInputHandler { let bufferRow = buffer.lines.get(buffer.y + buffer.ybase); this._dirtyRowService.markDirty(buffer.y); + + // handle wide chars: reset start_cell-1 if we would overwrite the second cell of a wide char + if (buffer.x && bufferRow.getWidth(buffer.x - 1) === 2) { + bufferRow.setCellFromCodePoint(buffer.x - 1, 0, 1, curAttr.fg, curAttr.bg); + } + for (let pos = start; pos < end; ++pos) { code = data[pos]; @@ -509,6 +515,12 @@ export class InputHandler extends Disposable implements IInputHandler { this._parser.precedingCodepoint = this._workCell.content; } } + + // handle wide chars: reset cell to the right if is second cell of a wide char + if (buffer.x < cols && bufferRow.getWidth(buffer.x) === 0 && !bufferRow.hasContent(buffer.x)) { + bufferRow.setCellFromCodePoint(buffer.x, 0, 1, curAttr.fg, curAttr.bg); + } + this._dirtyRowService.markDirty(buffer.y); } From ee741308069ae6401691829e31c55e7105412c92 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 26 Dec 2019 12:12:08 +1100 Subject: [PATCH 215/268] Standardize how colors helper lib is structured Fixes #2604 --- .../src/atlas/WebglCharAtlas.ts | 8 +- src/browser/Color.test.ts | 451 +++++++++--------- src/browser/Color.ts | 329 +++++++------ src/browser/ColorManager.ts | 60 +-- src/browser/renderer/BaseRenderLayer.ts | 8 +- .../renderer/atlas/DynamicCharAtlas.ts | 4 +- src/browser/renderer/dom/DomRenderer.ts | 4 +- .../dom/DomRendererRowFactory.test.ts | 38 +- .../renderer/dom/DomRendererRowFactory.ts | 6 +- 9 files changed, 473 insertions(+), 435 deletions(-) diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index c96c2a695..429669ee8 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -11,7 +11,7 @@ import { throwIfFalsy } from '../WebglUtils'; import { IColor } from 'browser/Types'; import { IDisposable } from 'xterm'; import { AttributeData } from 'common/buffer/AttributeData'; -import { toCss, ensureContrastRatioRgba } from 'browser/Color'; +import { channels, rgba } from 'browser/Color'; // In practice we're probably never going to exhaust a texture this large. For debugging purposes, // however, it can be useful to set this to a really tiny value, to verify that LRU eviction works. @@ -224,7 +224,7 @@ export class WebglCharAtlas implements IDisposable { return this._getColorFromAnsiIndex(fgColor).css; case Attributes.CM_RGB: const arr = AttributeData.toColorRGB(fgColor); - return toCss(arr[0], arr[1], arr[2]); + return channels.toCss(arr[0], arr[1], arr[2]); case Attributes.CM_DEFAULT: default: if (inverse) { @@ -287,14 +287,14 @@ export class WebglCharAtlas implements IDisposable { const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, inverse); const fgRgba = this._resolveForegroundRgba(fgColorMode, fgColor, inverse, bold); - const result = ensureContrastRatioRgba(bgRgba, fgRgba, this._config.minimumContrastRatio); + const result = rgba.ensureContrastRatio(bgRgba, fgRgba, this._config.minimumContrastRatio); if (!result) { this._config.colors.contrastCache.setCss(bg, fg, null); return undefined; } - const css = toCss( + const css = channels.toCss( (result >> 24) & 0xFF, (result >> 16) & 0xFF, (result >> 8) & 0xFF diff --git a/src/browser/Color.test.ts b/src/browser/Color.test.ts index cbeeaba95..ff8da246f 100644 --- a/src/browser/Color.test.ts +++ b/src/browser/Color.test.ts @@ -4,50 +4,243 @@ */ import { assert } from 'chai'; -import { blend, fromCss, toPaddedHex, toCss, toRgba, fromRgba, opaque, rgbRelativeLuminance, contrastRatio, ensureContrastRatioRgba } from 'browser/Color'; +import { channels, color, css, rgb, rgba, toPaddedHex, contrastRatio } from 'browser/Color'; describe('Color', () => { - describe('blend', () => { - it('should blend colors based on the alpha channel', () => { - assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF00', rgba: 0xFFFFFF00 }), { css: '#000000', rgba: 0x000000FF }); - assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF10', rgba: 0xFFFFFF10 }), { css: '#101010', rgba: 0x101010FF }); - assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF20', rgba: 0xFFFFFF20 }), { css: '#202020', rgba: 0x202020FF }); - assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF30', rgba: 0xFFFFFF30 }), { css: '#303030', rgba: 0x303030FF }); - assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF40', rgba: 0xFFFFFF40 }), { css: '#404040', rgba: 0x404040FF }); - assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF50', rgba: 0xFFFFFF50 }), { css: '#505050', rgba: 0x505050FF }); - assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF60', rgba: 0xFFFFFF60 }), { css: '#606060', rgba: 0x606060FF }); - assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF70', rgba: 0xFFFFFF70 }), { css: '#707070', rgba: 0x707070FF }); - assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF80', rgba: 0xFFFFFF80 }), { css: '#808080', rgba: 0x808080FF }); - assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF90', rgba: 0xFFFFFF90 }), { css: '#909090', rgba: 0x909090FF }); - assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFA0', rgba: 0xFFFFFFA0 }), { css: '#a0a0a0', rgba: 0xA0A0A0FF }); - assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFB0', rgba: 0xFFFFFFB0 }), { css: '#b0b0b0', rgba: 0xB0B0B0FF }); - assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFC0', rgba: 0xFFFFFFC0 }), { css: '#c0c0c0', rgba: 0xC0C0C0FF }); - assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFD0', rgba: 0xFFFFFFD0 }), { css: '#d0d0d0', rgba: 0xD0D0D0FF }); - assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFE0', rgba: 0xFFFFFFE0 }), { css: '#e0e0e0', rgba: 0xE0E0E0FF }); - assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFF0', rgba: 0xFFFFFFF0 }), { css: '#f0f0f0', rgba: 0xF0F0F0FF }); - assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFFF', rgba: 0xFFFFFFFF }), { css: '#FFFFFFFF', rgba: 0xFFFFFFFF }); + + describe('channels', () => { + describe('toCss', () => { + it('should convert an rgb array to css hex string', () => { + assert.equal(channels.toCss(0x00, 0x00, 0x00), '#000000'); + assert.equal(channels.toCss(0x10, 0x10, 0x10), '#101010'); + assert.equal(channels.toCss(0x20, 0x20, 0x20), '#202020'); + assert.equal(channels.toCss(0x30, 0x30, 0x30), '#303030'); + assert.equal(channels.toCss(0x40, 0x40, 0x40), '#404040'); + assert.equal(channels.toCss(0x50, 0x50, 0x50), '#505050'); + assert.equal(channels.toCss(0x60, 0x60, 0x60), '#606060'); + assert.equal(channels.toCss(0x70, 0x70, 0x70), '#707070'); + assert.equal(channels.toCss(0x80, 0x80, 0x80), '#808080'); + assert.equal(channels.toCss(0x90, 0x90, 0x90), '#909090'); + assert.equal(channels.toCss(0xa0, 0xa0, 0xa0), '#a0a0a0'); + assert.equal(channels.toCss(0xb0, 0xb0, 0xb0), '#b0b0b0'); + assert.equal(channels.toCss(0xc0, 0xc0, 0xc0), '#c0c0c0'); + assert.equal(channels.toCss(0xd0, 0xd0, 0xd0), '#d0d0d0'); + assert.equal(channels.toCss(0xe0, 0xe0, 0xe0), '#e0e0e0'); + assert.equal(channels.toCss(0xf0, 0xf0, 0xf0), '#f0f0f0'); + assert.equal(channels.toCss(0xff, 0xff, 0xff), '#ffffff'); + }); + }); + + describe('toRgba', () => { + it('should convert an rgb array to an rgba number', () => { + assert.equal(channels.toRgba(0x00, 0x00, 0x00), 0x000000FF); + assert.equal(channels.toRgba(0x10, 0x10, 0x10), 0x101010FF); + assert.equal(channels.toRgba(0x20, 0x20, 0x20), 0x202020FF); + assert.equal(channels.toRgba(0x30, 0x30, 0x30), 0x303030FF); + assert.equal(channels.toRgba(0x40, 0x40, 0x40), 0x404040FF); + assert.equal(channels.toRgba(0x50, 0x50, 0x50), 0x505050FF); + assert.equal(channels.toRgba(0x60, 0x60, 0x60), 0x606060FF); + assert.equal(channels.toRgba(0x70, 0x70, 0x70), 0x707070FF); + assert.equal(channels.toRgba(0x80, 0x80, 0x80), 0x808080FF); + assert.equal(channels.toRgba(0x90, 0x90, 0x90), 0x909090FF); + assert.equal(channels.toRgba(0xa0, 0xa0, 0xa0), 0xa0a0a0FF); + assert.equal(channels.toRgba(0xb0, 0xb0, 0xb0), 0xb0b0b0FF); + assert.equal(channels.toRgba(0xc0, 0xc0, 0xc0), 0xc0c0c0FF); + assert.equal(channels.toRgba(0xd0, 0xd0, 0xd0), 0xd0d0d0FF); + assert.equal(channels.toRgba(0xe0, 0xe0, 0xe0), 0xe0e0e0FF); + assert.equal(channels.toRgba(0xf0, 0xf0, 0xf0), 0xf0f0f0FF); + assert.equal(channels.toRgba(0xff, 0xff, 0xff), 0xffffffFF); + }); + it('should convert an rgba array to an rgba number', () => { + assert.equal(channels.toRgba(0x00, 0x00, 0x00, 0x00), 0x00000000); + assert.equal(channels.toRgba(0x10, 0x10, 0x10, 0x10), 0x10101010); + assert.equal(channels.toRgba(0x20, 0x20, 0x20, 0x20), 0x20202020); + assert.equal(channels.toRgba(0x30, 0x30, 0x30, 0x30), 0x30303030); + assert.equal(channels.toRgba(0x40, 0x40, 0x40, 0x40), 0x40404040); + assert.equal(channels.toRgba(0x50, 0x50, 0x50, 0x50), 0x50505050); + assert.equal(channels.toRgba(0x60, 0x60, 0x60, 0x60), 0x60606060); + assert.equal(channels.toRgba(0x70, 0x70, 0x70, 0x70), 0x70707070); + assert.equal(channels.toRgba(0x80, 0x80, 0x80, 0x80), 0x80808080); + assert.equal(channels.toRgba(0x90, 0x90, 0x90, 0x90), 0x90909090); + assert.equal(channels.toRgba(0xa0, 0xa0, 0xa0, 0xa0), 0xa0a0a0a0); + assert.equal(channels.toRgba(0xb0, 0xb0, 0xb0, 0xb0), 0xb0b0b0b0); + assert.equal(channels.toRgba(0xc0, 0xc0, 0xc0, 0xc0), 0xc0c0c0c0); + assert.equal(channels.toRgba(0xd0, 0xd0, 0xd0, 0xd0), 0xd0d0d0d0); + assert.equal(channels.toRgba(0xe0, 0xe0, 0xe0, 0xe0), 0xe0e0e0e0); + assert.equal(channels.toRgba(0xf0, 0xf0, 0xf0, 0xf0), 0xf0f0f0f0); + assert.equal(channels.toRgba(0xff, 0xff, 0xff, 0xff), 0xffffffff); + }); }); }); - describe('fromCss', () => { - it('should covert a CSS string to an IColor', () => { - assert.deepEqual(fromCss('#000000'), { css: '#000000', rgba: 0x000000FF }); - assert.deepEqual(fromCss('#101010'), { css: '#101010', rgba: 0x101010FF }); - assert.deepEqual(fromCss('#202020'), { css: '#202020', rgba: 0x202020FF }); - assert.deepEqual(fromCss('#303030'), { css: '#303030', rgba: 0x303030FF }); - assert.deepEqual(fromCss('#404040'), { css: '#404040', rgba: 0x404040FF }); - assert.deepEqual(fromCss('#505050'), { css: '#505050', rgba: 0x505050FF }); - assert.deepEqual(fromCss('#606060'), { css: '#606060', rgba: 0x606060FF }); - assert.deepEqual(fromCss('#707070'), { css: '#707070', rgba: 0x707070FF }); - assert.deepEqual(fromCss('#808080'), { css: '#808080', rgba: 0x808080FF }); - assert.deepEqual(fromCss('#909090'), { css: '#909090', rgba: 0x909090FF }); - assert.deepEqual(fromCss('#a0a0a0'), { css: '#a0a0a0', rgba: 0xa0a0a0FF }); - assert.deepEqual(fromCss('#b0b0b0'), { css: '#b0b0b0', rgba: 0xb0b0b0FF }); - assert.deepEqual(fromCss('#c0c0c0'), { css: '#c0c0c0', rgba: 0xc0c0c0FF }); - assert.deepEqual(fromCss('#d0d0d0'), { css: '#d0d0d0', rgba: 0xd0d0d0FF }); - assert.deepEqual(fromCss('#e0e0e0'), { css: '#e0e0e0', rgba: 0xe0e0e0FF }); - assert.deepEqual(fromCss('#f0f0f0'), { css: '#f0f0f0', rgba: 0xf0f0f0FF }); - assert.deepEqual(fromCss('#ffffff'), { css: '#ffffff', rgba: 0xffffffFF }); + describe('color', () => { + describe('blend', () => { + it('should blend colors based on the alpha channel', () => { + assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF00', rgba: 0xFFFFFF00 }), { css: '#000000', rgba: 0x000000FF }); + assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF10', rgba: 0xFFFFFF10 }), { css: '#101010', rgba: 0x101010FF }); + assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF20', rgba: 0xFFFFFF20 }), { css: '#202020', rgba: 0x202020FF }); + assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF30', rgba: 0xFFFFFF30 }), { css: '#303030', rgba: 0x303030FF }); + assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF40', rgba: 0xFFFFFF40 }), { css: '#404040', rgba: 0x404040FF }); + assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF50', rgba: 0xFFFFFF50 }), { css: '#505050', rgba: 0x505050FF }); + assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF60', rgba: 0xFFFFFF60 }), { css: '#606060', rgba: 0x606060FF }); + assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF70', rgba: 0xFFFFFF70 }), { css: '#707070', rgba: 0x707070FF }); + assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF80', rgba: 0xFFFFFF80 }), { css: '#808080', rgba: 0x808080FF }); + assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF90', rgba: 0xFFFFFF90 }), { css: '#909090', rgba: 0x909090FF }); + assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFA0', rgba: 0xFFFFFFA0 }), { css: '#a0a0a0', rgba: 0xA0A0A0FF }); + assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFB0', rgba: 0xFFFFFFB0 }), { css: '#b0b0b0', rgba: 0xB0B0B0FF }); + assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFC0', rgba: 0xFFFFFFC0 }), { css: '#c0c0c0', rgba: 0xC0C0C0FF }); + assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFD0', rgba: 0xFFFFFFD0 }), { css: '#d0d0d0', rgba: 0xD0D0D0FF }); + assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFE0', rgba: 0xFFFFFFE0 }), { css: '#e0e0e0', rgba: 0xE0E0E0FF }); + assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFF0', rgba: 0xFFFFFFF0 }), { css: '#f0f0f0', rgba: 0xF0F0F0FF }); + assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFFF', rgba: 0xFFFFFFFF }), { css: '#FFFFFFFF', rgba: 0xFFFFFFFF }); + }); + }); + + describe('opaque', () => { + it('should make the color opaque', () => { + assert.deepEqual(color.opaque({ css: '#00000000', rgba: 0x00000000 }), { css: '#000000', rgba: 0x000000FF }); + assert.deepEqual(color.opaque({ css: '#10101010', rgba: 0x10101010 }), { css: '#101010', rgba: 0x101010FF }); + assert.deepEqual(color.opaque({ css: '#20202020', rgba: 0x20202020 }), { css: '#202020', rgba: 0x202020FF }); + assert.deepEqual(color.opaque({ css: '#30303030', rgba: 0x30303030 }), { css: '#303030', rgba: 0x303030FF }); + assert.deepEqual(color.opaque({ css: '#40404040', rgba: 0x40404040 }), { css: '#404040', rgba: 0x404040FF }); + assert.deepEqual(color.opaque({ css: '#50505050', rgba: 0x50505050 }), { css: '#505050', rgba: 0x505050FF }); + assert.deepEqual(color.opaque({ css: '#60606060', rgba: 0x60606060 }), { css: '#606060', rgba: 0x606060FF }); + assert.deepEqual(color.opaque({ css: '#70707070', rgba: 0x70707070 }), { css: '#707070', rgba: 0x707070FF }); + assert.deepEqual(color.opaque({ css: '#80808080', rgba: 0x80808080 }), { css: '#808080', rgba: 0x808080FF }); + assert.deepEqual(color.opaque({ css: '#90909090', rgba: 0x90909090 }), { css: '#909090', rgba: 0x909090FF }); + assert.deepEqual(color.opaque({ css: '#a0a0a0a0', rgba: 0xa0a0a0a0 }), { css: '#a0a0a0', rgba: 0xa0a0a0FF }); + assert.deepEqual(color.opaque({ css: '#b0b0b0b0', rgba: 0xb0b0b0b0 }), { css: '#b0b0b0', rgba: 0xb0b0b0FF }); + assert.deepEqual(color.opaque({ css: '#c0c0c0c0', rgba: 0xc0c0c0c0 }), { css: '#c0c0c0', rgba: 0xc0c0c0FF }); + assert.deepEqual(color.opaque({ css: '#d0d0d0d0', rgba: 0xd0d0d0d0 }), { css: '#d0d0d0', rgba: 0xd0d0d0FF }); + assert.deepEqual(color.opaque({ css: '#e0e0e0e0', rgba: 0xe0e0e0e0 }), { css: '#e0e0e0', rgba: 0xe0e0e0FF }); + assert.deepEqual(color.opaque({ css: '#f0f0f0f0', rgba: 0xf0f0f0f0 }), { css: '#f0f0f0', rgba: 0xf0f0f0FF }); + assert.deepEqual(color.opaque({ css: '#ffffffff', rgba: 0xffffffff }), { css: '#ffffff', rgba: 0xffffffFF }); + }); + }); + }); + + describe('css', () => { + describe('toColor', () => { + it('should covert a CSS string to an IColor', () => { + assert.deepEqual(css.toColor('#000000'), { css: '#000000', rgba: 0x000000FF }); + assert.deepEqual(css.toColor('#101010'), { css: '#101010', rgba: 0x101010FF }); + assert.deepEqual(css.toColor('#202020'), { css: '#202020', rgba: 0x202020FF }); + assert.deepEqual(css.toColor('#303030'), { css: '#303030', rgba: 0x303030FF }); + assert.deepEqual(css.toColor('#404040'), { css: '#404040', rgba: 0x404040FF }); + assert.deepEqual(css.toColor('#505050'), { css: '#505050', rgba: 0x505050FF }); + assert.deepEqual(css.toColor('#606060'), { css: '#606060', rgba: 0x606060FF }); + assert.deepEqual(css.toColor('#707070'), { css: '#707070', rgba: 0x707070FF }); + assert.deepEqual(css.toColor('#808080'), { css: '#808080', rgba: 0x808080FF }); + assert.deepEqual(css.toColor('#909090'), { css: '#909090', rgba: 0x909090FF }); + assert.deepEqual(css.toColor('#a0a0a0'), { css: '#a0a0a0', rgba: 0xa0a0a0FF }); + assert.deepEqual(css.toColor('#b0b0b0'), { css: '#b0b0b0', rgba: 0xb0b0b0FF }); + assert.deepEqual(css.toColor('#c0c0c0'), { css: '#c0c0c0', rgba: 0xc0c0c0FF }); + assert.deepEqual(css.toColor('#d0d0d0'), { css: '#d0d0d0', rgba: 0xd0d0d0FF }); + assert.deepEqual(css.toColor('#e0e0e0'), { css: '#e0e0e0', rgba: 0xe0e0e0FF }); + assert.deepEqual(css.toColor('#f0f0f0'), { css: '#f0f0f0', rgba: 0xf0f0f0FF }); + assert.deepEqual(css.toColor('#ffffff'), { css: '#ffffff', rgba: 0xffffffFF }); + }); + }); + }); + + describe('rgb', () => { + describe('relativeLuminance', () => { + it('should calculate the relative luminance of the color', () => { + assert.equal(rgb.relativeLuminance(0x000000), 0); + assert.equal(rgb.relativeLuminance(0x101010).toFixed(4), '0.0052'); + assert.equal(rgb.relativeLuminance(0x202020).toFixed(4), '0.0144'); + assert.equal(rgb.relativeLuminance(0x303030).toFixed(4), '0.0296'); + assert.equal(rgb.relativeLuminance(0x404040).toFixed(4), '0.0513'); + assert.equal(rgb.relativeLuminance(0x505050).toFixed(4), '0.0802'); + assert.equal(rgb.relativeLuminance(0x606060).toFixed(4), '0.1170'); + assert.equal(rgb.relativeLuminance(0x707070).toFixed(4), '0.1620'); + assert.equal(rgb.relativeLuminance(0x808080).toFixed(4), '0.2159'); + assert.equal(rgb.relativeLuminance(0x909090).toFixed(4), '0.2789'); + assert.equal(rgb.relativeLuminance(0xA0A0A0).toFixed(4), '0.3515'); + assert.equal(rgb.relativeLuminance(0xB0B0B0).toFixed(4), '0.4342'); + assert.equal(rgb.relativeLuminance(0xC0C0C0).toFixed(4), '0.5271'); + assert.equal(rgb.relativeLuminance(0xD0D0D0).toFixed(4), '0.6308'); + assert.equal(rgb.relativeLuminance(0xE0E0E0).toFixed(4), '0.7454'); + assert.equal(rgb.relativeLuminance(0xF0F0F0).toFixed(4), '0.8714'); + assert.equal(rgb.relativeLuminance(0xFFFFFF), 1); + }); + }); + }); + + describe('rgba', () => { + describe('ensureContrastRatio', () => { + it('should return undefined if the color already meets the contrast ratio (black bg)', () => { + assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 1), undefined); + assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 2), undefined); + assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 3), undefined); + }); + it('should return a color that meets the contrast ratio (black bg)', () => { + assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 4), 0x707070ff); + assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 5), 0x7f7f7fff); + assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 6), 0x8c8c8cff); + assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 7), 0x989898ff); + assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 8), 0xa3a3a3ff); + assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 9), 0xadadadff); + assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 10), 0xb6b6b6ff); + assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 11), 0xbebebeff); + assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 12), 0xc5c5c5ff); + assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 13), 0xd1d1d1ff); + assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 14), 0xd6d6d6ff); + assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 15), 0xdbdbdbff); + assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 16), 0xe3e3e3ff); + assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 17), 0xe9e9e9ff); + assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 18), 0xeeeeeeff); + assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 19), 0xf4f4f4ff); + assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 20), 0xfafafaff); + assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 21), 0xffffffff); + }); + it('should return undefined if the color already meets the contrast ratio (white bg)', () => { + assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 1), undefined); + assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 2), undefined); + assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 3), undefined); + assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 4), undefined); + assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 5), undefined); + assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 6), undefined); + }); + it('should return a color that meets the contrast ratio (white bg)', () => { + assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 7), 0x565656ff); + assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 8), 0x4d4d4dff); + assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 9), 0x454545ff); + assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 10), 0x3e3e3eff); + assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 11), 0x373737ff); + assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 12), 0x313131ff); + assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 13), 0x313131ff); + assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 14), 0x272727ff); + assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 15), 0x232323ff); + assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 16), 0x1f1f1fff); + assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 17), 0x1b1b1bff); + assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 18), 0x151515ff); + assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 19), 0x101010ff); + assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 20), 0x080808ff); + assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 21), 0x000000ff); + }); + }); + + describe('toChannels', () => { + it('should convert an rgba number to an rgba array', () => { + assert.deepEqual(rgba.toChannels(0x00000000), [0x00, 0x00, 0x00, 0x00]); + assert.deepEqual(rgba.toChannels(0x10101010), [0x10, 0x10, 0x10, 0x10]); + assert.deepEqual(rgba.toChannels(0x20202020), [0x20, 0x20, 0x20, 0x20]); + assert.deepEqual(rgba.toChannels(0x30303030), [0x30, 0x30, 0x30, 0x30]); + assert.deepEqual(rgba.toChannels(0x40404040), [0x40, 0x40, 0x40, 0x40]); + assert.deepEqual(rgba.toChannels(0x50505050), [0x50, 0x50, 0x50, 0x50]); + assert.deepEqual(rgba.toChannels(0x60606060), [0x60, 0x60, 0x60, 0x60]); + assert.deepEqual(rgba.toChannels(0x70707070), [0x70, 0x70, 0x70, 0x70]); + assert.deepEqual(rgba.toChannels(0x80808080), [0x80, 0x80, 0x80, 0x80]); + assert.deepEqual(rgba.toChannels(0x90909090), [0x90, 0x90, 0x90, 0x90]); + assert.deepEqual(rgba.toChannels(0xa0a0a0a0), [0xa0, 0xa0, 0xa0, 0xa0]); + assert.deepEqual(rgba.toChannels(0xb0b0b0b0), [0xb0, 0xb0, 0xb0, 0xb0]); + assert.deepEqual(rgba.toChannels(0xc0c0c0c0), [0xc0, 0xc0, 0xc0, 0xc0]); + assert.deepEqual(rgba.toChannels(0xd0d0d0d0), [0xd0, 0xd0, 0xd0, 0xd0]); + assert.deepEqual(rgba.toChannels(0xe0e0e0e0), [0xe0, 0xe0, 0xe0, 0xe0]); + assert.deepEqual(rgba.toChannels(0xf0f0f0f0), [0xf0, 0xf0, 0xf0, 0xf0]); + assert.deepEqual(rgba.toChannels(0xffffffff), [0xff, 0xff, 0xff, 0xff]); + }); }); }); @@ -73,134 +266,6 @@ describe('Color', () => { }); }); - describe('toCss', () => { - it('should convert an rgb array to css hex string', () => { - assert.equal(toCss(0x00, 0x00, 0x00), '#000000'); - assert.equal(toCss(0x10, 0x10, 0x10), '#101010'); - assert.equal(toCss(0x20, 0x20, 0x20), '#202020'); - assert.equal(toCss(0x30, 0x30, 0x30), '#303030'); - assert.equal(toCss(0x40, 0x40, 0x40), '#404040'); - assert.equal(toCss(0x50, 0x50, 0x50), '#505050'); - assert.equal(toCss(0x60, 0x60, 0x60), '#606060'); - assert.equal(toCss(0x70, 0x70, 0x70), '#707070'); - assert.equal(toCss(0x80, 0x80, 0x80), '#808080'); - assert.equal(toCss(0x90, 0x90, 0x90), '#909090'); - assert.equal(toCss(0xa0, 0xa0, 0xa0), '#a0a0a0'); - assert.equal(toCss(0xb0, 0xb0, 0xb0), '#b0b0b0'); - assert.equal(toCss(0xc0, 0xc0, 0xc0), '#c0c0c0'); - assert.equal(toCss(0xd0, 0xd0, 0xd0), '#d0d0d0'); - assert.equal(toCss(0xe0, 0xe0, 0xe0), '#e0e0e0'); - assert.equal(toCss(0xf0, 0xf0, 0xf0), '#f0f0f0'); - assert.equal(toCss(0xff, 0xff, 0xff), '#ffffff'); - }); - }); - - describe('toRgba', () => { - it('should convert an rgb array to an rgba number', () => { - assert.equal(toRgba(0x00, 0x00, 0x00), 0x000000FF); - assert.equal(toRgba(0x10, 0x10, 0x10), 0x101010FF); - assert.equal(toRgba(0x20, 0x20, 0x20), 0x202020FF); - assert.equal(toRgba(0x30, 0x30, 0x30), 0x303030FF); - assert.equal(toRgba(0x40, 0x40, 0x40), 0x404040FF); - assert.equal(toRgba(0x50, 0x50, 0x50), 0x505050FF); - assert.equal(toRgba(0x60, 0x60, 0x60), 0x606060FF); - assert.equal(toRgba(0x70, 0x70, 0x70), 0x707070FF); - assert.equal(toRgba(0x80, 0x80, 0x80), 0x808080FF); - assert.equal(toRgba(0x90, 0x90, 0x90), 0x909090FF); - assert.equal(toRgba(0xa0, 0xa0, 0xa0), 0xa0a0a0FF); - assert.equal(toRgba(0xb0, 0xb0, 0xb0), 0xb0b0b0FF); - assert.equal(toRgba(0xc0, 0xc0, 0xc0), 0xc0c0c0FF); - assert.equal(toRgba(0xd0, 0xd0, 0xd0), 0xd0d0d0FF); - assert.equal(toRgba(0xe0, 0xe0, 0xe0), 0xe0e0e0FF); - assert.equal(toRgba(0xf0, 0xf0, 0xf0), 0xf0f0f0FF); - assert.equal(toRgba(0xff, 0xff, 0xff), 0xffffffFF); - }); - it('should convert an rgba array to an rgba number', () => { - assert.equal(toRgba(0x00, 0x00, 0x00, 0x00), 0x00000000); - assert.equal(toRgba(0x10, 0x10, 0x10, 0x10), 0x10101010); - assert.equal(toRgba(0x20, 0x20, 0x20, 0x20), 0x20202020); - assert.equal(toRgba(0x30, 0x30, 0x30, 0x30), 0x30303030); - assert.equal(toRgba(0x40, 0x40, 0x40, 0x40), 0x40404040); - assert.equal(toRgba(0x50, 0x50, 0x50, 0x50), 0x50505050); - assert.equal(toRgba(0x60, 0x60, 0x60, 0x60), 0x60606060); - assert.equal(toRgba(0x70, 0x70, 0x70, 0x70), 0x70707070); - assert.equal(toRgba(0x80, 0x80, 0x80, 0x80), 0x80808080); - assert.equal(toRgba(0x90, 0x90, 0x90, 0x90), 0x90909090); - assert.equal(toRgba(0xa0, 0xa0, 0xa0, 0xa0), 0xa0a0a0a0); - assert.equal(toRgba(0xb0, 0xb0, 0xb0, 0xb0), 0xb0b0b0b0); - assert.equal(toRgba(0xc0, 0xc0, 0xc0, 0xc0), 0xc0c0c0c0); - assert.equal(toRgba(0xd0, 0xd0, 0xd0, 0xd0), 0xd0d0d0d0); - assert.equal(toRgba(0xe0, 0xe0, 0xe0, 0xe0), 0xe0e0e0e0); - assert.equal(toRgba(0xf0, 0xf0, 0xf0, 0xf0), 0xf0f0f0f0); - assert.equal(toRgba(0xff, 0xff, 0xff, 0xff), 0xffffffff); - }); - }); - - describe('fromRgba', () => { - it('should convert an rgba number to an rgba array', () => { - assert.deepEqual(fromRgba(0x00000000), [0x00, 0x00, 0x00, 0x00]); - assert.deepEqual(fromRgba(0x10101010), [0x10, 0x10, 0x10, 0x10]); - assert.deepEqual(fromRgba(0x20202020), [0x20, 0x20, 0x20, 0x20]); - assert.deepEqual(fromRgba(0x30303030), [0x30, 0x30, 0x30, 0x30]); - assert.deepEqual(fromRgba(0x40404040), [0x40, 0x40, 0x40, 0x40]); - assert.deepEqual(fromRgba(0x50505050), [0x50, 0x50, 0x50, 0x50]); - assert.deepEqual(fromRgba(0x60606060), [0x60, 0x60, 0x60, 0x60]); - assert.deepEqual(fromRgba(0x70707070), [0x70, 0x70, 0x70, 0x70]); - assert.deepEqual(fromRgba(0x80808080), [0x80, 0x80, 0x80, 0x80]); - assert.deepEqual(fromRgba(0x90909090), [0x90, 0x90, 0x90, 0x90]); - assert.deepEqual(fromRgba(0xa0a0a0a0), [0xa0, 0xa0, 0xa0, 0xa0]); - assert.deepEqual(fromRgba(0xb0b0b0b0), [0xb0, 0xb0, 0xb0, 0xb0]); - assert.deepEqual(fromRgba(0xc0c0c0c0), [0xc0, 0xc0, 0xc0, 0xc0]); - assert.deepEqual(fromRgba(0xd0d0d0d0), [0xd0, 0xd0, 0xd0, 0xd0]); - assert.deepEqual(fromRgba(0xe0e0e0e0), [0xe0, 0xe0, 0xe0, 0xe0]); - assert.deepEqual(fromRgba(0xf0f0f0f0), [0xf0, 0xf0, 0xf0, 0xf0]); - assert.deepEqual(fromRgba(0xffffffff), [0xff, 0xff, 0xff, 0xff]); - }); - }); - - describe('opaque', () => { - it('should make the color opaque', () => { - assert.deepEqual(opaque({ css: '#00000000', rgba: 0x00000000 }), { css: '#000000', rgba: 0x000000FF }); - assert.deepEqual(opaque({ css: '#10101010', rgba: 0x10101010 }), { css: '#101010', rgba: 0x101010FF }); - assert.deepEqual(opaque({ css: '#20202020', rgba: 0x20202020 }), { css: '#202020', rgba: 0x202020FF }); - assert.deepEqual(opaque({ css: '#30303030', rgba: 0x30303030 }), { css: '#303030', rgba: 0x303030FF }); - assert.deepEqual(opaque({ css: '#40404040', rgba: 0x40404040 }), { css: '#404040', rgba: 0x404040FF }); - assert.deepEqual(opaque({ css: '#50505050', rgba: 0x50505050 }), { css: '#505050', rgba: 0x505050FF }); - assert.deepEqual(opaque({ css: '#60606060', rgba: 0x60606060 }), { css: '#606060', rgba: 0x606060FF }); - assert.deepEqual(opaque({ css: '#70707070', rgba: 0x70707070 }), { css: '#707070', rgba: 0x707070FF }); - assert.deepEqual(opaque({ css: '#80808080', rgba: 0x80808080 }), { css: '#808080', rgba: 0x808080FF }); - assert.deepEqual(opaque({ css: '#90909090', rgba: 0x90909090 }), { css: '#909090', rgba: 0x909090FF }); - assert.deepEqual(opaque({ css: '#a0a0a0a0', rgba: 0xa0a0a0a0 }), { css: '#a0a0a0', rgba: 0xa0a0a0FF }); - assert.deepEqual(opaque({ css: '#b0b0b0b0', rgba: 0xb0b0b0b0 }), { css: '#b0b0b0', rgba: 0xb0b0b0FF }); - assert.deepEqual(opaque({ css: '#c0c0c0c0', rgba: 0xc0c0c0c0 }), { css: '#c0c0c0', rgba: 0xc0c0c0FF }); - assert.deepEqual(opaque({ css: '#d0d0d0d0', rgba: 0xd0d0d0d0 }), { css: '#d0d0d0', rgba: 0xd0d0d0FF }); - assert.deepEqual(opaque({ css: '#e0e0e0e0', rgba: 0xe0e0e0e0 }), { css: '#e0e0e0', rgba: 0xe0e0e0FF }); - assert.deepEqual(opaque({ css: '#f0f0f0f0', rgba: 0xf0f0f0f0 }), { css: '#f0f0f0', rgba: 0xf0f0f0FF }); - assert.deepEqual(opaque({ css: '#ffffffff', rgba: 0xffffffff }), { css: '#ffffff', rgba: 0xffffffFF }); - }); - }); - - describe('rgbRelativeLuminance', () => { - it('should calculate the relative luminance of the color', () => { - assert.equal(rgbRelativeLuminance(0x000000), 0); - assert.equal(rgbRelativeLuminance(0x101010).toFixed(4), '0.0052'); - assert.equal(rgbRelativeLuminance(0x202020).toFixed(4), '0.0144'); - assert.equal(rgbRelativeLuminance(0x303030).toFixed(4), '0.0296'); - assert.equal(rgbRelativeLuminance(0x404040).toFixed(4), '0.0513'); - assert.equal(rgbRelativeLuminance(0x505050).toFixed(4), '0.0802'); - assert.equal(rgbRelativeLuminance(0x606060).toFixed(4), '0.1170'); - assert.equal(rgbRelativeLuminance(0x707070).toFixed(4), '0.1620'); - assert.equal(rgbRelativeLuminance(0x808080).toFixed(4), '0.2159'); - assert.equal(rgbRelativeLuminance(0x909090).toFixed(4), '0.2789'); - assert.equal(rgbRelativeLuminance(0xA0A0A0).toFixed(4), '0.3515'); - assert.equal(rgbRelativeLuminance(0xB0B0B0).toFixed(4), '0.4342'); - assert.equal(rgbRelativeLuminance(0xC0C0C0).toFixed(4), '0.5271'); - assert.equal(rgbRelativeLuminance(0xD0D0D0).toFixed(4), '0.6308'); - assert.equal(rgbRelativeLuminance(0xE0E0E0).toFixed(4), '0.7454'); - assert.equal(rgbRelativeLuminance(0xF0F0F0).toFixed(4), '0.8714'); - assert.equal(rgbRelativeLuminance(0xFFFFFF), 1); - }); - }); describe('contrastRatio', () => { it('should calculate the relative luminance of the color', () => { assert.equal(contrastRatio(0, 0), 1); @@ -212,56 +277,4 @@ describe('Color', () => { assert.equal(contrastRatio(1, 0), 21); }); }); - describe('ensureContrastRatioRgba', () => { - it('should return undefined if the color already meets the contrast ratio (black bg)', () => { - assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 1), undefined); - assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 2), undefined); - assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 3), undefined); - }); - it('should return a color that meets the contrast ratio (black bg)', () => { - assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 4), 0x707070ff); - assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 5), 0x7f7f7fff); - assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 6), 0x8c8c8cff); - assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 7), 0x989898ff); - assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 8), 0xa3a3a3ff); - assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 9), 0xadadadff); - assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 10), 0xb6b6b6ff); - assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 11), 0xbebebeff); - assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 12), 0xc5c5c5ff); - assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 13), 0xd1d1d1ff); - assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 14), 0xd6d6d6ff); - assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 15), 0xdbdbdbff); - assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 16), 0xe3e3e3ff); - assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 17), 0xe9e9e9ff); - assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 18), 0xeeeeeeff); - assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 19), 0xf4f4f4ff); - assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 20), 0xfafafaff); - assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 21), 0xffffffff); - }); - it('should return undefined if the color already meets the contrast ratio (white bg)', () => { - assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 1), undefined); - assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 2), undefined); - assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 3), undefined); - assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 4), undefined); - assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 5), undefined); - assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 6), undefined); - }); - it('should return a color that meets the contrast ratio (white bg)', () => { - assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 7), 0x565656ff); - assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 8), 0x4d4d4dff); - assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 9), 0x454545ff); - assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 10), 0x3e3e3eff); - assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 11), 0x373737ff); - assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 12), 0x313131ff); - assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 13), 0x313131ff); - assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 14), 0x272727ff); - assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 15), 0x232323ff); - assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 16), 0x1f1f1fff); - assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 17), 0x1b1b1bff); - assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 18), 0x151515ff); - assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 19), 0x101010ff); - assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 20), 0x080808ff); - assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 21), 0x000000ff); - }); - }); }); diff --git a/src/browser/Color.ts b/src/browser/Color.ts index e40ff9e1a..9649227e2 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -5,33 +5,186 @@ import { IColor } from 'browser/Types'; -export function blend(bg: IColor, fg: IColor): IColor { - const a = (fg.rgba & 0xFF) / 255; - if (a === 1) { - return { - css: fg.css, - rgba: fg.rgba - }; +/** + * Helper functions where the source type is "channels" (individual color channels as numbers). + */ +export namespace channels { + export function toCss(r: number, g: number, b: number, a?: number): string { + if (a !== undefined) { + return `#${toPaddedHex(r)}${toPaddedHex(g)}${toPaddedHex(b)}${toPaddedHex(a)}`; + } + return `#${toPaddedHex(r)}${toPaddedHex(g)}${toPaddedHex(b)}`; + } + + export function toRgba(r: number, g: number, b: number, a: number = 0xFF): number { + // >>> 0 forces an unsigned int + return (r << 24 | g << 16 | b << 8 | a) >>> 0; } - const fgR = (fg.rgba >> 24) & 0xFF; - const fgG = (fg.rgba >> 16) & 0xFF; - const fgB = (fg.rgba >> 8) & 0xFF; - const bgR = (bg.rgba >> 24) & 0xFF; - const bgG = (bg.rgba >> 16) & 0xFF; - const bgB = (bg.rgba >> 8) & 0xFF; - const r = bgR + Math.round((fgR - bgR) * a); - const g = bgG + Math.round((fgG - bgG) * a); - const b = bgB + Math.round((fgB - bgB) * a); - const css = toCss(r, g, b); - const rgba = toRgba(r, g, b); - return { css, rgba }; } -export function fromCss(css: string): IColor { - return { - css, - rgba: (parseInt(css.slice(1), 16) << 8 | 0xFF) >>> 0 - }; +/** + * Helper functions where the source type is `IColor`. + */ +export namespace color { + export function blend(bg: IColor, fg: IColor): IColor { + const a = (fg.rgba & 0xFF) / 255; + if (a === 1) { + return { + css: fg.css, + rgba: fg.rgba + }; + } + const fgR = (fg.rgba >> 24) & 0xFF; + const fgG = (fg.rgba >> 16) & 0xFF; + const fgB = (fg.rgba >> 8) & 0xFF; + const bgR = (bg.rgba >> 24) & 0xFF; + const bgG = (bg.rgba >> 16) & 0xFF; + const bgB = (bg.rgba >> 8) & 0xFF; + const r = bgR + Math.round((fgR - bgR) * a); + const g = bgG + Math.round((fgG - bgG) * a); + const b = bgB + Math.round((fgB - bgB) * a); + const css = channels.toCss(r, g, b); + const rgba = channels.toRgba(r, g, b); + return { css, rgba }; + } + + export function ensureContrastRatio(bg: IColor, fg: IColor, ratio: number): IColor | undefined { + const result = rgba.ensureContrastRatio(bg.rgba, fg.rgba, ratio); + if (!result) { + return undefined; + } + return rgba.toColor( + (result >> 24 & 0xFF), + (result >> 16 & 0xFF), + (result >> 8 & 0xFF) + ); + } + + export function opaque(color: IColor): IColor { + const rgbaColor = (color.rgba | 0xFF) >>> 0; + const [r, g, b] = rgba.toChannels(rgbaColor); + return { + css: channels.toCss(r, g, b), + rgba: rgbaColor + }; + } +} + +/** + * Helper functions where the source type is "css" (string: '#rgb', '#rgba', '#rrggbb', '#rrggbbaa'). + */ +export namespace css { + export function toColor(css: string): IColor { + return { + css, + rgba: (parseInt(css.slice(1), 16) << 8 | 0xFF) >>> 0 + }; + } +} + +/** + * Helper functions where the source type is "rgb" (number: 0xrrggbb). + */ +export namespace rgb { + /** + * Gets the relative luminance of an RGB color, this is useful in determining the contrast ratio + * between two colors. + * @param rgb The color to use. + * @see https://www.w3.org/TR/WCAG20/#relativeluminancedef + */ + export function relativeLuminance(rgb: number): number { + return relativeLuminance2( + (rgb >> 16) & 0xFF, + (rgb >> 8 ) & 0xFF, + (rgb ) & 0xFF); + } + + /** + * Gets the relative luminance of an RGB color, this is useful in determining the contrast ratio + * between two colors. + * @param r The red channel (0x00 to 0xFF). + * @param g The green channel (0x00 to 0xFF). + * @param b The blue channel (0x00 to 0xFF). + * @see https://www.w3.org/TR/WCAG20/#relativeluminancedef + */ + export function relativeLuminance2(r: number, g: number, b: number): number { + const rs = r / 255; + const gs = g / 255; + const bs = b / 255; + const rr = rs <= 0.03928 ? rs / 12.92 : Math.pow((rs + 0.055) / 1.055, 2.4); + const rg = gs <= 0.03928 ? gs / 12.92 : Math.pow((gs + 0.055) / 1.055, 2.4); + const rb = bs <= 0.03928 ? bs / 12.92 : Math.pow((bs + 0.055) / 1.055, 2.4); + return rr * 0.2126 + rg * 0.7152 + rb * 0.0722; + } +} + +/** + * Helper functions where the source type is "rgba" (number: 0xrrggbbaa). + */ +export namespace rgba { + export function ensureContrastRatio(bgRgba: number, fgRgba: number, ratio: number): number | undefined { + const bgL = rgb.relativeLuminance(bgRgba >> 8); + const fgL = rgb.relativeLuminance(fgRgba >> 8); + const cr = contrastRatio(bgL, fgL); + if (cr < ratio) { + if (fgL < bgL) { + return reduceLuminance(bgRgba, fgRgba, ratio); + } + return increaseLuminance(bgRgba, fgRgba, ratio); + } + return undefined; + } + + export function reduceLuminance(bgRgba: number, fgRgba: number, ratio: number): number { + // This is a naive but fast approach to reducing luminance as converting to + // HSL and back is expensive + const bgR = (bgRgba >> 24) & 0xFF; + const bgG = (bgRgba >> 16) & 0xFF; + const bgB = (bgRgba >> 8) & 0xFF; + let fgR = (fgRgba >> 24) & 0xFF; + let fgG = (fgRgba >> 16) & 0xFF; + let fgB = (fgRgba >> 8) & 0xFF; + let cr = contrastRatio(rgb.relativeLuminance2(fgR, fgB, fgG), rgb.relativeLuminance2(bgR, bgG, bgB)); + while (cr < ratio && (fgR > 0 || fgG > 0 || fgB > 0)) { + // Reduce by 10% until the ratio is hit + fgR -= Math.max(0, Math.ceil(fgR * 0.1)); + fgG -= Math.max(0, Math.ceil(fgG * 0.1)); + fgB -= Math.max(0, Math.ceil(fgB * 0.1)); + cr = contrastRatio(rgb.relativeLuminance2(fgR, fgB, fgG), rgb.relativeLuminance2(bgR, bgG, bgB)); + } + return (fgR << 24 | fgG << 16 | fgB << 8 | 0xFF) >>> 0; + } + + export function increaseLuminance(bgRgba: number, fgRgba: number, ratio: number): number { + // This is a naive but fast approach to increasing luminance as converting to + // HSL and back is expensive + const bgR = (bgRgba >> 24) & 0xFF; + const bgG = (bgRgba >> 16) & 0xFF; + const bgB = (bgRgba >> 8) & 0xFF; + let fgR = (fgRgba >> 24) & 0xFF; + let fgG = (fgRgba >> 16) & 0xFF; + let fgB = (fgRgba >> 8) & 0xFF; + let cr = contrastRatio(rgb.relativeLuminance2(fgR, fgB, fgG), rgb.relativeLuminance2(bgR, bgG, bgB)); + while (cr < ratio && (fgR < 0xFF || fgG < 0xFF || fgB < 0xFF)) { + // Increase by 10% until the ratio is hit + fgR = Math.min(0xFF, fgR + Math.ceil((255 - fgR) * 0.1)); + fgG = Math.min(0xFF, fgG + Math.ceil((255 - fgG) * 0.1)); + fgB = Math.min(0xFF, fgB + Math.ceil((255 - fgB) * 0.1)); + cr = contrastRatio(rgb.relativeLuminance2(fgR, fgB, fgG), rgb.relativeLuminance2(bgR, bgG, bgB)); + } + return (fgR << 24 | fgG << 16 | fgB << 8 | 0xFF) >>> 0; + } + + export function toChannels(value: number): [number, number, number, number] { + return [(value >> 24) & 0xFF, (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF]; + } + + export function toColor(r: number, g: number, b: number): IColor { + return { + css: channels.toCss(r, g, b), + rgba: channels.toRgba(r, g, b) + }; + } } export function toPaddedHex(c: number): string { @@ -39,62 +192,6 @@ export function toPaddedHex(c: number): string { return s.length < 2 ? '0' + s : s; } -export function toCss(r: number, g: number, b: number, a?: number): string { - if (a !== undefined) { - return `#${toPaddedHex(r)}${toPaddedHex(g)}${toPaddedHex(b)}${toPaddedHex(a)}`; - } - return `#${toPaddedHex(r)}${toPaddedHex(g)}${toPaddedHex(b)}`; -} - -export function toRgba(r: number, g: number, b: number, a: number = 0xFF): number { - // >>> 0 forces an unsigned int - return (r << 24 | g << 16 | b << 8 | a) >>> 0; -} - -export function fromRgba(value: number): [number, number, number, number] { - return [(value >> 24) & 0xFF, (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF]; -} - -export function opaque(color: IColor): IColor { - const rgba = (color.rgba | 0xFF) >>> 0; - const [r, g, b] = fromRgba(rgba); - return { - css: toCss(r, g, b), - rgba - }; -} - -/** - * Gets the relative luminance of an RGB color, this is useful in determining the contrast ratio - * between two colors. - * @param rgb The color to use. - * @see https://www.w3.org/TR/WCAG20/#relativeluminancedef - */ -export function rgbRelativeLuminance(rgb: number): number { - return rgbRelativeLuminance2( - (rgb >> 16) & 0xFF, - (rgb >> 8 ) & 0xFF, - (rgb ) & 0xFF); -} - -/** - * Gets the relative luminance of an RGB color, this is useful in determining the contrast ratio - * between two colors. - * @param r The red channel (0x00 to 0xFF). - * @param g The green channel (0x00 to 0xFF). - * @param b The blue channel (0x00 to 0xFF). - * @see https://www.w3.org/TR/WCAG20/#relativeluminancedef - */ -export function rgbRelativeLuminance2(r: number, g: number, b: number): number { - const rs = r / 255; - const gs = g / 255; - const bs = b / 255; - const rr = rs <= 0.03928 ? rs / 12.92 : Math.pow((rs + 0.055) / 1.055, 2.4); - const rg = gs <= 0.03928 ? gs / 12.92 : Math.pow((gs + 0.055) / 1.055, 2.4); - const rb = bs <= 0.03928 ? bs / 12.92 : Math.pow((bs + 0.055) / 1.055, 2.4); - return rr * 0.2126 + rg * 0.7152 + rb * 0.0722; -} - /** * Gets the contrast ratio between two relative luminance values. * @param l1 The first relative luminance. @@ -107,75 +204,3 @@ export function contrastRatio(l1: number, l2: number): number { } return (l1 + 0.05) / (l2 + 0.05); } - -export function rgbaToColor(r: number, g: number, b: number): IColor { - return { - css: toCss(r, g, b), - rgba: toRgba(r, g, b) - }; -} - -export function ensureContrastRatioRgba(bgRgba: number, fgRgba: number, ratio: number): number | undefined { - const bgL = rgbRelativeLuminance(bgRgba >> 8); - const fgL = rgbRelativeLuminance(fgRgba >> 8); - const cr = contrastRatio(bgL, fgL); - if (cr < ratio) { - if (fgL < bgL) { - return reduceLuminance(bgRgba, fgRgba, ratio); - } - return increaseLuminance(bgRgba, fgRgba, ratio); - } - return undefined; -} - -export function ensureContrastRatio(bg: IColor, fg: IColor, ratio: number): IColor | undefined { - const result = ensureContrastRatioRgba(bg.rgba, fg.rgba, ratio); - if (!result) { - return undefined; - } - return rgbaToColor( - (result >> 24 & 0xFF), - (result >> 16 & 0xFF), - (result >> 8 & 0xFF) - ); -} - -export function reduceLuminance(bgRgba: number, fgRgba: number, ratio: number): number { - // This is a naive but fast approach to reducing luminance as converting to - // HSL and back is expensive - const bgR = (bgRgba >> 24) & 0xFF; - const bgG = (bgRgba >> 16) & 0xFF; - const bgB = (bgRgba >> 8) & 0xFF; - let fgR = (fgRgba >> 24) & 0xFF; - let fgG = (fgRgba >> 16) & 0xFF; - let fgB = (fgRgba >> 8) & 0xFF; - let cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); - while (cr < ratio && (fgR > 0 || fgG > 0 || fgB > 0)) { - // Reduce by 10% until the ratio is hit - fgR -= Math.max(0, Math.ceil(fgR * 0.1)); - fgG -= Math.max(0, Math.ceil(fgG * 0.1)); - fgB -= Math.max(0, Math.ceil(fgB * 0.1)); - cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); - } - return (fgR << 24 | fgG << 16 | fgB << 8 | 0xFF) >>> 0; -} - -export function increaseLuminance(bgRgba: number, fgRgba: number, ratio: number): number { - // This is a naive but fast approach to increasing luminance as converting to - // HSL and back is expensive - const bgR = (bgRgba >> 24) & 0xFF; - const bgG = (bgRgba >> 16) & 0xFF; - const bgB = (bgRgba >> 8) & 0xFF; - let fgR = (fgRgba >> 24) & 0xFF; - let fgG = (fgRgba >> 16) & 0xFF; - let fgB = (fgRgba >> 8) & 0xFF; - let cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); - while (cr < ratio && (fgR < 0xFF || fgG < 0xFF || fgB < 0xFF)) { - // Increase by 10% until the ratio is hit - fgR = Math.min(0xFF, fgR + Math.ceil((255 - fgR) * 0.1)); - fgG = Math.min(0xFF, fgG + Math.ceil((255 - fgG) * 0.1)); - fgB = Math.min(0xFF, fgB + Math.ceil((255 - fgB) * 0.1)); - cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); - } - return (fgR << 24 | fgG << 16 | fgB << 8 | 0xFF) >>> 0; -} diff --git a/src/browser/ColorManager.ts b/src/browser/ColorManager.ts index b4bcdde04..ee0dd4c77 100644 --- a/src/browser/ColorManager.ts +++ b/src/browser/ColorManager.ts @@ -5,13 +5,13 @@ import { IColorManager, IColor, IColorSet, IColorContrastCache } from 'browser/Types'; import { ITheme } from 'common/services/Services'; -import { fromCss, toCss, blend, toRgba, toPaddedHex } from 'browser/Color'; +import { channels, color, css } from 'browser/Color'; import { ColorContrastCache } from 'browser/ColorContrastCache'; -const DEFAULT_FOREGROUND = fromCss('#ffffff'); -const DEFAULT_BACKGROUND = fromCss('#000000'); -const DEFAULT_CURSOR = fromCss('#ffffff'); -const DEFAULT_CURSOR_ACCENT = fromCss('#000000'); +const DEFAULT_FOREGROUND = css.toColor('#ffffff'); +const DEFAULT_BACKGROUND = css.toColor('#000000'); +const DEFAULT_CURSOR = css.toColor('#ffffff'); +const DEFAULT_CURSOR_ACCENT = css.toColor('#000000'); const DEFAULT_SELECTION = { css: 'rgba(255, 255, 255, 0.3)', rgba: 0xFFFFFF4D @@ -22,23 +22,23 @@ const DEFAULT_SELECTION = { export const DEFAULT_ANSI_COLORS = (() => { const colors = [ // dark: - fromCss('#2e3436'), - fromCss('#cc0000'), - fromCss('#4e9a06'), - fromCss('#c4a000'), - fromCss('#3465a4'), - fromCss('#75507b'), - fromCss('#06989a'), - fromCss('#d3d7cf'), + css.toColor('#2e3436'), + css.toColor('#cc0000'), + css.toColor('#4e9a06'), + css.toColor('#c4a000'), + css.toColor('#3465a4'), + css.toColor('#75507b'), + css.toColor('#06989a'), + css.toColor('#d3d7cf'), // bright: - fromCss('#555753'), - fromCss('#ef2929'), - fromCss('#8ae234'), - fromCss('#fce94f'), - fromCss('#729fcf'), - fromCss('#ad7fa8'), - fromCss('#34e2e2'), - fromCss('#eeeeec') + css.toColor('#555753'), + css.toColor('#ef2929'), + css.toColor('#8ae234'), + css.toColor('#fce94f'), + css.toColor('#729fcf'), + css.toColor('#ad7fa8'), + css.toColor('#34e2e2'), + css.toColor('#eeeeec') ]; // Fill in the remaining 240 ANSI colors. @@ -49,8 +49,8 @@ export const DEFAULT_ANSI_COLORS = (() => { const g = v[(i / 6) % 6 | 0]; const b = v[i % 6]; colors.push({ - css: toCss(r, g, b), - rgba: toRgba(r, g, b) + css: channels.toCss(r, g, b), + rgba: channels.toRgba(r, g, b) }); } @@ -58,8 +58,8 @@ export const DEFAULT_ANSI_COLORS = (() => { for (let i = 0; i < 24; i++) { const c = 8 + i * 10; colors.push({ - css: toCss(c, c, c), - rgba: toRgba(c, c, c) + css: channels.toCss(c, c, c), + rgba: channels.toRgba(c, c, c) }); } @@ -93,7 +93,7 @@ export class ColorManager implements IColorManager { cursor: DEFAULT_CURSOR, cursorAccent: DEFAULT_CURSOR_ACCENT, selection: DEFAULT_SELECTION, - selectionOpaque: blend(DEFAULT_BACKGROUND, DEFAULT_SELECTION), + selectionOpaque: color.blend(DEFAULT_BACKGROUND, DEFAULT_SELECTION), ansi: DEFAULT_ANSI_COLORS.slice(), contrastCache: this._contrastCache }; @@ -116,7 +116,7 @@ export class ColorManager implements IColorManager { this.colors.cursor = this._parseColor(theme.cursor, DEFAULT_CURSOR, true); this.colors.cursorAccent = this._parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT, true); this.colors.selection = this._parseColor(theme.selection, DEFAULT_SELECTION, true); - this.colors.selectionOpaque = blend(this.colors.background, this.colors.selection); + this.colors.selectionOpaque = color.blend(this.colors.background, this.colors.selection); this.colors.ansi[0] = this._parseColor(theme.black, DEFAULT_ANSI_COLORS[0]); this.colors.ansi[1] = this._parseColor(theme.red, DEFAULT_ANSI_COLORS[1]); this.colors.ansi[2] = this._parseColor(theme.green, DEFAULT_ANSI_COLORS[2]); @@ -195,7 +195,7 @@ export class ColorManager implements IColorManager { g = ((num >> 8) & 0xF) * 16; b = ((num >> 4) & 0xF) * 16; a = (num & 0xF) * 16; - rgba = toRgba(r, g, b, a); + rgba = channels.toRgba(r, g, b, a); } else { rgba = parseInt(css.substr(1), 16); r = (rgba >> 24) & 0xFF; @@ -206,13 +206,13 @@ export class ColorManager implements IColorManager { return { rgba, - css: toCss(r, g, b, a) + css: channels.toCss(r, g, b, a) }; } return { css, - rgba: toRgba(data[0], data[1], data[2], data[3]) + rgba: channels.toRgba(data[0], data[1], data[2], data[3]) }; } } diff --git a/src/browser/renderer/BaseRenderLayer.ts b/src/browser/renderer/BaseRenderLayer.ts index 4ea8bd52f..f109f42e6 100644 --- a/src/browser/renderer/BaseRenderLayer.ts +++ b/src/browser/renderer/BaseRenderLayer.ts @@ -15,7 +15,7 @@ import { IColorSet, IColor } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; import { IBufferService, IOptionsService } from 'common/services/Services'; import { throwIfFalsy } from 'browser/renderer/RendererUtils'; -import { toCss, ensureContrastRatioRgba, opaque } from 'browser/Color'; +import { channels, color, rgba } from 'browser/Color'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; @@ -325,7 +325,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { if (fgOverride) { this._ctx.fillStyle = fgOverride.css; } else if (cell.isBgDefault()) { - this._ctx.fillStyle = opaque(this._colors.background).css; + this._ctx.fillStyle = color.opaque(this._colors.background).css; } else if (cell.isBgRGB()) { this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`; } else { @@ -418,7 +418,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, isInverse); const fgRgba = this._resolveForegroundRgba(fgColorMode, fgColor, isInverse, isBold); - const result = ensureContrastRatioRgba(bgRgba, fgRgba, this._optionsService.options.minimumContrastRatio); + const result = rgba.ensureContrastRatio(bgRgba, fgRgba, this._optionsService.options.minimumContrastRatio); if (!result) { this._colors.contrastCache.setColor(cell.bg, cell.fg, null); @@ -426,7 +426,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { } const color: IColor = { - css: toCss( + css: channels.toCss( (result >> 24) & 0xFF, (result >> 16) & 0xFF, (result >> 8) & 0xFF diff --git a/src/browser/renderer/atlas/DynamicCharAtlas.ts b/src/browser/renderer/atlas/DynamicCharAtlas.ts index 84a501602..f2443a526 100644 --- a/src/browser/renderer/atlas/DynamicCharAtlas.ts +++ b/src/browser/renderer/atlas/DynamicCharAtlas.ts @@ -11,7 +11,7 @@ import { LRUMap } from 'browser/renderer/atlas/LRUMap'; import { isFirefox, isSafari } from 'common/Platform'; import { IColor } from 'browser/Types'; import { throwIfFalsy } from 'browser/renderer/RendererUtils'; -import { opaque } from 'browser/Color'; +import { color } from 'browser/Color'; // In practice we're probably never going to exhaust a texture this large. For debugging purposes, // however, it can be useful to set this to a really tiny value, to verify that LRU eviction works. @@ -223,7 +223,7 @@ export class DynamicCharAtlas extends BaseCharAtlas { private _getForegroundColor(glyph: IGlyphIdentifier): IColor { if (glyph.fg === INVERTED_DEFAULT_COLOR) { - return opaque(this._config.colors.background); + return color.opaque(this._config.colors.background); } else if (glyph.fg < 256) { // 256 color support return this._getColorFromAnsiIndex(glyph.fg); diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 942a27925..5fbcfdc9c 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -11,7 +11,7 @@ import { IColorSet, ILinkifierEvent, ILinkifier } from 'browser/Types'; import { ICharSizeService } from 'browser/services/Services'; import { IOptionsService, IBufferService } from 'common/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; -import { opaque } from 'browser/Color'; +import { color } from 'browser/Color'; const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-'; const ROW_CONTAINER_CLASS = 'xterm-rows'; @@ -231,7 +231,7 @@ export class DomRenderer extends Disposable implements IRenderer { `${this._terminalSelector} .${BG_CLASS_PREFIX}${i} { background-color: ${c.css}; }`; }); styles += - `${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { color: ${opaque(this._colors.background).css}; }` + + `${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { color: ${color.opaque(this._colors.background).css}; }` + `${this._terminalSelector} .${BG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { background-color: ${this._colors.foreground.css}; }`; this._themeStyleElement.innerHTML = styles; diff --git a/src/browser/renderer/dom/DomRendererRowFactory.test.ts b/src/browser/renderer/dom/DomRendererRowFactory.test.ts index c71945cfd..b6604b14f 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.test.ts @@ -11,7 +11,7 @@ import { BufferLine, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { IBufferLine } from 'common/Types'; import { CellData } from 'common/buffer/CellData'; import { MockOptionsService } from 'common/TestUtils.test'; -import { fromCss } from 'browser/Color'; +import { css } from 'browser/Color'; describe('DomRendererRowFactory', () => { let dom: jsdom.JSDOM; @@ -21,27 +21,27 @@ describe('DomRendererRowFactory', () => { beforeEach(() => { dom = new jsdom.JSDOM(''); rowFactory = new DomRendererRowFactory(dom.window.document, new MockOptionsService({ drawBoldTextInBrightColors: true }), { - background: fromCss('#010101'), - foreground: fromCss('#020202'), + background: css.toColor('#010101'), + foreground: css.toColor('#020202'), ansi: [ // dark: - fromCss('#2e3436'), - fromCss('#cc0000'), - fromCss('#4e9a06'), - fromCss('#c4a000'), - fromCss('#3465a4'), - fromCss('#75507b'), - fromCss('#06989a'), - fromCss('#d3d7cf'), + css.toColor('#2e3436'), + css.toColor('#cc0000'), + css.toColor('#4e9a06'), + css.toColor('#c4a000'), + css.toColor('#3465a4'), + css.toColor('#75507b'), + css.toColor('#06989a'), + css.toColor('#d3d7cf'), // bright: - fromCss('#555753'), - fromCss('#ef2929'), - fromCss('#8ae234'), - fromCss('#fce94f'), - fromCss('#729fcf'), - fromCss('#ad7fa8'), - fromCss('#34e2e2'), - fromCss('#eeeeec') + css.toColor('#555753'), + css.toColor('#ef2929'), + css.toColor('#8ae234'), + css.toColor('#fce94f'), + css.toColor('#729fcf'), + css.toColor('#ad7fa8'), + css.toColor('#34e2e2'), + css.toColor('#eeeeec') ] } as any); lineData = createEmptyLineData(2); diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index bd922f578..9ffe67010 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -8,7 +8,7 @@ import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Attributes } from 'common/buffer/Constants'; import { CellData } from 'common/buffer/CellData'; import { IOptionsService } from 'common/services/Services'; -import { ensureContrastRatio, rgbaToColor } from 'browser/Color'; +import { color, rgba } from 'browser/Color'; import { IColorSet, IColor } from 'browser/Types'; export const BOLD_CLASS = 'xterm-bold'; @@ -129,7 +129,7 @@ export class DomRendererRowFactory { } break; case Attributes.CM_RGB: - const color = rgbaToColor( + const color = rgba.toColor( (fg >> 16) & 0xFF, (fg >> 8) & 0xFF, (fg ) & 0xFF @@ -178,7 +178,7 @@ export class DomRendererRowFactory { // Calculate and store in cache if (adjustedColor === undefined) { - adjustedColor = ensureContrastRatio(bg, fg, this._optionsService.options.minimumContrastRatio); + adjustedColor = color.ensureContrastRatio(bg, fg, this._optionsService.options.minimumContrastRatio); this._colors.contrastCache.setColor(this._workCell.bg, this._workCell.fg, adjustedColor ?? null); } From 509327b4c787457a68c4815772a85a19277c5a21 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 26 Dec 2019 12:19:03 +1100 Subject: [PATCH 216/268] Use register over add for APIs returning disposables Fixes #2610 --- src/public/Terminal.ts | 25 ++++++++++++++++++++----- typings/xterm.d.ts | 25 +++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 397898c7e..96e13f634 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -74,10 +74,13 @@ export class Terminal implements ITerminalApi { public deregisterCharacterJoiner(joinerId: number): void { this._core.deregisterCharacterJoiner(joinerId); } - public addMarker(cursorYOffset: number): IMarker { + public registerMarker(cursorYOffset: number): IMarker { this._verifyIntegers(cursorYOffset); return this._core.addMarker(cursorYOffset); } + public addMarker(cursorYOffset: number): IMarker { + return this.registerMarker(cursorYOffset); + } public hasSelection(): boolean { return this._core.hasSelection(); } @@ -227,16 +230,28 @@ class BufferCellApiView implements IBufferCellApi { class ParserApi implements IParser { constructor(private _core: ITerminal) {} - public addCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean): IDisposable { + public registerCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean): IDisposable { return this._core.addCsiHandler(id, (params: IParams) => callback(params.toArray())); } - public addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean): IDisposable { + public addCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean): IDisposable { + return this.registerCsiHandler(id, callback); + } + public registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean): IDisposable { return this._core.addDcsHandler(id, (data: string, params: IParams) => callback(data, params.toArray())); } - public addEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable { + public addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean): IDisposable { + return this.registerDcsHandler(id, callback); + } + public registerEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable { return this._core.addEscHandler(id, handler); } - public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable { + public addEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable { + return this.registerEscHandler(id, handler); + } + public registerOscHandler(ident: number, callback: (data: string) => boolean): IDisposable { return this._core.addOscHandler(ident, callback); } + public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable { + return this.registerOscHandler(ident, callback); + } } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index ac33d087d..e848c547b 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -612,6 +612,11 @@ declare module 'xterm' { * alt buffer is active, undefined is returned. * @param cursorYOffset The y position offset of the marker from the cursor. */ + registerMarker(cursorYOffset: number): IMarker; + + /** + * @deprecated use `registerMarker` instead. + */ addMarker(cursorYOffset: number): IMarker; /** @@ -1078,6 +1083,11 @@ declare module 'xterm' { * The most recently added handler is tried first. * @return An IDisposable you can call to remove this handler. */ + registerCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean): IDisposable; + + /** + * @deprecated use `registerMarker` instead. + */ addCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean): IDisposable; /** @@ -1097,6 +1107,11 @@ declare module 'xterm' { * The most recently added handler is tried first. * @return An IDisposable you can call to remove this handler. */ + registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean): IDisposable; + + /** + * @deprecated use `registerMarker` instead. + */ addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean): IDisposable; /** @@ -1110,6 +1125,11 @@ declare module 'xterm' { * The most recently added handler is tried first. * @return An IDisposable you can call to remove this handler. */ + registerEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable; + + /** + * @deprecated use `registerMarker` instead. + */ addEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable; /** @@ -1128,6 +1148,11 @@ declare module 'xterm' { * The most recently added handler is tried first. * @return An IDisposable you can call to remove this handler. */ + registerOscHandler(ident: number, callback: (data: string) => boolean): IDisposable; + + /** + * @deprecated use `registerMarker` instead. + */ addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable; } } From 28d7eddcb4fed5a2d0ef30eb1ee11c5b7d24ee08 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 26 Dec 2019 14:04:47 +1100 Subject: [PATCH 217/268] Set glyph fg color based on original bg, not selection This involves resolving the rgb channels of the original background color and encoding them using the RGB color mode. Fixes #2599 --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 42 ++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index 7b35949d9..332abeb8f 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -6,13 +6,14 @@ import { createProgram, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils'; import { WebglCharAtlas } from './atlas/WebglCharAtlas'; import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; -import { COMBINED_CHAR_BIT_MASK, RENDER_MODEL_INDICIES_PER_CELL, RENDER_MODEL_FG_OFFSET } from './RenderModel'; +import { COMBINED_CHAR_BIT_MASK, RENDER_MODEL_INDICIES_PER_CELL, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_BG_OFFSET } from './RenderModel'; import { fill } from 'common/TypedArrayUtils'; import { slice } from './TypedArray'; -import { NULL_CELL_CODE, WHITESPACE_CELL_CODE, Attributes } from 'common/buffer/Constants'; +import { NULL_CELL_CODE, WHITESPACE_CELL_CODE, Attributes, FgFlags } from 'common/buffer/Constants'; import { Terminal, IBufferLine } from 'xterm'; -import { IColorSet } from 'browser/Types'; +import { IColorSet, IColor } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; +import { AttributeData } from 'common/buffer/AttributeData'; interface IVertices { attributes: Float32Array; @@ -254,18 +255,49 @@ export class GlyphRenderer { for (let x = startCol; x < endCol; x++) { const offset = (y * this._terminal.cols + x) * RENDER_MODEL_INDICIES_PER_CELL; const code = model.cells[offset]; + let fg = model.cells[offset + RENDER_MODEL_FG_OFFSET]; + if (fg & FgFlags.INVERSE) { + const workCell = new AttributeData(); + workCell.fg = fg; + workCell.bg = model.cells[offset + RENDER_MODEL_BG_OFFSET]; + // Get attributes from fg (excluding inverse) and resolve inverse by pullibng rgb colors + // from bg. This is needed since the inverse fg color should be based on the original bg + // color, not on the selection color + fg = (fg & ~(Attributes.CM_MASK | Attributes.RGB_MASK | FgFlags.INVERSE)); + switch (workCell.getBgColorMode()) { + case Attributes.CM_P16: + case Attributes.CM_P256: + const c = this._getColorFromAnsiIndex(workCell.getBgColor()).rgba; + fg |= (c >> 8) & Attributes.RED_MASK | (c >> 8) & Attributes.GREEN_MASK | (c >> 8) & Attributes.BLUE_MASK; + case Attributes.CM_RGB: + const arr = AttributeData.toColorRGB(workCell.getBgColor()); + fg |= arr[0] << Attributes.RED_SHIFT | arr[1] << Attributes.GREEN_SHIFT | arr[2] << Attributes.BLUE_SHIFT; + case Attributes.CM_DEFAULT: + default: + const c2 = this._colors.background.rgba; + fg |= (c2 >> 8) & Attributes.RED_MASK | (c2 >> 8) & Attributes.GREEN_MASK | (c2 >> 8) & Attributes.BLUE_MASK; + } + fg |= Attributes.CM_RGB; + } if (code & COMBINED_CHAR_BIT_MASK) { if (!line) { line = terminal.buffer.getLine(row); } const chars = line!.getCell(x)!.char; - 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, model.cells[offset + RENDER_MODEL_FG_OFFSET]); + this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, fg); } } } + private _getColorFromAnsiIndex(idx: number): IColor { + if (idx >= this._colors.ansi.length) { + throw new Error('No color found for idx ' + idx); + } + return this._colors.ansi[idx]; + } + public onResize(): void { const terminal = this._terminal; const gl = this._gl; From 3e3c51ae5342b207bab8f548eeaf931f66a1e4d9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 26 Dec 2019 14:26:53 +1100 Subject: [PATCH 218/268] Add a test for selection Part of #2600 --- .../src/WebglRenderer.api.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 1cd198d96..40000fd12 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -825,6 +825,30 @@ describe('WebGL Renderer Integration Tests', function(): void { }); }); + describe('selection', async () => { + before(async () => setupBrowser()); + after(async () => browser.close()); + beforeEach(async () => page.evaluate(`window.term.reset()`)); + + it.only('should resolve the inverse foreground color based on the original background color, not the selection', async () => { + const theme: ITheme = { + foreground: '#FF0000', + background: '#00FF00', + selection: '#0000FF' + }; + await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); + await writeSync(` █\\x1b[7m█\\x1b[0m`); + await pollFor(page, () => getCellColor(1, 1), [0, 255, 0, 255]); + await pollFor(page, () => getCellColor(2, 1), [255, 0, 0, 255]); + await pollFor(page, () => getCellColor(3, 1), [0, 255, 0, 255]); + await page.evaluate(`window.term.selectAll()`); + // Selection only cell needs to be first to ensure renderer has kicked in + await pollFor(page, () => getCellColor(1, 1), [0, 0, 255, 255]); + await pollFor(page, () => getCellColor(2, 1), [255, 0, 0, 255]); + await pollFor(page, () => getCellColor(3, 1), [0, 255, 0, 255]); + }); + }); + describe('allowTransparency', async () => { before(async () => setupBrowser({ rendererType: 'dom', allowTransparency: true})); after(async () => browser.close()); From 79860e7b1568e14abd84d4e3002bafc3411fe101 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 27 Dec 2019 02:59:27 +1100 Subject: [PATCH 219/268] Remove .only --- addons/xterm-addon-webgl/src/WebglRenderer.api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 40000fd12..8b9c84bf7 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -830,7 +830,7 @@ describe('WebGL Renderer Integration Tests', function(): void { after(async () => browser.close()); beforeEach(async () => page.evaluate(`window.term.reset()`)); - it.only('should resolve the inverse foreground color based on the original background color, not the selection', async () => { + it('should resolve the inverse foreground color based on the original background color, not the selection', async () => { const theme: ITheme = { foreground: '#FF0000', background: '#00FF00', From 9f8ea28db405f781380631f869bcfe32c464b778 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 26 Dec 2019 20:04:11 +0100 Subject: [PATCH 220/268] sanity checks in print wide char handling --- src/InputHandler.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 0124f4994..5c4541844 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -400,7 +400,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._dirtyRowService.markDirty(buffer.y); // handle wide chars: reset start_cell-1 if we would overwrite the second cell of a wide char - if (buffer.x && bufferRow.getWidth(buffer.x - 1) === 2) { + if (buffer.x && end - start > 0 && bufferRow.getWidth(buffer.x - 1) === 2) { bufferRow.setCellFromCodePoint(buffer.x - 1, 0, 1, curAttr.fg, curAttr.bg); } @@ -505,7 +505,7 @@ export class InputHandler extends Disposable implements IInputHandler { // This needs to check whether: // - fullwidth + surrogates: reset // - combining: only base char gets carried on (bug in xterm?) - if (end) { + if (end - start > 0) { bufferRow.loadCell(buffer.x - 1, this._workCell); if (this._workCell.getWidth() === 2 || this._workCell.getCode() > 0xFFFF) { this._parser.precedingCodepoint = 0; @@ -516,8 +516,8 @@ export class InputHandler extends Disposable implements IInputHandler { } } - // handle wide chars: reset cell to the right if is second cell of a wide char - if (buffer.x < cols && bufferRow.getWidth(buffer.x) === 0 && !bufferRow.hasContent(buffer.x)) { + // handle wide chars: reset cell to the right if it is second cell of a wide char + if (buffer.x < cols && end - start > 0 && bufferRow.getWidth(buffer.x) === 0 && !bufferRow.hasContent(buffer.x)) { bufferRow.setCellFromCodePoint(buffer.x, 0, 1, curAttr.fg, curAttr.bg); } From c512485514ba0651ddc8594cb060dd0bb758179d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 27 Dec 2019 08:33:42 -0800 Subject: [PATCH 221/268] Target es5 in attach addon Fixes #2646 --- addons/xterm-addon-attach/src/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-attach/src/tsconfig.json b/addons/xterm-addon-attach/src/tsconfig.json index d875aa530..5539aa564 100644 --- a/addons/xterm-addon-attach/src/tsconfig.json +++ b/addons/xterm-addon-attach/src/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2015", + "target": "es5", "lib": [ "dom", "es2015" From a4efefbf5eb45d48551ee5dab600a40a70e0e274 Mon Sep 17 00:00:00 2001 From: javacs3 Date: Tue, 31 Dec 2019 22:06:19 +0800 Subject: [PATCH 222/268] 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 3063f07d5..03c051b3c 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 000000000..12e0ac876 --- /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 3b8c47241..025e7194d 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 000000000..f8b99b556 --- /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 000000000..4e62ed59a --- /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 c8a66c4ba..787462f9e 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 1acf11f5d..e04a1ff83 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 5a7ae05bb..60cb91646 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 5f25f40db936dd85489ee766737d14bcbc6566e4 Mon Sep 17 00:00:00 2001 From: Yucong Sun Date: Thu, 2 Jan 2020 11:57:45 -0800 Subject: [PATCH 223/268] Adding FluffOS to xtermjs user list --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5cf2848f5..21109efe6 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,7 @@ Xterm.js is used in several world-class applications to provide great terminal e - [**tty-share**](https://tty-share.com): Extremely simple terminal sharing over the Internet. - [**Ten Hands**](https://github.com/saisandeepvaddi/ten-hands): One place to run your command-line tasks. - [**WebAssembly.sh**](https://webassembly.sh): A WebAssembly WASI browser terminal +- [**FluffOS**](https://www.fluffos.info): Active maintained LPMUD driver with websocket support. [And much more...](https://github.com/xtermjs/xterm.js/network/dependents) From df4b07f9f2841bbb9ca099422b070ac4aedd6c13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 4 Jan 2020 14:47:14 +0100 Subject: [PATCH 224/268] type fixes & docs --- addons/xterm-addon-unicode11/README.md | 3 +++ addons/xterm-addon-unicode11/src/UnicodeV11.ts | 2 +- src/Terminal.test.ts | 2 +- src/Terminal.ts | 14 +++++++------- src/TestUtils.test.ts | 3 ++- src/Types.d.ts | 3 ++- src/common/input/UnicodeV6.ts | 2 +- src/common/services/Services.ts | 2 +- src/public/Terminal.ts | 8 ++++---- typings/xterm.d.ts | 10 ++++++++-- 10 files changed, 30 insertions(+), 19 deletions(-) diff --git a/addons/xterm-addon-unicode11/README.md b/addons/xterm-addon-unicode11/README.md index eff46b395..ec01203e9 100644 --- a/addons/xterm-addon-unicode11/README.md +++ b/addons/xterm-addon-unicode11/README.md @@ -17,4 +17,7 @@ import { Unicode11Addon } from 'xterm-addon-unicode11'; const terminal = new Terminal(); const unicode11Addon = new Unicode11Addon(); terminal.loadAddon(unicode11Addon); + +// activate the new version +terminal.unicode.activeVersion = '11'; ``` diff --git a/addons/xterm-addon-unicode11/src/UnicodeV11.ts b/addons/xterm-addon-unicode11/src/UnicodeV11.ts index d1525d257..d9d548d6e 100644 --- a/addons/xterm-addon-unicode11/src/UnicodeV11.ts +++ b/addons/xterm-addon-unicode11/src/UnicodeV11.ts @@ -193,7 +193,7 @@ function bisearch(ucs: number, data: number[][]): boolean { export class UnicodeV11 implements IUnicodeVersionProvider { - public version = '11'; + public readonly version = '11'; constructor() { if (!table) { diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index 25a221762..d65c2478a 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -1039,7 +1039,7 @@ describe('Terminal', () => { // to get the special handling of fullwidth, surrogate and combining chars in the input handler beforeEach(() => { terminal = new TestTerminal({ cols: 10, rows: 5 }); - linkifier = new TestLinkifier((terminal as any)._bufferService, (terminal as any)._unicodeService); + linkifier = new TestLinkifier((terminal as any)._bufferService, terminal.unicodeService); mouseZoneManager = new TestMouseZoneManager(); linkifier.attachToDom({} as any, mouseZoneManager); }); diff --git a/src/Terminal.ts b/src/Terminal.ts index 232f03ea5..8ae367687 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -99,7 +99,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp private _instantiationService: IInstantiationService; private _logService: ILogService; public optionsService: IOptionsService; - private _unicodeService: IUnicodeService; + public unicodeService: IUnicodeService; // browser services private _charSizeService: ICharSizeService; @@ -211,8 +211,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._instantiationService.setService(ICoreMouseService, this._coreMouseService); this._dirtyRowService = this._instantiationService.createInstance(DirtyRowService); this._instantiationService.setService(IDirtyRowService, this._dirtyRowService); - this._unicodeService = this._instantiationService.createInstance(UnicodeService); - this._instantiationService.setService(IUnicodeService, this._unicodeService); + this.unicodeService = this._instantiationService.createInstance(UnicodeService); + this._instantiationService.setService(IUnicodeService, this.unicodeService); this._charsetService = this._instantiationService.createInstance(CharsetService); this._instantiationService.setService(ICharsetService, this._charsetService); @@ -248,7 +248,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._inputHandler.reset(); } else { // Register input handler and refire/handle events - this._inputHandler = new InputHandler(this, this._bufferService, this._charsetService, this._coreService, this._dirtyRowService, this._logService, this.optionsService, this._coreMouseService, this._unicodeService); + this._inputHandler = new InputHandler(this, this._bufferService, this._charsetService, this._coreService, this._dirtyRowService, this._logService, this.optionsService, this._coreMouseService, this.unicodeService); this._inputHandler.onRequestBell(() => this.bell()); this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end)); this._inputHandler.onRequestReset(() => this.reset()); @@ -258,7 +258,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp } if (!this.linkifier) { - this.linkifier = new Linkifier(this._bufferService, this._logService, this.optionsService, this._unicodeService); + this.linkifier = new Linkifier(this._bufferService, this._logService, this.optionsService, this.unicodeService); } if (this.options.windowsMode) { @@ -348,8 +348,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp } break; case 'unicodeVersion': - if (this._unicodeService.activeVersion !== this.optionsService.options.unicodeVersion) { - this._unicodeService.activeVersion = this.optionsService.options.unicodeVersion; + if (this.unicodeService.activeVersion !== this.optionsService.options.unicodeVersion) { + this.unicodeService.activeVersion = this.optionsService.options.unicodeVersion; } break; } diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 57a9fe785..c6e193a73 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -13,7 +13,7 @@ import { IDisposable, IMarker, IEvent, ISelectionPosition } from 'xterm'; import { Terminal } from './Terminal'; import { AttributeData } from 'common/buffer/AttributeData'; import { IColorManager, IColorSet, ILinkMatcherOptions, ILinkifier, IViewport } from 'browser/Types'; -import { IOptionsService } from 'common/services/Services'; +import { IOptionsService, IUnicodeService } from 'common/services/Services'; import { EventEmitter } from 'common/EventEmitter'; import { IParams, IFunctionIdentifier } from 'common/parser/Types'; import { ISelectionService } from 'browser/services/Services'; @@ -41,6 +41,7 @@ export class MockTerminal implements ITerminal { onResize: IEvent<{ cols: number; rows: number; }>; markers: IMarker[]; optionsService: IOptionsService; + unicodeService: IUnicodeService; addMarker(cursorYOffset: number): IMarker { throw new Error('Method not implemented.'); } diff --git a/src/Types.d.ts b/src/Types.d.ts index ebc5d338c..0b8750298 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -7,7 +7,7 @@ import { ITerminalOptions as IPublicTerminalOptions, IDisposable, IMarker, ISele import { ICharset, IAttributeData, CharData, CoreMouseEventType } from 'common/Types'; import { IEvent, IEventEmitter } from 'common/EventEmitter'; import { IColorSet, ILinkifier, ILinkMatcherOptions, IViewport } from 'browser/Types'; -import { IOptionsService } from 'common/services/Services'; +import { IOptionsService, IUnicodeService } from 'common/services/Services'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; import { IParams, IFunctionIdentifier } from 'common/parser/Types'; @@ -140,6 +140,7 @@ export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAcc optionsService: IOptionsService; // TODO: We should remove options once components adopt optionsService options: ITerminalOptions; + unicodeService: IUnicodeService; onBlur: IEvent; onFocus: IEvent; diff --git a/src/common/input/UnicodeV6.ts b/src/common/input/UnicodeV6.ts index d6bb74588..b30820306 100644 --- a/src/common/input/UnicodeV6.ts +++ b/src/common/input/UnicodeV6.ts @@ -84,7 +84,7 @@ function bisearch(ucs: number, data: number[][]): boolean { } export class UnicodeV6 implements IUnicodeVersionProvider { - public version = '6'; + public readonly version = '6'; constructor() { // init lookup table once diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index f6bd47a8f..ce24e5c18 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -328,6 +328,6 @@ export interface IUnicodeService { } export interface IUnicodeVersionProvider { - version: string; + readonly version: string; wcwidth(ucs: number): 0 | 1 | 2; } diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index ce432ce13..4f70e3474 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -263,18 +263,18 @@ class UnicodeApi implements IUnicodeHandling { constructor(private _core: ITerminal) {} public register(provider: IUnicodeVersionProvider): void { - (this._core as any)._unicodeService.register(provider); + this._core.unicodeService.register(provider); } public get versions(): string[] { - return (this._core as any)._unicodeService.versions; + return this._core.unicodeService.versions; } public get activeVersion(): string { - return (this._core as any)._unicodeService.activeVersion; + return this._core.unicodeService.activeVersion; } public set activeVersion(version: string) { - (this._core as any)._unicodeService.activeVersion = version; + this._core.unicodeService.activeVersion = version; } } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 3229b1583..8a7464a64 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -217,6 +217,7 @@ declare module 'xterm' { /** * Unicode version to be used for wcwidth. Default is version 6. + * New unicode versions can be registered through the `Terminal.unicode.register` API. */ unicodeVersion?: string; @@ -1173,7 +1174,7 @@ declare module 'xterm' { */ export interface IUnicodeVersionProvider { /** String indicating the Unicode version provided. */ - version: string; + readonly version: string; /** Unicode version dependent wcwidth implementation. */ wcwidth(codepoint: number): 0 | 1 | 2; } @@ -1189,6 +1190,11 @@ declare module 'xterm' { /** * Registered Unicode versions. */ - versions: string[]; + readonly versions: ReadonlyArray; + /** + * Getter/setter for activate Unicode version. + * FIXME: Should we go with this instead of get/SetOption? + */ + activeVersion: string; } } From 3e1dbd4c39eb2bbd95eecad7ee913b0d7046defa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 4 Jan 2020 15:39:00 +0100 Subject: [PATCH 225/268] fix api test --- addons/xterm-addon-unicode11/src/Unicode11Addon.api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-unicode11/src/Unicode11Addon.api.ts b/addons/xterm-addon-unicode11/src/Unicode11Addon.api.ts index a731e7bba..fb721b47b 100644 --- a/addons/xterm-addon-unicode11/src/Unicode11Addon.api.ts +++ b/addons/xterm-addon-unicode11/src/Unicode11Addon.api.ts @@ -46,7 +46,7 @@ describe('Unicode11Addon', () => { await page.evaluate(`window.term.unicode.activeVersion = '11';`); assert.deepEqual(await page.evaluate(`window.term.unicode.activeVersion`), '11'); // v6: 10, V11: 20 - assert.deepEqual(await page.evaluate(`window.term._core._unicodeService.getStringCellWidth('🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣')`), 20); + assert.deepEqual(await page.evaluate(`window.term._core.unicodeService.getStringCellWidth('🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣')`), 20); }); }); From a0d724c1864f83b0cc871b0ca660fa3a905eb382 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 5 Jan 2020 22:41:36 +0100 Subject: [PATCH 226/268] move doc to impl --- src/InputHandler.ts | 284 ++++++++++++++++++++++++-------------------- 1 file changed, 153 insertions(+), 131 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index c04f11137..5868f0dc5 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -32,6 +32,7 @@ const GLEVEL: {[key: string]: number} = {'(': 0, ')': 1, '*': 2, '+': 3, '-': 1, * Document common VT features here that are currently unsupported */ // @vt: unsupported DCS SIXEL "SIXEL Graphics" "DCS Ps ; Ps ; Ps ; q Pt ST" "Draw SIXEL image starting at cursor position." +// @vt: unsupported OSC 1 "Set Icon Name" "OSC 1 ; Pt BEL" "Set icon name." /** * Max length of the UTF32 input buffer. Real memory consumption is 4 times higher. @@ -201,200 +202,77 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI handler */ this._parser.setCsiHandler({final: '@'}, params => this.insertChars(params)); - // @vt: supported CSI SL "Scroll Left" "CSI Ps SP @" "Scroll viewport `Ps` times to the left." this._parser.setCsiHandler({intermediates: ' ', final: '@'}, params => this.scrollLeft(params)); - // @vt: supported CSI CUU "Cursor Up" "CSI Ps A" "Move cursor `Ps` times up (default=1)." this._parser.setCsiHandler({final: 'A'}, params => this.cursorUp(params)); - // @vt: supported CSI SR "Scroll Right" "CSI Ps SP A" "Scroll viewport `Ps` times to the right." this._parser.setCsiHandler({intermediates: ' ', final: 'A'}, params => this.scrollRight(params)); - // @vt: supported CSI CUD "Cursor Down" "CSI Ps B" "Move cursor `Ps` times down (default=1)." this._parser.setCsiHandler({final: 'B'}, params => this.cursorDown(params)); - // @vt: supported CSI CUF "Cursor Forward" "CSI Ps C" "Move cursor `Ps` times forward (default=1)." this._parser.setCsiHandler({final: 'C'}, params => this.cursorForward(params)); - // @vt: supported CSI CUB "Cursor Backward" "CSI Ps D" "Move cursor `Ps` times backward (default=1)." this._parser.setCsiHandler({final: 'D'}, params => this.cursorBackward(params)); - // @vt: supported CSI CNL "Cursor Next Line" "CSI Ps E" "Move cursor `Ps` times down (default=1) and to the first column." this._parser.setCsiHandler({final: 'E'}, params => this.cursorNextLine(params)); - // @vt: supported CSI CPL "Cursor Backward" "CSI Ps F" "Move cursor `Ps` times up (default=1) and to the first column." this._parser.setCsiHandler({final: 'F'}, params => this.cursorPrecedingLine(params)); - // @vt: supported CSI CHA "Cursor Horizontal Absolute" "CSI Ps G" "Move cursor to `Ps`-th column of the active row (default=1)." this._parser.setCsiHandler({final: 'G'}, params => this.cursorCharAbsolute(params)); - // @vt: supported CSI CUP "Cursor Position" "CSI Ps ; Ps H" "Set cursor to position [`Ps`, `Ps`]." this._parser.setCsiHandler({final: 'H'}, params => this.cursorPosition(params)); - // @vt: supported CSI CHT "Cursor Horizontal Tabulation" "CSI Ps I" "Move cursor `Ps` times tabs forward (default=1)." this._parser.setCsiHandler({final: 'I'}, params => this.cursorForwardTab(params)); - /** - * @vt: supported CSI ED "Erase In Display" "CSI Ps J" "Erase various parts of the viewport." - * TODO: document different modes... - */ this._parser.setCsiHandler({final: 'J'}, params => this.eraseInDisplay(params)); - // @vt: partly CSI DECSED "Selective Erase In Display" "CSI ? Ps J" "Currently the same as ED." this._parser.setCsiHandler({prefix: '?', final: 'J'}, params => this.eraseInDisplay(params)); - /** - * @vt: supported CSI EL "Erase In Line" "CSI Ps K" "Erase various parts of the active row." - * TODO: document different modes... - */ this._parser.setCsiHandler({final: 'K'}, params => this.eraseInLine(params)); - // @vt: partly CSI DECSEL "Selective Erase In Line" "CSI ? Ps K" "Currently the same as EL." this._parser.setCsiHandler({prefix: '?', final: 'K'}, params => this.eraseInLine(params)); - // @vt: supported CSI IL "Insert Line" "CSI Ps L" "Insert `Ps` blank lines at active row (default=1)." this._parser.setCsiHandler({final: 'L'}, params => this.insertLines(params)); - // @vt: supported CSI DL "Delete Line" "CSI Ps M" "Delete `Ps` lines at active row (default=1)." this._parser.setCsiHandler({final: 'M'}, params => this.deleteLines(params)); - // @vt: supported CSI DCH "Delete Character" "CSI Ps P" "Delete `Ps` characters in the active row (default=1)." this._parser.setCsiHandler({final: 'P'}, params => this.deleteChars(params)); - // @vt: supported CSI SU "Scroll Up" "CSI Ps S" "Scroll `Ps` lines up (default=1)." this._parser.setCsiHandler({final: 'S'}, params => this.scrollUp(params)); - // @vt: supported CSI SD "Scroll Down" "CSI Ps T" "Scroll `Ps` lines down (default=1)." this._parser.setCsiHandler({final: 'T'}, params => this.scrollDown(params)); - // @vt: supported CSI ECH "Erase Character" "CSI Ps X" "Erase `Ps` characters from current cursor position to hte right (default=1)." this._parser.setCsiHandler({final: 'X'}, params => this.eraseChars(params)); - // @vt: supported CSI CBT "Cursor Backward Tabulation" "CSI Ps Z" "Move cursor `Ps` tabs backward (default=1)." this._parser.setCsiHandler({final: 'Z'}, params => this.cursorBackwardTab(params)); - // @vt: supported CSI HPA "Horizontal Position Absolute" "CSI Ps `" "Same as CHA." this._parser.setCsiHandler({final: '`'}, params => this.charPosAbsolute(params)); - // @vt: supported CSI HPR "Horizontal Position Relative" "CSI Ps a" "Same as CUF." this._parser.setCsiHandler({final: 'a'}, params => this.hPositionRelative(params)); - /** - * @vt: supported CSI REP "Repeat Preceding Character" "CSI Ps b" "Repeat preceding character `Ps` times (default=1)." - * Has no effect if the sequence does not follow a printed character (NOOP for any other sequence in between). - * TODO: document character limitations due to xterm compliance - */ this._parser.setCsiHandler({final: 'b'}, params => this.repeatPrecedingCharacter(params)); - /** - * @vt: supported CSI DA1 "Primary Device Attributes" "CSI c" "Send primary device attributes." - * TODO: Describe response... - */ this._parser.setCsiHandler({final: 'c'}, params => this.sendDeviceAttributesPrimary(params)); - /** - * @vt: supported CSI DA2 "Secondary Device Attributes" "CSI > c" "Send primary device attributes." - * TODO: Describe response... - */ this._parser.setCsiHandler({prefix: '>', final: 'c'}, params => this.sendDeviceAttributesSecondary(params)); - // @vt: supported CSI VPA "Vertical Position Absolute" "CSI Ps d" "Move cursor to `Ps`-th row (default=1)." this._parser.setCsiHandler({final: 'd'}, params => this.linePosAbsolute(params)); - // @vt: supported CSI VPR "Vertical Position Relative" "CSI Ps e" "Move cursor `Ps` times down (default=1)." this._parser.setCsiHandler({final: 'e'}, params => this.vPositionRelative(params)); - // @vt: supported CSI HVP "Horizontal and Vertical Position" "CSI Ps ; Ps f" "Same as CUP." this._parser.setCsiHandler({final: 'f'}, params => this.hVPosition(params)); - // @vt: supported CSI TBC "Tab Clear" "CSI Ps g" "Clear tab stops at current position (0) or all (3) (default=0)." this._parser.setCsiHandler({final: 'g'}, params => this.tabClear(params)); - /** - * @vt: partly CSI SM "Set Mode" "CSI Pm h" "Set various terminal attributes." - * TODO: Describe all supported attributes. - */ this._parser.setCsiHandler({final: 'h'}, params => this.setMode(params)); - /** - * @vt: partly CSI DECSET "DEC Private Set Mode" "CSI ? Pm h" "Set various terminal attributes." - * TODO: Describe all supported attributes. - */ this._parser.setCsiHandler({prefix: '?', final: 'h'}, params => this.setModePrivate(params)); - /** - * @vt: partly CSI RM "Reset Mode" "CSI Pm l" "Set various terminal attributes." - * TODO: Describe all supported attributes. - */ this._parser.setCsiHandler({final: 'l'}, params => this.resetMode(params)); - /** - * @vt: partly CSI DECRST "DEC Private Reset Mode" "CSI ? Pm l" "Reset various terminal attributes." - * TODO: Describe all supported attributes. - */ this._parser.setCsiHandler({prefix: '?', final: 'l'}, params => this.resetModePrivate(params)); - /** - * @vt: partly CSI SGR "Select Graphic Rendition" "CSI Pm m" "Reset various text attributes." - * Detailed description goes here... - */ this._parser.setCsiHandler({final: 'm'}, params => this.charAttributes(params)); - // @vt: supported CSI DSR "Device Status Report" "CSI Ps n" "Request cursor position (CPR) with `Ps` = 6." this._parser.setCsiHandler({final: 'n'}, params => this.deviceStatus(params)); - // @vt: partly CSI DECDSR "DEC Device Status Report" "CSI ? Ps n" "Only CPR is supported (same as DSR)." this._parser.setCsiHandler({prefix: '?', final: 'n'}, params => this.deviceStatusPrivate(params)); - /** - * @vt: supported CSI DECSTR "Soft Terminal Reset" "CSI ! p" "Reset several terminal attributes to initial state." - * There are two terminal reset sequences - RIS and DECSTR. While RIS performs almost a full terminal bootstrap, - * DECSTR only resets certain attributes. For most needs DECSTR should be sufficient. - * Attributes reset to default values: - * - TODO: list attributes here ... - */ this._parser.setCsiHandler({intermediates: '!', final: 'p'}, params => this.softReset(params)); - /** - * @vt: supported CSI DECSCUSR "Set Cursor Style" "CSI Ps SP q" "Set cursor style." - * Supported cursor styles (note that most renderers dont implement the blink feature, - * thus will show the steady variant instead): - * - empty, 0 or 1: steady block - * - 2: blink block - * - 3: steady underline - * - 4: blink underline - * - 5: steady bar - * - 6: blink bar - */ this._parser.setCsiHandler({intermediates: ' ', final: 'q'}, params => this.setCursorStyle(params)); - /** - * @vt: supported CSI DECSTBM "Set Top and Bottom Margin" "CSI Ps ; Ps r" "Set top and bottom margins of the viewport [top;bottom] (default = viewport size)." - * TODO: document specialties like dependent cursor commands and scrolling... - */ this._parser.setCsiHandler({final: 'r'}, params => this.setScrollRegion(params)); - // @vt: partly CSI SCOSC "Save Cursor" "CSI s" "Save cursor position, charmap and text attributes." this._parser.setCsiHandler({final: 's'}, params => this.saveCursor(params)); - // @vt: partly CSI SCORC "Restore Cursor" "CSI u" "Restore cursor position, charmap and text attributes." this._parser.setCsiHandler({final: 'u'}, params => this.restoreCursor(params)); - // @vt: supported CSI DECIC "Insert Columns" "CSI Ps ' }" "Insert `Ps` columns at cursor position." this._parser.setCsiHandler({intermediates: '\'', final: '}'}, params => this.insertColumns(params)); - // @vt: supported CSI DECDC "Delete Columns" "CSI Ps ' ~" "Delete `Ps` columns at cursor position." this._parser.setCsiHandler({intermediates: '\'', final: '~'}, params => this.deleteColumns(params)); /** * execute handler */ - /** - * @vt: supported C0 BEL "Bell" "\a" "Ring the bell." - * The behavior of the bell is further customizable with `ITerminalOptions.bellStyle` - * and `ITerminalOptions.bellSound`. - */ this._parser.setExecuteHandler(C0.BEL, () => this.bell()); - // @vt: supported C0 LF "Line Feed" "\n" "Move the cursor one row down, scrolling if needed." this._parser.setExecuteHandler(C0.LF, () => this.lineFeed()); - // @vt: supported C0 VT "Vertical Tabulation" "\v" "Treated as LF." this._parser.setExecuteHandler(C0.VT, () => this.lineFeed()); - // @vt: supported C0 FF "Form Feed" "\f" "Treated as LF." this._parser.setExecuteHandler(C0.FF, () => this.lineFeed()); - // @vt: supported C0 CR "Carriage Return" "\r" "Move the cursor to the beginning of the row." this._parser.setExecuteHandler(C0.CR, () => this.carriageReturn()); - // @vt: supported C0 BS "Backspace" "\b" "Move the cursor one position to the left." this._parser.setExecuteHandler(C0.BS, () => this.backspace()); - // @vt: supported C0 HT "Horizontal Tabulation" "\t" "Move the cursor to the next character tab stop." this._parser.setExecuteHandler(C0.HT, () => this.tab()); - /** - * @vt: partly C0 SO "Shift Out" "\x0e" "Switch to an alternative character set." - * TODO: document supported native character sets and support limitations ... - */ this._parser.setExecuteHandler(C0.SO, () => this.shiftOut()); - // @vt: supported C0 SI "Shift In" "\x0f" "Return to regular character set after Shift Out." this._parser.setExecuteHandler(C0.SI, () => this.shiftIn()); // FIXME: What do to with missing? Old code just added those to print. - // @vt: supported C1 IND "Index" "\x84" "Move the cursor one line down scrolling if needed." this._parser.setExecuteHandler(C1.IND, () => this.index()); - // @vt: supported C1 NEL "Next Line" "\x85" "Move the cursor to the beginning of the next row." this._parser.setExecuteHandler(C1.NEL, () => this.nextLine()); - // @vt: supported C1 HTS "Horizontal Tabulation Set" "\x88" "Places a tab stop at the current cursor position." this._parser.setExecuteHandler(C1.HTS, () => this.tabSet()); /** * OSC handler */ // 0 - icon name + title - /** - * @vt: partly OSC 0 "Set Windows Title and Icon Name" "OSC 0 ; Pt BEL" "Set window title and icon name." - * Icon name is not supported. For Window Title see below. - */ this._parser.setOscHandler(0, new OscHandler((data: string) => this.setTitle(data))); // 1 - icon name - // @vt: unsupported OSC 1 "Set Icon Name" "OSC 1 ; Pt BEL" "Set icon name." // 2 - title - /** - * @vt: supported OSC 2 "Set Windows Title" "OSC 2 ; Pt BEL" "Set window title." - * xterm.js does not manipulate the title directly, instead exposes changes via the event `Terminal.onTitleChange`. - */ this._parser.setOscHandler(2, new OscHandler((data: string) => this.setTitle(data))); // 3 - set property X in the form "prop=value" // 4 - Change Color Number @@ -432,15 +310,10 @@ export class InputHandler extends Disposable implements IInputHandler { /** * ESC handlers */ - // @vt: supported ESC SC "Save Cursor" "ESC 7" "Save cursor position, charmap and text attributes." this._parser.setEscHandler({final: '7'}, () => this.saveCursor()); - // @vt: supported ESC RC "Restore Cursor" "ESC 8" "Restore cursor position, charmap and text attributes." this._parser.setEscHandler({final: '8'}, () => this.restoreCursor()); - // @vt: supported ESC IND "Index" "ESC D" "Move the cursor one line down scrolling if needed." this._parser.setEscHandler({final: 'D'}, () => this.index()); - // @vt: supported ESC NEL "Next Line" "ESC E" "Move the cursor to the beginning of the next row." this._parser.setEscHandler({final: 'E'}, () => this.nextLine()); - // @vt: supported ESC HTS "Horizontal Tabulation Set" "ESC H" "Places a tab stop at the current cursor position." this._parser.setEscHandler({final: 'H'}, () => this.tabSet()); // @vt: supported ESC IR "Reverse Index" "ESC M" "Move the cursor one line up scrolling if needed." this._parser.setEscHandler({final: 'M'}, () => this.reverseIndex()); @@ -463,7 +336,6 @@ export class InputHandler extends Disposable implements IInputHandler { this._parser.setEscHandler({intermediates: '.', final: flag}, () => this.selectCharset('.' + flag)); this._parser.setEscHandler({intermediates: '/', final: flag}, () => this.selectCharset('/' + flag)); // TODO: supported? } - // @vt: supported ESC DECALN "Screen Alignment Pattern" "ESC # 8" "Fill viewport with a test pattern (E)." this._parser.setEscHandler({intermediates: '#', final: '8'}, () => this.screenAlignmentPattern()); /** @@ -696,6 +568,10 @@ export class InputHandler extends Disposable implements IInputHandler { /** * BEL * Bell (Ctrl-G). + * + * @vt: supported C0 BEL "Bell" "\a" "Ring the bell." + * The behavior of the bell is further customizable with `ITerminalOptions.bellStyle` + * and `ITerminalOptions.bellSound`. */ public bell(): void { this._onRequestBell.fire(); @@ -704,7 +580,11 @@ export class InputHandler extends Disposable implements IInputHandler { /** * LF * Line Feed or New Line (NL). (LF is Ctrl-J). + * + * @vt: supported C0 LF "Line Feed" "\n" "Move the cursor one row down, scrolling if needed." */ + // @vt: supported C0 VT "Vertical Tabulation" "\v" "Treated as LF." + // @vt: supported C0 FF "Form Feed" "\f" "Treated as LF." public lineFeed(): void { // make buffer local for faster access const buffer = this._bufferService.buffer; @@ -732,6 +612,8 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CR * Carriage Return (Ctrl-M). + * + * @vt: supported C0 CR "Carriage Return" "\r" "Move the cursor to the beginning of the row." */ public carriageReturn(): void { this._bufferService.buffer.x = 0; @@ -740,6 +622,8 @@ export class InputHandler extends Disposable implements IInputHandler { /** * BS * Backspace (Ctrl-H). + * + * @vt: supported C0 BS "Backspace" "\b" "Move the cursor one position to the left." */ public backspace(): void { this._restrictCursor(); @@ -751,6 +635,8 @@ export class InputHandler extends Disposable implements IInputHandler { /** * TAB * Horizontal Tab (HT) (Ctrl-I). + * + * @vt: supported C0 HT "Horizontal Tabulation" "\t" "Move the cursor to the next character tab stop." */ public tab(): void { if (this._bufferService.buffer.x >= this._bufferService.cols) { @@ -767,6 +653,9 @@ export class InputHandler extends Disposable implements IInputHandler { * SO * Shift Out (Ctrl-N) -> Switch to Alternate Character Set. This invokes the * G1 character set. + * + * @vt: partly C0 SO "Shift Out" "\x0e" "Switch to an alternative character set." + * TODO: document supported native character sets and support limitations ... */ public shiftOut(): void { this._charsetService.setgLevel(1); @@ -776,6 +665,8 @@ export class InputHandler extends Disposable implements IInputHandler { * SI * Shift In (Ctrl-O) -> Switch to Standard Character Set. This invokes the G0 * character set (the default). + * + * @vt: supported C0 SI "Shift In" "\x0f" "Return to regular character set after Shift Out." */ public shiftIn(): void { this._charsetService.setgLevel(0); @@ -821,6 +712,8 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps A * Cursor Up Ps Times (default = 1) (CUU). + * + * @vt: supported CSI CUU "Cursor Up" "CSI Ps A" "Move cursor `Ps` times up (default=1)." */ public cursorUp(params: IParams): void { // stop at scrollTop @@ -835,6 +728,8 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps B * Cursor Down Ps Times (default = 1) (CUD). + * + * @vt: supported CSI CUD "Cursor Down" "CSI Ps B" "Move cursor `Ps` times down (default=1)." */ public cursorDown(params: IParams): void { // stop at scrollBottom @@ -849,6 +744,8 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps C * Cursor Forward Ps Times (default = 1) (CUF). + * + * @vt: supported CSI CUF "Cursor Forward" "CSI Ps C" "Move cursor `Ps` times forward (default=1)." */ public cursorForward(params: IParams): void { this._moveCursor(params.params[0] || 1, 0); @@ -857,6 +754,8 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps D * Cursor Backward Ps Times (default = 1) (CUB). + * + * @vt: supported CSI CUB "Cursor Backward" "CSI Ps D" "Move cursor `Ps` times backward (default=1)." */ public cursorBackward(params: IParams): void { this._moveCursor(-(params.params[0] || 1), 0); @@ -866,6 +765,8 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps E * Cursor Next Line Ps Times (default = 1) (CNL). * Other than cursorDown (CUD) also set the cursor to first column. + * + * @vt: supported CSI CNL "Cursor Next Line" "CSI Ps E" "Move cursor `Ps` times down (default=1) and to the first column." */ public cursorNextLine(params: IParams): void { this.cursorDown(params); @@ -876,6 +777,8 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps F * Cursor Previous Line Ps Times (default = 1) (CPL). * Other than cursorUp (CUU) also set the cursor to first column. + * + * @vt: supported CSI CPL "Cursor Backward" "CSI Ps F" "Move cursor `Ps` times up (default=1) and to the first column." */ public cursorPrecedingLine(params: IParams): void { this.cursorUp(params); @@ -885,6 +788,8 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps G * Cursor Character Absolute [column] (default = [row,1]) (CHA). + * + * @vt: supported CSI CHA "Cursor Horizontal Absolute" "CSI Ps G" "Move cursor to `Ps`-th column of the active row (default=1)." */ public cursorCharAbsolute(params: IParams): void { this._setCursor((params.params[0] || 1) - 1, this._bufferService.buffer.y); @@ -893,6 +798,8 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps ; Ps H * Cursor Position [row;column] (default = [1,1]) (CUP). + * + * @vt: supported CSI CUP "Cursor Position" "CSI Ps ; Ps H" "Set cursor to position [`Ps`, `Ps`] (default = [1, 1])." */ public cursorPosition(params: IParams): void { this._setCursor( @@ -906,6 +813,8 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Pm ` Character Position Absolute * [column] (default = [row,1]) (HPA). * Currently same functionality as CHA. + * + * @vt: supported CSI HPA "Horizontal Position Absolute" "CSI Ps `" "Same as CHA." */ public charPosAbsolute(params: IParams): void { this._setCursor((params.params[0] || 1) - 1, this._bufferService.buffer.y); @@ -915,6 +824,8 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Pm a Character Position Relative * [columns] (default = [row,col+1]) (HPR) * Currently same functionality as CUF. + * + * @vt: supported CSI HPR "Horizontal Position Relative" "CSI Ps a" "Same as CUF." */ public hPositionRelative(params: IParams): void { this._moveCursor(params.params[0] || 1, 0); @@ -923,6 +834,8 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Pm d Vertical Position Absolute (VPA) * [row] (default = [1,column]) + * + * @vt: supported CSI VPA "Vertical Position Absolute" "CSI Ps d" "Move cursor to `Ps`-th row (default=1)." */ public linePosAbsolute(params: IParams): void { this._setCursor(this._bufferService.buffer.x, (params.params[0] || 1) - 1); @@ -932,6 +845,8 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Pm e Vertical Position Relative (VPR) * [rows] (default = [row+1,column]) * reuse CSI Ps B ? + * + * @vt: supported CSI VPR "Vertical Position Relative" "CSI Ps e" "Move cursor `Ps` times down (default=1)." */ public vPositionRelative(params: IParams): void { this._moveCursor(0, params.params[0] || 1); @@ -942,6 +857,8 @@ export class InputHandler extends Disposable implements IInputHandler { * Horizontal and Vertical Position [row;column] (default = * [1,1]) (HVP). * Same as CUP. + * + * @vt: supported CSI HVP "Horizontal and Vertical Position" "CSI Ps ; Ps f" "Same as CUP." */ public hVPosition(params: IParams): void { this.cursorPosition(params); @@ -954,6 +871,8 @@ export class InputHandler extends Disposable implements IInputHandler { * Potentially: * Ps = 2 -> Clear Stops on Line. * http://vt100.net/annarbor/aaa-ug/section6.html + * + * @vt: supported CSI TBC "Tab Clear" "CSI Ps g" "Clear tab stops at current position (0) or all (3) (default=0)." */ public tabClear(params: IParams): void { const param = params.params[0]; @@ -967,6 +886,8 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps I * Cursor Forward Tabulation Ps tab stops (default = 1) (CHT). + * + * @vt: supported CSI CHT "Cursor Horizontal Tabulation" "CSI Ps I" "Move cursor `Ps` times tabs forward (default=1)." */ public cursorForwardTab(params: IParams): void { if (this._bufferService.buffer.x >= this._bufferService.cols) { @@ -980,6 +901,8 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps Z Cursor Backward Tabulation Ps tab stops (default = 1) (CBT). + * + * @vt: supported CSI CBT "Cursor Backward Tabulation" "CSI Ps Z" "Move cursor `Ps` tabs backward (default=1)." */ public cursorBackwardTab(params: IParams): void { if (this._bufferService.buffer.x >= this._bufferService.cols) { @@ -1038,7 +961,11 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 0 -> Selective Erase Below (default). * Ps = 1 -> Selective Erase Above. * Ps = 2 -> Selective Erase All. + * + * @vt: supported CSI ED "Erase In Display" "CSI Ps J" "Erase various parts of the viewport." + * TODO: document different modes... */ + // @vt: partly CSI DECSED "Selective Erase In Display" "CSI ? Ps J" "Currently the same as ED." public eraseInDisplay(params: IParams): void { this._restrictCursor(); let j; @@ -1098,7 +1025,11 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 0 -> Selective Erase to Right (default). * Ps = 1 -> Selective Erase to Left. * Ps = 2 -> Selective Erase All. + * + * @vt: supported CSI EL "Erase In Line" "CSI Ps K" "Erase various parts of the active row." + * TODO: document different modes... */ + // @vt: partly CSI DECSEL "Selective Erase In Line" "CSI ? Ps K" "Currently the same as EL." public eraseInLine(params: IParams): void { this._restrictCursor(); switch (params.params[0]) { @@ -1118,6 +1049,8 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps L * Insert Ps Line(s) (default = 1) (IL). + * + * @vt: supported CSI IL "Insert Line" "CSI Ps L" "Insert `Ps` blank lines at active row (default=1)." */ public insertLines(params: IParams): void { this._restrictCursor(); @@ -1148,6 +1081,8 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps M * Delete Ps Line(s) (default = 1) (DL). + * + * @vt: supported CSI DL "Delete Line" "CSI Ps M" "Delete `Ps` lines at active row (default=1)." */ public deleteLines(params: IParams): void { this._restrictCursor(); @@ -1197,6 +1132,8 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps P * Delete Ps Character(s) (default = 1) (DCH). + * + * @vt: supported CSI DCH "Delete Character" "CSI Ps P" "Delete `Ps` characters in the active row (default=1)." */ public deleteChars(params: IParams): void { this._restrictCursor(); @@ -1214,6 +1151,8 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps S Scroll up Ps lines (default = 1) (SU). + * + * @vt: supported CSI SU "Scroll Up" "CSI Ps S" "Scroll `Ps` lines up (default=1)." */ public scrollUp(params: IParams): void { let param = params.params[0] || 1; @@ -1230,6 +1169,8 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps T Scroll down Ps lines (default = 1) (SD). + * + * @vt: supported CSI SD "Scroll Down" "CSI Ps T" "Scroll `Ps` lines down (default=1)." */ public scrollDown(params: IParams): void { let param = params.params[0] || 1; @@ -1257,6 +1198,8 @@ export class InputHandler extends Disposable implements IInputHandler { * * Supported: * - always left shift (no line orientation setting respected) + * + * @vt: supported CSI SL "Scroll Left" "CSI Ps SP @" "Scroll viewport `Ps` times to the left." */ public scrollLeft(params: IParams): void { const buffer = this._bufferService.buffer; @@ -1285,6 +1228,8 @@ export class InputHandler extends Disposable implements IInputHandler { * * Supported: * - always right shift (no line orientation setting respected) + * + * @vt: supported CSI SR "Scroll Right" "CSI Ps SP A" "Scroll viewport `Ps` times to the right." */ public scrollRight(params: IParams): void { const buffer = this._bufferService.buffer; @@ -1303,6 +1248,8 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Pm ' } * Insert Ps Column(s) (default = 1) (DECIC), VT420 and up. + * + * @vt: supported CSI DECIC "Insert Columns" "CSI Ps ' }" "Insert `Ps` columns at cursor position." */ public insertColumns(params: IParams): void { const buffer = this._bufferService.buffer; @@ -1321,6 +1268,8 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Pm ' ~ * Delete Ps Column(s) (default = 1) (DECDC), VT420 and up. + * + * @vt: supported CSI DECDC "Delete Columns" "CSI Ps ' ~" "Delete `Ps` columns at cursor position." */ public deleteColumns(params: IParams): void { const buffer = this._bufferService.buffer; @@ -1339,6 +1288,8 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps X * Erase Ps Character(s) (default = 1) (ECH). + * + * @vt: supported CSI ECH "Erase Character" "CSI Ps X" "Erase `Ps` characters from current cursor position to hte right (default=1)." */ public eraseChars(params: IParams): void { this._restrictCursor(); @@ -1375,6 +1326,10 @@ export class InputHandler extends Disposable implements IInputHandler { * * Note: To get reset on a valid sequence working correctly without much runtime penalty, * the preceding codepoint is stored on the parser in `this.print` and reset during `parser.parse`. + * + * @vt: supported CSI REP "Repeat Preceding Character" "CSI Ps b" "Repeat preceding character `Ps` times (default=1)." + * Has no effect if the sequence does not follow a printed character (NOOP for any other sequence in between). + * TODO: document character limitations due to xterm compliance */ public repeatPrecedingCharacter(params: IParams): void { if (!this._parser.precedingCodepoint) { @@ -1425,6 +1380,9 @@ export class InputHandler extends Disposable implements IInputHandler { * More information: * xterm/charproc.c - line 2012, for more information. * vim responds with ^[[?0c or ^[[?1c after the terminal's response (?) + * + * @vt: supported CSI DA1 "Primary Device Attributes" "CSI c" "Send primary device attributes." + * TODO: Describe response... */ public sendDeviceAttributesPrimary(params: IParams): void { if (params.params[0] > 0) { @@ -1436,6 +1394,10 @@ export class InputHandler extends Disposable implements IInputHandler { this._coreService.triggerDataEvent(C0.ESC + '[?6c'); } } + /** + * @vt: supported CSI DA2 "Secondary Device Attributes" "CSI > c" "Send primary device attributes." + * TODO: Describe response... + */ public sendDeviceAttributesSecondary(params: IParams): void { if (params.params[0] > 0) { return; @@ -1541,6 +1503,9 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 2 0 0 4 -> Set bracketed paste mode. * Modes: * http: *vt100.net/docs/vt220-rm/chapter4.html + * + * @vt: partly CSI SM "Set Mode" "CSI Pm h" "Set various terminal attributes." + * TODO: Describe all supported attributes. */ public setMode(params: IParams): void { for (let i = 0; i < params.length; i++) { @@ -1554,6 +1519,10 @@ export class InputHandler extends Disposable implements IInputHandler { } } } + /** + * @vt: partly CSI DECSET "DEC Private Set Mode" "CSI ? Pm h" "Set various terminal attributes." + * TODO: Describe all supported attributes. + */ public setModePrivate(params: IParams): void { for (let i = 0; i < params.length; i++) { switch (params.params[i]) { @@ -1723,6 +1692,9 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 1 0 6 0 -> Reset legacy keyboard emulation (X11R6). * Ps = 1 0 6 1 -> Reset keyboard emulation to Sun/PC style. * Ps = 2 0 0 4 -> Reset bracketed paste mode. + * + * @vt: partly CSI RM "Reset Mode" "CSI Pm l" "Set various terminal attributes." + * TODO: Describe all supported attributes. */ public resetMode(params: IParams): void { for (let i = 0; i < params.length; i++) { @@ -1736,6 +1708,10 @@ export class InputHandler extends Disposable implements IInputHandler { } } } + /** + * @vt: partly CSI DECRST "DEC Private Reset Mode" "CSI ? Pm l" "Reset various terminal attributes." + * TODO: Describe all supported attributes. + */ public resetModePrivate(params: IParams): void { for (let i = 0; i < params.length; i++) { switch (params.params[i]) { @@ -1947,6 +1923,9 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps. * Ps = 4 8 ; 5 ; Ps -> Set background color to the second * Ps. + * + * @vt: partly CSI SGR "Select Graphic Rendition" "CSI Pm m" "Set/Reset various text attributes." + * Detailed description goes here... */ public charAttributes(params: IParams): void { // Optimize a single SGR0. @@ -2068,6 +2047,8 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 5 3 -> Report Locator status as * CSI ? 5 3 n Locator available, if compiled-in, or * CSI ? 5 0 n No Locator, if not. + * + * @vt: supported CSI DSR "Device Status Report" "CSI Ps n" "Request cursor position (CPR) with `Ps` = 6." */ public deviceStatus(params: IParams): void { switch (params.params[0]) { @@ -2084,6 +2065,7 @@ export class InputHandler extends Disposable implements IInputHandler { } } + // @vt: partly CSI DECDSR "DEC Device Status Report" "CSI ? Ps n" "Only CPR is supported (same as DSR)." public deviceStatusPrivate(params: IParams): void { // modern xterm doesnt seem to // respond to any of these except ?6, 6, and 5 @@ -2116,6 +2098,12 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI ! p Soft terminal reset (DECSTR). * http://vt100.net/docs/vt220-rm/table4-10.html + * + * @vt: supported CSI DECSTR "Soft Terminal Reset" "CSI ! p" "Reset several terminal attributes to initial state." + * There are two terminal reset sequences - RIS and DECSTR. While RIS performs almost a full terminal bootstrap, + * DECSTR only resets certain attributes. For most needs DECSTR should be sufficient. + * Attributes reset to default values: + * - TODO: list attributes here ... */ public softReset(params: IParams): void { this._coreService.isCursorHidden = false; @@ -2138,6 +2126,15 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 4 -> steady underline. * Ps = 5 -> blinking bar (xterm). * Ps = 6 -> steady bar (xterm). + * + * @vt: supported CSI DECSCUSR "Set Cursor Style" "CSI Ps SP q" "Set cursor style." + * Supported cursor styles (TODO: add note about `options.cursorBlink`): + * - empty, 0 or 1: steady block + * - 2: blink block + * - 3: steady underline + * - 4: blink underline + * - 5: steady bar + * - 6: blink bar */ public setCursorStyle(params: IParams): void { const param = params.params[0] || 1; @@ -2163,6 +2160,9 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps ; Ps r * Set Scrolling Region [top;bottom] (default = full size of win- * dow) (DECSTBM). + * + * @vt: supported CSI DECSTBM "Set Top and Bottom Margin" "CSI Ps ; Ps r" "Set top and bottom margins of the viewport [top;bottom] (default = viewport size)." + * TODO: document specialties like dependent cursor commands and scrolling... */ public setScrollRegion(params: IParams): void { const top = params.params[0] || 1; @@ -2184,7 +2184,10 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI s * ESC 7 * Save cursor (ANSI.SYS). + * + * @vt: partly CSI SCOSC "Save Cursor" "CSI s" "Save cursor position, charmap and text attributes." */ + // @vt: supported ESC SC "Save Cursor" "ESC 7" "Save cursor position, charmap and text attributes." public saveCursor(params?: IParams): void { this._bufferService.buffer.savedX = this._bufferService.buffer.x; this._bufferService.buffer.savedY = this._bufferService.buffer.ybase + this._bufferService.buffer.y; @@ -2198,7 +2201,10 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI u * ESC 8 * Restore cursor (ANSI.SYS). + * + * @vt: partly CSI SCORC "Restore Cursor" "CSI u" "Restore cursor position, charmap and text attributes." */ + // @vt: supported ESC RC "Restore Cursor" "ESC 8" "Restore cursor position, charmap and text attributes." public restoreCursor(params?: IParams): void { this._bufferService.buffer.x = this._bufferService.buffer.savedX || 0; this._bufferService.buffer.y = Math.max(this._bufferService.buffer.savedY - this._bufferService.buffer.ybase, 0); @@ -2216,6 +2222,13 @@ export class InputHandler extends Disposable implements IInputHandler { * OSC 0; ST (set icon name + window title) * OSC 2; ST (set window title) * Proxy to set window title. Icon name is not supported. + * + * @vt: partly OSC 0 "Set Windows Title and Icon Name" "OSC 0 ; Pt BEL" "Set window title and icon name." + * Icon name is not supported. For Window Title see below. + */ + /** + * @vt: supported OSC 2 "Set Windows Title" "OSC 2 ; Pt BEL" "Set window title." + * xterm.js does not manipulate the title directly, instead exposes changes via the event `Terminal.onTitleChange`. */ public setTitle(data: string): void { this._terminal.handleTitle(data); @@ -2226,7 +2239,10 @@ export class InputHandler extends Disposable implements IInputHandler { * C1.NEL * DEC mnemonic: NEL (https://vt100.net/docs/vt510-rm/NEL) * Moves cursor to first position on next line. + * + * @vt: supported C1 NEL "Next Line" "\x85" "Move the cursor to the beginning of the next row." */ + // @vt: supported ESC NEL "Next Line" "ESC E" "Move the cursor to the beginning of the next row." public nextLine(): void { this._bufferService.buffer.x = 0; this.index(); @@ -2298,7 +2314,10 @@ export class InputHandler extends Disposable implements IInputHandler { * C1.IND * DEC mnemonic: IND (https://vt100.net/docs/vt510-rm/IND.html) * Moves the cursor down one line in the same column. + * + * @vt: supported C1 IND "Index" "\x84" "Move the cursor one line down scrolling if needed." */ + // @vt: supported ESC IND "Index" "ESC D" "Move the cursor one line down scrolling if needed." public index(): void { this._restrictCursor(); const buffer = this._bufferService.buffer; @@ -2318,7 +2337,10 @@ export class InputHandler extends Disposable implements IInputHandler { * DEC mnemonic: HTS (https://vt100.net/docs/vt510-rm/HTS.html) * Sets a horizontal tab stop at the column position indicated by * the value of the active column when the terminal receives an HTS. + * + * @vt: supported C1 HTS "Horizontal Tabulation Set" "\x88" "Places a tab stop at the current cursor position." */ + // @vt: supported ESC HTS "Horizontal Tabulation Set" "ESC H" "Places a tab stop at the current cursor position." public tabSet(): void { this._bufferService.buffer.tabs[this._bufferService.buffer.x] = true; } @@ -2390,8 +2412,8 @@ export class InputHandler extends Disposable implements IInputHandler { * DEC mnemonic: DECALN (https://vt100.net/docs/vt510-rm/DECALN.html) * This control function fills the complete screen area with * a test pattern (E) used for adjusting screen alignment. - * - * TODO: move DECALN into compat addon + * + * @vt: supported ESC DECALN "Screen Alignment Pattern" "ESC # 8" "Fill viewport with a test pattern (E)." */ public screenAlignmentPattern(): void { // prepare cell data From 5b2ccdbda74c4449b4f01fde320b80648d8db4ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 5 Jan 2020 23:00:46 +0100 Subject: [PATCH 227/268] make linter happy --- src/InputHandler.ts | 102 ++++++++++++++++++++++---------------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 5868f0dc5..13ab57da8 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -568,7 +568,7 @@ export class InputHandler extends Disposable implements IInputHandler { /** * BEL * Bell (Ctrl-G). - * + * * @vt: supported C0 BEL "Bell" "\a" "Ring the bell." * The behavior of the bell is further customizable with `ITerminalOptions.bellStyle` * and `ITerminalOptions.bellSound`. @@ -580,7 +580,7 @@ export class InputHandler extends Disposable implements IInputHandler { /** * LF * Line Feed or New Line (NL). (LF is Ctrl-J). - * + * * @vt: supported C0 LF "Line Feed" "\n" "Move the cursor one row down, scrolling if needed." */ // @vt: supported C0 VT "Vertical Tabulation" "\v" "Treated as LF." @@ -612,7 +612,7 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CR * Carriage Return (Ctrl-M). - * + * * @vt: supported C0 CR "Carriage Return" "\r" "Move the cursor to the beginning of the row." */ public carriageReturn(): void { @@ -622,7 +622,7 @@ export class InputHandler extends Disposable implements IInputHandler { /** * BS * Backspace (Ctrl-H). - * + * * @vt: supported C0 BS "Backspace" "\b" "Move the cursor one position to the left." */ public backspace(): void { @@ -635,7 +635,7 @@ export class InputHandler extends Disposable implements IInputHandler { /** * TAB * Horizontal Tab (HT) (Ctrl-I). - * + * * @vt: supported C0 HT "Horizontal Tabulation" "\t" "Move the cursor to the next character tab stop." */ public tab(): void { @@ -653,7 +653,7 @@ export class InputHandler extends Disposable implements IInputHandler { * SO * Shift Out (Ctrl-N) -> Switch to Alternate Character Set. This invokes the * G1 character set. - * + * * @vt: partly C0 SO "Shift Out" "\x0e" "Switch to an alternative character set." * TODO: document supported native character sets and support limitations ... */ @@ -665,7 +665,7 @@ export class InputHandler extends Disposable implements IInputHandler { * SI * Shift In (Ctrl-O) -> Switch to Standard Character Set. This invokes the G0 * character set (the default). - * + * * @vt: supported C0 SI "Shift In" "\x0f" "Return to regular character set after Shift Out." */ public shiftIn(): void { @@ -712,7 +712,7 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps A * Cursor Up Ps Times (default = 1) (CUU). - * + * * @vt: supported CSI CUU "Cursor Up" "CSI Ps A" "Move cursor `Ps` times up (default=1)." */ public cursorUp(params: IParams): void { @@ -728,7 +728,7 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps B * Cursor Down Ps Times (default = 1) (CUD). - * + * * @vt: supported CSI CUD "Cursor Down" "CSI Ps B" "Move cursor `Ps` times down (default=1)." */ public cursorDown(params: IParams): void { @@ -744,7 +744,7 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps C * Cursor Forward Ps Times (default = 1) (CUF). - * + * * @vt: supported CSI CUF "Cursor Forward" "CSI Ps C" "Move cursor `Ps` times forward (default=1)." */ public cursorForward(params: IParams): void { @@ -754,7 +754,7 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps D * Cursor Backward Ps Times (default = 1) (CUB). - * + * * @vt: supported CSI CUB "Cursor Backward" "CSI Ps D" "Move cursor `Ps` times backward (default=1)." */ public cursorBackward(params: IParams): void { @@ -765,7 +765,7 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps E * Cursor Next Line Ps Times (default = 1) (CNL). * Other than cursorDown (CUD) also set the cursor to first column. - * + * * @vt: supported CSI CNL "Cursor Next Line" "CSI Ps E" "Move cursor `Ps` times down (default=1) and to the first column." */ public cursorNextLine(params: IParams): void { @@ -777,7 +777,7 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps F * Cursor Previous Line Ps Times (default = 1) (CPL). * Other than cursorUp (CUU) also set the cursor to first column. - * + * * @vt: supported CSI CPL "Cursor Backward" "CSI Ps F" "Move cursor `Ps` times up (default=1) and to the first column." */ public cursorPrecedingLine(params: IParams): void { @@ -788,7 +788,7 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps G * Cursor Character Absolute [column] (default = [row,1]) (CHA). - * + * * @vt: supported CSI CHA "Cursor Horizontal Absolute" "CSI Ps G" "Move cursor to `Ps`-th column of the active row (default=1)." */ public cursorCharAbsolute(params: IParams): void { @@ -798,7 +798,7 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps ; Ps H * Cursor Position [row;column] (default = [1,1]) (CUP). - * + * * @vt: supported CSI CUP "Cursor Position" "CSI Ps ; Ps H" "Set cursor to position [`Ps`, `Ps`] (default = [1, 1])." */ public cursorPosition(params: IParams): void { @@ -813,7 +813,7 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Pm ` Character Position Absolute * [column] (default = [row,1]) (HPA). * Currently same functionality as CHA. - * + * * @vt: supported CSI HPA "Horizontal Position Absolute" "CSI Ps `" "Same as CHA." */ public charPosAbsolute(params: IParams): void { @@ -824,7 +824,7 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Pm a Character Position Relative * [columns] (default = [row,col+1]) (HPR) * Currently same functionality as CUF. - * + * * @vt: supported CSI HPR "Horizontal Position Relative" "CSI Ps a" "Same as CUF." */ public hPositionRelative(params: IParams): void { @@ -834,7 +834,7 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Pm d Vertical Position Absolute (VPA) * [row] (default = [1,column]) - * + * * @vt: supported CSI VPA "Vertical Position Absolute" "CSI Ps d" "Move cursor to `Ps`-th row (default=1)." */ public linePosAbsolute(params: IParams): void { @@ -845,7 +845,7 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Pm e Vertical Position Relative (VPR) * [rows] (default = [row+1,column]) * reuse CSI Ps B ? - * + * * @vt: supported CSI VPR "Vertical Position Relative" "CSI Ps e" "Move cursor `Ps` times down (default=1)." */ public vPositionRelative(params: IParams): void { @@ -857,7 +857,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Horizontal and Vertical Position [row;column] (default = * [1,1]) (HVP). * Same as CUP. - * + * * @vt: supported CSI HVP "Horizontal and Vertical Position" "CSI Ps ; Ps f" "Same as CUP." */ public hVPosition(params: IParams): void { @@ -871,7 +871,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Potentially: * Ps = 2 -> Clear Stops on Line. * http://vt100.net/annarbor/aaa-ug/section6.html - * + * * @vt: supported CSI TBC "Tab Clear" "CSI Ps g" "Clear tab stops at current position (0) or all (3) (default=0)." */ public tabClear(params: IParams): void { @@ -886,7 +886,7 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps I * Cursor Forward Tabulation Ps tab stops (default = 1) (CHT). - * + * * @vt: supported CSI CHT "Cursor Horizontal Tabulation" "CSI Ps I" "Move cursor `Ps` times tabs forward (default=1)." */ public cursorForwardTab(params: IParams): void { @@ -901,7 +901,7 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps Z Cursor Backward Tabulation Ps tab stops (default = 1) (CBT). - * + * * @vt: supported CSI CBT "Cursor Backward Tabulation" "CSI Ps Z" "Move cursor `Ps` tabs backward (default=1)." */ public cursorBackwardTab(params: IParams): void { @@ -961,7 +961,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 0 -> Selective Erase Below (default). * Ps = 1 -> Selective Erase Above. * Ps = 2 -> Selective Erase All. - * + * * @vt: supported CSI ED "Erase In Display" "CSI Ps J" "Erase various parts of the viewport." * TODO: document different modes... */ @@ -1025,7 +1025,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 0 -> Selective Erase to Right (default). * Ps = 1 -> Selective Erase to Left. * Ps = 2 -> Selective Erase All. - * + * * @vt: supported CSI EL "Erase In Line" "CSI Ps K" "Erase various parts of the active row." * TODO: document different modes... */ @@ -1049,7 +1049,7 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps L * Insert Ps Line(s) (default = 1) (IL). - * + * * @vt: supported CSI IL "Insert Line" "CSI Ps L" "Insert `Ps` blank lines at active row (default=1)." */ public insertLines(params: IParams): void { @@ -1081,7 +1081,7 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps M * Delete Ps Line(s) (default = 1) (DL). - * + * * @vt: supported CSI DL "Delete Line" "CSI Ps M" "Delete `Ps` lines at active row (default=1)." */ public deleteLines(params: IParams): void { @@ -1132,7 +1132,7 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps P * Delete Ps Character(s) (default = 1) (DCH). - * + * * @vt: supported CSI DCH "Delete Character" "CSI Ps P" "Delete `Ps` characters in the active row (default=1)." */ public deleteChars(params: IParams): void { @@ -1151,7 +1151,7 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps S Scroll up Ps lines (default = 1) (SU). - * + * * @vt: supported CSI SU "Scroll Up" "CSI Ps S" "Scroll `Ps` lines up (default=1)." */ public scrollUp(params: IParams): void { @@ -1169,7 +1169,7 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps T Scroll down Ps lines (default = 1) (SD). - * + * * @vt: supported CSI SD "Scroll Down" "CSI Ps T" "Scroll `Ps` lines down (default=1)." */ public scrollDown(params: IParams): void { @@ -1198,7 +1198,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * Supported: * - always left shift (no line orientation setting respected) - * + * * @vt: supported CSI SL "Scroll Left" "CSI Ps SP @" "Scroll viewport `Ps` times to the left." */ public scrollLeft(params: IParams): void { @@ -1228,7 +1228,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * Supported: * - always right shift (no line orientation setting respected) - * + * * @vt: supported CSI SR "Scroll Right" "CSI Ps SP A" "Scroll viewport `Ps` times to the right." */ public scrollRight(params: IParams): void { @@ -1248,7 +1248,7 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Pm ' } * Insert Ps Column(s) (default = 1) (DECIC), VT420 and up. - * + * * @vt: supported CSI DECIC "Insert Columns" "CSI Ps ' }" "Insert `Ps` columns at cursor position." */ public insertColumns(params: IParams): void { @@ -1268,7 +1268,7 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Pm ' ~ * Delete Ps Column(s) (default = 1) (DECDC), VT420 and up. - * + * * @vt: supported CSI DECDC "Delete Columns" "CSI Ps ' ~" "Delete `Ps` columns at cursor position." */ public deleteColumns(params: IParams): void { @@ -1288,7 +1288,7 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps X * Erase Ps Character(s) (default = 1) (ECH). - * + * * @vt: supported CSI ECH "Erase Character" "CSI Ps X" "Erase `Ps` characters from current cursor position to hte right (default=1)." */ public eraseChars(params: IParams): void { @@ -1326,7 +1326,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * Note: To get reset on a valid sequence working correctly without much runtime penalty, * the preceding codepoint is stored on the parser in `this.print` and reset during `parser.parse`. - * + * * @vt: supported CSI REP "Repeat Preceding Character" "CSI Ps b" "Repeat preceding character `Ps` times (default=1)." * Has no effect if the sequence does not follow a printed character (NOOP for any other sequence in between). * TODO: document character limitations due to xterm compliance @@ -1380,7 +1380,7 @@ export class InputHandler extends Disposable implements IInputHandler { * More information: * xterm/charproc.c - line 2012, for more information. * vim responds with ^[[?0c or ^[[?1c after the terminal's response (?) - * + * * @vt: supported CSI DA1 "Primary Device Attributes" "CSI c" "Send primary device attributes." * TODO: Describe response... */ @@ -1503,7 +1503,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 2 0 0 4 -> Set bracketed paste mode. * Modes: * http: *vt100.net/docs/vt220-rm/chapter4.html - * + * * @vt: partly CSI SM "Set Mode" "CSI Pm h" "Set various terminal attributes." * TODO: Describe all supported attributes. */ @@ -1692,7 +1692,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 1 0 6 0 -> Reset legacy keyboard emulation (X11R6). * Ps = 1 0 6 1 -> Reset keyboard emulation to Sun/PC style. * Ps = 2 0 0 4 -> Reset bracketed paste mode. - * + * * @vt: partly CSI RM "Reset Mode" "CSI Pm l" "Set various terminal attributes." * TODO: Describe all supported attributes. */ @@ -1923,7 +1923,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps. * Ps = 4 8 ; 5 ; Ps -> Set background color to the second * Ps. - * + * * @vt: partly CSI SGR "Select Graphic Rendition" "CSI Pm m" "Set/Reset various text attributes." * Detailed description goes here... */ @@ -2047,7 +2047,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 5 3 -> Report Locator status as * CSI ? 5 3 n Locator available, if compiled-in, or * CSI ? 5 0 n No Locator, if not. - * + * * @vt: supported CSI DSR "Device Status Report" "CSI Ps n" "Request cursor position (CPR) with `Ps` = 6." */ public deviceStatus(params: IParams): void { @@ -2098,7 +2098,7 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI ! p Soft terminal reset (DECSTR). * http://vt100.net/docs/vt220-rm/table4-10.html - * + * * @vt: supported CSI DECSTR "Soft Terminal Reset" "CSI ! p" "Reset several terminal attributes to initial state." * There are two terminal reset sequences - RIS and DECSTR. While RIS performs almost a full terminal bootstrap, * DECSTR only resets certain attributes. For most needs DECSTR should be sufficient. @@ -2126,7 +2126,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 4 -> steady underline. * Ps = 5 -> blinking bar (xterm). * Ps = 6 -> steady bar (xterm). - * + * * @vt: supported CSI DECSCUSR "Set Cursor Style" "CSI Ps SP q" "Set cursor style." * Supported cursor styles (TODO: add note about `options.cursorBlink`): * - empty, 0 or 1: steady block @@ -2160,7 +2160,7 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps ; Ps r * Set Scrolling Region [top;bottom] (default = full size of win- * dow) (DECSTBM). - * + * * @vt: supported CSI DECSTBM "Set Top and Bottom Margin" "CSI Ps ; Ps r" "Set top and bottom margins of the viewport [top;bottom] (default = viewport size)." * TODO: document specialties like dependent cursor commands and scrolling... */ @@ -2184,7 +2184,7 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI s * ESC 7 * Save cursor (ANSI.SYS). - * + * * @vt: partly CSI SCOSC "Save Cursor" "CSI s" "Save cursor position, charmap and text attributes." */ // @vt: supported ESC SC "Save Cursor" "ESC 7" "Save cursor position, charmap and text attributes." @@ -2201,7 +2201,7 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI u * ESC 8 * Restore cursor (ANSI.SYS). - * + * * @vt: partly CSI SCORC "Restore Cursor" "CSI u" "Restore cursor position, charmap and text attributes." */ // @vt: supported ESC RC "Restore Cursor" "ESC 8" "Restore cursor position, charmap and text attributes." @@ -2222,7 +2222,7 @@ export class InputHandler extends Disposable implements IInputHandler { * OSC 0; ST (set icon name + window title) * OSC 2; ST (set window title) * Proxy to set window title. Icon name is not supported. - * + * * @vt: partly OSC 0 "Set Windows Title and Icon Name" "OSC 0 ; Pt BEL" "Set window title and icon name." * Icon name is not supported. For Window Title see below. */ @@ -2239,7 +2239,7 @@ export class InputHandler extends Disposable implements IInputHandler { * C1.NEL * DEC mnemonic: NEL (https://vt100.net/docs/vt510-rm/NEL) * Moves cursor to first position on next line. - * + * * @vt: supported C1 NEL "Next Line" "\x85" "Move the cursor to the beginning of the next row." */ // @vt: supported ESC NEL "Next Line" "ESC E" "Move the cursor to the beginning of the next row." @@ -2314,7 +2314,7 @@ export class InputHandler extends Disposable implements IInputHandler { * C1.IND * DEC mnemonic: IND (https://vt100.net/docs/vt510-rm/IND.html) * Moves the cursor down one line in the same column. - * + * * @vt: supported C1 IND "Index" "\x84" "Move the cursor one line down scrolling if needed." */ // @vt: supported ESC IND "Index" "ESC D" "Move the cursor one line down scrolling if needed." @@ -2337,7 +2337,7 @@ export class InputHandler extends Disposable implements IInputHandler { * DEC mnemonic: HTS (https://vt100.net/docs/vt510-rm/HTS.html) * Sets a horizontal tab stop at the column position indicated by * the value of the active column when the terminal receives an HTS. - * + * * @vt: supported C1 HTS "Horizontal Tabulation Set" "\x88" "Places a tab stop at the current cursor position." */ // @vt: supported ESC HTS "Horizontal Tabulation Set" "ESC H" "Places a tab stop at the current cursor position." @@ -2412,7 +2412,7 @@ export class InputHandler extends Disposable implements IInputHandler { * DEC mnemonic: DECALN (https://vt100.net/docs/vt510-rm/DECALN.html) * This control function fills the complete screen area with * a test pattern (E) used for adjusting screen alignment. - * + * * @vt: supported ESC DECALN "Screen Alignment Pattern" "ESC # 8" "Fill viewport with a test pattern (E)." */ public screenAlignmentPattern(): void { From fc2df6cfd67b635b19ec858e232dbede06b25363 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 6 Jan 2020 23:58:30 +0100 Subject: [PATCH 228/268] all multiple @vt in one multiline comment --- bin/extract_vtfeatures.js | 69 +++++++++++++++++++----------------- src/InputHandler.ts | 74 +++++++++++++++++++++++++++++---------- 2 files changed, 92 insertions(+), 51 deletions(-) diff --git a/bin/extract_vtfeatures.js b/bin/extract_vtfeatures.js index 2122e19aa..7c7eeb5c5 100644 --- a/bin/extract_vtfeatures.js +++ b/bin/extract_vtfeatures.js @@ -33,8 +33,16 @@ const TYPES = [ ]; const MARKDOWN_TMPL = ` -# Supported VT features by xterm.js -Version: {{version}} + +### TODO +- improve table sorting: + - sort C0/C1 in byte order + - sort OSC in numerical order + - sort CSI/ESC/DCS in final byte order +- references + + +xterm.js version: {{version}} ### Table of Contents @@ -60,7 +68,7 @@ Version: {{version}} ### General notes -This document lists xterm.js' support of typical VT commands. The commands are grouped by their type: +This document lists xterm.js' support of terminal sequences. The sequences are grouped by their type: - C0: single byte command (7bit control characters, byte range \\x00 .. \\x1f) - C1: single byte command (8bit control characters, byte range \\x80 .. \\x9f) @@ -69,18 +77,18 @@ This document lists xterm.js' support of typical VT commands. The commands are g - DCS - Device Control String: sequence starting with \`ESC P\` (7bit) or DCS (\`\\x90\` 8bit) - OSC - Operating System Command: sequence starting with \`ESC ]\` (7bit) or OSC (\`\\x9d\` 8bit) -Application Program Command (APC), Privacy Message (PM) and Start of String (SOS) are not supported, -any sequence of these types will be ignored. +Application Program Command (APC), Privacy Message (PM) and Start of String (SOS) are recognized but not supported, +any sequence of these types will be ignored. They are also not hookable by the API. -Note that the list only contains commands implemented in xterm.js' core codebase. Missing commands are either -not supported or unstable/experimental. Furthermore addons can provide additional commands. +Note that the list only contains sequences implemented in xterm.js' core codebase. Missing sequences are either +not supported or unstable/experimental. Furthermore addons or integrations can provide additional custom sequences. -To denote the sequences the lists use the same abbreviations as xterm does: +To denote the sequences the following tables use the same abbreviations as xterm does: - \`Ps\`: A single (usually optional) numeric parameter, composed of one or more decimal digits. - \`Pm\`: A multiple numeric parameter composed of any number of single numeric parameters, separated by ; character(s), e.g. \` Ps ; Ps ; ... \`. - \`Pt\`: A text parameter composed of printable characters. Note that for most commands with \`Pt\` only - ASCII printables are specified to work. Additionally xterm.js will let any character >C1 pass as printable. + ASCII printables are specified to work. Additionally the parser will let pass any codepoint greater than C1 as printable. {{#C0.length}} @@ -219,32 +227,32 @@ To denote the sequences the lists use the same abbreviations as xterm does: {{/OSC.hasLongDescriptions}} {{/OSC.length}} - - -### TODO -- improve table sorting: - - sort C0/C1 in byte order - - sort OSC in numerical order - - sort CSI/ESC/DCS in final byte order -- references ` function createAnchorSlug(s) { return s.toLowerCase().split(' ').join('-'); } -function parseMultiLine(filename, s) { +function* parseMultiLineGen(filename, s) { if (!~s.indexOf('@vt:')) { return; } const lines = s.split('\n').map(el => el.trim().replace(/[*]/, '').replace(/\s/, '')); let grabLine = false; - const longDescription = []; - let feature; + let longDescription = []; + let feature = undefined; for (const line of lines) { if (grabLine) { if (!line) { - break; + if (feature) { + feature.longDescription = longDescription; + feature.longTarget = createAnchorSlug(feature.name); + yield feature; + } + grabLine = false; + longDescription = []; + feature = undefined; + continue; } longDescription.push(line); } @@ -253,11 +261,6 @@ function parseMultiLine(filename, s) { grabLine = true; } } - if (feature) { - feature.longDescription = longDescription; - feature.longTarget = createAnchorSlug(feature.name); - return feature; - } } function parseSingleLine(filename, s) { @@ -288,7 +291,7 @@ function postProcessData(features) { featureTable[feature.type] = []; } featureTable[feature.type].push(feature); - if (feature.longDescription) { + if (feature.longDescription.length) { featureTable[feature.type].hasLongDescriptions = true; } } @@ -311,11 +314,13 @@ function main(filenames) { if (match.index === REX_COMMENTS.lastIndex) { REX_COMMENTS.lastIndex++; } - const feature = match[1] - ? parseMultiLine(filename, match[1]) - : parseSingleLine(filename, match[2]); - if (feature) { - features.push(feature); + if (match[1]) { + for (let feature of parseMultiLineGen(filename, match[1])) { + if (feature) features.push(feature); + } + } else { + const feature = parseSingleLine(filename, match[2]); + if (feature) features.push(feature); } } leftToProcess--; diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 13ab57da8..44c68f0b4 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -28,6 +28,22 @@ import { DcsHandler } from 'common/parser/DcsParser'; */ const GLEVEL: {[key: string]: number} = {'(': 0, ')': 1, '*': 2, '+': 3, '-': 1, '.': 2}; +/** + * VT commands done by the parser - FIXME: move this to the parser? + */ +// @vt: supported ESC CSI "Control Sequence Introducer" "ESC [" "Start of a CSI sequence." +// @vt: supported ESC OSC "Operating System Command" "ESC ]" "Start of an OSC sequence." +// @vt: supported ESC DCS "Device Control String" "ESC P" "Start of a DCS sequence." +// @vt: supported ESC ST "String Terminator" "ESC \" "Terminator used for string type sequences." +// @vt: supported ESC PM "Privacy Message" "ESC ^" "Start of a privacy message." +// @vt: supported ESC APC "Application Program Command" "ESC _" "Start of an APC sequence." +// @vt: supported C1 CSI "Control Sequence Introducer" "\x9b" "Start of a CSI sequence." +// @vt: supported C1 OSC "Operating System Command" "\x9d" "Start of an OSC sequence." +// @vt: supported C1 DCS "Device Control String" "\x90" "Start of a DCS sequence." +// @vt: supported C1 ST "String Terminator" "\x9c" "Terminator used for string type sequences." +// @vt: supported C1 PM "Privacy Message" "\x9e" "Start of a privacy message." +// @vt: supported C1 APC "Application Program Command" "\x9f" "Start of an APC sequence." + /** * Document common VT features here that are currently unsupported */ @@ -50,11 +66,13 @@ const MAX_PARSEBUFFER_LENGTH = 131072; * Request Status String (DECRQSS), VT420 and up. * Response: DECRPSS (https://vt100.net/docs/vt510-rm/DECRPSS.html) * - * @vt: partly DCS DECRQSS "Request Selection or Setting" "DCS $ q Pt ST" "Request several the terminal settings." - * Supported: - * - Graphic Rendition (SGR): `DCS $ q m ST` (always reporting 0m) - * - Top and Bottom Margins (DECSTBM): `DCS $ q m ST` - * - Cursor Style (DECSCUSR): `DCS $ q SP q ST` + * @vt: partly DCS DECRQSS "Request Selection or Setting" "DCS $ q Pt ST" "Request several terminal settings." + * Supported requests and responses: + * | Type | Request | Response | + * | -------------------------------- | ----------------- | -------- | + * | Graphic Rendition (SGR) | `DCS $ q m ST` | currently broken (reporting `0m`) | + * | Top and Bottom Margins (DECSTBM) | `DCS $ q m ST` | .... | + * | Cursor Style (DECSCUSR) | `DCS $ q SP q ST` | .... | */ class DECRQSS implements IDcsHandler { private _data: Uint32Array = new Uint32Array(0); @@ -111,18 +129,24 @@ class DECRQSS implements IDcsHandler { * DCS Ps; Ps| Pt ST * DECUDK (https://vt100.net/docs/vt510-rm/DECUDK.html) * not supported + * + * @vt: unsupported DCS DECUDK "User Defined Keys" "DCS Ps ; Ps | Pt ST" "Definitions for user-defined keys." */ /** * DCS + q Pt ST (xterm) * Request Terminfo String * not implemented + * + * @vt: unsupported DCS XTGETTCAP "Request Terminfo String" "DCS + q Pt ST" "Request Terminfo String." */ /** * DCS + p Pt ST (xterm) * Set Terminfo Data * not supported + * + * @vt: unsupported DCS XTSETTCAP "Set Terminfo Data" "DCS + p Pt ST" "Set Terminfo Data." */ @@ -315,7 +339,6 @@ export class InputHandler extends Disposable implements IInputHandler { this._parser.setEscHandler({final: 'D'}, () => this.index()); this._parser.setEscHandler({final: 'E'}, () => this.nextLine()); this._parser.setEscHandler({final: 'H'}, () => this.tabSet()); - // @vt: supported ESC IR "Reverse Index" "ESC M" "Move the cursor one line up scrolling if needed." this._parser.setEscHandler({final: 'M'}, () => this.reverseIndex()); this._parser.setEscHandler({final: '='}, () => this.keypadApplicationMode()); this._parser.setEscHandler({final: '>'}, () => this.keypadNumericMode()); @@ -581,10 +604,12 @@ export class InputHandler extends Disposable implements IInputHandler { * LF * Line Feed or New Line (NL). (LF is Ctrl-J). * - * @vt: supported C0 LF "Line Feed" "\n" "Move the cursor one row down, scrolling if needed." + * @vt: supported C0 LF "Line Feed" "\n" "Move the cursor one row down, scrolling if needed." + * + * @vt: supported C0 VT "Vertical Tabulation" "\v" "Treated as LF." + * + * @vt: supported C0 FF "Form Feed" "\f" "Treated as LF." */ - // @vt: supported C0 VT "Vertical Tabulation" "\v" "Treated as LF." - // @vt: supported C0 FF "Form Feed" "\f" "Treated as LF." public lineFeed(): void { // make buffer local for faster access const buffer = this._bufferService.buffer; @@ -964,8 +989,9 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: supported CSI ED "Erase In Display" "CSI Ps J" "Erase various parts of the viewport." * TODO: document different modes... + * + * @vt: partly CSI DECSED "Selective Erase In Display" "CSI ? Ps J" "Currently the same as ED." */ - // @vt: partly CSI DECSED "Selective Erase In Display" "CSI ? Ps J" "Currently the same as ED." public eraseInDisplay(params: IParams): void { this._restrictCursor(); let j; @@ -1028,8 +1054,9 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: supported CSI EL "Erase In Line" "CSI Ps K" "Erase various parts of the active row." * TODO: document different modes... + * + * @vt: partly CSI DECSEL "Selective Erase In Line" "CSI ? Ps K" "Currently the same as EL." */ - // @vt: partly CSI DECSEL "Selective Erase In Line" "CSI ? Ps K" "Currently the same as EL." public eraseInLine(params: IParams): void { this._restrictCursor(); switch (params.params[0]) { @@ -1394,6 +1421,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._coreService.triggerDataEvent(C0.ESC + '[?6c'); } } + /** * @vt: supported CSI DA2 "Secondary Device Attributes" "CSI > c" "Send primary device attributes." * TODO: Describe response... @@ -1519,6 +1547,7 @@ export class InputHandler extends Disposable implements IInputHandler { } } } + /** * @vt: partly CSI DECSET "DEC Private Set Mode" "CSI ? Pm h" "Set various terminal attributes." * TODO: Describe all supported attributes. @@ -1708,6 +1737,7 @@ export class InputHandler extends Disposable implements IInputHandler { } } } + /** * @vt: partly CSI DECRST "DEC Private Reset Mode" "CSI ? Pm l" "Reset various terminal attributes." * TODO: Describe all supported attributes. @@ -2128,7 +2158,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 6 -> steady bar (xterm). * * @vt: supported CSI DECSCUSR "Set Cursor Style" "CSI Ps SP q" "Set cursor style." - * Supported cursor styles (TODO: add note about `options.cursorBlink`): + * Supported cursor styles (TODO: add note about `ITerminalOptions.cursorBlink`): * - empty, 0 or 1: steady block * - 2: blink block * - 3: steady underline @@ -2186,8 +2216,9 @@ export class InputHandler extends Disposable implements IInputHandler { * Save cursor (ANSI.SYS). * * @vt: partly CSI SCOSC "Save Cursor" "CSI s" "Save cursor position, charmap and text attributes." + * + * @vt: supported ESC SC "Save Cursor" "ESC 7" "Save cursor position, charmap and text attributes." */ - // @vt: supported ESC SC "Save Cursor" "ESC 7" "Save cursor position, charmap and text attributes." public saveCursor(params?: IParams): void { this._bufferService.buffer.savedX = this._bufferService.buffer.x; this._bufferService.buffer.savedY = this._bufferService.buffer.ybase + this._bufferService.buffer.y; @@ -2203,8 +2234,9 @@ export class InputHandler extends Disposable implements IInputHandler { * Restore cursor (ANSI.SYS). * * @vt: partly CSI SCORC "Restore Cursor" "CSI u" "Restore cursor position, charmap and text attributes." + * + * @vt: supported ESC RC "Restore Cursor" "ESC 8" "Restore cursor position, charmap and text attributes." */ - // @vt: supported ESC RC "Restore Cursor" "ESC 8" "Restore cursor position, charmap and text attributes." public restoreCursor(params?: IParams): void { this._bufferService.buffer.x = this._bufferService.buffer.savedX || 0; this._bufferService.buffer.y = Math.max(this._bufferService.buffer.savedY - this._bufferService.buffer.ybase, 0); @@ -2225,8 +2257,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: partly OSC 0 "Set Windows Title and Icon Name" "OSC 0 ; Pt BEL" "Set window title and icon name." * Icon name is not supported. For Window Title see below. - */ - /** + * * @vt: supported OSC 2 "Set Windows Title" "OSC 2 ; Pt BEL" "Set window title." * xterm.js does not manipulate the title directly, instead exposes changes via the event `Terminal.onTitleChange`. */ @@ -2241,8 +2272,9 @@ export class InputHandler extends Disposable implements IInputHandler { * Moves cursor to first position on next line. * * @vt: supported C1 NEL "Next Line" "\x85" "Move the cursor to the beginning of the next row." + * + * @vt: supported ESC NEL "Next Line" "ESC E" "Move the cursor to the beginning of the next row." */ - // @vt: supported ESC NEL "Next Line" "ESC E" "Move the cursor to the beginning of the next row." public nextLine(): void { this._bufferService.buffer.x = 0; this.index(); @@ -2316,8 +2348,9 @@ export class InputHandler extends Disposable implements IInputHandler { * Moves the cursor down one line in the same column. * * @vt: supported C1 IND "Index" "\x84" "Move the cursor one line down scrolling if needed." + * + * @vt: supported ESC IND "Index" "ESC D" "Move the cursor one line down scrolling if needed." */ - // @vt: supported ESC IND "Index" "ESC D" "Move the cursor one line down scrolling if needed." public index(): void { this._restrictCursor(); const buffer = this._bufferService.buffer; @@ -2339,8 +2372,9 @@ export class InputHandler extends Disposable implements IInputHandler { * the value of the active column when the terminal receives an HTS. * * @vt: supported C1 HTS "Horizontal Tabulation Set" "\x88" "Places a tab stop at the current cursor position." + * + * @vt: supported ESC HTS "Horizontal Tabulation Set" "ESC H" "Places a tab stop at the current cursor position." */ - // @vt: supported ESC HTS "Horizontal Tabulation Set" "ESC H" "Places a tab stop at the current cursor position." public tabSet(): void { this._bufferService.buffer.tabs[this._bufferService.buffer.x] = true; } @@ -2351,6 +2385,8 @@ export class InputHandler extends Disposable implements IInputHandler { * DEC mnemonic: HTS * Moves the cursor up one line in the same column. If the cursor is at the top margin, * the page scrolls down. + * + * @vt: supported ESC IR "Reverse Index" "ESC M" "Move the cursor one line up scrolling if needed." */ public reverseIndex(): void { this._restrictCursor(); From 7d8c0268ea0d70bf765d8864d319dde0f0aedb16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 7 Jan 2020 00:29:48 +0100 Subject: [PATCH 229/268] remove unicodeVersion from options --- demo/client.ts | 3 +-- src/Terminal.ts | 5 ----- src/common/services/OptionsService.ts | 1 - src/common/services/Services.ts | 2 -- typings/xterm.d.ts | 20 ++++++++++---------- 5 files changed, 11 insertions(+), 20 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 3a015710c..2a0339eb8 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -265,8 +265,7 @@ function initOptions(term: TerminalType): void { fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], logLevel: ['debug', 'info', 'warn', 'error', 'off'], rendererType: ['dom', 'canvas'], - wordSeparator: null, - unicodeVersion: ['6', '11'] + wordSeparator: null }; const options = Object.keys((term)._core.options); const booleanOptions = []; diff --git a/src/Terminal.ts b/src/Terminal.ts index 8ae367687..6a75979b3 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -347,11 +347,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._windowsMode = undefined; } break; - case 'unicodeVersion': - if (this.unicodeService.activeVersion !== this.optionsService.options.unicodeVersion) { - this.unicodeService.activeVersion = this.optionsService.options.unicodeVersion; - } - break; } }); } diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 505d16e13..7cd111ac0 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -52,7 +52,6 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({ screenKeys: false, cancelEvents: false, useFlowControl: false, - unicodeVersion: '6', wordSeparator: ' ()[]{}\',:;"`' }); diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index ce24e5c18..ce7b33f77 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -238,7 +238,6 @@ export interface IPartialTerminalOptions { scrollSensitivity?: number; tabStopWidth?: number; theme?: ITheme; - unicodeVersion?: string; windowsMode?: boolean; wordSeparator?: string; } @@ -273,7 +272,6 @@ export interface ITerminalOptions { scrollSensitivity: number; tabStopWidth: number; theme: ITheme; - unicodeVersion: string; windowsMode: boolean; wordSeparator: string; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 8a7464a64..d0479897c 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -215,12 +215,6 @@ declare module 'xterm' { */ theme?: ITheme; - /** - * Unicode version to be used for wcwidth. Default is version 6. - * New unicode versions can be registered through the `Terminal.unicode.register` API. - */ - unicodeVersion?: string; - /** * Whether "Windows mode" is enabled. Because Windows backends winpty and * conpty operate by doing line wrapping on their side, xterm.js does not @@ -1173,9 +1167,14 @@ declare module 'xterm' { * Used to register custom Unicode versions with `Terminal.unicode.register`. */ export interface IUnicodeVersionProvider { - /** String indicating the Unicode version provided. */ + /** + * String indicating the Unicode version provided. + */ readonly version: string; - /** Unicode version dependent wcwidth implementation. */ + + /** + * Unicode version dependent wcwidth implementation. + */ wcwidth(codepoint: number): 0 | 1 | 2; } @@ -1187,13 +1186,14 @@ declare module 'xterm' { * Register a custom Unicode version provider. */ register(provider: IUnicodeVersionProvider): void; + /** * Registered Unicode versions. */ readonly versions: ReadonlyArray; + /** - * Getter/setter for activate Unicode version. - * FIXME: Should we go with this instead of get/SetOption? + * Getter/setter for active Unicode version. */ activeVersion: string; } From dae8c3599b872b69565f4bc2108091197a7a7ba3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 7 Jan 2020 00:47:07 +0100 Subject: [PATCH 230/268] make linter happy --- src/InputHandler.ts | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 44c68f0b4..09d351c60 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -129,7 +129,7 @@ class DECRQSS implements IDcsHandler { * DCS Ps; Ps| Pt ST * DECUDK (https://vt100.net/docs/vt510-rm/DECUDK.html) * not supported - * + * * @vt: unsupported DCS DECUDK "User Defined Keys" "DCS Ps ; Ps | Pt ST" "Definitions for user-defined keys." */ @@ -137,7 +137,7 @@ class DECRQSS implements IDcsHandler { * DCS + q Pt ST (xterm) * Request Terminfo String * not implemented - * + * * @vt: unsupported DCS XTGETTCAP "Request Terminfo String" "DCS + q Pt ST" "Request Terminfo String." */ @@ -145,7 +145,7 @@ class DECRQSS implements IDcsHandler { * DCS + p Pt ST (xterm) * Set Terminfo Data * not supported - * + * * @vt: unsupported DCS XTSETTCAP "Set Terminfo Data" "DCS + p Pt ST" "Set Terminfo Data." */ @@ -605,9 +605,9 @@ export class InputHandler extends Disposable implements IInputHandler { * Line Feed or New Line (NL). (LF is Ctrl-J). * * @vt: supported C0 LF "Line Feed" "\n" "Move the cursor one row down, scrolling if needed." - * + * * @vt: supported C0 VT "Vertical Tabulation" "\v" "Treated as LF." - * + * * @vt: supported C0 FF "Form Feed" "\f" "Treated as LF." */ public lineFeed(): void { @@ -989,7 +989,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: supported CSI ED "Erase In Display" "CSI Ps J" "Erase various parts of the viewport." * TODO: document different modes... - * + * * @vt: partly CSI DECSED "Selective Erase In Display" "CSI ? Ps J" "Currently the same as ED." */ public eraseInDisplay(params: IParams): void { @@ -1054,7 +1054,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: supported CSI EL "Erase In Line" "CSI Ps K" "Erase various parts of the active row." * TODO: document different modes... - * + * * @vt: partly CSI DECSEL "Selective Erase In Line" "CSI ? Ps K" "Currently the same as EL." */ public eraseInLine(params: IParams): void { @@ -2216,7 +2216,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Save cursor (ANSI.SYS). * * @vt: partly CSI SCOSC "Save Cursor" "CSI s" "Save cursor position, charmap and text attributes." - * + * * @vt: supported ESC SC "Save Cursor" "ESC 7" "Save cursor position, charmap and text attributes." */ public saveCursor(params?: IParams): void { @@ -2234,7 +2234,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Restore cursor (ANSI.SYS). * * @vt: partly CSI SCORC "Restore Cursor" "CSI u" "Restore cursor position, charmap and text attributes." - * + * * @vt: supported ESC RC "Restore Cursor" "ESC 8" "Restore cursor position, charmap and text attributes." */ public restoreCursor(params?: IParams): void { @@ -2257,7 +2257,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: partly OSC 0 "Set Windows Title and Icon Name" "OSC 0 ; Pt BEL" "Set window title and icon name." * Icon name is not supported. For Window Title see below. - * + * * @vt: supported OSC 2 "Set Windows Title" "OSC 2 ; Pt BEL" "Set window title." * xterm.js does not manipulate the title directly, instead exposes changes via the event `Terminal.onTitleChange`. */ @@ -2272,7 +2272,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Moves cursor to first position on next line. * * @vt: supported C1 NEL "Next Line" "\x85" "Move the cursor to the beginning of the next row." - * + * * @vt: supported ESC NEL "Next Line" "ESC E" "Move the cursor to the beginning of the next row." */ public nextLine(): void { @@ -2348,7 +2348,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Moves the cursor down one line in the same column. * * @vt: supported C1 IND "Index" "\x84" "Move the cursor one line down scrolling if needed." - * + * * @vt: supported ESC IND "Index" "ESC D" "Move the cursor one line down scrolling if needed." */ public index(): void { @@ -2372,7 +2372,7 @@ export class InputHandler extends Disposable implements IInputHandler { * the value of the active column when the terminal receives an HTS. * * @vt: supported C1 HTS "Horizontal Tabulation Set" "\x88" "Places a tab stop at the current cursor position." - * + * * @vt: supported ESC HTS "Horizontal Tabulation Set" "ESC H" "Places a tab stop at the current cursor position." */ public tabSet(): void { @@ -2385,7 +2385,7 @@ export class InputHandler extends Disposable implements IInputHandler { * DEC mnemonic: HTS * Moves the cursor up one line in the same column. If the cursor is at the top margin, * the page scrolls down. - * + * * @vt: supported ESC IR "Reverse Index" "ESC M" "Move the cursor one line up scrolling if needed." */ public reverseIndex(): void { From e240160d397615f3bb2ccdb04a80c5801268d819 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 7 Jan 2020 01:01:46 +0100 Subject: [PATCH 231/268] break command definition on new @vt entry --- bin/extract_vtfeatures.js | 16 +++++++++++++--- src/InputHandler.ts | 7 ------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/bin/extract_vtfeatures.js b/bin/extract_vtfeatures.js index 7c7eeb5c5..de91dca5c 100644 --- a/bin/extract_vtfeatures.js +++ b/bin/extract_vtfeatures.js @@ -252,11 +252,21 @@ function* parseMultiLineGen(filename, s) { grabLine = false; longDescription = []; feature = undefined; - continue; } - longDescription.push(line); + else if (line.indexOf('@vt:') === 0) { + if (feature) { + feature.longDescription = []; + feature.longTarget = createAnchorSlug(feature.name); + yield feature; + } + grabLine = true; + longDescription = []; + feature = undefined; + } else { + longDescription.push(line); + } } - if (~line.indexOf('@vt:')) { + if (line.indexOf('@vt:') === 0) { feature = parseSingleLine(filename, line); grabLine = true; } diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 09d351c60..0e0c5ebbb 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -605,9 +605,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Line Feed or New Line (NL). (LF is Ctrl-J). * * @vt: supported C0 LF "Line Feed" "\n" "Move the cursor one row down, scrolling if needed." - * * @vt: supported C0 VT "Vertical Tabulation" "\v" "Treated as LF." - * * @vt: supported C0 FF "Form Feed" "\f" "Treated as LF." */ public lineFeed(): void { @@ -2216,7 +2214,6 @@ export class InputHandler extends Disposable implements IInputHandler { * Save cursor (ANSI.SYS). * * @vt: partly CSI SCOSC "Save Cursor" "CSI s" "Save cursor position, charmap and text attributes." - * * @vt: supported ESC SC "Save Cursor" "ESC 7" "Save cursor position, charmap and text attributes." */ public saveCursor(params?: IParams): void { @@ -2234,7 +2231,6 @@ export class InputHandler extends Disposable implements IInputHandler { * Restore cursor (ANSI.SYS). * * @vt: partly CSI SCORC "Restore Cursor" "CSI u" "Restore cursor position, charmap and text attributes." - * * @vt: supported ESC RC "Restore Cursor" "ESC 8" "Restore cursor position, charmap and text attributes." */ public restoreCursor(params?: IParams): void { @@ -2272,7 +2268,6 @@ export class InputHandler extends Disposable implements IInputHandler { * Moves cursor to first position on next line. * * @vt: supported C1 NEL "Next Line" "\x85" "Move the cursor to the beginning of the next row." - * * @vt: supported ESC NEL "Next Line" "ESC E" "Move the cursor to the beginning of the next row." */ public nextLine(): void { @@ -2348,7 +2343,6 @@ export class InputHandler extends Disposable implements IInputHandler { * Moves the cursor down one line in the same column. * * @vt: supported C1 IND "Index" "\x84" "Move the cursor one line down scrolling if needed." - * * @vt: supported ESC IND "Index" "ESC D" "Move the cursor one line down scrolling if needed." */ public index(): void { @@ -2372,7 +2366,6 @@ export class InputHandler extends Disposable implements IInputHandler { * the value of the active column when the terminal receives an HTS. * * @vt: supported C1 HTS "Horizontal Tabulation Set" "\x88" "Places a tab stop at the current cursor position." - * * @vt: supported ESC HTS "Horizontal Tabulation Set" "ESC H" "Places a tab stop at the current cursor position." */ public tabSet(): void { From c50cd4cc3ef603a90d9303cd5816c6a3e97b38b4 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 7 Jan 2020 09:15:04 -0800 Subject: [PATCH 232/268] Flag lines as wrapped after CUP in windows mode Fixes #2666 --- src/Terminal.test.ts | 42 ++++++++++++++++++++++++++++++++++++++++++ src/Terminal.ts | 12 +++++++++++- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index 90522bf4f..9ea3a1841 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -1359,6 +1359,48 @@ describe('Terminal', () => { }).to.not.throw(); }); }); + + describe('Windows Mode', () => { + it('should mark lines as wrapped when the line ends in a non-null character after a LF', () => { + const data = [ + 'aaaaaaaaaa\n\r', // cannot wrap as it's the first + 'aaaaaaaaa\n\r', // wrapped (windows mode only) + 'aaaaaaaaa' // not wrapped + ]; + + const normalTerminal = new TestTerminal({rows: 5, cols: 10, windowsMode: false}); + normalTerminal.writeSync(data.join('')); + assert.equal(normalTerminal.buffer.lines.get(0).isWrapped, false); + assert.equal(normalTerminal.buffer.lines.get(1).isWrapped, false); + assert.equal(normalTerminal.buffer.lines.get(2).isWrapped, false); + + const windowsModeTerminal = new TestTerminal({rows: 5, cols: 10, windowsMode: true}); + windowsModeTerminal.writeSync(data.join('')); + assert.equal(windowsModeTerminal.buffer.lines.get(0).isWrapped, false); + assert.equal(windowsModeTerminal.buffer.lines.get(1).isWrapped, true, 'This line should wrap in Windows mode as the previous line ends in a non-null character'); + assert.equal(windowsModeTerminal.buffer.lines.get(2).isWrapped, false); + }); + + it('should mark lines as wrapped when the line ends in a non-null character after a CUP', () => { + const data = [ + 'aaaaaaaaaa\x1b[2;1H', // cannot wrap as it's the first + 'aaaaaaaaa\x1b[3;1H', // wrapped (windows mode only) + 'aaaaaaaaa' // not wrapped + ]; + + const normalTerminal = new TestTerminal({rows: 5, cols: 10, windowsMode: false}); + normalTerminal.writeSync(data.join('')); + assert.equal(normalTerminal.buffer.lines.get(0).isWrapped, false); + assert.equal(normalTerminal.buffer.lines.get(1).isWrapped, false); + assert.equal(normalTerminal.buffer.lines.get(2).isWrapped, false); + + const windowsModeTerminal = new TestTerminal({rows: 5, cols: 10, windowsMode: true}); + windowsModeTerminal.writeSync(data.join('')); + assert.equal(windowsModeTerminal.buffer.lines.get(0).isWrapped, false); + assert.equal(windowsModeTerminal.buffer.lines.get(1).isWrapped, true, 'This line should wrap in Windows mode as the previous line ends in a non-null character'); + assert.equal(windowsModeTerminal.buffer.lines.get(2).isWrapped, false); + }); + }); }); class TestLinkifier extends Linkifier { diff --git a/src/Terminal.ts b/src/Terminal.ts index 74c2d00ea..85cfbe67c 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -264,7 +264,17 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp private _enableWindowsMode(): void { if (!this._windowsMode) { - this._windowsMode = this.onLineFeed(handleWindowsModeLineFeed.bind(null, this._bufferService)); + const disposables: IDisposable[] = []; + disposables.push(this.onLineFeed(handleWindowsModeLineFeed.bind(null, this._bufferService))); + disposables.push(this.addCsiHandler({ final: 'H' }, () => { + handleWindowsModeLineFeed(this._bufferService); + return false; + })); + this._windowsMode = { + dispose: () => { + disposables.forEach(d => d.dispose()); + } + }; } } From 685d14c4d52dc2fe750437499a63169c68dc3f38 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 7 Jan 2020 09:16:16 -0800 Subject: [PATCH 233/268] Improve windows mode method name --- src/Terminal.ts | 6 +++--- src/common/WindowsMode.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 85cfbe67c..7d0def9bd 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -43,7 +43,7 @@ import { IKeyboardEvent, KeyboardResultType, IBufferLine, IAttributeData, CoreMo import { evaluateKeyboardEvent } from 'common/input/Keyboard'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; -import { handleWindowsModeLineFeed } from 'common/WindowsMode'; +import { updateWindowsModeWrappedState } from 'common/WindowsMode'; import { ColorManager } from 'browser/ColorManager'; import { RenderService } from 'browser/services/RenderService'; import { IOptionsService, IBufferService, ICoreMouseService, ICoreService, ILogService, IDirtyRowService, IInstantiationService, ICharsetService } from 'common/services/Services'; @@ -265,9 +265,9 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp private _enableWindowsMode(): void { if (!this._windowsMode) { const disposables: IDisposable[] = []; - disposables.push(this.onLineFeed(handleWindowsModeLineFeed.bind(null, this._bufferService))); + disposables.push(this.onLineFeed(updateWindowsModeWrappedState.bind(null, this._bufferService))); disposables.push(this.addCsiHandler({ final: 'H' }, () => { - handleWindowsModeLineFeed(this._bufferService); + updateWindowsModeWrappedState(this._bufferService); return false; })); this._windowsMode = { diff --git a/src/common/WindowsMode.ts b/src/common/WindowsMode.ts index ff0e75913..7cff094b2 100644 --- a/src/common/WindowsMode.ts +++ b/src/common/WindowsMode.ts @@ -6,7 +6,7 @@ import { CHAR_DATA_CODE_INDEX, NULL_CELL_CODE, WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; import { IBufferService } from 'common/services/Services'; -export function handleWindowsModeLineFeed(bufferService: IBufferService): void { +export function updateWindowsModeWrappedState(bufferService: IBufferService): void { // Winpty does not support wraparound mode which means that lines will never // be marked as wrapped. This causes issues for things like copying a line // retaining the wrapped new line characters or if consumers are listening From 016d176cbde304e854de8be20ae5405d5e0f5a6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 8 Jan 2020 17:51:56 +0100 Subject: [PATCH 234/268] more docs --- src/InputHandler.ts | 100 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 84 insertions(+), 16 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 0e0c5ebbb..41a6c0e7c 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -67,12 +67,22 @@ const MAX_PARSEBUFFER_LENGTH = 131072; * Response: DECRPSS (https://vt100.net/docs/vt510-rm/DECRPSS.html) * * @vt: partly DCS DECRQSS "Request Selection or Setting" "DCS $ q Pt ST" "Request several terminal settings." + * Response is in the form `ESC P 1 $ r Pt ST` for valid requests, where `Pt` contains the corresponding CSI string, + * `ESC P 0 ST` for invalid requests. * Supported requests and responses: - * | Type | Request | Response | - * | -------------------------------- | ----------------- | -------- | - * | Graphic Rendition (SGR) | `DCS $ q m ST` | currently broken (reporting `0m`) | - * | Top and Bottom Margins (DECSTBM) | `DCS $ q m ST` | .... | - * | Cursor Style (DECSCUSR) | `DCS $ q SP q ST` | .... | + * | Type | Request | Response (`Pt`) | + * | -------------------------------- | ----------------- | ------------------ | + * | Graphic Rendition (SGR) | `DCS $ q m ST` | always reporting `0m` (currently broken) | + * | Top and Bottom Margins (DECSTBM) | `DCS $ q r ST` | `Ptop ; Pbottom r` | + * | Cursor Style (DECSCUSR) | `DCS $ q SP q ST` | `Pstyle SP q` | + * | Protection Attribute (DECSCA) | `DCS $ q " q ST` | always reporting `0 " q` (DECSCA is unsupported) | + * | Conformance Level (DECSCL) | `DCS $ q " p ST` | always reporting `61 ; 1 " p` (DECSCL is unsupported) | + * + * TODO: + * - fix SGR report + * - either implement DECSCA or remove the report + * - either check which conformance is better suited or remove the report completely + * --> we are currently a mixture of all up to VT400 but dont follow anyone strictly */ class DECRQSS implements IDcsHandler { private _data: Uint32Array = new Uint32Array(0); @@ -104,7 +114,7 @@ class DECRQSS implements IDcsHandler { case '"q': // DECSCA return this._coreService.triggerDataEvent(`${C0.ESC}P1$r0"q${C0.ESC}\\`); case '"p': // DECSCL - return this._coreService.triggerDataEvent(`${C0.ESC}P1$r61"p${C0.ESC}\\`); + return this._coreService.triggerDataEvent(`${C0.ESC}P1$r61;1"p${C0.ESC}\\`); case 'r': // DECSTBM const pt = '' + (this._bufferService.buffer.scrollTop + 1) + ';' + (this._bufferService.buffer.scrollBottom + 1) + 'r'; @@ -605,6 +615,8 @@ export class InputHandler extends Disposable implements IInputHandler { * Line Feed or New Line (NL). (LF is Ctrl-J). * * @vt: supported C0 LF "Line Feed" "\n" "Move the cursor one row down, scrolling if needed." + * Scrolling is restricted to scroll margins and will only happen on the bottom line. + * * @vt: supported C0 VT "Vertical Tabulation" "\v" "Treated as LF." * @vt: supported C0 FF "Form Feed" "\f" "Treated as LF." */ @@ -678,7 +690,6 @@ export class InputHandler extends Disposable implements IInputHandler { * G1 character set. * * @vt: partly C0 SO "Shift Out" "\x0e" "Switch to an alternative character set." - * TODO: document supported native character sets and support limitations ... */ public shiftOut(): void { this._charsetService.setgLevel(1); @@ -737,6 +748,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Cursor Up Ps Times (default = 1) (CUU). * * @vt: supported CSI CUU "Cursor Up" "CSI Ps A" "Move cursor `Ps` times up (default=1)." + * If the cursor would pass the top scroll margin, it will stop there. */ public cursorUp(params: IParams): void { // stop at scrollTop @@ -753,6 +765,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Cursor Down Ps Times (default = 1) (CUD). * * @vt: supported CSI CUD "Cursor Down" "CSI Ps B" "Move cursor `Ps` times down (default=1)." + * If the cursor would pass the bottom scroll margin, it will stop there. */ public cursorDown(params: IParams): void { // stop at scrollBottom @@ -790,6 +803,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Other than cursorDown (CUD) also set the cursor to first column. * * @vt: supported CSI CNL "Cursor Next Line" "CSI Ps E" "Move cursor `Ps` times down (default=1) and to the first column." + * Same as CUD, additionally places the cursor at the first column. */ public cursorNextLine(params: IParams): void { this.cursorDown(params); @@ -802,6 +816,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Other than cursorUp (CUU) also set the cursor to first column. * * @vt: supported CSI CPL "Cursor Backward" "CSI Ps F" "Move cursor `Ps` times up (default=1) and to the first column." + * Same as CUU, additionally places the cursor at the first column. */ public cursorPrecedingLine(params: IParams): void { this.cursorUp(params); @@ -823,6 +838,9 @@ export class InputHandler extends Disposable implements IInputHandler { * Cursor Position [row;column] (default = [1,1]) (CUP). * * @vt: supported CSI CUP "Cursor Position" "CSI Ps ; Ps H" "Set cursor to position [`Ps`, `Ps`] (default = [1, 1])." + * If ORIGIN mode is set, places the cursor to the absolute position within the scroll margins. + * If ORIGIN mode is not set, places the cursor to the absolute position within the viewport. + * Note that the coordinates are 1-based, thus the top left position starts at `1 ; 1`. */ public cursorPosition(params: IParams): void { this._setCursor( @@ -846,7 +864,6 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Pm a Character Position Relative * [columns] (default = [row,col+1]) (HPR) - * Currently same functionality as CUF. * * @vt: supported CSI HPR "Horizontal Position Relative" "CSI Ps a" "Same as CUF." */ @@ -896,6 +913,7 @@ export class InputHandler extends Disposable implements IInputHandler { * http://vt100.net/annarbor/aaa-ug/section6.html * * @vt: supported CSI TBC "Tab Clear" "CSI Ps g" "Clear tab stops at current position (0) or all (3) (default=0)." + * Clearing tabstops off the active row (Ps = 2, VT100) is currently not supported. */ public tabClear(params: IParams): void { const param = params.params[0]; @@ -986,7 +1004,13 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 2 -> Selective Erase All. * * @vt: supported CSI ED "Erase In Display" "CSI Ps J" "Erase various parts of the viewport." - * TODO: document different modes... + * Supported param values: + * | Ps | Effect | + * | -- | ------------------------------------------------------------ | + * | 0 | Erase from the cursor through the end of the viewport. | + * | 1 | Erase from the beginning of the viewport through the cursor. | + * | 2 | Erase complete viewport. | + * | 3 | Erase scrollback. | * * @vt: partly CSI DECSED "Selective Erase In Display" "CSI ? Ps J" "Currently the same as ED." */ @@ -1051,7 +1075,12 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 2 -> Selective Erase All. * * @vt: supported CSI EL "Erase In Line" "CSI Ps K" "Erase various parts of the active row." - * TODO: document different modes... + * Supported param values: + * | Ps | Effect | + * | -- | -------------------------------------------------------- | + * | 0 | Erase from the cursor through the end of the row. | + * | 1 | Erase from the beginning of the line through the cursor. | + * | 2 | Erase complete line. | * * @vt: partly CSI DECSEL "Selective Erase In Line" "CSI ? Ps K" "Currently the same as EL." */ @@ -1076,6 +1105,9 @@ export class InputHandler extends Disposable implements IInputHandler { * Insert Ps Line(s) (default = 1) (IL). * * @vt: supported CSI IL "Insert Line" "CSI Ps L" "Insert `Ps` blank lines at active row (default=1)." + * For every inserted line at the scroll top one line at the scroll bottom gets removed. + * The cursor is set to the first column. + * IL has no effect if the cursor is outside the scroll margins. */ public insertLines(params: IParams): void { this._restrictCursor(); @@ -1108,6 +1140,9 @@ export class InputHandler extends Disposable implements IInputHandler { * Delete Ps Line(s) (default = 1) (DL). * * @vt: supported CSI DL "Delete Line" "CSI Ps M" "Delete `Ps` lines at active row (default=1)." + * For every deleted line at the scroll top one blank line at the scroll bottom gets appended. + * The cursor is set to the first column. + * DL has no effect if the cursor is outside the scroll margins. */ public deleteLines(params: IParams): void { this._restrictCursor(); @@ -1139,6 +1174,12 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps @ * Insert Ps (Blank) Character(s) (default = 1) (ICH). + * + * @vt: supported CSI ICH "Insert Characters" "CSI Ps @" "Insert `Ps` (blank) characters (default = 1)." + * The ICH sequence inserts `Ps` blank characters. The cursor remains at the beginning of the blank characters. + * Text between the cursor and right margin moves to the right. Characters moved past the right margin are lost. + * + * FIXME: check against xterm - should not work outside of scroll margins (see VT520 manual) */ public insertChars(params: IParams): void { this._restrictCursor(); @@ -1158,7 +1199,11 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps P * Delete Ps Character(s) (default = 1) (DCH). * - * @vt: supported CSI DCH "Delete Character" "CSI Ps P" "Delete `Ps` characters in the active row (default=1)." + * @vt: supported CSI DCH "Delete Character" "CSI Ps P" "Delete `Ps` characters (default=1)." + * As characters are deleted, the remaining characters between the cursor and right margin move to the left. + * Character attributes move with the characters. The terminal adds blank characters at the right margin. + * + * FIXME: check against xterm - should not work outside of scroll margins (see VT520 manual) */ public deleteChars(params: IParams): void { this._restrictCursor(); @@ -1178,6 +1223,9 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps S Scroll up Ps lines (default = 1) (SU). * * @vt: supported CSI SU "Scroll Up" "CSI Ps S" "Scroll `Ps` lines up (default=1)." + * TODO: explain behavior... + * + * FIXME: scrolled out lines at top = 1 should add to scrollback (xterm) */ public scrollUp(params: IParams): void { let param = params.params[0] || 1; @@ -1196,6 +1244,7 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps T Scroll down Ps lines (default = 1) (SD). * * @vt: supported CSI SD "Scroll Down" "CSI Ps T" "Scroll `Ps` lines down (default=1)." + * TODO: explain behavior... */ public scrollDown(params: IParams): void { let param = params.params[0] || 1; @@ -1225,6 +1274,8 @@ export class InputHandler extends Disposable implements IInputHandler { * - always left shift (no line orientation setting respected) * * @vt: supported CSI SL "Scroll Left" "CSI Ps SP @" "Scroll viewport `Ps` times to the left." + * SL moves the content of all lines within the scroll margins `Ps` times to the left. + * SL has no effect outside of the scroll margins. */ public scrollLeft(params: IParams): void { const buffer = this._bufferService.buffer; @@ -1255,6 +1306,9 @@ export class InputHandler extends Disposable implements IInputHandler { * - always right shift (no line orientation setting respected) * * @vt: supported CSI SR "Scroll Right" "CSI Ps SP A" "Scroll viewport `Ps` times to the right." + * SL moves the content of all lines within the scroll margins `Ps` times to the right. + * Content at the right margin is lost. + * SL has no effect outside of the scroll margins. */ public scrollRight(params: IParams): void { const buffer = this._bufferService.buffer; @@ -1275,6 +1329,9 @@ export class InputHandler extends Disposable implements IInputHandler { * Insert Ps Column(s) (default = 1) (DECIC), VT420 and up. * * @vt: supported CSI DECIC "Insert Columns" "CSI Ps ' }" "Insert `Ps` columns at cursor position." + * DECIC inserts `Ps` times blank columns at the cursor position for all lines with the scroll margins, + * moving content to the right. Content at the right margin is lost. + * DECIC has no effect outside the scrolling margins. */ public insertColumns(params: IParams): void { const buffer = this._bufferService.buffer; @@ -1295,6 +1352,9 @@ export class InputHandler extends Disposable implements IInputHandler { * Delete Ps Column(s) (default = 1) (DECDC), VT420 and up. * * @vt: supported CSI DECDC "Delete Columns" "CSI Ps ' ~" "Delete `Ps` columns at cursor position." + * DECDC deletes `Ps` times columns at the cursor position for all lines with the scroll margins, + * moving content to the left. Blank columns are added at the right margin. + * DECDC has no effect outside the scrolling margins. */ public deleteColumns(params: IParams): void { const buffer = this._bufferService.buffer; @@ -1314,7 +1374,9 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps X * Erase Ps Character(s) (default = 1) (ECH). * - * @vt: supported CSI ECH "Erase Character" "CSI Ps X" "Erase `Ps` characters from current cursor position to hte right (default=1)." + * @vt: supported CSI ECH "Erase Character" "CSI Ps X" "Erase `Ps` characters from current cursor position to the right (default=1)." + * ED erases `Ps` characters from current cursor position to the right. + * ED works inside or outside the scrolling margins. */ public eraseChars(params: IParams): void { this._restrictCursor(); @@ -1353,8 +1415,9 @@ export class InputHandler extends Disposable implements IInputHandler { * the preceding codepoint is stored on the parser in `this.print` and reset during `parser.parse`. * * @vt: supported CSI REP "Repeat Preceding Character" "CSI Ps b" "Repeat preceding character `Ps` times (default=1)." - * Has no effect if the sequence does not follow a printed character (NOOP for any other sequence in between). - * TODO: document character limitations due to xterm compliance + * REP repeats the previous character `Ps` times advancing the cursor, also wrapping if DECAWM is set. + * REP no effect if the sequence does not follow a printable ASCII character + * (NOOP for any other sequence in between or NON ASCII characters). */ public repeatPrecedingCharacter(params: IParams): void { if (!this._parser.precedingCodepoint) { @@ -2131,7 +2194,13 @@ export class InputHandler extends Disposable implements IInputHandler { * There are two terminal reset sequences - RIS and DECSTR. While RIS performs almost a full terminal bootstrap, * DECSTR only resets certain attributes. For most needs DECSTR should be sufficient. * Attributes reset to default values: - * - TODO: list attributes here ... + * - cursor is reset (default = visible, home position) + * - IRM is reset (dafault = false) + * - scroll margins are reset (default = viewport size) + * - erase attributes are reset to default + * - charsets are reset + * + * FIXME: there are several more attributes missing (see VT520 manual) */ public softReset(params: IParams): void { this._coreService.isCursorHidden = false; @@ -2190,7 +2259,6 @@ export class InputHandler extends Disposable implements IInputHandler { * dow) (DECSTBM). * * @vt: supported CSI DECSTBM "Set Top and Bottom Margin" "CSI Ps ; Ps r" "Set top and bottom margins of the viewport [top;bottom] (default = viewport size)." - * TODO: document specialties like dependent cursor commands and scrolling... */ public setScrollRegion(params: IParams): void { const top = params.params[0] || 1; From 297ea529a2dae92fb108f1d3d2427527cc65f77d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 8 Jan 2020 18:08:39 +0100 Subject: [PATCH 235/268] make linter happy --- src/InputHandler.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 41a6c0e7c..8279d03ed 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -68,7 +68,7 @@ const MAX_PARSEBUFFER_LENGTH = 131072; * * @vt: partly DCS DECRQSS "Request Selection or Setting" "DCS $ q Pt ST" "Request several terminal settings." * Response is in the form `ESC P 1 $ r Pt ST` for valid requests, where `Pt` contains the corresponding CSI string, - * `ESC P 0 ST` for invalid requests. + * `ESC P 0 ST` for invalid requests.\ * Supported requests and responses: * | Type | Request | Response (`Pt`) | * | -------------------------------- | ----------------- | ------------------ | @@ -77,7 +77,7 @@ const MAX_PARSEBUFFER_LENGTH = 131072; * | Cursor Style (DECSCUSR) | `DCS $ q SP q ST` | `Pstyle SP q` | * | Protection Attribute (DECSCA) | `DCS $ q " q ST` | always reporting `0 " q` (DECSCA is unsupported) | * | Conformance Level (DECSCL) | `DCS $ q " p ST` | always reporting `61 ; 1 " p` (DECSCL is unsupported) | - * + * * TODO: * - fix SGR report * - either implement DECSCA or remove the report @@ -472,7 +472,6 @@ export class InputHandler extends Disposable implements IInputHandler { } // insert combining char at last cursor position - // FIXME: needs handling after cursor jumps // buffer.x should never be 0 for a combining char // since they always follow a cell consuming char // therefore we can test for buffer.x to avoid overflow left @@ -489,10 +488,8 @@ export class InputHandler extends Disposable implements IInputHandler { } // goto next line if ch would overflow - // TODO: needs a global min terminal width of 2 - // FIXME: additionally ensure chWidth fits into a line - // --> maybe forbid cols= cols) { // autowrap - DECAWM // automatically wraps to the beginning of the next line @@ -616,7 +613,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: supported C0 LF "Line Feed" "\n" "Move the cursor one row down, scrolling if needed." * Scrolling is restricted to scroll margins and will only happen on the bottom line. - * + * * @vt: supported C0 VT "Vertical Tabulation" "\v" "Treated as LF." * @vt: supported C0 FF "Form Feed" "\f" "Treated as LF." */ @@ -1224,7 +1221,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: supported CSI SU "Scroll Up" "CSI Ps S" "Scroll `Ps` lines up (default=1)." * TODO: explain behavior... - * + * * FIXME: scrolled out lines at top = 1 should add to scrollback (xterm) */ public scrollUp(params: IParams): void { @@ -2199,7 +2196,7 @@ export class InputHandler extends Disposable implements IInputHandler { * - scroll margins are reset (default = viewport size) * - erase attributes are reset to default * - charsets are reset - * + * * FIXME: there are several more attributes missing (see VT520 manual) */ public softReset(params: IParams): void { From b51226f76c48d117a5e4c33130dc4a4579144aff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 9 Jan 2020 00:00:19 +0100 Subject: [PATCH 236/268] docs for SM/RM/DECSET/DECRST/SGR --- src/InputHandler.ts | 305 +++++++++++++++++++++++++++----------------- 1 file changed, 186 insertions(+), 119 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 8279d03ed..16a19d953 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -1220,7 +1220,6 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps S Scroll up Ps lines (default = 1) (SU). * * @vt: supported CSI SU "Scroll Up" "CSI Ps S" "Scroll `Ps` lines up (default=1)." - * TODO: explain behavior... * * FIXME: scrolled out lines at top = 1 should add to scrollback (xterm) */ @@ -1241,7 +1240,6 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps T Scroll down Ps lines (default = 1) (SD). * * @vt: supported CSI SD "Scroll Down" "CSI Ps T" "Scroll `Ps` lines down (default=1)." - * TODO: explain behavior... */ public scrollDown(params: IParams): void { let param = params.params[0] || 1; @@ -1448,6 +1446,23 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 1 5 -> Technical characters. * Ps = 2 2 -> ANSI color, e.g., VT525. * Ps = 2 9 -> ANSI text locator (i.e., DEC Locator mode). + * + * @vt: supported CSI DA1 "Primary Device Attributes" "CSI c" "Send primary device attributes." + * + * TODO: fix and cleanup response + */ + public sendDeviceAttributesPrimary(params: IParams): void { + if (params.params[0] > 0) { + return; + } + if (this._terminal.is('xterm') || this._terminal.is('rxvt-unicode') || this._terminal.is('screen')) { + this._coreService.triggerDataEvent(C0.ESC + '[?1;2c'); + } else if (this._terminal.is('linux')) { + this._coreService.triggerDataEvent(C0.ESC + '[?6c'); + } + } + + /** * CSI > Ps c * Send Device Attributes (Secondary DA). * Ps = 0 or omitted -> request the terminal's identification @@ -1466,23 +1481,9 @@ export class InputHandler extends Disposable implements IInputHandler { * xterm/charproc.c - line 2012, for more information. * vim responds with ^[[?0c or ^[[?1c after the terminal's response (?) * - * @vt: supported CSI DA1 "Primary Device Attributes" "CSI c" "Send primary device attributes." - * TODO: Describe response... - */ - public sendDeviceAttributesPrimary(params: IParams): void { - if (params.params[0] > 0) { - return; - } - if (this._terminal.is('xterm') || this._terminal.is('rxvt-unicode') || this._terminal.is('screen')) { - this._coreService.triggerDataEvent(C0.ESC + '[?1;2c'); - } else if (this._terminal.is('linux')) { - this._coreService.triggerDataEvent(C0.ESC + '[?6c'); - } - } - - /** * @vt: supported CSI DA2 "Secondary Device Attributes" "CSI > c" "Send primary device attributes." - * TODO: Describe response... + * + * TODO: fix and cleanup response */ public sendDeviceAttributesSecondary(params: IParams): void { if (params.params[0] > 0) { @@ -1510,6 +1511,32 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 4 -> Insert Mode (IRM). * Ps = 1 2 -> Send/receive (SRM). * Ps = 2 0 -> Automatic Newline (LNM). + * + * @vt: partly CSI SM "Set Mode" "CSI Pm h" "Set various terminal modes." + * Supported param values by SM: + * | Param | Action | Status | + * | ----- | -------------------------------------- | ----------- | + * | 2 | Keyboard Action Mode (KAM). Always on. | unsupported | + * | 4 | Insert Mode (IRM). | supported | + * | 12 | Send/receive (SRM). Always off. | unsupported | + * | 20 | Automatic Newline (LNM). Always off. | unsupported | + * + * FIXME: why is LNM commented out? + */ + public setMode(params: IParams): void { + for (let i = 0; i < params.length; i++) { + switch (params.params[i]) { + case 4: + this._terminal.insertMode = true; + break; + case 20: + // this._t.convertEol = true; + break; + } + } + } + + /** * CSI ? Pm h * DEC Private Mode Set (DECSET). * Ps = 1 -> Application Cursor Keys (DECCKM). @@ -1590,25 +1617,34 @@ export class InputHandler extends Disposable implements IInputHandler { * Modes: * http: *vt100.net/docs/vt220-rm/chapter4.html * - * @vt: partly CSI SM "Set Mode" "CSI Pm h" "Set various terminal attributes." - * TODO: Describe all supported attributes. - */ - public setMode(params: IParams): void { - for (let i = 0; i < params.length; i++) { - switch (params.params[i]) { - case 4: - this._terminal.insertMode = true; - break; - case 20: - // this._t.convertEol = true; - break; - } - } - } - - /** * @vt: partly CSI DECSET "DEC Private Set Mode" "CSI ? Pm h" "Set various terminal attributes." - * TODO: Describe all supported attributes. + * Supported param values by DECSET: + * | param | Action | Status | + * | ----- | ------------------------------------------------------- | ----------- | + * | 1 | Application Cursor Keys (DECCKM). | supported | + * | 2 | Designate US-ASCII for character sets G0-G3 (DECANM). | supported | + * | 3 | 132 Column Mode (DECCOLM). | supported | + * | 6 | Origin Mode (DECOM). | supported | + * | 7 | Auto-wrap Mode (DECAWM). | supported | + * | 8 | Auto-repeat Keys (DECARM). Always on. | unsupported | + * | 9 | X10 xterm mouse protocol. | supported | + * | 12 | Start Blinking Cursor. | supported | + * | 25 | Show Cursor (DECTCEM). | supported | + * | 47 | Use Alternate Screen Buffer. | supported | + * | 66 | Application keypad (DECNKM). | supported | + * | 1000 | X11 xterm mouse protocol. | supported | + * | 1002 | Use Cell Motion Mouse Tracking. | supported | + * | 1003 | Use All Motion Mouse Tracking. | supported | + * | 1004 | Send FocusIn/FocusOut events | supported | + * | 1005 | Enable UTF-8 Mouse Mode. | unsupported | + * | 1006 | Enable SGR Mouse Mode. | supported | + * | 1015 | Enable urxvt Mouse Mode. | unsupported | + * | 1047 | Use Alternate Screen Buffer. | supported | + * | 1048 | Save cursor as in DECSC. | supported | + * | 1049 | Save cursor and switch to alternate buffer clearing it. | partly | + * | 2004 | Set bracketed paste mode. | supported | + * + * FIXME: implement DECSCNM, 1049 should clear altbuffer */ public setModePrivate(params: IParams): void { for (let i = 0; i < params.length; i++) { @@ -1704,6 +1740,32 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 4 -> Replace Mode (IRM). * Ps = 1 2 -> Send/receive (SRM). * Ps = 2 0 -> Normal Linefeed (LNM). + * + * @vt: partly CSI RM "Reset Mode" "CSI Pm l" "Set various terminal attributes." + * Supported param values by RM: + * | Param | Action | Status | + * | ----- | -------------------------------------- | ----------- | + * | 2 | Keyboard Action Mode (KAM). Always on. | unsupported | + * | 4 | Replace Mode (IRM). (default) | supported | + * | 12 | Send/receive (SRM). Always off. | unsupported | + * | 20 | Normal Linefeed (LNM). Always off. | unsupported | + * + * FIXME: why is LNM commented out? + */ + public resetMode(params: IParams): void { + for (let i = 0; i < params.length; i++) { + switch (params.params[i]) { + case 4: + this._terminal.insertMode = false; + break; + case 20: + // this._t.convertEol = false; + break; + } + } + } + + /** * CSI ? Pm l * DEC Private Mode Reset (DECRST). * Ps = 1 -> Normal Cursor Keys (DECCKM). @@ -1780,25 +1842,34 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 1 0 6 1 -> Reset keyboard emulation to Sun/PC style. * Ps = 2 0 0 4 -> Reset bracketed paste mode. * - * @vt: partly CSI RM "Reset Mode" "CSI Pm l" "Set various terminal attributes." - * TODO: Describe all supported attributes. - */ - public resetMode(params: IParams): void { - for (let i = 0; i < params.length; i++) { - switch (params.params[i]) { - case 4: - this._terminal.insertMode = false; - break; - case 20: - // this._t.convertEol = false; - break; - } - } - } - - /** * @vt: partly CSI DECRST "DEC Private Reset Mode" "CSI ? Pm l" "Reset various terminal attributes." - * TODO: Describe all supported attributes. + * Supported param values by DECRST: + * | param | Action | Status | + * | ----- | ------------------------------------------------------- | ----------- | + * | 1 | Normal Cursor Keys (DECCKM). | supported | + * | 2 | Designate VT52 mode (DECANM). | unsupported | + * | 3 | 80 Column Mode (DECCOLM). | broken | + * | 6 | Normal Cursor Mode (DECOM). | supported | + * | 7 | No Wraparound Mode (DECAWM). | supported | + * | 8 | No Auto-repeat Keys (DECARM). | unsupported | + * | 9 | Don't send Mouse X & Y on button press. | supported | + * | 12 | Stop Blinking Cursor. | supported | + * | 25 | Hide Cursor (DECTCEM). | supported | + * | 47 | Use Normal Screen Buffer. | supported | + * | 66 | Numeric keypad (DECNKM). | supported | + * | 1000 | Don't send Mouse reports. | supported | + * | 1002 | Don't use Cell Motion Mouse Tracking. | supported | + * | 1003 | Don't use All Motion Mouse Tracking. | supported | + * | 1004 | Don't send FocusIn/FocusOut events. | supported | + * | 1005 | Disable UTF-8 Mouse Mode. | unsupported | + * | 1006 | Disable SGR Mouse Mode. | supported | + * | 1015 | Disable urxvt Mouse Mode. | unsupported | + * | 1047 | Use Normal Screen Buffer (clearing screen if in alt). | supported | + * | 1048 | Restore cursor as in DECRC. | supported | + * | 1049 | Use Normal Screen Buffer and restore cursor. | supported | + * | 2004 | Reset bracketed paste mode. | supported | + * + * FIXME: DECCOLM is currently broken (already fixed in window options PR) */ public resetModePrivate(params: IParams): void { for (let i = 0; i < params.length; i++) { @@ -1949,71 +2020,67 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Pm m Character Attributes (SGR). - * Ps = 0 -> Normal (default). - * Ps = 1 -> Bold. - * Ps = 2 -> Faint, decreased intensity (ISO 6429). - * Ps = 4 -> Underlined. - * Ps = 5 -> Blink (appears as Bold). - * Ps = 7 -> Inverse. - * Ps = 8 -> Invisible, i.e., hidden (VT300). - * Ps = 2 2 -> Normal (neither bold nor faint). - * Ps = 2 4 -> Not underlined. - * Ps = 2 5 -> Steady (not blinking). - * Ps = 2 7 -> Positive (not inverse). - * Ps = 2 8 -> Visible, i.e., not hidden (VT300). - * Ps = 3 0 -> Set foreground color to Black. - * Ps = 3 1 -> Set foreground color to Red. - * Ps = 3 2 -> Set foreground color to Green. - * Ps = 3 3 -> Set foreground color to Yellow. - * Ps = 3 4 -> Set foreground color to Blue. - * Ps = 3 5 -> Set foreground color to Magenta. - * Ps = 3 6 -> Set foreground color to Cyan. - * Ps = 3 7 -> Set foreground color to White. - * Ps = 3 9 -> Set foreground color to default (original). - * Ps = 4 0 -> Set background color to Black. - * Ps = 4 1 -> Set background color to Red. - * Ps = 4 2 -> Set background color to Green. - * Ps = 4 3 -> Set background color to Yellow. - * Ps = 4 4 -> Set background color to Blue. - * Ps = 4 5 -> Set background color to Magenta. - * Ps = 4 6 -> Set background color to Cyan. - * Ps = 4 7 -> Set background color to White. - * Ps = 4 9 -> Set background color to default (original). - * - * If 16-color support is compiled, the following apply. Assume - * that xterm's resources are set so that the ISO color codes are - * the first 8 of a set of 16. Then the aixterm colors are the - * bright versions of the ISO colors: - * Ps = 9 0 -> Set foreground color to Black. - * Ps = 9 1 -> Set foreground color to Red. - * Ps = 9 2 -> Set foreground color to Green. - * Ps = 9 3 -> Set foreground color to Yellow. - * Ps = 9 4 -> Set foreground color to Blue. - * Ps = 9 5 -> Set foreground color to Magenta. - * Ps = 9 6 -> Set foreground color to Cyan. - * Ps = 9 7 -> Set foreground color to White. - * Ps = 1 0 0 -> Set background color to Black. - * Ps = 1 0 1 -> Set background color to Red. - * Ps = 1 0 2 -> Set background color to Green. - * Ps = 1 0 3 -> Set background color to Yellow. - * Ps = 1 0 4 -> Set background color to Blue. - * Ps = 1 0 5 -> Set background color to Magenta. - * Ps = 1 0 6 -> Set background color to Cyan. - * Ps = 1 0 7 -> Set background color to White. - * - * If xterm is compiled with the 16-color support disabled, it - * supports the following, from rxvt: - * Ps = 1 0 0 -> Set foreground and background color to - * default. - * - * If 88- or 256-color support is compiled, the following apply. - * Ps = 3 8 ; 5 ; Ps -> Set foreground color to the second - * Ps. - * Ps = 4 8 ; 5 ; Ps -> Set background color to the second - * Ps. * * @vt: partly CSI SGR "Select Graphic Rendition" "CSI Pm m" "Set/Reset various text attributes." - * Detailed description goes here... + * SGR selects one or more character attributes at the same time. Multiple params (up to 32) + * are applied from in order from left to right. The changed attributes are applied to all new + * characters received. If you move characters in the viewport by scrolling or any other means, + * then the attributes move with the characters. + * Supported param values by SGR: + * | Param | Meaning | Status | + * | --------- | -------------------------------------------------------- | ----------- | + * | 0 | Normal (default). Resets any other preceding SGR. | supported | + * | 1 | Bold. (also see `options.drawBoldTextInBrightColors`) | supported | + * | 2 | Faint, decreased intensity. | supported | + * | 3 | Italic. | supported | + * | 4 | Underlined. (no support for newer underline styles) | supported | + * | 5 | Slowly blinking. | unsupported | + * | 6 | Rapidly blinking. | unsupported | + * | 7 | Inverse. Flips foreground and background color. | supported | + * | 8 | Invisible (hidden). | supported | + * | 9 | Crossed-out characters. | unsupported | + * | 21 | Doubly underlined. | unsupported | + * | 22 | Normal (neither bold nor faint). | supported | + * | 23 | No italic. | supported | + * | 24 | Not underlined. | supported | + * | 25 | Steady (not blinking). | supported | + * | 27 | Positive (not inverse). | supported | + * | 28 | Visible (not hidden). | supported | + * | 29 | Not Crossed-out. | unsupported | + * | 30 | Foreground color: Black. | supported | + * | 31 | Foreground color: Red. | supported | + * | 32 | Foreground color: Green. | supported | + * | 33 | Foreground color: Yellow. | supported | + * | 34 | Foreground color: Blue. | supported | + * | 35 | Foreground color: Magenta. | supported | + * | 36 | Foreground color: Cyan. | supported | + * | 37 | Foreground color: White. | supported | + * | 38 | Foreground color: Extended color (see below). | supported | + * | 39 | Foreground color: Default (original). | supported | + * | 40 | Background color: Black. | supported | + * | 41 | Background color: Red. | supported | + * | 42 | Background color: Green. | supported | + * | 43 | Background color: Yellow. | supported | + * | 44 | Background color: Blue. | supported | + * | 45 | Background color: Magenta. | supported | + * | 46 | Background color: Cyan. | supported | + * | 47 | Background color: White. | supported | + * | 48 | Background color: Extended color (see below). | supported | + * | 49 | Background color: Default (original). | supported | + * | 90 - 97 | Bright foreground color (analogous to 30 -37). | supported | + * | 100 - 107 | Bright background color (analogous to 40 -47). | supported | + * Extended colors are supported for foreground (Ps=38) and background (Ps=48) as follows: + * | Ps + 1 | Meaning | Status | + * | ------ | ----------------------------------------------------------------------- | ----------- | + * | 0 | Implementation defined. | unsupported | + * | 1 | Transparent. | unsupported | + * | 2 | RGB color, in the form `Ps ; 2 ; R ; G ; B` or `Ps : 2 : : R : G : B`. | supported | + * | 3 | CMY color. | unsupported | + * | 4 | CMYK color. | unsupported | + * | 5 | Indexed (256 colors), in the form `Ps ; 5 ; INDEX` or `Ps : 5 : INDEX`. | supported | + * + * FIXME: blinking is implemented in attrs, but not working in renderers? + * FIXME: remove dead branch for p=100 */ public charAttributes(params: IParams): void { // Optimize a single SGR0. @@ -2101,7 +2168,7 @@ export class InputHandler extends Disposable implements IInputHandler { } else if (p === 38 || p === 48) { // fg color 256 and RGB i += this._extractColor(params, i, attr); - } else if (p === 100) { + } else if (p === 100) { // FIXME: dead branch, p=100 already handled above! // reset fg/bg attr.fg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK); attr.fg |= DEFAULT_ATTR_DATA.fg & (Attributes.PCOLOR_MASK | Attributes.RGB_MASK); @@ -2222,7 +2289,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 6 -> steady bar (xterm). * * @vt: supported CSI DECSCUSR "Set Cursor Style" "CSI Ps SP q" "Set cursor style." - * Supported cursor styles (TODO: add note about `ITerminalOptions.cursorBlink`): + * Supported cursor styles: * - empty, 0 or 1: steady block * - 2: blink block * - 3: steady underline From 56f4c3d6aefc29370ec0c71593744eb48046dfda Mon Sep 17 00:00:00 2001 From: javacs3 Date: Thu, 9 Jan 2020 21:15:30 +0800 Subject: [PATCH 237/268] 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 db2968e69..183687ac1 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 c2a3c46e1..a34aa0bd3 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 b9c5ed4f9..1d011726c 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 7b35949d9..bc46a085c 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 2dcc704bd..33cc67d76 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 0e7e27059..0b2679ee4 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 81ac773ad..276a5c543 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 5854cef6e..fd61aaf2f 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 243266d06..c687b783b 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 12b912140bf3ea05c05785d79db2365c429004ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 9 Jan 2020 16:40:35 +0100 Subject: [PATCH 238/268] use 2 empty lines as end of vt definition --- bin/extract_vtfeatures.js | 20 +++++++++++-- src/InputHandler.ts | 59 ++++++++++++++++++++++++++++----------- 2 files changed, 59 insertions(+), 20 deletions(-) diff --git a/bin/extract_vtfeatures.js b/bin/extract_vtfeatures.js index de91dca5c..c0c70fbbe 100644 --- a/bin/extract_vtfeatures.js +++ b/bin/extract_vtfeatures.js @@ -233,6 +233,10 @@ function createAnchorSlug(s) { return s.toLowerCase().split(' ').join('-'); } +function empty(ar) { + return !ar.filter(Boolean, ar).length; +} + function* parseMultiLineGen(filename, s) { if (!~s.indexOf('@vt:')) { return; @@ -241,29 +245,34 @@ function* parseMultiLineGen(filename, s) { let grabLine = false; let longDescription = []; let feature = undefined; + let noLineCount = 0; for (const line of lines) { if (grabLine) { - if (!line) { + if (!line) noLineCount++; + if (noLineCount >= 2) { if (feature) { - feature.longDescription = longDescription; + feature.longDescription = empty(longDescription) ? [] : longDescription; feature.longTarget = createAnchorSlug(feature.name); yield feature; } grabLine = false; longDescription = []; feature = undefined; + noLineCount = 0; } else if (line.indexOf('@vt:') === 0) { if (feature) { - feature.longDescription = []; + feature.longDescription = empty(longDescription) ? [] : longDescription; feature.longTarget = createAnchorSlug(feature.name); yield feature; } grabLine = true; longDescription = []; feature = undefined; + noLineCount = 0; } else { longDescription.push(line); + if (line) noLineCount = 0; } } if (line.indexOf('@vt:') === 0) { @@ -271,6 +280,11 @@ function* parseMultiLineGen(filename, s) { grabLine = true; } } + if (grabLine && feature) { + feature.longDescription = empty(longDescription) ? [] : longDescription; + feature.longTarget = createAnchorSlug(feature.name); + yield feature; + } } function parseSingleLine(filename, s) { diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 16a19d953..614753d63 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -68,16 +68,19 @@ const MAX_PARSEBUFFER_LENGTH = 131072; * * @vt: partly DCS DECRQSS "Request Selection or Setting" "DCS $ q Pt ST" "Request several terminal settings." * Response is in the form `ESC P 1 $ r Pt ST` for valid requests, where `Pt` contains the corresponding CSI string, - * `ESC P 0 ST` for invalid requests.\ + * `ESC P 0 ST` for invalid requests. + * * Supported requests and responses: - * | Type | Request | Response (`Pt`) | - * | -------------------------------- | ----------------- | ------------------ | - * | Graphic Rendition (SGR) | `DCS $ q m ST` | always reporting `0m` (currently broken) | - * | Top and Bottom Margins (DECSTBM) | `DCS $ q r ST` | `Ptop ; Pbottom r` | - * | Cursor Style (DECSCUSR) | `DCS $ q SP q ST` | `Pstyle SP q` | - * | Protection Attribute (DECSCA) | `DCS $ q " q ST` | always reporting `0 " q` (DECSCA is unsupported) | + * + * | Type | Request | Response (`Pt`) | + * | -------------------------------- | ----------------- | ----------------------------------------------------- | + * | Graphic Rendition (SGR) | `DCS $ q m ST` | always reporting `0m` (currently broken) | + * | Top and Bottom Margins (DECSTBM) | `DCS $ q r ST` | `Ps ; Ps r` | + * | Cursor Style (DECSCUSR) | `DCS $ q SP q ST` | `Ps SP q` | + * | Protection Attribute (DECSCA) | `DCS $ q " q ST` | always reporting `0 " q` (DECSCA is unsupported) | * | Conformance Level (DECSCL) | `DCS $ q " p ST` | always reporting `61 ; 1 " p` (DECSCL is unsupported) | * + * * TODO: * - fix SGR report * - either implement DECSCA or remove the report @@ -1002,6 +1005,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: supported CSI ED "Erase In Display" "CSI Ps J" "Erase various parts of the viewport." * Supported param values: + * * | Ps | Effect | * | -- | ------------------------------------------------------------ | * | 0 | Erase from the cursor through the end of the viewport. | @@ -1073,6 +1077,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: supported CSI EL "Erase In Line" "CSI Ps K" "Erase various parts of the active row." * Supported param values: + * * | Ps | Effect | * | -- | -------------------------------------------------------- | * | 0 | Erase from the cursor through the end of the row. | @@ -1176,6 +1181,7 @@ export class InputHandler extends Disposable implements IInputHandler { * The ICH sequence inserts `Ps` blank characters. The cursor remains at the beginning of the blank characters. * Text between the cursor and right margin moves to the right. Characters moved past the right margin are lost. * + * * FIXME: check against xterm - should not work outside of scroll margins (see VT520 manual) */ public insertChars(params: IParams): void { @@ -1200,6 +1206,7 @@ export class InputHandler extends Disposable implements IInputHandler { * As characters are deleted, the remaining characters between the cursor and right margin move to the left. * Character attributes move with the characters. The terminal adds blank characters at the right margin. * + * * FIXME: check against xterm - should not work outside of scroll margins (see VT520 manual) */ public deleteChars(params: IParams): void { @@ -1221,6 +1228,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: supported CSI SU "Scroll Up" "CSI Ps S" "Scroll `Ps` lines up (default=1)." * + * * FIXME: scrolled out lines at top = 1 should add to scrollback (xterm) */ public scrollUp(params: IParams): void { @@ -1411,7 +1419,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: supported CSI REP "Repeat Preceding Character" "CSI Ps b" "Repeat preceding character `Ps` times (default=1)." * REP repeats the previous character `Ps` times advancing the cursor, also wrapping if DECAWM is set. - * REP no effect if the sequence does not follow a printable ASCII character + * REP has no effect if the sequence does not follow a printable ASCII character * (NOOP for any other sequence in between or NON ASCII characters). */ public repeatPrecedingCharacter(params: IParams): void { @@ -1449,6 +1457,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: supported CSI DA1 "Primary Device Attributes" "CSI c" "Send primary device attributes." * + * * TODO: fix and cleanup response */ public sendDeviceAttributesPrimary(params: IParams): void { @@ -1483,6 +1492,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: supported CSI DA2 "Secondary Device Attributes" "CSI > c" "Send primary device attributes." * + * * TODO: fix and cleanup response */ public sendDeviceAttributesSecondary(params: IParams): void { @@ -1514,6 +1524,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: partly CSI SM "Set Mode" "CSI Pm h" "Set various terminal modes." * Supported param values by SM: + * * | Param | Action | Status | * | ----- | -------------------------------------- | ----------- | * | 2 | Keyboard Action Mode (KAM). Always on. | unsupported | @@ -1521,6 +1532,7 @@ export class InputHandler extends Disposable implements IInputHandler { * | 12 | Send/receive (SRM). Always off. | unsupported | * | 20 | Automatic Newline (LNM). Always off. | unsupported | * + * * FIXME: why is LNM commented out? */ public setMode(params: IParams): void { @@ -1619,6 +1631,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: partly CSI DECSET "DEC Private Set Mode" "CSI ? Pm h" "Set various terminal attributes." * Supported param values by DECSET: + * * | param | Action | Status | * | ----- | ------------------------------------------------------- | ----------- | * | 1 | Application Cursor Keys (DECCKM). | supported | @@ -1644,6 +1657,7 @@ export class InputHandler extends Disposable implements IInputHandler { * | 1049 | Save cursor and switch to alternate buffer clearing it. | partly | * | 2004 | Set bracketed paste mode. | supported | * + * * FIXME: implement DECSCNM, 1049 should clear altbuffer */ public setModePrivate(params: IParams): void { @@ -1743,6 +1757,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: partly CSI RM "Reset Mode" "CSI Pm l" "Set various terminal attributes." * Supported param values by RM: + * * | Param | Action | Status | * | ----- | -------------------------------------- | ----------- | * | 2 | Keyboard Action Mode (KAM). Always on. | unsupported | @@ -1750,6 +1765,7 @@ export class InputHandler extends Disposable implements IInputHandler { * | 12 | Send/receive (SRM). Always off. | unsupported | * | 20 | Normal Linefeed (LNM). Always off. | unsupported | * + * * FIXME: why is LNM commented out? */ public resetMode(params: IParams): void { @@ -1844,6 +1860,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: partly CSI DECRST "DEC Private Reset Mode" "CSI ? Pm l" "Reset various terminal attributes." * Supported param values by DECRST: + * * | param | Action | Status | * | ----- | ------------------------------------------------------- | ----------- | * | 1 | Normal Cursor Keys (DECCKM). | supported | @@ -1869,6 +1886,7 @@ export class InputHandler extends Disposable implements IInputHandler { * | 1049 | Use Normal Screen Buffer and restore cursor. | supported | * | 2004 | Reset bracketed paste mode. | supported | * + * * FIXME: DECCOLM is currently broken (already fixed in window options PR) */ public resetModePrivate(params: IParams): void { @@ -2026,7 +2044,9 @@ export class InputHandler extends Disposable implements IInputHandler { * are applied from in order from left to right. The changed attributes are applied to all new * characters received. If you move characters in the viewport by scrolling or any other means, * then the attributes move with the characters. + * * Supported param values by SGR: + * * | Param | Meaning | Status | * | --------- | -------------------------------------------------------- | ----------- | * | 0 | Normal (default). Resets any other preceding SGR. | supported | @@ -2069,15 +2089,18 @@ export class InputHandler extends Disposable implements IInputHandler { * | 49 | Background color: Default (original). | supported | * | 90 - 97 | Bright foreground color (analogous to 30 -37). | supported | * | 100 - 107 | Bright background color (analogous to 40 -47). | supported | + * * Extended colors are supported for foreground (Ps=38) and background (Ps=48) as follows: - * | Ps + 1 | Meaning | Status | - * | ------ | ----------------------------------------------------------------------- | ----------- | - * | 0 | Implementation defined. | unsupported | - * | 1 | Transparent. | unsupported | - * | 2 | RGB color, in the form `Ps ; 2 ; R ; G ; B` or `Ps : 2 : : R : G : B`. | supported | - * | 3 | CMY color. | unsupported | - * | 4 | CMYK color. | unsupported | - * | 5 | Indexed (256 colors), in the form `Ps ; 5 ; INDEX` or `Ps : 5 : INDEX`. | supported | + * + * | Ps + 1 | Meaning | Status | + * | ------ | ------------------------------------------------------------- | ----------- | + * | 0 | Implementation defined. | unsupported | + * | 1 | Transparent. | unsupported | + * | 2 | RGB color as `Ps ; 2 ; R ; G ; B` or `Ps : 2 : : R : G : B`. | supported | + * | 3 | CMY color. | unsupported | + * | 4 | CMYK color. | unsupported | + * | 5 | Indexed (256 colors) as `Ps ; 5 ; INDEX` or `Ps : 5 : INDEX`. | supported | + * * * FIXME: blinking is implemented in attrs, but not working in renderers? * FIXME: remove dead branch for p=100 @@ -2257,13 +2280,15 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: supported CSI DECSTR "Soft Terminal Reset" "CSI ! p" "Reset several terminal attributes to initial state." * There are two terminal reset sequences - RIS and DECSTR. While RIS performs almost a full terminal bootstrap, * DECSTR only resets certain attributes. For most needs DECSTR should be sufficient. - * Attributes reset to default values: + * + * The following terminal attributes are reset to default values: * - cursor is reset (default = visible, home position) * - IRM is reset (dafault = false) * - scroll margins are reset (default = viewport size) * - erase attributes are reset to default * - charsets are reset * + * * FIXME: there are several more attributes missing (see VT520 manual) */ public softReset(params: IParams): void { From f9fddfc43d9f116f9cebd73ff124587218bf9049 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 9 Jan 2020 18:47:56 +0100 Subject: [PATCH 239/268] fix table sorting --- bin/extract_vtfeatures.js | 33 +++++++++++++++++++++++---------- src/InputHandler.ts | 30 ++++++++++++++++-------------- 2 files changed, 39 insertions(+), 24 deletions(-) diff --git a/bin/extract_vtfeatures.js b/bin/extract_vtfeatures.js index c0c70fbbe..a4ea3788a 100644 --- a/bin/extract_vtfeatures.js +++ b/bin/extract_vtfeatures.js @@ -33,15 +33,6 @@ const TYPES = [ ]; const MARKDOWN_TMPL = ` - -### TODO -- improve table sorting: - - sort C0/C1 in byte order - - sort OSC in numerical order - - sort CSI/ESC/DCS in final byte order -- references - - xterm.js version: {{version}} ### Table of Contents @@ -209,6 +200,8 @@ To denote the sequences the following tables use the same abbreviations as xterm {{#OSC.length}} ### OSC +**Note**: Other than listed in the table, the parser recognizes both ST (ECMA-48) and BEL (xterm) as OSC sequence finalizer. + | Identifier | Sequence | Short Description | Status | | ---------- | -------- | ----------------- | ------ | {{#OSC}} @@ -308,6 +301,26 @@ function parseSingleLine(filename, s) { } } +function getSorter(entry) { + switch (entry) { + case 'C0': + case 'C1': + // NOTE: expects hex value notation at last position in sequence + return (a, b) => parseInt(a.sequence.slice(-2), 16) - parseInt(b.sequence.slice(-2), 16); + case 'OSC': + // NOTE: expects the decimal function identifier in mnemonic + return (a, b) => parseInt(a.mnemonic) - parseInt(b.mnemonic); + case 'DCS': + return (a, b) => a.mnemonic > b.mnemonic; + case 'CSI': + case 'ESC': + // default sort order by final byte + return (a, b) => a.sequence.charCodeAt(a.sequence.length - 1) - b.sequence.charCodeAt(b.sequence.length - 1); + default: + return (a, b) => a.sequence > b.sequence; + } +}; + function postProcessData(features) { const featureTable = {}; for (const feature of features) { @@ -320,7 +333,7 @@ function postProcessData(features) { } } for (const entry in featureTable) { - featureTable[entry].sort((a, b) => a.sequence.slice(-1) > b.sequence.slice(-1)); + featureTable[entry].sort(getSorter(entry)); } // console.error(featureTable); featureTable.version = require('../package.json').version; diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 614753d63..5a5e7db37 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -37,12 +37,14 @@ const GLEVEL: {[key: string]: number} = {'(': 0, ')': 1, '*': 2, '+': 3, '-': 1, // @vt: supported ESC ST "String Terminator" "ESC \" "Terminator used for string type sequences." // @vt: supported ESC PM "Privacy Message" "ESC ^" "Start of a privacy message." // @vt: supported ESC APC "Application Program Command" "ESC _" "Start of an APC sequence." -// @vt: supported C1 CSI "Control Sequence Introducer" "\x9b" "Start of a CSI sequence." -// @vt: supported C1 OSC "Operating System Command" "\x9d" "Start of an OSC sequence." +// @vt: supported C1 CSI "Control Sequence Introducer" "\x9B" "Start of a CSI sequence." +// @vt: supported C1 OSC "Operating System Command" "\x9D" "Start of an OSC sequence." // @vt: supported C1 DCS "Device Control String" "\x90" "Start of a DCS sequence." -// @vt: supported C1 ST "String Terminator" "\x9c" "Terminator used for string type sequences." -// @vt: supported C1 PM "Privacy Message" "\x9e" "Start of a privacy message." -// @vt: supported C1 APC "Application Program Command" "\x9f" "Start of an APC sequence." +// @vt: supported C1 ST "String Terminator" "\x9C" "Terminator used for string type sequences." +// @vt: supported C1 PM "Privacy Message" "\x9E" "Start of a privacy message." +// @vt: supported C1 APC "Application Program Command" "\x9F" "Start of an APC sequence." +// @vt: supported C0 NUL "Null" "\0, \x00" "NUL is ignored." +// @vt: supported C0 ESC "Escape" "\e, \x1B" "Start of a sequence. Cancels any other sequence." /** * Document common VT features here that are currently unsupported @@ -602,7 +604,7 @@ export class InputHandler extends Disposable implements IInputHandler { * BEL * Bell (Ctrl-G). * - * @vt: supported C0 BEL "Bell" "\a" "Ring the bell." + * @vt: supported C0 BEL "Bell" "\a, \x07" "Ring the bell." * The behavior of the bell is further customizable with `ITerminalOptions.bellStyle` * and `ITerminalOptions.bellSound`. */ @@ -614,11 +616,11 @@ export class InputHandler extends Disposable implements IInputHandler { * LF * Line Feed or New Line (NL). (LF is Ctrl-J). * - * @vt: supported C0 LF "Line Feed" "\n" "Move the cursor one row down, scrolling if needed." + * @vt: supported C0 LF "Line Feed" "\n, \x0A" "Move the cursor one row down, scrolling if needed." * Scrolling is restricted to scroll margins and will only happen on the bottom line. * - * @vt: supported C0 VT "Vertical Tabulation" "\v" "Treated as LF." - * @vt: supported C0 FF "Form Feed" "\f" "Treated as LF." + * @vt: supported C0 VT "Vertical Tabulation" "\v, \x0B" "Treated as LF." + * @vt: supported C0 FF "Form Feed" "\f, \x0C" "Treated as LF." */ public lineFeed(): void { // make buffer local for faster access @@ -648,7 +650,7 @@ export class InputHandler extends Disposable implements IInputHandler { * CR * Carriage Return (Ctrl-M). * - * @vt: supported C0 CR "Carriage Return" "\r" "Move the cursor to the beginning of the row." + * @vt: supported C0 CR "Carriage Return" "\r, \x0D" "Move the cursor to the beginning of the row." */ public carriageReturn(): void { this._bufferService.buffer.x = 0; @@ -658,7 +660,7 @@ export class InputHandler extends Disposable implements IInputHandler { * BS * Backspace (Ctrl-H). * - * @vt: supported C0 BS "Backspace" "\b" "Move the cursor one position to the left." + * @vt: supported C0 BS "Backspace" "\b, \x08" "Move the cursor one position to the left." */ public backspace(): void { this._restrictCursor(); @@ -671,7 +673,7 @@ export class InputHandler extends Disposable implements IInputHandler { * TAB * Horizontal Tab (HT) (Ctrl-I). * - * @vt: supported C0 HT "Horizontal Tabulation" "\t" "Move the cursor to the next character tab stop." + * @vt: supported C0 HT "Horizontal Tabulation" "\t, \x09" "Move the cursor to the next character tab stop." */ public tab(): void { if (this._bufferService.buffer.x >= this._bufferService.cols) { @@ -689,7 +691,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Shift Out (Ctrl-N) -> Switch to Alternate Character Set. This invokes the * G1 character set. * - * @vt: partly C0 SO "Shift Out" "\x0e" "Switch to an alternative character set." + * @vt: partly C0 SO "Shift Out" "\x0E" "Switch to an alternative character set." */ public shiftOut(): void { this._charsetService.setgLevel(1); @@ -700,7 +702,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Shift In (Ctrl-O) -> Switch to Standard Character Set. This invokes the G0 * character set (the default). * - * @vt: supported C0 SI "Shift In" "\x0f" "Return to regular character set after Shift Out." + * @vt: supported C0 SI "Shift In" "\x0F" "Return to regular character set after Shift Out." */ public shiftIn(): void { this._charsetService.setgLevel(0); From f5c3b00c42d7fbc0fcae5eaf1bafd90c02f40351 Mon Sep 17 00:00:00 2001 From: kumaran-14 Date: Fri, 10 Jan 2020 00:02:18 +0530 Subject: [PATCH 240/268] Fixes Alt + click behaviour when rows are wrapped --- src/browser/input/MoveToCell.ts | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/browser/input/MoveToCell.ts b/src/browser/input/MoveToCell.ts index 406ec8075..72a242808 100644 --- a/src/browser/input/MoveToCell.ts +++ b/src/browser/input/MoveToCell.ts @@ -30,7 +30,30 @@ export function moveToCellSequence(targetX: number, targetY: number, bufferServi } // Only move horizontally for the normal buffer - return moveHorizontallyOnly(startX, startY, targetX, targetY, bufferService, applicationCursor); + let direction; + if(startY == targetY) { + direction = startX > targetX ? Direction.LEFT : Direction.RIGHT; + return repeat(Math.abs(startX - targetX), sequence(direction, applicationCursor)); + } else { + direction = startY > targetY ? Direction.LEFT : Direction.RIGHT; + return repeat(colsFromRowEnd(startY > targetY ? targetX : startX, bufferService), sequence(direction, applicationCursor)) + + repeat((Math.abs(wrappedRowsForRow(bufferService, startY) - wrappedRowsForRow(bufferService, targetY))-1)*bufferService.cols, sequence(direction, applicationCursor)) + + repeat(colsFromRowBeginning(startY > targetY ? startX : targetX, bufferService), sequence(direction, applicationCursor)); + } +} + +/** + * Find the number of cols from a row beginning to a col. + */ +function colsFromRowBeginning(currX: number, bufferService: IBufferService): number { + return Math.abs(currX); +} + +/** + * Find the number of cols from a col to row end. + */ +function colsFromRowEnd(currX: number, bufferService: IBufferService): number { + return Math.abs(bufferService.cols - currX); } /** From 76d4de225a541d19be340e2d0bba7e423211f300 Mon Sep 17 00:00:00 2001 From: kumaran-14 Date: Fri, 10 Jan 2020 00:40:49 +0530 Subject: [PATCH 241/268] Run eslint and fix errors --- src/browser/input/MoveToCell.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/browser/input/MoveToCell.ts b/src/browser/input/MoveToCell.ts index 72a242808..9b4932882 100644 --- a/src/browser/input/MoveToCell.ts +++ b/src/browser/input/MoveToCell.ts @@ -31,15 +31,14 @@ export function moveToCellSequence(targetX: number, targetY: number, bufferServi // Only move horizontally for the normal buffer let direction; - if(startY == targetY) { + if (startY === targetY) { direction = startX > targetX ? Direction.LEFT : Direction.RIGHT; return repeat(Math.abs(startX - targetX), sequence(direction, applicationCursor)); - } else { + } direction = startY > targetY ? Direction.LEFT : Direction.RIGHT; return repeat(colsFromRowEnd(startY > targetY ? targetX : startX, bufferService), sequence(direction, applicationCursor)) - + repeat((Math.abs(wrappedRowsForRow(bufferService, startY) - wrappedRowsForRow(bufferService, targetY))-1)*bufferService.cols, sequence(direction, applicationCursor)) + + repeat((Math.abs(wrappedRowsForRow(bufferService, startY) - wrappedRowsForRow(bufferService, targetY)) - 1 ) * bufferService.cols, sequence(direction, applicationCursor)) + repeat(colsFromRowBeginning(startY > targetY ? startX : targetX, bufferService), sequence(direction, applicationCursor)); - } } /** From 2d1af70e87fe0b27f81df2c188fdcaf5ea14ec9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 9 Jan 2020 20:40:18 +0100 Subject: [PATCH 242/268] lower header level; fix wrongly escaped sequences --- bin/extract_vtfeatures.js | 53 ++++++++++++++++++++++----------------- src/InputHandler.ts | 2 +- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/bin/extract_vtfeatures.js b/bin/extract_vtfeatures.js index a4ea3788a..75e27ed28 100644 --- a/bin/extract_vtfeatures.js +++ b/bin/extract_vtfeatures.js @@ -35,7 +35,7 @@ const TYPES = [ const MARKDOWN_TMPL = ` xterm.js version: {{version}} -### Table of Contents +## Table of Contents - [General notes](#general-notes) {{#C0.length}} @@ -57,16 +57,16 @@ xterm.js version: {{version}} - [OSC](#osc) {{/OSC.length}} -### General notes +## General notes -This document lists xterm.js' support of terminal sequences. The sequences are grouped by their type: +This document lists xterm.js' support of terminal sequences. The sequences are grouped by their sequence type: -- C0: single byte command (7bit control characters, byte range \\x00 .. \\x1f) -- C1: single byte command (8bit control characters, byte range \\x80 .. \\x9f) -- ESC: sequence starting with \`ESC\` (\`\\x1b\`) -- CSI - Control Sequence Introducer: sequence starting with \`ESC [\` (7bit) or CSI (\`\\x9b\` 8bit) +- C0: single byte command (7bit control codes, byte range \\x00 .. \\x1F, \x7F) +- C1: single byte command (8bit control codes, byte range \\x80 .. \\x9F) +- ESC: sequence starting with \`ESC\` (\`\\x1B\`) +- CSI - Control Sequence Introducer: sequence starting with \`ESC [\` (7bit) or CSI (\`\\x9B\` 8bit) - DCS - Device Control String: sequence starting with \`ESC P\` (7bit) or DCS (\`\\x90\` 8bit) -- OSC - Operating System Command: sequence starting with \`ESC ]\` (7bit) or OSC (\`\\x9d\` 8bit) +- OSC - Operating System Command: sequence starting with \`ESC ]\` (7bit) or OSC (\`\\x9D\` 8bit) Application Program Command (APC), Privacy Message (PM) and Start of String (SOS) are recognized but not supported, any sequence of these types will be ignored. They are also not hookable by the API. @@ -74,7 +74,7 @@ any sequence of these types will be ignored. They are also not hookable by the A Note that the list only contains sequences implemented in xterm.js' core codebase. Missing sequences are either not supported or unstable/experimental. Furthermore addons or integrations can provide additional custom sequences. -To denote the sequences the following tables use the same abbreviations as xterm does: +To denote the sequences the tables use the same abbreviations as xterm does: - \`Ps\`: A single (usually optional) numeric parameter, composed of one or more decimal digits. - \`Pm\`: A multiple numeric parameter composed of any number of single numeric parameters, separated by ; character(s), e.g. \` Ps ; Ps ; ... \`. @@ -83,7 +83,7 @@ To denote the sequences the following tables use the same abbreviations as xterm {{#C0.length}} -### C0 +## C0 | Mnemonic | Name | Sequence | Short Description | Status | | -------- | ---- | -------- | ----------------- | ------ | @@ -94,7 +94,7 @@ To denote the sequences the following tables use the same abbreviations as xterm {{#C0.hasLongDescriptions}} {{#C0}} {{#longDescription.length}} -#### {{name}} +### {{name}} {{#longDescription}} {{{.}}} {{/longDescription}} @@ -106,7 +106,7 @@ To denote the sequences the following tables use the same abbreviations as xterm {{#C1.length}} -### C1 +## C1 | Mnemonic | Name | Sequence | Short Description | Status | | -------- | ---- | -------- | ----------------- | ------ | @@ -117,7 +117,7 @@ To denote the sequences the following tables use the same abbreviations as xterm {{#C1.hasLongDescriptions}} {{#C1}} {{#longDescription.length}} -#### {{name}} +### {{name}} {{#longDescription}} {{{.}}} {{/longDescription}} @@ -129,18 +129,18 @@ To denote the sequences the following tables use the same abbreviations as xterm {{#CSI.length}} -### CSI +## CSI | Mnemonic | Name | Sequence | Short Description | Status | | -------- | ---- | -------- | ----------------- | ------ | {{#CSI}} -| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}})_{{/longDescription.length}} | {{status}} | +| {{mnemonic}} | {{name}} | \`\`{{{sequence}}}\`\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}})_{{/longDescription.length}} | {{status}} | {{/CSI}} {{#CSI.hasLongDescriptions}} {{#CSI}} {{#longDescription.length}} -#### {{name}} +### {{name}} {{#longDescription}} {{{.}}} {{/longDescription}} @@ -152,7 +152,7 @@ To denote the sequences the following tables use the same abbreviations as xterm {{#DCS.length}} -### DCS +## DCS | Mnemonic | Name | Sequence | Short Description | Status | | -------- | ---- | -------- | ----------------- | ------ | @@ -163,7 +163,7 @@ To denote the sequences the following tables use the same abbreviations as xterm {{#DCS.hasLongDescriptions}} {{#DCS}} {{#longDescription.length}} -#### {{name}} +### {{name}} {{#longDescription}} {{{.}}} {{/longDescription}} @@ -175,7 +175,7 @@ To denote the sequences the following tables use the same abbreviations as xterm {{#ESC.length}} -### ESC +## ESC | Mnemonic | Name | Sequence | Short Description | Status | | -------- | ---- | -------- | ----------------- | ------ | @@ -186,7 +186,7 @@ To denote the sequences the following tables use the same abbreviations as xterm {{#ESC.hasLongDescriptions}} {{#ESC}} {{#longDescription.length}} -#### {{name}} +### {{name}} {{#longDescription}} {{{.}}} {{/longDescription}} @@ -198,7 +198,7 @@ To denote the sequences the following tables use the same abbreviations as xterm {{#OSC.length}} -### OSC +## OSC **Note**: Other than listed in the table, the parser recognizes both ST (ECMA-48) and BEL (xterm) as OSC sequence finalizer. @@ -211,7 +211,7 @@ To denote the sequences the following tables use the same abbreviations as xterm {{#OSC.hasLongDescriptions}} {{#OSC}} {{#longDescription.length}} -#### {{name}} +### {{name}} {{#longDescription}} {{{.}}} {{/longDescription}} @@ -315,7 +315,14 @@ function getSorter(entry) { case 'CSI': case 'ESC': // default sort order by final byte - return (a, b) => a.sequence.charCodeAt(a.sequence.length - 1) - b.sequence.charCodeAt(b.sequence.length - 1); + return (a, b) => { + // ugly hack to fix sorting of workaround in HPA sequence "CSI Ps ` " + // for HPA compare with length - 2 instead + const HPA = 'CSI Ps ` '; + const aa = a.sequence === HPA ? a.sequence.slice(0, -1) : a.sequence; + const bb = b.sequence === HPA ? b.sequence.slice(0, -1) : b.sequence; + return aa.charCodeAt(aa.length - 1) - bb.charCodeAt(bb.length - 1); + }; default: return (a, b) => a.sequence > b.sequence; } diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 5a5e7db37..33b731a7c 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -857,7 +857,7 @@ export class InputHandler extends Disposable implements IInputHandler { * [column] (default = [row,1]) (HPA). * Currently same functionality as CHA. * - * @vt: supported CSI HPA "Horizontal Position Absolute" "CSI Ps `" "Same as CHA." + * @vt: supported CSI HPA "Horizontal Position Absolute" "CSI Ps ` " "Same as CHA." */ public charPosAbsolute(params: IParams): void { this._setCursor((params.params[0] || 1) - 1, this._bufferService.buffer.y); From 4010900e47d154514fbd91eca41c8b7a0a8db84e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 9 Jan 2020 22:40:08 +0100 Subject: [PATCH 243/268] simply status column, reason in title --- bin/extract_vtfeatures.js | 81 +++++--- src/InputHandler.ts | 394 +++++++++++++++++++------------------- 2 files changed, 253 insertions(+), 222 deletions(-) diff --git a/bin/extract_vtfeatures.js b/bin/extract_vtfeatures.js index 75e27ed28..51630a62c 100644 --- a/bin/extract_vtfeatures.js +++ b/bin/extract_vtfeatures.js @@ -17,7 +17,7 @@ const REX_COMMENTS = /^\s*?[/][*][*]([\s\S]*?)[*][/]|^\s*?\/\/ ([@]vt[:].*?)$/mu * regexp to parse the @vt line * expected data - "@vt: "" "" "" */ -const REX_VT_LINE = /^[@]vt\:\s*(\w+)\s*(\w+)\s*(\w+)\s*"(.*?)"\s*"(.*?)"\s*"(.*?)".*$/; +const REX_VT_LINE = /^[@]vt\:\s*(\w+|#\w+|#\w+\[.*?\])\s*(\w+)\s*(\w+)\s*"(.*?)"\s*"(.*?)"\s*"(.*?)".*$/; // known vt command types const TYPES = [ @@ -61,12 +61,12 @@ xterm.js version: {{version}} This document lists xterm.js' support of terminal sequences. The sequences are grouped by their sequence type: -- C0: single byte command (7bit control codes, byte range \\x00 .. \\x1F, \x7F) -- C1: single byte command (8bit control codes, byte range \\x80 .. \\x9F) +- C0: single byte command (7bit control codes, byte range \`\\x00\` .. \`\\x1F\`, \`\\x7F\`) +- C1: single byte command (8bit control codes, byte range \`\\x80\` .. \`\\x9F\`) - ESC: sequence starting with \`ESC\` (\`\\x1B\`) -- CSI - Control Sequence Introducer: sequence starting with \`ESC [\` (7bit) or CSI (\`\\x9B\` 8bit) -- DCS - Device Control String: sequence starting with \`ESC P\` (7bit) or DCS (\`\\x90\` 8bit) -- OSC - Operating System Command: sequence starting with \`ESC ]\` (7bit) or OSC (\`\\x9D\` 8bit) +- CSI - Control Sequence Introducer: sequence starting with \`ESC [\` (7bit) or CSI (\`\\x9B\`, 8bit) +- DCS - Device Control String: sequence starting with \`ESC P\` (7bit) or DCS (\`\\x90\`, 8bit) +- OSC - Operating System Command: sequence starting with \`ESC ]\` (7bit) or OSC (\`\\x9D\`, 8bit) Application Program Command (APC), Privacy Message (PM) and Start of String (SOS) are recognized but not supported, any sequence of these types will be ignored. They are also not hookable by the API. @@ -85,10 +85,10 @@ To denote the sequences the tables use the same abbreviations as xterm does: {{#C0.length}} ## C0 -| Mnemonic | Name | Sequence | Short Description | Status | -| -------- | ---- | -------- | ----------------- | ------ | +| Mnemonic | Name | Sequence | Short Description | Support | +| -------- | ---- | -------- | ----------------- | ------- | {{#C0}} -| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}})_{{/longDescription.length}} | {{status}} | +| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}})_{{/longDescription.length}} | {{{status}}} | {{/C0}} {{#C0.hasLongDescriptions}} @@ -108,10 +108,10 @@ To denote the sequences the tables use the same abbreviations as xterm does: {{#C1.length}} ## C1 -| Mnemonic | Name | Sequence | Short Description | Status | -| -------- | ---- | -------- | ----------------- | ------ | +| Mnemonic | Name | Sequence | Short Description | Support | +| -------- | ---- | -------- | ----------------- | ------- | {{#C1}} -| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}})_{{/longDescription.length}} | {{status}} | +| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}})_{{/longDescription.length}} | {{{status}}} | {{/C1}} {{#C1.hasLongDescriptions}} @@ -131,10 +131,10 @@ To denote the sequences the tables use the same abbreviations as xterm does: {{#CSI.length}} ## CSI -| Mnemonic | Name | Sequence | Short Description | Status | -| -------- | ---- | -------- | ----------------- | ------ | +| Mnemonic | Name | Sequence | Short Description | Support | +| -------- | ---- | -------- | ----------------- | ------- | {{#CSI}} -| {{mnemonic}} | {{name}} | \`\`{{{sequence}}}\`\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}})_{{/longDescription.length}} | {{status}} | +| {{mnemonic}} | {{name}} | \`\`{{{sequence}}}\`\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}})_{{/longDescription.length}} | {{{status}}} | {{/CSI}} {{#CSI.hasLongDescriptions}} @@ -154,10 +154,10 @@ To denote the sequences the tables use the same abbreviations as xterm does: {{#DCS.length}} ## DCS -| Mnemonic | Name | Sequence | Short Description | Status | -| -------- | ---- | -------- | ----------------- | ------ | +| Mnemonic | Name | Sequence | Short Description | Support | +| -------- | ---- | -------- | ----------------- | ------- | {{#DCS}} -| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}})_{{/longDescription.length}} | {{status}} | +| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}})_{{/longDescription.length}} | {{{status}}} | {{/DCS}} {{#DCS.hasLongDescriptions}} @@ -177,10 +177,10 @@ To denote the sequences the tables use the same abbreviations as xterm does: {{#ESC.length}} ## ESC -| Mnemonic | Name | Sequence | Short Description | Status | -| -------- | ---- | -------- | ----------------- | ------ | +| Mnemonic | Name | Sequence | Short Description | Support | +| -------- | ---- | -------- | ----------------- | ------- | {{#ESC}} -| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}})_{{/longDescription.length}} | {{status}} | +| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}})_{{/longDescription.length}} | {{{status}}} | {{/ESC}} {{#ESC.hasLongDescriptions}} @@ -202,10 +202,10 @@ To denote the sequences the tables use the same abbreviations as xterm does: **Note**: Other than listed in the table, the parser recognizes both ST (ECMA-48) and BEL (xterm) as OSC sequence finalizer. -| Identifier | Sequence | Short Description | Status | -| ---------- | -------- | ----------------- | ------ | +| Identifier | Sequence | Short Description | Support | +| ---------- | -------- | ----------------- | ------- | {{#OSC}} -| {{mnemonic}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}})_{{/longDescription.length}} | {{status}} | +| {{mnemonic}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}})_{{/longDescription.length}} | {{{status}}} | {{/OSC}} {{#OSC.hasLongDescriptions}} @@ -222,6 +222,34 @@ To denote the sequences the tables use the same abbreviations as xterm does: {{/OSC.length}} ` +// support status marcos +// applied for: +// - status field of single @vt line +// - all lines in long description +const MACRO = [ + // #Y - supported + [/#Y/g, s => '✓'], + // #N - unsupported + [/#N/g, s => '✗'], + // #P[reason] - partial support with a reason as title + [/#P\[(.*?)\]/g, (s, p1) => `Partial`], + // #B[reason] - supported but broken in a certain way, reason in title + [/#B\[(.*?)\]/g, (s, p1) => `Broken`] +]; + +function applyMacros(s) { + for (let i = 0; i < MACRO.length; ++i) { + s = s.replace(MACRO[i][0], MACRO[i][1]); + } + return s; +} + +function replaceStatus(s) { + if (s === 'supported') return '✓'; + if (s === 'unsupported') return '✗'; + return s; +} + function createAnchorSlug(s) { return s.toLowerCase().split(' ').join('-'); } @@ -264,7 +292,8 @@ function* parseMultiLineGen(filename, s) { feature = undefined; noLineCount = 0; } else { - longDescription.push(line); + //longDescription.push(line); + longDescription.push(applyMacros(line)); if (line) noLineCount = 0; } } @@ -331,6 +360,8 @@ function getSorter(entry) { function postProcessData(features) { const featureTable = {}; for (const feature of features) { + // feature.status = replaceStatus(feature.status); + feature.status = applyMacros(feature.status); if (featureTable[feature.type] === undefined) { featureTable[feature.type] = []; } diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 33b731a7c..53056cd02 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -31,26 +31,26 @@ const GLEVEL: {[key: string]: number} = {'(': 0, ')': 1, '*': 2, '+': 3, '-': 1, /** * VT commands done by the parser - FIXME: move this to the parser? */ -// @vt: supported ESC CSI "Control Sequence Introducer" "ESC [" "Start of a CSI sequence." -// @vt: supported ESC OSC "Operating System Command" "ESC ]" "Start of an OSC sequence." -// @vt: supported ESC DCS "Device Control String" "ESC P" "Start of a DCS sequence." -// @vt: supported ESC ST "String Terminator" "ESC \" "Terminator used for string type sequences." -// @vt: supported ESC PM "Privacy Message" "ESC ^" "Start of a privacy message." -// @vt: supported ESC APC "Application Program Command" "ESC _" "Start of an APC sequence." -// @vt: supported C1 CSI "Control Sequence Introducer" "\x9B" "Start of a CSI sequence." -// @vt: supported C1 OSC "Operating System Command" "\x9D" "Start of an OSC sequence." -// @vt: supported C1 DCS "Device Control String" "\x90" "Start of a DCS sequence." -// @vt: supported C1 ST "String Terminator" "\x9C" "Terminator used for string type sequences." -// @vt: supported C1 PM "Privacy Message" "\x9E" "Start of a privacy message." -// @vt: supported C1 APC "Application Program Command" "\x9F" "Start of an APC sequence." -// @vt: supported C0 NUL "Null" "\0, \x00" "NUL is ignored." -// @vt: supported C0 ESC "Escape" "\e, \x1B" "Start of a sequence. Cancels any other sequence." +// @vt: #Y ESC CSI "Control Sequence Introducer" "ESC [" "Start of a CSI sequence." +// @vt: #Y ESC OSC "Operating System Command" "ESC ]" "Start of an OSC sequence." +// @vt: #Y ESC DCS "Device Control String" "ESC P" "Start of a DCS sequence." +// @vt: #Y ESC ST "String Terminator" "ESC \" "Terminator used for string type sequences." +// @vt: #Y ESC PM "Privacy Message" "ESC ^" "Start of a privacy message." +// @vt: #Y ESC APC "Application Program Command" "ESC _" "Start of an APC sequence." +// @vt: #Y C1 CSI "Control Sequence Introducer" "\x9B" "Start of a CSI sequence." +// @vt: #Y C1 OSC "Operating System Command" "\x9D" "Start of an OSC sequence." +// @vt: #Y C1 DCS "Device Control String" "\x90" "Start of a DCS sequence." +// @vt: #Y C1 ST "String Terminator" "\x9C" "Terminator used for string type sequences." +// @vt: #Y C1 PM "Privacy Message" "\x9E" "Start of a privacy message." +// @vt: #Y C1 APC "Application Program Command" "\x9F" "Start of an APC sequence." +// @vt: #Y C0 NUL "Null" "\0, \x00" "NUL is ignored." +// @vt: #Y C0 ESC "Escape" "\e, \x1B" "Start of a sequence. Cancels any other sequence." /** * Document common VT features here that are currently unsupported */ -// @vt: unsupported DCS SIXEL "SIXEL Graphics" "DCS Ps ; Ps ; Ps ; q Pt ST" "Draw SIXEL image starting at cursor position." -// @vt: unsupported OSC 1 "Set Icon Name" "OSC 1 ; Pt BEL" "Set icon name." +// @vt: #N DCS SIXEL "SIXEL Graphics" "DCS Ps ; Ps ; Ps ; q Pt ST" "Draw SIXEL image starting at cursor position." +// @vt: #N OSC 1 "Set Icon Name" "OSC 1 ; Pt BEL" "Set icon name." /** * Max length of the UTF32 input buffer. Real memory consumption is 4 times higher. @@ -68,7 +68,7 @@ const MAX_PARSEBUFFER_LENGTH = 131072; * Request Status String (DECRQSS), VT420 and up. * Response: DECRPSS (https://vt100.net/docs/vt510-rm/DECRPSS.html) * - * @vt: partly DCS DECRQSS "Request Selection or Setting" "DCS $ q Pt ST" "Request several terminal settings." + * @vt: #P[See limited support below.] DCS DECRQSS "Request Selection or Setting" "DCS $ q Pt ST" "Request several terminal settings." * Response is in the form `ESC P 1 $ r Pt ST` for valid requests, where `Pt` contains the corresponding CSI string, * `ESC P 0 ST` for invalid requests. * @@ -145,7 +145,7 @@ class DECRQSS implements IDcsHandler { * DECUDK (https://vt100.net/docs/vt510-rm/DECUDK.html) * not supported * - * @vt: unsupported DCS DECUDK "User Defined Keys" "DCS Ps ; Ps | Pt ST" "Definitions for user-defined keys." + * @vt: #N DCS DECUDK "User Defined Keys" "DCS Ps ; Ps | Pt ST" "Definitions for user-defined keys." */ /** @@ -153,7 +153,7 @@ class DECRQSS implements IDcsHandler { * Request Terminfo String * not implemented * - * @vt: unsupported DCS XTGETTCAP "Request Terminfo String" "DCS + q Pt ST" "Request Terminfo String." + * @vt: #N DCS XTGETTCAP "Request Terminfo String" "DCS + q Pt ST" "Request Terminfo String." */ /** @@ -161,7 +161,7 @@ class DECRQSS implements IDcsHandler { * Set Terminfo Data * not supported * - * @vt: unsupported DCS XTSETTCAP "Set Terminfo Data" "DCS + p Pt ST" "Set Terminfo Data." + * @vt: #N DCS XTSETTCAP "Set Terminfo Data" "DCS + p Pt ST" "Set Terminfo Data." */ @@ -604,7 +604,7 @@ export class InputHandler extends Disposable implements IInputHandler { * BEL * Bell (Ctrl-G). * - * @vt: supported C0 BEL "Bell" "\a, \x07" "Ring the bell." + * @vt: #Y C0 BEL "Bell" "\a, \x07" "Ring the bell." * The behavior of the bell is further customizable with `ITerminalOptions.bellStyle` * and `ITerminalOptions.bellSound`. */ @@ -616,11 +616,11 @@ export class InputHandler extends Disposable implements IInputHandler { * LF * Line Feed or New Line (NL). (LF is Ctrl-J). * - * @vt: supported C0 LF "Line Feed" "\n, \x0A" "Move the cursor one row down, scrolling if needed." + * @vt: #Y C0 LF "Line Feed" "\n, \x0A" "Move the cursor one row down, scrolling if needed." * Scrolling is restricted to scroll margins and will only happen on the bottom line. * - * @vt: supported C0 VT "Vertical Tabulation" "\v, \x0B" "Treated as LF." - * @vt: supported C0 FF "Form Feed" "\f, \x0C" "Treated as LF." + * @vt: #Y C0 VT "Vertical Tabulation" "\v, \x0B" "Treated as LF." + * @vt: #Y C0 FF "Form Feed" "\f, \x0C" "Treated as LF." */ public lineFeed(): void { // make buffer local for faster access @@ -650,7 +650,7 @@ export class InputHandler extends Disposable implements IInputHandler { * CR * Carriage Return (Ctrl-M). * - * @vt: supported C0 CR "Carriage Return" "\r, \x0D" "Move the cursor to the beginning of the row." + * @vt: #Y C0 CR "Carriage Return" "\r, \x0D" "Move the cursor to the beginning of the row." */ public carriageReturn(): void { this._bufferService.buffer.x = 0; @@ -660,7 +660,7 @@ export class InputHandler extends Disposable implements IInputHandler { * BS * Backspace (Ctrl-H). * - * @vt: supported C0 BS "Backspace" "\b, \x08" "Move the cursor one position to the left." + * @vt: #Y C0 BS "Backspace" "\b, \x08" "Move the cursor one position to the left." */ public backspace(): void { this._restrictCursor(); @@ -673,7 +673,7 @@ export class InputHandler extends Disposable implements IInputHandler { * TAB * Horizontal Tab (HT) (Ctrl-I). * - * @vt: supported C0 HT "Horizontal Tabulation" "\t, \x09" "Move the cursor to the next character tab stop." + * @vt: #Y C0 HT "Horizontal Tabulation" "\t, \x09" "Move the cursor to the next character tab stop." */ public tab(): void { if (this._bufferService.buffer.x >= this._bufferService.cols) { @@ -691,7 +691,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Shift Out (Ctrl-N) -> Switch to Alternate Character Set. This invokes the * G1 character set. * - * @vt: partly C0 SO "Shift Out" "\x0E" "Switch to an alternative character set." + * @vt: #P[Only limited ISO-2022 charset support.] C0 SO "Shift Out" "\x0E" "Switch to an alternative character set." */ public shiftOut(): void { this._charsetService.setgLevel(1); @@ -702,7 +702,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Shift In (Ctrl-O) -> Switch to Standard Character Set. This invokes the G0 * character set (the default). * - * @vt: supported C0 SI "Shift In" "\x0F" "Return to regular character set after Shift Out." + * @vt: #Y C0 SI "Shift In" "\x0F" "Return to regular character set after Shift Out." */ public shiftIn(): void { this._charsetService.setgLevel(0); @@ -749,7 +749,7 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps A * Cursor Up Ps Times (default = 1) (CUU). * - * @vt: supported CSI CUU "Cursor Up" "CSI Ps A" "Move cursor `Ps` times up (default=1)." + * @vt: #Y CSI CUU "Cursor Up" "CSI Ps A" "Move cursor `Ps` times up (default=1)." * If the cursor would pass the top scroll margin, it will stop there. */ public cursorUp(params: IParams): void { @@ -766,7 +766,7 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps B * Cursor Down Ps Times (default = 1) (CUD). * - * @vt: supported CSI CUD "Cursor Down" "CSI Ps B" "Move cursor `Ps` times down (default=1)." + * @vt: #Y CSI CUD "Cursor Down" "CSI Ps B" "Move cursor `Ps` times down (default=1)." * If the cursor would pass the bottom scroll margin, it will stop there. */ public cursorDown(params: IParams): void { @@ -783,7 +783,7 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps C * Cursor Forward Ps Times (default = 1) (CUF). * - * @vt: supported CSI CUF "Cursor Forward" "CSI Ps C" "Move cursor `Ps` times forward (default=1)." + * @vt: #Y CSI CUF "Cursor Forward" "CSI Ps C" "Move cursor `Ps` times forward (default=1)." */ public cursorForward(params: IParams): void { this._moveCursor(params.params[0] || 1, 0); @@ -793,7 +793,7 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps D * Cursor Backward Ps Times (default = 1) (CUB). * - * @vt: supported CSI CUB "Cursor Backward" "CSI Ps D" "Move cursor `Ps` times backward (default=1)." + * @vt: #Y CSI CUB "Cursor Backward" "CSI Ps D" "Move cursor `Ps` times backward (default=1)." */ public cursorBackward(params: IParams): void { this._moveCursor(-(params.params[0] || 1), 0); @@ -804,7 +804,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Cursor Next Line Ps Times (default = 1) (CNL). * Other than cursorDown (CUD) also set the cursor to first column. * - * @vt: supported CSI CNL "Cursor Next Line" "CSI Ps E" "Move cursor `Ps` times down (default=1) and to the first column." + * @vt: #Y CSI CNL "Cursor Next Line" "CSI Ps E" "Move cursor `Ps` times down (default=1) and to the first column." * Same as CUD, additionally places the cursor at the first column. */ public cursorNextLine(params: IParams): void { @@ -817,7 +817,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Cursor Previous Line Ps Times (default = 1) (CPL). * Other than cursorUp (CUU) also set the cursor to first column. * - * @vt: supported CSI CPL "Cursor Backward" "CSI Ps F" "Move cursor `Ps` times up (default=1) and to the first column." + * @vt: #Y CSI CPL "Cursor Backward" "CSI Ps F" "Move cursor `Ps` times up (default=1) and to the first column." * Same as CUU, additionally places the cursor at the first column. */ public cursorPrecedingLine(params: IParams): void { @@ -829,7 +829,7 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps G * Cursor Character Absolute [column] (default = [row,1]) (CHA). * - * @vt: supported CSI CHA "Cursor Horizontal Absolute" "CSI Ps G" "Move cursor to `Ps`-th column of the active row (default=1)." + * @vt: #Y CSI CHA "Cursor Horizontal Absolute" "CSI Ps G" "Move cursor to `Ps`-th column of the active row (default=1)." */ public cursorCharAbsolute(params: IParams): void { this._setCursor((params.params[0] || 1) - 1, this._bufferService.buffer.y); @@ -839,7 +839,7 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps ; Ps H * Cursor Position [row;column] (default = [1,1]) (CUP). * - * @vt: supported CSI CUP "Cursor Position" "CSI Ps ; Ps H" "Set cursor to position [`Ps`, `Ps`] (default = [1, 1])." + * @vt: #Y CSI CUP "Cursor Position" "CSI Ps ; Ps H" "Set cursor to position [`Ps`, `Ps`] (default = [1, 1])." * If ORIGIN mode is set, places the cursor to the absolute position within the scroll margins. * If ORIGIN mode is not set, places the cursor to the absolute position within the viewport. * Note that the coordinates are 1-based, thus the top left position starts at `1 ; 1`. @@ -857,7 +857,7 @@ export class InputHandler extends Disposable implements IInputHandler { * [column] (default = [row,1]) (HPA). * Currently same functionality as CHA. * - * @vt: supported CSI HPA "Horizontal Position Absolute" "CSI Ps ` " "Same as CHA." + * @vt: #Y CSI HPA "Horizontal Position Absolute" "CSI Ps ` " "Same as CHA." */ public charPosAbsolute(params: IParams): void { this._setCursor((params.params[0] || 1) - 1, this._bufferService.buffer.y); @@ -867,7 +867,7 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Pm a Character Position Relative * [columns] (default = [row,col+1]) (HPR) * - * @vt: supported CSI HPR "Horizontal Position Relative" "CSI Ps a" "Same as CUF." + * @vt: #Y CSI HPR "Horizontal Position Relative" "CSI Ps a" "Same as CUF." */ public hPositionRelative(params: IParams): void { this._moveCursor(params.params[0] || 1, 0); @@ -877,7 +877,7 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Pm d Vertical Position Absolute (VPA) * [row] (default = [1,column]) * - * @vt: supported CSI VPA "Vertical Position Absolute" "CSI Ps d" "Move cursor to `Ps`-th row (default=1)." + * @vt: #Y CSI VPA "Vertical Position Absolute" "CSI Ps d" "Move cursor to `Ps`-th row (default=1)." */ public linePosAbsolute(params: IParams): void { this._setCursor(this._bufferService.buffer.x, (params.params[0] || 1) - 1); @@ -888,7 +888,7 @@ export class InputHandler extends Disposable implements IInputHandler { * [rows] (default = [row+1,column]) * reuse CSI Ps B ? * - * @vt: supported CSI VPR "Vertical Position Relative" "CSI Ps e" "Move cursor `Ps` times down (default=1)." + * @vt: #Y CSI VPR "Vertical Position Relative" "CSI Ps e" "Move cursor `Ps` times down (default=1)." */ public vPositionRelative(params: IParams): void { this._moveCursor(0, params.params[0] || 1); @@ -900,7 +900,7 @@ export class InputHandler extends Disposable implements IInputHandler { * [1,1]) (HVP). * Same as CUP. * - * @vt: supported CSI HVP "Horizontal and Vertical Position" "CSI Ps ; Ps f" "Same as CUP." + * @vt: #Y CSI HVP "Horizontal and Vertical Position" "CSI Ps ; Ps f" "Same as CUP." */ public hVPosition(params: IParams): void { this.cursorPosition(params); @@ -914,7 +914,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 2 -> Clear Stops on Line. * http://vt100.net/annarbor/aaa-ug/section6.html * - * @vt: supported CSI TBC "Tab Clear" "CSI Ps g" "Clear tab stops at current position (0) or all (3) (default=0)." + * @vt: #Y CSI TBC "Tab Clear" "CSI Ps g" "Clear tab stops at current position (0) or all (3) (default=0)." * Clearing tabstops off the active row (Ps = 2, VT100) is currently not supported. */ public tabClear(params: IParams): void { @@ -930,7 +930,7 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps I * Cursor Forward Tabulation Ps tab stops (default = 1) (CHT). * - * @vt: supported CSI CHT "Cursor Horizontal Tabulation" "CSI Ps I" "Move cursor `Ps` times tabs forward (default=1)." + * @vt: #Y CSI CHT "Cursor Horizontal Tabulation" "CSI Ps I" "Move cursor `Ps` times tabs forward (default=1)." */ public cursorForwardTab(params: IParams): void { if (this._bufferService.buffer.x >= this._bufferService.cols) { @@ -945,7 +945,7 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps Z Cursor Backward Tabulation Ps tab stops (default = 1) (CBT). * - * @vt: supported CSI CBT "Cursor Backward Tabulation" "CSI Ps Z" "Move cursor `Ps` tabs backward (default=1)." + * @vt: #Y CSI CBT "Cursor Backward Tabulation" "CSI Ps Z" "Move cursor `Ps` tabs backward (default=1)." */ public cursorBackwardTab(params: IParams): void { if (this._bufferService.buffer.x >= this._bufferService.cols) { @@ -1005,7 +1005,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 1 -> Selective Erase Above. * Ps = 2 -> Selective Erase All. * - * @vt: supported CSI ED "Erase In Display" "CSI Ps J" "Erase various parts of the viewport." + * @vt: #Y CSI ED "Erase In Display" "CSI Ps J" "Erase various parts of the viewport." * Supported param values: * * | Ps | Effect | @@ -1015,7 +1015,7 @@ export class InputHandler extends Disposable implements IInputHandler { * | 2 | Erase complete viewport. | * | 3 | Erase scrollback. | * - * @vt: partly CSI DECSED "Selective Erase In Display" "CSI ? Ps J" "Currently the same as ED." + * @vt: #P[Protection attributes are not supported.] CSI DECSED "Selective Erase In Display" "CSI ? Ps J" "Currently the same as ED." */ public eraseInDisplay(params: IParams): void { this._restrictCursor(); @@ -1077,7 +1077,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 1 -> Selective Erase to Left. * Ps = 2 -> Selective Erase All. * - * @vt: supported CSI EL "Erase In Line" "CSI Ps K" "Erase various parts of the active row." + * @vt: #Y CSI EL "Erase In Line" "CSI Ps K" "Erase various parts of the active row." * Supported param values: * * | Ps | Effect | @@ -1086,7 +1086,7 @@ export class InputHandler extends Disposable implements IInputHandler { * | 1 | Erase from the beginning of the line through the cursor. | * | 2 | Erase complete line. | * - * @vt: partly CSI DECSEL "Selective Erase In Line" "CSI ? Ps K" "Currently the same as EL." + * @vt: #P[Protection attributes are not supported.] CSI DECSEL "Selective Erase In Line" "CSI ? Ps K" "Currently the same as EL." */ public eraseInLine(params: IParams): void { this._restrictCursor(); @@ -1108,7 +1108,7 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps L * Insert Ps Line(s) (default = 1) (IL). * - * @vt: supported CSI IL "Insert Line" "CSI Ps L" "Insert `Ps` blank lines at active row (default=1)." + * @vt: #Y CSI IL "Insert Line" "CSI Ps L" "Insert `Ps` blank lines at active row (default=1)." * For every inserted line at the scroll top one line at the scroll bottom gets removed. * The cursor is set to the first column. * IL has no effect if the cursor is outside the scroll margins. @@ -1143,7 +1143,7 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps M * Delete Ps Line(s) (default = 1) (DL). * - * @vt: supported CSI DL "Delete Line" "CSI Ps M" "Delete `Ps` lines at active row (default=1)." + * @vt: #Y CSI DL "Delete Line" "CSI Ps M" "Delete `Ps` lines at active row (default=1)." * For every deleted line at the scroll top one blank line at the scroll bottom gets appended. * The cursor is set to the first column. * DL has no effect if the cursor is outside the scroll margins. @@ -1179,7 +1179,7 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps @ * Insert Ps (Blank) Character(s) (default = 1) (ICH). * - * @vt: supported CSI ICH "Insert Characters" "CSI Ps @" "Insert `Ps` (blank) characters (default = 1)." + * @vt: #Y CSI ICH "Insert Characters" "CSI Ps @" "Insert `Ps` (blank) characters (default = 1)." * The ICH sequence inserts `Ps` blank characters. The cursor remains at the beginning of the blank characters. * Text between the cursor and right margin moves to the right. Characters moved past the right margin are lost. * @@ -1204,7 +1204,7 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps P * Delete Ps Character(s) (default = 1) (DCH). * - * @vt: supported CSI DCH "Delete Character" "CSI Ps P" "Delete `Ps` characters (default=1)." + * @vt: #Y CSI DCH "Delete Character" "CSI Ps P" "Delete `Ps` characters (default=1)." * As characters are deleted, the remaining characters between the cursor and right margin move to the left. * Character attributes move with the characters. The terminal adds blank characters at the right margin. * @@ -1228,7 +1228,7 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps S Scroll up Ps lines (default = 1) (SU). * - * @vt: supported CSI SU "Scroll Up" "CSI Ps S" "Scroll `Ps` lines up (default=1)." + * @vt: #Y CSI SU "Scroll Up" "CSI Ps S" "Scroll `Ps` lines up (default=1)." * * * FIXME: scrolled out lines at top = 1 should add to scrollback (xterm) @@ -1249,7 +1249,7 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps T Scroll down Ps lines (default = 1) (SD). * - * @vt: supported CSI SD "Scroll Down" "CSI Ps T" "Scroll `Ps` lines down (default=1)." + * @vt: #Y CSI SD "Scroll Down" "CSI Ps T" "Scroll `Ps` lines down (default=1)." */ public scrollDown(params: IParams): void { let param = params.params[0] || 1; @@ -1278,7 +1278,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Supported: * - always left shift (no line orientation setting respected) * - * @vt: supported CSI SL "Scroll Left" "CSI Ps SP @" "Scroll viewport `Ps` times to the left." + * @vt: #Y CSI SL "Scroll Left" "CSI Ps SP @" "Scroll viewport `Ps` times to the left." * SL moves the content of all lines within the scroll margins `Ps` times to the left. * SL has no effect outside of the scroll margins. */ @@ -1310,7 +1310,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Supported: * - always right shift (no line orientation setting respected) * - * @vt: supported CSI SR "Scroll Right" "CSI Ps SP A" "Scroll viewport `Ps` times to the right." + * @vt: #Y CSI SR "Scroll Right" "CSI Ps SP A" "Scroll viewport `Ps` times to the right." * SL moves the content of all lines within the scroll margins `Ps` times to the right. * Content at the right margin is lost. * SL has no effect outside of the scroll margins. @@ -1333,7 +1333,7 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Pm ' } * Insert Ps Column(s) (default = 1) (DECIC), VT420 and up. * - * @vt: supported CSI DECIC "Insert Columns" "CSI Ps ' }" "Insert `Ps` columns at cursor position." + * @vt: #Y CSI DECIC "Insert Columns" "CSI Ps ' }" "Insert `Ps` columns at cursor position." * DECIC inserts `Ps` times blank columns at the cursor position for all lines with the scroll margins, * moving content to the right. Content at the right margin is lost. * DECIC has no effect outside the scrolling margins. @@ -1356,7 +1356,7 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Pm ' ~ * Delete Ps Column(s) (default = 1) (DECDC), VT420 and up. * - * @vt: supported CSI DECDC "Delete Columns" "CSI Ps ' ~" "Delete `Ps` columns at cursor position." + * @vt: #Y CSI DECDC "Delete Columns" "CSI Ps ' ~" "Delete `Ps` columns at cursor position." * DECDC deletes `Ps` times columns at the cursor position for all lines with the scroll margins, * moving content to the left. Blank columns are added at the right margin. * DECDC has no effect outside the scrolling margins. @@ -1379,7 +1379,7 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps X * Erase Ps Character(s) (default = 1) (ECH). * - * @vt: supported CSI ECH "Erase Character" "CSI Ps X" "Erase `Ps` characters from current cursor position to the right (default=1)." + * @vt: #Y CSI ECH "Erase Character" "CSI Ps X" "Erase `Ps` characters from current cursor position to the right (default=1)." * ED erases `Ps` characters from current cursor position to the right. * ED works inside or outside the scrolling margins. */ @@ -1419,7 +1419,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Note: To get reset on a valid sequence working correctly without much runtime penalty, * the preceding codepoint is stored on the parser in `this.print` and reset during `parser.parse`. * - * @vt: supported CSI REP "Repeat Preceding Character" "CSI Ps b" "Repeat preceding character `Ps` times (default=1)." + * @vt: #Y CSI REP "Repeat Preceding Character" "CSI Ps b" "Repeat preceding character `Ps` times (default=1)." * REP repeats the previous character `Ps` times advancing the cursor, also wrapping if DECAWM is set. * REP has no effect if the sequence does not follow a printable ASCII character * (NOOP for any other sequence in between or NON ASCII characters). @@ -1457,7 +1457,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 2 2 -> ANSI color, e.g., VT525. * Ps = 2 9 -> ANSI text locator (i.e., DEC Locator mode). * - * @vt: supported CSI DA1 "Primary Device Attributes" "CSI c" "Send primary device attributes." + * @vt: #Y CSI DA1 "Primary Device Attributes" "CSI c" "Send primary device attributes." * * * TODO: fix and cleanup response @@ -1492,7 +1492,7 @@ export class InputHandler extends Disposable implements IInputHandler { * xterm/charproc.c - line 2012, for more information. * vim responds with ^[[?0c or ^[[?1c after the terminal's response (?) * - * @vt: supported CSI DA2 "Secondary Device Attributes" "CSI > c" "Send primary device attributes." + * @vt: #Y CSI DA2 "Secondary Device Attributes" "CSI > c" "Send primary device attributes." * * * TODO: fix and cleanup response @@ -1524,15 +1524,15 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 1 2 -> Send/receive (SRM). * Ps = 2 0 -> Automatic Newline (LNM). * - * @vt: partly CSI SM "Set Mode" "CSI Pm h" "Set various terminal modes." + * @vt: #P[Only IRM is supported.] CSI SM "Set Mode" "CSI Pm h" "Set various terminal modes." * Supported param values by SM: * - * | Param | Action | Status | - * | ----- | -------------------------------------- | ----------- | - * | 2 | Keyboard Action Mode (KAM). Always on. | unsupported | - * | 4 | Insert Mode (IRM). | supported | - * | 12 | Send/receive (SRM). Always off. | unsupported | - * | 20 | Automatic Newline (LNM). Always off. | unsupported | + * | Param | Action | Support | + * | ----- | -------------------------------------- | ------- | + * | 2 | Keyboard Action Mode (KAM). Always on. | #N | + * | 4 | Insert Mode (IRM). | #Y | + * | 12 | Send/receive (SRM). Always off. | #N | + * | 20 | Automatic Newline (LNM). Always off. | #N | * * * FIXME: why is LNM commented out? @@ -1631,33 +1631,33 @@ export class InputHandler extends Disposable implements IInputHandler { * Modes: * http: *vt100.net/docs/vt220-rm/chapter4.html * - * @vt: partly CSI DECSET "DEC Private Set Mode" "CSI ? Pm h" "Set various terminal attributes." + * @vt: #P[See below for supported modes.] CSI DECSET "DEC Private Set Mode" "CSI ? Pm h" "Set various terminal attributes." * Supported param values by DECSET: * - * | param | Action | Status | - * | ----- | ------------------------------------------------------- | ----------- | - * | 1 | Application Cursor Keys (DECCKM). | supported | - * | 2 | Designate US-ASCII for character sets G0-G3 (DECANM). | supported | - * | 3 | 132 Column Mode (DECCOLM). | supported | - * | 6 | Origin Mode (DECOM). | supported | - * | 7 | Auto-wrap Mode (DECAWM). | supported | - * | 8 | Auto-repeat Keys (DECARM). Always on. | unsupported | - * | 9 | X10 xterm mouse protocol. | supported | - * | 12 | Start Blinking Cursor. | supported | - * | 25 | Show Cursor (DECTCEM). | supported | - * | 47 | Use Alternate Screen Buffer. | supported | - * | 66 | Application keypad (DECNKM). | supported | - * | 1000 | X11 xterm mouse protocol. | supported | - * | 1002 | Use Cell Motion Mouse Tracking. | supported | - * | 1003 | Use All Motion Mouse Tracking. | supported | - * | 1004 | Send FocusIn/FocusOut events | supported | - * | 1005 | Enable UTF-8 Mouse Mode. | unsupported | - * | 1006 | Enable SGR Mouse Mode. | supported | - * | 1015 | Enable urxvt Mouse Mode. | unsupported | - * | 1047 | Use Alternate Screen Buffer. | supported | - * | 1048 | Save cursor as in DECSC. | supported | - * | 1049 | Save cursor and switch to alternate buffer clearing it. | partly | - * | 2004 | Set bracketed paste mode. | supported | + * | param | Action | Support | + * | ----- | ------------------------------------------------------- | --------| + * | 1 | Application Cursor Keys (DECCKM). | #Y | + * | 2 | Designate US-ASCII for character sets G0-G3 (DECANM). | #Y | + * | 3 | 132 Column Mode (DECCOLM). | #Y | + * | 6 | Origin Mode (DECOM). | #Y | + * | 7 | Auto-wrap Mode (DECAWM). | #Y | + * | 8 | Auto-repeat Keys (DECARM). Always on. | #N | + * | 9 | X10 xterm mouse protocol. | #Y | + * | 12 | Start Blinking Cursor. | #Y | + * | 25 | Show Cursor (DECTCEM). | #Y | + * | 47 | Use Alternate Screen Buffer. | #Y | + * | 66 | Application keypad (DECNKM). | #Y | + * | 1000 | X11 xterm mouse protocol. | #Y | + * | 1002 | Use Cell Motion Mouse Tracking. | #Y | + * | 1003 | Use All Motion Mouse Tracking. | #Y | + * | 1004 | Send FocusIn/FocusOut events | #Y | + * | 1005 | Enable UTF-8 Mouse Mode. | #N | + * | 1006 | Enable SGR Mouse Mode. | #Y | + * | 1015 | Enable urxvt Mouse Mode. | #N | + * | 1047 | Use Alternate Screen Buffer. | #Y | + * | 1048 | Save cursor as in DECSC. | #Y | + * | 1049 | Save cursor and switch to alternate buffer clearing it. | #P[Does not clear the alternate buffer.] | + * | 2004 | Set bracketed paste mode. | #Y | * * * FIXME: implement DECSCNM, 1049 should clear altbuffer @@ -1757,15 +1757,15 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 1 2 -> Send/receive (SRM). * Ps = 2 0 -> Normal Linefeed (LNM). * - * @vt: partly CSI RM "Reset Mode" "CSI Pm l" "Set various terminal attributes." + * @vt: #P[Only IRM is supported.] CSI RM "Reset Mode" "CSI Pm l" "Set various terminal attributes." * Supported param values by RM: * - * | Param | Action | Status | - * | ----- | -------------------------------------- | ----------- | - * | 2 | Keyboard Action Mode (KAM). Always on. | unsupported | - * | 4 | Replace Mode (IRM). (default) | supported | - * | 12 | Send/receive (SRM). Always off. | unsupported | - * | 20 | Normal Linefeed (LNM). Always off. | unsupported | + * | Param | Action | Support | + * | ----- | -------------------------------------- | ------- | + * | 2 | Keyboard Action Mode (KAM). Always on. | #N | + * | 4 | Replace Mode (IRM). (default) | #Y | + * | 12 | Send/receive (SRM). Always off. | #N | + * | 20 | Normal Linefeed (LNM). Always off. | #N | * * * FIXME: why is LNM commented out? @@ -1860,33 +1860,33 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 1 0 6 1 -> Reset keyboard emulation to Sun/PC style. * Ps = 2 0 0 4 -> Reset bracketed paste mode. * - * @vt: partly CSI DECRST "DEC Private Reset Mode" "CSI ? Pm l" "Reset various terminal attributes." + * @vt: #P[See below for supported modes.] CSI DECRST "DEC Private Reset Mode" "CSI ? Pm l" "Reset various terminal attributes." * Supported param values by DECRST: * - * | param | Action | Status | - * | ----- | ------------------------------------------------------- | ----------- | - * | 1 | Normal Cursor Keys (DECCKM). | supported | - * | 2 | Designate VT52 mode (DECANM). | unsupported | - * | 3 | 80 Column Mode (DECCOLM). | broken | - * | 6 | Normal Cursor Mode (DECOM). | supported | - * | 7 | No Wraparound Mode (DECAWM). | supported | - * | 8 | No Auto-repeat Keys (DECARM). | unsupported | - * | 9 | Don't send Mouse X & Y on button press. | supported | - * | 12 | Stop Blinking Cursor. | supported | - * | 25 | Hide Cursor (DECTCEM). | supported | - * | 47 | Use Normal Screen Buffer. | supported | - * | 66 | Numeric keypad (DECNKM). | supported | - * | 1000 | Don't send Mouse reports. | supported | - * | 1002 | Don't use Cell Motion Mouse Tracking. | supported | - * | 1003 | Don't use All Motion Mouse Tracking. | supported | - * | 1004 | Don't send FocusIn/FocusOut events. | supported | - * | 1005 | Disable UTF-8 Mouse Mode. | unsupported | - * | 1006 | Disable SGR Mouse Mode. | supported | - * | 1015 | Disable urxvt Mouse Mode. | unsupported | - * | 1047 | Use Normal Screen Buffer (clearing screen if in alt). | supported | - * | 1048 | Restore cursor as in DECRC. | supported | - * | 1049 | Use Normal Screen Buffer and restore cursor. | supported | - * | 2004 | Reset bracketed paste mode. | supported | + * | param | Action | Support | + * | ----- | ------------------------------------------------------- | ------- | + * | 1 | Normal Cursor Keys (DECCKM). | #Y | + * | 2 | Designate VT52 mode (DECANM). | #N | + * | 3 | 80 Column Mode (DECCOLM). | #B[Switches to old column width instead of 80.] | + * | 6 | Normal Cursor Mode (DECOM). | #Y | + * | 7 | No Wraparound Mode (DECAWM). | #Y | + * | 8 | No Auto-repeat Keys (DECARM). | #N | + * | 9 | Don't send Mouse X & Y on button press. | #Y | + * | 12 | Stop Blinking Cursor. | #Y | + * | 25 | Hide Cursor (DECTCEM). | #Y | + * | 47 | Use Normal Screen Buffer. | #Y | + * | 66 | Numeric keypad (DECNKM). | #Y | + * | 1000 | Don't send Mouse reports. | #Y | + * | 1002 | Don't use Cell Motion Mouse Tracking. | #Y | + * | 1003 | Don't use All Motion Mouse Tracking. | #Y | + * | 1004 | Don't send FocusIn/FocusOut events. | #Y | + * | 1005 | Disable UTF-8 Mouse Mode. | #N | + * | 1006 | Disable SGR Mouse Mode. | #Y | + * | 1015 | Disable urxvt Mouse Mode. | #N | + * | 1047 | Use Normal Screen Buffer (clearing screen if in alt). | #Y | + * | 1048 | Restore cursor as in DECRC. | #Y | + * | 1049 | Use Normal Screen Buffer and restore cursor. | #Y | + * | 2004 | Reset bracketed paste mode. | #Y | * * * FIXME: DECCOLM is currently broken (already fixed in window options PR) @@ -2041,7 +2041,7 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Pm m Character Attributes (SGR). * - * @vt: partly CSI SGR "Select Graphic Rendition" "CSI Pm m" "Set/Reset various text attributes." + * @vt: #P[See below for supported attributes.] CSI SGR "Select Graphic Rendition" "CSI Pm m" "Set/Reset various text attributes." * SGR selects one or more character attributes at the same time. Multiple params (up to 32) * are applied from in order from left to right. The changed attributes are applied to all new * characters received. If you move characters in the viewport by scrolling or any other means, @@ -2049,59 +2049,59 @@ export class InputHandler extends Disposable implements IInputHandler { * * Supported param values by SGR: * - * | Param | Meaning | Status | - * | --------- | -------------------------------------------------------- | ----------- | - * | 0 | Normal (default). Resets any other preceding SGR. | supported | - * | 1 | Bold. (also see `options.drawBoldTextInBrightColors`) | supported | - * | 2 | Faint, decreased intensity. | supported | - * | 3 | Italic. | supported | - * | 4 | Underlined. (no support for newer underline styles) | supported | - * | 5 | Slowly blinking. | unsupported | - * | 6 | Rapidly blinking. | unsupported | - * | 7 | Inverse. Flips foreground and background color. | supported | - * | 8 | Invisible (hidden). | supported | - * | 9 | Crossed-out characters. | unsupported | - * | 21 | Doubly underlined. | unsupported | - * | 22 | Normal (neither bold nor faint). | supported | - * | 23 | No italic. | supported | - * | 24 | Not underlined. | supported | - * | 25 | Steady (not blinking). | supported | - * | 27 | Positive (not inverse). | supported | - * | 28 | Visible (not hidden). | supported | - * | 29 | Not Crossed-out. | unsupported | - * | 30 | Foreground color: Black. | supported | - * | 31 | Foreground color: Red. | supported | - * | 32 | Foreground color: Green. | supported | - * | 33 | Foreground color: Yellow. | supported | - * | 34 | Foreground color: Blue. | supported | - * | 35 | Foreground color: Magenta. | supported | - * | 36 | Foreground color: Cyan. | supported | - * | 37 | Foreground color: White. | supported | - * | 38 | Foreground color: Extended color (see below). | supported | - * | 39 | Foreground color: Default (original). | supported | - * | 40 | Background color: Black. | supported | - * | 41 | Background color: Red. | supported | - * | 42 | Background color: Green. | supported | - * | 43 | Background color: Yellow. | supported | - * | 44 | Background color: Blue. | supported | - * | 45 | Background color: Magenta. | supported | - * | 46 | Background color: Cyan. | supported | - * | 47 | Background color: White. | supported | - * | 48 | Background color: Extended color (see below). | supported | - * | 49 | Background color: Default (original). | supported | - * | 90 - 97 | Bright foreground color (analogous to 30 -37). | supported | - * | 100 - 107 | Bright background color (analogous to 40 -47). | supported | + * | Param | Meaning | Support | + * | --------- | -------------------------------------------------------- | ------- | + * | 0 | Normal (default). Resets any other preceding SGR. | #Y | + * | 1 | Bold. (also see `options.drawBoldTextInBrightColors`) | #Y | + * | 2 | Faint, decreased intensity. | #Y | + * | 3 | Italic. | #Y | + * | 4 | Underlined. (no support for newer underline styles) | #Y | + * | 5 | Slowly blinking. | #N | + * | 6 | Rapidly blinking. | #N | + * | 7 | Inverse. Flips foreground and background color. | #Y | + * | 8 | Invisible (hidden). | #Y | + * | 9 | Crossed-out characters. | #N | + * | 21 | Doubly underlined. | #N | + * | 22 | Normal (neither bold nor faint). | #Y | + * | 23 | No italic. | #Y | + * | 24 | Not underlined. | #Y | + * | 25 | Steady (not blinking). | #Y | + * | 27 | Positive (not inverse). | #Y | + * | 28 | Visible (not hidden). | #Y | + * | 29 | Not Crossed-out. | #N | + * | 30 | Foreground color: Black. | #Y | + * | 31 | Foreground color: Red. | #Y | + * | 32 | Foreground color: Green. | #Y | + * | 33 | Foreground color: Yellow. | #Y | + * | 34 | Foreground color: Blue. | #Y | + * | 35 | Foreground color: Magenta. | #Y | + * | 36 | Foreground color: Cyan. | #Y | + * | 37 | Foreground color: White. | #Y | + * | 38 | Foreground color: Extended color. | #P[Support for RGB and indexed colors, see below.] | + * | 39 | Foreground color: Default (original). | #Y | + * | 40 | Background color: Black. | #Y | + * | 41 | Background color: Red. | #Y | + * | 42 | Background color: Green. | #Y | + * | 43 | Background color: Yellow. | #Y | + * | 44 | Background color: Blue. | #Y | + * | 45 | Background color: Magenta. | #Y | + * | 46 | Background color: Cyan. | #Y | + * | 47 | Background color: White. | #Y | + * | 48 | Background color: Extended color. | #P[Support for RGB and indexed colors, see below.] | + * | 49 | Background color: Default (original). | #Y | + * | 90 - 97 | Bright foreground color (analogous to 30 - 37). | #Y | + * | 100 - 107 | Bright background color (analogous to 40 - 47). | #Y | * * Extended colors are supported for foreground (Ps=38) and background (Ps=48) as follows: * - * | Ps + 1 | Meaning | Status | - * | ------ | ------------------------------------------------------------- | ----------- | - * | 0 | Implementation defined. | unsupported | - * | 1 | Transparent. | unsupported | - * | 2 | RGB color as `Ps ; 2 ; R ; G ; B` or `Ps : 2 : : R : G : B`. | supported | - * | 3 | CMY color. | unsupported | - * | 4 | CMYK color. | unsupported | - * | 5 | Indexed (256 colors) as `Ps ; 5 ; INDEX` or `Ps : 5 : INDEX`. | supported | + * | Ps + 1 | Meaning | Support | + * | ------ | ------------------------------------------------------------- | ------- | + * | 0 | Implementation defined. | #N | + * | 1 | Transparent. | #N | + * | 2 | RGB color as `Ps ; 2 ; R ; G ; B` or `Ps : 2 : : R : G : B`. | #Y | + * | 3 | CMY color. | #N | + * | 4 | CMYK color. | #N | + * | 5 | Indexed (256 colors) as `Ps ; 5 ; INDEX` or `Ps : 5 : INDEX`. | #Y | * * * FIXME: blinking is implemented in attrs, but not working in renderers? @@ -2228,7 +2228,7 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI ? 5 3 n Locator available, if compiled-in, or * CSI ? 5 0 n No Locator, if not. * - * @vt: supported CSI DSR "Device Status Report" "CSI Ps n" "Request cursor position (CPR) with `Ps` = 6." + * @vt: #Y CSI DSR "Device Status Report" "CSI Ps n" "Request cursor position (CPR) with `Ps` = 6." */ public deviceStatus(params: IParams): void { switch (params.params[0]) { @@ -2245,7 +2245,7 @@ export class InputHandler extends Disposable implements IInputHandler { } } - // @vt: partly CSI DECDSR "DEC Device Status Report" "CSI ? Ps n" "Only CPR is supported (same as DSR)." + // @vt: #P[Only CPR is supported.] CSI DECDSR "DEC Device Status Report" "CSI ? Ps n" "Only CPR is supported (same as DSR)." public deviceStatusPrivate(params: IParams): void { // modern xterm doesnt seem to // respond to any of these except ?6, 6, and 5 @@ -2279,7 +2279,7 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI ! p Soft terminal reset (DECSTR). * http://vt100.net/docs/vt220-rm/table4-10.html * - * @vt: supported CSI DECSTR "Soft Terminal Reset" "CSI ! p" "Reset several terminal attributes to initial state." + * @vt: #Y CSI DECSTR "Soft Terminal Reset" "CSI ! p" "Reset several terminal attributes to initial state." * There are two terminal reset sequences - RIS and DECSTR. While RIS performs almost a full terminal bootstrap, * DECSTR only resets certain attributes. For most needs DECSTR should be sufficient. * @@ -2315,7 +2315,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 5 -> blinking bar (xterm). * Ps = 6 -> steady bar (xterm). * - * @vt: supported CSI DECSCUSR "Set Cursor Style" "CSI Ps SP q" "Set cursor style." + * @vt: #Y CSI DECSCUSR "Set Cursor Style" "CSI Ps SP q" "Set cursor style." * Supported cursor styles: * - empty, 0 or 1: steady block * - 2: blink block @@ -2349,7 +2349,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Set Scrolling Region [top;bottom] (default = full size of win- * dow) (DECSTBM). * - * @vt: supported CSI DECSTBM "Set Top and Bottom Margin" "CSI Ps ; Ps r" "Set top and bottom margins of the viewport [top;bottom] (default = viewport size)." + * @vt: #Y CSI DECSTBM "Set Top and Bottom Margin" "CSI Ps ; Ps r" "Set top and bottom margins of the viewport [top;bottom] (default = viewport size)." */ public setScrollRegion(params: IParams): void { const top = params.params[0] || 1; @@ -2372,8 +2372,8 @@ export class InputHandler extends Disposable implements IInputHandler { * ESC 7 * Save cursor (ANSI.SYS). * - * @vt: partly CSI SCOSC "Save Cursor" "CSI s" "Save cursor position, charmap and text attributes." - * @vt: supported ESC SC "Save Cursor" "ESC 7" "Save cursor position, charmap and text attributes." + * @vt: #P[TODO...] CSI SCOSC "Save Cursor" "CSI s" "Save cursor position, charmap and text attributes." + * @vt: #Y ESC SC "Save Cursor" "ESC 7" "Save cursor position, charmap and text attributes." */ public saveCursor(params?: IParams): void { this._bufferService.buffer.savedX = this._bufferService.buffer.x; @@ -2389,8 +2389,8 @@ export class InputHandler extends Disposable implements IInputHandler { * ESC 8 * Restore cursor (ANSI.SYS). * - * @vt: partly CSI SCORC "Restore Cursor" "CSI u" "Restore cursor position, charmap and text attributes." - * @vt: supported ESC RC "Restore Cursor" "ESC 8" "Restore cursor position, charmap and text attributes." + * @vt: #P[TODO...] CSI SCORC "Restore Cursor" "CSI u" "Restore cursor position, charmap and text attributes." + * @vt: #Y ESC RC "Restore Cursor" "ESC 8" "Restore cursor position, charmap and text attributes." */ public restoreCursor(params?: IParams): void { this._bufferService.buffer.x = this._bufferService.buffer.savedX || 0; @@ -2410,10 +2410,10 @@ export class InputHandler extends Disposable implements IInputHandler { * OSC 2; ST (set window title) * Proxy to set window title. Icon name is not supported. * - * @vt: partly OSC 0 "Set Windows Title and Icon Name" "OSC 0 ; Pt BEL" "Set window title and icon name." + * @vt: #P[Icon name is not exposed.] OSC 0 "Set Windows Title and Icon Name" "OSC 0 ; Pt BEL" "Set window title and icon name." * Icon name is not supported. For Window Title see below. * - * @vt: supported OSC 2 "Set Windows Title" "OSC 2 ; Pt BEL" "Set window title." + * @vt: #Y OSC 2 "Set Windows Title" "OSC 2 ; Pt BEL" "Set window title." * xterm.js does not manipulate the title directly, instead exposes changes via the event `Terminal.onTitleChange`. */ public setTitle(data: string): void { @@ -2426,8 +2426,8 @@ export class InputHandler extends Disposable implements IInputHandler { * DEC mnemonic: NEL (https://vt100.net/docs/vt510-rm/NEL) * Moves cursor to first position on next line. * - * @vt: supported C1 NEL "Next Line" "\x85" "Move the cursor to the beginning of the next row." - * @vt: supported ESC NEL "Next Line" "ESC E" "Move the cursor to the beginning of the next row." + * @vt: #Y C1 NEL "Next Line" "\x85" "Move the cursor to the beginning of the next row." + * @vt: #Y ESC NEL "Next Line" "ESC E" "Move the cursor to the beginning of the next row." */ public nextLine(): void { this._bufferService.buffer.x = 0; @@ -2501,8 +2501,8 @@ export class InputHandler extends Disposable implements IInputHandler { * DEC mnemonic: IND (https://vt100.net/docs/vt510-rm/IND.html) * Moves the cursor down one line in the same column. * - * @vt: supported C1 IND "Index" "\x84" "Move the cursor one line down scrolling if needed." - * @vt: supported ESC IND "Index" "ESC D" "Move the cursor one line down scrolling if needed." + * @vt: #Y C1 IND "Index" "\x84" "Move the cursor one line down scrolling if needed." + * @vt: #Y ESC IND "Index" "ESC D" "Move the cursor one line down scrolling if needed." */ public index(): void { this._restrictCursor(); @@ -2524,8 +2524,8 @@ export class InputHandler extends Disposable implements IInputHandler { * Sets a horizontal tab stop at the column position indicated by * the value of the active column when the terminal receives an HTS. * - * @vt: supported C1 HTS "Horizontal Tabulation Set" "\x88" "Places a tab stop at the current cursor position." - * @vt: supported ESC HTS "Horizontal Tabulation Set" "ESC H" "Places a tab stop at the current cursor position." + * @vt: #Y C1 HTS "Horizontal Tabulation Set" "\x88" "Places a tab stop at the current cursor position." + * @vt: #Y ESC HTS "Horizontal Tabulation Set" "ESC H" "Places a tab stop at the current cursor position." */ public tabSet(): void { this._bufferService.buffer.tabs[this._bufferService.buffer.x] = true; @@ -2538,7 +2538,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Moves the cursor up one line in the same column. If the cursor is at the top margin, * the page scrolls down. * - * @vt: supported ESC IR "Reverse Index" "ESC M" "Move the cursor one line up scrolling if needed." + * @vt: #Y ESC IR "Reverse Index" "ESC M" "Move the cursor one line up scrolling if needed." */ public reverseIndex(): void { this._restrictCursor(); @@ -2601,7 +2601,7 @@ export class InputHandler extends Disposable implements IInputHandler { * This control function fills the complete screen area with * a test pattern (E) used for adjusting screen alignment. * - * @vt: supported ESC DECALN "Screen Alignment Pattern" "ESC # 8" "Fill viewport with a test pattern (E)." + * @vt: #Y ESC DECALN "Screen Alignment Pattern" "ESC # 8" "Fill viewport with a test pattern (E)." */ public screenAlignmentPattern(): void { // prepare cell data From d2145368fa890261cf166a47a66f7aeb28c66c27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 10 Jan 2020 18:51:27 +0100 Subject: [PATCH 244/268] several template updates --- bin/extract_vtfeatures.js | 110 +++++++++++++++++++++++++++++++++++--- 1 file changed, 102 insertions(+), 8 deletions(-) diff --git a/bin/extract_vtfeatures.js b/bin/extract_vtfeatures.js index 51630a62c..7b77639d9 100644 --- a/bin/extract_vtfeatures.js +++ b/bin/extract_vtfeatures.js @@ -33,10 +33,14 @@ const TYPES = [ ]; const MARKDOWN_TMPL = ` +{::options parse_block_html="true" /} + xterm.js version: {{version}} ## Table of Contents + + + ## General notes This document lists xterm.js' support of terminal sequences. The sequences are grouped by their sequence type: @@ -82,22 +89,27 @@ To denote the sequences the tables use the same abbreviations as xterm does: ASCII printables are specified to work. Additionally the parser will let pass any codepoint greater than C1 as printable. + {{#C0.length}} ## C0 | Mnemonic | Name | Sequence | Short Description | Support | | -------- | ---- | -------- | ----------------- | ------- | {{#C0}} -| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}})_{{/longDescription.length}} | {{{status}}} | +| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}}){: .link-details}_{{/longDescription.length}} | {{{status}}} | {{/C0}} {{#C0.hasLongDescriptions}} {{#C0}} {{#longDescription.length}} +
+ ### {{name}} {{#longDescription}} {{{.}}} {{/longDescription}} + +
{{/longDescription.length}} {{/C0}} {{/C0.hasLongDescriptions}} @@ -111,16 +123,20 @@ To denote the sequences the tables use the same abbreviations as xterm does: | Mnemonic | Name | Sequence | Short Description | Support | | -------- | ---- | -------- | ----------------- | ------- | {{#C1}} -| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}})_{{/longDescription.length}} | {{{status}}} | +| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}}){: .link-details}_{{/longDescription.length}} | {{{status}}} | {{/C1}} {{#C1.hasLongDescriptions}} {{#C1}} {{#longDescription.length}} +
+ ### {{name}} {{#longDescription}} {{{.}}} {{/longDescription}} + +
{{/longDescription.length}} {{/C1}} {{/C1.hasLongDescriptions}} @@ -134,16 +150,20 @@ To denote the sequences the tables use the same abbreviations as xterm does: | Mnemonic | Name | Sequence | Short Description | Support | | -------- | ---- | -------- | ----------------- | ------- | {{#CSI}} -| {{mnemonic}} | {{name}} | \`\`{{{sequence}}}\`\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}})_{{/longDescription.length}} | {{{status}}} | +| {{mnemonic}} | {{name}} | \`\`{{{sequence}}}\`\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}}){: .link-details}_{{/longDescription.length}} | {{{status}}} | {{/CSI}} {{#CSI.hasLongDescriptions}} {{#CSI}} {{#longDescription.length}} +
+ ### {{name}} {{#longDescription}} {{{.}}} {{/longDescription}} + +
{{/longDescription.length}} {{/CSI}} {{/CSI.hasLongDescriptions}} @@ -157,16 +177,20 @@ To denote the sequences the tables use the same abbreviations as xterm does: | Mnemonic | Name | Sequence | Short Description | Support | | -------- | ---- | -------- | ----------------- | ------- | {{#DCS}} -| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}})_{{/longDescription.length}} | {{{status}}} | +| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}}){: .link-details}_{{/longDescription.length}} | {{{status}}} | {{/DCS}} {{#DCS.hasLongDescriptions}} {{#DCS}} {{#longDescription.length}} +
+ ### {{name}} {{#longDescription}} {{{.}}} {{/longDescription}} + +
{{/longDescription.length}} {{/DCS}} {{/DCS.hasLongDescriptions}} @@ -180,16 +204,20 @@ To denote the sequences the tables use the same abbreviations as xterm does: | Mnemonic | Name | Sequence | Short Description | Support | | -------- | ---- | -------- | ----------------- | ------- | {{#ESC}} -| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}})_{{/longDescription.length}} | {{{status}}} | +| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}}){: .link-details}_{{/longDescription.length}} | {{{status}}} | {{/ESC}} {{#ESC.hasLongDescriptions}} {{#ESC}} {{#longDescription.length}} +
+ ### {{name}} {{#longDescription}} {{{.}}} {{/longDescription}} + +
{{/longDescription.length}} {{/ESC}} {{/ESC.hasLongDescriptions}} @@ -205,21 +233,87 @@ To denote the sequences the tables use the same abbreviations as xterm does: | Identifier | Sequence | Short Description | Support | | ---------- | -------- | ----------------- | ------- | {{#OSC}} -| {{mnemonic}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}})_{{/longDescription.length}} | {{{status}}} | +| {{mnemonic}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}}){: .link-details}_{{/longDescription.length}} | {{{status}}} | {{/OSC}} {{#OSC.hasLongDescriptions}} {{#OSC}} {{#longDescription.length}} +
+ ### {{name}} {{#longDescription}} {{{.}}} {{/longDescription}} + +
{{/longDescription.length}} {{/OSC}} {{/OSC.hasLongDescriptions}} {{/OSC.length}} + + ` // support status marcos @@ -232,9 +326,9 @@ const MACRO = [ // #N - unsupported [/#N/g, s => '✗'], // #P[reason] - partial support with a reason as title - [/#P\[(.*?)\]/g, (s, p1) => `Partial`], + [/#P\[(.*?)\]/g, (s, p1) => `Partial`], // #B[reason] - supported but broken in a certain way, reason in title - [/#B\[(.*?)\]/g, (s, p1) => `Broken`] + [/#B\[(.*?)\]/g, (s, p1) => `Broken`] ]; function applyMacros(s) { From af67b724923b04a47f3a506e7d0d388886915676 Mon Sep 17 00:00:00 2001 From: kumaran-14 Date: Wed, 15 Jan 2020 21:32:04 +0530 Subject: [PATCH 245/268] Fix unit tests for MoveToCellSequence --- src/browser/input/MoveToCell.test.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/browser/input/MoveToCell.test.ts b/src/browser/input/MoveToCell.test.ts index bc4012c04..b78748ba1 100644 --- a/src/browser/input/MoveToCell.test.ts +++ b/src/browser/input/MoveToCell.test.ts @@ -23,15 +23,15 @@ describe('MoveToCell', () => { assert.equal(moveToCellSequence(4, 3, bufferService, false), '\x1b[C'); }); it('should ignore the Y value', () => { - assert.equal(moveToCellSequence(1, 1, bufferService, false), '\x1b[D\x1b[D'); - assert.equal(moveToCellSequence(1, 2, bufferService, false), '\x1b[D\x1b[D'); - assert.equal(moveToCellSequence(1, 3, bufferService, false), '\x1b[D\x1b[D'); - assert.equal(moveToCellSequence(1, 4, bufferService, false), '\x1b[D\x1b[D'); - assert.equal(moveToCellSequence(1, 5, bufferService, false), '\x1b[D\x1b[D'); + assert.equal(moveToCellSequence(1, 1, bufferService, false), '\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D'); + assert.equal(moveToCellSequence(1, 2, bufferService, false), '\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D'); + assert.equal(moveToCellSequence(1, 3, bufferService, false), '\u001b[D\u001b[D'); + assert.equal(moveToCellSequence(1, 4, bufferService, false), '\u001b[C\u001b[C\u001b[C'); + assert.equal(moveToCellSequence(1, 5, bufferService, false), '\u001b[C\u001b[C\u001b[C'); }); it('should use the correct character for application cursor', () => { - assert.equal(moveToCellSequence(2, 1, bufferService, false), '\x1b[D'); - assert.equal(moveToCellSequence(2, 1, bufferService, true), '\x1bOD'); + assert.equal(moveToCellSequence(2, 1, bufferService, false), '\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D'); + assert.equal(moveToCellSequence(2, 1, bufferService, true), '\u001bOD\u001bOD\u001bOD\u001bOD\u001bOD\u001bOD'); }); }); From 6240a5d7a4d2a01cbc4746024810873c76a54cb9 Mon Sep 17 00:00:00 2001 From: Jeff Baier Date: Wed, 22 Jan 2020 14:16:39 -0600 Subject: [PATCH 246/268] Add _terminalClass identifier to cursor blink animation css so that multiple DOM rendered instances no longer lose the cursor theme color Fixes #2674 --- src/browser/renderer/dom/DomRenderer.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 5fbcfdc9c..e8679026b 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -173,13 +173,13 @@ export class DomRenderer extends Disposable implements IRenderer { `}`; // Blink animation styles += - `@keyframes blink_box_shadow {` + + `@keyframes blink_box_shadow` + `_` + this._terminalClass + ` {` + ` 50% {` + ` box-shadow: none;` + ` }` + `}`; styles += - `@keyframes blink_block {` + + `@keyframes blink_block` + `_` + this._terminalClass + ` {` + ` 0% {` + ` background-color: ${this._colors.cursor.css};` + ` color: ${this._colors.cursorAccent.css};` + @@ -196,10 +196,10 @@ export class DomRenderer extends Disposable implements IRenderer { ` outline-offset: -1px;` + `}` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_BLINK_CLASS}:not(.${CURSOR_STYLE_BLOCK_CLASS}) {` + - ` animation: blink_box_shadow 1s step-end infinite;` + + ` animation: blink_box_shadow` + `_` + this._terminalClass + ` 1s step-end infinite;` + `}` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_BLINK_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` + - ` animation: blink_block 1s step-end infinite;` + + ` animation: blink_block` + `_` + this._terminalClass + ` 1s step-end infinite;` + `}` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` + ` background-color: ${this._colors.cursor.css};` + From 49bc27e24f7f13e45d387247bec0b7a2c84dfb19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 2 Feb 2020 13:07:11 +0100 Subject: [PATCH 247/268] fixing visual repr chars --- src/common/data/Charsets.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/common/data/Charsets.ts b/src/common/data/Charsets.ts index 56ca67991..56513b8ad 100644 --- a/src/common/data/Charsets.ts +++ b/src/common/data/Charsets.ts @@ -30,14 +30,14 @@ export const DEFAULT_CHARSET: ICharset | null = CHARSETS['B']; CHARSETS['0'] = { '`': '\u25c6', // '◆' 'a': '\u2592', // '▒' - 'b': '\u0009', // '\t' - 'c': '\u000c', // '\f' - 'd': '\u000d', // '\r' - 'e': '\u000a', // '\n' + 'b': '\u2409', // '␉' (HT) + 'c': '\u240c', // '␌' (FF) + 'd': '\u240d', // '␍' (CR) + 'e': '\u240a', // '␊' (LF) 'f': '\u00b0', // '°' 'g': '\u00b1', // '±' - 'h': '\u2424', // '\u2424' (NL) - 'i': '\u000b', // '\v' + 'h': '\u2424', // '␤' (NL) + 'i': '\u240b', // '␋' (VT) 'j': '\u2518', // '┘' 'k': '\u2510', // '┐' 'l': '\u250c', // '┌' From d5bf8966d9e6dad268346116d7878da58f9243b2 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 2 Feb 2020 05:10:57 -0800 Subject: [PATCH 248/268] Remove ; and : from default wordSeparator Fixes #2665 --- src/common/services/OptionsService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 7cd111ac0..b855974a1 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -52,7 +52,7 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({ screenKeys: false, cancelEvents: false, useFlowControl: false, - wordSeparator: ' ()[]{}\',:;"`' + wordSeparator: ' ()[]{}\',"`' }); /** From b591ccc40dcf9226da43ae63b30db7ec7b6cf370 Mon Sep 17 00:00:00 2001 From: kumaran-14 Date: Sun, 2 Feb 2020 23:28:24 +0530 Subject: [PATCH 249/268] Fix moveToCell when target is beyond buffer --- src/browser/input/MoveToCell.test.ts | 4 ++-- src/browser/input/MoveToCell.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/browser/input/MoveToCell.test.ts b/src/browser/input/MoveToCell.test.ts index b78748ba1..735697d95 100644 --- a/src/browser/input/MoveToCell.test.ts +++ b/src/browser/input/MoveToCell.test.ts @@ -23,14 +23,14 @@ describe('MoveToCell', () => { assert.equal(moveToCellSequence(4, 3, bufferService, false), '\x1b[C'); }); it('should ignore the Y value', () => { - assert.equal(moveToCellSequence(1, 1, bufferService, false), '\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D'); + assert.equal(moveToCellSequence(1, 1, bufferService, false), '\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D'); assert.equal(moveToCellSequence(1, 2, bufferService, false), '\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D'); assert.equal(moveToCellSequence(1, 3, bufferService, false), '\u001b[D\u001b[D'); assert.equal(moveToCellSequence(1, 4, bufferService, false), '\u001b[C\u001b[C\u001b[C'); assert.equal(moveToCellSequence(1, 5, bufferService, false), '\u001b[C\u001b[C\u001b[C'); }); it('should use the correct character for application cursor', () => { - assert.equal(moveToCellSequence(2, 1, bufferService, false), '\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D'); + assert.equal(moveToCellSequence(2, 1, bufferService, false), '\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D'); assert.equal(moveToCellSequence(2, 1, bufferService, true), '\u001bOD\u001bOD\u001bOD\u001bOD\u001bOD\u001bOD'); }); }); diff --git a/src/browser/input/MoveToCell.ts b/src/browser/input/MoveToCell.ts index 9b4932882..e3b2555ac 100644 --- a/src/browser/input/MoveToCell.ts +++ b/src/browser/input/MoveToCell.ts @@ -37,7 +37,7 @@ export function moveToCellSequence(targetX: number, targetY: number, bufferServi } direction = startY > targetY ? Direction.LEFT : Direction.RIGHT; return repeat(colsFromRowEnd(startY > targetY ? targetX : startX, bufferService), sequence(direction, applicationCursor)) - + repeat((Math.abs(wrappedRowsForRow(bufferService, startY) - wrappedRowsForRow(bufferService, targetY)) - 1 ) * bufferService.cols, sequence(direction, applicationCursor)) + + repeat((Math.abs(startY - targetY) - 1 ) * bufferService.cols, sequence(direction, applicationCursor)) + repeat(colsFromRowBeginning(startY > targetY ? startX : targetX, bufferService), sequence(direction, applicationCursor)); } From 7d84083f557e1cfd6ac04cc9c8c510afb3fdf036 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 3 Feb 2020 06:43:20 -0800 Subject: [PATCH 250/268] Fix algorithm to move the right amount of cells --- src/browser/input/MoveToCell.test.ts | 24 ++++++++++++++++-------- src/browser/input/MoveToCell.ts | 14 ++++++++------ 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/browser/input/MoveToCell.test.ts b/src/browser/input/MoveToCell.test.ts index 735697d95..5753ea46e 100644 --- a/src/browser/input/MoveToCell.test.ts +++ b/src/browser/input/MoveToCell.test.ts @@ -15,6 +15,9 @@ describe('MoveToCell', () => { bufferService = new MockBufferService(5, 5); bufferService.buffer.x = 3; bufferService.buffer.y = 3; + // Lines 2-4 are wrapped + bufferService.buffer.lines.get(3)!.isWrapped = true; + bufferService.buffer.lines.get(4)!.isWrapped = true; }); describe('normal buffer', () => { @@ -22,16 +25,21 @@ describe('MoveToCell', () => { assert.equal(moveToCellSequence(2, 3, bufferService, false), '\x1b[D'); assert.equal(moveToCellSequence(4, 3, bufferService, false), '\x1b[C'); }); - it('should ignore the Y value', () => { - assert.equal(moveToCellSequence(1, 1, bufferService, false), '\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D'); - assert.equal(moveToCellSequence(1, 2, bufferService, false), '\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D'); - assert.equal(moveToCellSequence(1, 3, bufferService, false), '\u001b[D\u001b[D'); - assert.equal(moveToCellSequence(1, 4, bufferService, false), '\u001b[C\u001b[C\u001b[C'); - assert.equal(moveToCellSequence(1, 5, bufferService, false), '\u001b[C\u001b[C\u001b[C'); + it('should wrap around entire row instead of doing up and down when the Y value differs', () => { + assert.equal(moveToCellSequence(2, 2, bufferService, false), '\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D'); + assert.equal(moveToCellSequence(4, 4, bufferService, false), '\x1b[C\x1b[C\x1b[C'); + }); + it('should wrap across 2+ rows', () => { + bufferService.buffer.y = 2; + assert.equal(moveToCellSequence(3, 4, bufferService, false), '\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D', '(3,2) to (3,4) = 10 cells'); + }); + it('should not move the cursor beyond the current wrapped row', () => { + assert.equal(moveToCellSequence(1, 1, bufferService, false), '\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D'); + assert.equal(moveToCellSequence(1, 5, bufferService, false), '\x1b[C\x1b[C\x1b[C'); }); it('should use the correct character for application cursor', () => { - assert.equal(moveToCellSequence(2, 1, bufferService, false), '\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D'); - assert.equal(moveToCellSequence(2, 1, bufferService, true), '\u001bOD\u001bOD\u001bOD\u001bOD\u001bOD\u001bOD'); + assert.equal(moveToCellSequence(2, 1, bufferService, false), '\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D'); + assert.equal(moveToCellSequence(2, 1, bufferService, true), '\x1bOD\x1bOD\x1bOD\x1bOD\x1bOD\x1bOD'); }); }); diff --git a/src/browser/input/MoveToCell.ts b/src/browser/input/MoveToCell.ts index e3b2555ac..1113f957e 100644 --- a/src/browser/input/MoveToCell.ts +++ b/src/browser/input/MoveToCell.ts @@ -35,24 +35,26 @@ export function moveToCellSequence(targetX: number, targetY: number, bufferServi direction = startX > targetX ? Direction.LEFT : Direction.RIGHT; return repeat(Math.abs(startX - targetX), sequence(direction, applicationCursor)); } - direction = startY > targetY ? Direction.LEFT : Direction.RIGHT; - return repeat(colsFromRowEnd(startY > targetY ? targetX : startX, bufferService), sequence(direction, applicationCursor)) - + repeat((Math.abs(startY - targetY) - 1 ) * bufferService.cols, sequence(direction, applicationCursor)) - + repeat(colsFromRowBeginning(startY > targetY ? startX : targetX, bufferService), sequence(direction, applicationCursor)); + direction = startY > targetY ? Direction.LEFT : Direction.RIGHT; + const rowDifference = Math.abs(startY - targetY); + const cellsToMove = colsFromRowEnd(startY > targetY ? targetX : startX, bufferService) + + (rowDifference - 1) * bufferService.cols + 1/*wrap around 1 row*/ + + colsFromRowBeginning(startY > targetY ? startX : targetX, bufferService); + return repeat(cellsToMove, sequence(direction, applicationCursor)); } /** * Find the number of cols from a row beginning to a col. */ function colsFromRowBeginning(currX: number, bufferService: IBufferService): number { - return Math.abs(currX); + return currX - 1; } /** * Find the number of cols from a col to row end. */ function colsFromRowEnd(currX: number, bufferService: IBufferService): number { - return Math.abs(bufferService.cols - currX); + return bufferService.cols - currX; } /** From bb944f190e5f8e388128b655ece302bcbb69fc0b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 3 Feb 2020 06:54:35 -0800 Subject: [PATCH 251/268] Fix and add more MoveToCell tests --- src/browser/input/MoveToCell.test.ts | 41 ++++++++++++++++++---------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/src/browser/input/MoveToCell.test.ts b/src/browser/input/MoveToCell.test.ts index 5753ea46e..61e5f90b9 100644 --- a/src/browser/input/MoveToCell.test.ts +++ b/src/browser/input/MoveToCell.test.ts @@ -15,31 +15,44 @@ describe('MoveToCell', () => { bufferService = new MockBufferService(5, 5); bufferService.buffer.x = 3; bufferService.buffer.y = 3; - // Lines 2-4 are wrapped - bufferService.buffer.lines.get(3)!.isWrapped = true; - bufferService.buffer.lines.get(4)!.isWrapped = true; }); describe('normal buffer', () => { it('should use the right directional escape sequences', () => { + assert.equal(moveToCellSequence(1, 3, bufferService, false), '\x1b[D\x1b[D'); assert.equal(moveToCellSequence(2, 3, bufferService, false), '\x1b[D'); assert.equal(moveToCellSequence(4, 3, bufferService, false), '\x1b[C'); + assert.equal(moveToCellSequence(5, 3, bufferService, false), '\x1b[C\x1b[C'); }); it('should wrap around entire row instead of doing up and down when the Y value differs', () => { - assert.equal(moveToCellSequence(2, 2, bufferService, false), '\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D'); - assert.equal(moveToCellSequence(4, 4, bufferService, false), '\x1b[C\x1b[C\x1b[C'); - }); - it('should wrap across 2+ rows', () => { - bufferService.buffer.y = 2; - assert.equal(moveToCellSequence(3, 4, bufferService, false), '\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D', '(3,2) to (3,4) = 10 cells'); - }); - it('should not move the cursor beyond the current wrapped row', () => { assert.equal(moveToCellSequence(1, 1, bufferService, false), '\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D'); - assert.equal(moveToCellSequence(1, 5, bufferService, false), '\x1b[C\x1b[C\x1b[C'); + assert.equal(moveToCellSequence(2, 1, bufferService, false), '\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D'); + assert.equal(moveToCellSequence(3, 1, bufferService, false), '\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D'); + assert.equal(moveToCellSequence(4, 1, bufferService, false), '\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D'); + assert.equal(moveToCellSequence(5, 1, bufferService, false), '\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D'); + assert.equal(moveToCellSequence(1, 2, bufferService, false), '\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D'); + assert.equal(moveToCellSequence(2, 2, bufferService, false), '\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D'); + assert.equal(moveToCellSequence(3, 2, bufferService, false), '\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D'); + assert.equal(moveToCellSequence(4, 2, bufferService, false), '\x1b[D\x1b[D\x1b[D\x1b[D'); + assert.equal(moveToCellSequence(5, 2, bufferService, false), '\x1b[D\x1b[D\x1b[D'); + assert.equal(moveToCellSequence(1, 4, bufferService, false), '\x1b[C\x1b[C\x1b[C'); + assert.equal(moveToCellSequence(2, 4, bufferService, false), '\x1b[C\x1b[C\x1b[C\x1b[C'); + assert.equal(moveToCellSequence(3, 4, bufferService, false), '\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C'); + assert.equal(moveToCellSequence(4, 4, bufferService, false), '\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C'); + assert.equal(moveToCellSequence(5, 4, bufferService, false), '\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C'); + assert.equal(moveToCellSequence(1, 5, bufferService, false), '\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C'); + assert.equal(moveToCellSequence(2, 5, bufferService, false), '\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C'); + assert.equal(moveToCellSequence(3, 5, bufferService, false), '\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C'); + assert.equal(moveToCellSequence(4, 5, bufferService, false), '\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C'); + assert.equal(moveToCellSequence(5, 5, bufferService, false), '\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C'); }); it('should use the correct character for application cursor', () => { - assert.equal(moveToCellSequence(2, 1, bufferService, false), '\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D'); - assert.equal(moveToCellSequence(2, 1, bufferService, true), '\x1bOD\x1bOD\x1bOD\x1bOD\x1bOD\x1bOD'); + assert.equal(moveToCellSequence(3, 1, bufferService, true), '\x1bOD\x1bOD\x1bOD\x1bOD\x1bOD\x1bOD\x1bOD\x1bOD\x1bOD\x1bOD'); + assert.equal(moveToCellSequence(3, 2, bufferService, true), '\x1bOD\x1bOD\x1bOD\x1bOD\x1bOD'); + assert.equal(moveToCellSequence(2, 3, bufferService, true), '\x1bOD'); + assert.equal(moveToCellSequence(4, 3, bufferService, true), '\x1bOC'); + assert.equal(moveToCellSequence(3, 4, bufferService, true), '\x1bOC\x1bOC\x1bOC\x1bOC\x1bOC'); + assert.equal(moveToCellSequence(3, 5, bufferService, true), '\x1bOC\x1bOC\x1bOC\x1bOC\x1bOC\x1bOC\x1bOC\x1bOC\x1bOC\x1bOC'); }); }); From 3d53847f30be8feb3b3ea5a8c504ec2e7181ae4a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 3 Feb 2020 07:00:06 -0800 Subject: [PATCH 252/268] Return exit code in Linux unit tests Fixes #2681 --- azure-pipelines.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f6f892c7e..e641e716b 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -27,7 +27,9 @@ jobs: displayName: 'Install dependencies and build' - script: | yarn test-unit-coverage --forbid-only + EXIT_CODE=$? ./node_modules/.bin/nyc report --reporter=cobertura + exit $EXIT_CODE displayName: 'Unit tests' - script: yarn lint displayName: 'Lint' From 8093a5f4191cc58475dc13ad547fae9d02bf30f9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 3 Feb 2020 07:02:14 -0800 Subject: [PATCH 253/268] Fix lint --- src/browser/input/MoveToCell.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/input/MoveToCell.ts b/src/browser/input/MoveToCell.ts index 1113f957e..4a7b55aaa 100644 --- a/src/browser/input/MoveToCell.ts +++ b/src/browser/input/MoveToCell.ts @@ -38,7 +38,7 @@ export function moveToCellSequence(targetX: number, targetY: number, bufferServi direction = startY > targetY ? Direction.LEFT : Direction.RIGHT; const rowDifference = Math.abs(startY - targetY); const cellsToMove = colsFromRowEnd(startY > targetY ? targetX : startX, bufferService) + - (rowDifference - 1) * bufferService.cols + 1/*wrap around 1 row*/ + + (rowDifference - 1) * bufferService.cols + 1 /*wrap around 1 row*/ + colsFromRowBeginning(startY > targetY ? startX : targetX, bufferService); return repeat(cellsToMove, sequence(direction, applicationCursor)); } From 412d6fe42cac9843cc988f8110ed487df1fc8f58 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 3 Feb 2020 10:13:13 -0800 Subject: [PATCH 254/268] Refresh cursor row when cursor settings updated Fixes #2683 --- src/Terminal.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Terminal.ts b/src/Terminal.ts index 7d0def9bd..f6c9351ba 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -308,6 +308,11 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._renderService?.clear(); this._charSizeService?.measure(); break; + case 'cursorBlink': + case 'cursorStyle': + // The DOM renderer needs a row refresh to update the cursor styles + this.refresh(this.buffer.y, this.buffer.y); + break; case 'drawBoldTextInBrightColors': case 'letterSpacing': case 'lineHeight': From 4a567c9f5ae080b401c9e1cf0d1fa9077df6f8ef Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 3 Feb 2020 10:56:46 -0800 Subject: [PATCH 255/268] Remove old unused options from codebase Fixes #2675 --- demo/client.ts | 3 --- src/Types.d.ts | 3 --- src/common/services/OptionsService.ts | 2 -- src/common/services/Services.ts | 2 -- src/public/Terminal.ts | 8 ++------ typings/xterm.d.ts | 28 +++------------------------ 6 files changed, 5 insertions(+), 41 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 2a0339eb8..c07d00817 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -248,10 +248,7 @@ function initOptions(term: TerminalType): void { // Internal only options 'cancelEvents', 'convertEol', - 'handler', - 'screenKeys', 'termName', - 'useFlowControl', // Complex option 'theme' ]; diff --git a/src/Types.d.ts b/src/Types.d.ts index 0b8750298..0f3a7751f 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -220,10 +220,7 @@ export interface ITerminalOptions extends IPublicTerminalOptions { [key: string]: any; cancelEvents?: boolean; convertEol?: boolean; - handler?: (data: string) => void; - screenKeys?: boolean; termName?: string; - useFlowControl?: boolean; } export interface IBrowser { diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index b855974a1..4e0b5e8b7 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -49,9 +49,7 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({ convertEol: false, termName: 'xterm', - screenKeys: false, cancelEvents: false, - useFlowControl: false, wordSeparator: ' ()[]{}\',"`' }); diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index ce7b33f77..fd11b9276 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -278,9 +278,7 @@ export interface ITerminalOptions { [key: string]: any; cancelEvents: boolean; convertEol: boolean; - screenKeys: boolean; termName: string; - useFlowControl: boolean; } export interface ITheme { diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 4f70e3474..c96af6989 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -146,10 +146,8 @@ export class Terminal implements ITerminalApi { this._core.paste(data); } public getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'fontWeight' | 'fontWeightBold' | 'logLevel' | 'rendererType' | 'termName' | 'wordSeparator'): string; - public getOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell'): boolean; - public getOption(key: 'colors'): string[]; + public getOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'visualBell'): boolean; public getOption(key: 'cols' | 'fontSize' | 'letterSpacing' | 'lineHeight' | 'rows' | 'tabStopWidth' | 'scrollback'): number; - public getOption(key: 'handler'): (data: string) => void; public getOption(key: string): any; public getOption(key: any): any { return this._core.optionsService.getOption(key); @@ -159,10 +157,8 @@ export class Terminal implements ITerminalApi { public setOption(key: 'logLevel', value: 'debug' | 'info' | 'warn' | 'error' | 'off'): void; public setOption(key: 'bellStyle', value: 'none' | 'visual' | 'sound' | 'both'): void; public setOption(key: 'cursorStyle', value: 'block' | 'underline' | 'bar'): void; - public setOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell', value: boolean): void; - public setOption(key: 'colors', value: string[]): void; + public setOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'visualBell', value: boolean): void; public setOption(key: 'fontSize' | 'letterSpacing' | 'lineHeight' | 'tabStopWidth' | 'scrollback', value: number): void; - public setOption(key: 'handler', value: (data: string) => void): void; public setOption(key: 'theme', value: ITheme): void; public setOption(key: 'cols' | 'rows', value: number): void; public setOption(key: string, value: any): void; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index d0479897c..99c1440d6 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -748,22 +748,12 @@ declare module 'xterm' { * Retrieves an option's value from the terminal. * @param key The option key. */ - getOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell' | 'windowsMode'): boolean; - /** - * Retrieves an option's value from the terminal. - * @param key The option key. - */ - getOption(key: 'colors'): string[]; + getOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'visualBell' | 'windowsMode'): boolean; /** * Retrieves an option's value from the terminal. * @param key The option key. */ getOption(key: 'cols' | 'fontSize' | 'letterSpacing' | 'lineHeight' | 'rows' | 'tabStopWidth' | 'scrollback'): number; - /** - * Retrieves an option's value from the terminal. - * @param key The option key. - */ - getOption(key: 'handler'): (data: string) => void; /** * Retrieves an option's value from the terminal. * @param key The option key. @@ -805,25 +795,13 @@ declare module 'xterm' { * @param key The option key. * @param value The option value. */ - setOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'popOnBell' | 'rightClickSelectsWord' | 'screenKeys' | 'useFlowControl' | 'visualBell' | 'windowsMode', value: boolean): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - */ - setOption(key: 'colors', value: string[]): void; + setOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'popOnBell' | 'rightClickSelectsWord' | 'visualBell' | 'windowsMode', value: boolean): void; /** * Sets an option on the terminal. * @param key The option key. * @param value The option value. */ setOption(key: 'fontSize' | 'letterSpacing' | 'lineHeight' | 'tabStopWidth' | 'scrollback', value: number): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - */ - setOption(key: 'handler', value: (data: string) => void): void; /** * Sets an option on the terminal. * @param key The option key. @@ -1171,7 +1149,7 @@ declare module 'xterm' { * String indicating the Unicode version provided. */ readonly version: string; - + /** * Unicode version dependent wcwidth implementation. */ From c512e48c5261441698a3da011d85154e813a7818 Mon Sep 17 00:00:00 2001 From: javacs3 Date: Tue, 4 Feb 2020 14:46:03 +0800 Subject: [PATCH 256/268] 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 39f0bfe72..a2352f40c 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 40deecf20..cdfab61f3 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 a980b3d1323661f8d678bfa5c458ec4773712eed Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 4 Feb 2020 06:43:17 -0800 Subject: [PATCH 257/268] Handle links safely by removing opener from window Fixes #2608 --- addons/xterm-addon-web-links/src/WebLinksAddon.ts | 8 +++++++- src/browser/Linkifier.ts | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/addons/xterm-addon-web-links/src/WebLinksAddon.ts b/addons/xterm-addon-web-links/src/WebLinksAddon.ts index 26d5904b9..3290a59cf 100644 --- a/addons/xterm-addon-web-links/src/WebLinksAddon.ts +++ b/addons/xterm-addon-web-links/src/WebLinksAddon.ts @@ -26,7 +26,13 @@ const end = ')($|' + negatedPathCharacterSet + ')'; const strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end); function handleLink(event: MouseEvent, uri: string): void { - window.open(uri, '_blank'); + const newWindow = window.open(); + if (newWindow) { + newWindow.opener = null; + newWindow.location.href = uri; + } else { + console.warn('Opening link blocked as opener could not be cleared'); + } } export class WebLinksAddon implements ITerminalAddon { diff --git a/src/browser/Linkifier.ts b/src/browser/Linkifier.ts index 30c76e8a3..971501b70 100644 --- a/src/browser/Linkifier.ts +++ b/src/browser/Linkifier.ts @@ -298,7 +298,13 @@ export class Linkifier implements ILinkifier { if (matcher.handler) { return matcher.handler(e, uri); } - window.open(uri, '_blank'); + const newWindow = window.open(); + if (newWindow) { + newWindow.opener = null; + newWindow.location.href = uri; + } else { + console.warn('Opening link blocked as opener could not be cleared'); + } }, () => { this._onLinkHover.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg)); From b7ae55a07b3347e703c2ec8862c76126e4a376a7 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 4 Feb 2020 06:46:25 -0800 Subject: [PATCH 258/268] Make parser stable, remove deprecated APIs Fixes #2607 --- typings/xterm.d.ts | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 99c1440d6..b8caa0658 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -1008,7 +1008,7 @@ declare module 'xterm' { } /** - * (EXPERIMENTAL) Data type to register a CSI, DCS or ESC callback in the parser + * Data type to register a CSI, DCS or ESC callback in the parser * in the form: * ESC I..I F * CSI Prefix P..P I..I F @@ -1052,7 +1052,7 @@ declare module 'xterm' { } /** - * (EXPERIMENTAL) Parser interface. + * Allows hooking into the parser for custom handling of escape sequences. */ export interface IParser { /** @@ -1069,11 +1069,6 @@ declare module 'xterm' { */ registerCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean): IDisposable; - /** - * @deprecated use `registerMarker` instead. - */ - addCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean): IDisposable; - /** * Adds a handler for DCS escape sequences. * @param id Specifies the function identifier under which the callback @@ -1093,11 +1088,6 @@ declare module 'xterm' { */ registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean): IDisposable; - /** - * @deprecated use `registerMarker` instead. - */ - addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean): IDisposable; - /** * Adds a handler for ESC escape sequences. * @param id Specifies the function identifier under which the callback @@ -1111,11 +1101,6 @@ declare module 'xterm' { */ registerEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable; - /** - * @deprecated use `registerMarker` instead. - */ - addEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable; - /** * Adds a handler for OSC escape sequences. * @param ident The number (first parameter) of the sequence. @@ -1133,11 +1118,6 @@ declare module 'xterm' { * @return An IDisposable you can call to remove this handler. */ registerOscHandler(ident: number, callback: (data: string) => boolean): IDisposable; - - /** - * @deprecated use `registerMarker` instead. - */ - addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable; } /** From 489d8110c97a6315a22e55d616369f56e7efce57 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 4 Feb 2020 06:53:17 -0800 Subject: [PATCH 259/268] Move windowOptions into public API part --- src/common/services/OptionsService.ts | 6 +++--- src/common/services/Services.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 94462ec9e..44a55d33d 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -45,13 +45,13 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({ theme: {}, rightClickSelectsWord: isMac, rendererType: 'canvas', + windowOptions: {}, windowsMode: false, + wordSeparator: ' ()[]{}\',"`', convertEol: false, termName: 'xterm', - cancelEvents: false, - windowOptions: {}, - wordSeparator: ' ()[]{}\',"`' + cancelEvents: false }); /** diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 74d264d7f..abc536a9c 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -274,13 +274,13 @@ export interface ITerminalOptions { tabStopWidth: number; theme: ITheme; windowsMode: boolean; + windowOptions: IWindowOptions; wordSeparator: string; [key: string]: any; cancelEvents: boolean; convertEol: boolean; termName: string; - windowOptions: IWindowOptions; } export interface ITheme { From 3d2d35f5b66723a968b8eb76631b8a2e1f322ed0 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 4 Feb 2020 07:01:25 -0800 Subject: [PATCH 260/268] Pull render service off instantiation service safely --- src/InputHandler.test.ts | 18 +++++++++--------- src/InputHandler.ts | 6 ++++-- src/Terminal.ts | 2 +- src/common/services/InstantiationService.ts | 6 +++++- src/common/services/Services.ts | 1 + 5 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 7bfbaf3b1..de43022b1 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -44,7 +44,7 @@ describe('InputHandler', () => { bufferService.buffer.x = 1; bufferService.buffer.y = 2; bufferService.buffer.ybase = 0; - const inputHandler = new TestInputHandler(terminal, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); + const inputHandler = new TestInputHandler(terminal, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); inputHandler.curAttrData.fg = 3; // Save cursor position inputHandler.saveCursor(); @@ -64,7 +64,7 @@ describe('InputHandler', () => { describe('setCursorStyle', () => { it('should call Terminal.setOption with correct params', () => { const optionsService = new MockOptionsService(); - const inputHandler = new InputHandler(new MockInputHandlingTerminal(), new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), optionsService, new MockCoreMouseService(), new MockUnicodeService()); + const inputHandler = new InputHandler(new MockInputHandlingTerminal(), new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), optionsService, new MockCoreMouseService(), new MockUnicodeService(), {} as any); inputHandler.setCursorStyle(Params.fromArray([0])); assert.equal(optionsService.options['cursorStyle'], 'block'); @@ -105,7 +105,7 @@ describe('InputHandler', () => { it('should toggle Terminal.bracketedPasteMode', () => { const terminal = new MockInputHandlingTerminal(); terminal.bracketedPasteMode = false; - const inputHandler = new InputHandler(terminal, new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); + const inputHandler = new InputHandler(terminal, new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); // Set bracketed paste mode inputHandler.setModePrivate(Params.fromArray([2004])); assert.equal(terminal.bracketedPasteMode, true); @@ -124,7 +124,7 @@ describe('InputHandler', () => { it('insertChars', function(): void { const term = new Terminal(); const bufferService = new MockBufferService(80, 30); - const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); + const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); // insert some data in first and second line inputHandler.parse(Array(bufferService.cols - 9).join('a')); @@ -162,7 +162,7 @@ describe('InputHandler', () => { it('deleteChars', function(): void { const term = new Terminal(); const bufferService = new MockBufferService(80, 30); - const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); + const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); // insert some data in first and second line inputHandler.parse(Array(bufferService.cols - 9).join('a')); @@ -203,7 +203,7 @@ describe('InputHandler', () => { it('eraseInLine', function(): void { const term = new Terminal(); const bufferService = new MockBufferService(80, 30); - const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); + const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); // fill 6 lines to test 3 different states inputHandler.parse(Array(bufferService.cols + 1).join('a')); @@ -232,7 +232,7 @@ describe('InputHandler', () => { it('eraseInDisplay', function(): void { const term = new Terminal({cols: 80, rows: 7}); const bufferService = new MockBufferService(80, 7); - const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); + const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); // fill display with a's for (let i = 0; i < bufferService.rows; ++i) inputHandler.parse(Array(bufferService.cols + 1).join('a')); @@ -367,7 +367,7 @@ describe('InputHandler', () => { describe('print', () => { it('should not cause an infinite loop (regression test)', () => { const term = new Terminal(); - const inputHandler = new InputHandler(term, new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); + const inputHandler = new InputHandler(term, new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); const container = new Uint32Array(10); container[0] = 0x200B; inputHandler.print(container, 0, 1); @@ -382,7 +382,7 @@ describe('InputHandler', () => { beforeEach(() => { term = new Terminal(); bufferService = new MockBufferService(80, 30); - handler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); + handler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); }); it('should handle DECSET/DECRST 47 (alt screen buffer)', () => { handler.parse('\x1b[?47h\r\n\x1b[31mJUNK\x1b[?47lTEST'); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index d0007f495..fb37f4ab1 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -18,9 +18,10 @@ import { NULL_CELL_CODE, NULL_CELL_WIDTH, Attributes, FgFlags, BgFlags, Content import { CellData } from 'common/buffer/CellData'; import { AttributeData } from 'common/buffer/AttributeData'; import { IAttributeData, IDisposable, IWindowOptions } from 'common/Types'; -import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService } from 'common/services/Services'; +import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService, IInstantiationService } from 'common/services/Services'; import { OscHandler } from 'common/parser/OscParser'; import { DcsHandler } from 'common/parser/DcsParser'; +import { IRenderService } from 'browser/services/Services'; /** * Map collect to glevel. Used in `selectCharset`. @@ -246,6 +247,7 @@ export class InputHandler extends Disposable implements IInputHandler { private readonly _optionsService: IOptionsService, private readonly _coreMouseService: ICoreMouseService, private readonly _unicodeService: IUnicodeService, + private readonly _instantiationService: IInstantiationService, private readonly _parser: IEscapeSequenceParser = new EscapeSequenceParser()) { super(); @@ -2459,7 +2461,7 @@ export class InputHandler extends Disposable implements IInputHandler { return; } const second = (params.length > 1) ? params.params[1] : 0; - const rs = (this._terminal as any)._renderService; // FIXME: import renderService to get rid of any type here + const rs = this._instantiationService.getService(IRenderService); switch (params.params[0]) { case 14: // GetWinSizePixels, returns CSI 4 ; height ; width t if (rs && second !== 2) { diff --git a/src/Terminal.ts b/src/Terminal.ts index a532c6dab..1bcac1f05 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -245,7 +245,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._inputHandler.reset(); } else { // Register input handler and refire/handle events - this._inputHandler = new InputHandler(this, this._bufferService, this._charsetService, this._coreService, this._dirtyRowService, this._logService, this.optionsService, this._coreMouseService, this.unicodeService); + this._inputHandler = new InputHandler(this, this._bufferService, this._charsetService, this._coreService, this._dirtyRowService, this._logService, this.optionsService, this._coreMouseService, this.unicodeService, this._instantiationService); this._inputHandler.onRequestBell(() => this.bell()); this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end)); this._inputHandler.onRequestReset(() => this.reset()); diff --git a/src/common/services/InstantiationService.ts b/src/common/services/InstantiationService.ts index abce8ac54..ade3bbe3b 100644 --- a/src/common/services/InstantiationService.ts +++ b/src/common/services/InstantiationService.ts @@ -36,7 +36,7 @@ export class ServiceCollection { return this._entries.has(id); } - get(id: IServiceIdentifier): T { + get(id: IServiceIdentifier): T | undefined { return this._entries.get(id); } } @@ -52,6 +52,10 @@ export class InstantiationService implements IInstantiationService { this._services.set(id, instance); } + public getService(id: IServiceIdentifier): T | undefined { + return this._services.get(id); + } + public createInstance(ctor: any, ...args: any[]): any { const serviceDependencies = getServiceDependencies(ctor).sort((a, b) => a.index - b.index); diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index abc536a9c..23b74e144 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -171,6 +171,7 @@ export interface IConstructorSignature8 { export const IInstantiationService = createDecorator('InstantiationService'); export interface IInstantiationService { setService(id: IServiceIdentifier, instance: T): void; + getService(id: IServiceIdentifier): T | undefined; createInstance(ctor: IConstructorSignature0): T; createInstance(ctor: IConstructorSignature1, first: A1): T; From 1b4a3475e86193bd4b4172099161c4443fb30708 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 4 Feb 2020 07:07:34 -0800 Subject: [PATCH 261/268] 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 b7951423c..95dfcd1e0 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 262/268] 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 5c5d59702..6c67febb5 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 263/268] 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 51abeba59..cac99d90c 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 264/268] 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 2af7e87e3..58f818ee4 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 265/268] 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 58f818ee4..067ffc31c 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 266/268] 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 067ffc31c..5f97cf109 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 267/268] 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 1d011726c..94a3b7ced 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 065dfb645..2b180a4ae 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 5f97cf109..2fcce2315 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 268/268] 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 94a3b7ced..1d011726c 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 2b180a4ae..065dfb645 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 2685d55ce..7b4307227 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; } /**