Compare commits

..
10 Commits
Author SHA1 Message Date
Daniel Imms 7d19769bd0 Merge pull request #2436 from Tyriar/r4_0_release
v4.0.2
2019-09-24 06:13:16 -07:00
Daniel Imms a5e43ad8cc v4.0.2 2019-09-24 06:10:43 -07:00
Daniel Imms ac69ef5e01 Merge pull request #2435 from Tyriar/r4_0_2433
Fix NPE when open called twice (release/4.0)
2019-09-23 10:55:26 -07:00
Daniel Imms b28be877bd Fix NPE when open called twice
Fixes #2433
2019-09-23 07:48:31 -07:00
Daniel Imms 217ed05606 Merge pull request #2426 from Tyriar/r401
v4.0.1
2019-09-12 14:57:08 -07:00
Daniel Imms 02aa803889 v4.0.1 2019-09-12 14:53:56 -07:00
Daniel Imms 9489bd8c5c Merge pull request #2418 from Tyriar/2417_edge_links
Fix web links addon support in Edge
2019-09-11 14:37:54 -07:00
Daniel Imms 324dc8d4dc Fix web links addon support in Edge
regex.flags isn't supported on Edge

Fixes #2417
2019-09-11 14:35:36 -07:00
Daniel Imms 70ecec3c92 Merge pull request #2416 from Tyriar/window_export
Expose ctor at window.Terminal
2019-09-11 08:54:18 -07:00
Daniel Imms e0997b51e1 Expose ctor at window.Terminal
Fixes #2415
2019-09-11 08:51:01 -07:00
190 changed files with 6387 additions and 13352 deletions
+2 -9
View File
@@ -8,19 +8,12 @@ RUN apt-get update \
# Verify git and process tools are installed
RUN apt-get install -y git procps
# Install yarn, puppeteer deps
# Install yarn
RUN apt-get install -y curl apt-transport-https lsb-release \
&& curl -sS https://dl.yarnpkg.com/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/pubkey.gpg | apt-key add - 2>/dev/null \
&& echo "deb https://dl.yarnpkg.com/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
&& apt-get update \
&& apt-get -y install --no-install-recommends \
yarn fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst ttf-freefont \
# https://github.com/Googlechrome/puppeteer/issues/290#issuecomment-322921352
gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 \
libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 \
libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 \
libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 \
ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget
&& apt-get -y install --no-install-recommends yarn
# Clean up
RUN apt-get autoremove -y \
+2 -2
View File
@@ -3,7 +3,7 @@
"dockerFile": "Dockerfile",
"appPort": 3000,
"extensions": [
"dbaeumer.vscode-eslint",
"editorconfig.editorconfig"
"editorconfig.editorconfig",
"ms-vscode.vscode-typescript-tslint-plugin"
]
}
-150
View File
@@ -1,150 +0,0 @@
{
"env": {
"browser": true,
"es6": true,
"node": true
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": [
"src/browser/tsconfig.json",
"src/common/tsconfig.json",
"src/tsconfig.json",
"test/api/tsconfig.json",
"test/benchmark/tsconfig.json",
"addons/xterm-addon-attach/src/tsconfig.json",
"addons/xterm-addon-fit/src/tsconfig.json",
"addons/xterm-addon-search/src/tsconfig.json",
"addons/xterm-addon-unicode11/src/tsconfig.json",
"addons/xterm-addon-web-links/src/tsconfig.json",
"addons/xterm-addon-webgl/src/tsconfig.json",
"addons/xterm-addon-serialize/src/tsconfig.json",
"addons/xterm-addon-serialize/benchmark/tsconfig.json"
],
"sourceType": "module"
},
"ignorePatterns": [
"**/typings/*.d.ts"
],
"plugins": [
"@typescript-eslint"
],
"rules": {
"@typescript-eslint/array-type": [
"error",
{
"default": "array-simple",
"readonly": "generic"
}
],
"@typescript-eslint/class-name-casing": "error",
"@typescript-eslint/consistent-type-definitions": "error",
"@typescript-eslint/explicit-function-return-type": [
"error",
{
"allowExpressions": true
}
],
"@typescript-eslint/indent": [
"error",
2
],
"@typescript-eslint/interface-name-prefix": [
"error",
"always"
],
"@typescript-eslint/member-delimiter-style": [
"error",
{
"multiline": {
"delimiter": "semi",
"requireLast": true
},
"singleline": {
"delimiter": "comma",
"requireLast": false
}
}
],
"@typescript-eslint/naming-convention": [
"error",
{ "selector": "default", "format": ["camelCase"] },
// variableLike
{ "selector": "variable", "format": ["camelCase", "UPPER_CASE"] },
{ "selector": "variable", "filter": "^I.+Service$", "format": ["PascalCase"], "prefix": ["I"] },
// memberLike
{ "selector": "memberLike", "modifiers": ["private"], "format": ["camelCase"], "leadingUnderscore": "require" },
{ "selector": "memberLike", "modifiers": ["protected"], "format": ["camelCase"], "leadingUnderscore": "require" },
{ "selector": "enumMember", "format": ["UPPER_CASE"] },
// memberLike - Allow enum-like objects to use UPPER_CASE
{ "selector": "property", "modifiers": ["public"], "format": ["camelCase", "UPPER_CASE"] },
{ "selector": "method", "modifiers": ["public"], "format": ["camelCase", "UPPER_CASE"] },
// typeLike
{ "selector": "typeLike", "format": ["PascalCase"] },
{ "selector": "interface", "format": ["PascalCase"], "prefix": ["I"] }
],
"@typescript-eslint/prefer-namespace-keyword": "error",
"@typescript-eslint/type-annotation-spacing": "error",
"@typescript-eslint/quotes": [
"error",
"single",
{ "allowTemplateLiterals": true }
],
"@typescript-eslint/semi": [
"error",
"always"
],
"comma-dangle": [
"error",
{
"objects": "never",
"arrays": "never",
"functions": "never"
}
],
"curly": [
"error",
"multi-line"
],
"eol-last": "error",
"eqeqeq": [
"error",
"always"
],
"keyword-spacing": "error",
"new-parens": "error",
"no-duplicate-imports": "error",
"no-else-return": [
"error",
{
"allowElseIf": false
}
],
"no-eval": "error",
"no-irregular-whitespace": "error",
"no-restricted-imports": [
"error",
{
"patterns": [
".*\\/out\\/.*"
]
}
],
"no-trailing-spaces": "error",
"no-unsafe-finally": "error",
"no-var": "error",
"one-var": [
"error",
"never"
],
"prefer-const": "error",
"spaced-comment": [
"error",
"always",
{
"markers": ["/"],
"exceptions": ["-"]
}
]
}
}
-5
View File
@@ -1,5 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Question
url: https://stackoverflow.com/questions/tagged/xtermjs
about: Please ask and answer questions here.
+8
View File
@@ -0,0 +1,8 @@
---
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.
-1
View File
@@ -4,7 +4,6 @@ node_modules/
lib/
out/
out-test/
.nyc_output/
Makefile.gyp
*.Makefile
*.target.gyp.mk
+1
View File
@@ -42,6 +42,7 @@
*.test.js
*.test.js.map
lib/test/
tslint.json
webpack.config.js
docs/
+1 -2
View File
@@ -1,4 +1,3 @@
{
"typescript.preferences.importModuleSpecifier": "non-relative",
"typescript.preferences.quoteStyle": "single"
"typescript.preferences.quoteStyle": "single"
}
-4
View File
@@ -52,7 +52,3 @@ By contributing code to xterm.js you:
holder has explicitly granted the right to use it like this,
through a compatible open source license or through a direct
agreement with you.)
### Test coverage
One area that always needs attention is improving out unit test coverage, you can view the code coverage report on [Azure Pipelines](https://dev.azure.com/xtermjs/xterm.js/_build/latest?definitionId=3) by clicking the Code Coverage tab.
+2 -9
View File
@@ -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 and minimum contrast ratio support can be turned on
- **Accessible**: Screen reader support can be turned on using the `screenReaderMode` option.
- **And much more**: Links, theming, addons, well documented API, etc.
## What xterm.js is not
@@ -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.
- [**Next Tech**](https://next.tech "Next Tech"): Online platform for interactive coding and web development courses. Live container-backed terminal uses xterm.js.
- [**Codevolve**](https://www.codevolve.com "Codevolve"): Online platform for interactive coding and web development courses. Live container-backed terminal uses xterm.js.
- [**RStudio**](https://www.rstudio.com/products/RStudio "RStudio"): RStudio is an integrated development environment (IDE) for R.
- [**Terminal for Atom**](https://github.com/jsmecham/atom-terminal-tab): A simple terminal for the Atom text editor.
- [**Eclipse Orion**](https://orionhub.org): A modern, open source software development environment that runs in the cloud. Code, deploy and run in the cloud.
@@ -154,13 +154,6 @@ Xterm.js is used in several world-class applications to provide great terminal e
- [**PHP App Server**](https://github.com/cubiclesoft/php-app-server/): Create lightweight, installable almost-native applications for desktop OSes. ExecTerminal (nicely wraps the xterm.js Terminal), TerminalManager, and RunProcessSDK are self-contained, reusable ES5+ compliant Javascript components.
- [**NgTerminal**](https://github.com/qwefgh90/ng-terminal): NgTerminal is a web terminal that leverages xterm.js on Angular 7+. You can easily add it into your application by adding `<ng-terminal></ng-terminal>` into your component.
- [**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
- [**Linode**](https://linode.com): Linode uses xterm.js to provide users a web console for their Linode instances.
- [**FluffOS**](https://www.fluffos.info): Active maintained LPMUD driver with websocket support.
- [**x-terminal**](https://atom.io/packages/x-terminal): Atom plugin for providing terminals inside your Atom workspace.
- [**CoCalc**](https://cocalc.com/): Lots of free software pre-installed, to chat, collaborate, develop, program, publish, research, share, teach, in C++, HTML, Julia, Jupyter, LaTeX, Markdown, Python, R, SageMath, Scala, ...
[And much more...](https://github.com/xtermjs/xterm.js/network/dependents)
+1 -1
View File
@@ -12,7 +12,7 @@ npm install --save xterm-addon-attach
```ts
import { Terminal } from 'xterm';
import { AttachAddon } from 'xterm-addon-attach';
import { FitAddon } from 'xterm-addon-attach';
const terminal = new Terminal();
const attachAddon = new AttachAddon(webSocket);
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-attach",
"version": "0.5.0",
"version": "0.2.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
@@ -16,6 +16,6 @@
"prepublishOnly": "npm run package"
},
"peerDependencies": {
"xterm": "^4.0.0"
"xterm": "^3.14.0"
}
}
@@ -3,51 +3,69 @@
* @license MIT
*/
import * as puppeteer from 'puppeteer';
import { assert } from 'chai';
import { ITerminalOptions } from 'xterm';
import WebSocket = require('ws');
import { openTerminal, pollFor, getBrowserType } from '../../../out-test/api/TestUtils';
import { Browser, Page } from 'playwright-core';
const APP = 'http://127.0.0.1:3000/test';
let browser: Browser;
let page: Page;
let browser: puppeteer.Browser;
let page: puppeteer.Page;
const width = 800;
const height = 600;
describe('AttachAddon', () => {
before(async function(): Promise<any> {
const browserType = getBrowserType();
browser = await browserType.launch({ dumpio: true,
headless: process.argv.indexOf('--headless') !== -1
this.timeout(20000);
browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1,
slowMo: 80,
args: [`--window-size=${width},${height}`]
});
page = await (await browser.newContext()).newPage();
await page.setViewportSize({ width, height });
page = (await browser.pages())[0];
await page.setViewport({ width, height });
});
after(async () => {
await browser.close();
});
beforeEach(async () => await page.goto(APP));
beforeEach(async function(): Promise<any> {
this.timeout(20000);
await page.goto(APP);
});
it('string', async function(): Promise<any> {
await openTerminal(page, { rendererType: 'dom' });
this.timeout(20000);
await openTerminal({ rendererType: 'dom' });
const port = 8080;
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.active.getLine(0).translateToString(true)`, 'foo');
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo');
server.close();
});
it('utf8', async function(): Promise<any> {
await openTerminal(page, { rendererType: 'dom' });
this.timeout(20000);
await openTerminal({ rendererType: 'dom' });
const port = 8080;
const server = new WebSocket.Server({ port });
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.active.getLine(0).translateToString(true)`, 'foo');
await page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}'), { inputUtf8: true }))`);
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo');
server.close();
});
});
async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
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');
}
}
+11 -19
View File
@@ -9,11 +9,13 @@ import { Terminal, IDisposable, ITerminalAddon } from 'xterm';
interface IAttachOptions {
bidirectional?: boolean;
inputUtf8?: boolean;
}
export class AttachAddon implements ITerminalAddon {
private _socket: WebSocket;
private _bidirectional: boolean;
private _utf8: boolean;
private _disposables: IDisposable[] = [];
constructor(socket: WebSocket, options?: IAttachOptions) {
@@ -21,19 +23,20 @@ export class AttachAddon implements ITerminalAddon {
// always set binary type to arraybuffer, we do not handle blobs
this._socket.binaryType = 'arraybuffer';
this._bidirectional = (options && options.bidirectional === false) ? false : true;
this._utf8 = !!(options && options.inputUtf8);
}
public activate(terminal: Terminal): void {
this._disposables.push(
addSocketListener(this._socket, 'message', ev => {
const data: ArrayBuffer | string = ev.data;
terminal.write(typeof data === 'string' ? data : new Uint8Array(data));
})
);
if (this._utf8) {
this._disposables.push(addSocketListener(this._socket, 'message',
(ev: MessageEvent | Event | CloseEvent) => terminal.writeUtf8(new Uint8Array((ev as any).data as ArrayBuffer))));
} else {
this._disposables.push(addSocketListener(this._socket, 'message',
(ev: MessageEvent | Event | CloseEvent) => terminal.write((ev as any).data as string)));
}
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()));
@@ -52,20 +55,9 @@ 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<K extends keyof WebSocketEventMap>(socket: WebSocket, type: K, handler: (this: WebSocket, ev: WebSocketEventMap[K]) => any): IDisposable {
function addSocketListener(socket: WebSocket, type: string, handler: (this: WebSocket, ev: MessageEvent | Event | CloseEvent) => any): IDisposable {
socket.addEventListener(type, handler);
return {
dispose: () => {
+2 -6
View File
@@ -1,7 +1,7 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"target": "es2015",
"lib": [
"dom",
"es2015"
@@ -10,11 +10,7 @@
"outDir": "../out",
"sourceMap": true,
"removeComments": true,
"strict": true,
"types": [
"../../../node_modules/@types/mocha",
"../../../out-test/api/TestUtils"
]
"strict": true
},
"include": [
"./**/*",
@@ -11,6 +11,14 @@ declare module 'xterm-addon-attach' {
* Whether input should be written to the backend. Defaults to `true`.
*/
bidirectional?: boolean;
/**
* Whether to use UTF8 binary transport for incoming messages. Defaults to `false`.
* Note: This must be in line with the server side of the websocket.
* Always send string messages from the backend if this options is false,
* otherwise always binary UTF8 data.
*/
inputUtf8?: boolean;
}
export class AttachAddon implements ITerminalAddon {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-fit",
"version": "0.3.0",
"version": "0.2.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
@@ -16,6 +16,6 @@
"prepublishOnly": "npm run package"
},
"peerDependencies": {
"xterm": "^4.0.0"
"xterm": "^3.14.0"
}
}
-113
View File
@@ -1,113 +0,0 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { assert } from 'chai';
import { openTerminal, getBrowserType } from '../../../out-test/api/TestUtils';
import { Browser, Page } from 'playwright-core';
const APP = 'http://127.0.0.1:3000/test';
let browser: Browser;
let page: Page;
const width = 1024;
const height = 768;
describe('FitAddon', () => {
before(async function(): Promise<any> {
const browserType = getBrowserType();
browser = await browserType.launch({ dumpio: true,
headless: process.argv.indexOf('--headless') !== -1
});
page = await (await browser.newContext()).newPage();
await page.setViewportSize({ width, height });
await page.goto(APP);
await openTerminal(page);
});
after(async () => {
await browser.close();
});
it('no terminal', async function(): Promise<any> {
await page.evaluate(`window.fit = new FitAddon();`);
assert.equal(await page.evaluate(`window.fit.proposeDimensions()`), undefined);
});
describe('proposeDimensions', () => {
afterEach(async () => {
return unloadFit();
});
it('default', async function(): Promise<any> {
await loadFit();
const dimensions: {cols: number, rows: number} = await page.evaluate(`window.fit.proposeDimensions()`);
assert.equal(dimensions.cols, 87);
assert.isAbove(dimensions.rows, 24);
assert.isBelow(dimensions.rows, 29);
});
it('width', async function(): Promise<any> {
await loadFit(1008);
const dimensions: {cols: number, rows: number} = await page.evaluate(`window.fit.proposeDimensions()`);
assert.equal(dimensions.cols, 110);
assert.isAbove(dimensions.rows, 24);
assert.isBelow(dimensions.rows, 29);
});
it('small', async function(): Promise<any> {
await loadFit(1, 1);
assert.deepEqual(await page.evaluate(`window.fit.proposeDimensions()`), {
cols: 2,
rows: 1
});
});
});
describe('fit', () => {
afterEach(async () => {
return unloadFit();
});
it('default', async function(): Promise<any> {
await loadFit();
await page.evaluate(`window.fit.fit()`);
const cols: number = await page.evaluate(`window.term.cols`);
const rows: number = await page.evaluate(`window.term.rows`);
assert.equal(cols, 87);
assert.isAbove(rows, 24);
assert.isBelow(rows, 29);
});
it('width', async function(): Promise<any> {
await loadFit(1008);
await page.evaluate(`window.fit.fit()`);
const cols: number = await page.evaluate(`window.term.cols`);
const rows: number = await page.evaluate(`window.term.rows`);
assert.equal(cols, 110);
assert.isAbove(rows, 24);
assert.isBelow(rows, 29);
});
it('small', async function(): Promise<any> {
await loadFit(1, 1);
await page.evaluate(`window.fit.fit()`);
assert.equal(await page.evaluate(`window.term.cols`), 2);
assert.equal(await page.evaluate(`window.term.rows`), 1);
});
});
});
async function loadFit(width: number = 800, height: number = 450): Promise<void> {
await page.evaluate(`
window.fit = new FitAddon();
window.term.loadAddon(window.fit);
document.querySelector('#terminal-container').style.width='${width}px';
document.querySelector('#terminal-container').style.height='${height}px';
`);
}
async function unloadFit(): Promise<void> {
await page.evaluate(`window.fit.dispose();`);
}
+5 -8
View File
@@ -17,9 +17,6 @@ interface ITerminalDimensions {
cols: number;
}
const MINIMUM_COLS = 2;
const MINIMUM_ROWS = 1;
export class FitAddon implements ITerminalAddon {
private _terminal: Terminal | undefined;
@@ -38,7 +35,7 @@ export class FitAddon implements ITerminalAddon {
}
// TODO: Remove reliance on private API
const core = (this._terminal as any)._core;
const core = (<any>this._terminal)._core;
// Force a full render
if (this._terminal.rows !== dims.rows || this._terminal.cols !== dims.cols) {
@@ -52,12 +49,12 @@ export class FitAddon implements ITerminalAddon {
return undefined;
}
if (!this._terminal.element || !this._terminal.element.parentElement) {
if (!this._terminal.element.parentElement) {
return undefined;
}
// TODO: Remove reliance on private API
const core = (this._terminal as any)._core;
const core = (<any>this._terminal)._core;
const parentElementStyle = window.getComputedStyle(this._terminal.element.parentElement);
const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
@@ -74,8 +71,8 @@ export class FitAddon implements ITerminalAddon {
const availableHeight = parentElementHeight - elementPaddingVer;
const availableWidth = parentElementWidth - elementPaddingHor - core.viewport.scrollBarWidth;
const geometry = {
cols: Math.max(MINIMUM_COLS, Math.floor(availableWidth / core._renderService.dimensions.actualCellWidth)),
rows: Math.max(MINIMUM_ROWS, Math.floor(availableHeight / core._renderService.dimensions.actualCellHeight))
cols: Math.floor(availableWidth / core._renderService.dimensions.actualCellWidth),
rows: Math.floor(availableHeight / core._renderService.dimensions.actualCellHeight)
};
return geometry;
}
+1 -5
View File
@@ -10,11 +10,7 @@
"outDir": "../out",
"sourceMap": true,
"removeComments": true,
"strict": true,
"types": [
"../../../node_modules/@types/mocha",
"../../../out-test/api/TestUtils"
]
"strict": true
},
"include": [
"./**/*",
@@ -1,70 +0,0 @@
/usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles
make[3]: Entering directory `/home/yan/ssd/searchtrunk/tmp/release'
make[3]: Nothing to be done for `libsrc/wrap_curl/CMakeFiles/wrap_curl.dir/build'.
make[3]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release'
[ 0%] /usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles
[ 0%] [ 0%] /usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles
Built target config_utils
[ 0%] make[3]: Entering directory `/home/yan/ssd/searchtrunk/tmp/release'
/usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles
Built target omega
make[3]: Entering directory `/home/yan/ssd/searchtrunk/tmp/release'
cd /home/yan/ssd/searchtrunk/tmp/release && /usr/bin/cmake -E cmake_depends "Unix Makefiles" /home/yan/ssd/searchtrunk /home/yan/ssd/searchtrunk/projects/protogen/cpp /home/yan/ssd/searchtrunk/tmp/release /home/yan/ssd/searchtrunk/tmp/release/projects/protogen/cpp /home/yan/ssd/searchtrunk/tmp/release/projects/protogen/cpp/CMakeFiles/protogen.dir/DependInfo.cmake --color=
/usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles
Built target morpheus
make[3]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release'
make -f libsrc/base/CMakeFiles/base.dir/build.make libsrc/base/CMakeFiles/base.dir/build
make[3]: Entering directory `/home/yan/ssd/searchtrunk/tmp/release'
make[3]: Nothing to be done for `libsrc/coroutine/CMakeFiles/coroutine.dir/build'.
Built target wrap_curl
make[3]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release'
/usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles
/usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles
[ 0%] [ 0%] [ 0%] [ 0%] Building CXX object libsrc/image_crop/CMakeFiles/image_crop.dir/src/crop_context.cpp.o
cd /home/yan/ssd/searchtrunk/tmp/release/libsrc/image_crop && /opt/rh/devtoolset-7/root/usr/bin/c++ -DGOGO_USE_LIBEVENT2=1 -DGOGO_USE_OPENSSL_SNI=1 -DHAVE_BOOST -DHAVE_GLIBC -DHAVE_MALLOC_TRIM -DHAVE_PREAD -DHAVE_READAHEAD -D_FILE_OFFSET_BITS=64 -D_STAT_LEMM -pipe -Wall -Wextra -Werror=multichar -Wno-deprecated -Wno-unused-parameter -pthread -fPIC -Woverloaded-virtual -Wnon-virtual-dtor -Werror -std=gnu++11 -Wno-unknown-pragmas -DBOOST_FILESYSTEM_VERSION=3 -DBOOST_NO_CXX11_HDR_CODECVT -DMAGICKCORE_QUANTUM_DEPTH=8 -DMAGICKCORE_HDRI_ENABLE=0 -Wno-unused-local-typedefs -D_GLIBCXX_USE_CXX11_ABI=0 -std=gnu++14 -DCXXHASH128_EXIST -static-libstdc++ -Wno-implicit-fallthrough -Wno-stringop-overflow -O3 -DNDEBUG -DBOOST_UBLAS_NDEBUG -g0 -march=core2 -mssse3 -msse4.1 -I/home/yan/ssd/searchtrunk/tmp/release/__inc -I/home/yan/ssd/searchtrunk/libsrc/image_crop/src -I/home/yan/ssd/searchtrunk/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/boost_1_69_0/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/icu-49.1.2/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/protobuf-3.4.0/include -I/home/yan/ssd/searchtrunk/tmp/release -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/opencv-3.0.0m2/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/dlib-19.7p1/include -Wno-array-bounds -o CMakeFiles/image_crop.dir/src/crop_context.cpp.o -c /home/yan/ssd/searchtrunk/libsrc/image_crop/src/crop_context.cpp
Building CXX object libsrc/image_crop/CMakeFiles/image_crop.dir/src/detector.cpp.o
Building CXX object libsrc/image_crop/CMakeFiles/image_crop.dir/src/image_crop.cpp.o
cd /home/yan/ssd/searchtrunk/tmp/release/libsrc/image_crop && /opt/rh/devtoolset-7/root/usr/bin/c++ -DGOGO_USE_LIBEVENT2=1 -DGOGO_USE_OPENSSL_SNI=1 -DHAVE_BOOST -DHAVE_GLIBC -DHAVE_MALLOC_TRIM -DHAVE_PREAD -DHAVE_READAHEAD -D_FILE_OFFSET_BITS=64 -D_STAT_LEMM -pipe -Wall -Wextra -Werror=multichar -Wno-deprecated -Wno-unused-parameter -pthread -fPIC -Woverloaded-virtual -Wnon-virtual-dtor -Werror -std=gnu++11 -Wno-unknown-pragmas -DBOOST_FILESYSTEM_VERSION=3 -DBOOST_NO_CXX11_HDR_CODECVT -DMAGICKCORE_QUANTUM_DEPTH=8 -DMAGICKCORE_HDRI_ENABLE=0 -Wno-unused-local-typedefs -D_GLIBCXX_USE_CXX11_ABI=0 -std=gnu++14 -DCXXHASH128_EXIST -static-libstdc++ -Wno-implicit-fallthrough -Wno-stringop-overflow -O3 -DNDEBUG -DBOOST_UBLAS_NDEBUG -g0 -march=core2 -mssse3 -msse4.1 -I/home/yan/ssd/searchtrunk/tmp/release/__inc -I/home/yan/ssd/searchtrunk/libsrc/image_crop/src -I/home/yan/ssd/searchtrunk/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/boost_1_69_0/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/icu-49.1.2/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/protobuf-3.4.0/include -I/home/yan/ssd/searchtrunk/tmp/release -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/opencv-3.0.0m2/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/dlib-19.7p1/include -Wno-array-bounds -o CMakeFiles/image_crop.dir/src/detector.cpp.o -c /home/yan/ssd/searchtrunk/libsrc/image_crop/src/detector.cpp
Built target coroutine
cd /home/yan/ssd/searchtrunk/tmp/release/libsrc/image_crop && /opt/rh/devtoolset-7/root/usr/bin/c++ -DGOGO_USE_LIBEVENT2=1 -DGOGO_USE_OPENSSL_SNI=1 -DHAVE_BOOST -DHAVE_GLIBC -DHAVE_MALLOC_TRIM -DHAVE_PREAD -DHAVE_READAHEAD -D_FILE_OFFSET_BITS=64 -D_STAT_LEMM -pipe -Wall -Wextra -Werror=multichar -Wno-deprecated -Wno-unused-parameter -pthread -fPIC -Woverloaded-virtual -Wnon-virtual-dtor -Werror -std=gnu++11 -Wno-unknown-pragmas -DBOOST_FILESYSTEM_VERSION=3 -DBOOST_NO_CXX11_HDR_CODECVT -DMAGICKCORE_QUANTUM_DEPTH=8 -DMAGICKCORE_HDRI_ENABLE=0 -Wno-unused-local-typedefs -D_GLIBCXX_USE_CXX11_ABI=0 -std=gnu++14 -DCXXHASH128_EXIST -static-libstdc++ -Wno-implicit-fallthrough -Wno-stringop-overflow -O3 -DNDEBUG -DBOOST_UBLAS_NDEBUG -g0 -march=core2 -mssse3 -msse4.1 -I/home/yan/ssd/searchtrunk/tmp/release/__inc -I/home/yan/ssd/searchtrunk/libsrc/image_crop/src -I/home/yan/ssd/searchtrunk/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/boost_1_69_0/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/icu-49.1.2/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/protobuf-3.4.0/include -I/home/yan/ssd/searchtrunk/tmp/release -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/opencv-3.0.0m2/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/dlib-19.7p1/include -Wno-array-bounds -o CMakeFiles/image_crop.dir/src/image_crop.cpp.o -c /home/yan/ssd/searchtrunk/libsrc/image_crop/src/image_crop.cpp
make[3]: Entering directory `/home/yan/ssd/searchtrunk/tmp/release'
make[3]: Nothing to be done for `libsrc/base/CMakeFiles/base.dir/build'.
make[3]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release'
/usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles
make[3]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release'
make -f projects/protogen/cpp/CMakeFiles/protogen.dir/build.make projects/protogen/cpp/CMakeFiles/protogen.dir/build
[ 0%] Built target base
In file included from /home/yan/ssd/searchtrunk/include/image_crop/detector.hpp:3:0,
from /home/yan/ssd/searchtrunk/libsrc/image_crop/src/detector.cpp:2:
/home/yan/ssd/searchtrunk/include/image_crop/crop_context.hpp:6:10: fatal error: opencv2/dnn.hpp: No such file or directory
#include <opencv2/dnn.hpp>
^~~~~~~~~~~~~~~~~
compilation terminated.
make[3]: *** [libsrc/image_crop/CMakeFiles/image_crop.dir/src/detector.cpp.o] Error 1
make[3]: *** Waiting for unfinished jobs....
In file included from /home/yan/ssd/searchtrunk/include/image_crop/image_crop.hpp:3:0,
from /home/yan/ssd/searchtrunk/libsrc/image_crop/src/image_crop.cpp:1:
/home/yan/ssd/searchtrunk/include/image_crop/crop_context.hpp:6:10: fatal error: opencv2/dnn.hpp: No such file or directory
#include <opencv2/dnn.hpp>
^~~~~~~~~~~~~~~~~
compilation terminated.
make[3]: *** [libsrc/image_crop/CMakeFiles/image_crop.dir/src/image_crop.cpp.o] Error 1
In file included from /home/yan/ssd/searchtrunk/libsrc/image_crop/src/crop_context.cpp:1:0:
/home/yan/ssd/searchtrunk/include/image_crop/crop_context.hpp:6:10: fatal error: opencv2/dnn.hpp: No such file or directory
#include <opencv2/dnn.hpp>
^~~~~~~~~~~~~~~~~
compilation terminated.
make[3]: *** [libsrc/image_crop/CMakeFiles/image_crop.dir/src/crop_context.cpp.o] Error 1
make[3]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release'
make[2]: *** [libsrc/image_crop/CMakeFiles/image_crop.dir/all] Error 2
make[2]: *** Waiting for unfinished jobs....
make[3]: Entering directory `/home/yan/ssd/searchtrunk/tmp/release'
make[3]: Nothing to be done for `projects/protogen/cpp/CMakeFiles/protogen.dir/build'.
make[3]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release'
/usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles 63 64 65 66
[ 25%] Built target protogen
make[2]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release'
make[1]: *** [all] Error 2
make[1]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release/projects/projectX'
make: *** [install] Error 2
yan@yPC:~/ssd/searchtrunk/projects/projectX$
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-search",
"version": "0.6.0",
"version": "0.2.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
@@ -16,6 +16,6 @@
"prepublishOnly": "npm run package"
},
"peerDependencies": {
"xterm": "^4.0.0"
"xterm": "^3.14.0"
}
}
+48 -116
View File
@@ -3,29 +3,30 @@
* @license MIT
*/
import * as puppeteer from 'puppeteer';
import { assert } from 'chai';
import { readFile } from 'fs';
import { resolve } from 'path';
import { openTerminal, writeSync, getBrowserType } from '../../../out-test/api/TestUtils';
import { Browser, Page } from 'playwright-core';
import { ITerminalOptions } from 'xterm';
const APP = 'http://127.0.0.1:3000/test';
let browser: Browser;
let page: Page;
let browser: puppeteer.Browser;
let page: puppeteer.Page;
const width = 800;
const height = 600;
describe('Search Tests', function(): void {
before(async function(): Promise<any> {
const browserType = getBrowserType();
browser = await browserType.launch({ dumpio: true,
headless: process.argv.indexOf('--headless') !== -1
describe('Search Tests', function (): void {
this.timeout(200000);
before(async function (): Promise<any> {
browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1,
slowMo: 80,
args: [`--window-size=${width},${height}`]
});
page = await (await browser.newContext()).newPage();
await page.setViewportSize({ width, height });
page = (await browser.pages())[0];
await page.setViewport({ width, height });
await page.goto(APP);
await openTerminal(page);
await openTerminal();
await page.evaluate(`window.search = new SearchAddon();`);
await page.evaluate(`window.term.loadAddon(window.search);`);
});
@@ -39,7 +40,7 @@ describe('Search Tests', function(): void {
});
it('Simple Search', async () => {
await writeSync(page, 'dafhdjfldshafhldsahfkjhldhjkftestlhfdsakjfhdjhlfdsjkafhjdlk');
await writeSync('dafhdjfldshafhldsahfkjhldhjkftestlhfdsakjfhdjhlfdsjkafhjdlk');
assert.deepEqual(await page.evaluate(`window.search.findNext('test')`), true);
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'test');
});
@@ -52,16 +53,16 @@ describe('Search Tests', function(): void {
}
dataString += makeData(50);
}
await writeSync(page, dataString);
await writeSync(dataString);
assert.deepEqual(await page.evaluate(`window.search.findNext('$^1_3{}test$#')`), true);
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), '$^1_3{}test$#');
});
it('Incremental Find Previous', async () => {
it ('Incremental Find Previous', async () => {
await page.evaluate(`window.term.writeln('package.jsonc\\n')`);
await writeSync(page, 'package.json pack package.lock');
await writeSync('package.json pack package.lock');
await page.evaluate(`window.search.findPrevious('pack', {incremental: true})`);
let line: string = await page.evaluate(`window.term.buffer.active.getLine(window.term.getSelectionPosition().startRow).translateToString()`);
let selectionPosition: { startColumn: number, startRow: number, endColumn: number, endRow: number } = await page.evaluate(`window.term.getSelectionPosition()`);
let line: string = await page.evaluate(`window.term.buffer.getLine(window.term.getSelectionPosition().startRow).translateToString()`);
let selectionPosition: {startColumn: number, startRow: number, endColumn: number, endRow: number} = await page.evaluate(`window.term.getSelectionPosition()`);
// We look further ahead in the line to ensure that pack was selected from package.lock
assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn + 8), 'package.lock');
await page.evaluate(`window.search.findPrevious('package.j', {incremental: true})`);
@@ -69,16 +70,16 @@ describe('Search Tests', function(): void {
assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn + 3), 'package.json');
await page.evaluate(`window.search.findPrevious('package.jsonc', {incremental: true})`);
// We have to reevaluate line because it should have switched starting rows at this point
line = await page.evaluate(`window.term.buffer.active.getLine(window.term.getSelectionPosition().startRow).translateToString()`);
line = await page.evaluate(`window.term.buffer.getLine(window.term.getSelectionPosition().startRow).translateToString()`);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn), 'package.jsonc');
});
it('Incremental Find Next', async () => {
it ('Incremental Find Next', async () => {
await page.evaluate(`window.term.writeln('package.lock pack package.json package.ups\\n')`);
await writeSync(page, 'package.jsonc');
await writeSync('package.jsonc');
await page.evaluate(`window.search.findNext('pack', {incremental: true})`);
let line: string = await page.evaluate(`window.term.buffer.active.getLine(window.term.getSelectionPosition().startRow).translateToString()`);
let selectionPosition: { startColumn: number, startRow: number, endColumn: number, endRow: number } = await page.evaluate(`window.term.getSelectionPosition()`);
let line: string = await page.evaluate(`window.term.buffer.getLine(window.term.getSelectionPosition().startRow).translateToString()`);
let selectionPosition: {startColumn: number, startRow: number, endColumn: number, endRow: number} = await page.evaluate(`window.term.getSelectionPosition()`);
// We look further ahead in the line to ensure that pack was selected from package.lock
assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn + 8), 'package.lock');
await page.evaluate(`window.search.findNext('package.j', {incremental: true})`);
@@ -86,107 +87,38 @@ describe('Search Tests', function(): void {
assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn + 3), 'package.json');
await page.evaluate(`window.search.findNext('package.jsonc', {incremental: true})`);
// We have to reevaluate line because it should have switched starting rows at this point
line = await page.evaluate(`window.term.buffer.active.getLine(window.term.getSelectionPosition().startRow).translateToString()`);
line = await page.evaluate(`window.term.buffer.getLine(window.term.getSelectionPosition().startRow).translateToString()`);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn), 'package.jsonc');
});
it('Simple Regex', async () => {
await writeSync(page, 'abc123defABCD');
it ('Simple Regex', async () => {
await writeSync('abc123defABCD');
await page.evaluate(`window.search.findNext('[a-z]+', {regex: true})`);
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'abc');
await page.evaluate(`window.search.findNext('[A-Z]+', {regex: true, caseSensitive: true})`);
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'ABCD');
});
it('Search for single result twice should not unselect it', async () => {
await writeSync(page, 'abc def');
assert.deepEqual(await page.evaluate(`window.search.findNext('abc')`), true);
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'abc');
assert.deepEqual(await page.evaluate(`window.search.findNext('abc')`), true);
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'abc');
});
describe('Regression tests', () => {
describe('#2444 wrapped line content not being found', () => {
let fixture: string;
before(async () => {
const rawFixture = await new Promise<Buffer>(r => readFile(resolve(__dirname, '../fixtures/issue-2444'), (err, data) => r(data)));
fixture = rawFixture.toString()
.replace(/\n/g, '\\n\\r')
.replace(/'/g, '\\\'');
});
it('should find all occurrences using findNext', async () => {
await writeSync(page, fixture);
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
let selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 53, endColumn: 30, endRow: 53 });
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 76, endColumn: 30, endRow: 76 });
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 96, endColumn: 30, endRow: 96 });
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 1, startRow: 114, endColumn: 7, endRow: 114 });
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 11, startRow: 115, endColumn: 17, endRow: 115 });
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 1, startRow: 126, endColumn: 7, endRow: 126 });
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 11, startRow: 127, endColumn: 17, endRow: 127 });
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 1, startRow: 135, endColumn: 7, endRow: 135 });
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 11, startRow: 136, endColumn: 17, endRow: 136 });
// Wrap around to first result
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 53, endColumn: 30, endRow: 53 });
});
it('should find all occurrences using findPrevious', async () => {
await writeSync(page, fixture);
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
let selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 11, startRow: 136, endColumn: 17, endRow: 136 });
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 1, startRow: 135, endColumn: 7, endRow: 135 });
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 11, startRow: 127, endColumn: 17, endRow: 127 });
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 1, startRow: 126, endColumn: 7, endRow: 126 });
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 11, startRow: 115, endColumn: 17, endRow: 115 });
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 1, startRow: 114, endColumn: 7, endRow: 114 });
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 96, endColumn: 30, endRow: 96 });
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 76, endColumn: 30, endRow: 76 });
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 53, endColumn: 30, endRow: 53 });
// Wrap around to first result
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 11, startRow: 136, endColumn: 17, endRow: 136 });
});
});
});
});
async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
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');
}
}
async function writeSync(data: string): Promise<void> {
await page.evaluate(`window.term.write('${data}');`);
while (true) {
if (await page.evaluate(`window.term._core.writeBuffer.length === 0`)) {
break;
}
}
}
function makeData(length: number): string {
let result = '';
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+32 -68
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { Terminal, IDisposable, ITerminalAddon, ISelectionPosition } from 'xterm';
import { Terminal, IDisposable, ITerminalAddon } from 'xterm';
export interface ISearchOptions {
regex?: boolean;
@@ -12,11 +12,6 @@ export interface ISearchOptions {
incremental?: boolean;
}
export interface ISearchPosition {
startCol: number;
startRow: number;
}
export interface ISearchResult {
term: string;
col: number;
@@ -64,35 +59,29 @@ export class SearchAddon implements ITerminalAddon {
let startCol = 0;
let startRow = 0;
let currentSelection: ISelectionPosition | undefined;
if (this._terminal.hasSelection()) {
const incremental = searchOptions ? searchOptions.incremental : false;
// Start from the selection end if there is a selection
// For incremental search, use existing row
currentSelection = this._terminal.getSelectionPosition()!;
const currentSelection = this._terminal.getSelectionPosition()!;
startRow = incremental ? currentSelection.startRow : currentSelection.endRow;
startCol = incremental ? currentSelection.startColumn : currentSelection.endColumn;
}
this._initLinesCache();
const searchPosition: ISearchPosition = {
startRow,
startCol
};
// Search startRow
let result = this._findInLine(term, searchPosition, searchOptions);
let result = this._findInLine(term, startRow, startCol, searchOptions);
// Search from startRow + 1 to end
if (!result) {
for (let y = startRow + 1; y < this._terminal.buffer.active.baseY + this._terminal.rows; y++) {
searchPosition.startRow = y;
searchPosition.startCol = 0;
for (let y = startRow + 1; y < this._terminal.buffer.baseY + this._terminal.rows; y++) {
// If the current line is wrapped line, increase index of column to ignore the previous scan
// Otherwise, reset beginning column index to zero with set new unwrapped line index
result = this._findInLine(term, searchPosition, searchOptions);
result = this._findInLine(term, y, 0, searchOptions);
if (result) {
break;
}
@@ -101,18 +90,13 @@ export class SearchAddon implements ITerminalAddon {
// If we hit the bottom and didn't search from the very top wrap back up
if (!result && startRow !== 0) {
for (let y = 0; y < startRow; y++) {
searchPosition.startRow = y;
searchPosition.startCol = 0;
result = this._findInLine(term, searchPosition, searchOptions);
result = this._findInLine(term, y, 0, searchOptions);
if (result) {
break;
}
}
}
// If there is only one result, return true.
if (!result && currentSelection) return true;
// Set selection and scroll if a result was found
return this._selectResult(result);
}
@@ -135,58 +119,48 @@ export class SearchAddon implements ITerminalAddon {
}
const isReverseSearch = true;
let startRow = this._terminal.buffer.active.baseY + this._terminal.rows;
let startRow = this._terminal.buffer.baseY + this._terminal.rows;
let startCol = this._terminal.cols;
let result: ISearchResult | undefined;
let result: ISearchResult | undefined = undefined;
const incremental = searchOptions ? searchOptions.incremental : false;
let currentSelection: ISelectionPosition | undefined;
if (this._terminal.hasSelection()) {
currentSelection = this._terminal.getSelectionPosition()!;
const currentSelection = this._terminal.getSelectionPosition()!;
// Start from selection start if there is a selection
startRow = currentSelection.startRow;
startCol = currentSelection.startColumn;
}
this._initLinesCache();
const searchPosition: ISearchPosition = {
startRow,
startCol
};
if (incremental) {
result = this._findInLine(term, searchPosition, searchOptions, false);
result = this._findInLine(term, startRow, startCol, searchOptions, false);
if (!(result && result.row === startRow && result.col === startCol)) {
result = this._findInLine(term, searchPosition, searchOptions, true);
result = this._findInLine(term, startRow, startCol, searchOptions, true);
}
} else {
result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch);
result = this._findInLine(term, startRow, startCol, searchOptions, isReverseSearch);
}
// Search from startRow - 1 to top
if (!result) {
searchPosition.startCol = Math.max(searchPosition.startCol, this._terminal.cols);
startCol = this._terminal.cols;
for (let y = startRow - 1; y >= 0; y--) {
searchPosition.startRow = y;
result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch);
result = this._findInLine(term, y, startCol, searchOptions, isReverseSearch);
if (result) {
break;
}
}
}
// If we hit the top and didn't search from the very bottom wrap back down
if (!result && startRow !== (this._terminal.buffer.active.baseY + this._terminal.rows)) {
for (let y = (this._terminal.buffer.active.baseY + this._terminal.rows); y > startRow; y--) {
searchPosition.startRow = y;
result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch);
if (!result && startRow !== (this._terminal.buffer.baseY + this._terminal.rows)) {
for (let y = (this._terminal.buffer.baseY + this._terminal.rows); y > startRow; y--) {
result = this._findInLine(term, y, startCol, searchOptions, isReverseSearch);
if (result) {
break;
}
}
}
// If there is only one result, return true.
if (!result && currentSelection) return true;
// Set selection and scroll if a result was found
return this._selectResult(result);
}
@@ -197,7 +171,7 @@ export class SearchAddon implements ITerminalAddon {
private _initLinesCache(): void {
const terminal = this._terminal!;
if (!this._linesCache) {
this._linesCache = new Array(terminal.buffer.active.length);
this._linesCache = new Array(terminal.buffer.length);
this._cursorMoveListener = terminal.onCursorMove(() => this._destroyLinesCache());
this._resizeListener = terminal.onResize(() => this._destroyLinesCache());
}
@@ -239,28 +213,18 @@ export class SearchAddon implements ITerminalAddon {
* started on an earlier line then it is skipped since it will be properly searched when the terminal line that the
* text starts on is searched.
* @param term The search term.
* @param position The position to start the search.
* @param row The line to start the search from.
* @param col The column to start the search from.
* @param searchOptions Search options.
* @return The search result if it was found.
*/
protected _findInLine(term: string, searchPosition: ISearchPosition, searchOptions: ISearchOptions = {}, isReverseSearch: boolean = false): ISearchResult | undefined {
protected _findInLine(term: string, row: number, col: number, searchOptions: ISearchOptions = {}, isReverseSearch: boolean = false): ISearchResult | undefined {
const terminal = this._terminal!;
let row = searchPosition.startRow;
const col = searchPosition.startCol;
// Ignore wrapped lines, only consider on unwrapped line (first row of command string).
const firstLine = terminal.buffer.active.getLine(row);
const firstLine = terminal.buffer.getLine(row);
if (firstLine && firstLine.isWrapped) {
if (isReverseSearch) {
searchPosition.startCol += terminal.cols;
return;
}
// This will iterate until we find the line start.
// When we find it, we will search using the calculated start column.
searchPosition.startRow--;
searchPosition.startCol += terminal.cols;
return this._findInLine(term, searchPosition, searchOptions);
return;
}
let stringLine = this._linesCache ? this._linesCache[row] : void 0;
if (stringLine === void 0) {
@@ -311,7 +275,7 @@ export class SearchAddon implements ITerminalAddon {
return;
}
const line = terminal.buffer.active.getLine(row);
const line = terminal.buffer.getLine(row);
if (line) {
for (let i = 0; i < resultIndex; i++) {
@@ -320,13 +284,13 @@ export class SearchAddon implements ITerminalAddon {
break;
}
// Adjust the searchIndex to normalize emoji into single chars
const char = cell.getChars();
const char = cell.char;
if (char.length > 1) {
resultIndex -= char.length - 1;
}
// Adjust the searchIndex for empty characters following wide unicode
// chars (eg. CJK)
const charWidth = cell.getWidth();
const charWidth = cell.width;
if (charWidth === 0) {
resultIndex++;
}
@@ -354,9 +318,9 @@ export class SearchAddon implements ITerminalAddon {
let lineWrapsToNext: boolean;
do {
const nextLine = terminal.buffer.active.getLine(lineIndex + 1);
const nextLine = terminal.buffer.getLine(lineIndex + 1);
lineWrapsToNext = nextLine ? nextLine.isWrapped : false;
const line = terminal.buffer.active.getLine(lineIndex);
const line = terminal.buffer.getLine(lineIndex);
if (!line) {
break;
}
@@ -380,8 +344,8 @@ export class SearchAddon implements ITerminalAddon {
}
terminal.select(result.col, result.row, result.term.length);
// If it is not in the viewport then we scroll else it just gets selected
if (result.row >= (terminal.buffer.active.viewportY + terminal.rows) || result.row < terminal.buffer.active.viewportY) {
let scroll = result.row - terminal.buffer.active.viewportY;
if (result.row > (terminal.buffer.viewportY + terminal.rows) || result.row < terminal.buffer.viewportY) {
let scroll = result.row - terminal.buffer.viewportY;
scroll = scroll - Math.floor(terminal.rows / 2);
terminal.scrollLines(scroll);
}
+1 -5
View File
@@ -10,11 +10,7 @@
"outDir": "../out",
"sourceMap": true,
"removeComments": true,
"strict": true,
"types": [
"../../../node_modules/@types/mocha",
"../../../out-test/api/TestUtils"
]
"strict": true
},
"include": [
"./**/*",
+2 -2
View File
@@ -17,7 +17,7 @@ declare module 'xterm-addon-search' {
/**
* Whether to search for a whole word, the result is only valid if it's
* surrounded in "non-word" characters such as `_`, `(`, `)` or space.
* suppounded in "non-word" characters such as `_`, `(`, `)` or space.
*/
wholeWord?: boolean;
@@ -27,7 +27,7 @@ declare module 'xterm-addon-search' {
caseSensitive?: boolean;
/**
* Whether to do an incremental search, this will expand the selection if it
* Whether to do an indcremental search, this will expand the selection if it
* still matches the term the user typed. Note that this only affects
* `findNext`, not `findPrevious`.
*/
-3
View File
@@ -1,3 +0,0 @@
lib
node_modules
out-benchmark
-5
View File
@@ -1,5 +0,0 @@
**/*.api.js
**/*.api.ts
tsconfig.json
.yarnrc
webpack.config.js
-42
View File
@@ -1,42 +0,0 @@
## 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
```
@@ -1,65 +0,0 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
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 'SerializeAddon';
class TestTerminal extends Terminal {
writeSync(data: string): void {
(this as any)._core.writeSync(data);
}
}
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;
const serializeAddon = new SerializeAddon();
before(() => {
terminal = new TestTerminal({ cols: 80, rows: 25, scrollback: 5000 });
serializeAddon.activate(terminal);
terminal.writeSync(content);
});
new ThroughputRuntimeCase('', () => {
return { payloadSize: serializeAddon.serialize().length };
}, { fork: false }).showAverageThroughput();
});
});
@@ -1,19 +0,0 @@
{
"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"
]
}
}
@@ -1,25 +0,0 @@
{
"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" }
]
}
-23
View File
@@ -1,23 +0,0 @@
{
"name": "xterm-addon-serialize",
"version": "0.2.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",
"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": "^4.0.0"
}
}
@@ -1,340 +0,0 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { assert } from 'chai';
import { openTerminal, writeSync, getBrowserType } from '../../../out-test/api/TestUtils';
import { Browser, Page } from 'playwright-core';
const APP = 'http://127.0.0.1:3000/test';
let browser: Browser;
let page: Page;
const width = 800;
const height = 600;
describe('SerializeAddon', () => {
before(async function(): Promise<any> {
const browserType = getBrowserType();
browser = await browserType.launch({ dumpio: true,
headless: process.argv.indexOf('--headless') !== -1
});
page = await (await browser.newContext()).newPage();
await page.setViewportSize({ width, height });
await page.goto(APP);
await openTerminal(page, { rows: 10, cols: 10, rendererType: 'dom' });
await page.evaluate(`
window.serializeAddon = new SerializeAddon();
window.term.loadAddon(window.serializeAddon);
`);
});
after(async () => await browser.close());
beforeEach(async () => await page.evaluate(`window.term.reset()`));
it('empty content', async function(): Promise<any> {
const rows = 10;
const cols = 10;
assert.equal(await page.evaluate(`serializeAddon.serialize();`), '');
});
it('trim last empty lines', async function(): Promise<any> {
const cols = 10;
const lines = [
'',
'',
digitsString(cols),
digitsString(cols),
'',
'',
digitsString(cols),
digitsString(cols),
'',
'',
''
];
await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.slice(0, 8).join('\r\n'));
});
it('digits content', async function(): Promise<any> {
const rows = 10;
const cols = 10;
const digitsLine = digitsString(cols);
const lines = newArray<string>(digitsLine, rows);
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<any> {
const rows = 10;
const halfRows = rows >> 1;
const cols = 10;
const lines = newArray<string>((index: number) => digitsString(cols, index), rows);
await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize(${halfRows});`), lines.slice(halfRows, 2 * halfRows).join('\r\n'));
});
it('serialize 0 rows of content', async function(): Promise<any> {
const rows = 10;
const cols = 10;
const lines = newArray<string>((index: number) => digitsString(cols, index), rows);
await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize(0);`), '');
});
it('serialize all rows of content with color16', async function(): Promise<any> {
const cols = 10;
const color16 = [
30, 31, 32, 33, 34, 35, 36, 37, // Set foreground color
90, 91, 92, 93, 94, 95, 96, 97,
40, 41, 42, 43, 44, 45, 46, 47, // Set background color
100, 101, 103, 104, 105, 106, 107
];
const rows = color16.length;
const lines = newArray<string>(
(index: number) => digitsString(cols, index, `\x1b[${color16[index % color16.length]}m`),
rows
);
await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
});
it('serialize all rows of content with fg/bg flags', async function(): Promise<any> {
const cols = 10;
const line = '+'.repeat(cols);
const lines: string[] = [
mkSGR(FG_P16_GREEN) + line, // Workaround: If we clear all flags a the end, serialize will use \x1b[0m to clear instead of the sepcific disable sequence
mkSGR(INVERSE) + line,
mkSGR(BOLD) + line,
mkSGR(UNDERLINED) + line,
mkSGR(BLINK) + line,
mkSGR(INVISIBLE) + line,
mkSGR(NO_INVERSE) + line,
mkSGR(NO_BOLD) + line,
mkSGR(NO_UNDERLINED) + line,
mkSGR(NO_BLINK) + line,
mkSGR(NO_INVISIBLE) + line
];
const rows = lines.length;
await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
});
it('serialize all rows of content with color256', async function(): Promise<any> {
const rows = 32;
const cols = 10;
const lines = newArray<string>(
(index: number) => digitsString(cols, index, `\x1b[38;5;${16 + index}m`),
rows
);
await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
});
it('serialize all rows of content with color16 and style separately', async function(): Promise<any> {
const cols = 10;
const line = '+'.repeat(cols);
const lines: string[] = [
mkSGR(FG_P16_RED) + line, // fg Red,
mkSGR(UNDERLINED) + line, // fg Red, Underlined
mkSGR(FG_P16_GREEN) + line, // fg Green, Underlined
mkSGR(INVERSE) + line, // fg Green, Underlined, Inverse
mkSGR(NO_INVERSE) + line, // fg Green, Underlined
mkSGR(INVERSE) + line, // fg Green, Underlined, Inverse
mkSGR(BG_P16_YELLOW) + line, // fg Green, bg Yellow, Underlined, Inverse
mkSGR(FG_RESET) + line, // bg Yellow, Underlined, Inverse
mkSGR(BG_RESET) + line, // Underlined, Inverse
mkSGR(NORMAL) + line // Back to normal
];
await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
});
it('serialize all rows of content with color16 and style together', async function(): Promise<any> {
const cols = 10;
const line = '+'.repeat(cols);
const lines: string[] = [
mkSGR(FG_P16_RED) + line, // fg Red
mkSGR(FG_P16_GREEN, BG_P16_YELLOW) + line, // fg Green, bg Yellow
mkSGR(UNDERLINED, ITALIC) + line, // fg Green, bg Yellow, Underlined, Italic
mkSGR(NO_UNDERLINED, NO_ITALIC) + line, // fg Green, bg Yellow
mkSGR(FG_RESET, ITALIC) + line, // bg Yellow, Italic
mkSGR(BG_RESET) + line, // Italic
mkSGR(NORMAL) + line, // Back to normal
mkSGR(FG_P16_RED) + line, // fg Red
mkSGR(FG_P16_GREEN, BG_P16_YELLOW) + line, // fg Green, bg Yellow
mkSGR(UNDERLINED, ITALIC) + line, // fg Green, bg Yellow, Underlined, Italic
mkSGR(NO_UNDERLINED, NO_ITALIC) + line, // fg Green, bg Yellow
mkSGR(FG_RESET, ITALIC) + line, // bg Yellow, Italic
mkSGR(BG_RESET) + line // Italic
];
await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
});
it('serialize all rows of content with color256 and style separately', async function(): Promise<any> {
const cols = 10;
const line = '+'.repeat(cols);
const lines: string[] = [
mkSGR(FG_P256_RED) + line, // fg Red 256,
mkSGR(UNDERLINED) + line, // fg Red 256, Underlined
mkSGR(FG_P256_GREEN) + line, // fg Green 256, Underlined
mkSGR(INVERSE) + line, // fg Green 256, Underlined, Inverse
mkSGR(NO_INVERSE) + line, // fg Green 256, Underlined
mkSGR(INVERSE) + line, // fg Green 256, Underlined, Inverse
mkSGR(BG_P256_YELLOW) + line, // fg Green 256, bg Yellow 256, Underlined, Inverse
mkSGR(FG_RESET) + line, // bg Yellow 256, Underlined, Inverse
mkSGR(BG_RESET) + line, // Underlined, Inverse
mkSGR(NORMAL) + line // Back to normal
];
await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
});
it('serialize all rows of content with color256 and style together', async function(): Promise<any> {
const cols = 10;
const line = '+'.repeat(cols);
const lines: string[] = [
mkSGR(FG_P256_RED) + line, // fg Red 256
mkSGR(FG_P256_GREEN, BG_P256_YELLOW) + line, // fg Green 256, bg Yellow 256
mkSGR(UNDERLINED, ITALIC) + line, // fg Green 256, bg Yellow 256, Underlined, Italic
mkSGR(NO_UNDERLINED, NO_ITALIC) + line, // fg Green 256, bg Yellow 256
mkSGR(FG_RESET, ITALIC) + line, // bg Yellow 256, Italic
mkSGR(BG_RESET) + line, // Italic
mkSGR(NORMAL) + line, // Back to normal
mkSGR(FG_P256_RED) + line, // fg Red 256
mkSGR(FG_P256_GREEN, BG_P256_YELLOW) + line, // fg Green 256, bg Yellow 256
mkSGR(UNDERLINED, ITALIC) + line, // fg Green 256, bg Yellow 256, Underlined, Italic
mkSGR(NO_UNDERLINED, NO_ITALIC) + line, // fg Green 256, bg Yellow 256
mkSGR(FG_RESET, ITALIC) + line, // bg Yellow 256, Italic
mkSGR(BG_RESET) + line // Italic
];
await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
});
it('serialize all rows of content with colorRGB and style separately', async function(): Promise<any> {
const cols = 10;
const line = '+'.repeat(cols);
const lines: string[] = [
mkSGR(FG_RGB_RED) + line, // fg Red RGB,
mkSGR(UNDERLINED) + line, // fg Red RGB, Underlined
mkSGR(FG_RGB_GREEN) + line, // fg Green RGB, Underlined
mkSGR(INVERSE) + line, // fg Green RGB, Underlined, Inverse
mkSGR(NO_INVERSE) + line, // fg Green RGB, Underlined
mkSGR(INVERSE) + line, // fg Green RGB, Underlined, Inverse
mkSGR(BG_RGB_YELLOW) + line, // fg Green RGB, bg Yellow RGB, Underlined, Inverse
mkSGR(FG_RESET) + line, // bg Yellow RGB, Underlined, Inverse
mkSGR(BG_RESET) + line, // Underlined, Inverse
mkSGR(NORMAL) + line // Back to normal
];
await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
});
it('serialize all rows of content with colorRGB and style together', async function(): Promise<any> {
const cols = 10;
const line = '+'.repeat(cols);
const lines: string[] = [
mkSGR(FG_RGB_RED) + line, // fg Red RGB
mkSGR(FG_RGB_GREEN, BG_RGB_YELLOW) + line, // fg Green RGB, bg Yellow RGB
mkSGR(UNDERLINED, ITALIC) + line, // fg Green RGB, bg Yellow RGB, Underlined, Italic
mkSGR(NO_UNDERLINED, NO_ITALIC) + line, // fg Green RGB, bg Yellow RGB
mkSGR(FG_RESET, ITALIC) + line, // bg Yellow RGB, Italic
mkSGR(BG_RESET) + line, // Italic
mkSGR(NORMAL) + line, // Back to normal
mkSGR(FG_RGB_RED) + line, // fg Red RGB
mkSGR(FG_RGB_GREEN, BG_RGB_YELLOW) + line, // fg Green RGB, bg Yellow RGB
mkSGR(UNDERLINED, ITALIC) + line, // fg Green RGB, bg Yellow RGB, Underlined, Italic
mkSGR(NO_UNDERLINED, NO_ITALIC) + line, // fg Green RGB, bg Yellow RGB
mkSGR(FG_RESET, ITALIC) + line, // bg Yellow RGB, Italic
mkSGR(BG_RESET) + line // Italic
];
await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
});
it('serialize tabs correctly', async () => {
const lines = [
'a\tb',
'aa\tc',
'aaa\td'
];
const expected = [
'a\x1b[7Cb',
'aa\x1b[6Cc',
'aaa\x1b[5Cd'
];
await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize();`), expected.join('\r\n'));
});
});
function newArray<T>(initial: T | ((index: number) => T), count: number): T[] {
const array: T[] = new Array<T>(count);
for (let i = 0; i < array.length; i++) {
if (typeof initial === 'function') {
array[i] = (<(index: number) => T>initial)(i);
} else {
array[i] = <T>initial;
}
}
return array;
}
function digitsString(length: number, from: number = 0, sgr: string = ''): string {
let s = sgr;
for (let i = 0; i < length; i++) {
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;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';
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;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';
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';
@@ -1,187 +0,0 @@
/**
* 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 constrain(value: number, low: number, high: number): number {
return Math.max(low, Math.min(value, high));
}
// TODO: Refine this template class later
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();
let oldCell = cell1;
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 c = line.getCell(col, oldCell === cell1 ? cell2 : cell1);
if (!c) {
console.warn(`Can't get cell at row=${row}, col=${col}`);
continue;
}
this._nextCell(c, oldCell, row, col);
oldCell = c;
}
}
this._rowEnd(row);
}
this._afterSerialize();
return this._serializeString();
}
protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
protected _rowEnd(row: number): void { }
protected _beforeSerialize(rows: number): void { }
protected _afterSerialize(): void { }
protected _serializeString(): 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<string>();
private _currentRow: string = '';
private _nullCellCount: number = 0;
constructor(buffer: IBuffer) {
super(buffer);
}
protected _beforeSerialize(rows: number): void {
this._allRows = new Array<string>(rows);
}
protected _rowEnd(row: number): void {
this._allRows[this._rowIndex++] = this._currentRow;
this._currentRow = '';
this._nullCellCount = 0;
}
protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
const sgrSeq: number[] = [];
const fgChanged = !equalFg(cell, oldCell);
const bgChanged = !equalBg(cell, oldCell);
const flagsChanged = !equalFlags(cell, 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.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.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) {
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.getChars() === '') {
this._nullCellCount++;
} else if (this._nullCellCount > 0) {
this._currentRow += `\x1b[${this._nullCellCount}C`;
this._nullCellCount = 0;
}
this._currentRow += cell.getChars();
}
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');
}
}
export class SerializeAddon implements ITerminalAddon {
private _terminal: Terminal | undefined;
constructor() { }
public activate(terminal: Terminal): void {
this._terminal = terminal;
}
public serialize(rows?: number): string {
// 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.buffer.active.length;
const handler = new StringSerializeHandler(this._terminal.buffer.active);
rows = (rows === undefined) ? maxRows : constrain(rows, 0, maxRows);
return handler.serialize(maxRows - rows, maxRows);
}
public dispose(): void { }
}
@@ -1,34 +0,0 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"lib": [
"dom",
"es2015"
],
"rootDir": ".",
"outDir": "../out",
"sourceMap": true,
"removeComments": true,
"baseUrl": ".",
"paths": {
"common/*": [
"../../../src/common/*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/mocha",
"../../../out-test/api/TestUtils"
]
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
],
"references": [
{
"path": "../../../src/common"
}
]
}
@@ -1,38 +0,0 @@
/**
* 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;
/**
* 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 bottom of the
* terminal. This defaults to the number of rows in the viewport.
*/
public serialize(rows?: number): string;
/**
* Disposes the addon.
*/
public dispose(): void;
}
}
@@ -1,31 +0,0 @@
/**
* 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'
};
-2
View File
@@ -1,2 +0,0 @@
lib
node_modules
-5
View File
@@ -1,5 +0,0 @@
**/*.api.js
**/*.api.ts
tsconfig.json
.yarnrc
webpack.config.js
-19
View File
@@ -1,19 +0,0 @@
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.
-23
View File
@@ -1,23 +0,0 @@
## 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);
// activate the new version
terminal.unicode.activeVersion = '11';
```
-21
View File
@@ -1,21 +0,0 @@
{
"name": "xterm-addon-unicode11",
"version": "0.1.1",
"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"
}
}
@@ -1,49 +0,0 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { assert } from 'chai';
import { openTerminal, getBrowserType } from '../../../out-test/api/TestUtils';
import { Browser, Page } from 'playwright-core';
const APP = 'http://127.0.0.1:3000/test';
let browser: Browser;
let page: Page;
const width = 800;
const height = 600;
describe('Unicode11Addon', () => {
before(async function(): Promise<any> {
const browserType = getBrowserType();
browser = await browserType.launch({ dumpio: true,
headless: process.argv.indexOf('--headless') !== -1
});
page = await (await browser.newContext()).newPage();
await page.setViewportSize({ width, height });
});
after(async () => {
await browser.close();
});
beforeEach(async function(): Promise<any> {
await page.goto(APP);
await openTerminal(page);
});
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);
});
});
@@ -1,16 +0,0 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*
* UnicodeVersionProvider for V11.
*/
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 { }
}
@@ -1,222 +0,0 @@
/**
* 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 readonly 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;
}
}
@@ -1,34 +0,0 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es2015",
"lib": [
"dom",
"es2015"
],
"rootDir": ".",
"outDir": "../out",
"sourceMap": true,
"removeComments": true,
"strict": true,
"baseUrl": ".",
"paths": {
"common/*": [
"../../../src/common/*"
]
},
"types": [
"../../../node_modules/@types/mocha",
"../../../out-test/api/TestUtils"
]
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
],
"references": [
{
"path": "../../../src/common"
}
]
}
@@ -1,14 +0,0 @@
/**
* 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;
}
}
@@ -1,38 +0,0 @@
/**
* 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/
}
]
},
resolve: {
modules: ['./node_modules'],
extensions: [ '.js' ],
alias: {
common: path.resolve('../../out/common')
}
},
output: {
filename: mainFile,
path: path.resolve('./lib'),
library: addonName,
libraryTarget: 'umd'
},
mode: 'production'
};
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-web-links",
"version": "0.3.0",
"version": "0.2.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
@@ -16,6 +16,6 @@
"prepublishOnly": "npm run package"
},
"peerDependencies": {
"xterm": "^4.0.0"
"xterm": "^3.14.0"
}
}
@@ -1,114 +0,0 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { ILinkProvider, IBufferCellPosition, ILink, Terminal, IBuffer } from 'xterm';
export class WebLinkProvider implements ILinkProvider {
constructor(
private readonly _terminal: Terminal,
private readonly _regex: RegExp,
private readonly _handler: (event: MouseEvent, uri: string) => void
) {
}
provideLink(position: IBufferCellPosition, callback: (link: ILink | undefined) => void): void {
callback(LinkComputer.computeLink(position, this._regex, this._terminal, this._handler));
}
}
export class LinkComputer {
public static computeLink(position: IBufferCellPosition, regex: RegExp, terminal: Terminal, handler: (event: MouseEvent, uri: string) => void): ILink | undefined {
const rex = new RegExp(regex.source, (regex.flags || '') + 'g');
const [line, startLineIndex] = LinkComputer._translateBufferLineToStringWithWrap(position.y - 1, false, terminal);
let match;
let stringIndex = -1;
while ((match = rex.exec(line)) !== null) {
const text = match[1];
if (!text) {
// something matched but does not comply with the given matchIndex
// since this is most likely a bug the regex itself we simply do nothing here
console.log('match found without corresponding matchIndex');
break;
}
// Get index, match.index is for the outer match which includes negated chars
// therefore we cannot use match.index directly, instead we search the position
// of the match group in text again
// also correct regex and string search offsets for the next loop run
stringIndex = line.indexOf(text, stringIndex + 1);
rex.lastIndex = stringIndex + text.length;
if (stringIndex < 0) {
// invalid stringIndex (should not have happened)
break;
}
let endX = stringIndex + text.length;
let endY = startLineIndex + 1;
while (endX > terminal.cols) {
endX -= terminal.cols;
endY++;
}
const range = {
start: {
x: stringIndex + 1,
y: startLineIndex + 1
},
end: {
x: endX,
y: endY
}
};
return { range, text, activate: handler };
}
}
/**
* Gets the entire line for the buffer line
* @param line The line being translated.
* @param trimRight Whether to trim whitespace to the right.
* @param terminal The terminal
*/
private static _translateBufferLineToStringWithWrap(lineIndex: number, trimRight: boolean, terminal: Terminal): [string, number] {
let lineString = '';
let lineWrapsToNext: boolean;
let prevLinesToWrap: boolean;
do {
const line = terminal.buffer.active.getLine(lineIndex);
if (!line) {
break;
}
if (line.isWrapped) {
lineIndex--;
}
prevLinesToWrap = line.isWrapped;
} while (prevLinesToWrap);
const startLineIndex = lineIndex;
do {
const nextLine = terminal.buffer.active.getLine(lineIndex + 1);
lineWrapsToNext = nextLine ? nextLine.isWrapped : false;
const line = terminal.buffer.active.getLine(lineIndex);
if (!line) {
break;
}
lineString += line.translateToString(!lineWrapsToNext && trimRight).substring(0, terminal.cols);
lineIndex++;
} while (lineWrapsToNext);
return [lineString, startLineIndex];
}
}
@@ -3,67 +3,87 @@
* @license MIT
*/
import * as puppeteer from 'puppeteer';
import { assert } from 'chai';
import { openTerminal, pollFor, writeSync, getBrowserType } from '../../../out-test/api/TestUtils';
import { Browser, Page } from 'playwright-core';
import { ITerminalOptions } from 'xterm';
const APP = 'http://127.0.0.1:3000/test';
let browser: Browser;
let page: Page;
let browser: puppeteer.Browser;
let page: puppeteer.Page;
const width = 800;
const height = 600;
describe('WebLinksAddon', () => {
before(async function(): Promise<any> {
const browserType = getBrowserType();
browser = await browserType.launch({ dumpio: true,
headless: process.argv.indexOf('--headless') !== -1
this.timeout(10000);
browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1,
slowMo: 80,
args: [`--window-size=${width},${height}`]
});
page = await (await browser.newContext()).newPage();
await page.setViewportSize({ width, height });
page = (await browser.pages())[0];
await page.setViewport({ width, height });
});
after(async () => await browser.close());
beforeEach(async () => await page.goto(APP));
after(async () => {
await browser.close();
});
beforeEach(async function(): Promise<any> {
this.timeout(5000);
await page.goto(APP);
});
it('.com', async function(): Promise<any> {
this.timeout(20000);
await testHostName('foo.com');
});
it('.com.au', async function(): Promise<any> {
this.timeout(20000);
await testHostName('foo.com.au');
});
it('.io', async function(): Promise<any> {
this.timeout(20000);
await testHostName('foo.io');
});
});
async function testHostName(hostname: string): Promise<void> {
await openTerminal(page, { rendererType: 'dom', cols: 40 });
await openTerminal({ rendererType: 'dom' });
await page.evaluate(`window.term.loadAddon(new window.WebLinksAddon())`);
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`);
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`);
await page.evaluate(`
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');
`);
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`);
}
async function pollForLinkAtCell(col: number, row: number, value: string): Promise<void> {
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);
async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
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');
}
}
async function getLinkAtCell(col: number, row: number): Promise<string> {
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, '');`);
}
@@ -1,10 +1,9 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { Terminal, ILinkMatcherOptions, ITerminalAddon, ILinkProvider, IDisposable } from 'xterm';
import { WebLinkProvider } from './WebLinkProvider';
import { Terminal, ILinkMatcherOptions, ITerminalAddon } from 'xterm';
const protocolClause = '(https?:\\/\\/)';
const domainCharacterSet = '[\\da-z\\.-]+';
@@ -27,44 +26,28 @@ const end = ')($|' + negatedPathCharacterSet + ')';
const strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end);
function handleLink(event: MouseEvent, uri: string): void {
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');
}
window.open(uri, '_blank');
}
export class WebLinksAddon implements ITerminalAddon {
private _linkMatcherId: number | undefined;
private _terminal: Terminal | undefined;
private _linkProvider: IDisposable | undefined;
constructor(
private _handler: (event: MouseEvent, uri: string) => void = handleLink,
private _options: ILinkMatcherOptions = {},
private _useLinkProvider: boolean = false
private _options: ILinkMatcherOptions = {}
) {
this._options.matchIndex = 1;
}
public activate(terminal: Terminal): void {
this._terminal = terminal;
if (this._useLinkProvider && 'registerLinkProvider' in this._terminal) {
this._linkProvider = this._terminal.registerLinkProvider(new WebLinkProvider(this._terminal, strictUrlRegex, this._handler));
} else {
// TODO: This should be removed eventually
this._linkMatcherId = (this._terminal as Terminal).registerLinkMatcher(strictUrlRegex, this._handler, this._options);
}
this._linkMatcherId = this._terminal.registerLinkMatcher(strictUrlRegex, this._handler, this._options);
}
public dispose(): void {
if (this._linkMatcherId !== undefined && this._terminal !== undefined) {
this._terminal.deregisterLinkMatcher(this._linkMatcherId);
}
this._linkProvider?.dispose();
}
}
@@ -10,11 +10,7 @@
"outDir": "../out",
"sourceMap": true,
"removeComments": true,
"strict": true,
"types": [
"../../../node_modules/@types/mocha",
"../../../out-test/api/TestUtils"
]
"strict": true
},
"include": [
"./**/*",
@@ -15,12 +15,8 @@ declare module 'xterm-addon-web-links' {
* Creates a new web links addon.
* @param handler The callback when the link is called.
* @param options Options for the link matcher.
* @param useLinkProvider Whether to use the new link provider API to create
* the links. This is an option because use of both link matcher (old) and
* link provider (new) may cause issues. Link provider will eventually be
* the default and only option.
*/
constructor(handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions, useLinkProvider?: boolean);
constructor(handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions);
/**
* Activates the addon
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-webgl",
"version": "0.6.0",
"version": "0.2.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
@@ -16,6 +16,6 @@
"prepublishOnly": "npm run package"
},
"peerDependencies": {
"xterm": "^4.0.0"
"xterm": "^3.14.0"
}
}
@@ -0,0 +1,28 @@
/**
* 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;
}
@@ -0,0 +1,14 @@
/**
* @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;
}
+15
View File
@@ -0,0 +1,15 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @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
}
+34 -53
View File
@@ -6,14 +6,15 @@
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, RENDER_MODEL_BG_OFFSET } from './RenderModel';
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, Attributes, FgFlags } from 'common/buffer/Constants';
import { NULL_CELL_CODE, WHITESPACE_CELL_CODE } from 'common/buffer/Constants';
import { getLuminance } from './ColorUtils';
import { Terminal, IBufferLine } from 'xterm';
import { IColorSet, IColor } from 'browser/Types';
import { IColorSet } from 'browser/Types';
import { IRenderDimensions } from 'browser/renderer/Types';
import { AttributeData } from 'common/buffer/AttributeData';
interface IVertices {
attributes: Float32Array;
@@ -102,7 +103,11 @@ export class GlyphRenderer {
private _dimensions: IRenderDimensions
) {
const gl = this._gl;
const program = throwIfFalsy(createProgram(gl, vertexShaderSource, fragmentShaderSource));
if (program === undefined) {
throw new Error('Could not create WebGL program');
}
this._program = program;
// Uniform locations
@@ -167,11 +172,11 @@ export class GlyphRenderer {
return this._atlas ? this._atlas.beginFrame() : true;
}
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);
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);
}
private _updateCell(array: Float32Array, x: number, y: number, code: number | undefined, bg: number, fg: number, chars?: string): void {
private _updateCell(array: Float32Array, x: number, y: number, code: number | undefined, attr: number, bg: number, fg: number, chars?: string): void {
const terminal = this._terminal;
const i = (y * terminal.cols + x) * INDICES_PER_CELL;
@@ -184,12 +189,12 @@ export class GlyphRenderer {
let rasterizedGlyph: IRasterizedGlyph;
if (!this._atlas) {
return;
throw new Error('atlas must be set before updating cell');
}
if (chars && chars.length > 1) {
rasterizedGlyph = this._atlas.getRasterizedGlyphCombinedChar(chars, bg, fg);
rasterizedGlyph = this._atlas.getRasterizedGlyphCombinedChar(chars, attr, bg, fg);
} else {
rasterizedGlyph = this._atlas.getRasterizedGlyph(code, bg, fg);
rasterizedGlyph = this._atlas.getRasterizedGlyph(code, attr, bg, fg);
}
// Fill empty if no glyph was found
@@ -218,86 +223,62 @@ export class GlyphRenderer {
this._vertices.selectionAttributes = slice(this._vertices.attributes, 0);
const bg = (this._colors.selectionOpaque.rgba >>> 8) | Attributes.CM_RGB;
// 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;
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);
this._updateSelectionRange(startCol, startCol + width, y, model, bg, fg);
}
} 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);
this._updateSelectionRange(startCol, startRowEndCol, model.selection.viewportCappedStartRow, model, bg, fg);
// 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);
this._updateSelectionRange(0, startRowEndCol, y, model, bg, fg);
}
// 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);
this._updateSelectionRange(0, endCol, model.selection.viewportCappedEndRow, model, bg, fg);
}
}
}
private _updateSelectionRange(startCol: number, endCol: number, y: number, model: IRenderModel, bg: number): void {
private _updateSelectionRange(startCol: number, endCol: number, y: number, model: IRenderModel, bg: number, fg: number): void {
const terminal = this._terminal;
const row = y + terminal.buffer.active.viewportY;
const row = y + terminal.buffer.viewportY;
let line: IBufferLine | undefined;
for (let x = startCol; x < endCol; x++) {
const offset = (y * this._terminal.cols + x) * RENDER_MODEL_INDICIES_PER_CELL;
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];
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.active.getLine(row);
line = terminal.buffer.getLine(row);
}
const chars = line!.getCell(x)!.getChars();
this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, fg, chars);
const chars = line!.getCell(x)!.char;
this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], attr, bg, fg, chars);
} else {
this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, fg);
this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], attr, 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;
@@ -6,11 +6,12 @@
import { createProgram, expandFloat32Array, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils';
import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelectionRenderModel } from './Types';
import { fill } from 'common/TypedArrayUtils';
import { Attributes, FgFlags } from 'common/buffer/Constants';
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
import { is256Color } from './atlas/CharAtlasUtils';
import { DEFAULT_COLOR } from 'common/buffer/Constants';
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';
const enum VertexAttribLocations {
POSITION = 0,
@@ -22,13 +23,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 vec4 a_color;
layout (location = ${VertexAttribLocations.COLOR}) in vec3 a_color;
layout (location = ${VertexAttribLocations.UNIT_QUAD}) in vec2 a_unitquad;
uniform mat4 u_projection;
uniform vec2 u_resolution;
out vec4 v_color;
out vec3 v_color;
void main() {
vec2 zeroToOne = (a_position + (a_unitquad * a_size)) / u_resolution;
@@ -39,12 +40,12 @@ void main() {
const fragmentShaderSource = `#version 300 es
precision lowp float;
in vec4 v_color;
in vec3 v_color;
out vec4 outColor;
void main() {
outColor = v_color;
outColor = vec4(v_color, 1);
}`;
interface IVertices {
@@ -155,7 +156,7 @@ export class RectangleRenderer {
private _updateCachedColors(): void {
this._bgFloat = this._colorToFloat32Array(this._colors.background);
this._selectionFloat = this._colorToFloat32Array(this._colors.selectionOpaque);
this._selectionFloat = this._colorToFloat32Array(this._colors.selection);
}
private _updateViewportRectangle(): void {
@@ -246,73 +247,47 @@ export class RectangleRenderer {
for (let y = 0; y < terminal.rows; y++) {
let currentStartX = -1;
let currentBg = 0;
let currentFg = 0;
let currentInverse = false;
let currentBg = DEFAULT_COLOR;
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];
const inverse = !!(fg & FgFlags.INVERSE);
if (bg !== currentBg || (fg !== currentFg && (currentInverse || inverse))) {
const modelIndex = ((y * terminal.cols) + x) * 4;
const bg = model.cells[modelIndex + 2];
if (bg !== currentBg) {
// A rectangle needs to be drawn if going from non-default to another color
if (currentBg !== 0 || (currentInverse && currentFg !== 0)) {
if (currentBg !== DEFAULT_COLOR) {
const offset = rectangleCount++ * INDICES_PER_RECTANGLE;
this._updateRectangle(vertices, offset, currentFg, currentBg, currentStartX, x, y);
this._updateRectangle(vertices, offset, currentBg, currentStartX, x, y);
}
currentStartX = x;
currentBg = bg;
currentFg = fg;
currentInverse = inverse;
}
}
// Finish rectangle if it's still going
if (currentBg !== 0 || (currentInverse && currentFg !== 0)) {
if (currentBg !== DEFAULT_COLOR) {
const offset = rectangleCount++ * INDICES_PER_RECTANGLE;
this._updateRectangle(vertices, offset, currentFg, currentBg, currentStartX, terminal.cols, y);
this._updateRectangle(vertices, offset, currentBg, currentStartX, terminal.cols, y);
}
}
vertices.count = rectangleCount;
}
private _updateRectangle(vertices: IVertices, offset: number, fg: number, bg: number, startX: number, endX: number, y: number): void {
let rgba: number | undefined;
if (fg & FgFlags.INVERSE) {
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;
}
private _updateRectangle(vertices: IVertices, offset: number, bg: number, startX: number, endX: number, y: number): void {
let color: IColor | null = null;
if (bg === INVERTED_DEFAULT_COLOR) {
color = this._colors.foreground;
} else if (is256Color(bg)) {
color = this._colors.ansi[bg];
} else {
switch (bg & Attributes.CM_MASK) {
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;
}
// TODO: Add support for true color
color = this._colors.foreground;
}
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 = ((rgba >> 24) & 0xFF) / 255;
const g = ((rgba >> 16) & 0xFF) / 255;
const b = ((rgba >> 8 ) & 0xFF) / 255;
const r = ((color.rgba >> 24) & 0xFF) / 255;
const g = ((color.rgba >> 16) & 0xFF) / 255;
const b = ((color.rgba >> 8 ) & 0xFF) / 255;
this._addRectangle(vertices.attributes, offset, x1, y1, (endX - startX) * this._dimensions.scaledCellWidth, this._dimensions.scaledCellHeight, r, g, b, 1);
}
+1 -3
View File
@@ -6,9 +6,7 @@
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 RENDER_MODEL_INDICIES_PER_CELL = 4;
export const COMBINED_CHAR_BIT_MASK = 0x80000000;
+15 -13
View File
@@ -5,7 +5,9 @@
import { assert } from 'chai';
import { sliceFallback } from './TypedArray';
type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Int8Array | Int16Array | Int32Array | Float32Array | Float64Array;
type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray
| Int8Array | Int16Array | Int32Array
| Float32Array | Float64Array;
function deepEquals(a: TypedArray, b: TypedArray): void {
assert.equal(a.length, b.length);
@@ -38,19 +40,19 @@ describe('polyfill conformance tests', function(): void {
deepEquals(sliceFallback(u321, -1), u322.slice(-1));
});
it('Int8Array', () => {
const i81 = new Int8Array(5);
const i82 = new Int8Array(5);
deepEquals(sliceFallback(i81, 2), i82.slice(2));
deepEquals(sliceFallback(i81, 65537), i82.slice(65537));
deepEquals(sliceFallback(i81, -1), i82.slice(-1));
});
const i81 = new Int8Array(5);
const i82 = new Int8Array(5);
deepEquals(sliceFallback(i81, 2), i82.slice(2));
deepEquals(sliceFallback(i81, 65537), i82.slice(65537));
deepEquals(sliceFallback(i81, -1), i82.slice(-1));
});
it('Int16Array', () => {
const i161 = new Int16Array(5);
const i162 = new Int16Array(5);
deepEquals(sliceFallback(i161, 2), i162.slice(2));
deepEquals(sliceFallback(i161, 65535), i162.slice(65535));
deepEquals(sliceFallback(i161, -1), i162.slice(-1));
});
const i161 = new Int16Array(5);
const i162 = new Int16Array(5);
deepEquals(sliceFallback(i161, 2), i162.slice(2));
deepEquals(sliceFallback(i161, 65535), i162.slice(65535));
deepEquals(sliceFallback(i161, -1), i162.slice(-1));
});
it('Int32Array', () => {
const i321 = new Int32Array(5);
const i322 = new Int32Array(5);
+3 -1
View File
@@ -3,7 +3,9 @@
* @license MIT
*/
export type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Int8Array | Int16Array | Int32Array | Float32Array | Float64Array;
export type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray
| Int8Array | Int16Array | Int32Array
| Float32Array | Float64Array;
export function slice<T extends TypedArray>(array: T, start?: number, end?: number): T {
// all modern engines that support .slice
+1 -1
View File
@@ -4,7 +4,7 @@
*/
export interface IRasterizedGlyphSet {
[bg: number]: { [fg: number]: IRasterizedGlyph } | undefined;
[flags: number]: IRasterizedGlyph;
}
/**
+4 -19
View File
@@ -9,35 +9,20 @@ import { IRenderService } from 'browser/services/Services';
import { IColorSet } from 'browser/Types';
export class WebglAddon implements ITerminalAddon {
private _terminal?: Terminal;
private _renderer?: WebglRenderer;
constructor(
private _preserveDrawingBuffer?: boolean
) {}
public activate(terminal: Terminal): void {
if (!terminal.element) {
throw new Error('Cannot activate WebglAddon before Terminal.open');
throw new Error('Cannot activate WebglRendererAddon before Terminal.open');
}
this._terminal = terminal;
const renderService: IRenderService = (<any>terminal)._core._renderService;
const renderService: IRenderService = (<any>terminal)._core._renderService;
const colors: IColorSet = (<any>terminal)._core._colorManager.colors;
this._renderer = new WebglRenderer(terminal, colors, this._preserveDrawingBuffer);
renderService.setRenderer(this._renderer);
renderService.setRenderer(new WebglRenderer(terminal, colors, this._preserveDrawingBuffer));
}
public dispose(): void {
if (!this._terminal) {
throw new Error('Cannot dispose WebglAddon because it is activated');
}
const renderService: IRenderService = (this._terminal as any)._core._renderService;
renderService.setRenderer((this._terminal as any)._core._createRenderer());
renderService.onResize(this._terminal.cols, this._terminal.rows);
this._renderer = undefined;
}
public get textureAtlas(): HTMLCanvasElement | undefined {
return this._renderer?.textureAtlas;
throw new Error('WebglRendererAddon.dispose Not yet implemented');
}
}
File diff suppressed because it is too large Load Diff
+63 -49
View File
@@ -11,15 +11,19 @@ import { acquireCharAtlas } from './atlas/CharAtlasCache';
import { WebglCharAtlas } from './atlas/WebglCharAtlas';
import { RectangleRenderer } from './RectangleRenderer';
import { IWebGL2RenderingContext } from './Types';
import { RenderModel, COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel';
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel';
import { Disposable } from 'common/Lifecycle';
import { NULL_CELL_CODE } from 'common/buffer/Constants';
import { Terminal, IEvent } from 'xterm';
import { DEFAULT_COLOR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CODE } from 'common/buffer/Constants';
import { Terminal } from 'xterm';
import { getLuminance } from './ColorUtils';
import { IRenderLayer } from './renderLayer/Types';
import { IRenderDimensions, IRenderer, IRequestRefreshRowsEvent } from 'browser/renderer/Types';
import { IRenderDimensions, IRenderer } from 'browser/renderer/Types';
import { IColorSet } from 'browser/Types';
import { EventEmitter } from 'common/EventEmitter';
import { CellData } from 'common/buffer/CellData';
import { FLAGS } from './Constants';
import { getCompatAttr } from './CharDataCompat';
export const INDICIES_PER_CELL = 4;
export class WebglRenderer extends Disposable implements IRenderer {
private _renderLayers: IRenderLayer[];
@@ -27,7 +31,6 @@ 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;
@@ -37,10 +40,6 @@ export class WebglRenderer extends Disposable implements IRenderer {
public dimensions: IRenderDimensions;
private _core: ITerminal;
private _isAttached: boolean;
private _onRequestRefreshRows = new EventEmitter<IRequestRefreshRowsEvent>();
public get onRequestRefreshRows(): IEvent<IRequestRefreshRowsEvent> { return this._onRequestRefreshRows.event; }
constructor(
private _terminal: Terminal,
@@ -49,11 +48,13 @@ export class WebglRenderer extends Disposable implements IRenderer {
) {
super();
this._core = (this._terminal as any)._core;
this._core = (<any>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)
new CursorRenderLayer(this._core.screenElement, 3, this._colors)
];
this.dimensions = {
scaledCharWidth: 0,
@@ -81,7 +82,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
};
this._gl = this._canvas.getContext('webgl2', contextAttributes) as IWebGL2RenderingContext;
if (!this._gl) {
throw new Error('WebGL2 not supported ' + this._gl);
throw new Error('WebGL2 not supported');
}
this._core.screenElement.appendChild(this._canvas);
@@ -90,8 +91,6 @@ 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 {
@@ -100,12 +99,20 @@ export class WebglRenderer extends Disposable implements IRenderer {
super.dispose();
}
public get textureAtlas(): HTMLCanvasElement | undefined {
return this._charAtlas?.cacheCanvas;
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);
@@ -178,7 +185,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
this._rectangleRenderer.updateSelection(this._model.selection, columnSelectMode);
this._glyphRenderer.updateSelection(this._model, columnSelectMode);
this._onRequestRefreshRows.fire({ start: 0, end: this._terminal.rows - 1 });
// TODO: #2102 Should this move to RenderCoordinator?
this._core.refresh(0, this._terminal.rows - 1);
}
public onCursorMove(): void {
@@ -198,8 +206,6 @@ 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;
}
@@ -225,16 +231,6 @@ 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 as any)._charSizeService.width && (this._core as any)._charSizeService.height) {
this._updateDimensions();
this._refreshCharAtlas();
this._isAttached = true;
} else {
return;
}
}
// Update render layers
this._renderLayers.forEach(l => l.onGridChanged(this._terminal, start, end));
@@ -259,34 +255,52 @@ 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++) {
line.loadCell(x, this._workCell);
const chars = this._workCell.getChars();
let code = this._workCell.getCode();
const i = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL;
const charData = line.get(x);
const chars = charData[CHAR_DATA_CHAR_INDEX];
let code = charData[CHAR_DATA_CODE_INDEX];
const attr = getCompatAttr(line, x); // charData[CHAR_DATA_ATTR_INDEX];
const i = ((y * terminal.cols) + x) * INDICIES_PER_CELL;
if (code !== NULL_CELL_CODE) {
this._model.lineLengths[y] = x + 1;
}
// Nothing has changed, no updates needed
if (this._model.cells[i] === code &&
this._model.cells[i + RENDER_MODEL_BG_OFFSET] === this._workCell.bg &&
this._model.cells[i + RENDER_MODEL_FG_OFFSET] === this._workCell.fg) {
if (this._model.cells[i] === code && this._model.cells[i + 1] === attr) {
continue;
}
// Resolve bg and fg and cache in the model
const flags = attr >> 18;
let bg = attr & 0x1ff;
let fg = (attr >> 9) & 0x1ff;
// If inverse flag is on, the foreground should become the background.
if (flags & FLAGS.INVERSE) {
const temp = bg;
bg = fg;
fg = temp;
if (fg === DEFAULT_COLOR) {
fg = INVERTED_DEFAULT_COLOR;
}
if (bg === DEFAULT_COLOR) {
bg = INVERTED_DEFAULT_COLOR;
}
}
const drawInBrightColor = terminal.options.drawBoldTextInBrightColors && !!(flags & FLAGS.BOLD) && fg < 8 && fg !== INVERTED_DEFAULT_COLOR;
fg += drawInBrightColor ? 8 : 0;
// Flag combined chars with a bit mask so they're easily identifiable
if (chars.length > 1) {
code = code | COMBINED_CHAR_BIT_MASK;
}
// Cache the results in the model
this._model.cells[i] = code;
this._model.cells[i + RENDER_MODEL_BG_OFFSET] = this._workCell.bg;
this._model.cells[i + RENDER_MODEL_FG_OFFSET] = this._workCell.fg;
this._model.cells[i ] = code;
this._model.cells[i + 1] = attr;
this._model.cells[i + 2] = bg;
this._model.cells[i + 3] = fg;
this._glyphRenderer.updateCell(x, y, code, this._workCell.bg, this._workCell.fg, chars);
this._glyphRenderer.updateCell(x, y, code, attr, bg, fg, chars);
}
}
this._rectangleRenderer.updateBackgrounds(this._model);
@@ -302,8 +316,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
}
// Translate from buffer position to viewport position
const viewportStartRow = start[1] - terminal.buffer.active.viewportY;
const viewportEndRow = end[1] - terminal.buffer.active.viewportY;
const viewportStartRow = start[1] - terminal.buffer.viewportY;
const viewportEndRow = end[1] - terminal.buffer.viewportY;
const viewportCappedStartRow = Math.max(viewportStartRow, 0);
const viewportCappedEndRow = Math.min(viewportEndRow, terminal.rows - 1);
@@ -329,7 +343,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
// TODO: Acquire CharSizeService properly
// Perform a new measure if the CharMeasure dimensions are not yet available
if (!(this._core as any)._charSizeService.width || !(this._core as any)._charSizeService.height) {
if (!(<any>this._core)._charSizeService.width || !(<any>this._core)._charSizeService.height) {
return;
}
@@ -340,12 +354,12 @@ export class WebglRenderer extends Disposable implements IRenderer {
// NOTE: ceil fixes sometime, floor does others :s
this.dimensions.scaledCharWidth = Math.floor((this._core as any)._charSizeService.width * this._devicePixelRatio);
this.dimensions.scaledCharWidth = Math.floor((<any>this._core)._charSizeService.width * this._devicePixelRatio);
// Calculate the scaled character height. Height is ceiled in case
// devicePixelRatio is a floating point number in order to ensure there is
// enough space to draw the character to the cell.
this.dimensions.scaledCharHeight = Math.ceil((this._core as any)._charSizeService.height * this._devicePixelRatio);
this.dimensions.scaledCharHeight = Math.ceil((<any>this._core)._charSizeService.height * this._devicePixelRatio);
// Calculate the scaled cell height, if lineHeight is not 1 then the value
// will be floored because since lineHeight can never be lower then 1, there
@@ -4,7 +4,7 @@
*/
import { ICharAtlasConfig } from './Types';
import { Attributes } from 'common/buffer/Constants';
import { DEFAULT_COLOR } from 'common/buffer/Constants';
import { Terminal, FontWeight } from 'xterm';
import { IColorSet, IColor } from 'browser/Types';
@@ -21,11 +21,9 @@ 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(),
contrastCache: colors.contrastCache
ansi: colors.ansi.slice()
};
return {
devicePixelRatio: window.devicePixelRatio,
@@ -36,8 +34,6 @@ 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'),
minimumContrastRatio: terminal.getOption('minimumContrastRatio'),
colors: clonedColors
};
}
@@ -56,12 +52,10 @@ 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;
}
export function is256Color(colorCode: number): boolean {
return (colorCode & Attributes.CM_MASK) === Attributes.CM_P16 || (colorCode & Attributes.CM_MASK) === Attributes.CM_P256;
return colorCode < DEFAULT_COLOR;
}
-2
View File
@@ -25,7 +25,5 @@ export interface ICharAtlasConfig {
scaledCharWidth: number;
scaledCharHeight: number;
allowTransparency: boolean;
drawBoldTextInBrightColors: boolean;
minimumContrastRatio: number;
colors: IColorSet;
}
@@ -4,14 +4,14 @@
*/
import { ICharAtlasConfig } from './Types';
import { DIM_OPACITY } from 'browser/renderer/atlas/Constants';
import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types';
import { DEFAULT_COLOR, Attributes } from 'common/buffer/Constants';
import { DEFAULT_COLOR, DEFAULT_ATTR } 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';
import { AttributeData } from 'common/buffer/AttributeData';
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.
@@ -68,12 +68,8 @@ 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,
private _config: ICharAtlasConfig
) {
constructor(document: Document, private _config: ICharAtlasConfig) {
this.cacheCanvas = document.createElement('canvas');
this.cacheCanvas.width = TEXTURE_WIDTH;
this.cacheCanvas.height = TEXTURE_HEIGHT;
@@ -86,6 +82,9 @@ 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 {
@@ -104,11 +103,9 @@ 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_COLOR, DEFAULT_COLOR);
const rasterizedGlyph = this._drawToCache(i, DEFAULT_ATTR, DEFAULT_COLOR, DEFAULT_COLOR);
this._cacheMap[i] = {
[DEFAULT_COLOR]: {
[DEFAULT_COLOR]: rasterizedGlyph
}
[DEFAULT_ATTR]: rasterizedGlyph
};
}
}
@@ -126,23 +123,16 @@ export class WebglCharAtlas implements IDisposable {
return false;
}
public getRasterizedGlyphCombinedChar(chars: string, bg: number, fg: number): IRasterizedGlyph {
public getRasterizedGlyphCombinedChar(chars: string, attr: number, bg: number, fg: number): IRasterizedGlyph {
let rasterizedGlyphSet = this._cacheMapCombined[chars];
if (!rasterizedGlyphSet) {
rasterizedGlyphSet = {};
this._cacheMapCombined[chars] = rasterizedGlyphSet;
}
let rasterizedGlyph: IRasterizedGlyph | undefined;
const rasterizedGlyphSetBg = rasterizedGlyphSet[bg];
if (rasterizedGlyphSetBg) {
rasterizedGlyph = rasterizedGlyphSetBg[fg];
}
let rasterizedGlyph = rasterizedGlyphSet[attr];
if (!rasterizedGlyph) {
rasterizedGlyph = this._drawToCache(chars, bg, fg);
if (!rasterizedGlyphSet[bg]) {
rasterizedGlyphSet[bg] = {};
}
rasterizedGlyphSet[bg]![fg] = rasterizedGlyph;
rasterizedGlyph = this._drawToCache(chars, attr, bg, fg);
rasterizedGlyphSet[attr] = rasterizedGlyph;
}
return rasterizedGlyph;
}
@@ -150,23 +140,16 @@ export class WebglCharAtlas implements IDisposable {
/**
* Gets the glyphs texture coords, drawing the texture if it's not already
*/
public getRasterizedGlyph(code: number, bg: number, fg: number): IRasterizedGlyph {
public getRasterizedGlyph(code: number, attr: number, bg: number, fg: number): IRasterizedGlyph {
let rasterizedGlyphSet = this._cacheMap[code];
if (!rasterizedGlyphSet) {
rasterizedGlyphSet = {};
this._cacheMap[code] = rasterizedGlyphSet;
}
let rasterizedGlyph: IRasterizedGlyph | undefined;
const rasterizedGlyphSetBg = rasterizedGlyphSet[bg];
if (rasterizedGlyphSetBg) {
rasterizedGlyph = rasterizedGlyphSetBg[fg];
}
let rasterizedGlyph = rasterizedGlyphSet[attr];
if (!rasterizedGlyph) {
rasterizedGlyph = this._drawToCache(code, bg, fg);
if (!rasterizedGlyphSet[bg]) {
rasterizedGlyphSet[bg] = {};
}
rasterizedGlyphSet[bg]![fg] = rasterizedGlyph;
rasterizedGlyph = this._drawToCache(code, attr, bg, fg);
rasterizedGlyphSet[attr] = rasterizedGlyph;
}
return rasterizedGlyph;
}
@@ -178,165 +161,48 @@ export class WebglCharAtlas implements IDisposable {
return this._config.colors.ansi[idx];
}
private _getBackgroundColor(bgColorMode: number, bgColor: number, inverse: boolean): IColor {
private _getBackgroundColor(bg: 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) {
return this._config.colors.foreground;
} else if (is256Color(bg)) {
return this._getColorFromAnsiIndex(bg);
}
switch (bgColorMode) {
case Attributes.CM_P16:
case Attributes.CM_P256:
return this._getColorFromAnsiIndex(bgColor);
case Attributes.CM_RGB:
const arr = AttributeData.toColorRGB(bgColor);
// TODO: This object creation is slow
return {
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;
}
// TODO: Support true color
return this._config.colors.background;
}
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;
}
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);
return channels.toCss(arr[0], arr[1], arr[2]);
case Attributes.CM_DEFAULT:
default:
if (inverse) {
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;
private _getForegroundColor(fg: number): IColor {
if (fg === INVERTED_DEFAULT_COLOR) {
return this._config.colors.background;
} else if (is256Color(fg)) {
return this._getColorFromAnsiIndex(fg);
}
// TODO: Support true color
return this._config.colors.foreground;
}
private _resolveBackgroundRgba(bgColorMode: number, bgColor: 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) {
return this._config.colors.foreground.rgba;
}
return this._config.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._config.drawBoldTextInBrightColors && bold && fgColor < 8) {
fgColor += 8;
}
return this._getColorFromAnsiIndex(fgColor).rgba;
case Attributes.CM_RGB:
return fgColor << 8;
case Attributes.CM_DEFAULT:
default:
if (inverse) {
return this._config.colors.background.rgba;
}
return this._config.colors.foreground.rgba;
}
}
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, inverse);
const fgRgba = this._resolveForegroundRgba(fgColorMode, fgColor, inverse, bold);
const result = rgba.ensureContrastRatio(bgRgba, fgRgba, this._config.minimumContrastRatio);
if (!result) {
this._config.colors.contrastCache.setCss(bg, fg, null);
return undefined;
}
const css = channels.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;
private _drawToCache(chars: string, bg: number, fg: number): IRasterizedGlyph;
private _drawToCache(codeOrChars: number | string, bg: number, fg: number): IRasterizedGlyph {
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 {
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);
this._tmpCtx.save();
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();
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;
bgColor = temp;
const temp2 = fgColorMode;
fgColorMode = bgColorMode;
bgColorMode = temp2;
}
// draw the background
const backgroundColor = this._getBackgroundColor(bgColorMode, bgColor, inverse);
const backgroundColor = this._getBackgroundColor(bg);
// Use a 'copy' composite operation to clear any existing glyph out of _tmpCtxWithAlpha, regardless of
// transparency in backgroundColor
this._tmpCtx.globalCompositeOperation = 'copy';
@@ -351,7 +217,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(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, bold);
this._tmpCtx.fillStyle = this._getForegroundColor(fg).css;
// Apply alpha to dim the character
if (dim) {
@@ -532,8 +398,3 @@ 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;
}
@@ -4,6 +4,7 @@
*/
import { IRenderLayer } from './Types';
import { IGlyphIdentifier } from '../atlas/Types';
import { acquireCharAtlas } from '../atlas/CharAtlasCache';
import { Terminal } from 'xterm';
import { IColorSet } from 'browser/Types';
@@ -24,6 +25,19 @@ 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,
@@ -74,7 +88,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
const oldCanvas = this._canvas;
this._alpha = alpha;
// Cloning preserves properties
this._canvas = this._canvas.cloneNode() as HTMLCanvasElement;
this._canvas = <HTMLCanvasElement>this._canvas.cloneNode();
this._initCanvas();
this._container.replaceChild(this._canvas, oldCanvas);
@@ -127,10 +141,10 @@ export abstract class BaseRenderLayer implements IRenderLayer {
*/
protected _fillCells(x: number, y: number, width: number, height: number): void {
this._ctx.fillRect(
x * this._scaledCellWidth,
y * this._scaledCellHeight,
width * this._scaledCellWidth,
height * this._scaledCellHeight);
x * this._scaledCellWidth,
y * this._scaledCellHeight,
width * this._scaledCellWidth,
height * this._scaledCellHeight);
}
/**
@@ -141,10 +155,10 @@ export abstract class BaseRenderLayer implements IRenderLayer {
*/
protected _fillBottomLineAtCells(x: number, y: number, width: number = 1): void {
this._ctx.fillRect(
x * this._scaledCellWidth,
(y + 1) * this._scaledCellHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */,
width * this._scaledCellWidth,
window.devicePixelRatio);
x * this._scaledCellWidth,
(y + 1) * this._scaledCellHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */,
width * this._scaledCellWidth,
window.devicePixelRatio);
}
/**
@@ -153,12 +167,12 @@ 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): void {
protected _fillLeftLineAtCell(x: number, y: number): void {
this._ctx.fillRect(
x * this._scaledCellWidth,
y * this._scaledCellHeight,
window.devicePixelRatio * width,
this._scaledCellHeight);
x * this._scaledCellWidth,
y * this._scaledCellHeight,
window.devicePixelRatio,
this._scaledCellHeight);
}
/**
@@ -170,10 +184,10 @@ export abstract class BaseRenderLayer implements IRenderLayer {
protected _strokeRectAtCell(x: number, y: number, width: number, height: number): void {
this._ctx.lineWidth = window.devicePixelRatio;
this._ctx.strokeRect(
x * this._scaledCellWidth + window.devicePixelRatio / 2,
y * this._scaledCellHeight + (window.devicePixelRatio / 2),
width * this._scaledCellWidth - window.devicePixelRatio,
(height * this._scaledCellHeight) - window.devicePixelRatio);
x * this._scaledCellWidth + window.devicePixelRatio / 2,
y * this._scaledCellHeight + (window.devicePixelRatio / 2),
width * this._scaledCellWidth - window.devicePixelRatio,
(height * this._scaledCellHeight) - window.devicePixelRatio);
}
/**
@@ -198,17 +212,17 @@ export abstract class BaseRenderLayer implements IRenderLayer {
protected _clearCells(x: number, y: number, width: number, height: number): void {
if (this._alpha) {
this._ctx.clearRect(
x * this._scaledCellWidth,
y * this._scaledCellHeight,
width * this._scaledCellWidth,
height * this._scaledCellHeight);
x * this._scaledCellWidth,
y * this._scaledCellHeight,
width * this._scaledCellWidth,
height * this._scaledCellHeight);
} else {
this._ctx.fillStyle = this._colors.background.css;
this._ctx.fillRect(
x * this._scaledCellWidth,
y * this._scaledCellHeight,
width * this._scaledCellWidth,
height * this._scaledCellHeight);
x * this._scaledCellWidth,
y * this._scaledCellHeight,
width * this._scaledCellWidth,
height * this._scaledCellHeight);
}
}
@@ -227,9 +241,9 @@ export abstract class BaseRenderLayer implements IRenderLayer {
this._ctx.textBaseline = 'middle';
this._clipRow(terminal, y);
this._ctx.fillText(
cell.getChars(),
x * this._scaledCellWidth + this._scaledCharLeft,
y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2);
cell.getChars(),
x * this._scaledCellWidth + this._scaledCharLeft,
y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2);
}
/**
@@ -240,10 +254,10 @@ export abstract class BaseRenderLayer implements IRenderLayer {
private _clipRow(terminal: Terminal, y: number): void {
this._ctx.beginPath();
this._ctx.rect(
0,
y * this._scaledCellHeight,
terminal.cols * this._scaledCellWidth,
this._scaledCellHeight);
0,
y * this._scaledCellHeight,
terminal.cols * this._scaledCellWidth,
this._scaledCellHeight);
this._ctx.clip();
}
@@ -8,8 +8,7 @@ import { BaseRenderLayer } from './BaseRenderLayer';
import { ICellData } from 'common/Types';
import { CellData } from 'common/buffer/CellData';
import { IColorSet } from 'browser/Types';
import { IRenderDimensions, IRequestRefreshRowsEvent } from 'browser/renderer/Types';
import { IEventEmitter } from 'common/EventEmitter';
import { IRenderDimensions } from 'browser/renderer/Types';
interface ICursorState {
x: number;
@@ -30,12 +29,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
private _cursorBlinkStateManager: CursorBlinkStateManager | undefined;
private _cell: ICellData = new CellData();
constructor(
container: HTMLElement,
zIndex: number,
colors: IColorSet,
private _onRequestRefreshRowsEvent: IEventEmitter<IRequestRefreshRowsEvent>
) {
constructor(container: HTMLElement, zIndex: number, colors: IColorSet) {
super(container, 'cursor', zIndex, true, colors);
this._state = {
x: 0,
@@ -76,14 +70,14 @@ export class CursorRenderLayer extends BaseRenderLayer {
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.pause();
}
this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.active.cursorY, end: terminal.buffer.active.cursorY });
terminal.refresh(terminal.buffer.cursorY, terminal.buffer.cursorY);
}
public onFocus(terminal: Terminal): void {
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.resume(terminal);
} else {
this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.active.cursorY, end: terminal.buffer.active.cursorY });
terminal.refresh(terminal.buffer.cursorY, terminal.buffer.cursorY);
}
}
@@ -95,12 +89,13 @@ export class CursorRenderLayer extends BaseRenderLayer {
});
}
} else {
this._cursorBlinkStateManager?.dispose();
this._cursorBlinkStateManager = undefined;
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.dispose();
}
// Request a refresh from the terminal as management of rendering is being
// moved back to the terminal
terminal.refresh(terminal.buffer.cursorY, terminal.buffer.cursorY);
}
// Request a refresh from the terminal as management of rendering is being
// moved back to the terminal
this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.active.cursorY, end: terminal.buffer.active.cursorY });
}
public onCursorMove(terminal: Terminal): void {
@@ -120,16 +115,13 @@ export class CursorRenderLayer extends BaseRenderLayer {
private _render(terminal: Terminal, triggeredByAnimationFrame: boolean): void {
// Don't draw the cursor if it's hidden
// TODO: Need to expose API for this
if (!(terminal as any)._core._coreService.isCursorInitialized || (terminal as any)._core._coreService.isCursorHidden) {
if (!(terminal as any)._core.cursorState || (terminal as any)._core.cursorHidden) {
this._clearCursor();
return;
}
const cursorY = terminal.buffer.active.baseY + terminal.buffer.active.cursorY;
const viewportRelativeCursorY = cursorY - terminal.buffer.active.viewportY;
// in case cursor.x == cols adjust visual cursor to cols - 1
const cursorX = Math.min(terminal.buffer.active.cursorX, terminal.cols - 1);
const cursorY = terminal.buffer.baseY + terminal.buffer.cursorY;
const viewportRelativeCursorY = cursorY - terminal.buffer.viewportY;
// Don't draw the cursor if it's off-screen
if (viewportRelativeCursorY < 0 || viewportRelativeCursorY >= terminal.rows) {
@@ -138,7 +130,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
}
// TODO: Need fast buffer API for loading cell
(terminal as any)._core.buffer.lines.get(cursorY).loadCell(cursorX, this._cell);
(terminal as any)._core.buffer.lines.get(cursorY).loadCell(terminal.buffer.cursorX, this._cell);
if (this._cell.content === undefined) {
return;
}
@@ -147,17 +139,12 @@ export class CursorRenderLayer extends BaseRenderLayer {
this._clearCursor();
this._ctx.save();
this._ctx.fillStyle = this._colors.cursor.css;
const cursorStyle = terminal.getOption('cursorStyle');
if (cursorStyle && cursorStyle !== 'block') {
this._cursorRenderers[cursorStyle](terminal, cursorX, viewportRelativeCursorY, this._cell);
} else {
this._renderBlurCursor(terminal, cursorX, viewportRelativeCursorY, this._cell);
}
this._renderBlurCursor(terminal, terminal.buffer.cursorX, viewportRelativeCursorY, this._cell);
this._ctx.restore();
this._state.x = cursorX;
this._state.x = terminal.buffer.cursorX;
this._state.y = viewportRelativeCursorY;
this._state.isFocused = false;
this._state.style = cursorStyle;
this._state.style = terminal.getOption('cursorStyle');
this._state.width = this._cell.getWidth();
return;
}
@@ -170,7 +157,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
if (this._state) {
// The cursor is already in the correct spot, don't redraw
if (this._state.x === cursorX &&
if (this._state.x === terminal.buffer.cursorX &&
this._state.y === viewportRelativeCursorY &&
this._state.isFocused === isTerminalFocused(terminal) &&
this._state.style === terminal.getOption('cursorStyle') &&
@@ -181,10 +168,10 @@ export class CursorRenderLayer extends BaseRenderLayer {
}
this._ctx.save();
this._cursorRenderers[terminal.getOption('cursorStyle') || 'block'](terminal, cursorX, viewportRelativeCursorY, this._cell);
this._cursorRenderers[terminal.getOption('cursorStyle') || 'block'](terminal, terminal.buffer.cursorX, viewportRelativeCursorY, this._cell);
this._ctx.restore();
this._state.x = cursorX;
this._state.x = terminal.buffer.cursorX;
this._state.y = viewportRelativeCursorY;
this._state.isFocused = false;
this._state.style = terminal.getOption('cursorStyle');
@@ -207,7 +194,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('cursorWidth'));
this._fillLeftLineAtCell(x, y);
this._ctx.restore();
}
@@ -302,7 +289,7 @@ class CursorBlinkStateManager {
// the regular interval is setup in order to support restarting the blink
// animation in a lightweight way (without thrashing clearInterval and
// setInterval).
this._blinkStartTimeout = window.setTimeout(() => {
this._blinkStartTimeout = <number><any>setTimeout(() => {
// Check if another animation restart was requested while this was being
// started
if (this._animationTimeRestarted) {
@@ -322,7 +309,7 @@ class CursorBlinkStateManager {
});
// Setup the blink interval
this._blinkInterval = window.setInterval(() => {
this._blinkInterval = <number><any>setInterval(() => {
// Adjust the animation time if it was restarted
if (this._animationTimeRestarted) {
// calc time diff
@@ -360,9 +347,6 @@ class CursorBlinkStateManager {
}
public resume(terminal: Terminal): void {
// Clear out any existing timers just in case
this.pause();
this._animationTimeRestarted = undefined;
this._restartInterval();
this.restartBlinkAnimation(terminal);
@@ -18,9 +18,6 @@ export class LinkRenderLayer extends BaseRenderLayer {
super(container, 'link', zIndex, true, colors);
terminal.linkifier.onLinkHover(e => this._onLinkHover(e));
terminal.linkifier.onLinkLeave(e => this._onLinkLeave(e));
terminal.linkifier2.onLinkHover(e => this._onLinkHover(e));
terminal.linkifier2.onLinkLeave(e => this._onLinkLeave(e));
}
public resize(terminal: Terminal, dim: IRenderDimensions): void {
+5 -17
View File
@@ -12,29 +12,17 @@
"removeComments": true,
"baseUrl": ".",
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
]
"common/*": [ "../../../src/common/*" ],
"browser/*": [ "../../../src/browser/*" ]
},
"strict": true,
"types": [
"../../../node_modules/@types/mocha",
"../../../out-test/api/TestUtils"
]
"strict": true
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
],
"references": [
{
"path": "../../../src/common"
},
{
"path": "../../../src/browser"
}
{ "path": "../../../src/common" },
{ "path": "../../../src/browser" }
]
}
@@ -10,8 +10,6 @@ declare module 'xterm-addon-webgl' {
* An xterm.js addon that provides search functionality.
*/
export class WebglAddon implements ITerminalAddon {
public textureAtlas?: HTMLCanvasElement;
constructor(preserveDrawingBuffer?: boolean);
/**
+48 -90
View File
@@ -6,55 +6,42 @@
jobs:
- job: Linux
pool:
vmImage: 'ubuntu-18.04'
vmImage: 'ubuntu-16.04'
steps:
- task: NodeTool@0
inputs:
versionSpec: '10.x'
versionSpec: '8.x'
displayName: 'Install Node.js'
- task: YarnInstaller@3
- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2
inputs:
versionSpec: '1.x'
versionSpec: "1.9.4"
displayName: 'Install Yarn'
- task: CacheBeta@1
inputs:
key: yarn2 | $(Agent.OS) | yarn.lock
path: node_modules
displayName: Cache node modules
- script: yarn --frozen-lockfile
- script: |
yarn
displayName: 'Install dependencies and build'
- script: |
yarn test-unit-coverage --forbid-only
EXIT_CODE=$?
./node_modules/.bin/nyc report --reporter=cobertura
exit $EXIT_CODE
yarn test-unit
displayName: 'Unit tests'
- script: yarn lint
- script: |
yarn lint
displayName: 'Lint'
- task: PublishCodeCoverageResults@1
inputs:
codeCoverageTool: Cobertura
summaryFileLocation: '$(System.DefaultWorkingDirectory)/**/*coverage.xml'
displayName: 'Publish coverage'
- job: macOS
pool:
vmImage: 'macOS-10.15'
vmImage: 'xcode9-macos10.13'
steps:
- task: NodeTool@0
inputs:
versionSpec: '10.x'
versionSpec: '8.x'
displayName: 'Install Node.js'
- task: CacheBeta@1
inputs:
key: yarn2 | $(Agent.OS) | yarn.lock
path: node_modules
displayName: Cache node modules
- script: yarn --frozen-lockfile
- script: |
yarn
displayName: 'Install dependencies and build'
- script: yarn test-unit --forbid-only
- script: |
yarn test-unit
displayName: 'Unit tests'
- script: yarn lint
- script: |
yarn lint
displayName: 'Lint'
- job: Windows
@@ -63,82 +50,55 @@ jobs:
steps:
- task: NodeTool@0
inputs:
versionSpec: '10.x'
versionSpec: '8.x'
displayName: 'Install Node.js'
- task: CacheBeta@1
inputs:
key: yarn2 | $(Agent.OS) | yarn.lock
path: node_modules
displayName: Cache node modules
- script: yarn --frozen-lockfile
- script: |
yarn
displayName: 'Install dependencies and build'
- script: yarn test-unit --forbid-only
- script: |
yarn test-unit
displayName: 'Unit tests'
- script: yarn lint
- script: |
yarn lint
displayName: 'Lint'
- job: Linux_IntegrationTests
pool:
vmImage: 'ubuntu-18.04'
vmImage: 'ubuntu-16.04'
steps:
- script: |
# source: https://github.com/microsoft/playwright/issues/1041
sudo apt update
sudo apt install libwoff1 libopus0 libwebp6 libwebpdemux2 libenchant1c2a libgudev-1.0-0 libsecret-1-0 libhyphen0 libgdk-pixbuf2.0-0 libegl1 libnotify4 libxslt1.1 libevent-2.1-6 libgles2 libgl1 libegl1 libvpx5
# for chromium
sudo apt install libnss3 libxss1 libasound2
# for firefox
sudo apt install libdbus-glib-1-2 libxt6
displayName: Install required packages
- task: NodeTool@0
inputs:
versionSpec: '10.x'
versionSpec: '8.x'
displayName: 'Install Node.js'
- task: YarnInstaller@3
- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2
inputs:
versionSpec: '1.x'
versionSpec: "1.9.4"
displayName: 'Install Yarn'
- task: CacheBeta@1
inputs:
key: yarn2 | $(Agent.OS) | yarn.lock
path: node_modules
displayName: Cache node modules
- script: yarn --frozen-lockfile
- script: |
yarn
displayName: 'Install dependencies and build'
- script: |
yarn start &
sleep 10
displayName: 'Start test server'
- script: yarn test-api-chromium --headless --forbid-only
displayName: 'Integration tests (Chromium)'
- script: xvfb-run --auto-servernum -- bash -c "yarn test-api-firefox --headless --forbid-only"
displayName: 'Integration tests (Firefox)'
yarn test-api --headless
displayName: 'Linux Integration tests'
- job: macOS_IntegrationTests
pool:
vmImage: 'macOS-10.15'
vmImage: 'xcode9-macos10.13'
steps:
- task: NodeTool@0
inputs:
versionSpec: '10.x'
versionSpec: '8.x'
displayName: 'Install Node.js'
- task: CacheBeta@1
inputs:
key: yarn2 | $(Agent.OS) | yarn.lock
path: node_modules
displayName: Cache node modules
- script: yarn --frozen-lockfile
- script: |
yarn
displayName: 'Install dependencies and build'
- script: |
yarn start &
sleep 10
displayName: 'Start test server'
- script: yarn test-api-chromium --headless --forbid-only
displayName: 'Integration tests (Chromium)'
- script: yarn test-api-firefox --headless --forbid-only
displayName: 'Integration tests (Firefox)'
- script: yarn test-api-webkit --headless --forbid-only
displayName: 'Integration tests (Webkit)'
yarn test-api --headless
displayName: 'MacOS Integration tests'
- job: Release
dependsOn:
@@ -147,24 +107,22 @@ jobs:
- Windows
- Linux_IntegrationTests
- macOS_IntegrationTests
condition: and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(variables['FORCE_RELEASE'], 'true')))
condition: and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(variables['Build.SourceBranch'], 'refs/heads/release/*')))
pool:
vmImage: 'ubuntu-16.04'
steps:
- task: NodeTool@0
inputs:
versionSpec: '10.x'
versionSpec: '8.x'
displayName: 'Install Node.js'
- task: YarnInstaller@3
- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2
inputs:
versionSpec: '1.x'
versionSpec: "1.9.4"
displayName: 'Install Yarn'
- task: CacheBeta@1
inputs:
key: yarn2 | $(Agent.OS) | yarn.lock
path: node_modules
displayName: Cache node modules
- script: yarn --frozen-lockfile
- script: |
yarn
BUILD_DIR=dist npm run build
displayName: 'Install dependencies and build'
- script: NPM_AUTH_TOKEN="$(NPM_AUTH_TOKEN)" node ./bin/publish.js
displayName: 'Package and publish to npm'
- script: |
NPM_AUTH_TOKEN="$(NPM_AUTH_TOKEN)" node ./bin/publish.js
displayName: 'Publish to npm'
-23
View File
@@ -1,23 +0,0 @@
/**
* Copyright (c) 2020 The xterm.js authors. All rights reserved.
* @license MIT
*/
const playwright = require('playwright-core');
const fs = require('fs');
// Default to chromium
let browserType = playwright['chromium'];
const index = process.argv.indexOf('--browser');
if (index !== -1 && process.argv.length > index + 1 && typeof process.argv[index + 1] === 'string') {
const string = process.argv[index + 1];
if (string === 'firefox' || string === 'webkit') {
browserType = playwright[string];
}
}
const exists = fs.existsSync(browserType.executablePath());
if (!exists) {
console.log(`Downloading ${browserType.name()}`);
browserType.downloadBrowserIfNeeded().then(() => process.exit(0));
}
-509
View File
@@ -1,509 +0,0 @@
/**
* 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;
/**
* regexp to parse the @vt line
* expected data - "@vt: <status> <kind> <mnemonic> "<name>" "<sequence>" "<short description>"
*/
const REX_VT_LINE = /^[@]vt\:\s*(\w+|#\w+|#\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 = `---
title: Supported Terminal Sequences
category: API
---
{::options parse_block_html="true" /}
# Supported Terminal Sequences
xterm.js version: {{version}}
## Table of Contents
<nav>
- [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}}
</nav>
## General notes
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\`)
- 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 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 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 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 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}}){: .link-details}_{{/longDescription.length}} | {{{status}}} |
{{/C0}}
{{#C0.hasLongDescriptions}}
{{#C0}}
{{#longDescription.length}}
<section class="sequence-details">
### {{name}}
{{#longDescription}}
{{{.}}}
{{/longDescription}}
</section>
{{/longDescription.length}}
{{/C0}}
{{/C0.hasLongDescriptions}}
{{/C0.length}}
{{#C1.length}}
## C1
| Mnemonic | Name | Sequence | Short Description | Support |
| -------- | ---- | -------- | ----------------- | ------- |
{{#C1}}
| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}}){: .link-details}_{{/longDescription.length}} | {{{status}}} |
{{/C1}}
{{#C1.hasLongDescriptions}}
{{#C1}}
{{#longDescription.length}}
<section class="sequence-details">
### {{name}}
{{#longDescription}}
{{{.}}}
{{/longDescription}}
</section>
{{/longDescription.length}}
{{/C1}}
{{/C1.hasLongDescriptions}}
{{/C1.length}}
{{#CSI.length}}
## CSI
| Mnemonic | Name | Sequence | Short Description | Support |
| -------- | ---- | -------- | ----------------- | ------- |
{{#CSI}}
| {{mnemonic}} | {{name}} | \`\`{{{sequence}}}\`\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}}){: .link-details}_{{/longDescription.length}} | {{{status}}} |
{{/CSI}}
{{#CSI.hasLongDescriptions}}
{{#CSI}}
{{#longDescription.length}}
<section class="sequence-details">
### {{name}}
{{#longDescription}}
{{{.}}}
{{/longDescription}}
</section>
{{/longDescription.length}}
{{/CSI}}
{{/CSI.hasLongDescriptions}}
{{/CSI.length}}
{{#DCS.length}}
## DCS
| Mnemonic | Name | Sequence | Short Description | Support |
| -------- | ---- | -------- | ----------------- | ------- |
{{#DCS}}
| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}}){: .link-details}_{{/longDescription.length}} | {{{status}}} |
{{/DCS}}
{{#DCS.hasLongDescriptions}}
{{#DCS}}
{{#longDescription.length}}
<section class="sequence-details">
### {{name}}
{{#longDescription}}
{{{.}}}
{{/longDescription}}
</section>
{{/longDescription.length}}
{{/DCS}}
{{/DCS.hasLongDescriptions}}
{{/DCS.length}}
{{#ESC.length}}
## ESC
| Mnemonic | Name | Sequence | Short Description | Support |
| -------- | ---- | -------- | ----------------- | ------- |
{{#ESC}}
| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}}){: .link-details}_{{/longDescription.length}} | {{{status}}} |
{{/ESC}}
{{#ESC.hasLongDescriptions}}
{{#ESC}}
{{#longDescription.length}}
<section class="sequence-details">
### {{name}}
{{#longDescription}}
{{{.}}}
{{/longDescription}}
</section>
{{/longDescription.length}}
{{/ESC}}
{{/ESC.hasLongDescriptions}}
{{/ESC.length}}
{{#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 | Support |
| ---------- | -------- | ----------------- | ------- |
{{#OSC}}
| {{mnemonic}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}}){: .link-details}_{{/longDescription.length}} | {{{status}}} |
{{/OSC}}
{{#OSC.hasLongDescriptions}}
{{#OSC}}
{{#longDescription.length}}
<section class="sequence-details">
### {{name}}
{{#longDescription}}
{{{.}}}
{{/longDescription}}
</section>
{{/longDescription.length}}
{{/OSC}}
{{/OSC.hasLongDescriptions}}
{{/OSC.length}}
<script type="text/javascript">
const linkStates = {};
function hideDetailSections() {
for (let section of document.getElementsByClassName('sequence-details')) section.style.display = 'none';
}
function decorateDetailLinks() {
for (let link of document.getElementsByClassName('link-details')) {
link.addEventListener("click", toggleDetails, false);
if (linkStates[link.href] === undefined) {
linkStates[link.href] = {open: false, links: [], trElem: null};
}
linkStates[link.href].links.push(link);
}
}
function openDetails(link) {
const closestTr = link.closest('tr');
if (!closestTr || !closestTr.parentNode) return;
const closestSection = document.getElementById(link.href.split('#')[1]).closest('section');
if (!closestSection || !closestSection.innerHTML) return;
const content = closestSection.innerHTML.split('</h3>')[1];
const newRow = closestTr.parentNode.insertRow(closestTr.rowIndex);
const cell = newRow.insertCell(0);
cell.innerHTML = '<div style="font-style: italic">' + content + '</div>';
cell.colSpan = closestTr.children.length;
const state = linkStates[link.href];
state.trElem = newRow;
state.open = true;
if (link.innerHTML === 'more') link.innerHTML = 'less';
}
function closeDetails(link) {
const state = linkStates[link.href];
if (state.trElem && state.trElem.rowIndex !== -1) {
if (state.trElem.parentNode) state.trElem.parentNode.deleteRow(state.trElem.rowIndex-1); // why -1 here?
state.trElem.remove();
state.trElem = null;
}
state.open = false;
if (link.innerHTML === 'less') link.innerHTML = 'more';
}
function toggleDetails(ev) {
if (!ev.target || !ev.target.href) return;
const isOpen = linkStates[ev.target.href].open;
if (isOpen === undefined) return;
if (isOpen) closeDetails(ev.target);
else openDetails(ev.target);
ev.preventDefault();
return false;
}
function load() {
hideDetailSections();
decorateDetailLinks();
}
load(); // we are inlined behind all needed data, thus dont wait for onload (avoids flickering)
</script>
`
// support status marcos
// applied for:
// - status field of single @vt line
// - all lines in long description
const MACRO = [
// #Y - supported
[/#Y/g, s => '<span title="supported">✓</span>'],
// #N - unsupported
[/#N/g, s => '<span title="unsupported">✗</span>'],
// #P[reason] - partial support with a reason as title
[/#P\[(.*?)\]/g, (s, p1) => `<span title="${p1}" style="text-decoration: underline">Partial</span>`],
// #B[reason] - supported but broken in a certain way, reason in title
[/#B\[(.*?)\]/g, (s, p1) => `<span title="${p1}" style="text-decoration: underline">Broken</span>`]
];
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 '<span title="supported">✓</span>';
if (s === 'unsupported') return '<span title="unsupported">✗</span>';
return s;
}
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;
}
const lines = s.split('\n').map(el => el.trim().replace(/[*]/, '').replace(/\s/, ''));
let grabLine = false;
let longDescription = [];
let feature = undefined;
let noLineCount = 0;
for (const line of lines) {
if (grabLine) {
if (!line) noLineCount++;
if (noLineCount >= 2) {
if (feature) {
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 = empty(longDescription) ? [] : longDescription;
feature.longTarget = createAnchorSlug(feature.name);
yield feature;
}
grabLine = true;
longDescription = [];
feature = undefined;
noLineCount = 0;
} else {
//longDescription.push(line);
longDescription.push(applyMacros(line));
if (line) noLineCount = 0;
}
}
if (line.indexOf('@vt:') === 0) {
feature = parseSingleLine(filename, line);
grabLine = true;
}
}
if (grabLine && feature) {
feature.longDescription = empty(longDescription) ? [] : longDescription;
feature.longTarget = createAnchorSlug(feature.name);
yield 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: [],
longTarget: '',
source: filename
};
}
}
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) => {
// 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;
}
};
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] = [];
}
featureTable[feature.type].push(feature);
if (feature.longDescription.length) {
featureTable[feature.type].hasLongDescriptions = true;
}
}
for (const entry in featureTable) {
featureTable[entry].sort(getSorter(entry));
}
// 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++;
}
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--;
if (!leftToProcess) {
postProcessData(features);
}
});
}
}
main(process.argv.slice(2))
-56
View File
@@ -1,56 +0,0 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*
* Script to initialize addon packages under "addons/" with outer deps.
*/
const path = require('path');
const cp = require('child_process');
const fs = require('fs');
const PACKAGE_ROOT = path.join(__dirname, '..');
// install addon deps
const addonsPath = path.join(PACKAGE_ROOT, 'addons');
if (fs.existsSync(addonsPath)) {
console.log('pulling addon dependencies...');
// whether to use yarn or npm
let hasYarn = false;
try {
cp.execSync('yarn --version').toString();
hasYarn = true;
} catch(e) {}
// walk all addon folders
fs.readdir(addonsPath, (err, files) => {
files.forEach(folder => {
const addonPath = path.join(addonsPath, folder);
// install only if there are dependencies listed
let packageJson;
try {
packageJson = require(path.join(addonPath, 'package.json'));
} catch (e) {
// swallow as changing branches can leave folders around
}
if (packageJson
&& (
(packageJson.devDependencies && Object.keys(packageJson.devDependencies).length)
|| (packageJson.dependencies && Object.keys(packageJson.dependencies).length)
)
)
{
console.log('Preparing', folder);
if (hasYarn) {
cp.execSync('yarn', {cwd: addonPath});
} else {
cp.execSync('npm install', {cwd: addonPath});
}
} else {
console.log('Skipped', folder);
}
});
});
}
+4 -24
View File
@@ -5,7 +5,6 @@
const cp = require('child_process');
const fs = require('fs');
const os = require('os');
const path = require('path');
// Setup auth
@@ -19,9 +18,8 @@ if (isDryRun) {
const changedFiles = getChangedFilesInCommit('HEAD');
// Publish xterm if any files were changed outside of the addons directory
let isStableRelease = false;
if (changedFiles.some(e => e.search(/^addons\//) === -1)) {
isStableRelease = checkAndPublishPackage(path.resolve(__dirname, '..'));
checkAndPublishPackage(path.resolve(__dirname, '..'));
}
// Publish addons if any files were changed inside of the addon
@@ -29,8 +27,6 @@ const addonPackageDirs = [
path.resolve(__dirname, '../addons/xterm-addon-attach'),
path.resolve(__dirname, '../addons/xterm-addon-fit'),
path.resolve(__dirname, '../addons/xterm-addon-search'),
path.resolve(__dirname, '../addons/xterm-addon-serialize'),
path.resolve(__dirname, '../addons/xterm-addon-unicode11'),
path.resolve(__dirname, '../addons/xterm-addon-web-links'),
path.resolve(__dirname, '../addons/xterm-addon-webgl')
];
@@ -43,11 +39,6 @@ addonPackageDirs.forEach(p => {
}
});
// Publish website if it's a stable release
if (isStableRelease) {
updateWebsite();
}
function checkAndPublishPackage(packageDir) {
const packageJson = require(path.join(packageDir, 'package.json'));
@@ -85,8 +76,6 @@ function checkAndPublishPackage(packageDir) {
}
console.groupEnd();
return isStableRelease;
}
function getNextBetaVersion(packageJson) {
@@ -99,7 +88,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]+$/)));
@@ -107,14 +96,14 @@ 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) {
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;
}
@@ -126,12 +115,3 @@ function getChangedFilesInCommit(commit) {
const changedFiles = output.split('\n').filter(e => e.length > 0);
return changedFiles;
}
function updateWebsite() {
console.log('Updating website');
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'website-'));
const packageJson = require(path.join(path.resolve(__dirname, '..'), 'package.json'));
if (!isDryRun) {
cp.spawnSync('sh', [path.join(__dirname, 'update-website.sh'), packageJson.version], { cwd, stdio: [process.stdin, process.stdout, process.stderr] });
}
}
+7 -41
View File
@@ -6,54 +6,24 @@
const cp = require('child_process');
const path = require('path');
const COVERAGE_LINES_THRESHOLD = 60;
// Add `out` to the NODE_PATH so absolute paths can be resolved.
const env = { ...process.env };
env.NODE_PATH = path.resolve(__dirname, '../out');
let testFiles = [
'./out/*test.js',
'./out/**/*test.js',
'./addons/**/out/*test.js',
'./out/**/*test.js'
];
let flagArgs = [];
// ability to inject particular test files via
// yarn test [testFileA testFileB ...]
if (process.argv.length > 2) {
const args = process.argv.slice(2);
flagArgs = args.filter(e => e.startsWith('--'));
// ability to inject particular test files via
// yarn test [testFileA testFileB ...]
files = args.filter(e => !e.startsWith('--'));
if (files.length) {
testFiles = files;
}
}
const checkCoverage = flagArgs.indexOf('--coverage') >= 0;
if (checkCoverage) {
flagArgs.splice(flagArgs.indexOf('--coverage'), 1);
const executable = npmBinScript('nyc');
const args = ['--check-coverage', `--lines=${COVERAGE_LINES_THRESHOLD}`, npmBinScript('mocha'), ...testFiles, ...flagArgs];
console.info('executable', executable);
console.info('args', args);
const run = cp.spawnSync(
executable,
args,
{
cwd: path.resolve(__dirname, '..'),
env,
stdio: 'inherit'
}
);
process.exit(run.status);
testFiles = process.argv.slice(2);
}
const run = cp.spawnSync(
npmBinScript('mocha'),
[...testFiles, ...flagArgs],
path.resolve(__dirname, '../node_modules/.bin/mocha'),
testFiles,
{
cwd: path.resolve(__dirname, '..'),
env,
@@ -61,8 +31,4 @@ const run = cp.spawnSync(
}
);
function npmBinScript(script) {
return path.resolve(__dirname, `../node_modules/.bin/` + (process.platform === 'win32' ? `${script}.cmd` : script));
}
process.exit(run.status);
process.exit(run.status);
-41
View File
@@ -1,41 +0,0 @@
#!/bin/sh
# Name the arguments
VERSION=$1
# Clone docs repo and update the documentation
git clone https://github.com/xtermjs/xtermjs.org
cd xtermjs.org
yarn
./bin/update-docs
# Add changes to index and only proceed if there are changes to commit
touch test-file
git add .
if ! git diff-index --quiet HEAD --; then
# Delete the upstream branch if it exists for some reason
export BRANCH_NAME=update-$VERSION
git branch -D $BRANCH_NAME || true
git push origin :$BRANCH_NAME || true
# Create commit and push it to update-x.y.z
git checkout -b $BRANCH_NAME
git config --global user.name Daniel Imms
git config --global user.email tyriar@tyriar.com
git commit -m 'Update docs for v$VERSION'
git push --set-upstream origin update-4.2.0
git push -f
# Create a PR in the GitHub repo
curl \
-H "Authorization: token $GITHUB_TOKEN" \
-X POST \
-d "{\"title\":\"Update docs for v$VERSION\",\"base\":\"master\",\"head\":\"xtermjs:$BRANCH_NAME\"}" \
https://api.github.com/repos/xtermjs/xtermjs.org/pulls
else
echo "No changes to commit"
fi
+26 -120
View File
@@ -12,20 +12,16 @@ import { Terminal } from '../out/public/Terminal';
import { AttachAddon } from '../addons/xterm-addon-attach/out/AttachAddon';
import { FitAddon } from '../addons/xterm-addon-fit/out/FitAddon';
import { SearchAddon, ISearchOptions } from '../addons/xterm-addon-search/out/SearchAddon';
import { SerializeAddon } from '../addons/xterm-addon-serialize/out/SerializeAddon';
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';
// 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';
// import { Unicode11Addon } from 'xterm-addon-unicode11';
// Pulling in the module's types relies on the <reference> above, it's looks a
// little weird here as we're importing "this" module
@@ -37,53 +33,19 @@ export interface IWindowWithTerminal extends Window {
AttachAddon?: typeof AttachAddon;
FitAddon?: typeof FitAddon;
SearchAddon?: typeof SearchAddon;
SerializeAddon?: typeof SerializeAddon;
WebLinksAddon?: typeof WebLinksAddon;
WebglAddon?: typeof WebglAddon;
Unicode11Addon?: typeof Unicode11Addon;
}
declare let window: IWindowWithTerminal;
let term;
let fitAddon: FitAddon;
let searchAddon: SearchAddon;
let protocol;
let socketURL;
let socket;
let pid;
type AddonType = 'attach' | 'fit' | 'search' | 'serialize' | 'unicode11' | 'web-links' | 'webgl';
interface IDemoAddon<T extends AddonType> {
name: T;
canChange: boolean;
ctor:
T extends 'attach' ? typeof AttachAddon :
T extends 'fit' ? typeof FitAddon :
T extends 'search' ? typeof SearchAddon :
T extends 'serialize' ? typeof SerializeAddon :
T extends 'web-links' ? typeof WebLinksAddon :
T extends 'unicode11' ? typeof Unicode11Addon :
typeof WebglAddon;
instance?:
T extends 'attach' ? AttachAddon :
T extends 'fit' ? FitAddon :
T extends 'search' ? SearchAddon :
T extends 'serialize' ? SerializeAddon :
T extends 'web-links' ? WebLinksAddon :
T extends 'webgl' ? WebglAddon :
T extends 'unicode11' ? typeof Unicode11Addon :
never;
}
const addons: { [T in AddonType]: IDemoAddon<T>} = {
attach: { name: 'attach', ctor: AttachAddon, canChange: false },
fit: { name: 'fit', ctor: FitAddon, canChange: false },
search: { name: 'search', ctor: SearchAddon, canChange: true },
serialize: { name: 'serialize', ctor: SerializeAddon, canChange: true },
'web-links': { name: 'web-links', ctor: WebLinksAddon, canChange: true },
webgl: { name: 'webgl', ctor: WebglAddon, canChange: true },
unicode11: { name: 'unicode11', ctor: Unicode11Addon, canChange: true }
};
const terminalContainer = document.getElementById('terminal-container');
const actionElements = {
findNext: <HTMLInputElement>document.querySelector('#find-next'),
@@ -125,14 +87,12 @@ if (document.location.pathname === '/test') {
window.AttachAddon = AttachAddon;
window.FitAddon = FitAddon;
window.SearchAddon = SearchAddon;
window.SerializeAddon = SerializeAddon;
window.Unicode11Addon = Unicode11Addon;
window.WebLinksAddon = WebLinksAddon;
window.WebglAddon = WebglAddon;
} else {
createTerminal();
document.getElementById('dispose').addEventListener('click', disposeRecreateButtonHandler);
document.getElementById('serialize').addEventListener('click', serializeButtonHandler);
document.getElementById('webgl').addEventListener('click', () => term.loadAddon(new WebglAddon()));
}
function createTerminal(): void {
@@ -148,17 +108,11 @@ function createTerminal(): void {
// Load addons
const typedTerm = term as TerminalType;
addons.search.instance = new SearchAddon();
addons.serialize.instance = new SerializeAddon();
addons.fit.instance = new FitAddon();
addons.unicode11.instance = new Unicode11Addon();
// TODO: Remove arguments when link provider API is the default
addons['web-links'].instance = new WebLinksAddon(undefined, undefined, true);
typedTerm.loadAddon(addons.fit.instance);
typedTerm.loadAddon(addons.search.instance);
typedTerm.loadAddon(addons.serialize.instance);
typedTerm.loadAddon(addons.unicode11.instance);
typedTerm.loadAddon(addons['web-links'].instance);
typedTerm.loadAddon(new WebLinksAddon());
searchAddon = new SearchAddon();
typedTerm.loadAddon(searchAddon);
fitAddon = new FitAddon();
typedTerm.loadAddon(fitAddon);
window.term = term; // Expose `term` to window for debugging purposes
term.onResize((size: { cols: number, rows: number }) => {
@@ -175,17 +129,17 @@ function createTerminal(): void {
socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/terminals/';
term.open(terminalContainer);
addons.fit.instance!.fit();
fitAddon.fit();
term.focus();
addDomListener(paddingElement, 'change', setPadding);
addDomListener(actionElements.findNext, 'keyup', (e) => {
addons.search.instance.findNext(actionElements.findNext.value, getSearchOptions(e));
searchAddon.findNext(actionElements.findNext.value, getSearchOptions(e));
});
addDomListener(actionElements.findPrevious, 'keyup', (e) => {
addons.search.instance.findPrevious(actionElements.findPrevious.value, getSearchOptions(e));
searchAddon.findPrevious(actionElements.findPrevious.value, getSearchOptions(e));
});
// fit is called within a setTimeout, cols and rows need this.
@@ -213,10 +167,15 @@ function createTerminal(): void {
}
function runRealTerminal(): void {
addons.attach.instance = new AttachAddon(socket);
term.loadAddon(addons.attach.instance);
/**
* The demo defaults to string transport by default.
* To run it with UTF8 binary transport, swap comment on
* the lines below. (Must also be switched in server.js)
*/
term.loadAddon(new AttachAddon(socket));
// term.loadAddon(new AttachAddon(socket, {inputUtf8: true}));
term._initialized = true;
initAddons(term);
}
function runFakeTerminal(): void {
@@ -225,7 +184,6 @@ function runFakeTerminal(): void {
}
term._initialized = true;
initAddons(term);
term.prompt = () => {
term.write('\r\n$ ');
@@ -259,16 +217,17 @@ function initOptions(term: TerminalType): void {
// Internal only options
'cancelEvents',
'convertEol',
'handler',
'screenKeys',
'termName',
'useFlowControl',
// Complex option
'theme',
'windowOptions'
'theme'
];
const stringOptions = {
bellSound: null,
bellStyle: ['none', 'sound'],
cursorStyle: ['block', 'underline', 'bar'],
fastScrollModifier: ['alt', 'ctrl', 'shift', undefined],
fontFamily: null,
fontWeight: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
@@ -301,7 +260,7 @@ function initOptions(term: TerminalType): void {
});
html += '</div><div class="option-group">';
numberOptions.forEach(o => {
html += `<div class="option"><label>${o} <input id="opt-${o}" type="number" value="${term.getOption(o)}" step="${o === 'lineHeight' || o === 'scrollSensitivity' ? '0.1' : '1'}"/></label></div>`;
html += `<div class="option"><label>${o} <input id="opt-${o}" type="number" value="${term.getOption(o)}" step="${o === 'lineHeight' ? '0.1' : '1'}"/></label></div>`;
});
html += '</div><div class="option-group">';
Object.keys(stringOptions).forEach(o => {
@@ -330,7 +289,7 @@ function initOptions(term: TerminalType): void {
console.log('change', o, input.value);
if (o === 'cols' || o === 'rows') {
updateTerminalSize();
} else if (o === 'lineHeight' || o === 'scrollSensitivity') {
} else if (o === 'lineHeight') {
term.setOption(o, parseFloat(input.value));
updateTerminalSize();
} else {
@@ -347,48 +306,6 @@ function initOptions(term: TerminalType): void {
});
}
function initAddons(term: TerminalType): void {
const fragment = document.createDocumentFragment();
Object.keys(addons).forEach((name: AddonType) => {
const addon = addons[name];
const checkbox = document.createElement('input') as HTMLInputElement;
checkbox.type = 'checkbox';
checkbox.checked = !!addon.instance;
if (!addon.canChange) {
checkbox.disabled = true;
}
checkbox.addEventListener('change', () => {
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;
}
});
const label = document.createElement('label');
label.classList.add('addon');
if (!addon.canChange) {
label.title = 'This addon is needed for the demo to operate';
}
label.appendChild(checkbox);
label.appendChild(document.createTextNode(name));
const wrapper = document.createElement('div');
wrapper.classList.add('addon');
wrapper.appendChild(label);
fragment.appendChild(wrapper);
});
document.getElementById('addons-container').appendChild(fragment);
}
function addDomListener(element: HTMLElement, type: string, handler: (...args: any[]) => any): void {
element.addEventListener(type, handler);
term._core.register({ dispose: () => element.removeEventListener(type, handler) });
@@ -401,16 +318,5 @@ function updateTerminalSize(): void {
const height = (rows * term._core._renderService.dimensions.actualCellHeight).toString() + 'px';
terminalContainer.style.width = width;
terminalContainer.style.height = height;
addons.fit.instance.fit();
}
function serializeButtonHandler(): void {
const output = addons.serialize.instance.serialize();
const outputString = JSON.stringify(output);
document.getElementById('serialize-output').innerText = outputString;
if ((document.getElementById('write-to-terminal') as HTMLInputElement).checked) {
term.reset();
term.write(output);
}
fitAddon.fit();
}
+8 -21
View File
@@ -2,11 +2,6 @@
<html>
<head>
<title>xterm.js demo</title>
<!--
WARNING: This demo is a barebones implementation designed for development and evaluation
purposes only. It is definitely NOT production ready and does not aim to be so. Exposing the
demo to the public as is would introduce security risks for the host.
-->
<link rel="shortcut icon" type="image/png" href="/logo.png">
<link rel="stylesheet" href="/xterm.css" />
<link rel="stylesheet" href="/style.css" />
@@ -17,16 +12,7 @@
<h1 style="color: #2D2E2C">xterm.js: A terminal for the <em style="color: #5DA5D5">web</em></h1>
<div id="terminal-container"></div>
<div>
<h3>Options</h3>
<p>These options can be set in the <code>Terminal</code> constructor or by using the <code>Terminal.setOption</code> function.</p>
<div id="options-container"></div>
</div>
<div>
<h3>Addons</h3>
<p>Addons can be loaded and unloaded on a particular terminal to extend its functionality.</p>
<div id="addons-container"></div>
<h3>Addons Control</h3>
<h4>SearchAddon</h4>
<h3>Actions</h3>
<p>
<label>Find next <input id="find-next"/></label>
<label>Find previous <input id="find-previous"/></label>
@@ -34,12 +20,11 @@
<label>Case sensitive<input type="checkbox" id="case-sensitive"/></label>
<label>Whole word<input type="checkbox" id="whole-word"/></label>
</p>
<h4>SerializeAddon</h4>
<p>
<button id="serialize">Serialize the content of terminal</button>
<label><input type="checkbox" id="write-to-terminal">Write back to terminal</label>
<div><pre id="serialize-output"></pre></div>
</p>
</div>
<div>
<h3>Options</h3>
<p>These options can be set in the <code>Terminal</code> constructor or using the <code>Terminal.setOption</code> function.</p>
<div id="options-container"></div>
</div>
<div>
<h3>Style</h3>
@@ -49,7 +34,9 @@
</div>
</div>
<hr/>
<p><strong>Attention:</strong> The demo is a barebones implementation and is designed for the development and evaluation of xterm.js only. Exposing the demo to the public as is would introduce security risks for the host.</p>
<button id="dispose" title="This is used to testing memory leaks">Dispose terminal</button>
<button id="webgl">Enable WebGL Renderer</button>
<script src="dist/client-bundle.js" defer ></script>
</body>
</html>
+14 -18
View File
@@ -1,16 +1,14 @@
/**
* WARNING: This demo is a barebones implementation designed for development and evaluation
* purposes only. It is definitely NOT production ready and does not aim to be so. Exposing the
* demo to the public as is would introduce security risks for the host.
**/
var express = require('express');
var expressWs = require('express-ws');
var os = require('os');
var pty = require('node-pty');
// Whether to use binary transport.
const USE_BINARY = os.platform() !== "win32";
/**
* Whether to use UTF8 binary transport.
* (Must also be switched in client.ts)
*/
const USE_BINARY_UTF8 = false;
function startServer() {
var app = express();
@@ -20,26 +18,24 @@ function startServer() {
logs = {};
app.use('/xterm.css', express.static(__dirname + '/../css/xterm.css'));
app.get('/logo.png', (req, res) => { // lgtm [js/missing-rate-limiting]
res.sendFile(__dirname + '/logo.png');
});
app.get('/logo.png', (req, res) => res.sendFile(__dirname + '/logo.png'));
app.get('/', (req, res) => { // lgtm [js/missing-rate-limiting]
app.get('/', function(req, res){
res.sendFile(__dirname + '/index.html');
});
app.get('/test', (req, res) => { // lgtm [js/missing-rate-limiting]
app.get('/test', function(req, res){
res.sendFile(__dirname + '/test.html');
});
app.get('/style.css', (req, res) => { // lgtm [js/missing-rate-limiting]
app.get('/style.css', function(req, res){
res.sendFile(__dirname + '/style.css');
});
app.use('/dist', express.static(__dirname + '/dist'));
app.use('/src', express.static(__dirname + '/src'));
app.post('/terminals', (req, res) => {
app.post('/terminals', function (req, res) {
const env = Object.assign({}, process.env);
env['COLORTERM'] = 'truecolor';
var cols = parseInt(req.query.cols),
@@ -50,7 +46,7 @@ function startServer() {
rows: rows || 24,
cwd: env.PWD,
env: env,
encoding: USE_BINARY ? null : 'utf8'
encoding: USE_BINARY_UTF8 ? null : 'utf8'
});
console.log('Created terminal with PID: ' + term.pid);
@@ -63,7 +59,7 @@ function startServer() {
res.end();
});
app.post('/terminals/:pid/size', (req, res) => {
app.post('/terminals/:pid/size', function (req, res) {
var pid = parseInt(req.params.pid),
cols = parseInt(req.query.cols),
rows = parseInt(req.query.rows),
@@ -112,7 +108,7 @@ function startServer() {
}
};
}
const send = USE_BINARY ? bufferUtf8(ws, 5) : buffer(ws, 5);
const send = USE_BINARY_UTF8 ? bufferUtf8(ws, 5) : buffer(ws, 5);
term.on('data', function(data) {
try {
-11
View File
@@ -30,14 +30,3 @@ p {
padding-left: 20px;
vertical-align: top;
}
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;
}
-1
View File
@@ -9,7 +9,6 @@
"xterm-addon-attach": ["../addons/xterm-addon-attach"],
"xterm-addon-fit": ["../addons/xterm-addon-fit"],
"xterm-addon-search": ["../addons/xterm-addon-search"],
"xterm-addon-serialize": ["../addons/xterm-addon-serialize"],
"xterm-addon-web-links": ["../addons/xterm-addon-web-links"],
"xterm-addon-webgl": ["../addons/xterm-addon-webgl"]
}
@@ -1,25 +0,0 @@
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB[5 A[5 A[5 A
@@ -1,25 +0,0 @@
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
@@ -1,25 +0,0 @@
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB[5 @[5 @[5 @
@@ -1,25 +0,0 @@
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
fghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
fghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
fghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
fghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
fghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
fghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
@@ -1,25 +0,0 @@
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB[5'}[5'}[5'}
@@ -1,25 +0,0 @@
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijk lmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
abcdefghijk lmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
abcdefghijk lmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
abcdefghijk lmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
abcdefghijk lmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
abcdefghijk lmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
@@ -1,25 +0,0 @@
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB[5'~[5'~[5'~
@@ -1,25 +0,0 @@
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijkqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijkqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijkqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijkqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijkqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijkqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
+22 -37
View File
@@ -1,7 +1,7 @@
{
"name": "xterm",
"description": "Full xterm terminal, in your browser",
"version": "4.5.0",
"version": "4.0.2",
"main": "lib/xterm.js",
"style": "css/xterm.css",
"types": "typings/xterm.d.ts",
@@ -11,61 +11,46 @@
"prepackage": "npm run build",
"package": "webpack",
"start": "node demo/start",
"lint": "eslint -c .eslintrc.json --ext .ts src/ addons/",
"lint": "tslint 'src/**/*.ts' 'addons/**/*.ts'",
"test": "npm run test-unit",
"posttest": "npm run lint",
"test-api": "npm run test-api-chromium",
"test-api-chromium": "mocha \"**/*.api.js\" --browser chromium --timeout 20000",
"test-api-firefox": "mocha \"**/*.api.js\" --browser firefox --timeout 20000",
"test-api-webkit": "mocha \"**/*.api.js\" --browser webkit --timeout 20000",
"pretest-api-chromium": "node ./bin/download_browser.js --browser chromium",
"pretest-api-firefox": "node ./bin/download_browser.js --browser firefox",
"pretest-api-webkit": "node ./bin/download_browser.js --browser webkit",
"test-api": "mocha \"**/*.api.js\"",
"test-unit": "node ./bin/test.js",
"test-unit-coverage": "node ./bin/test.js --coverage",
"build": "tsc -b ./tsconfig.all.json",
"prepare": "npm run setup",
"setup": "npm run build",
"presetup": "node ./bin/install-addons.js",
"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",
"clean": "rm -rf lib out addons/*/lib addons/*/out",
"vtfeatures": "node bin/extract_vtfeatures.js src/**/*.ts src/*.ts"
"clean": "rm -rf lib out addons/*/lib addons/*/out"
},
"devDependencies": {
"@types/chai": "^4.2.11",
"@types/debug": "^4.1.5",
"@types/deep-equal": "^1.0.1",
"@types/glob": "^7.1.1",
"@types/jsdom": "^16.2.0",
"@types/mocha": "^7.0.2",
"@types/node": "^10.17.17",
"@types/chai": "^3.4.34",
"@types/glob": "^5.0.35",
"@types/jsdom": "11.0.1",
"@types/mocha": "^2.2.33",
"@types/node": "6.0.108",
"@types/puppeteer": "^1.12.4",
"@types/utf8": "^2.1.6",
"@types/webpack": "^4.41.10",
"@types/ws": "^7.2.3",
"@typescript-eslint/eslint-plugin": "^2.27.0",
"@typescript-eslint/parser": "^2.27.0",
"chai": "^4.2.0",
"deep-equal": "^2.0.2",
"eslint": "^6.8.0",
"@types/webpack": "^4.4.11",
"@types/ws": "^6.0.1",
"chai": "3.5.0",
"express": "^4.17.1",
"express-ws": "^4.0.0",
"glob": "^7.0.5",
"jsdom": "^16.2.2",
"mocha": "^7.1.1",
"mustache": "^4.0.1",
"node-pty": "^0.9.0",
"nyc": "^15.0.1",
"playwright-core": "^0.11.1",
"jsdom": "^11.11.0",
"mocha": "^6.1.4",
"node-pty": "0.7.6",
"puppeteer": "^1.15.0",
"source-map-loader": "^0.2.4",
"ts-loader": "^6.0.4",
"typescript": "3.8",
"tslint": "^5.18.0",
"tslint-consistent-codestyle": "^1.13.0",
"typescript": "3.5",
"utf8": "^3.0.0",
"webpack": "^4.35.3",
"webpack-cli": "^3.3.11",
"webpack-cli": "^3.1.0",
"ws": "^7.0.0",
"xterm-benchmark": "^0.1.3"
}
-1
View File
@@ -56,7 +56,6 @@ export class AccessibilityManager extends Disposable {
this._rowContainer = document.createElement('div');
this._rowContainer.classList.add('xterm-accessibility-tree');
this._rowContainer.setAttribute('role', 'list');
this._rowElements = [];
for (let i = 0; i < this._terminal.rows; i++) {
this._rowElements[i] = this._createAccessibilityTreeNode();
+22 -325
View File
@@ -7,13 +7,13 @@ import { assert, expect } from 'chai';
import { InputHandler } from './InputHandler';
import { MockInputHandlingTerminal, TestTerminal } from './TestUtils.test';
import { Terminal } from './Terminal';
import { IBufferLine, IAttributeData } from 'common/Types';
import { IBufferLine } from 'common/Types';
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
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, MockCharsetService, MockUnicodeService } from 'common/TestUtils.test';
import { MockCoreService, MockBufferService, MockDirtyRowService, MockOptionsService, MockLogService, MockCoreMouseService } from 'common/TestUtils.test';
import { IBufferService } from 'common/services/Services';
import { DEFAULT_OPTIONS } from 'common/services/OptionsService';
import { clone } from 'common/Clone';
@@ -25,46 +25,34 @@ function getCursor(term: TestTerminal): number[] {
];
}
function getLines(term: TestTerminal, limit: number = term.rows): string[] {
const res: string[] = [];
for (let i = 0; i < limit; ++i) {
res.push(term.buffer.lines.get(i).translateToString(true));
}
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();
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 TestInputHandler(terminal, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any);
inputHandler.curAttrData.fg = 3;
const inputHandler = new InputHandler(terminal, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService());
// Save cursor position
inputHandler.saveCursor();
assert.equal(bufferService.buffer.x, 1);
assert.equal(bufferService.buffer.y, 2);
assert.equal(inputHandler.curAttrData.fg, 3);
assert.equal(terminal.curAttrData.fg, 3);
// Change cursor position
bufferService.buffer.x = 10;
bufferService.buffer.y = 20;
inputHandler.curAttrData.fg = 30;
terminal.curAttrData.fg = 30;
// Restore cursor position
inputHandler.restoreCursor();
assert.equal(bufferService.buffer.x, 1);
assert.equal(bufferService.buffer.y, 2);
assert.equal(inputHandler.curAttrData.fg, 3);
assert.equal(terminal.curAttrData.fg, 3);
});
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(), {} as any);
const inputHandler = new InputHandler(new MockInputHandlingTerminal(), new MockBufferService(80, 30), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), optionsService, new MockCoreMouseService());
inputHandler.setCursorStyle(Params.fromArray([0]));
assert.equal(optionsService.options['cursorStyle'], 'block');
@@ -105,7 +93,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(), {} as any);
const inputHandler = new InputHandler(terminal, new MockBufferService(80, 30), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService());
// Set bracketed paste mode
inputHandler.setModePrivate(Params.fromArray([2004]));
assert.equal(terminal.bracketedPasteMode, true);
@@ -124,7 +112,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(), {} as any);
const inputHandler = new InputHandler(term, bufferService, 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'));
@@ -162,7 +150,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(), {} as any);
const inputHandler = new InputHandler(term, bufferService, 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'));
@@ -203,7 +191,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(), {} as any);
const inputHandler = new InputHandler(term, bufferService, 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'));
@@ -232,7 +220,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(), {} as any);
const inputHandler = new InputHandler(term, bufferService, 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'));
@@ -367,7 +355,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(), {} as any);
const inputHandler = new InputHandler(term, new MockBufferService(80, 30), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService());
const container = new Uint32Array(10);
container[0] = 0x200B;
inputHandler.print(container, 0, 1);
@@ -382,7 +370,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(), {} as any);
handler = new InputHandler(term, bufferService, 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');
@@ -1137,6 +1125,13 @@ describe('InputHandler', () => {
beforeEach(() => {
term = new TestTerminal({cols: 10, rows: 10});
});
function getLines(term: TestTerminal, limit: number = term.rows): string[] {
const res: string[] = [];
for (let i = 0; i < limit; ++i) {
res.push(term.buffer.lines.get(i).translateToString(true));
}
return res;
}
it('scrollUp', () => {
term.writeSync('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[2;4r\x1b[2Sm');
assert.deepEqual(getLines(term), ['m', '3', '', '', '4', '5', '6', '7', '8', '9']);
@@ -1196,302 +1191,4 @@ describe('InputHandler', () => {
assert.deepEqual(getLines(term), ['0', '1', 'n', 'm', '', '', '6', '7', '8', '9']);
});
});
describe('SL/SR/DECIC/DECDC', () => {
let term: TestTerminal;
beforeEach(() => {
term = new TestTerminal({cols: 5, rows: 5, scrollback: 1});
});
it('SL (scrollLeft)', () => {
term.writeSync('12345'.repeat(6));
term.writeSync('\x1b[ @');
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '2345', '2345', '2345', '2345', '2345']);
term.writeSync('\x1b[0 @');
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '345', '345', '345', '345', '345']);
term.writeSync('\x1b[2 @');
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '5', '5', '5', '5', '5']);
});
it('SR (scrollRight)', () => {
term.writeSync('12345'.repeat(6));
term.writeSync('\x1b[ A');
assert.deepEqual(getLines(term, term.rows + 1), ['12345', ' 1234', ' 1234', ' 1234', ' 1234', ' 1234']);
term.writeSync('\x1b[0 A');
assert.deepEqual(getLines(term, term.rows + 1), ['12345', ' 123', ' 123', ' 123', ' 123', ' 123']);
term.writeSync('\x1b[2 A');
assert.deepEqual(getLines(term, term.rows + 1), ['12345', ' 1', ' 1', ' 1', ' 1', ' 1']);
});
it('insertColumns (DECIC)', () => {
term.writeSync('12345'.repeat(6));
term.writeSync('\x1b[3;3H');
term.writeSync('\x1b[\'}');
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']);
term.reset();
term.writeSync('12345'.repeat(6));
term.writeSync('\x1b[3;3H');
term.writeSync('\x1b[1\'}');
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']);
term.reset();
term.writeSync('12345'.repeat(6));
term.writeSync('\x1b[3;3H');
term.writeSync('\x1b[2\'}');
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '12 3', '12 3', '12 3', '12 3', '12 3']);
});
it('deleteColumns (DECDC)', () => {
term.writeSync('12345'.repeat(6));
term.writeSync('\x1b[3;3H');
term.writeSync('\x1b[\'~');
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '1245', '1245', '1245', '1245', '1245']);
term.reset();
term.writeSync('12345'.repeat(6));
term.writeSync('\x1b[3;3H');
term.writeSync('\x1b[1\'~');
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '1245', '1245', '1245', '1245', '1245']);
term.reset();
term.writeSync('12345'.repeat(6));
term.writeSync('\x1b[3;3H');
term.writeSync('\x1b[2\'~');
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '125', '125', '125', '125', '125']);
});
});
it('should parse big chunks in smaller subchunks', () => {
// max single chunk size is hardcoded as 131072
const calls: any[] = [];
const term = new TestTerminal({cols: 10, rows: 10});
(term as any)._inputHandler._parser.parse = (data: Uint32Array, length: number) => {
calls.push([data.length, length]);
};
term.writeSync('12345');
term.writeSync('a'.repeat(10000));
term.writeSync('a'.repeat(200000));
term.writeSync('a'.repeat(300000));
assert.deepEqual(calls, [
[4096, 5],
[10000, 10000],
[131072, 131072], [131072, 200000 - 131072],
[131072, 131072], [131072, 131072], [131072, 300000 - 131072 - 131072]
]);
});
describe('windowOptions', () => {
it('all should be disabled by default and not report', () => {
const term = new TestTerminal({cols: 10, rows: 10});
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, windowOptions: {getWinSizePixels: true}});
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, windowOptions: {getCellSizePixels: true}});
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, windowOptions: {getWinSizeChars: true}});
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('22/23 - PushTitle/PopTitle', () => {
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');
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, windowOptions: {pushTitle: true, popTitle: true}});
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, windowOptions: {pushTitle: true, popTitle: true}});
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, windowOptions: {setWinLines: true}});
term2.writeSync('\x1b[?3l');
assert.equal((term2 as any)._bufferService.cols, 80);
term2.writeSync('\x1b[?3h');
assert.equal((term2 as any)._bufferService.cols, 132);
});
});
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), ['¥¥ ¥¥', '¥¥ ¥', '¥¥ ¥', '¥¥¥¥¥', '']);
});
});
describe('DECSTR', () => {
let term: TestTerminal;
beforeEach(() => {
term = new TestTerminal({cols: 10, rows: 5, scrollback: 1});
term.writeSync('01234567890123');
});
it('should reset IRM', () => {
term.writeSync('\x1b[4h');
assert.equal(term.insertMode, true);
term.writeSync('\x1b[!p');
assert.equal(term.insertMode, false);
});
it('should reset cursor visibility', () => {
term.writeSync('\x1b[?25l');
assert.equal((term as any)._coreService.isCursorHidden, true);
term.writeSync('\x1b[!p');
assert.equal((term as any)._coreService.isCursorHidden, false);
});
it('should reset scroll margins', () => {
term.writeSync('\x1b[2;4r');
assert.equal((term as any)._bufferService.buffer.scrollTop, 1);
assert.equal((term as any)._bufferService.buffer.scrollBottom, 3);
term.writeSync('\x1b[!p');
assert.equal((term as any)._bufferService.buffer.scrollTop, 0);
assert.equal((term as any)._bufferService.buffer.scrollBottom, term.rows - 1);
});
it('should reset text attributes', () => {
term.writeSync('\x1b[1;2;32;43m');
assert.equal(!!term.curAttrData.isBold(), true);
term.writeSync('\x1b[!p');
assert.equal(!!term.curAttrData.isBold(), false);
assert.equal(term.curAttrData.fg, 0);
assert.equal(term.curAttrData.bg, 0);
});
it('should reset DECSC data', () => {
term.writeSync('\x1b7');
assert.equal((term as any)._bufferService.buffer.savedX, 4);
assert.equal((term as any)._bufferService.buffer.savedY, 1);
term.writeSync('\x1b[!p');
assert.equal((term as any)._bufferService.buffer.savedX, 0);
assert.equal((term as any)._bufferService.buffer.savedY, 0);
});
it('should reset DECOM', () => {
term.writeSync('\x1b[?6h');
assert.equal((term as any)._coreService.decPrivateModes.origin, true);
term.writeSync('\x1b[!p');
assert.equal((term as any)._coreService.decPrivateModes.origin, false);
});
});
});
+252 -896
View File
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More