diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index e5736562..141f7711 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM node:10 +FROM node:14 # Configure apt ENV DEBIAN_FRONTEND=noninteractive diff --git a/.eslintrc.json b/.eslintrc.json index 8c416f15..6031c195 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -43,10 +43,11 @@ "@typescript-eslint/array-type": [ "warn", { - "default": "array-simple", + "default": "array", "readonly": "generic" } ], + "@typescript-eslint/consistent-type-assertions": "warn", "@typescript-eslint/consistent-type-definitions": "warn", "@typescript-eslint/explicit-function-return-type": [ "warn", @@ -151,6 +152,10 @@ "warn", "never" ], + "object-curly-spacing": [ + "warn", + "always" + ], "prefer-const": "warn", "spaced-comment": [ "warn", @@ -160,5 +165,13 @@ "exceptions": ["-"] } ] - } + }, + "overrides": [ + { + "files": ["**/*.test.ts"], + "rules": { + "object-curly-spacing": "off" + } + } + ] } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 2f752faf..4d268f61 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,51 +1,70 @@ { - "version": "2.0.0", - "presentation": { - "echo": false, - "reveal": "always", - "focus": false, - "panel": "dedicated", - "showReuseMessage": true - }, - "tasks": [ - { - "type": "npm", - "script": "test", - "group": "test", - "problemMatcher": [] - }, - { - "type": "npm", - "script": "watch", - "group": "build", - "isBackground": true, - "problemMatcher": [], - "presentation": { - "group": "vscode" - } - }, - { - "type": "npm", - "script": "start", - "group": "build", - "isBackground": true, - "problemMatcher": [], - "presentation": { - "group": "vscode" - } - }, - { - "label": "Start demo", - "dependsOn": ["npm: watch", "npm: start"], - "group": { - "kind": "build", - "isDefault": true - }, - "isBackground": true, - "problemMatcher": [], - "presentation": { - "group": "vscode" - } - } - ] + "version": "2.0.0", + "presentation": { + "echo": false, + "reveal": "always", + "focus": false, + "panel": "dedicated", + "showReuseMessage": true + }, + "tasks": [ + { + "type": "npm", + "script": "test", + "group": "test", + "problemMatcher": [] + }, + { + "type": "npm", + "script": "watch", + "group": "build", + "isBackground": true, + "problemMatcher": "$tsc-watch", + "presentation": { + "group": "vscode" + } + }, + { + "type": "npm", + "script": "start", + "dependsOn": "npm: watch", + "group": "build", + "isBackground": true, + "problemMatcher": [], + "presentation": { + "group": "vscode" + } + }, + { + "label": "Start demo", + "dependsOn": "npm: start", + "group": { + "kind": "build", + "isDefault": true + }, + "isBackground": true, + "problemMatcher": { + "owner": "typescript", + "fileLocation": [ + "relative", + "${workspaceFolder}" + ], + "pattern": [ + { + "regexp": "^([^\\\\s].*)\\\\((\\\\d+,\\\\d+)\\\\):\\\\s*(.*)$", + "file": 1, + "location": 2, + "message": 3 + } + ], + "background": { + "beginsPattern": "assets by", + "endsPattern": "webpack \\d+\\.\\d+\\.\\d+ compiled successfully" + } + }, + "presentation": { + "group": "vscode" + } + } + ] } diff --git a/README.md b/README.md index 0e7df5ac..0ac211ed 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,11 @@ Xterm.js is a front-end component written in TypeScript that lets applications b ## Features -- **Terminal apps just work**: Xterm.js works with most terminal apps such as `bash`, `vim` and `tmux`, this includes support for curses-based apps and mouse event support. +- **Terminal apps just work**: Xterm.js works with most terminal apps such as `bash`, `vim`, and `tmux`, including support for curses-based apps and mouse events. - **Performant**: Xterm.js is *really* fast, it even includes a GPU-accelerated renderer. -- **Rich unicode support**: Supports CJK, emojis and IMEs. +- **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 and minimum contrast ratio support can be turned on. - **And much more**: Links, theming, addons, well documented API, etc. ## What xterm.js is not @@ -20,13 +20,13 @@ Xterm.js is a front-end component written in TypeScript that lets applications b ## Getting Started -First you need to install the module, we ship exclusively through [npm](https://www.npmjs.com/) so you need that installed and then add xterm.js as a dependency by running: +First, you need to install the module, we ship exclusively through [npm](https://www.npmjs.com/), so you need that installed and then add xterm.js as a dependency by running: -``` +```bash npm install xterm ``` -To start using xterm.js on your browser, add the `xterm.js` and `xterm.css` to the head of your html page. Then create a `
` onto which xterm can attach itself. Finally instantiate the `Terminal` object and then call the `open` function with the DOM object of the `div`. +To start using xterm.js on your browser, add the `xterm.js` and `xterm.css` to the head of your HTML page. Then create a `
` onto which xterm can attach itself. Finally, instantiate the `Terminal` object and then call the `open` function with the DOM object of the `div`. ```html @@ -58,7 +58,7 @@ import { Terminal } from 'xterm'; ⚠️ *This section describes the new addon format introduced in v3.14.0, see [here](https://github.com/xtermjs/xterm.js/blob/3.14.2/README.md#addons) for the instructions on the old format* -Addons are separate modules that extend the `Terminal` by building on the [xterm.js API](https://github.com/xtermjs/xterm.js/blob/master/typings/xterm.d.ts). To use an addon you first need to install it in your project: +Addons are separate modules that extend the `Terminal` by building on the [xterm.js API](https://github.com/xtermjs/xterm.js/blob/master/typings/xterm.d.ts). To use an addon, you first need to install it in your project: ```bash npm i -S xterm-addon-web-links @@ -76,7 +76,7 @@ const terminal = new Terminal(); terminal.loadAddon(new WebLinksAddon()); ``` -The xterm.js team maintains the following addons but they can be built by anyone: +The xterm.js team maintains the following addons, but anyone can build them: - [`xterm-addon-attach`](https://github.com/xtermjs/xterm.js/tree/master/addons/xterm-addon-attach): Attaches to a server running a process via a websocket - [`xterm-addon-fit`](https://github.com/xtermjs/xterm.js/tree/master/addons/xterm-addon-fit): Fits the terminal to the containing element @@ -85,23 +85,23 @@ The xterm.js team maintains the following addons but they can be built by anyone ## Browser Support -Since xterm.js is typically implemented as a developer tool, only modern browsers are supported officially. Specifically the latest versions of *Chrome*, *Edge*, *Firefox* and *Safari*. +Since xterm.js is typically implemented as a developer tool, only modern browsers are supported officially. Specifically the latest versions of *Chrome*, *Edge*, *Firefox*, and *Safari*. We also partially support *Internet Explorer 11*, meaning xterm.js should work for the most part, but we reserve the right to not provide workarounds specifically for it unless it's absolutely necessary to get the basic input/output flow working. -Xterm.js works seamlessly in [Electron](https://electronjs.org/) apps and may even work on earlier versions of the browsers, these are the versions we strive to keep working. +Xterm.js works seamlessly in [Electron](https://electronjs.org/) apps and may even work on earlier versions of the browsers. These are the versions we strive to keep working. ## API The full API for xterm.js is contained within the [TypeScript declaration file](https://github.com/xtermjs/xterm.js/blob/master/typings/xterm.d.ts), use the branch/tag picker in GitHub (`w`) to navigate to the correct version of the API. -Note that some APIs are marked *experimental*, these are added to enable experimentation with new ideas without committing to support it like a normal [semver](https://semver.org/) API. Note that these APIs can change radically between versions so be sure to read release notes if you plan on using experimental APIs. +Note that some APIs are marked *experimental*, these are added to enable experimentation with new ideas without committing to support it like a normal [semver](https://semver.org/) API. Note that these APIs can change radically between versions, so be sure to read release notes if you plan on using experimental APIs. ## Real-world uses Xterm.js is used in several world-class applications to provide great terminal experiences. - [**SourceLair**](https://www.sourcelair.com/): In-browser IDE that provides its users with fully-featured Linux terminals based on xterm.js. -- [**Microsoft Visual Studio Code**](http://code.visualstudio.com/): Modern, versatile and powerful open source code editor that provides an integrated terminal based on xterm.js. +- [**Microsoft Visual Studio Code**](http://code.visualstudio.com/): Modern, versatile, and powerful open source code editor that provides an integrated terminal based on xterm.js. - [**ttyd**](https://github.com/tsl0922/ttyd): A command-line tool for sharing terminal over the web, with fully-featured terminal emulation based on xterm.js. - [**Katacoda**](https://www.katacoda.com/): Katacoda is an Interactive Learning Platform for software developers, covering the latest Cloud Native technologies. - [**Eclipse Che**](http://www.eclipse.org/che): Developer workspace server, cloud IDE, and Eclipse next-generation IDE. @@ -113,10 +113,10 @@ Xterm.js is used in several world-class applications to provide great terminal e - [**Next Tech**](https://next.tech "Next Tech"): Online platform for interactive coding and web development courses. Live container-backed terminal uses xterm.js. - [**RStudio**](https://www.rstudio.com/products/RStudio "RStudio"): RStudio is an integrated development environment (IDE) for R. - [**Terminal for Atom**](https://github.com/jsmecham/atom-terminal-tab): A simple terminal for the Atom text editor. -- [**Eclipse Orion**](https://orionhub.org): A modern, open source software development environment that runs in the cloud. Code, deploy and run in the cloud. +- [**Eclipse Orion**](https://orionhub.org): A modern, open source software development environment that runs in the cloud. Code, deploy, and run in the cloud. - [**Gravitational Teleport**](https://github.com/gravitational/teleport): Gravitational Teleport is a modern SSH server for remotely accessing clusters of Linux servers via SSH or HTTPS. - [**Hexlet**](https://en.hexlet.io): Practical programming courses (JavaScript, PHP, Unix, databases, functional programming). A steady path from the first line of code to the first job. -- [**Selenoid UI**](https://github.com/aerokube/selenoid-ui): Simple UI for the scallable golang implementation of Selenium Hub named Selenoid. We use XTerm for streaming logs over websockets from docker containers. +- [**Selenoid UI**](https://github.com/aerokube/selenoid-ui): Simple UI for the scalable golang implementation of Selenium Hub named Selenoid. We use XTerm for streaming logs over websockets from docker containers. - [**Portainer**](https://portainer.io): Simple management UI for Docker. - [**SSHy**](https://github.com/stuicey/SSHy): HTML5 Based SSHv2 Web Client with E2E encryption utilising xterm.js, SJCL & websockets. - [**JupyterLab**](https://github.com/jupyterlab/jupyterlab): An extensible computational environment for Jupyter, supporting interactive data science and scientific computing across all programming languages. @@ -143,7 +143,6 @@ Xterm.js is used in several world-class applications to provide great terminal e - [**cPanel & WHM**](https://cpanel.com): The hosting platform of choice. - [**Nutanix**](https://github.com/nutanix): Nutanix Enterprise Cloud uses xterm in the webssh functionality within Nutanix Calm, and is also looking to move our old noserial (termjs) functionality to xterm.js. - [**SSH Web Client**](https://github.com/roke22/PHP-SSH2-Web-Client): SSH Web Client with PHP. -- [**Shellvault**](https://www.shellvault.io): The cloud-based SSH terminal you can access from anywhere. - [**Juno**](http://junolab.org/): A flexible Julia IDE, based on Atom. - [**webssh**](https://github.com/huashengdun/webssh): Web based ssh client. - [**info-beamer hosted**](https://info-beamer.com): Uses xterm.js to manage digital signage devices from the web dashboard. @@ -167,12 +166,19 @@ Xterm.js is used in several world-class applications to provide great terminal e - [**Laravel Ssh Web Client**](https://github.com/roke22/Laravel-ssh-client): Laravel server inventory with ssh web client to connect at server using xterm.js - [**Repl.it**](https://repl.it): Collaborative browser based IDE with support for 50+ different languages. - [**TeleType**](https://github.com/akshaykmr/TeleType): cli tool that allows you to share your terminal online conveniently. Show off mad cli-fu, help a colleague, teach, or troubleshoot. -- [**Intervue**](https://www.intervue.io): Pair programming for interviews. Multiple programming languages supported, with results displayed by xterm.js. -- [**TRASA**](https://trasa.io): Zero trust access to Web, SSH, RDP and Database services. +- [**Intervue**](https://www.intervue.io): Pair programming for interviews. Multiple programming languages are supported, with results displayed by xterm.js. +- [**TRASA**](https://trasa.io): Zero trust access to Web, SSH, RDP, and Database services. - [**Commas**](https://github.com/CyanSalt/commas): Commas is a hackable terminal and command runner. -[And much more...](https://github.com/xtermjs/xterm.js/network/dependents) +- [**Devtron**](https://github.com/devtron-labs/devtron): Software Delivery Workflow For Kubernetes. +- [**NxShell**](https://github.com/nxshell/nxshell): An easy to use new terminal for SSH. +- [**gifcast**](https://dstein64.github.io/gifcast/): Converts an asciinema cast to an animated GIF. +- [**WizardWebssh**](https://gitlab.com/mikeramsey/wizardwebssh): A terminal with Pyqt5 Widget for embedding, which can be used as an ssh client to connect to your ssh servers. It is written in Python, based on tornado, paramiko, and xterm.js. +- [**Wizard Assistant**](https://wizardassistant.com/): Wizard Assistant comes with advanced automation tools, preloaded common and special time-saving commands, and a built-in SSH terminal. Now you can remotely administer, troubleshoot, and analyze any system with ease. +- [**ucli**](https://github.com/tsadarsh/ucli): Command Line for everyone :family_man_woman_girl_boy: at [www.ucli.tech](https://www.ucli.tech). +- [**Tess**](https://github.com/SquitchYT/Tess/): Simple Terminal Fully Customizable for Everyone. +- [And much more...](https://github.com/xtermjs/xterm.js/network/dependents) -Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it in our list. Note: Please add any new contributions to the end of the list only. +Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it on our list. Note: Please add any new contributions to the end of the list only. ## Releases @@ -182,21 +188,21 @@ All current and past releases are available on this repo's [Releases page](https ### Beta builds -Our CI releases beta builds to npm for every change that goes into master, install the latest beta build with: +Our CI releases beta builds to npm for every change that goes into master. Install the latest beta build with: -``` +```bash npm install -S xterm@beta ``` -These should generally be stable but some bugs may slip in, we recommend using the beta build primarily to test out new features and for verifying bug fixes. +These should generally be stable, but some bugs may slip in. We recommend using the beta build primarily to test out new features and to verify bug fixes. ## Contributing -You can read the [guide on the wiki](https://github.com/xtermjs/xterm.js/wiki/Contributing) to learn how to contribute and setup xterm.js for development. +You can read the [guide on the wiki](https://github.com/xtermjs/xterm.js/wiki/Contributing) to learn how to contribute and set up xterm.js for development. ## License Agreement -If you contribute code to this project, you are implicitly allowing your code to be distributed under the MIT license. You are also implicitly verifying that all code is your original work. +If you contribute code to this project, you implicitly allow your code to be distributed under the MIT license. You are also implicitly verifying that all code is your original work. Copyright (c) 2017-2019, [The xterm.js authors](https://github.com/xtermjs/xterm.js/graphs/contributors) (MIT License)
Copyright (c) 2014-2017, SourceLair, Private Company ([www.sourcelair.com](https://www.sourcelair.com/home)) (MIT License)
diff --git a/addons/xterm-addon-attach/src/AttachAddon.ts b/addons/xterm-addon-attach/src/AttachAddon.ts index 279d1b2e..9fbd796b 100644 --- a/addons/xterm-addon-attach/src/AttachAddon.ts +++ b/addons/xterm-addon-attach/src/AttachAddon.ts @@ -20,7 +20,7 @@ export class AttachAddon implements ITerminalAddon { this._socket = socket; // always set binary type to arraybuffer, we do not handle blobs this._socket.binaryType = 'arraybuffer'; - this._bidirectional = (options && options.bidirectional === false) ? false : true; + this._bidirectional = !(options && options.bidirectional === false); } public activate(terminal: Terminal): void { @@ -41,7 +41,9 @@ export class AttachAddon implements ITerminalAddon { } public dispose(): void { - this._disposables.forEach(d => d.dispose()); + for (const d of this._disposables) { + d.dispose(); + } } private _sendData(data: string): void { diff --git a/addons/xterm-addon-attach/test/AttachAddon.api.ts b/addons/xterm-addon-attach/test/AttachAddon.api.ts index 41e579f5..ef26cfd2 100644 --- a/addons/xterm-addon-attach/test/AttachAddon.api.ts +++ b/addons/xterm-addon-attach/test/AttachAddon.api.ts @@ -7,7 +7,7 @@ import WebSocket = require('ws'); import { openTerminal, pollFor, getBrowserType } from '../../../out-test/api/TestUtils'; import { Browser, Page } from 'playwright'; -const APP = 'http://127.0.0.1:3000/test'; +const APP = 'http://127.0.0.1:3001/test'; let browser: Browser; let page: Page; @@ -18,7 +18,7 @@ describe('AttachAddon', () => { before(async function(): Promise { const browserType = getBrowserType(); browser = await browserType.launch({ - headless: process.argv.indexOf('--headless') !== -1 + headless: process.argv.includes('--headless') }); page = await (await browser.newContext()).newPage(); await page.setViewportSize({ width, height }); diff --git a/addons/xterm-addon-fit/package.json b/addons/xterm-addon-fit/package.json index 859d6ebf..f9d96c4a 100644 --- a/addons/xterm-addon-fit/package.json +++ b/addons/xterm-addon-fit/package.json @@ -1,6 +1,6 @@ { "name": "xterm-addon-fit", - "version": "0.4.0", + "version": "0.5.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" diff --git a/addons/xterm-addon-fit/test/FitAddon.api.ts b/addons/xterm-addon-fit/test/FitAddon.api.ts index 1111dff0..8859b5a6 100644 --- a/addons/xterm-addon-fit/test/FitAddon.api.ts +++ b/addons/xterm-addon-fit/test/FitAddon.api.ts @@ -7,7 +7,7 @@ import { assert } from 'chai'; import { openTerminal, getBrowserType } from '../../../out-test/api/TestUtils'; import { Browser, Page } from 'playwright'; -const APP = 'http://127.0.0.1:3000/test'; +const APP = 'http://127.0.0.1:3001/test'; let browser: Browser; let page: Page; @@ -18,7 +18,7 @@ describe('FitAddon', () => { before(async function(): Promise { const browserType = getBrowserType(); browser = await browserType.launch({ - headless: process.argv.indexOf('--headless') !== -1 + headless: process.argv.includes('--headless') }); page = await (await browser.newContext()).newPage(); await page.setViewportSize({ width, height }); @@ -45,7 +45,7 @@ describe('FitAddon', () => { describe('proposeDimensions', () => { afterEach(async () => { - return unloadFit(); + return await unloadFit(); }); it('default', async function(): Promise { @@ -84,7 +84,7 @@ describe('FitAddon', () => { describe('fit', () => { afterEach(async () => { - return unloadFit(); + return await unloadFit(); }); it('default', async function(): Promise { diff --git a/addons/xterm-addon-ligatures/README.md b/addons/xterm-addon-ligatures/README.md index 6336bf83..8f869d73 100644 --- a/addons/xterm-addon-ligatures/README.md +++ b/addons/xterm-addon-ligatures/README.md @@ -51,3 +51,4 @@ This package makes use of the following fonts for testing: [Fira Code License]: https://github.com/tonsky/FiraCode/blob/master/LICENSE [Iosevka]: https://github.com/be5invis/Iosevka [Iosevka License]: https://github.com/be5invis/Iosevka/blob/master/LICENSE.md + diff --git a/addons/xterm-addon-ligatures/package.json b/addons/xterm-addon-ligatures/package.json index a35cfc5c..ecbcc006 100644 --- a/addons/xterm-addon-ligatures/package.json +++ b/addons/xterm-addon-ligatures/package.json @@ -1,6 +1,6 @@ { "name": "xterm-addon-ligatures", - "version": "0.3.0", + "version": "0.5.1", "description": "Add support for programming ligatures to xterm.js", "author": { "name": "The xterm.js authors", @@ -32,7 +32,7 @@ "license": "MIT", "dependencies": { "font-finder": "^1.1.0", - "font-ligatures": "^1.3.3" + "font-ligatures": "^1.4.0" }, "devDependencies": { "@types/sinon": "^5.0.1", diff --git a/addons/xterm-addon-ligatures/src/font.ts b/addons/xterm-addon-ligatures/src/font.ts index 825fc797..abdbbc04 100644 --- a/addons/xterm-addon-ligatures/src/font.ts +++ b/addons/xterm-addon-ligatures/src/font.ts @@ -3,12 +3,28 @@ * @license MIT */ -import * as fontFinder from 'font-finder'; -import * as fontLigatures from 'font-ligatures'; +import { FontList } from 'font-finder'; +import { Font, loadBuffer, loadFile } from 'font-ligatures'; import parse from './parse'; -let fontsPromise: Promise | undefined = undefined; +interface IFontMetadata { + family: string; + fullName: string; + postscriptName: string; + blob: () => Promise; +} + +interface IFontAccessNavigator { + fonts: { + query: () => Promise; + }; + permissions: { + request?: (permission: { name: string }) => Promise<{state: string}>; + }; +} + +let fontsPromise: Promise> | undefined = undefined; /** * Loads the font ligature wrapper for the specified font family if it could be @@ -16,9 +32,50 @@ let fontsPromise: Promise | undefined = undefined; * @param fontFamily The CSS font family definition to resolve * @param cacheSize The size of the ligature cache to maintain if the font is resolved */ -export default async function load(fontFamily: string, cacheSize: number): Promise { +export default async function load(fontFamily: string, cacheSize: number): Promise { if (!fontsPromise) { - fontsPromise = fontFinder.list(); + // Web environment that supports font access API + if (typeof navigator !== 'undefined' && 'fonts' in navigator) { + try { + const status = await (navigator as unknown as IFontAccessNavigator).permissions.request?.({ + name: 'local-fonts' + }); + if (status && status.state !== 'granted') { + throw new Error('Permission to access local fonts not granted.'); + } + } catch (err) { + // A `TypeError` indicates the 'local-fonts' + // permission is not yet implemented, so + // only `throw` if this is _not_ the problem. + if (err.name !== 'TypeError') { + throw err; + } + } + const fonts: Record = {}; + try { + const fontsIterator = await (navigator as unknown as IFontAccessNavigator).fonts.query(); + for (const metadata of fontsIterator) { + if (!fonts.hasOwnProperty(metadata.family)) { + fonts[metadata.family] = []; + } + fonts[metadata.family].push(metadata); + } + fontsPromise = Promise.resolve(fonts); + } catch (err) { + console.error(err.name, err.message); + } + } + // Node environment or no font access API + else { + try { + fontsPromise = (await import('font-finder')).list(); + } catch (err) { + // No-op + } + } + if (!fontsPromise) { + fontsPromise = Promise.resolve({}); + } } const fonts = await fontsPromise; @@ -31,7 +88,11 @@ export default async function load(fontFamily: string, cacheSize: number): Promi } if (fonts.hasOwnProperty(family) && fonts[family].length > 0) { - return await fontLigatures.loadFile(fonts[family][0].path, { cacheSize }); + const font = fonts[family][0]; + if ('blob' in font) { + return loadBuffer(await (await font.blob()).arrayBuffer(), { cacheSize }); + } + return await loadFile(font.path, { cacheSize }); } } diff --git a/addons/xterm-addon-ligatures/src/parse.ts b/addons/xterm-addon-ligatures/src/parse.ts index 5ad7747f..6289e468 100644 --- a/addons/xterm-addon-ligatures/src/parse.ts +++ b/addons/xterm-addon-ligatures/src/parse.ts @@ -68,7 +68,7 @@ function parseString(context: IParseContext, quoteChar: '\'' | '"'): string { while (context.offset < context.input.length) { const char = context.input[context.offset++]; if (escaped) { - if (/[0-9a-fA-F]/.test(char)) { + if (/[\dA-Fa-f]/.test(char)) { // Unicode escape context.offset--; str += parseUnicode(context); @@ -107,7 +107,7 @@ function parseIdentifier(context: IParseContext): string { while (context.offset < context.input.length) { const char = context.input[context.offset++]; if (escaped) { - if (/[0-9a-fA-F]/.test(char)) { + if (/[\dA-Fa-f]/.test(char)) { // Unicode escape context.offset--; str += parseUnicode(context); @@ -156,7 +156,7 @@ function parseUnicode(context: IParseContext): string { // of the escape and is swallowed. return unicodeToString(str); } - if (str.length >= 6 || !/[0-9a-fA-F]/.test(char)) { + if (str.length >= 6 || !/[\dA-Fa-f]/.test(char)) { // If the next character is not a valid hex digit or we have reached the // maximum of 6 digits in the escape, terminate the escape. context.offset--; diff --git a/addons/xterm-addon-ligatures/webpack.config.js b/addons/xterm-addon-ligatures/webpack.config.js index 253e1207..ea69841e 100644 --- a/addons/xterm-addon-ligatures/webpack.config.js +++ b/addons/xterm-addon-ligatures/webpack.config.js @@ -30,7 +30,18 @@ module.exports = { }, mode: 'production', externals: { - 'font-finder':'font-finder', - 'font-ligatures':'font-ligatures' + 'font-finder': 'font-finder', + 'stream': 'stream', + 'os': 'os', + 'util': 'util' + }, + resolve: { + // The ligature modules contains fallbacks for node environments, we never want to browserify them + fallback: { + stream: false, + util: false, + os: false, + path: false + } } }; diff --git a/addons/xterm-addon-ligatures/yarn.lock b/addons/xterm-addon-ligatures/yarn.lock index d8e64e49..2aac858b 100644 --- a/addons/xterm-addon-ligatures/yarn.lock +++ b/addons/xterm-addon-ligatures/yarn.lock @@ -78,20 +78,28 @@ follow-redirects@1.5.10: dependencies: debug "=3.1.0" -font-finder@^1.0.3, font-finder@^1.0.4: +font-finder@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/font-finder/-/font-finder-1.0.4.tgz#2ca944954dd8d0e1b5bdc4c596cc08607761d89b" dependencies: get-system-fonts "^2.0.0" promise-stream-reader "^1.0.1" -font-ligatures@^1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/font-ligatures/-/font-ligatures-1.3.3.tgz#63fff18dc8adb3a11fe5eec1f4e8d7edfa8075b9" - integrity sha512-NSGpHgVNX81M7AWS1XylK1UZbN3QllfUIDAAuPv6TUcl5O2b781JcKS5L2RopAU0AqlTyX3ZuX/04eaMpbVrHA== +font-finder@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/font-finder/-/font-finder-1.1.0.tgz#2bff2b2762acba720239c8bec898a96daae90858" + integrity sha512-wpCL2uIbi6GurJbU7ZlQ3nGd61Ho+dSU6U83/xJT5UPFfN35EeCW/rOtS+5k+IuEZu2SYmHzDIPL9eA5tSYRAw== + dependencies: + get-system-fonts "^2.0.0" + promise-stream-reader "^1.0.1" + +font-ligatures@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/font-ligatures/-/font-ligatures-1.4.0.tgz#6a7b370d96be1358dddfad67830e82fbfd59e6dc" + integrity sha512-n7DFnnEpJ0NrVoLqZIL4tMGVs+CnFwQc92m80LWyrbgAFO4x234+t2/H9o4eOYA1eh6ta9dZAEEsJAwsBdNezA== dependencies: font-finder "^1.0.3" - lru-cache "^4.1.3" + lru-cache "^6.0.0" opentype.js "^0.8.0" get-system-fonts@^2.0.0: @@ -120,9 +128,9 @@ lodash.get@^4.4.2: resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" lodash@^4.17.15: - version "4.17.19" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" - integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== lolex@^2.7.5: version "2.7.5" @@ -136,12 +144,12 @@ lolex@^5.0.1: dependencies: "@sinonjs/commons" "^1.7.0" -lru-cache@^4.1.3: - version "4.1.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c" +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== dependencies: - pseudomap "^1.0.2" - yallist "^2.1.2" + yallist "^4.0.0" minimist@^1.2.5: version "1.2.5" @@ -190,10 +198,6 @@ promise-stream-reader@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/promise-stream-reader/-/promise-stream-reader-1.0.1.tgz#4e793a79c9d49a73ccd947c6da9c127f12923649" -pseudomap@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" - sinon@6.3.5: version "6.3.5" resolved "https://registry.yarnpkg.com/sinon/-/sinon-6.3.5.tgz#0f6d6a5b4ebaad1f6e8e019395542d1d02c144a0" @@ -224,9 +228,10 @@ type-detect@4.0.8, type-detect@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" -yallist@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== yauzl@^2.10.0: version "2.10.0" diff --git a/addons/xterm-addon-search/package.json b/addons/xterm-addon-search/package.json index 711335cb..659bd834 100644 --- a/addons/xterm-addon-search/package.json +++ b/addons/xterm-addon-search/package.json @@ -1,6 +1,6 @@ { "name": "xterm-addon-search", - "version": "0.7.0", + "version": "0.8.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index f5505689..4a73d998 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -158,8 +158,15 @@ export class SearchAddon implements ITerminalAddon { }; if (incremental) { + // Try to expand selection to right first. result = this._findInLine(term, searchPosition, searchOptions, false); - if (!(result && result.row === startRow && result.col === startCol)) { + const isOldResultHighlighted = result && result.row === startRow && result.col === startCol; + if (!isOldResultHighlighted) { + // If selection was not able to be expanded to the right, then try reverse search + if (currentSelection) { + searchPosition.startRow = currentSelection.endRow; + searchPosition.startCol = currentSelection.endColumn; + } result = this._findInLine(term, searchPosition, searchOptions, true); } } else { @@ -233,8 +240,8 @@ export class SearchAddon implements ITerminalAddon { * @param term the substring that starts at searchIndex */ private _isWholeWord(searchIndex: number, line: string, term: string): boolean { - return (((searchIndex === 0) || (NON_WORD_CHARACTERS.indexOf(line[searchIndex - 1]) !== -1)) && - (((searchIndex + term.length) === line.length) || (NON_WORD_CHARACTERS.indexOf(line[searchIndex + term.length]) !== -1))); + return ((searchIndex === 0) || (NON_WORD_CHARACTERS.includes(line[searchIndex - 1]))) && + (((searchIndex + term.length) === line.length) || (NON_WORD_CHARACTERS.includes(line[searchIndex + term.length]))); } /** @@ -245,6 +252,7 @@ export class SearchAddon implements ITerminalAddon { * @param term The search term. * @param position The position to start the search. * @param searchOptions Search options. + * @param isReverseSearch Whether the search should start from the right side of the terminal and search to the left. * @return The search result if it was found. */ protected _findInLine(term: string, searchPosition: ISearchPosition, searchOptions: ISearchOptions = {}, isReverseSearch: boolean = false): ISearchResult | undefined { @@ -254,7 +262,7 @@ export class SearchAddon implements ITerminalAddon { // Ignore wrapped lines, only consider on unwrapped line (first row of command string). const firstLine = terminal.buffer.active.getLine(row); - if (firstLine && firstLine.isWrapped) { + if (firstLine?.isWrapped) { if (isReverseSearch) { searchPosition.startCol += terminal.cols; return; @@ -386,7 +394,7 @@ export class SearchAddon implements ITerminalAddon { // 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; - scroll = scroll - Math.floor(terminal.rows / 2); + scroll -= Math.floor(terminal.rows / 2); terminal.scrollLines(scroll); } return true; diff --git a/addons/xterm-addon-search/test/SearchAddon.api.ts b/addons/xterm-addon-search/test/SearchAddon.api.ts index 3707aecd..3c94d416 100644 --- a/addons/xterm-addon-search/test/SearchAddon.api.ts +++ b/addons/xterm-addon-search/test/SearchAddon.api.ts @@ -9,7 +9,7 @@ import { resolve } from 'path'; import { openTerminal, writeSync, getBrowserType } from '../../../out-test/api/TestUtils'; import { Browser, Page } from 'playwright'; -const APP = 'http://127.0.0.1:3000/test'; +const APP = 'http://127.0.0.1:3001/test'; let browser: Browser; let page: Page; @@ -20,7 +20,7 @@ describe('Search Tests', function(): void { before(async function(): Promise { const browserType = getBrowserType(); browser = await browserType.launch({ - headless: process.argv.indexOf('--headless') !== -1 + headless: process.argv.includes('--headless') }); page = await (await browser.newContext()).newPage(); await page.setViewportSize({ width, height }); diff --git a/addons/xterm-addon-serialize/benchmark/SerializeAddon.benchmark.ts b/addons/xterm-addon-serialize/benchmark/SerializeAddon.benchmark.ts index 0147631d..87741b63 100644 --- a/addons/xterm-addon-serialize/benchmark/SerializeAddon.benchmark.ts +++ b/addons/xterm-addon-serialize/benchmark/SerializeAddon.benchmark.ts @@ -35,7 +35,7 @@ perfContext('Terminal: sh -c "dd if=/dev/urandom count=40 bs=1k | hexdump | lolc chunks.push(data as unknown as Buffer); length += data.length; }); - await new Promise(resolve => p.on('exit', () => resolve())); + await new Promise(resolve => p.on('exit', () => resolve())); contentUtf8 = Buffer.concat(chunks, length); // translate to content string const buffer = new Uint32Array(contentUtf8.length); diff --git a/addons/xterm-addon-serialize/package.json b/addons/xterm-addon-serialize/package.json index bb799aec..770771c4 100644 --- a/addons/xterm-addon-serialize/package.json +++ b/addons/xterm-addon-serialize/package.json @@ -1,6 +1,6 @@ { "name": "xterm-addon-serialize", - "version": "0.4.0", + "version": "0.5.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts index dad6aa4f..0caaaa03 100644 --- a/addons/xterm-addon-serialize/src/SerializeAddon.ts +++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts @@ -21,7 +21,7 @@ abstract class BaseSerializeHandler { const cell2 = this._buffer.getNullCell(); let oldCell = cell1; - this._beforeSerialize(endRow - startRow); + this._beforeSerialize(endRow - startRow, startRow, endRow); for (let row = startRow; row < endRow; row++) { const line = this._buffer.getLine(row); @@ -36,7 +36,7 @@ abstract class BaseSerializeHandler { oldCell = c; } } - this._rowEnd(row); + this._rowEnd(row, row === endRow - 1); } this._afterSerialize(); @@ -45,8 +45,8 @@ abstract class BaseSerializeHandler { } protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { } - protected _rowEnd(row: number): void { } - protected _beforeSerialize(rows: number): void { } + protected _rowEnd(row: number, isLastRow: boolean): void { } + protected _beforeSerialize(rows: number, startRow: number, endRow: number): void { } protected _afterSerialize(): void { } protected _serializeString(): string { return ''; } } @@ -71,27 +71,152 @@ function equalFlags(cell1: IBufferCell, cell2: IBufferCell): boolean { && cell1.isDim() === cell2.isDim(); } + + class StringSerializeHandler extends BaseSerializeHandler { private _rowIndex: number = 0; private _allRows: string[] = new Array(); + private _allRowSeparators: string[] = new Array(); private _currentRow: string = ''; private _nullCellCount: number = 0; - constructor(buffer: IBuffer) { - super(buffer); + // we can see a full colored cell and a null cell that only have background the same style + // but the information isn't preserved by null cell itself + // so wee need to record it when required. + private _cursorStyle: IBufferCell = this._buffer1.getNullCell(); + + // where exact the cursor styles comes from + // because we can't copy the cell directly + // so we remember where the content comes from instead + private _cursorStyleRow: number = 0; + private _cursorStyleCol: number = 0; + + // this is a null cell for reference for checking whether background is empty or not + private _backgroundCell: IBufferCell = this._buffer1.getNullCell(); + + private _firstRow: number = 0; + private _lastCursorRow: number = 0; + private _lastCursorCol: number = 0; + private _lastContentCursorRow: number = 0; + private _lastContentCursorCol: number = 0; + + constructor(private _buffer1: IBuffer, private _terminal: Terminal) { + super(_buffer1); } - protected _beforeSerialize(rows: number): void { + protected _beforeSerialize(rows: number, start: number, end: number): void { this._allRows = new Array(rows); + this._lastContentCursorRow = start; + this._lastCursorRow = start; + this._firstRow = start; } - protected _rowEnd(row: number): void { - this._allRows[this._rowIndex++] = this._currentRow; + private _thisRowLastChar: IBufferCell = this._buffer1.getNullCell(); + private _thisRowLastSecondChar: IBufferCell = this._buffer1.getNullCell(); + private _nextRowFirstChar: IBufferCell = this._buffer1.getNullCell(); + protected _rowEnd(row: number, isLastRow: boolean): void { + // if there is colorful empty cell at line end, whe must pad it back, or the the color block will missing + if (this._nullCellCount > 0 && !equalBg(this._cursorStyle, this._backgroundCell)) { + // use clear right to set background. + this._currentRow += `\x1b[${this._nullCellCount}X`; + } + + let rowSeparator = ''; + + // handle row separator + if (!isLastRow) { + // Enable BCE + if (row - this._firstRow >= this._terminal.rows) { + this._buffer1.getLine(this._cursorStyleRow)?.getCell(this._cursorStyleCol, this._backgroundCell); + } + + // Fetch current line + const currentLine = this._buffer1.getLine(row)!; + // Fetch next line + const nextLine = this._buffer1.getLine(row + 1)!; + + if (!nextLine.isWrapped) { + // just insert the line break + rowSeparator = '\r\n'; + // we sended the enter + this._lastCursorRow = row + 1; + this._lastCursorCol = 0; + } else { + rowSeparator = ''; + const thisRowLastChar = currentLine.getCell(currentLine.length - 1, this._thisRowLastChar)!; + const thisRowLastSecondChar = currentLine.getCell(currentLine.length - 2, this._thisRowLastSecondChar)!; + const nextRowFirstChar = nextLine.getCell(0, this._nextRowFirstChar)!; + const isNextRowFirstCharDoubleWidth = nextRowFirstChar.getWidth() > 1; + + // validate whether this line wrap is ever possible + // which mean whether cursor can placed at a overflow position (x === row) naturally + let isValid = false; + + if ( + // you must output character to cause overflow, control sequence can't do this + nextRowFirstChar.getChars() && + isNextRowFirstCharDoubleWidth ? this._nullCellCount <= 1 : this._nullCellCount <= 0 + ) { + if ( + // the last character can't be null, + // you can't use control sequence to move cursor to (x === row) + (thisRowLastChar.getChars() || thisRowLastChar.getWidth() === 0) && + // change background of the first wrapped cell also affects BCE + // so we mark it as invalid to simply the process to determine line separator + equalBg(thisRowLastChar, nextRowFirstChar) + ) { + isValid = true; + } + + if ( + // the second to last character can't be null if the next line starts with CJK, + // you can't use control sequence to move cursor to (x === row) + isNextRowFirstCharDoubleWidth && + (thisRowLastSecondChar.getChars() || thisRowLastSecondChar.getWidth() === 0) && + // change background of the first wrapped cell also affects BCE + // so we mark it as invalid to simply the process to determine line separator + equalBg(thisRowLastChar, nextRowFirstChar) && + equalBg(thisRowLastSecondChar, nextRowFirstChar) + ) { + isValid = true; + } + } + + if (!isValid) { + // force the wrap with magic + // insert enough character to force the wrap + rowSeparator = '-'.repeat(this._nullCellCount + 1); + // move back and erase next line head + rowSeparator += '\x1b[1D\x1b[1X'; + + if (this._nullCellCount > 0) { + // do these because we filled the last several null slot, which we shouldn't + rowSeparator += '\x1b[A'; + rowSeparator += `\x1b[${currentLine.length - this._nullCellCount}C`; + rowSeparator += `\x1b[${this._nullCellCount}X`; + rowSeparator += `\x1b[${currentLine.length - this._nullCellCount}D`; + rowSeparator += '\x1b[B'; + } + + // This is content and need the be serialized even it is invisible. + // without this, wrap will be missing from outputs. + this._lastContentCursorRow = row + 1; + this._lastContentCursorCol = 0; + + // force commit the cursor position + this._lastCursorRow = row + 1; + this._lastCursorCol = 0; + } + } + } + + this._allRows[this._rowIndex] = this._currentRow; + this._allRowSeparators[this._rowIndex++] = rowSeparator; this._currentRow = ''; this._nullCellCount = 0; } - protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { + private _diffStyle(cell: IBufferCell, oldCell: IBufferCell): number[] { const sgrSeq: number[] = []; const fgChanged = !equalFg(cell, oldCell); const bgChanged = !equalBg(cell, oldCell); @@ -99,7 +224,9 @@ class StringSerializeHandler extends BaseSerializeHandler { if (fgChanged || bgChanged || flagsChanged) { if (cell.isAttributeDefault()) { - this._currentRow += '\x1b[0m'; + if (!oldCell.isAttributeDefault()) { + sgrSeq.push(0); + } } else { if (fgChanged) { const color = cell.getFgColor(); @@ -131,30 +258,129 @@ class StringSerializeHandler extends BaseSerializeHandler { } } - if (sgrSeq.length) { + return sgrSeq; + } + + protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { + // a width 0 cell don't need to be count because it is just a placeholder after a CJK character; + const isPlaceHolderCell = cell.getWidth() === 0; + + if (isPlaceHolderCell) { + return; + } + + // this cell don't have content + const isEmptyCell = cell.getChars() === ''; + + const sgrSeq = this._diffStyle(cell, this._cursorStyle); + + // the empty cell style is only assumed to be changed when background changed, because foreground is always 0. + const styleChanged = isEmptyCell ? !equalBg(this._cursorStyle, cell) : sgrSeq.length > 0; + + /** + * handles style change + */ + if (styleChanged) { + // before update the style, we need to fill empty cell back + if (this._nullCellCount > 0) { + // use clear right to set background. + if (!equalBg(this._cursorStyle, this._backgroundCell)) { + this._currentRow += `\x1b[${this._nullCellCount}X`; + } + // use move right to move cursor. + this._currentRow += `\x1b[${this._nullCellCount}C`; + this._nullCellCount = 0; + } + + this._lastContentCursorRow = this._lastCursorRow = row; + this._lastContentCursorCol = this._lastCursorCol = col; + this._currentRow += `\x1b[${sgrSeq.join(';')}m`; + + // update the last cursor style + const line = this._buffer1.getLine(row); + if (line !== undefined) { + line.getCell(col, this._cursorStyle); + this._cursorStyleRow = row; + this._cursorStyleCol = col; + } } - // 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() === '') { + /** + * handles actual content + */ + if (isEmptyCell) { this._nullCellCount += cell.getWidth(); - } else if (this._nullCellCount > 0) { - this._currentRow += `\x1b[${this._nullCellCount}C`; - this._nullCellCount = 0; - } + } else { + if (this._nullCellCount > 0) { + // we can just assume we have same style with previous one here + // because style change is handled by previous stage + // use move right when background is empty, use clear right when there is background. + if (equalBg(this._cursorStyle, this._backgroundCell)) { + this._currentRow += `\x1b[${this._nullCellCount}C`; + } else { + this._currentRow += `\x1b[${this._nullCellCount}X`; + this._currentRow += `\x1b[${this._nullCellCount}C`; + } + this._nullCellCount = 0; + } - this._currentRow += cell.getChars(); + this._currentRow += cell.getChars(); + + // update cursor + this._lastContentCursorRow = this._lastCursorRow = row; + this._lastContentCursorCol = this._lastCursorCol = col + cell.getWidth(); + } } protected _serializeString(): string { let rowEnd = this._allRows.length; - for (; rowEnd > 0; rowEnd--) { - if (this._allRows[rowEnd - 1]) { - break; + + // the fixup is only required for data without scrollback + // because it will always be placed at last line otherwise + if (this._buffer1.length - this._firstRow <= this._terminal.rows) { + rowEnd = this._lastContentCursorRow + 1 - this._firstRow; + this._lastCursorCol = this._lastContentCursorCol; + this._lastCursorRow = this._lastContentCursorRow; + } + + let content = ''; + + for (let i = 0; i < rowEnd; i++) { + content += this._allRows[i]; + if (i + 1 < rowEnd) { + content += this._allRowSeparators[i]; } } - return this._allRows.slice(0, rowEnd).join('\r\n'); + + // restore the cursor + const realCursorRow = this._buffer1.baseY + this._buffer1.cursorY; + const realCursorCol = this._buffer1.cursorX; + + const cursorMoved = (realCursorRow !== this._lastCursorRow || realCursorCol !== this._lastCursorCol); + + const moveRight = (offset: number): void => { + if (offset > 0) { + content += `\u001b[${offset}C`; + } else if (offset < 0) { + content += `\u001b[${-offset}D`; + } + }; + const moveDown = (offset: number): void => { + if (offset > 0) { + content += `\u001b[${offset}B`; + } else if (offset < 0) { + content += `\u001b[${-offset}A`; + } + }; + + if (cursorMoved) { + moveDown(realCursorRow - this._lastCursorRow); + moveRight(realCursorCol - this._lastCursorCol); + } + + + return content; } } @@ -167,20 +393,33 @@ export class SerializeAddon implements ITerminalAddon { this._terminal = terminal; } - public serialize(rows?: number): string { - // TODO: Add re-position cursor support - // TODO: Add word wrap mode support + private _getString(buffer: IBuffer, scrollback?: number): string { + const maxRows = buffer.length; + const handler = new StringSerializeHandler(buffer, this._terminal!); + + const correctRows = (scrollback === undefined) ? maxRows : constrain(scrollback + this!._terminal!.rows, 0, maxRows); + const result = handler.serialize(maxRows - correctRows, maxRows); + + return result; + } + + public serialize(scrollback?: number): string { // 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); + if (this._terminal.buffer.active.type === 'normal') { + return this._getString(this._terminal.buffer.active, scrollback); + } - rows = (rows === undefined) ? maxRows : constrain(rows, 0, maxRows); + const normalScreenContent = this._getString(this._terminal.buffer.normal, scrollback); + // alt screen don't have scrollback + const alternativeScreenContent = this._getString(this._terminal.buffer.alternate, undefined); - return handler.serialize(maxRows - rows, maxRows); + return normalScreenContent + + '\u001b[?1049h\u001b[H' + + alternativeScreenContent; } public dispose(): void { } diff --git a/addons/xterm-addon-serialize/test/SerializeAddon.api.ts b/addons/xterm-addon-serialize/test/SerializeAddon.api.ts index 5b615db1..c46b3815 100644 --- a/addons/xterm-addon-serialize/test/SerializeAddon.api.ts +++ b/addons/xterm-addon-serialize/test/SerializeAddon.api.ts @@ -7,18 +7,34 @@ import { assert } from 'chai'; import { openTerminal, writeSync, getBrowserType } from '../../../out-test/api/TestUtils'; import { Browser, Page } from 'playwright'; -const APP = 'http://127.0.0.1:3000/test'; +const APP = 'http://127.0.0.1:3001/test'; let browser: Browser; let page: Page; const width = 800; const height = 600; +const writeRawSync = (page: any, str: string): Promise => writeSync(page, '\' +' + JSON.stringify(str) + '+ \''); + +const testNormalScreenEqual = async (page: any, str: string): Promise => { + await writeRawSync(page, str); + const originalBuffer = await page.evaluate(`inspectBuffer(term.buffer.normal);`); + + const result = await page.evaluate(`serializeAddon.serialize();`) as string; + await page.evaluate(`term.reset();`); + await writeRawSync(page, result); + const newBuffer = await page.evaluate(`inspectBuffer(term.buffer.normal);`); + + // chai decides -0 and 0 are different number... + // and firefox have a bug that output -0 for unknown reason + assert.equal(JSON.stringify(originalBuffer), JSON.stringify(newBuffer)); +}; + describe('SerializeAddon', () => { before(async function(): Promise { const browserType = getBrowserType(); browser = await browserType.launch({ - headless: process.argv.indexOf('--headless') !== -1 + headless: process.argv.includes('--headless') }); page = await (await browser.newContext()).newPage(); await page.setViewportSize({ width, height }); @@ -27,19 +43,74 @@ describe('SerializeAddon', () => { await page.evaluate(` window.serializeAddon = new SerializeAddon(); window.term.loadAddon(window.serializeAddon); + window.inspectBuffer = (buffer) => { + const lines = []; + for (let i = 0; i < buffer.length; i++) { + // Do this intentionally to get content of underlining source + const bufferLine = buffer.getLine(i)._line; + lines.push(JSON.stringify(bufferLine)); + } + return { + x: buffer.cursorX, + y: buffer.cursorY, + data: lines + }; + } `); }); after(async () => await browser.close()); beforeEach(async () => await page.evaluate(`window.term.reset()`)); + it('produce different output when we call test util with different text', async function(): Promise { + await writeRawSync(page, '12345'); + const buffer1 = await page.evaluate(`inspectBuffer(term.buffer.normal);`); + + await page.evaluate(`term.reset();`); + await writeRawSync(page, '67890'); + const buffer2 = await page.evaluate(`inspectBuffer(term.buffer.normal);`); + + assert.throw(() => { + assert.equal(JSON.stringify(buffer1), JSON.stringify(buffer2)); + }); + }); + + it('produce different output when we call test util with different line wrap', async function(): Promise { + await writeRawSync(page, '1234567890\r\n12345'); + const buffer3 = await page.evaluate(`inspectBuffer(term.buffer.normal);`); + + await page.evaluate(`term.reset();`); + await writeRawSync(page, '1234567890n12345'); + const buffer4 = await page.evaluate(`inspectBuffer(term.buffer.normal);`); + + assert.throw(() => { + assert.equal(JSON.stringify(buffer3), JSON.stringify(buffer4)); + }); + }); + it('empty content', async function(): Promise { const rows = 10; const cols = 10; assert.equal(await page.evaluate(`serializeAddon.serialize();`), ''); }); - it('trim last empty lines', async function(): Promise { + it('unwrap wrapped line', async function(): Promise { + const lines = ['123456789123456789']; + await writeSync(page, lines.join('\\r\\n')); + assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n')); + }); + + it('does not unwrap non-wrapped line', async function(): Promise { + const lines = [ + '123456789', + '123456789' + ]; + await writeSync(page, lines.join('\\r\\n')); + assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n')); + }); + + + it('preserve last empty lines', async function(): Promise { const cols = 10; const lines = [ '', @@ -55,7 +126,7 @@ describe('SerializeAddon', () => { '' ]; await writeSync(page, lines.join('\\r\\n')); - assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.slice(0, 8).join('\r\n')); + assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n')); }); it('digits content', async function(): Promise { @@ -67,21 +138,22 @@ describe('SerializeAddon', () => { assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n')); }); - it('serialize half rows of content', async function(): Promise { - const rows = 10; - const halfRows = rows >> 1; + it('serialize with half of scrollback', async function(): Promise { + const rows = 20; + const scrollback = rows - 10; + const halfScrollback = scrollback / 2; const cols = 10; const lines = newArray((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')); + assert.equal(await page.evaluate(`serializeAddon.serialize(${halfScrollback});`), lines.slice(halfScrollback, rows).join('\r\n')); }); - it('serialize 0 rows of content', async function(): Promise { - const rows = 10; + it('serialize 0 rows of scrollback', async function(): Promise { + const rows = 20; const cols = 10; const lines = newArray((index: number) => digitsString(cols, index), rows); await writeSync(page, lines.join('\\r\\n')); - assert.equal(await page.evaluate(`serializeAddon.serialize(0);`), ''); + assert.equal(await page.evaluate(`serializeAddon.serialize(0);`), lines.slice(rows - 10, rows).join('\r\n')); }); it('serialize all rows of content with color16', async function(): Promise { @@ -276,17 +348,15 @@ describe('SerializeAddon', () => { '中文中文', '12中文', '中文12', - '1中文中文中' // this line is going to be wrapped at last character because it has line length of 11 (1+2*5) - ]; - const expected = [ - '中文中文', - '12中文', - '中文12', - '1中文中文', - '中' + // This line is going to be wrapped at last character + // because it has line length of 11 (1+2*5). + // We concat it back without the null cell currently. + // But this may be incorrect. + // see also #3097 + '1中文中文中' ]; await writeSync(page, lines.join('\\r\\n')); - assert.equal(await page.evaluate(`serializeAddon.serialize();`), expected.join('\r\n')); + assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n')); }); it('serialize CJK Mixed with tab correctly', async () => { @@ -299,15 +369,127 @@ describe('SerializeAddon', () => { await writeSync(page, lines.join('\\r\\n')); assert.equal(await page.evaluate(`serializeAddon.serialize();`), expected.join('\r\n')); }); + + it('serialize with alt screen correctly', async () => { + const SMCUP = '\u001b[?1049h'; + const CUP = '\u001b[H'; + + const lines = [ + `1${SMCUP}${CUP}2` + ]; + const expected = [ + `1${SMCUP}${CUP}2` + ]; + + await writeSync(page, lines.join('\\r\\n')); + assert.equal(await page.evaluate(`window.term.buffer.active.type`), 'alternate'); + assert.equal(JSON.stringify(await page.evaluate(`serializeAddon.serialize();`)), JSON.stringify(expected.join('\r\n'))); + }); + + it('serialize without alt screen correctly', async () => { + const SMCUP = '\u001b[?1049h'; + const RMCUP = '\u001b[?1049l'; + + const lines = [ + `1${SMCUP}2${RMCUP}` + ]; + const expected = [ + `1` + ]; + + await writeSync(page, lines.join('\\r\\n')); + assert.equal(await page.evaluate(`window.term.buffer.active.type`), 'normal'); + assert.equal(JSON.stringify(await page.evaluate(`serializeAddon.serialize();`)), JSON.stringify(expected.join('\r\n'))); + }); + + it('serialize with background', async () => { + const CLEAR_RIGHT = (l: number): string => `\u001b[${l}X`; + + const lines = [ + `1\u001b[44m${CLEAR_RIGHT(5)}`, + `2${CLEAR_RIGHT(9)}` + ]; + + await testNormalScreenEqual(page, lines.join('\r\n')); + }); + + it('cause the BCE on scroll', async () => { + const CLEAR_RIGHT = (l: number): string => `\u001b[${l}X`; + + const padLines = newArray( + (index: number) => digitsString(10, index), + 10 + ); + + const lines = [ + ...padLines, + `\u001b[44m${CLEAR_RIGHT(5)}1111111111111111` + ]; + + await testNormalScreenEqual(page, lines.join('\r\n')); + }); + + it('handle invalid wrap before scroll', async () => { + const CLEAR_RIGHT = (l: number): string => `\u001b[${l}X`; + const MOVE_UP = (l: number): string => `\u001b[${l}A`; + const MOVE_DOWN = (l: number): string => `\u001b[${l}B`; + const MOVE_LEFT = (l: number): string => `\u001b[${l}D`; + + // A line wrap happened after current line. + // But there is no content. + // so wrap shouldn't even be able to happen. + const segments = [ + `123456789012345`, + MOVE_UP(1), + CLEAR_RIGHT(5), + MOVE_DOWN(1), + MOVE_LEFT(5), + CLEAR_RIGHT(5), + MOVE_UP(1), + '1' + ]; + + await testNormalScreenEqual(page, segments.join('')); + }); + + it('handle invalid wrap after scroll', async () => { + const CLEAR_RIGHT = (l: number): string => `\u001b[${l}X`; + const MOVE_UP = (l: number): string => `\u001b[${l}A`; + const MOVE_DOWN = (l: number): string => `\u001b[${l}B`; + const MOVE_LEFT = (l: number): string => `\u001b[${l}D`; + + const padLines = newArray( + (index: number) => digitsString(10, index), + 10 + ); + + // A line wrap happened after current line. + // But there is no content. + // so wrap shouldn't even be able to happen. + const lines = [ + padLines.join('\r\n'), + '\r\n', + `123456789012345`, + MOVE_UP(1), + CLEAR_RIGHT(5), + MOVE_DOWN(1), + MOVE_LEFT(5), + CLEAR_RIGHT(5), + MOVE_UP(1), + '1' + ]; + + await testNormalScreenEqual(page, lines.join('')); + }); }); function newArray(initial: T | ((index: number) => T), count: number): T[] { const array: T[] = new Array(count); for (let i = 0; i < array.length; i++) { if (typeof initial === 'function') { - array[i] = (<(index: number) => T>initial)(i); + array[i] = (initial as (index: number) => T)(i); } else { - array[i] = initial; + array[i] = initial as T; } } return array; diff --git a/addons/xterm-addon-serialize/typings/xterm-addon-serialize.d.ts b/addons/xterm-addon-serialize/typings/xterm-addon-serialize.d.ts index 9e7b500a..58be9a97 100644 --- a/addons/xterm-addon-serialize/typings/xterm-addon-serialize.d.ts +++ b/addons/xterm-addon-serialize/typings/xterm-addon-serialize.d.ts @@ -25,10 +25,10 @@ declare module 'xterm-addon-serialize' { * to restore the state. The cursor will also be positioned to the correct cell. * When restoring a terminal it is best to do before `Terminal.open` is called * to avoid wasting CPU cycles rendering incomplete frames. - * @param rows The number of rows to serialize, starting from the bottom of the - * terminal. This defaults to the number of rows in the viewport. + * @param scrollback The number of rows in scrollback buffer to serialize, starting from the bottom of the + * scrollback buffer. This defaults to the all available rows in the scrollback buffer. */ - public serialize(rows?: number): string; + public serialize(scrollback?: number): string; /** * Disposes the addon. diff --git a/addons/xterm-addon-unicode11/test/Unicode11Addon.api.ts b/addons/xterm-addon-unicode11/test/Unicode11Addon.api.ts index d0e09da2..ba536e90 100644 --- a/addons/xterm-addon-unicode11/test/Unicode11Addon.api.ts +++ b/addons/xterm-addon-unicode11/test/Unicode11Addon.api.ts @@ -7,7 +7,7 @@ import { assert } from 'chai'; import { openTerminal, getBrowserType } from '../../../out-test/api/TestUtils'; import { Browser, Page } from 'playwright'; -const APP = 'http://127.0.0.1:3000/test'; +const APP = 'http://127.0.0.1:3001/test'; let browser: Browser; let page: Page; @@ -18,7 +18,7 @@ describe('Unicode11Addon', () => { before(async function(): Promise { const browserType = getBrowserType(); browser = await browserType.launch({ - headless: process.argv.indexOf('--headless') !== -1 + headless: process.argv.includes('--headless') }); page = await (await browser.newContext()).newPage(); await page.setViewportSize({ width, height }); diff --git a/addons/xterm-addon-web-links/src/WebLinkProvider.ts b/addons/xterm-addon-web-links/src/WebLinkProvider.ts index af6ad7d3..f0caf974 100644 --- a/addons/xterm-addon-web-links/src/WebLinkProvider.ts +++ b/addons/xterm-addon-web-links/src/WebLinkProvider.ts @@ -3,25 +3,45 @@ * @license MIT */ -import { ILinkProvider, IBufferCellPosition, ILink, Terminal } from 'xterm'; +import { ILinkProvider, ILink, Terminal, IViewportRange } from 'xterm'; + +interface ILinkProviderOptions { + hover?(event: MouseEvent, text: string, location: IViewportRange): void; + leave?(event: MouseEvent, text: string): void; +} export class WebLinkProvider implements ILinkProvider { constructor( private readonly _terminal: Terminal, private readonly _regex: RegExp, - private readonly _handler: (event: MouseEvent, uri: string) => void + private readonly _handler: (event: MouseEvent, uri: string) => void, + private readonly _options: ILinkProviderOptions = {} ) { } public provideLinks(y: number, callback: (links: ILink[] | undefined) => void): void { - callback(LinkComputer.computeLink(y, this._regex, this._terminal, this._handler)); + const links = LinkComputer.computeLink(y, this._regex, this._terminal, this._handler); + callback(this._addCallbacks(links)); + } + + private _addCallbacks(links: ILink[]): ILink[] { + return links.map(link => { + link.leave = this._options.leave; + link.hover = (event: MouseEvent, uri: string): void => { + if (this._options.hover) { + const { range } = link; + this._options.hover(event, uri, range); + } + }; + return link; + }); } } export class LinkComputer { - public static computeLink(y: number, regex: RegExp, terminal: Terminal, handler: (event: MouseEvent, uri: string) => void): ILink[] { + public static computeLink(y: number, regex: RegExp, terminal: Terminal, activate: (event: MouseEvent, uri: string) => void): ILink[] { const rex = new RegExp(regex.source, (regex.flags || '') + 'g'); const [line, startLineIndex] = LinkComputer._translateBufferLineToStringWithWrap(y - 1, false, terminal); @@ -69,7 +89,7 @@ export class LinkComputer { } }; - result.push({ range, text, activate: handler }); + result.push({ range, text, activate }); } return result; diff --git a/addons/xterm-addon-web-links/src/WebLinksAddon.ts b/addons/xterm-addon-web-links/src/WebLinksAddon.ts index dce405a5..46ddcf7b 100644 --- a/addons/xterm-addon-web-links/src/WebLinksAddon.ts +++ b/addons/xterm-addon-web-links/src/WebLinksAddon.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { Terminal, ILinkMatcherOptions, ITerminalAddon, IDisposable } from 'xterm'; +import { Terminal, ILinkMatcherOptions, ITerminalAddon, IDisposable, IViewportRange } from 'xterm'; import { WebLinkProvider } from './WebLinkProvider'; const protocolClause = '(https?:\\/\\/)'; @@ -36,6 +36,11 @@ function handleLink(event: MouseEvent, uri: string): void { } } +interface ILinkProviderOptions { + hover?(event: MouseEvent, text: string, location: IViewportRange): void; + leave?(event: MouseEvent, text: string): void; +} + export class WebLinksAddon implements ITerminalAddon { private _linkMatcherId: number | undefined; private _terminal: Terminal | undefined; @@ -43,20 +48,22 @@ export class WebLinksAddon implements ITerminalAddon { constructor( private _handler: (event: MouseEvent, uri: string) => void = handleLink, - private _options: ILinkMatcherOptions = {}, + private _options: ILinkMatcherOptions | ILinkProviderOptions = {}, private _useLinkProvider: boolean = false ) { - 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)); + const options = this._options as ILinkProviderOptions; + this._linkProvider = this._terminal.registerLinkProvider(new WebLinkProvider(this._terminal, strictUrlRegex, this._handler, options)); } else { // TODO: This should be removed eventually - this._linkMatcherId = (this._terminal as Terminal).registerLinkMatcher(strictUrlRegex, this._handler, this._options); + const options = this._options as ILinkMatcherOptions; + options.matchIndex = 1; + this._linkMatcherId = (this._terminal as Terminal).registerLinkMatcher(strictUrlRegex, this._handler, options); } } diff --git a/addons/xterm-addon-web-links/test/WebLinksAddon.api.ts b/addons/xterm-addon-web-links/test/WebLinksAddon.api.ts index 6e42ff96..54650f1f 100644 --- a/addons/xterm-addon-web-links/test/WebLinksAddon.api.ts +++ b/addons/xterm-addon-web-links/test/WebLinksAddon.api.ts @@ -7,7 +7,7 @@ import { assert } from 'chai'; import { openTerminal, pollFor, writeSync, getBrowserType } from '../../../out-test/api/TestUtils'; import { Browser, Page } from 'playwright'; -const APP = 'http://127.0.0.1:3000/test'; +const APP = 'http://127.0.0.1:3001/test'; let browser: Browser; let page: Page; @@ -18,7 +18,7 @@ describe('WebLinksAddon', () => { before(async function(): Promise { const browserType = getBrowserType(); browser = await browserType.launch({ - headless: process.argv.indexOf('--headless') !== -1 + headless: process.argv.includes('--headless') }); page = await (await browser.newContext()).newPage(); await page.setViewportSize({ width, height }); diff --git a/addons/xterm-addon-web-links/typings/xterm-addon-web-links.d.ts b/addons/xterm-addon-web-links/typings/xterm-addon-web-links.d.ts index f0564704..78a258e5 100644 --- a/addons/xterm-addon-web-links/typings/xterm-addon-web-links.d.ts +++ b/addons/xterm-addon-web-links/typings/xterm-addon-web-links.d.ts @@ -4,7 +4,7 @@ */ -import { Terminal, ILinkMatcherOptions, ITerminalAddon } from 'xterm'; +import { Terminal, ILinkMatcherOptions, ITerminalAddon, IViewportRange } from 'xterm'; declare module 'xterm-addon-web-links' { /** @@ -20,7 +20,7 @@ declare module 'xterm-addon-web-links' { * 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 | ILinkProviderOptions, useLinkProvider?: boolean); /** * Activates the addon @@ -33,4 +33,21 @@ declare module 'xterm-addon-web-links' { */ public dispose(): void; } + + /** + * An object containing options for a link provider. + */ + export interface ILinkProviderOptions { + /** + * A callback that fires when the mouse hovers over a link for a period of + * time (defined by {@link ITerminalOptions.linkTooltipHoverDuration}). + */ + hover?(event: MouseEvent, text: string, location: IViewportRange): void; + + /** + * A callback that fires when the mouse leaves a link. Note that this can + * happen even when tooltipCallback hasn't fired for the link yet. + */ + leave?(event: MouseEvent, text: string): void; + } } diff --git a/addons/xterm-addon-webgl/README.md b/addons/xterm-addon-webgl/README.md index 026a738e..67fafe7b 100644 --- a/addons/xterm-addon-webgl/README.md +++ b/addons/xterm-addon-webgl/README.md @@ -1,8 +1,6 @@ ## xterm-addon-webgl -An addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables a WebGL-based renderer. This addon requires xterm.js v4+. - -⚠️ This is an experimental addon that is [missing some features and may be unstable](https://github.com/xtermjs/xterm.js/issues?q=is%3Aopen+is%3Aissue+label%3Aarea%2Faddon%2Fwebgl) ⚠️ +An addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables a WebGL2-based renderer. This addon requires xterm.js v4+. ### Install @@ -21,3 +19,18 @@ terminal.loadAddon(new WebglAddon()); ``` See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts) for more advanced usage. + +### Handling Context Loss + +The browser may drop WebGL contexts for various reasons like OOM or after the system has been suspended. There is an API exposed that fires the `webglcontextlost` event fired on the canvas so embedders can handle it however they wish. An easy, but suboptimal way, to handle this is by disposing of WebglAddon when the event fires: + +```ts +const terminal = new Terminal(); +const addon = new WebglAddon(); +addon.onContextLoss(e => { + addon.dispose(); +}); +terminal.loadAddon(addon); +``` + +Read more about handling WebGL context losses on the [Khronos wiki](https://www.khronos.org/webgl/wiki/HandlingContextLost). diff --git a/addons/xterm-addon-webgl/package.json b/addons/xterm-addon-webgl/package.json index c0e91b37..421f6092 100644 --- a/addons/xterm-addon-webgl/package.json +++ b/addons/xterm-addon-webgl/package.json @@ -1,6 +1,6 @@ { "name": "xterm-addon-webgl", - "version": "0.9.0", + "version": "0.11.1", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index 8be4c011..71b3659e 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -176,8 +176,9 @@ export class GlyphRenderer { const i = (y * terminal.cols + x) * INDICES_PER_CELL; - // Exit early if this is a null/space character - if (code === NULL_CELL_CODE || code === WHITESPACE_CELL_CODE || code === undefined/* This is used for the right side of wide chars */) { + // Exit early if this is a null character, allow space character to continue as it may have + // underline/strikethrough styles + if (code === NULL_CELL_CODE || code === undefined/* This is used for the right side of wide chars */) { fill(array, 0, i, i + INDICES_PER_CELL - 1 - CELL_POSITION_INDICES); return; } @@ -263,7 +264,7 @@ export class GlyphRenderer { // 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)); + fg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK | FgFlags.INVERSE); switch (workCell.getBgColorMode()) { case Attributes.CM_P16: case Attributes.CM_P256: diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index aef1301e..ad2393d8 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -3,14 +3,17 @@ * @license MIT */ -import { Terminal, ITerminalAddon } from 'xterm'; +import { Terminal, ITerminalAddon, IEvent } from 'xterm'; import { WebglRenderer } from './WebglRenderer'; -import { IRenderService } from 'browser/services/Services'; +import { ICharacterJoinerService, IRenderService } from 'browser/services/Services'; import { IColorSet } from 'browser/Types'; +import { EventEmitter } from 'common/EventEmitter'; export class WebglAddon implements ITerminalAddon { private _terminal?: Terminal; private _renderer?: WebglRenderer; + private _onContextLoss = new EventEmitter(); + public get onContextLoss(): IEvent { return this._onContextLoss.event; } constructor( private _preserveDrawingBuffer?: boolean @@ -21,9 +24,11 @@ export class WebglAddon implements ITerminalAddon { throw new Error('Cannot activate WebglAddon before Terminal.open'); } this._terminal = terminal; - const renderService: IRenderService = (terminal)._core._renderService; - const colors: IColorSet = (terminal)._core._colorManager.colors; - this._renderer = new WebglRenderer(terminal, colors, this._preserveDrawingBuffer); + const renderService: IRenderService = (terminal as any)._core._renderService; + const characterJoinerService: ICharacterJoinerService = (terminal as any)._core._characterJoinerService; + const colors: IColorSet = (terminal as any)._core._colorManager.colors; + this._renderer = new WebglRenderer(terminal, colors, characterJoinerService, this._preserveDrawingBuffer); + this._renderer.onContextLoss(() => this._onContextLoss.fire()); renderService.setRenderer(this._renderer); } diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 9f982165..9b75d1de 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -12,13 +12,17 @@ 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 { Disposable } from 'common/Lifecycle'; -import { NULL_CELL_CODE } from 'common/buffer/Constants'; +import { Content, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants'; import { Terminal, IEvent } from 'xterm'; import { IRenderLayer } from './renderLayer/Types'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types'; import { ITerminal, IColorSet } from 'browser/Types'; import { EventEmitter } from 'common/EventEmitter'; import { CellData } from 'common/buffer/CellData'; +import { addDisposableDomListener } from 'browser/Lifecycle'; +import { ICharacterJoinerService } from 'browser/services/Services'; +import { CharData, ICellData } from 'common/Types'; +import { AttributeData } from 'common/buffer/AttributeData'; export class WebglRenderer extends Disposable implements IRenderer { private _renderLayers: IRenderLayer[]; @@ -41,9 +45,13 @@ export class WebglRenderer extends Disposable implements IRenderer { private _onRequestRedraw = new EventEmitter(); public get onRequestRedraw(): IEvent { return this._onRequestRedraw.event; } + private _onContextLoss = new EventEmitter(); + public get onContextLoss(): IEvent { return this._onContextLoss.event; } + constructor( private _terminal: Terminal, private _colors: IColorSet, + private readonly _characterJoinerService: ICharacterJoinerService, preserveDrawingBuffer?: boolean ) { super(); @@ -82,6 +90,9 @@ export class WebglRenderer extends Disposable implements IRenderer { if (!this._gl) { throw new Error('WebGL2 not supported ' + this._gl); } + + this.register(addDisposableDomListener(this._canvas, 'webglcontextlost', (e) => { this._onContextLoss.fire(e); })); + this._core.screenElement!.appendChild(this._canvas); this._rectangleRenderer = new RectangleRenderer(this._terminal, this._colors, this._gl, this.dimensions); @@ -94,7 +105,9 @@ export class WebglRenderer extends Disposable implements IRenderer { } public dispose(): void { - this._renderLayers.forEach(l => l.dispose()); + for (const l of this._renderLayers) { + l.dispose(); + } this._core.screenElement!.removeChild(this._canvas); super.dispose(); } @@ -106,22 +119,18 @@ export class WebglRenderer extends Disposable implements IRenderer { public setColors(colors: IColorSet): void { this._colors = colors; // Clear layers and force a full render - this._renderLayers.forEach(l => { + for (const l of this._renderLayers) { l.setColors(this._terminal, this._colors); l.reset(this._terminal); - }); + } this._rectangleRenderer.setColors(); this._glyphRenderer.setColors(); this._refreshCharAtlas(); - this._rectangleRenderer.updateSelection(this._model.selection); - this._glyphRenderer.updateSelection(this._model); - // Force a full refresh this._model.clear(); - this._model.clearSelection(); } public onDevicePixelRatioChange(): void { @@ -138,10 +147,11 @@ export class WebglRenderer extends Disposable implements IRenderer { this._updateDimensions(); this._model.resize(this._terminal.cols, this._terminal.rows); - this._rectangleRenderer.onResize(); // Resize all render layers - this._renderLayers.forEach(l => l.resize(this._terminal, this.dimensions)); + for (const l of this._renderLayers) { + l.resize(this._terminal, this.dimensions); + } // Resize the canvas this._canvas.width = this.dimensions.scaledCanvasWidth; @@ -152,6 +162,13 @@ export class WebglRenderer extends Disposable implements IRenderer { // Resize the screen this._core.screenElement!.style.width = `${this.dimensions.canvasWidth}px`; this._core.screenElement!.style.height = `${this.dimensions.canvasHeight}px`; + + this._rectangleRenderer.onResize(); + if (this._model.selection.hasSelection) { + // Update selection as dimensions have changed + this._rectangleRenderer.updateSelection(this._model.selection); + } + this._glyphRenderer.setDimensions(this.dimensions); this._glyphRenderer.onResize(); @@ -159,7 +176,6 @@ export class WebglRenderer extends Disposable implements IRenderer { // Force a full refresh this._model.clear(); - this._model.clearSelection(); } public onCharSizeChanged(): void { @@ -167,30 +183,37 @@ export class WebglRenderer extends Disposable implements IRenderer { } public onBlur(): void { - this._renderLayers.forEach(l => l.onBlur(this._terminal)); + for (const l of this._renderLayers) { + l.onBlur(this._terminal); + } } public onFocus(): void { - this._renderLayers.forEach(l => l.onFocus(this._terminal)); + for (const l of this._renderLayers) { + l.onFocus(this._terminal); + } } public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void { - this._renderLayers.forEach(l => l.onSelectionChanged(this._terminal, start, end, columnSelectMode)); + for (const l of this._renderLayers) { + l.onSelectionChanged(this._terminal, start, end, columnSelectMode); + } this._updateSelectionModel(start, end, columnSelectMode); - this._rectangleRenderer.updateSelection(this._model.selection); - this._glyphRenderer.updateSelection(this._model); - this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1 }); } public onCursorMove(): void { - this._renderLayers.forEach(l => l.onCursorMove(this._terminal)); + for (const l of this._renderLayers) { + l.onCursorMove(this._terminal); + } } public onOptionsChanged(): void { - this._renderLayers.forEach(l => l.onOptionsChanged(this._terminal)); + for (const l of this._renderLayers) { + l.onOptionsChanged(this._terminal); + } this._updateDimensions(); this._refreshCharAtlas(); } @@ -220,12 +243,13 @@ export class WebglRenderer extends Disposable implements IRenderer { this._charAtlas?.clearTexture(); this._model.clear(); this._updateModel(0, this._terminal.rows - 1); - this._glyphRenderer.updateSelection(this._model); this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1 }); } public clear(): void { - this._renderLayers.forEach(l => l.reset(this._terminal)); + for (const l of this._renderLayers) { + l.reset(this._terminal); + } } public registerCharacterJoiner(handler: (text: string) => [number, number][]): number { @@ -248,12 +272,14 @@ export class WebglRenderer extends Disposable implements IRenderer { } // Update render layers - this._renderLayers.forEach(l => l.onGridChanged(this._terminal, start, end)); + for (const l of this._renderLayers) { + l.onGridChanged(this._terminal, start, end); + } // Tell renderer the frame is beginning if (this._glyphRenderer.beginFrame()) { this._model.clear(); - this._model.clearSelection(); + this._updateSelectionModel(undefined, undefined); } // Update model to reflect what's drawn @@ -266,16 +292,41 @@ export class WebglRenderer extends Disposable implements IRenderer { private _updateModel(start: number, end: number): void { const terminal = this._core; + let cell: ICellData = this._workCell; for (let y = start; y <= end; y++) { const row = y + terminal.buffer.ydisp; const line = terminal.buffer.lines.get(row)!; this._model.lineLengths[y] = 0; + const joinedRanges = this._characterJoinerService.getJoinedCharacters(row); for (let x = 0; x < terminal.cols; x++) { - line.loadCell(x, this._workCell); + line.loadCell(x, cell); - const chars = this._workCell.getChars(); - let code = this._workCell.getCode(); + // If true, indicates that the current character(s) to draw were joined. + let isJoined = false; + let lastCharX = x; + + // Process any joined character ranges as needed. Because of how the + // ranges are produced, we know that they are valid for the characters + // and attributes of our input. + if (joinedRanges.length > 0 && x === joinedRanges[0][0]) { + isJoined = true; + const range = joinedRanges.shift()!; + + // We already know the exact start and end column of the joined range, + // so we get the string and width representing it directly + cell = new JoinedCellData( + cell, + line!.translateToString(true, range[0], range[1]), + range[1] - range[0] + ); + + // Skip over the cells occupied by this range in the loop + lastCharX = range[1] - 1; + } + + const chars = cell.getChars(); + let code = cell.getCode(); const i = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL; if (code !== NULL_CELL_CODE) { @@ -284,33 +335,52 @@ export class WebglRenderer extends Disposable implements IRenderer { // 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) { + this._model.cells[i + RENDER_MODEL_BG_OFFSET] === cell.bg && + this._model.cells[i + RENDER_MODEL_FG_OFFSET] === cell.fg) { continue; } // Flag combined chars with a bit mask so they're easily identifiable if (chars.length > 1) { - code = code | COMBINED_CHAR_BIT_MASK; + 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 + RENDER_MODEL_BG_OFFSET] = cell.bg; + this._model.cells[i + RENDER_MODEL_FG_OFFSET] = cell.fg; - this._glyphRenderer.updateCell(x, y, code, this._workCell.bg, this._workCell.fg, chars); + this._glyphRenderer.updateCell(x, y, code, cell.bg, cell.fg, chars); + + if (isJoined) { + // Restore work cell + cell = this._workCell; + + // Null out non-first cells + for (x++; x < lastCharX; x++) { + const j = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL; + this._glyphRenderer.updateCell(x, y, NULL_CELL_CODE, 0, 0, NULL_CELL_CHAR); + this._model.cells[j] = NULL_CELL_CODE; + this._model.cells[j + RENDER_MODEL_BG_OFFSET] = this._workCell.bg; + this._model.cells[j + RENDER_MODEL_FG_OFFSET] = this._workCell.fg; + } + } } } this._rectangleRenderer.updateBackgrounds(this._model); + if (this._model.selection.hasSelection) { + // Model could be updated but the selection is unchanged + this._glyphRenderer.updateSelection(this._model); + } } - private _updateSelectionModel(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void { + private _updateSelectionModel(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void { const terminal = this._terminal; // Selection does not exist if (!start || !end || (start[0] === end[0] && start[1] === end[1])) { this._model.clearSelection(); + this._rectangleRenderer.updateSelection(this._model.selection); return; } @@ -323,6 +393,7 @@ export class WebglRenderer extends Disposable implements IRenderer { // No need to draw the selection if (viewportCappedStartRow >= terminal.rows || viewportCappedEndRow < 0) { this._model.clearSelection(); + this._rectangleRenderer.updateSelection(this._model.selection); return; } @@ -334,6 +405,8 @@ export class WebglRenderer extends Disposable implements IRenderer { this._model.selection.viewportCappedEndRow = viewportCappedEndRow; this._model.selection.startCol = start[0]; this._model.selection.endCol = end[0]; + + this._rectangleRenderer.updateSelection(this._model.selection); } /** @@ -408,3 +481,49 @@ export class WebglRenderer extends Disposable implements IRenderer { this.dimensions.actualCellWidth = this.dimensions.scaledCellWidth / this._devicePixelRatio; } } + +// TODO: Share impl with core +export class JoinedCellData extends AttributeData implements ICellData { + private _width: number; + // .content carries no meaning for joined CellData, simply nullify it + // thus we have to overload all other .content accessors + public content: number = 0; + public fg: number; + public bg: number; + public combinedData: string = ''; + + constructor(firstCell: ICellData, chars: string, width: number) { + super(); + this.fg = firstCell.fg; + this.bg = firstCell.bg; + this.combinedData = chars; + this._width = width; + } + + public isCombined(): number { + // always mark joined cell data as combined + return Content.IS_COMBINED_MASK; + } + + public getWidth(): number { + return this._width; + } + + public getChars(): string { + return this.combinedData; + } + + public getCode(): number { + // code always gets the highest possible fake codepoint (read as -1) + // this is needed as code is used by caches as identifier + return 0x1FFFFF; + } + + public setFromCharData(value: CharData): void { + throw new Error('not implemented'); + } + + public getAsCharData(): CharData { + return [this.fg, this.getChars(), this.getWidth(), this.getCode()]; + } +} diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 223ec16a..5f729e28 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -80,12 +80,12 @@ export class WebglCharAtlas implements IDisposable { // The canvas needs alpha because we use clearColor to convert the background color to alpha. // It might also contain some characters with transparent backgrounds if allowTransparency is // set. - this._cacheCtx = throwIfFalsy(this.cacheCanvas.getContext('2d', {alpha: true})); + this._cacheCtx = throwIfFalsy(this.cacheCanvas.getContext('2d', { alpha: true })); this._tmpCanvas = document.createElement('canvas'); - this._tmpCanvas.width = this._config.scaledCharWidth * 2 + TMP_CANVAS_GLYPH_PADDING * 2; + this._tmpCanvas.width = this._config.scaledCharWidth * 4 + 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._tmpCtx = throwIfFalsy(this._tmpCanvas.getContext('2d', { alpha: this._config.allowTransparency })); } public dispose(): void { @@ -317,6 +317,13 @@ export class WebglCharAtlas implements IDisposable { this.hasCanvasChanged = true; + // Allow 1 cell width per character, with a minimum of 2 (CJK), plus some padding. This is used + // to draw the glyph to the canvas as well as to restrict the bounding box search to ensure + // giant ligatures (eg. =====>) don't impact overall performance. + const allowedWidth = this._config.scaledCharWidth * Math.max(chars.length, 2) + TMP_CANVAS_GLYPH_PADDING * 2; + if (this._tmpCanvas.width < allowedWidth) { + this._tmpCanvas.width = allowedWidth; + } this._tmpCtx.save(); this._workAttributeData.fg = fg; @@ -331,6 +338,8 @@ export class WebglCharAtlas implements IDisposable { const inverse = !!this._workAttributeData.isInverse(); const dim = !!this._workAttributeData.isDim(); const italic = !!this._workAttributeData.isItalic(); + const underline = !!this._workAttributeData.isUnderline(); + const strikethrough = !!this._workAttributeData.isStrikethrough(); let fgColor = this._workAttributeData.getFgColor(); let fgColorMode = this._workAttributeData.getFgColorMode(); let bgColor = this._workAttributeData.getBgColor(); @@ -358,7 +367,7 @@ export class WebglCharAtlas implements IDisposable { const fontStyle = italic ? 'italic' : ''; this._tmpCtx.font = `${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`; - this._tmpCtx.textBaseline = 'middle'; + this._tmpCtx.textBaseline = 'ideographic'; this._tmpCtx.fillStyle = this._getForegroundCss(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, bold); @@ -367,8 +376,42 @@ export class WebglCharAtlas implements IDisposable { this._tmpCtx.globalAlpha = DIM_OPACITY; } + // Check if the char is a powerline glyph, these will be restricted to a single cell glyph, no + // padding on either side that are allowed for other glyphs since they are designed to be pixel + // perfect but may render with "bad" anti-aliasing + let isPowerlineGlyph = false; + if (chars.length === 1) { + const code = chars.charCodeAt(0); + if (code >= 0xE0A0 && code <= 0xE0D6) { + isPowerlineGlyph = true; + } + } + + // For powerline glyphs left/top padding is excluded (https://github.com/microsoft/vscode/issues/120129) + const padding = isPowerlineGlyph ? 0 : TMP_CANVAS_GLYPH_PADDING; + // Draw the character - this._tmpCtx.fillText(chars, TMP_CANVAS_GLYPH_PADDING, TMP_CANVAS_GLYPH_PADDING + this._config.scaledCharHeight / 2); + this._tmpCtx.fillText(chars, padding, padding + this._config.scaledCharHeight); + + // Draw underline and strikethrough + if (underline || strikethrough) { + const lineWidth = Math.max(1, Math.floor(this._config.fontSize / 10)); + const yOffset = this._tmpCtx.lineWidth % 2 === 1 ? 0.5 : 0; // When the width is odd, draw at 0.5 position + this._tmpCtx.lineWidth = lineWidth; + this._tmpCtx.strokeStyle = this._tmpCtx.fillStyle; + this._tmpCtx.beginPath(); + if (underline) { + this._tmpCtx.moveTo(padding, padding + this._config.scaledCharHeight - yOffset); + this._tmpCtx.lineTo(padding + this._config.scaledCharWidth, padding + this._config.scaledCharHeight - yOffset); + } + if (strikethrough) { + this._tmpCtx.moveTo(padding, padding + Math.floor(this._config.scaledCharHeight / 2) - yOffset); + this._tmpCtx.lineTo(padding + this._config.scaledCharWidth, padding + Math.floor(this._config.scaledCharHeight / 2) - yOffset); + } + this._tmpCtx.stroke(); + this._tmpCtx.closePath(); + } + this._tmpCtx.restore(); // clear the background from the character to avoid issues with drawing over the previous @@ -391,7 +434,7 @@ export class WebglCharAtlas implements IDisposable { return NULL_RASTERIZED_GLYPH; } - const rasterizedGlyph = this._findGlyphBoundingBox(imageData, this._workBoundingBox); + const rasterizedGlyph = this._findGlyphBoundingBox(imageData, this._workBoundingBox, allowedWidth, isPowerlineGlyph); const clippedImageData = this._clipImageData(imageData, this._workBoundingBox); // Check if there is enough room in the current row and go to next if needed @@ -424,11 +467,13 @@ export class WebglCharAtlas implements IDisposable { * @param imageData The image data to read. * @param boundingBox An IBoundingBox to put the clipped bounding box values. */ - private _findGlyphBoundingBox(imageData: ImageData, boundingBox: IBoundingBox): IRasterizedGlyph { + private _findGlyphBoundingBox(imageData: ImageData, boundingBox: IBoundingBox, allowedWidth: number, restrictedGlyph: boolean): IRasterizedGlyph { boundingBox.top = 0; + const height = restrictedGlyph ? this._config.scaledCharHeight : this._tmpCanvas.height; + const width = restrictedGlyph ? this._config.scaledCharWidth : allowedWidth; let found = false; - for (let y = 0; y < this._tmpCanvas.height; y++) { - for (let x = 0; x < this._tmpCanvas.width; x++) { + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { const alphaOffset = y * this._tmpCanvas.width * 4 + x * 4 + 3; if (imageData.data[alphaOffset] !== 0) { boundingBox.top = y; @@ -442,8 +487,8 @@ export class WebglCharAtlas implements IDisposable { } boundingBox.left = 0; found = false; - for (let x = 0; x < this._tmpCanvas.width; x++) { - for (let y = 0; y < this._tmpCanvas.height; y++) { + for (let x = 0; x < width; x++) { + for (let y = 0; y < height; y++) { const alphaOffset = y * this._tmpCanvas.width * 4 + x * 4 + 3; if (imageData.data[alphaOffset] !== 0) { boundingBox.left = x; @@ -455,10 +500,10 @@ export class WebglCharAtlas implements IDisposable { break; } } - boundingBox.right = this._tmpCanvas.width; + boundingBox.right = width; found = false; - for (let x = this._tmpCanvas.width - 1; x >= 0; x--) { - for (let y = 0; y < this._tmpCanvas.height; y++) { + for (let x = width - 1; x >= 0; x--) { + for (let y = 0; y < height; y++) { const alphaOffset = y * this._tmpCanvas.width * 4 + x * 4 + 3; if (imageData.data[alphaOffset] !== 0) { boundingBox.right = x; @@ -470,10 +515,10 @@ export class WebglCharAtlas implements IDisposable { break; } } - boundingBox.bottom = this._tmpCanvas.height; + boundingBox.bottom = height; found = false; - for (let y = this._tmpCanvas.height - 1; y >= 0; y--) { - for (let x = 0; x < this._tmpCanvas.width; x++) { + for (let y = height - 1; y >= 0; y--) { + for (let x = 0; x < width; x++) { const alphaOffset = y * this._tmpCanvas.width * 4 + x * 4 + 3; if (imageData.data[alphaOffset] !== 0) { boundingBox.bottom = y; @@ -497,8 +542,8 @@ export class WebglCharAtlas implements IDisposable { y: (boundingBox.bottom - boundingBox.top + 1) / TEXTURE_HEIGHT }, offset: { - x: -boundingBox.left + TMP_CANVAS_GLYPH_PADDING, - y: -boundingBox.top + TMP_CANVAS_GLYPH_PADDING + x: -boundingBox.left + (restrictedGlyph ? 0 : TMP_CANVAS_GLYPH_PADDING), + y: -boundingBox.top + (restrictedGlyph ? 0 : TMP_CANVAS_GLYPH_PADDING) } }; } diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index da3b4d41..cc210fd3 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -46,7 +46,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { } private _initCanvas(): void { - this._ctx = throwIfFalsy(this._canvas.getContext('2d', {alpha: this._alpha})); + this._ctx = throwIfFalsy(this._canvas.getContext('2d', { alpha: this._alpha })); // Draw the background if this is an opaque layer if (!this._alpha) { this._clearAll(); @@ -224,12 +224,12 @@ export abstract class BaseRenderLayer implements IRenderLayer { */ protected _fillCharTrueColor(terminal: Terminal, cell: CellData, x: number, y: number): void { this._ctx.font = this._getFont(terminal, false, false); - this._ctx.textBaseline = 'middle'; + this._ctx.textBaseline = 'ideographic'; this._clipRow(terminal, y); this._ctx.fillText( cell.getChars(), x * this._scaledCellWidth + this._scaledCharLeft, - y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2); + y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight); } /** diff --git a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts index b2e834d3..880896e0 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts @@ -193,7 +193,12 @@ export class CursorRenderLayer extends BaseRenderLayer { private _clearCursor(): void { if (this._state) { - this._clearCells(this._state.x, this._state.y, this._state.width, 1); + // Avoid potential rounding errors when device pixel ratio is less than 1 + if (window.devicePixelRatio < 1) { + this._clearAll(); + } else { + this._clearCells(this._state.x, this._state.y, this._state.width, 1); + } this._state = { x: 0, y: 0, diff --git a/addons/xterm-addon-webgl/test/WebglRenderer.api.ts b/addons/xterm-addon-webgl/test/WebglRenderer.api.ts index 558605e2..1722e570 100644 --- a/addons/xterm-addon-webgl/test/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/test/WebglRenderer.api.ts @@ -9,7 +9,7 @@ import { assert } from 'chai'; import { openTerminal, pollFor, writeSync, getBrowserType } from '../../../out-test/api/TestUtils'; import { Browser, Page } from 'playwright'; -const APP = 'http://127.0.0.1:3000/test'; +const APP = 'http://127.0.0.1:3001/test'; let browser: Browser; let page: Page; @@ -18,7 +18,7 @@ const height = 600; describe('WebGL Renderer Integration Tests', async () => { const browserType = getBrowserType(); - const isHeadless = process.argv.indexOf('--headless') !== -1; + const isHeadless = process.argv.includes('--headless'); // Firefox works only in non-headless mode https://github.com/microsoft/playwright/issues/1032 const areTestsEnabled = browserType.name() === 'chromium' || (browserType.name() === 'firefox' && !isHeadless); const itWebgl = areTestsEnabled ? it : it.skip; @@ -893,7 +893,7 @@ async function getCellColor(col: number, row: number): Promise { async function setupBrowser(options: ITerminalOptions = { rendererType: 'dom' }): Promise { const browserType = getBrowserType(); browser = await browserType.launch({ - headless: process.argv.indexOf('--headless') !== -1 + headless: process.argv.includes('--headless') }); page = await (await browser.newContext()).newPage(); await page.setViewportSize({ width, height }); diff --git a/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts b/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts index 5c15aa17..ee390d8a 100644 --- a/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts +++ b/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { Terminal, ITerminalAddon } from 'xterm'; +import { Terminal, ITerminalAddon, IEvent } from 'xterm'; declare module 'xterm-addon-webgl' { /** @@ -29,5 +29,10 @@ declare module 'xterm-addon-webgl' { * Clears the terminal's texture atlas and triggers a redraw. */ public clearTextureAtlas(): void; + + /** + * Fired when the WebglRenderer loses context + */ + public get onContextLoss(): IEvent; } } diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 4b2c5f73..f4220176 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -10,7 +10,7 @@ jobs: steps: - task: NodeTool@0 inputs: - versionSpec: '10.x' + versionSpec: '14.x' displayName: 'Install Node.js' - task: YarnInstaller@3 inputs: @@ -43,7 +43,7 @@ jobs: steps: - task: NodeTool@0 inputs: - versionSpec: '10.x' + versionSpec: '14.x' displayName: 'Install Node.js' - task: CacheBeta@1 inputs: @@ -63,7 +63,7 @@ jobs: steps: - task: NodeTool@0 inputs: - versionSpec: '10.x' + versionSpec: '14.x' displayName: 'Install Node.js' - task: CacheBeta@1 inputs: @@ -92,7 +92,7 @@ jobs: displayName: Install required packages - task: NodeTool@0 inputs: - versionSpec: '10.x' + versionSpec: '14.x' displayName: 'Install Node.js' - task: YarnInstaller@3 inputs: @@ -100,10 +100,6 @@ jobs: displayName: 'Install Yarn' - script: yarn --frozen-lockfile 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" @@ -115,14 +111,10 @@ jobs: steps: - task: NodeTool@0 inputs: - versionSpec: '10.x' + versionSpec: '14.x' displayName: 'Install Node.js' - script: yarn --frozen-lockfile 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 @@ -130,6 +122,21 @@ jobs: - script: yarn test-api-webkit --headless --forbid-only displayName: 'Integration tests (Webkit)' +- job: Windows_IntegrationTests + pool: + vmImage: 'vs2017-win2016' + steps: + - task: NodeTool@0 + inputs: + versionSpec: '14.x' + displayName: 'Install Node.js' + - script: yarn --frozen-lockfile + displayName: 'Install dependencies and build' + - script: yarn test-api-chromium --headless --forbid-only + displayName: 'Integration tests (Chromium)' + - script: yarn test-api-firefox --headless --forbid-only + displayName: 'Integration tests (Firefox)' + - job: Release dependsOn: - Linux @@ -137,13 +144,14 @@ jobs: - Windows - Linux_IntegrationTests - macOS_IntegrationTests + - Windows_IntegrationTests condition: and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(variables['FORCE_RELEASE'], 'true'))) pool: vmImage: 'ubuntu-16.04' steps: - task: NodeTool@0 inputs: - versionSpec: '10.x' + versionSpec: '14.x' displayName: 'Install Node.js' - task: YarnInstaller@3 inputs: diff --git a/bin/test_api.js b/bin/test_api.js index 7afb34bc..9fe4659a 100644 --- a/bin/test_api.js +++ b/bin/test_api.js @@ -34,19 +34,31 @@ if (process.argv.length > 2) { env.DEBUG = flagArgs.indexOf('--debug') >= 0 ? 'debug' : ''; +env.PORT = 3001; -const run = cp.spawnSync( - npmBinScript('mocha'), - [...testFiles, ...flagArgs], - { - cwd: path.resolve(__dirname, '..'), - env, - stdio: 'inherit' +const server = cp.spawn('node', ['demo/start'], { + cwd: path.resolve(__dirname, '..'), + env, + stdio: 'pipe' +}) + +server.stdout.on('data', (data) => { + // await for the server to fully start + if (data.indexOf("successfully") !== -1) { + const run = cp.spawnSync( + npmBinScript('mocha'), + [...testFiles, ...flagArgs], { + cwd: path.resolve(__dirname, '..'), + env, + stdio: 'inherit' + } + ); + + function npmBinScript(script) { + return path.resolve(__dirname, `../node_modules/.bin/` + (process.platform === 'win32' ? + `${script}.cmd` : script)); + } + + process.exit(run.status); } -); - -function npmBinScript(script) { - return path.resolve(__dirname, `../node_modules/.bin/` + (process.platform === 'win32' ? `${script}.cmd` : script)); -} - -process.exit(run.status); +}); diff --git a/css/xterm.css b/css/xterm.css index 7ddcc2d0..3fab18bd 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -36,7 +36,6 @@ */ .xterm { - font-feature-settings: "liga" 0; position: relative; user-select: none; -ms-user-select: none; @@ -169,3 +168,7 @@ .xterm-underline { text-decoration: underline; } + +.xterm-strikethrough { + text-decoration: line-through; +} diff --git a/demo/client.ts b/demo/client.ts index 93b7c26c..0bb124cd 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -16,6 +16,7 @@ import { SerializeAddon } from '../addons/xterm-addon-serialize/out/SerializeAdd 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'; +import { LigaturesAddon } from '../addons/xterm-addon-ligatures/out/LigaturesAddon'; // Use webpacked version (yarn package) // import { Terminal } from '../lib/xterm'; @@ -26,6 +27,7 @@ import { Unicode11Addon } from '../addons/xterm-addon-unicode11/out/Unicode11Add // import { WebLinksAddon } from 'xterm-addon-web-links'; // import { WebglAddon } from 'xterm-addon-webgl'; // import { Unicode11Addon } from 'xterm-addon-unicode11'; +// import { LigaturesAddon } from 'xterm-addon-ligatures'; // Pulling in the module's types relies on the above, it's looks a // little weird here as we're importing "this" module @@ -41,6 +43,7 @@ export interface IWindowWithTerminal extends Window { WebLinksAddon?: typeof WebLinksAddon; WebglAddon?: typeof WebglAddon; Unicode11Addon?: typeof Unicode11Addon; + LigaturesAddon?: typeof LigaturesAddon; } declare let window: IWindowWithTerminal; @@ -50,7 +53,7 @@ let socketURL; let socket; let pid; -type AddonType = 'attach' | 'fit' | 'search' | 'serialize' | 'unicode11' | 'web-links' | 'webgl'; +type AddonType = 'attach' | 'fit' | 'search' | 'serialize' | 'unicode11' | 'web-links' | 'webgl' | 'ligatures'; interface IDemoAddon { name: T; @@ -62,8 +65,9 @@ interface IDemoAddon { T extends 'serialize' ? typeof SerializeAddon : T extends 'web-links' ? typeof WebLinksAddon : T extends 'unicode11' ? typeof Unicode11Addon : + T extends 'ligatures' ? typeof LigaturesAddon : typeof WebglAddon; - instance?: + instance?: T extends 'attach' ? AttachAddon : T extends 'fit' ? FitAddon : T extends 'search' ? SearchAddon : @@ -71,6 +75,7 @@ interface IDemoAddon { T extends 'web-links' ? WebLinksAddon : T extends 'webgl' ? WebglAddon : T extends 'unicode11' ? typeof Unicode11Addon : + T extends 'ligatures' ? typeof LigaturesAddon : never; } @@ -81,7 +86,8 @@ const addons: { [T in AddonType]: IDemoAddon} = { 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 } + unicode11: { name: 'unicode11', ctor: Unicode11Addon, canChange: true }, + ligatures: { name: 'ligatures', ctor: LigaturesAddon, canChange: true } }; const terminalContainer = document.getElementById('terminal-container'); @@ -117,6 +123,7 @@ const disposeRecreateButtonHandler = () => { addons.search.instance = undefined; addons.serialize.instance = undefined; addons.unicode11.instance = undefined; + addons.ligatures.instance = undefined; addons['web-links'].instance = undefined; addons.webgl.instance = undefined; document.getElementById('dispose').innerHTML = 'Recreate Terminal'; @@ -133,6 +140,7 @@ if (document.location.pathname === '/test') { window.SearchAddon = SearchAddon; window.SerializeAddon = SerializeAddon; window.Unicode11Addon = Unicode11Addon; + window.LigaturesAddon = LigaturesAddon; window.WebLinksAddon = WebLinksAddon; window.WebglAddon = WebglAddon; } else { @@ -149,7 +157,8 @@ function createTerminal(): void { const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0; term = new Terminal({ - windowsMode: isWindows + windowsMode: isWindows, + fontFamily: 'Fira Code, courier-new, courier, monospace' } as ITerminalOptions); // Load addons diff --git a/demo/start.js b/demo/start.js index a14627c1..b40b9bc3 100644 --- a/demo/start.js +++ b/demo/start.js @@ -49,6 +49,14 @@ const clientConfig = { alias: { common: path.resolve('./out/common'), browser: path.resolve('./out/browser') + }, + fallback: { + // The ligature modules contains fallbacks for node environments, we never want to browserify them + stream: false, + util: false, + os: false, + path: false, + fs: false } }, output: { diff --git a/package.json b/package.json index aa6f5a1b..47fd0a89 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "xterm", "description": "Full xterm terminal, in your browser", - "version": "4.9.0", + "version": "4.13.0", "main": "lib/xterm.js", "style": "css/xterm.css", "types": "typings/xterm.d.ts", @@ -12,6 +12,7 @@ "package": "webpack", "compile": "tsc -b ./src/common/public/tsconfig.json", "start": "node demo/start", + "start-debug": "node --inspect-brk demo/start", "lint": "eslint -c .eslintrc.json --max-warnings 0 --ext .ts src/ addons/", "test": "npm run test-unit", "posttest": "npm run lint", @@ -34,37 +35,37 @@ "vtfeatures": "node bin/extract_vtfeatures.js src/**/*.ts src/*.ts" }, "devDependencies": { - "@types/chai": "^4.2.14", + "@types/chai": "^4.2.18", "@types/debug": "^4.1.5", "@types/deep-equal": "^1.0.1", "@types/glob": "^7.1.3", - "@types/jsdom": "^16.2.5", - "@types/mocha": "^8.0.3", - "@types/node": "^10.17.17", + "@types/jsdom": "^16.2.10", + "@types/mocha": "^8.2.2", + "@types/node": "^14.14.44", "@types/utf8": "^2.1.6", - "@types/webpack": "^4.41.24", - "@types/ws": "^7.2.9", - "@typescript-eslint/eslint-plugin": "^4.0.0", - "@typescript-eslint/parser": "^3.10.1", - "chai": "^4.2.0", - "deep-equal": "^2.0.4", - "eslint": "^7.12.1", + "@types/webpack": "^5.28.0", + "@types/ws": "^7.4.4", + "@typescript-eslint/eslint-plugin": "^4.23.0", + "@typescript-eslint/parser": "^4.23.0", + "chai": "^4.3.4", + "deep-equal": "^2.0.5", + "eslint": "^7.26.0", "express": "^4.17.1", "express-ws": "^4.0.0", - "glob": "^7.0.5", - "jsdom": "^16.4.0", - "mocha": "^8.2.1", - "mustache": "^4.0.1", - "node-pty": "^0.9.0", + "glob": "^7.1.7", + "jsdom": "^16.5.3", + "mocha": "^8.4.0", + "mustache": "^4.2.0", + "node-pty": "^0.10.1", "nyc": "^15.1.0", - "playwright": "^1.5.2", - "source-map-loader": "^1.1.2", - "ts-loader": "^8.0.8", - "typescript": "4.0", + "playwright": "^1.11.0", + "source-map-loader": "^2.0.1", + "ts-loader": "^9.1.2", + "typescript": "^4.2.4", "utf8": "^3.0.0", - "webpack": "^5.4.0", - "webpack-cli": "^4.2.0", - "ws": "^7.3.1", - "xterm-benchmark": "^0.1.3" + "webpack": "^5.37.0", + "webpack-cli": "^4.7.0", + "ws": "^7.4.5", + "xterm-benchmark": "^0.2.1" } } diff --git a/src/browser/AccessibilityManager.ts b/src/browser/AccessibilityManager.ts index e5cbb372..c1ffc39a 100644 --- a/src/browser/AccessibilityManager.ts +++ b/src/browser/AccessibilityManager.ts @@ -112,7 +112,7 @@ export class AccessibilityManager extends Disposable { } private _onBoundaryFocus(e: FocusEvent, position: BoundaryPosition): void { - const boundaryElement = e.target; + const boundaryElement = e.target as HTMLElement; const beforeBoundaryElement = this._rowElements[position === BoundaryPosition.TOP ? 1 : this._rowElements.length - 2]; // Don't scroll if the buffer top has reached the end in that direction diff --git a/src/browser/Clipboard.ts b/src/browser/Clipboard.ts index b0b42022..29e865c8 100644 --- a/src/browser/Clipboard.ts +++ b/src/browser/Clipboard.ts @@ -89,8 +89,8 @@ export function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextA export function rightClickHandler(ev: MouseEvent, textarea: HTMLTextAreaElement, screenElement: HTMLElement, selectionService: ISelectionService, shouldSelectWord: boolean): void { moveTextAreaUnderMouseCursor(ev, textarea, screenElement); - if (shouldSelectWord && !selectionService.isClickInSelection(ev)) { - selectionService.selectWordAtCursor(ev); + if (shouldSelectWord) { + selectionService.rightClickSelect(ev); } // Get textarea ready to copy from the context menu diff --git a/src/browser/ColorManager.test.ts b/src/browser/ColorManager.test.ts index 766f4c12..926a7df3 100644 --- a/src/browser/ColorManager.test.ts +++ b/src/browser/ColorManager.test.ts @@ -17,7 +17,7 @@ describe('ColorManager', () => { dom = new jsdom.JSDOM(''); window = dom.window; document = window.document; - (window).HTMLCanvasElement.prototype.getContext = () => ({ + (window as any).HTMLCanvasElement.prototype.getContext = () => ({ createLinearGradient(): any { return null; }, @@ -36,7 +36,7 @@ describe('ColorManager', () => { for (const key of Object.keys(cm.colors)) { if (key !== 'ansi' && key !== 'contrastCache') { // A #rrggbb or rgba(...) - assert.ok((cm.colors)[key].css.length >= 7); + assert.ok((cm.colors as any)[key].css.length >= 7); } } assert.equal(cm.colors.ansi.length, 256); diff --git a/src/browser/Linkifier.test.ts b/src/browser/Linkifier.test.ts index 2567a669..a07f69ed 100644 --- a/src/browser/Linkifier.test.ts +++ b/src/browser/Linkifier.test.ts @@ -174,7 +174,7 @@ describe('Linkifier', () => { assert.equal(mouseZoneManager.zones[0].y1, 1); assert.equal(mouseZoneManager.zones[0].y2, 1); // Fires done() - mouseZoneManager.zones[0].clickCallback({}); + mouseZoneManager.zones[0].clickCallback({} as any); } }); linkifier.linkifyRows(); @@ -210,7 +210,7 @@ describe('Linkifier', () => { let count = 0; linkifier.registerLinkMatcher(/test/, () => assert.fail(), { validationCallback: (url, cb) => { - count += 1; + count++; if (count === 2) { done(); } diff --git a/src/browser/Linkifier.ts b/src/browser/Linkifier.ts index 6d25e730..b17d66a8 100644 --- a/src/browser/Linkifier.ts +++ b/src/browser/Linkifier.ts @@ -89,7 +89,9 @@ export class Linkifier implements ILinkifier { if (this._rowsTimeoutId) { clearTimeout(this._rowsTimeoutId); } - this._rowsTimeoutId = setTimeout(() => this._linkifyRows(), Linkifier._timeBeforeLatency); + + // Cannot use window.setTimeout since tests need to run in node + this._rowsTimeoutId = setTimeout(() => this._linkifyRows(), Linkifier._timeBeforeLatency) as any as number; } /** diff --git a/src/browser/Linkifier2.ts b/src/browser/Linkifier2.ts index 73ea0268..89542936 100644 --- a/src/browser/Linkifier2.ts +++ b/src/browser/Linkifier2.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { ILinkifier2, ILinkProvider, IBufferCellPosition, ILink, ILinkifierEvent, ILinkDecorations } from 'browser/Types'; +import { ILinkifier2, ILinkProvider, IBufferCellPosition, ILink, ILinkifierEvent, ILinkDecorations, ILinkWithState } from 'browser/Types'; import { IDisposable } from 'common/Types'; import { IMouseService, IRenderService } from './services/Services'; import { IBufferService } from 'common/services/Services'; @@ -11,21 +11,12 @@ import { EventEmitter, IEvent } from 'common/EventEmitter'; import { Disposable, getDisposeArrayDisposable, disposeArray } from 'common/Lifecycle'; import { addDisposableDomListener } from 'browser/Lifecycle'; -interface ILinkState { - decorations: ILinkDecorations; - isHovered: boolean; -} - -interface ILinkWithState { - link: ILink; - state?: ILinkState; -} - export class Linkifier2 extends Disposable implements ILinkifier2 { private _element: HTMLElement | undefined; private _mouseService: IMouseService | undefined; private _renderService: IRenderService | undefined; private _linkProviders: ILinkProvider[] = []; + public get currentLink(): ILinkWithState | undefined { return this._currentLink; } protected _currentLink: ILinkWithState | undefined; private _lastMouseEvent: MouseEvent | undefined; private _linkCacheDisposables: IDisposable[] = []; diff --git a/src/browser/MouseZoneManager.ts b/src/browser/MouseZoneManager.ts index 59b6b0f1..b6740157 100644 --- a/src/browser/MouseZoneManager.ts +++ b/src/browser/MouseZoneManager.ts @@ -156,9 +156,7 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager { private _onTooltip(e: MouseEvent): void { this._tooltipTimeout = undefined; const zone = this._findZoneEventAt(e); - if (zone && zone.tooltipCallback) { - zone.tooltipCallback(e); - } + zone?.tooltipCallback(e); } private _onMouseDown(e: MouseEvent): void { diff --git a/src/browser/Terminal.test.ts b/src/browser/Terminal.test.ts index 8626be09..84e6a87e 100644 --- a/src/browser/Terminal.test.ts +++ b/src/browser/Terminal.test.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { assert, expect } from 'chai'; +import { assert } from 'chai'; import { MockViewport, MockCompositionHelper, MockRenderer, TestTerminal } from 'browser/TestUtils.test'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { CellData } from 'common/buffer/CellData'; @@ -29,10 +29,10 @@ describe('Terminal', () => { beforeEach(() => { term = new TestTerminal(termOptions); term.refresh = () => { }; - (term).renderer = new MockRenderer(); + (term as any).renderer = new MockRenderer(); term.viewport = new MockViewport(); - (term)._compositionHelper = new MockCompositionHelper(); - (term).element = { + (term as any)._compositionHelper = new MockCompositionHelper(); + (term as any).element = { classList: { toggle: () => { }, remove: () => { } @@ -55,55 +55,62 @@ describe('Terminal', () => { // term.onData(() => done()); // term.handler('fake'); // }); - it('should fire the onCursorMove event', (done) => { - term.onCursorMove(() => done()); - term.writeSync('foo'); + it('should fire the onCursorMove event', () => { + return new Promise(async r => { + term.onCursorMove(() => r()); + await term.writeP('foo'); + }); }); - it('should fire the onLineFeed event', (done) => { - term.onLineFeed(() => done()); - term.writeSync('\n'); + it('should fire the onLineFeed event', () => { + return new Promise(async r => { + term.onLineFeed(() => r()); + await term.writeP('\n'); + }); }); - it('should fire a scroll event when scrollback is created', (done) => { - term.onScroll(() => done()); - term.writeSync('\n'.repeat(INIT_ROWS)); + it('should fire a scroll event when scrollback is created', () => { + return new Promise(async r => { + term.onScroll(() => r()); + await term.writeP('\n'.repeat(INIT_ROWS)); + }); }); - it('should fire a scroll event when scrollback is cleared', (done) => { - term.writeSync('\n'.repeat(INIT_ROWS)); - term.onScroll(() => done()); - term.clear(); + it('should fire a scroll event when scrollback is cleared', () => { + return new Promise(async r => { + await term.writeP('\n'.repeat(INIT_ROWS)); + term.onScroll(() => r()); + term.clear(); + }); }); it('should fire a key event after a keypress DOM event', (done) => { term.onKey(e => { assert.equal(typeof e.key, 'string'); - expect(e.domEvent).to.be.an.instanceof(Object); + assert.equal(e.domEvent instanceof Object, true); done(); }); - const evKeyPress = { + const evKeyPress = { preventDefault: () => { }, stopPropagation: () => { }, type: 'keypress', keyCode: 13 - }; + } as KeyboardEvent; term.keyPress(evKeyPress); }); it('should fire a key event after a keydown DOM event', (done) => { term.onKey(e => { assert.equal(typeof e.key, 'string'); - expect(e.domEvent).to.be.an.instanceof(Object); + assert.equal(e.domEvent instanceof Object, true); done(); }); - (term).textarea = { value: '' }; - const evKeyDown = { + (term as any).textarea = { value: '' }; + const evKeyDown = { preventDefault: () => { }, stopPropagation: () => { }, type: 'keydown', keyCode: 13 - }; + } as KeyboardEvent; term.keyDown(evKeyDown); }); it('should fire the onResize event', (done) => { term.onResize(e => { - expect(e).to.have.keys(['cols', 'rows']); assert.equal(typeof e.cols, 'number'); assert.equal(typeof e.rows, 'number'); done(); @@ -124,21 +131,27 @@ describe('Terminal', () => { }); term.write('\x1b]2;title\x07'); }); + it('should fire the onBell event', (done) => { + term.onBell(e => { + done(); + }); + term.write('\x07'); + }); }); describe('attachCustomKeyEventHandler', () => { - const evKeyDown = { + const evKeyDown = { preventDefault: () => { }, stopPropagation: () => { }, type: 'keydown', keyCode: 77 - }; - const evKeyPress = { + } as KeyboardEvent; + const evKeyPress = { preventDefault: () => { }, stopPropagation: () => { }, type: 'keypress', keyCode: 77 - }; + } as KeyboardEvent; beforeEach(() => { term.clearSelection = () => { }; @@ -178,10 +191,10 @@ describe('Terminal', () => { assert.deepEqual(term.buffer.lines.get(i), term.buffer.getBlankLine(DEFAULT_ATTR_DATA)); } }); - it('should clear a buffer larger than rows', () => { + it('should clear a buffer larger than rows', async () => { // Fill the buffer with dummy rows for (let i = 0; i < term.rows * 2; i++) { - term.writeSync('test\n'); + await term.writeP('test\n'); } const promptLine = term.buffer.lines.get(term.buffer.ybase + term.buffer.y); @@ -225,22 +238,24 @@ describe('Terminal', () => { }); term.paste('\r\nfoo\nbar\r'); }); - it('should respect bracketed paste mode', done => { - term.onData(e => { - assert.equal(e, '\x1b[200~foo\x1b[201~'); - done(); + it('should respect bracketed paste mode', () => { + return new Promise(async r => { + term.onData(e => { + assert.equal(e, '\x1b[200~foo\x1b[201~'); + r(); + }); + await term.writeP('\x1b[?2004h'); + term.paste('foo'); }); - term.writeSync('\x1b[?2004h'); - term.paste('foo'); }); }); describe('scroll', () => { describe('scrollLines', () => { let startYDisp: number; - beforeEach(() => { + beforeEach(async () => { for (let i = 0; i < INIT_ROWS * 2; i++) { - term.writeSync('test\r\n'); + await term.writeP('test\r\n'); } startYDisp = INIT_ROWS + 1; }); @@ -273,9 +288,9 @@ describe('Terminal', () => { describe('scrollPages', () => { let startYDisp: number; - beforeEach(() => { + beforeEach(async () => { for (let i = 0; i < term.rows * 3; i++) { - term.writeSync('test\r\n'); + await term.writeP('test\r\n'); } startYDisp = (term.rows * 2) + 1; }); @@ -296,9 +311,9 @@ describe('Terminal', () => { }); describe('scrollToTop', () => { - beforeEach(() => { + beforeEach(async () => { for (let i = 0; i < term.rows * 3; i++) { - term.writeSync('test\r\n'); + await term.writeP('test\r\n'); } }); it('should scroll to the top', () => { @@ -310,9 +325,9 @@ describe('Terminal', () => { describe('scrollToBottom', () => { let startYDisp: number; - beforeEach(() => { + beforeEach(async () => { for (let i = 0; i < term.rows * 3; i++) { - term.writeSync('test\r\n'); + await term.writeP('test\r\n'); } startYDisp = (term.rows * 2) + 1; }); @@ -331,9 +346,9 @@ describe('Terminal', () => { describe('scrollToLine', () => { let startYDisp: number; - beforeEach(() => { + beforeEach(async () => { for (let i = 0; i < term.rows * 3; i++) { - term.writeSync('test\r\n'); + await term.writeP('test\r\n'); } startYDisp = (term.rows * 2) + 1; }); @@ -359,13 +374,13 @@ describe('Terminal', () => { describe('keyPress', () => { it('should scroll down, when a key is pressed and terminal is scrolled up', () => { - const event = { + const event = { type: 'keydown', key: 'a', keyCode: 65, preventDefault: () => { }, stopPropagation: () => { } - }; + } as KeyboardEvent; term.buffer.ydisp = 0; term.buffer.ybase = 40; @@ -375,10 +390,10 @@ describe('Terminal', () => { assert.equal(term.buffer.ydisp, term.buffer.ybase); }); - it('should not scroll down, when a custom keydown handler prevents the event', () => { + it('should not scroll down, when a custom keydown handler prevents the event', async () => { // Add some output to the terminal for (let i = 0; i < term.rows * 3; i++) { - term.writeSync('test\r\n'); + await term.writeP('test\r\n'); } const startYDisp = (term.rows * 2) + 1; term.attachCustomKeyEventHandler(() => { @@ -388,7 +403,7 @@ describe('Terminal', () => { assert.equal(term.buffer.ydisp, startYDisp); term.scrollLines(-1); assert.equal(term.buffer.ydisp, startYDisp - 1); - term.keyPress({ keyCode: 0 }); + term.keyPress({ keyCode: 0 }); assert.equal(term.buffer.ydisp, startYDisp - 1); }); }); @@ -717,75 +732,77 @@ describe('Terminal', () => { }); describe('unicode - surrogates', () => { - it('2 characters per cell', function (): void { + it('2 characters per cell', async function (): Promise { this.timeout(10000); // This is needed because istanbul patches code and slows it down const high = String.fromCharCode(0xD800); const cell = new CellData(); for (let i = 0xDC00; i <= 0xDCFF; ++i) { - term.writeSync(high + String.fromCharCode(i)); + await term.writeP(high + String.fromCharCode(i)); const tchar = term.buffer.lines.get(0)!.loadCell(0, cell); - expect(tchar.getChars()).eql(high + String.fromCharCode(i)); - expect(tchar.getChars().length).eql(2); - expect(tchar.getWidth()).eql(1); - expect(term.buffer.lines.get(0)!.loadCell(1, cell).getChars()).eql(''); + assert.equal(tchar.getChars(), high + String.fromCharCode(i)); + assert.equal(tchar.getChars().length, 2); + assert.equal(tchar.getWidth(), 1); + assert.equal(term.buffer.lines.get(0)!.loadCell(1, cell).getChars(), ''); term.reset(); } }); - it('2 characters at last cell', () => { + it('2 characters at last cell', async () => { const high = String.fromCharCode(0xD800); const cell = new CellData(); for (let i = 0xDC00; i <= 0xDCFF; ++i) { term.buffer.x = term.cols - 1; - term.writeSync(high + String.fromCharCode(i)); - expect(term.buffer.lines.get(0)!.loadCell(term.buffer.x - 1, cell).getChars()).eql(high + String.fromCharCode(i)); - expect(term.buffer.lines.get(0)!.loadCell(term.buffer.x - 1, cell).getChars().length).eql(2); - expect(term.buffer.lines.get(1)!.loadCell(0, cell).getChars()).eql(''); + await term.writeP(high + String.fromCharCode(i)); + assert.equal(term.buffer.lines.get(0)!.loadCell(term.buffer.x - 1, cell).getChars(), high + String.fromCharCode(i)); + assert.equal(term.buffer.lines.get(0)!.loadCell(term.buffer.x - 1, cell).getChars().length, 2); + assert.equal(term.buffer.lines.get(1)!.loadCell(0, cell).getChars(), ''); term.reset(); } }); - it('2 characters per cell over line end with autowrap', () => { + it('2 characters per cell over line end with autowrap', async function (): Promise { + this.timeout(10000); const high = String.fromCharCode(0xD800); const cell = new CellData(); for (let i = 0xDC00; i <= 0xDCFF; ++i) { term.buffer.x = term.cols - 1; - term.writeSync('a' + high + String.fromCharCode(i)); - expect(term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell).getChars()).eql('a'); - expect(term.buffer.lines.get(1)!.loadCell(0, cell).getChars()).eql(high + String.fromCharCode(i)); - expect(term.buffer.lines.get(1)!.loadCell(0, cell).getChars().length).eql(2); - expect(term.buffer.lines.get(1)!.loadCell(1, cell).getChars()).eql(''); + await term.writeP('a' + high + String.fromCharCode(i)); + assert.equal(term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell).getChars(), 'a'); + assert.equal(term.buffer.lines.get(1)!.loadCell(0, cell).getChars(), high + String.fromCharCode(i)); + assert.equal(term.buffer.lines.get(1)!.loadCell(0, cell).getChars().length, 2); + assert.equal(term.buffer.lines.get(1)!.loadCell(1, cell).getChars(), ''); term.reset(); } }); - it('2 characters per cell over line end without autowrap', () => { + it('2 characters per cell over line end without autowrap', async function (): Promise { + this.timeout(10000); const high = String.fromCharCode(0xD800); const cell = new CellData(); for (let i = 0xDC00; i <= 0xDCFF; ++i) { term.buffer.x = term.cols - 1; - term.writeSync('\x1b[?7l'); // Disable wraparound mode + await term.writeP('\x1b[?7l'); // Disable wraparound mode const width = wcwidth((0xD800 - 0xD800) * 0x400 + i - 0xDC00 + 0x10000); if (width !== 1) { continue; } - term.writeSync('a' + high + String.fromCharCode(i)); + await term.writeP('a' + high + String.fromCharCode(i)); // auto wraparound mode should cut off the rest of the line - expect(term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell).getChars()).eql(high + String.fromCharCode(i)); - expect(term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell).getChars().length).eql(2); - expect(term.buffer.lines.get(1)!.loadCell(1, cell).getChars()).eql(''); + assert.equal(term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell).getChars(), high + String.fromCharCode(i)); + assert.equal(term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell).getChars().length, 2); + assert.equal(term.buffer.lines.get(1)!.loadCell(1, cell).getChars(), ''); term.reset(); } }); - it('splitted surrogates', () => { + it('splitted surrogates', async function (): Promise { + this.timeout(10000); const high = String.fromCharCode(0xD800); const cell = new CellData(); for (let i = 0xDC00; i <= 0xDCFF; ++i) { - term.writeSync(high); - term.writeSync(String.fromCharCode(i)); + await term.writeP(high + String.fromCharCode(i)); const tchar = term.buffer.lines.get(0)!.loadCell(0, cell); - expect(tchar.getChars()).eql(high + String.fromCharCode(i)); - expect(tchar.getChars().length).eql(2); - expect(tchar.getWidth()).eql(1); - expect(term.buffer.lines.get(0)!.loadCell(1, cell).getChars()).eql(''); + assert.equal(tchar.getChars(), high + String.fromCharCode(i)); + assert.equal(tchar.getChars().length, 2); + assert.equal(tchar.getWidth(), 1); + assert.equal(term.buffer.lines.get(0)!.loadCell(1, cell).getChars(), ''); term.reset(); } }); @@ -793,239 +810,239 @@ describe('Terminal', () => { describe('unicode - combining characters', () => { const cell = new CellData(); - it('café', () => { - term.writeSync('cafe\u0301'); + it('café', async () => { + await term.writeP('cafe\u0301'); term.buffer.lines.get(0)!.loadCell(3, cell); - expect(cell.getChars()).eql('e\u0301'); - expect(cell.getChars().length).eql(2); - expect(cell.getWidth()).eql(1); + assert.equal(cell.getChars(), 'e\u0301'); + assert.equal(cell.getChars().length, 2); + assert.equal(cell.getWidth(), 1); }); - it('café - end of line', () => { + it('café - end of line', async () => { term.buffer.x = term.cols - 1 - 3; - term.writeSync('cafe\u0301'); + await term.writeP('cafe\u0301'); term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell); - expect(cell.getChars()).eql('e\u0301'); - expect(cell.getChars().length).eql(2); - expect(cell.getWidth()).eql(1); + assert.equal(cell.getChars(), 'e\u0301'); + assert.equal(cell.getChars().length, 2); + assert.equal(cell.getWidth(), 1); term.buffer.lines.get(0)!.loadCell(1, cell); - expect(cell.getChars()).eql(''); - expect(cell.getChars().length).eql(0); - expect(cell.getWidth()).eql(1); + assert.equal(cell.getChars(), ''); + assert.equal(cell.getChars().length, 0); + assert.equal(cell.getWidth(), 1); }); - it('multiple combined é', () => { - term.writeSync(Array(100).join('e\u0301')); + it('multiple combined é', async () => { + await term.writeP(Array(100).join('e\u0301')); for (let i = 0; i < term.cols; ++i) { term.buffer.lines.get(0)!.loadCell(i, cell); - expect(cell.getChars()).eql('e\u0301'); - expect(cell.getChars().length).eql(2); - expect(cell.getWidth()).eql(1); + assert.equal(cell.getChars(), 'e\u0301'); + assert.equal(cell.getChars().length, 2); + assert.equal(cell.getWidth(), 1); } term.buffer.lines.get(1)!.loadCell(0, cell); - expect(cell.getChars()).eql('e\u0301'); - expect(cell.getChars().length).eql(2); - expect(cell.getWidth()).eql(1); + assert.equal(cell.getChars(), 'e\u0301'); + assert.equal(cell.getChars().length, 2); + assert.equal(cell.getWidth(), 1); }); - it('multiple surrogate with combined', () => { - term.writeSync(Array(100).join('\uD800\uDC00\u0301')); + it('multiple surrogate with combined', async () => { + await term.writeP(Array(100).join('\uD800\uDC00\u0301')); for (let i = 0; i < term.cols; ++i) { term.buffer.lines.get(0)!.loadCell(i, cell); - expect(cell.getChars()).eql('\uD800\uDC00\u0301'); - expect(cell.getChars().length).eql(3); - expect(cell.getWidth()).eql(1); + assert.equal(cell.getChars(), '\uD800\uDC00\u0301'); + assert.equal(cell.getChars().length, 3); + assert.equal(cell.getWidth(), 1); } term.buffer.lines.get(1)!.loadCell(0, cell); - expect(cell.getChars()).eql('\uD800\uDC00\u0301'); - expect(cell.getChars().length).eql(3); - expect(cell.getWidth()).eql(1); + assert.equal(cell.getChars(), '\uD800\uDC00\u0301'); + assert.equal(cell.getChars().length, 3); + assert.equal(cell.getWidth(), 1); }); }); describe('unicode - fullwidth characters', () => { const cell = new CellData(); - it('cursor movement even', () => { - expect(term.buffer.x).eql(0); - term.writeSync('¥'); - expect(term.buffer.x).eql(2); + it('cursor movement even', async () => { + assert.equal(term.buffer.x, 0); + await term.writeP('¥'); + assert.equal(term.buffer.x, 2); }); - it('cursor movement odd', () => { + it('cursor movement odd', async () => { term.buffer.x = 1; - expect(term.buffer.x).eql(1); - term.writeSync('¥'); - expect(term.buffer.x).eql(3); + assert.equal(term.buffer.x, 1); + await term.writeP('¥'); + assert.equal(term.buffer.x, 3); }); - it('line of ¥ even', () => { - term.writeSync(Array(50).join('¥')); + it('line of ¥ even', async () => { + await term.writeP(Array(50).join('¥')); for (let i = 0; i < term.cols; ++i) { term.buffer.lines.get(0)!.loadCell(i, cell); if (i % 2) { - expect(cell.getChars()).eql(''); - expect(cell.getChars().length).eql(0); - expect(cell.getWidth()).eql(0); + assert.equal(cell.getChars(), ''); + assert.equal(cell.getChars().length, 0); + assert.equal(cell.getWidth(), 0); } else { - expect(cell.getChars()).eql('¥'); - expect(cell.getChars().length).eql(1); - expect(cell.getWidth()).eql(2); + assert.equal(cell.getChars(), '¥'); + assert.equal(cell.getChars().length, 1); + assert.equal(cell.getWidth(), 2); } } term.buffer.lines.get(1)!.loadCell(0, cell); - expect(cell.getChars()).eql('¥'); - expect(cell.getChars().length).eql(1); - expect(cell.getWidth()).eql(2); + assert.equal(cell.getChars(), '¥'); + assert.equal(cell.getChars().length, 1); + assert.equal(cell.getWidth(), 2); }); - it('line of ¥ odd', () => { + it('line of ¥ odd', async () => { term.buffer.x = 1; - term.writeSync(Array(50).join('¥')); + await term.writeP(Array(50).join('¥')); for (let i = 1; i < term.cols - 1; ++i) { term.buffer.lines.get(0)!.loadCell(i, cell); if (!(i % 2)) { - expect(cell.getChars()).eql(''); - expect(cell.getChars().length).eql(0); - expect(cell.getWidth()).eql(0); + assert.equal(cell.getChars(), ''); + assert.equal(cell.getChars().length, 0); + assert.equal(cell.getWidth(), 0); } else { - expect(cell.getChars()).eql('¥'); - expect(cell.getChars().length).eql(1); - expect(cell.getWidth()).eql(2); + assert.equal(cell.getChars(), '¥'); + assert.equal(cell.getChars().length, 1); + assert.equal(cell.getWidth(), 2); } } term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell); - expect(cell.getChars()).eql(''); - expect(cell.getChars().length).eql(0); - expect(cell.getWidth()).eql(1); + assert.equal(cell.getChars(), ''); + assert.equal(cell.getChars().length, 0); + assert.equal(cell.getWidth(), 1); term.buffer.lines.get(1)!.loadCell(0, cell); - expect(cell.getChars()).eql('¥'); - expect(cell.getChars().length).eql(1); - expect(cell.getWidth()).eql(2); + assert.equal(cell.getChars(), '¥'); + assert.equal(cell.getChars().length, 1); + assert.equal(cell.getWidth(), 2); }); - it('line of ¥ with combining odd', () => { + it('line of ¥ with combining odd', async () => { term.buffer.x = 1; - term.writeSync(Array(50).join('¥\u0301')); + await term.writeP(Array(50).join('¥\u0301')); for (let i = 1; i < term.cols - 1; ++i) { term.buffer.lines.get(0)!.loadCell(i, cell); if (!(i % 2)) { - expect(cell.getChars()).eql(''); - expect(cell.getChars().length).eql(0); - expect(cell.getWidth()).eql(0); + assert.equal(cell.getChars(), ''); + assert.equal(cell.getChars().length, 0); + assert.equal(cell.getWidth(), 0); } else { - expect(cell.getChars()).eql('¥\u0301'); - expect(cell.getChars().length).eql(2); - expect(cell.getWidth()).eql(2); + assert.equal(cell.getChars(), '¥\u0301'); + assert.equal(cell.getChars().length, 2); + assert.equal(cell.getWidth(), 2); } } term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell); - expect(cell.getChars()).eql(''); - expect(cell.getChars().length).eql(0); - expect(cell.getWidth()).eql(1); + assert.equal(cell.getChars(), ''); + assert.equal(cell.getChars().length, 0); + assert.equal(cell.getWidth(), 1); term.buffer.lines.get(1)!.loadCell(0, cell); - expect(cell.getChars()).eql('¥\u0301'); - expect(cell.getChars().length).eql(2); - expect(cell.getWidth()).eql(2); + assert.equal(cell.getChars(), '¥\u0301'); + assert.equal(cell.getChars().length, 2); + assert.equal(cell.getWidth(), 2); }); - it('line of ¥ with combining even', () => { - term.writeSync(Array(50).join('¥\u0301')); + it('line of ¥ with combining even', async () => { + await term.writeP(Array(50).join('¥\u0301')); for (let i = 0; i < term.cols; ++i) { term.buffer.lines.get(0)!.loadCell(i, cell); if (i % 2) { - expect(cell.getChars()).eql(''); - expect(cell.getChars().length).eql(0); - expect(cell.getWidth()).eql(0); + assert.equal(cell.getChars(), ''); + assert.equal(cell.getChars().length, 0); + assert.equal(cell.getWidth(), 0); } else { - expect(cell.getChars()).eql('¥\u0301'); - expect(cell.getChars().length).eql(2); - expect(cell.getWidth()).eql(2); + assert.equal(cell.getChars(), '¥\u0301'); + assert.equal(cell.getChars().length, 2); + assert.equal(cell.getWidth(), 2); } } term.buffer.lines.get(1)!.loadCell(0, cell); - expect(cell.getChars()).eql('¥\u0301'); - expect(cell.getChars().length).eql(2); - expect(cell.getWidth()).eql(2); + assert.equal(cell.getChars(), '¥\u0301'); + assert.equal(cell.getChars().length, 2); + assert.equal(cell.getWidth(), 2); }); - it('line of surrogate fullwidth with combining odd', () => { + it('line of surrogate fullwidth with combining odd', async () => { term.buffer.x = 1; - term.writeSync(Array(50).join('\ud843\ude6d\u0301')); + await term.writeP(Array(50).join('\ud843\ude6d\u0301')); for (let i = 1; i < term.cols - 1; ++i) { term.buffer.lines.get(0)!.loadCell(i, cell); if (!(i % 2)) { - expect(cell.getChars()).eql(''); - expect(cell.getChars().length).eql(0); - expect(cell.getWidth()).eql(0); + assert.equal(cell.getChars(), ''); + assert.equal(cell.getChars().length, 0); + assert.equal(cell.getWidth(), 0); } else { - expect(cell.getChars()).eql('\ud843\ude6d\u0301'); - expect(cell.getChars().length).eql(3); - expect(cell.getWidth()).eql(2); + assert.equal(cell.getChars(), '\ud843\ude6d\u0301'); + assert.equal(cell.getChars().length, 3); + assert.equal(cell.getWidth(), 2); } } term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell); - expect(cell.getChars()).eql(''); - expect(cell.getChars().length).eql(0); - expect(cell.getWidth()).eql(1); + assert.equal(cell.getChars(), ''); + assert.equal(cell.getChars().length, 0); + assert.equal(cell.getWidth(), 1); term.buffer.lines.get(1)!.loadCell(0, cell); - expect(cell.getChars()).eql('\ud843\ude6d\u0301'); - expect(cell.getChars().length).eql(3); - expect(cell.getWidth()).eql(2); + assert.equal(cell.getChars(), '\ud843\ude6d\u0301'); + assert.equal(cell.getChars().length, 3); + assert.equal(cell.getWidth(), 2); }); - it('line of surrogate fullwidth with combining even', () => { - term.writeSync(Array(50).join('\ud843\ude6d\u0301')); + it('line of surrogate fullwidth with combining even', async () => { + await term.writeP(Array(50).join('\ud843\ude6d\u0301')); for (let i = 0; i < term.cols; ++i) { term.buffer.lines.get(0)!.loadCell(i, cell); if (i % 2) { - expect(cell.getChars()).eql(''); - expect(cell.getChars().length).eql(0); - expect(cell.getWidth()).eql(0); + assert.equal(cell.getChars(), ''); + assert.equal(cell.getChars().length, 0); + assert.equal(cell.getWidth(), 0); } else { - expect(cell.getChars()).eql('\ud843\ude6d\u0301'); - expect(cell.getChars().length).eql(3); - expect(cell.getWidth()).eql(2); + assert.equal(cell.getChars(), '\ud843\ude6d\u0301'); + assert.equal(cell.getChars().length, 3); + assert.equal(cell.getWidth(), 2); } } term.buffer.lines.get(1)!.loadCell(0, cell); - expect(cell.getChars()).eql('\ud843\ude6d\u0301'); - expect(cell.getChars().length).eql(3); - expect(cell.getWidth()).eql(2); + assert.equal(cell.getChars(), '\ud843\ude6d\u0301'); + assert.equal(cell.getChars().length, 3); + assert.equal(cell.getWidth(), 2); }); }); describe('insert mode', () => { const cell = new CellData(); - it('halfwidth - all', () => { - term.writeSync(Array(9).join('0123456789').slice(-80)); + it('halfwidth - all', async () => { + await term.writeP(Array(9).join('0123456789').slice(-80)); term.buffer.x = 10; term.buffer.y = 0; term.write('\x1b[4h'); - term.writeSync('abcde'); - expect(term.buffer.lines.get(0)!.length).eql(term.cols); - expect(term.buffer.lines.get(0)!.loadCell(10, cell).getChars()).eql('a'); - expect(term.buffer.lines.get(0)!.loadCell(14, cell).getChars()).eql('e'); - expect(term.buffer.lines.get(0)!.loadCell(15, cell).getChars()).eql('0'); - expect(term.buffer.lines.get(0)!.loadCell(79, cell).getChars()).eql('4'); + await term.writeP('abcde'); + assert.equal(term.buffer.lines.get(0)!.length, term.cols); + assert.equal(term.buffer.lines.get(0)!.loadCell(10, cell).getChars(), 'a'); + assert.equal(term.buffer.lines.get(0)!.loadCell(14, cell).getChars(), 'e'); + assert.equal(term.buffer.lines.get(0)!.loadCell(15, cell).getChars(), '0'); + assert.equal(term.buffer.lines.get(0)!.loadCell(79, cell).getChars(), '4'); }); - it('fullwidth - insert', () => { - term.writeSync(Array(9).join('0123456789').slice(-80)); + it('fullwidth - insert', async () => { + await term.writeP(Array(9).join('0123456789').slice(-80)); term.buffer.x = 10; term.buffer.y = 0; term.write('\x1b[4h'); - term.writeSync('¥¥¥'); - expect(term.buffer.lines.get(0)!.length).eql(term.cols); - expect(term.buffer.lines.get(0)!.loadCell(10, cell).getChars()).eql('¥'); - expect(term.buffer.lines.get(0)!.loadCell(11, cell).getChars()).eql(''); - expect(term.buffer.lines.get(0)!.loadCell(14, cell).getChars()).eql('¥'); - expect(term.buffer.lines.get(0)!.loadCell(15, cell).getChars()).eql(''); - expect(term.buffer.lines.get(0)!.loadCell(79, cell).getChars()).eql('3'); + await term.writeP('¥¥¥'); + assert.equal(term.buffer.lines.get(0)!.length, term.cols); + assert.equal(term.buffer.lines.get(0)!.loadCell(10, cell).getChars(), '¥'); + assert.equal(term.buffer.lines.get(0)!.loadCell(11, cell).getChars(), ''); + assert.equal(term.buffer.lines.get(0)!.loadCell(14, cell).getChars(), '¥'); + assert.equal(term.buffer.lines.get(0)!.loadCell(15, cell).getChars(), ''); + assert.equal(term.buffer.lines.get(0)!.loadCell(79, cell).getChars(), '3'); }); - it('fullwidth - right border', () => { - term.writeSync(Array(41).join('¥')); + it('fullwidth - right border', async () => { + await term.writeP(Array(41).join('¥')); term.buffer.x = 10; term.buffer.y = 0; term.write('\x1b[4h'); - term.writeSync('a'); - expect(term.buffer.lines.get(0)!.length).eql(term.cols); - expect(term.buffer.lines.get(0)!.loadCell(10, cell).getChars()).eql('a'); - expect(term.buffer.lines.get(0)!.loadCell(11, cell).getChars()).eql('¥'); - expect(term.buffer.lines.get(0)!.loadCell(79, cell).getChars()).eql(''); // fullwidth char got replaced - term.writeSync('b'); - expect(term.buffer.lines.get(0)!.length).eql(term.cols); - expect(term.buffer.lines.get(0)!.loadCell(11, cell).getChars()).eql('b'); - expect(term.buffer.lines.get(0)!.loadCell(12, cell).getChars()).eql('¥'); - expect(term.buffer.lines.get(0)!.loadCell(79, cell).getChars()).eql(''); // empty cell after fullwidth + await term.writeP('a'); + assert.equal(term.buffer.lines.get(0)!.length, term.cols); + assert.equal(term.buffer.lines.get(0)!.loadCell(10, cell).getChars(), 'a'); + assert.equal(term.buffer.lines.get(0)!.loadCell(11, cell).getChars(), '¥'); + assert.equal(term.buffer.lines.get(0)!.loadCell(79, cell).getChars(), ''); // fullwidth char got replaced + await term.writeP('b'); + assert.equal(term.buffer.lines.get(0)!.length, term.cols); + assert.equal(term.buffer.lines.get(0)!.loadCell(11, cell).getChars(), 'b'); + assert.equal(term.buffer.lines.get(0)!.loadCell(12, cell).getChars(), '¥'); + assert.equal(term.buffer.lines.get(0)!.loadCell(79, cell).getChars(), ''); // empty cell after fullwidth }); }); @@ -1043,85 +1060,87 @@ describe('Terminal', () => { linkifier.attachToDom({} as any, mouseZoneManager); }); - function assertLinkifiesInTerminal(rowText: string, linkMatcherRegex: RegExp, links: {x1: number, y1: number, x2: number, y2: number}[], done: Mocha.Done): void { - terminal.writeSync(rowText); - linkifier.registerLinkMatcher(linkMatcherRegex, () => {}); - linkifier.linkifyRows(); - // Allow linkify to happen - setTimeout(() => { - assert.equal(mouseZoneManager.zones.length, links.length); - links.forEach((l, i) => { - assert.equal(mouseZoneManager.zones[i].x1, l.x1 + 1); - assert.equal(mouseZoneManager.zones[i].x2, l.x2 + 1); - assert.equal(mouseZoneManager.zones[i].y1, l.y1 + 1); - assert.equal(mouseZoneManager.zones[i].y2, l.y2 + 1); - }); - done(); - }, 0); + function assertLinkifiesInTerminal(rowText: string, linkMatcherRegex: RegExp, links: { x1: number, y1: number, x2: number, y2: number }[]): Promise { + return new Promise(async r => { + await terminal.writeP(rowText); + linkifier.registerLinkMatcher(linkMatcherRegex, () => { }); + linkifier.linkifyRows(); + // Allow linkify to happen + setTimeout(() => { + assert.equal(mouseZoneManager.zones.length, links.length); + links.forEach((l, i) => { + assert.equal(mouseZoneManager.zones[i].x1, l.x1 + 1); + assert.equal(mouseZoneManager.zones[i].x2, l.x2 + 1); + assert.equal(mouseZoneManager.zones[i].y1, l.y1 + 1); + assert.equal(mouseZoneManager.zones[i].y2, l.y2 + 1); + }); + r(); + }, 0); + }); } describe('unicode before the match', () => { - it('combining - match within one line', function(done: () => void): void { - assertLinkifiesInTerminal('e\u0301e\u0301e\u0301 foo', /foo/, [{x1: 4, x2: 7, y1: 0, y2: 0}], done); + it('combining - match within one line', () => { + return assertLinkifiesInTerminal('e\u0301e\u0301e\u0301 foo', /foo/, [{ x1: 4, x2: 7, y1: 0, y2: 0 }]); }); - it('combining - match over two lines', function(done: () => void): void { - assertLinkifiesInTerminal('e\u0301e\u0301e\u0301 foo', /foo/, [{x1: 8, x2: 1, y1: 0, y2: 1}], done); + it('combining - match over two lines', () => { + return assertLinkifiesInTerminal('e\u0301e\u0301e\u0301 foo', /foo/, [{ x1: 8, x2: 1, y1: 0, y2: 1 }]); }); - it('surrogate - match within one line', function(done: () => void): void { - assertLinkifiesInTerminal('𝄞𝄞𝄞 foo', /foo/, [{x1: 4, x2: 7, y1: 0, y2: 0}], done); + it('surrogate - match within one line', () => { + return assertLinkifiesInTerminal('𝄞𝄞𝄞 foo', /foo/, [{ x1: 4, x2: 7, y1: 0, y2: 0 }]); }); - it('surrogate - match over two lines', function(done: () => void): void { - assertLinkifiesInTerminal('𝄞𝄞𝄞 foo', /foo/, [{x1: 8, x2: 1, y1: 0, y2: 1}], done); + it('surrogate - match over two lines', () => { + return assertLinkifiesInTerminal('𝄞𝄞𝄞 foo', /foo/, [{ x1: 8, x2: 1, y1: 0, y2: 1 }]); }); - it('combining surrogate - match within one line', function(done: () => void): void { - assertLinkifiesInTerminal('𓂀\u0301𓂀\u0301𓂀\u0301 foo', /foo/, [{x1: 4, x2: 7, y1: 0, y2: 0}], done); + it('combining surrogate - match within one line', () => { + return assertLinkifiesInTerminal('𓂀\u0301𓂀\u0301𓂀\u0301 foo', /foo/, [{ x1: 4, x2: 7, y1: 0, y2: 0 }]); }); - it('combining surrogate - match over two lines', function(done: () => void): void { - assertLinkifiesInTerminal('𓂀\u0301𓂀\u0301𓂀\u0301 foo', /foo/, [{x1: 8, x2: 1, y1: 0, y2: 1}], done); + it('combining surrogate - match over two lines', () => { + return assertLinkifiesInTerminal('𓂀\u0301𓂀\u0301𓂀\u0301 foo', /foo/, [{ x1: 8, x2: 1, y1: 0, y2: 1 }]); }); - it('fullwidth - match within one line', function(done: () => void): void { - assertLinkifiesInTerminal('12 foo', /foo/, [{x1: 5, x2: 8, y1: 0, y2: 0}], done); + it('fullwidth - match within one line', () => { + return assertLinkifiesInTerminal('12 foo', /foo/, [{ x1: 5, x2: 8, y1: 0, y2: 0 }]); }); - it('fullwidth - match over two lines', function(done: () => void): void { - assertLinkifiesInTerminal('12 foo', /foo/, [{x1: 8, x2: 1, y1: 0, y2: 1}], done); + it('fullwidth - match over two lines', () => { + return assertLinkifiesInTerminal('12 foo', /foo/, [{ x1: 8, x2: 1, y1: 0, y2: 1 }]); }); - it('combining fullwidth - match within one line', function(done: () => void): void { - assertLinkifiesInTerminal('¥\u0301¥\u0301 foo', /foo/, [{x1: 5, x2: 8, y1: 0, y2: 0}], done); + it('combining fullwidth - match within one line', () => { + return assertLinkifiesInTerminal('¥\u0301¥\u0301 foo', /foo/, [{ x1: 5, x2: 8, y1: 0, y2: 0 }]); }); - it('combining fullwidth - match over two lines', function(done: () => void): void { - assertLinkifiesInTerminal('¥\u0301¥\u0301 foo', /foo/, [{x1: 8, x2: 1, y1: 0, y2: 1}], done); + it('combining fullwidth - match over two lines', () => { + return assertLinkifiesInTerminal('¥\u0301¥\u0301 foo', /foo/, [{ x1: 8, x2: 1, y1: 0, y2: 1 }]); }); }); describe('unicode within the match', () => { - it('combining - match within one line', function(done: () => void): void { - assertLinkifiesInTerminal('test cafe\u0301', /cafe\u0301/, [{x1: 5, x2: 9, y1: 0, y2: 0}], done); + it('combining - match within one line', () => { + return assertLinkifiesInTerminal('test cafe\u0301', /cafe\u0301/, [{ x1: 5, x2: 9, y1: 0, y2: 0 }]); }); - it('combining - match over two lines', function(done: () => void): void { - assertLinkifiesInTerminal('testtest cafe\u0301', /cafe\u0301/, [{x1: 9, x2: 3, y1: 0, y2: 1}], done); + it('combining - match over two lines', () => { + return assertLinkifiesInTerminal('testtest cafe\u0301', /cafe\u0301/, [{ x1: 9, x2: 3, y1: 0, y2: 1 }]); }); - it('surrogate - match within one line', function(done: () => void): void { - assertLinkifiesInTerminal('test a𝄞b', /a𝄞b/, [{x1: 5, x2: 8, y1: 0, y2: 0}], done); + it('surrogate - match within one line', () => { + return assertLinkifiesInTerminal('test a𝄞b', /a𝄞b/, [{ x1: 5, x2: 8, y1: 0, y2: 0 }]); }); - it('surrogate - match over two lines', function(done: () => void): void { - assertLinkifiesInTerminal('testtest a𝄞b', /a𝄞b/, [{x1: 9, x2: 2, y1: 0, y2: 1}], done); + it('surrogate - match over two lines', () => { + return assertLinkifiesInTerminal('testtest a𝄞b', /a𝄞b/, [{ x1: 9, x2: 2, y1: 0, y2: 1 }]); }); - it('combining surrogate - match within one line', function(done: () => void): void { - assertLinkifiesInTerminal('test a𓂀\u0301b', /a𓂀\u0301b/, [{x1: 5, x2: 8, y1: 0, y2: 0}], done); + it('combining surrogate - match within one line', () => { + return assertLinkifiesInTerminal('test a𓂀\u0301b', /a𓂀\u0301b/, [{ x1: 5, x2: 8, y1: 0, y2: 0 }]); }); - it('combining surrogate - match over two lines', function(done: () => void): void { - assertLinkifiesInTerminal('testtest a𓂀\u0301b', /a𓂀\u0301b/, [{x1: 9, x2: 2, y1: 0, y2: 1}], done); + it('combining surrogate - match over two lines', () => { + return assertLinkifiesInTerminal('testtest a𓂀\u0301b', /a𓂀\u0301b/, [{ x1: 9, x2: 2, y1: 0, y2: 1 }]); }); - it('fullwidth - match within one line', function(done: () => void): void { - assertLinkifiesInTerminal('test a1b', /a1b/, [{x1: 5, x2: 9, y1: 0, y2: 0}], done); + it('fullwidth - match within one line', () => { + return assertLinkifiesInTerminal('test a1b', /a1b/, [{ x1: 5, x2: 9, y1: 0, y2: 0 }]); }); - it('fullwidth - match over two lines', function(done: () => void): void { - assertLinkifiesInTerminal('testtest a1b', /a1b/, [{x1: 9, x2: 3, y1: 0, y2: 1}], done); + it('fullwidth - match over two lines', () => { + return assertLinkifiesInTerminal('testtest a1b', /a1b/, [{ x1: 9, x2: 3, y1: 0, y2: 1 }]); }); - it('combining fullwidth - match within one line', function(done: () => void): void { - assertLinkifiesInTerminal('test a¥\u0301b', /a¥\u0301b/, [{x1: 5, x2: 9, y1: 0, y2: 0}], done); + it('combining fullwidth - match within one line', () => { + return assertLinkifiesInTerminal('test a¥\u0301b', /a¥\u0301b/, [{ x1: 5, x2: 9, y1: 0, y2: 0 }]); }); - it('combining fullwidth - match over two lines', function(done: () => void): void { - assertLinkifiesInTerminal('testtest a¥\u0301b', /a¥\u0301b/, [{x1: 9, x2: 3, y1: 0, y2: 1}], done); + it('combining fullwidth - match over two lines', () => { + return assertLinkifiesInTerminal('testtest a¥\u0301b', /a¥\u0301b/, [{ x1: 9, x2: 3, y1: 0, y2: 1 }]); }); }); }); @@ -1130,12 +1149,12 @@ describe('Terminal', () => { let terminal: TestTerminal; beforeEach(() => { - terminal = new TestTerminal({rows: 5, cols: 10, scrollback: 5}); + terminal = new TestTerminal({ rows: 5, cols: 10, scrollback: 5 }); }); - it('multiline ascii', () => { + it('multiline ascii', async () => { const input = 'This is ASCII text spanning multiple lines.'; - terminal.writeSync(input); + await terminal.writeP(input); const s = terminal.buffer.iterator(true).next().content; assert.equal(input, s); for (let i = 0; i < input.length; ++i) { @@ -1144,9 +1163,9 @@ describe('Terminal', () => { } }); - it('combining e\u0301 in a sentence', () => { + it('combining e\u0301 in a sentence', async () => { const input = 'Sitting in the cafe\u0301 drinking coffee.'; - terminal.writeSync(input); + await terminal.writeP(input); const s = terminal.buffer.iterator(true).next().content; assert.equal(input, s); for (let i = 0; i < 19; ++i) { @@ -1164,9 +1183,9 @@ describe('Terminal', () => { } }); - it('multiline combining e\u0301', () => { + it('multiline combining e\u0301', async () => { const input = 'e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301'; - terminal.writeSync(input); + await terminal.writeP(input); const s = terminal.buffer.iterator(true).next().content; assert.equal(input, s); // every buffer cell index contains 2 string indices @@ -1176,9 +1195,9 @@ describe('Terminal', () => { } }); - it('surrogate char in a sentence', () => { + it('surrogate char in a sentence', async () => { const input = 'The 𝄞 is a clef widely used in modern notation.'; - terminal.writeSync(input); + await terminal.writeP(input); const s = terminal.buffer.iterator(true).next().content; assert.equal(input, s); for (let i = 0; i < 5; ++i) { @@ -1196,9 +1215,9 @@ describe('Terminal', () => { } }); - it('multiline surrogate char', () => { + it('multiline surrogate char', async () => { const input = '𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞'; - terminal.writeSync(input); + await terminal.writeP(input); const s = terminal.buffer.iterator(true).next().content; assert.equal(input, s); // every buffer cell index contains 2 string indices @@ -1208,10 +1227,10 @@ describe('Terminal', () => { } }); - it('surrogate char with combining', () => { + it('surrogate char with combining', async () => { // eye of Ra with acute accent - string length of 3 const input = '𓂀\u0301 - the eye hiroglyph with an acute accent.'; - terminal.writeSync(input); + await terminal.writeP(input); const s = terminal.buffer.iterator(true).next().content; assert.equal(input, s); // index 0..2 should map to 0 @@ -1223,9 +1242,9 @@ describe('Terminal', () => { } }); - it('multiline surrogate with combining', () => { + it('multiline surrogate with combining', async () => { const input = '𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301'; - terminal.writeSync(input); + await terminal.writeP(input); const s = terminal.buffer.iterator(true).next().content; assert.equal(input, s); // every buffer cell index contains 3 string indices @@ -1235,9 +1254,9 @@ describe('Terminal', () => { } }); - it('fullwidth chars', () => { + it('fullwidth chars', async () => { const input = 'These 123 are some fat numbers.'; - terminal.writeSync(input); + await terminal.writeP(input); const s = terminal.buffer.iterator(true).next().content; assert.equal(input, s); for (let i = 0; i < 6; ++i) { @@ -1254,9 +1273,9 @@ describe('Terminal', () => { } }); - it('multiline fullwidth chars', () => { + it('multiline fullwidth chars', async () => { const input = '12345678901234567890'; - terminal.writeSync(input); + await terminal.writeP(input); const s = terminal.buffer.iterator(true).next().content; assert.equal(input, s); for (let i = 9; i < input.length; ++i) { @@ -1265,9 +1284,9 @@ describe('Terminal', () => { } }); - it('fullwidth combining with emoji - match emoji cell', () => { + it('fullwidth combining with emoji - match emoji cell', async () => { const input = 'Lots of ¥\u0301 make me 😃.'; - terminal.writeSync(input); + await terminal.writeP(input); const s = terminal.buffer.iterator(true).next().content; assert.equal(input, s); const stringIndex = s.match(/😃/)!.index!; @@ -1275,14 +1294,14 @@ describe('Terminal', () => { assert(terminal.buffer.lines.get(bufferIndex[0])!.loadCell(bufferIndex[1], new CellData()).getChars(), '😃'); }); - it('multiline fullwidth chars with offset 1 (currently tests for broken behavior)', () => { + it('multiline fullwidth chars with offset 1 (currently tests for broken behavior)', async () => { const input = 'a12345678901234567890'; // the 'a' at the beginning moves all fullwidth chars one to the right // now the end of the line contains a dangling empty cell since // the next fullwidth char has to wrap early // the dangling last cell is wrongly added in the string // --> fixable after resolving #1685 - terminal.writeSync(input); + await terminal.writeP(input); const s = terminal.buffer.iterator(true).next().content; assert.equal(input, s); for (let i = 10; i < input.length; ++i) { @@ -1292,9 +1311,9 @@ describe('Terminal', () => { } }); - it('test fully wrapped buffer up to last char', () => { + it('test fully wrapped buffer up to last char', async () => { const input = Array(6).join('1234567890'); - terminal.writeSync(input); + await terminal.writeP(input); const s = terminal.buffer.iterator(true).next().content; assert.equal(input, s); for (let i = 0; i < input.length; ++i) { @@ -1303,10 +1322,10 @@ describe('Terminal', () => { } }); - it('test fully wrapped buffer up to last char with full width odd', () => { + it('test fully wrapped buffer up to last char with full width odd', async () => { const input = 'a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301' - + 'a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301'; - terminal.writeSync(input); + + 'a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301'; + await terminal.writeP(input); const s = terminal.buffer.iterator(true).next().content; assert.equal(input, s); for (let i = 0; i < input.length; ++i) { @@ -1321,17 +1340,17 @@ describe('Terminal', () => { } }); - it('should handle \t in lines correctly', () => { + it('should handle \t in lines correctly', async () => { const input = '\thttps://google.de'; - terminal.writeSync(input); + await terminal.writeP(input); const s = terminal.buffer.iterator(true).next().content; assert.equal(s, Array(terminal.optionsService.options.tabStopWidth + 1).join(' ') + 'https://google.de'); }); }); - describe('BufferStringIterator', function(): void { - it('iterator does not overflow buffer limits', function(): void { - const terminal = new TestTerminal({rows: 5, cols: 10, scrollback: 5}); + describe('BufferStringIterator', function (): void { + it('iterator does not overflow buffer limits', async () => { + const terminal = new TestTerminal({ rows: 5, cols: 10, scrollback: 5 }); const data = [ 'aaaaaaaaaa', 'aaaaaaaaa\n', @@ -1344,9 +1363,9 @@ describe('Terminal', () => { 'aaaaaaaaaa', 'aaaaaaaaaa' ]; - terminal.writeSync(data.join('')); + await terminal.writeP(data.join('')); // brute force test with insane values - expect(() => { + assert.doesNotThrow(() => { for (let overscan = 0; overscan < 20; ++overscan) { for (let start = -10; start < 20; ++start) { for (let end = -10; end < 20; ++end) { @@ -1357,182 +1376,67 @@ describe('Terminal', () => { } } } - }).to.not.throw(); + }); }); }); describe('Windows Mode', () => { - it('should mark lines as wrapped when the line ends in a non-null character after a LF', () => { + it('should mark lines as wrapped when the line ends in a non-null character after a LF', async () => { const data = [ 'aaaaaaaaaa\n\r', // cannot wrap as it's the first 'aaaaaaaaa\n\r', // wrapped (windows mode only) 'aaaaaaaaa' // not wrapped ]; - const normalTerminal = new TestTerminal({rows: 5, cols: 10, windowsMode: false}); - normalTerminal.writeSync(data.join('')); + const normalTerminal = new TestTerminal({ rows: 5, cols: 10, windowsMode: false }); + await normalTerminal.writeP(data.join('')); assert.equal(normalTerminal.buffer.lines.get(0)!.isWrapped, false); assert.equal(normalTerminal.buffer.lines.get(1)!.isWrapped, false); assert.equal(normalTerminal.buffer.lines.get(2)!.isWrapped, false); - const windowsModeTerminal = new TestTerminal({rows: 5, cols: 10, windowsMode: true}); - windowsModeTerminal.writeSync(data.join('')); + const windowsModeTerminal = new TestTerminal({ rows: 5, cols: 10, windowsMode: true }); + await windowsModeTerminal.writeP(data.join('')); assert.equal(windowsModeTerminal.buffer.lines.get(0)!.isWrapped, false); assert.equal(windowsModeTerminal.buffer.lines.get(1)!.isWrapped, true, 'This line should wrap in Windows mode as the previous line ends in a non-null character'); assert.equal(windowsModeTerminal.buffer.lines.get(2)!.isWrapped, false); }); - it('should mark lines as wrapped when the line ends in a non-null character after a CUP', () => { + it('should mark lines as wrapped when the line ends in a non-null character after a CUP', async () => { const data = [ 'aaaaaaaaaa\x1b[2;1H', // cannot wrap as it's the first 'aaaaaaaaa\x1b[3;1H', // wrapped (windows mode only) 'aaaaaaaaa' // not wrapped ]; - const normalTerminal = new TestTerminal({rows: 5, cols: 10, windowsMode: false}); - normalTerminal.writeSync(data.join('')); + const normalTerminal = new TestTerminal({ rows: 5, cols: 10, windowsMode: false }); + await normalTerminal.writeP(data.join('')); assert.equal(normalTerminal.buffer.lines.get(0)!.isWrapped, false); assert.equal(normalTerminal.buffer.lines.get(1)!.isWrapped, false); assert.equal(normalTerminal.buffer.lines.get(2)!.isWrapped, false); - const windowsModeTerminal = new TestTerminal({rows: 5, cols: 10, windowsMode: true}); - windowsModeTerminal.writeSync(data.join('')); + const windowsModeTerminal = new TestTerminal({ rows: 5, cols: 10, windowsMode: true }); + await windowsModeTerminal.writeP(data.join('')); assert.equal(windowsModeTerminal.buffer.lines.get(0)!.isWrapped, false); assert.equal(windowsModeTerminal.buffer.lines.get(1)!.isWrapped, true, 'This line should wrap in Windows mode as the previous line ends in a non-null character'); assert.equal(windowsModeTerminal.buffer.lines.get(2)!.isWrapped, false); }); }); - it('convertEol setting', function(): void { + it('convertEol setting', async () => { // not converting - const termNotConverting = new TestTerminal({cols: 15, rows: 10}); - termNotConverting.writeSync('Hello\nWorld'); - expect(termNotConverting.buffer.lines.get(0)!.translateToString(false)).equals('Hello '); - expect(termNotConverting.buffer.lines.get(1)!.translateToString(false)).equals(' World '); - expect(termNotConverting.buffer.lines.get(0)!.translateToString(true)).equals('Hello'); - expect(termNotConverting.buffer.lines.get(1)!.translateToString(true)).equals(' World'); + const termNotConverting = new TestTerminal({ cols: 15, rows: 10 }); + await termNotConverting.writeP('Hello\nWorld'); + assert.equal(termNotConverting.buffer.lines.get(0)!.translateToString(false), 'Hello '); + assert.equal(termNotConverting.buffer.lines.get(1)!.translateToString(false), ' World '); + assert.equal(termNotConverting.buffer.lines.get(0)!.translateToString(true), 'Hello'); + assert.equal(termNotConverting.buffer.lines.get(1)!.translateToString(true), ' World'); // converting - const termConverting = new TestTerminal({cols: 15, rows: 10, convertEol: true}); - termConverting.writeSync('Hello\nWorld'); - expect(termConverting.buffer.lines.get(0)!.translateToString(false)).equals('Hello '); - expect(termConverting.buffer.lines.get(1)!.translateToString(false)).equals('World '); - expect(termConverting.buffer.lines.get(0)!.translateToString(true)).equals('Hello'); - expect(termConverting.buffer.lines.get(1)!.translateToString(true)).equals('World'); - }); - describe('Terminal InputHandler integration', () => { - 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; - } - - // This suite cannot live in InputHandler unless Terminal.scroll moved into IBufferService - 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']); - }); - }); - - describe('BS with reverseWraparound set/unset', () => { - const ttyBS = '\x08 \x08'; // tty ICANON sends on pressing BS - - beforeEach(() => { - term = new TestTerminal({cols: 5, rows: 5, scrollback: 1}); - }); - - describe('reverseWraparound set', () => { - it('should not reverse outside of scroll margins', () => { - // prepare buffer content - term.writeSync('#####abcdefghijklmnopqrstuvwxy'); - assert.deepEqual(getLines(term, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', 'uvwxy']); - assert.equal(term.buffer.ydisp, 1); - assert.equal(term.buffer.x, 5); - assert.equal(term.buffer.y, 4); - term.writeSync(ttyBS.repeat(100)); - assert.deepEqual(getLines(term, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', ' y']); - - term.writeSync('\x1b[?45h'); - term.writeSync('uvwxy'); - - // set top/bottom to 1/3 (0-based) - term.writeSync('\x1b[2;4r'); - // place cursor below scroll bottom - term.buffer.x = 5; - term.buffer.y = 4; - term.writeSync(ttyBS.repeat(100)); - assert.deepEqual(getLines(term, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', ' ']); - - term.writeSync('uvwxy'); - // place cursor within scroll margins - term.buffer.x = 5; - term.buffer.y = 3; - term.writeSync(ttyBS.repeat(100)); - assert.deepEqual(getLines(term, 6), ['#####', 'abcde', ' ', ' ', ' ', 'uvwxy']); - assert.equal(term.buffer.x, 0); - assert.equal(term.buffer.y, term.buffer.scrollTop); // stops at 0, scrollTop - - term.writeSync('fghijklmnopqrst'); - // place cursor above scroll top - term.buffer.x = 5; - term.buffer.y = 0; - term.writeSync(ttyBS.repeat(100)); - assert.deepEqual(getLines(term, 6), ['#####', ' ', 'fghij', 'klmno', 'pqrst', 'uvwxy']); - }); - }); - }); + const termConverting = new TestTerminal({ cols: 15, rows: 10, convertEol: true }); + await termConverting.writeP('Hello\nWorld'); + assert.equal(termConverting.buffer.lines.get(0)!.translateToString(false), 'Hello '); + assert.equal(termConverting.buffer.lines.get(1)!.translateToString(false), 'World '); + assert.equal(termConverting.buffer.lines.get(0)!.translateToString(true), 'Hello'); + assert.equal(termConverting.buffer.lines.get(1)!.translateToString(true), 'World'); }); // FIXME: move to common/CoreTerminal.test once the trimming is moved over @@ -1542,22 +1446,22 @@ describe('Terminal', () => { let markers: IMarker[]; let disposeStack: IMarker[]; let term: TestTerminal; - beforeEach(() => { + beforeEach(async () => { term = new TestTerminal({}); markers = []; disposeStack = []; term.optionsService.setOption('scrollback', 1); term.resize(10, 5); markers.push(term.buffers.active.addMarker(term.buffers.active.y)); - term.writeSync('\x1b[r0\r\n'); + await term.writeP('\x1b[r0\r\n'); markers.push(term.buffers.active.addMarker(term.buffers.active.y)); - term.writeSync('1\r\n'); + await term.writeP('1\r\n'); markers.push(term.buffers.active.addMarker(term.buffers.active.y)); - term.writeSync('2\r\n'); + await term.writeP('2\r\n'); markers.push(term.buffers.active.addMarker(term.buffers.active.y)); - term.writeSync('3\r\n'); + await term.writeP('3\r\n'); markers.push(term.buffers.active.addMarker(term.buffers.active.y)); - term.writeSync('4'); + await term.writeP('4'); for (let i = 0; i < markers.length; ++i) { const marker = markers[i]; marker.onDispose(() => disposeStack.push(marker)); @@ -1566,15 +1470,15 @@ describe('Terminal', () => { it('initial', () => { assert.deepEqual(markers.map(m => m.line), [0, 1, 2, 3, 4]); }); - it('should dispose on normal trim off the top', () => { + it('should dispose on normal trim off the top', async () => { // moves top line into scrollback - term.writeSync('\n'); + await term.writeP('\n'); assert.deepEqual(disposeStack, []); // trims first marker - term.writeSync('\n'); + await term.writeP('\n'); assert.deepEqual(disposeStack, [markers[0]]); // trims second marker - term.writeSync('\n'); + await term.writeP('\n'); assert.deepEqual(disposeStack, [markers[0], markers[1]]); // trimmed marker objs should be disposed assert.deepEqual(disposeStack.map(el => el.isDisposed), [true, true]); @@ -1582,14 +1486,14 @@ describe('Terminal', () => { // trimmed markers should contain line -1 assert.deepEqual(disposeStack.map(el => el.line), [-1, -1]); }); - it('should dispose on DL', () => { - term.writeSync('\x1b[3;1H'); // move cursor to 0, 2 - term.writeSync('\x1b[2M'); // delete 2 lines + it('should dispose on DL', async () => { + await term.writeP('\x1b[3;1H'); // move cursor to 0, 2 + await term.writeP('\x1b[2M'); // delete 2 lines assert.deepEqual(disposeStack, [markers[2], markers[3]]); }); - it('should dispose on IL', () => { - term.writeSync('\x1b[3;1H'); // move cursor to 0, 2 - term.writeSync('\x1b[2L'); // insert 2 lines + it('should dispose on IL', async () => { + await term.writeP('\x1b[3;1H'); // move cursor to 0, 2 + await term.writeP('\x1b[2L'); // insert 2 lines assert.deepEqual(disposeStack, [markers[4], markers[3]]); assert.deepEqual(markers.map(el => el.line), [0, 1, 4, -1, -1]); }); diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index f8b8b3e4..5aed701a 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -21,8 +21,8 @@ * http://linux.die.net/man/7/urxvt */ -import { ICompositionHelper, ITerminal, IBrowser, CustomKeyEventHandler, ILinkifier, IMouseZoneManager, LinkMatcherHandler, ILinkMatcherOptions, IViewport, ILinkifier2 } from 'browser/Types'; -import { IRenderer, CharacterJoinerHandler } from 'browser/renderer/Types'; +import { ICompositionHelper, ITerminal, IBrowser, CustomKeyEventHandler, ILinkifier, IMouseZoneManager, LinkMatcherHandler, ILinkMatcherOptions, IViewport, ILinkifier2, CharacterJoinerHandler } from 'browser/Types'; +import { IRenderer } from 'browser/renderer/Types'; import { CompositionHelper } from 'browser/input/CompositionHelper'; import { Viewport } from 'browser/Viewport'; import { rightClickHandler, moveTextAreaUnderMouseCursor, handlePasteEvent, copyHandler, paste } from 'browser/Clipboard'; @@ -39,13 +39,13 @@ import { MouseZoneManager } from 'browser/MouseZoneManager'; import { AccessibilityManager } from './AccessibilityManager'; import { ITheme, IMarker, IDisposable, ISelectionPosition, ILinkProvider } from 'xterm'; import { DomRenderer } from 'browser/renderer/dom/DomRenderer'; -import { IKeyboardEvent, KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions } from 'common/Types'; +import { IKeyboardEvent, KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, ScrollSource, IAnsiColorChangeEvent } from 'common/Types'; import { evaluateKeyboardEvent } from 'common/input/Keyboard'; import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { ColorManager } from 'browser/ColorManager'; import { RenderService } from 'browser/services/RenderService'; -import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService, ICoreBrowserService } from 'browser/services/Services'; +import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService, ICoreBrowserService, ICharacterJoinerService } from 'browser/services/Services'; import { CharSizeService } from 'browser/services/CharSizeService'; import { IBuffer } from 'common/buffer/Types'; import { MouseService } from 'browser/services/MouseService'; @@ -53,6 +53,8 @@ import { Linkifier2 } from 'browser/Linkifier2'; import { CoreBrowserService } from 'browser/services/CoreBrowserService'; import { CoreTerminal } from 'common/CoreTerminal'; import { ITerminalOptions as IInitializedTerminalOptions } from 'common/services/Services'; +import { rgba } from 'browser/Color'; +import { CharacterJoinerService } from 'browser/services/CharacterJoinerService'; // Let it work inside Node.js for automated testing purposes. const document: Document = (typeof window !== 'undefined') ? window.document : null as any; @@ -70,7 +72,7 @@ export class Terminal extends CoreTerminal implements ITerminal { // private _visualBellTimer: number; - public browser: IBrowser = Browser; + public browser: IBrowser = Browser as any; // TODO: We should remove options once components adopt optionsService public get options(): IInitializedTerminalOptions { return this.optionsService.options; } @@ -81,6 +83,7 @@ export class Terminal extends CoreTerminal implements ITerminal { private _charSizeService: ICharSizeService | undefined; private _mouseService: IMouseService | undefined; private _renderService: IRenderService | undefined; + private _characterJoinerService: ICharacterJoinerService | undefined; private _selectionService: ISelectionService | undefined; private _soundService: ISoundService | undefined; @@ -110,6 +113,8 @@ export class Terminal extends CoreTerminal implements ITerminal { public get onSelectionChange(): IEvent { return this._onSelectionChange.event; } private _onTitleChange = new EventEmitter(); public get onTitleChange(): IEvent { return this._onTitleChange.event; } + private _onBell = new EventEmitter(); + public get onBell (): IEvent { return this._onBell.event; } private _onFocus = new EventEmitter(); public get onFocus(): IEvent { return this._onFocus.event; } @@ -146,8 +151,8 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(this._inputHandler.onRequestBell(() => this.bell())); this.register(this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end))); this.register(this._inputHandler.onRequestReset(() => this.reset())); - this.register(this._inputHandler.onRequestScroll((eraseAttr, isWrapped) => this.scroll(eraseAttr, isWrapped || undefined))); this.register(this._inputHandler.onRequestWindowsOptionsReport(type => this._reportWindowsOptions(type))); + this.register(this._inputHandler.onAnsiColorChange((event) => this._changeAnsiColor(event))); this.register(forwardEvent(this._inputHandler.onCursorMove, this._onCursorMove)); this.register(forwardEvent(this._inputHandler.onTitleChange, this._onTitleChange)); this.register(forwardEvent(this._inputHandler.onA11yChar, this._onA11yCharEmitter)); @@ -157,6 +162,19 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(this._bufferService.onResize(e => this._afterResize(e.cols, e.rows))); } + private _changeAnsiColor(event: IAnsiColorChangeEvent): void { + if (!this._colorManager) { return; } + + for (const ansiColor of event.colors) { + const color = rgba.toColor(ansiColor.red, ansiColor.green, ansiColor.blue); + + this._colorManager!.colors.ansi[ansiColor.colorIndex] = color; + } + + this._renderService?.setColors(this._colorManager!.colors); + this.viewport?.onThemeChange(this._colorManager!.colors); + } + public dispose(): void { if (this._isDisposed) { return; @@ -282,19 +300,26 @@ export class Terminal extends CoreTerminal implements ITerminal { } private _syncTextArea(): void { - if (!this.textarea || !this.buffer.isCursorInViewport || this._compositionHelper!.isComposing) { + if (!this.textarea || !this.buffer.isCursorInViewport || this._compositionHelper!.isComposing || !this._renderService) { return; } - - const cellHeight = Math.ceil(this._charSizeService!.height * this.optionsService.options.lineHeight); - const cursorTop = this._bufferService.buffer.y * cellHeight; - const cursorLeft = this._bufferService.buffer.x * this._charSizeService!.width; + const cursorY = this.buffer.ybase + this.buffer.y; + const bufferLine = this.buffer.lines.get(cursorY); + if (!bufferLine) { + return; + } + const cursorX = Math.min(this.buffer.x, this.cols - 1); + const cellHeight = this._renderService.dimensions.actualCellHeight; + const width = bufferLine.getWidth(cursorX); + const cellWidth = this._renderService.dimensions.actualCellWidth * width; + const cursorTop = this.buffer.y * this._renderService.dimensions.actualCellHeight; + const cursorLeft = cursorX * this._renderService.dimensions.actualCellWidth; // Sync the textarea to the exact position of the composition view so the IME knows where the // text is. this.textarea.style.left = cursorLeft + 'px'; this.textarea.style.top = cursorTop + 'px'; - this.textarea.style.width = this._charSizeService!.width + 'px'; + this.textarea.style.width = cellWidth + 'px'; this.textarea.style.height = cellHeight + 'px'; this.textarea.style.lineHeight = cellHeight + 'px'; this.textarea.style.zIndex = '-5'; @@ -371,7 +396,7 @@ export class Terminal extends CoreTerminal implements ITerminal { throw new Error('Terminal requires a parent element.'); } - if (!document.body.contains(parent)) { + if (!parent.isConnected) { this._logService.debug('Terminal.open was called on an element that was not attached to the DOM'); } @@ -423,6 +448,20 @@ export class Terminal extends CoreTerminal implements ITerminal { this._charSizeService = this._instantiationService.createInstance(CharSizeService, this._document, this._helperContainer); this._instantiationService.setService(ICharSizeService, this._charSizeService); + this._theme = this.options.theme || this._theme; + this._colorManager = new ColorManager(document, this.options.allowTransparency); + this.register(this.optionsService.onOptionChange(e => this._colorManager!.onOptionsChange(e))); + this._colorManager.setTheme(this._theme); + + this._characterJoinerService = this._instantiationService.createInstance(CharacterJoinerService); + this._instantiationService.setService(ICharacterJoinerService, this._characterJoinerService); + + const renderer = this._createRenderer(); + this._renderService = this.register(this._instantiationService.createInstance(RenderService, renderer, this.rows, this.screenElement)); + this._instantiationService.setService(IRenderService, this._renderService); + this.register(this._renderService.onRenderedBufferChange(e => this._onRender.fire(e))); + this.onResize(e => this._renderService!.resize(e.cols, e.rows)); + this._compositionView = document.createElement('div'); this._compositionView.classList.add('composition-view'); this._compositionHelper = this._instantiationService.createInstance(CompositionHelper, this.textarea, this._compositionView); @@ -431,24 +470,13 @@ export class Terminal extends CoreTerminal implements ITerminal { // Performance: Add viewport and helper elements from the fragment this.element.appendChild(fragment); - this._theme = this.options.theme || this._theme; - this._colorManager = new ColorManager(document, this.options.allowTransparency); - this.register(this.optionsService.onOptionChange(e => this._colorManager!.onOptionsChange(e))); - this._colorManager.setTheme(this._theme); - - const renderer = this._createRenderer(); - this._renderService = this.register(this._instantiationService.createInstance(RenderService, renderer, this.rows, this.screenElement)); - this._instantiationService.setService(IRenderService, this._renderService); - this.register(this._renderService.onRenderedBufferChange(e => this._onRender.fire(e))); - this.onResize(e => this._renderService!.resize(e.cols, e.rows)); - this._soundService = this._instantiationService.createInstance(SoundService); this._instantiationService.setService(ISoundService, this._soundService); this._mouseService = this._instantiationService.createInstance(MouseService); this._instantiationService.setService(IMouseService, this._mouseService); this.viewport = this._instantiationService.createInstance(Viewport, - (amount: number, suppressEvent: boolean) => this.scrollLines(amount, suppressEvent), + (amount: number) => this.scrollLines(amount, true, ScrollSource.VIEWPORT), this._viewportElement, this._viewportScrollArea ); @@ -467,7 +495,9 @@ export class Terminal extends CoreTerminal implements ITerminal { this._selectionService = this.register(this._instantiationService.createInstance(SelectionService, this.element, - this.screenElement)); + this.screenElement, + this.linkifier2 + )); this._instantiationService.setService(ISelectionService, this._selectionService); this.register(this._selectionService.onRequestScrollLines(e => this.scrollLines(e.amount, e.suppressScrollEvent))); this.register(this._selectionService.onSelectionChange(() => this._onSelectionChange.fire())); @@ -480,7 +510,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.textarea!.focus(); this.textarea!.select(); })); - this.register(this.onScroll(() => { + this.register(this._onScroll.event(ev => { this.viewport!.syncScrollArea(); this._selectionService!.refresh(); })); @@ -571,7 +601,7 @@ export class Terminal extends CoreTerminal implements ITerminal { let but: CoreMouseButton; let action: CoreMouseAction | undefined; - switch ((ev).overrideType || ev.type) { + switch ((ev as any).overrideType || ev.type) { case 'mousemove': action = CoreMouseAction.MOVE; if (ev.buttons === undefined) { @@ -819,7 +849,7 @@ export class Terminal extends CoreTerminal implements ITerminal { * Change the cursor style for different selection modes */ public updateCursorStyle(ev: KeyboardEvent): void { - if (this._selectionService && this._selectionService.shouldColumnSelect(ev)) { + if (this._selectionService?.shouldColumnSelect(ev)) { this.element!.classList.add('column-select'); } else { this.element!.classList.remove('column-select'); @@ -836,8 +866,8 @@ export class Terminal extends CoreTerminal implements ITerminal { } } - public scrollLines(disp: number, suppressScrollEvent?: boolean): void { - super.scrollLines(disp, suppressScrollEvent); + public scrollLines(disp: number, suppressScrollEvent?: boolean, source = ScrollSource.TERMINAL): void { + super.scrollLines(disp, suppressScrollEvent, source); this.refresh(0, this.rows - 1); } @@ -889,13 +919,19 @@ export class Terminal extends CoreTerminal implements ITerminal { } public registerCharacterJoiner(handler: CharacterJoinerHandler): number { - const joinerId = this._renderService!.registerCharacterJoiner(handler); + if (!this._characterJoinerService) { + throw new Error('Terminal must be opened first'); + } + const joinerId = this._characterJoinerService.register(handler); this.refresh(0, this.rows - 1); return joinerId; } public deregisterCharacterJoiner(joinerId: number): void { - if (this._renderService!.deregisterCharacterJoiner(joinerId)) { + if (!this._characterJoinerService) { + throw new Error('Terminal must be opened first'); + } + if (this._characterJoinerService.deregister(joinerId)) { this.refresh(0, this.rows - 1); } } @@ -984,7 +1020,7 @@ export class Terminal extends CoreTerminal implements ITerminal { if (!this._compositionHelper!.keydown(event)) { if (this.buffer.ybase !== this.buffer.ydisp) { - this.scrollToBottom(); + this._bufferService.scrollToBottom(); } return false; } @@ -1113,9 +1149,11 @@ export class Terminal extends CoreTerminal implements ITerminal { */ public bell(): void { if (this._soundBell()) { - this._soundService!.playBellSound(); + this._soundService?.playBellSound(); } + this._onBell.fire(); + // if (this._visualBell()) { // this.element.classList.add('visual-bell-active'); // clearTimeout(this._visualBellTimer); @@ -1168,7 +1206,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.buffer.lines.push(this.buffer.getBlankLine(DEFAULT_ATTR_DATA)); } this.refresh(0, this.rows - 1); - this._onScroll.fire(this.buffer.ydisp); + this._onScroll.fire({ position: this.buffer.ydisp, source: ScrollSource.TERMINAL }); } /** diff --git a/src/browser/Terminal2.test.ts b/src/browser/Terminal2.test.ts index 7d3fe851..4965e8f3 100644 --- a/src/browser/Terminal2.test.ts +++ b/src/browser/Terminal2.test.ts @@ -17,6 +17,7 @@ const ROWS = 25; const TESTFILES = glob.sync('**/escape_sequence_files/*.in', { cwd: path.join(__dirname, '../..')}); const SKIP_FILES = [ + 't0055-EL.in', // EL/ED handle cursor at cols differently (see #3362) 't0084-CBT.in', 't0101-NLM.in', 't0103-reverse_wrap.in', // not comparable, we deviate from xterm reverse wrap on purpose @@ -73,8 +74,8 @@ describe('Escape Sequence Files', function(): void { // register handler to trigger viewport scraping, wait for it to finish let content = ''; const OSC_CODE = 12345; - await new Promise(resolve => { - customHandler = term.addOscHandler(OSC_CODE, () => { + await new Promise(resolve => { + customHandler = term.registerOscHandler(OSC_CODE, () => { // grab terminal viewport content content = terminalToString(term); resolve(); @@ -105,7 +106,7 @@ function formatError(input: string, output: string, expected: string): string { function addLineNumber(start: number, color: string): (s: string) => string { let counter = start || 0; return (s: string): string => { - counter += 1; + counter++; return '\x1b[33m' + (' ' + counter).slice(-2) + color + s; }; } diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index 6a4b4a0d..9d5373c8 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -5,9 +5,9 @@ import { IDisposable, IMarker, ISelectionPosition, ILinkProvider } from 'xterm'; import { IEvent, EventEmitter } from 'common/EventEmitter'; -import { ICharSizeService, IMouseService, IRenderService, ISelectionService } from 'browser/services/Services'; -import { IRenderDimensions, IRenderer, CharacterJoinerHandler, IRequestRedrawEvent } from 'browser/renderer/Types'; -import { IColorSet, ILinkMatcherOptions, ITerminal, ILinkifier, ILinkifier2, IBrowser, IViewport, IColorManager, ICompositionHelper } from 'browser/Types'; +import { ICharacterJoinerService, ICharSizeService, IMouseService, IRenderService, ISelectionService } from 'browser/services/Services'; +import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types'; +import { IColorSet, ILinkMatcherOptions, ITerminal, ILinkifier, ILinkifier2, IBrowser, IViewport, IColorManager, ICompositionHelper, CharacterJoinerHandler } from 'browser/Types'; import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types'; import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset, ITerminalOptions } from 'common/Types'; import { Buffer } from 'common/buffer/Buffer'; @@ -21,6 +21,9 @@ export class TestTerminal extends Terminal { public get curAttrData(): IAttributeData { return (this as any)._inputHandler._curAttrData; } public keyDown(ev: any): boolean | undefined { return this._keyDown(ev); } public keyPress(ev: any): boolean { return this._keyPress(ev); } + public writeP(data: string | Uint8Array): Promise { + return new Promise(r => this.write(data, r)); + } } export class MockTerminal implements ITerminal { @@ -34,6 +37,7 @@ export class MockTerminal implements ITerminal { public onData!: IEvent; public onBinary!: IEvent; public onTitleChange!: IEvent; + public onBell!: IEvent; public onScroll!: IEvent; public onKey!: IEvent<{ key: string, domEvent: KeyboardEvent }>; public onRender!: IEvent<{ start: number, end: number }>; @@ -75,16 +79,16 @@ export class MockTerminal implements ITerminal { public attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void { throw new Error('Method not implemented.'); } - public addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable { + public registerCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean | Promise): IDisposable { throw new Error('Method not implemented.'); } - public addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean): IDisposable { + public registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean | Promise): IDisposable { throw new Error('Method not implemented.'); } - public addEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable { + public registerEscHandler(id: IFunctionIdentifier, handler: () => boolean | Promise): IDisposable { throw new Error('Method not implemented.'); } - public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable { + public registerOscHandler(ident: number, callback: (data: string) => boolean | Promise): IDisposable { throw new Error('Method not implemented.'); } public registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => boolean | void, options?: ILinkMatcherOptions): number { @@ -149,7 +153,7 @@ export class MockTerminal implements ITerminal { public textarea!: HTMLTextAreaElement; public rows!: number; public cols!: number; - public browser: IBrowser = Browser; + public browser: IBrowser = Browser as any; public writeBuffer!: string[]; public children!: HTMLElement[]; public cursorHidden!: boolean; @@ -281,8 +285,6 @@ export class MockRenderer implements IRenderer { public onDevicePixelRatioChange(): void { } public clear(): void { } public renderRows(start: number, end: number): void { } - public registerCharacterJoiner(handler: CharacterJoinerHandler): number { return 0; } - public deregisterCharacterJoiner(): boolean { return true; } } export class MockViewport implements IViewport { @@ -406,13 +408,20 @@ export class MockRenderService implements IRenderService { public clear(): void { throw new Error('Method not implemented.'); } - public registerCharacterJoiner(handler: CharacterJoinerHandler): number { - throw new Error('Method not implemented.'); - } - public deregisterCharacterJoiner(joinerId: number): boolean { - throw new Error('Method not implemented.'); - } public dispose(): void { throw new Error('Method not implemented.'); } } + +export class MockCharacterJoinerService implements ICharacterJoinerService { + public serviceBrand: undefined; + public register(handler: (text: string) => [number, number][]): number { + return 0; + } + public deregister(joinerId: number): boolean { + return true; + } + public getJoinedCharacters(row: number): [number, number][] { + return []; + } +} diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index e8e0cabd..c268c7bf 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -15,7 +15,6 @@ export interface ITerminal extends IPublicTerminal, ICoreTerminal { screenElement: HTMLElement | undefined; browser: IBrowser; buffer: IBuffer; - buffers: IBufferSet; viewport: IViewport | undefined; // TODO: We should remove options once components adopt optionsService options: ITerminalOptions; @@ -47,15 +46,16 @@ export interface IPublicTerminal extends IDisposable { onRender: IEvent<{ start: number, end: number }>; onResize: IEvent<{ cols: number, rows: number }>; onTitleChange: IEvent; + onBell: IEvent; blur(): void; focus(): void; resize(columns: number, rows: number): void; open(parent: HTMLElement): void; attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void; - addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable; - addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean): IDisposable; - addEscHandler(id: IFunctionIdentifier, callback: () => boolean): IDisposable; - addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable; + registerCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean | Promise): IDisposable; + registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean | Promise): IDisposable; + registerEscHandler(id: IFunctionIdentifier, callback: () => boolean | Promise): IDisposable; + registerOscHandler(ident: number, callback: (data: string) => boolean | Promise): IDisposable; registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): number; deregisterLinkMatcher(matcherId: number): void; registerLinkProvider(linkProvider: ILinkProvider): IDisposable; @@ -205,9 +205,19 @@ export interface ILinkifier { deregisterLinkMatcher(matcherId: number): boolean; } +interface ILinkState { + decorations: ILinkDecorations; + isHovered: boolean; +} +export interface ILinkWithState { + link: ILink; + state?: ILinkState; +} + export interface ILinkifier2 { onShowLinkUnderline: IEvent; onHideLinkUnderline: IEvent; + readonly currentLink: ILinkWithState | undefined; attachToDom(element: HTMLElement, mouseService: IMouseService, renderService: IRenderService): void; registerLinkProvider(linkProvider: ILinkProvider): IDisposable; @@ -292,3 +302,10 @@ interface IBufferCellPosition { x: number; y: number; } + +export type CharacterJoinerHandler = (text: string) => [number, number][]; + +export interface ICharacterJoiner { + id: number; + handler: CharacterJoinerHandler; +} diff --git a/src/browser/Viewport.ts b/src/browser/Viewport.ts index 29edce6f..162ed174 100644 --- a/src/browser/Viewport.ts +++ b/src/browser/Viewport.ts @@ -33,7 +33,7 @@ export class Viewport extends Disposable implements IViewport { private _ignoreNextScrollEvent: boolean = false; constructor( - private readonly _scrollLines: (amount: number, suppressEvent: boolean) => void, + private readonly _scrollLines: (amount: number) => void, private readonly _viewportElement: HTMLElement, private readonly _scrollArea: HTMLElement, @IBufferService private readonly _bufferService: IBufferService, @@ -151,12 +151,14 @@ export class Viewport extends Disposable implements IViewport { // Ignore the event if it was flagged to ignore (when the source of the event is from Viewport) if (this._ignoreNextScrollEvent) { this._ignoreNextScrollEvent = false; + // Still trigger the scroll so lines get refreshed + this._scrollLines(0); return; } const newRow = Math.round(this._lastScrollTop / this._currentRowHeight); const diff = newRow - this._bufferService.buffer.ydisp; - this._scrollLines(diff, true); + this._scrollLines(diff); } /** diff --git a/src/browser/input/CompositionHelper.test.ts b/src/browser/input/CompositionHelper.test.ts index b9a4f668..e29111e1 100644 --- a/src/browser/input/CompositionHelper.test.ts +++ b/src/browser/input/CompositionHelper.test.ts @@ -5,7 +5,7 @@ import { assert } from 'chai'; import { CompositionHelper } from 'browser/input/CompositionHelper'; -import { MockCharSizeService } from 'browser/TestUtils.test'; +import { MockRenderService } from 'browser/TestUtils.test'; import { MockCoreService, MockBufferService, MockOptionsService } from 'common/TestUtils.test'; describe('CompositionHelper', () => { @@ -42,14 +42,14 @@ describe('CompositionHelper', () => { }; handledText = ''; const bufferService = new MockBufferService(10, 5); - compositionHelper = new CompositionHelper(textarea, compositionView, bufferService, new MockOptionsService(), new MockCharSizeService(10, 10), coreService); + compositionHelper = new CompositionHelper(textarea, compositionView, bufferService, new MockOptionsService(), coreService, new MockRenderService()); }); describe('Input', () => { it('Should insert simple characters', (done) => { // First character 'ㅇ' compositionHelper.compositionstart(); - compositionHelper.compositionupdate({ data: 'ㅇ' }); + compositionHelper.compositionupdate({ data: 'ㅇ' }); textarea.value = 'ㅇ'; setTimeout(() => { // wait for any textarea updates compositionHelper.compositionend(); @@ -57,7 +57,7 @@ describe('CompositionHelper', () => { assert.equal(handledText, 'ㅇ'); // Second character 'ㅇ' compositionHelper.compositionstart(); - compositionHelper.compositionupdate({ data: 'ㅇ' }); + compositionHelper.compositionupdate({ data: 'ㅇ' }); textarea.value = 'ㅇㅇ'; setTimeout(() => { // wait for any textarea updates compositionHelper.compositionend(); @@ -73,13 +73,13 @@ describe('CompositionHelper', () => { it('Should insert complex characters', (done) => { // First character '앙' compositionHelper.compositionstart(); - compositionHelper.compositionupdate({ data: 'ㅇ' }); + compositionHelper.compositionupdate({ data: 'ㅇ' }); textarea.value = 'ㅇ'; setTimeout(() => { // wait for any textarea updates - compositionHelper.compositionupdate({ data: '아' }); + compositionHelper.compositionupdate({ data: '아' }); textarea.value = '아'; setTimeout(() => { // wait for any textarea updates - compositionHelper.compositionupdate({ data: '앙' }); + compositionHelper.compositionupdate({ data: '앙' }); textarea.value = '앙'; setTimeout(() => { // wait for any textarea updates compositionHelper.compositionend(); @@ -87,13 +87,13 @@ describe('CompositionHelper', () => { assert.equal(handledText, '앙'); // Second character '앙' compositionHelper.compositionstart(); - compositionHelper.compositionupdate({ data: 'ㅇ' }); + compositionHelper.compositionupdate({ data: 'ㅇ' }); textarea.value = '앙ㅇ'; setTimeout(() => { // wait for any textarea updates - compositionHelper.compositionupdate({ data: '아' }); + compositionHelper.compositionupdate({ data: '아' }); textarea.value = '앙아'; setTimeout(() => { // wait for any textarea updates - compositionHelper.compositionupdate({ data: '앙' }); + compositionHelper.compositionupdate({ data: '앙' }); textarea.value = '앙앙'; setTimeout(() => { // wait for any textarea updates compositionHelper.compositionend(); @@ -113,19 +113,19 @@ describe('CompositionHelper', () => { it('Should insert complex characters that change with following character', (done) => { // First character '아' compositionHelper.compositionstart(); - compositionHelper.compositionupdate({ data: 'ㅇ' }); + compositionHelper.compositionupdate({ data: 'ㅇ' }); textarea.value = 'ㅇ'; setTimeout(() => { // wait for any textarea updates - compositionHelper.compositionupdate({ data: '아' }); + compositionHelper.compositionupdate({ data: '아' }); textarea.value = '아'; setTimeout(() => { // wait for any textarea updates // Start second character '아' in first character - compositionHelper.compositionupdate({ data: '앙' }); + compositionHelper.compositionupdate({ data: '앙' }); textarea.value = '앙'; setTimeout(() => { // wait for any textarea updates compositionHelper.compositionend(); compositionHelper.compositionstart(); - compositionHelper.compositionupdate({ data: '아' }); + compositionHelper.compositionupdate({ data: '아' }); textarea.value = '아아'; setTimeout(() => { // wait for any textarea updates compositionHelper.compositionend(); @@ -142,14 +142,14 @@ describe('CompositionHelper', () => { it('Should insert multi-characters compositions', (done) => { // First character 'だ' compositionHelper.compositionstart(); - compositionHelper.compositionupdate({ data: 'd' }); + compositionHelper.compositionupdate({ data: 'd' }); textarea.value = 'd'; setTimeout(() => { // wait for any textarea updates - compositionHelper.compositionupdate({ data: 'だ' }); + compositionHelper.compositionupdate({ data: 'だ' }); textarea.value = 'だ'; setTimeout(() => { // wait for any textarea updates // Second character 'あ' - compositionHelper.compositionupdate({ data: 'だあ' }); + compositionHelper.compositionupdate({ data: 'だあ' }); textarea.value = 'だあ'; setTimeout(() => { // wait for any textarea updates compositionHelper.compositionend(); @@ -165,18 +165,18 @@ describe('CompositionHelper', () => { it('Should insert multi-character compositions that are converted to other characters with the same length', (done) => { // First character 'だ' compositionHelper.compositionstart(); - compositionHelper.compositionupdate({ data: 'd' }); + compositionHelper.compositionupdate({ data: 'd' }); textarea.value = 'd'; setTimeout(() => { // wait for any textarea updates - compositionHelper.compositionupdate({ data: 'だ' }); + compositionHelper.compositionupdate({ data: 'だ' }); textarea.value = 'だ'; setTimeout(() => { // wait for any textarea updates // Second character 'ー' - compositionHelper.compositionupdate({ data: 'だー' }); + compositionHelper.compositionupdate({ data: 'だー' }); textarea.value = 'だー'; setTimeout(() => { // wait for any textarea updates // Convert to katakana 'ダー' - compositionHelper.compositionupdate({ data: 'ダー' }); + compositionHelper.compositionupdate({ data: 'ダー' }); textarea.value = 'ダー'; setTimeout(() => { // wait for any textarea updates compositionHelper.compositionend(); @@ -193,18 +193,18 @@ describe('CompositionHelper', () => { it('Should insert multi-character compositions that are converted to other characters with different lengths', (done) => { // First character 'い' compositionHelper.compositionstart(); - compositionHelper.compositionupdate({ data: 'い' }); + compositionHelper.compositionupdate({ data: 'い' }); textarea.value = 'い'; setTimeout(() => { // wait for any textarea updates // Second character 'ま' - compositionHelper.compositionupdate({ data: 'いm' }); + compositionHelper.compositionupdate({ data: 'いm' }); textarea.value = 'いm'; setTimeout(() => { // wait for any textarea updates - compositionHelper.compositionupdate({ data: 'いま' }); + compositionHelper.compositionupdate({ data: 'いま' }); textarea.value = 'いま'; setTimeout(() => { // wait for any textarea updates // Convert to kanji '今' - compositionHelper.compositionupdate({ data: '今' }); + compositionHelper.compositionupdate({ data: '今' }); textarea.value = '今'; setTimeout(() => { // wait for any textarea updates compositionHelper.compositionend(); @@ -221,7 +221,7 @@ describe('CompositionHelper', () => { it('Should insert non-composition characters input immediately after composition characters', (done) => { // First character 'ㅇ' compositionHelper.compositionstart(); - compositionHelper.compositionupdate({ data: 'ㅇ' }); + compositionHelper.compositionupdate({ data: 'ㅇ' }); textarea.value = 'ㅇ'; setTimeout(() => { // wait for any textarea updates compositionHelper.compositionend(); diff --git a/src/browser/input/CompositionHelper.ts b/src/browser/input/CompositionHelper.ts index 8d393e33..4e176725 100644 --- a/src/browser/input/CompositionHelper.ts +++ b/src/browser/input/CompositionHelper.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { ICharSizeService } from 'browser/services/Services'; +import { IRenderService } from 'browser/services/Services'; import { IBufferService, ICoreService, IOptionsService } from 'common/services/Services'; interface IPosition { @@ -35,17 +35,23 @@ export class CompositionHelper { */ private _isSendingComposition: boolean; + /** + * Data already sent due to keydown event. + */ + private _dataAlreadySent: string; + constructor( private readonly _textarea: HTMLTextAreaElement, private readonly _compositionView: HTMLElement, @IBufferService private readonly _bufferService: IBufferService, @IOptionsService private readonly _optionsService: IOptionsService, - @ICharSizeService private readonly _charSizeService: ICharSizeService, - @ICoreService private readonly _coreService: ICoreService + @ICoreService private readonly _coreService: ICoreService, + @IRenderService private readonly _renderService: IRenderService ) { this._isComposing = false; this._isSendingComposition = false; this._compositionPosition = { start: 0, end: 0 }; + this._dataAlreadySent = ''; } /** @@ -55,6 +61,7 @@ export class CompositionHelper { this._isComposing = true; this._compositionPosition.start = this._textarea.value.length; this._compositionView.textContent = ''; + this._dataAlreadySent = ''; this._compositionView.classList.add('active'); } @@ -62,7 +69,7 @@ export class CompositionHelper { * Handles the compositionupdate event, updating the composition view. * @param ev The event. */ - public compositionupdate(ev: CompositionEvent): void { + public compositionupdate(ev: Pick): void { this._compositionView.textContent = ev.data; this.updateCompositionElements(); setTimeout(() => { @@ -147,6 +154,9 @@ export class CompositionHelper { if (this._isSendingComposition) { this._isSendingComposition = false; let input; + // Add length of data already sent due to keydown event, + // otherwise input characters can be duplicated. (Issue #3191) + currentCompositionPosition.start += this._dataAlreadySent.length; if (this._isComposing) { // Use the end position to get the string if a new composition has started. input = this._textarea.value.substring(currentCompositionPosition.start, currentCompositionPosition.end); @@ -156,7 +166,9 @@ export class CompositionHelper { // (eg. 2) after a composition character. input = this._textarea.value.substring(currentCompositionPosition.start); } - this._coreService.triggerDataEvent(input, true); + if (input.length > 0) { + this._coreService.triggerDataEvent(input, true); + } } }, 0); } @@ -176,6 +188,7 @@ export class CompositionHelper { const newValue = this._textarea.value; const diff = newValue.replace(oldValue, ''); if (diff.length > 0) { + this._dataAlreadySent = diff; this._coreService.triggerDataEvent(diff, true); } } @@ -194,9 +207,11 @@ export class CompositionHelper { } if (this._bufferService.buffer.isCursorInViewport) { - const cellHeight = Math.ceil(this._charSizeService.height * this._optionsService.options.lineHeight); - const cursorTop = this._bufferService.buffer.y * cellHeight; - const cursorLeft = this._bufferService.buffer.x * this._charSizeService.width; + const cursorX = Math.min(this._bufferService.buffer.x, this._bufferService.cols - 1); + + const cellHeight = this._renderService.dimensions.actualCellHeight; + const cursorTop = this._bufferService.buffer.y * this._renderService.dimensions.actualCellHeight; + const cursorLeft = cursorX * this._renderService.dimensions.actualCellWidth; this._compositionView.style.left = cursorLeft + 'px'; this._compositionView.style.top = cursorTop + 'px'; @@ -209,8 +224,9 @@ export class CompositionHelper { const compositionViewBounds = this._compositionView.getBoundingClientRect(); this._textarea.style.left = cursorLeft + 'px'; this._textarea.style.top = cursorTop + 'px'; - this._textarea.style.width = compositionViewBounds.width + 'px'; - this._textarea.style.height = compositionViewBounds.height + 'px'; + // Ensure the text area is at least 1x1, otherwise certain IMEs may break + this._textarea.style.width = Math.max(compositionViewBounds.width, 1) + 'px'; + this._textarea.style.height = Math.max(compositionViewBounds.height, 1) + 'px'; this._textarea.style.lineHeight = compositionViewBounds.height + 'px'; } diff --git a/src/browser/public/AddonManager.ts b/src/browser/public/AddonManager.ts index 0261fd68..06c78121 100644 --- a/src/browser/public/AddonManager.ts +++ b/src/browser/public/AddonManager.ts @@ -31,7 +31,7 @@ export class AddonManager implements IDisposable { }; this._addons.push(loadedAddon); instance.dispose = () => this._wrappedAddonDispose(loadedAddon); - instance.activate(terminal); + instance.activate(terminal as any); } private _wrappedAddonDispose(loadedAddon: ILoadedAddon): void { diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index 70247f88..adfee544 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -3,17 +3,15 @@ * @license MIT */ -import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferNamespace as IBufferNamespaceApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi, IParser, IFunctionIdentifier, ILinkProvider, IUnicodeHandling, IUnicodeVersionProvider, FontWeight } from 'xterm'; +import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBufferNamespace as IBufferNamespaceApi, IParser, ILinkProvider, IUnicodeHandling, FontWeight } from 'xterm'; import { ITerminal } from 'browser/Types'; -import { IBufferLine, ICellData } from 'common/Types'; -import { IBuffer, IBufferSet } from 'common/buffer/Types'; -import { CellData } from 'common/buffer/CellData'; import { Terminal as TerminalCore } from '../Terminal'; import * as Strings from '../LocalizableStrings'; -import { IEvent, EventEmitter } from 'common/EventEmitter'; +import { IEvent } from 'common/EventEmitter'; +import { ParserApi } from 'common/public/ParserApi'; +import { UnicodeApi } from 'common/public/UnicodeApi'; import { AddonManager } from './AddonManager'; -import { IParams } from 'common/parser/Types'; -import { BufferSet } from 'common/buffer/BufferSet'; +import { BufferNamespaceApi } from 'common/public/BufferNamespaceApi'; export class Terminal implements ITerminalApi { private _core: ITerminal; @@ -38,6 +36,7 @@ export class Terminal implements ITerminalApi { public get onData(): IEvent { return this._core.onData; } public get onBinary(): IEvent { return this._core.onBinary; } public get onTitleChange(): IEvent { return this._core.onTitleChange; } + public get onBell(): IEvent { return this._core.onBell; } public get onScroll(): IEvent { return this._core.onScroll; } public get onKey(): IEvent<{ key: string, domEvent: KeyboardEvent }> { return this._core.onKey; } public get onRender(): IEvent<{ start: number, end: number }> { return this._core.onRender; } @@ -217,123 +216,3 @@ export class Terminal implements ITerminalApi { } } } - -class BufferApiView implements IBufferApi { - constructor( - private _buffer: IBuffer, - public readonly type: 'normal' | 'alternate' - ) { } - - public init(buffer: IBuffer): BufferApiView { - this._buffer = buffer; - return this; - } - - public get cursorY(): number { return this._buffer.y; } - public get cursorX(): number { return this._buffer.x; } - public get viewportY(): number { return this._buffer.ydisp; } - public get baseY(): number { return this._buffer.ybase; } - public get length(): number { return this._buffer.lines.length; } - public getLine(y: number): IBufferLineApi | undefined { - const line = this._buffer.lines.get(y); - if (!line) { - return undefined; - } - return new BufferLineApiView(line); - } - public getNullCell(): IBufferCellApi { return new CellData(); } -} - -class BufferNamespaceApi implements IBufferNamespaceApi { - private _normal: BufferApiView; - private _alternate: BufferApiView; - private _onBufferChange = new EventEmitter(); - public get onBufferChange(): IEvent { return this._onBufferChange.event; } - - constructor(private _core: ITerminal) { - this._normal = new BufferApiView(this._core.buffers.normal, 'normal'); - this._alternate = new BufferApiView(this._core.buffers.alt, 'alternate'); - this._core.buffers.onBufferActivate(() => this._onBufferChange.fire(this.active)); - } - public get active(): IBufferApi { - if (this._core.buffers.active === this._core.buffers.normal) { return this.normal; } - if (this._core.buffers.active === this._core.buffers.alt) { return this.alternate; } - throw new Error('Active buffer is neither normal nor alternate'); - } - public get normal(): IBufferApi { - return this._normal.init(this._core.buffers.normal); - } - public get alternate(): IBufferApi { - return this._alternate.init(this._core.buffers.alt); - } -} - -class BufferLineApiView implements IBufferLineApi { - constructor(private _line: IBufferLine) { } - - public get isWrapped(): boolean { return this._line.isWrapped; } - public get length(): number { return this._line.length; } - public getCell(x: number, cell?: IBufferCellApi): IBufferCellApi | undefined { - if (x < 0 || x >= this._line.length) { - return undefined; - } - - if (cell) { - this._line.loadCell(x, cell); - return cell; - } - return this._line.loadCell(x, new CellData()); - } - public translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string { - return this._line.translateToString(trimRight, startColumn, endColumn); - } -} - -class ParserApi implements IParser { - constructor(private _core: ITerminal) { } - - public registerCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean): IDisposable { - return this._core.addCsiHandler(id, (params: IParams) => callback(params.toArray())); - } - public addCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean): IDisposable { - return this.registerCsiHandler(id, callback); - } - public registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean): IDisposable { - return this._core.addDcsHandler(id, (data: string, params: IParams) => callback(data, params.toArray())); - } - public addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean): IDisposable { - return this.registerDcsHandler(id, callback); - } - public registerEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable { - return this._core.addEscHandler(id, handler); - } - public addEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable { - return this.registerEscHandler(id, handler); - } - public registerOscHandler(ident: number, callback: (data: string) => boolean): IDisposable { - return this._core.addOscHandler(ident, callback); - } - public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable { - return this.registerOscHandler(ident, callback); - } -} - -class UnicodeApi implements IUnicodeHandling { - constructor(private _core: ITerminal) { } - - public register(provider: IUnicodeVersionProvider): void { - this._core.unicodeService.register(provider); - } - - public get versions(): string[] { - return this._core.unicodeService.versions; - } - - public get activeVersion(): string { - return this._core.unicodeService.activeVersion; - } - - public set activeVersion(version: string) { - this._core.unicodeService.activeVersion = version; - } -} diff --git a/src/browser/renderer/BaseRenderLayer.ts b/src/browser/renderer/BaseRenderLayer.ts index 8afec352..7986e510 100644 --- a/src/browser/renderer/BaseRenderLayer.ts +++ b/src/browser/renderer/BaseRenderLayer.ts @@ -66,7 +66,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { } private _initCanvas(): void { - this._ctx = throwIfFalsy(this._canvas.getContext('2d', {alpha: this._alpha})); + this._ctx = throwIfFalsy(this._canvas.getContext('2d', { alpha: this._alpha })); // Draw the background if this is an opaque layer if (!this._alpha) { this._clearAll(); @@ -152,6 +152,21 @@ export abstract class BaseRenderLayer implements IRenderLayer { height * this._scaledCellHeight); } + /** + * Fills a 1px line (2px on HDPI) at the middle of the cell. This uses the + * existing fillStyle on the context. + * @param x The column to fill. + * @param y The row to fill. + */ + protected _fillMiddleLineAtCells(x: number, y: number, width: number = 1): void { + const cellOffset = Math.ceil(this._scaledCellHeight * 0.5); + this._ctx.fillRect( + x * this._scaledCellWidth, + (y + 1) * this._scaledCellHeight - cellOffset - window.devicePixelRatio, + width * this._scaledCellWidth, + window.devicePixelRatio); + } + /** * Fills a 1px line (2px on HDPI) at the bottom of the cell. This uses the * existing fillStyle on the context. @@ -242,12 +257,12 @@ export abstract class BaseRenderLayer implements IRenderLayer { */ protected _fillCharTrueColor(cell: CellData, x: number, y: number): void { this._ctx.font = this._getFont(false, false); - this._ctx.textBaseline = 'middle'; + this._ctx.textBaseline = 'ideographic'; this._clipRow(y); this._ctx.fillText( cell.getChars(), x * this._scaledCellWidth + this._scaledCharLeft, - y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2); + y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight); } /** @@ -320,7 +335,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { private _drawUncachedChars(cell: ICellData, x: number, y: number, fgOverride?: IColor): void { this._ctx.save(); this._ctx.font = this._getFont(!!cell.isBold(), !!cell.isItalic()); - this._ctx.textBaseline = 'middle'; + this._ctx.textBaseline = 'ideographic'; if (cell.isInverse()) { if (fgOverride) { @@ -362,7 +377,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._ctx.fillText( cell.getChars(), x * this._scaledCellWidth + this._scaledCharLeft, - y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2); + y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight); this._ctx.restore(); } diff --git a/src/browser/renderer/CursorRenderLayer.ts b/src/browser/renderer/CursorRenderLayer.ts index d358d580..8fda0b35 100644 --- a/src/browser/renderer/CursorRenderLayer.ts +++ b/src/browser/renderer/CursorRenderLayer.ts @@ -37,10 +37,10 @@ export class CursorRenderLayer extends BaseRenderLayer { colors: IColorSet, rendererId: number, private _onRequestRedraw: IEventEmitter, - bufferService: IBufferService, - optionsService: IOptionsService, - private readonly _coreService: ICoreService, - private readonly _coreBrowserService: ICoreBrowserService + @IBufferService bufferService: IBufferService, + @IOptionsService optionsService: IOptionsService, + @ICoreService private readonly _coreService: ICoreService, + @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService ) { super(container, 'cursor', zIndex, true, colors, rendererId, bufferService, optionsService); this._state = { @@ -197,7 +197,12 @@ export class CursorRenderLayer extends BaseRenderLayer { private _clearCursor(): void { if (this._state) { - this._clearCells(this._state.x, this._state.y, this._state.width, 1); + // Avoid potential rounding errors when device pixel ratio is less than 1 + if (window.devicePixelRatio < 1) { + this._clearAll(); + } else { + this._clearCells(this._state.x, this._state.y, this._state.width, 1); + } this._state = { x: 0, y: 0, diff --git a/src/browser/renderer/LinkRenderLayer.ts b/src/browser/renderer/LinkRenderLayer.ts index c41955d9..2492f921 100644 --- a/src/browser/renderer/LinkRenderLayer.ts +++ b/src/browser/renderer/LinkRenderLayer.ts @@ -20,8 +20,8 @@ export class LinkRenderLayer extends BaseRenderLayer { rendererId: number, linkifier: ILinkifier, linkifier2: ILinkifier2, - bufferService: IBufferService, - optionsService: IOptionsService + @IBufferService bufferService: IBufferService, + @IOptionsService optionsService: IOptionsService ) { super(container, 'link', zIndex, true, colors, rendererId, bufferService, optionsService); linkifier.onShowLinkUnderline(e => this._onShowLinkUnderline(e)); diff --git a/src/browser/renderer/Renderer.ts b/src/browser/renderer/Renderer.ts index b9d02ff8..d5de40db 100644 --- a/src/browser/renderer/Renderer.ts +++ b/src/browser/renderer/Renderer.ts @@ -6,13 +6,12 @@ import { TextRenderLayer } from 'browser/renderer/TextRenderLayer'; import { SelectionRenderLayer } from 'browser/renderer/SelectionRenderLayer'; import { CursorRenderLayer } from 'browser/renderer/CursorRenderLayer'; -import { IRenderLayer, IRenderer, IRenderDimensions, CharacterJoinerHandler, ICharacterJoinerRegistry, IRequestRedrawEvent } from 'browser/renderer/Types'; +import { IRenderLayer, IRenderer, IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/Types'; import { LinkRenderLayer } from 'browser/renderer/LinkRenderLayer'; -import { CharacterJoinerRegistry } from 'browser/renderer/CharacterJoinerRegistry'; import { Disposable } from 'common/Lifecycle'; import { IColorSet, ILinkifier, ILinkifier2 } from 'browser/Types'; import { ICharSizeService, ICoreBrowserService } from 'browser/services/Services'; -import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services'; +import { IBufferService, IOptionsService, ICoreService, IInstantiationService } from 'common/services/Services'; import { removeTerminalFromCache } from 'browser/renderer/atlas/CharAtlasCache'; import { EventEmitter, IEvent } from 'common/EventEmitter'; @@ -23,7 +22,6 @@ export class Renderer extends Disposable implements IRenderer { private _renderLayers: IRenderLayer[]; private _devicePixelRatio: number; - private _characterJoinerRegistry: ICharacterJoinerRegistry; public dimensions: IRenderDimensions; @@ -35,20 +33,18 @@ export class Renderer extends Disposable implements IRenderer { private readonly _screenElement: HTMLElement, linkifier: ILinkifier, linkifier2: ILinkifier2, + @IInstantiationService instantiationService: IInstantiationService, @IBufferService private readonly _bufferService: IBufferService, @ICharSizeService private readonly _charSizeService: ICharSizeService, - @IOptionsService private readonly _optionsService: IOptionsService, - @ICoreService coreService: ICoreService, - @ICoreBrowserService coreBrowserService: ICoreBrowserService + @IOptionsService private readonly _optionsService: IOptionsService ) { super(); const allowTransparency = this._optionsService.options.allowTransparency; - this._characterJoinerRegistry = new CharacterJoinerRegistry(this._bufferService); this._renderLayers = [ - new TextRenderLayer(this._screenElement, 0, this._colors, this._characterJoinerRegistry, allowTransparency, this._id, this._bufferService, _optionsService), - new SelectionRenderLayer(this._screenElement, 1, this._colors, this._id, this._bufferService, _optionsService), - new LinkRenderLayer(this._screenElement, 2, this._colors, this._id, linkifier, linkifier2, this._bufferService, _optionsService), - new CursorRenderLayer(this._screenElement, 3, this._colors, this._id, this._onRequestRedraw, this._bufferService, _optionsService, coreService, coreBrowserService) + instantiationService.createInstance(TextRenderLayer, this._screenElement, 0, this._colors, allowTransparency, this._id), + instantiationService.createInstance(SelectionRenderLayer, this._screenElement, 1, this._colors, this._id), + instantiationService.createInstance(LinkRenderLayer, this._screenElement, 2, this._colors, this._id, linkifier, linkifier2), + instantiationService.createInstance(CursorRenderLayer, this._screenElement, 3, this._colors, this._id, this._onRequestRedraw) ]; this.dimensions = { scaledCharWidth: 0, @@ -210,12 +206,4 @@ export class Renderer extends Disposable implements IRenderer { this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._bufferService.rows; this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._bufferService.cols; } - - public registerCharacterJoiner(handler: CharacterJoinerHandler): number { - return this._characterJoinerRegistry.registerCharacterJoiner(handler); - } - - public deregisterCharacterJoiner(joinerId: number): boolean { - return this._characterJoinerRegistry.deregisterCharacterJoiner(joinerId); - } } diff --git a/src/browser/renderer/SelectionRenderLayer.ts b/src/browser/renderer/SelectionRenderLayer.ts index 80022f01..9054e3ca 100644 --- a/src/browser/renderer/SelectionRenderLayer.ts +++ b/src/browser/renderer/SelectionRenderLayer.ts @@ -23,8 +23,8 @@ export class SelectionRenderLayer extends BaseRenderLayer { zIndex: number, colors: IColorSet, rendererId: number, - bufferService: IBufferService, - optionsService: IOptionsService + @IBufferService bufferService: IBufferService, + @IOptionsService optionsService: IOptionsService ) { super(container, 'selection', zIndex, true, colors, rendererId, bufferService, optionsService); this._clearState(); diff --git a/src/browser/renderer/TextRenderLayer.ts b/src/browser/renderer/TextRenderLayer.ts index 1f35fae1..59fbb7b1 100644 --- a/src/browser/renderer/TextRenderLayer.ts +++ b/src/browser/renderer/TextRenderLayer.ts @@ -3,16 +3,17 @@ * @license MIT */ -import { ICharacterJoinerRegistry, IRenderDimensions } from 'browser/renderer/Types'; +import { IRenderDimensions } from 'browser/renderer/Types'; import { CharData, ICellData } from 'common/Types'; import { GridCache } from 'browser/renderer/GridCache'; import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer'; import { AttributeData } from 'common/buffer/AttributeData'; import { NULL_CELL_CODE, Content } from 'common/buffer/Constants'; -import { JoinedCellData } from 'browser/renderer/CharacterJoinerRegistry'; import { IColorSet } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; import { IOptionsService, IBufferService } from 'common/services/Services'; +import { ICharacterJoinerService } from 'browser/services/Services'; +import { JoinedCellData } from 'browser/services/CharacterJoinerService'; /** * This CharData looks like a null character, which will forc a clear and render @@ -26,22 +27,20 @@ export class TextRenderLayer extends BaseRenderLayer { private _characterWidth: number = 0; private _characterFont: string = ''; private _characterOverlapCache: { [key: string]: boolean } = {}; - private _characterJoinerRegistry: ICharacterJoinerRegistry; private _workCell = new CellData(); constructor( container: HTMLElement, zIndex: number, colors: IColorSet, - characterJoinerRegistry: ICharacterJoinerRegistry, alpha: boolean, rendererId: number, - bufferService: IBufferService, - optionsService: IOptionsService + @IBufferService bufferService: IBufferService, + @IOptionsService optionsService: IOptionsService, + @ICharacterJoinerService private readonly _characterJoinerService: ICharacterJoinerService ) { super(container, 'text', zIndex, alpha, colors, rendererId, bufferService, optionsService); this._state = new GridCache(); - this._characterJoinerRegistry = characterJoinerRegistry; } public resize(dim: IRenderDimensions): void { @@ -67,7 +66,6 @@ export class TextRenderLayer extends BaseRenderLayer { private _forEachCell( firstRow: number, lastRow: number, - joinerRegistry: ICharacterJoinerRegistry | null, callback: ( cell: ICellData, x: number, @@ -77,7 +75,7 @@ export class TextRenderLayer extends BaseRenderLayer { for (let y = firstRow; y <= lastRow; y++) { const row = y + this._bufferService.buffer.ydisp; const line = this._bufferService.buffer.lines.get(row); - const joinedRanges = joinerRegistry ? joinerRegistry.getJoinedCharacters(row) : []; + const joinedRanges = this._characterJoinerService.getJoinedCharacters(row); for (let x = 0; x < this._bufferService.cols; x++) { line!.loadCell(x, this._workCell); let cell = this._workCell; @@ -101,7 +99,6 @@ export class TextRenderLayer extends BaseRenderLayer { // We already know the exact start and end column of the joined range, // so we get the string and width representing it directly - cell = new JoinedCellData( this._workCell, line!.translateToString(true, range[0], range[1]), @@ -160,7 +157,7 @@ export class TextRenderLayer extends BaseRenderLayer { ctx.save(); - this._forEachCell(firstRow, lastRow, null, (cell, x, y) => { + this._forEachCell(firstRow, lastRow, (cell, x, y) => { // libvte and xterm both draw the background (but not foreground) of invisible characters, // so we should too. let nextFillStyle = null; // null represents default background color @@ -213,12 +210,12 @@ export class TextRenderLayer extends BaseRenderLayer { } private _drawForeground(firstRow: number, lastRow: number): void { - this._forEachCell(firstRow, lastRow, this._characterJoinerRegistry, (cell, x, y) => { + this._forEachCell(firstRow, lastRow, (cell, x, y) => { if (cell.isInvisible()) { return; } this._drawChars(cell, x, y); - if (cell.isUnderline()) { + if (cell.isUnderline() || cell.isStrikethrough()) { this._ctx.save(); if (cell.isInverse()) { @@ -247,7 +244,12 @@ export class TextRenderLayer extends BaseRenderLayer { } } - this._fillBottomLineAtCells(x, y, cell.getWidth()); + if (cell.isStrikethrough()) { + this._fillMiddleLineAtCells(x, y, cell.getWidth()); + } + if (cell.isUnderline()) { + this._fillBottomLineAtCells(x, y, cell.getWidth()); + } this._ctx.restore(); } }); diff --git a/src/browser/renderer/Types.d.ts b/src/browser/renderer/Types.d.ts index cab14b88..fc137bc8 100644 --- a/src/browser/renderer/Types.d.ts +++ b/src/browser/renderer/Types.d.ts @@ -7,8 +7,6 @@ import { IDisposable } from 'common/Types'; import { IColorSet } from 'browser/Types'; import { IEvent } from 'common/EventEmitter'; -export type CharacterJoinerHandler = (text: string) => [number, number][]; - export interface IRenderDimensions { scaledCharWidth: number; scaledCharHeight: number; @@ -54,19 +52,6 @@ export interface IRenderer extends IDisposable { onOptionsChanged(): void; clear(): void; renderRows(start: number, end: number): void; - registerCharacterJoiner(handler: CharacterJoinerHandler): number; - deregisterCharacterJoiner(joinerId: number): boolean; -} - -export interface ICharacterJoiner { - id: number; - handler: CharacterJoinerHandler; -} - -export interface ICharacterJoinerRegistry { - registerCharacterJoiner(handler: (text: string) => [number, number][]): number; - deregisterCharacterJoiner(joinerId: number): boolean; - getJoinedCharacters(row: number): [number, number][]; } export interface IRenderLayer extends IDisposable { @@ -106,16 +91,6 @@ export interface IRenderLayer extends IDisposable { */ onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void; - /** - * Registers a handler to join characters to render as a group - */ - registerCharacterJoiner?(joiner: ICharacterJoiner): void; - - /** - * Deregisters the specified character joiner handler - */ - deregisterCharacterJoiner?(joinerId: number): void; - /** * Resize the render layer. */ diff --git a/src/browser/renderer/atlas/CharAtlasUtils.ts b/src/browser/renderer/atlas/CharAtlasUtils.ts index 346b35f8..b196b373 100644 --- a/src/browser/renderer/atlas/CharAtlasUtils.ts +++ b/src/browser/renderer/atlas/CharAtlasUtils.ts @@ -10,15 +10,13 @@ import { ITerminalOptions } from 'common/services/Services'; export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, options: ITerminalOptions, colors: IColorSet): ICharAtlasConfig { // null out some fields that don't matter - const clonedColors = { + const clonedColors: IPartialColorSet = { foreground: colors.foreground, background: colors.background, cursor: undefined, cursorAccent: undefined, selection: undefined, - // 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(0, 16) + ansi: colors.ansi }; return { devicePixelRatio: window.devicePixelRatio, diff --git a/src/browser/renderer/atlas/DynamicCharAtlas.ts b/src/browser/renderer/atlas/DynamicCharAtlas.ts index 696fb63c..883ebe78 100644 --- a/src/browser/renderer/atlas/DynamicCharAtlas.ts +++ b/src/browser/renderer/atlas/DynamicCharAtlas.ts @@ -91,12 +91,12 @@ export class DynamicCharAtlas extends BaseCharAtlas { // The canvas needs alpha because we use clearColor to convert the background color to alpha. // It might also contain some characters with transparent backgrounds if allowTransparency is // set. - this._cacheCtx = throwIfFalsy(this._cacheCanvas.getContext('2d', {alpha: true})); + this._cacheCtx = throwIfFalsy(this._cacheCanvas.getContext('2d', { alpha: true })); const tmpCanvas = document.createElement('canvas'); tmpCanvas.width = this._config.scaledCharWidth; tmpCanvas.height = this._config.scaledCharHeight; - this._tmpCtx = throwIfFalsy(tmpCanvas.getContext('2d', {alpha: this._config.allowTransparency})); + this._tmpCtx = throwIfFalsy(tmpCanvas.getContext('2d', { alpha: this._config.allowTransparency })); this._width = Math.floor(TEXTURE_WIDTH / this._config.scaledCharWidth); this._height = Math.floor(TEXTURE_HEIGHT / this._config.scaledCharHeight); @@ -256,7 +256,7 @@ export class DynamicCharAtlas extends BaseCharAtlas { const fontStyle = glyph.italic ? 'italic' : ''; this._tmpCtx.font = `${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`; - this._tmpCtx.textBaseline = 'middle'; + this._tmpCtx.textBaseline = 'ideographic'; this._tmpCtx.fillStyle = this._getForegroundColor(glyph).css; @@ -265,7 +265,7 @@ export class DynamicCharAtlas extends BaseCharAtlas { this._tmpCtx.globalAlpha = DIM_OPACITY; } // Draw the character - this._tmpCtx.fillText(glyph.chars, 0, this._config.scaledCharHeight / 2); + this._tmpCtx.fillText(glyph.chars, 0, this._config.scaledCharHeight); this._tmpCtx.restore(); // clear the background from the character to avoid issues with drawing over the previous diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index f0a92259..d08cf987 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -3,13 +3,13 @@ * @license MIT */ -import { IRenderer, IRenderDimensions, CharacterJoinerHandler, IRequestRedrawEvent } from 'browser/renderer/Types'; +import { IRenderer, IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/Types'; import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_BLINK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from 'browser/renderer/dom/DomRendererRowFactory'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { Disposable } from 'common/Lifecycle'; import { IColorSet, ILinkifierEvent, ILinkifier, ILinkifier2 } from 'browser/Types'; import { ICharSizeService } from 'browser/services/Services'; -import { IOptionsService, IBufferService } from 'common/services/Services'; +import { IOptionsService, IBufferService, IInstantiationService } from 'common/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { color } from 'browser/Color'; import { removeElementFromParent } from 'browser/Dom'; @@ -49,6 +49,7 @@ export class DomRenderer extends Disposable implements IRenderer { private readonly _viewportElement: HTMLElement, private readonly _linkifier: ILinkifier, private readonly _linkifier2: ILinkifier2, + @IInstantiationService instantiationService: IInstantiationService, @ICharSizeService private readonly _charSizeService: ICharSizeService, @IOptionsService private readonly _optionsService: IOptionsService, @IBufferService private readonly _bufferService: IBufferService @@ -80,7 +81,7 @@ export class DomRenderer extends Disposable implements IRenderer { this._updateDimensions(); this._injectCss(); - this._rowFactory = new DomRendererRowFactory(document, this._optionsService, this._colors); + this._rowFactory = instantiationService.createInstance(DomRendererRowFactory, document, this._colors); this._element.classList.add(TERMINAL_CLASS_PREFIX + this._terminalClass); this._screenElement.appendChild(this._rowContainer); @@ -364,7 +365,7 @@ export class DomRenderer extends Disposable implements IRenderer { const row = y + this._bufferService.buffer.ydisp; const lineData = this._bufferService.buffer.lines.get(row); const cursorStyle = this._optionsService.options.cursorStyle; - rowElement.appendChild(this._rowFactory.createRow(lineData!, row === cursorAbsoluteY, cursorStyle, cursorX, cursorBlink, this.dimensions.actualCellWidth, this._bufferService.cols)); + rowElement.appendChild(this._rowFactory.createRow(lineData!, row, row === cursorAbsoluteY, cursorStyle, cursorX, cursorBlink, this.dimensions.actualCellWidth, this._bufferService.cols)); } } @@ -372,9 +373,6 @@ export class DomRenderer extends Disposable implements IRenderer { return `.${TERMINAL_CLASS_PREFIX}${this._terminalClass}`; } - public registerCharacterJoiner(handler: CharacterJoinerHandler): number { return -1; } - public deregisterCharacterJoiner(joinerId: number): boolean { return false; } - private _onLinkHover(e: ILinkifierEvent): void { this._setCellUnderline(e.x1, e.x2, e.y1, e.y2, e.cols, true); } @@ -389,7 +387,7 @@ export class DomRenderer extends Disposable implements IRenderer { if (!row) { return; } - const span = row.children[x]; + const span = row.children[x] as HTMLElement; if (span) { span.style.textDecoration = enabled ? 'underline' : 'none'; } diff --git a/src/browser/renderer/dom/DomRendererRowFactory.test.ts b/src/browser/renderer/dom/DomRendererRowFactory.test.ts index b6604b14..2f8d264a 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.test.ts @@ -12,6 +12,7 @@ import { IBufferLine } from 'common/Types'; import { CellData } from 'common/buffer/CellData'; import { MockOptionsService } from 'common/TestUtils.test'; import { css } from 'browser/Color'; +import { MockCharacterJoinerService } from 'browser/TestUtils.test'; describe('DomRendererRowFactory', () => { let dom: jsdom.JSDOM; @@ -20,7 +21,7 @@ describe('DomRendererRowFactory', () => { beforeEach(() => { dom = new jsdom.JSDOM(''); - rowFactory = new DomRendererRowFactory(dom.window.document, new MockOptionsService({ drawBoldTextInBrightColors: true }), { + rowFactory = new DomRendererRowFactory(dom.window.document, { background: css.toColor('#010101'), foreground: css.toColor('#020202'), ansi: [ @@ -43,13 +44,13 @@ describe('DomRendererRowFactory', () => { css.toColor('#34e2e2'), css.toColor('#eeeeec') ] - } as any); + } as any, new MockCharacterJoinerService(), new MockOptionsService({ drawBoldTextInBrightColors: true })); lineData = createEmptyLineData(2); }); describe('createRow', () => { it('should not create anything for an empty row', () => { - const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); + const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), '' ); @@ -59,7 +60,7 @@ describe('DomRendererRowFactory', () => { lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, '語', 2, '語'.charCodeAt(0)])); // There should be no element for the following "empty" cell lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, '', 0, 0])); - const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); + const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), '' ); @@ -67,7 +68,7 @@ describe('DomRendererRowFactory', () => { it('should add class for cursor and cursor style', () => { for (const style of ['block', 'bar', 'underline']) { - const fragment = rowFactory.createRow(lineData, true, style, 0, false, 5, 20); + const fragment = rowFactory.createRow(lineData, 0, true, style, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), ` ` ); @@ -75,7 +76,7 @@ describe('DomRendererRowFactory', () => { }); it('should add class for cursor blink', () => { - const fragment = rowFactory.createRow(lineData, true, 'block', 0, true, 5, 20); + const fragment = rowFactory.createRow(lineData, 0, true, 'block', 0, true, 5, 20); assert.equal(getFragmentHtml(fragment), ` ` ); @@ -84,7 +85,7 @@ describe('DomRendererRowFactory', () => { it('should not render cells that go beyond the terminal\'s columns', () => { lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)])); lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)])); - const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 1); + const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 1); assert.equal(getFragmentHtml(fragment), 'a' ); @@ -95,7 +96,7 @@ describe('DomRendererRowFactory', () => { const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.BOLD; lineData.setCell(0, cell); - const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); + const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), 'a' ); @@ -105,7 +106,7 @@ describe('DomRendererRowFactory', () => { const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.ITALIC; lineData.setCell(0, cell); - const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); + const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), 'a' ); @@ -115,7 +116,7 @@ describe('DomRendererRowFactory', () => { const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.DIM; lineData.setCell(0, cell); - const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); + const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), 'a' ); @@ -125,12 +126,22 @@ describe('DomRendererRowFactory', () => { const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.UNDERLINE; lineData.setCell(0, cell); - const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); + const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), 'a' ); }); + it('should add class for strikethrough', () => { + const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); + cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.STRIKETHROUGH; + lineData.setCell(0, cell); + const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20); + assert.equal(getFragmentHtml(fragment), + 'a' + ); + }); + it('should add classes for 256 foreground colors', () => { const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); cell.fg |= Attributes.CM_P256; @@ -138,7 +149,7 @@ describe('DomRendererRowFactory', () => { cell.fg &= ~Attributes.PCOLOR_MASK; cell.fg |= i; lineData.setCell(0, cell); - const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); + const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), `a` ); @@ -152,7 +163,7 @@ describe('DomRendererRowFactory', () => { cell.bg &= ~Attributes.PCOLOR_MASK; cell.bg |= i; lineData.setCell(0, cell); - const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); + const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), `a` ); @@ -164,7 +175,7 @@ describe('DomRendererRowFactory', () => { cell.fg |= Attributes.CM_P16 | 2 | FgFlags.INVERSE; cell.bg |= Attributes.CM_P16 | 1; lineData.setCell(0, cell); - const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); + const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), 'a' ); @@ -175,7 +186,7 @@ describe('DomRendererRowFactory', () => { cell.fg |= FgFlags.INVERSE; cell.bg |= Attributes.CM_P16 | 1; lineData.setCell(0, cell); - const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); + const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), 'a' ); @@ -185,7 +196,7 @@ describe('DomRendererRowFactory', () => { const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); cell.fg |= Attributes.CM_P16 | 1 | FgFlags.INVERSE; lineData.setCell(0, cell); - const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); + const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), 'a' ); @@ -198,7 +209,7 @@ describe('DomRendererRowFactory', () => { cell.fg &= ~Attributes.PCOLOR_MASK; cell.fg |= i; lineData.setCell(0, cell); - const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); + const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), `a` ); @@ -210,7 +221,7 @@ describe('DomRendererRowFactory', () => { cell.fg |= Attributes.CM_RGB | 1 << 16 | 2 << 8 | 3; cell.bg |= Attributes.CM_RGB | 4 << 16 | 5 << 8 | 6; lineData.setCell(0, cell); - const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); + const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), 'a' ); @@ -221,7 +232,7 @@ describe('DomRendererRowFactory', () => { cell.fg |= Attributes.CM_RGB | 1 << 16 | 2 << 8 | 3 | FgFlags.INVERSE; cell.bg |= Attributes.CM_RGB | 4 << 16 | 5 << 8 | 6; lineData.setCell(0, cell); - const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); + const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), 'a' ); diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index c7c87451..a61ebd73 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -10,11 +10,14 @@ import { CellData } from 'common/buffer/CellData'; import { IOptionsService } from 'common/services/Services'; import { color, rgba } from 'browser/Color'; import { IColorSet, IColor } from 'browser/Types'; +import { ICharacterJoinerService } from 'browser/services/Services'; +import { JoinedCellData } from 'browser/services/CharacterJoinerService'; export const BOLD_CLASS = 'xterm-bold'; export const DIM_CLASS = 'xterm-dim'; export const ITALIC_CLASS = 'xterm-italic'; export const UNDERLINE_CLASS = 'xterm-underline'; +export const STRIKETHROUGH_CLASS = 'xterm-strikethrough'; export const CURSOR_CLASS = 'xterm-cursor'; export const CURSOR_BLINK_CLASS = 'xterm-cursor-blink'; export const CURSOR_STYLE_BLOCK_CLASS = 'xterm-cursor-block'; @@ -26,8 +29,9 @@ export class DomRendererRowFactory { constructor( private readonly _document: Document, - private readonly _optionsService: IOptionsService, - private _colors: IColorSet + private _colors: IColorSet, + @ICharacterJoinerService private readonly _characterJoinerService: ICharacterJoinerService, + @IOptionsService private readonly _optionsService: IOptionsService ) { } @@ -35,9 +39,10 @@ export class DomRendererRowFactory { this._colors = colors; } - public createRow(lineData: IBufferLine, isCursorRow: boolean, cursorStyle: string | undefined, cursorX: number, cursorBlink: boolean, cellWidth: number, cols: number): DocumentFragment { + public createRow(lineData: IBufferLine, row: number, isCursorRow: boolean, cursorStyle: string | undefined, cursorX: number, cursorBlink: boolean, cellWidth: number, cols: number): DocumentFragment { const fragment = this._document.createDocumentFragment(); + const joinedRanges = this._characterJoinerService.getJoinedCharacters(row); // Find the line length first, this prevents the need to output a bunch of // empty cells at the end. This cannot easily be integrated into the main // loop below because of the colCount feature (which can be removed after we @@ -53,18 +58,58 @@ export class DomRendererRowFactory { for (let x = 0; x < lineLength; x++) { lineData.loadCell(x, this._workCell); - const width = this._workCell.getWidth(); + let width = this._workCell.getWidth(); // The character to the left is a wide character, drawing is owned by the char at x-1 if (width === 0) { continue; } + // If true, indicates that the current character(s) to draw were joined. + let isJoined = false; + let lastCharX = x; + + // Process any joined character ranges as needed. Because of how the + // ranges are produced, we know that they are valid for the characters + // and attributes of our input. + let cell = this._workCell; + if (joinedRanges.length > 0 && x === joinedRanges[0][0]) { + isJoined = true; + const range = joinedRanges.shift()!; + + // We already know the exact start and end column of the joined range, + // so we get the string and width representing it directly + cell = new JoinedCellData( + this._workCell, + lineData.translateToString(true, range[0], range[1]), + range[1] - range[0] + ); + + // Skip over the cells occupied by this range in the loop + lastCharX = range[1] - 1; + + // Recalculate width + width = cell.getWidth(); + } + const charElement = this._document.createElement('span'); if (width > 1) { charElement.style.width = `${cellWidth * width}px`; } + if (isJoined) { + // Ligatures in the DOM renderer must use display inline, as they may not show with + // inline-block if they are outside the bounds of the element + charElement.style.display = 'inline'; + + // The DOM renderer colors the background of the cursor but for ligatures all cells are + // joined. The workaround here is to show a cursor around the whole ligature so it shows up, + // the cursor looks the same when on any character of the ligature though + if (cursorX >= x && cursorX <= lastCharX) { + cursorX = x; + } + } + if (isCursorRow && x === cursorX) { charElement.classList.add(CURSOR_CLASS); @@ -85,33 +130,37 @@ export class DomRendererRowFactory { } } - if (this._workCell.isBold()) { + if (cell.isBold()) { charElement.classList.add(BOLD_CLASS); } - if (this._workCell.isItalic()) { + if (cell.isItalic()) { charElement.classList.add(ITALIC_CLASS); } - if (this._workCell.isDim()) { + if (cell.isDim()) { charElement.classList.add(DIM_CLASS); } - if (this._workCell.isUnderline()) { + if (cell.isUnderline()) { charElement.classList.add(UNDERLINE_CLASS); } - if (this._workCell.isInvisible()) { + if (cell.isInvisible()) { charElement.textContent = WHITESPACE_CELL_CHAR; } else { - charElement.textContent = this._workCell.getChars() || WHITESPACE_CELL_CHAR; + charElement.textContent = cell.getChars() || WHITESPACE_CELL_CHAR; } - let fg = this._workCell.getFgColor(); - let fgColorMode = this._workCell.getFgColorMode(); - let bg = this._workCell.getBgColor(); - let bgColorMode = this._workCell.getBgColorMode(); - const isInverse = !!this._workCell.isInverse(); + if (cell.isStrikethrough()) { + charElement.classList.add(STRIKETHROUGH_CLASS); + } + + let fg = cell.getFgColor(); + let fgColorMode = cell.getFgColorMode(); + let bg = cell.getBgColor(); + let bgColorMode = cell.getBgColorMode(); + const isInverse = !!cell.isInverse(); if (isInverse) { const temp = fg; fg = bg; @@ -125,7 +174,7 @@ export class DomRendererRowFactory { switch (fgColorMode) { case Attributes.CM_P16: case Attributes.CM_P256: - if (this._workCell.isBold() && fg < 8 && this._optionsService.options.drawBoldTextInBrightColors) { + if (cell.isBold() && fg < 8 && this._optionsService.options.drawBoldTextInBrightColors) { fg += 8; } if (!this._applyMinimumContrast(charElement, this._colors.background, this._colors.ansi[fg])) { @@ -168,6 +217,8 @@ export class DomRendererRowFactory { } fragment.appendChild(charElement); + + x = lastCharX; } return fragment; } diff --git a/src/browser/renderer/CharacterJoinerRegistry.test.ts b/src/browser/services/CharacterJoinerService.test.ts similarity index 59% rename from src/browser/renderer/CharacterJoinerRegistry.test.ts rename to src/browser/services/CharacterJoinerService.test.ts index bca12d6b..6b5326d9 100644 --- a/src/browser/renderer/CharacterJoinerRegistry.test.ts +++ b/src/browser/services/CharacterJoinerService.test.ts @@ -4,15 +4,15 @@ */ import { assert } from 'chai'; -import { ICharacterJoinerRegistry } from 'browser/renderer/Types'; -import { CharacterJoinerRegistry } from 'browser/renderer/CharacterJoinerRegistry'; +import { ICharacterJoinerService } from 'browser/services/Services'; +import { CharacterJoinerService } from 'browser/services/CharacterJoinerService'; import { BufferLine } from 'common/buffer/BufferLine'; import { IBufferLine } from 'common/Types'; import { CellData } from 'common/buffer/CellData'; import { MockBufferService } from 'common/TestUtils.test'; -describe('CharacterJoinerRegistry', () => { - let registry: ICharacterJoinerRegistry; +describe('CharacterJoinerService', () => { + let service: ICharacterJoinerService; beforeEach(() => { const bufferService = new MockBufferService(16, 10); @@ -39,225 +39,225 @@ describe('CharacterJoinerRegistry', () => { for (let i = 0; i < sub.length; ++i) line6.setCell(i + oldSize, sub.loadCell(i, new CellData())); lines.set(6, line6); - registry = new CharacterJoinerRegistry(bufferService); + service = new CharacterJoinerService(bufferService); }); it('has no joiners upon creation', () => { - assert.deepEqual(registry.getJoinedCharacters(0), []); + assert.deepEqual(service.getJoinedCharacters(0), []); }); it('returns ranges matched by the registered joiners', () => { - registry.registerCharacterJoiner(substringJoiner('->')); + service.register(substringJoiner('->')); assert.deepEqual( - registry.getJoinedCharacters(0), + service.getJoinedCharacters(0), [[2, 4], [7, 9], [12, 14]] ); }); it('processes the input using all provided joiners', () => { - registry.registerCharacterJoiner(substringJoiner('->')); + service.register(substringJoiner('->')); assert.deepEqual( - registry.getJoinedCharacters(1), + service.getJoinedCharacters(1), [[2, 4], [12, 14]] ); - registry.registerCharacterJoiner(substringJoiner('=>')); + service.register(substringJoiner('=>')); assert.deepEqual( - registry.getJoinedCharacters(1), + service.getJoinedCharacters(1), [[2, 4], [7, 9], [12, 14]] ); }); it('removes deregistered joiners from future calls', () => { - const joiner1 = registry.registerCharacterJoiner(substringJoiner('->')); - const joiner2 = registry.registerCharacterJoiner(substringJoiner('=>')); + const joiner1 = service.register(substringJoiner('->')); + const joiner2 = service.register(substringJoiner('=>')); assert.deepEqual( - registry.getJoinedCharacters(1), + service.getJoinedCharacters(1), [[2, 4], [7, 9], [12, 14]] ); - registry.deregisterCharacterJoiner(joiner1); + service.deregister(joiner1); assert.deepEqual( - registry.getJoinedCharacters(1), + service.getJoinedCharacters(1), [[7, 9]] ); - registry.deregisterCharacterJoiner(joiner2); + service.deregister(joiner2); assert.deepEqual( - registry.getJoinedCharacters(1), + service.getJoinedCharacters(1), [] ); }); it('doesn\'t process joins on differently-styled characters', () => { - registry.registerCharacterJoiner(substringJoiner('->')); + service.register(substringJoiner('->')); assert.deepEqual( - registry.getJoinedCharacters(2), + service.getJoinedCharacters(2), [[2, 4], [12, 14]] ); }); it('returns an empty list of ranges if there is nothing to be joined', () => { - registry.registerCharacterJoiner(substringJoiner('->')); + service.register(substringJoiner('->')); assert.deepEqual( - registry.getJoinedCharacters(3), + service.getJoinedCharacters(3), [] ); }); it('returns an empty list of ranges if the line is empty', () => { - registry.registerCharacterJoiner(substringJoiner('->')); + service.register(substringJoiner('->')); assert.deepEqual( - registry.getJoinedCharacters(4), + service.getJoinedCharacters(4), [] ); }); it('returns false when trying to deregister a joiner that does not exist', () => { - registry.registerCharacterJoiner(substringJoiner('->')); - assert.deepEqual(registry.deregisterCharacterJoiner(123), false); + service.register(substringJoiner('->')); + assert.deepEqual(service.deregister(123), false); assert.deepEqual( - registry.getJoinedCharacters(0), + service.getJoinedCharacters(0), [[2, 4], [7, 9], [12, 14]] ); }); it('doesn\'t process same-styled ranges that only have one character', () => { - registry.registerCharacterJoiner(substringJoiner('a')); - registry.registerCharacterJoiner(substringJoiner('b')); - registry.registerCharacterJoiner(substringJoiner('d')); + service.register(substringJoiner('a')); + service.register(substringJoiner('b')); + service.register(substringJoiner('d')); assert.deepEqual( - registry.getJoinedCharacters(5), + service.getJoinedCharacters(5), [[5, 6]] ); }); it('handles ranges that extend all the way to the end of the line', () => { - registry.registerCharacterJoiner(substringJoiner('-> d')); + service.register(substringJoiner('-> d')); assert.deepEqual( - registry.getJoinedCharacters(2), + service.getJoinedCharacters(2), [[12, 16]] ); }); it('handles adjacent ranges', () => { - registry.registerCharacterJoiner(substringJoiner('->')); - registry.registerCharacterJoiner(substringJoiner('> c ')); + service.register(substringJoiner('->')); + service.register(substringJoiner('> c ')); assert.deepEqual( - registry.getJoinedCharacters(2), + service.getJoinedCharacters(2), [[2, 4], [8, 12], [12, 14]] ); }); it('handles fullwidth characters in the middle of ranges', () => { - registry.registerCharacterJoiner(substringJoiner('wi¥de')); + service.register(substringJoiner('wi¥de')); assert.deepEqual( - registry.getJoinedCharacters(6), + service.getJoinedCharacters(6), [[0, 6]] ); }); it('handles fullwidth characters at the end of ranges', () => { - registry.registerCharacterJoiner(substringJoiner('wi¥')); + service.register(substringJoiner('wi¥')); assert.deepEqual( - registry.getJoinedCharacters(6), + service.getJoinedCharacters(6), [[0, 4]] ); }); it('handles emojis in the middle of ranges', () => { - registry.registerCharacterJoiner(substringJoiner('emo\xf0\x9f\x98\x81 ji')); + service.register(substringJoiner('emo\xf0\x9f\x98\x81 ji')); assert.deepEqual( - registry.getJoinedCharacters(6), + service.getJoinedCharacters(6), [[6, 13]] ); }); it('handles emojis at the end of ranges', () => { - registry.registerCharacterJoiner(substringJoiner('emo\xf0\x9f\x98\x81 ')); + service.register(substringJoiner('emo\xf0\x9f\x98\x81 ')); assert.deepEqual( - registry.getJoinedCharacters(6), + service.getJoinedCharacters(6), [[6, 11]] ); }); it('handles ranges after wide and emoji characters', () => { - registry.registerCharacterJoiner(substringJoiner('abc')); + service.register(substringJoiner('abc')); assert.deepEqual( - registry.getJoinedCharacters(6), + service.getJoinedCharacters(6), [[13, 16]] ); }); describe('range merging', () => { it('inserts a new range before the existing ones', () => { - registry.registerCharacterJoiner(() => [[1, 2], [2, 3]]); - registry.registerCharacterJoiner(() => [[0, 1]]); + service.register(() => [[1, 2], [2, 3]]); + service.register(() => [[0, 1]]); assert.deepEqual( - registry.getJoinedCharacters(0), + service.getJoinedCharacters(0), [[0, 1], [1, 2], [2, 3]] ); }); it('inserts in between two ranges', () => { - registry.registerCharacterJoiner(() => [[0, 2], [4, 6]]); - registry.registerCharacterJoiner(() => [[2, 4]]); + service.register(() => [[0, 2], [4, 6]]); + service.register(() => [[2, 4]]); assert.deepEqual( - registry.getJoinedCharacters(0), + service.getJoinedCharacters(0), [[0, 2], [2, 4], [4, 6]] ); }); it('inserts after the last range', () => { - registry.registerCharacterJoiner(() => [[0, 2], [4, 6]]); - registry.registerCharacterJoiner(() => [[6, 8]]); + service.register(() => [[0, 2], [4, 6]]); + service.register(() => [[6, 8]]); assert.deepEqual( - registry.getJoinedCharacters(0), + service.getJoinedCharacters(0), [[0, 2], [4, 6], [6, 8]] ); }); it('extends the beginning of a range', () => { - registry.registerCharacterJoiner(() => [[0, 2], [4, 6]]); - registry.registerCharacterJoiner(() => [[3, 5]]); + service.register(() => [[0, 2], [4, 6]]); + service.register(() => [[3, 5]]); assert.deepEqual( - registry.getJoinedCharacters(0), + service.getJoinedCharacters(0), [[0, 2], [3, 6]] ); }); it('extends the end of a range', () => { - registry.registerCharacterJoiner(() => [[0, 2], [4, 6]]); - registry.registerCharacterJoiner(() => [[1, 4]]); + service.register(() => [[0, 2], [4, 6]]); + service.register(() => [[1, 4]]); assert.deepEqual( - registry.getJoinedCharacters(0), + service.getJoinedCharacters(0), [[0, 4], [4, 6]] ); }); it('extends the last range', () => { - registry.registerCharacterJoiner(() => [[0, 2], [4, 6]]); - registry.registerCharacterJoiner(() => [[5, 7]]); + service.register(() => [[0, 2], [4, 6]]); + service.register(() => [[5, 7]]); assert.deepEqual( - registry.getJoinedCharacters(0), + service.getJoinedCharacters(0), [[0, 2], [4, 7]] ); }); it('connects two ranges', () => { - registry.registerCharacterJoiner(() => [[0, 2], [4, 6]]); - registry.registerCharacterJoiner(() => [[1, 5]]); + service.register(() => [[0, 2], [4, 6]]); + service.register(() => [[1, 5]]); assert.deepEqual( - registry.getJoinedCharacters(0), + service.getJoinedCharacters(0), [[0, 6]] ); }); it('connects more than two ranges', () => { - registry.registerCharacterJoiner(() => [[0, 2], [4, 6], [8, 10], [12, 14]]); - registry.registerCharacterJoiner(() => [[1, 10]]); + service.register(() => [[0, 2], [4, 6], [8, 10], [12, 14]]); + service.register(() => [[1, 10]]); assert.deepEqual( - registry.getJoinedCharacters(0), + service.getJoinedCharacters(0), [[0, 10], [12, 14]] ); }); @@ -270,7 +270,7 @@ function lineData(data: IPartialLineData[]): IBufferLine { const tline = new BufferLine(0); for (let i = 0; i < data.length; ++i) { const line = data[i][0]; - const attr = (data[i][1] || 0); + const attr = (data[i][1] || 0) as number; const offset = tline.length; tline.resize(tline.length + line.split('').length, CellData.fromCharData([0, '', 0, 0])); line.split('').map((char, idx) => tline.setCell(idx + offset, CellData.fromCharData([attr, char, 1, char.charCodeAt(0)]))); diff --git a/src/browser/renderer/CharacterJoinerRegistry.ts b/src/browser/services/CharacterJoinerService.ts similarity index 95% rename from src/browser/renderer/CharacterJoinerRegistry.ts rename to src/browser/services/CharacterJoinerService.ts index 5385b76b..ea65c29b 100644 --- a/src/browser/renderer/CharacterJoinerRegistry.ts +++ b/src/browser/services/CharacterJoinerService.ts @@ -4,11 +4,12 @@ */ import { IBufferLine, ICellData, CharData } from 'common/Types'; -import { ICharacterJoinerRegistry, ICharacterJoiner } from 'browser/renderer/Types'; +import { ICharacterJoiner } from 'browser/Types'; import { AttributeData } from 'common/buffer/AttributeData'; import { WHITESPACE_CELL_CHAR, Content } from 'common/buffer/Constants'; import { CellData } from 'common/buffer/CellData'; import { IBufferService } from 'common/services/Services'; +import { ICharacterJoinerService } from 'browser/services/Services'; export class JoinedCellData extends AttributeData implements ICellData { private _width: number; @@ -55,15 +56,18 @@ export class JoinedCellData extends AttributeData implements ICellData { } } -export class CharacterJoinerRegistry implements ICharacterJoinerRegistry { +export class CharacterJoinerService implements ICharacterJoinerService { + public serviceBrand: undefined; private _characterJoiners: ICharacterJoiner[] = []; private _nextCharacterJoinerId: number = 0; private _workCell: CellData = new CellData(); - constructor(private _bufferService: IBufferService) { } + constructor( + @IBufferService private _bufferService: IBufferService + ) { } - public registerCharacterJoiner(handler: (text: string) => [number, number][]): number { + public register(handler: (text: string) => [number, number][]): number { const joiner: ICharacterJoiner = { id: this._nextCharacterJoinerId++, handler @@ -73,7 +77,7 @@ export class CharacterJoinerRegistry implements ICharacterJoinerRegistry { return joiner.id; } - public deregisterCharacterJoiner(joinerId: number): boolean { + public deregister(joinerId: number): boolean { for (let i = 0; i < this._characterJoiners.length; i++) { if (this._characterJoiners[i].id === joinerId) { this._characterJoiners.splice(i, 1); @@ -177,7 +181,7 @@ export class CharacterJoinerRegistry implements ICharacterJoinerRegistry { // We merge any overlapping ranges across the different joiners const joinerRanges = this._characterJoiners[i].handler(text); for (let j = 0; j < joinerRanges.length; j++) { - CharacterJoinerRegistry._mergeRanges(joinedRanges, joinerRanges[j]); + CharacterJoinerService._mergeRanges(joinedRanges, joinerRanges[j]); } } this._stringRangesToCellRanges(joinedRanges, lineData, startCol); diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 174a1272..fc2eb435 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IRenderer, IRenderDimensions, CharacterJoinerHandler } from 'browser/renderer/Types'; +import { IRenderer, IRenderDimensions } from 'browser/renderer/Types'; import { RenderDebouncer } from 'browser/RenderDebouncer'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; @@ -174,6 +174,10 @@ export class RenderService extends Disposable implements IRenderService { } public onDevicePixelRatioChange(): void { + // Force char size measurement as DomMeasureStrategy(getBoundingClientRect) is not stable + // when devicePixelRatio changes + this._charSizeService.measure(); + this._renderer.onDevicePixelRatioChange(); this.refreshRows(0, this._rowCount - 1); } @@ -210,12 +214,4 @@ export class RenderService extends Disposable implements IRenderService { public clear(): void { this._renderer.clear(); } - - public registerCharacterJoiner(handler: CharacterJoinerHandler): number { - return this._renderer.registerCharacterJoiner(handler); - } - - public deregisterCharacterJoiner(joinerId: number): boolean { - return this._renderer.deregisterCharacterJoiner(joinerId); - } } diff --git a/src/browser/services/SelectionService.test.ts b/src/browser/services/SelectionService.test.ts index eb90ba44..514d5803 100644 --- a/src/browser/services/SelectionService.test.ts +++ b/src/browser/services/SelectionService.test.ts @@ -21,7 +21,7 @@ class TestSelectionService extends SelectionService { optionsService: IOptionsService, renderService: IRenderService ) { - super(null!, null!, bufferService, new MockCoreService(), new MockMouseService(), optionsService, renderService); + super(null!, null!, null!, bufferService, new MockCoreService(), new MockMouseService(), optionsService, renderService); } public get model(): SelectionModel { return this._model; } diff --git a/src/browser/services/SelectionService.ts b/src/browser/services/SelectionService.ts index a3d8d12c..c0547755 100644 --- a/src/browser/services/SelectionService.ts +++ b/src/browser/services/SelectionService.ts @@ -10,11 +10,13 @@ import * as Browser from 'common/Platform'; import { SelectionModel } from 'browser/selection/SelectionModel'; import { CellData } from 'common/buffer/CellData'; import { EventEmitter, IEvent } from 'common/EventEmitter'; -import { ICharSizeService, IMouseService, ISelectionService, IRenderService } from 'browser/services/Services'; +import { IMouseService, ISelectionService, IRenderService } from 'browser/services/Services'; +import { ILinkifier2 } from 'browser/Types'; import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services'; import { getCoordsRelativeToElement } from 'browser/input/Mouse'; import { moveToCellSequence } from 'browser/input/MoveToCell'; import { Disposable } from 'common/Lifecycle'; +import { getRangeLength } from 'common/buffer/BufferRange'; /** * The number of pixels the mouse needs to be above or below the viewport in @@ -105,6 +107,9 @@ export class SelectionService extends Disposable implements ISelectionService { private _workCell: CellData = new CellData(); private _mouseDownTimeStamp: number = 0; + private _oldHasSelection: boolean = false; + private _oldSelectionStart: [number, number] | undefined = undefined; + private _oldSelectionEnd: [number, number] | undefined = undefined; private _onLinuxMouseSelection = this.register(new EventEmitter()); public get onLinuxMouseSelection(): IEvent { return this._onLinuxMouseSelection.event; } @@ -118,6 +123,7 @@ export class SelectionService extends Disposable implements ISelectionService { constructor( private readonly _element: HTMLElement, private readonly _screenElement: HTMLElement, + private readonly _linkifier: ILinkifier2, @IBufferService private readonly _bufferService: IBufferService, @ICoreService private readonly _coreService: ICoreService, @IMouseService private readonly _mouseService: IMouseService, @@ -127,8 +133,8 @@ export class SelectionService extends Disposable implements ISelectionService { super(); // Init listeners - this._mouseMoveListener = event => this._onMouseMove(event); - this._mouseUpListener = event => this._onMouseUp(event); + this._mouseMoveListener = event => this._onMouseMove(event as MouseEvent); + this._mouseUpListener = event => this._onMouseUp(event as MouseEvent); this._coreService.onUserInput(() => { if (this.hasSelection) { this.clearSelection(); @@ -290,7 +296,7 @@ export class SelectionService extends Disposable implements ISelectionService { * Checks if the current click was inside the current selection * @param event The mouse event */ - public isClickInSelection(event: MouseEvent): boolean { + private _isClickInSelection(event: MouseEvent): boolean { const coords = this._getMouseBufferCoords(event); const start = this._model.finalSelectionStart; const end = this._model.finalSelectionEnd; @@ -313,13 +319,23 @@ export class SelectionService extends Disposable implements ISelectionService { * Selects word at the current mouse event coordinates. * @param event The mouse event. */ - public selectWordAtCursor(event: MouseEvent): void { + private _selectWordAtCursor(event: MouseEvent, allowWhitespaceOnlySelection: boolean): boolean { + // Check if there is a link under the cursor first and select that if so + const range = this._linkifier.currentLink?.link?.range; + if (range) { + this._model.selectionStart = [range.start.x - 1, range.start.y - 1]; + this._model.selectionStartLength = getRangeLength(range, this._bufferService.cols); + this._model.selectionEnd = undefined; + return true; + } + const coords = this._getMouseBufferCoords(event); if (coords) { - this._selectWordAt(coords, false); + this._selectWordAt(coords, allowWhitespaceOnlySelection); this._model.selectionEnd = undefined; - this.refresh(true); + return true; } + return false; } /** @@ -524,14 +540,12 @@ export class SelectionService extends Disposable implements ISelectionService { } /** - * Performs a double click, selecting the current work. + * Performs a double click, selecting the current word. * @param event The mouse event. */ private _onDoubleClick(event: MouseEvent): void { - const coords = this._getMouseBufferCoords(event); - if (coords) { + if (this._selectWordAtCursor(event, true)) { this._activeSelectionMode = SelectionMode.WORD; - this._selectWordAt(coords, true); } } @@ -681,11 +695,43 @@ export class SelectionService extends Disposable implements ISelectionService { this._coreService.triggerDataEvent(sequence, true); } } - } else if (this.hasSelection) { - this._onSelectionChange.fire(); + } else { + this._fireEventIfSelectionChanged(); } } + private _fireEventIfSelectionChanged(): void { + const start = this._model.finalSelectionStart; + const end = this._model.finalSelectionEnd; + const hasSelection = !!start && !!end && (start[0] !== end[0] || start[1] !== end[1]); + + if (!hasSelection) { + if (this._oldHasSelection) { + this._fireOnSelectionChange(start, end, hasSelection); + } + return; + } + + // Sanity check, these should not be undefined as there is a selection + if (!start || !end) { + return; + } + + if (!this._oldSelectionStart || !this._oldSelectionEnd || ( + start[0] !== this._oldSelectionStart[0] || start[1] !== this._oldSelectionStart[1] || + end[0] !== this._oldSelectionEnd[0] || end[1] !== this._oldSelectionEnd[1])) { + + this._fireOnSelectionChange(start, end, hasSelection); + } + } + + private _fireOnSelectionChange(start: [number, number] | undefined, end: [number, number] | undefined, hasSelection: boolean): void { + this._oldSelectionStart = start; + this._oldSelectionEnd = end; + this._oldHasSelection = hasSelection; + this._onSelectionChange.fire(); + } + private _onBufferActivate(e: {activeBuffer: IBuffer, inactiveBuffer: IBuffer}): void { this.clearSelection(); // Only adjust the selection on trim, shiftElements is rarely used (only in @@ -727,6 +773,15 @@ export class SelectionService extends Disposable implements ISelectionService { this.refresh(); } + public rightClickSelect(ev: MouseEvent): void { + if (!this._isClickInSelection(ev)) { + if (this._selectWordAtCursor(ev, false)) { + this.refresh(true); + } + this._fireEventIfSelectionChanged(); + } + } + /** * Gets positional information for the word at the coordinated specified. * @param coords The coordinates to get the word at. diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index 1c71d387..8c8a7bd9 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -4,7 +4,7 @@ */ import { IEvent } from 'common/EventEmitter'; -import { IRenderDimensions, IRenderer, CharacterJoinerHandler } from 'browser/renderer/Types'; +import { IRenderDimensions, IRenderer } from 'browser/renderer/Types'; import { IColorSet } from 'browser/Types'; import { ISelectionRedrawRequestEvent as ISelectionRequestRedrawEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; @@ -66,8 +66,6 @@ export interface IRenderService extends IDisposable { onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void; onCursorMove(): void; clear(): void; - registerCharacterJoiner(handler: CharacterJoinerHandler): number; - deregisterCharacterJoiner(joinerId: number): boolean; } export const ISelectionService = createDecorator('SelectionService'); @@ -91,8 +89,7 @@ export interface ISelectionService { selectAll(): void; selectLines(start: number, end: number): void; clearSelection(): void; - isClickInSelection(event: MouseEvent): boolean; - selectWordAtCursor(event: MouseEvent): void; + rightClickSelect(event: MouseEvent): void; shouldColumnSelect(event: KeyboardEvent | MouseEvent): boolean; shouldForceSelection(event: MouseEvent): boolean; refresh(isLinuxMouseSelection?: boolean): void; @@ -105,3 +102,13 @@ export interface ISoundService { playBellSound(): void; } + + +export const ICharacterJoinerService = createDecorator('CharacterJoinerService'); +export interface ICharacterJoinerService { + serviceBrand: undefined; + + register(handler: (text: string) => [number, number][]): number; + deregister(joinerId: number): boolean; + getJoinedCharacters(row: number): [number, number][]; +} diff --git a/src/browser/services/SoundService.ts b/src/browser/services/SoundService.ts index 8d940c13..3880b42d 100644 --- a/src/browser/services/SoundService.ts +++ b/src/browser/services/SoundService.ts @@ -13,7 +13,7 @@ export class SoundService implements ISoundService { public static get audioContext(): AudioContext | null { if (!SoundService._audioContext) { - const audioContextCtor: typeof AudioContext = (window).AudioContext || (window).webkitAudioContext; + const audioContextCtor: typeof AudioContext = (window as any).AudioContext || (window as any).webkitAudioContext; if (!audioContextCtor) { console.warn('Web Audio API is not supported by this browser. Consider upgrading to the latest version'); return null; diff --git a/src/common/CircularList.ts b/src/common/CircularList.ts index ab00e681..4d2c04ec 100644 --- a/src/common/CircularList.ts +++ b/src/common/CircularList.ts @@ -158,7 +158,7 @@ export class CircularList implements ICircularList { this._array[this._getCyclicIndex(i)] = this._array[this._getCyclicIndex(i + deleteCount)]; } this._length -= deleteCount; - this.onDeleteEmitter.fire({index: start, amount: deleteCount}); + this.onDeleteEmitter.fire({ index: start, amount: deleteCount }); } // Add items @@ -169,7 +169,7 @@ export class CircularList implements ICircularList { this._array[this._getCyclicIndex(start + i)] = items[i]; } if (items.length) { - this.onInsertEmitter.fire({index: start, amount: items.length}); + this.onInsertEmitter.fire({ index: start, amount: items.length }); } // Adjust length as needed diff --git a/src/common/Clone.test.ts b/src/common/Clone.test.ts index 370538ea..5fbe4bf9 100644 --- a/src/common/Clone.test.ts +++ b/src/common/Clone.test.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { assert, expect } from 'chai'; +import { assert } from 'chai'; import { clone } from 'common/Clone'; describe('clone', () => { @@ -124,6 +124,6 @@ describe('clone', () => { test.a.b.c = test; - expect(() => clone(test)).to.not.throw(); + assert.doesNotThrow(() => clone(test)); }); }); diff --git a/src/common/Clone.ts b/src/common/Clone.ts index 51c5abaa..37821fe0 100644 --- a/src/common/Clone.ts +++ b/src/common/Clone.ts @@ -16,7 +16,7 @@ export function clone(val: T, depth: number = 5): T { for (const key in val) { // Recursively clone eack item unless we're at the maximum depth - clonedObject[key] = depth <= 1 ? val[key] : (val[key] ? clone(val[key], depth - 1) : val[key]); + clonedObject[key] = depth <= 1 ? val[key] : (val[key] && clone(val[key], depth - 1)); } return clonedObject as T; diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index 2f636349..e61a8e16 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -22,12 +22,12 @@ */ import { Disposable } from 'common/Lifecycle'; -import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, ICoreMouseService, IUnicodeService, IDirtyRowService } from 'common/services/Services'; +import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, ICoreMouseService, IUnicodeService, IDirtyRowService, LogLevelEnum } from 'common/services/Services'; import { InstantiationService } from 'common/services/InstantiationService'; import { LogService } from 'common/services/LogService'; import { BufferService, MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/BufferService'; import { OptionsService } from 'common/services/OptionsService'; -import { ITerminalOptions, IDisposable, IBufferLine, IAttributeData, ICoreTerminal } from 'common/Types'; +import { ITerminalOptions, IDisposable, IBufferLine, IAttributeData, ICoreTerminal, IKeyboardEvent, IScrollEvent, ScrollSource } from 'common/Types'; import { CoreService } from 'common/services/CoreService'; import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter'; import { CoreMouseService } from 'common/services/CoreMouseService'; @@ -40,6 +40,9 @@ import { IBufferSet } from 'common/buffer/Types'; import { InputHandler } from 'common/InputHandler'; import { WriteBuffer } from 'common/input/WriteBuffer'; +// Only trigger this warning a single time per session +let hasWriteSyncWarnHappened = false; + export abstract class CoreTerminal extends Disposable implements ICoreTerminal { protected readonly _instantiationService: IInstantiationService; protected readonly _bufferService: IBufferService; @@ -55,8 +58,6 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { protected _inputHandler: InputHandler; private _writeBuffer: WriteBuffer; private _windowsMode: IDisposable | undefined; - /** An IBufferline to clone/copy from for new blank lines */ - private _cachedBlankLine: IBufferLine | undefined; private _onBinary = new EventEmitter(); public get onBinary(): IEvent { return this._onBinary.event; } @@ -66,8 +67,21 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { public get onLineFeed(): IEvent { return this._onLineFeed.event; } private _onResize = new EventEmitter<{ cols: number, rows: number }>(); public get onResize(): IEvent<{ cols: number, rows: number }> { return this._onResize.event; } - protected _onScroll = new EventEmitter(); - public get onScroll(): IEvent { return this._onScroll.event; } + protected _onScroll = new EventEmitter(); + /** + * Internally we track the source of the scroll but this is meaningless outside the library so + * it's filtered out. + */ + protected _onScrollApi?: EventEmitter; + public get onScroll(): IEvent { + if (!this._onScrollApi) { + this._onScrollApi = new EventEmitter(); + this.register(this._onScroll.event(ev => { + this._onScrollApi?.fire(ev.position); + })); + } + return this._onScrollApi.event; + } public get cols(): number { return this._bufferService.cols; } public get rows(): number { return this._bufferService.rows; } @@ -107,9 +121,17 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { this.register(forwardEvent(this._coreService.onData, this._onData)); this.register(forwardEvent(this._coreService.onBinary, this._onBinary)); this.register(this.optionsService.onOptionChange(key => this._updateOptions(key))); + this.register(this._bufferService.onScroll(event => { + this._onScroll.fire({ position: this._bufferService.buffer.ydisp, source: ScrollSource.TERMINAL }); + this._dirtyRowService.markRangeDirty(this._bufferService.buffer.scrollTop, this._bufferService.buffer.scrollBottom); + })); + this.register(this._inputHandler.onScroll(event => { + this._onScroll.fire({ position: this._bufferService.buffer.ydisp, source: ScrollSource.TERMINAL }); + this._dirtyRowService.markRangeDirty(this._bufferService.buffer.scrollTop, this._bufferService.buffer.scrollBottom); + })); // Setup WriteBuffer - this._writeBuffer = new WriteBuffer(data => this._inputHandler.parse(data)); + this._writeBuffer = new WriteBuffer((data, promiseResult) => this._inputHandler.parse(data, promiseResult)); } public dispose(): void { @@ -125,8 +147,21 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { this._writeBuffer.write(data, callback); } - public writeSync(data: string | Uint8Array): void { - this._writeBuffer.writeSync(data); + /** + * Write data to terminal synchonously. + * + * This method is unreliable with async parser handlers, thus should not + * be used anymore. If you need blocking semantics on data input consider + * `write` with a callback instead. + * + * @deprecated Unreliable, will be removed soon. + */ + public writeSync(data: string | Uint8Array, maxSubsequentCalls?: number): void { + if (this._logService.logLevel <= LogLevelEnum.WARN && !hasWriteSyncWarnHappened) { + this._logService.warn('writeSync is unreliable and will be removed soon.'); + hasWriteSyncWarnHappened = true; + } + this._writeBuffer.writeSync(data, maxSubsequentCalls); } public resize(x: number, y: number): void { @@ -145,66 +180,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { * @param isWrapped Whether the new line is wrapped from the previous line. */ public scroll(eraseAttr: IAttributeData, isWrapped: boolean = false): void { - const buffer = this._bufferService.buffer; - - let newLine: IBufferLine | undefined; - newLine = this._cachedBlankLine; - if (!newLine || newLine.length !== this.cols || newLine.getFg(0) !== eraseAttr.fg || newLine.getBg(0) !== eraseAttr.bg) { - newLine = buffer.getBlankLine(eraseAttr, isWrapped); - this._cachedBlankLine = newLine; - } - newLine.isWrapped = isWrapped; - - const topRow = buffer.ybase + buffer.scrollTop; - const bottomRow = buffer.ybase + buffer.scrollBottom; - - if (buffer.scrollTop === 0) { - // Determine whether the buffer is going to be trimmed after insertion. - const willBufferBeTrimmed = buffer.lines.isFull; - - // Insert the line using the fastest method - if (bottomRow === buffer.lines.length - 1) { - if (willBufferBeTrimmed) { - buffer.lines.recycle().copyFrom(newLine); - } else { - buffer.lines.push(newLine.clone()); - } - } else { - buffer.lines.splice(bottomRow + 1, 0, newLine.clone()); - } - - // Only adjust ybase and ydisp when the buffer is not trimmed - if (!willBufferBeTrimmed) { - buffer.ybase++; - // Only scroll the ydisp with ybase if the user has not scrolled up - if (!this._bufferService.isUserScrolling) { - buffer.ydisp++; - } - } else { - // When the buffer is full and the user has scrolled up, keep the text - // stable unless ydisp is right at the top - if (this._bufferService.isUserScrolling) { - buffer.ydisp = Math.max(buffer.ydisp - 1, 0); - } - } - } else { - // scrollTop is non-zero which means no line will be going to the - // scrollback, instead we can just shift them in-place. - const scrollRegionHeight = bottomRow - topRow + 1 /* as it's zero-based */; - buffer.lines.shiftElements(topRow + 1, scrollRegionHeight - 1, -1); - buffer.lines.set(bottomRow, newLine.clone()); - } - - // Move the viewport to the bottom of the buffer unless the user is - // scrolling. - if (!this._bufferService.isUserScrolling) { - buffer.ydisp = buffer.ybase; - } - - // Flag rows that need updating - this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom); - - this._onScroll.fire(buffer.ydisp); + this._bufferService.scroll(eraseAttr, isWrapped); } /** @@ -214,28 +190,8 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { * to avoid unwanted events being handled by the viewport when the event was triggered from the * viewport originally. */ - public scrollLines(disp: number, suppressScrollEvent?: boolean): void { - const buffer = this._bufferService.buffer; - if (disp < 0) { - if (buffer.ydisp === 0) { - return; - } - this._bufferService.isUserScrolling = true; - } else if (disp + buffer.ydisp >= buffer.ybase) { - this._bufferService.isUserScrolling = false; - } - - const oldYdisp = buffer.ydisp; - buffer.ydisp = Math.max(Math.min(buffer.ydisp + disp, buffer.ybase), 0); - - // No change occurred, don't trigger scroll/refresh - if (oldYdisp === buffer.ydisp) { - return; - } - - if (!suppressScrollEvent) { - this._onScroll.fire(buffer.ydisp); - } + public scrollLines(disp: number, suppressScrollEvent?: boolean, source?: ScrollSource): void { + this._bufferService.scrollLines(disp, suppressScrollEvent, source); } /** @@ -243,48 +199,45 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { * @param pageCount The number of pages to scroll (negative scrolls up). */ public scrollPages(pageCount: number): void { - this.scrollLines(pageCount * (this.rows - 1)); + this._bufferService.scrollPages(pageCount); } /** * Scrolls the display of the terminal to the top. */ public scrollToTop(): void { - this.scrollLines(-this._bufferService.buffer.ydisp); + this._bufferService.scrollToTop(); } /** * Scrolls the display of the terminal to the bottom. */ public scrollToBottom(): void { - this.scrollLines(this._bufferService.buffer.ybase - this._bufferService.buffer.ydisp); + this._bufferService.scrollToBottom(); } public scrollToLine(line: number): void { - const scrollAmount = line - this._bufferService.buffer.ydisp; - if (scrollAmount !== 0) { - this.scrollLines(scrollAmount); - } + this._bufferService.scrollToLine(line); } /** Add handler for ESC escape sequence. See xterm.d.ts for details. */ - public addEscHandler(id: IFunctionIdentifier, callback: () => boolean): IDisposable { - return this._inputHandler.addEscHandler(id, callback); + public registerEscHandler(id: IFunctionIdentifier, callback: () => boolean | Promise): IDisposable { + return this._inputHandler.registerEscHandler(id, callback); } /** Add handler for DCS escape sequence. See xterm.d.ts for details. */ - public addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean): IDisposable { - return this._inputHandler.addDcsHandler(id, callback); + public registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean | Promise): IDisposable { + return this._inputHandler.registerDcsHandler(id, callback); } /** Add handler for CSI escape sequence. See xterm.d.ts for details. */ - public addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable { - return this._inputHandler.addCsiHandler(id, callback); + public registerCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean | Promise): IDisposable { + return this._inputHandler.registerCsiHandler(id, callback); } /** Add handler for OSC escape sequence. See xterm.d.ts for details. */ - public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable { - return this._inputHandler.addOscHandler(ident, callback); + public registerOscHandler(ident: number, callback: (data: string) => boolean | Promise): IDisposable { + return this._inputHandler.registerOscHandler(ident, callback); } protected _setup(): void { @@ -322,7 +275,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { if (!this._windowsMode) { const disposables: IDisposable[] = []; disposables.push(this.onLineFeed(updateWindowsModeWrappedState.bind(null, this._bufferService))); - disposables.push(this.addCsiHandler({ final: 'H' }, () => { + disposables.push(this.registerCsiHandler({ final: 'H' }, () => { updateWindowsModeWrappedState(this._bufferService); return false; })); diff --git a/src/common/InputHandler.test.ts b/src/common/InputHandler.test.ts index 46f8eacc..92162d9a 100644 --- a/src/common/InputHandler.test.ts +++ b/src/common/InputHandler.test.ts @@ -3,9 +3,9 @@ * @license MIT */ -import { assert, expect } from 'chai'; +import { assert } from 'chai'; import { InputHandler } from 'common/InputHandler'; -import { IBufferLine, IAttributeData } from 'common/Types'; +import { IBufferLine, IAttributeData, IAnsiColorChangeEvent } from 'common/Types'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { CellData } from 'common/buffer/CellData'; import { Attributes, UnderlineStyle } from 'common/buffer/Constants'; @@ -17,6 +17,7 @@ import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; import { clone } from 'common/Clone'; import { BufferService } from 'common/services/BufferService'; import { CoreService } from 'common/services/CoreService'; +import { OscHandler } from 'common/parser/OscParser'; function getCursor(bufferService: IBufferService): number[] { return [ @@ -40,6 +41,19 @@ class TestInputHandler extends InputHandler { public get curAttrData(): IAttributeData { return (this as any)._curAttrData; } public get windowTitleStack(): string[] { return this._windowTitleStack; } public get iconNameStack(): string[] { return this._iconNameStack; } + public parseAnsiColorChange(data: string): IAnsiColorChangeEvent | null { return this._parseAnsiColorChange(data); } + + /** + * Promise based parse call to await the full resolve of given input data. + * This is useful to test async handlers in inputhandler directly. + */ + public async parseP(data: string | Uint8Array): Promise { + let result: Promise | void; + let prev: boolean | undefined; + while (result = this.parse(data, prev)) { + prev = await result; + } + } } describe('InputHandler', () => { @@ -52,11 +66,117 @@ describe('InputHandler', () => { optionsService = new MockOptionsService(); bufferService = new BufferService(optionsService); bufferService.resize(80, 30); - coreService = new CoreService(() => {}, bufferService, new MockLogService(), optionsService); + coreService = new CoreService(() => { }, bufferService, new MockLogService(), optionsService); inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockDirtyRowService(), new MockLogService(), optionsService, new MockCoreMouseService(), new MockUnicodeService()); }); + describe('SL/SR/DECIC/DECDC', () => { + beforeEach(() => { + bufferService.resize(5, 5); + optionsService.options.scrollback = 1; + bufferService.reset(); + }); + it('SL (scrollLeft)', () => { + inputHandler.parseP('12345'.repeat(6)); + inputHandler.parseP('\x1b[ @'); + assert.deepEqual(getLines(bufferService, 6), ['12345', '2345', '2345', '2345', '2345', '2345']); + inputHandler.parseP('\x1b[0 @'); + assert.deepEqual(getLines(bufferService, 6), ['12345', '345', '345', '345', '345', '345']); + inputHandler.parseP('\x1b[2 @'); + assert.deepEqual(getLines(bufferService, 6), ['12345', '5', '5', '5', '5', '5']); + }); + it('SR (scrollRight)', () => { + inputHandler.parseP('12345'.repeat(6)); + inputHandler.parseP('\x1b[ A'); + assert.deepEqual(getLines(bufferService, 6), ['12345', ' 1234', ' 1234', ' 1234', ' 1234', ' 1234']); + inputHandler.parseP('\x1b[0 A'); + assert.deepEqual(getLines(bufferService, 6), ['12345', ' 123', ' 123', ' 123', ' 123', ' 123']); + inputHandler.parseP('\x1b[2 A'); + assert.deepEqual(getLines(bufferService, 6), ['12345', ' 1', ' 1', ' 1', ' 1', ' 1']); + }); + it('insertColumns (DECIC)', () => { + inputHandler.parseP('12345'.repeat(6)); + inputHandler.parseP('\x1b[3;3H'); + inputHandler.parseP('\x1b[\'}'); + assert.deepEqual(getLines(bufferService, 6), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']); + bufferService.reset(); + inputHandler.parseP('12345'.repeat(6)); + inputHandler.parseP('\x1b[3;3H'); + inputHandler.parseP('\x1b[1\'}'); + assert.deepEqual(getLines(bufferService, 6), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']); + bufferService.reset(); + inputHandler.parseP('12345'.repeat(6)); + inputHandler.parseP('\x1b[3;3H'); + inputHandler.parseP('\x1b[2\'}'); + assert.deepEqual(getLines(bufferService, 6), ['12345', '12 3', '12 3', '12 3', '12 3', '12 3']); + }); + it('deleteColumns (DECDC)', () => { + inputHandler.parseP('12345'.repeat(6)); + inputHandler.parseP('\x1b[3;3H'); + inputHandler.parseP('\x1b[\'~'); + assert.deepEqual(getLines(bufferService, 6), ['12345', '1245', '1245', '1245', '1245', '1245']); + bufferService.reset(); + inputHandler.parseP('12345'.repeat(6)); + inputHandler.parseP('\x1b[3;3H'); + inputHandler.parseP('\x1b[1\'~'); + assert.deepEqual(getLines(bufferService, 6), ['12345', '1245', '1245', '1245', '1245', '1245']); + bufferService.reset(); + inputHandler.parseP('12345'.repeat(6)); + inputHandler.parseP('\x1b[3;3H'); + inputHandler.parseP('\x1b[2\'~'); + assert.deepEqual(getLines(bufferService, 6), ['12345', '125', '125', '125', '125', '125']); + }); + }); + + describe('BS with reverseWraparound set/unset', () => { + const ttyBS = '\x08 \x08'; // tty ICANON sends on pressing BS + beforeEach(() => { + bufferService.resize(5, 5); + optionsService.options.scrollback = 1; + bufferService.reset(); + }); + describe('reverseWraparound set', () => { + it('should not reverse outside of scroll margins', () => { + // prepare buffer content + inputHandler.parseP('#####abcdefghijklmnopqrstuvwxy'); + assert.deepEqual(getLines(bufferService, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', 'uvwxy']); + assert.equal(bufferService.buffers.active.ydisp, 1); + assert.equal(bufferService.buffers.active.x, 5); + assert.equal(bufferService.buffers.active.y, 4); + inputHandler.parseP(ttyBS.repeat(100)); + assert.deepEqual(getLines(bufferService, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', ' y']); + + inputHandler.parseP('\x1b[?45h'); + inputHandler.parseP('uvwxy'); + + // set top/bottom to 1/3 (0-based) + inputHandler.parseP('\x1b[2;4r'); + // place cursor below scroll bottom + bufferService.buffers.active.x = 5; + bufferService.buffers.active.y = 4; + inputHandler.parseP(ttyBS.repeat(100)); + assert.deepEqual(getLines(bufferService, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', ' ']); + + inputHandler.parseP('uvwxy'); + // place cursor within scroll margins + bufferService.buffers.active.x = 5; + bufferService.buffers.active.y = 3; + inputHandler.parseP(ttyBS.repeat(100)); + assert.deepEqual(getLines(bufferService, 6), ['#####', 'abcde', ' ', ' ', ' ', 'uvwxy']); + assert.equal(bufferService.buffers.active.x, 0); + assert.equal(bufferService.buffers.active.y, bufferService.buffers.active.scrollTop); // stops at 0, scrollTop + + inputHandler.parseP('fghijklmnopqrst'); + // place cursor above scroll top + bufferService.buffers.active.x = 5; + bufferService.buffers.active.y = 0; + inputHandler.parseP(ttyBS.repeat(100)); + assert.deepEqual(getLines(bufferService, 6), ['#####', ' ', 'fghij', 'klmno', 'pqrst', 'uvwxy']); + }); + }); + }); + it('save and restore cursor', () => { bufferService.buffer.x = 1; bufferService.buffer.y = 2; @@ -117,7 +237,7 @@ describe('InputHandler', () => { describe('setMode', () => { it('should toggle bracketedPasteMode', () => { const coreService = new MockCoreService(); - const inputHandler = new InputHandler(new MockBufferService(80, 30), new MockCharsetService(), coreService, new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); + const inputHandler = new TestInputHandler(new MockBufferService(80, 30), new MockCharsetService(), coreService, new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); // Set bracketed paste mode inputHandler.setModePrivate(Params.fromArray([2004])); assert.equal(coreService.decPrivateModes.bracketedPasteMode, true); @@ -126,130 +246,167 @@ describe('InputHandler', () => { assert.equal(coreService.decPrivateModes.bracketedPasteMode, false); }); }); - describe('regression tests', function(): void { + describe('regression tests', function (): void { function termContent(bufferService: IBufferService, trim: boolean): string[] { const result = []; for (let i = 0; i < bufferService.rows; ++i) result.push(bufferService.buffer.lines.get(i)!.translateToString(trim)); return result; } - it('insertChars', function(): void { + it('insertChars', async () => { const bufferService = new MockBufferService(80, 30); - const inputHandler = new InputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); + const inputHandler = new TestInputHandler( + bufferService, + new MockCharsetService(), + new MockCoreService(), + new MockDirtyRowService(), + new MockLogService(), + new MockOptionsService(), + new MockCoreMouseService(), + new MockUnicodeService() + ); // insert some data in first and second line - inputHandler.parse(Array(bufferService.cols - 9).join('a')); - inputHandler.parse('1234567890'); - inputHandler.parse(Array(bufferService.cols - 9).join('a')); - inputHandler.parse('1234567890'); + await inputHandler.parseP(Array(bufferService.cols - 9).join('a')); + await inputHandler.parseP('1234567890'); + await inputHandler.parseP(Array(bufferService.cols - 9).join('a')); + await inputHandler.parseP('1234567890'); const line1: IBufferLine = bufferService.buffer.lines.get(0)!; - expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + '1234567890'); + assert.equal(line1.translateToString(false), Array(bufferService.cols - 9).join('a') + '1234567890'); // insert one char from params = [0] bufferService.buffer.y = 0; bufferService.buffer.x = 70; inputHandler.insertChars(Params.fromArray([0])); - expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + ' 123456789'); + assert.equal(line1.translateToString(false), Array(bufferService.cols - 9).join('a') + ' 123456789'); // insert one char from params = [1] bufferService.buffer.y = 0; bufferService.buffer.x = 70; inputHandler.insertChars(Params.fromArray([1])); - expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + ' 12345678'); + assert.equal(line1.translateToString(false), Array(bufferService.cols - 9).join('a') + ' 12345678'); // insert two chars from params = [2] bufferService.buffer.y = 0; bufferService.buffer.x = 70; inputHandler.insertChars(Params.fromArray([2])); - expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + ' 123456'); + assert.equal(line1.translateToString(false), Array(bufferService.cols - 9).join('a') + ' 123456'); // insert 10 chars from params = [10] bufferService.buffer.y = 0; bufferService.buffer.x = 70; inputHandler.insertChars(Params.fromArray([10])); - expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + ' '); - expect(line1.translateToString(true)).equals(Array(bufferService.cols - 9).join('a')); + assert.equal(line1.translateToString(false), Array(bufferService.cols - 9).join('a') + ' '); + assert.equal(line1.translateToString(true), Array(bufferService.cols - 9).join('a')); }); - it('deleteChars', function(): void { + it('deleteChars', async () => { const bufferService = new MockBufferService(80, 30); - const inputHandler = new InputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); + const inputHandler = new TestInputHandler( + bufferService, + new MockCharsetService(), + new MockCoreService(), + new MockDirtyRowService(), + new MockLogService(), + new MockOptionsService(), + new MockCoreMouseService(), + new MockUnicodeService() + ); // insert some data in first and second line - inputHandler.parse(Array(bufferService.cols - 9).join('a')); - inputHandler.parse('1234567890'); - inputHandler.parse(Array(bufferService.cols - 9).join('a')); - inputHandler.parse('1234567890'); + await inputHandler.parseP(Array(bufferService.cols - 9).join('a')); + await inputHandler.parseP('1234567890'); + await inputHandler.parseP(Array(bufferService.cols - 9).join('a')); + await inputHandler.parseP('1234567890'); const line1: IBufferLine = bufferService.buffer.lines.get(0)!; - expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + '1234567890'); + assert.equal(line1.translateToString(false), Array(bufferService.cols - 9).join('a') + '1234567890'); // delete one char from params = [0] bufferService.buffer.y = 0; bufferService.buffer.x = 70; inputHandler.deleteChars(Params.fromArray([0])); - expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + '234567890 '); - expect(line1.translateToString(true)).equals(Array(bufferService.cols - 9).join('a') + '234567890'); + assert.equal(line1.translateToString(false), Array(bufferService.cols - 9).join('a') + '234567890 '); + assert.equal(line1.translateToString(true), Array(bufferService.cols - 9).join('a') + '234567890'); // insert one char from params = [1] bufferService.buffer.y = 0; bufferService.buffer.x = 70; inputHandler.deleteChars(Params.fromArray([1])); - expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + '34567890 '); - expect(line1.translateToString(true)).equals(Array(bufferService.cols - 9).join('a') + '34567890'); + assert.equal(line1.translateToString(false), Array(bufferService.cols - 9).join('a') + '34567890 '); + assert.equal(line1.translateToString(true), Array(bufferService.cols - 9).join('a') + '34567890'); // insert two chars from params = [2] bufferService.buffer.y = 0; bufferService.buffer.x = 70; inputHandler.deleteChars(Params.fromArray([2])); - expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + '567890 '); - expect(line1.translateToString(true)).equals(Array(bufferService.cols - 9).join('a') + '567890'); + assert.equal(line1.translateToString(false), Array(bufferService.cols - 9).join('a') + '567890 '); + assert.equal(line1.translateToString(true), Array(bufferService.cols - 9).join('a') + '567890'); + // insert 10 chars from params = [10] bufferService.buffer.y = 0; bufferService.buffer.x = 70; inputHandler.deleteChars(Params.fromArray([10])); - expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + ' '); - expect(line1.translateToString(true)).equals(Array(bufferService.cols - 9).join('a')); + assert.equal(line1.translateToString(false), Array(bufferService.cols - 9).join('a') + ' '); + assert.equal(line1.translateToString(true), Array(bufferService.cols - 9).join('a')); }); - it('eraseInLine', function(): void { + it('eraseInLine', async () => { const bufferService = new MockBufferService(80, 30); - const inputHandler = new InputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); + const inputHandler = new TestInputHandler( + bufferService, + new MockCharsetService(), + new MockCoreService(), + new MockDirtyRowService(), + new MockLogService(), + new MockOptionsService(), + new MockCoreMouseService(), + new MockUnicodeService() + ); // fill 6 lines to test 3 different states - inputHandler.parse(Array(bufferService.cols + 1).join('a')); - inputHandler.parse(Array(bufferService.cols + 1).join('a')); - inputHandler.parse(Array(bufferService.cols + 1).join('a')); + await inputHandler.parseP(Array(bufferService.cols + 1).join('a')); + await inputHandler.parseP(Array(bufferService.cols + 1).join('a')); + await inputHandler.parseP(Array(bufferService.cols + 1).join('a')); // params[0] - right erase bufferService.buffer.y = 0; bufferService.buffer.x = 70; inputHandler.eraseInLine(Params.fromArray([0])); - expect(bufferService.buffer.lines.get(0)!.translateToString(false)).equals(Array(71).join('a') + ' '); + assert.equal(bufferService.buffer.lines.get(0)!.translateToString(false), Array(71).join('a') + ' '); // params[1] - left erase bufferService.buffer.y = 1; bufferService.buffer.x = 70; inputHandler.eraseInLine(Params.fromArray([1])); - expect(bufferService.buffer.lines.get(1)!.translateToString(false)).equals(Array(71).join(' ') + ' aaaaaaaaa'); + assert.equal(bufferService.buffer.lines.get(1)!.translateToString(false), Array(71).join(' ') + ' aaaaaaaaa'); // params[1] - left erase bufferService.buffer.y = 2; bufferService.buffer.x = 70; inputHandler.eraseInLine(Params.fromArray([2])); - expect(bufferService.buffer.lines.get(2)!.translateToString(false)).equals(Array(bufferService.cols + 1).join(' ')); + assert.equal(bufferService.buffer.lines.get(2)!.translateToString(false), Array(bufferService.cols + 1).join(' ')); }); - it('eraseInDisplay', function(): void { + it('eraseInDisplay', async () => { const bufferService = new MockBufferService(80, 7); - const inputHandler = new InputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); + const inputHandler = new TestInputHandler( + bufferService, + new MockCharsetService(), + new MockCoreService(), + new MockDirtyRowService(), + new MockLogService(), + new MockOptionsService(), + new MockCoreMouseService(), + new MockUnicodeService() + ); // fill display with a's - for (let i = 0; i < bufferService.rows; ++i) inputHandler.parse(Array(bufferService.cols + 1).join('a')); + for (let i = 0; i < bufferService.rows; ++i) await inputHandler.parseP(Array(bufferService.cols + 1).join('a')); // params [0] - right and below erase bufferService.buffer.y = 5; bufferService.buffer.x = 40; inputHandler.eraseInDisplay(Params.fromArray([0])); - expect(termContent(bufferService, false)).eql([ + assert.deepEqual(termContent(bufferService, false), [ Array(bufferService.cols + 1).join('a'), Array(bufferService.cols + 1).join('a'), Array(bufferService.cols + 1).join('a'), @@ -258,7 +415,7 @@ describe('InputHandler', () => { Array(40 + 1).join('a') + Array(bufferService.cols - 40 + 1).join(' '), Array(bufferService.cols + 1).join(' ') ]); - expect(termContent(bufferService, true)).eql([ + assert.deepEqual(termContent(bufferService, true), [ Array(bufferService.cols + 1).join('a'), Array(bufferService.cols + 1).join('a'), Array(bufferService.cols + 1).join('a'), @@ -271,13 +428,13 @@ describe('InputHandler', () => { // reset bufferService.buffer.y = 0; bufferService.buffer.x = 0; - for (let i = 0; i < bufferService.rows; ++i) inputHandler.parse(Array(bufferService.cols + 1).join('a')); + for (let i = 0; i < bufferService.rows; ++i) await inputHandler.parseP(Array(bufferService.cols + 1).join('a')); // params [1] - left and above bufferService.buffer.y = 5; bufferService.buffer.x = 40; inputHandler.eraseInDisplay(Params.fromArray([1])); - expect(termContent(bufferService, false)).eql([ + assert.deepEqual(termContent(bufferService, false), [ Array(bufferService.cols + 1).join(' '), Array(bufferService.cols + 1).join(' '), Array(bufferService.cols + 1).join(' '), @@ -286,7 +443,7 @@ describe('InputHandler', () => { Array(41 + 1).join(' ') + Array(bufferService.cols - 41 + 1).join('a'), Array(bufferService.cols + 1).join('a') ]); - expect(termContent(bufferService, true)).eql([ + assert.deepEqual(termContent(bufferService, true), [ '', '', '', @@ -299,13 +456,13 @@ describe('InputHandler', () => { // reset bufferService.buffer.y = 0; bufferService.buffer.x = 0; - for (let i = 0; i < bufferService.rows; ++i) inputHandler.parse(Array(bufferService.cols + 1).join('a')); + for (let i = 0; i < bufferService.rows; ++i) await inputHandler.parseP(Array(bufferService.cols + 1).join('a')); // params [2] - whole screen bufferService.buffer.y = 5; bufferService.buffer.x = 40; inputHandler.eraseInDisplay(Params.fromArray([2])); - expect(termContent(bufferService, false)).eql([ + assert.deepEqual(termContent(bufferService, false), [ Array(bufferService.cols + 1).join(' '), Array(bufferService.cols + 1).join(' '), Array(bufferService.cols + 1).join(' '), @@ -314,7 +471,7 @@ describe('InputHandler', () => { Array(bufferService.cols + 1).join(' '), Array(bufferService.cols + 1).join(' ') ]); - expect(termContent(bufferService, true)).eql([ + assert.deepEqual(termContent(bufferService, true), [ '', '', '', @@ -327,242 +484,257 @@ describe('InputHandler', () => { // reset and add a wrapped line bufferService.buffer.y = 0; bufferService.buffer.x = 0; - inputHandler.parse(Array(bufferService.cols + 1).join('a')); // line 0 - inputHandler.parse(Array(bufferService.cols + 10).join('a')); // line 1 and 2 - for (let i = 3; i < bufferService.rows; ++i) inputHandler.parse(Array(bufferService.cols + 1).join('a')); + await inputHandler.parseP(Array(bufferService.cols + 1).join('a')); // line 0 + await inputHandler.parseP(Array(bufferService.cols + 10).join('a')); // line 1 and 2 + for (let i = 3; i < bufferService.rows; ++i) await inputHandler.parseP(Array(bufferService.cols + 1).join('a')); // params[1] left and above with wrap // confirm precondition that line 2 is wrapped - expect(bufferService.buffer.lines.get(2)!.isWrapped).true; + assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, true); bufferService.buffer.y = 2; bufferService.buffer.x = 40; inputHandler.eraseInDisplay(Params.fromArray([1])); - expect(bufferService.buffer.lines.get(2)!.isWrapped).false; + assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, false); // reset and add a wrapped line bufferService.buffer.y = 0; bufferService.buffer.x = 0; - inputHandler.parse(Array(bufferService.cols + 1).join('a')); // line 0 - inputHandler.parse(Array(bufferService.cols + 10).join('a')); // line 1 and 2 - for (let i = 3; i < bufferService.rows; ++i) inputHandler.parse(Array(bufferService.cols + 1).join('a')); + await inputHandler.parseP(Array(bufferService.cols + 1).join('a')); // line 0 + await inputHandler.parseP(Array(bufferService.cols + 10).join('a')); // line 1 and 2 + for (let i = 3; i < bufferService.rows; ++i) await inputHandler.parseP(Array(bufferService.cols + 1).join('a')); // params[1] left and above with wrap // confirm precondition that line 2 is wrapped - expect(bufferService.buffer.lines.get(2)!.isWrapped).true; + assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, true); bufferService.buffer.y = 1; bufferService.buffer.x = 90; // Cursor is beyond last column inputHandler.eraseInDisplay(Params.fromArray([1])); - expect(bufferService.buffer.lines.get(2)!.isWrapped).false; + assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, false); }); }); describe('print', () => { it('should not cause an infinite loop (regression test)', () => { - const inputHandler = new InputHandler(new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); + const inputHandler = new TestInputHandler( + new MockBufferService(80, 30), + new MockCharsetService(), + new MockCoreService(), + new MockDirtyRowService(), + new MockLogService(), + new MockOptionsService(), + new MockCoreMouseService(), + new MockUnicodeService() + ); const container = new Uint32Array(10); container[0] = 0x200B; inputHandler.print(container, 0, 1); }); - it('should clear cells to the right on early wrap-around', () => { + it('should clear cells to the right on early wrap-around', async () => { bufferService.resize(5, 5); optionsService.options.scrollback = 1; - inputHandler.parse('12345'); + await inputHandler.parseP('12345'); bufferService.buffer.x = 0; - inputHandler.parse('¥¥¥'); + await inputHandler.parseP('¥¥¥'); assert.deepEqual(getLines(bufferService, 2), ['¥¥', '¥']); }); }); describe('alt screen', () => { let bufferService: IBufferService; - let handler: InputHandler; + let handler: TestInputHandler; beforeEach(() => { bufferService = new MockBufferService(80, 30); - handler = new InputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); + handler = new TestInputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); }); - it('should handle DECSET/DECRST 47 (alt screen buffer)', () => { - handler.parse('\x1b[?47h\r\n\x1b[31mJUNK\x1b[?47lTEST'); - expect(bufferService.buffer.translateBufferLineToString(0, true)).to.equal(''); - expect(bufferService.buffer.translateBufferLineToString(1, true)).to.equal(' TEST'); + it('should handle DECSET/DECRST 47 (alt screen buffer)', async () => { + await handler.parseP('\x1b[?47h\r\n\x1b[31mJUNK\x1b[?47lTEST'); + assert.equal(bufferService.buffer.translateBufferLineToString(0, true), ''); + assert.equal(bufferService.buffer.translateBufferLineToString(1, true), ' TEST'); // Text color of 'TEST' should be red - expect((bufferService.buffer.lines.get(1)!.loadCell(4, new CellData()).getFgColor())).to.equal(1); + assert.equal((bufferService.buffer.lines.get(1)!.loadCell(4, new CellData()).getFgColor()), 1); }); - it('should handle DECSET/DECRST 1047 (alt screen buffer)', () => { - handler.parse('\x1b[?1047h\r\n\x1b[31mJUNK\x1b[?1047lTEST'); - expect(bufferService.buffer.translateBufferLineToString(0, true)).to.equal(''); - expect(bufferService.buffer.translateBufferLineToString(1, true)).to.equal(' TEST'); + it('should handle DECSET/DECRST 1047 (alt screen buffer)', async () => { + await handler.parseP('\x1b[?1047h\r\n\x1b[31mJUNK\x1b[?1047lTEST'); + assert.equal(bufferService.buffer.translateBufferLineToString(0, true), ''); + assert.equal(bufferService.buffer.translateBufferLineToString(1, true), ' TEST'); // Text color of 'TEST' should be red - expect((bufferService.buffer.lines.get(1)!.loadCell(4, new CellData()).getFgColor())).to.equal(1); + assert.equal((bufferService.buffer.lines.get(1)!.loadCell(4, new CellData()).getFgColor()), 1); }); - it('should handle DECSET/DECRST 1048 (alt screen cursor)', () => { - handler.parse('\x1b[?1048h\r\n\x1b[31mJUNK\x1b[?1048lTEST'); - expect(bufferService.buffer.translateBufferLineToString(0, true)).to.equal('TEST'); - expect(bufferService.buffer.translateBufferLineToString(1, true)).to.equal('JUNK'); + it('should handle DECSET/DECRST 1048 (alt screen cursor)', async () => { + await handler.parseP('\x1b[?1048h\r\n\x1b[31mJUNK\x1b[?1048lTEST'); + assert.equal(bufferService.buffer.translateBufferLineToString(0, true), 'TEST'); + assert.equal(bufferService.buffer.translateBufferLineToString(1, true), 'JUNK'); // Text color of 'TEST' should be default - expect(bufferService.buffer.lines.get(0)!.loadCell(0, new CellData()).fg).to.equal(DEFAULT_ATTR_DATA.fg); + assert.equal(bufferService.buffer.lines.get(0)!.loadCell(0, new CellData()).fg, DEFAULT_ATTR_DATA.fg); // Text color of 'JUNK' should be red - expect((bufferService.buffer.lines.get(1)!.loadCell(0, new CellData()).getFgColor())).to.equal(1); + assert.equal((bufferService.buffer.lines.get(1)!.loadCell(0, new CellData()).getFgColor()), 1); }); - it('should handle DECSET/DECRST 1049 (alt screen buffer+cursor)', () => { - handler.parse('\x1b[?1049h\r\n\x1b[31mJUNK\x1b[?1049lTEST'); - expect(bufferService.buffer.translateBufferLineToString(0, true)).to.equal('TEST'); - expect(bufferService.buffer.translateBufferLineToString(1, true)).to.equal(''); + it('should handle DECSET/DECRST 1049 (alt screen buffer+cursor)', async () => { + await handler.parseP('\x1b[?1049h\r\n\x1b[31mJUNK\x1b[?1049lTEST'); + assert.equal(bufferService.buffer.translateBufferLineToString(0, true), 'TEST'); + assert.equal(bufferService.buffer.translateBufferLineToString(1, true), ''); // Text color of 'TEST' should be default - expect(bufferService.buffer.lines.get(0)!.loadCell(0, new CellData()).fg).to.equal(DEFAULT_ATTR_DATA.fg); + assert.equal(bufferService.buffer.lines.get(0)!.loadCell(0, new CellData()).fg, DEFAULT_ATTR_DATA.fg); }); - it('should handle DECSET/DECRST 1049 - maintains saved cursor for alt buffer', () => { - handler.parse('\x1b[?1049h\r\n\x1b[31m\x1b[s\x1b[?1049lTEST'); - expect(bufferService.buffer.translateBufferLineToString(0, true)).to.equal('TEST'); + it('should handle DECSET/DECRST 1049 - maintains saved cursor for alt buffer', async () => { + await handler.parseP('\x1b[?1049h\r\n\x1b[31m\x1b[s\x1b[?1049lTEST'); + assert.equal(bufferService.buffer.translateBufferLineToString(0, true), 'TEST'); // Text color of 'TEST' should be default - expect(bufferService.buffer.lines.get(0)!.loadCell(0, new CellData()).fg).to.equal(DEFAULT_ATTR_DATA.fg); - handler.parse('\x1b[?1049h\x1b[uTEST'); - expect(bufferService.buffer.translateBufferLineToString(1, true)).to.equal('TEST'); + assert.equal(bufferService.buffer.lines.get(0)!.loadCell(0, new CellData()).fg, DEFAULT_ATTR_DATA.fg); + await handler.parseP('\x1b[?1049h\x1b[uTEST'); + assert.equal(bufferService.buffer.translateBufferLineToString(1, true), 'TEST'); // Text color of 'TEST' should be red - expect((bufferService.buffer.lines.get(1)!.loadCell(0, new CellData()).getFgColor())).to.equal(1); + assert.equal((bufferService.buffer.lines.get(1)!.loadCell(0, new CellData()).getFgColor()), 1); }); - it('should handle DECSET/DECRST 1049 - clears alt buffer with erase attributes', () => { - handler.parse('\x1b[42m\x1b[?1049h'); + it('should handle DECSET/DECRST 1049 - clears alt buffer with erase attributes', async () => { + await handler.parseP('\x1b[42m\x1b[?1049h'); // Buffer should be filled with green background - expect(bufferService.buffer.lines.get(20)!.loadCell(10, new CellData()).getBgColor()).to.equal(2); + assert.equal(bufferService.buffer.lines.get(20)!.loadCell(10, new CellData()).getBgColor(), 2); }); }); describe('text attributes', () => { - it('bold', () => { - inputHandler.parse('\x1b[1m'); + it('bold', async () => { + await inputHandler.parseP('\x1b[1m'); assert.equal(!!inputHandler.curAttrData.isBold(), true); - inputHandler.parse('\x1b[22m'); + await inputHandler.parseP('\x1b[22m'); assert.equal(!!inputHandler.curAttrData.isBold(), false); }); - it('dim', () => { - inputHandler.parse('\x1b[2m'); + it('dim', async () => { + await inputHandler.parseP('\x1b[2m'); assert.equal(!!inputHandler.curAttrData.isDim(), true); - inputHandler.parse('\x1b[22m'); + await inputHandler.parseP('\x1b[22m'); assert.equal(!!inputHandler.curAttrData.isDim(), false); }); - it('italic', () => { - inputHandler.parse('\x1b[3m'); + it('italic', async () => { + await inputHandler.parseP('\x1b[3m'); assert.equal(!!inputHandler.curAttrData.isItalic(), true); - inputHandler.parse('\x1b[23m'); + await inputHandler.parseP('\x1b[23m'); assert.equal(!!inputHandler.curAttrData.isItalic(), false); }); - it('underline', () => { - inputHandler.parse('\x1b[4m'); + it('underline', async () => { + await inputHandler.parseP('\x1b[4m'); assert.equal(!!inputHandler.curAttrData.isUnderline(), true); - inputHandler.parse('\x1b[24m'); + await inputHandler.parseP('\x1b[24m'); assert.equal(!!inputHandler.curAttrData.isUnderline(), false); }); - it('blink', () => { - inputHandler.parse('\x1b[5m'); + it('blink', async () => { + await inputHandler.parseP('\x1b[5m'); assert.equal(!!inputHandler.curAttrData.isBlink(), true); - inputHandler.parse('\x1b[25m'); + await inputHandler.parseP('\x1b[25m'); assert.equal(!!inputHandler.curAttrData.isBlink(), false); }); - it('inverse', () => { - inputHandler.parse('\x1b[7m'); + it('inverse', async () => { + await inputHandler.parseP('\x1b[7m'); assert.equal(!!inputHandler.curAttrData.isInverse(), true); - inputHandler.parse('\x1b[27m'); + await inputHandler.parseP('\x1b[27m'); assert.equal(!!inputHandler.curAttrData.isInverse(), false); }); - it('invisible', () => { - inputHandler.parse('\x1b[8m'); + it('invisible', async () => { + await inputHandler.parseP('\x1b[8m'); assert.equal(!!inputHandler.curAttrData.isInvisible(), true); - inputHandler.parse('\x1b[28m'); + await inputHandler.parseP('\x1b[28m'); assert.equal(!!inputHandler.curAttrData.isInvisible(), false); }); - it('colormode palette 16', () => { + it('strikethrough', async () => { + await inputHandler.parseP('\x1b[9m'); + assert.equal(!!inputHandler.curAttrData.isStrikethrough(), true); + await inputHandler.parseP('\x1b[29m'); + assert.equal(!!inputHandler.curAttrData.isStrikethrough(), false); + }); + it('colormode palette 16', async () => { assert.equal(inputHandler.curAttrData.getFgColorMode(), 0); // DEFAULT assert.equal(inputHandler.curAttrData.getBgColorMode(), 0); // DEFAULT // lower 8 colors for (let i = 0; i < 8; ++i) { - inputHandler.parse(`\x1b[${i + 30};${i + 40}m`); + await inputHandler.parseP(`\x1b[${i + 30};${i + 40}m`); assert.equal(inputHandler.curAttrData.getFgColorMode(), Attributes.CM_P16); assert.equal(inputHandler.curAttrData.getFgColor(), i); assert.equal(inputHandler.curAttrData.getBgColorMode(), Attributes.CM_P16); assert.equal(inputHandler.curAttrData.getBgColor(), i); } // reset to DEFAULT - inputHandler.parse(`\x1b[39;49m`); + await inputHandler.parseP(`\x1b[39;49m`); assert.equal(inputHandler.curAttrData.getFgColorMode(), 0); assert.equal(inputHandler.curAttrData.getBgColorMode(), 0); }); - it('colormode palette 256', () => { + it('colormode palette 256', async () => { assert.equal(inputHandler.curAttrData.getFgColorMode(), 0); // DEFAULT assert.equal(inputHandler.curAttrData.getBgColorMode(), 0); // DEFAULT // lower 8 colors for (let i = 0; i < 256; ++i) { - inputHandler.parse(`\x1b[38;5;${i};48;5;${i}m`); + await inputHandler.parseP(`\x1b[38;5;${i};48;5;${i}m`); assert.equal(inputHandler.curAttrData.getFgColorMode(), Attributes.CM_P256); assert.equal(inputHandler.curAttrData.getFgColor(), i); assert.equal(inputHandler.curAttrData.getBgColorMode(), Attributes.CM_P256); assert.equal(inputHandler.curAttrData.getBgColor(), i); } // reset to DEFAULT - inputHandler.parse(`\x1b[39;49m`); + await inputHandler.parseP(`\x1b[39;49m`); assert.equal(inputHandler.curAttrData.getFgColorMode(), 0); assert.equal(inputHandler.curAttrData.getFgColor(), -1); assert.equal(inputHandler.curAttrData.getBgColorMode(), 0); assert.equal(inputHandler.curAttrData.getBgColor(), -1); }); - it('colormode RGB', () => { + it('colormode RGB', async () => { assert.equal(inputHandler.curAttrData.getFgColorMode(), 0); // DEFAULT assert.equal(inputHandler.curAttrData.getBgColorMode(), 0); // DEFAULT - inputHandler.parse(`\x1b[38;2;1;2;3;48;2;4;5;6m`); + await inputHandler.parseP(`\x1b[38;2;1;2;3;48;2;4;5;6m`); assert.equal(inputHandler.curAttrData.getFgColorMode(), Attributes.CM_RGB); assert.equal(inputHandler.curAttrData.getFgColor(), 1 << 16 | 2 << 8 | 3); assert.deepEqual(AttributeData.toColorRGB(inputHandler.curAttrData.getFgColor()), [1, 2, 3]); assert.equal(inputHandler.curAttrData.getBgColorMode(), Attributes.CM_RGB); assert.deepEqual(AttributeData.toColorRGB(inputHandler.curAttrData.getBgColor()), [4, 5, 6]); // reset to DEFAULT - inputHandler.parse(`\x1b[39;49m`); + await inputHandler.parseP(`\x1b[39;49m`); assert.equal(inputHandler.curAttrData.getFgColorMode(), 0); assert.equal(inputHandler.curAttrData.getFgColor(), -1); assert.equal(inputHandler.curAttrData.getBgColorMode(), 0); assert.equal(inputHandler.curAttrData.getBgColor(), -1); }); - it('colormode transition RGB to 256', () => { + it('colormode transition RGB to 256', async () => { // enter RGB for FG and BG - inputHandler.parse(`\x1b[38;2;1;2;3;48;2;4;5;6m`); + await inputHandler.parseP(`\x1b[38;2;1;2;3;48;2;4;5;6m`); // enter 256 for FG and BG - inputHandler.parse(`\x1b[38;5;255;48;5;255m`); + await inputHandler.parseP(`\x1b[38;5;255;48;5;255m`); assert.equal(inputHandler.curAttrData.getFgColorMode(), Attributes.CM_P256); assert.equal(inputHandler.curAttrData.getFgColor(), 255); assert.equal(inputHandler.curAttrData.getBgColorMode(), Attributes.CM_P256); assert.equal(inputHandler.curAttrData.getBgColor(), 255); }); - it('colormode transition RGB to 16', () => { + it('colormode transition RGB to 16', async () => { // enter RGB for FG and BG - inputHandler.parse(`\x1b[38;2;1;2;3;48;2;4;5;6m`); + await inputHandler.parseP(`\x1b[38;2;1;2;3;48;2;4;5;6m`); // enter 16 for FG and BG - inputHandler.parse(`\x1b[37;47m`); + await inputHandler.parseP(`\x1b[37;47m`); assert.equal(inputHandler.curAttrData.getFgColorMode(), Attributes.CM_P16); assert.equal(inputHandler.curAttrData.getFgColor(), 7); assert.equal(inputHandler.curAttrData.getBgColorMode(), Attributes.CM_P16); assert.equal(inputHandler.curAttrData.getBgColor(), 7); }); - it('colormode transition 16 to 256', () => { + it('colormode transition 16 to 256', async () => { // enter 16 for FG and BG - inputHandler.parse(`\x1b[37;47m`); + await inputHandler.parseP(`\x1b[37;47m`); // enter 256 for FG and BG - inputHandler.parse(`\x1b[38;5;255;48;5;255m`); + await inputHandler.parseP(`\x1b[38;5;255;48;5;255m`); assert.equal(inputHandler.curAttrData.getFgColorMode(), Attributes.CM_P256); assert.equal(inputHandler.curAttrData.getFgColor(), 255); assert.equal(inputHandler.curAttrData.getBgColorMode(), Attributes.CM_P256); assert.equal(inputHandler.curAttrData.getBgColor(), 255); }); - it('colormode transition 256 to 16', () => { + it('colormode transition 256 to 16', async () => { // enter 256 for FG and BG - inputHandler.parse(`\x1b[38;5;255;48;5;255m`); + await inputHandler.parseP(`\x1b[38;5;255;48;5;255m`); // enter 16 for FG and BG - inputHandler.parse(`\x1b[37;47m`); + await inputHandler.parseP(`\x1b[37;47m`); assert.equal(inputHandler.curAttrData.getFgColorMode(), Attributes.CM_P16); assert.equal(inputHandler.curAttrData.getFgColor(), 7); assert.equal(inputHandler.curAttrData.getBgColorMode(), Attributes.CM_P16); assert.equal(inputHandler.curAttrData.getBgColor(), 7); }); - it('should zero missing RGB values', () => { - inputHandler.parse(`\x1b[38;2;1;2;3m`); - inputHandler.parse(`\x1b[38;2;5m`); + it('should zero missing RGB values', async () => { + await inputHandler.parseP(`\x1b[38;2;1;2;3m`); + await inputHandler.parseP(`\x1b[38;2;5m`); assert.deepEqual(AttributeData.toColorRGB(inputHandler.curAttrData.getFgColor()), [5, 0, 0]); }); }); @@ -572,141 +744,141 @@ describe('InputHandler', () => { inputHandler2 = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockDirtyRowService(), new MockLogService(), optionsService, new MockCoreMouseService(), new MockUnicodeService()); }); describe('should equal to semicolon', () => { - it('CSI 38:2::50:100:150 m', () => { + it('CSI 38:2::50:100:150 m', async () => { inputHandler.curAttrData.fg = 0xFFFFFFFF; inputHandler2.curAttrData.fg = 0xFFFFFFFF; - inputHandler2.parse('\x1b[38;2;50;100;150m'); - inputHandler.parse('\x1b[38:2::50:100:150m'); + await inputHandler2.parseP('\x1b[38;2;50;100;150m'); + await inputHandler.parseP('\x1b[38:2::50:100:150m'); assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150); assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); }); - it('CSI 38:2::50:100: m', () => { + it('CSI 38:2::50:100: m', async () => { inputHandler.curAttrData.fg = 0xFFFFFFFF; inputHandler2.curAttrData.fg = 0xFFFFFFFF; - inputHandler2.parse('\x1b[38;2;50;100;m'); - inputHandler.parse('\x1b[38:2::50:100:m'); + await inputHandler2.parseP('\x1b[38;2;50;100;m'); + await inputHandler.parseP('\x1b[38:2::50:100:m'); assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 0); assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); }); - it('CSI 38:2::50:: m', () => { + it('CSI 38:2::50:: m', async () => { inputHandler.curAttrData.fg = 0xFFFFFFFF; inputHandler2.curAttrData.fg = 0xFFFFFFFF; - inputHandler2.parse('\x1b[38;2;50;;m'); - inputHandler.parse('\x1b[38:2::50::m'); + await inputHandler2.parseP('\x1b[38;2;50;;m'); + await inputHandler.parseP('\x1b[38:2::50::m'); assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 0 << 8 | 0); assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); }); - it('CSI 38:2:::: m', () => { + it('CSI 38:2:::: m', async () => { inputHandler.curAttrData.fg = 0xFFFFFFFF; inputHandler2.curAttrData.fg = 0xFFFFFFFF; - inputHandler2.parse('\x1b[38;2;;;m'); - inputHandler.parse('\x1b[38:2::::m'); + await inputHandler2.parseP('\x1b[38;2;;;m'); + await inputHandler.parseP('\x1b[38:2::::m'); assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 0 << 16 | 0 << 8 | 0); assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); }); - it('CSI 38;2::50:100:150 m', () => { + it('CSI 38;2::50:100:150 m', async () => { inputHandler.curAttrData.fg = 0xFFFFFFFF; inputHandler2.curAttrData.fg = 0xFFFFFFFF; - inputHandler2.parse('\x1b[38;2;50;100;150m'); - inputHandler.parse('\x1b[38;2::50:100:150m'); + await inputHandler2.parseP('\x1b[38;2;50;100;150m'); + await inputHandler.parseP('\x1b[38;2::50:100:150m'); assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150); assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); }); - it('CSI 38;2;50:100:150 m', () => { + it('CSI 38;2;50:100:150 m', async () => { inputHandler.curAttrData.fg = 0xFFFFFFFF; inputHandler2.curAttrData.fg = 0xFFFFFFFF; - inputHandler2.parse('\x1b[38;2;50;100;150m'); - inputHandler.parse('\x1b[38;2;50:100:150m'); + await inputHandler2.parseP('\x1b[38;2;50;100;150m'); + await inputHandler.parseP('\x1b[38;2;50:100:150m'); assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150); assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); }); - it('CSI 38;2;50;100:150 m', () => { + it('CSI 38;2;50;100:150 m', async () => { inputHandler.curAttrData.fg = 0xFFFFFFFF; inputHandler2.curAttrData.fg = 0xFFFFFFFF; - inputHandler2.parse('\x1b[38;2;50;100;150m'); - inputHandler.parse('\x1b[38;2;50;100:150m'); + await inputHandler2.parseP('\x1b[38;2;50;100;150m'); + await inputHandler.parseP('\x1b[38;2;50;100:150m'); assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150); assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); }); - it('CSI 38:5:50 m', () => { + it('CSI 38:5:50 m', async () => { inputHandler.curAttrData.fg = 0xFFFFFFFF; inputHandler2.curAttrData.fg = 0xFFFFFFFF; - inputHandler2.parse('\x1b[38;5;50m'); - inputHandler.parse('\x1b[38:5:50m'); + await inputHandler2.parseP('\x1b[38;5;50m'); + await inputHandler.parseP('\x1b[38:5:50m'); assert.equal(inputHandler2.curAttrData.fg & 0xFF, 50); assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); }); - it('CSI 38:5: m', () => { + it('CSI 38:5: m', async () => { inputHandler.curAttrData.fg = 0xFFFFFFFF; inputHandler2.curAttrData.fg = 0xFFFFFFFF; - inputHandler2.parse('\x1b[38;5;m'); - inputHandler.parse('\x1b[38:5:m'); + await inputHandler2.parseP('\x1b[38;5;m'); + await inputHandler.parseP('\x1b[38:5:m'); assert.equal(inputHandler2.curAttrData.fg & 0xFF, 0); assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); }); - it('CSI 38;5:50 m', () => { + it('CSI 38;5:50 m', async () => { inputHandler.curAttrData.fg = 0xFFFFFFFF; inputHandler2.curAttrData.fg = 0xFFFFFFFF; - inputHandler2.parse('\x1b[38;5;50m'); - inputHandler.parse('\x1b[38;5:50m'); + await inputHandler2.parseP('\x1b[38;5;50m'); + await inputHandler.parseP('\x1b[38;5:50m'); assert.equal(inputHandler2.curAttrData.fg & 0xFF, 50); assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); }); }); describe('should fill early sequence end with default of 0', () => { - it('CSI 38:2 m', () => { + it('CSI 38:2 m', async () => { inputHandler.curAttrData.fg = 0xFFFFFFFF; inputHandler2.curAttrData.fg = 0xFFFFFFFF; - inputHandler2.parse('\x1b[38;2m'); - inputHandler.parse('\x1b[38:2m'); + await inputHandler2.parseP('\x1b[38;2m'); + await inputHandler.parseP('\x1b[38:2m'); assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 0 << 16 | 0 << 8 | 0); assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); }); - it('CSI 38:5 m', () => { + it('CSI 38:5 m', async () => { inputHandler.curAttrData.fg = 0xFFFFFFFF; inputHandler2.curAttrData.fg = 0xFFFFFFFF; - inputHandler2.parse('\x1b[38;5m'); - inputHandler.parse('\x1b[38:5m'); + await inputHandler2.parseP('\x1b[38;5m'); + await inputHandler.parseP('\x1b[38:5m'); assert.equal(inputHandler2.curAttrData.fg & 0xFF, 0); assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); }); }); describe('should not interfere with leading/following SGR attrs', () => { - it('CSI 1 ; 38:2::50:100:150 ; 4 m', () => { - inputHandler2.parse('\x1b[1;38;2;50;100;150;4m'); - inputHandler.parse('\x1b[1;38:2::50:100:150;4m'); + it('CSI 1 ; 38:2::50:100:150 ; 4 m', async () => { + await inputHandler2.parseP('\x1b[1;38;2;50;100;150;4m'); + await inputHandler.parseP('\x1b[1;38:2::50:100:150;4m'); assert.equal(!!inputHandler2.curAttrData.isBold(), true); assert.equal(!!inputHandler2.curAttrData.isUnderline(), true); assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150); assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); }); - it('CSI 1 ; 38:2::50:100: ; 4 m', () => { - inputHandler2.parse('\x1b[1;38;2;50;100;;4m'); - inputHandler.parse('\x1b[1;38:2::50:100:;4m'); + it('CSI 1 ; 38:2::50:100: ; 4 m', async () => { + await inputHandler2.parseP('\x1b[1;38;2;50;100;;4m'); + await inputHandler.parseP('\x1b[1;38:2::50:100:;4m'); assert.equal(!!inputHandler2.curAttrData.isBold(), true); assert.equal(!!inputHandler2.curAttrData.isUnderline(), true); assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 0); assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); }); - it('CSI 1 ; 38:2::50:100 ; 4 m', () => { - inputHandler2.parse('\x1b[1;38;2;50;100;;4m'); - inputHandler.parse('\x1b[1;38:2::50:100;4m'); + it('CSI 1 ; 38:2::50:100 ; 4 m', async () => { + await inputHandler2.parseP('\x1b[1;38;2;50;100;;4m'); + await inputHandler.parseP('\x1b[1;38:2::50:100;4m'); assert.equal(!!inputHandler2.curAttrData.isBold(), true); assert.equal(!!inputHandler2.curAttrData.isUnderline(), true); assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 0); assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); }); - it('CSI 1 ; 38:2:: ; 4 m', () => { - inputHandler2.parse('\x1b[1;38;2;;;;4m'); - inputHandler.parse('\x1b[1;38:2::;4m'); + it('CSI 1 ; 38:2:: ; 4 m', async () => { + await inputHandler2.parseP('\x1b[1;38;2;;;;4m'); + await inputHandler.parseP('\x1b[1;38:2::;4m'); assert.equal(!!inputHandler2.curAttrData.isBold(), true); assert.equal(!!inputHandler2.curAttrData.isUnderline(), true); assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 0); assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); }); - it('CSI 1 ; 38;2:: ; 4 m', () => { - inputHandler2.parse('\x1b[1;38;2;;;;4m'); - inputHandler.parse('\x1b[1;38;2::;4m'); + it('CSI 1 ; 38;2:: ; 4 m', async () => { + await inputHandler2.parseP('\x1b[1;38;2;;;;4m'); + await inputHandler.parseP('\x1b[1;38;2::;4m'); assert.equal(!!inputHandler2.curAttrData.isBold(), true); assert.equal(!!inputHandler2.curAttrData.isUnderline(), true); assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 0); @@ -718,372 +890,372 @@ describe('InputHandler', () => { beforeEach(() => { bufferService.resize(10, 10); }); - it('cursor forward (CUF)', () => { - inputHandler.parse('\x1b[C'); + it('cursor forward (CUF)', async () => { + await inputHandler.parseP('\x1b[C'); assert.deepEqual(getCursor(bufferService), [1, 0]); - inputHandler.parse('\x1b[1C'); + await inputHandler.parseP('\x1b[1C'); assert.deepEqual(getCursor(bufferService), [2, 0]); - inputHandler.parse('\x1b[4C'); + await inputHandler.parseP('\x1b[4C'); assert.deepEqual(getCursor(bufferService), [6, 0]); - inputHandler.parse('\x1b[100C'); + await inputHandler.parseP('\x1b[100C'); assert.deepEqual(getCursor(bufferService), [9, 0]); // should not change y bufferService.buffer.x = 8; bufferService.buffer.y = 4; - inputHandler.parse('\x1b[C'); + await inputHandler.parseP('\x1b[C'); assert.deepEqual(getCursor(bufferService), [9, 4]); }); - it('cursor backward (CUB)', () => { - inputHandler.parse('\x1b[D'); + it('cursor backward (CUB)', async () => { + await inputHandler.parseP('\x1b[D'); assert.deepEqual(getCursor(bufferService), [0, 0]); - inputHandler.parse('\x1b[1D'); + await inputHandler.parseP('\x1b[1D'); assert.deepEqual(getCursor(bufferService), [0, 0]); // place cursor at end of first line - inputHandler.parse('\x1b[100C'); - inputHandler.parse('\x1b[D'); + await inputHandler.parseP('\x1b[100C'); + await inputHandler.parseP('\x1b[D'); assert.deepEqual(getCursor(bufferService), [8, 0]); - inputHandler.parse('\x1b[1D'); + await inputHandler.parseP('\x1b[1D'); assert.deepEqual(getCursor(bufferService), [7, 0]); - inputHandler.parse('\x1b[4D'); + await inputHandler.parseP('\x1b[4D'); assert.deepEqual(getCursor(bufferService), [3, 0]); - inputHandler.parse('\x1b[100D'); + await inputHandler.parseP('\x1b[100D'); assert.deepEqual(getCursor(bufferService), [0, 0]); // should not change y bufferService.buffer.x = 4; bufferService.buffer.y = 4; - inputHandler.parse('\x1b[D'); + await inputHandler.parseP('\x1b[D'); assert.deepEqual(getCursor(bufferService), [3, 4]); }); - it('cursor down (CUD)', () => { - inputHandler.parse('\x1b[B'); + it('cursor down (CUD)', async () => { + await inputHandler.parseP('\x1b[B'); assert.deepEqual(getCursor(bufferService), [0, 1]); - inputHandler.parse('\x1b[1B'); + await inputHandler.parseP('\x1b[1B'); assert.deepEqual(getCursor(bufferService), [0, 2]); - inputHandler.parse('\x1b[4B'); + await inputHandler.parseP('\x1b[4B'); assert.deepEqual(getCursor(bufferService), [0, 6]); - inputHandler.parse('\x1b[100B'); + await inputHandler.parseP('\x1b[100B'); assert.deepEqual(getCursor(bufferService), [0, 9]); // should not change x bufferService.buffer.x = 8; bufferService.buffer.y = 0; - inputHandler.parse('\x1b[B'); + await inputHandler.parseP('\x1b[B'); assert.deepEqual(getCursor(bufferService), [8, 1]); }); - it('cursor up (CUU)', () => { - inputHandler.parse('\x1b[A'); + it('cursor up (CUU)', async () => { + await inputHandler.parseP('\x1b[A'); assert.deepEqual(getCursor(bufferService), [0, 0]); - inputHandler.parse('\x1b[1A'); + await inputHandler.parseP('\x1b[1A'); assert.deepEqual(getCursor(bufferService), [0, 0]); // place cursor at beginning of last row - inputHandler.parse('\x1b[100B'); - inputHandler.parse('\x1b[A'); + await inputHandler.parseP('\x1b[100B'); + await inputHandler.parseP('\x1b[A'); assert.deepEqual(getCursor(bufferService), [0, 8]); - inputHandler.parse('\x1b[1A'); + await inputHandler.parseP('\x1b[1A'); assert.deepEqual(getCursor(bufferService), [0, 7]); - inputHandler.parse('\x1b[4A'); + await inputHandler.parseP('\x1b[4A'); assert.deepEqual(getCursor(bufferService), [0, 3]); - inputHandler.parse('\x1b[100A'); + await inputHandler.parseP('\x1b[100A'); assert.deepEqual(getCursor(bufferService), [0, 0]); // should not change x bufferService.buffer.x = 8; bufferService.buffer.y = 9; - inputHandler.parse('\x1b[A'); + await inputHandler.parseP('\x1b[A'); assert.deepEqual(getCursor(bufferService), [8, 8]); }); - it('cursor next line (CNL)', () => { - inputHandler.parse('\x1b[E'); + it('cursor next line (CNL)', async () => { + await inputHandler.parseP('\x1b[E'); assert.deepEqual(getCursor(bufferService), [0, 1]); - inputHandler.parse('\x1b[1E'); + await inputHandler.parseP('\x1b[1E'); assert.deepEqual(getCursor(bufferService), [0, 2]); - inputHandler.parse('\x1b[4E'); + await inputHandler.parseP('\x1b[4E'); assert.deepEqual(getCursor(bufferService), [0, 6]); - inputHandler.parse('\x1b[100E'); + await inputHandler.parseP('\x1b[100E'); assert.deepEqual(getCursor(bufferService), [0, 9]); // should reset x to zero bufferService.buffer.x = 8; bufferService.buffer.y = 0; - inputHandler.parse('\x1b[E'); + await inputHandler.parseP('\x1b[E'); assert.deepEqual(getCursor(bufferService), [0, 1]); }); - it('cursor previous line (CPL)', () => { - inputHandler.parse('\x1b[F'); + it('cursor previous line (CPL)', async () => { + await inputHandler.parseP('\x1b[F'); assert.deepEqual(getCursor(bufferService), [0, 0]); - inputHandler.parse('\x1b[1F'); + await inputHandler.parseP('\x1b[1F'); assert.deepEqual(getCursor(bufferService), [0, 0]); // place cursor at beginning of last row - inputHandler.parse('\x1b[100E'); - inputHandler.parse('\x1b[F'); + await inputHandler.parseP('\x1b[100E'); + await inputHandler.parseP('\x1b[F'); assert.deepEqual(getCursor(bufferService), [0, 8]); - inputHandler.parse('\x1b[1F'); + await inputHandler.parseP('\x1b[1F'); assert.deepEqual(getCursor(bufferService), [0, 7]); - inputHandler.parse('\x1b[4F'); + await inputHandler.parseP('\x1b[4F'); assert.deepEqual(getCursor(bufferService), [0, 3]); - inputHandler.parse('\x1b[100F'); + await inputHandler.parseP('\x1b[100F'); assert.deepEqual(getCursor(bufferService), [0, 0]); // should reset x to zero bufferService.buffer.x = 8; bufferService.buffer.y = 9; - inputHandler.parse('\x1b[F'); + await inputHandler.parseP('\x1b[F'); assert.deepEqual(getCursor(bufferService), [0, 8]); }); - it('cursor character absolute (CHA)', () => { - inputHandler.parse('\x1b[G'); + it('cursor character absolute (CHA)', async () => { + await inputHandler.parseP('\x1b[G'); assert.deepEqual(getCursor(bufferService), [0, 0]); - inputHandler.parse('\x1b[1G'); + await inputHandler.parseP('\x1b[1G'); assert.deepEqual(getCursor(bufferService), [0, 0]); - inputHandler.parse('\x1b[2G'); + await inputHandler.parseP('\x1b[2G'); assert.deepEqual(getCursor(bufferService), [1, 0]); - inputHandler.parse('\x1b[5G'); + await inputHandler.parseP('\x1b[5G'); assert.deepEqual(getCursor(bufferService), [4, 0]); - inputHandler.parse('\x1b[100G'); + await inputHandler.parseP('\x1b[100G'); assert.deepEqual(getCursor(bufferService), [9, 0]); }); - it('cursor position (CUP)', () => { + it('cursor position (CUP)', async () => { bufferService.buffer.x = 5; bufferService.buffer.y = 5; - inputHandler.parse('\x1b[H'); + await inputHandler.parseP('\x1b[H'); assert.deepEqual(getCursor(bufferService), [0, 0]); bufferService.buffer.x = 5; bufferService.buffer.y = 5; - inputHandler.parse('\x1b[1H'); + await inputHandler.parseP('\x1b[1H'); assert.deepEqual(getCursor(bufferService), [0, 0]); bufferService.buffer.x = 5; bufferService.buffer.y = 5; - inputHandler.parse('\x1b[1;1H'); + await inputHandler.parseP('\x1b[1;1H'); assert.deepEqual(getCursor(bufferService), [0, 0]); bufferService.buffer.x = 5; bufferService.buffer.y = 5; - inputHandler.parse('\x1b[8H'); + await inputHandler.parseP('\x1b[8H'); assert.deepEqual(getCursor(bufferService), [0, 7]); bufferService.buffer.x = 5; bufferService.buffer.y = 5; - inputHandler.parse('\x1b[;8H'); + await inputHandler.parseP('\x1b[;8H'); assert.deepEqual(getCursor(bufferService), [7, 0]); bufferService.buffer.x = 5; bufferService.buffer.y = 5; - inputHandler.parse('\x1b[100;100H'); + await inputHandler.parseP('\x1b[100;100H'); assert.deepEqual(getCursor(bufferService), [9, 9]); }); - it('horizontal position absolute (HPA)', () => { - inputHandler.parse('\x1b[`'); + it('horizontal position absolute (HPA)', async () => { + await inputHandler.parseP('\x1b[`'); assert.deepEqual(getCursor(bufferService), [0, 0]); - inputHandler.parse('\x1b[1`'); + await inputHandler.parseP('\x1b[1`'); assert.deepEqual(getCursor(bufferService), [0, 0]); - inputHandler.parse('\x1b[2`'); + await inputHandler.parseP('\x1b[2`'); assert.deepEqual(getCursor(bufferService), [1, 0]); - inputHandler.parse('\x1b[5`'); + await inputHandler.parseP('\x1b[5`'); assert.deepEqual(getCursor(bufferService), [4, 0]); - inputHandler.parse('\x1b[100`'); + await inputHandler.parseP('\x1b[100`'); assert.deepEqual(getCursor(bufferService), [9, 0]); }); - it('horizontal position relative (HPR)', () => { - inputHandler.parse('\x1b[a'); + it('horizontal position relative (HPR)', async () => { + await inputHandler.parseP('\x1b[a'); assert.deepEqual(getCursor(bufferService), [1, 0]); - inputHandler.parse('\x1b[1a'); + await inputHandler.parseP('\x1b[1a'); assert.deepEqual(getCursor(bufferService), [2, 0]); - inputHandler.parse('\x1b[4a'); + await inputHandler.parseP('\x1b[4a'); assert.deepEqual(getCursor(bufferService), [6, 0]); - inputHandler.parse('\x1b[100a'); + await inputHandler.parseP('\x1b[100a'); assert.deepEqual(getCursor(bufferService), [9, 0]); // should not change y bufferService.buffer.x = 8; bufferService.buffer.y = 4; - inputHandler.parse('\x1b[a'); + await inputHandler.parseP('\x1b[a'); assert.deepEqual(getCursor(bufferService), [9, 4]); }); - it('vertical position absolute (VPA)', () => { - inputHandler.parse('\x1b[d'); + it('vertical position absolute (VPA)', async () => { + await inputHandler.parseP('\x1b[d'); assert.deepEqual(getCursor(bufferService), [0, 0]); - inputHandler.parse('\x1b[1d'); + await inputHandler.parseP('\x1b[1d'); assert.deepEqual(getCursor(bufferService), [0, 0]); - inputHandler.parse('\x1b[2d'); + await inputHandler.parseP('\x1b[2d'); assert.deepEqual(getCursor(bufferService), [0, 1]); - inputHandler.parse('\x1b[5d'); + await inputHandler.parseP('\x1b[5d'); assert.deepEqual(getCursor(bufferService), [0, 4]); - inputHandler.parse('\x1b[100d'); + await inputHandler.parseP('\x1b[100d'); assert.deepEqual(getCursor(bufferService), [0, 9]); // should not change x bufferService.buffer.x = 8; bufferService.buffer.y = 4; - inputHandler.parse('\x1b[d'); + await inputHandler.parseP('\x1b[d'); assert.deepEqual(getCursor(bufferService), [8, 0]); }); - it('vertical position relative (VPR)', () => { - inputHandler.parse('\x1b[e'); + it('vertical position relative (VPR)', async () => { + await inputHandler.parseP('\x1b[e'); assert.deepEqual(getCursor(bufferService), [0, 1]); - inputHandler.parse('\x1b[1e'); + await inputHandler.parseP('\x1b[1e'); assert.deepEqual(getCursor(bufferService), [0, 2]); - inputHandler.parse('\x1b[4e'); + await inputHandler.parseP('\x1b[4e'); assert.deepEqual(getCursor(bufferService), [0, 6]); - inputHandler.parse('\x1b[100e'); + await inputHandler.parseP('\x1b[100e'); assert.deepEqual(getCursor(bufferService), [0, 9]); // should not change x bufferService.buffer.x = 8; bufferService.buffer.y = 4; - inputHandler.parse('\x1b[e'); + await inputHandler.parseP('\x1b[e'); assert.deepEqual(getCursor(bufferService), [8, 5]); }); describe('should clamp cursor into addressible range', () => { - it('CUF', () => { + it('CUF', async () => { bufferService.buffer.x = 10000; bufferService.buffer.y = 10000; - inputHandler.parse('\x1b[C'); + await inputHandler.parseP('\x1b[C'); assert.deepEqual(getCursor(bufferService), [9, 9]); bufferService.buffer.x = -10000; bufferService.buffer.y = -10000; - inputHandler.parse('\x1b[C'); + await inputHandler.parseP('\x1b[C'); assert.deepEqual(getCursor(bufferService), [1, 0]); }); - it('CUB', () => { + it('CUB', async () => { bufferService.buffer.x = 10000; bufferService.buffer.y = 10000; - inputHandler.parse('\x1b[D'); + await inputHandler.parseP('\x1b[D'); assert.deepEqual(getCursor(bufferService), [8, 9]); bufferService.buffer.x = -10000; bufferService.buffer.y = -10000; - inputHandler.parse('\x1b[D'); + await inputHandler.parseP('\x1b[D'); assert.deepEqual(getCursor(bufferService), [0, 0]); }); - it('CUD', () => { + it('CUD', async () => { bufferService.buffer.x = 10000; bufferService.buffer.y = 10000; - inputHandler.parse('\x1b[B'); + await inputHandler.parseP('\x1b[B'); assert.deepEqual(getCursor(bufferService), [9, 9]); bufferService.buffer.x = -10000; bufferService.buffer.y = -10000; - inputHandler.parse('\x1b[B'); + await inputHandler.parseP('\x1b[B'); assert.deepEqual(getCursor(bufferService), [0, 1]); }); - it('CUU', () => { + it('CUU', async () => { bufferService.buffer.x = 10000; bufferService.buffer.y = 10000; - inputHandler.parse('\x1b[A'); + await inputHandler.parseP('\x1b[A'); assert.deepEqual(getCursor(bufferService), [9, 8]); bufferService.buffer.x = -10000; bufferService.buffer.y = -10000; - inputHandler.parse('\x1b[A'); + await inputHandler.parseP('\x1b[A'); assert.deepEqual(getCursor(bufferService), [0, 0]); }); - it('CNL', () => { + it('CNL', async () => { bufferService.buffer.x = 10000; bufferService.buffer.y = 10000; - inputHandler.parse('\x1b[E'); + await inputHandler.parseP('\x1b[E'); assert.deepEqual(getCursor(bufferService), [0, 9]); bufferService.buffer.x = -10000; bufferService.buffer.y = -10000; - inputHandler.parse('\x1b[E'); + await inputHandler.parseP('\x1b[E'); assert.deepEqual(getCursor(bufferService), [0, 1]); }); - it('CPL', () => { + it('CPL', async () => { bufferService.buffer.x = 10000; bufferService.buffer.y = 10000; - inputHandler.parse('\x1b[F'); + await inputHandler.parseP('\x1b[F'); assert.deepEqual(getCursor(bufferService), [0, 8]); bufferService.buffer.x = -10000; bufferService.buffer.y = -10000; - inputHandler.parse('\x1b[F'); + await inputHandler.parseP('\x1b[F'); assert.deepEqual(getCursor(bufferService), [0, 0]); }); - it('CHA', () => { + it('CHA', async () => { bufferService.buffer.x = 10000; bufferService.buffer.y = 10000; - inputHandler.parse('\x1b[5G'); + await inputHandler.parseP('\x1b[5G'); assert.deepEqual(getCursor(bufferService), [4, 9]); bufferService.buffer.x = -10000; bufferService.buffer.y = -10000; - inputHandler.parse('\x1b[5G'); + await inputHandler.parseP('\x1b[5G'); assert.deepEqual(getCursor(bufferService), [4, 0]); }); - it('CUP', () => { + it('CUP', async () => { bufferService.buffer.x = 10000; bufferService.buffer.y = 10000; - inputHandler.parse('\x1b[5;5H'); + await inputHandler.parseP('\x1b[5;5H'); assert.deepEqual(getCursor(bufferService), [4, 4]); bufferService.buffer.x = -10000; bufferService.buffer.y = -10000; - inputHandler.parse('\x1b[5;5H'); + await inputHandler.parseP('\x1b[5;5H'); assert.deepEqual(getCursor(bufferService), [4, 4]); }); - it('HPA', () => { + it('HPA', async () => { bufferService.buffer.x = 10000; bufferService.buffer.y = 10000; - inputHandler.parse('\x1b[5`'); + await inputHandler.parseP('\x1b[5`'); assert.deepEqual(getCursor(bufferService), [4, 9]); bufferService.buffer.x = -10000; bufferService.buffer.y = -10000; - inputHandler.parse('\x1b[5`'); + await inputHandler.parseP('\x1b[5`'); assert.deepEqual(getCursor(bufferService), [4, 0]); }); - it('HPR', () => { + it('HPR', async () => { bufferService.buffer.x = 10000; bufferService.buffer.y = 10000; - inputHandler.parse('\x1b[a'); + await inputHandler.parseP('\x1b[a'); assert.deepEqual(getCursor(bufferService), [9, 9]); bufferService.buffer.x = -10000; bufferService.buffer.y = -10000; - inputHandler.parse('\x1b[a'); + await inputHandler.parseP('\x1b[a'); assert.deepEqual(getCursor(bufferService), [1, 0]); }); - it('VPA', () => { + it('VPA', async () => { bufferService.buffer.x = 10000; bufferService.buffer.y = 10000; - inputHandler.parse('\x1b[5d'); + await inputHandler.parseP('\x1b[5d'); assert.deepEqual(getCursor(bufferService), [9, 4]); bufferService.buffer.x = -10000; bufferService.buffer.y = -10000; - inputHandler.parse('\x1b[5d'); + await inputHandler.parseP('\x1b[5d'); assert.deepEqual(getCursor(bufferService), [0, 4]); }); - it('VPR', () => { + it('VPR', async () => { bufferService.buffer.x = 10000; bufferService.buffer.y = 10000; - inputHandler.parse('\x1b[e'); + await inputHandler.parseP('\x1b[e'); assert.deepEqual(getCursor(bufferService), [9, 9]); bufferService.buffer.x = -10000; bufferService.buffer.y = -10000; - inputHandler.parse('\x1b[e'); + await inputHandler.parseP('\x1b[e'); assert.deepEqual(getCursor(bufferService), [0, 1]); }); - it('DCH', () => { + it('DCH', async () => { bufferService.buffer.x = 10000; bufferService.buffer.y = 10000; - inputHandler.parse('\x1b[P'); + await inputHandler.parseP('\x1b[P'); assert.deepEqual(getCursor(bufferService), [9, 9]); bufferService.buffer.x = -10000; bufferService.buffer.y = -10000; - inputHandler.parse('\x1b[P'); + await inputHandler.parseP('\x1b[P'); assert.deepEqual(getCursor(bufferService), [0, 0]); }); - it('DCH - should delete last cell', () => { - inputHandler.parse('0123456789\x1b[P'); + it('DCH - should delete last cell', async () => { + await inputHandler.parseP('0123456789\x1b[P'); assert.equal(bufferService.buffer.lines.get(0)!.translateToString(false), '012345678 '); }); - it('ECH', () => { + it('ECH', async () => { bufferService.buffer.x = 10000; bufferService.buffer.y = 10000; - inputHandler.parse('\x1b[X'); + await inputHandler.parseP('\x1b[X'); assert.deepEqual(getCursor(bufferService), [9, 9]); bufferService.buffer.x = -10000; bufferService.buffer.y = -10000; - inputHandler.parse('\x1b[X'); + await inputHandler.parseP('\x1b[X'); assert.deepEqual(getCursor(bufferService), [0, 0]); }); - it('ECH - should delete last cell', () => { - inputHandler.parse('0123456789\x1b[X'); + it('ECH - should delete last cell', async () => { + await inputHandler.parseP('0123456789\x1b[X'); assert.equal(bufferService.buffer.lines.get(0)!.translateToString(false), '012345678 '); }); - it('ICH', () => { + it('ICH', async () => { bufferService.buffer.x = 10000; bufferService.buffer.y = 10000; - inputHandler.parse('\x1b[@'); + await inputHandler.parseP('\x1b[@'); assert.deepEqual(getCursor(bufferService), [9, 9]); bufferService.buffer.x = -10000; bufferService.buffer.y = -10000; - inputHandler.parse('\x1b[@'); + await inputHandler.parseP('\x1b[@'); assert.deepEqual(getCursor(bufferService), [0, 0]); }); - it('ICH - should delete last cell', () => { - inputHandler.parse('0123456789\x1b[@'); + it('ICH - should delete last cell', async () => { + await inputHandler.parseP('0123456789\x1b[@'); assert.equal(bufferService.buffer.lines.get(0)!.translateToString(false), '012345678 '); }); }); @@ -1092,31 +1264,31 @@ describe('InputHandler', () => { beforeEach(() => { bufferService.resize(10, 10); }); - it('should default to whole viewport', () => { - inputHandler.parse('\x1b[r'); + it('should default to whole viewport', async () => { + await inputHandler.parseP('\x1b[r'); assert.equal(bufferService.buffer.scrollTop, 0); assert.equal(bufferService.buffer.scrollBottom, 9); - inputHandler.parse('\x1b[3;7r'); + await inputHandler.parseP('\x1b[3;7r'); assert.equal(bufferService.buffer.scrollTop, 2); assert.equal(bufferService.buffer.scrollBottom, 6); - inputHandler.parse('\x1b[0;0r'); + await inputHandler.parseP('\x1b[0;0r'); assert.equal(bufferService.buffer.scrollTop, 0); assert.equal(bufferService.buffer.scrollBottom, 9); }); - it('should clamp bottom', () => { - inputHandler.parse('\x1b[3;1000r'); + it('should clamp bottom', async () => { + await inputHandler.parseP('\x1b[3;1000r'); assert.equal(bufferService.buffer.scrollTop, 2); assert.equal(bufferService.buffer.scrollBottom, 9); }); - it('should only apply for top < bottom', () => { - inputHandler.parse('\x1b[7;2r'); + it('should only apply for top < bottom', async () => { + await inputHandler.parseP('\x1b[7;2r'); assert.equal(bufferService.buffer.scrollTop, 0); assert.equal(bufferService.buffer.scrollBottom, 9); }); - it('should home cursor', () => { + it('should home cursor', async () => { bufferService.buffer.x = 10000; bufferService.buffer.y = 10000; - inputHandler.parse('\x1b[2;7r'); + await inputHandler.parseP('\x1b[2;7r'); assert.deepEqual(getCursor(bufferService), [0, 0]); }); }); @@ -1124,76 +1296,76 @@ describe('InputHandler', () => { beforeEach(() => { bufferService.resize(10, 10); }); - it('scrollUp', () => { - inputHandler.parse('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[2;4r\x1b[2Sm'); + it('scrollUp', async () => { + await inputHandler.parseP('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(bufferService), ['m', '3', '', '', '4', '5', '6', '7', '8', '9']); }); - it('scrollDown', () => { - inputHandler.parse('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[2;4r\x1b[2Tm'); + it('scrollDown', async () => { + await inputHandler.parseP('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[2;4r\x1b[2Tm'); assert.deepEqual(getLines(bufferService), ['m', '', '', '1', '4', '5', '6', '7', '8', '9']); }); - it('insertLines - out of margins', () => { - inputHandler.parse('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[3;6r'); + it('insertLines - out of margins', async () => { + await inputHandler.parseP('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[3;6r'); assert.equal(bufferService.buffer.scrollTop, 2); assert.equal(bufferService.buffer.scrollBottom, 5); - inputHandler.parse('\x1b[2Lm'); + await inputHandler.parseP('\x1b[2Lm'); assert.deepEqual(getLines(bufferService), ['m', '1', '2', '3', '4', '5', '6', '7', '8', '9']); - inputHandler.parse('\x1b[2H\x1b[2Ln'); + await inputHandler.parseP('\x1b[2H\x1b[2Ln'); assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', '6', '7', '8', '9']); // skip below scrollbottom - inputHandler.parse('\x1b[7H\x1b[2Lo'); + await inputHandler.parseP('\x1b[7H\x1b[2Lo'); assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', 'o', '7', '8', '9']); - inputHandler.parse('\x1b[8H\x1b[2Lp'); + await inputHandler.parseP('\x1b[8H\x1b[2Lp'); assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', 'o', 'p', '8', '9']); - inputHandler.parse('\x1b[100H\x1b[2Lq'); + await inputHandler.parseP('\x1b[100H\x1b[2Lq'); assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', 'o', 'p', '8', 'q']); }); - it('insertLines - within margins', () => { - inputHandler.parse('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[3;6r'); + it('insertLines - within margins', async () => { + await inputHandler.parseP('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[3;6r'); assert.equal(bufferService.buffer.scrollTop, 2); assert.equal(bufferService.buffer.scrollBottom, 5); - inputHandler.parse('\x1b[3H\x1b[2Lm'); + await inputHandler.parseP('\x1b[3H\x1b[2Lm'); assert.deepEqual(getLines(bufferService), ['0', '1', 'm', '', '2', '3', '6', '7', '8', '9']); - inputHandler.parse('\x1b[6H\x1b[2Ln'); + await inputHandler.parseP('\x1b[6H\x1b[2Ln'); assert.deepEqual(getLines(bufferService), ['0', '1', 'm', '', '2', 'n', '6', '7', '8', '9']); }); - it('deleteLines - out of margins', () => { - inputHandler.parse('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[3;6r'); + it('deleteLines - out of margins', async () => { + await inputHandler.parseP('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[3;6r'); assert.equal(bufferService.buffer.scrollTop, 2); assert.equal(bufferService.buffer.scrollBottom, 5); - inputHandler.parse('\x1b[2Mm'); + await inputHandler.parseP('\x1b[2Mm'); assert.deepEqual(getLines(bufferService), ['m', '1', '2', '3', '4', '5', '6', '7', '8', '9']); - inputHandler.parse('\x1b[2H\x1b[2Mn'); + await inputHandler.parseP('\x1b[2H\x1b[2Mn'); assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', '6', '7', '8', '9']); // skip below scrollbottom - inputHandler.parse('\x1b[7H\x1b[2Mo'); + await inputHandler.parseP('\x1b[7H\x1b[2Mo'); assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', 'o', '7', '8', '9']); - inputHandler.parse('\x1b[8H\x1b[2Mp'); + await inputHandler.parseP('\x1b[8H\x1b[2Mp'); assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', 'o', 'p', '8', '9']); - inputHandler.parse('\x1b[100H\x1b[2Mq'); + await inputHandler.parseP('\x1b[100H\x1b[2Mq'); assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', 'o', 'p', '8', 'q']); }); - it('deleteLines - within margins', () => { - inputHandler.parse('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[3;6r'); + it('deleteLines - within margins', async () => { + await inputHandler.parseP('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[3;6r'); assert.equal(bufferService.buffer.scrollTop, 2); assert.equal(bufferService.buffer.scrollBottom, 5); - inputHandler.parse('\x1b[6H\x1b[2Mm'); + await inputHandler.parseP('\x1b[6H\x1b[2Mm'); assert.deepEqual(getLines(bufferService), ['0', '1', '2', '3', '4', 'm', '6', '7', '8', '9']); - inputHandler.parse('\x1b[3H\x1b[2Mn'); - assert.deepEqual(getLines(bufferService), ['0', '1', 'n', 'm', '', '', '6', '7', '8', '9']); + await inputHandler.parseP('\x1b[3H\x1b[2Mn'); + assert.deepEqual(getLines(bufferService), ['0', '1', 'n', 'm', '', '', '6', '7', '8', '9']); }); }); - it('should parse big chunks in smaller subchunks', () => { + it('should parse big chunks in smaller subchunks', async () => { // max single chunk size is hardcoded as 131072 const calls: any[] = []; bufferService.resize(10, 10); (inputHandler as any)._parser.parse = (data: Uint32Array, length: number) => { calls.push([data.length, length]); }; - inputHandler.parse('12345'); - inputHandler.parse('a'.repeat(10000)); - inputHandler.parse('a'.repeat(200000)); - inputHandler.parse('a'.repeat(300000)); + await inputHandler.parseP('12345'); + await inputHandler.parseP('a'.repeat(10000)); + await inputHandler.parseP('a'.repeat(200000)); + await inputHandler.parseP('a'.repeat(300000)); assert.deepEqual(calls, [ [4096, 5], [10000, 10000], @@ -1202,187 +1374,187 @@ describe('InputHandler', () => { ]); }); describe('windowOptions', () => { - it('all should be disabled by default and not report', () => { + it('all should be disabled by default and not report', async () => { bufferService.resize(10, 10); const stack: string[] = []; coreService.onData(data => stack.push(data)); - inputHandler.parse('\x1b[14t'); - inputHandler.parse('\x1b[16t'); - inputHandler.parse('\x1b[18t'); - inputHandler.parse('\x1b[20t'); - inputHandler.parse('\x1b[21t'); + await inputHandler.parseP('\x1b[14t'); + await inputHandler.parseP('\x1b[16t'); + await inputHandler.parseP('\x1b[18t'); + await inputHandler.parseP('\x1b[20t'); + await inputHandler.parseP('\x1b[21t'); assert.deepEqual(stack, []); }); - it('14 - GetWinSizePixels', () => { + it('14 - GetWinSizePixels', async () => { bufferService.resize(10, 10); optionsService.options.windowOptions.getWinSizePixels = true; const stack: string[] = []; coreService.onData(data => stack.push(data)); - inputHandler.parse('\x1b[14t'); + await inputHandler.parseP('\x1b[14t'); // does not report in test terminal due to missing renderer assert.deepEqual(stack, []); }); - it('16 - GetCellSizePixels', () => { + it('16 - GetCellSizePixels', async () => { bufferService.resize(10, 10); optionsService.options.windowOptions.getCellSizePixels = true; const stack: string[] = []; coreService.onData(data => stack.push(data)); - inputHandler.parse('\x1b[16t'); + await inputHandler.parseP('\x1b[16t'); // does not report in test terminal due to missing renderer assert.deepEqual(stack, []); }); - it('18 - GetWinSizeChars', () => { + it('18 - GetWinSizeChars', async () => { bufferService.resize(10, 10); optionsService.options.windowOptions.getWinSizeChars = true; const stack: string[] = []; coreService.onData(data => stack.push(data)); - inputHandler.parse('\x1b[18t'); + await inputHandler.parseP('\x1b[18t'); assert.deepEqual(stack, ['\x1b[8;10;10t']); bufferService.resize(50, 20); - inputHandler.parse('\x1b[18t'); + await inputHandler.parseP('\x1b[18t'); assert.deepEqual(stack, ['\x1b[8;10;10t', '\x1b[8;20;50t']); }); - it('22/23 - PushTitle/PopTitle', () => { + it('22/23 - PushTitle/PopTitle', async () => { bufferService.resize(10, 10); optionsService.options.windowOptions.pushTitle = true; optionsService.options.windowOptions.popTitle = true; const stack: string[] = []; inputHandler.onTitleChange(data => stack.push(data)); - inputHandler.parse('\x1b]0;1\x07'); - inputHandler.parse('\x1b[22t'); - inputHandler.parse('\x1b]0;2\x07'); - inputHandler.parse('\x1b[22t'); - inputHandler.parse('\x1b]0;3\x07'); - inputHandler.parse('\x1b[22t'); + await inputHandler.parseP('\x1b]0;1\x07'); + await inputHandler.parseP('\x1b[22t'); + await inputHandler.parseP('\x1b]0;2\x07'); + await inputHandler.parseP('\x1b[22t'); + await inputHandler.parseP('\x1b]0;3\x07'); + await inputHandler.parseP('\x1b[22t'); assert.deepEqual(inputHandler.windowTitleStack, ['1', '2', '3']); assert.deepEqual(inputHandler.iconNameStack, ['1', '2', '3']); assert.deepEqual(stack, ['1', '2', '3']); - inputHandler.parse('\x1b[23t'); - inputHandler.parse('\x1b[23t'); - inputHandler.parse('\x1b[23t'); - inputHandler.parse('\x1b[23t'); // one more to test "overflow" + await inputHandler.parseP('\x1b[23t'); + await inputHandler.parseP('\x1b[23t'); + await inputHandler.parseP('\x1b[23t'); + await inputHandler.parseP('\x1b[23t'); // one more to test "overflow" assert.deepEqual(inputHandler.windowTitleStack, []); assert.deepEqual(inputHandler.iconNameStack, []); assert.deepEqual(stack, ['1', '2', '3', '3', '2', '1']); }); - it('22/23 - PushTitle/PopTitle with ;1', () => { + it('22/23 - PushTitle/PopTitle with ;1', async () => { bufferService.resize(10, 10); optionsService.options.windowOptions.pushTitle = true; optionsService.options.windowOptions.popTitle = true; const stack: string[] = []; inputHandler.onTitleChange(data => stack.push(data)); - inputHandler.parse('\x1b]0;1\x07'); - inputHandler.parse('\x1b[22;1t'); - inputHandler.parse('\x1b]0;2\x07'); - inputHandler.parse('\x1b[22;1t'); - inputHandler.parse('\x1b]0;3\x07'); - inputHandler.parse('\x1b[22;1t'); + await inputHandler.parseP('\x1b]0;1\x07'); + await inputHandler.parseP('\x1b[22;1t'); + await inputHandler.parseP('\x1b]0;2\x07'); + await inputHandler.parseP('\x1b[22;1t'); + await inputHandler.parseP('\x1b]0;3\x07'); + await inputHandler.parseP('\x1b[22;1t'); assert.deepEqual(inputHandler.windowTitleStack, []); assert.deepEqual(inputHandler.iconNameStack, ['1', '2', '3']); assert.deepEqual(stack, ['1', '2', '3']); - inputHandler.parse('\x1b[23;1t'); - inputHandler.parse('\x1b[23;1t'); - inputHandler.parse('\x1b[23;1t'); - inputHandler.parse('\x1b[23;1t'); // one more to test "overflow" + await inputHandler.parseP('\x1b[23;1t'); + await inputHandler.parseP('\x1b[23;1t'); + await inputHandler.parseP('\x1b[23;1t'); + await inputHandler.parseP('\x1b[23;1t'); // one more to test "overflow" assert.deepEqual(inputHandler.windowTitleStack, []); assert.deepEqual(inputHandler.iconNameStack, []); assert.deepEqual(stack, ['1', '2', '3']); }); - it('22/23 - PushTitle/PopTitle with ;2', () => { + it('22/23 - PushTitle/PopTitle with ;2', async () => { bufferService.resize(10, 10); optionsService.options.windowOptions.pushTitle = true; optionsService.options.windowOptions.popTitle = true; const stack: string[] = []; inputHandler.onTitleChange(data => stack.push(data)); - inputHandler.parse('\x1b]0;1\x07'); - inputHandler.parse('\x1b[22;2t'); - inputHandler.parse('\x1b]0;2\x07'); - inputHandler.parse('\x1b[22;2t'); - inputHandler.parse('\x1b]0;3\x07'); - inputHandler.parse('\x1b[22;2t'); + await inputHandler.parseP('\x1b]0;1\x07'); + await inputHandler.parseP('\x1b[22;2t'); + await inputHandler.parseP('\x1b]0;2\x07'); + await inputHandler.parseP('\x1b[22;2t'); + await inputHandler.parseP('\x1b]0;3\x07'); + await inputHandler.parseP('\x1b[22;2t'); assert.deepEqual(inputHandler.windowTitleStack, ['1', '2', '3']); assert.deepEqual(inputHandler.iconNameStack, []); assert.deepEqual(stack, ['1', '2', '3']); - inputHandler.parse('\x1b[23;2t'); - inputHandler.parse('\x1b[23;2t'); - inputHandler.parse('\x1b[23;2t'); - inputHandler.parse('\x1b[23;2t'); // one more to test "overflow" + await inputHandler.parseP('\x1b[23;2t'); + await inputHandler.parseP('\x1b[23;2t'); + await inputHandler.parseP('\x1b[23;2t'); + await inputHandler.parseP('\x1b[23;2t'); // one more to test "overflow" assert.deepEqual(inputHandler.windowTitleStack, []); assert.deepEqual(inputHandler.iconNameStack, []); assert.deepEqual(stack, ['1', '2', '3', '3', '2', '1']); }); - it('DECCOLM - should only work with "SetWinLines" (24) enabled', () => { + it('DECCOLM - should only work with "SetWinLines" (24) enabled', async () => { // disabled bufferService.resize(10, 10); - inputHandler.parse('\x1b[?3l'); + await inputHandler.parseP('\x1b[?3l'); assert.equal(bufferService.cols, 10); - inputHandler.parse('\x1b[?3h'); + await inputHandler.parseP('\x1b[?3h'); assert.equal(bufferService.cols, 10); // enabled inputHandler.reset(); optionsService.options.windowOptions.setWinLines = true; - inputHandler.parse('\x1b[?3l'); + await inputHandler.parseP('\x1b[?3l'); assert.equal(bufferService.cols, 80); - inputHandler.parse('\x1b[?3h'); + await inputHandler.parseP('\x1b[?3h'); assert.equal(bufferService.cols, 132); }); }); describe('should correctly reset cells taken by wide chars', () => { - beforeEach(() => { + beforeEach(async () => { bufferService.resize(10, 5); optionsService.options.scrollback = 1; - inputHandler.parse('¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥'); + await inputHandler.parseP('¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥'); }); - it('print', () => { - inputHandler.parse('\x1b[H#'); + it('print', async () => { + await inputHandler.parseP('\x1b[H#'); assert.deepEqual(getLines(bufferService), ['# ¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - inputHandler.parse('\x1b[1;6H######'); + await inputHandler.parseP('\x1b[1;6H######'); assert.deepEqual(getLines(bufferService), ['# ¥ #####', '# ¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - inputHandler.parse('#'); + await inputHandler.parseP('#'); assert.deepEqual(getLines(bufferService), ['# ¥ #####', '##¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - inputHandler.parse('#'); + await inputHandler.parseP('#'); assert.deepEqual(getLines(bufferService), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - inputHandler.parse('\x1b[3;9H#'); + await inputHandler.parseP('\x1b[3;9H#'); assert.deepEqual(getLines(bufferService), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥#', '¥¥¥¥¥', '']); - inputHandler.parse('#'); + await inputHandler.parseP('#'); assert.deepEqual(getLines(bufferService), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥##', '¥¥¥¥¥', '']); - inputHandler.parse('#'); + await inputHandler.parseP('#'); assert.deepEqual(getLines(bufferService), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥##', '# ¥¥¥¥', '']); - inputHandler.parse('\x1b[4;10H#'); + await inputHandler.parseP('\x1b[4;10H#'); assert.deepEqual(getLines(bufferService), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥##', '# ¥¥¥ #', '']); }); - it('EL', () => { - inputHandler.parse('\x1b[1;6H\x1b[K#'); + it('EL', async () => { + await inputHandler.parseP('\x1b[1;6H\x1b[K#'); assert.deepEqual(getLines(bufferService), ['¥¥ #', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - inputHandler.parse('\x1b[2;5H\x1b[1K'); + await inputHandler.parseP('\x1b[2;5H\x1b[1K'); assert.deepEqual(getLines(bufferService), ['¥¥ #', ' ¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - inputHandler.parse('\x1b[3;6H\x1b[1K'); + await inputHandler.parseP('\x1b[3;6H\x1b[1K'); assert.deepEqual(getLines(bufferService), ['¥¥ #', ' ¥¥', ' ¥¥', '¥¥¥¥¥', '']); }); - it('ICH', () => { - inputHandler.parse('\x1b[1;6H\x1b[@'); + it('ICH', async () => { + await inputHandler.parseP('\x1b[1;6H\x1b[@'); assert.deepEqual(getLines(bufferService), ['¥¥ ¥', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - inputHandler.parse('\x1b[2;4H\x1b[2@'); + await inputHandler.parseP('\x1b[2;4H\x1b[2@'); assert.deepEqual(getLines(bufferService), ['¥¥ ¥', '¥ ¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - inputHandler.parse('\x1b[3;4H\x1b[3@'); + await inputHandler.parseP('\x1b[3;4H\x1b[3@'); assert.deepEqual(getLines(bufferService), ['¥¥ ¥', '¥ ¥¥', '¥ ¥', '¥¥¥¥¥', '']); - inputHandler.parse('\x1b[4;4H\x1b[4@'); + await inputHandler.parseP('\x1b[4;4H\x1b[4@'); assert.deepEqual(getLines(bufferService), ['¥¥ ¥', '¥ ¥¥', '¥ ¥', '¥ ¥', '']); }); - it('DCH', () => { - inputHandler.parse('\x1b[1;6H\x1b[P'); + it('DCH', async () => { + await inputHandler.parseP('\x1b[1;6H\x1b[P'); assert.deepEqual(getLines(bufferService), ['¥¥ ¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - inputHandler.parse('\x1b[2;6H\x1b[2P'); + await inputHandler.parseP('\x1b[2;6H\x1b[2P'); assert.deepEqual(getLines(bufferService), ['¥¥ ¥¥', '¥¥ ¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - inputHandler.parse('\x1b[3;6H\x1b[3P'); + await inputHandler.parseP('\x1b[3;6H\x1b[3P'); assert.deepEqual(getLines(bufferService), ['¥¥ ¥¥', '¥¥ ¥', '¥¥ ¥', '¥¥¥¥¥', '']); }); - it('ECH', () => { - inputHandler.parse('\x1b[1;6H\x1b[X'); + it('ECH', async () => { + await inputHandler.parseP('\x1b[1;6H\x1b[X'); assert.deepEqual(getLines(bufferService), ['¥¥ ¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - inputHandler.parse('\x1b[2;6H\x1b[2X'); + await inputHandler.parseP('\x1b[2;6H\x1b[2X'); assert.deepEqual(getLines(bufferService), ['¥¥ ¥¥', '¥¥ ¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - inputHandler.parse('\x1b[3;6H\x1b[3X'); + await inputHandler.parseP('\x1b[3;6H\x1b[3X'); assert.deepEqual(getLines(bufferService), ['¥¥ ¥¥', '¥¥ ¥', '¥¥ ¥', '¥¥¥¥¥', '']); }); }); @@ -1394,74 +1566,74 @@ describe('InputHandler', () => { optionsService.options.scrollback = 1; }); describe('reverseWraparound unset (default)', () => { - it('cannot delete last cell', () => { - inputHandler.parse('12345'); - inputHandler.parse(ttyBS); + it('cannot delete last cell', async () => { + await inputHandler.parseP('12345'); + await inputHandler.parseP(ttyBS); assert.deepEqual(getLines(bufferService, 1), ['123 5']); - inputHandler.parse(ttyBS.repeat(10)); + await inputHandler.parseP(ttyBS.repeat(10)); assert.deepEqual(getLines(bufferService, 1), [' 5']); }); - it('cannot access prev line', () => { - inputHandler.parse('12345'.repeat(2)); - inputHandler.parse(ttyBS); + it('cannot access prev line', async () => { + await inputHandler.parseP('12345'.repeat(2)); + await inputHandler.parseP(ttyBS); assert.deepEqual(getLines(bufferService, 2), ['12345', '123 5']); - inputHandler.parse(ttyBS.repeat(10)); + await inputHandler.parseP(ttyBS.repeat(10)); assert.deepEqual(getLines(bufferService, 2), ['12345', ' 5']); }); }); describe('reverseWraparound set', () => { - it('can delete last cell', () => { - inputHandler.parse('\x1b[?45h'); - inputHandler.parse('12345'); - inputHandler.parse(ttyBS); + it('can delete last cell', async () => { + await inputHandler.parseP('\x1b[?45h'); + await inputHandler.parseP('12345'); + await inputHandler.parseP(ttyBS); assert.deepEqual(getLines(bufferService, 1), ['1234 ']); - inputHandler.parse(ttyBS.repeat(7)); + await inputHandler.parseP(ttyBS.repeat(7)); assert.deepEqual(getLines(bufferService, 1), [' ']); }); - it('can access prev line if wrapped', () => { - inputHandler.parse('\x1b[?45h'); - inputHandler.parse('12345'.repeat(2)); - inputHandler.parse(ttyBS); + it('can access prev line if wrapped', async () => { + await inputHandler.parseP('\x1b[?45h'); + await inputHandler.parseP('12345'.repeat(2)); + await inputHandler.parseP(ttyBS); assert.deepEqual(getLines(bufferService, 2), ['12345', '1234 ']); - inputHandler.parse(ttyBS.repeat(7)); + await inputHandler.parseP(ttyBS.repeat(7)); assert.deepEqual(getLines(bufferService, 2), ['12 ', ' ']); }); - it('should lift isWrapped', () => { - inputHandler.parse('\x1b[?45h'); - inputHandler.parse('12345'.repeat(2)); + it('should lift isWrapped', async () => { + await inputHandler.parseP('\x1b[?45h'); + await inputHandler.parseP('12345'.repeat(2)); assert.equal(bufferService.buffer.lines.get(1)?.isWrapped, true); - inputHandler.parse(ttyBS.repeat(7)); + await inputHandler.parseP(ttyBS.repeat(7)); assert.equal(bufferService.buffer.lines.get(1)?.isWrapped, false); }); - it('stops at hard NLs', () => { - inputHandler.parse('\x1b[?45h'); - inputHandler.parse('12345\r\n'); - inputHandler.parse('12345'.repeat(2)); - inputHandler.parse(ttyBS.repeat(50)); + it('stops at hard NLs', async () => { + await inputHandler.parseP('\x1b[?45h'); + await inputHandler.parseP('12345\r\n'); + await inputHandler.parseP('12345'.repeat(2)); + await inputHandler.parseP(ttyBS.repeat(50)); assert.deepEqual(getLines(bufferService, 3), ['12345', ' ', ' ']); assert.equal(bufferService.buffer.x, 0); assert.equal(bufferService.buffer.y, 1); }); - it('handles wide chars correctly', () => { - inputHandler.parse('\x1b[?45h'); - inputHandler.parse('¥¥¥'); + it('handles wide chars correctly', async () => { + await inputHandler.parseP('\x1b[?45h'); + await inputHandler.parseP('¥¥¥'); assert.deepEqual(getLines(bufferService, 2), ['¥¥', '¥']); - inputHandler.parse(ttyBS); + await inputHandler.parseP(ttyBS); assert.deepEqual(getLines(bufferService, 2), ['¥¥', ' ']); assert.equal(bufferService.buffer.x, 1); - inputHandler.parse(ttyBS); + await inputHandler.parseP(ttyBS); assert.deepEqual(getLines(bufferService, 2), ['¥¥', ' ']); assert.equal(bufferService.buffer.x, 0); - inputHandler.parse(ttyBS); + await inputHandler.parseP(ttyBS); assert.deepEqual(getLines(bufferService, 2), ['¥ ', ' ']); assert.equal(bufferService.buffer.x, 3); // x=4 skipped due to early wrap-around - inputHandler.parse(ttyBS); + await inputHandler.parseP(ttyBS); assert.deepEqual(getLines(bufferService, 2), ['¥ ', ' ']); assert.equal(bufferService.buffer.x, 2); - inputHandler.parse(ttyBS); + await inputHandler.parseP(ttyBS); assert.deepEqual(getLines(bufferService, 2), [' ', ' ']); assert.equal(bufferService.buffer.x, 1); - inputHandler.parse(ttyBS); + await inputHandler.parseP(ttyBS); assert.deepEqual(getLines(bufferService, 2), [' ', ' ']); assert.equal(bufferService.buffer.x, 0); }); @@ -1472,72 +1644,72 @@ describe('InputHandler', () => { beforeEach(() => { bufferService.resize(10, 5); }); - it('4 | 24', () => { - inputHandler.parse('\x1b[4m'); + it('4 | 24', async () => { + await inputHandler.parseP('\x1b[4m'); assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.SINGLE); - inputHandler.parse('\x1b[24m'); + await inputHandler.parseP('\x1b[24m'); assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.NONE); }); - it('21 | 24', () => { - inputHandler.parse('\x1b[21m'); + it('21 | 24', async () => { + await inputHandler.parseP('\x1b[21m'); assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.DOUBLE); - inputHandler.parse('\x1b[24m'); + await inputHandler.parseP('\x1b[24m'); assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.NONE); }); - it('4:1 | 4:0', () => { - inputHandler.parse('\x1b[4:1m'); + it('4:1 | 4:0', async () => { + await inputHandler.parseP('\x1b[4:1m'); assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.SINGLE); - inputHandler.parse('\x1b[4:0m'); + await inputHandler.parseP('\x1b[4:0m'); assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.NONE); - inputHandler.parse('\x1b[4:1m'); + await inputHandler.parseP('\x1b[4:1m'); assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.SINGLE); - inputHandler.parse('\x1b[24m'); + await inputHandler.parseP('\x1b[24m'); assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.NONE); }); - it('4:2 | 4:0', () => { - inputHandler.parse('\x1b[4:2m'); + it('4:2 | 4:0', async () => { + await inputHandler.parseP('\x1b[4:2m'); assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.DOUBLE); - inputHandler.parse('\x1b[4:0m'); + await inputHandler.parseP('\x1b[4:0m'); assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.NONE); - inputHandler.parse('\x1b[4:2m'); + await inputHandler.parseP('\x1b[4:2m'); assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.DOUBLE); - inputHandler.parse('\x1b[24m'); + await inputHandler.parseP('\x1b[24m'); assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.NONE); }); - it('4:3 | 4:0', () => { - inputHandler.parse('\x1b[4:3m'); + it('4:3 | 4:0', async () => { + await inputHandler.parseP('\x1b[4:3m'); assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.CURLY); - inputHandler.parse('\x1b[4:0m'); + await inputHandler.parseP('\x1b[4:0m'); assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.NONE); - inputHandler.parse('\x1b[4:3m'); + await inputHandler.parseP('\x1b[4:3m'); assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.CURLY); - inputHandler.parse('\x1b[24m'); + await inputHandler.parseP('\x1b[24m'); assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.NONE); }); - it('4:4 | 4:0', () => { - inputHandler.parse('\x1b[4:4m'); + it('4:4 | 4:0', async () => { + await inputHandler.parseP('\x1b[4:4m'); assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.DOTTED); - inputHandler.parse('\x1b[4:0m'); + await inputHandler.parseP('\x1b[4:0m'); assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.NONE); - inputHandler.parse('\x1b[4:4m'); + await inputHandler.parseP('\x1b[4:4m'); assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.DOTTED); - inputHandler.parse('\x1b[24m'); + await inputHandler.parseP('\x1b[24m'); assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.NONE); }); - it('4:5 | 4:0', () => { - inputHandler.parse('\x1b[4:5m'); + it('4:5 | 4:0', async () => { + await inputHandler.parseP('\x1b[4:5m'); assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.DASHED); - inputHandler.parse('\x1b[4:0m'); + await inputHandler.parseP('\x1b[4:0m'); assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.NONE); - inputHandler.parse('\x1b[4:5m'); + await inputHandler.parseP('\x1b[4:5m'); assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.DASHED); - inputHandler.parse('\x1b[24m'); + await inputHandler.parseP('\x1b[24m'); assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.NONE); }); - it('4:x --> 4 should revert to single underline', () => { - inputHandler.parse('\x1b[4:5m'); + it('4:x --> 4 should revert to single underline', async () => { + await inputHandler.parseP('\x1b[4:5m'); assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.DASHED); - inputHandler.parse('\x1b[4m'); + await inputHandler.parseP('\x1b[4m'); assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.SINGLE); }); }); @@ -1545,9 +1717,9 @@ describe('InputHandler', () => { beforeEach(() => { bufferService.resize(10, 5); }); - it('defaults to FG color', () => { + it('defaults to FG color', async () => { for (const s of ['', '\x1b[30m', '\x1b[38;510m', '\x1b[38;2;1;2;3m']) { - inputHandler.parse(s); + await inputHandler.parseP(s); assert.equal(inputHandler.curAttrData.getUnderlineColor(), inputHandler.curAttrData.getFgColor()); assert.equal(inputHandler.curAttrData.getUnderlineColorMode(), inputHandler.curAttrData.getFgColorMode()); assert.equal(inputHandler.curAttrData.isUnderlineColorRGB(), inputHandler.curAttrData.isFgRGB()); @@ -1555,31 +1727,31 @@ describe('InputHandler', () => { assert.equal(inputHandler.curAttrData.isUnderlineColorDefault(), inputHandler.curAttrData.isFgDefault()); } }); - it('correctly sets P256/RGB colors', () => { - inputHandler.parse('\x1b[4m'); - inputHandler.parse('\x1b[58;5;123m'); + it('correctly sets P256/RGB colors', async () => { + await inputHandler.parseP('\x1b[4m'); + await inputHandler.parseP('\x1b[58;5;123m'); assert.equal(inputHandler.curAttrData.getUnderlineColor(), 123); assert.equal(inputHandler.curAttrData.getUnderlineColorMode(), Attributes.CM_P256); assert.equal(inputHandler.curAttrData.isUnderlineColorRGB(), false); assert.equal(inputHandler.curAttrData.isUnderlineColorPalette(), true); assert.equal(inputHandler.curAttrData.isUnderlineColorDefault(), false); - inputHandler.parse('\x1b[58;2::1:2:3m'); + await inputHandler.parseP('\x1b[58;2::1:2:3m'); assert.equal(inputHandler.curAttrData.getUnderlineColor(), (1 << 16) | (2 << 8) | 3); assert.equal(inputHandler.curAttrData.getUnderlineColorMode(), Attributes.CM_RGB); assert.equal(inputHandler.curAttrData.isUnderlineColorRGB(), true); assert.equal(inputHandler.curAttrData.isUnderlineColorPalette(), false); assert.equal(inputHandler.curAttrData.isUnderlineColorDefault(), false); }); - it('P256/RGB persistence', () => { + it('P256/RGB persistence', async () => { const cell = new CellData(); - inputHandler.parse('\x1b[4m'); - inputHandler.parse('\x1b[58;5;123m'); + await inputHandler.parseP('\x1b[4m'); + await inputHandler.parseP('\x1b[58;5;123m'); assert.equal(inputHandler.curAttrData.getUnderlineColor(), 123); assert.equal(inputHandler.curAttrData.getUnderlineColorMode(), Attributes.CM_P256); assert.equal(inputHandler.curAttrData.isUnderlineColorRGB(), false); assert.equal(inputHandler.curAttrData.isUnderlineColorPalette(), true); assert.equal(inputHandler.curAttrData.isUnderlineColorDefault(), false); - inputHandler.parse('ab'); + await inputHandler.parseP('ab'); bufferService.buffer!.lines.get(0)!.loadCell(1, cell); assert.equal(cell.getUnderlineColor(), 123); assert.equal(cell.getUnderlineColorMode(), Attributes.CM_P256); @@ -1587,13 +1759,13 @@ describe('InputHandler', () => { assert.equal(cell.isUnderlineColorPalette(), true); assert.equal(cell.isUnderlineColorDefault(), false); - inputHandler.parse('\x1b[4:0m'); + await inputHandler.parseP('\x1b[4:0m'); assert.equal(inputHandler.curAttrData.getUnderlineColor(), inputHandler.curAttrData.getFgColor()); assert.equal(inputHandler.curAttrData.getUnderlineColorMode(), inputHandler.curAttrData.getFgColorMode()); assert.equal(inputHandler.curAttrData.isUnderlineColorRGB(), inputHandler.curAttrData.isFgRGB()); assert.equal(inputHandler.curAttrData.isUnderlineColorPalette(), inputHandler.curAttrData.isFgPalette()); assert.equal(inputHandler.curAttrData.isUnderlineColorDefault(), inputHandler.curAttrData.isFgDefault()); - inputHandler.parse('a'); + await inputHandler.parseP('a'); bufferService.buffer!.lines.get(0)!.loadCell(1, cell); assert.equal(cell.getUnderlineColor(), 123); assert.equal(cell.getUnderlineColorMode(), Attributes.CM_P256); @@ -1607,15 +1779,15 @@ describe('InputHandler', () => { assert.equal(cell.isUnderlineColorPalette(), inputHandler.curAttrData.isFgPalette()); assert.equal(cell.isUnderlineColorDefault(), inputHandler.curAttrData.isFgDefault()); - inputHandler.parse('\x1b[4m'); - inputHandler.parse('\x1b[58;2::1:2:3m'); + await inputHandler.parseP('\x1b[4m'); + await inputHandler.parseP('\x1b[58;2::1:2:3m'); assert.equal(inputHandler.curAttrData.getUnderlineColor(), (1 << 16) | (2 << 8) | 3); assert.equal(inputHandler.curAttrData.getUnderlineColorMode(), Attributes.CM_RGB); assert.equal(inputHandler.curAttrData.isUnderlineColorRGB(), true); assert.equal(inputHandler.curAttrData.isUnderlineColorPalette(), false); assert.equal(inputHandler.curAttrData.isUnderlineColorDefault(), false); - inputHandler.parse('a'); - inputHandler.parse('\x1b[24m'); + await inputHandler.parseP('a'); + await inputHandler.parseP('\x1b[24m'); bufferService.buffer!.lines.get(0)!.loadCell(1, cell); assert.equal(cell.getUnderlineColor(), 123); assert.equal(cell.getUnderlineColorMode(), Attributes.CM_P256); @@ -1644,52 +1816,251 @@ describe('InputHandler', () => { }); }); describe('DECSTR', () => { - beforeEach(() => { + beforeEach(async () => { bufferService.resize(10, 5); optionsService.options.scrollback = 1; - inputHandler.parse('01234567890123'); + await inputHandler.parseP('01234567890123'); }); - it('should reset IRM', () => { - inputHandler.parse('\x1b[4h'); + it('should reset IRM', async () => { + await inputHandler.parseP('\x1b[4h'); assert.equal(coreService.modes.insertMode, true); - inputHandler.parse('\x1b[!p'); + await inputHandler.parseP('\x1b[!p'); assert.equal(coreService.modes.insertMode, false); }); - it('should reset cursor visibility', () => { - inputHandler.parse('\x1b[?25l'); + it('should reset cursor visibility', async () => { + await inputHandler.parseP('\x1b[?25l'); assert.equal(coreService.isCursorHidden, true); - inputHandler.parse('\x1b[!p'); + await inputHandler.parseP('\x1b[!p'); assert.equal(coreService.isCursorHidden, false); }); - it('should reset scroll margins', () => { - inputHandler.parse('\x1b[2;4r'); + it('should reset scroll margins', async () => { + await inputHandler.parseP('\x1b[2;4r'); assert.equal(bufferService.buffer.scrollTop, 1); assert.equal(bufferService.buffer.scrollBottom, 3); - inputHandler.parse('\x1b[!p'); + await inputHandler.parseP('\x1b[!p'); assert.equal(bufferService.buffer.scrollTop, 0); assert.equal(bufferService.buffer.scrollBottom, bufferService.rows - 1); }); - it('should reset text attributes', () => { - inputHandler.parse('\x1b[1;2;32;43m'); + it('should reset text attributes', async () => { + await inputHandler.parseP('\x1b[1;2;32;43m'); assert.equal(!!inputHandler.curAttrData.isBold(), true); - inputHandler.parse('\x1b[!p'); + await inputHandler.parseP('\x1b[!p'); assert.equal(!!inputHandler.curAttrData.isBold(), false); assert.equal(inputHandler.curAttrData.fg, 0); assert.equal(inputHandler.curAttrData.bg, 0); }); - it('should reset DECSC data', () => { - inputHandler.parse('\x1b7'); + it('should reset DECSC data', async () => { + await inputHandler.parseP('\x1b7'); assert.equal(bufferService.buffer.savedX, 4); assert.equal(bufferService.buffer.savedY, 1); - inputHandler.parse('\x1b[!p'); + await inputHandler.parseP('\x1b[!p'); assert.equal(bufferService.buffer.savedX, 0); assert.equal(bufferService.buffer.savedY, 0); }); - it('should reset DECOM', () => { - inputHandler.parse('\x1b[?6h'); + it('should reset DECOM', async () => { + await inputHandler.parseP('\x1b[?6h'); assert.equal(coreService.decPrivateModes.origin, true); - inputHandler.parse('\x1b[!p'); + await inputHandler.parseP('\x1b[!p'); assert.equal(coreService.decPrivateModes.origin, false); }); }); + describe('OSC', () => { + it('4: should parse correct Ansi color change data', () => { + // this is testing a private method + const event = inputHandler.parseAnsiColorChange('19;rgb:a1/b2/c3'); + + assert.isNotNull(event); + assert.deepEqual(event!.colors[0], { colorIndex: 19, red: 0xa1, green: 0xb2, blue: 0xc3 }); + }); + + it('4: should ignore incorrect Ansi color change data', () => { + // this is testing a private method + assert.isNull(inputHandler.parseAnsiColorChange('17;rgb:a/b/c')); + assert.isNull(inputHandler.parseAnsiColorChange('17;rgb:#aabbcc')); + assert.isNull(inputHandler.parseAnsiColorChange('17;rgba:aa/bb/cc')); + assert.isNull(inputHandler.parseAnsiColorChange('rgb:aa/bb/cc')); + }); + + it('4: should parse a list of Ansi color changes', () => { + // this is testing a private method + const event = inputHandler.parseAnsiColorChange('19;rgb:a1/b2/c3;17;rgb:00/11/22;255;rgb:01/ef/2d'); + + assert.isNotNull(event); + assert.equal(event!.colors.length, 3); + assert.deepEqual(event!.colors[0], { colorIndex: 19, red: 0xa1, green: 0xb2, blue: 0xc3 }); + assert.deepEqual(event!.colors[1], { colorIndex: 17, red: 0x00, green: 0x11, blue: 0x22 }); + assert.deepEqual(event!.colors[2], { colorIndex: 255, red: 0x01, green: 0xef, blue: 0x2d }); + }); + it('4: should ignore incorrect colors in a list of Ansi color changes', () => { + // this is testing a private method + const event = inputHandler.parseAnsiColorChange('19;rgb:a1/b2/c3;17;rgb:WR/ON/G;255;rgb:01/ef/2d'); + + assert.equal(event!.colors.length, 2); + assert.deepEqual(event!.colors[0], { colorIndex: 19, red: 0xa1, green: 0xb2, blue: 0xc3 }); + assert.deepEqual(event!.colors[1], { colorIndex: 255, red: 0x01, green: 0xef, blue: 0x2d }); + }); + it('4: should be case insensitive when parsing Ansi color changes', () => { + // this is testing a private method + const event = inputHandler.parseAnsiColorChange('19;rGb:A1/b2/C3'); + + assert.equal(event!.colors.length, 1); + assert.deepEqual(event!.colors[0], { colorIndex: 19, red: 0xa1, green: 0xb2, blue: 0xc3 }); + }); + it('4: should fire event on Ansi color change', async () => { + return new Promise(async r => { + inputHandler.onAnsiColorChange(e => { + assert.isNotNull(e); + assert.isNotNull(e!.colors); + assert.deepEqual(e!.colors[0], { colorIndex: 17, red: 0x1a, green: 0x2b, blue: 0x3c }); + assert.deepEqual(e!.colors[1], { colorIndex: 12, red: 0x11, green: 0x22, blue: 0x33 }); + r(); + }); + await inputHandler.parseP('\x1b]4;17;rgb:1a/2b/3c;12;rgb:11/22/33\x1b\\'); + }); + }); + }); + + // issue #3362 and #2979 + describe('EL/ED cursor at buffer.cols', () => { + beforeEach(() => { + bufferService.resize(10, 5); + }); + describe('cursor should stay at cols / does not overflow', () => { + it('EL0', async () => { + await inputHandler.parseP('##########\x1b[0K'); + assert.equal(bufferService.buffer.x, 10); + assert.deepEqual(getLines(bufferService), ['#'.repeat(10), '', '', '', '']); + }); + it('EL1', async () => { + await inputHandler.parseP('##########\x1b[1K'); + assert.equal(bufferService.buffer.x, 10); + assert.deepEqual(getLines(bufferService), ['', '', '', '', '']); + }); + it('EL2', async () => { + await inputHandler.parseP('##########\x1b[2K'); + assert.equal(bufferService.buffer.x, 10); + assert.deepEqual(getLines(bufferService), ['', '', '', '', '']); + }); + it('ED0', async () => { + await inputHandler.parseP('##########\x1b[0J'); + assert.equal(bufferService.buffer.x, 10); + assert.deepEqual(getLines(bufferService), ['#'.repeat(10), '', '', '', '']); + }); + it('ED1', async () => { + await inputHandler.parseP('##########\x1b[1J'); + assert.equal(bufferService.buffer.x, 10); + assert.deepEqual(getLines(bufferService), ['', '', '', '', '']); + }); + it('ED2', async () => { + await inputHandler.parseP('##########\x1b[2J'); + assert.equal(bufferService.buffer.x, 10); + assert.deepEqual(getLines(bufferService), ['', '', '', '', '']); + }); + it('ED3', async () => { + await inputHandler.parseP('##########\x1b[3J'); + assert.equal(bufferService.buffer.x, 10); + assert.deepEqual(getLines(bufferService), ['#'.repeat(10), '', '', '', '']); + }); + }); + describe('following sequence keeps working', () => { + // sequences to test (cursor related ones) + const SEQ = [ + /* ICH */ '\x1b[10@', + /* SL */ '\x1b[10 @', + /* CUU */ '\x1b[10A', + /* SR */ '\x1b[10 A', + /* CUD */ '\x1b[10B', + /* CUF */ '\x1b[10C', + /* CUB */ '\x1b[10D', + /* CNL */ '\x1b[10E', + /* CPL */ '\x1b[10F', + /* CHA */ '\x1b[10G', + /* CUP */ '\x1b[10;10H', + /* CHT */ '\x1b[10I', + /* IL */ '\x1b[10L', + /* DL */ '\x1b[10M', + /* DCH */ '\x1b[10P', + /* SU */ '\x1b[10S', + /* SD */ '\x1b[10T', + /* ECH */ '\x1b[10X', + /* CBT */ '\x1b[10Z', + /* HPA */ '\x1b[10`', + /* HPR */ '\x1b[10a', + /* REP */ '\x1b[10b', + /* VPA */ '\x1b[10d', + /* VPR */ '\x1b[10e', + /* HVP */ '\x1b[10;10f', + /* TBC */ '\x1b[0g', + /* SCOSC */ '\x1b[s', + /* DECIC */ '\x1b[10\'}', + /* DECDC */ '\x1b[10\'~' + ]; + it('cursor never advances beyond cols', async () => { + for (const seq of SEQ) { + await inputHandler.parseP('##########\x1b[2J' + seq); + assert.equal(bufferService.buffer.x <= bufferService.cols, true); + inputHandler.reset(); + bufferService.reset(); + } + }); + }); + }); +}); + + +describe('InputHandler - async handlers', () => { + let bufferService: IBufferService; + let coreService: ICoreService; + let optionsService: MockOptionsService; + let inputHandler: TestInputHandler; + + beforeEach(() => { + optionsService = new MockOptionsService(); + bufferService = new BufferService(optionsService); + bufferService.resize(80, 30); + coreService = new CoreService(() => { }, bufferService, new MockLogService(), optionsService); + coreService.onData(data => { console.log(data); }); + + inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockDirtyRowService(), new MockLogService(), optionsService, new MockCoreMouseService(), new MockUnicodeService()); + }); + + it('async CUP with CPR check', async () => { + const cup: number[][] = []; + const cpr: number[][] = []; + inputHandler.registerCsiHandler({ final: 'H' }, async params => { + cup.push(params.toArray() as number[]); + await new Promise(res => setTimeout(res, 50)); + // late call of real repositioning + return inputHandler.cursorPosition(params); + }); + coreService.onData(data => { + const m = data.match(/\x1b\[(.*?);(.*?)R/); + if (m) { + cpr.push([parseInt(m[1]), parseInt(m[2])]); + } + }); + await inputHandler.parseP('aaa\x1b[3;4H\x1b[6nbbb\x1b[6;8H\x1b[6n'); + assert.deepEqual(cup, cpr); + }); + it('async OSC between', async () => { + inputHandler.registerOscHandler(1000, async data => { + await new Promise(res => setTimeout(res, 50)); + assert.deepEqual(getLines(bufferService, 2), ['hello world!', '']); + assert.equal(data, 'some data'); + return true; + }); + await inputHandler.parseP('hello world!\r\n\x1b]1000;some data\x07second line'); + assert.deepEqual(getLines(bufferService, 2), ['hello world!', 'second line']); + }); + it('async DCS between', async () => { + inputHandler.registerDcsHandler({ final: 'a' }, async (data, params) => { + await new Promise(res => setTimeout(res, 50)); + assert.deepEqual(getLines(bufferService, 2), ['hello world!', '']); + assert.equal(data, 'some data'); + assert.deepEqual(params.toArray(), [1, 2]); + return true; + }); + await inputHandler.parseP('hello world!\r\n\x1bP1;2asome data\x1b\\second line'); + assert.deepEqual(getLines(bufferService, 2), ['hello world!', 'second line']); + }); }); diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index dbf695e6..d4354e90 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -4,7 +4,7 @@ * @license MIT */ -import { IInputHandler, IAttributeData, IDisposable, IWindowOptions } from 'common/Types'; +import { IInputHandler, IAttributeData, IDisposable, IWindowOptions, IAnsiColorChangeEvent, IParseStack } from 'common/Types'; import { C0, C1 } from 'common/data/EscapeSequences'; import { CHARSETS, DEFAULT_CHARSET } from 'common/data/Charsets'; import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser'; @@ -17,14 +17,14 @@ import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams, IFunctionId import { NULL_CELL_CODE, NULL_CELL_WIDTH, Attributes, FgFlags, BgFlags, Content, UnderlineStyle } from 'common/buffer/Constants'; import { CellData } from 'common/buffer/CellData'; import { AttributeData } from 'common/buffer/AttributeData'; -import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService } from 'common/services/Services'; +import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService, LogLevelEnum } from 'common/services/Services'; import { OscHandler } from 'common/parser/OscParser'; import { DcsHandler } from 'common/parser/DcsParser'; /** * Map collect to glevel. Used in `selectCharset`. */ -const GLEVEL: {[key: string]: number} = {'(': 0, ')': 1, '*': 2, '+': 3, '-': 1, '.': 2}; +const GLEVEL: {[key: string]: number} = { '(': 0, ')': 1, '*': 2, '+': 3, '-': 1, '.': 2 }; /** * VT commands done by the parser - FIXME: move this to the parser? @@ -97,6 +97,9 @@ export enum WindowsOptionsReportType { GET_CELL_SIZE_PIXELS = 1 } +// create a warning log if an async handler takes longer than the limit (in ms) +const SLOW_ASYNC_LIMIT = 5000; + /** * DCS subparser implementations */ @@ -146,36 +149,42 @@ class DECRQSS implements IDcsHandler { this._data = concat(this._data, data.subarray(start, end)); } - public unhook(success: boolean): void { + public unhook(success: boolean): boolean { if (!success) { this._data = new Uint32Array(0); - return; + return true; } const data = utf32ToString(this._data); this._data = new Uint32Array(0); switch (data) { // valid: DCS 1 $ r Pt ST (xterm) case '"q': // DECSCA - return this._coreService.triggerDataEvent(`${C0.ESC}P1$r0"q${C0.ESC}\\`); + this._coreService.triggerDataEvent(`${C0.ESC}P1$r0"q${C0.ESC}\\`); + break; case '"p': // DECSCL - return this._coreService.triggerDataEvent(`${C0.ESC}P1$r61;1"p${C0.ESC}\\`); + this._coreService.triggerDataEvent(`${C0.ESC}P1$r61;1"p${C0.ESC}\\`); + break; case 'r': // DECSTBM const pt = '' + (this._bufferService.buffer.scrollTop + 1) + ';' + (this._bufferService.buffer.scrollBottom + 1) + 'r'; - return this._coreService.triggerDataEvent(`${C0.ESC}P1$r${pt}${C0.ESC}\\`); + this._coreService.triggerDataEvent(`${C0.ESC}P1$r${pt}${C0.ESC}\\`); + break; case 'm': // SGR // TODO: report real settings instead of 0m - return this._coreService.triggerDataEvent(`${C0.ESC}P1$r0m${C0.ESC}\\`); + this._coreService.triggerDataEvent(`${C0.ESC}P1$r0m${C0.ESC}\\`); + break; case ' q': // DECSCUSR - const STYLES: {[key: string]: number} = {'block': 2, 'underline': 4, 'bar': 6}; + const STYLES: {[key: string]: number} = { 'block': 2, 'underline': 4, 'bar': 6 }; let style = STYLES[this._optionsService.options.cursorStyle]; style -= this._optionsService.options.cursorBlink ? 1 : 0; - return this._coreService.triggerDataEvent(`${C0.ESC}P1$r${style} q${C0.ESC}\\`); + this._coreService.triggerDataEvent(`${C0.ESC}P1$r${style} q${C0.ESC}\\`); + break; default: // invalid: DCS 0 $ r Pt ST (xterm) this._logService.debug('Unknown DCS $q %s', data); this._coreService.triggerDataEvent(`${C0.ESC}P0$r${C0.ESC}\\`); } + return true; } } @@ -231,8 +240,6 @@ export class InputHandler extends Disposable implements IInputHandler { public get onRequestRefreshRows(): IEvent { return this._onRequestRefreshRows.event; } private _onRequestReset = new EventEmitter(); public get onRequestReset(): IEvent { return this._onRequestReset.event; } - private _onRequestScroll = new EventEmitter(); - public get onRequestScroll(): IEvent { return this._onRequestScroll.event; } private _onRequestSyncScrollBar = new EventEmitter(); public get onRequestSyncScrollBar(): IEvent { return this._onRequestSyncScrollBar.event; } private _onRequestWindowsOptionsReport = new EventEmitter(); @@ -250,6 +257,16 @@ export class InputHandler extends Disposable implements IInputHandler { public get onScroll(): IEvent { return this._onScroll.event; } private _onTitleChange = new EventEmitter(); public get onTitleChange(): IEvent { return this._onTitleChange.event; } + private _onAnsiColorChange = new EventEmitter(); + public get onAnsiColorChange(): IEvent { return this._onAnsiColorChange.event; } + + private _parseStack: IParseStack = { + paused: false, + cursorStartX: 0, + cursorStartY: 0, + decodedLength: 0, + position: 0 + }; constructor( private readonly _bufferService: IBufferService, @@ -295,53 +312,53 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI handler */ - this._parser.setCsiHandler({final: '@'}, params => this.insertChars(params)); - this._parser.setCsiHandler({intermediates: ' ', final: '@'}, params => this.scrollLeft(params)); - this._parser.setCsiHandler({final: 'A'}, params => this.cursorUp(params)); - this._parser.setCsiHandler({intermediates: ' ', final: 'A'}, params => this.scrollRight(params)); - this._parser.setCsiHandler({final: 'B'}, params => this.cursorDown(params)); - this._parser.setCsiHandler({final: 'C'}, params => this.cursorForward(params)); - this._parser.setCsiHandler({final: 'D'}, params => this.cursorBackward(params)); - this._parser.setCsiHandler({final: 'E'}, params => this.cursorNextLine(params)); - this._parser.setCsiHandler({final: 'F'}, params => this.cursorPrecedingLine(params)); - this._parser.setCsiHandler({final: 'G'}, params => this.cursorCharAbsolute(params)); - this._parser.setCsiHandler({final: 'H'}, params => this.cursorPosition(params)); - this._parser.setCsiHandler({final: 'I'}, params => this.cursorForwardTab(params)); - this._parser.setCsiHandler({final: 'J'}, params => this.eraseInDisplay(params)); - this._parser.setCsiHandler({prefix: '?', final: 'J'}, params => this.eraseInDisplay(params)); - this._parser.setCsiHandler({final: 'K'}, params => this.eraseInLine(params)); - this._parser.setCsiHandler({prefix: '?', final: 'K'}, params => this.eraseInLine(params)); - this._parser.setCsiHandler({final: 'L'}, params => this.insertLines(params)); - this._parser.setCsiHandler({final: 'M'}, params => this.deleteLines(params)); - this._parser.setCsiHandler({final: 'P'}, params => this.deleteChars(params)); - this._parser.setCsiHandler({final: 'S'}, params => this.scrollUp(params)); - this._parser.setCsiHandler({final: 'T'}, params => this.scrollDown(params)); - this._parser.setCsiHandler({final: 'X'}, params => this.eraseChars(params)); - this._parser.setCsiHandler({final: 'Z'}, params => this.cursorBackwardTab(params)); - this._parser.setCsiHandler({final: '`'}, params => this.charPosAbsolute(params)); - this._parser.setCsiHandler({final: 'a'}, params => this.hPositionRelative(params)); - this._parser.setCsiHandler({final: 'b'}, params => this.repeatPrecedingCharacter(params)); - this._parser.setCsiHandler({final: 'c'}, params => this.sendDeviceAttributesPrimary(params)); - this._parser.setCsiHandler({prefix: '>', final: 'c'}, params => this.sendDeviceAttributesSecondary(params)); - this._parser.setCsiHandler({final: 'd'}, params => this.linePosAbsolute(params)); - this._parser.setCsiHandler({final: 'e'}, params => this.vPositionRelative(params)); - this._parser.setCsiHandler({final: 'f'}, params => this.hVPosition(params)); - this._parser.setCsiHandler({final: 'g'}, params => this.tabClear(params)); - this._parser.setCsiHandler({final: 'h'}, params => this.setMode(params)); - this._parser.setCsiHandler({prefix: '?', final: 'h'}, params => this.setModePrivate(params)); - this._parser.setCsiHandler({final: 'l'}, params => this.resetMode(params)); - this._parser.setCsiHandler({prefix: '?', final: 'l'}, params => this.resetModePrivate(params)); - this._parser.setCsiHandler({final: 'm'}, params => this.charAttributes(params)); - this._parser.setCsiHandler({final: 'n'}, params => this.deviceStatus(params)); - this._parser.setCsiHandler({prefix: '?', final: 'n'}, params => this.deviceStatusPrivate(params)); - this._parser.setCsiHandler({intermediates: '!', final: 'p'}, params => this.softReset(params)); - this._parser.setCsiHandler({intermediates: ' ', final: 'q'}, params => this.setCursorStyle(params)); - this._parser.setCsiHandler({final: 'r'}, params => this.setScrollRegion(params)); - this._parser.setCsiHandler({final: 's'}, params => this.saveCursor(params)); - this._parser.setCsiHandler({final: 't'}, params => this.windowOptions(params)); - this._parser.setCsiHandler({final: 'u'}, params => this.restoreCursor(params)); - this._parser.setCsiHandler({intermediates: '\'', final: '}'}, params => this.insertColumns(params)); - this._parser.setCsiHandler({intermediates: '\'', final: '~'}, params => this.deleteColumns(params)); + this._parser.registerCsiHandler({ final: '@' }, params => this.insertChars(params)); + this._parser.registerCsiHandler({ intermediates: ' ', final: '@' }, params => this.scrollLeft(params)); + this._parser.registerCsiHandler({ final: 'A' }, params => this.cursorUp(params)); + this._parser.registerCsiHandler({ intermediates: ' ', final: 'A' }, params => this.scrollRight(params)); + this._parser.registerCsiHandler({ final: 'B' }, params => this.cursorDown(params)); + this._parser.registerCsiHandler({ final: 'C' }, params => this.cursorForward(params)); + this._parser.registerCsiHandler({ final: 'D' }, params => this.cursorBackward(params)); + this._parser.registerCsiHandler({ final: 'E' }, params => this.cursorNextLine(params)); + this._parser.registerCsiHandler({ final: 'F' }, params => this.cursorPrecedingLine(params)); + this._parser.registerCsiHandler({ final: 'G' }, params => this.cursorCharAbsolute(params)); + this._parser.registerCsiHandler({ final: 'H' }, params => this.cursorPosition(params)); + this._parser.registerCsiHandler({ final: 'I' }, params => this.cursorForwardTab(params)); + this._parser.registerCsiHandler({ final: 'J' }, params => this.eraseInDisplay(params)); + this._parser.registerCsiHandler({ prefix: '?', final: 'J' }, params => this.eraseInDisplay(params)); + this._parser.registerCsiHandler({ final: 'K' }, params => this.eraseInLine(params)); + this._parser.registerCsiHandler({ prefix: '?', final: 'K' }, params => this.eraseInLine(params)); + this._parser.registerCsiHandler({ final: 'L' }, params => this.insertLines(params)); + this._parser.registerCsiHandler({ final: 'M' }, params => this.deleteLines(params)); + this._parser.registerCsiHandler({ final: 'P' }, params => this.deleteChars(params)); + this._parser.registerCsiHandler({ final: 'S' }, params => this.scrollUp(params)); + this._parser.registerCsiHandler({ final: 'T' }, params => this.scrollDown(params)); + this._parser.registerCsiHandler({ final: 'X' }, params => this.eraseChars(params)); + this._parser.registerCsiHandler({ final: 'Z' }, params => this.cursorBackwardTab(params)); + this._parser.registerCsiHandler({ final: '`' }, params => this.charPosAbsolute(params)); + this._parser.registerCsiHandler({ final: 'a' }, params => this.hPositionRelative(params)); + this._parser.registerCsiHandler({ final: 'b' }, params => this.repeatPrecedingCharacter(params)); + this._parser.registerCsiHandler({ final: 'c' }, params => this.sendDeviceAttributesPrimary(params)); + this._parser.registerCsiHandler({ prefix: '>', final: 'c' }, params => this.sendDeviceAttributesSecondary(params)); + this._parser.registerCsiHandler({ final: 'd' }, params => this.linePosAbsolute(params)); + this._parser.registerCsiHandler({ final: 'e' }, params => this.vPositionRelative(params)); + this._parser.registerCsiHandler({ final: 'f' }, params => this.hVPosition(params)); + this._parser.registerCsiHandler({ final: 'g' }, params => this.tabClear(params)); + this._parser.registerCsiHandler({ final: 'h' }, params => this.setMode(params)); + this._parser.registerCsiHandler({ prefix: '?', final: 'h' }, params => this.setModePrivate(params)); + this._parser.registerCsiHandler({ final: 'l' }, params => this.resetMode(params)); + this._parser.registerCsiHandler({ prefix: '?', final: 'l' }, params => this.resetModePrivate(params)); + this._parser.registerCsiHandler({ final: 'm' }, params => this.charAttributes(params)); + this._parser.registerCsiHandler({ final: 'n' }, params => this.deviceStatus(params)); + this._parser.registerCsiHandler({ prefix: '?', final: 'n' }, params => this.deviceStatusPrivate(params)); + this._parser.registerCsiHandler({ intermediates: '!', final: 'p' }, params => this.softReset(params)); + this._parser.registerCsiHandler({ intermediates: ' ', final: 'q' }, params => this.setCursorStyle(params)); + this._parser.registerCsiHandler({ final: 'r' }, params => this.setScrollRegion(params)); + this._parser.registerCsiHandler({ final: 's' }, params => this.saveCursor(params)); + this._parser.registerCsiHandler({ final: 't' }, params => this.windowOptions(params)); + this._parser.registerCsiHandler({ final: 'u' }, params => this.restoreCursor(params)); + this._parser.registerCsiHandler({ intermediates: '\'', final: '}' }, params => this.insertColumns(params)); + this._parser.registerCsiHandler({ intermediates: '\'', final: '~' }, params => this.deleteColumns(params)); /** * execute handler @@ -365,13 +382,14 @@ export class InputHandler extends Disposable implements IInputHandler { * OSC handler */ // 0 - icon name + title - this._parser.setOscHandler(0, new OscHandler((data: string) => { this.setTitle(data); this.setIconName(data); })); + this._parser.registerOscHandler(0, new OscHandler(data => { this.setTitle(data); this.setIconName(data); return true; })); // 1 - icon name - this._parser.setOscHandler(1, new OscHandler((data: string) => this.setIconName(data))); + this._parser.registerOscHandler(1, new OscHandler(data => this.setIconName(data))); // 2 - title - this._parser.setOscHandler(2, new OscHandler((data: string) => this.setTitle(data))); + this._parser.registerOscHandler(2, new OscHandler(data => this.setTitle(data))); // 3 - set property X in the form "prop=value" // 4 - Change Color Number + this._parser.registerOscHandler(4, new OscHandler(data => this.setAnsiColor(data))); // 5 - Change Special Color Number // 6 - Enable/disable Special Color Number c // 7 - current directory? (not in xterm spec, see https://gitlab.com/gnachman/iterm2/issues/3939) @@ -406,32 +424,32 @@ export class InputHandler extends Disposable implements IInputHandler { /** * ESC handlers */ - this._parser.setEscHandler({final: '7'}, () => this.saveCursor()); - this._parser.setEscHandler({final: '8'}, () => this.restoreCursor()); - this._parser.setEscHandler({final: 'D'}, () => this.index()); - this._parser.setEscHandler({final: 'E'}, () => this.nextLine()); - this._parser.setEscHandler({final: 'H'}, () => this.tabSet()); - this._parser.setEscHandler({final: 'M'}, () => this.reverseIndex()); - this._parser.setEscHandler({final: '='}, () => this.keypadApplicationMode()); - this._parser.setEscHandler({final: '>'}, () => this.keypadNumericMode()); - this._parser.setEscHandler({final: 'c'}, () => this.fullReset()); - this._parser.setEscHandler({final: 'n'}, () => this.setgLevel(2)); - this._parser.setEscHandler({final: 'o'}, () => this.setgLevel(3)); - this._parser.setEscHandler({final: '|'}, () => this.setgLevel(3)); - this._parser.setEscHandler({final: '}'}, () => this.setgLevel(2)); - this._parser.setEscHandler({final: '~'}, () => this.setgLevel(1)); - this._parser.setEscHandler({intermediates: '%', final: '@'}, () => this.selectDefaultCharset()); - this._parser.setEscHandler({intermediates: '%', final: 'G'}, () => this.selectDefaultCharset()); + this._parser.registerEscHandler({ final: '7' }, () => this.saveCursor()); + this._parser.registerEscHandler({ final: '8' }, () => this.restoreCursor()); + this._parser.registerEscHandler({ final: 'D' }, () => this.index()); + this._parser.registerEscHandler({ final: 'E' }, () => this.nextLine()); + this._parser.registerEscHandler({ final: 'H' }, () => this.tabSet()); + this._parser.registerEscHandler({ final: 'M' }, () => this.reverseIndex()); + this._parser.registerEscHandler({ final: '=' }, () => this.keypadApplicationMode()); + this._parser.registerEscHandler({ final: '>' }, () => this.keypadNumericMode()); + this._parser.registerEscHandler({ final: 'c' }, () => this.fullReset()); + this._parser.registerEscHandler({ final: 'n' }, () => this.setgLevel(2)); + this._parser.registerEscHandler({ final: 'o' }, () => this.setgLevel(3)); + this._parser.registerEscHandler({ final: '|' }, () => this.setgLevel(3)); + this._parser.registerEscHandler({ final: '}' }, () => this.setgLevel(2)); + this._parser.registerEscHandler({ final: '~' }, () => this.setgLevel(1)); + this._parser.registerEscHandler({ intermediates: '%', final: '@' }, () => this.selectDefaultCharset()); + this._parser.registerEscHandler({ intermediates: '%', final: 'G' }, () => this.selectDefaultCharset()); for (const flag in CHARSETS) { - this._parser.setEscHandler({intermediates: '(', final: flag}, () => this.selectCharset('(' + flag)); - this._parser.setEscHandler({intermediates: ')', final: flag}, () => this.selectCharset(')' + flag)); - this._parser.setEscHandler({intermediates: '*', final: flag}, () => this.selectCharset('*' + flag)); - this._parser.setEscHandler({intermediates: '+', final: flag}, () => this.selectCharset('+' + flag)); - this._parser.setEscHandler({intermediates: '-', final: flag}, () => this.selectCharset('-' + flag)); - this._parser.setEscHandler({intermediates: '.', final: flag}, () => this.selectCharset('.' + flag)); - this._parser.setEscHandler({intermediates: '/', final: flag}, () => this.selectCharset('/' + flag)); // TODO: supported? + this._parser.registerEscHandler({ intermediates: '(', final: flag }, () => this.selectCharset('(' + flag)); + this._parser.registerEscHandler({ intermediates: ')', final: flag }, () => this.selectCharset(')' + flag)); + this._parser.registerEscHandler({ intermediates: '*', final: flag }, () => this.selectCharset('*' + flag)); + this._parser.registerEscHandler({ intermediates: '+', final: flag }, () => this.selectCharset('+' + flag)); + this._parser.registerEscHandler({ intermediates: '-', final: flag }, () => this.selectCharset('-' + flag)); + this._parser.registerEscHandler({ intermediates: '.', final: flag }, () => this.selectCharset('.' + flag)); + this._parser.registerEscHandler({ intermediates: '/', final: flag }, () => this.selectCharset('/' + flag)); // TODO: supported? } - this._parser.setEscHandler({intermediates: '#', final: '8'}, () => this.screenAlignmentPattern()); + this._parser.registerEscHandler({ intermediates: '#', final: '8' }, () => this.screenAlignmentPattern()); /** * error handler @@ -444,17 +462,71 @@ export class InputHandler extends Disposable implements IInputHandler { /** * DCS handler */ - this._parser.setDcsHandler({intermediates: '$', final: 'q'}, new DECRQSS(this._bufferService, this._coreService, this._logService, this._optionsService)); + this._parser.registerDcsHandler({ intermediates: '$', final: 'q' }, new DECRQSS(this._bufferService, this._coreService, this._logService, this._optionsService)); } public dispose(): void { super.dispose(); } - public parse(data: string | Uint8Array): void { + /** + * Async parse support. + */ + private _preserveStack(cursorStartX: number, cursorStartY: number, decodedLength: number, position: number): void { + this._parseStack.paused = true; + this._parseStack.cursorStartX = cursorStartX; + this._parseStack.cursorStartY = cursorStartY; + this._parseStack.decodedLength = decodedLength; + this._parseStack.position = position; + } + + private _logSlowResolvingAsync(p: Promise): void { + // log a limited warning about an async handler taking too long + if (this._logService.logLevel <= LogLevelEnum.WARN) { + Promise.race([p, new Promise((res, rej) => setTimeout(() => rej('#SLOW_TIMEOUT'), SLOW_ASYNC_LIMIT))]) + .catch(err => { + if (err !== '#SLOW_TIMEOUT') { + throw err; + } + console.warn(`async parser handler taking longer than ${SLOW_ASYNC_LIMIT} ms`); + }); + } + } + + /** + * Parse call with async handler support. + * + * Whether the stack state got preserved for the next call, is indicated by the return value: + * - undefined (void): + * all handlers were sync, no stack save, continue normally with next chunk + * - Promise\: + * execution stopped at async handler, stack saved, continue with + * same chunk and the promise resolve value as `promiseResult` until the method returns `undefined` + * + * Note: This method should only be called by `Terminal.write` to ensure correct execution order and + * proper continuation of async parser handlers. + */ + public parse(data: string | Uint8Array, promiseResult?: boolean): void | Promise { + let result: void | Promise; let buffer = this._bufferService.buffer; - const cursorStartX = buffer.x; - const cursorStartY = buffer.y; + let cursorStartX = buffer.x; + let cursorStartY = buffer.y; + let start = 0; + const wasPaused = this._parseStack.paused; + + if (wasPaused) { + // assumption: _parseBuffer never mutates between async calls + if (result = this._parser.parse(this._parseBuffer, this._parseStack.decodedLength, promiseResult)) { + this._logSlowResolvingAsync(result); + return result; + } + cursorStartX = this._parseStack.cursorStartX; + cursorStartY = this._parseStack.cursorStartY; + this._parseStack.paused = false; + if (data.length > MAX_PARSEBUFFER_LENGTH) { + start = this._parseStack.position + MAX_PARSEBUFFER_LENGTH; + } + } this._logService.debug('parsing data', data); @@ -466,22 +538,35 @@ export class InputHandler extends Disposable implements IInputHandler { } // Clear the dirty row service so we know which lines changed as a result of parsing - this._dirtyRowService.clearRange(); + // Important: do not clear between async calls, otherwise we lost pending update information. + if (!wasPaused) { + this._dirtyRowService.clearRange(); + } // process big data in smaller chunks if (data.length > MAX_PARSEBUFFER_LENGTH) { - for (let i = 0; i < data.length; i += MAX_PARSEBUFFER_LENGTH) { + for (let i = start; i < data.length; i += MAX_PARSEBUFFER_LENGTH) { const end = i + MAX_PARSEBUFFER_LENGTH < data.length ? i + MAX_PARSEBUFFER_LENGTH : data.length; const len = (typeof data === 'string') ? this._stringDecoder.decode(data.substring(i, end), this._parseBuffer) : this._utf8Decoder.decode(data.subarray(i, end), this._parseBuffer); - this._parser.parse(this._parseBuffer, len); + if (result = this._parser.parse(this._parseBuffer, len)) { + this._preserveStack(cursorStartX, cursorStartY, len, i); + this._logSlowResolvingAsync(result); + return result; + } } } else { - const len = (typeof data === 'string') - ? this._stringDecoder.decode(data, this._parseBuffer) - : this._utf8Decoder.decode(data, this._parseBuffer); - this._parser.parse(this._parseBuffer, len); + if (!wasPaused) { + const len = (typeof data === 'string') + ? this._stringDecoder.decode(data, this._parseBuffer) + : this._utf8Decoder.decode(data, this._parseBuffer); + if (result = this._parser.parse(this._parseBuffer, len)) { + this._preserveStack(cursorStartX, cursorStartY, len, 0); + this._logSlowResolvingAsync(result); + return result; + } + } } buffer = this._bufferService.buffer; @@ -564,7 +649,7 @@ export class InputHandler extends Disposable implements IInputHandler { buffer.y++; if (buffer.y === buffer.scrollBottom + 1) { buffer.y--; - this._onRequestScroll.fire(this._eraseAttrData(), true); + this._bufferService.scroll(this._eraseAttrData(), true); } else { if (buffer.y >= this._bufferService.rows) { buffer.y = this._bufferService.rows - 1; @@ -634,40 +719,40 @@ export class InputHandler extends Disposable implements IInputHandler { } /** - * Forward addCsiHandler from parser. + * Forward registerCsiHandler from parser. */ - public addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable { + public registerCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean | Promise): IDisposable { if (id.final === 't' && !id.prefix && !id.intermediates) { // security: always check whether window option is allowed - return this._parser.addCsiHandler(id, params => { + return this._parser.registerCsiHandler(id, params => { if (!paramToWindowOption(params.params[0], this._optionsService.options.windowOptions)) { return true; } return callback(params); }); } - return this._parser.addCsiHandler(id, callback); + return this._parser.registerCsiHandler(id, callback); } /** - * Forward addDcsHandler from parser. + * Forward registerDcsHandler from parser. */ - public addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean): IDisposable { - return this._parser.addDcsHandler(id, new DcsHandler(callback)); + public registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean | Promise): IDisposable { + return this._parser.registerDcsHandler(id, new DcsHandler(callback)); } /** - * Forward addEscHandler from parser. + * Forward registerEscHandler from parser. */ - public addEscHandler(id: IFunctionIdentifier, callback: () => boolean): IDisposable { - return this._parser.addEscHandler(id, callback); + public registerEscHandler(id: IFunctionIdentifier, callback: () => boolean | Promise): IDisposable { + return this._parser.registerEscHandler(id, callback); } /** - * Forward addOscHandler from parser. + * Forward registerOscHandler from parser. */ - public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable { - return this._parser.addOscHandler(ident, new OscHandler(callback)); + public registerOscHandler(ident: number, callback: (data: string) => boolean | Promise): IDisposable { + return this._parser.registerOscHandler(ident, new OscHandler(callback)); } /** @@ -678,8 +763,9 @@ export class InputHandler extends Disposable implements IInputHandler { * The behavior of the bell is further customizable with `ITerminalOptions.bellStyle` * and `ITerminalOptions.bellSound`. */ - public bell(): void { + public bell(): boolean { this._onRequestBell.fire(); + return true; } /** @@ -692,7 +778,7 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y C0 VT "Vertical Tabulation" "\v, \x0B" "Treated as LF." * @vt: #Y C0 FF "Form Feed" "\f, \x0C" "Treated as LF." */ - public lineFeed(): void { + public lineFeed(): boolean { // make buffer local for faster access const buffer = this._bufferService.buffer; @@ -703,7 +789,7 @@ export class InputHandler extends Disposable implements IInputHandler { buffer.y++; if (buffer.y === buffer.scrollBottom + 1) { buffer.y--; - this._onRequestScroll.fire(this._eraseAttrData()); + this._bufferService.scroll(this._eraseAttrData()); } else if (buffer.y >= this._bufferService.rows) { buffer.y = this._bufferService.rows - 1; } @@ -714,6 +800,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._dirtyRowService.markDirty(buffer.y); this._onLineFeed.fire(); + return true; } /** @@ -722,8 +809,9 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y C0 CR "Carriage Return" "\r, \x0D" "Move the cursor to the beginning of the row." */ - public carriageReturn(): void { + public carriageReturn(): boolean { this._bufferService.buffer.x = 0; + return true; } /** @@ -737,7 +825,7 @@ export class InputHandler extends Disposable implements IInputHandler { * to the end of the previous row. Note that it is not possible to peek back into the scrollbuffer * with the cursor, thus at the home position (top-leftmost cell) this has no effect. */ - public backspace(): void { + public backspace(): boolean { const buffer = this._bufferService.buffer; // reverse wrap-around is disabled @@ -746,7 +834,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (buffer.x > 0) { buffer.x--; } - return; + return true; } // reverse wrap-around is enabled @@ -787,6 +875,7 @@ export class InputHandler extends Disposable implements IInputHandler { } } this._restrictCursor(); + return true; } /** @@ -795,15 +884,16 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y C0 HT "Horizontal Tabulation" "\t, \x09" "Move the cursor to the next character tab stop." */ - public tab(): void { + public tab(): boolean { if (this._bufferService.buffer.x >= this._bufferService.cols) { - return; + return true; } const originalX = this._bufferService.buffer.x; this._bufferService.buffer.x = this._bufferService.buffer.nextStop(); if (this._optionsService.options.screenReaderMode) { this._onA11yTab.fire(this._bufferService.buffer.x - originalX); } + return true; } /** @@ -813,8 +903,9 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #P[Only limited ISO-2022 charset support.] C0 SO "Shift Out" "\x0E" "Switch to an alternative character set." */ - public shiftOut(): void { + public shiftOut(): boolean { this._charsetService.setgLevel(1); + return true; } /** @@ -824,8 +915,9 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y C0 SI "Shift In" "\x0F" "Return to regular character set after Shift Out." */ - public shiftIn(): void { + public shiftIn(): boolean { this._charsetService.setgLevel(0); + return true; } /** @@ -872,7 +964,7 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y CSI CUU "Cursor Up" "CSI Ps A" "Move cursor `Ps` times up (default=1)." * If the cursor would pass the top scroll margin, it will stop there. */ - public cursorUp(params: IParams): void { + public cursorUp(params: IParams): boolean { // stop at scrollTop const diffToTop = this._bufferService.buffer.y - this._bufferService.buffer.scrollTop; if (diffToTop >= 0) { @@ -880,6 +972,7 @@ export class InputHandler extends Disposable implements IInputHandler { } else { this._moveCursor(0, -(params.params[0] || 1)); } + return true; } /** @@ -889,7 +982,7 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y CSI CUD "Cursor Down" "CSI Ps B" "Move cursor `Ps` times down (default=1)." * If the cursor would pass the bottom scroll margin, it will stop there. */ - public cursorDown(params: IParams): void { + public cursorDown(params: IParams): boolean { // stop at scrollBottom const diffToBottom = this._bufferService.buffer.scrollBottom - this._bufferService.buffer.y; if (diffToBottom >= 0) { @@ -897,6 +990,7 @@ export class InputHandler extends Disposable implements IInputHandler { } else { this._moveCursor(0, params.params[0] || 1); } + return true; } /** @@ -905,8 +999,9 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y CSI CUF "Cursor Forward" "CSI Ps C" "Move cursor `Ps` times forward (default=1)." */ - public cursorForward(params: IParams): void { + public cursorForward(params: IParams): boolean { this._moveCursor(params.params[0] || 1, 0); + return true; } /** @@ -915,8 +1010,9 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y CSI CUB "Cursor Backward" "CSI Ps D" "Move cursor `Ps` times backward (default=1)." */ - public cursorBackward(params: IParams): void { + public cursorBackward(params: IParams): boolean { this._moveCursor(-(params.params[0] || 1), 0); + return true; } /** @@ -927,9 +1023,10 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y CSI CNL "Cursor Next Line" "CSI Ps E" "Move cursor `Ps` times down (default=1) and to the first column." * Same as CUD, additionally places the cursor at the first column. */ - public cursorNextLine(params: IParams): void { + public cursorNextLine(params: IParams): boolean { this.cursorDown(params); this._bufferService.buffer.x = 0; + return true; } /** @@ -940,9 +1037,10 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y CSI CPL "Cursor Backward" "CSI Ps F" "Move cursor `Ps` times up (default=1) and to the first column." * Same as CUU, additionally places the cursor at the first column. */ - public cursorPrecedingLine(params: IParams): void { + public cursorPrecedingLine(params: IParams): boolean { this.cursorUp(params); this._bufferService.buffer.x = 0; + return true; } /** @@ -951,8 +1049,9 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y CSI CHA "Cursor Horizontal Absolute" "CSI Ps G" "Move cursor to `Ps`-th column of the active row (default=1)." */ - public cursorCharAbsolute(params: IParams): void { + public cursorCharAbsolute(params: IParams): boolean { this._setCursor((params.params[0] || 1) - 1, this._bufferService.buffer.y); + return true; } /** @@ -964,12 +1063,14 @@ export class InputHandler extends Disposable implements IInputHandler { * If ORIGIN mode is not set, places the cursor to the absolute position within the viewport. * Note that the coordinates are 1-based, thus the top left position starts at `1 ; 1`. */ - public cursorPosition(params: IParams): void { + public cursorPosition(params: IParams): boolean { this._setCursor( // col (params.length >= 2) ? (params.params[1] || 1) - 1 : 0, // row - (params.params[0] || 1) - 1); + (params.params[0] || 1) - 1 + ); + return true; } /** @@ -979,8 +1080,9 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y CSI HPA "Horizontal Position Absolute" "CSI Ps ` " "Same as CHA." */ - public charPosAbsolute(params: IParams): void { + public charPosAbsolute(params: IParams): boolean { this._setCursor((params.params[0] || 1) - 1, this._bufferService.buffer.y); + return true; } /** @@ -989,8 +1091,9 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y CSI HPR "Horizontal Position Relative" "CSI Ps a" "Same as CUF." */ - public hPositionRelative(params: IParams): void { + public hPositionRelative(params: IParams): boolean { this._moveCursor(params.params[0] || 1, 0); + return true; } /** @@ -999,8 +1102,9 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y CSI VPA "Vertical Position Absolute" "CSI Ps d" "Move cursor to `Ps`-th row (default=1)." */ - public linePosAbsolute(params: IParams): void { + public linePosAbsolute(params: IParams): boolean { this._setCursor(this._bufferService.buffer.x, (params.params[0] || 1) - 1); + return true; } /** @@ -1010,8 +1114,9 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y CSI VPR "Vertical Position Relative" "CSI Ps e" "Move cursor `Ps` times down (default=1)." */ - public vPositionRelative(params: IParams): void { + public vPositionRelative(params: IParams): boolean { this._moveCursor(0, params.params[0] || 1); + return true; } /** @@ -1022,8 +1127,9 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y CSI HVP "Horizontal and Vertical Position" "CSI Ps ; Ps f" "Same as CUP." */ - public hVPosition(params: IParams): void { + public hVPosition(params: IParams): boolean { this.cursorPosition(params); + return true; } /** @@ -1037,13 +1143,14 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y CSI TBC "Tab Clear" "CSI Ps g" "Clear tab stops at current position (0) or all (3) (default=0)." * Clearing tabstops off the active row (Ps = 2, VT100) is currently not supported. */ - public tabClear(params: IParams): void { + public tabClear(params: IParams): boolean { const param = params.params[0]; if (param === 0) { delete this._bufferService.buffer.tabs[this._bufferService.buffer.x]; } else if (param === 3) { this._bufferService.buffer.tabs = {}; } + return true; } /** @@ -1052,14 +1159,15 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y CSI CHT "Cursor Horizontal Tabulation" "CSI Ps I" "Move cursor `Ps` times tabs forward (default=1)." */ - public cursorForwardTab(params: IParams): void { + public cursorForwardTab(params: IParams): boolean { if (this._bufferService.buffer.x >= this._bufferService.cols) { - return; + return true; } let param = params.params[0] || 1; while (param--) { this._bufferService.buffer.x = this._bufferService.buffer.nextStop(); } + return true; } /** @@ -1067,9 +1175,9 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y CSI CBT "Cursor Backward Tabulation" "CSI Ps Z" "Move cursor `Ps` tabs backward (default=1)." */ - public cursorBackwardTab(params: IParams): void { + public cursorBackwardTab(params: IParams): boolean { if (this._bufferService.buffer.x >= this._bufferService.cols) { - return; + return true; } let param = params.params[0] || 1; @@ -1079,6 +1187,7 @@ export class InputHandler extends Disposable implements IInputHandler { while (param--) { buffer.x = buffer.prevStop(); } + return true; } @@ -1137,8 +1246,8 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #P[Protection attributes are not supported.] CSI DECSED "Selective Erase In Display" "CSI ? Ps J" "Currently the same as ED." */ - public eraseInDisplay(params: IParams): void { - this._restrictCursor(); + public eraseInDisplay(params: IParams): boolean { + this._restrictCursor(this._bufferService.cols); let j; switch (params.params[0]) { case 0: @@ -1184,6 +1293,7 @@ export class InputHandler extends Disposable implements IInputHandler { } break; } + return true; } /** @@ -1208,8 +1318,8 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #P[Protection attributes are not supported.] CSI DECSEL "Selective Erase In Line" "CSI ? Ps K" "Currently the same as EL." */ - public eraseInLine(params: IParams): void { - this._restrictCursor(); + public eraseInLine(params: IParams): boolean { + this._restrictCursor(this._bufferService.cols); switch (params.params[0]) { case 0: this._eraseInBufferLine(this._bufferService.buffer.y, this._bufferService.buffer.x, this._bufferService.cols); @@ -1222,6 +1332,7 @@ export class InputHandler extends Disposable implements IInputHandler { break; } this._dirtyRowService.markDirty(this._bufferService.buffer.y); + return true; } /** @@ -1233,7 +1344,7 @@ export class InputHandler extends Disposable implements IInputHandler { * The cursor is set to the first column. * IL has no effect if the cursor is outside the scroll margins. */ - public insertLines(params: IParams): void { + public insertLines(params: IParams): boolean { this._restrictCursor(); let param = params.params[0] || 1; @@ -1241,7 +1352,7 @@ export class InputHandler extends Disposable implements IInputHandler { const buffer = this._bufferService.buffer; if (buffer.y > buffer.scrollBottom || buffer.y < buffer.scrollTop) { - return; + return true; } const row: number = buffer.ybase + buffer.y; @@ -1257,6 +1368,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._dirtyRowService.markRangeDirty(buffer.y, buffer.scrollBottom); buffer.x = 0; // see https://vt100.net/docs/vt220-rm/chapter4.html - vt220 only? + return true; } /** @@ -1268,7 +1380,7 @@ export class InputHandler extends Disposable implements IInputHandler { * The cursor is set to the first column. * DL has no effect if the cursor is outside the scroll margins. */ - public deleteLines(params: IParams): void { + public deleteLines(params: IParams): boolean { this._restrictCursor(); let param = params.params[0] || 1; @@ -1276,7 +1388,7 @@ export class InputHandler extends Disposable implements IInputHandler { const buffer = this._bufferService.buffer; if (buffer.y > buffer.scrollBottom || buffer.y < buffer.scrollTop) { - return; + return true; } const row: number = buffer.ybase + buffer.y; @@ -1293,6 +1405,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._dirtyRowService.markRangeDirty(buffer.y, buffer.scrollBottom); buffer.x = 0; // see https://vt100.net/docs/vt220-rm/chapter4.html - vt220 only? + return true; } /** @@ -1306,7 +1419,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * FIXME: check against xterm - should not work outside of scroll margins (see VT520 manual) */ - public insertChars(params: IParams): void { + public insertChars(params: IParams): boolean { this._restrictCursor(); const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.ybase + this._bufferService.buffer.y); if (line) { @@ -1318,6 +1431,7 @@ export class InputHandler extends Disposable implements IInputHandler { ); this._dirtyRowService.markDirty(this._bufferService.buffer.y); } + return true; } /** @@ -1331,7 +1445,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * FIXME: check against xterm - should not work outside of scroll margins (see VT520 manual) */ - public deleteChars(params: IParams): void { + public deleteChars(params: IParams): boolean { this._restrictCursor(); const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.ybase + this._bufferService.buffer.y); if (line) { @@ -1343,6 +1457,7 @@ export class InputHandler extends Disposable implements IInputHandler { ); this._dirtyRowService.markDirty(this._bufferService.buffer.y); } + return true; } /** @@ -1353,7 +1468,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * FIXME: scrolled out lines at top = 1 should add to scrollback (xterm) */ - public scrollUp(params: IParams): void { + public scrollUp(params: IParams): boolean { let param = params.params[0] || 1; // make buffer local for faster access @@ -1364,6 +1479,7 @@ export class InputHandler extends Disposable implements IInputHandler { buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 0, buffer.getBlankLine(this._eraseAttrData())); } this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom); + return true; } /** @@ -1371,7 +1487,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y CSI SD "Scroll Down" "CSI Ps T" "Scroll `Ps` lines down (default=1)." */ - public scrollDown(params: IParams): void { + public scrollDown(params: IParams): boolean { let param = params.params[0] || 1; // make buffer local for faster access @@ -1382,6 +1498,7 @@ export class InputHandler extends Disposable implements IInputHandler { buffer.lines.splice(buffer.ybase + buffer.scrollTop, 0, buffer.getBlankLine(DEFAULT_ATTR_DATA)); } this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom); + return true; } /** @@ -1402,10 +1519,10 @@ export class InputHandler extends Disposable implements IInputHandler { * SL moves the content of all lines within the scroll margins `Ps` times to the left. * SL has no effect outside of the scroll margins. */ - public scrollLeft(params: IParams): void { + public scrollLeft(params: IParams): boolean { const buffer = this._bufferService.buffer; if (buffer.y > buffer.scrollBottom || buffer.y < buffer.scrollTop) { - return; + return true; } const param = params.params[0] || 1; for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) { @@ -1414,6 +1531,7 @@ export class InputHandler extends Disposable implements IInputHandler { line.isWrapped = false; } this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom); + return true; } /** @@ -1435,10 +1553,10 @@ export class InputHandler extends Disposable implements IInputHandler { * Content at the right margin is lost. * SL has no effect outside of the scroll margins. */ - public scrollRight(params: IParams): void { + public scrollRight(params: IParams): boolean { const buffer = this._bufferService.buffer; if (buffer.y > buffer.scrollBottom || buffer.y < buffer.scrollTop) { - return; + return true; } const param = params.params[0] || 1; for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) { @@ -1447,6 +1565,7 @@ export class InputHandler extends Disposable implements IInputHandler { line.isWrapped = false; } this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom); + return true; } /** @@ -1458,10 +1577,10 @@ export class InputHandler extends Disposable implements IInputHandler { * moving content to the right. Content at the right margin is lost. * DECIC has no effect outside the scrolling margins. */ - public insertColumns(params: IParams): void { + public insertColumns(params: IParams): boolean { const buffer = this._bufferService.buffer; if (buffer.y > buffer.scrollBottom || buffer.y < buffer.scrollTop) { - return; + return true; } const param = params.params[0] || 1; for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) { @@ -1470,6 +1589,7 @@ export class InputHandler extends Disposable implements IInputHandler { line.isWrapped = false; } this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom); + return true; } /** @@ -1481,10 +1601,10 @@ export class InputHandler extends Disposable implements IInputHandler { * moving content to the left. Blank columns are added at the right margin. * DECDC has no effect outside the scrolling margins. */ - public deleteColumns(params: IParams): void { + public deleteColumns(params: IParams): boolean { const buffer = this._bufferService.buffer; if (buffer.y > buffer.scrollBottom || buffer.y < buffer.scrollTop) { - return; + return true; } const param = params.params[0] || 1; for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) { @@ -1493,6 +1613,7 @@ export class InputHandler extends Disposable implements IInputHandler { line.isWrapped = false; } this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom); + return true; } /** @@ -1503,7 +1624,7 @@ export class InputHandler extends Disposable implements IInputHandler { * ED erases `Ps` characters from current cursor position to the right. * ED works inside or outside the scrolling margins. */ - public eraseChars(params: IParams): void { + public eraseChars(params: IParams): boolean { this._restrictCursor(); const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.ybase + this._bufferService.buffer.y); if (line) { @@ -1515,6 +1636,7 @@ export class InputHandler extends Disposable implements IInputHandler { ); this._dirtyRowService.markDirty(this._bufferService.buffer.y); } + return true; } /** @@ -1544,9 +1666,9 @@ export class InputHandler extends Disposable implements IInputHandler { * REP has no effect if the sequence does not follow a printable ASCII character * (NOOP for any other sequence in between or NON ASCII characters). */ - public repeatPrecedingCharacter(params: IParams): void { + public repeatPrecedingCharacter(params: IParams): boolean { if (!this._parser.precedingCodepoint) { - return; + return true; } // call print to insert the chars and handle correct wrapping const length = params.params[0] || 1; @@ -1555,6 +1677,7 @@ export class InputHandler extends Disposable implements IInputHandler { data[i] = this._parser.precedingCodepoint; } this.print(data, 0, data.length); + return true; } /** @@ -1582,15 +1705,16 @@ export class InputHandler extends Disposable implements IInputHandler { * * TODO: fix and cleanup response */ - public sendDeviceAttributesPrimary(params: IParams): void { + public sendDeviceAttributesPrimary(params: IParams): boolean { if (params.params[0] > 0) { - return; + return true; } if (this._is('xterm') || this._is('rxvt-unicode') || this._is('screen')) { this._coreService.triggerDataEvent(C0.ESC + '[?1;2c'); } else if (this._is('linux')) { this._coreService.triggerDataEvent(C0.ESC + '[?6c'); } + return true; } /** @@ -1617,9 +1741,9 @@ export class InputHandler extends Disposable implements IInputHandler { * * TODO: fix and cleanup response */ - public sendDeviceAttributesSecondary(params: IParams): void { + public sendDeviceAttributesSecondary(params: IParams): boolean { if (params.params[0] > 0) { - return; + return true; } // xterm and urxvt // seem to spit this @@ -1635,6 +1759,7 @@ export class InputHandler extends Disposable implements IInputHandler { } else if (this._is('screen')) { this._coreService.triggerDataEvent(C0.ESC + '[>83;40003;0c'); } + return true; } /** @@ -1662,7 +1787,7 @@ export class InputHandler extends Disposable implements IInputHandler { * | 12 | Send/receive (SRM). Always off. | #N | * | 20 | Automatic Newline (LNM). Always off. | #N | */ - public setMode(params: IParams): void { + public setMode(params: IParams): boolean { for (let i = 0; i < params.length; i++) { switch (params.params[i]) { case 4: @@ -1673,6 +1798,7 @@ export class InputHandler extends Disposable implements IInputHandler { break; } } + return true; } /** @@ -1788,7 +1914,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * FIXME: implement DECSCNM, 1049 should clear altbuffer */ - public setModePrivate(params: IParams): void { + public setModePrivate(params: IParams): boolean { for (let i = 0; i < params.length; i++) { switch (params.params[i]) { case 1: @@ -1881,6 +2007,7 @@ export class InputHandler extends Disposable implements IInputHandler { break; } } + return true; } @@ -1904,7 +2031,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * FIXME: why is LNM commented out? */ - public resetMode(params: IParams): void { + public resetMode(params: IParams): boolean { for (let i = 0; i < params.length; i++) { switch (params.params[i]) { case 4: @@ -1915,6 +2042,7 @@ export class InputHandler extends Disposable implements IInputHandler { break; } } + return true; } /** @@ -2026,7 +2154,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * FIXME: DECCOLM is currently broken (already fixed in window options PR) */ - public resetModePrivate(params: IParams): void { + public resetModePrivate(params: IParams): boolean { for (let i = 0; i < params.length; i++) { switch (params.params[i]) { case 1: @@ -2103,6 +2231,7 @@ export class InputHandler extends Disposable implements IInputHandler { break; } } + return true; } /** @@ -2236,7 +2365,7 @@ export class InputHandler extends Disposable implements IInputHandler { * | 6 | Rapidly blinking. | #N | * | 7 | Inverse. Flips foreground and background color. | #Y | * | 8 | Invisible (hidden). | #Y | - * | 9 | Crossed-out characters. | #N | + * | 9 | Crossed-out characters (strikethrough). | #Y | * | 21 | Doubly underlined. | #P[Currently outputs a single underline.] | * | 22 | Normal (neither bold nor faint). | #Y | * | 23 | No italic. | #Y | @@ -2244,7 +2373,7 @@ export class InputHandler extends Disposable implements IInputHandler { * | 25 | Steady (not blinking). | #Y | * | 27 | Positive (not inverse). | #Y | * | 28 | Visible (not hidden). | #Y | - * | 29 | Not Crossed-out. | #N | + * | 29 | Not Crossed-out (strikethrough). | #Y | * | 30 | Foreground color: Black. | #Y | * | 31 | Foreground color: Red. | #Y | * | 32 | Foreground color: Green. | #Y | @@ -2295,12 +2424,12 @@ export class InputHandler extends Disposable implements IInputHandler { * FIXME: blinking is implemented in attrs, but not working in renderers? * FIXME: remove dead branch for p=100 */ - public charAttributes(params: IParams): void { + public charAttributes(params: IParams): boolean { // Optimize a single SGR0. if (params.length === 1 && params.params[0] === 0) { this._curAttrData.fg = DEFAULT_ATTR_DATA.fg; this._curAttrData.bg = DEFAULT_ATTR_DATA.bg; - return; + return true; } const l = params.length; @@ -2349,6 +2478,9 @@ export class InputHandler extends Disposable implements IInputHandler { } else if (p === 8) { // invisible attr.fg |= FgFlags.INVISIBLE; + } else if (p === 9) { + // strikethrough + attr.fg |= FgFlags.STRIKETHROUGH; } else if (p === 2) { // dimmed text attr.bg |= BgFlags.DIM; @@ -2374,6 +2506,9 @@ export class InputHandler extends Disposable implements IInputHandler { } else if (p === 28) { // not invisible attr.fg &= ~FgFlags.INVISIBLE; + } else if (p === 29) { + // not strikethrough + attr.fg &= ~FgFlags.STRIKETHROUGH; } else if (p === 39) { // reset fg attr.fg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK); @@ -2399,6 +2534,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._logService.debug('Unknown SGR attribute: %d.', p); } } + return true; } /** @@ -2426,7 +2562,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y CSI DSR "Device Status Report" "CSI Ps n" "Request cursor position (CPR) with `Ps` = 6." */ - public deviceStatus(params: IParams): void { + public deviceStatus(params: IParams): boolean { switch (params.params[0]) { case 5: // status report @@ -2439,10 +2575,11 @@ export class InputHandler extends Disposable implements IInputHandler { this._coreService.triggerDataEvent(`${C0.ESC}[${y};${x}R`); break; } + return true; } // @vt: #P[Only CPR is supported.] CSI DECDSR "DEC Device Status Report" "CSI ? Ps n" "Only CPR is supported (same as DSR)." - public deviceStatusPrivate(params: IParams): void { + public deviceStatusPrivate(params: IParams): boolean { // modern xterm doesnt seem to // respond to any of these except ?6, 6, and 5 switch (params.params[0]) { @@ -2469,6 +2606,7 @@ export class InputHandler extends Disposable implements IInputHandler { // this.handler(C0.ESC + '[?50n'); break; } + return true; } /** @@ -2490,7 +2628,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * FIXME: there are several more attributes missing (see VT520 manual) */ - public softReset(params: IParams): void { + public softReset(params: IParams): boolean { this._coreService.isCursorHidden = false; this._onRequestSyncScrollBar.fire(); this._bufferService.buffer.scrollTop = 0; @@ -2508,6 +2646,7 @@ export class InputHandler extends Disposable implements IInputHandler { // reset DECOM this._coreService.decPrivateModes.origin = false; + return true; } /** @@ -2529,7 +2668,7 @@ export class InputHandler extends Disposable implements IInputHandler { * - 5: steady bar * - 6: blink bar */ - public setCursorStyle(params: IParams): void { + public setCursorStyle(params: IParams): boolean { const param = params.params[0] || 1; switch (param) { case 1: @@ -2547,6 +2686,7 @@ export class InputHandler extends Disposable implements IInputHandler { } const isBlinking = param % 2 === 1; this._optionsService.options.cursorBlink = isBlinking; + return true; } /** @@ -2556,7 +2696,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y CSI DECSTBM "Set Top and Bottom Margin" "CSI Ps ; Ps r" "Set top and bottom margins of the viewport [top;bottom] (default = viewport size)." */ - public setScrollRegion(params: IParams): void { + public setScrollRegion(params: IParams): boolean { const top = params.params[0] || 1; let bottom: number; @@ -2569,6 +2709,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._bufferService.buffer.scrollBottom = bottom - 1; this._setCursor(0, 0); } + return true; } /** @@ -2601,9 +2742,9 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 23 ; 2 -> Restore xterm window title from stack. supported * Ps >= 24 not implemented */ - public windowOptions(params: IParams): void { + public windowOptions(params: IParams): boolean { if (!paramToWindowOption(params.params[0], this._optionsService.options.windowOptions)) { - return; + return true; } const second = (params.length > 1) ? params.params[1] : 0; switch (params.params[0]) { @@ -2647,6 +2788,7 @@ export class InputHandler extends Disposable implements IInputHandler { } break; } + return true; } @@ -2658,12 +2800,13 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #P[TODO...] CSI SCOSC "Save Cursor" "CSI s" "Save cursor position, charmap and text attributes." * @vt: #Y ESC SC "Save Cursor" "ESC 7" "Save cursor position, charmap and text attributes." */ - public saveCursor(params?: IParams): void { + public saveCursor(params?: IParams): boolean { this._bufferService.buffer.savedX = this._bufferService.buffer.x; this._bufferService.buffer.savedY = this._bufferService.buffer.ybase + this._bufferService.buffer.y; this._bufferService.buffer.savedCurAttrData.fg = this._curAttrData.fg; this._bufferService.buffer.savedCurAttrData.bg = this._curAttrData.bg; this._bufferService.buffer.savedCharset = this._charsetService.charset; + return true; } @@ -2675,7 +2818,7 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #P[TODO...] CSI SCORC "Restore Cursor" "CSI u" "Restore cursor position, charmap and text attributes." * @vt: #Y ESC RC "Restore Cursor" "ESC 8" "Restore cursor position, charmap and text attributes." */ - public restoreCursor(params?: IParams): void { + public restoreCursor(params?: IParams): boolean { this._bufferService.buffer.x = this._bufferService.buffer.savedX || 0; this._bufferService.buffer.y = Math.max(this._bufferService.buffer.savedY - this._bufferService.buffer.ybase, 0); this._curAttrData.fg = this._bufferService.buffer.savedCurAttrData.fg; @@ -2685,6 +2828,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._charsetService.charset = this._bufferService.buffer.savedCharset; } this._restrictCursor(); + return true; } @@ -2698,17 +2842,59 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y OSC 2 "Set Windows Title" "OSC 2 ; Pt BEL" "Set window title." * xterm.js does not manipulate the title directly, instead exposes changes via the event `Terminal.onTitleChange`. */ - public setTitle(data: string): void { + public setTitle(data: string): boolean { this._windowTitle = data; this._onTitleChange.fire(data); + return true; } /** * OSC 1; ST * Note: Icon name is not exposed. */ - public setIconName(data: string): void { + public setIconName(data: string): boolean { this._iconName = data; + return true; + } + + protected _parseAnsiColorChange(data: string): IAnsiColorChangeEvent | null { + const result: IAnsiColorChangeEvent = { colors: [] }; + // example data: 5;rgb:aa/bb/cc + const regex = /(\d+);rgb:([\da-f]{2})\/([\da-f]{2})\/([\da-f]{2})/gi; + let match; + + while ((match = regex.exec(data)) !== null) { + result.colors.push({ + colorIndex: parseInt(match[1]), + red: parseInt(match[2], 16), + green: parseInt(match[3], 16), + blue: parseInt(match[4], 16) + }); + } + + if (result.colors.length === 0) { + return null; + } + + return result; + } + + /** + * OSC 4; ; ST (set ANSI color to ) + * + * @vt: #Y OSC 4 "Set ANSI color" "OSC 4 ; c ; spec BEL" "Change color number `c` to the color specified by `spec`." + * `c` is the color index between 0 and 255. `spec` color format is 'rgb:hh/hh/hh' where `h` are hexadecimal digits. + * There may be multipe c ; spec elements present in the same instruction, e.g. 1;rgb:10/20/30;2;rgb:a0/b0/c0. + */ + public setAnsiColor(data: string): boolean { + const event = this._parseAnsiColorChange(data); + if (event) { + this._onAnsiColorChange.fire(event); + } + else { + this._logService.warn(`Expected format ;rgb:// but got data: ${data}`); + } + return true; } /** @@ -2720,9 +2906,10 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y C1 NEL "Next Line" "\x85" "Move the cursor to the beginning of the next row." * @vt: #Y ESC NEL "Next Line" "ESC E" "Move the cursor to the beginning of the next row." */ - public nextLine(): void { + public nextLine(): boolean { this._bufferService.buffer.x = 0; this.index(); + return true; } /** @@ -2730,10 +2917,11 @@ export class InputHandler extends Disposable implements IInputHandler { * DEC mnemonic: DECKPAM (https://vt100.net/docs/vt510-rm/DECKPAM.html) * Enables the numeric keypad to send application sequences to the host. */ - public keypadApplicationMode(): void { + public keypadApplicationMode(): boolean { this._logService.debug('Serial port requested application keypad.'); this._coreService.decPrivateModes.applicationKeypad = true; this._onRequestSyncScrollBar.fire(); + return true; } /** @@ -2741,10 +2929,11 @@ export class InputHandler extends Disposable implements IInputHandler { * DEC mnemonic: DECKPNM (https://vt100.net/docs/vt510-rm/DECKPNM.html) * Enables the keypad to send numeric characters to the host. */ - public keypadNumericMode(): void { + public keypadNumericMode(): boolean { this._logService.debug('Switching back to normal keypad.'); this._coreService.decPrivateModes.applicationKeypad = false; this._onRequestSyncScrollBar.fire(); + return true; } /** @@ -2753,9 +2942,10 @@ export class InputHandler extends Disposable implements IInputHandler { * Select default character set. UTF-8 is not supported (string are unicode anyways) * therefore ESC % G does the same. */ - public selectDefaultCharset(): void { + public selectDefaultCharset(): boolean { this._charsetService.setgLevel(0); this._charsetService.setgCharset(0, DEFAULT_CHARSET); // US (default) + return true; } /** @@ -2774,16 +2964,16 @@ export class InputHandler extends Disposable implements IInputHandler { * ESC / C * Designate G3 Character Set (VT300). C = A -> ISO Latin-1 Supplemental. - Supported? */ - public selectCharset(collectAndFlag: string): void { + public selectCharset(collectAndFlag: string): boolean { if (collectAndFlag.length !== 2) { this.selectDefaultCharset(); - return; + return true; } if (collectAndFlag[0] === '/') { - return; // TODO: Is this supported? + return true; // TODO: Is this supported? } this._charsetService.setgCharset(GLEVEL[collectAndFlag[0]], CHARSETS[collectAndFlag[1]] || DEFAULT_CHARSET); - return; + return true; } /** @@ -2795,17 +2985,18 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y C1 IND "Index" "\x84" "Move the cursor one line down scrolling if needed." * @vt: #Y ESC IND "Index" "ESC D" "Move the cursor one line down scrolling if needed." */ - public index(): void { + public index(): boolean { this._restrictCursor(); const buffer = this._bufferService.buffer; this._bufferService.buffer.y++; if (buffer.y === buffer.scrollBottom + 1) { buffer.y--; - this._onRequestScroll.fire(this._eraseAttrData()); + this._bufferService.scroll(this._eraseAttrData()); } else if (buffer.y >= this._bufferService.rows) { buffer.y = this._bufferService.rows - 1; } this._restrictCursor(); + return true; } /** @@ -2818,8 +3009,9 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y C1 HTS "Horizontal Tabulation Set" "\x88" "Places a tab stop at the current cursor position." * @vt: #Y ESC HTS "Horizontal Tabulation Set" "ESC H" "Places a tab stop at the current cursor position." */ - public tabSet(): void { + public tabSet(): boolean { this._bufferService.buffer.tabs[this._bufferService.buffer.x] = true; + return true; } /** @@ -2831,7 +3023,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y ESC IR "Reverse Index" "ESC M" "Move the cursor one line up scrolling if needed." */ - public reverseIndex(): void { + public reverseIndex(): boolean { this._restrictCursor(); const buffer = this._bufferService.buffer; if (buffer.y === buffer.scrollTop) { @@ -2846,6 +3038,7 @@ export class InputHandler extends Disposable implements IInputHandler { buffer.y--; this._restrictCursor(); // quickfix to not run out of bounds } + return true; } /** @@ -2853,9 +3046,10 @@ export class InputHandler extends Disposable implements IInputHandler { * DEC mnemonic: RIS (https://vt100.net/docs/vt510-rm/RIS.html) * Reset to initial state. */ - public fullReset(): void { + public fullReset(): boolean { this._parser.reset(); this._onRequestReset.fire(); + return true; } public reset(): void { @@ -2882,8 +3076,9 @@ export class InputHandler extends Disposable implements IInputHandler { * When you use a locking shift, the character set remains in GL or GR until * you use another locking shift. (partly supported) */ - public setgLevel(level: number): void { + public setgLevel(level: number): boolean { this._charsetService.setgLevel(level); + return true; } /** @@ -2894,7 +3089,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y ESC DECALN "Screen Alignment Pattern" "ESC # 8" "Fill viewport with a test pattern (E)." */ - public screenAlignmentPattern(): void { + public screenAlignmentPattern(): boolean { // prepare cell data const cell = new CellData(); cell.content = 1 << Content.WIDTH_SHIFT | 'E'.charCodeAt(0); @@ -2914,5 +3109,6 @@ export class InputHandler extends Disposable implements IInputHandler { } this._dirtyRowService.markAllDirty(); this._setCursor(0, 0); + return true; } } diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 35357744..52fe00d0 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -3,13 +3,13 @@ * @license MIT */ -import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, IPartialTerminalOptions, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService, IUnicodeVersionProvider } from 'common/services/Services'; +import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, IPartialTerminalOptions, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService, IUnicodeVersionProvider, LogLevelEnum } from 'common/services/Services'; import { IEvent, EventEmitter } from 'common/EventEmitter'; import { clone } from 'common/Clone'; import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; import { IBufferSet, IBuffer } from 'common/buffer/Types'; import { BufferSet } from 'common/buffer/BufferSet'; -import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEventType, ICharset, IModes } from 'common/Types'; +import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEventType, ICharset, IModes, IAttributeData } from 'common/Types'; import { UnicodeV6 } from 'common/input/UnicodeV6'; export class MockBufferService implements IBufferService { @@ -17,6 +17,7 @@ export class MockBufferService implements IBufferService { public get buffer(): IBuffer { return this.buffers.active; } public buffers: IBufferSet = {} as any; public onResize: IEvent<{ cols: number, rows: number }> = new EventEmitter<{ cols: number, rows: number }>().event; + public onScroll: IEvent = new EventEmitter().event; public isUserScrolling: boolean = false; constructor( public cols: number, @@ -25,23 +26,41 @@ export class MockBufferService implements IBufferService { ) { this.buffers = new BufferSet(optionsService, this); } + public scrollPages(pageCount: number): void { + throw new Error('Method not implemented.'); + } + public scrollToTop(): void { + throw new Error('Method not implemented.'); + } + public scrollToLine(line: number): void { + throw new Error('Method not implemented.'); + } + public scroll(eraseAttr: IAttributeData, isWrapped: boolean): void { + throw new Error('Method not implemented.'); + } + public scrollToBottom(): void { + throw new Error('Method not implemented.'); + } + public scrollLines(disp: number, suppressScrollEvent?: boolean): void { + throw new Error('Method not implemented.'); + } public resize(cols: number, rows: number): void { this.cols = cols; this.rows = rows; } - public reset(): void {} + public reset(): void { } } export class MockCoreMouseService implements ICoreMouseService { public areMouseEventsActive: boolean = false; public activeEncoding: string = ''; public activeProtocol: string = ''; - public addEncoding(name: string): void {} - public addProtocol(name: string): void {} - public reset(): void {} + public addEncoding(name: string): void { } + public addProtocol(name: string): void { } + public reset(): void { } public triggerMouseEvent(event: ICoreMouseEvent): boolean { return false; } public onProtocolChange: IEvent = new EventEmitter().event; - public explainEvents(events: CoreMouseEventType): {[event: string]: boolean} { + public explainEvents(events: CoreMouseEventType): { [event: string]: boolean } { throw new Error('Method not implemented.'); } } @@ -50,9 +69,9 @@ export class MockCharsetService implements ICharsetService { public serviceBrand: any; public charset: ICharset | undefined; public glevel: number = 0; - public reset(): void {} - public setgLevel(g: number): void {} - public setgCharset(g: number, charset: ICharset): void {} + public reset(): void { } + public setgLevel(g: number): void { } + public setgCharset(g: number, charset: ICharset): void { } } export class MockCoreService implements ICoreService { @@ -75,27 +94,28 @@ export class MockCoreService implements ICoreService { public onData: IEvent = new EventEmitter().event; public onUserInput: IEvent = new EventEmitter().event; public onBinary: IEvent = new EventEmitter().event; - public reset(): void {} - public triggerDataEvent(data: string, wasUserInput?: boolean): void {} - public triggerBinaryEvent(data: string): void {} + public reset(): void { } + public triggerDataEvent(data: string, wasUserInput?: boolean): void { } + public triggerBinaryEvent(data: string): void { } } export class MockDirtyRowService implements IDirtyRowService { public serviceBrand: any; public start: number = 0; public end: number = 0; - public clearRange(): void {} - public markDirty(y: number): void {} - public markRangeDirty(y1: number, y2: number): void {} - public markAllDirty(): void {} + public clearRange(): void { } + public markDirty(y: number): void { } + public markRangeDirty(y1: number, y2: number): void { } + public markAllDirty(): void { } } export class MockLogService implements ILogService { public serviceBrand: any; - public debug(message: any, ...optionalParams: any[]): void {} - public info(message: any, ...optionalParams: any[]): void {} - public warn(message: any, ...optionalParams: any[]): void {} - public error(message: any, ...optionalParams: any[]): void {} + public logLevel = LogLevelEnum.DEBUG; + public debug(message: any, ...optionalParams: any[]): void { } + public info(message: any, ...optionalParams: any[]): void { } + public warn(message: any, ...optionalParams: any[]): void { } + public error(message: any, ...optionalParams: any[]): void { } } export class MockOptionsService implements IOptionsService { @@ -105,7 +125,7 @@ export class MockOptionsService implements IOptionsService { constructor(testOptions?: IPartialTerminalOptions) { if (testOptions) { for (const key of Object.keys(testOptions)) { - this.options[key] = (testOptions)[key]; + this.options[key] = (testOptions as any)[key]; } } } diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index bd0d11c6..36f75dd0 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -3,15 +3,21 @@ * @license MIT */ -import { ITerminalOptions as IPublicTerminalOptions } from 'xterm'; +import { IFunctionIdentifier, ITerminalOptions as IPublicTerminalOptions } from 'xterm'; import { IEvent, IEventEmitter } from 'common/EventEmitter'; import { IDeleteEvent, IInsertEvent } from 'common/CircularList'; import { IParams } from 'common/parser/Types'; import { IOptionsService, IUnicodeService } from 'common/services/Services'; +import { IBufferSet } from 'common/buffer/Types'; export interface ICoreTerminal { optionsService: IOptionsService; unicodeService: IUnicodeService; + buffers: IBufferSet; + registerCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean | Promise): IDisposable; + registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean | Promise): IDisposable; + registerEscHandler(id: IFunctionIdentifier, callback: () => boolean | Promise): IDisposable; + registerOscHandler(ident: number, callback: (data: string) => boolean | Promise): IDisposable; } export interface IDisposable { @@ -42,6 +48,16 @@ export interface IKeyboardEvent { type: string; } +export interface IScrollEvent { + position: number; + source: ScrollSource; +} + +export const enum ScrollSource { + TERMINAL, + VIEWPORT, +} + export interface ICircularList { length: number; maxLength: number; @@ -107,6 +123,7 @@ export interface IAttributeData { isInvisible(): number; isItalic(): number; isDim(): number; + isStrikethrough(): number; // color modes getFgColorMode(): number; @@ -328,15 +345,32 @@ export interface IWindowOptions { setWinLines?: boolean; } +export interface IAnsiColorChangeEventColor { + colorIndex: number; + red: number; + green: number; + blue: number; +} + +/** + * Event fired for OSC 4 command - to change ANSI color based on its index. + */ +export interface IAnsiColorChangeEvent { + colors: IAnsiColorChangeEventColor[]; +} + /** * Calls the parser and handles actions generated by the parser. */ export interface IInputHandler { onTitleChange: IEvent; - onRequestScroll: IEvent; - parse(data: string | Uint8Array): void; + parse(data: string | Uint8Array, promiseResult?: boolean): void | Promise; print(data: Uint32Array, start: number, end: number): void; + registerCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean | Promise): IDisposable; + registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean | Promise): IDisposable; + registerEscHandler(id: IFunctionIdentifier, callback: () => boolean | Promise): IDisposable; + registerOscHandler(ident: number, callback: (data: string) => boolean | Promise): IDisposable; /** C0 BEL */ bell(): void; /** C0 LF */ lineFeed(): void; @@ -389,6 +423,7 @@ export interface IInputHandler { /** CSI ' ~ */ deleteColumns(params: IParams): void; /** OSC 0 OSC 2 */ setTitle(data: string): void; + /** OSC 4 */ setAnsiColor(data: string): void; /** ESC E */ nextLine(): void; /** ESC = */ keypadApplicationMode(): void; /** ESC > */ keypadNumericMode(): void; @@ -412,3 +447,11 @@ export interface IInputHandler { ESC ~ */ setgLevel(level: number): void; /** ESC # 8 */ screenAlignmentPattern(): void; } + +interface IParseStack { + paused: boolean; + cursorStartX: number; + cursorStartY: number; + decodedLength: number; + position: number; +} diff --git a/src/common/buffer/AttributeData.ts b/src/common/buffer/AttributeData.ts index c7217a2a..43d378ea 100644 --- a/src/common/buffer/AttributeData.ts +++ b/src/common/buffer/AttributeData.ts @@ -33,13 +33,14 @@ export class AttributeData implements IAttributeData { public extended = new ExtendedAttrs(); // flags - public isInverse(): number { return this.fg & FgFlags.INVERSE; } - public isBold(): number { return this.fg & FgFlags.BOLD; } - public isUnderline(): number { return this.fg & FgFlags.UNDERLINE; } - public isBlink(): number { return this.fg & FgFlags.BLINK; } - public isInvisible(): number { return this.fg & FgFlags.INVISIBLE; } - public isItalic(): number { return this.bg & BgFlags.ITALIC; } - public isDim(): number { return this.bg & BgFlags.DIM; } + public isInverse(): number { return this.fg & FgFlags.INVERSE; } + public isBold(): number { return this.fg & FgFlags.BOLD; } + public isUnderline(): number { return this.fg & FgFlags.UNDERLINE; } + public isBlink(): number { return this.fg & FgFlags.BLINK; } + public isInvisible(): number { return this.fg & FgFlags.INVISIBLE; } + public isItalic(): number { return this.bg & BgFlags.ITALIC; } + public isDim(): number { return this.bg & BgFlags.DIM; } + public isStrikethrough(): number { return this.fg & FgFlags.STRIKETHROUGH; } // color modes public getFgColorMode(): number { return this.fg & Attributes.CM_MASK; } diff --git a/src/common/buffer/Buffer.ts b/src/common/buffer/Buffer.ts index e06ffb01..c788bf36 100644 --- a/src/common/buffer/Buffer.ts +++ b/src/common/buffer/Buffer.ts @@ -671,6 +671,6 @@ export class BufferStringIterator implements IBufferStringIterator { content += this._buffer.translateBufferLineToString(i, this._trimRight); } this._current = range.last + 1; - return {range, content}; + return { range, content }; } } diff --git a/src/common/buffer/BufferLine.test.ts b/src/common/buffer/BufferLine.test.ts index 4c5fc3a4..fa15a854 100644 --- a/src/common/buffer/BufferLine.test.ts +++ b/src/common/buffer/BufferLine.test.ts @@ -6,7 +6,7 @@ import { NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, DEFAULT_ATTR, Content, import { BufferLine } from 'common/buffer//BufferLine'; import { CellData } from 'common/buffer/CellData'; import { CharData, IBufferLine } from '../Types'; -import { assert, expect } from 'chai'; +import { assert } from 'chai'; import { AttributeData } from 'common/buffer/AttributeData'; @@ -151,20 +151,20 @@ describe('CellData', () => { describe('BufferLine', function(): void { it('ctor', function(): void { let line: IBufferLine = new TestBufferLine(0); - expect(line.length).equals(0); - expect(line.isWrapped).equals(false); + assert.equal(line.length, 0); + assert.equal(line.isWrapped, false); line = new TestBufferLine(10); - expect(line.length).equals(10); - expect(line.loadCell(0, new CellData()).getAsCharData()).eql([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); - expect(line.isWrapped).equals(false); + assert.equal(line.length, 10); + assert.deepEqual(line.loadCell(0, new CellData()).getAsCharData(), [0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); + assert.equal(line.isWrapped, false); line = new TestBufferLine(10, undefined, true); - expect(line.length).equals(10); - expect(line.loadCell(0, new CellData()).getAsCharData()).eql([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); - expect(line.isWrapped).equals(true); + assert.equal(line.length, 10); + assert.deepEqual(line.loadCell(0, new CellData()).getAsCharData(), [0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); + assert.equal(line.isWrapped, true); line = new TestBufferLine(10, CellData.fromCharData([123, 'a', 456, 'a'.charCodeAt(0)]), true); - expect(line.length).equals(10); - expect(line.loadCell(0, new CellData()).getAsCharData()).eql([123, 'a', 456, 'a'.charCodeAt(0)]); - expect(line.isWrapped).equals(true); + assert.equal(line.length, 10); + assert.deepEqual(line.loadCell(0, new CellData()).getAsCharData(), [123, 'a', 456, 'a'.charCodeAt(0)]); + assert.equal(line.isWrapped, true); }); it('insertCells', function(): void { const line = new TestBufferLine(3); @@ -172,7 +172,7 @@ describe('BufferLine', function(): void { line.setCell(1, CellData.fromCharData([2, 'b', 0, 'b'.charCodeAt(0)])); line.setCell(2, CellData.fromCharData([3, 'c', 0, 'c'.charCodeAt(0)])); line.insertCells(1, 3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)])); - expect(line.toArray()).eql([ + assert.deepEqual(line.toArray(), [ [1, 'a', 0, 'a'.charCodeAt(0)], [4, 'd', 0, 'd'.charCodeAt(0)], [4, 'd', 0, 'd'.charCodeAt(0)] @@ -186,7 +186,7 @@ describe('BufferLine', function(): void { line.setCell(3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)])); line.setCell(4, CellData.fromCharData([5, 'e', 0, 'e'.charCodeAt(0)])); line.deleteCells(1, 2, CellData.fromCharData([6, 'f', 0, 'f'.charCodeAt(0)])); - expect(line.toArray()).eql([ + assert.deepEqual(line.toArray(), [ [1, 'a', 0, 'a'.charCodeAt(0)], [4, 'd', 0, 'd'.charCodeAt(0)], [5, 'e', 0, 'e'.charCodeAt(0)], @@ -202,7 +202,7 @@ describe('BufferLine', function(): void { line.setCell(3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)])); line.setCell(4, CellData.fromCharData([5, 'e', 0, 'e'.charCodeAt(0)])); line.replaceCells(2, 4, CellData.fromCharData([6, 'f', 0, 'f'.charCodeAt(0)])); - expect(line.toArray()).eql([ + assert.deepEqual(line.toArray(), [ [1, 'a', 0, 'a'.charCodeAt(0)], [2, 'b', 0, 'b'.charCodeAt(0)], [6, 'f', 0, 'f'.charCodeAt(0)], @@ -218,7 +218,7 @@ describe('BufferLine', function(): void { line.setCell(3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)])); line.setCell(4, CellData.fromCharData([5, 'e', 0, 'e'.charCodeAt(0)])); line.fill(CellData.fromCharData([123, 'z', 0, 'z'.charCodeAt(0)])); - expect(line.toArray()).eql([ + assert.deepEqual(line.toArray(), [ [123, 'z', 0, 'z'.charCodeAt(0)], [123, 'z', 0, 'z'.charCodeAt(0)], [123, 'z', 0, 'z'.charCodeAt(0)], @@ -234,9 +234,9 @@ describe('BufferLine', function(): void { line.setCell(3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)])); line.setCell(4, CellData.fromCharData([5, 'e', 0, 'e'.charCodeAt(0)])); const line2 = line.clone(); - expect(TestBufferLine.prototype.toArray.apply(line2)).eql(line.toArray()); - expect(line2.length).equals(line.length); - expect(line2.isWrapped).equals(line.isWrapped); + assert.deepEqual(TestBufferLine.prototype.toArray.apply(line2), line.toArray()); + assert.equal(line2.length, line.length); + assert.equal(line2.isWrapped, line.isWrapped); }); it('copyFrom', function(): void { const line = new TestBufferLine(5); @@ -247,92 +247,92 @@ describe('BufferLine', function(): void { line.setCell(4, CellData.fromCharData([5, 'e', 0, 'e'.charCodeAt(0)])); const line2 = new TestBufferLine(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), true); line2.copyFrom(line); - expect(line2.toArray()).eql(line.toArray()); - expect(line2.length).equals(line.length); - expect(line2.isWrapped).equals(line.isWrapped); + assert.deepEqual(line2.toArray(), line.toArray()); + assert.equal(line2.length, line.length); + assert.equal(line2.isWrapped, line.isWrapped); }); it('should support combining chars', function(): void { // CHAR_DATA_CODE_INDEX resembles current behavior in InputHandler.print // --> set code to the last charCodeAt value of the string // Note: needs to be fixed once the string pointer is in place const line = new TestBufferLine(2, CellData.fromCharData([1, 'e\u0301', 0, '\u0301'.charCodeAt(0)])); - expect(line.toArray()).eql([[1, 'e\u0301', 0, '\u0301'.charCodeAt(0)], [1, 'e\u0301', 0, '\u0301'.charCodeAt(0)]]); + assert.deepEqual(line.toArray(), [[1, 'e\u0301', 0, '\u0301'.charCodeAt(0)], [1, 'e\u0301', 0, '\u0301'.charCodeAt(0)]]); const line2 = new TestBufferLine(5, CellData.fromCharData([1, 'a', 0, '\u0301'.charCodeAt(0)]), true); line2.copyFrom(line); - expect(line2.toArray()).eql(line.toArray()); + assert.deepEqual(line2.toArray(), line.toArray()); const line3 = line.clone(); - expect(TestBufferLine.prototype.toArray.apply(line3)).eql(line.toArray()); + assert.deepEqual(TestBufferLine.prototype.toArray.apply(line3), line.toArray()); }); describe('resize', function(): void { it('enlarge(false)', function(): void { const line = new TestBufferLine(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); line.resize(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)])); - expect(line.toArray()).eql((Array(10) as any).fill([1, 'a', 0, 'a'.charCodeAt(0)])); + assert.deepEqual(line.toArray(), (Array(10) as any).fill([1, 'a', 0, 'a'.charCodeAt(0)])); }); it('enlarge(true)', function(): void { const line = new TestBufferLine(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); line.resize(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)])); - expect(line.toArray()).eql((Array(10) as any).fill([1, 'a', 0, 'a'.charCodeAt(0)])); + assert.deepEqual(line.toArray(), (Array(10) as any).fill([1, 'a', 0, 'a'.charCodeAt(0)])); }); it('shrink(true) - should apply new size', function(): void { const line = new TestBufferLine(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); line.resize(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)])); - expect(line.toArray()).eql((Array(5) as any).fill([1, 'a', 0, 'a'.charCodeAt(0)])); + assert.deepEqual(line.toArray(), (Array(5) as any).fill([1, 'a', 0, 'a'.charCodeAt(0)])); }); it('shrink to 0 length', function(): void { const line = new TestBufferLine(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); line.resize(0, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)])); - expect(line.toArray()).eql((Array(0) as any).fill([1, 'a', 0, 'a'.charCodeAt(0)])); + assert.deepEqual(line.toArray(), (Array(0) as any).fill([1, 'a', 0, 'a'.charCodeAt(0)])); }); it('should remove combining data on replaced cells after shrinking then enlarging', () => { const line = new TestBufferLine(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); line.set(2, [ 0, '😁', 1, '😁'.charCodeAt(0) ]); line.set(9, [ 0, '😁', 1, '😁'.charCodeAt(0) ]); - expect(line.translateToString()).eql('aa😁aaaaaa😁'); - expect(Object.keys(line.combined).length).eql(2); + assert.equal(line.translateToString(), 'aa😁aaaaaa😁'); + assert.equal(Object.keys(line.combined).length, 2); line.resize(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)])); - expect(line.translateToString()).eql('aa😁aa'); + assert.equal(line.translateToString(), 'aa😁aa'); line.resize(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)])); - expect(line.translateToString()).eql('aa😁aaaaaaa'); - expect(Object.keys(line.combined).length).eql(1); + assert.equal(line.translateToString(), 'aa😁aaaaaaa'); + assert.equal(Object.keys(line.combined).length, 1); }); }); describe('getTrimLength', function(): void { it('empty line', function(): void { const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); - expect(line.getTrimmedLength()).equal(0); + assert.equal(line.getTrimmedLength(), 0); }); it('ASCII', function(): void { const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); line.setCell(2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); - expect(line.getTrimmedLength()).equal(3); + assert.equal(line.getTrimmedLength(), 3); }); it('surrogate', function(): void { const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); line.setCell(2, CellData.fromCharData([1, '𝄞', 1, '𝄞'.charCodeAt(0)])); - expect(line.getTrimmedLength()).equal(3); + assert.equal(line.getTrimmedLength(), 3); }); it('combining', function(): void { const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); line.setCell(2, CellData.fromCharData([1, 'e\u0301', 1, '\u0301'.charCodeAt(0)])); - expect(line.getTrimmedLength()).equal(3); + assert.equal(line.getTrimmedLength(), 3); }); it('fullwidth', function(): void { const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); line.setCell(2, CellData.fromCharData([1, '1', 2, '1'.charCodeAt(0)])); line.setCell(3, CellData.fromCharData([0, '', 0, 0])); - expect(line.getTrimmedLength()).equal(4); // also counts null cell after fullwidth + assert.equal(line.getTrimmedLength(), 4); // also counts null cell after fullwidth }); }); describe('translateToString with and w\'o trimming', function(): void { it('empty line', function(): void { const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); - expect(line.translateToString(false)).equal(' '); - expect(line.translateToString(true)).equal(''); + assert.equal(line.translateToString(false), ' '); + assert.equal(line.translateToString(true), ''); }); it('ASCII', function(): void { const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); @@ -340,14 +340,14 @@ describe('BufferLine', function(): void { line.setCell(2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); line.setCell(4, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); line.setCell(5, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); - expect(line.translateToString(false)).equal('a a aa '); - expect(line.translateToString(true)).equal('a a aa'); - expect(line.translateToString(false, 0, 5)).equal('a a a'); - expect(line.translateToString(false, 0, 4)).equal('a a '); - expect(line.translateToString(false, 0, 3)).equal('a a'); - expect(line.translateToString(true, 0, 5)).equal('a a a'); - expect(line.translateToString(true, 0, 4)).equal('a a '); - expect(line.translateToString(true, 0, 3)).equal('a a'); + assert.equal(line.translateToString(false), 'a a aa '); + assert.equal(line.translateToString(true), 'a a aa'); + assert.equal(line.translateToString(false, 0, 5), 'a a a'); + assert.equal(line.translateToString(false, 0, 4), 'a a '); + assert.equal(line.translateToString(false, 0, 3), 'a a'); + assert.equal(line.translateToString(true, 0, 5), 'a a a'); + assert.equal(line.translateToString(true, 0, 4), 'a a '); + assert.equal(line.translateToString(true, 0, 3), 'a a'); }); it('surrogate', function(): void { @@ -356,14 +356,14 @@ describe('BufferLine', function(): void { line.setCell(2, CellData.fromCharData([1, '𝄞', 1, '𝄞'.charCodeAt(0)])); line.setCell(4, CellData.fromCharData([1, '𝄞', 1, '𝄞'.charCodeAt(0)])); line.setCell(5, CellData.fromCharData([1, '𝄞', 1, '𝄞'.charCodeAt(0)])); - expect(line.translateToString(false)).equal('a 𝄞 𝄞𝄞 '); - expect(line.translateToString(true)).equal('a 𝄞 𝄞𝄞'); - expect(line.translateToString(false, 0, 5)).equal('a 𝄞 𝄞'); - expect(line.translateToString(false, 0, 4)).equal('a 𝄞 '); - expect(line.translateToString(false, 0, 3)).equal('a 𝄞'); - expect(line.translateToString(true, 0, 5)).equal('a 𝄞 𝄞'); - expect(line.translateToString(true, 0, 4)).equal('a 𝄞 '); - expect(line.translateToString(true, 0, 3)).equal('a 𝄞'); + assert.equal(line.translateToString(false), 'a 𝄞 𝄞𝄞 '); + assert.equal(line.translateToString(true), 'a 𝄞 𝄞𝄞'); + assert.equal(line.translateToString(false, 0, 5), 'a 𝄞 𝄞'); + assert.equal(line.translateToString(false, 0, 4), 'a 𝄞 '); + assert.equal(line.translateToString(false, 0, 3), 'a 𝄞'); + assert.equal(line.translateToString(true, 0, 5), 'a 𝄞 𝄞'); + assert.equal(line.translateToString(true, 0, 4), 'a 𝄞 '); + assert.equal(line.translateToString(true, 0, 3), 'a 𝄞'); }); it('combining', function(): void { const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); @@ -371,14 +371,14 @@ describe('BufferLine', function(): void { line.setCell(2, CellData.fromCharData([1, 'e\u0301', 1, '\u0301'.charCodeAt(0)])); line.setCell(4, CellData.fromCharData([1, 'e\u0301', 1, '\u0301'.charCodeAt(0)])); line.setCell(5, CellData.fromCharData([1, 'e\u0301', 1, '\u0301'.charCodeAt(0)])); - expect(line.translateToString(false)).equal('a e\u0301 e\u0301e\u0301 '); - expect(line.translateToString(true)).equal('a e\u0301 e\u0301e\u0301'); - expect(line.translateToString(false, 0, 5)).equal('a e\u0301 e\u0301'); - expect(line.translateToString(false, 0, 4)).equal('a e\u0301 '); - expect(line.translateToString(false, 0, 3)).equal('a e\u0301'); - expect(line.translateToString(true, 0, 5)).equal('a e\u0301 e\u0301'); - expect(line.translateToString(true, 0, 4)).equal('a e\u0301 '); - expect(line.translateToString(true, 0, 3)).equal('a e\u0301'); + assert.equal(line.translateToString(false), 'a e\u0301 e\u0301e\u0301 '); + assert.equal(line.translateToString(true), 'a e\u0301 e\u0301e\u0301'); + assert.equal(line.translateToString(false, 0, 5), 'a e\u0301 e\u0301'); + assert.equal(line.translateToString(false, 0, 4), 'a e\u0301 '); + assert.equal(line.translateToString(false, 0, 3), 'a e\u0301'); + assert.equal(line.translateToString(true, 0, 5), 'a e\u0301 e\u0301'); + assert.equal(line.translateToString(true, 0, 4), 'a e\u0301 '); + assert.equal(line.translateToString(true, 0, 3), 'a e\u0301'); }); it('fullwidth', function(): void { const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); @@ -389,20 +389,20 @@ describe('BufferLine', function(): void { line.setCell(6, CellData.fromCharData([0, '', 0, 0])); line.setCell(7, CellData.fromCharData([1, '1', 2, '1'.charCodeAt(0)])); line.setCell(8, CellData.fromCharData([0, '', 0, 0])); - expect(line.translateToString(false)).equal('a 1 11 '); - expect(line.translateToString(true)).equal('a 1 11'); - expect(line.translateToString(false, 0, 7)).equal('a 1 1'); - expect(line.translateToString(false, 0, 6)).equal('a 1 1'); - expect(line.translateToString(false, 0, 5)).equal('a 1 '); - expect(line.translateToString(false, 0, 4)).equal('a 1'); - expect(line.translateToString(false, 0, 3)).equal('a 1'); - expect(line.translateToString(false, 0, 2)).equal('a '); - expect(line.translateToString(true, 0, 7)).equal('a 1 1'); - expect(line.translateToString(true, 0, 6)).equal('a 1 1'); - expect(line.translateToString(true, 0, 5)).equal('a 1 '); - expect(line.translateToString(true, 0, 4)).equal('a 1'); - expect(line.translateToString(true, 0, 3)).equal('a 1'); - expect(line.translateToString(true, 0, 2)).equal('a '); + assert.equal(line.translateToString(false), 'a 1 11 '); + assert.equal(line.translateToString(true), 'a 1 11'); + assert.equal(line.translateToString(false, 0, 7), 'a 1 1'); + assert.equal(line.translateToString(false, 0, 6), 'a 1 1'); + assert.equal(line.translateToString(false, 0, 5), 'a 1 '); + assert.equal(line.translateToString(false, 0, 4), 'a 1'); + assert.equal(line.translateToString(false, 0, 3), 'a 1'); + assert.equal(line.translateToString(false, 0, 2), 'a '); + assert.equal(line.translateToString(true, 0, 7), 'a 1 1'); + assert.equal(line.translateToString(true, 0, 6), 'a 1 1'); + assert.equal(line.translateToString(true, 0, 5), 'a 1 '); + assert.equal(line.translateToString(true, 0, 4), 'a 1'); + assert.equal(line.translateToString(true, 0, 3), 'a 1'); + assert.equal(line.translateToString(true, 0, 2), 'a '); }); it('space at end', function(): void { const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); @@ -411,21 +411,21 @@ describe('BufferLine', function(): void { line.setCell(4, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); line.setCell(5, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); line.setCell(6, CellData.fromCharData([1, ' ', 1, ' '.charCodeAt(0)])); - expect(line.translateToString(false)).equal('a a aa '); - expect(line.translateToString(true)).equal('a a aa '); + assert.equal(line.translateToString(false), 'a a aa '); + assert.equal(line.translateToString(true), 'a a aa '); }); it('should always return some sane value', function(): void { // sanity check - broken line with invalid out of bound null width cells // this can atm happen with deleting/inserting chars in inputhandler by "breaking" // fullwidth pairs --> needs to be fixed after settling BufferLine impl const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false); - expect(line.translateToString(false)).equal(' '); - expect(line.translateToString(true)).equal(''); + assert.equal(line.translateToString(false), ' '); + assert.equal(line.translateToString(true), ''); }); it('should work with endCol=0', () => { const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false); line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); - expect(line.translateToString(true, 0, 0)).equal(''); + assert.equal(line.translateToString(true, 0, 0), ''); }); }); describe('addCharToCell', () => { diff --git a/src/common/buffer/BufferRange.test.ts b/src/common/buffer/BufferRange.test.ts new file mode 100644 index 00000000..69aacd99 --- /dev/null +++ b/src/common/buffer/BufferRange.test.ts @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2021 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert } from 'chai'; +import { getRangeLength } from 'common/buffer/BufferRange'; +import { IBufferRange } from 'xterm'; + +describe('BufferRange', () => { + describe('getRangeLength', () => { + it('should get range for single line', () => { + assert.equal(getRangeLength(createRange(1, 1, 4, 1), 0), 4); + }); + it('should throw for invalid range', () => { + assert.throws(() => getRangeLength(createRange(1, 3, 1, 1), 0)); + }); + it('should get range multiple lines', () => { + assert.equal(getRangeLength(createRange(1, 1, 4, 5), 5), 24); + }); + it('should get range for end line right after start line', () => { + assert.equal(getRangeLength(createRange(1, 1, 7, 2), 5), 12); + }); + }); +}); + +function createRange(x1: number, y1: number, x2: number, y2: number): IBufferRange { + return { + start: { x: x1, y: y1 }, + end: { x: x2, y: y2 } + }; +} diff --git a/src/common/buffer/BufferRange.ts b/src/common/buffer/BufferRange.ts new file mode 100644 index 00000000..a49cf481 --- /dev/null +++ b/src/common/buffer/BufferRange.ts @@ -0,0 +1,13 @@ +/** + * Copyright (c) 2021 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IBufferRange } from 'xterm'; + +export function getRangeLength(range: IBufferRange, bufferCols: number): number { + if (range.start.y > range.end.y) { + throw new Error(`Buffer range end (${range.end.x}, ${range.end.y}) cannot be before start (${range.start.x}, ${range.start.y})`); + } + return bufferCols * (range.end.y - range.start.y) + (range.end.x - range.start.x + 1); +} diff --git a/src/common/buffer/Constants.ts b/src/common/buffer/Constants.ts index ee86a804..a2c1b884 100644 --- a/src/common/buffer/Constants.ts +++ b/src/common/buffer/Constants.ts @@ -110,13 +110,14 @@ export const enum Attributes { export const enum FgFlags { /** - * bit 27..31 (32th bit unused) + * bit 27..32 */ INVERSE = 0x4000000, BOLD = 0x8000000, UNDERLINE = 0x10000000, BLINK = 0x20000000, - INVISIBLE = 0x40000000 + INVISIBLE = 0x40000000, + STRIKETHROUGH = 0x80000000, } export const enum BgFlags { diff --git a/src/common/input/Keyboard.ts b/src/common/input/Keyboard.ts index fdb777b4..20a28f61 100644 --- a/src/common/input/Keyboard.ts +++ b/src/common/input/Keyboard.ts @@ -356,7 +356,7 @@ export function evaluateKeyboardEvent( const keyCode = ev.ctrlKey ? ev.keyCode - 64 : ev.keyCode + 32; result.key = C0.ESC + String.fromCharCode(keyCode); } - } else if (isMac && !ev.altKey && !ev.ctrlKey && ev.metaKey) { + } else if (isMac && !ev.altKey && !ev.ctrlKey && !ev.shiftKey && ev.metaKey) { if (ev.keyCode === 65) { // cmd + a result.type = KeyboardResultType.SELECT_ALL; } diff --git a/src/common/input/TextDecoder.test.ts b/src/common/input/TextDecoder.test.ts index 92b0e03a..da1760a2 100644 --- a/src/common/input/TextDecoder.test.ts +++ b/src/common/input/TextDecoder.test.ts @@ -58,8 +58,8 @@ describe('text encodings', () => { const decoder = new StringToUtf32(); const target = new Uint32Array(5); for (let i = 0; i < 65536; ++i) { - // skip surrogate pairs - if (i >= 0xD800 && i <= 0xDFFF) { + // skip surrogate pairs and a BOM + if ((i >= 0xD800 && i <= 0xDFFF) || i === 0xFEFF) { continue; } const length = decoder.decode(String.fromCharCode(i), target); @@ -84,6 +84,14 @@ describe('text encodings', () => { decoder.clear(); } }); + + it('0xFEFF(BOM)', () => { + const decoder = new StringToUtf32(); + const target = new Uint32Array(5); + const length = decoder.decode(String.fromCharCode(0xFEFF), target); + assert.equal(length, 0); + decoder.clear(); + }); }); it('test strings', () => { @@ -118,8 +126,8 @@ describe('text encodings', () => { const decoder = new Utf8ToUtf32(); const target = new Uint32Array(5); for (let i = 0; i < 65536; ++i) { - // skip surrogate pairs - if (i >= 0xD800 && i <= 0xDFFF) { + // skip surrogate pairs and a BOM + if ((i >= 0xD800 && i <= 0xDFFF) || i === 0xFEFF) { continue; } const utf8Data = fromByteString(encode(String.fromCharCode(i))); @@ -142,6 +150,15 @@ describe('text encodings', () => { decoder.clear(); } }); + + it('0xFEFF(BOM)', () => { + const decoder = new Utf8ToUtf32(); + const target = new Uint32Array(5); + const utf8Data = fromByteString(encode(String.fromCharCode(0xFEFF))); + const length = decoder.decode(utf8Data, target); + assert.equal(length, 0); + decoder.clear(); + }); }); it('test strings', () => { @@ -215,6 +232,19 @@ describe('text encodings', () => { } assert(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€'); }); + + it('BOMs (3 byte sequences) - advance by 2', () => { + const decoder = new Utf8ToUtf32(); + const target = new Uint32Array(5); + const utf8Data = fromByteString('\xef\xbb\xbf\xef\xbb\xbf'); + let decoded = ''; + for (let i = 0; i < utf8Data.length; i += 2) { + const written = decoder.decode(utf8Data.slice(i, i + 2), target); + decoded += toString(target, written); + } + assert.equal(decoded, ''); + }); + it('test break after 3 bytes - issue #2495', () => { const decoder = new Utf8ToUtf32(); const target = new Uint32Array(5); diff --git a/src/common/input/TextDecoder.ts b/src/common/input/TextDecoder.ts index 6ecab011..715e9197 100644 --- a/src/common/input/TextDecoder.ts +++ b/src/common/input/TextDecoder.ts @@ -105,6 +105,10 @@ export class StringToUtf32 { } continue; } + if (code === 0xFEFF) { + // BOM + continue; + } target[size++] = code; } return size; @@ -188,8 +192,8 @@ export class Utf8ToUtf32 { target[size++] = cp; } } else if (type === 3) { - if (cp < 0x0800 || (cp >= 0xD800 && cp <= 0xDFFF)) { - // illegal codepoint + if (cp < 0x0800 || (cp >= 0xD800 && cp <= 0xDFFF) || cp === 0xFEFF) { + // illegal codepoint or BOM } else { target[size++] = cp; } @@ -286,8 +290,8 @@ export class Utf8ToUtf32 { continue; } codepoint = (byte1 & 0x0F) << 12 | (byte2 & 0x3F) << 6 | (byte3 & 0x3F); - if (codepoint < 0x0800 || (codepoint >= 0xD800 && codepoint <= 0xDFFF)) { - // illegal codepoint, no i-- here + if (codepoint < 0x0800 || (codepoint >= 0xD800 && codepoint <= 0xDFFF) || codepoint === 0xFEFF) { + // illegal codepoint or BOM, no i-- here continue; } target[size++] = codepoint; diff --git a/src/common/input/WriteBuffer.test.ts b/src/common/input/WriteBuffer.test.ts index d3dfebd0..89106423 100644 --- a/src/common/input/WriteBuffer.test.ts +++ b/src/common/input/WriteBuffer.test.ts @@ -85,5 +85,26 @@ describe('WriteBuffer', () => { done(); }); }); + it('writeSync called from action does not overflow callstack - issue #3265', () => { + wb = new WriteBuffer(data => { + const num = parseInt(data as string); + if (num < 1000000) { + wb.writeSync('' + (num + 1)); + } + }); + wb.writeSync('1'); + }); + it('writeSync maxSubsequentCalls argument', () => { + let last: string = ''; + wb = new WriteBuffer(data => { + last = data as string; + const num = parseInt(data as string); + if (num < 1000000) { + wb.writeSync('' + (num + 1), 10); + } + }); + wb.writeSync('1', 10); + assert.equal(last, '11'); // 1 + 10 sub calls = 11 + }); }); }); diff --git a/src/common/input/WriteBuffer.ts b/src/common/input/WriteBuffer.ts index c7d82458..cc84c9ab 100644 --- a/src/common/input/WriteBuffer.ts +++ b/src/common/input/WriteBuffer.ts @@ -31,33 +31,66 @@ const WRITE_TIMEOUT_MS = 12; */ const WRITE_BUFFER_LENGTH_THRESHOLD = 50; +// queueMicrotask polyfill for nodejs < v11 +const qmt: (cb: () => void) => void = (typeof queueMicrotask === 'undefined') + ? (cb: () => void) => { Promise.resolve().then(cb); } + : queueMicrotask; + + export class WriteBuffer { private _writeBuffer: (string | Uint8Array)[] = []; private _callbacks: ((() => void) | undefined)[] = []; private _pendingData = 0; private _bufferOffset = 0; + private _isSyncWriting = false; + private _syncCalls = 0; - constructor(private _action: (data: string | Uint8Array) => void) { } + constructor(private _action: (data: string | Uint8Array, promiseResult?: boolean) => void | Promise) { } + + /** + * @deprecated Unreliable, to be removed soon. + */ + public writeSync(data: string | Uint8Array, maxSubsequentCalls?: number): void { + // stop writeSync recursions with maxSubsequentCalls argument + // This is dangerous to use as it will lose the current data chunk + // and return immediately. + if (maxSubsequentCalls !== undefined && this._syncCalls > maxSubsequentCalls) { + // comment next line if a whole loop block should only contain x `writeSync` calls + // (total flat vs. deep nested limit) + this._syncCalls = 0; + return; + } + // append chunk to buffer + this._pendingData += data.length; + this._writeBuffer.push(data); + this._callbacks.push(undefined); + + // increase recursion counter + this._syncCalls++; + // exit early if another writeSync loop is active + if (this._isSyncWriting) { + return; + } + this._isSyncWriting = true; - public writeSync(data: string | Uint8Array): void { // force sync processing on pending data chunks to avoid in-band data scrambling // does the same as innerWrite but without event loop - if (this._writeBuffer.length) { - for (let i = this._bufferOffset; i < this._writeBuffer.length; ++i) { - const data = this._writeBuffer[i]; - const cb = this._callbacks[i]; - this._action(data); - if (cb) cb(); - } - // reset all to avoid reprocessing of chunks with scheduled innerWrite call - this._writeBuffer = []; - this._callbacks = []; - this._pendingData = 0; - // stop scheduled innerWrite by offset > length condition - this._bufferOffset = 0x7FFFFFFF; + // we have to do it here as single loop steps to not corrupt loop subject + // by another writeSync call triggered from _action + let chunk: string | Uint8Array | undefined; + while (chunk = this._writeBuffer.shift()) { + this._action(chunk); + const cb = this._callbacks.shift(); + if (cb) cb(); } - // handle current data chunk - this._action(data); + // reset to avoid reprocessing of chunks with scheduled innerWrite call + // stopping scheduled innerWrite by offset > length condition + this._pendingData = 0; + this._bufferOffset = 0x7FFFFFFF; + + // allow another writeSync to loop + this._isSyncWriting = false; + this._syncCalls = 0; } public write(data: string | Uint8Array, callback?: () => void): void { @@ -76,16 +109,97 @@ export class WriteBuffer { this._callbacks.push(callback); } - protected _innerWrite(): void { - const startTime = Date.now(); + /** + * Inner write call, that enters the sliced chunk processing by timing. + * + * `lastTime` indicates, when the last _innerWrite call had started. + * It is used to aggregate async handler execution under a timeout constraint + * effectively lowering the redrawing needs, schematically: + * + * macroTask _innerWrite: + * if (Date.now() - (lastTime | 0) < WRITE_TIMEOUT_MS): + * schedule microTask _innerWrite(lastTime) + * else: + * schedule macroTask _innerWrite(0) + * + * overall execution order on task queues: + * + * macrotasks: [...] --> _innerWrite(0) --> [...] --> screenUpdate --> [...] + * m t: | + * i a: [...] + * c s: | + * r k: while < timeout: + * o s: _innerWrite(timeout) + * + * `promiseResult` depicts the promise resolve value of an async handler. + * This value gets carried forward through all saved stack states of the + * paused parser for proper continuation. + * + * Note, for pure sync code `lastTime` and `promiseResult` have no meaning. + */ + protected _innerWrite(lastTime: number = 0, promiseResult: boolean = true): void { + const startTime = lastTime || Date.now(); while (this._writeBuffer.length > this._bufferOffset) { const data = this._writeBuffer[this._bufferOffset]; - const cb = this._callbacks[this._bufferOffset]; - this._bufferOffset++; + const result = this._action(data, promiseResult); + if (result) { + /** + * If we get a promise as return value, we re-schedule the continuation + * as thenable on the promise and exit right away. + * + * The exit here means, that we block input processing at the current active chunk, + * the exact execution position within the chunk is preserved by the saved + * stack content in InputHandler and EscapeSequenceParser. + * + * Resuming happens automatically from that saved stack state. + * Also the resolved promise value is passed along the callstack to + * `EscapeSequenceParser.parse` to correctly resume the stopped handler loop. + * + * Exceptions on async handlers will be logged to console async, but do not interrupt + * the input processing (continues with next handler at the current input position). + */ - this._action(data); - this._pendingData -= data.length; + /** + * If a promise takes long to resolve, we should schedule continuation behind setTimeout. + * This might already be too late, if our .then enters really late (executor + prev thens took very long). + * This cannot be solved here for the handler itself (it is the handlers responsibility to slice hard work), + * but we can at least schedule a screen update as we gain control. + */ + const continuation: (r: boolean) => void = (r: boolean) => Date.now() - startTime >= WRITE_TIMEOUT_MS + ? setTimeout(() => this._innerWrite(0, r)) + : this._innerWrite(startTime, r); + + /** + * Optimization considerations: + * The continuation above favors FPS over throughput by eval'ing `startTime` on resolve. + * This might schedule too many screen updates with bad throughput drops (in case a slow + * resolving handler sliced its work properly behind setTimeout calls). We cannot spot + * this condition here, also the renderer has no way to spot nonsense updates either. + * FIXME: A proper fix for this would track the FPS at the renderer entry level separately. + * + * If favoring of FPS shows bad throughtput impact, use the following instead. It favors + * throughput by eval'ing `startTime` upfront pulling at least one more chunk into the + * current microtask queue (executed before setTimeout). + */ + // const continuation: (r: boolean) => void = Date.now() - startTime >= WRITE_TIMEOUT_MS + // ? r => setTimeout(() => this._innerWrite(0, r)) + // : r => this._innerWrite(startTime, r); + + // Handle exceptions synchronously to current band position, idea: + // 1. spawn a single microtask which we allow to throw hard + // 2. spawn a promise immediately resolving to `true` + // (executed on the same queue, thus properly aligned before continuation happens) + result.catch(err => { + qmt(() => {throw err;}); + return Promise.resolve(false); + }).then(continuation); + return; + } + + const cb = this._callbacks[this._bufferOffset]; if (cb) cb(); + this._bufferOffset++; + this._pendingData -= data.length; if (Date.now() - startTime >= WRITE_TIMEOUT_MS) { break; @@ -99,10 +213,10 @@ export class WriteBuffer { this._callbacks = this._callbacks.slice(this._bufferOffset); this._bufferOffset = 0; } - setTimeout(() => this._innerWrite(), 0); + setTimeout(() => this._innerWrite()); } else { - this._writeBuffer = []; - this._callbacks = []; + this._writeBuffer.length = 0; + this._callbacks.length = 0; this._pendingData = 0; this._bufferOffset = 0; } diff --git a/src/common/parser/DcsParser.test.ts b/src/common/parser/DcsParser.test.ts index dbabb4fa..096ebb58 100644 --- a/src/common/parser/DcsParser.test.ts +++ b/src/common/parser/DcsParser.test.ts @@ -61,11 +61,12 @@ class TestHandler implements IDcsHandler { public put(data: Uint32Array, start: number, end: number): void { this.output.push([this.msg, 'PUT', utf32ToString(data, start, end)]); } - public unhook(success: boolean): void | boolean { + public unhook(success: boolean): boolean { this.output.push([this.msg, 'UNHOOK', success]); if (this.returnFalse) { return false; } + return true; } } @@ -84,7 +85,7 @@ describe('DcsParser', () => { }); describe('handler registration', () => { it('setDcsHandler', () => { - parser.setHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th')); + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th')); parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3])); let data = toUtf32('Here comes'); parser.put(data, 0, data.length); @@ -100,7 +101,7 @@ describe('DcsParser', () => { ]); }); it('clearDcsHandler', () => { - parser.setHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th')); + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th')); parser.clearHandler(identifier({intermediates: '+', final: 'p'})); parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3])); let data = toUtf32('Here comes'); @@ -117,8 +118,8 @@ describe('DcsParser', () => { ]); }); it('addDcsHandler', () => { - parser.setHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th1')); - parser.addHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th2')); + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th1')); + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th2')); parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3])); let data = toUtf32('Here comes'); parser.put(data, 0, data.length); @@ -137,8 +138,8 @@ describe('DcsParser', () => { ]); }); it('addDcsHandler with return false', () => { - parser.setHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th1')); - parser.addHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th2', true)); + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th1')); + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th2', true)); parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3])); let data = toUtf32('Here comes'); parser.put(data, 0, data.length); @@ -157,8 +158,8 @@ describe('DcsParser', () => { ]); }); it('dispose handlers', () => { - parser.setHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th1')); - const dispo = parser.addHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th2', true)); + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th1')); + const dispo = parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th2', true)); dispo.dispose(); parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3])); let data = toUtf32('Here comes'); @@ -176,7 +177,7 @@ describe('DcsParser', () => { }); describe('DcsHandlerFactory', () => { it('should be called once on end(true)', () => { - parser.setHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => reports.push([params.toArray(), data]))); + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => { reports.push([params.toArray(), data]); return true; })); parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3])); let data = toUtf32('Here comes'); parser.put(data, 0, data.length); @@ -186,7 +187,7 @@ describe('DcsParser', () => { assert.deepEqual(reports, [[[1, 2, 3], 'Here comes the mouse!']]); }); it('should not be called on end(false)', () => { - parser.setHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => reports.push([params.toArray(), data]))); + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => { reports.push([params.toArray(), data]); return true; })); parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3])); let data = toUtf32('Here comes'); parser.put(data, 0, data.length); @@ -196,8 +197,8 @@ describe('DcsParser', () => { assert.deepEqual(reports, []); }); it('should be disposable', () => { - parser.setHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => reports.push(['one', params.toArray(), data]))); - const dispo = parser.addHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => reports.push(['two', params.toArray(), data]))); + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => { reports.push(['one', params.toArray(), data]); return true; })); + const dispo = parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => { reports.push(['two', params.toArray(), data]); return true; })); parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3])); let data = toUtf32('Here comes'); parser.put(data, 0, data.length); @@ -215,8 +216,8 @@ describe('DcsParser', () => { assert.deepEqual(reports, [['two', [1, 2, 3], 'Here comes the mouse!'], ['one', [1, 2, 3], 'some other data']]); }); it('should respect return false', () => { - parser.setHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => reports.push(['one', params.toArray(), data]))); - parser.addHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => { reports.push(['two', params.toArray(), data]); return false; })); + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => { reports.push(['one', params.toArray(), data]); return true; })); + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => { reports.push(['two', params.toArray(), data]); return false; })); parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3])); let data = toUtf32('Here comes'); parser.put(data, 0, data.length); @@ -227,7 +228,7 @@ describe('DcsParser', () => { }); it('should work up to payload limit', function(): void { this.timeout(10000); - parser.setHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => reports.push([params.toArray(), data]))); + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => { reports.push([params.toArray(), data]); return true; })); parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3])); const data = toUtf32('A'.repeat(1000)); for (let i = 0; i < PAYLOAD_LIMIT; i += 1000) { @@ -238,7 +239,7 @@ describe('DcsParser', () => { }); it('should abort for payload limit +1', function(): void { this.timeout(10000); - parser.setHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => reports.push([params.toArray(), data]))); + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => { reports.push([params.toArray(), data]); return true; })); parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3])); let data = toUtf32('A'.repeat(1000)); for (let i = 0; i < PAYLOAD_LIMIT; i += 1000) { @@ -251,3 +252,208 @@ describe('DcsParser', () => { }); }); }); + + +class TestHandlerAsync implements IDcsHandler { + constructor(public output: any[], public msg: string, public returnFalse: boolean = false) {} + public hook(params: IParams): void { + this.output.push([this.msg, 'HOOK', params.toArray()]); + } + public put(data: Uint32Array, start: number, end: number): void { + this.output.push([this.msg, 'PUT', utf32ToString(data, start, end)]); + } + public async unhook(success: boolean): Promise { + // simple sleep to check in tests whether ordering gets messed up + await new Promise(res => setTimeout(res, 20)); + this.output.push([this.msg, 'UNHOOK', success]); + if (this.returnFalse) { + return false; + } + return true; + } +} +async function unhookP(parser: DcsParser, success: boolean): Promise { + let result: void | Promise; + let prev: boolean | undefined; + while (result = parser.unhook(success, prev)) { + prev = await result; + } +} + + +describe('DcsParser - async tests', () => { + let parser: DcsParser; + let reports: any[] = []; + beforeEach(() => { + reports = []; + parser = new DcsParser(); + parser.setHandlerFallback((id, action, data) => { + if (action === 'HOOK') { + data = data.toArray(); + } + reports.push([id, action, data]); + }); + }); + describe('sync and async mixed', () => { + describe('sync | async | sync', () => { + it('first should run, cleanup action for others', async () => { + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 's1', false)); + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandlerAsync(reports, 'a1', false)); + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 's2', false)); + parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3])); + let data = toUtf32('Here comes'); + parser.put(data, 0, data.length); + data = toUtf32('the mouse!'); + parser.put(data, 0, data.length); + await unhookP(parser, true); + assert.deepEqual(reports, [ + // messages from TestHandler + ['s2', 'HOOK', [1, 2, 3]], + ['a1', 'HOOK', [1, 2, 3]], + ['s1', 'HOOK', [1, 2, 3]], + ['s2', 'PUT', 'Here comes'], + ['a1', 'PUT', 'Here comes'], + ['s1', 'PUT', 'Here comes'], + ['s2', 'PUT', 'the mouse!'], + ['a1', 'PUT', 'the mouse!'], + ['s1', 'PUT', 'the mouse!'], + ['s2', 'UNHOOK', true], + ['a1', 'UNHOOK', false], // important: a1 before s1 + ['s1', 'UNHOOK', false] + ]); + }); + it('all should run', async () => { + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 's1', true)); + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandlerAsync(reports, 'a1', true)); + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 's2', true)); + parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3])); + let data = toUtf32('Here comes'); + parser.put(data, 0, data.length); + data = toUtf32('the mouse!'); + parser.put(data, 0, data.length); + await unhookP(parser, true); + assert.deepEqual(reports, [ + // messages from TestHandler + ['s2', 'HOOK', [1, 2, 3]], + ['a1', 'HOOK', [1, 2, 3]], + ['s1', 'HOOK', [1, 2, 3]], + ['s2', 'PUT', 'Here comes'], + ['a1', 'PUT', 'Here comes'], + ['s1', 'PUT', 'Here comes'], + ['s2', 'PUT', 'the mouse!'], + ['a1', 'PUT', 'the mouse!'], + ['s1', 'PUT', 'the mouse!'], + ['s2', 'UNHOOK', true], + ['a1', 'UNHOOK', true], // important: a1 before s1 + ['s1', 'UNHOOK', true] + ]); + }); + }); + describe('async | sync | async', () => { + it('first should run, cleanup action for others', async () => { + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandlerAsync(reports, 'a1', false)); + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 's1', false)); + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandlerAsync(reports, 'a2', false)); + parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3])); + let data = toUtf32('Here comes'); + parser.put(data, 0, data.length); + data = toUtf32('the mouse!'); + parser.put(data, 0, data.length); + await unhookP(parser, true); + assert.deepEqual(reports, [ + // messages from TestHandler + ['a2', 'HOOK', [1, 2, 3]], + ['s1', 'HOOK', [1, 2, 3]], + ['a1', 'HOOK', [1, 2, 3]], + ['a2', 'PUT', 'Here comes'], + ['s1', 'PUT', 'Here comes'], + ['a1', 'PUT', 'Here comes'], + ['a2', 'PUT', 'the mouse!'], + ['s1', 'PUT', 'the mouse!'], + ['a1', 'PUT', 'the mouse!'], + ['a2', 'UNHOOK', true], + ['s1', 'UNHOOK', false], // important: s1 between a2 .. a1 + ['a1', 'UNHOOK', false] + ]); + }); + it('all should run', async () => { + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandlerAsync(reports, 'a1', true)); + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 's1', true)); + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandlerAsync(reports, 'a2', true)); + parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3])); + let data = toUtf32('Here comes'); + parser.put(data, 0, data.length); + data = toUtf32('the mouse!'); + parser.put(data, 0, data.length); + await unhookP(parser, true); + assert.deepEqual(reports, [ + // messages from TestHandler + ['a2', 'HOOK', [1, 2, 3]], + ['s1', 'HOOK', [1, 2, 3]], + ['a1', 'HOOK', [1, 2, 3]], + ['a2', 'PUT', 'Here comes'], + ['s1', 'PUT', 'Here comes'], + ['a1', 'PUT', 'Here comes'], + ['a2', 'PUT', 'the mouse!'], + ['s1', 'PUT', 'the mouse!'], + ['a1', 'PUT', 'the mouse!'], + ['a2', 'UNHOOK', true], + ['s1', 'UNHOOK', true], // important: s1 between a2 .. a1 + ['a1', 'UNHOOK', true] + ]); + }); + }); + describe('DcsHandlerFactory', () => { + it('should be called once on end(true)', async () => { + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler(async (data, params) => { reports.push([params.toArray(), data]); return true; })); + parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3])); + let data = toUtf32('Here comes'); + parser.put(data, 0, data.length); + data = toUtf32(' the mouse!'); + parser.put(data, 0, data.length); + await unhookP(parser, true); + assert.deepEqual(reports, [[[1, 2, 3], 'Here comes the mouse!']]); + }); + it('should not be called on end(false)', async () => { + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler(async (data, params) => { reports.push([params.toArray(), data]); return true; })); + parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3])); + let data = toUtf32('Here comes'); + parser.put(data, 0, data.length); + data = toUtf32(' the mouse!'); + parser.put(data, 0, data.length); + await unhookP(parser, false); + assert.deepEqual(reports, []); + }); + it('should be disposable', async () => { + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler(async (data, params) => { reports.push(['one', params.toArray(), data]); return true; })); + const dispo = parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler(async (data, params) => { reports.push(['two', params.toArray(), data]); return true; })); + parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3])); + let data = toUtf32('Here comes'); + parser.put(data, 0, data.length); + data = toUtf32(' the mouse!'); + parser.put(data, 0, data.length); + await unhookP(parser, true); + assert.deepEqual(reports, [['two', [1, 2, 3], 'Here comes the mouse!']]); + dispo.dispose(); + parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3])); + data = toUtf32('some other'); + parser.put(data, 0, data.length); + data = toUtf32(' data'); + parser.put(data, 0, data.length); + await unhookP(parser, true); + assert.deepEqual(reports, [['two', [1, 2, 3], 'Here comes the mouse!'], ['one', [1, 2, 3], 'some other data']]); + }); + it('should respect return false', async () => { + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler(async (data, params) => { reports.push(['one', params.toArray(), data]); return true; })); + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler(async (data, params) => { reports.push(['two', params.toArray(), data]); return false; })); + parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3])); + let data = toUtf32('Here comes'); + parser.put(data, 0, data.length); + data = toUtf32(' the mouse!'); + parser.put(data, 0, data.length); + await unhookP(parser, true); + assert.deepEqual(reports, [['two', [1, 2, 3], 'Here comes the mouse!'], ['one', [1, 2, 3], 'Here comes the mouse!']]); + }); + }); + }); +}); diff --git a/src/common/parser/DcsParser.ts b/src/common/parser/DcsParser.ts index a09fd68c..b66524ba 100644 --- a/src/common/parser/DcsParser.ts +++ b/src/common/parser/DcsParser.ts @@ -4,7 +4,7 @@ */ import { IDisposable } from 'common/Types'; -import { IDcsHandler, IParams, IHandlerCollection, IDcsParser, DcsFallbackHandlerType } from 'common/parser/Types'; +import { IDcsHandler, IParams, IHandlerCollection, IDcsParser, DcsFallbackHandlerType, ISubParserStackState } from 'common/parser/Types'; import { utf32ToString } from 'common/input/TextDecoder'; import { Params } from 'common/parser/Params'; import { PAYLOAD_LIMIT } from 'common/parser/Constants'; @@ -15,14 +15,20 @@ export class DcsParser implements IDcsParser { private _handlers: IHandlerCollection = Object.create(null); private _active: IDcsHandler[] = EMPTY_HANDLERS; private _ident: number = 0; - private _handlerFb: DcsFallbackHandlerType = () => {}; + private _handlerFb: DcsFallbackHandlerType = () => { }; + private _stack: ISubParserStackState = { + paused: false, + loopPosition: 0, + fallThrough: false + }; public dispose(): void { this._handlers = Object.create(null); - this._handlerFb = () => {}; + this._handlerFb = () => { }; + this._active = EMPTY_HANDLERS; } - public addHandler(ident: number, handler: IDcsHandler): IDisposable { + public registerHandler(ident: number, handler: IDcsHandler): IDisposable { if (this._handlers[ident] === undefined) { this._handlers[ident] = []; } @@ -38,10 +44,6 @@ export class DcsParser implements IDcsParser { }; } - public setHandler(ident: number, handler: IDcsHandler): void { - this._handlers[ident] = [handler]; - } - public clearHandler(ident: number): void { if (this._handlers[ident]) delete this._handlers[ident]; } @@ -51,9 +53,13 @@ export class DcsParser implements IDcsParser { } public reset(): void { + // force cleanup leftover handlers if (this._active.length) { - this.unhook(false); + for (let j = this._stack.paused ? this._stack.loopPosition - 1 : this._active.length - 1; j >= 0; --j) { + this._active[j].unhook(false); + } } + this._stack.paused = false; this._active = EMPTY_HANDLERS; this._ident = 0; } @@ -82,20 +88,42 @@ export class DcsParser implements IDcsParser { } } - public unhook(success: boolean): void { + public unhook(success: boolean, promiseResult: boolean = true): void | Promise { if (!this._active.length) { this._handlerFb(this._ident, 'UNHOOK', success); } else { + let handlerResult: boolean | Promise = false; let j = this._active.length - 1; - for (; j >= 0; j--) { - if (this._active[j].unhook(success) !== false) { - break; - } + let fallThrough = false; + if (this._stack.paused) { + j = this._stack.loopPosition - 1; + handlerResult = promiseResult; + fallThrough = this._stack.fallThrough; + this._stack.paused = false; } - j--; - // cleanup left over handlers + if (!fallThrough && handlerResult === false) { + for (; j >= 0; j--) { + handlerResult = this._active[j].unhook(success); + if (handlerResult === true) { + break; + } else if (handlerResult instanceof Promise) { + this._stack.paused = true; + this._stack.loopPosition = j; + this._stack.fallThrough = false; + return handlerResult; + } + } + j--; + } + // cleanup left over handlers (fallThrough for async) for (; j >= 0; j--) { - this._active[j].unhook(false); + handlerResult = this._active[j].unhook(false); + if (handlerResult instanceof Promise) { + this._stack.paused = true; + this._stack.loopPosition = j; + this._stack.fallThrough = true; + return handlerResult; + } } } this._active = EMPTY_HANDLERS; @@ -103,19 +131,27 @@ export class DcsParser implements IDcsParser { } } +// predefine empty params as [0] (ZDM) +const EMPTY_PARAMS = new Params(); +EMPTY_PARAMS.addParam(0); + /** * Convenient class to create a DCS handler from a single callback function. * Note: The payload is currently limited to 50 MB (hardcoded). */ export class DcsHandler implements IDcsHandler { private _data = ''; - private _params: IParams | undefined; + private _params: IParams = EMPTY_PARAMS; private _hitLimit: boolean = false; - constructor(private _handler: (data: string, params: IParams) => any) {} + constructor(private _handler: (data: string, params: IParams) => boolean | Promise) { } public hook(params: IParams): void { - this._params = params.clone(); + // since we need to preserve params until `unhook`, we have to clone it + // (only borrowed from parser and spans multiple parser states) + // perf optimization: + // clone only, if we have non empty params, otherwise stick with default + this._params = (params.length > 1 || params.params[0]) ? params.clone() : EMPTY_PARAMS; this._data = ''; this._hitLimit = false; } @@ -131,14 +167,24 @@ export class DcsHandler implements IDcsHandler { } } - public unhook(success: boolean): any { - let ret; + public unhook(success: boolean): boolean | Promise { + let ret: boolean | Promise = false; if (this._hitLimit) { ret = false; } else if (success) { - ret = this._handler(this._data, this._params || new Params()); + ret = this._handler(this._data, this._params); + if (ret instanceof Promise) { + // need to hold data and params until `ret` got resolved + // dont care for errors, data will be freed anyway on next start + return ret.then(res => { + this._params = EMPTY_PARAMS; + this._data = ''; + this._hitLimit = false; + return res; + }); + } } - this._params = undefined; + this._params = EMPTY_PARAMS; this._data = ''; this._hitLimit = false; return ret; diff --git a/src/common/parser/EscapeSequenceParser.test.ts b/src/common/parser/EscapeSequenceParser.test.ts index 4934d346..4b23fa00 100644 --- a/src/common/parser/EscapeSequenceParser.test.ts +++ b/src/common/parser/EscapeSequenceParser.test.ts @@ -3,9 +3,9 @@ * @license MIT */ -import { IParsingState, IParams, ParamsArray, IOscParser, IOscHandler, OscFallbackHandlerType, IFunctionIdentifier } from 'common/parser/Types'; +import { IParsingState, IParams, ParamsArray, IOscParser, IOscHandler, OscFallbackHandlerType, IFunctionIdentifier, IParserStackState, ParserStackType, ResumableHandlersType } from 'common/parser/Types'; import { EscapeSequenceParser, TransitionTable, VT500_TRANSITION_TABLE } from 'common/parser/EscapeSequenceParser'; -import * as chai from 'chai'; +import { assert } from 'chai'; import { StringToUtf32, stringFromCodePoint, utf32ToString } from 'common/input/TextDecoder'; import { ParserState } from 'common/parser/Constants'; import { Params } from 'common/parser/Params'; @@ -24,7 +24,7 @@ function r(a: number, b: number): string[] { } class MockOscPutParser implements IOscParser { - private _fallback: OscFallbackHandlerType = () => {}; + private _fallback: OscFallbackHandlerType = () => { }; public data = ''; public reset(): void { this.data = ''; @@ -41,7 +41,7 @@ class MockOscPutParser implements IOscParser { this._fallback(id, 'END', this.data.slice(this.data.indexOf(';') + 1)); } } - public addHandler(ident: number, handler: IOscHandler): IDisposable { + public registerHandler(ident: number, handler: IOscHandler): IDisposable { throw new Error('not implemented'); } public setHandler(ident: number, handler: IOscHandler): void { @@ -92,43 +92,58 @@ class TestEscapeSequenceParser extends EscapeSequenceParser { public identifier(id: IFunctionIdentifier): number { return this._identifier(id); } + public get parseStack(): IParserStackState { + return this._parseStack; + } + private _trackStack = false; + public trackStackSavesOnPause(): void { + this._trackStack = true; + } + public trackedStack: IParserStackState[] = []; + public parse(data: Uint32Array, length: number, promiseResult?: boolean): void | Promise { + const result = super.parse(data, length, promiseResult); + if (result instanceof Promise && this._trackStack) { + this.trackedStack.push({ ...this.parseStack }); + } + return result; + } } // test object to collect parser actions and compare them with expected values const testTerminal: any = { calls: [], - clear: function (): void { + clear(): void { this.calls = []; }, - compare: function (value: any): void { - chai.expect(this.calls.slice()).eql(value); // weird bug w'o slicing here + compare(value: any): void { + assert.deepEqual(this.calls, value); }, - print: function (data: Uint32Array, start: number, end: number): void { + print(data: Uint32Array, start: number, end: number): void { let s = ''; for (let i = start; i < end; ++i) { s += stringFromCodePoint(data[i]); } this.calls.push(['print', s]); }, - actionOSC: function (s: string): void { + actionOSC(s: string): void { this.calls.push(['osc', s]); }, - actionExecute: function (flag: string): void { + actionExecute(flag: string): void { this.calls.push(['exe', flag]); }, - actionCSI: function (collect: string, params: IParams, flag: string): void { + actionCSI(collect: string, params: IParams, flag: string): void { this.calls.push(['csi', collect, params.toArray(), flag]); }, - actionESC: function (collect: string, flag: string): void { + actionESC(collect: string, flag: string): void { this.calls.push(['esc', collect, flag]); }, - actionDCSHook: function (params: IParams): void { + actionDCSHook(params: IParams): void { this.calls.push(['dcs hook', params.toArray()]); }, - actionDCSPrint: function (s: string): void { + actionDCSPrint(s: string): void { this.calls.push(['dcs put', s]); }, - actionDCSUnhook: function (success: boolean): void { + actionDCSUnhook(success: boolean): void { this.calls.push(['dcs unhook', success]); } }; @@ -191,40 +206,40 @@ function parse(parser: TestEscapeSequenceParser, data: string): void { parser.parse(container, decoder.decode(data, container)); } -describe('EscapeSequenceParser', function (): void { +describe('EscapeSequenceParser', () => { const parser = testParser; - describe('Parser init and methods', function (): void { - it('constructor', function (): void { + describe('Parser init and methods', () => { + it('constructor', () => { let p = new TestEscapeSequenceParser(); - chai.expect(p.transitions).equal(VT500_TRANSITION_TABLE); + assert.deepEqual(p.transitions, VT500_TRANSITION_TABLE); p = new TestEscapeSequenceParser(VT500_TRANSITION_TABLE); - chai.expect(p.transitions).equal(VT500_TRANSITION_TABLE); + assert.deepEqual(p.transitions, VT500_TRANSITION_TABLE); const tansitions: TransitionTable = new TransitionTable(10); p = new TestEscapeSequenceParser(tansitions); - chai.expect(p.transitions).equal(tansitions); + assert.deepEqual(p.transitions, tansitions); }); - it('inital states', function (): void { - chai.expect(parser.initialState).equal(ParserState.GROUND); - chai.expect(parser.currentState).equal(ParserState.GROUND); - chai.expect(parser.osc).equal(''); - chai.expect(parser.params).eql([0]); - chai.expect(parser.collect).equal(''); + it('inital states', () => { + assert.equal(parser.initialState, ParserState.GROUND); + assert.equal(parser.currentState, ParserState.GROUND); + assert.equal(parser.osc, ''); + assert.deepEqual(parser.params, [0]); + assert.equal(parser.collect, ''); }); - it('reset states', function (): void { + it('reset states', () => { parser.currentState = 124; parser.osc = '#'; parser.params = [123]; parser.collect = '#'; parser.reset(); - chai.expect(parser.currentState).equal(ParserState.GROUND); - chai.expect(parser.osc).equal(''); - chai.expect(parser.params).eql([0]); - chai.expect(parser.collect).equal(''); + assert.equal(parser.currentState, ParserState.GROUND); + assert.equal(parser.osc, ''); + assert.deepEqual(parser.params, [0]); + assert.equal(parser.collect, ''); }); }); - describe('state transitions and actions', function (): void { - it('state GROUND execute action', function (): void { + describe('state transitions and actions', () => { + it('state GROUND execute action', () => { parser.reset(); testTerminal.clear(); let exes = r(0x00, 0x18); @@ -233,26 +248,26 @@ describe('EscapeSequenceParser', function (): void { for (let i = 0; i < exes.length; ++i) { parser.currentState = ParserState.GROUND; parse(parser, exes[i]); - chai.expect(parser.currentState).equal(ParserState.GROUND); + assert.equal(parser.currentState, ParserState.GROUND); testTerminal.compare([['exe', exes[i]]]); parser.reset(); testTerminal.clear(); } }); - it('state GROUND print action', function (): void { + it('state GROUND print action', () => { parser.reset(); testTerminal.clear(); const printables = r(0x20, 0x7f); // NOTE: DEL excluded for (let i = 0; i < printables.length; ++i) { parser.currentState = ParserState.GROUND; parse(parser, printables[i]); - chai.expect(parser.currentState).equal(ParserState.GROUND); + assert.equal(parser.currentState, ParserState.GROUND); testTerminal.compare([['print', printables[i]]]); parser.reset(); testTerminal.clear(); } }); - it('trans ANYWHERE --> GROUND with actions', function (): void { + it('trans ANYWHERE --> GROUND with actions', () => { const exes = [ '\x18', '\x1a', '\x80', '\x81', '\x82', '\x83', '\x84', '\x85', '\x86', '\x87', '\x88', @@ -269,32 +284,32 @@ describe('EscapeSequenceParser', function (): void { for (let i = 0; i < exes.length; ++i) { parser.currentState = state; parse(parser, exes[i]); - chai.expect(parser.currentState).equal(ParserState.GROUND); + assert.equal(parser.currentState, ParserState.GROUND); testTerminal.compare((state in exceptions ? exceptions[state][exes[i]] : 0) || [['exe', exes[i]]]); parser.reset(); testTerminal.clear(); } parse(parser, '\x9c'); - chai.expect(parser.currentState).equal(ParserState.GROUND); + assert.equal(parser.currentState, ParserState.GROUND); testTerminal.compare([]); parser.reset(); testTerminal.clear(); } }); - it('trans ANYWHERE --> ESCAPE with clear', function (): void { + it('trans ANYWHERE --> ESCAPE with clear', () => { parser.reset(); for (state in states) { parser.currentState = state; parser.params = [23]; parser.collect = '#'; parse(parser, '\x1b'); - chai.expect(parser.currentState).equal(ParserState.ESCAPE); - chai.expect(parser.params).eql([0]); - chai.expect(parser.collect).equal(''); + assert.equal(parser.currentState, ParserState.ESCAPE); + assert.deepEqual(parser.params, [0]); + assert.equal(parser.collect, ''); parser.reset(); } }); - it('state ESCAPE execute rules', function (): void { + it('state ESCAPE execute rules', () => { parser.reset(); testTerminal.clear(); let exes = r(0x00, 0x18); @@ -303,23 +318,23 @@ describe('EscapeSequenceParser', function (): void { for (let i = 0; i < exes.length; ++i) { parser.currentState = ParserState.ESCAPE; parse(parser, exes[i]); - chai.expect(parser.currentState).equal(ParserState.ESCAPE); + assert.equal(parser.currentState, ParserState.ESCAPE); testTerminal.compare([['exe', exes[i]]]); parser.reset(); testTerminal.clear(); } }); - it('state ESCAPE ignore', function (): void { + it('state ESCAPE ignore', () => { parser.reset(); testTerminal.clear(); parser.currentState = ParserState.ESCAPE; parse(parser, '\x7f'); - chai.expect(parser.currentState).equal(ParserState.ESCAPE); + assert.equal(parser.currentState, ParserState.ESCAPE); testTerminal.compare([]); parser.reset(); testTerminal.clear(); }); - it('trans ESCAPE --> GROUND with ecs_dispatch action', function (): void { + it('trans ESCAPE --> GROUND with ecs_dispatch action', () => { parser.reset(); testTerminal.clear(); let dispatches = r(0x30, 0x50); @@ -329,24 +344,24 @@ describe('EscapeSequenceParser', function (): void { for (let i = 0; i < dispatches.length; ++i) { parser.currentState = ParserState.ESCAPE; parse(parser, dispatches[i]); - chai.expect(parser.currentState).equal(ParserState.GROUND); + assert.equal(parser.currentState, ParserState.GROUND); testTerminal.compare([['esc', '', dispatches[i]]]); parser.reset(); testTerminal.clear(); } }); - it('trans ESCAPE --> ESCAPE_INTERMEDIATE with collect action', function (): void { + it('trans ESCAPE --> ESCAPE_INTERMEDIATE with collect action', () => { parser.reset(); const collect = r(0x20, 0x30); for (let i = 0; i < collect.length; ++i) { parser.currentState = ParserState.ESCAPE; parse(parser, collect[i]); - chai.expect(parser.currentState).equal(ParserState.ESCAPE_INTERMEDIATE); - chai.expect(parser.collect).equal(collect[i]); + assert.equal(parser.currentState, ParserState.ESCAPE_INTERMEDIATE); + assert.equal(parser.collect, collect[i]); parser.reset(); } }); - it('state ESCAPE_INTERMEDIATE execute rules', function (): void { + it('state ESCAPE_INTERMEDIATE execute rules', () => { parser.reset(); testTerminal.clear(); let exes = r(0x00, 0x18); @@ -355,57 +370,57 @@ describe('EscapeSequenceParser', function (): void { for (let i = 0; i < exes.length; ++i) { parser.currentState = ParserState.ESCAPE_INTERMEDIATE; parse(parser, exes[i]); - chai.expect(parser.currentState).equal(ParserState.ESCAPE_INTERMEDIATE); + assert.equal(parser.currentState, ParserState.ESCAPE_INTERMEDIATE); testTerminal.compare([['exe', exes[i]]]); parser.reset(); testTerminal.clear(); } }); - it('state ESCAPE_INTERMEDIATE ignore', function (): void { + it('state ESCAPE_INTERMEDIATE ignore', () => { parser.reset(); testTerminal.clear(); parser.currentState = ParserState.ESCAPE_INTERMEDIATE; parse(parser, '\x7f'); - chai.expect(parser.currentState).equal(ParserState.ESCAPE_INTERMEDIATE); + assert.equal(parser.currentState, ParserState.ESCAPE_INTERMEDIATE); testTerminal.compare([]); parser.reset(); testTerminal.clear(); }); - it('state ESCAPE_INTERMEDIATE collect action', function (): void { + it('state ESCAPE_INTERMEDIATE collect action', () => { parser.reset(); const collect = r(0x20, 0x30); for (let i = 0; i < collect.length; ++i) { parser.currentState = ParserState.ESCAPE_INTERMEDIATE; parse(parser, collect[i]); - chai.expect(parser.currentState).equal(ParserState.ESCAPE_INTERMEDIATE); - chai.expect(parser.collect).equal(collect[i]); + assert.equal(parser.currentState, ParserState.ESCAPE_INTERMEDIATE); + assert.equal(parser.collect, collect[i]); parser.reset(); } }); - it('trans ESCAPE_INTERMEDIATE --> GROUND with esc_dispatch action', function (): void { + it('trans ESCAPE_INTERMEDIATE --> GROUND with esc_dispatch action', () => { parser.reset(); testTerminal.clear(); const collect = r(0x30, 0x7f); for (let i = 0; i < collect.length; ++i) { parser.currentState = ParserState.ESCAPE_INTERMEDIATE; parse(parser, collect[i]); - chai.expect(parser.currentState).equal(ParserState.GROUND); + assert.equal(parser.currentState, ParserState.GROUND); // '\x5c' --> ESC + \ (7bit ST) parser does not expose this as it already got handled testTerminal.compare((collect[i] === '\x5c') ? [] : [['esc', '', collect[i]]]); parser.reset(); testTerminal.clear(); } }); - it('trans ANYWHERE/ESCAPE --> CSI_ENTRY with clear', function (): void { + it('trans ANYWHERE/ESCAPE --> CSI_ENTRY with clear', () => { parser.reset(); // C0 parser.currentState = ParserState.ESCAPE; parser.params = [123]; parser.collect = '#'; parse(parser, '['); - chai.expect(parser.currentState).equal(ParserState.CSI_ENTRY); - chai.expect(parser.params).eql([0]); - chai.expect(parser.collect).equal(''); + assert.equal(parser.currentState, ParserState.CSI_ENTRY); + assert.deepEqual(parser.params, [0]); + assert.equal(parser.collect, ''); parser.reset(); // C1 for (state in states) { @@ -413,13 +428,13 @@ describe('EscapeSequenceParser', function (): void { parser.params = [123]; parser.collect = '#'; parse(parser, '\x9b'); - chai.expect(parser.currentState).equal(ParserState.CSI_ENTRY); - chai.expect(parser.params).eql([0]); - chai.expect(parser.collect).equal(''); + assert.equal(parser.currentState, ParserState.CSI_ENTRY); + assert.deepEqual(parser.params, [0]); + assert.equal(parser.collect, ''); parser.reset(); } }); - it('state CSI_ENTRY execute rules', function (): void { + it('state CSI_ENTRY execute rules', () => { parser.reset(); testTerminal.clear(); let exes = r(0x00, 0x18); @@ -428,59 +443,59 @@ describe('EscapeSequenceParser', function (): void { for (let i = 0; i < exes.length; ++i) { parser.currentState = ParserState.CSI_ENTRY; parse(parser, exes[i]); - chai.expect(parser.currentState).equal(ParserState.CSI_ENTRY); + assert.equal(parser.currentState, ParserState.CSI_ENTRY); testTerminal.compare([['exe', exes[i]]]); parser.reset(); testTerminal.clear(); } }); - it('state CSI_ENTRY ignore', function (): void { + it('state CSI_ENTRY ignore', () => { parser.reset(); testTerminal.clear(); parser.currentState = ParserState.CSI_ENTRY; parse(parser, '\x7f'); - chai.expect(parser.currentState).equal(ParserState.CSI_ENTRY); + assert.equal(parser.currentState, ParserState.CSI_ENTRY); testTerminal.compare([]); parser.reset(); testTerminal.clear(); }); - it('trans CSI_ENTRY --> GROUND with csi_dispatch action', function (): void { + it('trans CSI_ENTRY --> GROUND with csi_dispatch action', () => { parser.reset(); const dispatches = r(0x40, 0x7f); for (let i = 0; i < dispatches.length; ++i) { parser.currentState = ParserState.CSI_ENTRY; parse(parser, dispatches[i]); - chai.expect(parser.currentState).equal(ParserState.GROUND); + assert.equal(parser.currentState, ParserState.GROUND); testTerminal.compare([['csi', '', [0], dispatches[i]]]); parser.reset(); testTerminal.clear(); } }); - it('trans CSI_ENTRY --> CSI_PARAM with param/collect actions', function (): void { + it('trans CSI_ENTRY --> CSI_PARAM with param/collect actions', () => { parser.reset(); const params = ['\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', '\x38', '\x39']; const collect = ['\x3c', '\x3d', '\x3e', '\x3f']; for (let i = 0; i < params.length; ++i) { parser.currentState = ParserState.CSI_ENTRY; parse(parser, params[i]); - chai.expect(parser.currentState).equal(ParserState.CSI_PARAM); - chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); + assert.equal(parser.currentState, ParserState.CSI_PARAM); + assert.deepEqual(parser.params, [params[i].charCodeAt(0) - 48]); parser.reset(); } parser.currentState = ParserState.CSI_ENTRY; parse(parser, '\x3b'); - chai.expect(parser.currentState).equal(ParserState.CSI_PARAM); - chai.expect(parser.params).eql([0, 0]); + assert.equal(parser.currentState, ParserState.CSI_PARAM); + assert.deepEqual(parser.params, [0, 0]); parser.reset(); for (let i = 0; i < collect.length; ++i) { parser.currentState = ParserState.CSI_ENTRY; parse(parser, collect[i]); - chai.expect(parser.currentState).equal(ParserState.CSI_PARAM); - chai.expect(parser.collect).equal(collect[i]); + assert.equal(parser.currentState, ParserState.CSI_PARAM); + assert.equal(parser.collect, collect[i]); parser.reset(); } }); - it('state CSI_PARAM execute rules', function (): void { + it('state CSI_PARAM execute rules', () => { parser.reset(); testTerminal.clear(); let exes = r(0x00, 0x18); @@ -489,74 +504,74 @@ describe('EscapeSequenceParser', function (): void { for (let i = 0; i < exes.length; ++i) { parser.currentState = ParserState.CSI_PARAM; parse(parser, exes[i]); - chai.expect(parser.currentState).equal(ParserState.CSI_PARAM); + assert.equal(parser.currentState, ParserState.CSI_PARAM); testTerminal.compare([['exe', exes[i]]]); parser.reset(); testTerminal.clear(); } }); - it('state CSI_PARAM param action', function (): void { + it('state CSI_PARAM param action', () => { parser.reset(); const params = ['\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', '\x38', '\x39']; for (let i = 0; i < params.length; ++i) { parser.currentState = ParserState.CSI_PARAM; parse(parser, params[i]); - chai.expect(parser.currentState).equal(ParserState.CSI_PARAM); - chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); + assert.equal(parser.currentState, ParserState.CSI_PARAM); + assert.deepEqual(parser.params, [params[i].charCodeAt(0) - 48]); parser.reset(); } parser.currentState = ParserState.CSI_PARAM; parse(parser, '\x3b'); - chai.expect(parser.currentState).equal(ParserState.CSI_PARAM); - chai.expect(parser.params).eql([0, 0]); + assert.equal(parser.currentState, ParserState.CSI_PARAM); + assert.deepEqual(parser.params, [0, 0]); parser.reset(); }); - it('state CSI_PARAM ignore', function (): void { + it('state CSI_PARAM ignore', () => { parser.reset(); testTerminal.clear(); parser.currentState = ParserState.CSI_PARAM; parse(parser, '\x7f'); - chai.expect(parser.currentState).equal(ParserState.CSI_PARAM); + assert.equal(parser.currentState, ParserState.CSI_PARAM); testTerminal.compare([]); parser.reset(); testTerminal.clear(); }); - it('trans CSI_PARAM --> GROUND with csi_dispatch action', function (): void { + it('trans CSI_PARAM --> GROUND with csi_dispatch action', () => { parser.reset(); const dispatches = r(0x40, 0x7f); for (let i = 0; i < dispatches.length; ++i) { parser.currentState = ParserState.CSI_PARAM; parser.params = [0, 1]; parse(parser, dispatches[i]); - chai.expect(parser.currentState).equal(ParserState.GROUND); + assert.equal(parser.currentState, ParserState.GROUND); testTerminal.compare([['csi', '', [0, 1], dispatches[i]]]); parser.reset(); testTerminal.clear(); } }); - it('trans CSI_ENTRY --> CSI_INTERMEDIATE with collect action', function (): void { + it('trans CSI_ENTRY --> CSI_INTERMEDIATE with collect action', () => { parser.reset(); const collect = r(0x20, 0x30); for (let i = 0; i < collect.length; ++i) { parser.currentState = ParserState.CSI_ENTRY; parse(parser, collect[i]); - chai.expect(parser.currentState).equal(ParserState.CSI_INTERMEDIATE); - chai.expect(parser.collect).equal(collect[i]); + assert.equal(parser.currentState, ParserState.CSI_INTERMEDIATE); + assert.equal(parser.collect, collect[i]); parser.reset(); } }); - it('trans CSI_PARAM --> CSI_INTERMEDIATE with collect action', function (): void { + it('trans CSI_PARAM --> CSI_INTERMEDIATE with collect action', () => { parser.reset(); const collect = r(0x20, 0x30); for (let i = 0; i < collect.length; ++i) { parser.currentState = ParserState.CSI_PARAM; parse(parser, collect[i]); - chai.expect(parser.currentState).equal(ParserState.CSI_INTERMEDIATE); - chai.expect(parser.collect).equal(collect[i]); + assert.equal(parser.currentState, ParserState.CSI_INTERMEDIATE); + assert.equal(parser.collect, collect[i]); parser.reset(); } }); - it('state CSI_INTERMEDIATE execute rules', function (): void { + it('state CSI_INTERMEDIATE execute rules', () => { parser.reset(); testTerminal.clear(); let exes = r(0x00, 0x18); @@ -565,88 +580,88 @@ describe('EscapeSequenceParser', function (): void { for (let i = 0; i < exes.length; ++i) { parser.currentState = ParserState.CSI_INTERMEDIATE; parse(parser, exes[i]); - chai.expect(parser.currentState).equal(ParserState.CSI_INTERMEDIATE); + assert.equal(parser.currentState, ParserState.CSI_INTERMEDIATE); testTerminal.compare([['exe', exes[i]]]); parser.reset(); testTerminal.clear(); } }); - it('state CSI_INTERMEDIATE collect', function (): void { + it('state CSI_INTERMEDIATE collect', () => { parser.reset(); const collect = r(0x20, 0x30); for (let i = 0; i < collect.length; ++i) { parser.currentState = ParserState.CSI_INTERMEDIATE; parse(parser, collect[i]); - chai.expect(parser.currentState).equal(ParserState.CSI_INTERMEDIATE); - chai.expect(parser.collect).equal(collect[i]); + assert.equal(parser.currentState, ParserState.CSI_INTERMEDIATE); + assert.equal(parser.collect, collect[i]); parser.reset(); } }); - it('state CSI_INTERMEDIATE ignore', function (): void { + it('state CSI_INTERMEDIATE ignore', () => { parser.reset(); testTerminal.clear(); parser.currentState = ParserState.CSI_INTERMEDIATE; parse(parser, '\x7f'); - chai.expect(parser.currentState).equal(ParserState.CSI_INTERMEDIATE); + assert.equal(parser.currentState, ParserState.CSI_INTERMEDIATE); testTerminal.compare([]); parser.reset(); testTerminal.clear(); }); - it('trans CSI_INTERMEDIATE --> GROUND with csi_dispatch action', function (): void { + it('trans CSI_INTERMEDIATE --> GROUND with csi_dispatch action', () => { parser.reset(); const dispatches = r(0x40, 0x7f); for (let i = 0; i < dispatches.length; ++i) { parser.currentState = ParserState.CSI_INTERMEDIATE; parser.params = [0, 1]; parse(parser, dispatches[i]); - chai.expect(parser.currentState).equal(ParserState.GROUND); + assert.equal(parser.currentState, ParserState.GROUND); testTerminal.compare([['csi', '', [0, 1], dispatches[i]]]); parser.reset(); testTerminal.clear(); } }); - it('trans CSI_ENTRY --> CSI_PARAM for ":" (0x3a)', function (): void { + it('trans CSI_ENTRY --> CSI_PARAM for ":" (0x3a)', () => { parser.reset(); parser.currentState = ParserState.CSI_ENTRY; parse(parser, '\x3a'); - chai.expect(parser.currentState).equal(ParserState.CSI_PARAM); + assert.equal(parser.currentState, ParserState.CSI_PARAM); parser.reset(); }); - it('trans CSI_PARAM --> CSI_IGNORE', function (): void { + it('trans CSI_PARAM --> CSI_IGNORE', () => { parser.reset(); const chars = ['\x3c', '\x3d', '\x3e', '\x3f']; for (let i = 0; i < chars.length; ++i) { parser.currentState = ParserState.CSI_PARAM; parse(parser, '\x3b' + chars[i]); - chai.expect(parser.currentState).equal(ParserState.CSI_IGNORE); - chai.expect(parser.params).eql([0, 0]); + assert.equal(parser.currentState, ParserState.CSI_IGNORE); + assert.deepEqual(parser.params, [0, 0]); parser.reset(); } }); - it('trans CSI_PARAM --> CSI_IGNORE', function (): void { + it('trans CSI_PARAM --> CSI_IGNORE', () => { parser.reset(); const chars = ['\x3c', '\x3d', '\x3e', '\x3f']; for (let i = 0; i < chars.length; ++i) { - chai.expect(parser.params).eql([0]); + assert.deepEqual(parser.params, [0]); parser.currentState = ParserState.CSI_PARAM; parse(parser, '\x3b' + chars[i]); - chai.expect(parser.currentState).equal(ParserState.CSI_IGNORE); - chai.expect(parser.params).eql([0, 0]); + assert.equal(parser.currentState, ParserState.CSI_IGNORE); + assert.deepEqual(parser.params, [0, 0]); parser.reset(); } }); - it('trans CSI_INTERMEDIATE --> CSI_IGNORE', function (): void { + it('trans CSI_INTERMEDIATE --> CSI_IGNORE', () => { parser.reset(); const chars = r(0x30, 0x40); for (let i = 0; i < chars.length; ++i) { parser.currentState = ParserState.CSI_INTERMEDIATE; parse(parser, chars[i]); - chai.expect(parser.currentState).equal(ParserState.CSI_IGNORE); - chai.expect(parser.params).eql([0]); + assert.equal(parser.currentState, ParserState.CSI_IGNORE); + assert.deepEqual(parser.params, [0]); parser.reset(); } }); - it('state CSI_IGNORE execute rules', function (): void { + it('state CSI_IGNORE execute rules', () => { parser.reset(); testTerminal.clear(); let exes = r(0x00, 0x18); @@ -655,13 +670,13 @@ describe('EscapeSequenceParser', function (): void { for (let i = 0; i < exes.length; ++i) { parser.currentState = ParserState.CSI_IGNORE; parse(parser, exes[i]); - chai.expect(parser.currentState).equal(ParserState.CSI_IGNORE); + assert.equal(parser.currentState, ParserState.CSI_IGNORE); testTerminal.compare([['exe', exes[i]]]); parser.reset(); testTerminal.clear(); } }); - it('state CSI_IGNORE ignore', function (): void { + it('state CSI_IGNORE ignore', () => { parser.reset(); testTerminal.clear(); let ignored = r(0x20, 0x40); @@ -669,32 +684,32 @@ describe('EscapeSequenceParser', function (): void { for (let i = 0; i < ignored.length; ++i) { parser.currentState = ParserState.CSI_IGNORE; parse(parser, ignored[i]); - chai.expect(parser.currentState).equal(ParserState.CSI_IGNORE); + assert.equal(parser.currentState, ParserState.CSI_IGNORE); testTerminal.compare([]); parser.reset(); testTerminal.clear(); } }); - it('trans CSI_IGNORE --> GROUND', function (): void { + it('trans CSI_IGNORE --> GROUND', () => { parser.reset(); const dispatches = r(0x40, 0x7f); for (let i = 0; i < dispatches.length; ++i) { parser.currentState = ParserState.CSI_IGNORE; parser.params = [0, 1]; parse(parser, dispatches[i]); - chai.expect(parser.currentState).equal(ParserState.GROUND); + assert.equal(parser.currentState, ParserState.GROUND); testTerminal.compare([]); parser.reset(); testTerminal.clear(); } }); - it('trans ANYWHERE/ESCAPE --> SOS_PM_APC_STRING', function (): void { + it('trans ANYWHERE/ESCAPE --> SOS_PM_APC_STRING', () => { parser.reset(); // C0 let initializers = ['\x58', '\x5e', '\x5f']; for (let i = 0; i < initializers.length; ++i) { parse(parser, '\x1b' + initializers[i]); - chai.expect(parser.currentState).equal(ParserState.SOS_PM_APC_STRING); + assert.equal(parser.currentState, ParserState.SOS_PM_APC_STRING); parser.reset(); } // C1 @@ -703,12 +718,12 @@ describe('EscapeSequenceParser', function (): void { initializers = ['\x98', '\x9e', '\x9f']; for (let i = 0; i < initializers.length; ++i) { parse(parser, initializers[i]); - chai.expect(parser.currentState).equal(ParserState.SOS_PM_APC_STRING); + assert.equal(parser.currentState, ParserState.SOS_PM_APC_STRING); parser.reset(); } } }); - it('state SOS_PM_APC_STRING ignore rules', function (): void { + it('state SOS_PM_APC_STRING ignore rules', () => { parser.reset(); let ignored = r(0x00, 0x18); ignored = ignored.concat(['\x19']); @@ -717,25 +732,25 @@ describe('EscapeSequenceParser', function (): void { for (let i = 0; i < ignored.length; ++i) { parser.currentState = ParserState.SOS_PM_APC_STRING; parse(parser, ignored[i]); - chai.expect(parser.currentState).equal(ParserState.SOS_PM_APC_STRING); + assert.equal(parser.currentState, ParserState.SOS_PM_APC_STRING); parser.reset(); } }); - it('trans ANYWHERE/ESCAPE --> OSC_STRING', function (): void { + it('trans ANYWHERE/ESCAPE --> OSC_STRING', () => { parser.reset(); // C0 parse(parser, '\x1b]'); - chai.expect(parser.currentState).equal(ParserState.OSC_STRING); + assert.equal(parser.currentState, ParserState.OSC_STRING); parser.reset(); // C1 for (state in states) { parser.currentState = state; parse(parser, '\x9d'); - chai.expect(parser.currentState).equal(ParserState.OSC_STRING); + assert.equal(parser.currentState, ParserState.OSC_STRING); parser.reset(); } }); - it('state OSC_STRING ignore rules', function (): void { + it('state OSC_STRING ignore rules', () => { parser.reset(); const ignored = [ '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', /* '\x07', */ '\x08', @@ -744,37 +759,37 @@ describe('EscapeSequenceParser', function (): void { for (let i = 0; i < ignored.length; ++i) { parser.currentState = ParserState.OSC_STRING; parse(parser, ignored[i]); - chai.expect(parser.currentState).equal(ParserState.OSC_STRING); - chai.expect(parser.osc).equal(''); + assert.equal(parser.currentState, ParserState.OSC_STRING); + assert.equal(parser.osc, ''); parser.reset(); } }); - it('state OSC_STRING put action', function (): void { + it('state OSC_STRING put action', () => { parser.reset(); const puts = r(0x20, 0x80); for (let i = 0; i < puts.length; ++i) { parser.currentState = ParserState.OSC_STRING; parse(parser, puts[i]); - chai.expect(parser.currentState).equal(ParserState.OSC_STRING); - chai.expect(parser.osc).equal(puts[i]); + assert.equal(parser.currentState, ParserState.OSC_STRING); + assert.equal(parser.osc, puts[i]); parser.reset(); } }); - it('state DCS_ENTRY', function (): void { + it('state DCS_ENTRY', () => { parser.reset(); // C0 parse(parser, '\x1bP'); - chai.expect(parser.currentState).equal(ParserState.DCS_ENTRY); + assert.equal(parser.currentState, ParserState.DCS_ENTRY); parser.reset(); // C1 for (state in states) { parser.currentState = state; parse(parser, '\x90'); - chai.expect(parser.currentState).equal(ParserState.DCS_ENTRY); + assert.equal(parser.currentState, ParserState.DCS_ENTRY); parser.reset(); } }); - it('state DCS_ENTRY ignore rules', function (): void { + it('state DCS_ENTRY ignore rules', () => { parser.reset(); const ignored = [ '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08', @@ -783,35 +798,35 @@ describe('EscapeSequenceParser', function (): void { for (let i = 0; i < ignored.length; ++i) { parser.currentState = ParserState.DCS_ENTRY; parse(parser, ignored[i]); - chai.expect(parser.currentState).equal(ParserState.DCS_ENTRY); + assert.equal(parser.currentState, ParserState.DCS_ENTRY); parser.reset(); } }); - it('state DCS_ENTRY --> DCS_PARAM with param/collect actions', function (): void { + it('state DCS_ENTRY --> DCS_PARAM with param/collect actions', () => { parser.reset(); const params = ['\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', '\x38', '\x39']; const collect = ['\x3c', '\x3d', '\x3e', '\x3f']; for (let i = 0; i < params.length; ++i) { parser.currentState = ParserState.DCS_ENTRY; parse(parser, params[i]); - chai.expect(parser.currentState).equal(ParserState.DCS_PARAM); - chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); + assert.equal(parser.currentState, ParserState.DCS_PARAM); + assert.deepEqual(parser.params, [params[i].charCodeAt(0) - 48]); parser.reset(); } parser.currentState = ParserState.DCS_ENTRY; parse(parser, '\x3b'); - chai.expect(parser.currentState).equal(ParserState.DCS_PARAM); - chai.expect(parser.params).eql([0, 0]); + assert.equal(parser.currentState, ParserState.DCS_PARAM); + assert.deepEqual(parser.params, [0, 0]); parser.reset(); for (let i = 0; i < collect.length; ++i) { parser.currentState = ParserState.DCS_ENTRY; parse(parser, collect[i]); - chai.expect(parser.currentState).equal(ParserState.DCS_PARAM); - chai.expect(parser.collect).equal(collect[i]); + assert.equal(parser.currentState, ParserState.DCS_PARAM); + assert.equal(parser.collect, collect[i]); parser.reset(); } }); - it('state DCS_PARAM ignore rules', function (): void { + it('state DCS_PARAM ignore rules', () => { parser.reset(); const ignored = [ '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08', @@ -820,55 +835,55 @@ describe('EscapeSequenceParser', function (): void { for (let i = 0; i < ignored.length; ++i) { parser.currentState = ParserState.DCS_PARAM; parse(parser, ignored[i]); - chai.expect(parser.currentState).equal(ParserState.DCS_PARAM); + assert.equal(parser.currentState, ParserState.DCS_PARAM); parser.reset(); } }); - it('state DCS_PARAM param action', function (): void { + it('state DCS_PARAM param action', () => { parser.reset(); const params = ['\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', '\x38', '\x39']; for (let i = 0; i < params.length; ++i) { parser.currentState = ParserState.DCS_PARAM; parse(parser, params[i]); - chai.expect(parser.currentState).equal(ParserState.DCS_PARAM); - chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); + assert.equal(parser.currentState, ParserState.DCS_PARAM); + assert.deepEqual(parser.params, [params[i].charCodeAt(0) - 48]); parser.reset(); } parser.currentState = ParserState.DCS_PARAM; parse(parser, '\x3b'); - chai.expect(parser.currentState).equal(ParserState.DCS_PARAM); - chai.expect(parser.params).eql([0, 0]); + assert.equal(parser.currentState, ParserState.DCS_PARAM); + assert.deepEqual(parser.params, [0, 0]); parser.reset(); }); - it('trans DCS_ENTRY --> DCS_PARAM for ":" (0x3a)', function (): void { + it('trans DCS_ENTRY --> DCS_PARAM for ":" (0x3a)', () => { parser.reset(); parser.currentState = ParserState.DCS_ENTRY; parse(parser, '\x3a'); - chai.expect(parser.currentState).equal(ParserState.DCS_PARAM); + assert.equal(parser.currentState, ParserState.DCS_PARAM); parser.reset(); }); - it('trans DCS_PARAM --> DCS_IGNORE', function (): void { + it('trans DCS_PARAM --> DCS_IGNORE', () => { parser.reset(); const chars = ['\x3c', '\x3d', '\x3e', '\x3f']; for (let i = 0; i < chars.length; ++i) { parser.currentState = ParserState.DCS_PARAM; parse(parser, '\x3b' + chars[i]); - chai.expect(parser.currentState).equal(ParserState.DCS_IGNORE); - chai.expect(parser.params).eql([0, 0]); + assert.equal(parser.currentState, ParserState.DCS_IGNORE); + assert.deepEqual(parser.params, [0, 0]); parser.reset(); } }); - it('trans DCS_INTERMEDIATE --> DCS_IGNORE', function (): void { + it('trans DCS_INTERMEDIATE --> DCS_IGNORE', () => { parser.reset(); const chars = r(0x30, 0x40); for (let i = 0; i < chars.length; ++i) { parser.currentState = ParserState.DCS_INTERMEDIATE; parse(parser, chars[i]); - chai.expect(parser.currentState).equal(ParserState.DCS_IGNORE); + assert.equal(parser.currentState, ParserState.DCS_IGNORE); parser.reset(); } }); - it('state DCS_IGNORE ignore rules', function (): void { + it('state DCS_IGNORE ignore rules', () => { parser.reset(); let ignored = [ '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08', @@ -878,33 +893,33 @@ describe('EscapeSequenceParser', function (): void { for (let i = 0; i < ignored.length; ++i) { parser.currentState = ParserState.DCS_IGNORE; parse(parser, ignored[i]); - chai.expect(parser.currentState).equal(ParserState.DCS_IGNORE); + assert.equal(parser.currentState, ParserState.DCS_IGNORE); parser.reset(); } }); - it('trans DCS_ENTRY --> DCS_INTERMEDIATE with collect action', function (): void { + it('trans DCS_ENTRY --> DCS_INTERMEDIATE with collect action', () => { parser.reset(); const collect = r(0x20, 0x30); for (let i = 0; i < collect.length; ++i) { parser.currentState = ParserState.DCS_ENTRY; parse(parser, collect[i]); - chai.expect(parser.currentState).equal(ParserState.DCS_INTERMEDIATE); - chai.expect(parser.collect).equal(collect[i]); + assert.equal(parser.currentState, ParserState.DCS_INTERMEDIATE); + assert.equal(parser.collect, collect[i]); parser.reset(); } }); - it('trans DCS_PARAM --> DCS_INTERMEDIATE with collect action', function (): void { + it('trans DCS_PARAM --> DCS_INTERMEDIATE with collect action', () => { parser.reset(); const collect = r(0x20, 0x30); for (let i = 0; i < collect.length; ++i) { parser.currentState = ParserState.DCS_PARAM; parse(parser, collect[i]); - chai.expect(parser.currentState).equal(ParserState.DCS_INTERMEDIATE); - chai.expect(parser.collect).equal(collect[i]); + assert.equal(parser.currentState, ParserState.DCS_INTERMEDIATE); + assert.equal(parser.collect, collect[i]); parser.reset(); } }); - it('state DCS_INTERMEDIATE ignore rules', function (): void { + it('state DCS_INTERMEDIATE ignore rules', () => { parser.reset(); const ignored = [ '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08', @@ -913,72 +928,72 @@ describe('EscapeSequenceParser', function (): void { for (let i = 0; i < ignored.length; ++i) { parser.currentState = ParserState.DCS_INTERMEDIATE; parse(parser, ignored[i]); - chai.expect(parser.currentState).equal(ParserState.DCS_INTERMEDIATE); + assert.equal(parser.currentState, ParserState.DCS_INTERMEDIATE); parser.reset(); } }); - it('state DCS_INTERMEDIATE collect action', function (): void { + it('state DCS_INTERMEDIATE collect action', () => { parser.reset(); const collect = r(0x20, 0x30); for (let i = 0; i < collect.length; ++i) { parser.currentState = ParserState.DCS_INTERMEDIATE; parse(parser, collect[i]); - chai.expect(parser.currentState).equal(ParserState.DCS_INTERMEDIATE); - chai.expect(parser.collect).equal(collect[i]); + assert.equal(parser.currentState, ParserState.DCS_INTERMEDIATE); + assert.equal(parser.collect, collect[i]); parser.reset(); } }); - it('trans DCS_INTERMEDIATE --> DCS_IGNORE', function (): void { + it('trans DCS_INTERMEDIATE --> DCS_IGNORE', () => { parser.reset(); const chars = r(0x30, 0x40); for (let i = 0; i < chars.length; ++i) { parser.currentState = ParserState.DCS_INTERMEDIATE; parse(parser, '\x20' + chars[i]); - chai.expect(parser.currentState).equal(ParserState.DCS_IGNORE); - chai.expect(parser.collect).equal('\x20'); + assert.equal(parser.currentState, ParserState.DCS_IGNORE); + assert.equal(parser.collect, '\x20'); parser.reset(); } }); - it('trans DCS_ENTRY --> DCS_PASSTHROUGH with hook', function (): void { + it('trans DCS_ENTRY --> DCS_PASSTHROUGH with hook', () => { parser.reset(); testTerminal.clear(); const collect = r(0x40, 0x7f); for (let i = 0; i < collect.length; ++i) { parser.currentState = ParserState.DCS_ENTRY; parse(parser, collect[i]); - chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH); + assert.equal(parser.currentState, ParserState.DCS_PASSTHROUGH); testTerminal.compare([['dcs hook', [0]]]); parser.reset(); testTerminal.clear(); } }); - it('trans DCS_PARAM --> DCS_PASSTHROUGH with hook', function (): void { + it('trans DCS_PARAM --> DCS_PASSTHROUGH with hook', () => { parser.reset(); testTerminal.clear(); const collect = r(0x40, 0x7f); for (let i = 0; i < collect.length; ++i) { parser.currentState = ParserState.DCS_PARAM; parse(parser, collect[i]); - chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH); + assert.equal(parser.currentState, ParserState.DCS_PASSTHROUGH); testTerminal.compare([['dcs hook', [0]]]); parser.reset(); testTerminal.clear(); } }); - it('trans DCS_INTERMEDIATE --> DCS_PASSTHROUGH with hook', function (): void { + it('trans DCS_INTERMEDIATE --> DCS_PASSTHROUGH with hook', () => { parser.reset(); testTerminal.clear(); const collect = r(0x40, 0x7f); for (let i = 0; i < collect.length; ++i) { parser.currentState = ParserState.DCS_INTERMEDIATE; parse(parser, collect[i]); - chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH); + assert.equal(parser.currentState, ParserState.DCS_PASSTHROUGH); testTerminal.compare([['dcs hook', [0]]]); parser.reset(); testTerminal.clear(); } }); - it('state DCS_PASSTHROUGH put action', function (): void { + it('state DCS_PASSTHROUGH put action', () => { parser.reset(); testTerminal.clear(); let puts = r(0x00, 0x18); @@ -988,18 +1003,18 @@ describe('EscapeSequenceParser', function (): void { for (let i = 0; i < puts.length; ++i) { parser.currentState = ParserState.DCS_PASSTHROUGH; parse(parser, puts[i]); - chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH); + assert.equal(parser.currentState, ParserState.DCS_PASSTHROUGH); testTerminal.compare([['dcs put', puts[i]]]); parser.reset(); testTerminal.clear(); } }); - it('state DCS_PASSTHROUGH ignore', function (): void { + it('state DCS_PASSTHROUGH ignore', () => { parser.reset(); testTerminal.clear(); parser.currentState = ParserState.DCS_PASSTHROUGH; parse(parser, '\x7f'); - chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH); + assert.equal(parser.currentState, ParserState.DCS_PASSTHROUGH); testTerminal.compare([]); parser.reset(); testTerminal.clear(); @@ -1015,8 +1030,8 @@ describe('EscapeSequenceParser', function (): void { testTerminal.compare(value); } - describe('escape sequence examples', function (): void { - it('CSI with print and execute', function (): void { + describe('escape sequence examples', () => { + it('CSI with print and execute', () => { test('\x1b[<31;5mHello World! öäü€\nabc', [ ['csi', '<', [31, 5], 'm'], @@ -1025,19 +1040,19 @@ describe('EscapeSequenceParser', function (): void { ['print', 'abc'] ], null); }); - it('OSC', function (): void { + it('OSC', () => { test('\x1b]0;abc123€öäü\x07', [ ['osc', '0;abc123€öäü, success: true'] ], null); }); - it('single DCS', function (): void { + it('single DCS', () => { test('\x1bP1;2;3+$aäbc;däe\x9c', [ ['dcs hook', [1, 2, 3]], ['dcs put', 'äbc;däe'], ['dcs unhook', true] ], null); }); - it('multi DCS', function (): void { + it('multi DCS', () => { test('\x1bP1;2;3+$abc;de', [ ['dcs hook', [1, 2, 3]], ['dcs put', 'bc;de'] @@ -1048,7 +1063,7 @@ describe('EscapeSequenceParser', function (): void { ['dcs unhook', true] ], true); }); - it('print + DCS(C1)', function (): void { + it('print + DCS(C1)', () => { test('abc\x901;2;3+$abc;de\x9c', [ ['print', 'abc'], ['dcs hook', [1, 2, 3]], @@ -1056,26 +1071,26 @@ describe('EscapeSequenceParser', function (): void { ['dcs unhook', true] ], null); }); - it('print + PM(C1) + print', function (): void { + it('print + PM(C1) + print', () => { test('abc\x98123tzf\x9cdefg', [ ['print', 'abc'], ['print', 'defg'] ], null); }); - it('print + OSC(C1) + print', function (): void { + it('print + OSC(C1) + print', () => { test('abc\x9d123;tzf\x9cdefg', [ ['print', 'abc'], ['osc', '123;tzf, success: true'], ['print', 'defg'] ], null); }); - it('error recovery', function (): void { + it('error recovery', () => { test('\x1b[1€abcdefg\x9b<;c', [ ['print', 'abcdefg'], ['csi', '<', [0, 0], 'c'] ], null); }); - it('7bit ST should be swallowed', function (): void { + it('7bit ST should be swallowed', () => { test('abc\x9d123;tzf\x1b\\defg', [ ['print', 'abc'], ['osc', '123;tzf, success: true'], @@ -1091,7 +1106,7 @@ describe('EscapeSequenceParser', function (): void { ['print', 'abc'] ], null); }); - it('colon notation in DCS params', function (): void { + it('colon notation in DCS params', () => { test('abc\x901;2::55;3+$abc;de\x9c', [ ['print', 'abc'], ['dcs hook', [1, 2, [-1, 55], 3]], @@ -1127,50 +1142,50 @@ describe('EscapeSequenceParser', function (): void { }); }); - describe('coverage tests', function (): void { - it('CSI_IGNORE error', function (): void { + describe('coverage tests', () => { + it('CSI_IGNORE error', () => { parser.reset(); testTerminal.clear(); parser.currentState = ParserState.CSI_IGNORE; parse(parser, '€öäü'); - chai.expect(parser.currentState).equal(ParserState.CSI_IGNORE); + assert.equal(parser.currentState, ParserState.CSI_IGNORE); testTerminal.compare([]); parser.reset(); testTerminal.clear(); }); - it('DCS_IGNORE error', function (): void { + it('DCS_IGNORE error', () => { parser.reset(); testTerminal.clear(); parser.currentState = ParserState.DCS_IGNORE; parse(parser, '€öäü'); - chai.expect(parser.currentState).equal(ParserState.DCS_IGNORE); + assert.equal(parser.currentState, ParserState.DCS_IGNORE); testTerminal.compare([]); parser.reset(); testTerminal.clear(); }); - it('DCS_PASSTHROUGH error', function (): void { + it('DCS_PASSTHROUGH error', () => { parser.reset(); testTerminal.clear(); parser.currentState = ParserState.DCS_PASSTHROUGH; parse(parser, '\x901;2;3+$a€öäü'); - chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH); + assert.equal(parser.currentState, ParserState.DCS_PASSTHROUGH); testTerminal.compare([['dcs hook', [1, 2, 3]], ['dcs put', '€öäü']]); parser.reset(); testTerminal.clear(); }); - it('error else of if (code > 159)', function (): void { + it('error else of if (code > 159)', () => { parser.reset(); testTerminal.clear(); parser.currentState = ParserState.GROUND; parse(parser, '\x9c'); - chai.expect(parser.currentState).equal(ParserState.GROUND); + assert.equal(parser.currentState, ParserState.GROUND); testTerminal.compare([]); parser.reset(); testTerminal.clear(); }); }); - describe('set/clear handler', function (): void { + describe('set/clear handler', () => { const INPUT = '\x1b[1;31mhello \x1b%Gwor\x1bEld!\x1b[0m\r\n$>\x1b]1;foo=bar\x1b\\'; let parser2: TestEscapeSequenceParser; let print = ''; @@ -1187,269 +1202,275 @@ describe('EscapeSequenceParser', function (): void { osc.length = 0; dcs.length = 0; } - beforeEach(function (): void { + beforeEach(() => { parser2 = new TestEscapeSequenceParser(); clearAccu(); }); - it('print handler', function (): void { + it('print handler', () => { parser2.setPrintHandler(function (data: Uint32Array, start: number, end: number): void { for (let i = start; i < end; ++i) { print += stringFromCodePoint(data[i]); } }); parse(parser2, INPUT); - chai.expect(print).equal('hello world!$>'); + assert.equal(print, 'hello world!$>'); parser2.clearPrintHandler(); parser2.clearPrintHandler(); // should not throw clearAccu(); parse(parser2, INPUT); - chai.expect(print).equal(''); + assert.equal(print, ''); }); - it('ESC handler', function (): void { - parser2.setEscHandler({intermediates: '%', final: 'G'}, function (): void { + it('ESC handler', () => { + parser2.registerEscHandler({ intermediates: '%', final: 'G' }, function (): boolean { esc.push('%G'); + return true; }); - parser2.setEscHandler({final: 'E'}, function (): void { + parser2.registerEscHandler({ final: 'E' }, function (): boolean { esc.push('E'); + return true; }); parse(parser2, INPUT); - chai.expect(esc).eql(['%G', 'E']); - parser2.clearEscHandler({intermediates: '%', final: 'G'}); - parser2.clearEscHandler({intermediates: '%', final: 'G'}); // should not throw + assert.deepEqual(esc, ['%G', 'E']); + parser2.clearEscHandler({ intermediates: '%', final: 'G' }); + parser2.clearEscHandler({ intermediates: '%', final: 'G' }); // should not throw clearAccu(); parse(parser2, INPUT); - chai.expect(esc).eql(['E']); - parser2.clearEscHandler({final: 'E'}); + assert.deepEqual(esc, ['E']); + parser2.clearEscHandler({ final: 'E' }); clearAccu(); parse(parser2, INPUT); - chai.expect(esc).eql([]); + assert.deepEqual(esc, []); }); describe('ESC custom handlers', () => { it('prevent fallback', () => { - parser2.setEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('default - %G'); }); - parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom - %G'); return true; }); + parser2.registerEscHandler({ intermediates: '%', final: 'G' }, () => { esc.push('default - %G'); return true; }); + parser2.registerEscHandler({ intermediates: '%', final: 'G' }, () => { esc.push('custom - %G'); return true; }); parse(parser2, INPUT); - chai.expect(esc).eql(['custom - %G']); + assert.deepEqual(esc, ['custom - %G']); }); it('allow fallback', () => { - parser2.setEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('default - %G'); }); - parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom - %G'); return false; }); + parser2.registerEscHandler({ intermediates: '%', final: 'G' }, () => { esc.push('default - %G'); return true; }); + parser2.registerEscHandler({ intermediates: '%', final: 'G' }, () => { esc.push('custom - %G'); return false; }); parse(parser2, INPUT); - chai.expect(esc).eql(['custom - %G', 'default - %G']); + assert.deepEqual(esc, ['custom - %G', 'default - %G']); }); it('Multiple custom handlers fallback once', () => { - parser2.setEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('default - %G'); }); - parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom - %G'); return true; }); - parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom2 - %G'); return false; }); + parser2.registerEscHandler({ intermediates: '%', final: 'G' }, () => { esc.push('default - %G'); return true; }); + parser2.registerEscHandler({ intermediates: '%', final: 'G' }, () => { esc.push('custom - %G'); return true; }); + parser2.registerEscHandler({ intermediates: '%', final: 'G' }, () => { esc.push('custom2 - %G'); return false; }); parse(parser2, INPUT); - chai.expect(esc).eql(['custom2 - %G', 'custom - %G']); + assert.deepEqual(esc, ['custom2 - %G', 'custom - %G']); }); it('Multiple custom handlers no fallback', () => { - parser2.setEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('default - %G'); }); - parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom - %G'); return true; }); - parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom2 - %G'); return true; }); + parser2.registerEscHandler({ intermediates: '%', final: 'G' }, () => { esc.push('default - %G'); return true; }); + parser2.registerEscHandler({ intermediates: '%', final: 'G' }, () => { esc.push('custom - %G'); return true; }); + parser2.registerEscHandler({ intermediates: '%', final: 'G' }, () => { esc.push('custom2 - %G'); return true; }); parse(parser2, INPUT); - chai.expect(esc).eql(['custom2 - %G']); + assert.deepEqual(esc, ['custom2 - %G']); }); it('Execution order should go from latest handler down to the original', () => { const order: number[] = []; - parser2.setEscHandler({intermediates: '%', final: 'G'}, () => { order.push(1); }); - parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { order.push(2); return false; }); - parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { order.push(3); return false; }); + parser2.registerEscHandler({ intermediates: '%', final: 'G' }, () => { order.push(1); return true; }); + parser2.registerEscHandler({ intermediates: '%', final: 'G' }, () => { order.push(2); return false; }); + parser2.registerEscHandler({ intermediates: '%', final: 'G' }, () => { order.push(3); return false; }); parse(parser2, '\x1b%G'); - chai.expect(order).eql([3, 2, 1]); + assert.deepEqual(order, [3, 2, 1]); }); it('Dispose should work', () => { - parser2.setEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('default - %G'); }); - const dispo = parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom - %G'); return true; }); + parser2.registerEscHandler({ intermediates: '%', final: 'G' }, () => { esc.push('default - %G'); return true; }); + const dispo = parser2.registerEscHandler({ intermediates: '%', final: 'G' }, () => { esc.push('custom - %G'); return true; }); dispo.dispose(); parse(parser2, INPUT); - chai.expect(esc).eql(['default - %G']); + assert.deepEqual(esc, ['default - %G']); }); it('Should not corrupt the parser when dispose is called twice', () => { - parser2.setEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('default - %G'); }); - const dispo = parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom - %G'); return true; }); + parser2.registerEscHandler({ intermediates: '%', final: 'G' }, () => { esc.push('default - %G'); return true; }); + const dispo = parser2.registerEscHandler({ intermediates: '%', final: 'G' }, () => { esc.push('custom - %G'); return true; }); dispo.dispose(); dispo.dispose(); parse(parser2, INPUT); - chai.expect(esc).eql(['default - %G']); + assert.deepEqual(esc, ['default - %G']); }); }); - it('CSI handler', function (): void { - parser2.setCsiHandler({final: 'm'}, function (params: IParams): void { + it('CSI handler', () => { + parser2.registerCsiHandler({ final: 'm' }, function (params: IParams): boolean { csi.push(['m', params.toArray(), '']); + return true; }); parse(parser2, INPUT); - chai.expect(csi).eql([['m', [1, 31], ''], ['m', [0], '']]); - parser2.clearCsiHandler({final: 'm'}); - parser2.clearCsiHandler({final: 'm'}); // should not throw + assert.deepEqual(csi, [['m', [1, 31], ''], ['m', [0], '']]); + parser2.clearCsiHandler({ final: 'm' }); + parser2.clearCsiHandler({ final: 'm' }); // should not throw clearAccu(); parse(parser2, INPUT); - chai.expect(csi).eql([]); + assert.deepEqual(csi, []); }); describe('CSI custom handlers', () => { it('Prevent fallback', () => { const csiCustom: [string, ParamsArray, string][] = []; - parser2.setCsiHandler({final: 'm'}, params => { csi.push(['m', params.toArray(), '']); }); - parser2.addCsiHandler({final: 'm'}, params => { csiCustom.push(['m', params.toArray(), '']); return true; }); + parser2.registerCsiHandler({ final: 'm' }, params => { csi.push(['m', params.toArray(), '']); return true; }); + parser2.registerCsiHandler({ final: 'm' }, params => { csiCustom.push(['m', params.toArray(), '']); return true; }); parse(parser2, INPUT); - chai.expect(csi).eql([], 'Should not fallback to original handler'); - chai.expect(csiCustom).eql([['m', [1, 31], ''], ['m', [0], '']]); + assert.deepEqual(csi, [], 'Should not fallback to original handler'); + assert.deepEqual(csiCustom, [['m', [1, 31], ''], ['m', [0], '']]); }); it('Allow fallback', () => { const csiCustom: [string, ParamsArray, string][] = []; - parser2.setCsiHandler({final: 'm'}, params => { csi.push(['m', params.toArray(), '']); }); - parser2.addCsiHandler({final: 'm'}, params => { csiCustom.push(['m', params.toArray(), '']); return false; }); + parser2.registerCsiHandler({ final: 'm' }, params => { csi.push(['m', params.toArray(), '']); return true; }); + parser2.registerCsiHandler({ final: 'm' }, params => { csiCustom.push(['m', params.toArray(), '']); return false; }); parse(parser2, INPUT); - chai.expect(csi).eql([['m', [1, 31], ''], ['m', [0], '']], 'Should fallback to original handler'); - chai.expect(csiCustom).eql([['m', [1, 31], ''], ['m', [0], '']]); + assert.deepEqual(csi, [['m', [1, 31], ''], ['m', [0], '']], 'Should fallback to original handler'); + assert.deepEqual(csiCustom, [['m', [1, 31], ''], ['m', [0], '']]); }); it('Multiple custom handlers fallback once', () => { const csiCustom: [string, ParamsArray, string][] = []; const csiCustom2: [string, ParamsArray, string][] = []; - parser2.setCsiHandler({final: 'm'}, params => { csi.push(['m', params.toArray(), '']); }); - parser2.addCsiHandler({final: 'm'}, params => { csiCustom.push(['m', params.toArray(), '']); return true; }); - parser2.addCsiHandler({final: 'm'}, params => { csiCustom2.push(['m', params.toArray(), '']); return false; }); + parser2.registerCsiHandler({ final: 'm' }, params => { csi.push(['m', params.toArray(), '']); return true; }); + parser2.registerCsiHandler({ final: 'm' }, params => { csiCustom.push(['m', params.toArray(), '']); return true; }); + parser2.registerCsiHandler({ final: 'm' }, params => { csiCustom2.push(['m', params.toArray(), '']); return false; }); parse(parser2, INPUT); - chai.expect(csi).eql([], 'Should not fallback to original handler'); - chai.expect(csiCustom).eql([['m', [1, 31], ''], ['m', [0], '']]); - chai.expect(csiCustom2).eql([['m', [1, 31], ''], ['m', [0], '']]); + assert.deepEqual(csi, [], 'Should not fallback to original handler'); + assert.deepEqual(csiCustom, [['m', [1, 31], ''], ['m', [0], '']]); + assert.deepEqual(csiCustom2, [['m', [1, 31], ''], ['m', [0], '']]); }); it('Multiple custom handlers no fallback', () => { const csiCustom: [string, ParamsArray, string][] = []; const csiCustom2: [string, ParamsArray, string][] = []; - parser2.setCsiHandler({final: 'm'}, params => { csi.push(['m', params.toArray(), '']); }); - parser2.addCsiHandler({final: 'm'}, params => { csiCustom.push(['m', params.toArray(), '']); return true; }); - parser2.addCsiHandler({final: 'm'}, params => { csiCustom2.push(['m', params.toArray(), '']); return true; }); + parser2.registerCsiHandler({ final: 'm' }, params => { csi.push(['m', params.toArray(), '']); return true; }); + parser2.registerCsiHandler({ final: 'm' }, params => { csiCustom.push(['m', params.toArray(), '']); return true; }); + parser2.registerCsiHandler({ final: 'm' }, params => { csiCustom2.push(['m', params.toArray(), '']); return true; }); parse(parser2, INPUT); - chai.expect(csi).eql([], 'Should not fallback to original handler'); - chai.expect(csiCustom).eql([], 'Should not fallback once'); - chai.expect(csiCustom2).eql([['m', [1, 31], ''], ['m', [0], '']]); + assert.deepEqual(csi, [], 'Should not fallback to original handler'); + assert.deepEqual(csiCustom, [], 'Should not fallback once'); + assert.deepEqual(csiCustom2, [['m', [1, 31], ''], ['m', [0], '']]); }); it('Execution order should go from latest handler down to the original', () => { const order: number[] = []; - parser2.setCsiHandler({final: 'm'}, () => { order.push(1); }); - parser2.addCsiHandler({final: 'm'}, () => { order.push(2); return false; }); - parser2.addCsiHandler({final: 'm'}, () => { order.push(3); return false; }); + parser2.registerCsiHandler({ final: 'm' }, () => { order.push(1); return true; }); + parser2.registerCsiHandler({ final: 'm' }, () => { order.push(2); return false; }); + parser2.registerCsiHandler({ final: 'm' }, () => { order.push(3); return false; }); parse(parser2, '\x1b[0m'); - chai.expect(order).eql([3, 2, 1]); + assert.deepEqual(order, [3, 2, 1]); }); it('Dispose should work', () => { const csiCustom: [string, ParamsArray, string][] = []; - parser2.setCsiHandler({final: 'm'}, params => { csi.push(['m', params.toArray(), '']); }); - const customHandler = parser2.addCsiHandler({final: 'm'}, params => { csiCustom.push(['m', params.toArray(), '']); return true; }); + parser2.registerCsiHandler({ final: 'm' }, params => { csi.push(['m', params.toArray(), '']); return true; }); + const customHandler = parser2.registerCsiHandler({ final: 'm' }, params => { csiCustom.push(['m', params.toArray(), '']); return true; }); customHandler.dispose(); parse(parser2, INPUT); - chai.expect(csi).eql([['m', [1, 31], ''], ['m', [0], '']]); - chai.expect(csiCustom).eql([], 'Should not use custom handler as it was disposed'); + assert.deepEqual(csi, [['m', [1, 31], ''], ['m', [0], '']]); + assert.deepEqual(csiCustom, [], 'Should not use custom handler as it was disposed'); }); it('Should not corrupt the parser when dispose is called twice', () => { const csiCustom: [string, ParamsArray, string][] = []; - parser2.setCsiHandler({final: 'm'}, params => { csi.push(['m', params.toArray(), '']); }); - const customHandler = parser2.addCsiHandler({final: 'm'}, params => { csiCustom.push(['m', params.toArray(), '']); return true; }); + parser2.registerCsiHandler({ final: 'm' }, params => { csi.push(['m', params.toArray(), '']); return true; }); + const customHandler = parser2.registerCsiHandler({ final: 'm' }, params => { csiCustom.push(['m', params.toArray(), '']); return true; }); customHandler.dispose(); customHandler.dispose(); parse(parser2, INPUT); - chai.expect(csi).eql([['m', [1, 31], ''], ['m', [0], '']]); - chai.expect(csiCustom).eql([], 'Should not use custom handler as it was disposed'); + assert.deepEqual(csi, [['m', [1, 31], ''], ['m', [0], '']]); + assert.deepEqual(csiCustom, [], 'Should not use custom handler as it was disposed'); }); }); - it('EXECUTE handler', function (): void { - parser2.setExecuteHandler('\n', function (): void { + it('EXECUTE handler', () => { + parser2.setExecuteHandler('\n', function (): boolean { exe.push('\n'); + return true; }); - parser2.setExecuteHandler('\r', function (): void { + parser2.setExecuteHandler('\r', function (): boolean { exe.push('\r'); + return true; }); parse(parser2, INPUT); - chai.expect(exe).eql(['\r', '\n']); + assert.deepEqual(exe, ['\r', '\n']); parser2.clearExecuteHandler('\r'); parser2.clearExecuteHandler('\r'); // should not throw clearAccu(); parse(parser2, INPUT); - chai.expect(exe).eql(['\n']); + assert.deepEqual(exe, ['\n']); }); - it('OSC handler', function (): void { - parser2.setOscHandler(1, new OscHandler(function (data: string): void { + it('OSC handler', () => { + parser2.registerOscHandler(1, new OscHandler(function (data: string): boolean { osc.push([1, data]); + return true; })); parse(parser2, INPUT); - chai.expect(osc).eql([[1, 'foo=bar']]); + assert.deepEqual(osc, [[1, 'foo=bar']]); parser2.clearOscHandler(1); parser2.clearOscHandler(1); // should not throw clearAccu(); parse(parser2, INPUT); - chai.expect(osc).eql([]); + assert.deepEqual(osc, []); }); describe('OSC custom handlers', () => { it('Prevent fallback', () => { const oscCustom: [number, string][] = []; - parser2.setOscHandler(1, new OscHandler(data => osc.push([1, data]))); - parser2.addOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return true; })); + parser2.registerOscHandler(1, new OscHandler(data => { osc.push([1, data]); return true; })); + parser2.registerOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return true; })); parse(parser2, INPUT); - chai.expect(osc).eql([], 'Should not fallback to original handler'); - chai.expect(oscCustom).eql([[1, 'foo=bar']]); + assert.deepEqual(osc, [], 'Should not fallback to original handler'); + assert.deepEqual(oscCustom, [[1, 'foo=bar']]); }); it('Allow fallback', () => { const oscCustom: [number, string][] = []; - parser2.setOscHandler(1, new OscHandler(data => osc.push([1, data]))); - parser2.addOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return false; })); + parser2.registerOscHandler(1, new OscHandler(data => { osc.push([1, data]); return true; })); + parser2.registerOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return false; })); parse(parser2, INPUT); - chai.expect(osc).eql([[1, 'foo=bar']], 'Should fallback to original handler'); - chai.expect(oscCustom).eql([[1, 'foo=bar']]); + assert.deepEqual(osc, [[1, 'foo=bar']], 'Should fallback to original handler'); + assert.deepEqual(oscCustom, [[1, 'foo=bar']]); }); it('Multiple custom handlers fallback once', () => { const oscCustom: [number, string][] = []; const oscCustom2: [number, string][] = []; - parser2.setOscHandler(1, new OscHandler(data => osc.push([1, data]))); - parser2.addOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return true; })); - parser2.addOscHandler(1, new OscHandler(data => { oscCustom2.push([1, data]); return false; })); + parser2.registerOscHandler(1, new OscHandler(data => { osc.push([1, data]); return true; })); + parser2.registerOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return true; })); + parser2.registerOscHandler(1, new OscHandler(data => { oscCustom2.push([1, data]); return false; })); parse(parser2, INPUT); - chai.expect(osc).eql([], 'Should not fallback to original handler'); - chai.expect(oscCustom).eql([[1, 'foo=bar']]); - chai.expect(oscCustom2).eql([[1, 'foo=bar']]); + assert.deepEqual(osc, [], 'Should not fallback to original handler'); + assert.deepEqual(oscCustom, [[1, 'foo=bar']]); + assert.deepEqual(oscCustom2, [[1, 'foo=bar']]); }); it('Multiple custom handlers no fallback', () => { const oscCustom: [number, string][] = []; const oscCustom2: [number, string][] = []; - parser2.setOscHandler(1, new OscHandler(data => osc.push([1, data]))); - parser2.addOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return true; })); - parser2.addOscHandler(1, new OscHandler(data => { oscCustom2.push([1, data]); return true; })); + parser2.registerOscHandler(1, new OscHandler(data => { osc.push([1, data]); return true; })); + parser2.registerOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return true; })); + parser2.registerOscHandler(1, new OscHandler(data => { oscCustom2.push([1, data]); return true; })); parse(parser2, INPUT); - chai.expect(osc).eql([], 'Should not fallback to original handler'); - chai.expect(oscCustom).eql([], 'Should not fallback once'); - chai.expect(oscCustom2).eql([[1, 'foo=bar']]); + assert.deepEqual(osc, [], 'Should not fallback to original handler'); + assert.deepEqual(oscCustom, [], 'Should not fallback once'); + assert.deepEqual(oscCustom2, [[1, 'foo=bar']]); }); it('Execution order should go from latest handler down to the original', () => { const order: number[] = []; - parser2.setOscHandler(1, new OscHandler(() => order.push(1))); - parser2.addOscHandler(1, new OscHandler(() => { order.push(2); return false; })); - parser2.addOscHandler(1, new OscHandler(() => { order.push(3); return false; })); + parser2.registerOscHandler(1, new OscHandler(() => { order.push(1); return true; })); + parser2.registerOscHandler(1, new OscHandler(() => { order.push(2); return false; })); + parser2.registerOscHandler(1, new OscHandler(() => { order.push(3); return false; })); parse(parser2, '\x1b]1;foo=bar\x1b\\'); - chai.expect(order).eql([3, 2, 1]); + assert.deepEqual(order, [3, 2, 1]); }); it('Dispose should work', () => { const oscCustom: [number, string][] = []; - parser2.setOscHandler(1, new OscHandler(data => osc.push([1, data]))); - const customHandler = parser2.addOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return true; })); + parser2.registerOscHandler(1, new OscHandler(data => { osc.push([1, data]); return true; })); + const customHandler = parser2.registerOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return true; })); customHandler.dispose(); parse(parser2, INPUT); - chai.expect(osc).eql([[1, 'foo=bar']]); - chai.expect(oscCustom).eql([], 'Should not use custom handler as it was disposed'); + assert.deepEqual(osc, [[1, 'foo=bar']]); + assert.deepEqual(oscCustom, [], 'Should not use custom handler as it was disposed'); }); it('Should not corrupt the parser when dispose is called twice', () => { const oscCustom: [number, string][] = []; - parser2.setOscHandler(1, new OscHandler(data => osc.push([1, data]))); - const customHandler = parser2.addOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return true; })); + parser2.registerOscHandler(1, new OscHandler(data => { osc.push([1, data]); return true; })); + const customHandler = parser2.registerOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return true; })); customHandler.dispose(); customHandler.dispose(); parse(parser2, INPUT); - chai.expect(osc).eql([[1, 'foo=bar']]); - chai.expect(oscCustom).eql([], 'Should not use custom handler as it was disposed'); + assert.deepEqual(osc, [[1, 'foo=bar']]); + assert.deepEqual(oscCustom, [], 'Should not use custom handler as it was disposed'); }); }); - it('DCS handler', function (): void { - parser2.setDcsHandler({intermediates: '+', final: 'p'}, { + it('DCS handler', () => { + parser2.registerDcsHandler({ intermediates: '+', final: 'p' }, { hook: function (params: IParams): void { dcs.push(['hook', '', params.toArray(), 0]); }, @@ -1460,90 +1481,91 @@ describe('EscapeSequenceParser', function (): void { } dcs.push(['put', s]); }, - unhook: function (): void { + unhook: function (): boolean { dcs.push(['unhook']); + return true; } }); parse(parser2, '\x1bP1;2;3+pabc'); parse(parser2, ';de\x9c'); - chai.expect(dcs).eql([ + assert.deepEqual(dcs, [ ['hook', '', [1, 2, 3], 0], ['put', 'abc'], ['put', ';de'], ['unhook'] ]); - parser2.clearDcsHandler({intermediates: '+', final: 'p'}); - parser2.clearDcsHandler({intermediates: '+', final: 'p'}); // should not throw + parser2.clearDcsHandler({ intermediates: '+', final: 'p' }); + parser2.clearDcsHandler({ intermediates: '+', final: 'p' }); // should not throw clearAccu(); parse(parser2, '\x1bP1;2;3+pabc'); parse(parser2, ';de\x9c'); - chai.expect(dcs).eql([]); + assert.deepEqual(dcs, []); }); describe('DCS custom handlers', () => { const DCS_INPUT = '\x1bP1;2;3+pabc\x1b\\'; it('Prevent fallback', () => { const dcsCustom: [string, (number | number[])[], string][] = []; - parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => dcsCustom.push(['A', params.toArray(), data]))); - parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; })); + parser2.registerDcsHandler({ intermediates: '+', final: 'p' }, new DcsHandler((data, params) => { dcsCustom.push(['A', params.toArray(), data]); return true; })); + parser2.registerDcsHandler({ intermediates: '+', final: 'p' }, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; })); parse(parser2, DCS_INPUT); - chai.expect(dcsCustom).eql([['B', [1, 2, 3], 'abc']]); + assert.deepEqual(dcsCustom, [['B', [1, 2, 3], 'abc']]); }); it('Allow fallback', () => { const dcsCustom: [string, (number | number[])[], string][] = []; - parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => dcsCustom.push(['A', params.toArray(), data]))); - parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return false; })); + parser2.registerDcsHandler({ intermediates: '+', final: 'p' }, new DcsHandler((data, params) => { dcsCustom.push(['A', params.toArray(), data]); return true; })); + parser2.registerDcsHandler({ intermediates: '+', final: 'p' }, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return false; })); parse(parser2, DCS_INPUT); - chai.expect(dcsCustom).eql([['B', [1, 2, 3], 'abc'], ['A', [1, 2, 3], 'abc']]); + assert.deepEqual(dcsCustom, [['B', [1, 2, 3], 'abc'], ['A', [1, 2, 3], 'abc']]); }); it('Multiple custom handlers fallback once', () => { const dcsCustom: [string, (number | number[])[], string][] = []; - parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => dcsCustom.push(['A', params.toArray(), data]))); - parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; })); - parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['C', params.toArray(), data]); return false; })); + parser2.registerDcsHandler({ intermediates: '+', final: 'p' }, new DcsHandler((data, params) => { dcsCustom.push(['A', params.toArray(), data]); return true; })); + parser2.registerDcsHandler({ intermediates: '+', final: 'p' }, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; })); + parser2.registerDcsHandler({ intermediates: '+', final: 'p' }, new DcsHandler((data, params) => { dcsCustom.push(['C', params.toArray(), data]); return false; })); parse(parser2, DCS_INPUT); - chai.expect(dcsCustom).eql([['C', [1, 2, 3], 'abc'], ['B', [1, 2, 3], 'abc']]); + assert.deepEqual(dcsCustom, [['C', [1, 2, 3], 'abc'], ['B', [1, 2, 3], 'abc']]); }); it('Multiple custom handlers no fallback', () => { const dcsCustom: [string, (number | number[])[], string][] = []; - parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => dcsCustom.push(['A', params.toArray(), data]))); - parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; })); - parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['C', params.toArray(), data]); return true; })); + parser2.registerDcsHandler({ intermediates: '+', final: 'p' }, new DcsHandler((data, params) => { dcsCustom.push(['A', params.toArray(), data]); return true; })); + parser2.registerDcsHandler({ intermediates: '+', final: 'p' }, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; })); + parser2.registerDcsHandler({ intermediates: '+', final: 'p' }, new DcsHandler((data, params) => { dcsCustom.push(['C', params.toArray(), data]); return true; })); parse(parser2, DCS_INPUT); - chai.expect(dcsCustom).eql([['C', [1, 2, 3], 'abc']]); + assert.deepEqual(dcsCustom, [['C', [1, 2, 3], 'abc']]); }); it('Execution order should go from latest handler down to the original', () => { const order: number[] = []; - parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler(() => order.push(1))); - parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler(() => { order.push(2); return false; })); - parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler(() => { order.push(3); return false; })); + parser2.registerDcsHandler({ intermediates: '+', final: 'p' }, new DcsHandler(() => { order.push(1); return true; })); + parser2.registerDcsHandler({ intermediates: '+', final: 'p' }, new DcsHandler(() => { order.push(2); return false; })); + parser2.registerDcsHandler({ intermediates: '+', final: 'p' }, new DcsHandler(() => { order.push(3); return false; })); parse(parser2, DCS_INPUT); - chai.expect(order).eql([3, 2, 1]); + assert.deepEqual(order, [3, 2, 1]); }); it('Dispose should work', () => { const dcsCustom: [string, (number | number[])[], string][] = []; - parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => dcsCustom.push(['A', params.toArray(), data]))); - const dispo = parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; })); + parser2.registerDcsHandler({ intermediates: '+', final: 'p' }, new DcsHandler((data, params) => { dcsCustom.push(['A', params.toArray(), data]); return true; })); + const dispo = parser2.registerDcsHandler({ intermediates: '+', final: 'p' }, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; })); dispo.dispose(); parse(parser2, DCS_INPUT); - chai.expect(dcsCustom).eql([['A', [1, 2, 3], 'abc']]); + assert.deepEqual(dcsCustom, [['A', [1, 2, 3], 'abc']]); }); it('Should not corrupt the parser when dispose is called twice', () => { const dcsCustom: [string, (number | number[])[], string][] = []; - parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => dcsCustom.push(['A', params.toArray(), data]))); - const dispo = parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; })); + parser2.registerDcsHandler({ intermediates: '+', final: 'p' }, new DcsHandler((data, params) => { dcsCustom.push(['A', params.toArray(), data]); return true; })); + const dispo = parser2.registerDcsHandler({ intermediates: '+', final: 'p' }, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; })); dispo.dispose(); dispo.dispose(); parse(parser2, DCS_INPUT); - chai.expect(dcsCustom).eql([['A', [1, 2, 3], 'abc']]); + assert.deepEqual(dcsCustom, [['A', [1, 2, 3], 'abc']]); }); }); - it('ERROR handler', function (): void { + it('ERROR handler', () => { let errorState: IParsingState | null = null; parser2.setErrorHandler(function (state: IParsingState): IParsingState { errorState = state; return state; }); parse(parser2, '\x1b[1;2;€;3m'); // faulty escape sequence - chai.expect(errorState).eql({ + assert.deepEqual(errorState, { position: 6, code: '€'.charCodeAt(0), currentState: ParserState.CSI_PARAM, @@ -1555,7 +1577,7 @@ describe('EscapeSequenceParser', function (): void { parser2.clearErrorHandler(); // should not throw errorState = null; parse(parser2, '\x1b[1;2;a;3m'); - chai.expect(errorState).eql(null); + assert.equal(errorState, null); }); }); describe('function identifiers', () => { @@ -1563,69 +1585,69 @@ describe('EscapeSequenceParser', function (): void { it('prefix range 0x3c .. 0x3f, one byte', () => { for (let i = 0x3c; i <= 0x3f; ++i) { const c = String.fromCharCode(i); - chai.expect(parser.identToString(parser.identifier({prefix: c, final: 'z'}))).eql(c + 'z'); + assert.equal(parser.identToString(parser.identifier({ prefix: c, final: 'z' })), c + 'z'); } - chai.assert.throws(() => { parser.identifier({prefix: '\x3b', final: 'z'}); }, 'prefix must be in range 0x3c .. 0x3f'); - chai.assert.throws(() => { parser.identifier({prefix: '\x40', final: 'z'}); }, 'prefix must be in range 0x3c .. 0x3f'); - chai.assert.throws(() => { parser.identifier({prefix: '??', final: 'z'}); }, 'only one byte as prefix supported'); + assert.throws(() => { parser.identifier({ prefix: '\x3b', final: 'z' }); }, 'prefix must be in range 0x3c .. 0x3f'); + assert.throws(() => { parser.identifier({ prefix: '\x40', final: 'z' }); }, 'prefix must be in range 0x3c .. 0x3f'); + assert.throws(() => { parser.identifier({ prefix: '??', final: 'z' }); }, 'only one byte as prefix supported'); }); it('intermediates range 0x20 .. 0x2f, up to two bytes', () => { for (let i = 0x20; i <= 0x2f; ++i) { const c = String.fromCharCode(i); - chai.expect(parser.identToString(parser.identifier({intermediates: c + c, final: 'z'}))).eql(c + c + 'z'); + assert.equal(parser.identToString(parser.identifier({ intermediates: c + c, final: 'z' })), c + c + 'z'); } - chai.assert.throws(() => { parser.identifier({intermediates: '\x1f', final: 'z'}); }, 'intermediate must be in range 0x20 .. 0x2f'); - chai.assert.throws(() => { parser.identifier({intermediates: '\x30', final: 'z'}); }, 'intermediate must be in range 0x20 .. 0x2f'); - chai.assert.throws(() => { parser.identifier({intermediates: '!!!', final: 'z'}); }, 'only two bytes as intermediates are supported'); + assert.throws(() => { parser.identifier({ intermediates: '\x1f', final: 'z' }); }, 'intermediate must be in range 0x20 .. 0x2f'); + assert.throws(() => { parser.identifier({ intermediates: '\x30', final: 'z' }); }, 'intermediate must be in range 0x20 .. 0x2f'); + assert.throws(() => { parser.identifier({ intermediates: '!!!', final: 'z' }); }, 'only two bytes as intermediates are supported'); }); it('final CSI/DCS range 0x40 .. 0x7e (default), one byte', () => { for (let i = 0x40; i <= 0x7e; ++i) { const c = String.fromCharCode(i); - chai.expect(parser.identToString(parser.identifier({final: c}))).eql(c); + assert.equal(parser.identToString(parser.identifier({ final: c })), c); } - chai.assert.throws(() => { parser.identifier({final: '\x3f'}); }, 'final must be in range 64 .. 126'); - chai.assert.throws(() => { parser.identifier({final: '\x7f'}); }, 'final must be in range 64 .. 126'); - chai.assert.throws(() => { parser.identifier({final: 'zz'}); }, 'final must be a single byte'); + assert.throws(() => { parser.identifier({ final: '\x3f' }); }, 'final must be in range 64 .. 126'); + assert.throws(() => { parser.identifier({ final: '\x7f' }); }, 'final must be in range 64 .. 126'); + assert.throws(() => { parser.identifier({ final: 'zz' }); }, 'final must be a single byte'); }); it('final ESC range 0x30 .. 0x7e, one byte', () => { for (let i = 0x30; i <= 0x7e; ++i) { const final = String.fromCharCode(i); let handler: IDisposable | undefined; - chai.assert.doesNotThrow(() => { handler = parser.addEscHandler({final}, () => {}); }, 'final must be in range 48 .. 126'); + assert.doesNotThrow(() => { handler = parser.registerEscHandler({ final }, () => true); }, 'final must be in range 48 .. 126'); if (handler) handler.dispose(); } - chai.assert.throws(() => { parser.addEscHandler({final: '\x2f'}, () => {}); }, 'final must be in range 48 .. 126'); - chai.assert.throws(() => { parser.addEscHandler({final: '\x7f'}, () => {}); }, 'final must be in range 48 .. 126'); + assert.throws(() => { parser.registerEscHandler({ final: '\x2f' }, () => true); }, 'final must be in range 48 .. 126'); + assert.throws(() => { parser.registerEscHandler({ final: '\x7f' }, () => true); }, 'final must be in range 48 .. 126'); }); it('id calculation - should stacking prefix -> intermediate -> final', () => { - chai.expect(parser.identToString(parser.identifier({final: 'z'}))).eql('z'); - chai.expect(parser.identToString(parser.identifier({prefix: '?', final: 'z'}))).eql('?z'); - chai.expect(parser.identToString(parser.identifier({intermediates: '!', final: 'z'}))).eql('!z'); - chai.expect(parser.identToString(parser.identifier({prefix: '?', intermediates: '!', final: 'z'}))).eql('?!z'); - chai.expect(parser.identToString(parser.identifier({prefix: '?', intermediates: '!!', final: 'z'}))).eql('?!!z'); + assert.equal(parser.identToString(parser.identifier({ final: 'z' })), 'z'); + assert.equal(parser.identToString(parser.identifier({ prefix: '?', final: 'z' })), '?z'); + assert.equal(parser.identToString(parser.identifier({ intermediates: '!', final: 'z' })), '!z'); + assert.equal(parser.identToString(parser.identifier({ prefix: '?', intermediates: '!', final: 'z' })), '?!z'); + assert.equal(parser.identToString(parser.identifier({ prefix: '?', intermediates: '!!', final: 'z' })), '?!!z'); }); }); describe('identifier invocation', () => { it('ESC', () => { const callstack: string[] = []; - const h1 = parser.addEscHandler({final: 'z'}, () => { callstack.push('z'); }); - const h2 = parser.addEscHandler({intermediates: '!', final: 'z'}, () => { callstack.push('!z'); }); - const h3 = parser.addEscHandler({intermediates: '!!', final: 'z'}, () => { callstack.push('!!z'); }); + const h1 = parser.registerEscHandler({ final: 'z' }, () => { callstack.push('z'); return true; }); + const h2 = parser.registerEscHandler({ intermediates: '!', final: 'z' }, () => { callstack.push('!z'); return true; }); + const h3 = parser.registerEscHandler({ intermediates: '!!', final: 'z' }, () => { callstack.push('!!z'); return true; }); parse(parser, '\x1bz\x1b!z\x1b!!z'); h1.dispose(); h2.dispose(); h3.dispose(); parse(parser, '\x1bz\x1b!z\x1b!!z'); - chai.expect(callstack).eql(['z', '!z', '!!z']); + assert.deepEqual(callstack, ['z', '!z', '!!z']); }); it('CSI', () => { const callstack: any[] = []; - const h1 = parser.addCsiHandler({final: 'z'}, params => { callstack.push(['z', params.toArray()]); }); - const h2 = parser.addCsiHandler({intermediates: '!', final: 'z'}, params => { callstack.push(['!z', params.toArray()]); }); - const h3 = parser.addCsiHandler({intermediates: '!!', final: 'z'}, params => { callstack.push(['!!z', params.toArray()]); }); - const h4 = parser.addCsiHandler({prefix: '?', final: 'z'}, params => { callstack.push(['?z', params.toArray()]); }); - const h5 = parser.addCsiHandler({prefix: '?', intermediates: '!', final: 'z'}, params => { callstack.push(['?!z', params.toArray()]); }); - const h6 = parser.addCsiHandler({prefix: '?', intermediates: '!!', final: 'z'}, params => { callstack.push(['?!!z', params.toArray()]); }); + const h1 = parser.registerCsiHandler({ final: 'z' }, params => { callstack.push(['z', params.toArray()]); return true; }); + const h2 = parser.registerCsiHandler({ intermediates: '!', final: 'z' }, params => { callstack.push(['!z', params.toArray()]); return true; }); + const h3 = parser.registerCsiHandler({ intermediates: '!!', final: 'z' }, params => { callstack.push(['!!z', params.toArray()]); return true; }); + const h4 = parser.registerCsiHandler({ prefix: '?', final: 'z' }, params => { callstack.push(['?z', params.toArray()]); return true; }); + const h5 = parser.registerCsiHandler({ prefix: '?', intermediates: '!', final: 'z' }, params => { callstack.push(['?!z', params.toArray()]); return true; }); + const h6 = parser.registerCsiHandler({ prefix: '?', intermediates: '!!', final: 'z' }, params => { callstack.push(['?!!z', params.toArray()]); return true; }); parse(parser, '\x1b[1;z\x1b[1;!z\x1b[1;!!z\x1b[?1;z\x1b[?1;!z\x1b[?1;!!z'); h1.dispose(); h2.dispose(); @@ -1634,16 +1656,19 @@ describe('EscapeSequenceParser', function (): void { h5.dispose(); h6.dispose(); parse(parser, '\x1b[1;z\x1b[1;!z\x1b[1;!!z\x1b[?1;z\x1b[?1;!z\x1b[?1;!!z'); - chai.expect(callstack).eql([['z', [1, 0]], ['!z', [1, 0]], ['!!z', [1, 0]], ['?z', [1, 0]], ['?!z', [1, 0]], ['?!!z', [1, 0]]]); + assert.deepEqual( + callstack, + [['z', [1, 0]], ['!z', [1, 0]], ['!!z', [1, 0]], ['?z', [1, 0]], ['?!z', [1, 0]], ['?!!z', [1, 0]]] + ); }); it('DCS', () => { const callstack: any[] = []; - const h1 = parser.addDcsHandler({final: 'z'}, new DcsHandler((data, params) => { callstack.push(['z', params.toArray(), data]); })); - const h2 = parser.addDcsHandler({intermediates: '!', final: 'z'}, new DcsHandler((data, params) => { callstack.push(['!z', params.toArray(), data]); })); - const h3 = parser.addDcsHandler({intermediates: '!!', final: 'z'}, new DcsHandler((data, params) => { callstack.push(['!!z', params.toArray(), data]); })); - const h4 = parser.addDcsHandler({prefix: '?', final: 'z'}, new DcsHandler((data, params) => { callstack.push(['?z', params.toArray(), data]); })); - const h5 = parser.addDcsHandler({prefix: '?', intermediates: '!', final: 'z'}, new DcsHandler((data, params) => { callstack.push(['?!z', params.toArray(), data]); })); - const h6 = parser.addDcsHandler({prefix: '?', intermediates: '!!', final: 'z'}, new DcsHandler((data, params) => { callstack.push(['?!!z', params.toArray(), data]); })); + const h1 = parser.registerDcsHandler({ final: 'z' }, new DcsHandler((data, params) => { callstack.push(['z', params.toArray(), data]); return true; })); + const h2 = parser.registerDcsHandler({ intermediates: '!', final: 'z' }, new DcsHandler((data, params) => { callstack.push(['!z', params.toArray(), data]); return true; })); + const h3 = parser.registerDcsHandler({ intermediates: '!!', final: 'z' }, new DcsHandler((data, params) => { callstack.push(['!!z', params.toArray(), data]); return true; })); + const h4 = parser.registerDcsHandler({ prefix: '?', final: 'z' }, new DcsHandler((data, params) => { callstack.push(['?z', params.toArray(), data]); return true; })); + const h5 = parser.registerDcsHandler({ prefix: '?', intermediates: '!', final: 'z' }, new DcsHandler((data, params) => { callstack.push(['?!z', params.toArray(), data]); return true; })); + const h6 = parser.registerDcsHandler({ prefix: '?', intermediates: '!!', final: 'z' }, new DcsHandler((data, params) => { callstack.push(['?!!z', params.toArray(), data]); return true; })); parse(parser, '\x1bP1;zAB\x1b\\\x1bP1;!zAB\x1b\\\x1bP1;!!zAB\x1b\\\x1bP?1;zAB\x1b\\\x1bP?1;!zAB\x1b\\\x1bP?1;!!zAB\x1b\\'); h1.dispose(); h2.dispose(); @@ -1652,16 +1677,544 @@ describe('EscapeSequenceParser', function (): void { h5.dispose(); h6.dispose(); parse(parser, '\x1bP1;zAB\x1b\\\x1bP1;!zAB\x1b\\\x1bP1;!!zAB\x1b\\\x1bP?1;zAB\x1b\\\x1bP?1;!zAB\x1b\\\x1bP?1;!!zAB\x1b\\'); - chai.expect(callstack).eql([ - ['z', [1, 0], 'AB'], - ['!z', [1, 0], 'AB'], - ['!!z', [1, 0], 'AB'], - ['?z', [1, 0], 'AB'], - ['?!z', [1, 0], 'AB'], - ['?!!z', [1, 0], 'AB'] - ]); + assert.deepEqual( + callstack, + [ + ['z', [1, 0], 'AB'], + ['!z', [1, 0], 'AB'], + ['!!z', [1, 0], 'AB'], + ['?z', [1, 0], 'AB'], + ['?!z', [1, 0], 'AB'], + ['?!!z', [1, 0], 'AB'] + ] + ); }); }); }); // TODO: error conditions and error recovery (not implemented yet in parser) }); + + +/** + * async handler tests. + */ + +function parseSync(parser: TestEscapeSequenceParser, data: string): void | Promise { + const container = new Uint32Array(data.length); + const decoder = new StringToUtf32(); + return parser.parse(container, decoder.decode(data, container)); +} +async function parseP(parser: TestEscapeSequenceParser, data: string): Promise { + const container = new Uint32Array(data.length); + const decoder = new StringToUtf32(); + const len = decoder.decode(data, container); + let result: void | Promise; + let prev: boolean | undefined; + while (result = parser.parse(container, len, prev)) { + prev = await result; + } +} +function evalStackSaves(stackSaves: IParserStackState[], data: [number, ParserStackType, number][]): void { + assert.equal(stackSaves.length, data.length); + for (let i = 0; i < data.length; ++i) { + assert.equal(stackSaves[i].chunkPos, data[i][0]); + assert.equal(stackSaves[i].state, data[i][1]); + assert.equal(stackSaves[i].handlerPos, data[i][2]); + } +} +// helper similiar to assert.throws for async functions +async function throwsAsync(fn: () => Promise, message?: string | undefined): Promise { + let msg: string | undefined; + try { + await fn(); + } catch (e) { + if (e instanceof Error) { + msg = e.message; + } else if (typeof e === 'string') { + msg = e; + } + if (typeof message === 'string') { + assert.equal(msg, message); + } + return; + } + assert.throws(fn, message); +} + +describe('EscapeSequenceParser - async', () => { + // sequences: SGR 1;31 | hello SP | ESC %G | wor | ESC E | ld! | SGR 0 | EXE \r\n | $> | DCS 1;2 a [xyz] ST | OSC 1;foo=bar ST | FIN + // needed handlers: CSI m, PRINT, ESC %G, ESC E, EXE \r, EXE \n, OSC 1 + const INPUT = '\x1b[1;31mhello \x1b%Gwor\x1bEld!\x1b[0m\r\n$>\x1bP1;2axyz\x1b\\\x1b]1;foo=bar\x1b\\FIN'; + let RESULT: any[]; + let parser: TestEscapeSequenceParser; + const callstack: any[] = []; + function clearAccu(): void { + callstack.length = 0; + parser.trackedStack.length = 0; + } + beforeEach(() => { + RESULT = [ + ['SGR', [1, 31]], + ['PRINT', 'hello '], + ['ESC %G'], + ['PRINT', 'wor'], + ['ESC E'], + ['PRINT', 'ld!'], + ['SGR', [0]], + ['EXE \r'], + ['EXE \n'], + ['PRINT', '$>'], + ['DCS a', ['xyz', [1, 2]]], + ['OSC 1', 'foo=bar'], + ['PRINT', 'FIN'] + ]; + parser = new TestEscapeSequenceParser(); + parser.reset(); + parser.trackStackSavesOnPause(); + clearAccu(); + }); + describe('sync handlers should behave as before', () => { + beforeEach(() => { + parser.setPrintHandler((data, start, end) => { + let result = ''; + for (let i = start; i < end; ++i) { + result += stringFromCodePoint(data[i]); + } + callstack.push(['PRINT', result]); + }); + parser.registerCsiHandler({ final: 'm' }, params => { callstack.push(['SGR', params.toArray()]); return true; }); + parser.registerEscHandler({ intermediates: '%', final: 'G' }, () => { callstack.push(['ESC %G']); return true; }); + parser.registerEscHandler({ final: 'E' }, () => { callstack.push(['ESC E']); return true; }); + parser.setExecuteHandler('\r', () => { callstack.push(['EXE \r']); return true; }); + parser.setExecuteHandler('\n', () => { callstack.push(['EXE \n']); return true; }); + parser.registerOscHandler(1, new OscHandler(data => { callstack.push(['OSC 1', data]); return true; })); + parser.registerDcsHandler({final: 'a'}, new DcsHandler((data, params) => { callstack.push(['DCS a', [data, params.toArray()]]); return true;})); + }); + + it('sync handlers keep being parsed in sync mode', () => { + // note: if we have only sync handlers, a parse call should never return anything + assert.equal(!parseSync(parser, INPUT), true); + assert.equal(parser.parseStack.state, ParserStackType.NONE); // not paused + assert.equal(parser.trackedStack.length, 0); // never got paused + }); + it('correct result on sync parse call', () => { + parseSync(parser, INPUT); + assert.deepEqual(callstack, RESULT); + assert.equal(parser.trackedStack.length, 0); + }); + it('correct result on async parse call', async () => { + await parseP(parser, INPUT); + assert.deepEqual(callstack, RESULT); + assert.equal(parser.trackedStack.length, 0); + }); + }); + describe('async handlers', () => { + beforeEach(() => { + parser.setPrintHandler((data, start, end) => { + let result = ''; + for (let i = start; i < end; ++i) { + result += stringFromCodePoint(data[i]); + } + callstack.push(['PRINT', result]); + }); + parser.registerCsiHandler({ final: 'm' }, async params => { callstack.push(['SGR', params.toArray()]); return true; }); + parser.registerEscHandler({ intermediates: '%', final: 'G' }, async () => { callstack.push(['ESC %G']); return true; }); + parser.registerEscHandler({ final: 'E' }, async () => { callstack.push(['ESC E']); return true; }); + parser.setExecuteHandler('\r', () => { callstack.push(['EXE \r']); return true; }); + parser.setExecuteHandler('\n', () => { callstack.push(['EXE \n']); return true; }); + parser.registerOscHandler(1, new OscHandler(async data => { callstack.push(['OSC 1', data]); return true; })); + parser.registerDcsHandler({final: 'a'}, new DcsHandler(async (data, params) => { callstack.push(['DCS a', [data, params.toArray()]]); return true;})); + }); + + it('sync parse call does not work anymore', () => { + assert.notEqual(!parseSync(parser, INPUT), true); + assert.notDeepEqual(callstack, RESULT); + // due to sync calling we should save exactly one saved stack + // proper continuation is not possible anymore, as we lost the promise resolve value + assert.equal(parser.trackedStack.length, 1); + }); + it('improper continuation should throw', async () => { + /** + * Explanation: + * The first sync call will stop at the first promise returned, + * but does not await its resolve value. + * The second sync call to parse will fail due to missing `promiseResult`, + * which is needed for correct continuation. + */ + assert.notEqual(!parseSync(parser, INPUT), true); + assert.notDeepEqual(callstack, RESULT); + assert.throws(() => parseSync(parser, INPUT), 'improper continuation due to previous async handler, giving up parsing'); + // keeps being broken for further parse calls (sync and async) + assert.throws(() => parseSync(parser, 'random'), 'improper continuation due to previous async handler, giving up parsing'); + await throwsAsync(() => parseP(parser, 'foobar'), 'improper continuation due to previous async handler, giving up parsing'); + // reset should lift the error condition + parser.reset(); + await parseP(parser, INPUT); // does not throw anymore + }); + it('correct result on awaited parse call', async () => { + await parseP(parser, INPUT); + assert.deepEqual(callstack, RESULT); + evalStackSaves(parser.trackedStack, [ + [6, ParserStackType.CSI, 0], + [15, ParserStackType.ESC, 0], + [20, ParserStackType.ESC, 0], + [27, ParserStackType.CSI, 0], + [41, ParserStackType.DCS, 0], + [54, ParserStackType.OSC, 0] + ]); + }); + it('correct result on chunked awaited parse calls', async () => { + RESULT = [ + ['SGR', [1, 31]], + ['PRINT', 'h'], // due to single char input PRINT is split + ['PRINT', 'e'], + ['PRINT', 'l'], + ['PRINT', 'l'], + ['PRINT', 'o'], + ['PRINT', ' '], + ['ESC %G'], + ['PRINT', 'w'], + ['PRINT', 'o'], + ['PRINT', 'r'], + ['ESC E'], + ['PRINT', 'l'], + ['PRINT', 'd'], + ['PRINT', '!'], + ['SGR', [0]], + ['EXE \r'], + ['EXE \n'], + ['PRINT', '$'], + ['PRINT', '>'], + ['DCS a', ['xyz', [1, 2]]], + ['OSC 1', 'foo=bar'], + ['PRINT', 'F'], + ['PRINT', 'I'], + ['PRINT', 'N'] + ]; + + // split to single char input + for (let i = 0; i < INPUT.length; ++i) { + // Note: a single fully awaited parse call always ends in sync mode, + // which re-enables faster sync processing in the higher up callstack + await parseP(parser, INPUT[i]); + } + assert.deepEqual(callstack, RESULT); + evalStackSaves(parser.trackedStack, [ + [0, ParserStackType.CSI, 0], + [0, ParserStackType.ESC, 0], + [0, ParserStackType.ESC, 0], + [0, ParserStackType.CSI, 0], + [0, ParserStackType.DCS, 0], + [0, ParserStackType.OSC, 0] + ]); + }); + it('multiple async SGR handlers', async () => { + // register with fallback + const SGR2 = parser.registerCsiHandler({ final: 'm' }, async params => { callstack.push(['2# SGR', params.toArray()]); return false; }); + await parseP(parser, INPUT); + // should contain [2# SGR, SGR] call pairs + for (let i = 0; i < callstack.length; ++i) { + const entry = callstack[i]; + if (entry[0] === '2# SGR') assert.equal(callstack[i + 1][0], 'SGR', 'Should fallback to original handler'); + } + evalStackSaves(parser.trackedStack, [ + [6, ParserStackType.CSI, 1], + [6, ParserStackType.CSI, 0], + [15, ParserStackType.ESC, 0], + [20, ParserStackType.ESC, 0], + [27, ParserStackType.CSI, 1], + [27, ParserStackType.CSI, 0], + [41, ParserStackType.DCS, 0], + [54, ParserStackType.OSC, 0] + ]); + clearAccu(); + // after dispose we should be back to RESULT + SGR2.dispose(); + await parseP(parser, INPUT); + assert.deepEqual(callstack, RESULT, 'Should not call custom handler'); + evalStackSaves(parser.trackedStack, [ + [6, ParserStackType.CSI, 0], + [15, ParserStackType.ESC, 0], + [20, ParserStackType.ESC, 0], + [27, ParserStackType.CSI, 0], + [41, ParserStackType.DCS, 0], + [54, ParserStackType.OSC, 0] + ]); + clearAccu(); + + // register without fallback + const SGR22 = parser.registerCsiHandler({ final: 'm' }, async params => { callstack.push(['2# SGR', params.toArray()]); return true; }); + await parseP(parser, INPUT); + // should only contain 2# SGR + for (let i = 0; i < callstack.length; ++i) { + const entry = callstack[i]; + if (entry[0] === '2# SGR') assert.notEqual(callstack[i + 1][0], 'SGR', 'Should not fallback to original handler'); + } + evalStackSaves(parser.trackedStack, [ + [6, ParserStackType.CSI, 1], + [15, ParserStackType.ESC, 0], + [20, ParserStackType.ESC, 0], + [27, ParserStackType.CSI, 1], + [41, ParserStackType.DCS, 0], + [54, ParserStackType.OSC, 0] + ]); + clearAccu(); + // after dispose we should be back to RESULT + SGR22.dispose(); + await parseP(parser, INPUT); + assert.deepEqual(callstack, RESULT, 'Should not call custom handler'); + evalStackSaves(parser.trackedStack, [ + [6, ParserStackType.CSI, 0], + [15, ParserStackType.ESC, 0], + [20, ParserStackType.ESC, 0], + [27, ParserStackType.CSI, 0], + [41, ParserStackType.DCS, 0], + [54, ParserStackType.OSC, 0] + ]); + }); + it('multiple async ESC handlers', async () => { + // register with fallback + const ESC2 = parser.registerEscHandler({ final: 'E' }, async () => { callstack.push(['2# ESC E']); return false; }); + await parseP(parser, INPUT); + for (let i = 0; i < callstack.length; ++i) { + const entry = callstack[i]; + if (entry[0] === '2# ESC E') assert.equal(callstack[i + 1][0], 'ESC E', 'Should fallback to original handler'); + } + evalStackSaves(parser.trackedStack, [ + [6, ParserStackType.CSI, 0], + [15, ParserStackType.ESC, 0], + [20, ParserStackType.ESC, 1], + [20, ParserStackType.ESC, 0], + [27, ParserStackType.CSI, 0], + [41, ParserStackType.DCS, 0], + [54, ParserStackType.OSC, 0] + ]); + clearAccu(); + // after dispose we should be back to RESULT + ESC2.dispose(); + await parseP(parser, INPUT); + assert.deepEqual(callstack, RESULT, 'Should not call custom handler'); + evalStackSaves(parser.trackedStack, [ + [6, ParserStackType.CSI, 0], + [15, ParserStackType.ESC, 0], + [20, ParserStackType.ESC, 0], + [27, ParserStackType.CSI, 0], + [41, ParserStackType.DCS, 0], + [54, ParserStackType.OSC, 0] + ]); + clearAccu(); + + // register without fallback + const ESC22 = parser.registerEscHandler({ final: 'E' }, async () => { callstack.push(['2# ESC E']); return true; }); + await parseP(parser, INPUT); + for (let i = 0; i < callstack.length; ++i) { + const entry = callstack[i]; + if (entry[0] === '2# ESC E') assert.notEqual(callstack[i + 1][0], 'ESC E', 'Should not fallback to original handler'); + } + evalStackSaves(parser.trackedStack, [ + [6, ParserStackType.CSI, 0], + [15, ParserStackType.ESC, 0], + [20, ParserStackType.ESC, 1], + [27, ParserStackType.CSI, 0], + [41, ParserStackType.DCS, 0], + [54, ParserStackType.OSC, 0] + ]); + clearAccu(); + // after dispose we should be back to RESULT + ESC22.dispose(); + await parseP(parser, INPUT); + assert.deepEqual(callstack, RESULT, 'Should not call custom handler'); + evalStackSaves(parser.trackedStack, [ + [6, ParserStackType.CSI, 0], + [15, ParserStackType.ESC, 0], + [20, ParserStackType.ESC, 0], + [27, ParserStackType.CSI, 0], + [41, ParserStackType.DCS, 0], + [54, ParserStackType.OSC, 0] + ]); + }); + it('sync/async SGR mixed', async () => { + // sync with fallback + const SGR2 = parser.registerCsiHandler({ final: 'm' }, params => { callstack.push(['2# SGR', params.toArray()]); return false; }); + // async with fallback + const SGR3 = parser.registerCsiHandler({ final: 'm' }, async params => { callstack.push(['3# SGR', params.toArray()]); return false; }); + await parseP(parser, INPUT); + // should contain [3# SGR, 2# SGR, SGR] call triples + for (let i = 0; i < callstack.length; ++i) { + const entry = callstack[i]; + if (entry[0] === '3# SGR') { + assert.equal(callstack[i + 1][0], '2# SGR', 'Should fallback to next handler'); + assert.equal(callstack[i + 2][0], 'SGR', 'Should fallback to original handler'); + } + } + evalStackSaves(parser.trackedStack, [ + [6, ParserStackType.CSI, 2], + [6, ParserStackType.CSI, 0], + [15, ParserStackType.ESC, 0], + [20, ParserStackType.ESC, 0], + [27, ParserStackType.CSI, 2], + [27, ParserStackType.CSI, 0], + [41, ParserStackType.DCS, 0], + [54, ParserStackType.OSC, 0] + ]); + clearAccu(); + // dispose SGR2 (sync one) + SGR2.dispose(); + await parseP(parser, INPUT); + // should contain [3# SGR, SGR] call pairs + for (let i = 0; i < callstack.length; ++i) { + const entry = callstack[i]; + if (entry[0] === '3# SGR') { + assert.equal(callstack[i + 1][0], 'SGR', 'Should fallback to original handler'); + } + } + evalStackSaves(parser.trackedStack, [ + [6, ParserStackType.CSI, 1], + [6, ParserStackType.CSI, 0], + [15, ParserStackType.ESC, 0], + [20, ParserStackType.ESC, 0], + [27, ParserStackType.CSI, 1], + [27, ParserStackType.CSI, 0], + [41, ParserStackType.DCS, 0], + [54, ParserStackType.OSC, 0] + ]); + clearAccu(); + // dispose SGR3 (async one) + SGR3.dispose(); + await parseP(parser, INPUT); + assert.deepEqual(callstack, RESULT, 'Should not call custom handler'); + evalStackSaves(parser.trackedStack, [ + [6, ParserStackType.CSI, 0], + [15, ParserStackType.ESC, 0], + [20, ParserStackType.ESC, 0], + [27, ParserStackType.CSI, 0], + [41, ParserStackType.DCS, 0], + [54, ParserStackType.OSC, 0] + ]); + }); + it('multiple async OSC handlers', async () => { + // register with fallback + const OSC2 = parser.registerOscHandler(1, new OscHandler(async data => { callstack.push(['2# OSC 1', data]); return false; })); + await parseP(parser, INPUT); + for (let i = 0; i < callstack.length; ++i) { + const entry = callstack[i]; + if (entry[0] === '2# OSC 1') assert.equal(callstack[i + 1][0], 'OSC 1', 'Should fallback to original handler'); + } + evalStackSaves(parser.trackedStack, [ + [6, ParserStackType.CSI, 0], + [15, ParserStackType.ESC, 0], + [20, ParserStackType.ESC, 0], + [27, ParserStackType.CSI, 0], + [41, ParserStackType.DCS, 0], + [54, ParserStackType.OSC, 0], + [54, ParserStackType.OSC, 0] + ]); + clearAccu(); + // after dispose we should be back to RESULT + OSC2.dispose(); + await parseP(parser, INPUT); + assert.deepEqual(callstack, RESULT, 'Should not call custom handler'); + evalStackSaves(parser.trackedStack, [ + [6, ParserStackType.CSI, 0], + [15, ParserStackType.ESC, 0], + [20, ParserStackType.ESC, 0], + [27, ParserStackType.CSI, 0], + [41, ParserStackType.DCS, 0], + [54, ParserStackType.OSC, 0] + ]); + clearAccu(); + + // register without fallback + const OSC22 = parser.registerOscHandler(1, new OscHandler(async data => { callstack.push(['2# OSC 1', data]); return true; })); + await parseP(parser, INPUT); + for (let i = 0; i < callstack.length; ++i) { + const entry = callstack[i]; + if (entry[0] === '2# OSC 1') assert.notEqual(callstack[i + 1][0], 'OSC 1', 'Should fallback to original handler'); + } + evalStackSaves(parser.trackedStack, [ + [6, ParserStackType.CSI, 0], + [15, ParserStackType.ESC, 0], + [20, ParserStackType.ESC, 0], + [27, ParserStackType.CSI, 0], + [41, ParserStackType.DCS, 0], + [54, ParserStackType.OSC, 0] + ]); + clearAccu(); + // after dispose we should be back to RESULT + OSC22.dispose(); + await parseP(parser, INPUT); + assert.deepEqual(callstack, RESULT, 'Should not call custom handler'); + evalStackSaves(parser.trackedStack, [ + [6, ParserStackType.CSI, 0], + [15, ParserStackType.ESC, 0], + [20, ParserStackType.ESC, 0], + [27, ParserStackType.CSI, 0], + [41, ParserStackType.DCS, 0], + [54, ParserStackType.OSC, 0] + ]); + clearAccu(); + }); + it('multiple async DCS handlers', async () => { + // register with fallback + const DCS2 = parser.registerDcsHandler({final: 'a'}, new DcsHandler(async (data, params) => { callstack.push(['#2 DCS a', [data, params.toArray()]]); return false;})); + await parseP(parser, INPUT); + for (let i = 0; i < callstack.length; ++i) { + const entry = callstack[i]; + if (entry[0] === '2# DCS a') assert.equal(callstack[i + 1][0], 'DCS a', 'Should fallback to original handler'); + } + evalStackSaves(parser.trackedStack, [ + [6, ParserStackType.CSI, 0], + [15, ParserStackType.ESC, 0], + [20, ParserStackType.ESC, 0], + [27, ParserStackType.CSI, 0], + [41, ParserStackType.DCS, 0], + [41, ParserStackType.DCS, 0], + [54, ParserStackType.OSC, 0] + ]); + clearAccu(); + // after dispose we should be back to RESULT + DCS2.dispose(); + await parseP(parser, INPUT); + assert.deepEqual(callstack, RESULT, 'Should not call custom handler'); + evalStackSaves(parser.trackedStack, [ + [6, ParserStackType.CSI, 0], + [15, ParserStackType.ESC, 0], + [20, ParserStackType.ESC, 0], + [27, ParserStackType.CSI, 0], + [41, ParserStackType.DCS, 0], + [54, ParserStackType.OSC, 0] + ]); + clearAccu(); + + // register without fallback + const DCS22 = parser.registerDcsHandler({final: 'a'}, new DcsHandler(async (data, params) => { callstack.push(['#2 DCS a', [data, params.toArray()]]); return true;})); + await parseP(parser, INPUT); + for (let i = 0; i < callstack.length; ++i) { + const entry = callstack[i]; + if (entry[0] === '2# DCS a') assert.notEqual(callstack[i + 1][0], 'DCS a', 'Should fallback to original handler'); + } + evalStackSaves(parser.trackedStack, [ + [6, ParserStackType.CSI, 0], + [15, ParserStackType.ESC, 0], + [20, ParserStackType.ESC, 0], + [27, ParserStackType.CSI, 0], + [41, ParserStackType.DCS, 0], + [54, ParserStackType.OSC, 0] + ]); + clearAccu(); + // after dispose we should be back to RESULT + DCS22.dispose(); + await parseP(parser, INPUT); + assert.deepEqual(callstack, RESULT, 'Should not call custom handler'); + evalStackSaves(parser.trackedStack, [ + [6, ParserStackType.CSI, 0], + [15, ParserStackType.ESC, 0], + [20, ParserStackType.ESC, 0], + [27, ParserStackType.CSI, 0], + [41, ParserStackType.DCS, 0], + [54, ParserStackType.OSC, 0] + ]); + clearAccu(); + }); + }); +}); diff --git a/src/common/parser/EscapeSequenceParser.ts b/src/common/parser/EscapeSequenceParser.ts index 96d206b9..f20a7e91 100644 --- a/src/common/parser/EscapeSequenceParser.ts +++ b/src/common/parser/EscapeSequenceParser.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams, IOscHandler, IHandlerCollection, CsiHandlerType, OscFallbackHandlerType, IOscParser, EscHandlerType, IDcsParser, DcsFallbackHandlerType, IFunctionIdentifier, ExecuteFallbackHandlerType, CsiFallbackHandlerType, EscFallbackHandlerType, PrintHandlerType, PrintFallbackHandlerType, ExecuteHandlerType } from 'common/parser/Types'; +import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams, IOscHandler, IHandlerCollection, CsiHandlerType, OscFallbackHandlerType, IOscParser, EscHandlerType, IDcsParser, DcsFallbackHandlerType, IFunctionIdentifier, ExecuteFallbackHandlerType, CsiFallbackHandlerType, EscFallbackHandlerType, PrintHandlerType, PrintFallbackHandlerType, ExecuteHandlerType, IParserStackState, ParserStackType, ResumableHandlersType } from 'common/parser/Types'; import { ParserState, ParserAction } from 'common/parser/Constants'; import { Disposable } from 'common/Lifecycle'; import { IDisposable } from 'common/Types'; @@ -239,7 +239,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP // handler lookup containers protected _printHandler: PrintHandlerType; - protected _executeHandlers: {[flag: number]: ExecuteHandlerType}; + protected _executeHandlers: { [flag: number]: ExecuteHandlerType }; protected _csiHandlers: IHandlerCollection; protected _escHandlers: IHandlerCollection; protected _oscParser: IOscParser; @@ -253,6 +253,15 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP protected _escHandlerFb: EscFallbackHandlerType; protected _errorHandlerFb: (state: IParsingState) => IParsingState; + // parser stack save for async handler support + protected _parseStack: IParserStackState = { + state: ParserStackType.NONE, + handlers: [], + handlerPos: 0, + transition: 0, + chunkPos: 0 + }; + constructor( protected readonly _transitions: TransitionTable = VT500_TRANSITION_TABLE ) { @@ -280,7 +289,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP this._errorHandler = this._errorHandlerFb; // swallow 7bit ST (ESC+\) - this.setEscHandler({final: '\\'}, () => {}); + this.registerEscHandler({ final: '\\' }, () => true); } protected _identifier(id: IFunctionIdentifier, finalRange: number[] = [0x40, 0x7e]): number { @@ -344,7 +353,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP this._printHandler = this._printHandlerFb; } - public addEscHandler(id: IFunctionIdentifier, handler: EscHandlerType): IDisposable { + public registerEscHandler(id: IFunctionIdentifier, handler: EscHandlerType): IDisposable { const ident = this._identifier(id, [0x30, 0x7e]); if (this._escHandlers[ident] === undefined) { this._escHandlers[ident] = []; @@ -360,9 +369,6 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP } }; } - public setEscHandler(id: IFunctionIdentifier, handler: EscHandlerType): void { - this._escHandlers[this._identifier(id, [0x30, 0x7e])] = [handler]; - } public clearEscHandler(id: IFunctionIdentifier): void { if (this._escHandlers[this._identifier(id, [0x30, 0x7e])]) delete this._escHandlers[this._identifier(id, [0x30, 0x7e])]; } @@ -380,7 +386,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP this._executeHandlerFb = handler; } - public addCsiHandler(id: IFunctionIdentifier, handler: CsiHandlerType): IDisposable { + public registerCsiHandler(id: IFunctionIdentifier, handler: CsiHandlerType): IDisposable { const ident = this._identifier(id); if (this._csiHandlers[ident] === undefined) { this._csiHandlers[ident] = []; @@ -396,9 +402,6 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP } }; } - public setCsiHandler(id: IFunctionIdentifier, handler: CsiHandlerType): void { - this._csiHandlers[this._identifier(id)] = [handler]; - } public clearCsiHandler(id: IFunctionIdentifier): void { if (this._csiHandlers[this._identifier(id)]) delete this._csiHandlers[this._identifier(id)]; } @@ -406,11 +409,8 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP this._csiHandlerFb = callback; } - public addDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): IDisposable { - return this._dcsParser.addHandler(this._identifier(id), handler); - } - public setDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): void { - this._dcsParser.setHandler(this._identifier(id), handler); + public registerDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): IDisposable { + return this._dcsParser.registerHandler(this._identifier(id), handler); } public clearDcsHandler(id: IFunctionIdentifier): void { this._dcsParser.clearHandler(this._identifier(id)); @@ -419,11 +419,8 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP this._dcsParser.setHandlerFallback(handler); } - public addOscHandler(ident: number, handler: IOscHandler): IDisposable { - return this._oscParser.addHandler(ident, handler); - } - public setOscHandler(ident: number, handler: IOscHandler): void { - this._oscParser.setHandler(ident, handler); + public registerOscHandler(ident: number, handler: IOscHandler): IDisposable { + return this._oscParser.registerHandler(ident, handler); } public clearOscHandler(ident: number): void { this._oscParser.clearHandler(ident); @@ -439,6 +436,15 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP this._errorHandler = this._errorHandlerFb; } + /** + * Reset parser to initial values. + * + * This can also be used to lift the improper continuation error condition + * when dealing with async handlers. Use this only as a last resort to silence + * that error when the terminal has no pending data to be processed. Note that + * the interrupted async handler might continue its work in the future messing + * up the terminal state even further. + */ public reset(): void { this.currentState = this.initialState; this._oscParser.reset(); @@ -447,9 +453,31 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP this._params.addParam(0); // ZDM this._collect = 0; this.precedingCodepoint = 0; + // abort pending continuation from async handler + // Here the RESET type indicates, that the next parse call will + // ignore any saved stack, instead continues sync with next codepoint from GROUND + if (this._parseStack.state !== ParserStackType.NONE) { + this._parseStack.state = ParserStackType.RESET; + this._parseStack.handlers = []; // also release handlers ref + } } - + /** + * Async parse support. + */ + protected _preserveStack( + state: ParserStackType, + handlers: ResumableHandlersType, + handlerPos: number, + transition: number, + chunkPos: number + ): void { + this._parseStack.state = state; + this._parseStack.handlers = handlers; + this._parseStack.handlerPos = handlerPos; + this._parseStack.transition = transition; + this._parseStack.chunkPos = chunkPos; + } /** * Parse UTF32 codepoints in `data` up to `length`. @@ -464,23 +492,141 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP * - DCS_PARAM:PARAM * - OSC_STRING:OSC_PUT * - DCS_PASSTHROUGH:DCS_PUT + * + * Note on asynchronous handler support: + * Any handler returning a promise will be treated as asynchronous. + * To keep the in-band blocking working for async handlers, `parse` pauses execution, + * creates a stack save and returns the promise to the caller. + * For proper continuation of the paused state it is important + * to await the promise resolving. On resolve the parse must be repeated + * with the same chunk of data and the resolved value in `promiseResult` + * until no promise is returned. + * + * Important: With only sync handlers defined, parsing is completely synchronous as well. + * As soon as an async handler is involved, synchronous parsing is not possible anymore. + * + * Boilerplate for proper parsing of multiple chunks with async handlers: + * + * ```typescript + * async function parseMultipleChunks(chunks: Uint32Array[]): Promise { + * for (const chunk of chunks) { + * let result: void | Promise; + * let prev: boolean | undefined; + * while (result = parser.parse(chunk, chunk.length, prev)) { + * prev = await result; + * } + * } + * // finished parsing all chunks... + * } + * ``` */ - public parse(data: Uint32Array, length: number): void { + public parse(data: Uint32Array, length: number, promiseResult?: boolean): void | Promise { let code = 0; let transition = 0; - let currentState = this.currentState; - const osc = this._oscParser; - const dcs = this._dcsParser; - let collect = this._collect; - const params = this._params; - const table: Uint8Array = this._transitions.table; + let start = 0; + let handlerResult: void | boolean | Promise; + + // resume from async handler + if (this._parseStack.state) { + // allow sync parser reset even in continuation mode + // Note: can be used to recover parser from improper continuation error below + if (this._parseStack.state === ParserStackType.RESET) { + this._parseStack.state = ParserStackType.NONE; + start = this._parseStack.chunkPos + 1; // continue with next codepoint in GROUND + } else { + if (promiseResult === undefined || this._parseStack.state === ParserStackType.FAIL) { + /** + * Reject further parsing on improper continuation after pausing. + * This is a really bad condition with screwed up execution order and prolly messed up + * terminal state, therefore we exit hard with an exception and reject any further parsing. + * + * Note: With `Terminal.write` usage this exception should never occur, as the top level + * calls are guaranteed to handle async conditions properly. If you ever encounter this + * exception in your terminal integration it indicates, that you injected data chunks to + * `InputHandler.parse` or `EscapeSequenceParser.parse` synchronously without waiting for + * continuation of a running async handler. + * + * It is possible to get rid of this error by calling `reset`. But dont rely on that, + * as the pending async handler still might mess up the terminal later. Instead fix the faulty + * async handling, so this error will not be thrown anymore. + */ + this._parseStack.state = ParserStackType.FAIL; + throw new Error('improper continuation due to previous async handler, giving up parsing'); + } + + // we have to resume the old handler loop if: + // - return value of the promise was `false` + // - handlers are not exhausted yet + const handlers = this._parseStack.handlers; + let handlerPos = this._parseStack.handlerPos - 1; + switch (this._parseStack.state) { + case ParserStackType.CSI: + if (promiseResult === false && handlerPos > -1) { + for (; handlerPos >= 0; handlerPos--) { + handlerResult = (handlers as CsiHandlerType[])[handlerPos](this._params); + if (handlerResult === true) { + break; + } else if (handlerResult instanceof Promise) { + this._parseStack.handlerPos = handlerPos; + return handlerResult; + } + } + } + this._parseStack.handlers = []; + break; + case ParserStackType.ESC: + if (promiseResult === false && handlerPos > -1) { + for (; handlerPos >= 0; handlerPos--) { + handlerResult = (handlers as EscHandlerType[])[handlerPos](); + if (handlerResult === true) { + break; + } else if (handlerResult instanceof Promise) { + this._parseStack.handlerPos = handlerPos; + return handlerResult; + } + } + } + this._parseStack.handlers = []; + break; + case ParserStackType.DCS: + code = data[this._parseStack.chunkPos]; + handlerResult = this._dcsParser.unhook(code !== 0x18 && code !== 0x1a, promiseResult); + if (handlerResult) { + return handlerResult; + } + if (code === 0x1b) this._parseStack.transition |= ParserState.ESCAPE; + this._params.reset(); + this._params.addParam(0); // ZDM + this._collect = 0; + break; + case ParserStackType.OSC: + code = data[this._parseStack.chunkPos]; + handlerResult = this._oscParser.end(code !== 0x18 && code !== 0x1a, promiseResult); + if (handlerResult) { + return handlerResult; + } + if (code === 0x1b) this._parseStack.transition |= ParserState.ESCAPE; + this._params.reset(); + this._params.addParam(0); // ZDM + this._collect = 0; + break; + } + // cleanup before continuing with the main sync loop + this._parseStack.state = ParserStackType.NONE; + start = this._parseStack.chunkPos + 1; + this.precedingCodepoint = 0; + this.currentState = this._parseStack.transition & TableAccess.TRANSITION_STATE_MASK; + } + } + + // continue with main sync loop // process input string - for (let i = 0; i < length; ++i) { + for (let i = start; i < length; ++i) { code = data[i]; // normal transition & action lookup - transition = table[currentState << TableAccess.INDEX_STATE_SHIFT | (code < 0xa0 ? code : NON_ASCII_PRINTABLE)]; + transition = this._transitions.table[this.currentState << TableAccess.INDEX_STATE_SHIFT | (code < 0xa0 ? code : NON_ASCII_PRINTABLE)]; switch (transition >> TableAccess.TRANSITION_ACTION_SHIFT) { case ParserAction.PRINT: // read ahead with loop unrolling @@ -520,9 +666,9 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP { position: i, code, - currentState, - collect, - params, + currentState: this.currentState, + collect: this._collect, + params: this._params, abort: false }); if (inject.abort) return; @@ -530,16 +676,21 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP break; case ParserAction.CSI_DISPATCH: // Trigger CSI Handler - const handlers = this._csiHandlers[collect << 8 | code]; + const handlers = this._csiHandlers[this._collect << 8 | code]; let j = handlers ? handlers.length - 1 : -1; for (; j >= 0; j--) { - // undefined or true means success and to stop bubbling - if (handlers[j](params) !== false) { + // true means success and to stop bubbling + // a promise indicates an async handler that needs to finish before progressing + handlerResult = handlers[j](this._params); + if (handlerResult === true) { break; + } else if (handlerResult instanceof Promise) { + this._preserveStack(ParserStackType.CSI, handlers, j, transition, i); + return handlerResult; } } if (j < 0) { - this._csiHandlerFb(collect << 8 | code, params); + this._csiHandlerFb(this._collect << 8 | code, this._params); } this.precedingCodepoint = 0; break; @@ -548,91 +699,98 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP do { switch (code) { case 0x3b: - params.addParam(0); // ZDM + this._params.addParam(0); // ZDM break; case 0x3a: - params.addSubParam(-1); + this._params.addSubParam(-1); break; default: // 0x30 - 0x39 - params.addDigit(code - 48); + this._params.addDigit(code - 48); } } while (++i < length && (code = data[i]) > 0x2f && code < 0x3c); i--; break; case ParserAction.COLLECT: - collect <<= 8; - collect |= code; + this._collect <<= 8; + this._collect |= code; break; case ParserAction.ESC_DISPATCH: - const handlersEsc = this._escHandlers[collect << 8 | code]; + const handlersEsc = this._escHandlers[this._collect << 8 | code]; let jj = handlersEsc ? handlersEsc.length - 1 : -1; for (; jj >= 0; jj--) { - // undefined or true means success and to stop bubbling - if (handlersEsc[jj]() !== false) { + // true means success and to stop bubbling + // a promise indicates an async handler that needs to finish before progressing + handlerResult = handlersEsc[jj](); + if (handlerResult === true) { break; + } else if (handlerResult instanceof Promise) { + this._preserveStack(ParserStackType.ESC, handlersEsc, jj, transition, i); + return handlerResult; } } if (jj < 0) { - this._escHandlerFb(collect << 8 | code); + this._escHandlerFb(this._collect << 8 | code); } this.precedingCodepoint = 0; break; case ParserAction.CLEAR: - params.reset(); - params.addParam(0); // ZDM - collect = 0; + this._params.reset(); + this._params.addParam(0); // ZDM + this._collect = 0; break; case ParserAction.DCS_HOOK: - dcs.hook(collect << 8 | code, params); + this._dcsParser.hook(this._collect << 8 | code, this._params); break; case ParserAction.DCS_PUT: // inner loop - exit DCS_PUT: 0x18, 0x1a, 0x1b, 0x7f, 0x80 - 0x9f // unhook triggered by: 0x1b, 0x9c (success) and 0x18, 0x1a (abort) for (let j = i + 1; ; ++j) { if (j >= length || (code = data[j]) === 0x18 || code === 0x1a || code === 0x1b || (code > 0x7f && code < NON_ASCII_PRINTABLE)) { - dcs.put(data, i, j); + this._dcsParser.put(data, i, j); i = j - 1; break; } } break; case ParserAction.DCS_UNHOOK: - dcs.unhook(code !== 0x18 && code !== 0x1a); + handlerResult = this._dcsParser.unhook(code !== 0x18 && code !== 0x1a); + if (handlerResult) { + this._preserveStack(ParserStackType.DCS, [], 0, transition, i); + return handlerResult; + } if (code === 0x1b) transition |= ParserState.ESCAPE; - params.reset(); - params.addParam(0); // ZDM - collect = 0; + this._params.reset(); + this._params.addParam(0); // ZDM + this._collect = 0; this.precedingCodepoint = 0; break; case ParserAction.OSC_START: - osc.start(); + this._oscParser.start(); break; case ParserAction.OSC_PUT: // inner loop: 0x20 (SP) included, 0x7F (DEL) included for (let j = i + 1; ; j++) { - if (j >= length || (code = data[j]) < 0x20 || (code > 0x7f && code <= 0x9f)) { - osc.put(data, i, j); + if (j >= length || (code = data[j]) < 0x20 || (code > 0x7f && code < NON_ASCII_PRINTABLE)) { + this._oscParser.put(data, i, j); i = j - 1; break; } } break; case ParserAction.OSC_END: - osc.end(code !== 0x18 && code !== 0x1a); + handlerResult = this._oscParser.end(code !== 0x18 && code !== 0x1a); + if (handlerResult) { + this._preserveStack(ParserStackType.OSC, [], 0, transition, i); + return handlerResult; + } if (code === 0x1b) transition |= ParserState.ESCAPE; - params.reset(); - params.addParam(0); // ZDM - collect = 0; + this._params.reset(); + this._params.addParam(0); // ZDM + this._collect = 0; this.precedingCodepoint = 0; break; } - currentState = transition & TableAccess.TRANSITION_STATE_MASK; + this.currentState = transition & TableAccess.TRANSITION_STATE_MASK; } - - // save collected intermediates - this._collect = collect; - - // save state - this.currentState = currentState; } } diff --git a/src/common/parser/OscParser.test.ts b/src/common/parser/OscParser.test.ts index 127bd83a..5c7f5777 100644 --- a/src/common/parser/OscParser.test.ts +++ b/src/common/parser/OscParser.test.ts @@ -23,11 +23,12 @@ class TestHandler implements IOscHandler { public put(data: Uint32Array, start: number, end: number): void { this.output.push([this.msg, this.id, 'PUT', utf32ToString(data, start, end)]); } - public end(success: boolean): void | boolean { + public end(success: boolean): boolean { this.output.push([this.msg, this.id, 'END', success]); if (this.returnFalse) { return false; } + return true; } } @@ -78,7 +79,7 @@ describe('OscParser', () => { }); describe('handler registration', () => { it('setOscHandler', () => { - parser.setHandler(1234, new TestHandler(1234, reports, 'th')); + parser.registerHandler(1234, new TestHandler(1234, reports, 'th')); parser.start(); let data = toUtf32('1234;Here comes'); parser.put(data, 0, data.length); @@ -94,7 +95,7 @@ describe('OscParser', () => { ]); }); it('clearOscHandler', () => { - parser.setHandler(1234, new TestHandler(1234, reports, 'th')); + parser.registerHandler(1234, new TestHandler(1234, reports, 'th')); parser.clearHandler(1234); parser.start(); let data = toUtf32('1234;Here comes'); @@ -111,8 +112,8 @@ describe('OscParser', () => { ]); }); it('addOscHandler', () => { - parser.setHandler(1234, new TestHandler(1234, reports, 'th1')); - parser.addHandler(1234, new TestHandler(1234, reports, 'th2')); + parser.registerHandler(1234, new TestHandler(1234, reports, 'th1')); + parser.registerHandler(1234, new TestHandler(1234, reports, 'th2')); parser.start(); let data = toUtf32('1234;Here comes'); parser.put(data, 0, data.length); @@ -131,8 +132,8 @@ describe('OscParser', () => { ]); }); it('addOscHandler with return false', () => { - parser.setHandler(1234, new TestHandler(1234, reports, 'th1')); - parser.addHandler(1234, new TestHandler(1234, reports, 'th2', true)); + parser.registerHandler(1234, new TestHandler(1234, reports, 'th1')); + parser.registerHandler(1234, new TestHandler(1234, reports, 'th2', true)); parser.start(); let data = toUtf32('1234;Here comes'); parser.put(data, 0, data.length); @@ -151,8 +152,8 @@ describe('OscParser', () => { ]); }); it('dispose handlers', () => { - parser.setHandler(1234, new TestHandler(1234, reports, 'th1')); - const dispo = parser.addHandler(1234, new TestHandler(1234, reports, 'th2', true)); + parser.registerHandler(1234, new TestHandler(1234, reports, 'th1')); + const dispo = parser.registerHandler(1234, new TestHandler(1234, reports, 'th2', true)); dispo.dispose(); parser.start(); let data = toUtf32('1234;Here comes'); @@ -170,7 +171,7 @@ describe('OscParser', () => { }); describe('OscHandlerFactory', () => { it('should be called once on end(true)', () => { - parser.setHandler(1234, new OscHandler(data => reports.push([1234, data]))); + parser.registerHandler(1234, new OscHandler(data => { reports.push([1234, data]); return true; })); parser.start(); let data = toUtf32('1234;Here comes'); parser.put(data, 0, data.length); @@ -180,7 +181,7 @@ describe('OscParser', () => { assert.deepEqual(reports, [[1234, 'Here comes the mouse!']]); }); it('should not be called on end(false)', () => { - parser.setHandler(1234, new OscHandler(data => reports.push([1234, data]))); + parser.registerHandler(1234, new OscHandler(data => { reports.push([1234, data]); return true; })); parser.start(); let data = toUtf32('1234;Here comes'); parser.put(data, 0, data.length); @@ -190,8 +191,8 @@ describe('OscParser', () => { assert.deepEqual(reports, []); }); it('should be disposable', () => { - parser.setHandler(1234, new OscHandler(data => reports.push(['one', data]))); - const dispo = parser.addHandler(1234, new OscHandler(data => reports.push(['two', data]))); + parser.registerHandler(1234, new OscHandler(data => { reports.push(['one', data]); return true; })); + const dispo = parser.registerHandler(1234, new OscHandler(data => { reports.push(['two', data]); return true; })); parser.start(); let data = toUtf32('1234;Here comes'); parser.put(data, 0, data.length); @@ -209,8 +210,8 @@ describe('OscParser', () => { assert.deepEqual(reports, [['two', 'Here comes the mouse!'], ['one', 'some other data']]); }); it('should respect return false', () => { - parser.setHandler(1234, new OscHandler(data => reports.push(['one', data]))); - parser.addHandler(1234, new OscHandler(data => { reports.push(['two', data]); return false; })); + parser.registerHandler(1234, new OscHandler(data => { reports.push(['one', data]); return true; })); + parser.registerHandler(1234, new OscHandler(data => { reports.push(['two', data]); return false; })); parser.start(); let data = toUtf32('1234;Here comes'); parser.put(data, 0, data.length); @@ -221,7 +222,7 @@ describe('OscParser', () => { }); it('should work up to payload limit', function(): void { this.timeout(10000); - parser.setHandler(1234, new OscHandler(data => reports.push([1234, data]))); + parser.registerHandler(1234, new OscHandler(data => { reports.push([1234, data]); return true; })); parser.start(); let data = toUtf32('1234;'); parser.put(data, 0, data.length); @@ -234,7 +235,7 @@ describe('OscParser', () => { }); it('should abort for payload limit +1', function(): void { this.timeout(10000); - parser.setHandler(1234, new OscHandler(data => reports.push([1234, data]))); + parser.registerHandler(1234, new OscHandler(data => { reports.push([1234, data]); return true; })); parser.start(); let data = toUtf32('1234;'); parser.put(data, 0, data.length); @@ -249,3 +250,204 @@ describe('OscParser', () => { }); }); }); + + +class TestHandlerAsync implements IOscHandler { + constructor(public id: number, public output: any[], public msg: string, public returnFalse: boolean = false) {} + public start(): void { + this.output.push([this.msg, this.id, 'START']); + } + public put(data: Uint32Array, start: number, end: number): void { + this.output.push([this.msg, this.id, 'PUT', utf32ToString(data, start, end)]); + } + public async end(success: boolean): Promise { + await new Promise(res => setTimeout(res, 20)); + this.output.push([this.msg, this.id, 'END', success]); + if (this.returnFalse) { + return false; + } + return true; + } +} +async function endP(parser: OscParser, success: boolean): Promise { + let result: void | Promise; + let prev: boolean | undefined; + while (result = parser.end(success, prev)) { + prev = await result; + } +} + +describe('OscParser - async tests', () => { + let parser: OscParser; + let reports: any[] = []; + beforeEach(() => { + reports = []; + parser = new OscParser(); + parser.setHandlerFallback((id, action, data) => { + reports.push([id, action, data]); + }); + }); + describe('sync and async mixed', () => { + describe('sync | async | sync', () => { + it('first should run, cleanup action for others', async () => { + parser.registerHandler(1234, new TestHandler(1234, reports, 's1')); + parser.registerHandler(1234, new TestHandlerAsync(1234, reports, 'a1')); + parser.registerHandler(1234, new TestHandler(1234, reports, 's2')); + parser.start(); + let data = toUtf32('1234;Here comes'); + parser.put(data, 0, data.length); + data = toUtf32('the mouse!'); + parser.put(data, 0, data.length); + await endP(parser, true); + assert.deepEqual(reports, [ + // messages from TestHandler + ['s2', 1234, 'START'], + ['a1', 1234, 'START'], + ['s1', 1234, 'START'], + ['s2', 1234, 'PUT', 'Here comes'], + ['a1', 1234, 'PUT', 'Here comes'], + ['s1', 1234, 'PUT', 'Here comes'], + ['s2', 1234, 'PUT', 'the mouse!'], + ['a1', 1234, 'PUT', 'the mouse!'], + ['s1', 1234, 'PUT', 'the mouse!'], + ['s2', 1234, 'END', true], + ['a1', 1234, 'END', false], + ['s1', 1234, 'END', false] + ]); + }); + it('all should run', async () => { + parser.registerHandler(1234, new TestHandler(1234, reports, 's1', true)); + parser.registerHandler(1234, new TestHandlerAsync(1234, reports, 'a1', true)); + parser.registerHandler(1234, new TestHandler(1234, reports, 's2', true)); + parser.start(); + let data = toUtf32('1234;Here comes'); + parser.put(data, 0, data.length); + data = toUtf32('the mouse!'); + parser.put(data, 0, data.length); + await endP(parser, true); + assert.deepEqual(reports, [ + // messages from TestHandler + ['s2', 1234, 'START'], + ['a1', 1234, 'START'], + ['s1', 1234, 'START'], + ['s2', 1234, 'PUT', 'Here comes'], + ['a1', 1234, 'PUT', 'Here comes'], + ['s1', 1234, 'PUT', 'Here comes'], + ['s2', 1234, 'PUT', 'the mouse!'], + ['a1', 1234, 'PUT', 'the mouse!'], + ['s1', 1234, 'PUT', 'the mouse!'], + ['s2', 1234, 'END', true], + ['a1', 1234, 'END', true], + ['s1', 1234, 'END', true] + ]); + }); + }); + describe('async | sync | async', () => { + it('first should run, cleanup action for others', async () => { + parser.registerHandler(1234, new TestHandlerAsync(1234, reports, 's1')); + parser.registerHandler(1234, new TestHandler(1234, reports, 'a1')); + parser.registerHandler(1234, new TestHandlerAsync(1234, reports, 's2')); + parser.start(); + let data = toUtf32('1234;Here comes'); + parser.put(data, 0, data.length); + data = toUtf32('the mouse!'); + parser.put(data, 0, data.length); + await endP(parser, true); + assert.deepEqual(reports, [ + // messages from TestHandler + ['s2', 1234, 'START'], + ['a1', 1234, 'START'], + ['s1', 1234, 'START'], + ['s2', 1234, 'PUT', 'Here comes'], + ['a1', 1234, 'PUT', 'Here comes'], + ['s1', 1234, 'PUT', 'Here comes'], + ['s2', 1234, 'PUT', 'the mouse!'], + ['a1', 1234, 'PUT', 'the mouse!'], + ['s1', 1234, 'PUT', 'the mouse!'], + ['s2', 1234, 'END', true], + ['a1', 1234, 'END', false], + ['s1', 1234, 'END', false] + ]); + }); + it('all should run', async () => { + parser.registerHandler(1234, new TestHandlerAsync(1234, reports, 's1', true)); + parser.registerHandler(1234, new TestHandler(1234, reports, 'a1', true)); + parser.registerHandler(1234, new TestHandlerAsync(1234, reports, 's2', true)); + parser.start(); + let data = toUtf32('1234;Here comes'); + parser.put(data, 0, data.length); + data = toUtf32('the mouse!'); + parser.put(data, 0, data.length); + await endP(parser, true); + assert.deepEqual(reports, [ + // messages from TestHandler + ['s2', 1234, 'START'], + ['a1', 1234, 'START'], + ['s1', 1234, 'START'], + ['s2', 1234, 'PUT', 'Here comes'], + ['a1', 1234, 'PUT', 'Here comes'], + ['s1', 1234, 'PUT', 'Here comes'], + ['s2', 1234, 'PUT', 'the mouse!'], + ['a1', 1234, 'PUT', 'the mouse!'], + ['s1', 1234, 'PUT', 'the mouse!'], + ['s2', 1234, 'END', true], + ['a1', 1234, 'END', true], + ['s1', 1234, 'END', true] + ]); + }); + }); + describe('OscHandlerFactory', () => { + it('should be called once on end(true)', async () => { + parser.registerHandler(1234, new OscHandler(async data => { reports.push([1234, data]); return true; })); + parser.start(); + let data = toUtf32('1234;Here comes'); + parser.put(data, 0, data.length); + data = toUtf32(' the mouse!'); + parser.put(data, 0, data.length); + parser.end(true); + await endP(parser, true); + assert.deepEqual(reports, [[1234, 'Here comes the mouse!']]); + }); + it('should not be called on end(false)', async () => { + parser.registerHandler(1234, new OscHandler(async data => { reports.push([1234, data]); return true; })); + parser.start(); + let data = toUtf32('1234;Here comes'); + parser.put(data, 0, data.length); + data = toUtf32(' the mouse!'); + parser.put(data, 0, data.length); + await endP(parser, false); + assert.deepEqual(reports, []); + }); + it('should be disposable', async () => { + parser.registerHandler(1234, new OscHandler(async data => { reports.push(['one', data]); return true; })); + const dispo = parser.registerHandler(1234, new OscHandler(async data => { reports.push(['two', data]); return true; })); + parser.start(); + let data = toUtf32('1234;Here comes'); + parser.put(data, 0, data.length); + data = toUtf32(' the mouse!'); + parser.put(data, 0, data.length); + await endP(parser, true); + assert.deepEqual(reports, [['two', 'Here comes the mouse!']]); + dispo.dispose(); + parser.start(); + data = toUtf32('1234;some other'); + parser.put(data, 0, data.length); + data = toUtf32(' data'); + parser.put(data, 0, data.length); + await endP(parser, true); + assert.deepEqual(reports, [['two', 'Here comes the mouse!'], ['one', 'some other data']]); + }); + it('should respect return false', async () => { + parser.registerHandler(1234, new OscHandler(async data => { reports.push(['one', data]); return true; })); + parser.registerHandler(1234, new OscHandler(async data => { reports.push(['two', data]); return false; })); + parser.start(); + let data = toUtf32('1234;Here comes'); + parser.put(data, 0, data.length); + data = toUtf32(' the mouse!'); + parser.put(data, 0, data.length); + await endP(parser, true); + assert.deepEqual(reports, [['two', 'Here comes the mouse!'], ['one', 'Here comes the mouse!']]); + }); + }); + }); +}); diff --git a/src/common/parser/OscParser.ts b/src/common/parser/OscParser.ts index e8c5a801..32710aed 100644 --- a/src/common/parser/OscParser.ts +++ b/src/common/parser/OscParser.ts @@ -3,19 +3,26 @@ * @license MIT */ -import { IOscHandler, IHandlerCollection, OscFallbackHandlerType, IOscParser } from 'common/parser/Types'; +import { IOscHandler, IHandlerCollection, OscFallbackHandlerType, IOscParser, ISubParserStackState } from 'common/parser/Types'; import { OscState, PAYLOAD_LIMIT } from 'common/parser/Constants'; import { utf32ToString } from 'common/input/TextDecoder'; import { IDisposable } from 'common/Types'; +const EMPTY_HANDLERS: IOscHandler[] = []; export class OscParser implements IOscParser { private _state = OscState.START; + private _active = EMPTY_HANDLERS; private _id = -1; private _handlers: IHandlerCollection = Object.create(null); private _handlerFb: OscFallbackHandlerType = () => { }; + private _stack: ISubParserStackState = { + paused: false, + loopPosition: 0, + fallThrough: false + }; - public addHandler(ident: number, handler: IOscHandler): IDisposable { + public registerHandler(ident: number, handler: IOscHandler): IDisposable { if (this._handlers[ident] === undefined) { this._handlers[ident] = []; } @@ -30,9 +37,6 @@ export class OscParser implements IOscParser { } }; } - public setHandler(ident: number, handler: IOscHandler): void { - this._handlers[ident] = [handler]; - } public clearHandler(ident: number): void { if (this._handlers[ident]) delete this._handlers[ident]; } @@ -42,58 +46,40 @@ export class OscParser implements IOscParser { public dispose(): void { this._handlers = Object.create(null); - this._handlerFb = () => {}; + this._handlerFb = () => { }; + this._active = EMPTY_HANDLERS; } public reset(): void { - // cleanup handlers if payload was already sent + // force cleanup handlers if payload was already sent if (this._state === OscState.PAYLOAD) { - this.end(false); + for (let j = this._stack.paused ? this._stack.loopPosition - 1 : this._active.length - 1; j >= 0; --j) { + this._active[j].end(false); + } } + this._stack.paused = false; + this._active = EMPTY_HANDLERS; this._id = -1; this._state = OscState.START; } private _start(): void { - const handlers = this._handlers[this._id]; - if (!handlers) { + this._active = this._handlers[this._id] || EMPTY_HANDLERS; + if (!this._active.length) { this._handlerFb(this._id, 'START'); } else { - for (let j = handlers.length - 1; j >= 0; j--) { - handlers[j].start(); + for (let j = this._active.length - 1; j >= 0; j--) { + this._active[j].start(); } } } private _put(data: Uint32Array, start: number, end: number): void { - const handlers = this._handlers[this._id]; - if (!handlers) { + if (!this._active.length) { this._handlerFb(this._id, 'PUT', utf32ToString(data, start, end)); } else { - for (let j = handlers.length - 1; j >= 0; j--) { - handlers[j].put(data, start, end); - } - } - } - - private _end(success: boolean): void { - // other than the old code we always have to call .end - // to keep the bubbling we use `success` to indicate - // whether a handler should execute - const handlers = this._handlers[this._id]; - if (!handlers) { - this._handlerFb(this._id, 'END', success); - } else { - let j = handlers.length - 1; - for (; j >= 0; j--) { - if (handlers[j].end(success) !== false) { - break; - } - } - j--; - // cleanup left over handlers - for (; j >= 0; j--) { - handlers[j].end(false); + for (let j = this._active.length - 1; j >= 0; j--) { + this._active[j].put(data, start, end); } } } @@ -101,7 +87,6 @@ export class OscParser implements IOscParser { public start(): void { // always reset leftover handlers this.reset(); - this._id = -1; this._state = OscState.ID; } @@ -144,7 +129,7 @@ export class OscParser implements IOscParser { * Whether the OSC got aborted or finished normally * is indicated by `success`. */ - public end(success: boolean): void { + public end(success: boolean, promiseResult: boolean = true): void | Promise { if (this._state === OscState.START) { return; } @@ -156,8 +141,49 @@ export class OscParser implements IOscParser { if (this._state === OscState.ID) { this._start(); } - this._end(success); + + if (!this._active.length) { + this._handlerFb(this._id, 'END', success); + } else { + let handlerResult: boolean | Promise = false; + let j = this._active.length - 1; + let fallThrough = false; + if (this._stack.paused) { + j = this._stack.loopPosition - 1; + handlerResult = promiseResult; + fallThrough = this._stack.fallThrough; + this._stack.paused = false; + } + if (!fallThrough && handlerResult === false) { + for (; j >= 0; j--) { + handlerResult = this._active[j].end(success); + if (handlerResult === true) { + break; + } else if (handlerResult instanceof Promise) { + this._stack.paused = true; + this._stack.loopPosition = j; + this._stack.fallThrough = false; + return handlerResult; + } + } + j--; + } + // cleanup left over handlers + // we always have to call .end for proper cleanup, + // here we use `success` to indicate whether a handler should execute + for (; j >= 0; j--) { + handlerResult = this._active[j].end(false); + if (handlerResult instanceof Promise) { + this._stack.paused = true; + this._stack.loopPosition = j; + this._stack.fallThrough = true; + return handlerResult; + } + } + } + } + this._active = EMPTY_HANDLERS; this._id = -1; this._state = OscState.START; } @@ -171,7 +197,7 @@ export class OscHandler implements IOscHandler { private _data = ''; private _hitLimit: boolean = false; - constructor(private _handler: (data: string) => any) {} + constructor(private _handler: (data: string) => boolean | Promise) { } public start(): void { this._data = ''; @@ -189,12 +215,21 @@ export class OscHandler implements IOscHandler { } } - public end(success: boolean): any { - let ret; + public end(success: boolean): boolean | Promise { + let ret: boolean | Promise = false; if (this._hitLimit) { ret = false; } else if (success) { ret = this._handler(this._data); + if (ret instanceof Promise) { + // need to hold data until `ret` got resolved + // dont care for errors, data will be freed anyway on next start + return ret.then(res => { + this._data = ''; + this._hitLimit = false; + return res; + }); + } } this._data = ''; this._hitLimit = false; diff --git a/src/common/parser/Types.d.ts b/src/common/parser/Types.d.ts index e2fac89f..3a621eab 100644 --- a/src/common/parser/Types.d.ts +++ b/src/common/parser/Types.d.ts @@ -6,6 +6,7 @@ import { IDisposable } from 'common/Types'; import { ParserState } from 'common/parser/Constants'; + /** sequence params serialized to js arrays */ export type ParamsArray = (number | number[])[]; @@ -69,7 +70,7 @@ export interface IParsingState { * CSI handler types. * Note: `params` is borrowed. */ -export type CsiHandlerType = (params: IParams) => boolean | void; +export type CsiHandlerType = (params: IParams) => boolean | Promise; export type CsiFallbackHandlerType = (ident: number, params: IParams) => void; /** @@ -93,20 +94,20 @@ export interface IDcsHandler { * execution of the command should depend on `success`. * To save memory also cleanup data structures here. */ - unhook(success: boolean): void | boolean; + unhook(success: boolean): boolean | Promise; } export type DcsFallbackHandlerType = (ident: number, action: 'HOOK' | 'PUT' | 'UNHOOK', payload?: any) => void; /** * ESC handler types. */ -export type EscHandlerType = () => boolean | void; +export type EscHandlerType = () => boolean | Promise; export type EscFallbackHandlerType = (identifier: number) => void; /** * EXECUTE handler types. */ -export type ExecuteHandlerType = () => boolean | void; +export type ExecuteHandlerType = () => boolean; export type ExecuteFallbackHandlerType = (ident: number) => void; /** @@ -129,7 +130,7 @@ export interface IOscHandler { * execution of the command should depend on `success`. * To save memory also cleanup data structures here. */ - end(success: boolean): void | boolean; + end(success: boolean): boolean | Promise; } export type OscFallbackHandlerType = (ident: number, action: 'START' | 'PUT' | 'END', payload?: any) => void; @@ -160,7 +161,7 @@ export interface IEscapeSequenceParser extends IDisposable { * Parse UTF32 codepoints in `data` up to `length`. * @param data The data to parse. */ - parse(data: Uint32Array, length: number): void; + parse(data: Uint32Array, length: number, promiseResult?: boolean): void | Promise; /** * Get string from numercial function identifier `ident`. @@ -174,29 +175,25 @@ export interface IEscapeSequenceParser extends IDisposable { setPrintHandler(handler: PrintHandlerType): void; clearPrintHandler(): void; - setEscHandler(id: IFunctionIdentifier, handler: EscHandlerType): void; + registerEscHandler(id: IFunctionIdentifier, handler: EscHandlerType): IDisposable; clearEscHandler(id: IFunctionIdentifier): void; setEscHandlerFallback(handler: EscFallbackHandlerType): void; - addEscHandler(id: IFunctionIdentifier, handler: EscHandlerType): IDisposable; setExecuteHandler(flag: string, handler: ExecuteHandlerType): void; clearExecuteHandler(flag: string): void; setExecuteHandlerFallback(handler: ExecuteFallbackHandlerType): void; - setCsiHandler(id: IFunctionIdentifier, handler: CsiHandlerType): void; + registerCsiHandler(id: IFunctionIdentifier, handler: CsiHandlerType): IDisposable; clearCsiHandler(id: IFunctionIdentifier): void; setCsiHandlerFallback(callback: CsiFallbackHandlerType): void; - addCsiHandler(id: IFunctionIdentifier, handler: CsiHandlerType): IDisposable; - setDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): void; + registerDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): IDisposable; clearDcsHandler(id: IFunctionIdentifier): void; setDcsHandlerFallback(handler: DcsFallbackHandlerType): void; - addDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): IDisposable; - setOscHandler(ident: number, handler: IOscHandler): void; + registerOscHandler(ident: number, handler: IOscHandler): IDisposable; clearOscHandler(ident: number): void; setOscHandlerFallback(handler: OscFallbackHandlerType): void; - addOscHandler(ident: number, handler: IOscHandler): IDisposable; setErrorHandler(handler: (state: IParsingState) => IParsingState): void; clearErrorHandler(): void; @@ -209,8 +206,7 @@ export interface IEscapeSequenceParser extends IDisposable { */ export interface ISubParser extends IDisposable { reset(): void; - addHandler(ident: number, handler: T): IDisposable; - setHandler(ident: number, handler: T): void; + registerHandler(ident: number, handler: T): IDisposable; clearHandler(ident: number): void; setHandlerFallback(handler: U): void; put(data: Uint32Array, start: number, end: number): void; @@ -218,12 +214,12 @@ export interface ISubParser extends IDisposable { export interface IOscParser extends ISubParser { start(): void; - end(success: boolean): void; + end(success: boolean, promiseResult?: boolean): void | Promise; } export interface IDcsParser extends ISubParser { hook(ident: number, params: IParams): void; - unhook(success: boolean): void; + unhook(success: boolean, promiseResult?: boolean): void | Promise; } /** @@ -242,3 +238,37 @@ export interface IFunctionIdentifier { export interface IHandlerCollection { [key: string]: T[]; } + +/** + * Types for async parser support. + */ + +// type of saved stack state in parser +export const enum ParserStackType { + NONE = 0, + FAIL, + RESET, + CSI, + ESC, + OSC, + DCS +} + +// aggregate of resumable handler lists +export type ResumableHandlersType = CsiHandlerType[] | EscHandlerType[]; + +// saved stack state of the parser +export interface IParserStackState { + state: ParserStackType; + handlers: ResumableHandlersType; + handlerPos: number; + transition: number; + chunkPos: number; +} + +// saved stack state of subparser (OSC and DCS) +export interface ISubParserStackState { + paused: boolean; + loopPosition: number; + fallThrough: boolean; +} diff --git a/src/common/public/BufferApiView.ts b/src/common/public/BufferApiView.ts new file mode 100644 index 00000000..ca9ef2d8 --- /dev/null +++ b/src/common/public/BufferApiView.ts @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2021 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi } from 'xterm'; +import { IBuffer } from 'common/buffer/Types'; +import { BufferLineApiView } from 'common/public/BufferLineApiView'; +import { CellData } from 'common/buffer/CellData'; + +export class BufferApiView implements IBufferApi { + constructor( + private _buffer: IBuffer, + public readonly type: 'normal' | 'alternate' + ) { } + + public init(buffer: IBuffer): BufferApiView { + this._buffer = buffer; + return this; + } + + public get cursorY(): number { return this._buffer.y; } + public get cursorX(): number { return this._buffer.x; } + public get viewportY(): number { return this._buffer.ydisp; } + public get baseY(): number { return this._buffer.ybase; } + public get length(): number { return this._buffer.lines.length; } + public getLine(y: number): IBufferLineApi | undefined { + const line = this._buffer.lines.get(y); + if (!line) { + return undefined; + } + return new BufferLineApiView(line); + } + public getNullCell(): IBufferCellApi { return new CellData(); } +} diff --git a/src/common/public/BufferLineApiView.ts b/src/common/public/BufferLineApiView.ts new file mode 100644 index 00000000..60375015 --- /dev/null +++ b/src/common/public/BufferLineApiView.ts @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2021 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { CellData } from 'common/buffer/CellData'; +import { IBufferLine, ICellData } from 'common/Types'; +import { IBufferCell as IBufferCellApi, IBufferLine as IBufferLineApi } from 'xterm'; + +export class BufferLineApiView implements IBufferLineApi { + constructor(private _line: IBufferLine) { } + + public get isWrapped(): boolean { return this._line.isWrapped; } + public get length(): number { return this._line.length; } + public getCell(x: number, cell?: IBufferCellApi): IBufferCellApi | undefined { + if (x < 0 || x >= this._line.length) { + return undefined; + } + + if (cell) { + this._line.loadCell(x, cell as ICellData); + return cell; + } + return this._line.loadCell(x, new CellData()); + } + public translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string { + return this._line.translateToString(trimRight, startColumn, endColumn); + } +} diff --git a/src/common/public/BufferNamespaceApi.ts b/src/common/public/BufferNamespaceApi.ts new file mode 100644 index 00000000..d86f6bf5 --- /dev/null +++ b/src/common/public/BufferNamespaceApi.ts @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2021 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IBuffer as IBufferApi, IBufferNamespace as IBufferNamespaceApi } from 'xterm'; +import { BufferApiView } from 'common/public/BufferApiView'; +import { IEvent, EventEmitter } from 'common/EventEmitter'; +import { ICoreTerminal } from 'common/Types'; + +export class BufferNamespaceApi implements IBufferNamespaceApi { + private _normal: BufferApiView; + private _alternate: BufferApiView; + private _onBufferChange = new EventEmitter(); + public get onBufferChange(): IEvent { return this._onBufferChange.event; } + + constructor(private _core: ICoreTerminal) { + this._normal = new BufferApiView(this._core.buffers.normal, 'normal'); + this._alternate = new BufferApiView(this._core.buffers.alt, 'alternate'); + this._core.buffers.onBufferActivate(() => this._onBufferChange.fire(this.active)); + } + public get active(): IBufferApi { + if (this._core.buffers.active === this._core.buffers.normal) { return this.normal; } + if (this._core.buffers.active === this._core.buffers.alt) { return this.alternate; } + throw new Error('Active buffer is neither normal nor alternate'); + } + public get normal(): IBufferApi { + return this._normal.init(this._core.buffers.normal); + } + public get alternate(): IBufferApi { + return this._alternate.init(this._core.buffers.alt); + } +} diff --git a/src/common/public/ParserApi.ts b/src/common/public/ParserApi.ts new file mode 100644 index 00000000..67df4be5 --- /dev/null +++ b/src/common/public/ParserApi.ts @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2021 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IParams } from 'common/parser/Types'; +import { IDisposable, IFunctionIdentifier, IParser } from 'xterm'; +import { ICoreTerminal } from 'common/Types'; + +export class ParserApi implements IParser { + constructor(private _core: ICoreTerminal) { } + + public registerCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean | Promise): IDisposable { + return this._core.registerCsiHandler(id, (params: IParams) => callback(params.toArray())); + } + public addCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean | Promise): IDisposable { + return this.registerCsiHandler(id, callback); + } + public registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean | Promise): IDisposable { + return this._core.registerDcsHandler(id, (data: string, params: IParams) => callback(data, params.toArray())); + } + public addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean | Promise): IDisposable { + return this.registerDcsHandler(id, callback); + } + public registerEscHandler(id: IFunctionIdentifier, handler: () => boolean | Promise): IDisposable { + return this._core.registerEscHandler(id, handler); + } + public addEscHandler(id: IFunctionIdentifier, handler: () => boolean | Promise): IDisposable { + return this.registerEscHandler(id, handler); + } + public registerOscHandler(ident: number, callback: (data: string) => boolean | Promise): IDisposable { + return this._core.registerOscHandler(ident, callback); + } + public addOscHandler(ident: number, callback: (data: string) => boolean | Promise): IDisposable { + return this.registerOscHandler(ident, callback); + } +} diff --git a/src/common/public/UnicodeApi.ts b/src/common/public/UnicodeApi.ts new file mode 100644 index 00000000..8a669a05 --- /dev/null +++ b/src/common/public/UnicodeApi.ts @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2021 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { ICoreTerminal } from 'common/Types'; +import { IUnicodeHandling, IUnicodeVersionProvider } from 'xterm'; + +export class UnicodeApi implements IUnicodeHandling { + constructor(private _core: ICoreTerminal) { } + + public register(provider: IUnicodeVersionProvider): void { + this._core.unicodeService.register(provider); + } + + public get versions(): string[] { + return this._core.unicodeService.versions; + } + + public get activeVersion(): string { + return this._core.unicodeService.activeVersion; + } + + public set activeVersion(version: string) { + this._core.unicodeService.activeVersion = version; + } +} diff --git a/src/common/services/BufferService.ts b/src/common/services/BufferService.ts index 47e54729..99594d22 100644 --- a/src/common/services/BufferService.ts +++ b/src/common/services/BufferService.ts @@ -8,6 +8,7 @@ import { BufferSet } from 'common/buffer/BufferSet'; import { IBufferSet, IBuffer } from 'common/buffer/Types'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; +import { IAttributeData, IBufferLine, ScrollSource } from 'common/Types'; export const MINIMUM_COLS = 2; // Less than 2 can mess with wide chars export const MINIMUM_ROWS = 1; @@ -23,9 +24,14 @@ export class BufferService extends Disposable implements IBufferService { private _onResize = new EventEmitter<{ cols: number, rows: number }>(); public get onResize(): IEvent<{ cols: number, rows: number }> { return this._onResize.event; } + private _onScroll = new EventEmitter(); + public get onScroll(): IEvent { return this._onScroll.event; } public get buffer(): IBuffer { return this.buffers.active; } + /** An IBufferline to clone/copy from for new blank lines */ + private _cachedBlankLine: IBufferLine | undefined; + constructor( @IOptionsService private _optionsService: IOptionsService ) { @@ -52,4 +58,128 @@ export class BufferService extends Disposable implements IBufferService { this.buffers.reset(); this.isUserScrolling = false; } + + /** + * Scroll the terminal down 1 row, creating a blank line. + * @param isWrapped Whether the new line is wrapped from the previous line. + */ + public scroll(eraseAttr: IAttributeData, isWrapped: boolean = false): void { + const buffer = this.buffer; + + let newLine: IBufferLine | undefined; + newLine = this._cachedBlankLine; + if (!newLine || newLine.length !== this.cols || newLine.getFg(0) !== eraseAttr.fg || newLine.getBg(0) !== eraseAttr.bg) { + newLine = buffer.getBlankLine(eraseAttr, isWrapped); + this._cachedBlankLine = newLine; + } + newLine.isWrapped = isWrapped; + + const topRow = buffer.ybase + buffer.scrollTop; + const bottomRow = buffer.ybase + buffer.scrollBottom; + + if (buffer.scrollTop === 0) { + // Determine whether the buffer is going to be trimmed after insertion. + const willBufferBeTrimmed = buffer.lines.isFull; + + // Insert the line using the fastest method + if (bottomRow === buffer.lines.length - 1) { + if (willBufferBeTrimmed) { + buffer.lines.recycle().copyFrom(newLine); + } else { + buffer.lines.push(newLine.clone()); + } + } else { + buffer.lines.splice(bottomRow + 1, 0, newLine.clone()); + } + + // Only adjust ybase and ydisp when the buffer is not trimmed + if (!willBufferBeTrimmed) { + buffer.ybase++; + // Only scroll the ydisp with ybase if the user has not scrolled up + if (!this.isUserScrolling) { + buffer.ydisp++; + } + } else { + // When the buffer is full and the user has scrolled up, keep the text + // stable unless ydisp is right at the top + if (this.isUserScrolling) { + buffer.ydisp = Math.max(buffer.ydisp - 1, 0); + } + } + } else { + // scrollTop is non-zero which means no line will be going to the + // scrollback, instead we can just shift them in-place. + const scrollRegionHeight = bottomRow - topRow + 1 /* as it's zero-based */; + buffer.lines.shiftElements(topRow + 1, scrollRegionHeight - 1, -1); + buffer.lines.set(bottomRow, newLine.clone()); + } + + // Move the viewport to the bottom of the buffer unless the user is + // scrolling. + if (!this.isUserScrolling) { + buffer.ydisp = buffer.ybase; + } + + this._onScroll.fire(buffer.ydisp); + } + + /** + * Scroll the display of the terminal + * @param disp The number of lines to scroll down (negative scroll up). + * @param suppressScrollEvent Don't emit the scroll event as scrollLines. This is used + * to avoid unwanted events being handled by the viewport when the event was triggered from the + * viewport originally. + */ + public scrollLines(disp: number, suppressScrollEvent?: boolean, source?: ScrollSource): void { + const buffer = this.buffer; + if (disp < 0) { + if (buffer.ydisp === 0) { + return; + } + this.isUserScrolling = true; + } else if (disp + buffer.ydisp >= buffer.ybase) { + this.isUserScrolling = false; + } + + const oldYdisp = buffer.ydisp; + buffer.ydisp = Math.max(Math.min(buffer.ydisp + disp, buffer.ybase), 0); + + // No change occurred, don't trigger scroll/refresh + if (oldYdisp === buffer.ydisp) { + return; + } + + if (!suppressScrollEvent) { + this._onScroll.fire(buffer.ydisp); + } + } + + /** + * Scroll the display of the terminal by a number of pages. + * @param pageCount The number of pages to scroll (negative scrolls up). + */ + public scrollPages(pageCount: number): void { + this.scrollLines(pageCount * (this.rows - 1)); + } + + /** + * Scrolls the display of the terminal to the top. + */ + public scrollToTop(): void { + this.scrollLines(-this.buffer.ydisp); + } + + /** + * Scrolls the display of the terminal to the bottom. + */ + public scrollToBottom(): void { + this.scrollLines(this.buffer.ybase - this.buffer.ydisp); + } + + public scrollToLine(line: number): void { + const scrollAmount = line - this.buffer.ydisp; + if (scrollAmount !== 0) { + this.scrollLines(scrollAmount); + } + } } diff --git a/src/common/services/InstantiationService.ts b/src/common/services/InstantiationService.ts index e5727fa6..8280948a 100644 --- a/src/common/services/InstantiationService.ts +++ b/src/common/services/InstantiationService.ts @@ -42,6 +42,8 @@ export class ServiceCollection { } export class InstantiationService implements IInstantiationService { + public serviceBrand: undefined; + private readonly _services: ServiceCollection = new ServiceCollection(); constructor() { diff --git a/src/common/services/LogService.ts b/src/common/services/LogService.ts index 3c0ccaf1..de4e18f8 100644 --- a/src/common/services/LogService.ts +++ b/src/common/services/LogService.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { ILogService, IOptionsService } from 'common/services/Services'; +import { ILogService, IOptionsService, LogLevelEnum } from 'common/services/Services'; type LogType = (message?: any, ...optionalParams: any[]) => void; @@ -19,21 +19,12 @@ interface IConsole { // module doesn't depend on them so we need to explicitly declare it. declare const console: IConsole; - -export enum LogLevel { - DEBUG = 0, - INFO = 1, - WARN = 2, - ERROR = 3, - OFF = 4 -} - -const optionsKeyToLogLevel: { [key: string]: LogLevel } = { - debug: LogLevel.DEBUG, - info: LogLevel.INFO, - warn: LogLevel.WARN, - error: LogLevel.ERROR, - off: LogLevel.OFF +const optionsKeyToLogLevel: { [key: string]: LogLevelEnum } = { + debug: LogLevelEnum.DEBUG, + info: LogLevelEnum.INFO, + warn: LogLevelEnum.WARN, + error: LogLevelEnum.ERROR, + off: LogLevelEnum.OFF }; const LOG_PREFIX = 'xterm.js: '; @@ -41,7 +32,7 @@ const LOG_PREFIX = 'xterm.js: '; export class LogService implements ILogService { public serviceBrand: any; - private _logLevel!: LogLevel; + public logLevel: LogLevelEnum = LogLevelEnum.OFF; constructor( @IOptionsService private readonly _optionsService: IOptionsService @@ -55,7 +46,7 @@ export class LogService implements ILogService { } private _updateLogLevel(): void { - this._logLevel = optionsKeyToLogLevel[this._optionsService.options.logLevel]; + this.logLevel = optionsKeyToLogLevel[this._optionsService.options.logLevel]; } private _evalLazyOptionalParams(optionalParams: any[]): void { @@ -72,25 +63,25 @@ export class LogService implements ILogService { } public debug(message: string, ...optionalParams: any[]): void { - if (this._logLevel <= LogLevel.DEBUG) { + if (this.logLevel <= LogLevelEnum.DEBUG) { this._log(console.log, message, optionalParams); } } public info(message: string, ...optionalParams: any[]): void { - if (this._logLevel <= LogLevel.INFO) { + if (this.logLevel <= LogLevelEnum.INFO) { this._log(console.info, message, optionalParams); } } public warn(message: string, ...optionalParams: any[]): void { - if (this._logLevel <= LogLevel.WARN) { + if (this.logLevel <= LogLevelEnum.WARN) { this._log(console.warn, message, optionalParams); } } public error(message: string, ...optionalParams: any[]): void { - if (this._logLevel <= LogLevel.ERROR) { + if (this.logLevel <= LogLevelEnum.ERROR) { this._log(console.error, message, optionalParams); } } diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index c733e73e..ce297322 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -5,7 +5,7 @@ import { IEvent } from 'common/EventEmitter'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; -import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset, IWindowOptions, IModes } from 'common/Types'; +import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset, IWindowOptions, IModes, IAttributeData, ScrollSource } from 'common/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; export const IBufferService = createDecorator('BufferService'); @@ -17,9 +17,14 @@ export interface IBufferService { readonly buffer: IBuffer; readonly buffers: IBufferSet; isUserScrolling: boolean; - onResize: IEvent<{ cols: number, rows: number }>; - + onScroll: IEvent; + scroll(eraseAttr: IAttributeData, isWrapped?: boolean): void; + scrollToBottom(): void; + scrollToTop(): void; + scrollToLine(line: number): void; + scrollLines(disp: number, suppressScrollEvent?: boolean, source?: ScrollSource): void; + scrollPages(pageCount: number): void; resize(cols: number, rows: number): void; reset(): void; } @@ -152,6 +157,8 @@ type GetLeadingNonServiceArgs = export const IInstantiationService = createDecorator('InstantiationService'); export interface IInstantiationService { + serviceBrand: undefined; + setService(id: IServiceIdentifier, instance: T): void; getService(id: IServiceIdentifier): T | undefined; createInstance any, R extends InstanceType>(t: Ctor, ...args: GetLeadingNonServiceArgs>): R; @@ -161,6 +168,8 @@ export const ILogService = createDecorator('LogService'); export interface ILogService { serviceBrand: undefined; + logLevel: LogLevelEnum; + debug(message: any, ...optionalParams: any[]): void; info(message: any, ...optionalParams: any[]): void; warn(message: any, ...optionalParams: any[]): void; @@ -181,6 +190,13 @@ export interface IOptionsService { export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | number; export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'off'; +export enum LogLevelEnum { + DEBUG = 0, + INFO = 1, + WARN = 2, + ERROR = 3, + OFF = 4 +} export type RendererType = 'dom' | 'canvas'; export interface IPartialTerminalOptions { diff --git a/test/api/CharWidth.api.ts b/test/api/CharWidth.api.ts index 01d69861..d7cea109 100644 --- a/test/api/CharWidth.api.ts +++ b/test/api/CharWidth.api.ts @@ -6,7 +6,7 @@ import { pollFor, openTerminal, getBrowserType } from './TestUtils'; import { Browser, Page } from 'playwright'; -const APP = 'http://127.0.0.1:3000/test'; +const APP = 'http://127.0.0.1:3001/test'; let browser: Browser; let page: Page; diff --git a/test/api/InputHandler.api.ts b/test/api/InputHandler.api.ts index 16ab2301..a695abdc 100644 --- a/test/api/InputHandler.api.ts +++ b/test/api/InputHandler.api.ts @@ -8,7 +8,7 @@ import { pollFor, openTerminal, getBrowserType } from './TestUtils'; import { Browser, Page } from 'playwright'; import { IRenderDimensions } from 'browser/renderer/Types'; -const APP = 'http://127.0.0.1:3000/test'; +const APP = 'http://127.0.0.1:3001/test'; let browser: Browser; let page: Page; diff --git a/test/api/MouseTracking.api.ts b/test/api/MouseTracking.api.ts index 0bd0cc18..3df21a90 100644 --- a/test/api/MouseTracking.api.ts +++ b/test/api/MouseTracking.api.ts @@ -6,7 +6,7 @@ import { pollFor, writeSync, openTerminal, getBrowserType } from './TestUtils'; import { Browser, Page } from 'playwright'; -const APP = 'http://127.0.0.1:3000/test'; +const APP = 'http://127.0.0.1:3001/test'; let browser: Browser; let page: Page; diff --git a/test/api/Parser.api.ts b/test/api/Parser.api.ts index 77101e23..0ef57cf1 100644 --- a/test/api/Parser.api.ts +++ b/test/api/Parser.api.ts @@ -7,15 +7,15 @@ import { assert } from 'chai'; import { writeSync, openTerminal, getBrowserType } from './TestUtils'; import { Browser, Page } from 'playwright'; -const APP = 'http://127.0.0.1:3000/test'; +const APP = 'http://127.0.0.1:3001/test'; let browser: Browser; let page: Page; const width = 800; const height = 600; -describe('Parser Integration Tests', function(): void { - before(async function(): Promise { +describe('Parser Integration Tests', function (): void { + before(async function (): Promise { const browserType = getBrowserType(); browser = await browserType.launch({ headless: process.argv.indexOf('--headless') !== -1 @@ -28,84 +28,189 @@ describe('Parser Integration Tests', function(): void { after(async () => browser.close()); - describe('addCsiHandler', () => { + describe('registerCsiHandler', () => { it('should call custom CSI handler with js array params', async () => { await page.evaluate(` window.term.reset(); window._customCsiHandlerParams = []; - const _customCsiHandler = window.term.parser.addCsiHandler({final: 'm'}, (params, collect) => { + const _customCsiHandler = window.term.parser.registerCsiHandler({final: 'm'}, (params, collect) => { window._customCsiHandlerParams.push(params); return false; }, ''); `); await writeSync(page, '\x1b[38;5;123mparams\x1b[38:2::50:100:150msubparams'); - assert.deepEqual(await page.evaluate(`(() => window._customCsiHandlerParams)();`), [[38, 5, 123], [38, [2, -1, 50, 100, 150]]]); + assert.deepEqual(await page.evaluate(`window._customCsiHandlerParams`), [ + [38, 5, 123], + [38, [2, -1, 50, 100, 150]] + ]); + }); + it('async', async () => { + await page.evaluate(` + window.term.reset(); + window._customCsiHandlerCallStack = []; + const _customCsiHandlerA = window.term.parser.registerCsiHandler({intermediates:'+', final: 'Z'}, params => { + window._customCsiHandlerCallStack.push(['A', params]); + return false; + }); + const _customCsiHandlerB = window.term.parser.registerCsiHandler({intermediates:'+', final: 'Z'}, async params => { + await new Promise(res => setTimeout(res, 50)); + window._customCsiHandlerCallStack.push(['B', params]); + return false; + }); + const _customCsiHandlerC = window.term.parser.registerCsiHandler({intermediates:'+', final: 'Z'}, params => { + window._customCsiHandlerCallStack.push(['C', params]); + return false; + }); + `); + await writeSync(page, '\x1b[1;2+Z'); + assert.deepEqual(await page.evaluate(`window._customCsiHandlerCallStack`), [ + ['C', [1, 2]], + ['B', [1, 2]], + ['A', [1, 2]] + ]); }); }); - describe('addDcsHandler', () => { + describe('registerDcsHandler', () => { it('should respects return value', async () => { await page.evaluate(` window.term.reset(); window._customDcsHandlerCallStack = []; - const _customDcsHandlerA = window.term.parser.addDcsHandler({intermediates:'+', final: 'p'}, (data, params) => { + const _customDcsHandlerA = window.term.parser.registerDcsHandler({intermediates:'+', final: 'p'}, (data, params) => { window._customDcsHandlerCallStack.push(['A', params, data]); return false; }); - const _customDcsHandlerB = window.term.parser.addDcsHandler({intermediates:'+', final: 'p'}, (data, params) => { + const _customDcsHandlerB = window.term.parser.registerDcsHandler({intermediates:'+', final: 'p'}, (data, params) => { window._customDcsHandlerCallStack.push(['B', params, data]); return true; }); - const _customDcsHandlerC = window.term.parser.addDcsHandler({intermediates:'+', final: 'p'}, (data, params) => { + const _customDcsHandlerC = window.term.parser.registerDcsHandler({intermediates:'+', final: 'p'}, (data, params) => { window._customDcsHandlerCallStack.push(['C', params, data]); return false; }); `); await writeSync(page, '\x1bP1;2+psome data\x1b\\\\'); - assert.deepEqual(await page.evaluate(`(() => window._customDcsHandlerCallStack)();`), [['C', [1, 2], 'some data'], ['B', [1, 2], 'some data']]); + assert.deepEqual(await page.evaluate(`window._customDcsHandlerCallStack`), [ + ['C', [1, 2], 'some data'], + ['B', [1, 2], 'some data'] + ]); + }); + it('async', async () => { + await page.evaluate(` + window.term.reset(); + window._customDcsHandlerCallStack = []; + const _customDcsHandlerA = window.term.parser.registerDcsHandler({intermediates:'+', final: 'q'}, (data, params) => { + window._customDcsHandlerCallStack.push(['A', params, data]); + return false; + }); + const _customDcsHandlerB = window.term.parser.registerDcsHandler({intermediates:'+', final: 'q'}, async (data, params) => { + await new Promise(res => setTimeout(res, 50)); + window._customDcsHandlerCallStack.push(['B', params, data]); + return false; + }); + const _customDcsHandlerC = window.term.parser.registerDcsHandler({intermediates:'+', final: 'q'}, (data, params) => { + window._customDcsHandlerCallStack.push(['C', params, data]); + return false; + }); + `); + await writeSync(page, '\x1bP1;2+qsome data\x1b\\\\'); + assert.deepEqual(await page.evaluate(`window._customDcsHandlerCallStack`), [ + ['C', [1, 2], 'some data'], + ['B', [1, 2], 'some data'], + ['A', [1, 2], 'some data'] + ]); }); }); - describe('addEscHandler', () => { + describe('registerEscHandler', () => { it('should respects return value', async () => { await page.evaluate(` window.term.reset(); window._customEscHandlerCallStack = []; - const _customEscHandlerA = window.term.parser.addEscHandler({intermediates:'(', final: 'B'}, () => { + const _customEscHandlerA = window.term.parser.registerEscHandler({intermediates:'(', final: 'B'}, () => { window._customEscHandlerCallStack.push('A'); return false; }); - const _customEscHandlerB = window.term.parser.addEscHandler({intermediates:'(', final: 'B'}, () => { + const _customEscHandlerB = window.term.parser.registerEscHandler({intermediates:'(', final: 'B'}, () => { window._customEscHandlerCallStack.push('B'); return true; }); - const _customEscHandlerC = window.term.parser.addEscHandler({intermediates:'(', final: 'B'}, () => { + const _customEscHandlerC = window.term.parser.registerEscHandler({intermediates:'(', final: 'B'}, () => { window._customEscHandlerCallStack.push('C'); return false; }); `); await writeSync(page, '\x1b(B'); - assert.deepEqual(await page.evaluate(`(() => window._customEscHandlerCallStack)();`), ['C', 'B']); + assert.deepEqual(await page.evaluate(`window._customEscHandlerCallStack`), ['C', 'B']); + }); + it('async', async () => { + await page.evaluate(` + window.term.reset(); + window._customEscHandlerCallStack = []; + const _customEscHandlerA = window.term.parser.registerEscHandler({intermediates:'(', final: 'Z'}, () => { + window._customEscHandlerCallStack.push('A'); + return false; + }); + const _customEscHandlerB = window.term.parser.registerEscHandler({intermediates:'(', final: 'Z'}, async () => { + await new Promise(res => setTimeout(res, 50)); + window._customEscHandlerCallStack.push('B'); + return false; + }); + const _customEscHandlerC = window.term.parser.registerEscHandler({intermediates:'(', final: 'Z'}, () => { + window._customEscHandlerCallStack.push('C'); + return false; + }); + `); + await writeSync(page, '\x1b(Z'); + assert.deepEqual(await page.evaluate(`window._customEscHandlerCallStack`), ['C', 'B', 'A']); }); }); - describe('addOscHandler', () => { + describe('registerOscHandler', () => { it('should respects return value', async () => { await page.evaluate(` window.term.reset(); window._customOscHandlerCallStack = []; - const _customOscHandlerA = window.term.parser.addOscHandler(1234, data => { + const _customOscHandlerA = window.term.parser.registerOscHandler(1234, data => { window._customOscHandlerCallStack.push(['A', data]); return false; }); - const _customOscHandlerB = window.term.parser.addOscHandler(1234, data => { + const _customOscHandlerB = window.term.parser.registerOscHandler(1234, data => { window._customOscHandlerCallStack.push(['B', data]); return true; }); - const _customOscHandlerC = window.term.parser.addOscHandler(1234, data => { + const _customOscHandlerC = window.term.parser.registerOscHandler(1234, data => { window._customOscHandlerCallStack.push(['C', data]); return false; }); `); await writeSync(page, '\x1b]1234;some data\x07'); - assert.deepEqual(await page.evaluate(`(() => window._customOscHandlerCallStack)();`), [['C', 'some data'], ['B', 'some data']]); + assert.deepEqual(await page.evaluate(`window._customOscHandlerCallStack`), [ + ['C', 'some data'], + ['B', 'some data'] + ]); + }); + it('async', async () => { + await page.evaluate(` + window.term.reset(); + window._customOscHandlerCallStack = []; + const _customOscHandlerA = window.term.parser.registerOscHandler(666, data => { + window._customOscHandlerCallStack.push(['A', data]); + return false; + }); + const _customOscHandlerB = window.term.parser.registerOscHandler(666, async data => { + await new Promise(res => setTimeout(res, 50)); + window._customOscHandlerCallStack.push(['B', data]); + return false; + }); + const _customOscHandlerC = window.term.parser.registerOscHandler(666, data => { + window._customOscHandlerCallStack.push(['C', data]); + return false; + }); + `); + await writeSync(page, '\x1b]666;some data\x07'); + assert.deepEqual(await page.evaluate(`window._customOscHandlerCallStack`), [ + ['C', 'some data'], + ['B', 'some data'], + ['A', 'some data'] + ]); }); }); }); diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index c5ce8f92..9e951ffe 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -7,7 +7,7 @@ import { assert } from 'chai'; import { pollFor, timeout, writeSync, openTerminal, getBrowserType } from './TestUtils'; import { Browser, Page } from 'playwright'; -const APP = 'http://127.0.0.1:3000/test'; +const APP = 'http://127.0.0.1:3001/test'; let browser: Browser; let page: Page; @@ -410,6 +410,16 @@ describe('API Integration Tests', function(): void { await page.evaluate(`window.term.write('\\x1b]2;foo\\x9c')`); await pollFor(page, `window.calls`, ['foo']); }); + it('onBell', async () => { + await openTerminal(page); + await page.evaluate(` + window.calls = []; + window.term.onBell(() => window.calls.push(true)); + `); + await pollFor(page, `window.calls`, []); + await page.evaluate(`window.term.write('\\x07')`); + await pollFor(page, `window.calls`, [true]); + }); }); describe('buffer', () => { @@ -625,11 +635,11 @@ describe('API Integration Tests', function(): void { `); const dims = await getDimensions(); await moveMouseCell(page, dims, 5, 1); - await pollFor(page, `window.calls`, ['provide 1', 'match', 'hover']); + await timeout(100); await moveMouseCell(page, dims, 4, 1); await pollFor(page, `window.calls`, ['provide 1', 'match', 'hover', 'leave' ]); await moveMouseCell(page, dims, 7, 1); - await pollFor(page, `window.calls`, ['provide 1', 'match', 'hover', 'leave', 'hover']); + await timeout(100); await moveMouseCell(page, dims, 8, 1); await pollFor(page, `window.calls`, ['provide 1', 'match', 'hover', 'leave', 'hover', 'leave']); await page.evaluate(`window.disposable.dispose()`); diff --git a/test/api/TestUtils.ts b/test/api/TestUtils.ts index f36d2830..2b1e8828 100644 --- a/test/api/TestUtils.ts +++ b/test/api/TestUtils.ts @@ -6,8 +6,9 @@ import * as playwright from 'playwright'; import deepEqual = require('deep-equal'); import { ITerminalOptions } from 'xterm'; +import { deepStrictEqual, fail } from 'assert'; -export async function pollFor(page: playwright.Page, evalOrFn: string | (() => Promise), val: T, preFn?: () => Promise): Promise { +export async function pollFor(page: playwright.Page, evalOrFn: string | (() => Promise), val: T, preFn?: () => Promise, maxDuration?: number): Promise { if (preFn) { await preFn(); } @@ -18,8 +19,14 @@ export async function pollFor(page: playwright.Page, evalOrFn: string | (() = } if (!deepEqual(result, val)) { + if (maxDuration === undefined) { + maxDuration = 2000; + } + if (maxDuration <= 0) { + deepStrictEqual(result, val, 'pollFor max duration exceeded'); + } return new Promise(r => { - setTimeout(() => r(pollFor(page, evalOrFn, val, preFn)), 1); + setTimeout(() => r(pollFor(page, evalOrFn, val, preFn, maxDuration! - 10)), 10); }); } } diff --git a/test/benchmark/EscapeSequenceParser.benchmark.ts b/test/benchmark/EscapeSequenceParser.benchmark.ts index e4591dde..c2707f19 100644 --- a/test/benchmark/EscapeSequenceParser.benchmark.ts +++ b/test/benchmark/EscapeSequenceParser.benchmark.ts @@ -6,9 +6,11 @@ import { perfContext, before, beforeEach, ThroughputRuntimeCase } from 'xterm-be import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser'; import { C0, C1 } from 'common/data/EscapeSequences'; -import { IDcsHandler, IParams } from 'common/parser/Types'; +import { IDcsHandler, IOscHandler, IParams } from 'common/parser/Types'; import { OscHandler } from 'common/parser/OscParser'; +import { DcsHandler } from '../../out/common/parser/DcsParser'; +const SIZE = 5000000; function toUtf32(s: string): Uint32Array { const result = new Uint32Array(s.length); @@ -18,10 +20,16 @@ function toUtf32(s: string): Uint32Array { return result; } -class DcsHandler implements IDcsHandler { +class FastDcsHandler implements IDcsHandler { public hook(params: IParams): void {} public put(data: Uint32Array, start: number, end: number): void {} - public unhook(): void {} + public unhook(success: boolean): boolean { return true; } +} + +class FastOscHandler implements IOscHandler { + public start(): void {} + public put(data: Uint32Array, start: number, end: number): void {} + public end(success: boolean): boolean { return true; } } @@ -32,80 +40,81 @@ perfContext('Parser throughput - 50MB data', () => { beforeEach(() => { parser = new EscapeSequenceParser(); parser.setPrintHandler((data, start, end) => {}); - parser.setCsiHandler({final: '@'}, params => {}); - parser.setCsiHandler({final: 'A'}, params => {}); - parser.setCsiHandler({final: 'B'}, params => {}); - parser.setCsiHandler({final: 'C'}, params => {}); - parser.setCsiHandler({final: 'D'}, params => {}); - parser.setCsiHandler({final: 'E'}, params => {}); - parser.setCsiHandler({final: 'F'}, params => {}); - parser.setCsiHandler({final: 'G'}, params => {}); - parser.setCsiHandler({final: 'H'}, params => {}); - parser.setCsiHandler({final: 'I'}, params => {}); - parser.setCsiHandler({final: 'J'}, params => {}); - parser.setCsiHandler({final: 'K'}, params => {}); - parser.setCsiHandler({final: 'L'}, params => {}); - parser.setCsiHandler({final: 'M'}, params => {}); - parser.setCsiHandler({final: 'P'}, params => {}); - parser.setCsiHandler({final: 'S'}, params => {}); - parser.setCsiHandler({final: 'T'}, params => {}); - parser.setCsiHandler({final: 'X'}, params => {}); - parser.setCsiHandler({final: 'Z'}, params => {}); - parser.setCsiHandler({final: '`'}, params => {}); - parser.setCsiHandler({final: 'a'}, params => {}); - parser.setCsiHandler({final: 'b'}, params => {}); - parser.setCsiHandler({final: 'c'}, params => {}); - parser.setCsiHandler({final: 'd'}, params => {}); - parser.setCsiHandler({final: 'e'}, params => {}); - parser.setCsiHandler({final: 'f'}, params => {}); - parser.setCsiHandler({final: 'g'}, params => {}); - parser.setCsiHandler({final: 'h'}, params => {}); - parser.setCsiHandler({final: 'l'}, params => {}); - parser.setCsiHandler({final: 'm'}, params => {}); - parser.setCsiHandler({final: 'n'}, params => {}); - parser.setCsiHandler({final: 'p'}, params => {}); - parser.setCsiHandler({final: 'q'}, params => {}); - parser.setCsiHandler({final: 'r'}, params => {}); - parser.setCsiHandler({final: 's'}, params => {}); - parser.setCsiHandler({final: 'u'}, params => {}); - parser.setExecuteHandler(C0.BEL, () => {}); - parser.setExecuteHandler(C0.LF, () => {}); - parser.setExecuteHandler(C0.VT, () => {}); - parser.setExecuteHandler(C0.FF, () => {}); - parser.setExecuteHandler(C0.CR, () => {}); - parser.setExecuteHandler(C0.BS, () => {}); - parser.setExecuteHandler(C0.HT, () => {}); - parser.setExecuteHandler(C0.SO, () => {}); - parser.setExecuteHandler(C0.SI, () => {}); - parser.setExecuteHandler(C1.IND, () => {}); - parser.setExecuteHandler(C1.NEL, () => {}); - parser.setExecuteHandler(C1.HTS, () => {}); - parser.setOscHandler(0, new OscHandler((data) => {})); - parser.setOscHandler(2, new OscHandler((data) => {})); - parser.setEscHandler({final: '7'}, () => {}); - parser.setEscHandler({final: '8'}, () => {}); - parser.setEscHandler({final: 'D'}, () => {}); - parser.setEscHandler({final: 'E'}, () => {}); - parser.setEscHandler({final: 'H'}, () => {}); - parser.setEscHandler({final: 'M'}, () => {}); - parser.setEscHandler({final: '='}, () => {}); - parser.setEscHandler({final: '>'}, () => {}); - parser.setEscHandler({final: 'c'}, () => {}); - parser.setEscHandler({final: 'n'}, () => {}); - parser.setEscHandler({final: 'o'}, () => {}); - parser.setEscHandler({final: '|'}, () => {}); - parser.setEscHandler({final: '}'}, () => {}); - parser.setEscHandler({final: '~'}, () => {}); - parser.setEscHandler({intermediates: '%', final: '@'}, () => {}); - parser.setEscHandler({intermediates: '%', final: 'G'}, () => {}); - parser.setDcsHandler({final: 'q'}, new DcsHandler()); + parser.registerCsiHandler({final: '@'}, params => true); + parser.registerCsiHandler({final: 'A'}, params => true); + parser.registerCsiHandler({final: 'B'}, params => true); + parser.registerCsiHandler({final: 'C'}, params => true); + parser.registerCsiHandler({final: 'D'}, params => true); + parser.registerCsiHandler({final: 'E'}, params => true); + parser.registerCsiHandler({final: 'F'}, params => true); + parser.registerCsiHandler({final: 'G'}, params => true); + parser.registerCsiHandler({final: 'H'}, params => true); + parser.registerCsiHandler({final: 'I'}, params => true); + parser.registerCsiHandler({final: 'J'}, params => true); + parser.registerCsiHandler({final: 'K'}, params => true); + parser.registerCsiHandler({final: 'L'}, params => true); + parser.registerCsiHandler({final: 'M'}, params => true); + parser.registerCsiHandler({final: 'P'}, params => true); + parser.registerCsiHandler({final: 'S'}, params => true); + parser.registerCsiHandler({final: 'T'}, params => true); + parser.registerCsiHandler({final: 'X'}, params => true); + parser.registerCsiHandler({final: 'Z'}, params => true); + parser.registerCsiHandler({final: '`'}, params => true); + parser.registerCsiHandler({final: 'a'}, params => true); + parser.registerCsiHandler({final: 'b'}, params => true); + parser.registerCsiHandler({final: 'c'}, params => true); + parser.registerCsiHandler({final: 'd'}, params => true); + parser.registerCsiHandler({final: 'e'}, params => true); + parser.registerCsiHandler({final: 'f'}, params => true); + parser.registerCsiHandler({final: 'g'}, params => true); + parser.registerCsiHandler({final: 'h'}, params => true); + parser.registerCsiHandler({final: 'l'}, params => true); + parser.registerCsiHandler({final: 'm'}, params => true); + parser.registerCsiHandler({final: 'n'}, params => true); + parser.registerCsiHandler({final: 'p'}, params => true); + parser.registerCsiHandler({final: 'q'}, params => true); + parser.registerCsiHandler({final: 'r'}, params => true); + parser.registerCsiHandler({final: 's'}, params => true); + parser.registerCsiHandler({final: 'u'}, params => true); + parser.setExecuteHandler(C0.BEL, () => true); + parser.setExecuteHandler(C0.LF, () => true); + parser.setExecuteHandler(C0.VT, () => true); + parser.setExecuteHandler(C0.FF, () => true); + parser.setExecuteHandler(C0.CR, () => true); + parser.setExecuteHandler(C0.BS, () => true); + parser.setExecuteHandler(C0.HT, () => true); + parser.setExecuteHandler(C0.SO, () => true); + parser.setExecuteHandler(C0.SI, () => true); + parser.setExecuteHandler(C1.IND, () => true); + parser.setExecuteHandler(C1.NEL, () => true); + parser.setExecuteHandler(C1.HTS, () => true); + parser.registerOscHandler(0, new OscHandler(data => true)); + parser.registerOscHandler(1, new FastOscHandler()); + parser.registerEscHandler({final: '7'}, () => true); + parser.registerEscHandler({final: '8'}, () => true); + parser.registerEscHandler({final: 'D'}, () => true); + parser.registerEscHandler({final: 'E'}, () => true); + parser.registerEscHandler({final: 'H'}, () => true); + parser.registerEscHandler({final: 'M'}, () => true); + parser.registerEscHandler({final: '='}, () => true); + parser.registerEscHandler({final: '>'}, () => true); + parser.registerEscHandler({final: 'c'}, () => true); + parser.registerEscHandler({final: 'n'}, () => true); + parser.registerEscHandler({final: 'o'}, () => true); + parser.registerEscHandler({final: '|'}, () => true); + parser.registerEscHandler({final: '}'}, () => true); + parser.registerEscHandler({final: '~'}, () => true); + parser.registerEscHandler({intermediates: '%', final: '@'}, () => true); + parser.registerEscHandler({intermediates: '%', final: 'G'}, () => true); + parser.registerDcsHandler({final: 'p'}, new DcsHandler(data => true)); + parser.registerDcsHandler({final: 'q'}, new FastDcsHandler()); }); perfContext('PRINT - a', () => { before(() => { const data = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; let content = ''; - while (content.length < 50000000) { + while (content.length < SIZE) { content += data; } parsed = toUtf32(content); @@ -120,7 +129,7 @@ perfContext('Parser throughput - 50MB data', () => { before(() => { const data = '\n\n\n\n\n\n\n'; let content = ''; - while (content.length < 50000000) { + while (content.length < SIZE) { content += data; } parsed = toUtf32(content); @@ -135,7 +144,7 @@ perfContext('Parser throughput - 50MB data', () => { before(() => { const data = '\x1bE\x1bE\x1bE\x1bE\x1bE\x1bE\x1bE\x1bE\x1bE\x1bE'; let content = ''; - while (content.length < 50000000) { + while (content.length < SIZE) { content += data; } parsed = toUtf32(content); @@ -150,7 +159,7 @@ perfContext('Parser throughput - 50MB data', () => { before(() => { const data = '\x1b%G\x1b%G\x1b%G\x1b%G\x1b%G\x1b%G\x1b%G\x1b%G\x1b%G\x1b%G'; let content = ''; - while (content.length < 50000000) { + while (content.length < SIZE) { content += data; } parsed = toUtf32(content); @@ -165,7 +174,7 @@ perfContext('Parser throughput - 50MB data', () => { before(() => { const data = '\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A'; let content = ''; - while (content.length < 50000000) { + while (content.length < SIZE) { content += data; } parsed = toUtf32(content); @@ -180,7 +189,7 @@ perfContext('Parser throughput - 50MB data', () => { before(() => { const data = '\x1b[?p\x1b[?p\x1b[?p\x1b[?p\x1b[?p\x1b[?p\x1b[?p\x1b[?p\x1b[?p\x1b[?p'; let content = ''; - while (content.length < 50000000) { + while (content.length < SIZE) { content += data; } parsed = toUtf32(content); @@ -195,7 +204,7 @@ perfContext('Parser throughput - 50MB data', () => { before(() => { const data = '\x1b[1;2m\x1b[1;2m\x1b[1;2m\x1b[1;2m\x1b[1;2m\x1b[1;2m\x1b[1;2m\x1b[1;2m\x1b[1;2m\x1b[1;2m'; let content = ''; - while (content.length < 50000000) { + while (content.length < SIZE) { content += data; } parsed = toUtf32(content); @@ -210,7 +219,7 @@ perfContext('Parser throughput - 50MB data', () => { before(() => { const data = '\x1b[1;2;3;4;5;6;7;8;9;0m\x1b[1;2;3;4;5;6;7;8;9;0m\x1b[1;2;3;4;5;6;7;8;9;0m'; let content = ''; - while (content.length < 50000000) { + while (content.length < SIZE) { content += data; } parsed = toUtf32(content); @@ -221,11 +230,11 @@ perfContext('Parser throughput - 50MB data', () => { }, {fork: true}).showAverageThroughput(); }); - perfContext('OSC (short) - OSC 0;hi ST', () => { + perfContext('OSC string interface (short seq) - OSC 0;hi ST', () => { before(() => { const data = '\x1b]0;hi\x1b\\\x1b]0;hi\x1b\\\x1b]0;hi\x1b\\\x1b]0;hi\x1b\\x1b]0;hi\x1b\\'; let content = ''; - while (content.length < 50000000) { + while (content.length < SIZE) { content += data; } parsed = toUtf32(content); @@ -236,11 +245,11 @@ perfContext('Parser throughput - 50MB data', () => { }, {fork: true}).showAverageThroughput(); }); - perfContext('OSC (long) - OSC 0; ST', () => { + perfContext('OSC string interface (long seq) - OSC 0; ST', () => { before(() => { const data = '\x1b]0;Lorem ipsum dolor sit amet, consetetur sadipscing elitr.\x1b\\'; let content = ''; - while (content.length < 50000000) { + while (content.length < SIZE) { content += data; } parsed = toUtf32(content); @@ -251,11 +260,41 @@ perfContext('Parser throughput - 50MB data', () => { }, {fork: true}).showAverageThroughput(); }); - perfContext('DCS (short)', () => { + perfContext('OSC class interface (short seq) - OSC 0;hi ST', () => { before(() => { - const data = '\x1bPq~~\x1b\\'; + const data = '\x1b]1;hi\x1b\\\x1b]1;hi\x1b\\\x1b]1;hi\x1b\\\x1b]1;hi\x1b\\x1b]1;hi\x1b\\'; let content = ''; - while (content.length < 50000000) { + while (content.length < SIZE) { + content += data; + } + parsed = toUtf32(content); + }); + new ThroughputRuntimeCase('', () => { + parser.parse(parsed, parsed.length); + return {payloadSize: parsed.length}; + }, {fork: true}).showAverageThroughput(); + }); + + perfContext('OSC class interface (long seq) - OSC 0; ST', () => { + before(() => { + const data = '\x1b]1;Lorem ipsum dolor sit amet, consetetur sadipscing elitr.\x1b\\'; + let content = ''; + while (content.length < SIZE) { + content += data; + } + parsed = toUtf32(content); + }); + new ThroughputRuntimeCase('', () => { + parser.parse(parsed, parsed.length); + return {payloadSize: parsed.length}; + }, {fork: true}).showAverageThroughput(); + }); + + perfContext('DCS string interface (short seq)', () => { + before(() => { + const data = '\x1bPphi\x1b\\\x1bPphi\x1b\\\x1bPphi\x1b\\\x1bPphi\x1b\\\x1bPphi\x1b\\'; + let content = ''; + while (content.length < SIZE) { content += data; } parsed = toUtf32(content); @@ -266,11 +305,41 @@ perfContext('Parser throughput - 50MB data', () => { }, {fork: true}).showAverageThroughput(); }); - perfContext('DCS (long)', () => { + perfContext('DCS string interface (long seq)', () => { before(() => { - const data = '\x1bPq#0;2;0;0;0#1;2;100;100;0#2;2;0;100;0#1~~@@vv@@~~@@~~$#2??}}GG}}??}}??-#1!14@\x1b\\'; + const data = '\x1bPpLorem ipsum dolor sit amet, consetetur sadipscing elitr.\x1b\\'; let content = ''; - while (content.length < 50000000) { + while (content.length < SIZE) { + content += data; + } + parsed = toUtf32(content); + }); + new ThroughputRuntimeCase('', async () => { + parser.parse(parsed, parsed.length); + return {payloadSize: parsed.length}; + }, {fork: true}).showAverageThroughput(); + }); + + perfContext('DCS class interface (short seq)', () => { + before(() => { + const data = '\x1bPqhi\x1b\\\x1bPqhi\x1b\\\x1bPqhi\x1b\\\x1bPqhi\x1b\\\x1bPqhi\x1b\\'; + let content = ''; + while (content.length < SIZE) { + content += data; + } + parsed = toUtf32(content); + }); + new ThroughputRuntimeCase('', async () => { + parser.parse(parsed, parsed.length); + return {payloadSize: parsed.length}; + }, {fork: true}).showAverageThroughput(); + }); + + perfContext('DCS class interface (long seq)', () => { + before(() => { + const data = '\x1bPqLorem ipsum dolor sit amet, consetetur sadipscing elitr.\x1b\\'; + let content = ''; + while (content.length < SIZE) { content += data; } parsed = toUtf32(content); diff --git a/test/benchmark/Terminal.benchmark.ts b/test/benchmark/Terminal.benchmark.ts index 76139cb6..40a3c208 100644 --- a/test/benchmark/Terminal.benchmark.ts +++ b/test/benchmark/Terminal.benchmark.ts @@ -29,7 +29,7 @@ perfContext('Terminal: ls -lR /usr/lib', () => { chunks.push(data as unknown as Buffer); length += data.length; }); - await new Promise(resolve => p.on('exit', () => resolve())); + await new Promise(resolve => p.on('exit', () => resolve())); contentUtf8 = Buffer.concat(chunks, length); // translate to content string const buffer = new Uint32Array(contentUtf8.length); @@ -44,24 +44,24 @@ perfContext('Terminal: ls -lR /usr/lib', () => { } }); - perfContext('write', () => { + perfContext('write/string/async', () => { let terminal: Terminal; before(() => { terminal = new Terminal({cols: 80, rows: 25, scrollback: 1000}); }); - new ThroughputRuntimeCase('', () => { - terminal.writeSync(content); + new ThroughputRuntimeCase('', async () => { + await new Promise(res => terminal.write(content, res)); return {payloadSize: contentUtf8.length}; }, {fork: false}).showAverageThroughput(); }); - perfContext('writeUtf8', () => { + perfContext('write/Utf8/async', () => { let terminal: Terminal; before(() => { terminal = new Terminal({cols: 80, rows: 25, scrollback: 1000}); }); - new ThroughputRuntimeCase('', () => { - terminal.writeSync(content); + new ThroughputRuntimeCase('', async () => { + await new Promise(res => terminal.write(content, res)); return {payloadSize: contentUtf8.length}; }, {fork: false}).showAverageThroughput(); }); diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index d860ddfa..6bfb71ae 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -709,6 +709,12 @@ declare module 'xterm' { */ onTitleChange: IEvent; + /** + * Adds an event listener for when the bell is triggered. + * @returns an `IDisposable` to stop listening. + */ + onBell: IEvent; + /** * Unfocus the terminal. */ @@ -1501,6 +1507,23 @@ declare module 'xterm' { /** * Allows hooking into the parser for custom handling of escape sequences. + * + * Note on sync vs. async handlers: + * xterm.js implements all parser actions with synchronous handlers. + * In general custom handlers should also operate in sync mode wherever + * possible to keep the parser fast. + * Still the exposed interfaces allow to register async handlers by returning + * a `Promise`. Here the parser will pause input processing until + * the promise got resolved or rejected (in-band blocking). This "full stop" + * on the input chain allows to implement backpressure from a certain async + * action while the terminal state will not progress any further from input. + * It does not mean that the terminal state will not change at all in between, + * as user actions like resize or reset are still processed immediately. + * It is an error to assume a stable terminal state while giving back control + * in between, e.g. by multiple chained `then` calls. + * Downside of an async handler is a rather bad throughput performance, + * thus use async handlers only as a last resort or for actions that have + * to rely on async interfaces itself. */ export interface IParser { /** @@ -1510,12 +1533,11 @@ declare module 'xterm' { * @param callback The function to handle the sequence. The callback is * called with the numerical params. If the sequence has subparams the * array will contain subarrays with their numercial values. - * Return true if the sequence was handled; false if we should try - * a previous handler (set by addCsiHandler or setCsiHandler). - * The most recently added handler is tried first. + * Return `true` if the sequence was handled, `false` if the parser should try + * a previous handler. The most recently added handler is tried first. * @return An IDisposable you can call to remove this handler. */ - registerCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean): IDisposable; + registerCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean | Promise): IDisposable; /** * Adds a handler for DCS escape sequences. @@ -1529,12 +1551,11 @@ declare module 'xterm' { * big payloads. Currently xterm.js limits DCS payload to 10 MB * which should give enough room for most use cases. * The function gets the payload and numerical parameters as arguments. - * Return true if the sequence was handled; false if we should try - * a previous handler (set by addDcsHandler or setDcsHandler). - * The most recently added handler is tried first. + * Return `true` if the sequence was handled, `false` if the parser should try + * a previous handler. The most recently added handler is tried first. * @return An IDisposable you can call to remove this handler. */ - registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean): IDisposable; + registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean | Promise): IDisposable; /** * Adds a handler for ESC escape sequences. @@ -1542,12 +1563,11 @@ declare module 'xterm' { * gets registered, e.g. {intermediates: '%' final: 'G'} for * default charset selection. * @param callback The function to handle the sequence. - * Return true if the sequence was handled; false if we should try - * a previous handler (set by addEscHandler or setEscHandler). - * The most recently added handler is tried first. + * Return `true` if the sequence was handled, `false` if the parser should try + * a previous handler. The most recently added handler is tried first. * @return An IDisposable you can call to remove this handler. */ - registerEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable; + registerEscHandler(id: IFunctionIdentifier, handler: () => boolean | Promise): IDisposable; /** * Adds a handler for OSC escape sequences. @@ -1560,12 +1580,11 @@ declare module 'xterm' { * big payloads. Currently xterm.js limits OSC payload to 10 MB * which should give enough room for most use cases. * The callback is called with OSC data string. - * Return true if the sequence was handled; false if we should try - * a previous handler (set by addOscHandler or setOscHandler). - * The most recently added handler is tried first. + * Return `true` if the sequence was handled, `false` if the parser should try + * a previous handler. The most recently added handler is tried first. * @return An IDisposable you can call to remove this handler. */ - registerOscHandler(ident: number, callback: (data: string) => boolean): IDisposable; + registerOscHandler(ident: number, callback: (data: string) => boolean | Promise): IDisposable; } /** diff --git a/yarn.lock b/yarn.lock index 8d8c9dd3..ee0f0558 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,13 @@ # yarn lockfile v1 +"@babel/code-frame@7.12.11": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" + integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== + dependencies: + "@babel/highlight" "^7.10.4" + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" @@ -116,6 +123,11 @@ dependencies: "@babel/types" "^7.8.3" +"@babel/helper-validator-identifier@^7.14.0": + version "7.14.0" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz#d26cad8a47c65286b15df1547319a5d0bcf27288" + integrity sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A== + "@babel/helper-validator-identifier@^7.9.0": version "7.9.0" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz#ad53562a7fc29b3b9a91bbf7d10397fd146346ed" @@ -130,6 +142,15 @@ "@babel/traverse" "^7.9.0" "@babel/types" "^7.9.0" +"@babel/highlight@^7.10.4": + version "7.14.0" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.0.tgz#3197e375711ef6bf834e67d0daec88e4f46113cf" + integrity sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg== + dependencies: + "@babel/helper-validator-identifier" "^7.14.0" + chalk "^2.0.0" + js-tokens "^4.0.0" + "@babel/highlight@^7.8.3": version "7.9.0" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.9.0.tgz#4e9b45ccb82b79607271b2979ad82c7b68163079" @@ -177,10 +198,24 @@ lodash "^4.17.13" to-fast-properties "^2.0.0" -"@eslint/eslintrc@^0.2.1": - version "0.2.1" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.2.1.tgz#f72069c330461a06684d119384435e12a5d76e3c" - integrity sha512-XRUeBZ5zBWLYgSANMpThFddrZZkEbGHgUdt5UJjZfnlN9BGCiUBrf+nvbRupSjMvqzwnQN0qwCmOxITt1cfywA== +"@dabh/diagnostics@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@dabh/diagnostics/-/diagnostics-2.0.2.tgz#290d08f7b381b8f94607dc8f471a12c675f9db31" + integrity sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q== + dependencies: + colorspace "1.1.x" + enabled "2.0.x" + kuler "^2.0.0" + +"@discoveryjs/json-ext@^0.5.0": + version "0.5.2" + resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz#8f03a22a04de437254e8ce8cc84ba39689288752" + integrity sha512-HyYEUDeIj5rRQU2Hk5HTB2uHsbRQpF70nvMhVzi+VJR0X+xNEhjPui4/kBf3VeH/wqD28PT4sVOm8qqLjBrSZg== + +"@eslint/eslintrc@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.1.tgz#442763b88cecbe3ee0ec7ca6d6dd6168550cbf14" + integrity sha512-5v7TDE9plVhvxQeWLXDTvFvJBdH6pEsdnl2g/dAptmuFEPedQ4Erq5rsDsX+mvAM610IhNaO2W5V1dOOnDKxkQ== dependencies: ajv "^6.12.4" debug "^4.1.1" @@ -189,7 +224,6 @@ ignore "^4.0.6" import-fresh "^3.2.1" js-yaml "^3.13.1" - lodash "^4.17.19" minimatch "^3.0.4" strip-json-comments "^3.1.1" @@ -208,41 +242,48 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== -"@nodelib/fs.scandir@2.1.3": - version "2.1.3" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" - integrity sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw== +"@kwsites/file-exists@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@kwsites/file-exists/-/file-exists-1.1.1.tgz#ad1efcac13e1987d8dbaf235ef3be5b0d96faa99" + integrity sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw== dependencies: - "@nodelib/fs.stat" "2.0.3" + debug "^4.1.1" + +"@kwsites/promise-deferred@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz#8ace5259254426ccef57f3175bc64ed7095ed919" + integrity sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw== + +"@nodelib/fs.scandir@2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz#d4b3549a5db5de2683e0c1071ab4f140904bbf69" + integrity sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA== + dependencies: + "@nodelib/fs.stat" "2.0.4" run-parallel "^1.1.9" -"@nodelib/fs.stat@2.0.3", "@nodelib/fs.stat@^2.0.2": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz#34dc5f4cabbc720f4e60f75a747e7ecd6c175bd3" - integrity sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA== +"@nodelib/fs.stat@2.0.4", "@nodelib/fs.stat@^2.0.2": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz#a3f2dd61bab43b8db8fa108a121cfffe4c676655" + integrity sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q== "@nodelib/fs.walk@^1.2.3": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz#011b9202a70a6366e436ca5c065844528ab04976" - integrity sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ== + version "1.2.6" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz#cce9396b30aa5afe9e3756608f5831adcb53d063" + integrity sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow== dependencies: - "@nodelib/fs.scandir" "2.1.3" + "@nodelib/fs.scandir" "2.1.4" fastq "^1.6.0" -"@types/anymatch@*": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a" - integrity sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA== - "@types/app-root-path@^1.2.4": version "1.2.4" resolved "https://registry.yarnpkg.com/@types/app-root-path/-/app-root-path-1.2.4.tgz#a78b703282b32ac54de768f5512ecc3569919dc7" integrity sha1-p4twMoKzKsVN52j1US7MNWmRncc= -"@types/chai@^4.2.14": - version "4.2.14" - resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.14.tgz#44d2dd0b5de6185089375d976b4ec5caf6861193" - integrity sha512-G+ITQPXkwTrslfG5L/BksmbLUA0M1iybEsmCWPqzSxsRRhJZimBKJkoMi8fr/CPygPTj4zO5pJH7I2/cm9M7SQ== +"@types/chai@^4.2.18": + version "4.2.18" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.18.tgz#0c8e298dbff8205e2266606c1ea5fbdba29b46e4" + integrity sha512-rS27+EkB/RE1Iz3u0XtVL5q36MGDWbgYe7zWiodyKNUnthxY0rukK5V36eiUCtCisB7NN8zKYH6DO2M37qxFEQ== "@types/cli-table@^0.3.0": version "0.3.0" @@ -272,28 +313,23 @@ "@types/eslint" "*" "@types/estree" "*" -"@types/eslint-visitor-keys@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" - integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== - "@types/eslint@*": - version "7.2.4" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.2.4.tgz#d12eeed7741d2491b69808576ac2d20c14f74c41" - integrity sha512-YCY4kzHMsHoyKspQH+nwSe+70Kep7Vjt2X+dZe5Vs2vkRudqtoFoUIv1RlJmZB8Hbp7McneupoZij4PadxsK5Q== + version "7.2.10" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.2.10.tgz#4b7a9368d46c0f8cd5408c23288a59aa2394d917" + integrity sha512-kUEPnMKrqbtpCq/KTaGFFKAcz6Ethm2EjCoKIDaCmfRBWLbFuTcOJfTlorwbnboXBzahqWLgUp1BQeKHiJzPUQ== dependencies: "@types/estree" "*" "@types/json-schema" "*" -"@types/estree@*", "@types/estree@^0.0.45": - version "0.0.45" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.45.tgz#e9387572998e5ecdac221950dab3e8c3b16af884" - integrity sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g== +"@types/estree@*", "@types/estree@^0.0.47": + version "0.0.47" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.47.tgz#d7a51db20f0650efec24cd04994f523d93172ed4" + integrity sha512-c5ciR06jK8u9BstrmJyO97m+klJrrhCf9u3rLu3DEAJBirxRqSCvDQoYKmxuYwQI5SZChAWu+tq9oVlGRuzPAg== -"@types/fs-extra@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-7.0.0.tgz#9c4ad9e1339e7448a76698829def1f159c1b636c" - integrity sha512-ndoMMbGyuToTy4qB6Lex/inR98nPiNHacsgMPvy+zqMLgSxbt8VtWpDArpGp69h1fEDQHn1KB+9DWD++wgbwYA== +"@types/fs-extra@9.0.1": + version "9.0.1" + resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-9.0.1.tgz#91c8fc4c51f6d5dbe44c2ca9ab09310bd00c7918" + integrity sha512-B42Sxuaz09MhC3DDeW5kubRcQ5by4iuVQ0cRRWM2lggLzAa/KVom0Aft/208NgMvNQQZ86s5rVcqDdn/SH0/mg== dependencies: "@types/node" "*" @@ -305,29 +341,29 @@ "@types/minimatch" "*" "@types/node" "*" -"@types/jsdom@^16.2.5": - version "16.2.5" - resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-16.2.5.tgz#74ebad438741d249ecb416c5486dcde4217eb66c" - integrity sha512-k/ZaTXtReAjwWu0clU0KLS53dyqZnA8mm+jwKFeFrvufXgICp+VNbskETFxKKAguv0pkaEKTax5MaRmvalM+TA== +"@types/jsdom@^16.2.10": + version "16.2.10" + resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-16.2.10.tgz#c05ea94682d035943ae2453b79d56178496b6653" + integrity sha512-q3aIjp3ehhVSXSbvNyuireAfvU2umRiZ2aLumyeZewCnoNaokrRDdTu5IvaeE9pzNtWHXrUnM9lb22Vl3W08EA== dependencies: "@types/node" "*" "@types/parse5" "*" "@types/tough-cookie" "*" -"@types/json-schema@*", "@types/json-schema@^7.0.6": +"@types/json-schema@*", "@types/json-schema@^7.0.3": + version "7.0.7" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" + integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA== + +"@types/json-schema@^7.0.6": version "7.0.6" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0" integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw== -"@types/json-schema@^7.0.3": - version "7.0.5" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd" - integrity sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ== - -"@types/mathjs@^5.0.1": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@types/mathjs/-/mathjs-5.0.1.tgz#b98e163ea396b4f27bec20ee25ffb8fe9e656af8" - integrity sha512-EFBuueI+BRed9bnUO6/9my55b4FH+VQIvqMm58h9JGbtaGCkqr3YSDhnmVbM1SJjF//8SURERSypzNwejOk7lA== +"@types/mathjs@^6.0.11": + version "6.0.12" + resolved "https://registry.yarnpkg.com/@types/mathjs/-/mathjs-6.0.12.tgz#1c2a60352852676e10936ce150b9500d36555973" + integrity sha512-bpKs8CDJ0aOiiJguywryE/U6Wre/uftJ89xhp4aCgF4oRb3Yug2VyZ87958gmSeq4WMsvWPMs2Q5TtFv+dJtaA== dependencies: decimal.js "^10.0.0" @@ -336,95 +372,61 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== -"@types/mocha@^5.2.7": - version "5.2.7" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.7.tgz#315d570ccb56c53452ff8638738df60726d5b6ea" - integrity sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ== - -"@types/mocha@^8.0.3": - version "8.0.3" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-8.0.3.tgz#51b21b6acb6d1b923bbdc7725c38f9f455166402" - integrity sha512-vyxR57nv8NfcU0GZu8EUXZLTbCMupIUwy95LJ6lllN+JRPG25CwMHoB1q5xKh8YKhQnHYRAn4yW2yuHbf/5xgg== +"@types/mocha@^8.2.1", "@types/mocha@^8.2.2": + version "8.2.2" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-8.2.2.tgz#91daa226eb8c2ff261e6a8cbf8c7304641e095e0" + integrity sha512-Lwh0lzzqT5Pqh6z61P3c3P5nm6fzQK/MMHl9UKeneAeInVflBSz1O2EkX6gM6xfJd7FBXBY5purtLx7fUiZ7Hw== "@types/node@*": version "13.9.5" resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.5.tgz#59738bf30b31aea1faa2df7f4a5f55613750cf00" integrity sha512-hkzMMD3xu6BrJpGVLeQ3htQQNAcOrJjX7WFmtK8zWQpz2UJf13LCFF2ALA7c9OVdvc2vQJeDdjfR35M0sBCxvw== -"@types/node@^10.17.17": - version "10.17.18" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.18.tgz#ae364d97382aacdebf583fa4e7132af2dfe56a0c" - integrity sha512-DQ2hl/Jl3g33KuAUOcMrcAOtsbzb+y/ufakzAdeK9z/H/xsvkpbETZZbPNMIiQuk24f5ZRMCcZIViAwyFIiKmg== +"@types/node@^12.12.37": + version "12.20.12" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.12.tgz#fd9c1c2cfab536a2383ed1ef70f94adea743a226" + integrity sha512-KQZ1al2hKOONAs2MFv+yTQP1LkDWMrRJ9YCVRalXltOfXsBmH5IownLxQaiq0lnAHwAViLnh2aTYqrPcRGEbgg== -"@types/node@^12.0.4": - version "12.12.32" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.32.tgz#0ccc836d273e8a3cddf568daf22729cfa57c1925" - integrity sha512-44/reuCrwiQEsXud3I5X3sqI5jIXAmHB5xoiyKUw965olNHF3IWKjBLKK3F9LOSUZmK+oDt8jmyO637iX+hMgA== +"@types/node@^14.14.44": + version "14.14.44" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.44.tgz#df7503e6002847b834371c004b372529f3f85215" + integrity sha512-+gaugz6Oce6ZInfI/tK4Pq5wIIkJMEJUu92RB3Eu93mtj4wjjjz9EB5mLp5s1pSsLXdC/CPut/xF20ZzAQJbTA== "@types/parse5@*": - version "5.0.2" - resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-5.0.2.tgz#a877a4658f8238c8266faef300ae41c84d72ec8a" - integrity sha512-BOl+6KDs4ItndUWUFchy3aEqGdHhw0BC4Uu+qoDonN/f0rbUnJbm71Ulj8Tt9jLFRaAxPLKvdS1bBLfx1qXR9g== + version "6.0.0" + resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-6.0.0.tgz#38590dc2c3cf5717154064e3ee9b6947ee21b299" + integrity sha512-oPwPSj4a1wu9rsXTEGIJz91ISU725t0BmSnUhb57sI+M8XEmvUop84lzuiYdq0Y5M6xLY8DBPg0C2xEQKLyvBA== -"@types/puppeteer@^1.12.4": - version "1.20.4" - resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-1.20.4.tgz#30cb0a4ee5394c420119cbdf9f079d6595a07f67" - integrity sha512-T/kFgyLnYWk0H94hxI0HbOLnqHvzBRpfS0F0oo9ESGI24oiC2fEjDcMbBjuK3wH7VLsaIsp740vVXVzR1dsMNg== +"@types/puppeteer@^5.4.3": + version "5.4.3" + resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-5.4.3.tgz#cdca84aa7751d77448d8a477dbfa0af1f11485f2" + integrity sha512-3nE8YgR9DIsgttLW+eJf6mnXxq8Ge+27m5SU3knWmrlfl6+KOG0Bf9f7Ua7K+C4BnaTMAh3/UpySqdAYvrsvjg== dependencies: "@types/node" "*" -"@types/source-list-map@*": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" - integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA== - -"@types/tapable@*": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.5.tgz#9adbc12950582aa65ead76bffdf39fe0c27a3c02" - integrity sha512-/gG2M/Imw7cQFp8PGvz/SwocNrmKFjFsm5Pb8HdbHkZ1K8pmuPzOX4VeVoiEecFCVf4CsN1r3/BRvx+6sNqwtQ== - "@types/tough-cookie@*": - version "2.3.6" - resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.6.tgz#c880579e087d7a0db13777ff8af689f4ffc7b0d5" - integrity sha512-wHNBMnkoEBiRAd3s8KTKwIuO9biFtTf0LehITzBhSco+HQI0xkXZbLOD55SW3Aqw3oUkHstkm5SPv58yaAdFPQ== - -"@types/uglify-js@*": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.0.4.tgz#96beae23df6f561862a830b4288a49e86baac082" - integrity sha512-SudIN9TRJ+v8g5pTG8RRCqfqTMNqgWCKKd3vtynhGzkIIjxaicNAMuY5TRadJ6tzDu3Dotf3ngaMILtmOdmWEQ== - dependencies: - source-map "^0.6.1" + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.0.tgz#fef1904e4668b6e5ecee60c52cc6a078ffa6697d" + integrity sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A== "@types/utf8@^2.1.6": version "2.1.6" resolved "https://registry.yarnpkg.com/@types/utf8/-/utf8-2.1.6.tgz#430cabb71a42d0a3613cce5621324fe4f5a25753" integrity sha512-pRs2gYF5yoKYrgSaira0DJqVg2tFuF+Qjp838xS7K+mJyY2jJzjsrl6y17GbIa4uMRogMbxs+ghNCvKg6XyNrA== -"@types/webpack-sources@*": - version "0.1.7" - resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-0.1.7.tgz#0a330a9456113410c74a5d64180af0cbca007141" - integrity sha512-XyaHrJILjK1VHVC4aVlKsdNN5KBTwufMb43cQs+flGxtPAf/1Qwl8+Q0tp5BwEGaI8D6XT1L+9bSWXckgkjTLw== +"@types/webpack@^5.28.0": + version "5.28.0" + resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-5.28.0.tgz#78dde06212f038d77e54116cfe69e88ae9ed2c03" + integrity sha512-8cP0CzcxUiFuA9xGJkfeVpqmWTk9nx6CWwamRGCj95ph1SmlRRk9KlCZ6avhCbZd4L68LvYT6l1kpdEnQXrF8w== dependencies: "@types/node" "*" - "@types/source-list-map" "*" - source-map "^0.6.1" + tapable "^2.2.0" + webpack "^5" -"@types/webpack@^4.41.24": - version "4.41.24" - resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.24.tgz#75b664abe3d5bcfe54e64313ca3b43e498550422" - integrity sha512-1A0MXPwZiMOD3DPMuOKUKcpkdPo8Lq33UGggZ7xio6wJ/jV1dAu5cXDrOfGDnldUroPIRLsr/DT43/GqOA4RFQ== - dependencies: - "@types/anymatch" "*" - "@types/node" "*" - "@types/tapable" "*" - "@types/uglify-js" "*" - "@types/webpack-sources" "*" - source-map "^0.6.0" - -"@types/ws@^7.2.9": - version "7.2.9" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.2.9.tgz#cadfac473acfab192678a487c3ecbb13a503547f" - integrity sha512-gmXYAXr7G4BrRMnkGQGkGonc3ArVro9VZd//C1uns/qqsJyl2dxaJdlPMhZbcq5MTxFFC+ttFWtHSfVW5+hlRA== +"@types/ws@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.4.tgz#93e1e00824c1de2608c30e6de4303ab3b4c0c9bc" + integrity sha512-d/7W23JAXPodQNbOZNXvl2K+bqAQrCMwlh/nuQsPSQk6Fq0opHoPrUw43aHsvSbIiQPr8Of2hkFbnz1XBFVyZQ== dependencies: "@types/node" "*" @@ -435,112 +437,74 @@ dependencies: "@types/node" "*" -"@typescript-eslint/eslint-plugin@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.0.0.tgz#99349a501447fed91de18346705c0c65cf603bee" - integrity sha512-5e6q1TR7gS2P+8W2xndCu7gBh3BzmYEo70OyIdsmCmknHha/yNbz2vdevl+tP1uoaMOcrzg4gyrAijuV3DDBHA== +"@typescript-eslint/eslint-plugin@^4.23.0": + version "4.23.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.23.0.tgz#29d3c9c81f6200b1fd6d8454cfb007ba176cde80" + integrity sha512-tGK1y3KIvdsQEEgq6xNn1DjiFJtl+wn8JJQiETtCbdQxw1vzjXyAaIkEmO2l6Nq24iy3uZBMFQjZ6ECf1QdgGw== dependencies: - "@typescript-eslint/experimental-utils" "4.0.0" - "@typescript-eslint/scope-manager" "4.0.0" + "@typescript-eslint/experimental-utils" "4.23.0" + "@typescript-eslint/scope-manager" "4.23.0" debug "^4.1.1" functional-red-black-tree "^1.0.1" + lodash "^4.17.15" regexpp "^3.0.0" semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/experimental-utils@3.10.1": - version "3.10.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz#e179ffc81a80ebcae2ea04e0332f8b251345a686" - integrity sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw== +"@typescript-eslint/experimental-utils@4.23.0": + version "4.23.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.23.0.tgz#f2059434cd6e5672bfeab2fb03b7c0a20622266f" + integrity sha512-WAFNiTDnQfrF3Z2fQ05nmCgPsO5o790vOhmWKXbbYQTO9erE1/YsFot5/LnOUizLzU2eeuz6+U/81KV5/hFTGA== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/types" "3.10.1" - "@typescript-eslint/typescript-estree" "3.10.1" + "@typescript-eslint/scope-manager" "4.23.0" + "@typescript-eslint/types" "4.23.0" + "@typescript-eslint/typescript-estree" "4.23.0" eslint-scope "^5.0.0" eslint-utils "^2.0.0" -"@typescript-eslint/experimental-utils@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.0.0.tgz#fbec21a3b5ab59127edb6ce2e139ed378cc50eb5" - integrity sha512-hbX6zR+a/vcpFVNJYN/Nbd7gmaMosDTxHEKcvmhWeWcq/0UDifrqmCfkkodbAKL46Fn4ekSBMTyq2zlNDzcQxw== +"@typescript-eslint/parser@^4.23.0": + version "4.23.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.23.0.tgz#239315d38e42e852bef43a4b0b01bef78f78911c" + integrity sha512-wsvjksHBMOqySy/Pi2Q6UuIuHYbgAMwLczRl4YanEPKW5KVxI9ZzDYh3B5DtcZPQTGRWFJrfcbJ6L01Leybwug== dependencies: - "@types/json-schema" "^7.0.3" - "@typescript-eslint/scope-manager" "4.0.0" - "@typescript-eslint/types" "4.0.0" - "@typescript-eslint/typescript-estree" "4.0.0" - eslint-scope "^5.0.0" - eslint-utils "^2.0.0" - -"@typescript-eslint/parser@^3.10.1": - version "3.10.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-3.10.1.tgz#1883858e83e8b442627e1ac6f408925211155467" - integrity sha512-Ug1RcWcrJP02hmtaXVS3axPPTTPnZjupqhgj+NnZ6BCkwSImWk/283347+x9wN+lqOdK9Eo3vsyiyDHgsmiEJw== - dependencies: - "@types/eslint-visitor-keys" "^1.0.0" - "@typescript-eslint/experimental-utils" "3.10.1" - "@typescript-eslint/types" "3.10.1" - "@typescript-eslint/typescript-estree" "3.10.1" - eslint-visitor-keys "^1.1.0" - -"@typescript-eslint/scope-manager@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.0.0.tgz#8c9e3b3b8cdf5a1fbe671d9fad73ff67bc027ea8" - integrity sha512-9gcWUPoWo7gk/+ZQPg7L1ySRmR5HLIy3Vu6/LfhQbuzIkGm6v2CGIjpVRISoDLFRovNRDImd4aP/sa8O4yIEBg== - dependencies: - "@typescript-eslint/types" "4.0.0" - "@typescript-eslint/visitor-keys" "4.0.0" - -"@typescript-eslint/types@3.10.1": - version "3.10.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.10.1.tgz#1d7463fa7c32d8a23ab508a803ca2fe26e758727" - integrity sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ== - -"@typescript-eslint/types@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.0.0.tgz#ec1f9fc06b8558a1d5afa6e337182d08beece7f5" - integrity sha512-bK+c2VLzznX2fUWLK6pFDv3cXGTp7nHIuBMq1B9klA+QCsqLHOOqe5TQReAQDl7DN2RfH+neweo0oC5hYlG7Rg== - -"@typescript-eslint/typescript-estree@3.10.1": - version "3.10.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz#fd0061cc38add4fad45136d654408569f365b853" - integrity sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w== - dependencies: - "@typescript-eslint/types" "3.10.1" - "@typescript-eslint/visitor-keys" "3.10.1" + "@typescript-eslint/scope-manager" "4.23.0" + "@typescript-eslint/types" "4.23.0" + "@typescript-eslint/typescript-estree" "4.23.0" debug "^4.1.1" - glob "^7.1.6" - is-glob "^4.0.1" - lodash "^4.17.15" - semver "^7.3.2" - tsutils "^3.17.1" -"@typescript-eslint/typescript-estree@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.0.0.tgz#2244c63de2f2190bc5718eb0fb3fd2c437d42097" - integrity sha512-ewFMPi2pMLDNIXGMPdf8r7El2oPSZw9PEYB0j+WcpKd7AX2ARmajGa7RUHTukllWX2bj4vWX6JLE1Oih2BMokA== +"@typescript-eslint/scope-manager@4.23.0": + version "4.23.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.23.0.tgz#8792ef7eacac122e2ec8fa2d30a59b8d9a1f1ce4" + integrity sha512-ZZ21PCFxPhI3n0wuqEJK9omkw51wi2bmeKJvlRZPH5YFkcawKOuRMQMnI8mH6Vo0/DoHSeZJnHiIx84LmVQY+w== dependencies: - "@typescript-eslint/types" "4.0.0" - "@typescript-eslint/visitor-keys" "4.0.0" + "@typescript-eslint/types" "4.23.0" + "@typescript-eslint/visitor-keys" "4.23.0" + +"@typescript-eslint/types@4.23.0": + version "4.23.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.23.0.tgz#da1654c8a5332f4d1645b2d9a1c64193cae3aa3b" + integrity sha512-oqkNWyG2SLS7uTWLZf6Sr7Dm02gA5yxiz1RP87tvsmDsguVATdpVguHr4HoGOcFOpCvx9vtCSCyQUGfzq28YCw== + +"@typescript-eslint/typescript-estree@4.23.0": + version "4.23.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.23.0.tgz#0753b292097523852428a6f5a1aa8ccc1aae6cd9" + integrity sha512-5Sty6zPEVZF5fbvrZczfmLCOcby3sfrSPu30qKoY1U3mca5/jvU5cwsPb/CO6Q3ByRjixTMIVsDkqwIxCf/dMw== + dependencies: + "@typescript-eslint/types" "4.23.0" + "@typescript-eslint/visitor-keys" "4.23.0" debug "^4.1.1" globby "^11.0.1" is-glob "^4.0.1" - lodash "^4.17.15" semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/visitor-keys@3.10.1": - version "3.10.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz#cd4274773e3eb63b2e870ac602274487ecd1e931" - integrity sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ== +"@typescript-eslint/visitor-keys@4.23.0": + version "4.23.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.23.0.tgz#7215cc977bd3b4ef22467b9023594e32f9e4e455" + integrity sha512-5PNe5cmX9pSifit0H+nPoQBXdbNzi5tOEec+3riK+ku4e3er37pKxMKDH5Ct5Y4fhWxcD4spnlYjxi9vXbSpwg== dependencies: - eslint-visitor-keys "^1.1.0" - -"@typescript-eslint/visitor-keys@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.0.0.tgz#e2bbb69d98076d6a3f06abcb2048225a74362c33" - integrity sha512-sTouJbv6rjVJeTE4lpSBVYXq/u5K3gbB6LKt7ccFEZPTZB/VeQ0ssUz9q5Hx++sCqBbdF8PzrrgvEnicXAR6NQ== - dependencies: - "@typescript-eslint/types" "4.0.0" + "@typescript-eslint/types" "4.23.0" eslint-visitor-keys "^2.0.0" "@ungap/promise-all-settled@1.1.2": @@ -548,162 +512,143 @@ resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== -"@webassemblyjs/ast@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" - integrity sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA== +"@webassemblyjs/ast@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.0.tgz#a5aa679efdc9e51707a4207139da57920555961f" + integrity sha512-kX2W49LWsbthrmIRMbQZuQDhGtjyqXfEmmHyEi4XWnSZtPmxY0+3anPIzsnRb45VH/J55zlOfWvZuY47aJZTJg== dependencies: - "@webassemblyjs/helper-module-context" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/wast-parser" "1.9.0" + "@webassemblyjs/helper-numbers" "1.11.0" + "@webassemblyjs/helper-wasm-bytecode" "1.11.0" -"@webassemblyjs/floating-point-hex-parser@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz#3c3d3b271bddfc84deb00f71344438311d52ffb4" - integrity sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA== +"@webassemblyjs/floating-point-hex-parser@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.0.tgz#34d62052f453cd43101d72eab4966a022587947c" + integrity sha512-Q/aVYs/VnPDVYvsCBL/gSgwmfjeCb4LW8+TMrO3cSzJImgv8lxxEPM2JA5jMrivE7LSz3V+PFqtMbls3m1exDA== -"@webassemblyjs/helper-api-error@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz#203f676e333b96c9da2eeab3ccef33c45928b6a2" - integrity sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw== +"@webassemblyjs/helper-api-error@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.0.tgz#aaea8fb3b923f4aaa9b512ff541b013ffb68d2d4" + integrity sha512-baT/va95eXiXb2QflSx95QGT5ClzWpGaa8L7JnJbgzoYeaA27FCvuBXU758l+KXWRndEmUXjP0Q5fibhavIn8w== -"@webassemblyjs/helper-buffer@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz#a1442d269c5feb23fcbc9ef759dac3547f29de00" - integrity sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA== +"@webassemblyjs/helper-buffer@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.0.tgz#d026c25d175e388a7dbda9694e91e743cbe9b642" + integrity sha512-u9HPBEl4DS+vA8qLQdEQ6N/eJQ7gT7aNvMIo8AAWvAl/xMrcOSiI2M0MAnMCy3jIFke7bEee/JwdX1nUpCtdyA== -"@webassemblyjs/helper-code-frame@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz#647f8892cd2043a82ac0c8c5e75c36f1d9159f27" - integrity sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA== +"@webassemblyjs/helper-numbers@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.0.tgz#7ab04172d54e312cc6ea4286d7d9fa27c88cd4f9" + integrity sha512-DhRQKelIj01s5IgdsOJMKLppI+4zpmcMQ3XboFPLwCpSNH6Hqo1ritgHgD0nqHeSYqofA6aBN/NmXuGjM1jEfQ== dependencies: - "@webassemblyjs/wast-printer" "1.9.0" + "@webassemblyjs/floating-point-hex-parser" "1.11.0" + "@webassemblyjs/helper-api-error" "1.11.0" + "@xtuc/long" "4.2.2" -"@webassemblyjs/helper-fsm@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz#c05256b71244214671f4b08ec108ad63b70eddb8" - integrity sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw== +"@webassemblyjs/helper-wasm-bytecode@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.0.tgz#85fdcda4129902fe86f81abf7e7236953ec5a4e1" + integrity sha512-MbmhvxXExm542tWREgSFnOVo07fDpsBJg3sIl6fSp9xuu75eGz5lz31q7wTLffwL3Za7XNRCMZy210+tnsUSEA== -"@webassemblyjs/helper-module-context@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz#25d8884b76839871a08a6c6f806c3979ef712f07" - integrity sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g== +"@webassemblyjs/helper-wasm-section@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.0.tgz#9ce2cc89300262509c801b4af113d1ca25c1a75b" + integrity sha512-3Eb88hcbfY/FCukrg6i3EH8H2UsD7x8Vy47iVJrP967A9JGqgBVL9aH71SETPx1JrGsOUVLo0c7vMCN22ytJew== dependencies: - "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/helper-buffer" "1.11.0" + "@webassemblyjs/helper-wasm-bytecode" "1.11.0" + "@webassemblyjs/wasm-gen" "1.11.0" -"@webassemblyjs/helper-wasm-bytecode@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz#4fed8beac9b8c14f8c58b70d124d549dd1fe5790" - integrity sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw== - -"@webassemblyjs/helper-wasm-section@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz#5a4138d5a6292ba18b04c5ae49717e4167965346" - integrity sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-buffer" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/wasm-gen" "1.9.0" - -"@webassemblyjs/ieee754@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz#15c7a0fbaae83fb26143bbacf6d6df1702ad39e4" - integrity sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg== +"@webassemblyjs/ieee754@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.0.tgz#46975d583f9828f5d094ac210e219441c4e6f5cf" + integrity sha512-KXzOqpcYQwAfeQ6WbF6HXo+0udBNmw0iXDmEK5sFlmQdmND+tr773Ti8/5T/M6Tl/413ArSJErATd8In3B+WBA== dependencies: "@xtuc/ieee754" "^1.2.0" -"@webassemblyjs/leb128@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.9.0.tgz#f19ca0b76a6dc55623a09cffa769e838fa1e1c95" - integrity sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw== +"@webassemblyjs/leb128@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.0.tgz#f7353de1df38aa201cba9fb88b43f41f75ff403b" + integrity sha512-aqbsHa1mSQAbeeNcl38un6qVY++hh8OpCOzxhixSYgbRfNWcxJNJQwe2rezK9XEcssJbbWIkblaJRwGMS9zp+g== dependencies: "@xtuc/long" "4.2.2" -"@webassemblyjs/utf8@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.9.0.tgz#04d33b636f78e6a6813227e82402f7637b6229ab" - integrity sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w== +"@webassemblyjs/utf8@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.0.tgz#86e48f959cf49e0e5091f069a709b862f5a2cadf" + integrity sha512-A/lclGxH6SpSLSyFowMzO/+aDEPU4hvEiooCMXQPcQFPPJaYcPQNKGOCLUySJsYJ4trbpr+Fs08n4jelkVTGVw== -"@webassemblyjs/wasm-edit@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz#3fe6d79d3f0f922183aa86002c42dd256cfee9cf" - integrity sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw== +"@webassemblyjs/wasm-edit@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.0.tgz#ee4a5c9f677046a210542ae63897094c2027cb78" + integrity sha512-JHQ0damXy0G6J9ucyKVXO2j08JVJ2ntkdJlq1UTiUrIgfGMmA7Ik5VdC/L8hBK46kVJgujkBIoMtT8yVr+yVOQ== dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-buffer" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/helper-wasm-section" "1.9.0" - "@webassemblyjs/wasm-gen" "1.9.0" - "@webassemblyjs/wasm-opt" "1.9.0" - "@webassemblyjs/wasm-parser" "1.9.0" - "@webassemblyjs/wast-printer" "1.9.0" + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/helper-buffer" "1.11.0" + "@webassemblyjs/helper-wasm-bytecode" "1.11.0" + "@webassemblyjs/helper-wasm-section" "1.11.0" + "@webassemblyjs/wasm-gen" "1.11.0" + "@webassemblyjs/wasm-opt" "1.11.0" + "@webassemblyjs/wasm-parser" "1.11.0" + "@webassemblyjs/wast-printer" "1.11.0" -"@webassemblyjs/wasm-gen@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz#50bc70ec68ded8e2763b01a1418bf43491a7a49c" - integrity sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA== +"@webassemblyjs/wasm-gen@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.0.tgz#3cdb35e70082d42a35166988dda64f24ceb97abe" + integrity sha512-BEUv1aj0WptCZ9kIS30th5ILASUnAPEvE3tVMTrItnZRT9tXCLW2LEXT8ezLw59rqPP9klh9LPmpU+WmRQmCPQ== dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/ieee754" "1.9.0" - "@webassemblyjs/leb128" "1.9.0" - "@webassemblyjs/utf8" "1.9.0" + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/helper-wasm-bytecode" "1.11.0" + "@webassemblyjs/ieee754" "1.11.0" + "@webassemblyjs/leb128" "1.11.0" + "@webassemblyjs/utf8" "1.11.0" -"@webassemblyjs/wasm-opt@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz#2211181e5b31326443cc8112eb9f0b9028721a61" - integrity sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A== +"@webassemblyjs/wasm-opt@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.0.tgz#1638ae188137f4bb031f568a413cd24d32f92978" + integrity sha512-tHUSP5F4ywyh3hZ0+fDQuWxKx3mJiPeFufg+9gwTpYp324mPCQgnuVKwzLTZVqj0duRDovnPaZqDwoyhIO8kYg== dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-buffer" "1.9.0" - "@webassemblyjs/wasm-gen" "1.9.0" - "@webassemblyjs/wasm-parser" "1.9.0" + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/helper-buffer" "1.11.0" + "@webassemblyjs/wasm-gen" "1.11.0" + "@webassemblyjs/wasm-parser" "1.11.0" -"@webassemblyjs/wasm-parser@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz#9d48e44826df4a6598294aa6c87469d642fff65e" - integrity sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA== +"@webassemblyjs/wasm-parser@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.0.tgz#3e680b8830d5b13d1ec86cc42f38f3d4a7700754" + integrity sha512-6L285Sgu9gphrcpDXINvm0M9BskznnzJTE7gYkjDbxET28shDqp27wpruyx3C2S/dvEwiigBwLA1cz7lNUi0kw== dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-api-error" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/ieee754" "1.9.0" - "@webassemblyjs/leb128" "1.9.0" - "@webassemblyjs/utf8" "1.9.0" + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/helper-api-error" "1.11.0" + "@webassemblyjs/helper-wasm-bytecode" "1.11.0" + "@webassemblyjs/ieee754" "1.11.0" + "@webassemblyjs/leb128" "1.11.0" + "@webassemblyjs/utf8" "1.11.0" -"@webassemblyjs/wast-parser@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz#3031115d79ac5bd261556cecc3fa90a3ef451914" - integrity sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw== +"@webassemblyjs/wast-printer@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.0.tgz#680d1f6a5365d6d401974a8e949e05474e1fab7e" + integrity sha512-Fg5OX46pRdTgB7rKIUojkh9vXaVN6sGYCnEiJN1GYkb0RPwShZXp6KTDqmoMdQPKhcroOXh3fEzmkWmCYaKYhQ== dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/floating-point-hex-parser" "1.9.0" - "@webassemblyjs/helper-api-error" "1.9.0" - "@webassemblyjs/helper-code-frame" "1.9.0" - "@webassemblyjs/helper-fsm" "1.9.0" + "@webassemblyjs/ast" "1.11.0" "@xtuc/long" "4.2.2" -"@webassemblyjs/wast-printer@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz#4935d54c85fef637b00ce9f52377451d00d47899" - integrity sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/wast-parser" "1.9.0" - "@xtuc/long" "4.2.2" +"@webpack-cli/configtest@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-1.0.3.tgz#204bcff87cda3ea4810881f7ea96e5f5321b87b9" + integrity sha512-WQs0ep98FXX2XBAfQpRbY0Ma6ADw8JR6xoIkaIiJIzClGOMqVRvPCWqndTxf28DgFopWan0EKtHtg/5W1h0Zkw== -"@webpack-cli/info@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-1.1.0.tgz#c596d5bc48418b39df00c5ed7341bf0f102dbff1" - integrity sha512-uNWSdaYHc+f3LdIZNwhdhkjjLDDl3jP2+XBqAq9H8DjrJUvlOKdP8TNruy1yEaDfgpAIgbSAN7pye4FEHg9tYQ== +"@webpack-cli/info@^1.2.4": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-1.2.4.tgz#7381fd41c9577b2d8f6c2594fad397ef49ad5573" + integrity sha512-ogE2T4+pLhTTPS/8MM3IjHn0IYplKM4HbVNMCWA9N4NrdPzunwenpCsqKEXyejMfRu6K8mhauIPYf8ZxWG5O6g== dependencies: envinfo "^7.7.3" -"@webpack-cli/serve@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.1.0.tgz#13ad38f89b6e53d1133bac0006a128217a6ebf92" - integrity sha512-7RfnMXCpJ/NThrhq4gYQYILB18xWyoQcBey81oIyVbmgbc6m5ZHHyFK+DyH7pLHJf0p14MxL4mTsoPAgBSTpIg== +"@webpack-cli/serve@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.4.0.tgz#f84fd07bcacefe56ce762925798871092f0f228e" + integrity sha512-xgT/HqJ+uLWGX+Mzufusl3cgjAcnqYYskaB7o0vRcwOEfuu6hMzSILQpnIzFMGsTaeaX4Nnekl+6fadLbl1/Vg== "@xtuc/ieee754@^1.2.0": version "1.2.0" @@ -736,40 +681,38 @@ acorn-globals@^6.0.0: acorn "^7.1.1" acorn-walk "^7.1.1" -acorn-jsx@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.2.0.tgz#4c66069173d6fdd68ed85239fc256226182b2ebe" - integrity sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ== +acorn-jsx@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" + integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== acorn-walk@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.1.1.tgz#345f0dffad5c735e7373d2fec9a1023e6a44b83e" - integrity sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ== + version "7.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" + integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== acorn@^7.1.1, acorn@^7.4.0: - version "7.4.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.0.tgz#e1ad486e6c54501634c6c397c5c121daa383607c" - integrity sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w== + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.0.4: - version "8.0.4" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.0.4.tgz#7a3ae4191466a6984eee0fe3407a4f3aa9db8354" - integrity sha512-XNP0PqF1XD19ZlLKvB7cMmnZswW4C/03pRHgirB30uSJTaS3A3V1/P4sS3HPvFmjoriPCJQs+JDSbm4bL1TxGQ== +acorn@^8.1.0, acorn@^8.2.1: + version "8.2.4" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.2.4.tgz#caba24b08185c3b56e3168e97d15ed17f4d31fd0" + integrity sha512-Ibt84YwBDDA890eDiDCEqcbwvHlBvzzDkU2cGBBDDI1QWT12jTiXIOn2CIw5KK4i6N5Z2HUxwYjzriDyqaqqZg== + +agent-base@5: + version "5.1.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c" + integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g== agent-base@6: - version "6.0.1" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.1.tgz#808007e4e5867decb0ab6ab2f928fbdb5a596db4" - integrity sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg== + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== dependencies: debug "4" -agent-base@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" - integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== - dependencies: - es6-promisify "^5.0.0" - aggregate-error@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.0.1.tgz#db2fe7246e536f40d9b5442a39e117d7dd6a24e0" @@ -783,7 +726,17 @@ ajv-keywords@^3.5.2: resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== -ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.4, ajv@^6.12.5, ajv@^6.5.5: +ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ajv@^6.12.5: version "6.12.5" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.5.tgz#19b0e8bae8f476e5ba666300387775fb1a00a4da" integrity sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag== @@ -793,10 +746,15 @@ ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.4, ajv@^6.12.5, ajv@^6.5.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ansi-colors@3.2.3: - version "3.2.3" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813" - integrity sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw== +ajv@^8.0.1: + version "8.3.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.3.0.tgz#25ee7348e32cdc4a1dbb38256bf6bdc451dd577c" + integrity sha512-RYE7B5An83d7eWnDR8kbdaIFqmKCNsP16ay1hDbJEU+sa0e3H9SebskCt0Uufem6cfAVu7Col6ubcn/W+Sm8/Q== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" ansi-colors@4.1.1, ansi-colors@^4.1.1: version "4.1.1" @@ -813,17 +771,12 @@ ansi-regex@^3.0.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= -ansi-regex@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" - integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== - ansi-regex@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== -ansi-styles@^3.2.0, ansi-styles@^3.2.1: +ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== @@ -846,10 +799,10 @@ anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" -app-root-path@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.2.1.tgz#d0df4a682ee408273583d43f6f79e9892624bc9a" - integrity sha512-91IFKeKk7FjfmezPKkwtaRvSpnUc4gDwPAjA1YZ9Gn0q0PPeW+vbeUsZuyDwjI7+QTHhcLen2v25fi/AmhvbJA== +app-root-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-3.0.0.tgz#210b6f43873227e18a4b810a032283311555d5ad" + integrity sha512-qMcx+Gy2UZynHjOHOIXPNvpf+9cjvk3cWrBBK7zg4gH9+clobJRb9NGzcT7mQTcV/6Gm/1WelUtqxVXnNlrwcw== append-transform@^2.0.0: version "2.0.0" @@ -870,10 +823,10 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" -array-back@^4.0.0, array-back@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/array-back/-/array-back-4.0.1.tgz#9b80312935a52062e1a233a9c7abeb5481b30e90" - integrity sha512-Z/JnaVEXv+A9xabHzN43FiiiWEE7gPCRXMrVmRm00tWbjZRul1iHm7ECzlyNq1p4a4ATXz+G9FJ3GqGOkOV3fg== +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== array-filter@^1.0.0: version "1.0.0" @@ -907,29 +860,32 @@ assertion-error@^1.1.0: resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== -astral-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" - integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== async-limiter@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== -async@^2.6.1: - version "2.6.3" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" - integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== - dependencies: - lodash "^4.17.14" +async@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720" + integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw== asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= -available-typed-arrays@^1.0.0, available-typed-arrays@^1.0.2: +at-least-node@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" + integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== + +available-typed-arrays@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz#6b098ca9d8039079ee3f77f7b783c4480ba513f5" integrity sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ== @@ -942,15 +898,20 @@ aws-sign2@~0.7.0: integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= aws4@^1.8.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e" - integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug== + version "1.11.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" + integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + bcrypt-pbkdf@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" @@ -958,16 +919,20 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -big.js@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" - integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== - binary-extensions@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== +bl@^4.0.3: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + body-parser@1.19.0: version "1.19.0" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" @@ -1010,14 +975,15 @@ browser-stdout@1.3.1: integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== browserslist@^4.14.5: - version "4.14.5" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.14.5.tgz#1c751461a102ddc60e40993639b709be7f2c4015" - integrity sha512-Z+vsCZIvCBvqLoYkBFTwEYH3v5MCQbsAjp50ERycpOjnPmolg1Gjy4+KaWWpm8QOJt9GHkhdqAl14NpCX73CWA== + version "4.16.6" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.6.tgz#d7901277a5a88e554ed305b183ec9b0c08f66fa2" + integrity sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ== dependencies: - caniuse-lite "^1.0.30001135" - electron-to-chromium "^1.3.571" - escalade "^3.1.0" - node-releases "^1.1.61" + caniuse-lite "^1.0.30001219" + colorette "^1.2.2" + electron-to-chromium "^1.3.723" + escalade "^3.1.1" + node-releases "^1.1.71" buffer-crc32@~0.2.3: version "0.2.13" @@ -1029,6 +995,14 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== +buffer@^5.2.1, buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + builtin-modules@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" @@ -1049,6 +1023,14 @@ caching-transform@^4.0.0: package-hash "^4.0.0" write-file-atomic "^3.0.0" +call-bind@^1.0.0, call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -1064,29 +1046,29 @@ camelcase@^6.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.1.0.tgz#27dc176173725fb0adf8a48b647f4d7871944d78" integrity sha512-WCMml9ivU60+8rEJgELlFp1gxFcEGxwYleE3bziHEDeqsqAWGHdimB7beBFGjLzVNgPGyDsfgXLQEYMpmIFnVQ== -caniuse-lite@^1.0.30001135: - version "1.0.30001154" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001154.tgz#f3bbc245ce55e4c1cd20fa731b097880181a7f17" - integrity sha512-y9DvdSti8NnYB9Be92ddMZQrcOe04kcQtcxtBx4NkB04+qZ+JUWotnXBJTmxlKudhxNTQ3RRknMwNU2YQl/Org== +caniuse-lite@^1.0.30001219: + version "1.0.30001228" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001228.tgz#bfdc5942cd3326fa51ee0b42fbef4da9d492a7fa" + integrity sha512-QQmLOGJ3DEgokHbMSA8cj2a+geXqmnpyOFT0lhQV6P3/YOJvGDEwoedcwxEQ30gJIwIIunHIicunJ2rzK5gB2A== caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= -chai@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/chai/-/chai-4.2.0.tgz#760aa72cf20e3795e84b12877ce0e83737aa29e5" - integrity sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw== +chai@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.4.tgz#b55e655b31e1eac7099be4c08c21964fce2e6c49" + integrity sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA== dependencies: assertion-error "^1.1.0" check-error "^1.0.2" deep-eql "^3.0.1" get-func-name "^2.0.0" - pathval "^1.1.0" + pathval "^1.1.1" type-detect "^4.0.5" -chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.0, chalk@^2.4.2: +chalk@^2.0.0, chalk@^2.3.0: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -1103,15 +1085,23 @@ chalk@^4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad" + integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + check-error@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII= -chokidar@3.4.3: - version "3.4.3" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.3.tgz#c1df38231448e45ca4ac588e6c79573ba6a57d5b" - integrity sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ== +chokidar@3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a" + integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw== dependencies: anymatch "~3.1.1" braces "~3.0.2" @@ -1121,55 +1111,50 @@ chokidar@3.4.3: normalize-path "~3.0.0" readdirp "~3.5.0" optionalDependencies: - fsevents "~2.1.2" + fsevents "~2.3.1" + +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== chrome-devtools-frontend@1.0.445684: version "1.0.445684" resolved "https://registry.yarnpkg.com/chrome-devtools-frontend/-/chrome-devtools-frontend-1.0.445684.tgz#8540131836024df2b70fe90d0322af368931d762" integrity sha1-hUATGDYCTfK3D+kNAyKvNokx12I= -chrome-timeline@0.0.12: - version "0.0.12" - resolved "https://registry.yarnpkg.com/chrome-timeline/-/chrome-timeline-0.0.12.tgz#1516223b4bf289750b4b244c3f88d0095b10f914" - integrity sha512-lDVZGV2VYVS7kTmoLxwOmoTvKLxBHRiBQqGNvD87Bky+uIsnK6ThwESqIj+rsjx218Vq9tU5H6Bv2u6IsafS/g== +chrome-timeline@0.0.15: + version "0.0.15" + resolved "https://registry.yarnpkg.com/chrome-timeline/-/chrome-timeline-0.0.15.tgz#4fbc719d6abf08e0a0b1f0d5fea80ac461c4410b" + integrity sha512-6vc+MiyFsiOIAje+Z9MFurk9sDnKE5/WknPDpvPfApzApUxdhrml2ybTBRe2U3m2v0vYGis4VMLn9nuuoiofDA== dependencies: "@types/app-root-path" "^1.2.4" - "@types/fs-extra" "^7.0.0" - "@types/puppeteer" "^1.12.4" - app-root-path "^2.2.1" + "@types/fs-extra" "9.0.1" + "@types/puppeteer" "^5.4.3" + app-root-path "^3.0.0" devtools-timeline-model "^1.4.0" - puppeteer "^1.17.0" - simple-git "^1.113.0" - winston "^3.2.1" + fs-extra "9.0.1" + puppeteer "^5.5.0" + simple-git "^2.37.0" + winston "^3.3.3" chrome-trace-event@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" - integrity sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ== - dependencies: - tslib "^1.9.0" + version "1.0.3" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" + integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== clean-stack@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== -cli-table@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.1.tgz#f53b05266a8b1a0b934b3d0821e6e2dc5914ae23" - integrity sha1-9TsFJmqLGguTSz0IIebi3FkUriM= +cli-table@^0.3.6: + version "0.3.6" + resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.6.tgz#e9d6aa859c7fe636981fd3787378c2a20bce92fc" + integrity sha512-ZkNZbnZjKERTY5NwC2SeMeLeifSPq/pubeRoTpdr3WchLlnZg6hEgvHkK5zL7KNFdd9PmHN8lxrENUwI3cE8vQ== dependencies: colors "1.0.3" -cliui@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" - integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== - dependencies: - string-width "^3.1.0" - strip-ansi "^5.2.0" - wrap-ansi "^5.1.0" - cliui@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" @@ -1179,6 +1164,24 @@ cliui@^6.0.0: strip-ansi "^6.0.0" wrap-ansi "^6.2.0" +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +clone-deep@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" + integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== + dependencies: + is-plain-object "^2.0.4" + kind-of "^6.0.2" + shallow-clone "^3.0.0" + clone@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" @@ -1209,9 +1212,9 @@ color-name@^1.0.0, color-name@~1.1.4: integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== color-string@^1.5.2: - version "1.5.3" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.3.tgz#c9bbc5f01b58b5492f3d6857459cb6590ce204cc" - integrity sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw== + version "1.5.5" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.5.tgz#65474a8f0e7439625f3d27a6a19d89fc45223014" + integrity sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg== dependencies: color-name "^1.0.0" simple-swizzle "^0.2.2" @@ -1224,15 +1227,10 @@ color@3.0.x: color-convert "^1.9.1" color-string "^1.5.2" -colorette@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b" - integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw== - -colornames@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/colornames/-/colornames-1.1.1.tgz#f8889030685c7c4ff9e2a559f5077eb76a816f96" - integrity sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y= +colorette@^1.2.1, colorette@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" + integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== colors@1.0.3: version "1.0.3" @@ -1267,51 +1265,36 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" -command-line-usage@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/command-line-usage/-/command-line-usage-6.1.0.tgz#f28376a3da3361ff3d36cfd31c3c22c9a64c7cb6" - integrity sha512-Ew1clU4pkUeo6AFVDFxCbnN7GIZfXl48HIOQeFQnkO3oOqvpI7wdqtLRwv9iOCZ/7A+z4csVZeiDdEcj8g6Wiw== - dependencies: - array-back "^4.0.0" - chalk "^2.4.2" - table-layout "^1.0.0" - typical "^5.2.0" - commander@^2.12.1, commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.0.tgz#b990bfb8ac030aedc6d11bc04d1488ffef56db75" - integrity sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q== +commander@^6.1.0, commander@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" + integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== + +commander@^7.0.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= -complex.js@2.0.11: - version "2.0.11" - resolved "https://registry.yarnpkg.com/complex.js/-/complex.js-2.0.11.tgz#09a873fbf15ffd8c18c9c2201ccef425c32b8bf1" - integrity sha512-6IArJLApNtdg1P1dFtn3dnyzoZBEF0MwMnrfF1exSBRpZYoy4yieMkpZhQDC0uwctw48vii0CFVyHfpgZ/DfGw== +complex.js@^2.0.11: + version "2.0.12" + resolved "https://registry.yarnpkg.com/complex.js/-/complex.js-2.0.12.tgz#fa4df97d8928e5f7b6a86b35bdeecc3a3eda8a22" + integrity sha512-oQX99fwL6LrTVg82gDY1dIWXy6qZRnRL35N+YhIX0N7tSwsa0KFy6IEMHTNuCW4mP7FS7MEqZ/2I/afzYwPldw== concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@^1.6.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" - integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== - dependencies: - buffer-from "^1.0.0" - inherits "^2.0.3" - readable-stream "^2.2.2" - typedarray "^0.0.6" - content-disposition@0.5.3: version "0.5.3" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" @@ -1346,7 +1329,7 @@ core-util-is@1.0.2, core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= -cross-spawn@^7.0.0, cross-spawn@^7.0.2: +cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -1365,10 +1348,10 @@ cssom@~0.3.6: resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== -cssstyle@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.2.0.tgz#e4c44debccd6b7911ed617a4395e5754bba59992" - integrity sha512-sEb3XFPx3jNnCAMtqrXPDeSgQr+jojtCeNf8cvMNMh1cG970+lljssvQDzPq6lmmJu2Vhqood/gtEomBiHOGnA== +cssstyle@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" + integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== dependencies: cssom "~0.3.6" @@ -1388,27 +1371,27 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" -debug@2.6.9, debug@^2.6.9: +debug@2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== dependencies: ms "2.0.0" -debug@3.2.6, debug@^3.1.0: - version "3.2.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" - integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== - dependencies: - ms "^2.1.1" - -debug@4, debug@4.2.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: +debug@4, debug@^4.1.0, debug@^4.1.1: version "4.2.0" resolved "https://registry.yarnpkg.com/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1" integrity sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg== dependencies: ms "2.1.2" +debug@4.3.1, debug@^4.0.1, debug@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== + dependencies: + ms "2.1.2" + decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -1419,10 +1402,10 @@ decamelize@^4.0.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== -decimal.js@10.2.0, decimal.js@^10.0.0, decimal.js@^10.2.0: - version "10.2.0" - resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.0.tgz#39466113a9e036111d02f82489b5fd6b0b5ed231" - integrity sha512-vDPw+rDgn3bZe1+F/pyEwb1oMG2XTlRVgAa6B4KccTEpYgF8w6eQllVbQcfIJnZyvzFtFpxnpGtx8dd7DJp/Rw== +decimal.js@^10.0.0, decimal.js@^10.2.1: + version "10.2.1" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.1.tgz#238ae7b0f0c793d3e3cea410108b35a2c01426a3" + integrity sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw== deep-eql@^3.0.1: version "3.0.1" @@ -1431,31 +1414,27 @@ deep-eql@^3.0.1: dependencies: type-detect "^4.0.0" -deep-equal@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.0.4.tgz#6b0b407a074666033169df3acaf128e1c6f3eab6" - integrity sha512-BUfaXrVoCfgkOQY/b09QdO9L3XNoF2XH0A3aY9IQwQL/ZjLOe8FQgCNVl1wiolhsFo8kFdO9zdPViCPbmaJA5w== +deep-equal@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.0.5.tgz#55cd2fe326d83f9cbf7261ef0e060b3f724c5cb9" + integrity sha512-nPiRgmbAtm1a3JsnLCf6/SLfXcjyN5v8L1TXzdCmHrXJ4hx+gW/w1YCcn7z8gJtSiDArZCgYtbao3QqLm/N1Sw== dependencies: - es-abstract "^1.18.0-next.1" - es-get-iterator "^1.1.0" + call-bind "^1.0.0" + es-get-iterator "^1.1.1" + get-intrinsic "^1.0.1" is-arguments "^1.0.4" is-date-object "^1.0.2" is-regex "^1.1.1" isarray "^2.0.5" - object-is "^1.1.3" + object-is "^1.1.4" object-keys "^1.1.1" - object.assign "^4.1.1" + object.assign "^4.1.2" regexp.prototype.flags "^1.3.0" side-channel "^1.0.3" which-boxed-primitive "^1.0.1" which-collection "^1.0.1" which-typed-array "^1.1.2" -deep-extend@~0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" @@ -1475,7 +1454,7 @@ defaults@^1.0.3: dependencies: clone "^1.0.2" -define-properties@^1.1.2, define-properties@^1.1.3: +define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== @@ -1497,6 +1476,11 @@ destroy@~1.0.4: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= +devtools-protocol@0.0.818844: + version "0.0.818844" + resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.818844.tgz#d1947278ec85b53e4c8ca598f607a28fa785ba9e" + integrity sha512-AD1hi7iVJ8OD0aMLQU5VK0XH9LDlA1+BcPIgrAxPfaibx2DbWucuyOhc4oyQCbnvDDO68nN6/LcKfqTP343Jjg== + devtools-timeline-model@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/devtools-timeline-model/-/devtools-timeline-model-1.4.0.tgz#91f9624fb0313fa3ebeda7bf99865357bc66c726" @@ -1505,21 +1489,12 @@ devtools-timeline-model@^1.4.0: chrome-devtools-frontend "1.0.445684" resolve "1.1.7" -diagnostics@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/diagnostics/-/diagnostics-1.1.1.tgz#cab6ac33df70c9d9a727490ae43ac995a769b22a" - integrity sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ== - dependencies: - colorspace "1.1.x" - enabled "1.0.x" - kuler "1.0.x" +diff@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" + integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== -diff@3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" - integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== - -diff@4.0.2, diff@^4.0.1: +diff@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== @@ -1558,134 +1533,94 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= -electron-to-chromium@^1.3.571: - version "1.3.585" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.585.tgz#71cdb722c73488b9475ad1c572cf43a763ef9081" - integrity sha512-xoeqjMQhgHDZM7FiglJAb2aeOxHZWFruUc3MbAGTgE7GB8rr5fTn1Sdh5THGuQtndU3GuXlu91ZKqRivxoCZ/A== - -emoji-regex@^7.0.1: - version "7.0.3" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" - integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== +electron-to-chromium@^1.3.723: + version "1.3.727" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.727.tgz#857e310ca00f0b75da4e1db6ff0e073cc4a91ddf" + integrity sha512-Mfz4FIB4FSvEwBpDfdipRIrwd6uo8gUDoRDF4QEYb4h4tSuI3ov594OrjU6on042UlFHouIJpClDODGkPcBSbg== emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== -emojis-list@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" - integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== - -enabled@1.0.x: - version "1.0.2" - resolved "https://registry.yarnpkg.com/enabled/-/enabled-1.0.2.tgz#965f6513d2c2d1c5f4652b64a2e3396467fc2f93" - integrity sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M= - dependencies: - env-variable "0.0.x" +enabled@2.0.x: + version "2.0.0" + resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" + integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= -end-of-stream@^1.1.0: +end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== dependencies: once "^1.4.0" -enhanced-resolve@^4.0.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz#3b806f3bfafc1ec7de69551ef93cca46c1704126" - integrity sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ== - dependencies: - graceful-fs "^4.1.2" - memory-fs "^0.5.0" - tapable "^1.0.0" - -enhanced-resolve@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.3.1.tgz#3f988d0d7775bdc2d96ede321dc81f8249492f57" - integrity sha512-G1XD3MRGrGfNcf6Hg0LVZG7GIKcYkbfHa5QMxt1HDUTdYoXH0JR1xXyg+MaKLF73E9A27uWNVxvFivNRYeUB6w== +enhanced-resolve@^5.0.0, enhanced-resolve@^5.8.0: + version "5.8.2" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.8.2.tgz#15ddc779345cbb73e97c611cd00c01c1e7bf4d8b" + integrity sha512-F27oB3WuHDzvR2DOGNTaYy0D5o0cnrv8TeI482VM4kYgQd/FT9lUQwuNsJ0oOHtBUq7eiW5ytqzp7nBFknL+GA== dependencies: graceful-fs "^4.2.4" - tapable "^2.0.0" + tapable "^2.2.0" -enquirer@^2.3.5, enquirer@^2.3.6: +enquirer@^2.3.5: version "2.3.6" resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== dependencies: ansi-colors "^4.1.1" -env-variable@0.0.x: - version "0.0.6" - resolved "https://registry.yarnpkg.com/env-variable/-/env-variable-0.0.6.tgz#74ab20b3786c545b62b4a4813ab8cf22726c9808" - integrity sha512-bHz59NlBbtS0NhftmR8+ExBEekE7br0e01jw+kk0NDro7TtZzBYZ5ScGPs3OmwnpyfHTHOtr1Y6uedCdrIldtg== - envinfo@^7.7.3: - version "7.7.3" - resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.7.3.tgz#4b2d8622e3e7366afb8091b23ed95569ea0208cc" - integrity sha512-46+j5QxbPWza0PB1i15nZx0xQ4I/EfQxg9J8Had3b408SV63nEtor2e+oiY63amTo9KTuh2a3XLObNwduxYwwA== + version "7.8.1" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.8.1.tgz#06377e3e5f4d379fea7ac592d5ad8927e0c4d475" + integrity sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw== -errno@^0.1.3: - version "0.1.7" - resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" - integrity sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg== - dependencies: - prr "~1.0.1" - -es-abstract@^1.17.0-next.1, es-abstract@^1.17.4, es-abstract@^1.17.5: - version "1.17.5" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.5.tgz#d8c9d1d66c8981fb9200e2251d799eee92774ae9" - integrity sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg== +es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2: + version "1.18.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0.tgz#ab80b359eecb7ede4c298000390bc5ac3ec7b5a4" + integrity sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw== dependencies: + call-bind "^1.0.2" es-to-primitive "^1.2.1" function-bind "^1.1.1" + get-intrinsic "^1.1.1" has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.1.5" - is-regex "^1.0.5" - object-inspect "^1.7.0" + has-symbols "^1.0.2" + is-callable "^1.2.3" + is-negative-zero "^2.0.1" + is-regex "^1.1.2" + is-string "^1.0.5" + object-inspect "^1.9.0" object-keys "^1.1.1" - object.assign "^4.1.0" - string.prototype.trimleft "^2.1.1" - string.prototype.trimright "^2.1.1" + object.assign "^4.1.2" + string.prototype.trimend "^1.0.4" + string.prototype.trimstart "^1.0.4" + unbox-primitive "^1.0.0" -es-abstract@^1.18.0-next.0, es-abstract@^1.18.0-next.1: - version "1.18.0-next.1" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.1.tgz#6e3a0a4bda717e5023ab3b8e90bec36108d22c68" - integrity sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA== +es-get-iterator@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.2.tgz#9234c54aba713486d7ebde0220864af5e2b283f7" + integrity sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ== dependencies: - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - has "^1.0.3" + call-bind "^1.0.2" + get-intrinsic "^1.1.0" has-symbols "^1.0.1" - is-callable "^1.2.2" - is-negative-zero "^2.0.0" - is-regex "^1.1.1" - object-inspect "^1.8.0" - object-keys "^1.1.1" - object.assign "^4.1.1" - string.prototype.trimend "^1.0.1" - string.prototype.trimstart "^1.0.1" - -es-get-iterator@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.0.tgz#bb98ad9d6d63b31aacdc8f89d5d0ee57bcb5b4c8" - integrity sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ== - dependencies: - es-abstract "^1.17.4" - has-symbols "^1.0.1" - is-arguments "^1.0.4" - is-map "^2.0.1" - is-set "^2.0.1" + is-arguments "^1.1.0" + is-map "^2.0.2" + is-set "^2.0.2" is-string "^1.0.5" isarray "^2.0.5" +es-module-lexer@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.4.1.tgz#dda8c6a14d8f340a24e34331e0fab0cb50438e0e" + integrity sha512-ooYciCUtfw6/d2w56UVeqHPcoCFAiJdz5XOkYpv/Txl1HMUozpXjz/2RIQgqwKdXNDPSF1W7mJCFse3G+HDyAA== + es-to-primitive@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" @@ -1700,19 +1635,7 @@ es6-error@^4.0.1: resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== -es6-promise@^4.0.3: - version "4.2.8" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" - integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== - -es6-promisify@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" - integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= - dependencies: - es6-promise "^4.0.3" - -escalade@^3.1.0: +escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== @@ -1722,28 +1645,33 @@ escape-html@~1.0.3: resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= -escape-latex@1.2.0: +escape-latex@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/escape-latex/-/escape-latex-1.2.0.tgz#07c03818cf7dac250cce517f4fda1b001ef2bca1" integrity sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw== -escape-string-regexp@1.0.5, escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= - escape-string-regexp@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -escodegen@^1.14.1: - version "1.14.1" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.1.tgz#ba01d0c8278b5e95a9a45350142026659027a457" - integrity sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ== +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escodegen@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" + integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw== dependencies: esprima "^4.0.1" - estraverse "^4.2.0" + estraverse "^5.2.0" esutils "^2.0.2" optionator "^0.8.1" optionalDependencies: @@ -1770,17 +1698,17 @@ eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== eslint-visitor-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" - integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" + integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== -eslint@^7.12.1: - version "7.12.1" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.12.1.tgz#bd9a81fa67a6cfd51656cdb88812ce49ccec5801" - integrity sha512-HlMTEdr/LicJfN08LB3nM1rRYliDXOmfoO4vj39xN6BLpFzF00hbwBoqHk8UcJ2M/3nlARZWy/mslvGEuZFvsg== +eslint@^7.26.0: + version "7.26.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.26.0.tgz#d416fdcdcb3236cd8f282065312813f8c13982f6" + integrity sha512-4R1ieRf52/izcZE7AlLy56uIHHDLT74Yzz2Iv2l6kDaYvEu9x+wMB5dZArVL8SYGXSYV2YAg70FcW5Y5nGGNIg== dependencies: - "@babel/code-frame" "^7.0.0" - "@eslint/eslintrc" "^0.2.1" + "@babel/code-frame" "7.12.11" + "@eslint/eslintrc" "^0.4.1" ajv "^6.10.0" chalk "^4.0.0" cross-spawn "^7.0.2" @@ -1790,13 +1718,13 @@ eslint@^7.12.1: eslint-scope "^5.1.1" eslint-utils "^2.1.0" eslint-visitor-keys "^2.0.0" - espree "^7.3.0" - esquery "^1.2.0" + espree "^7.3.1" + esquery "^1.4.0" esutils "^2.0.2" - file-entry-cache "^5.0.1" + file-entry-cache "^6.0.1" functional-red-black-tree "^1.0.1" glob-parent "^5.0.0" - globals "^12.1.0" + globals "^13.6.0" ignore "^4.0.6" import-fresh "^3.0.0" imurmurhash "^0.1.4" @@ -1804,7 +1732,7 @@ eslint@^7.12.1: js-yaml "^3.13.1" json-stable-stringify-without-jsonify "^1.0.1" levn "^0.4.1" - lodash "^4.17.19" + lodash "^4.17.21" minimatch "^3.0.4" natural-compare "^1.4.0" optionator "^0.9.1" @@ -1813,17 +1741,17 @@ eslint@^7.12.1: semver "^7.2.1" strip-ansi "^6.0.0" strip-json-comments "^3.1.0" - table "^5.2.3" + table "^6.0.4" text-table "^0.2.0" v8-compile-cache "^2.0.3" -espree@^7.3.0: - version "7.3.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.0.tgz#dc30437cf67947cf576121ebd780f15eeac72348" - integrity sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw== +espree@^7.3.0, espree@^7.3.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" + integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== dependencies: acorn "^7.4.0" - acorn-jsx "^5.2.0" + acorn-jsx "^5.3.1" eslint-visitor-keys "^1.3.0" esprima@^4.0.0, esprima@^4.0.1: @@ -1831,10 +1759,10 @@ esprima@^4.0.0, esprima@^4.0.1: resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57" - integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ== +esquery@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== dependencies: estraverse "^5.1.0" @@ -1845,7 +1773,7 @@ esrecurse@^4.3.0: dependencies: estraverse "^5.2.0" -estraverse@^4.1.1, estraverse@^4.2.0: +estraverse@^4.1.1: version "4.3.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== @@ -1866,23 +1794,23 @@ etag@~1.8.1: integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= events@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.2.0.tgz#93b87c18f8efcd4202a461aec4dfc0556b639379" - integrity sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg== + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== -execa@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" - integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== +execa@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.0.0.tgz#4029b0007998a841fbd1032e5f4de86a3c1e3376" + integrity sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ== dependencies: - cross-spawn "^7.0.0" - get-stream "^5.0.0" - human-signals "^1.1.1" + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" is-stream "^2.0.0" merge-stream "^2.0.0" - npm-run-path "^4.0.0" - onetime "^5.1.0" - signal-exit "^3.0.2" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" strip-final-newline "^2.0.0" express-ws@^4.0.0: @@ -1933,17 +1861,7 @@ extend@~3.0.2: resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== -extract-zip@^1.6.6: - version "1.7.0" - resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.7.0.tgz#556cc3ae9df7f452c493a0cfb51cc30277940927" - integrity sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA== - dependencies: - concat-stream "^1.6.2" - debug "^2.6.9" - mkdirp "^0.5.4" - yauzl "^2.10.0" - -extract-zip@^2.0.1: +extract-zip@^2.0.0, extract-zip@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== @@ -1970,9 +1888,9 @@ fast-deep-equal@^3.1.1: integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA== fast-glob@^3.1.1: - version "3.2.4" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3" - integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ== + version "3.2.5" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.5.tgz#7939af2a656de79a4f1901903ee8adcaa7cb9661" + integrity sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" @@ -1996,10 +1914,15 @@ fast-safe-stringify@^2.0.4: resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743" integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA== +fastest-levenshtein@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2" + integrity sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow== + fastq@^1.6.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.8.0.tgz#550e1f9f59bbc65fe185cb6a9b4d95357107f481" - integrity sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q== + version "1.11.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.11.0.tgz#bb9fb955a07130a918eb63c1f5161cc32a5d0858" + integrity sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g== dependencies: reusify "^1.0.4" @@ -2010,17 +1933,17 @@ fd-slicer@~1.1.0: dependencies: pend "~1.2.0" -fecha@^2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/fecha/-/fecha-2.3.3.tgz#948e74157df1a32fd1b12c3a3c3cdcb6ec9d96cd" - integrity sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg== +fecha@^4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.1.tgz#0a83ad8f86ef62a091e22bb5a039cd03d23eecce" + integrity sha512-MMMQ0ludy/nBs1/o0zVOiKTpG7qMbonKUzjJgQFEuvq6INZ1OraKPRAWkBq5vlKLOUMpmNYG1JoN3oDPUQ9m3Q== -file-entry-cache@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" - integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== dependencies: - flat-cache "^2.0.1" + flat-cache "^3.0.4" fill-range@^7.0.1: version "7.0.1" @@ -2051,13 +1974,6 @@ find-cache-dir@^3.2.0: make-dir "^3.0.2" pkg-dir "^4.1.0" -find-up@3.0.0, find-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" - integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== - dependencies: - locate-path "^3.0.0" - find-up@5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" @@ -2074,31 +1990,28 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" -flat-cache@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" - integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== +flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== dependencies: - flatted "^2.0.0" - rimraf "2.6.3" - write "1.0.3" - -flat@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/flat/-/flat-4.1.0.tgz#090bec8b05e39cba309747f1d588f04dbaf98db2" - integrity sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw== - dependencies: - is-buffer "~2.0.3" + flatted "^3.1.0" + rimraf "^3.0.2" flat@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== -flatted@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" - integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== +flatted@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.1.1.tgz#c4b489e80096d9df1dfc97c79871aea7c617c469" + integrity sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA== + +fn.name@1.x.x: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" + integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== foreach@^2.0.5: version "2.0.5" @@ -2132,10 +2045,10 @@ forwarded@~0.1.2: resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= -fraction.js@4.0.12: - version "4.0.12" - resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.0.12.tgz#0526d47c65a5fb4854df78bc77f7bec708d7b8c3" - integrity sha512-8Z1K0VTG4hzYY7kA/1sj4/r1/RWLBD3xwReT/RCrUCbzPszjNQCCsy3ktkU/eaEqX3MYa4pY37a52eiBlPMlhA== +fraction.js@^4.0.13: + version "4.0.13" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.0.13.tgz#3c1c315fa16b35c85fffa95725a36fa729c69dfe" + integrity sha512-E1fz2Xs9ltlUp+qbiyx9wmt2n9dRzPsS11Jtdb8D2o+cC7wr9xkkKsVKJuBX0ST+LVS+LhLO+SbLJNtfWcJvXA== fresh@0.5.2: version "0.5.2" @@ -2147,15 +2060,30 @@ fromentries@^1.2.0: resolved "https://registry.yarnpkg.com/fromentries/-/fromentries-1.2.0.tgz#e6aa06f240d6267f913cea422075ef88b63e7897" integrity sha512-33X7H/wdfO99GdRLLgkjUrD4geAFdq/Uv0kl3HD4da6HDixd2GUg8Mw7dahLCV9r/EARkmtYBB6Tch4EEokFTQ== +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + +fs-extra@9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc" + integrity sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^1.0.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -fsevents@~2.1.2: - version "2.1.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" - integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== +fsevents@~2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== function-bind@^1.1.1: version "1.1.1" @@ -2172,7 +2100,7 @@ gensync@^1.0.0-beta.1: resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg== -get-caller-file@^2.0.1: +get-caller-file@^2.0.1, get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== @@ -2182,18 +2110,32 @@ get-func-name@^2.0.0: resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE= +get-intrinsic@^1.0.1, get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== -get-stream@^5.0.0, get-stream@^5.1.0: +get-stream@^5.1.0: version "5.2.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== dependencies: pump "^3.0.0" +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" @@ -2202,9 +2144,9 @@ getpass@^0.1.1: assert-plus "^1.0.0" glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@~5.1.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" - integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" @@ -2213,10 +2155,10 @@ glob-to-regexp@^0.4.1: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== -glob@7.1.3: - version "7.1.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" - integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== +glob@7.1.6, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" @@ -2225,10 +2167,10 @@ glob@7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" -glob@7.1.6, glob@^7.0.5, glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: - version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== +glob@^7.1.1, glob@^7.1.7: + version "7.1.7" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" + integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" @@ -2249,10 +2191,17 @@ globals@^12.1.0: dependencies: type-fest "^0.8.1" +globals@^13.6.0: + version "13.8.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.8.0.tgz#3e20f504810ce87a8d72e55aecf8435b50f4c1b3" + integrity sha512-rHtdA6+PDBIjeEvA91rpqzEvk/k3/i7EeNQiryiWuJH0Hw9cpyJMAt2jtbAwUaRdhD+573X4vWw6IcjKPasi9Q== + dependencies: + type-fest "^0.20.2" + globby@^11.0.1: - version "11.0.1" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357" - integrity sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ== + version "11.0.3" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.3.tgz#9b1f0cb523e171dd1ad8c7b2a9fb4b644b9593cb" + integrity sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg== dependencies: array-union "^2.1.0" dir-glob "^3.0.1" @@ -2261,11 +2210,16 @@ globby@^11.0.1: merge2 "^1.3.0" slash "^3.0.0" -graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.2.4: +graceful-fs@^4.1.15, graceful-fs@^4.1.2: version "4.2.4" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== +graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4: + version "4.2.6" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" + integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== + growl@1.10.5: version "1.10.5" resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" @@ -2277,13 +2231,18 @@ har-schema@^2.0.0: integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= har-validator@~5.1.3: - version "5.1.3" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" - integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== + version "5.1.5" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" + integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== dependencies: - ajv "^6.5.5" + ajv "^6.12.3" har-schema "^2.0.0" +has-bigints@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" + integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -2294,10 +2253,10 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-symbols@^1.0.0, has-symbols@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" - integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== +has-symbols@^1.0.1, has-symbols@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" + integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== has@^1.0.3: version "1.0.3" @@ -2362,13 +2321,13 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" -https-proxy-agent@^2.2.1: - version "2.2.4" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" - integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg== +https-proxy-agent@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz#702b71fb5520a132a66de1f67541d9e62154d82b" + integrity sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg== dependencies: - agent-base "^4.3.0" - debug "^3.1.0" + agent-base "5" + debug "4" https-proxy-agent@^5.0.0: version "5.0.0" @@ -2378,10 +2337,10 @@ https-proxy-agent@^5.0.0: agent-base "6" debug "4" -human-signals@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" - integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== iconv-lite@0.4.24: version "0.4.24" @@ -2397,6 +2356,11 @@ iconv-lite@^0.6.2: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + ignore@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" @@ -2408,9 +2372,9 @@ ignore@^5.1.4: integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== import-fresh@^3.0.0, import-fresh@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" - integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ== + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== dependencies: parent-module "^1.0.0" resolve-from "^4.0.0" @@ -2441,7 +2405,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -2456,30 +2420,27 @@ interpret@^2.2.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== -ip-regex@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" - integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= - ipaddr.js@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== -is-arguments@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3" - integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA== +is-arguments@^1.0.4, is-arguments@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.0.tgz#62353031dfbee07ceb34656a6bde59efecae8dd9" + integrity sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg== + dependencies: + call-bind "^1.0.0" is-arrayish@^0.3.1: version "0.3.2" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== -is-bigint@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.0.tgz#73da8c33208d00f130e9b5e15d23eac9215601c4" - integrity sha512-t5mGUXC/xRheCK431ylNiSkGGpBp8bHENBcENTkDT6ppwPzEVxNGZRvgvmOEfbWkFhA7D2GEuE2mmQTr78sl2g== +is-bigint@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.2.tgz#ffb381442503235ad245ea89e45b3dbff040ee5a" + integrity sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA== is-binary-path@~2.1.0: version "2.1.0" @@ -2488,30 +2449,29 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-boolean-object@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.1.tgz#10edc0900dd127697a92f6f9807c7617d68ac48e" - integrity sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ== +is-boolean-object@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.1.tgz#3c0878f035cb821228d350d2e1e36719716a3de8" + integrity sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng== + dependencies: + call-bind "^1.0.2" -is-buffer@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.4.tgz#3e572f23c8411a5cfd9557c849e3665e0b290623" - integrity sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A== +is-callable@^1.1.4, is-callable@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e" + integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ== -is-callable@^1.1.4, is-callable@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" - integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q== - -is-callable@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9" - integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA== +is-core-module@^2.2.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.4.0.tgz#8e9fc8e15027b011418026e98f0e6f4d86305cc1" + integrity sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A== + dependencies: + has "^1.0.3" is-date-object@^1.0.1, is-date-object@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" - integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.4.tgz#550cfcc03afada05eea3dd30981c7b09551f73e5" + integrity sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A== is-extglob@^2.1.1: version "2.1.1" @@ -2535,20 +2495,20 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" -is-map@^2.0.1: +is-map@^2.0.1, is-map@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" + integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg== + +is-negative-zero@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.1.tgz#520dafc4307bb8ebc33b813de5ce7c9400d644a1" - integrity sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw== + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" + integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== -is-negative-zero@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.0.tgz#9553b121b0fac28869da9ed459e20c7543788461" - integrity sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE= - -is-number-object@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197" - integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw== +is-number-object@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.5.tgz#6edfaeed7950cff19afedce9fbfca9ee6dd289eb" + integrity sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw== is-number@^7.0.0: version "7.0.0" @@ -2560,52 +2520,56 @@ is-plain-obj@^2.1.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== -is-potential-custom-element-name@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz#0c52e54bcca391bb2c494b21e8626d7336c6e397" - integrity sha1-DFLlS8yjkbssSUsh6GJtczbG45c= - -is-regex@^1.0.5, is-regex@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9" - integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg== +is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== dependencies: - has-symbols "^1.0.1" + isobject "^3.0.1" -is-set@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.1.tgz#d1604afdab1724986d30091575f54945da7e5f43" - integrity sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA== +is-potential-custom-element-name@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" + integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== -is-stream@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= +is-regex@^1.1.1, is-regex@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.3.tgz#d029f9aff6448b93ebbe3f33dac71511fdcbef9f" + integrity sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ== + dependencies: + call-bind "^1.0.2" + has-symbols "^1.0.2" + +is-set@^2.0.1, is-set@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec" + integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g== is-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== -is-string@^1.0.4, is-string@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" - integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== +is-string@^1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.6.tgz#3fe5d5992fb0d93404f32584d4b0179a71b54a5f" + integrity sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w== -is-symbol@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" - integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== dependencies: - has-symbols "^1.0.1" + has-symbols "^1.0.2" is-typed-array@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.3.tgz#a4ff5a5e672e1a55f99c7f54e59597af5c1df04d" - integrity sha512-BSYUBOK/HJibQ30wWkWold5txYwMUXQct9YHAQJr8fSwvZoiglcqB0pd7vEN23+Tsi9IUEjztdOSzl4qLVYGTQ== + version "1.1.5" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.5.tgz#f32e6e096455e329eb7b423862456aa213f0eb4e" + integrity sha512-S+GRDgJlR3PyEbsX/Fobd9cqpZBuvUS+8asRqYDMLCb2qMzt1oz5m5oxQCxOgUDxiWsOVNi4yaF+/uvdlHlYug== dependencies: - available-typed-arrays "^1.0.0" - es-abstract "^1.17.4" + available-typed-arrays "^1.0.2" + call-bind "^1.0.2" + es-abstract "^1.18.0-next.2" foreach "^2.0.5" has-symbols "^1.0.1" @@ -2644,6 +2608,11 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= +isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= + isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -2713,39 +2682,38 @@ istanbul-reports@^3.0.2: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -javascript-natural-sort@0.7.1: +javascript-natural-sort@^0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz#f9e2303d4507f6d74355a73664d1440fb5a0ef59" integrity sha1-+eIwPUUH9tdDVac2ZNFED7Wg71k= -jest-worker@^26.6.1: - version "26.6.1" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.1.tgz#c2ae8cde6802cc14056043f997469ec170d9c32a" - integrity sha512-R5IE3qSGz+QynJx8y+ICEkdI2OJ3RJjRQVEyCcFAd3yVhQSEtquziPO29Mlzgn07LOVE8u8jhJ1FqcwegiXWOw== +jest-worker@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" + integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== dependencies: "@types/node" "*" merge-stream "^2.0.0" supports-color "^7.0.0" jpeg-js@^0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.2.tgz#8b345b1ae4abde64c2da2fe67ea216a114ac279d" - integrity sha512-+az2gi/hvex7eLTMTlbRLOhH6P6WFdk2ITI8HJsaH2VqYO0I594zXSYEP+tf4FW+8Cy68ScDXoAsQdyQanv3sw== + version "0.4.3" + resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.3.tgz#6158e09f1983ad773813704be80680550eff977b" + integrity sha512-ru1HWKek8octvUHFHvE5ZzQ1yAsJmIvRdGWvSoKV52XKyuyYA437QWDttXT8eZXDSbuMpHlLzPDZUPd6idIz+Q== js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@3.13.1: - version "3.13.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" - integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== +js-yaml@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.0.0.tgz#f426bc0ff4b4051926cd588c71113183409a121f" + integrity sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q== dependencies: - argparse "^1.0.7" - esprima "^4.0.0" + argparse "^2.0.1" -js-yaml@3.14.0, js-yaml@^3.13.1: +js-yaml@^3.13.1: version "3.14.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== @@ -2758,36 +2726,36 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= -jsdom@^16.4.0: - version "16.4.0" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.4.0.tgz#36005bde2d136f73eee1a830c6d45e55408edddb" - integrity sha512-lYMm3wYdgPhrl7pDcRmvzPhhrGVBeVhPIqeHjzeiHN3DFmD1RBpbExbi8vU7BJdH8VAZYovR8DMt0PNNDM7k8w== +jsdom@^16.5.3: + version "16.5.3" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.5.3.tgz#13a755b3950eb938b4482c407238ddf16f0d2136" + integrity sha512-Qj1H+PEvUsOtdPJ056ewXM4UJPCi4hhLA8wpiz9F2YvsRBhuFsXxtrIFAgGBDynQA9isAMGE91PfUYbdMPXuTA== dependencies: - abab "^2.0.3" - acorn "^7.1.1" + abab "^2.0.5" + acorn "^8.1.0" acorn-globals "^6.0.0" cssom "^0.4.4" - cssstyle "^2.2.0" + cssstyle "^2.3.0" data-urls "^2.0.0" - decimal.js "^10.2.0" + decimal.js "^10.2.1" domexception "^2.0.1" - escodegen "^1.14.1" + escodegen "^2.0.0" html-encoding-sniffer "^2.0.1" is-potential-custom-element-name "^1.0.0" nwsapi "^2.2.0" - parse5 "5.1.1" + parse5 "6.0.1" request "^2.88.2" - request-promise-native "^1.0.8" - saxes "^5.0.0" + request-promise-native "^1.0.9" + saxes "^5.0.1" symbol-tree "^3.2.4" - tough-cookie "^3.0.1" + tough-cookie "^4.0.0" w3c-hr-time "^1.0.2" w3c-xmlserializer "^2.0.0" webidl-conversions "^6.1.0" whatwg-encoding "^1.0.5" whatwg-mimetype "^2.3.0" - whatwg-url "^8.0.0" - ws "^7.2.3" + whatwg-url "^8.5.0" + ws "^7.4.4" xml-name-validator "^3.0.0" jsesc@^2.5.1: @@ -2805,6 +2773,11 @@ json-schema-traverse@^0.4.1: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + json-schema@0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" @@ -2820,13 +2793,6 @@ json-stringify-safe@~5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= -json5@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" - integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== - dependencies: - minimist "^1.2.0" - json5@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.2.tgz#43ef1f0af9835dd624751a6b7fa48874fb2d608e" @@ -2834,6 +2800,15 @@ json5@^2.1.2: dependencies: minimist "^1.2.5" +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -2844,17 +2819,15 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" -kuler@1.0.x: - version "1.0.1" - resolved "https://registry.yarnpkg.com/kuler/-/kuler-1.0.1.tgz#ef7c784f36c9fb6e16dd3150d152677b2b0228a6" - integrity sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ== - dependencies: - colornames "^1.1.1" +kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== -leven@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" - integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== +kuler@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" + integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== levn@^0.4.1: version "0.4.1" @@ -2872,36 +2845,10 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" -loader-runner@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.1.0.tgz#f70bc0c29edbabdf2043e7ee73ccc3fe1c96b42d" - integrity sha512-oR4lB4WvwFoC70ocraKhn5nkKSs23t57h9udUgw8o0iH8hMXeEoRuUgfcvgUwAJ1ZpRqBvcou4N2SMvM1DwMrA== - -loader-utils@^1.0.2: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" - integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^1.0.1" - -loader-utils@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0" - integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^2.1.2" - -locate-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" - integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== - dependencies: - p-locate "^3.0.0" - path-exists "^3.0.0" +loader-runner@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384" + integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw== locate-path@^5.0.0: version "5.0.0" @@ -2917,27 +2864,25 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" +lodash.clonedeep@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= + lodash.flattendeep@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI= -lodash.sortby@^4.7.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" - integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= +lodash.truncate@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" + integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= -lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19: - version "4.17.20" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" - integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== - -log-symbols@2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" - integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg== - dependencies: - chalk "^2.0.1" +lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.7.0: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== log-symbols@4.0.0: version "4.0.0" @@ -2946,17 +2891,24 @@ log-symbols@4.0.0: dependencies: chalk "^4.0.0" -logform@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/logform/-/logform-2.1.2.tgz#957155ebeb67a13164069825ce67ddb5bb2dd360" - integrity sha512-+lZh4OpERDBLqjiwDLpAWNQu6KMjnlXH2ByZwCuSqVPJletw0kTWJf5CgSNAUKn1KUkv3m2cUz/LK8zyEy7wzQ== +logform@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/logform/-/logform-2.2.0.tgz#40f036d19161fc76b68ab50fdc7fe495544492f2" + integrity sha512-N0qPlqfypFx7UHNn4B3lzS/b0uLqt2hmuoa+PpuXNYgozdJYAyauF5Ky0BWVjrxDlMWiT3qN4zPq3vVAfZy7Yg== dependencies: colors "^1.2.1" fast-safe-stringify "^2.0.4" - fecha "^2.3.3" + fecha "^4.2.0" ms "^2.1.1" triple-beam "^1.3.0" +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + make-dir@^3.0.0, make-dir@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.0.2.tgz#04a1acbf22221e1d6ef43559f43e05a90dbb4392" @@ -2964,33 +2916,25 @@ make-dir@^3.0.0, make-dir@^3.0.2: dependencies: semver "^6.0.0" -mathjs@^5.10.3: - version "5.10.3" - resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-5.10.3.tgz#e998885f932ea8886db8b40f7f5b199f89b427f1" - integrity sha512-ySjg30BC3dYjQm73ILZtwcWzFJde0VU6otkXW/57IjjuYRa3Qaf0Kb8pydEuBZYtqW2OxreAtsricrAmOj3jIw== +mathjs@^9.3.0: + version "9.3.2" + resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-9.3.2.tgz#6523dd5c963d200ff1cea0ff7963b10521b82185" + integrity sha512-0YKSKAeN9OkbIQrxfxnBT4kk/KlH71piWOsvVvAasyRIj/Xd/zlpc5VP/aFxwr+llOq2F3f6booPEu2fWv3yjQ== dependencies: - complex.js "2.0.11" - decimal.js "10.2.0" - escape-latex "1.2.0" - fraction.js "4.0.12" - javascript-natural-sort "0.7.1" - seed-random "2.2.0" - tiny-emitter "2.1.0" - typed-function "1.1.0" + complex.js "^2.0.11" + decimal.js "^10.2.1" + escape-latex "^1.2.0" + fraction.js "^4.0.13" + javascript-natural-sort "^0.7.1" + seedrandom "^3.0.5" + tiny-emitter "^2.1.0" + typed-function "^2.0.0" media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= -memory-fs@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.5.0.tgz#324c01288b88652966d161db77838720845a8e3c" - integrity sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA== - dependencies: - errno "^0.1.3" - readable-stream "^2.0.1" - merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" @@ -3012,19 +2956,31 @@ methods@~1.1.2: integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= micromatch@^4.0.0, micromatch@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" - integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== + version "4.0.4" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" + integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== dependencies: braces "^3.0.1" - picomatch "^2.0.5" + picomatch "^2.2.3" mime-db@1.44.0: version "1.44.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== -mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.19, mime-types@~2.1.24: +mime-db@1.47.0: + version "1.47.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.47.0.tgz#8cb313e59965d3c05cfbf898915a267af46a335c" + integrity sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw== + +mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.19: + version "2.1.30" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.30.tgz#6e7be8b4c479825f85ed6326695db73f9305d62d" + integrity sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg== + dependencies: + mime-db "1.47.0" + +mime-types@~2.1.24: version "2.1.27" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== @@ -3036,10 +2992,10 @@ mime@1.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -mime@^2.0.3, mime@^2.4.6: - version "2.4.6" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1" - integrity sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA== +mime@^2.4.6: + version "2.5.2" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe" + integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg== mimic-fn@^2.1.0: version "2.1.0" @@ -3053,83 +3009,52 @@ minimatch@3.0.4, minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -minimist@^1.2.0, minimist@^1.2.5: +minimist@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -mkdirp@0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.4.tgz#fd01504a6797ec5c9be81ff43d204961ed64a512" - integrity sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw== - dependencies: - minimist "^1.2.5" +mkdirp-classic@^0.5.2: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== -mkdirp@^0.5.1, mkdirp@^0.5.4: +mkdirp@^0.5.3: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== dependencies: minimist "^1.2.5" -mocha@^6.1.4: - version "6.2.3" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-6.2.3.tgz#e648432181d8b99393410212664450a4c1e31912" - integrity sha512-0R/3FvjIGH3eEuG17ccFPk117XL2rWxatr81a57D+r/x2uTYZRbdZ4oVidEUMh2W2TJDa7MdAb12Lm2/qrKajg== - dependencies: - ansi-colors "3.2.3" - browser-stdout "1.3.1" - debug "3.2.6" - diff "3.5.0" - escape-string-regexp "1.0.5" - find-up "3.0.0" - glob "7.1.3" - growl "1.10.5" - he "1.2.0" - js-yaml "3.13.1" - log-symbols "2.2.0" - minimatch "3.0.4" - mkdirp "0.5.4" - ms "2.1.1" - node-environment-flags "1.0.5" - object.assign "4.1.0" - strip-json-comments "2.0.1" - supports-color "6.0.0" - which "1.3.1" - wide-align "1.1.3" - yargs "13.3.2" - yargs-parser "13.1.2" - yargs-unparser "1.6.0" - -mocha@^8.2.1: - version "8.2.1" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-8.2.1.tgz#f2fa68817ed0e53343d989df65ccd358bc3a4b39" - integrity sha512-cuLBVfyFfFqbNR0uUKbDGXKGk+UDFe6aR4os78XIrMQpZl/nv7JYHcvP5MFIAb374b2zFXsdgEGwmzMtP0Xg8w== +mocha@^8.3.2, mocha@^8.4.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-8.4.0.tgz#677be88bf15980a3cae03a73e10a0fc3997f0cff" + integrity sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ== dependencies: "@ungap/promise-all-settled" "1.1.2" ansi-colors "4.1.1" browser-stdout "1.3.1" - chokidar "3.4.3" - debug "4.2.0" - diff "4.0.2" + chokidar "3.5.1" + debug "4.3.1" + diff "5.0.0" escape-string-regexp "4.0.0" find-up "5.0.0" glob "7.1.6" growl "1.10.5" he "1.2.0" - js-yaml "3.14.0" + js-yaml "4.0.0" log-symbols "4.0.0" minimatch "3.0.4" - ms "2.1.2" - nanoid "3.1.12" + ms "2.1.3" + nanoid "3.1.20" serialize-javascript "5.0.1" strip-json-comments "3.1.1" - supports-color "7.2.0" + supports-color "8.1.1" which "2.0.2" wide-align "1.1.3" - workerpool "6.0.2" - yargs "13.3.2" - yargs-parser "13.1.2" + workerpool "6.1.0" + yargs "16.2.0" + yargs-parser "20.2.4" yargs-unparser "2.0.0" ms@2.0.0: @@ -3142,25 +3067,30 @@ ms@2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== -ms@2.1.2, ms@^2.1.1: +ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -mustache@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/mustache/-/mustache-4.0.1.tgz#d99beb031701ad433338e7ea65e0489416c854a2" - integrity sha512-yL5VE97+OXn4+Er3THSmTdCFCtx5hHWzrolvH+JObZnUYwuaG7XV+Ch4fR2cIrcYI0tFHxS7iyFYl14bW8y2sA== +ms@2.1.3, ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +mustache@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/mustache/-/mustache-4.2.0.tgz#e5892324d60a12ec9c2a73359edca52972bf6f64" + integrity sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ== nan@^2.14.0: - version "2.14.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" - integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== + version "2.14.2" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" + integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== -nanoid@3.1.12: - version "3.1.12" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.12.tgz#6f7736c62e8d39421601e4a0c77623a97ea69654" - integrity sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A== +nanoid@3.1.20: + version "3.1.20" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788" + integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw== natural-compare@^1.4.0: version "1.4.0" @@ -3177,13 +3107,10 @@ neo-async@^2.6.2: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== -node-environment-flags@1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.5.tgz#fa930275f5bf5dae188d6192b24b4c8bbac3d76a" - integrity sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ== - dependencies: - object.getownpropertydescriptors "^2.0.3" - semver "^5.7.0" +node-fetch@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" + integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== node-preload@^0.2.1: version "0.2.1" @@ -3192,24 +3119,24 @@ node-preload@^0.2.1: dependencies: process-on-spawn "^1.0.0" -node-pty@^0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.9.0.tgz#8f9bcc0d1c5b970a3184ffd533d862c7eb6590a6" - integrity sha512-MBnCQl83FTYOu7B4xWw10AW77AAh7ThCE1VXEv+JeWj8mSpGo+0bwgsV+b23ljBFwEM9OmsOv3kM27iUPPm84g== +node-pty@^0.10.1: + version "0.10.1" + resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.10.1.tgz#cd05d03a2710315ec40221232ec04186f6ac2c6d" + integrity sha512-JTdtUS0Im/yRsWJSx7yiW9rtpfmxqxolrtnyKwPLI+6XqTAPW/O2MjS8FYL4I5TsMbH2lVgDb2VMjp+9LoQGNg== dependencies: nan "^2.14.0" -node-releases@^1.1.61: - version "1.1.65" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.65.tgz#52d9579176bd60f23eba05c4438583f341944b81" - integrity sha512-YpzJOe2WFIW0V4ZkJQd/DGR/zdVwc/pI4Nl1CZrBO19FdRcSTmsuhdttw9rsTzzJLrNcSloLiBbEYx1C4f6gpA== +node-releases@^1.1.71: + version "1.1.71" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.71.tgz#cb1334b179896b1c89ecfdd4b725fb7bbdfc7dbb" + integrity sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg== normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -npm-run-path@^4.0.0: +npm-run-path@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== @@ -3259,57 +3186,34 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-inspect@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" - integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== +object-inspect@^1.9.0: + version "1.10.3" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.10.3.tgz#c2aa7d2d09f50c99375704f7a0adf24c5782d369" + integrity sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw== -object-inspect@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" - integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== - -object-is@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.3.tgz#2e3b9e65560137455ee3bd62aec4d90a2ea1cc81" - integrity sha512-teyqLvFWzLkq5B9ki8FVWA902UER2qkxmdA4nLf+wjOLAWgxzCWZNCxpDq9MvE8MmhWNr+I8w3BN49Vx36Y6Xg== +object-is@^1.1.4: + version "1.1.5" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" + integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.18.0-next.1" -object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: +object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object.assign@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" - integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== - dependencies: - define-properties "^1.1.2" - function-bind "^1.1.1" - has-symbols "^1.0.0" - object-keys "^1.0.11" - -object.assign@^4.1.0, object.assign@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.1.tgz#303867a666cdd41936ecdedfb1f8f3e32a478cdd" - integrity sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA== +object.assign@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== dependencies: + call-bind "^1.0.0" define-properties "^1.1.3" - es-abstract "^1.18.0-next.0" has-symbols "^1.0.1" object-keys "^1.1.1" -object.getownpropertydescriptors@^2.0.3: - version "2.1.0" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" - integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" @@ -3324,12 +3228,14 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -one-time@0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/one-time/-/one-time-0.0.4.tgz#f8cdf77884826fe4dff93e3a9cc37b1e4480742e" - integrity sha1-+M33eISCb+Tf+T46nMN7HkSAdC4= +one-time@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/one-time/-/one-time-1.0.0.tgz#e06bc174aed214ed58edede573b433bbf827cb45" + integrity sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g== + dependencies: + fn.name "1.x.x" -onetime@^5.1.0: +onetime@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== @@ -3360,7 +3266,7 @@ optionator@^0.9.1: type-check "^0.4.0" word-wrap "^1.2.3" -p-limit@^2.0.0, p-limit@^2.2.0: +p-limit@^2.2.0: version "2.2.2" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e" integrity sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ== @@ -3374,12 +3280,12 @@ p-limit@^3.0.2: dependencies: p-try "^2.0.0" -p-locate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" - integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== +p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== dependencies: - p-limit "^2.0.0" + yocto-queue "^0.1.0" p-locate@^4.1.0: version "4.1.0" @@ -3424,21 +3330,16 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" -parse5@5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" - integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== +parse5@6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" + integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== -path-exists@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= - path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -3469,10 +3370,10 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -pathval@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0" - integrity sha1-uULm1L3mUwBe9rcTYd74cn0GReA= +pathval@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" + integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== pend@~1.2.0: version "1.2.0" @@ -3484,11 +3385,16 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1: +picomatch@^2.0.4, picomatch@^2.2.1: version "2.2.2" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== +picomatch@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.3.tgz#465547f359ccc206d3c48e46a1bcb89bf7ee619d" + integrity sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg== + pkg-dir@^4.1.0, pkg-dir@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" @@ -3496,11 +3402,12 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" -playwright@^1.5.2: - version "1.5.2" - resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.5.2.tgz#e127142cba86c918fad9f68315db5e79524af64c" - integrity sha512-on7IEui47bDZta0txL86QKMDSgjbxERkLc5N0+lU2zajIfN/Ld6vMl+xiROEUPlT/QtqVekq9pTDGdcc0yScMQ== +playwright@^1.11.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.11.0.tgz#0796cf08f4756e8187e01c705315d8e1fb48e25f" + integrity sha512-s3FQBRpu/pW/vZ/lFYhG/Q3WBUbT2rvMgrgy1PHDA7QtPN910C2rj9Ovd6A/m8yxuLnltd/OKqvlAGevWISHKw== dependencies: + commander "^6.1.0" debug "^4.1.1" extract-zip "^2.0.1" https-proxy-agent "^5.0.0" @@ -3511,7 +3418,9 @@ playwright@^1.5.2: proper-lockfile "^4.1.1" proxy-from-env "^1.1.0" rimraf "^3.0.2" + stack-utils "^2.0.3" ws "^7.3.1" + yazl "^2.5.1" pngjs@^5.0.0: version "5.0.0" @@ -3546,11 +3455,11 @@ progress@^2.0.0, progress@^2.0.1, progress@^2.0.3: integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== proper-lockfile@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.1.tgz#284cf9db9e30a90e647afad69deb7cb06881262c" - integrity sha512-1w6rxXodisVpn7QYvLk706mzprPTAPCYAqxMvctmPN3ekuRk/kuGkGc82pangZiAt4R3lwSuUzheTTn0/Yb7Zg== + version "4.1.2" + resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz#c8b9de2af6b2f1601067f98e01ac66baa223141f" + integrity sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA== dependencies: - graceful-fs "^4.1.11" + graceful-fs "^4.2.4" retry "^0.12.0" signal-exit "^3.0.2" @@ -3567,12 +3476,7 @@ proxy-from-env@^1.0.0, proxy-from-env@^1.1.0: resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== -prr@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" - integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= - -psl@^1.1.28: +psl@^1.1.28, psl@^1.1.33: version "1.8.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== @@ -3590,19 +3494,23 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -puppeteer@^1.17.0: - version "1.20.0" - resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-1.20.0.tgz#e3d267786f74e1d87cf2d15acc59177f471bbe38" - integrity sha512-bt48RDBy2eIwZPrkgbcwHtb51mj2nKvHOPMaSH2IsWiv7lOG9k9zhaRzpDZafrk05ajMc3cu+lSQYYOfH2DkVQ== +puppeteer@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-5.5.0.tgz#331a7edd212ca06b4a556156435f58cbae08af00" + integrity sha512-OM8ZvTXAhfgFA7wBIIGlPQzvyEETzDjeRa4mZRCRHxYL+GNH5WAuYUQdja3rpWZvkX/JKqmuVgbsxDNsDFjMEg== dependencies: debug "^4.1.0" - extract-zip "^1.6.6" - https-proxy-agent "^2.2.1" - mime "^2.0.3" + devtools-protocol "0.0.818844" + extract-zip "^2.0.0" + https-proxy-agent "^4.0.0" + node-fetch "^2.6.1" + pkg-dir "^4.2.0" progress "^2.0.1" proxy-from-env "^1.0.0" - rimraf "^2.6.1" - ws "^6.1.0" + rimraf "^3.0.2" + tar-fs "^2.0.0" + unbzip2-stream "^1.3.3" + ws "^7.2.3" qs@6.7.0: version "6.7.0" @@ -3614,6 +3522,11 @@ qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -3636,7 +3549,7 @@ raw-body@2.4.0: iconv-lite "0.4.24" unpipe "1.0.0" -readable-stream@^2.0.1, readable-stream@^2.2.2, readable-stream@^2.3.6: +readable-stream@^2.3.7: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -3649,7 +3562,7 @@ readable-stream@^2.0.1, readable-stream@^2.2.2, readable-stream@^2.3.6: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.1.1: +readable-stream@^3.1.1, readable-stream@^3.4.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -3672,18 +3585,13 @@ rechoir@^0.7.0: dependencies: resolve "^1.9.0" -reduce-flatten@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-2.0.0.tgz#734fd84e65f375d7ca4465c69798c25c9d10ae27" - integrity sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w== - regexp.prototype.flags@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz#7aba89b3c13a64509dabcf3ca8d9fbb9bdf5cb75" - integrity sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ== + version "1.3.1" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26" + integrity sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" regexpp@^3.0.0, regexpp@^3.1.0: version "3.1.0" @@ -3697,19 +3605,19 @@ release-zalgo@^1.0.0: dependencies: es6-error "^4.0.1" -request-promise-core@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.3.tgz#e9a3c081b51380dfea677336061fea879a829ee9" - integrity sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ== +request-promise-core@1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f" + integrity sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw== dependencies: - lodash "^4.17.15" + lodash "^4.17.19" -request-promise-native@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.8.tgz#a455b960b826e44e2bf8999af64dff2bfe58cb36" - integrity sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ== +request-promise-native@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.9.tgz#e407120526a5efdc9a39b28a5679bf47b9d9dc28" + integrity sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g== dependencies: - request-promise-core "1.1.3" + request-promise-core "1.1.4" stealthy-require "^1.1.1" tough-cookie "^2.3.3" @@ -3744,6 +3652,11 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + require-main-filename@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" @@ -3779,10 +3692,11 @@ resolve@^1.3.2: path-parse "^1.0.6" resolve@^1.9.0: - version "1.17.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" - integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== + version "1.20.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" + integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== dependencies: + is-core-module "^2.2.0" path-parse "^1.0.6" retry@^0.12.0: @@ -3795,20 +3709,6 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -rimraf@2.6.3: - version "2.6.3" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" - integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== - dependencies: - glob "^7.1.3" - -rimraf@^2.6.1: - version "2.7.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" - integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== - dependencies: - glob "^7.1.3" - rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" @@ -3817,16 +3717,23 @@ rimraf@^3.0.0, rimraf@^3.0.2: glob "^7.1.3" run-parallel@^1.1.9: - version "1.1.9" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" - integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@~5.2.0: +safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-buffer@^5.1.0: version "5.2.0" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== @@ -3836,10 +3743,10 @@ safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@~5.2.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -saxes@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.0.tgz#b7d30284d7583a5ca6ad0248b56d8889da53788b" - integrity sha512-LXTZygxhf8lfwKaTP/8N9CsVdjTlea3teze4lL6u37ivbgGbV0GGMuNtS/I9rnD/HC2/txUM7Df4S2LVl1qhiA== +saxes@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" + integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== dependencies: xmlchars "^2.2.0" @@ -3852,12 +3759,12 @@ schema-utils@^3.0.0: ajv "^6.12.5" ajv-keywords "^3.5.2" -seed-random@2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/seed-random/-/seed-random-2.2.0.tgz#2a9b19e250a817099231a5b99a4daf80b7fbed54" - integrity sha1-KpsZ4lCoFwmSMaW5mk2vgLf77VQ= +seedrandom@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7" + integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg== -semver@^5.3.0, semver@^5.4.1, semver@^5.7.0: +semver@^5.3.0, semver@^5.4.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -3867,10 +3774,12 @@ semver@^6.0.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.2.1, semver@^7.3.2: - version "7.3.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" - integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== +semver@^7.2.1, semver@^7.3.2, semver@^7.3.4: + version "7.3.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + dependencies: + lru-cache "^6.0.0" send@0.17.1: version "0.17.1" @@ -3918,6 +3827,13 @@ setprototypeof@1.1.1: resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== +shallow-clone@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" + integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== + dependencies: + kind-of "^6.0.2" + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -3931,24 +3847,27 @@ shebang-regex@^3.0.0: integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== side-channel@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.3.tgz#cdc46b057550bbab63706210838df5d4c19519c3" - integrity sha512-A6+ByhlLkksFoUepsGxfj5x1gTSrs+OydsRptUxeNCabQpCFUvcwIczgOigI8vhY/OJCnPnyE9rGiwgvr9cS1g== + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== dependencies: - es-abstract "^1.18.0-next.0" - object-inspect "^1.8.0" + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" -signal-exit@^3.0.2: +signal-exit@^3.0.2, signal-exit@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== -simple-git@^1.113.0: - version "1.132.0" - resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-1.132.0.tgz#53ac4c5ec9e74e37c2fd461e23309f22fcdf09b1" - integrity sha512-xauHm1YqCTom1sC9eOjfq3/9RKiUA9iPnxBbrY2DdL8l4ADMu0jjM5l5lphQP5YWNqAL2aXC/OeuQ76vHtW5fg== +simple-git@^2.37.0: + version "2.38.1" + resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-2.38.1.tgz#6c3ce211777a65482598f4bd93c66443465228c0" + integrity sha512-SeMgUEA6Cmk7Ta57ZzMbkBcAfh8A+DZ6ACZ2onfCp/9UklC9yMJgvcH+GGI3QDTv0lTDIbWtl5LSbBk1UtEfeg== dependencies: - debug "^4.0.1" + "@kwsites/file-exists" "^1.1.1" + "@kwsites/promise-deferred" "^1.1.1" + debug "^4.3.1" simple-swizzle@^0.2.2: version "0.2.2" @@ -3962,31 +3881,33 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -slice-ansi@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" - integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== dependencies: - ansi-styles "^3.2.0" - astral-regex "^1.0.0" - is-fullwidth-code-point "^2.0.0" + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" source-list-map@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== -source-map-loader@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-1.1.2.tgz#5b782bf08496d3a7f355e1780df0e25190a80991" - integrity sha512-bjf6eSENOYBX4JZDfl9vVLNsGAQ6Uz90fLmOazcmMcyDYOBFsGxPNn83jXezWLY9bJsVAo1ObztxPcV8HAbjVA== +source-map-js@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e" + integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug== + +source-map-loader@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-2.0.1.tgz#b4fd0ae7fa7e7d3954300f383f2d6fcc230a4261" + integrity sha512-UzOTTQhoNPeTNzOxwFw220RSRzdGSyH4lpNyWjR7Qm34P4/N0W669YSUFdH07+YNeN75h765XLHmNsF/bm97RQ== dependencies: abab "^2.0.5" iconv-lite "^0.6.2" - loader-utils "^2.0.0" - schema-utils "^3.0.0" - source-map "^0.6.1" - whatwg-mimetype "^2.3.0" + source-map-js "^0.6.2" source-map-support@~0.5.19: version "0.5.19" @@ -4048,6 +3969,13 @@ stack-trace@0.0.x: resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA= +stack-utils@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.3.tgz#cd5f030126ff116b78ccb3c027fe302713b61277" + integrity sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw== + dependencies: + escape-string-regexp "^2.0.0" + "statuses@>= 1.5.0 < 2", statuses@~1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" @@ -4066,15 +3994,6 @@ stealthy-require@^1.1.1: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" -string-width@^3.0.0, string-width@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" - integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== - dependencies: - emoji-regex "^7.0.1" - is-fullwidth-code-point "^2.0.0" - strip-ansi "^5.1.0" - string-width@^4.1.0, string-width@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" @@ -4084,37 +4003,21 @@ string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" -string.prototype.trimend@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" - integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== +string.prototype.trimend@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" + integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.5" -string.prototype.trimleft@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz#9bdb8ac6abd6d602b17a4ed321870d2f8dcefc74" - integrity sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag== +string.prototype.trimstart@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" + integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - function-bind "^1.1.1" - -string.prototype.trimright@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz#440314b15996c866ce8a0341894d45186200c5d9" - integrity sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g== - dependencies: - define-properties "^1.1.3" - function-bind "^1.1.1" - -string.prototype.trimstart@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" - integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" string_decoder@^1.1.1: version "1.3.0" @@ -4144,13 +4047,6 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" -strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" - integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== - dependencies: - ansi-regex "^4.1.0" - strip-ansi@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" @@ -4168,27 +4064,15 @@ strip-final-newline@^2.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== -strip-json-comments@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= - strip-json-comments@3.1.1, strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -supports-color@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.0.0.tgz#76cfe742cf1f41bb9b1c29ad03068c05b4c0e40a" - integrity sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg== - dependencies: - has-flag "^3.0.0" - -supports-color@7.2.0, supports-color@^7.0.0, supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== +supports-color@8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== dependencies: has-flag "^4.0.0" @@ -4199,57 +4083,72 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" +supports-color@^7.0.0, supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + symbol-tree@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== -table-layout@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/table-layout/-/table-layout-1.0.1.tgz#8411181ee951278ad0638aea2f779a9ce42894f9" - integrity sha512-dEquqYNJiGwY7iPfZ3wbXDI944iqanTSchrACLL2nOB+1r+h1Nzu2eH+DuPPvWvm5Ry7iAPeFlgEtP5bIp5U7Q== +table@^6.0.4: + version "6.7.0" + resolved "https://registry.yarnpkg.com/table/-/table-6.7.0.tgz#26274751f0ee099c547f6cb91d3eff0d61d155b2" + integrity sha512-SAM+5p6V99gYiiy2gT5ArdzgM1dLDed0nkrWmG6Fry/bUS/m9x83BwpJUOf1Qj/x2qJd+thL6IkIx7qPGRxqBw== dependencies: - array-back "^4.0.1" - deep-extend "~0.6.0" - typical "^5.2.0" - wordwrapjs "^4.0.0" + ajv "^8.0.1" + lodash.clonedeep "^4.5.0" + lodash.truncate "^4.4.2" + slice-ansi "^4.0.0" + string-width "^4.2.0" + strip-ansi "^6.0.0" -table@^5.2.3: - version "5.4.6" - resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" - integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== +tapable@^2.1.1, tapable@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.0.tgz#5c373d281d9c672848213d0e037d1c4165ab426b" + integrity sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw== + +tar-fs@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" + integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== dependencies: - ajv "^6.10.2" - lodash "^4.17.14" - slice-ansi "^2.1.0" - string-width "^3.0.0" + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.1.4" -tapable@^1.0.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" - integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== - -tapable@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.0.0.tgz#a49c3d6a8a2bb606e7db372b82904c970d537a08" - integrity sha512-bjzn0C0RWoffnNdTzNi7rNDhs1Zlwk2tRXgk8EiHKAOX1Mag3d6T0Y5zNa7l9CJ+EoUne/0UHdwS8tMbkh9zDg== - -terser-webpack-plugin@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.0.3.tgz#ec60542db2421f45735c719d2e17dabfbb2e3e42" - integrity sha512-zFdGk8Lh9ZJGPxxPE6jwysOlATWB8GMW8HcfGULWA/nPal+3VdATflQvSBSLQJRCmYZnfFJl6vkRTiwJGNgPiQ== +tar-stream@^2.1.4: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== dependencies: - jest-worker "^26.6.1" - p-limit "^3.0.2" + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + +terser-webpack-plugin@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.1.1.tgz#7effadee06f7ecfa093dbbd3e9ab23f5f3ed8673" + integrity sha512-5XNNXZiR8YO6X6KhSGXfY0QrGrCRlSwAEjIIrlRQR4W8nP69TaJUlh3bkuac6zzgspiGPfKEHcY295MMVExl5Q== + dependencies: + jest-worker "^26.6.2" + p-limit "^3.1.0" schema-utils "^3.0.0" serialize-javascript "^5.0.1" source-map "^0.6.1" - terser "^5.3.8" + terser "^5.5.1" -terser@^5.3.8: - version "5.3.8" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.3.8.tgz#991ae8ba21a3d990579b54aa9af11586197a75dd" - integrity sha512-zVotuHoIfnYjtlurOouTazciEfL7V38QMAOhGqpXDEg6yT13cF4+fEP9b0rrCEQTn+tT46uxgFsTZzhygk+CzQ== +terser@^5.5.1: + version "5.7.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.7.0.tgz#a761eeec206bc87b605ab13029876ead938ae693" + integrity sha512-HP5/9hp2UaZt5fYkuhNBR8YyRcT8juw8+uFbAme53iN9hblvKnLUTKkmwJG6ocWpIKf8UK4DoeWG4ty0J6S6/g== dependencies: commander "^2.20.0" source-map "~0.7.2" @@ -4274,7 +4173,12 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= -tiny-emitter@2.1.0: +through@^2.3.8: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + +tiny-emitter@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== @@ -4304,16 +4208,16 @@ tough-cookie@^2.3.3, tough-cookie@~2.5.0: psl "^1.1.28" punycode "^2.1.1" -tough-cookie@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2" - integrity sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg== +tough-cookie@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" + integrity sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg== dependencies: - ip-regex "^2.1.0" - psl "^1.1.28" + psl "^1.1.33" punycode "^2.1.1" + universalify "^0.1.2" -tr46@^2.0.0: +tr46@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.0.2.tgz#03273586def1595ae08fedb38d7733cee91d2479" integrity sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg== @@ -4325,31 +4229,30 @@ triple-beam@^1.2.0, triple-beam@^1.3.0: resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9" integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw== -ts-loader@^8.0.8: - version "8.0.8" - resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-8.0.8.tgz#5c514c895d3b2462bf0148f63da0c22b227f7867" - integrity sha512-wihija1i2Ub9FxKFoEgx/phToJiDZKLIbTFURgf4Efxla2QuVucDO6ZpHf2jNJsRtDQfBId0g+1HF0biIjoT6Q== +ts-loader@^9.1.2: + version "9.1.2" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.1.2.tgz#ba9b9abb05a514e8ff825791a3f6fcf793272728" + integrity sha512-ryMgATvLLl+z8zQvdlm6Pep0slmwxFWIEnq/5VdiLVjqQXnFJgO+qNLGIIP+d2N2jsFZ9MibZCVDb2bSp7OmEA== dependencies: - chalk "^2.3.0" - enhanced-resolve "^4.0.0" - loader-utils "^1.0.2" + chalk "^4.1.0" + enhanced-resolve "^5.0.0" micromatch "^4.0.0" - semver "^6.0.0" + semver "^7.3.4" -tslib@^1.8.0, tslib@^1.8.1: +tslib@^1.13.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tslib@^1.8.1: version "1.11.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA== -tslib@^1.9.0: - version "1.13.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" - integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== - -tslint@^5.17.0: - version "5.20.1" - resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.20.1.tgz#e401e8aeda0152bc44dd07e614034f3f80c67b7d" - integrity sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg== +tslint@^6.1.3: + version "6.1.3" + resolved "https://registry.yarnpkg.com/tslint/-/tslint-6.1.3.tgz#5c23b2eccc32487d5523bd3a470e9aa31789d904" + integrity sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg== dependencies: "@babel/code-frame" "^7.0.0" builtin-modules "^1.1.1" @@ -4359,10 +4262,10 @@ tslint@^5.17.0: glob "^7.1.1" js-yaml "^3.13.1" minimatch "^3.0.4" - mkdirp "^0.5.1" + mkdirp "^0.5.3" resolve "^1.3.2" semver "^5.3.0" - tslib "^1.8.0" + tslib "^1.13.0" tsutils "^2.29.0" tsutils@^2.29.0: @@ -4373,9 +4276,9 @@ tsutils@^2.29.0: tslib "^1.8.1" tsutils@^3.17.1: - version "3.17.1" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" - integrity sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g== + version "3.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== dependencies: tslib "^1.8.1" @@ -4410,6 +4313,11 @@ type-detect@^4.0.0, type-detect@^4.0.5: resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + type-fest@^0.8.0, type-fest@^0.8.1: version "0.8.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" @@ -4423,10 +4331,10 @@ type-is@~1.6.17, type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" -typed-function@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/typed-function/-/typed-function-1.1.0.tgz#ea149706e0fb42aca1791c053a6d94ccd6c4fdcb" - integrity sha512-TuQzwiT4DDg19beHam3E66oRXhyqlyfgjHB/5fcvsRXbfmWPJfto9B4a0TBdTrQAPGlGmXh/k7iUI+WsObgORA== +typed-function@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/typed-function/-/typed-function-2.0.0.tgz#15ab3825845138a8b1113bd89e60cd6a435739e8" + integrity sha512-Hhy1Iwo/e4AtLZNK10ewVVcP2UEs408DS35ubP825w/YgSBK1KVLwALvvIG4yX75QJrxjCpcWkzkVRB0BwwYlA== typedarray-to-buffer@^3.1.5: version "3.1.5" @@ -4435,25 +4343,43 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -typedarray@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" - integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= +typescript@^4.2.3, typescript@^4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.4.tgz#8610b59747de028fda898a8aef0e103f156d0961" + integrity sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg== -typescript@4.0: - version "4.0.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.5.tgz#ae9dddfd1069f1cb5beb3ef3b2170dd7c1332389" - integrity sha512-ywmr/VrTVCmNTJ6iV2LwIrfG1P+lv6luD8sUJs+2eI9NLGigaN+nUQc13iHqisq7bra9lnmUSYqbJvegraBOPQ== +unbox-primitive@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" + integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw== + dependencies: + function-bind "^1.1.1" + has-bigints "^1.0.1" + has-symbols "^1.0.2" + which-boxed-primitive "^1.0.2" -typescript@^3.5.1: - version "3.9.7" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa" - integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw== +unbzip2-stream@^1.3.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7" + integrity sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg== + dependencies: + buffer "^5.2.1" + through "^2.3.8" -typical@^5.0.0, typical@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/typical/-/typical-5.2.0.tgz#4daaac4f2b5315460804f0acf6cb69c52bb93066" - integrity sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg== +universalify@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + +universalify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d" + integrity sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug== + +universalify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" + integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" @@ -4488,9 +4414,9 @@ uuid@^3.3.2, uuid@^3.3.3: integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== v8-compile-cache@^2.0.3, v8-compile-cache@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz#9471efa3ef9128d2f7c6a7ca39c4dd6b5055b132" - integrity sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q== + version "2.3.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" + integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== vary@~1.1.2: version "1.1.2" @@ -4521,9 +4447,9 @@ w3c-xmlserializer@^2.0.0: xml-name-validator "^3.0.0" watchpack@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.0.1.tgz#2f2192c542c82a3bcde76acd3411470c120426a8" - integrity sha512-vO8AKGX22ZRo6PiOFM9dC0re8IcKh8Kd/aH2zeqUc6w4/jBGlTy2P7fTC6ekT0NjVeGjgU2dGC5rNstKkeLEQg== + version "2.1.1" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.1.1.tgz#e99630550fca07df9f90a06056987baa40a689c7" + integrity sha512-Oo7LXCmc1eE1AjyuSBmtC3+Wy4HcV8PxWh2kP6fOl8yTlNS7r0K9l1ao2lrrUza7V39Y3D/BbJgY8VeSlc5JKw== dependencies: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" @@ -4545,31 +4471,32 @@ webidl-conversions@^6.1.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== -webpack-cli@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.2.0.tgz#10a09030ad2bd4d8b0f78322fba6ea43ec56aaaa" - integrity sha512-EIl3k88vaF4fSxWSgtAQR+VwicfLMTZ9amQtqS4o+TDPW9HGaEpbFBbAZ4A3ZOT5SOnMxNOzROsSTPiE8tBJPA== +webpack-cli@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.7.0.tgz#3195a777f1f802ecda732f6c95d24c0004bc5a35" + integrity sha512-7bKr9182/sGfjFm+xdZSwgQuFjgEcy0iCTIBxRUeteJ2Kr8/Wz0qNJX+jw60LU36jApt4nmMkep6+W5AKhok6g== dependencies: - "@webpack-cli/info" "^1.1.0" - "@webpack-cli/serve" "^1.1.0" + "@discoveryjs/json-ext" "^0.5.0" + "@webpack-cli/configtest" "^1.0.3" + "@webpack-cli/info" "^1.2.4" + "@webpack-cli/serve" "^1.4.0" colorette "^1.2.1" - command-line-usage "^6.1.0" - commander "^6.2.0" - enquirer "^2.3.6" - execa "^4.1.0" + commander "^7.0.0" + execa "^5.0.0" + fastest-levenshtein "^1.0.12" import-local "^3.0.2" interpret "^2.2.0" - leven "^3.1.0" rechoir "^0.7.0" v8-compile-cache "^2.2.0" - webpack-merge "^4.2.2" + webpack-merge "^5.7.3" -webpack-merge@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.2.2.tgz#a27c52ea783d1398afd2087f547d7b9d2f43634d" - integrity sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g== +webpack-merge@^5.7.3: + version "5.7.3" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.7.3.tgz#2a0754e1877a25a8bbab3d2475ca70a052708213" + integrity sha512-6/JUQv0ELQ1igjGDzHkXbVDRxkfA57Zw7PfiupdLFJYrgFqY5ZP8xxbpp2lU3EPwYx89ht5Z/aDkD40hFCm5AA== dependencies: - lodash "^4.17.15" + clone-deep "^4.0.1" + wildcard "^2.0.0" webpack-sources@^2.1.1: version "2.2.0" @@ -4579,33 +4506,32 @@ webpack-sources@^2.1.1: source-list-map "^2.0.1" source-map "^0.6.1" -webpack@^5.4.0: - version "5.4.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.4.0.tgz#4fdc6ec8a0ff9160701fb8f2eb8d06b33ecbae0f" - integrity sha512-udpYTyqz8toTTdaOsL2QKPLeZLt2IEm9qY7yTXuFEQhKu5bk0yQD9BtAdVQksmz4jFbbWOiWmm3NHarO0zr/ng== +webpack@^5, webpack@^5.37.0: + version "5.37.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.37.0.tgz#2ab00f613faf494504eb2beef278dab7493cc39d" + integrity sha512-yvdhgcI6QkQkDe1hINBAJ1UNevqNGTVaCkD2SSJcB8rcrNNl922RI8i2DXUAuNfANoxwsiXXEA4ZPZI9q2oGLA== dependencies: "@types/eslint-scope" "^3.7.0" - "@types/estree" "^0.0.45" - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-module-context" "1.9.0" - "@webassemblyjs/wasm-edit" "1.9.0" - "@webassemblyjs/wasm-parser" "1.9.0" - acorn "^8.0.4" + "@types/estree" "^0.0.47" + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/wasm-edit" "1.11.0" + "@webassemblyjs/wasm-parser" "1.11.0" + acorn "^8.2.1" browserslist "^4.14.5" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.3.1" + enhanced-resolve "^5.8.0" + es-module-lexer "^0.4.0" eslint-scope "^5.1.1" events "^3.2.0" glob-to-regexp "^0.4.1" graceful-fs "^4.2.4" json-parse-better-errors "^1.0.2" - loader-runner "^4.1.0" + loader-runner "^4.2.0" mime-types "^2.1.27" neo-async "^2.6.2" - pkg-dir "^4.2.0" schema-utils "^3.0.0" - tapable "^2.0.0" - terser-webpack-plugin "^5.0.3" + tapable "^2.1.1" + terser-webpack-plugin "^5.1.1" watchpack "^2.0.0" webpack-sources "^2.1.1" @@ -4621,25 +4547,25 @@ whatwg-mimetype@^2.3.0: resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== -whatwg-url@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.0.0.tgz#37f256cb746398e19b107bd6ef820b4ae2d15871" - integrity sha512-41ou2Dugpij8/LPO5Pq64K5q++MnRCBpEHvQr26/mArEKTkCV5aoXIqyhuYtE0pkqScXwhf2JP57rkRTYM29lQ== +whatwg-url@^8.0.0, whatwg-url@^8.5.0: + version "8.5.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.5.0.tgz#7752b8464fc0903fec89aa9846fc9efe07351fd3" + integrity sha512-fy+R77xWv0AiqfLl4nuGUlQ3/6b5uNfQ4WAbGQVMYshCTCCPK9psC1nWh3XHuxGVCtlcDDQPQW1csmmIQo+fwg== dependencies: - lodash.sortby "^4.7.0" - tr46 "^2.0.0" - webidl-conversions "^5.0.0" + lodash "^4.7.0" + tr46 "^2.0.2" + webidl-conversions "^6.1.0" -which-boxed-primitive@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.1.tgz#cbe8f838ebe91ba2471bb69e9edbda67ab5a5ec1" - integrity sha512-7BT4TwISdDGBgaemWU0N0OU7FeAEJ9Oo2P1PHRm/FCWoEi2VLWC9b6xvxAA3C/NMpxg3HXVgi0sMmGbNUbNepQ== +which-boxed-primitive@^1.0.1, which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== dependencies: - is-bigint "^1.0.0" - is-boolean-object "^1.0.0" - is-number-object "^1.0.3" - is-string "^1.0.4" - is-symbol "^1.0.2" + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" which-collection@^1.0.1: version "1.0.1" @@ -4657,24 +4583,18 @@ which-module@^2.0.0: integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= which-typed-array@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.2.tgz#e5f98e56bda93e3dac196b01d47c1156679c00b2" - integrity sha512-KT6okrd1tE6JdZAy3o2VhMoYPh3+J6EMZLyrxBQsZflI1QCZIxMrIYLkosd8Twf+YfknVIHmYQPgJt238p8dnQ== + version "1.1.4" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.4.tgz#8fcb7d3ee5adf2d771066fba7cf37e32fe8711ff" + integrity sha512-49E0SpUe90cjpoc7BOJwyPHRqSAd12c10Qm2amdEZrJPCY2NDxaW01zHITrem+rnETY3dwrbH3UUrUwagfCYDA== dependencies: available-typed-arrays "^1.0.2" - es-abstract "^1.17.5" + call-bind "^1.0.0" + es-abstract "^1.18.0-next.1" foreach "^2.0.5" function-bind "^1.1.1" has-symbols "^1.0.1" is-typed-array "^1.1.3" -which@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - which@2.0.2, which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" @@ -4689,55 +4609,43 @@ wide-align@1.1.3: dependencies: string-width "^1.0.2 || 2" -winston-transport@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.3.0.tgz#df68c0c202482c448d9b47313c07304c2d7c2c66" - integrity sha512-B2wPuwUi3vhzn/51Uukcao4dIduEiPOcOt9HJ3QeaXgkJ5Z7UwpBzxS4ZGNHtrxrUvTwemsQiSys0ihOf8Mp1A== +wildcard@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" + integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== + +winston-transport@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.4.0.tgz#17af518daa690d5b2ecccaa7acf7b20ca7925e59" + integrity sha512-Lc7/p3GtqtqPBYYtS6KCN3c77/2QCev51DvcJKbkFPQNoj1sinkGwLGFDxkXY9J6p9+EPnYs+D90uwbnaiURTw== dependencies: - readable-stream "^2.3.6" + readable-stream "^2.3.7" triple-beam "^1.2.0" -winston@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/winston/-/winston-3.2.1.tgz#63061377976c73584028be2490a1846055f77f07" - integrity sha512-zU6vgnS9dAWCEKg/QYigd6cgMVVNwyTzKs81XZtTFuRwJOcDdBg7AU0mXVyNbs7O5RH2zdv+BdNZUlx7mXPuOw== +winston@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/winston/-/winston-3.3.3.tgz#ae6172042cafb29786afa3d09c8ff833ab7c9170" + integrity sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw== dependencies: - async "^2.6.1" - diagnostics "^1.1.1" - is-stream "^1.1.0" - logform "^2.1.1" - one-time "0.0.4" - readable-stream "^3.1.1" + "@dabh/diagnostics" "^2.0.2" + async "^3.1.0" + is-stream "^2.0.0" + logform "^2.2.0" + one-time "^1.0.0" + readable-stream "^3.4.0" stack-trace "0.0.x" triple-beam "^1.3.0" - winston-transport "^4.3.0" + winston-transport "^4.4.0" word-wrap@^1.2.3, word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== -wordwrapjs@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/wordwrapjs/-/wordwrapjs-4.0.0.tgz#9aa9394155993476e831ba8e59fb5795ebde6800" - integrity sha512-Svqw723a3R34KvsMgpjFBYCgNOSdcW3mQFK4wIfhGQhtaFVOJmdYoXgi63ne3dTlWgatVcUc7t4HtQ/+bUVIzQ== - dependencies: - reduce-flatten "^2.0.0" - typical "^5.0.0" - -workerpool@6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.0.2.tgz#e241b43d8d033f1beb52c7851069456039d1d438" - integrity sha512-DSNyvOpFKrNusaaUwk+ej6cBj1bmhLcBfj80elGk+ZIo5JSkq+unB1dLKEOcNfJDZgjGICfhQ0Q5TbP0PvF4+Q== - -wrap-ansi@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" - integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== - dependencies: - ansi-styles "^3.2.0" - string-width "^3.0.0" - strip-ansi "^5.0.0" +workerpool@6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.1.0.tgz#a8e038b4c94569596852de7a8ea4228eefdeb37b" + integrity sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg== wrap-ansi@^6.2.0: version "6.2.0" @@ -4748,6 +4656,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -4763,13 +4680,6 @@ write-file-atomic@^3.0.0: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" -write@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" - integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== - dependencies: - mkdirp "^0.5.1" - ws@^5.2.0: version "5.2.2" resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.2.tgz#dffef14866b8e8dc9133582514d1befaf96e980f" @@ -4777,17 +4687,10 @@ ws@^5.2.0: dependencies: async-limiter "~1.0.0" -ws@^6.1.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" - integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== - dependencies: - async-limiter "~1.0.0" - -ws@^7.2.3, ws@^7.3.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.1.tgz#d0547bf67f7ce4f12a72dfe31262c68d7dc551c8" - integrity sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA== +ws@^7.2.3, ws@^7.3.1, ws@^7.4.4, ws@^7.4.5: + version "7.4.6" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" + integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== xml-name-validator@^3.0.0: version "3.0.0" @@ -4799,38 +4702,46 @@ xmlchars@^2.2.0: resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== -xterm-benchmark@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/xterm-benchmark/-/xterm-benchmark-0.1.3.tgz#c637d078f7b73f77a4342299e706b4d0e52ab46a" - integrity sha512-HBSeUOFlr9JVMFNkL5w8EkuWccczkfZAX6adK5fSot1sRRAFJS9NZcXH/yRLZp9S24qOUBJznMZGa9CjWB3h3g== +xterm-benchmark@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/xterm-benchmark/-/xterm-benchmark-0.2.1.tgz#f7741127aa8eceaffbb0cbbca93d374075e44d64" + integrity sha512-QtfTIlCrjPlzmdjaxRXC0JeE1Dwk25WjNzHR1bTKRqSSQToVw+rcp53o5V5I9AaVWef8vCdJ7lVxXuuX4sphew== dependencies: "@types/app-root-path" "^1.2.4" "@types/cli-table" "^0.3.0" - "@types/mathjs" "^5.0.1" - "@types/mocha" "^5.2.7" - "@types/node" "^12.0.4" - app-root-path "^2.2.1" - chrome-timeline "0.0.12" - cli-table "^0.3.1" + "@types/mathjs" "^6.0.11" + "@types/mocha" "^8.2.1" + "@types/node" "^12.12.37" + "@types/puppeteer" "^5.4.3" + app-root-path "^3.0.0" + chrome-timeline "0.0.15" + cli-table "^0.3.6" columnify "^1.5.4" - commander "^2.20.0" - mathjs "^5.10.3" - mocha "^6.1.4" - tslint "^5.17.0" - typescript "^3.5.1" + commander "^6.2.1" + mathjs "^9.3.0" + mocha "^8.3.2" + tslint "^6.1.3" + typescript "^4.2.3" y18n@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" - integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== + version "4.0.1" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4" + integrity sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ== -yargs-parser@13.1.2, yargs-parser@^13.1.2: - version "13.1.2" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" - integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yargs-parser@20.2.4: + version "20.2.4" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" + integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== yargs-parser@^18.1.1: version "18.1.2" @@ -4840,14 +4751,10 @@ yargs-parser@^18.1.1: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-unparser@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-1.6.0.tgz#ef25c2c769ff6bd09e4b0f9d7c605fb27846ea9f" - integrity sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw== - dependencies: - flat "^4.1.0" - lodash "^4.17.15" - yargs "^13.3.0" +yargs-parser@^20.2.2: + version "20.2.7" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.7.tgz#61df85c113edfb5a7a4e36eb8aa60ef423cbc90a" + integrity sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw== yargs-unparser@2.0.0: version "2.0.0" @@ -4859,21 +4766,18 @@ yargs-unparser@2.0.0: flat "^5.0.2" is-plain-obj "^2.1.0" -yargs@13.3.2, yargs@^13.3.0: - version "13.3.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" - integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== +yargs@16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== dependencies: - cliui "^5.0.0" - find-up "^3.0.0" - get-caller-file "^2.0.1" + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^3.0.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^13.1.2" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" yargs@^15.0.2: version "15.3.1" @@ -4899,3 +4803,15 @@ yauzl@^2.10.0: dependencies: buffer-crc32 "~0.2.3" fd-slicer "~1.1.0" + +yazl@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/yazl/-/yazl-2.5.1.tgz#a3d65d3dd659a5b0937850e8609f22fffa2b5c35" + integrity sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw== + dependencies: + buffer-crc32 "~0.2.3" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==