diff --git a/.eslintrc.json b/.eslintrc.json index bef0e9e2..9ba50120 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -14,30 +14,34 @@ "test/api/tsconfig.json", "test/benchmark/tsconfig.json", "test/playwright/tsconfig.json", - "addons/xterm-addon-attach/src/tsconfig.json", - "addons/xterm-addon-attach/test/tsconfig.json", - "addons/xterm-addon-canvas/src/tsconfig.json", - "addons/xterm-addon-canvas/test/tsconfig.json", - "addons/xterm-addon-fit/src/tsconfig.json", - "addons/xterm-addon-fit/test/tsconfig.json", - "addons/xterm-addon-image/src/tsconfig.json", - "addons/xterm-addon-image/test/tsconfig.json", - "addons/xterm-addon-ligatures/src/tsconfig.json", - "addons/xterm-addon-search/src/tsconfig.json", - "addons/xterm-addon-search/test/tsconfig.json", - "addons/xterm-addon-serialize/src/tsconfig.json", - "addons/xterm-addon-serialize/test/tsconfig.json", - "addons/xterm-addon-serialize/benchmark/tsconfig.json", - "addons/xterm-addon-unicode11/src/tsconfig.json", - "addons/xterm-addon-unicode11/test/tsconfig.json", - "addons/xterm-addon-web-links/src/tsconfig.json", - "addons/xterm-addon-web-links/test/tsconfig.json", - "addons/xterm-addon-webgl/src/tsconfig.json", - "addons/xterm-addon-webgl/test/tsconfig.json" + "addons/addon-attach/src/tsconfig.json", + "addons/addon-attach/test/tsconfig.json", + "addons/addon-canvas/src/tsconfig.json", + "addons/addon-canvas/test/tsconfig.json", + "addons/addon-fit/src/tsconfig.json", + "addons/addon-fit/test/tsconfig.json", + "addons/addon-image/src/tsconfig.json", + "addons/addon-image/test/tsconfig.json", + "addons/addon-ligatures/src/tsconfig.json", + "addons/addon-search/src/tsconfig.json", + "addons/addon-search/test/tsconfig.json", + "addons/addon-serialize/src/tsconfig.json", + "addons/addon-serialize/test/tsconfig.json", + "addons/addon-serialize/benchmark/tsconfig.json", + "addons/addon-unicode11/src/tsconfig.json", + "addons/addon-unicode11/test/tsconfig.json", + "addons/addon-unicode-graphemes/src/tsconfig.json", + "addons/addon-unicode-graphemes/test/tsconfig.json", + "addons/addon-unicode-graphemes/benchmark/tsconfig.json", + "addons/addon-web-links/src/tsconfig.json", + "addons/addon-web-links/test/tsconfig.json", + "addons/addon-webgl/src/tsconfig.json", + "addons/addon-webgl/test/tsconfig.json" ], "sourceType": "module" }, "ignorePatterns": [ + "addons/*/src/third-party/*.ts", "**/inwasm-sdks/*", "**/typings/*.d.ts", "**/node_modules", diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fb003eb7..0a64b6a7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,26 +26,28 @@ jobs: zip -r compressed-build \ ./out/* \ ./out-test/* \ - ./addons/xterm-addon-attach/out/* \ - ./addons/xterm-addon-attach/out-test/* \ - ./addons/xterm-addon-canvas/out/* \ - ./addons/xterm-addon-canvas/out-test/* \ - ./addons/xterm-addon-fit/out/* \ - ./addons/xterm-addon-fit/out-test/* \ - ./addons/xterm-addon-image/out/* \ - ./addons/xterm-addon-image/out-test/* \ - ./addons/xterm-addon-ligatures/out/* \ - ./addons/xterm-addon-ligatures/out-test/* \ - ./addons/xterm-addon-search/out/* \ - ./addons/xterm-addon-search/out-test/* \ - ./addons/xterm-addon-serialize/out/* \ - ./addons/xterm-addon-serialize/out-test/* \ - ./addons/xterm-addon-unicode11/out/* \ - ./addons/xterm-addon-unicode11/out-test/* \ - ./addons/xterm-addon-web-links/out/* \ - ./addons/xterm-addon-web-links/out-test/* \ - ./addons/xterm-addon-webgl/out/* \ - ./addons/xterm-addon-webgl/out-test/* + ./addons/addon-attach/out/* \ + ./addons/addon-attach/out-test/* \ + ./addons/addon-canvas/out/* \ + ./addons/addon-canvas/out-test/* \ + ./addons/addon-fit/out/* \ + ./addons/addon-fit/out-test/* \ + ./addons/addon-image/out/* \ + ./addons/addon-image/out-test/* \ + ./addons/addon-ligatures/out/* \ + ./addons/addon-ligatures/out-test/* \ + ./addons/addon-search/out/* \ + ./addons/addon-search/out-test/* \ + ./addons/addon-serialize/out/* \ + ./addons/addon-serialize/out-test/* \ + ./addons/addon-unicode11/out/* \ + ./addons/addon-unicode11/out-test/* \ + ./addons/addon-unicode-graphemes/out/* \ + ./addons/addon-unicode-graphemes/out-test/* \ + ./addons/addon-web-links/out/* \ + ./addons/addon-web-links/out-test/* \ + ./addons/addon-webgl/out/* \ + ./addons/addon-webgl/out-test/* - name: Upload artifacts uses: actions/upload-artifact@v3 with: @@ -68,6 +70,8 @@ jobs: yarn --frozen-lockfile yarn install-addons - name: Lint code + env: + NODE_OPTIONS: --max_old_space_size=4096 run: yarn lint - name: Lint API run: yarn lint-api @@ -261,10 +265,10 @@ jobs: run: yarn build-demo - name: Integration tests (core) # Tests use 50% workers to reduce flakiness run: yarn test-playwright-${{ matrix.browser }} --workers=50% --forbid-only --suite=core - - name: Integration tests (xterm-addon-canvas) - run: yarn test-playwright-${{ matrix.browser }} --workers=50% --forbid-only --suite=xterm-addon-canvas - - name: Integration tests (xterm-addon-webgl) - run: yarn test-playwright-${{ matrix.browser }} --workers=50% --forbid-only --suite=xterm-addon-webgl + - name: Integration tests (addon-canvas) + run: yarn test-playwright-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-canvas + - name: Integration tests (addon-webgl) + run: yarn test-playwright-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-webgl test-api: needs: build @@ -302,3 +306,41 @@ jobs: ls -R - name: Integration tests (${{ matrix.browser }}) run: yarn test-api-${{ matrix.browser }} --headless --forbid-only + + release-dry-run: + needs: build + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [18] + steps: + - uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node-version }}.x + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }}.x + cache: 'yarn' + - name: Install dependencies + run: | + yarn --frozen-lockfile + yarn install-addons + - name: Install playwright + run: npx playwright install + - uses: actions/download-artifact@v3 + with: + name: build-artifacts + - name: Unzip artifacts + shell: bash + run: | + if [ "$RUNNER_OS" == "Windows" ]; then + pwsh -Command "7z x compressed-build.zip -aoa -o${{ github.workspace }}" + else + unzip -o compressed-build.zip + fi + ls -R + - name: Package headless + run: | + yarn package-headless + node ./bin/package_headless.js + - name: Publish to npm (dry run) + run: node ./bin/publish.js --dry diff --git a/README.md b/README.md index 0abcd32c..7514e76c 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ To start using xterm.js on your browser, add the `xterm.js` and `xterm.css` to t The recommended way to load xterm.js is via the ES6 module syntax: ```javascript -import { Terminal } from 'xterm'; +import { Terminal } from '@xterm/xterm'; ``` ### Addons @@ -59,14 +59,14 @@ import { Terminal } from 'xterm'; 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 +npm i -S @xterm/addon-web-links ``` Then import the addon, instantiate it and call `Terminal.loadAddon`: ```ts -import { Terminal } from 'xterm'; -import { WebLinksAddon } from 'xterm-addon-web-links'; +import { Terminal } from '@xterm/xterm'; +import { WebLinksAddon } from '@xterm/addon-web-links'; const terminal = new Terminal(); // Load WebLinksAddon on terminal, this is all that's needed to get web links @@ -76,10 +76,15 @@ terminal.loadAddon(new WebLinksAddon()); 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 -- [`xterm-addon-search`](https://github.com/xtermjs/xterm.js/tree/master/addons/xterm-addon-search): Adds search functionality -- [`xterm-addon-web-links`](https://github.com/xtermjs/xterm.js/tree/master/addons/xterm-addon-web-links): Adds web link detection and interaction +- [`@xterm/addon-attach`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-attach): Attaches to a server running a process via a websocket +- [`@xterm/addon-canvas`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-canvas): Renders xterm.js using a `canvas` element's 2d context +- [`@xterm/addon-fit`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-fit): Fits the terminal to the containing element +- [`@xterm/addon-image`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-image): Adds image support +- [`@xterm/addon-search`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-search): Adds search functionality +- [`@xterm/addon-serialize`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-serialize): Serializes the terminal's buffer to a VT sequences or HTML +- [`@xterm/addon-unicode11`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-unicode11): Updates character widths to their unicode11 values +- [`@xterm/addon-web-links`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-web-links): Adds web link detection and interaction +- [`@xterm/addon-webgl`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-webgl): Renders xterm.js using a `canvas` element's webgl2 context ## Browser Support diff --git a/addons/xterm-addon-attach/.gitignore b/addons/addon-attach/.gitignore similarity index 100% rename from addons/xterm-addon-attach/.gitignore rename to addons/addon-attach/.gitignore diff --git a/addons/xterm-addon-attach/.npmignore b/addons/addon-attach/.npmignore similarity index 100% rename from addons/xterm-addon-attach/.npmignore rename to addons/addon-attach/.npmignore diff --git a/addons/xterm-addon-attach/LICENSE b/addons/addon-attach/LICENSE similarity index 100% rename from addons/xterm-addon-attach/LICENSE rename to addons/addon-attach/LICENSE diff --git a/addons/xterm-addon-attach/README.md b/addons/addon-attach/README.md similarity index 61% rename from addons/xterm-addon-attach/README.md rename to addons/addon-attach/README.md index 67ebf17b..fa123fe2 100644 --- a/addons/xterm-addon-attach/README.md +++ b/addons/addon-attach/README.md @@ -1,22 +1,22 @@ -## xterm-addon-attach +## @xterm/addon-attach An addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables attaching to a web socket. This addon requires xterm.js v4+. ### Install ```bash -npm install --save xterm-addon-attach +npm install --save @xterm/addon-attach ``` ### Usage ```ts -import { Terminal } from 'xterm'; -import { AttachAddon } from 'xterm-addon-attach'; +import { Terminal } from '@xterm/xterm'; +import { AttachAddon } from '@xterm/addon-attach'; const terminal = new Terminal(); const attachAddon = new AttachAddon(webSocket); terminal.loadAddon(attachAddon); ``` -See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/xterm-addon-attach/typings/xterm-addon-attach.d.ts) for more advanced usage. +See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/addon-attach/typings/addon-attach.d.ts) for more advanced usage. diff --git a/addons/xterm-addon-web-links/package.json b/addons/addon-attach/package.json similarity index 65% rename from addons/xterm-addon-web-links/package.json rename to addons/addon-attach/package.json index 25f4ce83..94b483dd 100644 --- a/addons/xterm-addon-web-links/package.json +++ b/addons/addon-attach/package.json @@ -1,13 +1,13 @@ { - "name": "xterm-addon-web-links", + "name": "@xterm/addon-attach", "version": "0.9.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" }, - "main": "lib/xterm-addon-web-links.js", - "types": "typings/xterm-addon-web-links.d.ts", - "repository": "https://github.com/xtermjs/xterm.js", + "main": "lib/addon-attach.js", + "types": "typings/addon-attach.d.ts", + "repository": "https://github.com/xtermjs/xterm.js/tree/master/addons/addon-attach", "license": "MIT", "keywords": [ "terminal", @@ -21,7 +21,6 @@ "prepublishOnly": "npm run package" }, "peerDependencies": { - "xterm": "^5.0.0" + "@xterm/xterm": "^5.0.0" } - } diff --git a/addons/xterm-addon-attach/src/AttachAddon.ts b/addons/addon-attach/src/AttachAddon.ts similarity index 92% rename from addons/xterm-addon-attach/src/AttachAddon.ts rename to addons/addon-attach/src/AttachAddon.ts index 7fd8df29..37a10d17 100644 --- a/addons/xterm-addon-attach/src/AttachAddon.ts +++ b/addons/addon-attach/src/AttachAddon.ts @@ -5,13 +5,14 @@ * Implements the attach method, that attaches the terminal to a WebSocket stream. */ -import { Terminal, IDisposable, ITerminalAddon } from 'xterm'; +import type { Terminal, IDisposable, ITerminalAddon } from '@xterm/xterm'; +import type { AttachAddon as IAttachApi } from '@xterm/addon-attach'; interface IAttachOptions { bidirectional?: boolean; } -export class AttachAddon implements ITerminalAddon { +export class AttachAddon implements ITerminalAddon , IAttachApi { private _socket: WebSocket; private _bidirectional: boolean; private _disposables: IDisposable[] = []; diff --git a/addons/xterm-addon-attach/src/tsconfig.json b/addons/addon-attach/src/tsconfig.json similarity index 77% rename from addons/xterm-addon-attach/src/tsconfig.json rename to addons/addon-attach/src/tsconfig.json index 7e68574d..9838a3c5 100644 --- a/addons/xterm-addon-attach/src/tsconfig.json +++ b/addons/addon-attach/src/tsconfig.json @@ -13,7 +13,12 @@ "strict": true, "types": [ "../../../node_modules/@types/mocha" - ] + ], + "paths": { + "@xterm/addon-attach": [ + "../typings/addon-attach.d.ts" + ] + } }, "include": [ "./**/*", diff --git a/addons/xterm-addon-attach/test/AttachAddon.api.ts b/addons/addon-attach/test/AttachAddon.api.ts similarity index 100% rename from addons/xterm-addon-attach/test/AttachAddon.api.ts rename to addons/addon-attach/test/AttachAddon.api.ts diff --git a/addons/xterm-addon-attach/test/tsconfig.json b/addons/addon-attach/test/tsconfig.json similarity index 100% rename from addons/xterm-addon-attach/test/tsconfig.json rename to addons/addon-attach/test/tsconfig.json diff --git a/addons/xterm-addon-attach/tsconfig.json b/addons/addon-attach/tsconfig.json similarity index 100% rename from addons/xterm-addon-attach/tsconfig.json rename to addons/addon-attach/tsconfig.json diff --git a/addons/xterm-addon-attach/typings/xterm-addon-attach.d.ts b/addons/addon-attach/typings/addon-attach.d.ts similarity index 82% rename from addons/xterm-addon-attach/typings/xterm-addon-attach.d.ts rename to addons/addon-attach/typings/addon-attach.d.ts index 2956b9d9..4e1b2fc3 100644 --- a/addons/xterm-addon-attach/typings/xterm-addon-attach.d.ts +++ b/addons/addon-attach/typings/addon-attach.d.ts @@ -3,9 +3,9 @@ * @license MIT */ -import { Terminal, ITerminalAddon } from 'xterm'; +import { Terminal, ITerminalAddon } from '@xterm/xterm'; -declare module 'xterm-addon-attach' { +declare module '@xterm/addon-attach' { export interface IAttachOptions { /** * Whether input should be written to the backend. Defaults to `true`. diff --git a/addons/xterm-addon-attach/webpack.config.js b/addons/addon-attach/webpack.config.js similarity index 92% rename from addons/xterm-addon-attach/webpack.config.js rename to addons/addon-attach/webpack.config.js index 65996f19..599bb142 100644 --- a/addons/xterm-addon-attach/webpack.config.js +++ b/addons/addon-attach/webpack.config.js @@ -6,7 +6,7 @@ const path = require('path'); const addonName = 'AttachAddon'; -const mainFile = 'xterm-addon-attach.js'; +const mainFile = 'addon-attach.js'; module.exports = { entry: `./out/${addonName}.js`, diff --git a/addons/xterm-addon-canvas/.gitignore b/addons/addon-canvas/.gitignore similarity index 100% rename from addons/xterm-addon-canvas/.gitignore rename to addons/addon-canvas/.gitignore diff --git a/addons/xterm-addon-canvas/.npmignore b/addons/addon-canvas/.npmignore similarity index 100% rename from addons/xterm-addon-canvas/.npmignore rename to addons/addon-canvas/.npmignore diff --git a/addons/xterm-addon-canvas/LICENSE b/addons/addon-canvas/LICENSE similarity index 100% rename from addons/xterm-addon-canvas/LICENSE rename to addons/addon-canvas/LICENSE diff --git a/addons/addon-canvas/README.md b/addons/addon-canvas/README.md new file mode 100644 index 00000000..502ade25 --- /dev/null +++ b/addons/addon-canvas/README.md @@ -0,0 +1,28 @@ +## @xterm/addon-canvas + +An addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables a canvas-based renderer using a 2d context to draw. This addon requires xterm.js v5+. + +The purpose of this addon is to be used as a fallback for the [webgl addon](https://www.npmjs.com/package/@xterm/addon-webgl) when better performance is desired over the default DOM renderer, but WebGL2 isn't supported or performant for some reason. + +### Install + +```bash +npm install --save @xterm/addon-canvas +``` + +### Usage + +```ts +import { Terminal } from '@xterm/xterm'; +import { CanvasAddon } from '@xterm/addon-canvas'; + +const terminal = new Terminal(); +terminal.open(element); +terminal.loadAddon(new CanvasAddon()); +``` + +See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/addon-canvas/typings/addon-canvas.d.ts) for more advanced usage. + +### See also + +- [@xterm/addon-webgl](https://www.npmjs.com/package/@xterm/addon-webgl) A renderer for xterm.js that uses WebGL diff --git a/addons/xterm-addon-canvas/package.json b/addons/addon-canvas/package.json similarity index 69% rename from addons/xterm-addon-canvas/package.json rename to addons/addon-canvas/package.json index 76b97758..9aabb001 100644 --- a/addons/xterm-addon-canvas/package.json +++ b/addons/addon-canvas/package.json @@ -1,13 +1,13 @@ { - "name": "xterm-addon-canvas", + "name": "@xterm/addon-canvas", "version": "0.5.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" }, - "main": "lib/xterm-addon-canvas.js", - "types": "typings/xterm-addon-canvas.d.ts", - "repository": "https://github.com/xtermjs/xterm.js", + "main": "lib/addon-canvas.js", + "types": "typings/addon-canvas.d.ts", + "repository": "https://github.com/xtermjs/xterm.js/tree/master/addons/addon-canvas", "license": "MIT", "keywords": [ "terminal", @@ -23,6 +23,6 @@ "start-server-only": "node ../../demo/start-server-only" }, "peerDependencies": { - "xterm": "^5.0.0" + "@xterm/xterm": "^5.0.0" } } diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/addon-canvas/src/BaseRenderLayer.ts similarity index 98% rename from addons/xterm-addon-canvas/src/BaseRenderLayer.ts rename to addons/addon-canvas/src/BaseRenderLayer.ts index 731641a3..e7e23400 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/addon-canvas/src/BaseRenderLayer.ts @@ -19,7 +19,7 @@ import { ICellData } from 'common/Types'; import { CellData } from 'common/buffer/CellData'; import { WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; -import { Terminal } from 'xterm'; +import { Terminal } from '@xterm/xterm'; import { IRenderLayer } from './Types'; export abstract class BaseRenderLayer extends Disposable implements IRenderLayer { @@ -59,7 +59,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer ) { super(); this._cellColorResolver = new CellColorResolver(this._terminal, this._optionsService, this._selectionModel, this._decorationService, this._coreBrowserService, this._themeService); - this._canvas = document.createElement('canvas'); + this._canvas = this._coreBrowserService.mainDocument.createElement('canvas'); this._canvas.classList.add(`xterm-${id}-layer`); this._canvas.style.zIndex = zIndex.toString(); this._initCanvas(); @@ -91,7 +91,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer public handleGridChanged(startRow: number, endRow: number): void {} public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void { - this._selectionModel.update(this._terminal, start, end, columnSelectMode); + this._selectionModel.update((this._terminal as any)._core, start, end, columnSelectMode); } protected _setTransparency(alpha: boolean): void { diff --git a/addons/xterm-addon-canvas/src/CanvasAddon.ts b/addons/addon-canvas/src/CanvasAddon.ts similarity index 93% rename from addons/xterm-addon-canvas/src/CanvasAddon.ts rename to addons/addon-canvas/src/CanvasAddon.ts index 91438a4c..7f7f679b 100644 --- a/addons/xterm-addon-canvas/src/CanvasAddon.ts +++ b/addons/addon-canvas/src/CanvasAddon.ts @@ -3,16 +3,17 @@ * @license MIT */ +import type { ITerminalAddon, Terminal } from '@xterm/xterm'; +import type { CanvasAddon as ICanvasApi } from '@xterm/addon-canvas'; import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services'; import { ITerminal } from 'browser/Types'; import { EventEmitter, forwardEvent } from 'common/EventEmitter'; import { Disposable, toDisposable } from 'common/Lifecycle'; import { setTraceLogger } from 'common/services/LogService'; import { IBufferService, IDecorationService, ILogService } from 'common/services/Services'; -import { ITerminalAddon, Terminal } from 'xterm'; import { CanvasRenderer } from './CanvasRenderer'; -export class CanvasAddon extends Disposable implements ITerminalAddon { +export class CanvasAddon extends Disposable implements ITerminalAddon , ICanvasApi { private _terminal?: Terminal; private _renderer?: CanvasRenderer; @@ -65,4 +66,8 @@ export class CanvasAddon extends Disposable implements ITerminalAddon { this._renderer = undefined; })); } + + public clearTextureAtlas(): void { + this._renderer?.clearTextureAtlas(); + } } diff --git a/addons/xterm-addon-canvas/src/CanvasRenderer.ts b/addons/addon-canvas/src/CanvasRenderer.ts similarity index 99% rename from addons/xterm-addon-canvas/src/CanvasRenderer.ts rename to addons/addon-canvas/src/CanvasRenderer.ts index cd05f921..40b89546 100644 --- a/addons/xterm-addon-canvas/src/CanvasRenderer.ts +++ b/addons/addon-canvas/src/CanvasRenderer.ts @@ -12,7 +12,7 @@ import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IThemeS import { EventEmitter, forwardEvent } from 'common/EventEmitter'; import { Disposable, toDisposable } from 'common/Lifecycle'; import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; -import { Terminal } from 'xterm'; +import { Terminal } from '@xterm/xterm'; import { CursorRenderLayer } from './CursorRenderLayer'; import { LinkRenderLayer } from './LinkRenderLayer'; import { SelectionRenderLayer } from './SelectionRenderLayer'; diff --git a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts b/addons/addon-canvas/src/CursorRenderLayer.ts similarity index 99% rename from addons/xterm-addon-canvas/src/CursorRenderLayer.ts rename to addons/addon-canvas/src/CursorRenderLayer.ts index c5174f86..adc1db18 100644 --- a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts +++ b/addons/addon-canvas/src/CursorRenderLayer.ts @@ -12,7 +12,7 @@ import { isFirefox } from 'common/Platform'; import { ICellData } from 'common/Types'; import { CellData } from 'common/buffer/CellData'; import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; -import { Terminal } from 'xterm'; +import { Terminal } from '@xterm/xterm'; import { BaseRenderLayer } from './BaseRenderLayer'; interface ICursorState { diff --git a/addons/xterm-addon-canvas/src/GridCache.test.ts b/addons/addon-canvas/src/GridCache.test.ts similarity index 100% rename from addons/xterm-addon-canvas/src/GridCache.test.ts rename to addons/addon-canvas/src/GridCache.test.ts diff --git a/addons/xterm-addon-canvas/src/GridCache.ts b/addons/addon-canvas/src/GridCache.ts similarity index 100% rename from addons/xterm-addon-canvas/src/GridCache.ts rename to addons/addon-canvas/src/GridCache.ts diff --git a/addons/xterm-addon-canvas/src/LinkRenderLayer.ts b/addons/addon-canvas/src/LinkRenderLayer.ts similarity index 98% rename from addons/xterm-addon-canvas/src/LinkRenderLayer.ts rename to addons/addon-canvas/src/LinkRenderLayer.ts index 882f7d8c..1cdf71c2 100644 --- a/addons/xterm-addon-canvas/src/LinkRenderLayer.ts +++ b/addons/addon-canvas/src/LinkRenderLayer.ts @@ -9,7 +9,7 @@ import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants'; import { IRenderDimensions } from 'browser/renderer/shared/Types'; import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; -import { Terminal } from 'xterm'; +import { Terminal } from '@xterm/xterm'; import { BaseRenderLayer } from './BaseRenderLayer'; export class LinkRenderLayer extends BaseRenderLayer { diff --git a/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts b/addons/addon-canvas/src/SelectionRenderLayer.ts similarity index 99% rename from addons/xterm-addon-canvas/src/SelectionRenderLayer.ts rename to addons/addon-canvas/src/SelectionRenderLayer.ts index 10950868..01612a1d 100644 --- a/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts +++ b/addons/addon-canvas/src/SelectionRenderLayer.ts @@ -7,7 +7,7 @@ import { IRenderDimensions } from 'browser/renderer/shared/Types'; import { BaseRenderLayer } from './BaseRenderLayer'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; -import { Terminal } from 'xterm'; +import { Terminal } from '@xterm/xterm'; interface ISelectionState { start?: [number, number]; diff --git a/addons/xterm-addon-canvas/src/TextRenderLayer.ts b/addons/addon-canvas/src/TextRenderLayer.ts similarity index 99% rename from addons/xterm-addon-canvas/src/TextRenderLayer.ts rename to addons/addon-canvas/src/TextRenderLayer.ts index 46d052f3..43c33d64 100644 --- a/addons/xterm-addon-canvas/src/TextRenderLayer.ts +++ b/addons/addon-canvas/src/TextRenderLayer.ts @@ -11,7 +11,7 @@ import { AttributeData } from 'common/buffer/AttributeData'; import { CellData } from 'common/buffer/CellData'; import { Content, NULL_CELL_CODE } from 'common/buffer/Constants'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; -import { Terminal } from 'xterm'; +import { Terminal } from '@xterm/xterm'; import { BaseRenderLayer } from './BaseRenderLayer'; import { GridCache } from './GridCache'; diff --git a/addons/xterm-addon-canvas/src/Types.d.ts b/addons/addon-canvas/src/Types.d.ts similarity index 100% rename from addons/xterm-addon-canvas/src/Types.d.ts rename to addons/addon-canvas/src/Types.d.ts diff --git a/addons/xterm-addon-canvas/src/tsconfig.json b/addons/addon-canvas/src/tsconfig.json similarity index 89% rename from addons/xterm-addon-canvas/src/tsconfig.json rename to addons/addon-canvas/src/tsconfig.json index f6fb08a7..1ed8c2ac 100644 --- a/addons/xterm-addon-canvas/src/tsconfig.json +++ b/addons/addon-canvas/src/tsconfig.json @@ -17,6 +17,9 @@ ], "browser/*": [ "../../../src/browser/*" + ], + "@xterm/addon-canvas": [ + "../typings/addon-canvas.d.ts" ] }, "strict": true, diff --git a/addons/xterm-addon-canvas/test/CanvasRenderer.test.ts b/addons/addon-canvas/test/CanvasRenderer.test.ts similarity index 80% rename from addons/xterm-addon-canvas/test/CanvasRenderer.test.ts rename to addons/addon-canvas/test/CanvasRenderer.test.ts index 76f57394..2782081c 100644 --- a/addons/xterm-addon-canvas/test/CanvasRenderer.test.ts +++ b/addons/addon-canvas/test/CanvasRenderer.test.ts @@ -4,7 +4,7 @@ */ import test from '@playwright/test'; -import { ISharedRendererTestContext, injectSharedRendererTests } from '../../../out-test/playwright/SharedRendererTests'; +import { ISharedRendererTestContext, injectSharedRendererTests, injectSharedRendererTestsStandalone } from '../../../out-test/playwright/SharedRendererTests'; import { ITestContext, createTestContext, openTerminal } from '../../../out-test/playwright/TestUtils'; let ctx: ITestContext; @@ -28,4 +28,5 @@ test.describe('Canvas Renderer Integration Tests', () => { test.skip(({ browserName }) => browserName === 'webkit'); injectSharedRendererTests(ctxWrapper); + injectSharedRendererTestsStandalone(ctxWrapper); }); diff --git a/addons/xterm-addon-canvas/test/playwright.config.ts b/addons/addon-canvas/test/playwright.config.ts similarity index 100% rename from addons/xterm-addon-canvas/test/playwright.config.ts rename to addons/addon-canvas/test/playwright.config.ts diff --git a/addons/xterm-addon-canvas/test/tsconfig.json b/addons/addon-canvas/test/tsconfig.json similarity index 100% rename from addons/xterm-addon-canvas/test/tsconfig.json rename to addons/addon-canvas/test/tsconfig.json diff --git a/addons/xterm-addon-canvas/tsconfig.json b/addons/addon-canvas/tsconfig.json similarity index 100% rename from addons/xterm-addon-canvas/tsconfig.json rename to addons/addon-canvas/tsconfig.json diff --git a/addons/xterm-addon-canvas/typings/xterm-addon-canvas.d.ts b/addons/addon-canvas/typings/addon-canvas.d.ts similarity index 90% rename from addons/xterm-addon-canvas/typings/xterm-addon-canvas.d.ts rename to addons/addon-canvas/typings/addon-canvas.d.ts index c983825c..a679f0a5 100644 --- a/addons/xterm-addon-canvas/typings/xterm-addon-canvas.d.ts +++ b/addons/addon-canvas/typings/addon-canvas.d.ts @@ -3,9 +3,9 @@ * @license MIT */ -import { Terminal, ITerminalAddon, IEvent } from 'xterm'; +import { Terminal, ITerminalAddon, IEvent } from '@xterm/xterm'; -declare module 'xterm-addon-canvas' { +declare module '@xterm/addon-canvas' { /** * An xterm.js addon that provides search functionality. */ diff --git a/addons/xterm-addon-canvas/webpack.config.js b/addons/addon-canvas/webpack.config.js similarity index 94% rename from addons/xterm-addon-canvas/webpack.config.js rename to addons/addon-canvas/webpack.config.js index 9c98760a..9daa08f9 100644 --- a/addons/xterm-addon-canvas/webpack.config.js +++ b/addons/addon-canvas/webpack.config.js @@ -6,7 +6,7 @@ const path = require('path'); const addonName = 'CanvasAddon'; -const mainFile = 'xterm-addon-canvas.js'; +const mainFile = 'addon-canvas.js'; module.exports = { entry: `./out/${addonName}.js`, diff --git a/addons/xterm-addon-fit/.gitignore b/addons/addon-fit/.gitignore similarity index 100% rename from addons/xterm-addon-fit/.gitignore rename to addons/addon-fit/.gitignore diff --git a/addons/xterm-addon-fit/.npmignore b/addons/addon-fit/.npmignore similarity index 100% rename from addons/xterm-addon-fit/.npmignore rename to addons/addon-fit/.npmignore diff --git a/addons/xterm-addon-fit/LICENSE b/addons/addon-fit/LICENSE similarity index 100% rename from addons/xterm-addon-fit/LICENSE rename to addons/addon-fit/LICENSE diff --git a/addons/xterm-addon-fit/README.md b/addons/addon-fit/README.md similarity index 66% rename from addons/xterm-addon-fit/README.md rename to addons/addon-fit/README.md index 321b2cf7..076e5127 100644 --- a/addons/xterm-addon-fit/README.md +++ b/addons/addon-fit/README.md @@ -1,18 +1,18 @@ -## xterm-addon-fit +## @xterm/addon-fit An addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables fitting the terminal's dimensions to a containing element. This addon requires xterm.js v4+. ### Install ```bash -npm install --save xterm-addon-fit +npm install --save @xterm/addon-fit ``` ### Usage ```ts -import { Terminal } from 'xterm'; -import { FitAddon } from 'xterm-addon-fit'; +import { Terminal } from '@xterm/xterm'; +import { FitAddon } from '@xterm/addon-fit'; const terminal = new Terminal(); const fitAddon = new FitAddon(); @@ -21,4 +21,4 @@ terminal.open(containerElement); fitAddon.fit(); ``` -See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/xterm-addon-fit/typings/xterm-addon-fit.d.ts) for more advanced usage. +See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/addon-fit/typings/addon-fit.d.ts) for more advanced usage. diff --git a/addons/xterm-addon-fit/package.json b/addons/addon-fit/package.json similarity index 66% rename from addons/xterm-addon-fit/package.json rename to addons/addon-fit/package.json index df9ea857..585f3621 100644 --- a/addons/xterm-addon-fit/package.json +++ b/addons/addon-fit/package.json @@ -1,13 +1,13 @@ { - "name": "xterm-addon-fit", + "name": "@xterm/addon-fit", "version": "0.8.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" }, - "main": "lib/xterm-addon-fit.js", - "types": "typings/xterm-addon-fit.d.ts", - "repository": "https://github.com/xtermjs/xterm.js", + "main": "lib/addon-fit.js", + "types": "typings/addon-fit.d.ts", + "repository": "https://github.com/xtermjs/xterm.js/tree/master/addons/addon-fit", "license": "MIT", "keywords": [ "terminal", @@ -21,6 +21,6 @@ "prepublishOnly": "npm run package" }, "peerDependencies": { - "xterm": "^5.0.0" + "@xterm/xterm": "^5.0.0" } } diff --git a/addons/xterm-addon-fit/src/FitAddon.ts b/addons/addon-fit/src/FitAddon.ts similarity index 93% rename from addons/xterm-addon-fit/src/FitAddon.ts rename to addons/addon-fit/src/FitAddon.ts index f7245462..2087e614 100644 --- a/addons/xterm-addon-fit/src/FitAddon.ts +++ b/addons/addon-fit/src/FitAddon.ts @@ -3,7 +3,8 @@ * @license MIT */ -import { Terminal, ITerminalAddon } from 'xterm'; +import type { Terminal, ITerminalAddon } from '@xterm/xterm'; +import type { FitAddon as IFitApi } from '@xterm/addon-fit'; import { IRenderDimensions } from 'browser/renderer/shared/Types'; interface ITerminalDimensions { @@ -21,7 +22,7 @@ interface ITerminalDimensions { const MINIMUM_COLS = 2; const MINIMUM_ROWS = 1; -export class FitAddon implements ITerminalAddon { +export class FitAddon implements ITerminalAddon , IFitApi { private _terminal: Terminal | undefined; public activate(terminal: Terminal): void { diff --git a/addons/xterm-addon-fit/src/tsconfig.json b/addons/addon-fit/src/tsconfig.json similarity index 87% rename from addons/xterm-addon-fit/src/tsconfig.json rename to addons/addon-fit/src/tsconfig.json index 668f910b..068b4337 100644 --- a/addons/xterm-addon-fit/src/tsconfig.json +++ b/addons/addon-fit/src/tsconfig.json @@ -17,6 +17,9 @@ "paths": { "browser/*": [ "../../../src/browser/*" + ], + "@xterm/addon-fit": [ + "../typings/addon-fit.d.ts" ] } }, diff --git a/addons/xterm-addon-fit/test/FitAddon.api.ts b/addons/addon-fit/test/FitAddon.api.ts similarity index 100% rename from addons/xterm-addon-fit/test/FitAddon.api.ts rename to addons/addon-fit/test/FitAddon.api.ts diff --git a/addons/xterm-addon-fit/test/tsconfig.json b/addons/addon-fit/test/tsconfig.json similarity index 100% rename from addons/xterm-addon-fit/test/tsconfig.json rename to addons/addon-fit/test/tsconfig.json diff --git a/addons/xterm-addon-fit/tsconfig.json b/addons/addon-fit/tsconfig.json similarity index 100% rename from addons/xterm-addon-fit/tsconfig.json rename to addons/addon-fit/tsconfig.json diff --git a/addons/xterm-addon-fit/typings/xterm-addon-fit.d.ts b/addons/addon-fit/typings/addon-fit.d.ts similarity index 92% rename from addons/xterm-addon-fit/typings/xterm-addon-fit.d.ts rename to addons/addon-fit/typings/addon-fit.d.ts index 2d1b8f1d..e3d20e29 100644 --- a/addons/xterm-addon-fit/typings/xterm-addon-fit.d.ts +++ b/addons/addon-fit/typings/addon-fit.d.ts @@ -3,9 +3,9 @@ * @license MIT */ -import { Terminal, ITerminalAddon } from 'xterm'; +import { Terminal, ITerminalAddon } from '@xterm/xterm'; -declare module 'xterm-addon-fit' { +declare module '@xterm/addon-fit' { /** * An xterm.js addon that enables resizing the terminal to the dimensions of * its containing element. diff --git a/addons/xterm-addon-fit/webpack.config.js b/addons/addon-fit/webpack.config.js similarity index 93% rename from addons/xterm-addon-fit/webpack.config.js rename to addons/addon-fit/webpack.config.js index 4b542150..e220668c 100644 --- a/addons/xterm-addon-fit/webpack.config.js +++ b/addons/addon-fit/webpack.config.js @@ -6,7 +6,7 @@ const path = require('path'); const addonName = 'FitAddon'; -const mainFile = 'xterm-addon-fit.js'; +const mainFile = 'addon-fit.js'; module.exports = { entry: `./out/${addonName}.js`, diff --git a/addons/xterm-addon-image/.gitignore b/addons/addon-image/.gitignore similarity index 100% rename from addons/xterm-addon-image/.gitignore rename to addons/addon-image/.gitignore diff --git a/addons/xterm-addon-image/.npmignore b/addons/addon-image/.npmignore similarity index 100% rename from addons/xterm-addon-image/.npmignore rename to addons/addon-image/.npmignore diff --git a/addons/xterm-addon-image/LICENSE b/addons/addon-image/LICENSE similarity index 100% rename from addons/xterm-addon-image/LICENSE rename to addons/addon-image/LICENSE diff --git a/addons/xterm-addon-image/README.md b/addons/addon-image/README.md similarity index 98% rename from addons/xterm-addon-image/README.md rename to addons/addon-image/README.md index 686df9b4..9fd327e3 100644 --- a/addons/xterm-addon-image/README.md +++ b/addons/addon-image/README.md @@ -1,4 +1,4 @@ -## xterm-addon-image +## @xterm/addon-image Inline image output in xterm.js. Supports SIXEL and iTerm's inline image protocol (IIP). @@ -9,15 +9,15 @@ Inline image output in xterm.js. Supports SIXEL and iTerm's inline image protoco ### Install from npm ```bash -npm install --save xterm-addon-image +npm install --save @xterm/addon-image ``` ### Usage ```ts -import { Terminal } from 'xterm'; -import { ImageAddon, IImageAddonOptions } from 'xterm-addon-image'; +import { Terminal } from '@xterm/xterm'; +import { ImageAddon, IImageAddonOptions } from '@xterm/addon-image'; // customize as needed (showing addon defaults) const customSettings: IImageAddonOptions = { @@ -227,5 +227,5 @@ _How can I adjust the memory usage?_ - 0.1.2 bugfix: reset clear flag - 0.1.1 bugfixes: - clear sticky image tiles on render - - create xterm-addon-image folder from bootstrap.sh + - create folder from bootstrap.sh - fix peer dependency in package.json diff --git a/addons/xterm-addon-image/fixture/endless.sh b/addons/addon-image/fixture/endless.sh old mode 100755 new mode 100644 similarity index 100% rename from addons/xterm-addon-image/fixture/endless.sh rename to addons/addon-image/fixture/endless.sh diff --git a/addons/xterm-addon-image/fixture/example.png b/addons/addon-image/fixture/example.png similarity index 100% rename from addons/xterm-addon-image/fixture/example.png rename to addons/addon-image/fixture/example.png diff --git a/addons/xterm-addon-image/fixture/gcrglf.sh b/addons/addon-image/fixture/gcrglf.sh old mode 100755 new mode 100644 similarity index 100% rename from addons/xterm-addon-image/fixture/gcrglf.sh rename to addons/addon-image/fixture/gcrglf.sh diff --git a/addons/xterm-addon-image/fixture/growing_rect.js b/addons/addon-image/fixture/growing_rect.js similarity index 100% rename from addons/xterm-addon-image/fixture/growing_rect.js rename to addons/addon-image/fixture/growing_rect.js diff --git a/addons/xterm-addon-image/fixture/iip/palette.iip b/addons/addon-image/fixture/iip/palette.iip similarity index 100% rename from addons/xterm-addon-image/fixture/iip/palette.iip rename to addons/addon-image/fixture/iip/palette.iip diff --git a/addons/xterm-addon-image/fixture/iip/spinfox.iip b/addons/addon-image/fixture/iip/spinfox.iip similarity index 100% rename from addons/xterm-addon-image/fixture/iip/spinfox.iip rename to addons/addon-image/fixture/iip/spinfox.iip diff --git a/addons/xterm-addon-image/fixture/iip/w3c_gif.iip b/addons/addon-image/fixture/iip/w3c_gif.iip similarity index 100% rename from addons/xterm-addon-image/fixture/iip/w3c_gif.iip rename to addons/addon-image/fixture/iip/w3c_gif.iip diff --git a/addons/xterm-addon-image/fixture/iip/w3c_jpg.iip b/addons/addon-image/fixture/iip/w3c_jpg.iip similarity index 100% rename from addons/xterm-addon-image/fixture/iip/w3c_jpg.iip rename to addons/addon-image/fixture/iip/w3c_jpg.iip diff --git a/addons/xterm-addon-image/fixture/iip/w3c_png.iip b/addons/addon-image/fixture/iip/w3c_png.iip similarity index 100% rename from addons/xterm-addon-image/fixture/iip/w3c_png.iip rename to addons/addon-image/fixture/iip/w3c_png.iip diff --git a/addons/xterm-addon-image/fixture/inspect_palette.sh b/addons/addon-image/fixture/inspect_palette.sh old mode 100755 new mode 100644 similarity index 100% rename from addons/xterm-addon-image/fixture/inspect_palette.sh rename to addons/addon-image/fixture/inspect_palette.sh diff --git a/addons/xterm-addon-image/fixture/overdraw.sh b/addons/addon-image/fixture/overdraw.sh old mode 100755 new mode 100644 similarity index 100% rename from addons/xterm-addon-image/fixture/overdraw.sh rename to addons/addon-image/fixture/overdraw.sh diff --git a/addons/xterm-addon-image/fixture/palette.blob b/addons/addon-image/fixture/palette.blob similarity index 100% rename from addons/xterm-addon-image/fixture/palette.blob rename to addons/addon-image/fixture/palette.blob diff --git a/addons/xterm-addon-image/fixture/palette.png b/addons/addon-image/fixture/palette.png similarity index 100% rename from addons/xterm-addon-image/fixture/palette.png rename to addons/addon-image/fixture/palette.png diff --git a/addons/xterm-addon-image/fixture/palette.sixel b/addons/addon-image/fixture/palette.sixel similarity index 100% rename from addons/xterm-addon-image/fixture/palette.sixel rename to addons/addon-image/fixture/palette.sixel diff --git a/addons/xterm-addon-image/fixture/testimages/agfa-makernotes.jpg b/addons/addon-image/fixture/testimages/agfa-makernotes.jpg similarity index 100% rename from addons/xterm-addon-image/fixture/testimages/agfa-makernotes.jpg rename to addons/addon-image/fixture/testimages/agfa-makernotes.jpg diff --git a/addons/xterm-addon-image/fixture/testimages/iphone_hdr_YES.jpg b/addons/addon-image/fixture/testimages/iphone_hdr_YES.jpg similarity index 100% rename from addons/xterm-addon-image/fixture/testimages/iphone_hdr_YES.jpg rename to addons/addon-image/fixture/testimages/iphone_hdr_YES.jpg diff --git a/addons/xterm-addon-image/fixture/testimages/nikon-e950.jpg b/addons/addon-image/fixture/testimages/nikon-e950.jpg similarity index 100% rename from addons/xterm-addon-image/fixture/testimages/nikon-e950.jpg rename to addons/addon-image/fixture/testimages/nikon-e950.jpg diff --git a/addons/xterm-addon-image/fixture/testimages/sony-alpha-6000.jpg b/addons/addon-image/fixture/testimages/sony-alpha-6000.jpg similarity index 100% rename from addons/xterm-addon-image/fixture/testimages/sony-alpha-6000.jpg rename to addons/addon-image/fixture/testimages/sony-alpha-6000.jpg diff --git a/addons/xterm-addon-image/fixture/testimages/spinfox.png b/addons/addon-image/fixture/testimages/spinfox.png similarity index 100% rename from addons/xterm-addon-image/fixture/testimages/spinfox.png rename to addons/addon-image/fixture/testimages/spinfox.png diff --git a/addons/xterm-addon-image/fixture/testimages/w3c_home.gif b/addons/addon-image/fixture/testimages/w3c_home.gif similarity index 100% rename from addons/xterm-addon-image/fixture/testimages/w3c_home.gif rename to addons/addon-image/fixture/testimages/w3c_home.gif diff --git a/addons/xterm-addon-image/fixture/testimages/w3c_home.jpg b/addons/addon-image/fixture/testimages/w3c_home.jpg similarity index 100% rename from addons/xterm-addon-image/fixture/testimages/w3c_home.jpg rename to addons/addon-image/fixture/testimages/w3c_home.jpg diff --git a/addons/xterm-addon-image/fixture/testimages/w3c_home.png b/addons/addon-image/fixture/testimages/w3c_home.png similarity index 100% rename from addons/xterm-addon-image/fixture/testimages/w3c_home.png rename to addons/addon-image/fixture/testimages/w3c_home.png diff --git a/addons/xterm-addon-image/fixture/testimages/w3c_home_2.gif b/addons/addon-image/fixture/testimages/w3c_home_2.gif similarity index 100% rename from addons/xterm-addon-image/fixture/testimages/w3c_home_2.gif rename to addons/addon-image/fixture/testimages/w3c_home_2.gif diff --git a/addons/xterm-addon-image/fixture/testimages/w3c_home_2.jpg b/addons/addon-image/fixture/testimages/w3c_home_2.jpg similarity index 100% rename from addons/xterm-addon-image/fixture/testimages/w3c_home_2.jpg rename to addons/addon-image/fixture/testimages/w3c_home_2.jpg diff --git a/addons/xterm-addon-image/fixture/testimages/w3c_home_2.png b/addons/addon-image/fixture/testimages/w3c_home_2.png similarity index 100% rename from addons/xterm-addon-image/fixture/testimages/w3c_home_2.png rename to addons/addon-image/fixture/testimages/w3c_home_2.png diff --git a/addons/xterm-addon-image/fixture/testimages/w3c_home_256.gif b/addons/addon-image/fixture/testimages/w3c_home_256.gif similarity index 100% rename from addons/xterm-addon-image/fixture/testimages/w3c_home_256.gif rename to addons/addon-image/fixture/testimages/w3c_home_256.gif diff --git a/addons/xterm-addon-image/fixture/testimages/w3c_home_256.jpg b/addons/addon-image/fixture/testimages/w3c_home_256.jpg similarity index 100% rename from addons/xterm-addon-image/fixture/testimages/w3c_home_256.jpg rename to addons/addon-image/fixture/testimages/w3c_home_256.jpg diff --git a/addons/xterm-addon-image/fixture/testimages/w3c_home_256.png b/addons/addon-image/fixture/testimages/w3c_home_256.png similarity index 100% rename from addons/xterm-addon-image/fixture/testimages/w3c_home_256.png rename to addons/addon-image/fixture/testimages/w3c_home_256.png diff --git a/addons/xterm-addon-image/fixture/testimages/w3c_home_animation.gif b/addons/addon-image/fixture/testimages/w3c_home_animation.gif similarity index 100% rename from addons/xterm-addon-image/fixture/testimages/w3c_home_animation.gif rename to addons/addon-image/fixture/testimages/w3c_home_animation.gif diff --git a/addons/xterm-addon-image/fixture/testimages/w3c_home_gray.gif b/addons/addon-image/fixture/testimages/w3c_home_gray.gif similarity index 100% rename from addons/xterm-addon-image/fixture/testimages/w3c_home_gray.gif rename to addons/addon-image/fixture/testimages/w3c_home_gray.gif diff --git a/addons/xterm-addon-image/fixture/testimages/w3c_home_gray.jpg b/addons/addon-image/fixture/testimages/w3c_home_gray.jpg similarity index 100% rename from addons/xterm-addon-image/fixture/testimages/w3c_home_gray.jpg rename to addons/addon-image/fixture/testimages/w3c_home_gray.jpg diff --git a/addons/xterm-addon-image/fixture/testimages/w3c_home_gray.png b/addons/addon-image/fixture/testimages/w3c_home_gray.png similarity index 100% rename from addons/xterm-addon-image/fixture/testimages/w3c_home_gray.png rename to addons/addon-image/fixture/testimages/w3c_home_gray.png diff --git a/addons/xterm-addon-image/fixture/textcursor.sh b/addons/addon-image/fixture/textcursor.sh old mode 100755 new mode 100644 similarity index 100% rename from addons/xterm-addon-image/fixture/textcursor.sh rename to addons/addon-image/fixture/textcursor.sh diff --git a/addons/xterm-addon-image/package.json b/addons/addon-image/package.json similarity index 69% rename from addons/xterm-addon-image/package.json rename to addons/addon-image/package.json index 4669fea2..8572e330 100644 --- a/addons/xterm-addon-image/package.json +++ b/addons/addon-image/package.json @@ -1,13 +1,13 @@ { - "name": "xterm-addon-image", + "name": "@xterm/addon-image", "version": "0.6.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" }, - "main": "lib/xterm-addon-image.js", - "types": "typings/xterm-addon-image.d.ts", - "repository": "https://github.com/xtermjs/xterm.js", + "main": "lib/addon-image.js", + "types": "typings/addon-image.d.ts", + "repository": "https://github.com/xtermjs/xterm.js/tree/master/addons/addon-image", "license": "MIT", "keywords": [ "terminal", @@ -22,7 +22,7 @@ "prepublishOnly": "npm run package" }, "peerDependencies": { - "xterm": "^5.2.0" + "@xterm/xterm": "^5.2.0" }, "devDependencies": { "sixel": "^0.16.0", diff --git a/addons/xterm-addon-image/src/IIPHandler.ts b/addons/addon-image/src/IIPHandler.ts similarity index 100% rename from addons/xterm-addon-image/src/IIPHandler.ts rename to addons/addon-image/src/IIPHandler.ts diff --git a/addons/xterm-addon-image/src/IIPHeaderParser.test.ts b/addons/addon-image/src/IIPHeaderParser.test.ts similarity index 100% rename from addons/xterm-addon-image/src/IIPHeaderParser.test.ts rename to addons/addon-image/src/IIPHeaderParser.test.ts diff --git a/addons/xterm-addon-image/src/IIPHeaderParser.ts b/addons/addon-image/src/IIPHeaderParser.ts similarity index 100% rename from addons/xterm-addon-image/src/IIPHeaderParser.ts rename to addons/addon-image/src/IIPHeaderParser.ts diff --git a/addons/xterm-addon-image/src/IIPMetrics.test.ts b/addons/addon-image/src/IIPMetrics.test.ts similarity index 94% rename from addons/xterm-addon-image/src/IIPMetrics.test.ts rename to addons/addon-image/src/IIPMetrics.test.ts index 688d8d39..8ad5f05c 100644 --- a/addons/xterm-addon-image/src/IIPMetrics.test.ts +++ b/addons/addon-image/src/IIPMetrics.test.ts @@ -36,7 +36,7 @@ const TEST_IMAGES: [string, IMetrics][] = [ describe('IIPMetrics', () => { it('bunch of testimages', () => { for (let i = 0; i < TEST_IMAGES.length; ++i) { - const imageData = fs.readFileSync('./addons/xterm-addon-image/fixture/testimages/' + TEST_IMAGES[i][0]); + const imageData = fs.readFileSync('./addons/addon-image/fixture/testimages/' + TEST_IMAGES[i][0]); assert.deepStrictEqual(imageType(imageData), TEST_IMAGES[i][1]); } }); diff --git a/addons/xterm-addon-image/src/IIPMetrics.ts b/addons/addon-image/src/IIPMetrics.ts similarity index 100% rename from addons/xterm-addon-image/src/IIPMetrics.ts rename to addons/addon-image/src/IIPMetrics.ts diff --git a/addons/xterm-addon-image/src/ImageAddon.ts b/addons/addon-image/src/ImageAddon.ts similarity index 98% rename from addons/xterm-addon-image/src/ImageAddon.ts rename to addons/addon-image/src/ImageAddon.ts index bb633e6b..167e93a7 100644 --- a/addons/xterm-addon-image/src/ImageAddon.ts +++ b/addons/addon-image/src/ImageAddon.ts @@ -3,14 +3,14 @@ * @license MIT */ +import type { ITerminalAddon, IDisposable } from '@xterm/xterm'; +import type { ImageAddon as IImageApi } from '@xterm/addon-image'; import { IIPHandler } from './IIPHandler'; -import { ITerminalAddon, IDisposable } from 'xterm'; import { ImageRenderer } from './ImageRenderer'; import { ImageStorage, CELL_SIZE_DEFAULT } from './ImageStorage'; import { SixelHandler } from './SixelHandler'; import { ITerminalExt, IImageAddonOptions, IResetHandler } from './Types'; - // default values of addon ctor options const DEFAULT_OPTIONS: IImageAddonOptions = { enableSizeReports: true, @@ -48,7 +48,7 @@ const enum GaStatus { } -export class ImageAddon implements ITerminalAddon { +export class ImageAddon implements ITerminalAddon , IImageApi { private _opts: IImageAddonOptions; private _defaultOpts: IImageAddonOptions; private _storage: ImageStorage | undefined; diff --git a/addons/xterm-addon-image/src/ImageRenderer.ts b/addons/addon-image/src/ImageRenderer.ts similarity index 99% rename from addons/xterm-addon-image/src/ImageRenderer.ts rename to addons/addon-image/src/ImageRenderer.ts index 9b528efe..214a2e6b 100644 --- a/addons/xterm-addon-image/src/ImageRenderer.ts +++ b/addons/addon-image/src/ImageRenderer.ts @@ -4,7 +4,7 @@ */ import { toRGBA8888 } from 'sixel/lib/Colors'; -import { IDisposable } from 'xterm'; +import { IDisposable } from '@xterm/xterm'; import { ICellSize, ITerminalExt, IImageSpec, IRenderDimensions, IRenderService } from './Types'; import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle'; diff --git a/addons/xterm-addon-image/src/ImageStorage.ts b/addons/addon-image/src/ImageStorage.ts similarity index 99% rename from addons/xterm-addon-image/src/ImageStorage.ts rename to addons/addon-image/src/ImageStorage.ts index 2fb12c14..39de0959 100644 --- a/addons/xterm-addon-image/src/ImageStorage.ts +++ b/addons/addon-image/src/ImageStorage.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IDisposable } from 'xterm'; +import { IDisposable } from '@xterm/xterm'; import { ImageRenderer } from './ImageRenderer'; import { ITerminalExt, IExtendedAttrsImage, IImageAddonOptions, IImageSpec, IBufferLineExt, BgFlags, Cell, Content, ICellSize, ExtFlags, Attributes, UnderlineStyle } from './Types'; diff --git a/addons/xterm-addon-image/src/SixelHandler.ts b/addons/addon-image/src/SixelHandler.ts similarity index 100% rename from addons/xterm-addon-image/src/SixelHandler.ts rename to addons/addon-image/src/SixelHandler.ts diff --git a/addons/xterm-addon-image/src/Types.d.ts b/addons/addon-image/src/Types.d.ts similarity index 98% rename from addons/xterm-addon-image/src/Types.d.ts rename to addons/addon-image/src/Types.d.ts index 62f4481a..60de0f3d 100644 --- a/addons/xterm-addon-image/src/Types.d.ts +++ b/addons/addon-image/src/Types.d.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IDisposable, IMarker, Terminal } from 'xterm'; +import { IDisposable, IMarker, Terminal } from '@xterm/xterm'; // private imports from base repo we build against import { Attributes, BgFlags, Content, ExtFlags, UnderlineStyle } from 'common/buffer/Constants'; diff --git a/addons/xterm-addon-image/src/tsconfig.json b/addons/addon-image/src/tsconfig.json similarity index 83% rename from addons/xterm-addon-image/src/tsconfig.json rename to addons/addon-image/src/tsconfig.json index da519e17..a18b47c9 100644 --- a/addons/xterm-addon-image/src/tsconfig.json +++ b/addons/addon-image/src/tsconfig.json @@ -14,7 +14,8 @@ "baseUrl": ".", "paths": { "browser/*": [ "../../../src/browser/*" ], - "common/*": [ "../../../src/common/*" ] + "common/*": [ "../../../src/common/*" ], + "@xterm/addon-image": [ "../typings/addon-image.d.ts" ] } }, "include": [ diff --git a/addons/xterm-addon-image/test/ImageAddon.api.ts b/addons/addon-image/test/ImageAddon.api.ts similarity index 95% rename from addons/xterm-addon-image/test/ImageAddon.api.ts rename to addons/addon-image/test/ImageAddon.api.ts index 43334bbb..776e96bf 100644 --- a/addons/xterm-addon-image/test/ImageAddon.api.ts +++ b/addons/addon-image/test/ImageAddon.api.ts @@ -39,7 +39,7 @@ interface IDimensions { // image: 640 x 80, 512 color const TESTDATA: ITestData = (() => { - const data8 = readFileSync('./addons/xterm-addon-image/fixture/palette.blob'); + const data8 = readFileSync('./addons/addon-image/fixture/palette.blob'); const data32 = new Uint32Array(data8.buffer); const palette = new Set(); for (let i = 0; i < data32.length; ++i) palette.add(data32[i]); @@ -58,11 +58,11 @@ const SIXEL_SEQ_0 = introducer(0) + TESTDATA.sixel + FINALIZER; // NOTE: the data is loaded as string for easier transport through playwright const TESTDATA_IIP: [string, [number, number]][] = [ - [readFileSync('./addons/xterm-addon-image/fixture/iip/palette.iip', { encoding: 'utf-8' }), [640, 80]], - [readFileSync('./addons/xterm-addon-image/fixture/iip/spinfox.iip', { encoding: 'utf-8' }), [148, 148]], - [readFileSync('./addons/xterm-addon-image/fixture/iip/w3c_gif.iip', { encoding: 'utf-8' }), [72, 48]], - [readFileSync('./addons/xterm-addon-image/fixture/iip/w3c_jpg.iip', { encoding: 'utf-8' }), [72, 48]], - [readFileSync('./addons/xterm-addon-image/fixture/iip/w3c_png.iip', { encoding: 'utf-8' }), [72, 48]] + [readFileSync('./addons/addon-image/fixture/iip/palette.iip', { encoding: 'utf-8' }), [640, 80]], + [readFileSync('./addons/addon-image/fixture/iip/spinfox.iip', { encoding: 'utf-8' }), [148, 148]], + [readFileSync('./addons/addon-image/fixture/iip/w3c_gif.iip', { encoding: 'utf-8' }), [72, 48]], + [readFileSync('./addons/addon-image/fixture/iip/w3c_jpg.iip', { encoding: 'utf-8' }), [72, 48]], + [readFileSync('./addons/addon-image/fixture/iip/w3c_png.iip', { encoding: 'utf-8' }), [72, 48]] ]; describe('ImageAddon', () => { diff --git a/addons/xterm-addon-image/test/tsconfig.json b/addons/addon-image/test/tsconfig.json similarity index 100% rename from addons/xterm-addon-image/test/tsconfig.json rename to addons/addon-image/test/tsconfig.json diff --git a/addons/xterm-addon-image/tsconfig.json b/addons/addon-image/tsconfig.json similarity index 100% rename from addons/xterm-addon-image/tsconfig.json rename to addons/addon-image/tsconfig.json diff --git a/addons/xterm-addon-image/typings/xterm-addon-image.d.ts b/addons/addon-image/typings/addon-image.d.ts similarity index 97% rename from addons/xterm-addon-image/typings/xterm-addon-image.d.ts rename to addons/addon-image/typings/addon-image.d.ts index 94dc6f65..48ba488c 100644 --- a/addons/xterm-addon-image/typings/xterm-addon-image.d.ts +++ b/addons/addon-image/typings/addon-image.d.ts @@ -3,9 +3,9 @@ * @license MIT */ -import { Terminal, ITerminalAddon } from 'xterm'; +import { Terminal, ITerminalAddon } from '@xterm/xterm'; -declare module 'xterm-addon-image' { +declare module '@xterm/addon-image' { export interface IImageAddonOptions { /** * Enable size reports in windowOptions: @@ -37,7 +37,7 @@ declare module 'xterm-addon-image' { * * Note: `storageLimit` bytes are calculated from images by multiplying the pixels with 4 * (4 channels with one byte, images are stored as RGBA8888). - * + * * Default is 2^16 (4096 x 4096 pixels). */ pixelLimit?: number; diff --git a/addons/xterm-addon-image/webpack.config.js b/addons/addon-image/webpack.config.js similarity index 71% rename from addons/xterm-addon-image/webpack.config.js rename to addons/addon-image/webpack.config.js index fff66bec..b4283b66 100644 --- a/addons/xterm-addon-image/webpack.config.js +++ b/addons/addon-image/webpack.config.js @@ -6,7 +6,7 @@ const path = require('path'); const addonName = 'ImageAddon'; -const mainFile = 'xterm-addon-image.js'; +const mainFile = 'addon-image.js'; const addon = { entry: `./out/${addonName}.js`, @@ -21,6 +21,14 @@ const addon = { } ] }, + resolve: { + modules: ['./node_modules'], + extensions: [ '.js' ], + alias: { + common: path.resolve('../../out/common'), + browser: path.resolve('../../out/browser') + } + }, output: { filename: mainFile, path: path.resolve('./lib'), diff --git a/addons/xterm-addon-ligatures/.gitignore b/addons/addon-ligatures/.gitignore similarity index 100% rename from addons/xterm-addon-ligatures/.gitignore rename to addons/addon-ligatures/.gitignore diff --git a/addons/xterm-addon-ligatures/.npmignore b/addons/addon-ligatures/.npmignore similarity index 100% rename from addons/xterm-addon-ligatures/.npmignore rename to addons/addon-ligatures/.npmignore diff --git a/addons/xterm-addon-ligatures/LICENSE b/addons/addon-ligatures/LICENSE similarity index 100% rename from addons/xterm-addon-ligatures/LICENSE rename to addons/addon-ligatures/LICENSE diff --git a/addons/xterm-addon-ligatures/README.md b/addons/addon-ligatures/README.md similarity index 93% rename from addons/xterm-addon-ligatures/README.md rename to addons/addon-ligatures/README.md index fad2a7ef..3b5521e0 100644 --- a/addons/xterm-addon-ligatures/README.md +++ b/addons/addon-ligatures/README.md @@ -1,4 +1,4 @@ -## xterm-addon-ligatures +## @xterm/addon-ligatures Add support for programming ligatures to [xterm.js] when running in environments with access to [Node.js] APIs (such as [Electron]). @@ -10,14 +10,14 @@ Add support for programming ligatures to [xterm.js] when running in environments ### Install ```bash -npm install --save xterm-addon-ligatures +npm install --save @xterm/addon-ligatures ``` ### Usage ```ts -import { Terminal } from 'xterm'; -import { LigaturesAddon } from 'xterm-addon-ligatures'; +import { Terminal } from '@xterm/xterm'; +import { LigaturesAddon } from '@xterm/addon-ligatures'; const terminal = new Terminal(); const ligaturesAddon = new LigaturesAddon(); diff --git a/addons/xterm-addon-ligatures/bin/download-fonts.js b/addons/addon-ligatures/bin/download-fonts.js similarity index 100% rename from addons/xterm-addon-ligatures/bin/download-fonts.js rename to addons/addon-ligatures/bin/download-fonts.js diff --git a/addons/xterm-addon-ligatures/package.json b/addons/addon-ligatures/package.json similarity index 79% rename from addons/xterm-addon-ligatures/package.json rename to addons/addon-ligatures/package.json index 1ca896ca..30251888 100644 --- a/addons/xterm-addon-ligatures/package.json +++ b/addons/addon-ligatures/package.json @@ -1,14 +1,14 @@ { - "name": "xterm-addon-ligatures", + "name": "@xterm/addon-ligatures", "version": "0.7.0", "description": "Add support for programming ligatures to xterm.js", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" }, - "main": "lib/xterm-addon-ligatures.js", - "types": "typings/xterm-addon-ligatures.d.ts", - "repository": "https://github.com/xtermjs/xterm.js", + "main": "lib/addon-ligatures.js", + "types": "typings/addon-ligatures.d.ts", + "repository": "https://github.com/xtermjs/xterm.js/tree/master/addons/addon-ligatures", "engines": { "node": ">8.0.0" }, @@ -42,6 +42,6 @@ "yauzl": "^2.10.0" }, "peerDependencies": { - "xterm": "^5.0.0" + "@xterm/xterm": "^5.0.0" } } diff --git a/addons/xterm-addon-ligatures/src/LigaturesAddon.ts b/addons/addon-ligatures/src/LigaturesAddon.ts similarity index 87% rename from addons/xterm-addon-ligatures/src/LigaturesAddon.ts rename to addons/addon-ligatures/src/LigaturesAddon.ts index c4586b38..cb589303 100644 --- a/addons/xterm-addon-ligatures/src/LigaturesAddon.ts +++ b/addons/addon-ligatures/src/LigaturesAddon.ts @@ -3,7 +3,8 @@ * @license MIT */ -import { Terminal } from 'xterm'; +import type { Terminal } from '@xterm/xterm'; +import type { LigaturesAddon as ILigaturesApi } from '@xterm/addon-ligatures'; import { enableLigatures } from '.'; import { ILigatureOptions } from './Types'; @@ -12,7 +13,7 @@ export interface ITerminalAddon { dispose(): void; } -export class LigaturesAddon implements ITerminalAddon { +export class LigaturesAddon implements ITerminalAddon , ILigaturesApi { private readonly _fallbackLigatures: string[]; private _terminal: Terminal | undefined; diff --git a/addons/xterm-addon-ligatures/src/Types.d.ts b/addons/addon-ligatures/src/Types.d.ts similarity index 100% rename from addons/xterm-addon-ligatures/src/Types.d.ts rename to addons/addon-ligatures/src/Types.d.ts diff --git a/addons/xterm-addon-ligatures/src/font.ts b/addons/addon-ligatures/src/font.ts similarity index 100% rename from addons/xterm-addon-ligatures/src/font.ts rename to addons/addon-ligatures/src/font.ts diff --git a/addons/xterm-addon-ligatures/src/index.test.ts b/addons/addon-ligatures/src/index.test.ts similarity index 99% rename from addons/xterm-addon-ligatures/src/index.test.ts rename to addons/addon-ligatures/src/index.test.ts index c7bf09ce..c6aead98 100644 --- a/addons/xterm-addon-ligatures/src/index.test.ts +++ b/addons/addon-ligatures/src/index.test.ts @@ -11,7 +11,7 @@ import * as fontLigatures from 'font-ligatures'; import * as ligatureSupport from '.'; -describe('xterm-addon-ligatures', () => { +describe('LigaturesAddon', () => { let onRefresh: sinon.SinonStub; let term: MockTerminal; diff --git a/addons/xterm-addon-ligatures/src/index.ts b/addons/addon-ligatures/src/index.ts similarity index 98% rename from addons/xterm-addon-ligatures/src/index.ts rename to addons/addon-ligatures/src/index.ts index 82675626..bd8ff215 100644 --- a/addons/xterm-addon-ligatures/src/index.ts +++ b/addons/addon-ligatures/src/index.ts @@ -3,7 +3,7 @@ * @license MIT */ -import type { Terminal } from 'xterm'; +import type { Terminal } from '@xterm/xterm'; import { Font } from 'font-ligatures'; import load from './font'; diff --git a/addons/xterm-addon-ligatures/src/parse.test.ts b/addons/addon-ligatures/src/parse.test.ts similarity index 100% rename from addons/xterm-addon-ligatures/src/parse.test.ts rename to addons/addon-ligatures/src/parse.test.ts diff --git a/addons/xterm-addon-ligatures/src/parse.ts b/addons/addon-ligatures/src/parse.ts similarity index 100% rename from addons/xterm-addon-ligatures/src/parse.ts rename to addons/addon-ligatures/src/parse.ts diff --git a/addons/xterm-addon-ligatures/src/tsconfig.json b/addons/addon-ligatures/src/tsconfig.json similarity index 75% rename from addons/xterm-addon-ligatures/src/tsconfig.json rename to addons/addon-ligatures/src/tsconfig.json index 28969f97..cc9a9bef 100644 --- a/addons/xterm-addon-ligatures/src/tsconfig.json +++ b/addons/addon-ligatures/src/tsconfig.json @@ -10,7 +10,12 @@ "preserveWatchOutput": true, "types": [ "../../../node_modules/@types/mocha" - ] + ], + "paths": { + "@xterm/addon-ligatures" : [ + "../typings/addon-ligatures.d.ts" + ] + } }, "include": [ "./**/*", diff --git a/addons/xterm-addon-ligatures/tsconfig.json b/addons/addon-ligatures/tsconfig.json similarity index 100% rename from addons/xterm-addon-ligatures/tsconfig.json rename to addons/addon-ligatures/tsconfig.json diff --git a/addons/xterm-addon-ligatures/typings/xterm-addon-ligatures.d.ts b/addons/addon-ligatures/typings/addon-ligatures.d.ts similarity index 89% rename from addons/xterm-addon-ligatures/typings/xterm-addon-ligatures.d.ts rename to addons/addon-ligatures/typings/addon-ligatures.d.ts index 4d8fbdf0..2fd4f3e7 100644 --- a/addons/xterm-addon-ligatures/typings/xterm-addon-ligatures.d.ts +++ b/addons/addon-ligatures/typings/addon-ligatures.d.ts @@ -2,15 +2,15 @@ * Copyright (c) 2018 The xterm.js authors. All rights reserved. * @license MIT * - * This contains the type declarations for the xterm-addon-ligatures library. + * This contains the type declarations for the @xterm/addon-ligatures library. * Note that some interfaces may differ between this file and the actual * implementation in src/, that's because this file declares the *public* API * which is intended to be stable and consumed by external programs. */ -import { Terminal, ITerminalAddon } from 'xterm'; +import { Terminal, ITerminalAddon } from '@xterm/xterm'; -declare module 'xterm-addon-ligatures' { +declare module '@xterm/addon-ligatures' { /** * An xterm.js addon that enables web links. */ diff --git a/addons/xterm-addon-ligatures/webpack.config.js b/addons/addon-ligatures/webpack.config.js similarity index 95% rename from addons/xterm-addon-ligatures/webpack.config.js rename to addons/addon-ligatures/webpack.config.js index 5aeebe2e..6ec7f42d 100644 --- a/addons/xterm-addon-ligatures/webpack.config.js +++ b/addons/addon-ligatures/webpack.config.js @@ -6,7 +6,7 @@ const path = require('path'); const addonName = 'LigaturesAddon'; -const mainFile = 'xterm-addon-ligatures.js'; +const mainFile = 'addon-ligatures.js'; module.exports = { entry: `./out/${addonName}.js`, diff --git a/addons/xterm-addon-ligatures/yarn.lock b/addons/addon-ligatures/yarn.lock similarity index 100% rename from addons/xterm-addon-ligatures/yarn.lock rename to addons/addon-ligatures/yarn.lock diff --git a/addons/xterm-addon-search/.gitignore b/addons/addon-search/.gitignore similarity index 100% rename from addons/xterm-addon-search/.gitignore rename to addons/addon-search/.gitignore diff --git a/addons/xterm-addon-search/.npmignore b/addons/addon-search/.npmignore similarity index 100% rename from addons/xterm-addon-search/.npmignore rename to addons/addon-search/.npmignore diff --git a/addons/xterm-addon-search/LICENSE b/addons/addon-search/LICENSE similarity index 100% rename from addons/xterm-addon-search/LICENSE rename to addons/addon-search/LICENSE diff --git a/addons/xterm-addon-search/README.md b/addons/addon-search/README.md similarity index 62% rename from addons/xterm-addon-search/README.md rename to addons/addon-search/README.md index 91bcc34b..fab0b03f 100644 --- a/addons/xterm-addon-search/README.md +++ b/addons/addon-search/README.md @@ -1,18 +1,18 @@ -## xterm-addon-search +## @xterm/addon-search An addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables searching the buffer. This addon requires xterm.js v4+. ### Install ```bash -npm install --save xterm-addon-search +npm install --save @xterm/addon-search ``` ### Usage ```ts -import { Terminal } from 'xterm'; -import { SearchAddon } from 'xterm-addon-search'; +import { Terminal } from '@xterm/xterm'; +import { SearchAddon } from '@xterm/addon-search'; const terminal = new Terminal(); const searchAddon = new SearchAddon(); @@ -20,4 +20,4 @@ terminal.loadAddon(searchAddon); searchAddon.findNext('foo'); ``` -See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/xterm-addon-search/typings/xterm-addon-search.d.ts) for more advanced usage. +See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/addon-search/typings/addon-search.d.ts) for more advanced usage. diff --git a/addons/xterm-addon-search/fixtures/issue-2444 b/addons/addon-search/fixtures/issue-2444 similarity index 100% rename from addons/xterm-addon-search/fixtures/issue-2444 rename to addons/addon-search/fixtures/issue-2444 diff --git a/addons/xterm-addon-search/package.json b/addons/addon-search/package.json similarity index 54% rename from addons/xterm-addon-search/package.json rename to addons/addon-search/package.json index d2986140..369d11c3 100644 --- a/addons/xterm-addon-search/package.json +++ b/addons/addon-search/package.json @@ -1,13 +1,13 @@ { - "name": "xterm-addon-search", + "name": "@xterm/addon-search", "version": "0.13.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" }, - "main": "lib/xterm-addon-search.js", - "types": "typings/xterm-addon-search.d.ts", - "repository": "https://github.com/xtermjs/xterm.js", + "main": "lib/addon-search.js", + "types": "typings/addon-search.d.ts", + "repository": "https://github.com/xtermjs/xterm.js/tree/master/addons/addon-search", "license": "MIT", "keywords": [ "terminal", @@ -15,12 +15,11 @@ "xterm.js" ], "scripts": { - "build": "../../node_modules/.bin/tsc -p .", - "prepackage": "npm run build", + "prepackage": "../../node_modules/.bin/tsc -p .", "package": "../../node_modules/.bin/webpack", "prepublishOnly": "npm run package" }, "peerDependencies": { - "xterm": "^5.0.0" + "@xterm/xterm": "^5.0.0" } } diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/addon-search/src/SearchAddon.ts similarity index 96% rename from addons/xterm-addon-search/src/SearchAddon.ts rename to addons/addon-search/src/SearchAddon.ts index 10fc1ecc..3fae7373 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/addon-search/src/SearchAddon.ts @@ -3,7 +3,8 @@ * @license MIT */ -import { Terminal, IDisposable, ITerminalAddon, IDecoration } from 'xterm'; +import type { Terminal, IDisposable, ITerminalAddon, IDecoration } from '@xterm/xterm'; +import type { SearchAddon as ISearchApi } from '@xterm/addon-search'; import { EventEmitter } from 'common/EventEmitter'; import { Disposable, toDisposable, disposeArray, MutableDisposable } from 'common/Lifecycle'; @@ -61,7 +62,7 @@ const NON_WORD_CHARACTERS = ' ~!@#$%^&*()+`-=[]{}|\\;:"\',./<>?'; const LINES_CACHE_TIME_TO_LIVE = 15 * 1000; // 15 secs const DEFAULT_HIGHLIGHT_LIMIT = 1000; -export class SearchAddon extends Disposable implements ITerminalAddon { +export class SearchAddon extends Disposable implements ITerminalAddon , ISearchApi { private _terminal: Terminal | undefined; private _cachedSearchTerm: string | undefined; private _highlightedLines: Set = new Set(); @@ -119,6 +120,10 @@ export class SearchAddon extends Disposable implements ITerminalAddon { } } + public clearActiveDecoration(): void { + this._selectedDecoration.clear(); + } + /** * Find the next instance of the term, then scroll to and select it. If it * doesn't exist, do nothing. @@ -130,9 +135,10 @@ export class SearchAddon extends Disposable implements ITerminalAddon { if (!this._terminal) { throw new Error('Cannot use addon until it has been loaded'); } + const didOptionsChanged = this._lastSearchOptions ? this._didOptionsChange(this._lastSearchOptions, searchOptions) : true; this._lastSearchOptions = searchOptions; if (searchOptions?.decorations) { - if (this._cachedSearchTerm === undefined || term !== this._cachedSearchTerm) { + if (this._cachedSearchTerm === undefined || term !== this._cachedSearchTerm || didOptionsChanged) { this._highlightAllMatches(term, searchOptions); } } @@ -298,9 +304,10 @@ export class SearchAddon extends Disposable implements ITerminalAddon { if (!this._terminal) { throw new Error('Cannot use addon until it has been loaded'); } + const didOptionsChanged = this._lastSearchOptions ? this._didOptionsChange(this._lastSearchOptions, searchOptions) : true; this._lastSearchOptions = searchOptions; if (searchOptions?.decorations) { - if (this._cachedSearchTerm === undefined || term !== this._cachedSearchTerm) { + if (this._cachedSearchTerm === undefined || term !== this._cachedSearchTerm || didOptionsChanged) { this._highlightAllMatches(term, searchOptions); } } @@ -312,6 +319,22 @@ export class SearchAddon extends Disposable implements ITerminalAddon { return found; } + private _didOptionsChange(lastSearchOptions: ISearchOptions, searchOptions?: ISearchOptions): boolean { + if (!searchOptions) { + return false; + } + if (lastSearchOptions.caseSensitive !== searchOptions.caseSensitive) { + return true; + } + if (lastSearchOptions.regex !== searchOptions.regex) { + return true; + } + if (lastSearchOptions.wholeWord !== searchOptions.wholeWord) { + return true; + } + return false; + } + private _fireResults(searchOptions?: ISearchOptions): void { if (searchOptions?.decorations) { let resultIndex = -1; diff --git a/addons/xterm-addon-search/src/tsconfig.json b/addons/addon-search/src/tsconfig.json similarity index 86% rename from addons/xterm-addon-search/src/tsconfig.json rename to addons/addon-search/src/tsconfig.json index 1f08b12d..bd3e48ad 100644 --- a/addons/xterm-addon-search/src/tsconfig.json +++ b/addons/addon-search/src/tsconfig.json @@ -17,6 +17,9 @@ "paths": { "common/*": [ "../../../src/common/*" + ], + "@xterm/addon-search" : [ + "../typings/addon-search.d.ts" ] } }, diff --git a/addons/xterm-addon-search/test/SearchAddon.api.ts b/addons/addon-search/test/SearchAddon.api.ts similarity index 100% rename from addons/xterm-addon-search/test/SearchAddon.api.ts rename to addons/addon-search/test/SearchAddon.api.ts diff --git a/addons/xterm-addon-search/test/tsconfig.json b/addons/addon-search/test/tsconfig.json similarity index 100% rename from addons/xterm-addon-search/test/tsconfig.json rename to addons/addon-search/test/tsconfig.json diff --git a/addons/xterm-addon-search/tsconfig.json b/addons/addon-search/tsconfig.json similarity index 100% rename from addons/xterm-addon-search/tsconfig.json rename to addons/addon-search/tsconfig.json diff --git a/addons/xterm-addon-search/typings/xterm-addon-search.d.ts b/addons/addon-search/typings/addon-search.d.ts similarity index 97% rename from addons/xterm-addon-search/typings/xterm-addon-search.d.ts rename to addons/addon-search/typings/addon-search.d.ts index 3a3b7c89..282004a2 100644 --- a/addons/xterm-addon-search/typings/xterm-addon-search.d.ts +++ b/addons/addon-search/typings/addon-search.d.ts @@ -3,9 +3,9 @@ * @license MIT */ -import { Terminal, ITerminalAddon, IEvent } from 'xterm'; +import { Terminal, ITerminalAddon, IEvent } from '@xterm/xterm'; -declare module 'xterm-addon-search' { +declare module '@xterm/addon-search' { /** * Options for a search. */ diff --git a/addons/xterm-addon-search/webpack.config.js b/addons/addon-search/webpack.config.js similarity index 94% rename from addons/xterm-addon-search/webpack.config.js rename to addons/addon-search/webpack.config.js index 30526812..a770f93f 100644 --- a/addons/xterm-addon-search/webpack.config.js +++ b/addons/addon-search/webpack.config.js @@ -6,7 +6,7 @@ const path = require('path'); const addonName = 'SearchAddon'; -const mainFile = 'xterm-addon-search.js'; +const mainFile = 'addon-search.js'; module.exports = { entry: `./out/${addonName}.js`, diff --git a/addons/xterm-addon-serialize/.gitignore b/addons/addon-serialize/.gitignore similarity index 100% rename from addons/xterm-addon-serialize/.gitignore rename to addons/addon-serialize/.gitignore diff --git a/addons/xterm-addon-serialize/.npmignore b/addons/addon-serialize/.npmignore similarity index 100% rename from addons/xterm-addon-serialize/.npmignore rename to addons/addon-serialize/.npmignore diff --git a/addons/xterm-addon-serialize/README.md b/addons/addon-serialize/README.md similarity index 71% rename from addons/xterm-addon-serialize/README.md rename to addons/addon-serialize/README.md index 12e0ac87..862ebac3 100644 --- a/addons/xterm-addon-serialize/README.md +++ b/addons/addon-serialize/README.md @@ -1,4 +1,4 @@ -## xterm-addon-serialize +## @xterm/addon-serialize An addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables xterm.js to serialize a terminal framebuffer into string or html. This addon requires xterm.js v4+. @@ -7,14 +7,14 @@ An addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables xterm. ### Install ```bash -npm install --save xterm-addon-serialize +npm install --save @xterm/addon-serialize ``` ### Usage ```ts -import { Terminal } from "xterm"; -import { SerializeAddon } from "xterm-addon-serialize"; +import { Terminal } from "@xterm/xterm"; +import { SerializeAddon } from "@xterm/addon-serialize"; const terminal = new Terminal(); const serializeAddon = new SerializeAddon(); @@ -25,7 +25,7 @@ terminal.write("something...", () => { }); ``` -See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/xterm-addon-serialize/typings/xterm-addon-serialize.d.ts) for more advanced usage. +See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/addon-serialize/typings/addon-serialize.d.ts) for more advanced usage. ### Benchmark @@ -35,8 +35,8 @@ See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/xterm- $ git clone https://github.com/xtermjs/xterm.js.git $ cd xterm.js $ yarn -$ cd addons/xterm-addon-serialize +$ cd addons/addon-serialize $ yarn benchmark && yarn benchmark-baseline -$ # change some code in `xterm-addon-serialize` +$ # change some code in `@xterm/addon-serialize` $ yarn benchmark-eval ``` diff --git a/addons/xterm-addon-serialize/benchmark/SerializeAddon.benchmark.ts b/addons/addon-serialize/benchmark/SerializeAddon.benchmark.ts similarity index 95% rename from addons/xterm-addon-serialize/benchmark/SerializeAddon.benchmark.ts rename to addons/addon-serialize/benchmark/SerializeAddon.benchmark.ts index 87741b63..b60dba1d 100644 --- a/addons/xterm-addon-serialize/benchmark/SerializeAddon.benchmark.ts +++ b/addons/addon-serialize/benchmark/SerializeAddon.benchmark.ts @@ -31,11 +31,11 @@ perfContext('Terminal: sh -c "dd if=/dev/urandom count=40 bs=1k | hexdump | lolc }); const chunks: Buffer[] = []; let length = 0; - p.on('data', data => { + p.onData(data => { chunks.push(data as unknown as Buffer); length += data.length; }); - await new Promise(resolve => p.on('exit', () => resolve())); + await new Promise(resolve => p.onExit(() => resolve())); contentUtf8 = Buffer.concat(chunks, length); // translate to content string const buffer = new Uint32Array(contentUtf8.length); diff --git a/addons/xterm-addon-serialize/benchmark/benchmark.json b/addons/addon-serialize/benchmark/benchmark.json similarity index 100% rename from addons/xterm-addon-serialize/benchmark/benchmark.json rename to addons/addon-serialize/benchmark/benchmark.json diff --git a/addons/xterm-addon-serialize/benchmark/tsconfig.json b/addons/addon-serialize/benchmark/tsconfig.json similarity index 82% rename from addons/xterm-addon-serialize/benchmark/tsconfig.json rename to addons/addon-serialize/benchmark/tsconfig.json index 6bbb2a4b..1f6429f6 100644 --- a/addons/xterm-addon-serialize/benchmark/tsconfig.json +++ b/addons/addon-serialize/benchmark/tsconfig.json @@ -14,7 +14,10 @@ "paths": { "common/*": ["../../../src/common/*"], "browser/*": ["../../../src/browser/*"], - "SerializeAddon": ["../src/SerializeAddon"] + "SerializeAddon": ["../src/SerializeAddon"], + "@xterm/addon-serialize": [ + "../typings/addon-serialize.d.ts" + ] } }, "include": ["../**/*", "../../../typings/xterm.d.ts"], diff --git a/addons/xterm-addon-serialize/package.json b/addons/addon-serialize/package.json similarity index 80% rename from addons/xterm-addon-serialize/package.json rename to addons/addon-serialize/package.json index c8203229..763c52ca 100644 --- a/addons/xterm-addon-serialize/package.json +++ b/addons/addon-serialize/package.json @@ -1,13 +1,13 @@ { - "name": "xterm-addon-serialize", + "name": "@xterm/addon-serialize", "version": "0.11.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" }, - "main": "lib/xterm-addon-serialize.js", - "types": "typings/xterm-addon-serialize.d.ts", - "repository": "https://github.com/xtermjs/xterm.js", + "main": "lib/addon-serialize.js", + "types": "typings/addon-serialize.d.ts", + "repository": "https://github.com/xtermjs/xterm.js/tree/master/addons/addon-serialize", "license": "MIT", "keywords": [ "terminal", @@ -24,6 +24,6 @@ "benchmark-eval": "NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json --eval out-benchmark/benchmark/*benchmark.js" }, "peerDependencies": { - "xterm": "^5.0.0" + "@xterm/xterm": "^5.0.0" } } diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.test.ts b/addons/addon-serialize/src/SerializeAddon.test.ts similarity index 99% rename from addons/xterm-addon-serialize/src/SerializeAddon.test.ts rename to addons/addon-serialize/src/SerializeAddon.test.ts index 75148ab9..d41cfa12 100644 --- a/addons/xterm-addon-serialize/src/SerializeAddon.test.ts +++ b/addons/addon-serialize/src/SerializeAddon.test.ts @@ -44,7 +44,7 @@ class TestSelectionService { } } -describe('xterm-addon-serialize', () => { +describe('SerializeAddon', () => { let dom: jsdom.JSDOM; let window: jsdom.DOMWindow; diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/addon-serialize/src/SerializeAddon.ts similarity index 98% rename from addons/xterm-addon-serialize/src/SerializeAddon.ts rename to addons/addon-serialize/src/SerializeAddon.ts index 7a2db089..961d8546 100644 --- a/addons/xterm-addon-serialize/src/SerializeAddon.ts +++ b/addons/addon-serialize/src/SerializeAddon.ts @@ -5,9 +5,10 @@ * (EXPERIMENTAL) This Addon is still under development */ +import type { IBuffer, IBufferCell, IBufferRange, ITerminalAddon, Terminal } from '@xterm/xterm'; +import type { SerializeAddon as ISerializeApi } from '@xterm/addon-serialize'; import { DEFAULT_ANSI_COLORS } from 'browser/services/ThemeService'; import { IAttributeData, IColor } from 'common/Types'; -import { IBuffer, IBufferCell, IBufferRange, ITerminalAddon, Terminal } from 'xterm'; function constrain(value: number, low: number, high: number): number { return Math.max(low, Math.min(value, high)); @@ -411,7 +412,7 @@ class StringSerializeHandler extends BaseSerializeHandler { } } -export class SerializeAddon implements ITerminalAddon { +export class SerializeAddon implements ITerminalAddon , ISerializeApi { private _terminal: Terminal | undefined; public activate(terminal: Terminal): void { @@ -604,7 +605,7 @@ export class HTMLSerializeHandler extends BaseSerializeHandler { (color >> 8) & 255, (color ) & 255 ]; - return rgb.map(x => this._padStart(x.toString(16), 2, '0')).join(''); + return '#' + rgb.map(x => this._padStart(x.toString(16), 2, '0')).join(''); } if (isFg ? cell.isFgPalette() : cell.isBgPalette()) { return this._ansiColors[color].css; diff --git a/addons/xterm-addon-serialize/src/tsconfig.json b/addons/addon-serialize/src/tsconfig.json similarity index 88% rename from addons/xterm-addon-serialize/src/tsconfig.json rename to addons/addon-serialize/src/tsconfig.json index 22ffe53e..6fb641ef 100644 --- a/addons/xterm-addon-serialize/src/tsconfig.json +++ b/addons/addon-serialize/src/tsconfig.json @@ -17,6 +17,9 @@ ], "browser/*": [ "../../../src/browser/*" + ], + "@xterm/addon-serialize": [ + "../typings/addon-serialize.d.ts" ] }, "strict": true, diff --git a/addons/xterm-addon-serialize/test/SerializeAddon.api.ts b/addons/addon-serialize/test/SerializeAddon.api.ts similarity index 100% rename from addons/xterm-addon-serialize/test/SerializeAddon.api.ts rename to addons/addon-serialize/test/SerializeAddon.api.ts diff --git a/addons/xterm-addon-serialize/test/tsconfig.json b/addons/addon-serialize/test/tsconfig.json similarity index 100% rename from addons/xterm-addon-serialize/test/tsconfig.json rename to addons/addon-serialize/test/tsconfig.json diff --git a/addons/xterm-addon-serialize/tsconfig.json b/addons/addon-serialize/tsconfig.json similarity index 100% rename from addons/xterm-addon-serialize/tsconfig.json rename to addons/addon-serialize/tsconfig.json diff --git a/addons/xterm-addon-serialize/typings/xterm-addon-serialize.d.ts b/addons/addon-serialize/typings/addon-serialize.d.ts similarity index 96% rename from addons/xterm-addon-serialize/typings/xterm-addon-serialize.d.ts rename to addons/addon-serialize/typings/addon-serialize.d.ts index 4cb6283d..0b127b50 100644 --- a/addons/xterm-addon-serialize/typings/xterm-addon-serialize.d.ts +++ b/addons/addon-serialize/typings/addon-serialize.d.ts @@ -3,9 +3,9 @@ * @license MIT */ -import { Terminal, ITerminalAddon } from 'xterm'; +import { Terminal, ITerminalAddon } from '@xterm/xterm'; -declare module 'xterm-addon-serialize' { +declare module '@xterm/addon-serialize' { /** * An xterm.js addon that enables serialization of terminal contents. */ diff --git a/addons/xterm-addon-serialize/webpack.config.js b/addons/addon-serialize/webpack.config.js similarity index 94% rename from addons/xterm-addon-serialize/webpack.config.js rename to addons/addon-serialize/webpack.config.js index fee297d8..bd08ca37 100644 --- a/addons/xterm-addon-serialize/webpack.config.js +++ b/addons/addon-serialize/webpack.config.js @@ -6,7 +6,7 @@ const path = require('path'); const addonName = 'SerializeAddon'; -const mainFile = 'xterm-addon-serialize.js'; +const mainFile = 'addon-serialize.js'; module.exports = { entry: `./out/${addonName}.js`, diff --git a/addons/addon-unicode-graphemes/.gitignore b/addons/addon-unicode-graphemes/.gitignore new file mode 100644 index 00000000..03c051b3 --- /dev/null +++ b/addons/addon-unicode-graphemes/.gitignore @@ -0,0 +1,3 @@ +lib +node_modules +out-benchmark diff --git a/addons/xterm-addon-unicode11/.npmignore b/addons/addon-unicode-graphemes/.npmignore similarity index 100% rename from addons/xterm-addon-unicode11/.npmignore rename to addons/addon-unicode-graphemes/.npmignore diff --git a/addons/addon-unicode-graphemes/LICENSE b/addons/addon-unicode-graphemes/LICENSE new file mode 100644 index 00000000..b6c38b15 --- /dev/null +++ b/addons/addon-unicode-graphemes/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2023, The xterm.js authors (https://github.com/xtermjs/xterm.js) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/addons/addon-unicode-graphemes/README.md b/addons/addon-unicode-graphemes/README.md new file mode 100644 index 00000000..6e717c26 --- /dev/null +++ b/addons/addon-unicode-graphemes/README.md @@ -0,0 +1,22 @@ +## @xterm/addon-unicode-graphemes + +⚠️ **This addon is currently experimental and may introduce unexpected and non-standard behavior** + +An addon providing enhanced Unicode support (include grapheme clustering) for xterm.js. + +The file `src/UnicodeProperties.ts` is generated and depends on the Unicode version. See [the unicode-properties project](https://github.com/PerBothner/unicode-properties) for credits and re-generation instructions. + +### Install + +This addon is not yet published to npm + +### Usage + +```ts +import { Terminal } from '@xterm/xterm'; +import { UnicodeGraphemesAddon } from '@xterm/addon-unicode-graphemes'; + +const terminal = new Terminal(); +const unicodeGraphemesAddon = new UnicodeGraphemesAddon(); +terminal.loadAddon(unicodeGraphemesAddon); +``` diff --git a/addons/addon-unicode-graphemes/benchmark/UnicodeGraphemeAddon.benchmark.ts b/addons/addon-unicode-graphemes/benchmark/UnicodeGraphemeAddon.benchmark.ts new file mode 100644 index 00000000..f1acf8b3 --- /dev/null +++ b/addons/addon-unicode-graphemes/benchmark/UnicodeGraphemeAddon.benchmark.ts @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { perfContext, before, ThroughputRuntimeCase } from 'xterm-benchmark'; + +import { spawn } from 'node-pty'; +import { Utf8ToUtf32, stringFromCodePoint } from 'common/input/TextDecoder'; +import { Terminal } from 'browser/Terminal'; +import { UnicodeGraphemeProvider } from 'UnicodeGraphemeProvider'; + + +function fakedAddonLoad(terminal: any): void { + // resembles what UnicodeGraphemesAddon.activate does under the hood + terminal.unicodeService.register(new UnicodeGraphemeProvider()); + terminal.unicodeService.activeVersion = '15-graphemes'; +} + + +perfContext('Terminal: ls -lR /usr/lib', () => { + let content = ''; + let contentUtf8: Uint8Array; + + before(async () => { + // grab output from "ls -lR /usr" + const p = spawn('ls', ['--color=auto', '-lR', '/usr/lib'], { + name: 'xterm-256color', + cols: 80, + rows: 25, + cwd: process.env.HOME, + env: process.env, + encoding: (null as unknown as string) // needs to be fixed in node-pty + }); + const chunks: Buffer[] = []; + let length = 0; + p.onData(data => { + chunks.push(data as unknown as Buffer); + length += data.length; + }); + await new Promise(resolve => p.onExit(() => resolve())); + contentUtf8 = Buffer.concat(chunks, length); + // translate to content string + const buffer = new Uint32Array(contentUtf8.length); + const decoder = new Utf8ToUtf32(); + const codepoints = decoder.decode(contentUtf8, buffer); + for (let i = 0; i < codepoints; ++i) { + content += stringFromCodePoint(buffer[i]); + // peek into content to force flat repr in v8 + if (!(i % 10000000)) { + content[i]; + } + } + }); + + perfContext('write/string/async', () => { + let terminal: Terminal; + before(() => { + terminal = new Terminal({ cols: 80, rows: 25, scrollback: 1000 }); + fakedAddonLoad(terminal); + }); + new ThroughputRuntimeCase('', async () => { + await new Promise(res => terminal.write(content, res)); + return { payloadSize: contentUtf8.length }; + }, { fork: false }).showAverageThroughput(); + }); + + perfContext('write/Utf8/async', () => { + let terminal: Terminal; + before(() => { + terminal = new Terminal({ cols: 80, rows: 25, scrollback: 1000 }); + }); + new ThroughputRuntimeCase('', async () => { + await new Promise(res => terminal.write(content, res)); + return { payloadSize: contentUtf8.length }; + }, { fork: false }).showAverageThroughput(); + }); +}); diff --git a/addons/addon-unicode-graphemes/benchmark/benchmark.json b/addons/addon-unicode-graphemes/benchmark/benchmark.json new file mode 100644 index 00000000..1333eac8 --- /dev/null +++ b/addons/addon-unicode-graphemes/benchmark/benchmark.json @@ -0,0 +1,19 @@ +{ + "APP_PATH": ".benchmark", + "evalConfig": { + "tolerance": { + "*": [0.75, 1.5], + "*.dev": [0.01, 1.5], + "*.cv": [0.01, 1.5], + "EscapeSequenceParser.benchmark.js.*.averageThroughput.mean": [0.9, 5] + }, + "skip": [ + "*.median", + "*.runs", + "*.dev", + "*.cv", + "EscapeSequenceParser.benchmark.js.*.averageRuntime", + "Terminal.benchmark.js.*.averageRuntime" + ] + } +} diff --git a/addons/addon-unicode-graphemes/benchmark/tsconfig.json b/addons/addon-unicode-graphemes/benchmark/tsconfig.json new file mode 100644 index 00000000..c6f37a6c --- /dev/null +++ b/addons/addon-unicode-graphemes/benchmark/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "lib": ["dom", "es6"], + "outDir": "../out-benchmark", + "types": ["../../../node_modules/@types/node"], + "moduleResolution": "node", + "strict": false, + "target": "es2015", + "module": "commonjs", + "baseUrl": ".", + "paths": { + "common/*": ["../../../src/common/*"], + "browser/*": ["../../../src/browser/*"], + "UnicodeGraphemeProvider": ["../src/UnicodeGraphemeProvider"], + "@xterm/addon-unicode-graphemes": [ + "../typings/addon-unicode-graphemes.d.ts" + ] + } + }, + "include": ["../**/*", "../../../typings/xterm.d.ts"], + "exclude": ["../../../**/*test.ts", "../../**/*api.ts"], + "references": [ + { "path": "../../../src/common" }, + { "path": "../../../src/browser" } + ] +} diff --git a/addons/addon-unicode-graphemes/package.json b/addons/addon-unicode-graphemes/package.json new file mode 100644 index 00000000..d49eda87 --- /dev/null +++ b/addons/addon-unicode-graphemes/package.json @@ -0,0 +1,29 @@ +{ + "name": "@xterm/addon-unicode-graphemes", + "version": "0.1.0", + "author": { + "name": "The xterm.js authors", + "url": "https://xtermjs.org/" + }, + "main": "lib/addon-unicode-graphemes.js", + "types": "typings/addon-unicode-graphemes.d.ts", + "repository": "https://github.com/xtermjs/xterm.js/tree/master/addons/addon-unicode-graphemes", + "license": "MIT", + "keywords": [ + "terminal", + "xterm", + "xterm.js" + ], + "scripts": { + "build": "../../node_modules/.bin/tsc -p .", + "prepackage": "npm run build", + "package": "../../node_modules/.bin/webpack", + "prepublishOnly": "npm run package", + "benchmark": "NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json out-benchmark/benchmark/*benchmark.js", + "benchmark-baseline": "NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json --baseline out-benchmark/benchmark/*benchmark.js", + "benchmark-eval": "NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json --eval out-benchmark/benchmark/*benchmark.js" + }, + "peerDependencies": { + "@xterm/xterm": "^5.0.0" + } +} diff --git a/addons/addon-unicode-graphemes/src/UnicodeGraphemeProvider.ts b/addons/addon-unicode-graphemes/src/UnicodeGraphemeProvider.ts new file mode 100644 index 00000000..3d5717a0 --- /dev/null +++ b/addons/addon-unicode-graphemes/src/UnicodeGraphemeProvider.ts @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2023 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IUnicodeVersionProvider } from '@xterm/xterm'; +import { UnicodeCharProperties, UnicodeCharWidth } from 'common/services/Services'; +import { UnicodeService } from 'common/services/UnicodeService'; +import * as UC from './third-party/UnicodeProperties'; + +export class UnicodeGraphemeProvider implements IUnicodeVersionProvider { + public readonly version; + public ambiguousCharsAreWide: boolean = false; + public readonly handleGraphemes: boolean; + + constructor(handleGraphemes: boolean = true) { + this.version = handleGraphemes ? '15-graphemes' : '15'; + this.handleGraphemes = handleGraphemes; + } + + private static readonly _plainNarrowProperties: UnicodeCharProperties + = UnicodeService.createPropertyValue(UC.GRAPHEME_BREAK_Other, 1, false); + + public charProperties(codepoint: number, preceding: UnicodeCharProperties): UnicodeCharProperties { + // Optimize the simple ASCII case, under the condition that + // UnicodeService.extractCharKind(preceding) === GRAPHEME_BREAK_Other + // (which also covers the case that preceding === 0). + if ((codepoint >= 32 && codepoint < 127) && (preceding >> 3) === 0) { + return UnicodeGraphemeProvider._plainNarrowProperties; + } + + let charInfo = UC.getInfo(codepoint); + let w = UC.infoToWidthInfo(charInfo); + let shouldJoin = false; + if (w >= 2) { + // Treat emoji_presentation_selector as WIDE. + w = w === 3 || this.ambiguousCharsAreWide || codepoint === 0xfe0f ? 2 : 1; + } else { + w = 1; + } + if (preceding !== 0) { + const oldWidth = UnicodeService.extractWidth(preceding); + if (this.handleGraphemes) { + charInfo = UC.shouldJoin(UnicodeService.extractCharKind(preceding), charInfo); + } else { + charInfo = w === 0 ? 1 : 0; + } + shouldJoin = charInfo > 0; + if (shouldJoin) { + if (oldWidth > w) { + w = oldWidth; + } else if (charInfo === 32) { // UC.GRAPHEME_BREAK_SAW_Regional_Pair) + w = 2; + } + } + } + return UnicodeService.createPropertyValue(charInfo, w, shouldJoin); + } + + public wcwidth(codepoint: number): UnicodeCharWidth { + const charInfo = UC.getInfo(codepoint); + const w = UC.infoToWidthInfo(charInfo); + const kind = (charInfo & UC.GRAPHEME_BREAK_MASK) >> UC.GRAPHEME_BREAK_SHIFT; + if (kind === UC.GRAPHEME_BREAK_Extend || kind === UC.GRAPHEME_BREAK_Prepend) { + return 0; + } + if (w >= 2 && (w === 3 || this.ambiguousCharsAreWide)) { + return 2; + } + return 1; + } +} diff --git a/addons/addon-unicode-graphemes/src/UnicodeGraphemesAddon.ts b/addons/addon-unicode-graphemes/src/UnicodeGraphemesAddon.ts new file mode 100644 index 00000000..6e4d2266 --- /dev/null +++ b/addons/addon-unicode-graphemes/src/UnicodeGraphemesAddon.ts @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2023 The xterm.js authors. All rights reserved. + * @license MIT + * + * UnicodeVersionProvider for V15 with grapeme cluster handleing. + */ + +import type { Terminal, ITerminalAddon, IUnicodeHandling } from '@xterm/xterm'; +import type { UnicodeGraphemesAddon as IUnicodeGraphemesApi } from '@xterm/addon-unicode-graphemes'; +import { UnicodeGraphemeProvider } from './UnicodeGraphemeProvider'; + +export class UnicodeGraphemesAddon implements ITerminalAddon , IUnicodeGraphemesApi { + private _provider15Graphemes?: UnicodeGraphemeProvider; + private _provider15?: UnicodeGraphemeProvider; + private _unicode?: IUnicodeHandling; + private _oldVersion: string = ''; + + public activate(terminal: Terminal): void { + if (! this._provider15) { + this._provider15 = new UnicodeGraphemeProvider(false); + } + if (! this._provider15Graphemes) { + this._provider15Graphemes = new UnicodeGraphemeProvider(true); + } + const unicode = terminal.unicode; + this._unicode = unicode; + unicode.register(this._provider15); + unicode.register(this._provider15Graphemes); + this._oldVersion = unicode.activeVersion; + unicode.activeVersion = '15-graphemes'; + } + + public dispose(): void { + if (this._unicode) { + this._unicode.activeVersion = this._oldVersion; + } + } +} diff --git a/addons/addon-unicode-graphemes/src/third-party/UnicodeProperties.ts b/addons/addon-unicode-graphemes/src/third-party/UnicodeProperties.ts new file mode 100644 index 00000000..0ee147f8 --- /dev/null +++ b/addons/addon-unicode-graphemes/src/third-party/UnicodeProperties.ts @@ -0,0 +1,147 @@ +import UnicodeTrie from './unicode-trie'; +const trieRaw = "AAARAAAAAABwxwAAAb4LQfTtmw+sVmUdx58LL/ffe/kjzNBV80gW1F3yR+6CvbJiypoZa0paWmAWSluErSBbFtYkkuZykq6QamGJ4WRqo2kFGy6dYWtEq6G1MFAJbRbOVTQr+x7f5+x97q/n/3me87wXzm/3s+f/7/d7/p7znnvOlvGMbQM7wIPgEbAPHABPgcPgefAS+BfYwuv/F/Q2OulBxKcK6TMRPxu8FcwFbwcjYCFYDC4Cl4ArwNXgGvBJsA58UdBDwy+jbBO4La8DtoEd4H7wkNBuN+KPgn3gADgIngaHwFHwF/AyeAWMm4C+TGi3LdiJ/EnIex04A2RgFpgD5oKFYDG4CLwHXAo+IKSvAqt4/evA9bz9jWA6+Cq3dyvCP8HWNwX93wF38/ROcD94SCjP2+1B+BiPP4HwgOD/7xD/I08fRniMx48jPAFeBeuF+n29jE0G08FZvaPHYWZvh9mcEfAOjlhXx/qGfd2QvLO3zccmtMnzliC9lPt+GenD1nyMiK/LNf1cycs+gfAzPJ6vtxe4jhuQtx5sBLeA28G3eb3v8/Beif4HkPewxu5G6N/rMP4qfgEdvwZPgj+AZ8Cx3nYfxiE8Dk6AV0FfH/YEOB28AbwJDIPzQAtcAC4Gl/Z19F+J+NVCehWPr0b46b7RvixvdPg8yr7U10l/BfFN4La8DdgGdoAHwU/AI2AfOACeAofB8+AlcAKwfvyBKeCM/o7NrF9PXmdWv9/Ynot2I7ztIg8dF5I2a8i63CjZU+9Fm2Wcy4U4ZQVYyeOrwVoev57UuxHcJKRvFuJXgnU8/nUebtbYrKmpCUOx31P7UVNTU1NTU1NTU1OGLTz8Xr/77+W7+9vP0or0MxPMbXaizY8FW3sQ3wseB/t5/kGEh8DR/vbzwL8i/Af4Dy8fP8BYE0weaKenI/wV/DhrQG97JspngzlgLpgHzgPzwUhdVpfVZXVZXRa87HxwAVgQ4Pn5WEd85l5TUzOasvezFw/E3b/LoP9D4CpwrcTWWsGXNQOj748/G9k3G56d1KYxmbELwQbwKFiJvBM8nDWlHa5E+AOwCzwLzjkNeeB28NvTeB1OYyr0gQ1g99R23nGE50xj7MPgc+A+8K5Bxj4FHgB/G2z/T9XEzCZjd/S0WYX4Pc3/r/Nn5I0f6qQXIP5x8ENwBMyYyNhHJ3b0pOCuLrBvM941NTU1JyNHEp+BrC8dMyalt1/m3uWfhmeULzRGp9d3wf0WZSN8+prCr60Wz09tuNmx35sl9Y825HXvRN39KNveaL8flb9f913kbec67kHeTsR3gYcH2uV7ED4m2HhCYi/X9ZuBzvuXv0f8iKIfx5B/XCg7gTgbVPdvAsomCuWnD45eK28UyvL3Jt+s0fU2TVnOXJQvJHUWIb0ELAWXgCt4+UcMumSsEtpch/g6ouMGpG/ieZsc9N/q4YsLd3D9WyPbsWEbfNgO7hN82TWY/n8xKbmsC3xQsYKf+7sjrx2TH+u4H3vhx+OO6+X9hmtXN7C/4r15EPaeBs9J7L7YBeeED/k7wn8fbIf/Rji+yVizmd4vW6bB19cb/PU9w7MxMA60bzPHgM8+zG623+OnzOf55yNc3Gw/k303wveBy3nZcoTXgNVgLfiCRNcG5N3SbIebwZ08fhe4l8d/BH7K4yI/4+HPwS/BAfBks+PzIaHuc3x+ivSL4GUyZ68I6fwZYRNMG2qnz+Th2QjfMtTx/1zE5w61nyN+Q7C3aKgdin1dgrylYBn4INdhGn/Z2FfFiqH01/SUXMvnPD+jC+j85N/RqRhR/DYaS6T+P09K1mD+vzW+5zVqqeVUl0wTz2lK8odJHRGXfBufdGLSoSo3+ZFJ6sl0qvJVNmhI4z4i06mrZ6uT1le1z5h5HE3tMiHPtQ5javu+ItMXUr/MXpmwmyRL3D6U7UwIMyYfczGu0qdqb2pbhcw4xQkhWQBMerrZ/liXrGTbsQwTwrEu4zSczKLrd7fCSKiKn+zSo8BWXMe8myXWOivrUxWi60OPoQ7VIasbQ0S/Ukk3rZVullNhHEL1rYoxUF0PTfm6elWJzq54ZsU4z11ohOy0oxT2izFqCNj4TesXcWZo6+Jfqr1O+1O1beqDagypj2J9F1u2daucj3Eknmq/6PaHrK7Mb1o35DiW1a/a76LuhlDXZX25SOz11S33ErKxDb2/fc/bFKI6axskn+4/W90u9mOtbRf7smsoTdvOfwoRz0t6DaP9k81v6P7Re5aUQudTd303rX+bZzBl97/KR7E+Xbux9lLI+aNr1PfaYLpPDiW2/vrYTX1drMIeXbMye6HXlw8292Jl7ZXxLxRlxXbcaH9drjFlxfa3Qozx8NWRi834lPVZbD+SmN7EJPzc9TVCSVXXDps9L+513b2J7fMu176V2YOhx1A3JrJ8KrLxUumpcu5j/lYT+2tzLRVDZmhjO442a1Clu0ox9VPVXzE/lcS4V0k1D6LI1pJsz8fct9SGbO5l/rmKzTlvsxdj3IvRtC2uv0t1fotltvd2VaCy5Sp5m0EhnZG4CCNxXZrWp/VUIrOjapfnNw11ZNI0V/GWzKNuxtzGKKTEtJeR0NVmpojbtBuW5On0u0is9ZMxvU8ZM+8vEyadtu10oqtP9Q4rcJEm85+Two/QkpGwjI6YkgkhtUfzZOW6fFVexuRri+qj9TJJHZkdmW5abiu0rs6uj2TMfmx06bISUj9tZ9Lja8dVQtox6WpxTJKfW3M4MSTmvU4sWy1CU6BF4jIfdNeDjHWuO1lCWIm2Jr2ixNZvklD2fP0Q6+vsmO4hqN1hJvfDtV5G8mTlsvau4qPP1a64L1skT6QYEzEtq0PzGZOfCbSdSmcKTP7Qs86Ej/1hEpelaV6IMdT5ayu2+nT9tmnnO746XbLxE8t0qOrYtJWhmk9bvaLfsrotRVw1PnR+bcafSUKZ6Mps7smobybJLH2R6WqRkJa1DHV0UmbfUcksiSF0HExSpp+uY0zbTklMaCm7blzEtg8h1rNMXNaYi05ZXsbC75sQ/4+aUxFV2jL50Q3jE0rK2rVtN09By8OHoo1vH2LPSdE323mr2sdu0pUZiDkWLRKWnfeQY6taKzHF9n/GPv8jd/0/egiRvYMR24fU79iY3s9Qva9RlYR8n8HHtq9fMcT1HRWfdZXiHd9YInt/iI4PTaf+BimXKvdXYU+3hlRpHzs2dVK/cxhDn+xs0I2jzxjL5kpXz1VU72aLtkK/97sALKyQqu25SshvG6h08/cLrlKswRklKXvvXfa+pZt+y8nah5YUv2Oo/ap/X2URdRfico9K69hcp6r6XaCz5Wo/hs/iNTGF6N6tV92/9ZS0Wba9SlT3pKF/e6W674+x9ly+VRL73cPU8ygb31D3eSqfVd+iqET0y3YMYojoO11XqrTt2nPxmeq1HYeqxkmUMt8DiesjpoTSr+qDrD+qPZDiOZxMdH0pRPX8MFUfQtv0Xbs+a1a1NnRryNZ/2+tsaPG5ZoX0RXZei88yZGdo4UMPj/cwv/kMJboxLISuQbE+1VW12Mx7FWOrW3M9Hv7Y+uxyraPSo8B2TGPuLdOeZha+hBKf8Sjsm/oR+7pmsx/oeOraFWdXleeV6oyl41zm+mgSuq9C6ox1TsU8D+m4dwMmf8v2nz7Tm+fYfj7HV1K/x1HWjquvY+2dllxM64ue87Su772zzbXIVC+WxLZTRR9MdkMTypZNH1z6G0tUvoccwxA+hfLNdV+a7MaQqscztMi+7QnxDZXvd1dldWQOyMbApb1Jd2h91Ffx+y9Xfb7tClokboOvrRhrbVpFFO8z+65t2/u4su9MUx028znH01/TGVDmHAj13W1o+1USw+eUfYtpO+b82rRNsb6oPpV+1fdBqddB6n3WDXvdJDZrJ0QfQp6bsc/kqq4BIddHWXGdN1pmWveh58F1zYUW1zmOITHXWOg1XrZvZSWUf77tq1ofqear6muaT1lIQp3bofabSafJVlnfYo9B6LGr8uzz2Xchvzfw+T9PlgiV/A8="; + +declare const Buffer: any; +function _dec(s: string): Uint8Array { + if (typeof Buffer !== 'undefined') return Buffer.from(s, 'base64'); + const bs = atob(s); + const r = new Uint8Array(bs.length); + for (let i = 0; i < r.length; ++i) r[i] = bs.charCodeAt(i); + return r; +} + +const trieData = new UnicodeTrie(_dec(trieRaw)); +export const GRAPHEME_BREAK_MASK = 0xF; +export const GRAPHEME_BREAK_SHIFT = 0; +export const CHARWIDTH_MASK = 0x30; +export const CHARWIDTH_SHIFT = 4; + +// Values for the GRAPHEME_BREAK property +export const GRAPHEME_BREAK_Other = 0; // includes CR, LF, Control +export const GRAPHEME_BREAK_Prepend = 1; +export const GRAPHEME_BREAK_Extend = 2; +export const GRAPHEME_BREAK_Regional_Indicator = 3; +export const GRAPHEME_BREAK_SpacingMark = 4; +export const GRAPHEME_BREAK_Hangul_L = 5; +export const GRAPHEME_BREAK_Hangul_V = 6; +export const GRAPHEME_BREAK_Hangul_T = 7; +export const GRAPHEME_BREAK_Hangul_LV = 8; +export const GRAPHEME_BREAK_Hangul_LVT = 9; +export const GRAPHEME_BREAK_ZWJ = 10; +export const GRAPHEME_BREAK_ExtPic = 11; + +// Only used as return value from shouldJoin/shouldJoinBackwards. +// (Must be positive; distinct from other values; +// and become GRAPHEME_BREAK_Other when masked with GRAPHEME_BREAK_MASK.) +const GRAPHEME_BREAK_SAW_Regional_Pair = 32; + +export const CHARWIDTH_NORMAL = 0; +export const CHARWIDTH_FORCE_1COLUMN = 1; +export const CHARWIDTH_EA_AMBIGUOUS = 2; +export const CHARWIDTH_WIDE = 3; + +// In the following 'info' is an encoded value from trie.get(codePoint) + +// In the following 'info' is an encoded value from trie.get(codePoint) + +export function infoToWidthInfo(info: number): number { + return (info & CHARWIDTH_MASK) >> CHARWIDTH_SHIFT; +} + +export function infoToWidth(info: number, ambiguousIsWide = false): 0 | 1 |2 { + const v = infoToWidthInfo(info); + return v < CHARWIDTH_EA_AMBIGUOUS ? 1 + : v >= CHARWIDTH_WIDE || ambiguousIsWide ? 2 : 1; +} + +export function strWidth(str: string, preferWide: boolean): number { + let width = 0; + for (let i = 0; i < str.length;) { + const codePoint = str.codePointAt(i) as number; + width += infoToWidth(getInfo(codePoint), preferWide); + i += (codePoint <= 0xffff) ? 1 : 2; + } + return width; +} + +export function columnToIndexInContext(str: string, startIndex: number, column: number, preferWide: boolean): number { + let rv = 0; + for (let i = startIndex; ;) { + if (i >= str.length) + return i; + const codePoint = str.codePointAt(i) as number; + const w = infoToWidth(getInfo(codePoint), preferWide); + rv += w; + if (rv > column) + return i; + i += (codePoint <= 0xffff) ? 1 : 2; + } +} + + +// Test if should break between beforeState and afterCode. +// Return <= 0 if should break; > 0 if should join. +// 'beforeState' is the return value from the previous possible break; +// the value 0 is start of string. +// 'afterCode' is the GRAPHEME_BREAK_Xxx value for the following codepoint. +export function shouldJoin(beforeState: number, afterInfo: number): number { + let beforeCode = (beforeState & GRAPHEME_BREAK_MASK) >> GRAPHEME_BREAK_SHIFT; + let afterCode = (afterInfo & GRAPHEME_BREAK_MASK) >> GRAPHEME_BREAK_SHIFT; + if (_shouldJoin(beforeCode, afterCode)) { + if (afterCode === GRAPHEME_BREAK_Regional_Indicator) + return GRAPHEME_BREAK_SAW_Regional_Pair; + else + return afterCode + 16; + } else + return afterCode - 16; +} + +export function shouldJoinBackwards(beforeInfo: number, afterState: number): number { + let afterCode = (afterState & GRAPHEME_BREAK_MASK) >> GRAPHEME_BREAK_SHIFT; + let beforeCode = (beforeInfo & GRAPHEME_BREAK_MASK) >> GRAPHEME_BREAK_SHIFT; + if (_shouldJoin(beforeCode, afterCode)) { + if (beforeCode === GRAPHEME_BREAK_Regional_Indicator) + return GRAPHEME_BREAK_SAW_Regional_Pair; + else + return beforeCode + 16; + } else + return beforeCode - 16; +} + +/** Doesn't handle an odd number of RI characters. */ +function _shouldJoin(beforeCode: number, afterCode: number): boolean { + if (beforeCode >= GRAPHEME_BREAK_Hangul_L + && beforeCode <= GRAPHEME_BREAK_Hangul_LVT) { + if (beforeCode == GRAPHEME_BREAK_Hangul_L // GB6 + && (afterCode == GRAPHEME_BREAK_Hangul_L + || afterCode == GRAPHEME_BREAK_Hangul_V + || afterCode == GRAPHEME_BREAK_Hangul_LV + || afterCode == GRAPHEME_BREAK_Hangul_LVT)) + return true; + if ((beforeCode == GRAPHEME_BREAK_Hangul_LV // GB7 + || beforeCode == GRAPHEME_BREAK_Hangul_V) + && (afterCode == GRAPHEME_BREAK_Hangul_V + || afterCode == GRAPHEME_BREAK_Hangul_T)) + return true; + if ((beforeCode == GRAPHEME_BREAK_Hangul_LVT // GB8 + || beforeCode == GRAPHEME_BREAK_Hangul_T) + && afterCode == GRAPHEME_BREAK_Hangul_T) + return true; + } + if (afterCode == GRAPHEME_BREAK_Extend // GB9 + || afterCode == GRAPHEME_BREAK_ZWJ + || beforeCode == GRAPHEME_BREAK_Prepend // GB9a + || afterCode == GRAPHEME_BREAK_SpacingMark) // GB9b + return true; + if (beforeCode == GRAPHEME_BREAK_ZWJ // GB11 + && afterCode == GRAPHEME_BREAK_ExtPic) + return true; + if (afterCode == GRAPHEME_BREAK_Regional_Indicator // GB12, GB13 + && beforeCode == GRAPHEME_BREAK_Regional_Indicator) + return true; + return false; +} + +export function getInfo(codePoint: number): number { + return trieData.get(codePoint); +} diff --git a/addons/addon-unicode-graphemes/src/third-party/tiny-inflate.ts b/addons/addon-unicode-graphemes/src/third-party/tiny-inflate.ts new file mode 100644 index 00000000..a8d2e8a4 --- /dev/null +++ b/addons/addon-unicode-graphemes/src/third-party/tiny-inflate.ts @@ -0,0 +1,380 @@ +var TINF_OK = 0; +var TINF_DATA_ERROR = -3; + +class Tree { + table = new Uint16Array(16); /* table of code length counts */ + trans = new Uint16Array(288); /* code -> symbol translation table */ +}; + +class Data { + tag: number = 0; + bitcount: number = 0; + destLen: number = 0; + ltree: Tree; + dtree: Tree; + source: Uint8Array; + dest: Uint8Array; + sourceIndex: number = 0; + + constructor(source: Uint8Array, dest: Uint8Array) { + this.source = source; + this.dest = dest; + this.ltree = new Tree(); /* dynamic length/symbol tree */ + this.dtree = new Tree(); /* dynamic distance tree */ + } +} + +/* --------------------------------------------------- * + * -- uninitialized global data (static structures) -- * + * --------------------------------------------------- */ + +var sltree = new Tree(); +var sdtree = new Tree(); + +/* extra bits and base tables for length codes */ +var length_bits = new Uint8Array(30); +var length_base = new Uint16Array(30); + +/* extra bits and base tables for distance codes */ +var dist_bits = new Uint8Array(30); +var dist_base = new Uint16Array(30); + +/* special ordering of code length codes */ +var clcidx = new Uint8Array([ + 16, 17, 18, 0, 8, 7, 9, 6, + 10, 5, 11, 4, 12, 3, 13, 2, + 14, 1, 15 +]); + +/* used by tinf_decode_trees, avoids allocations every call */ +const code_tree = new Tree(); +const lengths = new Uint8Array(288 + 32); + +/* ----------------------- * + * -- utility functions -- * + * ----------------------- */ + +/* build extra bits and base tables */ +function tinf_build_bits_base(bits: Uint8Array, base: Uint16Array, delta: number, first: number): void { + var i, sum; + + /* build bits table */ + for (i = 0; i < delta; ++i) bits[i] = 0; + for (i = 0; i < 30 - delta; ++i) bits[i + delta] = i / delta | 0; + + /* build base table */ + for (sum = first, i = 0; i < 30; ++i) { + base[i] = sum; + sum += 1 << bits[i]; + } +} + +/* build the fixed huffman trees */ +function tinf_build_fixed_trees(lt: Tree, dt: Tree): void { + var i; + + /* build fixed length tree */ + for (i = 0; i < 7; ++i) lt.table[i] = 0; + + lt.table[7] = 24; + lt.table[8] = 152; + lt.table[9] = 112; + + for (i = 0; i < 24; ++i) lt.trans[i] = 256 + i; + for (i = 0; i < 144; ++i) lt.trans[24 + i] = i; + for (i = 0; i < 8; ++i) lt.trans[24 + 144 + i] = 280 + i; + for (i = 0; i < 112; ++i) lt.trans[24 + 144 + 8 + i] = 144 + i; + + /* build fixed distance tree */ + for (i = 0; i < 5; ++i) dt.table[i] = 0; + + dt.table[5] = 32; + + for (i = 0; i < 32; ++i) dt.trans[i] = i; +} + +/* given an array of code lengths, build a tree */ +var offs = new Uint16Array(16); + +function tinf_build_tree(t: Tree, lengths: Uint8Array, off: number, num: number): void { + var i, sum; + + /* clear code length count table */ + for (i = 0; i < 16; ++i) t.table[i] = 0; + + /* scan symbol lengths, and sum code length counts */ + for (i = 0; i < num; ++i) t.table[lengths[off + i]]++; + + t.table[0] = 0; + + /* compute offset table for distribution sort */ + for (sum = 0, i = 0; i < 16; ++i) { + offs[i] = sum; + sum += t.table[i]; + } + + /* create code->symbol translation table (symbols sorted by code) */ + for (i = 0; i < num; ++i) { + if (lengths[off + i]) t.trans[offs[lengths[off + i]]++] = i; + } +} + +/* ---------------------- * + * -- decode functions -- * + * ---------------------- */ + +/* get one bit from source stream */ +function tinf_getbit(d: Data): number { + /* check if tag is empty */ + if (!d.bitcount--) { + /* load next tag */ + d.tag = d.source[d.sourceIndex++]; + d.bitcount = 7; + } + + /* shift bit out of tag */ + var bit = d.tag & 1; + d.tag >>>= 1; + + return bit; +} + +/* read a num bit value from a stream and add base */ +function tinf_read_bits(d: Data, num: number, base: number): number { + if (!num) + return base; + + while (d.bitcount < 24) { + d.tag |= d.source[d.sourceIndex++] << d.bitcount; + d.bitcount += 8; + } + + var val = d.tag & (0xffff >>> (16 - num)); + d.tag >>>= num; + d.bitcount -= num; + return val + base; +} + +/* given a data stream and a tree, decode a symbol */ +function tinf_decode_symbol(d: Data, t: Tree): number { + while (d.bitcount < 24) { + d.tag |= d.source[d.sourceIndex++] << d.bitcount; + d.bitcount += 8; + } + + var sum = 0, cur = 0, len = 0; + var tag = d.tag; + + /* get more bits while code value is above sum */ + do { + cur = 2 * cur + (tag & 1); + tag >>>= 1; + ++len; + + sum += t.table[len]; + cur -= t.table[len]; + } while (cur >= 0); + + d.tag = tag; + d.bitcount -= len; + + return t.trans[sum + cur]; +} + +/* given a data stream, decode dynamic trees from it */ +function tinf_decode_trees(d: Data, lt: Tree, dt: Tree): void { + var hlit, hdist, hclen; + var i, num, length; + + /* get 5 bits HLIT (257-286) */ + hlit = tinf_read_bits(d, 5, 257); + + /* get 5 bits HDIST (1-32) */ + hdist = tinf_read_bits(d, 5, 1); + + /* get 4 bits HCLEN (4-19) */ + hclen = tinf_read_bits(d, 4, 4); + + for (i = 0; i < 19; ++i) lengths[i] = 0; + + /* read code lengths for code length alphabet */ + for (i = 0; i < hclen; ++i) { + /* get 3 bits code length (0-7) */ + var clen = tinf_read_bits(d, 3, 0); + lengths[clcidx[i]] = clen; + } + + /* build code length tree */ + tinf_build_tree(code_tree, lengths, 0, 19); + + /* decode code lengths for the dynamic trees */ + for (num = 0; num < hlit + hdist;) { + var sym = tinf_decode_symbol(d, code_tree); + + switch (sym) { + case 16: + /* copy previous code length 3-6 times (read 2 bits) */ + var prev = lengths[num - 1]; + for (length = tinf_read_bits(d, 2, 3); length; --length) { + lengths[num++] = prev; + } + break; + case 17: + /* repeat code length 0 for 3-10 times (read 3 bits) */ + for (length = tinf_read_bits(d, 3, 3); length; --length) { + lengths[num++] = 0; + } + break; + case 18: + /* repeat code length 0 for 11-138 times (read 7 bits) */ + for (length = tinf_read_bits(d, 7, 11); length; --length) { + lengths[num++] = 0; + } + break; + default: + /* values 0-15 represent the actual code lengths */ + lengths[num++] = sym; + break; + } + } + + /* build dynamic trees */ + tinf_build_tree(lt, lengths, 0, hlit); + tinf_build_tree(dt, lengths, hlit, hdist); +} + +/* ----------------------------- * + * -- block inflate functions -- * + * ----------------------------- */ + +/* given a stream and two trees, inflate a block of data */ +function tinf_inflate_block_data(d: Data, lt: Tree, dt: Tree): number { + for (;;) { + var sym = tinf_decode_symbol(d, lt); + + /* check for end of block */ + if (sym === 256) { + return TINF_OK; + } + + if (sym < 256) { + d.dest[d.destLen++] = sym; + } else { + var length, dist, offs; + var i; + + sym -= 257; + + /* possibly get more bits from length code */ + length = tinf_read_bits(d, length_bits[sym], length_base[sym]); + + dist = tinf_decode_symbol(d, dt); + + /* possibly get more bits from distance code */ + offs = d.destLen - tinf_read_bits(d, dist_bits[dist], dist_base[dist]); + + /* copy match */ + for (i = offs; i < offs + length; ++i) { + d.dest[d.destLen++] = d.dest[i]; + } + } + } +} + +/* inflate an uncompressed block of data */ +function tinf_inflate_uncompressed_block(d: Data) { + var length, invlength; + var i; + + /* unread from bitbuffer */ + while (d.bitcount > 8) { + d.sourceIndex--; + d.bitcount -= 8; + } + + /* get length */ + length = d.source[d.sourceIndex + 1]; + length = 256 * length + d.source[d.sourceIndex]; + + /* get one's complement of length */ + invlength = d.source[d.sourceIndex + 3]; + invlength = 256 * invlength + d.source[d.sourceIndex + 2]; + + /* check length */ + if (length !== (~invlength & 0x0000ffff)) + return TINF_DATA_ERROR; + + d.sourceIndex += 4; + + /* copy block */ + for (i = length; i; --i) + d.dest[d.destLen++] = d.source[d.sourceIndex++]; + + /* make sure we start next block on a byte boundary */ + d.bitcount = 0; + + return TINF_OK; +} + +/* inflate stream from source to dest */ +function tinf_uncompress(source: Uint8Array, dest: Uint8Array) { + var d = new Data(source, dest); + var bfinal, btype, res; + + do { + /* read final block flag */ + bfinal = tinf_getbit(d); + + /* read block type (2 bits) */ + btype = tinf_read_bits(d, 2, 0); + + /* decompress block */ + switch (btype) { + case 0: + /* decompress uncompressed block */ + res = tinf_inflate_uncompressed_block(d); + break; + case 1: + /* decompress block with fixed huffman trees */ + res = tinf_inflate_block_data(d, sltree, sdtree); + break; + case 2: + /* decompress block with dynamic huffman trees */ + tinf_decode_trees(d, d.ltree, d.dtree); + res = tinf_inflate_block_data(d, d.ltree, d.dtree); + break; + default: + res = TINF_DATA_ERROR; + } + + if (res !== TINF_OK) + throw new Error('Data error'); + + } while (!bfinal); + + if (d.destLen < d.dest.length) { + if (typeof d.dest.slice === 'function') + return d.dest.slice(0, d.destLen); + else + return d.dest.subarray(0, d.destLen); + } + + return d.dest; +} + +/* -------------------- * + * -- initialization -- * + * -------------------- */ + +/* build fixed huffman trees */ +tinf_build_fixed_trees(sltree, sdtree); + +/* build extra bits and base tables */ +tinf_build_bits_base(length_bits, length_base, 4, 3); +tinf_build_bits_base(dist_bits, dist_base, 2, 1); + +/* fix a special case */ +length_bits[28] = 0; +length_base[28] = 258; + +export default tinf_uncompress diff --git a/addons/addon-unicode-graphemes/src/third-party/unicode-trie.ts b/addons/addon-unicode-graphemes/src/third-party/unicode-trie.ts new file mode 100644 index 00000000..2125f04d --- /dev/null +++ b/addons/addon-unicode-graphemes/src/third-party/unicode-trie.ts @@ -0,0 +1,134 @@ +import inflate from './tiny-inflate' + +// Shift size for getting the index-1 table offset. +const SHIFT_1 = 6 + 5; + +// Shift size for getting the index-2 table offset. +const SHIFT_2 = 5; + +// Difference between the two shift sizes, +// for getting an index-1 offset from an index-2 offset. 6=11-5 +const SHIFT_1_2 = SHIFT_1 - SHIFT_2; + +// Number of index-1 entries for the BMP. 32=0x20 +// This part of the index-1 table is omitted from the serialized form. +const OMITTED_BMP_INDEX_1_LENGTH = 0x10000 >> SHIFT_1; + +// Number of entries in an index-2 block. 64=0x40 +const INDEX_2_BLOCK_LENGTH = 1 << SHIFT_1_2; + +// Mask for getting the lower bits for the in-index-2-block offset. */ +const INDEX_2_MASK = INDEX_2_BLOCK_LENGTH - 1; + +// Shift size for shifting left the index array values. +// Increases possible data size with 16-bit index values at the cost +// of compactability. +// This requires data blocks to be aligned by DATA_GRANULARITY. +const INDEX_SHIFT = 2; + +// Number of entries in a data block. 32=0x20 +const DATA_BLOCK_LENGTH = 1 << SHIFT_2; + +// Mask for getting the lower bits for the in-data-block offset. +const DATA_MASK = DATA_BLOCK_LENGTH - 1; + +// The part of the index-2 table for U+D800..U+DBFF stores values for +// lead surrogate code _units_ not code _points_. +// Values for lead surrogate code _points_ are indexed with this portion of the table. +// Length=32=0x20=0x400>>SHIFT_2. (There are 1024=0x400 lead surrogates.) +const LSCP_INDEX_2_OFFSET = 0x10000 >> SHIFT_2; +const LSCP_INDEX_2_LENGTH = 0x400 >> SHIFT_2; + +// Count the lengths of both BMP pieces. 2080=0x820 +const INDEX_2_BMP_LENGTH = LSCP_INDEX_2_OFFSET + LSCP_INDEX_2_LENGTH; + +// The 2-byte UTF-8 version of the index-2 table follows at offset 2080=0x820. +// Length 32=0x20 for lead bytes C0..DF, regardless of SHIFT_2. +const UTF8_2B_INDEX_2_OFFSET = INDEX_2_BMP_LENGTH; +const UTF8_2B_INDEX_2_LENGTH = 0x800 >> 6; // U+0800 is the first code point after 2-byte UTF-8 + +// The index-1 table, only used for supplementary code points, at offset 2112=0x840. +// Variable length, for code points up to highStart, where the last single-value range starts. +// Maximum length 512=0x200=0x100000>>SHIFT_1. +// (For 0x100000 supplementary code points U+10000..U+10ffff.) +// +// The part of the index-2 table for supplementary code points starts +// after this index-1 table. +// +// Both the index-1 table and the following part of the index-2 table +// are omitted completely if there is only BMP data. +const INDEX_1_OFFSET = UTF8_2B_INDEX_2_OFFSET + UTF8_2B_INDEX_2_LENGTH; + +// The alignment size of a data block. Also the granularity for compaction. +const DATA_GRANULARITY = 1 << INDEX_SHIFT; + +const isBigEndian = (new Uint8Array(new Uint32Array([0x12345678]).buffer)[0] === 0x12); + +class UnicodeTrie { + private data: Uint32Array; + private highStart: number; + private errorValue: number; + constructor(data: Uint8Array) { + // read binary format + + const view = new DataView(data.buffer); + this.highStart = view.getUint32(0, true); + this.errorValue = view.getUint32(4, true); + let uncompressedLength = view.getUint32(8, true); + data = data.subarray(12); + + // double inflate the actual trie data + data = inflate(data, new Uint8Array(uncompressedLength)); + data = inflate(data, new Uint8Array(uncompressedLength)); + + if (isBigEndian) { + // swap bytes from little-endian + const len = data.length; + for (let i = 0; i < len; i += 4) { + // Exchange data[i] and data[i + 3]: + let x = data[i]; data[i] = data[i+3]; data[i+3] = x; + // Exchange data[i + 1] and data[i + 2]: + let y = data[i+1]; data[i+1] = data[i+2]; data[i+2] = y; + } + } + + this.data = new Uint32Array(data.buffer); + + } + + get(codePoint: number): number { + let index; + if ((codePoint < 0) || (codePoint > 0x10ffff)) { + return this.errorValue; + } + + if ((codePoint < 0xd800) || ((codePoint > 0xdbff) && (codePoint <= 0xffff))) { + // Ordinary BMP code point, excluding leading surrogates. + // BMP uses a single level lookup. BMP index starts at offset 0 in the index. + // data is stored in the index array itself. + index = (this.data[codePoint >> SHIFT_2] << INDEX_SHIFT) + (codePoint & DATA_MASK); + return this.data[index]; + } + + if (codePoint <= 0xffff) { + // Lead Surrogate Code Point. A Separate index section is stored for + // lead surrogate code units and code points. + // The main index has the code unit data. + // For this function, we need the code point data. + index = (this.data[LSCP_INDEX_2_OFFSET + ((codePoint - 0xd800) >> SHIFT_2)] << INDEX_SHIFT) + (codePoint & DATA_MASK); + return this.data[index]; + } + + if (codePoint < this.highStart) { + // Supplemental code point, use two-level lookup. + index = this.data[(INDEX_1_OFFSET - OMITTED_BMP_INDEX_1_LENGTH) + (codePoint >> SHIFT_1)]; + index = this.data[index + ((codePoint >> SHIFT_2) & INDEX_2_MASK)]; + index = (index << INDEX_SHIFT) + (codePoint & DATA_MASK); + return this.data[index]; + } + + return this.data[this.data.length - DATA_GRANULARITY]; + } +} + +export default UnicodeTrie diff --git a/addons/addon-unicode-graphemes/src/tsconfig.json b/addons/addon-unicode-graphemes/src/tsconfig.json new file mode 100644 index 00000000..e9967bbe --- /dev/null +++ b/addons/addon-unicode-graphemes/src/tsconfig.json @@ -0,0 +1,36 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es2021", + "lib": [ + "dom", + "es2021" + ], + "rootDir": ".", + "outDir": "../out", + "sourceMap": true, + "removeComments": true, + "strict": true, + "baseUrl": ".", + "paths": { + "common/*": [ + "../../../src/common/*" + ], + "@xterm/addon-unicode-graphemes": [ + "../typings/addon-unicode-graphemes.d.ts" + ] + }, + "types": [ + "../../../node_modules/@types/mocha" + ] + }, + "include": [ + "./**/*", + "../../../typings/xterm.d.ts" + ], + "references": [ + { + "path": "../../../src/common" + } + ] +} diff --git a/addons/addon-unicode-graphemes/test/UnicodeGraphemesAddon.api.ts b/addons/addon-unicode-graphemes/test/UnicodeGraphemesAddon.api.ts new file mode 100644 index 00000000..e13ad292 --- /dev/null +++ b/addons/addon-unicode-graphemes/test/UnicodeGraphemesAddon.api.ts @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert } from 'chai'; +import { openTerminal, launchBrowser } from '../../../out-test/api/TestUtils'; +import { Browser, Page } from '@playwright/test'; + +const APP = 'http://127.0.0.1:3001/test'; + +let browser: Browser; +let page: Page; +const width = 800; +const height = 600; + +describe('UnicodeGraphemesAddon', () => { + before(async function(): Promise { + browser = await launchBrowser(); + page = await (await browser.newContext()).newPage(); + await page.setViewportSize({ width, height }); + }); + + after(async () => { + await browser.close(); + }); + + beforeEach(async function(): Promise { + await page.goto(APP); + await openTerminal(page); + }); + async function evalWidth(str: string): Promise { + return page.evaluate(`window.term._core.unicodeService.getStringCellWidth('${str}')`); + } + const ourVersion = '15-graphemes'; + it('wcwidth V15 emoji test', async () => { + await page.evaluate(` + window.unicode = new UnicodeGraphemesAddon(); + window.term.loadAddon(window.unicode); + `); + // should have loaded '15-graphemes' + assert.deepEqual(await page.evaluate(`window.term.unicode.versions`), ['6', '15', '15-graphemes']); + // switch should not throw + await page.evaluate(`window.term.unicode.activeVersion = '${ourVersion}';`); + assert.equal(await page.evaluate(`window.term.unicode.activeVersion`), ourVersion); + assert.equal(await evalWidth('🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣'), 20, + '10 emoji - width 10 in V6; 20 in V11 or later'); + assert.equal(await evalWidth('\u{1F476}\u{1F3FF}\u{1F476}'), 4, + 'baby with emoji modifier fitzpatrick type-6; baby'); + assert.equal(await evalWidth('\u{1F469}\u200d\u{1f469}\u200d\u{1f466}'), 2, + 'woman+zwj+woman+zwj+boy'); + assert.equal(await evalWidth('=\u{1F3CB}\u{FE0F}=\u{F3CB}\u{1F3FE}\u200D\u2640='), 7, + 'person lifting weights (plain, emoji); woman lighting weights, medium dark'); + assert.equal(await evalWidth('\u{1F469}\u{1F469}\u{200D}\u{1F393}\u{1F468}\u{1F3FF}\u{200D}\u{1F393}'), 6, + 'woman; woman student; man student dark'); + assert.equal(await evalWidth('\u{1f1f3}\u{1f1f4}/'), 3, + 'regional indicator symbol letters N and O, cluster'); + assert.equal(await evalWidth('\u{1f1f3}/\u{1f1f4}'), 3, + 'regional indicator symbol letters N and O, separated'); + assert.equal(await evalWidth('\u0061\u0301'), 1, + 'letter a with acute accent'); + assert.equal(await evalWidth('{\u1100\u1161\u11a8\u1100\u1161}'), 6, + 'Korean Jamo'); + assert.equal(await evalWidth('\uAC00=\uD685='), 6, + 'Hangul syllables (pre-composed)'); + assert.equal(await evalWidth('(\u26b0\ufe0e)'), 3, + 'coffin with text presentation'); + assert.equal(await evalWidth('(\u26b0\ufe0f)'), 4, + 'coffin with emoji presentation'); + assert.equal(await evalWidth(''), 16, + 'Égalité (using separate acute) emoij_presentation'); + }); +}); diff --git a/addons/addon-unicode-graphemes/test/tsconfig.json b/addons/addon-unicode-graphemes/test/tsconfig.json new file mode 100644 index 00000000..4b3cb31c --- /dev/null +++ b/addons/addon-unicode-graphemes/test/tsconfig.json @@ -0,0 +1,35 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es2015", + "lib": [ + "dom", + "es2015" + ], + "rootDir": ".", + "outDir": "../out-test", + "sourceMap": true, + "removeComments": true, + "strict": true, + "baseUrl": ".", + "paths": { + "common/*": [ + "../../../src/common/*" + ] + }, + "types": [ + "../../../node_modules/@types/mocha", + "../../../node_modules/@types/node", + "../../../out-test/api/TestUtils" + ] + }, + "include": [ + "./**/*", + "../../../typings/xterm.d.ts" + ], + "references": [ + { + "path": "../../../src/common" + } + ] +} diff --git a/addons/addon-unicode-graphemes/tsconfig.json b/addons/addon-unicode-graphemes/tsconfig.json new file mode 100644 index 00000000..0e7b5c35 --- /dev/null +++ b/addons/addon-unicode-graphemes/tsconfig.json @@ -0,0 +1,9 @@ +{ + "files": [], + "include": [], + "references": [ + { "path": "./src" }, + { "path": "./test" }, + { "path": "./benchmark" } + ] +} diff --git a/addons/addon-unicode-graphemes/typings/addon-unicode-graphemes.d.ts b/addons/addon-unicode-graphemes/typings/addon-unicode-graphemes.d.ts new file mode 100644 index 00000000..9443f1c8 --- /dev/null +++ b/addons/addon-unicode-graphemes/typings/addon-unicode-graphemes.d.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2023 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { Terminal, ITerminalAddon } from '@xterm/xterm'; + +declare module '@xterm/addon-unicode-graphemes' { + export class UnicodeGraphemesAddon implements ITerminalAddon { + constructor(); + public activate(terminal: Terminal): void; + public dispose(): void; + } +} diff --git a/addons/addon-unicode-graphemes/webpack.config.js b/addons/addon-unicode-graphemes/webpack.config.js new file mode 100644 index 00000000..6a80bdea --- /dev/null +++ b/addons/addon-unicode-graphemes/webpack.config.js @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +const path = require('path'); + +const addonName = 'UnicodeGraphemesAddon'; +const mainFile = 'addon-unicode-graphemes.js'; + +module.exports = { + entry: `./out/${addonName}.js`, + devtool: 'source-map', + module: { + rules: [ + { + test: /\.js$/, + use: ["source-map-loader"], + enforce: "pre", + exclude: /node_modules/ + } + ] + }, + resolve: { + modules: ['./node_modules'], + extensions: [ '.js' ], + alias: { + common: path.resolve('../../out/common') + } + }, + output: { + filename: mainFile, + path: path.resolve('./lib'), + library: addonName, + libraryTarget: 'umd' + }, + mode: 'production' +}; diff --git a/addons/xterm-addon-unicode11/.gitignore b/addons/addon-unicode11/.gitignore similarity index 100% rename from addons/xterm-addon-unicode11/.gitignore rename to addons/addon-unicode11/.gitignore diff --git a/addons/xterm-addon-web-links/.npmignore b/addons/addon-unicode11/.npmignore similarity index 100% rename from addons/xterm-addon-web-links/.npmignore rename to addons/addon-unicode11/.npmignore diff --git a/addons/xterm-addon-unicode11/LICENSE b/addons/addon-unicode11/LICENSE similarity index 100% rename from addons/xterm-addon-unicode11/LICENSE rename to addons/addon-unicode11/LICENSE diff --git a/addons/xterm-addon-unicode11/README.md b/addons/addon-unicode11/README.md similarity index 63% rename from addons/xterm-addon-unicode11/README.md rename to addons/addon-unicode11/README.md index ec01203e..ccfe77cf 100644 --- a/addons/xterm-addon-unicode11/README.md +++ b/addons/addon-unicode11/README.md @@ -1,18 +1,18 @@ -## xterm-addon-unicode11 +## @xterm/addon-unicode11 An addon providing Unicode version 11 rules for xterm.js. ### Install ```bash -npm install --save xterm-addon-unicode11 +npm install --save @xterm/addon-unicode11 ``` ### Usage ```ts -import { Terminal } from 'xterm'; -import { Unicode11Addon } from 'xterm-addon-unicode11'; +import { Terminal } from '@xterm/xterm'; +import { Unicode11Addon } from '@xterm/addon-unicode11'; const terminal = new Terminal(); const unicode11Addon = new Unicode11Addon(); diff --git a/addons/xterm-addon-unicode11/package.json b/addons/addon-unicode11/package.json similarity index 64% rename from addons/xterm-addon-unicode11/package.json rename to addons/addon-unicode11/package.json index e1651961..ad6a4892 100644 --- a/addons/xterm-addon-unicode11/package.json +++ b/addons/addon-unicode11/package.json @@ -1,13 +1,13 @@ { - "name": "xterm-addon-unicode11", + "name": "@xterm/addon-unicode11", "version": "0.6.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" }, - "main": "lib/xterm-addon-unicode11.js", - "types": "typings/xterm-addon-unicode11.d.ts", - "repository": "https://github.com/xtermjs/xterm.js", + "main": "lib/addon-unicode11.js", + "types": "typings/addon-unicode11.d.ts", + "repository": "https://github.com/xtermjs/xterm.js/tree/master/addons/addon-unicode11", "license": "MIT", "keywords": [ "terminal", @@ -21,6 +21,6 @@ "prepublishOnly": "npm run package" }, "peerDependencies": { - "xterm": "^5.0.0" + "@xterm/xterm": "^5.0.0" } } diff --git a/addons/xterm-addon-unicode11/src/Unicode11Addon.ts b/addons/addon-unicode11/src/Unicode11Addon.ts similarity index 58% rename from addons/xterm-addon-unicode11/src/Unicode11Addon.ts rename to addons/addon-unicode11/src/Unicode11Addon.ts index d4ddf77e..301145ee 100644 --- a/addons/xterm-addon-unicode11/src/Unicode11Addon.ts +++ b/addons/addon-unicode11/src/Unicode11Addon.ts @@ -5,11 +5,11 @@ * UnicodeVersionProvider for V11. */ -import { Terminal, ITerminalAddon } from 'xterm'; +import type { Terminal, ITerminalAddon } from '@xterm/xterm'; +import type { Unicode11Addon as IUnicode11Api } from '@xterm/addon-unicode11'; import { UnicodeV11 } from './UnicodeV11'; - -export class Unicode11Addon implements ITerminalAddon { +export class Unicode11Addon implements ITerminalAddon , IUnicode11Api { public activate(terminal: Terminal): void { terminal.unicode.register(new UnicodeV11()); } diff --git a/addons/xterm-addon-unicode11/src/UnicodeV11.ts b/addons/addon-unicode11/src/UnicodeV11.ts similarity index 92% rename from addons/xterm-addon-unicode11/src/UnicodeV11.ts rename to addons/addon-unicode11/src/UnicodeV11.ts index b616091a..79568960 100644 --- a/addons/xterm-addon-unicode11/src/UnicodeV11.ts +++ b/addons/addon-unicode11/src/UnicodeV11.ts @@ -3,9 +3,9 @@ * @license MIT */ -import { IUnicodeVersionProvider } from 'xterm'; - -type CharWidth = 0 | 1 | 2; +import { IUnicodeVersionProvider } from '@xterm/xterm'; +import { UnicodeCharProperties, UnicodeCharWidth } from 'common/services/Services'; +import { UnicodeService } from 'common/services/UnicodeService'; const BMP_COMBINING = [ [0x0300, 0x036F], [0x0483, 0x0489], [0x0591, 0x05BD], @@ -210,12 +210,26 @@ export class UnicodeV11 implements IUnicodeVersionProvider { } } - public wcwidth(num: number): CharWidth { + public wcwidth(num: number): UnicodeCharWidth { if (num < 32) return 0; if (num < 127) return 1; - if (num < 65536) return table[num] as CharWidth; + if (num < 65536) return table[num] as UnicodeCharWidth; if (bisearch(num, HIGH_COMBINING)) return 0; if (bisearch(num, HIGH_WIDE)) return 2; return 1; } + + public charProperties(codepoint: number, preceding: UnicodeCharProperties): UnicodeCharProperties { + let width = this.wcwidth(codepoint); + let shouldJoin = width === 0 && preceding !== 0; + if (shouldJoin) { + const oldWidth = UnicodeService.extractWidth(preceding); + if (oldWidth === 0) { + shouldJoin = false; + } else if (oldWidth > width) { + width = oldWidth; + } + } + return UnicodeService.createPropertyValue(0, width, shouldJoin); + } } diff --git a/addons/xterm-addon-unicode11/src/tsconfig.json b/addons/addon-unicode11/src/tsconfig.json similarity index 86% rename from addons/xterm-addon-unicode11/src/tsconfig.json rename to addons/addon-unicode11/src/tsconfig.json index e2c1464e..8eb7752b 100644 --- a/addons/xterm-addon-unicode11/src/tsconfig.json +++ b/addons/addon-unicode11/src/tsconfig.json @@ -15,6 +15,9 @@ "paths": { "common/*": [ "../../../src/common/*" + ], + "@xterm/addon-unicode11": [ + "../typings/addon-unicode11.d.ts" ] }, "types": [ diff --git a/addons/xterm-addon-unicode11/test/Unicode11Addon.api.ts b/addons/addon-unicode11/test/Unicode11Addon.api.ts similarity index 92% rename from addons/xterm-addon-unicode11/test/Unicode11Addon.api.ts rename to addons/addon-unicode11/test/Unicode11Addon.api.ts index c7f3588f..83c7a61d 100644 --- a/addons/xterm-addon-unicode11/test/Unicode11Addon.api.ts +++ b/addons/addon-unicode11/test/Unicode11Addon.api.ts @@ -36,7 +36,7 @@ describe('Unicode11Addon', () => { window.term.loadAddon(window.unicode11); `); // should have loaded '11' - assert.deepEqual(await page.evaluate(`window.term.unicode.versions`), ['6', '11']); + assert.deepEqual((await page.evaluate(`window.term.unicode.versions`) as string[]).includes('11'), true); // switch should not throw await page.evaluate(`window.term.unicode.activeVersion = '11';`); assert.deepEqual(await page.evaluate(`window.term.unicode.activeVersion`), '11'); diff --git a/addons/xterm-addon-unicode11/test/tsconfig.json b/addons/addon-unicode11/test/tsconfig.json similarity index 100% rename from addons/xterm-addon-unicode11/test/tsconfig.json rename to addons/addon-unicode11/test/tsconfig.json diff --git a/addons/xterm-addon-unicode11/tsconfig.json b/addons/addon-unicode11/tsconfig.json similarity index 100% rename from addons/xterm-addon-unicode11/tsconfig.json rename to addons/addon-unicode11/tsconfig.json diff --git a/addons/xterm-addon-unicode11/typings/xterm-addon-unicode11.d.ts b/addons/addon-unicode11/typings/addon-unicode11.d.ts similarity index 71% rename from addons/xterm-addon-unicode11/typings/xterm-addon-unicode11.d.ts rename to addons/addon-unicode11/typings/addon-unicode11.d.ts index 1d0dce1b..74bd288d 100644 --- a/addons/xterm-addon-unicode11/typings/xterm-addon-unicode11.d.ts +++ b/addons/addon-unicode11/typings/addon-unicode11.d.ts @@ -3,9 +3,9 @@ * @license MIT */ -import { Terminal, ITerminalAddon } from 'xterm'; +import { Terminal, ITerminalAddon } from '@xterm/xterm'; -declare module 'xterm-addon-unicode11' { +declare module '@xterm/addon-unicode11' { export class Unicode11Addon implements ITerminalAddon { constructor(); public activate(terminal: Terminal): void; diff --git a/addons/xterm-addon-unicode11/webpack.config.js b/addons/addon-unicode11/webpack.config.js similarity index 94% rename from addons/xterm-addon-unicode11/webpack.config.js rename to addons/addon-unicode11/webpack.config.js index 10c0adc3..1913481d 100644 --- a/addons/xterm-addon-unicode11/webpack.config.js +++ b/addons/addon-unicode11/webpack.config.js @@ -6,7 +6,7 @@ const path = require('path'); const addonName = 'Unicode11Addon'; -const mainFile = 'xterm-addon-unicode11.js'; +const mainFile = 'addon-unicode11.js'; module.exports = { entry: `./out/${addonName}.js`, diff --git a/addons/xterm-addon-web-links/.gitignore b/addons/addon-web-links/.gitignore similarity index 100% rename from addons/xterm-addon-web-links/.gitignore rename to addons/addon-web-links/.gitignore diff --git a/addons/xterm-addon-webgl/.npmignore b/addons/addon-web-links/.npmignore similarity index 100% rename from addons/xterm-addon-webgl/.npmignore rename to addons/addon-web-links/.npmignore diff --git a/addons/xterm-addon-web-links/LICENSE b/addons/addon-web-links/LICENSE similarity index 100% rename from addons/xterm-addon-web-links/LICENSE rename to addons/addon-web-links/LICENSE diff --git a/addons/xterm-addon-web-links/README.md b/addons/addon-web-links/README.md similarity index 55% rename from addons/xterm-addon-web-links/README.md rename to addons/addon-web-links/README.md index e53f4edf..16dfa571 100644 --- a/addons/xterm-addon-web-links/README.md +++ b/addons/addon-web-links/README.md @@ -1,21 +1,21 @@ -## xterm-addon-web-links +## @xterm/addon-web-links An addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables web links. This addon requires xterm.js v4+. ### Install ```bash -npm install --save xterm-addon-web-links +npm install --save @xterm/addon-web-links ``` ### Usage ```ts -import { Terminal } from 'xterm'; -import { WebLinksAddon } from 'xterm-addon-web-links'; +import { Terminal } from '@xterm/xterm'; +import { WebLinksAddon } from '@xterm/addon-web-links'; const terminal = new Terminal(); terminal.loadAddon(new WebLinksAddon()); ``` -See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/xterm-addon-web-links/typings/xterm-addon-web-links.d.ts) for more advanced usage. +See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/addon-web-links/typings/addon-web-links.d.ts) for more advanced usage. diff --git a/addons/xterm-addon-attach/package.json b/addons/addon-web-links/package.json similarity index 64% rename from addons/xterm-addon-attach/package.json rename to addons/addon-web-links/package.json index f93cc612..6367907a 100644 --- a/addons/xterm-addon-attach/package.json +++ b/addons/addon-web-links/package.json @@ -1,13 +1,13 @@ { - "name": "xterm-addon-attach", + "name": "@xterm/addon-web-links", "version": "0.9.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" }, - "main": "lib/xterm-addon-attach.js", - "types": "typings/xterm-addon-attach.d.ts", - "repository": "https://github.com/xtermjs/xterm.js", + "main": "lib/addon-web-links.js", + "types": "typings/addon-web-links.d.ts", + "repository": "https://github.com/xtermjs/xterm.js/tree/master/addons/addon-web-links", "license": "MIT", "keywords": [ "terminal", @@ -21,6 +21,7 @@ "prepublishOnly": "npm run package" }, "peerDependencies": { - "xterm": "^5.0.0" + "@xterm/xterm": "^5.0.0" } + } diff --git a/addons/xterm-addon-web-links/src/WebLinkProvider.ts b/addons/addon-web-links/src/WebLinkProvider.ts similarity index 99% rename from addons/xterm-addon-web-links/src/WebLinkProvider.ts rename to addons/addon-web-links/src/WebLinkProvider.ts index 1c9486f5..25dd983c 100644 --- a/addons/xterm-addon-web-links/src/WebLinkProvider.ts +++ b/addons/addon-web-links/src/WebLinkProvider.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { ILinkProvider, ILink, Terminal, IViewportRange, IBufferLine } from 'xterm'; +import { ILinkProvider, ILink, Terminal, IViewportRange, IBufferLine } from '@xterm/xterm'; export interface ILinkProviderOptions { hover?(event: MouseEvent, text: string, location: IViewportRange): void; diff --git a/addons/xterm-addon-web-links/src/WebLinksAddon.ts b/addons/addon-web-links/src/WebLinksAddon.ts similarity index 89% rename from addons/xterm-addon-web-links/src/WebLinksAddon.ts rename to addons/addon-web-links/src/WebLinksAddon.ts index 5c956cec..8902d8e0 100644 --- a/addons/xterm-addon-web-links/src/WebLinksAddon.ts +++ b/addons/addon-web-links/src/WebLinksAddon.ts @@ -3,7 +3,8 @@ * @license MIT */ -import { Terminal, ITerminalAddon, IDisposable } from 'xterm'; +import type { Terminal, ITerminalAddon, IDisposable } from '@xterm/xterm'; +import type { WebLinksAddon as IWebLinksApi } from '@xterm/addon-web-links'; import { ILinkProviderOptions, WebLinkProvider } from './WebLinkProvider'; // consider everthing starting with http:// or https:// @@ -34,7 +35,7 @@ function handleLink(event: MouseEvent, uri: string): void { } } -export class WebLinksAddon implements ITerminalAddon { +export class WebLinksAddon implements ITerminalAddon , IWebLinksApi { private _terminal: Terminal | undefined; private _linkProvider: IDisposable | undefined; diff --git a/addons/xterm-addon-web-links/src/tsconfig.json b/addons/addon-web-links/src/tsconfig.json similarity index 76% rename from addons/xterm-addon-web-links/src/tsconfig.json rename to addons/addon-web-links/src/tsconfig.json index 7e68574d..217402de 100644 --- a/addons/xterm-addon-web-links/src/tsconfig.json +++ b/addons/addon-web-links/src/tsconfig.json @@ -13,7 +13,12 @@ "strict": true, "types": [ "../../../node_modules/@types/mocha" - ] + ], + "paths": { + "@xterm/addon-web-links": [ + "../typings/addon-web-links.d.ts" + ] + } }, "include": [ "./**/*", diff --git a/addons/xterm-addon-web-links/test/WebLinksAddon.api.ts b/addons/addon-web-links/test/WebLinksAddon.api.ts similarity index 100% rename from addons/xterm-addon-web-links/test/WebLinksAddon.api.ts rename to addons/addon-web-links/test/WebLinksAddon.api.ts diff --git a/addons/xterm-addon-web-links/test/tsconfig.json b/addons/addon-web-links/test/tsconfig.json similarity index 100% rename from addons/xterm-addon-web-links/test/tsconfig.json rename to addons/addon-web-links/test/tsconfig.json diff --git a/addons/xterm-addon-web-links/tsconfig.json b/addons/addon-web-links/tsconfig.json similarity index 100% rename from addons/xterm-addon-web-links/tsconfig.json rename to addons/addon-web-links/tsconfig.json diff --git a/addons/xterm-addon-web-links/typings/xterm-addon-web-links.d.ts b/addons/addon-web-links/typings/addon-web-links.d.ts similarity index 91% rename from addons/xterm-addon-web-links/typings/xterm-addon-web-links.d.ts rename to addons/addon-web-links/typings/addon-web-links.d.ts index dc69d732..4ae4934e 100644 --- a/addons/xterm-addon-web-links/typings/xterm-addon-web-links.d.ts +++ b/addons/addon-web-links/typings/addon-web-links.d.ts @@ -4,9 +4,9 @@ */ -import { Terminal, ITerminalAddon, IViewportRange } from 'xterm'; +import { Terminal, ITerminalAddon, IViewportRange } from '@xterm/xterm'; -declare module 'xterm-addon-web-links' { +declare module '@xterm/addon-web-links' { /** * An xterm.js addon that enables web links. */ diff --git a/addons/xterm-addon-web-links/webpack.config.js b/addons/addon-web-links/webpack.config.js similarity index 92% rename from addons/xterm-addon-web-links/webpack.config.js rename to addons/addon-web-links/webpack.config.js index fd87d07e..4484dbf6 100644 --- a/addons/xterm-addon-web-links/webpack.config.js +++ b/addons/addon-web-links/webpack.config.js @@ -6,7 +6,7 @@ const path = require('path'); const addonName = 'WebLinksAddon'; -const mainFile = 'xterm-addon-web-links.js'; +const mainFile = 'addon-web-links.js'; module.exports = { entry: `./out/${addonName}.js`, diff --git a/addons/xterm-addon-webgl/.gitignore b/addons/addon-webgl/.gitignore similarity index 100% rename from addons/xterm-addon-webgl/.gitignore rename to addons/addon-webgl/.gitignore diff --git a/addons/addon-webgl/.npmignore b/addons/addon-webgl/.npmignore new file mode 100644 index 00000000..b203232a --- /dev/null +++ b/addons/addon-webgl/.npmignore @@ -0,0 +1,29 @@ +# Blacklist - exclude everything except npm defaults such as LICENSE, etc +* +!*/ + +# Whitelist - lib/ +!lib/**/*.d.ts + +!lib/**/*.js +!lib/**/*.js.map + +!lib/**/*.css + +# Whitelist - src/ +!src/**/*.ts +!src/**/*.d.ts + +!src/**/*.js +!src/**/*.js.map + +!src/**/*.css + +# Blacklist - src/ test files +src/**/*.test.ts +src/**/*.test.d.ts +src/**/*.test.js +src/**/*.test.js.map + +# Whitelist - typings/ +!typings/*.d.ts diff --git a/addons/xterm-addon-webgl/LICENSE b/addons/addon-webgl/LICENSE similarity index 100% rename from addons/xterm-addon-webgl/LICENSE rename to addons/addon-webgl/LICENSE diff --git a/addons/xterm-addon-webgl/README.md b/addons/addon-webgl/README.md similarity index 71% rename from addons/xterm-addon-webgl/README.md rename to addons/addon-webgl/README.md index 5dee6b62..05d753e8 100644 --- a/addons/xterm-addon-webgl/README.md +++ b/addons/addon-webgl/README.md @@ -1,25 +1,25 @@ -## xterm-addon-webgl +## @xterm/addon-webgl An addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables a WebGL2-based renderer. This addon requires xterm.js v4+. ### Install ```bash -npm install --save xterm-addon-webgl +npm install --save @xterm/addon-webgl ``` ### Usage ```ts -import { Terminal } from 'xterm'; -import { WebglAddon } from 'xterm-addon-webgl'; +import { Terminal } from '@xterm/xterm'; +import { WebglAddon } from '@xterm/addon-webgl'; const terminal = new Terminal(); terminal.open(element); 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. +See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/addon-webgl/typings/addon-webgl.d.ts) for more advanced usage. ### Handling Context Loss @@ -38,4 +38,4 @@ Read more about handling WebGL context losses on the [Khronos wiki](https://www. ### See also -- [xterm-addon-canvas](https://www.npmjs.com/package/xterm-addon-canvas) A renderer for xterm.js that uses a 2d canvas that can be used as a fallback when WebGL is not available +- [@xterm/addon-canvas](https://www.npmjs.com/package/@xterm/addon-canvas) A renderer for xterm.js that uses a 2d canvas that can be used as a fallback when WebGL is not available diff --git a/addons/xterm-addon-webgl/package.json b/addons/addon-webgl/package.json similarity index 69% rename from addons/xterm-addon-webgl/package.json rename to addons/addon-webgl/package.json index 1f88e150..9a31c306 100644 --- a/addons/xterm-addon-webgl/package.json +++ b/addons/addon-webgl/package.json @@ -1,13 +1,13 @@ { - "name": "xterm-addon-webgl", + "name": "@xterm/addon-webgl", "version": "0.16.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" }, - "main": "lib/xterm-addon-webgl.js", - "types": "typings/xterm-addon-webgl.d.ts", - "repository": "https://github.com/xtermjs/xterm.js", + "main": "lib/addon-webgl.js", + "types": "typings/addon-webgl.d.ts", + "repository": "https://github.com/xtermjs/xterm.js/tree/master/addons/addon-webgl", "license": "MIT", "keywords": [ "terminal", @@ -23,7 +23,6 @@ "start-server-only": "node ../../demo/start-server-only" }, "peerDependencies": { - "xterm": "^5.0.0" + "@xterm/xterm": "^5.0.0" } - } diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/addon-webgl/src/GlyphRenderer.ts similarity index 99% rename from addons/xterm-addon-webgl/src/GlyphRenderer.ts rename to addons/addon-webgl/src/GlyphRenderer.ts index 4dc80d33..1fb0e18c 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/addon-webgl/src/GlyphRenderer.ts @@ -8,8 +8,7 @@ import { TextureAtlas } from 'browser/renderer/shared/TextureAtlas'; import { IRasterizedGlyph, IRenderDimensions, ITextureAtlas } from 'browser/renderer/shared/Types'; import { NULL_CELL_CODE } from 'common/buffer/Constants'; import { Disposable, toDisposable } from 'common/Lifecycle'; -import { traceCall } from 'common/services/LogService'; -import { Terminal } from 'xterm'; +import { Terminal } from '@xterm/xterm'; import { IRenderModel, IWebGL2RenderingContext, IWebGLVertexArrayObject } from './Types'; import { createProgram, GLTexture, PROJECTION_MATRIX } from './WebglUtils'; @@ -213,7 +212,6 @@ export class GlyphRenderer extends Disposable { return this._atlas ? this._atlas.beginFrame() : true; } - @traceCall public updateCell(x: number, y: number, code: number, bg: number, fg: number, ext: number, chars: string, lastBg: number): void { // Since this function is called for every cell (`rows*cols`), it must be very optimized. It // should not instantiate any variables unless a new glyph is drawn to the cache where the diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/addon-webgl/src/RectangleRenderer.ts similarity index 99% rename from addons/xterm-addon-webgl/src/RectangleRenderer.ts rename to addons/addon-webgl/src/RectangleRenderer.ts index 5e9c364e..2a1f5392 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/addon-webgl/src/RectangleRenderer.ts @@ -10,7 +10,7 @@ import { ReadonlyColorSet } from 'browser/Types'; import { Attributes, FgFlags } from 'common/buffer/Constants'; import { Disposable, toDisposable } from 'common/Lifecycle'; import { IColor } from 'common/Types'; -import { Terminal } from 'xterm'; +import { Terminal } from '@xterm/xterm'; import { RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; import { IRenderModel, IWebGL2RenderingContext, IWebGLVertexArrayObject } from './Types'; import { createProgram, expandFloat32Array, PROJECTION_MATRIX } from './WebglUtils'; diff --git a/addons/xterm-addon-webgl/src/RenderModel.ts b/addons/addon-webgl/src/RenderModel.ts similarity index 100% rename from addons/xterm-addon-webgl/src/RenderModel.ts rename to addons/addon-webgl/src/RenderModel.ts diff --git a/addons/xterm-addon-webgl/src/TypedArray.test.ts b/addons/addon-webgl/src/TypedArray.test.ts similarity index 100% rename from addons/xterm-addon-webgl/src/TypedArray.test.ts rename to addons/addon-webgl/src/TypedArray.test.ts diff --git a/addons/xterm-addon-webgl/src/TypedArray.ts b/addons/addon-webgl/src/TypedArray.ts similarity index 100% rename from addons/xterm-addon-webgl/src/TypedArray.ts rename to addons/addon-webgl/src/TypedArray.ts diff --git a/addons/xterm-addon-webgl/src/Types.d.ts b/addons/addon-webgl/src/Types.d.ts similarity index 100% rename from addons/xterm-addon-webgl/src/Types.d.ts rename to addons/addon-webgl/src/Types.d.ts diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/addon-webgl/src/WebglAddon.ts similarity index 86% rename from addons/xterm-addon-webgl/src/WebglAddon.ts rename to addons/addon-webgl/src/WebglAddon.ts index 3e091f0b..2b3a7f96 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/addon-webgl/src/WebglAddon.ts @@ -3,17 +3,19 @@ * @license MIT */ +import type { ITerminalAddon, Terminal } from '@xterm/xterm'; +import type { WebglAddon as IWebglApi } from '@xterm/addon-webgl'; import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services'; import { ITerminal } from 'browser/Types'; import { EventEmitter, forwardEvent } from 'common/EventEmitter'; import { Disposable, toDisposable } from 'common/Lifecycle'; import { getSafariVersion, isSafari } from 'common/Platform'; import { ICoreService, IDecorationService, ILogService, IOptionsService } from 'common/services/Services'; -import { ITerminalAddon, Terminal } from 'xterm'; +import { IWebGL2RenderingContext } from './Types'; import { WebglRenderer } from './WebglRenderer'; import { setTraceLogger } from 'common/services/LogService'; -export class WebglAddon extends Disposable implements ITerminalAddon { +export class WebglAddon extends Disposable implements ITerminalAddon , IWebglApi { private _terminal?: Terminal; private _renderer?: WebglRenderer; @@ -30,7 +32,16 @@ export class WebglAddon extends Disposable implements ITerminalAddon { private _preserveDrawingBuffer?: boolean ) { if (isSafari && getSafariVersion() < 16) { - throw new Error('Webgl2 is only supported on Safari 16 and above'); + // Perform an extra check to determine if Webgl2 is manually enabled in developer settings + const contextAttributes = { + antialias: false, + depth: false, + preserveDrawingBuffer: true + }; + const gl = document.createElement('canvas').getContext('webgl2', contextAttributes) as IWebGL2RenderingContext; + if (!gl) { + throw new Error('Webgl2 is only supported on Safari 16 and above'); + } } super(); } diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/addon-webgl/src/WebglRenderer.ts similarity index 99% rename from addons/xterm-addon-webgl/src/WebglRenderer.ts rename to addons/addon-webgl/src/WebglRenderer.ts index 68b280ab..bb17baa8 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/addon-webgl/src/WebglRenderer.ts @@ -18,9 +18,8 @@ import { CharData, IBufferLine, ICellData } from 'common/Types'; import { AttributeData } from 'common/buffer/AttributeData'; import { CellData } from 'common/buffer/CellData'; import { Attributes, Content, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants'; -import { traceCall } from 'common/services/LogService'; import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; -import { Terminal } from 'xterm'; +import { Terminal } from '@xterm/xterm'; import { GlyphRenderer } from './GlyphRenderer'; import { RectangleRenderer } from './RectangleRenderer'; import { COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_EXT_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL, RenderModel } from './RenderModel'; @@ -89,7 +88,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._updateCursorBlink(); this.register(_optionsService.onOptionChange(() => this._handleOptionsChanged())); - this._canvas = document.createElement('canvas'); + this._canvas = this._coreBrowserService.mainDocument.createElement('canvas'); const contextAttributes = { antialias: false, @@ -220,7 +219,7 @@ export class WebglRenderer extends Disposable implements IRenderer { for (const l of this._renderLayers) { l.handleSelectionChanged(this._terminal, start, end, columnSelectMode); } - this._model.selection.update(this._terminal, start, end, columnSelectMode); + this._model.selection.update(this._core, start, end, columnSelectMode); this._requestRedrawViewport(); } @@ -318,7 +317,6 @@ export class WebglRenderer extends Disposable implements IRenderer { return false; } - @traceCall public renderRows(start: number, end: number): void { if (!this._isAttached) { if (this._coreBrowserService.window.document.body.contains(this._core.screenElement!) && this._charSizeService.width && this._charSizeService.height) { diff --git a/addons/xterm-addon-webgl/src/WebglUtils.ts b/addons/addon-webgl/src/WebglUtils.ts similarity index 100% rename from addons/xterm-addon-webgl/src/WebglUtils.ts rename to addons/addon-webgl/src/WebglUtils.ts diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/addon-webgl/src/renderLayer/BaseRenderLayer.ts similarity index 98% rename from addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts rename to addons/addon-webgl/src/renderLayer/BaseRenderLayer.ts index 3aaac435..93e0fcb1 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -12,7 +12,7 @@ import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { Disposable, toDisposable } from 'common/Lifecycle'; import { CellData } from 'common/buffer/CellData'; import { IOptionsService } from 'common/services/Services'; -import { Terminal } from 'xterm'; +import { Terminal } from '@xterm/xterm'; import { IRenderLayer } from './Types'; export abstract class BaseRenderLayer extends Disposable implements IRenderLayer { @@ -38,7 +38,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer protected readonly _themeService: IThemeService ) { super(); - this._canvas = document.createElement('canvas'); + this._canvas = this._coreBrowserService.mainDocument.createElement('canvas'); this._canvas.classList.add(`xterm-${id}-layer`); this._canvas.style.zIndex = zIndex.toString(); this._initCanvas(); diff --git a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts b/addons/addon-webgl/src/renderLayer/LinkRenderLayer.ts similarity index 98% rename from addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts rename to addons/addon-webgl/src/renderLayer/LinkRenderLayer.ts index aefc9f6e..0aaf5e70 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts +++ b/addons/addon-webgl/src/renderLayer/LinkRenderLayer.ts @@ -9,7 +9,7 @@ import { IRenderDimensions } from 'browser/renderer/shared/Types'; import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { ILinkifier2, ILinkifierEvent } from 'browser/Types'; import { IOptionsService } from 'common/services/Services'; -import { Terminal } from 'xterm'; +import { Terminal } from '@xterm/xterm'; import { BaseRenderLayer } from './BaseRenderLayer'; export class LinkRenderLayer extends BaseRenderLayer { diff --git a/addons/xterm-addon-webgl/src/renderLayer/Types.ts b/addons/addon-webgl/src/renderLayer/Types.ts similarity index 96% rename from addons/xterm-addon-webgl/src/renderLayer/Types.ts rename to addons/addon-webgl/src/renderLayer/Types.ts index 3dbdfd9c..feeceb14 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/Types.ts +++ b/addons/addon-webgl/src/renderLayer/Types.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IDisposable, Terminal } from 'xterm'; +import { IDisposable, Terminal } from '@xterm/xterm'; import { IRenderDimensions } from 'browser/renderer/shared/Types'; export interface IRenderLayer extends IDisposable { diff --git a/addons/xterm-addon-webgl/src/tsconfig.json b/addons/addon-webgl/src/tsconfig.json similarity index 90% rename from addons/xterm-addon-webgl/src/tsconfig.json rename to addons/addon-webgl/src/tsconfig.json index 4d4194db..924d3762 100644 --- a/addons/xterm-addon-webgl/src/tsconfig.json +++ b/addons/addon-webgl/src/tsconfig.json @@ -17,6 +17,9 @@ ], "browser/*": [ "../../../src/browser/*" + ], + "@xterm/addon-webgl": [ + "../typings/addon-webgl.d.ts" ] }, "strict": true, diff --git a/addons/xterm-addon-webgl/test/WebglRenderer.test.ts b/addons/addon-webgl/test/WebglRenderer.test.ts similarity index 82% rename from addons/xterm-addon-webgl/test/WebglRenderer.test.ts rename to addons/addon-webgl/test/WebglRenderer.test.ts index 718d8e03..4b73c08b 100644 --- a/addons/xterm-addon-webgl/test/WebglRenderer.test.ts +++ b/addons/addon-webgl/test/WebglRenderer.test.ts @@ -4,7 +4,7 @@ */ import test from '@playwright/test'; -import { ISharedRendererTestContext, injectSharedRendererTests } from '../../../out-test/playwright/SharedRendererTests'; +import { ISharedRendererTestContext, injectSharedRendererTests, injectSharedRendererTestsStandalone } from '../../../out-test/playwright/SharedRendererTests'; import { ITestContext, createTestContext, openTerminal } from '../../../out-test/playwright/TestUtils'; import { platform } from 'os'; @@ -29,4 +29,5 @@ test.describe('WebGL Renderer Integration Tests', async () => { } injectSharedRendererTests(ctxWrapper); + injectSharedRendererTestsStandalone(ctxWrapper); }); diff --git a/addons/xterm-addon-webgl/test/playwright.config.ts b/addons/addon-webgl/test/playwright.config.ts similarity index 100% rename from addons/xterm-addon-webgl/test/playwright.config.ts rename to addons/addon-webgl/test/playwright.config.ts diff --git a/addons/xterm-addon-webgl/test/tsconfig.json b/addons/addon-webgl/test/tsconfig.json similarity index 100% rename from addons/xterm-addon-webgl/test/tsconfig.json rename to addons/addon-webgl/test/tsconfig.json diff --git a/addons/xterm-addon-webgl/tsconfig.json b/addons/addon-webgl/tsconfig.json similarity index 100% rename from addons/xterm-addon-webgl/tsconfig.json rename to addons/addon-webgl/tsconfig.json diff --git a/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts b/addons/addon-webgl/typings/addon-webgl.d.ts similarity index 91% rename from addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts rename to addons/addon-webgl/typings/addon-webgl.d.ts index 79108e0d..8c560421 100644 --- a/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts +++ b/addons/addon-webgl/typings/addon-webgl.d.ts @@ -3,9 +3,9 @@ * @license MIT */ -import { Terminal, ITerminalAddon, IEvent } from 'xterm'; +import { Terminal, ITerminalAddon, IEvent } from '@xterm/xterm'; -declare module 'xterm-addon-webgl' { +declare module '@xterm/addon-webgl' { /** * An xterm.js addon that provides search functionality. */ diff --git a/addons/xterm-addon-webgl/webpack.config.js b/addons/addon-webgl/webpack.config.js similarity index 94% rename from addons/xterm-addon-webgl/webpack.config.js rename to addons/addon-webgl/webpack.config.js index 578b1102..f31ffd51 100644 --- a/addons/xterm-addon-webgl/webpack.config.js +++ b/addons/addon-webgl/webpack.config.js @@ -6,7 +6,7 @@ const path = require('path'); const addonName = 'WebglAddon'; -const mainFile = 'xterm-addon-webgl.js'; +const mainFile = 'addon-webgl.js'; module.exports = { entry: `./out/${addonName}.js`, diff --git a/addons/xterm-addon-canvas/README.md b/addons/xterm-addon-canvas/README.md deleted file mode 100644 index bffac96e..00000000 --- a/addons/xterm-addon-canvas/README.md +++ /dev/null @@ -1,28 +0,0 @@ -## xterm-addon-canvas - -An addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables a canvas-based renderer using a 2d context to draw. This addon requires xterm.js v5+. - -The purpose of this addon is to be used as a fallback for the [webgl addon](https://www.npmjs.com/package/xterm-addon-webgl) when better performance is desired over the default DOM renderer, but WebGL2 isn't supported or performant for some reason. - -### Install - -```bash -npm install --save xterm-addon-canvas -``` - -### Usage - -```ts -import { Terminal } from 'xterm'; -import { CanvasAddon } from 'xterm-addon-canvas'; - -const terminal = new Terminal(); -terminal.open(element); -terminal.loadAddon(new CanvasAddon()); -``` - -See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/xterm-addon-canvas/typings/xterm-addon-canvas.d.ts) for more advanced usage. - -### See also - -- [xterm-addon-webgl](https://www.npmjs.com/package/xterm-addon-webgl) A renderer for xterm.js that uses WebGL diff --git a/bin/package_headless.js b/bin/package_headless.js index d002a95e..6f891f05 100644 --- a/bin/package_headless.js +++ b/bin/package_headless.js @@ -14,7 +14,7 @@ console.log('> headless/package.json'); const xtermPackageJson = require('../package.json'); const xtermHeadlessPackageJson = { ...xtermPackageJson, - name: 'xterm-headless', + name: '@xterm/headless', description: 'A headless terminal component that runs in Node.js', main: 'lib-headless/xterm-headless.js', types: 'typings/xterm-headless.d.ts', diff --git a/bin/publish.js b/bin/publish.js index e9c7c3e8..360e09fe 100644 --- a/bin/publish.js +++ b/bin/publish.js @@ -27,16 +27,17 @@ if (changedFiles.some(e => e.search(/^addons\//) === -1)) { // Publish addons if any files were changed inside of the addon const addonPackageDirs = [ - path.resolve(__dirname, '../addons/xterm-addon-attach'), - path.resolve(__dirname, '../addons/xterm-addon-canvas'), - path.resolve(__dirname, '../addons/xterm-addon-fit'), - // path.resolve(__dirname, '../addons/xterm-addon-image'), - path.resolve(__dirname, '../addons/xterm-addon-ligatures'), - path.resolve(__dirname, '../addons/xterm-addon-search'), - path.resolve(__dirname, '../addons/xterm-addon-serialize'), - path.resolve(__dirname, '../addons/xterm-addon-unicode11'), - path.resolve(__dirname, '../addons/xterm-addon-web-links'), - path.resolve(__dirname, '../addons/xterm-addon-webgl') + path.resolve(__dirname, '../addons/addon-attach'), + path.resolve(__dirname, '../addons/addon-canvas'), + path.resolve(__dirname, '../addons/addon-fit'), + path.resolve(__dirname, '../addons/addon-image'), + path.resolve(__dirname, '../addons/addon-ligatures'), + path.resolve(__dirname, '../addons/addon-search'), + path.resolve(__dirname, '../addons/addon-serialize'), + path.resolve(__dirname, '../addons/addon-unicode11'), + // path.resolve(__dirname, '../addons/addon-unicode-graphemes'), + path.resolve(__dirname, '../addons/addon-web-links'), + path.resolve(__dirname, '../addons/addon-webgl') ]; console.log(`Checking if addons need to be published`); for (const p of addonPackageDirs) { @@ -56,8 +57,9 @@ function checkAndPublishPackage(packageDir) { const packageJson = require(path.join(packageDir, 'package.json')); // Determine if this is a stable or beta release - const publishedVersions = getPublishedVersions(packageJson); - const isStableRelease = !publishedVersions.includes(packageJson.version); + // TODO: Uncomment when publishing 5.4 + // const publishedVersions = getPublishedVersions(packageJson); + const isStableRelease = false; //!publishedVersions.includes(packageJson.version); // Get the next version let nextVersion = isStableRelease ? packageJson.version : getNextBetaVersion(packageJson); @@ -70,7 +72,7 @@ function checkAndPublishPackage(packageDir) { fs.writeFileSync(packageJsonFile, JSON.stringify(packageJson, null, 2)); // Publish - const args = ['publish']; + const args = ['publish', '--access', 'public']; if (!isStableRelease) { args.push('--tag', 'beta'); } @@ -94,8 +96,7 @@ function checkAndPublishPackage(packageDir) { function getNextBetaVersion(packageJson) { if (!/^\d+\.\d+\.\d+$/.exec(packageJson.version)) { - console.error('The package.json version must be of the form x.y.z'); - process.exit(1); + throw new Error('The package.json version must be of the form x.y.z'); } const tag = 'beta'; const stableVersion = packageJson.version.split('.'); @@ -113,9 +114,30 @@ function getNextBetaVersion(packageJson) { return `${nextStableVersion}-${tag}.${latestTagVersion + 1}`; } +function asArray(value) { + return Array.isArray(value) ? value : [value]; +} + function getPublishedVersions(packageJson, version, tag) { - const versionsProcess = cp.spawnSync('npm', ['view', packageJson.name, 'versions', '--json']); - const versionsJson = JSON.parse(versionsProcess.stdout); + const versionsProcess = cp.spawnSync(os.platform === 'win32' ? 'npm.cmd' : 'npm', ['view', packageJson.name, 'versions', '--json']); + if (versionsProcess.stdout.length === 0 && versionsProcess.stderr) { + const err = versionsProcess.stderr.toString(); + if (err.indexOf('404 Not Found - GET https://registry.npmjs.org/@xterm') > 0) { + return []; + } + throw new Error('Could not get published versions\n' + err); + } + const output = JSON.parse(versionsProcess.stdout); + if (typeof output === 'object' && !Array.isArray(output)) { + if (output.error?.code === 'E404') { + return []; + } + throw new Error('Could not get published versions\n' + output); + } + if (!output || Array.isArray(output) && output.length === 0) { + return []; + } + const versionsJson = asArray(output); if (tag) { return versionsJson.filter(v => !v.search(new RegExp(`${version}-${tag}.[0-9]+`))); } diff --git a/bin/test_playwright.js b/bin/test_playwright.js index 02c2a763..0cd6f85c 100644 --- a/bin/test_playwright.js +++ b/bin/test_playwright.js @@ -19,8 +19,8 @@ while (argv.some(e => e.startsWith('--suite='))) { let configs = [ { name: 'core', path: 'out-test/playwright/playwright.config.js' }, - { name: 'xterm-addon-canvas', path: 'addons/xterm-addon-canvas/out-test/playwright.config.js' }, - { name: 'xterm-addon-webgl', path: 'addons/xterm-addon-webgl/out-test/playwright.config.js' } + { name: 'addon-canvas', path: 'addons/addon-canvas/out-test/playwright.config.js' }, + { name: 'addon-webgl', path: 'addons/addon-webgl/out-test/playwright.config.js' } ]; if (suiteFilter) { diff --git a/demo/client.ts b/demo/client.ts index 06949a8a..7e0830a5 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -10,39 +10,41 @@ // Use tsc version (yarn watch) import { Terminal } from '../out/browser/public/Terminal'; -import { AttachAddon } from '../addons/xterm-addon-attach/out/AttachAddon'; -import { CanvasAddon } from '../addons/xterm-addon-canvas/out/CanvasAddon'; -import { FitAddon } from '../addons/xterm-addon-fit/out/FitAddon'; -import { SearchAddon, ISearchOptions } from '../addons/xterm-addon-search/out/SearchAddon'; -import { SerializeAddon } from '../addons/xterm-addon-serialize/out/SerializeAddon'; -import { WebLinksAddon } from '../addons/xterm-addon-web-links/out/WebLinksAddon'; -import { WebglAddon } from '../addons/xterm-addon-webgl/out/WebglAddon'; -import { Unicode11Addon } from '../addons/xterm-addon-unicode11/out/Unicode11Addon'; -import { LigaturesAddon } from '../addons/xterm-addon-ligatures/out/LigaturesAddon'; +import { AttachAddon } from '../addons/addon-attach/out/AttachAddon'; +import { CanvasAddon } from '../addons/addon-canvas/out/CanvasAddon'; +import { FitAddon } from '../addons/addon-fit/out/FitAddon'; +import { SearchAddon, ISearchOptions } from '../addons/addon-search/out/SearchAddon'; +import { SerializeAddon } from '../addons/addon-serialize/out/SerializeAddon'; +import { WebLinksAddon } from '../addons/addon-web-links/out/WebLinksAddon'; +import { WebglAddon } from '../addons/addon-webgl/out/WebglAddon'; +import { Unicode11Addon } from '../addons/addon-unicode11/out/Unicode11Addon'; +import { UnicodeGraphemesAddon } from '../addons/addon-unicode-graphemes/out/UnicodeGraphemesAddon'; +import { LigaturesAddon } from '../addons/addon-ligatures/out/LigaturesAddon'; // Playwright/WebKit on Windows does not support WebAssembly https://stackoverflow.com/q/62311688/1156119 -import type { ImageAddonType, IImageAddonOptions } from '../addons/xterm-addon-image/out/ImageAddon'; +import type { ImageAddonType, IImageAddonOptions } from '../addons/addon-image/out/ImageAddon'; let ImageAddon: ImageAddonType | undefined; // eslint-disable-line @typescript-eslint/naming-convention if ('WebAssembly' in window) { - const imageAddon = require('../addons/xterm-addon-image/out/ImageAddon'); + const imageAddon = require('../addons/addon-image/out/ImageAddon'); ImageAddon = imageAddon.ImageAddon; } // Use webpacked version (yarn package) // import { Terminal } from '../lib/xterm'; -// import { AttachAddon } from 'xterm-addon-attach'; -// import { FitAddon } from 'xterm-addon-fit'; -// import { ImageAddon } from 'xterm-addon-image'; -// import { SearchAddon, ISearchOptions } from 'xterm-addon-search'; -// import { SerializeAddon } from 'xterm-addon-serialize'; -// import { WebLinksAddon } from 'xterm-addon-web-links'; -// import { WebglAddon } from 'xterm-addon-webgl'; -// import { Unicode11Addon } from 'xterm-addon-unicode11'; -// import { LigaturesAddon } from 'xterm-addon-ligatures'; +// import { AttachAddon } from '@xterm/addon-attach'; +// import { FitAddon } from '@xterm/addon-fit'; +// import { ImageAddon } from '@xterm/addon-image'; +// import { SearchAddon, ISearchOptions } from '@xterm/addon-search'; +// import { SerializeAddon } from '@xterm/addon-serialize'; +// import { WebLinksAddon } from '@xterm/addon-web-links'; +// import { WebglAddon } from '@@xterm/addon-webgl'; +// import { Unicode11Addon } from '@xterm/addon-unicode11'; +// import { UnicodeGraphemesAddon } from '@xterm/addon-unicode-graphemes'; +// 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 -import { Terminal as TerminalType, ITerminalOptions } from 'xterm'; +import { Terminal as TerminalType, ITerminalOptions } from '@xterm/xterm'; export interface IWindowWithTerminal extends Window { term: TerminalType; @@ -56,6 +58,7 @@ export interface IWindowWithTerminal extends Window { WebLinksAddon?: typeof WebLinksAddon; // eslint-disable-line @typescript-eslint/naming-convention WebglAddon?: typeof WebglAddon; // eslint-disable-line @typescript-eslint/naming-convention Unicode11Addon?: typeof Unicode11Addon; // eslint-disable-line @typescript-eslint/naming-convention + UnicodeGraphemesAddon?: typeof UnicodeGraphemesAddon; // eslint-disable-line @typescript-eslint/naming-convention LigaturesAddon?: typeof LigaturesAddon; // eslint-disable-line @typescript-eslint/naming-convention } declare let window: IWindowWithTerminal; @@ -67,7 +70,7 @@ let socket; let pid; let autoResize: boolean = true; -type AddonType = 'attach' | 'canvas' | 'fit' | 'image' | 'search' | 'serialize' | 'unicode11' | 'webLinks' | 'webgl' | 'ligatures'; +type AddonType = 'attach' | 'canvas' | 'fit' | 'image' | 'search' | 'serialize' | 'unicode11' | 'unicodeGraphemes' | 'webLinks' | 'webgl' | 'ligatures'; interface IDemoAddon { name: T; @@ -81,8 +84,9 @@ interface IDemoAddon { T extends 'serialize' ? typeof SerializeAddon : T extends 'webLinks' ? typeof WebLinksAddon : T extends 'unicode11' ? typeof Unicode11Addon : - T extends 'ligatures' ? typeof LigaturesAddon : - typeof WebglAddon + T extends 'unicodeGraphemes' ? typeof UnicodeGraphemesAddon : + T extends 'ligatures' ? typeof LigaturesAddon : + typeof WebglAddon ); instance?: ( T extends 'attach' ? AttachAddon : @@ -94,8 +98,9 @@ interface IDemoAddon { T extends 'webLinks' ? WebLinksAddon : T extends 'webgl' ? WebglAddon : T extends 'unicode11' ? typeof Unicode11Addon : - T extends 'ligatures' ? typeof LigaturesAddon : - never + T extends 'unicodeGraphemes' ? typeof UnicodeGraphemesAddon : + T extends 'ligatures' ? typeof LigaturesAddon : + never ); } @@ -109,6 +114,7 @@ const addons: { [T in AddonType]: IDemoAddon } = { webLinks: { name: 'webLinks', ctor: WebLinksAddon, canChange: true }, webgl: { name: 'webgl', ctor: WebglAddon, canChange: true }, unicode11: { name: 'unicode11', ctor: Unicode11Addon, canChange: true }, + unicodeGraphemes: { name: 'unicodeGraphemes', ctor: UnicodeGraphemesAddon, canChange: true }, ligatures: { name: 'ligatures', ctor: LigaturesAddon, canChange: true } }; @@ -178,6 +184,7 @@ const disposeRecreateButtonHandler: () => void = () => { addons.search.instance = undefined; addons.serialize.instance = undefined; addons.unicode11.instance = undefined; + addons.unicodeGraphemes.instance = undefined; addons.ligatures.instance = undefined; addons.webLinks.instance = undefined; addons.webgl.instance = undefined; @@ -226,6 +233,7 @@ if (document.location.pathname === '/test') { window.SearchAddon = SearchAddon; window.SerializeAddon = SerializeAddon; window.Unicode11Addon = Unicode11Addon; + window.UnicodeGraphemesAddon = UnicodeGraphemesAddon; window.LigaturesAddon = LigaturesAddon; window.WebLinksAddon = WebLinksAddon; window.WebglAddon = WebglAddon; @@ -244,6 +252,7 @@ if (document.location.pathname === '/test') { document.getElementById('ansi-colors').addEventListener('click', ansiColorsTest); document.getElementById('osc-hyperlinks').addEventListener('click', addAnsiHyperlink); document.getElementById('sgr-test').addEventListener('click', sgrTest); + document.getElementById('add-grapheme-clusters').addEventListener('click', addGraphemeClusters); document.getElementById('add-decoration').addEventListener('click', addDecoration); document.getElementById('add-overview-ruler').addEventListener('click', addOverviewRuler); document.getElementById('weblinks-test').addEventListener('click', testWeblinks); @@ -277,7 +286,7 @@ function createTerminal(): void { addons.serialize.instance = new SerializeAddon(); addons.fit.instance = new FitAddon(); addons.image.instance = new ImageAddon(); - addons.unicode11.instance = new Unicode11Addon(); + addons.unicodeGraphemes.instance = new UnicodeGraphemesAddon(); try { // try to start with webgl renderer (might throw on older safari/webkit) addons.webgl.instance = new WebglAddon(); } catch (e) { @@ -288,7 +297,7 @@ function createTerminal(): void { typedTerm.loadAddon(addons.image.instance); typedTerm.loadAddon(addons.search.instance); typedTerm.loadAddon(addons.serialize.instance); - typedTerm.loadAddon(addons.unicode11.instance); + typedTerm.loadAddon(addons.unicodeGraphemes.instance); typedTerm.loadAddon(addons.webLinks.instance); window.term = term; // Expose `term` to window for debugging purposes @@ -451,9 +460,7 @@ function initOptions(term: TerminalType): void { }; const options = Object.getOwnPropertyNames(term.options); const booleanOptions = []; - const numberOptions = [ - 'overviewRulerWidth' - ]; + const numberOptions = []; options.filter(o => blacklistedOptions.indexOf(o) === -1).forEach(o => { switch (typeof term.options[o]) { case 'boolean': @@ -613,6 +620,9 @@ function initAddons(term: TerminalType): void { if (name === 'unicode11' && checkbox.checked) { term.unicode.activeVersion = '11'; } + if (name === 'unicodeGraphemes' && checkbox.checked) { + term.unicode.activeVersion = '15-graphemes'; + } if (name === 'search' && checkbox.checked) { addon.instance.onDidChangeResults(e => updateFindResults(e)); } @@ -648,6 +658,8 @@ function initAddons(term: TerminalType): void { }, 0); } else if (name === 'unicode11') { term.unicode.activeVersion = '11'; + } else if (name === 'unicodeGraphemes') { + term.unicode.activeVersion = '15-graphemes'; } else if (name === 'search') { addon.instance.onDidChangeResults(e => updateFindResults(e)); } @@ -662,7 +674,7 @@ function initAddons(term: TerminalType): void { addons.webgl.instance.textureAtlas.remove(); } else if (name === 'canvas') { addons.canvas.instance.textureAtlas.remove(); - } else if (name === 'unicode11') { + } else if (name === 'unicode11' || name === 'unicodeGraphemes') { term.unicode.activeVersion = '6'; } addon.instance!.dispose(); @@ -1113,6 +1125,24 @@ function getRandomSgr(): string { return randomSgrAttributes[Math.floor(Math.random() * randomSgrAttributes.length)]; } +function addGraphemeClusters(): void { + term.write('\n\n\r'); + term.writeln('🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣 [Simple emoji v6: 10 cells, v15: 20 cells]'); + term.writeln('\u{1F476}\u{1F3FF}\u{1F476} [baby with emoji modifier fitzpatrick type-6; baby]'); + term.writeln('\u{1F469}\u200d\u{1f469}\u200d\u{1f466} [woman+zwj+woman+zwj+boy]'); + term.writeln('\u{1F64B}\u{1F64B}\u{200D}\u{2642}\u{FE0F} [person/man raising hand]'); + term.writeln('\u{1F3CB}\u{FE0F}=\u{1F3CB}\u{1F3FE}\u{200D}\u{2640}\u{FE0F} [person lifting weights emoji; woman lighting weights, medium dark]'); + term.writeln('\u{1F469}\u{1F469}\u{200D}\u{1F393}\u{1F468}\u{1F3FF}\u{200D}\u{1F393} [woman; woman student; man student dark]'); + term.writeln('\u{1f1f3}\u{1f1f4}_ [REGIONAL INDICATOR SYMBOL LETTER N and RI O]'); + term.writeln('\u{1f1f3}_\u{1f1f4} {RI N; underscore; RI O]'); + term.writeln('\u0061\u0301 [letter a with acute accent]'); + term.writeln('\u1100\u1161\u11A8=\u1100\u1161= [Korean Jamo]'); + term.writeln('\uAC00=\uD685= [Hangul syllables (pre-composed)]'); + term.writeln('(\u26b0\ufe0e) [coffin with text_presentation]'); + term.writeln('(\u26b0\ufe0f) [coffin with Emoji_presentation]'); + term.writeln(' [Égalité (using separate acute) emoij_presentation]'); +} + function addDecoration(): void { term.options['overviewRulerWidth'] = 15; const marker = term.registerMarker(1); diff --git a/demo/index.html b/demo/index.html index 7d7329a7..caff2ca2 100644 --- a/demo/index.html +++ b/demo/index.html @@ -97,6 +97,7 @@
+
Decorations
diff --git a/demo/tsconfig.json b/demo/tsconfig.json index f728f20e..f0aebb1f 100644 --- a/demo/tsconfig.json +++ b/demo/tsconfig.json @@ -6,13 +6,13 @@ "sourceMap": true, "baseUrl": ".", "paths": { - "xterm-addon-attach": ["../addons/xterm-addon-attach"], - "xterm-addon-fit": ["../addons/xterm-addon-fit"], - "xterm-addon-image": ["../addons/xterm-addon-image"], - "xterm-addon-search": ["../addons/xterm-addon-search"], - "xterm-addon-serialize": ["../addons/xterm-addon-serialize"], - "xterm-addon-web-links": ["../addons/xterm-addon-web-links"], - "xterm-addon-webgl": ["../addons/xterm-addon-webgl"] + "addon-attach": ["../addons/addon-attach"], + "addon-fit": ["../addons/addon-fit"], + "addon-image": ["../addons/addon-image"], + "addon-search": ["../addons/addon-search"], + "addon-serialize": ["../addons/addon-serialize"], + "addon-web-links": ["../addons/addon-web-links"], + "addon-webgl": ["../addons/addon-webgl"] } }, "include": [ diff --git a/headless/README.md b/headless/README.md index 1d2d6156..205fd79c 100644 --- a/headless/README.md +++ b/headless/README.md @@ -15,7 +15,7 @@ npm install xterm-headless Then import as you would a regular node package. The recommended way to load `xterm-headless` is with TypeScript and the ES6 module syntax: ```javascript -import { Terminal } from 'xterm-headless'; +import { Terminal } from '@xterm/headless'; ``` ## API diff --git a/package.json b/package.json index 4915cf3e..d0510e5d 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "xterm", + "name": "@xterm/xterm", "description": "Full xterm terminal, in your browser", "version": "5.3.0", "main": "lib/xterm.js", @@ -86,7 +86,7 @@ "jsdom": "^18.0.1", "mocha": "^10.1.0", "mustache": "^4.2.0", - "node-pty": "^0.10.1", + "node-pty": "1.1.0-beta5", "nyc": "^15.1.0", "source-map-loader": "^3.0.0", "source-map-support": "^0.5.20", diff --git a/src/browser/AccessibilityManager.ts b/src/browser/AccessibilityManager.ts index 60ba9ffc..5f529589 100644 --- a/src/browser/AccessibilityManager.ts +++ b/src/browser/AccessibilityManager.ts @@ -5,13 +5,11 @@ import * as Strings from 'browser/LocalizableStrings'; import { ITerminal, IRenderDebouncer } from 'browser/Types'; -import { isMac } from 'common/Platform'; import { TimeBasedDebouncer } from 'browser/TimeBasedDebouncer'; import { Disposable, toDisposable } from 'common/Lifecycle'; -import { ScreenDprMonitor } from 'browser/ScreenDprMonitor'; -import { IRenderService } from 'browser/services/Services'; -import { addDisposableDomListener } from 'browser/Lifecycle'; +import { ICoreBrowserService, IRenderService } from 'browser/services/Services'; import { IBuffer } from 'common/buffer/Types'; +import { IInstantiationService } from 'common/services/Services'; const MAX_ROWS_TO_READ = 20; @@ -30,8 +28,6 @@ export class AccessibilityManager extends Disposable { private _liveRegionLineCount: number = 0; private _liveRegionDebouncer: IRenderDebouncer; - private _screenDprMonitor: ScreenDprMonitor; - private _topBoundaryFocusListener: (e: FocusEvent) => void; private _bottomBoundaryFocusListener: (e: FocusEvent) => void; @@ -50,13 +46,15 @@ export class AccessibilityManager extends Disposable { constructor( private readonly _terminal: ITerminal, + @IInstantiationService instantiationService: IInstantiationService, + @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService, @IRenderService private readonly _renderService: IRenderService ) { super(); - this._accessibilityContainer = document.createElement('div'); + this._accessibilityContainer = this._coreBrowserService.mainDocument.createElement('div'); this._accessibilityContainer.classList.add('xterm-accessibility'); - this._rowContainer = document.createElement('div'); + this._rowContainer = this._coreBrowserService.mainDocument.createElement('div'); this._rowContainer.setAttribute('role', 'list'); this._rowContainer.classList.add('xterm-accessibility-tree'); this._rowElements = []; @@ -73,7 +71,7 @@ export class AccessibilityManager extends Disposable { this._refreshRowsDimensions(); this._accessibilityContainer.appendChild(this._rowContainer); - this._liveRegion = document.createElement('div'); + this._liveRegion = this._coreBrowserService.mainDocument.createElement('div'); this._liveRegion.classList.add('live-region'); this._liveRegion.setAttribute('aria-live', 'assertive'); this._accessibilityContainer.appendChild(this._liveRegion); @@ -94,13 +92,7 @@ export class AccessibilityManager extends Disposable { this.register(this._terminal.onKey(e => this._handleKey(e.key))); this.register(this._terminal.onBlur(() => this._clearLiveRegion())); this.register(this._renderService.onDimensionsChange(() => this._refreshRowsDimensions())); - - this._screenDprMonitor = new ScreenDprMonitor(window); - this.register(this._screenDprMonitor); - this._screenDprMonitor.setListener(() => this._refreshRowsDimensions()); - // This shouldn't be needed on modern browsers but is present in case the - // media query that drives the ScreenDprMonitor isn't supported - this.register(addDisposableDomListener(window, 'resize', () => this._refreshRowsDimensions())); + this.register(this._coreBrowserService.onDprChange(() => this._refreshRowsDimensions())); this._refreshRows(); this.register(toDisposable(() => { @@ -133,26 +125,12 @@ export class AccessibilityManager extends Disposable { this._liveRegion.textContent += Strings.tooMuchOutput; } } - - // Only detach/attach on mac as otherwise messages can go unaccounced - if (isMac) { - if (this._liveRegion.textContent && this._liveRegion.textContent.length > 0 && !this._liveRegion.parentNode) { - setTimeout(() => { - this._accessibilityContainer.appendChild(this._liveRegion); - }, 0); - } - } } } private _clearLiveRegion(): void { this._liveRegion.textContent = ''; this._liveRegionLineCount = 0; - - // Only detach/attach on mac as otherwise messages can go unaccounced - if (isMac) { - this._liveRegion.remove(); - } } private _handleKey(keyChar: string): void { @@ -276,7 +254,7 @@ export class AccessibilityManager extends Disposable { } private _createAccessibilityTreeNode(): HTMLElement { - const element = document.createElement('div'); + const element = this._coreBrowserService.mainDocument.createElement('div'); element.setAttribute('role', 'listitem'); element.tabIndex = -1; this._refreshRowDimensions(element); diff --git a/src/browser/ScreenDprMonitor.ts b/src/browser/ScreenDprMonitor.ts deleted file mode 100644 index 1c3f31b7..00000000 --- a/src/browser/ScreenDprMonitor.ts +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { Disposable, toDisposable } from 'common/Lifecycle'; - -export type ScreenDprListener = (newDevicePixelRatio?: number, oldDevicePixelRatio?: number) => void; - -/** - * The screen device pixel ratio monitor allows listening for when the - * window.devicePixelRatio value changes. This is done not with polling but with - * the use of window.matchMedia to watch media queries. When the event fires, - * the listener will be reattached using a different media query to ensure that - * any further changes will register. - * - * The listener should fire on both window zoom changes and switching to a - * monitor with a different DPI. - */ -export class ScreenDprMonitor extends Disposable { - private _currentDevicePixelRatio: number; - private _outerListener: ((this: MediaQueryList, ev: MediaQueryListEvent) => any) | undefined; - private _listener: ScreenDprListener | undefined; - private _resolutionMediaMatchList: MediaQueryList | undefined; - - constructor(private _parentWindow: Window) { - super(); - this._currentDevicePixelRatio = this._parentWindow.devicePixelRatio; - this.register(toDisposable(() => { - this.clearListener(); - })); - } - - public setListener(listener: ScreenDprListener): void { - if (this._listener) { - this.clearListener(); - } - this._listener = listener; - this._outerListener = () => { - if (!this._listener) { - return; - } - this._listener(this._parentWindow.devicePixelRatio, this._currentDevicePixelRatio); - this._updateDpr(); - }; - this._updateDpr(); - } - - private _updateDpr(): void { - if (!this._outerListener) { - return; - } - - // Clear listeners for old DPR - this._resolutionMediaMatchList?.removeListener(this._outerListener); - - // Add listeners for new DPR - this._currentDevicePixelRatio = this._parentWindow.devicePixelRatio; - this._resolutionMediaMatchList = this._parentWindow.matchMedia(`screen and (resolution: ${this._parentWindow.devicePixelRatio}dppx)`); - this._resolutionMediaMatchList.addListener(this._outerListener); - } - - public clearListener(): void { - if (!this._resolutionMediaMatchList || !this._listener || !this._outerListener) { - return; - } - this._resolutionMediaMatchList.removeListener(this._outerListener); - this._resolutionMediaMatchList = undefined; - this._listener = undefined; - this._outerListener = undefined; - } -} diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 131774f1..fad2d80b 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -54,13 +54,10 @@ import { evaluateKeyboardEvent } from 'common/input/Keyboard'; import { toRgbString } from 'common/input/XParseColor'; import { DecorationService } from 'common/services/DecorationService'; import { IDecorationService } from 'common/services/Services'; -import { IDecoration, IDecorationOptions, IDisposable, ILinkProvider, IMarker } from 'xterm'; +import { IDecoration, IDecorationOptions, IDisposable, ILinkProvider, IMarker } from '@xterm/xterm'; import { WindowsOptionsReportType } from '../common/InputHandler'; import { AccessibilityManager } from './AccessibilityManager'; -// Let it work inside Node.js for automated testing purposes. -const document: Document = (typeof window !== 'undefined') ? window.document : null as any; - export class Terminal extends CoreTerminal implements ITerminal { public textarea: HTMLTextAreaElement | undefined; public element: HTMLElement | undefined; @@ -397,7 +394,19 @@ export class Terminal extends CoreTerminal implements ITerminal { this._logService.debug('Terminal.open was called on an element that was not attached to the DOM'); } - this._document = parent.ownerDocument!; + // If the terminal is already opened + if (this.element?.ownerDocument.defaultView && this._coreBrowserService) { + // Adjust the window if needed + if (this.element.ownerDocument.defaultView !== this._coreBrowserService.window) { + this._coreBrowserService.window = this.element.ownerDocument.defaultView; + } + return; + } + + this._document = parent.ownerDocument; + if (this.options.documentOverride && this.options.documentOverride instanceof Document) { + this._document = this.optionsService.rawOptions.documentOverride as Document; + } // Create main element container this.element = this._document.createElement('div'); @@ -408,25 +417,25 @@ export class Terminal extends CoreTerminal implements ITerminal { // Performance: Use a document fragment to build the terminal // viewport and helper elements detached from the DOM - const fragment = document.createDocumentFragment(); - this._viewportElement = document.createElement('div'); + const fragment = this._document.createDocumentFragment(); + this._viewportElement = this._document.createElement('div'); this._viewportElement.classList.add('xterm-viewport'); fragment.appendChild(this._viewportElement); - this._viewportScrollArea = document.createElement('div'); + this._viewportScrollArea = this._document.createElement('div'); this._viewportScrollArea.classList.add('xterm-scroll-area'); this._viewportElement.appendChild(this._viewportScrollArea); - this.screenElement = document.createElement('div'); + this.screenElement = this._document.createElement('div'); this.screenElement.classList.add('xterm-screen'); // Create the container that will hold helpers like the textarea for // capturing DOM Events. Then produce the helpers. - this._helperContainer = document.createElement('div'); + this._helperContainer = this._document.createElement('div'); this._helperContainer.classList.add('xterm-helpers'); this.screenElement.appendChild(this._helperContainer); fragment.appendChild(this.screenElement); - this.textarea = document.createElement('textarea'); + this.textarea = this._document.createElement('textarea'); this.textarea.classList.add('xterm-helper-textarea'); this.textarea.setAttribute('aria-label', Strings.promptLabel); if (!Browser.isChromeOS) { @@ -441,7 +450,12 @@ export class Terminal extends CoreTerminal implements ITerminal { // Register the core browser service before the generic textarea handlers are registered so it // handles them first. Otherwise the renderers may use the wrong focus state. - this._coreBrowserService = this._instantiationService.createInstance(CoreBrowserService, this.textarea, this._document.defaultView ?? window); + this._coreBrowserService = this.register(this._instantiationService.createInstance(CoreBrowserService, + this.textarea, + parent.ownerDocument.defaultView ?? window, + // Force unsafe null in node.js environment for tests + this._document ?? (typeof window !== 'undefined') ? window.document : null as any + )); this._instantiationService.setService(ICoreBrowserService, this._coreBrowserService); this.register(addDisposableDomListener(this.textarea, 'focus', (ev: KeyboardEvent) => this._handleTextAreaFocus(ev))); @@ -463,7 +477,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(this._renderService.onRenderedViewportChange(e => this._onRender.fire(e))); this.onResize(e => this._renderService!.resize(e.cols, e.rows)); - this._compositionView = document.createElement('div'); + this._compositionView = this._document.createElement('div'); this._compositionView.classList.add('composition-view'); this._compositionHelper = this._instantiationService.createInstance(CompositionHelper, this.textarea, this._compositionView); this._helperContainer.appendChild(this._compositionView); @@ -561,7 +575,7 @@ export class Terminal extends CoreTerminal implements ITerminal { } private _createRenderer(): IRenderer { - return this._instantiationService.createInstance(DomRenderer, this.element!, this.screenElement!, this._viewportElement!, this.linkifier2); + return this._instantiationService.createInstance(DomRenderer, this, this._document!, this.element!, this.screenElement!, this._viewportElement!, this._helperContainer!, this.linkifier2); } /** diff --git a/src/browser/Terminal2.test.ts b/src/browser/Terminal2.test.ts index 4965e8f3..abbb42d3 100644 --- a/src/browser/Terminal2.test.ts +++ b/src/browser/Terminal2.test.ts @@ -9,7 +9,7 @@ import * as os from 'os'; import * as fs from 'fs'; import * as pty from 'node-pty'; import { Terminal } from 'browser/Terminal'; -import { IDisposable } from 'xterm'; +import { IDisposable } from '@xterm/xterm'; // all test files expect terminal in 80x25 const COLS = 80; diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index 7b464a33..7e43017a 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IDisposable, IMarker, ILinkProvider, IDecorationOptions, IDecoration } from 'xterm'; +import { IDisposable, IMarker, ILinkProvider, IDecorationOptions, IDecoration } from '@xterm/xterm'; import { IEvent, EventEmitter } from 'common/EventEmitter'; import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IMouseService, IRenderService, ISelectionService, IThemeService } from 'browser/services/Services'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types'; @@ -350,11 +350,16 @@ export class MockCompositionHelper implements ICompositionHelper { } export class MockCoreBrowserService implements ICoreBrowserService { + public onDprChange = new EventEmitter().event; + public onWindowChange = new EventEmitter().event; public serviceBrand: undefined; public isFocused: boolean = true; public get window(): Window & typeof globalThis { throw Error('Window object not available in tests'); } + public get mainDocument(): Document { + throw Error('Document object not available in tests'); + } public dpr: number = 1; } diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index f2b5e220..b1f31b20 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -6,7 +6,7 @@ import { IEvent } from 'common/EventEmitter'; import { CharData, IColor, ICoreTerminal, ITerminalOptions } from 'common/Types'; import { IBuffer } from 'common/buffer/Types'; -import { IDisposable, Terminal as ITerminalApi } from 'xterm'; +import { IDisposable, Terminal as ITerminalApi } from '@xterm/xterm'; import { IMouseService, IRenderService } from './services/Services'; /** diff --git a/src/browser/decorations/BufferDecorationRenderer.ts b/src/browser/decorations/BufferDecorationRenderer.ts index ba4f6ca8..6ba9ae1e 100644 --- a/src/browser/decorations/BufferDecorationRenderer.ts +++ b/src/browser/decorations/BufferDecorationRenderer.ts @@ -3,8 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { addDisposableDomListener } from 'browser/Lifecycle'; -import { IRenderService } from 'browser/services/Services'; +import { ICoreBrowserService, IRenderService } from 'browser/services/Services'; import { Disposable, toDisposable } from 'common/Lifecycle'; import { IBufferService, IDecorationService, IInternalDecoration } from 'common/services/Services'; @@ -19,6 +18,7 @@ export class BufferDecorationRenderer extends Disposable { constructor( private readonly _screenElement: HTMLElement, @IBufferService private readonly _bufferService: IBufferService, + @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService, @IDecorationService private readonly _decorationService: IDecorationService, @IRenderService private readonly _renderService: IRenderService ) { @@ -33,7 +33,7 @@ export class BufferDecorationRenderer extends Disposable { this._dimensionsChanged = true; this._queueRefresh(); })); - this.register(addDisposableDomListener(window, 'resize', () => this._queueRefresh())); + this.register(this._coreBrowserService.onDprChange(() => this._queueRefresh())); this.register(this._bufferService.buffers.onBufferActivate(() => { this._altBufferIsActive = this._bufferService.buffer === this._bufferService.buffers.alt; })); @@ -70,7 +70,7 @@ export class BufferDecorationRenderer extends Disposable { } private _createElement(decoration: IInternalDecoration): HTMLElement { - const element = document.createElement('div'); + const element = this._coreBrowserService.mainDocument.createElement('div'); element.classList.add('xterm-decoration'); element.classList.toggle('xterm-decoration-top-layer', decoration?.options?.layer === 'top'); element.style.width = `${Math.round((decoration.options.width || 1) * this._renderService.dimensions.css.cell.width)}px`; diff --git a/src/browser/decorations/OverviewRulerRenderer.ts b/src/browser/decorations/OverviewRulerRenderer.ts index d5765385..103d5d9a 100644 --- a/src/browser/decorations/OverviewRulerRenderer.ts +++ b/src/browser/decorations/OverviewRulerRenderer.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { ColorZoneStore, IColorZone, IColorZoneStore } from 'browser/decorations/ColorZoneStore'; -import { addDisposableDomListener } from 'browser/Lifecycle'; import { ICoreBrowserService, IRenderService } from 'browser/services/Services'; import { Disposable, toDisposable } from 'common/Lifecycle'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; @@ -52,10 +51,10 @@ export class OverviewRulerRenderer extends Disposable { @IDecorationService private readonly _decorationService: IDecorationService, @IRenderService private readonly _renderService: IRenderService, @IOptionsService private readonly _optionsService: IOptionsService, - @ICoreBrowserService private readonly _coreBrowseService: ICoreBrowserService + @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService ) { super(); - this._canvas = document.createElement('canvas'); + this._canvas = this._coreBrowserService.mainDocument.createElement('canvas'); this._canvas.classList.add('xterm-decoration-overview-ruler'); this._refreshCanvasDimensions(); this._viewportElement.parentElement?.insertBefore(this._canvas, this._viewportElement); @@ -112,7 +111,7 @@ export class OverviewRulerRenderer extends Disposable { // overview ruler width changed this.register(this._optionsService.onSpecificOptionChange('overviewRulerWidth', () => this._queueRefresh(true))); // device pixel ratio changed - this.register(addDisposableDomListener(this._coreBrowseService.window, 'resize', () => this._queueRefresh(true))); + this.register(this._coreBrowserService.onDprChange(() => this._queueRefresh(true))); // set the canvas dimensions this._queueRefresh(true); } @@ -135,11 +134,11 @@ export class OverviewRulerRenderer extends Disposable { } private _refreshDrawHeightConstants(): void { - drawHeight.full = Math.round(2 * this._coreBrowseService.dpr); + drawHeight.full = Math.round(2 * this._coreBrowserService.dpr); // Calculate actual pixels per line const pixelsPerLine = this._canvas.height / this._bufferService.buffer.lines.length; // Clamp actual pixels within a range - const nonFullHeight = Math.round(Math.max(Math.min(pixelsPerLine, 12), 6) * this._coreBrowseService.dpr); + const nonFullHeight = Math.round(Math.max(Math.min(pixelsPerLine, 12), 6) * this._coreBrowserService.dpr); drawHeight.left = nonFullHeight; drawHeight.center = nonFullHeight; drawHeight.right = nonFullHeight; @@ -157,9 +156,9 @@ export class OverviewRulerRenderer extends Disposable { private _refreshCanvasDimensions(): void { this._canvas.style.width = `${this._width}px`; - this._canvas.width = Math.round(this._width * this._coreBrowseService.dpr); + this._canvas.width = Math.round(this._width * this._coreBrowserService.dpr); this._canvas.style.height = `${this._screenElement.clientHeight}px`; - this._canvas.height = Math.round(this._screenElement.clientHeight * this._coreBrowseService.dpr); + this._canvas.height = Math.round(this._screenElement.clientHeight * this._coreBrowserService.dpr); this._refreshDrawConstants(); this._refreshColorZonePadding(); } @@ -211,7 +210,7 @@ export class OverviewRulerRenderer extends Disposable { if (this._animationFrame !== undefined) { return; } - this._animationFrame = this._coreBrowseService.window.requestAnimationFrame(() => { + this._animationFrame = this._coreBrowserService.window.requestAnimationFrame(() => { this._refreshDecorations(); this._animationFrame = undefined; }); diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index 2c75d7b8..6f009f74 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -13,7 +13,7 @@ import { AddonManager } from 'common/public/AddonManager'; import { BufferNamespaceApi } from 'common/public/BufferNamespaceApi'; import { ParserApi } from 'common/public/ParserApi'; import { UnicodeApi } from 'common/public/UnicodeApi'; -import { IBufferNamespace as IBufferNamespaceApi, IDecoration, IDecorationOptions, IDisposable, ILinkProvider, ILocalizableStrings, IMarker, IModes, IParser, ITerminalAddon, Terminal as ITerminalApi, ITerminalInitOnlyOptions, IUnicodeHandling } from 'xterm'; +import { IBufferNamespace as IBufferNamespaceApi, IDecoration, IDecorationOptions, IDisposable, ILinkProvider, ILocalizableStrings, IMarker, IModes, IParser, ITerminalAddon, Terminal as ITerminalApi, ITerminalInitOnlyOptions, IUnicodeHandling } from '@xterm/xterm'; /** * The set of options that only have an effect when set in the Terminal constructor. diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 5f276954..1549b130 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -7,9 +7,10 @@ import { DomRendererRowFactory, RowCss } from 'browser/renderer/dom/DomRendererR import { WidthCache } from 'browser/renderer/dom/WidthCache'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants'; import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils'; -import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types'; +import { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRenderModel'; +import { IRenderDimensions, IRenderer, IRequestRedrawEvent, ISelectionRenderModel } from 'browser/renderer/shared/Types'; import { ICharSizeService, ICoreBrowserService, IThemeService } from 'browser/services/Services'; -import { ILinkifier2, ILinkifierEvent, ReadonlyColorSet } from 'browser/Types'; +import { ILinkifier2, ILinkifierEvent, ITerminal, ReadonlyColorSet } from 'browser/Types'; import { color } from 'common/Color'; import { EventEmitter } from 'common/EventEmitter'; import { Disposable, toDisposable } from 'common/Lifecycle'; @@ -25,7 +26,6 @@ const SELECTION_CLASS = 'xterm-selection'; let nextTerminalId = 1; - /** * A fallback renderer for when canvas is slow. This is not meant to be * particularly fast or feature complete, more just stable and usable for when @@ -41,15 +41,19 @@ export class DomRenderer extends Disposable implements IRenderer { private _rowElements: HTMLElement[] = []; private _selectionContainer: HTMLElement; private _widthCache: WidthCache; + private _selectionRenderModel: ISelectionRenderModel = createSelectionRenderModel(); public dimensions: IRenderDimensions; public readonly onRequestRedraw = this.register(new EventEmitter()).event; constructor( + private readonly _terminal: ITerminal, + private readonly _document: Document, private readonly _element: HTMLElement, private readonly _screenElement: HTMLElement, private readonly _viewportElement: HTMLElement, + private readonly _helperContainer: HTMLElement, private readonly _linkifier2: ILinkifier2, @IInstantiationService instantiationService: IInstantiationService, @ICharSizeService private readonly _charSizeService: ICharSizeService, @@ -59,12 +63,12 @@ export class DomRenderer extends Disposable implements IRenderer { @IThemeService private readonly _themeService: IThemeService ) { super(); - this._rowContainer = document.createElement('div'); + this._rowContainer = this._document.createElement('div'); this._rowContainer.classList.add(ROW_CONTAINER_CLASS); this._rowContainer.style.lineHeight = 'normal'; this._rowContainer.setAttribute('aria-hidden', 'true'); this._refreshRowElements(this._bufferService.cols, this._bufferService.rows); - this._selectionContainer = document.createElement('div'); + this._selectionContainer = this._document.createElement('div'); this._selectionContainer.classList.add(SELECTION_CLASS); this._selectionContainer.setAttribute('aria-hidden', 'true'); @@ -96,7 +100,7 @@ export class DomRenderer extends Disposable implements IRenderer { this._dimensionsStyleElement.remove(); })); - this._widthCache = new WidthCache(document); + this._widthCache = new WidthCache(this._document, this._helperContainer); this._widthCache.setFont( this._optionsService.rawOptions.fontFamily, this._optionsService.rawOptions.fontSize, @@ -130,7 +134,7 @@ export class DomRenderer extends Disposable implements IRenderer { } if (!this._dimensionsStyleElement) { - this._dimensionsStyleElement = document.createElement('style'); + this._dimensionsStyleElement = this._document.createElement('style'); this._screenElement.appendChild(this._dimensionsStyleElement); } @@ -150,7 +154,7 @@ export class DomRenderer extends Disposable implements IRenderer { private _injectCss(colors: ReadonlyColorSet): void { if (!this._themeStyleElement) { - this._themeStyleElement = document.createElement('style'); + this._themeStyleElement = this._document.createElement('style'); this._screenElement.appendChild(this._themeStyleElement); } @@ -276,7 +280,7 @@ export class DomRenderer extends Disposable implements IRenderer { private _refreshRowElements(cols: number, rows: number): void { // Add missing elements for (let i = this._rowElements.length; i <= rows; i++) { - const row = document.createElement('div'); + const row = this._document.createElement('div'); this._rowContainer.appendChild(row); this._rowElements.push(row); } @@ -289,6 +293,7 @@ export class DomRenderer extends Disposable implements IRenderer { public handleResize(cols: number, rows: number): void { this._refreshRowElements(cols, rows); this._updateDimensions(); + this.handleSelectionChanged(this._selectionRenderModel.selectionStart, this._selectionRenderModel.selectionEnd, this._selectionRenderModel.columnSelectMode); } public handleCharSizeChanged(): void { @@ -318,11 +323,13 @@ export class DomRenderer extends Disposable implements IRenderer { return; } + this._selectionRenderModel.update(this._terminal, start, end, columnSelectMode); + // Translate from buffer position to viewport position - const viewportStartRow = start[1] - this._bufferService.buffer.ydisp; - const viewportEndRow = end[1] - this._bufferService.buffer.ydisp; - const viewportCappedStartRow = Math.max(viewportStartRow, 0); - const viewportCappedEndRow = Math.min(viewportEndRow, this._bufferService.rows - 1); + const viewportStartRow = this._selectionRenderModel.viewportStartRow; + const viewportEndRow = this._selectionRenderModel.viewportEndRow; + const viewportCappedStartRow = this._selectionRenderModel.viewportCappedStartRow; + const viewportCappedEndRow = this._selectionRenderModel.viewportCappedEndRow; // No need to draw the selection if (viewportCappedStartRow >= this._bufferService.rows || viewportCappedEndRow < 0) { @@ -330,7 +337,7 @@ export class DomRenderer extends Disposable implements IRenderer { } // Create the selections - const documentFragment = document.createDocumentFragment(); + const documentFragment = this._document.createDocumentFragment(); if (columnSelectMode) { const isXFlipped = start[0] > end[0]; @@ -362,11 +369,17 @@ export class DomRenderer extends Disposable implements IRenderer { * @param colEnd The end columns. */ private _createSelectionElement(row: number, colStart: number, colEnd: number, rowCount: number = 1): HTMLElement { - const element = document.createElement('div'); + const element = this._document.createElement('div'); + const left = colStart * this.dimensions.css.cell.width; + let width = this.dimensions.css.cell.width * (colEnd - colStart); + if (left + width > this.dimensions.css.canvas.width) { + width = this.dimensions.css.canvas.width - left; + } + element.style.height = `${rowCount * this.dimensions.css.cell.height}px`; element.style.top = `${row * this.dimensions.css.cell.height}px`; - element.style.left = `${colStart * this.dimensions.css.cell.width}px`; - element.style.width = `${this.dimensions.css.cell.width * (colEnd - colStart)}px`; + element.style.left = `${left}px`; + element.style.width = `${width}px`; return element; } diff --git a/src/browser/renderer/dom/DomRendererRowFactory.test.ts b/src/browser/renderer/dom/DomRendererRowFactory.test.ts index 36923dbb..14c4ade1 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.test.ts @@ -14,8 +14,8 @@ import { MockCoreService, MockDecorationService, MockOptionsService } from 'comm import { MockCharacterJoinerService, MockCoreBrowserService, MockThemeService } from 'browser/TestUtils.test'; import { TestWidthCache } from 'browser/renderer/dom/WidthCache.test'; - -const EMPTY_WIDTH = new TestWidthCache(new jsdom.JSDOM('').window.document); +const dom = new jsdom.JSDOM(''); +const EMPTY_WIDTH = new TestWidthCache(dom.window.document, dom.window.document.createElement('div')); describe('DomRendererRowFactory', () => { @@ -96,6 +96,24 @@ describe('DomRendererRowFactory', () => { } }); + it('should not display cursor for before initializing', () => { + const coreService = new MockCoreService(); + coreService.isCursorInitialized = false; + const rowFactory = new DomRendererRowFactory( + dom.window.document, + new MockCharacterJoinerService(), + new MockOptionsService(), + new MockCoreBrowserService(), + coreService, + new MockDecorationService(), + new MockThemeService() + ); + const spans = rowFactory.createRow(lineData, 0, true, 'block', undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + assert.equal(extractHtml(spans), + ` ` + ); + }); + describe('attributes', () => { it('should add class for bold', () => { const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index 614b2301..6ab68e7d 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -218,7 +218,7 @@ export class DomRendererRowFactory { } } - if (!this._coreService.isCursorHidden && isCursorCell) { + if (!this._coreService.isCursorHidden && isCursorCell && this._coreService.isCursorInitialized) { classes.push(RowCss.CURSOR_CLASS); if (this._coreBrowserService.isFocused) { if (cursorBlink) { diff --git a/src/browser/renderer/dom/WidthCache.test.ts b/src/browser/renderer/dom/WidthCache.test.ts index 8efc6ff4..fc524437 100644 --- a/src/browser/renderer/dom/WidthCache.test.ts +++ b/src/browser/renderer/dom/WidthCache.test.ts @@ -36,7 +36,8 @@ function castf32(v: number): number { describe('WidthCache', () => { let wc: TestWidthCache; beforeEach(() => { - wc = new TestWidthCache(new jsdom.JSDOM('').window.document); + const dom = new jsdom.JSDOM(''); + wc = new TestWidthCache(dom.window.document, dom.window.document.createElement('div')); wc.setFont('monospace', 15, 'normal', 'bold'); }); describe('cache invalidation', () => { diff --git a/src/browser/renderer/dom/WidthCache.ts b/src/browser/renderer/dom/WidthCache.ts index 01b3f658..1527bad0 100644 --- a/src/browser/renderer/dom/WidthCache.ts +++ b/src/browser/renderer/dom/WidthCache.ts @@ -45,25 +45,28 @@ export class WidthCache implements IDisposable { private _container: HTMLDivElement; private _measureElements: HTMLSpanElement[] = []; - constructor(_document: Document) { + constructor(_document: Document, _helperContainer: HTMLElement) { this._container = _document.createElement('div'); - this._container.style.position = 'absolute'; - this._container.style.top = '-50000px'; - this._container.style.width = '50000px'; + this._container.classList.add('xterm-width-cache-measure-container'); + this._container.setAttribute('aria-hidden', 'true'); // SP should stack in spans this._container.style.whiteSpace = 'pre'; // avoid undercuts in non-monospace fonts from kerning this._container.style.fontKerning = 'none'; const regular = _document.createElement('span'); + regular.classList.add('xterm-char-measure-element'); const bold = _document.createElement('span'); + bold.classList.add('xterm-char-measure-element'); bold.style.fontWeight = 'bold'; const italic = _document.createElement('span'); + italic.classList.add('xterm-char-measure-element'); italic.style.fontStyle = 'italic'; const boldItalic = _document.createElement('span'); + boldItalic.classList.add('xterm-char-measure-element'); boldItalic.style.fontWeight = 'bold'; boldItalic.style.fontStyle = 'italic'; @@ -74,7 +77,7 @@ export class WidthCache implements IDisposable { this._container.appendChild(italic); this._container.appendChild(boldItalic); - _document.body.appendChild(this._container); + _helperContainer.appendChild(this._container); this.clear(); } diff --git a/src/browser/renderer/shared/CellColorResolver.ts b/src/browser/renderer/shared/CellColorResolver.ts index fa0428b1..5837a675 100644 --- a/src/browser/renderer/shared/CellColorResolver.ts +++ b/src/browser/renderer/shared/CellColorResolver.ts @@ -4,7 +4,7 @@ import { ReadonlyColorSet } from 'browser/Types'; import { Attributes, BgFlags, ExtFlags, FgFlags, NULL_CELL_CODE, UnderlineStyle } from 'common/buffer/Constants'; import { IDecorationService, IOptionsService } from 'common/services/Services'; import { ICellData } from 'common/Types'; -import { Terminal } from 'xterm'; +import { Terminal } from '@xterm/xterm'; // Work variables to avoid garbage collection let $fg = 0; diff --git a/src/browser/renderer/shared/CharAtlasCache.ts b/src/browser/renderer/shared/CharAtlasCache.ts index 67343912..0a871483 100644 --- a/src/browser/renderer/shared/CharAtlasCache.ts +++ b/src/browser/renderer/shared/CharAtlasCache.ts @@ -4,7 +4,7 @@ */ import { TextureAtlas } from 'browser/renderer/shared/TextureAtlas'; -import { ITerminalOptions, Terminal } from 'xterm'; +import { ITerminalOptions, Terminal } from '@xterm/xterm'; import { ITerminal, ReadonlyColorSet } from 'browser/Types'; import { ICharAtlasConfig, ITextureAtlas } from 'browser/renderer/shared/Types'; import { generateConfig, configEquals } from 'browser/renderer/shared/CharAtlasUtils'; diff --git a/src/browser/renderer/shared/CharAtlasUtils.ts b/src/browser/renderer/shared/CharAtlasUtils.ts index 955bd4e6..f8fe9104 100644 --- a/src/browser/renderer/shared/CharAtlasUtils.ts +++ b/src/browser/renderer/shared/CharAtlasUtils.ts @@ -5,7 +5,7 @@ import { ICharAtlasConfig } from './Types'; import { Attributes } from 'common/buffer/Constants'; -import { ITerminalOptions } from 'xterm'; +import { ITerminalOptions } from '@xterm/xterm'; import { IColorSet, ReadonlyColorSet } from 'browser/Types'; import { NULL_COLOR } from 'common/Color'; diff --git a/src/browser/renderer/shared/CustomGlyphs.ts b/src/browser/renderer/shared/CustomGlyphs.ts index c08bc4b1..cf6292af 100644 --- a/src/browser/renderer/shared/CustomGlyphs.ts +++ b/src/browser/renderer/shared/CustomGlyphs.ts @@ -474,7 +474,7 @@ function drawPatternChar( if (!pattern) { const width = charDefinition[0].length; const height = charDefinition.length; - const tmpCanvas = document.createElement('canvas'); + const tmpCanvas = ctx.canvas.ownerDocument.createElement('canvas'); tmpCanvas.width = width; tmpCanvas.height = height; const tmpCtx = throwIfFalsy(tmpCanvas.getContext('2d')); diff --git a/src/browser/renderer/shared/SelectionRenderModel.ts b/src/browser/renderer/shared/SelectionRenderModel.ts index db375778..d3b7a99c 100644 --- a/src/browser/renderer/shared/SelectionRenderModel.ts +++ b/src/browser/renderer/shared/SelectionRenderModel.ts @@ -3,8 +3,9 @@ * @license MIT */ +import { ITerminal } from 'browser/Types'; import { ISelectionRenderModel } from 'browser/renderer/shared/Types'; -import { Terminal } from 'xterm'; +import { Terminal } from '@xterm/xterm'; class SelectionRenderModel implements ISelectionRenderModel { public hasSelection!: boolean; @@ -35,7 +36,7 @@ class SelectionRenderModel implements ISelectionRenderModel { this.selectionEnd = undefined; } - public update(terminal: Terminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void { + public update(terminal: ITerminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void { this.selectionStart = start; this.selectionEnd = end; // Selection does not exist @@ -45,8 +46,9 @@ class SelectionRenderModel implements ISelectionRenderModel { } // Translate from buffer position to viewport position - const viewportStartRow = start[1] - terminal.buffer.active.viewportY; - const viewportEndRow = end[1] - terminal.buffer.active.viewportY; + const viewportY = terminal.buffers.active.ydisp; + const viewportStartRow = start[1] - viewportY; + const viewportEndRow = end[1] - viewportY; const viewportCappedStartRow = Math.max(viewportStartRow, 0); const viewportCappedEndRow = Math.min(viewportEndRow, terminal.rows - 1); diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts index a7e55e72..c0d5e9d6 100644 --- a/src/browser/renderer/shared/Types.d.ts +++ b/src/browser/renderer/shared/Types.d.ts @@ -3,8 +3,8 @@ * @license MIT */ -import { FontWeight, Terminal } from 'xterm'; -import { IColorSet } from 'browser/Types'; +import { FontWeight, Terminal } from '@xterm/xterm'; +import { IColorSet, ITerminal } from 'browser/Types'; import { IDisposable } from 'common/Types'; import { IEvent } from 'common/EventEmitter'; @@ -168,6 +168,6 @@ export interface ISelectionRenderModel { readonly selectionStart: [number, number] | undefined; readonly selectionEnd: [number, number] | undefined; clear(): void; - update(terminal: Terminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode?: boolean): void; + update(terminal: ITerminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode?: boolean): void; isCellSelected(terminal: Terminal, x: number, y: number): boolean; } diff --git a/src/browser/services/CoreBrowserService.ts b/src/browser/services/CoreBrowserService.ts index e992f255..575b62b6 100644 --- a/src/browser/services/CoreBrowserService.ts +++ b/src/browser/services/CoreBrowserService.ts @@ -3,22 +3,49 @@ * @license MIT */ +import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle'; import { ICoreBrowserService } from './Services'; +import { EventEmitter, forwardEvent } from 'common/EventEmitter'; +import { addDisposableDomListener } from 'browser/Lifecycle'; -export class CoreBrowserService implements ICoreBrowserService { +export class CoreBrowserService extends Disposable implements ICoreBrowserService { public serviceBrand: undefined; private _isFocused = false; private _cachedIsFocused: boolean | undefined = undefined; + private _screenDprMonitor = new ScreenDprMonitor(this._window); + + private readonly _onDprChange = this.register(new EventEmitter()); + public readonly onDprChange = this._onDprChange.event; + private readonly _onWindowChange = this.register(new EventEmitter()); + public readonly onWindowChange = this._onWindowChange.event; constructor( private _textarea: HTMLTextAreaElement, - public readonly window: Window & typeof globalThis + private _window: Window & typeof globalThis, + public readonly mainDocument: Document ) { + super(); + + // Monitor device pixel ratio + this.register(this.onWindowChange(w => this._screenDprMonitor.setWindow(w))); + this.register(forwardEvent(this._screenDprMonitor.onDprChange, this._onDprChange)); + this._textarea.addEventListener('focus', () => this._isFocused = true); this._textarea.addEventListener('blur', () => this._isFocused = false); } + public get window(): Window & typeof globalThis { + return this._window; + } + + public set window(value: Window & typeof globalThis) { + if (this._window !== value) { + this._window = value; + this._onWindowChange.fire(this._window); + } + } + public get dpr(): number { return this.window.devicePixelRatio; } @@ -31,3 +58,80 @@ export class CoreBrowserService implements ICoreBrowserService { return this._cachedIsFocused; } } + + +/** + * The screen device pixel ratio monitor allows listening for when the + * window.devicePixelRatio value changes. This is done not with polling but with + * the use of window.matchMedia to watch media queries. When the event fires, + * the listener will be reattached using a different media query to ensure that + * any further changes will register. + * + * The listener should fire on both window zoom changes and switching to a + * monitor with a different DPI. + */ +class ScreenDprMonitor extends Disposable { + private _currentDevicePixelRatio: number; + private _outerListener: ((this: MediaQueryList, ev: MediaQueryListEvent) => any) | undefined; + private _resolutionMediaMatchList: MediaQueryList | undefined; + private _windowResizeListener = this.register(new MutableDisposable()); + + private readonly _onDprChange = this.register(new EventEmitter()); + public readonly onDprChange = this._onDprChange.event; + + constructor(private _parentWindow: Window) { + super(); + + // Initialize listener and dpr value + this._outerListener = () => this._setDprAndFireIfDiffers(); + this._currentDevicePixelRatio = this._parentWindow.devicePixelRatio; + this._updateDpr(); + + // Monitor active window resize + this._setWindowResizeListener(); + + // Setup additional disposables + this.register(toDisposable(() => this.clearListener())); + } + + + public setWindow(parentWindow: Window): void { + this._parentWindow = parentWindow; + this._setWindowResizeListener(); + this._setDprAndFireIfDiffers(); + } + + private _setWindowResizeListener(): void { + this._windowResizeListener.value = addDisposableDomListener(this._parentWindow, 'resize', () => this._setDprAndFireIfDiffers()); + } + + private _setDprAndFireIfDiffers(): void { + if (this._parentWindow.devicePixelRatio !== this._currentDevicePixelRatio) { + this._onDprChange.fire(this._parentWindow.devicePixelRatio); + } + this._updateDpr(); + } + + private _updateDpr(): void { + if (!this._outerListener) { + return; + } + + // Clear listeners for old DPR + this._resolutionMediaMatchList?.removeListener(this._outerListener); + + // Add listeners for new DPR + this._currentDevicePixelRatio = this._parentWindow.devicePixelRatio; + this._resolutionMediaMatchList = this._parentWindow.matchMedia(`screen and (resolution: ${this._parentWindow.devicePixelRatio}dppx)`); + this._resolutionMediaMatchList.addListener(this._outerListener); + } + + public clearListener(): void { + if (!this._resolutionMediaMatchList || !this._outerListener) { + return; + } + this._resolutionMediaMatchList.removeListener(this._outerListener); + this._resolutionMediaMatchList = undefined; + this._outerListener = undefined; + } +} diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index e6d259c2..9fa8d234 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -3,16 +3,14 @@ * @license MIT */ -import { addDisposableDomListener } from 'browser/Lifecycle'; import { RenderDebouncer } from 'browser/RenderDebouncer'; -import { ScreenDprMonitor } from 'browser/ScreenDprMonitor'; import { IRenderDebouncerWithCallback } from 'browser/Types'; import { IRenderDimensions, IRenderer } from 'browser/renderer/shared/Types'; import { ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services'; import { EventEmitter } from 'common/EventEmitter'; import { Disposable, MutableDisposable } from 'common/Lifecycle'; import { DebouncedIdleTask } from 'common/TaskQueue'; -import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; +import { IBufferService, IDecorationService, IInstantiationService, IOptionsService } from 'common/services/Services'; interface ISelectionState { start: [number, number] | undefined; @@ -25,7 +23,6 @@ export class RenderService extends Disposable implements IRenderService { private _renderer: MutableDisposable = this.register(new MutableDisposable()); private _renderDebouncer: IRenderDebouncerWithCallback; - private _screenDprMonitor: ScreenDprMonitor; private _pausedResizeTask = new DebouncedIdleTask(); private _isPaused: boolean = false; @@ -59,6 +56,7 @@ export class RenderService extends Disposable implements IRenderService { @IDecorationService decorationService: IDecorationService, @IBufferService bufferService: IBufferService, @ICoreBrowserService coreBrowserService: ICoreBrowserService, + @IInstantiationService instantiationService: IInstantiationService, @IThemeService themeService: IThemeService ) { super(); @@ -66,9 +64,7 @@ export class RenderService extends Disposable implements IRenderService { this._renderDebouncer = new RenderDebouncer(coreBrowserService.window, (start, end) => this._renderRows(start, end)); this.register(this._renderDebouncer); - this._screenDprMonitor = new ScreenDprMonitor(coreBrowserService.window); - this._screenDprMonitor.setListener(() => this.handleDevicePixelRatioChange()); - this.register(this._screenDprMonitor); + this.register(coreBrowserService.onDprChange(() => this.handleDevicePixelRatioChange())); this.register(bufferService.onResize(() => this._fullRefresh())); this.register(bufferService.buffers.onBufferActivate(() => this._renderer.value?.clear())); @@ -104,10 +100,6 @@ export class RenderService extends Disposable implements IRenderService { 'cursorStyle' ], () => this.refreshRows(bufferService.buffer.y, bufferService.buffer.y, true))); - // dprchange should handle this case, we need this as well for browsers that don't support the - // matchMedia query. - this.register(addDisposableDomListener(coreBrowserService.window, 'resize', () => this.handleDevicePixelRatioChange())); - this.register(themeService.onChangeColors(() => this._fullRefresh())); // Detect whether IntersectionObserver is detected and enable renderer pause diff --git a/src/browser/services/SelectionService.ts b/src/browser/services/SelectionService.ts index e6a14cb6..e134802e 100644 --- a/src/browser/services/SelectionService.ts +++ b/src/browser/services/SelectionService.ts @@ -644,7 +644,9 @@ export class SelectionService extends Disposable implements ISelectionService { if (this._model.selectionEnd[1] < buffer.lines.length) { const line = buffer.lines.get(this._model.selectionEnd[1]); if (line && line.hasWidth(this._model.selectionEnd[0]) === 0) { - this._model.selectionEnd[0]++; + if (this._model.selectionEnd[0] < this._bufferService.cols) { + this._model.selectionEnd[0]++; + } } } diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index d96285f7..5c14fa8a 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -28,12 +28,21 @@ export interface ICoreBrowserService { serviceBrand: undefined; readonly isFocused: boolean; + + readonly onDprChange: IEvent; + readonly onWindowChange: IEvent; + /** - * Parent window that the terminal is rendered into. DOM and rendering APIs - * (e.g. requestAnimationFrame) should be invoked in the context of this - * window. + * Gets or sets the parent window that the terminal is rendered into. DOM and rendering APIs (e.g. + * requestAnimationFrame) should be invoked in the context of this window. This should be set when + * the window hosting the xterm.js instance changes. */ - readonly window: Window & typeof globalThis; + window: Window & typeof globalThis; + /** + * The document of the primary window to be used to create elements when working with multiple + * windows. This is defined by the documentOverride setting. + */ + readonly mainDocument: Document; /** * Helper for getting the devicePixelRatio of the parent window. */ diff --git a/src/common/Color.ts b/src/common/Color.ts index 108d72f3..9bfed4e6 100644 --- a/src/common/Color.ts +++ b/src/common/Color.ts @@ -113,6 +113,7 @@ export namespace css { let $ctx: CanvasRenderingContext2D | undefined; let $litmusColor: CanvasGradient | undefined; if (!isNode) { + // This is guaranteed to run in the first window, so document should be correct const canvas = document.createElement('canvas'); canvas.width = 1; canvas.height = 1; diff --git a/src/common/EventEmitter.ts b/src/common/EventEmitter.ts index fd959042..589748a3 100644 --- a/src/common/EventEmitter.ts +++ b/src/common/EventEmitter.ts @@ -71,3 +71,8 @@ export class EventEmitter implements IEventEmitter { export function forwardEvent(from: IEvent, to: IEventEmitter): IDisposable { return from(e => to.fire(e)); } + +export function runAndSubscribe(event: IEvent, handler: (e: T | undefined) => any): IDisposable { + handler(undefined); + return event(e => handler(e)); +} diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index bbc9256c..a3b8fbec 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -10,13 +10,14 @@ import { CHARSETS, DEFAULT_CHARSET } from 'common/data/Charsets'; import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser'; import { Disposable } from 'common/Lifecycle'; import { StringToUtf32, stringFromCodePoint, Utf8ToUtf32 } from 'common/input/TextDecoder'; -import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; +import { BufferLine, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { EventEmitter } from 'common/EventEmitter'; import { IParsingState, IEscapeSequenceParser, IParams, IFunctionIdentifier } from 'common/parser/Types'; 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, ICoreMouseService, ICharsetService, IUnicodeService, LogLevelEnum, IOscLinkService } from 'common/services/Services'; +import { UnicodeService } from 'common/services/UnicodeService'; import { OscHandler } from 'common/parser/OscParser'; import { DcsHandler } from 'common/parser/DcsParser'; import { IBuffer } from 'common/buffer/Types'; @@ -48,7 +49,7 @@ const GLEVEL: { [key: string]: number } = { '(': 0, ')': 1, '*': 2, '+': 3, '-': /** * Document xterm VT features here that are currently unsupported */ -// @vt: #E[Supported via xterm-addon-image.] DCS SIXEL "SIXEL Graphics" "DCS Ps ; Ps ; Ps ; q Pt ST" "Draw SIXEL image." +// @vt: #E[Supported via @xterm/addon-image.] DCS SIXEL "SIXEL Graphics" "DCS Ps ; Ps ; Ps ; q Pt ST" "Draw SIXEL image." // @vt: #N DCS DECUDK "User Defined Keys" "DCS Ps ; Ps \| Pt ST" "Definitions for user-defined keys." // @vt: #N DCS XTGETTCAP "Request Terminfo String" "DCS + q Pt ST" "Request Terminfo String." // @vt: #N DCS XTSETTCAP "Set Terminfo Data" "DCS + p Pt ST" "Set Terminfo Data." @@ -494,8 +495,13 @@ export class InputHandler extends Disposable implements IInputHandler { this._onCursorMove.fire(); } - // Refresh any dirty rows accumulated as part of parsing - this._onRequestRefreshRows.fire(this._dirtyRowTracker.start, this._dirtyRowTracker.end); + // Refresh any dirty rows accumulated as part of parsing, fire only for rows within the + // _viewport_ which is relative to ydisp, not relative to ybase. + const viewportEnd = this._dirtyRowTracker.end + (this._bufferService.buffer.ybase - this._bufferService.buffer.ydisp); + const viewportStart = this._dirtyRowTracker.start + (this._bufferService.buffer.ybase - this._bufferService.buffer.ydisp); + if (viewportStart < this._bufferService.rows) { + this._onRequestRefreshRows.fire(Math.min(viewportStart, this._bufferService.rows - 1), Math.min(viewportEnd, this._bufferService.rows - 1)); + } } public print(data: Uint32Array, start: number, end: number): void { @@ -516,13 +522,10 @@ export class InputHandler extends Disposable implements IInputHandler { bufferRow.setCellFromCodePoint(this._activeBuffer.x - 1, 0, 1, curAttr.fg, curAttr.bg, curAttr.extended); } + let precedingJoinState = this._parser.precedingJoinState; for (let pos = start; pos < end; ++pos) { code = data[pos]; - // calculate print space - // expensive call, therefore we save width in line buffer - chWidth = this._unicodeService.wcwidth(code); - // get charset replacement character // charset is only defined for ASCII, therefore we only // search for an replacement char if code < 127 @@ -533,6 +536,12 @@ export class InputHandler extends Disposable implements IInputHandler { } } + const currentInfo = this._unicodeService.charProperties(code, precedingJoinState); + chWidth = UnicodeService.extractWidth(currentInfo); + const shouldJoin = UnicodeService.extractShouldJoin(currentInfo); + const oldWidth = shouldJoin ? UnicodeService.extractWidth(precedingJoinState) : 0; + precedingJoinState = currentInfo; + if (screenReaderMode) { this._onA11yChar.fire(stringFromCodePoint(code)); } @@ -540,34 +549,16 @@ export class InputHandler extends Disposable implements IInputHandler { this._oscLinkService.addLineToLink(this._getCurrentLinkId(), this._activeBuffer.ybase + this._activeBuffer.y); } - // insert combining char at last cursor position - // this._activeBuffer.x should never be 0 for a combining char - // since they always follow a cell consuming char - // therefore we can test for this._activeBuffer.x to avoid overflow left - if (!chWidth && this._activeBuffer.x) { - if (!bufferRow.getWidth(this._activeBuffer.x - 1)) { - // found empty cell after fullwidth, need to go 2 cells back - // it is save to step 2 cells back here - // since an empty cell is only set by fullwidth chars - bufferRow.addCodepointToCell(this._activeBuffer.x - 2, code); - } else { - bufferRow.addCodepointToCell(this._activeBuffer.x - 1, code); - } - continue; - } - // goto next line if ch would overflow // NOTE: To avoid costly width checks here, // the terminal does not allow a cols < 2. - if (this._activeBuffer.x + chWidth - 1 >= cols) { + if (this._activeBuffer.x + chWidth - oldWidth > cols) { // autowrap - DECAWM // automatically wraps to the beginning of the next line if (wraparoundMode) { - // clear left over cells to the right - while (this._activeBuffer.x < cols) { - bufferRow.setCellFromCodePoint(this._activeBuffer.x++, 0, 1, curAttr.fg, curAttr.bg, curAttr.extended); - } - this._activeBuffer.x = 0; + const oldRow = bufferRow; + let oldCol = this._activeBuffer.x - oldWidth; + this._activeBuffer.x = oldWidth; this._activeBuffer.y++; if (this._activeBuffer.y === this._activeBuffer.scrollBottom + 1) { this._activeBuffer.y--; @@ -582,6 +573,16 @@ export class InputHandler extends Disposable implements IInputHandler { } // row changed, get it again bufferRow = this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!; + if (oldWidth > 0 && bufferRow instanceof BufferLine) { + // Combining character widens 1 column to 2. + // Move old character to next line. + bufferRow.copyCellsFrom(oldRow as BufferLine, + oldCol, 0, oldWidth, false); + } + // clear left over cells to the right + while (oldCol < cols) { + oldRow.setCellFromCodePoint(oldCol++, 0, 1, curAttr.fg, curAttr.bg, curAttr.extended); + } } else { this._activeBuffer.x = cols - 1; if (chWidth === 2) { @@ -592,10 +593,27 @@ export class InputHandler extends Disposable implements IInputHandler { } } + // insert combining char at last cursor position + // this._activeBuffer.x should never be 0 for a combining char + // since they always follow a cell consuming char + // therefore we can test for this._activeBuffer.x to avoid overflow left + if (shouldJoin && this._activeBuffer.x) { + const offset = bufferRow.getWidth(this._activeBuffer.x - 1) ? 1 : 2; + // if empty cell after fullwidth, need to go 2 cells back + // it is save to step 2 cells back here + // since an empty cell is only set by fullwidth chars + bufferRow.addCodepointToCell(this._activeBuffer.x - offset, + code, chWidth); + for (let delta = chWidth - oldWidth; --delta >= 0; ) { + bufferRow.setCellFromCodePoint(this._activeBuffer.x++, 0, 0, curAttr.fg, curAttr.bg, curAttr.extended); + } + continue; + } + // insert mode: move characters to right if (insertMode) { // right shift cells according to the width - bufferRow.insertCells(this._activeBuffer.x, chWidth, this._activeBuffer.getNullCell(curAttr), curAttr); + bufferRow.insertCells(this._activeBuffer.x, chWidth - oldWidth, this._activeBuffer.getNullCell(curAttr), curAttr); // test last cell - since the last cell has only room for // a halfwidth char any fullwidth shifted there is lost // and will be set to empty cell @@ -617,20 +635,8 @@ export class InputHandler extends Disposable implements IInputHandler { } } } - // store last char in Parser.precedingCodepoint for REP to work correctly - // This needs to check whether: - // - fullwidth + surrogates: reset - // - combining: only base char gets carried on (bug in xterm?) - if (end - start > 0) { - bufferRow.loadCell(this._activeBuffer.x - 1, this._workCell); - if (this._workCell.getWidth() === 2 || this._workCell.getCode() > 0xFFFF) { - this._parser.precedingCodepoint = 0; - } else if (this._workCell.isCombined()) { - this._parser.precedingCodepoint = this._workCell.getChars().charCodeAt(0); - } else { - this._parser.precedingCodepoint = this._workCell.content; - } - } + + this._parser.precedingJoinState = precedingJoinState; // handle wide chars: reset cell to the right if it is second cell of a wide char if (this._activeBuffer.x < cols && end - start > 0 && bufferRow.getWidth(this._activeBuffer.x) === 0 && !bufferRow.hasContent(this._activeBuffer.x)) { @@ -1576,9 +1582,8 @@ export class InputHandler extends Disposable implements IInputHandler { * If the character preceding REP is a control function or part of a control function, * the effect of REP is not defined by this Standard. * - * Since we propagate the terminal as xterm-256color we have to follow xterm's behavior: - * - fullwidth + surrogate chars are ignored - * - for combining chars only the base char gets repeated + * We extend xterm's behavior to allow repeating entire grapheme clusters. + * This isn't 100% xterm-compatible, but it seems saner and more useful. * - text attrs are applied normally * - wrap around is respected * - any valid sequence resets the carried forward char @@ -1592,16 +1597,29 @@ export class InputHandler extends Disposable implements IInputHandler { * (NOOP for any other sequence in between or NON ASCII characters). */ public repeatPrecedingCharacter(params: IParams): boolean { - if (!this._parser.precedingCodepoint) { + const joinState = this._parser.precedingJoinState; + if (!joinState) { return true; } // call print to insert the chars and handle correct wrapping const length = params.params[0] || 1; - const data = new Uint32Array(length); - for (let i = 0; i < length; ++i) { - data[i] = this._parser.precedingCodepoint; + const chWidth = UnicodeService.extractWidth(joinState); + const x = this._activeBuffer.x - chWidth; + const bufferRow = this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!; + const text = bufferRow.getString(x); + const data = new Uint32Array(text.length * length); + let idata = 0; + for (let itext = 0; itext < text.length; ) { + const ch = text.codePointAt(itext) || 0; + data[idata++] = ch; + itext += ch > 0xffff ? 2 : 1; } - this.print(data, 0, data.length); + let tlength = idata; + for (let i = 1; i < length; ++i) { + data.copyWithin(tlength, 0, idata); + tlength += idata; + } + this.print(data, 0, tlength); return true; } diff --git a/src/common/Platform.ts b/src/common/Platform.ts index 41d8552e..1007fc0a 100644 --- a/src/common/Platform.ts +++ b/src/common/Platform.ts @@ -12,8 +12,9 @@ interface INavigator { // We're declaring a navigator global here as we expect it in all runtimes (node and browser), but // we want this module to live in common. declare const navigator: INavigator; +declare const process: unknown; -export const isNode = (typeof navigator === 'undefined') ? true : false; +export const isNode = (typeof process !== 'undefined') ? true : false; const userAgent = (isNode) ? 'node' : navigator.userAgent; const platform = (isNode) ? 'node' : navigator.platform; diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 3e554d10..39e13fba 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -3,7 +3,8 @@ * @license MIT */ -import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, ICoreMouseService, ICharsetService, IUnicodeService, IUnicodeVersionProvider, LogLevelEnum, IDecorationService, IInternalDecoration, IOscLinkService } from 'common/services/Services'; +import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, ICoreMouseService, ICharsetService, UnicodeCharProperties, UnicodeCharWidth, IUnicodeService, IUnicodeVersionProvider, LogLevelEnum, IDecorationService, IInternalDecoration, IOscLinkService } from 'common/services/Services'; +import { UnicodeService } from 'common/services/UnicodeService'; import { IEvent, EventEmitter } from 'common/EventEmitter'; import { clone } from 'common/Clone'; import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; @@ -11,7 +12,7 @@ import { IBufferSet, IBuffer } from 'common/buffer/Types'; import { BufferSet } from 'common/buffer/BufferSet'; import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEventType, ICharset, IModes, IAttributeData, IOscLinkData, IDisposable } from 'common/Types'; import { UnicodeV6 } from 'common/input/UnicodeV6'; -import { IDecorationOptions, IDecoration } from 'xterm'; +import { IDecorationOptions, IDecoration } from '@xterm/xterm'; export class MockBufferService implements IBufferService { public serviceBrand: any; @@ -77,7 +78,7 @@ export class MockCharsetService implements ICharsetService { export class MockCoreService implements ICoreService { public serviceBrand: any; - public isCursorInitialized: boolean = false; + public isCursorInitialized: boolean = true; public isCursorHidden: boolean = false; public isFocused: boolean = false; public modes: IModes = { @@ -168,7 +169,20 @@ export class MockUnicodeService implements IUnicodeService { public versions: string[] = []; public activeVersion: string = ''; public onChange: IEvent = new EventEmitter().event; - public wcwidth = (codepoint: number): number => this._provider.wcwidth(codepoint); + public wcwidth = (codepoint: number): UnicodeCharWidth => this._provider.wcwidth(codepoint); + public charProperties(codepoint: number, preceding: UnicodeCharProperties): UnicodeCharProperties { + let width = this.wcwidth(codepoint); + let shouldJoin = width === 0 && preceding !== 0; + if (shouldJoin) { + const oldWidth = UnicodeService.extractWidth(preceding); + if (oldWidth === 0) { + shouldJoin = false; + } else if (oldWidth > width) { + width = oldWidth; + } + } + return UnicodeService.createPropertyValue(0, width, shouldJoin); + } public getStringCellWidth(s: string): number { throw new Error('Method not implemented.'); } diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index 8698e106..3e6476a3 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -9,7 +9,7 @@ import { Attributes, UnderlineStyle } from 'common/buffer/Constants'; // eslint- import { IBufferSet } from 'common/buffer/Types'; import { IParams } from 'common/parser/Types'; import { ICoreMouseService, ICoreService, IOptionsService, IUnicodeService } from 'common/services/Services'; -import { IFunctionIdentifier, ITerminalOptions as IPublicTerminalOptions } from 'xterm'; +import { IFunctionIdentifier, ITerminalOptions as IPublicTerminalOptions } from '@xterm/xterm'; export interface ICoreTerminal { coreMouseService: ICoreMouseService; @@ -236,7 +236,7 @@ export interface IBufferLine { loadCell(index: number, cell: ICellData): ICellData; setCell(index: number, cell: ICellData): void; setCellFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number, eAttrs: IExtendedAttrs): void; - addCodepointToCell(index: number, codePoint: number): void; + addCodepointToCell(index: number, codePoint: number, width: number): void; insertCells(pos: number, n: number, ch: ICellData, eraseAttr?: IAttributeData): void; deleteCells(pos: number, n: number, fill: ICellData, eraseAttr?: IAttributeData): void; replaceCells(start: number, end: number, fill: ICellData, eraseAttr?: IAttributeData, respectProtect?: boolean): void; diff --git a/src/common/buffer/BufferLine.test.ts b/src/common/buffer/BufferLine.test.ts index 3e115b4b..b8684b82 100644 --- a/src/common/buffer/BufferLine.test.ts +++ b/src/common/buffer/BufferLine.test.ts @@ -443,7 +443,7 @@ describe('BufferLine', function(): void { describe('addCharToCell', () => { it('should set width to 1 for empty cell', () => { const line = new TestBufferLine(3, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); - line.addCodepointToCell(0, '\u0301'.charCodeAt(0)); + line.addCodepointToCell(0, '\u0301'.charCodeAt(0), 0); const cell = line.loadCell(0, new CellData()); // chars contains single combining char // width is set to 1 @@ -456,7 +456,7 @@ describe('BufferLine', function(): void { const cell = line .loadCell(0, new CellData()); cell.setFromCharData([123, 'e\u0301', 1, 'e\u0301'.charCodeAt(1)]); line.setCell(0, cell); - line.addCodepointToCell(0, '\u0301'.charCodeAt(0)); + line.addCodepointToCell(0, '\u0301'.charCodeAt(0), 0); line.loadCell(0, cell); // chars contains 3 chars // width is set to 1 @@ -469,7 +469,7 @@ describe('BufferLine', function(): void { const cell = line .loadCell(0, new CellData()); cell.setFromCharData([123, 'e', 1, 'e'.charCodeAt(1)]); line.setCell(0, cell); - line.addCodepointToCell(0, '\u0301'.charCodeAt(0)); + line.addCodepointToCell(0, '\u0301'.charCodeAt(0), 0); line.loadCell(0, cell); // chars contains 2 chars // width is set to 1 diff --git a/src/common/buffer/BufferLine.ts b/src/common/buffer/BufferLine.ts index de25dc28..a268f2bc 100644 --- a/src/common/buffer/BufferLine.ts +++ b/src/common/buffer/BufferLine.ts @@ -227,7 +227,7 @@ export class BufferLine implements IBufferLine { * onto a leading char. Since we already set the attrs * by the previous `setDataFromCodePoint` call, we can omit it here. */ - public addCodepointToCell(index: number, codePoint: number): void { + public addCodepointToCell(index: number, codePoint: number, width: number): void { let content = this._data[index * CELL_SIZE + Cell.CONTENT]; if (content & Content.IS_COMBINED_MASK) { // we already have a combined string, simply add @@ -245,8 +245,12 @@ export class BufferLine implements IBufferLine { // simply set the data in the cell buffer with a width of 1 content = codePoint | (1 << Content.WIDTH_SHIFT); } - this._data[index * CELL_SIZE + Cell.CONTENT] = content; } + if (width) { + content &= ~Content.WIDTH_MASK; + content |= width << Content.WIDTH_SHIFT; + } + this._data[index * CELL_SIZE + Cell.CONTENT] = content; } public insertCells(pos: number, n: number, fillCellData: ICellData, eraseAttr?: IAttributeData): void { diff --git a/src/common/buffer/BufferRange.test.ts b/src/common/buffer/BufferRange.test.ts index 69aacd99..15d1b831 100644 --- a/src/common/buffer/BufferRange.test.ts +++ b/src/common/buffer/BufferRange.test.ts @@ -5,7 +5,7 @@ import { assert } from 'chai'; import { getRangeLength } from 'common/buffer/BufferRange'; -import { IBufferRange } from 'xterm'; +import { IBufferRange } from '@xterm/xterm'; describe('BufferRange', () => { describe('getRangeLength', () => { diff --git a/src/common/buffer/BufferRange.ts b/src/common/buffer/BufferRange.ts index a49cf481..aa7ee405 100644 --- a/src/common/buffer/BufferRange.ts +++ b/src/common/buffer/BufferRange.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IBufferRange } from 'xterm'; +import { IBufferRange } from '@xterm/xterm'; export function getRangeLength(range: IBufferRange, bufferCols: number): number { if (range.start.y > range.end.y) { diff --git a/src/common/input/Keyboard.test.ts b/src/common/input/Keyboard.test.ts index f2d0936b..c598fc58 100644 --- a/src/common/input/Keyboard.test.ts +++ b/src/common/input/Keyboard.test.ts @@ -106,6 +106,21 @@ describe('Keyboard', () => { it('should return \\x1b[5B for ctrl+down', () => { assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 40 }).key, '\x1b[1;5B'); // CSI 5 B }); + it('should return \\x08 for ctrl+backspace', () => { + assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 8 }).key, '\x08'); + }); + it('should return \\x1b\\x7f for alt+backspace', () => { + assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 8 }).key, '\x1b\x7f'); + }); + it('should return \\x1b\\x08 for ctrl+alt+backspace', () => { + assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, altKey: true, keyCode: 8 }).key, '\x1b\x08'); + }); + it('should return \\x1b[3;2~ for shift+delete', () => { + assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 46 }).key, '\x1b[3;2~'); + }); + it('should return \\x1b[3;3~ for alt+delete', () => { + assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 46 }).key, '\x1b[3;3~'); + }); describe('On non-macOS platforms', () => { // Evalueate alt + arrow key movement, which is a feature of terminal emulators but not VT100 diff --git a/src/common/input/Keyboard.ts b/src/common/input/Keyboard.ts index 9420a974..b86eeac4 100644 --- a/src/common/input/Keyboard.ts +++ b/src/common/input/Keyboard.ts @@ -83,11 +83,10 @@ export function evaluateKeyboardEvent( break; case 8: // backspace + result.key = ev.ctrlKey ? '\b' : C0.DEL; // ^H or ^? if (ev.altKey) { - result.key = C0.ESC + C0.DEL; // \e ^? - break; + result.key = C0.ESC + result.key; } - result.key = C0.DEL; // ^? break; case 9: // tab diff --git a/src/common/input/UnicodeV6.ts b/src/common/input/UnicodeV6.ts index bf63a18b..83265f70 100644 --- a/src/common/input/UnicodeV6.ts +++ b/src/common/input/UnicodeV6.ts @@ -2,9 +2,8 @@ * Copyright (c) 2019 The xterm.js authors. All rights reserved. * @license MIT */ -import { IUnicodeVersionProvider } from 'common/services/Services'; - -type CharWidth = 0 | 1 | 2; +import { IUnicodeVersionProvider, UnicodeCharProperties, UnicodeCharWidth } from 'common/services/Services'; +import { UnicodeService } from 'common/services/UnicodeService'; const BMP_COMBINING = [ [0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489], @@ -121,12 +120,26 @@ export class UnicodeV6 implements IUnicodeVersionProvider { } } - public wcwidth(num: number): CharWidth { + public wcwidth(num: number): UnicodeCharWidth { if (num < 32) return 0; if (num < 127) return 1; - if (num < 65536) return table[num] as CharWidth; + if (num < 65536) return table[num] as UnicodeCharWidth; if (bisearch(num, HIGH_COMBINING)) return 0; if ((num >= 0x20000 && num <= 0x2fffd) || (num >= 0x30000 && num <= 0x3fffd)) return 2; return 1; } + + public charProperties(codepoint: number, preceding: UnicodeCharProperties): UnicodeCharProperties { + let width = this.wcwidth(codepoint); + let shouldJoin = width === 0 && preceding !== 0; + if (shouldJoin) { + const oldWidth = UnicodeService.extractWidth(preceding); + if (oldWidth === 0) { + shouldJoin = false; + } else if (oldWidth > width) { + width = oldWidth; + } + } + return UnicodeService.createPropertyValue(0, width, shouldJoin); + } } diff --git a/src/common/parser/EscapeSequenceParser.ts b/src/common/parser/EscapeSequenceParser.ts index de206322..b3d02768 100644 --- a/src/common/parser/EscapeSequenceParser.ts +++ b/src/common/parser/EscapeSequenceParser.ts @@ -230,7 +230,7 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable { export class EscapeSequenceParser extends Disposable implements IEscapeSequenceParser { public initialState: number; public currentState: number; - public precedingCodepoint: number; + public precedingJoinState: number; // UnicodeJoinProperties // buffers over several parse calls protected _params: Params; @@ -271,7 +271,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP this._params = new Params(); // defaults to 32 storable params/subparams this._params.addParam(0); // ZDM this._collect = 0; - this.precedingCodepoint = 0; + this.precedingJoinState = 0; // set default fallback handlers and handler lookup containers this._printHandlerFb = (data, start, end): void => { }; @@ -448,7 +448,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP this._params.reset(); this._params.addParam(0); // ZDM this._collect = 0; - this.precedingCodepoint = 0; + this.precedingJoinState = 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 @@ -610,7 +610,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP // cleanup before continuing with the main sync loop this._parseStack.state = ParserStackType.NONE; start = this._parseStack.chunkPos + 1; - this.precedingCodepoint = 0; + this.precedingJoinState = 0; this.currentState = this._parseStack.transition & TableAccess.TRANSITION_STATE_MASK; } } @@ -653,7 +653,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP case ParserAction.EXECUTE: if (this._executeHandlers[code]) this._executeHandlers[code](); else this._executeHandlerFb(code); - this.precedingCodepoint = 0; + this.precedingJoinState = 0; break; case ParserAction.IGNORE: break; @@ -688,7 +688,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP if (j < 0) { this._csiHandlerFb(this._collect << 8 | code, this._params); } - this.precedingCodepoint = 0; + this.precedingJoinState = 0; break; case ParserAction.PARAM: // inner loop: digits (0x30 - 0x39) and ; (0x3b) and : (0x3a) @@ -727,7 +727,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP if (jj < 0) { this._escHandlerFb(this._collect << 8 | code); } - this.precedingCodepoint = 0; + this.precedingJoinState = 0; break; case ParserAction.CLEAR: this._params.reset(); @@ -758,7 +758,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP this._params.reset(); this._params.addParam(0); // ZDM this._collect = 0; - this.precedingCodepoint = 0; + this.precedingJoinState = 0; break; case ParserAction.OSC_START: this._oscParser.start(); @@ -783,7 +783,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP this._params.reset(); this._params.addParam(0); // ZDM this._collect = 0; - this.precedingCodepoint = 0; + this.precedingJoinState = 0; break; } this.currentState = transition & TableAccess.TRANSITION_STATE_MASK; diff --git a/src/common/parser/Types.d.ts b/src/common/parser/Types.d.ts index a1ea0ec2..2ed4acdc 100644 --- a/src/common/parser/Types.d.ts +++ b/src/common/parser/Types.d.ts @@ -146,11 +146,12 @@ export type PrintFallbackHandlerType = PrintHandlerType; */ export interface IEscapeSequenceParser extends IDisposable { /** - * Preceding codepoint to get REP working correctly. - * This must be set by the print handler as last action. - * It gets reset by the parser for any valid sequence beside REP itself. + * Preceding grapheme-join-state. + * Used for joining grapheme clusters across calls to `print`. + * Also used by REP to check if repeating a character is allowed. + * It gets reset by the parser for any valid sequence besides text. */ - precedingCodepoint: number; + precedingJoinState: number; // More specifically: UnicodeJoinProperties /** * Reset the parser to its initial state (handlers are kept). diff --git a/src/common/public/AddonManager.test.ts b/src/common/public/AddonManager.test.ts index e947976a..239952ee 100644 --- a/src/common/public/AddonManager.test.ts +++ b/src/common/public/AddonManager.test.ts @@ -5,7 +5,7 @@ import { assert } from 'chai'; import { AddonManager, ILoadedAddon } from './AddonManager'; -import { ITerminalAddon } from 'xterm'; +import { ITerminalAddon } from '@xterm/xterm'; class TestAddonManager extends AddonManager { public get addons(): ILoadedAddon[] { diff --git a/src/common/public/AddonManager.ts b/src/common/public/AddonManager.ts index af04a269..9a36e2a0 100644 --- a/src/common/public/AddonManager.ts +++ b/src/common/public/AddonManager.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { ITerminalAddon, IDisposable, Terminal } from 'xterm'; +import { ITerminalAddon, IDisposable, Terminal } from '@xterm/xterm'; export interface ILoadedAddon { instance: ITerminalAddon; diff --git a/src/common/public/BufferApiView.ts b/src/common/public/BufferApiView.ts index ca9ef2d8..834cedbe 100644 --- a/src/common/public/BufferApiView.ts +++ b/src/common/public/BufferApiView.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi } from 'xterm'; +import { IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi } from '@xterm/xterm'; import { IBuffer } from 'common/buffer/Types'; import { BufferLineApiView } from 'common/public/BufferLineApiView'; import { CellData } from 'common/buffer/CellData'; diff --git a/src/common/public/BufferLineApiView.ts b/src/common/public/BufferLineApiView.ts index 60375015..560dd0bf 100644 --- a/src/common/public/BufferLineApiView.ts +++ b/src/common/public/BufferLineApiView.ts @@ -5,7 +5,7 @@ import { CellData } from 'common/buffer/CellData'; import { IBufferLine, ICellData } from 'common/Types'; -import { IBufferCell as IBufferCellApi, IBufferLine as IBufferLineApi } from 'xterm'; +import { IBufferCell as IBufferCellApi, IBufferLine as IBufferLineApi } from '@xterm/xterm'; export class BufferLineApiView implements IBufferLineApi { constructor(private _line: IBufferLine) { } diff --git a/src/common/public/BufferNamespaceApi.ts b/src/common/public/BufferNamespaceApi.ts index aeaa4ac8..cef0ed16 100644 --- a/src/common/public/BufferNamespaceApi.ts +++ b/src/common/public/BufferNamespaceApi.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IBuffer as IBufferApi, IBufferNamespace as IBufferNamespaceApi } from 'xterm'; +import { IBuffer as IBufferApi, IBufferNamespace as IBufferNamespaceApi } from '@xterm/xterm'; import { BufferApiView } from 'common/public/BufferApiView'; import { EventEmitter } from 'common/EventEmitter'; import { ICoreTerminal } from 'common/Types'; diff --git a/src/common/public/ParserApi.ts b/src/common/public/ParserApi.ts index 67df4be5..afcc01be 100644 --- a/src/common/public/ParserApi.ts +++ b/src/common/public/ParserApi.ts @@ -4,7 +4,7 @@ */ import { IParams } from 'common/parser/Types'; -import { IDisposable, IFunctionIdentifier, IParser } from 'xterm'; +import { IDisposable, IFunctionIdentifier, IParser } from '@xterm/xterm'; import { ICoreTerminal } from 'common/Types'; export class ParserApi implements IParser { diff --git a/src/common/public/UnicodeApi.ts b/src/common/public/UnicodeApi.ts index 8a669a05..ec9bd1f7 100644 --- a/src/common/public/UnicodeApi.ts +++ b/src/common/public/UnicodeApi.ts @@ -4,7 +4,7 @@ */ import { ICoreTerminal } from 'common/Types'; -import { IUnicodeHandling, IUnicodeVersionProvider } from 'xterm'; +import { IUnicodeHandling, IUnicodeVersionProvider } from '@xterm/xterm'; export class UnicodeApi implements IUnicodeHandling { constructor(private _core: ICoreTerminal) { } diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index 36faa509..da759152 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -9,7 +9,7 @@ import { Disposable, toDisposable } from 'common/Lifecycle'; import { IDecorationService, IInternalDecoration } from 'common/services/Services'; import { SortedList } from 'common/SortedList'; import { IColor } from 'common/Types'; -import { IDecoration, IDecorationOptions, IMarker } from 'xterm'; +import { IDecoration, IDecorationOptions, IMarker } from '@xterm/xterm'; // Work variables to avoid garbage collection let $xmin = 0; diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 3c572445..eb9dbfa8 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -18,6 +18,7 @@ export const DEFAULT_OPTIONS: Readonly> = { cursorInactiveStyle: 'outline', customGlyphs: true, drawBoldTextInBrightColors: true, + documentOverride: null, fastScrollModifier: 'alt', fastScrollSensitivity: 5, fontFamily: 'courier-new, courier, monospace', diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index b4ee5a76..304e8cbb 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -7,7 +7,7 @@ import { IEvent, IEventEmitter } from 'common/EventEmitter'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset, IWindowOptions, IModes, IAttributeData, ScrollSource, IDisposable, IColor, CursorStyle, CursorInactiveStyle, IOscLinkData } from 'common/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; -import { IDecorationOptions, IDecoration, ILinkHandler, IWindowsPty, ILogger } from 'xterm'; +import { IDecorationOptions, IDecoration, ILinkHandler, IWindowsPty, ILogger } from '@xterm/xterm'; export const IBufferService = createDecorator('BufferService'); export interface IBufferService { @@ -217,6 +217,7 @@ export interface ITerminalOptions { cursorInactiveStyle?: CursorInactiveStyle; customGlyphs?: boolean; disableStdin?: boolean; + documentOverride?: any | null; drawBoldTextInBrightColors?: boolean; fastScrollModifier?: 'none' | 'alt' | 'ctrl' | 'shift'; fastScrollSensitivity?: number; @@ -296,6 +297,29 @@ export interface IOscLinkService { getLinkData(linkId: number): IOscLinkData | undefined; } +/* + * Width and Grapheme_Cluster_Break properties of a character as a bit mask. + * + * bit 0: shouldJoin - should combine with preceding character. + * bit 1..2: wcwidth - see UnicodeCharWidth. + * bit 3..31: class of character (currently only 4 bits are used). + * This is used to determined grapheme clustering - i.e. which codepoints + * are to be combined into a single compound character. + * + * Use the UnicodeService static function createPropertyValue to create a + * UnicodeCharProperties; use extractShouldJoin, extractWidth, and + * extractCharKind to extract the components. + */ +export type UnicodeCharProperties = number; + +/** + * Width in columns of a character. + * In a CJK context, "half-width" characters (such as Latin) are width 1, + * while "full-width" characters (such as Kanji) are 2 columns wide. + * Combining characters (such as accents) are width 0. + */ +export type UnicodeCharWidth = 0 | 1 | 2; + export const IUnicodeService = createDecorator('UnicodeService'); export interface IUnicodeService { serviceBrand: undefined; @@ -311,13 +335,20 @@ export interface IUnicodeService { /** * Unicode version dependent */ - wcwidth(codepoint: number): number; + wcwidth(codepoint: number): UnicodeCharWidth; getStringCellWidth(s: string): number; + /** + * Return character width and type for grapheme clustering. + * If preceding != 0, it is the return code from the previous character; + * in that case the result specifies if the characters should be joined. + */ + charProperties(codepoint: number, preceding: UnicodeCharProperties): UnicodeCharProperties; } export interface IUnicodeVersionProvider { readonly version: string; - wcwidth(ucs: number): 0 | 1 | 2; + wcwidth(ucs: number): UnicodeCharWidth; + charProperties(codepoint: number, preceding: UnicodeCharProperties): UnicodeCharProperties; } export const IDecorationService = createDecorator('DecorationService'); diff --git a/src/common/services/UnicodeService.test.ts b/src/common/services/UnicodeService.test.ts index a2c4b636..01e3c086 100644 --- a/src/common/services/UnicodeService.test.ts +++ b/src/common/services/UnicodeService.test.ts @@ -12,6 +12,9 @@ class DummyProvider implements IUnicodeVersionProvider { public wcwidth(n: number): 0 | 1 | 2 { return 2; } + public charProperties(codepoint: number): number { + return UnicodeService.createPropertyValue(0, this.wcwidth(codepoint)); + } } describe('unicode provider', () => { diff --git a/src/common/services/UnicodeService.ts b/src/common/services/UnicodeService.ts index 4f3596d2..767eecaf 100644 --- a/src/common/services/UnicodeService.ts +++ b/src/common/services/UnicodeService.ts @@ -2,9 +2,10 @@ * Copyright (c) 2019 The xterm.js authors. All rights reserved. * @license MIT */ + import { EventEmitter } from 'common/EventEmitter'; import { UnicodeV6 } from 'common/input/UnicodeV6'; -import { IUnicodeService, IUnicodeVersionProvider } from 'common/services/Services'; +import { IUnicodeService, IUnicodeVersionProvider, UnicodeCharProperties, UnicodeCharWidth } from 'common/services/Services'; export class UnicodeService implements IUnicodeService { public serviceBrand: any; @@ -16,6 +17,19 @@ export class UnicodeService implements IUnicodeService { private readonly _onChange = new EventEmitter(); public readonly onChange = this._onChange.event; + public static extractShouldJoin(value: UnicodeCharProperties): boolean { + return (value & 1) !== 0; + } + public static extractWidth(value: UnicodeCharProperties): UnicodeCharWidth { + return ((value >> 1) & 0x3) as UnicodeCharWidth; + } + public static extractCharKind(value: UnicodeCharProperties): number { + return value >> 3; + } + public static createPropertyValue(state: number, width: number, shouldJoin: boolean = false): UnicodeCharProperties { + return ((state & 0xffffff) << 3) | ((width & 3) << 1) | (shouldJoin?1:0); + } + constructor() { const defaultProvider = new UnicodeV6(); this.register(defaultProvider); @@ -51,12 +65,13 @@ export class UnicodeService implements IUnicodeService { /** * Unicode version dependent interface. */ - public wcwidth(num: number): number { + public wcwidth(num: number): UnicodeCharWidth { return this._activeProvider.wcwidth(num); } public getStringCellWidth(s: string): number { let result = 0; + let precedingInfo = 0; const length = s.length; for (let i = 0; i < length; ++i) { let code = s.charCodeAt(i); @@ -79,8 +94,18 @@ export class UnicodeService implements IUnicodeService { result += this.wcwidth(second); } } - result += this.wcwidth(code); + const currentInfo = this.charProperties(code, precedingInfo); + let chWidth = UnicodeService.extractWidth(currentInfo); + if (UnicodeService.extractShouldJoin(currentInfo)) { + chWidth -= UnicodeService.extractWidth(precedingInfo); + } + result += chWidth; + precedingInfo = currentInfo; } return result; } + + public charProperties(codepoint: number, preceding: UnicodeCharProperties): UnicodeCharProperties { + return this._activeProvider.charProperties(codepoint, preceding); + } } diff --git a/src/headless/public/Terminal.test.ts b/src/headless/public/Terminal.test.ts index f869a991..2175c668 100644 --- a/src/headless/public/Terminal.test.ts +++ b/src/headless/public/Terminal.test.ts @@ -5,7 +5,7 @@ import { deepStrictEqual, strictEqual, throws } from 'assert'; import { Terminal } from 'headless/public/Terminal'; -import { ITerminalOptions } from 'xterm-headless'; +import { ITerminalOptions } from '@xterm/headless'; let term: Terminal; diff --git a/src/headless/public/Terminal.ts b/src/headless/public/Terminal.ts index b018d37c..df202660 100644 --- a/src/headless/public/Terminal.ts +++ b/src/headless/public/Terminal.ts @@ -7,7 +7,7 @@ import { IEvent } from 'common/EventEmitter'; import { BufferNamespaceApi } from 'common/public/BufferNamespaceApi'; import { ParserApi } from 'common/public/ParserApi'; import { UnicodeApi } from 'common/public/UnicodeApi'; -import { IBufferNamespace as IBufferNamespaceApi, IMarker, IModes, IParser, ITerminalAddon, ITerminalInitOnlyOptions, IUnicodeHandling, Terminal as ITerminalApi } from 'xterm-headless'; +import { IBufferNamespace as IBufferNamespaceApi, IMarker, IModes, IParser, ITerminalAddon, ITerminalInitOnlyOptions, IUnicodeHandling, Terminal as ITerminalApi } from '@xterm/headless'; import { Terminal as TerminalCore } from 'headless/Terminal'; import { AddonManager } from 'common/public/AddonManager'; import { ITerminalOptions } from 'common/Types'; diff --git a/src/headless/tsconfig.json b/src/headless/tsconfig.json index b579af97..e48c4fd5 100644 --- a/src/headless/tsconfig.json +++ b/src/headless/tsconfig.json @@ -17,7 +17,7 @@ }, "include": [ "./**/*", - "../../typings/xterm.d.ts", // common/Types.d.ts imports from 'xterm' + "../../typings/xterm.d.ts", // common/Types.d.ts imports from '@xterm/xterm' "../../typings/xterm-headless.d.ts" ], "references": [ diff --git a/test/api/TestUtils.ts b/test/api/TestUtils.ts index 702fcb2d..9ebf67b6 100644 --- a/test/api/TestUtils.ts +++ b/test/api/TestUtils.ts @@ -5,7 +5,7 @@ import * as playwright from '@playwright/test'; import deepEqual = require('deep-equal'); -import { ITerminalInitOnlyOptions, ITerminalOptions } from 'xterm'; +import { ITerminalInitOnlyOptions, ITerminalOptions } from '@xterm/xterm'; import { deepStrictEqual } from 'assert'; export async function pollFor(page: playwright.Page, evalOrFn: string | (() => Promise), val: T, preFn?: () => Promise, maxDuration?: number): Promise { @@ -43,9 +43,19 @@ export async function timeout(ms: number): Promise { return new Promise(r => setTimeout(r, ms)); } -export async function openTerminal(page: playwright.Page, options: ITerminalOptions & ITerminalInitOnlyOptions = {}): Promise { +export async function openTerminal(page: playwright.Page, options: ITerminalOptions & ITerminalInitOnlyOptions = {}, testOptions: { loadUnicodeGraphemesAddon: boolean } = { loadUnicodeGraphemesAddon: true }): Promise { await page.evaluate(`window.term = new Terminal(${JSON.stringify({ allowProposedApi: true, ...options })})`); await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); + + // HACK: This is a soft layer breaker that's temporarily included until unicode graphemes have + // more complete integration tests. See https://github.com/xtermjs/xterm.js/pull/4519#discussion_r1285234453 + if (testOptions.loadUnicodeGraphemesAddon) { + await page.evaluate(` + window.unicode = new UnicodeGraphemesAddon(); + window.term.loadAddon(window.unicode); + window.term.unicode.activeVersion = '15-graphemes'; + `); + } await page.waitForSelector('.xterm-rows'); } diff --git a/test/benchmark/Terminal.benchmark.ts b/test/benchmark/Terminal.benchmark.ts index 578a83d0..d2b6ed8f 100644 --- a/test/benchmark/Terminal.benchmark.ts +++ b/test/benchmark/Terminal.benchmark.ts @@ -25,11 +25,11 @@ perfContext('Terminal: ls -lR /usr/lib', () => { }); const chunks: Buffer[] = []; let length = 0; - p.on('data', data => { + p.onData(data => { chunks.push(data as unknown as Buffer); length += data.length; }); - await new Promise(resolve => p.on('exit', () => resolve())); + await new Promise(resolve => p.onExit(() => resolve())); contentUtf8 = Buffer.concat(chunks, length); // translate to content string const buffer = new Uint32Array(contentUtf8.length); diff --git a/test/playwright/InputHandler.test.ts b/test/playwright/InputHandler.test.ts index e422bd1c..fb2eeb9c 100644 --- a/test/playwright/InputHandler.test.ts +++ b/test/playwright/InputHandler.test.ts @@ -255,18 +255,18 @@ test.describe('InputHandler Integration Tests', () => { `); await pollFor(ctx.page, () => getLinesAsArray(4), ['##', '##', '##', '######']); await pollFor(ctx.page, () => getCursor(), { col: 6, row: 3 }); - // should not repeat on fullwidth chars + // repeat on fullwidth chars await ctx.page.evaluate(` window.term.reset(); - window.term.write('¥\x1b[10b'); + window.term.write('¥\x1b[8b'); `); - await pollFor(ctx.page, () => getLinesAsArray(1), ['¥']); - // should repeat only base char of combining + await pollFor(ctx.page, () => getLinesAsArray(1), ['¥¥¥¥¥']); + // change from xterm: repeat grapheme cluster await ctx.page.evaluate(` window.term.reset(); - window.term.write('e\u0301\x1b[5b'); + window.term.write('e\u0301\x1b[2b'); `); - await pollFor(ctx.page, () => getLinesAsArray(1), ['e\u0301eeeee']); + await pollFor(ctx.page, () => getLinesAsArray(1), ['e\u0301e\u0301e\u0301']); // should wrap correctly await ctx.page.evaluate(` window.term.reset(); diff --git a/test/playwright/MouseTracking.test.ts b/test/playwright/MouseTracking.test.ts index dbce55cd..1cefd7cd 100644 --- a/test/playwright/MouseTracking.test.ts +++ b/test/playwright/MouseTracking.test.ts @@ -4,7 +4,7 @@ */ import { test } from '@playwright/test'; import { deepStrictEqual, ok, strictEqual } from 'assert'; -import type { IDisposable } from 'xterm'; +import type { IDisposable } from '@xterm/xterm'; import { createTestContext, ITestContext, openTerminal, pollFor } from './TestUtils'; let ctx: ITestContext; diff --git a/test/playwright/Parser.test.ts b/test/playwright/Parser.test.ts index 42406270..b793b00e 100644 --- a/test/playwright/Parser.test.ts +++ b/test/playwright/Parser.test.ts @@ -4,7 +4,7 @@ */ import { test } from '@playwright/test'; import { deepStrictEqual, ok, strictEqual } from 'assert'; -import type { IDisposable } from 'xterm'; +import type { IDisposable } from '@xterm/xterm'; import { createTestContext, ITestContext, openTerminal, pollFor } from './TestUtils'; let ctx: ITestContext; diff --git a/test/playwright/Renderer.test.ts b/test/playwright/Renderer.test.ts index c5c1e94d..77bf7941 100644 --- a/test/playwright/Renderer.test.ts +++ b/test/playwright/Renderer.test.ts @@ -5,7 +5,7 @@ import { test } from '@playwright/test'; import { ITestContext, createTestContext, openTerminal } from './TestUtils'; -import { ISharedRendererTestContext, injectSharedRendererTests } from './SharedRendererTests'; +import { ISharedRendererTestContext, injectSharedRendererTestsStandalone, injectSharedRendererTests } from './SharedRendererTests'; let ctx: ITestContext; const ctxWrapper: ISharedRendererTestContext = { value: undefined } as any; @@ -18,5 +18,5 @@ test.afterAll(async () => await ctx.page.close()); test.describe('DOM Renderer Integration Tests', () => { injectSharedRendererTests(ctxWrapper); + injectSharedRendererTestsStandalone(ctxWrapper); }); - diff --git a/test/playwright/SharedRendererTests.ts b/test/playwright/SharedRendererTests.ts index e1e56bc9..377be163 100644 --- a/test/playwright/SharedRendererTests.ts +++ b/test/playwright/SharedRendererTests.ts @@ -5,8 +5,8 @@ import { IImage32, decodePng } from '@lunapaint/png-codec'; import { LocatorScreenshotOptions, test } from '@playwright/test'; -import { ITheme } from 'xterm'; -import { ITestContext, MaybeAsync, pollFor, pollForApproximate } from './TestUtils'; +import { ITheme } from '@xterm/xterm'; +import { ITestContext, MaybeAsync, openTerminal, pollFor, pollForApproximate } from './TestUtils'; export interface ISharedRendererTestContext { value: ITestContext; @@ -1092,6 +1092,30 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void }); test.describe('regression tests', () => { + test('#4736: inactive selection background should replace regular cell background color', async () => { + const theme: ITheme = { + selectionBackground: '#FF0000', + selectionInactiveBackground: '#0000FF' + }; + await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.value.proxy.writeln(' '); + await ctx.value.proxy.writeln(' O '); + await ctx.value.proxy.writeln(' '); + await ctx.value.proxy.focus(); + await ctx.value.proxy.selectAll(); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [255, 0, 0, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 2), [255, 0, 0, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 3), [255, 0, 0, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 3), [255, 0, 0, 255]); + await ctx.value.proxy.blur(); + frameDetails = undefined; + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 255, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0, 0, 255, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 2), [0, 0, 255, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 3), [0, 0, 255, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 3), [0, 0, 255, 255]); + }); test('#4758: multiple invisible text characters without SGR change should not be rendered', async () => { // Regression test: #4758 when multiple invisible characters are used await ctx.value.proxy.writeln(`■\x1b[8m■■`); @@ -1150,6 +1174,40 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void }); } +/** + * Injects shared renderer tests where it's required to re-initialize the terminal for each test. + * This is much slower than just calling `Terminal.reset` but testing some features needs this + * treatment. + */ +export function injectSharedRendererTestsStandalone(ctx: ISharedRendererTestContext): void { + test.beforeEach(async () => { + // Recreate terminal + await openTerminal(ctx.value); + ctx.value.page.evaluate(` + window.term.options.minimumContrastRatio = 1; + window.term.options.allowTransparency = false; + window.term.options.theme = undefined; + `); + // Clear the cached screenshot before each test + frameDetails = undefined; + }); + test.describe('regression tests', () => { + test('#4790: cursor should not be displayed before focusing', async () => { + const theme: ITheme = { + cursor: '#0000FF' + }; + await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]); + await ctx.value.proxy.focus(); + frameDetails = undefined; + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 255, 255]); + await ctx.value.proxy.blur(); + frameDetails = undefined; + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]); + }); + }); +} + /** * Gets the color of the pixel in the center of a cell. * @param ctx The test context. diff --git a/test/playwright/Terminal.test.ts b/test/playwright/Terminal.test.ts index 3a4e00f0..19f6eb31 100644 --- a/test/playwright/Terminal.test.ts +++ b/test/playwright/Terminal.test.ts @@ -22,7 +22,7 @@ test.describe('API Integration Tests', () => { }); test('Proposed API check', async () => { - await openTerminal(ctx, { allowProposedApi: false }); + await openTerminal(ctx, { allowProposedApi: false }, { loadUnicodeGraphemesAddon: false }); await ctx.page.evaluate(` try { window.term.markers; diff --git a/test/playwright/TestUtils.ts b/test/playwright/TestUtils.ts index 18a1f1bb..3e925f79 100644 --- a/test/playwright/TestUtils.ts +++ b/test/playwright/TestUtils.ts @@ -10,7 +10,7 @@ import type { IRenderService } from 'browser/services/Services'; import type { ICoreTerminal, IMarker } from 'common/Types'; import * as playwright from '@playwright/test'; import { PageFunction } from 'playwright-core/types/structs'; -import { IBuffer, IBufferCell, IBufferLine, IBufferNamespace, IBufferRange, IDecoration, IDecorationOptions, IModes, ITerminalInitOnlyOptions, ITerminalOptions, Terminal } from 'xterm'; +import { IBuffer, IBufferCell, IBufferLine, IBufferNamespace, IBufferRange, IDecoration, IDecorationOptions, IModes, ITerminalInitOnlyOptions, ITerminalOptions, Terminal } from '@xterm/xterm'; import { EventEmitter } from '../../out/common/EventEmitter'; export interface ITestContext { @@ -351,7 +351,7 @@ class TerminalCoreProxy { } } -export async function openTerminal(ctx: ITestContext, options: ITerminalOptions | ITerminalInitOnlyOptions = {}): Promise { +export async function openTerminal(ctx: ITestContext, options: ITerminalOptions | ITerminalInitOnlyOptions = {}, testOptions: { loadUnicodeGraphemesAddon: boolean } = { loadUnicodeGraphemesAddon: true }): Promise { await ctx.page.evaluate(` if ('term' in window) { try { @@ -366,6 +366,15 @@ export async function openTerminal(ctx: ITestContext, options: ITerminalOptions window.term = new window.Terminal(${JSON.stringify({ allowProposedApi: true, ...options })}); window.term.open(document.querySelector('#terminal-container')); `); + // HACK: This is a soft layer breaker that's temporarily included until unicode graphemes have + // more complete integration tests. See https://github.com/xtermjs/xterm.js/pull/4519#discussion_r1285234453 + if (testOptions.loadUnicodeGraphemesAddon) { + await ctx.page.evaluate(` + window.unicode = new UnicodeGraphemesAddon(); + window.term.loadAddon(window.unicode); + window.term.unicode.activeVersion = '15-graphemes'; + `); + } await ctx.page.waitForSelector('.xterm-rows'); ctx.termHandle = await ctx.page.evaluateHandle('window.term'); await ctx.proxy.initTerm(); diff --git a/tsconfig.all.json b/tsconfig.all.json index 79ced9cc..e2028479 100644 --- a/tsconfig.all.json +++ b/tsconfig.all.json @@ -7,15 +7,16 @@ { "path": "./test/api" }, { "path": "./test/benchmark" }, { "path": "./test/playwright" }, - { "path": "./addons/xterm-addon-attach" }, - { "path": "./addons/xterm-addon-canvas" }, - { "path": "./addons/xterm-addon-fit" }, - { "path": "./addons/xterm-addon-image" }, - { "path": "./addons/xterm-addon-ligatures" }, - { "path": "./addons/xterm-addon-search" }, - { "path": "./addons/xterm-addon-serialize" }, - { "path": "./addons/xterm-addon-unicode11" }, - { "path": "./addons/xterm-addon-web-links" }, - { "path": "./addons/xterm-addon-webgl" } + { "path": "./addons/addon-attach" }, + { "path": "./addons/addon-canvas" }, + { "path": "./addons/addon-fit" }, + { "path": "./addons/addon-image" }, + { "path": "./addons/addon-ligatures" }, + { "path": "./addons/addon-search" }, + { "path": "./addons/addon-serialize" }, + { "path": "./addons/addon-unicode11" }, + { "path": "./addons/addon-unicode-graphemes" }, + { "path": "./addons/addon-web-links" }, + { "path": "./addons/addon-webgl" } ] } diff --git a/typings/xterm-headless.d.ts b/typings/xterm-headless.d.ts index 688e2eda..f8cef382 100644 --- a/typings/xterm-headless.d.ts +++ b/typings/xterm-headless.d.ts @@ -7,7 +7,7 @@ * to be stable and consumed by external programs. */ -declare module 'xterm-headless' { +declare module '@xterm/headless' { /** * A string representing log level. */ @@ -1240,6 +1240,7 @@ declare module 'xterm-headless' { * Unicode version dependent wcwidth implementation. */ wcwidth(codepoint: number): 0 | 1 | 2; + charProperties(codepoint: number, preceding: number): number; } /** diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 44402b84..70b0c6d7 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -9,7 +9,7 @@ /// -declare module 'xterm' { +declare module '@xterm/xterm' { /** * A string or number representing text font weight. */ @@ -89,6 +89,17 @@ declare module 'xterm' { */ disableStdin?: boolean; + /** + * A {@link Document} to use instead of the one that xterm.js was attached + * to. The purpose of this is to improve support in multi-window + * applications where HTML elements may be references across multiple + * windows which can cause problems with `instanceof`. + * + * The type is `any` because using `Document` can cause TS to have + * performance/compiler problems. + */ + documentOverride?: any | null; + /** * Whether to draw bold text in bright colors. The default is true. */ @@ -962,7 +973,8 @@ declare module 'xterm' { resize(columns: number, rows: number): void; /** - * Opens the terminal within an element. + * Opens the terminal within an element. This should also be called if the + * xterm.js element ever changes browser window. * @param parent The element to create the terminal within. This element * must be visible (have dimensions) when `open` is called as several DOM- * based measurements need to be performed when this function is called. @@ -1773,6 +1785,7 @@ declare module 'xterm' { * Unicode version dependent wcwidth implementation. */ wcwidth(codepoint: number): 0 | 1 | 2; + charProperties(codepoint: number, preceding: number): number; } /** diff --git a/webpack.config.js b/webpack.config.js index ca7c059a..3769bfb0 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -39,8 +39,10 @@ const config = { output: { filename: 'xterm.js', path: path.resolve('./lib'), - libraryTarget: 'umd' + libraryTarget: 'umd', + // Force usage of globalThis instead of global / self. (This is cross-env compatible) + globalObject: 'globalThis', }, - mode: 'production' + mode: 'production', }; module.exports = config; diff --git a/yarn.lock b/yarn.lock index 50a4fb84..a0cb39da 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15,6 +15,14 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" +"@babel/code-frame@^7.22.13": + version "7.22.13" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" + integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== + dependencies: + "@babel/highlight" "^7.22.13" + chalk "^2.4.2" + "@babel/code-frame@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.5.tgz#234d98e1551960604f1246e6475891a570ad5658" @@ -48,7 +56,7 @@ json5 "^2.2.2" semver "^6.3.1" -"@babel/generator@^7.22.7", "@babel/generator@^7.22.9": +"@babel/generator@^7.22.9": version "7.22.9" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.9.tgz#572ecfa7a31002fa1de2a9d91621fd895da8493d" integrity sha512-KtLMbmicyuK2Ak/FTCJVbDnkN1SlT8/kceFTiuDiiRUUSMnHMidxSCdG4ndkTOHHpoomWe/4xkvHkEOncwjYIw== @@ -58,6 +66,16 @@ "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" +"@babel/generator@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420" + integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g== + dependencies: + "@babel/types" "^7.23.0" + "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" + jsesc "^2.5.1" + "@babel/helper-compilation-targets@^7.22.9": version "7.22.9" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.9.tgz#f9d0a7aaaa7cd32a3f31c9316a69f5a9bcacb892" @@ -69,18 +87,23 @@ lru-cache "^5.1.1" semver "^6.3.1" +"@babel/helper-environment-visitor@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" + integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== + "@babel/helper-environment-visitor@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz#f06dd41b7c1f44e1f8da6c4055b41ab3a09a7e98" integrity sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q== -"@babel/helper-function-name@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz#ede300828905bb15e582c037162f99d5183af1be" - integrity sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ== +"@babel/helper-function-name@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" + integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== dependencies: - "@babel/template" "^7.22.5" - "@babel/types" "^7.22.5" + "@babel/template" "^7.22.15" + "@babel/types" "^7.23.0" "@babel/helper-hoist-variables@^7.22.5": version "7.22.5" @@ -126,6 +149,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + "@babel/helper-validator-identifier@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz#9544ef6a33999343c8740fa51350f30eeaaaf193" @@ -145,6 +173,15 @@ "@babel/traverse" "^7.22.6" "@babel/types" "^7.22.5" +"@babel/highlight@^7.22.13": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54" + integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + "@babel/highlight@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.5.tgz#aa6c05c5407a67ebce408162b7ede789b4d22031" @@ -154,6 +191,11 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@babel/parser@^7.22.15", "@babel/parser@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719" + integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw== + "@babel/parser@^7.22.5", "@babel/parser@^7.22.7": version "7.22.7" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.7.tgz#df8cf085ce92ddbdbf668a7f186ce848c9036cae" @@ -166,6 +208,15 @@ dependencies: regenerator-runtime "^0.13.11" +"@babel/template@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" + integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== + dependencies: + "@babel/code-frame" "^7.22.13" + "@babel/parser" "^7.22.15" + "@babel/types" "^7.22.15" + "@babel/template@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.5.tgz#0c8c4d944509875849bd0344ff0050756eefc6ec" @@ -176,21 +227,30 @@ "@babel/types" "^7.22.5" "@babel/traverse@^7.22.6", "@babel/traverse@^7.22.8": - version "7.22.8" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.8.tgz#4d4451d31bc34efeae01eac222b514a77aa4000e" - integrity sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw== + version "7.23.2" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8" + integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw== dependencies: - "@babel/code-frame" "^7.22.5" - "@babel/generator" "^7.22.7" - "@babel/helper-environment-visitor" "^7.22.5" - "@babel/helper-function-name" "^7.22.5" + "@babel/code-frame" "^7.22.13" + "@babel/generator" "^7.23.0" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" "@babel/helper-hoist-variables" "^7.22.5" "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/parser" "^7.22.7" - "@babel/types" "^7.22.5" + "@babel/parser" "^7.23.0" + "@babel/types" "^7.23.0" debug "^4.1.0" globals "^11.1.0" +"@babel/types@^7.22.15", "@babel/types@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb" + integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg== + dependencies: + "@babel/helper-string-parser" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.20" + to-fast-properties "^2.0.0" + "@babel/types@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.5.tgz#cd93eeaab025880a3a47ec881f4b096a5b786fbe" @@ -1145,7 +1205,7 @@ chai@^4.3.4: pathval "^1.1.1" type-detect "^4.0.5" -chalk@^2.0.0: +chalk@^2.0.0, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -1963,9 +2023,9 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5: integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== get-func-name@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" - integrity sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig== + version "2.0.2" + resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" + integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1: version "1.2.1" @@ -2813,10 +2873,10 @@ mustache@^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.17.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb" - integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ== +nan@^2.17.0: + version "2.18.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.18.0.tgz#26a6faae7ffbeb293a39660e88a76b82e30b7554" + integrity sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w== nanoid@3.3.3: version "3.3.3" @@ -2850,12 +2910,12 @@ node-preload@^0.2.1: dependencies: process-on-spawn "^1.0.0" -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== +node-pty@1.1.0-beta5: + version "1.1.0-beta5" + resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.1.0-beta5.tgz#364386b7058a93070234064f13164ec1ef914993" + integrity sha512-j3QdgFHnLY0JWxztrvM3g67RaQLOGvytv+C6mFu0PqD+JILlzqfwuoyqRqVxdZZjoOTUXPfSRj1qPVCaCH+eOw== dependencies: - nan "^2.14.0" + nan "^2.17.0" node-releases@^2.0.12: version "2.0.13"